zoukankan      html  css  js  c++  java
  • 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接口的实现类,就可以搭配注解使用了

  • 相关阅读:
    ES6模块
    遍历数组和对象的方法
    JVM知识(六):linux环境下查看JVM内存大小
    WebSocket实时消息推送
    SpringBoot中基于Mybatis多数据源操作
    浅谈Redis中的雪崩和穿透及击穿。
    Hibernate与Mybatis的区别
    Java动态代理和反射机制
    JSON对象和JSON字符串的区别
    JVM知识(五):垃圾回收算法
  • 原文地址:https://www.cnblogs.com/gezi1007/p/12944975.html
Copyright © 2011-2022 走看看