본문 이미지 수정

This commit is contained in:
dyhj625 2025-01-24 12:38:24 +09:00
parent 9386b18ec4
commit d42e4fa0ca
2 changed files with 223 additions and 295 deletions

View File

@ -16,25 +16,11 @@
package io.company.localhost.controller.api;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.sql.Blob;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
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.RestController;
import org.springframework.web.bind.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.PageSerializable;
import io.company.localhost.common.annotation.Member;
import io.company.localhost.common.annotation.ParameterCheck;
@ -50,8 +36,8 @@ import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
@Slf4j
public class BoardController {
private final localbordService boardService;
private final PasswordEncoder passwordEncoder;
/**
* 공지사항 목록 조회
@ -59,27 +45,10 @@ public class BoardController {
* @return 전체 공지사항 목록
*/
@Member
@ParameterCheck
@ParameterCheck
@GetMapping("/notices")
public ApiResponse<List<MapDto>> getNotices(@ReqMap MapDto map) {
List<MapDto> posts = boardService.getNotices(map);
// Blob 데이터를 처리 (문자열로 변환)
for (MapDto post : posts) {
Object content = post.get("content");
if (content instanceof Blob) {
Blob blob = (Blob) content;
post.put("content", safeBlobToString(blob));
}
// "id" 값을 Number로 받고 longValue() 변환
Object idObject = post.get("id");
long postId = ((Number) idObject).longValue();
post.put("hasAttachment", boardService.hasAttachments(postId));
}
return ApiResponse.ok(posts);
return ApiResponse.ok(boardService.getNotices(map));
}
/**
@ -88,335 +57,169 @@ public class BoardController {
* @return 페이징된 자유/익명 게시판 목록
*/
@Member
@ParameterCheck
@ParameterCheck
@GetMapping("/general")
public ApiResponse<PageInfo<MapDto>> getGeneralPosts(@ReqMap MapDto map) {
PageInfo<MapDto> posts = boardService.getGeneralPosts(map);
// Blob 데이터를 처리 (문자열로 변환)
for (MapDto post : posts.getList()) {
Object content = post.get("content");
if (content instanceof Blob) {
Blob blob = (Blob) content;
post.put("content", safeBlobToString(blob));
}
// "id" 값을 Number로 받고 longValue() 변환
Object idObject = post.get("id");
if (idObject instanceof Number) {
long postId = ((Number) idObject).longValue();
post.put("commentCount", boardService.getCommentCount(postId));
post.put("hasAttachment", boardService.hasAttachments(postId));
MapDto reactions = boardService.getBoardReactions(postId);
post.put("likeCount", reactions.getOrDefault("likeCount", 0));
post.put("dislikeCount", reactions.getOrDefault("dislikeCount", 0));
} else {
post.put("commentCount", 0); // id가 없거나 잘못된 경우 기본값 설정
}
}
return ApiResponse.ok(posts);
return ApiResponse.ok(boardService.getGeneralPosts(map));
}
/**
* 안전하게 Blob 데이터를 문자열로 변환하는 메서드
* @ReqMap
* @return 변환된 문자열 또는 null
*/
private String safeBlobToString(Blob blob) {
if (blob == null) {
return null; // Blob이 null이면 null 반환
}
try {
long blobLength = blob.length();
if (blobLength > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Blob is too large to process.");
}
return new String(blob.getBytes(1, (int) blobLength), StandardCharsets.UTF_8);
} catch (Exception e) {
System.err.println("Failed to convert Blob to String: " + e.getMessage());
e.printStackTrace();
return null; // 변환 실패 null 반환
}
}
/**
* 게시물 작성
* @ReqMap map 요청 파라미터 (LOCBRDTTL, LOCBRDCON, MEMBERSEQ, LOCBRDTYP,
* LOCBRDPWD(익명일떄에), LOCBRDCAT(지식커뮤니티만))
* LOCBRDPWD(익명일 때만), LOCBRDCAT(지식커뮤니티만))
* @return 작성된 게시물의 ID
*/
@Member
@ParameterCheck
@PostMapping
public ApiResponse<?> createBoard(@ReqMap MapDto map) {
// 비밀번호 암호화
if (map.containsKey("LOCBRDPWD")) {
String rawPassword = map.getString("LOCBRDPWD");
String encodedPassword = passwordEncoder.encode(rawPassword);
map.put("LOCBRDPWD", encodedPassword);
}
@Member
@ParameterCheck
@PostMapping
public ApiResponse<BigInteger> createBoard(@ReqMap MapDto map) {
return ApiResponse.ok(boardService.createBoard(map));
}
// <--- 프론드 단에서 해줌 할필요 없음 삭제 --->
// LOCBRDCON 필드를 JSON 문자열로 변환
// ObjectMapper objectMapper = new ObjectMapper();
// if (map.containsKey("LOCBRDCON")) {
// try {
// String jsonContent = objectMapper.writeValueAsString(map.get("LOCBRDCON"));
// map.put("LOCBRDCON", jsonContent); // JSON 문자열로 변환된 설정
// } catch (Exception e) {
// throw new RuntimeException("Error serializing LOCBRDCON field", e);
// }
// }
//로그인 미개발 ->임시
map.put("MEMBERSEQ", 1);
/**
* 게시물 상세보기
* @param boardId 게시물 ID
* @return 게시물 상세정보
*/
@Member
@ParameterCheck
@GetMapping("/{boardId}")
public ApiResponse<MapDto> getBoardDetail(@PathVariable("boardId") Long boardId) {
return ApiResponse.ok(boardService.getBoardDetail(boardId));
}
BigInteger createdIdx = boardService.createBoard(map); // 작성된 게시물의 idx를 반환
Map<String, Object> responseData = new HashMap<>();
responseData.put("CMNBRDSEQ", createdIdx);
responseData.put("message", "게시물이 작성되었습니다.");
return ApiResponse.ok(responseData);
}
/**
* 게시물 삭제
* @param boardId 게시물 ID
* @return 삭제 결과 메시지
*/
@Member
@ParameterCheck
@DeleteMapping("/{boardId}")
public ApiResponse<String> deleteBoard(@PathVariable("boardId") Long boardId, @ReqMap MapDto map) {
map.put("LOCBRDSEQ", boardId);
boardService.deleteBoard(map);
return ApiResponse.ok("게시물이 삭제되었습니다.");
}
/**
* 게시물 수정
* @param boardId 게시물 ID
* @ReqMap map 수정 데이터 (LOCBRDTTL, LOCBRDCON)
* @return 수정 결과 메시지
*/
@Member
@ParameterCheck
@PutMapping("/{boardId}")
public ApiResponse<String> updateBoard(@PathVariable("boardId") Long boardId, @ReqMap MapDto map) {
map.put("LOCBRDSEQ", boardId);
boardService.updateBoard(map);
return ApiResponse.ok("게시물이 수정되었습니다.");
}
/**
* 첨부파일 추가
* @ReqMap map 요청 파라미터 (CMNFLEREG, CMNFLESIZ, CMNFLEEXT, CMNFLEORG, CMNFLENAM, CMNFLEPAT, CMNBRDSEQ)
* @return 첨부파일 저장 결과 메시지
*/
@Member
@ParameterCheck
@Member
@ParameterCheck
@PostMapping("/{CMNBRDSEQ}/attachments")
public ApiResponse<String> uploadAttachment(@ReqMap MapDto map) {
String filename = UUID.randomUUID().toString();
map.put("CMNFLENAM", filename);
map.put("CMNFLEREG", 1);
// 파일 데이터 저장
boardService.addAttachment(map);
return ApiResponse.ok("첨부파일이 저장되었습니다.");
}
/**
* 게시물 상세보기
* @param boardId 게시물 ID
* @return 게시물 상세정보, 첨부파일 목록, 댓글 목록
*/
@Member
@ParameterCheck
@GetMapping("/{boardId}")
public ApiResponse<MapDto> getBoardDetail(@PathVariable("boardId") Long boardId) {
log.info("Fetching details for board ID: {}", boardId);
// 조회수 증가
boardService.incrementViewCount(boardId);
// 게시물 상세정보 조회
MapDto boardDetail = boardService.getBoardDetail(boardId);
if (boardDetail == null) {
throw new IllegalArgumentException("Board not found for ID: " + boardId);
}
// Blob 데이터를 문자열로 변환
if (boardDetail.get("content") instanceof Blob) {
try {
Blob blob = (Blob) boardDetail.get("content");
String contentString = new String(blob.getBytes(1, (int) blob.length()), StandardCharsets.UTF_8);
boardDetail.put("content", contentString);
} catch (Exception e) {
throw new RuntimeException("Failed to process Blob content", e);
}
}
MapDto reactions = boardService.getBoardReactions(boardId);
boardDetail.put("likeCount", reactions.getOrDefault("likeCount", 0));
boardDetail.put("dislikeCount", reactions.getOrDefault("dislikeCount", 0));
List<MapDto> attachments = boardService.getAttachments(boardId);
List<MapDto> comments = boardService.getComments(boardId.intValue());
List<MapDto> commentReactions = boardService.getCommentReactions(boardId);
for (MapDto comment : comments) {
Integer commentId = (Integer) comment.get("LOCCMTSEQ");
for (MapDto reaction : commentReactions) {
if (reaction.get("LOCCMTSEQ").equals(commentId)) {
comment.put("likeCount", reaction.getOrDefault("likeCount", 0));
comment.put("dislikeCount", reaction.getOrDefault("dislikeCount", 0));
}
}
}
// 결과 조합
MapDto result = new MapDto();
result.put("boardDetail", boardDetail);
result.put("attachments", attachments);
result.put("comments", comments);
result.put("commentCount", boardService.getCommentCount(boardId));
result.put("hasAttachment", boardService.hasAttachments(boardId));
return ApiResponse.ok(result);
}
/**
* 게시물 삭제
* @param boardId 게시물 ID
* @return 삭제 결과 메시지
*/
@Member
@ParameterCheck
@DeleteMapping("/{boardId}")
public ApiResponse<String> deleteBoard(@PathVariable("boardId") Long boardId, @ReqMap MapDto map) {
map.put("LOCBRDSEQ", boardId);
log.info("Deleting board with ID: {}", boardId);
boardService.deleteBoard(map);
return ApiResponse.ok("게시물이 삭제되었습니다.");
}
/**
* 게시물 수정
* @param boardId 게시물 ID
* @ReqMap map 수정 데이터 (LOCBRDTTL, LOCBRDCON)
* @return 수정 결과 메시지
*/
@Member
@ParameterCheck
@PutMapping("/{boardId}")
public ApiResponse<String> updateBoard(@PathVariable("boardId") Long boardId,@ReqMap MapDto map) {
map.put("LOCBRDSEQ", boardId);
boardService.updateBoard(map);
return ApiResponse.ok("게시물이 수정되었습니다.");
}
/**
* 게시물,댓글 좋아요/싫어요 추가
/**
* 게시물, 댓글 좋아요/싫어요 추가
* @ReqMap map 데이터 (LOCCMTSEQ, MEMBERSEQ, LOCGOBGOD, LOCGOBBAD, LOCBRDSEQ)
* @return 반응 추가 결과 메시지
*/
@Member
@ParameterCheck
@Member
@ParameterCheck
@PostMapping("/{LOCBRDSEQ}/{LOCCMTSEQ}/reaction")
public ApiResponse<String> reactToBoard(@ReqMap MapDto map) {
//로그인 미개발 ->임시
map.put("MEMBERSEQ", 1);
boardService.reactToBoard(map);
return ApiResponse.ok("반응이 성공적으로 처리되었습니다.");
}
/**
* 댓글/대댓글 조회
* @param boardId 게시물 ID
* @return 댓글과 대댓글의 계층 구조 데이터
*/
@Member
@ParameterCheck
@Member
@ParameterCheck
@GetMapping("/{boardId}/comments")
public ApiResponse<List<MapDto>> getComments(@PathVariable("boardId") int boardId) {
// 모든 댓글과 대댓글 조회
List<MapDto> comments = boardService.getComments(boardId);
return ApiResponse.ok(comments);
return ApiResponse.ok(boardService.getComments(boardId));
}
/**
/**
* 댓글/대댓글 작성
* @param boardId 게시물 ID
* @ReqMap map 댓글 데이터 (LOCBRDSEQ, LOCCMTRPY, LOCCMTPNT, LOCCMTPWD, MEMBERSEQ )
* @return 작성 결과 메시지
*/
@Member
@ParameterCheck
@Member
@ParameterCheck
@PostMapping("/{LOCBRDSEQ}/comment")
public ApiResponse<String> addCommentOrReply(@ReqMap MapDto map) {
//로그인 미개발 ->임시
map.put("MEMBERSEQ", 1);
// 비밀번호 암호화 (비밀번호가 있는 경우에만)
if (map.containsKey("LOCCMTPWD")) {
String rawPassword = map.getString("LOCCMTPWD");
String encodedPassword = passwordEncoder.encode(rawPassword);
map.put("LOCCMTPWD", encodedPassword);
}
boardService.addCommentOrReply(map);
return ApiResponse.ok("댓글 또는 대댓글이 작성되었습니다.");
}
/**
/**
* 댓글/대댓글 수정
* @param commentId 댓글 ID
* @ReqMap map 수정 데이터 (LOCCMTSEQ, LOCCMTRPY )
* @ReqMap map 수정 데이터 (LOCCMTSEQ, LOCCMTRPY)
* @return 수정 결과 메시지
*/
@Member
@ParameterCheck
@Member
@ParameterCheck
@PutMapping("/comment/{commentId}")
public ApiResponse<String> updateComment(@PathVariable("commentId") int commentId, @ReqMap MapDto map) {
// 기존 댓글 조회
MapDto existingComment = boardService.getCommentById(commentId);
if (existingComment == null) {
throw new IllegalArgumentException("Comment not found for ID: " + commentId);
}
map.put("LOCCMTSEQ", commentId);
boardService.updateComment(map);
return ApiResponse.ok("댓글이 수정되었습니다.");
}
/**
/**
* 댓글/대댓글 삭제
* @param commentId 댓글 ID
* @ReqMap map 삭제 데이터
* @return 삭제 결과 메시지
*/
@Member
@ParameterCheck
@Member
@ParameterCheck
@DeleteMapping("/comment/{commentId}")
public ApiResponse<String> deleteComment(@PathVariable("commentId") int commentId, @ReqMap MapDto map) {
map.put("LOCCMTSEQ", commentId);
boardService.deleteComment(map);
return ApiResponse.ok("댓글이 삭제되었습니다.");
}
/**
/**
* 댓글 비밀번호 확인
* @param commentId 댓글 ID
* @ReqMap map 비밀번호 데이터
* @return 비밀번호 확인 결과
*/
@Member
@ParameterCheck
@Member
@ParameterCheck
@PostMapping("/comment/{commentId}/password")
public ApiResponse<Boolean> checkCommentPassword(@PathVariable("commentId") int commentId, @ReqMap MapDto map) {
// DB에서 암호화된 비밀번호 조회
String storedPassword = boardService.getCommentPassword(commentId);
String rawPassword = map.getString("LOCCMTPWD");
// 비밀번호 검증
boolean isMatch = passwordEncoder.matches(rawPassword, storedPassword);
return ApiResponse.ok(isMatch);
return ApiResponse.ok(boardService.getCommentPassword(commentId).equals(map.getString("LOCCMTPWD")));
}
/**
/**
* 게시물 비밀번호 확인
* @param boardId 게시물 ID
* @ReqMap map 비밀번호 데이터
* @return 비밀번호 확인 결과
*/
@Member
@ParameterCheck
@Member
@ParameterCheck
@PostMapping("/{boardId}/password")
public ApiResponse<Boolean> checkBoardPassword(@PathVariable("boardId") int boardId, @ReqMap MapDto map) {
// DB에서 암호화된 비밀번호 조회
String storedPassword = boardService.getBoardPassword(boardId);
String rawPassword = map.getString("LOCBRDPWD");
// 비밀번호 검증
boolean isMatch = passwordEncoder.matches(rawPassword, storedPassword);
return ApiResponse.ok(isMatch);
return ApiResponse.ok(boardService.getBoardPassword(boardId).equals(map.getString("LOCBRDPWD")));
}
}

