IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 瑞吉外卖项目-1:项目搭建 -> 正文阅读

[Java知识库]瑞吉外卖项目-1:项目搭建

需求分析

后台系统

菜品管理(批量删除、起售停售)

套餐管理(修改、起售停售)

订单明细

员工管理

前台系统

个人中心(退出登录、最新订单查询、历史订单、地址管理-修改地址、地址管理-删除地址)

购物车(删除购物车中的商品)

技术架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dSWFcRuN-1654599626144)(E:\CodeStudy\学习笔记\笔记图库\博客项目图库\image-20220425122837673.png)]

构建数据库

数据库表说明

image-20220425210159641

后台开发

公共返回结果类

定义一个通用返回结果,服务端响应的数据最终都会分装成此对象,通过此结果类与前端进行交互。使用泛型,可以兼容不同的实体类。

@Data
public class R<T> {

    private Integer code; //编码:1成功,0和其它数字为失败

    private String msg; //错误信息

    private T data; //数据

    private Map map = new HashMap(); //动态数据

    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }

    public static <T> R<T> error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}

登录退出功能

登录功能的逻辑判断

image-20220426120032022
package com.yawn.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yawn.reggie.common.R;
import com.yawn.reggie.entity.Employee;
import com.yawn.reggie.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
        //1.将页面提交的密码进行MD5加密处理
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());

        //2.根据页面提交的用户名查找数据库
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);

        //3.如果没有查询到则返回登录失败结果
        if(emp == null){
            return R.error("登陆失败");
        }

        //4.判断密码是否一致
        if(!password.equals(emp.getPassword())){
            return R.error("密码错误");
        }
        //判断员工状态是否正常
        if(emp.getStatus()==0){
            return R.error("账号已禁用");
        }
        //验证成功将员工id存入session
        request.getSession().setAttribute("employee",emp.getId());
        return R.success(emp);
    }
}

登录功能完善

使用过滤器或拦截器,判断用户是否已经登录。步骤如下:

1.创建自定义的 LoginCheckFilter

2.在启动类上加入 @ServletComponentScan注解

3.完善过滤器处理逻辑,逻辑如下:

image-20220426213103532
package com.yawn.reggie.filter;

import com.alibaba.fastjson.JSON;
import com.yawn.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        //1.获取本次请求的URI
        String requestURI = request.getRequestURI();

        log.info("拦截到请求:{}",requestURI);

        //定义不需要处理的请求
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };

        //2.判断本次请求是否需要处理
        boolean check = check(urls, requestURI);
        if(check){
            log.info("本次请求{}不需要处理",requestURI);
            filterChain.doFilter(request,response);
            return;
        }

        //3.判断是否登录,如果已经登录则放行
        if(request.getSession().getAttribute("employee")!=null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
            filterChain.doFilter(request,response);
            return;
        }

        log.info("用户未登录");
        //4.如果未登录则拦截,不是直接返回登录页面,而是通过向客户端页面响应数据,再由前端代码控制跳转到登录页面
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;
    }

    //匹配路径
    public boolean check(String[] urls, String requestURI){
        for (String url : urls) {
            boolean match = PATH_MATCHER.match(url, requestURI);
            if(match) return true;
        }
        return false;
    }
}

后台退出功能

1.清理 Session 中的用户 id

2.返回结果

@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
    //清理 Session 中的用户 id
    request.getSession().removeAttribute("employee");
    //返回结果
    return R.success("退出成功");
}

员工管理

新增员工

image-20220426221742329
@PostMapping
public R<String> save(HttpServletRequest request, @RequestBody Employee employee){
    log.info("新增员工,员工信息:{}",employee.toString());
    //设置初始密码为123456,并加密。注意参数类型的转换
    employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
    //设置录入时间
    employee.setCreateTime(LocalDateTime.now());
    employee.setUpdateTime(LocalDateTime.now());
    //设置创建人
    Long empId = (Long) request.getSession().getAttribute("employee");
    employee.setCreateUser(empId);
    employee.setUpdateUser(empId);
    //手动设置好信息后,存起来
    employeeService.save(employee);
    return R.success("添加成功");
}

全局异常处理器

编写一个异常处理器,处理整个项目的异常情况,每个模块发生异常都可以进行捕捉。这样就不用重复得在每个模块中使用 try…catch 捕获异常,减轻代码量。基本原理是将异常处理功能通过 AOP 进行封装后,切入各个业务模块中。

