Database 类型皮肤(file_system=database)处理流程

私有/二开皮肤:Liquid 与配置存 o_theme_asset + OSS,而非 public/theme/{name}/ 整目录。
file 类型对比见 § 二;安装→装修→前台全链路见 theme-decoration-and-liquid-rendering.md § 一


一、定位与特征

维度file_system=filefile_system=database
典型 typepublic(共享皮)private(店铺私有)
典型 name市场皮肤名,如 brooklyn多为 dbtheme_{themeId}
模板物理位置public/theme/{name}/o_theme_asset 表 + OSS
安装后 initinstall()立即 initThemeData等 asset 同步后 themeAssetSyncFinish
前台 FileSystemOemsaasLocalOemsaasDatabase
静态资源 URL/theme/{dir}/assets/...OSS content_oss_bucketasset_url filter)

适用场景:主题复制为 DB 版、ThemeTool 在线编辑/同步、店铺级二开皮肤。


二、核心表 o_theme_asset

Model:ThemeAssetModelo_theme_asset

字段说明
store_id / theme_id / theme_name租户、主题、冗余目录名
folder逻辑目录:/config/layout/templates/sections/snippets/assets/locales
file_name文件名,如 theme.liquidsettings_schema.json
content小文件 inline(liquid、json 等)
content_oss_bucket大文件 OSS 路径/assets 下 css/js/图片等)
public_urlOSS 对外 URL
checksum内容或 OSS 路径 md5,用于 URL 版本号
content_type / size类型与大小

2.1 存储分流规则

写入时(ThemeAssetService::formatThemeAssetDataByFiles / API insert):

folder 以 /assets 开头
  → 文件上传 OSS:uploads/{storeId}/cart/themes/{themeId}{folder}/{fileName}
  → content 留空,只存 content_oss_bucket + public_url

其他 folder(config、layout、sections…)
  → content 字段存全文
  → content_oss_bucket 通常为空

复制 DB 主题时(formatThemeAssetDataByDatabase):OSS 对象 copy 到新 themeId 路径,并更新 checksum


三、生命周期总览

flowchart TB
    subgraph Create["创建皮肤行"]
        INS[POST themes/ install<br/>file_system=database]
        CP[POST themes/copy<br/>→ private + database]
        ROW[(o_store_theme)]
    end

    subgraph Sync["同步主题文件"]
        TT[ThemeTool / themeasset API]
        TA[(o_theme_asset)]
        OSS[OSS 静态资源]
    end

    subgraph Init["初始化装修数据"]
        FIN[POST themes/id/assetsyncfinish]
        INIT[initThemeData]
        ST[(o_store_theme.params)]
        TB[(o_theme_block)]
    end

    subgraph Runtime["前台渲染"]
        FETCH[HomeBaseController::fetch]
        ASSETS[Context.themeAssets Redis]
        LQ[OemsaasDatabase + OemsaasFilter]
    end

    INS --> ROW
    CP --> ROW
    TT --> TA
    TT --> OSS
    TA --> FIN
    FIN --> INIT
    INIT --> ST & TB
    ROW --> FETCH
    TA --> ASSETS --> LQ
    ST & TB --> LQ

四、阶段 A:安装 / 复制(仅有 o_store_theme 行)

4.1 安装 ThemesService::install(..., $fileSystem='database')

1. 插入 o_store_theme:
     type = private
     file_system = database
     default = 0
     name 可能后续改为 dbtheme_{id}
2. 不调用 initThemeData(与 file 安装不同)
3. 等待外部把文件写入 o_theme_asset

4.2 复制为 DB 版 ThemesService::copy()

1. 新 o_store_theme:file_system=database, type=private, name=dbtheme_{newId}
2. ThemeBlockService::copy — 复制 o_theme_block
3. ThemeRouteGroupHandlerService::copy — 复制组装修
4. ThemeAssetService::copyThemeContent — 核心:
     源 file_system=file → 扫磁盘 → 写 o_theme_asset + OSS
     源 file_system=database → 复制表行 + OSS copy
5. ThemeLanguagesService::copy

此时若源主题已有 params,blocks 已复制;新装且无 params 的皮肤仍依赖 § 五 初始化。


五、阶段 B:ThemeTool / themeasset API 同步文件

外部工具(ThemeTool)或后台通过 themeasset 路由组维护文件。

方法路径说明
GETthemeasset/分页列表
GETthemeasset/detail单文件(含 content)
POSTthemeasset/新增文件
PUTthemeasset/更新;若改 /config/settings_data.json 且店铺尚无 params → 触发 initThemeDataBySettingData
POST/DELETEthemeasset/delete(s)删除;清 OSS + Redis
DELETEthemeasset/{themeId}按 theme_id 清空
GETthemes/{id}/themeasset/files列文件元数据(不含 content)
GETthemes/{id}/themedown/{id}打包 zip(DB 从表+OSS 重建)

同步完成回调(关键):

POST themes/{id}/assetsyncfinish
  body: { type: 'init' | ... }

ThemesService::themeAssetSyncFinish($themeId, $type):
  若 type == 'init' 或 o_store_theme.params 为空
    → initThemeData(theme.name, 'database', themeId)

initThemeData 在 database 模式下:

  1. getThemeConfigFromDatabase($themeId) — 读 /config 下 json
  2. getThemeSectionsConfigFromDatabase — 读 /sections/*.liquid 解析 schema
  3. o_store_theme.params/preview_params + 批量 o_theme_block(逻辑同 file 皮肤)

详见 theme-decoration-and-liquid-rendering.md § 1.2


六、阶段 C:读配置与 schema(后台 + init)

6.1 getThemeConfigFromDatabase($themeId)

$configContent = ThemeAssetService::getContentsByFolder($themeId, '/config');
settings_data.json DB 有则用,否则 fallback public/theme/default/config/
settings_schema.json 同上,再 mergeDefaultSettingsSchema()

6.2 getThemeSectionsConfigFromDatabase($themeId, $sectionNames)

对每个 section 名:

  1. 优先 o_theme_asset /sections/{name}.liquidcontent
  2. 缺失则 @file_get_contents(public/theme/default/sections/{name}.liquid)
  3. 正则提取 {% schema %} → JSON

section schema 缓存(themeConfigs Redis)当前在代码中被注释禁用,每次直读 DB/磁盘。

6.3 getContentsByFolder 与 Redis

Key: CacheKeyHelper::themeFiles($themeId)  — Hash,field = folder
Miss → 查 o_theme_asset WHERE folder=? → 缓存 1 天
返回: [ file_name => content, ... ]

七、阶段 D:前台渲染

7.1 请求入口 HomeBaseController::fetch()

file_system=database 且非 checkout 强制 system 皮:

// 1. 预加载全量 asset 元数据到 Context(Redis 加速)
Context->themeAssets = ThemeAssetService::getThemeAssetsByDatabase(theme_id);
 
// 2. Layout
//    若 DB 有 /layout/{file}.liquid 的 content → parse($content)
//    否则 file_get_contents 磁盘 theme_dir/layout(若存在)

checkout 页强制 theme_dir=system,不走 DB 皮肤 layout 覆盖逻辑。

7.2 Redis:themeAssets 全量 Hash

Key:   {storeId}:themeassets:theme_id:{themeId}
Field: md5(themeId + '_' + '/sections/header.liquid')
Value: o_theme_asset 整行 JSON

TTL: 1 天
Miss: SELECT * FROM o_theme_asset WHERE theme_id=?

单文件 API 更新时:若 key 已存在则 hSet 单 field(updateAssetsCache);并 del themeFiles(themeId)

7.3 Liquid FileSystem:OemsaasDatabase

TagTemplate / TagSection / TagIncludefile_system=database 时使用:

readTemplateFile(templatePath):
  1. 拼路径:/{folder}/{name}.liquid  如 /templates/index.liquid
  2. getOneThemeAssetContent(themeAssets, path, themeId)
     → 只读 asset 行的 content 字段(inline 文本)
  3. 无 content → fullPath() 回退磁盘:
       public/theme/{theme_dir}/... 或 public/theme/default/...
  4. file_get_contents 磁盘文件

注意:Liquid 模板走 content/assets 下 OSS 文件不能通过 readTemplateFile 当 liquid 读,仅用于静态资源 URL。

7.4 静态资源 OemsaasFilter::asset_url

file_system=database:
  fileFullPath = '/assets/' + trim(file)
  themeAsset = getOneThemeAsset(themeAssets, path, themeId)
  URL = content_oss_bucket + '?v=' + checksum

无 OSS 记录时回退 file 皮肤 URL 规则。

7.5 语言包 LangService

database 皮肤额外 merge:

getContentsByFolder(theme_id, '/locales')
 locales/{lang}.json content 合并进 themeLang

八、Fallback 策略汇总

资源类型优先回退
settings_*.jsono_theme_asset /configpublic/theme/default/config/
section schemao_theme_asset /sectionspublic/theme/default/sections/
layout liquido_theme_asset /layout content磁盘 theme/{theme_dir}/layout
template/section/snippetthemeAssets → contentOemsaasDatabase → 磁盘 default/theme_dir
/assets 静态文件OSS content_oss_bucket磁盘 /theme/{theme_dir}/assets(filter 内)

设计意图:DB 皮肤可只存差异文件,缺省与 default 磁盘皮对齐。


九、与装修数据表的关系

数据database 皮肤存哪说明
主题文件(liquid/json)o_theme_asset不写入 block 表
全局装修o_store_theme.paramsinitThemeData 从 DB config 抽取
页面积木o_theme_block与 file 皮肤相同
组装修 / 视角同 file 皮肤与 asset 存储无关

发布主题publish)只同步 params / block 预览字段,回写 o_theme_asset(除非通过 themeasset API 单独改文件)。


十、删除与缓存失效

删除主题 ThemesService::delete()

DELETE o_store_theme
DELETE o_theme_block
ThemeAssetService::deletes({ theme_id })  → 删表行 + OSS 对象
DELETE o_theme_route_group*
redis del: themeData, themeSections, themeConfigs

单文件更新/删除:del themeFiles(themeId);若 themeAssets 存在则 hDel 或整 key 过期。

发布默认主题 / 删视角等也会 touch 相关 Redis key。


十一、调试参数

GET 参数作用
show_theme_contentdump layout DB content
show_theme_templatedump template 读 asset 过程
show_public_urldump asset_url OSS 解析
show_db_theme_errordatabase FileSystem 异常 dd
liquid_cache控制 Liquid AST 文件缓存

十二、与 file 皮肤流程差异(清单)

步骤filedatabase
安装后立即有 blocks❌ 需 assetsyncfinish
编辑 liquid 文件ThemeTool 改磁盘或重新部署PUT themeasset 改表
复制主题copy() 扫磁盘入 DBcopyThemeContent 复制行+OSS
前台读模板直接读 public/theme/Redis hash → content → 磁盘 fallback
打包下载扫目录 zip从 DB+OSS 重建 zip,checksum 缓存

十三、典型时序:从复制到访客可见

sequenceDiagram
    participant Admin as 商家后台
    participant API as app/api
    participant DB as MySQL
    participant OSS as OSS
    participant Home as app/home

    Admin->>API: POST themes/copy
    API->>DB: o_store_theme + o_theme_block + o_theme_asset
    API->>OSS: copy /assets 对象

    alt params 为空
        Admin->>API: POST themes/{id}/assetsyncfinish type=init
        API->>DB: initThemeData → params + blocks
    end

    Admin->>API: POST themes/{id}/publishments
    API->>DB: params ← preview_params

    Home->>DB: default 主题 o_store_theme
    Home->>DB: getThemeAssetsByDatabase → Redis
    Home->>Home: fetch → OemsaasDatabase 渲染
    Home->>OSS: asset_url 静态资源

十四、关键代码索引

职责路径
Asset CRUD / 复制 / Rediscommon/services/ThemeAssetService.php
DB 配置/section 读取common/services/ThemesService.phpgetThemeConfigFromDatabase, getThemeSectionsConfigFromDatabase, themeAssetSyncFinish
安装/复制ThemesService::install, copy
前台预加载app/home/HomeBaseController.phpfetch()
Liquid DB 读文件extend/liquidExtend/fileSystem/OemsaasDatabase.php
Tag 选 FileSystemTagTemplate.php, TagSection.php, TagInclude.php
静态 URLextend/liquidExtend/filters/OemsaasFilter.php
语言包common/services/LangService.php
APIapp/api/controller/Themes.php — themeasset、assetsyncfinish
路由app/api/route/route.phpthemeassetassetsyncfinish
Redis Keyextend/helper/CacheKeyHelper.phpthemeAssets, themeFiles, themeAssetsFileKey
渲染缓存详解database-theme-render-cache.md — Key、Value、TTL、失效

十五、排障

现象排查
安装 DB 皮肤后无装修是否调 assetsyncfinisho_store_theme.params 是否空
前台白屏/缺模板o_theme_asset 是否有 /layout/templates;Redis themeAssets 是否 stale
样式/JS 404/assets 行是否有 content_oss_bucket;OSS 路径是否含正确 themeId
改 liquid 不生效PUT themeasset 后是否更新 Redis;del themeFiles
section 新字段不显示DB section liquid 是否更新;fallback 是否仍读旧 default 磁盘
与 file 皮肤表现不一致对比 theme_dir(dbtheme_id 磁盘常无目录,全靠 DB)

上级文档:theme-decoration-and-liquid-rendering.md