1. 目标和结论
商品详情页现在不是只展示当前商品,而是要让用户在任意入口都能继续找到可购买、可返现的商品。尤其是用户粘贴的商品没有返佣、没有库存、不能转链时,不能让链路断掉,要推荐“相似可返现商品”。
POST /api/v1/products/recommendations 获取推荐商品。后端新增 SIMILAR 场景,专门处理“当前商品无法返现时,推荐同类替代商品”的场景。
当前业务为 User-only 单层邀请返佣:所有用户业务角色都是 USER,用户之间只有一层直接邀请返佣。商品详情接口中可能带有 viewer、starLevel、shareCommissionBps 等字段,前端一律把 viewer.role 按 USER 处理,starLevel 不参与收益计算;邀请人可得比例以服务端当前有效规则返回的结果为准。
2. 三类推荐场景
| 场景 | scene | 出现位置 | 业务含义 | 关键原则 |
|---|---|---|---|---|
| 店铺/品牌推荐 | SHOP |
商品详情页“根据这个商品推荐:关联店铺/品牌商品” | 尽量展示同店铺或同品牌的商品。 | 不做泛化兜底,避免把别的店铺商品误展示成同店铺商品。 |
| 同品类推荐 | CATEGORY |
商品详情页“也是推荐:根据商品品类推荐” | 展示同类目下的可返现商品。 | 如果同类目搜空,可以退化到通用热门商品,保证页面不断流。 |
| 无返佣相似商品推荐 | SIMILAR |
后端已支持;H5 无返佣替代区当前仍使用 CATEGORY |
根据商品标题、类目、店铺名提取关键词,推荐相同或相近商品。 | 优先使用商品标题提取出的核心词,再用类目/店铺兜底。 |
3. 整体调用流程
sequenceDiagram
participant User as 用户
participant H5 as 商品详情页
participant API as BeeX API
participant Cache as 本地/服务端缓存
participant TK as TikTok APIs
participant Shopee as Shopee API
participant Lazada as Lazada API
User->>H5: 打开商品详情
H5->>API: POST /api/v1/products/resolve
API-->>H5: 商品详情 + 用户可见返现 + 兼容用户上下文
H5->>Cache: 读取推荐缓存
Cache-->>H5: 先显示旧推荐(如有)
H5->>API: POST /api/v1/products/recommendations scene=SHOP
H5->>API: POST /api/v1/products/recommendations scene=CATEGORY
alt 当前商品无返佣或不可转链
H5->>API: POST /api/v1/products/recommendations scene=CATEGORY
Note over H5,API: 后端支持 SIMILAR,但当前 H5 尚未切换
end
API->>TK: TikTok Campaign / Creator 搜索
API->>Shopee: Shopee productOfferV2 搜索
API->>Lazada: Lazada product feed/search
API-->>H5: 推荐商品列表
H5->>Cache: 写入 72 小时缓存
4. 接口协议
4.1 推荐接口
POST /api/v1/products/recommendations
Content-Type: application/json
| 字段 | 类型 | 说明 |
|---|---|---|
countryCode | string | 当前历史字段,服务端仍兼容。国家已经按数据中心隔离,业务判断不依赖它。 |
platform | string | TIKTOK、SHOPEE、LAZADA。为空时可以跨平台聚合。 |
scene | enum | SHOP、CATEGORY、SIMILAR。 |
keyword | string | 运营或前端显式传入的搜索词,优先级最高。 |
productName | string | 当前商品标题,主要给 SIMILAR 场景提取相似商品关键词。 |
shopId | string | 店铺 ID。Shopee 的店铺推荐必须依赖它。 |
shopName | string | 店铺名。TikTok Campaign 缓存可以用精确店铺名匹配。 |
categoryId | string | 平台类目 ID。品类推荐优先使用。 |
categoryName | string | 类目名。类目 ID 缺失时用于关键词兜底。 |
excludeProductId | string | 排除 BeeX 当前商品 ID,避免推荐列表出现自己。 |
excludePlatformProductId | string | 排除平台商品 ID,避免跨来源重复。 |
pageSize | number | 推荐条数,默认由后端限制。 |
cursor | string | 分页游标,当前详情推荐主要取首屏。 |
4.2 SHOP 请求示例
{
"countryCode": "ID",
"platform": "TIKTOK",
"scene": "SHOP",
"shopName": "LUNAVEE INDONESIA",
"shopId": "749322114",
"excludeProductId": "tiktok-1733045523587827027",
"excludePlatformProductId": "1733045523587827027",
"pageSize": 20
}
4.3 CATEGORY 请求示例
{
"countryCode": "ID",
"platform": "TIKTOK",
"scene": "CATEGORY",
"categoryId": "601493",
"categoryName": "Sabun & Sabun Mandi",
"excludeProductId": "tiktok-1733045523587827027",
"excludePlatformProductId": "1733045523587827027",
"pageSize": 20
}
4.4 SIMILAR 请求示例
{
"countryCode": "ID",
"platform": "TIKTOK",
"scene": "SIMILAR",
"productName": "Lunavée - Rose Flowers Fragrance Body Wash 400ML",
"categoryId": "601493",
"categoryName": "Sabun & Sabun Mandi",
"shopName": "LUNAVEE INDONESIA",
"excludeProductId": "tiktok-1733045523587827027",
"excludePlatformProductId": "1733045523587827027",
"pageSize": 20
}
5. H5 实现
商品详情页在 app/pages/product-detail.vue 内部处理三类推荐:
| 方法 | 场景 | 说明 |
|---|---|---|
loadShopProductRecommendations() | SHOP | 根据当前商品的 shopId/shopName 拉店铺/品牌商品。 |
loadCategoryProductRecommendations() | CATEGORY | 根据当前商品的 categoryId/categoryName 拉同品类商品。 |
loadUnavailablePlatformRecommendations() | CATEGORY | 当前商品无返佣、不可购买或不可转链时,当前 H5 按类目拉替代商品。后端虽已支持 SIMILAR + productName,但尚未接入这条 H5 调用。 |
H5 会给推荐接口做本地缓存。缓存键包含 scene、platform、当前商品 ID、店铺、类目、商品标题和 pageSize。这样同一商品反复进入详情时,会先显示本地缓存,再后台刷新。
6. 后端实现
6.1 新增枚举
ProductRecommendationScene 现在包含:
public enum ProductRecommendationScene {
SHOP,
CATEGORY,
SIMILAR
}
6.2 请求模型新增 productName
AffiliateProductRecommendationRequest 增加 productName,用于无返佣替代商品搜索。它不替代 keyword,而是在前端没有显式搜索词时作为关键词来源。
6.3 关键词生成规则
| scene | 关键词优先级 | 说明 |
|---|---|---|
SHOP | keyword → shopName → categoryName | 店铺场景优先使用店铺信息。 |
CATEGORY | keyword → categoryName → shopName | 品类场景优先使用类目信息。 |
SIMILAR | keyword → productName → categoryName → shopName | 相似商品场景优先使用商品标题。 |
6.4 SIMILAR 的标题压缩
商品标题通常很长,比如包含促销词、规格、单位、国家名、套装词。后端不会直接拿完整标题搜索,而是用 compactSimilarKeyword() 做压缩:
- 按字母和数字切词。
- 转小写。
- 过滤通用词和促销词,例如
official、shop、produk、promo、bundle、ml、gram、indonesia。 - 过滤纯数字和过短词。
- 最多取前 4 个核心词。
这样 Lunavée - Rose Flowers Fragrance Body Wash 400ML 会被压缩为更适合搜索的核心词,而不是把无意义规格也带到平台搜索。
6.5 关键词过滤增强
后端 filterByKeyword() 现在支持两种命中方式:
- 完整归一化字符串包含:适合简单关键词。
- 所有关键词 token 都命中:适合标题里有空格、横杠、标点的场景。
匹配字段包括商品名、品牌名、类目、店铺名、平台商品 ID 和 BeeX 商品 ID。这样可以避免因为 Cool-Vita、Coolvita、Cool Vita 这种标点差异导致错误 miss。
7. 平台搜索策略
7.1 TikTok
| scene | 数据来源 | 策略 |
|---|---|---|
SHOP | Partner Campaign 本地缓存 | 用 shopId 或精确 shopName 匹配。不会用 Creator 关键词模糊搜店铺,避免跨店污染。 |
CATEGORY | Partner Campaign 缓存 + Creator Search | 优先按类目找 Campaign 高返佣商品,再通过 Creator 搜索补足。 |
SIMILAR | Partner Campaign 缓存 + Creator Search | 先用相似关键词过滤 Campaign 缓存;如果不足,再按类目/Creator 搜索兜底。 |
7.2 Shopee
| scene | 数据来源 | 策略 |
|---|---|---|
SHOP | Shopee productOfferV2 | 必须有 shopId。如果只有店铺名,当前不做模糊店铺搜索。 |
CATEGORY | Shopee productOfferV2 | 优先传 productCatId,没有类目 ID 时用关键词。 |
SIMILAR | Shopee productOfferV2 | 使用从商品名压缩出来的关键词,同时尽量带类目过滤。 |
7.3 Lazada
Lazada 当前通过 Affiliate Product Feed/Search 类能力做推荐。SIMILAR 会把压缩后的商品关键词传给 Lazada 搜索;如果 Lazada 返回的商品缺少价格、佣金或稳定跳转链接,后端会过滤或降级展示。
productId 返回价格、图片和佣金。不能拿到完整信息时,BeeX 不能在 C 端承诺返现金额,只能展示可确认的信息。
8. 过滤、缓存和兜底规则
8.1 过滤规则
推荐列表只应该展示能形成购买链路的商品。后端会过滤掉当前商品本身,并尽量过滤掉以下商品:
- 没有稳定购买链接的商品。
- 价格小于等于 0 的商品。
- 用户可见返现小于等于 0 的商品。
- TikTok Campaign 中
review_status != APPROVED的商品。 - 平台返回无库存、下架、不可购买、店铺休假等明确状态的商品。
8.2 兜底规则
| scene | 是否兜底 | 原因 |
|---|---|---|
SHOP | 否 | 店铺推荐必须准确。搜不到就隐藏,不应该拿别的店铺冒充。 |
CATEGORY | 是 | 品类推荐是“猜你喜欢”,搜空时可以用热门商品保持页面连续。 |
SIMILAR | 是 | 当前商品不可返现时,目标是给用户一个继续购买的出口,可以先同类,再热门兜底。 |
8.3 缓存规则
H5 对推荐结果使用本地缓存,先渲染缓存,再后台刷新。这样详情页不会因为多个推荐接口同时请求而卡住主内容。
| 缓存内容 | 缓存键 | 设计理由 |
|---|---|---|
| 店铺推荐 | scene + platform + productId + shopId/shopName | 同一商品、同一店铺可复用。 |
| 品类推荐 | scene + platform + productId + categoryId/categoryName | 避免不同品类串数据。 |
| 相似推荐 | scene + platform + productId + productName + category/shop | 标题不同,相似推荐也应该不同。 |
9. 用户上下文与字段
用户上下文接口返回体示例:
GET /api/v1/growth/users/{userId}/role
商品详情返回体结构示例:
{
"shareCommissionBps": 1200,
"viewer": {
"role": "USER",
"starLevel": 1
}
}
viewer.role 按 USER 处理,starLevel(若字段仍在)不参与收益计算。shareCommissionBps 表达当前有效的一级邀请返佣比例。
cashbackMinor、活动返佣明细和当前有效规则结果,一律以服务端当前有效规则为准。
10. 代码位置和验证
10.1 后端代码
| 文件 | 作用 |
|---|---|
beex-service/seahub-core/src/main/java/com/seahub/x/core/affiliate/ProductRecommendationScene.java | 新增 SIMILAR 场景。 |
beex-service/seahub-core/src/main/java/com/seahub/x/core/affiliate/AffiliateProductRecommendationRequest.java | 新增 productName 请求字段。 |
beex-service/seahub-core/src/main/java/com/seahub/x/core/affiliate/AffiliateProductService.java | 推荐搜索主逻辑、关键词压缩、平台路由、缓存商品过滤、兜底规则。 |
beex-service/seahub-core/src/main/java/com/seahub/x/core/affiliate/AffiliateProductItem.java | 当前仍保留 viewer 和 shareCommissionBps 兼容字段;业务角色统一按 USER 处理。 |
10.2 H5 代码
| 文件 | 作用 |
|---|---|
beex-app-h5/app/pages/product-detail.vue | 详情页加载 SHOP/CATEGORY/SIMILAR 推荐,缓存推荐结果,使用详情返回的 viewer 更新本地角色。 |
beex-app-h5/app/types/beex-api.ts | 前端接口类型新增 SIMILAR、productName、viewer。 |
10.3 已做验证
- 后端:
mvn -pl seahub-core -DskipTests compile通过。 - H5:
npm run build通过。 - 代码格式:
git diff --check通过。
10.4 还需要业务测试的点
- 用一个有返佣 TikTok 商品进入详情,确认店铺推荐、品类推荐正常出现。
- 用一个无返佣或不可转链商品进入详情,确认
SIMILAR能返回替代商品。 - 用 Shopee 商品进入详情,确认有
shopId时店铺推荐正常,没有shopId时不会错误兜底。 - 用 Lazada 商品进入详情,确认能拿到的字段展示正常,拿不到返现时不展示虚假返现金额。
- 确认详情页对
/api/v1/growth/users/{userId}/role返回值一律按USER身份处理,收益以服务端当前有效规则为准。 - 若后续把无返佣替代区切换为
SIMILAR,需单独完成 H5 接线和回归测试后再把本文状态改为“已闭环”。