瑞吉外卖java项目实战(句句解析版)

瑞吉外卖项目

Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目

包含自己学习时候的理解 有可能不对欢迎指正,我会查看并且进行正确的修改,也有老师讲的时候的讲解

创建项目

创建项目,数据库 我就不多讲解了 (项目这里导入了backend 和front包 都是前端代码)在这里插入图片描述
注意:这里导入了backend 和 front 包的时候,springboot并不知道这些是静态资源,所以要编写一个配置类,配置mvc静态资源的映射 如果不编写输入时url时不出现页面

在这里插入图片描述

以下是springboot默认的静态资源路径

spring.resources.static-locations=
					classpath:/META-INF/resources/,
					classpath:/resources/,
					classpath:/static/,
					classpath:/public/

编写配置类
告诉springboot 要访问backend 就是 resource下面那个backend 你直接输入
localhost:8080/backend/… 就去resource/backend/ 下面去找
另外:reources包下在建一个resources包装静态资源不能直接放在第一个reources下

这时直接输入网站就可以访问了

在这里插入图片描述

配置pom和appliciation.yml

导入依赖如果有问题可以私信我帮助解决一下 建议配置本地maven 能解决大部分问题

创建controller层 ,service层 ,mapper层

然后实现相应的Empoyee 类或接口 让他们继承对应mybatis plus 提供的类
这个类里有很多我们会经常用的实现方法,所以尽管表面看起来没有神什么代码但其实已经有很多实现方法了,感兴趣的同学们可以ctrl+b 看一下

在这里插入图片描述

创建common类 (包含通用类如通用返回结果类R)

注意R类里的success和error和add方法经常用到
在这里插入图片描述

编写员工登录逻辑

弱弱的说一句不知道为什么他要把方法写在controller类里,应该是service类里吧,我们这里尊重作者

@Slf4j
@RequestMapping("/employee")
// @ResponseBody 注解是将返回的数据结构转换为 Json 格式
@RestController
// @RestController 注解包含了原来的 @Controller 和 @ResponseBody 注解
//使用了 @RestController 注解即可将返回的数据结构转换成 Json 格式,
// Spring Boot 中默认使用的 Json 解析技术框架是 jackson。
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @PostMapping("/login")
    //@PostMapping是@RequestMapping(method = RequestMethod.POST)缩写的组合注解,
    // 用于将 HTTP 的post 请求映射到特定处理程序的方法注解。
    //RequestBody 前端点登录发送的请求会带来账号密码参数 但是是json形式 接受的时候要用这个注解
    //要与实体类里的username 一样
    //request 是因为要存一份在session里 request可以get一个session
    //这个session是服务器端共享,每个游览器(客户端)独享的。我们可以在session存储数据,实现数据共享。
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {
        String pwd = employee.getPassword();
        //md5解密
        pwd = DigestUtils.md5DigestAsHex(pwd.getBytes());
        //mybatis-plus 的方法
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        //和数据库进行判断
        queryWrapper.eq(Employee::getUsername, employee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);
        System.out.println(emp);
        if (emp == null) {
            return R.error("登陆失败");
        }
        if (!emp.getPassword().equals(pwd)) {
            return R.error("密码不对");
        }
        if (emp.getStatus() == 0) {
            return R.error("账号被锁");
        }
        System.out.println("登录成功");
        //这里设定employee的id的key为employee
        System.out.println("----这个是empoyee---" + employee);
        System.out.println("----这个是emp---" + emp);
        request.getSession().setAttribute("employee", emp.getId());
        return R.success(emp);
    }

==注意:这里我直接加上了session 原视频开始是没有的,是后来完善的,另外 request.getSession().setAttribute(“employee”, emp.getId()); 这句话传递的是emp。getId()而不是employee.id(),employee这里id是空的 所以我在发现写错了之前session一直出错… ==

过滤器和拦截器

我们这里用的是过滤器

@Slf4j
@WebFilter(filterName = "LoginCheckfilter", urlPatterns = "/*")
//   /* 所有的请求都拦截
// 过滤器要有这个注解
//添加过滤器和拦截器 防止直接输入网站 从而跳过 登陆界面
public class LoginCheckfilter implements Filter {

    /*
    有些路径后面带html,有些不带html
    用路径匹配器,支持通配符
     */
    static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        log.info("拦截到请求{}", request.getRequestURI());
        /*
       filterchain.dofilter() 方法用于执行过滤器链中的所有过滤器。它返回过滤器链处理后的结果。
        */
        String requestURI = request.getRequestURI();

        //不需要处理的URI路径
        //backen和front 包里有些带.html后缀 有些不带 用路径通配符
        String[] URIS = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };

        boolean check = check(requestURI, URIS);
        //如果不需要处理就直接放行
        if (check) {
            log.info("本次请求{}不需要处理",requestURI);
            filterChain.doFilter(request, response);
            return;
        }


        //如果session 有对象 说明就已经登陆了
        if (request.getSession().getAttribute("employee") != null) {
            //取得属性的名称之后,我们就可以用getAttribute()方法将它的属性值拿出来了。
            System.out.println("----session--- 不为空");
            log.info("用户登录,用户id为{}",request.getSession().getAttribute("employee"));
            filterChain.doFilter(request, response);
            return;
        }

        // 如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
        //通过JSON.toJSONSting方法 将R对象转换成josn数据 然后调用write写回去
        //"NOTLOGIN" 对应 request.js 里的”NOTLOGIN“
        log.info("用户未登录");
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;
    }

    /**
     * 路径匹配看一下是否要放行
     *
     * @param requestURI 传进来的uri
     * @return
     */
    public static boolean check(String requestURI, String[] UIRS) {

        for (String url : UIRS) {
            //通配符
            boolean match = PATH_MATCHER.match(url, requestURI);
            if (match) {
                return true;
            }
        }
        return false;
    }

}

另外我想起了一个面试题 拦截器和过滤器有什么不同?
有兴趣大家可以研究一下

编写员工退出

  /**
     * 员工退出
     *
     * @param request
     * @return
     */
    @PostMapping("/logout")
    //@RequestMapping(method = RequestMethod.POST)的快捷方式
    public R<String> logout(HttpServletRequest request) {
        //清理session 传进去的叫employee 移除的也叫employee
        request.getSession().removeAttribute("employee");
        return R.success("退出成功");
    }

编写员工新增

注意这里密码用MD5加密

   /**
     * 新增员工
     *
     * @param employee
     * @return
     */
    @PostMapping
    public R<String> save(@RequestBody Employee employee, HttpServletRequest request) {
        log.info("新增员工,输出员工信息:{}", employee.toString());
        //设定初始密码时123456 并且加密
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        //获得当前用户id
        Long empId = (long) request.getSession().getAttribute("employee");

        employee.setCreateUser(empId);
        employee.setUpdateUser(empId);
        //mybatis plus 里的方法
        employeeService.save(employee);

        return R.success("新增员工成功");
    }

注意我们这里先不先不设置MetaObjectHandler类 我们在 公共字段填充代码里写,可以看目录。

编写全局异常

aop编程


/**
 * 全局异常处理
 */

/**
 * @ControllerAdvice
 * annotation: 注解
 * 只要类上加了 RestController, Controller 这两个注解 就会被拦截到
 */
@ControllerAdvice(annotations = {RestController.class, Controller.class})
/**
 * 因为要返回json数据所以用ResponsBody
 */
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 异常处理方法
     * @return
     */
    //@ExceptionHandler注解中可以添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常,
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex) {
        log.error(ex.getMessage());

        if (ex.getMessage().contains("Duplicate entry")) {
            //用空格分开
            String[] split = ex.getMessage().split(" ");
            //split【2】得到重复的用户名 加已存在
            String msg = split[2] + "已存在";
            return R.error(msg);
        }

        return R.error("未知错误");
    }
}

员工分页查询

 /**
     *  员工分页查询
     * @param page 页码
     * @param pageSize 一页的大小 10
     * @param name 搜索时输入的值
     */
    @GetMapping("/page")//因为传过来的是get
    //在request.js 里 将json数据解析 然后拼接成url 并转换成key value形式 所以这里的参数不用加 @RequestBody
    //这里 参数要和 前端url 里的key 一样 前段是?page= 后端参数名字就要叫page
    public R<Page> page(int page, int pageSize, String name) {

        log.info("page = {},pageSize = {},name = {}", page, pageSize, name);

        //构造分页构造器
        //Page也是Spring Data库中的一个接口,主要用于存储JPA查询数据库的结果集。
        Page pageInfo = new Page(page, pageSize);

        //构造条件构造器
        //mybatis puls 提供的 和数据库进行比较
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
        //添加过滤条件
        //StringUtils已经提供了一个判断如果name为空的情况 就不需要自己判断了
        //如果空就不执行下面这句话 ,如果不为空再执行这句话
        queryWrapper.like(StringUtils.isNotEmpty(name), Employee::getName, name);
        //添加排序条件 根据更新时间排序
        queryWrapper.orderByDesc(Employee::getUpdateTime);

        //执行查询
        employeeService.page(pageInfo, queryWrapper);

        return R.success(pageInfo);
    }

