SpringBoot其实不是新框架,而是默认配置了很多框架的使用方式。就像maven整合了所有jar包,Springboot整合了所有框架,并通过一行简单的main方法启动应用。
继承了spring的框架们:
电商秒杀应用简介:
商品列表页获取秒杀商品列表
进入商品详情页获取秒杀商品详情
秒杀开始后进入下单确认页下单并支付成功
项目实战:
使用IDEA+Maven搭建SpringBoot开发环境
集成Mybatis操作数据库
实现秒杀项目
第2章·基础项目搭建的基本流程:
构建Maven项目—>引入SpringBoot依赖—>接入Mybatis
2.1 使用IDEA创建maven项目
new->project->maven项目->选择maven-archetype-quickstart
1.导入springboot相关配置:
https://blog.csdn.net/m0_37657841/article/details/90524410
https://blog.csdn.net/midnight_time/article/details/90717676
跟着做完后输入8080出现了一下页面说明springboot工程在不需要任何外力下内嵌了tomcat容器
2.SpringMVC+RESTful的使用:
???有点失败,网页没有出来,依旧是上面那样。没有报错也不知为啥。
***************************
APPLICATION FAILED TO START
***************************
Description:
Web server failed to start. Port 8080 was already in use.
----------------------------------------------------------------
看到报错了。。。8080已经被用了??之前tomcat不是都关了吗。。
注:8080 是默认端口,如果要使用其他端口,可以在res下面写 application.properties 修改,如:server.port=8090
然后我把idea停止运行再试就成功了。。。。所以是自己占了自己??晕
好了,还是挺神奇的,都没有在index.jsp上面写对应到方法的requestmapping?(不过简单的显示确实不需要页面。。。
3.接入Mybatis(看安利视频的博主图文并茂的博客链接)
mybatis竟然有自动生成mapping.xml文件的插件。。。
创建数据库,填写mybatis-generator.xml,这个xml里面指定mybatis插件连接数据库和让插件生成什么文件生到哪里。
运行Mybatis插件,自动生成相关文件
就还挺神奇的,实体类(对应表),DAO接口(写方法)和mapping.xml文件(sql语句),这3个都不需要手动编写了。
只需要把包写好就行,比如实体类老师先写好了包dataobject,然后改生成文件中的包的路径com.miaoshaproject.dataobject
然后写生成对应表及类名
运行run:mybatis_generator
然后application.properties中配置SpringBoot项目数据源
------------------------------------------------------------------------------------------------------------------
编写测试的时候@MapperScan注解报红??
找半天后发现pom文件里mybatis的包引错了。。。
要用mybatis-springboot
<!--5、引入的第五个:Mybatis相关的jar包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
------------------------------------------------------------------------------------------------------
然后要运行app这个,换过来。千万运行之前那个generator的maven
网友总结了所有代码,但没有详细注解说明:
https://www.cnblogs.com/victorbu/p/10538615.html
输入8090端口可以看到了:
然后手动网数据库插入一条数据:
显示数据库中找到的用户的名字
以上就是第二章基础搭建,springboot接入mybatis,编写generator.xml让插件自动生成实体类,dao接口和映射文件。
第三章 用户模块开发
上一章,我们学会了:
构建Maven项目
引入SpringBoot
引入Mybatis
简单使用SpringMVC
https://blog.csdn.net/midnight_time/article/details/91048543
https://blog.csdn.net/m0_37657841/article/details/90545984
接下来就要详细的学习使用SpringMVC了,具体知识点有:
1.MVC分层架构
之前学的都是数据库查询到的一行实体对象数据直接返回到前端,但实际开发中不可以直接返回数据库的数据,需要service层有一个model掩盖一下。
password属性也加到model里面。 model层才是真正处理业务核心层,而dataobject实体类仅仅只是对数据库的映射。
但是model也不需要全都返回给前端,所以在cotroller层再增加一个viewobject层。
由cotroller层的@requestbody和@requestparm在页面展示了json数据
1. DAO层 --- dataobject,与数据库一一映射
2. Service层 --- model,整合因业务需求而分离的dataobject
3. Controller层 --- viewobject,屏蔽如密码等敏感信息返回给前端
2.通用返回类型
Controller中的方法返回值类型会出现不同,比如返回viewobject,返回null,返回异常Exception信息等等。如果每个方法都有独自的返回值类型的话,那样的设计不太好,也不容易以统一的形式(比如统一的JSON格式)呈现给前端。好的解决方法就是独立出一层:response,创建一种通用的返回值类型CommonReturnType,它有两个属性status和data。
1.增加一个response包。创建CommonReturnType类给他统一返回类型。
public class CommonReturnType { //表明对应请求的返回结果 success fail private String status; //若status=success,则data内返回前端需要的json数据 //若status=fail,则data内使用通用的错误码格式 private Object data; // public CommonReturnType(String status, Object data) {//这才是构造方法 // this.status = status; // this.data = data; // } //定义一个通用的创建方法 是自己写的方法 不是构造方法 那为什么不用构造方法呢?因为他这个可以返回值! public static CommonReturnType create(Object result){//传入对象或基本数据类型都可以 return create(result, "success");//调用下面这个重载的方法,传入的都依然返回 但是多设置了一个status } public static CommonReturnType create(Object result, String status){ CommonReturnType type = new CommonReturnType(); type.setStatus(status); type.setData(result); return type; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }
修改cotroller的返回值为这个,则可以看到浏览器返回多了个status和data 统一的返回类型
(学MVC时那个前端控制器和视图控制器可以自动对应success.jsp,fail.jsp也很好用。。这里springboot好像没有web.xml配置这些了??
定义通用的返回对象——返回错误信息
1.创建error包
2.创建commonError接口
public interface CommonError { public int getErrCode(); public String getErrMsg(); public CommonError setErrMsg(String errMsg);
3.创建接口的实现类为枚举类(就可以直接写定义),因为异常有ErrorCode和ErrorMsg两个属性,因此我们将异常封装一下抽象到一个枚举类EmBusinessError中
//实现类是一个枚举类 这个类可以传给cotroller里面捕获异常相关的继承exception的新建一个BusinessException类 public enum EmBusinessError implements CommonError { //通用错误类型00001 PARAMETER_VALIDATION_ERROR(00001, "参数不合法"), //10000开头为用户信息相关错误定义 USER_NOT_EXIST(10001, "用户不存在") //下面还可以继续方便增加枚举的错误信息 代码修改可用性高 ; private int errCode; private String errMsg; EmBusinessError(int errCode, String errMsg) { this.errCode = errCode; this.errMsg = errMsg; } @Override public int getErrCode() { return this.errCode; } @Override public String getErrMsg() { return this.errMsg; } @Override public CommonError setErrMsg(String errMsg) { this.errMsg = errMsg; return this; } }
使用这个类就是,这个类可以传给cotroller里面捕获异常相关的继承exception的新建一个BusinessException类
//包装器业务异常实现 和em类一样实现接口,且都可以通过set方法覆盖掉原来的接口对象 public class BusinessException extends Exception implements CommonError { private CommonError commonError; //直接接收EmBusinessError的传参用于构造业务异常(?怎么传啊?这个参数就是来自em实现类?还是里面的set方法返回值? // 应该是后者em的set方法返回值传进这里面初始化) public BusinessException(CommonError commonError) { super();//有一些Exception自身初始化的机制在里面 this.commonError = commonError; } //接收自定义errMsg的方式构造业务异常 public BusinessException(CommonError commonError, String errMsg) { super(); this.commonError = commonError; this.commonError.setErrMsg(errMsg); } @Override public int getErrCode() { return this.commonError.getErrCode(); } @Override public String getErrMsg() { return this.commonError.getErrMsg(); } @Override public CommonError setErrMsg(String errMsg) { this.commonError.setErrMsg(errMsg); return this; } }
然后就可以在controller里面抛出了,用法是这样的,参数直接传写好的枚举就可以!!省事儿!
//若获取的对应用户信息不存在 if (userModel == null) { throw new BusinessException(EmBusinessError.USER_NOT_EXIST); }
但不管是直接throw的业务异常或者JVM虚拟机抛出的运行时异常,仅仅是抛到了Tomcat的容器层,我们在Controller层并不能进行处理。所以接着来
4.定义@exceptionHandler解决未被controller层吸收的exception
像处理异常这种工作,不只是UserController要用到,以后处理其他模块也有可能有异常,因此将代码提取到新建一个BaseController中,让UserController继承BaseController,这样以后扩展其他模块时,代码就可以复用了。
@Controller("user") @RequestMapping("/user") public class UserController extends BaseController{ @Autowired private UserService userService; @RequestMapping("/get") @ResponseBody public CommonReturnType getUser(@RequestParam(name = "id") Integer id) throws BusinessException { //调用service服务获取对应id的用户对象并返回给前端 UserModel userModel = userService.getUserById(id); //若获取的对应用户信息不存在 if (userModel == null) { // userModel.setEncrptPassword("123"); throw new BusinessException(EmBusinessError.USER_NOT_EXIST); } //将核心领域模型用户对象转化为可供UI使用的viewobject UserVO userVO = convertFromModel(userModel); //返回通用对象 return CommonReturnType.create(userVO); } //把model转成VO 主要是为了调用service方法生成的是model,但我们需要VO类 private UserVO convertFromModel(UserModel userModel){ if(userModel == null){ return null; } UserVO userVO = new UserVO(); BeanUtils.copyProperties(userModel,userVO); return userVO; } }
以上统一了返回类型和错误信息
完成了基础能力建设,接下来进入模型能力管理。功能方面了
用户信息管理:
otp短信获取
otp注册用户
用户手机登录
3.otp(一次性密码)验证码生成
这里是后台生成随机数发短信给用户,跟之前学的网页上生成随机数让用户填写不太一样
//用户获取!!otp短信方法 @RequestMapping("/getotp") @ResponseBody public CommonReturnType getOtp(@RequestParam(name="telphone")String telphone){ //需要按照一定的规则生成OTP验证码 就是单纯的Random随机数 Random random = new Random(); int randomInt = random.nextInt(99999);//此时随机数取值[0,99999) randomInt+=10000;//此时取值[10000,109999) String otpCode = String.valueOf(randomInt); //将OTP验证码同对应用户的手机号关联 企业级是使用Redis来实现的,(键值对形式的数据库,反复点击可以反复覆盖) // 但是目前这个项目属于入门级别没讲到分布式,暂时用httpServletRequest对象获取session方式来绑定手机号与验证码。 request.getSession().setAttribute(telphone,otpCode);//就这样做关联了 //将OTP验证码通过短信通道发送给用户,省略(有兴趣可买第三方服务的短信通道) System.out.println("telphone = "+telphone+"&otpCode ="+otpCode); return CommonReturnType.create(null); }
控制台打印了手机号和绑定的验证码:
3.6 用户模型管理——Metronic模板简介
采用前后端分离的思想,建立一个html文件夹,引入static文件夹(下载资料里)
前端文件保存在本地的哪个盘下都可以,因为是通过ajax来异步获取接口
getotp.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>GetOtp</title> <script src = "static/assets/global/plugins/jquery-1.11.0.min.js" type="text/javascript"></script> <link href="static/assets/global/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css"/> <link href="static/assets/global/plugins/css/component.css" rel="stylesheet" type="text/css"/> <link href="static/assets/admin/pages/css/login.css" rel="stylesheet" type="text/css"/> </head> <body class="login"> <div class="content"> <h3 class="form-title">获取otp信息</h3> <div class="form-group"> <label class="control-label">手机号</label> <div> <input class="form-control" type="text" placeholder="手机号" name="telphone" id="telphone"/> </div> </div> <div class="form-actions"> <button class="btn blue" id="getotp" type="submit"> 获取otp短信 </button> </div> </div> </body> <script> jQuery(document).ready(function () { //绑定otp的click事件用于向后端发送获取手机验证码的请求 $("#getotp").on("click",function () { var telphone=$("#telphone").val(); if (telphone==null || telphone=="") { alert("手机号不能为空"); return false; } //映射到后端@RequestMapping(value = "/getotp", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED}) $.ajax({ type:"POST", contentType:"application/x-www-form-urlencoded", url:"http://localhost:8090/user/getotp", data:{ "telphone":$("#telphone").val(), }, success:function (data) { if (data.status=="success") { alert("otp已经发送到了您的手机,请注意查收"); }else { alert("otp发送失败,原因为" + data.data.errMsg); } }, error:function (data) { alert("otp发送失败,原因为"+data.responseText); } }); }); }); </script> </html>
这里的$#是取地址去id取元素 好像不是jsp的el表达式
但是有跨域请求出错,因为html是本地文件的端口,服务器是主机端口。跨域请求错误,只需要在UserController类上加一个注解@CrossOrigin
即可
控制台打印成功
4.登录、注册功能
3.9 用户模型管理——用户注册功能实现
1.实现方法:conttoller中用户注册方法,也是通用返回类型给前端,就是数据加状态,不返回就create方法传进null。
// 用户注册方法 @RequestMapping(value = "/register", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED}) @ResponseBody //要从请求体拿的参数就都要写出 public CommonReturnType register(@RequestParam(name = "telphone") String telphone, @RequestParam(name = "otpCode") String otpCode, @RequestParam(name = "name") String name, @RequestParam(name = "gender") String gender, @RequestParam(name = "age") String age, @RequestParam(name = "password") String password) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException { //验证手机号和对应的otpCode相符合 String inSessionOtpCode = (String) this.request.getSession().getAttribute(telphone);//找到session里的otp(用户提交的手机号必须一致才能找到) if (!com.alibaba.druid.util.StringUtils.equals(otpCode, inSessionOtpCode)) {throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "短信验证码不符合"); }//符合的话往下走 给数据库传入用户前端填写的数据 所以要去业务层写方法 //用户的注册流程 UserModel userModel = new UserModel(); userModel.setName(name); userModel.setAge(Integer.valueOf(age)); userModel.setGender(Byte.valueOf(gender)); userModel.setTelphone(telphone); userModel.setRegisterMode("byphone"); //密码加密才能传入数据库(?。。 userModel.setEncrptPassword(this.EncodeByMd5(password)); userService.register(userModel); return CommonReturnType.create(null); } //密码加密 public String EncodeByMd5(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException { //确定计算方法 MessageDigest md5 = MessageDigest.getInstance("MD5"); BASE64Encoder base64en = new BASE64Encoder(); //加密字符串 String newstr = base64en.encode(md5.digest(str.getBytes("utf-8"))); return newstr; }
要做校验在传入数据库之前判断各个属性的数据是否为空,在serviceimpl里面加注册register方法。
@Override @Transactional public void register(UserModel userModel) throws BusinessException { if (userModel == null) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } if (StringUtils.isEmpty(userModel.getName()) //因为字符串类型不能用==null判断所以用StringUtils.isEmpty方法(依赖的?) || userModel.getGender() == null || userModel.getAge() == null || StringUtils.isEmpty(userModel.getTelphone())) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } //实现传入的model->dataobject方法 下面写一个转换方法 这些框架以后也有用 UserDO userDO = convertFromModel(userModel); userDOMapper.insertSelective(userDO);//这个好像之前也学过我还测试来着(学框架的时候在哪里???找不着了。。。) // 就是如果传入了null是不会覆盖之前有数据的值。如果是insert就会覆盖了。 UserPasswordDO userPasswordDO = convertPasswordFromModel(userModel); userPasswordDOMapper.insertSelective(userPasswordDO); return; } //model->dataobject的转换方法 private UserDO convertFromModel(UserModel userModel) { if (userModel == null) { return null; } UserDO userDO = new UserDO(); BeanUtils.copyProperties(userModel, userDO); return userDO; } //model的password->dataobject的password转换方法 private UserPasswordDO convertPasswordFromModel(UserModel userModel) { if (userModel == null) { return null; } UserPasswordDO userPasswordDO = new UserPasswordDO(); userPasswordDO.setEncrptPassword(userModel.getEncrptPassword()); userPasswordDO.setUserId(userModel.getId()); return userPasswordDO; }
引入做输入校验的依赖(apache.lang包) StringUtils用的这个包
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.7</version> </dependency>
提交到数据库insert要记得加事务控制。@Transactional
首先在getotp界面添加注册成功的跳转界面,然后写前端注册界面:也用h5 复制getotp写一个register.html
七、用户登录流程
还需要解决跨域的问题,因为
//跨域请求中,不能做到session共享,要设置
@CrossOrigin(allowCredentials = "true",allowedHeaders = "*")
然后前端也要加ajax受信
//允许跨域请求
xhrFields:{withCredentials:true},
--------------------------------------------------------------------------------------------------
我加了register.html忘记加getotp了 debug半天也不太会,最后感觉sout比debug好使。。。
-------------------------------------------------------------------------------------------------
还是出错,刚刚是手机号和验证码不匹配的错误,现在直接未知错误。。。。
debug半天发现是serviceimpl的这里有问题 那么就是mapper的问题??这不是自动生成的嘛。。。。我晕
还有我都给model设置id了它还是等于null???可能idea已经被我这频繁启动暂停折磨疯掉了...
这都什么人间疾苦。。
包导错了改过来了(StringUtils这个包用刚刚添加的依赖apache公司的),还是不成功.................................
花费一下午也没找出问题,放弃了。总之这个包是得注释掉,但是注释掉这个包,我的数据库操作还是可能有问题。不再用这个了。
ps:github下载的要重新设置maven的仓库地址,setting地址和证书-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true
然后clean-install-reimport操作。但首先要改成本地都有的版本比较好。
------------------------------------------------------------------------------------------------------------
<insert id="insertSelective" parameterType="com.miaoshaproject.dataobject.UserDO" keyProperty="id" useGeneratedKeys="true">
添加id自增
------------0529经同学找错发现错在数据库中表的设计,非空必须要有默认值--------------
varchar类型默认空的就是,写两个单引号 ‘’
int类型默认0就是
id有自增所以不必默认
改过来真的就注册成功了.....难怪必须用下载的数据库,人家的建表语句里定义了默认值...
还有就是,返回未知错误没有返回什么错误,找错误应该想到所有异常最后都汇聚的BaseController里面
增加打印错误
----------------------------------------------------------------------------------------------------------------
上面并没有做手机号的唯一性验证
首先,在数据库中添加索引:
索引名称为:telphone_unique_index,索引字段选择telphone,索引类型为UNIQUE,索引方法为BTREE
然后修改以下代码:
try { userMapper.insertSelective(userDO); } catch (DuplicateKeyException ex) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "手机号已注册");//二重奏构造之二自定义的业务错误 }
八、用户登录流程
用户注册流程,后端Controller层需要获取用户输入,然后封装成Model,传递给Service层,Service层再拆分成dataobject,调用DAO层,将DO插入数据库。
登录和注册流程相似,只不过在调用DAO层时,不是进行插入操作,而是查询操作。
1.controller层写登录方法/login
//用户登录接口 @RequestMapping(value = "/login", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED}) @ResponseBody public CommonReturnType login(@RequestParam(name = "telphone") String telphone, @RequestParam(name = "password") String password) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException { //入参校验 if (StringUtils.isEmpty(telphone) || StringUtils.isEmpty(password)) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } //用户登录服务,用来校验用户登录是否合法 //用户加密后的密码 UserModel userModel = userService.validateLogin(telphone, this.EncodeByMd5(password)); //将登陆凭证加入到用户登录成功的session内 this.httpServletRequest.getSession().setAttribute("IS_LOGIN", true); this.httpServletRequest.getSession().setAttribute("LOGIN_USER", userModel); return CommonReturnType.create(null); }
2.service层
接口
/* telphone:用户注册手机 encrptPassowrd:用户加密后的密码 */ UserModel validateLogin(String telphone, String encrptPassword) throws BusinessException;
实现类
@Override public UserModel validateLogin(String telphone, String encrptPassword) throws BusinessException { //通过用户手机获取用户信息 UserDO userDO = userDOMapper.selectByTelphone(telphone); if (userDO == null) { throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL); } userPasswordDO userPasswordDO = userPasswordDOMapper.selectByUserId(userDO.getId()); UserModel userModel = convertFromDataObject(userDO, userPasswordDO); //比对用户信息内加密的密码是否和传输进来的密码相匹配 if (StringUtils.equals(encrptPassword, userModel.getEncrptPassword())) { throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL); } return userModel; }
返回业务异常时用到的是在枚举类里直接拿的
所以要在枚举类里添加该异常
USER_LOGIN_FAIL(20002,"用户手机号或密码不正确"),
3.dao
需要添加四个文件(Mybatis自动生成的只有selectByPrimaryKey)
UserDOMapper 添加 selectByTelphone
UserPasswordDOMapper 添加 selectByUserId (这个之前测试返回类型的时候写过了!!
DOMapper.java中的方法与DOMapper.xml 中数据库CURD语句一一映射。
4.前端login.html
<body class="login"> <div class="content"> <h3 class="form-title">用户登录</h3> <div class="form-group"> <label class="control-label">手机号</label> <div> <input class="form-control" type="text" placeholder="手机号" name="telphone" id="telphone"/> </div> </div> <div class="form-group"> <label class="control-label">密码</label> <div> <input class="form-control" type="password" placeholder="密码" name="password" id="password"/> </div> </div> <div class="form-actions"> <button class="btn blue" id="login" type="submit"> 登录 </button> <button class="btn green" id="register" type="submit"> 注册 </button> </div> </div> </body> <script> jQuery(document).ready(function () { //绑定注册按钮的click事件用于跳转到注册页面 $("#register").on("click",function () { window.location.href = "getotp.html"; }); //绑定登录按钮的click事件用于登录 $("#login").on("click",function () { var telphone=$("#telphone").val(); var password=$("#password").val(); if (telphone==null || telphone=="") { alert("手机号不能为空"); return false; } if (password==null || password=="") { alert("密码不能为空"); return false; } //映射到后端@RequestMapping(value = "/login", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED}) $.ajax({ type:"POST", contentType:"application/x-www-form-urlencoded", url:"http://localhost:8090/user/login", data:{ "telphone":telphone, "password":password }, //允许跨域请求 xhrFields:{withCredentials:true}, success:function (data) { if (data.status=="success") { alert("登录成功"); }else { alert("登录失败,原因为" + data.data.errMsg); } }, error:function (data) { alert("登录失败,原因为"+data.responseText); } }); return false; }); });
有点好奇客户端非webview展示html的,那种原生的app怎么传数据给服务器。尤其我的课题不能用表单传,应该也是ajax这样data里面就可以
点击登录:
点击注册回到getotp.html
3.10 优化校验规则
去仓库可以直接搜有用的轮子 ,老师直接搜了validator然后点击版本号找依赖就是。
<!--校验--> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.4.Final</version> </dependency>
好用❤❤❤❤❤
好用❤❤❤❤
好用❤❤❤❤
http://maven.outofmemory.cn/hot/
2.对validator进行一个简单的封装
新建validator的目录
新建一个ValidationResult的类
校验后的结果类,有一个布尔类型的hasError属性,一个Map类型的errorMsgMap,和他们的set,get方法。
还有一个getErrMsg的方法从Map拿value。
public class ValidationResult { //校验结果是否有错 private boolean hasErrors = false; //存放错误信息的map private Map<String, String> errorMsgMap = new HashMap<>(); public boolean isHasErrors() { return hasErrors; } public void setHasErrors(boolean hasErrors) { this.hasErrors = hasErrors; } public Map<String, String> getErrorMsgMap() { return errorMsgMap; } public void setErrorMsgMap(Map<String, String> errorMsgMap) { this.errorMsgMap = errorMsgMap; } //实现通用的通过格式化字符串信息获取错误结果的msg方法 public String getErrMsg() { return StringUtils.join(errorMsgMap.values().toArray(), ",");
//join方法是在每个元素之间打后面的参数即逗号,万一又是年龄不为空,手机号不为空等多个错误 } }
新建一个ValidatiorImpl的类
主要是实现implements InitializingBean接口的实现类,配合实体类上的属性们写注解用
@Component public class ValidatorImpl implements InitializingBean { private Validator validator;//校验包里的类 //实现校验方法并返回校验结果 public ValidationResult validate(Object bean) {//主要是这个方法做校验!!! final ValidationResult result = new ValidationResult(); Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(bean); if (constraintViolationSet.size() > 0) { //有错误 result.setHasErrors(true);//此时hasError属性为true了 constraintViolationSet.forEach(constraintViolation ->{ String errMsg = constraintViolation.getMessage();//在实体类属性的注解上 String propertyName = constraintViolation.getPropertyPath().toString(); result.getErrorMsgMap().put(propertyName, errMsg);//校验结果类存入错误信息 }); } return result; } @Override public void afterPropertiesSet() throws Exception { //将hibernate validator通过工厂的初始化方式使其实例化 this.validator = Validation.buildDefaultValidatorFactory().getValidator(); } }
在UserServiceImpl中使用validator做校验,之后要给别的实体类做校验也可以用
//校验入参 ValidationResult result = validator.validate(userModel); if (result.isHasErrors()) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, result.getErrMsg());
//后面是错误的信息,实体类注解上的,会覆盖 }
使用这个校验在业务层impl,以后做校验时只需要在model的属性上做注解即可。
给属性们加注解作为限制条件@NotNull @NotBlank
要认准这个包!!!
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class UserModel {
private Integer id;
@NotBlank(message = "姓名不能为空")
private String name;
@NotNull(message = "姓名不能为空")
private Byte gender;
@NotNull(message = "年龄不能为空")
@Min(value = 0,message = "年龄不能小于0")//还能加这种判断条件
@Max(value = 150,message = "年龄不能大于150")
private Integer age;
@NotBlank(message = "手机号不能为空")
private String telphone;
private String registerMode;
private String thirdPartyId;
@NotBlank(message = "密码不能为空")
private String encrptPassword;
我们的报错系统和校验系统就很健壮了。
----------------------------------------------------------------------------------------------------------------------------------------------
关于后端所有带异常的方法的返回值
我们这套代码是前后端分离,且前后端都有进行校验。有些校验只有后端才能完成,
比如说:
手机号是否重复
验证码是否获取成功
是否登录成功
是否注册成功
参数是否合法
用户登录手机号和密码是否匹配等等。
这些校验之后,如果异常则会抛出对应的异常, Service层throw Exception会抛到调用服务的Controller层,Controller中throw Exception最终都会在BaseController中进行return给前端。(走的还是通用返回类型)
列一下后端所有带异常的方法的返回值,有助于逻辑的理解。前面学的时候没太明白。
正确信息通用返回CommonReturnTyppe使用二重奏方法,只传入data或者null就是方法一默认的状态是success,方法二传入data和自定义string类型的状态。
异常信息最终也是在basecontroller里用通用返回类型方法二,responsedata是一个map集合接收businessexception里的异常信息作为data,后面是自定义string的状态"fail"。
具体说这个responedata是commonerror接口的实现类bussinessexception继承exception获得的,他的构造方法是二重奏方法,第一个构造方法可以直接传入也是commonerror接口实现类的embusinesserror枚举设置好的错误类型,第二个构造方法是传入coomonerror还传入自定义的errmsg覆盖掉commonerror里面的errmsg.
{ "status":"fail", "data":{ "errorCode":10001, "errorMsg":"未知错误"//来自枚举类的UNKNOWN_ERROR(10002, "未知错误"),但是是由baseController里面的定义系统错误时赋上的名字
} }
|
类名 | 方法名 | 返回正确信息 | 返回异常信息 |
Controller层 | BaseController | handlerException | 无 |
return CommonReturnType.create (responseData,“fail”);//所以最终还是通用返回类型 |
Controller层 | UserController | getUser |
return CommonReturnType .create(userVO); |
无,但会throw new BusinessException (EmBusinessError.USER_NOT_EXIST); 通过BaseController进行return |
Controller层 | UserController | getOtp |
return CommonReturnType .create (otpCodeObj, “successGetOtpCode”); |
无,但会throw new BusinessException (EmBusinessError.PARAMETER_VALIDATION_ERROR, “手机号已重复注册”); 通过BaseController进行return |
Controller层 | UserController | register |
return CommonReturnType. create(null); |
无,但会 throw new BusinessException (EmBusinessError.PARAMETER_VALIDATION_ERROR, “短信验证码错误”); 通过BaseController进行return |
Controller层 | UserController | login |
return CommonReturnType. create(null); |
无,但会throw new BusinessException (EmBusinessError.PARAMETER_VALIDATION_ERROR); 通过BaseController进行return |
Service层 | UserServiceImpl | register | 无 |
throw new BusinessException (EmBusinessError.PARAMETER_VALIDATION_ERROR); 通过BaseController进行return throw new BusinessException (EmBusinessError.PARAMETER_VALIDATION_ERROR, result.getErrMsg()); 通过BaseController进行return throw new BusinessException (EmBusinessError.PARAMETER_VALIDATION_ERROR, “手机号已重复注册”); 通过BaseController进行return |
Service层 | UserServiceImpl | validateLogin | return userModel; |
throw new BusinessException (EmBusinessError.USER_LOGIN_FAIL); 通过BaseController进行return throw new BusinessException (EmBusinessError.PARAMETER_VALIDATION_ERROR); 通过BaseController进行return |
----------------------------------------------------------------------------------------------
总结:
第二章 应用SpringBoot完成基础项目搭建
1) 7步构建maven框架
2)5步引入springboot依赖,选择Building a RESTful Web Service
3)9步完成Mybatis接入SpringBoot项目。(其中创建数据库一定要注意除了自增的id以外,其他栏若设置了非空,就一定要写默认值!!!)
步数总结看该博主:
https://blog.csdn.net/midnight_time/article/details/90717676
代码复制看该博主:
https://blog.csdn.net/m0_37657841/article/details/90524410
第三章 用户模块开发
1)MVC分层架构,dao已经自动生成,要加上service和controller
2)通用返回类型
之前springMVC的controller里面的方法是String为返回类型执行后return一个jsp页面,也有viod的了,这里viod也要改为返回通用类型,数据为null即可,也有返回ModelAndView,后来@responsebody以后也可以直接把返回值为User对象直接返回给前端...(springMVCday2的response课,各种返回类型以及异常处理都有学,但是学完就忘好难过)。
这里是统一返回数据和状态,数据的正确信息返回viewobject或null或者像验证码这样的自定义返回类型(博主用了我们没用),异常信息(异常信息包含errorCode和errorMsg)或空。有了这样结构化的返回值,再加上@ResponseBody注解,就会被序列化为前端容易理解的JSON字符串。(而且不需要jkson解析json和Javabean的jar包??
3)otp验证码生成
controller就能完成,不需要数据库。使用httpServletRequest.getSession().setAttribute(telphone, otpCode);在session域里面绑定手机号与验证码。(企业应该用redis)
4)登录、注册功能
需要与数据库交互了。
发送验证码之后跳转到注册,注册成功后跳转到登录。那么一开始应该是注册登录界面,不选登录点击注册应该要跳转到发送验证码。。
关于密码表中用户id要对应用户表中的id的操作:
1.要在useDOMapper.xml中给insertSelective方法设置自增(所以userDO就有了id可以给Model?)
2.在UserModel转换为密码实体类 userPasswordDO的时候要设置密码表中的userId : userPasswordDO.setUserId(userModel.getId());
3.userModel中的id也不可能一开始就有,毕竟用户不会传id,所以需要在userDO执行insertSelective方法之后,通过UserDO,从数据库中查询到当前id,然后赋值给userModel。让userModel带着它去构建userPasswordDO。 : userModel.setId(userDO.getId());
5)项目中的校验逻辑
首先自定义一个校验结果类,要是出错了可以存出错信息给通用返回类型给前端返回信息,然后一个实现 InitializingBean接口的实现类,就可以搭配注解使用了