本笔记源自黑马程序员的视频课程——《瑞吉外卖》,总结了课程笔记、相关知识点以及可能遇到的问题解决方案,并且增加了课程中未实现的功能,供读者参考。笔记全面且条理清晰,希望帮助读者学习和理解这个外卖项目。
本项目全部笔记见:外卖项目笔记合集
1. 个人页面功能开发
1.1 地址管理
1.1.1 需求分析
地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址。

1.1.2 数据模型
用户的地址信息会存储在address_book表,即地址簿表中。具体表结构如下:

1.1.3 准备工作
1、实体类AddressBook(直接从课程资料中导入即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| @Data public class AddressBook implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long userId;
private String consignee;
private String phone;
private String sex;
private String provinceCode;
private String provinceName;
private String cityCode;
private String cityName;
private String districtCode;
private String districtName;
private String detail;
private String label;
private Integer isDefault;
@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;
private Integer isDeleted; }
|
2、Mapper接口AddressBookMapper
1 2 3 4
| @Mapper public interface AddressBookMapper extends BaseMapper<AddressBook> {
}
|
3、Service接口AddressBookService
1 2 3
| public interface AddressBookService extends IService<AddressBook> {
}
|
4、Service实现类AddressBookServicelmpl
1 2 3 4
| @Service public class AddressBookServiceImpl extends ServiceImpl<AddressBookMapper, AddressBook> implements AddressBookService {
}
|
5、控制层AddressBookController(直接从课程资料中导入即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| @Slf4j @RestController @RequestMapping("/addressBook") public class AddressBookController {
@Autowired private AddressBookService addressBookService;
@PostMapping public R<AddressBook> save(@RequestBody AddressBook addressBook) { addressBook.setUserId(BaseContext.getCurrentId()); log.info("addressBook:{}", addressBook); addressBookService.save(addressBook); return R.success(addressBook); }
@PutMapping("default") public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) { log.info("addressBook:{}", addressBook); LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId()); wrapper.set(AddressBook::getIsDefault, 0); addressBookService.update(wrapper);
addressBook.setIsDefault(1); addressBookService.updateById(addressBook); return R.success(addressBook); }
@GetMapping("/{id}") public R get(@PathVariable Long id) { AddressBook addressBook = addressBookService.getById(id); if (addressBook != null) { return R.success(addressBook); } else { return R.error("没有找到该对象"); } }
@GetMapping("default") public R<AddressBook> getDefault() { LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId()); queryWrapper.eq(AddressBook::getIsDefault, 1);
AddressBook addressBook = addressBookService.getOne(queryWrapper);
if (null == addressBook) { return R.error("没有找到该对象"); } else { return R.success(addressBook); } }
@GetMapping("/list") public R<List<AddressBook>> list(AddressBook addressBook) { addressBook.setUserId(BaseContext.getCurrentId()); log.info("addressBook:{}", addressBook);
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId()); queryWrapper.orderByDesc(AddressBook::getUpdateTime);
return R.success(addressBookService.list(queryWrapper)); } }
|
1.1.4 修改地址(补充功能)
这一节为自己补充实现的功能,可供参考。如有问题欢迎讨论。

前后端交互过程:
1、回显信息:用户点击[某个地址]右侧的“修改”按钮,会执行toAddressEditPage(item)方法,发请求到/front/page/address-edit.html?id=${item.id}。在address-eidt.html页面会执行钩子函数,执行initData()方法,其中执行addressFindOneApi(params.id)方法,向/addressBook/${id}发送GET请求。得到服务端响应的数据后,回显到表单中。/addressBook/${id}请求的处理前面已经给出,此处可以实现。
2、更新地址信息到数据表:用户填写表单后,点击“保存地址”按钮,执行saveAddress方法。此时是对某一条地址信息操作,有id,所以执行updateAddressApi(this.form)方法,更新地址,发送ajax请求到/addressBook,请求方式put,携带参数data。需要服务器处理。
代码开发:
在AddressBookController中新增update方法:
1 2 3 4 5 6 7 8 9 10 11 12
|
@PutMapping public R<String> update(@RequestBody AddressBook addressBook){ addressBookService.updateById(addressBook); return R.success("修改成功"); }
|
1.1.5 删除地址(补充功能)
这一节为自己补充实现的功能,可供参考。如有问题欢迎讨论。
用户对某个地址点击编辑按钮后,可以跳转到编辑地址页面,删除该地址。

