feat(order-service): 重构分类资料收集与订单状态链路
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user