View File

@ -1,11 +1,15 @@
package io.company.localhost.service;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
@ -20,7 +24,9 @@ public class localbordService {
private final localbordMapper boardMapper;
public List<MapDto> getNotices(MapDto map) {
return boardMapper.getNotices(map);
List<MapDto> posts = boardMapper.getNotices(map);
enrichPostsWithAdditionalData(posts);
return posts;
}
public PageInfo<MapDto> getGeneralPosts(MapDto map) {
@ -35,6 +41,7 @@ public class localbordService {
PageHelper.startPage(page, size);
List<MapDto> result = boardMapper.getGeneralPosts(map);
enrichPostsWithAdditionalData(result);
return PageUtil.redefineNavigation(new PageInfo<>(result, size));
}
@ -45,7 +52,7 @@ public class localbordService {
public BigInteger createBoard(MapDto map) {
boardMapper.createBoard(map);
return (BigInteger) map.get("LOCBRDSEQ"); // Mapper에서 자동 생성된 key를 가져옴
return (BigInteger) map.get("LOCBRDSEQ");
}
public void addAttachment(MapDto map) {
@ -53,7 +60,11 @@ public class localbordService {
}
public MapDto getBoardDetail(Long boardId) {
return boardMapper.selectBoardDetail(boardId);
MapDto boardDetail = boardMapper.selectBoardDetail(boardId);
if (boardDetail != null) {
enrichBoardDetail(boardDetail);
}
return boardDetail;
}
public List<MapDto> getAttachments(Long boardId) {
@ -61,7 +72,7 @@ public class localbordService {
}
public void deleteBoard(MapDto map) {
boardMapper.deleteCommentsByBoardId(map);
boardMapper.deleteCommentsByBoardId(map);
boardMapper.deleteBoard(map);
}
@ -70,14 +81,11 @@ public class localbordService {
}
public void reactToBoard(MapDto map) {
// 기존 반응 확인
MapDto existingReaction = boardMapper.findReaction(map);
if (existingReaction != null) {
// 기존 반응이 있는 경우 업데이트
boardMapper.updateReaction(map);
} else {
// 기존 반응이 없는 경우 새로 삽입
boardMapper.insertReaction(map);
}
}
@ -87,8 +95,8 @@ public class localbordService {
}
public void addCommentOrReply(MapDto map) {
if (map.get("LOCCMTPNT") == null) {
map.put("LOCCMTPNT", null);
if (map.get("LOCCMTPNT") == null) {
map.put("LOCCMTPNT", null);
}
boardMapper.addCommentOrReply(map);
}
@ -98,22 +106,17 @@ public class localbordService {
}
public void deleteComment(MapDto map) {
// 댓글인지 대댓글인지 확인
boolean isReply = boardMapper.isReply(map);
if (isReply) {
// 대댓글 삭제
boardMapper.deleteReply(map);
boardMapper.deleteReply(map);
} else {
// 댓글인 경우
boolean hasReplies = boardMapper.hasReplies(map);
if (hasReplies) {
// 대댓글이 있는 경우 내용 업데이트
boardMapper.softDeleteComment(map);
boardMapper.softDeleteComment(map);
} else {
// 대댓글이 없는 경우 댓글 삭제
boardMapper.deleteComment(map);
boardMapper.deleteComment(map);
}
}
}
@ -147,6 +150,128 @@ public class localbordService {
return boardMapper.getCommentReactions(boardId);
}
private void enrichBoardDetail(MapDto boardDetail) {
long boardId = ((Number) boardDetail.get("id")).longValue();
boardDetail.put("hasAttachment", hasAttachments(boardId));
boardDetail.put("commentCount", getCommentCount(boardId));
MapDto reactions = getBoardReactions(boardId);
boardDetail.put("likeCount", reactions.getOrDefault("likeCount", 0));
boardDetail.put("dislikeCount", reactions.getOrDefault("dislikeCount", 0));
// Blob 데이터를 문자열로 변환
Object content = boardDetail.get("content");
if (content != null) {
String contentString = convertBlobToString(content); // Blob을 문자열로 변환
boardDetail.put("content", contentString); // JSON 변환 가능
}
}
private String convertBlobToString(Object blob) {
try {
if (blob instanceof String) {
return (String) blob; // 이미 문자열인 경우 반환
} else if (blob instanceof java.sql.Blob) {
java.sql.Blob sqlBlob = (java.sql.Blob) blob;
long blobLength = sqlBlob.length();
byte[] blobBytes = sqlBlob.getBytes(1, (int) blobLength);
return new String(blobBytes, StandardCharsets.UTF_8);
} else if (blob instanceof ByteArrayInputStream) {
ByteArrayInputStream inputStream = (ByteArrayInputStream) blob;
byte[] bytes = inputStream.readAllBytes();
return new String(bytes, StandardCharsets.UTF_8);
} else {
System.err.println("Unsupported blob type: " + blob.getClass());
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to convert Blob to String: " + e.getMessage(), e);
}
return null;
}
private String extractFirstImageUrl(String jsonContent) {
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonContent);
if (!rootNode.isArray()) {
System.err.println("JSON content is not an array");
return null;
}
for (JsonNode node : rootNode) {
JsonNode insertNode = node.get("insert");
if (insertNode != null && insertNode.has("image")) {
return insertNode.get("image").asText(); // 번째 이미지 URL 반환
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to extract first image URL: " + e.getMessage(), e);
}
return null; // 이미지가 없는 경우
}
private String extractPlainTextFromJson(String jsonContent) {
StringBuilder plainTextBuilder = new StringBuilder();
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonContent);
// JSON 배열인지 확인
if (!rootNode.isArray()) {
System.err.println("JSON content is not an array");
return "";
}
// JSON 노드 순회
for (JsonNode node : rootNode) {
JsonNode insertNode = node.get("insert");
// insert 노드가 텍스트인지 확인
if (insertNode != null && insertNode.isTextual()) {
String text = insertNode.asText();
// '\n' 제거하고 순수 텍스트만 추가
if (!text.trim().isEmpty() && !text.trim().equals("\n")) {
plainTextBuilder.append(text.trim());
}
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to extract plain text: " + e.getMessage(), e);
}
return plainTextBuilder.toString();
}
private void enrichPostsWithAdditionalData(List<MapDto> posts) {
for (MapDto post : posts) {
Object idObject = post.get("id");
if (idObject instanceof Number) {
long postId = ((Number) idObject).longValue();
post.put("commentCount", getCommentCount(postId));
post.put("hasAttachment", hasAttachments(postId));
MapDto reactions = getBoardReactions(postId);
post.put("likeCount", reactions.getOrDefault("likeCount", 0));
post.put("dislikeCount", reactions.getOrDefault("dislikeCount", 0));
Object content = post.get("content");
if (content != null) {
String contentString = convertBlobToString(content);
post.put("content", contentString);
// 번째 이미지 URL 순수 텍스트 추출
String firstImageUrl = extractFirstImageUrl(contentString);
post.put("firstImageUrl", firstImageUrl);
String plainContent = extractPlainTextFromJson(contentString);
post.put("plainContent", plainContent);
}
} else {
post.put("commentCount", 0);
}
}
}
}