项目笔记-瑞吉外卖(全)

这篇具有很好参考价值的文章主要介绍了项目笔记-瑞吉外卖(全)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.业务开发

1.对后端返回请求值的分析

2.对不同种请求参数的分析

3.事务管理

day01

1.软件开发整体介绍

项目笔记-瑞吉外卖(全)

2.项目整体介绍⭐️

  • 后端:管理菜品和员工信息
  • 前台:通过手机端,可以浏览菜品和添加客户端

开发项目流程:

  1. 实现基本需求,用户能在手机浏览器访问
  2. 对移动端应用改进,使用微信小程序实现
  3. 对系统进行优化升级,提高系统的使用性能

技术选型:

项目笔记-瑞吉外卖(全)

功能架构:

项目笔记-瑞吉外卖(全)

角色:

项目笔记-瑞吉外卖(全)

3.开发环境搭建

  • 涉及数据库 + maven
  1. 数据库表介绍:

    项目笔记-瑞吉外卖(全)

  2. Maven项目搭建

    • 第一步,先创建一个maven空项目,然后设置好pom.xml文件和application.yml文件
    • 第二步,配置springboot环境,启动测试
    • 第三部,导入前端静态资源,加入配置类来将浏览器路径和本地项目文件路径做匹配
    @Slf4j
    @Configuration
    public class WebMvcConfig extends WebMvcConfigurationSupport {
        /**
         * 设置静态资源映射
         * @param registry
         */
        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            log.info("开始进行静态资源映射...");
            registry.addResourceHandler("/backend/**").//浏览器地址栏
                    //映射到真实的路径(映射的真实路径末尾必须添加斜杠`/`)
                    addResourceLocations("classPath:/backend/");//这里不要加空格符,贴着放
            registry.addResourceHandler("/front/**")
                    .addResourceLocations("classpath:/front/");
        }
    }
    

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

4.登录功能⭐️

查看项目代码的一般逻辑:

​ 前端html界面,找到响应的元素,找到对应的js动态方法,分析发送(Ajax)请求到后端的过程,处理好后端代码,返回处理的R对象给前端来判断使用(判断运用是否正确),最后前端再决定跳转到哪一个界面

  1. 需求分析

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    前端代码

    项目笔记-瑞吉外卖(全)

  2. 功能结构

    项目笔记-瑞吉外卖(全)

4.1代码实现
  1. 导入通用返回结果类R类

    ​ 前端代码与R类关系

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    R类

    项目笔记-瑞吉外卖(全)

  2. 梳理登录方法逻辑

    项目笔记-瑞吉外卖(全)

  3. 代码实现

    项目笔记-瑞吉外卖(全)

5.退出功能

  1. 功能逻辑

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

6.页面效果出现

  • index.html

    menuList属性值封装了不同页面的信息

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)


day02

完成功能:

项目笔记-瑞吉外卖(全)

1.完善登录功能

  1. 问题分析

    使用过滤器或者拦截器实现

    项目笔记-瑞吉外卖(全)

  2. 代码实现步骤

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

  3. 具体实现

    • 1.拦截器用原生的Servlet拦截,因此主加载类要加上@ServletComponentScan注解拦截

      2.加上日志注解,能够使用日志输出

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    • 2.具体逻辑

    前端处理部分

    项目笔记-瑞吉外卖(全)

    前端处理响应拦截器:如果是这个状态那么自动跳转回登录页面

    项目笔记-瑞吉外卖(全)

    后端部分:

    package com.itiheima.reggie.filter;
    
    import com.alibaba.fastjson.JSON;
    import com.itiheima.reggie.entity.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;
    
    /**
     * @author qin start
     * @create 2023-04-24-11:28
     */
    @WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")//拦截所有路径
    @Slf4j
    public class LoginCheckFilter implements Filter {
    
        //spring路径匹配器
        public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            //转成http格式的Servlet
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
    
    
    //        1、获取本次请求的URI
            //定义不需要处理的请求路径
            String url = request.getRequestURI();
            String[] urls = new String[]{
                    "/employee/login",
                    "/employee/logout",
                    "/backend/**",//静态资源放行
                    "/front/**"
            };
    //        2、判断本次请求是否需要处理
            boolean check = check(url, urls);
    //        3、如果不需要处理,则直接放行
            if(check){
                log.info("拦截到的请求:{}",url);
                filterChain.doFilter(request,response);
                return;
            }
    //        4、判断登录状态,如果已登录,则直接放行
            //通过判断session存储的数据
            if(request.getSession().getAttribute("id") != null){
                log.info("登陆成功!用户id为:{}",request.getSession().getAttribute("id"));
                filterChain.doFilter(request,response);
                return;
            }
    //        5、如果未登录则返回未登录结果
    //
            //这里要用输出流,因为不是控制器自动返回json格式对象
            log.info("登陆失败!跳转回登录界面");
            response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));//不放行
            return;
        }
    
        /**
         * 判断请求路径是否在不需要处理的路径里
         * @param url
         * @param urls
         * @return
         */
        public boolean check(String url,String[] urls){
            for (String pattern : urls) {
                //这里顺序不能搞反,第一个参数为匹配模式
                if(PATH_MATCHER.match(pattern,url)){
                    return true;
                }
            }
            return false;
        }
    }
    
    

2.新增员工功能

  1. 功能
  • 数据模型中,employee字段要唯一

项目笔记-瑞吉外卖(全)

项目笔记-瑞吉外卖(全)

  1. 执行流程

    项目笔记-瑞吉外卖(全)

  2. 代码实现

    @PostMapping
    public R<String> addEmployee(HttpServletRequest request,@RequestBody Employee employee){
    
        //        log.info(employee.toString());
    
        //设置初始密码
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes(StandardCharsets.UTF_8)));
    
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
    
        employee.setCreateUser((long)request.getSession().getAttribute("id"));
        employee.setUpdateUser((long)request.getSession().getAttribute("id"));
    
        employeeService.save(employee);
    
        return R.success("新增员工成功");
    }
    
    1. 处理数据库插入重复名字异常

      项目笔记-瑞吉外卖(全)

      全局异常处理器来处理异常

      关键点在@ControllerAdvice和@ExceptionHandler,一个用来拦截方法,一个用来处理异常

      @ControllerAdvice捕获方法后,有异常就处理

      @ControllerAdvice(annotations = {RestController.class, Controller.class})
      @ResponseBody//java对象转为json格式的数据
      @Slf4j
      public class GlobalExceptionHandler {
      
          //用来捕获插入重复数据异常
          @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
          public R<String> exceptionHandler (SQLIntegrityConstraintViolationException exception){
              log.error(exception.getMessage());
              return R.error("failed");
          }
      }
      
      //用来捕获插入重复数据异常
      @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
      public R<String> exceptionHandler (SQLIntegrityConstraintViolationException exception){
          if (exception.getMessage().contains("Duplicate entry")){
              String[] split = exception.getMessage().split(" ");//根据空格符分割数组
              String msg = split[2] + "已存在";
              return R.error(msg);
          }
          return R.error("unknown error");
      }
      
    2. 小结:

      项目笔记-瑞吉外卖(全)

3.启用禁用员工信息⭐️(自定义消息转换器使用)

  1. 需求分析

项目笔记-瑞吉外卖(全)

  1. 启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作在Controller中创建update方法,此方法是一个通用的修改员工信息的方法

    @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("员工修改信息成功");
    }
    
    
  2. 出现问题:

    项目笔记-瑞吉外卖(全)

    原因:js对后端传过来的数据long类型精度丢失,因为Java对象默认通过SpringMVC消息转换器传递过来的数据默认是一般的json格式,"id"字段会被当做整型数据处理,而js中Long型精度和后端不匹配。

    **解决:**对SpringMVC配置自定义的消息处理器,将"id"对应的json格式数据转为字符串值

  3. 具体解决步骤:

    ①提供对象转换器Jackson0bjectMapper,基于Jackson进行Java对象到json数据的转换
    ②在WebMcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换

    //JacksonObjectMapper
    package com.itzq.reggie.common;
    
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
    import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
    import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
    import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
    import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
    import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
    import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
    import java.math.BigInteger;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.time.LocalTime;
    import java.time.format.DateTimeFormatter;
    import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
    
    /**
     * 对象映射器:基于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框架的消息转换器
         * @param converters
         */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    
        //创建消息转换器对象
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //设置对象转化器,底层使用jackson将java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //将上面的消息转换器对象追加到mvc框架的转换器集合当中(index设置为0,表示设置在第一个位置,避免被其它转换器接收,从而达不到想要的功能)
        converters.add(0,messageConverter);
    
    }
    
    

4.编辑员工信息

  1. 功能分析项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

  2. 后端功能代码实现

    回显数据

    @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("没有查询到该员工信息");
    }
    
    

    修改功能

    前面在禁用员工的时候使用到了修改数据,所以这里不用配置

day03

1.公共字段自动填充

  • 问题分析:

    设置修改时间和修改人等字段在每张表中基本上都有,而且属于多条记录共有的具有相似功能的字段,因此可以每次修改或者插入的时候自动处理

  • 用到技术:①Mybatis Plus公共字段自动填充ThreadLocal线程内部属性

  • 技术详解:

    ThreadLocal线程内部属性:客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程,因此一次请求中ThreadLocal对象也应该相同

    因此,可以用ThreadLocal用于保存登录用户id,解决实现公共字段填充类无法在方法上自动装配HttpServletRequest的困境,做到会话间数据共享

  1. 实现步骤

    项目笔记-瑞吉外卖(全)

  2. 具体代码实现

    第一版

    package com.itzq.reggie.common;
    
    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;
    
    import java.time.LocalDateTime;
    
    @Component
    @Slf4j
    public class MyMetaObjectHandler implements MetaObjectHandler {
    
        @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", new Long(1));
            metaObject.setValue("updateUser", new Long(1));
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("公共字段自动填充【update】");
            log.info(metaObject.toString());
        }
    }
    

    第二版

    @Component
    @Slf4j
    public class MyMetaObjectHandler implements MetaObjectHandler {
    
        /**
         * 插入时对公共字段赋值
         * @param metaObject
         */
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("公共字段自动填充【insert】");
            //1.直接给公共字段设置值
            metaObject.setValue("createTime", LocalDateTime.now());
            metaObject.setValue("updateTime", LocalDateTime.now());
            metaObject.setValue("createUser", BaseContext.get());
            metaObject.setValue("updateUser", BaseContext.get());
        }
    
        /**
         * 更新时对公共字段赋值
         * @param metaObject
         */
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("公共字段自动填充【update】");
    
            metaObject.setValue("updateTime", LocalDateTime.now());
            metaObject.setValue("updateUser", new Long(1));
    
    
        }
    }
    
    /**
     * 工具类,用来获取当前登录用户的id
     * 这里设置为静态的可以,因为每个线程的ThreadLocal值不同,这样声明成静态的时候不同线程会赋予不同的ThreadLocal值
     * @author qin start
     * @create 2023-04-26-17:34
     */
    public class BaseContext {
    
        private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    
        public static void set(Long id){
            threadLocal.set(id);
        }
    
        public static Long get(){
            return threadLocal.get();
        }
    }
    

2.新增分类

  1. 功能分析

    项目笔记-瑞吉外卖(全)

  2. 代码实现
    项目笔记-瑞吉外卖(全)

    @RestController
    @RequestMapping("/category")
    @Slf4j
    public class CategoryController {
    
        @Autowired
        private CategoryService categoryService;
        @PostMapping
        public R<String> save(@RequestBody Category category){
            log.info("新增菜品:{}",category);
            categoryService.save(category);
    
            return R.success("1");
        }
    }
    

3.分类的分页查询

  1. 项目笔记-瑞吉外卖(全)

  2. 代码实现

    @GetMapping("/page")
    public R<Page> page(int page,int pageSize){
    
        //1.定义分页构造器
        Page<Category> pageInfo = new Page<>(page,pageSize);
    
        //2.定义条件构造器
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Category::getSort);
    
        //3.进行查询
        categoryService.page(pageInfo,queryWrapper);
    
        return R.success(pageInfo);
    }
    

4.分类删除⭐️

  1. 项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

  2. 简单代码开发(第一版)

    @DeleteMapping
    public R<String> delete(long ids){//这里一定要为long类型
        log.info("删除菜品id:{}",ids);
    
        categoryService.removeById(ids);
    
        return R.success("1");
    }
    
  3. 第二版代码开发思路+实现⭐️

    因为菜品``和套餐都有关联到分类的可能性,因此如果删除分类时,分类里有相应的菜品和套餐,那么要判断不能删除

    ①页面发送Ajax请求,传过来要删除菜品分类的id

    ②根据id去菜品表套餐表去查询有几条数据,如果有数据的话抛出一个自定义业务异常,提示不能删除

    ③没有业务异常的话,进行正常的删除操作

    1.增加一个业务方法

    @Service
    public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
        @Autowired
        private DishService dishService;
        @Autowired
        private SetMealService setMealService;
        @Override
        public void remove(long id) {
    
    
            //1.根据id去查询菜品表
            LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Dish::getCategoryId,id);
    
            int count = dishService.count(queryWrapper);
            //抛出自定义业务处理异常
            if(count > 0){
                throw new CustomException("无法删除分类,该分类下存在菜品信息");
            }
    
    
            //2.根据id去查询套餐表
            LambdaQueryWrapper<Setmeal> queryWrapper1 = new LambdaQueryWrapper<>();
            queryWrapper1.eq(Setmeal::getCategoryId,id);
    
            int count1 = setMealService.count(queryWrapper1);
            //抛出自定义业务处理异常
            if(count1 > 0){
                throw new CustomException("无法删除分类,该分类下存在套餐信息");
            }
            //3.如果没有关联,那么调用父类ServiceImpl的方法删除
            super.removeById(id);
        }
    }
    

    2.自定义异常类和全局异常类方法

    public class CustomException extends RuntimeException{
        public CustomException(String message){
            super(message);
        }
    }
    
    //用来处理删除菜品分类信息
        @ExceptionHandler(CustomException.class)
        public R<String> exceptionHandler (CustomException ex){
    
            return R.error(ex.getMessage());
        }
    
    

3.修改分类

  1. 需求分析

  2. 代码实现

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

day04

菜品管理相关内容

项目笔记-瑞吉外卖(全)

1.文件上传⭐️

如果部署上去之后,无法打开页面,先clean一下,再打开项目

  1. 需求分析

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    • 文件过滤器会先拦截下来请求,直接返回“未登录”的信息

    项目笔记-瑞吉外卖(全)

  2. 具体实现

    • MutipartFile文件名必须为file,与前端标签名保持一致

    • 文件上传后转存问题:如果文件上传后不转存到指定位置,那么默认存储在一个本地的临时文件中,程序运行后就会删除,因此要文件转存

    • 代码:

      注意点:①读取路径用@Value注解从配置文件中读取

      ​ ②文件名的拼接

      ​ ③文件路径的判别合法问题

      ​ ④返回文件名称

      @RestController
      @RequestMapping("/common")
      @Slf4j
      public class CommonController {
      
      
          //@Value注解用来取值
          @Value("${reggie.path}")
          private String basePath;
      
          /**
           * 上传文件
           * @param file  文件参数名必须为file
           * @return
           */
          @RequestMapping("/upload")
          public R<String> upload(@RequestBody MultipartFile file){
      
              log.info("上传图片:{}",file.toString());
      
              //1.生成文件名字,实现文件的保存功能,目前先保存到本地上
              //获取文件类型
              String originalFilename = file.getOriginalFilename();
              String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
              //使用UUID生成自定义文件名
              String fileName = UUID.randomUUID().toString() + suffix;
      
              //创建存放的目录对象
              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);
      
          }
      
      }
      

