瑞吉外卖-全网最全笔记-Day05

业务开发Day5-01-本章内容介绍

效果展示

  • 套餐管理界面
    瑞吉外卖-全网最全笔记-Day05_第1张图片

  • 新增套餐界面
    瑞吉外卖-全网最全笔记-Day05_第2张图片

  • 客户端展示
    瑞吉外卖-全网最全笔记-Day05_第3张图片

目录

  1. 新增套餐
  2. 套餐信息分页查询
  3. 删除套餐

业务开发Day5-02-新增套餐_需求分析&数据模型

需求分析

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

数据模型

  • 新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表,还需要向setmeal_dish表插入套餐和菜品关联数据
  • 所以在新增套餐时,涉及到两个表:
  1. setmeal----套餐表
  2. setmeal_dish----套餐菜品关系表

setmeal
瑞吉外卖-全网最全笔记-Day05_第4张图片

setmeal_dish
瑞吉外卖-全网最全笔记-Day05_第5张图片

业务开发Day5-03-新增套餐_代码开发_准备工作&梳理交互过程

代码开发-准备工作

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

  1. 实体类SetmealDish(直接从课程资料中导入即可,Setmeal实体前面课程中已经导入过了)
  2. DTO SetmealDto (直接从课程资料中导入即可)
  3. Mapper接口SetmealDishMapper
  4. 业务层接口SetmealDishService
  5. 业务层实现类SetmealDishservicelmpl
  6. 控制层SetmealController

SetmealDish—实体类

package com.itzq.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 套餐菜品关系
 */
@Data
public class SetmealDish implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //套餐id
    private Long setmealId;


    //菜品id
    private Long dishId;


    //菜品名称 (冗余字段)
    private String name;

    //菜品原价
    private BigDecimal price;

    //份数
    private Integer copies;


    //排序
    private Integer sort;


    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;


    @TableField(fill = FieldFill.INSERT)
    private Long createUser;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;


    //是否删除
    private Integer isDeleted;
}

DTO SetmealDto—数据传输对象

package com.itzq.reggie.dto;


import com.itzq.reggie.entity.Setmeal;
import com.itzq.reggie.entity.SetmealDish;
import lombok.Data;
import java.util.List;

@Data
public class SetmealDto extends Setmeal {

    private List<SetmealDish> setmealDishes;

    private String categoryName;
}

SetmealDishMapper接口

package com.itzq.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itzq.reggie.entity.SetmealDish;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SetmealDishMapper extends BaseMapper<SetmealDish> {
}


SetmealDishService接口

package com.itzq.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itzq.reggie.entity.SetmealDish;

public interface SetmealDishService extends IService<SetmealDish> {
}

SetmealDishservicelmpl实现类

package com.itzq.reggie.service.Impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itzq.reggie.entity.SetmealDish;
import com.itzq.reggie.mapper.SetmealDishMapper;
import com.itzq.reggie.service.SetmealDishService;
import org.springframework.stereotype.Service;

@Service
public class SetmealDishServiceImpl extends ServiceImpl<SetmealDishMapper,SetmealDish> implements SetmealDishService {
}

SetmealController控制层

package com.itzq.reggie.controller;

import com.itzq.reggie.service.SetmealDishService;
import com.itzq.reggie.service.SetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/setmeal")
@Slf4j
public class SetmealController {
    @Autowired
    private SetmealService setmealService;
    
    @Autowired
    private SetmealDishService setmealDishService;
    
}

代码开发-梳理交互过程

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

  1. 页面(backend/page/combo/add.html)发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中(已完成)
  2. 页面发送ajax请求,请求服务端,获取菜品分类数据并展示到添加菜品窗口中
  3. 页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中
  4. 页面发送请求进行图片上传,请求服务端将图片保存到服务器(已完成)
  5. 页面发送请求进行图片下载,将上传的图片进行回显(已完成)
  6. 点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端

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

业务开发Day5-04-新增套餐_代码开发_根据分类查询菜品

前端分析

启动项目,进入套餐管理,点击新建套餐,会发现页面发送的请求未被服务端接收
瑞吉外卖-全网最全笔记-Day05_第6张图片

