前言

学完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的核心代码管理起来,方便后面的模块调用。

  1. <dependencyManagement>
  2. <dependencies>
  3. <dependency>
  4. <groupId>cn.linstudy.travel</groupId>
  5. <artifactId>travel-core</artifactId>
  6. <version>1.0</version>
  7. </dependency>
  8. </dependencies>
  9. </dependencyManagement>

完整的pom文件:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <parent>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-parent</artifactId>
  9. <version>2.4.3</version>
  10. <relativePath/>
  11. </parent>
  12. <groupId>cn.linstudy.travel</groupId>
  13. <artifactId>travel-parent</artifactId>
  14. <packaging>pom</packaging>
  15. <version>1.0</version>
  16. <description>父类,用于导入各种需要的依赖,不写代码</description>
  17. <modules>
  18. <module>travel-core</module>
  19. <module>travel-mgrsite</module>
  20. <module>travel-website-api</module>
  21. </modules>
  22. <properties>
  23. <maven.compiler.source>11</maven.compiler.source>
  24. <maven.compiler.target>11</maven.compiler.target>
  25. </properties>
  26. <dependencyManagement>
  27. <dependencies>
  28. <dependency>
  29. <groupId>cn.linstudy.travel</groupId>
  30. <artifactId>travel-core</artifactId>
  31. <version>1.0</version>
  32. </dependency>
  33. </dependencies>
  34. </dependencyManagement>
  35. </project>

3.2、travel-core

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

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

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <!--需要先引入父目录-->
  6. <parent>
  7. <artifactId>travel-parent</artifactId>
  8. <groupId>cn.linstudy.travel</groupId>
  9. <version>1.0</version>
  10. <relativePath>../pom.xml</relativePath>
  11. </parent>
  12. <modelVersion>4.0.0</modelVersion>
  13. <artifactId>travel-core</artifactId>
  14. <description>用于抽取重复的代码:mapper、service、domain</description>
  15. <properties>
  16. <maven.compiler.source>11</maven.compiler.source>
  17. <maven.compiler.target>11</maven.compiler.target>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.projectlombok</groupId>
  22. <artifactId>lombok</artifactId>
  23. <optional>true</optional>
  24. </dependency>
  25. <dependency>
  26. <groupId>com.baomidou</groupId>
  27. <artifactId>mybatis-plus-boot-starter</artifactId>
  28. <version>3.4.0</version>
  29. </dependency>
  30. <dependency>
  31. <groupId>com.alibaba</groupId>
  32. <artifactId>druid-spring-boot-starter</artifactId>
  33. <version>1.1.24</version>
  34. </dependency>
  35. <dependency>
  36. <groupId>mysql</groupId>
  37. <artifactId>mysql-connector-java</artifactId>
  38. </dependency>
  39. <dependency>
  40. <groupId>com.alibaba</groupId>
  41. <artifactId>fastjson</artifactId>
  42. <version>1.2.74</version>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-starter-web</artifactId>
  47. <scope>provided</scope>
  48. </dependency>
  49. <dependency>
  50. <groupId>io.springfox</groupId>
  51. <artifactId>springfox-swagger2</artifactId>
  52. <version>2.9.2</version>
  53. </dependency>
  54. <dependency>
  55. <groupId>io.springfox</groupId>
  56. <artifactId>springfox-swagger-ui</artifactId>
  57. <version>2.9.2</version>
  58. </dependency>
  59. <dependency>
  60. <groupId>org.springframework.boot</groupId>
  61. <artifactId>spring-boot-starter-data-redis</artifactId>
  62. </dependency>
  63. <dependency>
  64. <groupId>com.aliyun.oss</groupId>
  65. <artifactId>aliyun-sdk-oss</artifactId>
  66. <version>3.5.0</version>
  67. </dependency>
  68. <dependency>
  69. <groupId>commons-io</groupId>
  70. <artifactId>commons-io</artifactId>
  71. <version>2.6</version>
  72. </dependency>
  73. <dependency>
  74. <groupId>commons-beanutils</groupId>
  75. <artifactId>commons-beanutils</artifactId>
  76. <version>1.9.3</version>
  77. </dependency>
  78. <dependency>
  79. <groupId>org.springframework.boot</groupId>
  80. <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  81. </dependency>
  82. </dependencies>
  83. </project>

