SpringBoot其实不是新框架,而是默认配置了很多框架的使用方式。就像maven整合了所有jar包,Springboot整合了所有框架,并通过一行简单的main方法启动应用。
继承了spring的框架们:
电商秒杀应用简介:
商品列表页获取秒杀商品列表
进入商品详情页获取秒杀商品详情
秒杀开始后进入下单确认页下单并支付成功
项目实战:
使用IDEA+Maven搭建SpringBoot开发环境
集成Mybatis操作数据库
实现秒杀项目
构建Maven项目—>引入SpringBoot依赖—>接入Mybatis
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容器
???有点失败,网页没有出来,依旧是上面那样。没有报错也不知为啥。
***************************
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?(不过简单的显示确实不需要页面。。。
mybatis竟然有自动生成mapping.xml文件的插件。。。
创建数据库,填写mybatis-generator.xml,这个xml里面指定mybatis插件连接数据库和让插件生成什么文件生到哪里。
运行Mybatis插件,自动生成相关文件
就还挺神奇的,实体类(对应表),DAO接口(写方法)和mapping.xml文件(sql语句),这3个都不需要手动编写了。只需要把包写好就行。
运行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; }
5.项目中的校验逻辑
要做校验在传入数据库之前判断各个属性的数据是否为空,在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包)
<!-- 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
这都什么人间疾苦。。
包导错了改过来了,还是不成功.................................
花费一下午也没找出问题,放弃了。总之这个包是得注释掉,但是注释掉这个包,我的数据库操作还是可能有问题。不再用这个了。
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自增
八、用户登录流程
用户注册流程,后端Controller层需要获取用户输入,然后封装成Model,传递给Service层,Service层再拆分成dataobject,调用DAO层,将DO插入数据库。
登录和注册流程相似,只不过在调用DAO层时,不是进行插入操作,而是查询操作。
查询密码的时候,需要修改两个文件(Mybatis自动生成的只有selectByPrimaryKey)
UserPasswordDOMapper.xml 添加 selectByUserId
UserPasswordDOMapper.java 添加 selectByUserId
UserPasswordDOMapper.java中的方法与UserPasswordDOMapper.xml 中数据库CURD语句一一映射。
其他流程可以参考注册流程,这里就不再赘述。可以看https://blog.csdn.net/m0_37657841/article/details/90545984
3.10 优化校验规则
去仓库可以直接搜有用的轮子 ,老师直接搜了validator然后点击版本号找依赖就是。
好用?????
好用????
好用????
http://maven.outofmemory.cn/hot/
2.对validator进行一个简单的封装
新建validator的目录
新建一个ValidationResult的类
新建一个ValidatiorImpl的类
给属性们加注解作为限制条件@NotNull @NotBlank
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;
在UserServiceImpl中使用validator做校验
//校验入参 ValidationResult result = validator.validate(userModel); if (result.isHasErrors()) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, result.getErrMsg()); }
使用这个校验在业务层impl,以后做校验时只需要在model的属性上做注解即可。
我们的报错系统和校验系统就很健壮了。
九、后端所有带异常的方法的返回值
我们这套代码是前后端分离,且前后端都有进行校验。有些校验只有后端才能完成,
比如说:
手机号是否重复
验证码是否获取成功
是否登录成功
是否注册成功
参数是否合法
用户登录手机号和密码是否匹配等等。
这些校验之后,如果异常则会抛出对应的异常, Service层throw Exception会抛到调用服务的Controller层,Controller中throw Exception最终都会在BaseController中进行return给前端。
列一下后端所有带异常的方法的返回值,有助于逻辑的理解。
|
类名 | 方法名 | 返回正确信息 | 返回异常信息 |
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 |
原文:https://www.cnblogs.com/gezi1007/p/12944975.html