组装修(Route Group Decoration)

针对同一类详情页(商品、专辑、优惠券等),按对象划分不同装修方案。
上级文档:theme-decoration-and-liquid-rendering.md


一、要解决什么问题

默认情况下,所有商品详情页共用 group_id = 0(默认组)o_theme_block 的装修。

组装修允许商家:

  • 创建分组(如「爆款商品组」「清仓组」)
  • 把指定商品/专辑/优惠券等 obj 绑定到分组
  • 为每个分组维护独立的积木块配置(o_theme_block.group_id > 0

访客打开某个商品详情时,系统根据 商品 ID → group_id → 该组的 blocks 渲染,而不是默认组。

flowchart LR
    subgraph Default["默认组 group_id=0"]
        D1[商品 A 详情]
        D2[商品 B 详情]
        DB0[(o_theme_block<br/>group_id=0)]
    end

    subgraph Custom["自定义组 group_id=5"]
        C1[商品 C 详情]
        DB5[(o_theme_block<br/>group_id=5)]
    end

    D1 --> DB0
    D2 --> DB0
    C1 --> DB5

二、数据表

2.1 o_theme_route_group(分组定义)

Model:ThemeRouteGroupModel

字段说明
store_id / theme_id租户 + 主题隔离
name分组名称(同 theme + obj_type 下不可重名)
obj_type对象类型(见 § 三)
is_default标记用;虚拟默认组不在表中

2.2 o_theme_route_group_item(对象 ↔ 分组绑定)

Model:ThemeRouteGroupItemModel

字段说明
group_id所属分组
obj_id商品 ID、专辑 ID 等
obj_type与 group 一致

唯一性约束(业务层):同一 store_id + theme_id + obj_id + obj_type 只能属于一个组。绑到新组前会 deleteObjIdRelation 解绑旧组。

2.3 o_theme_block.group_id(分组下的积木)

group_id含义
0默认组(未绑定到自定义组的对象使用)
> 0自定义组专属积木块

默认组与自定义组的 blocks 分库存储,互不影响。发布、预览字段与普通积木相同(params / preview_params)。


三、支持的 obj_type 与 route

ThemeRouteGroupModel::getAllObjTypeList()8 种

obj_type对应 route前台 Controller 示例
PRODUCTproduct/detailProduct::detail
COLLECTIONcollection/detailCollection::detail
TOPICtopic/detailTopic::detail
BLOGblog/detailBlog::detail
COUPONcoupon/detailCoupon::detail
PROMOTIONpromotion/detailPromotion::detail
NEWSnews/detailNews::detail
ACCOUNTaccount/default个人中心(obj_id 为客户级别 ID)

映射方法:ThemeRouteGroupHandlerService::objTypeToRoute() / routeToObjType()

支持组装修的路由列表getAllSupportGroupRouteList() = 上述 8 种 route。

GET themes/{id}/configsThemesConfigResponse 为每个 pages_setting 项追加 is_support_group: true/false


四、虚拟「默认组」

未在 o_theme_route_group_item 中绑定的对象,走默认组

默认组 不存在于 DB,由代码构造:

// ThemeRouteGroupHandlerService::generateDefaultGroup()
id       => 0  (DEFAULT_GROUP_ID)
name     => 'default'
obj_type => 当前类型
is_default => 1

后台列表 GET themes-group/ 第一页会在结果头部插入这条虚拟记录(ThemeGroup::list)。


五、后台 API(themes-group

路由:app/api/route/route.php → 组 themes-group

方法路径说明
GET/分组列表(含虚拟 default);参数 theme_id, obj_type, name
GET/:id分组详情 + items
POST/创建分组并复制积木
PUT/:id更新名称 + 重绑 obj
DELETE/:id删组、items、该组 blocks、清缓存
GET/findobj_id + obj_type 查组(商品详情跳装修)
GET/searchroute + route_handle 查组(装修内页跳转)
GET/:id/item组内第一个对象(预览用)
GET/:id/:objType/item默认组 fallback 第一个对象
GET/:id/items组内对象分页
GET/:id/product-collection商品组下商品所属专辑(下拉)

5.1 创建分组 POST themes-group/

Body(ThemeGroupCreateRequest):

字段说明
theme_id主题 ID
name组名
obj_typePRODUCT
obj_ids逗号分隔对象 ID,可选
copy_group_id从哪复制积木(见下)

复制积木逻辑ThemeRouteGroupService::add):

copy_group_id行为
未传 / 空字符串复制默认组下该 route 的 require 块(如 product_detail)到新组
0复制默认组该 route 全部 blocks
> 0复制指定组已发布 blocks(copyThemeBlock

5.2 装修编辑器拉数据

GET themes/{id}/data?route=product/detail&route_handle=xxx&group_id=

group_id 参数行为
不传按 obj 自动匹配 group
0强制默认组(themeDataByHandler
> 0强制指定组(themeDataByGroup

编辑器固定 preview=1

新增积木:POST themes/{id}/sections,body 含 group_id(非支持 route 时强制为 0)。


六、前台匹配流程

6.1 入口:ThemesService::themeData()

route 不在 supportGroupRouteList
  → themeDataByHandler(group_id=0 的 blocks)

group_id === 0(显式默认组)
  → themeDataByHandler

group_id 为空:
  obj_id ← themeGroupExtData / Context / getObjIdByHandler
  group_id ← ThemeRouteGroupCacheService::getGroupIdByObjIdAndType()

group_id 仍空 → themeDataByHandler

group_id > 0 → themeDataByGroup → getThemeGroupSectionsData

6.2 obj_id 从哪来

来源场景
ThemeRouteGroupHandlerService::setCurrentRequestObjId($id)详情 Controller 在 fetch 前写入 Context(Product、Collection 等)
TagGetBlocksobj_id 参数Liquid 显式传参
getObjIdByHandler(objType, routeHandle)由 URL handle 反查 ID;商品强制 preview=1 忽略上下架

getObjIdByHandler 失败时回退 getObjIdByRawHandler(保留大小写再查一次)。

6.3 obj_id → group_id

ThemeRouteGroupItemModel::getGroupByObjIdAndType(store, theme, obj_id, obj_type)
  → 有记录:返回对应 o_theme_route_group
  → 无记录:generateDefaultGroup(),group_id = 0

Redis 缓存:CacheKeyHelper::themeGroupIdByObjIdAndType($themeId, $objId, $objType),TTL 1 天。
组或 item 变更时 ThemeRouteGroupCacheService::cleanCacheBy* 删除。

6.4 读取哪批 blocks

路径查询
默认组ThemeBlockService::list()group_id=0 + route
自定义组ThemeBlockService::listByGroupId() / previewListByGroupId() → 仅 group_id

之后均走 sectionsFormat() 补 header/footer/require 等固定块。

组 blocks Redis 缓存 field:{route}:routeGroup:{groupId}(默认主题、非 preview)。

6.5 Liquid:TagGetBlocks

{% get_blocks route={routes.current_route} route_handle={routes.current_route_handle} obj_id=xxx group_id=xxx limit=80 %}
  • 不传 obj_id 时用 getCurrentRequestObjId()
  • 装修器左侧预览商品区:需传 group_id / obj_id,避免默认组与绑定组 blocks 混用(见 TagGetBlocks 注释)

七、后台 UI 对应(推断)

后台区域数据 / API
商品详情页「组装修」入口GET themes-group/find?obj_id=&obj_type=PRODUCT
装修器内切换分组group_id + GET themes/{id}/data
分组管理列表GET themes-group/?obj_type=PRODUCT(首条 default)
创建分组弹窗POST themes-group/ + 选 obj + copy_group_id
组内对象列表GET themes-group/{id}/items
左侧预览对象GET themes-group/{id}/itemgetDefaultGroupFirstItem

八、与默认组 / 单页 handle 装修的关系

  • 默认组group_id=0):所有未绑定对象的详情页共用;route_handle 可为空或具体 handle(专辑/博客等有 per-handle 装修逻辑)。
  • 组装修:同一 route 下按 obj 切换整组 blocks;组内 blocks 的 route_handle 通常为空(博客/新闻详情在 sectionsFormat 里会强制 route_handle='' 补 fixed 块)。

两者可并存:未绑定的商品走默认组;绑定的走自定义组。


九、主题复制

ThemesService::copy()ThemeRouteGroupHandlerService::copy()

  1. 查新主题下 group_id > 0 的 blocks
  2. 逐组 ThemeRouteGroupService::copy() 重建 group + items
  3. ThemeBlockModel::changeGroupId() 回写 block 的 group_id

十、扩展新 obj_type 检查清单

  1. ThemeRouteGroupModel::getAllObjTypeList() 增加常量
  2. ThemeRouteGroupHandlerService::objTypeToRoute()
  3. getObjIdByHandler() / getObjIdByRawHandler()
  4. defaultGroupFirstItem() 默认预览对象
  5. 详情 Controller 调用 setCurrentRequestObjId()
  6. settings_schema.pages_setting 增加 route(若新详情页)
  7. getThemeConfigneedPagePreviewUrl 的 switch 补预览 URL(可选)

十一、关键代码索引

组件路径
匹配编排common/services/ThemesService.phpthemeData, themeDataByGroup, themeDataByHandler
分组 CRUDcommon/services/ThemeRouteGroupService.php
obj ↔ groupcommon/services/ThemeRouteGroupHandlerService.php
缓存common/services/ThemeRouteGroupCacheService.php
前台 tagextend/liquidExtend/tags/TagGetBlocks.php
APIapp/api/controller/ThemeGroup.php
写 obj_idapp/home/controller/Product.php 等 detail 方法

十二、排障

现象排查
绑定商品仍显示默认组装修o_theme_route_group_item 是否有行;Redis theme_group_id:* 是否 stale
装修器与线上一致但组不对编辑器是否传了 group_idTagGetBlocks 的 obj_id
新组无积木创建时 copy_group_id;默认组是否有已发布 blocks
组内改 block 不影响其他商品确认 o_theme_block.group_id 是否为该组 ID