瀏覽代碼

docs(devrule): 更新错误码体系和API设计规范

- 将ExceptionResultCode枚举替换为ErrorCode枚举
- 新增完整的错误码分类和设计原则说明
- 规范控制器中错误码的使用方式
- 统一API响应格式和状态码定义
- 完善Swagger注解描述和响应示例
- 明确异常处理和错误响应的一致性要求
mcbaiyun 1 月之前
父節點
當前提交
8e532f6665

+ 3 - 3
docs/DevRule/01-代码风格指南.md

@@ -109,7 +109,7 @@
   - `ConstraintViolationException`(参数校验错误)
   - `CustomException`(业务异常)
   - `Exception`(未捕获的通用异常)
-- 处理器将错误信息统一返回项目内定义的响应封装 `work.baiyun.chronicdiseaseapp.common.R`,并使用 `work.baiyun.chronicdiseaseapp.enums.ExceptionResultCode` 中的 code/msg。
+- 处理器将错误信息统一返回项目内定义的响应封装 `work.baiyun.chronicdiseaseapp.common.R`,并使用 `work.baiyun.chronicdiseaseapp.enums.ErrorCode` 中的 code/msg。
 
 ### 4.4 拦截器(AuthInterceptor)行为约定
 - 拦截器优先解析标准 Authorization 头:
@@ -167,7 +167,7 @@ String tokenHeader = request.getHeader("token");
 ## 6. 其他约定与建议
 - 构造器注入优先于字段注入。
 - 在控制器/服务中尽量使用 `AuthPrincipal`/`currentUserId` 作为当前请求用户的来源,而不是重新解析 token。
-- 如果文档中的旧条目(如“自定义异常继承 Exception”)与上面实际实现冲突,以代码为准并及时修正文档。
+- 如果文档中的旧条目(如"自定义异常继承 Exception")与上面实际实现冲突,以代码为准并及时修正文档。
 
 ---
 
@@ -195,4 +195,4 @@ public class ExampleController {
     return R.success(200, "ok", currentUserId);
   }
 }
-```
+```

+ 4 - 4
docs/DevRule/03-API设计规范.md

@@ -62,11 +62,11 @@
 2. **失败状态码**:
    - 400:参数校验异常、请求参数错误
    - 500:服务器内部错误
-   - 具体业务失败码定义在`FailResultCode`枚举中
+   - 具体业务失败码定义在`ErrorCode`枚举中
 
 3. **异常状态码**:
-   - 定义在`ExceptionResultCode`枚举中
-   - 400:参数校验异常、系统异常
+   - 定义在`ErrorCode`枚举中
+   - 包括各种业务相关的错误码,如参数错误、资源不存在等
 
 ## 认证与授权
 1. **认证方式**:
@@ -151,4 +151,4 @@
 ## 测试规范
 - API接口应编写单元测试和集成测试
 - 测试应覆盖正常流程和异常情况
-- 使用Mock框架模拟依赖服务
+- 使用Mock框架模拟依赖服务

+ 97 - 3
docs/DevRule/06-日志和错误处理规范.md

