diff --git a/packages/ui/certd-client/src/views/certd/mine/user-profile.vue b/packages/ui/certd-client/src/views/certd/mine/user-profile.vue index 11009f97e..08f624945 100644 --- a/packages/ui/certd-client/src/views/certd/mine/user-profile.vue +++ b/packages/ui/certd-client/src/views/certd/mine/user-profile.vue @@ -396,7 +396,7 @@ const userAvatar = computed(() => { return userInfo.value.avatar; } - return `api/basic/file/download?token=${userStore.getToken}&key=${userInfo.value.avatar}`; + return `api/basic/file/download?key=${userInfo.value.avatar}`; }); onMounted(async () => { diff --git a/packages/ui/certd-server/src/configuration-cache.ts b/packages/ui/certd-server/src/configuration-cache.ts new file mode 100644 index 000000000..7b1091800 --- /dev/null +++ b/packages/ui/certd-server/src/configuration-cache.ts @@ -0,0 +1,6 @@ +export function shouldSetDefaultNoCache(path: string, cacheControl?: string) { + if (cacheControl) { + return false; + } + return path === '/' || path === '/index.html' || path.startsWith('/api'); +} diff --git a/packages/ui/certd-server/src/configuration.test.ts b/packages/ui/certd-server/src/configuration.test.ts new file mode 100644 index 000000000..9b1266f81 --- /dev/null +++ b/packages/ui/certd-server/src/configuration.test.ts @@ -0,0 +1,22 @@ +/// +/// + +import assert from "node:assert/strict"; + +import { shouldSetDefaultNoCache } from "./configuration-cache.js"; + +describe("shouldSetDefaultNoCache", () => { + it("sets default no-cache for html and api responses without cache headers", () => { + assert.equal(shouldSetDefaultNoCache("/"), true); + assert.equal(shouldSetDefaultNoCache("/index.html"), true); + assert.equal(shouldSetDefaultNoCache("/api/basic/file/download"), true); + }); + + it("keeps explicit cache headers from file responses", () => { + assert.equal(shouldSetDefaultNoCache("/api/basic/file/download", "public,max-age=259200"), false); + }); + + it("ignores non-html and non-api paths", () => { + assert.equal(shouldSetDefaultNoCache("/static/images/logo.svg"), false); + }); +}); diff --git a/packages/ui/certd-server/src/configuration.ts b/packages/ui/certd-server/src/configuration.ts index a3450eaa3..13ee49993 100644 --- a/packages/ui/certd-server/src/configuration.ts +++ b/packages/ui/certd-server/src/configuration.ts @@ -20,6 +20,7 @@ import * as commercial from '@certd/commercial-core'; import * as upload from '@midwayjs/upload'; import { setLogger } from '@certd/acme-client'; import {HiddenMiddleware} from "./middleware/hidden.js"; +import { shouldSetDefaultNoCache } from './configuration-cache.js'; // import * as swagger from '@midwayjs/swagger'; //@ts-ignore // process.env.UV_THREADPOOL_SIZE = 2 @@ -123,7 +124,7 @@ export class MainConfiguration { this.app.getMiddleware().insertFirst(async (ctx: IMidwayKoaContext, next: NextFunction) => { await next(); - if (ctx.path === '/' || ctx.path === '/index.html' || ctx.path.startsWith("/api")) { + if (shouldSetDefaultNoCache(ctx.path, ctx.response.get('Cache-Control'))) { ctx.response.set('Cache-Control', 'public,max-age=0'); } }); diff --git a/packages/ui/certd-server/src/controller/basic/file-controller.test.ts b/packages/ui/certd-server/src/controller/basic/file-controller.test.ts index a09837292..f968d1c15 100644 --- a/packages/ui/certd-server/src/controller/basic/file-controller.test.ts +++ b/packages/ui/certd-server/src/controller/basic/file-controller.test.ts @@ -3,7 +3,7 @@ import assert from "node:assert/strict"; -import { isImageFile } from "./file-controller.js"; +import { getImageDownloadOptions, isImageFile } from "./file-controller.js"; describe("FileController.isImageFile", () => { it("detects uploaded logo image files", () => { @@ -17,4 +17,23 @@ describe("FileController.isImageFile", () => { assert.equal(isImageFile("data/upload/public/user/cert.pem"), false); assert.equal(isImageFile("data/upload/public/user/logo"), false); }); + + it("builds koa-send options that keep image cache headers at 3 days", () => { + const options = getImageDownloadOptions("data/upload/public/user/logo.png"); + + assert.equal(options?.maxage, 259200000); + + const headers: Record = {}; + options?.setHeaders({ + setHeader(key: string, value: string) { + headers[key] = value; + }, + }); + + assert.equal(headers["Cache-Control"], "public,max-age=259200"); + }); + + it("does not build cache options for non-image files", () => { + assert.equal(getImageDownloadOptions("data/upload/private/user/cert.pem"), undefined); + }); }); diff --git a/packages/ui/certd-server/src/controller/basic/file-controller.ts b/packages/ui/certd-server/src/controller/basic/file-controller.ts index e0340fd79..1fb6bd203 100644 --- a/packages/ui/certd-server/src/controller/basic/file-controller.ts +++ b/packages/ui/certd-server/src/controller/basic/file-controller.ts @@ -12,6 +12,18 @@ export function isImageFile(filePath: string) { return imageExtSet.has(filePath.substring(filePath.lastIndexOf('.')).toLowerCase()); } +export function getImageDownloadOptions(filePath: string) { + if (!isImageFile(filePath)) { + return undefined; + } + return { + maxage: imageCacheSeconds * 1000, + setHeaders(res: any) { + res.setHeader('Cache-Control', `public,max-age=${imageCacheSeconds}`); + }, + }; +} + /** */ @Provide() @@ -47,11 +59,10 @@ export class FileController extends BaseController { userId = this.getUserId(); } const filePath = this.fileService.getFile(key, userId); - if (isImageFile(filePath)) { - this.ctx.response.set('Cache-Control', `public,max-age=${imageCacheSeconds}`); - } else { + const sendOptions = getImageDownloadOptions(filePath); + if (!sendOptions) { this.ctx.response.attachment(filePath); } - await send(this.ctx, filePath); + await send(this.ctx, filePath, sendOptions); } }