diff --git a/src/main/java/com/xit/biz/ctgy/controller/CtgyFileMgtController.java b/src/main/java/com/xit/biz/ctgy/controller/CtgyFileMgtController.java new file mode 100644 index 0000000..5bf612f --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/controller/CtgyFileMgtController.java @@ -0,0 +1,83 @@ +package com.xit.biz.ctgy.controller; + +import com.xit.biz.cmm.dto.CmmFileDto; +import com.xit.biz.cmm.entity.CmmFileMst; +import com.xit.biz.ctgy.dto.MinInfoBoard680Dto; + +import com.xit.biz.ctgy.entity.MinInfoBoard680; +import com.xit.biz.ctgy.service.ICtgyFileService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import com.xit.core.util.AssertUtils; +import com.xit.core.util.Checks; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Nonnull; +import java.util.List; + +@Tag(name = "CtgyFileMgtController", description = "파일 관리") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/ctgy/file") +public class CtgyFileMgtController { + private final ICtgyFileService fileService; + + @Operation(summary = "파일 조회" , description = "등록된 파일 조회") + @GetMapping("/{id}") + public ResponseEntity findFiles(@PathVariable("inCode") @NonNull final Long inCode) { + AssertUtils.isTrue(!Checks.isEmpty(inCode), "대상 게시글[inCode]을 선택해 주세요."); + return RestResponse.of(fileService.findFiles(inCode)); + } + + //@Operation(summary = "파일 저장" , description = "파일 저장") + //@PostMapping() +// public ResponseEntity saveFiles2(CmmFileMst cmmFileMst, @RequestParam("files") MultipartFile[] files) { +// AssertUtils.isTrue(!Checks.isEmpty(cmmFileMst), "파일 정보가 존재하지 않습니다."); +// AssertUtils.isTrue(!Checks.isEmpty(cmmFileMst.getFileCtgCd()), "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다."); +// AssertUtils.isTrue(!Checks.isEmpty(cmmFileMst.getFileBizId()), "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다."); +// AssertUtils.isTrue(!Checks.isEmpty(files), "대상 파일이 존재하지 않습니다."); +// return RestResponse.of(fileService.saveFiles(cmmFileMst, files)); +// +// RedirectAttributes redirectAttributes +// redirectAttributes.addFlashAttribute("message", +// "You successfully uploaded " + file.getOriginalFilename() + "!"); +// +// return "redirect:/"; +// } + + @Operation(summary = "파일 저장" , description = "파일 저장") + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity saveFiles(@Nonnull MinInfoBoard680Dto dto) { + AssertUtils.isTrue(!Checks.isEmpty(dto), "파일 정보가 존재하지 않습니다."); +// AssertUtils.isTrue(!Checks.isEmpty(cmmFileDto.getFileCtgCd()), "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다."); +// AssertUtils.isTrue(!Checks.isEmpty(cmmFileDto.getFileBizId()), "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다."); +// AssertUtils.isTrue(!Checks.isEmpty(cmmFileDto.getFiles()), "대상 파일이 존재하지 않습니다."); +// CmmFileMst cmmFileMst = CmmFileMst.builder() +// .fileMstId(cmmFileDto.getFileMstId()) +// .fileCtgCd(cmmFileDto.getFileCtgCd()) +// .fileBizId(cmmFileDto.getFileBizId()) +// .build(); + MinInfoBoard680 minInfoBoard680 = MinInfoBoard680.builder() + .inCode(dto.getInCode()) + .inBgubun(dto.getInBgubun()) + .inTitle(dto.getInTitle()) + .inContents(dto.getInContents()) + .build(); + //fileService.saveFiles(minInfoBoard680, dto.getFiles()); + return RestResponse.of(fileService.saveFiles(minInfoBoard680, dto.getFiles())); + +// RedirectAttributes redirectAttributes +// redirectAttributes.addFlashAttribute("message", +// "You successfully uploaded " + file.getOriginalFilename() + "!"); +// +// return "redirect:/"; + } +} diff --git a/src/main/java/com/xit/biz/ctgy/dto/MinInfoBoard680Dto.java b/src/main/java/com/xit/biz/ctgy/dto/MinInfoBoard680Dto.java index 0ea5f8d..4023e44 100644 --- a/src/main/java/com/xit/biz/ctgy/dto/MinInfoBoard680Dto.java +++ b/src/main/java/com/xit/biz/ctgy/dto/MinInfoBoard680Dto.java @@ -5,8 +5,10 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.springframework.web.multipart.MultipartFile; import javax.persistence.Column; +import javax.persistence.Transient; import java.io.Serializable; @Schema(name = "MinInfoBoard680Dto", description = "공지사항") @@ -58,4 +60,8 @@ public class MinInfoBoard680Dto implements Serializable { @Schema(required = false, title = "기타", example = " ", description = "Input Description...") private String inEtc; + + //@Transient + @Schema(required = false, title = "파일", example = " ", description = "파일") + private MultipartFile[] files; } diff --git a/src/main/java/com/xit/biz/ctgy/entity/MinInfoBoard680.java b/src/main/java/com/xit/biz/ctgy/entity/MinInfoBoard680.java index 9559938..b204cf1 100644 --- a/src/main/java/com/xit/biz/ctgy/entity/MinInfoBoard680.java +++ b/src/main/java/com/xit/biz/ctgy/entity/MinInfoBoard680.java @@ -1,17 +1,19 @@ package com.xit.biz.ctgy.entity; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.*; import javax.persistence.*; import java.io.Serializable; @Schema(name = "MinInfoBoard680", description = "공지사항") -@NoArgsConstructor -@Data -@Entity @Table(name = "min_info_board680", schema = "", catalog = "") +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder public class MinInfoBoard680 implements Serializable { private static final long SerialVersionUID = 1L; @@ -71,6 +73,4 @@ public class MinInfoBoard680 implements Serializable { @Schema(required = false, title = "기타", example = " ", description = "Input Description...") @Column(name = "in_etc", nullable = true) private String inEtc; - - } diff --git a/src/main/java/com/xit/biz/ctgy/service/ICtgyFileService.java b/src/main/java/com/xit/biz/ctgy/service/ICtgyFileService.java new file mode 100644 index 0000000..c73917f --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/service/ICtgyFileService.java @@ -0,0 +1,18 @@ +package com.xit.biz.ctgy.service; + +import com.xit.biz.cmm.entity.CmmFileMst; +import com.xit.biz.ctgy.entity.MinInfoBoard680; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-16 + */ +public interface ICtgyFileService { + MinInfoBoard680 findFiles(Long inCode); + + List saveFiles(@Nonnull MinInfoBoard680 minInfoBoard680, MultipartFile[] files); +} diff --git a/src/main/java/com/xit/biz/ctgy/service/impl/CtgyFileService.java b/src/main/java/com/xit/biz/ctgy/service/impl/CtgyFileService.java new file mode 100644 index 0000000..c004b03 --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/service/impl/CtgyFileService.java @@ -0,0 +1,145 @@ +package com.xit.biz.ctgy.service.impl; + +import com.xit.biz.cmm.entity.CmmFileDtl; +import com.xit.biz.cmm.entity.CmmFileMst; +import com.xit.biz.cmm.entity.ids.CmmFileDtlIds; +import com.xit.biz.cmm.repository.ICmmFileDtlRepository; +import com.xit.biz.cmm.repository.ICmmFileMstRepository; +import com.xit.biz.cmm.service.ICmmFileService; +import com.xit.biz.ctgy.CtgyConstants; +import com.xit.biz.ctgy.entity.MinInfoBoard680; +import com.xit.biz.ctgy.entity.Tf680Recall; +import com.xit.biz.ctgy.repository.IPublicBoardRepository; +import com.xit.biz.ctgy.service.ICtgyFileService; +import com.xit.core.support.jpa.JpaUtil; +import com.xit.core.util.AssertUtils; +import com.xit.core.util.CommUtil; +import com.xit.core.util.DateUtil; +import io.jsonwebtoken.lang.Assert; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CtgyFileService implements ICtgyFileService { + + @Value("${file.cmm.upload.root:c:/data/file/upload}") + private String rootPath; + + @Value("${file.cmm.upload.path:/kangnamSIM/simUpFile/}") + private String uploadPath; + + @Value("${file.cmm.upload.url}") + private String serviceUrl; + + @Value("${file.cmm.upload.allow.ext:}") + private String allowExt; + + @Value("${file.cmm.upload.max.size:1024}") + private long maxSize; + + private final IPublicBoardRepository repository; + + @Override + public MinInfoBoard680 findFiles(Long inCode) { + Assert.notNull(inCode, "대상 게시글[inCode]을 선택해 주세요."); + + return repository.findById(inCode).orElse(null); + } + + /** + * 파일 등록 + * 신규등록시는 CmmFileMst.fileMstId가 null, 변경시 not null + * 변경시 동일한 파일 이름이 존재하면 DB 및 해당 파일 삭제후 신규로 생성 + * + * @param entity MinInfoBoard680 + * @param files MultipartFile[] + * @return CmmFileMst + */ + @Override + @Transactional + public List saveFiles(@Nonnull MinInfoBoard680 entity, MultipartFile[] files) { + String makePath = ""; + + makePath = File.separator + DateUtil.getToday(""); + + // 파일 경로 : upload root 제외 + String urlPath = this.uploadPath + makePath; + // 물리적인 파일 저장 위치 + String fileUploadPath = this.rootPath + urlPath; + File file = new File(fileUploadPath); + if(!file.exists()) file.mkdirs(); + + List entityList = new ArrayList<>(); + for(MultipartFile mf : files){ + if(!mf.isEmpty()) { + String orgFileName = ""; + try { + orgFileName = StringUtils.cleanPath(Objects.requireNonNull(mf.getOriginalFilename())); + + // 파일 저장 && 전송 + MinInfoBoard680 savedEntity = repository.findById(entity.getInCode()).orElseGet(MinInfoBoard680::new); + savedEntity.setInFilename(orgFileName); + savedEntity.setInFilesize(mf.getSize()); + savedEntity.setInFileurl(serviceUrl+urlPath); + JpaUtil.saveIfNullId(savedEntity.getInCode(), repository, savedEntity); + + entityList.add(savedEntity); + mf.transferTo(new File(fileUploadPath + File.separator + orgFileName)); + + // inputStream을 가져와서 + // copyOfLocation (저장위치)로 파일을 쓴다. + // copy의 옵션은 기존에 존재하면 REPLACE(대체한다), 오버라이딩 한다 + //Files.copy(multipartFile.getInputStream(), copyOfLocation, StandardCopyOption.REPLACE_EXISTING); + + }catch(IOException e){ + String errMsg = String.format("File Upload Error :: %s", orgFileName); + //TODO : 에러처리 + //return RestError.of(String.format("File Upload Error :: %s", orgFileName)); + AssertUtils.isTrue(false, String.format("File Upload Error :: %s", orgFileName)); + } + } + } + return entityList; + } + +// @Transactional +// public void removeExistsUploadFile(String fileMstId, String orgFileNm){ +// CmmFileDtl cmmFileDtl = cmmFileDtlRepository.findByFileMstIdAndOrgFileNmIgnoreCase(fileMstId, orgFileNm); +// if(cmmFileDtl != null){ +// CmmFileDtlIds cmmFileDtlIds = new CmmFileDtlIds(); +// cmmFileDtlIds.setFileMstId(fileMstId); +// cmmFileDtlIds.setFileId(cmmFileDtl.getFileId()); +// cmmFileDtlRepository.deleteById(cmmFileDtlIds); +// new File(this.uploadPath + cmmFileDtl.getFileUpldPath() + File.separator + cmmFileDtl.getFileId()).delete(); +// } +// } +} + +/* + // File.seperator 는 OS종속적이다. + // Spring에서 제공하는 cleanPath()를 통해서 ../ 내부 점들에 대해서 사용을 억제한다 + Path copyOfLocation = Paths.get(uploadDir + File.separator + StringUtils.cleanPath(multipartFile.getOriginalFilename())); + try { + // inputStream을 가져와서 + // copyOfLocation (저장위치)로 파일을 쓴다. + // copy의 옵션은 기존에 존재하면 REPLACE(대체한다), 오버라이딩 한다 + Files.copy(multipartFile.getInputStream(), copyOfLocation, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + e.printStackTrace(); + throw new FileStorageException("Could not store file : " + multipartFile.getOriginalFilename()); + } +*/ diff --git a/src/main/java/com/xit/core/config/filter/log/ReadableRequestWrapperFilter.java b/src/main/java/com/xit/core/config/filter/log/ReadableRequestWrapperFilter.java index 54449b3..6078763 100644 --- a/src/main/java/com/xit/core/config/filter/log/ReadableRequestWrapperFilter.java +++ b/src/main/java/com/xit/core/config/filter/log/ReadableRequestWrapperFilter.java @@ -9,6 +9,8 @@ import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import javax.servlet.*; @@ -36,8 +38,14 @@ public class ReadableRequestWrapperFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - ReadableRequestWrapper wrapper = new ReadableRequestWrapper((HttpServletRequest) request); - chain.doFilter(wrapper, response); + + // Multipart skip + //if(!Objects.equals(request.getContentType(), MediaType.MULTIPART_FORM_DATA_VALUE)) { + ReadableRequestWrapper wrapper = new ReadableRequestWrapper((HttpServletRequest) request); + chain.doFilter(wrapper, response); + //}else{ + // chain.doFilter(request, response); + //} } @Override diff --git a/src/main/java/com/xit/core/config/filter/xss/RequestBodyLucyXssFilter.java b/src/main/java/com/xit/core/config/filter/xss/RequestBodyLucyXssFilter.java index ca12a76..d68ab29 100644 --- a/src/main/java/com/xit/core/config/filter/xss/RequestBodyLucyXssFilter.java +++ b/src/main/java/com/xit/core/config/filter/xss/RequestBodyLucyXssFilter.java @@ -4,6 +4,7 @@ import com.xit.core.util.Checks; import org.apache.commons.lang3.StringUtils; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; import javax.servlet.*; import javax.servlet.annotation.WebFilter; @@ -13,6 +14,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.Objects; /** @@ -25,14 +27,19 @@ import java.util.List; initParams = {@WebInitParam(name = "extUrl", value = StringUtils.EMPTY)}, urlPatterns = "/api/*" ) -@Order(Ordered.HIGHEST_PRECEDENCE) +// Multipart 처리시 우선 순위가 뒤에 와야함 +@Order(Ordered.LOWEST_PRECEDENCE) public class RequestBodyLucyXssFilter implements Filter { private List extUrl; @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { - +// Multipart skip +// if(Objects.equals(req.getContentType(), MediaType.MULTIPART_FORM_DATA_VALUE)) { +// chain.doFilter(req, res); +// return; +// } HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; RequestLucyXssWrapper reqWrapper = null; diff --git a/src/main/resources/config/conf.yml b/src/main/resources/config/conf.yml index 29e8af5..4db8931 100644 --- a/src/main/resources/config/conf.yml +++ b/src/main/resources/config/conf.yml @@ -17,8 +17,12 @@ api: file: cmm: upload: - allow: - ext: + root: c:/data/file/upload + # 공지사항 + path: /kangnamSIM/simUpFile + url: http://traffic.gangnam.go.kr + allow: + ext: max: - size: 10000000 - path: d:/data/file/upload + size: 10000000 +