Browse Source

[add]:整合redis

erp、
youjianchi 1 year ago
parent
commit
0a377aab60
  1. 133
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java
  2. 13
      ruoyi-admin/src/main/resources/application-druid.yml
  3. 91
      ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml
  4. 82
      ruoyi-admin/src/main/resources/templates/monitor/online/online.html
  5. 19
      ruoyi-common/pom.xml
  6. 316
      ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
  7. 22
      ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java
  8. 28
      ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java
  9. 71
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java
  10. 45
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java
  11. 210
      ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java
  12. 50
      ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java
  13. 34
      ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java
  14. 41
      ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java
  15. 62
      ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java
  16. 117
      ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java
  17. 29
      ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/LogoutFilter.java
  18. 99
      ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java
  19. 39
      ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java
  20. 175
      ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java
  21. 131
      ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/SpringSessionValidationScheduler.java
  22. 100
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/CacheService.java
  23. 52
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserOnlineMapper.java
  24. 4
      ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java
  25. 4
      ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java
  26. 75
      ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java
  27. 70
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java
  28. 66
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java
  29. 141
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java
  30. 57
      ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml

133
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java

@ -1,26 +1,37 @@
package com.ruoyi.web.controller.monitor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.ShiroConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.enums.OnlineStatus;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.framework.shiro.session.OnlineSession;
import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.domain.SysUserOnline;
import com.ruoyi.system.service.ISysUserOnlineService;
/**
* 在线用户监控
@ -34,10 +45,7 @@ public class SysUserOnlineController extends BaseController
private String prefix = "monitor/online";
@Autowired
private ISysUserOnlineService userOnlineService;
@Autowired
private OnlineSessionDAO onlineSessionDAO;
private RedisSessionDAO redisSessionDAO;
@RequiresPermissions("monitor:online:view")
@GetMapping()
@ -51,38 +59,109 @@ public class SysUserOnlineController extends BaseController
@ResponseBody
public TableDataInfo list(SysUserOnline userOnline)
{
startPage();
List<SysUserOnline> list = userOnlineService.selectUserOnlineList(userOnline);
return getDataTable(list);
String ipaddr = userOnline.getIpaddr();
String loginName = userOnline.getLoginName();
TableDataInfo rspData = new TableDataInfo();
Collection<Session> sessions = redisSessionDAO.getActiveSessions();
Iterator<Session> it = sessions.iterator();
List<SysUserOnline> sessionList = new ArrayList<SysUserOnline>();
while (it.hasNext())
{
SysUserOnline user = getSession(it.next());
if (StringUtils.isNotNull(user))
{
if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(loginName))
{
if (StringUtils.equals(ipaddr, user.getIpaddr())
&& StringUtils.equals(loginName, user.getLoginName()))
{
sessionList.add(user);
}
}
else if (StringUtils.isNotEmpty(ipaddr))
{
if (StringUtils.equals(ipaddr, user.getIpaddr()))
{
sessionList.add(user);
}
}
else if (StringUtils.isNotEmpty(loginName))
{
if (StringUtils.equals(loginName, user.getLoginName()))
{
sessionList.add(user);
}
}
else
{
sessionList.add(user);
}
}
}
rspData.setRows(sessionList);
rspData.setTotal(sessionList.size());
return rspData;
}
@RequiresPermissions(value = { "monitor:online:batchForceLogout", "monitor:online:forceLogout" }, logical = Logical.OR)
@Log(title = "在线用户", businessType = BusinessType.FORCE)
@PostMapping("/batchForceLogout")
@ResponseBody
public AjaxResult batchForceLogout(String ids)
public AjaxResult batchForceLogout(@RequestBody List<SysUserOnline> sysUserOnlines)
{
for (String sessionId : Convert.toStrArray(ids))
for (SysUserOnline userOnline : sysUserOnlines)
{
SysUserOnline online = userOnlineService.selectOnlineById(sessionId);
if (online == null)
String sessionId = userOnline.getSessionId();
String loginName = userOnline.getLoginName();
if (sessionId.equals(ShiroUtils.getSessionId()))
{
return error("用户已下线");
return error("当前登录用户无法强退");
}
redisSessionDAO.delete(redisSessionDAO.readSession(sessionId));
removeUserCache(loginName, sessionId);
}
return success();
}
OnlineSession onlineSession = (OnlineSession) onlineSessionDAO.readSession(online.getSessionId());
if (onlineSession == null)
private SysUserOnline getSession(Session session)
{
Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (null == obj)
{
return error("用户已下线");
return null;
}
if (sessionId.equals(ShiroUtils.getSessionId()))
if (obj instanceof SimplePrincipalCollection)
{
return error("当前登录用户无法强退");
SimplePrincipalCollection spc = (SimplePrincipalCollection) obj;
obj = spc.getPrimaryPrincipal();
if (null != obj && obj instanceof SysUser)
{
SysUser sysUser = (SysUser) obj;
SysUserOnline userOnline = new SysUserOnline();
userOnline.setSessionId(session.getId().toString());
userOnline.setLoginName(sysUser.getLoginName());
if (StringUtils.isNotNull(sysUser.getDept()) && StringUtils.isNotEmpty(sysUser.getDept().getDeptName()))
{
userOnline.setDeptName(sysUser.getDept().getDeptName());
}
onlineSessionDAO.delete(onlineSession);
online.setStatus(OnlineStatus.off_line);
userOnlineService.saveOnline(online);
userOnlineService.removeUserCache(online.getLoginName(), sessionId);
userOnline.setIpaddr(session.getHost());
userOnline.setStartTimestamp(session.getStartTimestamp());
userOnline.setLastAccessTime(session.getLastAccessTime());
return userOnline;
}
return success();
}
return null;
}
public void removeUserCache(String loginName, String sessionId)
{
Cache<String, Deque<Serializable>> cache = SpringUtils.getBean(RedisCacheManager.class).getCache(ShiroConstants.SYS_USERCACHE);
Deque<Serializable> deque = cache.get(loginName);
if (StringUtils.isEmpty(deque) || deque.size() == 0)
{
return;
}
deque.remove(sessionId);
cache.put(loginName, deque);
}
}

13
ruoyi-admin/src/main/resources/application-druid.yml

@ -1,5 +1,18 @@
# 数据源配置
spring:
# redis配置
redis:
database: 0
host: 47.116.8.180
port: 6379
password: Read-in2023
timeout: 6000ms # 连接超时时长(毫秒)
lettuce:
pool:
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 5 # 连接池中的最小空闲连接
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver

91
ruoyi-admin/src/main/resources/ehcache/ehcache-shiro.xml

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="ruoyi" updateCheck="false">
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir"/>
<!-- maxEntriesLocalHeap:堆内存中最大缓存对象数,0没有限制 -->
<!-- maxElementsInMemory: 在内存中缓存的element的最大数目。-->
<!-- eternal:elements是否永久有效,如果为true,timeouts将被忽略,element将永不过期 -->
<!-- timeToIdleSeconds:失效前的空闲秒数,当eternal为false时,这个属性才有效,0为不限制 -->
<!-- timeToLiveSeconds:失效前的存活秒数,创建时间到失效时间的间隔为存活时间,当eternal为false时,这个属性才有效,0为不限制 -->
<!-- overflowToDisk: 如果内存中数据超过内存限制,是否要缓存到磁盘上 -->
<!-- statistics:是否收集统计信息。如果需要监控缓存使用情况,应该打开这个选项。默认为关闭(统计会影响性能)。设置statistics="true"开启统计 -->
<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="1000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="false">
</defaultCache>
<!-- 登录记录缓存 锁定10分钟 -->
<cache name="loginRecordCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="false">
</cache>
<!-- 系统活跃用户缓存 -->
<cache name="sys-userCache"
maxEntriesLocalHeap="10000"
overflowToDisk="false"
eternal="false"
diskPersistent="false"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
statistics="false">
</cache>
<!-- 系统用户授权缓存 没必要过期 -->
<cache name="sys-authCache"
maxEntriesLocalHeap="10000"
overflowToDisk="false"
eternal="false"
diskPersistent="false"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
memoryStoreEvictionPolicy="LRU"
statistics="false"/>
<!-- 系统缓存 -->
<cache name="sys-cache"
maxEntriesLocalHeap="1000"
eternal="true"
overflowToDisk="true"
statistics="false">
</cache>
<!-- 系统参数缓存 -->
<cache name="sys-config"
maxEntriesLocalHeap="1000"
eternal="true"
overflowToDisk="true"
statistics="false">
</cache>
<!-- 系统字典缓存 -->
<cache name="sys-dict"
maxEntriesLocalHeap="1000"
eternal="true"
overflowToDisk="true"
statistics="false">
</cache>
<!-- 系统会话缓存 -->
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="10000"
overflowToDisk="false"
eternal="false"
diskPersistent="false"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
statistics="false"/>
</ehcache>

82
ruoyi-admin/src/main/resources/templates/monitor/online/online.html

@ -44,6 +44,7 @@
$(function() {
var options = {
sidePagination: "client",
uniqueId: "sessionId",
url: prefix + "/list",
exportUrl: prefix + "/export",
@ -80,30 +81,6 @@
field: 'ipaddr',
title: '主机'
},
{
field: 'loginLocation',
title: '登录地点'
},
{
field: 'browser',
title: '浏览器'
},
{
field: 'os',
title: '操作系统'
},
{
field: 'status',
title: '会话状态',
align: 'center',
formatter: function(value, row, index) {
if (value == 'on_line') {
return '<span class="badge badge-primary">在线</span>';
} else if (value == 'off_line') {
return '<span class="badge badge-danger">离线</span>';
}
}
},
{
field: 'startTimestamp',
title: '登录时间',
@ -118,7 +95,7 @@
title: '操作',
align: 'center',
formatter: function(value, row, index) {
var msg = '<a class="btn btn-danger btn-xs ' + forceFlag + '" href="javascript:void(0)" onclick="forceLogout(\'' + row.sessionId + '\')"><i class="fa fa-sign-out"></i>强退</a> ';
var msg = '<a class="btn btn-danger btn-xs ' + forceFlag + '" href="javascript:void(0)" onclick="forceLogout(\'' + row.sessionId + '\',\'' + row.loginName + '\')"><i class="fa fa-sign-out"></i>强退</a> ';
return msg;
}
}]
@ -127,10 +104,31 @@
});
// 单条强退
function forceLogout(sessionId) {
function forceLogout(sessionId, loginName) {
$.modal.confirm("确定要强制选中用户下线吗?", function() {
var data = { "ids": sessionId };
$.operate.post(prefix + "/batchForceLogout", data);
var data = [];
var session = {};
session.sessionId = sessionId;
session.loginName = loginName;
data.push(session);
$.ajax({
url: prefix + "/batchForceLogout",
method: 'POST',
data: JSON.stringify(data),
headers: {
'Content-Type': 'application/json;charset=utf8'
},
dataType: "json",
beforeSend: function() {
$.modal.loading("正在处理中,请稍后...");
},
success: function(result) {
if (typeof callback == "function") {
callback(result);
}
$.operate.ajaxSuccess(result);
}  
});
})
}
@ -142,9 +140,31 @@
return;
}
$.modal.confirm("确认要强退选中的" + rows.length + "条数据吗?", function() {
var url = prefix + "/batchForceLogout";
var data = { "ids": rows.join() };
$.operate.post(url, data);
var data = [];
$.map($("#" + table.options.id).bootstrapTable('getSelections'), function (row) {
var session = {};
session.sessionId = row.sessionId;
session.loginName = row.loginName;
data.push(session);
});
$.ajax({
url: prefix + "/batchForceLogout",
method: 'POST',
data: JSON.stringify(data),
headers: {
'Content-Type': 'application/json;charset=utf8'
},
dataType: "json",
beforeSend: function() {
$.modal.loading("正在处理中,请稍后...");
},
success: function(result) {
if (typeof callback == "function") {
callback(result);
}
$.operate.ajaxSuccess(result);
}  
});
});
}
</script>

19
ruoyi-common/pom.xml

@ -49,10 +49,23 @@
<artifactId>shiro-core</artifactId>
</dependency>
<!-- Shiro使用EhCache缓存框架 -->
<!-- shiro整合redis -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.3.1</version>
<exclusions>
<exclusion>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- springboot整合redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- pagehelper 分页插件 -->

316
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java

@ -0,0 +1,316 @@
package com.ruoyi.common.core.redis;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
/**
* spring redis 工具类
*
* @author ruoyi
**/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象IntegerString实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 根据key获取缓存中的val+1
*
* @param key 缓存的值
* @return 数值
*/
public Long increment(final String key)
{
return redisTemplate.boundValueOps(key).increment();
}
/**
* 缓存基本的对象IntegerString实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 生成流水号
* @param prefix
* @return
*/
public String generateBillNo(String prefix) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String yearMonthDay = sdf.format(new Date());
String codeKey = prefix+yearMonthDay;
Long no = 0L;
//获取每日流水
if (hasKey(codeKey)) {
no = Long.valueOf(get(codeKey).toString());
}
//新增每日流水
else{
//每日流水设置3天过期,避免生成过多
if(!set(codeKey,0,60*60*24*3)){
throw new RuntimeException("Redis新增单据编号流水失败");
}
}
no = incr(codeKey, 1);
if (no == null) {
throw new RuntimeException("Redis单据编号流水递增失败");
}
String serialNum = String.format("%03d",no);
return codeKey+serialNum;
}
}

