Initial commit

This commit is contained in:
2026-05-29 17:49:25 +09:00
commit 330105cb27
1081 changed files with 148694 additions and 0 deletions
@@ -0,0 +1,99 @@
= 엑셀 다운로드 및 업로드
== 개요
SDL 6.0은 사용자 관리, 외부 사용자 관리, 역할 관리, 메뉴 사용 이력, 파일 다운로드 이력, 코드 관리, 링크사이트 관리에서 엑셀 다운로드를 지원한다. +
그 외 메뉴에서 엑셀 다운로드 기능을 적용하려면 <<_공통컴포넌트_유틸,공통컴포넌트 & 유틸>>의 <<_excel_download_button,Excel Download Button>>, <<_excel_upload_button,Excel Upload Button>> 매뉴얼을 참고한다.
== API
.ExcelController.java
. 엑셀 다운로드 +
GET /excel/excel-download +
. 엑셀 업로드 +
POST /excel/excel-upload
== 엑셀 다운로드
=== excel.xml 설정
sdl-base/src/main/resources/excel 폴더에 다운로드 할 기능의 xml 양식을 만든다. +
다국어 적용(message.properties)이 된다.
. 대외비 표기여부, 시트보호 여부, 제목
[source,xml]
----
<CONFIDENTIAL>true</CONFIDENTIAL> <!--> 대외비 표기여부(false : 태그작성 x) <-->
<PROTECTION>true</PROTECTION> <!--> 시트보호 여부(false : 태그작성 x) <-->
<PASSWORD>sdl</PASSWORD> <!--> 시트보호 암호(PROTECTION = false : 태그작성 x) <-->
<TITLE>sdl.excel.user.title</TITLE> <!--> 제목 <-->
----
[start=2]
. 문서 Comment 출력
.. 코멘트를 여러개 작성 가능
[source,xml]
----
<COMMENTS>
<COMMENT>
<COMMENT_LABEL>sdl.excel.accessLog.comment</COMMENT_LABEL> <!--> 코멘트 <-->
<COMMENT_FONT_COLOR>10</COMMENT_FONT_COLOR> <!--> 코멘트 글자색 <-->
</COMMENT>
<COMMENT>
<COMMENT_LABEL>2번째 코멘트</COMMENT_LABEL>
<COMMENT_FONT_COLOR>10</COMMENT_FONT_COLOR>
</COMMENT>
</COMMENTS>
----
[start=3]
. 헤더
[source,xml]
----
<HEADERS>
<HEADER>
<HEADER_LABEL>NO.</HEADER_LABEL> <!--> 컬럼명 <-->
<ROWSPAN>3</ROWSPAN> <!--> 열병합 <-->
<HEADER_FONT_COLOR>8</HEADER_FONT_COLOR> <!--> 컬럼 글자색 <-->
<HEADER_BACKGROUND_COLOR>44</HEADER_BACKGROUND_COLOR> <!--> 컬럼 배경색 <-->
</HEADER>
<HEADER>
<HEADER_LABEL>sdp.user.label.compName</HEADER_LABEL>
<COLSPAN>3</COLSPAN> <!--> 행병합 <-->
</HEADER>
</HEADERS>
----
[start=4]
. 컬럼
.. 첫번째 Row 에 No. 필드 넣을 경우 No. 필드에 대한 <COLUMN>를 작성하지 않아도 된다.
[source,xml]
----
<COLUMNS>
<COLUMN>
<FIELD_NAME>compName</FIELD_NAME> <!--> HEADER_LABEL에 대응하는 엔티티명 <-->
<COLUMN_WIDTH>15</COLUMN_WIDTH> <!--> 컬럼 너비 <-->
<CELL_ALIGN>LEFT</CELL_ALIGN> <!--> 셀 정렬 <-->
<COLUMN_LOCKING>true</COLUMN_LOCKING> <!--> 셀 잠금 여부(false : 태그작성 x) <-->
<COLUMN_HIDDEN>true</COLUMN_HIDDEN> <!--> 셀 숨김 여부(false : 태그작성 x) <-->
<COLUMN_TYPE>Number</COLUMN_TYPE> <!--> 날짜 형식(Date), 숫자 형식(Number)으로 출력 <-->
<CELL_FONT_COLOR>10</CELL_FONT_COLOR> <!--> 컬럼 글자색 <-->
<BACKGROUND_COLOR>13</BACKGROUND_COLOR> <!--> 컬럼 배경색 <-->
</COLUMN>
</COLUMNS>
----
IMPORTANT: 간혹 다운로드 받은 엑셀에 초록색 경고가 뜨는데 이것은 DB에 문자로 저장된 숫자를 불러오기 때문이다. +
따라서 <COLUMN_TYPE>을 'Number'로 하면 엑셀에서도 숫자로 잘 나올 것이다.
== 엑셀 업로드
=== excelUploadSample.xml 설정
SampleExcelUpload.vue 파일에 샘플로 엑셀 업로드가 구현되어 있다.
* 업로드할 데이터의 양식에 맞추어 excel.xml을 만들고 업로드 한다.
** 업로드 excel.xml 파일은 다운로드 excel.xml 파일과 같은 경로에 만든다.
** 날짜 형식 데이터가 업로드되지 않는 경우 `yyyy-MM-dd` 형태로 입력하거나 텍스트 서식으로 입력한다.
@@ -0,0 +1,211 @@
= 파일 업/다운로드
== 개요
클라이언트의 파일 업/다운로드 API 호출을 처리한다.
=== 파일 업로드
단일 파일 업로드를 처리한다. 하나의 파일만 업로드하는 경우가 아니라면 보통은 멀티 파일 업로드를 이용한다.
=== 멀티 파일 업로드
====
컨트롤러에서 파일 업로드 서비스를 호출한다.
.FileManagerController
[source,java]
----
@PostMapping("/resource/attachments/multifile-upload")
public List<AttachFile> uploadMultiFile(
@Parameter(description = "MultipartFile[]", required = true) @RequestParam(required = true) MultipartFile[] files,
@Parameter(description = "다운로드 구분 (컴포넌트 한개 이상일 경우 구분)", required = true) @RequestParam(required = true) String downloadType) {
return fileManagerService.save(downloadType, Arrays.asList(files));
}
----
====
====
서비스에서 지정된 경로에 파일 리소스를 저장하고, 업로드된 파일 정보를 DB 에 저장한다.
.FileManagerServiceImpl
[source,java]
----
/**
* 기본 업로드 패스 설정 값
*/
@Value("${common.upload-path}")
private String fileUploadPath;
/**
* 업로드 루트 하위 폴더 자릿수 설정 값
*/
@Value("${common.upload.directory-name-len}")
private int directoryNameLen;
/**
* 사용자지정 업로드 패스 설정 여부
*/
@Value("${custom.upload-path.enabled}")
private boolean customUploadPathEnabled;
/**
* 사용자지정 업로드 패스 설정 값
*/
@Value("${custom.upload-path}")
private String customUploadPaths;
@Override
@Transactional
public List<AttachFile> save(String downloadType, List<MultipartFile> files) {
return this.save(downloadType, null, files);
}
@Override
@Transactional
public List<AttachFile> save(String downloadType, String refId, List<MultipartFile> files) {
List<AttachFile> attachFileList = new ArrayList<>();
files.stream().forEach(file -> {
AttachFile attachFile = this.save(downloadType, refId, file);
attachFileList.add(attachFile);
});
return attachFileList;
}
@Override
@Transactional
public AttachFile save(String downloadType, String refId, MultipartFile file) {
String fileId = this.store(file, downloadType); // <1>
AttachFile attachFile = new AttachFile();
attachFile.setDownloadType(downloadType);
attachFile.setFilePathName(this.rootUploadPath.toString());
attachFile.setFileExtensionName(fileId);
attachFile.setFileName(file.getOriginalFilename());
attachFile.setFileMimeTypeName(file.getContentType());
attachFile.setFileSize(file.getSize());
attachFile.setOwnerObjectPkId(refId);
return fileManagerDao.insertFileInfo(attachFile); // <2>
}
----
<1> 파일 리소스 저장
<2> 업로드된 파일 정보 DB저장
====
====
파일 리소스 저장 경로는 기본 업로드 패스 값(common.upload-path)과 하위 디렉토리 길이 설정 값(common.upload.directory-name-len)에 따라 결정된다. +
기본 업로드 패스외에 사용자정의 업로드 패스 설정도 가능하다.
.config.properties
[source,properties]
----
## File Attach Configuration
common.upload-path=/NAS/SDL/upload // <1>
common.upload.directory-name-len=2 // <2>
# custom upload path 설정
custom.upload-path.enabled=false // <3>
custom.upload-path=\ // <4>
notice=/NAS/SDL/upload/notice,\
faq=/NAS/SDL/upload/faq
----
<1> 기본 업로드 패스 설정
<2> 업로드 패스 하위 디렉토리 길이 +
파일명(UUID)에서 이 길이 만큼 잘라서 기본 업로드 패스 하위 디렉토리가 생성된다.
<3> 사용자정의 업로드 패스 사용 여부
<4> 파일 컴포넌트별 사용자정의 업로드 패스 설정 (custom.upload-path.enabled=true 일 경우 적용됨)
====
=== 파일 다운로드
컨트롤러에서 파일 다운로드 서비스를 호출한다.
.FileManagerController
[source,java]
----
@GetMapping("/resource/attachments/file-download/{fileId}")
public ResponseEntity<Resource> downloadFile(
@Parameter(description = "File ID", required = true) @PathVariable String fileId,
@Parameter(description = "다운로드 구분 (컴포넌트 한개 이상일 경우 구분)", required = true) @RequestParam(required = true) String downloadType,
HttpServletRequest request) {
long startTime = System.nanoTime();
if (StringUtils.isBlank(fileId)) {
throw new FileManagerException("No File ID");
}
AttachFile attachFile = fileManagerService.getAttachFile(fileId, downloadType); // <1>
if (attachFile == null) {
throw new FileManagerException("Cannot find file info: " + fileId);
}
Resource resource = fileManagerService.getResource(attachFile.getFileExtensionName(), attachFile.getFilePathName()); // <2>
String contentType = attachFile.getFileMimeTypeName();
if (StringUtils.isBlank(contentType)) {
contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
}
String fileName = attachFile.getFileName(); // <3>
String encodeFileName = null;
// 다운로드 파일명 UTF-8 인코딩
encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
// 다운로드 이력 로깅
fileManagerService.saveFileDownloadLog(fileName, startTime, request); // <4>
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header("Content-Transfer-Encoding", "binary")
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodeFileName + "\"")
.body(resource);
}
----
<1> 파일 정보 조회
<2> 파일 리소스 가져오기
<3> 파일 업로드 시점의 파일명
<4> 파일 다운로드 이력 로깅
=== 멀티 파일 다운로드
여러 파일을 zip 파일 형태로 다운로드할 수 있도록 제공한다.
.FileManagerController
[source,java]
----
@Value("${common.download.zipfilename}")
private String zipFileName;
@GetMapping("/resource/attachments/multifile-download")
public ResponseEntity<ByteArrayResource> downloadMultiFile(
@Parameter(description = "File IDs", required = true) @RequestParam(required = true) String[] fileIds,
@Parameter(description = "다운로드 구분 (컴포넌트 한개 이상일 경우 구분)", required = true) @RequestParam(required = true) String downloadType,
@Parameter(description = "zip 파일명", required = false) @RequestParam(required = false) String zipFileName,
HttpServletRequest request) {
long startTime = System.nanoTime();
if (fileIds == null || fileIds.length == 0) {
throw new FileManagerException("No File ID(s)");
}
if (StringUtils.isBlank(zipFileName)) {
zipFileName = this.zipFileName;
}
// 다운로드 파일명 UTF-8 인코딩
String fileName = null;
fileName = URLEncoder.encode(zipFileName, StandardCharsets.UTF_8).replace("+", "%20");
// 다운로드 이력 로깅
fileManagerService.saveFileDownloadLog(zipFileName, startTime, request);
ByteArrayResource baResource = fileManagerService.getByteArrayResource(fileIds, downloadType);
return ResponseEntity.ok()
.contentLength(baResource.contentLength())
.contentType(MediaType.parseMediaType("application/zip"))
.header("Content-Transfer-Encoding", "binary")
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + ".zip\";")
.body(baResource);
}
----