Skip to content

v2.0.0(20260529) ​

IMPORTANT

v2.0.0 基于 v1.3.4-beta 之后的实际代码变化整理。本版本包含大量不兼容升级,涉及后端模块结构、数据库 ID、Liquibase、密码加密、前端依赖升级、前端 API 前缀和动态路由。存量系统升级前必须先完成备份和测试库演练。

1. 升级前检查 ​

1.1 必备备份 ​

升级前至少备份以下内容:

  • 生产数据库完整备份。
  • config/{profile} 下所有后端配置。
  • 上传资源目录或 OSS bucket 关键对象。
  • 前端 .env* 配置和 Nginx 配置。
  • 所有自定义后端业务代码、前端页面和生成器模板。
  • 当前运行版本 tag、commit、构建产物和回滚脚本。

1.2 推荐升级策略 ​

v2.0.0 不建议直接在生产库原地执行迁移。推荐流程:

  1. 使用 v2.0.0 代码创建全新测试库。
  2. 通过 Liquibase 初始化 v2.0.0 表结构和初始化数据。
  3. 将 v1.3.4-beta 生产库数据导入影子库。
  4. 编写或人工执行数据映射脚本,重点处理 ID、用户、角色、菜单、部门、字典、资源、生成器表。
  5. 在影子环境完成后端启动、前端登录、权限、菜单、字典、数据权限、上传、导入导出、生成器回归。
  6. 通过演练结果决定生产迁移窗口和回滚方案。
  7. 如需同步角色授权和数据权限范围,可在完成菜单、角色基础数据迁移后,使用角色管理中的 SQL 导出能力生成 sys_role_menu / sys_data_role_relation 辅助脚本。

2. 后端模块结构迁移 ​

v2.0.0 后端主结构调整为:

text
sz-boot-parent
├── sz-common        # 通用技术能力
├── sz-module        # 业务模块
└── sz-service       # 启动服务

关键变化:

v1.3.4-betav2.0.0说明
sz-service/sz-service-admin 承载大量业务实现sz-module/sz-module-admin 承载 admin 业务实现服务层职责收敛为启动装配
sz-common-generatorsz-module/sz-module-generator代码生成器迁移到业务模块层
MySQL 数据库能力集中在 sz-common-db-mysqlsz-common-db-core + sz-common-db-mysql / sz-common-db-postgresql公共流程与方言实现拆分
业务常量散落sz-module-common统一承载跨模块契约、配置 key、字典常量

自定义业务迁移建议:

  • 不要继续把新业务写入 sz-service-admin。
  • sz-module-admin 是官方核心模块,主要承载 RBAC、系统管理、认证授权、菜单、角色、字典等基础能力。客户自有业务不建议直接改入该模块,否则后续同步官方版本时更容易产生冲突。
  • 独立业务域建议新建 sz-module-*,再由 sz-service-admin 或自定义服务引入。这样官方核心、客户业务和派生产品组合入口可以保持清晰边界。
  • 跨模块共享 DTO、接口、字典常量、配置 key 放入 sz-module-common。

独立业务模块的完整接入流程见 独立业务模块接入指南。

3. Spring Boot 4 与依赖升级 ​

后端已升级到 Spring Boot 4.0.6,并同步调整依赖:

组件v2.0.0 当前值
Java21
Spring Boot4.0.6
Sa-Token1.45.0
Jackson3.1.3
MyBatis-Flex1.11.7
HikariCP7.0.2
Springdoc OpenAPI2.8.14

迁移注意:

  • 自定义 starter、拦截器、Jackson 序列化配置需要按 Spring Boot 4 / Jackson 3 检查。
  • 旧依赖若只支持 Spring Boot 3,需要升级或替换。
  • 后端编译建议先执行 mvn -pl sz-service/sz-service-admin -am compile -DskipTests。

4. Liquibase 与数据库迁移 ​

v2.0.0 使用模块级 Liquibase:

text
sz-service/sz-service-admin/src/main/resources/db/changelog/changelog-master.xml
sz-module/sz-module-admin/src/main/resources/db/changelog/module-admin-changelog.xml
sz-module/sz-module-audit/src/main/resources/db/changelog/module-audit-changelog.xml
sz-module/sz-module-generator/src/main/resources/db/changelog/module-generator-changelog.xml

目录按能力拆分:

  • framework:框架核心表和必要初始化数据。
  • audit:操作审计、性能日志、异常日志相关表和菜单权限。
  • generator:代码生成器表和初始化数据。
  • demo:演示数据,可按环境决定是否启用。

升级存量库时重点检查:

  • 旧表是否已存在,是否会与 v2.0.0 初始化 changeSet 冲突。
  • 主键和关联字段是否需要从自增/字符串迁移为 Long。
  • sys_menu、sys_role_menu、sys_dept、sys_user_role、sys_user_dept、sys_dict、sys_dict_type、sys_resource 是否有自定义数据。
  • 审计表 sys_operation_log、sys_operation_log_detail 是 v2.0.0 新增能力,存量库通常不需要迁移历史数据,但需要评估表空间、保留周期和清理策略。
  • 生成器相关表是否需要重建或导入。

WARNING

v2.0.0 的 changelog 更适合初始化新库。存量库建议先在影子库中验证“初始化新库 + 数据迁移”的方案,不建议未经演练直接在生产库执行。

4.1 数据库迁移脚本免责声明 ​

CAUTION

