黑马瑞吉外卖


主要讲解后端代码思路

1,环境搭建

数据库环境搭建
在这里插入图片描述

导入项目所需的jar包

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.5</version>
            </plugin>
        </plugins>
    </build>

配置application.yml文件

server:
  port: 8080
spring:
  application:
    name: reggie_take_out
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      password: lss
      username: root
  mail:
    host: smtp.qq.com
    username: 854511856@qq.com
    password: uxgfymlnqokvbcie
    default-encoding: UTF-8
    test-connection: true
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true

mybatis-plus:
  configuration:
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
reggie:
  path: D:\photos\

导入前端资源
backend:客户端
front:移动端
在这里插入图片描述

创建配置类WebMVCConfig,设置静态资源映射,可以访问前端页面
在这里插入图片描述

@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport{
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始静态资源映射...");
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }
}

2,优化前功能开发

1,后台登录功能

1)登录页面展示
在这里插入图片描述
2)查看登录请求信息
通过浏览器调试工具,可以发现,点击登录按钮时,页面会发送请求(请求地址为http://localhost:8080/employee/login)并提供参数(username和password)
在这里插入图片描述

前端代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3)后端代码
创建Controller、Service、Mapper
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在controller中创建登录方法
在这里插入图片描述

@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
    @Resource(name = "employeeService")
    private EmployeeService employeeService;

    @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);
        if(emp == null){
            return R.error("登录失败");
        }
        if(!emp.getPassword().equals(password)){
            return R.error("登录失败");
        }
        if(emp.getStatus() == 0){
            return R.error("账号已禁用");
        }
        request.getSession().setAttribute("employee",emp.getId());
        return R.success(emp);
    }
}

2,后台退出功能

需求分析
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
前端代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
后端代码

@PostMapping("/logout")
    public R<String> logout(HttpServletRequest request){
        request.getSession().removeAttribute("employee");
        return R.success("退出成功");
    }

3,完善登录功能

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

@Component
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*/")
@Slf4j
public class LoginCheckFilter implements Filter {
    //路径匹配器,支持通配符
    public 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;
        String requestURI = request.getRequestURI();
        log.info("拦截到请求: {}",requestURI);
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**",
                "/common/**",
                "/user/sendMsg",
                "/user/login"
        };
        boolean check = check(urls, requestURI);
        if(check){
            log.info("本次请求{}不需要处理",requestURI);
            filterChain.doFilter(request,response);
            return;
        }
        if(request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);
            long id = Thread.currentThread().getId();
            log.info("线程id为:{}",id);
            filterChain.doFilter(request,response);
            return;
        }
        log.info("用户未登录");
        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;
    }
}

4,新增员工功能

需求分析
在这里插入图片描述
在这里插入图片描述
数据模型
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
代码开发
在这里插入图片描述
在这里插入图片描述
前端代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
后端代码

@PostMapping
    public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
        log.info("新增员工,员工信息:{}",employee.toString());
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        employeeService.save(employee);
        return R.success("新增员工成功");
    }

在这里插入图片描述
![v!v](https://img-blog.csdnimg.cn/7b7d489dea3542f3af85cb9b08a94b35.png
在这里插入图片描述

@RestControllerAdvice(annotations = {RestController.class, Controller.class})
@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("失败了");
    }
}

5,员工信息分页查询功能

在这里插入图片描述
在这里插入图片描述
代码开发
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
前端代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
后端代码
在这里插入图片描述
在这里插入图片描述

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

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

@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.isNotEmpty(name),Employee::getName,name);
        queryWrapper.orderByDesc(Employee::getUpdateTime);
        employeeService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }

6,启用/禁用员工账号功能

需求分析
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
前端代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
后端代码

@PutMapping
    public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
        log.info(employee.toString());
        employeeService.updateById(employee);
        return R.success("员工信息修改成功");
    }

功能测试
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码修复
在这里插入图片描述
在这里插入图片描述

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);
    }
}
@Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转换器...");
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        converters.add(0,messageConverter);
    }

7,编辑员工信息功能