根据id员工修改

注意这里是因为mybatis-puls 不考虑null 的值 前端只传过来 status还有id
其他的值都是空所以他只更新不为空的值 如果想让他更新null的值要改配置

在MyBatis-Plus配置文件中修改field-strategy字段验证的值为0即可

 /**
     * 根据id修改员工信息
     * @param employee
     * @return
     */
    @PutMapping
    public R<String> update(@RequestBody Employee employee,HttpServletRequest request){
        log.info(employee.toString());
        employee.setUpdateUser((long)request.getSession().getAttribute("employee"));
        employee.setUpdateTime(LocalDateTime.now());
        //代码简单是因为我们用的mybatis puls  updateById 是继承Iservice这个接口
        employeeService.updateById(employee);
        return R.success("员工信息修改成功");
    }

注意这里update会失败因为json解析long型的数据时会改变精度 所以要进行修改
注意 update次数是0
在这里插入图片描述

完善员工修改 增加对象映射器

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }

扩展mvc框架消息转换器

注意这里要控制下标把咱们的转换器放在0位置也就是先用咱们这个转换器而不是先用默认的转换器

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

MappingJackson2HttpMessageConverter是springboot中默认的Json消息转换器。

跟据id查询员工

 /**
     * 根据id查询数字
     */
    @GetMapping("/{id}")
    public R<Employee>getById(@PathVariable long id){

        Employee employee =employeeService.getById(id);
        return null;
    }

注意这里和之前员工分页不同!

这个是员工分页
在这里插入图片描述
这个是key-value 让括号里声明的参数和key值一样就可以

这个是根据id查询员工
在这里插入图片描述

这个是直接获取url上的值所以要用@PathVariable 注解

另外 编辑员工用的也是上一个update 方法所以根据id查完之后就可以直接编辑

公共字段填充代码

在这里插入图片描述
我们现在可以把操作员工那里的重复加入的代码(比如设置更新人是谁等等)删去了
我们这里先不得到 updateUser 因为我们在MyMetaObjectHandler类里是获取不到session的

ThreadLocal 获取session

在这里插入图片描述
注意:每个线程都只能看到自己线程的值 ,每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题

代码如下:
在这里插入图片描述
我们在设置婉工具类后就可以在BaseContxt 类里调用get方法获取session 里的id了
在这里插入图片描述
因为filter 先运行filter 里的 BaseContext.setCurrentId(empId); 然后运行MyMetaObjectHandler进行赋值

全局异常处理

在这里插入图片描述
在这里插入图片描述
先获取带字段里带Duplicate entry 说明这个是异常 然后根据空格分开

新增分类

在这里插入图片描述
这里我们后端只需要写一个方法就可以新增套餐分类,新增菜品分类,两个窗口的逻辑,因为请求的url路径和穿的json 参数都一样的
在这里插入图片描述

显示页面

 @GetMapping("/page")

    public R<Page> page(int page, int pageSize) {
        log.info("page = {},pageSize = {}", page, pageSize);
        //构造分页构造器
        //Page也是Spring Data库中的一个接口,主要用于存储JPA查询数据库的结果集。
        Page pageInfo = new Page(page, pageSize);
        //构造条件构造器
        //mybatis puls 提供的 和数据库进行比较
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper();
        //添加排序条件 根据更新时间排序
        queryWrapper.orderByAsc(Category::getSort);
        //执行查询
        categoryService.page(pageInfo, queryWrapper);
        return R.success(pageInfo);
    }

删除分类(跟菜品和套餐)

在这里插入图片描述
接收ids
在这里插入图片描述
注意这里老师传的参数是ids 我开始写的id 报了id为空的异常。。。 大家要细心一点

