Explorar o código

更新上传,不通过nginx读取文件

lrf hai 1 ano
pai
achega
d7f2b8f381

+ 4 - 1
package.json

@@ -39,6 +39,9 @@
     "typescript": "~4.8.0"
   },
   "dependencies": {
-    "@midwayjs/axios": "^3.9.0"
+    "@midwayjs/axios": "^3.9.0",
+    "@midwayjs/upload": "^3.12.7",
+    "@types/mime": "^3.0.2",
+    "mime": "^3.0.0"
   }
 }

+ 18 - 1
src/config/config.default.ts

@@ -1 +1,18 @@
-export const config = {};
+import { uploadWhiteList } from '@midwayjs/upload';
+import { join } from 'path';
+export const config = {
+  upload: {
+    // mode: UploadMode, 默认为file,即上传到服务器临时目录,可以配置为 stream
+    mode: 'file',
+    // fileSize: string, 最大上传文件大小,默认为 10mb
+    fileSize: '100mb',
+    // whitelist: string[],文件扩展名白名单
+    whitelist: uploadWhiteList,
+    // tmpdir: string,上传的文件临时存储路径
+    tmpdir: join(process.cwd(), 'uploadTemp'),
+    // cleanTimeout: number,上传的文件在临时目录中多久之后自动删除,默认为 5 分钟
+    cleanTimeout: 5 * 60 * 1000,
+    // base64: boolean,设置原始body是否是base64格式,默认为false,一般用于腾讯云的兼容
+    base64: false,
+  },
+};

+ 2 - 1
src/configuration.ts

@@ -9,6 +9,7 @@ import { DefaultErrorFilter } from './filter/default.filter';
 import * as axios from '@midwayjs/axios';
 import { ServiceError, FrameworkErrorEnum } from './error/service.error';
 import { IMidwayContainer } from '@midwayjs/core';
