berryberrybin / board-project

0 stars 0 forks source link

[nginx] url 서버 이미지 불러오기 #28

Open berryberrybin opened 1 year ago

berryberrybin commented 1 year ago

Nginx를 이용한 이미지 서버 ⎻ Nginx 기본 설정 파일 작성

wapl-pay-server > MediaService > config > fastcgi.conf

wapl-pay-server > MediaService > config > http.conf

server {
    listen 8888;
    listen [::]:8888;
    server_name localhost;

    # body size 최대 10M
    client_max_body_size 10M; 

    # url 재작성
    rewrite "^/images/([0-9a-z]{2})([0-9a-z]{2})([0-9a-z]{2})([0-9a-z]{2})([0-9a-z]{2})([0-9a-z]{2})(.*)$" /images/download/$1/$2/$3/$4/$5/$6/$7 last;
    rewrite "^/documents/([0-9a-z]{2})([0-9a-z]{2})([0-9a-z]{2})([0-9a-z]{2})([0-9a-z]{2})([0-9a-z]{2})(.*)$" /documents/download/$1/$2/$3/$4/$5/$6/$7 last;
    rewrite_log on;

    # CGI script
    location ~ \.py$ {
        include conf.d/fastcgi.conf;
    }

    # 이미지 단건 업로드
    location /images/upload {
        include conf.d/upload.conf;
        upload_store /upload;
        upload_pass /upload.py;
    }

    # 이미지 다중 업로드
    location /images/upload_many {
        include conf.d/upload.conf;
        upload_store /upload;
        upload_pass /upload_many.py;
    }

    # 이미지 다운로드
    location /images/download/ {
        alias /images/;
        try_files $uri $uri.jpg $uri.jpeg $uri.gif $uri.png $uri.svg $uri.svgz $uri.bmp $uri.tif $uri.tiff $uri.ico $uri/ =404;
        image_filter_buffer 15M; # TODO: image_filter_buffer 크기가 body size와 동일?
        image_filter resize 500 -; # TODO: 리사이징 사이즈 어떻게 결정할지
    }

    # 이미지 삭제
    location /images/delete {

        # POST 요청
        limit_except POST {
            deny all;
        }

        rewrite_log on;
        rewrite "^/images/delete/([0-9a-z]+)" /delete.py?type=images&id=$1 last;
    }
}

wapl-pay-server > MediaService > config > nginx.conf

user  nginx; # nginx user 설정 
worker_processes auto; # nginx worker process 수 설정 

error_log /var/log/nginx/error.log info; # nginx 에러 로그 설정

events {
    worker_connections  1024; # nginx worker process 1개 당 최대 동시 접속 수
}

http {    
    # 다운로드 설정
    include mime.types;  # 미디어 타입 설정
    sendfile on;

    # 클라이언트 timeout 관련 설정
    client_header_buffer_size 256k;
    large_client_header_buffers 4 512k;
    proxy_buffers 4 256k;
    client_body_buffer_size 256k;
    client_header_timeout 2048;
    client_body_timeout 2048;
    keepalive_timeout 600s;
    send_timeout 600s;

    # 하위 설정
    include conf.d/http.conf;
}
berryberrybin commented 1 year ago

fileId를 통해 fileUri 응답 구현

myshop-studio-dx 프로젝트 > resource > application-local.yml

application:
  media-server:
    base-uri: http://192.168.159.42:8888
    upload-path: /images/upload
    upload-many-path: /images/upload_many
    download-path: /images/download/  # 추가한 코드 

myshop-studio-dx 프로젝트 > ... > myshop > config

package com.tmax.cm.myshop.config.property;

@Getter
@ConstructorBinding
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "application")
public class AppProperties {

    private final MediaServer mediaServer;

    @Getter
    @RequiredArgsConstructor
    public static class MediaServer {
        private final String baseUri;
        private final String uploadPath;
        private final String uploadManyPath;
        private final String downloadPath;
    }
}
package com.tmax.cm.myshop.config;
@Configuration
@RequiredArgsConstructor
public class RestTemplateConfiguration {
    private final AppProperties appProperties;

    @Bean
    public RestTemplate restTemplate(){
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setConnectTimeout(3000);
        factory.setReadTimeout(5000);
        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(appProperties.getMediaServer().getBaseUri()));
        return restTemplate;
    }
}

MediaService

package com.tmax.cm.myshop.service.common;
@Service
@RequiredArgsConstructor
public class MediaService {
    private final RestTemplate restTemplate;
    private final AppProperties appProperties;

    public List<FileInfo> uploadAttachedImages(List<MultipartFile> attachedImages) {
        if (attachedImages == null || attachedImages.size() == 0) {
            return new ArrayList<>();
        }
                 // return requestUpload(attachedImages); List<FileInfo> 해당 fileUri를 각각 넣어주는 코드 추가 
        List<FileInfo> fileInfos = requestUpload(attachedImages);

        fileInfos.forEach(fileInfo -> fileInfo.setFileUri(
            getImageUri(getFileExtension(fileInfo.getFileName()), fileInfo.getFileId())));
        return fileInfos;
    }

    private String getFileExtension(String fileName) {  // 파일이름에서 확장자 도출
        return fileName.substring(fileName.lastIndexOf("."));
    }

    public List<FileInfo> requestUpload(List<MultipartFile> attachedImages) {
        HttpEntity<MultiValueMap<String, Object>> requestEntity = createUploadRequestEntity(attachedImages);

        if (attachedImages.size() == 1) {
            ResponseEntity<MediaInfo.Single> response = restTemplate.postForEntity(
                appProperties.getMediaServer().getUploadPath(),
                requestEntity, MediaInfo.Single.class);
            if (response.getStatusCode() != HttpStatus.OK || response.getBody().getCode() != HttpStatus.OK.value()) {
                throw new BusinessException(ErrorCode.INTERNAL_SERVER_ERROR, null, null);
            }
            return Collections.singletonList(response.getBody().getData());
        } else {
            ResponseEntity<MediaInfo.Multi> response = restTemplate.postForEntity(
                appProperties.getMediaServer().getUploadManyPath(), requestEntity, MediaInfo.Multi.class);
            if (response.getStatusCode() != HttpStatus.OK || response.getBody().getCode() != HttpStatus.OK.value()) {
                throw new BusinessException(ErrorCode.INTERNAL_SERVER_ERROR, null, null);
            }
            return response.getBody().getData();
        }
    }

    private HttpEntity<MultiValueMap<String, Object>> createUploadRequestEntity(List<MultipartFile> attachedImages) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();

        for (MultipartFile attachedImage : attachedImages) {
            body.add("file", attachedImage.getResource());
        }
        return new HttpEntity<>(body, headers);
    }

    private String getImageUri(String fileExtension, String fileId) { // 파일Uri 계산 로직 
        String directory = fileId.substring(0, 12).replaceAll("(.{2})(?!$)", "$1/");
        String fileName = fileId.substring(12) + fileExtension;
        String imageUri =
            appProperties.getMediaServer().getBaseUri() + appProperties.getMediaServer().getDownloadPath() + directory
                + "/" + fileName;
        return imageUri;
    }
}
berryberrybin commented 1 year ago

wapl-pay-server > MediaService > scripts > utils.py

#!/usr/bin/env python
import os
import uuid
import shutil
from errors import NoSuchFileError, DuplicateFileError

def generate_uuid():
    '''
    Generate uuid for processing files uploaded from nginx module.

    Returns:
        randomly generated uuid
    '''
    return str(uuid.uuid4())

def generate_path(uuid, root_dir, path_threshold=12, path_step=2):
    '''
    Generate new path structure using uuid for files uploaded by nginx module.

    Arguments:
        - uuid {str} -- uuid used for generating directory structure
        - root_dir {str} -- root directory for the uploaded file to be stored
        - path_threshold {int} -- threshold index for generating path structure
        - path_step {int} -- step interval for generating path structure

    Returns:
        - new_dir {str} -- path-like string. newly generated directory using uuid
        - new_name {str} -- newly generated file name using uuid
    '''

    # subdirectory structure
    dir = uuid[:path_threshold]
    sub_dir = "/".join([
        dir[i : i+path_step] for  in range(0, path_threshold, path_step)
    ])
    new_dir = os.path.join(root_dir, sub_dir)

    # file name
    new_name = uuid[path_threshold:]

    return new_dir, new_name

def move_file(src_path, dst_path):
    '''
    Move file.

    Arguments:
        - src_path {str} -- source file path
        - dst_path {str} -- destination file path
    '''
    shutil.move(src_path, dst_path)

def move_upload_to_storage(file, root_dir):
    '''
    Move uploaded files from nginx module to storage.

    Arguments:
        - file {list[str]} -- information of file uploaded via nginx module
            - file_name -- name of uploaded file
            - content_type -- HTTP content_type of uploaded file
            - path -- temporary path of uploaded file
        - root_dir {str} -- root directory for the uploaded file to be stored

    Returns:
        - file_name {str} -- name of uploaded file via nginx module
        - uuid_processed {str} -- id of uploaded file via media service
    '''

    # uploaded file info
    file_name, _, path = file
    _, ext = os.path.splitext(file_name)

    # generate uuid
    uuid = generate_uuid()
    uuid_processed = uuid.replace("-", "")

    # generate directory structure for new path
    new_dir, new_name = generate_path(uuid_processed, root_dir)

    # make new_dir recursively if it does not exists
    if not os.path.exists(new_dir):
        os.makedirs(new_dir)

    # move file to new path
    new_path = os.path.join(new_dir, new_name + ext)
    move_file(path, new_path)

    return file_name, uuid_processed

def rollback_upload(paths):
    '''
    Delete uploaded files from nginx module when upload request is invalid.

    Arguments:
        - paths {list[str]} -- temporary paths of uploaded files via nginx module
    '''
    for path in paths:
        os.remove(path)

def delete_from_storage(file_id, paths):
    '''
    Delete files from media service storage when user requests.

    Arguments:
        - file_id {str} -- id of file to be deleted
        - paths {list[str]} -- file paths matched with requested file_id
    '''
    if not paths:
        raise NoSuchFileError(file_id)
    elif len(paths) >= 2:
        raise DuplicateFileError(file_id)
    else:
        os.remove(paths[0])
berryberrybin commented 1 year ago

결과

image