规范
规范就像软件开发的地图和指南,帮助团队成员在项目旅程中找到正确的方向。它们为代码的组织、命名和风格设立了框架,使得整个团队能够以更加一致的方式合作。通过规范,我们可以避免陷入混乱和混乱的境地,并确保我们的代码库保持清晰、易于理解和可维护。
不要担心! 以下规范看起来有可能比较多,但是我们代码生成器基本都帮助我们实现了。
后端
1. 项目结构
我们以表teacher_statistics为例,其业务代码结构如下:
以后新业务与teacher_statics同理,在com.sz.admin目录下创建业务相关包名,并同时遵循下述结构。
虽然看起来目录结构比较多,但我们不用担心,代码生成器很容易帮我们创建它们,并且可以根据自己的实际需求灵活生成所需文件。
sz-boot-parent/sz-module/sz-module-admin/
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── sz
│ │ │ ├── admin
│ │ │ │ ├── system # rbac系统相关代码等都存放到此包下
│ │ │ │ │ │ ...
│ │ │ │ └── teacher # teacher_statstics 表抽取包名,存放所有和teacher相关的业务
│ │ │ │ ├── controller # controller层
│ │ │ │ │ └── TeacherStatisticsController.java
│ │ │ │ ├── mapper
│ │ │ │ │ └── TeacherStatisticsMapper.java # mybatis mapper
│ │ │ │ ├── pojo # pojo层
│ │ │ │ │ ├── dto
│ │ │ │ │ │ ├── TeacherStatisticsCreateDTO.java # DTO add
│ │ │ │ │ │ ├── TeacherStatisticsImportDTO.java # DTO 导入
│ │ │ │ │ │ ├── TeacherStatisticsListDTO.java # DTO 列表查询条件
│ │ │ │ │ │ └── TeacherStatisticsUpdateDTO.java # DTO update
│ │ │ │ │ ├── po
│ │ │ │ │ │ └── TeacherStatistics.java # PO 数据库实体映射
│ │ │ │ │ └── vo
│ │ │ │ │ └── TeacherStatisticsVO.java
│ │ │ │ └── service # service层
│ │ │ │ ├── impl
│ │ │ │ │ └── TeacherStatisticsServiceImpl.java
│ │ │ │ └── TeacherStatisticsService.java
│ │ └── resources
│ │ ├── ...
│ │ ├── mapper
│ │ │ ├── system
│ │ │ ├── ...
│ │ │ └── teacher # mybatis mapperXml
│ │ │ └── TeacherStatisticsMapper.xml2. 分层
在sz-admin框架中,我们采用DTO、VO和PO来组织POJO类,以优化数据管理和传输:
- DTO(数据传输对象):(Data Transfer Object)专为服务层与控制器间的数据交换设计,仅包含数据属性,不涉及业务逻辑。
- VO(视图对象):定制化数据结构,用于向用户界面传递信息。VO可以基于DTO进行扩展,包含简化或特定格式的数据,并可能集成少量业务逻辑以满足展示需求。
- PO(持久化对象):代表数据库中的持久化数据模型,与数据库表结构直接对应,属性映射数据库字段。
3. 时间格式
在创建数据表时,除非有特定需求,我们通常将时间字段设置为datetime类型,这在Java中对应于LocalDateTime或LocalDate类型。
无需担心时间格式的解析问题,因为我们的框架已经在sz-common模块中的JacksonConfiguration.java[1]类中提供了相应的处理。
4. 主键ID 与全局雪花ID策略
框架规定,所有主键ID(bigint)、关联ID(bigint) 在 Java 中都以 Long 类型进行映射。
ID 生成策略已默认切换为雪花ID(Snowflake),由 sz-common-db-core 中的 SzIdGenerator 统一实现(基于 Hutool Snowflake),并已在 MybatisFlexConfiguration 中注册为全局 KeyGenerator,无需在每个实体上单独配置。
如需在实体上显式声明(推荐,可提升可读性):
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.sz.db.id.SzIdGenerator;
@Table("sys_user")
public class SysUser implements Serializable {
@Id(keyType = KeyType.Generator, value = SzIdGenerator.NAME) // 雪花ID
private Long id;
// ...
}雪花ID 配置项(位于各环境 mybatis-flex.yml):
sz:
id:
worker-id: ${SZ_WORKER_ID:1} # 工作机器ID,范围 0-31
datacenter-id: ${SZ_DATACENTER_ID:1} # 数据中心ID,范围 0-31配置项由 SzSnowflakeProperties 绑定,并在 MybatisFlexConfiguration 中创建全局 Snowflake 实例、注册 SzIdGenerator。完整配置说明见 配置 - mybatis-flex.yml;如果是从旧版本升级,还需要结合 升级指南 - 全局 ID 迁移 处理存量主键和关系表。
生产多节点部署必读
多节点部署时,每个节点必须通过环境变量配置不同的 SZ_WORKER_ID 和 SZ_DATACENTER_ID,否则不同节点可能生成相同 ID,导致主键冲突。
# 节点1
SZ_WORKER_ID=1 SZ_DATACENTER_ID=1
# 节点2
SZ_WORKER_ID=2 SZ_DATACENTER_ID=15. Web API
我们的Web API严格遵循RESTful API设计原则,以确保高效和一致的资源管理。以下是API路由和操作的概览:
如:
| Method | Router | CRUD | 描述 |
|---|---|---|---|
| POST | /teacher-statics | 新增 | 用于创建新的教师统计数据 |
| PUT | /teacher-statics | 修改 | 用于更新教师统计数据。 |
| DELETE | /teacher-statics | 删除 | 用于删除教师统计数据。 |
| GET | /teacher-statics | 查询、分页查询 | 用于检索教师统计数据列表,支持分页查询。 |
| GET | /teacher-statics/{id} | 详情 | 用于获取指定ID的教师统计数据详情。 |
| POST | /teacher-statics/import | 导入 | 用于执行教师统计数据的批量导入。 |
| POST | /teacher-statics/export | 导出 | 用于执行教师统计数据的批量导出。 |
6. Service 接口命名规范
在teacher_staticstics表的业务逻辑中,我们遵循以下接口方法命名规则,以确保代码的清晰和一致性:
public interface TeacherStatisticsService extends IService<TeacherStatistics> {
// 新增
void create(TeacherStatisticsCreateDTO dto);
// 修改
void update(TeacherStatisticsUpdateDTO dto);
// 分页
PageResult<TeacherStatisticsVO> page(TeacherStatisticsListDTO dto);
// 列表(不分页)
List<TeacherStatisticsVO> list(TeacherStatisticsListDTO dto);
// 删除
void remove(SelectIdsDTO dto);
// 详情
TeacherStatisticsVO detail(Long id);
// 导入
void importExcel(MultipartFile file);
// 导出
void exportExcel(TeacherStatisticsListDTO dto, HttpServletResponse response);
}7. OpenAPI 注解
框架使用 Springdoc OpenAPI 3 生成接口元数据,并通过 Swagger UI 提供当前主线接口文档页面。Controller 方法和 POJO 类应适当使用 io.swagger.v3.oas.annotations 相关注解,以确保 API 的自文档化和规范性。
NOTE
v2.0.0 基于 Spring Boot 4.x,Knife4j 暂未作为当前主线适配,实际业务已暂停使用 Knife4j UI。
例如,您可以这样使用注解:
// controller.java
@Tag(name = "教师统计总览表")
public class TeacherStatisticsController {
@Operation(summary = "新增")
public ApiResult create(@RequestBody TeacherStatisticsCreateDTO dto) {
teacherStatisticsService.create(dto);
return ApiResult.success();
}
// 二进制文件上传特殊样式
@Operation(summary = "导入")
@Parameters({
@Parameter(name = "file", description = "上传文件", schema = @Schema(type = "string", format = "binary"), required = true),
})
public void importExcel(MultipartFile file) {
teacherStatisticsService.importExcel(file);
}
// ...
}// DTO.java、PO.java、VO.java
@Data
@Schema(description = "TeacherStatistics添加DTO")
public class TeacherStatisticsCreateDTO {
@Schema(description = "统计年限")
private String year;
@Schema(description = "统计月份")
private String month;
@Schema(description = "统计年月")
private String duringTime;
@Schema(description = "教师id")
private Long teacherId;
@Schema(description = "讲师区分类型")
private Integer teacherCommonType;
@Schema(description = "授课总数")
private Integer totalTeaching;
@Schema(description = "服务班次数")
private Integer totalClassCount;
@Schema(description = "课时总数")
private BigDecimal totalHours;
@Schema(description = "核对状态")
private Integer checkStatus;
@Schema(description = "核对时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime checkTime;
@Schema(description = "最近一次同步时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime lastSyncTime;
@Schema(description = "备注")
private String remark;
}生成 API 文档 UI 如下: 
8. @Autowired 注解
框架中如无特殊情况禁止使用@Autowired注解!!如需引入bean可使用如下两种方式:
- 使用 @RequiredArgsConstructor:在类的构造函数中声明 final 类型的依赖,Lombok 会自动生成构造函数,并自动注入对应的 bean。例如:
@RequiredArgsConstructor
public class TeacherStatisticsController {
private final TeacherStatisticsService teacherStatisticsService;
}- 使用 @Resource 注解:通过
@Resource注解直接注入对应的 bean。例如:
public class TeacherStatisticsController {
@Resource
private TeacherStatisticsService teacherStatisticsService;
}9. 接口鉴权
在我们的框架中,我们强烈建议为Controller接口方法明确指定权限要求,除非有特定的业务需求。这种做法不仅有助于增强系统的安全性,还能确保操作的合规性。
以下是如何使用 @SaCheckPermission 注解来定义接口方法的权限范围的示例代码:
public class SysRoleController {
// 仅允许拥有 `sys.role.create_btn` 权限标识的用户或超级管理员角色访问。
@Operation(summary = "新增角色")
@SaCheckPermission(value = "sys.role.create_btn", orRole = GlobalConstant.SUPER_ROLE)
public ApiResult create(@Valid @RequestBody SysRoleAddDTO dto) {
sysRoleService.create(dto);
return ApiResult.success();
}
// 需要同时具备 `sys.role.setting_btn` 和 `sys.role.update_btn` 权限标识的用户或超级管理员角色才能访问。
@Operation(summary = "查询角色菜单信息")
@SaCheckPermission(value = {"sys.role.setting_btn", "sys.role.update_btn"}, mode = SaMode.AND, orRole = GlobalConstant.SUPER_ROLE)
@GetMapping("/menu")
public ApiResult findRoleMenuByRoleId(@NotZero @RequestParam Long roleId) {
return ApiPageResult.success(sysRoleMenuService.queryRoleMenu(roleId));
}
// 更多方法...
}在实际业务中,我们通常会遇到三种鉴权场景:
- 明确鉴权:对于需要进行权限控制的按钮(或接口),应使用
@SaCheckPermission注解明确指定所需的权限标识。 - 无需鉴权:对于不需要鉴权的接口,如登录接口,可以在Controller上使用
@SaIgnore注解,或在sa-token.yml配置文件中通过router.whitelist配置来实现。 - 有条件的鉴权:对于某些公共查询接口,虽然不需要验证具体的权限标识,但要求用户必须已经登录。在这种情况下,无需在Controller层添加额外的鉴权处理。
WARNING
数据权限的核心功能依赖于@SaCheckPermission注解来实现。为了确保数据权限的正常运行,请确保所有需要实现权限控制的Controller层都正确配置了该注解。这将帮助系统自动识别并应用相应的权限规则。
10. 数据字典常量类使用规范
在后端业务代码中引用数据字典值时,禁止直接硬编码字典项 ID 字符串,必须使用框架提供的字典常量类。
字典常量类位于 sz-module-common 模块的 com.sz.platform.constant.dict 包下,现有常量类如下:
| 常量类 | 对应字典 type_code | 说明 |
|---|---|---|
AccountStatusConstant | account_status | 账户状态(正常/禁用/禁言) |
MenuTypeConstant | menu_type | 菜单类型(目录/菜单/按钮) |
LoginStatusConstant | login_status | 登录状态 |
UserTagConstant | user_tag | 用户标签 |
DataScopeRelationTypeConstant | data_scope_relation_type | 数据权限关联类型(部门/个人) |
反例(禁止):
// ❌ 直接硬编码字典项ID,可读性差,维护成本高
if ("1000001".equals(user.getStatus())) {
// 正常状态处理
}正例(推荐):
import com.sz.platform.constant.dict.AccountStatusConstant;
// ✅ 使用常量类,语义清晰,维护友好
if (AccountStatusConstant.NORMAL.equals(user.getStatus())) {
// 正常状态处理
}TIP
新增业务字典后,建议同步在 com.sz.platform.constant.dict 包下创建对应的常量类,命名规范为 {业务名}Constant,并在类注释中注明对应的 type_code,便于团队统一维护。
11. 全局错误响应与 HTTP 状态码
框架通过 CommonResponseEnum 枚举统一定义所有错误响应,每条错误均绑定一个语义化的 HTTP 状态码,由 GlobalExceptionHandler 统一拦截并返回。
响应结构
所有接口响应统一为 ApiResult 结构:
{
"code": "C100",
"message": "参数校验异常",
"data": null
}成功响应:HTTP 200,code 为 "0000"。
内置错误码对照表
| 枚举名 | 错误码 | HTTP 状态码 | 说明 |
|---|---|---|---|
VALID_ERROR | C100 | 400 Bad Request | 参数校验异常(@Valid 校验失败) |
BAD_USERNAME_OR_PASSWORD | C101 | 401 Unauthorized | 账户不存在或密码错误 |
CNT_PASSWORD_ERR | C102 | 401 Unauthorized | 密码错误次数过多,账户锁定 |
CLIENT_INVALID | C103 | 401 Unauthorized | 无效的 ClientId |
CLIENT_BLOCKED | C104 | 401 Unauthorized | Client 认证已禁用 |
INVALID_TOKEN | C105 | 401 Unauthorized | 无效 Token(未登录 / Token 过期) |
INVALID_USER | C106 | 401 Unauthorized | 无效用户 |
BAD_USERNAME_STATUS_INVALID | C107 | 401 Unauthorized | 用户被禁用 |
INVALID_PERMISSION | C108 | 403 Forbidden | 无权限执行此操作 |
WEBSOCKET_SEND_FAIL | C109 | 500 Internal Server Error | WebSocket 消息发送异常 |
DEBOUNCE | C110 | 429 Too Many Requests | 请求过于频繁(防抖拦截) |
INVALID_ID | C1000 | 400 Bad Request | 无效 ID |
EXISTS | C1001 | 409 Conflict | 数据已存在 |
NOT_EXISTS | C1002 | 404 Not Found | 数据不存在 |
FILE_NOT_EXISTS | C1003 | 404 Not Found | 文件不存在 |
FILE_UPLOAD_EXT_ERROR | C1004 | 400 Bad Request | 文件类型不被允许 |
FILE_UPLOAD_SIZE_ERROR | C1005 | 413 Payload Too Large | 文件超过 10MB |
FILE_UPLOAD_ERROR | C1006 | 400 Bad Request | 上传文件失败 |
USERNAME_EXISTS | C1007 | 409 Conflict | 用户名已存在 |
EXCEL_IMPORT_ERROR | C1009 | 422 Unprocessable Entity | Excel 导入失败 |
CAPTCHA_LIMIT | C1013 | 429 Too Many Requests | 验证码请求达到最大次数 |
LOGIN_LIMIT | C1015 | 429 Too Many Requests | 登录请求达到最大次数 |
UNKNOWN | C9999 | 500 Internal Server Error | 未知异常(兜底) |
自定义业务异常
业务模块可通过实现 ResponseEnumTemplate 接口扩展自己的错误枚举:
import com.sz.core.common.enums.ResponseEnumTemplate;
import com.sz.core.common.enums.ErrorPrefixEnum;
import org.springframework.http.HttpStatus;
public enum TeacherResponseEnum implements ResponseEnumTemplate<TeacherResponseEnum> {
SCHEDULE_CONFLICT(1, "课程时间冲突", HttpStatus.CONFLICT),
TEACHER_NOT_FOUND(2, "教师信息不存在", HttpStatus.NOT_FOUND);
private final int code;
private final String message;
private final HttpStatus httpStatus;
TeacherResponseEnum(int code, String message, HttpStatus httpStatus) {
this.code = code; this.message = message; this.httpStatus = httpStatus;
}
@Override public int getCode() { return code; }
@Override public String getMessage() { return message; }
@Override public HttpStatus httpStatus() { return httpStatus; }
@Override
public ErrorPrefixEnum getCodePrefixEnum() {
return ErrorPrefixEnum.ADMIN; // Admin 业务模块使用 ADMIN 前缀(A)
}
}在 Service 中抛出:
// 方式一:断言式(推荐)
TeacherResponseEnum.TEACHER_NOT_FOUND.assertTrue(teacher != null);
// 方式二:直接抛出
throw new BusinessException(TeacherResponseEnum.SCHEDULE_CONFLICT);NOTE
ErrorPrefixEnum 决定错误码前缀:当前通用错误使用 COMMON("C"),Admin 业务使用 ADMIN("A"),资源模块使用 RESOURCE("R")。新增模块如需独立错误码前缀,应先在枚举中显式维护,避免随意复用历史文档中的 BUSINESS 前缀。
前端
同样,我们以表teacher_statistics为例,其业务代码结构如下:
sz-admin/
└── src
├── api
│ ├── types
│ │ └── teacher
│ │ └── teacherStatistics.ts # 对象类型,类似 Java 实体对象
│ └── modules
│ └── teacher
│ └── teacherStatistics.ts # API 路由,使用 adminHttp
├── modules # 可组合业务模块,适合代码生成器、SSO、商城等较大业务域
│ └── toolbox
│ ├── register.ts # 模块注册入口
│ └── views
│ └── generator
│ └── index.vue
└── views # 普通页面和存量页面
├── system
│ ├── ...
└── teacher # 简单 CRUD 可继续放在 views
└── teacherStatistics
├── components
│ └── TeacherStatisticsForm.vue
└── index.vue普通业务页面可以继续采用 src/api/modules + src/views 的轻量结构;当业务需要独立启用、跨派生项目复用或团队并行维护时,再放入 src/modules/<domain> 并通过 src/core、src/editions 注册。后端 module、前端 module、API 前缀和菜单映射的完整步骤见 独立业务模块接入指南。
2. 按钮鉴权
在我们的开发框架中,对于按钮控件,除非有特定的业务需求,否则必须明确定义它们的权限级别。
例如,以下是使用v-auth指令来指定权限的代码示例:
TIP
v-auth 指令有三种使用方式:
单一权限:
具有指定字符串内的权限
vuev-auth="'sys.menu.create_btn'"多条件AND:
必须同时具有conditions数组包含的权限
vuev-auth="[{ type: 'and', conditions: ['sys.role.setting_btn', 'sys.role.update_btn'] }]"多条件OR:
具有conditions数组内的任一权限
vuev-auth="[{ type: 'or', conditions: ['sys.user.unlock_btn','sys.user.role_set_btn','sys.user.delete_btn' ] }]"
<template>
<div class="table-box">
<ProTable
ref="proTableRef"
title="教师统计"
:indent="20"
:columns="columns"
:search-columns="searchColumns"
:request-api="getTableList"
row-key="id"
>
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<el-button
type="primary"
v-auth="'teacher.statistics.create'"
:icon="CirclePlus"
@click="openAddEdit('新增教师统计')"
>
新增
</el-button>
...
<el-button
v-auth="'teacher.statistics.remove'"
type="primary"
link
:icon="Delete"
@click="deleteInfo(row)"
>
删除
</el-button>
</template>
</ProTable>
</div>
</template>
...3. 代码提交前检查
为了确保项目的长期可持续性和可维护性,我们制定了以下代码提交前的检查流程。请遵循以下步骤,以确保您的代码符合项目标准:
类型检查 (
type-check)在提交代码前,请运行 TypeScript 类型检查,以确保代码的类型安全。避免使用
any类型(“any script编程”),因为这会削弱类型系统的优势。如果在构建阶段发现类型错误,构建过程将失败。shellpnpm run type-check代码风格检查 (
lint)使用 ESLint 进行代码风格检查,确保代码符合项目的风格规范。这有助于保持代码的一致性和可读性。
shellpnpm run lint代码格式化 (
format)在提交之前,对代码进行格式化,以确保其整洁和一致。这有助于减少因格式问题引起的代码审查。
shellpnpm run format
我们推荐使用集成开发环境(IDE)来自动化上述流程。大多数现代编辑器都支持快捷方式或插件来执行这些操作。以下是在 WebStorm 中执行这些操作的示例:

打开package.json 分别运行划线部分左侧头部的运行图标即可。
位于/sz-common/sz-common-core模块下,包com.sz.core.common.configuration ↩︎
