[Admin_공지사항] 다중 이미지 삽입 / 이미지 테이블 조인 / 공지사항 등록 / 공지사항 목록 (4)

2024. 1. 26. 21:30Project/ShoppingMall

반응형

 

🟢 Entity

▪️  ImageEntity

공지테이블과 조인해주기

여러 개의 이미지를 1개의 공지글에 첨부 가능 → @ManyToOne

외래키 설정    noticeId

더보기
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table(name = "image")
@SequenceGenerator(
    name = "image_SEQ",
    sequenceName = "image_SEQ",
    initialValue = 1,
    allocationSize = 1
)

public class ImageEntity extends BaseEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator ="image_SEQ")
  private Integer imageId;

  @Column(name = "imageFile")
  private String imageFile;

  //대표이미지=0, 서브이미지=1, 상세이미지=2
  @Column(name = "imageType")
  private Integer imageType;

  //외래키 (productId) - 상품테이블과 조인 (여러 개의 이미지가 하나의 상품에 매핑)
  @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
  @JoinColumn(name = "productId")
  private ProductEntity productEntity;

  //외래키 (noticeId) - 공지테이블과 조인 (여러 개의 이미지가 하나의 공지 글에 매핑)
  @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
  @JoinColumn(name = "noticeId")
  private NoticeEntity noticeEntity;

}
  //외래키 (noticeId) - 공지테이블과 조인 (여러 개의 이미지가 하나의 공지 글에 매핑)
  @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
  @JoinColumn(name = "noticeId")
  private NoticeEntity noticeEntity;

 

▪️  NoticeEntity

이미지테이블과 조인해주기

1개의 공지글에 여러 개의 이미지 첨부 가능 → @OneToMany

 

mappedBy = "noticeEntity" : 양방향 매핑을 의미. 관계의 주인(Owner)이 되는 쪽을 지정

                                              ImageEntity 클래스 내에서 noticeEntity 필드를 사용하여 매핑

 

cascade = CascadeType.ALL : 모든 작업에 대해 Cascade를 수행하도록 설정

                                                  공지사항 엔터티가 삭제되면 관련된 모든 이미지 엔터티도 삭제

 

private List<ImageEntity> noticeImages = new ArrayList<>()

공지사항 엔터티에 대한 여러 이미지 엔터티를 담는 컬렉션

더보기
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table(name = "notice")
@SequenceGenerator(
    name = "notice_SEQ",
    sequenceName = "notice_SEQ",
    initialValue = 1,
    allocationSize = 1)
public class NoticeEntity extends BaseEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "notice_SEQ")
  private Integer noticeId;

  //공지테이블과 매핑
  //공지 게시글 1개에 여러 개의 이미지
  @OneToMany(mappedBy = "noticeEntity", cascade = CascadeType.ALL)
  private List<ImageEntity> noticeImages = new ArrayList<>();

  @Column(name="noticeRole")
  @Enumerated(EnumType.STRING)
  private NoticeRole noticeRole;

  @Column(name="noticeTitle",nullable = false,length = 50)
  private String noticeTitle;          //제목

  @Column(name="noticeWriter", nullable = false,length = 20)
  private String noticeWriter;         //작성자

  @Column(name="noticeContent", nullable = false,length = 2000)
  private String noticeContent;        //공지내용

  @Column(name = "noticeView")
  private Integer noticeView;

}
  //공지테이블과 매핑
  //공지 게시글 1개에 여러 개의 이미지
  @OneToMany(mappedBy = "noticeEntity", cascade = CascadeType.ALL)
  private List<ImageEntity> noticeImages = new ArrayList<>();
CascadeType.ALL: 모든 작업에 대해 Cascade를 수행
                 저장, 업데이트, 삭제 등 모든 상태 변화가 관련된 엔터티에 전파

CascadeType.PERSIST: 영속성 컨텍스트에 엔터티를 추가할 때만 Cascade를 수행
                     저장(INSERT) 작업에 대해서만 전파