22
ruoyi-common/src/main/java/com/ruoyi/common/utils/CacheUtils.java

@ -3,8 +3,8 @@ package com.ruoyi.common.utils;
import java.util.Iterator;
import java.util.Set;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.crazycake.shiro.RedisCache;
import org.crazycake.shiro.RedisCacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.utils.spring.SpringUtils;
@ -18,7 +18,7 @@ public class CacheUtils
{
private static Logger logger = LoggerFactory.getLogger(CacheUtils.class);
private static CacheManager cacheManager = SpringUtils.getBean(CacheManager.class);
private static RedisCacheManager cacheManager = SpringUtils.getBean(RedisCacheManager.class);
private static final String SYS_CACHE = "sys-cache";
@ -124,7 +124,7 @@ public class CacheUtils
*/
public static void removeAll(String cacheName)
{
Cache<String, Object> cache = getCache(cacheName);
RedisCache<String, Object> cache = getCache(cacheName);
Set<String> keys = cache.keys();
for (Iterator<String> it = keys.iterator(); it.hasNext();)
{
@ -175,23 +175,13 @@ public class CacheUtils
* @param cacheName
* @return
*/
public static Cache<String, Object> getCache(String cacheName)
public static RedisCache<String, Object> getCache(String cacheName)
{
Cache<String, Object> cache = cacheManager.getCache(cacheName);
if (cache == null)
{
throw new RuntimeException("当前系统中没有定义“" + cacheName + "”这个缓存。");
}
return cache;
}
/**
* 获取所有缓存
*
* @return 缓存组
*/
public static String[] getCacheNames()
{
return ((EhCacheManager) cacheManager).getCacheManager().getCacheNames();
return (RedisCache<String, Object>) cache;
}
}

28
ruoyi-common/src/main/java/com/ruoyi/common/utils/DictUtils.java

@ -1,9 +1,12 @@
package com.ruoyi.common.utils;
import java.util.Collection;
import java.util.List;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.spring.SpringUtils;
/**
* 字典工具类
@ -26,7 +29,7 @@ public class DictUtils
*/
public static void setDictCache(String key, List<SysDictData> dictDatas)
{
CacheUtils.put(getCacheName(), getCacheKey(key), dictDatas);
SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas);
}
/**
@ -37,7 +40,7 @@ public class DictUtils
*/
public static List<SysDictData> getDictCache(String key)
{
Object cacheObj = CacheUtils.get(getCacheName(), getCacheKey(key));
Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
if (StringUtils.isNotNull(cacheObj))
{
List<SysDictData> DictDatas = StringUtils.cast(cacheObj);
@ -83,7 +86,7 @@ public class DictUtils
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.containsAny(separator, dictValue) && StringUtils.isNotEmpty(datas))
if (StringUtils.containsAny(dictValue, separator) && StringUtils.isNotEmpty(datas))
{
for (SysDictData dict : datas)
{
@ -91,7 +94,7 @@ public class DictUtils
{
if (value.equals(dict.getDictValue()))
{
propertyString.append(dict.getDictLabel() + separator);
propertyString.append(dict.getDictLabel()).append(separator);
break;
}
}
@ -123,7 +126,7 @@ public class DictUtils
StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas))
if (StringUtils.containsAny(dictLabel, separator) && StringUtils.isNotEmpty(datas))
{
for (SysDictData dict : datas)
{
@ -131,7 +134,7 @@ public class DictUtils
{
if (label.equals(dict.getDictLabel()))
{
propertyString.append(dict.getDictValue() + separator);
propertyString.append(dict.getDictValue()).append(separator);
break;
}
}
@ -150,12 +153,23 @@ public class DictUtils
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 删除指定字典缓存
*
* @param key 字典键
*/
public static void removeDictCache(String key)
{
SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key));
}
/**
* 清空字典缓存
*/
public static void clearDictCache()
{
CacheUtils.removeAll(getCacheName());
Collection<String> keys = SpringUtils.getBean(RedisCache.class).keys(Constants.SYS_DICT_KEY + "*");
SpringUtils.getBean(RedisCache.class).deleteObject(keys);
}
/**

71
ruoyi-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java

@ -0,0 +1,71 @@
package com.ruoyi.framework.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
/**
* Redis使用FastJson序列化
*
* @author ruoyi
*/
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
@SuppressWarnings("unused")
private ObjectMapper objectMapper = new ObjectMapper();
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static
{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJson2JsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
public void setObjectMapper(ObjectMapper objectMapper)
{
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
}
protected JavaType getJavaType(Class<?> clazz)
{
return TypeFactory.defaultInstance().constructType(clazz);
}
}

45
ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java

@ -0,0 +1,45 @@
package com.ruoyi.framework.config;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
/**
* redis配置
*
* @author ruoyi
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}

210
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java

@ -1,41 +1,32 @@
package com.ruoyi.framework.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.security.CipherUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.shiro.realm.UserRealm;
import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
import com.ruoyi.framework.shiro.session.OnlineSessionFactory;
import com.ruoyi.framework.shiro.web.filter.LogoutFilter;
import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter;
import com.ruoyi.framework.shiro.web.filter.kickout.KickoutSessionFilter;
import com.ruoyi.framework.shiro.web.filter.online.OnlineSessionFilter;
import com.ruoyi.framework.shiro.web.filter.sync.SyncOnlineSessionFilter;
import com.ruoyi.framework.shiro.web.session.OnlineWebSessionManager;
import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.io.ResourceUtils;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
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.springframework.beans.factory.annotation.Qualifier;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.security.CipherUtils;
import com.ruoyi.framework.shiro.realm.UserRealm;
import com.ruoyi.framework.shiro.web.filter.LogoutFilter;
import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter;
import com.ruoyi.framework.shiro.web.filter.kickout.KickoutSessionFilter;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
/**
* 权限配置加载
@ -118,105 +109,103 @@ public class ShiroConfig
private String unauthorizedUrl;
/**
* 缓存管理器 使用Ehcache实现
* 是否开启记住我功能
*/
@Value("${shiro.rememberMe.enabled: false}")
private boolean rememberMe;
/**
* redis缓存地址
*/
@Value("${spring.redis.port}")
private String redisPort;
/**
* redis缓存端口
*/
@Value("${spring.redis.host}")
private String redisHost;
/**
* redis数据库索引
*/
@Value("${spring.redis.database}")
private int database;
/**
* redis密码
*/
@Value("${spring.redis.password}")
private String password;
/**
* Cache Manager (shiro-redis)
*/
@Bean
public EhCacheManager getEhCacheManager()
{
net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi");
EhCacheManager em = new EhCacheManager();
if (StringUtils.isNull(cacheManager))
{
em.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream()));
return em;
}
else
public RedisCacheManager redisCacheManager()
{
em.setCacheManager(cacheManager);
return em;
}
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}
/**
* 返回配置文件流 避免ehcache配置文件一直被占用无法完全销毁项目重新部署
* RedisManager (shiro-redis)
*/
protected InputStream getCacheManagerConfigFileInputStream()
{
String configFile = "classpath:ehcache/ehcache-shiro.xml";
InputStream inputStream = null;
try
{
inputStream = ResourceUtils.getInputStreamForPath(configFile);
byte[] b = IOUtils.toByteArray(inputStream);
InputStream in = new ByteArrayInputStream(b);
return in;
}
catch (IOException e)
@Bean
public RedisManager redisManager()
{
throw new ConfigurationException(
"Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e);
}
finally
RedisManager redisManager = new RedisManager();
redisManager.setHost(redisHost + ":" + redisPort);
redisManager.setDatabase(database);
if (StringUtils.isNotEmpty(password))
{
IOUtils.closeQuietly(inputStream);
redisManager.setPassword(password);
}
redisManager.setTimeout(expireTime * 60);
return redisManager;
}
/**
* 自定义Realm
*/
@Bean
public UserRealm userRealm(EhCacheManager cacheManager)
public UserRealm userRealm()
{
UserRealm userRealm = new UserRealm();
userRealm.setAuthorizationCacheName(Constants.SYS_AUTH_CACHE);
userRealm.setCacheManager(cacheManager);
userRealm.setCacheManager(redisCacheManager());
return userRealm;
}
/**
* 自定义sessionDAO会话
* RedisSessionDAO (shiro-redis)
*/
@Bean
public OnlineSessionDAO sessionDAO()
public RedisSessionDAO redisSessionDAO()
{
OnlineSessionDAO sessionDAO = new OnlineSessionDAO();
return sessionDAO;
}
/**
* 自定义sessionFactory会话
*/
@Bean
public OnlineSessionFactory sessionFactory()
{
OnlineSessionFactory sessionFactory = new OnlineSessionFactory();
return sessionFactory;
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setExpire(expireTime * 60);
return redisSessionDAO;
}
/**
* 会话管理器
*/
@Bean
public OnlineWebSessionManager sessionManager()
public SessionManager sessionManager()
{
OnlineWebSessionManager manager = new OnlineWebSessionManager();
DefaultWebSessionManager manager = new DefaultWebSessionManager();
// 加入缓存管理器
manager.setCacheManager(getEhCacheManager());
// 删除过期的session
manager.setDeleteInvalidSessions(true);
// 设置全局session超时时间
manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
manager.setCacheManager(redisCacheManager());
// 去掉 JSESSIONID
manager.setSessionIdUrlRewritingEnabled(false);
// 定义要使用的无效的Session定时调度器
manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class));
// 是否定时检查session
manager.setSessionValidationSchedulerEnabled(true);
// 自定义SessionDao
manager.setSessionDAO(sessionDAO());
// 自定义sessionFactory
manager.setSessionFactory(sessionFactory());
manager.setSessionDAO(redisSessionDAO());
// 设置全局session超时时间
manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
return manager;
}
@ -230,9 +219,9 @@ public class ShiroConfig
// 设置realm.
securityManager.setRealm(userRealm);
// 记住我
securityManager.setRememberMeManager(rememberMeManager());
securityManager.setRememberMeManager(rememberMe ? rememberMeManager() : null);
// 注入缓存管理器;
securityManager.setCacheManager(getEhCacheManager());
securityManager.setCacheManager(redisCacheManager());
// session管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
@ -245,6 +234,7 @@ public class ShiroConfig
{
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setLoginUrl(loginUrl);
logoutFilter.setCacheManager(redisCacheManager());
return logoutFilter;
}
@ -292,8 +282,6 @@ public class ShiroConfig
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
filters.put("onlineSession", onlineSessionFilter());
filters.put("syncOnlineSession", syncOnlineSessionFilter());
filters.put("captchaValidate", captchaValidateFilter());
filters.put("kickout", kickoutSessionFilter());
// 注销成功,则跳转到指定页面
@ -301,33 +289,12 @@ public class ShiroConfig
shiroFilterFactoryBean.setFilters(filters);
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession");
filterChainDefinitionMap.put("/**", "user,kickout");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 自定义在线用户处理过滤器
*/
public OnlineSessionFilter onlineSessionFilter()
{
OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter();
onlineSessionFilter.setLoginUrl(loginUrl);
onlineSessionFilter.setOnlineSessionDAO(sessionDAO());
return onlineSessionFilter;
}
/**
* 自定义在线用户同步过滤器
*/
public SyncOnlineSessionFilter syncOnlineSessionFilter()
{
SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter();
syncOnlineSessionFilter.setOnlineSessionDAO(sessionDAO());
return syncOnlineSessionFilter;
}
/**
* 自定义验证码过滤器
*/
@ -369,7 +336,7 @@ public class ShiroConfig
public KickoutSessionFilter kickoutSessionFilter()
{
KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();
kickoutSessionFilter.setCacheManager(getEhCacheManager());
kickoutSessionFilter.setCacheManager(redisCacheManager());
kickoutSessionFilter.setSessionManager(sessionManager());
// 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录
kickoutSessionFilter.setMaxSession(maxSession);
@ -393,11 +360,18 @@ public class ShiroConfig
* 开启Shiro注解通知器
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager)
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager)
{
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator()
{
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
}

50
ruoyi-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java

@ -1,55 +1,24 @@
package com.ruoyi.framework.manager;
import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler;
import net.sf.ehcache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
/**
* 确保应用退出时能关闭后台线程
*
* @author cj
* @author ruoyi
*/
@Component
public class ShutdownManager
{
private static final Logger logger = LoggerFactory.getLogger("sys-user");
@Autowired(required = false)
private SpringSessionValidationScheduler springSessionValidationScheduler;
@Autowired(required = false)
private EhCacheManager ehCacheManager;
@PreDestroy
public void destroy()
{
shutdownSpringSessionValidationScheduler();
shutdownAsyncManager();
shutdownEhCacheManager();
}
/**
* 停止Seesion会话检查
*/
private void shutdownSpringSessionValidationScheduler()
{
if (springSessionValidationScheduler != null && springSessionValidationScheduler.isEnabled())
{
try
{
logger.info("====关闭会话验证任务====");
springSessionValidationScheduler.disableSessionValidation();
}
catch (Exception e)
{
logger.error(e.getMessage(), e);
}
}
}
/**
@ -67,21 +36,4 @@ public class ShutdownManager
logger.error(e.getMessage(), e);
}
}
private void shutdownEhCacheManager()
{
try
{
logger.info("====关闭缓存====");
if (ehCacheManager != null)
{
CacheManager cacheManager = ehCacheManager.getCacheManager();
cacheManager.shutdown();
}
}
catch (Exception e)
{
logger.error(e.getMessage(), e);
}
}
}

34
ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java

@ -10,12 +10,9 @@ import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.shiro.session.OnlineSession;
import com.ruoyi.system.domain.SysLogininfor;
import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.domain.SysUserOnline;
import com.ruoyi.system.service.ISysOperLogService;
import com.ruoyi.system.service.ISysUserOnlineService;
import com.ruoyi.system.service.impl.SysLogininforServiceImpl;
import eu.bitwalker.useragentutils.UserAgent;
@ -29,37 +26,6 @@ public class AsyncFactory
{
private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
/**
* 同步session到数据库
*
* @param session 在线用户会话
* @return 任务task
*/
public static TimerTask syncSessionToDb(final OnlineSession session)
{
return new TimerTask()
{
@Override
public void run()
{
SysUserOnline online = new SysUserOnline();
online.setSessionId(String.valueOf(session.getId()));
online.setDeptName(session.getDeptName());
online.setLoginName(session.getLoginName());
online.setStartTimestamp(session.getStartTimestamp());
online.setLastAccessTime(session.getLastAccessTime());
online.setExpireTime(session.getTimeout());
online.setIpaddr(session.getHost());
online.setLoginLocation(AddressUtils.getRealAddressByIP(session.getHost()));
online.setBrowser(session.getBrowser());
online.setOs(session.getOs());
online.setStatus(session.getStatus());
SpringUtils.getBean(ISysUserOnlineService.class).saveOnline(online);
}
};
}
/**
* 操作日志记录
*

41
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java

@ -1,16 +1,13 @@
package com.ruoyi.framework.shiro.service;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import java.util.concurrent.TimeUnit;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.ShiroConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException;
import com.ruoyi.common.utils.MessageUtils;
@ -26,31 +23,40 @@ import com.ruoyi.framework.manager.factory.AsyncFactory;
public class SysPasswordService
{
@Autowired
private CacheManager cacheManager;
private Cache<String, AtomicInteger> loginRecordCache;
private RedisCache redisCache;
@Value(value = "${user.password.maxRetryCount}")
private String maxRetryCount;
@PostConstruct
public void init()
/**
* 登录记录 cache key
*/
private final String SYS_LOGINRECORDCACHE_KEY = "sys_loginRecordCache:";
/**
* 设置cache key
*
* @param loginName 登录名
* @return 缓存键key
*/
private String getCacheKey(String loginName)
{
loginRecordCache = cacheManager.getCache(ShiroConstants.LOGINRECORDCACHE);
return SYS_LOGINRECORDCACHE_KEY + loginName;
}
public void validate(SysUser user, String password)
{
String loginName = user.getLoginName();
AtomicInteger retryCount = loginRecordCache.get(loginName);
Integer retryCount = redisCache.getCacheObject(getCacheKey(loginName));
if (retryCount == null)
{
retryCount = new AtomicInteger(0);
loginRecordCache.put(loginName, retryCount);
retryCount = 0;
redisCache.setCacheObject(getCacheKey(loginName), retryCount, 10, TimeUnit.MINUTES);
}
if (retryCount.incrementAndGet() > Integer.valueOf(maxRetryCount).intValue())
if (retryCount >= Integer.valueOf(maxRetryCount).intValue())
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount)));
throw new UserPasswordRetryLimitExceedException(Integer.valueOf(maxRetryCount).intValue());
@ -58,8 +64,9 @@ public class SysPasswordService
if (!matches(user, password))
{
retryCount = retryCount + 1;
AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.count", retryCount)));
loginRecordCache.put(loginName, retryCount);
redisCache.setCacheObject(getCacheKey(loginName), retryCount, 10, TimeUnit.MINUTES);
throw new UserPasswordNotMatchException();
}
else
@ -75,7 +82,7 @@ public class SysPasswordService
public void clearLoginRecordCache(String loginName)
{
loginRecordCache.remove(loginName);
redisCache.deleteObject(getCacheKey(loginName));
}
public String encryptPassword(String loginName, String password, String salt)

