今日核心:完成用户端地址管理和购物车功能。这两个模块涵盖了用户端开发中最常用的 Session 管理、ThreadLocal 上下文传递、数据库字段约束处理、MyBatis-Plus 自动填充等核心技能。
一、今日完成的功能模块
| 模块 | 功能 | 接口数量 | 状态 |
|---|---|---|---|
| 地址管理 | 新增、查询列表、查询默认、设置默认、修改、删除 | 7个 | ✅ 完成 |
| 购物车 | 添加、查询列表、清空 | 3个 | ✅ 完成 |
二、核心技术点
2.1 Session 管理用户端登录状态
问题:用户端登录后,后续请求如何识别用户身份?
解决方案:登录成功后,将用户 ID 存入 Session:
session.setAttribute("user", dbUser.getId());后续请求中,从 Session 获取用户 ID:
Long userId = (Long) session.getAttribute("user"); if (userId == null) { return R.error("用户未登录"); }2.2 过滤器中的用户端登录判断
在LoginCheckFilter中,需要同时处理管理端和用户端的登录状态:
// 管理端判断 if (request.getSession().getAttribute("employee") != null) { Long empId = (Long) request.getSession().getAttribute("employee"); BaseContext.setCurrentId(empId); filterChain.doFilter(request, response); return; } // 用户端判断 if (request.getSession().getAttribute("user") != null) { Long userId = (Long) request.getSession().getAttribute("user"); BaseContext.setCurrentId(userId); filterChain.doFilter(request, response); return; }2.3 白名单设计
需要放行的路径(不需要登录即可访问):
String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**", "/user/sendMsg", "/user/login", "/user/logout", "/common/**", "/dish/user/list", "/setmeal/user/list" // 注意:/addressBook/** 不在白名单中,需要登录后访问 };2.4 公共字段自动填充
AddressBook实体类中的createUser、updateUser、createTime、updateTime使用 MyBatis-Plus 自动填充:
@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser;2.5 数据库字段约束处理
遇到的坑:address_book表中的sex字段设置为NOT NULL且无默认值,但前端不传该字段,导致插入失败。
解决方案:修改数据库表结构,允许sex为 NULL:
ALTER TABLE address_book MODIFY COLUMN sex INT NULL;三、地址管理模块详解
3.1 接口清单
| 接口 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 新增地址 | POST | /addressBook | 添加收货地址 |
| 查询列表 | GET | /addressBook/list | 获取当前用户所有地址 |
| 查询默认 | GET | /addressBook/default | 获取默认地址 |
| 设置默认 | PUT | /addressBook/default | 设置指定地址为默认 |
| 修改地址 | PUT | /addressBook | 编辑地址信息 |
| 删除地址 | DELETE | /addressBook | 删除地址 |
| 根据ID查询 | GET | /addressBook/{id} | 获取单个地址详情 |
3.2 核心代码:新增地址
@PostMapping public R<AddressBook> save(@RequestBody AddressBook addressBook) { // 从 Session 获取当前登录用户 ID Long userId = (Long) session.getAttribute("user"); if (userId == null) { return R.error("用户未登录"); } addressBook.setUserId(userId); addressBookService.save(addressBook); return R.success(addressBook); }3.3 核心代码:设置默认地址
@PutMapping("/default") public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) { Long userId = (Long) session.getAttribute("user"); if (userId == null) { return R.error("用户未登录"); } // 1. 将当前用户的所有地址设为非默认 LambdaQueryWrapper<AddressBook> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(AddressBook::getUserId, userId); AddressBook update = new AddressBook(); update.setIsDefault(0); addressBookService.update(update, wrapper); // 2. 将指定地址设为默认 addressBook.setIsDefault(1); addressBookService.updateById(addressBook); return R.success(addressBook); }四、购物车模块详解
4.1 接口清单
| 接口 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 添加 | POST | /shoppingCart/add | 添加菜品/套餐到购物车(数量+1) |
| 查询列表 | GET | /shoppingCart/list | 获取当前用户购物车内容 |
| 清空 | DELETE | /shoppingCart/clean | 清空购物车 |
4.2 核心代码:添加购物车
@PostMapping("/add") public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart) { Long userId = (Long) session.getAttribute("user"); if (userId == null) { return R.error("用户未登录"); } shoppingCart.setUserId(userId); // 查询当前菜品/套餐是否已在购物车中 LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(ShoppingCart::getUserId, userId); if (shoppingCart.getDishId() != null) { wrapper.eq(ShoppingCart::getDishId, shoppingCart.getDishId()); wrapper.eq(ShoppingCart::getDishFlavor, shoppingCart.getDishFlavor()); } else { wrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId()); } ShoppingCart cart = shoppingCartService.getOne(wrapper); if (cart != null) { // 已存在,数量+1 cart.setNumber(cart.getNumber() + 1); shoppingCartService.updateById(cart); } else { // 不存在,新增,数量设为1 shoppingCart.setNumber(1); shoppingCart.setCreateTime(LocalDateTime.now()); shoppingCartService.save(shoppingCart); cart = shoppingCart; } return R.success(cart); }五、遇到的坑及解决方案
坑一:新增地址返回“系统繁忙”
错误信息:
Column 'create_user' cannot be null
原因:/addressBook/**被错误地加到了白名单中,导致请求不经过过滤器,BaseContext中没有用户 ID,自动填充失败。
解决方案:从白名单中删除/addressBook/**,让请求经过过滤器。
坑二:sex字段没有默认值
错误信息:
Field 'sex' doesn't have a default value
原因:数据库表中sex字段设置为NOT NULL且无默认值,但前端不传该字段。
解决方案:修改数据库表结构,允许sex为 NULL:
ALTER TABLE address_book MODIFY COLUMN sex INT NULL;坑三:APIFox 离线空间无法管理 Cookie
问题:在 APIFox 离线空间中测试时,登录后的 Session 无法自动携带。
解决方案:改用浏览器 Console 测试,浏览器会自动管理 Cookie。
六、测试方法(浏览器 Console)
6.1 登录
fetch('/user/login', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({phone: '13812345678', code: '验证码'}) }) .then(res => res.json()) .then(data => console.log('登录结果:', data));6.2 新增地址
fetch('/addressBook', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ consignee: '张三', phone: '13812345678', detail: '北京市朝阳区xxx', label: '家' }) }) .then(res => res.json()) .then(data => console.log('新增地址结果:', data));6.3 查询地址列表
fetch('/addressBook/list') .then(res => res.json()) .then(data => console.log('地址列表:', data));6.4 添加购物车
fetch('/shoppingCart/add', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ dishId: '1397849739276890114', name: '辣子鸡', amount: 7800 }) }) .then(res => res.json()) .then(data => console.log('添加购物车结果:', data));6.5 查询购物车
fetch('/addressBook', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ consignee: '张三', phone: '13812345678', detail: '北京市朝阳区xxx', label: '家' }) }) .then(res => res.json()) .then(data => console.log('新增地址结果:', data));七、面试高频问题及回答
Q1:用户端如何判断登录状态?
答:用户登录成功后,将用户 ID 存入 Session。后续请求通过过滤器判断 Session 中是否存在user属性,如果存在则放行,否则返回NOTLOGIN。
Q2:为什么地址管理不能放在白名单里?
答:地址管理需要获取当前登录用户的 ID 来关联地址。如果放在白名单里,请求不经过过滤器,BaseContext中没有用户 ID,MyBatis-Plus 自动填充createUser等字段时会填入null,导致数据库报错。
Q3:购物车中同一菜品多次添加如何处理?
答:先查询该菜品是否已在当前用户的购物车中。如果存在,则数量 +1;如果不存在,则新增一条记录,数量设为 1。
Q4:APIFox 测试时为什么浏览器正常但 APIFox 不正常?
答:浏览器会自动管理 Cookie,登录成功后后续请求会自动携带 Session ID。APIFox 离线空间不支持自动管理 Cookie,需要手动设置或切换到在线项目。
八、项目当前进度
| 模块 | 状态 |
|---|---|
| 管理端(员工、分类、菜品、套餐、订单) | ✅ 全部完成 |
| 用户端登录 | ✅ 完成 |
| 用户端菜品/套餐展示 | ✅ 完成 |
| 用户端地址管理 | ✅ 完成 |
| 用户端购物车 | ✅ 完成 |
九、写在最后给观众老爷们
为什么会有六那样的测试方法呢?
因为用户端的前端映射出现了问题?我也不知道具体是什么问题,请看图:
本来应该长这样才对: