// multer 설치
npm i multer
# File Access
ACCESS_FILE_POST_IMAGE_PATH=/files/posts
ACCESS_FILE_USER_PROFILE_PATH=/files/profiles
# File
FILE_POST_IMAGE_PATH=storage/images/posts
FILE_POST_IMAGE_SIZE=10485760
FILE_USER_PROFILE_PATH=storage/images/profiles
FILE_USER_PROFILE_SIZE=10485760
/**
* @file app/middlewares/multer/uploaders/profile.uploader.js
* @description 게시글 이미지 업로드
* 251127 v1.0.0 CK init
*/
import multer from 'multer';
import fs from 'fs';
import dayjs from 'dayjs'
import myError from '../../../errors/customs/my.error.js'
import { BAD_FILE_ERROR } from '../../../../configs/responseCode.config.js';
/**
* 게시글 이미지 업로드 처리 미들웨어
* @param {import("express").Request} req
* @param {import("express").Response} res
* @param {import("express").NextFunction} next
*/
export default function(req,res, next) {
// multer 객체 정의
const upload = multer({
// storage: 파일을 저장할 위치를 상세하게 저장하는 프로퍼티
storage: multer.diskStorage({
// 파일 저장 경로 설정
destination(req, file, callback) {
// 저장 디렉토리 설정
if(!fs.existsSync(process.env.FILE_USER_PROFILE_PATH)) {
// 해당 디렉토리 없으면 생성 처리
fs.mkdirSync(
process.env.FILE_USER_PROFILE_PATH,
{
recursive: true, // 중간 디렉토리까지 모두 생성
mode: 0o755 // 권한 설정 rwxr-xr-x
}
)
}
callback(null, process.env.FILE_USER_PROFILE_PATH);
},
// 파일명 설정
filename(req, file, callback) {
// 저장할 파일명 생성
const uniqueFileName = `${dayjs().format('YYYYMMDD')}_${crypto.randomUUID()}`;
const fileNameParts = file.originalname.split('.');
const extention = fileNameParts[fileNameParts.length - 1].toLowerCase();
callback(null, `${uniqueFileName}.${extention}`);
}
}),
// fileFilter: 파일 필터링 처리를 제어하는 프로퍼티
fileFilter(req, file, callback) {
if(!file.mimetype.startsWith('image/')) {
return callback(myError('이미지 파일 아님', BAD_FILE_ERROR));
}
callback(null, true);
},
// limits: 파일 사이즈 제한, 파일 개수 제한
limits: {
fileSize: parseInt(process.env.FILE_USER_PROFILE_SIZE)
}
}).single('image');
// 예외 처리
upload(req, res, err => {
if(err instanceof multer.MulterError || err) {
next(myError(err.message, BAD_FILE_ERROR));
}
next();
});
}
/**
* @file app/middlewares/multer/uploaders/profile.uploader.js
* @description 게시글 이미지 업로드
* 251127 v1.0.0 CK init
*/
import multer from 'multer';
import fs from 'fs';
import dayjs from 'dayjs'
import myError from '../../../errors/customs/my.error.js'
import { BAD_FILE_ERROR } from '../../../../configs/responseCode.config.js';
/**
* 게시글 이미지 업로드 처리 미들웨어
* @param {import("express").Request} req
* @param {import("express").Response} res
* @param {import("express").NextFunction} next
*/
export default function(req,res, next) {
// multer 객체 정의
const upload = multer({
// storage: 파일을 저장할 위치를 상세하게 저장하는 프로퍼티
storage: multer.diskStorage({
// 파일 저장 경로 설정
destination(req, file, callback) {
// 저장 디렉토리 설정
if(!fs.existsSync(process.env.FILE_USER_PROFILE_PATH)) {
// 해당 디렉토리 없으면 생성 처리
fs.mkdirSync(
process.env.FILE_USER_PROFILE_PATH,
{
recursive: true, // 중간 디렉토리까지 모두 생성
mode: 0o755 // 권한 설정 rwxr-xr-x
}
)
}
callback(null, process.env.FILE_USER_PROFILE_PATH);
},
// 파일명 설정
filename(req, file, callback) {
// 저장할 파일명 생성
const uniqueFileName = `${dayjs().format('YYYYMMDD')}_${crypto.randomUUID()}`;
const fileNameParts = file.originalname.split('.');
const extention = fileNameParts[fileNameParts.length - 1].toLowerCase();
callback(null, `${uniqueFileName}.${extention}`);
}
}),
// fileFilter: 파일 필터링 처리를 제어하는 프로퍼티
fileFilter(req, file, callback) {
if(!file.mimetype.startsWith('image/')) {
return callback(myError('이미지 파일 아님', BAD_FILE_ERROR));
}
callback(null, true);
},
// limits: 파일 사이즈 제한, 파일 개수 제한
limits: {
fileSize: parseInt(process.env.FILE_USER_PROFILE_SIZE)
}
}).single('image');
// 예외 처리
upload(req, res, err => {
if(err instanceof multer.MulterError || err) {
next(myError(err.message, BAD_FILE_ERROR));
}
next();
});
}
/**
* @file app/middlewares/multer/multer.middleware.js
* @description multer 미들웨어(업로더를 모아서 내보내기)
* 251127 v1.0.0 CK init
*/
import postUploader from "./uploaders/post.uploader.js";
import profileUploader from "./uploaders/profile.uploader.js";
export default {
postUploader,
profileUploader,
}
/**
* @file app/controllers/files.controller.js
* @description 파일 업로드 관련 컨트롤러
* 251127 v1.0.0 CK init
*/
import { BAD_FILE_ERROR, SUCCESS } from "../../configs/responseCode.config.js";
import myError from "../errors/customs/my.error.js";
import { createBaseResponse } from "../utils/createBaseResponse.util.js";
/**
* 게시글 이미지 업로드 컨트롤러
* @param {import("express").Request} req - Request 객체
* @param {import("express").Response} res - Response 객체
* @param {import("express").NextFunction} next - NextFunction 객체
* @returns
*/
async function storePost(req, res, next) {
try {
// 파일 여부 확인
if(!req.file) {
throw myError('파일 없음', BAD_FILE_ERROR);
}
const result = {
path: `${process.env.APP_URL}${process.env.ACCESS_FILE_POST_IMAGE_PATH}/${req.file.filename}`
};
return res.status(SUCCESS.status).send(createBaseResponse(SUCCESS, result));
} catch (error) {
next(error);
}
}
/**
* 유저 프로필 업로드 컨트롤러
* @param {import("express").Request} req - Request 객체
* @param {import("express").Response} res - Response 객체
* @param {import("express").NextFunction} next - NextFunction 객체
* @returns
*/
async function storeProfile(req, res, next) {
try {
// 파일 여부 확인
if(!req.file) {
throw myError('파일 없음', BAD_FILE_ERROR);
}
const result = {
path: `${process.env.APP_URL}${process.env.ACCESS_FILE_USER_PROFILE_PATH}/${req.file.filename}`
};
return res.status(SUCCESS.status).send(createBaseResponse(SUCCESS, result));
} catch (error) {
next(error);
}
}
export default {
storePost,
storeProfile,
}
/**
* @file routes/files.router.js
* @description 파일 관련 라우터
* 251127 v1.0.0 CK init
*/
import express from 'express';
import multerMiddleware from '../app/middlewares/multer/multer.middleware.js';
import filesController from '../app/controllers/files.controller.js';
import authMiddleware from '../app/middlewares/auth/auth.middleware.js';
const filesRouter = express.Router();
filesRouter.post('/posts', authMiddleware, multerMiddleware.postUploader, filesController.storePost);
filesRouter.post('/profiles', authMiddleware, multerMiddleware.profileUploader, filesController.storeProfile);
export default filesRouter;
// ---------------------------
// 정적 파일 제공 등록
// ---------------------------
app.use(process.env.ACCESS_FILE_POST_IMAGE_PATH, express.static(process.env.FILE_POST_IMAGE_PATH));
app.use(process.env.ACCESS_FILE_USER_PROFILE_PATH, express.static(process.env.FILE_USER_PROFILE_PATH));
// ---------------------------
// 라우터 정의
// ---------------------------
app.use('/api/files', filesRouter);