Files
order_site/apps/backend/src/middleware/http-cache.ts
T
yml2213 5092e88618 perf(admin): 打手等级筛选用 UNION 形式、概览加短缓存、静态配置接口加缓存头
三处都出自审计清单,改动互不重叠。

1) 打手等级筛选(生产实测 1119ms)
   原写法是对 worker_users 的两个相关 EXISTS(接单人 / 拼单参与人),规划器无法用它
   驱动索引,只能顺序扫完整张 work_orders。改成先把等级解析成打手 id
   (worker_users(level_id) 有索引,等级只有 6 个、解析出几百个 id),再走索引。
   注意:**不能**写成 `assigned_worker_id = ANY(...) OR id IN (...)` —— 实测 1852ms,
   比原写法还慢(OR 右支要逐行回表取 id)。用 UNION 形式:
     wo.id IN (SELECT id FROM work_orders WHERE assigned_worker_id = ANY(...) UNION
               SELECT work_order_id FROM work_order_shares WHERE worker_id = ANY(...) AND status != 'cancelled')
   实测 61ms(count),语义与原来完全一致(含"空数组=过滤掉全部")。
   三个入口 listWorkOrders / countWorkOrders / getWorkOrderStatistics 都先解析;
   未解析时保留原相关 EXISTS 兜底。

2) 平台概览短缓存(5 秒)
   一次并发 5 条 count(4 条打 work_orders,一条是运营态 CASE),而池只有 8 条连接,
   后台刷新时与其他请求互相争抢(抓包显示整批 9 个请求同时发出、它自身 167ms)。
   看板计数滞后几秒无感,用 WorkerListCache 缓存并合并并发。

3) 三个静态配置接口加 HTTP 缓存头(private, max-age=300)
   接单分类、快手行业店铺、自定义店铺此前没有任何 Cache-Control,每次刷新都真发一次
   请求(抓包里 4 个这类请求)。带 private 是因为这些是带鉴权的后台接口。
   **dev-mock/status 故意不加**:它是可切换状态,缓存会让"切了开关界面没变"。

验证:后端 547 项单测全绿(新增 4 项守卫,分别锁住 UNION 形式、三个入口都解析、
概览缓存、以及 dev-mock 不得加缓存)。
2026-10-01 15:36:46 +08:00

20 lines
894 B
TypeScript

import type { NextFunction, Request, Response } from 'express'
/**
* 给「几乎不变」的配置类接口加 HTTP 缓存头。
*
* 背景:后台刷新页面时前端会重新拉一遍这些静态配置(接单分类、快手行业店铺、
* 自定义店铺、dev-mock 状态)。它们此前没有任何 Cache-Control,于是每次刷新都真的
* 走一次网络 —— 生产抓包显示一次刷新里有 4 个这类请求,且都和业务查询挤在同一个
* 连接池窗口内。
*
* 必须带 `private`:这些是带鉴权的后台接口,禁止任何共享/中间缓存留存响应。
*/
export function setHttpCache(maxAgeSeconds: number) {
const headerValue = `private, max-age=${Math.max(0, Math.floor(maxAgeSeconds))}`
return (_req: Request, res: Response, next: NextFunction): void => {
res.setHeader('Cache-Control', headerValue)
next()
}
}