Skip to content

升级指南

v2.0.1

IMPORTANT

v2.0.1 是 v2.0.0 之后的功能增强和修复版本,重点涉及代码生成器、Excel、审计、前端模块注册和 AI 协作资料。

1. 代码生成器重构

本次升级重点优化代码生成器,生成目标和生成前确认流程有较大调整:

  • 支持选择生成到已有后端模块或新建后端模块。
  • 支持联动后端 API 前缀、前端模块和 Liquibase 接入。
  • 编辑页、字段配置页、代码预览页交互重构。
  • 字段智能推断增强,可提示字典、上传、时间、自动填充、逻辑删除和数据权限等常见配置。
  • 生成前磁盘检查增强,会检查模块接入、包扫描范围、路径和数据权限字段等问题。

如果升级前已经导入过表,建议升级后重新打开对应表的生成配置,确认后端模块、API 前缀、前端模块名和生成路径,再执行预览和磁盘检查。

WARNING

新建模块和自动补齐模块接入会修改 POM、application.yml、Liquibase 入口和前端 register.ts 等宿主文件。二开项目使用前请先预览,确认生成计划后再写入。

2. 数据库变更

  • generator_table 增加生成目标相关字段,用于保存后端模块、API 前缀和前端模块等配置。
  • 初始化脚本补充“角色管理-SQL导出”按钮和默认角色授权。
  • 初始化脚本修正菜单标题:“登陆日志”改为“登录日志”。

如果你的系统中超级管理员角色不是 role_id = 1,或已经二开了角色初始化数据,升级后需要在角色管理中手工给目标角色补充“SQL导出”按钮权限。

CAUTION

升级前请备份数据库,并先在测试库或影子库验证 Liquibase 执行结果。代码生成器表如果有大量二开数据,升级后请重点检查生成配置是否符合预期。

3. 前端配置

前端新增动态模块 API 上下文配置:

text
VITE_API_CONTEXT_PATH=/api
VITE_API_PROXY_TARGET=http://127.0.0.1:9991

动态模块请求会基于 VITE_API_CONTEXT_PATH 和模块 API 前缀拼接。升级后请同步检查 .env*、Vite proxy、Nginx location 和后端 server.servlet.context-path 是否一致。

前端还新增了 src/modules/**/register.ts 自动发现能力。如果二开项目手动注册过模块,需要确认模块名唯一,避免重复注册。

4. Excel 与审计

  • Excel 优化 LocalDateLocalDateTimeLocalTime 的导入导出转换,导出格式分别为 yyyy-MM-ddyyyy-MM-dd HH:mm:ssHH:mm:ss
  • 审计日志优化慢操作和异常诊断记录逻辑。

如果生产环境开启审计诊断,请检查 config/{profile}/audit-log.yml 中的慢操作阈值、诊断开关和日志保留策略。

5. AI 协作资料

本版本在 sz-boot-parent 中新增 AI 协作资料:

text
docs/project-knowledge-conventions.md
docs/codex-skills/README.md
docs/codex-skills/skills/sz-code-generator-workflow/SKILL.md
docs/codex-skills/skills/sz-liquibase-db-compat/SKILL.md

说明:

  • project-knowledge-conventions.md 是 Sz 体系通用知识库。
  • sz-code-generator-workflow 用于代码生成器相关生成、检查、清理和回滚。
  • sz-liquibase-db-compat 用于 Liquibase 与双库兼容迁移。
  • 这些文件不参与 Maven 构建,不影响服务启动,也不会改变运行时行为。

6. 推荐验证

后端:

bash
E:\opt\apache-maven-3.9.6-bin\apache-maven-3.9.6\bin\mvn.cmd -pl sz-service/sz-service-admin -am compile -DskipTests

如果目标库是 PostgreSQL:

bash
E:\opt\apache-maven-3.9.6-bin\apache-maven-3.9.6\bin\mvn.cmd -pl sz-service/sz-service-admin -am compile -DskipTests -Ppostgresql

前端:

bash
pnpm run type-check
pnpm run build

代码生成器重点回归:

  1. 打开旧导入表配置,保存后执行预览和磁盘检查。
  2. 使用已有模块生成一个普通 CRUD,确认前后端、菜单脚本和 Liquibase 预览正常。
  3. 如需新建模块,先预览生成计划,再确认是否写入。
  4. 导出包含 LocalDateLocalDateTimeLocalTime 字段的 Excel,确认格式正确。

v2.0.0

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_menusys_role_menusys_deptsys_user_rolesys_user_deptsys_dictsys_dict_typesys_resource 是否有自定义数据。
  • 审计表 sys_operation_logsys_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_dictsys_dict:static_loadedsys_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-mysqlsz-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_idmenu_idrelation_type_cdrelation_id
手工控制数据过滤Service 层使用 DataScopeSessionSimpleDataScopeHelper 开启上下文只对需要过滤的查询显式开启,避免误伤所有查询

WARNING

旧表中的 sys_data_rolesys_data_role_menu 如果仍存在,不应继续作为 v2.0.0 官网主流程描述。当前后端以 SysRoleMenuServiceImplsys_role_menusys_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_idpermission_typemenu_iddata_scope_cd
  • sys_data_role_relation:当数据范围为“自定义”时,记录角色在指定菜单下关联的部门或用户明细,包含 role_idmenu_idrelation_type_cdrelation_id

推荐使用方式:

  1. 先完成目标环境的 sys_rolesys_menusys_deptsys_user 等基础数据迁移或初始化。
  2. 确认目标环境与导出环境的角色 ID、菜单 ID、部门 ID、用户 ID 是否一致;如果不一致,先使用迁移映射表或人工映射方案修正脚本中的 ID。
  3. 在源环境的角色管理中选择目标角色,导出角色-权限脚本,并按目标数据库选择 SQL 方言。
  4. 检查脚本中的 sys_role_menusys_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。
  • 如果多个业务模块各自维护字典,建议新增来源,例如 ssoshopcrm,并规划不同 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_dictsys_dict:static_loadedsys_dict:static_types静态字典全量预热后记录已加载状态和静态类型列表

前端 optionsStore 也同步调整:

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

迁移注意事项:

  • 旧前端如果仍只依赖 GET /sys-dict/dict 全量返回,短期仍可使用;新页面建议改为 useDict / useDictOptions,让静态字典预热与动态字典按需加载协同工作。
  • 自定义动态字典必须实现 DynamicDictLoader#getTypeCode()getTypeName(),最终完整 typeCode 会自动加上 dynamic_ 前缀;不同动态加载器的完整 typeCode 不能重复。
  • 如果升级后发现字典下拉仍是旧数据,先清理 Redis 中的 sys_dictsys_dict:static_loadedsys_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_logsys_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_tablesys.operation.log.detail 权限。
  6. 进入“操作审计”页面,分别验证审计日志、性能日志、异常日志和详情抽屉。

生产注意:

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

完整使用说明见 审计日志

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

v2.0.0 新增 src/coresrc/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-auditaudit-log.ymlVITE_AUDIT_API_BASE,上线前规划日志保留周期
新功能脚本预览与导出用于菜单、字典、角色-权限等初始化或迁移脚本确认;角色-权限导出重点核对 sys_role_menusys_data_role_relation
新功能PostgreSQL 支持通过 DB_TYPE + 数据库模块二选一启用
新功能前端 edition / module 注册派生项目通过 edition 注册登录适配器和业务模块
改变前端构建链升级到 Vite 7 / Node 20.19+ / pnpm 10.17.1先升级本机和 CI 环境,再执行 pnpm install --frozen-lockfilepnpm type-checkpnpm build
改变API 前缀拆分为 admin/audit/generator前端接口迁移为 adminHttp / auditHttp / generatorHttp
改变字典加载拆分为静态预热 + 按类型按需加载自定义动态字典需保证完整 dynamic_* typeCode 唯一;升级后可清理字典 Redis 缓存验证
改变数据权限合并到系统角色授权不再按独立数据角色作为官网主流程,重点迁移 sys_role_menusys_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 参数回跳。
  • 代码生成器导入表、预览、生成、菜单初始化。

v1.3.4-beta

1. 资源模块(sz-common-resource)全新引入

本次升级引入了全新的 sz-common-resource 资源模块,用于替代原来直接调用 OssClient 的上传方式。新模块以「场景(Scene)」为核心概念,支持为不同业务场景独立配置存储类型(本地磁盘 / OSS 公有 / OSS 私有)、命名规则、路径策略和访问安全策略,并通过统一接口 POST /resource/upload 对前端暴露上传能力。

IMPORTANT

本次升级涉及配置文件结构调整。原 oss.yml 顶级 oss: 配置迁移为 sz.oss:sz.resource: 两段,请务必同步更新配置文件。

1.1 两个模块的职责划分

本次升级涉及两个相互配合的模块,职责明确分离:

模块职责配置前缀
sz-common-oss厂商适配层。封装 MinIO / 阿里云 / 腾讯云 / 七牛云等底层 S3 SDK,负责 bucket 接入凭据管理。sz.oss.*
sz-common-resource业务抽象层。以「场景(sceneCode)」为核心,屏蔽底层存储细节,统一管理文件上传、路径规则、命名策略、访问模式和安全白名单。sz.resource.*

业务代码只需调用 ResourceService,不再直接依赖 OssClient;底层存储介质(本地/OSS)和访问方式(直链/预签名/代理)均通过 yml 配置切换,代码无需改动。

NOTE