/**
     * 根据id删除分类,删除之前要判断
     *
     * @param id
     */
    @Override
    public void remove(Long id) {
        //查询是否关联了菜品,如果已经关联  ,抛出一个异常

        //LambdaQueryWrapper.eq 方法是 MyBatis Plus 中用于构建等于(equal)条件的方法。
        // 它接收两个参数,第一个参数是数据库表中的列名,第二个参数是要匹配的值。
        // 该方法返回一个 LambdaQueryWrapper 对象,可以配合其它条件继续构建复杂的查询语句。
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<Dish>();

        dishLambdaQueryWrapper.eq(Dish::getCategoryId, id);

        int count1 = dishService.count(dishLambdaQueryWrapper);
        //查询是否关联了菜品,如果已经关联  ,抛出一个异常

        if (count1 > 0) {

            throw new CustomException("当下分类关联了菜品,不能删啊0.0");
        }

        LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();

        setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId, id);

        int count2 = dishService.count(dishLambdaQueryWrapper);
        //查询是否关联了套餐,如果已经关联  ,抛出一个异常

        if (count2 > 0) {
            throw new CustomException("当下分类关联了套餐,不能删啊0.0");
        }
        //正常删除分类
        super.removeById(id);

    }

设置全局异常处理
在这里插入图片描述

修改分类信息

在这里插入图片描述
前端来看修改和新增用的是一个窗口

文件上传

在这里插入图片描述

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

    /**
     * 那么系统中如何使用这些配置信息呢,spring中提供了@Value注解来解决这个问题。
     * 通常我们会将配置信息以key=value的形式存储在properties配置文件中。
     */
    @Value("${takeout.path}")
    private String basePath;

    @PostMapping("/upload")
    public R<String> upload(MultipartFile file){
        //file 是一个临时文件,要转存到一个位置不然本次请求完成后就会消失
        log.info(file.toString());

        String originalFilename = file.getOriginalFilename();

        String substring = originalFilename.substring(originalFilename.lastIndexOf("."));

        String fileName = UUID.randomUUID().toString()+substring ;

        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);
    }
    @GetMapping("/download")
    public void download(String name, HttpServletResponse response){
        log.info(name);

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


    }
}

配置spring.yml 配置文件
在这里插入图片描述
用到了输入输出流 javase学的忘记了的学生可以搜一下学一下

新建菜品页面


    @GetMapping("/list")
    //点击新建菜品 传参为type=1 可以用String type 来接收 但是我们这里采用封装成category的方法
    public R<List<Category>> list(Category category){

        log.info("进入新建菜品页面");
        //条件构造器
        LambdaQueryWrapper<Category> categoryLambdaQueryWrapper = new LambdaQueryWrapper<>();
        //添加条件 
        categoryLambdaQueryWrapper.eq(category.getType()!=null,Category::getType,category.getType());
        //添加排序条件  优先用sort来排序 如果sort相同那么采用更新时间进行排序
        categoryLambdaQueryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);

        List<Category> list = categoryService.list(categoryLambdaQueryWrapper);

        return R.success(list);

    }

这个表里有两个id 第一个id是表本身自带的 一个是另一个表的菜品id这这里叫dish_id

在这里插入图片描述

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


    @Autowired
    private DishFlavorservice dishFlavorservice;

    @Transactional//保证数据一致性
    @Override
    public void saveWithFalver(DishDto dishDto) {
        //保存菜品的基本信息到菜品dish
        this.save(dishDto);
        Long id = dishDto.getId();
        //菜品口味
        List<DishFlavor> flavors = dishDto.getFlavors();
        //lambda表达式 Java8 新特性

        //这里的id 是根据appliciation.yml 里的 id-type: ASSIGN_ID 注入的
        flavors=flavors.stream().map((dishFlavor) -> {
            dishFlavor.setDishId(id);
            return dishFlavor;
        }).collect(Collectors.toList());//变为一个链表并且把dish_id 赋值

//        //保存菜品口味数据到菜品口味表dish——flavor
       dishFlavorservice.saveBatch(flavors);
    }
}

lambda 表达式这里大家可以搜索学习一下是新特性当然也可以用foreach

