• 8.菜品展示、购物车、下单开发


    菜品展示、购物车、下单开发

    1、用户地址簿开发

    1.1、导入用户地址簿相关功能代码

    • 实体类AddressBook(直接从课程资料中导入即可)
    • Mapper接口AddressBookMapper
    • 业务层接口AddressBookService
    • 业务层实现类AddressBookServicelmpl
    • 控制层AddressBookController(直接从课程资料中导入即可)

    1.2、用户地址修改

    1.2.1、请求分析

    修改用户地址时前端发送的请求:

    image-20220624224039416

    携带的参数:

    image-20220624224054848

    1.2.2、方法编写

    AddressBookController编写update方法

    /**
     * 修改收货地址
     */
    @PutMapping
    public R<String> update(@RequestBody AddressBook addressBook) {
        log.info("修改用户地址信息:{}", addressBook);
        addressBookService.updateById(addressBook);
        return R.success("修改地址成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.2.3、测试修改

    1.3、删除收获地址

    1.3.1、前端请求分析

    1、请求地址:

    image-20220624224840690

    2、请求参数

    image-20220624224852894

    1.3.2、修改AddressBook实体类

    isDeleted属性添加@TableLogic(value = "0", delval = "1")注解,用于逻辑删除

    image-20220624225203666

    1.3.3、编写后端接口

    AddressBookController编写delete方法

    /**
     * 逻辑删除收货地址
     * @param ids
     * @return
     */
    @DeleteMapping
    public R<String> delete(Long ids) {
        log.info("删除收货地址id:{}", ids);
        addressBookService.removeById(ids);
        return R.success("删除地址成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1.3.4、测试接口

    2、菜品展示

    2.1、显示异常问题

    手机端登陆后显示菜品展示页面,但登陆后没有显示相关信息。原因是前端请求问题,front/index.html这里请求了两个接口,如果其中一个请求失败,那么就都会失败。由于我们没有编写购物车的相关接口,所以显示会失败。

    image-20220624234203296

    解决方法:让购物车的请求返回一个假数据

    在front目录下编写一个cartData.json:

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

    修改front/api/main.js中的cartListApi方法:

    image-20220624234534034

    重启服务测试

    2.1.1、阿里云OSS菜品图片显示(可选)

    如果使用阿里云OSS存储图片,需要修改front/js/common.js文件里的imgPath方法,地址改为自己OSS的地址

    image-20220625000536743

    2.2、规格选择

    当一个菜品设置了口味属性,即一个dish有对应的dish_flavor,菜品展示界面的“+”号就会变为“规格选择”。但选择查询出来的菜品都是“+”号。原因是查询接口范围的是List<Dish>,Dish实体类中没有口味属性,需要修改为返回List<DishDto>

    修改DishController里的list方法

    /**
     * 根据条件查询对应的菜品数据
     *
     * @param dish
     * @return
     */
    @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish) {
    
        // 狗仔查询条件
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
        // 查询状态为1的菜品
        queryWrapper.eq(Dish::getStatus, 1);
    
        // 添加排序条件
        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);
    
            // 设置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> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
            dishFlavorLambdaQueryWrapper.eq(DishFlavor::getDishId, dishId);
            // SQL: select * from dish_flavor where dish_id = ?
            List<Di	shFlavor> dishFlavorList = dishFlavorService.list(dishFlavorLambdaQueryWrapper);
            dishDto.setFlavors(dishFlavorList);
    
            return dishDto;
        }).collect(Collectors.toList());
    
        return R.success(dishDtoList);
    }
    
    • 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

    重启服务测试

    2.3、套餐展示

    套餐展示与菜品展示发送的请求不一样。

    SetmealController编写list方法

    /**
     * 根据分类id和状态查询套餐
     * @param setmeal
     * @return
     */
    @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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    重启服务测试

    3、购物车

    执行流程:

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

    3.1、准备工作

    • 实体类ShoppingCart (直接从课程资料中导入即可)
    • Mapper接口ShoppingCartMapper
    • 业务层接口ShoppingCartService
    • 业务层实现类ShoppingCartServicelmpl
    • 控制层ShoppingCartController

    3.2、添加菜品/套餐方法

    在ShoppingCartController中创建add方法

    这里设置创建时间没法使用自动注入。因为购物车字段只有一个创建时间,没有其他三个字段。当然也可以修改自动注入方法,判断有这个属性时再进行注入。在需要自动注入的属性上添加注解@TableField(fill = FieldFill.INSERT)。下面的代码就直接设置了创建时间。

    @PostMapping("/add")
    public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart, HttpSession session) {
        log.info("购物车数据:{}", shoppingCart);
    
        // 设置用户id,指定当前是哪个用户添加的购物车数据
        Long userId = (Long) session.getAttribute("user");
        shoppingCart.setUserId(userId);
    
        // 查询当前菜品或者套餐是否在购物车中
        Long dishId = shoppingCart.getDishId();
    
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId, userId); // 匹配用户id
    
        if (dishId != null) {
            // 添加到购物车的是菜品
            queryWrapper.eq(ShoppingCart::getDishId, dishId);
        } else {
            // 添加的是套餐
            queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId());
        }
    
        // 查询当前菜品或者套餐是否在购物车中
        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);
            cartServiceOne = shoppingCart;
        }
    
        return R.success(cartServiceOne);
    }
    
    • 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

    3.3、减去添加的菜品/套餐方法

    当添加某些菜品后,点击“-”号对菜品数量进行调整,点击“-”按钮发送的请求如下:

    image-20220625181605167

    请求参数:

    image-20220625181630828

    在ShoppingCartController中创建sub方法:

    /**
     * 减少菜品/套餐数量
     */
    @PostMapping("/sub")
    public R<ShoppingCart> sub(@RequestBody Map<String, Object> params, HttpSession session) {
        log.info("减少菜品/套餐数量:{}", params);
    
        Long dishId = null, setmealId = null;
        if (params.get("dishId") != null) {
            dishId = Long.valueOf((String) params.get("dishId"));
        }
        if (params.get("setmealId") != null) {
            setmealId = Long.valueOf((String) params.get("setmealId"));
        }
    
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        Long userId = (Long) session.getAttribute("user");
        queryWrapper.eq(ShoppingCart::getUserId, userId); // 匹配用户id
        if (dishId != null) {
            queryWrapper.eq(ShoppingCart::getDishId, dishId); // 匹配菜品
        } else {
            queryWrapper.eq(ShoppingCart::getSetmealId, setmealId); // 匹配套餐
        }
    
        ShoppingCart shoppingCart = shoppingCartService.getOne(queryWrapper);
        int number = shoppingCart.getNumber() - 1;
        shoppingCart.setNumber(number);
    
        if (number == 0) { // 数量为0,删除该购物车记录
            shoppingCartService.remove(queryWrapper); // delete shopping_cart where user_id = ? and dish_id/setmeal_id = ?
        } else {
            // 更新数据
            shoppingCartService.updateById(shoppingCart);
        }
        
        return R.success(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
    • 34
    • 35
    • 36
    • 37

    这里为什么返回ShoppingCart,因为前端页面需要用到shoppingCart里的number属性显示菜品/套餐数量

    3.4、购物车列表显示

    修改front/api/main.js,把原先修改的前端Api复原:

    image-20220625194816780

    ShoppingCartController编写list方法

    /**
     * 查看购物车
     * @param session
     * @return
     */
    @GetMapping("/list")
    public R<List<ShoppingCart>> list(HttpSession session) {
        log.info("查看购物车");
    
        Long userId = (Long) session.getAttribute("user");
    
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId, userId);
        queryWrapper.orderByAsc(ShoppingCart::getCreateTime);
    
        List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
    
        return R.success(list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.5、清空购物车

    ShoppingCartController编写clean方法

    /**
     * 清空购物车
     * @return
     */
    @DeleteMapping("/clean")
    public R<String> clean(HttpSession session) {
    
        Long userId = (Long) session.getAttribute("user");
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId, userId);
    
        shoppingCartService.remove(queryWrapper);
    
        return R.success("清空购物车成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    4、下单

    执行流程:

    1. 在购物车中点击去绍算按钮,页面跳转到订单确认页面
    2. 在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的默认地址
    3. 在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的购物车数据
    4. 在订单确认页面点击去支付按钮,发送ajax请求,请求服务端完成下单操作

    准备工作:

    • 实体类Orders、OrderDetail (直接从课程资料中导入即可)
    • Mapper接口OrderMapper、OrderDetailMapper
    • 业务层接口OrderService、OrderDetailService
    • 业务层实现类OrderServicelmpl、OrderDetailServicelmpl
    • 控制层OrderController.、OrderDetailController

    4.1、OrderController

    创建映射方法submit

    @Slf4j
    @RestController
    @RequestMapping("/order")
    public class OrderController {
    
        @Autowired
        private OrderService orderService;
    
        /**
         * 用户下单
         * @param orders
         * @return
         */
        @PostMapping("/submit")
        public R<String> submit(@RequestBody Orders orders) {
            log.info("订单数据:{}", orders);
            orderService.submit(orders);
            return R.success("下单成功");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4.2、OrderService

    public interface OrderService extends IService<Orders> {
        void submit(Orders orders);
    }
    
    • 1
    • 2
    • 3

    4.3、OrderServiceImpl

    实现映射方法submit。

    我觉得表orders中字段number字段有点多余,就不设置了

    order_detail表中的order_id字段关联表orders的id字段。

    @Service
    public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {
    
        @Autowired
        ShoppingCartService shoppingCartService;
    
        @Autowired
        OrderService orderService;
    
        @Autowired
        UserService userService;
    
        @Autowired
        AddressBookService addressBookService;
    
        @Autowired
        OrderDetailService orderDetailService;
    
        @Transactional
        @Override
        public void submit(Orders orders) {
            // 获得当前用户id,查询用户信息
            Long userId = BaseContext.getCurrentId();
            User user = userService.getById(userId);
    
            // 查询当前用户的购物车数据
            LambdaQueryWrapper<ShoppingCart> shoppingCartLambdaQueryWrapper = new LambdaQueryWrapper<>();
            shoppingCartLambdaQueryWrapper.eq(ShoppingCart::getUserId, userId);
            List<ShoppingCart> shoppingCarts = shoppingCartService.list(shoppingCartLambdaQueryWrapper);
    
            if (shoppingCarts == null || shoppingCarts.size() == 0) {
                throw new CustomException("购物车为空,不能下单");
            }
    
            // 查询送货地址
            Long addressBookId = orders.getAddressBookId();
            AddressBook addressBook = addressBookService.getById(addressBookId);
            if (addressBook == null) {
                throw new CustomException("用户地址信息有误,不能下单");
            }
    
            // 处理订单金额
            AtomicInteger amount = new AtomicInteger(0);
            shoppingCarts.forEach(item -> {
                BigDecimal cost = item.getAmount().multiply(new BigDecimal(item.getNumber()));
                amount.addAndGet(cost.intValue()); // addAndGet方法需要传入int类型
            });
    
            // 想订单表插入数据,一条数据
            orders.setUserId(userId);
            orders.setOrderTime(LocalDateTime.now());
            orders.setCheckoutTime(LocalDateTime.now()); // 没有实现支付功能
            orders.setStatus(2); // 待出餐状态
            orders.setAmount(BigDecimal.valueOf(amount.get())); // 设置订单费用
            orders.setUserId(userId);
            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()));
            // 保存订单
            orderService.save(orders);
    
            // 向订单明细表插入多条数据
            List<OrderDetail> orderDetails = shoppingCarts.stream().map(item -> {
                OrderDetail orderDetail = new OrderDetail();
                BeanUtils.copyProperties(item, orderDetail);
                orderDetail.setOrderId(orders.getId()); // orders在调用save方法后会自动注入id
                return orderDetail;
            }).collect(Collectors.toList());
    
            orderDetailService.saveBatch(orderDetails);
    
            // 清空购物车
            shoppingCartService.remove(shoppingCartLambdaQueryWrapper);
        }
    }
    
    • 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

    4.4、测试支付接口

  • 相关阅读:
    dameng数据库数据id decimal类型,精度丢失
    分享好用无广告的手机浏览器,亲测值得下载
    【UniApp】-uni-app-路由
    Vue组件通信应用实践总结
    详细讲解 —— 进程(Java EE初阶)
    Qt下多线程的四种使用方法总结及代码示例
    【数据提取】 Python 提取PDF中的文字和图片
    计算机网络两位伟人
    Linux入门攻坚——24、BIND编译安装、Telnet和OpenSSH
    面试被问了几百遍的 IoC 和 AOP ,还在傻傻搞不清楚?
  • 原文地址:https://blog.csdn.net/xjhqre/article/details/125465321