@@ -70,13 +70,107 @@
   }
   ```
 
-### 错误响应格式
+### 错误码体系
 
-- 使用 `R.fail(code, message)` 返回错误响应。
-- 错误码定义在 `ExceptionResultCode` 枚举中。
+#### 错误码设计原则
+
+1. **统一性**: 所有错误码在项目中保持统一格式和规范
+2. **可读性**: 错误码具有明确的含义和业务指向
+3. **可扩展性**: 错误码体系支持模块化扩展
+4. **兼容性**: 与现有系统和API保持兼容
+
+#### 错误码结构
+
+错误码采用数字编码方式,每个错误码由5位数字组成:
+- 第1位:错误级别(1-通用错误,2-用户相关,3-微信相关,4-数据相关,5-地理位置相关)
+- 第2-5位:具体错误编号
+
+#### 错误码分类
+
+##### 通用错误码 (1000-1999)
+
+| 错误码 | 错误信息 | 说明 |
+|--------|---------|------|
+| 1000 | 系统内部错误 | 未预期的系统错误 |
+| 1001 | 参数错误 | 请求参数不合法或缺失 |
+| 1002 | 未授权访问 | 缺少有效的身份认证信息 |
+| 1003 | 权限不足 | 当前用户无权执行该操作 |
+| 1004 | 资源不存在 | 请求的资源未找到 |
+| 1005 | 请求方法不被允许 | 使用了不支持的HTTP方法 |
+
+##### 用户相关错误码 (2000-2999)
+
+| 错误码 | 错误信息 | 说明 |
+|--------|---------|------|
+| 2000 | 用户不存在 | 指定的用户ID找不到对应用户 |
+| 2001 | 用户密码错误 | 提供的用户密码不正确 |
+| 2002 | 用户未登录 | 用户会话已过期或未登录 |
+| 2003 | 用户已存在 | 尝试创建已存在的用户 |
+| 2004 | 用户账户被锁定 | 用户账户因安全原因被锁定 |
+
+##### 微信相关错误码 (3000-3999)
+
+| 错误码 | 错误信息 | 说明 |
+|--------|---------|------|
+| 3000 | 微信授权码无效 | 提供的微信code参数无效或已过期 |
+| 3001 | 获取微信openid失败 | 调用微信接口获取openid时失败 |
+| 3002 | 用户角色不能为空 | 创建用户时未指定角色类型 |
+| 3003 | 用户角色无效 | 提供的用户角色代码不合法 |
+
+##### 数据相关错误码 (4000-4999)
+
+| 错误码 | 错误信息 | 说明 |
+|--------|---------|------|
+| 4000 | 数据不存在 | 指定的数据记录未找到 |
+| 4001 | 无权访问该数据 | 当前用户无权访问指定的数据 |
+| 4002 | 数据删除失败 | 执行数据删除操作时发生错误 |
+| 4003 | 数据保存失败 | 执行数据保存操作时发生错误 |
+
+##### 地理位置相关错误码 (5000-5999)
+
+| 错误码 | 错误信息 | 说明 |
+|--------|---------|------|
+| 5000 | 地理位置请求失败 | 调用地理位置服务时发生错误 |
+| 5001 | 地理位置服务不可用 | 地理位置服务暂时不可用 |
+
+#### 错误码使用规范
+
+##### 错误码枚举类
+
+项目中使用`ErrorCode`枚举类来定义和管理所有错误码,位于`work.baiyun.chronicdiseaseapp.enums.ErrorCode`。
+
+##### 控制器中使用错误码
+
+所有控制器在返回错误响应时,应使用统一的[R.fail()](file:///D:/慢病APP/other/EmulatedProject/src/main/java/work/baiyun/chronicdiseaseapp/common/R.java#L30-L35)方法,并传入相应的错误码和错误信息:
+
+```java
+// 正确用法
+return R.fail(ErrorCode.PARAMETER_ERROR.getCode(), 
+              ErrorCode.PARAMETER_ERROR.getMessage() + ": Missing code parameter");
+
+// 错误用法(不推荐)
+return R.fail(400, "Missing code parameter");
+```
+
+##### 异常处理中使用错误码
+
+在全局异常处理器或控制器的try-catch块中,应根据具体的异常类型返回相应的错误码:
+
+```java
+try {
+    // 业务逻辑
+} catch (IllegalArgumentException e) {
+    return R.fail(ErrorCode.PARAMETER_ERROR.getCode(), 
+                  ErrorCode.PARAMETER_ERROR.getMessage() + ": " + e.getMessage());
+} catch (Exception e) {
+    return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), 
+                  ErrorCode.SYSTEM_ERROR.getMessage() + ": " + e.getMessage());
+}
+```
 
 ### 异常处理原则
 
 - 业务逻辑中的异常应抛出 `CustomException` 或记录日志后处理。
 - 全局异常处理器确保所有未捕获异常被统一处理。
 - 异常信息不应暴露敏感数据给客户端。
+- 所有控制器应统一使用R类返回错误响应,避免直接抛出异常,确保API响应格式的一致性。

+ 29 - 1
src/main/java/work/baiyun/chronicdiseaseapp/controller/BloodGlucoseDataController.java

@@ -8,6 +8,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
 import work.baiyun.chronicdiseaseapp.common.R;
 import work.baiyun.chronicdiseaseapp.model.vo.AddBloodGlucoseDataRequest;
 import work.baiyun.chronicdiseaseapp.model.vo.BloodGlucoseDataResponse;
@@ -25,6 +29,14 @@ public class BloodGlucoseDataController {
     private BloodGlucoseDataService service;
 
     @Operation(summary = "添加血糖数据", description = "添加血糖测量记录,包含测量类型和值")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "血糖数据添加成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/add", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> add(@RequestBody AddBloodGlucoseDataRequest req) {
         try {
@@ -36,6 +48,14 @@ public class BloodGlucoseDataController {
     }
 
     @Operation(summary = "分页查询血糖数据", description = "根据时间范围和分页参数查询血糖记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功查询血糖数据列表",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Page.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/list", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> list(@RequestBody BaseQueryRequest req) {
         try {
@@ -47,6 +67,14 @@ public class BloodGlucoseDataController {
     }
 
     @Operation(summary = "删除血糖数据", description = "根据ID删除血糖测量记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "血糖数据删除成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/delete", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> delete(@RequestBody DeleteBloodGlucoseDataRequest req) {
         try {
@@ -56,4 +84,4 @@ public class BloodGlucoseDataController {
             return R.fail(500, e.getMessage());
         }
     }
-}
+}

+ 29 - 1
src/main/java/work/baiyun/chronicdiseaseapp/controller/BloodPressureDataController.java

@@ -8,6 +8,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
 import work.baiyun.chronicdiseaseapp.common.R;
 import work.baiyun.chronicdiseaseapp.model.vo.AddBloodPressureDataRequest;
 import work.baiyun.chronicdiseaseapp.model.vo.BloodPressureDataResponse;
@@ -25,6 +29,14 @@ public class BloodPressureDataController {
     private BloodPressureDataService service;
 
     @Operation(summary = "添加血压数据", description = "添加收缩压和舒张压的测量记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "血压数据添加成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/add", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> add(@RequestBody AddBloodPressureDataRequest req) {
         try {
@@ -36,6 +48,14 @@ public class BloodPressureDataController {
     }
 
     @Operation(summary = "分页查询血压数据", description = "根据时间范围和分页参数查询血压记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功查询血压数据列表",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Page.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/list", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> list(@RequestBody BaseQueryRequest req) {
         try {
@@ -47,6 +67,14 @@ public class BloodPressureDataController {
     }
 
     @Operation(summary = "删除血压数据", description = "根据ID删除血压测量记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "血压数据删除成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/delete", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> delete(@RequestBody DeleteBloodPressureDataRequest req) {
         try {
@@ -56,4 +84,4 @@ public class BloodPressureDataController {
             return R.fail(500, e.getMessage());
         }
     }
-}
+}

+ 17 - 3
src/main/java/work/baiyun/chronicdiseaseapp/controller/GeoController.java

@@ -8,8 +8,13 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.client.RestTemplate;
 import work.baiyun.chronicdiseaseapp.common.R;
+import work.baiyun.chronicdiseaseapp.enums.ErrorCode;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
 
 @RestController
 @RequestMapping("/geo")
@@ -20,15 +25,24 @@ public class GeoController {
     private RestTemplate restTemplate;
 
     @Operation(summary = "获取最近位置", description = "根据经纬度查询最近位置字符串")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功获取位置信息",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = String.class))),
+        @ApiResponse(responseCode = "500", description = "地理位置请求失败",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @GetMapping("/nearest")
-    public R<String> getNearestGeo(@RequestParam double latitude, @RequestParam double longitude) throws work.baiyun.chronicdiseaseapp.exception.CustomException {
+    public R<String> getNearestGeo(@RequestParam double latitude, @RequestParam double longitude) {
         try {
             String url = "http://45.207.222.6/geo/nearest.php?latitude=" + latitude + "&longitude=" + longitude;
             ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
             return R.success(200, "ok", response.getBody());
         } catch (Exception e) {
-            // 异常由全局处理器处理,这里抛出 CustomException 或直接返回 fail
-            throw new work.baiyun.chronicdiseaseapp.exception.CustomException("地理编码请求失败: " + e.getMessage());
+            return R.fail(ErrorCode.GEO_REQUEST_FAILED.getCode(), 
+                         ErrorCode.GEO_REQUEST_FAILED.getMessage() + ": " + e.getMessage(), 
+                         null);
         }
     }
 }

+ 29 - 1
src/main/java/work/baiyun/chronicdiseaseapp/controller/HeartRateDataController.java

@@ -8,6 +8,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
 import work.baiyun.chronicdiseaseapp.common.R;
 import work.baiyun.chronicdiseaseapp.model.vo.AddHeartRateDataRequest;
 import work.baiyun.chronicdiseaseapp.model.vo.HeartRateDataResponse;
@@ -25,6 +29,14 @@ public class HeartRateDataController {
     private HeartRateDataService service;
 
     @Operation(summary = "添加心率数据", description = "添加心率测量记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "心率数据添加成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/add", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> add(@RequestBody AddHeartRateDataRequest req) {
         try {
@@ -36,6 +48,14 @@ public class HeartRateDataController {
     }
 
     @Operation(summary = "分页查询心率数据", description = "根据时间范围和分页参数查询心率记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功查询心率数据列表",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Page.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/list", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> list(@RequestBody BaseQueryRequest req) {
         try {
@@ -47,6 +67,14 @@ public class HeartRateDataController {
     }
 
     @Operation(summary = "删除心率数据", description = "根据ID删除心率测量记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "心率数据删除成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/delete", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> delete(@RequestBody DeleteHeartRateDataRequest req) {
         try {
@@ -56,4 +84,4 @@ public class HeartRateDataController {
             return R.fail(500, e.getMessage());
         }
     }
-}
+}

+ 29 - 1
src/main/java/work/baiyun/chronicdiseaseapp/controller/PhysicalDataController.java

@@ -8,6 +8,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
 import work.baiyun.chronicdiseaseapp.common.R;
 import work.baiyun.chronicdiseaseapp.model.vo.AddPhysicalDataRequest;
 import work.baiyun.chronicdiseaseapp.model.vo.PhysicalDataResponse;
@@ -25,6 +29,14 @@ public class PhysicalDataController {
     private PhysicalDataService physicalDataService;
 
     @Operation(summary = "添加体格数据", description = "添加身高/体重等体格测量记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "体格数据添加成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/add", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> addPhysicalData(@RequestBody AddPhysicalDataRequest req) {
         try {
@@ -36,6 +48,14 @@ public class PhysicalDataController {
     }
 
     @Operation(summary = "分页查询体格数据", description = "根据时间范围和分页参数查询体格测量记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功查询体格数据列表",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Page.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/list", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> listPhysicalData(@RequestBody BaseQueryRequest req) {
         try {
@@ -47,6 +67,14 @@ public class PhysicalDataController {
     }
 
     @Operation(summary = "删除体格数据", description = "根据ID删除体格测量记录")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "体格数据删除成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/delete", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> deletePhysicalData(@RequestBody DeletePhysicalDataRequest req) {
         try {
@@ -56,4 +84,4 @@ public class PhysicalDataController {
             return R.fail(500, e.getMessage());
         }
     }
-}
+}

+ 41 - 30
src/main/java/work/baiyun/chronicdiseaseapp/controller/UserBindingController.java

@@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
@@ -31,13 +32,15 @@ public class UserBindingController {
     private UserBindingService userBindingService;
 
     @Operation(summary = "创建用户绑定关系", description = "创建患者与医生或家属的绑定关系")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "绑定关系创建成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/create", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiResponse(responseCode = "200", description = "OK",
-                 content = @Content(mediaType = "application/json",
-                                   schema = @Schema(implementation = Void.class)))
-    @ApiResponse(responseCode = "500", description = "Internal Server Error",
-                 content = @Content(mediaType = "application/json",
-                                   schema = @Schema(implementation = Void.class)))
     public R<?> create(@RequestBody CreateUserBindingRequest req) {
         try {
             userBindingService.createUserBinding(req);
@@ -48,13 +51,15 @@ public class UserBindingController {
     }
 
     @Operation(summary = "删除用户绑定关系", description = "解除患者与医生或家属的绑定关系")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "绑定关系删除成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/delete", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiResponse(responseCode = "200", description = "OK",
-                 content = @Content(mediaType = "application/json",
-                                   schema = @Schema(implementation = Void.class)))
-    @ApiResponse(responseCode = "500", description = "Internal Server Error",
-                 content = @Content(mediaType = "application/json",
-                                   schema = @Schema(implementation = Void.class)))
     public R<?> delete(@RequestBody DeleteUserBindingRequest req) {
         try {
             userBindingService.deleteUserBinding(req.getPatientUserId(), req.getBoundUserId());
@@ -65,13 +70,15 @@ public class UserBindingController {
     }
 
     @Operation(summary = "分页查询患者的绑定关系列表", description = "根据患者ID和绑定类型查询绑定关系列表")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功查询绑定关系列表",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = UserBindingPageResult.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/list-by-patient", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiResponse(responseCode = "200", description = "OK",
-                 content = @Content(mediaType = "application/json",
-                                   schema = @Schema(implementation = UserBindingPageResult.class)))
-    @ApiResponse(responseCode = "500", description = "Internal Server Error",
-                 content = @Content(mediaType = "application/json",
-                                   schema = @Schema(implementation = Void.class)))
     public R<?> listByPatient(Long patientUserId, String bindingType, @RequestBody BaseQueryRequest req) {
         try {
             Page<UserBindingResponse> page = userBindingService.listBindingsByPatient(patientUserId, bindingType, req);
@@ -82,13 +89,15 @@ public class UserBindingController {
     }
 
     @Operation(summary = "分页查询用户被绑定的关系列表", description = "根据被绑定用户ID和绑定类型查询被绑定关系列表")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功查询被绑定关系列表",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = UserBindingPageResult.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/list-by-bound-user", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiResponse(responseCode = "200", description = "OK",
-                 content = @Content(mediaType = "application/json",
-                                   schema = @Schema(implementation = UserBindingPageResult.class)))
-    @ApiResponse(responseCode = "500", description = "Internal Server Error",
-                 content = @Content(mediaType = "application/json",
-                                   schema = @Schema(implementation = Void.class)))
     public R<?> listByBoundUser(Long boundUserId, String bindingType, @RequestBody BaseQueryRequest req) {
         try {
             Page<UserBindingResponse> page = userBindingService.listBindingsByBoundUser(boundUserId, bindingType, req);
@@ -99,13 +108,15 @@ public class UserBindingController {
     }
 
     @Operation(summary = "检查用户绑定关系", description = "检查患者与医生或家属是否存在绑定关系")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功检查绑定关系",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = CheckUserBindingResponse.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/check", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
-    @ApiResponse(responseCode = "200", description = "OK",
-                 content = @Content(mediaType = "application/json",
-                                   schema = @Schema(implementation = CheckUserBindingResponse.class)))
-    @ApiResponse(responseCode = "500", description = "Internal Server Error",
-                 content = @Content(mediaType = "application/json",
-                                   schema = @Schema(implementation = Void.class)))
     public R<?> check(@RequestBody CheckUserBindingRequest req) {
         try {
             CheckUserBindingResponse response = userBindingService.checkUserBinding(req);

+ 68 - 13
src/main/java/work/baiyun/chronicdiseaseapp/controller/WeChatController.java

@@ -7,6 +7,10 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
 import work.baiyun.chronicdiseaseapp.common.R;
 import work.baiyun.chronicdiseaseapp.model.vo.GetOpenidRequest;
 import work.baiyun.chronicdiseaseapp.service.WeChatService;
@@ -14,6 +18,7 @@ import work.baiyun.chronicdiseaseapp.enums.PermissionGroup;
 import work.baiyun.chronicdiseaseapp.mapper.UserInfoMapper;
 import work.baiyun.chronicdiseaseapp.model.po.UserInfo;
 import work.baiyun.chronicdiseaseapp.service.TokenService;
+import work.baiyun.chronicdiseaseapp.enums.ErrorCode;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 
@@ -37,21 +42,34 @@ public class WeChatController {
 
 
     @Operation(summary = "获取 openid", description = "根据小程序 code 获取 openid 并创建/返回用户 token")
-
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功获取openid并返回token",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Map.class))),
+        @ApiResponse(responseCode = "400", description = "请求参数错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/get_openid", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> getOpenid(@RequestBody(required = false) GetOpenidRequest req) {
         if (req == null || req.getCode() == null || req.getCode().isEmpty()) {
-            return R.fail(400, "Missing code parameter");
+            return R.fail(ErrorCode.PARAMETER_ERROR.getCode(), 
+                         ErrorCode.PARAMETER_ERROR.getMessage() + ": Missing code parameter");
         }
 
         // role is required now
         if (req.getRole() == null) {
-            return R.fail(400, "Missing role parameter");
+            return R.fail(ErrorCode.WECHAT_ROLE_REQUIRED.getCode(), 
+                         ErrorCode.WECHAT_ROLE_REQUIRED.getMessage());
         }
 
         String openid = weChatService.getOpenId(req.getCode());
         if (openid == null) {
-            return R.fail(500, "Failed to get openid");
+            return R.fail(ErrorCode.WECHAT_GET_OPENID_FAILED.getCode(), 
+                         ErrorCode.WECHAT_GET_OPENID_FAILED.getMessage());
         }
 
         // map incoming role int to PermissionGroup enum
@@ -59,12 +77,13 @@ public class WeChatController {
         try {
             pg = PermissionGroup.fromCode(req.getRole());
         } catch (IllegalArgumentException e) {
-            return R.fail(400, "Invalid role parameter");
+            return R.fail(ErrorCode.WECHAT_ROLE_INVALID.getCode(), 
+                         ErrorCode.WECHAT_ROLE_INVALID.getMessage());
         }
 
         // 查找是否存在 role + wx_openid 的用户
         QueryWrapper<UserInfo> qw = new QueryWrapper<>();
-    qw.eq("role", pg.getCode()).eq("wx_openid", openid);
+        qw.eq("role", pg.getCode()).eq("wx_openid", openid);
         UserInfo ui = userInfoMapper.selectOne(qw);
         if (ui == null) {
             // create new user info with provided role and openid
@@ -91,23 +110,39 @@ public class WeChatController {
      * 向后兼容:如果没有 Authorization header,仍然支持 X-Token 或 token header,或 POST body 中的 { "token": "..." }。
      */
     @Operation(summary = "获取用户信息", description = "根据 token 返回当前用户信息(支持 Authorization/X-Token/token)")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "成功获取用户信息",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Map.class))),
+        @ApiResponse(responseCode = "401", description = "未授权访问",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "404", description = "用户不存在",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/user_info", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> getUserInfo(@RequestBody(required = false) Map<String, String> body, HttpServletRequest request) {
         Long userId;
         try {
             userId = work.baiyun.chronicdiseaseapp.util.SecurityUtils.getCurrentUserId();
         } catch (work.baiyun.chronicdiseaseapp.exception.CustomException e) {
-            return R.fail(401, "No valid userId");
+            return R.fail(ErrorCode.UNAUTHORIZED.getCode(), 
+                         ErrorCode.UNAUTHORIZED.getMessage());
         }
 
         UserInfo ui = userInfoMapper.selectById(userId);
         if (ui == null) {
-            return R.fail(404, "User not found");
+            return R.fail(ErrorCode.USER_NOT_EXIST.getCode(), 
+                         ErrorCode.USER_NOT_EXIST.getMessage());
         }
 
         Map<String, Object> out = new HashMap<>();
         out.put("id", ui.getId());
-    out.put("wx_openid", ui.getWxOpenid());
+        out.put("wx_openid", ui.getWxOpenid());
         out.put("role", ui.getRole() != null ? ui.getRole().getCode() : null);
         out.put("avatar", ui.getAvatar());
         out.put("nickname", ui.getNickname());
@@ -120,6 +155,23 @@ public class WeChatController {
     }
 
     @Operation(summary = "更新用户信息", description = "更新用户的头像、昵称、手机号、年龄、地址等")
+    @ApiResponses(value = {
+        @ApiResponse(responseCode = "200", description = "用户信息更新成功",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "400", description = "请求参数错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "401", description = "未授权访问",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "404", description = "用户不存在",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class))),
+        @ApiResponse(responseCode = "500", description = "服务器内部错误",
+            content = @Content(mediaType = "application/json",
+                schema = @Schema(implementation = Void.class)))
+    })
     @PostMapping(path = "/update_user_info", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
     public R<?> updateUserInfo(@RequestBody(required = false) work.baiyun.chronicdiseaseapp.model.vo.UpdateUserInfoRequest req,
                                HttpServletRequest request) {
@@ -127,12 +179,14 @@ public class WeChatController {
         try {
             userId = work.baiyun.chronicdiseaseapp.util.SecurityUtils.getCurrentUserId();
         } catch (work.baiyun.chronicdiseaseapp.exception.CustomException e) {
-            return R.fail(401, "No valid userId");
+            return R.fail(ErrorCode.UNAUTHORIZED.getCode(), 
+                         ErrorCode.UNAUTHORIZED.getMessage());
         }
 
         UserInfo ui = userInfoMapper.selectById(userId);
         if (ui == null) {
-            return R.fail(404, "User not found");
+            return R.fail(ErrorCode.USER_NOT_EXIST.getCode(), 
+                         ErrorCode.USER_NOT_EXIST.getMessage());
         }
 
         if (req != null) {
@@ -145,7 +199,8 @@ public class WeChatController {
                 try {
                     ui.setSex(work.baiyun.chronicdiseaseapp.enums.Gender.fromCode(req.getSex()));
                 } catch (IllegalArgumentException e) {
-                    return R.fail(400, "Invalid sex value");
+                    return R.fail(ErrorCode.PARAMETER_ERROR.getCode(), 
+                                 ErrorCode.PARAMETER_ERROR.getMessage() + ": Invalid sex value");
                 }
             }
             userInfoMapper.updateById(ui);
@@ -153,4 +208,4 @@ public class WeChatController {
 
         return R.success(200, "ok");
     }
-}
+}

+ 53 - 0
src/main/java/work/baiyun/chronicdiseaseapp/enums/ErrorCode.java

@@ -0,0 +1,53 @@
+package work.baiyun.chronicdiseaseapp.enums;
+
+/**
+ * 错误码枚举类,定义系统中使用的各种错误码
+ */
+public enum ErrorCode {
+    // 通用错误码 1000-1999
+    SYSTEM_ERROR(1000, "系统内部错误"),
+    PARAMETER_ERROR(1001, "参数错误"),
+    UNAUTHORIZED(1002, "未授权访问"),
+    FORBIDDEN(1003, "权限不足"),
+    NOT_FOUND(1004, "资源不存在"),
+    METHOD_NOT_ALLOWED(1005, "请求方法不被允许"),
+    
+    // 用户相关错误码 2000-2999
+    USER_NOT_EXIST(2000, "用户不存在"),
+    USER_PASSWORD_ERROR(2001, "用户密码错误"),
+    USER_NOT_LOGIN(2002, "用户未登录"),
+    USER_ALREADY_EXIST(2003, "用户已存在"),
+    USER_ACCOUNT_LOCKED(2004, "用户账户被锁定"),
+    
+    // 微信相关错误码 3000-3999
+    WECHAT_CODE_INVALID(3000, "微信授权码无效"),
+    WECHAT_GET_OPENID_FAILED(3001, "获取微信openid失败"),
+    WECHAT_ROLE_REQUIRED(3002, "用户角色不能为空"),
+    WECHAT_ROLE_INVALID(3003, "用户角色无效"),
+    
+    // 数据相关错误码 4000-4999
+    DATA_NOT_FOUND(4000, "数据不存在"),
+    DATA_ACCESS_DENIED(4001, "无权访问该数据"),
+    DATA_DELETE_FAILED(4002, "数据删除失败"),
+    DATA_SAVE_FAILED(4003, "数据保存失败"),
+    
+    // 地理位置相关错误码 5000-5999
+    GEO_REQUEST_FAILED(5000, "地理位置请求失败"),
+    GEO_SERVICE_UNAVAILABLE(5001, "地理位置服务不可用");
+
+    private final int code;
+    private final String message;
+
+    ErrorCode(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}