黑马程序员Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目——第五部分

发布时间:2024年01月10日

在这里插入图片描述

在这里插入图片描述

1. 手机验证码登录

目前市面上有很多第三方提供的短信服务,这些第三方短信服务会和各个运营商(移动、联通、电信)对接,我们只需要注册成为会员并且按照提供的开发文档进行调用就可以发送短信。需要说明的是,这些短信服务一般都是收费服务。
常用短信服务:

  • 阿里云
  • 华为云
  • 腾讯云
  • 京东
  • 梦网
  • 乐信

阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或用群发助手,即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。

在这里插入图片描述

1.1 短信发送

这里使用的是阿里云短信,但是吧,对于个人来说,几乎是申请不到短信的模板签名,所以这里只能大概介绍一下怎么使用阿里云发送短信,具体调用阿里云短信服务的Java代码也会给出;

先引入maven坐标:

<!--阿里云短信服务-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.5.16</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
            <version>2.1.0</version>
        </dependency>

短信签名:短信发送者的署名,表示发送方的身份。
在这里插入图片描述

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

然后调用api:

package com.itheima.reggie.utils;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;

/**
 * 短信发送工具类
 */
public class SMSUtils {

	/**
	 * 发送短信
	 * @param signName 签名
	 * @param templateCode 模板
	 * @param phoneNumbers 手机号
	 * @param param 参数
	 */
	public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
       
		DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
		IAcsClient client = new DefaultAcsClient(profile);

		SendSmsRequest request = new SendSmsRequest();
		request.setSysRegionId("cn-hangzhou");
		request.setPhoneNumbers(phoneNumbers);
		request.setSignName(signName);
		request.setTemplateCode(templateCode);
		request.setTemplateParam("{\"code\":\""+param+"\"}");
		try {
			SendSmsResponse response = client.getAcsResponse(request);
			System.out.println("短信发送成功");
		}catch (ClientException e) {
			e.printStackTrace();
		}
	}
}

1.2 短信验证码登陆

需求分析:

在这里插入图片描述

数据模型:

在这里插入图片描述

前后端交互过程:

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

  1. 在登录页面(front/page/login.html)输入手机号,点击[获取验证码]按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信
  2. 在登录页面输入验证码,点击[登录]按钮,发送ajax请求, 在服务端处理登录请求

开发手机验证码登录功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

代码开发:

在开发业务功能前,先将需要用到的类和接口基本结构创建好:

  • 实体类User (直接从课程资料中导入即可)
  • Mapper接口UserMapper
  • 业务层接口UserService
  • 业务层实现类UserServicelmpl
  • 控制层UserController
  • 工具类SMSUtils,ValidateCodeUtils

SMSUtils

package com.itheima.reggie.utils;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;

/**
 * 短信发送工具类
 */
public class SMSUtils {

	/**
	 * 发送短信
	 * @param signName 签名
	 * @param templateCode 模板
	 * @param phoneNumbers 手机号
	 * @param param 参数
	 */
	public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
		DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
		IAcsClient client = new DefaultAcsClient(profile);

		SendSmsRequest request = new SendSmsRequest();
		request.setSysRegionId("cn-hangzhou");
		request.setPhoneNumbers(phoneNumbers);
		request.setSignName(signName);
		request.setTemplateCode(templateCode);
		request.setTemplateParam("{\"code\":\""+param+"\"}");
		try {
			SendSmsResponse response = client.getAcsResponse(request);
			System.out.println("短信发送成功");
		}catch (ClientException e) {
			e.printStackTrace();
		}
	}

}

ValidateCodeUtils

package com.itheima.reggie.utils;

import java.util.Random;

/**
 * 随机生成验证码工具类
 */