下方脚本是辅助脚本,用于帮助你在测试库中盘点差异、补齐 v2.0.0 必要基础结构或生成迁移映射。它们无法覆盖所有用户的二开字段、历史数据、索引命名、约束命名和业务规则。请务必先在备份库或影子库执行,确认结果后再决定是否用于生产。不要在未备份的生产库直接执行。

建议执行顺序:

  1. 先备份旧库。
  2. 在测试环境执行“结构盘点脚本”。
  3. 使用 v2.0.0 初始化一个新库。
  4. 对比旧库与新库结构。
  5. 按业务数据情况选择“保留旧 ID”或“重建雪花 ID 映射”。
  6. 执行字典来源补齐脚本。
  7. 检查字典静态缓存与动态字典按需加载是否符合预期,必要时清理 Redis 中的 sys_dict、sys_dict:static_loaded、sys_dict:static_types。
  8. 手工检查菜单、角色、部门、字典、用户、资源、生成器表。

4.2 备份命令 ​

MySQL:

bash
mysqldump -h 127.0.0.1 -P 3306 -u root -p \
  --single-transaction --routines --triggers --events \
  --databases sz_admin_preview \
  > sz_admin_preview_v134_backup.sql

PostgreSQL:

bash
pg_dump -h 127.0.0.1 -p 5432 -U postgres \
  --format=custom --blobs --verbose \
  --file=sz_admin_preview_v134_backup.dump \
  sz_admin_preview

4.3 结构盘点脚本 ​

MySQL 8:

sql
-- v2_precheck_mysql.sql
-- 用途:盘点 v1.3.4-beta 存量库中 v2.0.0 重点关注的表和字段。
-- 执行:mysql -h127.0.0.1 -uroot -p sz_admin_preview < v2_precheck_mysql.sql

SELECT DATABASE() AS current_database, VERSION() AS mysql_version;

SELECT table_name
FROM information_schema.tables
WHERE table_schema = DATABASE()
  AND table_name IN (
    'sys_user', 'sys_role', 'sys_menu', 'sys_role_menu',
    'sys_dept', 'sys_dept_closure', 'sys_user_role', 'sys_user_dept',
    'sys_dict', 'sys_dict_type', 'sys_dict_source',
    'sys_resource', 'generator_table', 'generator_table_column'
  )
ORDER BY table_name;

SELECT table_name, column_name, column_type, is_nullable, column_key
FROM information_schema.columns
WHERE table_schema = DATABASE()
  AND table_name IN ('sys_dict_type', 'sys_dict', 'sys_user', 'sys_menu', 'sys_role')
  AND column_name IN ('id', 'source_code', 'sys_dict_type_id', 'pwd', 'password', 'component', 'parent_id')
ORDER BY table_name, ordinal_position;

SELECT 'sys_dict_type without source_code' AS item, COUNT(*) AS count_value
FROM sys_dict_type
WHERE NOT EXISTS (
  SELECT 1
  FROM information_schema.columns
  WHERE table_schema = DATABASE()
    AND table_name = 'sys_dict_type'
    AND column_name = 'source_code'
);

PostgreSQL:

sql
-- v2_precheck_postgresql.sql
-- 执行:psql -h 127.0.0.1 -U postgres -d sz_admin_preview -f v2_precheck_postgresql.sql

SELECT current_database() AS current_database, version() AS postgresql_version;

SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
  AND table_name IN (
    'sys_user', 'sys_role', 'sys_menu', 'sys_role_menu',
    'sys_dept', 'sys_dept_closure', 'sys_user_role', 'sys_user_dept',
    'sys_dict', 'sys_dict_type', 'sys_dict_source',
    'sys_resource', 'generator_table', 'generator_table_column'
  )
ORDER BY table_name;

SELECT table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
  AND table_name IN ('sys_dict_type', 'sys_dict', 'sys_user', 'sys_menu', 'sys_role')
  AND column_name IN ('id', 'source_code', 'sys_dict_type_id', 'pwd', 'password', 'component', 'parent_id')
ORDER BY table_name, ordinal_position;

4.4 迁移映射表脚本 ​

如果你的旧库 ID 已经是 BIGINT 且没有冲突,可以优先保留旧 ID,降低关系表迁移成本。若必须重排 ID,请先建立映射表,再按映射更新关系表。

MySQL 8:

sql
-- v2_id_map_mysql.sql
-- 用途:为后续人工或脚本化迁移准备 ID 映射表。
-- new_id 默认先等于 old_id;如需重排雪花 ID,请先更新本表 new_id,再迁移关系表。

CREATE TABLE IF NOT EXISTS sz_migration_id_map (
  table_name VARCHAR(64) NOT NULL,
  old_id VARCHAR(64) NOT NULL,
  new_id BIGINT NOT NULL,
  remark VARCHAR(255) NOT NULL DEFAULT '',
  PRIMARY KEY (table_name, old_id)
);

INSERT IGNORE INTO sz_migration_id_map (table_name, old_id, new_id, remark)
SELECT 'sys_user', CAST(id AS CHAR), id, '用户'
FROM sys_user
WHERE id IS NOT NULL;

INSERT IGNORE INTO sz_migration_id_map (table_name, old_id, new_id, remark)
SELECT 'sys_role', CAST(id AS CHAR), id, '角色'
FROM sys_role
WHERE id IS NOT NULL;

