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、测试修改

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.3.4、测试接口

2、菜品展示

2.1、显示异常问题

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

image-20220624234203296

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

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

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

修改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);
}

重启服务测试

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);
}

重启服务测试

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);
}

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);
}

这里为什么返回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);
}

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("清空购物车成功");
}

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("下单成功");
    }
}

4.2、OrderService

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

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);
    }
}

4.4、测试支付接口

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值