Merge branch 'main' into yoon

This commit is contained in:
yoon 2025-03-06 09:29:12 +09:00
commit 963ff1f106
11 changed files with 158 additions and 74 deletions

8
Jenkinsfile vendored
View File

@ -21,8 +21,7 @@ pipeline {
echo "Tomcat is not running, skipping shutdown..."
) else (
echo "Tomcat is running, shutting down..."
cd C:\\localhost-tomcat\\apache-tomcat-10.1.36-windows-x64\\apache-tomcat-10.1.36\\bin
call shutdown.bat
net stop localtomcat
ping -n 5 127.0.0.1 > nul
)
@ -35,9 +34,8 @@ pipeline {
ping -n 5 127.0.0.1 > nul
echo "start"
cd /d C:\\localhost-tomcat\\apache-tomcat-10.1.36-windows-x64\\apache-tomcat-10.1.36\\bin
call startup.bat
ping -n 5 127.0.0.1 > nul
net start localtomcat
ping -n 8 127.0.0.1 > nul
'''
}
}

12
WEB-INF/web.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" id="WebApp_ID">
<display-name>localhost</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>

View File

@ -16,13 +16,23 @@
package io.company.localhost.controller.api;
import java.io.File;
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.Base64;
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.*;
import org.springframework.web.multipart.MultipartFile;
import com.github.pagehelper.PageInfo;
@ -49,6 +59,7 @@ public class BoardController {
private final commoncodService commoncodService;
private final PasswordEncoder passwordEncoder;
/**
* 공지사항 목록 조회
* @ReqMap map 요청 파라미터 (searchKeyword)
@ -84,7 +95,6 @@ public class BoardController {
@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);
@ -114,6 +124,34 @@ public class BoardController {
return ApiResponse.ok(board);
}
/**
* 파일 다운로드 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)
@ -148,13 +186,20 @@ public class BoardController {
@Member
@ParameterCheck
@PostMapping("/{CMNBRDSEQ}/attachments")
public ApiResponse<String> uploadAttachment(@ReqMap MapDto map) {
Long userId = AuthUtil.getUser().getId();
map.put("CMNFLEREG", userId);
boardService.insertAttachment(map);
return ApiResponse.ok("첨부파일이 저장되었습니다.");
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)

View File

@ -65,9 +65,6 @@ public interface localbordMapper {
// 댓글/대댓글 수정
void updateComment(MapDto map);
// 대댓글인지 확인
int selectIsReply(MapDto map);
// 댓글에 대댓글이 있는지 확인
int selectHasReplies(MapDto map);
@ -77,9 +74,6 @@ public interface localbordMapper {
// 댓글 삭제 (대댓글 없음)
void deleteComment(MapDto map);
// 대댓글 삭제
void deleteReply(MapDto map);
// 댓글 비밀번호 조회
String selectCommentPassword(int commentId);

View File

@ -34,6 +34,9 @@ public class FileService {
@Value("${filePath.profile}")
private String uploadPath;
@Value("${filePath.boardfile}")
private String boardFilePath;
/**
* 파일 업로드
*
@ -65,4 +68,37 @@ public class FileService {
throw new RuntimeException("파일 업로드 실패: " + e.getMessage());
}
}
/**
* 게시판 파일 업로드
*
* @param file
* @return
* @throws RuntimeException
*/
public String boardUploadFile(MultipartFile file) {
try {
System.out.println(file);
// 원본 파일명
String originalFilename = file.getOriginalFilename();
// 파일 확장자
String extension = FilenameUtils.getExtension(originalFilename);
// UUID를 사용하여 고유한 파일명 생성
String newFilename = UUID.randomUUID().toString() + "." + extension;
// 최종 저장 경로 생성 (기본경로 + 파일명)
Path targetPath = Paths.get(boardFilePath, newFilename);
// 저장될 디렉토리가 없는 경우 생성
Files.createDirectories(targetPath.getParent());
// 동일 파일명이 있을 경우 덮어쓰기
Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
// 저장된 파일의 상대 경로 반환
return targetPath.toString();
} catch (IOException e) {
throw new RuntimeException("파일 업로드 실패: " + e.getMessage());
}
}
}

View File

@ -15,11 +15,16 @@
package io.company.localhost.service;
import java.math.BigInteger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
import io.company.localhost.utils.BlobUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -36,6 +41,7 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class localbordService {
private final localbordMapper boardMapper;
private final FileService fileService;
private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
public List<MapDto> selectNotices(MapDto map) {
@ -78,14 +84,15 @@ public class localbordService {
return (BigInteger) map.get("LOCBRDSEQ");
}
public void insertAttachment(MapDto map) {
public void insertAttachment(MapDto map, MultipartFile file) {
String boardSeqStr = (String) map.get("CMNBRDSEQ");
Long boardSeq = Long.parseLong(boardSeqStr);
map.put("CMNBRDSEQ", boardSeq);
String newFilename = UUID.randomUUID().toString();
map.put("CMNFLENAM", newFilename);
String Path = fileService.boardUploadFile(file);
map.put("CMNFLEPAT", Path);
boardMapper.insertAttachment(map);
}
@ -166,24 +173,17 @@ public class localbordService {
}
public void deleteComment(MapDto map) {
// 댓글이 대댓글 확인
boolean isReply = boardMapper.selectIsReply(map) > 0;
// 댓글이 대댓글 있는 확인
boolean hasReplies = boardMapper.selectHasReplies(map) > 0;
if (isReply) {
// 대댓글이면 바로 삭제
boardMapper.deleteReply(map);
if (hasReplies) {
// 대댓글이 있는 경우, '삭제된 댓글입니다.' 변경 (소프트 삭제)
boardMapper.updateSoftDeleteComment(map);
} else {
// 댓글에 대댓글이 있는지 확인
boolean hasReplies = boardMapper.selectHasReplies(map) > 0;
if (hasReplies) {
// 대댓글이 있는 경우, '삭제된 댓글입니다.' 변경 (소프트 삭제)
boardMapper.updateSoftDeleteComment(map);
} else {
// 대댓글이 없는 경우, 완전 삭제
boardMapper.deleteComment(map);
}
// 대댓글이 없는 경우, 완전 삭제
boardMapper.deleteComment(map);
}
}
public String selectCommentPassword(int commentId) {

View File

@ -218,30 +218,38 @@ public class localvacaService {
return emp;
}).collect(Collectors.toList());
}
/**
* 연차 계산 로직
*/
private int procCalculateTotalVacation(LocalDate hireDate) {
LocalDate today = LocalDate.now();
/**
* 연차 계산 로직
*/
public int procCalculateTotalVacation(LocalDate hireDate) {
LocalDate today = LocalDate.now(); // 현재 날짜
int yearsWorked = hireDate.until(today).getYears();
int hireMonth = hireDate.getMonthValue();
// 🔹 1년 미만: 연간 12개 지급
// 🔹 1년 미만: 입사한 월을 고려하여 연차 개수 계산
if (yearsWorked < 1) {
return 12;
return 12 - hireMonth;
} else {
int totalVacation = 12 - hireMonth; // 1년 미만 사용하고 남은 연차 반영
LocalDate nextIncreaseDate = hireDate.plusYears(1).withMonth(hireMonth).withDayOfMonth(1);
// 🔹 매년 입사월에 15개 지급
while (!nextIncreaseDate.isAfter(today)) {
totalVacation += 15;
nextIncreaseDate = nextIncreaseDate.plusYears(1);
}
// 🔹 입사년도 2년마다 입사월에 15개에서 1개씩 추가 지급
LocalDate additionalIncreaseDate = hireDate.plusYears(2).withMonth(hireMonth).withDayOfMonth(1);
int extraIncrease = 1;
while (!additionalIncreaseDate.isAfter(today)) {
totalVacation += extraIncrease;
additionalIncreaseDate = additionalIncreaseDate.plusYears(2);
extraIncrease++; // 2년마다 1개씩 증가
}
return totalVacation;
}
// 🔹 1년 이상 기본 15개
int totalVacation = 15;
LocalDate nextIncreaseDate = hireDate.plusYears(2).withMonth(1).withDayOfMonth(1);
// 🔹 2년마다 1개 추가
while (nextIncreaseDate.isBefore(today) || nextIncreaseDate.isEqual(today)) {
totalVacation += 1;
nextIncreaseDate = nextIncreaseDate.plusYears(2);
}
return totalVacation;
}
public List<MapDto> selectSentVacationCount(MapDto map) {

View File

@ -0,0 +1,4 @@
ssl:
key-store: classpath:localhost.p12
key-store-password: pmgk1234
key-store-type: PKCS12

View File

@ -82,10 +82,6 @@ server:
secure: true
same-site: NONE
partitioned: true
ssl:
key-store: classpath:localhost.p12
key-store-password: pmgk1234
key-store-type: PKCS12
logging:
level:

View File

@ -175,13 +175,15 @@
<!-- 댓글 삭제 -->
<update id="updateSoftDeleteComment">
UPDATE localcomt
SET LOCCMTRPY = '삭제된 댓글입니다'
SET LOCCMTRPY = '삭제된 댓글입니다',
LOCCMTUDT = NOW()
WHERE LOCCMTSEQ = #{LOCCMTSEQ}
AND EXISTS (
SELECT 1 FROM localcomt WHERE LOCCMTPNT = #{LOCCMTSEQ}
)
</update>
<!-- 댓글 삭제 (대댓글 없을 경우) -->
<delete id="deleteComment">
DELETE FROM localcomt
@ -191,19 +193,6 @@
)
</delete>
<!-- 대댓글 삭제 -->
<delete id="deleteReply">
DELETE FROM localcomt
WHERE LOCCMTSEQ = #{LOCCMTSEQ}
AND LOCCMTPNT IS NOT NULL
</delete>
<!-- 대댓글인지 확인 -->
<select id="selectIsReply" resultType="int">
SELECT COUNT(1) FROM localcomt
WHERE LOCCMTSEQ = #{LOCCMTSEQ} AND LOCCMTPNT IS NOT NULL
</select>
<!-- 댓글에 대댓글이 있는지 확인 -->
<select id="selectHasReplies" resultType="int">
SELECT COUNT(1) FROM localcomt WHERE LOCCMTPNT = #{LOCCMTSEQ}

View File

@ -4,8 +4,10 @@
<sql id="searchConditions">
<!-- 검색어 조건 -->
<if test="searchKeyword != null and searchKeyword != ''">
and (w.WRDDICTTL like CONCAT('%', #{searchKeyword}, '%')
or w.WRDDICCON like CONCAT('%', #{searchKeyword}, '%'))
and (
REGEXP_REPLACE(w.WRDDICTTL, '\\[\\{.*?"insert":"', '') LIKE CONCAT('%', #{searchKeyword}, '%')
OR REGEXP_REPLACE(w.WRDDICCON, '\\[\\{.*?"insert":"', '') LIKE CONCAT('%', #{searchKeyword}, '%')
)
</if>
<!-- 색인표 조건 -->
<if test="indexKeyword != null and indexKeyword != ''">