sz-common-oss 同步修复了部分云厂商配置问题,包括:腾讯云 / 七牛云因 region 填写错误导致 Presigned URL 返回 403、阿里云 2025 年 3 月后中国内地地域需配置 CNAME domain、MinIO URL 协议拼接重复等问题。

各厂商的详细接入说明和排查指引,请阅读项目源码中的 sz-common-oss/docs/oss-provider-guide.md

1.2 配置文件结构说明

新版配置分为两段写在 oss.yml 中:

  • sz.oss.*:S3 协议接入凭据(对接 MinIO / 阿里云 / 腾讯云等)。
  • sz.resource.*:业务资源体系,按场景(sceneCode)定义存储类型、命名规则、路径策略、访问模式和安全白名单。

以下为实际项目配置示例(config/local/oss.yml),完整展示了三种典型场景配置方式:

yml
sz:
  # oss 存储配置(厂商接入凭据)
  oss:
    # 支持 S3 协议的厂商: MINIO / ALIYUN / TENCENT / QINIU
    provider: MINIO
    endpoint: 192.168.100.176:9000
    accessKey: your-access-key
    secretKey: your-secret-key
    # 全局存储桶(场景未指定 bucket 时回退使用)
    bucketName: test
    domain: http://192.168.100.176:9000
    scheme: http

  # 场景资源配置(业务资源体系)
  resource:
    # 本地存储根目录(LOCAL 类型场景使用),支持相对路径或绝对路径
    root: ./data
    # 全局默认存储类型(场景未单独配置 type 时生效)
    default-storage-type: LOCAL
    # 全局安全白名单(场景未单独配置 exts 时生效)
    security:
      allowed-exts: [jpg, jpeg, png, gif, bmp, webp, svg, ico, pdf, doc, docx, xls, xlsx, ppt, pptx, rtf, txt, csv, zip, rar, 7z, tar, gz, mp3, wav, ogg, mp4, mov, avi, wmv]
    max-size: 50MB

    scenes:

      # ========= 场景示例:账户头像 =========
      # 同一个 sceneCode 可注释切换三种模式,根据环境选择一种激活

      # 【模式一:本地存储 + Java 服务代理访问】(当前激活)
      # 适合:开发/测试环境,无需额外 OSS 服务
      - code: admin.user.logo
        type: LOCAL
        serve-mode: DIRECT
        base-url: http://127.0.0.1:9991/api/admin/resource/file/logo  # Java 服务代理地址
        # base-url: http://127.0.0.1:7980/providers  # 或使用 Nginx 代理地址
        path: logo
        naming: ORIGINAL
        path-strategy: BIZ_DATE
        exts: [svg, png, jpg, jpeg, webp, gif]
        max-size: 3

      # 【模式二:OSS 私有桶 + 预签名 URL 访问】(注释状态)
      # 适合:需要访问控制的生产环境,URL 有时效性,安全性高
      # - code: admin.user.logo
      #   type: OSS
      #   serve-mode: PRESIGNED
      #   bucket: client-logos
      #   base-url: http://192.168.100.176:9000/client-logos
      #   naming: ORIGINAL
      #   path-strategy: BIZ_DATE
      #   exts: [svg, png, jpg, jpeg, webp, gif]
      #   max-size: 3

      # 【模式三:OSS 公有桶 + 直链访问】(注释状态)
      # 适合:公开资源,直链访问,无访问控制需求
      # - code: admin.user.logo
      #   type: OSS
      #   serve-mode: DIRECT
      #   bucket: client-logos-public
      #   base-url: http://192.168.100.176:9000/client-logos-public
      #   naming: UUID
      #   path-strategy: BIZ_DATE
      #   exts: [svg, png, jpg, jpeg, webp, gif]
      #   max-size: 3

      # ========= 其他业务场景示例 =========

      - code: teacher.attachment
        type: LOCAL
        serve-mode: DIRECT
        path: teacher-attachments
        base-url: http://127.0.0.1:9991/api/admin/resource/file/teacher-attachments
        naming: ORIGINAL
        path-strategy: BIZ_DATE
        exts: [png, jpg, jpeg, webp, gif, pdf, doc, docx, xls, xlsx]
        max-size: 10

      - code: teacher.richtext
        type: LOCAL
        serve-mode: DIRECT
        path: teacher-richtext
        base-url: http://127.0.0.1:9991/api/admin/resource/file/teacher-richtext
        naming: UUID
        path-strategy: DATE
        exts: [png, jpg, jpeg, webp, gif, pdf, doc, docx, xls, xlsx]
        max-size: 5

1.3 三种访问模式对照说明

admin.user.logo 场景在配置文件中同时提供了三种模式的写法(注释切换),按需取消注释、注释掉其他两种即可:

模式typeserve-mode适用场景注意事项
本地 + Java 代理LOCALDIRECT开发/测试环境,无需 OSSbase-url 需配置为 Java 服务的资源代理地址或 Nginx 代理地址
OSS 私有 + 预签名OSSPRESIGNED生产环境,需要访问控制生成的 URL 有时效性(默认 3600 秒),适合合同、证件等敏感资源
OSS 公有 + 直链OSSDIRECT公开资源,无访问控制需求bucket 需设为公读,base-url 填写 OSS 公网访问地址

1.4 场景核心配置字段说明

