升级指南
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),完整展示了三种典型场景配置方式:
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: 51.3 三种访问模式对照说明
admin.user.logo 场景在配置文件中同时提供了三种模式的写法(注释切换),按需取消注释、注释掉其他两种即可:
| 模式 | type | serve-mode | 适用场景 | 注意事项 |
|---|---|---|---|---|
| 本地 + Java 代理 | LOCAL | DIRECT | 开发/测试环境,无需 OSS | base-url 需配置为 Java 服务的资源代理地址或 Nginx 代理地址 |
| OSS 私有 + 预签名 | OSS | PRESIGNED | 生产环境,需要访问控制 | 生成的 URL 有时效性(默认 3600 秒),适合合同、证件等敏感资源 |
| OSS 公有 + 直链 | OSS | DIRECT | 公开资源,无访问控制需求 | bucket 需设为公读,base-url 填写 OSS 公网访问地址 |
1.4 场景核心配置字段说明
| 字段 | 说明 | 可选值 |
|---|---|---|
code | 场景唯一标识(即前端 sceneCode),全局不可重复 | 自定义字符串,建议 模块.用途 格式 |
type | 存储类型 | LOCAL(本地磁盘)/ OSS(对象存储) |
path | LOCAL 类型必填,相对于 root 的存储子目录 | 字符串路径 |
bucket | OSS 类型必填,对应的 bucket 名称 | 字符串 |
naming | 文件命名规则 | UUID(随机)/ ORIGINAL(原文件名,冲突时补时间戳) |
path-strategy | 路径生成策略 | FLAT(扁平)/ DATE(按日期)/ BIZ_DATE(bizKey + 日期) |
serve-mode | 访问模式 | DIRECT(直链)/ PRESIGNED(预签名,仅 OSS)/ TOKEN(token 鉴权) |
base-url | DIRECT 模式必填,文件访问根地址 | 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-url 或 serve-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。
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(对应后端配置的场景码),并新增了可选的 bizKey 和 pathSegments 属性。同时,上传接口响应字段也发生了变化。
WARNING
这是一个破坏性变更。使用了以下组件的页面必须同步修改,否则上传功能将无法正常工作。
2.1 涉及组件汇总
| 组件 | 变更内容 |
|---|---|
UploadFiles.vue | dir → sceneCode,响应字段 url/filename/fileId → accessUrl/originName/resourceId |
SimplifyUpload/index.vue | dir → sceneCode,新增可选 bizKey、pathSegments |
Img.vue | dir → sceneCode,新增可选 bizKey、pathSegments,新增 crop 裁剪属性 |
Imgs.vue | dir → sceneCode,新增可选 bizKey、pathSegments |
JoditEditor/index.vue | uploadDir → sceneCode,默认值变为 system.richtext |
2.2 迁移示例
<!-- 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" /><!-- 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) | 说明 |
|---|---|---|
url | accessUrl | 文件访问地址 |
filename | originName | 原始文件名 |
fileId | resourceId | 文件唯一标识 |
objectName | objectKey | 存储对象键(入库字段) |
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 | 类型 | 默认值 | 说明 |
|---|---|---|---|
files | ResourceRef[] | string[] | [] | 文件列表,来自后端回填 accessUrl 的 ResourceRef[] 或纯 URL 数组 |
align | 'left' | 'center' | 'right' | 'left' | 列表对齐方式 |
maxRows | number | 0 | 折叠阈值,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 | ✅ | 原始文件名,展示用 |
contentType | ✅ | MIME 类型,用于判断图片/文件 |
sceneCode | ✅ | 场景编码,后端 resolveUrl 回填时使用 |
accessUrl | ❌ | 后端动态回填,不入库 |
完整使用示例(以教师统计模块为例)
<!-- 列表页:ProTable column template 中展示附件 -->
<template #url="{ row }">
<file-download-list :files="row?.url" align="left" :max-rows="3" />
</template><!-- 表单页: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>// 提交时:将上传结果转换为 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_id、delete_time)的自动填充机制。
旧版行为:框架在所有实体执行逻辑删除时,无差别地尝试查找并填充 delete_time、delete_id 字段,但字段名规则固定,不支持自定义,灵活性不足。
新版行为:只有标注了 @LogicDeleteFill 的实体类,才会在逻辑删除时自动填充附加字段;同时支持通过注解属性自定义字段名。
NOTE
@LogicDeleteFill 只控制 delete_id、delete_time 等附加字段的自动填充行为,与逻辑删除本身无关。未标注该注解的实体,逻辑删除(@Column(isLogicDelete = true))依然正常执行,仅不再自动填充附加字段。
3.1 变更说明
| 版本 | 行为 |
|---|---|
| v1.3.3-beta 及以前 | 所有实体逻辑删除时,框架无差别尝试填充 delete_time、delete_id,字段名固定不可自定义 |
| v1.3.4-beta 起 | 仅标注了 @LogicDeleteFill 的实体才触发附加字段自动填充,字段名可通过注解属性自定义 |
框架内置的 SysDict、SysDictType、SysMenu 已默认添加该注解,无需额外处理。
3.2 使用说明
对于需要自动填充 delete_id / delete_time 的自定义 PO 类,在类上添加 @LogicDeleteFill 即可:
// 使用默认字段名(delete_time / delete_id)
@LogicDeleteFill
public class BizOrderPO {
@Column(isLogicDelete = true)
private Integer deleted;
private LocalDateTime deleteTime; // 自动填充
private Long deleteId; // 自动填充
}如果实体中的删除时间或删除人字段名与默认值不同,可通过注解属性显式指定(空字符串表示不填充该字段):
// 自定义字段名
@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") | 必填 |
validRows | Excel 数据验证覆盖行数(从第 2 行起) | 1000 |
@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 支持 * 通配符。
@SpringBootApplication
@EnableExcelTemplateScan(basePackages = "com.sz.sso.*.pojo.dto")
public class AdminApplication { ... }4.2 导入模板自动生成
配置好注解后,前端通过模板下载接口即可获得自动生成的空白导入模板,无需手动维护 Excel 文件。
三级查找策略(按优先级):
classpath:/templates/{templateName}— 静态模板文件(优先)sys_temp_file表 — 手动上传到系统的模板文件ExcelTemplateScanRegistry按alias动态生成 — 兜底策略,根据 DTO 注解实时生成
必填列效果
@ImportColumn(required = true) 的字段在生成的模板中有两个可见效果:
- 表头文字前自动追加红色
*前缀 - 选中该列数据格时弹出"此列为必填字段"的输入提示;填写空值提交时触发 Excel STOP 级别拦截弹框

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

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() | 返回当前操作人 ID | null |
完整示例
// ① 导入 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
| 字段 | 类型 | 说明 |
|---|---|---|
rowNo | Integer | Excel 行号(1-based,第 1 行为表头,数据从第 2 行起) |
bizKey | String | 业务主识别值(如教师 ID "3"),用于定位问题行 |
bizKeyLabel | String | 主识别值标签(如 "教师ID") |
errorCode | String | 错误码(可选) |
errorMsg | String | 错误信息描述 |
rowData | Map | 当前行原始数据快照(DTO 序列化后的 Map,供追溯) |
接口返回结构 ExcelImportResultVO
| 字段 | 说明 |
|---|---|
batchId | 批次 ID(UUID),唯一标识本次导入任务 |
success | 成功条数 |
fail | 失败条数 |
failDetails | 失败明细列表(框架预留字段,供后续扩展直接内联返回) |
持久化
框架自动将批次信息和失败记录落库,无需业务代码干预。
sys_import_batch — 批次记录:
| 字段 | 示例值 | 说明 |
|---|---|---|
batch_id | 8649352b-976e-42d8-9e40-21aaaddd50b4 | 批次唯一 ID |
biz_type | teacher_statistics | 业务类型 |
biz_name | 教师统计导入 | 业务名称 |
file_name | 教师统计导入模板 (11).xlsx | 原始上传文件名 |
total_count | 8 | 总行数 |
success_count | 6 | 成功条数 |
fail_count | 2 | 失败条数 |
status | FINISHED | 批次状态:PROCESSING / FINISHED / FAILED |
create_time | 2026-04-30 22:08:12 | 导入开始时间 |
finish_time | 2026-04-30 22:08:12 | 导入完成时间 |
sys_import_fail_record — 失败行记录:
| 字段 | 示例值 | 说明 |
|---|---|---|
batch_id | 8649352b-976e-42d8-9e40-21aaaddd50b4 | 关联批次 ID |
biz_type | teacher_statistics | 业务类型 |
row_no | 3 | Excel 行号 |
biz_key | 3 | 业务主识别值(教师 ID) |
biz_key_label | 教师ID | 主识别值标签 |
error_msg | 错误编号xxx2 | 错误信息 |
handle_status | PENDING | 处理状态(默认 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 参数)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
title | string | ✅ | 弹窗标题(显示为"导入:{title}") |
alias | string | ✅ | 对应 @ExcelTemplate.alias,用于模板下载查找 |
fileName | string | ✅ | 下载的模板文件名 |
templateName | string | 模板展示名(不传则与 fileName 相同) | |
importApi | Function | ✅ | 调用导入接口的方法 |
tempApi | Function | 下载模板接口方法(不传则使用框架默认) | |
getTableList | Function | 导入成功后刷新表格的回调 | |
param | object | 透传给导入接口的额外参数 | |
fileSize | number | 最大上传文件大小(MB,默认 5) | |
resultTip | string | 有失败条数时显示的引导提示文案 |
调用示例
// 声明组件引用
const ImportExcelRef = ref<InstanceType<typeof ImportExcel>>()
// 打开导入弹窗
ImportExcelRef.value?.acceptParams({
title: '教师统计',
alias: '教师统计导入模板.xlsx',
fileName: '教师统计导入模板.xlsx',
tempApi: downloadTemplate,
importApi: importTeacherStatisticsExcelApi,
getTableList: proTableRef.value?.getTableList,
resultTip: '失败结果可到【Excel失败记录 - 教师统计导入】页面查看'
})导入结果弹窗
上传完成后自动弹出导入结果:

结果弹窗展示:
- 批次 ID:可一键复制,后续在"Excel 失败记录"页面按此 ID 精确筛查失败明细
- 成功 / 失败条数:绿色成功数、红色失败数
- 引导提示(仅
fail > 0且传入resultTip时显示):橙色提示区,告知用户到哪个页面查看失败详情
5. 用户 Store 变更
用户信息的 Store 结构发生调整,userInfo 已重命名为 profile,并调整了持久化策略(不再持久化用户信息,仅持久化 token,用户信息在每次路由初始化时由接口重新拉取)。
NOTE
若项目中有自定义代码直接引用了 userStore.userInfo,请同步修改为 userStore.profile。
// 迁移前
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 → avatarv1.3.3-beta
1. oss.yml 配置参数安全增强说明
本次升级对 OSS 配置文件 oss.yml 进行了补充和优化,全面提升了文件上传的安全性和合规性。新增了 allowedExts 和 allowedMimeTypes 配置项,用于精确控制允许上传的文件扩展名和 MIME 类型,有效防止非法或高风险类型文件被上传。请根据实际业务需求进行相应的配置和同步。
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】之间自由选择,实现页面弹出的交互优化。 
- 支持两种展示模式:弹窗(Dialog)与抽屉(Drawer)。
- 切换后可让表单、详情等界面以不同形态呈现,提升页面可用性与美观度。
窗口展示方式切换示例:
弹窗模式:

抽屉模式: 
2. (前端)参数管理与 OSS 组件私有/公有访问能力升级
TIP
本次“资源访问模式”与 OSS 组件私有/公有访问能力的升级为兼容性增强,不会对当前数据库中已存储的资源地址数据造成任何影响,请放心升级。
本次升级在参数管理中引入“资源访问模式”参数,并全面支持 OSS 资源的私有化访问场景,涉及全部主流文件与图片加载组件适配。
一、新增“资源访问模式”参数及实时同步
- 在参数表单中增加“是否否前端加载”选项(是/否)。
- 可通过该属性切换前端关键资源(如 Minio OSS 文件)的访问方式:
- 选择
public时,前端直接使用数据库中存储的完整资源 URL 进行加载,适用于公开资源; - 选择
private时,前端通过 Minio 获取临时授权地址访问资源,适用于需鉴权保护的私有资源。
- 选择

“是否前端加载”为“是”时,参数将自动加载到前端缓存中。可通过 configStore 快速获取相关参数:
const configStore = useConfigStore();
const accessMode = configStore.getConfigOption('oss.accessMode');二、参数加载与实时同步机制
参数加载时机
页面首次加载(或 F5 刷新)时会自动拉取参数,保证使用最新配置;

【全新支持】已集成 WebSocket 实时同步:当系统参数发生变更并通过 WebSocket 推送后,前端将第一时间自动接收并完成参数的热更新(无需刷新页面)。
使用此能力需确保已开启并正确配置 WebSocket,如下图所示为加载触发效果:

三、OSS 私有访问地址支持组件列表
为了更好地支持 private 权限的 Minio 资源访问,系统已同步对以下组件进行适配升级:
Avatar头像组件:实现头像图片 OSS 私有访问地址的获取与展示。FileDownloadList文件回显展示组件:支持私有文件回显通过临时授权地址动态加载。Img单图组件:支持加载和回显私有图片资源。Imgs多图片组件:支持批量回显私有授权图片资源。- 账户管理 - 添加/编辑用户页面:头像字段支持私有 OSS 地址加载。
上述组件均会按资源访问模式自动判断并加载私有或公有预览地址,无需手动切换。
四、富文本 OSS 空间独立配置
富文本场景默认面向公共展示,现已支持独立 bucket 空间配置:
JoditEditor富文本组件:通过oss.richtextBucketName指定专用 bucket,始终以 public 方式访问,避免私有临时地址失效困扰。
五、自定义获取 OSS 预览地址的通用方法
为方便自定义组件或业务逻辑中按需获取私有 OSS 资源临时访问地址,系统提供了统一的公共方法:
// oss.ts 文件
// 使用方式
await getOssPreviewUrl(data.url)调用该方法即可自动判断资源访问模式,按需获取对应的预览地址,大幅简化自定义集成流程。
3. WebSocket 实时同步能力扩展
本次升级扩展了 WebSocket 实时推送机制,除了支持前端参数的同步(如资源访问模式等),现已全面支持字典数据与权限数据的实时同步。
当后端发生字典、权限变更时,前端无需刷新页面即可自动接收最新配置,显著优化系统的数据时效性和一致性体验。
- 支持同步数据范围:全局参数、字典枚举、权限配置等主干数据。
- 无需用户手动操作,WebSocket 推送实现秒级感知和更新。

注:如需启用该能力,请确保后端已正确开启并配置 WebSocket 服务。
4. oss.yml 配置参数同步变更说明
本次升级对 OSS 相关配置文件 oss.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 的富文本编辑器组件,提升内容编辑体验。
如何使用
请参考演示案例 —— [教师统计]。

组件示例代码:
<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 算法进行加密后发送,最大限度保障密码信息不被窃取或篡改。

一、登录请求示例
前端登录请求体示例:
- 加密字段:password
- 随机因子字段:iv
- 其他辅助参数:clientId、requestId 等
后端会依据
iv参数完成解密过程,确保密码传输安全可靠。
二、登录请求数限制参数优化
在参数管理模块新增和优化了如下登录安全管控参数:
| 参数名称 | Key | 说明 |
|---|---|---|
| 登录请求次数限制 | sys.login.requestLimit | 某时间周期内的登录请求次数限制,按 requestId(Ip+UA)判断,0为不限制 |
| 登录次数计数周期(分) | sys.login.requestCycle | 登录请求次数统计的周期,单位为分钟,默认 10 分钟 |