2.文件下载

  1. 前端分析

    项目笔记-瑞吉外卖(全)

  2. 代码实现

    • 注意name字段由前端传过来的参数/common/download?name=${response.data}自动赋值
    • 注意这里读取是从服务端读,发送是HttpServletResponse获取的输出去,传送回去需要先设置响应头数据格式
    @GetMapping("/download")
    //这的name由前端自动赋值
    public void downLoad(String name, HttpServletResponse response){
        //获取文件名称,从本地数据来创建一个input流进行读取,output流进行输出
        File file = new File(basePath + name);
    
        try {
            FileInputStream is = new FileInputStream(file);
    
            OutputStream outputStream = response.getOutputStream();
            response.setContentType("image/jpeg");
            //文件读写操作
            int len = 0;
            byte[] bytes = new byte[1024];
    
            while((len = is.read(bytes)) != -1){
                outputStream.write(bytes);
                //所储存的数据全部清空
                outputStream.flush();
            }
            is.close();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

3.新增菜品⭐️

  1. 需求分析

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

  2. 功能实现

    • 回显菜品分类信息

      因为一进去发送地址在category下,因此要在category下编写相应的请求

      项目笔记-瑞吉外卖(全)

      /**
           * 根据条件查询分类信息,并返回json数据
           * @param category
           * @return
           */
      @GetMapping("/list")
      public R<List<Category>> list(Category category){
      
          LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
      
          queryWrapper.eq(category.getType() != null,Category::getType,category.getType());
      
          queryWrapper.orderByDesc(Category::getSort).orderByDesc(Category::getUpdateTime);
      
          List<Category> list = categoryService.list(queryWrapper);
      
          return R.success(list);
      }
      
    • 处理前端发过来的请求

      DTO继承实体类,扩展实体类

    • 将DTO中的数据保存到两张表中

      @Service
      @Transactional
      public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
      
          @Autowired
          DishFlavorService dishFlavorService;
      
          /**
           * 根据菜品信息,保存数据到菜品表和菜品口味表
           * @param dishDto
           */
          @Override
          public void saveWithFlavor(DishDto dishDto) {
              //保存菜品的基本信息到菜品的基本表中,如果有的菜品的属性对不上,那么不保存
              super.save(dishDto);
      
              //这里保存之后,会将表中的数据重新填回到dishDto,因此也就获取了此时的菜品id
      
              //获取新增的菜品id,为口味表中的每一个口味增加相应的菜品id
              Long id = dishDto.getId();
              //获取菜品口味
              List<DishFlavor> flavors = dishDto.getFlavors();
      
              for (DishFlavor flavor : flavors) {
                  flavor.setDishId(id);
              }
      
              //批量保存菜品口味数据到菜品口味表
              dishFlavorService.saveBatch(flavors);
              
          }
      }
      
  3. 总结分析

    • 处理前端请求
    • 后端返回值识前端需求的数据为准,后端定义了数据模型之后只要根据前端需求,将相应的需求放在R.data属性中即可

4.菜品信息分页查询

  1. 需求分析

    • 处理分页查询中的难点在于:如何将dish表中的categoryId字段切换为categoryName,因为前端页面需要菜品分类而不是菜品ID。所以我们就要用到dishDto这个类扩展dish类,① 先从dish表中查数据然后封装到page类中 ② 将page类中的分页信息数据数据拷贝到Page这个分页信息类 ③ 将page中的records信息映射到page的分页信息类

    封装Dish数据

  2. 代码实现

    • BeanUtils工具类属于org.springframework.beans.BeanUtils,是spring框架提供的工具类,简化数据封装,用于封装JavaBean,避免了大量的get,set方法进行拷贝赋值
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
    
        //1.根据名字查询分页信息
        //首先保证名字不为空
        Page<Dish> dishPageInfo = new Page<>(page,pageSize);
        Page<DishDto> dtoPageInfo = new Page<>();
    
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(name != null,Dish::getName,name);
        dishService.page(dishPageInfo,queryWrapper);
    
        //2.对dish分页信息进行拷贝
        BeanUtils.copyProperties(dishPageInfo,dtoPageInfo,"records");
    
        List<Dish> records = dishPageInfo.getRecords();
    
        //3.返回页面的新records数据,就是data数据
        List<DishDto> list = records.stream().map((item) -> {
    
            //用来将每一个item变为dishDto,然后返回给Page<dishDto>
            DishDto dishDto = new DishDto();
            BeanUtils.copyProperties(item,dishDto);
    
            Long categoryId = item.getCategoryId();
    
            Category category = categoryService.getById(categoryId);
            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
    
            return dishDto;
    
        }).collect(Collectors.toList());
    
        dtoPageInfo.setRecords(list);
    
        return R.success(dtoPageInfo);
    }
    

5.菜品修改信息⭐️

  • 凡是涉及对数据库数据的多次增删改(>=2次),都需要事务控制,来防止一次修改出错而接着照常执行的错误
  1. 需求分析

    • 保存修改数据的时候发送的请求
  2. 代码实现

    • DishServiceImpl —> 回显功能

      进行简单的查询两个表dish和Flavor,中间桥梁是dishID;查询数据封装到DishDto,返回给页面

      dish中有dishID,而Flavor表中有dishID,可根据dishId查询口味表

      /**
           * 根据id查询查询dishDto来赋值
           * 回显信息
           * @param id
           * @return
           */
      @Override
      public DishDto getByIdWithFlavors(Long id) {
      
          //查询dish表信息,获取对应菜品
          Dish dish = this.getById(id);
          DishDto dishDto = new DishDto();
          //拷贝到拓展对象中
          BeanUtils.copyProperties(dish,dishDto);
      
          Long dishId = dish.getId();
          LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
          queryWrapper.eq(dishId != null,DishFlavor::getDishId,dishId);
          List<DishFlavor> list = dishFlavorService.list(queryWrapper);
      
          dishDto.setFlavors(list);
      
          //返回主角数据
          return dishDto;
      }
      
    • 保存修改数据:

      操作:清理当前菜品口味信息,然后批量插入口味信息 —> 避免了还需要判断是清楚还是增加的麻烦

      ①更新dish表②更新口味表(先清除之间口味信息,再批量插入当前口味信息)

      DishServiceImpl

      /**
           * 修改菜品信息
           * @param dishDto
           */
      @Override
      @Transactional//保证事务一致性
      public void updateWithFlavor(DishDto dishDto) {
          //1.更新dish表中的信息
          this.updateById(dishDto);
      
      
          //清理当前菜品,然后批量插入口味信息
          //2.根据id清除相关口味信息
          Long id = dishDto.getId();
          LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
          queryWrapper.eq(DishFlavor::getDishId,id);
      
          dishFlavorService.remove(queryWrapper);
      
          //3.批量插入口味信息,重新设置flavor信息,因为传递过来的flavor的dishId属性没赋上值
          List<DishFlavor> flavors = dishDto.getFlavors();
      
          flavors = flavors.stream().map((item) -> {
              item.setDishId(dishDto.getId());
              return item;
          }).collect(Collectors.toList());
      
          dishFlavorService.saveBatch(flavors);
      }
      

day05

1.概述

项目笔记-瑞吉外卖(全)

数据模型

注意套餐关系表中存储的数据,一个套餐对应多个菜品,分开存储

项目笔记-瑞吉外卖(全)

项目笔记-瑞吉外卖(全)

项目笔记-瑞吉外卖(全)

项目笔记-瑞吉外卖(全)

