diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JacksonJsonUtil.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JacksonJsonUtil.java new file mode 100644 index 000000000..5e80ac88b --- /dev/null +++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JacksonJsonUtil.java @@ -0,0 +1,128 @@ +package org.dromara.common.json.utils; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +@Slf4j +public class JacksonJsonUtil { + private static ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + static { + //对象的所有字段全部列入 + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS); + + //取消默认转换timesstamps(时间戳)形式 + OBJECT_MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + + //忽略空bean转json错误 + OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + + //忽略在json字符串中存在,在java类中不存在字段,防止错误 + OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + } + + public static String objToJson(T obj) { + if (obj == null) { + return null; + } + + try { + return obj instanceof String ? (String)obj : OBJECT_MAPPER.writeValueAsString(obj); + } catch (Exception e) { + log.error("obj To json is error" , e); + return null; + } + } + + /** + * 返回格式化好的json串 + * @param obj + * @param + * @return + */ + public static String objToJsonPretty(T obj) { + if (obj == null) { + return null; + } + + try { + return obj instanceof String ? (String)obj : OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + } catch (Exception e) { + log.warn("obj To json pretty is error", e); + return null; + } + } + + public static T json2Object(String json, Class clazz) { + if (StringUtils.isEmpty(json) || clazz == null) { + return null; + } + + try { + return clazz.equals(String.class) ? (T)json : OBJECT_MAPPER.readValue(json, clazz); + } catch (Exception e) { + log.warn("json To obj is error", e); + return null; + } + } + + /** + * 通过 TypeReference 处理List这类多泛型问题 + * @param json + * @param typeReference + * @param + * @return + */ + public static T json2Object(String json, TypeReference typeReference) { + if (StringUtils.isEmpty(json) || typeReference == null) { + return null; + } + + try { + return (T)(typeReference.getType().equals(String.class) ? json : OBJECT_MAPPER.readValue(json, typeReference)); + } catch (Exception e) { + log.warn("json To obj is error", e); + return null; + } + } + + /** + * 通过jackson 的javatype 来处理多泛型的转换 + * @param json + * @param collectionClazz + * @param elements + * @param + * @return + */ + public static T json2Object(String json, Class collectionClazz, Class...elements) { + JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(collectionClazz, elements); + + try { + return OBJECT_MAPPER.readValue(json, javaType); + } catch (Exception e) { + log.warn("json To obj is error", e); + return null; + } + } + + + public static Map toLinkedHashMap(String jsonStr) { + + Map data = null; + try { + data = OBJECT_MAPPER.readValue(jsonStr, new TypeReference>() {}); + } catch (IOException e) { + e.printStackTrace(); + } + return data; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java index c07a2bb3b..5a5e4781f 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java @@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.dromara.common.core.factory.YmlPropertySourceFactory; +import org.dromara.common.mybatis.core.mapper.MySqlInjector; import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler; import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor; import org.mybatis.spring.annotation.MapperScan; @@ -82,6 +83,14 @@ public class MybatisPlusConfig { return new DefaultIdentifierGenerator(NetUtil.getLocalhost()); } + /** + * 全量更新 + * @return + */ + @Bean + public MySqlInjector sqlInjector() { + return new MySqlInjector(); + } /** * PaginationInnerInterceptor 分页插件,自动识别数据库类型 * https://baomidou.com/pages/97710a/ diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java index 1f36f0400..d2fdc4fcc 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java @@ -30,7 +30,7 @@ import java.util.stream.Collectors; * @since 2021-05-13 */ @SuppressWarnings("unchecked") -public interface BaseMapperPlus extends BaseMapper { +public interface BaseMapperPlus extends CommonMapper { Log log = LogFactory.getLog(BaseMapperPlus.class); diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/CommonMapper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/CommonMapper.java new file mode 100644 index 000000000..f0e560f9c --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/CommonMapper.java @@ -0,0 +1,32 @@ +package org.dromara.common.mybatis.core.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @Author warden + * @Date 2023/1/17 17:37 + */ +public interface CommonMapper extends BaseMapper { + /** + * 全量插入,等价于insert + * {@link com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn} + * + * @param entityList + * @return + */ + int insertBatchSomeColumn(List entityList); + + /** + * 全量更新,不忽略null字段,等价于update + * 解决mybatis-plus会自动忽略null字段不更新 + * {@link com.baomidou.mybatisplus.extension.injector.methods.AlwaysUpdateSomeColumnById} + * + * @param entity + * @return + */ + int alwaysUpdateSomeColumnById(@Param(Constants.ENTITY) T entity); +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/MySqlInjector.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/MySqlInjector.java new file mode 100644 index 000000000..771747367 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/MySqlInjector.java @@ -0,0 +1,33 @@ +package org.dromara.common.mybatis.core.mapper; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.extension.injector.methods.AlwaysUpdateSomeColumnById; +import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn; + +import java.util.List; + +/** + * 自定义Sql注入 + * + * @author nieqiurong 2018/8/11 20:23. + */ +public class MySqlInjector extends DefaultSqlInjector { + + @Override + public List getMethodList(Class mapperClass, TableInfo tableInfo) { + List methodList = super.getMethodList(mapperClass, tableInfo); + + /** + * 以下 3 个为内置选装件 + * 头 2 个支持字段筛选函数 + */ + // 例: 不要指定了 update 填充的字段 + methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE)); + // 标识了INSERT的字段不更新(createTime 和 createBy) + methodList.add(new AlwaysUpdateSomeColumnById(i -> i.getFieldFill() != FieldFill.INSERT)); + return methodList; + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/BeanUtil.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/BeanUtil.java new file mode 100644 index 000000000..8098a75ab --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/BeanUtil.java @@ -0,0 +1,167 @@ +package org.dromara.common.mybatis.core.utils; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.model.LoginUser; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.satoken.utils.LoginHelper; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * @Author warden + * @Date 2022/12/8 14:56 + */ +@Slf4j +public class BeanUtil { + public static final String ADD = "ADD"; + public static final String REMOVE = "REMOVE"; + public static final String NORMAL = "NORMAL"; + + /** + * 比对两条记录数据库字段是否相同,相同返回true,不同返回false + * + * @param compare + * @param current + * @return + */ + public static boolean diffObject(Object compare, Object current) { + List fieldList = new ArrayList<>(); + + Field[] allFields = ReflectUtil.getFieldsDirectly(compare.getClass(), true); + + String[] dontCompareFields = {"createBy", "createTime", "updateBy", "updateTime", "params", "searchValue", "serialVersionUID"}; + List dCList = new ArrayList<>(); + dCList.addAll(Arrays.asList(dontCompareFields)); + for (Field field : allFields) { + if (dCList.contains(field.getName())) { + continue; + } + TableField tableField = field.getAnnotation(TableField.class); + if (tableField == null || tableField.exist()) { + fieldList.add(field); + } + } + + for (Field field : fieldList) { + try { + //抑制Java对其的检查 + field.setAccessible(true); + + //获取 object 中 field 所代表的属性值 + Object comp = field.get(compare); + Object curr = field.get(current); + if (comp != null && curr != null) { + if (!comp.toString().equals(curr.toString())) { + return false; + } + } else { + if ((comp == null && curr != null) || (comp != null && curr == null)) { + return false; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return true; + } + + /** + * 比较原List与现List的差异,返回Map + * Map的三个KEY值:ADD(新增)\REMOVE(删除)\NORMAL(相同) + * + * @param compareList 需要比较的list(页面传过来的) + * @param currList 当前的list(当前数据库中的) + * @return + */ + public static Map> diffList(Set compareList, Set currList) { + + if (CollectionUtil.isEmpty(compareList) && CollectionUtil.isEmpty(currList)) { + return null; + } + + Map> result = new HashMap(); + + result.put(ADD, new HashSet()); + result.put(REMOVE, new HashSet()); + result.put(NORMAL, new HashSet()); + + if (CollectionUtil.isEmpty(compareList)) { + result.put(REMOVE, currList); + } else if (CollectionUtil.isEmpty(currList)) { + result.put(ADD, compareList); + } else { + for (Long comStr : compareList) { + if (currList.contains(comStr)) { + result.get(NORMAL).add(comStr); + } else { + result.get(ADD).add(comStr); + } + } + + for (Long currStr : currList) { + if (compareList.contains(currStr)) { + result.get(NORMAL).add(currStr); + } else { + result.get(REMOVE).add(currStr); + } + } + } + return result; + } + + public static Map diffList(List compareList, List currList) { + Set compareSet = new HashSet<>(); + Set currSet = new HashSet<>(); + + if (CollectionUtil.isNotEmpty(compareList)) { + for (Long val : compareList) { + compareSet.add(val); + } + } + + if (CollectionUtil.isNotEmpty(currList)) { + for (Long val : currList) { + currSet.add(val); + } + } + + return diffList(compareSet, currSet); + + } + + public static void fillValue(Object object) { + if (object instanceof BaseEntity) { + BaseEntity baseEntity = (BaseEntity) object; + Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime()) + ? baseEntity.getCreateTime() : new Date(); + baseEntity.setCreateTime(current); + baseEntity.setUpdateTime(current); + Long userId = baseEntity.getCreateBy() != null + ? baseEntity.getCreateBy() : getLoginUserId(); + // 当前已登录 且 创建人为空 则填充 + baseEntity.setCreateBy(userId); + // 当前已登录 且 更新人为空 则填充 + baseEntity.setUpdateBy(userId); + } + } + + /** + * 获取登录用户名 + */ + private static Long getLoginUserId() { + LoginUser loginUser; + try { + loginUser = LoginHelper.getLoginUser(); + } catch (Exception e) { + log.warn("自动注入警告 => 用户未登录"); + return null; + } + return loginUser.getUserId(); + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/SaveDiffUtil.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/SaveDiffUtil.java new file mode 100644 index 000000000..dd27c86c5 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/utils/SaveDiffUtil.java @@ -0,0 +1,286 @@ +package org.dromara.common.mybatis.core.utils; + +import cn.hutool.core.util.ReflectUtil; +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.common.mybatis.core.mapper.CommonMapper; + +import java.util.*; + +/** + * 比对列表数据,用于判断差异并保存到数据库中 + * 全量保存 + */ +public class SaveDiffUtil { + // 获取mybatisplus 的ID生成器 + private final static IdentifierGenerator IDENTIFIER_GENERATOR = SpringUtils.getBean("idGenerator"); + + public static Map> saveFormDiff(List formList, List oriList, BaseMapper mapper) { + return saveFormDiff(formList, oriList, mapper, null, null); + } + + public static Map> saveFormDiff(List formList, List oriList, BaseMapper mapper, Boolean exeUpdate) { + return saveFormDiff(formList, oriList, mapper, exeUpdate, null); + } + + /** + * 需要处理手工修改的记录 + * + * @param formList 表单提交的数据 + * @param oriList 数据库数据 + * @param mapper 处理实体的mapper + * @param exeUpdate 是否执行更新 + * @param updateAll 更新操作是否全字段更新 + * @throws Exception + */ + public static Map> saveFormDiff(List formList, List oriList, BaseMapper mapper, + Boolean exeUpdate, Boolean updateAll) { + if (exeUpdate == null) { + exeUpdate = false; + } + if (updateAll == null) { + updateAll = false; + } + + + if (formList == null) { + formList = new ArrayList(); + } + + Map> result = getFormDiff(formList, oriList); + + if (result != null) { + Set addList = result.get(BeanUtil.ADD); + Set removeList = result.get(BeanUtil.REMOVE); + Set updateList = result.get(BeanUtil.NORMAL); + + // 添加的对象 + if (addList != null && addList.size() > 0) { + List addObjList = new ArrayList(); + for (Long id : addList) { + Iterator it = formList.iterator(); + while (it.hasNext()) { + Object obj = it.next(); + Long objId = (Long) ReflectUtil.getFieldValue(obj, "id"); + if (id.equals(objId)) { + addObjList.add(obj); +// it.remove(); 不可删除,需要返回 + break; + } + } + } + if (addObjList != null && addObjList.size() > 0) { +// List list = cn.hutool.core.bean.BeanUtil.copyToList(addObjList, clz); + ((BaseMapperPlus) mapper).insertBatch(addObjList); + } + } + + if (removeList != null && removeList.size() > 0) { + mapper.deleteBatchIds(removeList); + } + + if (exeUpdate && updateList != null && updateList.size() > 0) { + List updateObjList = new ArrayList(); + + for (Long id : updateList) { + Object compareObj = findObj(formList, id); + Object currObj = findObj(oriList, id); + + // 比对数据库字段是否有修改 + boolean diff = BeanUtil.diffObject(compareObj, currObj); + if (!diff) { + updateObjList.add(compareObj); + } + } + + if (updateObjList.size() > 0) { + for (Object obj : updateObjList) { + if (updateAll) { + // 全量更新 + ((CommonMapper) mapper).alwaysUpdateSomeColumnById(obj); + } else { + // 更新不为空 + mapper.updateById(obj); + } + } + } + } + } + + return result; + } + + public static Map> getFormDiff(List formList, List oriList) { + // 需要比较的 + List compareList = new ArrayList<>(); + // 原始数据 + List currList = new ArrayList<>(); + + updateObject(formList, compareList); + updateObject(oriList, currList); + + Map> result = BeanUtil.diffList(compareList, currList); + + return result; + } + + + private static void updateObject(List objList, List targetList, String idFieldName) { + if (idFieldName == null) { + idFieldName = "id"; + } + + for (Object obj : objList) { + Object val = ReflectUtil.getFieldValue(obj, idFieldName); + if (val != null) { + targetList.add(val); + } else { + Long uuid = IDENTIFIER_GENERATOR.nextId(obj).longValue(); + targetList.add(uuid); + ReflectUtil.setFieldValue(obj, "id", uuid); + } + } + } + + private static void updateObject(List objList, List targetList) { + updateObject(objList, targetList, null); + } + + /** + * 根据查询的属性找对象的key并赋值到目标列表中 + * + * @param oriList + * @param targetList + * @param searchFieldName + * @param idFieldName + */ + public static void updateObject(List oriList, List targetList, String searchFieldName, String idFieldName) { + if (oriList == null) { + return; + } + for (Object oriObj : oriList) { + Object searchStr = ReflectUtil.getFieldValue(oriObj, searchFieldName); + + if (searchStr != null) { + for (Object tarObj : targetList) { + Object tarStr = ReflectUtil.getFieldValue(tarObj, searchFieldName); + if (searchStr.equals(tarStr)) { + Object idStr = ReflectUtil.getFieldValue(oriObj, idFieldName); + if (idStr != null) { + ReflectUtil.setFieldValue(tarObj, idFieldName, idStr); + } + continue; + } + } + } + + } + } + + /** + * 根据属性及属性值,从列表中查找对象 + * + * @param objList + * @param idFieldName + * @param keyValue + * @return + */ + private static Object findObj(List objList, String idFieldName, Object keyValue) { + + if (idFieldName == null) { + idFieldName = "id"; + } + + Object result = null; + for (Object obj : objList) { + Object val = ReflectUtil.getFieldValue(obj, idFieldName); + if (keyValue.equals(val)) { + result = obj; + break; + } + } + + return result; + } + + private static Object findObj(List objList, Object keyValue) { + return findObj(objList, null, keyValue); + } + +// /** +// * 修改关系记录(根据linkID+pkId添加和删除) +// */ +// /** +// * @param currentUser 当前用户 +// * @param formList 表单提交的数据 +// * @param oriList 数据库中的数据 +// * @param clz 要插入表中的对象对应的类 +// * @param findId 根据哪个属性查找传入的formList、oriList +// * @param pkIdName 根据哪个属性删除(添加)关系记录 +// * @param linkIdName 外键字段名称 +// * @param linkId 外键字段值 +// * @param mapper +// * @throws Exception +// */ +// public static void saveRelaDiff( +// List formList, +// List oriList, +// Class clz, +// String findId, +// String pkIdName, +// String linkIdName, +// String linkId, +// BaseMapper mapper) throws Exception { +// // 需要比较的 +// List compareList = new ArrayList<>(); +// // 原始数据 +// List currList = new ArrayList<>(); +// +// updateObject(formList, compareList, findId); +// updateObject(oriList, currList, findId); +// +// Map> result = BeanUtil.diffList(compareList, currList); +// +// if (result != null) { +// Set addList = result.get(BeanUtil.ADD); +// Set removeList = result.get(BeanUtil.REMOVE); +// +// //删除数据 +// if (removeList != null && removeList.size() > 0) { +// for (String removeId : removeList) { +// Object obj = clz.newInstance(); +// ReflectUtil.setFieldValue(obj, "delFlag", DelFlag.DELETE); +// +// Example remExample = new Example(clz); +// Example.Criteria remCriteria = remExample.createCriteria(); +// //获取删除标记为正常的记录 +// remCriteria.andEqualTo(linkIdName, linkId); +// remCriteria.andEqualTo(pkIdName, removeId); +// remCriteria.andNotEqualTo("delFlag", DelFlag.DELETE); +// mapper.updateByExampleSelective(obj, remExample); +// } +// } +// +// //添加资源 +// if (addList != null && addList.size() > 0) { +// List addObjList = new ArrayList(); +// for (String addId : addList) { +// Object obj = clz.newInstance(); +// ReflectUtil.setFieldValue(obj, linkIdName, linkId); +// ReflectUtil.setFieldValue(obj, pkIdName, addId); +// +// BeanUtil.setCreateUser(currentUser, obj); +// BeanUtil.setUpdateUser(currentUser, obj); +// +// addObjList.add(obj); +// } +// +// if (addObjList.size() > 0) { +// mapper.batchInsert(addObjList); +// } +// } +// } +// } +} diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java index c9c2f9529..9e35c8d19 100644 --- a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java +++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java @@ -12,6 +12,15 @@ import org.dromara.common.tenant.helper.TenantHelper; */ public class TenantKeyPrefixHandler extends KeyPrefixHandler { + // 用于获取指定租户下的缓存 + private static final ThreadLocal TENANT_ID = new ThreadLocal<>(); + + public static void setTenantId(String tenantId) { + if (StringUtils.isNotEmpty(tenantId)) { + TENANT_ID.set(tenantId); + } + } + public TenantKeyPrefixHandler(String keyPrefix) { super(keyPrefix); } @@ -27,7 +36,12 @@ public class TenantKeyPrefixHandler extends KeyPrefixHandler { if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { return super.map(name); } - String tenantId = TenantHelper.getTenantId(); + + String tenantId = TENANT_ID.get(); + TENANT_ID.remove(); + if (tenantId == null) { + tenantId = TenantHelper.getTenantId(); + } if (StringUtils.startsWith(name, tenantId)) { // 如果存在则直接返回 return super.map(name); diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java index c3f09773b..30a873dd7 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysUserOnlineController.java @@ -4,22 +4,18 @@ import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.bean.BeanUtil; -import org.dromara.common.core.constant.CacheConstants; -import org.dromara.common.core.constant.GlobalConstants; +import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.dto.UserOnlineDTO; -import org.dromara.common.core.utils.StreamUtils; -import org.dromara.common.core.utils.StringUtils; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.TableDataInfo; -import org.dromara.common.redis.utils.RedisUtils; import org.dromara.common.web.core.BaseController; import org.dromara.system.domain.SysUserOnline; -import lombok.RequiredArgsConstructor; +import org.dromara.system.domain.bo.SysOnlineUserBo; +import org.dromara.system.service.ISysUserOnlineService; import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -33,6 +29,8 @@ import java.util.List; @RequestMapping("/monitor/online") public class SysUserOnlineController extends BaseController { + private final ISysUserOnlineService onlineService; + /** * 获取在线用户监控列表 * @@ -42,33 +40,12 @@ public class SysUserOnlineController extends BaseController { @SaCheckPermission("monitor:online:list") @GetMapping("/list") public TableDataInfo list(String ipaddr, String userName) { - // 获取所有未过期的 token - List keys = StpUtil.searchTokenValue("", 0, -1, false); - List userOnlineDTOList = new ArrayList<>(); - for (String key : keys) { - String token = StringUtils.substringAfterLast(key, ":"); - // 如果已经过期则跳过 - if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) { - continue; - } - userOnlineDTOList.add(RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token)); - } - if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) { - userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline -> - StringUtils.equals(ipaddr, userOnline.getIpaddr()) && - StringUtils.equals(userName, userOnline.getUserName()) - ); - } else if (StringUtils.isNotEmpty(ipaddr)) { - userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline -> - StringUtils.equals(ipaddr, userOnline.getIpaddr()) - ); - } else if (StringUtils.isNotEmpty(userName)) { - userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline -> - StringUtils.equals(userName, userOnline.getUserName()) - ); - } + SysOnlineUserBo userBo = new SysOnlineUserBo(); + userBo.setIpaddr(ipaddr); + userBo.setUserName(userName); + List userOnlineDTOList = this.onlineService.getOnlineUsers(null, userBo); + // 倒序排序 Collections.reverse(userOnlineDTOList); - userOnlineDTOList.removeAll(Collections.singleton(null)); List userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class); return TableDataInfo.build(userOnlineList); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOnlineUserBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOnlineUserBo.java new file mode 100644 index 000000000..bd6553385 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysOnlineUserBo.java @@ -0,0 +1,14 @@ +package org.dromara.system.domain.bo; + +import lombok.Data; + +/** + * @Author warden + * @Date 2023/6/19 17:34 + */ +@Data +public class SysOnlineUserBo { + private String ipaddr; + private String userName; +// private String tenantId; +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserOnlineService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserOnlineService.java new file mode 100644 index 000000000..b4544f9a3 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserOnlineService.java @@ -0,0 +1,13 @@ +package org.dromara.system.service; + +import org.dromara.common.core.domain.dto.UserOnlineDTO; +import org.dromara.system.domain.bo.SysOnlineUserBo; + +import java.util.List; + +/** + * @Author warden + */ +public interface ISysUserOnlineService { + List getOnlineUsers(String tenantId, SysOnlineUserBo userBo); +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java index eb681519a..42ac97b05 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java @@ -1,6 +1,8 @@ package org.dromara.system.service.impl; +import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.secure.BCrypt; +import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; @@ -11,6 +13,7 @@ import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.CacheNames; import org.dromara.common.core.constant.Constants; import org.dromara.common.core.constant.TenantConstants; +import org.dromara.common.core.domain.dto.UserOnlineDTO; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.SpringUtils; @@ -18,10 +21,12 @@ import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.system.domain.*; +import org.dromara.system.domain.bo.SysOnlineUserBo; import org.dromara.system.domain.bo.SysTenantBo; import org.dromara.system.domain.vo.SysTenantVo; import org.dromara.system.mapper.*; import org.dromara.system.service.ISysTenantService; +import org.dromara.system.service.ISysUserOnlineService; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @@ -52,6 +57,7 @@ public class SysTenantServiceImpl implements ISysTenantService { private final SysDictTypeMapper dictTypeMapper; private final SysDictDataMapper dictDataMapper; private final SysConfigMapper configMapper; + private final ISysUserOnlineService onlineService; /** * 查询租户 @@ -261,6 +267,17 @@ public class SysTenantServiceImpl implements ISysTenantService { @CacheEvict(cacheNames = CacheNames.SYS_TENANT, key = "#bo.tenantId") @Override public int updateTenantStatus(SysTenantBo bo) { + // 停用租户时,要将租户下的所有在线用户全部剔除,避免依然有权限操作 + if (Constants.FAIL.equals(bo.getStatus())) { + List userOnlineDTOList = this.onlineService.getOnlineUsers(bo.getTenantId(), null); + for (UserOnlineDTO online : userOnlineDTOList) { + try { + StpUtil.kickoutByTokenValue(online.getTokenId()); + } catch (NotLoginException ignored) { + } + } + } + SysTenant tenant = MapstructUtils.convert(bo, SysTenant.class); return baseMapper.updateById(tenant); } diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserOnlineServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserOnlineServiceImpl.java new file mode 100644 index 000000000..29e18b613 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserOnlineServiceImpl.java @@ -0,0 +1,61 @@ +package org.dromara.system.service.impl; + + +import cn.dev33.satoken.stp.StpUtil; +import org.dromara.common.core.constant.CacheConstants; +import org.dromara.common.core.domain.dto.UserOnlineDTO; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.common.tenant.handle.TenantKeyPrefixHandler; +import org.dromara.system.domain.bo.SysOnlineUserBo; +import org.dromara.system.service.ISysUserOnlineService; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class SysUserOnlineServiceImpl implements ISysUserOnlineService { + + @Override + public List getOnlineUsers(String tenantId, SysOnlineUserBo userBo) { + String ipaddr = userBo == null ? null : userBo.getIpaddr(); + String userName = userBo == null ? null : userBo.getUserName(); + // 获取所有未过期的 token + List keys = StpUtil.searchTokenValue("", 0, -1, false); + List userOnlineDTOList = new ArrayList<>(); + for (String key : keys) { + String token = StringUtils.substringAfterLast(key, ":"); + // 如果已经过期则跳过 + if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) { + continue; + } + + TenantKeyPrefixHandler.setTenantId(tenantId); + UserOnlineDTO online = RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token); + + if (online != null) { + userOnlineDTOList.add(online); + } + } +// // 清空为空的数据 +// userOnlineDTOList.removeAll(Collections.singleton(null)); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) { + userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline -> + StringUtils.equals(ipaddr, userOnline.getIpaddr()) && + StringUtils.equals(userName, userOnline.getUserName()) + ); + } else if (StringUtils.isNotEmpty(ipaddr)) { + userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline -> + StringUtils.equals(ipaddr, userOnline.getIpaddr()) + ); + } else if (StringUtils.isNotEmpty(userName)) { + userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline -> + StringUtils.equals(userName, userOnline.getUserName()) + ); + } + + return userOnlineDTOList; + } +}