• SpringBoot项目


    目录

    一、前后端界面搭建

    1.项目前期准备

    2.数据表介绍 

     3.构建SpringBoot项目

    4、首页功能实现 

    ① 首页数据绑定语法 

    ② 公共跳转

    5、用户登陆 

    ① 构建UserDto,定义mobile和password属性 

    ② 定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp) 

    ③ 在IUserService中定义Login方法,并返回JsonResponseBody 

    ④ 在UserServiceImpl实现类中要做五个事情 

    ⑤ 全局异常处理 

    ⑥ 自定义注解参数校验(JSR303) 

    6、前端及数据库密码加密 

     7、登录令牌管理


    一、前后端界面搭建

    1.项目前期准备

    技术点介绍

    前端:Freemarker、jQuery

    后端:SpringBoot、MybatisPlus、Lombok

    中间件:Redis

    2.数据表介绍 

    用户表:t_user

    商品表:t_goods

    订单表:t_order

    订单项表:t_order_item

    数据源表:t_dict_type

    数据项表:t_dict_data

     # 后续微服务秒杀项目所用
    秒杀商品表:t_seckill_goods
    秒杀订单表:t_seckill_order

     3.构建SpringBoot项目

    1.创建项目,这里面我什么也没有勾, 完全按照我们下面的依赖就好了,

    为什么呢?因为有些版本是不兼容的,可能同样的java代码,它会跟随这你的框架版本的不一样,而导致失效,报一些莫名其妙的错误。

    2.创建SpringBoot项目并配置POM,涉及到的所有依赖

    spring-boot-starter-freemarker
    spring-boot-starter-web
    mysql-connector-java 5.1.44
    lombok

    mybatis-plus-boot-starter 3.4.0
    mybatis-plus-generator 3.4.0

    HikariCP

    commons-codec
    commons-lang3 3.6

    spring-boot-starter-validation

    spring-boot-starter-data-redis

    pom.xml参考如下:

     
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        4.0.0
       
            org.springframework.boot
            spring-boot-starter-parent
            2.3.9.RELEASE
           
       

        com.zking
        testspbootpro
        0.0.1-SNAPSHOT
        testspbootpro
        Demo project for Spring Boot

       
            1.8
       

       
           
           
                org.springframework.boot
                spring-boot-starter-freemarker
           

           
           
                org.springframework.boot
                spring-boot-starter-web
           

           
           
                mysql
                mysql-connector-java
                runtime
                5.1.44
           

           
           
                org.projectlombok
                lombok
                true
           

           
           
                org.springframework.boot
                spring-boot-starter-test
                test
               
                   
                        org.junit.vintage
                        junit-vintage-engine
                   

               

           

           
                junit
                junit
                test
           

           
           
                com.baomidou
                mybatis-plus-boot-starter
                3.4.0
           

           
           
                com.baomidou
                mybatis-plus-generator
                3.4.0
           

           
           
                com.zaxxer
                HikariCP
           

           
           
                commons-codec
                commons-codec
           

           
                org.apache.commons
                commons-lang3
                3.6
           

           
           
                org.springframework.boot
                spring-boot-starter-validation
           

           
           
                org.springframework.boot
                spring-boot-starter-data-redis
           

           
           
                org.apache.commons
                commons-pool2
           

           
           
           
                com.alipay.sdk
                alipay-easysdk
                2.0.1
           

       

       
           
               
                    org.springframework.boot
                    spring-boot-maven-plugin
               

           

       

    3. 因为我们什么也没有勾,里面什么也没有,先把它转为yml版本的,然后配置application.yml

    1)添加数据库及连接池配置
    2)添加freemarker配置
    3)添加mybatis-plus配置
    4)添加logging日志配置

    application.yml参考如下:

     server:
        port: 8081
        servlet:
            context-path: /
    spring:
        datasource:
            url: jdbc:mysql://localhost:3306/spbootpro?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&characterEncoding=UTF8
            driver-class-name: com.mysql.jdbc.Driver
            password: 123456
            username: root
            hikari:
                # 最小空闲连接数量
                minimum-idle: 5
                # 空闲连接存活最大时间,默认600000(10分钟)
                idle-timeout: 180000
                # 连接池最大连接数,默认是10
                maximum-pool-size: 10
                # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
                auto-commit: true
                # 连接池名称
                pool-name: MyHikariCP
                # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
                max-lifetime: 1800000
                # 数据库连接超时时间,默认30秒,即30000
                connection-timeout: 30000
        freemarker:
            #设置编码格式
            charset: UTF-8
            #后缀
            suffix:
            #文档类型
            content-type: text/html
            #模板前端
            template-loader-path: classpath:/templates/
            #启用模板
            enabled: true
        mvc:
            static-path-pattern: /static/**
        redis:
            #服务端IP
            host: 192.168.29.128
            #端口
            port: 6379
            #密码
            password: 123456
            #选择数据库
            database: 0
            #超时时间
            timeout: 10000ms
            #Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问
            #Lettuce线程安全,Jedis线程非安全
            lettuce:
                pool:
                    #最大连接数,默认8
                    max-active: 8
                    #最大连接阻塞等待时间,默认-1
                    max-wait: 10000ms
                    #最大空闲连接,默认8
                    max-idle: 200
                    #最小空闲连接,默认0
                    min-idle: 5
    #mybatis-plus配置
    mybatis-plus:
        #所对应的 XML 文件位置
        mapper-locations: classpath*:/mapper/*Mapper.xml
        #别名包扫描路径
        type-aliases-package: com.jwj.spbootpro.model
        configuration:
            #驼峰命名规则
            map-underscore-to-camel-case: true
    #日志配置
    logging:
        level:
            com.jwj.spbootpro.mapper: debug

    4. 启动类配置

    package com.jwj.spbootpro;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    @MapperScan({"/com.jwj.spbootpro.mapper"})
    @EnableTransactionManagement
    @SpringBootApplication
    public class SpbootproApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpbootproApplication.class, args);
        }
    
    }
    

    5.首页访问

     导入前端页面及页面对应的js/css/images文件

     把它copy到resource里面去,这里面的都是静态,死数据,等待这我们把这些数据变活。

     

    我们来测试一下这个项目到底搭建成功没有,创建一个类IndexController.java

    1. package com.jwj.spbootpro.controller;
    2. import org.springframework.stereotype.Controller;
    3. import org.springframework.web.bind.annotation.RequestMapping;
    4. /**
    5. * @author 敢敢
    6. * @site www.javajwj.com
    7. * @company xxx公司
    8. * @create  2022-11-05 15:21
    9. */
    10. @Controller
    11. public class IndexController {
    12. @RequestMapping("/")
    13. public String index(){
    14. // #后缀
    15. // suffix:
    16. // #模板前端
    17. // template-loader-path: classpath:/templates/
    18. // /templates+index.html+"" 前缀+逻辑视图名+后缀
    19. return "index.xml";
    20. }
    21. }

    运行一下:访问http://localhost:8081/,出现界面如下,代表项目构建成功

    目前我们看到的都是死数据,我们要把它变活。 

    4、首页功能实现 

    主要查询出 装饰摆件 和 墙式壁挂两个类别的上坪在首页进行展示 

     SELECT * FROM t_goods;
    SELECT * FROM t_goods where goods_type = '01';
    SELECT * FROM t_goods where goods_type = '07';

    SELECT * FROM t_dict_type;
    SELECT * FROM t_dict_data;

    导入我们的Mybatis-plus生成器和工具类

    导入模块生成类 

    将自定义的代码生成模板放到templates目录下,覆盖Mybatis-plus默认的代码生成默认

    为了生效,我们把target删除掉。

    注意:在CodeGenerator.java表里面把表名改为你自己的表名

    package com.jwj.spbootpro.generator;
    
    import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
    import com.baomidou.mybatisplus.core.toolkit.StringPool;
    import com.baomidou.mybatisplus.core.toolkit.StringUtils;
    import com.baomidou.mybatisplus.generator.AutoGenerator;
    import com.baomidou.mybatisplus.generator.InjectionConfig;
    import com.baomidou.mybatisplus.generator.config.*;
    import com.baomidou.mybatisplus.generator.config.po.TableInfo;
    import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
    import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Scanner;
    
    public class CodeGenerator {
    
        /**
         * 

    * 读取控制台内容 *

    */ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotBlank(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir") + "/spbootpro"; gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("jwj"); gc.setOpen(false); gc.setBaseColumnList(true); gc.setBaseResultMap(true); // gc.setSwagger2(true); 实体属性 Swagger2 注解 mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/spbootpro?useUnicode=true&useSSL=false&characterEncoding=utf8"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); //pc.setModuleName(scanner("模块名")); pc.setParent("com.jwj.spbootpro"); //设置包名 pc.setEntity("model"); mpg.setPackageInfo(pc); // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker String templatePath = "/templates/mybatis-generator/mapper2.xml.ftl"; // 如果模板引擎是 velocity // String templatePath = "/templates/mapper.xml.vm"; // 自定义输出配置 List focList = new ArrayList<>(); // 自定义配置会被优先输出 focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! return projectPath + "/src/main/resources/mapper/" + pc.getModuleName() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); /* cfg.setFileCreate(new IFileCreate() { @Override public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { // 判断自定义文件夹是否需要创建 checkDir("调用默认方法创建的目录,自定义目录用"); if (fileType == FileType.MAPPER) { // 已经生成 mapper 文件判断存在,不想重新生成返回 false return !new File(filePath).exists(); } // 允许生成模板文件 return true; } }); */ cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); // 配置自定义输出模板 //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 templateConfig.setMapper("templates/mybatis-generator/mapper2.java"); templateConfig.setEntity("templates/mybatis-generator/entity2.java"); templateConfig.setService("templates/mybatis-generator/service2.java"); templateConfig.setServiceImpl("templates/mybatis-generator/serviceImpl2.java"); templateConfig.setController("templates/mybatis-generator/controller2.java"); templateConfig.setXml(null); mpg.setTemplate(templateConfig); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!"); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); strategy.setEntitySerialVersionUID(false); // 公共父类 //strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!"); // 写于父类中的公共字段 strategy.setSuperEntityColumns("id"); strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix("t_"); mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); } }

     运行CodeGenerator.java里面的main方法,生成成功后的截图

    生成出来的代码要被spring所接管

    首页方法改造 IndexController.java 

    1. package com.jwj.spbootpro.controller;
    2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    3. import com.jwj.spbootpro.model.Goods;
    4. import com.jwj.spbootpro.service.IGoodsService;
    5. import com.jwj.spbootpro.utils.DataUtils;
    6. import org.springframework.beans.factory.annotation.Autowired;
    7. import org.springframework.stereotype.Controller;
    8. import org.springframework.ui.Model;
    9. import org.springframework.web.bind.annotation.RequestMapping;
    10. import java.util.List;
    11. import java.util.Map;
    12. /**
    13. * @author 敢敢
    14. * @site www.javajwj.com
    15. * @company xxx公司
    16. * @create  2022-11-07 18:58
    17. */
    18. @Controller
    19. public class IndexController {
    20. @Autowired
    21. private IGoodsService goodsService;
    22. @RequestMapping("/")
    23. public String index(Model model){
    24. // 摆件花艺
    25. List<Goods> goods01 = goodsService.list(new QueryWrapper<Goods>()
    26. .eq("goods_type", "01")
    27. .last("limit 6"));
    28. // 壁挂北欧
    29. List<Goods> goods07 = goodsService.list(new QueryWrapper<Goods>()
    30. .eq("goods_type", "07")
    31. .last("limit 12"));
    32. // 为了方便首页数据展示,方便摆放
    33. DataUtils<Goods> dataUtils = new DataUtils<>();
    34. Map<String, List<Goods>> gt01 = dataUtils.transfor(3,goods01);
    35. Map<String, List<Goods>> gt07 = dataUtils.transfor(4, goods07);
    36. model.addAttribute("gt01",gt01);
    37. model.addAttribute("gt07",gt07);
    38. return "index.html";
    39. }
    40. }

    ① 首页数据绑定语法 

     1) list集合判空
    <#if goods07?? && goods07?size gt 0>

    2) 遍历map集合,获取所有的keys
    <#list goods07?keys as key>

    3) 根据key获取对应value值goods01[key]
    <#list goods07[key] as g>

    首页绑值  index.html        把所有的数据都变活

    1. <!DOCTYPE html>
    2. <html>
    3. <head lang="en">
    4. <#include "common/head.html">
    5. <link rel="stylesheet" type="text/css" href="css/public.css"/>
    6. <link rel="stylesheet" type="text/css" href="css/index.css" />
    7. </head>
    8. <body>
    9. <!------------------------------head------------------------------>
    10. <#include "common/top.html">
    11. <!-------------------------banner--------------------------->
    12. <div class="block_home_slider">
    13. <div id="home_slider" class="flexslider">
    14. <ul class="slides">
    15. <li>
    16. <div class="slide">
    17. <img src="img/banner2.jpg"/>
    18. </div>
    19. </li>
    20. <li>
    21. <div class="slide">
    22. <img src="img/banner1.jpg"/>
    23. </div>
    24. </li>
    25. </ul>
    26. </div>
    27. </div>
    28. <!------------------------------thImg------------------------------>
    29. <div class="thImg">
    30. <div class="clearfix">
    31. <a href="${ctx}/page/vase_proList.html"><img src="img/i1.jpg"/></a>
    32. <a href="${ctx}/page/proList.html"><img src="img/i2.jpg"/></a>
    33. <a href="#2"><img src="img/i3.jpg"/></a>
    34. </div>
    35. </div>
    36. <!------------------------------news------------------------------>
    37. <div class="news">
    38. <div class="wrapper">
    39. <h2><img src="img/ih1.jpg"/></h2>
    40. <div class="top clearfix">
    41. <a href="${ctx}/page/proDetail.html"><img src="img/n1.jpg"/><p></p></a>
    42. <a href="${ctx}/page/proDetail.html"><img src="img/n2.jpg"/><p></p></a>
    43. <a href="${ctx}/page/proDetail.html"><img src="img/n3.jpg"/><p></p></a>
    44. </div>
    45. <div class="bott clearfix">
    46. <a href="${ctx}/page/proDetail.html"><img src="img/n4.jpg"/><p></p></a>
    47. <a href="${ctx}/page/proDetail.html"><img src="img/n5.jpg"/><p></p></a>
    48. <a href="${ctx}/page/proDetail.html"><img src="img/n6.jpg"/><p></p></a>
    49. </div>
    50. <h2><img src="img/ih2.jpg"/></h2>
    51. <#if gt01?? && gt01?size gt 0>
    52. <#-- 遍历gt01中所有的key,是为了该key中的对象-->
    53. <#list gt01?keys as key>
    54. <div class="flower clearfix tran">
    55. <#list gt01[key] as g>
    56. <a href="proDetail.html" class="clearfix">
    57. <dl>
    58. <dt>
    59. <span class="abl"></span>
    60. <img src="${g.goodsImg}"/>
    61. <span class="abr"></span>
    62. </dt>
    63. <dd>${g.goodsName}</dd>
    64. <dd><span>¥ ${g.goodsPrice}</span></dd>
    65. </dl>
    66. </a>
    67. </#list>
    68. </div>
    69. </#list>
    70. </#if>
    71. </div>
    72. </div>
    73. <!------------------------------ad------------------------------>
    74. <a href="#" class="ad"><img src="img/ib1.jpg"/></a>
    75. <!------------------------------people------------------------------>
    76. <div class="people">
    77. <div class="wrapper">
    78. <h2><img src="img/ih3.jpg"/></h2>
    79. <#if gt07?? && gt07?size gt 0>
    80. <#list gt07?keys as key>
    81. <div class="pList clearfix tran">
    82. <#list gt07[key] as g>
    83. <a href="proDetail.html">
    84. <dl>
    85. <dt>
    86. <span class="abl"></span>
    87. <img src="${g.goodsImg}"/>
    88. <span class="abr"></span>
    89. </dt>
    90. <dd>${g.goodsName}</dd>
    91. <dd><span>¥${g.goodsPrice}</span></dd>
    92. </dl>
    93. </a>
    94. </#list>
    95. </div>
    96. </#list>
    97. </#if>
    98. </div>
    99. </div>
    100. <#include "common/footer.html"/>
    101. <script src="js/public.js" type="text/javascript" charset="utf-8"></script>
    102. <script src="js/nav.js" type="text/javascript" charset="utf-8"></script>
    103. <script src="js/jquery.flexslider-min.js" type="text/javascript" charset="utf-8"></script>
    104. <script type="text/javascript">
    105. $(function() {
    106. $('#home_slider').flexslider({
    107. animation: 'slide',
    108. controlNav: true,
    109. directionNav: true,
    110. animationLoop: true,
    111. slideshow: true,
    112. slideshowSpeed:2000,
    113. useCSS: false
    114. });
    115. });
    116. </script>
    117. </body>
    118. </html>

    运行一下:

    在去刷新一下界面,效果如图所示:

    ② 公共跳转

    现在我们点什么都不能跳转,跳转失败,看效果如图所示:

    1)创建公共跳转控制器PageController.java

    1. package com.jwj.spbootpro.controller;
    2. import org.springframework.stereotype.Controller;
    3. import org.springframework.web.bind.annotation.PathVariable;
    4. import org.springframework.web.bind.annotation.RequestMapping;
    5. /**
    6. * @author 敢敢
    7. * @site www.javajwj.com
    8. * @company xxx公司
    9. * @create  2022-11-08 14:32
    10. */
    11. @Controller
    12. public class PageController {
    13. /**
    14. *
    15. * @param page 直接跳转页面(没有层级文件夹的情况)
    16. * 列如:http://localhost:8080/page/logiin.html
    17. * @return
    18. */
    19. @RequestMapping("/page/{page}")
    20. public String page(@PathVariable("page") String page){
    21. return page;
    22. }
    23. /**
    24. *
    25. * @param dir
    26. * @param page
    27. * 直接跳转页面(存在层级文件夹的情况)
    28. * 列如: http://localhost:8080/page/user/login.html
    29. * @return
    30. */
    31. @RequestMapping("/page/{dir}/{page}")
    32. public String page(@PathVariable("dir") String dir,
    33. @PathVariable("page") String page){
    34. return dir + "/" + page;
    35. }
    36. }

     以上公共跳转控制器配置后,所有页面效果都可以使用了,这里我就展示一个登录,如图所示:

    5、用户登陆 

    1)创建UserController类实现用户登录

    1.1)构建UserDto,定义mobile和password属性

    1.2)创建UserController类

    1.3)定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp)
    1.4)定义响应封装类JsonResponseBody和JsonResponseStatus

    我们在js里面随便建一个包放我们的登录界面的js代码,我这里建的是others,里面建一个类叫login.js,在login.html里面引入

    我们在写login.js方法的时候,要先确保能编译成功。

    login.js

    1. $(function () {
    2. alert(1);
    3. //给登录按钮添加点击事件
    4. $("#login").click(function () {
    5. let mobile = $("#mobile").val();
    6. let password = $("#password").val();
    7. // 向后台发起登录ajax请求
    8. $.post("/user/toLogin",{
    9. mobile:mobile,
    10. password:password
    11. },function (res) {
    12. if(res.code!=200){
    13. alert(res.msg);
    14. }else
    15. //alert(rs.msg);
    16. location.href='/';
    17. },"json");
    18. });
    19. });

    ① 构建UserDto,定义mobile和password属性 

    定义UserDto.java接受前台传递的参数

    1. package com.jwj.spbootpro.model.dto;
    2. import com.jwj.spbootpro.validator.IsMobile;
    3. import lombok.Data;
    4. import javax.validation.constraints.NotBlank;
    5. @Data
    6. public class UserDto {
    7. @NotBlank(message = "手机号码不能为空!")
    8. @IsMobile
    9. private String mobile;
    10. @NotBlank(message = "密码不能为空!")
    11. private String password;
    12. }

    ② 定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp) 

    处理浏览器端的请求 UserController.java 

    1. package com.jwj.spbootpro.controller;
    2. import com.jwj.spbootpro.model.dto.UserDto;
    3. import com.jwj.spbootpro.service.IUserService;
    4. import com.jwj.spbootpro.utils.JsonResponseBody;
    5. import com.sun.deploy.net.HttpResponse;
    6. import org.springframework.beans.factory.annotation.Autowired;
    7. import org.springframework.web.bind.annotation.RequestMapping;
    8. import org.springframework.web.bind.annotation.RestController;
    9. import javax.servlet.http.HttpServletRequest;
    10. /**
    11. *

    12. * 用户信息表 前端控制器
    13. *

    14. *
    15. * @author jwj
    16. * @since 2022-11-07
    17. */
    18. @RestController
    19. @RequestMapping("/user")
    20. public class UserController {
    21. @Autowired
    22. private IUserService userService;
    23. /**
    24. * JsonResponseBody:返回json数据
    25. */
    26. @RequestMapping("/toLogin")
    27. public JsonResponseBody toLogin(@Valid UserDto userDto,
    28. HttpServletRequest request,
    29. HttpResponse response){
    30. return userService.toLogin(userDto,response);
    31. }
    32. }

    ③ 在IUserService中定义Login方法,并返回JsonResponseBody 

    IUserService.java  

    1. package com.jwj.spbootpro.service;
    2. import com.jwj.spbootpro.model.User;
    3. import com.baomidou.mybatisplus.extension.service.IService;
    4. import com.jwj.spbootpro.model.dto.UserDto;
    5. import com.jwj.spbootpro.utils.JsonResponseBody;
    6. import com.sun.deploy.net.HttpResponse;
    7. /**
    8. *

    9. * 用户信息表 服务类
    10. *

    11. *
    12. * @author jwj
    13. * @since 2022-11-07
    14. */
    15. public interface IUserService extends IService {
    16. JsonResponseBody toLogin(UserDto userDto, HttpResponse response);
    17. }

    ④ 在UserServiceImpl实现类中要做五个事情 

    4.1)判断mobile和password是否为空

    4.2)判断mobile格式是否正确

    4.3)根据用户手机号码查询用户是否存在
    4.4)校验账号
    4.5)校验密码 

    全局异常处理 

    5.1)创建BusinessException
    5.2)创建GlobalExceptionHandler
    5.3)修改userLogin中的异常处理方式

    自定义注解参数校验(JSR303) 

    6.1)创建自定义注解IsMobile
    6.2)创建自定义校验规则类MobileValidator
    6.3)在UserVo类的mobile属性中使用IsMobile注解 

    自定义JSR303注解,完成服务端登录账户的验证  IsMobile.java

    1. package com.jwj.spbootpro.validator;
    2. import javax.validation.Constraint;
    3. import javax.validation.Payload;
    4. import java.lang.annotation.*;
    5. @Documented
    6. @Constraint(
    7. validatedBy = {MobileValidator.class}
    8. )
    9. @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    10. @Retention(RetentionPolicy.RUNTIME)
    11. public @interface IsMobile {
    12. boolean required() default true;
    13. String message() default "手机号码格式错误!";
    14. Class[] groups() default {};
    15. Classextends Payload>[] payload() default {};
    16. }

    MobileValidator.java

    1. package com.jwj.spbootpro.validator;
    2. import com.jwj.spbootpro.utils.ValidatorUtils;
    3. import javax.validation.ConstraintValidator;
    4. import javax.validation.ConstraintValidatorContext;
    5. public class MobileValidator implements ConstraintValidator<IsMobile,String> {
    6. private boolean required=false;
    7. @Override
    8. public void initialize(IsMobile constraintAnnotation) {
    9. this.required=constraintAnnotation.required();
    10. }
    11. @Override
    12. public boolean isValid(String mobile, ConstraintValidatorContext constraintValidatorContext) {
    13. if(!this.required)
    14. return false;
    15. return ValidatorUtils.isMobile(mobile);
    16. }
    17. }

    UserServiceImpl.java

    1. package com.jwj.spbootpro.service.impl;
    2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    3. import com.jwj.spbootpro.exception.BusinessException;
    4. import com.jwj.spbootpro.model.User;
    5. import com.jwj.spbootpro.mapper.UserMapper;
    6. import com.jwj.spbootpro.model.dto.UserDto;
    7. import com.jwj.spbootpro.service.IUserService;
    8. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    9. import com.jwj.spbootpro.utils.CookieUtils;
    10. import com.jwj.spbootpro.utils.JsonResponseBody;
    11. import com.jwj.spbootpro.utils.JsonResponseStatus;
    12. import com.jwj.spbootpro.utils.MD5Utils;
    13. import com.sun.deploy.net.HttpResponse;
    14. import org.springframework.beans.factory.annotation.Autowired;
    15. import org.springframework.stereotype.Service;
    16. import javax.servlet.http.HttpServletRequest;
    17. import javax.servlet.http.HttpServletResponse;
    18. import java.util.UUID;
    19. /**
    20. *

    21. * 用户信息表 服务实现类
    22. *

    23. *
    24. * @author jwj
    25. * @since 2022-11-07
    26. */
    27. @Service
    28. public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    29. @Autowired
    30. private UserMapper userMapper;//在UserMapper类里面要被Spring所标记@Repository交给它管理
    31. @Override
    32. public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest request, HttpServletResponse response) {
    33. // 4.1)判断mobile和password是否为空(已由JSP303完成)
    34. // 4.2)判断mobile格式是否正确(自定义验证注解)
    35. // 4.3)根据用户手机号码查询用户对象信息
    36. // select * from t_user where mobile = '' UserMapper.xml
    37. User user = userMapper.selectOne(new QueryWrapper<User>()
    38. .eq("id", userDto.getMobile()));
    39. // 4.4)校验账号
    40. // 判断用户对象是否存在
    41. if(user == null)
    42. // 只要抛出了这个异常,它就会被全局异常进行处理 GlobalExceptionHandler.java
    43. throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);
    44. // 判断用户对象密码与输入密码是否一致
    45. // 4.5)校验密码
    46. if(!user.getPassword().equals(pwd))
    47. throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
    48. return new JsonResponseBody<>();
    49. }
    50. }

    现在我们测试肯定是不成功的,因为我们的数据库加密了

    测试多种情况

    1.手机号为空

    2.手机号为非法字符

    3.密码为空

    4.手机号不存在

    5.手机号密码正确 

    6、前端及数据库密码加密 

    前端加密:防止客户端浏览器F12导致密码泄露

    后端加密:防止数据库数据泄露导致密码泄露

    MD5Utils.java

    1. package com.jwj.spbootpro.utils;
    2. import org.apache.commons.codec.digest.DigestUtils;
    3. import org.springframework.stereotype.Component;
    4. import java.util.UUID;
    5. /**
    6. * MD5加密
    7. * 用户端:password=MD5(明文+固定Salt)
    8. * 服务端:password=MD5(用户输入+随机Salt)
    9. * 用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。
    10. */
    11. @Component
    12. public class MD5Utils {
    13. //加密盐,与前端一致
    14. private static String salt="f1g2h3j4";
    15. /**
    16. * md5加密
    17. * @param src
    18. * @return
    19. */
    20. public static String md5(String src){
    21. return DigestUtils.md5Hex(src);
    22. }
    23. /**
    24. * 获取加密的盐
    25. * @return
    26. */
    27. public static String createSalt(){
    28. return UUID.randomUUID().toString().replace("-","");
    29. }
    30. /**
    31. * 将前端的明文密码通过MD5加密方式加密成后端服务所需密码
    32. * 注意:该步骤实际是在前端完成!!!
    33. * @param inputPass 明文密码
    34. * @return
    35. */
    36. public static String inputPassToFormpass(String inputPass){
    37. // 123456
    38. // 13123456f2
    39. //混淆固定盐salt,安全性更可靠
    40. String str=salt.charAt(1)+""+salt.charAt(5)+inputPass+salt.charAt(0)+""+salt.charAt(3);
    41. return md5(str);
    42. }
    43. /**
    44. * 将后端密文密码+随机salt生成数据库的密码
    45. * @param formPass
    46. * @param salt
    47. * @return
    48. */
    49. public static String formPassToDbPass(String formPass,String salt){
    50. //混淆固定盐salt,安全性更可靠
    51. String str=salt.charAt(7)+""+salt.charAt(4)+formPass+salt.charAt(1)+""+salt.charAt(5);
    52. return md5(str);
    53. }
    54. /**
    55. * 将用户输入的密码转换成数据库的密码
    56. * @param inputPass 明文密码
    57. * @param salt 盐
    58. * @return
    59. */
    60. public static String inputPassToDbPass(String inputPass,String salt){
    61. String formPass = inputPassToFormpass(inputPass);
    62. String dbPass = formPassToDbPass(formPass, salt);
    63. return dbPass;
    64. }
    65. public static void main(String[] args) {
    66. //d7aaa28e3b8e6c88352bd5e7c23829f9
    67. //5512a78a188b318c074a15f9b056a712
    68. String formPass = inputPassToFormpass("123456");
    69. System.out.println("前端加密密码:"+formPass);
    70. String salt = createSalt();
    71. System.out.println("后端加密随机盐:"+salt);
    72. String dbPass = formPassToDbPass(formPass, salt);
    73. System.out.println("后端加密密码:"+dbPass);
    74. System.out.println("-------------------------------------------");
    75. String dbPass1 = inputPassToDbPass("123456", salt);
    76. System.out.println("最终加密密码:"+dbPass1);
    77. }
    78. }

    运行里面的main方法,每次加密都会不一样。

     login.js变更如下

    1. $(function () {
    2. alert(4);
    3. //给登录按钮添加点击事件
    4. $("#login").click(function () {
    5. let mobile = $("#mobile").val();
    6. let password = $("#password").val();
    7. //1.密码加密
    8. //1) 定义固定盐
    9. let salt='f1g2h3j4';
    10. //2) 固定盐混淆
    11. let temp=salt.charAt(1)+""+salt.charAt(5)+password+salt.charAt(0)+""+salt.charAt(3);
    12. //3) 使用MD5完成前端第一次加密
    13. let pwd=md5(temp);
    14. // 向后台发起登录ajax请求
    15. $.post("/user/toLogin",{
    16. mobile:mobile,
    17. password:pwd
    18. },function (res) {
    19. if(res.code!=200){
    20. alert(res.msg);
    21. }else
    22. alert(res.msg);
    23. // location.href='/';
    24. },"json");
    25. });
    26. });

    UserServiceImpl .java 

    1. package com.jwj.spbootpro.service.impl;
    2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    3. import com.jwj.spbootpro.exception.BusinessException;
    4. import com.jwj.spbootpro.model.User;
    5. import com.jwj.spbootpro.mapper.UserMapper;
    6. import com.jwj.spbootpro.model.dto.UserDto;
    7. import com.jwj.spbootpro.service.IUserService;
    8. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    9. import com.jwj.spbootpro.utils.CookieUtils;
    10. import com.jwj.spbootpro.utils.JsonResponseBody;
    11. import com.jwj.spbootpro.utils.JsonResponseStatus;
    12. import com.jwj.spbootpro.utils.MD5Utils;
    13. import com.sun.deploy.net.HttpResponse;
    14. import org.springframework.beans.factory.annotation.Autowired;
    15. import org.springframework.stereotype.Service;
    16. import javax.servlet.http.HttpServletRequest;
    17. import javax.servlet.http.HttpServletResponse;
    18. import java.util.UUID;
    19. /**
    20. *

    21. * 用户信息表 服务实现类
    22. *

    23. *
    24. * @author jwj
    25. * @since 2022-11-07
    26. */
    27. @Service
    28. public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    29. @Autowired
    30. private UserMapper userMapper;//在UserMapper类里面要被Spring所标记@Repository交给它管理
    31. @Override
    32. public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest request, HttpServletResponse response) {
    33. // 4.1)判断mobile和password是否为空(已由JSP303完成)
    34. // 4.2)判断mobile格式是否正确(自定义验证注解)
    35. // 4.3)根据用户手机号码查询用户对象信息
    36. // select * from t_user where mobile = '' UserMapper.xml
    37. User user = userMapper.selectOne(new QueryWrapper<User>()
    38. .eq("id", userDto.getMobile()));
    39. // 4.4)校验账号
    40. // 判断用户对象是否存在
    41. if(user == null)
    42. // 只要抛出了这个异常,它就会被全局异常进行处理 GlobalExceptionHandler.java
    43. throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);
    44. // 判断用户对象密码与输入密码是否一致
    45. // 前台传递到后台的密码,要经过工具类md5加密一次,才有可能跟数据库密码匹配上
    46. String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());
    47. // 4.5)校验密码
    48. if(!user.getPassword().equals(pwd))
    49. throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
    50. return new JsonResponseBody<>();
    51. }
    52. }

     注意:要在login.html 里面引入md5.js

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <#include "common/head.html">
    5. <link rel="stylesheet" type="text/css" href="css/public.css"/>
    6. <link rel="stylesheet" type="text/css" href="css/login.css"/>
    7. <script type="text/javascript" src="js/jquery-1.12.4.min.js"></script>
    8. <script type="text/javascript" src="js/md5.js"></script>
    9. <script type="text/javascript" src="js/others/login.js"></script>
    10. </head>
    11. <body>
    12. <!-------------------login-------------------------->
    13. <div class="login">
    14. <form action="#" method="post">
    15. <h1><a href="${ctx}/"><img src="img/temp/logo.png"></a></h1>
    16. <p></p>
    17. <div class="msg-warn hide"><b></b>公共场所不建议自动登录,以防账号丢失</div>
    18. <p><input style="font-size:14px;" type="text" id="mobile" value="" placeholder="昵称/邮箱/手机号"></p>
    19. <p><input style="font-size:14px;" type="password" id="password" value="" placeholder="密码"></p>
    20. <p><input type="button" id="login" value="登 录"></p>
    21. <p class="txt"><a class="" href="${ctx}/page/reg.html">免费注册</a><a href="${ctx}/page/forget.html">忘记密码?</a></p>
    22. </form>
    23. </div>
    24. </body>
    25. </html>

    泄露问题就解决了,如下:

     7、登录令牌管理

    将登录的用户数据分别保留在客户端以及服务端

    UserServiceImpl .java变更如下: 

    1. package com.jwj.spbootpro.service.impl;
    2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    3. import com.jwj.spbootpro.exception.BusinessException;
    4. import com.jwj.spbootpro.model.User;
    5. import com.jwj.spbootpro.mapper.UserMapper;
    6. import com.jwj.spbootpro.model.dto.UserDto;
    7. import com.jwj.spbootpro.service.IUserService;
    8. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    9. import com.jwj.spbootpro.service.IredisService;
    10. import com.jwj.spbootpro.utils.CookieUtils;
    11. import com.jwj.spbootpro.utils.JsonResponseBody;
    12. import com.jwj.spbootpro.utils.JsonResponseStatus;
    13. import com.jwj.spbootpro.utils.MD5Utils;
    14. import com.sun.deploy.net.HttpResponse;
    15. import org.springframework.beans.factory.annotation.Autowired;
    16. import org.springframework.stereotype.Service;
    17. import javax.servlet.http.HttpServletRequest;
    18. import javax.servlet.http.HttpServletResponse;
    19. import java.util.UUID;
    20. /**
    21. *

    22. * 用户信息表 服务实现类
    23. *

    24. *
    25. * @author jwj
    26. * @since 2022-11-07
    27. */
    28. @Service
    29. public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    30. @Autowired
    31. private UserMapper userMapper;//在UserMapper类里面要被Spring所标记@Repository交给它管理
    32. @Autowired
    33. private IredisService redisService;
    34. @Override
    35. public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest request, HttpServletResponse response) {
    36. // 4.1)判断mobile和password是否为空(已由JSP303完成)
    37. // 4.2)判断mobile格式是否正确(自定义验证注解)
    38. // 4.3)根据用户手机号码查询用户对象信息
    39. // select * from t_user where mobile = '' UserMapper.xml
    40. User user = userMapper.selectOne(new QueryWrapper<User>()
    41. .eq("id", userDto.getMobile()));
    42. // 4.4)校验账号
    43. // 判断用户对象是否存在
    44. if(user == null)
    45. // 只要抛出了这个异常,它就会被全局异常进行处理 GlobalExceptionHandler.java
    46. throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);
    47. // 判断用户对象密码与输入密码是否一致
    48. // 前台传递到后台的密码,要经过工具类md5加密一次,才有可能跟数据库密码匹配上
    49. String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());
    50. // 4.5)校验密码
    51. if(!user.getPassword().equals(pwd))
    52. throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
    53. //6.将登陆用户对象与token令牌进行绑定保存到cookie和redis
    54. //创建登陆令牌token
    55. String token= UUID.randomUUID().toString().replace("-","");
    56. //将token令牌保存到cookie中
    57. CookieUtils.setCookie(request,response,"token",token,7200);
    58. //将登陆token令牌与用户对象user绑定到redis中
    59. redisService.setUserToRedis(token,user);
    60. //将用户登陆的昵称设置到cookie中
    61. CookieUtils.setCookie(request,response,"nickname",user.getNickname());
    62. return new JsonResponseBody<>();
    63. }
    64. }

    在 service里面创建一个借口IredisService.java

    1. package com.jwj.spbootpro.service;
    2. import com.jwj.spbootpro.model.User;
    3. /**
    4. * @author 敢敢
    5. * @site www.javajwj.com
    6. * @company xxx公司
    7. * @create  2022-11-08 17:29
    8. */
    9. public interface IredisService {
    10. /**
    11. * 将用户对象user与登陆token令牌保存到redis
    12. * @param token 登陆令牌token
    13. * @param user 登陆用户对象user
    14. */
    15. void setUserToRedis(String token, User user);
    16. /**
    17. * 根据登陆token令牌到redis中获取对应的用户登陆对象信息
    18. * @param token 登陆令牌token
    19. * @return 登陆用户对象
    20. */
    21. User getUserByToken(String token);
    22. }

    实现RedisServiceImpl.java 方法

    1. package com.jwj.spbootpro.service.impl;
    2. import com.jwj.spbootpro.model.User;
    3. import com.jwj.spbootpro.service.IredisService;
    4. import org.springframework.beans.factory.annotation.Autowired;
    5. import org.springframework.data.redis.core.RedisTemplate;
    6. import org.springframework.stereotype.Service;
    7. import java.util.concurrent.TimeUnit;
    8. /**
    9. * @author 敢敢
    10. * @site www.javajwj.com
    11. * @company xxx公司
    12. * @create  2022-11-08 17:31
    13. */
    14. @Service
    15. public class RedisServiceImpl implements IredisService {
    16. @Autowired
    17. private RedisTemplate<String,Object> redisTemplate;
    18. @Override
    19. public void setUserToRedis(String token, User user) {
    20. redisTemplate.opsForValue().set("user:"+token,user,7200L, TimeUnit.SECONDS);
    21. }
    22. @Override
    23. public User getUserByToken(String token) {
    24. return (User) redisTemplate.opsForValue().get("user:"+token);
    25. }
    26. }

     运行结果如下:

  • 相关阅读:
    arcgis添加天地图山东wtms服务
    Java 一文详解二叉树的层序遍历
    【LeetCode】最大连续 1 的个数
    从一篇AMA揭幕单慢雾安全技术
    使用 OpenCV 进行 YOLO 对象检测
    Linux的资源和限制
    【瑞吉外卖】day03:完善登录功能与新增员工
    驱动操作控制LED灯
    Kafka Leader和Follower故障处理细节
    SpringCloudAlibaba实战-快速上手
  • 原文地址:https://blog.csdn.net/weixin_67465673/article/details/127702607