SpringBoot的旅游项目——day01(学习记录附赠源码)

2021-08-16 17:05:49 浏览数 (1)

文章目录

  • 前言
  • 第一天
    • 一、技术选型
    • 二、搭建项目
    • 三、引入依赖
      • 3.1、父目录
      • 3.2、travel-core
      • 3.3、travel-website-api
      • 3.4、创建配置类
    • 四、测试
      • 4.1、travel-core写代码
      • 4.2、travel-website-api写接口
      • 4.3、浏览器访问
      • 4.4、整合Swagger2
        • 4.4.1、编写配置文件
        • 4.4.2、控制器中添加注解,用以扫描
    • 五、注册
      • 5.1、校验手机号码合法性
      • 5.2、编写校验手机是否注册接口
        • 5.2.1、技术难点分析
        • 5.2.2、配置跨域
      • 5.3、编写发短信工具类(调用阿里云接口)**(踩了巨坑)**
      • 5.4、调用工具类发送短信
      • 5.5、将验证码放入Redis
        • 5.5.1、使用枚举重写Redis的key
      • 5.6、参数校验
        • 5.6.1、自定义异常
        • 5.6.2、统一异常处理
        • 5.6.1、断言
      • 5.7、封装VO
      • 5.8、重写mapper的insert方法
      • 5.9、完整ServiceImpl代码
      • 5.4、测试

前言

学完SpringBoot的项目,Github地址,欢迎start,一起学习!

第一天

一、技术选型

    基于SpringBoot VUE的前后端分离的仿照马蜂窝的项目。

    后端选用的技术为:

  1. SpringBoot
  2. MySQL
  3. MyBatis-Plus
  4. Redis
  5. MongDB
  6. Elasticsearch

二、搭建项目

创建文件夹

    本项目使用的搭建方式是多模块的搭建方式。我们首先需要在Idea的工作空间中新建一个文件夹,用于存放父目录。

在文件夹中创建一个父目录

    这个是一个父目录,不写代码,主要的工作是用于引入一些所有的子目录都需要引入的依赖。

创建travel-core

    由于我们需要写的domain、service、mapper是很多的子目录都需要的,为了防止代码冗余,我们将这些代码抽取成一个公共的模块,取名叫做:travel-core。

创建travel-website

    本次系统采用的1是前后端分离的项目,我们将静态资源抽取出来成濑一个专门放纯静态页面的模块,不做任何1的业务逻辑的实现,仅仅实现前端数据的展示和js。

创建travel-website-api

    既然有了前端的页面,如果想成为一个完整的项目,就必须需要接口,我们将和前端交互的接口抽取成一个模块。

创建travel-mgrsite

    有了前台还不够,我们需要后台来进行管理,我们将后台的接口抽取成一个专门的模块。

三、引入依赖

3.1、父目录

    我们需要将travel-core的核心代码管理起来,方便后面的模块调用。

代码语言:javascript复制
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>cn.linstudy.travelgroupId>
        <artifactId>travel-coreartifactId>
        <version>1.0version>
      dependency>
    dependencies>
  dependencyManagement>

    完整的pom文件:

代码语言:javascript复制
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0modelVersion>
  <parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.4.3version>
    <relativePath/>
  parent>

  <groupId>cn.linstudy.travelgroupId>
  <artifactId>travel-parentartifactId>
  <packaging>pompackaging>
  <version>1.0version>
  <description>父类,用于导入各种需要的依赖,不写代码description>
  <modules>
    <module>travel-coremodule>
    <module>travel-mgrsitemodule>
    <module>travel-website-apimodule>
  modules>

  <properties>
    <maven.compiler.source>11maven.compiler.source>
    <maven.compiler.target>11maven.compiler.target>
  properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>cn.linstudy.travelgroupId>
        <artifactId>travel-coreartifactId>
        <version>1.0version>
      dependency>
    dependencies>
  dependencyManagement>

project>

3.2、travel-core

    接下来我们需要处理travel-core核心模块的依赖了,首先要做的是先引入父目录的依赖同时指定父目录的pom.xml文件的位置。

    因为这个模块需要处理事情主要是一些通用的domain、service、mapper,所以需要引入常用的依赖。完整的pom文件为:

代码语言:javascript复制
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
  <parent>
    <artifactId>travel-parentartifactId>
    <groupId>cn.linstudy.travelgroupId>
    <version>1.0version>
    <relativePath>../pom.xmlrelativePath>
  parent>
  <modelVersion>4.0.0modelVersion>

  <artifactId>travel-coreartifactId>
  <description>用于抽取重复的代码:mapper、service、domaindescription>

  <properties>
    <maven.compiler.source>11maven.compiler.source>
    <maven.compiler.target>11maven.compiler.target>
  properties>

  <dependencies>
    <dependency>
      <groupId>org.projectlombokgroupId>
      <artifactId>lombokartifactId>
      <optional>trueoptional>
    dependency>

    <dependency>
      <groupId>com.baomidougroupId>
      <artifactId>mybatis-plus-boot-starterartifactId>
      <version>3.4.0version>
    dependency>

    <dependency>
      <groupId>com.alibabagroupId>
      <artifactId>druid-spring-boot-starterartifactId>
      <version>1.1.24version>
    dependency>

    <dependency>
      <groupId>mysqlgroupId>
      <artifactId>mysql-connector-javaartifactId>
    dependency>

    <dependency>
      <groupId>com.alibabagroupId>
      <artifactId>fastjsonartifactId>
      <version>1.2.74version>
    dependency>


    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-webartifactId>
      <scope>providedscope>
    dependency>
    <dependency>
      <groupId>io.springfoxgroupId>
      <artifactId>springfox-swagger2artifactId>
      <version>2.9.2version>
    dependency>
    <dependency>
      <groupId>io.springfoxgroupId>
      <artifactId>springfox-swagger-uiartifactId>
      <version>2.9.2version>
    dependency>

    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>

    <dependency>
      <groupId>com.aliyun.ossgroupId>
      <artifactId>aliyun-sdk-ossartifactId>
      <version>3.5.0version>
    dependency>
    <dependency>
      <groupId>commons-iogroupId>
      <artifactId>commons-ioartifactId>
      <version>2.6version>
    dependency>

    <dependency>
      <groupId>commons-beanutilsgroupId>
      <artifactId>commons-beanutilsartifactId>
      <version>1.9.3version>
    dependency>
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-data-elasticsearchartifactId>
    dependency>

  dependencies>

project>

3.3、travel-website-api

    我们需要在travel-website-api中引入travel-core,这样才可以获取到travel-core中抽取的代码,同时需要引入一些额外的依赖。

代码语言:javascript复制
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <artifactId>travel-parentartifactId>
    <groupId>cn.linstudy.travelgroupId>
    <version>1.0version>
  parent>
  <modelVersion>4.0.0modelVersion>

  <artifactId>travel-website-apiartifactId>
  <description>用于处理前端请求的接口description>
  <properties>
    <maven.compiler.source>11maven.compiler.source>
    <maven.compiler.target>11maven.compiler.target>
  properties>

  <dependencies>
    <dependency>
      <groupId>cn.linstudy.travelgroupId>
      <artifactId>travel-coreartifactId>
    dependency>

    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-webartifactId>
    dependency>
  dependencies>

project>

3.4、创建配置类

    在以前传统的开发,我们需要在启动类头上贴MapperScan注解,表示需要扫描mapper接口并且创建代理对象的位置。     由于我们这次是分模块开发,无法在启动类上扫描Mapper接口,所以我们创建一个配置类来进行配置。

代码语言:javascript复制
/**
 * @Description 配置类
 * @Author XiaoLin
 * @Date 2021/4/9 14:51
 */