INSERT IGNORE INTO sz_migration_id_map (table_name, old_id, new_id, remark)
SELECT 'sys_menu', CAST(id AS CHAR), id, '菜单'
FROM sys_menu
WHERE id IS NOT NULL;

INSERT IGNORE INTO sz_migration_id_map (table_name, old_id, new_id, remark)
SELECT 'sys_dept', CAST(id AS CHAR), id, '部门'
FROM sys_dept
WHERE id IS NOT NULL;

INSERT IGNORE INTO sz_migration_id_map (table_name, old_id, new_id, remark)
SELECT 'sys_dict_type', CAST(id AS CHAR), id, '字典类型'
FROM sys_dict_type
WHERE id IS NOT NULL;

PostgreSQL:

sql
-- v2_id_map_postgresql.sql

CREATE TABLE IF NOT EXISTS sz_migration_id_map (
  table_name VARCHAR(64) NOT NULL,
  old_id VARCHAR(64) NOT NULL,
  new_id BIGINT NOT NULL,
  remark VARCHAR(255) NOT NULL DEFAULT '',
  PRIMARY KEY (table_name, old_id)
);

INSERT INTO sz_migration_id_map (table_name, old_id, new_id, remark)
SELECT 'sys_user', id::text, id, '用户'
FROM sys_user
WHERE id IS NOT NULL
ON CONFLICT (table_name, old_id) DO NOTHING;

INSERT INTO sz_migration_id_map (table_name, old_id, new_id, remark)
SELECT 'sys_role', id::text, id, '角色'
FROM sys_role
WHERE id IS NOT NULL
ON CONFLICT (table_name, old_id) DO NOTHING;

INSERT INTO sz_migration_id_map (table_name, old_id, new_id, remark)
SELECT 'sys_menu', id::text, id, '菜单'
FROM sys_menu
WHERE id IS NOT NULL
ON CONFLICT (table_name, old_id) DO NOTHING;

INSERT INTO sz_migration_id_map (table_name, old_id, new_id, remark)
SELECT 'sys_dept', id::text, id, '部门'
FROM sys_dept
WHERE id IS NOT NULL
ON CONFLICT (table_name, old_id) DO NOTHING;

INSERT INTO sz_migration_id_map (table_name, old_id, new_id, remark)
SELECT 'sys_dict_type', id::text, id, '字典类型'
FROM sys_dict_type
WHERE id IS NOT NULL
ON CONFLICT (table_name, old_id) DO NOTHING;

5. 全局 ID 迁移 ​

v2.0.0 将大量 ID 统一为雪花 Long,由 sz-common-db-core 中的 SzIdGenerator 提供。

雪花 ID 的运行期参数位于各环境 config/{profile}/mybatis-flex.yml:

yaml
sz:
  id:
    worker-id: ${SZ_WORKER_ID:1}
    datacenter-id: ${SZ_DATACENTER_ID:1}

这两个配置会被 SzSnowflakeProperties 读取,并在 MybatisFlexConfiguration 中创建全局 Snowflake 与 MyBatis-Flex KeyGenerator。它们只影响升级后新写入数据的 ID 生成,不会自动改造旧库 ID。配置细节见 配置 - mybatis-flex.yml,实体主键写法见 规范 - 主键ID 与全局雪花ID策略。

CAUTION

生产或预览环境多节点部署时,必须为每个节点显式设置不同的 SZ_WORKER_ID / SZ_DATACENTER_ID 组合。升级演练时也建议按真实部署拓扑配置,避免上线后才暴露雪花 ID 冲突。

需要重点处理:

  • Java 实体、DTO、VO 中的 ID 类型。
  • 前端 API 类型中的 ID 类型。
  • 数据库主键、外键和关系表字段。
  • 菜单、角色、部门、用户、字典、资源、生成器等初始化数据。

如果旧系统中有字符串 ID 或自增 ID,建议建立 ID 映射表:

old_idnew_idtable_nameremark

迁移关系表时必须使用映射后的新 ID,避免角色菜单、用户角色、用户部门、部门闭包关系断裂。

6. 密码加密迁移 ​

v2.0.0 密码加密切换为 BcryptUtils,旧密码规则与新规则不兼容。

可选方案:

方案说明适用场景
全员重置密码升级后统一重置为临时密码,用户首次登录后修改内部系统、用户量可控
管理员按需重置保留账号,用户登录失败后由管理员重置用户量较少
临时兼容旧密码登录时先校验 BCrypt,失败后尝试旧算法,成功后重写为 BCrypt用户量较大,但需要额外开发

当前官方代码已切换为新规则,升级文档不假设存在旧密码兼容逻辑。

7. MySQL / PostgreSQL 切换 ​

v2.0.0 支持 MySQL 和 PostgreSQL,但二者不是运行时随意混用。

7.1 配置数据库类型 ​

application.yml:

yaml
DB_TYPE: mysql # 可选 mysql / postgresql
spring:
  config:
    import:
      - file:config/${spring.profiles.active}/${DB_TYPE}.yml

7.2 选择数据库模块 ​

sz-service/sz-service-admin/pom.xml 中只保留一个数据库实现:

xml
<!-- MySQL -->
<dependency>
    <groupId>com.sz</groupId>
    <artifactId>sz-common-db-mysql</artifactId>
    <version>${project.version}</version>
</dependency>

<!-- PostgreSQL:使用时启用,并注释 MySQL 依赖 -->
<!--
<dependency>
    <groupId>com.sz</groupId>
    <artifactId>sz-common-db-postgresql</artifactId>
    <version>${project.version}</version>
</dependency>
-->

WARNING

sz-common-db-mysql 和 sz-common-db-postgresql 不应同时启用,否则数据权限方言可能冲突。

8. 数据权限迁移与行为变化 ​

v2.0.0 的数据权限主流程已经合并到 系统角色授权 中。旧版若曾使用独立数据角色、数据权限管理菜单或自定义数据角色表,需要按新模型重新梳理。

核心变化:

旧口径v2.0.0 当前口径迁移建议
独立数据角色 / 数据权限角色系统角色在具体菜单上的数据范围配置将旧数据角色能力映射到 sys_role_menu.permission_type = scope
独立数据权限菜单菜单管理 use_data_scope 开关 + 角色管理授权弹窗需要数据权限的菜单设置 sys_menu.use_data_scope = 'T'
自定义部门/用户范围sys_data_role_relation 保存角色 + 菜单 + 用户/部门关系自定义范围迁移时必须同时写入 role_id、menu_id、relation_type_cd、relation_id
手工控制数据过滤Service 层使用 DataScopeSession 或 SimpleDataScopeHelper 开启上下文只对需要过滤的查询显式开启,避免误伤所有查询

WARNING

旧表中的 sys_data_role、sys_data_role_menu 如果仍存在,不应继续作为 v2.0.0 官网主流程描述。当前后端以 SysRoleMenuServiceImpl、sys_role_menu、sys_data_role_relation 为准。

8.1 数据权限结构检查脚本 ​

以下脚本用于在测试库中盘点数据权限相关字段,不会修改数据。

MySQL 8:

sql
SELECT table_name, column_name, column_type, is_nullable
FROM information_schema.columns
WHERE table_schema = DATABASE()
  AND (
    (table_name = 'sys_menu' AND column_name = 'use_data_scope')
    OR (table_name = 'sys_role_menu' AND column_name IN ('permission_type', 'data_scope_cd', 'menu_id', 'role_id'))
    OR (table_name = 'sys_data_role_relation' AND column_name IN ('role_id', 'menu_id', 'relation_type_cd', 'relation_id'))
  )
ORDER BY table_name, ordinal_position;

SELECT use_data_scope, COUNT(*) AS menu_count
FROM sys_menu
GROUP BY use_data_scope;

SELECT permission_type, data_scope_cd, COUNT(*) AS relation_count
FROM sys_role_menu
GROUP BY permission_type, data_scope_cd
ORDER BY permission_type, data_scope_cd;

PostgreSQL:

sql
SELECT table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
  AND (
    (table_name = 'sys_menu' AND column_name = 'use_data_scope')
    OR (table_name = 'sys_role_menu' AND column_name IN ('permission_type', 'data_scope_cd', 'menu_id', 'role_id'))
    OR (table_name = 'sys_data_role_relation' AND column_name IN ('role_id', 'menu_id', 'relation_type_cd', 'relation_id'))
  )
ORDER BY table_name, ordinal_position;

SELECT use_data_scope, COUNT(*) AS menu_count
FROM sys_menu
GROUP BY use_data_scope;

SELECT permission_type, data_scope_cd, COUNT(*) AS relation_count
FROM sys_role_menu
GROUP BY permission_type, data_scope_cd
ORDER BY permission_type, data_scope_cd;

8.2 迁移建议 ​

  1. 先确认哪些菜单确实需要数据权限,将这些菜单的 use_data_scope 设置为 T。
  2. 将旧数据角色对应的范围映射到系统角色。如果一个旧数据角色被多个用户共用,建议先确认它应合并到哪个 sys_role。
  3. 对非自定义范围,在 sys_role_menu 中写入一条 permission_type = 'scope'、data_scope_cd = 1006001~1006004 的记录。
  4. 对自定义范围,在 sys_role_menu 中写入 data_scope_cd = 1006005,并在 sys_data_role_relation 中写入部门/用户明细。
  5. 确认对应 Controller 方法存在 @SaCheckPermission,且权限标识能映射到开启数据权限的菜单。
  6. 确认 Service 查询中使用 DataScopeSession(YourEntity.class) 或 SimpleDataScopeHelper.start(YourEntity.class)。
  7. 迁移后清理 Redis 中旧的登录 Session,让用户重新登录后重新计算 LoginUser.dataScope。

TIP

代码生成器的“自动创建数据权限”默认会为生成菜单设置 use_data_scope,并在生成 Service 模板中写入 DataScopeSession。对手写模块,也应按同样规则补齐菜单开关、角色范围和 Service 查询上下文。

8.3 角色-权限 SQL 迁移脚本便捷导出 ​

v2.0.0 在角色管理中新增角色-权限脚本导出能力,对应后端接口 POST /sys-role/menu/script/export,权限点为 sys.role.sql_btn。该能力用于把某个角色当前已配置的功能权限、菜单级数据权限范围和自定义范围明细导出为可预览、可复制的 SQL / Liquibase XML。

导出脚本覆盖的核心表:

  • sys_role_menu:角色与菜单、按钮权限、菜单级数据权限范围的关系,包含 role_id、permission_type、menu_id、data_scope_cd。
  • sys_data_role_relation:当数据范围为“自定义”时,记录角色在指定菜单下关联的部门或用户明细,包含 role_id、menu_id、relation_type_cd、relation_id。

