cms项目(2)--Layui封装和登录功能实现
笔记中涉及资源:
提取码:Coke
一、Layui封装和登录功能的实现
①:重写executeLogin方法实现登录功能
- 引入fastJson依赖
<!-- fastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.55</version>
</dependency>
- 重写executeLogin方法
com.it.portal.security.filter.CmsAuthenticationFilter
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json; charset=UTF-8");
response.getWriter().write(JSON.toJSONString("密码错误!"));
return false;
}
- login.html页面发送ajax请求
$.ajax({
url : '${adminPath}/login.do',
method : 'POST',
data:{},
success : function(data) {
}
})
②:Layui封装使用
- 封装前的改动(较大)更改login.html页面script标签中的内容
删除原本的内容,我们自己来写
<script>
layui.use('form', function () {
var form = layui.form;
// 粒子线条背景
$(document).ready(function(){
$('.layui-container').particleground({
dotColor:'#5cbdaa',
lineColor:'#5cbdaa'
});
});
// 监听提交
form.on('submit(login)', function (data) {
$.ajax({
url : '${adminPath}/login.do',
method : 'POST',
data:{},
success : function(data) {
console.log(data);
}
})
return false;
});
});
</script>
修改后测试
- 开始封装(主要封装ajax请求)
<script>
layui.use('form', function () {
var form = layui.form;
// 粒子线条背景
$(document).ready(function () {
$('.layui-container').particleground({
dotColor: '#5cbdaa',
lineColor: '#5cbdaa'
});
});
// 封装ajax请求
let core = {
http: function (option) {
let opt = options = {
url: '',
method: 'POST',
data: {},
success: function (res) {
if (res === "密码错误!"){
console.log("密码错误了!")
}
}
}
// 传入的参数option替换掉默认的options最后赋值给opt
Object.assign(opt,options,option);
$.ajax(opt);
}
}
// 监听提交
form.on('submit(login)', function (data) {
core.http({
url: '${adminPath}/login.do'
})
return false;
});
// $.ajax({
// url: '${adminPath}/login.do',
// method: 'POST',
// data: {},
// success: function (data) {
// console.log(data);
// }
// })
});
</script>
修改后测试
③:安装SonarLin插件
- 插件安装
SonarLint是一个代码质量检测插件,可以帮助我们检测出代码中的坏味道
- 插件使用
④:封装结果集Result
1.创建Result类
com.it.context.foundation.Result
@Setter
@Getter
@AllArgsConstructor
public class Result<T extends Serializable> implements Serializable {
private int code;
private String msg;
private T data;
public static <W extends Serializable> Result<W> success() {
return new Result<>(200,null,null);
}
public static <W extends Serializable> Result<W> success(String msg) {
return new Result<>(200,msg,null);
}
public static <W extends Serializable> Result<W> success(W data) {
return new Result<>(200,null,data);
}
public static <W extends Serializable> Result<W> success(String msg,W data) {
return new Result<>(200,msg,data);
}
public static <W extends Serializable> Result<W> success(int code, String msg) {
return new Result<>(code,msg,null);
}
public static <W extends Serializable> Result<W> success(int code, String msg,W data) {
return new Result<>(code,msg,data);
}
public static <W extends Serializable> Result<W> fail() {
return new Result<>(500,null,null);
}
public static <W extends Serializable> Result<W> fail(String msg,W data) {
return new Result<>(500,msg,data);
}
public static <W extends Serializable> Result<W> fail(int code, String msg) {
return new Result<>(code,msg,null);
}
public static <W extends Serializable> Result<W> fail(int code, String msg,W data) {
return new Result<>(code,msg,data);
}
}
2.修改com.it.portal.security.filter.CmsAuthenticationFilter类
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json; charset=UTF-8");
response.getWriter().write(JSON.toJSONString(Result.success("登录成功!")));
return false;
}
3.测试
⑤:发送请求出现加载效果
参考layui layui.org.cn/layer/index…
修改login.html页面代码
layui.use(['form','layer'], function () {
var form = layui.form, layer=layui.layer;
// 粒子线条背景
$(document).ready(function () {
$('.layui-container').particleground({
dotColor: '#5cbdaa',
lineColor: '#5cbdaa'
});
});
// 封装ajax请求
let core = {
http: function (option) {
let opt = options = {
url: '',
method: 'POST',
data: {},
beforeSend:function() {
//加载层-默认风格
layui.layer.load();
//此处演示关闭
setTimeout(function(){
layer.closeAll('loading');
}, 2000);
},
success: function (res) {
if (res === "密码错误!"){
console.log("密码错误了!")
}
}
}
// 传入的参数option替换掉默认的options最后赋值给opt
Object.assign(opt,options,option);
$.ajax(opt);
}
}
测试
但是现在有一个问题,每次请求都有一个加载效果,有些请求是不需要的
- 解决办法:配置一个可控制的加载效果,默认有加载效果
// 封装ajax请求
let core = {
http: function (option) {
let opt ={load:true}, options = {
url: '',
method: 'POST',
contentType:"application/x-www-from-urlencoded",
dataType:"json",
data: {},
beforeSend:function() {
//加载层-默认风格
this.load && layui.layer.load(0,{shade:0.1});
},
success: function (res) {
if (res.code === CONSTANT.HTTP.SUCCESS){
//此处演示关闭
setTimeout(function(){
layer.closeAll('loading');
}, 2000);
console.log("登录成功!")
}
}
}
// 传入的参数option替换掉默认的options最后赋值给opt
Object.assign(opt,options,option);
$.ajax(opt);
}
}
⑥:优化加载效果代码
1.新建文件
cms-portal/src/main/webapp/admin/js/core.js
2.访问测试(正常)
- 登录成功后再关闭加载效果
4.将状态码定义为常量
⑦:前端节流(throttle)
节流指的都是某个函数在一定时间间隔内只执行第一次回调.举个常见的节流案例: 我们把某个表单的提交按钮--button 设成每三秒内最多执行一次 click 响应;当你首次点击后,函数会无视之后三秒的所有响应; 三秒结束后,button又恢复正常 click响应功能,以此类推.
有什么用呢?通常,这类提交button的@click响应会给后端发送API请求,频繁的点击意味着频繁的请求(流量)-会给后端带来很大的压力;此外,这些回调请求返回后,往往会在前端响应其他事件(如刷新页面),可能导致页面不停的加载,影响用户体验.所以我们要给这个 button添加节流函数,防止一些无意义的点击响应.
1.添加限流工具类
2.短时内发送多次同样的ajax请求时,ajax取消请求
3.为了演示效果,后台处理请求时加上睡眠时间
4.测试
⑧:封装layui
1. 在core.js中添加以下代码
let core={
//限流工具类
throttle:function(method,args,context){
clearTimeout(method.tId);
method.tId=setTimeout(function(){
method.call(context,args);
},200);
},
http:function(option){
this.cancel && this.cancel.abort();
let opt={load:true},loadHandler,options ={
url:"",
method:"post",
contentType:"application/x-www-form-urlencoded",
dataType:"json",
beforeSend:function(){
this.load && (loadHandler = LayUtil.layer.init(function(inner,layer){
inner.loading(0,{shade:0.1})
}))
},
success:function(res){
if(res.code != null ){
loadHandler.closeLoading();
}
}
};
Object.assign(opt,options,option);
this.cancel=$.ajax(opt);
}
};
const CONSTANT = {
//http相关
HTTP:{
SUCCESS:200,
ERROR:500
}
};
// layui工具类
function LayUtil(){
}
LayUtil.prototype = {
construct:LayUtil,
//弹窗
layer:(function(LayUtil){
function Inner(){
}
Inner.prototype={
construct:Inner,
init:function(callback){
let that = this;
layui.use('layer',function(){
that.layer = layui.layer;
if(callback instanceof Function){
callback(that,that.layer);
}
})
return this;
},
//显示loading加载
loading:function(config={}){
this.layer.load(config);
},
closeLoading:function(){
this.layer.closeAll('loading');
}
}
LayUtil.layer = new Inner();
})(LayUtil),
//form表单
form:(function(LayUtil){
function Inner(){
}
Inner.prototype={
construct:Inner,
init:function(callback){
let that =this;
layui.use('form',function(){
that.form = layui.form;
that.form.render();
if(callback instanceof Function){
callback(that,that.form)
}
});
return this;
},
//提交表单
submit:function(callback,name,type="submit"){
this.form.on(type+"("+(name===undefined?'go':name)+")",function(obj){
if(callback instanceof Function){
callback(obj);
return false;
}
return true;
})
},
//验证
verify:function(validator){
this.form.verify(validator);
}
}
LayUtil.form = new Inner();
})(LayUtil)
}
2. 在login.html页面调用自定义的工具方法
<script>
// 粒子线条背景
$(document).ready(function(){
$('.layui-container').particleground({
dotColor:'#5cbdaa',
lineColor:'#5cbdaa'
});
});
LayUtil.form.init(function(inner,form){
inner.submit(function(data){
core.throttle(submit,data.field);
})
});
function submit(data) {
core.http({
url:"${adminPath}/login.do",
data:data
});
}
</script>
- 测试
⑨:完善验证码功能
1. 编写获取Sessioon的工具类
创建 com.it.context.utils.UtilsShiro 类
public class UtilsShiro {
/**
* 通过shiro获取session
* @return Session
*/
public static Session getSession(){
return SecurityUtils.getSubject().getSession();
}
}
2. 编写获取request和response的工具类
创建 com.it.context.utils.UtilsHttp 类
如何在service中获取request和response?
正常来说在service层是没有request的,直接从controller传过去的话是不规范的行为,而且传递的话也很麻烦.
在Web开发中,service层或者某个工具类中需要获取到HttpServletRequest对象还是比较常见的。一种方式是将HttpServletRequest作为方法的参数从contrller层一直放下传递,不过 这种有点费劲,且做起来不是优雅;还有另一种则是RequestContextHolder,直接在需要用的地方使用如下方式取HttpServletRequest即可,使用代码如下:
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return requestAttributes.getResponse();
public class UtilsHttp {
/**
* 获取request
* @return HttpServletRequest
*/
public static HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return requestAttributes.getRequest();
}
/**
* 获取response
* @return HttpServletResponse
*/
public static HttpServletResponse getResponse() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return requestAttributes.getResponse();
}
}
3. 将验证码提取出来做为公共的
创建 com.it.service.api.CommonService 接口
public interface CommonService {
void imageCaptcha();
}
创建 com.it.service.impl.CommonServiceImpl 实现类
禁止图像缓存
// 设置响应的类型格式为图片格式
response.setContentType("image/jpeg");
// 禁止图像缓存
response.setHeader("Pragma","no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires",0);
@Service
@Slf4j
public class CommonServiceImpl implements CommonService {
private static final String IMAGE_CAPTCHA = "image_captcha";
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Autowired
private Producer captchaProducer;
@Override
public void imageCaptcha() {
// 生成验证码字符
String text = captchaProducer.createText();
// 根据生成的验证码字符生成图片
BufferedImage image = captchaProducer.createImage(text);
// 保存到redis中
Serializable sessionId = UtilsShiro.getSession().getId();
redisTemplate.opsForValue().set(sessionId + IMAGE_CAPTCHA, text, 60, TimeUnit.SECONDS);
HttpServletResponse response = UtilsHttp.getResponse();
// 设置响应的类型格式为图片格式
response.setContentType("image/jpeg");
// 禁止图像缓存
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
// try()括号中的流会自动关闭
try( ServletOutputStream outputStream = response.getOutputStream();) {
ImageIO.write(image, "jpg", outputStream);
} catch (IOException e) {
log.error("验证码生成失败!");
e.printStackTrace();
}
}
}
4. 获取用户输入的验证码
com.it.portal.security.filter.CmsAuthenticationFilter
@Autowired
private CommonService commonService;
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json; charset=UTF-8");
// 获取验证码
String captcha = WebUtils.getCleanParam(request, "captcha");
String imageCaptcha = commonService.verifyImageCaptcha(captcha);
if (Objects.nonNull(imageCaptcha)) {
response.getWriter().write(JSON.toJSONString(Result.fail(imageCaptcha,null)));
return false;
}
response.getWriter().write(JSON.toJSONString(Result.success("登录成功!")));
return false;
}
5. 验证图片验证码
com.it.service.api.CommonService 接口中添加方法
/**
* 验证码图片验证码
* @param captcha 验证码
* @return String
*/
String verifyImageCaptcha(String captcha);
com.it.service.impl.CommonServiceImpl 实现类中实现接口方法
/**
* 验证码图片验证码
* @param captcha 验证码
* @return String
*/
@Override
public String verifyImageCaptcha( String captcha) {
// 1. 从redis中获取验证码
Serializable sessionId = UtilsShiro.getSession().getId();
String imageCaptcha = redisTemplate.opsForValue().get(sessionId + IMAGE_CAPTCHA);
// 2. 判断验证码是否为空
if (Objects.isNull(imageCaptcha)) {
// 验证码失效
return "验证码失效,请重新输入!";
}
// 3. 判断验证码是否相等
if (!StringUtils.equals(captcha, imageCaptcha)) {
return "验证码错误!";
}
return null;
}
6. 测试
⑩:分层和层间数据传递
1. 分层介绍
最基本就是三层了,其实多层的基础也是三层:界面层、业务逻辑层、存储层
我们着重讨论的不是如何分层和层的定义,而是在分层情况下,讨论层与层之间的数据传递问题。现在的程序很少仔细地去分析层与层之间的数据传递问题,通常都是一个对象从界面生成开始一路向后传,直接保存到数据库.
我们来看一个简单的例子,应用程序的添加用户功能:
要为这个界面设计接收的数据结构也很简单:
class Login2 {
private String name;
private String password;
}
然后我们在添加的时候把这个对象save(login)到数据库里边就可以了.这种类似Login,可以直接存储到数据库中的数据结构命名为Persistence Object,简称PO.看起来从头到脚用一个数据结构并没有什么问题啊?
问题来了现在需要改变需求,通常我们需要给用户密码输入两次,所以界面修改如下:
这样,提交到服务器的数据结构就应该是这样:
class Login2 {
private String name;
private String password;
private String password2;
}
然后服务器做的第一件事情就是比较password和password2是否相等,然后new—个Login结构,把name和password填充到里边,然后保存到数据库.我们把Login2类似的界面层和其它层沟通的数据结构叫做View Object,简称VO.
Java开发过程中,基本实体类包都以entity或者model来称呼,可是不少项目中,却以Bo、Vo来命名,面试的时候,也有可能被问到这些问题。那么,这几者分别代表什么意思呢?
最常用实体类,基本和数据表——对应,一个实体一张表。
代表业务对象的意思,Bo就是把业务逻辑封装为一个对象(注意是逻辑,业务逻辑),这个对象可以包括一个或多个其它的对象。通过调用Dao方法,结合Po或Vo进行务操作。
代表值对象的意思,通常用于业务层之间的数据传递,由new创建,由GC回收。 主要体现在视图的对象,对于一个WEB页面将整个页面的属性封装成一个对象,然后用一个VO对象在控制层与视图层进行传输交换。
代表持久层对象的意思,对应数据库中表的字段,数据库表中的记录在java对象中的显示状态,最形象的理解就是一个PO就是数据库中的一条记录。
代表数据传输对象的意思 是一种设计模式之间传输数据的软件应用系统,数据传输目标往往是数据访问对象从数据库中检索数据.
代表简单无规则java对象 纯的传统意义的java对象,最基本的Java Bean只有属性加上属性的get和set方法.
2. 邻域模型
- 如果常规的MVC模式公用一个bean,会带来很多问题:
a.接口只需要3个参数,而你返回表里全部参数,泄露了部分信息;
b.你不想别人知道数据库里字段,如果保持一致,能根据常规的驼峰命名完全能知道
c.数据库一个字段在不通的接口里返回的业务意义不一样,需要转换翻译;
- 于是引入业界目前常用的领域模型来划分,避免上述问题:DO,BO,DTO
DO:数据库层面,与你的表结构一致
BO:业务处理对象,基础服务于服务组件处理与返回的对象
DTO:数据传输对象,请求和返回对象
带来的影响:引入上述模型,尽量各层做到隔离,但是带来问题是不同领域之间需要转换.
3. mapStruct
01. 介绍
MapStruct是一个代码生成器的工具类,简化了不同的Java Bean之间映射的处理,所以映射指的就是从一个实体变化成另一个实体。在实际项目中,我们经常会将PO转DTO、DTO转PO等一些实体间的转换。在转换时大部分属性都是相同的,只有少部分的不同,这时我们可以通过mapStruct的一些注解来匹配不同属性,可以让不同实体之间的转换变的简单。
MapStruct官网地址: mapstruct.org/
相比于apache-beanutils,beanutils对—些深层次对象拷贝做不到,虽然可以通过改写其内部源码实现对嵌套对象属性拷贝,但是出现特殊业务转换,如属性名字不匹配,beanutils对于mapstruct就相形见绌了。
02. mapStruct的使用及注意事项
- 引入依赖
<!--mapStruct-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.2.0.Final</version>
</dependency>
再使用 mapstruct,项目的时候出现错误:java: Internal error in the mapping processor: java.lang.NullPointerException
解决方案:修改编辑器配置
路径:Setting --> Build,Execution,Deployment --> Compiler --> User-local build process VM options (overrides Shared options)
配置内容:-Djps.track.ap.dependencies=false
4. mapStruct自定义转换规则
对一些特殊的转换 我们可以进行自定义订制,有时我们需要int转boolean,那么我们就可以定义转换规则.
public class PersonTransRule {
public boolean intToBoolean(int age) {
return age >18;
)
在mapping中引入规则:使用mapper注解的uses属性,参数类型为class数组,可以指定多个转换规则类
@Mapper(uses = PersonTransRule.class)
二、 完善登录功能
①:编写持久层代码
1. 创建实体类
com.it.dao.entity.CmsUserPrimaryEntity
@Getter
@Setter
public class CmsUserPrimaryEntity {
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer id;
private String username;
private String password;
private String salt;
private String email;
private Integer loginCount;
}
2. 创建mapper接口
com.it.dao.mapper.CmsUserPrimaryMapper
public interface CmsUserPrimaryMapper {
/**
* 根据名称查询
* @param username 姓名
* @return 用户信息实体类
*/
CmsUserPrimaryEntity getByUsername(String username);
}
3. 创建mapper.xml文件
com/it/dao/mapper/mappers/CmsUserPrimaryMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.dao.mapper.CmsUserPrimaryMapper">
<resultMap id="baseUserMap" type="com.it.dao.entity.CmsUserPrimaryEntity">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<result property="status" column="status"/>
<result property="salt" column="salt"/>
<result property="deleted" column="deleted"/>
</resultMap>
<sql id="baseUserSql">
id,
username,
password,
salt,
email,
status,
deleted
</sql>
<select id="getByUsername" resultMap="baseUserMap">
select
<include refid="baseUserSql"/>
from cms_user_primary where username = #{username};
</select>
</mapper>
②:编写应用层代码
1. 创建Dto类
com.it.service.dto.CmsUserPrimaryDto
public class CmsUserPrimaryDto {
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer id;
private String username;
private String password;
private String salt;
private String email;
private Integer loginCount;
}
2. 定义Entity和Dto之间的转换关系
com.it.service.converter.CmsUserPrimaryConverter
@Mapper
public interface CmsUserPrimaryConverter {
CmsUserPrimaryConverter CONVERTER = Mappers.getMapper(CmsUserPrimaryConverter.class);
CmsUserPrimaryDto entityToDto(CmsUserPrimaryEntity entity);
}
3. 创建Service接口和实现类
com.it.service.api.CmsUserService
public interface CmsUserService {
/**
* 根据用户名查找
* @param username 用户名
* @return 用户实体类
*/
CmsUserPrimaryDto getByUsername(String username);
}
com.it.service.impl.CmsUserServiceImpl
@Service
public class CmsUserServiceImpl implements CmsUserService {
@Autowired
private CmsUserPrimaryMapper cmsUserPrimaryMapper;
@Override
public CmsUserPrimaryDto getByUsername(String username) {
CmsUserPrimaryEntity userPrimaryEntity = cmsUserPrimaryMapper.getByUsername(username);
return CmsUserPrimaryConverter.CONVERTER.entityToDto(userPrimaryEntity);
}
}
③:完善登录功能
1. 获取shiro的subject
com.it.context.utils.UtilsShiro
/**
* 获取shiro的subject
* @return Subject
*/
public static Subject getSubject(){
return SecurityUtils.getSubject();
}
2. 修改executeLogin方法
完善登录流程 com.it.portal.security.filter.CmsAuthenticationFilter
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json; charset=UTF-8");
// 获取验证码
String captcha = WebUtils.getCleanParam(request, "captcha");
String imageCaptcha = commonService.verifyImageCaptcha(captcha);
if (1 >2 &&Objects.nonNull(imageCaptcha)) {
response.getWriter().write(JSON.toJSONString(Result.fail(imageCaptcha,null)));
return false;
}
Subject subject = UtilsShiro.getSubject();
AuthenticationToken token = this.createToken(request, response);
try {
// 使用token登录
subject.login(token);
response.getWriter().write(JSON.toJSONString(Result.success("登录成功!")));
} catch (IncorrectCredentialsException | UnknownAccountException e) {
response.getWriter().write(JSON.toJSONString(Result.fail("用户名或密码错误!",null)));
}
response.getWriter().write(JSON.toJSONString(Result.success("登录成功!")));
return false;
}
④:提取出公用字段
1. 提取公用字段
创建 com.it.core.BaseDto 类
public class BaseDto<PK extends Serializable> implements Serializable {
private PK id;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public PK getId() {
return id;
}
public void setId(PK id) {
this.id = id;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}
修改 com.it.service.dto.CmsUserPrimaryDto 类
@Getter
@Setter
public class CmsUserPrimaryDto extends BaseDto<Integer> {
private String username;
private String password;
private String salt;
private String email;
private Integer loginCount;
}
创建 com.it.core.BaseEntity 类
public class BaseEntity<PK extends Serializable> implements Serializable {
private PK id;
private LocalDateTime createTime;
private LocalDateTime updateTime;
public PK getId() {
return id;
}
public void setId(PK id) {
this.id = id;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}
2. 创建副表实体类
com.it.dao.entity.CmsUserEntity
@Setter
@Getter
public class CmsUserEntity extends BaseEntity<Integer> {
private String username;
private Boolean status;
private Boolean admin;
/**
* 超级管理员
*/
private Boolean administrator;
}
3. 创建副表Dto
创建 com.it.service.dto.CmsUserDto 类
@Getter
@Setter
public class CmsUserDto extends BaseDto<Integer> {
private String username;
private Integer status;
private Boolean admin;
/**
* 超级管理员
*/
private Boolean administrator;
}
4. 修改主表Dto
修改 com.it.dao.entity.CmsUserPrimaryEntity
@Getter
@Setter
public class CmsUserPrimaryEntity extends BaseEntity<Integer>{
private String username;
private String password;
private String salt;
private String email;
private Integer loginCount;
}
⑤:创建副表的Mapper类和文件
创建 com.it.dao.mapper.CmsUserMapper 接口
public interface CmsUserMapper {
/**
* 根据名称查询
* @param username 姓名
* @return 用户信息实体类
*/
CmsUserEntity getByUsername(String username);
}
创建 com/it/dao/mapper/mappers/CmsUserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.it.dao.mapper.CmsUserMapper">
<resultMap id="baseUserMap" type="com.it.dao.entity.CmsUserEntity">
<id property="id" column="id"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="username" column="username"/>
<result property="status" column="status"/>
<result property="admin" column="is_admin"/>
<result property="administrator" column="is_super"/>
</resultMap>
<sql id="baseUserSql">
id,
username,
status
</sql>
<select id="getByUsername" resultMap="baseUserMap">
select
<include refid="baseUserSql"/>
from cms_user where username = #{username} and deleted = 1
</select>
</mapper>
⑥:定义副表Entity和Dto之间的转换
修改 com.it.service.api.CmsUserService
public interface CmsUserService {
/**
* 根据用户名查找
* @param username 用户名
* @return 用户实体类
*/
CmsUserDto selectByUsername(String username);
}
创建
@Mapper
public interface CmsUserConverter {
CmsUserConverter CONVERTER = Mappers.getMapper(CmsUserConverter.class);
// CmsUserEntity dtoToEntity(CmsUserDto cmsUserDto);
CmsUserDto entityToDto(CmsUserEntity entity);
}
修改 com.it.service.impl.CmsUserServiceImpl
@Service
public class CmsUserServiceImpl implements CmsUserService {
@Autowired
private CmsUserMapper cmsUserMapper;
@Override
public CmsUserDto selectByUsername(String username) {
return CmsUserConverter.CONVERTER.entityToDto(cmsUserMapper.getByUsername(username));
}
}
修改 com.it.portal.security.realm.UsernamePasswordCaptchaRealm
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取username
String usename = (String) authenticationToken.getPrincipal();
// 现在副表中查找用户是否存在
CmsUserDto cmsUserDto = cmsUserService.selectByUsername(usename);
if (Objects.isNull(cmsUserDto)) {
throw new UnknownAccountException();
}
// 校验用户状态 是否禁用
return null;
}
⑦:登录状态验证
1. 使用枚举定义用户状态
创建 com.it.core.BaseEnum 接口约束必须有的状态
public interface BaseEnum {
/**
* 获取标号
* @return 标号
*/
int getOrdinal();
/**
* 获取状态信息
* @return 状态信息
*/
String getLabel();
}
创建 com.it.dao.enums.UserStatusEunm 枚举
@Getter
public enum UserStatusEunm implements BaseEnum {
NORMAL(1, "正常"),
DISABLED(2, "禁用"),
LOCKED(3, "锁定"),
UNACTIVATED(4,"未激活");
private int ordinal;
private String label;
UserStatusEunm(int ordinal, String label) {
this.ordinal = ordinal;
this.label = label;
}
}
2. 修改副表Dto用户的状态
com.it.service.dto.CmsUserDto
private UserStatusEunm status;
3. 校验状态代码
com.it.portal.security.realm.UsernamePasswordCaptchaRealm
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取username
String usename = (String) authenticationToken.getPrincipal();
// 现在副表中查找用户是否存在
CmsUserDto cmsUserDto = cmsUserService.selectByUsername(usename);
if (Objects.isNull(cmsUserDto)) {
throw new UnknownAccountException();
}
// 校验用户状态 是否禁用
verifyStatus(cmsUserDto.getStatus());
return null;
}
/**
* 校验状态
* @param userStatusEunm 校验状态
*/
private void verifyStatus(UserStatusEunm userStatusEunm){
if (UserStatusEunm.DISABLED.equals(userStatusEunm)) {
throw new DisabledAccountException("您的账号已被禁用,请联系管理员!");
}else if (UserStatusEunm.LOCKED.equals(userStatusEunm)){
throw new DisabledAccountException("您的账号已被锁定,请联系管理员!");
}else if(UserStatusEunm.UNACTIVATED.equals(userStatusEunm)){
throw new DisabledAccountException("您的账号未激活,请联系管理员!");
}
}
4. Key promoter插件-提示快捷键
⑧:校验密码
1. 创建Mapper接口
创建通用Mapper接口 com.it.core.BaseMapper
public interface BaseMapper<ENTITY extends BaseEntity<PK>, PK extends Serializable> {
/**
* 根据id查找
* @param id id
* @return 实体类
*/
ENTITY selectById(PK id);
}
com.it.dao.mapper.CmsUserPrimaryMapper
public interface CmsUserPrimaryMapper extends BaseMapper<CmsUserPrimaryEntity, Integer>{
}
修改 com/it/dao/mapper/mappers/CmsUserPrimaryMapper.xml
<mapper namespace="com.it.dao.mapper.CmsUserPrimaryMapper">
<resultMap id="baseUserMap" type="com.it.dao.entity.CmsUserPrimaryEntity">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<result property="salt" column="salt"/>
</resultMap>
<sql id="baseUserSql">
id,
username,
password,
salt,
email
</sql>
<select id="selectById" resultMap="baseUserMap">
select
<include refid="baseUserSql"/>
from cms_user_primary where id = #{id}
</select>
</mapper>
2. 创建主表Service接口和实体类
创建统一的Service接口 com.it.core.BaseService
public interface BaseService<DTO extends BaseDto<PK>, PK extends Serializable>{
/**
* 根据id查询
* @param id id
* @return Dto
*/
DTO getById(PK id);
}
创建 com.it.service.api.CmsUserPrimaryService
public interface CmsUserPrimaryService extends BaseService<CmsUserPrimaryDto,Integer> {
}
创建 com.it.service.impl.CmsUserPrimaryServiceImpl 实现类
@Service
public class CmsUserPrimaryServiceImpl implements CmsUserPrimaryService {
@Autowired
private CmsUserPrimaryMapper cmsUserPrimaryMapper;
@Override
public CmsUserPrimaryDto getById(Integer id) {
return CmsUserPrimaryConverter.CONVERTER.entityToDto(cmsUserPrimaryMapper.selectById(id));
}
}
3. 自定义转换规则
创建 com.it.dao.enums.EnumConverter
public class EnumConverter {
/**
* 将entity中的int类型转换为UserStatusEnum 枚举类型
* @param status 状态
* @return 枚举
*/
public static UserStatusEnum toUserStatusEunm(int status){
return (UserStatusEnum)converter(UserStatusEnum.values(),status);
}
/**
* 通用枚举转换器,统一循环枚举比对
* @param baseEnums 枚举数组
* @param status 状态
* @return 枚举
*/
public static BaseEnum converter(BaseEnum[] baseEnums, int status){
for (BaseEnum baseEnum : baseEnums) {
if (Objects.equals(baseEnum.getOrdinal(),status)){
return baseEnum;
}
}
return null;
}
}
com.it.service.converter.CmsUserConverter 中使用自定义转换规则
@Mapper(uses = EnumConverter.class)
public interface CmsUserConverter {
CmsUserConverter CONVERTER = Mappers.getMapper(CmsUserConverter.class);
// CmsUserEntity dtoToEntity(CmsUserDto cmsUserDto);
CmsUserDto entityToDto(CmsUserEntity entity);
}
4. 校验密码
com.it.portal.security.realm.UsernamePasswordCaptchaRealm
@Autowired
private CmsUserService cmsUserService;
@Autowired
private CmsUserPrimaryService cmsUserPrimaryService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取username
String usename = (String) authenticationToken.getPrincipal();
// 现在副表中查找用户是否存在
CmsUserDto cmsUserDto = cmsUserService.selectByUsername(usename);
if (Objects.isNull(cmsUserDto)) {
throw new UnknownAccountException();
}
// 校验用户状态 是否禁用
verifyStatus(cmsUserDto.getStatus());
// 查询用户主表信息
CmsUserPrimaryDto cmsUserPrimaryDto = cmsUserPrimaryService.getById(cmsUserDto.getId());
// 校验密码
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(cmsUserDto, cmsUserPrimaryDto.getPassword(),
ByteSource.Util.bytes(cmsUserPrimaryDto.getSalt()), getName());
// 清理验证信息
super.clearCachedAuthenticationInfo(simpleAuthenticationInfo.getPrincipals());
return simpleAuthenticationInfo;
}
5. 测试
⑨:完赛前端的跳转
1. 刷新页面时光标聚焦在输入框中
cms-portal/src/main/webapp/WEB-INF/admin/login.html 添加以下代码
$('input[name="username"]').focus();
2. 指定跳转的页面
cms-portal/src/main/webapp/WEB-INF/admin/login.html 修改以下代码
function submit(data) {
core.http({
url:"${adminPath}/login.do",
data:data
}, function (res){
if (res.code === CONSTANT.HTTP.SUCCESS){
setTimeout(function (){
location.href = "${adminPath}/index.do";
}, 600);
}
});
}
cms-portal/src/main/webapp/admin/js/core.js
let core = {
//限流工具类
throttle: function (method, args, context) {
clearTimeout(method.tId);
method.tId = setTimeout(function () {
method.call(context, args);
}, 200);
},
http: function (option, callback) {
this.cancel && this.cancel.abort();
let opt = {load: true}, loadHandler, options = {
url: "",
method: "post",
contentType: "application/x-www-form-urlencoded",
dataType: "json",
beforeSend: function () {
this.load && ((loadTime = new Date().getTime()) && (loadHandler = LayUtil.layer.init(function (inner, layer) {
inner.loading(0, {shade: 0.1})
})))
},
success: function (res) {
// 处理loading 加载
if (this.load && loadHandler) {
let time = 0;
if (new Date().getTime() - loadTime < 500) {
time = 500;
}
setTimeout(function () {
loadHandler.closeLoading();
}, time)
}
// 判断请求接口
switch (res.code) {
case CONSTANT.HTTP.SUCCESS:
console.log(res);
core.prompt.msg(res.msg, {shade: 0.3, time: 1200}, null);
break;
case CONSTANT.HTTP.ERROR:
break;
}
// 处理自定义回调
(callback instanceof Function) && callback(res)
}
}
Object.assign(opt, options, option);
this.cancel = $.ajax(opt);
},
// 提示相关
prompt: {
msg: function (content, option, callback) {
LayUtil.layer.init(function (inner) {
inner.msg(content, option, callback);
})
}
}
};
const CONSTANT = {
//http相关
HTTP: {
SUCCESS: 200,
ERROR: 500
}
};
// layui工具类
function LayUtil() {
}
LayUtil.prototype = {
construct: LayUtil,
//弹窗
layer: (function (LayUtil) {
function Inner() {
}
Inner.prototype = {
construct: Inner,
init: function (callback) {
let that = this;
layui.use('layer', function () {
that.layer = layui.layer;
if (callback instanceof Function) {
callback(that, that.layer);
}
})
return this;
},
//显示loading加载
loading: function (config = {}) {
this.layer.load(config);
},
// 关闭loading
closeLoading: function () {
this.layer.closeAll('loading');
},
msg:function (content,option,callback){
console.log(layer.msg(content,option,callback));
}
}
LayUtil.layer = new Inner();
})(LayUtil),
//form表单
form: (function (LayUtil) {
function Inner() {
}
Inner.prototype = {
construct: Inner,
init: function (callback) {
let that = this;
layui.use('form', function () {
that.form = layui.form;
that.form.render();
if (callback instanceof Function) {
callback(that, that.form)
}
});
return this;
},
//提交表单
submit: function (callback, name, type = "submit") {
this.form.on(type + "(" + (name === undefined ? 'go' : name) + ")", function (obj) {
if (callback instanceof Function) {
callback(obj);
return false;
}
return true;
})
},
//验证
verify: function (validator) {
this.form.verify(validator);
}
}
LayUtil.form = new Inner();
})(LayUtil)
}
com.it.portal.controller.admin.LoginController
@GetMapping("login.do")
public String toLogin() {
// 判断用户是否已经登录
Subject subject = UtilsShiro.getSubject();
if (subject.isAuthenticated()){
return "redirect:index.do";
}
return "/admin/login";
}
3. 测试
⑩:创建常量池存放常量值
com.it.context.constant.ConstantsPool
public class ConstantsPool {
private ConstantsPool(){}
/**
* 异常相关
*/
public static final String EXCEPTION_NETWORK = "网络范围,请稍后重试!";
}
com.it.portal.security.filter.CmsAuthenticationFilter