目录
菜品启售和停售
菜品批量启售和批量停售
菜品的批量删除
套餐管理的启售,停售
套餐管理的修改(前端展示有个bug,已解决)
后台按条件查看和展示客户订单
手机端减少购物车中的菜品或者套餐数量
用户查看自己订单
移动端的再来一单功能
移动端点击套餐图片查看套餐具体菜品
删除地址
修改地址
这个是自己基于学习B站 黑马瑞吉外卖项目,补充一些视频里面没有定义的功能或者是需要自己实现的功能;仅供学习参考,本人可能代码不太规范,但是功能自己测试是没有问题的;
黑马程序员Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV13a411q753?spm_id_from=333.337.search-card.all.click
菜品启售和停售前端发过来的请求(使用的是post方式):http://localhost:8080/dish/status/1?ids=1516568538387079169
后端接受的请求:
@PostMapping("/status/{status}")
public R status(@PathVariable("status") Integer status,Long ids){
log.info("status:{}",status);
log.info("ids:{}",ids);
return null;
}
先看看后端能不能接收到前端传过来的数据:
发现可以接收到前端参数后,开始补全controller层代码:在DishController中添加下面的接口代码;
/**
* 对菜品进行停售或者是起售
* @return
*/
@PostMapping("/status/{status}")
public R status(@PathVariable("status") Integer status,Long ids){
log.info("status:{}",status);
log.info("ids:{}",ids);
Dish dish = dishService.getById(ids);
if (dish != null){
dish.setStatus(status);
dishService.updateById(dish);
return R.success("开始启售");
}
return R.error("售卖状态设置异常");
}
菜品批量启售和批量停售
把上面对单个菜品的售卖状态的方法进行修改;
/**
* 对菜品批量或者是单个 进行停售或者是起售
* @return
*/
@PostMapping("/status/{status}")
//这个参数这里一定记得加注解才能获取到参数,否则这里非常容易出问题
public R status(@PathVariable("status") Integer status,@RequestParam List ids){
//log.info("status:{}",status);
//log.info("ids:{}",ids);
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
queryWrapper.in(ids !=null,Dish::getId,ids);
//根据数据进行批量查询
List list = dishService.list(queryWrapper);
for (Dish dish : list) {
if (dish != null){
dish.setStatus(status);
dishService.updateById(dish);
}
}
return R.success("售卖状态修改成功");
}
注意:controller层的代码是不可以直接写业务的,建议把它抽离到service层,controller调用一下service的方法就行;下面的批量删除功能是抽离的,controller没有写业务代码;
菜品的批量删除前端发来的请求:
在DishController中添加接口:
在DishFlavor实体类中,在private Integer isDeleted;字段上加上@TableLogic注解,表示删除是逻辑删除,由mybatis-plus提供的;
/**
* 套餐批量删除和单个删除
* @return
*/
@DeleteMapping
public R delete(@RequestParam("ids") List ids){
//删除菜品 这里的删除是逻辑删除
dishService.deleteByIds(ids);
//删除菜品对应的口味 也是逻辑删除
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(DishFlavor::getDishId,ids);
dishFlavorService.remove(queryWrapper);
return R.success("菜品删除成功");
}
DishServicez中添加相关的方法:
//根据传过来的id批量或者是单个的删除菜品
void deleteByIds(List ids);
在实现类实现该方法:
/**
*套餐批量删除和单个删除
* @param ids
*/
@Override
@Transactional
public void deleteByIds(List ids) {
//构造条件查询器
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
//先查询该菜品是否在售卖,如果是则抛出业务异常
queryWrapper.in(ids!=null,Dish::getId,ids);
List list = this.list(queryWrapper);
for (Dish dish : list) {
Integer status = dish.getStatus();
//如果不是在售卖,则可以删除
if (status == 0){
this.removeById(dish.getId());
}else {
//此时应该回滚,因为可能前面的删除了,但是后面的是正在售卖
throw new CustomException("删除菜品中有正在售卖菜品,无法全部删除");
}
}
}
功能测试:单个删除,批量删除,批量删除中有启售的...
测试成功!
套餐管理的启售,停售前端发来的请求:
根据前面菜品模块自己实现的功能,我们可以知道,我们只需要写一个批量处理的方法就可以完成单个或者是批量套餐的启售,停售;
SetmealController中的controller层代码:
/**
* 对菜品批量或者是单个 进行停售或者是起售
* @return
*/
@PostMapping("/status/{status}")
//这个参数这里一定记得加注解才能获取到参数,否则这里非常容易出问题
public R status(@PathVariable("status") Integer status,@RequestParam List ids){
setmealService.updateSetmealStatusById(status,ids);
return R.success("售卖状态修改成功");
}
SetmealService中添加下面方法:
/**
* 根据套餐id修改售卖状态
* @param status
* @param ids
*/
void updateSetmealStatusById(Integer status,List ids);
该方法的实现:
/**
* 根据套餐id修改售卖状态
* @param status
* @param ids
*/
@Override
public void updateSetmealStatusById(Integer status, List ids) {
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
queryWrapper.in(ids !=null,Setmeal::getId,ids);
List list = this.list(queryWrapper);
for (Setmeal setmeal : list) {
if (setmeal != null){
setmeal.setStatus(status);
this.updateById(setmeal);
}
}
}
记得功能测试,我自己测试的时候是没有问题的;
套餐管理的修改分为两步:数据回显示,和提交修改数据到数据库
前端点击套餐修改,前端发过来的请求:
请求方式是:get
携带的参数是:stemealId
然后我们发现在d出编辑窗口是没有数据的:并且报了一个404,那就是说是**在数据回显的时候报错了**,没有找到具体的回显接口来处理这个请求;
SetmealController 中添加下面的代码:
/**
* 回显套餐数据:根据套餐id查询套餐
* @return
*/
@GetMapping("/{id}")
public R getData(@PathVariable Long id){
SetmealDto setmealDto = setmealService.getDate(id);
return R.success(setmealDto);
}
SetmealService添加下面的代码:
/**
* 回显套餐数据:根据套餐id查询套餐
* @return
*/
SetmealDto getDate(Long id);
该方法的实现:
/**
* 回显套餐数据:根据套餐id查询套餐
* @return
*/
@Override
public SetmealDto getDate(Long id) {
Setmeal setmeal = this.getById(id);
SetmealDto setmealDto = new SetmealDto();
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
//在关联表中查询,setmealdish
queryWrapper.eq(id!=null,SetmealDish::getSetmealId,id);
if (setmeal != null){
BeanUtils.copyProperties(setmeal,setmealDto);
List list = setmealDishService.list(queryWrapper);
setmealDto.setSetmealDishes(list);
return setmealDto;
}
return null;
}
测试:数据回显成功:
但是这样我们再点击添加菜品会发现,右边只展示菜品的价格并没有展示菜品对应的名称:
已选菜品中的菜品并没有展示对应的菜品名;
修改后的运行情况展示:(个人感觉这个菜品搜索框没啥用。。。。反正我是搜索不出来。。)
修改具体的前端代码:把backend/combo/add.html中的335行修改为下面的代码;
因为这里的item是代表dish对象,dish实体类是使用name作为菜品名称的;
修改完成后,点击保存,我们发现前端发出一个put请求:
携带的参数为:
根据前端传过来的数据和需要的返回值,我们就可以知道controller层方法的返回值和用什么参数来接收前端传给我们的数据;注意这个套餐里面的菜品也要保存修改:需要把setealDish保存到seteal_dish表中;
点击修改后的保存,后端会接收到下面的数据:发现setmealId == null,所以这里需要自己单独填充;
controller层代码:
为了不把问题复杂化,我是先把相关的setmealDish内容移除然后再重新添加,这样就可以不用考虑dish重复的问题和哪些修改哪些没修改;
@PutMapping
public R edit(@RequestBody SetmealDto setmealDto){
if (setmealDto==null){
return R.error("请求异常");
}
if (setmealDto.getSetmealDishes()==null){
return R.error("套餐没有菜品,请添加套餐");
}
List setmealDishes = setmealDto.getSetmealDishes();
Long setmealId = setmealDto.getId();
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SetmealDish::getSetmealId,setmealId);
setmealDishService.remove(queryWrapper);
//为setmeal_dish表填充相关的属性
for (SetmealDish setmealDish : setmealDishes) {
setmealDish.setSetmealId(setmealId);
}
//批量把setmealDish保存到setmeal_dish表
setmealDishService.saveBatch(setmealDishes);
setmealService.updateById(setmealDto);
return R.success("套餐修改成功");
}
后台按条件查看和展示客户订单
点击订单明细,前端会发下面的请求:携带的数据是分页使查询用的;
先写个controller看能不能接收到前端传过来的参数:发现只要参数和前端传过来的参数名对应就可以拿到参数的
主要使用到mybatis-plus动态sql语句的生成:
这里我就直接把功能直接写在controller层了,看自己需求分层;(本人这里偷个懒)
/**
* 后台查询订单明细
* @param page
* @param pageSize
* @param number
* @param beginTime
* @param endTime
* @return
*/
@GetMapping("/page")
public R page(int page, int pageSize, String number,String beginTime,String endTime){
//分页构造器对象
Page pageInfo = new Page<>(page,pageSize);
//构造条件查询对象
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
//添加查询条件 动态sql 字符串使用StringUtils.isNotEmpty这个方法来判断
//这里使用了范围查询的动态SQL,这里是重点!!!
queryWrapper.like(number!=null,Orders::getNumber,number)
.gt(StringUtils.isNotEmpty(beginTime),Orders::getOrderTime,beginTime)
.lt(StringUtils.isNotEmpty(endTime),Orders::getOrderTime,endTime);
orderService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
测试:
但是如果你想要这个username显示用户名的话,那么有两种办法:
方法1:就是在注册的user表中添加用户名;(实际上这个用户在注册的时候是没有填写username这个选项的,所以这里查询出来全是null,所以前端就展示不出来用户)
方法二:(推荐使用)
因为我们不可能老是自己去数据库修改具体的值,所以这里我们使用用户下单的consignee来显示,数据库中也有,但是数据库中的consignee是可以为null的,所以在后台代码中帮订单添加该属性的时候要判断是否null!然后就是去修改前端代码就行:
把72行的userName改成consignee就行;
测试效果:
手机端减少购物车中的菜品或者套餐数量(前端展示有一点小问题)前端请求: http://localhost:8080/shoppingCart/sub
请求方式:post
携带参数可能是dish_id 也可能是 setmealId,所以我们需要实体类shoppingCart来接收;
遇到的bug: 就是购物车里面的菜品和套餐的数量可能会减少至负数!!!所以这里要判断和需要前端处理;
而且不知道为什么。。。。上面的数量已经为0了,但是下面的加减还是可以变话的就导致了数据库中的数据可以为负数。。。前端的问题,,,暂时使用一个简单的做法解决。。。把数据库的该字段设置为无符号字段,所以当num数小于0的时候就会报错(500接口异常),但是左下角的小购物车还是会显示菜品为0
在ShoppingCartController中添加下面的接口方法来接收请求:
/**
* 客户端的套餐或者是菜品数量减少设置
* 没必要设置返回值
* @param shoppingCart
*/
@PostMapping("/sub")
@Transactional
public R sub(@RequestBody ShoppingCart shoppingCart){
Long dishId = shoppingCart.getDishId();
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
//代表数量减少的是菜品数量
if (dishId != null){
//通过dishId查出购物车对象
queryWrapper.eq(ShoppingCart::getDishId,dishId);
ShoppingCart cart1 = shoppingCartService.getOne(queryWrapper);
cart1.setNumber(cart1.getNumber()-1);
//对数据进行更新 *** 作
shoppingCartService.updateById(cart1);
return R.success(cart1);
}
Long setmealId = shoppingCart.getSetmealId();
if (setmealId != null){
//代表是套餐数量减少
queryWrapper.eq(ShoppingCart::getSetmealId,setmealId);
ShoppingCart cart2 = shoppingCartService.getOne(queryWrapper);
cart2.setNumber(cart2.getNumber()-1);
//对数据进行更新 *** 作
shoppingCartService.updateById(cart2);
return R.success(cart2);
}
return R.error(" *** 作异常");
}
解决前端展示的bug:对上面的代码进行修改(这是评论区一个老哥提供的思路)
/**
* 客户端的套餐或者是菜品数量减少设置
* 没必要设置返回值
* @param shoppingCart
*/
@PostMapping("/sub")
@Transactional
public R sub(@RequestBody ShoppingCart shoppingCart){
Long dishId = shoppingCart.getDishId();
LambdaQueryWrapper 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).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);
}
//如果两个大if判断都进不去
return R.error("操作异常");
}
用户查看自己订单
在OrderController中添加下面的方法;
/**
* 用户订单分页查询
* @param page
* @param pageSize
* @return
*/
@GetMapping("/userPage")
public R page(int page, int pageSize){
//分页构造器对象
Page pageInfo = new Page<>(page,pageSize);
//构造条件查询对象
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
//添加排序条件,根据更新时间降序排列
queryWrapper.orderByDesc(Orders::getOrderTime);
orderService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
其实这里还没有完善!!!下面继续完善代码;
通过order.html这个页面我们可以发现:前端还需要下面这些数据;所以我们后端要传给它。。。
分析前端代码: 这个item是从order.orderDetails里面 获取到的,但是orders实体类里面并没有orderDetails这个属性,而且数据库中这个order表里面也没有这个字段,所以这里我使用的是dto来封装数据给前端,这就需要使用到dto对象的分页查询了,,,,,而且离谱的是前端就是传了一个分页页面大小的数据,,,,所以我们只能从本地线程中获取用户id开始,,一路查询数据。。。。。
创建OrdersDto实体类:
package com.itheima.reggie.dto;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.itheima.reggie.entity.OrderDetail;
import com.itheima.reggie.entity.Orders;
import lombok.Data;
import java.util.List;
/**
* @author LJM
* @create 2022/5/3
*/
@Data
public class OrderDto extends Orders {
private List orderDetails;
}
代码:这里面的代码我踩了很多坑才写出来的,看到这里的小伙伴,希望给个赞,码字不易^_^谢谢
不建议大家把业务写在controller,我是懒,所以才写在这里的。。。请勿效仿!
/**
* 用户端展示自己的订单分页查询
* @param page
* @param pageSize
* @return
* 遇到的坑:原来分页对象中的records集合存储的对象是分页泛型中的对象,里面有分页泛型对象的数据
* 开始的时候我以为前端只传过来了分页数据,其他所有的数据都要从本地线程存储的用户id开始查询,
* 结果就出现了一个用户id查询到 n个订单对象,然后又使用 n个订单对象又去查询 m 个订单明细对象,
* 结果就出现了评论区老哥出现的bug(嵌套显示数据....)
* 正确方法:直接从分页对象中获取订单id就行,问题大大简化了......
*/
@GetMapping("/userPage")
public R page(int page, int pageSize){
//分页构造器对象
Page pageInfo = new Page<>(page,pageSize);
Page pageDto = new Page<>(page,pageSize);
//构造条件查询对象
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
//这里是直接把当前用户分页的全部结果查询出来,要添加用户id作为查询条件,否则会出现用户可以查询到其他用户的订单情况
queryWrapper.eq(Orders::getUserId,BaseContext.getCurrentId());
//添加排序条件,根据更新时间降序排列
queryWrapper.orderByDesc(Orders::getOrderTime);
orderService.page(pageInfo,queryWrapper);
//通过OrderId查询对应的OrderDetail
LambdaQueryWrapper queryWrapper2 = new LambdaQueryWrapper<>();
//对OrderDto进行需要的属性赋值
List records = pageInfo.getRecords();
List orderDtoList = records.stream().map((item) ->{
OrderDto orderDto = new OrderDto();
//此时的orderDto对象里面orderDetails属性还是空 下面准备为它赋值
Long orderId = item.getId();//获取订单id
List orderDetailList = this.getOrderDetailListByOrderId(orderId);
BeanUtils.copyProperties(item,orderDto);
//对orderDto进行OrderDetails属性的赋值
orderDto.setOrderDetails(orderDetailList);
return orderDto;
}).collect(Collectors.toList());
//使用dto的分页有点难度.....需要重点掌握
BeanUtils.copyProperties(pageInfo,pageDto,"records");
pageDto.setRecords(orderDtoList);
return R.success(pageDto);
}
代码测试:
点击去支付,然后点击去查看订单:
收工!
移动端的再来一单功能由于这里没有写后台的确认订单功能,所以这里通过数据库修改订单状态来完成测试!
先把数据库中的订单表中的status改一些为4:这样在前端才能点击这个再来一单的按钮:
在order.html中可以看见这样一段前端代码:
//状态是4才会让你点击下面这个再来一单
再来一单
然后找到addOrderAgain这个方法:前端使用post请求,请求地址order/again:
写后端接口:不建议把业务代码写在controller,不然以后想复用的时候就会很麻烦的!!!
//客户端点击再来一单
/**
* 前端点击再来一单是直接跳转到购物车的,所以为了避免数据有问题,再跳转之前我们需要把购物车的数据给清除
* ①通过orderId获取订单明细
* ②把订单明细的数据的数据塞到购物车表中,不过在此之前要先把购物车表中的数据给清除(清除的是当前登录用户的购物车表中的数据),
* 不然就会导致再来一单的数据有问题;
* (这样可能会影响用户体验,但是对于外卖来说,用户体验的影响不是很大,电商项目就不能这么干了)
*/
@PostMapping("/again")
public R againSubmit(@RequestBody Map map){
String ids = map.get("id");
long id = Long.parseLong(ids);
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderDetail::getOrderId,id);
//获取该订单对应的所有的订单明细表
List orderDetailList = orderDetailService.list(queryWrapper);
//通过用户id把原来的购物车给清空,这里的clean方法是视频中讲过的,建议抽取到service中,那么这里就可以直接调用了
shoppingCartService.clean();
//获取用户id
Long userId = BaseContext.getCurrentId();
List 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);
return R.success(" *** 作成功");
}
测试:
点击再来一单:
并且购物车表中也有数据:
移动端点击套餐图片查看套餐具体菜品点击移动端套餐的图片,发现会向后端发送一个get请求,浏览器具体请求的图片我就不放了,我在前端页面找到了对应的axios请求:
至于前端展示需要的具体数据,我在前端页面没有找到。。。。。。然后就不想找了,但是找到了下面的代码;
然后我就试了三次,一次是返回R> ,一次是R>,还有一次是创建了一个SetmealDishDto,用R>,但是这个用dto怎么把dish的图片有序的设置进去我是真的处理不了>_<, 最后还是选择返回R>,,,,,不知道返回这个数据对不对哈,如果评论区有老哥有更好的写法,可以在评论区分享一下; 这个事情告诉我们和前端对接好是多么的重要!!!
代码:
@GetMapping("/dish/{id}")
//这里前端是使用路径来传值的,要注意,不然你前端的请求都接收不到,就有点尴尬哈
public R> dish(@PathVariable("id") Long SetmealId){
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SetmealDish::getSetmealId,SetmealId);
List list = setmealDishService.list(queryWrapper);
LambdaQueryWrapper queryWrapper2 = new LambdaQueryWrapper<>();
ArrayList dishIdList = new ArrayList<>();
for (SetmealDish setmealDish : list) {
Long dishId = setmealDish.getDishId();
dishIdList.add(dishId);
}
queryWrapper2.in(Dish::getId, dishIdList);
List dishList = dishService.list(queryWrapper2);
return R.success(dishList);
}
测试展示:
删除地址前端点击删除地址:然后发送删除请求到后端
在后端使用controller接收:
/**
* 根据地址id删除用户地址
* @param id
* @return
*/
@DeleteMapping
public R delete(@RequestParam("ids") Long id){
if (id == null){
return R.error("请求异常");
}
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AddressBook::getId,id).eq(AddressBook::getUserId,BaseContext.getCurrentId());
addressBookService.remove(queryWrapper);
//addressBookService.removeById(id); 感觉直接使用这个removeById不太严谨.....
return R.success("删除地址成功");
}
修改地址
点击修改符号,发现回显信息已经写好了;
回显信息的接口之前已经写好了:点击编辑,前端会发送下面的请求;
在该方法打一个debug,看回显信息是不是该接口:然后发现确实是该接口;
编写修改接口:修改完成后点击保存地址,前端会发下面的请求:
编写相关的接口:
/**
* 修改收货地址
* @param addressBook
* @return
*/
@PutMapping
public R update(@RequestBody AddressBook addressBook){
if (addressBook == null){
return R.error("请求异常");
}
addressBookService.updateById(addressBook);
return R.success("修改成功");
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)