推荐使用方式:

  1. 先完成目标环境的 sys_role、sys_menu、sys_dept、sys_user 等基础数据迁移或初始化。
  2. 确认目标环境与导出环境的角色 ID、菜单 ID、部门 ID、用户 ID 是否一致;如果不一致,先使用迁移映射表或人工映射方案修正脚本中的 ID。
  3. 在源环境的角色管理中选择目标角色,导出角色-权限脚本,并按目标数据库选择 SQL 方言。
  4. 检查脚本中的 sys_role_menu 和 sys_data_role_relation 是否符合预期,尤其是 permission_type = 'scope' 与自定义范围数据。
  5. 先在影子库或测试库回放脚本,确认菜单显示、按钮权限、数据权限查询都正确后,再决定是否用于生产迁移。

CAUTION

角色-权限导出脚本不是全量 RBAC 迁移工具。它不会创建角色、菜单、部门或用户,只负责辅助迁移角色与权限、数据范围之间的关系。若目标库 ID 与源库 ID 不一致,必须先完成 ID 映射,否则可能把权限授给错误的角色、菜单或部门。生产环境执行前仍需备份并在影子库验证。

回放后可使用下面的检查 SQL 做基础核对:

sql
-- v2_role_permission_after_import_check.sql
-- 用途:核对角色权限脚本回放后,sys_role_menu 与 sys_data_role_relation 的基础分布。

SELECT role_id, permission_type, data_scope_cd, COUNT(*) AS row_count
FROM sys_role_menu
GROUP BY role_id, permission_type, data_scope_cd
ORDER BY role_id, permission_type, data_scope_cd;

SELECT
  srm.role_id,
  srm.menu_id,
  srm.data_scope_cd,
  COUNT(sdr.id) AS custom_relation_count
FROM sys_role_menu srm
LEFT JOIN sys_data_role_relation sdr
  ON sdr.role_id = srm.role_id
 AND sdr.menu_id = srm.menu_id
WHERE srm.permission_type = 'scope'
GROUP BY srm.role_id, srm.menu_id, srm.data_scope_cd
ORDER BY srm.role_id, srm.menu_id;

9. 字典来源(sys_dict_source)迁移与新功能 ​

v2.0.0 新增 sys_dict_source 字典来源管理,用于给字典类型划分来源和 ID 区间。它解决的是“框架内置字典”和“业务自定义字典”混在一起后难以治理的问题。

默认来源:

source_codesource_nameID 区间说明
framework框架内置1000-1999官方保留字典来源
custom业务自定义2000-2999默认业务字典来源

相关变化:

  • 新增表 sys_dict_source。
  • sys_dict_type 新增 source_code 字段。
  • 官方内置字典类型默认归属 framework。
  • 业务新增字典建议归属 custom 或自行扩展新的来源区间。
  • 字典类型 ID 建议继续采用“4 位类型 ID + 3 位字典项序号”的可读规则,例如类型 2001 下的字典项可使用 2001001。

TIP

截图占位:后续可补充“字典来源列表页”“新增/编辑字典来源弹窗”“字典类型关联来源字段”的截图。

9.1 MySQL 补齐脚本 ​

sql
-- v2_dict_source_mysql.sql
-- 用途:在存量库中补齐 sys_dict_source 表和 sys_dict_type.source_code 字段。
-- 执行前必须备份数据库,并确认 sys_dict_type 不存在自定义同名字段。

START TRANSACTION;

CREATE TABLE IF NOT EXISTS sys_dict_source (
  id BIGINT NOT NULL COMMENT '主键ID',
  source_code VARCHAR(64) NOT NULL COMMENT '来源编码',
  source_name VARCHAR(64) NOT NULL COMMENT '来源名称',
  start_id BIGINT NOT NULL COMMENT '起始ID',
  end_id BIGINT NOT NULL COMMENT '结束ID',
  status VARCHAR(1) NOT NULL DEFAULT 'T' COMMENT '状态',
  remark VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注',
  create_time DATETIME NULL COMMENT '创建时间',
  update_time DATETIME NULL COMMENT '更新时间',
  create_id BIGINT NULL COMMENT '创建人ID',
  update_id BIGINT NULL COMMENT '更新人ID',
  PRIMARY KEY (id),
  UNIQUE KEY uk_sys_dict_source_code (source_code)
) COMMENT='字典来源表';

INSERT INTO sys_dict_source
  (id, source_code, source_name, start_id, end_id, status, remark, create_time, update_time, create_id, update_id)
VALUES
  (1, 'framework', '框架内置', 1000, 1999, 'T', '框架保留来源,对应 1000-1999 区间', NOW(), NOW(), 1, 1),
  (2, 'custom', '业务自定义', 2000, 2999, 'T', '业务默认来源,对应 2000-2999 区间', NOW(), NOW(), 1, 1)
ON DUPLICATE KEY UPDATE
  source_name = VALUES(source_name),
  start_id = VALUES(start_id),
  end_id = VALUES(end_id),
  status = VALUES(status),
  remark = VALUES(remark),
  update_time = NOW();

DELIMITER //
CREATE PROCEDURE sz_add_dict_type_source_code()
BEGIN
  IF NOT EXISTS (
    SELECT 1
    FROM information_schema.columns
    WHERE table_schema = DATABASE()
      AND table_name = 'sys_dict_type'
      AND column_name = 'source_code'
  ) THEN
    ALTER TABLE sys_dict_type
      ADD COLUMN source_code VARCHAR(64) NOT NULL DEFAULT '' COMMENT '所属来源';
  END IF;
