学习【瑞吉外卖⑦】SpringBoot单体项目_移动端开发



  • 本文章发布在CSDN上,一是方便博主自己线上阅览,二是巩固自己所学知识。
  • 博客内容主要参考上述视频和资料,视频中出现的 PPT 内容大体也为本文所录。
  • 对于视频中要求自行开发的功能,本博客也补充了。


  • 若文章内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系博主删除。


上一篇学习瑞吉外卖⑥SpringBoot单体项目https://blog.csdn.net/yanzhaohanwei/article/details/125228024


0.总目录



  • 效果展示(菜品展示功能、购物车功能、下单功能)

1.效果展示图_1


1.用户地址簿业务功能


1.1.需求分析


地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。

同一个用户可以有多个地址信息,但是只能有一个默认地址。

2.用户地址簿相关功能展示图


1.2.数据模型


用户的地址信息会存储在 address_book 表,即 地址簿表 中。

3.address_book 表结构


1.3.准备工作



  1. 实体类 AddressBook

com/itheima/reggie/entity/AddressBook.java

package com.itheima.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 地址簿
 */
@Data
public class AddressBook implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;

    //用户 id
    private Long userId;

    //收货人
    private String consignee;

    //手机号
    private String phone;

    //性别 0 女 1 男
    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;

    //是否默认 0 否 1是
    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;
}

  1. Mapper 接口 AddressBookMapper

com/itheima/reggie/mapper/AddressBookMapper.java

package com.itheima.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.AddressBook;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface AddressBookMapper extends BaseMapper<AddressBook> { }

  1. 业务层接口 AddressBookService

com/itheima/reggie/service/AddressBookService.java

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.AddressBook;

public interface AddressBookService extends IService<AddressBook> { }

  1. 业务层实现类 AddressBookServicelmpl

com/itheima/reggie/service/impl/AddressBookServiceImpl.java

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.AddressBook;
import com.itheima.reggie.mapper.AddressBookMapper;
import com.itheima.reggie.service.AddressBookService;
import org.springframework.stereotype.Service;

@Service
public class AddressBookServiceImpl extends ServiceImpl<AddressBookMapper, AddressBook> implements AddressBookService { }

  1. 控制层 AddressBookController

com/itheima/reggie/controller/AddressBookController.java

package com.itheima.reggie.controller;

import com.itheima.reggie.service.AddressBookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 地址簿管理
 */
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {
    @Autowired
    private AddressBookService addressBookService;
}

1.4.代码开发


1.4.1.新增地址簿


com/itheima/reggie/controller/AddressBookController.java

/**
 * 新增地址
 *
 * @param addressBook
 * @return
 */
@PostMapping
public R<AddressBook> save(@RequestBody AddressBook addressBook) {
    addressBook.setUserId(BaseContext.getCurrentId());
    log.info("addressBook:{}", addressBook);
    addressBookService.save(addressBook);
    return R.success(addressBook);
}

1.4.2.设置默认地址


com/itheima/reggie/controller/AddressBookController.java

/**
 * 设置默认地址
 *
 * @param addressBook
 * @return
 */
@PutMapping("default")
public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
    log.info("addressBook:{}", addressBook);
    LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<AddressBook>();
    wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
    wrapper.set(AddressBook::getIsDefault, 0);
    //SQL:update address_book set is_default = 0 where user_id = ?
    addressBookService.update(wrapper);

    addressBook.setIsDefault(1);
    //SQL:update address_book set is_default = 1 where id = ?
    addressBookService.updateById(addressBook);
    return R.success(addressBook);
}

1.4.3.根据id查询地址


com/itheima/reggie/controller/AddressBookController.java

/**
 * 根据 id 查询地址
 *
 * @param id
 * @return
 */
@GetMapping("/{id}")
public R get(@PathVariable Long id) {
    AddressBook addressBook = addressBookService.getById(id);
    if (addressBook != null) {
        return R.success(addressBook);
    } else {
        return R.error("没有找到该对象");
    }
}

1.4.4.查询默认地址


com/itheima/reggie/controller/AddressBookController.java

/**
 * 查询默认地址
 *
 * @return
 */
@GetMapping("default")
public R<AddressBook> getDefault() {
    LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
    queryWrapper.eq(AddressBook::getIsDefault, 1);

    //SQL:select * from address_book where user_id = ? and is_default = 1
    AddressBook addressBook = addressBookService.getOne(queryWrapper);

    if (null == addressBook) {
        return R.error("没有找到该对象");
    } else {
        return R.success(addressBook);
    }
}

1.4.5.查询指定用户的全部地址


com/itheima/reggie/controller/AddressBookController.java

/**
 * 查询指定用户的全部地址
 *
 * @param addressBook
 * @return
 */
@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);

    //SQL:select * from address_book where user_id = ? order by update_time desc
    return R.success(addressBookService.list(queryWrapper));
}

1.5.功能测试


http://localhost:8080/front/index.html

4.登录后的页面


http://localhost:8080/front/page/user.html

5.用户管理中心界面


http://localhost:8080/front/page/address.html

6.用户地址界面


http://localhost:8080/front/page/address-edit.html

  • 这里的手机号不一定需要与我们登录的手机号一致,这里并没有限制条件。

7.新增地址_1

  • 我们这里再新增一个地址。

8.新增地址_2


http://localhost:8080/front/page/address.html

  • 选中其一为默认地址。
    9.新增地址界面设置默认地址

  • 这时我们可以打开数据库观看,发现相应记录中的 is_default 字段的值变为了 1

10.地址表的is_default的情况


1.6.地址簿功能补充


显然,上述的地址管理功能是不完整的。

地址管理既然能 增加,也必然可以 修改删除


同时,官方也给我们写好了前端,那么我们只需写一些后台代码就行了。

官方要是不给前端页面的话,我这种懒人是懒得补未完成的代码功能的。


http://localhost:8080/front/page/address.html

11


http://localhost:8080/front/page/address-edit.html?id=1535994735380975618

  • 在编辑界面里,我们就可以执行删除地址或者是编辑地址的操作了。

12

  • 由上面给出的路径我们很容易推测出前端页面是 front/page/address-edit.html(前端资源中命名较为规范)

1.6.1.编辑地址功能


编辑地址簿信息的功能


  • 前端分析编辑地址功能

当我们进入到之前已经添加过的用户的界面时,再次点击保存页面时,前端控制台网络状态为 404

之前我们在 AddressBookController 类中已经写好了地址簿信息的回显方法


当前页面地址:http://localhost:8080/front/page/address-edit.html?id=1535994735380975618

在这里插入图片描述

在这里插入图片描述


  • 代码编写编辑地址功能

com/itheima/reggie/controller/AddressBookController.java

/**
 * 修改地址
 *
 * @param addressBook
 * @return
 */
@PutMapping
public R<String> update(@RequestBody AddressBook addressBook) {
    log.info("addressBook:{}", addressBook);
    if (addressBook == null) {
        return R.error("数据异常");
    }
    addressBookService.updateById(addressBook);
    return R.success("修改数据成功");
}

1.6.2.删除地址功能


删除地址簿信息的功能


  • 前端分析删除地址功能

浏览器控制台上显示查询到的 字符串参数ids: 1535994735380975618,传递的数据就是这个 ids

在这里插入图片描述


  • 代码编写删除地址功能

com/itheima/reggie/controller/AddressBookController.java

/**
 * 删除地址
 *
 * @param id
 * @return
 */
@DeleteMapping
public R<String> delete(@RequestParam("ids") Long id) {
    if (id == null) {
        return R.error("请求异常");
    }
    /*int addressStatus = addressBookService.getById(id).getIsDefault();
    if (addressStatus == 1) {
        return R.error("默认地址不可删除");
    }*/
    LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<AddressBook>();
    queryWrapper.eq(AddressBook::getId, id);
    queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
    addressBookService.remove(queryWrapper);
    return R.success("删除地址成功");
}

2.菜品展示功能


2.1.需求分析


用户登录成功后跳转到系统首页,在首页需要根据分类来展示菜品和套餐。

如果菜品设置了口味信息需要展示 [选择规格] 按钮,否则显示 [+] 按钮。

在这里插入图片描述


2.2.梳理交互过程


在开发代码之前,需要梳理一下前端页面和服务端的交互过程

  1. 页面(front/index.html)发送 ajax 请求,获取分类数据(菜品分类和套餐分类)
  2. 页面发送 ajax 请求,获取第一个分类下的菜品或者套餐

开发菜品展示功能,其实就是在服务端编写代码去处理前端页面发送的这 2 次请求即可。


首页 localhost:8080/front/index.html 加载完成后,还发送了一次 ajax 请求用于加载购物车数据。

在这里插入图片描述
在这里插入图片描述


以下是 resources 目录下的 front/index.html 中的部分代码

methods:{
    //初始化数据
    initData(){
      Promise.all([categoryListApi(),cartListApi({})]).then(res=>{
        ... ...
    },
    ... ...
}

相关方法在 resources 目录下的 front/api/main.js 里。

//获取所有的菜品分类
//获取所有的菜品分类
function categoryListApi() {
    return $axios({
        'url': '/category/list',
        'method': 'get',
    })
}

... ...

//获取购物车内商品的集合
function cartListApi(data) {
    return $axios({
        'url': '/shoppingCart/list',
        'method': 'get',
        params: {...data}
    })
}

此处可以将这次请求的地址暂时修改一下,从静态 json 文件获取数据,等后续开发购物车功能时再修改回来。

resources 目录下的 front/api/main.js

//获取购物车内商品的集合
function cartListApi(data) {
    return $axios({
    	/******************************************************/
        //'url': '/shoppingCart/list', // 注释掉的代码
        'url':'/front/cartData.json', // 添加的代码
        /******************************************************/
        'method': 'get',
        params: {...data}
    })
}

resources 目录下的 front/cartData.json 的内容

{
  "code": 1,
  "msg": null,
  "data": [],
  "map": {}
}

进行完以上的操作后,就可以正常展示菜品了。

在这里插入图片描述

  • 显然,我们易推出这是之前写过的两个方法于此发挥了作用。
    • com/itheima/reggie/controller/CategoryController.java 写的 list 方法(根据条件查询分类数据)
    • com/itheima/reggie/controller/DishController.java 写的 list (根据条件查询对应的菜品数据)

但是,这里有个 Bug

该项目前端处理后的结果是:如果菜品设置了口味信息需要展示 [选择规格] 按钮,否则显示 [+] 按钮。

但这里是什么也没有显示,故我们要修改部分代码。

在这里插入图片描述

究其原因还是list 方法返回的对象中的 Dish 类里没有口味的相关属性。

我们可以把其改为 DishDto 类,之前继承了 Dish 类,且拓展了 flavors 等属性。

根据 DishDto 来更改 com/itheima/reggie/controller/DishController.java 里的 list 方法的代码。


2.3.代码开发


改造一下 DishController 类中的 list 方法。

com/itheima/reggie/controller/DishController.java

/**
 * 根据条件查询对应的菜品数据 和 菜品口味的数据
 *
 * @param dish
 * @return
 */
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish) {
    //构造查询条件
    LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<Dish>();
    //添加查询条件
    queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
    queryWrapper.eq(Dish::getStatus, 1);
    //添加排序条件
    queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
    //调用 service 方法拿到 list 集合
    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();//分类 id
        //根据 id 查询分类对象
        Category category = categoryService.getById(categoryId);

        if (category != null) {
            String categoryName = category.getName();
            dishDto.setCategoryName(categoryName);
        }

        //获取的当前菜品的 id
        Long dishId = item.getId();

        LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<DishFlavor>();
        lambdaQueryWrapper.eq(DishFlavor::getDishId, dishId);
        //最终获取到了口味的集合对象:select * from dish_flavor where dish_id = ?
        List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
        dishDto.setFlavors(dishFlavorList);

        return dishDto;
    }).collect(Collectors.toList());
    /* *********************************************************** */

    return R.success(dishDtoList);
}

最终的效果如下

在这里插入图片描述


2.4.套餐展示问题


2.4.1.前端分析


点击 XX套餐 时,浏览器控制台显示 404

在这里插入图片描述

显然,我们根据上图可以推出解决办法:在 setmealController 编写 list 方法(根据分类 id 查询对应的套餐信息)。


