从 Vue 到万物:AI 加速下的前端开发者全栈进化论【第四课】

Day 4 | API 设计 & 全栈串联:前端和后端终于"对话"了


API 设计的核心原则(前端开发者的视角)

前端写 API 调用,天然理解 HTTP 请求。但"设计" API 是什么感觉?

想象你是后端,别人(前端)要来拿数据。你要考虑:

  1. 我要暴露哪些端点? ------ 粒度怎么切?
  2. 请求参数怎么传? ------ path variable?query string?还是 body JSON?
  3. 返回什么格式? ------ 直接返回数据库记录?还是加工过的结构?
  4. 谁来调用? ------ 只有你的前端?还是第三方?

这就是 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' } })


今日任务清单

  1. 实现完整的登录流程:后端 JWT 登录接口 + 前端 axios 调用 + token 存储 + 请求拦截器附加 token
  2. 配置 Vite 代理 :让前端开发服务器正确代理到 localhost:8080,验证 CORS 问题消失
  3. 实现一个事故图片上传接口 :后端 MinIO 上传 + 前端 FormData 上传 + 返回 URL 存入 image_urls JSON 字段
  4. AI 挑战:让 AI 给你写一个完整的"事故统计接口"------按严重程度分组统计数量,并让它解释 SQL 怎么写的

思维升级

API 是前后端的"合同"。合同要清晰、统一、版本可控。 全栈开发者的核心竞争力之一,就是能设计出前端用起来舒服、后端维护起来不痛苦的数据接口。

相关推荐
用户938515635071 天前
从一个"房子"讲起:为什么 Next.js 是面向 AI 的全栈框架(附博客实战拆解)
ai编程·全栈·next.js
小酒星小杜3 天前
不会画画,也能把故事做成漫画吗?先做这场测试
人工智能·产品·全栈
东方小月4 天前
从零开发一个 Coding Agent(十三):实现安全的 read 文件读取工具
前端·人工智能·全栈
小酒星小杜4 天前
AI漫画真正的门槛,不是出图,而是让角色持续出演
人工智能·产品·全栈
GitLqr4 天前
Flutter 实战:图片上传前的压缩优化,解决带宽与存储的“大户”问题
flutter·全栈·dart
面包龙5 天前
什么是 AI?从人工智能、机器学习到大语言模型和 Agent
人工智能·程序员·全栈
SL_staff5 天前
中小离散制造如何用工艺路线模板解决‘人走流程丢’——JVS-APS 实践解析
java·设计模式·全栈
Bruce_Liuxiaowei6 天前
从零到可运行:基于 Vue3 + FastAPI + DeepSeek-V3 的 AI 英语单词学习系统全栈实战
人工智能·python·学习·fastapi·全栈·智能体
HjhIron6 天前
手把手教你用 Next.js 14 + Redis 从零搭建一个全栈 Markdown 笔记系统
前端·全栈·next.js