2.新增套餐

  1. 分析⭐️

    和上一个开发类似

    1. 前端提交过来的信息包含套餐基本信息套餐与菜品关联信息

      因此需要设置一个setmealDto,Dto中包含套餐基本信息和套餐与菜品关联信息

    2. 后端在setmealController中接收这个Dto,然后新增业务方法去处理Dto

    3. 业务方法:

      ①将dto基本信息传入到套餐基本信息表

      ②将套餐id这个对象中的list集合中的数据添加到套餐菜品表

      ③涉及操作两张表,需要加入@transactional注解,要么同时成功,要么同时失败

    项目笔记-瑞吉外卖(全)

  2. 功能实现一:回显添加菜品

    根据分类categoryId,来去相应dish表中查询信息,进而回显信息

    /**
         * 根据categoryId,回显对应分类下的菜品信息
         * @param dish
         * @return
         */
    @GetMapping("/list")
    public R<List<Dish>> list(Dish dish){
    
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        //两个eq信息
        queryWrapper.eq(dish != null,Dish::getCategoryId,dish.getCategoryId());
        queryWrapper.eq(Dish::getStatus,1);
    
        //添加排序条件
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
    
        List<Dish> list = dishService.list(queryWrapper);
    
        return R.success(list);
    }
    

    功能实现二:

    实现添加菜品功能

    • 这里要注意setMealId在前端传过来的数据没有,需要将前端基本信息添加到SetMeal表中,才能得到相应的Id,然后为套餐菜品对象赋值上值

    SetMealServiceImpl

    @Service
    public class SetMealServiceImpl extends ServiceImpl<SetMealMapper, Setmeal> implements SetMealService {
    
        @Autowired
        private SetMealDishService setMealDishService;
        /**
         * 新增菜品套餐
         * @param setmealDto
         */
        @Override
        public void saveWithDish(SetmealDto setmealDto) {
            //1.调用setMeal本有的功能,顺便得到套餐id
            this.save(setmealDto);
    
            //2.将从数据库得到的套餐id封装回setmealDishes对象中
            List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
    
            setmealDishes.stream().map((item) -> {
                item.setSetmealId(setmealDto.getId());
                return item;
            }).collect(Collectors.toList());
    
    
            //3.
            setMealDishService.saveBatch(setmealDishes);
        }
    }
    

3.套餐分页查询

  • 功能与day04的菜品信息分类查询相似
  • 在套餐管理界面,套餐分类字段显示的是categoryId对应的中文,但在数据库里查询到的是categoryId,因此需要利用categoryId查询到categoryName,并赋值给数据传输对象SetmealDto
/**
     * 套餐分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
@GetMapping("/page")
public R<Page> list(int page, int pageSize, String name){
    //分页构造器对象
    Page<Setmeal> pageInfo = new Page<>(page, pageSize);
    Page<SetmealDto> dtoPage = new Page<>();

    //构造查询条件对象
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(name != null, Setmeal::getName, name);

    //操作数据库
    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);
        //获取categoryId
        Long categoryId = item.getCategoryId();
        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);
}

4.删除套餐信息

  1. 需求分析

    项目笔记-瑞吉外卖(全)

    提供一个方法处理删除一个和删除多个请求

    项目笔记-瑞吉外卖(全)

  2. 代码开发

    注意点:

    ①接受前端ids数据,传过来的数据本身是数组形式,所以加不加注解无所谓,但是List是列表,所以要加注解@RequestParam

    ②根据id删除套餐,不仅删除套餐,也删除关联套餐表中的信息

    业务逻辑:(SetMealServiceImpl

    ​ 1.查询套餐状态,确定是否可用删除
    ​ 2.如果不能删除,抛出一个业务异常,提示在售卖中
    ​ 3.如果可以删除,先删除套餐表中的数据
    ​ 4.删除关系表中的数据

/**
     * 根据ids删除套餐信息
     * @param ids
     */
@Override
@Transactional
public void removeWithDish(List<Long> ids) {

    //        1.查询套餐状态,确定是否可用删除
    //SQL语句:select count(*) from setMeal where id in ids and status = 1;
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.in(Setmeal::getId,ids);
    queryWrapper.eq(Setmeal::getStatus,1);
    int count = this.count(queryWrapper);
    //        2.如果不能删除,抛出一个业务异常,提示**在售卖中**
    if(count > 0){
        throw new CustomException("商品还在销售,不能删除");
    }
    //        3.如果可以删除,先删除套餐表中的数据
    this.removeByIds(ids);
    //        4.删除关系表中的数据
    //根据套餐id去关系表中去查数据,然后匹配删除
    //delete from setMealDish where setmealId in ids
    LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
    setMealDishService.remove(lambdaQueryWrapper);

}
  1. 起售,停售操作(SetMealServiceImpl)

    • 这里采用遍历操作实现批量停售,起售不太好,应该用mp的具体更新方法操作,等学了mp之后再来补吧,希望还记得
/**
     * 更改售卖状态
     * @param ids
     * @param status 1表示启售,0表示停售
     */
@Override
public void changeStatus(List<Long> ids, int status) {

    //改变售卖状态
    for (int i = 0; i < ids.size(); i++) {
        Long id = ids.get(i);
        //根据id得到每个dish菜品。
        Setmeal setmeal = this.getById(id);
        setmeal.setStatus(status);
        this.updateById(setmeal);
    }

}

5.短信发送

  1. 概述

    项目笔记-瑞吉外卖(全)

    项目笔记-瑞吉外卖(全)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zEC2oEPD-1685267442440)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305082027150.png)]

  2. 代码开发

6.手机验证码登录

  1. 需求分析

  2. 代码开发(前期准备)

    ①对短信服务进行放行,否则会自动跳回到登录页面

    ②改写全局过滤器,对移动端用户进行验证码访问,进行放行

  3. 代码开发(发送验证码)

    项目笔记-瑞吉外卖(全)

    避坑:

    这个测试的时候,前端页面有问题,login.html不发送ajax请求,解决办法:把day05代码中的所有前端代码替换到自己的项目中就行了

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

    //2.生成验证码,采用短信发送服务进行发送短信
    if(phone != null){
        String code = String.valueOf(ValidateCodeUtils.generateValidateCode(4));
        log.info("验证码为:{}",code);

        //            SMSUtils.sendMessage("瑞吉外卖","SMS_460725810",phone,code);

        //3.将发送的验证码保存在session中,便于之后查验
        session.setAttribute(phone,code);

        return R.success("发送验证码成功,等待查收");

    }

    //4.短信发送错误,返回错误信息;成功,返回成功信息
    return R.error("发送验证码失败");
}
  1. 代码开发(验证码登录)

    1. 因前端传过来的对象,后端没有相应的实体类与其对应

      这时可以采取拓展实体类dto或者是map集合的方式接收

    2. 登录方法返回值为User对象,这样让浏览器也保存一份用户信息