//这个注解是关键
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

    //添加要处理的异常信息
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.error(ex.getMessage());

        //这里的字符串处理很有用,学习一下
        if(ex.getMessage().contains("Duplicate entry")){
            String[] split = ex.getMessage().split(" ");
            String msg = split[2] + "已存在";
            return R.error(msg);
        }
        return R.error("未知错误");
    }
}

总结

image-20220430223040426

这是典型的请求响应式流程。

员工信息分页查询

image-20220430223631870

小心得:使用 Vue 这种框架开发,真的要做好交互一致,很多在前端代码中定义好的参数、变量名等,后端代码也和其保持一致,不可以自己随便定义,所以对于后端开发者来说,基本的前端阅读能力也是很重要的,最好能系统学习一下前端。

使用 MyBatisPlus 分页插件

@Configuration
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

编写 Controller

//分页查询
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
    log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
    //构造分页构造器
    Page pageInfo = new Page(page,pageSize);
    //构造条件构造器
    LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
    //添加过滤条件
    queryWrapper.like(!StringUtils.isEmpty(name),Employee::getName,name);
    //添加排序条件
    queryWrapper.orderByDesc(Employee::getUpdateTime);
    //执行查询
    employeeService.page(pageInfo,queryWrapper);
    return R.success(pageInfo);
}

启用/禁用员工账号

image-20220501120149613

image-20220501120250833[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mgqMFAAO-1654599626145)(E:\CodeStudy\学习笔记\笔记图库\博客项目图库\image-20220501120322702.png)]

image-20220501120250833[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HFOZT91v-1654599626145)(E:\CodeStudy\学习笔记\笔记图库\博客项目图库\image-20220501120322702.png)]

注意点:js 处理 long 型数据会出现精度丢失的情况(js 16位,SQL 19位),导致提交的 id 与数据库 id 不一致。解决办法是在服务端给页面响应 json 数据时,将 long 型数据统一转换为 String 字符串。

image-20220501144944842
/**
 * 扩展mvc框架的消息转换器
 * @param converters
 */
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    log.info("扩展消息转换器...");
    //创建消息转换器对象
    MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
    //设置对象转换器,底层使用Jackson将Java对象转为json
    messageConverter.setObjectMapper(new JacksonObjectMapper());
    //将上面的消息转换器对象追加到mvc框架的转换器集合中
    converters.add(0,messageConverter); //下标为0表示把自己编写的消息转换器放在最前面
}
/**
 * 根据id修改员工信息
 * @param request
 * @param employee
 * @return
 */
@PutMapping
public R<String> update(HttpServletRequest request, @RequestBody Employee employee){
    log.info(employee.toString());
    Long empId = (Long)request.getSession().getAttribute("employee");
    employee.setUpdateTime(LocalDateTime.now());
    employee.setUpdateUser(empId);
    //这里是因为点击“修改”或“禁用”的时候,弹出确认框询问是否确定,如果确定,则前端代码提交的是一个已经更改的用户数据,我们拿到这个更改后的数据就可以直接更新
    employeeService.updateById(employee);
    return R.success("员工信息修改成功");
}

编辑员工信息

image-20220501151530417

简单来说就是,修改和新增是同一个页面,点击“编辑”按钮,这时会先判断是否存在 id ,有 id 是进入编辑页面,无 id 是保存页面。然后前端页面会发送一个带有用户 id 的请求,并且要求服务端进行信息查询,服务端接收请求后根据用户 id 查询数据后,以 json 格式返回用户数据,前端页面拿到后进行数据回显,然后用户进行修改,点击“保存”按钮,由于进入的是编辑页面,所以保存按钮请求的是服务端的更新操作。

/**
 * 根据id查询员工信息
 * @param id
 * @return
 */
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
    log.info("根据id查询员工信息...");
    Employee employee = employeeService.getById(id);
    if(employee != null){
        return R.success(employee);
    }
    return R.error("没有查询到对应员工信息");
}

分类管理

公共字段自动填充

image-20220501161210402 image-20220501161944277

获取当前用户 id 的解决方案

由于在 MyMetaObjecthandler 类中是不能获得 HttpSession 对象的,因此就无法通过 session 获得用户 id,可以用 ThreadLocal 解决这个问题,它是 JDK 中提供的一个类。

image-20220501165543739 image-20220501170224445 image-20220501170604112

1.编写工具类

/**
 * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
 * 因为是工具类,所以将方法和变量设置为静态方法
 */
public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    /**
     * 设置值
     * @param id
     */
    public static void setCurrentId(Long id){
        threadLocal.set(id);
    }
    /**
     * 获取值
     * @return
     */
    public static Long getCurrentId(){
        return threadLocal.get();
    }
}

2.在 LoginCheckFilter 过滤器中获取 id

Long empId = (Long) request.getSession().getAttribute("employee"); //获取用户id
BaseContext.setCurrentId(empId); //将用户id存入ThreadLocal中

3.编写自定义元数据对象处理器

/**
 * 自定义元数据对象处理器
 */
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
    /**
     * 插入操作,自动填充
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("公共字段自动填充[insert]...");
        log.info(metaObject.toString());
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("createUser", BaseContext.getCurrentId());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }

    /**
     * 更新操作,自动填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共字段自动填充[update]...");
        log.info(metaObject.toString());

        long id = Thread.currentThread().getId();
        log.info("线程id为:{}",id);

        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }
}

新增分类

首先搭建框架,步骤与员工管理相同,省略…

然后编写控制器:

@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

    @PostMapping
    public R<String> save(@RequestBody Category category){
        log.info("category:{}",category);
        categoryService.save(category);
        return R.success("新增分类成功");
    }
}

分类信息分页查询

逻辑与员工信息分页查询一样,只是查询的表发生了变化。

/**
 * 分页查询
 * @param page
 * @param pageSize
 * @return
 */
@GetMapping("/page")
public R<Page> page(int page, int pageSize){
    //分页构造器
    Page<Category> pageInfo = new Page<>(page,pageSize);
    //条件构造器,根据菜品分类sort属性排序
    LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
    //添加排序条件,根据sort进行排序
    queryWrapper.orderByAsc(Category::getSort);
    //分页查询
    categoryService.page(pageInfo,queryWrapper);
    return R.success(pageInfo);
}

删除分类

删除分类时,需要先判断该类别中是否有菜品,如果有不能删除。通过在业务代码中实现自己编写的类完成这个功能。

1.自定义删除菜品分类方法,先在菜品业务接口中定义方法,再在实现类中实现重写方法。

public interface CategoryService extends IService<Category> {
    public void remove(Long id);
}
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

    @Autowired
    private DishService dishService;
    @Autowired
    private SetmealService setmealService;

    /**
     * 自定义删除菜品分类方法
     * @param id
     */
    @Override
    public void remove(Long id) {
        //构建条件查询器
        LambdaQueryWrapper<Dish> queryWrapper1 = new LambdaQueryWrapper<>();
        //构建查询条件
        queryWrapper1.eq(Dish::getCategoryId,id);
        //执行查询
        int count1 = dishService.count(queryWrapper1);
        //判断是否存在菜品,存在则抛出异常
        if(count1>0){
            throw new CustomException("当前分类下关联了菜品,不能删除");
        }

        //构建条件查询器
        LambdaQueryWrapper<Setmeal> queryWrapper2 = new LambdaQueryWrapper<>();
        //构建查询条件
        queryWrapper2.eq(Setmeal::getCategoryId,id);
        //执行查询
        int count2 = setmealService.count(queryWrapper2);
        //判断是否存在菜品,存在则抛出异常
        if(count2>0){
            throw new CustomException("当前分类下关联了套餐,不能删除");
        }

        //既不关联菜品,也不关联套餐,这时可以正常删除分类,直接调用父类的方法
        super.removeById(id);
    }
}

2.自定义业务异常类,放在 common 包下。

public class CustomException extends RuntimeException {
    public CustomException(String message){
        super(message);
    }
}

3.在全局异常处理器中添加该异常。

/**
 * 异常处理方法
 * @return
 */
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
    log.error(ex.getMessage());
    return R.error(ex.getMessage());
}

4.编写控制器。

/**
 * 根据id删除分类
 * @param id
 * @return
 */
@DeleteMapping
public R<String> delete(Long id){
    log.info("删除分类,id为:{}",id);
    categoryService.remove(id);
    return R.success("分类信息删除成功");
}

修改分类

/**
 * 根据id修改分类信息
 * @param category
 * @return
 */
@PutMapping
public R<String> update(@RequestBody Category category){
    log.info("修改分类信息:{}",category);
    categoryService.updateById(category);
    return R.success("修改分类信息成功");
}

菜品业务管理

文件上传下载

image-20220502112343247

Spring 框架在 spring-web 包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在 Controller 的方法中声明一个 MultipartFile 类型的参数即可接收上传的文件。

image-20220502112814469

1.在 application.yml 配置文件中配置默认路径

reggie:
  path: E:\CodeStudy\Others\外卖项目\1 瑞吉外卖项目\代码\day04\

2.编写控制层代码

@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {

    @Value("${reggie.path}")
    private String basePath;

    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public R<String> upload(MultipartFile file){
        //file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
        log.info(file.toString());

        //原始文件名
        String originalFilename = file.getOriginalFilename();//abc.jpg
        String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); //后缀字符串

        //使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
        String fileName = UUID.randomUUID().toString() + suffix;//dfsdfdfd.jpg

        //创建一个目录对象
        File dir = new File(basePath);
        //判断当前目录是否存在
        if(!dir.exists()){
            //目录不存在,需要创建
            dir.mkdirs();
        }
        try {
            //将临时文件转存到指定位置
            file.transferTo(new File(basePath + fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.success(fileName);
    }
    
    /**
     * 文件下载,这里的下载指的是从后端输出图片到前端页面,也就是图片数据的回显
     * @param name
     * @param response
     */
    @GetMapping("/download")
    public void download(String name, HttpServletResponse response){
        try {
            //输入流,通过输入流读取文件内容
            FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
            //输出流,通过输出流将文件写回浏览器
            ServletOutputStream outputStream = response.getOutputStream();

            response.setContentType("image/jpeg");

            int len = 0;
            byte[] bytes = new byte[1024];
            while ((len = fileInputStream.read(bytes)) != -1){
                outputStream.write(bytes,0,len);
                outputStream.flush();
            }
            
            //关闭资源
            outputStream.close();
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

新增菜品

1.添加菜品时需要查询菜品分类信息,因为需要编写查询菜品分类方法,在 CategoryController 中添加方法。

@GetMapping("/list")
public R<List<Category>> list(Category category){
    LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper();
    queryWrapper.eq(category.getType() != null,Category::getType,category.getType());
    queryWrapper.orderByAsc(Category::getSort).orderByAsc(Category::getUpdateTime);
    List<Category> list = categoryService.list(queryWrapper);
    return R.success(list);
}

2.编写菜品DTO

分析:

  • Dish 表中没有专门的口味属性(DishFlavors),如果保存方法传入的参数直接为 Dish,是无法保存的
  • 采用和做个人博客项目时类似的方法,个人博客是在显示博客详情页的时候重新封装了一个类,类里面有博客类属性、分类属性等;而这里是在新的类中放入菜品基本信息和口味信息,用于保存和显示。
image-20220503104204677
@Data
public class DishDto extends Dish {

    private List<DishFlavor> flavors = new ArrayList<>();

    private String categoryName;

    private Integer copies;
}

小心得:

  • 善于使用 Debug 和日志排查问题,Debug 和日志可以看到具体的流程和数据生成过程
  • @RequestBody 注解是为了保证控制器接受到前端页面的请求数据为 Json 格式

3.在菜品业务服务层编写实现方法(先写接口方法,再写实现类)

注意,用到了事务需要在启动类开启事务支持,即在启动类添加 @EnableTransactionManagement 注解

@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {

    @Autowired
    private DishFlavorService dishFlavorService;

    @Override
    @Transactional
    public void saveWithFlavor(DishDto dishDto) {
        //保存菜品的基本信息到菜品表dish
        this.save(dishDto);

        Long dishId = dishDto.getId();//菜品id

        //菜品口味信息保存,因为直接保存时菜品的id无法保存进来
        List<DishFlavor> dishFlavorList = dishDto.getFlavors();
        dishFlavorList.stream().map((item) -> {
            item.setDishId(dishId);
            return item;
        }).collect(Collectors.toList());

        //保存菜品口味数据到菜品口味表dish_flavor
        dishFlavorService.saveBatch(dishFlavorList);
    }
}

4.控制层代码编写(选择哪种请求方式是根据前端代码决定的)

@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
    log.info(dishDto.toString());
    dishService.saveWithFlavor(dishDto);
    return R.success("保存成功");
}

菜品信息分页查询

分析:菜品分类列显示的是分类名称,但是数据其实是代码,无法直接显示,因此需要转换,方法就是使用 dishDto 类。过程稍微有些复杂,但是简单来说就是创建一个 dishDto 类型的 Page;然后将 dish 类型的 Page 除了 records 的属性赋值给 dishDto 类型的 Page,因为两个类型的 Page 中 records 代表的是两个类型的记录,类型不一样,不能直接进行赋值;再对 records 单独处理,将菜品名称取出赋给 dishDto 中的 categoryName 属性;最后返回的是dishDto 类型的 Page。

@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
    //构造分页构造器
    Page<Dish> pageInfo = new Page<>(page,pageSize);
    Page<DishDto> dishDtoPage = new Page<>();

    //构造条件构造器
    LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
    //添加过滤条件
    queryWrapper.like(name != null,Dish::getName,name);
    //添加排序条件
    queryWrapper.orderByAsc(Dish::getUpdateTime);
    //执行分页查询(不是用query)
    dishService.page(pageInfo,queryWrapper);

    //对象拷贝
    BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
    //处理records
    List<Dish> records = pageInfo.getRecords();
    List<DishDto> list = records.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){
            dishDto.setCategoryName(category.getName());
        }
        //返回处理好的dishDto对象
        return dishDto;
    }).collect(Collectors.toList());
    dishDtoPage.setRecords(list);
    return R.success(dishDtoPage);
}

