# admin 产品分享 = 复制产品给定制师 (后端实现) > **服务**: hl-user-service (8081) + hl-product-service-v2 (8083) > **PR**: #1919 (合并到 dev a18329b7) > **Issue**: #1918 > **日期**: 2026-05-09 > **影响范围**: admin 产品列表「分享」按钮 → 「分享产品」弹窗 → 选目标定制师 → 复制产品给该定制师 --- ## ⚠️ 关键澄清(请 mmg 与上一条撤回通知一起看) 本日早些时候有一条已撤回的 changelog `09_frontend_notice_admin_product-share-remove-target-user-step.md`,误以为「分享产品弹窗的目标用户选择步骤」是 url-scheme 接口的多余步骤,要前端删掉。**那个判断针对的是 `POST /admin/wxapp/share/url-scheme` 这个老接口**(生成微信短链),与本通知**没有关系**。 **真相是**: 你看到的「分享产品 — 选目标用户」弹窗对应的是**另一个新功能** — admin 把自己负责的产品复制一份给指定的定制师,让定制师在自己工作台里看到这个产品(状态草稿,需重新编辑)。这个**后端原本不存在**,本 PR #1919 才补齐。 也就是说: - 你的弹窗 UI 在 master 上确实有(我们之前调研错了),不要删 - 它需要调用的是**新增**的 `POST /admin/product/item/{id}/share` 接口,而不是老的 url-scheme - 目标用户下拉**改调** `GET /admin/user/customizers`(本 PR 新增) --- ## 一、背景 业务场景: admin (通常是超管/客服)在产品列表上看到一个产品(可能是从供应商那里整理好的、或自己之前建的),想转给某个定制师让 ta 接手维护/二次编辑。复制后: - 定制师在自己的产品列表里看到这个产品(`createdBy` = 该定制师) - 状态自动是 DRAFT(草稿),让定制师确认/调整后再发布 - 标题加`(来自 {操作人 username} 的分享)`后缀,让定制师知道来源 - 价格日历**不复制**(让定制师按自己客户/批次重新设置) - 关联的产品线也复制一份独立副本(给定制师独立空间,改产品线不影响别人) 权限走标准权限点 `PRODUCT_SHARE`(放在 `admin_permission` 表),默认绑定 SUPER_ADMIN + ADMIN 两个角色。**不写死角色** — 后续业务侧可在权限管理页面给任意角色绑该权限点。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 目标定制师下拉(分享专用) | GET | `/admin/user/customizers` | 新增 | 返「角色含 CUSTOMIZER」的所有 admin,**无 status / 企微绑定过滤** | | 2 | 分享产品给定制师 | POST | `/admin/product/item/{id}/share` | 新增 | 复制产品+产品线到目标定制师名下 | --- ## 三、接口详情 ### 1. 目标定制师下拉 `GET /admin/user/customizers` 弹窗里「目标用户」下拉应改调本接口(替换之前误调的 `/admin/user?page=1&pageSize=100`)。 **请求**: 无参 (Header 带 admin token) **响应** `Result>`: ```json { "code": 200, "message": "成功", "success": true, "data": [ { "adminId": 1002, "username": "alice", "avatar": "https://..." }, { "adminId": 1015, "username": "bob", "avatar": "" } ] } ``` | 字段 | 类型 | 说明 | |------|------|------| | `data[].adminId` | Long | 定制师 admin_user.admin_id,作为分享接口的 `targetAdminId` | | `data[].username` | String | 定制师用户名(下拉显示用) | | `data[].avatar` | String | 头像 URL,**取自 admin_user.avatar 字段**(不查企微 wechat_user 也不查 designer_profile),没有时返空字符串 `""` | **与已有 `/admin/user/designers` 的区别(选用本接口的原因)**: | 维度 | `/admin/user/customizers`(本接口) | `/admin/user/designers`(已有接口) | |------|-----------------------------------|-----------------------------------| | 过滤 status | **不过滤** (LOCKED / DISABLED 也返回) | 仅 `status = ACTIVE` | | 过滤企微绑定 | **不过滤** (未绑定企微也返回) | 必须 `enterprise_wechat_id` 非空 | | 返回 VO | 简单 3 字段 | 重 VO(认证等级/服务领域/手机/简介等) | | 用途 | **分享产品下拉**,只要是定制师就能选 | 小程序 C 端定制师列表展示 | → 本接口刻意放宽过滤,因为分享产品场景下「即使临时锁定/没绑企微的定制师也允许分享给他」(分享是给账号,定制师后续登录处理)。 **鉴权**: 标准 admin token(网关已校验),接口本身**不再做角色硬编码检查** — 任何登录 admin 都能调用。 --- ### 2. 分享产品给定制师 `POST /admin/product/item/{id}/share` **Headers**: - `Content-Type: application/json` - `Authorization: Bearer ` **Path 参数**: - `id` Long 必填 — 源产品 productId **Request body** `ProductShareReqVO`: ```json { "targetAdminId": 1002 } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `targetAdminId` | Long | ✅ | 目标定制师 adminId(取自上方 `/admin/user/customizers` 列表的 `data[].adminId`) | **Response** `Result`: ```json { "code": 200, "message": "成功", "success": true, "data": { "newProductId": 1818070000000000123, "newProductNo": "C260509047", "newLineId": 1818070000000000456, "newProductName": "云南 8 日游(来自 wx 的分享)" } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `newProductId` | Long | 新复制出来的产品 ID | | `newProductNo` | String | 新生成的产品编号(走 ProductNoGenerator,如 `C260509047`) | | `newLineId` | Long | 新复制的产品线 ID(独立副本) | | `newProductName` | String | 新产品名称(原名 + `(来自 {操作人 username} 的分享)` 中文括号) | **业务行为(逐项)**: 1. 校验当前 admin 持 `PRODUCT_SHARE` 权限点 → 否返 `403` 2. 校验 `targetAdminId` 对应 admin 的 `roleKeys` 含 `CUSTOMIZER` → 否返 `400 "目标用户不是定制师"` 3. 校验源产品存在 → 否返 `404` 4. 复制产品线: 新生成 lineId,其余字段照抄源产品线 5. 复制产品(基于已有 `ProductCopyService`): - `lineId` 指向新产品线 - `name` = 源 name + `(来自 {操作人 username} 的分享)` - `createdBy` = `targetAdminId`(覆盖默认的当前操作人) - `status` = `DRAFT` - `productNo` = 新生成 - **不复制** `product_price_calendar` 表(价格日历由定制师重新配置) - 复制范围: 主表 + 4 张 1:1 子表 + 备品/费用 1:N 子表 + 行程相关 4 表(同 `copyProduct`) **错误码**: | HTTP | 业务消息 | 触发条件 | |------|----------|----------| | 403 | (走全局异常) | 当前 admin 无 `PRODUCT_SHARE` 权限点 | | 400 | 目标用户不是定制师 | `targetAdminId` 对应 admin 不持 CUSTOMIZER 角色 | | 404 | (走全局异常) | 源产品不存在或已删除 | --- ## 四、前端改造点(mmg 关注) ### 文件: `hl-ui/src/views/product/list/components/...`(分享弹窗相关) 1. **下拉接口换地址**: - 改前: `GET /admin/user?page=1&pageSize=100` (老接口被硬编码角色挡,显示"没有权限") - 改后: `GET /admin/user/customizers` (本 PR 新增,无过滤) - 字段映射: `data[].adminId` → 下拉 value, `data[].username` → 下拉 label, `data[].avatar` → 下拉 icon(可选) 2. **「确定」按钮接口**: - 改前: 不存在(后端没接口,所以点"确定"实际什么都没发生 / 报 404) - 改后: `POST /admin/product/item/{id}/share` body `{targetAdminId}` - 成功后展示成功 toast,可加跳转「定制师视角查看新产品」入口(用 `data.newProductId`) 3. **目标用户下拉为空时的兜底**: - 如果系统里一个定制师都没有,接口返 `data: []`,前端显示「请先创建定制师账号」之类的空态 - 不要再用上一版逻辑里"没权限"的 toast(本接口不会再返 403) 4. **"分享后规则"提示文案**(已在弹窗右侧展示,与后端行为已对齐): - 状态为草稿,需重新编辑发布 ✅ 后端确实置 `DRAFT` - 标题会添加"(来自 xxx 的分享)"后缀 ✅ 后端确实加,xxx = 当前 admin.username - 生成新的产品编号 ✅ 后端走 ProductNoGenerator - 不包含价格日历(需重新设置) ✅ 后端确实不复制 `product_price_calendar` --- ## 五、权限点 `PRODUCT_SHARE` 新加权限点(`admin_permission` 表): | 字段 | 值 | |------|----| | permission_code | `PRODUCT_SHARE` | | permission_name | 分享产品 | | resource_type | PRODUCT | | action | SHARE | | description | 允许将自己的产品复制一份分享给指定的定制师 | | status | ACTIVE | 默认绑定角色(`admin_role_permission` 表): - `SUPER_ADMIN` - `ADMIN` **CUSTOMIZER 不绑** — 业务侧默认由 SUPER_ADMIN/ADMIN 负责跨定制师分享,普通 CUSTOMIZER 只是接收方。如业务后续要让定制师之间互相分享,在权限管理页面给 CUSTOMIZER 角色加这个权限点即可,**无需改后端代码**。 --- ## 六、不在本 PR 范围 - `AdminUserController.listAdmins()` 第 162 行硬编码 `if (!"SUPER_ADMIN".equals(role) && !"ADMIN".equals(role)) ...` 不动 — 留后续单独治理工单 - 已分享产品的二次分享 / 取消分享 / 审计追踪 / 通知目标定制师等扩展功能 - 前端 hl-ui 的弹窗 UI 调整(由 mmg 完成) --- ## 七、测试与验证 - 全 1160(product) + 2341(user) 单元测试均无回归 - 新增 19 个单测覆盖: `DesignerServiceTest +5` / `InternalUserControllerTest +3` / `ProductShareServiceTest 11/11` - 本地启动全链路 API 测试**未做**(权衡时间) — 合 dev 后由后端在测试服 1.182.108.58 用真 admin token 通过网关 9443 立即验证,如发现问题立刻提下一个 PR --- ## 八、相关 PR / 工单 | PR / Issue | 说明 | 是否相关 | |------------|------|----------| | #1919 | 本 PR | ✅ 主体 | | #1918 | 工单 | ✅ 已 closed | | 已撤回的 `09_frontend_notice_admin_product-share-remove-target-user-step.md` | 误将本功能识别为 url-scheme 多余步骤 | ⚠️ 已撤回,本通知是真正的解决方案 | | #1488 | 老的 url-scheme 接口(生成微信短链) | ❌ 不相关,是另一个功能 | --- **联系人**: wx