- sys.login.requestLimit:限制一段时间内的登录请求次数。若超限,将触发登录保护措施。requestId 由客户端 IP 和 UserAgent 组合生成。
- sys.login.requestCycle:指定登录请求次数的统计时间范围,默认值为 10 分钟,可根据实际需求灵活调整。
2. 【代码生成器】多文件上传(fileUpload)功能
本次升级,代码生成器新增对多文件上传的支持,涉及后端接口类型变更、前端表单与列表渲染方式,以及数据库表结构调整。请务必阅读并据此更新相关模块,避免兼容性问题。
一、后端类型调整
新增 Java 类型 List<UploadResult> 用于后端服务存储文件。只需在字段定义中选择该类型,代码生成器将在表单中使用 <upload-files> 组件进行多文件上传渲染。 增加java类型List<UploadResult> 用于后端服务的存储,设置此类型后生成的Form表单将使用<upload-files>组件进行渲染。

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

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

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

四、数据库结构变更注意事项
此次升级需调整表结构,将 url 字段类型由原 varchar 修改为 json,即:
url json DEFAULT NULL COMMENT '文件地址(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. 账户管理新增账户类型
本次升级账户管理模块,新增了账户类型管理功能,为系统账户的身份和权限提供更灵活的控制。 通过账户类型,可精准指定“超级管理员账户”,并针对超管账户自动提升其数据权限与菜单权限,实现更高的系统管控能力。

一、账户类型管理功能说明
- 系统账户的身份由账户类型决定。可在账户管理页面,对账户类型进行选择与配置。
- 新增“超级管理员账户”类型,该类型账户拥有更高的数据和菜单访问权限,适合平台级运维及系统配置管理场景。
二、超级管理员权限配置
- 超管账户的权限受账户类型与角色参数双重控制。系统支持通过参数管理模块进行超级管理员角色的配置。
- 参数 key:
sys.admin.superAdminRoleId- 用于指定拥有超级管理员权责的角色 ID,对应的角色将被赋予超管账户的特权。

4. 重构角色权限管理(破坏性更新)
此次升级对角色权限管理模块进行了重构,涉及数据表结构调整与权限配置合并,属于破坏性更新。原有权限配置可能失效,请升级后务必参考新版方案重新进行角色和权限设置,谨防数据丢失。
一、调整内容与迁移说明
- 数据权限角色已删除,相关功能已合并至系统角色管理。
- 数据权限管理菜单已移除,数据权限配置入口合并到【角色管理】-【权限按钮】页面完成。
- 只有开启数据权限的菜单才能进行配置,并且需要代码声明的对应实现,详见 数据权限-使用指南
二、全新权限配置交互说明
新版的权限设置页面,采用目录(菜单)与属性结构的分区样式。
- 左侧为目录和菜单,支持菜单分组、筛选。
- 右侧为当前选中菜单的权限配置区:包括功能权限(按钮、操作)和数据权限(访问范围)。
- 数据权限的开关需在菜单管理中配置,仅开启数据权限的菜单才能在角色编辑页面配置数据范围,且需后台代码声明相应实现。
具体交互效果如下:
1. 功能权限启用但数据权限未启用

仅可勾选功能层面的操作,数据权限配置不可用。
2. 数据权限启用(非自定义模式)

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

支持针对用户、部门等维度灵活自定义数据访问范围,可精细化授权权限颗粒。
4. 功能权限与数据权限均未启用,仅设置菜单外链

仅保留菜单授权功能,如外部链接等,其他权限与数据范围控制关闭。
三、数据库变更注意事项
- 表
sys_data_role_menu、sys_data_role标记为废弃,相关业务已合并至sys_role和sys_data_role_relation表。
5. [ImportExcel] 组件模板信息功能优化
本次对 ImportExcel 组件的模板区域进行了升级优化,实现了更加灵活的模板文件下载配置。
一、模板区域结构调整说明
组件升级后,模板信息区域需显示模板的“标识”及“文件名”,并通过下载按钮提供模板文件下载入口。通过指定标识和文件名,系统能够准确定位并下载唯一的模板文件。

二、使用方式说明
在调用 ImportExcel 组件时,需通过参数指定模板标识(alias)和模板文件名(fileName)。组件接口会自动根据这两个参数查询模板文件,并完成下载。
示例代码:
// 导入
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);
};注意:
alias和fileName应与【模板文件管理】模块中的标识和模板名称保持一致。
三、模板文件管理操作说明
模板文件的标识和文件名来源于【模板文件管理】模块,需提前在系统中进行相关配置。
下图为标识和模板文件名管理操作示例:


- 标识:唯一标记模板文件(如 ‘jstj’)。
- 模板名称:实际上传的模板文件名(如 ‘教师统计模板.xlsx’)。
- 备注:可用于区分不同用途模板,便于后续维护。
新增模板文件时,请确保标识和文件名参数正确填写,以便 ImportExcel 组件能正常完成模板定位和下载。
6. 菜单管理操作优化说明
本次对菜单管理操作进行全面优化,无论是目录、菜单还是按钮类型,其创建所需属性均进行了大幅精简,移除非必要字段,目的是提升用户创建效率。
一、属性精简及权限标识逻辑调整
- 简化菜单创建:不同类型所需属性更精炼,仅需填写必填项即可完成创建。
- 权限标识调整:移除目录、菜单类型上的权限标识,将权限标识配置逻辑完全转移到按钮类型,只有按钮支持单独设置权限标识,更接近权限实际管控场景。
- 更合理的权限查询:查询权限标识统一由按钮决定,简化权限配置和后续维护。
二、菜单路由名称优化及升级提示
- 所有系统组件相关菜单的路由名称进行了简化。例如,角色管理由
/system/roleManage更改为/system/role等。此更改可能导致原有路由失效,升级后请及时检查和修正引用路径。 - 路由名称的简化有助于统一风格和便于快速定位。
三、不同菜单类型创建所需属性示例
1. 目录类型

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

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

- 必填属性:上级、类型、名称、权限标识、显示状态、排序
只有按钮类型可设置权限标识,目录与菜单类型不能再独立设置权限标识。
7. 【代码生成器】数据权限功能优化
代码生成器现已全面支持自动创建数据权限,让数据权限集成更加高效、便捷。在“生成信息”Tab页下,新增了“自动创建数据权限”选项,且默认开启。启用该功能后,生成的菜单项会自动配置数据权限,并在后台自动生成数据权限控制相关的声明和实现代码。

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

v1.2.6-beta
- 优化: [mysql.yml] - 指定 liquibase 版本表名,兼容不同环境表名大小写,避免版本控制失效。
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 # 增加配置项优化:[ProTable-TableColumn组件] 支持多标签展示
- 支持 tag=false 且 enum 配置时,自动翻译多值并逗号拼接展示。
- 支持 tag=true 且 enum 配置时,多标签分组展示及收起。
- 支持 tag 未设置且 enum 配置时,正常翻译多值展示。
- 兼容多种数据格式(数组、逗号分隔字符串)。
- 空值统一展示为 "--"。
- 新增 tagLimit 属性说明:设置展示标签数量,超出部分通过 Popover 收起展示(tagLimit=-1 时展示全部标签,默认值tagLimit=3)。

v1.2.5-beta
重构:字典加载器相关类的包结构和接口定义,优化动态字典加载逻辑
新增
DynamicDictLoader接口,专用于动态字典处理,规范动态字典加载流程。以
UserOptionDictLoader.java为例,其他相关类依照新接口实现同步调整,具体变动包括:类继承自
DynamicDictLoader,替换原有DictLoader接口;实现
getTypeCode()与getTypeName()等新接口方法,统一字典类型标识与名称的获取方式;优化
loadDict()方法,简化动态字典的缓存与回源逻辑,提升性能和易读性;代码层面将部分依赖
DynamicDictEnum的调用方式进行标准化。
更新
DynamicDictEnum枚举类,移除 typeCode 的dynamic_前缀,由DynamicDictLoader接口自动拼接。
@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);
}
}@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多文件上传组件, 简化使用方式并修复一些已知问题。
上传中:

上传完毕:

<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><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管理数据库脚本
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
结构变动:
└─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版本弃用)
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: truev1.2.0-beta
代码生成忽略表前缀功能
ymlsz: # 生成工具 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
删除 .eslintignore、.eslintrc.cjs,切换Eslint配置为eslint.config.js
遵循Eslint和TypeScript最佳实践,避免使用namespace,切换至type。
切换目录
/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')写法tsimport { 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下的缓存对象,重新登陆即可!
