feat(order-service): 重构分类资料收集与订单状态链路

This commit is contained in:
yml2213
2026-11-03 19:46:49 +08:00
parent 6e4758a3c7
commit 9f14e8de7a
59 changed files with 4866 additions and 1734 deletions
@@ -25,12 +25,18 @@ import {
saveAdminWorkProductRule,
saveAdminWorkProductRuleMapping,
saveAdminWorkerAnnouncementConfig,
archiveAdminOrderServiceProfile,
listAdminOrderServiceAutoPublishJobs,
listAdminOrderServiceProfiles,
publishAdminOrderServiceProfile,
saveAdminOrderServiceProfile,
saveAdminWorkerFinanceConfig,
saveAdminWorkerHallConfig,
saveAdminWorkerLevel,
saveAdminWorkerPlatformNotificationConfig,
saveAdminWorkerProductMatchConfig,
saveAdminWorkerProductNameSuffixConfig,
retryAdminOrderServiceAutoPublishJob,
syncAdminWorkProductRulePrice,
testAdminKuaishouProductMatch,
testAdminProductMatchByOrderItem,
@@ -137,6 +143,71 @@ router.delete(
}),
)
router.get(
'/worker-platform/order-service/profiles',
requireAdminRoles(['admin', 'operator', 'support']),
createJsonHandler(() => listAdminOrderServiceProfiles(), {
successMessage: 'ok',
errorMessage: '读取订单服务配置失败',
scope: '[admin/worker-platform/order-service/profiles]',
}),
)
router.post(
'/worker-platform/order-service/profiles',
requireAdminRoles(['admin', 'operator']),
createJsonHandler((req) => saveAdminOrderServiceProfile(req.body || {}), {
successMessage: '订单服务配置已保存',
errorMessage: '保存订单服务配置失败',
scope: '[admin/worker-platform/order-service/profiles]',
audit: (_req, data) => ({
action: 'order_service_profile_saved',
targetType: 'order_service_profile',
targetId: String((data as { profile?: { id?: number } })?.profile?.id || ''),
data: data as Record<string, unknown>,
}),
}),
)
router.post(
'/worker-platform/order-service/profiles/:profileId/publish',
requireAdminRoles(['admin', 'operator']),
createJsonHandler(
(req) =>
publishAdminOrderServiceProfile(
String(req.params.profileId || ''),
req.adminSession?.userId ?? null,
),
{
successMessage: '订单服务配置已发布',
errorMessage: '发布订单服务配置失败',
scope: '[admin/worker-platform/order-service/profiles/:profileId/publish]',
audit: (req) => ({
action: 'order_service_profile_published',
targetType: 'order_service_profile',
targetId: String(req.params.profileId || ''),
data: {},
}),
},
),
)
router.post(
'/worker-platform/order-service/profiles/:profileId/archive',
requireAdminRoles(['admin', 'operator']),
createJsonHandler((req) => archiveAdminOrderServiceProfile(String(req.params.profileId || '')), {
successMessage: '订单服务配置已归档',
errorMessage: '归档订单服务配置失败',
scope: '[admin/worker-platform/order-service/profiles/:profileId/archive]',
audit: (req) => ({
action: 'order_service_profile_archived',
targetType: 'order_service_profile',
targetId: String(req.params.profileId || ''),
data: {},
}),
}),
)
router.get(
'/worker-platform/product-rules',
requireAdminRoles(['admin', 'operator', 'support']),
@@ -466,4 +537,30 @@ router.post(
}),
)
router.get(
'/worker-platform/order-service/auto-publish-jobs',
requireAdminRoles(['admin', 'operator']),
createJsonHandler((req) => listAdminOrderServiceAutoPublishJobs(req.query.status), {
successMessage: 'ok',
errorMessage: '读取自动发布任务失败',
scope: '[admin/worker-platform/order-service/auto-publish-jobs]',
}),
)
router.post(
'/worker-platform/order-service/auto-publish-jobs/:jobId/retry',
requireAdminRoles(['admin', 'operator']),
createJsonHandler((req) => retryAdminOrderServiceAutoPublishJob(String(req.params.jobId || '')), {
successMessage: '自动发布任务已重新排队',
errorMessage: '重试自动发布任务失败',
scope: '[admin/worker-platform/order-service/auto-publish-jobs/:jobId/retry]',
audit: (req) => ({
action: 'order_service_auto_publish_retried',
targetType: 'order_service_auto_publish_job',
targetId: String(req.params.jobId || ''),
data: {},
}),
}),
)
export default router
-171
View File
@@ -1,171 +0,0 @@
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
+119
View File
@@ -0,0 +1,119 @@
import { Router } from 'express'
import {
createFailureCooldownMiddleware,
createRateLimitMiddleware,
getParamRateLimitKey,
} from '../middleware/rate-limit.js'
import {
getOrderServiceByPlatformOrderId,
getOrderServiceUploadContext,
submitOrderServiceMaterial,
} from '../services/worker-platform/index.js'
import {
ORDER_SERVICE_SCREENSHOT_CONTENT_TYPES,
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 readRateLimit = createRateLimitMiddleware({
scope: 'order-service:read',
windowMs: 60_000,
max: 120,
key: getParamRateLimitKey('orderNo'),
})
const readIpRateLimit = createRateLimitMiddleware({
scope: 'order-service:read:ip',
windowMs: 60_000,
max: 300,
})
const readFailureCooldown = createFailureCooldownMiddleware({
scope: 'order-service:read:fail:ip',
cooldownMs: 2_000,
})
const writeRateLimit = createRateLimitMiddleware({
scope: 'order-service:write',
windowMs: 60_000,
max: 20,
key: getParamRateLimitKey('orderNo'),
})
const writeIpRateLimit = createRateLimitMiddleware({
scope: 'order-service:write:ip',
windowMs: 60_000,
max: 40,
})
const uploadRateLimit = createRateLimitMiddleware({
scope: 'order-service:upload',
windowMs: 60_000,
max: 30,
key: getParamRateLimitKey('orderNo'),
})
const uploadIpRateLimit = createRateLimitMiddleware({
scope: 'order-service:upload:ip',
windowMs: 60_000,
max: 60,
})
router.get(
'/:orderNo',
readFailureCooldown,
readIpRateLimit,
readRateLimit,
createRouteHandler((req) => getOrderServiceByPlatformOrderId(req.params.orderNo), {
errorMessage: '读取订单服务信息失败',
scope: '[order-service/:orderNo]',
}),
)
router.post(
'/:orderNo/material',
writeIpRateLimit,
writeRateLimit,
createRouteHandler((req) => submitOrderServiceMaterial(req.params.orderNo, req.body || {}), {
successMessage: '资料已提交',
errorMessage: '提交订单资料失败',
scope: '[order-service/:orderNo/material]',
}),
)
router.post(
'/:orderNo/files',
uploadIpRateLimit,
uploadRateLimit,
uploadSingleFile,
createRouteHandler(
async (req) => {
const { order } = await getOrderServiceUploadContext(
req.params.orderNo,
req.query.workOrderId,
)
return uploadFileAsset({
file: req.file,
scene: 'collect-material',
uploaderType: 'collect',
uploaderId: `work-order-${order.id}`,
allowedContentTypes: ORDER_SERVICE_SCREENSHOT_CONTENT_TYPES,
})
},
{
successMessage: '文件已上传',
errorMessage: '文件上传失败',
scope: '[order-service/:orderNo/files]',
},
),
)
router.use((req, res) => {
res.status(404).json(buildNotFoundPayload(req))
})
export default router
+1 -1
View File
@@ -9,7 +9,7 @@ import { createRouteHandler } from '../utils/http.js'
*
* 公开接口(无需登录),但按「订单号 + IP」限流:这是无 token 方案的第一道防线
* (见 docs/抖音-码速达/05-最小接入方案分析.md §3.2)。
* 与 `/collect`(routes/collect.ts)完全同型。
* 订单资料与进度查询已统一到 `/api/v1/o`(routes/order-service.ts)。
*/
const router = Router()