Skip to content

数据模型

三类数据,别混

这是整套设计的地基

类别应该在哪谁维护
目录(catalog)app_infoapp_versionapp_categoryapp_version_menuapp_dependencyapp_plan平台级,全局一份平台方(上架端)
安装实例app_installapp_install_task每个部署各一份各部署自己
迁移记录app_schema_history每个部署各一份应用升级时自动写

关键前提:目录类表已经配置成"不按租户隔离" —— 它们列在 application.ymlsecurity.tenant.excludes 里:

yaml
  excludes:
    - sys_menu
    - sys_tenant
    - ...
    # 应用市场:应用目录属于平台级数据,不按租户隔离
    - app_info
    - app_version
    - app_category
    - app_version_menu
    - app_dependency
    - app_plan

tenant_idapp_install / app_install_task / app_schema_history 不在此列,由多租户插件自动隔离

由此得到两条推论:

  • 想把目录集中到一台服务器、其它部署远程读 → 可行,而且不需要传租户参数
  • 想集中安装实例 → 不要做,那是每个部署自己的运行状态。

一张表看懂边界

┌─────────────────── 平台级(全局一份) ───────────────────┐
│  app_info          应用目录                             │
│  app_version       版本与安装包(packageUrl / 校验和)    │
│  app_category      分类                                 │
│  app_version_menu  版本 → 菜单 的归属映射(权威记录)      │
│  app_dependency    应用依赖声明                          │
│  app_plan          套餐/定价(预留)                     │
└────────────────────────────────────────────────────────┘
┌─────────────────── 部署级(按租户隔离) ─────────────────┐
│  app_install       本部署装了哪些应用、状态、版本          │
│  app_install_task  安装/升级/卸载的任务与步骤日志          │
│  app_schema_history 迁移执行账本                         │
└────────────────────────────────────────────────────────┘

app_version_menu:菜单归属的权威记录

它存 (version_id, menu_id, menu_key) 三元组,是"这个菜单属于哪个应用"的唯一权威来源

为什么不直接在 sys_menu 上加一列 app_code

  • 加列要动框架的实体 / VO / Mapper XML / 菜单管理页面,代价大得多;
  • 而且会和 app_version_menu 形成两份真相

反查链路(菜单归属提供者):

app_install(本租户装了的应用) → app_info(编码 / 名称)
    → app_version(这些应用的版本) → app_version_menu(版本 → 菜单)

归属要连后代一起算

映射表里只有清单声明过的菜单,而按钮(menu_type='F')历史上来自平台初始化 SQL。 不收进来的话管理员能随手删掉应用按钮,恢复只能靠重装。

所以清单里必须写按钮type: "F" + 唯一的 menu_key)。

app_schema_history:迁移账本

字段说明
app_code应用编码
script脚本文件名
checksum脚本内容的 SHA-256(统一存 LF 归一化摘要
success是否成功
execution_ms执行耗时
tenant_id只记"谁最先应用" —— 不是去重判据

唯一键必须是 (app_code, script)

这是整个账本的关键约束,表结构与代码必须一致

曾经 DDL 写的是 (tenant_id, app_code, version)(少了 script、多了 version), 于是一个版本里只要有第二个迁移脚本,第二次记账就撞唯一键

Duplicate entry '000000-exam-1.0.1' for key 'app_schema_history.uk_schema_history'

→ 安装/升级直接失败。

它藏得久是因为所有应用的 migrations/ 一直只有一个脚本;而它不是升级专有的毛病 —— 全新部署安装一个多脚本应用同样会炸。

存量部署执行 script/sql/update/app_market_schema_history_fix.sql (幂等:先去重、再删旧键、再建新键)。回归测试会在 DDL 与代码不一致时直接失败。

卸载会删哪些表

依据是应用包自己,平台不维护"应用 → 表"的清单:

  1. 扫描 <pluginDir>/migrations/<appCode>/ 下的脚本;
  2. 解析出 CREATE TABLE 的表名(先剥注释,且只认 CREATE TABLE);
  3. 只用这个集合去 DROP。

这样"应用拥有哪些表"这件事只有一个来源 —— 应用自己声明的。

再加上平台表前缀保护:sys_ / app_ / gen_ / flow_ / act_ / qrtz_ / snailjob_ / undo_log 开头的一律跳过并告警。

关键状态字段

字段取值
app_infostatus0 草稿 / 1 已上架 / 2 已下架
app_infoinstall_modesystem 系统应用(平台自带,不可卸载、不进市场)/ optional 可选应用
app_infoaudienceplatform 平台管理员 / tenant B 端租户管理员 / member C 端用户(逗号分隔)
app_infohome_path入口路由,如 /gallery。系统应用必填;已安装应用留空则从菜单树推导
app_versionstatus0 待发布 / 1 已发布 / 2 已弃用
app_installstatusinstalling / installed / enabled / disabled / upgrading / failed / uninstalling / uninstalled

"能不能在市场里看到"的完整口径

三个条件同时满足:install_mode = 'optional' status = '1'(已上架) 命中当前身份的 audience

audience = 'platform' 的应用只有超管看得到(普通租户被 FIND_IN_SET('tenant', audience) 滤掉)—— 这是"普通用户不该看到系统管理入口"的实现方式:靠可见范围过滤,而不是靠隐藏

相关阅读

基于 MIT 协议开源 · 文档与官网由源码生成