public class ValidateCodeUtils {
    /**
     * 随机生成验证码
     * @param length 长度为4位或者6位
     * @return
     */
    public static Integer generateValidateCode(int length){
        Integer code =null;
        if(length == 4){
            code = new Random().nextInt(9999);//生成随机数,最大为9999
            if(code < 1000){
                code = code + 1000;//保证随机数为4位数字
            }
        }else if(length == 6){
            code = new Random().nextInt(999999);//生成随机数,最大为999999
            if(code < 100000){
                code = code + 100000;//保证随机数为6位数字
            }
        }else{
            throw new RuntimeException("只能生成4位或6位数字验证码");
        }
        return code;
    }

    /**
     * 随机生成指定长度字符串验证码
     * @param length 长度
     * @return
     */
    public static String generateValidateCode4String(int length){
        Random rdm = new Random();
        String hash1 = Integer.toHexString(rdm.nextInt());
        String capstr = hash1.substring(0, length);
        return capstr;
    }
}

导入user实体类;

创建userMapper:

package com.itheima.reggie.mapper;

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

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

创建service:

package com.itheima.reggie.service;

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


public interface UserService extends IService<User> {
}

实现service:

package com.itheima.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.User;
import com.itheima.reggie.mapper.UserMapper;
import com.itheima.reggie.service.UserService;
import org.springframework.stereotype.Service;


@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

导入工具类

1.2.1 发送验证码

注意这个资料有点残缺,补全:

在longin.html中找到这个获取验证码的方法,把一行注释,然后添加一行代码一行;

getCode(){
    this.form.code = ''
    const regex = /^(13[0-9]{9})|(15[0-9]{9})|(17[0-9]{9})|(18[0-9]{9})|(19[0-9]{9})$/;
    if (regex.test(this.form.phone)) {
        this.msgFlag = false
        //this.form.code = (Math.random()*1000000).toFixed(0)
        sendMsgApi({phone:this.form.phone})  //添加的
    }else{
        this.msgFlag = true
    }
},

在login.js中添加一个方法:

function sendMsgApi(data){
    return $axios({
        'url':'/user/sendMsg',
        'method':'post',
        data
    })
}

登陆拦截器LongCheckFilter中添加新的白名单:

String[] urls = new String[]{
        "/employee/login",
        "/employee/logout",
        "/backend/**",
        "/front/**",
        "/common/**",
        "/user/sendMsg", //移动端发送短信
        "/user/login"  // 移动端登陆
};

并且在里面继续添加一个手机端登陆状态的放行判断:

//4-2判断移动端登录状态,如果已登录,则直接放行
        if(request.getSession().getAttribute("user") != null){
            //log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));
            //把用户id存储到本地的threadLocal
            Long userId = (Long) request.getSession().getAttribute("user");
            BaseContext.setCurrentId(userId);


            filterChain.doFilter(request,response);
            return;
        }

编写controller:

@Autowired
private UserService userService;

/**
 * 发送手机短信验证码
 * @param user
 * @return
 */
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
    //获取手机号
    String phone = user.getPhone();
    if (StringUtils.isNotEmpty(phone)){
        //随机生成的4为验证码
        Integer integerCode = ValidateCodeUtils.generateValidateCode(4);
        String code = integerCode.toString();
        
        log.info("code={}",code);
        //调用阿里云提供的短信服务api完成发送短信  这里个人用户申请不了阿里云短信服务的签名,所以这里在后台输出了
        //SMSUtils.sendMessage("","","","");

        //把验证码存起来  这里使用session来存放验证码,当然也可以存到redis
        session.setAttribute(phone,code);
        return R.success("手机验证码发送成功");
    }

    return R.error("手机验证码发送失败");
}

功能测试:访问手机端,输入手机号,看能不能在后台打印验证码;

1.2.1 使用验证码登陆(使用map接收数据)

注意:测试的时候发现前端页面明明填了验证码,发现验证码并没有被携带在前端的请求参数中,所以后端也没有拿到验证码这个数据,一看就是前端发请求的地方的参数携带少了;修改一下参数就行;