@Configuration
@MapperScan(basePackages = "cn.linstudy.travel.mapper")
public class CoreConfig {

}

四、测试

    搭建好之后就是需要测试了,我们需要在travel-core中按照惯例写一些抽取的代码。

4.1、travel-core写代码

代码语言:javascript复制
/**
    * @Description: 用于抽取所有的实体类id
    * @author XiaoLin
    * @date 2021/4/9
    */
public abstract class BaseDomain implements Serializable {
    // MyBatis-Plus表示这个是id自增
    @ApiModelProperty(value = "主键id")
    @TableId(type = IdType.AUTO)
    protected Long id;
}
代码语言:javascript复制
/**
 * @Description 用户信息实体类
 * @Author XiaoLin
 * @Date 2021/4/9 14:18
 */
@Setter
@Getter
@TableName("userinfo")
@ApiModel(value = "cn.linstudy.travel.domain",description = "用户信息实体类")
public class UserInfo extends BaseDomain{

    public static final int GENDER_SECRET = 0; //保密
    public static final int GENDER_MALE = 1;   //男
    public static final int GENDER_FEMALE = 2;  //女
    public static final int STATE_NORMAL = 0;  //正常
    public static final int STATE_DISABLE = 1;  //冻结

    @ApiModelProperty(value = "昵称")
    private String nickname;

    @ApiModelProperty(value = "手机")
    private String phone;

    @ApiModelProperty(value = "手机")
    private String email;  //邮箱

    @JsonIgnore
    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "性别")
    private Integer gender = GENDER_SECRET;

    @ApiModelProperty(value = "用户级别")
    private Integer level = 0;

    @ApiModelProperty(value = "所在城市")
    private String city;

    @ApiModelProperty(value = "头像")
    private String headImgUrl;

    @ApiModelProperty(value = "个性签名")
    private String info;

    @ApiModelProperty(value = "状态")
    private Integer state = STATE_NORMAL;

}
代码语言:javascript复制
/**
 * @Description 用户Mapper
 * @Author XiaoLin
 * @Date 2021/4/9 14:20
 */
public interface UserInfoMapper extends BaseMapper<UserInfo> { // 继承MyBatis-Plus的通用Mapper,泛型是实体类
}
代码语言:javascript复制
/**
 * @Description 用户业务层接口
 * @Author XiaoLin
 * @Date 2021/4/9 14:21
 */
public interface UserInfoService extends IService<UserInfo> { // 继承MyBatis-Plus的通用Service接口,泛型是实体类

}
代码语言:javascript复制
/**
 * @Description 用户业务层接口实现类
 * @Author XiaoLin
 * @Date 2021/4/9 14:23
 */
@Service
@Transactional
// 实现MyBatis-Plus的通用Service实现类,泛型参数一是mapper接口,第二个是用户实体类
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper,UserInfo> implements UserInfoService {

}

4.2、travel-website-api写接口

    到了测试环节,我们需要在travel-website-api中写接口来进行测试。

代码语言:javascript复制
/**
 * @Description
 * @Author XiaoLin
 * @Date 2021/4/9 14:46
 */
@RestController
public class UserInfoController {

  @Autowired
  UserInfoService userInfoService;

  @GetMapping("detail")
//  @ApiImplicitParam(name = "id", value = "用户id")
      public Object getUser( String id){
    return userInfoService.getById(Long.valueOf(id));
  }
}

4.3、浏览器访问

    启动后我们需要在浏览器中输入:http://localhost:8080/detail?id=1进行测试。

4.4、整合Swagger2

    每次测试我们都要在浏览器中进行输入稍显麻烦,我们可以整合Swagger2来进行接口测试。所以我们可以尝试整合Swagger2。

4.4.1、编写配置文件

    我们需要在travel-core中创建一个Swagger的配置类。

代码语言:javascript复制
/**
 * @Description Swagger配置类
 * @Author XiaoLin
 * @Date 2021/4/9 17:22
 */
@Configuration//表示这是一个配置类

public class SwaggerConfig {

  @Bean
  public Docket reeateDocket(){
    List<Parameter> parameterList=new ArrayList<>();
    ParameterBuilder parameterBuilder=new ParameterBuilder();
    parameterBuilder.name("token")
        .description("swagger调试用,模拟传入用户凭证")
        .modelRef(new ModelRef("String"))
        .parameterType("header").required(false);
    parameterList.add(parameterBuilder.build());
    return new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(apiInfo())//创建该Api的基本信息(这些基本信息会展现在文档页面中)
        .select()//函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger ui来展现
        .apis(RequestHandlerSelectors.basePackage("cn.linstudy.travel.controller"))//指定需要扫描的包路路径
        .paths(PathSelectors.any())
        .build()
        .globalOperationParameters(parameterList)
        ;
  }
  //配置swagger的信息
  private ApiInfo apiInfo(){
    return new ApiInfoBuilder()
        .title("SpringBoot-Travel项目实战")
        .description("接口")
        .termsOfServiceUrl("")
        .version("1.0")
        .build();
  }
}

    启动服务器,并且输入网址:http://localhost:8080/swagger-ui.html

4.4.2、控制器中添加注解,用以扫描
代码语言:javascript复制
/**
 * @Description
 * @Author XiaoLin
 * @Date 2021/4/9 14:46
 */
@RestController
@Api(tags = "用户相关接口")
public class UserInfoController {

  @Autowired
  UserInfoService userInfoService;

  @ApiOperation(value = "根据id查询用户")
  @GetMapping("detail")
  @ApiImplicitParam(name = "id", value = "用户id")
      public Object getUser( String id){
    return userInfoService.getById(Long.valueOf(id));
  }
}

五、注册

5.1、校验手机号码合法性

    注册首先需要做的是校验手机的合法性,确保用户输入合法的手机号用于下一步发短信验证码。

代码语言:javascript复制
$(function () {
    $('#_js_loginBtn').click(function () {
        var val = $('#inputPassword').val();

        //js 正则表达语法:
        //    / /g  : 正则表达式对象

        // ^1  以1开头
        //  d 数字 0-9 数字中一个
        //  {10}  重复个数   d{10} 表示10个数字
        // $  以xx结束
        //  [3456789]  代码 3 4 5 6 7 8 9 中一个数
        // 正则表达式校验
        if (/^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])d{8}$/g.test(val)) {

            // 如果匹配的话就发请求到后台校验手机号是否注册过
            $.get(domainUrl   "/users/checkPhone", {phone:val}, function (data) {
                if(!data){
                    $('#inputPassword').next().text('').hide()
                    $('.login-box').hide()
                    $('.signup-box').show()
                    $("#phone").val(val);
                }else{
                   
                    $('#inputPassword').next().text('手机号码已注册.').show()
                }
            })
        } else {
            // 匹配不通过表示手机号格式不正确
            $('#inputPassword').next().text('手机号码格式不正确').show()
        }
    });

5.2、编写校验手机是否注册接口

5.2.1、技术难点分析
5.2.2、配置跨域

    由于我们的项目是前后端分离的,会涉及到跨域的问题,所以我们首先需要解决的问题是跨域。我们在travel-core中的config包新建一个配置类,专门用于处理跨域请求。

代码语言:javascript复制
/**
 * @Description 解决跨域配置类
 * @Author XiaoLin
 * @Date 2021/4/9 20:31
 */
@Configuration
public class WebConfigurer implements WebMvcConfigurer {

  @Bean
  public WebConfigurer corsConfigurer() {
    return new WebConfigurer() {
      @Override
      //重写父类提供的跨域请求处理的接口
      public void addCorsMappings(CorsRegistry registry) {
        //添加映射路径
        registry.addMapping("/**")
            //放行哪些原始域
            .allowedOriginPatterns("*")
            //是否发送Cookie信息
            .allowCredentials(true)
            //放行哪些原始域(请求方式)
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            //放行哪些原始域(头部信息)
            .allowedHeaders("*")
            //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
            .exposedHeaders("Header1", "Header2");
      }

    };

  }
}

5.3、编写发短信工具类(调用阿里云接口)(踩了巨坑)

    我们需要调用阿里云的接口来进行发短信,我在网上找了一个工具类(放在travel-core中),想着不能把阿里云短信的配置信息直接打在代码里面,造成硬编码的问题,所以我想着把他抽取出来,放在配置文件中。

代码语言:javascript复制
aliyun:
  accessKeyId: "你的阿里云accessKeyId"
  secret: "你的阿里云密钥"

    但是我在赋值的是时候傻眼了,因为这里的值都是静态的变量,用@Value注解没办法进行赋值。下面贴出初始的工具类。

代码语言:javascript复制
public class SendMessageUtils {
 
    // 产品名称:云通信短信API产品,开发者无需替换
    static final String product = "Dysmsapi";
    // 产品域名,开发者无需替换
    static final String domain = "dysmsapi.aliyuncs.com";
 
    // TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
    static final String accessKeyId = "youaccessKeyId";           // TODO 改这里
    static final String accessKeySecret = "youaccessKeySecret"; // TODO 改这里
 
 
 
    public static SendSmsResponse sendSms(String telephone, String code) throws ClientException {
 
        // 可自助调整超时时间
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");
 
        // 初始化acsClient,暂不支持region化
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
        DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
        IAcsClient acsClient = new DefaultAcsClient(profile);
 
        // 组装请求对象-具体描述见控制台-文档部分内容
        SendSmsRequest request = new SendSmsRequest();
        // 必填:待发送手机号
        request.setPhoneNumbers(telephone);
        // 必填:短信签名-可在短信控制台中找到
        request.setSignName("你的短信签名"); // TODO 改这里
        // 必填:短信模板-可在短信控制台中找到
        request.setTemplateCode("你的短信模板");  // TODO 改这里
        // 可选:模板中的变量替换JSON串,如模板内容为"亲爱的用户,您的验证码为${code}"时,此处的值为
        request.setTemplateParam("{"code":""   code   ""}");
 
        // 选填-上行短信扩展码(无特殊需求用户请忽略此字段)
        // request.setSmsUpExtendCode("90997");
 
        // 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
        request.setOutId("yourOutId");
 
        // hint 此处可能会抛出异常,注意catch
        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
        if(sendSmsResponse.getCode()!= null && sendSmsResponse.getCode().equals("OK")){
                System.out.println("短信发送成功!");
        }else {
                System.out.println("短信发送失败!");
        }
        return sendSmsResponse;
    }

}

    我试了好几次都无法1获取到值,为了应对@Value注解1赋值给静态变量的问题,需要加上seter方法进行赋值,而且记住要删除默认生成的setter方法的static修饰符,否则还是无法获取。将从yml中获取的值赋值给set方法的参数,随后赋值给成员变量,但是要记住一定要删除默认生成的setter方法的static修饰符

代码语言:javascript复制
@PropertySource(value = "classpath:application-core.yml")
@Component
public class SendMessageUtils {


  private static String accessKeyId;  // TODO 修改成自己的
  private static String accessKeySecret;   // TODO 修改成自己的
    
  @Value("${aliyun.accessKeyId}")
  public  void setAccessKeyId(String accessKeyId) {
    SendMessageUtils.accessKeyId = accessKeyId;
  }


  @Value("${aliyun.secret}")
  public  void setAccessKeySecret(String secret) {
    SendMessageUtils.accessKeySecret = secret;
  }

 
  //产品名称:云通信短信API产品,开发者无需替换
  static final String product = "Dysmsapi";
  //产品域名,开发者无需替换
  static final String domain = "dysmsapi.aliyuncs.com";


  public static SendSmsResponse sendSms(String telephone, String code) throws ClientException {
    //可自助调整超时时间
    System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
    System.setProperty("sun.net.client.defaultReadTimeout", "10000");
    System.out.println(1/0);
    //初始化acsClient,暂不支持region化
    IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
    DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
    IAcsClient acsClient = new DefaultAcsClient(profile);
    //组装请求对象-具体描述见控制台-文档部分内容
    SendSmsRequest request = new SendSmsRequest();

    //必填:待发送手机号
    request.setPhoneNumbers(telephone);
    //必填:短信签名-可在短信控制台中找到
    request.setSignName("XiaoLin");    // TODO 修改成自己的
    //必填:短信模板-可在短信控制台中找到
    request.setTemplateCode("SMS_213078152");    // TODO 修改成自己的
    //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
//        request.setTemplateParam("{"name":"Tom", "code":"123"}");
    request.setTemplateParam("{"code":""   code   ""}");
    //选填-上行短信扩展码(无特殊需求用户请忽略此字段)
    //request.setSmsUpExtendCode("90997");
    //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
//    request.setOutId("yourOutId");
    //hint 此处可能会抛出异常,注意catch
    SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
    if (sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) {
      System.out.println("短信发送成功!");
    } else {
      System.out.println("短信发送失败!");
    }
    return sendSmsResponse;
  }

}

5.4、调用工具类发送短信

代码语言:javascript复制
/**
      * @Description: 发送验证码实现类
      * @author XiaoLin
      * @date 2021/4/10
      * @Param: [phone]
      * @return cn.linstudy.travel.qo.response.JsonResult
      */
  @Override
  public JsonResult sendVerifyCode(String phone) {
    try {
      String code = VerifyCodeUtils.generateVerifyCode(4);
      System.out.println("发送了短信");
      SendMessageUtils.sendSms(phone,code);
      userInfoRedisService.setVerifyCode(phone,code);
      return JsonResult.success();
    } catch (Exception e) {
      return JsonResult.error(SystemConstant.CODE_SEND_PHONE_MESSAGE,e.getMessage());

    }
  }

5.5、将验证码放入Redis

5.5.1、使用枚举重写Redis的key

枚举类的特点:

  1. 枚举类构造器是私有的
  2. 枚举类定义完成之后,枚举类的个数是固定的。

因为防止有些人不按照我们的规定进行拼key,所以我们利用枚举来进行重写key

代码语言:javascript复制
package cn.linstudy.travel.redis;

/**
* @Description
* @Author XiaoLin
* @Date 2021/4/10 20:03
*/
@Getter
public enum RedisKeyEnum {

 // 用户注册验证码 key 实例对象
 ENUM_VERYFY_CODE("veryfy_code",SystemConstant.VERIFY_CODE_VAI_TIME*60L);


 // 前缀
 private String prefix;
 // 有效时长
 private Long time;

 RedisKeyEnum(String prefix, long time) {
   this.prefix = prefix;
   this.time = time;
 }

 // 拼接key
 public String join(String... values){
   StringBuilder sb = new StringBuilder();
   sb.append(this.prefix);
   for (String value : values) {
     sb.append(":").append(value);
   }
   return sb.toString();
 }
}
代码语言:javascript复制
package cn.linstudy.travel.redis.service;

/**
 * @Description 用户缓存的业务类
 * @Author XiaoLin
 * @Date 2021/4/10 9:13
 */
public interface UserInfoRedisService {

  /**
   * 将验证码设置到redis中
   * @param phone
   * @param code
   */
  void setVerifyCode(String phone, String code);

}
代码语言:javascript复制
package cn.linstudy.travel.redis.service.impl;

/**
 * @Description
 * @Author XiaoLin
 * @Date 2021/4/10 9:18
 */
@Service
public class UserInfoRedisServiceImpl implements UserInfoRedisService {

  @Autowired
  private StringRedisTemplate template;
  @Override
  public void setVerifyCode(String phone, String code) {
    String  key = RedisKeyEnum.ENUM_VERYFY_CODE.join(phone);
    String value = code;
    // 将验证码放入Redis,并且设置时效
    template.opsForValue().set(key,value, RedisKeyEnum.ENUM_VERYFY_CODE.getTime(), TimeUnit.SECONDS);
  }

}

5.6、参数校验

    虽然在前台进行了参数的校验,但是在后台也是需要进行参数的非空校验的,不排除有些人通过接口测试的方式进入方法。

5.6.1、自定义异常
代码语言:javascript复制
package cn.linstudy.travel.exception;

/**
 * @Description 自定义异常
 * @Author XiaoLin
 * @Date 2021/4/10 10:34
 */
public class LogicException extends RuntimeException{

  // 标记非系统异常
  public LogicException(String message) {
    super(message);
  }
}
5.6.2、统一异常处理

    所有的异常都会被捕获,然后来到这里。

代码语言:javascript复制
package cn.linstudy.travel.advice;
/**
 通用异常处理类
 *  ControllerAdvice  controller类功能增强注解, 动态代理controller类实现一些额外功能
 *  请求进入controller映射方法之前做功能增强: 经典用法:日期格式化
 *  请求进入controller映射方法之后做功能增强: 经典用法:统一异常处理
 * @Author XiaoLin
 * @Date 2021/4/10 19:17
 */
public class CommonExceptionHandler {

  //这个方法定义的跟映射方法操作一样
  @ExceptionHandler(LogicException.class)
  @ResponseBody
  public Object LogicException(Exception e, HttpServletResponse resp) {
    e.printStackTrace();
    resp.setContentType("application/json;charset=utf-8");
    return JsonResult.error(SystemConstant.CODE_ERROR_PARAM, e.getMessage(), null);
  }

  @ExceptionHandler(RuntimeException.class)
  @ResponseBody
  public Object  RuntimeException(Exception e, HttpServletResponse resp) {
    e.printStackTrace();
    resp.setContentType("application/json;charset=utf-8");
    return JsonResult.defaultError();
  }
}
5.6.1、断言

    SpringBoot有一种断言方式,我们需要重写断言来进行判断来简化if-else操作,但是原生的断言不适合我们,我们需要重写。

代码语言:javascript复制
public class AssertsUtils {

  private AssertsUtils() {
  }

  /**
   * @return void
   * @Description: 判断指定的参数是否为null,或者空串,如果为空抛出异常
   * @author XiaoLin
   * @date 2021/4/10
   * @Param: [text, message]
   */
  public static void hasText(String text, String message) {
    if (!StringUtils.hasText(text)) {
      throw new LogicException(message);
    }
  }
    
  /**
      * @Description: 判断传入的参数是否相等
      * @author XiaoLin
      * @date 2021/4/10
      * @Param: [param1, param2, message]
      * @return void
      */
  public static void isEquals(String param1, String param2, String message) {
    if (param1 == null || param2 == null) {
      throw new LogicException("传入的参数为空");
    }
    if (!param1.equals(param2)) {
      throw new LogicException(message);
    }
  }

}

5.7、封装VO

    我们将前台传进来的数据封装成一个注册的VO

代码语言:javascript复制
package cn.linstudy.travel.vo;

/**
 * @Description 用户注册提交表单的VO
 * @Author XiaoLin
 * @Date 2021/4/10 9:48
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("userinfo")
public class UserInfoRegisterVO extends BaseDomain {

  @ApiModelProperty(value = "昵称")
  private String nickname;

  @ApiModelProperty(value = "密码")
  private String password;

  // 这个字段不会映射到数据库中
  @TableField(exist = false)
  @ApiModelProperty(value = "确认密码")
  private String repeatPassword;

  @ApiModelProperty(value = "手机")
  private String phone;
}

5.8、重写mapper的insert方法

    由于MyBatis-Plus的Mapper的insert方法不适用于我们封装的VO对象进行增加,所以我们需要重写Mapper的insert方法。

代码语言:javascript复制
package cn.linstudy.travel.mapper;

/**
 * @Description 用户Mapper
 * @Author XiaoLin
 * @Date 2021/4/9 14:20
 */