2.4.2.代码编写


com/itheima/reggie/controller/SetmealController.java

/**
 * 根据分类 id 查询对应的套餐信息
 *
 * @param setmeal
 * @return
 */
@RequestMapping("/list")
public R<List<Setmeal>> list(Setmeal setmeal) {
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<Setmeal>();
    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);
}

最终效果如下

在这里插入图片描述


2.5.套餐展示功能再修复


套餐展示问题的修复

  • 此处依旧是 套餐展示问题 的修复,视频中(或 2.4 中)并未完全解决该问题。

2.5.1.前端分析


我们点击菜品图片时,页面会跳出菜品的具体信息。

在这里插入图片描述

但当我们点击套餐时,控制台的网络状态显示:404

当前页面地址:http://localhost:8080/front/index.html

在这里插入图片描述


以下是对应的一些前端代码。


resources 目录下的 front/api/main.js

//获取菜品分类对应的菜品
function dishListApi(data) {
    return $axios({
        'url': '/dish/list',
        'method': 'get',
        params: {...data}
    })
}
//获取菜品分类对应的套餐
function setmealListApi(data) {
    return $axios({
        'url': '/setmeal/list',
        'method': 'get',
        params: {...data}
    })
}

resources 目录下的 front/index.html

 //获取菜品数据
async getDishList(){
  if(!this.categoryId){
    return
  }
  const res = await dishListApi({categoryId:this.categoryId,status:1})
  if(res.code === 1){
    let dishList = res.data
    const cartData  = this.cartData
    if(dishList.length > 0 && cartData.length > 0){
      dishList.forEach(dish=>{
        cartData.forEach(cart=>{
          if(dish.id === cart.dishId){
            dish.number = cart.number
          }
        })
      })
    }
    this.dishList = dishList
  }else{
    this.$notify({ type:'warning', message:res.msg});
  }
},
//获取套餐数据 setmealId
async getSetmealData(){
  if(!this.categoryId){
    return
  }
  const res = await setmealListApi({categoryId:this.categoryId,status:1})
  if(res.code === 1){
      let dishList = res.data
      const cartData  = this.cartData
      if(dishList.length > 0 && cartData.length > 0){
        dishList.forEach(dish=>{
          cartData.forEach(cart=>{
            if(dish.id === cart.setmealId){
              dish.number = cart.number
            }
          })
        })
      }
      this.dishList = dishList
  }else{
    this.$notify({ type:'warning', message:res.msg});
  }
},

那么接下来我们要做的事情就是在 Controller 层中编写好接口。


2.5.2.代码编写


此处代码来源地址https://blog.csdn.net/weixin_53142722/article/details/124371940


com/itheima/reggie/controller/SetmealController.java

@Autowired
private DishService dishService;
/**
 * 移动端点击套餐图片查看套餐具体内容
 * 这里返回的是 dto 对象,前端需要 copies 这个属性
 * 前端主要要展示的信息是:套餐中菜品的基本信息、图片、菜品描述、菜品的份数
 *
 * @param setmealId
 * @return
 */
@GetMapping("/dish/{id}")
public R<List<DishDto>> dish(@PathVariable("id") Long setmealId) {
    //构造查询条件
    LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<SetmealDish>();
    //添加查询条件
    queryWrapper.eq(SetmealDish::getSetmealId, setmealId);

    //获取套餐里面的所有菜品,即 SetmealDish 表里面的数据
    List<SetmealDish> list = setmealDishService.list(queryWrapper);

    List<DishDto> dishDtos = list.stream().map((setmealDish) -> {
        DishDto dishDto = new DishDto();
        //其实这个 BeanUtils 的拷贝是浅拷贝,这里要注意一下
        BeanUtils.copyProperties(setmealDish, dishDto);
        //这里是为了把套餐中的菜品的基本信息填充到 dto 中,比如菜品描述,菜品图片等菜品的基本信息
        Long dishId = setmealDish.getDishId();
        Dish dish = dishService.getById(dishId);
        BeanUtils.copyProperties(dish, dishDto);

        return dishDto;
    }).collect(Collectors.toList());

    return R.success(dishDtos);
}

最终,套餐也可以正常展示数据(套餐内所有菜品的描述、价格、份量)。

在这里插入图片描述


3.购物车业务功能


3.1.需求分析


移动端用户可以将菜品或者套餐添加到购物车。

对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;

对于套餐来说,可以直接点击 [+] 将当前套餐加入购物车。

在购物车中可以修改菜品和套餐的数量,也可以清空购物车。


3.2.数据模型


购物车对应的数据表为 shopping_cart

在这里插入图片描述


3.3.梳理交互过程


在开发代码之前,需要梳理一下购物车操作时前端页面和服务端的交互过程

  1. 点击 [加入购物车] 或者 [+] 按钮,页面发送 ajax 请求,请求服务端,将菜品或者套餐添加到购物车
  2. 点击购物车图标,页面发送 ajax 请求,请求服务端查询购物车中的菜品和套餐
  3. 点击清空购物车按钮,页面发送 ajax 请求,请求服务端来执行清空购物车操作

开发购物车功能,其实就是在服务端编写代码去处理前端页面发送的这 3 次请求即可。


3.4.准备工作



com/itheima/reggie/entity/ShoppingCart.java

package com.itheima.reggie.entity;

import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 购物车
 */
@Data
public class ShoppingCart implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    //名称
    private String name;

    //用户id
    private Long userId;

    //菜品id
    private Long dishId;

    //套餐id
    private Long setmealId;

    //口味
    private String dishFlavor;

    //数量
    private Integer number;

    //金额
    private BigDecimal amount;

    //图片
    private String image;

    private LocalDateTime createTime;
}

com/itheima/reggie/mapper/ShoppingCartMapper.java

package com.itheima.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.ShoppingCart;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ShoppingCartMapper extends BaseMapper<ShoppingCart> {}

com/itheima/reggie/service/ShoppingCartService.java

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.ShoppingCart;

public interface ShoppingCartService extends IService<ShoppingCart> {}

com/itheima/reggie/service/impl/ShoppingCartServiceImpl.java

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.ShoppingCart;
import com.itheima.reggie.mapper.ShoppingCartMapper;
import com.itheima.reggie.service.ShoppingCartService;
import org.springframework.stereotype.Service;

