# 01-代码风格指南 以下文档在原有代码风格约定基础上,已同步并补充当前项目实际实现的约定(例如:使用 Jakarta 包、异常基类为 RuntimeException、拦截器 token 优先级等)。请以此文档为团队规范。 ## 1. 命名规范 ### 1.1 包名 - 包名全部使用小写字母,使用点号分隔单词。 - 示例:`work.baiyun.chronicdiseaseapp` ### 1.2 类名和接口名 - 使用驼峰命名法(CamelCase),首字母大写。 - 示例:`SpringAPP`、`WeChatController`、`UserService` ### 1.3 方法名 - 使用驼峰命名法(camelCase),首字母小写。 - 示例:`getOpenid`、`generateToken`、`addInterceptors` ### 1.4 变量名和字段名 - 使用驼峰命名法(camelCase),首字母小写。 - 示例:`weChatService`、`username`、`authInterceptor` ### 1.5 常量名 - 全部大写,使用下划线分隔单词。 - 示例:`TOKEN_TTL_HOURS`、`REFRESH_THRESHOLD_HOURS` ### 1.6 枚举值 - 全部大写,使用下划线分隔单词。 - 示例:`MALE`、`FEMALE` ## 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注释 `/** */`。 - 示例: ```java /** * 性别枚举,数据库存储为整数: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`): ```java 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`,并使用 Lombok 的 `@Getter`/`@Setter`。 ### 4.3 异常基类与全局处理 - 自定义业务异常 `CustomException` 继承自 `RuntimeException`(运行时异常),由全局异常处理器 `CustomExceptionHandler` 处理。 - 全局异常处理器使用 `@RestControllerAdvice`,并处理如下异常类型: - `BindException`(参数绑定错误) - `ConstraintViolationException`(参数校验错误) - `CustomException`(业务异常) - `Exception`(未捕获的通用异常) - 处理器将错误信息统一返回项目内定义的响应封装 `work.baiyun.chronicdiseaseapp.common.R`,并使用 `work.baiyun.chronicdiseaseapp.enums.ExceptionResultCode` 中的 code/msg。 ### 4.4 拦截器(AuthInterceptor)行为约定 - 拦截器优先解析标准 Authorization 头: - `Authorization: Bearer ` (优先,且忽略大小写的 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`)中的行为: ```java // 优先处理 Authorization: Bearer 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` 作为统一响应体。核心字段: - `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`(包含 `userId` 与 `PermissionGroup`);若 token 无效或过期返回 `null`。实现中会处理接近过期时的续期逻辑(例如小于 24 小时时延长有效期)。 - `void revokeToken(String token)`:撤销 token(例如登出时使用)。 ## 5. 异常与日志记录约定 - 对未捕获异常进行日志记录(使用 SLF4J 的 Logger),并在响应中返回通用错误码与信息;针对参数校验返回精确的校验错误消息列表。 ## 6. 其他约定与建议 - 构造器注入优先于字段注入。 - 在控制器/服务中尽量使用 `AuthPrincipal`/`currentUserId` 作为当前请求用户的来源,而不是重新解析 token。 - 如果文档中的旧条目(如“自定义异常继承 Exception”)与上面实际实现冲突,以代码为准并及时修正文档。 --- 文档已根据仓库中实际实现同步关键点,若需要我可以: - 将其它规则(例如导入分组、更多示例)补充完整; - 或者生成一份 PR 描述,列出文档修改与代码不一致的具体代码位置。 ## 7. 快速示例(可复制到 Controller) 下面的片段示例展示如何在 Controller 中读取由 `AuthInterceptor` 放入的当前用户信息,并返回基于 `R` 的响应: ```java 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 getCurrentUser(HttpServletRequest request) { Long currentUserId = (Long) request.getAttribute("currentUserId"); if (currentUserId == null) { return R.fail(401, "未登录"); } return R.success(200, "ok", currentUserId); } } ```