01-代码风格指南.md 11 KB

01-代码风格指南

以下文档在原有代码风格约定基础上,已同步并补充当前项目实际实现的约定(例如:使用 Jakarta 包、异常基类为 RuntimeException、拦截器 token 优先级等)。请以此文档为团队规范。

1. 命名规范

1.1 包名

  • 包名全部使用小写字母,使用点号分隔单词。
  • 示例:work.baiyun.chronicdiseaseapp

1.2 类名和接口名

  • 使用驼峰命名法(CamelCase),首字母大写。
  • 示例:SpringAPPWeChatControllerUserService

1.3 方法名

  • 使用驼峰命名法(camelCase),首字母小写。
  • 示例:getOpenidgenerateTokenaddInterceptors

1.4 变量名和字段名

  • 使用驼峰命名法(camelCase),首字母小写。
  • 示例:weChatServiceusernameauthInterceptor

1.5 常量名

  • 全部大写,使用下划线分隔单词。
  • 示例:TOKEN_TTL_HOURSREFRESH_THRESHOLD_HOURS

1.6 枚举值

  • 全部大写,使用下划线分隔单词。
  • 示例:MALEFEMALE

2. 代码格式

2.1 缩进

  • 使用4个空格进行缩进,不使用制表符(Tab)。

2.2 花括号

  • 类和方法的花括号在声明行的末尾。

2.3 导入语句

  • 导入语句按以下顺序分组:
    1. Java标准库(java.*)
    2. 第三方库(com.*、org.*等)
    3. 项目内部包(work.baiyun.*)
  • 组间用空行分隔。

2.4 行长度

  • 单行代码不超过120个字符,超出时适当换行。

2.5 空格

  • 运算符前后加空格,如 a + b
  • 方法参数之间用逗号加空格分隔,如 method(a, b, c)
  • 控制语句关键字后加空格,如 if (condition)

3. 注释与架构相关约定

3.1 使用的包(Jakarta)

  • 本项目使用 Jakarta 规范包(例如 jakarta.servlet.*, jakarta.validation.*),而非旧的 javax.* 命名空间。

3.2 类注释

  • 使用Javadoc注释 /** */
  • 示例:

    /**
    * 性别枚举,数据库存储为整数:1=男, 2=女
    */
    public enum Gender { ... }
    

3.3 方法注释

  • 公共方法使用Javadoc注释并描述入参、返回值与异常。

3.4 字段注释

  • 使用 /** */ 注释字段,描述字段含义并可配合 @Schema 等注解提供文档。

3.5 Lombok 与简化样板代码

  • 项目使用 Lombok(例如 @Getter, @Setter, @RequiredArgsConstructor 等)以减少样板代码。

4. 关键实现约定(来自代码库)

以下约定是从当前代码实现中抽取的,文档应与代码保持一致。

4.1 枚举约定

  • 枚举使用 @EnumValue(MyBatis-Plus)标记用于数据库存储的值。
  • 枚举应提供 getCode()getDescription() 方法;通常还包含 fromCode(int) 的静态方法用于反序列化,以及 toString() 返回描述字符串。
  • 示例(摘自 work.baiyun.chronicdiseaseapp.enums.Gender):

    public enum Gender {
      MALE(1, "男"),
      FEMALE(2, "女");
    
      @EnumValue
      private final int code;
      private final String description;
    
      public int getCode() { return code; }
      public String getDescription() { return description; }
    
      public static Gender fromCode(int code) { ... }
    }
    

4.2 分页对象

  • 项目使用 com.github.pagehelper.PageInfo 来计算总数,分页返回封装类为 work.baiyun.chronicdiseaseapp.common.Page<T>,并使用 Lombok 的 @Getter/@Setter

4.3 异常基类与全局处理

  • 自定义业务异常 CustomException 继承自 RuntimeException(运行时异常),由全局异常处理器 CustomExceptionHandler 处理。
  • 全局异常处理器使用 @RestControllerAdvice,并处理如下异常类型:
    • BindException(参数绑定错误)
    • ConstraintViolationException(参数校验错误)
    • CustomException(业务异常)
    • Exception(未捕获的通用异常)
  • 处理器将错误信息统一返回项目内定义的响应封装 work.baiyun.chronicdiseaseapp.common.R,并使用 work.baiyun.chronicdiseaseapp.enums.ErrorCode 中的 code/msg。

4.4 拦截器(AuthInterceptor)行为约定

  • 拦截器优先解析标准 Authorization 头:
    • Authorization: Bearer <token> (优先,且忽略大小写的 Bearer 前缀)
    • Authorization 未提供 token,则回退到 X-Token header 或 token header
    • 若无 token,返回 401(HttpStatus.UNAUTHORIZED)并拒绝请求
  • 拦截器允许 CORS 预检(OPTIONS)请求直接通过。
  • 验证通过后,将 currentUserId(用户 id)和 currentUserRole(权限组,PermissionGroup)放入 HttpServletRequest 的 attribute 中以供 Controller 使用。

示例摘录(来自 work.baiyun.chronicdiseaseapp.config.AuthInterceptor)中的行为:

// 优先处理 Authorization: Bearer <token>
String authHeader = request.getHeader("Authorization");
String xToken = request.getHeader("X-Token");
String tokenHeader = request.getHeader("token");
// 解析逻辑:优先 Authorization(支持 Bearer),回退 X-Token,再回退 token

4.5 WebMvc 配置

  • WebMvcConfig 使用构造函数注入 AuthInterceptor(带 @Autowired 构造器),并在 addInterceptors 中注册拦截器。
  • 默认拦截所有路径 /**,但排除登录/Swagger/OpenAPI 相关路径,示例排除路径列表:/, /get_openid, /v3/api-docs/**, /swagger-ui/**, /doc.html, /webjars/**, /favicon.ico 等。

4.6 主启动类约定

  • 主类 SpringAPP 使用 @EnableKnife4j 启用 Knife4j(API 文档增强),并在 @SpringBootApplication 中排除 ThymeleafAutoConfiguration(项目未使用 Thymeleaf)。

4.6 PermissionGroup(权限组)

  • 项目定义了 work.baiyun.chronicdiseaseapp.enums.PermissionGroup,用于表示用户权限组并用于访问控制。
  • 枚举值与代码实现如下:
    • SYS_ADMIN(1, "管理员")
    • DOCTOR(2, "医生")
    • PATIENT(3, "患者")
    • PATIENT_FAMILY(4, "患者家属")
  • 每个枚举包含 @EnumValue 标记的 code(用于数据库存储)和 description,并提供 getCode()getDescription()fromCode(int) 方法。

4.7 统一响应封装(R

  • 项目通过 work.baiyun.chronicdiseaseapp.common.R<T> 作为统一响应体。核心字段:
    • Integer code:响应码
    • String message:提示消息
    • T data:响应主体数据
  • 常用静态构造方法(实现来自代码):
    • R.success(Integer code, String message) 返回无数据的成功响应
    • R.success(Integer code, String message, T data) 返回带数据的成功响应
    • R.fail(Integer code, String message) 返回无数据的失败响应
    • R.fail(Integer code, String message, T data) 返回带数据的失败响应

4.8 TokenService(令牌服务)

  • work.baiyun.chronicdiseaseapp.service.TokenService 是令牌管理的接口,重要方法:
    • String createToken(Long userId):为用户生成并保存新的 token,返回 token 字符串(通常在登录或刷新时调用)。
    • AuthPrincipal validateToken(String token):校验 token 并返回 AuthPrincipal(包含 userIdPermissionGroup);若 token 无效或过期返回 null。实现中会处理接近过期时的续期逻辑(例如小于 24 小时时延长有效期)。
    • void revokeToken(String token):撤销 token(例如登出时使用)。

5. 异常与日志记录约定

  • 对未捕获异常进行日志记录(使用 SLF4J 的 Logger),并在响应中返回通用错误码与信息;针对参数校验返回精确的校验错误消息列表。

6. 其他约定与建议

  • 构造器注入优先于字段注入。
  • 在控制器/服务中尽量使用 AuthPrincipal/currentUserId 作为当前请求用户的来源,而不是重新解析 token。
  • 如果文档中的旧条目(如"自定义异常继承 Exception")与上面实际实现冲突,以代码为准并及时修正文档。

Swagger 注解与接口文档约定(补充)

为保证生成的 API 文档准确且易读,接口方法应遵循以下注解约定:

  • 对于返回泛型包装类(如 R<T>ResponseEntity<T>)的接口,在 Controller 方法上显式使用 @Operation / @ApiResponse 并通过 @Content + @Schema(implementation = Xxx.class) 指定实际返回类型。避免依赖 Swagger 在运行时推断泛型。
  • 对每个公开响应字段在 DTO/VO 中使用 @Schema(description = "...") 注解,确保字段说明在文档中显示。
  • 例子(局部示例):

    @Operation(summary = "获取用户")
    @ApiResponse(responseCode = "200", description = "OK",
      content = @Content(schema = @Schema(implementation = UserResponse.class)))
    public R<?> getUser(...) { ... }
    

此约定应作为代码审查项之一,确保接口文档与实现同步。


文档已根据仓库中实际实现同步关键点,若需要我可以:

  • 将其它规则(例如导入分组、更多示例)补充完整;
  • 或者生成一份 PR 描述,列出文档修改与代码不一致的具体代码位置。

7. 快速示例(可复制到 Controller)

下面的片段示例展示如何在 Controller 中读取由 AuthInterceptor 放入的当前用户信息,并返回基于 R 的响应:

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import work.baiyun.chronicdiseaseapp.common.R;

@RestController
public class ExampleController {
  @GetMapping("/me")
  public R<Long> getCurrentUser(HttpServletRequest request) {
    Long currentUserId = (Long) request.getAttribute("currentUserId");
    if (currentUserId == null) {
      return R.fail(401, "未登录");
    }
    return R.success(200, "ok", currentUserId);
  }
}

VO/DTO 映射与 ID 类型处理(补充)

为避免把数据库实体类型直接暴露给前端,建议在映射层(Service -> VO/DTO)统一处理 ID 字段类型转换。常见做法:

  • 在实体(PO)中使用 Long/Long 类型表示 ID,在响应 VO 中使用 String 类型;映射时显式将 id 转为字符串。
  • 推荐示例(简洁版):

    PhysicalDataResponse r = new PhysicalDataResponse();
    BeanUtils.copyProperties(record, r);
    if (record.getId() != null) {
      r.setId(record.getId().toString());
    }
    
  • 若团队引入 MapStruct 等映射框架,可在 mapper 配置中添加转换规则(例如 @Mapping(target = "id", expression = "java(String.valueOf(record.getId()))")),并在 05-依赖管理规范.md 中记录所用版本作为依赖约定。

实现约定:

  • 映射逻辑放在 Service 的转换方法或单独的 Mapper 类中,避免 Controller/Repository 直接操作类型转换。
  • 映射示例应写入代码风格指南,作为团队的示例片段,便于审查与复用。