目录
① 构建UserDto,定义mobile和password属性
② 定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp)
③ 在IUserService中定义Login方法,并返回JsonResponseBody
一、前后端界面搭建
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依赖 -->
mybatis-plus-boot-starter 3.4.0
mybatis-plus-generator 3.4.0
<!-- hariki连接池 -->
HikariCP
<!-- MD5依赖 -->
commons-codec
commons-lang3 3.6
<!-- valid验证依赖 -->
spring-boot-starter-validation
<!-- redis -->
spring-boot-starter-data-redis
pom.xml参考如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zking</groupId>
<artifactId>testspbootpro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>testspbootpro</name>
<description>Demo project for Spring Boot</description><properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--freemarker-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency><!--spring web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency><!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>5.1.44</version>
</dependency><!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency><!--junit-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency><dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency><!-- mybatis plus依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!-- mybatis-plus-generator依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency><!--hariki-->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency><!-- MD5依赖 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency><dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency><!-- valid验证依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency><!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><!--commons-pool2 对象池依赖 2.0版本的lettuce需要-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency><!--spring-session将session借助于第三方存储(redis/mongodb等等),默认redis-->
<!--<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies><build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build></project>
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/,出现界面如下,代表项目构建成功
目前我们看到的都是死数据,我们要把它变活。
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 { /** * <p> * 读取控制台内容 * </p> */ 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<FileOutConfig> 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
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;
}
}
以上公共跳转控制器配置后,所有页面效果都可以使用了,这里我就展示一个登录,如图所示:
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
$(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,定义mobile和password属性
定义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;
}
② 定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp)
处理浏览器端的请求 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;
/**
* <p>
* 用户信息表 前端控制器
* </p>
*
* @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中定义Login方法,并返回JsonResponseBody
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;
/**
* <p>
* 用户信息表 服务类
* </p>
*
* @author jwj
* @since 2022-11-07
*/
public interface IUserService extends IService<User> {
JsonResponseBody toLogin(UserDto userDto, HttpResponse response);
}
④ 在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
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;
/**
* <p>
* 用户信息表 服务实现类
* </p>
*
* @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.手机号密码正确
6、前端及数据库密码加密
前端加密:防止客户端浏览器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;
/**
* <p>
* 用户信息表 服务实现类
* </p>
*
* @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>
泄露问题就解决了,如下:
7、登录令牌管理
将登录的用户数据分别保留在客户端以及服务端
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;
/**
* <p>
* 用户信息表 服务实现类
* </p>
*
* @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);
}
}
运行结果如下: