- 公开查询只返回待完善工单,待分配/已验收等状态提示「该订单当前无需补充资料」 - 404 失败查询按 IP 冷却 2 秒,成功查询不计数;前端显示「查询过于频繁」 - 提交成功后本地展示成功页,刷新后按严格模式提示无需补充资料 - 收敛补资料页重复提示,移除状态标签与工单号,商品与订单号合并展示 - 修复 React StrictMode 下刷新重复请求查询接口的问题
172 lines
4.3 KiB
TypeScript
172 lines
4.3 KiB
TypeScript
import { Router } from 'express'
|
|
|
|
import {
|
|
createFailureCooldownMiddleware,
|
|
createRateLimitMiddleware,
|
|
getBodyFieldRateLimitKey,
|
|
getParamRateLimitKey,
|
|
} from '../middleware/rate-limit.js'
|
|
import {
|
|
collectLookupWorkOrder,
|
|
collectSubmitWorkOrder,
|
|
getPublicWorkOrderMaterialLink,
|
|
getPublicWorkOrderMaterialLinkUploadContext,
|
|
submitPublicWorkOrderMaterialLink,
|
|
} from '../services/worker-platform/index.js'
|
|
import { uploadFileAsset } from '../services/file-storage/file-storage-service.js'
|
|
import { buildNotFoundPayload, createRouteHandler } from '../utils/http.js'
|
|
import { uploadSingleFile } from './file-upload.js'
|
|
|
|
const router = Router()
|
|
|
|
const collectRateLimit = createRateLimitMiddleware({
|
|
scope: 'collect',
|
|
windowMs: 60_000,
|
|
max: 60,
|
|
key: getBodyFieldRateLimitKey('orderNo'),
|
|
})
|
|
|
|
const materialLinkReadRateLimit = createRateLimitMiddleware({
|
|
scope: 'collect:link:read',
|
|
windowMs: 60_000,
|
|
max: 120,
|
|
key: getParamRateLimitKey('code'),
|
|
})
|
|
|
|
/** 平台订单号可被枚举,额外加一层只按 IP 的总限流,避免换 code 绕过单链接限流。 */
|
|
const materialLinkReadIpRateLimit = createRateLimitMiddleware({
|
|
scope: 'collect:link:read:ip',
|
|
windowMs: 60_000,
|
|
max: 300,
|
|
})
|
|
|
|
/** 查询失败(404)后按 IP 冷却 2 秒;正常查询不计数,避免误伤用户。 */
|
|
const materialLinkReadFailureCooldown = createFailureCooldownMiddleware({
|
|
scope: 'collect:link:read:fail:ip',
|
|
cooldownMs: 2_000,
|
|
})
|
|
|
|
const materialLinkWriteRateLimit = createRateLimitMiddleware({
|
|
scope: 'collect:link:write',
|
|
windowMs: 60_000,
|
|
max: 20,
|
|
key: getParamRateLimitKey('code'),
|
|
})
|
|
|
|
const materialLinkWriteIpRateLimit = createRateLimitMiddleware({
|
|
scope: 'collect:link:write:ip',
|
|
windowMs: 60_000,
|
|
max: 40,
|
|
})
|
|
|
|
const materialLinkUploadRateLimit = createRateLimitMiddleware({
|
|
scope: 'collect:link:upload',
|
|
windowMs: 60_000,
|
|
max: 30,
|
|
key: getParamRateLimitKey('code'),
|
|
})
|
|
|
|
const materialLinkUploadIpRateLimit = createRateLimitMiddleware({
|
|
scope: 'collect:link:upload:ip',
|
|
windowMs: 60_000,
|
|
max: 60,
|
|
})
|
|
|
|
router.post(
|
|
'/lookup',
|
|
collectRateLimit,
|
|
createRouteHandler((req) => collectLookupWorkOrder(req.body || {}), {
|
|
successMessage: 'ok',
|
|
errorMessage: '查询待完善订单失败',
|
|
scope: '[collect/lookup]',
|
|
}),
|
|
)
|
|
|
|
router.post(
|
|
'/submit',
|
|
collectRateLimit,
|
|
createRouteHandler((req) => collectSubmitWorkOrder(req.body || {}), {
|
|
successMessage: '资料已提交',
|
|
errorMessage: '提交补充资料失败',
|
|
scope: '[collect/submit]',
|
|
}),
|
|
)
|
|
|
|
router.post(
|
|
'/upload',
|
|
collectRateLimit,
|
|
uploadSingleFile,
|
|
createRouteHandler(
|
|
(req) => {
|
|
return uploadFileAsset({
|
|
file: req.file,
|
|
scene: req.body?.scene || 'collect-material',
|
|
uploaderType: 'collect',
|
|
uploaderId: String(req.body?.orderNo || 'anonymous'),
|
|
})
|
|
},
|
|
{
|
|
successMessage: '文件已上传',
|
|
errorMessage: '文件上传失败',
|
|
scope: '[collect/upload]',
|
|
},
|
|
),
|
|
)
|
|
|
|
router.get(
|
|
'/link/:code',
|
|
materialLinkReadFailureCooldown,
|
|
materialLinkReadIpRateLimit,
|
|
materialLinkReadRateLimit,
|
|
createRouteHandler((req) => getPublicWorkOrderMaterialLink(String(req.params.code || '')), {
|
|
errorMessage: '读取补资料链接失败',
|
|
scope: '[collect/link/:code]',
|
|
}),
|
|
)
|
|
|
|
router.post(
|
|
'/link/:code/submit',
|
|
materialLinkWriteIpRateLimit,
|
|
materialLinkWriteRateLimit,
|
|
createRouteHandler(
|
|
(req) => submitPublicWorkOrderMaterialLink(String(req.params.code || ''), req.body || {}),
|
|
{
|
|
successMessage: '资料已提交',
|
|
errorMessage: '提交补资料失败',
|
|
scope: '[collect/link/:code/submit]',
|
|
},
|
|
),
|
|
)
|
|
|
|
router.post(
|
|
'/link/:code/upload',
|
|
materialLinkUploadIpRateLimit,
|
|
materialLinkUploadRateLimit,
|
|
uploadSingleFile,
|
|
createRouteHandler(
|
|
async (req) => {
|
|
const { order } = await getPublicWorkOrderMaterialLinkUploadContext(
|
|
String(req.params.code || ''),
|
|
req.query.workOrderId,
|
|
)
|
|
return uploadFileAsset({
|
|
file: req.file,
|
|
scene: req.body?.scene || 'collect-material',
|
|
uploaderType: 'collect',
|
|
uploaderId: `work-order-${order.id}`,
|
|
})
|
|
},
|
|
{
|
|
successMessage: '文件已上传',
|
|
errorMessage: '文件上传失败',
|
|
scope: '[collect/link/:code/upload]',
|
|
},
|
|
),
|
|
)
|
|
|
|
router.use((req, res) => {
|
|
res.status(404).json(buildNotFoundPayload(req))
|
|
})
|
|
|
|
export default router
|