需求分析
在这里插入图片描述
在这里插入图片描述
代码开发
在这里插入图片描述
前端代码
第一次交互
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
后端代码

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

第二次交互
前端代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8,公共字段自动填充功能

问题分析
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码实现
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info(metaObject.toString());
        log.info("公共字段自动填充insert...");
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("createUser", BaseContext.getCurrentId());
        metaObject.setValue("updateUser", BaseContext.getCurrentId());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info(metaObject.toString());
        log.info("公共字段自动填充update...");
        long id = Thread.currentThread().getId();
        log.info("线程id为:{}",id);
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("updateUser", BaseContext.getCurrentId());
    }
}

在这里插入图片描述
在这里插入图片描述
功能完善
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

9,新增分类功能

需求分析

后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台系统中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PcwPR1ib-1660546100845)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815110958284.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-buRGa9GN-1660546100847)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815111008037.png)]

可以在后台系统的分类管理页面分别添加菜品分类和套餐分类,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zoQX5P6U-1660546100848)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815111037960.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZxFTj6yf-1660546100848)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815111047924.png)]

数据模型

新增分类,其实就是将我们新增窗口录入的分类数据插入到category表,表结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XTB65LM3-1660546100848)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815111156292.png)]

需要注意,category表中对name字段加入了唯一约束,保证分类的名称是唯一的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HHwZleE2-1660546100849)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815111218312.png)]

代码开发

1、页面(backend/page/category/list.html)发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端

2、服务端Controller接收页面提交的数据并调用Service将数据进行保存

3、Service调用Mapper操作数据库,保存数据

可以看到新增菜品分类和新增套餐分类请求的服务端地址和提交的json数据结构相同,所以服务端只需要提供一个方法统一处理即可:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-55pqVhM7-1660546100849)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815112058497.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lgXyHmoo-1660546100849)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815112106509.png)]

前端代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-coojjyKC-1660546100850)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815112029344.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nL9dU3Hf-1660546100850)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815112158945.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2tRrq2sc-1660546100850)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815112226836.png)]

后端代码

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

10,分类信息分页查询功能、

需求分析

系统中的分类很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IWiKpRIZ-1660546159848)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815112509093.png)]
代码开发

在开发代码之前,需要梳理一下整个程序的执行过程:

1、页面发送ajax请求,将分页查询参数(page、pageSize)提交到服务端

2、服务端Controller接收页面提交的数据并调用Service查询数据

3、Service调用Mapper操作数据库,查询分页数据

4、Controller将查询到的分页数据响应给页面

5、页面接收到分页数据并通过ElementUI的Table组件展示到页面上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yFNoFm28-1660546100851)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815112651161.png)]

前端代码

页面中使用的是ElementUI提供的分页组件进行分页条的展示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-si4iuNXz-1660546100851)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815112702650.png)]
页面中创建VUE对象完成后会调用init方法,在init方法中发送ajax请求并提交分页参数(page、pageSize),请求服务端Controller进行分页查询

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qdo9RhZR-1660546100852)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815112714585.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EcY3isNk-1660546100852)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815112728850.png)]

后端代码

@GetMapping("/page")
    public R<Page> page(int page,int pageSize){
        Page<Category> pageInfo = new Page(page,pageSize);
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByAsc(Category::getSort);
        categoryService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }

11,删除分类功能

需求分析

在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QsMWmZtF-1660546100852)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815113229058.png)]

代码开发

在开发代码之前,需要梳理一下整个程序的执行过程:

1、页面发送ajax请求,将参数(id)提交到服务端

2、服务端Controller接收页面提交的数据并调用Service删除数据

3、Service调用Mapper操作数据库

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FYb0zE2p-1660546100852)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815113424023.png)](https://img-blog.csdnimg.cn/d8ec7c9cd5674df9b8a4c89c017e4ac7.png)
后端代码

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

功能完善

前面我们已经实现了根据id删除分类的功能,但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功能完善。

要完善分类删除功能,需要先准备基础的类和接口:

1、实体类Dish和Setmeal

2、Mapper接口DishMapper和SetmealMapper

3、Service接口DishService和SetmealService

4、Service实现类DishServiceImpl和SetmealServiceImpl

具体实现步骤:

1)创建自定义业务异常类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4OVec3pH-1660546100853)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815113609991.png)]