菜品分页查询

 /*
    菜品信息分页查询
     */
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize, String name) {

        //构造分页构造器对象
        Page<Dish> dishPage = new Page<>(page, pageSize);
        Page<DishDto> dishDtoPage = new Page<>();

        //条件构造器
        LambdaQueryWrapper<Dish> QueryWrapper = new LambdaQueryWrapper<>();
        //添加过滤条件 用名字
        LambdaQueryWrapper<Dish> like = QueryWrapper.like(name != null, Dish::getName, name);

        //用更新时间进行排序
        QueryWrapper.orderByDesc(Dish::getUpdateTime);
        //执行分页 查询
        dishService.page(dishPage, QueryWrapper);
        //对象拷贝
        BeanUtils.copyProperties(dishPage, dishDtoPage, "records");
        List<Dish> records = dishPage.getRecords();
        List<DishDto> list = records.stream().map((item) -> {
            DishDto dishDto = new DishDto();
            //遍历出来的属性拷贝到dishDto
            BeanUtils.copyProperties(item, dishDto);
            Long categoryId = item.getCategoryId();//分类

            //根据id查询分类对象
            Category category = categoryService.getById(categoryId);

            if(category!=null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
            return dishDto;
        }).collect(Collectors.toList());
        dishDtoPage.setRecords(list);
        return R.success(dishDtoPage);
    }

修改菜品


    /**
     * 修改菜品
     * @param dishDto
     */
    @Override
    @Transactional
    public void updateWithFalvor(DishDto dishDto) {
        //更新dish表信息
        this.updateById(dishDto);
        //清理当前菜品对应口味数据--dish_falvor表的delete操作
        LambdaQueryWrapper<DishFlavor> QueryWrapper = new LambdaQueryWrapper();
        QueryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
        dishFlavorservice.remove(QueryWrapper);
        //添加当前提交过来的口味数据,dish_flavor表的insert操作
        List<DishFlavor> flavors = dishDto.getFlavors();
        flavors=flavors.stream().map((item)->{
            log.info("item.tostring()={}",item.toString());
            log.info("dishDto.getId().toString()={}",dishDto.getId().toString());
            item.setDishId(dishDto.getId());
            return item;
        }).collect(Collectors.toList());

    }

根据菜品id查询

在这里插入图片描述
发现他传了一个菜品id key-value形式 我们这里可以用Long categoryid来接收

/**
     * 根据条件来查询对应的菜品数据
     * @param dish
     * @return
     */
     @GetMapping("/list")
    public R<List<Dish>> 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);

        return R.success(list);
    }

新增套餐

/**
     * 新增套餐 同时保存套餐与菜品的关联关系
     *
     */
    @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);
    }

套餐分页查询

 /**
     * 套餐分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        //分页构造器对象
        Page<Setmeal> pageInfo = new Page<>(page,pageSize);
        Page<SetmealDto> dtoPage = 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,dtoPage,"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());

        dtoPage.setRecords(list);
        return R.success(dtoPage);
    }

在这里插入图片描述

删除套餐

在这里插入图片描述
删除一个和删除多个是一样的 无非是id传过来的个数不一样

  /**
     * 删除套餐,同时需要删除套餐和菜品的关联数据
     * @param ids
     */
    @Transactional
    @Override
    public void removeWithDish(List<Long> ids) {
        //select count(*) from setmeal where id in (1,2,3) and status = 1
        //查询套餐状态,确定是否可用删除
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.in(Setmeal::getId,ids);
        System.out.println("-------------------------------------------------------------");
        queryWrapper.eq(Setmeal::getStatus,1);

        int count = this.count(queryWrapper);
        if(count > 0){
            //如果不能删除,抛出一个业务异常
            throw new CustomException("套餐正在售卖中,不能删除");
        }

        //如果可以删除,先删除套餐表中的数据---setmeal
        this.removeByIds(ids);

        //delete from setmeal_dish where setmeal_id in (1,2,3)
        LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
        //删除关系表中的数据----setmeal_dish
        setmealDishService.remove(lambdaQueryWrapper);

    }

手机验证码登录

/**
     * 移动端用户登录
     * @param map
     * @param session
     * @return
     */
    @PostMapping("/login")
    public R<User> login(@RequestBody Map map, HttpSession session){
        log.info(map.toString());
        log.info("进入方法--------------------");
        //获取手机号
        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());
            return R.success(user);
        }
        return R.error("登录失败");
    }

不要忘记加过滤器
在这里插入图片描述
在这里插入图片描述
我在做这里的时候遇到了找不到sendMsgApi的问题 我把target文件删掉然后重新启动项目就好了
front文件用的day6