@Service
public class ShoppingCartServiceImpl extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {}

com/itheima/reggie/controller/ShoppingCartController.java

package com.itheima.reggie.controller;

import com.itheima.reggie.service.ShoppingCartService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 购物车
 */
@Slf4j
@RestController
@RequestMapping("/shoppingCart")
public class ShoppingCartController {
    @Autowired
    private ShoppingCartService shoppingCartService;
}

3.5.代码开发


在开发代码之前,需要梳理一下购物车操作时前端页面和服务端的交互过程

  1. 点击 [加入购物车] 或者 [+] 按钮,页面发送 ajax 请求,请求服务端,将菜品或者套餐添加到购物车
  2. 点击购物车图标,页面发送 ajax 请求,请求服务端查询购物车中的菜品和套餐
  3. 点击清空购物车按钮,页面发送 ajax 请求,请求服务端来执行清空购物车操作

开发购物车功能,其实就是在服务端编写代码去处理前端页面发送的这 3 次请求即可。


3.5.1.添加购物车


前端分析

  • 点击 “加入购物车” 按钮时
    • 页面发送的 ajax 请求是:http:localhost:8080/shoppingCart/add,请求方法为 POST
    • 菜品 请求负载是:{"amount":金额数字, "dishFlavor":"xxx口味", "dishId":"xxx菜品Id", "name":"xxx菜品名", "image":"xxx.jpeg"}
    • 套餐 请求负载是:{"amount":金额数字, "setmealId":"xxx套餐Id", "name":"xxx套餐名", "image":"xxx.jpeg"}

经过前端控制台测试可知:菜品套餐 添加到购物车走的服务端的方法是同一个,但提交的参数略有所差异。


代码编写

com/itheima/reggie/controller/ShoppingCartController.java

/**
 * 添加 菜品/套餐 进购物车
 * 
 * @param shoppingCart
 * @return
 */
@PostMapping("/add")
public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart) {
    log.info("购物车数据:{}", shoppingCart);

    //设置用户 id,指定当前是哪个用户的购物车数据
    Long currentId = BaseContext.getCurrentId();
    shoppingCart.setUserId(currentId);

    Long dishId = shoppingCart.getDishId();

    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<ShoppingCart>();
    queryWrapper.eq(ShoppingCart::getUserId, currentId);

    if (dishId != null) {
        //添加到购物车的是菜品
        queryWrapper.eq(ShoppingCart::getDishId, dishId);
    } else {
        //添加到购物车的是套餐
        queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId());
    }

    //查询当前菜品或者套餐是否在购物车中
    //SQL:select * from shopping_cart where user_id = ? and dish_id / setmeal_id = ?
    ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);

    if (cartServiceOne != null) {
        //如果已经存在,就在原来数量基础上加一
        Integer number = cartServiceOne.getNumber();
        cartServiceOne.setNumber(number + 1);
        shoppingCartService.updateById(cartServiceOne);
    } else {
        //如果不存在,则添加到购物车,数量默认就是一
        shoppingCart.setNumber(1);
        shoppingCart.setCreateTime(LocalDateTime.now());
        shoppingCartService.save(shoppingCart);
    }

    return R.success(cartServiceOne);
}

3.5.2.查看购物车


处理之前的前端代码

之前在进行菜品展示的功能代码的开发时,页面初始化的时候会加载 cartListApi()

由于尚未设置购物车代码,故采用空数据代替来避免报错,方便代码开发。

此时需要我们将该文件内容改回来,其位置在 resources 目录下的 front/api/main.js

//获取购物车内商品的集合
function cartListApi(data) {
    return $axios({
    	/***********************************************************************/
        'url': '/shoppingCart/list', // 之前注释的代码,现在需要使用的代码
        //'url':'/front/cartData.json', // 之前添加的代码,现在需要注释的代码
        /***********************************************************************/
        'method': 'get',
        params: {...data}
    })
}

后台代码编写

这里要注意的一点是用户只能看到自己的购物车。

com/itheima/reggie/controller/ShoppingCartController.java

/**
 * 查看购物车
 *
 * @return
 */
@GetMapping("/list")
public R<List<ShoppingCart>> list() {
    log.info("查看购物车...");

    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
    queryWrapper.orderByAsc(ShoppingCart::getCreateTime);

    List<ShoppingCart> list = shoppingCartService.list(queryWrapper);

    return R.success(list);
}

3.5.3.清空购物车


前端分析

点击 “购物车” 图标上方的 “清空” 按钮

页面请求的 URLhttp://localhost:8080/shoppingCart/clean;请求的方式是 DELETE

实际上就是根据 user_id 来删除 shopping_cart 表内的数据。


代码编写

com/itheima/reggie/controller/ShoppingCartController.java

/**
 * 清空购物车
 *
 * @return
 */
@DeleteMapping("/clean")
public R<String> clean() {
    //SQL:delete from shopping_cart where user_id = ?
    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());

    shoppingCartService.remove(queryWrapper);

    return R.success("清空购物车成功");
}

3.6.购物车功能补充


减少菜品/套餐功能


3.6.1.前端分析


当我们点击 [—] 时,我们希望 菜品/套餐 可以正常减少。减少至0时为止,不可变为负数。

在这里插入图片描述

根据浏览器控制台的反馈可以知道

  1. 请求 URLhttp:/ /localhost:8080/shoppingCart/sub
  2. 请求方式:POST
  3. 请求负载:{"dishId":"1397854865672679425","setmealId":null}

3.6.2.代码编写


此处代码源自 瑞吉外卖项目 | 剩余功能的补充 中的一节:手机端减少购物车中的菜品或者套餐数量。

com/itheima/reggie/controller/ShoppingCartController.java

/**
 * 减少[菜品/套餐]的数量
 *
 * @param shoppingCart
 * @return
 */
