目录
① 构建UserDto,定义mobile和password属性
② 定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp)
③ 在IUserService中定义Login方法,并返回JsonResponseBody
技术点介绍
前端:Freemarker、jQuery
后端:SpringBoot、MybatisPlus、Lombok
中间件:Redis
用户表:t_user
商品表:t_goods
订单表:t_order
订单项表:t_order_item
数据源表:t_dict_type
数据项表:t_dict_data
# 后续微服务秒杀项目所用
秒杀商品表:t_seckill_goods
秒杀订单表:t_seckill_order
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
- package com.jwj.spbootpro.controller;
-
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
-
- /**
- * @author 敢敢
- * @site www.javajwj.com
- * @company xxx公司
- * @create 2022-11-05 15:21
- */
- @Controller
- public class IndexController {
- @RequestMapping("/")
- public String index(){
- // #后缀
- // suffix:
- // #模板前端
- // template-loader-path: classpath:/templates/
- // /templates+index.html+"" 前缀+逻辑视图名+后缀
- return "index.xml";
- }
- }
运行一下:访问http://localhost:8081/,出现界面如下,代表项目构建成功
目前我们看到的都是死数据,我们要把它变活。
主要查询出 装饰摆件 和 墙式壁挂两个类别的上坪在首页进行展示
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"; // 自定义输出配置 ListfocList = 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
- package com.jwj.spbootpro.controller;
-
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.jwj.spbootpro.model.Goods;
- import com.jwj.spbootpro.service.IGoodsService;
- import com.jwj.spbootpro.utils.DataUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.web.bind.annotation.RequestMapping;
-
- import java.util.List;
- import java.util.Map;
-
- /**
- * @author 敢敢
- * @site www.javajwj.com
- * @company xxx公司
- * @create 2022-11-07 18:58
- */
- @Controller
- public class IndexController {
- @Autowired
- private IGoodsService goodsService;
-
-
- @RequestMapping("/")
- public String index(Model model){
- // 摆件花艺
- List<Goods> goods01 = goodsService.list(new QueryWrapper<Goods>()
- .eq("goods_type", "01")
- .last("limit 6"));
-
- // 壁挂北欧
- List<Goods> goods07 = goodsService.list(new QueryWrapper<Goods>()
- .eq("goods_type", "07")
- .last("limit 12"));
-
- // 为了方便首页数据展示,方便摆放
- DataUtils<Goods> dataUtils = new DataUtils<>();
- Map<String, List<Goods>> gt01 = dataUtils.transfor(3,goods01);
- Map<String, List<Goods>> gt07 = dataUtils.transfor(4, goods07);
- model.addAttribute("gt01",gt01);
- model.addAttribute("gt07",gt07);
- return "index.html";
- }
- }
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 把所有的数据都变活
- <!DOCTYPE html>
- <html>
- <head lang="en">
- <#include "common/head.html">
- <link rel="stylesheet" type="text/css" href="css/public.css"/>
- <link rel="stylesheet" type="text/css" href="css/index.css" />
- </head>
- <body>
- <!------------------------------head------------------------------>
- <#include "common/top.html">
-
- <!-------------------------banner--------------------------->
- <div class="block_home_slider">
- <div id="home_slider" class="flexslider">
- <ul class="slides">
- <li>
- <div class="slide">
- <img src="img/banner2.jpg"/>
- </div>
- </li>
- <li>
- <div class="slide">
- <img src="img/banner1.jpg"/>
- </div>
- </li>
- </ul>
- </div>
- </div>
-
- <!------------------------------thImg------------------------------>
- <div class="thImg">
- <div class="clearfix">
- <a href="${ctx}/page/vase_proList.html"><img src="img/i1.jpg"/></a>
- <a href="${ctx}/page/proList.html"><img src="img/i2.jpg"/></a>
- <a href="#2"><img src="img/i3.jpg"/></a>
- </div>
- </div>
-
- <!------------------------------news------------------------------>
- <div class="news">
- <div class="wrapper">
- <h2><img src="img/ih1.jpg"/></h2>
- <div class="top clearfix">
- <a href="${ctx}/page/proDetail.html"><img src="img/n1.jpg"/><p></p></a>
- <a href="${ctx}/page/proDetail.html"><img src="img/n2.jpg"/><p></p></a>
- <a href="${ctx}/page/proDetail.html"><img src="img/n3.jpg"/><p></p></a>
- </div>
- <div class="bott clearfix">
- <a href="${ctx}/page/proDetail.html"><img src="img/n4.jpg"/><p></p></a>
- <a href="${ctx}/page/proDetail.html"><img src="img/n5.jpg"/><p></p></a>
- <a href="${ctx}/page/proDetail.html"><img src="img/n6.jpg"/><p></p></a>
- </div>
- <h2><img src="img/ih2.jpg"/></h2>
- <#if gt01?? && gt01?size gt 0>
- <#-- 遍历gt01中所有的key,是为了该key中的对象-->
- <#list gt01?keys as key>
- <div class="flower clearfix tran">
- <#list gt01[key] as g>
- <a href="proDetail.html" class="clearfix">
- <dl>
- <dt>
- <span class="abl"></span>
- <img src="${g.goodsImg}"/>
- <span class="abr"></span>
- </dt>
- <dd>${g.goodsName}</dd>
- <dd><span>¥ ${g.goodsPrice}</span></dd>
- </dl>
- </a>
- </#list>
- </div>
- </#list>
- </#if>
-
- </div>
- </div>
-
- <!------------------------------ad------------------------------>
- <a href="#" class="ad"><img src="img/ib1.jpg"/></a>
-
- <!------------------------------people------------------------------>
- <div class="people">
- <div class="wrapper">
- <h2><img src="img/ih3.jpg"/></h2>
- <#if gt07?? && gt07?size gt 0>
- <#list gt07?keys as key>
- <div class="pList clearfix tran">
- <#list gt07[key] as g>
- <a href="proDetail.html">
- <dl>
- <dt>
- <span class="abl"></span>
- <img src="${g.goodsImg}"/>
- <span class="abr"></span>
- </dt>
- <dd>${g.goodsName}</dd>
- <dd><span>¥${g.goodsPrice}</span></dd>
- </dl>
- </a>
- </#list>
- </div>
- </#list>
- </#if>
- </div>
- </div>
-
- <#include "common/footer.html"/>
-
- <script src="js/public.js" type="text/javascript" charset="utf-8"></script>
- <script src="js/nav.js" type="text/javascript" charset="utf-8"></script>
- <script src="js/jquery.flexslider-min.js" type="text/javascript" charset="utf-8"></script>
- <script type="text/javascript">
- $(function() {
- $('#home_slider').flexslider({
- animation: 'slide',
- controlNav: true,
- directionNav: true,
- animationLoop: true,
- slideshow: true,
- slideshowSpeed:2000,
- useCSS: false
- });
-
- });
- </script>
- </body>
- </html>
运行一下:
在去刷新一下界面,效果如图所示:
现在我们点什么都不能跳转,跳转失败,看效果如图所示:
1)创建公共跳转控制器PageController.java
- package com.jwj.spbootpro.controller;
-
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.PathVariable;
- import org.springframework.web.bind.annotation.RequestMapping;
-
- /**
- * @author 敢敢
- * @site www.javajwj.com
- * @company xxx公司
- * @create 2022-11-08 14:32
- */
- @Controller
- public class PageController {
- /**
- *
- * @param page 直接跳转页面(没有层级文件夹的情况)
- * 列如:http://localhost:8080/page/logiin.html
- * @return
- */
- @RequestMapping("/page/{page}")
- public String page(@PathVariable("page") String page){
- return page;
- }
-
- /**
- *
- * @param dir
- * @param page
- * 直接跳转页面(存在层级文件夹的情况)
- * 列如: http://localhost:8080/page/user/login.html
- * @return
- */
- @RequestMapping("/page/{dir}/{page}")
- public String page(@PathVariable("dir") String dir,
- @PathVariable("page") String page){
- return dir + "/" + page;
-
- }
- }
以上公共跳转控制器配置后,所有页面效果都可以使用了,这里我就展示一个登录,如图所示:
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
- $(function () {
- alert(1);
-
- //给登录按钮添加点击事件
- $("#login").click(function () {
- let mobile = $("#mobile").val();
- let password = $("#password").val();
-
- // 向后台发起登录ajax请求
- $.post("/user/toLogin",{
- mobile:mobile,
- password:password
- },function (res) {
- if(res.code!=200){
- alert(res.msg);
- }else
- //alert(rs.msg);
- location.href='/';
- },"json");
- });
- });
定义UserDto.java接受前台传递的参数
- package com.jwj.spbootpro.model.dto;
-
- import com.jwj.spbootpro.validator.IsMobile;
- import lombok.Data;
-
- import javax.validation.constraints.NotBlank;
-
- @Data
- public class UserDto {
- @NotBlank(message = "手机号码不能为空!")
- @IsMobile
- private String mobile;
- @NotBlank(message = "密码不能为空!")
- private String password;
- }
处理浏览器端的请求 UserController.java
- package com.jwj.spbootpro.controller;
-
-
- import com.jwj.spbootpro.model.dto.UserDto;
- import com.jwj.spbootpro.service.IUserService;
- import com.jwj.spbootpro.utils.JsonResponseBody;
- import com.sun.deploy.net.HttpResponse;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.RequestMapping;
-
- import org.springframework.web.bind.annotation.RestController;
-
- import javax.servlet.http.HttpServletRequest;
-
- /**
- *
- * 用户信息表 前端控制器
- *
- *
- * @author jwj
- * @since 2022-11-07
- */
- @RestController
- @RequestMapping("/user")
- public class UserController {
- @Autowired
- private IUserService userService;
-
- /**
- * JsonResponseBody:返回json数据
- */
- @RequestMapping("/toLogin")
- public JsonResponseBody toLogin(@Valid UserDto userDto,
- HttpServletRequest request,
- HttpResponse response){
- return userService.toLogin(userDto,response);
- }
- }
IUserService.java
- package com.jwj.spbootpro.service;
-
- import com.jwj.spbootpro.model.User;
- import com.baomidou.mybatisplus.extension.service.IService;
- import com.jwj.spbootpro.model.dto.UserDto;
- import com.jwj.spbootpro.utils.JsonResponseBody;
- import com.sun.deploy.net.HttpResponse;
-
- /**
- *
- * 用户信息表 服务类
- *
- *
- * @author jwj
- * @since 2022-11-07
- */
- public interface IUserService extends IService
{ -
- JsonResponseBody toLogin(UserDto userDto, HttpResponse response);
- }
4.1)判断mobile和password是否为空
4.2)判断mobile格式是否正确
4.3)根据用户手机号码查询用户是否存在
4.4)校验账号
4.5)校验密码
5.1)创建BusinessException
5.2)创建GlobalExceptionHandler
5.3)修改userLogin中的异常处理方式
6.1)创建自定义注解IsMobile
6.2)创建自定义校验规则类MobileValidator
6.3)在UserVo类的mobile属性中使用IsMobile注解
自定义JSR303注解,完成服务端登录账户的验证 IsMobile.java
- package com.jwj.spbootpro.validator;
-
- import javax.validation.Constraint;
- import javax.validation.Payload;
- import java.lang.annotation.*;
-
- @Documented
- @Constraint(
- validatedBy = {MobileValidator.class}
- )
- @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface IsMobile {
-
- boolean required() default true;
-
- String message() default "手机号码格式错误!";
-
- Class>[] groups() default {};
-
- Class extends Payload>[] payload() default {};
-
- }
MobileValidator.java
- package com.jwj.spbootpro.validator;
-
-
-
- import com.jwj.spbootpro.utils.ValidatorUtils;
-
- import javax.validation.ConstraintValidator;
- import javax.validation.ConstraintValidatorContext;
-
- public class MobileValidator implements ConstraintValidator<IsMobile,String> {
-
- private boolean required=false;
-
- @Override
- public void initialize(IsMobile constraintAnnotation) {
- this.required=constraintAnnotation.required();
- }
-
- @Override
- public boolean isValid(String mobile, ConstraintValidatorContext constraintValidatorContext) {
- if(!this.required)
- return false;
- return ValidatorUtils.isMobile(mobile);
- }
- }
UserServiceImpl.java
- package com.jwj.spbootpro.service.impl;
-
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.jwj.spbootpro.exception.BusinessException;
- import com.jwj.spbootpro.model.User;
- import com.jwj.spbootpro.mapper.UserMapper;
- import com.jwj.spbootpro.model.dto.UserDto;
- import com.jwj.spbootpro.service.IUserService;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.jwj.spbootpro.utils.CookieUtils;
- import com.jwj.spbootpro.utils.JsonResponseBody;
- import com.jwj.spbootpro.utils.JsonResponseStatus;
- import com.jwj.spbootpro.utils.MD5Utils;
- import com.sun.deploy.net.HttpResponse;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.util.UUID;
-
- /**
- *
- * 用户信息表 服务实现类
- *
- *
- * @author jwj
- * @since 2022-11-07
- */
- @Service
- public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
- @Autowired
- private UserMapper userMapper;//在UserMapper类里面要被Spring所标记@Repository交给它管理
-
-
- @Override
- public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest request, HttpServletResponse response) {
- // 4.1)判断mobile和password是否为空(已由JSP303完成)
- // 4.2)判断mobile格式是否正确(自定义验证注解)
- // 4.3)根据用户手机号码查询用户对象信息
- // select * from t_user where mobile = '' UserMapper.xml
- User user = userMapper.selectOne(new QueryWrapper<User>()
- .eq("id", userDto.getMobile()));
- // 4.4)校验账号
- // 判断用户对象是否存在
- if(user == null)
- // 只要抛出了这个异常,它就会被全局异常进行处理 GlobalExceptionHandler.java
- throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);
-
- // 判断用户对象密码与输入密码是否一致
- // 4.5)校验密码
- if(!user.getPassword().equals(pwd))
- throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
-
- return new JsonResponseBody<>();
- }
- }
现在我们测试肯定是不成功的,因为我们的数据库加密了
测试多种情况
1.手机号为空
2.手机号为非法字符
3.密码为空
4.手机号不存在
5.手机号密码正确
前端加密:防止客户端浏览器F12导致密码泄露
后端加密:防止数据库数据泄露导致密码泄露
MD5Utils.java
- package com.jwj.spbootpro.utils;
-
- import org.apache.commons.codec.digest.DigestUtils;
- import org.springframework.stereotype.Component;
-
- import java.util.UUID;
-
- /**
- * MD5加密
- * 用户端:password=MD5(明文+固定Salt)
- * 服务端:password=MD5(用户输入+随机Salt)
- * 用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。
- */
- @Component
- public class MD5Utils {
-
- //加密盐,与前端一致
- private static String salt="f1g2h3j4";
-
- /**
- * md5加密
- * @param src
- * @return
- */
- public static String md5(String src){
- return DigestUtils.md5Hex(src);
- }
-
- /**
- * 获取加密的盐
- * @return
- */
- public static String createSalt(){
- return UUID.randomUUID().toString().replace("-","");
- }
-
- /**
- * 将前端的明文密码通过MD5加密方式加密成后端服务所需密码
- * 注意:该步骤实际是在前端完成!!!
- * @param inputPass 明文密码
- * @return
- */
- public static String inputPassToFormpass(String inputPass){
- // 123456
- // 13123456f2
- //混淆固定盐salt,安全性更可靠
- String str=salt.charAt(1)+""+salt.charAt(5)+inputPass+salt.charAt(0)+""+salt.charAt(3);
- return md5(str);
- }
-
- /**
- * 将后端密文密码+随机salt生成数据库的密码
- * @param formPass
- * @param salt
- * @return
- */
- public static String formPassToDbPass(String formPass,String salt){
- //混淆固定盐salt,安全性更可靠
- String str=salt.charAt(7)+""+salt.charAt(4)+formPass+salt.charAt(1)+""+salt.charAt(5);
- return md5(str);
- }
-
- /**
- * 将用户输入的密码转换成数据库的密码
- * @param inputPass 明文密码
- * @param salt 盐
- * @return
- */
- public static String inputPassToDbPass(String inputPass,String salt){
- String formPass = inputPassToFormpass(inputPass);
- String dbPass = formPassToDbPass(formPass, salt);
- return dbPass;
- }
-
- public static void main(String[] args) {
- //d7aaa28e3b8e6c88352bd5e7c23829f9
- //5512a78a188b318c074a15f9b056a712
- String formPass = inputPassToFormpass("123456");
- System.out.println("前端加密密码:"+formPass);
- String salt = createSalt();
- System.out.println("后端加密随机盐:"+salt);
- String dbPass = formPassToDbPass(formPass, salt);
- System.out.println("后端加密密码:"+dbPass);
- System.out.println("-------------------------------------------");
- String dbPass1 = inputPassToDbPass("123456", salt);
- System.out.println("最终加密密码:"+dbPass1);
- }
- }
运行里面的main方法,每次加密都会不一样。
login.js变更如下
- $(function () {
- alert(4);
-
- //给登录按钮添加点击事件
- $("#login").click(function () {
- let mobile = $("#mobile").val();
- let password = $("#password").val();
-
- //1.密码加密
- //1) 定义固定盐
- let salt='f1g2h3j4';
- //2) 固定盐混淆
- let temp=salt.charAt(1)+""+salt.charAt(5)+password+salt.charAt(0)+""+salt.charAt(3);
- //3) 使用MD5完成前端第一次加密
- let pwd=md5(temp);
-
- // 向后台发起登录ajax请求
- $.post("/user/toLogin",{
- mobile:mobile,
- password:pwd
- },function (res) {
- if(res.code!=200){
- alert(res.msg);
- }else
- alert(res.msg);
- // location.href='/';
- },"json");
- });
- });
UserServiceImpl .java
- package com.jwj.spbootpro.service.impl;
-
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.jwj.spbootpro.exception.BusinessException;
- import com.jwj.spbootpro.model.User;
- import com.jwj.spbootpro.mapper.UserMapper;
- import com.jwj.spbootpro.model.dto.UserDto;
- import com.jwj.spbootpro.service.IUserService;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.jwj.spbootpro.utils.CookieUtils;
- import com.jwj.spbootpro.utils.JsonResponseBody;
- import com.jwj.spbootpro.utils.JsonResponseStatus;
- import com.jwj.spbootpro.utils.MD5Utils;
- import com.sun.deploy.net.HttpResponse;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.util.UUID;
-
- /**
- *
- * 用户信息表 服务实现类
- *
- *
- * @author jwj
- * @since 2022-11-07
- */
- @Service
- public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
- @Autowired
- private UserMapper userMapper;//在UserMapper类里面要被Spring所标记@Repository交给它管理
-
-
- @Override
- public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest request, HttpServletResponse response) {
- // 4.1)判断mobile和password是否为空(已由JSP303完成)
- // 4.2)判断mobile格式是否正确(自定义验证注解)
- // 4.3)根据用户手机号码查询用户对象信息
- // select * from t_user where mobile = '' UserMapper.xml
- User user = userMapper.selectOne(new QueryWrapper<User>()
- .eq("id", userDto.getMobile()));
- // 4.4)校验账号
- // 判断用户对象是否存在
- if(user == null)
- // 只要抛出了这个异常,它就会被全局异常进行处理 GlobalExceptionHandler.java
- throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);
-
- // 判断用户对象密码与输入密码是否一致
- // 前台传递到后台的密码,要经过工具类md5加密一次,才有可能跟数据库密码匹配上
- String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());
-
- // 4.5)校验密码
- if(!user.getPassword().equals(pwd))
- throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
-
- return new JsonResponseBody<>();
- }
- }
注意:要在login.html 里面引入md5.js
- <!DOCTYPE html>
- <html>
- <head>
- <#include "common/head.html">
- <link rel="stylesheet" type="text/css" href="css/public.css"/>
- <link rel="stylesheet" type="text/css" href="css/login.css"/>
- <script type="text/javascript" src="js/jquery-1.12.4.min.js"></script>
- <script type="text/javascript" src="js/md5.js"></script>
- <script type="text/javascript" src="js/others/login.js"></script>
- </head>
- <body>
- <!-------------------login-------------------------->
- <div class="login">
- <form action="#" method="post">
- <h1><a href="${ctx}/"><img src="img/temp/logo.png"></a></h1>
- <p></p>
- <div class="msg-warn hide"><b></b>公共场所不建议自动登录,以防账号丢失</div>
- <p><input style="font-size:14px;" type="text" id="mobile" value="" placeholder="昵称/邮箱/手机号"></p>
- <p><input style="font-size:14px;" type="password" id="password" value="" placeholder="密码"></p>
- <p><input type="button" id="login" value="登 录"></p>
- <p class="txt"><a class="" href="${ctx}/page/reg.html">免费注册</a><a href="${ctx}/page/forget.html">忘记密码?</a></p>
- </form>
- </div>
-
- </body>
- </html>
泄露问题就解决了,如下:
将登录的用户数据分别保留在客户端以及服务端
UserServiceImpl .java变更如下:
- package com.jwj.spbootpro.service.impl;
-
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.jwj.spbootpro.exception.BusinessException;
- import com.jwj.spbootpro.model.User;
- import com.jwj.spbootpro.mapper.UserMapper;
- import com.jwj.spbootpro.model.dto.UserDto;
- import com.jwj.spbootpro.service.IUserService;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.jwj.spbootpro.service.IredisService;
- import com.jwj.spbootpro.utils.CookieUtils;
- import com.jwj.spbootpro.utils.JsonResponseBody;
- import com.jwj.spbootpro.utils.JsonResponseStatus;
- import com.jwj.spbootpro.utils.MD5Utils;
- import com.sun.deploy.net.HttpResponse;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.util.UUID;
-
- /**
- *
- * 用户信息表 服务实现类
- *
- *
- * @author jwj
- * @since 2022-11-07
- */
- @Service
- public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
- @Autowired
- private UserMapper userMapper;//在UserMapper类里面要被Spring所标记@Repository交给它管理
- @Autowired
- private IredisService redisService;
-
- @Override
- public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest request, HttpServletResponse response) {
- // 4.1)判断mobile和password是否为空(已由JSP303完成)
- // 4.2)判断mobile格式是否正确(自定义验证注解)
- // 4.3)根据用户手机号码查询用户对象信息
- // select * from t_user where mobile = '' UserMapper.xml
- User user = userMapper.selectOne(new QueryWrapper<User>()
- .eq("id", userDto.getMobile()));
- // 4.4)校验账号
- // 判断用户对象是否存在
- if(user == null)
- // 只要抛出了这个异常,它就会被全局异常进行处理 GlobalExceptionHandler.java
- throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);
-
- // 判断用户对象密码与输入密码是否一致
- // 前台传递到后台的密码,要经过工具类md5加密一次,才有可能跟数据库密码匹配上
- String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());
-
- // 4.5)校验密码
- if(!user.getPassword().equals(pwd))
- throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
- //6.将登陆用户对象与token令牌进行绑定保存到cookie和redis
- //创建登陆令牌token
- String token= UUID.randomUUID().toString().replace("-","");
- //将token令牌保存到cookie中
- CookieUtils.setCookie(request,response,"token",token,7200);
- //将登陆token令牌与用户对象user绑定到redis中
- redisService.setUserToRedis(token,user);
- //将用户登陆的昵称设置到cookie中
- CookieUtils.setCookie(request,response,"nickname",user.getNickname());
- return new JsonResponseBody<>();
- }
- }
在 service里面创建一个借口IredisService.java
- package com.jwj.spbootpro.service;
-
- import com.jwj.spbootpro.model.User;
-
- /**
- * @author 敢敢
- * @site www.javajwj.com
- * @company xxx公司
- * @create 2022-11-08 17:29
- */
- public interface IredisService {
- /**
- * 将用户对象user与登陆token令牌保存到redis
- * @param token 登陆令牌token
- * @param user 登陆用户对象user
- */
- void setUserToRedis(String token, User user);
-
- /**
- * 根据登陆token令牌到redis中获取对应的用户登陆对象信息
- * @param token 登陆令牌token
- * @return 登陆用户对象
- */
- User getUserByToken(String token);
- }
实现RedisServiceImpl.java 方法
- package com.jwj.spbootpro.service.impl;
-
- import com.jwj.spbootpro.model.User;
- import com.jwj.spbootpro.service.IredisService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.stereotype.Service;
-
- import java.util.concurrent.TimeUnit;
-
- /**
- * @author 敢敢
- * @site www.javajwj.com
- * @company xxx公司
- * @create 2022-11-08 17:31
- */
- @Service
- public class RedisServiceImpl implements IredisService {
- @Autowired
- private RedisTemplate<String,Object> redisTemplate;
-
- @Override
- public void setUserToRedis(String token, User user) {
- redisTemplate.opsForValue().set("user:"+token,user,7200L, TimeUnit.SECONDS);
- }
-
- @Override
- public User getUserByToken(String token) {
- return (User) redisTemplate.opsForValue().get("user:"+token);
- }
- }
运行结果如下: