chore: 优化私有图片上传和查看逻辑

This commit is contained in:
xiaojunnuo
2026-05-25 23:05:23 +08:00
parent deac92faf8
commit ba1fe54ef8
10 changed files with 212 additions and 43 deletions
@@ -1,9 +1,10 @@
import { Controller, Fields, Files, Get, Inject, Post, Provide, Query } from '@midwayjs/core';
import { BaseController, Constants, FileService, UploadFileItem, uploadTmpFileCacheKey } from '@certd/lib-server';
import { BaseController, Constants, FileService, PermissionException, UploadFileItem, uploadTmpFileCacheKey } from '@certd/lib-server';
import send from 'koa-send';
import { nanoid } from 'nanoid';
import { cache } from '@certd/basic';
import { UploadFileInfo } from '@midwayjs/upload';
import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
const imageExtSet = new Set(['.apng', '.avif', '.bmp', '.gif', '.ico', '.jpeg', '.jpg', '.png', '.svg', '.webp']);
const imageCacheSeconds = 3 * 24 * 60 * 60;
@@ -32,6 +33,9 @@ export class FileController extends BaseController {
@Inject()
fileService: FileService;
@Inject()
authService: AuthService;
@Post('/upload', { description: Constants.per.authOnly })
async upload(@Files() files: UploadFileInfo<string>[], @Fields() fields: any) {
console.log('files', files, fields);
@@ -52,17 +56,30 @@ export class FileController extends BaseController {
});
}
@Get('/download', { description: Constants.per.guest })
@Get('/download', { description: Constants.per.guestOptionalAuth })
async download(@Query('key') key: string) {
let userId: any = null;
if (!key.startsWith('/public')) {
userId = this.getUserId();
}
const filePath = this.fileService.getFile(key, userId);
const filePath = this.getDownloadFilePath(key);
const sendOptions = getImageDownloadOptions(filePath);
if (!sendOptions) {
this.ctx.response.attachment(filePath);
}
await send(this.ctx, filePath, sendOptions);
}
private getDownloadFilePath(key: string) {
const isPrivateFile = !key.startsWith('/public');
const userId = isPrivateFile ? this.getUserId() : null;
try {
return this.fileService.getFile(key, userId);
} catch (e) {
if (!(e instanceof PermissionException) || !isPrivateFile || !this.authService.isAdmin(this.ctx)) {
throw e;
}
const adminFilePath = this.fileService.getFile(key, userId, true);
if (!isImageFile(adminFilePath)) {
throw e;
}
return adminFilePath;
}
}
}
@@ -0,0 +1,56 @@
/// <reference types="mocha" />
/// <reference types="node" />
import assert from "node:assert/strict";
import jwt from "jsonwebtoken";
import { Constants } from "@certd/lib-server";
import { AuthorityMiddleware } from "./authority.js";
function createMiddleware(permission: string) {
const middleware = new AuthorityMiddleware();
middleware.secret = "test-secret";
middleware.webRouterService = {
async getMatchedRouterInfo() {
return { description: permission };
},
} as any;
return middleware;
}
function createCtx(token?: string) {
return {
path: "/api/basic/file/download",
method: "GET",
query: token ? { token } : {},
headers: {},
get() {
return "";
},
} as any;
}
describe("AuthorityMiddleware guestOptionalAuth", () => {
it("continues without user when token is not provided", async () => {
const middleware = createMiddleware(Constants.per.guestOptionalAuth);
const ctx = createCtx();
let called = false;
await middleware.resolve()(ctx, async () => {
called = true;
});
assert.equal(called, true);
assert.equal(ctx.user, undefined);
});
it("sets user when token is provided", async () => {
const middleware = createMiddleware(Constants.per.guestOptionalAuth);
const token = jwt.sign({ id: 1, roles: [1] }, middleware.secret);
const ctx = createCtx(token);
await middleware.resolve()(ctx, async () => {});
assert.equal(ctx.user.id, 1);
assert.deepEqual(ctx.user.roles, [1]);
});
});
@@ -52,29 +52,7 @@ export class AuthorityMiddleware implements IWebMiddleware {
return;
}
let token = ctx.get('Authorization') || '';
token = token.replace('Bearer ', '').trim();
if (!token) {
//尝试从cookie中获取token
const cookie = ctx.headers.cookie;
if (cookie) {
const items = cookie.split(';');
for (const item of items) {
if (!item || !item.trim()) {
continue;
}
const [key, value] = item.split('=');
if (key.trim() === 'certd_token') {
token = value.trim();
break;
}
}
}
}
if (!token) {
//尝试从query中获取token
token = (ctx.query.token as string) || '';
}
const token = this.getTokenFromRequest(ctx);
if (token) {
try {
@@ -84,6 +62,10 @@ export class AuthorityMiddleware implements IWebMiddleware {
return this.notAuth(ctx);
}
} else {
if (permission === Constants.per.guestOptionalAuth) {
await next();
return;
}
//找找openKey
const openKey = await this.doOpenHandler(ctx);
if (!openKey) {
@@ -101,6 +83,10 @@ export class AuthorityMiddleware implements IWebMiddleware {
await next();
return;
}
if (permission === Constants.per.guestOptionalAuth) {
await next();
return;
}
const pass = await this.authService.checkPermission(ctx, permission);
if (!pass) {
@@ -123,6 +109,30 @@ export class AuthorityMiddleware implements IWebMiddleware {
return;
}
private getTokenFromRequest(ctx: IMidwayKoaContext) {
let token = ctx.get('Authorization') || '';
token = token.replace('Bearer ', '').trim();
if (token) {
return token;
}
const cookie = ctx.headers.cookie;
if (cookie) {
const items = cookie.split(';');
for (const item of items) {
if (!item || !item.trim()) {
continue;
}
const [key, value] = item.split('=');
if (key.trim() === 'certd_token') {
return value.trim();
}
}
}
return (ctx.query.token as string) || '';
}
async doOpenHandler(ctx: IMidwayKoaContext) {
//开放接口
const openKey = ctx.get('x-certd-token') || '';