@PostMapping("/sub")
@Transactional
public R<ShoppingCart> sub(@RequestBody ShoppingCart shoppingCart) {

    Long dishId = shoppingCart.getDishId();
    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    //代表数量减少的是菜品数量
    if (dishId != null) {
        //通过 dishId 查出购物车对象
        queryWrapper.eq(ShoppingCart::getDishId, dishId);

        //这里必须要加两个条件,否则会出现用户互相修改对方与自己购物车中相同套餐或者是菜品的数量
        queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
        ShoppingCart cart1 = shoppingCartService.getOne(queryWrapper);

        cart1.setNumber(cart1.getNumber() - 1);

        Integer LatestNumber = cart1.getNumber();
        if (LatestNumber > 0) {
            //对数据进行更新操作
            shoppingCartService.updateById(cart1);
        } else if (LatestNumber == 0) {
            //如果购物车的菜品数量减为0,那么就把菜品从购物车删除
            shoppingCartService.removeById(cart1.getId());
        } else if (LatestNumber < 0) {
            return R.error("操作异常");
        }
        return R.success(cart1);
    }

    Long setmealId = shoppingCart.getSetmealId();
    //代表数量减少的是套餐数量
    if (setmealId != null) {
        //代表是套餐数量减少
        queryWrapper.eq(ShoppingCart::getSetmealId, setmealId);
        queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());

        ShoppingCart cart2 = shoppingCartService.getOne(queryWrapper);
        cart2.setNumber(cart2.getNumber() - 1);
        Integer LatestNumber = cart2.getNumber();

        if (LatestNumber > 0) {
            //对数据进行更新操作
            shoppingCartService.updateById(cart2);
        } else if (LatestNumber == 0) {
            //如果购物车的套餐数量减为0,那么就把套餐从购物车删除
            shoppingCartService.removeById(cart2.getId());
        } else if (LatestNumber < 0) {
            return R.error("操作异常");
        }

        return R.success(cart2);
    }

    //如果以上两个判断条件都不满足的话
    return R.error("操作异常");
}

4.用户下单业务功能


4.1.需求分析


移动端用户将 菜品 / 套餐加入购物车后,可以点击购物车中的 [去结算] 按钮

页面会跳转到订单确认页面,点击 [去支付] 按钮则完成下单操作

在这里插入图片描述

需要注意的是这里只是把用户的支付订单保存到数据库,并没有真正的实现支付功能。

因为真正的支付功能是需要去申请支付资质的,个人用户是很难申请到的。


4.2.数据模型


  • orders订单表

在这里插入图片描述


  • order_detail订单明细表

在这里插入图片描述


4.3.准备工作



  1. 实体类:Orders

com/itheima/reggie/entity/Orders.java

package com.itheima.reggie.entity;

import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单
 */
@Data
public class Orders implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    //订单号
    private String number;

    //订单状态 1待付款,2待派送,3已派送,4已完成,5已取消
    private Integer status;

    //下单用户 id
    private Long userId;
    
    //地址 id
    private Long addressBookId;

    //下单时间
    private LocalDateTime orderTime;

    //结账时间
    private LocalDateTime checkoutTime;

    //支付方式:1.微信;2.支付宝
    private Integer payMethod;

    //实收金额
    private BigDecimal amount;

    //备注
    private String remark;

    //用户名
    private String userName;

    //手机号
    private String phone;

    //地址
    private String address;

    //收货人
    private String consignee;
}

  1. 实体类:OrderDetail

com/itheima/reggie/entity/OrderDetail.java

package com.itheima.reggie.entity;

import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 * 订单明细
 */
@Data
public class OrderDetail implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    //名称
    private String name;

    //订单 id
    private Long orderId;

    //菜品 id
    private Long dishId;

    //套餐 id
    private Long setmealId;

    //口味
    private String dishFlavor;

    //数量
    private Integer number;

    //金额
    private BigDecimal amount;

    //图片
    private String image;
}

  1. Mapper接口:OrderMapper

com/itheima/reggie/mapper/OrderMapper.java

package com.itheima.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Orders;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderMapper extends BaseMapper<Orders> { }

  1. Mapper接口:OrderDetailMapper

com/itheima/reggie/mapper/OrderDetailMapper.java

package com.itheima.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.OrderDetail;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderDetailMapper extends BaseMapper<OrderDetail> { }

  1. 业务层接口:OrderService

com/itheima/reggie/service/OrderService.java

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Orders;

public interface OrderService extends IService<Orders> { }

  1. 业务层接口:OrderDetailService

com/itheima/reggie/service/OrderDetailService.java

package com.itheima.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.OrderDetail;

public interface OrderDetailService extends IService<OrderDetail> { }

  1. 业务层实现类:OrderServicelmpl

com/itheima/reggie/service/impl/OrderServiceImpl.java

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.*;
import com.itheima.reggie.mapper.OrderMapper;
import com.itheima.reggie.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService { }

  1. 业务层实现类:OrderDetailServicelmpl

com/itheima/reggie/service/impl/OrderDetailServiceImpl.java

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.OrderDetail;
import com.itheima.reggie.mapper.OrderDetailMapper;
import com.itheima.reggie.service.OrderDetailService;
import org.springframework.stereotype.Service;

@Service
public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService { }

  1. 控制层:OrderController

com/itheima/reggie/controller/OrderController.java

package com.itheima.reggie.controller;

import com.itheima.reggie.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 订单
 */
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderService orderService;
}

  1. 控制层:OrderDetailController

com/itheima/reggie/controller/OrderDetailController.java

package com.itheima.reggie.controller;

import com.itheima.reggie.service.OrderDetailService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 订单明细
 */
@Slf4j
@RestController
@RequestMapping("/orderDetail")
public class OrderDetailController {
    @Autowired
    private OrderDetailService orderDetailService;
}

