Files
gc-plan/week7/教案.md
2026-04-29 23:45:17 +08:00

10 KiB
Raw Blame History

Week 7前后端分离实战

目标:将 Vue 3 前端与 Spring Boot 后端完整对接,交付前后端分离的学生管理系统 v3。

前置:完成 Week 5Spring Boot 后端)和 Week 6Vue 3 前端基础)。

本周产出:前后端分离的完整 SPA 应用含登录注册、JWT 认证、CRUD、分页搜索、角色权限。

启动方式

# 1. 启动后端IDEA 中运行 Week7Application.java
# 2. 启动前端
cd week7/frontend
npm install    # 首次运行
npm run dev    # http://localhost:5173

Day 1前后端分离架构设计

知识点

  • 前后端分离 vs 单体架构:各自优劣
  • 前端Vite 开发服务器5173→ 用户浏览器渲染
  • 后端Spring Boot8080→ 只返回 JSON不渲染 HTML
  • 通信方式HTTP + JSONJWT 无状态认证
  • Vite proxy开发环境将 /api 请求代理到后端(解决跨域)
  • 生产环境Nginx 反向代理,统一域名/端口
  • CORSCross-Origin Resource SharingSpring Security 如何配置

动手 → 理解

  1. 阅读 vite.config.jsserver.proxy 配置,理解代理原理
  2. 阅读 SecurityConfig.javacorsConfigurationSource() Bean
  3. 在 DevTools Network 面板查看:/api/students 请求的远程地址是 5173 还是 8080
  4. 对比 Week 5 的单体架构和 Week 7 的分离架构图
  5. 把 Vite proxy 注释掉观察跨域错误CORS error

核心文件

  • frontend/vite.config.jsserver.proxy
  • backend/.../config/SecurityConfig.java → CORS 配置
  • frontend/src/api/http.js → Axios 实例

思考题前后端分离的核心优势是什么有什么代价首次加载速度、SEO


Day 2登录注册页面 + JWT 对接

知识点

  • 登录流程:表单输入 → Axios POST → 后端验证 → 返回 JWT → Pinia store 保存 → 跳转首页
  • 注册流程:表单输入 → POST → 后端创建用户 → 返回 JWT → 自动登录
  • Pinia Auth Storetokenusernamerole 状态管理
  • localStorage:持久化 Token页面刷新不丢失
  • <router-link> vs 编程式导航 router.push()
  • 导航守卫:router.beforeEach() 检查登录状态
  • HTTP 拦截器:自动附加 Authorization: Bearer <token>

动手 → 理解

  1. 打开 DevTools → Application → Local Storage观察 wk7_tokenwk7_usernamewk7_role
  2. 在登录页输入错误密码,观察 Network 面板的 401 响应
  3. 在 DevTools 中手动删除 Local Storage 的 token刷新页面观察跳转
  4. 阅读 router/index.jsbeforeEach 守卫逻辑
  5. 注册一个新账号后,检查数据库中 users 表是否新增记录

核心文件

  • frontend/src/views/LoginView.vue — 登录页面
  • frontend/src/views/RegisterView.vue — 注册页面
  • frontend/src/stores/auth.js — 认证状态管理
  • frontend/src/api/auth.js — 认证 API
  • frontend/src/router/index.js — 导航守卫

思考题:为什么 Token 存在 localStorage 而不是 Pinia store 中Pinia store 刷新后会丢失吗?


Day 3CRUD 页面实现

知识点

  • 列表渲染:v-for + Axios GET 请求
  • 新增/编辑弹窗:v-if 控制显示,表单双向绑定 v-model
  • 表单校验HTML5 原生校验 + 自定义 JS 校验
  • 删除确认:confirm() + DELETE 请求
  • 区分新增和编辑:根据是否传入 student 对象
  • 角色权限:v-if="auth.isAdmin" 控制按钮显示
  • 乐观更新 vs 悲观更新(操作后重新拉取数据)

动手 → 理解

  1. 用 admin 登录,点击"新增",填写表单提交 → 观察 Network 面板 POST 请求
  2. 点击"编辑",修改成绩后保存 → 观察 PUT 请求的请求体
  3. 点击"删除" → 确认后观察 DELETE 请求,刷新页面确认数据消失
  4. 用 user 登录,观察"新增/编辑/删除"按钮是否隐藏
  5. 用 user 登录,在浏览器控制台执行 fetch('/api/students', {method:'POST'}) 看 403

核心文件

  • frontend/src/views/StudentListView.vue — 列表 + 表格
  • frontend/src/components/StudentForm.vue — 表单弹窗props/emits
  • frontend/src/api/student.js — CRUD API 调用

思考题:为什么不直接在表格行内编辑,而是用弹窗?各自适用什么场景?


Day 4分页、搜索、排序

知识点

  • 分页参数:page0-basedsize(每页数量)
  • 前端分页 vs 后端分页:数据量决定策略
  • 分页组件:首页/上一页/页码/下一页/末页 + 省略号逻辑
  • 搜索防抖:@keyup.enter 触发搜索(避免每次按键都请求)
  • 排序:后端 ORDER BY 实现MP LambdaQueryWrapper / JPA Sort
  • URL 状态同步:搜索关键词和页码是否反映在 URL 参数中