CascadeType.MERGE: 병합 작업에 대해서만 Cascade를 수행
                   엔터티의 상태를 업데이트할 때만 전파

CascadeType.REMOVE: 삭제 작업에 대해서만 Cascade를 수행
                    부모 엔터티를 삭제할 때 자식 엔터티도 함께 삭제

CascadeType.REFRESH: 엔터티를 새로고침할 때 Cascade를 수행

 

 

 

🟢 DTO

▪️  ImageDTO

공지사항 외래키 추가 → noticeId

더보기
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ImageDTO {

  private Integer imageId;          // 이미지 고유번호

  private String imageFile;         // 이미지 파일이름

  private Integer imageType;         // 이미지 종류 (대표이미지=0, 서브이미지=1, 상세이미지=2)

  private Integer productId;        // 상품번호(외래키)

  private Integer noticeId;         // 공지번호(외래키)

}
private Integer noticeId;         // 공지번호(외래키)

 

▪️  ImageDTO

공지사항 외래키 추가 → noticeId

더보기
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ImageDTO {

  private Integer imageId;          // 이미지 고유번호

  private String imageFile;         // 이미지 파일이름

  private Integer imageType;         // 이미지 종류 (대표이미지=0, 서브이미지=1, 상세이미지=2)

  private Integer productId;        // 상품번호(외래키)

  private Integer noticeId;         // 공지번호(외래키)

}
private Integer noticeId;         // 공지번호(외래키)

 

 

🟢 Service

▪️  Config → WebMvcConfig