3.3、travel-website-api

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

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>travel-parent</artifactId>
  7. <groupId>cn.linstudy.travel</groupId>
  8. <version>1.0</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>travel-website-api</artifactId>
  12. <description>用于处理前端请求的接口</description>
  13. <properties>
  14. <maven.compiler.source>11</maven.compiler.source>
  15. <maven.compiler.target>11</maven.compiler.target>
  16. </properties>
  17. <dependencies>
  18. <dependency>
  19. <groupId>cn.linstudy.travel</groupId>
  20. <artifactId>travel-core</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter-web</artifactId>
  25. </dependency>
  26. </dependencies>
  27. </project>

3.4、创建配置类

在以前传统的开发,我们需要在启动类头上贴MapperScan注解,表示需要扫描mapper接口并且创建代理对象的位置。

    由于我们这次是分模块开发,无法在启动类上扫描Mapper接口,所以我们创建一个配置类来进行配置。

  1. /**
  2. * @Description 配置类
  3. * @Author XiaoLin
  4. * @Date 2021/4/9 14:51
  5. */
  6. @Configuration
  7. @MapperScan(basePackages = "cn.linstudy.travel.mapper")
  8. public class CoreConfig {
  9. }

四、测试

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

4.1、travel-core写代码

  1. /**
  2. * @Description: 用于抽取所有的实体类id
  3. * @author XiaoLin
  4. * @date 2021/4/9
  5. */
  6. public abstract class BaseDomain implements Serializable {
  7. // MyBatis-Plus表示这个是id自增
  8. @ApiModelProperty(value = "主键id")
  9. @TableId(type = IdType.AUTO)
  10. protected Long id;
  11. }
  1. /**
  2. * @Description 用户信息实体类
  3. * @Author XiaoLin
  4. * @Date 2021/4/9 14:18
  5. */
  6. @Setter
  7. @Getter
  8. @TableName("userinfo")
  9. @ApiModel(value = "cn.linstudy.travel.domain",description = "用户信息实体类")
  10. public class UserInfo extends BaseDomain{
  11. public static final int GENDER_SECRET = 0; //保密
  12. public static final int GENDER_MALE = 1; //男
  13. public static final int GENDER_FEMALE = 2; //女
  14. public static final int STATE_NORMAL = 0; //正常
  15. public static final int STATE_DISABLE = 1; //冻结
  16. @ApiModelProperty(value = "昵称")
  17. private String nickname;
  18. @ApiModelProperty(value = "手机")
  19. private String phone;
  20. @ApiModelProperty(value = "手机")
  21. private String email; //邮箱
  22. @JsonIgnore
  23. @ApiModelProperty(value = "密码")
  24. private String password;
  25. @ApiModelProperty(value = "性别")
  26. private Integer gender = GENDER_SECRET;
  27. @ApiModelProperty(value = "用户级别")
  28. private Integer level = 0;
  29. @ApiModelProperty(value = "所在城市")
  30. private String city;
  31. @ApiModelProperty(value = "头像")
  32. private String headImgUrl;
  33. @ApiModelProperty(value = "个性签名")
  34. private String info;
  35. @ApiModelProperty(value = "状态")
  36. private Integer state = STATE_NORMAL;
  37. }
  1. /**
  2. * @Description 用户Mapper
  3. * @Author XiaoLin
  4. * @Date 2021/4/9 14:20
  5. */
  6. public interface UserInfoMapper extends BaseMapper<UserInfo> { // 继承MyBatis-Plus的通用Mapper,泛型是实体类
  7. }
  1. /**
  2. * @Description 用户业务层接口
  3. * @Author XiaoLin
  4. * @Date 2021/4/9 14:21
  5. */
  6. public interface UserInfoService extends IService<UserInfo> { // 继承MyBatis-Plus的通用Service接口,泛型是实体类
  7. }
  1. /**
  2. * @Description 用户业务层接口实现类
  3. * @Author XiaoLin
  4. * @Date 2021/4/9 14:23
  5. */
  6. @Service
  7. @Transactional
  8. // 实现MyBatis-Plus的通用Service实现类,泛型参数一是mapper接口,第二个是用户实体类
  9. public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper,UserInfo> implements UserInfoService {
  10. }

4.2、travel-website-api写接口

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

  1. /**
  2. * @Description
  3. * @Author XiaoLin
  4. * @Date 2021/4/9 14:46
  5. */
  6. @RestController
  7. public class UserInfoController {
  8. @Autowired
  9. UserInfoService userInfoService;
  10. @GetMapping("detail")
  11. // @ApiImplicitParam(name = "id", value = "用户id")
  12. public Object getUser( String id){
  13. return userInfoService.getById(Long.valueOf(id));
  14. }
  15. }

4.3、浏览器访问

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

4.4、整合Swagger2

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

4.4.1、编写配置文件

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

  1. /**
  2. * @Description Swagger配置类
  3. * @Author XiaoLin
  4. * @Date 2021/4/9 17:22
  5. */
  6. @Configuration//表示这是一个配置类
  7. public class SwaggerConfig {
  8. @Bean
  9. public Docket reeateDocket(){
  10. List<Parameter> parameterList=new ArrayList<>();
  11. ParameterBuilder parameterBuilder=new ParameterBuilder();
  12. parameterBuilder.name("token")
  13. .description("swagger调试用,模拟传入用户凭证")
  14. .modelRef(new ModelRef("String"))
  15. .parameterType("header").required(false);
  16. parameterList.add(parameterBuilder.build());
  17. return new Docket(DocumentationType.SWAGGER_2)
  18. .apiInfo(apiInfo())//创建该Api的基本信息(这些基本信息会展现在文档页面中)
  19. .select()//函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger ui来展现
  20. .apis(RequestHandlerSelectors.basePackage("cn.linstudy.travel.controller"))//指定需要扫描的包路路径
  21. .paths(PathSelectors.any())
  22. .build()
  23. .globalOperationParameters(parameterList)
  24. ;
  25. }
  26. //配置swagger的信息
  27. private ApiInfo apiInfo(){
  28. return new ApiInfoBuilder()
  29. .title("SpringBoot-Travel项目实战")
  30. .description("接口")
  31. .termsOfServiceUrl("")
  32. .version("1.0")
  33. .build();
  34. }
  35. }

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

4.4.2、控制器中添加注解,用以扫描

  1. /**
  2. * @Description
  3. * @Author XiaoLin
  4. * @Date 2021/4/9 14:46
  5. */
  6. @RestController
  7. @Api(tags = "用户相关接口")
  8. public class UserInfoController {
  9. @Autowired
  10. UserInfoService userInfoService;
  11. @ApiOperation(value = "根据id查询用户")
  12. @GetMapping("detail")
  13. @ApiImplicitParam(name = "id", value = "用户id")
  14. public Object getUser( String id){
  15. return userInfoService.getById(Long.valueOf(id));
  16. }
  17. }

五、注册

5.1、校验手机号码合法性

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

  1. $(function () {
  2. $('#_js_loginBtn').click(function () {
  3. var val = $('#inputPassword').val();
  4. //js 正则表达语法:
  5. // / /g : 正则表达式对象
  6. // ^1 以1开头
  7. // \d 数字 0-9 数字中一个
  8. // {10} 重复个数 \d{10} 表示10个数字
  9. // $ 以xx结束
  10. // [3456789] 代码 3 4 5 6 7 8 9 中一个数
  11. // 正则表达式校验
  12. 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)) {
  13. // 如果匹配的话就发请求到后台校验手机号是否注册过
  14. $.get(domainUrl + "/users/checkPhone", {phone:val}, function (data) {
  15. if(!data){
  16. $('#inputPassword').next().text('').hide()
  17. $('.login-box').hide()
  18. $('.signup-box').show()
  19. $("#phone").val(val);
  20. }else{
  21. $('#inputPassword').next().text('手机号码已注册.').show()
  22. }
  23. })
  24. } else {
  25. // 匹配不通过表示手机号格式不正确
  26. $('#inputPassword').next().text('手机号码格式不正确').show()
  27. }
  28. });

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

5.2.1、技术难点分析

5.2.2、配置跨域

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

  1. /**
  2. * @Description 解决跨域配置类
  3. * @Author XiaoLin
  4. * @Date 2021/4/9 20:31
  5. */
  6. @Configuration
  7. public class WebConfigurer implements WebMvcConfigurer {
  8. @Bean
  9. public WebConfigurer corsConfigurer() {
  10. return new WebConfigurer() {
  11. @Override
  12. //重写父类提供的跨域请求处理的接口
  13. public void addCorsMappings(CorsRegistry registry) {
  14. //添加映射路径
  15. registry.addMapping("/**")
  16. //放行哪些原始域
  17. .allowedOriginPatterns("*")
  18. //是否发送Cookie信息
  19. .allowCredentials(true)
  20. //放行哪些原始域(请求方式)
  21. .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
  22. //放行哪些原始域(头部信息)
  23. .allowedHeaders("*")
  24. //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
  25. .exposedHeaders("Header1", "Header2");
  26. }
  27. };
  28. }
  29. }

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

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

  1. aliyun:
  2. accessKeyId: "你的阿里云accessKeyId"
  3. secret: "你的阿里云密钥"

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

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

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

  1. @PropertySource(value = "classpath:application-core.yml")
  2. @Component
  3. public class SendMessageUtils {
  4. private static String accessKeyId; // TODO 修改成自己的
  5. private static String accessKeySecret; // TODO 修改成自己的
  6. @Value("${aliyun.accessKeyId}")
  7. public void setAccessKeyId(String accessKeyId) {
  8. SendMessageUtils.accessKeyId = accessKeyId;
  9. }
  10. @Value("${aliyun.secret}")
  11. public void setAccessKeySecret(String secret) {
  12. SendMessageUtils.accessKeySecret = secret;
  13. }
  14. //产品名称:云通信短信API产品,开发者无需替换
  15. static final String product = "Dysmsapi";
  16. //产品域名,开发者无需替换
  17. static final String domain = "dysmsapi.aliyuncs.com";
  18. public static SendSmsResponse sendSms(String telephone, String code) throws ClientException {
  19. //可自助调整超时时间
  20. System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
  21. System.setProperty("sun.net.client.defaultReadTimeout", "10000");
  22. System.out.println(1/0);
  23. //初始化acsClient,暂不支持region化
  24. IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
  25. DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
  26. IAcsClient acsClient = new DefaultAcsClient(profile);
  27. //组装请求对象-具体描述见控制台-文档部分内容
  28. SendSmsRequest request = new SendSmsRequest();
  29. //必填:待发送手机号
  30. request.setPhoneNumbers(telephone);
  31. //必填:短信签名-可在短信控制台中找到
  32. request.setSignName("XiaoLin"); // TODO 修改成自己的
  33. //必填:短信模板-可在短信控制台中找到
  34. request.setTemplateCode("SMS_213078152"); // TODO 修改成自己的
  35. //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
  36. // request.setTemplateParam("{\"name\":\"Tom\", \"code\":\"123\"}");
  37. request.setTemplateParam("{\"code\":\"" + code + "\"}");
  38. //选填-上行短信扩展码(无特殊需求用户请忽略此字段)
  39. //request.setSmsUpExtendCode("90997");
  40. //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
  41. // request.setOutId("yourOutId");
  42. //hint 此处可能会抛出异常,注意catch
  43. SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
  44. if (sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) {
  45. System.out.println("短信发送成功!");
  46. } else {
  47. System.out.println("短信发送失败!");
  48. }
  49. return sendSmsResponse;
  50. }
  51. }

5.4、调用工具类发送短信

  1. /**
  2. * @Description: 发送验证码实现类
  3. * @author XiaoLin
  4. * @date 2021/4/10
  5. * @Param: [phone]
  6. * @return cn.linstudy.travel.qo.response.JsonResult
  7. */
  8. @Override
  9. public JsonResult sendVerifyCode(String phone) {
  10. try {
  11. String code = VerifyCodeUtils.generateVerifyCode(4);
  12. System.out.println("发送了短信");
  13. SendMessageUtils.sendSms(phone,code);
  14. userInfoRedisService.setVerifyCode(phone,code);
  15. return JsonResult.success();
  16. } catch (Exception e) {
  17. return JsonResult.error(SystemConstant.CODE_SEND_PHONE_MESSAGE,e.getMessage());
  18. }
  19. }

5.5、将验证码放入Redis

5.5.1、使用枚举重写Redis的key

枚举类的特点:

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

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

  1. package cn.linstudy.travel.redis;
  2. /**
  3. * @Description
  4. * @Author XiaoLin
  5. * @Date 2021/4/10 20:03
  6. */
  7. @Getter
  8. public enum RedisKeyEnum {
  9. // 用户注册验证码 key 实例对象
  10. ENUM_VERYFY_CODE("veryfy_code",SystemConstant.VERIFY_CODE_VAI_TIME*60L);
  11. // 前缀
  12. private String prefix;
  13. // 有效时长
  14. private Long time;
  15. RedisKeyEnum(String prefix, long time) {
  16. this.prefix = prefix;
  17. this.time = time;
  18. }
  19. // 拼接key
  20. public String join(String... values){
  21. StringBuilder sb = new StringBuilder();
  22. sb.append(this.prefix);
  23. for (String value : values) {
  24. sb.append(":").append(value);
  25. }
  26. return sb.toString();
  27. }
  28. }
  1. package cn.linstudy.travel.redis.service;
  2. /**
  3. * @Description 用户缓存的业务类
  4. * @Author XiaoLin
  5. * @Date 2021/4/10 9:13
  6. */
  7. public interface UserInfoRedisService {
  8. /**
  9. * 将验证码设置到redis中
  10. * @param phone
  11. * @param code
  12. */
  13. void setVerifyCode(String phone, String code);
  14. }
  1. package cn.linstudy.travel.redis.service.impl;
  2. /**
  3. * @Description
  4. * @Author XiaoLin
  5. * @Date 2021/4/10 9:18
  6. */
  7. @Service
  8. public class UserInfoRedisServiceImpl implements UserInfoRedisService {
  9. @Autowired
  10. private StringRedisTemplate template;
  11. @Override
  12. public void setVerifyCode(String phone, String code) {
  13. String key = RedisKeyEnum.ENUM_VERYFY_CODE.join(phone);
  14. String value = code;
  15. // 将验证码放入Redis,并且设置时效
  16. template.opsForValue().set(key,value, RedisKeyEnum.ENUM_VERYFY_CODE.getTime(), TimeUnit.SECONDS);
  17. }
  18. }

5.6、参数校验

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

5.6.1、自定义异常

  1. package cn.linstudy.travel.exception;
  2. /**
  3. * @Description 自定义异常
  4. * @Author XiaoLin
  5. * @Date 2021/4/10 10:34
  6. */
  7. public class LogicException extends RuntimeException{
  8. // 标记非系统异常
  9. public LogicException(String message) {
  10. super(message);
  11. }
  12. }

5.6.2、统一异常处理

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

  1. package cn.linstudy.travel.advice;
  2. /**
  3. 通用异常处理类
  4. * ControllerAdvice controller类功能增强注解, 动态代理controller类实现一些额外功能
  5. * 请求进入controller映射方法之前做功能增强: 经典用法:日期格式化
  6. * 请求进入controller映射方法之后做功能增强: 经典用法:统一异常处理
  7. * @Author XiaoLin
  8. * @Date 2021/4/10 19:17
  9. */
  10. public class CommonExceptionHandler {
  11. //这个方法定义的跟映射方法操作一样
  12. @ExceptionHandler(LogicException.class)
  13. @ResponseBody
  14. public Object LogicException(Exception e, HttpServletResponse resp) {
  15. e.printStackTrace();
  16. resp.setContentType("application/json;charset=utf-8");
  17. return JsonResult.error(SystemConstant.CODE_ERROR_PARAM, e.getMessage(), null);
  18. }
  19. @ExceptionHandler(RuntimeException.class)
  20. @ResponseBody
  21. public Object RuntimeException(Exception e, HttpServletResponse resp) {
  22. e.printStackTrace();
  23. resp.setContentType("application/json;charset=utf-8");
  24. return JsonResult.defaultError();
  25. }
  26. }

5.6.1、断言

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

  1. public class AssertsUtils {
  2. private AssertsUtils() {
  3. }
  4. /**
  5. * @return void
  6. * @Description: 判断指定的参数是否为null,或者空串,如果为空抛出异常
  7. * @author XiaoLin
  8. * @date 2021/4/10
  9. * @Param: [text, message]
  10. */
  11. public static void hasText(String text, String message) {
  12. if (!StringUtils.hasText(text)) {
  13. throw new LogicException(message);
  14. }
  15. }
  16. /**
  17. * @Description: 判断传入的参数是否相等
  18. * @author XiaoLin
  19. * @date 2021/4/10
  20. * @Param: [param1, param2, message]
  21. * @return void
  22. */
  23. public static void isEquals(String param1, String param2, String message) {
  24. if (param1 == null || param2 == null) {
  25. throw new LogicException("传入的参数为空");
  26. }
  27. if (!param1.equals(param2)) {
  28. throw new LogicException(message);
  29. }
  30. }
  31. }

5.7、封装VO

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

  1. package cn.linstudy.travel.vo;
  2. /**
  3. * @Description 用户注册提交表单的VO
  4. * @Author XiaoLin
  5. * @Date 2021/4/10 9:48
  6. */
  7. @Data
  8. @AllArgsConstructor
  9. @NoArgsConstructor
  10. @TableName("userinfo")
  11. public class UserInfoRegisterVO extends BaseDomain {
  12. @ApiModelProperty(value = "昵称")
  13. private String nickname;
  14. @ApiModelProperty(value = "密码")
  15. private String password;
  16. // 这个字段不会映射到数据库中
  17. @TableField(exist = false)
  18. @ApiModelProperty(value = "确认密码")
  19. private String repeatPassword;
  20. @ApiModelProperty(value = "手机")
  21. private String phone;
  22. }

5.8、重写mapper的insert方法

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

  1. package cn.linstudy.travel.mapper;
  2. /**
  3. * @Description 用户Mapper
  4. * @Author XiaoLin
  5. * @Date 2021/4/9 14:20
  6. */
  7. public interface UserInfoMapper extends BaseMapper<UserInfo> {// 继承MyBatis-Plus的通用Mapper,泛型是实体类
  8. @Insert("insert into userinfo( nickname, phone, password) values (#{nickname},#{phone},#{password})")
  9. void insert(UserInfoRegisterVO userInfoRegisterVO);
  10. }

5.9、完整ServiceImpl代码

  1. package cn.linstudy.travel.service.impl;
  2. import cn.linstudy.travel.constant.SystemConstant;
  3. import cn.linstudy.travel.domain.UserInfo;
  4. import cn.linstudy.travel.exception.LogicException;
  5. import cn.linstudy.travel.mapper.UserInfoMapper;
  6. import cn.linstudy.travel.qo.response.JsonResult;
  7. import cn.linstudy.travel.redis.service.UserInfoRedisService;
  8. import cn.linstudy.travel.service.UserInfoService;
  9. import cn.linstudy.travel.utils.AssertsUtils;
  10. import cn.linstudy.travel.utils.SendMessageUtils;
  11. import cn.linstudy.travel.utils.VerifyCodeUtils;
  12. import cn.linstudy.travel.vo.UserInfoRegisterVO;
  13. import com.aliyuncs.exceptions.ClientException;
  14. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  15. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  16. import javax.security.auth.login.LoginException;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.stereotype.Service;
  19. import org.springframework.transaction.annotation.Transactional;
  20. /**
  21. * @Description 用户业务层接口实现类
  22. * @Author XiaoLin
  23. * @Date 2021/4/9 14:23
  24. */
  25. @Service
  26. @Transactional
  27. // 实现MyBatis-Plus的通用Service实现类,泛型参数一是mapper接口,第二个是用户实体类
  28. public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper,UserInfo> implements UserInfoService {
  29. @Autowired
  30. UserInfoMapper userInfoMapper;
  31. /**
  32. * @Description: 发送验证码实现类
  33. * @author XiaoLin
  34. * @date 2021/4/10
  35. * @Param: [phone]
  36. * @return cn.linstudy.travel.qo.response.JsonResult
  37. */
  38. @Override
  39. public JsonResult sendVerifyCode(String phone) {
  40. try {
  41. String code = VerifyCodeUtils.generateVerifyCode(4);
  42. SendMessageUtils.sendSms(phone,code);
  43. userInfoRedisService.setVerifyCode(phone,code);
  44. return JsonResult.success();
  45. } catch (Exception e) {
  46. return JsonResult.error(SystemConstant.CODE_SEND_PHONE_MESSAGE,e.getMessage());
  47. }
  48. }
  49. /**
  50. * @Description: 用户注册实现类
  51. * @author XiaoLin
  52. * @date 2021/4/10
  53. * @Param: [userInfoRegisterVO]
  54. * @return cn.linstudy.travel.qo.response.JsonResult
  55. */
  56. @Override
  57. public JsonResult register(UserInfoRegisterVO userInfoRegisterVO) {
  58. AssertsUtils.hasText(userInfoRegisterVO.getNickname(),"昵称不能为空");
  59. AssertsUtils.hasText(userInfoRegisterVO.getPassword(),"密码不能为空");
  60. AssertsUtils.hasText(userInfoRegisterVO.getRepeatPassword(),"再次密码不能为空");
  61. AssertsUtils.hasText(userInfoRegisterVO.getPhone(),"手机不能为空");
  62. AssertsUtils.isEquals(userInfoRegisterVO.getPassword(),userInfoRegisterVO.getRepeatPassword(),"两次的密码不一样");
  63. try {
  64. // 注册
  65. userInfoMapper.insert(userInfoRegisterVO);
  66. }catch (Exception e){
  67. e.printStackTrace();
  68. }
  69. return new JsonResult(SystemConstant.CODE_SUCCESS,SystemConstant.MSG_SUCCESS);
  70. }
  71. }

5.4、测试

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

SpringBoot的旅游项目——day01(学习记录附赠源码)的更多相关文章

  1. JUC.Lock(锁机制)学习笔记[附详细源码解析]

    锁机制学习笔记 目录: CAS的意义 锁的一些基本原理 ReentrantLock的相关代码结构 两个重要的状态 I.AQS的state(int类型,32位) II.Node的waitStatus 获 ...

  2. JUC.Condition学习笔记[附详细源码解析]

    目录 Condition的概念 大体实现流程 I.初始化状态 II.await()操作 III.signal()操作 3个主要方法 Condition的数据结构 线程何时阻塞和释放 await()方法 ...

  3. SpringBoot学习入门之Hello项目的构建、单元测试和热部署等(配图文,配置信息详解,附案例源码)

    前言: 本文章主要是个人在学习SpringBoot框架时做的一些准备,参考老师讲解进行完善对SpringBoot构建简单项目的学习汇集成本篇文章,作为自己对SpringBoot框架的总结与笔记. 你将 ...

  4. winserver的consul部署实践与.net core客户端使用(附demo源码)

    winserver的consul部署实践与.net core客户端使用(附demo源码)   前言 随着微服务兴起,服务的管理显得极其重要.都知道微服务就是”拆“,把臃肿的单块应用,拆分成多个轻量级的 ...

  5. Spring Boot整合ElasticSearch和Mysql 附案例源码

    导读 前二天,写了一篇ElasticSearch7.8.1从入门到精通的(点我直达),但是还没有整合到SpringBoot中,下面演示将ElasticSearch和mysql整合到Spring Boo ...

  6. OAuth2学习及DotNetOpenAuth部分源码研究

    OAuth2学习及DotNetOpenAuth部分源码研究 在上篇文章中我研究了OpenId及DotNetOpenAuth的相关应用,这一篇继续研究OAuth2. 一.什么是OAuth2 OAuth是 ...

  7. memcached学习笔记——存储命令源码分析上篇

    原创文章,转载请标明,谢谢. 上一篇分析过memcached的连接模型,了解memcached是如何高效处理客户端连接,这一篇分析memcached源码中的process_update_command ...

  8. Sping学习笔记(一)----Spring源码阅读环境的搭建

    idea搭建spring源码阅读环境 安装gradle Github下载Spring源码 新建学习spring源码的项目 idea搭建spring源码阅读环境 安装gradle 在官网中下载gradl ...

  9. mybatis源码学习(一) 原生mybatis源码学习

    最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...

随机推荐

  1. 水墨屏开发设备,旧 Kindle 改造而成

    原文地址:Turning an old Amazon Kindle into a eink development platform 原文作者:adq 译者 & 校正:HelloGitHub- ...

  2. Prism.WPF -- Prism框架使用(下)

    本文参考Prism官方示例 命令使用 Prism提供了两种命令:DelegateCommand和CompositeCommand. DelegateCommand DelegateCommand封装了 ...

  3. Windows 环境下搭建 RocketMQ

    Apache 官网: http://rocketmq.apache.org/ RocketMQ 的 Github 地址: English:https://github.com/apache/rocke ...

  4. 这是你没见过的不一样的redis

    转: 这是你没见过的不一样的redis 提到Redis,大家一定会想到的几个点是什么呢? 高并发,KV存储,内存数据库,丰富的数据结构,单线程(6版本之前) 那么,接下来,上面提到的这些,都会一一给大 ...

  5. Chome 88如何正确隐藏 webdriver?

    从 Chrome 88开始,它的 V8 引擎升级了,一些接口发生了改变. 使用 Selenium 调用 Chrome 的时候,只需要增加一个配置参数: chrome_options.add_argum ...

  6. 大数据实战-Hive-技巧实战

    1.union 和 union all 前者可以去重 select sex,address from test where dt='20210218' union all select sex,add ...

  7. C# 基础 - linq 举例应用

    public class Player { public string Id { get; set; } public string Name { get; set; } public int Age ...

  8. web之面试常问问题:如何实现水平垂直居中?

    前提准备,在HTML页面中定义一个div,div中内容自定义. <div class="box sc">致我们呼啸而过的青春</div> 样式: div.b ...

  9. C指针与二维数组

    先贴上完整的代码: #include<stdio.h> int main(int argc, char *argv[]){ int a[3] [5]={1,2,3,4,5,6,7,8,9, ...

  10. 云原生的弹性 AI 训练系列之一:基于 AllReduce 的弹性分布式训练实践

    引言 随着模型规模和数据量的不断增大,分布式训练已经成为了工业界主流的 AI 模型训练方式.基于 Kubernetes 的 Kubeflow 项目,能够很好地承载分布式训练的工作负载,业已成为了云原生 ...