动手 → 理解

  1. 在搜索框输入"张",按回车 → 观察请求 URL 中的 keyword 参数
  2. 点击分页组件的"下一页" → 观察 page 参数变化
  3. 打开 Pagination.vue,理解省略号(...)的生成逻辑
  4. 修改后端 StudentMpService.list() 中的排序字段为 age → 观察列表顺序变化
  5. 在 Network 面板中对比:搜索前后 total 数量的变化

核心文件

  • frontend/src/components/Pagination.vue — 分页组件
  • frontend/src/views/StudentListView.vuesearch() / goPage() 方法
  • backend/.../service/mp/StudentMpService.java → LambdaQueryWrapper 排序

思考题:为什么页码从 0 开始Spring Data Pageable而不是从 1 开始?


Day 5文件上传头像

知识点

  • 前端:<input type="file"> + FormData
  • 后端:MultipartFile 接收文件
  • Spring Boot 文件上传配置:spring.servlet.multipart.max-file-size
  • 文件存储:本地目录 vs OSS对象存储
  • 文件命名UUID 避免冲突
  • 文件访问:静态资源映射 addResourceHandlers()
  • 预览:上传后在前端显示缩略图

动手 → 理解

  1. 在表单中增加 <input type="file"> 字段
  2. FormData 封装文件 + JSON 字段POST 到后端
  3. 检查后端 StudentController 中的 @RequestParam MultipartFile 处理
  4. 上传一个图片后,浏览器直接访问上传后的 URL
  5. 尝试上传超大文件(>10MB观察 MaxUploadSizeExceededException

核心文件

  • frontend/src/components/StudentForm.vue → 文件输入
  • backend/.../controller/StudentController.java → MultipartFile 处理
  • backend/.../application.yml → multipart 配置

思考题为什么不在数据库中存储文件二进制数据BLOB而是存储文件路径


Day 6交互体验完善

知识点

  • Loading 状态:数据加载中显示 spinner/骨架屏
  • 错误状态:网络异常、后端报错时的用户提示
  • 空状态:无数据时的友好占位
  • 按钮 Loading提交时禁用按钮 + 显示"保存中..."
  • Toast 消息:操作成功/失败的轻提示
  • 乐观更新:先更新 UI 再发请求(失败时回滚)
  • 防抖debounce和节流throttle

动手 → 理解

  1. 强制停止后端,刷新前端页面 → 观察错误状态展示
  2. 删除所有学生数据 → 观察空状态提示
  3. 点击保存按钮时,快速双击 → 观察 loading 状态是否阻止了重复提交
  4. 修改 StudentListView.vue 中的 loading 实现为骨架屏
  5. 给搜索框加 300ms 的防抖

核心文件

  • frontend/src/views/StudentListView.vue → loading / error / empty 三种状态
  • frontend/src/components/StudentForm.vueformLoading 按钮状态

思考题Loading、Empty、Error 三种状态分别应该在哪里处理(组件内 vs 全局)?


Day 7前后端联调 & 部署概念

知识点

  • 前后端联调:同时启动两个项目,确认所有接口正常
  • Vite build生产环境构建 → 输出 dist/ 目录
  • Nginx 部署:反向代理将前后端统一到 80 端口
  • Nginx 配置示例:location /api { proxy_pass http://localhost:8080; } + location / { root dist/; }
  • 环境变量:.env.development vs .env.production
  • Docker 部署概念:前端容器 + 后端容器 + Nginx 容器
  • 部署检查清单数据库连接、Redis 连接、CORS 配置

动手 → 理解

  1. 运行 npm run build,观察生成的 dist/ 目录结构
  2. npx serve dist/ 预览生产构建
  3. 用 Postman 测试后端所有 API 端点GET/POST/PUT/DELETE + Token
  4. 画出完整的请求流程图:浏览器 → Nginx → 前端静态文件 / 后端 API
  5. 阅读 Nginx 反向代理配置示例

核心概念

           ┌─────────────┐
           │   浏览器      │
           │ localhost:80  │
           └──────┬───────┘
                  │
           ┌──────┴───────┐
           │   Nginx       │
           │   端口 80     │
           └──┬────────┬───┘
              │        │
     /        │        │  /api
              │        │
    ┌─────┴──┐  ┌─────┴─────┐
    │ dist/  │  │ Spring    │
    │ 静态   │  │ Boot:8080 │
    └────────┘  └───────────┘

思考题Vite proxy 只用于开发环境,为什么生产环境要用 Nginx 而不是继续用 Vite proxy


Week 7 总结

维度 Week 5单体 Week 6纯前端 Week 7分离
前端框架 原生 HTML/JS Vue 3 独立 Vue 3 + 后端对接
页面路由 Vue Router Vue Router + 导航守卫
状态管理 localStorage Pinia 独立 Pinia + API 同步
认证 Login 页面display 切换) 模拟登录 JWT 真实认证
数据 fetch 直连 无后端 Axios + 拦截器
部署 Spring Boot 单端口 Vite dev 前后端分端口 / Nginx

下一阶段Week 8 — 工程化能力

  • JUnit 5 + Mockito 单元测试
  • Testcontainers 集成测试
  • Knife4j API 文档
  • Docker 容器化
  • Git 工作流
  • CI/CD 入门