GET /admin/user/customizers - 定制师下拉(无 status / 企微过滤)
POST /admin/product/item/{id}/share body {targetAdminId} - 复制产品+产品线给目标定制师
权限点 PRODUCT_SHARE 默认绑 SUPER_ADMIN / ADMIN
明确澄清: 与 09_frontend_notice_admin_product-share-remove-target-user-step.md
(已撤回) 不是同一个事情, 那个通知针对的是 url-scheme 老接口, 本通知是真正的
"分享产品" 新功能 (复制产品到目标定制师名下), mmg 不要混淆。
10 KiB
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<List<CustomizerSimpleVO>>:
{
"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/jsonAuthorization: Bearer <admin token>
Path 参数:
idLong 必填 — 源产品 productId
Request body ProductShareReqVO:
{ "targetAdminId": 1002 }
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
targetAdminId |
Long | ✅ | 目标定制师 adminId(取自上方 /admin/user/customizers 列表的 data[].adminId) |
Response Result<ProductShareRespVO>:
{
"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} 的分享) 中文括号) |
业务行为(逐项):
- 校验当前 admin 持
PRODUCT_SHARE权限点 → 否返403 - 校验
targetAdminId对应 admin 的roleKeys含CUSTOMIZER→ 否返400 "目标用户不是定制师" - 校验源产品存在 → 否返
404 - 复制产品线: 新生成 lineId,其余字段照抄源产品线
- 复制产品(基于已有
ProductCopyService):lineId指向新产品线name= 源 name +(来自 {操作人 username} 的分享)createdBy=targetAdminId(覆盖默认的当前操作人)status=DRAFTproductNo= 新生成- 不复制
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/...(分享弹窗相关)
-
下拉接口换地址:
- 改前:
GET /admin/user?page=1&pageSize=100(老接口被硬编码角色挡,显示"没有权限") - 改后:
GET /admin/user/customizers(本 PR 新增,无过滤) - 字段映射:
data[].adminId→ 下拉 value,data[].username→ 下拉 label,data[].avatar→ 下拉 icon(可选)
- 改前:
-
「确定」按钮接口:
- 改前: 不存在(后端没接口,所以点"确定"实际什么都没发生 / 报 404)
- 改后:
POST /admin/product/item/{id}/sharebody{targetAdminId} - 成功后展示成功 toast,可加跳转「定制师视角查看新产品」入口(用
data.newProductId)
-
目标用户下拉为空时的兜底:
- 如果系统里一个定制师都没有,接口返
data: [],前端显示「请先创建定制师账号」之类的空态 - 不要再用上一版逻辑里"没权限"的 toast(本接口不会再返 403)
- 如果系统里一个定制师都没有,接口返
-
"分享后规则"提示文案(已在弹窗右侧展示,与后端行为已对齐):
- 状态为草稿,需重新编辑发布 ✅ 后端确实置
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_ADMINADMIN
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