修改菜品

分析:与新增菜品共用一个页面,但需要先进行数据回显,因此这里稍微复杂一些的是设计回显的方法和数据表结构。

1.在 DishService 接口中新增一个 getByIdWithFlavor 方法,然后在实现类中设计具体实现方法。

接口:public DishDto getByIdWithFlavor(Long id);
实现类:
@Override
    public DishDto getByIdWithFlavor(Long id) {
        //查询菜品基本信息,从dish表查询
        Dish dish = this.getById(id);

        DishDto dishDto = new DishDto();
        BeanUtils.copyProperties(dish,dishDto);

        //查询当前菜品对应的口味信息,从dish_flavor表查询
        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(DishFlavor::getDishId,dish.getId());
        List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
        dishDto.setFlavors(flavors);

        return dishDto;
    }

2.在 DishService 接口中新增一个 updateWithFlavor方法,然后在实现类中设计具体实现方法。

接口:public void updateWithFlavor(DishDto dishDto);
方法:
    @Override
    @Transactional
    public void updateWithFlavor(DishDto dishDto) {
        //1.更新dish表基本信息
        this.updateById(dishDto);

        //2.清理当前菜品对应口味数据---dish_flavor表的delete操作
        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
        dishFlavorService.remove(queryWrapper);

        //3.添加当前提交过来的口味数据---dish_flavor表的insert操作
        List<DishFlavor> flavors = dishDto.getFlavors();
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishDto.getId());
            return item;
        }).collect(Collectors.toList());
        dishFlavorService.saveBatch(flavors);
    }

3.控制器

/**
 * 根据id查询菜品信息和对应的口味信息
 * @param id
 * @return
 */
@GetMapping("/{id}")
public R<DishDto> get(@PathVariable Long id){
    DishDto dishDto = dishService.getByIdWithFlavor(id);
    return R.success(dishDto);
}
/**
 * 修改菜品
 * @param dishDto
 * @return
 */
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
    log.info(dishDto.toString());
    dishService.updateWithFlavor(dishDto);
    return R.success("新增菜品成功");
}

套餐业务管理

新增套餐

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOjRU3da-1654599626146)(E:\CodeStudy\学习笔记\笔记图库\博客项目图库\image-20220504132400496.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZyPtNr7G-1654599626146)(E:\CodeStudy\学习笔记\笔记图库\博客项目图库\image-20220504132400496.png)]

1.框架搭建,包括创建实体类,Dto类,Mapper,Service,Controller

2.业务代码和新增分类相似,编写业务代码和控制层代码

image-20220504134953811

①在 DishController 中添加响应查询菜品信息的方法

@GetMapping("/list")
public R<List<Dish>> list(Dish dish){
    log.info("dish:{}", dish);
    //条件构造器
    LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(null != dish.getCategoryId(),Dish::getCategoryId,dish.getCategoryId());
    queryWrapper.eq(Dish::getStatus, 1);
    queryWrapper.orderByDesc(Dish::getUpdateTime);
    List<Dish> list = dishService.list(queryWrapper);
    return R.success(list);
}

