415 lines
14 KiB
Java
415 lines
14 KiB
Java
/************************************************************
|
|
*
|
|
* @packageName : io.company.localhost.controller.api
|
|
* @fileName : BoardController.java
|
|
* @author : 서지희
|
|
* @date : 25.01.07
|
|
* @description : 게시판
|
|
*
|
|
* ===========================================================
|
|
* DATE AUTHOR NOTE
|
|
* -----------------------------------------------------------
|
|
* 25.01.07 서지희 최초 생성
|
|
*
|
|
*************************************************************/
|
|
|
|
package io.company.localhost.controller.api;
|
|
|
|
import java.io.IOException;
|
|
import java.math.BigInteger;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import org.springframework.core.io.Resource;
|
|
import org.springframework.core.io.UrlResource;
|
|
import org.springframework.http.HttpHeaders;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
import org.springframework.web.bind.annotation.PathVariable;
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
import org.springframework.web.bind.annotation.PutMapping;
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
import org.springframework.web.bind.annotation.RequestParam;
|
|
import org.springframework.web.bind.annotation.RequestPart;
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
import com.github.pagehelper.PageInfo;
|
|
|
|
import io.company.localhost.common.annotation.Member;
|
|
import io.company.localhost.common.annotation.ParameterCheck;
|
|
import io.company.localhost.common.annotation.ReqMap;
|
|
import io.company.localhost.common.dto.ApiResponse;
|
|
import io.company.localhost.common.dto.MapDto;
|
|
import io.company.localhost.common.exception.NotFoundHandler;
|
|
import io.company.localhost.service.commoncodService;
|
|
import io.company.localhost.service.localbordService;
|
|
import io.company.localhost.utils.AuthUtil;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
@RestController
|
|
@RequestMapping("/api/board")
|
|
@RequiredArgsConstructor
|
|
@Slf4j
|
|
public class BoardController {
|
|
|
|
private final localbordService boardService;
|
|
private final commoncodService commoncodService;
|
|
private final PasswordEncoder passwordEncoder;
|
|
|
|
|
|
/**
|
|
* 공지사항 목록 조회
|
|
* @ReqMap map 요청 파라미터 (searchKeyword)
|
|
* @return 전체 공지사항 목록
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@GetMapping("/notices")
|
|
public ApiResponse<List<MapDto>> getNotices(@ReqMap MapDto map) {
|
|
|
|
// size를 안전하게 Integer로 변환하여 MapDto에 다시 넣기
|
|
Object sizeObj = map.get("size");
|
|
Integer size = null;
|
|
|
|
if (sizeObj instanceof String) {
|
|
size = Integer.parseInt((String) sizeObj);
|
|
}else {
|
|
size = null;
|
|
}
|
|
|
|
map.put("size", size);
|
|
|
|
return ApiResponse.ok(boardService.selectNotices(map));
|
|
}
|
|
|
|
/**
|
|
* 자유/익명 게시판 목록 조회
|
|
* @ReqMap map 요청 파라미터 (page, searchKeyword, orderBy, size)
|
|
* @return 페이징된 자유/익명 게시판 목록
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@GetMapping("/general")
|
|
public ApiResponse<PageInfo<MapDto>> getGeneralPosts(@ReqMap MapDto map) {
|
|
return ApiResponse.ok(boardService.selectGeneralPosts(map));
|
|
}
|
|
|
|
/**
|
|
* 게시물 작성
|
|
* @ReqMap map 요청 파라미터 (LOCBRDTTL, LOCBRDCON, MEMBERSEQ, LOCBRDTYP,
|
|
* LOCBRDPWD(익명일 때만), LOCBRDCAT(지식커뮤니티만))
|
|
* @return 작성된 게시물의 ID
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@PostMapping
|
|
public ApiResponse<BigInteger> createBoard(@ReqMap MapDto map) {
|
|
|
|
if (map.containsKey("LOCBRDPWD") && !map.getString("LOCBRDPWD").trim().isEmpty()) { // 빈 값 체크
|
|
String rawPassword = map.getString("LOCBRDPWD");
|
|
String hashedPassword = passwordEncoder.encode(rawPassword);
|
|
map.put("LOCBRDPWD", hashedPassword);
|
|
}
|
|
|
|
return ApiResponse.ok(boardService.insertBoard(map));
|
|
}
|
|
|
|
/**
|
|
* 게시물 상세보기
|
|
* @param boardId 게시물 ID
|
|
* @return 게시물 상세정보
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@GetMapping("/{boardId}")
|
|
public ApiResponse<MapDto> getBoardDetail(@PathVariable("boardId") Long boardId) {
|
|
MapDto board = boardService.selectBoardDetail(boardId);
|
|
if (board == null) {
|
|
//throw new NotFoundHandler("게시물 ID " + boardId + "을(를) 찾을 수 없습니다.");
|
|
String errMessage = "게시물 ID " + boardId + "을(를) 찾을 수 없습니다.";
|
|
return ApiResponse.error(HttpStatus.NOT_FOUND, errMessage);
|
|
}
|
|
// 📌 첨부파일 목록 추가
|
|
List<MapDto> attachments = boardService.selectAttachments(boardId);
|
|
board.put("attachments", attachments != null ? attachments : new ArrayList<>());
|
|
|
|
return ApiResponse.ok(board);
|
|
}
|
|
|
|
/**
|
|
* 게시물 수정 조회(익명 게시글은 비밀번호 값 필수)
|
|
*
|
|
* @param boardId
|
|
* @param map
|
|
* @return
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@PostMapping("/{boardId}")
|
|
public ApiResponse<MapDto> getBoardDetail2(@PathVariable("boardId") Long boardId, @ReqMap MapDto map) {
|
|
map.put("boardId", boardId);
|
|
return boardService.selectBoardDetail2(map);
|
|
}
|
|
|
|
/**
|
|
* 파일 다운로드 API
|
|
* @param path 파일 경로
|
|
* @return 파일 데이터 (바이너리 응답)
|
|
*/
|
|
@GetMapping("/download")
|
|
public ResponseEntity<Resource> downloadFile(@RequestParam String path) {
|
|
try {
|
|
Path filePath = Paths.get(path).normalize();
|
|
Resource resource = new UrlResource(filePath.toUri());
|
|
|
|
if (!resource.exists() || !resource.isReadable()) {
|
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
|
|
}
|
|
|
|
String contentType = Files.probeContentType(filePath);
|
|
String fileName = filePath.getFileName().toString();
|
|
|
|
return ResponseEntity.ok()
|
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
|
|
.header(HttpHeaders.CONTENT_TYPE, contentType != null ? contentType : "application/octet-stream")
|
|
.body(resource);
|
|
|
|
} catch (IOException e) {
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 게시물 삭제
|
|
* @ReqMap map 수정 데이터 (LOCBRDSEQ)
|
|
* @return 삭제 결과 메시지
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@DeleteMapping("/{boardId}")
|
|
public ApiResponse<String> deleteBoard(@ReqMap MapDto map) {
|
|
return boardService.deleteBoard(map);
|
|
}
|
|
|
|
|
|
/**
|
|
* 게시물 수정
|
|
*
|
|
* @param map 수정 데이터 (LOCBRDTTL, LOCBRDCON, LOCBRDSEQ, delListInfo)
|
|
* @param files 첨부파일 정보
|
|
* @return
|
|
* @throws IOException
|
|
*/
|
|
@Member
|
|
@PutMapping("/{boardId}")
|
|
public ApiResponse<String> updateBoard(@ReqMap MapDto map, @RequestPart(value = "files", required = false) List<MultipartFile> files) throws IOException {
|
|
return boardService.updateBoardWithFiles(map, files);
|
|
}
|
|
|
|
|
|
/**
|
|
* 첨부파일 추가
|
|
* @ReqMap map 요청 파라미터 (CMNFLEREG, CMNFLESIZ, CMNFLEEXT, CMNFLEORG, CMNFLENAM, CMNFLEPAT, CMNBRDSEQ)
|
|
* @return 첨부파일 저장 결과 메시지
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@PostMapping("/{CMNBRDSEQ}/attachments")
|
|
public ApiResponse<String> uploadAttachment(@ReqMap MapDto map, @RequestParam("file") MultipartFile file) {
|
|
try {
|
|
Long userId = AuthUtil.getUser().getId();
|
|
map.put("CMNFLEREG", userId);
|
|
|
|
boardService.insertAttachment(map, file);
|
|
|
|
return ApiResponse.ok("첨부파일이 저장되었습니다.");
|
|
} catch (Exception e) {
|
|
return ApiResponse.ok("첨부파일 저장 실패: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 게시물, 댓글 좋아요/싫어요 추가
|
|
* @ReqMap map 데이터 (LOCCMTSEQ, MEMBERSEQ, LOCGOBGOD, LOCGOBBAD, LOCBRDSEQ)
|
|
* @return 반응 추가 결과 메시지
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@PostMapping("/{LOCBRDSEQ}/{LOCCMTSEQ}/reaction")
|
|
public ApiResponse<String> reactToBoard(@ReqMap MapDto map) {
|
|
Long userId = AuthUtil.getUser().getId();
|
|
map.put("MEMBERSEQ", userId);
|
|
boardService.procReactToBoard(map);
|
|
return ApiResponse.ok("반응이 성공적으로 처리되었습니다.");
|
|
}
|
|
|
|
/**
|
|
* 댓글 조회
|
|
* @ReqMap map 수정 데이터 (LOCBRDSEQ, page)
|
|
* @return 댓글
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@GetMapping("/{boardId}/comments")
|
|
public ApiResponse<PageInfo<MapDto>> selectComments(@ReqMap MapDto map) {
|
|
|
|
// 댓글조회
|
|
PageInfo<MapDto> comments = boardService.selectComments(map);
|
|
|
|
return ApiResponse.ok(comments);
|
|
}
|
|
|
|
/**
|
|
* 대댓글 조회
|
|
* @ReqMap map 수정 데이터 (LOCCMTPNT)
|
|
* @return 대댓글
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@GetMapping("/{boardId}/reply")
|
|
public ApiResponse<List<MapDto>> getComments(@ReqMap MapDto map) {
|
|
List<MapDto> replies = boardService.selectReply(map);
|
|
|
|
return ApiResponse.ok(replies);
|
|
}
|
|
|
|
/**
|
|
* 댓글/대댓글 작성
|
|
* @param boardId 게시물 ID
|
|
* @ReqMap map 댓글 데이터 (LOCBRDSEQ, LOCCMTRPY, LOCCMTPNT, LOCCMTPWD, MEMBERSEQ )
|
|
* @return 작성 결과 메시지
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@PostMapping("/{LOCBRDSEQ}/comment")
|
|
public ApiResponse<String> addCommentOrReply(@ReqMap MapDto map) {
|
|
|
|
if (map.containsKey("LOCCMTPWD") && !map.getString("LOCCMTPWD").trim().isEmpty()) { // 빈 값 체크
|
|
String rawPassword = map.getString("LOCCMTPWD");
|
|
String hashedPassword = passwordEncoder.encode(rawPassword);
|
|
map.put("LOCCMTPWD", hashedPassword);
|
|
}
|
|
boardService.insertCommentOrReply(map);
|
|
return ApiResponse.ok("댓글 또는 대댓글이 작성되었습니다.");
|
|
}
|
|
|
|
/**
|
|
* 댓글/대댓글 수정
|
|
* @param commentId 댓글 ID
|
|
* @ReqMap map 수정 데이터 (LOCCMTSEQ, LOCCMTRPY)
|
|
* @return 수정 결과 메시지
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@PutMapping("/comment/{commentId}")
|
|
public ApiResponse<String> updateComment(@ReqMap MapDto map) {
|
|
boardService.updateComment(map);
|
|
return ApiResponse.ok("댓글이 수정되었습니다.");
|
|
}
|
|
|
|
/**
|
|
* 댓글/대댓글 삭제
|
|
* @param commentId 댓글 ID
|
|
* @return 삭제 결과 메시지
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@DeleteMapping("/comment/{commentId}")
|
|
public ApiResponse<String> deleteComment(@PathVariable("commentId") Long commentId, @RequestParam(value = "LOCCMTPNT") Long parentId) {
|
|
MapDto map = new MapDto();
|
|
map.put("LOCCMTSEQ", commentId);
|
|
map.put("LOCCMTPNT", parentId);
|
|
|
|
boardService.deleteComment(map);
|
|
return ApiResponse.ok("댓글이 삭제되었습니다.");
|
|
}
|
|
|
|
/**
|
|
* 게시물 비밀번호 확인 (해싱된 비밀번호 비교 적용)
|
|
* @ReqMap map 수정 데이터 (LOCBRDSEQ, LOCBRDPWD)
|
|
* @return 비밀번호 확인 결과
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@PostMapping("/{boardId}/password")
|
|
public ApiResponse<Boolean> checkBoardPassword(@ReqMap MapDto map) {
|
|
// boardId 데이터 타입 변환
|
|
Object boardIdObj = map.get("LOCBRDSEQ");
|
|
int boardId = 0;
|
|
|
|
if (boardIdObj instanceof Integer) {
|
|
boardId = (Integer) boardIdObj;
|
|
} else if (boardIdObj instanceof String) {
|
|
boardId = Integer.parseInt((String) boardIdObj);
|
|
}
|
|
|
|
String rawPassword = map.getString("LOCBRDPWD");
|
|
|
|
String storedHashedPassword = boardService.selectBoardPassword(boardId);
|
|
if (storedHashedPassword == null) {
|
|
throw new NotFoundHandler("해당 게시물이 존재하지 않습니다.");
|
|
|
|
}
|
|
|
|
boolean isMatch = passwordEncoder.matches(rawPassword, storedHashedPassword);
|
|
if (!isMatch) {
|
|
//throw new InvalidPasswordException("비밀번호가 일치하지 않습니다.");
|
|
return ApiResponse.error(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다.");
|
|
}
|
|
|
|
return ApiResponse.ok(true);
|
|
}
|
|
|
|
/**
|
|
* 댓글 비밀번호 확인 (해싱된 비밀번호 비교 적용)
|
|
* @ReqMap map 수정 데이터 (LOCCMTSEQ, LOCCMTPWD)
|
|
* @return 비밀번호 확인 결과
|
|
*/
|
|
@Member
|
|
@ParameterCheck
|
|
@PostMapping("/comment/{commentId}/password")
|
|
public ApiResponse<Boolean> checkCommentPassword(@ReqMap MapDto map) {
|
|
// commentId 데이터 타입 변환
|
|
Object commentIdObj = map.get("LOCCMTSEQ");
|
|
int commentId = 0;
|
|
|
|
if (commentIdObj instanceof Integer) {
|
|
commentId = (Integer) commentIdObj;
|
|
} else if (commentIdObj instanceof String) {
|
|
commentId = Integer.parseInt((String) commentIdObj);
|
|
}
|
|
String rawPassword = map.getString("LOCCMTPWD");
|
|
|
|
String storedHashedPassword = boardService.selectCommentPassword(commentId);
|
|
if (storedHashedPassword == null) {
|
|
throw new NotFoundHandler("해당 댓글이 존재하지 않습니다.");
|
|
}
|
|
|
|
boolean isMatch = passwordEncoder.matches(rawPassword, storedHashedPassword);
|
|
if (!isMatch) {
|
|
//throw new InvalidPasswordException("비밀번호가 일치하지 않습니다.");
|
|
return ApiResponse.error(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다.");
|
|
}
|
|
|
|
return ApiResponse.ok(true);
|
|
}
|
|
|
|
/**
|
|
* 카테고리 목록 조회
|
|
* @return 카테고리 리스트
|
|
*/
|
|
@GetMapping("/categories")
|
|
public ApiResponse<List<MapDto>> SelectCategories() {
|
|
List<MapDto> categories = commoncodService.selectCategoryList();
|
|
return ApiResponse.ok(categories);
|
|
}
|
|
} |