交互过程:
1、用户点击“删除地址”按钮,执行deleteAddress方法,其中执行deleteAddressApi({ids:this.id })方法,向/addressBook发送DELETE请求,参数跟在url后。
2、如果得到服务器执行成功的响应(code=1),则请求/front/page/address.html,跳转回地址列表页面。
代码开发:
AddressBookController中新增delete方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@DeleteMapping public R<String> delete(String[] ids){ for(String id : ids){ addressBookService.removeById(id); } return R.success("删除成功"); }
|
1.2 退出登录(补充功能)
这一节为自己补充实现的功能,可供参考。如有问题欢迎讨论。
1.2.1 前后端交互过程

在/front/page/user.html页面点击“退出登录”,执行toPageLogin方法,执行loginoutApi()方法,发送请求到/user/loginout,method= post,需要服务端处理。服务端响应后跳转到登录页面/front/page/login.html。

1.2.2 代码开发
在UserController中新增logout方法:
1 2 3 4 5 6 7 8 9 10 11 12
|
@PostMapping("/loginout") public R<String> logout(HttpServletRequest httpServletRequest){ httpServletRequest.getSession().removeAttribute("user"); return R.success("登出成功"); }
|
2. 菜品展示(用户端首页)
2.1 需求分析
用户登录成功后,跳转到系统首页(/front/index.html)。在首页需要根据分类来展示菜品和套餐。如果菜品设置了口味信息,需要展示 [选择规格] 按钮,否则显示 [+] 按钮。
2.2 梳理交互过程&代码开发
在开发代码之前,需要梳理一下前端页面和服务端的交互过程:
1、页面(front/index.html)发送ajax请求,获取分类数据(菜品分类和套餐分类)
具体的过程为:
执行/front/index.html页面的mounted()方法后,执行initData方法。
注:mounted()是在created()函数之后被调用的,即组件实例被创建并初始化后执行的第一个函数。
initData()方法中,执行categoryListApi()方法,和cartListApi({})方法,分别向/category/list
和/shoppingCart/list
发送GET请求。分别是”获取所有的菜品分类”,和”获取购物车内商品的集合”。
/category/list
的处理方法已经写过,但由于前端写的是:Promise.all([categoryListApi(),cartListApi({})]).then()
,意思是:两个方法同时都成功,页面才能渲染。而/shoppingCart/list
请求还没有写,所以展示有问题。
此处可以将这次请求的地址暂时修改一下,从静态json文件获取数据,等后续开发购物车功能时,再修改回来。
front/api/main.js中:
1 2 3 4 5 6 7 8 9
| function cartListApi(data) { return $axios({ 'url':'/front/cartData.json', 'method': 'get', params:{...data} }) }
|
front/cartData.json:
1
| {"code":1,"msg":null,"data":[],"map":{}}
|
加了静态文件/front/cartData.json之后,首页可以看到第一个分类的所有菜品了。
2、点“菜品分类”,显示该分类的所有菜品:
用户每点击一个分类,就发送请求/dish/list?categoryId=xxx&status=1
,以展示该菜品分类下的所有菜品。
之前在DishController里写过对于此请求的处理,能够显示分类名称categoryName。但是此处前端还需要flavors数据。因为有口味数据flavors时,页面需要显示“选择规格”,而不仅仅是单纯的“ + ”。所以还需要去dish_flavor表中查每一个dish的口味数据,然后将flavors响应给前端。改造如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
@GetMapping("/list") public R<List<DishDto>> list(Dish dish){ LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Dish::getStatus, 1); queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId()); queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime); List<Dish> list = dishService.list(queryWrapper);
List<DishDto> dishDtoList = list.stream().map((item)->{ DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item, dishDto);
Long categoryId = item.getCategoryId(); Category category = categoryService.getById(categoryId); if(category!=null){ String categoryName = category.getName(); dishDto.setCategoryName(categoryName); }
Long dishId = item.getId(); LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(DishFlavor::getDishId, dishId); List<DishFlavor> flavors = dishFlavorService.list(lambdaQueryWrapper); dishDto.setFlavors(flavors);
return dishDto; }).collect(Collectors.toList());
return R.success(dishDtoList); }
|
3、点“套餐分类”,显示该分类的所有套餐:
用户每点击一个套餐分类,就发送请求/setmeal/list?categoryId=xxxx&status=1
,以展示该套餐分类下的所有菜品。需要服务端处理。
在SetmealController中新增list方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@GetMapping("list") public R<List<Setmeal>> list(Setmeal setmeal){ LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(setmeal.getCategoryId() != null, Setmeal::getCategoryId, setmeal.getCategoryId() ); queryWrapper.eq(setmeal.getStatus() != null, Setmeal::getStatus, setmeal.getStatus()); queryWrapper.orderByDesc(Setmeal::getUpdateTime);
List<Setmeal> list = setmealService.list(queryWrapper); return R.success(list); }
|
3. 购物车
3.1 需求分析
移动端用户可以将菜品或者套餐添加到购物车。
对于菜品来说,如果设置了口味信息,则需要选择规格后,才能加入购物车;
对于套餐来说,可以直接点击 [+] ****将当前套餐加入购物车。
在购物车中可以修改菜品和套餐的数量,也可以清空购物车。

