开发规范
TIP
规定后端日常开发中的编码风格、分层写法、异常处理、事务管理等约定,适用于 Spring Boot 3 + MyBatis-Plus + Guava 平台 技术栈。
设计原则
| 原则 | 说明 |
|---|---|
| 分层清晰 | Controller → Service → Repository,禁止跨层调用 |
| 接口与实现分离 | Service 定义接口,impl/ 包下提供实现 |
| 统一返回 | 所有接口返回 RestResult 或 IPage,禁止直接返回 PO |
| 异常统一 | 业务异常通过 BizException 抛出,全局处理器统一处理 |
| 安全优先 | 参数校验、防注入、敏感信息脱敏 |
技术栈约定
| 类别 | 选型 | 说明 |
|---|---|---|
| 框架 | Spring Boot 3.5.14 | Java 25,Spring 生态 |
| ORM | MyBatis-Plus 3.5.16 | 继承 DBMapper<T> |
| 缓存 | Redis 8.8.0 + Caffeine | 二级缓存架构 |
| 消息队列 | RabbitMQ 4.3.2 | Spring AMQP |
| 注册/配置 | Nacos 2025.0.0.0 | 服务注册 + 配置中心 |
| 限流 | Sentinel 2025.0.0.0 | URL 限流、熔断 |
| API 文档 | Springdoc OpenAPI 2.8.8 | Knife4j 4.5.0 |
| 工具库 | Hutool 5.8.46 | 字符串、日期、集合工具 |
| 日志 | SLF4J + Logback | 占位符形式 |
代码风格
通用
- 缩进 4 空格,UTF-8 编码
- 语句末尾加分号
- 单行长度建议不超过 120 字符
- 使用 Lombok 简化 POJO 代码(
@Data、@Slf4j)
类结构顺序
java
@Tag(name = "admin-sysuser")
@RestController
@RequestMapping("/sysuser/")
@Slf4j
public class SysUserController {
// 1. 常量(static final)
private static final String MODULE_NAME = "用户管理";
// 2. 依赖注入(@Resource / @Autowired)
@Resource
private SysUserService sysUserService;
// 3. 公开接口
@PostMapping("/findUsers")
public IPage<Map<String, Object>> findUsers(@RequestBody String params) { }
// 4. 私有方法
private void validateParams(Map<String, Object> param) { }
}Controller 规范
基本结构
java
@Tag(name = "admin-sysuser")
@RestController
@RequestMapping("/sysuser/")
@Slf4j
public class SysUserController {
@Resource
SysUserService sysUserService;
@Operation(summary = "查询->全部用户信息", description = "查询全部用户信息")
@PostMapping(value = "/findUsers")
public IPage<Map<String, Object>> findUsers(
@Parameter(description = "查询条件参数", required = true)
@RequestBody String params) {
Page<Map<String, Object>> page = new Page<>();
String conditions = MyUtil.getConditionsWhere(params, page);
return sysUserService.findUsers(page, conditions, deptId);
}
}接口约定
| 约定 | 说明 |
|---|---|
| 路径风格 | RESTful,模块前缀 + 操作(/sysuser/findUsers) |
| 请求格式 | JSON(@RequestBody) |
| 分页参数 | 通过 JSON 内部 pageNo / pageSize 传递 |
| 返回格式 | IPage<Map<String, Object>>(列表)或 Map<String, Object>(单笔) |
| 参数校验 | 使用 AssertMyUtil.notNull() / notEmpty() 校验 |
禁止事项
- 禁止 Controller 中写业务逻辑(仅做参数接收、校验、调用 Service)
- 禁止 Controller 直接调用 Mapper
- 禁止 返回 PO 实体(应返回 DTO 或 Map)
Service 规范
接口定义
java
public interface SysUserService {
IPage<Map<String, Object>> findUsers(Page<Map<String, Object>> page, String qc, String deptId);
Map<String, Object> saveUser(Map<String, Object> param);
Map<String, Object> updateUser(Long id, Map<String, Object> param);
}实现类
java
@Service
@Slf4j
public class SysUserServiceImpl implements SysUserService {
@Resource
private SysUserMapper sysUserMapper;
@Override
public IPage<Map<String, Object>> findUsers(Page<Map<String, Object>> page, String conditions, String deptId) {
// 业务逻辑
}
}事务管理
- 单表操作无需显式事务
- 多表/多服务调用使用
@Transactional:
java
@Transactional(rollbackFor = Exception.class)
public void saveUserWithRoles(SysUserPO user, List<Long> roleIds) {
sysUserMapper.insert(user);
sysUserRoleMapper.batchInsert(user.getId(), roleIds);
}Repository 规范
Mapper 接口
继承 DBMapper<T>(Guava 平台封装的 MyBatis-Plus BaseMapper):
java
@Repository
public interface SysUserMapper extends DBMapper<SysUserPO> {
@SelectProvider(SysUserSqlProvider.class)
IPage<Map<String, Object>> findUsers(Page<Map<String, Object>> page, String conditions);
@SelectProvider(SysUserSqlProvider.class)
Map<String, Object> selectUserInfoById(String userId);
}SQL Provider
复杂动态 SQL 使用 Provider 类:
java
public class SysUserSqlProvider implements ProviderMethodResolver {
public static String findUsers(Page<Map<String, Object>> page, String conditions) {
StringBuffer sql = new StringBuffer();
sql.append("SELECT u.id, u.account, u.user_name userName ");
sql.append("FROM sys_user u ");
sql.append("WHERE ").append(conditions);
sql.append(" AND u.del_flag = 0");
return sql.toString();
}
}分页查询
统一使用 MyBatis-Plus Page + IPage:
java
@PostMapping("/findUsers")
public IPage<Map<String, Object>> findUsers(@RequestBody String params) {
Page<Map<String, Object>> page = new Page<>();
String conditions = MyUtil.getConditionsWhere(params, page);
return sysUserService.findUsers(page, conditions, deptId);
}数据模型规范
持久化对象(PO)
所有 PO 继承 BasePO,自动填充审计字段:
java
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_user")
public class SysUserPO extends BasePO {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("account")
private String account;
}BasePO 公共字段
| 字段 | 类型 | 说明 | 自动填充 |
|---|---|---|---|
id | Long | 主键 | 数据库自增 |
createBy | String | 创建人 | MetaObjectHandler |
createTime | LocalDateTime | 创建时间 | MetaObjectHandler |
lastUpdateBy | String | 最后更新人 | MetaObjectHandler |
lastUpdateTime | LocalDateTime | 最后更新时间 | MetaObjectHandler |
delFlag | Integer | 删除标记(0=正常,1=删除) | 默认 0 |
status | Integer | 状态(0=启用,1=禁用) | 默认 0 |
version | Integer | 乐观锁版本号 | 自动递增 |
数据传输对象(DTO)
服务间数据传输使用 DTO,继承 BaseDTO:
java
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(name = "sys_user", description = "用户管理")
public class SysUserDTO extends BaseDTO {
@Schema(description = "编号")
private Long id;
}软删除
项目采用 SQL 级软删除(WHERE del_flag = 0),非 @TableLogic 注解方式。所有查询需在 SQL 中显式加上 del_flag = 0 条件。
乐观锁
通过 version 字段实现乐观锁,更新时自动校验版本号。
异常处理
统一异常处理器
java
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public RestResult<?> handleBizException(BizException e) {
return RestResult.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public RestResult<?> handleException(Exception e) {
log.error("服务器异常", e);
return RestResult.fail(BizCode.SERVER_ERROR);
}
}业务异常码(BizCode)
通过 BizCode 枚举统一管理业务错误码:
java
// 通用
REQUEST_NULL(10001, "请求有错误")
SERVER_ERROR(10004, "服务器异常")
// 登录认证
AUTH_TOKEN_INVALID(23002, "token校验失败:token无效")
AUTH_TOKEN_TIMEOUT(23003, "token过期:请重新登录")异常响应格式
业务异常统一返回 HTTP 200,通过 body 区分成功/失败:
json
{
"code": 23003,
"status": "fail",
"message": "token过期:请重新登录",
"datas": null
}参数校验
使用 AssertMyUtil 进行参数校验:
java
AssertMyUtil.notNull(param, BizCode.VARIABLE_NOT_EMPTY, "param");
AssertMyUtil.notEmpty(userId, BizCode.METHOD_ARGS_NOTNULL, "userId");
AssertMyUtil.isNull(result, BizCode.VARIABLE_NOT_EMPTY, "result");日志规范
日志级别
| 级别 | 使用场景 |
|---|---|
| DEBUG | 开发调试(仅 dev 环境输出) |
| INFO | 关键业务节点(登录、数据变更) |
| WARN | 可恢复的异常(缓存 miss、重试) |
| ERROR | 不可恢复异常(需人工介入) |
日志格式
java
// ✅ 推荐:占位符形式,避免字符串拼接
log.info("用户登录成功, userId={}, account={}", userId, account);
// ✅ 推荐:异常记录完整堆栈
log.error("查询用户失败, account={}", account, e);
// ❌ 不推荐:字符串拼接
log.info("用户登录成功, userId=" + userId);操作日志
使用 @MarkLog 注解记录操作日志到数据库:
java
@MarkLog(value = "新增用户-[{0}]", param = "account")
@PostMapping(value = "/saveUser")
public Map<String, Object> saveUser(@RequestBody Map<String, Object> param) {
// ...
}配置规范
环境隔离
| Profile | 用途 |
|---|---|
dev | 本地开发,开启 DEBUG 日志、API 文档 |
uat | 预发布验证 |
prod | 生产运行,关闭 API 文档 |
Nacos 配置
数据库、Redis 等敏感配置托管在 Nacos 远程配置中心:
yaml
spring:
config:
import:
- optional:nacos:common.yml?group=guava&refreshEnabled=true
- optional:nacos:admin.yml?group=guava&refreshEnabled=true安全约定
- 禁止将密码、密钥写入源码或提交到仓库
- 用户密码使用
PasswordEncoder加密存储 - 接口需 Token 校验(
@CheckToken/@NoCheckToken) - 敏感信息(手机号、身份证)展示时脱敏
- SQL 使用参数拼接时注意防注入
反例与正例
分层违规
java
// ❌ Controller 直接调用 Mapper
@PostMapping("/findUsers")
public IPage findUsers(@RequestBody String params) {
return sysUserMapper.findUsers(page, conditions); // 跳过 Service
}
// ✅ Controller → Service → Mapper
@PostMapping("/findUsers")
public IPage findUsers(@RequestBody String params) {
return sysUserService.findUsers(page, conditions, deptId);
}返回 PO 实体
java
// ❌ 直接返回 PO,暴露数据库结构
@GetMapping("/getUser")
public SysUserPO getUser(@RequestParam Long id) {
return sysUserMapper.selectById(id);
}
// ✅ 返回 DTO 或 Map
@GetMapping("/getUser")
public Map<String, Object> getUser(@RequestParam Long id) {
return sysUserService.getUserInfoById(id);
}字符串拼接日志
java
// ❌ 字符串拼接
log.info("用户登录成功, userId=" + userId + ", account=" + account);
// ✅ 占位符形式
log.info("用户登录成功, userId={}, account={}", userId, account);异常吞没
java
// ❌ 捕获异常后无任何处理
try {
sysUserService.saveUser(param);
} catch (Exception e) {
// 空的
}
// ✅ 捕获异常后记录日志或抛出业务异常
try {
sysUserService.saveUser(param);
} catch (Exception e) {
log.error("保存用户失败, param={}", param, e);
throw new BizException(BizCode.SERVER_ERROR);
}Code Review 要点
- 目录与命名符合 目录规范 / 命名规范
- 分层清晰,无跨层调用
- 对外 API 有 Javadoc 和 OpenAPI 注解,见 注释规范
- 参数校验完整(
AssertMyUtil) - 异常处理规范,无吞没异常
- 日志使用占位符形式
- 无硬编码敏感信息
- 事务使用正确(
@Transactional)
开发 Checklist
功能开发完成、提交前确认:
- 功能符合需求,边界情况已考虑(空数据、无权限)
- 分层调用正确(Controller → Service → Repository)
- 参数校验完整
- 异常处理规范
- 日志输出合理
- 无遗留
TODO/ 调试代码(或已关联任务) - API 文档注解完整