지난번 글의 회원관리-회원코드 수정에 이어 이번글에서는 회원탈퇴 처리 및 공지사항에 대한 설명과 기능구현을 해보겠습니다. 부족해도 좋게 봐주세요 :)
Html (회원 탈퇴 여부)
<td>
<span th:text="${member.withdrawalRequest == 0 ? 'X' : 'O'}"></span>
<button class="delete-btn" type="button" th:alt="${member.withdrawalRequest}"
th:data-memberid="${member.id}" th:data-withdrawalRequest="${member.withdrawalRequest}"
onclick="deleteMember(this)">회원탈퇴</button>
</td>
- <span th:text="${member.withdrawalRequest == 0 ? 'X' : 'O'}"></span> : 해당 행의 회원의 탈퇴 요청 상태를 withdrawalRequest 값에 따라 표시.(삼항 연산자를 이용해서 0이면 X , 0이 아니면 O를 출력)
- 버튼 클릭 시 deleteMember(this) 함수가 호출. th:data-memberid="${member.id}" 회원ID를 동적으로 할당, th:data-withdrawalRequest="${member.withdrawalRequest}" 탈퇴 요청 상태를 전달.
JavaScript
// 회원 탈퇴 처리함수
function deleteMember(button) {
const memberId = button.getAttribute('data-memberid');
const withdrawalRequest = button.getAttribute('data-withdrawalRequest');
if(withdrawalRequest === '0') {
swal.fire({
title: '탈퇴 처리 불가',
text: '탈퇴 요청을 하지 않은 회원입니다.',
icon: 'warning',
confirmButtonText: '확인'
});
return;
}
swal.fire({
title: '정말로 탈퇴 처리 하시겠습니까?',
text: '이 작업은 되돌릴 수 없습니다.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: '회원 탈퇴',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed) {
$.ajax({
url: '/delete-member-admin',
type: 'POST',
data: { id: memberId },
success: function(response) {
swal.fire({
title: '탈퇴 처리 성공',
text: response,
icon: 'success'
}).then(() => {
location.reload();
});
},
error: function(xhr) {
console.error('Error : ', xhr.status, xhr.statusText);
if(xhr.status === 401) {
swal.fire({
title: '권한 오류',
text: '관리자 로그인 후 시도하세요.',
icon: 'warning'
}).then(() => {
window.location.href = '/admin/admin_login';
});
} else {
swal.fire('오류', '회원 탈퇴 처리 실패... 다시 시도해주세요.');
}
}
});
}
});
}
- button.getAttribute(); 를 통해서 html에서 할당한 ""의 값을 가져온다.
- if(withdrawalRequest === '0') {...} : 탈퇴 요청 확인. '0'이면 경고메시지와 함수를 중. 이후 탈퇴 요청 확인 팝업을 표시하고, confirmButtonText: '회원 탈퇴' 버튼을 클릭 시 result.isConfirmed 가 true. > ajax요청.
Controller
// 회원 탈퇴 처리
@PostMapping("/delete-member-admin")
public ResponseEntity<String> deleteMemberAction (HttpSession session, Model model,
@RequestParam("id") String memberId) {
Member admin = (Member)session.getAttribute("admin");
if(admin == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body("로그인후 가능한 기능입니다."); // 401 Unauthorized 응답
}
try {
Member member = memberService.getMember(memberId);
if(member == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body("회원 정보를 찾을 수 없습니다.");
}
memberService.deleteMember(member);
return ResponseEntity.ok("회원 탈퇴가 성공적으로 처리되었습니다.");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).
body("회원 탈퇴 처리 중 오류가 발생했습니다.");
}
}
- ResponseEntity<String> : Http 상태 코드, 응답 메시지를 반환.
- @RequestParam("id") String memberId : 요청한 파라미터에서 "id"값을 읽어와 memberId로 저장.
- memberService.getMember(memberId); : 탈퇴 요청을 처리할 회원 정보를 가져온다.
- memberService.deleteMember(member); : 회원이 존재하면 회원정보 삭제(CRUD의 Delete기능). 이후ResponseEntity.ok("회원 탈퇴가 성공적으로 처리되었습니다."); 로 응답 메시지 반환.
+++ 테스트 +++
===== 공지사항 관리 =====
Notice 클래스 생성
package com.demo.domain;
import java.util.Date;
import java.util.List;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@DynamicInsert
@DynamicUpdate
@Builder
@ToString
@Entity
public class Notice { //공지사항 table
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "noticeseq")
@SequenceGenerator(name="noticeseq", sequenceName="noticeseq", allocationSize = 1)
private int notice_seq; //공지사항 seq
private String title; //공지사항 제목
private String content; //공지사항 내용
@ColumnDefault("0")
private int likeCount; //공지사항 좋아요수
@ColumnDefault("0")
private int viewCount; //공지사항 조회수
private String noticeImageUrl; //공지사항 이미지 url
@ElementCollection
private List<String> uploadedImages;
@Temporal(value=TemporalType.TIMESTAMP)
@ColumnDefault("sysdate")
private Date notice_date; //공지사항 작성 날짜
//
@ManyToOne
@JoinColumn(name="id")
private Member member; //Member테이블 조회
}
Repository 생성
package com.demo.persistence;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.demo.domain.Notice;
import com.demo.domain.Review;
public interface NoticeRepository extends JpaRepository<Notice, Integer> {
// Spring Data JPA가 제공하는 기본 메서드로 대부분의 단순한 데이터 작업을 처리할 수 있으므로,
// 별도로 JPQL이나 네이티브 SQL을 작성하지 않았습니다.
// 리뷰 상세 조회
@Query(value = "SELECT * FROM notice WHERE notice_seq = :notice_seq", nativeQuery = true)
Notice getNoticeBySeq(@Param("notice_seq") int notice_seq);
}
Service 작성
package com.demo.service;
import java.io.IOException;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.demo.domain.FileUploadUtil;
import com.demo.domain.Notice;
import com.demo.domain.Review;
import com.demo.persistence.NoticeRepository;
@Service
public class NoticeServiceImpl implements NoticeService {
@Autowired
private NoticeRepository noticeRepo;
@Override
public Notice createNotice(Notice notice) {
return noticeRepo.save(notice);
}
@Override
public void deleteNotice(int notice_seq) {
noticeRepo.deleteById(notice_seq);
}
@Override
public void updateNotice(Notice vo) {
Notice update_notice = noticeRepo.getNoticeBySeq(vo.getNotice_seq());
vo.setMember(update_notice.getMember());
vo.setNotice_seq(update_notice.getNotice_seq());
vo.setMember(update_notice.getMember());
noticeRepo.save(update_notice);
}
@Override
public Notice getNoticeById(int notice_seq) { // 단일 조회
return noticeRepo.findById(notice_seq)
.orElseThrow(() -> new IllegalArgumentException("공지사항을 찾을 수 없습니다."));
}
@Override
public List<Notice> getAllNotices() {
return noticeRepo.findAll();
}
@Override
@Transactional
public void deleteImage(int notice_seq, int imageIndex) {
Notice notice = noticeRepo.getNoticeBySeq(notice_seq);
List<String> uploadImages = notice.getUploadedImages();
if (imageIndex >= 0 && imageIndex < uploadImages.size()) {
// 이미지 리스트에서 해당 인덱스의 이미지 삭제
String imageUrlToRemove = uploadImages.remove(imageIndex);
try {
// 파일 시스템에서 이미지 삭제
FileUploadUtil.deleteFile(imageUrlToRemove);
} catch (IOException e) {
// IOException을 RuntimeException으로 전환하여 처리
throw new RuntimeException("Failed to delete image file: " + imageUrlToRemove, e);
}
notice.setUploadedImages(uploadImages);
noticeRepo.save(notice);
} else {
throw new IllegalArgumentException("Invalid image index: " + imageIndex);
}
}
}
- @Service : 클래스가 서비스 계층임을 명시하고, @Autowired private NoticeRepository noticeRepo; Repository를 주입아 데이터베이스와 작업처리.
- public Notice createNotice(Notice notice) {... : 공지사항 생성 메서드 noticeRepo.save(notice); save메서드를 통해 새로운 공지사항을 DB에 저장한다. 입력받은 notice 객체를 저장하고, 저장된 객체를 반환.
- public void deleteNotice(int notice_seq) {... : notice_seq 를 기준으로 해당 공지사항을 삭제하는 메서드. notice_seq 값이 DB에 존재하지 않으면 예외 발생.
- public void updateNotice(Notice vo) {... : Notice update_notice = noticeRepo.getNoticeBySeq(vo.getNotice_seq()); 수정할 공지사항을 조회한 뒤 update_notice 객체에 저장. 기존 vo.set...(update_notice.get(...)); 객체에 수정한 데이들을 저장. noticeRepo.save(update_notice);
- :
Html (adminMain.html)
<!-- 공지사항 섹션 -->
<div class="admin-section">
<h3 class="clickable" onclick="toggleContent(this)">공지사항 관리</h3>
<div class="content-list" style="display: none;">
<a th:href="@{/admin-notice-list}">전체리스트</a><br>
<a th:href="@{/admin-notice-write}">작성</a><br>
</div>
</div>
Controller (매핑 url)
- 전체리스트 ( /admin-notice-list )
// 모든 공지사항
@GetMapping("/admin-notice-list")
public String allNoticeList(HttpSession session, Model model) {
Member admin = (Member)session.getAttribute("admin");
if(admin == null) {
model.addAttribute("message", "로그인 페이지로 이동..");
model.addAttribute("text", "공지사항 관리는 관리자 로그인 후 이용 가능합니다.");
model.addAttribute("messageType", "info");
return "admin/admin_login";
}
model.addAttribute("admin", admin);
List<Notice> notices = noticeService.getAllNotices();
model.addAttribute("notices", notices);
return "admin/section/notice_list"; // 뷰 이름
}
- Member admin = (Member)session.getAttribute("admin"); : 현재 세션에서 admin 정보를 가져와 admin 객체에 저장.
- model.addAttribute("admin", admin); : 세션에서 가져온 객체 admin을 html에서 활용할 수 있도록 model에 추가.
- List<Notice> notices = noticeService.getAllNotices(); : 모든 공지사항을 가져온 뒤, model.addAttribute("notices", notices); model로 전달.
- return "admin/section/notice_list"; : Spring MVC가 이 뷰 이름을 사용해 html 파일을 찾아 렌더링.
- 작성 ( /admin-notice-write )
// 관리자 공지사항 작성
@GetMapping("/admin-notice-write")
public String writeNotice(HttpSession session, Model model) {
Member admin = (Member)session.getAttribute("admin");
if(admin == null) {
model.addAttribute("message", "로그인 페이지로 이동");
model.addAttribute("text", "공지사항 작성을 위해 로그인해주세요.");
model.addAttribute("messageType", "info");
return "admin/admin_login";
}
return "admin/section/notice_write"; // 뷰 이름
}
공지사항 전체리스트 Html (notice_list.html)
<div class="all-container">
<h1>공지사항 관리</h1>
<button class="notice-write-btn" type="button" onclick="location.href='/admin-notice-write'">공지사항 작성</button>
<div class="items">
<div class="item" th:if="${notices.isEmpty()}">
<h3>아직 작성한 공지사항이 없습니다...</h3>
</div>
<div class="item" th:unless="${notices.isEmpty()}">
<div class="box" th:each="notice : ${notices}">
<a th:href="@{notice_detail(notice_seq=${notice.notice_seq})}">
<th:block th:if="${#lists.isEmpty(notice.uploadedImages)}">
<img th:src="@{/images/no_img.jpg}" alt="이미지 없음" style="width:200px; height:200px;">
</th:block>
<th:block th:unless="${#lists.isEmpty(notice.uploadedImages)}">
<div th:with="firstImage=${notice.uploadedImages[0]}">
<img th:src="@{${firstImage}}" th:alt="${notice.title}" style="width:200px; height:200px;">
</div>
</th:block>
</a>
<h4>제목 : <span th:text="${notice.title}"></span></h4>
<h4>좋아요수 : <span th:text="${notice.likeCount}"></span> /
조회수 : <span th:text="${notice.viewCount}"></span></h4>
</div>
</div>
</div>
</div>
공지사항 작성 Html (notice_write.html)
<div class="all-container">
<h1>공지사항 작성</h1>
<div class="notice-write-container">
<div class="item">
<form class="notice-write-form" id="notice-write-form" method="post" enctype="multipart/form-data">
<label for="title">제목</label>
<input type="text" id="title" name="title" placeholder="제목을 입력해주세요." class="form-input" />
<label for="content">내용</label>
<textarea name="content" id="content" placeholder="내용을 입력해주세요." class="form-textarea"></textarea>
<label class="custom-file-uploads" for="uploadFile">
<input type="file" name="uploadFile" id="uploadFile" multiple>
이미지 업로드 (선택 사항)
</label>
</form>
</div>
<button class="before-btn" type="button" onclick="window.location.href='/admin-notice-list'">이전페이지</button>
<button class="write-btn" type="button" onclick="notice_write()">공지사항 작성</button>
</div>
</div>
- 필수 입력 항목 제목,내용을 입력 후 <button class="write-btn" type="button" onclick="notice_write()">공지사항 작성</button> 버튼을 통해 처리할 함수 notice_write() 호출.
JavaScript notice_write()함수
// 공지사항 작성
function notice_write() {
if($("#title").val() == "") {
swal.fire({
title: '제목을 입력해주세요.',
icon: 'warning'
});
$("#title").focus();
return false;
} else if($("#content").val() == "") {
swal.fire({
title: '내용을 입력해주세요.',
icon: 'warning'
});
$("#content").focus();
return false;
} else {
swal.fire({
title: '공지사항 작성 성공!',
text: '공지사항 목록 페이지로 이동합니다.',
icon: 'success',
confirmButtonText: '확인'
}).then((result) => {
if(result.isConfirmed) {
$("#notice-write-form").attr("action", "/notice-write-action").submit();
}
});
}
}
- 필수 입력 항목인 제목(title), 내용(content)이 빈칸이 아닌지 확인한다. => 빈칸이면 경고메시지.
- 모두 확인이 되면 성공 메시지와 함께 확인 버튼을 클릭result.isConfirmed하면 "/notice-write-action" url로 controller에 요청.
Controller (/notice-write-action)
// 관리자 공지사항 작성 처리
@PostMapping("/notice-write-action")
public String writeNoticeAction(HttpSession session, Model model, Notice notice,
@RequestParam("uploadFile") MultipartFile[] uploadFile) {
Member admin = (Member)session.getAttribute("admin");
if(admin == null) {
model.addAttribute("message", "로그인 페이지로 이동..");
model.addAttribute("text", "공지사항 작성은 관리자 로그인 후 이용 가능합니다.");
model.addAttribute("messageType", "info");
return "admin/admin_login";
}
notice.setMember(admin);
List<String> fileUrls = new ArrayList<>();
for (MultipartFile file : uploadFile) {
if(!file.isEmpty()) {
// 경로
String uploadDir = "C:/ThisIsJava/SpringBootWorkspace/Book/uploads2/";
// 파일이름 수정
String originalName = file.getOriginalFilename();
String fileExtension = originalName.substring(originalName.lastIndexOf("."));
String uuid = UUID.randomUUID().toString();
String fileName = uuid + fileExtension;
try {
// 파일 저자ㅇ
FileUploadUtil.saveFile(uploadDir, fileName, file);
// url 생성
String fileUrl = "/uploads2/" + fileName;
fileUrls.add(fileUrl);
} catch (IOException e) {
e.printStackTrace();
model.addAttribute("message", "파일 업로드 중 오류 발생");
model.addAttribute("messageType", "error");
}
notice.setUploadedImages(fileUrls);
}
}
noticeService.createNotice(notice);
return "redirect:/admin-notice-list";
}
- @PostMapping("/notice-write-action") : /notice-write-action 경로로 들어오는 post 요청 처리. 작성한 공지사항 데이터(Notice notice)와 첨부파일(@RequestParam("uploadFile") MultipartFile[] uploadFile)을 함께 받는다.
- Member admin = (Member)session.getAttribute("admin"); : 세션에 저장된 관리자(admin)정보를 확인 및 객체에 저장.
- notice.setMember(admin); : 공지사항 작성자 정보를 현재 로그인한 관리자로 저장.
- 파일 업로드 처리 과정은 생략...
- noticeService.createNotice(notice); : 전달받은 공지사항 객체를 DB에 저장.
++ 테스트
공지사항 상세보기 ( <a th:href="@{notice_detail(notice_seq=${notice.notice_seq})}"> )
- 매핑 Controller
// 공지사항 상세
@GetMapping("/notice_detail")
public String noticeDetailView(HttpSession session, Model model, @RequestParam("notice_seq") int notice_seq) {
Member admin = (Member)session.getAttribute("admin");
if(admin == null) {
model.addAttribute("message", "로그인 페이지로 이동");
model.addAttribute("text", "공지사항 수정을 위해 로그인해주세요.");
model.addAttribute("messageType", "info");
return "admin/admin_login";
}
Notice notice = noticeService.getNoticeById(notice_seq);
List<String> uploadImages = notice.getUploadedImages();
model.addAttribute("uploadImages", uploadImages);
model.addAttribute("notice", notice);
model.addAttribute("admin", admin);
return "admin/section/notice_detail";
}
- @GetMapping("/notice_detail") : /notice_detail 로 들어온 get 요청 처리.
- @RequestParam("notice_seq") int notice_seq : notice_seq 값 추출.
- noticeService.getNoticeById(notice_seq); : 추출한 notice_seq 값으로 공지사항을 조회 -> Notice notice 객체로 반환.
- List<String> uploadImages = notice.getUploadedImages(); , model.addAttribute("uploadImages", uploadImages); : 조회한 notice 에 첨부 이미지 리스트를 가져와 model 로 전달.
공지사항 상세 Html (notice_detail.html)
<div class="all-container">
<h1>공지사항 상세 보기</h1>
<div class="notice-detail-container">
<div class="count-box">
<h4>
조회수 : [[${notice.viewCount}]]
좋아요수 : [[${notice.likeCount}]]
</h4>
</div>
<div class="notice-detail-content">
<input type="hidden" id="notice-seq" th:value="${notice.notice_seq}">
<th:block th:if="${uploadImages == null or #lists.isEmpty(uploadImages)}">
<img th:src="@{/images/no_img.jpg}" alt="이미지 없음" style="width:200px; height:200px;">
</th:block>
<th:block th:unless="${uploadImages == null or #lists.isEmpty(uploadImages)}">
<div th:each="image : ${uploadImages}">
<img th:src="@{${image}}" th:alt="${notice.title}" style="width:200px; height:200px;">
</div>
</th:block>
<h4>작성자 : <span th:text="${notice.member.name}"></span></h4>
<h4>제목 : <span id="notice-title" th:text="${notice.title}"></span></h4>
<h4>내용 : <span th:text="${notice.content}"></span></h4>
<h4>작성일 : <span th:text="${#dates.format(notice.notice_date, 'yyyy-MM-dd HH:mm')}"></span></h4>
</div>
<div class="btn-box">
<button class="edit-btn" type="button" onclick="checkAndEdit()">수정</button>
<button class="before-btn" type="button" onclick="window.location.href='/admin-notice-list'">목록</button>
<button class="delete-btn" type="button" onclick="noticedelete()">삭제</button>
</div>
</div>
</div>
공지사항 수정 함수 (checkAndEdit())
<script th:inline="javascript">
var writer = /*[[${notice.member.id}]]*/ '';
var viewer = /*[[${admin.id}]]*/ '';
var notice_seq = /*[[${notice.notice_seq}]]*/ 0;
function checkAndEdit() {
if(writer == viewer) {
window.location.href = '/admin-notice-ED?notice_seq=' + notice_seq;
} else {
swal.fire({
title: '공지사항 수정 실패',
text: '작성자만 수정 가능합니다.',
icon: 'warning',
confirmButtonText: '확인'
});
}
}
</script>
- if(writer == viewer) : 작성자와(writer) 상세보기를 하고있는 사용자(viewer)의 id를 비교해서 일치하면, window.location.href = '/admin-notice-ED?notice_seq=' + notice_seq; url주소에 해당 공지사항의 고유번호(notice_seq)와 함께 요청.
Controller
// 공지사항 수정
@GetMapping("/admin-notice-ED")
public String editDeleteNotice(HttpSession session, Model model,
@RequestParam("notice_seq") int notice_seq) {
Member admin = (Member)session.getAttribute("admin");
Notice notice = noticeService.getNoticeById(notice_seq);
if(admin == null) {
model.addAttribute("message", "로그인 페이지로 이동");
model.addAttribute("text", "공지사항 수정을 위해 로그인해주세요.");
model.addAttribute("messageType", "info");
return "admin/admin_login";
}
if(!notice.getMember().getId().equals(admin.getId())) { // 작성자와 로그인유저 비교
model.addAttribute("message", "수정 불가");
model.addAttribute("text", "작성자만 수정할 수 있습니다.");
model.addAttribute("messageType", "error");
return "redirect:/admin-notice-list";
}
model.addAttribute("notice", notice);
return "admin/section/notice_edit";
}
- @RequestParam("notice_seq") int notice_seq : notice_seq 값 추출.
- Member admin = (Member)session.getAttribute("admin"); : 로그인한 관리자 정보 저장.
- Notice notice = noticeService.getNoticeById(notice_seq); : 추출한 notice_seq 값으로 공지사항 조회.
- model.addAttribute("notice", notice); : 조회한 공지사항을 model로 전달.
공지사항 수정 Html (notice_edit.html)
<div class="all-container">
<h1>공지사항 수정</h1>
<div class="notice-edit-container">
<form class="notice-edit-form" id="notice-edit-form" method="post" enctype="multipart/form-data">
<input type="hidden" name="notice_seq" th:value="${notice.notice_seq}" />
<!-- 이미지 관리 -->
<div class="uploaded-images">
<th:block th:if="${#lists.isEmpty(notice.uploadedImages)}">
<div class="uploaded-image">
<img th:src="@{/images/no_img.jpg}" alt="이미지 없음" style="width:200px; height:200px;">
</div>
</th:block>
<th:block th:unless="${#lists.isEmpty(notice.uploadedImages)}">
<th:block th:each="image, iterStat : ${notice.uploadedImages}">
<div class="uploaded-image">
<img th:src="@{${image}}" th:alt="${notice.title}" style="width:200px; height:200px;">
<button type="button" th:data-notice-seq="${notice.notice_seq}"
th:data-index="${iterStat.index}" class="edit-imgdelete-btn"
onclick="deleteImage(this)">삭제
</button>
</div>
</th:block>
</th:block>
</div>
<label>제목</label>
<input type="text" name="title" id="title" th:value="${notice.title}" />
<label>내용</label>
<textarea name="content" id="content" th:text="${notice.content}"></textarea>
<label class="custom-file-upload">
<input type="file" name="uploadFile" id="uploadFile" multiple />
파일 업로드
</label>
</form>
<div class="edit-button">
<button class="before-btn" type="button" onclick="window.location.href='/admin-notice-list'">목록으로</button>
<button class="edit-btn" type="button" onclick="update_notice()">공지사항 수정</button>
</div>
</div>
</div>
-- 이미지 삭제
<button type="button" th:data-notice-seq="${notice.notice_seq}"
th:data-index="${iterStat.index}" class="edit-imgdelete-btn"
onclick="deleteImage(this)">삭제
</button>
- th:data-notice-seq="${notice.notice_seq}" : 공지사항 고유번호 저장.
- th:data-index="${iterStat.index}" : 이미지 배열에서 삭제할 이미지의 인덱스 저장.
// 이미지 삭제
function deleteImage(buttonElement) {
const notice_seq = buttonElement.getAttribute('data-notice-seq');
const imageIndex = buttonElement.getAttribute('data-index');
const remainingImages = [];
$(".uploaded-image").each(function(index, element) {
if(index !== imageIndex) {
remainingImages.push($(element).find("img").attr("src"));
}
});
// 삭제된 이미지를 제외한 나머지 이미지를 서버로 전송
$("#notice-edit-form").append(
$("<input>", {
type: "hidden",
name: "uploadedImages",
value: remainingImages.join(",")
})
);
$.ajax({
url: '/delete-image-notice',
type: 'GET',
data: {
notice_seq: notice_seq,
imageIndex: imageIndex
},
success: function(response) {
swal.fire({
title: '삭제 성공',
text: '이미지를 성공적으로 삭제하였습니다.',
icon: 'success',
confirmButtonText: '확인'
}).then((result) => {
if(result.isConfirmed) {
location.reload();
}
});
},
error: function(xhr, status, error) {
swal.fire({
title: '삭제 실패',
text: '이미지 삭제 중 오류가 발생했습니다.',
icon: 'error',
confirmButtonText: '확인'
});
}
});
}
- buttonElement.getAttribute(...); : 를 통해 데이터 추출.
- $(".uploaded-image") : 로 업로드된 이미지 모두 순회하고, if(index !== imageIndex) {... 삭제 이미지를 제외한 나머지 이미지를 배열에 추가 => remainingImages.push($(element).find("img").attr("src")); 남아있는 이미지의 src경로를 배열에 저장.
- $("#notice-edit-form").append( : 삭제할 이미지 정보를 서버로 전송. value: remainingImages.join(",") 데이터를 ,로 구분된 문자열로 저장.
// 공지사항 이미지 삭제
@GetMapping("/delete-image-notice")
@ResponseBody
public ResponseEntity<String> deleteImageNotice(@RequestParam("notice_seq") int notice_seq,
@RequestParam("imageIndex") int imageIndex) {
try {
noticeService.deleteImage(notice_seq, imageIndex);
return ResponseEntity.ok("이미지 삭제 성공!");
} catch(Exception e) {
return ResponseEntity.status(500).body("이미지 삭제 실패 : " + e.getMessage());
}
}
- @RequestParam("notice_seq") int notice_seq : 공지사항 고유번호
- @RequestParam("imageIndex") int imageIndex : 삭제할 이미지 index
- noticeService.deleteImage(notice_seq, imageIndex); : service의 메서드를 통해 해당 이미지 삭제 처리. *( 메서드는 위에 service 작성에 있어요)*
-- 수정
<button class="edit-btn" type="button" onclick="update_notice()">공지사항 수정</button>
// 공지사항 수정
function update_notice() {
if($("#title").val() == "") {
swal.fire({
title: '제목을 입력해주세요.',
icon: 'warning'
});
$("#title").focus();
return false;
} else if($("#content").val() == "") {
swal.fire({
title: '내용을 입력해주세요.',
icon: 'warning'
});
$("#content").focus();
return false;
} else {
swal.fire({
title: '리뷰 수정 성공',
icon: 'success',
confirmButtonText: '확인'
}).then((result) => {
if(result.isConfirmed) {
$("#notice-edit-form").attr("action", "/update-notice").submit();
}
});
}
}
controller 처리
// 공지사항 수정 처리
@PostMapping("/update-notice")
public String updateNotice(HttpSession session, Model model, Notice vo,
@RequestParam("uploadFile") MultipartFile[] uploadFile,
@RequestParam("notice_seq") int notice_seq) {
Member admin = (Member)session.getAttribute("admin");
Notice notice = noticeService.getNoticeById(notice_seq);
if(admin == null) {
model.addAttribute("message", "로그인 페이지로 이동");
model.addAttribute("text", "공지사항 수정을 위해 로그인해주세요.");
model.addAttribute("messageType", "info");
return "admin/admin_login";
}
if(!notice.getMember().getId().equals(admin.getId())) { // 작성자와 로그인유저 비교
model.addAttribute("message", "수정 불가");
model.addAttribute("text", "작성자만 수정할 수 있습니다.");
model.addAttribute("messageType", "error");
return "redirect:/admin-notice-list";
}
notice.setTitle(vo.getTitle());
notice.setContent(vo.getContent());
// 기존 이미지 리스트 유지
List<String> existingImages = notice.getUploadedImages();
if(existingImages == null) {
existingImages = new ArrayList<>();
}
// 새로 업로드된 이미지 추가
if(vo.getUploadedImages() != null) {
existingImages.addAll(vo.getUploadedImages());
}
// 수정된 이미지 리스트 설정
notice.setUploadedImages(existingImages);
// 파일 업로드
if(uploadFile.length > 0 || !notice.getUploadedImages().isEmpty()) {
List<String> fileUrls = new ArrayList<>();
if(!notice.getUploadedImages().isEmpty()) {
fileUrls.addAll(notice.getUploadedImages());
}
for(MultipartFile file : uploadFile) {
if(!file.isEmpty()) {
// 경로
String uploadDir = "C:/ThisIsJava/SpringBootWorkspace/Book/uploads2/";
// 파일이름 수정
String originalName = file.getOriginalFilename();
String fileExtension = originalName.substring(originalName.lastIndexOf("."));
String uuid = UUID.randomUUID().toString();
String fileName = uuid + fileExtension;
try {
// 파일 저자ㅇ
FileUploadUtil.saveFile(uploadDir, fileName, file);
// url 생성
String fileUrl = "/uploads2/" + fileName;
fileUrls.add(fileUrl);
} catch (IOException e) {
e.printStackTrace();
model.addAttribute("message", "파일 업로드 중 오류 발생");
model.addAttribute("messageType", "error");
return "redirect:/admin-notice-list";
}
}
}
notice.setUploadedImages(fileUrls);
}
noticeService.updateNotice(notice);
return "redirect:/admin-notice-list";
}
- noticeService.getNoticeById(notice_seq); 로 수정할 공지사항을 조회 => notice.setTitle(vo.getTitle()); , notice.setContent(vo.getContent()); 제목과 내용을 Notice vo 로 수정한 데이터로 설정. => noticeService.updateNotice(notice); 공지사항 수정.
++ 테스트
-- 삭제
notice_detail.html의 삭제 버튼
<button class="delete-btn" type="button" onclick="noticedelete()">삭제</button>
삭제 버튼 클릭 시 호출 함수
// 공지사항 삭제
function noticedelete() {
const noticeSeq = document.getElementById("notice-seq").value; // 공지사항의 ID
const noticeTitle = document.getElementById("notice-title").innerText; // 제목
Swal.fire({
title: '삭제 확인',
text: `${noticeTitle} 공지사항을 삭제하시겠습니까?`,
icon: 'warning',
showCancelButton: true,
confirmButtonText: '삭제',
cancelButtonText: '취소'
}).then((result) => {
if (result.isConfirmed) {
// AJAX 요청을 통해 공지사항 삭제
$.ajax({
url: '/notice/delete', // 삭제 요청 URL
type: 'DELETE',
data: { notice_seq: noticeSeq }, // 삭제할 공지사항의 ID
success: function(response) {
// 삭제 성공 시
Swal.fire({
title: '삭제 완료',
text: '공지사항이 삭제되었습니다.',
icon: 'success'
}).then(() => {
location.href = '/admin-notice-list';
});
},
error: function(xhr, status, error) {
// 삭제 실패 시
Swal.fire({
title: '삭제 실패',
text: '공지사항 삭제에 실패했습니다.',
icon: 'error'
});
}
});
}
});
}
처리 Controller
// 공지사항 삭제 처리
@DeleteMapping("/notice/delete")
public ResponseEntity<String> deleteNotice(@RequestParam("notice_seq") int notice_seq) {
try {
noticeService.deleteNotice(notice_seq);
return ResponseEntity.ok("공지사항 삭제 완료");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("삭제 실패: " + e.getMessage());
}
}
- @RequestParam("notice_seq") int notice_seq 전달받은 notice_seq 로 noticeService.deleteNotice(notice_seq); 해당 공지사항을 삭제. return ResponseEntity.ok("공지사항 삭제 완료");
++ 테스트
'SpringBoot 프로젝트' 카테고리의 다른 글
Spring Boot - 관리자 페이지 (5) (0) | 2024.12.07 |
---|---|
Spring Boot - 관리자 페이지 (4) (0) | 2024.12.07 |
Spring Boot - 관리자 페이지 (2) (0) | 2024.11.25 |
Spring Boot - 관리자 페이지 (1) (0) | 2024.11.25 |
Spring Boot - Q&A 페이지 구현하기 (2) (0) | 2024.11.25 |