3.2 数据模型
购物车对应的数据表为shopping_cart表,具体表结构如下:

3.3 梳理交互过程
在开发代码之前,需要梳理一下购物车操作时前端页面和服务端的交互过程:
1、点击”选择规格”,再点击“加入购物车”,或者直接点击“+”按钮,页面发送ajax请求,请求服务端,将菜品或者套餐添加到购物车
这一步的具体过程:
- 用户在某菜品处,点击“选择规格”,执行flavorClick(flavor,item)方法,显示选择口味的窗口。
- 选择后,点“加入购物车”,执行dialogFlavorAddCart()方法,其中执行addCart(item)方法,携带dishFlavor口味、amount金额、dishId菜品id或者setmealId套餐id、name菜名、image图片,执行addCartApi(params)方法,向
/shoppingCart/add
发送请求,method=post。需要服务端处理,将菜品添加到shopping_cart表中。
- 服务端处理后,响应到前端。执行getCartData()方法,其中执行cartListApi({})方法,展示购物车内商品数据。
2、点击购物车图标,页面发送ajax请求/shoppingCart/list,请求服务端查询购物车中的菜品和套餐
3、(补充功能)对购物车内某菜品点击“+”或“-”,页面发送请求,修改菜品数量
4、点击清空购物车按钮,页面发送ajax请求,请求服务端来执行清空购物车操作
这一步的具体过程:
在购物车点击“清空”,执行clearCart方法,其中执行clearCartApi()方法,向/shoppingCart/clean
发送请求,method = delete,需要服务端处理。
3.4 准备工作
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
1、实体类ShoppingCart(直接从课程资料中导入即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Data public class ShoppingCart implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private Long userId;
private Long dishId;
private Long setmealId;
private String dishFlavor;
private Integer number;
private BigDecimal amount;
private String image;
private LocalDateTime createTime; }
|
2、Mapper接口ShoppingCartMapper
1 2 3
| @Mapper public interface ShoppingCartMapper extends BaseMapper<ShoppingCart> { }
|
3、Service接口ShoppingcartService
1 2
| public interface ShoppingCartService extends IService<ShoppingCart> { }
|
4、Service实现类ShoppingCartServicelmpl
1 2 3
| @Service public class ShoppingCartServiceImpl extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService { }
|
5、控制层ShoppingCartController
1 2 3 4 5 6 7 8 9
| @Slf4j @RestController @RequestMapping("/shoppingCart") public class ShoppingCartController {
@Autowired private ShoppingCartService shoppingCartService;
}
|
3.5 代码开发
3.5.1 添加购物车
对应3.3中第1步的需求。
处理请求/shoppingCart/add
,将菜品添加到shopping_cart表中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
@PostMapping("/add") public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){ log.info("购物车数据:{}", shoppingCart); Long currentId = BaseContext.getCurrentId(); shoppingCart.setUserId(currentId);
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ShoppingCart::getUserId, currentId); if(shoppingCart.getDishId() != null){ queryWrapper.eq(ShoppingCart::getDishId, shoppingCart.getDishId()); }else if(shoppingCart.getSetmealId() != null){ queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId()); } ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper); if(cartServiceOne == null){ shoppingCart.setNumber(1); shoppingCart.setCreateTime(LocalDateTime.now()); shoppingCartService.save(shoppingCart); cartServiceOne = shoppingCart; }else { Integer number = cartServiceOne.getNumber(); cartServiceOne.setNumber( number + 1); shoppingCartService.updateById(cartServiceOne); } return R.success(cartServiceOne); }
|
3.5.2 查看购物车
购物车内如果有菜品,就可以点购物车查看。发送请求/shoppingCart/list。
现在可以把之前的前端假数据改回来:
1 2 3 4 5 6 7 8 9
| function cartListApi(data) { return $axios({ 'url': '/shoppingCart/list', 'method': 'get', params:{...data} }) }
|
在ShoppingCartController中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@GetMapping("/list") public R<List<ShoppingCart>> list(){ log.info("查看购物车"); Long currentId = BaseContext.getCurrentId(); LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ShoppingCart::getUserId, currentId); queryWrapper.orderByDesc(ShoppingCart::getCreateTime); List<ShoppingCart> list = shoppingCartService.list(queryWrapper); return R.success(list); }
|
3.5.3 修改菜品数量(补充功能)
这一节为自己补充实现的功能,可供参考。如有问题欢迎讨论。
分析:

代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
@PostMapping("/sub") public R<ShoppingCart> sub(@RequestBody ShoppingCart shoppingCart) { log.info("购物车数据:{}", shoppingCart);
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId()); if(shoppingCart.getDishId() != null){ queryWrapper.eq(ShoppingCart::getDishId, shoppingCart.getDishId()); }else if(shoppingCart.getSetmealId() != null){ queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId()); } ShoppingCart one = shoppingCartService.getOne(queryWrapper); Integer number = one.getNumber(); if(number == 1){ one.setNumber(number - 1); shoppingCartService.remove(queryWrapper); }else{ one.setNumber(number - 1); shoppingCartService.updateById(one); } return R.success(one); }
|
此处可能遇到的问题(已解决):从购物车减少菜品数量,减少至0,会清空购物车。但是从菜单减少菜品数量,减少至0,还会显示“- 1 +”,而不是“选择规格”。
并且从购物车减少菜品后,菜单的数量会刷新,而如果从购物车减菜品减到0,菜单处还会显示“- 1 +”,而不是“选择规格”。
这是因为,前端需要根据number属性的值来显示“选择规格”或者“ - 1 + ”。number属性为0时,显示“选择规格”。所以需要注意:当菜品数量为1时,删除记录之前,也应该给number减1。one.setNumber(number - 1);
1 2 3 4 5 6
| this.dishList.forEach(dish=>{ if(dish.id === (item.dishId || item.setmealId)){ dish.number = (res.data.number === 0 ? undefined : res.data.number) } })
|
3.5.4 清空购物车
处理请求/shoppingCart/clean。将该登录用户对应的所有shopping_cart中的数据都删除。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@DeleteMapping("/clean") public R<String> clean(){ LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId()); shoppingCartService.remove(queryWrapper);
return R.success("清空购物车成功"); }
|
4. 用户端订单功能
4.1 用户下单
4.1.1 需求分析
用户将菜品或者套餐加入购物车后,可以点击购物车中的“去结算”按钮,页面跳转到订单确认页面,点击“去支付”按钮,完成下单操作。

4.1.2 数据模型
用户下单业务对应的数据表为orders表和order_detail表。
orders表(订单表):

order_detail表(订单明细表):