爆系统接口异常,服务端未定义查询菜品的方法
瑞吉外卖-全网最全笔记-Day05_第7张图片

相关代码

在DishController类中,添加list方法
注意:需要添加额外的查询条件,只查询status为1的数据,表示该菜品为起售状态,才能被加入套餐中,供用户选择

    @GetMapping("/list")
    public R<List<Dish>> list(Dish dish){

        //构造查询条件
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
        //添加条件,查询状态为1(1为起售,0为停售)的菜品
        queryWrapper.eq(Dish::getStatus,1);

        List<Dish> list = dishService.list(queryWrapper);
        //添加排序条件
        return R.success(list);
    }

重启项目,发现查询数据成功,并回显到前端页面
瑞吉外卖-全网最全笔记-Day05_第8张图片

业务开发Day5-05-新增套餐_代码开发_服务端接收页面提交的数据

前端分析

启动项目,来到添加套餐页面,输入数据,点击保存
瑞吉外卖-全网最全笔记-Day05_第9张图片

查看前端页面发送的请求,请求方式
瑞吉外卖-全网最全笔记-Day05_第10张图片

前端页面传输json数据给服务端
瑞吉外卖-全网最全笔记-Day05_第11张图片

相关代码(测试版)

在SetmealController类中添加save方法

@PostMapping
    public R<String> save(@RequestBody SetmealDto setmealDto){
        log.info("数据传输对象setmealDto:{}",setmealDto.toString());
        return null;
    }

添加断点瑞吉外卖-全网最全笔记-Day05_第12张图片

debug方式重启项目,来到添加套餐页面,输入数据,点击保存
瑞吉外卖-全网最全笔记-Day05_第13张图片

跳转到服务端,查看是否接收到客服端提交的数据,发现数据成功接收
瑞吉外卖-全网最全笔记-Day05_第14张图片

业务开发Day5-06-新增套餐_代码开发_保存数据到对应表

相关代码

在SetmealService接口,添加saveWithDish方法
瑞吉外卖-全网最全笔记-Day05_第15张图片

实现类SetmealServicelmpl,实现接口添加的方法,并向方法中添加代码逻辑

  • 保存套餐的基本信息
  • 保存套餐和菜品的关联信息
    @Override
    @Transactional
    public void saveWithDish(SetmealDto setmealDto) {
        //保存套餐的基本信息,操作setmeal,执行insert操作
        save(setmealDto);

        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();

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

        //保存套餐和菜品的关联信息
        setmealDishService.saveBatch(setmealDishes);
    }

在SetmealController控制层的save方法中,调用saveWithDish方法,将数据保存至数据库

    @PostMapping
    public R<String> save(@RequestBody SetmealDto setmealDto){
        log.info("数据传输对象setmealDto:{}",setmealDto.toString());
        setmealService.saveWithDish(setmealDto);
        return R.success("新增套餐成功");
    }

业务开发Day5-07-新增套餐_代码开发_功能测试

功能测试

来到新增套餐页面,输入数据,点击保存
瑞吉外卖-全网最全笔记-Day05_第16张图片

setmeal_dish表—数据插入成功
在这里插入图片描述

setmeal—数据插入成功
在这里插入图片描述

业务开发Day5-08-套餐信息分页查询_需求分析&梳理交互过程

需求分析

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

梳理交互过程

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

  1. 页面(backend/page/combo/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据
  2. 页面发送请求,请求服务端进行图片下载,用于页面图片展示

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

业务开发Day5-09-套餐信息分页查询_代码开发&功能测试

前端分析

点击套餐管理,前端页面发送ajax请求,请求方式:get
瑞吉外卖-全网最全笔记-Day05_第17张图片

代码开发

SetmealController类中,添加list方法

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

注意
在套餐管理界面,套餐分类字段显示的是categoryId对应的中文,但在数据库里查询到的是categoryId,因此需要利用categoryId查询到categoryName,并赋值给数据传输对象SetmealDto

功能测试

启动项目,点击套餐管理,前端发送ajax请求,服务端接收前端发出的请求,并做相应的处理,向页面返回数据
瑞吉外卖-全网最全笔记-Day05_第18张图片
数据成功回显到页面

业务开发Day5-10-删除套餐_需求分析&梳理交互过程

需求分析

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

梳理交互过程

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

  1. 删除单个套餐时,页面发送ajax请求,根据套餐id删除对应套餐
    瑞吉外卖-全网最全笔记-Day05_第19张图片

  2. 删除多个套餐时,页面发送ajax请求,根据提交的多个套餐id删除对应套餐开发删除套餐功能
    瑞吉外卖-全网最全笔记-Day05_第20张图片
    其实就是在服务端编写代码去处理前端页面发送的这2次请求即可

注意

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

业务开发Day5-11-删除套餐_代码开发&功能测试

代码开发(测试)

在SetmealController中添加delete方法

    @DeleteMapping
    public R<String> delete(@RequestParam List<Long> ids){
        log.info("ids为:",ids);

        return null;
    }

在delete方法上,添加断点
瑞吉外卖-全网最全笔记-Day05_第21张图片

debug方式启动项目,来到套餐管理页面,点击删除按钮
瑞吉外卖-全网最全笔记-Day05_第22张图片

跳转到服务端,查询ids可知服务端成功接收到前端传来的数据信息
在这里插入图片描述

代码开发(完善)

在SetmealService接口中添加removeWithDish方法
瑞吉外卖-全网最全笔记-Day05_第23张图片

在SetmealServicelmpl实现类中实现对应接口中添加的方法

	@Override
    @Transactional
    public void removeWithDish(List<Long> ids) {
        //select count(*) from setmeal where ids in(1,2,3) and status = 1
        //查询套餐状态,确定是否可以删除
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(Setmeal::getId,ids);
        queryWrapper.eq(Setmeal::getStatus,1);

        int count = super.count(queryWrapper);

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

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

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

        setmealDishService.remove(dishLambdaQueryWrapper);


    }

在SetmealController中完善代码—调用removeWithDish方法,实现套餐数据删除成功

@DeleteMapping
    public R<String> delete(@RequestParam List<Long> ids){
        log.info("ids为:",ids);
        setmealService.removeWithDish(ids);
        return R.success("套餐数据删除成功");

    }

注意:将setmeal表中status字段值改为0—为停售状态,方便测试
在这里插入图片描述

重启项目,来到套餐管理界面,点击删除按钮
瑞吉外卖-全网最全笔记-Day05_第24张图片

页面显示删除成功
瑞吉外卖-全网最全笔记-Day05_第25张图片

setmeal表中该行已被删除
在这里插入图片描述

业务开发Day5-12-本章内容介绍

手机验证码登录

  • 点击获取验证码
  • 收到短信,并输入验证码
  • 点击登录,登录成功
    瑞吉外卖-全网最全笔记-Day05_第26张图片

客户端登录成功页面
瑞吉外卖-全网最全笔记-Day05_第27张图片

本章内容介绍

  1. 短信发送
  2. 手机验证码登录(基于阿里云讲解)

业务开发Day5-13-短信发送_短信服务介绍和阿里云短信服务介绍

短信服务介绍

  • 目前市面上有很多第三方提供的短信服务,这些第三方短信服务会和各个运营商(移动、联通、电信)对接
  • 我们只需要注册成为会员并且按照提供的开发文档进行调用就可以发送短信
    -* 需要说明的是*,这些短信服务一般都是收费服务

常用短信服务:

  • 阿里云
  • 华为云
  • 腾讯云
  • 京东
  • 梦网
  • 乐信

阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或用群发助手,即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。

应用场景:

  • 验证码
  • 推广短信
  • 推广短信

阿里云短信服务介绍

打开浏览器,登录阿里云—网址:https://cn.aliyun.com/

点击产品,在搜索框中输入短信服务,并点击搜索
瑞吉外卖-全网最全笔记-Day05_第28张图片

来到短信息服务界面
瑞吉外卖-全网最全笔记-Day05_第29张图片

选择符合自己业务需求的短信套餐包
瑞吉外卖-全网最全笔记-Day05_第30张图片

业务开发Day5-14-短信发送_阿里云短信服务

设置短信签名

开通短信服务之后,进入短信服务管理页面,选择国内消息菜单,我们需要在这里添加短信签名
瑞吉外卖-全网最全笔记-Day05_第31张图片

什么是短信签名?

  • 短信签名是短信发送者的署名,表示发送方的身份
  • 我们要调用阿里云短信服务发送短信,签名是必不可少的部分

添加短信签名方式
注意:个人申请签名是有一定的难度的,所以我们只需要了解一下使用短信签名的具体流程
瑞吉外卖-全网最全笔记-Day05_第32张图片

设置短信模板

切换到【模板管理】标签页:
瑞吉外卖-全网最全笔记-Day05_第33张图片
短信模板包含短信发送内容、场景、变量信息

每一个被设置好的模板有一个短信模板详情,模板详情包含了模板的6条信息
瑞吉外卖-全网最全笔记-Day05_第34张图片

添加模板,并且提交后审核通过
瑞吉外卖-全网最全笔记-Day05_第35张图片

设置AccessKey

AccessKey 是访问阿里云 API 的密钥,具有账户的完全权限,我们要想在后面通过API调用阿里云短信服务的接口发送短信,那么就必须要设置AccessKey。

光标移动到用户头像上,在弹出的窗口中点击【AccessKey管理】︰
瑞吉外卖-全网最全笔记-Day05_第36张图片

进入到AccessKey的管理界面之后,提示两个选项:

  1. 继续使用AccessKey
  2. 开始使用子用户AccessKey
    瑞吉外卖-全网最全笔记-Day05_第37张图片

区别:

  1. 继续使用AccessKey
  • 如果选择的是该选项,我们创建的是阿里云账号的AccessKey,是具有账户的完全权限
  • 有了这个AccessKey之后,我们就可以通过API调用阿里云服务,不仅是短信服务,其他服务也可以调用
  • 相对来说,并不安全,当前的AccessKey泄露,会影响到当前账户的其他云服务。
  1. 开始使用子用户AccessKey
  • 可以创建一个子用户,这个子用户可以分配比较低的权限,比如仅分配短信发送的权限,不具备操作其他的服务的权限
  • 即使这个AccessKey泄漏了,也不会影响其他的云服务, 相对安全。

创建子用户AccessKey。

  1. 点击创建用户
    瑞吉外卖-全网最全笔记-Day05_第38张图片

  2. 输入登录名称和显示名称(都是自定义),选择—Open API调用访问
    注意:在java代码当中使用这个用户,所以我们选择—Open API调用访问
    瑞吉外卖-全网最全笔记-Day05_第39张图片

  3. 成功创建子用户AccessKey

  • AccessKey ID:用户名
  • AccessKey Secret:密码
  • 需要将这对用户名和密码保存起来,后面在我们的程序当中会使用
    瑞吉外卖-全网最全笔记-Day05_第40张图片

权限管理

在新创建的子用户下点击添加权限
瑞吉外卖-全网最全笔记-Day05_第41张图片

因为我们只需要使用短信服务,所以我们在搜索框输入sms,点击需要添加的权限
瑞吉外卖-全网最全笔记-Day05_第42张图片

授权成功
瑞吉外卖-全网最全笔记-Day05_第43张图片
表示当前我们只给该用户授予了两个权限,即使用户名和密码泄露,其他人也只能调用短信服务
授权成功之后就可以用代码的方式来调用短信服务

AccessKey泄露需要进行的处理

  • AccessKey泄露出去,别人就可以使用我们的 AccessKey来发送短信,我们就需要收回我们的 AccessKey
  • 我们可以禁用或者删除对应的 AccessKey
  • 操作之后,相当于这个 AccessKey就作废了
    瑞吉外卖-全网最全笔记-Day05_第44张图片

业务开发Day5-15-短信发送_代码开发_参照官方文档封装发送短信工具类

参照官方文档

使用阿里云短信服务发送短信,可以参照官方提供的文档即可。

具体开发步骤:

  1. 导入maven坐标
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.5.16</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
    <version>2.1.0</version>
</dependency>
  1. 在reggie包下新建utils包,导入该工具类
package com.itzq.reggie.utils;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;

/**
 * 短信发送工具类
 */
public class SMSUtils {

	/**
	 * 发送短信
	 * @param signName 签名
	 * @param templateCode 模板
	 * @param phoneNumbers 手机号
	 * @param param 参数
	 */
	public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
		DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
		IAcsClient client = new DefaultAcsClient(profile);

		SendSmsRequest request = new SendSmsRequest();
		request.setSysRegionId("cn-hangzhou");
		request.setPhoneNumbers(phoneNumbers);
		request.setSignName(signName);
		request.setTemplateCode(templateCode);
		request.setTemplateParam("{\"code\":\""+param+"\"}");
		try {
			SendSmsResponse response = client.getAcsResponse(request);
			System.out.println("短信发送成功");
		}catch (ClientException e) {
			e.printStackTrace();
		}
	}

}