2)在CategoryService中扩展remove方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ij3bnGpv-1660546100853)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815113622019.png)]

3)在CategoryServiceImpl中实现remove方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BrFvrlTj-1660546100853)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815113851003.png)]

4)在GlobalExceptionHandler中处理自定义异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DBqsBP0K-1660546100854)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815113859245.png)]

12,修改分类功能

需求分析

在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SRckdOaK-1660546100854)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815113939562.png)]

在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:

1、点击修改按钮时,弹出修改窗口并回显数据

2、点击确定按钮,发送ajax请求,将修改窗口的分类信息以json方式提交给服务端

3、服务端接收分类信息,并进行处理,完成后给页面响应

4、页面接收到服务端响应信息后进行相应处理

后端代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SDgmfRzo-1660546100854)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815132215971.png)]

13,文件上传下载功能

文件上传

文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。

文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。

文件上传时,对页面的form表单有如下要求:

lmethod=“post” 采用post方式提交数据

lenctype=“multipart/form-data” 采用multipart格式上传文件

ltype=“file” 使用input的file控件上传

举例:

目前一些前端组件库也提供了相应的上传组件,但是底层原理还是基于form表单的文件上传。

例如ElementUI中提供的upload上传组件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NllO9eME-1660546100854)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815132651412.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BiMmtWsO-1660546100855)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815132655963.png)]

服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:

lcommons-fileupload

lcommons-io

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g115O8NI-1660546100855)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815132712227.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DMXB6RuC-1660546100855)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815132839326.png)]

文件下载

文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。

通过浏览器进行文件下载,通常有两种表现形式:

以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录

直接在浏览器中打开

通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。

文件下载,页面端可以使用标签展示下载的图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-md8KNXSK-1660546100855)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815132900976.png)]
在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N9fynDK1-1660546100856)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815132915422.png)]

14,新增菜品功能

需求分析

后台系统中可以管理菜品信息,通过新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPeZNPp2-1660546100856)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815133048817.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNRZFZ5J-1660546100857)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815133055259.png)]

新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。

所以在新增菜品时,涉及到两个表:

dish 菜品表

dish_flavor 菜品口味表

数据模型

菜品表dish:
在这里插入图片描述

菜品口味表dish_flavor:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8dqcObqz-1660546100857)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815133214750.png)]

代码开发

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

实体类 DishFlavor(直接从课程资料中导入即可,Dish实体前面课程中已经导入过了)

Mapper接口 DishFlavorMapper

业务层接口 DishFlavorService

业务层实现类 DishFlavorServiceImpl

控制层 DishController

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

1、页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2DgeNVk8-1660546100857)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815133515669.png)]

2、页面发送请求进行图片上传,请求服务端将图片保存到服务器,直接使用我们前面开发的CommonController的upload方法来处理即可。

3、页面发送请求进行图片下载,将上传的图片进行回显,直接使用我们前面开发的CommonController的download方法来处理即可。

4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Khnucmvy-1660546100858)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815133715094.png)]

开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。

导入DishDto

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B1alJeRX-1660546100858)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815133824578.png)]

新增菜品时,如果页面添加了对应的口味,最终需要向dish和dish_flavor两个表插入数据,为了保证数据一致性,需要启用事务管理,可以在项目启动类上加入EnableTransactionManagement注解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bgL1cUqy-1660546100858)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815133842334.png)]
在DishController中提供save方法,此处先保证数据能够正确接收到,具体逻辑后续再完善:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D5bnacYm-1660546100858)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815133859830.png)]

在DishService接口中扩展saveWithFlavor方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGpodVF7-1660546100859)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815133917070.png)]

在DishServiceImpl类中实现saveWithFlavor方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VSsp35qw-1660546100859)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815134004677.png)]

在DishController中调用业务层方法完成菜品和口味的保存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N2QSABfl-1660546100859)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815134018016.png)]

15,菜品信息分页查询功能

需求分析

