Day 4 | API 设计 & 全栈串联:前端和后端终于"对话"了
API 设计的核心原则(前端开发者的视角)
前端写 API 调用,天然理解 HTTP 请求。但"设计" API 是什么感觉?
想象你是后端,别人(前端)要来拿数据。你要考虑:
- 我要暴露哪些端点? ------ 粒度怎么切?
- 请求参数怎么传? ------ path variable?query string?还是 body JSON?
- 返回什么格式? ------ 直接返回数据库记录?还是加工过的结构?
- 谁来调用? ------ 只有你的前端?还是第三方?
这就是 API 设计。
RESTful API 设计入门
RESTful 是一套约定俗成的 API 设计规范,前端开发者其实天天在用它:
| 操作 | HTTP 方法 | URL 设计 | 说明 |
|---|---|---|---|
| 查全部 | GET |
/api/accidents |
获取事故列表 |
| 查单个 | GET |
/api/accidents/{id} |
获取详情 |
| 新增 | POST |
/api/accidents |
创建事故 |
| 修改 | PUT |
/api/accidents/{id} |
更新事故 |
| 删除 | DELETE |
/api/accidents/{id} |
删除事故 |
| 统计 | GET |
/api/accidents/stats |
聚合数据(不属于 CRUD,加个 stats) |
前端类比:
router.get('/accidents')相当于后端@GetMapping("/accidents")。RESTful 只是把 URL 当成资源路径来组织,跟 Vue Router 的理念一脉相承。
统一响应结构:前后端对话的"共同语言"
前端 axios 收到响应后,最怕的是格式不统一:
javascript
// 有的接口这样返回
{ data: { id: 1, title: '...' } }
// 有的接口这样返回
{ code: 200, message: 'success', data: { id: 1, title: '...' } }
// 有的接口返回 200 但业务失败
{ code: 401, message: '未登录', data: null }
全栈项目必须有统一的响应结构:
java
// 后端:统一 Result 包装
public class Result<T> {
private int code; // 业务状态码(200成功,401未登录,500错误)
private String message; // 提示信息
private T data; // 泛型数据体
public static <T> Result<T> success(T data) {
Result<T> r = new Result<>();
r.code = 200;
r.message = "success";
r.data = data;
return r;
}
public static <T> Result<T> fail(int code, String message) {
Result<T> r = new Result<>();
r.code = code;
r.message = message;
return r;
}
}
javascript
// 前端:axios 响应拦截器统一处理
axios.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
// 业务级错误(非 HTTP 401)
if (res.code === 401) {
router.push('/login')
}
return Promise.reject(res)
}
return res.data // 返回 data 字段给调用方
},
error => {
ElMessage.error(error.response?.data?.message || '网络错误')
return Promise.reject(error)
}
)
关键点: 后端返回 HTTP 200 + 业务码 code=401 ≠ HTTP 401。前端 axios 拦截器必须识别业务码 401,跳转登录。这是全栈联调里最容易踩的坑之一。
跨域(CORS)与代理
前端开发时,前端(localhost:5173)调用后端(localhost:8080),浏览器会阻止------这就是 CORS 问题。
开发环境:用 Vite 代理(推荐)
javascript
// vite.config.js
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
这样前端 axios.get('/api/accidents') 实际发到 http://localhost:8080/api/accidents,对浏览器来说没有跨域。
生产环境 :Nginx 反向代理,把 /api 统一代理到后端。
身份认证:Token 的全栈闭环
前后端分离项目中,身份认证通常是 JWT Token 方案:
sql
登录流程:
前端 → POST /api/login { username, password }
后端 → 验证成功 → 生成 JWT → 返回 { token, user }
前端 → 存 token 到 localStorage,每次请求附上
请求流程:
前端 → GET /api/accidents(header: Authorization: Bearer <token>)
后端 → Filter 拦截 → 解析 JWT → 验证通过 → 放行 Controller
后端 JWT 验证 Filter(简化版):
java
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
@Autowired private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
// 放行登录接口
if (request.getRequestURI().contains("/api/login")) {
chain.doFilter(request, response);
return;
}
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
if (jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsernameFromToken(token);
// 把用户信息存到请求上下文,供 Controller 使用
request.setAttribute("username", username);
}
}
chain.doFilter(request, response);
}
}
文件上传:MinIO 对象存储
事故分析系统有图片上传需求。直接存数据库太慢,存服务器本地磁盘不可靠------正确的方案是对象存储。
架构:
bash
前端 → POST /api/upload → 后端 Controller
后端 → 读取文件 → 上传到 MinIO(对象存储服务)
后端 → 返回 MinIO URL → 前端保存到 image_urls 字段
java
// 后端上传接口(简化版)
@PostMapping("/upload")
public Result<String> upload(@RequestParam("file") MultipartFile file) throws Exception {
String objectName = UUID.randomUUID() + "-" + file.getOriginalFilename();
minioClient.putObject(
PutObjectArgs.builder()
.bucket("accident-media")
.object(objectName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build()
);
String url = minioClient.getObjectUrl("accident-media", objectName);
return Result.success(url);
}
前端对应:
axios.post('/api/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } })
今日任务清单
- 实现完整的登录流程:后端 JWT 登录接口 + 前端 axios 调用 + token 存储 + 请求拦截器附加 token
- 配置 Vite 代理 :让前端开发服务器正确代理到
localhost:8080,验证 CORS 问题消失 - 实现一个事故图片上传接口 :后端 MinIO 上传 + 前端 FormData 上传 + 返回 URL 存入
image_urlsJSON 字段 - AI 挑战:让 AI 给你写一个完整的"事故统计接口"------按严重程度分组统计数量,并让它解释 SQL 怎么写的
思维升级
API 是前后端的"合同"。合同要清晰、统一、版本可控。 全栈开发者的核心竞争力之一,就是能设计出前端用起来舒服、后端维护起来不痛苦的数据接口。