더보기
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  //application에서 사용자 변수 읽어오기
  @Value(("${uploadPath}"))
  String uploadPath;


  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/images/**")
        .addResourceLocations(uploadPath);    //자원의 위치
  }

}

 

▪️  FileService

더보기
@Service
@RequiredArgsConstructor
public class FileService {

  //파일을 저장할 경로
  @Value("${imgLocation}")
  private String imgLocation;


  //저장할 경로,파일명,데이터 값
  public String uploadFile(String originalFileName, byte[] filedata) throws Exception {

    UUID uuid = UUID.randomUUID();    //문자열 생성
    String extendsion = originalFileName.substring(originalFileName.lastIndexOf("."));    //문자열 분리
    String saveFileName = uuid.toString()+extendsion;    //새로운 파일명
    String uploadFullurl = imgLocation+saveFileName;      //저장위치 및 파일명

    //하드디스크에 파일 저장
    FileOutputStream fos = new FileOutputStream(uploadFullurl);
    fos.write(filedata);
    fos.close();

    return saveFileName;    //데이터베이스에 저장할 파일명
  }


  // 파일 삭제 (상품을 수정시 기존 파일을 삭제하고 새로운 파일을 저장)
  public void deleteFile(String fileName) throws Exception {

    String deleteFileName = imgLocation + fileName;

    File deleteFile = new File(deleteFileName);
    if (deleteFile.exists()) {
      deleteFile.delete();
    }
  }
}

 

▪️  ImageService

이미지 파일을 업로드하고, 해당 이미지 정보를 데이터베이스의 이미지 테이블(ImageEntity)에 저장

@Service
@RequiredArgsConstructor
@Transactional
public class ImageService {

  //파일업로드
  private final FileService fileService;
  private final ModelMapper modelMapper = new ModelMapper();
  private final ImageRepository imageRepository;


  // 공지테이블에 이미지 삽입 메서드
public void noticeImage(ImageDTO imageDTO, NoticeEntity noticeEntity, MultipartFile imageFile) throws Exception {

    // 원본 파일명과 새로운 파일명 초기화
    String originalFileName = imageFile.getOriginalFilename();  // 저장 할 파일명
    String newFileName = "";                                      // UUID로 생성된 새로운 파일명

    // 파일이 존재할 경우에만 업로드 진행
    if (originalFileName != null && originalFileName.length() > 0) {
        // 파일 업로드
        newFileName = fileService.uploadFile(originalFileName, imageFile.getBytes());
    }

    // 새로운 파일명으로 변경된 이미지 파일명 설정
    imageDTO.setImageFile(newFileName);

    // DTO를 Entity로 변환
    ImageEntity imageEntity = modelMapper.map(imageDTO, ImageEntity.class);

    // 공지사항 테이블과 이미지 테이블 간의 관계 설정
    imageEntity.setNoticeEntity(noticeEntity);

    try {
        // 이미지 엔터티를 저장
        imageRepository.save(imageEntity);
    } catch (Exception e) {
        // 예외 처리 (생략된 부분)
    }
    
}

 

▪️  NoticeService

글 목록을 불러올 때 이미지 정보도 함께 로드되어야함

그렇지 않으면 ModelMapperMultipartFile에 대한 매핑 오류를 발생

 

    //이미지 관리 DTO
    private List<ImageDTO> imageDTOs;

더보기
@Service
@RequiredArgsConstructor
@Transactional
public class NoticeService {

  private final FileService fileService;
  private final ImageService imageService;

  private final NoticeRepository noticeRepository;
  private final CommentRepository commentRepository;

  private final ModelMapper modelMapper = new ModelMapper();

  //공지등록
  public void insert(NoticeDTO noticeDTO, List<MultipartFile> imageFiles) throws Exception {

    //이미지 저장
    //데이터베이스에 저장 할 이미지, 업로드한 실제 이미지
    List<ImageDTO> dataDTO = noticeDTO.getImageDTOs();
    List<MultipartFile> images = noticeDTO.getImages();

    //공지사항 등록
    NoticeEntity data = modelMapper.map(noticeDTO, NoticeEntity.class);
    NoticeEntity noticeEntity = noticeRepository.save(data);

    //공지에 이미지 저장
    int index = 0;

    for(MultipartFile file : images) {
      ImageDTO imageDTO = dataDTO.get(index);

      try {
        //이미지 등록
        imageService.noticeImage(imageDTO, noticeEntity, file);
      } catch (IOException e) {
        //
      }
      index++;
    }


  }


  //공지목록 (관리자)
  public Page<NoticeDTO> findAll(String type, String keyword, String noticeRole, Pageable page) throws  Exception {

    int currentpage = page.getPageNumber()-1;
    int blockLimit = 10;

    Pageable pageable = PageRequest.of(currentpage, blockLimit, Sort.by(Sort.Direction.DESC, "noticeId"));

    Page<NoticeEntity> noticeEntityPage;

    if("tc".equals(type) && keyword != null && !keyword.isEmpty()) {
      noticeEntityPage = noticeRepository.findByNoticeTitleOrNoticeContent(keyword, pageable);
    } else if("r".equals(type) && noticeRole != null  && !noticeRole.isEmpty()) {
      noticeEntityPage = noticeRepository.findByNoticeRole(NoticeRole.valueOf(noticeRole), pageable);
    } else {
      noticeEntityPage = noticeRepository.findAll(pageable);
    }

    return noticeEntityPage.map(noticeEntity -> NoticeDTO.builder()
        .noticeId(noticeEntity.getNoticeId())
        .noticeTitle(noticeEntity.getNoticeTitle())
        .noticeWriter(noticeEntity.getNoticeWriter())
        .noticeRole(noticeEntity.getNoticeRole())
        .noticeContent(noticeEntity.getNoticeContent())
        .noticeView(noticeEntity.getNoticeView())
        .imageDTOs(noticeEntity.getNoticeImages().stream()
            .map(imageEntity -> modelMapper.map(imageEntity, ImageDTO.class))
            .collect(Collectors.toList()))
        .reDate(noticeEntity.getReDate())
        .modDate(noticeEntity.getModDate())
        .build()
    );

  }

}

 

▪️  메소드 분리해서 작성하는 방법

@Service
@RequiredArgsConstructor
@Transactional
public class NoticeService {

  private final FileService fileService;
  private final ImageService imageService;

  private final NoticeRepository noticeRepository;
  private final CommentRepository commentRepository;

  private final ModelMapper modelMapper = new ModelMapper();

  //공지등록
  public void insert(NoticeDTO noticeDTO, List<MultipartFile> imageFiles) throws Exception {

    //이미지 저장
    //데이터베이스에 저장 할 이미지, 업로드한 실제 이미지
    List<ImageDTO> dataDTO = noticeDTO.getImageDTOs();
    List<MultipartFile> images = noticeDTO.getImages();

    //공지사항 등록
    NoticeEntity data = modelMapper.map(noticeDTO, NoticeEntity.class);
    NoticeEntity noticeEntity = noticeRepository.save(data);

    //공지에 이미지 저장
    int index = 0;

    for(MultipartFile file : images) {
      ImageDTO imageDTO = dataDTO.get(index);

      try {
        //이미지 등록
        imageService.noticeImage(imageDTO, noticeEntity, file);
      } catch (IOException e) {
        //
      }
      index++;
    }


  }


  //이미지테이블 전달
  private List<ImageDTO> mapImagesToDTOs(List<ImageEntity> imagesEntities) {

    //이미지가 없는 경우, 빈 리스트를 반환
    if (imagesEntities == null) {
      return Collections.emptyList();
    }

    //이미지 목록이 null이 아닌 경우
    //이미지 목록을 스트림으로 변환, map 함수를 사용하여 각 ImageEntity를 ImageDTO로 매핑
    //collect(Collectors.toList())를 사용하여 스트림의 결과를 리스트로 수집
    return imagesEntities.stream()
        .map(imageEntity -> modelMapper.map(imageEntity, ImageDTO.class))
        .collect(Collectors.toList());
  }




  //공지목록 (관리자)
  public Page<NoticeDTO> findAll(String type, String keyword, String noticeRole, Pageable page) throws  Exception {

    int currentpage = page.getPageNumber()-1;
    int blockLimit = 10;

    Pageable pageable = PageRequest.of(currentpage, blockLimit, Sort.by(Sort.Direction.DESC, "noticeId"));

    Page<NoticeEntity> noticeEntityPage;

    if("tc".equals(type) && keyword != null && !keyword.isEmpty()) {
      noticeEntityPage = noticeRepository.findByNoticeTitleOrNoticeContent(keyword, pageable);
    } else if("r".equals(type) && noticeRole != null  && !noticeRole.isEmpty()) {
      noticeEntityPage = noticeRepository.findByNoticeRole(NoticeRole.valueOf(noticeRole), pageable);
    } else {
      noticeEntityPage = noticeRepository.findAll(pageable);
    }


    List<NoticeDTO> noticeDTOList = new ArrayList<>();

    for(NoticeEntity noticeEntity : noticeEntityPage) {
      NoticeDTO noticeDTO = NoticeDTO.builder()
          .noticeId(noticeEntity.getNoticeId())
          .noticeTitle(noticeEntity.getNoticeTitle())
          .noticeWriter(noticeEntity.getNoticeWriter())
          .noticeRole(noticeEntity.getNoticeRole())
          .noticeContent(noticeEntity.getNoticeContent())
          .noticeView(noticeEntity.getNoticeView())
          .imageDTOs(mapImagesToDTOs(noticeEntity.getNoticeImages()))
          .reDate(noticeEntity.getReDate())
          .modDate(noticeEntity.getModDate())
          .build();

      noticeDTOList.add(noticeDTO);
    }

    return new PageImpl<>(noticeDTOList, pageable, noticeEntityPage.getTotalElements());

  }

}

 

 

 

🟢 Contoroller

▪️  NoticeController

@RequestParam : HTTP 요청에서 특정한 이름의 파라미터를 찾아 해당 파라미터 값을 메서드 파라미터에 매핑하는 역할

 

List<MultipartFile> imageFiles : 여러 개의 파일을 받기 위한 자료구조

                                                       클라이언트에서 전송한 파일들이 이 리스트에 담겨옴

더보기
@Controller
@Transactional
@RequiredArgsConstructor
public class NoticeController {

  private final NoticeService noticeService;
  private final CommentService commentService;

  //관리자
  //공지등록폼
  @GetMapping("/admin_noticeinsert")
  public String noticeForm(Model model) throws Exception {

    NoticeDTO noticeDTO = new NoticeDTO();

    model.addAttribute("noticeDTO", noticeDTO);
    model.addAttribute("noticeRole", NoticeRole.values());

    return "admin/notice_insert";
  }
  
  //공지등록처리 (이미지 등록 처리)
  @PostMapping("/admin_noticeinsert")
  public String noticeProc(@Valid NoticeDTO noticeDTO,
                           BindingResult bindingResult,
                           @RequestParam("images") List<MultipartFile> imageFiles,
                           Model model) throws Exception {

    if(bindingResult.hasErrors()) {
      model.addAttribute("noticeRole", NoticeRole.values());
      return "admin/notice_insert";
    }

    try {
      noticeService.insert(noticeDTO, imageFiles);
      model.addAttribute("success", "공지 등록이 완료되었습니다.");
      model.addAttribute("searchUrl", "/admin_noticelist");
      return "message";
    } catch (Exception e) {
      model.addAttribute("error", "공지 등록 실패! 다시 등록해주세요.");
      model.addAttribute("searchUrl", "/admin_noticeinsert");
      return "message";
    }

  }

  
  //공지목록
  @GetMapping("/admin_noticelist")
  public String noticeList(@RequestParam(value = "type", defaultValue = "") String type,
                           @RequestParam(value = "keyword", defaultValue = "") String keyword,
                           @RequestParam(value = "noticeRole", defaultValue = "") String noticeRole,
                           @PageableDefault(page = 1) Pageable pageable,
                           Model model) throws Exception {

    Page<NoticeDTO> noticeDTOS = noticeService.findAll(type, keyword, noticeRole, pageable);

    int blockLimit = 5;
    int startPage = (((int)(Math.ceil((double) pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1;
    int endPage = Math.min(startPage + blockLimit - 1, noticeDTOS.getTotalPages());

    int prevPage = noticeDTOS.getNumber();
    int currentPage = noticeDTOS.getNumber() + 1;
    int nextPage = noticeDTOS.getNumber() + 2;
    int lastPage = noticeDTOS.getTotalPages();

    model.addAttribute("noticeDTOS", noticeDTOS);

    model.addAttribute("startPage", startPage);
    model.addAttribute("endPage", endPage);
    model.addAttribute("prevPage", prevPage);
    model.addAttribute("currentPage", currentPage);
    model.addAttribute("nextPage", nextPage);
    model.addAttribute("lastPage", lastPage);

    model.addAttribute("type", type);
    model.addAttribute("keyword", keyword);

    return "/admin/notice_list";
  }

}

▪️  Alert창

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Alert</title>
</head>

<body>

    <script th:inline="javascript">
        var message = [[${message}]];
        var error = [[${error}]];
        var success = [[${success}]];

        console.log("Message:", message);
        console.log("Error:", error);
        console.log("Success", success);

        var confirmationMessage = message || error || success;

        if (confirmationMessage) {
            var userConfirmed = confirm(confirmationMessage);

            if (userConfirmed) {
                console.log("확인되었습니다.");
                location.replace([[${searchUrl}]]);
            } else {
                console.log("취소되었습니다.");
                location.replace(history.go(-1));
            }
        }
    </script>

</body>
</html>

 


🟢 insert

▪️  admin - Insert

더보기
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/adminlayout}">

<head>
    <meta charset="utf-8">
    <title>ADMIN - Notice Insert</title>
</head>

<body>
<div layout:fragment="content">
    <div class="main-wrapper wrapper-2">

        <div class="breadcrumb-area breadcrumb-padding-8">
            <div class="container">
                <div class="breadcrumb-content text-center">
                    <div class="breadcrumb-title">
                        <h2>Notice</h2>
                    </div>
                </div>
            </div>
        </div>

        <div class="notice-area bg-white pb-130">
            <div class="container">
                <div class="notice-info-wrap">
                    <form th:action="@{/admin_noticeinsert}" method="post" enctype="multipart/form-data" th:object="${noticeDTO}">
                        <input type="hidden" name="noticeView" value="0">
                        <table class="table notice-table">
                            <tbody>
                            <tr>
                                <th scope="row" class="notice-padding">제목</th>
                                <td class="notice-info">
                                    <input type="text" name="noticeTitle" id="noticeTitle">
                                    <span class="text-danger" th:if="${#fields.hasErrors('noticeTitle')}" th:errors="*{noticeTitle}"></span>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row" class="notice-padding">작성자</th>
                                <td class="notice-info">
                                    <input type="text" name="noticeWriter" id="noticeWriter">
                                    <span class="text-danger" th:if="${#fields.hasErrors('noticeWriter')}" th:errors="*{noticeWriter}"></span>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row" class="notice-padding">공지유형</th>
                                <td>
                                    <div class="sidebar-widget update">
                                        <div class="sidebar-archive-wrap">
                                            <select name="noticeRole" id="noticeRole">
                                                <option value="" th:selected="${noticeRole == ''}"> == 선택 == </option>
                                                <option value="DELIVERY" th:selected="${noticeRole=='DELIVERY'}">배송공지</option>
                                                <option value="PRODUCT" th:selected="${noticeRole=='PRODUCT'}">상품공지</option>
                                                <option value="TIP" th:selected="${noticeRole=='TIP'}">청소꿀팁</option>
                                                <option value="EVENT" th:selected="${noticeRole=='EVENT'}">이벤트</option>
                                                <option value="POINT" th:selected="${noticeRole=='POINT'}">적립금</option>
                                                <option value="ETC" th:selected="${noticeRole=='ETC'}">기타</option>
                                            </select>
                                            <span class="text-danger" th:if="${#fields.hasErrors('noticeRole')}" th:errors="*{noticeRole}"></span>
                                        </div>
                                    </div>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row" class="notice-padding">공지내용</th>
                                <td class="notice-info">
                                    <textarea rows="15" name="noticeContent" id="noticeContent"></textarea>
                                    <span class="text-danger" th:if="${#fields.hasErrors('noticeContent')}" th:errors="*{noticeContent}"></span>
                                </td>
                            </tr>

                            <tr>
                                <th scope="row" class="notice-padding" id="imageDTOs[3]">이미지 1</th>
                                <td class="notice-info">
                                    <input type="hidden" name="imageDTOs[3].imageType" value="3">
                                    <input type="file" name="images" onchange="previewImage(this, 'previewImg3')">
                                    <img id="previewImg3" class="img-thumbnail" style="max-width: 100px; max-height: 100px;">
                                </td>
                            </tr>

                            <tr>
                                <th scope="row" class="notice-padding" id="imageDTOs[1]">이미지 2</th>
                                <td class="notice-info">
                                    <input type="hidden" name="imageDTOs[1].imageType" value="1">
                                    <input type="file" name="images" onchange="previewImage(this, 'previewImg1')">
                                    <img id="previewImg1" class="img-thumbnail" style="max-width: 100px; max-height: 100px;">
                                </td>
                            </tr>

                            <script>
                                //매개변수 : 파일 입력란과 미리보기 이미지의 ID
                                function previewImage(input, previewId) {
                                    var preview = document.getElementById(previewId);

                                    //파일 입력란에서 선택된 파일 객체를 가져오기
                                    var file = input.files[0];

                                    //파일을 읽어오는데 사용되는 FileReader 객체를 생성
                                    var reader = new FileReader();

                                    //파일이 로드될 때 실행되는 이벤트 핸들러를 정의
                                    reader.onload = function (e) {
                                        preview.src = e.target.result;
                                    };

                                    //파일이 존재하는 경우에만 readAsDataURL 메서드를 사용하여
                                    //파일을 읽어와 데이터 URL로 변환
                                    if (file) {
                                        reader.readAsDataURL(file);
                                    }
                                }

                                //이벤트 핸들러를 사용하여 문서가 로드되면 아래의 코드 블록 실행
                                $(document).ready(function () {

                                    //"images"로 시작하는 모든 파일 입력란에 대해 change 이벤트 핸들러를 추가
                                    $('input[name^="images"]').change(function () {

                                        //현재 파일 입력란의 ID에서 'fileInput'을 제거하여 인덱스를 추출
                                        var index = $(this).attr('id').replace('fileInput', '');

                                        //파일 입력란과 미리보기 이미지의 ID를 전달하여 previewImage 함수를 호출
                                        previewImage(this, 'previewImg' + index);
                                    });
                                });
                            </script>
                            </tbody>
                        </table>
                        <div class="row">
                            <div class="col d-flex justify-content-center">
                                <div class="notice-button-container">
                                    <div class="notice-content">
                                        <div class="notice-btn-date-wrap list-btn">
                                            <div class="notice-btn">
                                                <button type="submit" class="btn btn-lg">등록</button>
                                                <button type="reset" class="btn btn-lg">다시</button>
                                                <button type="button" class="btn btn-lg" th:onclick="|location.href='@{/admin_noticelist}'|">취소</button>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

 

 

 ▪️ 이미지 관리 DTO

private List<ImageDTO> imageDTOs;

▪️ 이미지 종류
private Integer imageType;

                            <tr>
                                <th scope="row" class="notice-padding" id="imageDTOs[3]">이미지 1</th>
                                <td class="notice-info">
                                    <input type="hidden" name="imageDTOs[3].imageType" value="3">
                                    <input type="file" name="images" onchange="previewImage(this, 'previewImg3')">
                                    <img id="previewImg3" class="img-thumbnail" style="max-width: 100px; max-height: 100px;">
                                </td>
                            </tr>

 

▪️ 삽입된 이미지 미리보기

                            <script>
                                //매개변수 : 파일 입력란과 미리보기 이미지의 ID
                                function previewImage(input, previewId) {
                                    var preview = document.getElementById(previewId);

                                    //파일 입력란에서 선택된 파일 객체를 가져오기
                                    var file = input.files[0];

                                    //파일을 읽어오는데 사용되는 FileReader 객체를 생성
                                    var reader = new FileReader();

                                    //파일이 로드될 때 실행되는 이벤트 핸들러를 정의
                                    reader.onload = function (e) {
                                        preview.src = e.target.result;
                                    };

                                    //파일이 존재하는 경우에만 readAsDataURL 메서드를 사용하여
                                    //파일을 읽어와 데이터 URL로 변환
                                    if (file) {
                                        reader.readAsDataURL(file);
                                    }
                                }

                                //이벤트 핸들러를 사용하여 문서가 로드되면 아래의 코드 블록 실행
                                $(document).ready(function () {

                                    //"images"로 시작하는 모든 파일 입력란에 대해 change 이벤트 핸들러를 추가
                                    $('input[name^="images"]').change(function () {

                                        //현재 파일 입력란의 ID에서 'fileInput'을 제거하여 인덱스를 추출
                                        var index = $(this).attr('id').replace('fileInput', '');

                                        //파일 입력란과 미리보기 이미지의 ID를 전달하여 previewImage 함수를 호출
                                        previewImage(this, 'previewImg' + index);
                                    });
                                });
                            </script>

 

 

 

반응형