Browse Source

[add]:整合redis

erp、
youjianchi 10 months ago
parent
commit
0a377aab60
  1. 135
      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. 21
      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

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

@ -1,26 +1,37 @@
package com.ruoyi.web.controller.monitor; 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 java.util.List;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; 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.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.ShiroConstants;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; 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.page.TableDataInfo;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.enums.OnlineStatus;
import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.framework.shiro.session.OnlineSession; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.shiro.session.OnlineSessionDAO; import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.domain.SysUserOnline; 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"; private String prefix = "monitor/online";
@Autowired @Autowired
private ISysUserOnlineService userOnlineService; private RedisSessionDAO redisSessionDAO;
@Autowired
private OnlineSessionDAO onlineSessionDAO;
@RequiresPermissions("monitor:online:view") @RequiresPermissions("monitor:online:view")
@GetMapping() @GetMapping()
@ -51,38 +59,109 @@ public class SysUserOnlineController extends BaseController
@ResponseBody @ResponseBody
public TableDataInfo list(SysUserOnline userOnline) public TableDataInfo list(SysUserOnline userOnline)
{ {
startPage(); String ipaddr = userOnline.getIpaddr();
List<SysUserOnline> list = userOnlineService.selectUserOnlineList(userOnline); String loginName = userOnline.getLoginName();
return getDataTable(list); 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) @RequiresPermissions(value = { "monitor:online:batchForceLogout", "monitor:online:forceLogout" }, logical = Logical.OR)
@Log(title = "在线用户", businessType = BusinessType.FORCE) @Log(title = "在线用户", businessType = BusinessType.FORCE)
@PostMapping("/batchForceLogout") @PostMapping("/batchForceLogout")
@ResponseBody @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); String sessionId = userOnline.getSessionId();
if (online == null) String loginName = userOnline.getLoginName();
{
return error("用户已下线");
}
OnlineSession onlineSession = (OnlineSession) onlineSessionDAO.readSession(online.getSessionId());
if (onlineSession == null)
{
return error("用户已下线");
}
if (sessionId.equals(ShiroUtils.getSessionId())) if (sessionId.equals(ShiroUtils.getSessionId()))
{ {
return error("当前登录用户无法强退"); return error("当前登录用户无法强退");
} }
onlineSessionDAO.delete(onlineSession); redisSessionDAO.delete(redisSessionDAO.readSession(sessionId));
online.setStatus(OnlineStatus.off_line); removeUserCache(loginName, sessionId);
userOnlineService.saveOnline(online);
userOnlineService.removeUserCache(online.getLoginName(), sessionId);
} }
return success(); return success();
} }
private SysUserOnline getSession(Session session)
{
Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (null == obj)
{
return null;
}
if (obj instanceof SimplePrincipalCollection)
{
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());
}
userOnline.setIpaddr(session.getHost());
userOnline.setStartTimestamp(session.getStartTimestamp());
userOnline.setLastAccessTime(session.getLastAccessTime());
return userOnline;
}
}
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: 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: datasource:
type: com.alibaba.druid.pool.DruidDataSource type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver 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() { $(function() {
var options = { var options = {
sidePagination: "client",
uniqueId: "sessionId", uniqueId: "sessionId",
url: prefix + "/list", url: prefix + "/list",
exportUrl: prefix + "/export", exportUrl: prefix + "/export",
@ -80,30 +81,6 @@
field: 'ipaddr', field: 'ipaddr',
title: '主机' 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', field: 'startTimestamp',
title: '登录时间', title: '登录时间',
@ -118,7 +95,7 @@
title: '操作', title: '操作',
align: 'center', align: 'center',
formatter: function(value, row, index) { 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; return msg;
} }
}] }]
@ -127,10 +104,31 @@
}); });
// 单条强退 // 单条强退
function forceLogout(sessionId) { function forceLogout(sessionId, loginName) {
$.modal.confirm("确定要强制选中用户下线吗?", function() { $.modal.confirm("确定要强制选中用户下线吗?", function() {
var data = { "ids": sessionId }; var data = [];
$.operate.post(prefix + "/batchForceLogout", 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; return;
} }
$.modal.confirm("确认要强退选中的" + rows.length + "条数据吗?", function() { $.modal.confirm("确认要强退选中的" + rows.length + "条数据吗?", function() {
var url = prefix + "/batchForceLogout"; var data = [];
var data = { "ids": rows.join() }; $.map($("#" + table.options.id).bootstrapTable('getSelections'), function (row) {
$.operate.post(url, data); 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> </script>

21
ruoyi-common/pom.xml

@ -48,11 +48,24 @@
<groupId>org.apache.shiro</groupId> <groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId> <artifactId>shiro-core</artifactId>
</dependency> </dependency>
<!-- Shiro使用EhCache缓存框架 --> <!-- shiro整合redis -->
<dependency> <dependency>
<groupId>org.apache.shiro</groupId> <groupId>org.crazycake</groupId>
<artifactId>shiro-ehcache</artifactId> <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> </dependency>
<!-- pagehelper 分页插件 --> <!-- 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.Iterator;
import java.util.Set; import java.util.Set;
import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager; import org.crazycake.shiro.RedisCache;
import org.apache.shiro.cache.ehcache.EhCacheManager; import org.crazycake.shiro.RedisCacheManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
@ -18,7 +18,7 @@ public class CacheUtils
{ {
private static Logger logger = LoggerFactory.getLogger(CacheUtils.class); 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"; private static final String SYS_CACHE = "sys-cache";
@ -124,7 +124,7 @@ public class CacheUtils
*/ */
public static void removeAll(String cacheName) public static void removeAll(String cacheName)
{ {
Cache<String, Object> cache = getCache(cacheName); RedisCache<String, Object> cache = getCache(cacheName);
Set<String> keys = cache.keys(); Set<String> keys = cache.keys();
for (Iterator<String> it = keys.iterator(); it.hasNext();) for (Iterator<String> it = keys.iterator(); it.hasNext();)
{ {
@ -175,23 +175,13 @@ public class CacheUtils
* @param cacheName * @param cacheName
* @return * @return
*/ */
public static Cache<String, Object> getCache(String cacheName) public static RedisCache<String, Object> getCache(String cacheName)
{ {
Cache<String, Object> cache = cacheManager.getCache(cacheName); Cache<String, Object> cache = cacheManager.getCache(cacheName);
if (cache == null) if (cache == null)
{ {
throw new RuntimeException("当前系统中没有定义“" + cacheName + "”这个缓存。"); throw new RuntimeException("当前系统中没有定义“" + cacheName + "”这个缓存。");
} }
return cache; return (RedisCache<String, Object>) cache;
}
/**
* 获取所有缓存
*
* @return 缓存组
*/
public static String[] getCacheNames()
{
return ((EhCacheManager) cacheManager).getCacheManager().getCacheNames();
} }
} }

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

@ -1,9 +1,12 @@
package com.ruoyi.common.utils; package com.ruoyi.common.utils;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysDictData; 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) 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) 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)) if (StringUtils.isNotNull(cacheObj))
{ {
List<SysDictData> DictDatas = StringUtils.cast(cacheObj); List<SysDictData> DictDatas = StringUtils.cast(cacheObj);
@ -83,7 +86,7 @@ public class DictUtils
StringBuilder propertyString = new StringBuilder(); StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType); 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) for (SysDictData dict : datas)
{ {
@ -91,7 +94,7 @@ public class DictUtils
{ {
if (value.equals(dict.getDictValue())) if (value.equals(dict.getDictValue()))
{ {
propertyString.append(dict.getDictLabel() + separator); propertyString.append(dict.getDictLabel()).append(separator);
break; break;
} }
} }
@ -123,7 +126,7 @@ public class DictUtils
StringBuilder propertyString = new StringBuilder(); StringBuilder propertyString = new StringBuilder();
List<SysDictData> datas = getDictCache(dictType); 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) for (SysDictData dict : datas)
{ {
@ -131,7 +134,7 @@ public class DictUtils
{ {
if (label.equals(dict.getDictLabel())) if (label.equals(dict.getDictLabel()))
{ {
propertyString.append(dict.getDictValue() + separator); propertyString.append(dict.getDictValue()).append(separator);
break; break;
} }
} }
@ -150,12 +153,23 @@ public class DictUtils
return StringUtils.stripEnd(propertyString.toString(), separator); 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() 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; package com.ruoyi.framework.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import java.util.LinkedHashMap;
import com.ruoyi.common.constant.Constants; import java.util.Map;
import com.ruoyi.common.utils.StringUtils; import javax.servlet.Filter;
import com.ruoyi.common.utils.security.CipherUtils; import org.apache.shiro.codec.Base64;
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 org.apache.shiro.mgt.SecurityManager; 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.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie; 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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.constant.Constants;
import javax.servlet.Filter; import com.ruoyi.common.utils.StringUtils;
import java.io.ByteArrayInputStream; import com.ruoyi.common.utils.security.CipherUtils;
import java.io.IOException; import com.ruoyi.framework.shiro.realm.UserRealm;
import java.io.InputStream; import com.ruoyi.framework.shiro.web.filter.LogoutFilter;
import java.util.LinkedHashMap; import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter;
import java.util.Map; 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; 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 @Bean
public EhCacheManager getEhCacheManager() public RedisCacheManager redisCacheManager()
{ {
net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi"); RedisCacheManager redisCacheManager = new RedisCacheManager();
EhCacheManager em = new EhCacheManager(); redisCacheManager.setRedisManager(redisManager());
if (StringUtils.isNull(cacheManager)) redisCacheManager.setPrincipalIdFieldName("userId");
{ return redisCacheManager;
em.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream()));
return em;
}
else
{
em.setCacheManager(cacheManager);
return em;
}
} }
/** /**
* 返回配置文件流 避免ehcache配置文件一直被占用无法完全销毁项目重新部署 * RedisManager (shiro-redis)
*/ */
protected InputStream getCacheManagerConfigFileInputStream() @Bean
public RedisManager redisManager()
{ {
String configFile = "classpath:ehcache/ehcache-shiro.xml"; RedisManager redisManager = new RedisManager();
InputStream inputStream = null; redisManager.setHost(redisHost + ":" + redisPort);
try redisManager.setDatabase(database);
{ if (StringUtils.isNotEmpty(password))
inputStream = ResourceUtils.getInputStreamForPath(configFile);
byte[] b = IOUtils.toByteArray(inputStream);
InputStream in = new ByteArrayInputStream(b);
return in;
}
catch (IOException e)
{ {
throw new ConfigurationException( redisManager.setPassword(password);
"Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e);
}
finally
{
IOUtils.closeQuietly(inputStream);
} }
redisManager.setTimeout(expireTime * 60);
return redisManager;
} }
/** /**
* 自定义Realm * 自定义Realm
*/ */
@Bean @Bean
public UserRealm userRealm(EhCacheManager cacheManager) public UserRealm userRealm()
{ {
UserRealm userRealm = new UserRealm(); UserRealm userRealm = new UserRealm();
userRealm.setAuthorizationCacheName(Constants.SYS_AUTH_CACHE); userRealm.setAuthorizationCacheName(Constants.SYS_AUTH_CACHE);
userRealm.setCacheManager(cacheManager); userRealm.setCacheManager(redisCacheManager());
return userRealm; return userRealm;
} }
/** /**
* 自定义sessionDAO会话 * RedisSessionDAO (shiro-redis)
*/
@Bean
public OnlineSessionDAO sessionDAO()
{
OnlineSessionDAO sessionDAO = new OnlineSessionDAO();
return sessionDAO;
}
/**
* 自定义sessionFactory会话
*/ */
@Bean @Bean
public OnlineSessionFactory sessionFactory() public RedisSessionDAO redisSessionDAO()
{ {
OnlineSessionFactory sessionFactory = new OnlineSessionFactory(); RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
return sessionFactory; redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setExpire(expireTime * 60);
return redisSessionDAO;
} }
/** /**
* 会话管理器 * 会话管理器
*/ */
@Bean @Bean
public OnlineWebSessionManager sessionManager() public SessionManager sessionManager()
{ {
OnlineWebSessionManager manager = new OnlineWebSessionManager(); DefaultWebSessionManager manager = new DefaultWebSessionManager();
// 加入缓存管理器 // 加入缓存管理器
manager.setCacheManager(getEhCacheManager()); manager.setCacheManager(redisCacheManager());
// 删除过期的session
manager.setDeleteInvalidSessions(true);
// 设置全局session超时时间
manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
// 去掉 JSESSIONID // 去掉 JSESSIONID
manager.setSessionIdUrlRewritingEnabled(false); manager.setSessionIdUrlRewritingEnabled(false);
// 定义要使用的无效的Session定时调度器
manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class));
// 是否定时检查session
manager.setSessionValidationSchedulerEnabled(true);
// 自定义SessionDao // 自定义SessionDao
manager.setSessionDAO(sessionDAO()); manager.setSessionDAO(redisSessionDAO());
// 自定义sessionFactory // 设置全局session超时时间
manager.setSessionFactory(sessionFactory()); manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
return manager; return manager;
} }
@ -230,9 +219,9 @@ public class ShiroConfig
// 设置realm. // 设置realm.
securityManager.setRealm(userRealm); securityManager.setRealm(userRealm);
// 记住我 // 记住我
securityManager.setRememberMeManager(rememberMeManager()); securityManager.setRememberMeManager(rememberMe ? rememberMeManager() : null);
// 注入缓存管理器; // 注入缓存管理器;
securityManager.setCacheManager(getEhCacheManager()); securityManager.setCacheManager(redisCacheManager());
// session管理器 // session管理器
securityManager.setSessionManager(sessionManager()); securityManager.setSessionManager(sessionManager());
return securityManager; return securityManager;
@ -245,6 +234,7 @@ public class ShiroConfig
{ {
LogoutFilter logoutFilter = new LogoutFilter(); LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setLoginUrl(loginUrl); logoutFilter.setLoginUrl(loginUrl);
logoutFilter.setCacheManager(redisCacheManager());
return logoutFilter; return logoutFilter;
} }
@ -292,8 +282,6 @@ public class ShiroConfig
Map<String, Filter> filters = new LinkedHashMap<String, Filter>(); Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
filters.put("onlineSession", onlineSessionFilter());
filters.put("syncOnlineSession", syncOnlineSessionFilter());
filters.put("captchaValidate", captchaValidateFilter()); filters.put("captchaValidate", captchaValidateFilter());
filters.put("kickout", kickoutSessionFilter()); filters.put("kickout", kickoutSessionFilter());
// 注销成功,则跳转到指定页面 // 注销成功,则跳转到指定页面
@ -301,33 +289,12 @@ public class ShiroConfig
shiroFilterFactoryBean.setFilters(filters); shiroFilterFactoryBean.setFilters(filters);
// 所有请求需要认证 // 所有请求需要认证
filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession"); filterChainDefinitionMap.put("/**", "user,kickout");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean; 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() public KickoutSessionFilter kickoutSessionFilter()
{ {
KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter(); KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();
kickoutSessionFilter.setCacheManager(getEhCacheManager()); kickoutSessionFilter.setCacheManager(redisCacheManager());
kickoutSessionFilter.setSessionManager(sessionManager()); kickoutSessionFilter.setSessionManager(sessionManager());
// 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录 // 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录
kickoutSessionFilter.setMaxSession(maxSession); kickoutSessionFilter.setMaxSession(maxSession);
@ -393,11 +360,18 @@ public class ShiroConfig
* 开启Shiro注解通知器 * 开启Shiro注解通知器
*/ */
@Bean @Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager)
@Qualifier("securityManager") SecurityManager securityManager)
{ {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor; 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; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
/** /**
* 确保应用退出时能关闭后台线程 * 确保应用退出时能关闭后台线程
* *
* @author cj * @author ruoyi
*/ */
@Component @Component
public class ShutdownManager public class ShutdownManager
{ {
private static final Logger logger = LoggerFactory.getLogger("sys-user"); private static final Logger logger = LoggerFactory.getLogger("sys-user");
@Autowired(required = false)
private SpringSessionValidationScheduler springSessionValidationScheduler;
@Autowired(required = false)
private EhCacheManager ehCacheManager;
@PreDestroy @PreDestroy
public void destroy() public void destroy()
{ {
shutdownSpringSessionValidationScheduler();
shutdownAsyncManager(); 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); 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.ShiroUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils; 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.SysLogininfor;
import com.ruoyi.system.domain.SysOperLog; import com.ruoyi.system.domain.SysOperLog;
import com.ruoyi.system.domain.SysUserOnline;
import com.ruoyi.system.service.ISysOperLogService; import com.ruoyi.system.service.ISysOperLogService;
import com.ruoyi.system.service.ISysUserOnlineService;
import com.ruoyi.system.service.impl.SysLogininforServiceImpl; import com.ruoyi.system.service.impl.SysLogininforServiceImpl;
import eu.bitwalker.useragentutils.UserAgent; import eu.bitwalker.useragentutils.UserAgent;
@ -29,37 +26,6 @@ public class AsyncFactory
{ {
private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); 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; package com.ruoyi.framework.shiro.service;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.crypto.hash.Md5Hash;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.Constants; 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.domain.entity.SysUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException; import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException; import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException;
import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.MessageUtils;
@ -26,31 +23,40 @@ import com.ruoyi.framework.manager.factory.AsyncFactory;
public class SysPasswordService public class SysPasswordService
{ {
@Autowired @Autowired
private CacheManager cacheManager; private RedisCache redisCache;
private Cache<String, AtomicInteger> loginRecordCache;
@Value(value = "${user.password.maxRetryCount}") @Value(value = "${user.password.maxRetryCount}")
private String 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) public void validate(SysUser user, String password)
{ {
String loginName = user.getLoginName(); String loginName = user.getLoginName();
AtomicInteger retryCount = loginRecordCache.get(loginName); Integer retryCount = redisCache.getCacheObject(getCacheKey(loginName));
if (retryCount == null) if (retryCount == null)
{ {
retryCount = new AtomicInteger(0); retryCount = 0;
loginRecordCache.put(loginName, retryCount); 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))); AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount)));
throw new UserPasswordRetryLimitExceedException(Integer.valueOf(maxRetryCount).intValue()); throw new UserPasswordRetryLimitExceedException(Integer.valueOf(maxRetryCount).intValue());
@ -58,8 +64,9 @@ public class SysPasswordService
if (!matches(user, password)) if (!matches(user, password))
{ {
retryCount = retryCount + 1;
AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.count", retryCount))); 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(); throw new UserPasswordNotMatchException();
} }
else else
@ -75,7 +82,7 @@ public class SysPasswordService
public void clearLoginRecordCache(String loginName) public void clearLoginRecordCache(String loginName)
{ {
loginRecordCache.remove(loginName); redisCache.deleteObject(getCacheKey(loginName));
} }
public String encryptPassword(String loginName, String password, String salt) 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; package com.ruoyi.framework.shiro.web.filter;
import java.io.Serializable;
import java.util.Deque;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; 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.session.SessionException;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.ruoyi.common.constant.Constants; 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.domain.entity.SysUser;
import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.ShiroUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.manager.AsyncManager; import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory; 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 String loginUrl;
private Cache<String, Deque<Serializable>> cache;
public String getLoginUrl() public String getLoginUrl()
{ {
return loginUrl; 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"))); 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(); subject.logout();
@ -74,6 +79,17 @@ public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter
return false; 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 * 退出跳转URL
*/ */
@ -87,4 +103,11 @@ public class LogoutFilter extends org.apache.shiro.web.filter.authc.LogoutFilter
} }
return super.getRedirectUrl(request, response, subject); 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; package com.ruoyi.framework.web.service;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set; 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 org.springframework.stereotype.Service;
import com.ruoyi.common.constant.Constants; import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.CacheUtils; 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 @Service
public class CacheService 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() public String[] getCacheNames()
{ {
String[] cacheNames = CacheUtils.getCacheNames(); String[] cacheNames = { "shiro:session", "shiro:cache:sys-authCache", "shiro:cache:sys-userCache", "sys_dict",
return ArrayUtils.removeElement(cacheNames, Constants.SYS_AUTH_CACHE); "sys_config", "sys_loginRecordCache" };
return cacheNames;
} }
/** /**
@ -33,7 +56,13 @@ public class CacheService
*/ */
public Set<String> getCacheKeys(String cacheName) 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) 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) 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) public void clearCacheKey(String cacheName, String cacheKey)
{ {
CacheUtils.remove(cacheName, cacheKey); redisCache.deleteObject(cacheKey);
} }
/** /**
@ -74,10 +149,7 @@ public class CacheService
*/ */
public void clearAll() public void clearAll()
{ {
String[] cacheNames = getCacheNames(); Collection<String> cacheKeys = redisCache.keys("*");
for (String cacheName : cacheNames) redisCache.deleteObject(cacheKeys);
{
CacheUtils.removeAll(cacheName);
}
} }
} }

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 结果 * @return 结果
*/ */
public String checkConfigKeyUnique(SysConfig config); 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 所有字典类型 * @return 所有字典类型
*/ */
public List<Ztree> selectDictTree(SysDictType dictType); 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; package com.ruoyi.system.service.impl;
import java.util.Collection;
import java.util.List; import java.util.List;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import com.ruoyi.common.exception.BusinessException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants; import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.core.text.Convert; 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.common.utils.StringUtils;
import com.ruoyi.system.domain.SysConfig; import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper; import com.ruoyi.system.mapper.SysConfigMapper;
@ -24,6 +26,9 @@ public class SysConfigServiceImpl implements ISysConfigService
{ {
@Autowired @Autowired
private SysConfigMapper configMapper; private SysConfigMapper configMapper;
@Autowired
private RedisCache redisCache;
/** /**
* 项目启动时初始化参数到缓存 * 项目启动时初始化参数到缓存
@ -31,11 +36,7 @@ public class SysConfigServiceImpl implements ISysConfigService
@PostConstruct @PostConstruct
public void init() public void init()
{ {
List<SysConfig> configsList = configMapper.selectConfigList(new SysConfig()); loadingConfigCache();
for (SysConfig config : configsList)
{
CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue());
}
} }
/** /**
@ -61,7 +62,7 @@ public class SysConfigServiceImpl implements ISysConfigService
@Override @Override
public String selectConfigByKey(String configKey) 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)) if (StringUtils.isNotEmpty(configValue))
{ {
return configValue; return configValue;
@ -71,7 +72,7 @@ public class SysConfigServiceImpl implements ISysConfigService
SysConfig retConfig = configMapper.selectConfig(config); SysConfig retConfig = configMapper.selectConfig(config);
if (StringUtils.isNotNull(retConfig)) if (StringUtils.isNotNull(retConfig))
{ {
CacheUtils.put(getCacheName(), getCacheKey(configKey), retConfig.getConfigValue()); redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());
return retConfig.getConfigValue(); return retConfig.getConfigValue();
} }
return StringUtils.EMPTY; return StringUtils.EMPTY;
@ -101,7 +102,7 @@ public class SysConfigServiceImpl implements ISysConfigService
int row = configMapper.insertConfig(config); int row = configMapper.insertConfig(config);
if (row > 0) if (row > 0)
{ {
CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
} }
return row; return row;
} }
@ -118,7 +119,7 @@ public class SysConfigServiceImpl implements ISysConfigService
int row = configMapper.updateConfig(config); int row = configMapper.updateConfig(config);
if (row > 0) if (row > 0)
{ {
CacheUtils.put(getCacheName(), getCacheKey(config.getConfigKey()), config.getConfigValue()); redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
} }
return row; return row;
} }
@ -127,11 +128,11 @@ public class SysConfigServiceImpl implements ISysConfigService
* 批量删除参数配置对象 * 批量删除参数配置对象
* *
* @param ids 需要删除的数据ID * @param ids 需要删除的数据ID
* @return 结果
*/ */
@Override @Override
public int deleteConfigByIds(String ids) public int deleteConfigByIds(String ids)
{ {
int result = 0;
Long[] configIds = Convert.toLongArray(ids); Long[] configIds = Convert.toLongArray(ids);
for (Long configId : configIds) for (Long configId : configIds)
{ {
@ -140,23 +141,44 @@ public class SysConfigServiceImpl implements ISysConfigService
{ {
throw new BusinessException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); throw new BusinessException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey()));
} }
int count = configMapper.deleteConfigByIds(Convert.toStrArray(ids));
result+=count;
redisCache.deleteObject(getCacheKey(config.getConfigKey()));
} }
int count = configMapper.deleteConfigByIds(Convert.toStrArray(ids)); return result;
if (count > 0) }
{
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 @Override
public void clearCache() 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; return UserConstants.CONFIG_KEY_UNIQUE;
} }
/**
* 获取cache name
*
* @return 缓存名
*/
private String getCacheName()
{
return Constants.SYS_CONFIG_CACHE;
}
/** /**
* 设置cache key * 设置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; package com.ruoyi.system.service.impl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -38,12 +41,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
@PostConstruct @PostConstruct
public void init() public void init()
{ {
List<SysDictType> dictTypeList = dictTypeMapper.selectDictTypeAll(); loadingDictCache();
for (SysDictType dictType : dictTypeList)
{
List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(dictType.getDictType());
DictUtils.setDictCache(dictType.getDictType(), dictDatas);
}
} }
/** /**
@ -120,11 +118,11 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
* 批量删除字典类型 * 批量删除字典类型
* *
* @param ids 需要删除的数据 * @param ids 需要删除的数据
* @return 结果
*/ */
@Override @Override
public int deleteDictTypeByIds(String ids) public int deleteDictTypeByIds(String ids)
{ {
int result = 0;
Long[] dictIds = Convert.toLongArray(ids); Long[] dictIds = Convert.toLongArray(ids);
for (Long dictId : dictIds) for (Long dictId : dictIds)
{ {
@ -133,17 +131,30 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
{ {
throw new BusinessException(String.format("%1$s已分配,不能删除", dictType.getDictName())); 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); return result;
if (count > 0) }
/**
* 加载字典缓存数据
*/
@Override
public void loadingDictCache()
{
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.clearDictCache(); DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList()));
} }
return count;
} }
/** /**
* 清空缓存数据 * 清空字典缓存数据
*/ */
@Override @Override
public void clearCache() public void clearCache()
@ -151,19 +162,29 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
DictUtils.clearDictCache(); DictUtils.clearDictCache();
} }
/**
* 重置字典缓存数据
*/
@Override
public void resetDictCache()
{
clearCache();
loadingDictCache();
}
/** /**
* 新增保存字典类型信息 * 新增保存字典类型信息
* *
* @param dictType 字典类型信息 * @param dict 字典类型信息
* @return 结果 * @return 结果
*/ */
@Override @Override
public int insertDictType(SysDictType dictType) public int insertDictType(SysDictType dict)
{ {
int row = dictTypeMapper.insertDictType(dictType); int row = dictTypeMapper.insertDictType(dict);
if (row > 0) if (row > 0)
{ {
DictUtils.clearDictCache(); DictUtils.setDictCache(dict.getDictType(), null);
} }
return row; return row;
} }
@ -171,19 +192,20 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService
/** /**
* 修改保存字典类型信息 * 修改保存字典类型信息
* *
* @param dictType 字典类型信息 * @param dict 字典类型信息
* @return 结果 * @return 结果
*/ */
@Override @Override
@Transactional @Transactional
public int updateDictType(SysDictType dictType) public int updateDictType(SysDictType dict)
{ {
SysDictType oldDict = dictTypeMapper.selectDictTypeById(dictType.getDictId()); SysDictType oldDict = dictTypeMapper.selectDictTypeById(dict.getDictId());
dictDataMapper.updateDictDataType(oldDict.getDictType(), dictType.getDictType()); dictDataMapper.updateDictDataType(oldDict.getDictType(), dict.getDictType());
int row = dictTypeMapper.updateDictType(dictType); int row = dictTypeMapper.updateDictType(dict);
if (row > 0) if (row > 0)
{ {
DictUtils.clearDictCache(); List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(dict.getDictType());
DictUtils.setDictCache(dict.getDictType(), dictDatas);
} }
return row; 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