RESTful详解
RESTful 详解
本章位置:第二阶段 Java 核心框架
前置知识:HTTP、Servlet、SpringBoot 请求响应、JSON
本章内容:REST、RESTful API、资源设计、HTTP 方法、状态码、幂等性、接口规范
下一篇:综合案例:RBAC 权限系统
学习目标:理解 RESTful 的设计思想,能够使用 SpringBoot 设计规范、清晰、易维护的 Web API。
一、RESTful 到底是什么
REST:
Representational State Transfer
中文常翻译为:
表现层状态转移
RESTful:
符合 REST 设计思想的接口风格。
注意:
RESTful 不是某个框架
不是某个 Java 类
不是某个注解
它是一种:
Web API 设计风格
二、为什么需要 RESTful
以前我们可能设计:
/getStudent
/addStudent
/updateStudent
/deleteStudent
看起来能用,但问题是:
URL 中混入了动作
接口风格不统一
资源含义不清晰
前后端约定越来越乱
RESTful 更推荐:
GET /students
GET /students/1
POST /students
PUT /students/1
DELETE /students/1
通过:
HTTP Method
表达:
动作
通过:
URL
表达:
资源
三、RESTful 核心思想
最核心一句话:
URL 表示资源,HTTP Method 表示对资源执行什么操作。
例如:
/students
表示:
学生资源
四、资源是什么
资源可以是:
学生
用户
商品
订单
文章
课程
评论
例如:
/students
/users
/products
/orders
/articles
五、URL 尽量使用名词
不推荐:
/getStudents
/createStudent
/deleteStudent
推荐:
/students
然后搭配:
GET
POST
DELETE
六、为什么 URL 不推荐大量动词
因为:
动作已经由 HTTP Method 表达
例如:
GET /students
已经表达:
获取学生
没必要再写:
GET /getStudents
七、资源通常使用复数名词
推荐:
/students
/users
/orders
而不是:
/student
/user
/order
虽然不是绝对规则,但:
团队统一
比个人随意更重要。
八、RESTful 常见 HTTP 方法
主要:
GET
POST
PUT
PATCH
DELETE
九、GET
用于:
查询资源
例如:
GET /students
查询学生列表。
十、GET 查询单个资源
GET /students/10
表示:
查询 id=10 的学生
十一、POST
常用于:
创建资源
例如:
POST /students
Body:
{
"studentNo": "20260001",
"name": "张三",
"age": 20
}
十二、PUT
通常表示:
整体更新资源
例如:
PUT /students/10
请求体:
{
"studentNo": "20260001",
"name": "张三",
"age": 21,
"major": "软件工程"
}
十三、PATCH
通常表示:
部分更新
例如:
PATCH /students/10
Body:
{
"age": 21
}
表示:
只修改 age
十四、DELETE
用于:
删除资源
例如:
DELETE /students/10
十五、CRUD 与 HTTP Method
可以对应:
| CRUD | HTTP Method | 示例 |
|---|---|---|
| Create | POST | POST /students |
| Read | GET | GET /students/1 |
| Update | PUT/PATCH | PUT /students/1 |
| Delete | DELETE | DELETE /students/1 |
十六、SpringBoot RESTful Controller
@RestController
@RequestMapping(
"/students"
)
public class StudentController {
}
十七、查询列表
@GetMapping
public List<Student> list() {
return studentService.findAll();
}
接口:
GET /students
十八、查询详情
@GetMapping(
"/{id}"
)
public Student detail(
@PathVariable
Long id
) {
return studentService.findById(
id
);
}
接口:
GET /students/1
十九、新增
@PostMapping
public Student create(
@RequestBody
StudentCreateDTO dto
) {
return studentService.create(
dto
);
}
接口:
POST /students
二十、修改
@PutMapping(
"/{id}"
)
public Student update(
@PathVariable
Long id,
@RequestBody
StudentUpdateDTO dto
) {
return studentService.update(
id,
dto
);
}
二十一、删除
@DeleteMapping(
"/{id}"
)
public void delete(
@PathVariable
Long id
) {
studentService.deleteById(
id
);
}
二十二、RESTful 不是必须 CRUD 一一对应
现实业务还有:
登录
退出
支付
审批
提交
取消订单
这些并不总能简单对应:
CRUD
所以 RESTful 不是:
死板语法
而是:
资源化设计思想
二十三、特殊动作怎么办
例如订单支付:
POST /orders/10/pay
虽然:
pay
是动词,但比硬扭成:
PUT /payment-status/...
更清晰时完全可以使用。
二十四、RESTful 不是宗教
接口设计优先:
清晰
一致
可理解
可维护
不要为了“绝对 REST”:
把简单业务设计得非常绕
二十五、PathVariable
RESTful 非常常用:
@PathVariable
例如:
/students/10
Java:
@PathVariable
Long id
二十六、Query Parameter
查询列表时:
筛选
分页
排序
更适合:
Query String
例如:
GET /students?pageNum=1&pageSize=10
二十七、筛选条件
例如:
GET /students?major=软件工程&minAge=18
而不是:
/students/software-engineering/18
因为这些是:
查询条件
不是资源身份。
二十八、分页设计
常见:
GET /students?page=1&size=10
或者:
GET /students?pageNum=1&pageSize=10
团队统一即可。
二十九、排序设计
例如:
GET /students?sort=score,desc
或者:
GET /students?sortBy=score&sortOrder=desc
三十、排序字段必须白名单
如果数据库 SQL 最终使用:
动态 ORDER BY
不要直接信任:
sortBy
必须在后端映射:
score
→ score
createTime
→ create_time
其他字段拒绝。
三十一、搜索设计
例如:
GET /students?keyword=张
也可以:
GET /students/search?keyword=张
如果只是普通列表过滤:
优先 /students?keyword=
更自然。
三十二、子资源
例如:
学生
评论
可以:
GET /students/10/comments
表示:
学生 10 的评论
三十三、子资源不要无限嵌套
不推荐:
/schools/1/colleges/2/majors/3/students/4/comments/5
路径太深:
难维护
通常 1~2 层关系已经足够。
三十四、资源唯一标识
例如:
/students/10
这里:
10
就是资源标识。
可以是:
数据库 id
业务编号
UUID
slug
三十五、不要在 URL 暴露敏感信息
例如不要:
/users/身份证号
如果没有必要。
URL 会出现在:
浏览器历史
日志
代理日志
三十六、HTTP 状态码是 RESTful 的重要组成
接口不能只看:
JSON code
HTTP 自己已经定义:
状态码
应该合理利用。
三十七、200 OK
用于:
成功查询
成功修改
一般成功响应
例如:
GET /students/1
→ 200
三十八、201 Created
资源创建成功:
POST /students
推荐:
201 Created
三十九、204 No Content
操作成功:
没有响应体
例如:
DELETE /students/1
可以返回:
204
四十、400 Bad Request
表示:
客户端请求格式/参数错误
例如:
age=abc
JSON 语法错误
缺少必填参数
四十一、401 Unauthorized
严格讲通常表示:
未认证
即:
你还没有证明自己是谁
例如:
Token 缺失
Token 无效
四十二、403 Forbidden
表示:
已经认证
但没有权限
例如:
普通用户删除管理员
四十三、404 Not Found
表示:
资源不存在
例如:
GET /students/999999
学生不存在。
四十四、405 Method Not Allowed
例如接口:
GET /students
客户端:
DELETE /students
但没有这个映射:
405
四十五、409 Conflict
表示:
资源冲突
例如:
studentNo 已存在
用户名冲突
版本冲突
很多业务非常适合:
409
四十六、415 Unsupported Media Type
例如:
接口需要 application/json
客户端却发送:
text/plain
四十七、422 Unprocessable Content
有些 API 会用:
422
表达:
请求格式正确
但业务字段校验失败
是否采用:
取决于团队规范
四十八、500 Internal Server Error
表示:
服务端未知异常
不能把所有业务错误都返回:
500
四十九、状态码不要乱用
错误示例:
学生不存在
→ 500
更合理:
404
五十、统一响应对象
即使使用 HTTP 状态码,项目也可以统一 JSON:
{
"code": "STUDENT_NOT_FOUND",
"message": "学生不存在",
"data": null
}
其中:
HTTP Status
负责协议语义
业务 code
负责前端程序识别
message
负责提示
data
负责数据
五十一、不要让业务 code 完全复制 HTTP 状态码
例如:
code = 404
虽然简单,但复杂系统里:
业务错误种类很多
更清晰可以:
STUDENT_NOT_FOUND
STUDENT_NO_EXISTS
ORDER_CANNOT_CANCEL
五十二、幂等性是什么
幂等:
同一个请求执行一次和执行多次,对服务器最终状态的影响相同。
五十三、GET 通常应该幂等
例如:
GET /students/1
执行:
1 次
10 次
不应该改变数据库。
五十四、PUT 通常是幂等的
例如:
把学生年龄设置为 20
执行一次:
age=20
执行十次:
还是 age=20
五十五、DELETE 通常是幂等的
第一次:
删除成功
第二次:
资源已经不存在
最终状态仍然:
不存在
五十六、POST 通常不是幂等的
POST /orders
执行两次:
可能创建两个订单
五十七、为什么幂等性重要
网络中可能发生:
客户端超时
客户端重试
网关重试
用户重复点击
如果接口不考虑幂等:
可能重复扣款
重复下单
重复新增
五十八、支付接口为什么特别重视幂等
用户点击支付:
网络超时
客户端不知道:
到底成功没有
再次请求:
不能再扣一次钱
所以需要:
业务幂等设计
五十九、常见幂等方案
例如:
唯一业务号
幂等 Key
数据库 UNIQUE
状态机
分布式锁
Token
六十、RESTful 和幂等不是同一件事
RESTful:
接口设计风格
幂等:
请求重复执行的语义
两者相关,但:
不是同一个概念
六十一、PUT 和 PATCH 的区别
PUT:
通常是完整替换/整体更新
PATCH:
部分更新
六十二、实际项目是否一定严格区分
不一定。
很多企业项目:
全部修改统一 PUT
即使只是改部分字段。
关键是:
团队约定一致
六十三、PATCH 的一个风险
如果 DTO:
private String name;
传:
{}
和:
{
"name": null
}
可能需要表达不同含义:
没修改 name
明确把 name 清空
所以部分更新设计:
比看起来复杂
六十四、DTO 的价值
不要让 Controller 直接暴露数据库 Entity。
例如 Entity:
public class User {
private Long id;
private String username;
private String passwordHash;
private Integer deleted;
private LocalDateTime createTime;
}
新增接口:
前端不应该控制 deleted
也不应该控制 createTime
六十五、新增 DTO
public class StudentCreateDTO {
private String studentNo;
private String name;
private Integer age;
private String major;
}
六十六、修改 DTO
public class StudentUpdateDTO {
private String name;
private Integer age;
private String major;
}
六十七、查询 DTO
public class StudentQueryDTO {
private String keyword;
private String major;
private Integer pageNum = 1;
private Integer pageSize = 10;
}
六十八、VO
响应:
public class StudentVO {
private Long id;
private String studentNo;
private String name;
private Integer age;
private String major;
}
六十九、DTO 和 VO 为什么分开
DTO:
前端给后端什么
VO:
后端给前端什么
这两个方向:
需求经常不同
七十、密码字段例子
注册 DTO:
password
用户 VO:
绝不能返回 passwordHash
这就是分对象的价值。
七十一、分页响应
常见:
{
"records": [],
"pageNum": 1,
"pageSize": 10,
"total": 100,
"totalPages": 10
}
七十二、分页参数要限制
不能让用户:
pageSize=1000000
否则:
可能一次查询大量数据
建议:
默认 10/20
最大 100
七十三、分页参数校验
if (
pageSize > 100
) {
pageSize = 100;
}
更正式可以:
Validation
七十四、过滤参数设计
例如:
GET /students?major=软件工程&gender=男
不要设计:
/students/software-engineering/male
因为:
这不是资源层级
只是过滤条件
七十五、时间范围查询
例如:
GET /orders?startTime=2026-09-01&endTime=2026-09-30
后端解析:
时间格式
时区
边界
要有统一规范。
七十六、日期格式统一
团队最好统一:
ISO 8601
例如:
2026-09-10T15:30:00+08:00
或者明确:
yyyy-MM-dd HH:mm:ss
不要不同接口各自乱来。
七十七、时间戳和时区
前后端跨地区时:
时区非常重要
不要只返回:
2026-09-10 10:00:00
却不知道:
哪个时区
七十八、URL 版本控制
大型 API 可能:
/api/v1/students
以后升级:
/api/v2/students
七十九、什么时候需要 API 版本
如果:
移动端已经发布
第三方正在调用
旧客户端无法同步升级
API 版本很有价值。
内部简单项目:
可以不必过度设计
八十、统一前缀
例如:
/api
Controller:
@RequestMapping(
"/api/students"
)
或者通过:
网关
context path
统一配置。
八十一、接口命名避免大小写混乱
推荐:
/students
/order-items
不要:
/getStudentList
/GetStudent
/student_List
八十二、URL 中常用 kebab-case
例如:
/order-items
/user-profiles
相比:
/orderItems
URL 中很多团队更偏好:
短横线
八十三、JSON 字段常用 camelCase
例如:
{
"studentNo": "20260001",
"createTime": "..."
}
和 Java:
camelCase
一致。
八十四、数据库可以 snake_case
数据库:
student_no
create_time
Java / JSON:
studentNo
createTime
MyBatis:
mapUnderscoreToCamelCase
可以转换。
八十五、RESTful 的典型学生接口
GET
/api/students
GET
/api/students/{id}
POST
/api/students
PUT
/api/students/{id}
PATCH
/api/students/{id}
DELETE
/api/students/{id}
八十六、查询列表 Controller
@GetMapping
public Result<PageResult<StudentVO>>
list(
StudentQueryDTO query
) {
return Result.success(
studentService.findPage(
query
)
);
}
八十七、查询详情 Controller
@GetMapping(
"/{id}"
)
public Result<StudentVO>
detail(
@PathVariable
Long id
) {
StudentVO student =
studentService.findById(
id
);
return Result.success(
student
);
}
八十八、新增 Controller
@PostMapping
public ResponseEntity<Result<StudentVO>>
create(
@RequestBody
StudentCreateDTO dto
) {
StudentVO student =
studentService.create(
dto
);
return ResponseEntity
.status(
HttpStatus.CREATED
)
.body(
Result.success(
student
)
);
}
八十九、修改 Controller
@PutMapping(
"/{id}"
)
public Result<StudentVO>
update(
@PathVariable
Long id,
@RequestBody
StudentUpdateDTO dto
) {
return Result.success(
studentService.update(
id,
dto
)
);
}
九十、删除 Controller
@DeleteMapping(
"/{id}"
)
public ResponseEntity<Void>
delete(
@PathVariable
Long id
) {
studentService.deleteById(
id
);
return ResponseEntity
.noContent()
.build();
}
九十一、Controller 不应该塞业务逻辑
不推荐:
if (
student.getScore() > 90
) {
// 大量业务
}
Controller 应该:
薄
主要做:
请求
参数
调用 Service
返回响应
九十二、Service 承担业务
例如:
学号是否重复
学生是否存在
状态是否允许修改
权限是否允许
都应该主要放:
Service
九十三、RESTful 不等于 Controller 越短越好
重点不是:
代码行数
而是:
职责清晰
九十四、异常处理
如果每个 Controller 都:
try {
} catch (
Exception e
) {
}
会重复很多。
SpringBoot 更推荐:
全局异常处理
九十五、@RestControllerAdvice 预览
@RestControllerAdvice
public class GlobalExceptionHandler {
}
九十六、@ExceptionHandler
例如:
@ExceptionHandler(
StudentNotFoundException.class
)
public ResponseEntity<Result<Void>>
handleStudentNotFound(
StudentNotFoundException e
) {
return ResponseEntity
.status(
HttpStatus.NOT_FOUND
)
.body(
Result.fail(
"STUDENT_NOT_FOUND",
e.getMessage()
)
);
}
九十七、为什么全局异常处理更好
可以统一:
业务异常
参数异常
系统异常
HTTP 状态码
错误 JSON
Controller 更干净。
九十八、自定义业务异常
public class StudentNotFoundException
extends RuntimeException {
public StudentNotFoundException(
Long id
) {
super(
"学生不存在,id="
+ id
);
}
}
九十九、不要把异常堆栈返回给用户
错误:
{
"message": "java.sql.SQLException at ..."
}
会泄露:
表结构
包名
SQL
内部实现
应该:
日志记录详细信息
客户端只返回必要信息
一百、参数校验
例如 DTO:
public class StudentCreateDTO {
@NotBlank(
message = "学号不能为空"
)
private String studentNo;
@NotBlank(
message = "姓名不能为空"
)
private String name;
@Min(
value = 1,
message = "年龄不能小于 1"
)
@Max(
value = 150,
message = "年龄不能大于 150"
)
private Integer age;
}
一百零一、Controller 使用 @Valid
@PostMapping
public Result<StudentVO>
create(
@Valid
@RequestBody
StudentCreateDTO dto
) {
return Result.success(
studentService.create(
dto
)
);
}
一百零二、参数校验失败
通常:
Spring MVC 抛出校验异常
再由:
全局异常处理器
统一返回:
400
一百零三、RESTful 与安全
RESTful URL 好看:
不等于安全
例如:
DELETE /users/10
必须检查:
用户是否登录
用户是否有权限
能不能删除目标用户
一百零四、前端隐藏按钮不等于权限
即使 Vue 不显示:
删除按钮
用户仍然可以:
Postman
curl
直接调用 API。
所以:
后端必须权限校验
一百零五、敏感操作必须鉴权
例如:
删除
修改权限
支付
退款
重置密码
都不能只靠:
前端限制
一百零六、RESTful 与 JWT
前后端分离常见:
Authorization: Bearer <token>
API:
从 Header 获取 Token
后端:
认证
授权
这会在后面的 RBAC 综合案例继续实践。
一百零七、批量删除怎么设计
一种常见方式:
DELETE /students
Body:
{
"ids": [1, 2, 3]
}
但部分客户端/网关对 DELETE Body 支持不统一。
一百零八、另一种批量操作设计
可以:
POST /students/batch-delete
Body:
{
"ids": [1, 2, 3]
}
这不“纯 REST”,但:
业务清晰
兼容性好
完全可以接受。
一百零九、批量创建
POST /students/batch
Body:
[
{
"studentNo": "001",
"name": "张三"
},
{
"studentNo": "002",
"name": "李四"
}
]
一百一十、接口不要一次返回无限数据
错误:
GET /students
→ 100 万条
应该:
分页
限制 pageSize
必要时流式导出
一百一十一、文件导出通常不是普通 JSON API
例如:
GET /students/export
返回:
Excel 文件
这仍然可以属于 REST API,但响应类型:
不是 application/json
一百一十二、接口响应 Content-Type
JSON:
application/json
Excel:
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
图片:
image/png
PDF:
application/pdf
一百一十三、上传接口
例如:
POST /files
请求:
multipart/form-data
这也是资源化:
创建一个文件资源
一百一十四、Location 响应头
创建成功后可以:
201 Created
并返回:
Location: /students/100
表示:
新资源地址
一百一十五、ETag 简单了解
HTTP 可以通过:
ETag
支持:
缓存
并发更新控制
当前阶段了解即可。
一百一十六、乐观锁与 REST API
修改学生:
客户端读取 version=5
更新:
{
"name": "张三",
"version": 5
}
数据库:
UPDATE student
SET
name = ?,
version = version + 1
WHERE
id = ?
AND version = ?;
如果更新 0 行:
说明版本冲突
可以返回:
409 Conflict
一百一十七、接口重复提交
例如:
POST /orders
用户快速点两次。
可以通过:
客户端按钮防抖
+
后端幂等
+
数据库唯一约束
共同处理。
一百一十八、URL 不应该反映数据库表结构
例如数据库表:
tb_sys_user
接口没必要:
/tb_sys_user
应该:
/users
因为 API 面向:
业务资源
不是数据库实现。
一百一十九、接口不要暴露内部类名
不要:
/getSysUserEntityList
API 应独立于:
Java 类名
一百二十、RESTful 与前后端分离
Vue:
axios.get('/api/students')
SpringBoot:
@GetMapping(
"/api/students"
)
响应:
JSON
这是典型:
前后端分离
一百二十一、Axios GET
例如:
axios.get(
"/api/students",
{
params: {
pageNum: 1,
pageSize: 10
}
}
)
对应:
@GetMapping(
"/api/students"
)
一百二十二、Axios POST
axios.post(
"/api/students",
{
studentNo: "20260001",
name: "张三",
age: 20
}
)
对应:
@PostMapping
@RequestBody
一百二十三、Axios PUT
axios.put(
"/api/students/1",
{
name: "张三",
age: 21
}
)
一百二十四、Axios DELETE
axios.delete(
"/api/students/1"
)
一百二十五、CORS 简单预览
如果前端:
localhost:5173
后端:
localhost:8080
浏览器会认为:
不同源
可能遇到:
CORS
一百二十六、什么叫 Origin
Origin 由:
协议
主机
端口
组成。
例如:
http://localhost:5173
和:
http://localhost:8080
端口不同:
就是不同源
一百二十七、@CrossOrigin
学习阶段可以:
@CrossOrigin
@RestController
public class StudentController {
}
允许跨域。
真实项目更推荐:
统一 CORS 配置
一百二十八、不要无脑允许所有跨域
生产环境:
*
可能过于宽松。
应该根据:
实际前端域名
配置。
一百二十九、RESTful API 文档要写什么
至少:
接口名称
Method
URL
请求参数
请求 Body
响应示例
状态码
业务错误码
一百三十、接口文档示例
接口:
查询学生详情
Method:
GET
URL:
/api/students/{id}
Path:
id
Long
学生 ID
响应:
{
"code": "SUCCESS",
"message": "success",
"data": {
"id": 1,
"studentNo": "20260001",
"name": "张三"
}
}
一百三十一、接口文档为什么重要
前后端分离后:
前端
后端
测试
都需要同一份接口约定。
一百三十二、Swagger / OpenAPI 预览
后面项目通常使用:
OpenAPI
Swagger UI
Knife4j
自动生成:
接口文档
当前先理解:
接口设计规范
一百三十三、常见错误:把所有接口都用 POST
例如:
POST /getStudents
POST /deleteStudent
虽然能用,但:
HTTP 语义丢失
缓存能力差
接口可读性差
一百三十四、常见错误:URL 动词太多
不推荐:
/api/getAllStudentList
推荐:
GET /api/students
一百三十五、常见错误:返回永远 200
有些系统:
不管什么错误 HTTP 都 200
然后 JSON:
{
"code": 500
}
这种做法方便某些旧系统统一处理,但会:
削弱 HTTP 语义
现代 API 更推荐合理使用:
HTTP Status
一百三十六、常见错误:业务 code 没有规范
例如不同 Controller:
1001
A001
50001
ERR_X
随便定义。
应该:
统一错误码体系
一百三十七、常见错误:查询接口修改数据库
例如:
GET /orders/1/pay
访问一下就支付。
非常不合理。
GET 应该尽量:
安全
只读
一百三十八、Safe Method
HTTP 中通常认为:
GET
HEAD
OPTIONS
属于:
安全方法
即语义上不应修改服务器状态。
一百三十九、Safe 和 Idempotent 不一样
GET
安全 + 幂等
PUT
不安全,但通常幂等
DELETE
不安全,但通常幂等
POST
通常不安全,也不幂等
一百四十、常见错误:DELETE 接口写成 GET
GET /students/delete?id=1
问题:
浏览器预加载
爬虫
缓存
误访问
都可能造成风险。
一百四十一、常见错误:Path 和 Query 不分
资源身份:
/students/1
使用:
Path
查询条件:
?major=软件工程
使用:
Query
一百四十二、常见错误:分页从 0 还是 1 不统一
有的接口:
page=0
有的:
page=1
前端极易出错。
必须:
团队统一
一百四十三、常见错误:时间格式不统一
接口 A:
2026-09-10
接口 B:
2026/09/10
接口 C:
1757480000000
应该:
建立统一时间规范
一百四十四、常见错误:字段命名不统一
一个接口:
studentId
另一个:
student_id
另一个:
stuId
前端会非常痛苦。
一百四十五、常见错误:返回 Entity
直接返回数据库实体可能泄露:
deleted
passwordHash
内部状态
审计字段
推荐:
VO
一百四十六、常见错误:分页返回 List בלבד
如果只返回:
[
{},
{}
]
前端不知道:
总数
总页数
当前页
应该返回:
分页对象
一百四十七、常见错误:接口无限 pageSize
必须限制:
最大 pageSize
防止:
恶意大查询
一百四十八、常见错误:排序字段直接 ${}
这是:
SQL 注入风险
必须:
白名单
一百四十九、常见错误:前端错误信息直接暴露数据库异常
不要:
{
"message": "Duplicate entry 'xxx' for key ..."
}
应该转成:
{
"code": "STUDENT_NO_EXISTS",
"message": "学号已存在"
}
一百五十、RESTful 设计步骤
设计一个新模块时可以按:
1. 找资源
2. 定 URL
3. 定 HTTP Method
4. 定 Path / Query
5. 定 Request DTO
6. 定 Response VO
7. 定状态码
8. 定业务错误码
9. 定分页/排序规则
10. 定权限
一百五十一、学生模块设计
资源:
students
列表:
GET /api/students
详情:
GET /api/students/{id}
新增:
POST /api/students
修改:
PUT /api/students/{id}
删除:
DELETE /api/students/{id}
一百五十二、评论模块设计
资源:
comments
查询某学生评论:
GET /api/students/{studentId}/comments
新增评论:
POST /api/students/{studentId}/comments
删除评论:
DELETE /api/comments/{id}
一百五十三、订单模块设计
GET /api/orders
GET /api/orders/{id}
POST /api/orders
POST /api/orders/{id}/cancel
POST /api/orders/{id}/pay
最后两个是:
业务动作
这样设计往往比硬套 CRUD 更清晰。
一百五十四、状态机业务不要只当字段更新
例如订单取消:
不是简单:
status=CANCELLED
还可能包含:
校验当前状态
释放库存
退款
记录日志
发送消息
因此:
POST /orders/{id}/cancel
表达业务动作反而更合理。
一百五十五、练习 1:改造旧 CRUD URL
把:
/getStudentList
/getStudentById
/addStudent
/updateStudent
/deleteStudent
改成:
GET /students
GET /students/{id}
POST /students
PUT /students/{id}
DELETE /students/{id}
一百五十六、练习 2:分页接口
设计:
GET /students?pageNum=1&pageSize=10
要求:
默认页码
默认 pageSize
最大 pageSize
一百五十七、练习 3:搜索接口
支持:
keyword
major
minAge
maxAge
设计 Query DTO。
一百五十八、练习 4:新增 DTO
不要直接使用:
Student Entity
创建:
StudentCreateDTO
一百五十九、练习 5:响应 VO
创建:
StudentVO
只返回:
前端需要的字段
一百六十、练习 6:状态码
分别设计:
查询成功
创建成功
删除成功
参数错误
学生不存在
学号冲突
服务器异常
对应 HTTP 状态码。
一百六十一、练习 7:统一错误响应
设计:
{
"code": "STUDENT_NOT_FOUND",
"message": "学生不存在",
"data": null
}
一百六十二、练习 8:全局异常处理
创建:
StudentNotFoundException
GlobalExceptionHandler
让 Controller 不写重复 try/catch。
一百六十三、练习 9:接口幂等
思考:
POST /orders
重复执行如何避免创建两条订单。
一百六十四、练习 10:PUT 和 PATCH
分别设计:
整体修改学生
只修改学生专业
理解两者语义区别。
一百六十五、练习 11:子资源
设计:
学生
选课记录
接口:
GET /students/{id}/courses
一百六十六、练习 12:业务动作
设计订单:
支付
取消
确认收货
不要机械全部设计成:
PUT status
一百六十七、必须掌握的 RESTful 核心
资源
URL
HTTP Method
Path Variable
Query Parameter
Request Body
Status Code
DTO
VO
幂等性
一百六十八、必须掌握 HTTP Method
GET
查询
POST
创建/动作
PUT
整体更新
PATCH
部分更新
DELETE
删除
一百六十九、必须掌握状态码
200
成功
201
创建成功
204
成功无响应体
400
请求错误
401
未认证
403
无权限
404
资源不存在
405
Method 不允许
409
资源冲突
415
媒体类型错误
500
服务器异常
一百七十、必须回答的问题
学完后应该能够回答:
1. REST 是什么?
2. RESTful 是什么?
3. RESTful 是框架吗?
4. RESTful 为什么推荐 URL 用名词?
5. 为什么动作交给 HTTP Method 表达?
6. GET、POST、PUT、PATCH、DELETE 分别表示什么?
7. PathVariable 和 RequestParam 有什么区别?
8. 查询条件应该放 Path 还是 Query?
9. 什么是资源?
10. 什么是子资源?
11. 为什么 URL 不应该直接对应数据库表名?
12. 为什么 GET 不应该修改数据?
13. 什么是安全方法?
14. 什么是幂等性?
15. GET 为什么通常幂等?
16. POST 为什么通常不幂等?
17. PUT 和 PATCH 有什么区别?
18. DELETE 为什么通常认为幂等?
19. 200、201、204 有什么区别?
20. 401 和 403 有什么区别?
21. 404 和 409 分别适合什么情况?
22. 为什么业务错误不能全部返回 500?
23. DTO 和 VO 是什么?
24. 为什么不推荐 Controller 直接暴露 Entity?
25. 为什么分页必须限制 pageSize?
26. 为什么动态排序字段需要白名单?
27. RESTful 是否要求所有业务都绝对不能出现动词 URL?
28. 订单支付为什么可以设计 /orders/{id}/pay?
29. 为什么需要统一错误响应?
30. 为什么权限必须在后端校验?
一百七十一、RESTful 知识结构
RESTful
│
├─ Resource
│ ├─ students
│ ├─ users
│ └─ orders
│
├─ URL
│ ├─ /students
│ ├─ /students/{id}
│ └─ 子资源
│
├─ HTTP Method
│ ├─ GET
│ ├─ POST
│ ├─ PUT
│ ├─ PATCH
│ └─ DELETE
│
├─ Request
│ ├─ Path
│ ├─ Query
│ ├─ Header
│ └─ Body
│
├─ Response
│ ├─ Status Code
│ ├─ Result
│ └─ VO
│
├─ Design
│ ├─ DTO
│ ├─ Pagination
│ ├─ Sorting
│ ├─ Filtering
│ └─ Version
│
└─ Quality
├─ Idempotency
├─ Validation
├─ Error Handling
├─ Security
└─ Consistency
一百七十二、从 SpringBoot 请求响应到 RESTful
上一章重点:
SpringBoot
怎么接收请求
怎么获取参数
怎么返回 JSON
这一章重点:
这些接口到底应该怎么设计
所以可以理解:
SpringBoot 请求响应
解决“怎么写”
RESTful
解决“怎么设计”
一百七十三、从 RESTful 到 RBAC 综合案例
下一阶段综合案例会真正使用:
用户
角色
权限
菜单
登录
鉴权
分页
CRUD
接口可以设计:
GET /users
POST /users
PUT /users/{id}
DELETE /users/{id}
GET /roles
POST /roles/{id}/permissions
同时结合:
SpringBoot
MyBatis
事务
RESTful
统一响应
异常处理
一百七十四、本章总结
RESTful 最核心的一句话:
URL 表示资源,HTTP Method 表示对资源的操作。
例如:
GET /students
查询学生列表
GET /students/1
查询学生 1
POST /students
新增学生
PUT /students/1
整体修改学生 1
PATCH /students/1
部分修改学生 1
DELETE /students/1
删除学生 1
RESTful 不只是:
URL 好看
还包括:
HTTP Method
状态码
幂等性
请求结构
响应结构
资源语义
错误处理
接口一致性
状态码要合理:
200
正常成功
201
创建成功
204
删除成功无响应体
400
参数错误
401
未认证
403
无权限
404
资源不存在
409
业务冲突
415
Content-Type 不支持
500
服务端异常
接口对象建议分:
Entity
数据库对象
DTO
请求对象
VO
响应对象
分页和筛选通常:
Query Parameter
资源身份通常:
Path Variable
RESTful 不是死规则:
业务动作明显时
可以使用清晰的动作型子路径
例如:
POST /orders/{id}/pay
POST /orders/{id}/cancel
最终目标永远是:
清晰
一致
安全
可维护
按照课程表,下一篇正式进入:
综合案例:RBAC 权限系统
后面会把:
SpringBoot
MyBatis
RESTful
事务
用户
角色
权限
组合成一个完整综合项目。