Merge branch 'dev' into feature/qcast-api-99

This commit is contained in:
LAPTOP-L3VE7KK2\USER 2024-10-24 16:01:14 +09:00
commit a3d86010dd
8 changed files with 559 additions and 3 deletions

View File

@ -12,7 +12,6 @@ import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@ -24,7 +23,7 @@ public class ExcelDownService {
private final ZipFileManager zipFileManager;
@Autowired Messages message;
Messages message;
@Value("${file.ini.root.path}")
private String baseDirPath;

View File

@ -0,0 +1,58 @@
package com.interplug.qcast.biz.file;
import com.interplug.qcast.biz.file.dto.FileRequest;
import com.interplug.qcast.biz.object.dto.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/api/file")
@RequiredArgsConstructor
@Tag(name = "FileController", description = "파일 관련 API")
public class FileController {
private final FileService fileService;
@Operation(description = "파일을 다운로드 한다.")
@PostMapping("/fileDownload")
@ResponseStatus(HttpStatus.OK)
public void fileDownload(
HttpServletRequest request,
HttpServletResponse response,
@RequestBody FileRequest fileRequest)
throws Exception {
fileService.downloadFile(request, response, fileRequest);
}
@Operation(description = "모든 파일을 zip 다운로드 한다.")
@PostMapping("/zipFileDownload")
@ResponseStatus(HttpStatus.OK)
public void allFileDownload(
HttpServletRequest request,
HttpServletResponse response,
@RequestBody FileRequest fileRequest)
throws Exception {
fileService.downloadZipFile(request, response, fileRequest);
}
@Operation(description = "파일을 업로드 한다.")
@PostMapping("/fileUpload")
@ResponseStatus(HttpStatus.OK)
public Integer fileUpload(
HttpServletRequest request, HttpServletResponse response, FileRequest fileRequest)
throws Exception {
List<FileRequest> saveFileList = fileService.getUploadFileList(request, fileRequest);
Integer resultCnt = fileService.setFile(saveFileList, null);
return resultCnt;
}
}

View File

@ -0,0 +1,18 @@
package com.interplug.qcast.biz.file;
import com.interplug.qcast.biz.file.dto.FileRequest;
import com.interplug.qcast.biz.file.dto.FileResponse;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FileMapper {
Integer deleteFile(FileRequest fileDeleteReq) throws Exception;
Integer insertFile(FileRequest fileInsertReq) throws Exception;
FileResponse selectFile(FileRequest fileDeleteReq);
List<FileResponse> selectFileList(FileRequest fileDeleteReq);
}

View File

@ -0,0 +1,304 @@
package com.interplug.qcast.biz.file;
import com.interplug.qcast.biz.file.dto.FileRequest;
import com.interplug.qcast.biz.file.dto.FileResponse;
import com.interplug.qcast.config.Exception.ErrorCode;
import com.interplug.qcast.config.Exception.QcastException;
import com.interplug.qcast.config.message.Messages;
import com.interplug.qcast.util.ZipFileManager;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
@Slf4j
@Service
@RequiredArgsConstructor
public class FileService {
Messages message;
private final FileMapper fileMapper;
@Value("${file.ini.root.path}")
private String baseDirPath;
private ZipFileManager zipFileManager;
public List<FileRequest> getUploadFileList(HttpServletRequest request, FileRequest fileRequest)
throws Exception {
List<FileRequest> saveFileList = new ArrayList<>();
List<MultipartFile> multipartFileList = new ArrayList<>();
if (!MultipartHttpServletRequest.class.isAssignableFrom(request.getClass())) {
return new ArrayList<>();
}
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Map<String, List<MultipartFile>> mapMultipart = multipartRequest.getMultiFileMap();
if (mapMultipart.isEmpty()) {
return null;
}
String strParamKey;
for (String s : mapMultipart.keySet()) {
strParamKey = s;
multipartFileList.addAll(mapMultipart.get(strParamKey));
}
int iFileCnt = multipartFileList.size();
if (iFileCnt > 0) {
int iFileSeq = 1;
String strSeparator = File.separator;
String strFileFolderPath =
baseDirPath + strSeparator + fileRequest.getObjectNo() + strSeparator;
// planNo가 있는 경우
if (fileRequest.getPlanNo() != null && !fileRequest.getPlanNo().isEmpty()) {
strFileFolderPath += fileRequest.getPlanNo() + strSeparator;
}
log.info("### fileFolderPath : {}", strFileFolderPath);
// 파일 폴더 생성
makeFileFolder(strFileFolderPath);
for (MultipartFile multipartFile : multipartFileList) {
if (!multipartFile.isEmpty()) {
// 파일 확장자 발췌 확장자 소문자 변환
String strFileExt = multipartFile.getOriginalFilename();
if (strFileExt != null && !strFileExt.isEmpty()) {
strFileExt = strFileExt.substring(strFileExt.lastIndexOf(".") + 1).toLowerCase().trim();
} else {
strFileExt = "";
}
// 파일 확장자 사이즈 검증
List<String> listExtension = this.getFileExtension();
if (!validFileExtension(listExtension, strFileExt)) {
multipartFile.getInputStream().close();
continue;
}
String strSrcFileNm = multipartFile.getOriginalFilename();
File file = new File(strFileFolderPath, strSrcFileNm);
multipartFile.transferTo(file);
FileRequest fileSaveReq = new FileRequest();
fileSaveReq.setObjectNo(fileRequest.getObjectNo());
fileSaveReq.setPlanNo(fileRequest.getPlanNo());
fileSaveReq.setCategory(fileRequest.getCategory());
fileSaveReq.setFaileName(strSrcFileNm);
fileSaveReq.setUserId(fileRequest.getUserId());
saveFileList.add(fileSaveReq);
iFileSeq++;
}
}
}
return saveFileList;
}
// 파일 확장자 검증
private boolean validFileExtension(List<String> listExtension, String strFileExt)
throws Exception {
boolean bResult = true;
try {
if (strFileExt == null || strFileExt.isEmpty()) {
return false;
}
for (String s : listExtension) {
strFileExt = strFileExt.toLowerCase();
// 업로드 가능한 파일 확장자 검증
if (s.equals(strFileExt)) {
return true;
}
}
} catch (Exception e) {
log.error("### FileService - validFileExtension : Exception Error");
return false;
}
return bResult;
}
// 업로드 가능한 파일 확장자 조회
public List<String> getFileExtension() {
return Arrays.asList(
"mp3", "wma", "wav", "m4a", "3gp", "hwp", "doc", "docx", "ppt", "pptx", "zip", "xls",
"xlsx", "pdf", "txt", "jpg", "gif", "png");
}
// 파일 폴더 생성
private void makeFileFolder(String strMakeDir) {
try {
File dir = new File(strMakeDir.replace("//", "/"));
if (!dir.exists()) {
if (!dir.mkdirs()) {
log.error("### Failed to create directories: {}", strMakeDir);
throw new QcastException(ErrorCode.INTERNAL_SERVER_ERROR);
}
}
} catch (Exception e) {
log.error("### FileService - makeFileFolder : Exception Error");
}
}
// 파일정보 저장 삭제
public Integer setFile(List<FileRequest> saveFilelist, List<FileRequest> deleteFilelist)
throws Exception {
Integer iResult = 0;
// 파일 삭제
if (deleteFilelist != null && !deleteFilelist.isEmpty()) {
for (FileRequest fileDeleteReq : deleteFilelist) {
if (!"".equals(fileDeleteReq.getObjectNo())) {
fileMapper.deleteFile(fileDeleteReq); // 파일 삭제
}
}
}
// 파일 저장
if (saveFilelist != null && !saveFilelist.isEmpty()) {
for (FileRequest fileInsertReq : saveFilelist) {
if (!"".equals(fileInsertReq.getObjectNo()) && !"".equals(fileInsertReq.getCategory())) {
iResult += fileMapper.insertFile(fileInsertReq);
}
}
}
return iResult;
}
// 파일 1건 다운로드
public void downloadFile(
HttpServletRequest request, HttpServletResponse response, FileRequest fileRequest)
throws Exception {
// 필수값 체크
if (fileRequest.getObjectNo() == null || fileRequest.getObjectNo().isEmpty()) {
throw new QcastException(
ErrorCode.INVALID_INPUT_VALUE,
message.getMessage("common.message.required.data", "Object No"));
} else if (fileRequest.getNo() == 0) {
throw new QcastException(
ErrorCode.INVALID_INPUT_VALUE, message.getMessage("common.message.required.data", "No"));
}
// 파일정보 조회
FileResponse fileResponse = fileMapper.selectFile(fileRequest);
if (fileResponse == null) {
throw new QcastException(
ErrorCode.NOT_FOUND, message.getMessage(" common.message.file.download.exists"));
}
// 첨부파일 물리적 경로
String strSeparator = File.separator;
String filePath = baseDirPath + strSeparator + fileResponse.getObjectNo();
if (fileResponse.getPlanNo() != null && !fileResponse.getPlanNo().isEmpty()) {
filePath += strSeparator + fileResponse.getPlanNo();
}
filePath += strSeparator + fileResponse.getFaileName();
File file = new File(filePath);
if (!file.exists()) {
log.error("### File not found: {}", filePath);
throw new QcastException(ErrorCode.INTERNAL_SERVER_ERROR);
}
String mimeType = URLConnection.guessContentTypeFromName(file.getName());
if (mimeType == null) {
mimeType = "application/octet-stream";
}
String originalFileName = fileResponse.getFaileName();
String encodedFileName =
URLEncoder.encode(originalFileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
response.setHeader("Content-Transfer-Encoding", "binary;");
response.setHeader("Pragma", "no-cache;");
response.setHeader("Expires", "-1;");
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
response.setContentType(mimeType);
response.setContentLength((int) file.length());
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
FileCopyUtils.copy(inputStream, response.getOutputStream());
} catch (IOException e) {
throw new QcastException(
ErrorCode.INTERNAL_SERVER_ERROR,
message.getMessage("common.message.file.download.error"));
}
}
// 파일 N건 ZIP 다운로드
public void downloadZipFile(
HttpServletRequest request, HttpServletResponse response, FileRequest fileRequest)
throws Exception {
// 필수값 체크
if (fileRequest.getObjectNo() == null || fileRequest.getObjectNo().isEmpty()) {
throw new QcastException(
ErrorCode.INVALID_INPUT_VALUE,
message.getMessage("common.message.required.data", "Object No"));
}
String strZipFileName = fileRequest.getZipFileName();
if (strZipFileName == null || strZipFileName.isEmpty()) {
strZipFileName = "Download";
}
// 파일 조회
List<FileResponse> fileList = fileMapper.selectFileList(fileRequest);
if (fileList == null || fileList.size() == 0) {
throw new QcastException(
ErrorCode.NOT_FOUND, message.getMessage(" common.message.file.download.exists"));
}
// 첨부파일 물리적 경로
FileResponse fileResponse = fileList.get(0);
String zipFilePath = fileResponse.getObjectNo();
String filePath = baseDirPath + File.separator + fileResponse.getObjectNo() + File.separator;
if (fileResponse.getPlanNo() != null && !fileResponse.getPlanNo().isEmpty()) {
zipFilePath += File.separator + fileResponse.getPlanNo();
filePath += fileResponse.getPlanNo() + File.separator;
}
String finalFilePath = filePath;
String finalZipFilePath = zipFilePath;
List<Map<String, String>> listFile =
fileList.stream()
.map(
file -> {
Map<String, String> map = new HashMap<>();
map.put("directory", finalZipFilePath);
map.put("filename", finalFilePath + file.getFaileName());
return map;
})
.collect(Collectors.toList());
// zip 파일로 변환
zipFileManager.createZipFile(response, strZipFileName, listFile);
}
}

View File

@ -0,0 +1,35 @@
package com.interplug.qcast.biz.file.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
// @Data
@Getter
@Setter
public class FileRequest {
@Schema(description = "물건 번호")
private String objectNo;
@Schema(description = "PLAN 번호")
private String planNo;
@Schema(description = "파일 번호")
private int no;
@Schema(description = "카테고리")
private String category;
@Schema(description = "파일 이름")
private String faileName;
@Schema(description = "사용자(등록자,수정자) ID")
private String userId;
@Schema(description = "zip 파일 이름")
private String zipFileName;
@Schema(description = "planNo null 체크 Flag (NULL=1, NOT NULL=0)", defaultValue = "1")
private String planNoNullChkFlg = "1";
}

View File

@ -0,0 +1,41 @@
package com.interplug.qcast.biz.file.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
// @Data
@Getter
@Setter
public class FileResponse {
@Schema(description = "물건 번호")
private String objectNo;
@Schema(description = "PLAN 번호")
private String planNo;
@Schema(description = "파일 번호")
private int no;
@Schema(description = "카테고리")
private String category;
@Schema(description = "파일 이름")
private String faileName;
@Schema(description = "업로드 일시")
private String uploadDatetime;
@Schema(description = "사용자 ID")
private String userId;
@Schema(description = "삭제 플래그")
private String delFlg;
@Schema(description = "마지막 수정 일시")
private String lastEditDatetime;
@Schema(description = "마지막 수정자")
private String lastEditUser;
}

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.interplug.qcast.biz.file.FileMapper">
<select id="selectFile" parameterType="com.interplug.qcast.biz.file.dto.FileRequest" resultType="com.interplug.qcast.biz.file.dto.FileResponse">
/* sqlid : com.interplug.qcast.biz.file.selectFile */
SELECT
TOP 1
U.OBJECT_NO
, U.NO
, U.FAILE_NAME
, U.CATEGORY
FROM T_UPLOAD U WITH (NOLOCK)
WHERE U.OBJECT_NO = #{objectNo}
<if test="planNo != null and planNo != ''">
AND U.PLAN_NO = #{planNo}
</if>
<if test='(planNo == null or planNo != "" ) and planNoNullChkFlg == "1"'>
AND U.PLAN_NO IS NULL
</if>
<if test="no != null and no != ''">
AND U.NO = #{no}
</if>
<if test="category != null and category != ''">
AND U.CATEGORY = #{category}
</if>
</select>
<select id="selectFileList" parameterType="com.interplug.qcast.biz.file.dto.FileRequest" resultType="com.interplug.qcast.biz.file.dto.FileResponse">
/* sqlid : com.interplug.qcast.biz.file.selectFileList */
SELECT
U.OBJECT_NO
, U.PLAN_NO
, U.NO
, U.CATEGORY
, U.FAILE_NAME
, U.UPLOAD__DATETIME
, U.USER_ID
, U.DEL_FLG
, U.LAST_EDIT_DATETIME
, U.LAST_EDIT_USER
FROM T_UPLOAD U WITH (NOLOCK)
WHERE U.OBJECT_NO = #{objectNo}
<if test="planNo != null and planNo != ''">
AND U.PLAN_NO = #{planNo}
</if>
<if test='(planNo == null or planNo != "" ) and planNoNullChkFlg == "1"'>
AND U.PLAN_NO IS NULL
</if>
<if test="no != null and no != ''">
AND U.NO = #{no}
</if>
<if test="category != null and category != ''">
AND U.CATEGORY = #{category}
</if>
</select>
<insert id="insertFile" parameterType="com.interplug.qcast.biz.file.dto.FileRequest" >
/* sqlid : com.interplug.qcast.biz.file.insertFile */
INSERT INTO T_UPLOAD (
OBJECT_NO
, NO
, CATEGORY
, FAILE_NAME
, UPLOAD__DATETIME
, USER_ID
, DEL_FLG
, LAST_EDIT_DATETIME
, LAST_EDIT_USER
, PLAN_NO
) VALUES (
#{objectNo}
, (SELECT COALESCE(MAX(NO) + 1, 1) FROM T_UPLOAD WHERE OBJECT_NO = #{objectNo})
, #{category}
, #{faileName}
, GETDATE()
, #{userId}
, 0
, GETDATE()
, #{userId}
, #{planNo}
)
</insert>
<update id="deleteFile" parameterType="com.interplug.qcast.biz.file.dto.FileRequest" >
/* sqlid : com.interplug.qcast.biz.file.deleteFile */
UPDATE T_UPLOAD
SET
DEL_FLG = 1
, LAST_EDIT_DATETIME = GETDATE()
, LAST_EDIT_USER = #{userId}
WHERE OBJECT_NO = #{objectNo}
<if test="planNo != null and planNo != ''">
AND PLAN_NO = #{planNo}
</if>
<if test="no != null and no != ''">
AND NO = #{no}
</if>
</update>
</mapper>

View File

@ -33,7 +33,7 @@
, LAST_EDIT_DATETIME
) VALUES (
#{spnAttrCd}
, #{SpnTypeCd}
, #{spnTypeCd}
, #{useFlg}
, #{remarks}
, #{delFlg}