地址

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

菜品展示

  /**
     * 根据条件来查询对应的菜品数据
     * @param dish
     * @return
     */

    @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish){
        //构造查询条件
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());
        //添加条件,查询状态为1(起售状态)的菜品
        queryWrapper.eq(Dish::getStatus,1);

        //添加排序条件
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

        List<Dish> list = dishService.list(queryWrapper);

        List<DishDto> dishDtoList = list.stream().map((item) -> {
            DishDto dishDto = new DishDto();

            BeanUtils.copyProperties(item,dishDto);

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

新增购物车

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

查看购物车

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

用户下单

  @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
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 瑞吉外是一个基于Java开发的项目实战,适用于在线外订餐系统。该项目提供了用户注册、登录、浏览餐厅、查看菜单、下订单等功能。 首先,我们需要在网盘上下载瑞吉外项目源代码文件。通过提供的下载链接,我们可以将项目源代码文件下载到本地。下载完成后,我们可以将文件解压缩,并使用Java开发工具(如Eclipse或IntelliJ IDEA)导入项目。 接下来,我们需要安装项目所需的Java开发环境。确保已经安装了JDK(Java Development Kit)和Maven(项目构建工具)。这样可以保证项目能够正常编译和运行。 在导入项目后,我们可以查看项目的目录结构。主要包括源代码、配置文件和静态资源文件等。在源代码文件夹中,我们可以找到各种Java类文件,包括控制器、实体类、服务类等。配置文件夹中包含项目的配置文件,用于配置数据库连接、日志记录等。静态资源文件夹中包含了项目所需的各种图片、样式表和JavaScript文件等。 在开始开发之前,我们需要先配置数据库。将提供的SQL脚本文件导入到MySQL数据库中,并在项目配置文件中修改数据库连接相关的配置信息。 接下来,我们可以根据需求对项目进行开发和定制化。例如,我们可以根据需要添加更多的功能模块,如优惠券管理、配送员管理等。我们也可以根据需求修改前端页面的样式和布局,以满足用户的需求。 开发完成后,我们可以使用Maven将项目打包成可执行的WAR文件。将WAR文件上传至服务器,并部署在Tomcat等Java Web服务器上。通过访问服务器的IP地址和端口号,我们就可以在浏览器中访问瑞吉外系统了。 总之,下载并实战瑞吉外项目需要下载源代码文件,并在Java开发工具中导入项目。然后,我们可以根据需求进行开发和定制化,并最终将项目打包部署在服务器上。最后,我们可以通过浏览器访问项目,体验瑞吉外系统的功能。 ### 回答2: 瑞吉外是一个基于Java语言开发的项目实战项目的主要目标是实现一个在线外订餐系统。用户可以通过网页或手机应用程序浏览餐厅菜单、下订单、查看订单状态等功能。 该项目的开发环境主要包括Java SE、Java EE、Spring框架和MySQL数据库。其中,Java SE用于实现基本的语言特性和数据处理操作,Java EE用于构建Web应用程序,Spring框架用于实现系统的MVC架构,MySQL数据库用于存储用户信息、菜品信息和订单数据等。 项目的实施步骤如下: 1. 需求分析:首先,根据用户的需求分析,确定项目的基本功能和需求。 2. 系统设计:基于需求分析的结果,进行系统设计,包括数据库设计、界面设计和系统架构设计等。 3. 环境搭建:安装配置Java开发环境,包括JDK、开发工具(如Eclipse或IntelliJ IDEA)、Web服务器(如Tomcat)和数据库管理系统(MySQL)。 4. 数据库建模:创建数据库表结构,定义各个表之间的关系。 5. 编码实现:根据系统设计的结果,进行编码实现,包括前端界面的开发和后端功能的开发。 6. 软件测试:对已实现的功能进行测试,包括单元测试、集成测试和系统测试等,保证系统的稳定性和可靠性。 7. 部署上线:将项目部署到服务器上,使用户可以通过网络访问系统。 8. 运维和优化:监控系统运行情况,对性能进行优化和改进。 最后,用户可以通过网盘下载瑞吉外的源代码和相关文档,以便学习和参考。项目实战瑞吉外的开发过程将帮助开发者熟悉Java开发技术,并理解实际项目的需求分析、系统设计和开发实施等流程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值