查看短信服务产品文档的java SDK,了解短信服务java SDK的使用方法以及示例
瑞吉外卖-全网最全笔记-Day05_第45张图片

业务开发Day5-16-手机验证码登录_需求分析_数据模型

需求分析

为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能

手机验证码登录的优点:

  • 方便快捷,无需注册,直接登录
  • 使用短信验证码作为登录凭证,无需记忆密码
  • 安全

登录流程:

  • 输入手机号 > 获取验证码 > 输入验证码 > 点击登录 > 登录成功

注意:通过手机验证码登录,手机号是区分不同用户的标识

用户登录端界面
瑞吉外卖-全网最全笔记-Day05_第46张图片

数据模型

通过手机验证码登录时,涉及的表为user表,即用户表。结构如下:
瑞吉外卖-全网最全笔记-Day05_第47张图片

注意:

  • 手机号是区分不同用户的标识,在用户登录的时候判断所输入的手机号是否存储在表中
  • 如果不在表中,说明该用户为一个新的用户,将该用户自动保在user表中

业务开发Day5-17-手机验证码登录_代码开发_梳理交互过程&修改LoginCheckFilter

梳理交互过程

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

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

开发手机验证码登录功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

代码开发-准备工作

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

  • 实体类user (直接从课程资料中导入即可)
  • Mapper接口UserMapper
  • 业务层接口UserService
  • 业务层实现类UserServicelmpl
  • 控制层UserController
  • 工具类SMSutils、ValidateCodeutils(直接从课程资料中导入即可)