62
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysShiroService.java

@ -1,62 +0,0 @@
package com.ruoyi.framework.shiro.service;
import java.io.Serializable;
import org.apache.shiro.session.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.shiro.session.OnlineSession;
import com.ruoyi.system.domain.SysUserOnline;
import com.ruoyi.system.service.ISysUserOnlineService;
/**
* 会话db操作处理
*
* @author ruoyi
*/
@Component
public class SysShiroService
{
@Autowired
private ISysUserOnlineService onlineService;
/**
* 删除会话
*
* @param onlineSession 会话信息
*/
public void deleteSession(OnlineSession onlineSession)
{
onlineService.deleteOnlineById(String.valueOf(onlineSession.getId()));
}
/**
* 获取会话信息
*
* @param sessionId
* @return
*/
public Session getSession(Serializable sessionId)
{
SysUserOnline userOnline = onlineService.selectOnlineById(String.valueOf(sessionId));
return StringUtils.isNull(userOnline) ? null : createSession(userOnline);
}
public Session createSession(SysUserOnline userOnline)
{
OnlineSession onlineSession = new OnlineSession();
if (StringUtils.isNotNull(userOnline))
{
onlineSession.setId(userOnline.getSessionId());
onlineSession.setHost(userOnline.getIpaddr());
onlineSession.setBrowser(userOnline.getBrowser());
onlineSession.setOs(userOnline.getOs());
onlineSession.setDeptName(userOnline.getDeptName());
onlineSession.setLoginName(userOnline.getLoginName());
onlineSession.setStartTimestamp(userOnline.getStartTimestamp());
onlineSession.setLastAccessTime(userOnline.getLastAccessTime());
onlineSession.setTimeout(userOnline.getExpireTime());
}
return onlineSession;
}
}