4.1.3 梳理交互过程
在开发代码之前,需要梳理一下用户下单操作时,前端页面和服务端的交互过程:
1、在购物车中点击“去结算”按钮,页面跳转到订单确认页面
这一步的具体过程为:
用户将菜品或者套餐加入购物车后,点击购物车中的“去结算”按钮,执行toAddOrderPage()方法,跳转到/front/page/add-order.html
页面。
2、在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的默认地址
这一步的具体过程为:
在/front/page/add-order.html
页面,会执行钩子函数,执行initData()方法,执行defaultAddress()方法,其中执行getDefaultAddressApi()方法,请求/addressBook/default
,method = get,获取该用户的默认地址,显示到下单页面上。需要服务端处理,返回默认地址信息。前面已经写过。
如果响应成功,执行getFinishTime()方法,获取送达时间;如果响应失败(没有获取到默认地址),跳转到地址编辑页面/front/page/address-edit.html
。
3、在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的购物车数据
这一步的具体过程为:
在/front/page/add-order.html
页面,会执行钩子函数,执行initData()方法,执行getCartData()方法,其中执行cartListApi({})方法,请求/shoppingCart/list
,method = get,获取购物车内商品的集合,显示到下单页面上。需要服务端把购物车商品集合数据返回。前面已经写过。
4、在订单确认页面点击“去支付”按钮,发送ajax请求,请求服务端完成下单操作
这一步的具体过程为:
点击“去支付”,执行goToPaySuccess方法,其中执行addOrderApi(params)方法,携带的参数是备注信息remark、付款方式payMethod、addressBookId,请求/order/submit
,method=post。需要服务端处理,将订单信息保存至数据库。服务端操作成功并响应后,前端跳转到/front/page/pay-success.html
页面。
4.1.4 准备工作
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
1、实体类Orders、OrderDetail(直接从课程资料中导入即可)
Orders:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @Data public class Orders implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String number;
private Integer status;
private Long userId;
private Long addressBookId;
private LocalDateTime orderTime;
private LocalDateTime checkoutTime;
private Integer payMethod;
private BigDecimal amount;
private String remark;
private String userName;
private String phone;
private String address;
private String consignee; }
|
OrderDetail:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Data public class OrderDetail implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private Long orderId;
private Long dishId;
private Long setmealId;
private String dishFlavor;
private Integer number;
private BigDecimal amount;
private String image; }
|
2、Mapper接口OrderMapper、OrderDetailMapper
OrderMapper:
1 2 3
| @Mapper public interface OrderMapper extends BaseMapper<Orders> { }
|
OrderDetailMapper:
1 2 3
| @Mapper public interface OrderDetailMapper extends BaseMapper<OrderDetail> { }
|
3、业务层接口OrderService、OrderDetailService
OrderService:
1 2
| public interface OrderService extends IService<Orders> { }
|
OrderDetailService:
1 2
| public interface OrderDetailService extends IService<OrderDetail> { }
|
4、业务层实现类OrderServicelmpl、OrderDetailServicelmpl
OrderServicelmpl:
1 2 3
| @Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService { }
|
OrderDetailServicelmpl:
1 2 3
| @Service public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService { }
|
5、控制层OrderController、OrderDetailController
OrderController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Slf4j @RestController @RequestMapping("/order") public class OrderController {
@Autowired private OrderService orderService;
@Autowired OrderDetailService orderDetailService;
@Autowired ShoppingCartService shoppingCartService;
@Autowired UserService userService;
}
|
OrderDetailController:
1 2 3 4 5 6 7 8 9
| @Slf4j @RestController @RequestMapping("/orderDetail") public class OrderDetailController {
@Autowired private OrderDetailService orderDetailService;
}
|
4.1.5 代码开发
在OrderController新增submit方法,将信息保存至数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@PostMapping("/submit") public R<String> submit(@RequestBody Orders orders){ orderService.submit(orders); return R.success("支付成功"); }
|
在OrderService新增submit方法,并在OrderServicelmpl中实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
@Override @Transactional public void submit(Orders orders) { Long currentId = BaseContext.getCurrentId();
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ShoppingCart::getUserId, currentId); List<ShoppingCart> shoppingCartList = shoppingCartService.list(queryWrapper);
if(shoppingCartList == null || shoppingCartList.size() == 0){ throw new CustomException("购物车为空,不能下单"); }
User user = userService.getById(currentId);
AddressBook addressBook = addressBookService.getById(orders.getAddressBookId()); if(addressBook == null){ throw new CustomException("用户地址有误,不能下单"); } long orderId = IdWorker.getId(); AtomicInteger amount = new AtomicInteger(0);
List<OrderDetail> orderDetails = shoppingCartList.stream().map( (item) -> { OrderDetail orderDetail = new OrderDetail(); BeanUtils.copyProperties(item, orderDetail, "userId", "createTime"); orderDetail.setOrderId(orderId); amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue()); return orderDetail; }).collect(Collectors.toList());
orders.setNumber(String.valueOf(orderId)); orders.setId(orderId); orders.setStatus(2); orders.setUserId(currentId); orders.setOrderTime(LocalDateTime.now()); orders.setCheckoutTime(LocalDateTime.now()); orders.setAmount(new BigDecimal(amount.get())); orders.setPhone(addressBook.getPhone()); String address = (addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName()) + (addressBook.getCityName() == null ? "" : addressBook.getCityName()) + (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName()) + (addressBook.getDetail() == null ? "" : addressBook.getDetail()); orders.setAddress(address); orders.setUserName(user.getName()); orders.setConsignee(addressBook.getConsignee());
this.save(orders);
orderDetailService.saveBatch(orderDetails);
shoppingCartService.remove(queryWrapper); }
|
4.2 用户查看订单&历史订单
4.2.1 梳理交互过程和需求
1、下单成功后,在/front/page/order.html
页面点击“查看订单”,发送请求到/order/userPage?page=1&pageSize=5

