upload.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import multer from 'multer';
  2. import path from 'path';
  3. import { v4 as uuidv4 } from 'uuid';
  4. import { config } from '../config/index.js';
  5. import { AppError } from './error.js';
  6. import { ERROR_CODES, HTTP_STATUS } from '@media-manager/shared';
  7. import fs from 'fs';
  8. // 确保上传目录存在
  9. const uploadDirs = ['videos', 'images', 'covers'];
  10. uploadDirs.forEach(dir => {
  11. const fullPath = path.join(config.upload.path, dir);
  12. if (!fs.existsSync(fullPath)) {
  13. try {
  14. fs.mkdirSync(fullPath, { recursive: true });
  15. } catch (err) {
  16. console.error(`[upload] Failed to create upload dir ${fullPath}:`, err);
  17. }
  18. }
  19. });
  20. // 视频存储配置
  21. const videoStorage = multer.diskStorage({
  22. destination: (_req, _file, cb) => {
  23. cb(null, path.join(config.upload.path, 'videos'));
  24. },
  25. filename: (_req, file, cb) => {
  26. const ext = path.extname(file.originalname);
  27. cb(null, `${uuidv4()}${ext}`);
  28. },
  29. });
  30. // 图片存储配置
  31. const imageStorage = multer.diskStorage({
  32. destination: (_req, _file, cb) => {
  33. cb(null, path.join(config.upload.path, 'images'));
  34. },
  35. filename: (_req, file, cb) => {
  36. const ext = path.extname(file.originalname);
  37. cb(null, `${uuidv4()}${ext}`);
  38. },
  39. });
  40. // 视频文件过滤
  41. const videoFilter = (_req: Express.Request, file: Express.Multer.File, cb: multer.FileFilterCallback) => {
  42. if (config.upload.allowedVideoTypes.includes(file.mimetype)) {
  43. cb(null, true);
  44. } else {
  45. cb(new AppError(
  46. '不支持的视频格式',
  47. HTTP_STATUS.BAD_REQUEST,
  48. ERROR_CODES.VIDEO_FORMAT_UNSUPPORTED
  49. ));
  50. }
  51. };
  52. // 图片文件过滤
  53. const imageFilter = (_req: Express.Request, file: Express.Multer.File, cb: multer.FileFilterCallback) => {
  54. if (config.upload.allowedImageTypes.includes(file.mimetype)) {
  55. cb(null, true);
  56. } else {
  57. cb(new AppError(
  58. '不支持的图片格式',
  59. HTTP_STATUS.BAD_REQUEST,
  60. ERROR_CODES.VALIDATION
  61. ));
  62. }
  63. };
  64. // 视频上传中间件
  65. export const uploadVideo = multer({
  66. storage: videoStorage,
  67. limits: {
  68. fileSize: config.upload.maxVideoSize,
  69. },
  70. fileFilter: videoFilter,
  71. }).single('video');
  72. // 图片上传中间件
  73. export const uploadImage = multer({
  74. storage: imageStorage,
  75. limits: {
  76. fileSize: config.upload.maxImageSize,
  77. },
  78. fileFilter: imageFilter,
  79. }).single('image');
  80. // 封面上传中间件
  81. export const uploadCover = multer({
  82. storage: multer.diskStorage({
  83. destination: (_req, _file, cb) => {
  84. cb(null, path.join(config.upload.path, 'covers'));
  85. },
  86. filename: (_req, file, cb) => {
  87. const ext = path.extname(file.originalname);
  88. cb(null, `${uuidv4()}${ext}`);
  89. },
  90. }),
  91. limits: {
  92. fileSize: config.upload.maxImageSize,
  93. },
  94. fileFilter: imageFilter,
  95. }).single('cover');