117
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/session/OnlineSessionDAO.java

@ -1,117 +0,0 @@
package com.ruoyi.framework.shiro.session;
import java.io.Serializable;
import java.util.Date;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import com.ruoyi.common.enums.OnlineStatus;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.shiro.service.SysShiroService;
/**
* 针对自定义的ShiroSession的db操作
*
* @author ruoyi
*/
public class OnlineSessionDAO extends EnterpriseCacheSessionDAO
{
/**
* 同步session到数据库的周期 单位为毫秒默认1分钟
*/
@Value("${shiro.session.dbSyncPeriod}")
private int dbSyncPeriod;
/**
* 上次同步数据库的时间戳
*/
private static final String LAST_SYNC_DB_TIMESTAMP = OnlineSessionDAO.class.getName() + "LAST_SYNC_DB_TIMESTAMP";
@Autowired
private SysShiroService sysShiroService;
public OnlineSessionDAO()
{
super();
}
public OnlineSessionDAO(long expireTime)
{
super();
}
/**
* 根据会话ID获取会话
*
* @param sessionId 会话ID
* @return ShiroSession
*/
@Override
protected Session doReadSession(Serializable sessionId)
{
return sysShiroService.getSession(sessionId);
}
@Override
public void update(Session session) throws UnknownSessionException
{
super.update(session);
}
/**
* 更新会话如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
*/
public void syncToDb(OnlineSession onlineSession)
{
Date lastSyncTimestamp = (Date) onlineSession.getAttribute(LAST_SYNC_DB_TIMESTAMP);
if (lastSyncTimestamp != null)
{
boolean needSync = true;
long deltaTime = onlineSession.getLastAccessTime().getTime() - lastSyncTimestamp.getTime();
if (deltaTime < dbSyncPeriod * 60 * 1000)
{
// 时间差不足 无需同步
needSync = false;
}
// isGuest = true 访客
boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;
// session 数据变更了 同步
if (!isGuest && onlineSession.isAttributeChanged())
{
needSync = true;
}
if (!needSync)
{
return;
}
}
// 更新上次同步数据库时间
onlineSession.setAttribute(LAST_SYNC_DB_TIMESTAMP, onlineSession.getLastAccessTime());
// 更新完后 重置标识
if (onlineSession.isAttributeChanged())
{
onlineSession.resetAttributeChanged();
}
AsyncManager.me().execute(AsyncFactory.syncSessionToDb(onlineSession));
}
/**
* 当会话过期/停止如用户退出时属性等会调用
*/
@Override
protected void doDelete(Session session)
{
OnlineSession onlineSession = (OnlineSession) session;
if (null == onlineSession)
{
return;
}
onlineSession.setStatus(OnlineStatus.off_line);
sysShiroService.deleteSession(onlineSession);
}
}

