카테고리 없음

Nginx를 사용한 스토리지 서버 구축 및 테스트 API 구현

프로틴형님 2024. 9. 23. 12:24

Nginx 컨테이너 띄우기

docker-compose.yml

services:
  nginx:
    image: nginx
    container_name: nginx-file-server
    restart: always
    ports:
      - "40405:40405"
    volumes:
      - /nginx/file:/storage

default.conf 설정

/etc/nginx/conf.d/default.conf 파일

server {
    listen       40405;
    listen  [::]:40405;
    server_name  localhost;
    charset utf-8;

    access_log  /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log debug;

    client_max_body_size 500M;
    dav_methods PUT DELETE;
    dav_access user:rw group:rw all:r;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    location /storage/ {
        root /;
        client_max_body_size 500M;
        charset utf-8;

        # Enable DAV methods for file uploads
        dav_methods PUT DELETE;
        create_full_put_path on;
        dav_access user:rw group:rw all:r;


        access_log  /var/log/nginx/data-access.log;
        error_log /var/log/nginx/data-error.log debug;
    }

    location /file {
        alias /storage;
        autoindex on;
        charset utf-8;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

설정하고 reload

nginx -s reload

Controller (TEST API)

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("${api_prefix}/v1")
public class FileController {

    private final FileService fileService;

    @Operation(summary = "컨텐츠 업로드")
    @PostMapping(value = "/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ApiResponse<?> uploadFile(@AuthenticationPrincipal AuthMember authMember,
                                     @RequestParam MultipartFile file) throws IOException {
        fileService.uploadFile(authMember, file);
        return ApiResponse.createSuccessWithNoContent(ResponseCode.RESOURCE_CREATED);
    }

    @Operation(summary = "저장된 모든 컨텐츠 삭제")
    @DeleteMapping("/file/all")
    public ApiResponse<?> deleteAll(@AuthenticationPrincipal AuthMember authMember) {
        fileService.deleteAllFiles();
        return ApiResponse.createSuccessWithNoContent(ResponseCode.SUCCESS);
    }

    @Operation(summary = "특정 컨테츠 업데이트")
    @PutMapping(value = "/file/all", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ApiResponse<?> updateAll(@AuthenticationPrincipal AuthMember authMember,
                                    @RequestParam MultipartFile file) throws IOException {
        fileService.updateAllFiles(authMember, file);
        return ApiResponse.createSuccessWithNoContent(ResponseCode.SUCCESS);
    }

    @Operation(summary = "컨텐츠 다운로드")
    @GetMapping(value = "/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public ResponseEntity<byte[]> downloadFile(@AuthenticationPrincipal AuthMember authMember,
                                               @ModelAttribute FileDownloadRequest downloadRequest) {
        FileDownloadResponse fileData = fileService.downloadFile(authMember, downloadRequest);
        byte[] file = fileData.getFileContent();
        log.info("파일 다운로드 성공 : " + fileData.getFileName());

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileData.getFileName() + "\"")
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .contentLength(file.length)
                .body(file);
    }
}

OpenFeign 설정

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

@FeignClient(name = "nginxFileClient", url = "${nginx.url}")
public interface NginxFileClient {
    @PutMapping(value = "/storage/{filePath}", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    void uploadFile(@PathVariable("filePath") String filePath, @RequestBody byte[] file);

    @DeleteMapping("/storage/{filePath}")
    void deleteFile(@PathVariable("filePath") String filePath);

    @GetMapping(value = "/file/{filePath}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    byte[] downloadFile(@PathVariable("filePath") String filePath);
}

Service

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class FileService {
    private final FileRepository fileRepository;
    private final NginxFileClient nginxFileClient;

    private final String PREFIX = "/storage";

    @Value("${nginx.path}")
    private String basePath;

    @Value("${nginx.url}")
    private String baseUrl;

    public void uploadFile(AuthMember authMember, MultipartFile file) throws IOException {
        if (file.isEmpty()) throw new CustomBadRequestException("파일이 첨부되지 않았습니다.");

        String fileName = generateFileName(file.getOriginalFilename());
        String filePath = basePath + fileName;
        String fileUrl = baseUrl + PREFIX + filePath;

        /**
         * 추후 들어오는 이미지 용도에 따라 filePath 관리
         */

        log.info("fileName : " + filePath);
        nginxFileClient.uploadFile(filePath, file.getBytes());
        log.info("nginx file 업로드 성공");

        File uploadFile = File.builder()
                .fileName(fileName)
                .filePath(filePath)
                .fileUrl(fileUrl)
                .fileSize(file.getSize())
                .build();

        /**
         * 추후 user랑 file 연결
         */

        fileRepository.save(uploadFile);
        log.info("파일 생성 완료");
    }

    public void deleteFile(String filePath) {
        log.info(filePath);
        nginxFileClient.deleteFile(filePath);
        log.info("nginx file 삭제 성공: {}", filePath);
    }

    public void updateFile(String oldFilePath, MultipartFile newFile) throws IOException {
        if (newFile.isEmpty()) throw new CustomBadRequestException("새로운 파일이 첨부되지 않았습니다.");

        deleteFile(oldFilePath);
        log.info("기존 파일 삭제 성공: {}", oldFilePath);

        String newFileName = generateFileName(newFile.getOriginalFilename());
        String newFilePath = basePath + newFileName;
        String newFileUrl = baseUrl + PREFIX + newFilePath;

        /**
         * 추후 들어오는 이미지 용도에 따라 filePath 관리
         */

        nginxFileClient.uploadFile(newFilePath, newFile.getBytes());
        log.info("nginx에 새로운 파일 업로드 성공: {}", newFilePath);

        File updatedFile = File.builder()
                .fileName(newFileName)
                .filePath(newFilePath)
                .fileUrl(newFileUrl)
                .fileSize(newFile.getSize())
                .build();

        fileRepository.deleteByFilePath(oldFilePath);
        fileRepository.save(updatedFile);
        log.info("파일 정보 업데이트 완료: {}", newFilePath);
    }

    public FileDownloadResponse downloadFile(AuthMember authMember, FileDownloadRequest fileDownloadRequest) {
        String fileUrl = fileDownloadRequest.fileUrl().replace("/file/", "/storage/");
        File file = fileRepository.findByFileUrl(fileUrl)
                .orElseThrow(() -> new CustomBadRequestException("해당 파일이 존재하지 않습니다."));

        return FileDownloadResponse.builder()
                .fileContent(nginxFileClient.downloadFile(file.getFilePath()))
                .fileName(file.getFileName())
                .build();
    }

    public void updateAllFiles(AuthMember authMember, MultipartFile newFile) throws IOException {
        List<File> files = fileRepository.findAll();
        files.stream().forEach(file -> {
            try {
                updateFile(file.getFilePath(), newFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    public void deleteAllFiles() {
        List<File> files = fileRepository.findAll();
        files.stream().forEach(file -> {
            deleteFile(file.getFilePath());
            fileRepository.delete(file);
        });
    }

    private String generateFileName(String fileName) {
        String uniqueId = UUID.randomUUID().toString();
        String extension = fileName.substring(fileName.lastIndexOf("."));
        String originalName = fileName.substring(0, fileName.lastIndexOf("."));
        return originalName + "_" + uniqueId + extension;
    }
}

 

레퍼런스

Nginx Static Contents Server