系统中的菜品数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XQEv2k2X-1660546100860)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815134239300.png)]

在开发代码之前,需要梳理一下菜品分页查询时前端页面和服务端的交互过程:

1、页面(backend/page/food/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据

2、页面发送请求,请求服务端进行图片下载,用于页面图片展示

开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yoaFHTWW-1660546100860)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815134554414.png)]

16,修改菜品功能

需求分析

在菜品管理列表页面点击修改按钮,跳转到修改菜品页面,在修改页面回显菜品相关信息并进行修改,最后点击确定按钮完成修改操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fK2Ord7b-1660546100860)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815134622442.png)]

代码开发

在开发代码之前,需要梳理一下修改菜品时前端页面(add.html)和服务端的交互过程:

1、页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示

2、页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显

3、页面发送请求,请求服务端进行图片下载,用于页图片回显

4、点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端

开发修改菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。

页面发送ajax请求,请求服务端获取分类数据,用于修改页面中菜品分类下拉框中数据展示。

前面我们在开发新增菜品时已经完成了查询分类数据的代码开发,此处直接使用此方法即可,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-prU2HcWP-1660546100860)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815134743409.png)]

页面发送ajax请求,请求服务端,根据id查询当前菜品信息和对应的口味信息,用于修改页面中菜品信息回显。

在DishService接口中扩展getByIdWithFlavor方法:

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uZLYK0RW-1660546100861)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815134827795.png)](https://img-blog.csdnimg.cn/9f5a9ef058bf40dda04e52444c245236.png)

在DishServiceImpl中实现getByIdWithFlavor方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SRnZIgXH-1660546100861)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815134839049.png)]

页面发送ajax请求,请求服务端,根据id查询当前菜品信息和对应的口味信息,用于修改页面中菜品信息回显。

在DishController中创建get方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wWUSDQJO-1660546100861)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815135110729.png)]

页面发送请求,请求服务端进行图片下载,用于修改页面图片回显。

前面我们已经在CommonController中提供了下载方法download,此处直接使用即可。

点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端。

在修改菜品信息时需要注意,除了要更新dish菜品表,还需要更新dish_flavor菜品口味表。

在DishService接口中扩展方法updateWithFlavor:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7rwFpsxx-1660546100861)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815135152587.png)]

在DishServiceImpl中实现方法updateWithFlavor:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bc4qAX5M-1660546100862)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815135253096.png)]

在DishController中创建update方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-31jwtkzv-1660546100862)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815135324000.png)]

17,新增套餐功能

需求分析

套餐就是菜品的集合。

后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P76O6dmI-1660546100862)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815135457228.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9hflrtwZ-1660546100863)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815135504577.png)]

数据模型

新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表,还需要向setmeal_dish表插入套餐和菜品关联数据。

所以在新增套餐时,涉及到两个表:

setmeal 套餐表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-knHKs4Qu-1660546100863)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815135559582.png)]

setmeal_dish 套餐菜品关系表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAE1vZhd-1660546100863)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815135605020.png)]

代码开发

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

实体类 SetmealDish

DTO SetmealDto

Mapper接口 SetmealDishMapper

业务层接口 SetmealDishService

业务层实现类 SetmealDishServiceImpl

控制层 SetmealController

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

1、页面(backend/page/combo/add.html)发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NXpVE7kB-1660546100863)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140033535.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KfS9rvNr-1660546100864)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140039716.png)]

2、页面发送ajax请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xCZRLhDs-1660546100864)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140050057.png)]
在这里插入图片描述
3、页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gBWZMliz-1660546100864)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140112098.png)]

在DishController中创建list方法,根据条件查询菜品信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XeFbmhvY-1660546100865)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140130143.png)]

4、页面发送请求进行图片上传,请求服务端将图片保存到服务器,直接使用我们前面开发的CommonController的upload方法来处理即可。

5、页面发送请求进行图片下载,将上传的图片进行回显,直接使用我们前面开发的CommonController的download方法来处理即可。

6、点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端

开发新增套餐功能,其实就是在服务端编写代码去处理前端页面发送的这6次请求即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lwHgwfP8-1660546100865)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140248428.png)]

