yml2213
|
319e082254
|
fix(douyin-mashida): 领取消息只发给有自动发货领取单元的单,人工单不再假发货
根因:notifyBuyerClaimLink 对 `/h5` 形态的 claimLinkBase 走了一条捷径——只拼订单号
(`${base}/${orderNo}`)就发消息,跳过了「该订单项是否有自动发货领取单元」的校验。
领取 token 只由 affiliate_dash 执行器在建单时创建,未命中 AFFILIATE_DASH_SKU_MAPPING
回落 manual_dispatch 的人工单、以及自动准备失败的订单都没有 token,却照样收到
「您的订单已发货,请点击链接领取」(线上实测:非映射商品「套装-牛仔情诗」)。
- claim-link.ts:新增 buildDouyinMashidaBuyerClaimUrl(无自动领取单元一律返回空串 = 不发消息)
与 isDouyinMashidaH5ClaimBase(/h5 形态判定收敛到一处)
- notify-service.ts:两种基址形态统一先 resolveOrderItemClaimLink 拿 taskId 作为「会自动发货」
的证据,缺失则跳过并留痕「该订单项没有自动发货领取单元,跳过领取链接消息」
- claim-link.test.ts:补齐人工单不发消息、/h5 与 token 形态拼接、基址/订单号缺失等用例
验证:npm run check 全绿(prettier + SQL guard + eslint + tsc + 703 单测 + build)
|
2026-10-13 13:48:58 +08:00 |
|
yml2213
|
7e197c9413
|
fix(frontend): H5 领取前缀整段接管,/h5 裸链接不再跳后台登录
根因:公开路径判定原先只认「带参数」形态(isH5ClaimPath 只认 /h5/<orderNo>),
裸前缀 https://<域名>/h5(后台 claimLinkBase / certLink 的基址)判定不命中,
于是落回主站 HashRouter 被后台路由接管成
https://<域名>/h5/#/admin/dashboard 后台登录页。
- utils/claim-path.ts:isClaimPath / isH5ClaimPath 改为命名空间级判定(含裸前缀与多段路径),
参数解析仍严格;新增 resolveH5ClaimOrderNoFromInput(纯订单号 / 整条链接 / 缺协议 /
带后台残留 hash 都能取出订单号,只有前缀时返回空串)
- pages/claim/H5ClaimEntryPage.tsx:新增 /h5 领取入口页,买家输订单号或粘贴完整链接后
跳 /h5/<订单号>,走与分享链接同一套领取流程
- App.tsx:H5ClaimApplication 接管 /h5 整段前缀(/h5 入口页、/h5/:orderNo 领取页、
其它子路径回入口页);useClearStrayHash 抹掉历史残留的 #/admin/dashboard
- tests/claim-path.test.ts:补齐裸前缀、相邻前缀 /h5x、粘贴链接解析等回归用例
验证:npm run check 全绿(prettier + eslint + tsc + 143 单测 + vite build)
|
2026-10-13 13:42:06 +08:00 |
|
yml2213
|
5a73fb74a9
|
feat(douyin-mashida): 新增抖音券核销后台界面(列表/核销/冲正/同步/对账/作废)
抖音订单并非纯自动发货:内部 SKU 命中 AFFILIATE_DASH_SKU_MAPPING 才走自动通道,
未命中显式回落人工,且核销依赖买家自己到 /redeem 输订单号。此前核销侧在后台完全
不可见:自动核销是 void .catch(warn) 只落日志,cancelVerify 出网方法已就绪但没有入口。
照快手电子凭证的骨架做一套抖音版(两张表两套状态机,不复用):
- 券码状态、平台侧状态、同步错误全部可视化,支持只看同步失败
- 核销/冲正/重新同步/平台对账/重发券码/作废,每个写操作都带审计
- 券码列显示「核销实际会用的券码」(platform_cert_no 优先),平台改过名时额外标注
- 冲正先打平台 cancelVerify 再回退本地,避免本地与平台状态漂移
|
2026-10-12 19:45:06 +08:00 |
|
yml2213
|
cb064d7cd5
|
fix(worker-platform): 工单号前缀改 WK 并在检索侧容错 O/0,彻底解决「按工单号搜不到」
## 线上定位到的真正根因
客服搜 `WO26101213031939` 搜不到,但用同单的平台号 `2628501763808879` 搜得到。
到生产库核对后确认:
- 库里真实值 `work_order_no = WO26101213031939`(hex 574f… = 字母 O),状态 pending_acceptance;
- 用字母 O 的 SQL 分支 → 1 行;用数字 0(`W026101213031939`)→ 0 行;
- 全库 127,906 单**全部**以 `WO` 开头、`W0` 开头 0 单、字母 `o` 只出现在第 2 位。
即:前缀里的字母 O 被输成数字 0(`WO` / `W0` 在界面字体里几乎无法区分),
落进通配符分支后必然为空 —— 与前面几轮的「筛选把单藏起来」是两个独立成因,
这也解释了为什么改筛选逻辑一直没解决。
## 修法
1. 生成端换前缀:generateWorkOrderNo 产出 `WK…`(K 与 0 不会看混),从源头消掉歧义;
2. 检索端同时兼容三种写法(历史 WO 号无需迁移):
- isWorkOrderNoLookupKeyword 放宽为 `^W[oOkK0]…`;
- 新增 buildWorkOrderNoKeywordVariants:W0→[W0, WO, WK]、WO→[WO, W0]、WK→[WK],
SQL 分支对每个候选下发「等值 + 前缀」条件(仍是索引条件,不会退化成全表扫);
- 前端 isExplicitOrderNoSearch 同步(否则面板不清理筛选、实时补丁护栏也不生效);
3. 顺手加一层归一化:NFKC(全角→半角)+ 去零宽字符,前后端同一口径
(normalizeLookupKeyword / normalizeSearchText),覆盖「粘贴带不可见字符」这类同族问题;
4. 寄卖匹配弹窗的占位提示更新为 WK... / WO...。
## 验证
- 后端 npm run check 全绿:692 tests(689 pass / 3 skip),新增
work-order-keyword-lookup.test.ts 直接断言 SQL 参数与「不得出现前置通配符」;
- 前端 npm run check 全绿:139 tests;
- 既有用例 worker-platform-repo.test.ts 的期望值同步为新形态(两条候选条件)。
|
2026-10-12 16:51:08 +08:00 |
|
yml2213
|
fe4d02d136
|
fix(worker-platform): 按工单号搜不到彻底收口 —— 显式单号检索不再被任何排他筛选吞掉
## 修的是什么
线上反复复发的「后台接单平台按工单号搜不到、某些状态尤其明显」,根因不是一处,
而是同一类问题在 5 个地方各留了一半(历次修复都只补了一处,所以总复发):
1. 状态筛选走运营态派生:拼单母单一旦有人参与,open → in_progress /
pending_acceptance,于是「单号 + 待接单」永远为空(没人参与时反而搜得到),
这正是「有人接单后就搜不到」;非拼单单子被接单后同理。
2. 「单号 + 接单人员 / 分类 / 打手等级 / 创建时间」照样过滤掉目标单
(接单人员是 INNER JOIN 可见集合,不在集合里整行消失)。
d6523635 只在面板查询按钮里清了 状态/赠送阶段/验收模式,这三个没清。
3. 待办通知跳转(buildNotificationActionPath 带 ?workOrderId=)设下的
「定位工单」是 wo.id = X 排他条件,不手点「取消定位」就一直生效,
之后搜任何别的单号都是空 —— 这条路径历次修复从未涉及。
4. AdminLayout 把服务端 operation='remove' 直接判成 mismatch,
绕过了「显式单号不删行」的护栏。
5. 打手端「我的订单」默认页签固定「代练中」,搜索不联动页签、
实时补丁又拿状态当可证伪条件删行 → 打手端同类问题(此前只修了关键词匹配)。
## 修法(收口到单一真源,不再按按钮打补丁)
后端:
- 新增 isExplicitOrderNoLookupKeyword()(工单号 / 平台单号 / SUB·INV·AS·DT);
- 后台列表筛选抽成纯函数 resolveAdminWorkOrderListFilters():显式单号检索时
不下推 状态/VIP待补图/验收模式/赠送阶段/接单人员/分类/打手等级/创建时间/定位工单;
- 打手端 resolveWorkerMyOrderStatusFilter():只放掉状态与 VIP 待补图,
**严格保留 workerSharingId 可见范围**(否则打手能搜到别人的单)。
前端:
- admin-realtime-work-orders.ts:显式单号检索直接判 unknown(含服务端 remove),
remove 的短路移交给判定函数;
- WorkOrdersPanel:单号检索时清空全部排他筛选并提示「已按单号精准检索,自动忽略…」,
通知定位工单时也清掉其它筛选,避免残留;
- WorkerOrdersPage:单号检索自动切「全部订单」;WorkerLayout 走新的
resolveWorkerMyOrderRealtimeVerdict(),与后台同策。
刻意不改(避免越权/召回不可用单):抢单大厅(候选必须是 open)、
寄卖手动匹配候选(只做未接单整单)。
## 验证
后端 npm run check 全绿:682 pass / 3 skip(新增 6 项);
前端 npm run check 全绿:138 pass(新增 6 项,含「单号检索 + 实时事件不得删行」,
以及「服务端 remove 不得绕过单号护栏」的回归);format/lint/typecheck/build 通过。
无数据库迁移。
|
2026-10-12 15:48:30 +08:00 |
|
yml2213
|
966b804bfc
|
feat(worker): 保留超时订单买家资料与快捷复制功能以方便打手核对辨认
- 调整目的:打手在订单超时退回大厅或被抢后,需要核对游戏内对应的好友与账号以分辨是哪一单,避免误认;
- 列表与抽屉展示:在桌面端表格、移动端卡片及详情抽屉中,恢复买家资料与主页截图展示,保留「复制信息」「复制图片」按钮,并补充「已超时,仅供核对辨认」轻量警示;
- 履约隔离:依然严格禁用一切提交验收、修改截图与申请取消等履约操作;
- 验证方式:通过前端 prettier、eslint、tsc、vitest、vite build 与后端全部 676 个测试。
|
2026-10-11 15:39:10 +08:00 |
|
yml2213
|
c5e8b78a33
|
fix(worker): 修复打手订单可见范围查询中参数类型推导不兼容错误
- 显式类型转换:在 worker_visible 的 4 个 UNION 分支中,为 workerSharePlaceholder 显式补充 ::bigint 与 ::text 类型转换;
- 避免类型推导冲突:消除由于 JSON 字符串比较导致的 bigint = text 操作符缺失错误(operator does not exist: text = bigint);
- 验证方式:在 PostgreSQL 容器中验证 PREPARE/EXECUTE 与在后端容器内实测 listWorkOrders 正常返回 20 条记录。
|
2026-10-11 15:25:48 +08:00 |
|
yml2213
|
9ce29964c7
|
style(frontend): 格式化前端代码并修复 eslint 异步操作未标记告警
- 统一 prettier 代码风格;
- 为 AdminTaskDetailPage 与 AdminTasksPage 中未捕获的 Promise 添加 void 标记;
- 验证方式:npm --prefix apps/frontend run check 全流程通过。
|
2026-10-11 15:20:54 +08:00 |
|
yml2213
|
6012add3c9
|
feat(worker): 打手接单界面支持查看超时历史订单并增加安全免责警示
- 数据库与查询层:通过索引 idx_work_order_events_timeout_worker_id 关联打手历史 timeout 事件与取消的拼单份额,打手在退回大厅或被他人抢走后仍能在「已超时」tab 查看历史记录;
- 状态与权限管控:针对超时工单将个人端状态稳定映射为 open,报酬与冻结押金置为 0,彻底禁用上传图片、提交验收、好友资料、申请取消等履约操作;
- 风险防护与免责说明:列表与抽屉详情顶部增加醒目的免责声明与警示横幅(告知订单已退回大厅或被他人接手,严禁在游戏内继续赠送,造成的损失平台概不负责);锁定买家游戏资料与快捷复制;
- 验证方式:通过后端全部 676 个测试、typecheck、check:sql 及前端 check(eslint、vitest/node:test、tsc、vite build)。
|
2026-10-11 15:20:46 +08:00 |
|
yml2213
|
b82f9c4063
|
fix(worker-platform): 补齐拼单份额超时自动处置机制与退回大厅流程
- 根因排查:此前超时扫描仅针对整单 in_progress,而拼单母单为 open 且 deadline_at 为空,导致打手超时的拼单份额无法被定时扫描命中与释放;
- 定时扫描:在 settleOverdueWorkOrders 中引入 listOverdueWorkOrderShares,自动扫描超时未提交资料的拼单份额(joined);
- 超时处置:新增 settleOverdueWorkOrderShare 事务处理,按母单 timeout_policy 释放或扣除押金,份额标记 cancelled,母单保持或恢复 open 状态重回抢单大厅,记录 timeout 事件并推送实时变更;
- 提交防呆:在打手提交拼单验收时检测个人份额是否已超时,超时则立即执行处置并拦截提交;
- 倒计时展示:在 mapWorkOrderForWorker 中根据 myShare.created_at + timeout_minutes 为拼单打手计算并展示个人履约截止时间;
- 质量验证:补齐拼单截止时间计算与处置链路测试,全量 676 个测试均通过,通过 npm run check 校验。
|
2026-10-11 14:53:56 +08:00 |
|
yml2213
|
06d888fb6f
|
fix(products): 恢复官方正版商品图并统一渠道代码格式化
- 从上游系统同步官方原始高清商品图(lucky_coin_x2/x30/x90),剔除此前劣质变形的合成图
- 验证全量 40 款商品资产与 affiliate_dash 官方权威库 100% 逐字节对齐
- 任务详情页接入 formatGameChannelLabel 统一渠道展示逻辑(1-1 -> 安卓-微信)
- 验证方式:npm run build、npm test(674 自动化单测全部通过)
|
2026-10-10 21:34:59 +08:00 |
|
yml2213
|
87f6f13995
|
fix(asset): 区分幸运币 x2、x30、x90 对应规格金币立绘
- lucky_coin_x2.webp: 金币徽章 + x2 数量角标
- lucky_coin_x30.webp: 金币徽章 + x30 数量角标
- lucky_coin_x90.webp: 金币徽章 + x90 数量角标
- 保持字体、斜度、色彩与边角透明度一致
|
2026-10-10 21:28:48 +08:00 |
|
yml2213
|
ed8bdf399d
|
fix(voucher): 替换幸运币纯净无水印透明底素材并统一区服渠道中文转换
- 将幸运币 2/30/90 个 webp 素材替换为纯净 RGBA 透明金币立绘,消除白底多边形及「幸运币礼包(小)」文字水印
- 增加 formatGameChannelLabel 转换函数:将腾讯/DLC area-plat 协议代号(如 1-1)统一转换为用户友好的「安卓-微信」
- 领取页第 2/3/4 步与凭据大图全局统一应用区服中文转换
|
2026-10-10 21:18:31 +08:00 |
|
yml2213
|
f3f2550b19
|
feat(admin): 彻底优化任务列表与详情界面,集成到账存证凭单与联盟直充面板
- 优化任务列表角色列:支持从 affiliateDash 及 claimIdentity 中自动提取并展示角色名与 UID(含一键复制)
- 列表操作栏支持一键查看到账存证凭单,支持直接复制图片发给买家或下载保存
- 任务详情页全面现代化重构:
- 新增「兑换成功 · 官方直充到账存证凭据」大卡片,实时生成 640x960 Retina 高清卡片,支持一键写入系统剪贴板(微信/QQ/客服窗口直接 Ctrl+V 发送)、下载 PNG 大图与全屏预览
- 新增「联盟直充履约 (affiliate_dash)」专属面板,结构化展示上游订单号、直充状态、点数积分、核销状态及供应商回执
- 优化凭据生成器 (voucher.ts):强制优先读取本地高清商品图,彻底消除上游图片文字水印与圆环破图问题
- 验证方式:npm run build & npm test (133 项单测全量通过)
|
2026-10-10 21:04:01 +08:00 |
|
yml2213
|
215f3351ba
|
fix(claim): 优化H5领取与绑定界面商品图展示并集成发货存证凭据生成
- 修复后端 affiliate_dash 领取详情接口漏传 imageUrl 字段的问题
- 优化前端 ClaimProductImage 占位逻辑,消除无图或失败时的空灰色方块
- 移植并集成 affiliate_dash 前端 Canvas 高清发货到账存证凭据(Retina 640x960)
- 在兑换成功页面提供结果图预览、一键复制到剪贴板与下载凭据图片功能
|
2026-10-10 20:55:02 +08:00 |
|
yml2213
|
c137741215
|
feat(frontend): vite 白名单放行 sakurafrp 域名后缀
|
2026-10-10 20:34:24 +08:00 |
|
yml2213
|
73de90ea4f
|
fix(admin): 平台推送地址优先回显已配置的公网域名
- 引入 resolveDouyinMashidaPublicOrigin 从已配置的 claimLinkBase/certLink 提取公网域名
- 更新后台面板 message_url 展示逻辑,即使从 localhost 访问也清晰呈现已配置的公网推送地址
- 向平台订阅推送时使用已解析的公网域名,避免错误推送 localhost
- 补充前端单元测试并通过全量构建校验
|
2026-10-10 20:18:23 +08:00 |
|
yml2213
|
fa1a67a03d
|
chore(vite): dev server 允许 ngrok-free.dev 域名访问
验证方式:ngrok 静态域名下直连自检通过,HTTP 状态 200 正常
|
2026-10-10 20:15:16 +08:00 |
|
yml2213
|
ccf8de97a9
|
chore(douyin-mashida): 联调脚本适配 /h5 路径与统一后台接口调用
- set-public-url 脚本将 certLink 与 claimLinkBase 统一设置为 <公网地址>/h5
- subscribe 脚本改走后台 HTTP 管理接口,避免在宿主机执行时直连数据库导致连接异常
验证方式:ngrok 隧道下端到端执行 subscribe 与 preflight 自动化校验全部通过
|
2026-10-10 20:12:33 +08:00 |
|
yml2213
|
c54dbda344
|
feat(douyin-mashida): 全面统一为 /h5/<订单号> 直充路径,更新面板提示与直发链接生成
- 抖音码速达买家直充链接与兜底配置基址统一改为 https://<域名>/h5(直充路径为 /h5/<订单号>)
- 前端配置面板示例与提示升级为真实 16 位平台订单号形态(如 /h5/2628301923197355)
- 一键填入域名同步适配 /h5 基址,并在后端发券与发货私信推送中直接生成 /h5/<订单号>
- 数据库本地配置同步更新为 http://localhost/h5
验证方式:前端类型检查与打包构建通过,全量 132 项前端单测与 674 项后端单测全部通过
|
2026-10-10 20:10:09 +08:00 |
|
yml2213
|
d8ce40e346
|
feat(douyin-mashida): 彻底重构优化配置面板,支持一键填域名/订阅推送/网关自检并剔除冗余字段
- 剔除历史旧卡券时代废弃的 afterVerifyLink(去使用链接)与 afterVerifyMessage(通知正文)
- 面板新增一键填入当前域名、向开放平台一键订阅推送、停用推送以及网关连通与签名自检功能
- 业务配置重构为「基础接入与商户凭据」、「买家 H5 直充与发货私信」、「高级网关设置」三层清晰结构
- 后端与前端服务新增 /douyin-mashida/subscribe 与 /douyin-mashida/test-gateway 统一接口
验证方式:前端类型检查与打包构建通过,全量 132 项前端单测与 674 项后端单测全部通过
|
2026-10-10 20:05:46 +08:00 |
|
yml2213
|
748c18bc7d
|
feat(order): 精准区分自动直发与接单平台打手履约,智能门控H5直充按钮
- 后端增加按订单批量关联接单工单查询,精准识别履约类型(自动直发 / 打手履约 / 电子凭证 / 拼多多发货)
- 订单列表与详情页中,仅当订单为自动直发(affiliate_dash / 码速达直充)时展示【H5】一键复制直充链接与兑换状态控制
- 接单平台工单单与纯核销单隐藏直充 H5 按钮与兑换暂停/关闭标签,防止客服向打手单买家误发直充链接
- 交付模式标签升级为「⚡ 自动直发」、「👥 打手履约」、「🎫 电子凭证」、「📦 拼多多发货」
验证方式:前端类型检查与打包构建通过,全量 132 项前端单测与 674 项后端单测全部通过
|
2026-10-10 19:44:20 +08:00 |
|
yml2213
|
9a6b4313a8
|
fix(order): 移除订单列表冗长的买家信息列,优化多平台表格密度
- 移除订单列表中的「买家信息」列,消除抖音等虚拟品类中超长加密 open_id / 匿名买家造成的垂直与水平排版挤压
- 紧凑化表格宽度(scroll 960px),提升列表浏览效率
- 详情页继续保留完整买家原始载荷供追溯
验证方式:前端类型检查及 132 项单测全通过,前端打包构建通过
|
2026-10-10 19:04:09 +08:00 |
|
yml2213
|
03020e85c7
|
feat(order): 订单列表页UI重构与多平台(快手/91卡券、抖音、拼多多)适配
- 顶部新增平台快捷分流器(Segmented),支持「全部 / 快手·91卡券 / 抖音码速达 / 拼多多 / 本地模拟」一键筛选
- 重构订单表格列布局为平台与订单号、买家信息、商品规格、交付与履约状态、金额与支付时间、快捷操作等现代化字段
- 优化快手与 91卡券业务归集逻辑,统一数据查询与徽标显示口径
- 增强订单行快捷操作,支持一键复制 H5 领取链接与查看详情
验证方式:后端 674 项单测全通过,前端 132 项单测全通过,前端打包构建通过
|
2026-10-10 18:54:40 +08:00 |
|
yml2213
|
3f2bb3f09a
|
fix(admin): 修复恢复启用订单兑换时 orderId 闭包失效及 H5 页面重试机制
- 修复任务详情页和订单详情页中 statusMutation 闭包提前捕获空 orderId 导致恢复启用请求未发出的问题,改为动态从 query.data 解析最新的 orderId
- 订单详情页统一切换为 App.useApp 提供的 modal/message 实例,保证异步弹窗与提示稳定渲染
- H5 买家领取页面在暂停或关闭等异常状态卡片中增加【重新检测状态】按钮,客服恢复启用后买家可一键重新加载最新状态
- 验证方式:前端类型检查(0 error)、单测 132 项全通及前端 Vite 重新构建成功
|
2026-10-10 18:46:49 +08:00 |
|
yml2213
|
6971002579
|
feat(admin): 履约完成或终态时自动隐藏暂停与关闭兑换按钮
- 任务详情页(AdminTaskDetailPage):若任务已履约完成(completed/delivered/success)或订单已完成/已退款,自动隐藏【暂停兑换】和【关闭兑换】按钮,避免误操作
- 订单详情页(AdminOrderDetailPage):若订单下任务已全部履约完成或订单终态,同样自动隐藏【暂停兑换】和【关闭兑换】
- 针对已被暂停/关闭的历史订单,始终保留【恢复启用】入口以便误操作时补救
- 验证方式:前端类型检查(0 error)、前端单测 132 项全通及前端 Vite 重新构建成功
|
2026-10-10 18:42:12 +08:00 |
|
yml2213
|
9b29f7513a
|
fix(frontend): 修复 AdminTaskDetailPage 中 useMutation 的 Hook 调用顺序
- 将 useMutation 提升至组件顶层,避免因位于 query.isLoading 等早期 return 分支之后导致 React Hooks 顺序变动报错
- 验证方式:前端类型检查(0 error)、单测 132 项全通及前端 Vite 重新构建成功
|
2026-10-10 18:40:07 +08:00 |
|
yml2213
|
122144a2b7
|
feat(task): 任务详情与列表页增加H5订单号领取链接及兑换有效性控制
- 后端 AdminTaskDetail 视图补充关联订单的 claimStatus、claimStatusReason 等状态字段
- 任务详情页升级“领取链接”卡片:支持固定 H5 链接展示、状态标签(有效/暂停/关闭),并支持一键复制及【暂停/关闭/恢复启用】弹窗操作
- 任务详情页保留历史 Token 链接作为辅助对比展示
- 任务列表页在平台订单号旁增加一键复制 H5 领取链接的快捷按钮
- 验证方式:前后端类型检查、全量单元测试(后端 674、前端 132)及前后端全量构建均通过
|
2026-10-10 18:38:49 +08:00 |
|
yml2213
|
412d219cca
|
feat(claim): 支持固定H5订单号领取与订单兑换有效性后台控制
- 数据库新增 099_order_claim_status.sql 迁移,扩展 claim_status、claim_status_reason 等字段
- 后端新增 /claims/h5/:orderNo、UID提交、分销提交接口,保留原有 token 接口双轨兼容
- 增加兑换有效性强拦截:暂停/关闭/退款/取消订单精准阻断并返回友好提示
- 后台新增 POST /admin/orders/:orderId/claim-status 接口与审计日志记录
- 前端管理后台(订单列表、详情页)支持 H5 兑换状态展示、一键复制链接及暂停/关闭/启用弹窗
- 前端领取页面适配 /h5/:orderNo 路由及暂停/关闭等状态的异常展示
- 验证方式:运行前后端类型检查、全量单元测试(后端 674、前端 132)及全量构建编译均通过
|
2026-10-10 18:05:46 +08:00 |
|
yml2213
|
2d727a6797
|
fix(worker-platform): 修复对已冻结打手调级时误报冻结原因必填的问题
- 做了什么:
1. 后端 reviewAdminWorkerUser 中优化 freezeReason 校验条件:区分是否为新变为 rejected 状态,若账号原本已处于冻结态且本次仅调整等级未变动状态,直接沿用已有冻结原因,不再强制要求传 freezeReason;
2. 前端 submitLevel 在提交调级时补充传递打手既有的 freezeReason;
3. 后端新增对应测试用例确保后续回归。
- 为什么:
当打手处于冻结状态(rejected)时,管理员对其执行调级操作因未携带 freezeReason 触发了冻结原因校验错误,导致调级失败。
- 验证方式:
运行 npm run check(671 项测试全绿,类型与构建检查通过)。
|
2026-10-10 13:51:58 +08:00 |
|
yml2213
|
85fc2eaa7c
|
feat(worker-platform): 支持后台人工调级锁定等级与解除自动升级限制
- 做了什么:
1. 新增数据库迁移 098_worker_level_lock.sql,在 worker_users 表增加 is_level_locked、level_locked_at、level_locked_reason、level_locked_by 字段;
2. 后端自动升级函数 maybeUpgradeWorkerLevelWithClient 和判定函数 shouldApplyAutomaticWorkerLevel 增加 is_level_locked 守卫,被锁定的打手跳过自动升级;
3. 后台打手审核调级服务 reviewAdminWorkerUser 支持等级锁定参数与变更记录,人工调级时自动写入 worker_level_logs 流水,并提供 updateAdminWorkerLevelLock 专用接口;
4. 前端打手列表等级列展示「已锁定」状态及 Tooltip 原因,调级弹窗支持「锁定自动升级」开关与原因输入,操作列提供「解除限制」快捷操作;
5. 打手详情抽屉补充展示等级锁定说明。
- 为什么:
解决打手被人工调整等级(如 VIP5 调至 VIP4)后,因历史累计完成单数满足门槛导致后续结算再次自动升级回高等级的问题,并支持后续随时解除限制恢复自动升级。
- 验证方式:
1. 后端新增 shouldApplyAutomaticWorkerLevel 与 mapWorkerUser 单元测试,运行 npm run check(670 测试全过,类型与 SQL guard 验证通过);
2. 前端新增 worker-level-lock.test.ts 单元测试,运行 npm run check(130 测试全过,typecheck 与 build 验证通过)。
|
2026-10-10 13:44:57 +08:00 |
|
yml2213
|
687bb40db1
|
fix(migrations): 已应用的迁移文件不能删除(否则服务起不来)
上一轮纠偏时我把迁移 096(`claim_submission_json`)从文件系统删掉了,只留新的 097 去 DROP 列。
结果 `node-pg-migrate` 的排序校验直接拒绝启动:
Not run migration 097_drop_claim_submission is preceding already run migration 096_...
因为数据库里已记录 096 已执行、而文件系统里没有它。后端因此进入重试循环、`/health/ready` 一直 503
(生产同样会中招,属于启动阻塞)。
正确做法:**保留 096 文件**(结构演进留在历史里),用 097 承担「删列」这一步;
两边的 `schema_migrations` 记录都完整,已应用 096 的环境与全新库都能正常迁移。
同时**还原 `check-sql-guard.ts` 为严格版**:既然不需要删迁移了,就不该放宽守卫
(原规则「历史迁移只能新增、不可修改/删除」继续生效,实测通过)。
实测:恢复后启动日志正常执行 `097_drop_claim_submission (UP)`、`/health/ready` 200、
`fulfillment_tasks.claim_submission_json` 已删除;`npm run check` 671/668/0 全绿。
|
2026-10-09 19:10:31 +08:00 |
|
yml2213
|
1339c6887e
|
refactor(claim): 删除自建皮肤领取页,路径式 /claim/<token> 复用现成领取页 + 物品图片
**删掉上一轮自建的领取页**
- `pages/claim-skin/*`、`services/claim-skin.ts`、`types/claim-skin.ts`、`tests/claim-skin.test.ts`
及其样式(client.css 里 `.claim-skin-*` 整段 254 行)
- 后台我加的展示项:「皮肤自动发货」筛选项、执行器标签、「买家提交信息」卡片
**路径式链接复用现成领取页**
- `utils/claim-path.ts`(中性命名 `isClaimPath` / `resolveClaimToken`)+ 单测
- `App.tsx`:pathname 命中 `/claim/<token>` → 独立 BrowserRouter 渲染**现成的 `ClaimPage`**
(页面零复制零改动:它只用 `useParams`,不读 hash);`#/claim/:token` 仍走主站 HashRouter,
两者互不影响,且有用例断言 hash 入口不命中路径判定
**物品图片**
- 后端领取详情的 `product.imageUrl` 接上(内部 SKU 在 `spec_json.code` / `sku_code` /
`sku_name` 哪个字段都能回退找到图,映射不到返回空串)
- 领取页新增 `ClaimProductImage`:居中大图(宽度 min(220px,62%)、圆角 16px),
空值或加载失败都降级虚线占位块,不出现破图
`npm run check`:前端 128 / 128 全绿(去掉旧用例 17 个、新增路径与图片用例)
|
2026-10-09 16:13:46 +08:00 |
|
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 |
|
yml2213
|
56ad06239b
|
fix(fulfillment): 抖音未命中皮肤映射时显式回落人工,避免静默漏单
原本未命中的抖音订单项只靠全局 `unmatchedExecutorKey` 兜底,而线上/联调环境该值
可能是空串(dev 库实测就是 `unmatchedExecutorKey=''`)——那种配置下未命中的抖音订单
既不建履约任务、也不进人工,等于**静默漏单**(需求要求的是「接单平台走人工」)。
改为在抖音分支里显式指定回落:命中权威映射 → `douyin_mashida_skin`;
未命中 → 直接以 `preferredExecutorKey=manual_dispatch` 选中人工通道,不依赖全局配置。
实测(dev 真实配置,`unmatchedExecutorKey` 为空串):
幸运币2个 / 套装-浪漫天命 → douyin_mashida_skin(isConfigured=true)
代练上分-星耀到王者(未命中)→ manual_dispatch(isConfigured=true)
空 SKU → manual_dispatch
对照 91卡券/快手 → 行为不变(未配映射仍 isConfigured=false)
|
2026-10-09 15:09:58 +08:00 |
|
yml2213
|
215a612b06
|
chore(douyin-mashida): 联调脚本支持皮肤领取链接
- `set-public-url`:切公网地址时**同时**写 `certLink` 与新的 `claimLinkBase`
(只切前者会让皮肤订单仍指向旧域名的领取页),并打印两者
- `preflight`:新增领取链接自检 —— 未填给 ⚠️(皮肤订单会退回固定兑换链接、
买家又要手输订单号);带 `#` 直接判为阻塞(会把人带到打手领取页);非 https 阻塞
|
2026-10-09 15:07:03 +08:00 |
|
yml2213
|
eacca11768
|
feat(claim-skin): 买家皮肤领取页(路径式 /claim/<token>)
买家从卡券/订单消息里的链接打开,看到物品图片与信息,填游戏账号即可领取。
- **路径式无 `#`**:新增独立 BrowserRouter 分支(照 `/redeem` 的做法),
`isClaimSkinPath` 只认 `/claim/<token>` 两段精确匹配;
打手领取页仍是主站 HashRouter 的 `#/claim/:token`(其 pathname 为 `/`),两者互不影响
- **物品卡片**:商品图(映射不到或加载失败都降级为占位块,不出现破图)、
内部 SKU、数量 ×N、订单号、引流标题(灰色副信息)
- **领取表单**:按接口下发的 `claimFields` 动态渲染(游戏账号/角色ID),
提交前二次确认让买家核对账号
- **状态**:待领取展示表单;已领取展示成功态(优先于 tokenStatus);
token 失效/过期/关闭/已用分别给中文提示与联系客服引导
- 纯函数抽到 `utils/claim-skin.ts` 并补 20 个单测(路由判定、状态映射、字段校验、格式化)
|
2026-10-09 15:06:56 +08:00 |
|
yml2213
|
b70741daec
|
feat(admin): 后台可配领取链接/发消息模板 + 可见买家账号 + 修正皮肤领取链接
**面板新增三项(抖音码速达接入配置)**
- 领取链接基址 (claimLinkBase):带实时预览「皮肤订单最终链接」,并拦截两类误配 ——
带 `#`(`#/claim` 是打手领取页,会把买家带进绑定 UID 流程)、缺 http(s) 协议
- 发消息模板 (messageTemplate):提示可用占位符;模板里没有 `{link}` 直接报错提示
- 付款后发领取消息开关 (claimMessageEnabled)
- 顺带把默认出网超时的展示值对齐后端(10s → 20s)
**后台可见买家提交的账号(运营发货依据)**
- `getAdminTaskDetail` 新增 `claimSubmission`,按 `canViewSensitiveTaskData` 门禁下发
(与 claimToken 明文同一门禁);任务详情新增「买家提交信息」卡片(可一键复制账号)
**修正:皮肤任务的领取链接**
- 详情接口原来用全局 `buildClaimUrl`(默认 hash 形式 `…/#/claim/…`),那是**打手**领取页;
皮肤任务改用 `claimLinkBase + '/' + token`,运营复制给买家的链接才是买家页
- 顺带把「履约渠道」筛选项加上「皮肤自动发货」、任务详情执行器标签加上它、
「来源渠道」加上「抖音码速达」(此前筛不出来抖音订单)
|
2026-10-09 15:06:49 +08:00 |
|
yml2213
|
56a8184576
|
feat(douyin-mashida): 皮肤自动发货(按内部 SKU 分流 + 领取链接 + 买家领取接口)
抖音码速达的皮肤类商品改为「自动发货 + 领取链接」:买家不用输订单号,点链接直接领。
**分流(按内部 SKU)**
- 平台闸门放行 `provider='douyin_mashida' && platform='doudian'`,走**独立分支**
(原有 91/快手路径一行未动,避免外溢)
- 判据:内部 SKU(`sku_order_list[].code`,即 order_items.sku_code)命中权威表
`AFFILIATE_DASH_SKU_MAPPING` → 新执行器 `douyin_mashida_skin`;未命中 → `manual_dispatch`
- 皮肤判定与商品图同源(都用那张权威表),保证「能路由 ⇒ 有图」
- 履约路由新增 `internal_sku` 匹配维度(`rules[].internalSku`,NFKC+去空白+小写,exact/contains);
旧规则缺 `matchField` 时默认 `product_name`,**归一化/优先级/回退顺序完全不变**
**执行器与领取 token**
- 新增 `douyin-mashida-skin-executor`:建 claim token、置 `link_generated/pending_claim`,
`resolveDeliveryLink` 返回本站路径式领取链接;注册进 registry、planner 分支、
bootstrap 种子 profile(`抖音皮肤自动发货`);进 `ROUTABLE_EXECUTOR_KEYS`
- token 粒度 = 一个履约单元一个(`claim_tokens.task_id` 唯一);一单多项时
cert_link/买家消息取 task id 最小的那个并告警
**cert_link 与发消息**
- 皮肤订单且该订单项已有 token 且配了 `claimLinkBase` → `cert_link = claimLinkBase/token`;
其它情况一律用 `certLink`(老行为不变)
- 领取链接**只认** `claimLinkBase`,绝不回退 `buildClaimUrl`/`orders.claimBaseUrl`
(那是 hash 形式的打手领取页,语义不同)
- 付款建单+发券成功后按 `messageTemplate`(`{link}/{sku}/{orderNo}/{productTitle}`)
经 `order.message` 发给买家;best-effort,失败只记 warn
**买家领取接口**
- `GET/POST /api/v1/claim-skin/:token`(限流、公开);详情含内部SKU/引流标题/商品图/
数量/金额/支付时间/动态表单字段;token 失效给明确 errorCode(404/410/409),不打 500
- 提交账号:先核销平台券再落记录(避免「记账了但券没核销」的资损),已提交幂等
**隐私**
- 买家游戏账号落 `fulfillment_tasks.claim_submission_json`(迁移 096),
**不进** `work_orders.material_json`(那是发给打手的素材);详情接口不返回券码明文/手机号/地址/open_id
|
2026-10-09 15:06:43 +08:00 |
|
yml2213
|
ecc8ffaee4
|
fix(douyin-mashida): 出网响应按大整数安全解析(product_id 被压精度)
真实联调发现:抖店大量 id 是 19 位数字,超过 Number.MAX_SAFE_INTEGER,
入站推送我们用 express.text() 收原文保住了精度,但**出网响应是 JSON.parse 出来的**,
19 位数字在解析瞬间就被压成相邻 double:
平台真值 product_id = 3847255376114352567
我们存到的 = 3847255376114352600
后果:拿这个 product_id 去调 product.detail 报「商品不存在」(实测),
任何按商品ID做的映射/查询都会错。
修复:抖音出网响应改用共享的 parseJsonObject(..., { preserveLargeIntegers: true })。
utils/json.ts 里其实早有 wrapLargeIntegerLiterals(≥16 位/超 MAX_SAFE_INTEGER 才包裹,
只处理值位置的整数字面量,不误伤字符串内数字与短数字),但全仓没有调用方 —— 现在接上。
实测:同一笔订单 product_id 已精确为 …567,product.detail 返回 code=10000 并拿到商品图。
补了 json 解析的大整数单测(含「字符串里的数字、短数字不受影响」的反例)。
|
2026-10-09 14:47:55 +08:00 |
|
yml2213
|
0b619b2915
|
feat(asset): 商品图资源 + 内部 SKU → 图片解析
买家领取页要展示「待领取物品图片」,图片按 affiliate_dash 的英文 sku 命名
(如 lucky_coin_x2.webp = 内部 SKU「幸运币2个」),从 affiliate_dash 项目
复制 38 个 slug(40 个文件,其中 2 个 slug 同时有 png/webp)到前端 publicDir
`apps/frontend/public/images/products/`,由 Vite 原样对外(不进 bundle)。
新增 `services/platforms/affiliate-dash/product-image.ts`:
内部 SKU →(复用权威映射表 AFFILIATE_DASH_SKU_MAPPING)→ 英文 sku → 图片 URL。
映射不到返回空串,由前端展示占位图(不猜、不拼不存在的文件);
以后图片挪 CDN 只改这一处。
测试含「映射表每个 sku 都必须有对应图片文件」的守卫,防止新增商品漏图。
|
2026-10-09 14:47:50 +08:00 |
|
yml2213
|
de2fb3e008
|
fix(douyin-mashida): 平台会改写券码 —— 核销改用平台侧券码 + 失败自愈
真实联调踩到的坑(已用真单复现并定位):
`coupons.syncV2` 返回 `10000 成功` **并不代表平台沿用了我们提交的 cert_no**。
实测 3 笔订单里有 2 笔,平台把我们的 DYC 券码换成了它自己生成的券码。
我们却一直拿**自己的**券码去 `coupons.verifyV3`,平台回
`10017 卡券核销失败[isv.business-failed:010021|核销码与订单不匹配]`;
更坑的是这个错误平台要 **~10.5 秒**才返回,正好越过当时的 10 秒超时,
前端只看到 `douyin_mashida_gateway_timeout`,完全看不出真实原因。
修复:
- 新增 `coupons.list` 出网封装;同步成功后按 order_id 回查平台侧 `cert_no`/`cert_id`
并落库(迁移 095 新增 platform_cert_no / platform_cert_id / platform_status / platform_synced_at)
- 核销时用 `resolveRedeemVerifyCertNo()`:**平台侧券码优先**,取不到才退回我方券码
- 核销失败后自愈 `tryRecoverVerify()`:
① 回查平台,若平台侧已核销 → 按对账结果标记本地已核销(修复"平台已核销、我们报失败"的漂移)
② 若是券码被平台改过 → 用平台券码**重试一次**
- 回查不到该订单的券 → 标记 sync 失败并告警(暴露"syncV2 成功但平台无券"的幽灵券)
- 默认出网超时 10s → 20s(平台业务错误要 ~10.5s 才返回,10s 会把业务错误伪装成超时)
实测验证:对一笔「平台侧已核销、本地仍是 synced」的订单调确认兑换,
10.7s 后返回 `status=verified`,platform_cert_no 成功回填为平台券码、
platform_status=3,并打出「对账发现平台侧已核销,按平台状态标记本地已核销」告警。
|
2026-10-09 14:08:55 +08:00 |
|
yml2213
|
7834ee7bef
|
feat(douyin-mashida): 商品展示统一改为内部 SKU(引流标题降级)
平台 sku_order_list[].code 是商家维护的**内部 SKU**(如「幸运币2个」),
product_name 只是店铺用来**引流**的长标题(如「【测试专用】和平精英皮肤小店入口…」)。
商家按内部 SKU 认货(后续履约匹配也用它),原来的展示把引流标题当商品名,
列表/详情/兑换页三处都会让人认错货。
改为以内部 SKU 为主、引流标题降级为副信息:
- 后台订单列表「商品 / 任务」:itemTitle = 内部 SKU,skuNick = 引流标题
- 后台订单详情「商品明细」:商品 = 内部 SKU;SKU/规格 不再重复展示同一串
- 兑换中心 /redeem:商品 = 内部 SKU(粗体)+ 引流标题(灰色小字,新增 productTitle 字段)
实现只在 `source === 'douyin_mashida'` 时生效(resolveOrderItemTitle /
resolveOrderItemSkuNick 加 douyin 分支),其它平台展示逻辑不变;
用真实订单形态补了单测。
|
2026-10-09 13:31:45 +08:00 |
|
yml2213
|
bce1168bbb
|
chore(douyin-mashida): 新增推送订阅与自测脚本
排查「下了单但本地什么都没收到」时定位到根因:**平台推送是订阅制的**,
只填 uid/secret 不会收到任何消息,必须调「消息设置」接口 `message.config`
配置 `message_url` + `topics` + `status=1`。此脚本把这一步自动化:
- 订阅:从后台配置读真实 uid/secret → 调 message.config(status=1)
→ 打印平台返回;`--disable` 可关闭推送(隧道失效时避免平台一直推空地址)
- 自测:打平台 `messagetest` 入口尝试触发一次推送
(注:该入口对 data 的解析要求未探明,实测各种编码/字段形态都返回
`20006 data参数内容解析异常`,故不把它当作必过项;真实下单才是权威验证)
- 顺带修复 lint:`Boolean(args.test)` 属多余的布尔转换
实测:订阅返回 `code=10000 成功`;随后用真密钥按平台形态签名推送自有订单,
后端成功调真实 orderDetail 建单并完成 coupons.syncV2(sync_status=success)。
|
2026-10-09 12:43:34 +08:00 |
|
yml2213
|
064267ad86
|
fix(douyin-mashida): pay_time 兼容平台实际返回的字符串格式
真实联调发现:`order.orderDetail` 返回的 `pay_time` 是格式化字符串
`"2026-10-09 12:28:02"`(北京时间),而文档写的是秒级 Integer,
消息推送里的 `pay_time` 又确实是 Integer —— 两种形态并存。
原实现只走 `parseEpochSecondsIso`,对字符串 `Number("2026-10-09 12:28:02")` = NaN,
会把真实支付时间丢掉、退化成"收到时间"。
改为 `parseDouyinMashidaTimeIso`:纯数字按秒级时间戳,`YYYY-MM-DD HH:mm:ss`(含 T)
按 +08:00 解析;平台用 `1970-01-01 08:00:00`(epoch 0)表示"无",返回空串。
用真实 orderDetail 响应反例补了单测(含 19 位订单号 + 子单号等于父单号)。
|
2026-10-09 12:43:24 +08:00 |
|
yml2213
|
e9a87194bd
|
chore(douyin-mashida): 公网联调工具链(切地址 + 前置自检)
真实联调必须让平台能主动推到本机,所以需要隧道;这两个脚本把「配置切换」和
「联调前该验的东西」自动化,避免手工漏配(尤其 certLink 漏改、占位密钥没换):
- `douyin-mashida:set-public-url`:把 certLink/afterVerifyLink 切成隧道地址,
打印平台侧要配的三项(message_url / topics / 卡券链接),并自检隧道:
用错误签名打公网地址,期望 401 —— 证明「隧道 → Caddy → 后端 → 验签」通了,
不需要密钥、也不产生真实订单
- `douyin-mashida:preflight`:四步自检
① 配置就绪度(含识别早期联调留下的占位密钥,它会让 hasSecret=true 骗过你)
② 真实网关连通性:平台是先校 uid 再校签名,带 --secret 时若平台报的不是
签名错误即证明签名/密钥正确(正式推送前就能排掉密钥问题)
③ 公网回调可达性 ④ certLink 与公网域名是否一致
- 平台文档只给了 code=10000 表示成功,无完整错误码表,故脚本不翻译码值、
只原样打印,并对实测观测到的码(如 10014)加说明
同时修掉 vite dev server 的一个潜在坑:`VITE_ALLOWED_HOSTS` 早已在 .env 与
compose 中配置,但 vite.config.ts 的白名单是硬编码的、从未读取该变量,
换隧道域名必然被 Vite 拦(Blocked request);现已真正接上并默认放行
常见隧道域名后缀。
|
2026-10-09 12:01:42 +08:00 |
|
yml2213
|
8c33ff62ec
|
feat(redeem): 固定兑换链接 /redeem(输入订单号 → 状态分流 → 兑换)
- 路径式入口 `https://<域名>/redeem`(不带 #):主站是 HashRouter,
故在 App.tsx 里早于 isWorkerSite() 插入独立 BrowserRouter 分支,
主站与打手站路由均不受影响;Caddy 已有 try_files 兜底,无需改部署
- 页面按 7 种状态渲染:not_found / unpaid / cancelled / refunded /
pending_certificate / redeemable / verified
- not_found 保留用户输入的订单号,不清空
- pending_certificate 只给显式「刷新」,不做自动轮询(公开限流接口,避免空转)
- verified 只展示后端已掩码的券码,绝不渲染明文 certNo
- confirm 不假设成功:一律按返回的 status 重渲染
- 纯函数抽到 utils/redeem.ts(无 React/antd、无 @ 别名),可直接跑单测
|
2026-10-09 12:01:35 +08:00 |
|
yml2213
|
080480f03b
|
feat(admin): 后台新增「抖音码速达」配置面板
- 新增 Tab:/admin/platform-shops?tab=douyinMashida
- 面板字段:enabled / baseUrl / uid / secret / certLink / afterVerifyLink /
afterVerifyMessage / messageType / timeoutMs / handledTopics(tags)
- 密钥脱敏:出参 secret 永远为空,输入框未编辑时对外 value 恒为 '',
掩码不会污染后端「空串=保留原值」语义
- 顶部展示 ready 状态,未就绪时用 Alert 列出缺哪几项;
始终展示平台侧要配的推送地址(可复制)
- baseUrl 命中本地地址时提示「当前指向本地网关桩,不会请求真实码速达」
- 把 AdminPlatformShopsPage 内联的 SecretFieldControl 抽成共享组件
(函数体与原实现逐字节一致,8 处既有调用点参数不变)
|
2026-10-09 12:01:29 +08:00 |
|
yml2213
|
72606796b3
|
feat(douyin-mashida): 抖音码速达最小接入后端(消息建单/券码同步/兑换核销/退款作废)
- 签名:出网 md5(secret+Σ(名+值)+secret) 与推送 md5(secret+data+pub_time+topic+secret)
两套独立实现,含文档 golden 向量单测(与 91 大写 / affiliate_dash HMAC 都不通用)
- 进单:trade_TradePaid 推送 → 验签 → webhook_events 幂等 → order.orderDetail
→ upsertOrderFromSource(provider=douyin_mashida / platform=doudian)
- 精度:19 位订单号超出 JS 安全整数,JSON.parse 会把 p_id 抹成 ...000;
故解析后从原始 JSON 文本用正则抓回精确数字(单测同时证明"不修会错")
- 券码:迁移 094 redeem_certificates((provider,order_id) 唯一做发券幂等)
+ DYC 券码生成 + coupons.syncV2 同步(子单号优先、父单兜底)
- 兑换:POST /api/v1/redeem/lookup|confirm,按 orderNo+IP 限流;
核销成功后 order.message 通知买家(best-effort,失败只记错误日志)
- 退款/取消:作废未核销券;券已核销时打 WARN 资损告警(已消费不可回滚)
- 后台:GET/POST /api/v1/admin/platform-config/douyin-mashida,
secret 出参置空 + 掩码,掩码回写不覆盖原值
- 消息路由挂在全局 body parser 之前(app.ts),验签必须用 body 原始字符串
|
2026-10-09 12:00:50 +08:00 |
|
yml2213
|
893388b37f
|
feat(consignment): 寄售仓库在库列表增加通过审核入库时间并在手动匹配中补全时间展示
- 寄售仓库 (ConsignmentInventoryPanel):表格中增加「通过审核入库时间」列并格式化展示,详情弹窗规范化展示入库时间、出库时间与提报时间
- 手动匹配弹窗 (ConsignmentManualMatchModal):候选订单列表展示工单创建时间,顶部横幅与已选概要展示现货入库时间及订单创建时间;从订单匹配现货时展示现货入库时间
- 内部匹配接口与工单拦截:后端 listInventoryMatchableOrders 返回工单创建时间及平台单号;发布拦截弹窗中增加展示现货入库时间
- 验证方式:运行前后端单元测试全部通过,前端 build 校验通过
|
2026-10-08 15:41:23 +08:00 |
|