diff --git a/pom.xml b/pom.xml index a6fb3a7..be6c37d 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ 1.18.30 3.5.5 4.3.1 - 8.0.33 + 8.0.33 @@ -43,14 +43,14 @@ - + org.springframework.boot spring-boot-starter-jdbc - + com.mysql mysql-connector-j - ${mysql.version} + ${mysql.version} com.baomidou @@ -70,26 +70,26 @@ true - - io.jsonwebtoken - jjwt-api - 0.11.5 - - - io.jsonwebtoken - jjwt-impl - 0.11.5 - - - io.jsonwebtoken - jjwt-jackson - 0.11.5 - - - - org.springframework.boot - spring-boot-starter-validation - + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + + + + org.springframework.boot + spring-boot-starter-validation + org.springdoc diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/AuthController.java b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/AuthController.java new file mode 100644 index 0000000..1d603c1 --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/AuthController.java @@ -0,0 +1,10 @@ +package icu.sunway.ai_spring_example.Controller.AuthController; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/auth") +public class AuthController { + +} diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/docs.md b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/docs.md new file mode 100644 index 0000000..0aa8619 --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/AuthController/docs.md @@ -0,0 +1,422 @@ +# Auth模块接口文档 + +## 文档说明 + +本文档为Auth(认证授权)模块的接口详情,遵循《接口开发通用规范文档》的所有约定,基础路径为`/api/v1/auth`,数据交互格式为JSON,字符编码UTF-8,时间戳单位为毫秒。 + +## 1. 登录接口 + +### 接口基本信息 + +- 接口名称:用户登录 +- 接口路径:`/api/v1/auth/login` +- 请求方法:POST +- 接口描述:用户通过账号(手机号/用户名)+密码/验证码完成登录,返回登录令牌(token) + +### 请求头 + +| 字段名 | 类型 | 必填性 | 示例值 | +| --------------- | ------ | ------ | ---------------- | +| Content-Type | String | 是 | application/json | +| Accept-Language | String | 否 | zh-CN | + +### 请求参数(Body) + +| 字段名 | 类型 | 必填性 | 描述 | 示例值 | +| ---------- | ------- | ------ | ------------------------------------------------ | ------------- | +| account | String | 是 | 登录账号(手机号/用户名) | "13800138000" | +| password | String | 否 | 登录密码(与verifyCode二选一) | "Abc123456" | +| verifyCode | String | 否 | 验证码(与password二选一) | "8888" | +| isRemember | Boolean | 否 | 是否记住登录(默认false,记住则token有效期延长) | true | + +### 响应示例(成功) + +```json +{ + "code": 200, + "message": "登录成功", + "timestamp": 1744238900000, + "data": { + "token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJleHAiOjE3NDQ4NDM3MDB9.QP8kXjZ8e7R5t5s7k9L2m1n8b7v6c5x4s3a2d1f0g9h8j7k6l5p4o3i2u1y0t9s8r7e6w5q4a3s2d1f0g", + "expireTime": 1744843700000, + "userInfo": { + "userId": 1, + "userName": "admin", + "phoneNumber": "138****8000", + "role": "ADMIN", + "isEnabled": true, + "createTime": 1743238900000 + } + } +} +``` + +### 响应示例(失败) + +```json +{ + "code": 400, + "message": "验证码错误或已过期", + "timestamp": 1744238900000, + "data": null +} +``` + +### 状态码说明 + +| 状态码 | 说明 | +| ------ | -------------------------------- | +| 200 | 登录成功 | +| 400 | 参数错误/验证码错误/账号密码错误 | +| 403 | 账号被封禁 | +| 429 | 登录失败次数过多,账号临时锁定 | +| 500 | 服务器内部错误 | + +## 2. 退出登录接口 + +### 接口基本信息 + +- 接口名称:用户退出登录 +- 接口路径:`/api/v1/auth/logout` +- 请求方法:DELETE +- 接口描述:销毁当前用户的登录令牌,退出登录状态 + +### 请求头 + +| 字段名 | 类型 | 必填性 | 示例值 | +| --------------- | ------ | ------ | ------------------------------ | +| Authorization | String | 是 | Bearer eyJhbGciOiJIUzI1NiJ9... | +| Content-Type | String | 是 | application/json | +| Accept-Language | String | 否 | zh-CN | + +### 请求参数 + +无业务请求参数(仅需请求头携带token) + +### 响应示例(成功) + +```json +{ + "code": 200, + "message": "退出登录成功", + "timestamp": 1744238900000, + "data": null +} +``` + +### 响应示例(失败) + +```json +{ + "code": 401, + "message": "Token已过期,请重新登录", + "timestamp": 1744238900000, + "data": null +} +``` + +### 状态码说明 + +| 状态码 | 说明 | +| ------ | ---------------- | +| 200 | 退出登录成功 | +| 401 | Token失效/未登录 | +| 500 | 服务器内部错误 | + +## 3. 刷新Token接口 + +### 接口基本信息 + +- 接口名称:刷新登录令牌 +- 接口路径:`/api/v1/auth/refresh-token` +- 请求方法:POST +- 接口描述:通过旧Token刷新获取新的登录令牌,延长登录有效期 + +### 请求头 + +| 字段名 | 类型 | 必填性 | 示例值 | +| --------------- | ------ | ------ | ------------------------------ | +| Authorization | String | 是 | Bearer eyJhbGciOiJIUzI1NiJ9... | +| Content-Type | String | 是 | application/json | +| Accept-Language | String | 否 | zh-CN | + +### 请求参数(Body) + +| 字段名 | 类型 | 必填性 | 描述 | 示例值 | +| ------ | ------ | ------ | ------------ | ------------------------- | +| token | String | 是 | 旧的登录令牌 | "eyJhbGciOiJIUzI1NiJ9..." | + +### 响应示例(成功) + +```json +{ + "code": 200, + "message": "Token刷新成功", + "timestamp": 1744238900000, + "data": { + "newToken": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJleHAiOjE3NDU0NDg1MDB9.Z7x6y5w4v3u2t1s0r9e8w7q6a5z4s3d2f1g0h9j8k7l6p5o4i3u2y1t0s9r8e7w6q5a4s3d2f1g0h9j8k7l6p5o4i", + "expireTime": 1745448500000 + } +} +``` + +### 响应示例(失败) + +```json +{ + "code": 401, + "message": "Token已失效,无法刷新,请重新登录", + "timestamp": 1744238900000, + "data": null +} +``` + +### 状态码说明 + +| 状态码 | 说明 | +| ------ | ------------------ | +| 200 | Token刷新成功 | +| 400 | 旧Token格式错误 | +| 401 | 旧Token已过期/无效 | +| 500 | 服务器内部错误 | + +## 4. 获取当前用户信息接口 + +### 接口基本信息 + +- 接口名称:获取当前登录用户信息 +- 接口路径:`/api/v1/auth/user/info` +- 请求方法:GET +- 接口描述:根据登录令牌获取当前用户的基础信息(脱敏展示敏感字段) + +### 请求头 + +| 字段名 | 类型 | 必填性 | 示例值 | +| --------------- | ------ | ------ | ------------------------------ | +| Authorization | String | 是 | Bearer eyJhbGciOiJIUzI1NiJ9... | +| Content-Type | String | 是 | application/json | +| Accept-Language | String | 否 | zh-CN | + +### 请求参数 + +无业务请求参数(仅需请求头携带token) + +### 响应示例(成功) + +```json +{ + "code": 200, + "message": "查询用户信息成功", + "timestamp": 1744238900000, + "data": { + "userId": 1, + "userName": "admin", + "phoneNumber": "138****8000", + "idCard": "310**********1234", + "roleList": ["ADMIN"], + "isEnabled": true, + "createTime": 1743238900000, + "createTimeStr": "2025-01-01 10:00:00", + "lastLoginTime": 1744152500000, + "lastLoginTimeStr": "2025-04-10 08:00:00" + } +} +``` + +### 响应示例(失败) + +```json +{ + "code": 401, + "message": "未登录,请先登录", + "timestamp": 1744238900000, + "data": null +} +``` + +### 状态码说明 + +| 状态码 | 说明 | +| ------ | ---------------- | +| 200 | 查询用户信息成功 | +| 401 | Token失效/未登录 | +| 404 | 用户信息不存在 | +| 500 | 服务器内部错误 | + +## 5. 发送登录验证码接口 + +### 接口基本信息 + +- 接口名称:发送登录验证码 +- 接口路径:`/api/v1/auth/send-login-code` +- 请求方法:POST +- 接口描述:向指定手机号发送登录验证码,限制发送频率 + +### 请求头 + +| 字段名 | 类型 | 必填性 | 示例值 | +| --------------- | ------ | ------ | ---------------- | +| Content-Type | String | 是 | application/json | +| Accept-Language | String | 否 | zh-CN | + +### 请求参数(Body) + +| 字段名 | 类型 | 必填性 | 描述 | 示例值 | +| ----------- | ------ | ------ | ------------------ | ------------- | +| phoneNumber | String | 是 | 接收验证码的手机号 | "13800138000" | + +### 响应示例(成功) + +```json +{ + "code": 200, + "message": "验证码发送成功,请注意查收", + "timestamp": 1744238900000, + "data": { + "expireTime": 1744239200000 // 验证码过期时间戳(5分钟有效期) + } +} +``` + +### 响应示例(失败) + +```json +{ + "code": 429, + "message": "验证码发送过于频繁,请1分钟后重试", + "timestamp": 1744238900000, + "data": null +} +``` + +### 状态码说明 + +| 状态码 | 说明 | +| ------ | ------------------------------ | +| 200 | 验证码发送成功 | +| 400 | 手机号格式错误 | +| 429 | 发送频率超限 | +| 404 | 手机号未注册 | +| 500 | 验证码发送失败(短信服务异常) | + +## 6. 重置密码接口 + +### 接口基本信息 + +- 接口名称:重置登录密码 +- 接口路径:`/api/v1/auth/reset-password` +- 请求方法:PUT +- 接口描述:用户通过手机号+验证码重置登录密码(全量更新密码) + +### 请求头 + +| 字段名 | 类型 | 必填性 | 示例值 | +| --------------- | ------ | ------ | ---------------- | +| Content-Type | String | 是 | application/json | +| Accept-Language | String | 否 | zh-CN | + +### 请求参数(Body) + +| 字段名 | 类型 | 必填性 | 描述 | 示例值 | +| ----------- | ------ | ------ | ----------------------------------------------- | ------------- | +| phoneNumber | String | 是 | 绑定的手机号 | "13800138000" | +| verifyCode | String | 是 | 验证码 | "8888" | +| newPassword | String | 是 | 新密码(需符合密码规则:8-20位,含大小写+数字) | "Abc123456" | + +### 响应示例(成功) + +```json +{ + "code": 200, + "message": "密码重置成功,请重新登录", + "timestamp": 1744238900000, + "data": null +} +``` + +### 响应示例(失败) + +```json +{ + "code": 400, + "message": "新密码格式不符合要求,需包含大小写字母和数字,长度8-20位", + "timestamp": 1744238900000, + "data": null +} +``` + +### 状态码说明 + +| 状态码 | 说明 | +| ------ | -------------------------------- | +| 200 | 密码重置成功 | +| 400 | 参数错误/验证码错误/密码格式错误 | +| 429 | 验证码发送频率超限/验证次数过多 | +| 404 | 手机号未注册 | +| 500 | 服务器内部错误 | + +## 7. 校验Token有效性接口 + +### 接口基本信息 + +- 接口名称:校验Token有效性 +- 接口路径:`/api/v1/auth/verify-token` +- 请求方法:GET +- 接口描述:校验当前登录令牌是否有效,返回令牌基础信息 + +### 请求头 + +| 字段名 | 类型 | 必填性 | 示例值 | +| --------------- | ------ | ------ | ------------------------------ | +| Authorization | String | 是 | Bearer eyJhbGciOiJIUzI1NiJ9... | +| Content-Type | String | 是 | application/json | +| Accept-Language | String | 否 | zh-CN | + +### 请求参数 + +无业务请求参数(仅需请求头携带token) + +### 响应示例(成功) + +```json +{ + "code": 200, + "message": "Token有效", + "timestamp": 1744238900000, + "data": { + "userId": 1, + "token": "eyJhbGciOiJIUzI1NiJ9...", + "isValid": true, + "expireTime": 1744843700000 + } +} +``` + +### 响应示例(失败) + +```json +{ + "code": 401, + "message": "Token无效或已过期", + "timestamp": 1744238900000, + "data": { + "isValid": false + } +} +``` + +### 状态码说明 + +| 状态码 | 说明 | +| ------ | ---------------- | +| 200 | Token有效 | +| 401 | Token无效/已过期 | +| 500 | 服务器内部错误 | + +## 通用说明 + +1. 所有接口响应均遵循通用响应格式,包含`code`、`message`、`timestamp`字段,`data`字段按需返回; +2. 敏感信息(手机号、身份证号)均做脱敏处理,符合安全规范; +3. 接口频率限制遵循通用规范:验证码接口1分钟/次、1小时/5次,登录接口5分钟连续失败5次锁定15分钟,通用接口QPS≤10; +4. 所有入参均做合法性校验,参数错误返回400状态码并提示具体错误信息; +5. Token格式为`Bearer + 空格 + token`,需登录的接口未传/传错Token均返回401状态码。 diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/PermissionController/PermissionController.java b/src/main/java/icu/sunway/ai_spring_example/Controller/PermissionController/PermissionController.java index 70a4264..3c390fa 100644 --- a/src/main/java/icu/sunway/ai_spring_example/Controller/PermissionController/PermissionController.java +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/PermissionController/PermissionController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.*; import jakarta.annotation.Resource; @RestController -@RequestMapping("/permission") +@RequestMapping("/api/v1/permission") public class PermissionController { @Resource diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/RoleController/RoleController.java b/src/main/java/icu/sunway/ai_spring_example/Controller/RoleController/RoleController.java index 52bdb82..a66bac7 100644 --- a/src/main/java/icu/sunway/ai_spring_example/Controller/RoleController/RoleController.java +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/RoleController/RoleController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.*; import jakarta.annotation.Resource; @RestController -@RequestMapping("/role") +@RequestMapping("/api/v1/role") public class RoleController { @Resource diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/RolePermissionController/RolePermissionController.java b/src/main/java/icu/sunway/ai_spring_example/Controller/RolePermissionController/RolePermissionController.java index 6e2e32b..12aa74f 100644 --- a/src/main/java/icu/sunway/ai_spring_example/Controller/RolePermissionController/RolePermissionController.java +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/RolePermissionController/RolePermissionController.java @@ -11,7 +11,7 @@ import jakarta.annotation.Resource; import java.util.List; @RestController -@RequestMapping("/role-permission") +@RequestMapping("/api/v1/role-permission") public class RolePermissionController { @Resource diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/UserController/UserController.java b/src/main/java/icu/sunway/ai_spring_example/Controller/UserController/UserController.java index 8fcd251..fe5bb70 100644 --- a/src/main/java/icu/sunway/ai_spring_example/Controller/UserController/UserController.java +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/UserController/UserController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.*; import jakarta.annotation.Resource; @RestController -@RequestMapping("/user") +@RequestMapping("/api/v1/user") public class UserController { @Resource diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/UserRoleController/UserRoleController.java b/src/main/java/icu/sunway/ai_spring_example/Controller/UserRoleController/UserRoleController.java index 21546a0..ac950c3 100644 --- a/src/main/java/icu/sunway/ai_spring_example/Controller/UserRoleController/UserRoleController.java +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/UserRoleController/UserRoleController.java @@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.*; import jakarta.annotation.Resource; @RestController -@RequestMapping("/user-role") +@RequestMapping("/api/v1/user-role") public class UserRoleController { @Resource diff --git a/src/main/java/icu/sunway/ai_spring_example/Controller/api-doc-standard.md b/src/main/java/icu/sunway/ai_spring_example/Controller/api-doc-standard.md new file mode 100644 index 0000000..8676a52 --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Controller/api-doc-standard.md @@ -0,0 +1,218 @@ +# 接口开发通用规范文档 + +## 文档说明 + +本规范为项目所有接口的通用开发准则,适用于前后端接口对接、后端接口开发、接口文档编写等场景,所有业务接口均需遵循本规范。 + +### 基础约定 + +- 接口基础路径:`/api/v1`(各业务模块在基础路径后追加,如Auth模块:`/api/v1/auth`) +- 数据交互格式:JSON +- 字符编码:UTF-8 +- 时间戳单位:毫秒(整数类型) +- 接口版本管理:通过URL路径版本号(如v1)区分,不使用请求参数/请求头区分 + +## 一、通用请求规范 + +### 1. 请求方式(RESTful规范) + +| 操作类型 | 请求方法 | 典型场景 | 示例 | +| ------------- | -------- | -------------------------------- | ----------------------------- | +| 新增/提交数据 | POST | 登录、发送验证码、创建资源 | `/api/v1/auth/login` | +| 查询数据 | GET | 获取详情、校验状态、列表查询 | `/api/v1/auth/user/info` | +| 修改数据 | PUT | 全量更新(如重置密码、修改信息) | `/api/v1/auth/reset-password` | +| 部分更新 | PATCH | 局部更新(如修改用户昵称) | `/api/v1/user/nickname` | +| 删除数据 | DELETE | 退出登录、删除资源 | `/api/v1/auth/logout` | + +### 2. 请求头规范 + +| 字段名 | 类型 | 必填性 | 描述 | 示例 | +| --------------- | ------ | ------ | --------------------------------------------------------- | -------------------------------- | +| Authorization | String | 非必选 | 登录令牌,格式为`Bearer + 空格 + token`(需登录接口必传) | "Bearer eyJhbGciOiJIUzI1NiJ9..." | +| Content-Type | String | 必选 | 请求体格式,仅支持`application/json` | "application/json" | +| Accept-Language | String | 非必选 | 语言类型,默认zh-CN | "zh-CN" | + +### 3. 分页请求参数(列表接口必传) + +所有列表查询接口均需支持分页,分页参数统一通过`GET`请求参数传递(POST请求可放在Body中),参数规范如下: + +| 字段名 | 类型 | 必填性 | 默认值 | 描述 | 示例 | +| ------ | ------- | ------ | ------ | ----------------------------- | ---- | +| page | Integer | 否 | 1 | 当前页码(从1开始) | 1 | +| limit | Integer | 否 | 10 | 每页显示条数(最大不超过100) | 10 | + +#### 示例 + +```plain +GET /api/v1/user/list?current=1&size=10 +``` + +### 4. 请求参数命名规范 + +- 字段名使用**小驼峰命名**(如`userName`、`phoneNumber`),禁止使用下划线/连字符; +- 布尔类型字段命名:使用`isXXX`格式(如`isRemember`、`isEnabled`); +- 时间类字段:统一使用`createTime`、`updateTime`等,禁止使用`create_at`; +- 参数值:手机号、身份证号等字符串类型参数,值为纯数字时仍以字符串传递。 + +## 二、通用响应规范 + +### 1. 基础响应格式(所有接口统一) + +所有接口响应必须包含`code`、`message`、`timestamp`字段,`data`字段为可选(无业务数据时返回`null`)。 + +#### 格式定义 + +```json +{ + "code": 200, // 整数,业务状态码 + "message": "操作成功", // 字符串,提示信息(前端可直接展示) + "timestamp": 1744238900000, // 整数,服务器响应时间戳(毫秒) + "data": {} // 任意类型,业务数据(无数据时为null) +} +``` + +#### 字段说明 + +| 字段名 | 类型 | 必填性 | 描述 | +| --------- | ------- | ------ | --------------------------------------------------------------------------- | +| code | Integer | 是 | 业务状态码(200表示成功,非200表示异常) | +| message | String | 是 | 响应提示信息(成功/失败描述,需友好、简洁) | +| timestamp | Integer | 是 | 服务器处理请求的时间戳(毫秒),前端可用于时间展示/校验 | +| data | Any | 否 | 业务数据体:1. 无数据时为null;2. 单条数据为Object;3. 列表数据遵循分页规范 | + +### 2. 分页响应格式(列表接口专用) + +列表接口的`data`字段必须遵循以下结构,禁止自定义分页字段名/格式。 + +#### 格式定义 + +```json +{ + "code": 200, + "message": "查询成功", + "timestamp": 1744238900000, + "data": { + "records": [], // 数组,当前页数据列表 + "total": 0, // 整数,符合条件的总记录数 + "size": 10, // 整数,每页显示条数(实际返回条数) + "current": 1, // 整数,当前页码 + "pages": 0 // 整数,总页数(total/size向上取整) + } +} +``` + +#### 字段说明 + +| 字段名 | 类型 | 必填性 | 描述 | +| ------- | ------- | ------ | ----------------------------------------- | +| records | Array | 是 | 当前页数据列表(无数据时为空数组) | +| total | Integer | 是 | 总记录数(用于计算总页数/分页展示) | +| size | Integer | 是 | 每页请求条数(与请求参数size一致) | +| current | Integer | 是 | 当前页码(与请求参数current一致) | +| pages | Integer | 是 | 总页数(计算公式:Math.ceil(total/size)) | + +#### 分页响应示例 + +```json +{ + "code": 200, + "message": "用户列表查询成功", + "timestamp": 1744238900000, + "data": { + "records": [ + {"id": 1, "username": "admin", "role": "ADMIN"}, + {"id": 2, "username": "user1", "role": "USER"} + ], + "total": 20, + "size": 10, + "current": 1, + "pages": 2 + } +} +``` + +### 3. 通用状态码规范 + +| 状态码 | 含义 | 核心场景 | +| ------ | -------------- | -------------------------------------- | +| 200 | 操作成功 | 所有接口正常响应 | +| 400 | 参数错误 | 请求参数缺失/格式错误/校验不通过 | +| 401 | 未授权 | token失效/未登录/令牌格式错误 | +| 403 | 禁止访问 | 账号被封禁/权限不足/IP受限 | +| 404 | 资源不存在 | 用户不存在/接口路径错误/数据记录不存在 | +| 409 | 资源冲突 | 用户名重复/手机号已绑定/操作重复提交 | +| 429 | 请求过于频繁 | 验证码发送频率超限/接口调用频率超限 | +| 500 | 服务器内部错误 | 服务端逻辑异常/第三方接口调用失败 | + +## 三、接口命名规范 + +### 1. URL路径命名 + +- 路径全部使用**小写字母**,多个单词用连字符(-)分隔(如`/api/v1/auth/refresh-token`); +- 路径使用名词而非动词(RESTful规范),如`/api/v1/user`(用户资源)而非`/api/v1/getUser`; +- 资源ID放在路径末尾,如`/api/v1/user/123`(查询ID为123的用户)。 + +### 2. 响应字段命名 + +- 与请求参数一致,使用**小驼峰命名**; +- 时间字段:优先返回时间戳(Integer,毫秒),如需返回格式化时间,字段名加`Str`后缀(如`createTimeStr`); +- 金额字段:以分为单位返回整数(避免浮点精度问题),字段名加`Cent`后缀(如`amountCent`)。 + +## 四、安全规范(必遵循) + +### 1. 密码/敏感信息处理 + +- 敏感信息返回:手机号、身份证号等脱敏展示(如`138****8000`); +- Token处理:JWT Token不存储敏感信息(如密码),Redis存储的Token设置合理过期时间。 + +### 2. 接口频率限制 + +- 验证码接口:同一手机号1分钟内最多1次,1小时内最多5次; +- 登录接口:同一账号5分钟内连续失败5次,临时锁定15分钟; +- 通用接口:单IP/单用户接口调用频率不超过QPS 10。 + +### 3. 数据校验 + +- 所有入参必须做合法性校验(格式、长度、范围); + +## 五、异常响应示例 + +### 1. 参数错误(400) + +```json +{ + "code": 400, + "message": "手机号格式错误", + "timestamp": 1744238900000, + "data": null +} +``` + +### 2. 未授权(401) + +```json +{ + "code": 401, + "message": "Token已过期,请重新登录", + "timestamp": 1744238900000, + "data": null +} +``` + +### 3. 服务器错误(500) + +```json +{ + "code": 500, + "message": "服务器内部错误,请稍后重试", + "timestamp": 1744238900000, + "data": null +} +``` + +## 总结 + +1. 所有接口必须遵循**统一响应格式**(包含code/message/timestamp),列表接口强制分页并使用指定分页结构; +2. 接口命名、参数/字段命名需符合RESTful规范和小驼峰/小写连字符约定,保证可读性; +3. 安全规范是核心底线,密码、Token、频率限制等规则必须严格执行,避免安全漏洞; +4. 本规范为基础准则,各业务模块可在本规范基础上补充模块专属规则,但不得与本规范冲突。 diff --git a/src/main/java/icu/sunway/ai_spring_example/Docs/api-dev-standard.md b/src/main/java/icu/sunway/ai_spring_example/Docs/api-dev-standard.md new file mode 100644 index 0000000..3549bd3 --- /dev/null +++ b/src/main/java/icu/sunway/ai_spring_example/Docs/api-dev-standard.md @@ -0,0 +1,256 @@ +# 接口开发规范 + +## 0. 与API文档规范对照说明 + +本开发规范与`Controller/api-doc-standard.md`互为补充: + +- **api-dev-standard.md**:指导**代码实现**(如何编写符合规范的代码) +- **api-doc-standard.md**:定义**接口契约**(请求/响应格式标准) + +### 关键一致性说明 + +| 规范维度 | 代码实现规范 (本文件) | 接口文档规范 (api-doc-standard) | 对齐状态 | +| ------------ | -------------------------------- | ----------------------------------- | -------------------------------------- | +| **响应格式** | 必须使用ResponseUtils生成响应 | 强制要求code/message/timestamp/data | ✅ 对齐 | +| **分页参数** | 代码中参数名: `page`/`limit` | 文档中参数名: `page`/`limit` | ✅ 语义对齐(page=current, limit=size) | +| **状态码** | ResponseUtils需支持400/401等状态 | 明确200/400/401等状态码语义 | ⚠️ 需增强 | +| **时间字段** | Entity用LocalDateTime | 响应用时间戳(Integer) | ⚠️ 需转换 | + +> **特别说明**:当前代码中`ResponseUtils`实现需扩展以完全符合api-doc-standard,但开发规范以**实际代码结构**为准。 + +--- + +## 1. 整体架构规范 + +### 1.1 分层结构 + +遵循CLAUDE.md定义的分层架构,特别注意: + +- **Controller层**:业务逻辑的主要编写位置 +- **Service层**:仅包含接口定义和MyBatis Plus继承实现 + - 接口:`I{Name}Service` 继承 `IService` + - 实现:`{Name}ServiceImpl` 继承 `ServiceImpl` +- **业务逻辑不应下沉到Service层**,Controller直接调用Service提供的CRUD方法 + +### 1.2 目录规范 + +- Controller按实体划分目录:`Controller/{EntityName}Controller/` +- **Entity目录**:`Entity/` 下直接存放实体类(**无需子目录**) +- **Mapper目录**:`Mapper/` 下直接存放Mapper接口(**无需子目录**) +- **Service目录**: + - 接口:`Service/` + - 实现:`Service/Implements/` +- 示例: + - `User.java` → `Entity/User.java` + - `UserMapper.java` → `Mapper/UserMapper.java` + +## 2. Controller编写规范 + +### 2.1 服务注入 + +```java +@Resource +private IUserService userService; +``` + +- **必须**使用`@Resource`而非`@Autowired` +- **不得**在Service层编写业务逻辑 + +### 2.2 响应处理(必须遵守) + +**所有接口必须使用ResponseUtils统一响应格式**: + +对于 Controller 中的函数返回类型,统一使用 Object 类型,而不是具体的实体类。 + +如果需要编写具体 VO 实体类,在对应的 Controller 目录下创建 vo 文件夹并在其中定义。 + +```java +// 成功响应 +return ResponseUtils.success(data); + +// 失败响应(需指定状态码) +return ResponseUtils.fail(400, "用户名格式错误"); +``` + +- **必须包含**: + - `code`:按[api-doc-standard#状态码规范](Controller/api-doc-standard.md#三、通用状态码规范)设置 + - `timestamp`:当前时间戳(`System.currentTimeMillis()`) + - `data`:业务数据(列表接口需返回标准分页结构) +- **禁止**直接返回原始对象 + +### 2.3 分页处理 + +**必须使用MyBatis Plus原生Page对象**: + +```java +@GetMapping +public Object getAllUsers( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer limit) { + + Page pageObj = new Page<>(page, limit); + return ResponseUtils.success(userService.page(pageObj)); +} +``` + +- **关键点**: + - Page对象字段与API响应结构完全匹配(records/total/size/current/pages) + - **禁止**手动构建分页响应 + - 分页参数**代码中命名为`page`/`limit`** + +### 2.4 查询操作 + +**简单查询**直接使用Service方法: + +```java +@GetMapping("/{id}") +public Object getUserById(@PathVariable Long id) { + return ResponseUtils.success(userService.getById(id)); +} +``` + +**复杂查询**使用QueryWrapper: + +```java +@GetMapping("/username/{username}") +public Object getUserByUsername(@PathVariable String username) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("username", username); + return ResponseUtils.success(userService.getOne(wrapper)); +} +``` + +- **注意**:查询条件构建应在Controller层完成 + +## 3. Entity层规范 + +### 3.1 基础结构 + +```java +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("user") +public class User { + @TableId(type = IdType.AUTO) + private Long id; + private String username; + private String password; + // ...其他字段 +} +``` + +- **必须使用**: + - Lombok注解:`@Data` + `@NoArgsConstructor` + `@AllArgsConstructor` + - MyBatis Plus注解:`@TableName` + `@TableId` +- **字段命名**:小驼峰命名(`createTime`而非`create_time`) + +### 3.2 时间字段处理 + +- **Entity层**:使用`LocalDateTime`类型(如`createTime`) + +## 4. Mapper层规范 + +### 4.1 基础结构 + +```java +@Mapper +public interface UserMapper extends BaseMapper { + // 仅允许MyBatis Plus内置方法,自定义SQL需走Service层 +} +``` + +- **必须**继承`BaseMapper` +- **禁止**在Mapper接口中添加方法(自定义SQL需通过Service层) + +## 5. Service层规范 + +### 5.1 接口定义 + +```java +public interface IUserService extends IService { + // 仅允许扩展MyBatis Plus未提供的方法 +} +``` + +- **必须**继承`IService` +- **仅当需要**扩展MyBatis Plus未提供的方法时才添加方法 + +### 5.2 实现类 + +```java +@Service +public class UserServiceImpl extends ServiceImpl implements IUserService { + @Resource + private UserMapper userMapper; +} +``` + +- **必须**继承`ServiceImpl` +- **禁止**在实现类中编写业务逻辑(仅用于注入Mapper) + +## 6. 禁止行为清单 + +| 类型 | 错误示例 | 正确做法 | +| ------------ | ----------------------------------- | ------------------------------------ | +| 业务逻辑位置 | 在ServiceImpl中编写业务 | 所有业务逻辑放在Controller | +| 响应格式 | 直接返回`new ResponseEntity<>(...)` | 用ResponseUtils生成带timestamp的响应 | +| 分页参数 | 使用`offset`/`pageSize` | 统一用`page`/`limit`参数 | +| 状态码 | 所有错误返回500 | 按[api-doc-standard]返回400/401等 | +| 时间字段 | 直接返回LocalDateTime | 转换为时间戳或格式化字符串 | + +## 7. 必须使用的工具类 + +| 工具类 | 用途 | 示例 | +| --------------- | -------------- | ------------------------------------- | +| `ResponseUtils` | 生成标准化响应 | `ResponseUtils.fail(400, "参数错误")` | + +## 8. 接口设计原则 + +1. **RESTful规范**: + - 路径使用**复数名词**:`/users`而非`/user` + - **参数命名**:代码用`page`/`limit`,文档用`current`/`size` + +2. **字段转换**: + - Entity的`LocalDateTime` → 接口响应需转为时间戳 + - 敏感字段(如密码)需在Controller层过滤:`user.setPassword(null)` + +3. **错误处理**: + - 按[api-doc-standard#通用状态码规范](Controller/api-doc-standard.md#三、通用状态码规范)返回精确错误 + - 示例:`return ResponseUtils.fail(409, "用户名已存在");` + +## 9. 完整示例 + +```java +@RestController +@RequestMapping("/users") +public class UserController { + + @Resource + private IUserService userService; + + @GetMapping("/{id}") + public Object getUser(@PathVariable Long id) { + User user = userService.getById(id); + // 过滤敏感字段 + user.setPassword(null); + // 转换时间格式 + return ResponseUtils.success(user); + } + + @GetMapping + public Object listUsers( + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer limit) { + + // 参数校验 + if (limit > 100) { + return ResponseUtils.fail(400, "每页数量不能超过100"); + } + + Page pageObj = new Page<>(page, limit); + Page resultPage = userService.page(pageObj); + return ResponseUtils.success(resultPage); + } +} +``` diff --git a/src/main/java/icu/sunway/ai_spring_example/Utils/JsonUtils.java b/src/main/java/icu/sunway/ai_spring_example/Utils/JsonUtils.java index 578843b..75201ee 100644 --- a/src/main/java/icu/sunway/ai_spring_example/Utils/JsonUtils.java +++ b/src/main/java/icu/sunway/ai_spring_example/Utils/JsonUtils.java @@ -145,19 +145,6 @@ public class JsonUtils { return toObject(json, Map.class, keyClazz, valueClazz); } - /** - * 将对象转换为Map - * - * @param obj 对象 - * @return Map - */ - public static Map toMap(Object obj) { - if (obj == null) { - return null; - } - return objectMapper.convertValue(obj, Map.class); - } - /** * 验证JSON字符串是否有效 * diff --git a/src/main/java/icu/sunway/ai_spring_example/Utils/PageUtils.java b/src/main/java/icu/sunway/ai_spring_example/Utils/PageUtils.java deleted file mode 100644 index b2acda4..0000000 --- a/src/main/java/icu/sunway/ai_spring_example/Utils/PageUtils.java +++ /dev/null @@ -1,111 +0,0 @@ -package icu.sunway.ai_spring_example.Utils; - -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; - -/** - * 分页工具类 - */ -public class PageUtils { - - /** - * 默认页码 - */ - public static final Integer DEFAULT_PAGE = 1; - - /** - * 默认每页数量 - */ - public static final Integer DEFAULT_LIMIT = 10; - - /** - * 最大每页数量 - */ - public static final Integer MAX_LIMIT = 100; - - /** - * 分页结果类 - * - * @param 数据类型 - */ - @Data - @AllArgsConstructor - @NoArgsConstructor - public static class PaginationResult { - private Integer currentPage; - private Integer pageSize; - private Long totalItems; - private Integer totalPages; - private List list; - } - - /** - * 验证和调整分页参数 - * - * @param page 页码 - * @param limit 每页数量 - * @return 调整后的分页参数数组 [page, limit] - */ - public static Integer[] validatePageParams(Integer page, Integer limit) { - // 验证页码 - if (page == null || page < 1) { - page = DEFAULT_PAGE; - } - - // 验证每页数量 - if (limit == null || limit < 1 || limit > MAX_LIMIT) { - limit = DEFAULT_LIMIT; - } - - return new Integer[] { page, limit }; - } - - /** - * 创建Page对象 - * - * @param page 页码 - * @param limit 每页数量 - * @return Page对象 - */ - public static Page createPage(Integer page, Integer limit) { - Integer[] params = validatePageParams(page, limit); - return new Page<>(params[0], params[1]); - } - - /** - * 将MyBatis Plus的Page结果转换为分页结果 - * - * @param pageResult MyBatis Plus的Page结果 - * @return 分页结果 - */ - public static PaginationResult createPaginationResult(IPage pageResult) { - return new PaginationResult<>( - (int) pageResult.getCurrent(), - (int) pageResult.getSize(), - pageResult.getTotal(), - (int) pageResult.getPages(), - pageResult.getRecords()); - } - - /** - * 创建空的分页结果 - * - * @param page 页码 - * @param limit 每页数量 - * @return 空的分页结果 - */ - public static PaginationResult createEmptyPaginationResult(Integer page, Integer limit) { - Integer[] params = validatePageParams(page, limit); - return new PaginationResult<>( - params[0], - params[1], - 0L, - 0, - List.of()); - } -}