/**
     * 发送手机验证码
     * @param map
     */
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){

    log.info("登录操作:{}",map);
    //1.获取手机号和验证码
    String phone = map.get("phone").toString();
    String code = map.get("code").toString();
    //2.进行手机号和验证码比对,如果成功进行登录的逻辑
    Object codeInSession = session.getAttribute("phone");
    if(codeInSession != null && codeInSession.equals(code)){

        //3.匹配结果对上之后,如果手机号在表中不存在自动完成祖册
        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("id",user.getId());

        return R.success(user);
    }

    //4.短信发送错误,返回错误信息;成功,返回成功信息
    return R.error("登陆失败");
}

day06

这一天都是移动端开发

1.地址簿相关功能

  1. 需求分析
/**
 * 地址簿管理
 */
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {

    @Autowired
    private AddressBookService addressBookService;

    /**
     * 新增
     */
    @PostMapping
    public R<AddressBook> save(@RequestBody AddressBook addressBook) {
        addressBook.setUserId(BaseContext.get());
        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.get());
        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.get());
        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.get());
        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));
    }
}

2.菜品展示

  1. 需求分析

    image-20230509151017659

    ①因为发送两次请求,第二次失败,所以展示信息有误,这里将第二次的改用假数据

    ②存在问题:页面发送的请求为http://localhost:8080/dish/list?categoryId=1397844263642378242&status=1,这个list请求在Controller中只设置了返回List类型,而Dish实体类中没有相应的口味信息,因此在前端页面上不会显示口味信息,所以要拓展dish实体类,返回dishDto

    1. 修改list方法,返回dishDto的代码
    
    /**
         * 根据categoryId,回显对应分类以及菜品口味信息
         * @param dish
         * @return
         */
    @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish){
    
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        //两个eq信息
        queryWrapper.eq(dish != null,Dish::getCategoryId,dish.getCategoryId());
        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();
    
            //1.对象拷贝(每一个list数据)
            BeanUtils.copyProperties(item,dishDto);
            Long categoryId = item.getCategoryId();  //分类id
            //通过categoryId查询到category内容
            Category category = categoryService.getById(categoryId);
            //判空
            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
    
            //2.将菜品口味信息赋值给dto
    
            Long id = item.getId();
            LambdaQueryWrapper<DishFlavor> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
            dishFlavorLambdaQueryWrapper.eq(DishFlavor::getDishId,id);
    
            List<DishFlavor> dishFlavors = dishFlavorService.list(dishFlavorLambdaQueryWrapper);
    
            dishDto.setFlavors(dishFlavors);
            return dishDto;
        }).collect(Collectors.toList());
    
        return R.success(dishDtoList);
    }
    
    1. 设置setMeal方法,用于展示套餐
    /**
         * 用于移动端展示数据
         * @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);
    }
    

3.加入购物车

  1. 数据分析

    项目笔记-瑞吉外卖(全)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btcgqkdD-1685267442442)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305141908129.png)]

  2. 代码开发

    项目笔记-瑞吉外卖(全)

    设置userId,确定哪个用户点的,然后判断number数据类型

    项目笔记-瑞吉外卖(全)

    @PostMapping("/add")
    public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){
    
        log.info("购物车数据:{}",shoppingCart);
        //1.将当前用户的id设置进数据库中
        Long currentId = BaseContext.get();
        shoppingCart.setUserId(currentId);
    
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,currentId);
    
        //2.判断当前传过来的是菜品信息还是套餐信息。用于后续判断当前菜品或者套餐是否在购物车中
        if(shoppingCart.getDishId() != null){
            queryWrapper.eq(ShoppingCart::getDishId,shoppingCart.getDishId());
        }else{
            queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());
        }
    
        //select * from shoppingCart where userId = ? and ...
    
        ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);
        //3.进行查询记录,如果能查到记录,那么说明数据库中有,需要在原有基础上+1
        if(cartServiceOne != null){
            Integer number = cartServiceOne.getNumber();
            number += 1;
            cartServiceOne.setNumber(number);
    
            shoppingCartService.updateById(cartServiceOne);
        }else{
            shoppingCart.setNumber(1);
            shoppingCartService.save(shoppingCart);
            cartServiceOne.setNumber(1);
        }
    
        return R.success(cartServiceOne);
    }
    
    1. 展示购物车信息 /list

      根据userId,去查购物车返回list集合就可

      按照登录id去数据库中查询信息
      不同用户的userid不同,所以购物车信息不同

4.用户下单⭐️

用到了很多张表,具有可学习性

  1. 订单表插入数据 — 从购物表中算出总金额 查询用户信息
  2. 订单明细表中插入多条数据 — 从购物表得出
  1. 需求分析

    项目笔记-瑞吉外卖(全)

    订单明细表

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OMTzZb4b-1685267442445)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305142010371.png)]

  2. 代码开发

    项目笔记-瑞吉外卖(全)

    用户下单分析一:(下单之后,存入订单表订单明细表中)

    ​ 传递参数:

    ​ 不需要传递过来购物车信息用户id,因为在登陆过程中已经知道用户id且购物车信息可根据用户id查询出来

    用户下单分析二:

    ​ 获得当前用户id

    ​ 查询当前用户的购物车数据
    ​ 向订单表插入数据,一条数据
    ​ 向订单明细表插入数据,多条数据
    ​ 清空购物车数据

2.项目优化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j3E0jDZj-1685267442447)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305262033884.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wz3p48bs-1685267442447)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305262034094.png)]

day01

1.引入版本控制

  1. 先创建一个远程的空仓库,复制链接
  2. 本地先创建仓库,然后连接远程仓库推送
  3. 创建两个分支,masterv1.0v1.0用于开发缓存内容,开发完成后,合并到master分支

2.环境搭建

  1. springboot-redis-starter

  2. redis文件配置

  3. 设置redis配置类,便于观察

    项目笔记-瑞吉外卖(全)

3.短信验证码(Redis)

  1. 实现思路

    项目笔记-瑞吉外卖(全)

4.缓存菜品数据

  1. 实现思路

    主从一致

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TAWU5g3p-1685267442449)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271513707.png)]

  2. 实现代码

    • 查询菜品
    1. 先根据前端传递过来的categoryId和status构造一个key,从缓存数据库中获取,如果能获取到,那么直接返回
    2. 获取不到dishDto,进行1去数据库查询,然后放入缓存数据库,然后返回
    //查询方法
    @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish){
    
    
        List<DishDto> dishDtoList = null;
    
        //1.第一次访问构造key,存入缓存数据库中
        String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();
    
        //2.从缓存数据库中拿数据
        dishDtoList = (List<DishDto>)redisTemplate.opsForValue().get(key);
    
        //3.判断缓存数据库中有没有,有的话直接去拿数据
        if(dishDtoList != null){
            return R.success(dishDtoList);
        }
        //4.如果没有的话,去数据库查询
        //.....
    }
    
    //修改方法
    @PostMapping
    public R<String> add(@RequestBody DishDto dishDto){
    
        log.info(dishDto.toString());
    
        dishService.saveWithFlavor(dishDto);
    
        //添加后后删除数据库中对应的key  删除dish开头+dishid
        String key = "dish_" + dishDto.getCategoryId() + "_1";
        redisTemplate.delete(key);
    
        return R.success("新增菜品成功");
    }
    

5.SpringCache

  1. 概述

    常用注解

    • 使用spring cache

      使用哪种缓存技术,就导入对应的包,然后开启注解即可

    • 使用jar包

      使用基础功能的缓存,导入web包即可

      使用基于redis等缓存技术,那么要导入spring-boot-starter-cache

    项目笔记-瑞吉外卖(全)

  2. 普通cache使用

    • 普通的缓存在加注解之后放在一个线程安全的map中,基于内存

    • 初始化缓存方式

      ①注入cachemanager,加上注解缓存操作(获取方法内参数,通过SPEL),缓存的对象的类要实现序列化接口

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FrJVSACv-1685267442450)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271636494.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-icyXONac-1685267442450)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271638756.png)]

    删除缓存

    项目笔记-瑞吉外卖(全)

    查询数据

    项目笔记-瑞吉外卖(全)

  3. redis使用

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gUFBCIaN-1685267442452)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271707236.png)]

6.使用springCache缓存套餐数据

项目笔记-瑞吉外卖(全)

缓存的数据:分类 + 不同种类

项目笔记-瑞吉外卖(全)

day02

1.MySQL主从复制

  1. 介绍

    实现读写分离,减轻单台数据库压力和防止主数据库数据损毁

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lZYOYNoS-1685267442454)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271739718.png)]

  2. 原理—从库和主库通过日志做一样的操作

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kgG6J8Kg-1685267442454)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271745822.png)]

  3. 操作步骤

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jLGSubZH-1685267442454)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271859784.png)]

    第二步:重启MySQL

    项目笔记-瑞吉外卖(全)

    #mysql8第三步执行
    create user xiaoming identified by 'Root@123456';
    grant replication slave on *.* to xiaoming;
    

    MySQL主从复制略过了。。。之后有时间再来补吧

2.Nginx

1.概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gebXbH18-1685267442455)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271928408.png)]

项目笔记-瑞吉外卖(全)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0j5WVZVf-1685267442456)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271939160.png)]

2.基本命令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0W8kKqq-1685267442457)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271942072.png)]

./nginx -v查看版本

./nginx -t检查配置文件是否正确

3.配置文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KB1hPHBa-1685267442458)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281346785.png)]

4.niginx具体应用⭐️
1.部署静态资源
  1. 配置信息在config文件里
  2. 服务可开多个

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mk1fM9XZ-1685267442458)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281352592.png)]

2.反向代理

正向代理是在客户端设置,反向代理是在服务端设置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGfUbUtV-1685267442459)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281401642.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KvR5Nqlq-1685267442459)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281404563.png)]

实现反向代理 ----转发

3.负载均衡

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ZuNYVVu-1685267442460)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281413916.png)]

实现

负载均衡算法

  • 默认轮询算法

day03

1.前后端分离

1.概述

项目笔记-瑞吉外卖(全)

项目笔记-瑞吉外卖(全)

2.前后端分离开发

变化:前后端代码不再混合在一个工程中

  1. 开发流程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MgWOZ1cB-1685267442461)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281432025.png)]

3.YApi

提供API接口数据

4.Swagger(生成接口文档)
  1. 介绍

    项目笔记-瑞吉外卖(全)

  2. 具体实现

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehhnnKDk-1685267442463)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281443013.png)]

    项目笔记-瑞吉外卖(全)

  3. 功能

    因为包扫描,所以能真正地controller

  4. 常用注解

    目的是为了生成接口的时候,个性化定制

    项目笔记-瑞吉外卖(全)

2.项目部署

部署架构

  • 前端项目
  1. 部署静态资源nginx上

    将前端静态资源文件放在nginx的html文件夹下,然后修改nginx配置信息,映射静态文件

  2. 配置反向代理

    项目笔记-瑞吉外卖(全)

  • 后端项目

    1. 项目笔记-瑞吉外卖(全)

    2. 打包项目 部署工程

      (如果访问后端项目,访问超时,可能是因为数据库的问题,可能远端没有配置数据库)

  • 总结

    之后再启动项目,先启动前端项目,再启动后端项目
    .aliyuncs.com/img/202305281417603.png" alt=“image-20230528141758552” style=“zoom:33%;” />

day03

1.前后端分离

1.概述

[外链图片转存中…(img-RglKcxwc-1685267442460)]

[外链图片转存中…(img-gqfy4Vyd-1685267442461)]

2.前后端分离开发

变化:前后端代码不再混合在一个工程中

  1. 开发流程

    [外链图片转存中…(img-MgWOZ1cB-1685267442461)]

3.YApi

提供API接口数据

4.Swagger(生成接口文档)
  1. 介绍

    [外链图片转存中…(img-OcSGU8NN-1685267442462)]

  2. 具体实现

    [外链图片转存中…(img-ehhnnKDk-1685267442463)]

    [外链图片转存中…(img-qddnjUoD-1685267442464)]

  3. 功能

    因为包扫描,所以能真正地controller

  4. 常用注解

    目的是为了生成接口的时候,个性化定制

    [外链图片转存中…(img-OkPdI6g0-1685267442464)]

2.项目部署

部署架构

  • 前端项目
  1. 部署静态资源nginx上

    将前端静态资源文件放在nginx的html文件夹下,然后修改nginx配置信息,映射静态文件

  2. 配置反向代理

    [外链图片转存中…(img-mFWI408T-1685267442465)]

  • 后端项目

    1. [外链图片转存中…(img-sitC0vrN-1685267442465)]

    2. 打包项目 部署工程

      (如果访问后端项目,访问超时,可能是因为数据库的问题,可能远端没有配置数据库)

  • 总结

    之后再启动项目,先启动前端项目,再启动后端项目文章来源地址https://www.toymoban.com/news/detail-466971.html

到了这里,关于项目笔记-瑞吉外卖(全)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 做完瑞吉外卖项目的一点笔记和源码

    源码在 https://gitee.com/pluto8/take-out 1、软件开发流程 需求分析 :产品原型,需求规格说明书(文档形式) 设计:产品文档、UI界面设计、概要设计、详细设计、数据库设计 编码:项目代码,单元测试 测试:测试用例,测试报告 上线运维:软件环境安装,配置 2、角色分工:

    2024年02月08日
    浏览(36)
  • 【SpringBoot项目实战+思维导图】瑞吉外卖①(项目介绍、开发环境搭建、后台登陆/退出功能开发)

    全文主体框架来源于黑马瑞吉外卖的项目资料,我在文中会嵌入如下五个方面的个人内容: 项目中易发生错误的地方 项目中涉及的一些难理解知识点 一些遗忘知识点的回顾 业务的多种实现方法 我在做项目时的思考和一些踩坑 作为一名软件开发工程师,我们需要了解在软件开

    2024年02月05日
    浏览(51)
  • 瑞吉外卖项目——瑞吉外卖

    需求分析:产品原型、需求规格说明书 设计:产品文档、UI界面设计、概要设计、详细设计、数据库设计 编码:项目代码、单元测试 测试:测试用例、测试报告 上线运维:软件环境安装、配置 项目经理:对整个项目负责,任务分配、把控进度 产品经理:进行需求调研,输

    2023年04月26日
    浏览(90)
  • 瑞吉外卖笔记

    直接创建新工程 继承父工程的形式来做这个,这里新建父工程 pom文件 创建测试类并启动 导入 在默认页面和前台页面的情况下,直接把这俩拖到resource目录下直接访问是访问不到的,因为被mvc框架拦截了 所以我们要编写一个映射类放行这些资源 创建配置映射类 访问成功 用

    2024年01月19日
    浏览(34)
  • 瑞吉外卖项目记录

    本文为个人学习黑马《瑞吉外卖》项目后进行的项目总结,更偏向于对自己编写文本能力的锻炼以及对项目知识点的简短记录。因为个人能力问题,其中可行性分析和测试部分只进行了小标题的陈列,并没有进行编辑。对《瑞吉外卖》项目感兴趣的朋友也可以浏览本文后再去

    2024年02月05日
    浏览(32)
  • 瑞吉外卖项目----(2)缓存优化

    将项目推送到远程仓库里,教程在git 提交远程仓库前建议取消代码检查 创建新的分支v1.0(用于实现缓存优化)并推送到远程仓库 1.1.1 maven坐标 导入spring-data-redis的maven坐标: 1.1.2 配置文件 在application.yml中加入redis相关配置: 1.1.3 配置类 在项目中加入RedisConfig 1.2.1 实现思路

    2024年02月14日
    浏览(43)
  • 瑞吉外卖项目——前后端分离

    前后端分离开发,就是在项目开发过程中,对于前端代码的开发由专门的 前端开发人员 负责,后端代码则由 后端开发人员 负责,这样可以做到分工明确、各司其职,提高开发效率,前后端代码并行开发,可以加快项目开发进度。 目前,前后端分离开发方式已经被越来越多

    2023年04月20日
    浏览(46)
  • 【java】【项目实战】[外卖五]菜品管理业务开发

    目录 一、文件上传与下载 1.1 文件上传介绍 1.2 文件下载介绍 1.3 文件上传代码实现 1.3.1 新增upload.html 1.3.2 修改application.yml  1.3.3 CommonController 1.3.4  功能测试 1.4 文件下载代码实现  1.4.1  CommonController 1.4.2  功能测试 二、新增菜品 2.1 需求分析 2.2 数据模型 2.3 代码实现 2.3.

    2024年02月11日
    浏览(42)
  • 【SpringBoot项目】SpringBoot项目-瑞吉外卖【day01】

    🌕博客x主页:己不由心王道长🌕! 🌎文章说明:SpringBoot项目-瑞吉外卖【day01】🌎 ✅系列专栏:SpringBoot项目 🌴本篇内容:对黑马的瑞吉外卖项目的day01进行笔记和项目实现🌴 ☕️每日一语:人有退路,就有些许安全感。等到哪一天,你真没了退路,你就发现眼前哪条路都

    2023年04月08日
    浏览(47)
  • 【SpringBoot项目】SpringBoot项目-瑞吉外卖【day03】分类管理

    🌕博客x主页:己不由心王道长🌕! 🌎文章说明:SpringBoot项目-瑞吉外卖【day03】分类管理🌎 ✅系列专栏:SpringBoot项目 🌴本篇内容:对黑马的瑞吉外卖项目的day03进行笔记和项目实现🌴 ☕️每日一语:生活不可能像你想象得那么好,但也不会像你想象得那么糟。☕️ 🚩

    2024年02月22日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包