实体类user

package com.itzq.reggie.entity;

import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
/**
 * 用户信息
 */
@Data
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //姓名
    private String name;


    //手机号
    private String phone;


    //性别 0 女 1 男
    private String sex;


    //身份证号
    private String idNumber;


    //头像
    private String avatar;


    //状态 0:禁用,1:正常
    private Integer status;
}

Mapper接口UserMapper

package com.itzq.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itzq.reggie.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

业务层接口UserService

package com.itzq.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itzq.reggie.entity.User;

public interface UserService extends IService<User> {
}

业务层实现类UserServicelmpl

package com.itzq.reggie.service.Impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itzq.reggie.entity.User;
import com.itzq.reggie.mapper.UserMapper;
import com.itzq.reggie.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServicelmpl extends ServiceImpl<UserMapper, User> implements UserService {
}

控制层UserController

package com.itzq.reggie.controller;

import com.itzq.reggie.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

}

工具类SMSutils、ValidateCodeutils(直接从课程资料中导入即可)

  1. SMSutils类
package com.itzq.reggie.utils;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;

/**
 * 短信发送工具类
 */
public class SMSUtils {

	/**
	 * 发送短信
	 * @param signName 签名
	 * @param templateCode 模板
	 * @param phoneNumbers 手机号
	 * @param param 参数
	 */
	public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
		DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
		IAcsClient client = new DefaultAcsClient(profile);

		SendSmsRequest request = new SendSmsRequest();
		request.setSysRegionId("cn-hangzhou");
		request.setPhoneNumbers(phoneNumbers);
		request.setSignName(signName);
		request.setTemplateCode(templateCode);
		request.setTemplateParam("{\"code\":\""+param+"\"}");
		try {
			SendSmsResponse response = client.getAcsResponse(request);
			System.out.println("短信发送成功");
		}catch (ClientException e) {
			e.printStackTrace();
		}
	}

}

  1. ValidateCodeutils类
package com.itzq.reggie.utils;

import java.util.Random;

/**
 * 随机生成验证码工具类
 */
public class ValidateCodeUtils {
    /**
     * 随机生成验证码
     * @param length 长度为4位或者6位
     * @return
     */
    public static Integer generateValidateCode(int length){
        Integer code =null;
        if(length == 4){
            code = new Random().nextInt(9999);//生成随机数,最大为9999
            if(code < 1000){
                code = code + 1000;//保证随机数为4位数字
            }
        }else if(length == 6){
            code = new Random().nextInt(999999);//生成随机数,最大为999999
            if(code < 100000){
                code = code + 100000;//保证随机数为6位数字
            }
        }else{
            throw new RuntimeException("只能生成4位或6位数字验证码");
        }
        return code;
    }

    /**
     * 随机生成指定长度字符串验证码
     * @param length 长度
     * @return
     */
    public static String generateValidateCode4String(int length){
        Random rdm = new Random();
        String hash1 = Integer.toHexString(rdm.nextInt());
        String capstr = hash1.substring(0, length);
        return capstr;
    }
}

修改LoginCheckFilter

前面我们已经完成了LoginCheckFilter过滤器的开发,此过滤器用于检查用户的登录状态。我们在进行手机验证码登录时,发送的请求需要在此过滤器处理时直接放行。

在LoginCheckFilter类中的urls数组中,添加下面两条数据

  1. “/user/sendMsg”, //移动端发送短信
  2. “/user/login” //移动端登录
    瑞吉外卖-全网最全笔记-Day05_第48张图片

启动项目,在浏览器中输入访问地址:http://localhost:8080/front/page/login.html
瑞吉外卖-全网最全笔记-Day05_第49张图片

注意:
使用h5开发的,自适应手机屏幕的大小,在浏览器中,需使用浏览器的手机模式打开,下面为具体步骤:

  1. 按住F12,弹出对应的页面
  2. 点击红色方框所标注的位置,将页面切换至手机浏览模式
    瑞吉外卖-全网最全笔记-Day05_第50张图片

成功显示用户登录页面
瑞吉外卖-全网最全笔记-Day05_第51张图片

在LoginCheckFilter类下添加代码,判断用户是否登录

 		//4-2 判断登录状态,如果已登录,则直接放行
        if(request.getSession().getAttribute("user") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));

            Long userId = (Long)request.getSession().getAttribute("user");
            BaseContext.setCurrentId(userId);

            filterChain.doFilter(request,response);
            return;
        }

添加代码的位置
瑞吉外卖-全网最全笔记-Day05_第52张图片

业务开发Day5-18-手机验证码登录_代码开发_发送验证码短信

注意

在给出的前端资源资料中,login.html是被后面章节修改过的,因此我们需要重新导入front目录
瑞吉外卖-全网最全笔记-Day05_第53张图片

在给出的代码目录中,找到day05下的front目录,复制该目录,将项目中的front目录覆盖
瑞吉外卖-全网最全笔记-Day05_第54张图片
修改完成后,访问前端页面可能出现问题,因此我们需要重启项目,删除浏览器中的数据
若还是不能解决,关闭idea,重新打开idea代码编辑器

前端分析

在用户登录界面中,输入电话号码,点击获取验证码,页面会发送一个ajax请求
请求地址:http://localhost:8080/user/sendMsg
请求方式:POST
瑞吉外卖-全网最全笔记-Day05_第55张图片

代码开发

注意

  • 发送短信只需要调用封装的工具类中的方法即可
  • 使用手机号登录功能流程跑通,在测试中我们不用真正的发送短信,只需要将验证码信息,通过日志输出
  • 登录时,我们直接从控制台就可以看到生成的验证码(实际上也就是发送到我们手机上的验证码)

在UserController控制层中,添加sendMsg方法

	@PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user, HttpSession session){
        //获取手机号
        String phone = user.getPhone();

        if (StringUtils.isNotEmpty(phone)){
            //生成随机的4位验证码
            String code = ValidateCodeUtils.generateValidateCode4String(4);
            log.info("code={}",code);

            //调用阿里云提供的短信服务API完成短信发送
            //SMSUtils.sendMessage("瑞吉外卖","",phone,code);

            //需要将生成的验证码保存到session
            session.setAttribute(phone,code);
            return R.success("短信发送成功");
        }
        return R.error("短信发送失败");
    }