29
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/LogoutFilter.java

@ -1,20 +1,23 @@
package com.ruoyi.framework.shiro.web.filter;
import java.io.Serializable;
import java.util.Deque;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.SessionException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.ShiroConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.system.service.ISysUserOnlineService;
/**
* 退出过滤器
@ -30,6 +33,8 @@ public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter
*/
private String loginUrl;
private Cache<String, Deque<Serializable>> cache;
public String getLoginUrl()
{
return loginUrl;
@ -56,7 +61,7 @@ public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter
// 记录用户退出日志
AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
// 清理缓存
SpringUtils.getBean(ISysUserOnlineService.class).removeUserCache(loginName, ShiroUtils.getSessionId());
removeUserCache(loginName, ShiroUtils.getSessionId());
}
// 退出登录
subject.logout();
@ -74,6 +79,17 @@ public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter
return false;
}
public void removeUserCache(String loginName, String sessionId)
{
Deque<Serializable> deque = cache.get(loginName);
if (StringUtils.isEmpty(deque) || deque.size() == 0)
{
return;
}
deque.remove(sessionId);
cache.put(loginName, deque);
}
/**
* 退出跳转URL
*/
@ -87,4 +103,11 @@ public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter
}
return super.getRedirectUrl(request, response, subject);
}
// 设置Cache的key的前缀
public void setCacheManager(CacheManager cacheManager)
{
// 必须和ehcache缓存配置中的缓存name一致
this.cache = cacheManager.getCache(ShiroConstants.SYS_USERCACHE);
}
}

99
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/online/OnlineSessionFilter.java

@ -1,99 +0,0 @@
package com.ruoyi.framework.shiro.web.filter.online;
import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Value;
import com.ruoyi.common.constant.ShiroConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.enums.OnlineStatus;
import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.framework.shiro.session.OnlineSession;
import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
/**
* 自定义访问控制
*
* @author ruoyi
*/
public class OnlineSessionFilter extends AccessControlFilter
{
/**
* 强制退出后重定向的地址
*/
@Value("${shiro.user.loginUrl}")
private String loginUrl;
private OnlineSessionDAO onlineSessionDAO;
/**
* 表示是否允许访问mappedValue就是[urls]配置中拦截器参数部分如果允许访问返回true否则false
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception
{
Subject subject = getSubject(request, response);
if (subject == null || subject.getSession() == null)
{
return true;
}
Session session = onlineSessionDAO.readSession(subject.getSession().getId());
if (session != null && session instanceof OnlineSession)
{
OnlineSession onlineSession = (OnlineSession) session;
request.setAttribute(ShiroConstants.ONLINE_SESSION, onlineSession);
// 把user对象设置进去
boolean isGuest = onlineSession.getUserId() == null || onlineSession.getUserId() == 0L;
if (isGuest == true)
{
SysUser user = ShiroUtils.getSysUser();
if (user != null)
{
onlineSession.setUserId(user.getUserId());
onlineSession.setLoginName(user.getLoginName());
onlineSession.setAvatar(user.getAvatar());
onlineSession.setDeptName(user.getDept().getDeptName());
onlineSession.markAttributeChanged();
}
}
if (onlineSession.getStatus() == OnlineStatus.off_line)
{
return false;
}
}
return true;
}
/**
* 表示当访问拒绝时是否已经处理了如果返回true表示需要继续处理如果返回false表示该拦截器实例已经处理了将直接返回即可
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception
{
Subject subject = getSubject(request, response);
if (subject != null)
{
subject.logout();
}
saveRequestAndRedirectToLogin(request, response);
return false;
}
// 跳转到登录页
@Override
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException
{
WebUtils.issueRedirect(request, response, loginUrl);
}
public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO)
{
this.onlineSessionDAO = onlineSessionDAO;
}
}

39
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/filter/sync/SyncOnlineSessionFilter.java

@ -1,39 +0,0 @@
package com.ruoyi.framework.shiro.web.filter.sync;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.web.filter.PathMatchingFilter;
import com.ruoyi.common.constant.ShiroConstants;
import com.ruoyi.framework.shiro.session.OnlineSession;
import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
/**
* 同步Session数据到Db
*
* @author ruoyi
*/
public class SyncOnlineSessionFilter extends PathMatchingFilter
{
private OnlineSessionDAO onlineSessionDAO;
/**
* 同步会话数据到DB 一次请求最多同步一次 防止过多处理 需要放到Shiro过滤器之前
*/
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception
{
OnlineSession session = (OnlineSession) request.getAttribute(ShiroConstants.ONLINE_SESSION);
// 如果session stop了 也不同步
// session停止时间,如果stopTimestamp不为null,则代表已停止
if (session != null && session.getUserId() != null && session.getStopTimestamp() == null)
{
onlineSessionDAO.syncToDb(session);
}
return true;
}
public void setOnlineSessionDAO(OnlineSessionDAO onlineSessionDAO)
{
this.onlineSessionDAO = onlineSessionDAO;
}
}

175
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/OnlineWebSessionManager.java

@ -1,175 +0,0 @@
package com.ruoyi.framework.shiro.web.session;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.shiro.session.ExpiredSessionException;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.constant.ShiroConstants;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.shiro.session.OnlineSession;
import com.ruoyi.system.domain.SysUserOnline;
import com.ruoyi.system.service.ISysUserOnlineService;
/**
* 主要是在此如果会话的属性修改了 就标识下其修改了 然后方便 OnlineSessionDao同步
*
* @author ruoyi
*/
public class OnlineWebSessionManager extends DefaultWebSessionManager
{
private static final Logger log = LoggerFactory.getLogger(OnlineWebSessionManager.class);
@Override
public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException
{
super.setAttribute(sessionKey, attributeKey, value);
if (value != null && needMarkAttributeChanged(attributeKey))
{
OnlineSession session = getOnlineSession(sessionKey);
session.markAttributeChanged();
}
}
private boolean needMarkAttributeChanged(Object attributeKey)
{
if (attributeKey == null)
{
return false;
}
String attributeKeyStr = attributeKey.toString();
// 优化 flash属性没必要持久化
if (attributeKeyStr.startsWith("org.springframework"))
{
return false;
}
if (attributeKeyStr.startsWith("javax.servlet"))
{
return false;
}
if (attributeKeyStr.equals(ShiroConstants.CURRENT_USERNAME))
{
return false;
}
return true;
}
@Override
public Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException
{
Object removed = super.removeAttribute(sessionKey, attributeKey);
if (removed != null)
{
OnlineSession s = getOnlineSession(sessionKey);
s.markAttributeChanged();
}
return removed;
}
public OnlineSession getOnlineSession(SessionKey sessionKey)
{
OnlineSession session = null;
Object obj = doGetSession(sessionKey);
if (StringUtils.isNotNull(obj))
{
session = new OnlineSession();
BeanUtils.copyBeanProp(session, obj);
}
return session;
}
/**
* 验证session是否有效 用于删除过期session
*/
@Override
public void validateSessions()
{
if (log.isInfoEnabled())
{
log.info("invalidation sessions...");
}
int invalidCount = 0;
int timeout = (int) this.getGlobalSessionTimeout();
if (timeout < 0)
{
// 永不过期不进行处理
return;
}
Date expiredDate = DateUtils.addMilliseconds(new Date(), 0 - timeout);
ISysUserOnlineService userOnlineService = SpringUtils.getBean(ISysUserOnlineService.class);
List<SysUserOnline> userOnlineList = userOnlineService.selectOnlineByExpired(expiredDate);
// 批量过期删除
List<String> needOfflineIdList = new ArrayList<String>();
for (SysUserOnline userOnline : userOnlineList)
{
try
{
SessionKey key = new DefaultSessionKey(userOnline.getSessionId());
Session session = retrieveSession(key);
if (session != null)
{
throw new InvalidSessionException();
}
}
catch (InvalidSessionException e)
{
if (log.isDebugEnabled())
{
boolean expired = (e instanceof ExpiredSessionException);
String msg = "Invalidated session with id [" + userOnline.getSessionId() + "]"
+ (expired ? " (expired)" : " (stopped)");
log.debug(msg);
}
invalidCount++;
needOfflineIdList.add(userOnline.getSessionId());
userOnlineService.removeUserCache(userOnline.getLoginName(), userOnline.getSessionId());
}
}
if (needOfflineIdList.size() > 0)
{
try
{
userOnlineService.batchDeleteOnline(needOfflineIdList);
}
catch (Exception e)
{
log.error("batch delete db session error.", e);
}
}
if (log.isInfoEnabled())
{
String msg = "Finished invalidation session.";
if (invalidCount > 0)
{
msg += " [" + invalidCount + "] sessions were stopped.";
}
else
{
msg += " No sessions were stopped.";
}
log.info(msg);
}
}
@Override
protected Collection<Session> getActiveSessions()
{
throw new UnsupportedOperationException("getActiveSessions method not supported");
}
}

131
ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/web/session/SpringSessionValidationScheduler.java

@ -1,131 +0,0 @@
package com.ruoyi.framework.shiro.web.session;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.SessionValidationScheduler;
import org.apache.shiro.session.mgt.ValidatingSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Lazy;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.Threads;
/**
* 自定义任务调度器完成
*
* @author ruoyi
*/
@Component
public class SpringSessionValidationScheduler implements SessionValidationScheduler
{
private static final Logger log = LoggerFactory.getLogger(SpringSessionValidationScheduler.class);
public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
/**
* 定时器用于处理超时的挂起请求也用于连接断开时的重连
*/
@Autowired
@Qualifier("scheduledExecutorService")
private ScheduledExecutorService executorService;
private volatile boolean enabled = false;
/**
* 会话验证管理器
*/
@Autowired
@Qualifier("sessionManager")
@Lazy
private ValidatingSessionManager sessionManager;
// 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟
@Value("${shiro.session.validationInterval}")
private long sessionValidationInterval;
@Override
public boolean isEnabled()
{
return this.enabled;
}
/**
* Specifies how frequently (in milliseconds) this Scheduler will call the
* {@link org.apache.shiro.session.mgt.ValidatingSessionManager#validateSessions()
* ValidatingSessionManager#validateSessions()} method.
*
* <p>
* Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
*
* @param sessionValidationInterval
*/
public void setSessionValidationInterval(long sessionValidationInterval)
{
this.sessionValidationInterval = sessionValidationInterval;
}
/**
* Starts session validation by creating a spring PeriodicTrigger.
*/
@Override
public void enableSessionValidation()
{
enabled = true;
if (log.isDebugEnabled())
{
log.debug("Scheduling session validation job using Spring Scheduler with "
+ "session validation interval of [" + sessionValidationInterval + "]ms...");
}
try
{
executorService.scheduleAtFixedRate(new Runnable()
{
@Override
public void run()
{
if (enabled)
{
sessionManager.validateSessions();
}
}
}, 1000, sessionValidationInterval * 60 * 1000, TimeUnit.MILLISECONDS);
this.enabled = true;
if (log.isDebugEnabled())
{
log.debug("Session validation job successfully scheduled with Spring Scheduler.");
}
}
catch (Exception e)
{
if (log.isErrorEnabled())
{
log.error("Error starting the Spring Scheduler session validation job. Session validation may not occur.", e);
}
}
}
@Override
public void disableSessionValidation()
{
if (log.isDebugEnabled())
{
log.debug("Stopping Spring Scheduler session validation job...");
}
if (this.enabled)
{
Threads.shutdownAndAwaitTermination(executorService);
}
this.enabled = false;
}
}

100
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/CacheService.java

@ -1,10 +1,22 @@
package com.ruoyi.framework.web.service;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.session.mgt.SimpleSession;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.crazycake.shiro.IRedisManager;
import org.crazycake.shiro.exception.SerializationException;
import org.crazycake.shiro.serializer.ObjectSerializer;
import org.crazycake.shiro.serializer.StringSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.CacheUtils;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.StringUtils;
/**
* 缓存操作处理
@ -14,6 +26,16 @@ import com.ruoyi.common.utils.CacheUtils;
@Service
public class CacheService
{
@Autowired
private RedisCache redisCache;
@Autowired
private IRedisManager redisManager;
private final String DEFAULT_SESSION_KEY_PREFIX = "shiro:session:";
private final String DEFAULT_AUTHCACHE_KEY_PREFIX = "shiro:cache:sys-authCache";
/**
* 获取所有缓存名称
*
@ -21,8 +43,9 @@ public class CacheService
*/
public String[] getCacheNames()
{
String[] cacheNames = CacheUtils.getCacheNames();
return ArrayUtils.removeElement(cacheNames, Constants.SYS_AUTH_CACHE);
String[] cacheNames = { "shiro:session", "shiro:cache:sys-authCache", "shiro:cache:sys-userCache", "sys_dict",
"sys_config", "sys_loginRecordCache" };
return cacheNames;
}
/**
@ -33,7 +56,13 @@ public class CacheService
*/
public Set<String> getCacheKeys(String cacheName)
{
return CacheUtils.getCache(cacheName).keys();
Set<String> tmpKeys = new HashSet<String>();
Collection<String> cacheKeys = redisCache.keys(cacheName + ":*");
for (String cacheKey : cacheKeys)
{
tmpKeys.add(cacheKey);
}
return tmpKeys;
}
/**
@ -45,7 +74,52 @@ public class CacheService
*/
public Object getCacheValue(String cacheName, String cacheKey)
{
return CacheUtils.get(cacheName, cacheKey);
if (cacheKey.contains(DEFAULT_SESSION_KEY_PREFIX))
{
try
{
SimpleSession simpleSession = (SimpleSession) new ObjectSerializer().deserialize(redisManager.get(new StringSerializer().serialize(cacheKey)));
Object obj = simpleSession.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (null == obj)
{
return "未登录会话";
}
if (obj instanceof SimplePrincipalCollection)
{
SimplePrincipalCollection spc = (SimplePrincipalCollection) obj;
obj = spc.getPrimaryPrincipal();
if (null != obj && obj instanceof SysUser)
{
SysUser sysUser = (SysUser) obj;
return sysUser;
}
}
return obj;
}
catch (SerializationException e)
{
return null;
}
}
if (cacheKey.contains(DEFAULT_AUTHCACHE_KEY_PREFIX))
{
try
{
SimpleAuthorizationInfo simpleAuthorizationInfo = (SimpleAuthorizationInfo) new ObjectSerializer().deserialize(redisManager.get(new StringSerializer().serialize(cacheKey)));
JSONObject authJson = new JSONObject();
if (StringUtils.isNotNull(simpleAuthorizationInfo))
{
authJson.put("roles", simpleAuthorizationInfo.getRoles());
authJson.put("permissions", simpleAuthorizationInfo.getStringPermissions());
}
return authJson;
}
catch (SerializationException e)
{
return null;
}
}
return redisCache.getCacheObject(cacheKey);
}
/**
@ -55,7 +129,8 @@ public class CacheService
*/
public void clearCacheName(String cacheName)
{
CacheUtils.removeAll(cacheName);
Collection<String> cacheKeys = redisCache.keys(cacheName + ":*");
redisCache.deleteObject(cacheKeys);
}
/**
@ -66,7 +141,7 @@ public class CacheService
*/
public void clearCacheKey(String cacheName, String cacheKey)
{
CacheUtils.remove(cacheName, cacheKey);
redisCache.deleteObject(cacheKey);
}
/**
@ -74,10 +149,7 @@ public class CacheService
*/
public void clearAll()
{
String[] cacheNames = getCacheNames();
for (String cacheName : cacheNames)
{
CacheUtils.removeAll(cacheName);
}
Collection<String> cacheKeys = redisCache.keys("*");
redisCache.deleteObject(cacheKeys);
}
}

52
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserOnlineMapper.java

@ -1,52 +0,0 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.system.domain.SysUserOnline;
/**
* 在线用户 数据层
*
* @author ruoyi
*/
public interface SysUserOnlineMapper
{
/**
* 通过会话序号查询信息
*
* @param sessionId 会话ID
* @return 在线用户信息
*/
public SysUserOnline selectOnlineById(String sessionId);
/**
* 通过会话序号删除信息
*
* @param sessionId 会话ID
* @return 在线用户信息
*/
public int deleteOnlineById(String sessionId);
/**
* 保存会话信息
*
* @param online 会话信息
* @return 结果
*/
public int saveOnline(SysUserOnline online);
/**
* 查询会话集合
*
* @param userOnline 会话参数
* @return 会话集合
*/
public List<SysUserOnline> selectUserOnlineList(SysUserOnline userOnline);
/**
* 查询过期会话集合
*
* @param lastAccessTime 过期时间
* @return 会话集合
*/
public List<SysUserOnline> selectOnlineByExpired(String lastAccessTime);
}

4
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java

@ -70,4 +70,8 @@ public interface ISysConfigService
* @return 结果
*/
public String checkConfigKeyUnique(SysConfig config);
public void resetConfigCache();
public void loadingConfigCache();
}

4
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysDictTypeService.java

@ -96,4 +96,8 @@ public interface ISysDictTypeService
* @return 所有字典类型
*/
public List<Ztree> selectDictTree(SysDictType dictType);
public void loadingDictCache();
public void resetDictCache();
}

75
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserOnlineService.java

@ -1,75 +0,0 @@
package com.ruoyi.system.service;
import java.util.Date;
import java.util.List;
import com.ruoyi.system.domain.SysUserOnline;
/**
* 在线用户 服务层
*
* @author ruoyi
*/
public interface ISysUserOnlineService
{
/**
* 通过会话序号查询信息
*
* @param sessionId 会话ID
* @return 在线用户信息
*/
public SysUserOnline selectOnlineById(String sessionId);
/**
* 通过会话序号删除信息
*
* @param sessionId 会话ID
* @return 在线用户信息
*/
public void deleteOnlineById(String sessionId);
/**
* 通过会话序号删除信息
*
* @param sessions 会话ID集合
* @return 在线用户信息
*/
public void batchDeleteOnline(List<String> sessions);
/**
* 保存会话信息
*
* @param online 会话信息
*/
public void saveOnline(SysUserOnline online);
/**
* 查询会话集合
*
* @param userOnline 分页参数
* @return 会话集合
*/
public List<SysUserOnline> selectUserOnlineList(SysUserOnline userOnline);
/**
* 强退用户
*
* @param sessionId 会话ID
*/
public void forceLogout(String sessionId);
/**
* 清理用户缓存
*
* @param loginName 登录名称
* @param sessionId 会话ID
*/
public void removeUserCache(String loginName, String sessionId);
/**
* 查询会话集合
*
* @param expiredDate 有效期
* @return 会话集合
*/
public List<SysUserOnline> selectOnlineByExpired(Date expiredDate);
}

70
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java

@ -1,14 +1,16 @@
package com.ruoyi.system.service.impl;
import java.util.Collection;
import java.util.List;
import javax.annotation.PostConstruct;
import com.ruoyi.common.exception.BusinessException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.utils.CacheUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
@ -25,17 +27,16 @@ public class SysConfigServiceImpl implements ISysConfigService
@Autowired
private SysConfigMapper configMapper;
@Autowired
private RedisCache redisCache;
/**
* 项目启动时初始化参数到缓存
*/
@PostConstruct
public void init()
{
List<SysConfig> configsList = configMapper.selectConfigList(new SysConfig());
for (SysConfig config : configsList)
{
CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue());
}
loadingConfigCache();
}
/**
@ -61,7 +62,7 @@ public class SysConfigServiceImpl implements ISysConfigService
@Override
public String selectConfigByKey(String configKey)
{
String configValue = Convert.toStr(CacheUtils.get(getCacheName(), getCacheKey(configKey)));
String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey)));
if (StringUtils.isNotEmpty(configValue))
{
return configValue;
@ -71,7 +72,7 @@ public class SysConfigServiceImpl implements ISysConfigService
SysConfig retConfig = configMapper.selectConfig(config);
if (StringUtils.isNotNull(retConfig))
{
CacheUtils.put(getCacheName(), getCacheKey(configKey), retConfig.getConfigValue());
redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());
return retConfig.getConfigValue();
}
return StringUtils.EMPTY;
@ -101,7 +102,7 @@ public class SysConfigServiceImpl implements ISysConfigService
int row = configMapper.insertConfig(config);
if (row > 0)
{
CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue());
redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
}
return row;
}
@ -118,7 +119,7 @@ public class SysConfigServiceImpl implements ISysConfigService
int row = configMapper.updateConfig(config);
if (row > 0)
{
CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue());
redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
}
return row;
}
@ -127,11 +128,11 @@ public class SysConfigServiceImpl implements ISysConfigService
* 批量删除参数配置对象
*
* @param ids 需要删除的数据ID
* @return 结果
*/
@Override
public int deleteConfigByIds(String ids)
{
int result = 0;
Long[] configIds = Convert.toLongArray(ids);
for (Long configId : configIds)
{
@ -140,23 +141,44 @@ public class SysConfigServiceImpl implements ISysConfigService
{
throw new BusinessException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey()));
}
}
int count = configMapper.deleteConfigByIds(Convert.toStrArray(ids));
if (count > 0)
{
result+=count;
redisCache.deleteObject(getCacheKey(config.getConfigKey()));
}
return result;
}
CacheUtils.removeAll(getCacheName());
/**
* 加载参数缓存数据
*/
@Override
public void loadingConfigCache()
{
List<SysConfig> configsList = configMapper.selectConfigList(new SysConfig());
for (SysConfig config : configsList)
{
redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
}
return count;
}
/**
* 清空缓存数据
* 清空参数缓存数据
*/
@Override
public void clearCache()
{
CacheUtils.removeAll(getCacheName());
Collection<String> keys = redisCache.keys(Constants.SYS_CONFIG_KEY + "*");
redisCache.deleteObject(keys);
}
/**
* 重置参数缓存数据
*/
@Override
public void resetConfigCache()
{
clearCache();
loadingConfigCache();
}
/**
@ -177,16 +199,6 @@ public class SysConfigServiceImpl implements ISysConfigService
return UserConstants.CONFIG_KEY_UNIQUE;
}
/**
* 获取cache name
*
* @return 缓存名
*/
private String getCacheName()
{
return Constants.SYS_CONFIG_CACHE;
}
/**
* 设置cache key
*

66
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java

@ -1,7 +1,10 @@
package com.ruoyi.system.service.impl;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -38,12 +41,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
@PostConstruct
public void init()
{
List<SysDictType> dictTypeList = dictTypeMapper.selectDictTypeAll();
for (SysDictType dictType : dictTypeList)
{
List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(dictType.getDictType());
DictUtils.setDictCache(dictType.getDictType(), dictDatas);
}
loadingDictCache();
}
/**
@ -120,11 +118,11 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
* 批量删除字典类型
*
* @param ids 需要删除的数据
* @return 结果
*/
@Override
public int deleteDictTypeByIds(String ids)
{
int result = 0;
Long[] dictIds = Convert.toLongArray(ids);
for (Long dictId : dictIds)
{
@ -133,17 +131,30 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
{
throw new BusinessException(String.format("%1$s已分配,不能删除", dictType.getDictName()));
}
int count = dictTypeMapper.deleteDictTypeById(dictId);
result+=count;
DictUtils.removeDictCache(dictType.getDictType());
}
int count = dictTypeMapper.deleteDictTypeByIds(dictIds);
if (count > 0)
return result;
}
/**
* 加载字典缓存数据
*/
@Override
public void loadingDictCache()
{
DictUtils.clearDictCache();
SysDictData dictData = new SysDictData();
dictData.setStatus("0");
Map<String, List<SysDictData>> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType));
for (Map.Entry<String, List<SysDictData>> entry : dictDataMap.entrySet())
{
DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList()));
}
return count;
}
/**
* 清空缓存数据
* 清空字典缓存数据
*/
@Override
public void clearCache()
@ -151,19 +162,29 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
DictUtils.clearDictCache();
}
/**
* 重置字典缓存数据
*/
@Override
public void resetDictCache()
{
clearCache();
loadingDictCache();
}
/**
* 新增保存字典类型信息
*
* @param dictType 字典类型信息
* @param dict 字典类型信息
* @return 结果
*/
@Override
public int insertDictType(SysDictType dictType)
public int insertDictType(SysDictType dict)
{
int row = dictTypeMapper.insertDictType(dictType);
int row = dictTypeMapper.insertDictType(dict);
if (row > 0)
{
DictUtils.clearDictCache();
DictUtils.setDictCache(dict.getDictType(), null);
}
return row;
}
@ -171,19 +192,20 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
/**
* 修改保存字典类型信息
*
* @param dictType 字典类型信息
* @param dict 字典类型信息
* @return 结果
*/
@Override
@Transactional
public int updateDictType(SysDictType dictType)
public int updateDictType(SysDictType dict)
{
SysDictType oldDict = dictTypeMapper.selectDictTypeById(dictType.getDictId());
dictDataMapper.updateDictDataType(oldDict.getDictType(), dictType.getDictType());
int row = dictTypeMapper.updateDictType(dictType);
SysDictType oldDict = dictTypeMapper.selectDictTypeById(dict.getDictId());
dictDataMapper.updateDictDataType(oldDict.getDictType(), dict.getDictType());
int row = dictTypeMapper.updateDictType(dict);
if (row > 0)
{
DictUtils.clearDictCache();
List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(dict.getDictType());
DictUtils.setDictCache(dict.getDictType(), dictDatas);
}
return row;
}