在SetmealController中提供save方法,此处先保证数据能够正确接收到,具体逻辑后续再完善:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dz6Ar05n-1660546100865)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140301461.png)]

在SetmealService接口中扩展saveWithDish方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0I23XVa4-1660546100865)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140331760.png)]

在SetmealServiceImpl类中实现saveWithDish方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iYFWlYg9-1660546100866)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140401268.png)]

在SetmealController中调用业务层方法完成套餐的保存:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kyWWVdYF-1660546100866)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140421425.png)]

18,套餐信息分页查询功能

需求分析

系统中的套餐数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-83OfpUG4-1660546100866)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140455242.png)]

在开发代码之前,需要梳理一下套餐分页查询时前端页面和服务端的交互过程:

1、页面(backend/page/combo/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据

2、页面发送请求,请求服务端进行图片下载,用于页面图片展示

开发套餐信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

在SetmealController中创建分页查询方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-94hNZ7fN-1660546100867)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140550831.png)]

19,删除套餐功能

需求分析

在套餐管理列表页面点击删除按钮,可以删除对应的套餐信息。也可以通过复选框选择多个套餐,点击批量删除按钮一次删除多个套餐。注意,对于状态为售卖中的套餐不能删除,需要先停售,然后才能删除。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZTGKVQ7l-1660546100867)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140618265.png)]

代码开发

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

1、删除单个套餐时,页面发送ajax请求,根据套餐id删除对应套餐

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NcJSmLNR-1660546100867)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140653634.png)]

2、删除多个套餐时,页面发送ajax请求,根据提交的多个套餐id删除对应套餐

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3njjpp7x-1660546100867)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140658604.png)]

开发删除套餐功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

观察删除单个套餐和批量删除套餐的请求信息可以发现,两种请求的地址请求方式都是相同的,不同的则是传递的id个数,所以在服务端可以提供一个方法来统一处理。

在SetmealController中创建delete方法,接收页面提交的参数,具体逻辑后续再完善:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XtS8OhkG-1660546100868)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140716026.png)]

在SetmealService接口中扩展removeWithDish方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yk6KX958-1660546100868)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815140728232.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sEPX806l-1660546100868)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815141257489.png)]

完善SetmealController的delete方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c9Xo6iK0-1660546100868)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815141314795.png)]

20,邮箱发送短信验证码功能

1,导入依赖

<!--mail短信依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

2,编写resources下的yml配置文件

spring:
mail:
    host: smtp.qq.com
	# 你的QQ邮箱,这里为发件人,填自己的即可
    username: *********@qq.com
	# 你的QQ邮箱授权码
    password: ************

三、获取QQ邮箱授权码
1、打开QQ邮箱网页版点击设置
在这里插入图片描述
2、打开设置后点击账户
在这里插入图片描述
3、在账户页下拉找到POP3/SMTP服务开启获得授权码
在这里插入图片描述
在这里插入图片描述
4、把授权码编写到resources下的yml里即可

代码开发
在这里插入图片描述
前端代码
第一次请求
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第二次请求
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
后端代码
1、编写Service层的UserService

public interface UserService extends IService<User> {
    //发送邮件
    void sendMsg(String to,String subject,String text);
}

2、编写Service里Impl层的UserServiceImpl

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    //把yml配置的邮箱号赋值到from
    @Value("${spring.mail.username}")
    private String from;
    //发送邮件需要的对象
    @Autowired
    private JavaMailSender javaMailSender;
    //邮件发送人
    @Override
    public void sendMsg(String to, String subject, String text) {
        //发送简单邮件,简单邮件不包括附件等别的
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from);
        message.setTo(to);
        message.setSubject(subject);
        message.setText(text);
        //发送邮件
        javaMailSender.send(message);
    }
}

