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

前言

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

第一天

一、技术选型

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

    后端选用的技术为:

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

二、搭建项目

image-20210409192943185

创建文件夹

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

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

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

创建travel-core

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

创建travel-website

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

创建travel-website-api

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

创建travel-mgrsite

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

三、引入依赖

3.1、父目录

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

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>cn.linstudy.travel</groupId>
        <artifactId>travel-core</artifactId>
        <version>1.0</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

    完整的pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.3</version>
    <relativePath/>
  </parent>

  <groupId>cn.linstudy.travel</groupId>
  <artifactId>travel-parent</artifactId>
  <packaging>pom</packaging>
  <version>1.0</version>
  <description>父类,用于导入各种需要的依赖,不写代码</description>
  <modules>
    <module>travel-core</module>
    <module>travel-mgrsite</module>
    <module>travel-website-api</module>
  </modules>

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

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>cn.linstudy.travel</groupId>
        <artifactId>travel-core</artifactId>
        <version>1.0</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

</project>

3.2、travel-core

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

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

<?xml version="1.0" encoding="UTF-8"?>
<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-parent</artifactId>
    <groupId>cn.linstudy.travel</groupId>
    <version>1.0</version>
    <relativePath>../pom.xml</relativePath>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>travel-core</artifactId>
  <description>用于抽取重复的代码:mapper、service、domain</description>

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

  <dependencies>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>

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

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.24</version>
    </dependency>

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

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


    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.9.2</version>
    </dependency>
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.9.2</version>
    </dependency>

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

    <dependency>
      <groupId>com.aliyun.oss</groupId>
      <artifactId>aliyun-sdk-oss</artifactId>
      <version>3.5.0</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.6</version>
    </dependency>

    <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.9.3</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>

  </dependencies>

</project>

3.3、travel-website-api

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

<?xml version="1.0" encoding="UTF-8"?>
<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-parent</artifactId>
    <groupId>cn.linstudy.travel</groupId>
    <version>1.0</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

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

  <dependencies>
    <dependency>
      <groupId>cn.linstudy.travel</groupId>
      <artifactId>travel-core</artifactId>
    </dependency>

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

</project>

3.4、创建配置类

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

/**
 * @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写代码

/**
    * @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;
}

/**
 * @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;

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

/**
 * @Description 用户业务层接口
 * @Author XiaoLin
 * @Date 2021/4/9 14:21
 */
public interface UserInfoService extends IService<UserInfo> { // 继承MyBatis-Plus的通用Service接口,泛型是实体类

}

/**
 * @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中写接口来进行测试。

/**
 * @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进行测试。

image-20210409192727450

4.4、整合Swagger2

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

4.4.1、编写配置文件

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

/**
 * @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

image-20210409192035942

4.4.2、控制器中添加注解,用以扫描
/**
 * @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));
  }
}

image-20210409192744121

image-20210409192832367

五、注册

用户注册

手机注册分析

5.1、校验手机号码合法性

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

$(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包新建一个配置类,专门用于处理跨域请求。

/**
 * @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中),想着不能把阿里云短信的配置信息直接打在代码里面,造成硬编码的问题,所以我想着把他抽取出来,放在配置文件中。

aliyun:
  accessKeyId: "你的阿里云accessKeyId"
  secret: "你的阿里云密钥"

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

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修饰符

@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、调用工具类发送短信

短信流程

/**
      * @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

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

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

}
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、自定义异常
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、统一异常处理

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

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操作,但是原生的断言不适合我们,我们需要重写。

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

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方法。

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代码

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进行测试即可。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟空打码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值