②在 SetmealServiceImpl 中添加如下方法

@Service
@Slf4j
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {

    @Autowired
    private SetmealDishService setmealDishService;

    @Override
    @Transactional
    public void saveWithDish(SetmealDto setmealDto) {
        //保存套餐的基本信息,操作setmeal,执行insert操作
        this.save(setmealDto);

        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        setmealDishes.stream().map((item)->{
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());

        //保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作
        setmealDishService.saveBatch(setmealDishes);
    }
}

③在 SetmealController 中添加如下方法

@PostMapping
public R<String> save(@RequestBody SetmealDto setmealDto){
    setmealService.saveWithDish(setmealDto);
    return R.success("新增套餐成功");
}

套餐分页查询

@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
    //构造分页构造器
    Page<Setmeal> pageInfo = new Page<>(page,pageSize);
    Page<SetmealDto> setmealDtoPage = new Page<>();
    //构造查询构造器
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    //添加查询条件,根据name进行like模糊查询
    queryWrapper.like(name != null,Setmeal::getName,name);
    //添加排序条件,根据更新时间降序排列
    queryWrapper.orderByDesc(Setmeal::getUpdateTime);
    setmealService.page(pageInfo,queryWrapper);
    //属性拷贝
    BeanUtils.copyProperties(pageInfo,setmealDtoPage,"records");
    List<Setmeal> records = pageInfo.getRecords();
    List<SetmealDto> list = records.stream().map((item)->{
        SetmealDto setmealDto = new SetmealDto();
        //对象拷贝
        BeanUtils.copyProperties(item,setmealDto);
        //分类id
        Long categoryId = item.getCategoryId();
        //根据分类id查询分类对象
        Category category = categoryService.getById(categoryId);
        if(category != null){
            //分类名称
            String categoryName = category.getName();
            setmealDto.setCategoryName(categoryName);
        }
        return setmealDto;
    }).collect(Collectors.toList());
    setmealDtoPage.setRecords(list);
    return R.success(setmealDtoPage);
}

删除套餐

image-20220504152941059

需要注意套餐与菜品之间存在关联关系,因为删除套餐的同时要需要把响应的菜品信息删除,当然是setmeal_dish表中的菜品

/**
 * 删除套餐,同时需要删除套餐和菜品的关联数据
 * @param ids
 */
@Override
@Transactional
public void removeWithDish(List<Long> ids) {
    //查询套餐状态是否可以删除
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.in(Setmeal::getId,ids);
    queryWrapper.eq(Setmeal::getStatus,1);
    int count = this.count(queryWrapper);

    if(count > 0){
        //如果不能删除,抛出一个业务异常
        throw new CustomException("套餐正在售卖中,不能删除");
    }
    //删除setmeal
    this.removeByIds(ids);
    //删除setmeal_dish
    LambdaQueryWrapper<SetmealDish> queryWrapper2 = new LambdaQueryWrapper<>();
    queryWrapper2.in(SetmealDish::getSetmealId,ids);
    setmealDishService.remove(queryWrapper2);
}
@DeleteMapping
public R<String> delete(@RequestParam List<Long> ids){
    setmealService.removeWithDish(ids);
    return R.success("套餐数据删除成功");
}

修改套餐

和分类修改类似,暂不写。

停起售套餐

分析:传入的是 id 号,需要根据 id 号 更新整条记录。

/**
 * 改变套餐状态
 * @param ids
 */
@Override
@Transactional
public void changeStatus(List<Long> ids) {
    for (Long id:ids) {
        Setmeal setmeal = new Setmeal();
        Setmeal setmeal1 = this.getById(id);
        BeanUtils.copyProperties(setmeal1,setmeal,"status");
        Integer status = setmeal1.getStatus();
        if(status == 0){
            setmeal.setStatus(1);
        }else {
            setmeal.setStatus(0);
        }
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Setmeal::getId,id);
        this.update(setmeal,queryWrapper);
    }
}
@PostMapping("/status/{params.status}")
public R<String> change(@RequestParam List<Long> ids){
    setmealService.changeStatus(ids);
    return R.success("状态修改成功");
}

