# spring boot + mybatis + jwt + shiro + redis
**Repository Path**: l1-git/rbac-demo
## Basic Information
- **Project Name**: spring boot + mybatis + jwt + shiro + redis
- **Description**: 一个简单的RBAC实现(基于spring boot + mybatis + jwt + shiro + redis + postgresql)
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 6
- **Created**: 2022-07-25
- **Last Updated**: 2022-07-25
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## **基于spring boot + mybatis + jwt + shiro + redis + postgresql的RBAC实现*
##### 一、搭建数据库
1、用户表
```plsql
-- auto-generated definition
create table t_user
(
id bigint not null
constraint "T_USER_pkey"
primary key,
username varchar(64) not null,
password varchar(128) not null,
name varchar(64),
roleid bigint not null,
age integer,
icon varchar(128),
sex varchar(2),
phone varchar(11),
email varchar(64),
area varchar(128)
);
comment on table t_user is '用户表';
comment on column t_user.id is '用户ID';
comment on column t_user.username is '用户名';
comment on column t_user.password is '用户密码';
comment on column t_user.roleid is '角色ID';
alter table t_user owner to postgres;
```
| 字段 | 类型 | 备注 |
| --------- | ------- | ------------ |
| id | serial4 | 用户编号 |
| username | varchar | 用户名 |
| passwords | varchar | 用户密码 |
| roleid | int4 | 用户权限编号 |
2、角色表(由于是简单实现其功能,所以此处简单代替)
```plsql
create table t_role
(
id bigint not null
constraint "T_ROLE_pkey"
primary key,
name varchar(64),
auths varchar(128),
parentid bigint
);
comment on table t_role is '角色表';
comment on column t_role.id is '角色ID ';
comment on column t_role.name is '角色编号';
comment on column t_role.auths is '权限ID';
comment on column t_role.parentid is '父级角色ID';
alter table t_role owner to postgres;
```
| 字段 | 类型 | 备注 |
| -------- | ------- | ---------- |
| id | serial4 | 角色编号 |
| rolename | varchar | 角色名称 |
| auths | varchar | 角色描述 |
| parentid | Integer | 父级角色ID |
##### 二、pom.xml中导入依赖
```xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.7.RELEASE
com.leaves.auth
rbac-demo
0.0.1-SNAPSHOT
rbac-demo
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-jdbc
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.4
org.postgresql
postgresql
runtime
org.projectlombok
lombok
true
com.alibaba
druid
1.1.21
redis.clients
jedis
2.9.0
org.crazycake
shiro-redis
3.2.3
org.apache.shiro
shiro-spring
1.3.2
org.apache.shiro
shiro-ehcache
1.2.5
com.auth0
java-jwt
3.5.0
org.slf4j
slf4j-api
1.7.25
commons-fileupload
commons-fileupload
1.3.1
commons-io
commons-io
1.4
tk.mybatis
mapper-spring-boot-starter
2.1.5
com.github.pagehelper
pagehelper-spring-boot-starter
1.2.5
cn.hutool
hutool-all
5.4.3
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
RBAC-DEMO
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
```
##### 三、配置application.yml
```yaml
#端口号
server:
port: 8088
spring:
#数据库连接配置
datasource:
driver-class-name: org.postgresql.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:postgresql://localhost:5432/rbac?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&socketTimeout=30000
username: rbac
password: 123456
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
#允许运行程序中存在多个main函数
main:
allow-bean-definition-overriding: true
#Redis
redis:
host: localhost
port: 6379
database: 0
jedis:
pool:
max-active: 8
max-idle: 8
max-wait: -1
min-idle: 0
timeout: 3000ms
#文件上传配置
autoconfigure:
exclude: org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
#mybatis配置
mybatis:
type-aliases-package: com.leaves.auth.entity
mapper-locations: classpath*:/mapper/*.xml
#日志等级
logging:
level:
com:
leaves:
auth:
mapper: debug
config: classpath:logback-spring.xml
#文件上传保存地址
upload:
local:
path: D:/uploadFile/file
```
##### 四、工具类redisUitls包:
###### 1、Constant
```java
/**
* @Author: LEAVES
* @Date: 2020年12月30日 11时06分23秒
* @Version 1.0
* @Description: 常量
*/
private Constant() {}
/**
* redis-OK
*/
public static final String OK = "OK";
/**
* redis过期时间,以秒为单位,一分钟
*/
public static final int EXRP_MINUTE = 60;
/**
* redis过期时间,以秒为单位,一小时
*/
public static final int EXRP_HOUR = 60 * 60;
/**
* redis过期时间,以秒为单位,一天
*/
public static final int EXRP_DAY = 60 * 60 * 24;
/**
* redis-key-前缀-shiro:cache:
*/
public static final String PREFIX_SHIRO_CACHE = "shiro:cache:";
/**
* redis-key-前缀-shiro:access_token:
*/
public static final String PREFIX_SHIRO_ACCESS_TOKEN = "shiro:access_token:";
/**
* redis-key-前缀-shiro:refresh_token:
*/
public static final String PREFIX_SHIRO_REFRESH_TOKEN = "shiro:refresh_token:";
/**
* JWT-account:
*/
public static final String ACCOUNT = "account";
/**
* JWT-currentTimeMillis:
*/
public static final String CURRENT_TIME_MILLIS = "currentTimeMillis";
/**
* PASSWORD_MAX_LEN
*/
public static final Integer PASSWORD_MAX_LEN = 8;
```
###### 2、SerializableUtil
```java
/**
* @Author: LEAVES
* @Date: 2020年12月30日 11时06分23秒
* @Version 1.0
* @Description: Serializable工具(JDK)(也可以使用Protobuf自行百度)
*/
@Slf4j
public class SerializableUtil {
private SerializableUtil() {}
/**
* 序列化
*/
public static byte[] serializable(Object object) {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
return baos.toByteArray();
} catch (IOException e) {
log.error("SerializableUtil工具类序列化出现IOException异常:{}", e.getMessage());
throw new CustomException("SerializableUtil工具类序列化出现IOException异常:" + e.getMessage());
} finally {
try {
if (oos != null) {
oos.close();
}
if (baos != null) {
baos.close();
}
} catch (IOException e) {
log.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage());
}
}
}
/**
* 反序列化
*/
public static Object unserializable(byte[] bytes) {
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (ClassNotFoundException e) {
log.error("SerializableUtil工具类反序列化出现ClassNotFoundException异常:{}", e.getMessage());
throw new CustomException("SerializableUtil工具类反序列化出现ClassNotFoundException异常:" + e.getMessage());
} catch (IOException e) {
log.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage());
} finally {
try {
if (ois != null) {
ois.close();
}
if (bais != null) {
bais.close();
}
} catch (IOException e) {
log.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage());
}
}
}
}
```
###### 3、StringUtil
```java
/**
* @Author: LEAVES
* @Date: 2020年12月30日 11时06分23秒
* @Version 1.0
* @Description: String工具
*/
public class StringUtil {
private StringUtil() {}
/**
* 定义下划线
*/
private static final char UNDERLINE = '_';
/**
* String为空判断(不允许空格)
* @param str
* @return
*/
public static boolean isBlank(String str) {
return str == null || "".equals(str.trim());
}
/**
* String不为空判断(不允许空格)
* @param str
* @return
*/
public static boolean isNotBlank(String str) {
return !isBlank(str);
}
/**
* Byte数组为空判断
* @param bytes
* @return boolean
*/
public static boolean isNull(byte[] bytes) {
// 根据byte数组长度为0判断
return bytes == null || bytes.length == 0;
}
/**
* Byte数组不为空判断
* @param bytes
* @return boolean
*/
public static boolean isNotNull(byte[] bytes) {
return !isNull(bytes);
}
/**
* 驼峰转下划线工具
* @param param
* @return java.lang.String
*/
public static String camelToUnderline(String param) {
if (isNotBlank(param)) {
int len = param.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = param.charAt(i);
if (Character.isUpperCase(c)) {
sb.append(UNDERLINE);
sb.append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
} else {
return "";
}
}
/**
* 下划线转驼峰工具
* @param param
* @return java.lang.String
*/
public static String underlineToCamel(String param) {
if (isNotBlank(param)) {
int len = param.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = param.charAt(i);
if (c == 95) {
i++;
if (i < len) {
sb.append(Character.toUpperCase(param.charAt(i)));
}
} else {
sb.append(c);
}
}
return sb.toString();
} else {
return "";
}
}
/**
* 在字符串两周添加''
* @param param
* @return java.lang.String
*/
public static String addSingleQuotes(String param) {
return "\'" + param + "\'";
}
}
```
##### 五、配置类redis
###### 1、JedisConfig
```java
/**
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
* @Version 1.0
*/
@Configuration
public class JedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port ;
@Bean
public Jedis getJedis(){
return new Jedis("127.0.0.1",6379);
}
// 封装jedispool对象(将配置对象注入其中)
@Bean
public JedisPool jedisPool(@Qualifier("jedisPoolConfig") JedisPoolConfig jedisPoolConfig ){
JedisPool pool = new JedisPool(jedisPoolConfig,host,port);
return pool;
}
// 封装jedispool配置对象
@Bean("jedisPoolConfig")
public JedisPoolConfig jedisPoolConfig(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(1);
poolConfig.setMaxTotal(10);
return poolConfig;
}
}
```
###### 2、JedisUtil
```java
/**
* 封装常见的jedis操作接口
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
* @Version 1.0
*/
@Component
public class JedisUtil {
/**
* 静态注入JedisPool连接池
* 本来是正常注入JedisUtil,可以在Controller和Service层使用,但是重写Shiro的CustomCache无法注入JedisUtil
* 现在改为静态注入JedisPool连接池,JedisUtil直接调用静态方法即可
* https://blog.csdn.net/W_Z_W_888/article/details/79979103
*/
private static JedisPool jedisPool;
@Autowired
public void setJedisPool(JedisPool jedisPool) {
JedisUtil.jedisPool = jedisPool;
}
/**
* 获取Jedis实例
* @param
* @return redis.clients.jedis.Jedis
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static synchronized Jedis getJedis() {
try {
if (jedisPool != null) {
return jedisPool.getResource();
} else {
return null;
}
} catch (Exception e) {
throw new CustomException("获取Jedis资源异常:" + e.getMessage());
}
}
/**
* 释放Jedis资源
* @param
* @return void
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static void closePool() {
try {
jedisPool.close();
} catch (Exception e) {
throw new CustomException("释放Jedis资源异常:" + e.getMessage());
}
}
/**
* 获取redis键值-object
* @param key
* @return java.lang.Object
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static Object getObject(String key) {
try (Jedis jedis = jedisPool.getResource()) {
byte[] bytes = jedis.get(key.getBytes());
if (StringUtil.isNotNull(bytes)) {
return SerializableUtil.unserializable(bytes);
}
} catch (Exception e) {
throw new CustomException("获取Redis键值getObject方法异常:key=" + key + " cause=" + e.getMessage());
}
return null;
}
/**
* 设置redis键值-object
* @param key
* @param value
* @return java.lang.String
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static String setObject(String key, Object value) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.set(key.getBytes(), SerializableUtil.serializable(value));
} catch (Exception e) {
throw new CustomException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
}
}
/**
* 设置redis键值-object-expiretime
* @param key
* @param value
* @param expiretime
* @return java.lang.String
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static String setObject(String key, Object value, int expiretime) {
String result;
try (Jedis jedis = jedisPool.getResource()) {
result = jedis.set(key.getBytes(), SerializableUtil.serializable(value));
if (Constant.OK.equals(result)) {
jedis.expire(key.getBytes(), expiretime);
}
return result;
} catch (Exception e) {
throw new CustomException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
}
}
/**
* 获取redis键值-Json
* @param key
* @return java.lang.Object
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static String getJson(String key) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.get(key);
} catch (Exception e) {
throw new CustomException("获取Redis键值getJson方法异常:key=" + key + " cause=" + e.getMessage());
}
}
/**
* 设置redis键值-Json
* @param key
* @param value
* @return java.lang.String
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static String setJson(String key, String value) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.set(key, value);
} catch (Exception e) {
throw new CustomException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
}
}
/**
* 设置redis键值-Json-expiretime
* @param key
* @param value
* @param expiretime
* @return java.lang.String
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static String setJson(String key, String value, int expiretime) {
String result;
try (Jedis jedis = jedisPool.getResource()) {
result = jedis.set(key, value);
if (Constant.OK.equals(result)) {
jedis.expire(key, expiretime);
}
return result;
} catch (Exception e) {
throw new CustomException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
}
}
/**
* 删除key
* @param key
* @return java.lang.Long
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static Long delKey(String key) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.del(key.getBytes());
} catch (Exception e) {
throw new CustomException("删除Redis的键delKey方法异常:key=" + key + " cause=" + e.getMessage());
}
}
/**
* key是否存在
* @param key
* @return java.lang.Boolean
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static Boolean exists(String key) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.exists(key.getBytes());
} catch (Exception e) {
throw new CustomException("查询Redis的键是否存在exists方法异常:key=" + key + " cause=" + e.getMessage());
}
}
/**
* 模糊查询获取key集合(keys的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,生产不推荐使用)
* @param key
* @return java.util.Set
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static Set keysS(String key) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.keys(key);
} catch (Exception e) {
throw new CustomException("模糊查询Redis的键集合keysS方法异常:key=" + key + " cause=" + e.getMessage());
}
}
/**
* 模糊查询获取key集合(keys的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,生产不推荐使用)
* @param key
* @return java.util.Set
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static Set keysB(String key) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.keys(key.getBytes());
} catch (Exception e) {
throw new CustomException("模糊查询Redis的键集合keysB方法异常:key=" + key + " cause=" + e.getMessage());
}
}
/**
* 获取过期剩余时间
* @param key
* @return java.lang.String
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static Long ttl(String key) {
Long result = -2L;
try (Jedis jedis = jedisPool.getResource()) {
result = jedis.ttl(key);
return result;
} catch (Exception e) {
throw new CustomException("获取Redis键过期剩余时间ttl方法异常:key=" + key + " cause=" + e.getMessage());
}
}
}
```
###### 3、RedisOperator
```java
/**
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
* @Version 1.0
* @Description: 使用redisTemplate的操作实现类
*/
@Component
public class RedisOperator {
@Resource
private StringRedisTemplate redisTemplate;
// Key(键),简单的key-value操作
/**
* 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
*
* @param key
* @return
*/
public long ttl(String key) {
return redisTemplate.getExpire(key);
}
/**
* 实现命令:expire 设置过期时间,单位秒
*
* @param key
* @return
*/
public void expire(String key, long timeout) {
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:INCR key,增加key一次
*
* @param key
* @return
*/
public long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
*/
public Set keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 实现命令:DEL key,删除一个key
*
* @param key
*/
public void del(String key) {
redisTemplate.delete(key);
}
// String(字符串)
/**
* 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
*
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
*
* @param key
* @param value
* @param timeout
* (以秒为单位)
*/
public void set(String key, String value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:GET key,返回 key所关联的字符串值。
*
* @param key
* @return value
*/
public String get(String key) {
return (String)redisTemplate.opsForValue().get(key);
}
// Hash(哈希表)
/**
* 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
*
* @param key
* @param field
* @param value
*/
public void hset(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
/**
* 实现命令:HGET key field,返回哈希表 key中给定域 field的值
*
* @param key
* @param field
* @return
*/
public String hget(String key, String field) {
return (String) redisTemplate.opsForHash().get(key, field);
}
/**
* 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
*
* @param key
* @param fields
*/
public void hdel(String key, Object... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
/**
* 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
*
* @param key
* @return
*/
public Map