文章目录
- 前言
- 第一天
- 一、技术选型
- 二、搭建项目
- 三、引入依赖
- 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的前后端分离的仿照马蜂窝的项目。
后端选用的技术为:
- SpringBoot
- MySQL
- MyBatis-Plus
- Redis
- MongDB
- 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接口,所以我们创建一个配置类来进行配置。
/**
* @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
注解没办法进行赋值。下面贴出初始的工具类。
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、调用工具类发送短信
代码语言: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
枚举类的特点:
- 枚举类构造器是私有的
- 枚举类定义完成之后,枚举类的个数是固定的。
因为防止有些人不按照我们的规定进行拼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进行测试即可。