async btnLogin(){
    if(this.form.phone && this.form.code){
        this.loading = true
        
        //const res = await loginApi({phone:this.form.phone})  这里是资料给的代码
        const res = await loginApi(this.form)  //这里是自己加的
        
        ....
}

controller层代码

/**
 * 移动端用户登录
 * @param map
 * @param session
 * @return
 */
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){
    //log.info(map.toString());

    //获取手机号
    String phone = map.get("phone").toString();

    //获取验证码
    String code = map.get("code").toString();

    //从Session中获取保存的验证码
    Object codeInSession = session.getAttribute(phone);

    //进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
    if(codeInSession != null && codeInSession.equals(code)){
        //如果能够比对成功,说明登录成功

        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getPhone,phone);
        //根据用户的手机号去用户表获取用户
        User user = userService.getOne(queryWrapper);
        if(user == null){
            //判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
            user = new User();
            user.setPhone(phone);
            user.setStatus(1); //可设置也可不设置,因为数据库我们设置了默认值
            //注册新用户
            userService.save(user);
        }
        //这一行容易漏。。保存用户登录状态
        session.setAttribute("user",user.getId()); //在session中保存用户的登录状态,这样才过滤器的时候就不会被拦截了
        return R.success(user);
    }
    return R.error("登录失败");
}

功能测试:验证码正确后跳转到手机端;

在这里插入图片描述

2. 地址管理

导入用户地址簿类:从资料中获取就行

数据模型:

代码开发:

准备工作,导入AddressBook实体类,检查一下实体类的属性是否和数据库中的表字段一一对应;

创建mapper:

package com.itheima.reggie.mapper;

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

import java.util.List;

@Mapper
public interface AddressBookMapper extends BaseMapper<AddressBook> {

}
package com.itheima.reggie.service;

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

public interface AddressBookService extends IService<AddressBook> {

}

实现类:

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 {

}

controller层编写:

package com.itheima.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.reggie.common.BaseContext;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.AddressBook;
import com.itheima.reggie.service.AddressBookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 地址簿管理
 */
@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);
        //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);
    }

    /**
     * 根据id查询地址
     */
    @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);

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

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

功能测试:

在这里插入图片描述

3. 手机端展示

3.1 菜品展示

前端重点代码:

在这里插入图片描述

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

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

在这里插入图片描述

我们发现前端的展示页面中请求到了category的数据,服务器也响应了数据给前端页面,但是我们也看见了手机端并没有展示相关的套餐数据;这是因为在加载页面的时候,前端一共发了两个list请求,具体的请求看上面的前端代码,请求购物车信息的请求返回404,所以导致category的数据也没有被展示;

这里我们先在其他地方静态的接收购物车的请求,这样就可以先显示category的数据;先用假数据测试一下:

修改购物车的请求地址,

//获取购物车内商品的集合
function cartListApi(data) {
    return $axios({
        //'url': '/shoppingCart/list',
        'url':'/front/cartData.json',
        'method': 'get',
        params:{...data}
    })
}
// 假数据文件
{"code":1,"msg":null,"data":[],"map":{}}

功能测试:

在这里插入图片描述

但是我们也发现了bug,就是展示的菜品没有对应的口味信息,比如甜度,辣度。。。我们之前是添加过相关的口味数据的;

这是因为我们在请求获取菜品信息的时候,我们返回的数据是R<List> ,Dish这个类是没有相关的口味信息的,所以即便前端请求了这个口味的信息,但是后端是没有给它返回的,所以体现在前端就是口味信息没有展示出来;所以我们需要对DishController中的list接口进行修改;

bug修复:

//方法改造
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish){ //会自动映射的
    //这里可以传categoryId,但是为了代码通用性更强,这里直接使用dish类来接受(因为dish里面是有categoryId的),以后传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();
        //为一个新的对象赋值,一定要考虑你为它赋过几个值,否则你自己都不知道就返回了null的数据
        //为dishDto对象的基本属性拷贝
        BeanUtils.copyProperties(item,dishDto);
        Long categoryId = item.getCategoryId();
        Category category = categoryService.getById(categoryId);
        if (category != null){
            String categoryName = category.getName();
            dishDto.setCategoryName(categoryName);
        }
        //为dishdto赋值flavors属性
        //当前菜品的id
        Long dishId = item.getId();
        //创建条件查询对象
        LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);
        //select * from dish_flavor where dish_id = ?
        //这里之所以使用list来条件查询那是因为同一个dish_id 可以查出不同的口味出来,就是查询的结果不止一个
        List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
        dishDto.setFlavors(dishFlavorList);

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

    return R.success(dishDtoList);
}

3.2 套餐展示

套餐展示的前端请求地址和携带的参数:

在这里插入图片描述

在SetmealController中添加相应的方法来接收前端的请求:

    /**
     * 根据条件查询套餐数据
     * @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);

    }

功能测试:

在这里插入图片描述

4. 购物车

4.1 添加菜品和套餐进购物车

数据模型: 购物车对应的数据表为shopping_cart,具体结构如下:从资料中导入相关的实体类就行

代码开发:

加入购物车功能:

加入购物车的前端请求和携带的参数:

在这里插入图片描述

开发准备工作:

导入实体类ShoppingCart;

创建对应的mapper:

package com.itheima.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.ShoppingCart;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;

@Mapper
public interface ShoppingCartMapper extends BaseMapper<ShoppingCart> {

}

创建service:

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

public interface ShoppingCartService extends IService<ShoppingCart> {

}

实现类:

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 {

}

controller层开发:

@Autowired
private ShoppingCartService shoppingCartService;

/**
 * 添加购物车
 * @param shoppingCart
 * @return
 */
@PostMapping("/add")
public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){

    //先设置用户id,指定当前是哪个用户的购物车数据  因为前端没有传这个id给我们,但是这个id又非常重要(数据库这个字段不能为null),
    // 所以要想办法获取到,我们在用户登录的时候就已经保存了用户的id
    Long currentId = BaseContext.getCurrentId();
    shoppingCart.setUserId(currentId);

    Long dishId = shoppingCart.getDishId();

    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    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 {
        //如果不存在,则添加到购物车,数量默认是1
        shoppingCart.setNumber(1);
        shoppingCartService.save(shoppingCart);
        cartServiceOne = shoppingCart;
    }
    return R.success(cartServiceOne);
}

功能测试:

在这里插入图片描述

不过这个只能把菜品或者是套餐加入到购物车,那个减号点了是没效果的;

4.2 查看购物车

把相关的前端请求地址给改掉, 不再请求假数据,至于存放假数据的json文件是否删除,看你自己,删不删都没什么影响;

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

注意:一定要有用户的概念,不同用户看到的购物车是不一样的!!!

controller:

/**
 * 查看购物车
 * @return
 * 前端没有传数据给我们,这里就不用接收了
 */
@GetMapping
public R<List<ShoppingCart>> list(){
    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);
}

功能测试:

在这里插入图片描述

4.3 清空购物车

/**
 * 清空购物车
 * @return
 */
@DeleteMapping("/clean")
public R<String> clean(){

    //sql:delete from shopping_cart where userId =?
    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
    shoppingCartService.remove(queryWrapper);
    
    return R.success("清空购物车成功");

}

4.4 减少购物车点菜品或者套餐

前端请求: http://localhost:8080/shoppingCart/sub

请求方式:post

携带参数可能是dish_id 也可能是 setmealId,所以我们需要实体类shoppingCart来接收;

遇到的bug: 就是购物车里面的菜品和套餐的数量可能会减少至负数!!!所以这里我们要在入库前做一次判断;把数据库的该字段设置为无符号字段,所以当num数小于0的时候就会报错(500接口异常),但是左下角的小购物车还是会显示菜品为0,所以在后端的代码也要进行判断操作;