public interface UserInfoMapper extends BaseMapper<UserInfo> {// 继承MyBatis-Plus的通用Mapper,泛型是实体类

  @Insert("insert into userinfo( nickname, phone, password) values (#{nickname},#{phone},#{password})")
  void insert(UserInfoRegisterVO userInfoRegisterVO);
}

5.9、完整ServiceImpl代码

代码语言:javascript复制
package cn.linstudy.travel.service.impl;


import cn.linstudy.travel.constant.SystemConstant;
import cn.linstudy.travel.domain.UserInfo;
import cn.linstudy.travel.exception.LogicException;
import cn.linstudy.travel.mapper.UserInfoMapper;
import cn.linstudy.travel.qo.response.JsonResult;
import cn.linstudy.travel.redis.service.UserInfoRedisService;
import cn.linstudy.travel.service.UserInfoService;
import cn.linstudy.travel.utils.AssertsUtils;
import cn.linstudy.travel.utils.SendMessageUtils;
import cn.linstudy.travel.utils.VerifyCodeUtils;
import cn.linstudy.travel.vo.UserInfoRegisterVO;
import com.aliyuncs.exceptions.ClientException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import javax.security.auth.login.LoginException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @Description 用户业务层接口实现类
 * @Author XiaoLin
 * @Date 2021/4/9 14:23
 */
@Service
@Transactional
// 实现MyBatis-Plus的通用Service实现类,泛型参数一是mapper接口,第二个是用户实体类
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper,UserInfo> implements UserInfoService {

  @Autowired
  UserInfoMapper userInfoMapper;

  /**
      * @Description: 发送验证码实现类
      * @author XiaoLin
      * @date 2021/4/10
      * @Param: [phone]
      * @return cn.linstudy.travel.qo.response.JsonResult
      */
  @Override
  public JsonResult sendVerifyCode(String phone) {
    try {
      String code = VerifyCodeUtils.generateVerifyCode(4);
      SendMessageUtils.sendSms(phone,code);
      userInfoRedisService.setVerifyCode(phone,code);
      return JsonResult.success();
    } catch (Exception e) {
      return JsonResult.error(SystemConstant.CODE_SEND_PHONE_MESSAGE,e.getMessage());
    }
  }

  /**
      * @Description: 用户注册实现类
      * @author XiaoLin
      * @date 2021/4/10
      * @Param: [userInfoRegisterVO]
      * @return cn.linstudy.travel.qo.response.JsonResult
      */
  @Override
  public JsonResult register(UserInfoRegisterVO userInfoRegisterVO) {
    AssertsUtils.hasText(userInfoRegisterVO.getNickname(),"昵称不能为空");
    AssertsUtils.hasText(userInfoRegisterVO.getPassword(),"密码不能为空");
    AssertsUtils.hasText(userInfoRegisterVO.getRepeatPassword(),"再次密码不能为空");
    AssertsUtils.hasText(userInfoRegisterVO.getPhone(),"手机不能为空");
    AssertsUtils.isEquals(userInfoRegisterVO.getPassword(),userInfoRegisterVO.getRepeatPassword(),"两次的密码不一样");
    try {
      // 注册
    userInfoMapper.insert(userInfoRegisterVO);
    }catch (Exception e){
      e.printStackTrace();
    }
    return new JsonResult(SystemConstant.CODE_SUCCESS,SystemConstant.MSG_SUCCESS);
  }

}

5.4、测试

    直接使用swagger进行测试即可。

0 人点赞