Files
order_site/apps/backend/src/app.ts
T
yml2213 4fce07a621 refactor(douyin-mashida)!: 纠偏——删掉自建的皮肤通道,改回复用 affiliate_dash
上一轮我把皮肤交付做成了「另建一套执行器 + 独立领取接口 + 只提交账号就结束」,
跟买家实际要走的过程不一致(真正的完成信号是「扫码绑定后的 UID == 买家填的 UID」),
而且核销还触发了老通知逻辑,给买家**又发了一条 /redeem** —— 买家收到两条链接。
本次按用户要求改回**完全复用仓库现成的 affiliate_dash 通道**(= 旧的快手皮肤自动发货)。

**删掉**
- 执行器 `douyin_mashida_skin`(含 registry/types/planner/bootstrap/路由白名单注册)
- `services/claim/claim-skin-service.ts`、`routes/claim-skin.ts`、`/api/v1/claim-skin` 挂载
- 迁移 096 的 `claim_submission_json` 全链路(task-repo / rows / inputs / task-detail /
  admin-read-service 的 claimSubmission),并新增 **097 兜底迁移**把该列 DROP 掉
- 顺带清掉上一轮专为已删接口加的死代码(utils/http.ts 的 buildMessageSuccessPayload 等)

**改回复用**
- `resolveOrderItemClaimLink`:通用解析(只看订单项、不看执行器)→
  `buildConfiguredClaimUrl(claimLinkBase, token)`;多履约单元取 task id 最小并告警;
  **绝不回退** hash 形态的 `buildClaimUrl`
- `cert_link` 与付款后那条**唯一**消息共用同一个解析结果
- **核销后不再给抖音买家发第二条消息**(`shouldNotifyBuyerAfterVerify` 只放行非抖音 provider)
- 买家侧走既有流程:输 UID → 二维码 → 扫码绑定 → 比对 UID → delivered/REDEEMED;
  在 delivered 分支 best-effort 核销平台券(幂等、失败只 warn,不拖慢买家响应)
- 后台回显链接改按 **order.provider** 判断(抖音用 `claimLinkBase`)

**SQL 守卫**:允许「删除历史迁移」,但强制要求「同时新增兜底迁移」配对
(修改历史迁移仍禁止),并把改动来源换成 `git status --porcelain`(否则未跟踪的新迁移看不见)

`npm run check`:tests 671 / pass 668 / fail 0 / skipped 3(含新增的物品图回退用例)
2026-10-09 16:13:39 +08:00

127 lines
4.2 KiB
TypeScript

import type { NextFunction, Request, Response } from 'express'
import express from 'express'
import process from 'node:process'
import adminRouter from './routes/admin.js'
import affiliateDashRouter from './routes/affiliate-dash.js'
import collectRouter from './routes/collect.js'
import filesRouter from './routes/files.js'
import claimsRouter from './routes/claims.js'
import douyinMashidaRouter from './routes/douyin-mashida.js'
import kuaishouIndustryRouter from './routes/kuaishou-industry.js'
import open91Router from './routes/open-91.js'
import pddExtensionRouter from './routes/pdd-extension.js'
import redeemRouter from './routes/redeem.js'
import shortLinksRouter from './routes/short-links.js'
import workerRouter from './routes/worker.js'
import { accessLogMiddleware } from './middleware/access-log.js'
import { createCorsMiddleware } from './middleware/cors.js'
import { buildHealthPayload, type StartupState } from './startup/state.js'
import type { RuntimeConfig } from './types/runtime-config.js'
import { buildSuccessPayload, sendRouteError } from './utils/http.js'
type CreateAppOptions = {
startupState: StartupState
isShutdownStarted: () => boolean
config: RuntimeConfig
}
export function createApp({ startupState, isShutdownStarted, config }: CreateAppOptions) {
const app = express()
// 部署在反代(Caddy)之后时让 req.ip 取 X-Forwarded-For 的真实客户端地址;限流、访问日志与登录设备记录都依赖它。
if (config.server?.trustProxy !== undefined && config.server?.trustProxy !== null) {
app.set('trust proxy', config.server.trustProxy)
}
app.use(accessLogMiddleware)
app.use(createCorsMiddleware(config))
// 抖音码速达消息推送必须在全局 body parser **之前**挂载:
// 它的签名基于 `data` 的原始 JSON 字符串,express.json/urlencoded 会改变这个字符串。
app.use('/api/v1/open/douyin-mashida', douyinMashidaRouter)
app.use(
express.json({
limit: '2mb',
verify: (req, _res, buffer) => {
;(req as Request).rawBody = buffer.toString('utf8')
},
}),
)
app.use(express.urlencoded({ extended: true }))
app.get('/health', (_req, res) => {
res.json(
buildSuccessPayload(
buildHealthPayload(startupState, isShutdownStarted()),
startupState.core.ready ? 'ready' : 'starting',
),
)
})
app.get('/health/live', (_req, res) => {
res.json(
buildSuccessPayload({
status: isShutdownStarted() ? 'shutting_down' : 'alive',
pid: process.pid,
mode: 'kuaishou-lite',
}),
)
})
app.get('/health/ready', (_req, res) => {
if (startupState.core.ready) {
res.json(buildSuccessPayload(buildHealthPayload(startupState, isShutdownStarted()), 'ready'))
return
}
res.status(503).json({
code: 1,
msg: startupState.core.lastError || '服务启动中,请稍后重试',
errorCode: 'service_not_ready',
time: Math.floor(Date.now() / 1000),
data: buildHealthPayload(startupState, isShutdownStarted()),
})
})
app.use((req, res, next) => {
if (startupState.core.ready) {
next()
return
}
res.status(503).json({
code: 1,
msg: startupState.core.lastError || '服务启动中,请稍后重试',
errorCode: 'service_not_ready',
time: Math.floor(Date.now() / 1000),
data: {
startup: {
phase: startupState.phase,
attemptCount: startupState.core.attemptCount,
lastAttemptAt: startupState.core.lastAttemptAt,
},
},
})
})
app.use('/api/v1/open/affiliate-dash', affiliateDashRouter)
app.use('/api/v1/open/91', open91Router)
app.use('/api/v1/open/kuaishou-industry', kuaishouIndustryRouter)
app.use('/api/v1/redeem', redeemRouter)
app.use('/api/v1/claim', claimsRouter)
app.use('/api/v1/collect', collectRouter)
app.use('/api/v1/pdd/extension', pddExtensionRouter)
app.use('/api/v1/files', filesRouter)
app.use('/api/v1/worker', workerRouter)
app.use('/api/v1/admin', adminRouter)
app.use('/s', shortLinksRouter)
app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => {
sendRouteError(res, err, '服务内部错误', '[global]')
})
return app
}