心得:

  • 一定要结合前端代码编写方法,同时利用页面的响应工具抓请求状态,搞清楚自己这次请求响应传输的到底是什么数据
  • 很多功能写不出来可能是对封装的方法不熟悉,因为要善于运用别人分享的资源,多查

手机验证码登录

短信发送

使用阿里云的短信服务,具体申请步骤不难,但是审核麻烦,这里没有申请,了解如何调用 API 即可。

<!--阿里云短信服务-->
<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>
/**
 * 短信发送工具类
 */
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();
      }
   }
}

验证码登录

image-20220504213920656

1.搭建框架,包括编写用户实体类,Mapper 层、业务层、控制层,代码和前面类似,这里省略

2.完善登录过滤器,过滤未登录的用户,放行短信发送请求和登录请求;同时判断用户是否登录,登录则放行

image-20220504214756301
//3.2判断是否登录,如果已经登录则放行
if(request.getSession().getAttribute("user")!=null){
    log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));
    Long userId = (Long) request.getSession().getAttribute("user"); //获取用户id
    BaseContext.setCurrentId(userId); //将用户id存入ThreadLocal中
    filterChain.doFilter(request,response);
    return;
}

3.客户端页面使用H5开发,调试页面时,将页面设置为移动端模式

image-20220504215038264

4.编写控制器

分析:保存登录信息时,直接传递用户类是不行的,因为用户类中没有验证码信息。解决方法有两种,一个是像之前一样编写一个用户Dto类,另一个是使用 Map 保存,这样更方便一些。

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;

    /**
     * 发送手机短信验证码
     * @param user
     * @return
     */
    @PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user, HttpSession session) {
        //获取手机号
        String phone = user.getPhone();

        if(phone.length()>0){
            //生成4位验证码
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            log.info("code={}",code);
            //调用阿里云提供的短信服务API完成发送短信
            //SMSUtils.sendMessage("reggie","",phone,code);
            //需要将生成的验证码保存到Session
            session.setAttribute(phone,code);
            R.success("手机验证码短信发送成功");
        }
        return R.error("短信发送失败");
    }
    /**
     * 移动端用户登录
     * @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 code1 = session.getAttribute(phone);
        //判断验证码是否匹配
        if(code1 != null && code1.equals(code)){
            //验证成功
            //查询用户是否存在
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone,phone);
            User user = userService.getOne(queryWrapper);
            if(user == null){
                //user为空表示是新用户,需要进行注册
                user = new User();
                user.setPhone(phone);
                user.setStatus(1);
                userService.save(user);
            }
            session.setAttribute("user",user.getId());
            return R.success(user);
        }
        return R.error("登录失败");
    }
}

前端页面: getCode() 方法中获取code 的方法换成 sendMsgApi({phone:this.form.phone}),另外在 api 包下的 login.js 文件下添加 sendMsgApi 方法。

5.bug记录

  • 总是出现 sendMsg 未定义,原因是没有清理浏览器缓存…,前端项目清理缓存有时候很重要!
  • 可以通过注释掉front下的login.html的66行,也就是生成随机验证码的那一行,然后加上两行,
    const res = sendMsgApi({phone:this.form.phone})
    sessionStorage.setItem(“code”,res)
    然后在btnlogin()这个函数中,const res = await loginApi({phone:this.form.phone,code:this.form.code})

导入用户地址簿

image-20220505122220716

1.首先搭建框架,省略

2.控制层代码编写

@RestController
@RequestMapping("/addressBook")
@Slf4j
public class AddressBookController {

    @Autowired
    private AddressBookService addressBookService;

    /**
     * 新增
     */
    @PostMapping
    public R<AddressBook> save(@RequestBody AddressBook addressBook) {
        addressBook.setUserId(BaseContext.getCurrentId());
        addressBookService.save(addressBook);
        log.info("addressBook:{}", addressBook);
        return R.success(addressBook);
    }

    /**
     * 设置默认地址
     */
    @PutMapping("default")
    public R<AddressBook> setDefault(@RequestBody AddressBook addressBook){
        log.info("addressBook:{}", addressBook);
        Long currentId = BaseContext.getCurrentId();
        LambdaUpdateWrapper<AddressBook> queryWrapper = new LambdaUpdateWrapper<>();
        //将当前用户的所有默认地址信息都更新为0
        queryWrapper.eq(AddressBook::getUserId,currentId);
        queryWrapper.set(AddressBook::getIsDefault,0);
        addressBookService.update(queryWrapper);
        //把此条地址设为默认地址
        addressBook.setIsDefault(1);
        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);
        //一个用户很可能同时拥有几个地址,所以应该同时查询当前用户id和默认地址
        //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));
    }

    /**
     * 更新地址
     * @param addressBook
     * @return
     */
    @PutMapping
    private R<AddressBook> update(@RequestBody AddressBook addressBook){
        log.info("addressBook:{}", addressBook);
        Long currentId = BaseContext.getCurrentId();
        LambdaUpdateWrapper<AddressBook> queryWrapper = new LambdaUpdateWrapper<>();
        queryWrapper.eq(AddressBook::getUserId,currentId);
        queryWrapper.eq(AddressBook::getId,addressBook.getId());
        addressBookService.updateById(addressBook);
        return R.success(addressBook);
    }
}

菜品展示

image-20220505215123961

1.修改菜品展示表,在 DishController 中修改 list 方法。

分析:后台菜品展示列表返回类型为 Dish,其中不包含口味信息,这样用户在前台添加菜品进入购物车时就无法添加口味信息,因此需要进行修改。

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

2.展示套餐信息,在 SetmealController 中添加 list 方法,用于信息套餐具体的菜品信息。

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

购物车

image-20220505223842445 image-20220505224352330
@RestController
@RequestMapping("/shoppingCart")
@Slf4j
public class ShoppingCartController {

    @Autowired
    private ShoppingCartService shoppingCartService;

    /**
     * 添加购物车
     * @param shoppingCart
     * @return
     */
    @PostMapping("/add")
    public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){
        log.info("购物车数据:{}",shoppingCart);
        //设置用户id
        shoppingCart.setUserId(BaseContext.getCurrentId());
        //构建查询构造器
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
        //判断添加的是菜品还是套餐
        Long dishId = shoppingCart.getDishId();
        if(dishId != null){
            //添加的是菜品,添加相应查询条件
            queryWrapper.eq(ShoppingCart::getDishId,dishId);
        }else {
            //添加的是套餐
            queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());
        }
        //判断是否是第一次添加
        ShoppingCart shoppingCart1 = shoppingCartService.getOne(queryWrapper);

        if(shoppingCart1 != null){
            //当前菜品或套餐已经存在,只需要把数量加1即可
            Integer number = shoppingCart1.getNumber();
            shoppingCart1.setNumber(number+1);
            shoppingCartService.updateById(shoppingCart1);
        }else {
            //当前菜品或套餐不存在,需要把传入的数据存起来
            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCart1 = shoppingCart;
            shoppingCartService.save(shoppingCart1);
        }
        return R.success(shoppingCart1);
    }

    /**
     * 查看购物车
     * @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);
    }

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

下单

image-20220506193944262

注:具体的支付功能并没有开发,因为需要申请资质,这里只是将订单信息保存即可。

image-20220506194157481 image-20220506194711797

1.业务层实现

分析:这里主要是业务层代码比较复杂,需要考虑下单时的具体的实体类信息的设置,前端传递的信息不完整,因此需要手动设置订单表属性和订单明细表属性,两个表存放的数据属性不一样。而这些属性内容主要来自于用户表,购物车表还有地址表。一些细节的处理也需要学习,比如金额总数相关操作使用原子整型类,生成订单号的工具类,对购物车数据遍历求总金额的同时设置订单详细数据等。

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {

    @Autowired
    private ShoppingCartService shoppingCartService;
    @Autowired
    private UserService userService;
    @Autowired
    private AddressBookService addressBookService;
    @Autowired
    private OrderDetailService orderDetailService;

    @Override
    @Transactional
    public void submit(Orders orders) {
        //获取当前用户id
        Long currentId = BaseContext.getCurrentId();
        //查询当前用户购物车数据
        LambdaQueryWrapper<ShoppingCart> queryWrapper =new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,currentId);
        List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
        if(list == null || list.size() == 0){
            throw new CustomException("购物车为空,不能下单");
        }
        //查询用户数据
        User user = userService.getById(currentId);
        //查询地址数据
        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 = list.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(currentId);
        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(queryWrapper);
    }
}

rderId(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(currentId);
    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(queryWrapper);
}

}


2.控制层,直接调用方法即可,内容略。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-06-14 22:21:09  更:2022-06-14 22:27:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:57:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码