Shiro这个Java安全框架我一直都想学会怎么去使用,但每次依照着别人的博客尝试把它配置到自己的项目中,总是出现各种问题,导致一直没有成功。经过不懈努力,这一次终于成功了!从零搭建整个项目,并通过一个简单的用户登录功能来进行说明!
1.jdk1.8 / tomcat7、
2.HTML / CSS / JavaScript / thymeleaf / layui
3.shiro / springboot / mybatis-plus / mysql5.7
spring:
profiles:
active: dev #yml切换
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
servlet:
content-type: text/html
jackson:
date-format: yyyy-MM-dd HH:mm:ss #指定日期格式,比如yyyy-MM-dd HH:mm:ss
time-zone: GMT+8 #指定时区
aop:
proxy-target-class: true #基于类的代理将起作用(这时需要cglib库)
mybatis-plus:
# 实体类扫描路径
type-aliases-package: com.liuhu.rainbow.system.entity
# xml 扫描路径
mapper-locations: classpath:mapper/*/*.xml
configuration:
jdbc-type-for-null: null
global-config:
# 关闭 mybatis-plus的 banner
banner: false
rainbow:
version: v1.0
server:
port: 8081
tomcat:
uri-encoding: UTF-8
spring:
datasource:
dynamic:
# 是否开启 SQL日志输出,生产环境建议关闭,有性能损耗
p6spy: true
hikari:
connection-timeout: 30000
max-lifetime: 1800000
max-pool-size: 15
min-idle: 5
connection-test-query: select 1
pool-name: FebsHikariCP
# 配置默认数据源
primary: base
datasource:
# 数据源-1,名称为 base
base:
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://129.211.60.109:3306/febs_base?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
redis:
# Redis数据库索引(默认为 0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis 密码
password: root
jedis:
pool:
# 连接池中的最小空闲连接
min-idle: 8
# 连接池中的最大空闲连接
max-idle: 500
# 连接池最大连接数(使用负值表示没有限制)
max-active: 2000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: 10000
# 连接超时时间(毫秒)
timeout: 0
4.0.0
com.liuhu
rainbow
0.0.1-SNAPSHOT
rainbow
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
1.8
UTF-8
UTF-8
3.1.1
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
com.baomidou
mybatis-plus-boot-starter
${mybatis.plus.version}
com.baomidou
mybatis-plus-generator
${mybatis.plus.version}
com.baomidou
dynamic-datasource-spring-boot-starter
2.5.4
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-cache
org.freemarker
freemarker
2.3.28
p6spy
p6spy
3.8.1
mysql
mysql-connector-java
runtime
org.apache.shiro
shiro-spring
1.4.0
org.crazycake
shiro-redis
3.2.2
com.github.theborakompanioni
thymeleaf-extras-shiro
2.0.0
org.apache.commons
commons-lang3
commons-io
commons-io
2.6
com.google.guava
guava
27.0-jre
org.projectlombok
lombok
true
com.alibaba
fastjson
1.2.44
com.github.whvcse
EasyCaptcha
1.5.0
jitpack.io
https://jitpack.io
org.springframework.boot
spring-boot-maven-plugin
package com.liuhu.rainbow.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;
/**
* 菜单表
* @author melo、lh
* @createTime 2019-07-03 17:37:58
*/
@Data
@TableName("t_menu")
public class Menu implements Serializable {
private static final long serialVersionUID = 8571011372410167901L;
// 菜单
public static final String TYPE_MENU = "0";
// 按钮
public static final String TYPE_BUTTON = "1";
public static final Long TOP_NODE = 0L;
/**
* 菜单/按钮ID
*/
@TableId(value = "MENU_ID", type = IdType.AUTO)
private Long menuId;
/**
* 上级菜单ID
*/
@TableField("PARENT_ID")
private Long parentId;
/**
* 菜单/按钮名称
*/
@TableField("MENU_NAME")
@NotBlank(message = "{required}")
private String menuName;
/**
* 菜单URL
*/
@TableField("URL")
private String url;
/**
* 权限标识
*/
@TableField("PERMS")
private String perms;
/**
* 图标
*/
@TableField("ICON")
private String icon;
/**
* 类型 0菜单 1按钮
*/
@TableField("TYPE")
private String type;
/**
* 排序
*/
@TableField("ORDER_NUM")
private Long orderNum;
/**
* 创建时间
*/
@TableField("CREATE_TIME")
private Date createTime;
/**
* 修改时间
*/
@TableField("MODIFY_TIME")
private Date modifyTime;
}
package com.liuhu.rainbow.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;
/**
* 角色表
* @author melo、lh
* @createTime 2019-07-03 17:35:21
*/
@Data
@TableName("t_role")
public class Role implements Serializable {
private static final long serialVersionUID = -4493960686192269860L;
/**
* 角色ID
*/
@TableId(value = "ROLE_ID", type = IdType.AUTO)
private Long roleId;
/**
* 角色名称
*/
@TableField("ROLE_NAME")
private String roleName;
/**
* 角色描述
*/
@TableField("REMARK")
private String remark;
/**
* 创建时间
*/
@TableField("CREATE_TIME")
private Date createTime;
/**
* 修改时间
*/
@TableField("MODIFY_TIME")
private Date modifyTime;
/**
* 角色对应的菜单(按钮) id
*/
private transient String menuIds;
}
package com.liuhu.rainbow.system.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
* 角色菜单
* @author melo、lh
* @createTime 2019-07-03 17:37:58
*/
@Data
@TableName("t_role_menu")
public class RoleMenu implements Serializable {
private static final long serialVersionUID = -5200596408874170216L;
/**
* 角色ID
*/
@TableField("ROLE_ID")
private Long roleId;
/**
* 菜单/按钮ID
*/
@TableField("MENU_ID")
private Long menuId;
}
package com.liuhu.rainbow.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;
/**
* 用户表
* @author melo、lh
* @createTime 2019-07-05 17:00:55
*/
@Data
@TableName("t_user")
public class User implements Serializable {
private static final long serialVersionUID = -4352868070794165001L;
// 用户状态:有效
public static final String STATUS_VALID = "1";
// 用户状态:锁定
public static final String STATUS_LOCK = "0";
// 默认头像
public static final String DEFAULT_AVATAR = "default.jpg";
// 默认密码
public static final String DEFAULT_PASSWORD = "1234qwer";
// 性别男
public static final String SEX_MALE = "0";
// 性别女
public static final String SEX_FEMALE = "1";
// 性别保密
public static final String SEX_UNKNOW = "2";
// 黑色主题
public static final String THEME_BLACK = "black";
// 白色主题
public static final String THEME_WHITE = "white";
// TAB开启
public static final String TAB_OPEN = "1";
// TAB关闭
public static final String TAB_CLOSE = "0";
/**
* 用户 ID
*/
@TableId(value = "USER_ID", type = IdType.AUTO)
private Long userId;
/**
* 用户名
*/
@TableField("USERNAME")
private String username;
/**
* 密码
*/
@TableField("PASSWORD")
private String password;
/**
* 部门 ID
*/
@TableField("DEPT_ID")
private Long deptId;
/**
* 邮箱
*/
@TableField("EMAIL")
private String email;
/**
* 联系电话
*/
@TableField("MOBILE")
private String mobile;
/**
* 状态 0锁定 1有效
*/
@TableField("STATUS")
@NotBlank(message = "{required}")
private String status;
/**
* 创建时间
*/
@TableField("CREATE_TIME")
private Date createTime;
/**
* 修改时间
*/
@TableField("MODIFY_TIME")
private Date modifyTime;
/**
* 最近访问时间
*/
@TableField("LAST_LOGIN_TIME")
@JsonFormat(pattern = "yyyy年MM月dd日 HH时mm分ss秒", timezone = "GMT+8")
private Date lastLoginTime;
/**
* 性别 0男 1女 2 保密
*/
@TableField("SSEX")
private String sex;
/**
* 头像
*/
@TableField("AVATAR")
private String avatar;
/**
* 主题
*/
@TableField("THEME")
private String theme;
/**
* 是否开启 tab 0开启,1关闭
*/
@TableField("IS_TAB")
private String isTab;
/**
* 描述
*/
@TableField("DESCRIPTION")
private String description;
/**
* 部门名称
*/
@TableField(exist = false)
private String deptName;
@TableField(exist = false)
private String createTimeFrom;
@TableField(exist = false)
private String createTimeTo;
/**
* 角色 ID
*/
@TableField(exist = false)
private String roleId;
@TableField(exist = false)
private String roleName;
public Long getId() {
return userId;
}
}
package com.liuhu.rainbow.system.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
* 角色用户表
* @author melo、lh
* @createTime 2019-07-05 17:01:05
*/
@Data
@TableName("t_user_role")
public class UserRole implements Serializable {
private static final long serialVersionUID = 2354394771912648574L;
/**
* 用户ID
*/
@TableField("USER_ID")
private Long userId;
/**
* 角色ID
*/
@TableField("ROLE_ID")
private Long roleId;
}
package com.liuhu.rainbow.system.service;
import com.liuhu.rainbow.system.entity.User;
/**
* 用户业务层接口
* @author melo、lh
* @createTime 2019-07-05 17:33:11
*/
public interface IUserService {
/**
* 通过用户名查找用户
* @param username
* @return com.liuhu.rainbow.system.entity.User
* @author melo、lh
* @createTime 2019-07-04 08:56:35
*/
User findByName(String username);
}
package com.liuhu.rainbow.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.liuhu.rainbow.system.entity.Role;
import java.util.List;
/**
* 角色业务层接口
* @author melo、lh
* @createTime 2019-07-05 17:33:11
*/
public interface IRoleService {
/**
* 通过用户名查找用户角色
* @param username 用户名
* @return java.util.List
* @author melo、lh
* @createTime 2019-07-04 09:14:17
*/
List findUserRole(String username);
}
package com.liuhu.rainbow.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.liuhu.rainbow.common.entity.MenuTree;
import com.liuhu.rainbow.system.entity.Menu;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 权限业务层接口
* @author melo、lh
* @createTime 2019-07-04 09:30:34
*/
public interface IMenuService {
/**
* 查找用户权限集
* @param username
* @return java.util.List
* @author melo、lh
* @createTime 2019-07-04 09:30:18
*/
List
package com.liuhu.rainbow.system.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liuhu.rainbow.system.entity.User;
import com.liuhu.rainbow.system.mapper.UserMapper;
import com.liuhu.rainbow.system.service.IUserRoleService;
import com.liuhu.rainbow.system.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 用户业务层实现类
* @author melo、lh
* @createTime 2019-07-05 17:32:27
*/
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
public User findByName(String username) {
return userMapper.findByName(username);
}
}
package com.liuhu.rainbow.system.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liuhu.rainbow.system.entity.Role;
import com.liuhu.rainbow.system.mapper.RoleMapper;
import com.liuhu.rainbow.system.service.IRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 角色业务层实现类
* @author melo、lh
* @createTime 2019-07-05 17:30:01
*/
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class RoleServiceImpl implements IRoleService {
@Autowired
private RoleMapper roleMapper;
@Override
public List findUserRole(String username) {
return roleMapper.findUserRole(username);
}
}
package com.liuhu.rainbow.system.service.impl;
import com.liuhu.rainbow.common.entity.MenuTree;
import com.liuhu.rainbow.common.util.TreeUtil;
import com.liuhu.rainbow.system.entity.Menu;
import com.liuhu.rainbow.system.mapper.MenuMapper;
import com.liuhu.rainbow.system.service.IMenuService;
import com.sun.xml.internal.bind.v2.TODO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/**
* 资源业务层实现类
* @author melo、lh
* @createTime 2019-07-04 09:32:55
*/
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class MenuServiceImpl implements IMenuService {
@Autowired
private MenuMapper menuMapper;
@Override
public List findUserPermissions(String username) {
return menuMapper.findUserPermissions(username);
}
}
package com.liuhu.rainbow.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.liuhu.rainbow.system.entity.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 用户持久层
* @author melo、lh
* @createTime 2019-07-04 09:00:08
*/
@Repository
public interface UserMapper extends BaseMapper {
/**
* 通过用户名查找用户
* @param username
* @return com.liuhu.rainbow.system.entity.User
* @author melo、lh
* @createTime 2019-07-04 08:56:35
*/
User findByName(@Param("username") String username);
}
package com.liuhu.rainbow.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.liuhu.rainbow.system.entity.Role;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 角色持久层
* @author melo、lh
* @createTime 2019-07-04 09:14:44
*/
@Repository
public interface RoleMapper extends BaseMapper {
/**
* 通过用户名查找用户角色
* @param username 用户名
* @return java.util.List
* @author melo、lh
* @createTime 2019-07-04 09:14:17
*/
List findUserRole(@Param("username") String username);
}
package com.liuhu.rainbow.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liuhu.rainbow.common.entity.MenuTree;
import com.liuhu.rainbow.system.entity.Menu;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 资源持久层
* @author melo、lh
* @createTime 2019-07-04 09:33:51
*/
@Repository
public interface MenuMapper extends BaseMapper {
/**
* 查找用户权限集
* @param username
* @return java.util.List
* @author melo、lh
* @createTime 2019-07-04 09:30:18
*/
List findUserPermissions(@Param("username")String username);
/**
* 查找用户权限集
* @param username
* @return java.util.List
* @author melo、lh
* @createTime 2019-07-04 09:30:18
*/
List findUserMenus(@Param("username") String username);
}
package com.liuhu.rainbow.common.shiro;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.liuhu.rainbow.common.properties.RainbowProperties;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.util.Base64Utils;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
/**
* Shiro 配置类
*
* @author MrBird
*/
@Configuration
public class ShiroConfig {
@Autowired
private RainbowProperties rainbowProperties;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.database:0}")
private int database;
/**
* shiro 中配置 redis 缓存
* @return org.crazycake.shiro.RedisManager
* @author melo、lh
* @createTime 2019-07-05 16:48:34
*/
private RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host + ":" + port);
if (StringUtils.isNotBlank(password)){
redisManager.setPassword(password);
}
redisManager.setTimeout(timeout);
redisManager.setDatabase(database);
return redisManager;
}
/**
* 缓存管理
* @return org.crazycake.shiro.RedisManager
* @author melo、lh
* @createTime 2019-07-05 16:48:34
*/
private RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* shiro请求过滤处理
* @param securityManager
* @return org.apache.shiro.spring.web.ShiroFilterFactoryBean
* @author melo、lh
* @createTime 2019-07-05 16:49:14
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置 securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 登录的 url
shiroFilterFactoryBean.setLoginUrl(rainbowProperties.getShiro().getLoginUrl());
// 登录成功后跳转的 url
shiroFilterFactoryBean.setSuccessUrl(rainbowProperties.getShiro().getSuccessUrl());
// 未授权 url
shiroFilterFactoryBean.setUnauthorizedUrl(rainbowProperties.getShiro().getUnauthorizedUrl());
LinkedHashMap filterChainDefinitionMap = new LinkedHashMap<>();
// 设置免认证 url
String[] anonUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(rainbowProperties.getShiro().getAnonUrl(), ",");
for (String url : anonUrls) {
filterChainDefinitionMap.put(url, "anon");
}
// 配置退出过滤器,其中具体的退出代码 Shiro已经替我们实现了
filterChainDefinitionMap.put(rainbowProperties.getShiro().getLogoutUrl(), "logout");
// 除上以外所有 url都必须认证通过才可以访问,未通过认证自动访问 LoginUrl
filterChainDefinitionMap.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 配置安全管理器
* @param shiroRealm
* @return org.apache.shiro.mgt.SecurityManager
* @author melo、lh
* @createTime 2019-07-05 16:49:54
*/
@Bean
public SecurityManager securityManager(ShiroRealm shiroRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 配置 SecurityManager,并注入 shiroRealm
securityManager.setRealm(shiroRealm);
// 配置 shiro session管理器
securityManager.setSessionManager(sessionManager());
// 配置 缓存管理类 cacheManager
securityManager.setCacheManager(cacheManager());
// 配置 rememberMeCookie
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* rememberMe cookie 效果是重开浏览器后无需重新登录
* @return org.apache.shiro.web.servlet.SimpleCookie
* @author melo、lh
* @createTime 2019-07-05 16:50:14
*/
private SimpleCookie rememberMeCookie() {
// 设置 cookie 名称,对应 login.html 页面的
SimpleCookie cookie = new SimpleCookie("rememberMe");
// 设置 cookie 的过期时间,单位为秒,这里为一天
cookie.setMaxAge(rainbowProperties.getShiro().getCookieTimeout());
return cookie;
}
/**
* cookie管理对象
* @return org.apache.shiro.web.mgt.CookieRememberMeManager
* @author melo、lh
* @createTime 2019-07-05 16:50:29
*/
private CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
// rememberMe cookie 加密的密钥
String encryptKey = "febs_shiro_key";
byte[] encryptKeyBytes = encryptKey.getBytes(StandardCharsets.UTF_8);
String rememberKey = Base64Utils.encodeToString(Arrays.copyOf(encryptKeyBytes, 16));
cookieRememberMeManager.setCipherKey(Base64.decode(rememberKey));
return cookieRememberMeManager;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 用于开启 Thymeleaf 中的 shiro 标签的使用
* @return at.pollux.thymeleaf.shiro.dialect.ShiroDialect
* @author melo、lh
* @createTime 2019-07-05 16:50:43
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* session 管理对象
* @return org.apache.shiro.web.session.mgt.DefaultWebSessionManager
* @author melo、lh
* @createTime 2019-07-05 16:51:05
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection listeners = new ArrayList<>();
listeners.add(new ShiroSessionListener());
// 设置 session超时时间
sessionManager.setGlobalSessionTimeout(rainbowProperties.getShiro().getSessionTimeout() * 1000L);
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
}
package com.liuhu.rainbow.common.shiro;
import com.liuhu.rainbow.system.entity.Menu;
import com.liuhu.rainbow.system.entity.Role;
import com.liuhu.rainbow.system.entity.User;
import com.liuhu.rainbow.system.service.IMenuService;
import com.liuhu.rainbow.system.service.IRoleService;
import com.liuhu.rainbow.system.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 自定义实现 ShiroRealm,包含认证和授权两大模块
* @author melo、lh
* @createTime 2019-07-05 16:52:00
*/
@Component
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private IUserService userService;
@Autowired
private IRoleService roleService;
@Autowired
private IMenuService menuService;
/**
* 授权模块,获取用户角色和权限
* @param principal
* @return org.apache.shiro.authz.AuthorizationInfo
* @author melo、lh
* @createTime 2019-07-05 16:52:09
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
User user = (User) SecurityUtils.getSubject().getPrincipal();
String username = user.getUsername();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 获取用户角色集
List roleList = this.roleService.findUserRole(username);
// 角色名称集合
Set roleSet = new HashSet<>();
for (Role role: roleList) {
roleSet.add(role.getRoleName());
}
/*Set roleSet = roleList.stream().map(Role::getRoleName).collect(Collectors.toSet());*/
info.setRoles(roleSet);
// 获取用户权限集合
List permissionList = this.menuService.findUserPermissions(username);
// 权限集合
Set permissionSet = new HashSet<>();
for (Menu menu: permissionList) {
permissionSet .add(menu.getPerms());
}
/* Set permissionSet = permissionList.stream().map(Menu::getPerms).collect(Collectors.toSet());*/
info.setStringPermissions(permissionSet);
return info;
}
/**
* 用户认证
* @param token 身份认证
* @return org.apache.shiro.authc.AuthenticationInfo
* @author melo、lh
* @createTime 2019-07-05 16:53:14
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取用户输入的用户名和密码
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
// 通过用户名到数据库查询用户信息
User user = this.userService.findByName(username);
if (user == null){
throw new UnknownAccountException("用户名或密码错误!");
}
if (!StringUtils.equals(password, user.getPassword())) {
throw new IncorrectCredentialsException("用户名或密码错误!");
}
if (User.STATUS_LOCK.equals(user.getStatus())){
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}
return new SimpleAuthenticationInfo(user,password,this.getName());
}
/**
* 清除当前用户权限缓存
* 使用方法:在需要清除用户权限的地方注入 ShiroRealm,
* 然后调用其 clearCache方法。
* @return void
* @author melo、lh
* @createTime 2019-07-05 16:52:40
*/
public void clearCache() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
}
package cc.mrbird.febs.common.configure;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.Arrays;
@Configuration
public class RedisConfigure extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.jedis.pool.max-wait}")
private long maxWaitMillis;
@Value("${spring.redis.database:0}")
private int database;
@Bean
public JedisPool redisPoolFactory() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
if (StringUtils.isNotBlank(password)) {
return new JedisPool(jedisPoolConfig, host, port, timeout, password, database);
} else {
return new JedisPool(jedisPoolConfig, host, port, timeout, null, database);
}
}
@Bean
JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
redisStandaloneConfiguration.setDatabase(database);
JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder();
jedisClientConfiguration.connectTimeout(Duration.ofMillis(timeout));
jedisClientConfiguration.usePooling();
return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration.build());
}
@Bean(name = "redisTemplate")
@SuppressWarnings({"unchecked", "rawtypes"})
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate
package com.liuhu.rainbow.common.function;
import com.liuhu.rainbow.common.exception.RedisConnectException;
/**
*
* @author melo、lh
* @createTime 2019-07-05 16:47:32
*/
@FunctionalInterface
public interface JedisExecutor {
R excute(T t) throws RedisConnectException;
}
package com.liuhu.rainbow.common.properties;
import lombok.Data;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
/**
* 讀取shiro配置文件屬性
* @author melo、lh
* @createTime 2019-07-03 17:57:50
*/
@Data
@SpringBootConfiguration
@PropertySource(value = {"classpath:rainbow.properties"})
@ConfigurationProperties(prefix = "rainbow")
public class RainbowProperties {
private ShiroProperties shiro = new ShiroProperties();
private boolean openAopLog = true;
}
package com.liuhu.rainbow.common.properties;
import lombok.Data;
/**
* shiro属性
* @author melo、lh
* @createTime 2019-07-05 16:48:09
*/
@Data
public class ShiroProperties {
private long sessionTimeout;
private int cookieTimeout;
private String anonUrl;
private String loginUrl;
private String successUrl;
private String logoutUrl;
private String unauthorizedUrl;
}
package com.liuhu.rainbow.common.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
/**
* MD5
* @author melo、lh
* @createTime 2019-07-05 16:59:32
*/
public class MD5Util {
protected MD5Util() {
}
/**加密方式*/
private static final String ALGORITH_NAME = "md5";
/**加密测试*/
private static final int HASH_ITERATIONS = 1024;
/**
* 通过MD5加密
* @param username 用户名
* @param password 密码
* @return java.lang.String
* @author melo、lh
* @createTime 2019-07-05 16:43:41
*/
public static String encrypt(String username, String password) {
// 小写的用户名作为盐加密
String source = StringUtils.lowerCase(username);
password = StringUtils.lowerCase(password);
return new SimpleHash(ALGORITH_NAME, password, ByteSource.Util.bytes(source), HASH_ITERATIONS).toHex();
}
public static void main(String[] args) {
String encrypt = encrypt("admin", "admin");
System.out.println(encrypt);
}
}
package com.liuhu.rainbow.common.util;
import com.liuhu.rainbow.common.constant.RainbowConstant;
import com.liuhu.rainbow.common.exception.RedisConnectException;
import com.liuhu.rainbow.monitor.service.IRedisService;
import com.wf.captcha.Captcha;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.io.IOException;
/**
* 验证码工具类
* @author melo、lh
* @createTime 2019-07-05 16:57:24
*/
@Slf4j
public class CaptchaUtil {
private static IRedisService redisService = SpringContextUtil.getBean(IRedisService.class);
// gif 类型验证码
private static final int GIF_TYPE = 1;
// png 类型验证码
private static final int PNG_TYPE = 0;
// 验证码图片默认高度
private static final int DEFAULT_HEIGHT = 48;
// 验证码图片默认宽度
private static final int DEFAULT_WIDTH = 130;
// 验证码默认位数
private static final int DEFAULT_LEN = 5;
public static void out(HttpServletRequest request, HttpServletResponse response) throws IOException {
out(DEFAULT_LEN, request, response);
}
public static void out(int len, HttpServletRequest request, HttpServletResponse response) throws IOException {
out(DEFAULT_WIDTH, DEFAULT_HEIGHT, len, null, request, response);
}
public static void out(int len, Font font, HttpServletRequest request, HttpServletResponse response) throws IOException {
out(DEFAULT_WIDTH, DEFAULT_HEIGHT, len, null, font, request, response);
}
public static void out(int width, int height, int len, Integer vType, HttpServletRequest request, HttpServletResponse response) throws IOException {
out(width, height, len, vType, null, request, response);
}
public static void out(int width, int height, int len, Integer vType, Font font, HttpServletRequest request, HttpServletResponse response) throws IOException {
outCaptcha(width, height, len, font, GIF_TYPE, vType, request, response);
}
public static void outPng(HttpServletRequest request, HttpServletResponse response) throws IOException {
outPng(DEFAULT_LEN, request, response);
}
public static void outPng(int len, HttpServletRequest request, HttpServletResponse response) throws IOException {
outPng(DEFAULT_WIDTH, DEFAULT_HEIGHT, len, null, request, response);
}
public static void outPng(int len, Font font, HttpServletRequest request, HttpServletResponse response) throws IOException {
outPng(DEFAULT_WIDTH, DEFAULT_HEIGHT, len, null, font, request, response);
}
public static void outPng(int width, int height, int len, Integer vType, HttpServletRequest request, HttpServletResponse response) throws IOException {
outPng(width, height, len, vType, null, request, response);
}
public static void outPng(int width, int height, int len, Integer vType, Font font, HttpServletRequest request, HttpServletResponse response) throws IOException {
outCaptcha(width, height, len, font, PNG_TYPE, vType, request, response);
}
public static boolean verify(String code, HttpServletRequest request) {
HttpSession session = request.getSession();
String key = RainbowConstant.CODE_PREFIX + session.getId();
String sessionCode = "";
try {
sessionCode = redisService.get(key);
} catch (RedisConnectException e) {
log.error("获取验证码异常", e);
}
return StringUtils.equalsIgnoreCase(code, sessionCode);
}
private static void outCaptcha(int width, int height, int len, Font font, int cType, Integer vType, HttpServletRequest request, HttpServletResponse response) throws IOException {
setHeader(response, cType);
Captcha captcha = null;
if (cType == GIF_TYPE) {
captcha = new GifCaptcha(width, height, len);
} else {
captcha = new SpecCaptcha(width, height, len);
}
if (font != null) {
captcha.setFont(font);
}
if (vType != null) {
captcha.setCharType(vType);
}
HttpSession session = request.getSession();
String code = captcha.text().toLowerCase();
String key = RainbowConstant.CODE_PREFIX + session.getId();
try {
redisService.set(key, code, 120000L);
} catch (RedisConnectException e) {
log.error("保存验证码异常", e);
}
captcha.out(response.getOutputStream());
}
public static void setHeader(HttpServletResponse response, int cType) {
if (cType == GIF_TYPE) {
response.setContentType("image/gif");
} else {
response.setContentType("image/png");
}
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0L);
}
}
package com.liuhu.rainbow.common.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;
/**
* 时间工具类
* @author melo、lh
* @createTime 2019-07-05 16:57:45
*/
public class DateUtil {
public static final String FULL_TIME_PATTERN = "yyyyMMddHHmmss";
public static final String FULL_TIME_SPLIT_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String CST_TIME_PATTERN = "EEE MMM dd HH:mm:ss zzz yyyy";
public static String formatFullTime(LocalDateTime localDateTime) {
return formatFullTime(localDateTime, FULL_TIME_PATTERN);
}
public static String formatFullTime(LocalDateTime localDateTime, String pattern) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
return localDateTime.format(dateTimeFormatter);
}
public static String getDateFormat(Date date, String dateFormatType) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormatType, Locale.CHINA);
return simpleDateFormat.format(date);
}
public static String formatCSTTime(String date, String format) throws ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(CST_TIME_PATTERN, Locale.US);
Date usDate = simpleDateFormat.parse(date);
return DateUtil.getDateFormat(usDate, format);
}
public static String formatInstant(Instant instant, String format) {
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
return localDateTime.format(DateTimeFormatter.ofPattern(format));
}
}
package com.liuhu.rainbow.monitor.entity;
import lombok.Data;
import lombok.ToString;
import java.util.HashMap;
import java.util.Map;
/**
* @author MrBird
*/
@Data
@ToString
public class RedisInfo {
private static Map map = new HashMap<>();
static {
map.put("redis_version", "Redis 服务器版本");
map.put("redis_git_sha1", "Git SHA1");
map.put("redis_git_dirty", "Git dirty flag");
map.put("os", "Redis 服务器的宿主操作系统");
map.put("arch_bits", " 架构(32 或 64 位)");
map.put("multiplexing_api", "Redis 所使用的事件处理机制");
map.put("gcc_version", "编译 Redis 时所使用的 GCC 版本");
map.put("process_id", "服务器进程的 PID");
map.put("run_id", "Redis 服务器的随机标识符(用于 Sentinel 和集群)");
map.put("tcp_port", "TCP/IP 监听端口");
map.put("uptime_in_seconds", "自 Redis 服务器启动以来,经过的秒数");
map.put("uptime_in_days", "自 Redis 服务器启动以来,经过的天数");
map.put("lru_clock", " 以分钟为单位进行自增的时钟,用于 LRU 管理");
map.put("connected_clients", "已连接客户端的数量(不包括通过从属服务器连接的客户端)");
map.put("client_longest_output_list", "当前连接的客户端当中,最长的输出列表");
map.put("client_longest_input_buf", "当前连接的客户端当中,最大输入缓存");
map.put("blocked_clients", "正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量");
map.put("used_memory", "由 Redis 分配器分配的内存总量,以字节(byte)为单位");
map.put("used_memory_human", "以人类可读的格式返回 Redis 分配的内存总量");
map.put("used_memory_rss", "从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps 等命令的输出一致");
map.put("used_memory_peak", " Redis 的内存消耗峰值(以字节为单位)");
map.put("used_memory_peak_human", "以人类可读的格式返回 Redis 的内存消耗峰值");
map.put("used_memory_lua", "Lua 引擎所使用的内存大小(以字节为单位)");
map.put("mem_fragmentation_ratio", "sed_memory_rss 和 used_memory 之间的比率");
map.put("mem_allocator", "在编译时指定的, Redis 所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc");
map.put("redis_build_id", "redis_build_id");
map.put("redis_mode", "运行模式,单机(standalone)或者集群(cluster)");
map.put("atomicvar_api", "atomicvar_api");
map.put("hz", "redis内部调度(进行关闭timeout的客户端,删除过期key等等)频率,程序规定serverCron每秒运行10次。");
map.put("executable", "server脚本目录");
map.put("config_file", "配置文件目录");
map.put("client_biggest_input_buf", "当前连接的客户端当中,最大输入缓存,用client list命令观察qbuf和qbuf-free两个字段最大值");
map.put("used_memory_rss_human", "以人类可读的方式返回 Redis 已分配的内存总量");
map.put("used_memory_peak_perc", "内存使用率峰值");
map.put("total_system_memory", "系统总内存");
map.put("total_system_memory_human", "以人类可读的方式返回系统总内存");
map.put("used_memory_lua_human", "以人类可读的方式返回Lua 引擎所使用的内存大小");
map.put("maxmemory", "最大内存限制,0表示无限制");
map.put("maxmemory_human", "以人类可读的方式返回最大限制内存");
map.put("maxmemory_policy", "超过内存限制后的处理策略");
map.put("loading", "服务器是否正在载入持久化文件");
map.put("rdb_changes_since_last_save", "离最近一次成功生成rdb文件,写入命令的个数,即有多少个写入命令没有持久化");
map.put("rdb_bgsave_in_progress", "服务器是否正在创建rdb文件");
map.put("rdb_last_save_time", "离最近一次成功创建rdb文件的时间戳。当前时间戳 - rdb_last_save_time=多少秒未成功生成rdb文件");
map.put("rdb_last_bgsave_status", "最近一次rdb持久化是否成功");
map.put("rdb_last_bgsave_time_sec", "最近一次成功生成rdb文件耗时秒数");
map.put("rdb_current_bgsave_time_sec", "如果服务器正在创建rdb文件,那么这个域记录的就是当前的创建操作已经耗费的秒数");
map.put("aof_enabled", "是否开启了aof");
map.put("aof_rewrite_in_progress", "标识aof的rewrite操作是否在进行中");
map.put("aof_rewrite_scheduled",
"rewrite任务计划,当客户端发送bgrewriteaof指令,如果当前rewrite子进程正在执行,那么将客户端请求的bgrewriteaof变为计划任务,待aof子进程结束后执行rewrite ");
map.put("aof_last_rewrite_time_sec", "最近一次aof rewrite耗费的时长");
map.put("aof_current_rewrite_time_sec", "如果rewrite操作正在进行,则记录所使用的时间,单位秒");
map.put("aof_last_bgrewrite_status", "上次bgrewrite aof操作的状态");
map.put("aof_last_write_status", "上次aof写入状态");
map.put("total_commands_processed", "redis处理的命令数");
map.put("total_connections_received", "新创建连接个数,如果新创建连接过多,过度地创建和销毁连接对性能有影响,说明短连接严重或连接池使用有问题,需调研代码的连接设置");
map.put("instantaneous_ops_per_sec", "redis当前的qps,redis内部较实时的每秒执行的命令数");
map.put("total_net_input_bytes", "redis网络入口流量字节数");
map.put("total_net_output_bytes", "redis网络出口流量字节数");
map.put("instantaneous_input_kbps", "redis网络入口kps");
map.put("instantaneous_output_kbps", "redis网络出口kps");
map.put("rejected_connections", "拒绝的连接个数,redis连接个数达到maxclients限制,拒绝新连接的个数");
map.put("sync_full", "主从完全同步成功次数");
map.put("sync_partial_ok", "主从部分同步成功次数");
map.put("sync_partial_err", "主从部分同步失败次数");
map.put("expired_keys", "运行以来过期的key的数量");
map.put("evicted_keys", "运行以来剔除(超过了maxmemory后)的key的数量");
map.put("keyspace_hits", "命中次数");
map.put("keyspace_misses", "没命中次数");
map.put("pubsub_channels", "当前使用中的频道数量");
map.put("pubsub_patterns", "当前使用的模式的数量");
map.put("latest_fork_usec", "最近一次fork操作阻塞redis进程的耗时数,单位微秒");
map.put("role", "实例的角色,是master or slave");
map.put("connected_slaves", "连接的slave实例个数");
map.put("master_repl_offset", "主从同步偏移量,此值如果和上面的offset相同说明主从一致没延迟");
map.put("repl_backlog_active", "复制积压缓冲区是否开启");
map.put("repl_backlog_size", "复制积压缓冲大小");
map.put("repl_backlog_first_byte_offset", "复制缓冲区里偏移量的大小");
map.put("repl_backlog_histlen",
"此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小");
map.put("used_cpu_sys", "将所有redis主进程在核心态所占用的CPU时求和累计起来");
map.put("used_cpu_user", "将所有redis主进程在用户态所占用的CPU时求和累计起来");
map.put("used_cpu_sys_children", "将后台进程在核心态所占用的CPU时求和累计起来");
map.put("used_cpu_user_children", "将后台进程在用户态所占用的CPU时求和累计起来");
map.put("cluster_enabled", "实例是否启用集群模式");
map.put("db0", "db0的key的数量,以及带有生存期的key的数,平均存活时间");
}
private String key;
private String value;
private String description;
public void setKey(String key) {
this.key = key;
this.description = map.get(this.key);
}
}
package com.liuhu.rainbow.monitor.service;
import com.liuhu.rainbow.common.exception.RedisConnectException;
import com.liuhu.rainbow.monitor.entity.RedisInfo;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* redis工具类接口
* @author melo、lh
* @createTime 2019-07-05 17:00:00
*/
public interface IRedisService {
/**
* 获取 redis 的详细信息
*
* @return List
*/
List getRedisInfo() throws RedisConnectException;
/**
* 获取 redis key 数量
*
* @return Map
*/
Map getKeysSize() throws RedisConnectException;
/**
* 获取 redis 内存信息
*
* @return Map
*/
Map getMemoryInfo() throws RedisConnectException;
/**
* 获取 key
*
* @param pattern 正则
* @return Set
*/
Set getKeys(String pattern) throws RedisConnectException;
/**
* get命令
*
* @param key key
* @return String
*/
String get(String key) throws RedisConnectException;
/**
* set命令
*
* @param key key
* @param value value
* @return String
*/
String set(String key, String value) throws RedisConnectException;
/**
* set 命令
*
* @param key key
* @param value value
* @param milliscends 毫秒
* @return String
*/
String set(String key, String value, Long milliscends) throws RedisConnectException;
/**
* del命令
*
* @param key key
* @return Long
*/
Long del(String... key) throws RedisConnectException;
/**
* exists命令
*
* @param key key
* @return Boolean
*/
Boolean exists(String key) throws RedisConnectException;
/**
* pttl命令
*
* @param key key
* @return Long
*/
Long pttl(String key) throws RedisConnectException;
/**
* pexpire命令
*
* @param key key
* @param milliscends 毫秒
* @return Long
*/
Long pexpire(String key, Long milliscends) throws RedisConnectException;
/**
* zadd 命令
*
* @param key key
* @param score score
* @param member value
*/
Long zadd(String key, Double score, String member) throws RedisConnectException;
/**
* zrangeByScore 命令
*
* @param key key
* @param min min
* @param max max
* @return Set
*/
Set zrangeByScore(String key, String min, String max) throws RedisConnectException;
/**
* zremrangeByScore 命令
*
* @param key key
* @param start start
* @param end end
* @return Long
*/
Long zremrangeByScore(String key, String start, String end) throws RedisConnectException;
/**
* zrem 命令
*
* @param key key
* @param members members
* @return Long
*/
Long zrem(String key, String... members) throws RedisConnectException;
}
package com.liuhu.rainbow.monitor.service.impl;
import com.liuhu.rainbow.common.exception.RedisConnectException;
import com.liuhu.rainbow.common.function.JedisExecutor;
import com.liuhu.rainbow.monitor.entity.RedisInfo;
import com.liuhu.rainbow.monitor.service.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Client;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.*;
/**
* Redis 工具类,只封装了几个常用的 redis 命令,可根据实际需要按类似的方式扩展即可。
* @author melo、lh
* @createTime 2019-07-05 16:58:52
*/
@Component
public class RedisServiceImpl implements IRedisService {
@Autowired
JedisPool jedisPool;
private static String separator = System.getProperty("line.separator");
/**
* 处理 jedis请求
*
* @param j 处理逻辑,通过 lambda行为参数化
* @return 处理结果
*/
private T excuteByJedis(JedisExecutor j) throws RedisConnectException {
try (Jedis jedis = jedisPool.getResource()) {
return j.excute(jedis);
} catch (Exception e) {
throw new RedisConnectException(e.getMessage());
}
}
@Override
public List getRedisInfo() throws RedisConnectException {
String info = this.excuteByJedis(
j -> {
Client client = j.getClient();
client.info();
return client.getBulkReply();
}
);
List infoList = new ArrayList<>();
String[] strs = Objects.requireNonNull(info).split(separator);
RedisInfo redisInfo;
if (strs.length > 0) {
for (String str1 : strs) {
redisInfo = new RedisInfo();
String[] str = str1.split(":");
if (str.length > 1) {
String key = str[0];
String value = str[1];
redisInfo.setKey(key);
redisInfo.setValue(value);
infoList.add(redisInfo);
}
}
}
return infoList;
}
@Override
public Map getKeysSize() throws RedisConnectException {
Long dbSize = this.excuteByJedis(
j -> {
Client client = j.getClient();
client.dbSize();
return client.getIntegerReply();
}
);
Map map = new HashMap<>();
map.put("dbSize", dbSize);
return map;
}
@Override
public Map getMemoryInfo() throws RedisConnectException {
String info = this.excuteByJedis(
j -> {
Client client = j.getClient();
client.info();
return client.getBulkReply();
}
);
String[] strs = Objects.requireNonNull(info).split(separator);
Map map = null;
for (String s : strs) {
String[] detail = s.split(":");
if ("used_memory".equals(detail[0])) {
map = new HashMap<>();
map.put("used_memory", detail[1].substring(0, detail[1].length() - 1));
break;
}
}
return map;
}
@Override
public Set getKeys(String pattern) throws RedisConnectException {
return this.excuteByJedis(j -> j.keys(pattern));
}
@Override
public String get(String key) throws RedisConnectException {
return this.excuteByJedis(j -> j.get(key.toLowerCase()));
}
@Override
public String set(String key, String value) throws RedisConnectException {
return this.excuteByJedis(j -> j.set(key.toLowerCase(), value));
}
@Override
public String set(String key, String value, Long milliscends) throws RedisConnectException {
String result = this.set(key.toLowerCase(), value);
this.pexpire(key, milliscends);
return result;
}
@Override
public Long del(String... key) throws RedisConnectException {
return this.excuteByJedis(j -> j.del(key));
}
@Override
public Boolean exists(String key) throws RedisConnectException {
return this.excuteByJedis(j -> j.exists(key));
}
@Override
public Long pttl(String key) throws RedisConnectException {
return this.excuteByJedis(j -> j.pttl(key));
}
@Override
public Long pexpire(String key, Long milliseconds) throws RedisConnectException {
return this.excuteByJedis(j -> j.pexpire(key, milliseconds));
}
@Override
public Long zadd(String key, Double score, String member) throws RedisConnectException {
return this.excuteByJedis(j -> j.zadd(key, score, member));
}
@Override
public Set zrangeByScore(String key, String min, String max) throws RedisConnectException {
return this.excuteByJedis(j -> j.zrangeByScore(key, min, max));
}
@Override
public Long zremrangeByScore(String key, String start, String end) throws RedisConnectException {
return this.excuteByJedis(j -> j.zremrangeByScore(key, start, end));
}
@Override
public Long zrem(String key, String... members) throws RedisConnectException {
return this.excuteByJedis(j -> j.zrem(key, members));
}
}
package com.liuhu.rainbow.common.util;
import com.liuhu.rainbow.common.constant.RainbowConstant;
import javax.servlet.http.HttpServletRequest;
/**
* 系统工具类
* @author melo、lh
* @createTime 2019-07-04 14:06:18
*/
public class RainbowUtil {
/**
* 返回视图加前缀
* @param viewName 视图名称
* @return java.lang.String
* @author melo、lh
* @createTime 2019-07-04 14:06:33
*/
public static String view(String viewName){
return RainbowConstant.VIEW_PREFIX+viewName;
}
/**
* 判断是否为 ajax请求
* @param request
* @return boolean
* @author melo、lh
* @createTime 2019-07-04 14:42:51
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
return (request.getHeader("X-Requested-With") != null
&& "XMLHttpRequest".equals(request.getHeader("X-Requested-With")));
}
}
package com.liuhu.rainbow.common.exception;
/**
* 自定义异常
* @author melo、lh
* @createTime 2019-07-04 11:25:26
*/
public class RainbowException extends Exception{
public RainbowException(String message) {
super(message);
}
}
package com.liuhu.rainbow.common.exception;
/**
* Redis 连接异常
* @author melo、lh
* @createTime 2019-07-05 16:47:22
*/
public class RedisConnectException extends Exception {
private static final long serialVersionUID = 1639374111871115063L;
public RedisConnectException(String message) {
super(message);
}
}
package com.liuhu.rainbow.common.entity;
import org.springframework.http.HttpStatus;
import java.util.HashMap;
/**
* 响应类
* @author melo、lh
* @createTime 2019-07-05 15:24:16
*/
public class RainbowResponse extends HashMap {
private static final long serialVersionUID = -8713837118340960775L;
public RainbowResponse code(HttpStatus status) {
this.put("code", status.value());
return this;
}
public RainbowResponse message(String message) {
this.put("message", message);
return this;
}
public RainbowResponse data(Object data) {
this.put("data", data);
return this;
}
public RainbowResponse success() {
this.code(HttpStatus.OK);
return this;
}
public RainbowResponse fail() {
this.code(HttpStatus.INTERNAL_SERVER_ERROR);
return this;
}
@Override
public RainbowResponse put(String key, Object value) {
super.put(key, value);
return this;
}
}
package com.liuhu.rainbow.system.controller;
import com.liuhu.rainbow.common.entity.RainbowResponse;
import com.liuhu.rainbow.common.exception.RainbowException;
import com.liuhu.rainbow.common.util.CaptchaUtil;
import com.liuhu.rainbow.common.util.MD5Util;
import com.liuhu.rainbow.common.util.RainbowUtil;
import com.liuhu.rainbow.system.service.IUserService;
import com.wf.captcha.Captcha;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
/**
* 登陆控制层
* @author melo、lh
* @createTime 2019-07-05 17:00:02
*/
@Validated
@RestController
public class LoginController {
@Autowired
private IUserService userService;
@PostMapping("login")
public RainbowResponse login(
@NotBlank(message = "{required}") String username,
@NotBlank(message = "{required}") String password,
@NotBlank(message = "{required}") String verifyCode,
boolean rememberMe, HttpServletRequest request) throws RainbowException {
if (!CaptchaUtil.verify(verifyCode, request)) {
throw new RainbowException("验证码错误!");
}
// 加密
password = MD5Util.encrypt(username.toLowerCase(), password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return new RainbowResponse().success();
} catch (UnknownAccountException | IncorrectCredentialsException | LockedAccountException e) {
throw new RainbowException(e.getMessage());
} catch (AuthenticationException e) {
throw new RainbowException("认证失败!");
}
}
@GetMapping("images/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
CaptchaUtil.outPng(110, 34, 4, Captcha.TYPE_ONLY_NUMBER, request, response);
}
}
### 9. 登录页面
FEBS 权限系统