END//
DELIMITER ;

CALL sz_add_dict_type_source_code();
DROP PROCEDURE sz_add_dict_type_source_code;

UPDATE sys_dict_type
SET source_code = CASE
  WHEN id BETWEEN 1000 AND 1999 THEN 'framework'
  WHEN id BETWEEN 2000 AND 2999 THEN 'custom'
  ELSE 'custom'
END
WHERE source_code IS NULL OR source_code = '';

COMMIT;

9.2 PostgreSQL 补齐脚本 ​

sql
-- v2_dict_source_postgresql.sql

BEGIN;

CREATE TABLE IF NOT EXISTS sys_dict_source (
  id BIGINT PRIMARY KEY,
  source_code VARCHAR(64) NOT NULL,
  source_name VARCHAR(64) NOT NULL,
  start_id BIGINT NOT NULL,
  end_id BIGINT NOT NULL,
  status VARCHAR(1) NOT NULL DEFAULT 'T',
  remark VARCHAR(255) NOT NULL DEFAULT '',
  create_time TIMESTAMP,
  update_time TIMESTAMP,
  create_id BIGINT,
  update_id BIGINT
);

CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_dict_source_code
  ON sys_dict_source (source_code);

INSERT INTO sys_dict_source
  (id, source_code, source_name, start_id, end_id, status, remark, create_time, update_time, create_id, update_id)
VALUES
  (1, 'framework', '框架内置', 1000, 1999, 'T', '框架保留来源,对应 1000-1999 区间', NOW(), NOW(), 1, 1),
  (2, 'custom', '业务自定义', 2000, 2999, 'T', '业务默认来源,对应 2000-2999 区间', NOW(), NOW(), 1, 1)
ON CONFLICT (source_code) DO UPDATE SET
  source_name = EXCLUDED.source_name,
  start_id = EXCLUDED.start_id,
  end_id = EXCLUDED.end_id,
  status = EXCLUDED.status,
  remark = EXCLUDED.remark,
  update_time = NOW();

ALTER TABLE sys_dict_type
  ADD COLUMN IF NOT EXISTS source_code VARCHAR(64) NOT NULL DEFAULT '';

UPDATE sys_dict_type
SET source_code = CASE
  WHEN id BETWEEN 1000 AND 1999 THEN 'framework'
  WHEN id BETWEEN 2000 AND 2999 THEN 'custom'
  ELSE 'custom'
END
WHERE source_code IS NULL OR source_code = '';

COMMIT;

9.3 字典迁移建议 ​

  • 如果你的项目没有自定义字典,执行 v2.0.0 初始化数据即可。
  • 如果已有自定义字典类型,建议将 id >= 2000 的业务字典归属 custom。
  • 如果业务字典已经占用了 1000-1999 区间,需要先盘点冲突,再决定是否重排 ID。
  • 如果多个业务模块各自维护字典,建议新增来源,例如 sso、shop、crm,并规划不同 ID 区间。

9.4 字典加载与缓存行为变化 ​

v2.0.0 对字典加载链路做了进一步优化。当前推荐理解为:

能力当前接口 / 实现说明
查询全部字典GET /sys-dict/dict返回静态字典 + 动态字典,适合管理端全量需要的场景
查询静态字典GET /sys-dict/static只返回 sys_dict_type / sys_dict 维护的静态字典,前端路由初始化时用于预热
批量按类型查询GET /sys-dict/code?typeCode=account_status&typeCode=dynamic_user_options按需查询一个或多个 typeCode,动态字典也走该入口
后端加载器DictLoaderFactory动态字典按完整 dynamic_* typeCode 精确命中,静态字典交给默认 DefaultStaticDictLoader
Redis 标记sys_dict、sys_dict:static_loaded、sys_dict:static_types静态字典全量预热后记录已加载状态和静态类型列表

前端 optionsStore 也同步调整:

  • 路由初始化时优先调用 getStaticDict(),预热静态字典。
  • 页面调用 useDictOptions(typeCode) 时,如果该类型未加载或已过期,会通过 getDictByCode 按需补拉。
  • Store 内部维护 loadedTypes、expiredTypes、loadingTypes,避免同一个字典类型重复并发请求。
  • 收到 WebSocket SYNC_DICT 后,会标记已加载字典过期,再重新预热静态字典。

迁移注意事项:

  • 旧前端如果仍只依赖 GET /sys-dict/dict 全量返回,短期仍可使用;新页面建议改为 useDict / useDictOptions,让静态字典预热与动态字典按需加载协同工作。
  • 自定义动态字典必须实现 DynamicDictLoader#getTypeCode() 与 getTypeName(),最终完整 typeCode 会自动加上 dynamic_ 前缀;不同动态加载器的完整 typeCode 不能重复。
  • 如果升级后发现字典下拉仍是旧数据,先清理 Redis 中的 sys_dict、sys_dict:static_loaded、sys_dict:static_types,再重新登录或等待 WebSocket 同步。

10. API 前缀与前端调用迁移 ​

后端新增 API 前缀配置:

yaml
sz:
  api-prefix:
    modules:
      admin:
        enabled: true
        prefix: /admin
      audit:
        enabled: true
        prefix: /audit
      generator:
        enabled: true
        prefix: /generator

前端新增独立 HTTP 实例:

ts
import { adminHttp } from '@/api/client';
import { auditHttp } from '@/api/client';
import { generatorHttp } from '@/api/client';