+import * as upload from '@midwayjs/upload';
 
 const axiosResponse = response => {
   if (response.status === 200) return response.data;
@@ -21,7 +22,7 @@ const axiosError = error => {
 };
 @Configuration({
   namespace: 'free',
-  imports: [koa, typegoose, axios],
+  imports: [koa, typegoose, axios, upload],
   importConfigs: [
     {
       default: DefaultConfig,

+ 58 - 0
src/controller/file.controller.ts

@@ -0,0 +1,58 @@
+import { Inject, Controller, Post, Files, Fields, Config, Get } from '@midwayjs/decorator';
+import { Context } from '@midwayjs/koa';
+import { join, sep } from 'path';
+import { FileService } from '../service/file.service';
+import { createReadStream } from 'fs';
+import { getType } from 'mime';
+@Controller('/')
+export class FileController {
+  @Inject()
+  ctx: Context;
+  @Inject()
+  fileService: FileService;
+  @Config('koa.globalPrefix')
+  globalPrefix: string;
+
+  uploadDir = 'upload';
+
+  /**
+   * 文件上传
+   * @param {Array} files 文件数组
+   * @param {object} fields 其他字段
+   * @param {string} project 项目名
+   * @param {string} catalog 文件层级名 用'_'连接上下层级
+   * @param {string} item 文件名,没有默认用时间戳
+   */
+  @Post('/api/files/:project/upload')
+  @Post('/api/files/:project/:catalog/upload')
+  @Post('/api/files/:project/:catalog/:item/upload')
+  async upload(@Files() files, @Fields() fields) {
+    const { project, catalog, item } = this.ctx.params;
+    const dirs = [project];
+    if (catalog && catalog !== '_') {
+      const subs = catalog.split('_');
+      dirs.push(...subs);
+    }
+    let path = join(process.cwd(), this.uploadDir);
+    // TODO: 检查分级目录是否存在,不存在则创建
+    for (let i = 0; i < dirs.length; i++) {
+      const p = `${path}${sep}${dirs.slice(0, i + 1).join(sep)}`;
+      this.fileService.mkdir(p);
+    }
+    path = `${join(path, dirs.join(sep))}${sep}`;
+    const file = files[0];
+    const ext = this.fileService.getExt(file.filename);
+    let filename = `${this.fileService.getNowDateTime()}${ext}`;
+    if (item) filename = item;
+    const uri = `${this.globalPrefix}/files/${dirs.join('/')}/${filename}`;
+    this.fileService.moveFile(file.data, `${path}${filename}`);
+    return { id: filename, name: filename, uri };
+  }
+  @Get('/files/*')
+  async readFile() {
+    const shortRealPath = this.fileService.getFileShortRealPath();
+    const realPath = join(process.cwd(), this.uploadDir, shortRealPath);
+    this.ctx.body = createReadStream(realPath);
+    this.ctx.response.set('content-type', `/files/${getType(realPath)}`);
+  }
+}

+ 10 - 4
src/middleware/response.middleware.ts

@@ -2,7 +2,7 @@ import { IMiddleware } from '@midwayjs/core';
 import { Middleware, App } from '@midwayjs/decorator';
 import { NextFunction, Context, Application } from '@midwayjs/koa';
 import { VOBase } from '../interface/VOBase';
-import { get } from 'lodash'
+import { get } from 'lodash';
 @Middleware()
 export class ResponseMiddleware implements IMiddleware<Context, NextFunction> {
   @App()
@@ -14,10 +14,16 @@ export class ResponseMiddleware implements IMiddleware<Context, NextFunction> {
       const swaggerPath = this.app.getConfig()?.swagger?.swaggerPath;
       const url = get(ctx, 'request.originalUrl');
       if (!url.includes(swaggerPath)) {
+        //检查返回头,如果返回头中有/files,那就说明是读取文件.不能更改body
         const response = ctx.response;
-        const body = response.body;
-        const nb = new VOBase(body as object);
-        return nb;
+        const resHeaderContentType = response.header['content-type'] as string;
+        if (!resHeaderContentType.includes('/files')) {
+          const body = response.body;
+          const nb = new VOBase(body as object);
+          return nb;
+        } else {
+          response.set('content-type', resHeaderContentType.replace('/files', ''));
+        }
       }
     };
   }

+ 63 - 0
src/service/file.service.ts

@@ -0,0 +1,63 @@
+import { Config, Inject, Provide } from '@midwayjs/decorator';
+import { existsSync, mkdirSync, renameSync } from 'fs';
+import { Context } from '@midwayjs/koa';
+import { dirname, extname, sep } from 'path';
+@Provide()
+export class FileService {
+  @Inject()
+  ctx: Context;
+  @Config('koa.globalPrefix')
+  globalPrefix: string;
+
+  getFileShortRealPath() {
+    let originalUrl = this.ctx.request.originalUrl;
+    //先去掉请求前缀
+    originalUrl = originalUrl.replace(this.globalPrefix, '');
+    // 再去掉files
+    originalUrl = originalUrl.replace('/files', '');
+    const arr = originalUrl.split('/');
+    // 首行为空去掉
+    arr.splice(0, 1);
+    // 最后一个数据为文件,其余的为路径拼成一起就行
+    return arr.join(sep);
+  }
+  /**
+   * 移动文件
+   * @param tempPath 临时上传文件位置
+   * @param path 实际文件应处位置
+   */
+  moveFile(tempPath: string, path: string) {
+    renameSync(tempPath, path);
+  }
+
+  // 创建文件夹
+  mkdir(dn: string) {
+    if (existsSync(dn)) {
+      return true;
+    }
+    if (this.mkdir(dirname(dn))) {
+      mkdirSync(dn);
+      return true;
+    }
+  }
+  /**获取文件名后缀 */
+  getExt(name: string) {
+    return extname(name);
+  }
+  /**获取年月日时分秒, 格式: 年月日时分秒 */
+  getNowDateTime() {
+    const date = new Date();
+    const y = date.getFullYear();
+    const m = date.getMonth() + 1;
+    const mstr = m < 10 ? '0' + m : m;
+    const d = date.getDate();
+    const dstr = d < 10 ? '0' + d : d;
+    const h = date.getHours();
+    const hstr = h < 10 ? '0' + h : h;
+    const minute = date.getMinutes();
+    const minutestr = minute < 10 ? '0' + minute : minute;
+    const second = date.getSeconds();
+    const secondstr = second < 10 ? '0' + second : second;
+    return `${y}${mstr}${dstr}${hstr}${minutestr}${secondstr}`;
+  }
+}