4.4.梳理交互过程


  • 在开发代码之前,需要梳理一下用户下单操作时前端页面和服务端的交互过程
    • 在购物车中点击 [去结算] 按钮,页面跳转到订单确认页面(localhost:8080/front/page/add-order.html
    • 在订单确认页面,发送 ajax 请求(localhost:8080/addressBook/default),请求服务端获取当前登录用户的默认地址
    • 在订单确认页面,发送 ajax 请求(localhost:8080/shoppingCart/list),请求服务端获取当前登录用户的购物车数据
    • 在订单确认页面点击 [去支付] 按钮,发送 ajax 请求(localhost:8080/order/submit),请求服务端完成下单操作

开发用户下单功能,其实就是在服务端编写代码去处理前端页面发送的请求。


因为之前(或是前端)已经处理过前三步的请求了,实际上我们需要实现的只有第四步。

此次操作中前端页面的请求地址是:localhost:8080/order/submit,请求方式是:POST

提交过来的参数有:{remark: "备注信息", payMethod: 1, addressBookId: "XXXX"}


4.5.代码开发


com/itheima/reggie/service/OrderService.java

//用户下单
public void submit(Orders orders);

com/itheima/reggie/service/impl/OrderServiceImpl.java

@Autowired
private ShoppingCartService shoppingCartService;

@Autowired
private UserService userService;

@Autowired
private AddressBookService addressBookService;

@Autowired
private OrderDetailService orderDetailService;
/**
 * 用户下单
 * @param orders
 */
@Transactional
public void submit(Orders orders) {
    //获得当前用户id
    Long userId = BaseContext.getCurrentId();

    //查询当前用户的购物车数据
    LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(ShoppingCart::getUserId,userId);
    List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper);

    if(shoppingCarts == null || shoppingCarts.size() == 0){
        throw new CustomException("购物车为空,不能下单");
    }

    //查询用户数据
    User user = userService.getById(userId);

    //查询地址数据
    Long addressBookId = orders.getAddressBookId();
    AddressBook addressBook = addressBookService.getById(addressBookId);
    if(addressBook == null){
        throw new CustomException("用户地址信息有误,不能下单");
    }

    long orderId = IdWorker.getId();//订单号

    AtomicInteger amount = new AtomicInteger(0);

    List<OrderDetail> orderDetails = shoppingCarts.stream().map((item) -> {
        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setOrderId(orderId);
        orderDetail.setNumber(item.getNumber());
        orderDetail.setDishFlavor(item.getDishFlavor());
        orderDetail.setDishId(item.getDishId());
        orderDetail.setSetmealId(item.getSetmealId());
        orderDetail.setName(item.getName());
        orderDetail.setImage(item.getImage());
        orderDetail.setAmount(item.getAmount());
        amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue());
        return orderDetail;
    }).collect(Collectors.toList());


    orders.setId(orderId);
    orders.setOrderTime(LocalDateTime.now());
    orders.setCheckoutTime(LocalDateTime.now());
    orders.setStatus(2);
    orders.setAmount(new BigDecimal(amount.get()));
    orders.setUserId(userId);//下单用户 ID
    orders.setNumber(String.valueOf(orderId));
    orders.setUserName(user.getName());
    orders.setConsignee(addressBook.getConsignee());
    orders.setPhone(addressBook.getPhone());
    orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName())
            + (addressBook.getCityName() == null ? "" : addressBook.getCityName())
            + (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName())
            + (addressBook.getDetail() == null ? "" : addressBook.getDetail()));
    //向订单表插入数据,一条数据
    this.save(orders);

    //向订单明细表插入数据,多条数据
    orderDetailService.saveBatch(orderDetails);

    //清空购物车数据
    shoppingCartService.remove(wrapper);
}

com/itheima/reggie/controller/OrderController.java

/**
 * 用户下单
 *
 * @param orders
 * @return
 */
@PostMapping("/submit")
public R<String> submit(@RequestBody Orders orders) {
    log.info("订单数据:{}", orders);
    orderService.submit(orders);
    return R.success("下单成功");
}

如果没有填写默认地址的话,系统会直接跳转到填写地址簿的页面。要求你填写信息。

填完信息,购物车里有菜的话,就会跳转到订单页面。

http://localhost:8080/front/page/add-order.html

在这里插入图片描述


下单成功的话,就会跳转到这个页面。

http://localhost:8080/front/page/pay-success.html

在这里插入图片描述



  • 视频教程对应的部分(PPT、代码、相关功能分析部分)结束
  • 目前前 7 篇的内容对应的视频为: P1 ~ P102
  • 视频自 P102 之后的部分是 GitLinuxRedis 的学习。
  • 视频自 P155 为项目优化的部分,至 P190 结束。

  • 但是,即使是初步功能,视频中也有一些尚未完成的功能没有编写代码,意在各位自行处理,加强自己的动手能力
  • 故本篇博客接下来的内容仍是进行项目 初步开发 的功能补全

4.6.补充功能


  • 虽然视频关于订单功能的内容以及结束了,但订单功能尚有未完成的部分。
  • 接下来要解决的部分是:1.用户查看订单功能;2.用户再来一单的功能。

4.6.1.用户查看订单功能


用户查看订单功能


  1. 前端分析

此处通过前端控制台来查看其发送的 ajax 请求

在这里插入图片描述


  1. 编写代码

根据上图情况来编写后台代码

com/itheima/reggie/controller/OrderController.java

/**
 * 用户查看订单
 *
 * @param page
 * @param pageSize
 * @return
 */
@GetMapping("/page")
public R<Page> page(int page, int pageSize) {
    Page<Orders> pageInfo = new Page<Orders>(page, pageSize);
    LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<Orders>();
    queryWrapper.orderByDesc(Orders::getOrderTime);
    orderService.page(pageInfo, queryWrapper);
    return R.success(pageInfo);
}

  1. 效果展示

http://localhost:8080/front/page/order.html

在这里插入图片描述

显然有数据尚未处理,接下来查看前端的页面上的代码


  1. 前端再分析

resources 目录下的 front/page/order.html 中的部分代码

<van-cell v-for="(order,index) in orderList" :key="index" class="item">
    <div class="timeStatus">
        <span>{{order.orderTime}}</span>
        <span>{{getStatus(order.status)}}</span>
        <!-- <span>正在派送</span> -->
    </div>
    <div class="dishList">
        <div v-for="(item,index) in order.orderDetails" :key="index" class="item">
            <span>{{item.name}}</span>
            <span>x{{item.number}}</span>
        </div>
    </div>
    <div class="result">
        <span>共{{order.sumNum}} 件商品,实付</span><span class="price">¥{{order.amount}}</span>
    </div>
    <div class="btn" v-if="order.status === 4">
        <div class="btnAgain" @click="addOrderAgain(order)">再来一单</div>
    </div>
</van-cell>

由此可知,需要设计一个 DTO 类来封装页面提交的数据


  1. 代码改进