在这里插入图片描述

在ShoppingCartController中添加下面的接口方法来接收请求:(这个是修改了一次的代码,思路用的是评论区一个老哥提供的思路)

    /**
     * 客户端的套餐或者是菜品数量减少设置
     * 没必要设置返回值
     * @param shoppingCart
     */
    @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).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("操作异常");
    }

5. 用户订单

5.1 用户下单功能

需求分析:

注意:这里只是把用户的支付订单保存到数据库,因为真正的支付功能是需要去申请支付资质的,个人用户很难申请到;

数据模型:用户下单业务对应的数据表为order表和order_detail表:

order:订单表;
order_detail:订单明细表;

代码开发:

前端和服务器的交互过程:

第一次交互:

然后点击去支付:然后前端就会发生http://localhost:8080/order/submit这个请求;并且携带三个参数:一个是地址,一个是字符方式,一个是客户的备注;

在这里插入图片描述

开发准备工作:

导入实体类:order,order_details

创建mapper:

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> {

}
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> {

}

创建service及其实现:

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

public interface OrderService extends IService<Orders> {
}
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.OrderDetail;

public interface OrderDetailService extends IService<OrderDetail> {
}
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 org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {
}
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 {

}

controller层开发:

前端请求的地址和携带的参数前面已经分析过了;

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

service添加submit方法:

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

方法的实现:

@Autowired
private ShoppingCartService shoppingCartService;

@Autowired
private UserService userService;

@Autowired
private AddressBookService addressBookService;

@Autowired
OrderDetailService orderDetailService;

/**
 * 用户下单
 * @param orders
 */
@Override
@Transactional
public void submit(Orders orders) {
    //前端请求携带的参数是没有用户id的,所以要获取用户id
    Long userId = BaseContext.getCurrentId();
    //创建查询条件
    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId,userId);
    //查询当前用户的购物车数据
    List<ShoppingCart> shoppingCarts = shoppingCartService.list(queryWrapper);
    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();//使用工具生成订单号
    orders.setId(orderId);

    //进行购物车的金额数据计算 顺便把订单明细给计算出来
    AtomicInteger amount = new AtomicInteger(0);//使用原子类来保存计算的金额结果
    //这个item是集合中的每一个shoppingCarts对象,是在变化的
    List<OrderDetail> orderDetails = shoppingCarts.stream().map((item)->{
        //每对item进行一次遍历就产生一个新的orderDetail对象,然后对orderDetail进行设置,然后返回被收集,被封装成一个集合
        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());//单份的金额
        //addAndGet进行累加 item.getAmount()单份的金额  multiply乘  item.getNumber()份数
        amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue());
        return orderDetail;

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

    //向订单插入数据,一条数据  因为前端传过来的数据太少了,所以我们需要对相关的属性进行填值

    orders.setOrderTime(LocalDateTime.now());
    orders.setCheckoutTime(LocalDateTime.now());
    orders.setStatus(2);
    //Amount是指订单总的金额
    orders.setAmount(new BigDecimal(amount.get()));//总金额
    orders.setUserId(userId);
    orders.setNumber(String.valueOf(orderId));
    if (user.getName() != null){
            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);

    //清空购物车数据  queryWrapper封装了userId我们直接使用这个条件来进行删除就行
    shoppingCartService.remove(queryWrapper);
}

功能测试;

5.1 用户查看自己订单

/**
 * 用户订单分页查询
 * @param page
 * @param pageSize
 * @return
 */
@GetMapping("/userPage")
public R<Page> page(int page, int pageSize){

    //分页构造器对象
    Page<Orders> pageInfo = new Page<>(page,pageSize);
    //构造条件查询对象
    LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();

    //添加排序条件,根据更新时间降序排列
    queryWrapper.orderByDesc(Orders::getOrderTime);
    orderService.page(pageInfo,queryWrapper);

    return R.success(pageInfo);
}

功能测试;

5.2 用户再来一单功能

由于这里没有写后台的确认订单功能,所以这里通过数据库修改订单状态来完成测试!

先把数据库中的订单表中的status改一些为4:这样在前端才能点击这个再来一单

在这里插入图片描述

在order.html中可以看见这样一段前端代码:

<div class="btn" v-if="order.status === 4">  //状态是4才会让你点击下面这个再来一单
     <div class="btnAgain" @click="addOrderAgain(order)">再来一单
     </div>
</div>

然后找到addOrderAgain这个方法:前端使用post请求,请求地址order/again

在这里插入图片描述

写后端接口:不建议把业务代码写在controller,不然以后想复用的时候就会很麻烦的!!!

这里我偷懒了,所以写再controller中了;

//客户端点击再来一单
    /**
     * 前端点击再来一单是直接跳转到购物车的,所以为了避免数据有问题,再跳转之前我们需要把购物车的数据给清除
     * ①通过orderId获取订单明细
     * ②把订单明细的数据的数据塞到购物车表中,不过在此之前要先把购物车表中的数据给清除(清除的是当前登录用户的购物车表中的数据),
     * 不然就会导致再来一单的数据有问题;
     * (这样可能会影响用户体验,但是对于外卖来说,用户体验的影响不是很大,电商项目就不能这么干了)
     */
    @PostMapping("/again")
    public R<String> 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);
        //获取该订单对应的所有的订单明细表
        List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper);

        //通过用户id把原来的购物车给清空,这里的clean方法是视频中讲过的,建议抽取到service中,那么这里就可以直接调用了
        shoppingCartService.clean();

        //获取用户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);

        return R.success("操作成功");
    }

测试:自己参数就行;

6. 移动端开发(转发)

6.1 后台按条件查看和展示客户订单

6.2 手机端减少购物车中的菜品或者套餐数量

前端请求: http://localhost:8080/shoppingCart/sub

请求方式:post

携带参数可能是dish_id 也可能是 setmealId,所以我们需要实体类shoppingCart来接收;

遇到的bug: 就是购物车里面的菜品和套餐的数量可能会减少至负数!!!所以这里要判断和需要前端处理;

而且不知道为什么。。。。上面的数量已经为0了,但是下面的加减还是可以变话的就导致了数据库中的数据可以为负数。。。前端的问题,,,暂时使用一个简单的做法解决。。。把数据库的该字段设置为无符号字段,所以当num数小于0的时候就会报错(500接口异常),但是左下角的小购物车还是会显示菜品为0

在ShoppingCartController中添加下面的接口方法来接收请求:

    /**
     * 客户端的套餐或者是菜品数量减少设置
     * 没必要设置返回值
     * @param shoppingCart
     */
    @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);
            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<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).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("操作异常");
    }

6.3 用户查看自己订单

在OrderController中添加下面的方法;

/**
 * 用户订单分页查询
 * @param page
 * @param pageSize
 * @return
 */
@GetMapping("/userPage")
public R<Page> page(int page, int pageSize){

    //分页构造器对象
    Page<Orders> pageInfo = new Page<>(page,pageSize);
    //构造条件查询对象
    LambdaQueryWrapper<Orders> 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<OrderDetail> orderDetails;
}

代码:这里面的代码我踩了很多坑才写出来的,看到这里的小伙伴,希望给个赞,码字不易_谢谢

