package com.ga.common.file; import com.ga.common.exception.BizException; import com.ga.common.exception.ErrorCode; import com.ga.common.util.SecurityUtil; 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.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDate; import java.util.List; import java.util.UUID; /** * 파일 업로드/다운로드. 로컬 파일시스템 저장 (S3 등으로 교체 가능한 구조). */ @Slf4j @Service @RequiredArgsConstructor public class FileService { private final FileMapper mapper; @Value("${ga.file.upload-dir:./uploads}") private String uploadDir; @Transactional public FileVO upload(MultipartFile file, String fileGroup) { if (file == null || file.isEmpty()) { throw new BizException(ErrorCode.FILE_UPLOAD_FAIL, "빈 파일입니다"); } String original = file.getOriginalFilename(); if (original == null) original = "unknown"; String ext = extension(original); String stored = UUID.randomUUID() + (ext.isEmpty() ? "" : "." + ext); String relPath = LocalDate.now().toString().replace("-", "/"); Path dir = Paths.get(uploadDir, relPath); try { Files.createDirectories(dir); Path dest = dir.resolve(stored); file.transferTo(dest.toFile()); FileVO vo = new FileVO(); vo.setFileGroup(fileGroup); vo.setOriginalName(original); vo.setStoredName(stored); vo.setFilePath(dest.toAbsolutePath().toString()); vo.setFileSize(file.getSize()); vo.setContentType(file.getContentType()); vo.setExtension(ext); vo.setCreatedBy(SecurityUtil.getCurrentUserId()); vo.setCreatedAt(java.time.LocalDateTime.now()); mapper.insert(vo); return vo; } catch (IOException e) { log.error("File upload error", e); throw new BizException(ErrorCode.FILE_UPLOAD_FAIL, e.getMessage()); } } public FileVO download(Long fileId) { FileVO vo = mapper.selectById(fileId); if (vo == null) throw new BizException(ErrorCode.FILE_NOT_FOUND); mapper.incrementDownloadCount(fileId); return vo; } public List listByGroup(String fileGroup) { return mapper.selectByGroup(fileGroup); } @Transactional public void delete(Long fileId) { FileVO vo = mapper.selectById(fileId); if (vo == null) throw new BizException(ErrorCode.FILE_NOT_FOUND); mapper.markDeleted(fileId); // 물리 삭제는 배치로 처리 } private String extension(String name) { int idx = name.lastIndexOf('.'); return idx >= 0 ? name.substring(idx + 1).toLowerCase() : ""; } }