Database 类型皮肤(file_system=database)处理流程
私有/二开皮肤:Liquid 与配置存
o_theme_asset+ OSS,而非public/theme/{name}/整目录。
与 file 类型对比见 § 二;安装→装修→前台全链路见 theme-decoration-and-liquid-rendering.md § 一。
一、定位与特征
| 维度 | file_system=file | file_system=database |
|---|---|---|
典型 type | public(共享皮) | private(店铺私有) |
典型 name | 市场皮肤名,如 brooklyn | 多为 dbtheme_{themeId} |
| 模板物理位置 | public/theme/{name}/ | o_theme_asset 表 + OSS |
| 安装后 init | install() 内立即 initThemeData | 等 asset 同步后 themeAssetSyncFinish |
| 前台 FileSystem | OemsaasLocal | OemsaasDatabase |
| 静态资源 URL | /theme/{dir}/assets/... | OSS content_oss_bucket(asset_url filter) |
适用场景:主题复制为 DB 版、ThemeTool 在线编辑/同步、店铺级二开皮肤。
二、核心表 o_theme_asset
Model:ThemeAssetModel → o_theme_asset
| 字段 | 说明 |
|---|---|
store_id / theme_id / theme_name | 租户、主题、冗余目录名 |
folder | 逻辑目录:/config、/layout、/templates、/sections、/snippets、/assets、/locales |
file_name | 文件名,如 theme.liquid、settings_schema.json |
content | 小文件 inline(liquid、json 等) |
content_oss_bucket | 大文件 OSS 路径(/assets 下 css/js/图片等) |
public_url | OSS 对外 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 路由组维护文件。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | themeasset/ | 分页列表 |
| GET | themeasset/detail | 单文件(含 content) |
| POST | themeasset/ | 新增文件 |
| PUT | themeasset/ | 更新;若改 /config/settings_data.json 且店铺尚无 params → 触发 initThemeDataBySettingData |
| POST/DELETE | themeasset/delete(s) | 删除;清 OSS + Redis |
| DELETE | themeasset/{themeId} | 按 theme_id 清空 |
| GET | themes/{id}/themeasset/files | 列文件元数据(不含 content) |
| GET | themes/{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 模式下:
getThemeConfigFromDatabase($themeId)— 读/config下 jsongetThemeSectionsConfigFromDatabase— 读/sections/*.liquid解析 schema- 写
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 名:
- 优先
o_theme_asset/sections/{name}.liquid的 content - 缺失则
@file_get_contents(public/theme/default/sections/{name}.liquid) - 正则提取
{% schema %}→ JSON
section schema 缓存(
themeConfigsRedis)当前在代码中被注释禁用,每次直读 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 / TagInclude 在 file_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_*.json | o_theme_asset /config | public/theme/default/config/ |
| section schema | o_theme_asset /sections | public/theme/default/sections/ |
| layout liquid | o_theme_asset /layout content | 磁盘 theme/{theme_dir}/layout |
| template/section/snippet | themeAssets → content | OemsaasDatabase → 磁盘 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.params | initThemeData 从 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_content | dump layout DB content |
show_theme_template | dump template 读 asset 过程 |
show_public_url | dump asset_url OSS 解析 |
show_db_theme_error | database FileSystem 异常 dd |
liquid_cache | 控制 Liquid AST 文件缓存 |
十二、与 file 皮肤流程差异(清单)
| 步骤 | file | database |
|---|---|---|
| 安装后立即有 blocks | ✅ | ❌ 需 assetsyncfinish |
| 编辑 liquid 文件 | ThemeTool 改磁盘或重新部署 | PUT themeasset 改表 |
| 复制主题 | copy() 扫磁盘入 DB | copyThemeContent 复制行+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 / 复制 / Redis | common/services/ThemeAssetService.php |
| DB 配置/section 读取 | common/services/ThemesService.php — getThemeConfigFromDatabase, getThemeSectionsConfigFromDatabase, themeAssetSyncFinish |
| 安装/复制 | ThemesService::install, copy |
| 前台预加载 | app/home/HomeBaseController.php — fetch() |
| Liquid DB 读文件 | extend/liquidExtend/fileSystem/OemsaasDatabase.php |
| Tag 选 FileSystem | TagTemplate.php, TagSection.php, TagInclude.php |
| 静态 URL | extend/liquidExtend/filters/OemsaasFilter.php |
| 语言包 | common/services/LangService.php |
| API | app/api/controller/Themes.php — themeasset、assetsyncfinish |
| 路由 | app/api/route/route.php — themeasset、assetsyncfinish |
| Redis Key | extend/helper/CacheKeyHelper.php — themeAssets, themeFiles, themeAssetsFileKey |
| 渲染缓存详解 | database-theme-render-cache.md — Key、Value、TTL、失效 |
十五、排障
| 现象 | 排查 |
|---|---|
| 安装 DB 皮肤后无装修 | 是否调 assetsyncfinish;o_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) |