3,在过滤器放行验证码请求和登录请求
在这里插入图片描述
4,编写UserController层

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;
    //获取验证码
    @PostMapping("/sendMsg")
    public R<String> sendMsg(HttpSession session, @RequestBody User user){
        //获取邮箱号
        //相当于发送短信定义的String to
        String email = user.getPhone();
        String subject = "瑞吉外卖";
        //StringUtils.isNotEmpty字符串非空判断
        if (StringUtils.isNotEmpty(email)) {
            //发送一个四位数的验证码,把验证码变成String类型
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            String text = "【瑞吉外卖】您好,您的登录验证码为:" + code + ",请尽快登录";
            log.info("验证码为:" + code);
            //发送短信
            userService.sendMsg(email,subject,text);
            //将验证码保存到session当中
            session.setAttribute(email,code);
            return R.success("验证码发送成功");
        }
        return R.error("验证码发送异常,请重新发送");
    }
    //登录
    @PostMapping("/login")
    //Map存JSON数据
    public R<User> login(HttpSession session,@RequestBody Map map){
        //获取邮箱,用户输入的
        String phone = map.get("phone").toString();
        //获取验证码,用户输入的
        String code = map.get("code").toString();
        //获取session中保存的验证码
        Object sessionCode = session.getAttribute(phone);
        //如果session的验证码和用户输入的验证码进行比对,&&同时
        if (sessionCode != null && sessionCode.equals(code)) {
            //要是User数据库没有这个邮箱则自动注册,先看看输入的邮箱是否存在数据库
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone,phone);
            //获得唯一的用户,因为手机号是唯一的
            User user = userService.getOne(queryWrapper);
            //要是User数据库没有这个邮箱则自动注册
            if (user == null) {
                user = new User();
                user.setPhone(phone);
                user.setStatus(1);
                //取邮箱的前五位为用户名
                user.setName(phone.substring(0,6));
                userService.save(user);
            }
            //不保存这个用户名就登不上去,因为过滤器需要得到这个user才能放行,程序才知道你登录了
            session.setAttribute("user", user.getId());
            return R.success(user);
        }
        return R.error("登录失败");
    }
}

21,导入用户地址簿相关功能代码

需求分析

地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sM9MN0s4-1660546897845)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815141626916.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eM4uFTP0-1660546897846)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815141631754.png)]

数据模型

用户的地址信息会存储在address_book表,即地址簿表中。具体表结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fKdeua4a-1660546897846)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815141646975.png)]

功能代码清单:

实体类 AddressBook

Mapper接口 AddressBookMapper

业务层接口 AddressBookService

业务层实现类 AddressBookServiceImpl

控制层 AddressBookController

22,菜品展示

需求分析

用户登录成功后跳转到系统首页,在首页需要根据分类来展示菜品和套餐。如果菜品设置了口味信息,需要展示![在这里插入图片描述](https://img-blog.csdnimg.cn/66ade445108e4e5fb002a3d7c1ed09

​ 按钮,否则显示+按钮。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LuU09OJ5-1660546897848)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815143539763.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Gb0jBcl-1660546897848)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815143544400.png)]

代码开发

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

1、页面(front/index.html)发送ajax请求,获取分类数据(菜品分类和套餐分类)

2、页面发送ajax请求,获取第一个分类下的菜品或者套餐

开发菜品展示功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

注意:首页加载完成后,还发送了一次ajax请求用于加载购物车数据,此处可以将这次请求的地址暂时修改一下,从静态json文件获取数据,等后续开发购物车功能时再修改回来,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AxTKxYu6-1660546897849)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815143629603.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KMsOiQNe-1660546897849)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815143634120.png)]

在修改DishController的list方法,原来此方法的返回值类型为:R<List>。为了满足移动端对数据的要求(菜品基本信息和菜品对应的口味信息),现在需要将方法的返回值类型改为:R<List>
在这里插入图片描述

在SetmealController中创建list方法,根据条件查询套餐数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cadHIZxp-1660546897850)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815143826090.png)]

23,购物车

需求分析

移动端用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qwaQVb3v-1660546897850)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815143911535.png)]
将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QMH5tM3i-1660546897851)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815143929636.png)]

数据模型

购物车对应的数据表为shopping_cart表,具体表结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VVPI9JaJ-1660546897851)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815143950320.png)]

代码开发

在开发代码之前,需要梳理一下购物车操作时前端页面和服务端的交互过程:

1、点击 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gpmtAbOp-1660546897851)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144035704.png)]
或者 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BZtUjtFu-1660546897852)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144040464.png)]
按钮,页面发送ajax请求,请求服务端,将菜品或者套餐添加到购物车

2、点击购物车图标,页面发送ajax请求,请求服务端查询购物车中的菜品和套餐

3、点击清空购物车按钮,页面发送ajax请求,请求服务端来执行清空购物车操作

开发购物车功能,其实就是在服务端编写代码去处理前端页面发送的这3次请求即可。

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

实体类 ShoppingCart

Mapper接口 ShoppingCartMapper

业务层接口 ShoppingCartService

业务层实现类 ShoppingCartServiceImpl

控制层 ShoppingCartController

添加购物车

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NT4Pshgu-1660546897852)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144140610.png)]

查看购物车

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qfSCTIMT-1660546897853)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144154704.png)]

清空购物车

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YiupVLcb-1660546897853)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144213638.png)]

24,用户下单

需求分析

移动端用户将菜品或者套餐加入购物车后,可以点击购物车中的 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QXuJ2dkE-1660546897853)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144248829.png)]
按钮,页面跳转到订单确认页面,点击 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jinfBsLw-1660546897854)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144252944.png)]
按钮则完成下单操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uz4WJiUp-1660546897854)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144300230.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gHdR6jBW-1660546897854)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144304653.png)]

数据模型

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

orders:订单表

在这里插入图片描述

order_detail:订单明细表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YCKt5JdL-1660546897855)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144336372.png)]

在开发代码之前,需要梳理一下用户下单操作时前端页面和服务端的交互过程:

1、在购物车中点击 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-deslrdJi-1660546897856)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144357967.png)]
按钮,页面跳转到订单确认页面

2、在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的默认地址

3、在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的购物车数据

4、在订单确认页面点击 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传![(img-2lGaYa1V-1660546897856)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144401611.png)]![按钮,发送ajax请求,请求服务端完成下单操作

开发用户下单功能,其实就是在服务端编写代码去处理前端页面发送的请求即可。

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

实体类 Orders、OrderDetail

Mapper接口 OrderMapper、OrderDetailMapper

业务层接口 OrderService、OrderDetailService

业务层实现类 OrderServiceImpl、OrderDetailServiceImpl

控制层 OrderController、OrderDetailController

在OrderController中创建submit方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ytXJNxPN-1660546897857)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144444705.png)]

在OrderService接口中扩展submit方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVhGTqb6-1660546897857)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220815144505259.png)]

@Transactional
    public void submit(Orders orders) {
        Long userId = BaseContext.getCurrentId();
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,userId);
        List<ShoppingCart> shoppingCarts = shoppingCartService.list(queryWrapper);
        if(shoppingCarts == null || shoppingCarts.size() == 0){
            throw new CustomException("购物车为空,不能下单");
        }
        User user = userService.getById(userId);
        AddressBook addressBook  = addressBookService.getById(orders.getAddressBookId());
        if(addressBook == null){
            throw new CustomException("用户地址信息有误,不能下单");
        }
        long orderId = IdWorker.getId();
        AtomicInteger amount = new AtomicInteger(0);
        List<OrderDetail> orderDetails = new ArrayList<>();
        for (ShoppingCart shoppingCart : shoppingCarts) {
            OrderDetail orderDetail = new OrderDetail();
            orderDetail.setOrderId(orderId);
            orderDetail.setNumber(shoppingCart.getNumber());
            orderDetail.setDishFlavor(shoppingCart.getDishFlavor());
            orderDetail.setDishId(shoppingCart.getDishId());
            orderDetail.setSetmealId(shoppingCart.getSetmealId());
            orderDetail.setName(shoppingCart.getName());
            orderDetail.setImage(shoppingCart.getImage());
            orderDetail.setAmount(shoppingCart.getAmount());
            amount.addAndGet(shoppingCart.getAmount().multiply(new BigDecimal(shoppingCart.getNumber())).intValue());
            orderDetails.add(orderDetail);
        }
        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(queryWrapper);
    }
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值