• 一个SpringBoot单体项目-->瑞吉外卖项目之前台浏览端基础功能开发


    一 前台用户登录功能实现

    1.1 关于腾讯云的短信服务(个人注册与开通)

    具体实现参考这两篇:
    https://blog.:.net/mjl1125/article/details/122047618
    https://blog.csdn.net/mjl1125/article/details/122049929

    1.2 关于本项目实现短信发送功能步骤

    1. 引入依赖
    
    • 1
          
            <dependency>
                <groupId>com.tencentcloudapigroupId>
                <artifactId>tencentcloud-sdk-javaartifactId>
                
                
                <version>3.1.322version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    2. 创建发送短信工具包
    
    • 1
    package cn.zkwf.takeout.utils;
    
    import com.tencentcloudapi.common.Credential;
    import com.tencentcloudapi.common.profile.ClientProfile;
    import com.tencentcloudapi.common.profile.HttpProfile;
    import com.tencentcloudapi.common.exception.TencentCloudSDKException;
    import com.tencentcloudapi.sms.v20210111.SmsClient;
    import com.tencentcloudapi.sms.v20210111.models.*;
    
    public class SMSUtils
    {
    
    	
        public static void sendMsg(String phone,String param){
             try{
                // 实例化一个认证对象,入参需要传入腾讯云账户secretId,secretKey,此处还需注意密钥对的保密
                // 密钥可前往https://console.cloud.tencent.com/cam/capi网站进行获取
                Credential cred = new Credential("secretId", "secretKey");
                // 实例化一个http选项,可选的,没有特殊需求可以跳过
                HttpProfile httpProfile = new HttpProfile();
                httpProfile.setEndpoint("sms.tencentcloudapi.com");
                // 实例化一个client选项,可选的,没有特殊需求可以跳过
                ClientProfile clientProfile = new ClientProfile();
                clientProfile.setHttpProfile(httpProfile);
                // 实例化要请求产品的client对象,clientProfile是可选的
                SmsClient client = new SmsClient(cred, "ap-guangzhou", clientProfile);
                // 实例化一个请求对象,每个接口都会对应一个request对象
                SendSmsRequest req = new SendSmsRequest();
                String[] phoneNumberSet1 = {"+86 "+phone};
                req.setPhoneNumberSet(phoneNumberSet1);
    
                req.setSmsSdkAppId("签名id");
                req.setSignName("签名");
                req.setTemplateId("模版编号");
    
                String[] templateParamSet1 = {param};
                req.setTemplateParamSet(templateParamSet1);
    
                // 返回的resp是一个SendSmsResponse的实例,与请求对象对应
                SendSmsResponse resp = client.SendSms(req);
                // 输出json格式的字符串回包
                System.out.println(SendSmsResponse.toJsonString(resp));
            } catch (TencentCloudSDKException e) {
                System.out.println(e.toString());
            }
    	}
    }
    
    • 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
    3 导入随机验证码产生工具包
    
    • 1
    package cn.zkwf.takeout.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;
        }
    }
    
    
    • 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
    4. 调工具发送消息
    
    • 1

    1.3 手机验证码登录

    1.3.1 需求分析

    为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能。手机验证码登录有如下优点:

    1). 方便快捷,无需注册,直接登录

    2). 使用短信验证码作为登录凭证,无需记忆密码

    3). 安全
    **登录流程:**输入手机号 > 获取验证码 > 输入验证码 > 点击登录 > 登录成功

    1.3.2 交互流程

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

    1.3.3 代码开发

    修改LoginCheckFilter

    	我们在进行手机验证码登录时,发送的两个请求(获取验证码和登录)需要在此过滤器处理时直接放行。对于移动的端的页面,也是用户登录之后,才可以访问的,那么这个时候就需要在 LoginCheckFilter 中进行判定,如果移动端用户已登录,我们获取到用户登录信息,存入ThreadLocal中(在后续的业务处理中,如果需要获取当前登录用户ID,直接从ThreadLocal中获取),然后放行。
    
    • 1
    //4-2、判断登录状态,如果已登录,则直接放行
    if(request.getSession().getAttribute("user") != null){
        log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));
    
        Long userId = (Long) request.getSession().getAttribute("user");
        BaseContext.setCurrentId(userId);
    
        filterChain.doFilter(request,response);
        return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    发送短信验证码

      @PostMapping("/sendMsg")
        public R<String> sendMsg(@RequestBody User user, HttpSession httpSession){
            log.info("user:{}",user.toString());
    
            //获取phone
            String phone = user.getPhone();
            //phone进行判空
            if (phone!=null&&!"".equals(phone)&&!"null".equals(phone)){
                //设置发送验证码
                String param =String.valueOf(ValidateCodeUtils.generateValidateCode(6));
    
                //将验证码保存到Session中 用来支持login逻辑
                //httpSession.setAttribute("param",param);
    
                //使用redis进行缓存操作 依赖注入redis模版
                redisTemplate.opsForValue().set(phone,param,5, TimeUnit.MINUTES);
    
    
                log.info("验证码:{}",param);
                //调用短信发送工具类
                SMSUtils.sendMsg(phone,param);
    
                return R.success("短信发送成功!!");
            }
    
            return R.error("未知错误,请重试!");
        }
    
    • 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

    二 前台用户地址簿功能实现

    1.1 需求分析

    地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址
    对于地址簿管理,我们需要实现以下几个功能: (对单表的增删改查)

    • 新增地址
    • 地址列表查询
    • 设置默认地址
    • 编辑地址
    • 删除地址

    1.2 分析数据库

    请添加图片描述
    这里面有一个字段is_default,实际上在设置默认地址时,只需要更新这个字段就可以了。

    1.3 具体实现

    package cn.zkwf.takeout.controller;
    
    import cn.zkwf.takeout.common.ThreadContext;
    import cn.zkwf.takeout.entity.AddressBook;
    import cn.zkwf.takeout.resulttype.R;
    import cn.zkwf.takeout.service.AddressBookService;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    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
    @Api(tags = "预定地址相关接口")
    @RequestMapping("/addressBook")
    public class AddressBookController {
    
        @Autowired
        private AddressBookService addressBookService;
    
        /**
         * 新增
         */
        @ApiOperation(value = "增加地址")
        @PostMapping
        public R<AddressBook> save(@RequestBody AddressBook addressBook) {
            addressBook.setUserId(ThreadContext.getParameter());
            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, ThreadContext.getParameter());
            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, ThreadContext.getParameter());
            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(ThreadContext.getParameter());
            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
    • 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
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    三 前台用户菜品展示实现

    3.1 需求分析

    请添加图片描述

    3.2 交互流程

    1). 页面(front/index.html)发送ajax请求,获取分类数据
    2). 页面发送ajax请求,获取第一个分类下的菜品或者套餐
    A. 根据分类ID查询套餐列表:
    B. 根据分类ID查询菜品列表:
    分析得知:服务端我们主要提供两个方法, 分别用来
    A. 根据分类ID查询菜品列表(包含菜品口味列表);
    B. 根据分类ID查询套餐列表

    3.3 代码开发

    3.3.1 查询菜品方法修改

    分析:由于之前实现的根据分类查询菜品列表,仅仅查询了菜品的基本信息,未查询菜品口味信息,而移动端用户在点餐时,是需要选择口味信息的,我们需要修改DishController的list方法,原来此方法的返回值类型为:R。为了满足移动端对数据的要求(菜品基本信息和菜品对应的口味信息),现在需要将方法的返回值类型改为:R ,因为在DishDto中封装了菜品对应的口味信息:
    代码逻辑:
    A. 根据分类ID查询,查询目前正在启售的菜品列表 (已实现)
    B. 遍历菜品列表,并查询菜品的分类信息及菜品的口味列表
    C. 组装数据DishDto,并返回
    代码实现:

    @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);
    
                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<>();
                lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);
                //SQL: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);
        }
    
    • 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

    3.3.2 根据分类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

    四 前台用户购物实现

    4.1 需求分析

    	移动端用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击 +号 将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。
    	需要实现的功能包括: 
    			1). 添加购物车
    			2). 查询购物车
    			3). 清空购物车
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.2 数据模型

    • 购物车数据是关联用户的,在表结构中,我们需要记录,每一个用户的购物车数据是哪些
    • 菜品列表展示出来的既有套餐,又有菜品,如果APP端选择的是套餐,就保存套餐ID(setmeal_id),如果APP端选择的是菜品,就保存菜品ID(dish_id)
    • 对同一个菜品/套餐,如果选择多份不需要添加多条记录,增加数量number即可

    4.3 交互过程

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

    4.4 代码开发

    4.4.1 添加购物车

    实现逻辑
    A. 获取当前登录用户,为购物车对象赋值
    B. 根据当前登录用户ID 及 本次添加的菜品ID/套餐ID,查询购物车数据是否存在
    C. 如果已经存在,就在原来数量基础上加1
    D. 如果不存在,则添加到购物车,数量默认就是1
    代码实现

    /**
    * 添加购物车
    * @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<>();
        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);
            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
    • 40
    • 41
    • 42
    • 43
    • 44

    4.4.2 查询购物车

    分析
    根据当前登录用户ID查询购物车列表,并对查询的结果进行创建时间的倒序排序。
    代码实现

    /**
    * 查看购物车
    * @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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4.4.3 清空购物车

    分析
    在方法中获取当前登录用户,根据登录用户ID,删除购物车数据。
    代码实现

    /**
    * 清空购物车
    * @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("清空购物车成功");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    五 前台用户下单实现

    5.1 需求分析

    移动端用户将菜品或者套餐加入购物车后,可以点击购物车中的 “去结算” 按钮,页面跳转到订单确认页面,点击 “去支付” 按钮则完成下单操作。

    5.2 数据模型

    用户下单业务对应的数据表为orders表和order_detail表(一对多关系,一个订单关联多个订单明细):
    请添加图片描述
    注意:用户提交订单时,需要往订单表orders中插入一条记录,并且需要往order_detail中插入一条或多条记录。

    5.3 交互过程

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

    5.4 代码开发

    下单功能的具体实现逻辑
    A. 获得当前用户id, 查询当前用户的购物车数据
    B. 根据当前登录用户id, 查询用户数据
    C. 根据地址ID, 查询地址数据
    D. 组装订单明细数据, 批量保存订单明细
    E. 组装订单数据, 批量保存订单数据
    F. 删除当前用户的购物车列表数据
    代码实现

    @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);
        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);
    }
    
    • 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
    • 80
    • 81
    • 82
    • 83
  • 相关阅读:
    【面试题】Java基础
    易基因|TSD物种全基因组DNA甲基化模式对孵育性别和过去孵育温度的响应 | 性别决定
    【mcuclub】继电器
    HTTP VS HTTPS
    适时而变,联创未来|2022数字技能职业教育生态研讨会圆满落幕
    提升Mac运行速度的十大小技巧,你用过几个?
    大厂面试必备 - async/await 详解
    pandas使用列表(list)数据创建dataframe、pandas使用嵌套列表(nested list)数据创建dataframe
    【算法笔记】Kruskal/Prim算法——求解最小生成树问题
    阐述 Git 命令 reset 和 revert
  • 原文地址:https://blog.csdn.net/ljbnb/article/details/126015721