# 产品分享链接带 adminId → 下单/支付锁定为分享人定制师 **类型**: 后端 + 前端协作改动 **前端处理者**: mmg **日期**: 2026-05-07 **关联**: 工单 #1814 / PR #1825 (已合 dev) **影响页面**: - 管理后台「产品列表 → 分享到小程序」弹窗(URL Scheme/URL Link 拼接) - 小程序「产品详情页」(`packages/product/core/detail/detail`) 接收 adminId 参数 - 小程序「下单」接口 (`POST /mp/order/create`) 新增 customizerId 选填字段 - 小程序「支付预下单」接口 (`POST /mp/payment/prepay`) 新增 customizerId 选填字段 --- ## 这是什么 定制师傅分享产品给客户时, 客户点链接进小程序下单, 订单的"定制师"锁定为分享人 adminId, 不再走系统随机分配。 设计参考: 截图里管理后台「分享到小程序」弹窗已能生成 URL Scheme / URL Link, 路径 `packages/product/core/detail/detail?id=&adminId=`。 --- ## 仅产品分享 ✅ 仅产品详情页支持 (URL `packages/product/core/detail/detail`) ❌ 不支持产品线 / 活动 / 订单 / 其他页面分享(产品线/活动等不带 adminId, 不受影响) 定制师傅在小程序内自己分享 (微信原生 onShareAppMessage) 暂不支持 — 需要先做 mp 用户和 admin 账号关联, 已开 #1815 单独跟进。 --- ## 前端必做的 4 件事 ### 1. 管理后台 PC「分享到小程序」弹窗 — URL 拼接 adminId 文件: `hl-ui/.../components/ProductShareDialog.vue` (或类似名) URL Scheme / URL Link 生成时, **小程序页面路径**字段值必须拼接 `&adminId=<当前登录管理员 adminId>`: ```js const path = `packages/product/core/detail/detail?id=${productId}&adminId=${currentAdminId}` ``` `currentAdminId` 从当前登录态拿(Vuex/Pinia 里的 `userStore.adminId`)。**类型必须是 number, 不要是 string** — 后端 `customizerId: Long` 强类型反序列化, String 会 400。 ### 2. 小程序产品详情页 — 解析 URL adminId 存 sessionStorage `packages/product/core/detail/detail.vue` onLoad / onShow: ```js onLoad(options) { if (options.adminId) { const adminId = Number(options.adminId) if (Number.isFinite(adminId) && adminId > 0) { uni.setStorageSync('share_customizer_id', adminId) } } } ``` **多次进入不同分享链接**: 每次都覆盖, 不累加(用户先进 A 师傅链接、再进 B 师傅链接, 最后进的 B 链接覆盖之前的 A)。 ### 3. 创建订单接口 — 透传 customizerId `POST /mp/order/create` 请求 body 加字段: ```js const customizerId = uni.getStorageSync('share_customizer_id') || null const body = { // ... 原有字段 customizerId, // null 或 number, 不要传 string } ``` 字段约定: | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `customizerId` | `number` (Long) | ❌ 选填 | 分享人 adminId; 不传/null/0 走系统随机分配; 后端 `@Min(1)` 校验 | ### 4. 支付预下单接口 — 同样透传 customizerId `POST /mp/payment/prepay` 请求 body 加字段: ```js const customizerId = uni.getStorageSync('share_customizer_id') || null const body = { orderId: '...', customizerId, // null 或 number } ``` **为什么支付路径也要传**? "以用户支付的链接为准" — 用户先进 A 师傅链接创建订单(订单 customizerId=A), 创建后退出去, 又进 B 师傅链接来这个订单页支付, 此时支付透传 customizerId=B → 后端漂移回写订单 customizerId=B。如果支付时不传 customizerId, 订单保持创建时的 customizerId 不变。 --- ## 后端兜底语义 (不要在前端做这些校验) 后端 CustomizerValidator 严格校验 4 类失败场景, 失败都**走系统随机分配**(不报错, 兼容老前端): | 失败原因 | 触发条件 | 前端表现 | |---------|---------|---------| | ADMIN_NOT_FOUND | adminId 不存在/已删除 | 兜底随机, 静默 | | INACTIVE_STATUS | admin status ≠ ACTIVE (禁用/锁定) | 兜底随机, 静默 | | WRONG_ROLE | admin roleKey ≠ CUSTOMIZER (普通管理员/客服/经理) | 兜底随机, 静默 | | FEIGN_ERROR | user-service Feign 失败 | 兜底随机, 静默 | | INVALID_INPUT | customizerId == null / ≤ 0 | 兜底随机, 静默 | 后端在所有失败路径**只打 WARN 日志不返错**, 前端不需要展示"分享师傅不可用"这类提示, 用户体验完全无感。 --- ## 验证方式 (前端联调时) 1. 用一个**有效 CUSTOMIZER + ACTIVE** 的 adminId 拼 URL 分享 → 客户下单后看「我的订单」是不是显示这个师傅 2. 创建订单后**不立刻支付**, 退出小程序, 再用另一个**有效 CUSTOMIZER** 师傅的分享链接进入这个订单 → 支付前透传新 customizerId → 订单详情应该显示新师傅 3. 不分享直接下单 (URL 无 adminId 参数) → 系统随机分配, 行为同改造前 --- ## 兼容性 - ✅ **零破坏性平滑发布**: 老前端不传 customizerId → 后端 null fallback → 兜底随机 = 改造前行为, 后端先合不影响线上 - ✅ 老订单 customizer_id 不动, 仅新建/支付路径生效 - ❌ 不影响 admin 端创建订单流程(后台代下单仍是 admin 自己当 customizer) --- ## 字段约束硬规则 - 类型必须 `number` (Long) — 不要传 `string` - URL `?adminId=` 后端拿到是 `string`, 必须前端 `Number(adminId)` 转换后才放进请求 body - 不传 / 传 `null` / 传 `0` / 传负数 → 全部走兜底随机 - 不需要前端做任何校验 (validity / role 等), 后端全权负责 --- ## 不在本期范围 (后续工单跟进) - ❌ 定制师傅在小程序内分享 (微信原生 onShareAppMessage 拼 URL): 需要先做 mp 用户与 admin 账号绑定 → #1815 - ❌ 分享统计 / 分销提成: 现有 `sharer_openid` 字段独立维护, 不与 customizer 强绑定 --- ## 责任人 @mmg 接入前端: 4 步 (PC 弹窗 URL / 小程序 onLoad 解析 / 创建订单透传 / 支付透传)