具体过程为:
用户下单后,跳转到/front/page/pay-success.html
页面。点“查看订单”,执行toOrderPage方法,请求/front/page/order.html
。
在/front/page/order.html
页面的钩子函数中,执行getList()方法,执行orderPagingApi(this.paging)方法,请求/order/userPage?page=1&pageSize=5
。需要服务端处理,响应回分页信息records。
4.2.2 代码开发
目标:处理请求/order/userPage?page=1&pageSize=5
并响应回分页信息records。
在OrderController中新增userPage方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
|
@GetMapping("/userPage") public R<Page> userPage(int page, int pageSize){
Page<Orders> pageInfo = new Page<>(page, pageSize); Page<OrdersDto> ordersDtoPage = new Page<>();
LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Orders::getUserId, BaseContext.getCurrentId()); queryWrapper.orderByDesc(Orders::getOrderTime); orderService.page(pageInfo, queryWrapper);
BeanUtils.copyProperties(pageInfo, ordersDtoPage,"records"); List<Orders> records = pageInfo.getRecords();
List<OrdersDto> list = records.stream().map((item)->{ OrdersDto ordersDto = new OrdersDto(); BeanUtils.copyProperties(item, ordersDto); Long orderId = item.getId(); LambdaQueryWrapper<OrderDetail> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(OrderDetail::getOrderId, orderId); List<OrderDetail> orderDetails = orderDetailService.list(lambdaQueryWrapper); ordersDto.setOrderDetails(orderDetails);
Integer sumNum = 0; for (OrderDetail orderDetail : orderDetails) { Integer number = orderDetail.getNumber(); sumNum += number; } ordersDto.setSumNum(sumNum);
return ordersDto; }).collect(Collectors.toList());
ordersDtoPage.setRecords(list);
return R.success(ordersDtoPage); }
|
5. 管理后台订单功能
5.1 商家后台订单明细

5.1.1 需求分析
在管理后台首页/backend/index.html,点击“订单明细”,跳转到/backend/page/order/list.html。
该页面会首先执行钩子函数created(),执行init()方法,其中执行getOrderDetailPage({ page: this.page, pageSize: this.pageSize, number: this.input || undefined, beginTime: this.beginTime || undefined, endTime: this.endTime || undefined })方法,通过url带着page、pageSize、number(订单号)、beginTime、endTime等参数,发送请求到/order/page
,method=get。需要服务器处理,并响应回来分页数据。
5.1.2 代码开发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
@GetMapping("/page") public R<Page> page(Integer page, Integer pageSize, String number, String beginTime, String endTime){
Page<Orders> pageInfo = new Page<>(page, pageSize);
LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(StringUtils.isNotEmpty(number), Orders::getNumber, number); if (beginTime != null && endTime != null) { queryWrapper.ge(Orders::getOrderTime, beginTime); queryWrapper.le(Orders::getOrderTime, endTime); } queryWrapper.orderByDesc(Orders::getOrderTime);
orderService.page(pageInfo, queryWrapper);
return R.success(pageInfo); }
|
5.2 商家订单派送状态更改
5.2.1 需求分析

在backend/page/order/list.html页面点击“派送”,会执行cancelOrDeliveryOrComplete (status, id)方法,要求确认是否更改订单状态,如果确认,会执行editOrderDetail(params)方法,发送ajax请求至/order
,method=put,携带数据是id(订单id)和status(新的订单状态)。需要服务端处理该请求,根据订单id,修改orders表中的status,并响应回前端。
前端判断status是不是3,如果是3,就在页面显示“订单已派送”,否则显示“订单已完成”。
5.2.2 代码开发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@PutMapping public R<Orders> editStatus(@RequestBody Orders orders){ Long orderId = orders.getId();
Orders ordersInDB = orderService.getById(orderId); ordersInDB.setStatus(orders.getStatus()); orderService.updateById(ordersInDB);
return R.success(ordersInDB); }
|