优化缓存系统

v1.4.1
Parker 4 years ago
parent e98b06e7fe
commit 60fa1c3bfc

@ -24,6 +24,8 @@ package org.opsli.common.constants;
*/
public interface CacheConstants {
String PREFIX_NAME = "opsli";
/** 热点数据 */
String HOT_DATA = "hotData";

@ -15,6 +15,7 @@
*/
package org.opsli.core.aspect;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
@ -27,6 +28,7 @@ import org.opsli.common.annotation.hotdata.EnableHotData;
import org.opsli.common.annotation.hotdata.HotDataDel;
import org.opsli.common.annotation.hotdata.HotDataPut;
import org.opsli.common.constants.CacheConstants;
import org.opsli.common.utils.Props;
import org.opsli.core.cache.local.CacheUtil;
import org.opsli.core.cache.pushsub.entity.CacheDataEntity;
import org.opsli.core.cache.pushsub.enums.CacheType;
@ -57,7 +59,13 @@ import static org.opsli.common.constants.OrderConstants.HOT_DATA_ORDER;
public class CacheDataAop {
/** 热点数据前缀 */
public static final String PREFIX_NAME = "opsli:";
public static final String PREFIX_NAME;
static{
// 缓存前缀
Props props = new Props("application.yaml");
PREFIX_NAME = props.getStr("spring.cache-conf.prefix",CacheConstants.PREFIX_NAME) + ":";
}
@Autowired
private RedisPlugin redisPlugin;
@ -80,33 +88,35 @@ public class CacheDataAop {
public Object hotDataPutProcess(ProceedingJoinPoint point) throws Throwable {
Object[] args= point.getArgs();
Object returnValue = point.proceed(args);
// 判断 方法上是否使用 HotData注解 如果没有表示开启热数据 则直接跳过
// 判断 方法上是否使用 EnableHotData注解 如果没有表示开启热数据 则直接跳过
Annotation annotation = point.getTarget().getClass().getAnnotation(EnableHotData.class);
if(annotation == null){
return returnValue;
}
// ====== 如果 使用了 EnableHotData ,表示开启热数据加载 则执行下段代码
CacheDataEntity cacheDataEntity = this.putHandlerData(point, returnValue);
if(cacheDataEntity == null){
List<CacheDataEntity> cacheDataEntityList = this.putHandlerData(point, returnValue);
// 非法判断
if(CollUtil.isEmpty(cacheDataEntityList)){
return returnValue;
}
// 更新缓存数据
// 热点数据
if(CacheConstants.HOT_DATA.equals(cacheDataEntity.getCacheName())){
CacheUtil.putByKeyOriginal(cacheDataEntity.getKey(), returnValue);
}
// 永久数据
else if(CacheConstants.EDEN_DATA.equals(cacheDataEntity.getCacheName())) {
CacheUtil.putEdenByKeyOriginal(cacheDataEntity.getKey(), returnValue);
}
for (CacheDataEntity cacheDataEntity : cacheDataEntityList) {
// 更新缓存数据
// 热点数据
if(CacheConstants.HOT_DATA.equals(cacheDataEntity.getCacheName())){
CacheUtil.putByKeyOriginal(cacheDataEntity.getKey(), returnValue);
}
// 永久数据
else if(CacheConstants.EDEN_DATA.equals(cacheDataEntity.getCacheName())) {
CacheUtil.putEdenByKeyOriginal(cacheDataEntity.getKey(), returnValue);
}
// 广播缓存数据 - 通知其他服务器同步数据
redisPlugin.sendMessage(
CacheDataMsgFactory.createMsg(cacheDataEntity.getType(),
cacheDataEntity.getKey(), returnValue, CacheType.UPDATE)
);
// 广播缓存数据 - 通知其他服务器同步数据
redisPlugin.sendMessage(
CacheDataMsgFactory.createMsg(cacheDataEntity.getType(),
cacheDataEntity.getKey(), returnValue, CacheType.UPDATE)
);
}
return returnValue;
}
@ -122,7 +132,7 @@ public class CacheDataAop {
public Object hotDataDelProcess(ProceedingJoinPoint point) throws Throwable {
Object[] args= point.getArgs();
Object returnValue = point.proceed(args);
// 判断 方法上是否使用 HotData注解 如果没有表示开启热数据 则直接跳过
// 判断 方法上是否使用 EnableHotData注解 如果没有表示开启热数据 则直接跳过
Annotation annotation = point.getTarget().getClass().getAnnotation(EnableHotData.class);
if(annotation == null){
return returnValue;
@ -139,9 +149,9 @@ public class CacheDataAop {
return returnValue;
}
// ====== 如果 使用了 EnableHotData ,表示开启热数据加载 则执行下段代码
List<CacheDataEntity> cacheDataEntityList = this.delHandlerData(point, args);
if(cacheDataEntityList == null || cacheDataEntityList.size() == 0){
// 非法判断
if(CollUtil.isEmpty(cacheDataEntityList)){
return returnValue;
}
@ -167,59 +177,44 @@ public class CacheDataAop {
* PUT
* @param point
*/
private CacheDataEntity putHandlerData(ProceedingJoinPoint point, Object returnValue){
CacheDataEntity ret;
// 返回值为空直接
if(returnValue == null){
return null;
}
private List<CacheDataEntity> putHandlerData(ProceedingJoinPoint point, Object returnValue){
// 这里 只对 继承了 ApiWrapper 的类做处理
if(!(returnValue instanceof ApiWrapper)){
return null;
}
// 消息集合 后续可能会考虑 多消息存储
List<CacheDataEntity> cacheDataEntities = Lists.newArrayListWithCapacity(1);
// 报错不处理
try {
String methodName= point.getSignature().getName();
Class<?> classTarget= point.getTarget().getClass();
Class<?>[] par=((MethodSignature) point.getSignature()).getParameterTypes();
Method objMethod = classTarget.getMethod(methodName, par);
// 获得方法
Method objMethod = this.getMethod(point);
if(objMethod == null) return null;
// 获取注解参数
HotDataPut aCache= objMethod.getAnnotation(HotDataPut.class);
if(aCache != null){
// 类型
PushSubType type;
// key 前缀
StringBuilder keyBuf = new StringBuilder(PREFIX_NAME);
// 热点数据
if(CacheConstants.HOT_DATA.equals(aCache.name())){
keyBuf.append(CacheConstants.HOT_DATA).append(":");
type = PushSubType.HOT_DATA;
}
// 系统数据
else if(CacheConstants.EDEN_DATA.equals(aCache.name())){
keyBuf.append(CacheConstants.EDEN_DATA).append(":");
type = PushSubType.EDEN_DATA;
} else {
// 获得缓存类型
PushSubType type = this.judgeCacheType(aCache.name());
if(type == null) {
// 如果都不是 则直接退出 不走缓存
return null;
}
// key 前缀
StringBuilder keyBuf = this.judgeCacheKeyBuf(aCache.name());
try {
// 这里 只对 继承了 BaseEntity 的类做处理
ApiWrapper apiWrapper = (ApiWrapper) returnValue;
// 这里 只对 继承了 BaseEntity 的类做处理
ApiWrapper apiWrapper = (ApiWrapper) returnValue;
// key 存储ID
String key = keyBuf.append(apiWrapper.getId()).toString();
// key 存储ID
String key = keyBuf.append(apiWrapper.getId()).toString();
ret = new CacheDataEntity();
ret.setKey(key);
ret.setType(type);
ret.setCacheName(aCache.name());
CacheDataEntity ret = new CacheDataEntity(key, type ,aCache.name());
// 存放数据
this.putCacheData(cacheDataEntities, ret);
return ret;
}catch (Exception e){
log.error(e.getMessage(),e);
}
return cacheDataEntities;
}
}catch (Exception e){
log.error(e.getMessage(),e);
@ -237,89 +232,67 @@ public class CacheDataAop {
return null;
}
// DEL 消息集合
// 消息集合
List<CacheDataEntity> cacheDataEntities = Lists.newArrayListWithCapacity(args.length);
// 报错不处理
try {
String methodName= point.getSignature().getName();
Class<?> classTarget= point.getTarget().getClass();
Class<?>[] par=((MethodSignature) point.getSignature()).getParameterTypes();
Method objMethod = classTarget.getMethod(methodName, par);
// 获得方法
Method objMethod = this.getMethod(point);
if(objMethod == null) return null;
// 获取注解参数
HotDataDel aCache= objMethod.getAnnotation(HotDataDel.class);
if(aCache != null){
// 类型
PushSubType type;
// key 前缀
StringBuilder keyBuf = new StringBuilder(PREFIX_NAME);
// 热点数据
if(CacheConstants.HOT_DATA.equals(aCache.name())){
keyBuf.append(CacheConstants.HOT_DATA).append(":");
type = PushSubType.HOT_DATA;
}
// 系统数据
else if(CacheConstants.EDEN_DATA.equals(aCache.name())){
keyBuf.append(CacheConstants.EDEN_DATA).append(":");
type = PushSubType.EDEN_DATA;
} else {
// 获得缓存类型
PushSubType type = this.judgeCacheType(aCache.name());
if(type == null) {
// 如果都不是 则直接退出 不走缓存
return null;
}
try {
// 处理数据
for (Object arg : args) {
if (arg instanceof String) {
// key 前缀
StringBuilder keyBuf = this.judgeCacheKeyBuf(aCache.name());
// 处理数据
for (Object arg : args) {
if (arg instanceof String) {
// key 存储ID
String key = keyBuf.toString() + arg;
CacheDataEntity ret = new CacheDataEntity(key, type ,aCache.name());
// 存放数据
this.putCacheData(cacheDataEntities, ret);
} else if (arg instanceof String[]) {
String[] ids = (String[]) arg;
for (String id : ids) {
// key 存储ID
String key = keyBuf.toString() + arg;
CacheDataEntity ret = new CacheDataEntity();
ret.setKey(key);
ret.setType(type);
ret.setCacheName(aCache.name());
cacheDataEntities.add(ret);
} else if (arg instanceof String[]) {
String[] ids = (String[]) arg;
for (String id : ids) {
String key = keyBuf.toString() + id;
CacheDataEntity ret = new CacheDataEntity(key, type ,aCache.name());
// 存放数据
this.putCacheData(cacheDataEntities, ret);
}
} else if (arg instanceof ApiWrapper) {
// key 存储ID
ApiWrapper apiWrapper = (ApiWrapper) arg;
String key = keyBuf.toString() + apiWrapper.getId();
CacheDataEntity ret = new CacheDataEntity(key, type ,aCache.name());
// 存放数据
this.putCacheData(cacheDataEntities, ret);
} else if (arg instanceof Collection) {
try {
Collection<ApiWrapper> baseEntityList = (Collection<ApiWrapper>) arg;
for (ApiWrapper baseEntity : baseEntityList) {
// key 存储ID
String key = keyBuf.toString() + id;
CacheDataEntity ret = new CacheDataEntity();
ret.setKey(key);
ret.setType(type);
ret.setCacheName(aCache.name());
cacheDataEntities.add(ret);
}
} else if (arg instanceof ApiWrapper) {
// key 存储ID
ApiWrapper apiWrapper = (ApiWrapper) arg;
String key = keyBuf.toString() + apiWrapper.getId();
CacheDataEntity ret = new CacheDataEntity();
ret.setKey(key);
ret.setType(type);
ret.setCacheName(aCache.name());
cacheDataEntities.add(ret);
} else if (arg instanceof Collection) {
try {
Collection<ApiWrapper> baseEntityList = (Collection<ApiWrapper>) arg;
for (ApiWrapper baseEntity : baseEntityList) {
// key 存储ID
String key = keyBuf.toString() + baseEntity.getId();
CacheDataEntity ret = new CacheDataEntity();
ret.setKey(key);
ret.setType(type);
ret.setCacheName(aCache.name());
cacheDataEntities.add(ret);
}
}catch (Exception e){
log.error(e.getMessage(),e);
String key = keyBuf.toString() + baseEntity.getId();
CacheDataEntity ret = new CacheDataEntity(key, type ,aCache.name());
// 存放数据
this.putCacheData(cacheDataEntities, ret);
}
}catch (Exception e){
log.error(e.getMessage(),e);
}
}
return cacheDataEntities;
}catch (Exception e){
log.error(e.getMessage(),e);
}
return cacheDataEntities;
}
}catch (Exception e){
log.error(e.getMessage(),e);
@ -327,4 +300,73 @@ public class CacheDataAop {
return null;
}
// =====================
/**
*
* @param point
* @return
*/
private Method getMethod(ProceedingJoinPoint point){
Method m = null;
try {
String methodName= point.getSignature().getName();
Class<?> classTarget= point.getTarget().getClass();
Class<?>[] par=((MethodSignature) point.getSignature()).getParameterTypes();
m = classTarget.getMethod(methodName, par);
}catch (Exception ignored){}
return m;
}
/**
*
* @param cacheDataList
* @param cacheData
*/
private void putCacheData(List<CacheDataEntity> cacheDataList, CacheDataEntity cacheData){
// 非法判断
if(CollUtil.isEmpty(cacheDataList)){
return;
}
cacheDataList.add(cacheData);
}
/**
* Key
* @param cacheName
* @return
*/
private StringBuilder judgeCacheKeyBuf(String cacheName){
// key 前缀
StringBuilder keyBuf = new StringBuilder(PREFIX_NAME);
// 热点数据
if(CacheConstants.HOT_DATA.equals(cacheName)){
keyBuf.append(CacheConstants.HOT_DATA).append(":");
}
// 系统数据
else if(CacheConstants.EDEN_DATA.equals(cacheName)){
keyBuf.append(CacheConstants.EDEN_DATA).append(":");
}
return keyBuf;
}
/**
*
* @param cacheName
* @return
*/
private PushSubType judgeCacheType(String cacheName){
PushSubType type = null;
// 热点数据
if(CacheConstants.HOT_DATA.equals(cacheName)){
type = PushSubType.HOT_DATA;
}
// 系统数据
else if(CacheConstants.EDEN_DATA.equals(cacheName)){
type = PushSubType.EDEN_DATA;
}
return type;
}
}

@ -13,29 +13,27 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.opsli.modulars.system.area.entity;
package org.opsli.core.base.entity;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.opsli.core.base.entity.BaseEntity;
/**
* @BelongsProject: opsli-boot
* @BelongsPackage: org.opsli.modulars.system.area.entity
* @Author: Parker
* @CreateTime: 2020-11-28 18:59:59
* @Description:
* @Description: Tree
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class HasChildren{
public class HasChildren {
/** 父级主键 */
private String parentId;
/** 地域名称 */
/** 下级数量 */
private Integer count;

@ -25,6 +25,7 @@ import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.opsli.common.constants.CacheConstants;
import org.opsli.common.utils.Props;
import org.opsli.core.aspect.CacheDataAop;
import org.opsli.plugins.cache.EhCachePlugin;
import org.opsli.plugins.redis.RedisPlugin;
@ -65,18 +66,27 @@ import static org.opsli.common.constants.OrderConstants.UTIL_ORDER;
@AutoConfigureAfter({RedisPlugin.class , EhCachePlugin.class})
public class CacheUtil {
public static final String JSON_KEY = "data";
/** 空状态 key 前缀 */
private static final String NIL_FLAG_PREFIX = "opsli:nil:";
/** 热点数据缓存时间 秒 */
private static int ttlHotData = 60000;
/** Redis插件 */
private static RedisPlugin redisPlugin;
/** EhCache插件 */
private static EhCachePlugin ehCachePlugin;
/** Json key */
public static final String JSON_KEY = "data";
/** 空状态 key 前缀 */
private static final String NIL_FLAG_PREFIX;
/** 热点数据前缀 */
public static final String PREFIX_NAME;
static {
// 缓存前缀
Props props = new Props("application.yaml");
PREFIX_NAME = props.getStr("spring.cache-conf.prefix",CacheConstants.PREFIX_NAME) + ":";
NIL_FLAG_PREFIX = PREFIX_NAME + "nil:";
try {
// 读取配置信息
CacheUtil.readPropertyXML();

@ -15,7 +15,10 @@
*/
package org.opsli.core.cache.pushsub.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.opsli.core.cache.pushsub.enums.PushSubType;
/**
@ -26,6 +29,8 @@ import org.opsli.core.cache.pushsub.enums.PushSubType;
* @Description: Entity
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CacheDataEntity {
/** key */
@ -37,4 +42,9 @@ public class CacheDataEntity {
/** 缓存名称 */
private String cacheName;
public static void main(String[] args) {
CacheDataEntity ret = new CacheDataEntity("123", PushSubType.EDEN_DATA, "12aaaa");
System.out.println(ToStringBuilder.reflectionToString(ret));
}
}

@ -47,6 +47,11 @@ public enum MsgArgsType {
/** 菜单数据*/
MENU_MODEL_DATA,
/** 组织 用户ID */
ORG_USER_ID,
/** 组织 用户数据 */
ORG_USER_DATA,
/** 缓存数据Key */
CACHE_DATA_KEY,
/** 缓存数据Value */

@ -33,6 +33,9 @@ public enum PushSubType {
/** 菜单数据 */
MENU,
/** 组织数据 */
ORG,
/** 热点数据 */
HOT_DATA,

@ -31,7 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired;
* @BelongsPackage: org.opsli.core.cache.pushsub.handler
* @Author: Parker
* @CreateTime: 2020-09-15 16:24
* @Description:
* @Description:
*/
@Slf4j
public class MenuHandler implements RedisPushSubHandler{
@ -46,7 +46,7 @@ public class MenuHandler implements RedisPushSubHandler{
@Override
public void handler(JSONObject msgJson) {
// 用户刷新
// 菜单刷新
this.menuHandler(msgJson);
}
@ -59,7 +59,7 @@ public class MenuHandler implements RedisPushSubHandler{
// 数据为空则不执行
if(data == null) return;
// 获得用户ID 和 用户名
// 获得菜单编号
String menuCode = (String) msgJson.get(MsgArgsType.MENU_CODE.toString());
if(StringUtils.isEmpty(menuCode)){
return;

@ -0,0 +1,76 @@
/**
* Copyright 2020 OPSLI https://www.opsli.com
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.opsli.core.cache.pushsub.handler;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.opsli.common.constants.CacheConstants;
import org.opsli.core.cache.local.CacheUtil;
import org.opsli.core.cache.pushsub.enums.MsgArgsType;
import org.opsli.core.cache.pushsub.enums.PushSubType;
import org.opsli.core.utils.MenuUtil;
import org.opsli.core.utils.OrgUtil;
import org.opsli.plugins.cache.EhCachePlugin;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @BelongsProject: opsli-boot
* @BelongsPackage: org.opsli.core.cache.pushsub.handler
* @Author: Parker
* @CreateTime: 2020-09-15 16:24
* @Description:
*/
@Slf4j
public class OrgHandler implements RedisPushSubHandler{
@Autowired
EhCachePlugin ehCachePlugin;
@Override
public PushSubType getType() {
return PushSubType.ORG;
}
@Override
public void handler(JSONObject msgJson) {
// 用户刷新
this.orgHandler(msgJson);
}
/**
*
* @param msgJson
*/
private void orgHandler(JSONObject msgJson){
JSONObject data = msgJson.getJSONObject(MsgArgsType.ORG_USER_DATA.toString());
// 数据为空则不执行
if(data == null) return;
// 获得用户ID
String userId = (String) msgJson.get(MsgArgsType.ORG_USER_ID.toString());
if(StringUtils.isEmpty(userId)){
return;
}
// 先删除
ehCachePlugin.delete(CacheConstants.HOT_DATA, OrgUtil.PREFIX_CODE + userId);
// 清除空拦截
CacheUtil.delNilFlag(OrgUtil.PREFIX_CODE + userId);
}
}

@ -0,0 +1,61 @@
/**
* Copyright 2020 OPSLI https://www.opsli.com
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.opsli.core.cache.pushsub.msgs;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.experimental.Accessors;
import org.opsli.api.wrapper.system.menu.MenuModel;
import org.opsli.api.wrapper.system.user.UserOrgRefModel;
import org.opsli.core.cache.pushsub.enums.MsgArgsType;
import org.opsli.core.cache.pushsub.enums.PushSubType;
import org.opsli.core.cache.pushsub.receiver.RedisPushSubReceiver;
import org.opsli.plugins.redis.pushsub.entity.BaseSubMessage;
/**
* @BelongsProject: opsli-boot
* @BelongsPackage: org.opsli.core.cache.pushsub.msgs
* @Author: Parker
* @CreateTime: 2020-09-15 16:50
* @Description:
*/
@Data
@Accessors(chain = true)
public final class OrgMsgFactory extends BaseSubMessage{
/** 通道 */
private static final String CHANNEL = RedisPushSubReceiver.BASE_CHANNEL+RedisPushSubReceiver.CHANNEL;
private OrgMsgFactory(){}
/**
* -
*/
public static BaseSubMessage createOrgMsg(UserOrgRefModel orgRefModel){
BaseSubMessage baseSubMessage = new BaseSubMessage();
// 数据
JSONObject jsonObj = new JSONObject();
jsonObj.put(MsgArgsType.ORG_USER_ID.toString(), orgRefModel.getUserId());
jsonObj.put(MsgArgsType.ORG_USER_DATA.toString(), orgRefModel);
// 用户
baseSubMessage.build(CHANNEL,PushSubType.ORG.toString(),jsonObj);
return baseSubMessage;
}
}

@ -18,7 +18,9 @@ package org.opsli.core.utils;
import com.google.code.kaptcha.Producer;
import org.apache.commons.lang3.StringUtils;
import org.opsli.api.web.system.dict.DictDetailApi;
import org.opsli.common.constants.CacheConstants;
import org.opsli.common.exception.TokenException;
import org.opsli.common.utils.Props;
import org.opsli.core.msg.TokenMsg;
import org.opsli.plugins.redis.RedisLockPlugins;
import org.opsli.plugins.redis.RedisPlugin;
@ -45,13 +47,20 @@ import static org.opsli.common.constants.OrderConstants.UTIL_ORDER;
public class CaptchaUtil{
/** 缓存前缀 */
private static final String PREFIX = "opsli:temp:captcha:";
private static final String PREFIX = "temp:captcha:";
/** 默认验证码保存 5 分钟 */
private static final int TIME_OUT = 300;
/** Redis插件 */
private static RedisPlugin redisPlugin;
/** 谷歌验证码 */
private static Producer producer;
/** 热点数据前缀 */
public static final String PREFIX_NAME;
static{
// 缓存前缀
Props props = new Props("application.yaml");
PREFIX_NAME = props.getStr("spring.cache-conf.prefix", CacheConstants.PREFIX_NAME) + ":";
}
/**
*
@ -66,7 +75,7 @@ public class CaptchaUtil{
//生成文字验证码
String code = producer.createText();
boolean ret = redisPlugin.put(PREFIX + uuid, code, TIME_OUT);
boolean ret = redisPlugin.put(PREFIX_NAME + PREFIX + uuid, code, TIME_OUT);
if(ret){
return producer.createImage(code);
@ -84,14 +93,14 @@ public class CaptchaUtil{
if(StringUtils.isEmpty(uuid)) return false;
// 验证码
String codeTemp = (String) redisPlugin.get(PREFIX + uuid);
String codeTemp = (String) redisPlugin.get(PREFIX_NAME + PREFIX + uuid);
if(StringUtils.isEmpty(codeTemp)){
throw new TokenException(TokenMsg.EXCEPTION_CAPTCHA_NULL);
}
// 删除验证码
//redisPlugin.del(PREFIX + uuid);
//redisPlugin.del(PREFIX_NAME + PREFIX + uuid);
return codeTemp.equalsIgnoreCase(code);
}
@ -106,7 +115,7 @@ public class CaptchaUtil{
if(StringUtils.isEmpty(uuid)) return false;
//删除验证码
return redisPlugin.del(PREFIX + uuid);
return redisPlugin.del(PREFIX_NAME + PREFIX + uuid);
}

@ -23,6 +23,7 @@ import org.opsli.api.web.system.user.UserApi;
import org.opsli.api.wrapper.system.menu.MenuModel;
import org.opsli.core.cache.local.CacheUtil;
import org.opsli.core.cache.pushsub.msgs.MenuMsgFactory;
import org.opsli.core.cache.pushsub.msgs.OrgMsgFactory;
import org.opsli.plugins.redis.RedisLockPlugins;
import org.opsli.plugins.redis.RedisPlugin;
import org.opsli.plugins.redis.lock.RedisLock;
@ -58,7 +59,7 @@ public class MenuUtil {
/** Redis分布式锁 */
private static RedisLockPlugins redisLockPlugins;
/** 用户Service */
/** 菜单 Api */
private static MenuApi menuApi;
@ -139,13 +140,17 @@ public class MenuUtil {
}
MenuModel menuModel = CacheUtil.get(PREFIX_CODE + menu.getMenuCode(), MenuModel.class);
boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_CODE + menu.getMenuCode());
// 只要不为空 则执行刷新
if (menuModel != null){
// 先删除
CacheUtil.del(PREFIX_CODE + menu.getMenuCode());
if (hasNilFlag){
// 清除空拦截
CacheUtil.delNilFlag(PREFIX_CODE + menu.getMenuCode());
}
if(menuModel != null){
// 先删除
CacheUtil.del(PREFIX_CODE + menu.getMenuCode());
// 发送通知消息
redisPlugin.sendMessage(

@ -0,0 +1,183 @@
/**
* Copyright 2020 OPSLI https://www.opsli.com
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.opsli.core.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.opsli.api.base.result.ResultVo;
import org.opsli.api.web.system.menu.MenuApi;
import org.opsli.api.web.system.user.UserApi;
import org.opsli.api.wrapper.system.menu.MenuModel;
import org.opsli.api.wrapper.system.user.UserOrgRefModel;
import org.opsli.core.cache.local.CacheUtil;
import org.opsli.core.cache.pushsub.msgs.MenuMsgFactory;
import org.opsli.core.cache.pushsub.msgs.OrgMsgFactory;
import org.opsli.plugins.redis.RedisLockPlugins;
import org.opsli.plugins.redis.RedisPlugin;
import org.opsli.plugins.redis.lock.RedisLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import static org.opsli.common.constants.OrderConstants.UTIL_ORDER;
/**
* @BelongsProject: opsli-boot
* @BelongsPackage: org.opsli.core.utils
* @Author: Parker
* @CreateTime: 2020-09-19 20:03
* @Description:
*/
@Slf4j
@Order(UTIL_ORDER)
@Component
@AutoConfigureAfter({RedisPlugin.class , RedisLockPlugins.class, UserApi.class})
@Lazy(false)
public class OrgUtil {
/** 前缀 */
public static final String PREFIX_CODE = "org:userId:";
/** Redis插件 */
private static RedisPlugin redisPlugin;
/** Redis分布式锁 */
private static RedisLockPlugins redisLockPlugins;
/** 用户 Api */
private static UserApi userApi;
/**
* userId
* @param userId
* @return
*/
public static UserOrgRefModel getOrgByUserId(String userId){
// 先从缓存里拿
UserOrgRefModel orgRefModel = CacheUtil.get(PREFIX_CODE + userId, UserOrgRefModel.class);
if (orgRefModel != null){
return orgRefModel;
}
// 拿不到 --------
// 防止缓存穿透判断
boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_CODE + userId);
if(hasNilFlag){
return null;
}
// 锁凭证 redisLock 贯穿全程
RedisLock redisLock = new RedisLock();
redisLock.setLockName(PREFIX_CODE + userId)
.setAcquireTimeOut(3000L)
.setLockTimeOut(5000L);
try {
// 这里增加分布式锁 防止缓存击穿
// ============ 尝试加锁
redisLock = redisLockPlugins.tryLock(redisLock);
if(redisLock == null){
return null;
}
// 如果获得锁 则 再次检查缓存里有没有, 如果有则直接退出, 没有的话才发起数据库请求
orgRefModel = CacheUtil.get(PREFIX_CODE + userId, UserOrgRefModel.class);
if (orgRefModel != null){
return orgRefModel;
}
// 查询数据库
ResultVo<UserOrgRefModel> resultVo = userApi.getOrgInfoByUserId(userId);
if(resultVo.isSuccess()){
orgRefModel = resultVo.getData();
// 存入缓存
CacheUtil.put(PREFIX_CODE + userId, orgRefModel);
}
}catch (Exception e){
log.error(e.getMessage(),e);
}finally {
// ============ 释放锁
redisLockPlugins.unLock(redisLock);
redisLock = null;
}
if(orgRefModel == null){
// 设置空变量 用于防止穿透判断
CacheUtil.putNilFlag(PREFIX_CODE + userId);
return null;
}
return orgRefModel;
}
// ============== 刷新缓存 ==============
/**
* -
* @param userId
* @return
*/
public static void refreshMenu(String userId){
if(StringUtils.isEmpty(userId)){
return;
}
UserOrgRefModel orgRefModel = CacheUtil.get(PREFIX_CODE + userId, UserOrgRefModel.class);
boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_CODE + userId);
// 只要不为空 则执行刷新
if (hasNilFlag){
// 清除空拦截
CacheUtil.delNilFlag(PREFIX_CODE + userId);
}
if(orgRefModel != null){
// 先删除
CacheUtil.del(PREFIX_CODE + userId);
// 发送通知消息
redisPlugin.sendMessage(
OrgMsgFactory.createOrgMsg(orgRefModel)
);
}
}
// =====================================
@Autowired
public void setRedisPlugin(RedisPlugin redisPlugin) {
OrgUtil.redisPlugin = redisPlugin;
}
@Autowired
public void setRedisLockPlugins(RedisLockPlugins redisLockPlugins) {
OrgUtil.redisLockPlugins = redisLockPlugins;
}
@Autowired
public void setUserApi(UserApi userApi) {
OrgUtil.userApi = userApi;
}
}

@ -26,6 +26,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.opsli.api.base.result.ResultVo;
import org.opsli.api.wrapper.system.user.UserModel;
import org.opsli.common.constants.CacheConstants;
import org.opsli.common.constants.SignConstants;
import org.opsli.common.constants.TokenConstants;
import org.opsli.common.exception.ServiceException;
@ -62,11 +63,11 @@ public class UserTokenUtil {
/** token 缓存名 */
public static final String TOKEN_NAME = TokenConstants.ACCESS_TOKEN;
/** 缓存前缀 */
private static final String PREFIX = "opsli:ticket:";
private static final String TICKET_PREFIX;
/** 账号失败次数 */
public static final String ACCOUNT_SLIP_COUNT_PREFIX = "opsli:account:slip:count:";
public static final String ACCOUNT_SLIP_COUNT_PREFIX;
/** 账号失败锁定KEY */
public static final String ACCOUNT_SLIP_LOCK_PREFIX = "opsli:account:slip:lock:";
public static final String ACCOUNT_SLIP_LOCK_PREFIX;
/** 账号失败阈值 */
public static final int ACCOUNT_SLIP_COUNT;
/** 账号失败N次后弹出验证码 */
@ -77,15 +78,24 @@ public class UserTokenUtil {
/** Redis插件 */
private static RedisPlugin redisPlugin;
/** 热点数据前缀 */
public static final String PREFIX_NAME;
static {
static{
// 缓存前缀
Props props = new Props("application.yaml");
PREFIX_NAME = props.getStr("spring.cache-conf.prefix", CacheConstants.PREFIX_NAME) + ":";
TICKET_PREFIX = PREFIX_NAME + "ticket:";
ACCOUNT_SLIP_COUNT_PREFIX = PREFIX_NAME + "account:slip:count:";
ACCOUNT_SLIP_LOCK_PREFIX = PREFIX_NAME + "account:slip:lock:";
// 配置数据
ACCOUNT_SLIP_COUNT = props.getInt("opsli.login.slip-count", 5);
ACCOUNT_SLIP_VERIFY_COUNT = props.getInt("opsli.login.slip-verify-count", 3);
ACCOUNT_SLIP_LOCK_SPEED = props.getInt("opsli.login.slip-lock-speed", 300);
}
/**
* user Token
* @param user
@ -121,7 +131,7 @@ public class UserTokenUtil {
// token 缓存真实失效时间 建议大于 最终时间 -- 多加了20分钟的失效时间
// 在redis存一份 token 是为了防止 认为造假
boolean tokenFlag = redisPlugin.put(PREFIX + signTokenHex, endTimestamp, expire + 20);
boolean tokenFlag = redisPlugin.put(TICKET_PREFIX + signTokenHex, endTimestamp, expire + 20);
if(tokenFlag){
map.put("token", signToken);
map.put("expire", endTimestamp);
@ -177,7 +187,7 @@ public class UserTokenUtil {
// 生成MD5 16进制码 用于缩减存储
String signTokenHex = new Md5Hash(token).toHex();
redisPlugin.del(PREFIX + signTokenHex);
redisPlugin.del(TICKET_PREFIX + signTokenHex);
// 删除相关信息
String userId = getUserIdByToken(token);
@ -209,7 +219,7 @@ public class UserTokenUtil {
// 2. 校验当前缓存中token是否失效
// 生成MD5 16进制码 用于缩减存储
String signTokenHex = new Md5Hash(token).toHex();
Long endTimestamp = (Long) redisPlugin.get(PREFIX + signTokenHex);
Long endTimestamp = (Long) redisPlugin.get(TICKET_PREFIX + signTokenHex);
if(endTimestamp == null){
return false;
}

@ -29,6 +29,7 @@ import org.opsli.common.api.TokenThreadLocal;
import org.opsli.common.exception.TokenException;
import org.opsli.common.utils.Props;
import org.opsli.core.cache.local.CacheUtil;
import org.opsli.core.cache.pushsub.msgs.MenuMsgFactory;
import org.opsli.core.cache.pushsub.msgs.UserMsgFactory;
import org.opsli.core.msg.TokenMsg;
import org.opsli.plugins.redis.RedisLockPlugins;
@ -537,6 +538,16 @@ public class UserUtil {
UserModel userModelByUsername = CacheUtil.get(PREFIX_USERNAME + user.getUsername(),
UserModel.class);
boolean hasNilFlagById = CacheUtil.hasNilFlag(PREFIX_ID + user.getId());
boolean hasNilFlagByName = CacheUtil.hasNilFlag(PREFIX_USERNAME + user.getUsername());
// 只要有一个不为空 则执行刷新
if (hasNilFlagById || hasNilFlagByName){
// 清除空拦截
CacheUtil.delNilFlag(PREFIX_ID + user.getId());
CacheUtil.delNilFlag(PREFIX_USERNAME + user.getUsername());
}
// 只要有一个不为空 则执行刷新
if (userModelById != null || userModelByUsername != null){
// 先删除
@ -562,17 +573,24 @@ public class UserUtil {
public static void refreshUserRoles(String userId){
try {
Object obj = CacheUtil.get(PREFIX_ID_ROLES + userId);
boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_ID_ROLES + userId);
// 只要不为空 则执行刷新
if (hasNilFlag){
// 清除空拦截
CacheUtil.delNilFlag(PREFIX_ID_ROLES + userId);
}
if(obj != null){
// 先删除
CacheUtil.del(PREFIX_ID_ROLES + userId);
// 清除空拦截
CacheUtil.delNilFlag(PREFIX_ID_ROLES + userId);
// 发送通知消息
redisPlugin.sendMessage(
UserMsgFactory.createUserRolesMsg(userId, null)
);
}
}catch (Exception e){
log.error(e.getMessage(), e);
}
@ -586,11 +604,17 @@ public class UserUtil {
public static void refreshUserAllPerms(String userId){
try {
Object obj = CacheUtil.get(PREFIX_ID_PERMISSIONS + userId);
boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_ID_PERMISSIONS + userId);
// 只要不为空 则执行刷新
if (hasNilFlag){
// 清除空拦截
CacheUtil.delNilFlag(PREFIX_ID_PERMISSIONS + userId);
}
if(obj != null){
// 先删除
CacheUtil.del(PREFIX_ID_PERMISSIONS + userId);
// 清除空拦截
CacheUtil.delNilFlag(PREFIX_ID_PERMISSIONS + userId);
// 发送通知消息
redisPlugin.sendMessage(
@ -610,17 +634,24 @@ public class UserUtil {
public static void refreshUserMenus(String userId){
try {
Object obj = CacheUtil.get(PREFIX_ID_MENUS + userId);
boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_ID_MENUS + userId);
// 只要不为空 则执行刷新
if (hasNilFlag){
// 清除空拦截
CacheUtil.delNilFlag(PREFIX_ID_MENUS + userId);
}
if(obj != null){
// 先删除
CacheUtil.del(PREFIX_ID_MENUS + userId);
// 清除空拦截
CacheUtil.delNilFlag(PREFIX_ID_MENUS + userId);
// 发送通知消息
redisPlugin.sendMessage(
UserMsgFactory.createUserMenusMsg(userId, null)
);
}
}catch (Exception e){
log.error(e.getMessage(), e);
}

@ -17,6 +17,10 @@ opsli:
- sys_tenant
- sys_user
- sys_user_role_ref
- sys_area
- sys_org
- sys_org_user_ref
# 排除字段
exclude-fields:

@ -16,6 +16,9 @@
package org.opsli.plugins.redis.lock;
import org.opsli.common.constants.CacheConstants;
import org.opsli.common.utils.Props;
import java.util.concurrent.atomic.AtomicInteger;
/**
@ -27,7 +30,17 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public class RedisLock {
private static final String LOCK_PREFIX = "opsli:lock:";
private static final String LOCK_PREFIX;
/** 热点数据前缀 */
public static final String PREFIX_NAME;
static {
// 缓存前缀
Props props = new Props("application.yaml");
PREFIX_NAME = props.getStr("spring.cache-conf.prefix", CacheConstants.PREFIX_NAME) + ":";
LOCK_PREFIX = PREFIX_NAME + "lock:";
}
/** 锁名称 */
private String lockName;
@ -44,8 +57,6 @@ public class RedisLock {
/** 线程锁 */
private AtomicInteger atomicInteger;
/**
*
*/

@ -66,7 +66,18 @@ spring:
# 默认编码
default-encoding: UTF-8
# 一级缓存 ---- redis 配置
# 缓存配置项
cache-conf:
# 前缀
prefix: opsli
# 一级缓存 ---- EhCache 配置
cache:
# 开启 是否启用本地缓存
enable: true
# 加载配置文件
jcache:
config: classpath:config/ehcache-opsli.xml
# 二级缓存 ---- Redis 配置
redis:
# 开启消息订阅
pushsub:
@ -78,13 +89,6 @@ spring:
max-wait: -1ms #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
min-idle: 0 #最小等待连接中的数量,设 0 为没有限制
shutdown-timeout: 100ms
# 二级缓存 ---- EhCache 配置
cache:
# 开启 是否启用本地缓存
enable: true
# 加载配置文件
jcache:
config: classpath:config/ehcache-opsli.xml
# 数据库配置
datasource:

Loading…
Cancel
Save