141
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserOnlineServiceImpl.java

@ -1,141 +0,0 @@
package com.ruoyi.system.service.impl;
import java.io.Serializable;
import java.util.Date;
import java.util.Deque;
import java.util.List;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.constant.ShiroConstants;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysUserOnline;
import com.ruoyi.system.mapper.SysUserOnlineMapper;
import com.ruoyi.system.service.ISysUserOnlineService;
/**
* 在线用户 服务层处理
*
* @author ruoyi
*/
@Service
public class SysUserOnlineServiceImpl implements ISysUserOnlineService
{
@Autowired
private SysUserOnlineMapper userOnlineDao;
@Autowired
private EhCacheManager ehCacheManager;
/**
* 通过会话序号查询信息
*
* @param sessionId 会话ID
* @return 在线用户信息
*/
@Override
public SysUserOnline selectOnlineById(String sessionId)
{
return userOnlineDao.selectOnlineById(sessionId);
}
/**
* 通过会话序号删除信息
*
* @param sessionId 会话ID
* @return 在线用户信息
*/
@Override
public void deleteOnlineById(String sessionId)
{
SysUserOnline userOnline = selectOnlineById(sessionId);
if (StringUtils.isNotNull(userOnline))
{
userOnlineDao.deleteOnlineById(sessionId);
}
}
/**
* 通过会话序号删除信息
*
* @param sessions 会话ID集合
* @return 在线用户信息
*/
@Override
public void batchDeleteOnline(List<String> sessions)
{
for (String sessionId : sessions)
{
SysUserOnline userOnline = selectOnlineById(sessionId);
if (StringUtils.isNotNull(userOnline))
{
userOnlineDao.deleteOnlineById(sessionId);
}
}
}
/**
* 保存会话信息
*
* @param online 会话信息
*/
@Override
public void saveOnline(SysUserOnline online)
{
userOnlineDao.saveOnline(online);
}
/**
* 查询会话集合
*
* @param userOnline 在线用户
*/
@Override
public List<SysUserOnline> selectUserOnlineList(SysUserOnline userOnline)
{
return userOnlineDao.selectUserOnlineList(userOnline);
}
/**
* 强退用户
*
* @param sessionId 会话ID
*/
@Override
public void forceLogout(String sessionId)
{
userOnlineDao.deleteOnlineById(sessionId);
}
/**
* 清理用户缓存
*
* @param loginName 登录名称
* @param sessionId 会话ID
*/
@Override
public void removeUserCache(String loginName, String sessionId)
{
Cache<String, Deque<Serializable>> cache = ehCacheManager.getCache(ShiroConstants.SYS_USERCACHE);
Deque<Serializable> deque = cache.get(loginName);
if (StringUtils.isEmpty(deque) || deque.size() == 0)
{
return;
}
deque.remove(sessionId);
}
/**
* 查询会话集合
*
* @param expiredDate 失效日期
*/
@Override
public List<SysUserOnline> selectOnlineByExpired(Date expiredDate)
{
String lastAccessTime = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, expiredDate);
return userOnlineDao.selectOnlineByExpired(lastAccessTime);
}
}

57
ruoyi-system/src/main/resources/mapper/system/SysUserOnlineMapper.xml

@ -1,57 +0,0 @@
<?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.ruoyi.system.mapper.SysUserOnlineMapper">
<resultMap type="SysUserOnline" id="SysUserOnlineResult">
<id property="sessionId" column="sessionId" />
<result property="loginName" column="login_name" />
<result property="deptName" column="dept_name" />
<result property="ipaddr" column="ipaddr" />
<result property="loginLocation" column="login_location" />
<result property="browser" column="browser" />
<result property="os" column="os" />
<result property="status" column="status" />
<result property="startTimestamp" column="start_timestamp" />
<result property="lastAccessTime" column="last_access_time" />
<result property="expireTime" column="expire_time" />
</resultMap>
<sql id="selectOnlineVo">
select sessionId, login_name, dept_name, ipaddr, login_location, browser, os, status, start_timestamp, last_access_time, expire_time
from sys_user_online
</sql>
<select id="selectOnlineById" parameterType="String" resultMap="SysUserOnlineResult">
<include refid="selectOnlineVo"/>
where sessionId = #{sessionId}
</select>
<insert id="saveOnline" parameterType="SysUserOnline">
replace into sys_user_online(sessionId, login_name, dept_name, ipaddr, login_location, browser, os, status, start_timestamp, last_access_time, expire_time)
values (#{sessionId}, #{loginName}, #{deptName}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{status}, #{startTimestamp}, #{lastAccessTime}, #{expireTime})
</insert>
<delete id="deleteOnlineById" parameterType="String">
delete from sys_user_online where sessionId = #{sessionId}
</delete>
<select id="selectUserOnlineList" parameterType="SysUserOnline" resultMap="SysUserOnlineResult">
<include refid="selectOnlineVo"/>
<where>
<if test="ipaddr != null and ipaddr != ''">
AND ipaddr like concat('%', #{ipaddr}, '%')
</if>
<if test="loginName != null and loginName != ''">
AND login_name like concat('%', #{loginName}, '%')
</if>
</where>
</select>
<select id="selectOnlineByExpired" parameterType="String" resultMap="SysUserOnlineResult">
<include refid="selectOnlineVo"/> o
WHERE o.last_access_time <![CDATA[ <= ]]> #{lastAccessTime} ORDER BY o.last_access_time ASC
</select>
</mapper>
Loading…
Cancel
Save