旧写法:

ts
import http from '@/api';
import { ADMIN_MODULE } from '@/api/helper/prefix';

export const getUserPage = params => http.get(ADMIN_MODULE + '/sys-user/page', params);

新写法:

ts
import { adminHttp } from '@/api/client';

export const getUserPage = params => adminHttp.get('/sys-user/page', params);

生成器接口使用:

ts
import { generatorHttp } from '@/api/client';

export const getGeneratorPage = params => generatorHttp.get('/generator-table/page', params);

审计接口使用:

ts
import { auditHttp } from '@/api/client';

export const getOperationLogPage = params => auditHttp.get('/sys-operation-log', params);

后端模块前缀由各模块通过 ApiPrefixRegister 声明默认值,配置文件中的 sz.api-prefix.modules.<module>.prefix 用于按环境覆盖。新增自定义业务模块时,应同步补充后端前缀、前端 HTTP client、Vite proxy 和 Nginx location。

11. 前端环境变量迁移 ​

旧版常见配置:

properties
VITE_API_URL=http://127.0.0.1:9991/api

v2.0.0 改为:

properties
VITE_ADMIN_API_BASE=/api/admin
VITE_AUDIT_API_BASE=/api/audit
VITE_GENERATOR_API_BASE=/api/generator
VITE_API_PROXY_TARGET=http://127.0.0.1:9991
VITE_SOCKET_URL=ws://127.0.0.1:9993/socket
VITE_APP_CLIENT_ID=195da9fcce574852b850068771cde034
VITE_ADMIN_BYPASS_PERMISSION=true

生产环境需要同步 Nginx:

nginx
location /api/admin/ {
    proxy_pass http://sz-service-admin:9991/api/admin/;
}

location /api/audit/ {
    proxy_pass http://sz-service-admin:9991/api/audit/;
}

location /api/generator/ {
    proxy_pass http://sz-service-admin:9991/api/generator/;
}

11.1 前端依赖与构建环境升级 ​

本次前端待提交代码包含一次集中依赖升级,升级后请先确认本机、CI 和部署环境的 Node / pnpm 版本,再安装依赖。

项目旧版本/旧口径新版本/新口径迁移注意
Node.jsNode 20 泛版本或 Node 18 类型配置>=20.19.0,CI 示例使用 20.19.5package.json 已声明 engines.node,低版本 Node 可能无法运行 Vite 7。
pnpm未锁定或 9.x 口径pnpm@10.17.1package.json 已声明 packageManager,建议使用 Corepack 或全局安装指定版本。
Vite6.4.27.3.3本地开发、CI、Docker 构建需要同步 Node 版本;Vite proxy 仍按 API base 动态生成。
@vitejs/plugin-vue6.0.66.0.7与 Vite 7 配套。
vite-plugin-vue-devtools7.7.98.1.2仅影响开发调试能力。
Vue^3.5.33^3.5.35保持 Vue 3.5 主线。
Element Plus^2.13.7^2.14.0升级后重点回归表单、表格、弹窗和上传组件。
Pinia^2.3.1^3.0.4Store 写法保持 Composition API 风格。
pinia-plugin-persistedstate^3.2.3^4.7.1持久化配置字段由 paths 调整为 pick,二开 store 若使用旧字段需要同步修改。
Vue Router^4.6.4^5.0.7升级后重点回归动态路由、标签页、KeepAlive 和登录回跳。
Axios1.15.21.16.1继续通过统一 HTTP 实例处理业务码、401 和 blob 错误。
@vueuse/core^10.11.1^14.3.0使用组合式工具的自定义页面需做类型检查。
Sass~1.87.0~1.100.0当前 Vite 配置使用 api: 'modern-compiler'。

推荐本地处理步骤:

bash
corepack enable
corepack prepare pnpm@10.17.1 --activate
pnpm install --frozen-lockfile
pnpm type-check
pnpm build

如果你的二开项目维护了自定义 Pinia store,请重点搜索 paths:、PersistedStateOptions 等旧写法,并改为当前 pinia-plugin-persistedstate v4 的 pick / PersistenceOptions 口径。

12. 审计日志模块迁移与启用 ​

v2.0.0 新增独立审计模块,用于沉淀操作审计、性能日志和异常日志。全新安装时,Liquibase 会创建 sys_operation_log、sys_operation_log_detail,并初始化“操作审计”菜单和按钮权限;存量升级时,一般不迁移历史操作日志,而是从启用 v2.0.0 后开始记录新数据。

启用步骤:

  1. 确认 sz-service-admin 已引入 sz-module-audit。
  2. 确认 application.yml 导入 config/${spring.profiles.active}/audit-log.yml。
  3. 在环境配置中保留或调整 sz.api-prefix.modules.audit.prefix=/audit。
  4. 前端配置 VITE_AUDIT_API_BASE=/api/audit,并同步 Vite proxy / Nginx / 网关。
  5. 为角色分配 sys.operation.log.query_table 和 sys.operation.log.detail 权限。
  6. 进入“操作审计”页面,分别验证审计日志、性能日志、异常日志和详情抽屉。

生产注意:

  • record-response-body 默认关闭,排障时再临时开启。
  • sql-mode=all 不建议长期用于生产,可优先使用 off 或临时 slow。
  • 审计日志会持续增长,上线前应规划保留周期、归档或清理任务。
  • 请求参数、响应体和异常堆栈可能包含敏感信息,二开接口仍应避免把密码、密钥、令牌等字段写入审计明细。