测试

  • 重启项目,在浏览器地址栏中输入地址:http://localhost:8080/front/page/login.html

  • 来到登录页面,输入手机号,点击获取验证码
    瑞吉外卖-全网最全笔记-Day05_第56张图片

  • 后端获取到生成的验证码
    在这里插入图片描述

  • 在用户登录页面上,输入验证码,点击登录
    瑞吉外卖-全网最全笔记-Day05_第57张图片

  • 报404错误,后端还没有处理该请求的代码
    瑞吉外卖-全网最全笔记-Day05_第58张图片

业务开发Day5-19-手机验证码登录_代码开发_登录效验

前端分析

来到用户登录界面,按住F12,点击登录按钮,页面发送ajax请求,查看请求的地址以及方式
瑞吉外卖-全网最全笔记-Day05_第59张图片

页面以json数据格式传输给服务端
瑞吉外卖-全网最全笔记-Day05_第60张图片

代码开发

在UserController控制层类中,添加login方法,测试服务端是否可以接受前端提交的数据

	@PostMapping("/login")
    public R<String> login(@RequestBody Map map, HttpSession session){

        log.info(map.toString());
        return R.error("短信发送失败");
    }

重启项目,来到用户登录界面,输入正确手机号,点击获取验证码,查看服务端日志打印出的验证码信息,输入验证码,点击登录,前端发送ajax请求
瑞吉外卖-全网最全笔记-Day05_第61张图片

前端发送ajax请求,服务端通过日志打印出手机和验证码信息,接收数据成功
瑞吉外卖-全网最全笔记-Day05_第62张图片

注意
在login方法中,接收数据的参数类型为Map类型,也可以重新定义一个UserDto(用户类数据传输对象)用来接收数据
在这里插入图片描述

完善用户登录代码

@PostMapping("/login")
    public R<User> login(@RequestBody Map map, HttpSession session){
        log.info(map.toString());
        //获取手机号
        String phone = map.get("phone").toString();
        //获取验证码
        String code = map.get("code").toString();
        //从session中获取保存的验证码
        String codeInSession = session.getAttribute(phone).toString();
        //进行验证码的对比(页面提交的验证码和session中保存的验证码)
        if (code != null && code.equals(codeInSession)){
            //如果比对成功,则登录成功
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone,phone);
            User user = userService.getOne(queryWrapper);

            if (user == null){
                //判断当前手机号是否为新用户,如果是新用户则自动完成注入
                user = new User();
                user.setPhone(phone);
                userService.save(user);
            }
            return R.success(user);
        }

        return R.error("登录失败");
    }

业务开发Day5-20-手机验证码登录_功能测试

测试

重启项目,进入用户登录界面,输入手机号,点击获取验证码
瑞吉外卖-全网最全笔记-Day05_第63张图片

服务器控制台打印出验证码日志信息
瑞吉外卖-全网最全笔记-Day05_第64张图片

在用户界面输入获取到的验证码,点击登录
瑞吉外卖-全网最全笔记-Day05_第65张图片

页面跳转到用户登录界面

  • 经过分析得出:未将userId存储到session中
  • 导致前端发送请求时,进入filter过滤器,判断用户登录状态为未登录状态
  • 即跳转到登录界面
    瑞吉外卖-全网最全笔记-Day05_第66张图片

在login方法中,添加代码
目的:将userId保存到session当中,前端页面发送请求到服务端,经过filter过滤器,判断用户状态为已登录状态,页面将不会跳转到用户登录界面
瑞吉外卖-全网最全笔记-Day05_第67张图片

重启项目,在用户登录页面输入手机号,和获取到的验证码,点击登录
瑞吉外卖-全网最全笔记-Day05_第68张图片

页面成功跳转到服务用户界面
瑞吉外卖-全网最全笔记-Day05_第69张图片

user表中成功添加上测试的手机号码(未注册的手机号码)
瑞吉外卖-全网最全笔记-Day05_第70张图片

你可能感兴趣的:(瑞吉外卖,java,前端框架,后端,spring,maven)