com/itheima/reggie/dto/OrdersDto.java

package com.itheima.reggie.dto;

import com.itheima.reggie.entity.OrderDetail;
import com.itheima.reggie.entity.Orders;
import lombok.Data;

import java.util.List;

@Data
public class OrdersDto extends Orders {
    private String userName;

    private String phone;

    private String address;

    private String consignee;

    private List<OrderDetail> orderDetails;
}

自此开始,用户查看订单功能的代码自此开始完全摘抄自 【瑞吉外卖项目剩余功能补充】

我这里只是把大佬的控制层代码放到了业务层里。

让我自己想是想不出来的,只能说大佬牛皮。


com/itheima/reggie/service/OrderService.java

 //根据订单 id 来得到一个订单明细的集合
public List<OrderDetail> getOrderDetailListByOrderId(Long orderId);

//用户查看自己的订单信息
public void page(Page<Orders> ordersPageInfo, Page<OrdersDto> ordersDtoPageInfo);

com/itheima/reggie/service/impl/OrderServiceImpl.java

/**
 * 根据订单 id 来得到一个订单明细的集合
 *
 * @param orderId
 * @return
 */
public List<OrderDetail> getOrderDetailListByOrderId(Long orderId) {
    /*
	    此处为从 [用户查看自己的订单信息] 功能代码中抽离的一个方法
	    旨在通过订单的 id 来查询订单明细,得到一个订单明细的集合
	        
	    单独抽离出来是为了避免在 stream 中遍历时
	    直接使用的构造条件来查询导致 eq 叠加
	    从而导致后面查询的数据都是 null
    */
    LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(OrderDetail::getOrderId, orderId);
    List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper);
    return orderDetailList;
}

/**
 * 用户查看自己的订单信息
 *
 * @param ordersPageInfo
 * @param ordersDtoPageInfo
 * @return
 */
@Transactional
public void page(Page<Orders> ordersPageInfo, Page<OrdersDto> ordersDtoPageInfo) {
    //构造条件查询对象
    LambdaQueryWrapper<Orders> queryWrapper_1 = new LambdaQueryWrapper<Orders>();
    //这里是直接把当前用户分页的全部结果查询出来,要添加用户 id 作为查询条件,否则会出现用户可以查询到其他用户的订单的情况
    queryWrapper_1.eq(Orders::getUserId, BaseContext.getCurrentId());
    //添加排序条件,根据更新时间降序排列
    queryWrapper_1.orderByDesc(Orders::getOrderTime);

    //分页查询(queryWrapper_1 -> ordersPageInfo)
    orderService.page(ordersPageInfo, queryWrapper_1);

    //对 OrderDto 进行必要的属性赋值
    List<Orders> records = ordersPageInfo.getRecords();
    List<OrdersDto> orderDtoList = records.stream().map((item) -> {
        OrdersDto orderDto = new OrdersDto();
        Long orderId = item.getId();//获取订单 id

        //通过订单的 id 来查询订单明细,得到一个订单明细的集合
        List<OrderDetail> orderDetailList = this.getOrderDetailListByOrderId(orderId);

        //为 orderDetails 里面的属性赋值
        BeanUtils.copyProperties(item, orderDto);

        //对 orderDto 进行 OrderDetails 属性的赋值
        orderDto.setOrderDetails(orderDetailList);

        return orderDto;
    }).collect(Collectors.toList());

    BeanUtils.copyProperties(ordersPageInfo, ordersDtoPageInfo, "records");
    ordersDtoPageInfo.setRecords(orderDtoList);
}

com/itheima/reggie/controller/OrderController.java

/**
 * 用户端展示自己的订单查询
 *
 * @param page
 * @param pageSize
 * @return
 */
@GetMapping("/userPage")
public R<Page> page(int page, int pageSize) {
    //分页构造器对象
    Page<Orders> ordersPageInfo = new Page<Orders>(page, pageSize);
    Page<OrdersDto> ordersDtoPageInfo = new Page<OrdersDto>(page, pageSize);
    orderService.page(ordersPageInfo, ordersDtoPageInfo);
    return R.success(ordersDtoPageInfo);
}

  1. 最终效果图

http://localhost:8080/front/page/order.html

在这里插入图片描述

如果你的订单没有出现 “再来一单” 这样的字眼,那也是正常的。

因为前段页面设置了只有在订单完成的情况下(status=4)才能显现出这样的字眼。

所以要想现在显现出 “再来一单” 的话,在数据库中更改 status 的值就行了。


4.6.2.再来一单功能


用户再来一单的功能


  1. 梳理交互过程

resources 目录下的 front/page/order.html 的部分代码

<div class="btn" v-if="order.status === 4">
    <div class="btnAgain" @click="addOrderAgain(order)">再来一单</div>
</div>
async addOrderAgain(order) {
    const res = await orderAgainApi({id: order.id})
    if (res.code === 1) {
        window.requestAnimationFrame(() => {
            window.location.href = '/front/index.html'
        })
    } else {
        this.$notify({type: 'warning', message: res.msg});
    }
},

显然,当 “再来一单” 的操作完成时,其会跳转到最初的页面:/front/index.html


resources 目录下的 front/api/order.js中的部分代码

//再来一单
function orderAgainApi(data) {
  return $axios({
      'url': '/order/again',
      'method': 'post',
      data
  })
}

若是想要出现 “再来一单” 的按钮,请先将数据库中的表 order_detail 中的 status 改为 4

status 值的含义:订单状态(1.待付款,2.待派送,3.已派送,4.已完成,5.已取消)


http://localhost:8080/front/page/order.html

在这里插入图片描述


  1. 代码编写


大体思路是

  • 首先获取到 订单id,再找到相应 idorder 对象,再更新一些属性(订单id、订单号、下单时间、订单状态)的数据。
  • 之后再通过 订单id 找到相应的订单详细表,再更新些属性(订单id)的数据。
    • SELECT id,name,order_id,dish_id,setmeal_id,dish_flavor,number,amount,image FROM order_detail WHERE (order_id = ?);

com/itheima/reggie/controller/OrderController.java

/**
 * 再来一单
 *
 * @param order_1
 * @return
 */
@PostMapping("/again")
@Transactional
public R<String> again(@RequestBody Orders order_1) {
    log.info("再来一单_数据测试:{}", order_1);

    //取得[订单id]
    Long id = order_1.getId();
    //通过[订单id]获得相应的[order]对象
    Orders orders = orderService.getById(id);

    //在[order]对象中重新设置[订单id]
    long orderId = IdWorker.getId();
    orders.setId(orderId);

    //在[order]对象中重新设置[订单号码]
    String number = String.valueOf(IdWorker.getId());
    orders.setNumber(number);

    //在[order]对象中重新设置[下单时间]
    orders.setOrderTime(LocalDateTime.now());
    orders.setCheckoutTime(LocalDateTime.now());

	//在[order]对象中重新设置[订单状态]
    orders.setStatus(2);

    //最终,将这条数据插入订单表(orders)
    orderService.save(orders);

    LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<OrderDetail>();
    //设置查询条件
    queryWrapper.eq(OrderDetail::getOrderId, id);
    List<OrderDetail> list = orderDetailService.list(queryWrapper);

    list.stream().map((item) -> {
        //订单明细表 id
        long detailId = IdWorker.getId();
        //设置[订单号码]
        item.setOrderId(orderId);
        item.setId(detailId);
        return item;
    }).collect(Collectors.toList());

    //向 订单明细表 中插入多条数据
    orderDetailService.saveBatch(list);
    return R.success("再来一单成功");
}

但是这里仍然有 BUG

用户若是要修改备注,就必须要跳转到订单的页面才行。

再来一单 的操作完成后,直接跳转到 index.html。(万一用户手抖点到了怎么办


  1. 代码改进


com/itheima/reggie/service/ShoppingCartService.java

//清空购物车
public R<String> clean();

com/itheima/reggie/service/impl/ShoppingCartServiceImpl.java

/**
 * 清空购物车
 *
 * @return
 */
@Transactional
public R<String> clean() {
    //SQL:delete from shopping_cart where user_id = ?
    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
    this.remove(queryWrapper);
    return R.success("清空购物车成功");
}

以下描述摘抄自博客 瑞吉外卖项目 | 剩余功能补充

  • 前端点击 “再来一单” 按钮,会直接跳转到 index.html,且购物车中是包含 [菜品/套餐] 信息的。
  • 为了避免数据出现问题。再跳转到该页面之前,我们需要清除购物车的数据。
  • 具体的代码编写步骤:
    • 通过 orderId 获取订单明细;
    • 把订单明细的数据的数据塞到购物车表中。
      • 但在进行该操作之前需要清除购物车中原有数据,以保证 “再来一单” 的数据不出现问题。
        • 其中清除的是当前登录用户的购物车表中的数据
      • 虽然这样可能会影响用户体验,但是就外卖而言,该操作对用户体验影响不大。电商项目就不能这么干了。

com/itheima/reggie/service/OrderService.java

//再来一单
public void againSubmit(@RequestBody Map<String, String> map);

com/itheima/reggie/service/impl/OrderServiceImpl.java

/**
 * 再来一单
 *
 * @param map
 */
@Transactional
public void againSubmit(@RequestBody Map<String, String> map) {
    String ids = map.get("id");
    long id = Long.parseLong(ids);
    LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(OrderDetail::getOrderId, id);
    //获取该订单对应的所有的订单明细表
    //SQL 语句:SELECT id,name,order_id,dish_id,setmeal_id,dish_flavor,number,amount,image FROM order_detail WHERE (order_id = ?);
    List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper);

    //通过用户 id 把原来的购物车给清空
    shoppingCartService.clean();//此处的 clean 方法在视频中出现过,建议抽取到 service 层中以方便调用

    //获取用户 id
    Long userId = BaseContext.getCurrentId();
    List<ShoppingCart> shoppingCartList = orderDetailList.stream().map((item) -> {
        //把从 order 表中和 order_details 表中获取到的数据赋值给这个购物车对象
        ShoppingCart shoppingCart = new ShoppingCart();
        shoppingCart.setUserId(userId);
        shoppingCart.setImage(item.getImage());
        Long dishId = item.getDishId();
        Long setmealId = item.getSetmealId();
        if (dishId != null) {
            //如果是菜品那就添加菜品的查询条件
            shoppingCart.setDishId(dishId);
        } else {
            //添加到购物车的是套餐
            shoppingCart.setSetmealId(setmealId);
        }
        shoppingCart.setName(item.getName());
        shoppingCart.setDishFlavor(item.getDishFlavor());
        shoppingCart.setNumber(item.getNumber());
        shoppingCart.setAmount(item.getAmount());
        shoppingCart.setCreateTime(LocalDateTime.now());
        return shoppingCart;
    }).collect(Collectors.toList());

    //把携带数据的购物车批量插入购物车表
    shoppingCartService.saveBatch(shoppingCartList);
}

com/itheima/reggie/controller/OrderController.java

/**
 * 再来一单
 *
 * @param map
 * @return
 */
@PostMapping("/again")
public R<String> againSubmit(@RequestBody Map<String, String> map) {
    orderService.againSubmit(map);
    return R.success("操作成功");
}

5.补充功能


5.1.用户登出功能


  1. 梳理交互过程

在这里插入图片描述
由上可知,请求 URLhttp://localhost:8080/user/loginout,请求方法:POST

于此要做的就是在 Controlller 层来处理该请求,清除 session 中保存的用户 id


com/itheima/reggie/controller/UserController.java

/**
 * 用户退出登录
 *
 * @param request
 * @return
 */
@PostMapping("/loginout")
public R<String> logout(HttpServletRequest request) {
    //清理 session 中的用户 id
    request.getSession().removeAttribute("user");
    return R.success("退出成功");
}

5.2.其他功能


  • 其他功能前面已经完成,此处只是设置软链接方便跳转。强迫症

  1. 编辑地址功能
  2. 删除地址功能
  3. 点击图片查看套餐详情
  4. 减少菜品/套餐功能
  5. 用户查看订单功能
  6. 再来一单功能

但鉴于该功能是后台功能,故放在下一篇博客中详述。

在这里插入图片描述


下一篇学习瑞吉外卖⑧SpringBoot单体项目https://blog.csdn.net/yanzhaohanwei/article/details/125362891


  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值