字段说明可选值
code场景唯一标识(即前端 sceneCode),全局不可重复自定义字符串,建议 模块.用途 格式
type存储类型LOCAL(本地磁盘)/ OSS(对象存储)
pathLOCAL 类型必填,相对于 root 的存储子目录字符串路径
bucketOSS 类型必填,对应的 bucket 名称字符串
naming文件命名规则UUID(随机)/ ORIGINAL(原文件名,冲突时补时间戳)
path-strategy路径生成策略FLAT(扁平)/ DATE(按日期)/ BIZ_DATE(bizKey + 日期)
serve-mode访问模式DIRECT(直链)/ PRESIGNED(预签名,仅 OSS)/ TOKEN(token 鉴权)
base-urlDIRECT 模式必填,文件访问根地址URL 字符串
exts场景级文件扩展名白名单(覆盖全局 security.allowed-exts字符串列表
max-size场景级最大文件大小,支持 MB 单位3(MB)或 3MB

TIP

完整配置说明、所有场景案例(Nginx 前置、OSS 公有/私有、TOKEN 鉴权等)及 ResourceService API 参考,请阅读项目源码中的 sz-common-resource/docs/resource-user-guide.md

1.5 存储设计:数据库只存 objectKey

理解这一设计原则,是正确使用资源模块的关键。

数据库存储的是 objectKey,而不是完整的访问 URL。

objectKey 是文件在存储介质中的相对路径(对象键),例如:

# 本地存储场景下的 objectKey 示例
admin/20260430/avatar.jpg

# OSS 存储场景下的 objectKey 示例
logo/admin/20260430/a3f9c1.jpg

完整的访问 URL 是在运行时base-url(场景配置) + objectKey 动态拼装得到的,并不持久化到数据库。


为什么这样设计?

如果将完整 URL 存入数据库,一旦发生以下任何变化,历史数据全部失效,必须批量更新数据库:

  • 服务器 IP 或端口变更(如 localhost:19991 → 生产域名)
  • 存储类型切换(本地磁盘 → OSS)
  • OSS Bucket 或域名变更(换 CDN、换厂商)
  • 访问模式变更(公有直链 → 私有预签名)

而存储 objectKey 后,只需修改 oss.yml 中对应场景的 base-urlserve-mode 配置,所有历史记录在下一次查询时即可自动适配新地址,配置一处,全局生效,数据库无需改动


完整数据流示意:

【上传时】
前端 → POST /resource/upload(携带 sceneCode)
     → 返回 { accessUrl, objectKey, resourceId, ... }
     → 业务代码:入库保存 objectKey(不保存 accessUrl)

【查询时】
数据库读取 objectKey
     → @OssUrlFill 切面根据 sceneCode 的当前配置(base-url / serve-mode)
       动态拼装或生成 accessUrl
     → 接口返回完整可访问的 accessUrl 给前端

核心收益总结:

场景存储完整 URL存储 objectKey(当前方案)
迁移存储环境需批量更新数据库只改 yml 配置,数据库不动
切换 LOCAL → OSS历史数据全部失效历史数据自动适配
切换公有 → 私有访问静态 URL 无法签名@OssUrlFill 自动改用预签名策略
多环境(开发/测试/生产)各环境数据不互通各环境独立配置 base-url,数据结构一致

TIP

上传接口返回的 accessUrl 用于当次前端展示(如上传成功后立即预览),入库时应保存 objectKey。后续查询时通过 @OssUrlFill 自动回填 accessUrl 返回给前端,前端无感知。


1.6 @OssUrlFill 注解

正因为入库保存的是 objectKey,查询接口需要一种机制在返回时自动将其转换为可访问的 accessUrl,这就是 @OssUrlFill 注解的职责。

标注在 VO 的字段上,框架会在接口返回前自动按场景配置完成转换,无需在业务代码中手动拼接 URL。

java
public class UserVO {
    /**
     * 头像字段,数据库存储的是 objectKey(如 admin/20260430/avatar.jpg)
     * @OssUrlFill 会在返回时自动按 admin.user.logo 场景配置转换为完整 accessUrl
     */
    @OssUrlFill(sceneCode = "admin.user.logo")
    private String avatar;
}

2. 前端上传组件 Props 变更(Breaking Change)

本次升级统一了前端上传接口,所有上传组件的 dir 属性均已变更为 sceneCode(对应后端配置的场景码),并新增了可选的 bizKeypathSegments 属性。同时,上传接口响应字段也发生了变化。

WARNING

这是一个破坏性变更。使用了以下组件的页面必须同步修改,否则上传功能将无法正常工作。

2.1 涉及组件汇总

组件变更内容
UploadFiles.vuedirsceneCode,响应字段 url/filename/fileIdaccessUrl/originName/resourceId
SimplifyUpload/index.vuedirsceneCode,新增可选 bizKeypathSegments
Img.vuedirsceneCode,新增可选 bizKeypathSegments,新增 crop 裁剪属性
Imgs.vuedirsceneCode,新增可选 bizKeypathSegments
JoditEditor/index.vueuploadDirsceneCode,默认值变为 system.richtext

2.2 迁移示例

vue
<!-- UploadFiles -->
<UploadFiles :dir="'upload/avatar'" v-model="form.fileList" />

<!-- Img 单图上传 -->
<Img :dir="'upload/avatar'" v-model="form.avatar" />

<!-- Imgs 多图上传 -->
<Imgs :dir="'upload/images'" v-model="form.images" />

<!-- JoditEditor 富文本 -->
<JoditEditor :upload-dir="'teacher/aabb'" v-model="form.content" />
vue
<!-- UploadFiles:dir → sceneCode,值改为后端配置的场景码 -->
<UploadFiles :sceneCode="'admin.user.logo'" v-model="form.fileList" />

<!-- Img 单图上传:dir → sceneCode -->
<Img :sceneCode="'admin.user.logo'" v-model="form.avatar" />

<!-- Imgs 多图上传:dir → sceneCode -->
<Imgs :sceneCode="'teacher.attachment'" v-model="form.images" />

<!-- JoditEditor 富文本:uploadDir → sceneCode,使用富文本场景码 -->
<JoditEditor scene-code="system.richtext" v-model="form.content" />

2.3 响应字段变更(UploadFiles)

如果你在业务代码中直接使用了上传接口的响应字段,请参照以下映射关系进行迁移:

旧字段(IUploadResult新字段(IResourceUploadResult说明
urlaccessUrl文件访问地址
filenameoriginName原始文件名
fileIdresourceId文件唯一标识
objectNameobjectKey存储对象键(入库字段)
dirTag已废弃,由 sceneCode 替代

2.4 getOssPreviewUrl 已移除

旧版上传组件在回显时需调用 getOssPreviewUrl()objectKey / objectName 转换为可访问 URL(触发 OSS 预签名)。

新版统一改为由后端在 accessUrl 字段直接返回可访问 URL:LOCAL 模式返回直链,OSS 私有模式返回预签名 URL,OSS 公有模式返回直链。前端直接使用 accessUrl,无需任何转换。

NOTE

如有业务代码中存在 getOssPreviewUrl() 调用,可直接移除,改为使用接口返回的 accessUrl 字段。

2.5 多附件展示 — FileDownloadList 组件

FileDownloadList 组件用于在列表页或详情页展示多个文件附件,统一适配 resource 体系,与 UploadFiles 上传组件配套使用。

组件能力:

  • 展示文件名 + 下载操作
  • 图片类型自动识别,支持 el-image-viewer 全屏预览,切换时显示文件名
  • 超出 maxRows 条时自动折叠,点击展开/收起
  • 兼容 ResourceRef[](后端回填 accessUrl)和 string[](纯 URL)两种数据格式

Props 说明

Prop类型默认值说明
filesResourceRef[] | string[][]文件列表,来自后端回填 accessUrlResourceRef[] 或纯 URL 数组
align'left' | 'center' | 'right''left'列表对齐方式
maxRowsnumber0折叠阈值,0 表示不折叠

数据流

【上传时】前端 UploadFiles 获得 IResourceUploadResult[]
  → 提交时 map 为 ResourceRef[](只取 objectKey / originName / contentType / sceneCode)
  → 入库保存 ResourceRef JSON 数组(不存 accessUrl)

【查询时】后端读取 ResourceRef[]
  → resolveUrl 按 sceneCode 回填 accessUrl 字段
  → 接口返回含 accessUrl 的 ResourceRef[] 给前端

【展示时】FileDownloadList :files="row.url"
  → 读取 f.accessUrl 作为下载/预览 URL
  → 读取 f.originName 显示文件名
  → 读取 f.contentType 判断是否为图片

后端入库字段(ResourceRef 结构)

字段是否入库说明
objectKey存储对象键,唯一标识文件
originName原始文件名,展示用
contentTypeMIME 类型,用于判断图片/文件
sceneCode场景编码,后端 resolveUrl 回填时使用
accessUrl后端动态回填,不入库

完整使用示例(以教师统计模块为例)

vue
<!-- 列表页:ProTable column template 中展示附件 -->
<template #url="{ row }">
  <file-download-list :files="row?.url" align="left" :max-rows="3" />
</template>
vue
<!-- 表单页:UploadFiles 上传附件 -->
<el-form-item label="附件" prop="url">
  <upload-files
    v-model="fileUrls"
    :limit="5"
    :file-size="3"
    scene-code="teacher.attachment"
    path-segments="template,teacher"
    @all-success="handleAllSuccess"
  />
</el-form-item>
ts
// 提交时:将上传结果转换为 ResourceRef[] 入库(只保留 objectKey,不保存 accessUrl)
form.url = (fileUrls.value as IResourceUploadResult[])
  .filter(Boolean)
  .map(({ objectKey, originName, contentType }): ResourceRef => ({
    objectKey,
    originName,
    contentType,
    sceneCode: 'teacher.attachment'
  }))

NOTE

accessUrl 由后端根据场景配置(sceneCode)动态回填,前端不入库、不手动拼接,确保切换存储环境或访问模式时历史数据自动适配。


3. @LogicDeleteFill 注解

本次升级引入 @LogicDeleteFill 注解,用于优化逻辑删除时附加字段(delete_iddelete_time)的自动填充机制。

旧版行为:框架在所有实体执行逻辑删除时,无差别地尝试查找并填充 delete_timedelete_id 字段,但字段名规则固定,不支持自定义,灵活性不足。

新版行为:只有标注了 @LogicDeleteFill 的实体类,才会在逻辑删除时自动填充附加字段;同时支持通过注解属性自定义字段名

NOTE

@LogicDeleteFill 只控制 delete_iddelete_time附加字段的自动填充行为,与逻辑删除本身无关。未标注该注解的实体,逻辑删除(@Column(isLogicDelete = true))依然正常执行,仅不再自动填充附加字段。

3.1 变更说明

版本行为
v1.3.3-beta 及以前所有实体逻辑删除时,框架无差别尝试填充 delete_timedelete_id,字段名固定不可自定义
v1.3.4-beta 起仅标注了 @LogicDeleteFill 的实体才触发附加字段自动填充,字段名可通过注解属性自定义

框架内置的 SysDictSysDictTypeSysMenu 已默认添加该注解,无需额外处理。

3.2 使用说明

对于需要自动填充 delete_id / delete_time 的自定义 PO 类,在类上添加 @LogicDeleteFill 即可:

java
// 使用默认字段名(delete_time / delete_id)
@LogicDeleteFill
public class BizOrderPO {

    @Column(isLogicDelete = true)
    private Integer deleted;

    private LocalDateTime deleteTime;  // 自动填充

    private Long deleteId;             // 自动填充
}

如果实体中的删除时间或删除人字段名与默认值不同,可通过注解属性显式指定(空字符串表示不填充该字段):

java
// 自定义字段名
@LogicDeleteFill(deleteTimeColumn = "deleted_at", deleteByColumn = "deleted_by")
public class BizContractPO {

    @Column(isLogicDelete = true)
    private Integer deleted;

    private LocalDateTime deletedAt;   // 自动填充

    private Long deletedBy;            // 自动填充
}

// 只填充删除时间,不填充删除人
@LogicDeleteFill(deleteByColumn = "")
public class BizLogPO {

    @Column(isLogicDelete = true)
    private Integer deleted;

    private LocalDateTime deleteTime;  // 自动填充

    // 无 deleteId 字段,传空字符串跳过填充
}

4. Excel 导入功能增强

本次升级新增三个注解并引入统一导入框架(AbstractExcelImportTemplate),向后兼容,无需迁移

4.1 新增注解说明

@ExcelTemplate

标注在导入 DTO 类上,将该类注册为可下载的导入模板。alias 作为注册唯一 key 和下载时的文件名,需配合 @EnableExcelTemplateScan 启用扫描。

属性说明默认值
alias模板唯一标识,同时作为下载文件名(如 "教师统计导入模板.xlsx"必填
validRowsExcel 数据验证覆盖行数(从第 2 行起)1000
java
@ExcelTemplate(alias = "教师统计导入模板.xlsx")
public class TeacherStatisticsImportDTO {

    @ImportColumn(required = true, columnWidth = 100)
    @ExcelProperty(value = "统计年份")
    private String year;

    @ImportColumn(required = true)
    @ExcelProperty(value = "统计月份")
    private String month;

    @ExcelProperty(value = "讲师区分类型")
    @DictFormat(dictType = "account_status", isSelected = true)  // 字典下拉
    private String teacherCommonType;

    @ExcelEnumFormat(preset = ExcelEnumPreset.YES_NO)            // 枚举下拉
    @ExcelProperty(value = "是否无效")
    private YesNoEnum hasInvalid;

    @ExcelIgnore  // 不出现在模板中
    private String checkStatus;
}
@ImportColumn

字段级注解,控制该列在导入模板中的必填校验和列宽。

属性说明默认值
required是否必填。true 时表头自动加红色 * 前缀,并在 Excel 中生成非空数据验证弹框false
columnWidth指定列宽(字符单位)。-1 表示自动计算-1
@EnableExcelTemplateScan

标注在 Spring Boot 启动类或配置类上,启动时扫描指定包路径,将所有带 @ExcelTemplate 的 DTO 类注册到模板中心。basePackages 支持 * 通配符。

java
@SpringBootApplication
@EnableExcelTemplateScan(basePackages = "com.sz.sso.*.pojo.dto")
public class AdminApplication { ... }

4.2 导入模板自动生成

配置好注解后,前端通过模板下载接口即可获得自动生成的空白导入模板,无需手动维护 Excel 文件。

三级查找策略(按优先级):

  1. classpath:/templates/{templateName} — 静态模板文件(优先)
  2. sys_temp_file 表 — 手动上传到系统的模板文件
  3. ExcelTemplateScanRegistryalias 动态生成 — 兜底策略,根据 DTO 注解实时生成

必填列效果

@ImportColumn(required = true) 的字段在生成的模板中有两个可见效果:

  • 表头文字前自动追加红色 * 前缀
  • 选中该列数据格时弹出"此列为必填字段"的输入提示;填写空值提交时触发 Excel STOP 级别拦截弹框

Excel 导入模板表头,必填列红色 * 标注

模板下载后填入数据的效果(字典/枚举列自动生成下拉选项):

填写数据的模板示例


4.3 AbstractExcelImportTemplate — 统一导入框架

框架提供抽象基类 AbstractExcelImportTemplate<T>,业务侧继承后只需实现核心业务逻辑,框架自动处理:批次创建、分片执行、失败记录落库、批次状态更新。

必须实现的方法

方法说明
importDtoClass()返回导入 DTO 的 Class
bizType()业务类型字符串,用于失败记录归类(如 "teacher_statistics"
bizName()业务名称,写入批次记录(如 "教师统计导入"
doImport(batchId, rows)执行实际业务写入,返回 ExcelImportBizResult(含成功数和失败明细)
convertExcelFailItems(failRows)将 FastExcel 解析失败行转换为统一的 ExcelImportFailItem

可选钩子

方法说明默认行为
chunkSize()分片大小,0 表示不分片0
beforeImport(batchId, rows)导入前回调空实现
afterChunk(batchId, i, total, result)每片执行后回调空实现
afterImport(batchId, aggregated)全部执行完毕后回调空实现
onFailBatch(batchId, ex)批次异常时回调空实现
currentOperatorId()返回当前操作人 IDnull

完整示例

java
// ① 导入 DTO(带注解)
@ExcelTemplate(alias = "教师统计导入模板.xlsx")
public class TeacherStatisticsImportDTO {
    @ImportColumn(required = true)
    @ExcelProperty("统计年份")
    private String year;

    @ImportColumn(required = true)
    @ExcelProperty("统计月份")
    private String month;

    @ExcelProperty("教师id")
    private String teacherId;
    // ... 其他字段
}

// ② 业务导入器
@Component
public class TeacherStatisticsExcelImporter
        extends AbstractExcelImportTemplate<TeacherStatisticsImportDTO> {

    @Autowired
    private TeacherStatisticsMapper mapper;

    @Override
    protected Class<TeacherStatisticsImportDTO> importDtoClass() {
        return TeacherStatisticsImportDTO.class;
    }

    @Override
    protected String bizType() { return "teacher_statistics"; }

    @Override
    protected String bizName() { return "教师统计导入"; }

    @Override
    protected ExcelImportBizResult doImport(String batchId,
            List<TeacherStatisticsImportDTO> rows) {
        List<ExcelImportFailItem> failItems = new ArrayList<>();
        List<TeacherStatisticsPO> successList = new ArrayList<>();

        for (int i = 0; i < rows.size(); i++) {
            TeacherStatisticsImportDTO dto = rows.get(i);
            // 业务校验
            if (someValidationFail(dto)) {
                failItems.add(ExcelImportFailItem.builder()
                    .rowNo(i + 2)                    // Excel 行号(首行为表头,从第2行起)
                    .bizKey(dto.getTeacherId())
                    .bizKeyLabel("教师ID")
                    .errorMsg("校验失败原因描述")
                    .build());
                continue;
            }
            successList.add(convert(dto));
        }
        mapper.insertBatch(successList);
        return ExcelImportBizResult.of(successList.size(), failItems);
    }

    @Override
    protected List<ExcelImportFailItem> convertExcelFailItems(
            List<ExcelFailRow<TeacherStatisticsImportDTO>> failRows) {
        // 使用框架工具方法,指定业务主识别字段
        return failRows.stream()
            .map(row -> buildExcelFailItem(row,
                TeacherStatisticsImportDTO::getTeacherId, "教师ID"))
            .toList();
    }
}

// ③ Controller 接口
@PostMapping("/import")
@SaCheckPermission("teacher.statistics.import")
public ApiResult<ExcelImportResultVO> importExcel(@ModelAttribute ImportExcelDTO dto) {
    return ApiResult.success(teacherStatisticsService.importExcel(dto));
}

4.4 错误处理方案

导入过程中的错误分为两类,框架统一收口处理:

解析失败(FastExcel 阶段)

表头不匹配、数据格式错误等在 Excel 解析阶段产生,由 convertExcelFailItems() 转换为统一结构。

业务校验失败

数据通过 Excel 解析,但不满足业务规则(重复数据、关联不存在等),在 doImport() 中自行判断后加入失败列表。

统一失败项结构 ExcelImportFailItem

字段类型说明
rowNoIntegerExcel 行号(1-based,第 1 行为表头,数据从第 2 行起)
bizKeyString业务主识别值(如教师 ID "3"),用于定位问题行
bizKeyLabelString主识别值标签(如 "教师ID"
errorCodeString错误码(可选)
errorMsgString错误信息描述
rowDataMap当前行原始数据快照(DTO 序列化后的 Map,供追溯)

接口返回结构 ExcelImportResultVO

字段说明
batchId批次 ID(UUID),唯一标识本次导入任务
success成功条数
fail失败条数
failDetails失败明细列表(框架预留字段,供后续扩展直接内联返回)

持久化

框架自动将批次信息和失败记录落库,无需业务代码干预。

sys_import_batch — 批次记录:

字段示例值说明
batch_id8649352b-976e-42d8-9e40-21aaaddd50b4批次唯一 ID
biz_typeteacher_statistics业务类型
biz_name教师统计导入业务名称
file_name教师统计导入模板 (11).xlsx原始上传文件名
total_count8总行数
success_count6成功条数
fail_count2失败条数
statusFINISHED批次状态:PROCESSING / FINISHED / FAILED
create_time2026-04-30 22:08:12导入开始时间
finish_time2026-04-30 22:08:12导入完成时间

sys_import_fail_record — 失败行记录:

字段示例值说明
batch_id8649352b-976e-42d8-9e40-21aaaddd50b4关联批次 ID
biz_typeteacher_statistics业务类型
row_no3Excel 行号
biz_key3业务主识别值(教师 ID)
biz_key_label教师ID主识别值标签
error_msg错误编号xxx2错误信息
handle_statusPENDING处理状态(默认 PENDING,预留人工处理流程扩展)
row_data{"year":"2013","month":"03","teacherId":"3",...}原始行数据 JSON 快照

NOTE

失败记录已完整落库(sys_import_batch / sys_import_fail_record),包含行号、原始数据快照、错误信息等字段,可结合自身业务自行实现失败记录查询、按批次筛查或人工处理标记等功能。


4.5 前端使用方式

框架提供通用导入弹窗组件 ImportExcel,封装了模板下载、文件上传、进度反馈、结果展示的完整流程。

导入弹窗

导入弹窗界面

弹窗包含:模板标识信息、下载模板按钮(调用三级查找策略生成模板文件)、文件拖拽/点击上传区、数据覆盖开关(将用户的选择以 isCover 参数透传给后端;具体覆盖逻辑需在 doImport() 中自行根据该参数实现)。

组件 Props(acceptParams 参数)

参数类型必填说明
titlestring弹窗标题(显示为"导入:{title}")
aliasstring对应 @ExcelTemplate.alias,用于模板下载查找
fileNamestring下载的模板文件名
templateNamestring模板展示名(不传则与 fileName 相同)
importApiFunction调用导入接口的方法
tempApiFunction下载模板接口方法(不传则使用框架默认)
getTableListFunction导入成功后刷新表格的回调
paramobject透传给导入接口的额外参数
fileSizenumber最大上传文件大小(MB,默认 5
resultTipstring有失败条数时显示的引导提示文案

调用示例

typescript
// 声明组件引用
const ImportExcelRef = ref<InstanceType<typeof ImportExcel>>()

// 打开导入弹窗
ImportExcelRef.value?.acceptParams({
  title: '教师统计',
  alias: '教师统计导入模板.xlsx',
  fileName: '教师统计导入模板.xlsx',
  tempApi: downloadTemplate,
  importApi: importTeacherStatisticsExcelApi,
  getTableList: proTableRef.value?.getTableList,
  resultTip: '失败结果可到【Excel失败记录 - 教师统计导入】页面查看'
})

导入结果弹窗

上传完成后自动弹出导入结果:

导入结果弹窗,成功 6 条失败 2 条

结果弹窗展示:

  • 批次 ID:可一键复制,后续在"Excel 失败记录"页面按此 ID 精确筛查失败明细
  • 成功 / 失败条数:绿色成功数、红色失败数
  • 引导提示(仅 fail > 0 且传入 resultTip 时显示):橙色提示区,告知用户到哪个页面查看失败详情

5. 用户 Store 变更

用户信息的 Store 结构发生调整,userInfo 已重命名为 profile,并调整了持久化策略(不再持久化用户信息,仅持久化 token,用户信息在每次路由初始化时由接口重新拉取)。

NOTE

若项目中有自定义代码直接引用了 userStore.userInfo,请同步修改为 userStore.profile

ts
// 迁移前
import { useUserStore } from '@/stores/modules/user'
const userStore = useUserStore()
console.log(userStore.userInfo.username)
console.log(userStore.userInfo.logo)

// 迁移后
import { useUserStore } from '@/stores/modules/user'
const userStore = useUserStore()
console.log(userStore.profile?.username)
console.log(userStore.profile?.avatar)  // 字段名 logo → avatar

v1.3.3-beta

1. oss.yml 配置参数安全增强说明

本次升级对 OSS 配置文件 oss.yml 进行了补充和优化,全面提升了文件上传的安全性和合规性。新增了 allowedExtsallowedMimeTypes 配置项,用于精确控制允许上传的文件扩展名和 MIME 类型,有效防止非法或高风险类型文件被上传。请根据实际业务需求进行相应的配置和同步。

yml
oss:
  # 支持S3协议的厂商
  provider: MINIO
  endpoint: 192.168.56.101:9000
  accessKey: a4jtJvgEmk4ead5dzac6
  secretKey: UW6kxTGRetIAahV759rFkgoQ8ilXLRUMW0ULdIoo
  bucketName: test
  richtextBucketName: static
  domain: http://192.168.56.101:9000
  # 命名方式: UUID、ORIGINAL原文件名偏好,冲突会补充时间
  naming: original
  scheme: http
  allowedExts:
    # 图片
    - jpg
    - jpeg
    - png
    - gif
    - bmp
    - webp
    - svg
    - ico
    # 文档
    - pdf
    - doc
    - docx
    - xls
    - xlsx
    - ppt
    - pptx
    - rtf
    - txt
    - csv
    - odt
    - ods
    - odp
    - pages
    - numbers
    - keynote
    # 压缩包
    - zip
    - rar
    - 7z
    - tar
    - gz
    # 音频格式
    - mp3
    - wav
    - ogg
    # 视频格式
    - mp4
    - mov
    - avi
    - wmv
  allowedMimeTypes:
    # 图片
    - image/jpeg
    - image/pjpeg
    - image/png
    - image/gif
    - image/bmp
    - image/webp
    - image/svg+xml
    - image/vnd.microsoft.icon
    # 文档
    - application/pdf
    - application/msword
    - application/vnd.openxmlformats-officedocument.wordprocessingml.document
    - application/vnd.ms-excel
    - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    - application/vnd.ms-powerpoint
    - application/vnd.openxmlformats-officedocument.presentationml.presentation
    - application/rtf
    - text/plain
    - text/csv
    - application/vnd.oasis.opendocument.text
    - application/vnd.oasis.opendocument.spreadsheet
    - application/vnd.oasis.opendocument.presentation
    - application/vnd.apple.pages
    - application/vnd.apple.numbers
    - application/vnd.apple.keynote
    # 压缩包
    - application/zip
    - application/x-zip-compressed
    - application/x-rar-compressed
    - application/x-7z-compressed
    - application/x-tar
    - application/gzip
    - application/x-gzip
    # 音频
    - audio/mpeg
    - audio/wav
    - audio/ogg
    # 视频
    - video/mp4
    - video/quicktime
    - video/x-msvideo
    - video/x-ms-wmv
    # 二进制
    - application/octet-stream
  • 新增配置项说明
    • allowedExts:用于指定允许上传的文件扩展名,建议根据实际需要精确定义,提升上传安全性。
    • allowedMimeTypes:用于指定允许上传的 MIME 类型,有助于规避高风险或不被支持的文件类型。
    • 其余参数保持兼容原有配置,继续支持原有存储和业务逻辑。

请及时同步本次 oss.yml 配置更新,以确保服务安全稳定运行。

v1.3.2-beta

1. 代码生成器:新增窗口展示方式配置

本次升级,代码生成器支持灵活切换窗口的展示方式。可根据业务需求,在【弹窗 Dialog】和【抽屉 Drawer】之间自由选择,实现页面弹出的交互优化。 sz-admin 代码生成器弹窗与抽屉展示方式配置截图

  • 支持两种展示模式:弹窗(Dialog)与抽屉(Drawer)。
  • 切换后可让表单、详情等界面以不同形态呈现,提升页面可用性与美观度。

窗口展示方式切换示例:

弹窗模式:

sz-admin 代码生成器弹窗模式(Dialog)展示效果截图

抽屉模式: sz-admin 代码生成器抽屉模式(Drawer)展示效果截图


2. (前端)参数管理与 OSS 组件私有/公有访问能力升级

TIP

本次“资源访问模式”与 OSS 组件私有/公有访问能力的升级为兼容性增强,不会对当前数据库中已存储的资源地址数据造成任何影响,请放心升级。

本次升级在参数管理中引入“资源访问模式”参数,并全面支持 OSS 资源的私有化访问场景,涉及全部主流文件与图片加载组件适配。

一、新增“资源访问模式”参数及实时同步

  • 在参数表单中增加“是否否前端加载”选项(是/否)。
  • 可通过该属性切换前端关键资源(如 Minio OSS 文件)的访问方式:
    • 选择 public 时,前端直接使用数据库中存储的完整资源 URL 进行加载,适用于公开资源;
    • 选择 private 时,前端通过 Minio 获取临时授权地址访问资源,适用于需鉴权保护的私有资源。

sz-admin Minio OSS 私有访问临时授权地址配置截图

“是否前端加载”为“是”时,参数将自动加载到前端缓存中。可通过 configStore 快速获取相关参数:

js
const configStore = useConfigStore();
const accessMode = configStore.getConfigOption('oss.accessMode');

二、参数加载与实时同步机制

  • 参数加载时机

    • 页面首次加载(或 F5 刷新)时会自动拉取参数,保证使用最新配置;

      sz-admin 前端参数 F5 刷新自动拉取最新配置截图

    • 【全新支持】已集成 WebSocket 实时同步:当系统参数发生变更并通过 WebSocket 推送后,前端将第一时间自动接收并完成参数的热更新(无需刷新页面)。

    • 使用此能力需确保已开启并正确配置 WebSocket,如下图所示为加载触发效果:

    sz-admin WebSocket 实时推送参数热更新触发效果截图

三、OSS 私有访问地址支持组件列表

为了更好地支持 private 权限的 Minio 资源访问,系统已同步对以下组件进行适配升级:

  • Avatar 头像组件:实现头像图片 OSS 私有访问地址的获取与展示。
  • FileDownloadList 文件回显展示组件:支持私有文件回显通过临时授权地址动态加载。
  • Img 单图组件:支持加载和回显私有图片资源。
  • Imgs 多图片组件:支持批量回显私有授权图片资源。
  • 账户管理 - 添加/编辑用户页面:头像字段支持私有 OSS 地址加载。

上述组件均会按资源访问模式自动判断并加载私有或公有预览地址,无需手动切换。

四、富文本 OSS 空间独立配置

富文本场景默认面向公共展示,现已支持独立 bucket 空间配置:

  • JoditEditor 富文本组件:通过 oss.richtextBucketName 指定专用 bucket,始终以 public 方式访问,避免私有临时地址失效困扰。

五、自定义获取 OSS 预览地址的通用方法

为方便自定义组件或业务逻辑中按需获取私有 OSS 资源临时访问地址,系统提供了统一的公共方法:

js
// oss.ts 文件
// 使用方式
await getOssPreviewUrl(data.url)

调用该方法即可自动判断资源访问模式,按需获取对应的预览地址,大幅简化自定义集成流程。


3. WebSocket 实时同步能力扩展

本次升级扩展了 WebSocket 实时推送机制,除了支持前端参数的同步(如资源访问模式等),现已全面支持字典数据权限数据的实时同步。
当后端发生字典、权限变更时,前端无需刷新页面即可自动接收最新配置,显著优化系统的数据时效性和一致性体验。

  • 支持同步数据范围:全局参数、字典枚举、权限配置等主干数据。
  • 无需用户手动操作,WebSocket 推送实现秒级感知和更新。

sz-admin WebSocket 用户级别字典与权限数据实时同步推送截图

注:如需启用该能力,请确保后端已正确开启并配置 WebSocket 服务。


4. oss.yml 配置参数同步变更说明

本次升级对 OSS 相关配置文件 oss.yml 进行了调整和补充,以更好地适配不同业务场景下的资源访问需求,尤其新增了富文本专用存储桶参数,便于公用文件的管理和访问。请根据实际情况及时同步更新配置文件。

yml
oss:
  # 支持S3协议的厂商
  provider: MINIO
  endpoint: 192.168.56.101:9000
  accessKey: a4jtJvgEmk4ead5dzac6
  secretKey: UW6kxTGRetIAahV759rFkgoQ8ilXLRUMW0ULdIoo
  # 全局存储桶,偏私有存储
  bucketName: test
  domain: http://192.168.56.101:9000
  richtextBucketName: static # 富文本专用存储桶,偏公用存储
  # 命名方式: UUID、ORIGINAL原文件名偏好,冲突会补充时间
  naming: original
  scheme: http
  • 新增配置项说明
    • richtextBucketName:用于富文本内容专属的 OSS 存储桶,通常为公开访问,建议独立于主业务存储桶,以便权限与内容分离管理。
    • 其余参数与原有版本一致,可继续沿用。

v1.3.1-beta

1. 新增富文本编辑器组件

在本次升级中,新增了基于 Jodit 的富文本编辑器组件,提升内容编辑体验。

如何使用
请参考演示案例 —— [教师统计]

富文本编辑器效果演示

组件示例代码

html
<jodit-editor
	v-model="paramsProps.row.contentHtml"
    :upload-dir="'teacher/aabb'"
    :height="'400px'"
/>

同时,代码生成器也已支持自动生成 jodit 富文本编辑器类型字段:

代码生成器支持富文本编辑器

如需为表单或表格字段配置富文本编辑功能,只需在代码生成器对应字段选择jodit-editor类型即可。

v1.3.0-beta

1. 登录密码传输升级:支持 AES-GCM 加密,提升安全性

为强化系统账户安全,登录相关密码参数现已全面支持 AES-GCM 加密传输。前后端交互时,用户密码字段(password)将以 AES-GCM 算法进行加密后发送,最大限度保障密码信息不被窃取或篡改。

sz-admin 登录请求 AES-GCM 加密 password 字段网络请求截图

一、登录请求示例

前端登录请求体示例:

  • 加密字段:password
  • 随机因子字段:iv
  • 其他辅助参数:clientId、requestId 等

后端会依据 iv 参数完成解密过程,确保密码传输安全可靠。

二、登录请求数限制参数优化

在参数管理模块新增和优化了如下登录安全管控参数:

参数名称Key说明
登录请求次数限制sys.login.requestLimit某时间周期内的登录请求次数限制,按 requestId(Ip+UA)判断,0为不限制
登录次数计数周期(分)sys.login.requestCycle登录请求次数统计的周期,单位为分钟,默认 10 分钟

sz-admin 登录请求次数限制参数 sys.login.requestLimit 配置截图

  • sys.login.requestLimit:限制一段时间内的登录请求次数。若超限,将触发登录保护措施。requestId 由客户端 IP 和 UserAgent 组合生成。
  • sys.login.requestCycle:指定登录请求次数的统计时间范围,默认值为 10 分钟,可根据实际需求灵活调整。

2. 【代码生成器】多文件上传(fileUpload)功能

本次升级,代码生成器新增对多文件上传的支持,涉及后端接口类型变更、前端表单与列表渲染方式,以及数据库表结构调整。请务必阅读并据此更新相关模块,避免兼容性问题。

一、后端类型调整

新增 Java 类型 List<UploadResult> 用于后端服务存储文件。只需在字段定义中选择该类型,代码生成器将在表单中使用 <upload-files> 组件进行多文件上传渲染。 增加java类型List<UploadResult> 用于后端服务的存储,设置此类型后生成的Form表单将使用<upload-files>组件进行渲染。

sz-admin 代码生成器 List<UploadResult> 多文件上传字段类型配置截图

二、前端表单增强

设置字段类型为 List<UploadResult> 后,表单展示样式升级为“多文件上传”。用户可通过拖拽或选择方式上传多个文件。上传区域及回显效果如下:

sz-admin upload-files 多文件拖拽上传组件渲染效果截图

重要说明: 在使用 <upload-files> 组件时,需通过 dir 属性指定目标文件夹路径。
代码生成器生成的默认上传目录为 tmp,如需自定义上传存储目录,例如按业务归类(如 "teacher"),请在代码相应位置调整 dir 属性的赋值。

示例代码:

vue
<upload-files
  v-model:modelValue="fileUrls"
  :limit="5" 				// 指定文件上传数量,代码生成器默认指定5个
  :file-size="3"  			// 指定单文件大小3M,代码生成器默认指定3M 
  :dir="'teacher'"          // 指定上传目标文件夹,代码生成器默认指定tmp文件夹
  :debug="true"
  @all-success="handleAllSuccess"
/>

建议在不同业务场景下合理规划上传目录(dir),以便于文件管理和归档。如需修改默认上传路径,请手动调整前端赋值逻辑。

三、列表页面回显方式

需要在前端指定组件类型fileUpload,此项配置后,ProTable 列表页面会自动采用 slot + <file-download-list> 回显附件列表:

sz-admin ProTable fileUpload 字段组件类型配置截图

列表页面的附件列表展示效果如下:

sz-admin FileDownloadList 列表页附件回显展示效果截图

四、数据库结构变更注意事项

此次升级需调整表结构,将 url 字段类型由原 varchar 修改为 json,即:

url json DEFAULT NULL COMMENT '文件地址(JSON)'

此举带来不兼容性更新,如需升级请确保先备份数据再做迁移及历史兼容性处理。

存储后的 JSON 结构示例

每个附件对应一个对象,整体保存为 JSON 数组,如下所示:

json
[
    {
        "url": "https://minioapi.szadmin.cn/test/teacher/20251021/bg1.png",
        "etag": "73be62dc9778dc13478c59f0c236feca",
        "size": 105636,
        "dirTag": "teacher",
        "fileId": 158,
        "filename": "bg1.png",
        "metaData": {
            "original-filename": "bg1.png"
        },
        "objectName": "teacher/20251021/bg1.png",
        "contextType": "image/png"
    },
    {
        "url": "https://minioapi.szadmin.cn/test/teacher/20251021/bg7.png",
        "etag": "6b6857c36b4fded86645d0ed2662869b",
        "size": 66861,
        "dirTag": "teacher",
        "fileId": 156,
        "filename": "bg7.png",
        "metaData": {
            "original-filename": "bg7.png"
        },
        "objectName": "teacher/20251021/bg7.png",
        "contextType": "image/png"
    },
    {
        "url": "https://minioapi.szadmin.cn/test/teacher/20251021/bg10.png",
        "etag": "9a546cb494af380c9e2cf5efac2ae362",
        "size": 87531,
        "dirTag": "teacher",
        "fileId": 157,
        "filename": "bg10.png",
        "metaData": {
            "original-filename": "bg10.png"
        },
        "objectName": "teacher/20251021/bg10.png",
        "contextType": "image/png"
    }
]

3. 账户管理新增账户类型

本次升级账户管理模块,新增了账户类型管理功能,为系统账户的身份和权限提供更灵活的控制。 通过账户类型,可精准指定“超级管理员账户”,并针对超管账户自动提升其数据权限与菜单权限,实现更高的系统管控能力。

sz-admin 账户管理新增账户类型权限角色体系截图

一、账户类型管理功能说明

  • 系统账户的身份由账户类型决定。可在账户管理页面,对账户类型进行选择与配置。
  • 新增“超级管理员账户”类型,该类型账户拥有更高的数据和菜单访问权限,适合平台级运维及系统配置管理场景。

二、超级管理员权限配置

  • 超管账户的权限受账户类型与角色参数双重控制。系统支持通过参数管理模块进行超级管理员角色的配置。
  • 参数 key:sys.admin.superAdminRoleId
    • 用于指定拥有超级管理员权责的角色 ID,对应的角色将被赋予超管账户的特权。

sz-admin 超级管理员角色 ID 参数 sys.admin.superAdminRoleId 配置截图

4. 重构角色权限管理(破坏性更新)

此次升级对角色权限管理模块进行了重构,涉及数据表结构调整权限配置合并,属于破坏性更新。原有权限配置可能失效,请升级后务必参考新版方案重新进行角色和权限设置,谨防数据丢失。

一、调整内容与迁移说明

  • 数据权限角色已删除,相关功能已合并至系统角色管理
  • 数据权限管理菜单已移除,数据权限配置入口合并到【角色管理】-【权限按钮】页面完成。
  • 只有开启数据权限的菜单才能进行配置,并且需要代码声明的对应实现,详见 数据权限-使用指南

二、全新权限配置交互说明

新版的权限设置页面,采用目录(菜单)与属性结构的分区样式。

  • 左侧为目录和菜单,支持菜单分组、筛选。
  • 右侧为当前选中菜单的权限配置区:包括功能权限(按钮、操作)和数据权限(访问范围)。
  • 数据权限的开关需在菜单管理中配置,仅开启数据权限的菜单才能在角色编辑页面配置数据范围,且需后台代码声明相应实现。

具体交互效果如下:

1. 功能权限启用但数据权限未启用

sz-admin 角色权限管理功能权限启用数据权限未启用状态截图

仅可勾选功能层面的操作,数据权限配置不可用。

2. 数据权限启用(非自定义模式)

sz-admin 角色权限管理数据权限启用非自定义模式配置截图

3. 数据权限启用(自定义模式)

sz-admin 角色权限管理数据权限启用自定义模式配置截图

支持针对用户、部门等维度灵活自定义数据访问范围,可精细化授权权限颗粒。

4. 功能权限与数据权限均未启用,仅设置菜单外链

sz-admin 角色管理仅菜单外链功能权限与数据权限均未启用截图

仅保留菜单授权功能,如外部链接等,其他权限与数据范围控制关闭。

三、数据库变更注意事项

  • sys_data_role_menusys_data_role 标记为废弃,相关业务已合并至 sys_rolesys_data_role_relation 表。

5. [ImportExcel] 组件模板信息功能优化

本次对 ImportExcel 组件的模板区域进行了升级优化,实现了更加灵活的模板文件下载配置。


一、模板区域结构调整说明

组件升级后,模板信息区域需显示模板的“标识”及“文件名”,并通过下载按钮提供模板文件下载入口。通过指定标识和文件名,系统能够准确定位并下载唯一的模板文件。

sz-admin ImportExcel 组件模板标识与文件名管理功能截图


二、使用方式说明

在调用 ImportExcel 组件时,需通过参数指定模板标识(alias)和模板文件名(fileName)。组件接口会自动根据这两个参数查询模板文件,并完成下载。

示例代码:

vue
// 导入
const ImportExcelRef = ref<InstanceType<typeof ImportExcel>>();
const importData = () => {
  const params = {
    title: '教师统计',
    tempApi: downloadTemplate,
    importApi: importTeacherStatisticsExcelApi,
    getTableList: proTableRef.value?.getTableList,
    alias: 'jstj',  // 指定模板标识
    fileName: '教师统计模板.xlsx'  // 指定模板文件名
  };
  ImportExcelRef.value?.acceptParams(params);
};

注意:aliasfileName 应与【模板文件管理】模块中的标识和模板名称保持一致。


三、模板文件管理操作说明

模板文件的标识和文件名来源于【模板文件管理】模块,需提前在系统中进行相关配置。
下图为标识和模板文件名管理操作示例:

sz-admin 模板文件管理标识与模板名称配置示例截图

sz-admin 模板文件管理标识与模板名称配置示例截图二

  • 标识:唯一标记模板文件(如 ‘jstj’)。
  • 模板名称:实际上传的模板文件名(如 ‘教师统计模板.xlsx’)。
  • 备注:可用于区分不同用途模板,便于后续维护。

新增模板文件时,请确保标识和文件名参数正确填写,以便 ImportExcel 组件能正常完成模板定位和下载。

6. 菜单管理操作优化说明

本次对菜单管理操作进行全面优化,无论是目录、菜单还是按钮类型,其创建所需属性均进行了大幅精简,移除非必要字段,目的是提升用户创建效率。


一、属性精简及权限标识逻辑调整

  • 简化菜单创建:不同类型所需属性更精炼,仅需填写必填项即可完成创建。
  • 权限标识调整:移除目录、菜单类型上的权限标识,将权限标识配置逻辑完全转移到按钮类型,只有按钮支持单独设置权限标识,更接近权限实际管控场景。
  • 更合理的权限查询:查询权限标识统一由按钮决定,简化权限配置和后续维护。

二、菜单路由名称优化及升级提示

  • 所有系统组件相关菜单的路由名称进行了简化。例如,角色管理由 /system/roleManage 更改为 /system/role 等。此更改可能导致原有路由失效,升级后请及时检查和修正引用路径。
  • 路由名称的简化有助于统一风格和便于快速定位。

三、不同菜单类型创建所需属性示例

1. 目录类型

sz-admin 菜单管理目录类型创建必填属性截图

  • 必填属性:上级、类型、图标、名称、显示状态、排序
2. 菜单类型

sz-admin 菜单管理菜单类型创建必填属性截图

  • 必填属性:上级、类型、图标、名称、路由相关(名称、地址、组件路径)、显示状态、是否外链、是否全屏/缓存、固定标签页、排序
3. 按钮类型

sz-admin 菜单管理按钮类型创建必填属性截图

  • 必填属性:上级、类型、名称、权限标识、显示状态、排序

只有按钮类型可设置权限标识,目录与菜单类型不能再独立设置权限标识。


7. 【代码生成器】数据权限功能优化

代码生成器现已全面支持自动创建数据权限,让数据权限集成更加高效、便捷。在“生成信息”Tab页下,新增了“自动创建数据权限”选项,且默认开启。启用该功能后,生成的菜单项会自动配置数据权限,并在后台自动生成数据权限控制相关的声明和实现代码。

sz-admin 代码生成器自动创建数据权限功能配置截图

一、功能说明与使用要点

  • 自动创建数据权限:开启后,代码生成器会为新建的菜单自动配置数据权限,无需手动二次设置。
  • 后端自动实现声明:关联的实体、查询将自动集成数据权限控制,提升开发效率与规范性。
  • 风险提示:大部分 SQL 查询将会自动受数据权限控制影响。对于特殊或复杂的 SQL 场景,请务必验证生成结果,复杂情况下有可能导致部分查询无效或异常,建议开发者谨慎使用本功能并根据实际需求对代码进行必要调整。

二、角色权限与数据权限配置

生成代码后,进入“角色管理”,即可为各角色分配对应菜单的访问权限,并灵活调整每个角色的数据权限范围,实现精细化的数据管控。

sz-admin 角色管理为角色分配菜单与数据权限范围配置截图

v1.2.6-beta

    1. 优化: [mysql.yml] - 指定 liquibase 版本表名,兼容不同环境表名大小写,避免版本控制失效。
yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/sz_admin_test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: Yanfa2023@
    hikari:
      #连接池名
      pool-name: HikariCP
      #最小空闲连接数
      minimum-idle: 5
      # 空闲连接存活最大时间,默认10分钟
      idle-timeout: 600000
      # 连接池最大连接数,默认是10
      maximum-pool-size: 10
      # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
      auto-commit: true
      # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
      max-lifetime: 1800000
      # 数据库连接超时时间,默认30秒
      connection-timeout: 30000
      # 连接测试query
      connection-test-query: SELECT 1
  # -- 切换数据库脚本管理工具为 liquibase --    
  liquibase:
    change-log: classpath:db/changelog/changelog-master.xml
    enabled: true
    database-change-log-table: databasechangelog # 增加配置项
    database-change-log-lock-table: databasechangeloglock # 增加配置项
    1. 优化:[ProTable-TableColumn组件] 支持多标签展示

      • 支持 tag=false 且 enum 配置时,自动翻译多值并逗号拼接展示。
      • 支持 tag=true 且 enum 配置时,多标签分组展示及收起。
      • 支持 tag 未设置且 enum 配置时,正常翻译多值展示。
      • 兼容多种数据格式(数组、逗号分隔字符串)。
      • 空值统一展示为 "--"。
      • 新增 tagLimit 属性说明:设置展示标签数量,超出部分通过 Popover 收起展示(tagLimit=-1 时展示全部标签,默认值tagLimit=3)。

      badf4d1921354fe79949b3e8bcfaa627

v1.2.5-beta

  • 重构:字典加载器相关类的包结构和接口定义,优化动态字典加载逻辑

    1. 新增 DynamicDictLoader 接口,专用于动态字典处理,规范动态字典加载流程。

    2. UserOptionDictLoader.java为例,其他相关类依照新接口实现同步调整,具体变动包括:

      • 类继承自 DynamicDictLoader,替换原有 DictLoader 接口;

      • 实现 getTypeCode()getTypeName() 等新接口方法,统一字典类型标识与名称的获取方式;

      • 优化 loadDict() 方法,简化动态字典的缓存与回源逻辑,提升性能和易读性;

      • 代码层面将部分依赖 DynamicDictEnum 的调用方式进行标准化。

    3. 更新 DynamicDictEnum 枚举类,移除 typeCode 的 dynamic_ 前缀,由 DynamicDictLoader 接口自动拼接。

java
@Component
@RequiredArgsConstructor
public class UserOptionDictLoader implements DictLoader {
public class UserOptionDictLoader implements DynamicDictLoader {

    private final RedisCache redisCache;

    private final SysUserService sysUserService;

    @Override
    public DynamicDictEnum getDynamicTypeCode() {
        return DynamicDictEnum.DYNAMIC_USER_OPTIONS;
    }

    @Override
    public String getTypeCode() {
        return DynamicDictEnum.DYNAMIC_USER_OPTIONS.getTypeCode();
    }

    @Override
    public String getTypeName() {
        return DynamicDictEnum.DYNAMIC_USER_OPTIONS.getName();
    }

    @Override
    public Map<String, List<DictVO>> loadDict() {
        String key = getDynamicTypeCode().getTypeCode();
        String name = getDynamicTypeCode().getName();
        String key = getDynamicTypeCode();
        String name = getTypeName();
        if (redisCache.hasHashKey(key)) {
            return Map.of(key, redisCache.getDictByType(key));
        }

        DictVO dictVO;
        List<DictVO> list = new ArrayList<>();
        List<UserOptionVO> userOptions = sysUserService.getUserOptions();
        for (int i = 0; i < userOptions.size(); i++) {
            UserOptionVO option = userOptions.get(i);
            dictVO = DictVO.builder().id(option.getId().toString()).codeName(option.getNickname()).alias(option.getUsername()).sort(i + 1).sysDictTypeCode(key)
                    .sysDictTypeName(name).callbackShowStyle("primary").isDynamic(true).isLock("F").isShow("T").build();
            list.add(dictVO);
        }
        redisCache.setDict(key, list);
        return Map.of(key, list);
    }

    @Override
    public List<DictVO> getDict(String typeCode) {
        return loadDict().get(typeCode);
    }

}
java
@Getter
@RequiredArgsConstructor
public enum DynamicDictEnum {

    // @formatter:off
    DYNAMIC_USER_OPTIONS("dynamic_user_options", "用户信息");
    DYNAMIC_USER_OPTIONS("user_options", "用户信息");
    // @formatter:on
    private final String typeCode; // 类型代码

    private final String name; // 名称

}

v1.2.2-beta

  • 优化UploadFiles多文件上传组件, 简化使用方式并修复一些已知问题。

    上传中:

    sz-admin UploadFiles 多文件上传组件上传中状态截图

    上传完毕:

    sz-admin UploadFiles 多文件上传组件上传完成状态截图

vue
<UploadFiles
  v-model:file-list="fileList" // file-list
  multiple
  :limit="5"
  :file-size="3"
  width="300px"
  height="200px"
  @change="fileChange"
  :accept="'.xlsx,.xls,.doc,.docx'"
  tip="支持上传 .xlsx, .xls, .docx, .doc, .pdf 格式的文件,单个文件大小不能超过 3M"
>
<template #tip> 支持上传 .xlsx, .xls, .docx, .doc, .pdf 格式的文件,单个文件大小不能超过 3M </template>
</UploadFiles>
<script setup lang="ts">
...
const fileList = ref<UploadUserFile[]>();
...

// 接收父组件传过来的参数
const acceptParams = (params: View.DefaultParams) => {
  paramsProps.value = params;
  visible.value = true;
  fileList.value = params.row.url   //  入参赋值,回显
    ? [ 
        {
          name: getFileNameFromUrl(params.row.url),
          url: params.row.url
        }
      ] 
    : [];
  fileUrls.value = params.row.url;
};
...
</script>
vue
<UploadFiles
  v-model:modelValue="fileUrls" // 使用modelValue
  multiple
  :limit="5"
  :file-size="3"
  @change="fileChange"
  accept=".xlsx,.xls,.docx,.doc,.pdf"
  tip="支持上传 .xlsx, .xls, .docx, .doc, .pdf 格式的文件,单个文件大小不能超过 3M" // [可选] 使用tip自定义显示信息,如不输入也会生成默认值,效果与所示话术一致
>
</UploadFiles>
<script setup lang="ts">
...
const fileUrls = ref<string[]>([]);
...

// 接收父组件传过来的参数
const acceptParams = (params: View.DefaultParams) => {
  paramsProps.value = params;
  visible.value = true;
  fileUrls.value = params.row.url;  // 入参赋值,回显
};

// 提交数据(新增/编辑)
const ruleFormRef = ref<InstanceType<typeof ElForm>>();
const handleSubmit = () => {
  ruleFormRef.value!.validate(async valid => {
    if (!valid) return;
    try {
      const urlArr = fileUrls.value; // 提交时获取值。如果接受参数是数组,无需处理
      paramsProps.value.row.url = urlArr[0];
      await paramsProps.value.api!(paramsProps.value.row);
      ElMessage.success({ message: `${paramsProps.value.title}成功!` });
      paramsProps.value.getTableList!();
      visible.value = false;
    } catch (error) {
      console.log(error);
    }
  });
};

</script>

v1.2.1-beta

  • 新增Liquibase管理数据库脚本
yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/sz_admin_test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: Yanfa2023@
    hikari:
      #连接池名
      pool-name: HikariCP
      #最小空闲连接数
      minimum-idle: 5
      # 空闲连接存活最大时间,默认10分钟
      idle-timeout: 600000
      # 连接池最大连接数,默认是10
      maximum-pool-size: 10
      # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
      auto-commit: true
      # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
      max-lifetime: 1800000
      # 数据库连接超时时间,默认30秒
      connection-timeout: 30000
      # 连接测试query
      connection-test-query: SELECT 1
  # -- 切换数据库脚本管理工具为 liquibase --    
  liquibase: #
    change-log: classpath:db/changelog/changelog-master.xml #
    enabled: true #

IMPORTANT

结构变动:

shell
└─resources
    ├─db
  ├─business
      V0.1__README_DDL.sql
  ├─changelog   # Liquibase脚本目录
  changelog-master.xml # changelog主配置
  ├─business # 业务脚本
  └─1.0.0 # 版本号
          000_demo.sql #//
  └─framework # 框架脚本
      ├─1.1.0-beta # 版本号
      001_system.sql # sql脚本
      └─1.2.0-beta # 版本号
              001_sys_message.sql # sql脚本

  └─framework
          V1.1__20230509_Init_DDL.sql
          V1.2__20240511_Update_DDL.sql
          V1.3__20240530_Update_DDL.sql
          V1.4__20240603_Update_DDL.sql
	...	
          V2.6__20250422_Update_DDL.sql
  • 关闭Flyway脚本管理工具(Flyway将于v1.3.0-beta版本弃用
yml
flyway:
  framework: # 框架迁移脚本管理
    enabled: true #
    enabled: false #
    locations: classpath:/db/framework
    baseline-on-migrate: true
    baseline-version: 1
    table: t_db_version
    validate-on-migrate: true
  business: # 业务迁移脚本管理
    enabled: true #
    enabled: false #
    locations: classpath:db/business
    baseline-on-migrate: true
    baseline-version: 1
    table: t_db_version_business
    validate-on-migrate: true

v1.2.0-beta

  • 代码生成忽略表前缀功能

    yml
    sz:
      # 生成工具
      generator:
        path:
          # 前端项目地址
          web:
          # 后端项目地址,默认自动检测springboot项目路径,无需配置。
          api: E://code//Gitlab//sz-framework//sz-admin
        # 模块名,指定代码生成的模块
        module-name: sz-service
        service-name: sz-service-admin        
        global:
          author: sz-admin
          packages: com.sz.admin
           # 忽略的表前缀。若启用此配置,以表名 `t_db_version` 为例,生成的代码和实体类将自动去除前缀 `t_`,仅使用 `db_version` 或 `DbVersion`。
          ignore-table-prefix:	#
            enabled: true	#
            prefixes:	#
              - t_	#

v1.1.0-beta

  • 升级Eslint9

    1. 删除 .eslintignore、.eslintrc.cjs,切换Eslint配置为eslint.config.js

    2. 遵循Eslint和TypeScript最佳实践,避免使用namespace,切换至type。

    3. 切换目录/api/interface/api/types

    下面以/api/interface/captcha.ts为例:

    ts
    // 登录模块
    export namespace ICaptcha {
      export interface Info {
        bigImageBase64: string;
        bigWidth: number;
        bigHeight: number;
        smallImageBase64: string;
        smallWidth: number;
        smallHeight: number;
        requestId: string;
        posY: number;
        secretKey: string;
      }
      export interface VerifyImageParams {
        requestId: string;
        moveEncrypted: string;
      }
    }
    ts
    // 登录模块
    export type CaptchaInfo = {
      bigImageBase64: string;
      bigWidth: number;
      bigHeight: number;
      smallImageBase64: string;
      smallWidth: number;
      smallHeight: number;
      requestId: string;
      posY: number;
      secretKey: string;
    };
    export type CaptchaVerifyImageParams = {
      requestId: string;
      moveEncrypted: string;
    }
  • 新增useDictOptionshook,用于简化optionsStore.getDictOptions('dynamic_user_options')写法

    ts
    import { useOptionsStore } from '@/stores/modules/options';
    import { useDictOptions } from "@/hooks/useDictOptions";
    // 先声明store
    const optionsStore = useOptionsStore();
    // 使用时
    // 表格配置项
    const columns: ColumnProps<SysTempFileRow>[] = [
      { type: 'selection', width: 80 },
      { prop: 'id', label: '模板标识', width: 120 },
      { prop: 'tempName', label: '模版名' },
      { prop: 'url', label: '文件', width: 120 },
      { prop: 'remark', label: '备注' },
      { prop: 'history', label: '历史' },
      {
        prop: 'createId',
        label: '创建人',
        tag: true,
        enum: optionsStore.getDictOptions('dynamic_user_options'),
        enum: useDictOptions('dynamic_user_options'),
        fieldNames: {
          label: 'codeName',
          value: 'id',
          tagType: 'callbackShowStyle'
        }
      },
      { prop: 'createTime', label: '创建时间' },
      {
        prop: 'updateId',
        label: '更新人',
        tag: true,
        enum: optionsStore.getDictOptions('dynamic_user_options'),
        enum: useDictOptions('dynamic_user_options'),
        fieldNames: {
          label: 'codeName',
          value: 'id',
          tagType: 'callbackShowStyle'
        }
      },
      { prop: 'updateTime', label: '更新时间' },
      { prop: 'operation', label: '操作', width: 250, fixed: 'right' }
    ]
    ts
    <template>
      <el-dialog v-model="visible" :title="`${paramsProps.title}`" :destroy-on-close="true" width="580px" draggable>
        <el-form
          ref="ruleFormRef"
          label-width="140px"
          label-suffix=" :"
          :rules="rules"
          :model="paramsProps.row"
          @submit.enter.prevent="handleSubmit"
        >
        
    		...
            
          <el-form-item label="讲师区分类型" prop="teacherCommonType">
            <el-select v-model="paramsProps.row.teacherCommonType" clearable placeholder="请选择讲师区分类型">
              <el-option
                v-for="item in optionsStore.getDictOptions('account_status')"
                v-for="item in accountStatusOption"
                :key="item.id"
                :label="item.codeName"
                :value="Number(item.id)"
              />
            </el-select>
          </el-form-item>
    		
    		...
    		
        </el-form>
        <template #footer>
          <el-button @click="visible = false"> 取消 </el-button>
          <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
        </template>
      </el-dialog>
    </template>
    
    <script setup lang="ts">
    import { ref, reactive } from 'vue';
    import { type ElForm, ElMessage } from 'element-plus';
    import { useDictOptions } from '@/hooks/useDictOptions';
    
    defineOptions({
      name: 'TeacherStatisticsForm'
    });
    
    const optionsStore = useOptionsStore();
    const accountStatusOption = useDictOptions('account_status');
    
    ...
    
    defineExpose({
      acceptParams
    });
    </script>
    
    <style scoped lang="scss"></style>
  • (sass)官方已经不推荐使用 @import 规则来导入 SCSS 文件,而是提倡使用新的 @use 规则

    vue
    <style scoped lang="scss">
    @import './index.scss';  // [!code --]
    @use './index'; // [!code ++]
    </style>
  • 升级sa-token 1.40.0 至 1.41.0,官方更换了sa-session的序列化对象结构,因此对redis序列化可能失效,导致账户登录失败。

    解决方案:清空redis中的Authorizationkey下的缓存对象,重新登陆即可!