不建议大家把业务写在controller,我是为了方便才写这里的,在企业的实际开发中千万不要这么干!!!请勿效仿!

    //抽离的一个方法,通过订单id查询订单明细,得到一个订单明细的集合
    //这里抽离出来是为了避免在stream中遍历的时候直接使用构造条件来查询导致eq叠加,从而导致后面查询的数据都是null
    public List<OrderDetail> getOrderDetailListByOrderId(Long orderId){
        LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderDetail::getOrderId, orderId);
        List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper);
        return orderDetailList;
    }

    /**
     * 用户端展示自己的订单分页查询
     * @param page
     * @param pageSize
     * @return
     * 遇到的坑:原来分页对象中的records集合存储的对象是分页泛型中的对象,里面有分页泛型对象的数据
     * 开始的时候我以为前端只传过来了分页数据,其他所有的数据都要从本地线程存储的用户id开始查询,
     * 结果就出现了一个用户id查询到 n个订单对象,然后又使用 n个订单对象又去查询 m 个订单明细对象,
     * 结果就出现了评论区老哥出现的bug(嵌套显示数据....)
     * 正确方法:直接从分页对象中获取订单id就行,问题大大简化了......
     */
    @GetMapping("/userPage")
    public R<Page> page(int page, int pageSize){
        //分页构造器对象
        Page<Orders> pageInfo = new Page<>(page,pageSize);
        Page<OrderDto> pageDto = new Page<>(page,pageSize);
        //构造条件查询对象
        LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Orders::getUserId,BaseContext.getCurrentId());
        //这里是直接把当前用户分页的全部结果查询出来,要添加用户id作为查询条件,否则会出现用户可以查询到其他用户的订单情况
        //添加排序条件,根据更新时间降序排列
        queryWrapper.orderByDesc(Orders::getOrderTime);
        orderService.page(pageInfo,queryWrapper);

        //通过OrderId查询对应的OrderDetail
        LambdaQueryWrapper<OrderDetail> queryWrapper2 = new LambdaQueryWrapper<>();

        //对OrderDto进行需要的属性赋值
        List<Orders> records = pageInfo.getRecords();
        List<OrderDto> orderDtoList = records.stream().map((item) ->{
            OrderDto orderDto = new OrderDto();
            //此时的orderDto对象里面orderDetails属性还是空 下面准备为它赋值
            Long orderId = item.getId();//获取订单id
            List<OrderDetail> 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);
    }

代码测试:

点击去支付,然后点击去查看订单:

收工!

6.4 移动端的再来一单功能

由于这里没有写后台的确认订单功能,所以这里通过数据库修改订单状态来完成测试!

先把数据库中的订单表中的status改一些为4:这样在前端才能点击这个再来一单的按钮:

在order.html中可以看见这样一段前端代码:

<div class="btn" v-if="order.status === 4">  //状态是4才会让你点击下面这个再来一单
     <div class="btnAgain" @click="addOrderAgain(order)">再来一单
     </div>
</div>

然后找到addOrderAgain这个方法:前端使用post请求,请求地址order/again:

写后端接口:不建议把业务代码写在controller,不然以后想复用的时候就会很麻烦的!!!

    //客户端点击再来一单
    /**
     * 前端点击再来一单是直接跳转到购物车的,所以为了避免数据有问题,再跳转之前我们需要把购物车的数据给清除
     * ①通过orderId获取订单明细
     * ②把订单明细的数据的数据塞到购物车表中,不过在此之前要先把购物车表中的数据给清除(清除的是当前登录用户的购物车表中的数据),
     * 不然就会导致再来一单的数据有问题;
     * (这样可能会影响用户体验,但是对于外卖来说,用户体验的影响不是很大,电商项目就不能这么干了)
     */
    @PostMapping("/again")
    public R<String> 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);
        //获取该订单对应的所有的订单明细表
        List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper);

        //通过用户id把原来的购物车给清空,这里的clean方法是视频中讲过的,建议抽取到service中,那么这里就可以直接调用了
        shoppingCartService.clean();

        //获取用户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);

        return R.success("操作成功");
    }

测试:

点击再来一单:

并且购物车表中也有数据:

6.5 移动端点击套餐图片查看套餐具体菜品

点击移动端套餐的图片,发现会向后端发送一个get请求,浏览器具体请求的图片我就不放了,我在前端页面找到了对应的axios请求:

至于前端展示需要的具体数据,我在前端页面没有找到。。。。。。然后就不想找了,但是找到了下面的代码;

然后我就试了三次,一次是返回R<List> ,一次是R<List>,还有一次是创建了一个SetmealDishDto,用R<List>,但是这个用dto怎么把dish的图片有序的设置进去我是真的处理不了>_<, 最后还是选择返回R<List>,,,,,不知道返回这个数据对不对哈,如果评论区有老哥有更好的写法,可以在评论区分享一下; 这个事情告诉我们和前端对接好是多么的重要!!!

下面的写法是修正后的写法,是评论区一位老哥指点后写出的;我开始返回的确实是<List>,不过下面的代码是纠正后的代码;

代码:


        /**
     * 移动端点击套餐图片查看套餐具体内容
     * 这里返回的是dto 对象,因为前端需要copies这个属性
     * 前端主要要展示的信息是:套餐中菜品的基本信息,图片,菜品描述,以及菜品的份数
     * @param SetmealId
     * @return
     */
    //这里前端是使用路径来传值的,要注意,不然你前端的请求都接收不到,就有点尴尬哈
    @GetMapping("/dish/{id}")
    public R<List<DishDto>> dish(@PathVariable("id") Long SetmealId){
        LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
        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);
    }

测试展示: 之前我自己第一次写的这个代码的展示效果是没有这个菜品的具体份数的,因为当时我返回给前端的数据是:<List>;

6.6 删除地址

前端点击删除地址:然后发送删除请求到后端

在后端使用controller接收:

     /**
     * 根据地址id删除用户地址
     * @param id
     * @return
     */
    @DeleteMapping
    public R<String> delete(@RequestParam("ids") Long id){

        if (id == null){
            return R.error("请求异常");
        }
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AddressBook::getId,id).eq(AddressBook::getUserId,BaseContext.getCurrentId());
        addressBookService.remove(queryWrapper);
        //addressBookService.removeById(id);  感觉直接使用这个removeById不太严谨.....
        return R.success("删除地址成功");
    }

6.7 修改地址

点击修改符号,发现回显信息已经写好了;

回显信息的接口之前已经写好了:点击编辑,前端会发送下面的请求;

在该方法打一个debug,看回显信息是不是该接口:然后发现确实是该接口;

编写修改接口:修改完成后点击保存地址,前端会发下面的请求:

编写相关的接口:

    /**
     * 修改收货地址
     * @param addressBook
     * @return
     */
    @PutMapping
    public R<String> update(@RequestBody AddressBook addressBook){

        if (addressBook == null){
            return R.error("请求异常");
        }
        addressBookService.updateById(addressBook);

        return R.success("修改成功");
    }

6.8 后台订单状态的修改

在后台订单明细中点击派送按钮:前端会发送下面的请求来:是json格式的数据;

请求地址:http://localhost:8080/order

后台接收:

    @PutMapping
    public R<String> orderStatusChange(@RequestBody Map<String,String> map){

        String id = map.get("id");
        Long orderId = Long.parseLong(id);
        Integer status = Integer.parseInt(map.get("status"));

        if(orderId == null || status==null){
            return R.error("传入信息不合法");
        }
        Orders orders = orderService.getById(orderId);
        orders.setStatus(status);
        orderService.updateById(orders);

        return R.success("订单状态修改成功");

    }

测试:自己测试就行;

6.9 移动端登陆退出功能

    /**
     * 退出功能
     * ①在controller中创建对应的处理方法来接受前端的请求,请求方式为post;
     * ②清理session中的用户id
     * ③返回结果(前端页面会进行跳转到登录页面)
     * @return
     */
    @PostMapping("/loginout")
    public R<String> logout(HttpServletRequest request){
        //清理session中的用户id
        request.getSession().removeAttribute("user");
        return R.success("退出成功");
    }
文章来源:https://blog.csdn.net/weixin_46225503/article/details/135445159
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。