SpringBoot构建电商基础秒杀项目(一)

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;
    }
}
View Code

修改cotroller的返回值为这个,则可以看到浏览器返回多了个statusdata 统一的返回类型

(学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;
    }
}
View Code

使用这个类就是,这个类可以传给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;
    }
}
View Code

然后就可以在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;
    }

}
View Code

以上统一了返回类型和错误信息

完成了基础能力建设,接下来进入模型能力管理。功能方面了

用户信息管理:

     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>
View Code

这里的$#是取地址去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);

    }
login

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>

好用❤❤❤❤❤

https://mvnrepository.com/

好用❤❤❤❤

https://www.mvnjar.com/

好用❤❤❤❤

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接口的实现类,就可以搭配注解使用了

原文地址:https://www.cnblogs.com/gezi1007/p/12944975.html