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(含新增的物品图回退用例)
This commit is contained in:
yml2213
2026-10-09 16:13:39 +08:00
parent 56ad06239b
commit 4fce07a621
37 changed files with 590 additions and 1300 deletions
-55
View File
@@ -1,55 +0,0 @@
import { Router } from 'express'
import type { Request, Response } from 'express'
import { createRateLimitMiddleware, getParamRateLimitKey } from '../middleware/rate-limit.js'
import { getSkinClaimDetail, submitSkinClaim } from '../services/claim/claim-skin-service.js'
import { buildMessageSuccessPayload, sendRouteError } from '../utils/http.js'
/**
* 抖音码速达「皮肤自动发货」买家领取页(公开接口,无需登录)。
*
* 响应契约(前端并行开发中):
* - 成功:`{ code: 0, message, data }`
* - 失败:仓库标准错误体 `{ code: 1, msg, message, errorCode, time, data: null }`,
* 前端按 `errorCode` 展示中文提示(`claim_token_not_found` / `claim_token_expired` /
* `claim_task_closed` / `claim_skin_task_invalid` / `claim_skin_game_account_*`)。
*
* 限流与 `routes/claims.ts` 同款:按客户端 IP + token 分桶。
*/
const router = Router()
const claimSkinReadRateLimit = createRateLimitMiddleware({
scope: 'claimSkin:read',
windowMs: 60_000,
max: 120,
key: getParamRateLimitKey('token'),
})
const claimSkinWriteRateLimit = createRateLimitMiddleware({
scope: 'claimSkin:write',
windowMs: 60_000,
max: 30,
key: getParamRateLimitKey('token'),
})
router.get('/:token', claimSkinReadRateLimit, async (req: Request, res: Response) => {
try {
const data = await getSkinClaimDetail(req.params.token)
res.json(buildMessageSuccessPayload(data, 'success'))
} catch (error) {
sendRouteError(res, error, '查询领取详情失败', '[claim-skin/:token]', { messageAlias: true })
}
})
router.post('/:token', claimSkinWriteRateLimit, async (req: Request, res: Response) => {
try {
const body = (req.body || {}) as Record<string, unknown>
const data = await submitSkinClaim(req.params.token, { gameAccount: body.gameAccount })
res.json(buildMessageSuccessPayload(data, '领取成功'))
} catch (error) {
sendRouteError(res, error, '领取失败,请稍后重试', '[claim-skin/:token]', {
messageAlias: true,
})
}
})
export default router