完整使用说明见 审计日志。

13. 前端模块化与路由迁移 ​

v2.0.0 新增 src/core 和 src/editions:

text
src/core/       # 登录适配器、模块注册、菜单组件解析
src/editions/   # 产品组合入口
src/modules/    # 可组合业务模块

动态路由解析顺序:

  1. 已注册模块的 components 映射。
  2. src/modules/<domain>/views/<rest>.vue。
  3. 旧目录 src/views/<component>.vue。

迁移建议:

  • 普通旧页面可以暂时保留在 src/views。
  • 新增较大业务模块建议放入 src/modules/<domain>。
  • 派生项目需要在 edition 中注册业务模块。
  • 后端菜单 component 字段必须与前端可解析路径一致。

14. WebSocket 迁移 ​

v2.0.0 前后端 WebSocket 均有重构:

  • 前端增加心跳和鉴权关闭码处理。
  • 后端使用 Sec-WebSocket-Protocol 传递 token。
  • 鉴权失效关闭码为 4401。
  • 心跳消息使用 PING / PONG。
  • 前端将 axios 业务码 C105、blob 下载错误中的登录失效、HTTP 401 和 WebSocket 4401 收敛到 src/core/authSession.ts,避免多处重复清理登录态。

升级后需要验证:

  • 未配置 VITE_SOCKET_URL 时前端不应建立连接。
  • token 失效时前端能停止重连、清理登录态并跳转登录页。
  • 登录失效跳转是否带上 back 参数,重新登录后是否能回到原目标页面。
  • 字典、权限、配置同步消息能被正确处理。

15. 代码生成器迁移 ​

后端配置示例:

yaml
sz:
  generator:
    path:
      web:
      api:
    module-name: sz-module
    service-name: sz-module-admin
    global:
      author: sz-admin
      packages: com.sz.admin

变化点:

  • 后端模块从 sz-common-generator 迁移为 sz-module-generator。
  • 生成目标模块默认从 sz-service 调整为 sz-module。
  • 生成服务名默认从 sz-service-admin 调整为 sz-module-admin。
  • 前端生成器页面位于 src/modules/toolbox。
  • 支持 MySQL / PostgreSQL 元数据读取和 Liquibase 初始化脚本生成。

16. 新功能与行为变化速览 ​

本节帮助你快速理解 v2.0.0 不只是“改目录”,还新增或改变了哪些面向使用者的能力。

类型变化迁移/使用建议
新功能字典来源管理 sys_dict_source先补齐来源表和 sys_dict_type.source_code,再规划业务字典区间
新功能审计日志启用 sz-module-audit、audit-log.yml 和 VITE_AUDIT_API_BASE,上线前规划日志保留周期
新功能脚本预览与导出用于菜单、字典、角色-权限等初始化或迁移脚本确认;角色-权限导出重点核对 sys_role_menu 与 sys_data_role_relation
新功能PostgreSQL 支持通过 DB_TYPE + 数据库模块二选一启用
新功能前端 edition / module 注册派生项目通过 edition 注册登录适配器和业务模块
改变前端构建链升级到 Vite 7 / Node 20.19+ / pnpm 10.17.1先升级本机和 CI 环境,再执行 pnpm install --frozen-lockfile、pnpm type-check、pnpm build
改变API 前缀拆分为 admin/audit/generator前端接口迁移为 adminHttp / auditHttp / generatorHttp
改变字典加载拆分为静态预热 + 按类型按需加载自定义动态字典需保证完整 dynamic_* typeCode 唯一;升级后可清理字典 Redis 缓存验证
改变数据权限合并到系统角色授权不再按独立数据角色作为官网主流程,重点迁移 sys_role_menu 与 sys_data_role_relation
改变WebSocket 心跳、关闭码与会话过期统一处理重点验证 axios C105 / HTTP 401 / WebSocket 4401 都能清理登录态并跳转登录
改变全局错误码前缀通用错误为 C,Admin 为 A,Resource 为 R
兼容旧 src/views 页面仍可兜底解析新业务建议迁移到 src/modules/<domain>
不兼容密码加密改为 BCrypt存量用户需重置或开发兼容登录

TIP

截图占位:后续可补充“脚本预览弹窗”“字典来源管理”“审计日志三页签”“前端模块路由未命中 warn”“WebSocket 鉴权失效重登”的截图。

17. 验证清单 ​

后端:

bash
mvn -pl sz-service/sz-service-admin -am compile -DskipTests

前端:

bash
pnpm install --frozen-lockfile
pnpm type-check
pnpm build

重点业务回归:

  • 登录、退出、token 失效。
  • 菜单加载、动态路由、按钮权限。
  • 用户、角色、部门、菜单、字典、参数管理。
  • 字典静态预热、指定类型按需加载、WebSocket 字典同步与 Redis 字典缓存清理。
  • 审计日志、性能日志、异常日志列表与详情;确认 traceId、慢操作阈值、异常堆栈和权限按钮可用。
  • 数据权限范围查询。
  • 角色-权限脚本导出、SQL 方言切换、目标库回放和权限重新登录生效。
  • 文件上传、资源访问、导入导出。
  • WebSocket 鉴权、心跳、字典/权限/配置同步、会话过期统一跳转和 back 参数回跳。
  • 代码生成器导入表、预览、生成、菜单初始化。

返回升级索引