Răsfoiți Sursa

私有元素表相关bug

liuyc 2 ani în urmă
părinte
comite
51ac1da7fe
14 a modificat fișierele cu 1745 adăugiri și 87 ștergeri
  1. 22 0
      blade-service/blade-manager/pom.xml
  2. 21 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/aop/AvoidRepeatableCommit.java
  3. 78 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/aop/AvoidRepeatableCommitAspect.java
  4. 6 11
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ProjectInfoController.java
  5. 2 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeContractMapper.java
  6. 24 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeContractMapper.xml
  7. 6 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreePrivateMapper.xml
  8. 2 5
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ArchiveTreeServiceImpl.java
  9. 3 3
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsFormElementServiceImpl.java
  10. 20 22
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java
  11. 44 13
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreePrivateServiceImpl.java
  12. 29 33
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeServiceImpl.java
  13. 122 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/unit/ProtoStuffSerializerUtil.java
  14. 1366 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/unit/RedisUtil.java

+ 22 - 0
blade-service/blade-manager/pom.xml

@@ -136,6 +136,28 @@
             <version>4.6.0.2</version>
         </dependency>
 
+        <!-- 序列化相关-->
+        <dependency>
+            <groupId>com.dyuproject.protostuff</groupId>
+            <artifactId>protostuff-core</artifactId>
+            <version>1.1.5</version>
+        </dependency>
+        <dependency>
+            <groupId>com.dyuproject.protostuff</groupId>
+            <artifactId>protostuff-runtime</artifactId>
+            <version>1.1.5</version>
+        </dependency>
+        <dependency>
+            <groupId>com.bstek.ureport</groupId>
+            <artifactId>ureport2-console</artifactId>
+            <version>2.2.9</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>me.zhyd.oauth</groupId>
+            <artifactId>JustAuth</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 21 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/aop/AvoidRepeatableCommit.java

@@ -0,0 +1,21 @@
+package org.springblade.manager.aop;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 避免接口重复提交AOP
+ * @author liuyc
+ * @since  2022-09-02
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AvoidRepeatableCommit {
+    /**
+     * 过期时间,单位毫秒
+     */
+    long timeout() default 60000;
+
+}

+ 78 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/aop/AvoidRepeatableCommitAspect.java

@@ -0,0 +1,78 @@
+package org.springblade.manager.aop;
+
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.utils.IpUtils;
+import org.apache.commons.lang.StringUtils;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springblade.manager.unit.RedisUtil;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.lang.reflect.Method;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 避免接口重复提交AOP
+ * @author liuyc
+ * @since  2022-09-02
+ */
+@Aspect
+@Component
+@Slf4j
+public class AvoidRepeatableCommitAspect {
+
+    private final static String KEY_NAME = "submit-wbs-project:";
+
+    @Resource
+    private RedisUtil redisUtil;
+
+    @Around("@annotation(org.springblade.manager.aop.AvoidRepeatableCommit)")
+    public Object around(ProceedingJoinPoint point) throws Throwable {
+
+        String ip = IpUtils.getLocalIp();
+
+        //获取注解
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        Method method = signature.getMethod();
+
+        //目标类、方法
+        String className = method.getDeclaringClass().getName();
+
+        String name = method.getName();
+
+        //类名和方法
+        String ipKey = String.format("%s#%s", className, name);
+
+        //HashCode
+        int hashCode = Math.abs(ipKey.hashCode());
+
+        String key = String.format("%s:%s_%d",KEY_NAME, ip, hashCode);
+
+        log.info("ipKey={},hashCode={},key={}", ipKey, hashCode, key);
+
+        AvoidRepeatableCommit avoidRepeatableCommit = method.getAnnotation(AvoidRepeatableCommit.class);
+
+        long timeout = avoidRepeatableCommit.timeout();
+
+        String value = redisUtil.get(key);
+
+        if (StringUtils.isNotEmpty(value)) {
+            throw new ServiceException("请勿重复提交请求,60秒后重试!");
+        }
+
+        //过期时间
+        redisUtil.setEx(key, String.valueOf(SnowFlakeUtil.getId()), timeout, TimeUnit.MILLISECONDS);
+
+        //执行方法
+        return point.proceed();
+
+    }
+
+}

+ 6 - 11
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ProjectInfoController.java

@@ -7,6 +7,7 @@ import lombok.AllArgsConstructor;
 
 import javax.validation.Valid;
 
+import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.tool.api.R;
@@ -211,21 +212,15 @@ public class ProjectInfoController extends BladeController {
     @PostMapping("/submitWbsTreeInProject")
     @ApiOperationSupport(order = 12)
     @ApiOperation(value = "保存或修改分配项目级wbs树", notes = "传入WbsTreeContractDTO")
-    public R submitWbsTreeInProject(@RequestBody WbsTreeContractDTO pawDTO){
+    public R submitWbsTreeInProject(@RequestBody WbsTreeContractDTO pawDTO) {
         if (pawDTO.getWbsType() == 1) {
-            boolean b = wbsTreeService.submitWbsTreeInProject1(pawDTO);
-            if (b) {
-                return R.success("关联质检项目级树失败");
+            if (wbsTreeService.submitWbsTreeInProject1(pawDTO)) {
+                return R.success("关联质检项目级树成功");
             }
-            return R.fail(200,"关联质检项目级树失败");
         } else if (pawDTO.getWbsType() == 2) {
-            boolean b = wbsTreeService.submitWbsTreeInProject1(pawDTO);
-            if (b) {
-                return R.success("关联试验项目级树失败");
-            }
-            return R.fail(200,"关联试验项目级树失败");
+            throw new ServiceException("当前试验功能未开放");
         }
-        return R.fail(200, "操作失败");
+        return R.fail("操作失败");
     }
 
     /**

+ 2 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeContractMapper.java

@@ -77,4 +77,6 @@ public interface WbsTreeContractMapper extends BaseMapper<WbsTreeContract> {
 
     void updateBatchByIds2(@Param("wbsTreePrivate") WbsTreePrivate wbsTreePrivate,@Param("id")  Long id);
 
+    void updateContractTablesInfo(@Param("contractInfoId") Long contractInfoId,@Param("wbsTreePrivate") WbsTreePrivate wbsTreePrivate);
+
 }

+ 24 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeContractMapper.xml

@@ -448,6 +448,9 @@
             partition_code = #{wbsTree.partitionCode},
             table_type = #{wbsTree.tableType},
             table_owner = #{wbsTree.tableOwner}
+            <if test="wbsTree.sort != null and wbsTree.sort != ''">
+                , sort = #{wbsTree.sort}
+            </if>
         WHERE id = #{wbsTree.id}
           AND contract_id = #{id}
           AND status = 1
@@ -464,12 +467,33 @@
             partition_code = #{wbsTreePrivate.partitionCode},
             table_type = #{wbsTreePrivate.tableType},
             table_owner = #{wbsTreePrivate.tableOwner}
+            <if test="wbsTreePrivate.sort != null and wbsTreePrivate.sort != ''">
+                , sort = #{wbsTreePrivate.sort}
+            </if>
         WHERE id = #{wbsTreePrivate.id}
           AND contract_id = #{id}
           AND status = 1
           AND is_deleted = 0
     </update>
 
+    <update id="updateContractTablesInfo">
+        UPDATE m_wbs_tree_contract
+        SET dept_name = #{wbsTreePrivate.deptName},
+            full_name =#{wbsTreePrivate.deptName},
+            table_type  = #{wbsTreePrivate.tableType},
+            table_owner = #{wbsTreePrivate.tableOwner}
+            <if test="wbsTreePrivate.sort != null and wbsTreePrivate.sort != ''">
+                , sort = #{wbsTreePrivate.sort}
+            </if>
+        WHERE id = #{wbsTreePrivate.id}
+        AND project_id = #{wbsTreePrivate.projectId}
+        AND contract_id = #{contractInfoId}
+        AND old_id is null
+        AND type = 2
+        AND status = 1
+        AND is_deleted = 0
+    </update>
+
     <select id="selectQueryValueLikeNodeName" resultMap="ResultMap">
         select * from m_wbs_tree_contract where is_deleted = 0 and contract_id = #{contractId} and (dept_name like concat('%',#{queryValue},'%') or full_name like concat('%',#{queryValue},'%')) and `type` = '1'
     </select>

+ 6 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreePrivateMapper.xml

@@ -209,6 +209,9 @@
             partition_code = #{wbsTree.partitionCode},
             table_type = #{wbsTree.tableType},
             table_owner = #{wbsTree.tableOwner}
+            <if test="wbsTree.sort != null and wbsTree.sort != ''">
+                , sort = #{wbsTree.sort}
+            </if>
         WHERE id = #{wbsTree.id}
         AND project_id = #{projectId}
         AND status = 1
@@ -225,6 +228,9 @@
             partition_code = #{wbsTreePrivate.partitionCode},
             table_type = #{wbsTreePrivate.tableType},
             table_owner = #{wbsTreePrivate.tableOwner}
+            <if test="wbsTreePrivate.sort != null and wbsTreePrivate.sort != ''">
+                , sort = #{wbsTreePrivate.sort}
+            </if>
         WHERE id = #{wbsTreePrivate.id}
           AND project_id = #{projectId}
           AND wbs_id = #{wbsId}

+ 2 - 5
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ArchiveTreeServiceImpl.java

@@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.StringPool;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
-import com.fasterxml.jackson.databind.BeanProperty;
 import lombok.AllArgsConstructor;
 import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.secure.utils.AuthUtil;
@@ -18,7 +17,6 @@ import org.springblade.manager.vo.ArchiveTreeVO;
 import org.springblade.manager.mapper.ArchiveTreeMapper;
 import org.springblade.manager.service.IArchiveTreeService;
 import org.springblade.core.mp.base.BaseServiceImpl;
-import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
@@ -178,8 +176,7 @@ public class ArchiveTreeServiceImpl extends BaseServiceImpl<ArchiveTreeMapper, A
             archiveTreeDTO.setIsStorageNode(1);
         }
 
-        //新增默认排序
-        //获取同级节点List
+        //默认排序
         if (archiveTreeDTO.getId() == null) {
             List<ArchiveTree> trees = baseMapper.selectList(Wrappers.<ArchiveTree>query().lambda()
                     .eq(ArchiveTree::getParentId, archiveTreeDTO.getParentId())
@@ -191,7 +188,7 @@ public class ArchiveTreeServiceImpl extends BaseServiceImpl<ArchiveTreeMapper, A
                 }
                 return true;
             }).collect(Collectors.toList());
-            //获取最大sort
+            //最大sort
             Optional<ArchiveTree> max = collect.stream().max(Comparator.comparingInt(ArchiveTree::getSort));
             Integer sort = 0;
             if (max.isPresent()) {

+ 3 - 3
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsFormElementServiceImpl.java

@@ -80,7 +80,7 @@ public class WbsFormElementServiceImpl extends BaseServiceImpl<WbsFormElementMap
     @Override
     public Boolean initTable(List<WbsFormElement> elementList, String tableName) {
         String sql = createSQL(elementList);
-        //建表
+
         return wbsFormElementMapper.createTable(sql, tableName) >= 0;
     }
 
@@ -314,9 +314,9 @@ public class WbsFormElementServiceImpl extends BaseServiceImpl<WbsFormElementMap
                 }*/
 
                 //设置默认长度
-                if (eLength < 255 && eLength > 65) {
+                if (eLength <= 255 && eLength > 65) {
                     eLength = 255;
-                } else if (eLength == 1000) {
+                } else if (eLength <= 1000 && eLength > 500) {
                     eLength = 500;
                 } else if (eLength >= 0 && eLength <= 65) {
                     eLength = 20;

+ 20 - 22
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java

@@ -74,7 +74,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
         List<String> collect7 = new ArrayList<>();
 
         if (list.size() > 0) {
-            //节点下所有表单Id
+            //所有表单
             List<WbsTreeContract> collect2 = list.stream().filter(f -> f.getType() == 2).collect(Collectors.toList());
             List<Long> collect4 = collect2.stream().map(WbsTreeContract::getId).collect(Collectors.toList());
             collect3 = collect4.stream().map(String::valueOf).collect(Collectors.toList());
@@ -85,11 +85,11 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
             collect7 = collect6.stream().map(String::valueOf).collect(Collectors.toList());
 
         }
-        //idList1-入参ids、collect7-当前项目所有节点ids
-        //需要新增节点ids
+
+        //新增节点ids
         List<String> collect8 = collect7;
         List<String> saveIds = idList1.stream().filter(f -> !collect8.contains(f)).collect(Collectors.toList());
-        //需要删除节点ids
+        //删除节点ids
         List<String> delIds = collect8.stream().filter(f -> !idList1.contains(f)).collect(Collectors.toList());
 
         if (saveIds.size() == 0 && delIds.size() == 0) {
@@ -102,7 +102,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                     .eq(WbsTreePrivate::getType, 2)
             );
 
-            //获取当前引用节点下的所有表
+            //获取当前引用节点下的所有表
             wbsTreePrivateList.forEach(wbsTreePrivate -> {
                 idList1.forEach(id -> {
                     if (Long.parseLong(id) == (wbsTreePrivate.getParentId()) && wbsTreePrivate.getType() == 2) {
@@ -112,7 +112,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                 });
             });
 
-            //私有wbs树下所有元素表的Id collect2
+            //私有wbs树下所有元素表的Id
             List<Long> collect = wbsTreePrivateList2.stream().map(WbsTreePrivate::getId).collect(Collectors.toList());
             List<String> collect2 = collect.stream().map(String::valueOf).collect(Collectors.toList());
             //获取所有wbsTreePrivate新增的表单Id
@@ -134,13 +134,11 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                 });
                 this.saveBatch(wbsTreeContracts, 10000);
             }
-
         } else {
-            //节点id改变,进行新增或删除节点以及该节点下的元素表
 
             //删除
             if (delIds.size() > 0) {
-                //判断是否被监理合同引用
+                //是否被监理合同引用
                 List<ContractRelationJlyz> contractRelationJLYZList = baseMapper.selectContractRelationInfoByidSG2(pawDTO.getContractId());
                 if (contractRelationJLYZList.size() > 0) {
                     throw new ServiceException("当前施工合同段wbs树被监理或业主合同段引用中,删除失败!");
@@ -148,7 +146,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
 
                 List<Long> ids1 = delIds.stream().map(Long::parseLong).collect(Collectors.toList());
 
-                //获取当前节点、表的信息
+                //获取当前节点、表
                 List<WbsTreeContract> wbsTreeContractList = baseMapper.selectTableListInfo(ids1, pawDTO.getWbsId(), pawDTO.getProjectId(), pawDTO.getContractId());
 
                 List<WbsTreeContract> tableNames = new ArrayList<>();
@@ -166,8 +164,8 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                     String names = StringUtils.join(nodeNames, " , ");
                     throw new ServiceException(StringUtil.format("节点下的 {} 中存在填报数据,删除失败!", names));
                 }
-                //删除
                 baseMapper.deleteBatch(ids1, pawDTO.getWbsId(), pawDTO.getProjectId(), pawDTO.getContractId());
+
             }
 
             //新增
@@ -176,7 +174,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                 List<WbsTreePrivate> wbsTreePrivatesList = new ArrayList<>();
                 ArrayList<ConstructionLedger> constructionLedgerList = new ArrayList<>();
 
-                //获取wbs私有树下节点、表 saveIds=新增的节点id
+                //获取wbs私有树下节点、表
                 List<WbsTreePrivate> wbsTreePrivates = wbsTreePrivateMapper.selectNodeAndTable2(pawDTO.getWbsId(), pawDTO.getProjectId());
 
                 for (WbsTreePrivate wbsTreePrivate : wbsTreePrivates) {
@@ -193,11 +191,11 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
 
                 //初始化
                 wbsTreePrivatesList.forEach(wbsTreePrivate -> {
-                    //节点、表信息
+                    //节点、表
                     WbsTreeContract wbsTreeContract = getWbsTreeContract(wbsTreePrivate, pawDTO);
                     wbsTreeContractList.add(wbsTreeContract);
 
-                    //台账信息
+                    //台账
                     if (wbsTreeContract.getDeptCategory() == 6) {
                         ConstructionLedger constructionLedger = new ConstructionLedger();
                         constructionLedger.setIsBeton(0);
@@ -209,7 +207,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                     }
                 });
 
-                //新增合同段wbs树、元素
+                //新增合同段节点、
                 this.saveBatch(wbsTreeContractList, 10000);
 
                 //新增施工台账
@@ -321,7 +319,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
 
 
     public List<WbsTreeContractTreeVO3> lazyTreeThree(String contractIdRelation, Long parentId, String contractId) {
-        //根据当前监理、业主合同段id,获取关联权限信息
+        //获取关联信息
         List<ContractRelationJlyz> listContractRelationInfo = baseMapper.selectContractRelationInfo(Long.valueOf(contractId));
 
         //获取关联施工合同段Id
@@ -349,16 +347,17 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
 
     @Override
     public List<WbsTreeContract> searchNodeAllTable(String primaryKeyId, String tableOwner, String contractId, String projectId) {
-        //获取当前节点
         WbsTreeContract wbsTreeContract = baseMapper.selectOne(Wrappers.<WbsTreeContract>query().lambda()
                 .eq(WbsTreeContract::getPKeyId, primaryKeyId));
         if (wbsTreeContract == null) {
             return Collections.emptyList();
         }
 
-        //获取当前项目合同段的角色roleId-默认查询施工合同段
         //当前项目合同段只会存在一个角色roleId
         SaveUserInfoByProject userInfo = baseMapper.selectRoleInfo(AuthUtil.getUserId(), contractId, projectId);
+        if (userInfo == null){
+            throw new ServiceException("请先分配当前用户对该合同段的角色信息");
+        }
         String roleId = userInfo.getRoleId();
 
         //获取当前用户角色与表单所属方关系信息
@@ -367,21 +366,20 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
             throw new ServiceException("当前用户角色未授权,请先分配角色查看元素表相对应的权限");
         }
 
-        //获取tableOwnerNumber
         List<String> tableOwnerNumbers = wbsTableOwnerRoleList.stream().map(WbsTableOwnerRole::getTableOwnerNumber).collect(Collectors.toList());
 
         List<String> tableOwnerList = null;
         if (StringUtils.isNotEmpty(tableOwner)) {
             String tableOwners = "";
-            if (tableOwner.equals("1")) { //此处加载字典owner_type 固定写死 施工质检1= 1 2 3
+            //此处加载字典owner_type 固定写死 施工质检1= 1 2 3 , 监理抽检2= 4 5 6
+            if (tableOwner.equals("1")) {
                 tableOwners = "1,2,3";
-            } else if (tableOwner.equals("2")) { //监理抽检2= 4 5 6
+            } else if (tableOwner.equals("2")) {
                 tableOwners = "4,5,6";
             }
             tableOwnerList = Func.toStrList(tableOwners);
         }
 
-        //根据tableOwnerList、tableOwnerNumber 获取当前节点下表单
         return baseMapper.selectWbsTreeContractList(tableOwnerNumbers, wbsTreeContract.getProjectId(), wbsTreeContract.getWbsId(),
                 wbsTreeContract.getContractId(), wbsTreeContract.getId(), wbsTreeContract.getContractIdRelation(), tableOwnerList);
 

+ 44 - 13
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreePrivateServiceImpl.java

@@ -2,6 +2,7 @@ package org.springblade.manager.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
 import org.springblade.common.utils.SnowFlakeUtil;
@@ -14,10 +15,7 @@ import org.springblade.manager.dto.WbsTreePrivateDTO2;
 import org.springblade.manager.dto.WbsTreePrivateDTO3;
 import org.springblade.manager.entity.*;
 
-import org.springblade.manager.mapper.ContractInfoMapper;
-import org.springblade.manager.mapper.WbsInfoMapper;
-import org.springblade.manager.mapper.WbsTreeContractMapper;
-import org.springblade.manager.mapper.WbsTreePrivateMapper;
+import org.springblade.manager.mapper.*;
 import org.springblade.manager.service.IWbsTreePrivateService;
 import org.springblade.manager.vo.WbsNodeTableVO;
 import org.springblade.manager.vo.WbsTreePrivateVO;
@@ -40,7 +38,6 @@ public class WbsTreePrivateServiceImpl extends BaseServiceImpl<WbsTreePrivateMap
 
     @Override
     public List<WbsTreePrivateVO> tree(String wbsId, String projectId) {
-        //获取wbsType
         WbsInfo wbsInfo = wbsInfoMapper.selectOne(Wrappers.<WbsInfo>query().lambda().eq(WbsInfo::getId, wbsId));
         if (wbsInfo != null) {
             return ForestNodeMerger.merge(baseMapper.tree(wbsId, projectId, wbsInfo.getWbsType()));
@@ -68,7 +65,7 @@ public class WbsTreePrivateServiceImpl extends BaseServiceImpl<WbsTreePrivateMap
             wbsTreePrivate.setStatus(1);
             Long pKeyId = SnowFlakeUtil.getId();
             wbsTreePrivate.setPKeyId(pKeyId);
-            //获取wbsType
+
             WbsInfo wbsInfo = wbsInfoMapper.selectById(wbsTreePrivate.getWbsId());
             wbsTreePrivate.setWbsType(String.valueOf(wbsInfo.getWbsType())); //质检=1 试验=2 计量=3 日志=4
 
@@ -78,7 +75,7 @@ public class WbsTreePrivateServiceImpl extends BaseServiceImpl<WbsTreePrivateMap
                 wbsTreePrivate.setAncestors(ancestors);
             }
 
-            //初始化排序sort
+            //初始化sort
             List<WbsTreePrivate> trees = baseMapper.selectList(Wrappers.<WbsTreePrivate>query().lambda()
                     .eq(WbsTreePrivate::getProjectId, wbsTreePrivate.getProjectId())
                     .eq(WbsTreePrivate::getParentId, wbsTreePrivate.getParentId())
@@ -92,7 +89,7 @@ public class WbsTreePrivateServiceImpl extends BaseServiceImpl<WbsTreePrivateMap
                 }
                 return true;
             }).collect(Collectors.toList());
-            //获取最大sort
+            //最大sort
             Optional<WbsTreePrivate> max = collect.stream().max(Comparator.comparingInt(WbsTreePrivate::getSort));
             Integer sort = 0;
             if (max.isPresent()) {
@@ -155,8 +152,6 @@ public class WbsTreePrivateServiceImpl extends BaseServiceImpl<WbsTreePrivateMap
             wbsTree.setSort(number);
             baseMapper.updateSortById(wbsTree.getPKeyId(), wbsTree.getSort());
 
-            //同步合同段节点排序
-            //获取projectId、id、wbsId
             WbsTreePrivate objPrivate = baseMapper.selectOne(Wrappers.<WbsTreePrivate>query().lambda().eq(WbsTreePrivate::getPKeyId, wbsTree.getPKeyId()));
             if (objPrivate != null) {
                 String projectId = objPrivate.getProjectId();
@@ -172,7 +167,6 @@ public class WbsTreePrivateServiceImpl extends BaseServiceImpl<WbsTreePrivateMap
             }
             number++;
         }
-
         return true;
     }
 
@@ -180,11 +174,9 @@ public class WbsTreePrivateServiceImpl extends BaseServiceImpl<WbsTreePrivateMap
     public boolean wbsTreePrivateTableSort(List<WbsTreePrivateDTO2> wbsTreeDTO) {
         int number = 1;
         for (WbsTreePrivateDTO2 wbsTreePrivate : wbsTreeDTO) {
-            //私有wbs树排序
             wbsTreePrivate.setSort(number);
             baseMapper.updateSortById2(wbsTreePrivate.getPKeyId(), wbsTreePrivate.getSort());
 
-            //获取projectId、id、wbsId
             WbsTreePrivate objPrivate = baseMapper.selectOne(Wrappers.<WbsTreePrivate>query().lambda().eq(WbsTreePrivate::getPKeyId, wbsTreePrivate.getPKeyId()));
             if (objPrivate != null) {
                 String projectId = objPrivate.getProjectId();
@@ -218,9 +210,48 @@ public class WbsTreePrivateServiceImpl extends BaseServiceImpl<WbsTreePrivateMap
     @Transactional(rollbackFor = Exception.class)
     public boolean updateBatchByPid(List<WbsTreePrivateDTO3> wbsTreePrivates) {
         try {
+            //修改当前私有项目元素表基础信息
             wbsTreePrivates.forEach(list -> {
                 baseMapper.updateBatchByPid(list);
             });
+
+            //修改当前项目下所有合同段中的元素表基础信息
+            String projectId = "";
+            String parentId = "";
+            List<WbsTreePrivate> wbsTreePrivateList = new ArrayList<>();
+            int number = 0;
+            for (WbsTreePrivateDTO3 wbsTreePrivate : wbsTreePrivates) {
+                WbsTreePrivate wbsTreePrivate1 = baseMapper.selectOne(Wrappers.<WbsTreePrivate>query().lambda().eq(WbsTreePrivate::getPKeyId, wbsTreePrivate.getPKeyId()));
+                wbsTreePrivateList.add(wbsTreePrivate1);
+                if (number == 0) {
+                    projectId = wbsTreePrivate1.getProjectId();
+                    parentId = String.valueOf(wbsTreePrivate1.getParentId());
+                    number = 1;
+                }
+            }
+            if (StringUtils.isNotEmpty(projectId) && StringUtils.isNotEmpty(parentId) && wbsTreePrivateList.size() > 0) {
+                //获取所有合同段ids
+                List<ContractInfo> contractInfos = contractInfoMapper.selectList(Wrappers.<ContractInfo>query().lambda().eq(ContractInfo::getPId, projectId));
+                if (contractInfos.size() > 0) {
+                    List<Long> contractInfoIds = contractInfos.stream().map(ContractInfo::getId).collect(Collectors.toList());
+                    for (Long contractInfoId : contractInfoIds) {
+                        for (WbsTreePrivate wbsTreePrivate : wbsTreePrivateList) {
+                            //判断当前合同段是否存在该元素表
+                            WbsTreeContract wbsTreeContract = wbsTreeContractMapper.selectOne(Wrappers.<WbsTreeContract>query().lambda()
+                                    .eq(WbsTreeContract::getContractId, contractInfoId)
+                                    .eq(WbsTreeContract::getProjectId, wbsTreePrivate.getProjectId())
+                                    .eq(WbsTreeContract::getId, wbsTreePrivate.getId())
+                                    .eq(WbsTreeContract::getStatus, 1)
+                                    .isNull(WbsTreeContract::getOldId) //不是客户端的复制或新增的节点
+                            );
+                            if (wbsTreeContract != null) {
+                                wbsTreeContractMapper.updateContractTablesInfo(contractInfoId, wbsTreePrivate);
+                            }
+                        }
+                    }
+                }
+            }
+
         } catch (Exception e) {
             throw new ServiceException("操作失败");
         }

+ 29 - 33
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeServiceImpl.java

@@ -2,7 +2,6 @@ package org.springblade.manager.service.impl;
 
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
-import lombok.SneakyThrows;
 import net.sourceforge.pinyin4j.PinyinHelper;
 import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
 import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
@@ -10,17 +9,17 @@ import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
 import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType;
 import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
 import org.apache.commons.lang.StringUtils;
-import org.jsoup.helper.DataUtil;
 import org.springblade.common.utils.SnowFlakeUtil;
 import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.redis.cache.BladeRedis;
 import org.springblade.core.secure.utils.AuthUtil;
-import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.constant.BladeConstant;
 import org.springblade.core.tool.node.ForestNodeMerger;
 import org.springblade.core.tool.utils.DateUtil;
 import org.springblade.core.tool.utils.Func;
 import org.springblade.core.tool.utils.StringPool;
 import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.manager.aop.AvoidRepeatableCommit;
 import org.springblade.manager.dto.FormElementDTO;
 import org.springblade.manager.dto.WbsTreeBatchImportDTO;
 import org.springblade.manager.dto.WbsTreeContractDTO;
@@ -32,6 +31,7 @@ import org.springblade.manager.mapper.*;
 import org.springblade.manager.service.IWbsFormElementService;
 import org.springblade.manager.service.IWbsTreeService;
 import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.manager.unit.RedisUtil;
 import org.springblade.manager.vo.*;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
@@ -57,6 +57,7 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
     private final WbsTreeContractMapper wbsTreeContractMapper;
     private final ContractInfoMapper contractInfoMapper;
     private final FormulaMapper formulaMapper;
+    private final BladeRedis bladeRedis;
 
     @Override
     public IPage<WbsTreeVO> selectWbsTreePage(IPage<WbsTreeVO> page, WbsTreeVO wbsTree) {
@@ -89,9 +90,7 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
     }
 
     private List<WbsTreeVO2> buildWbsTreeByStream(List<WbsTreeVO2> wbsTreeVO2s) {
-        //根节点
         List<WbsTreeVO2> list = wbsTreeVO2s.stream().filter(f -> f.getParentId() == 0L).collect(Collectors.toList());
-        //子节点分组
         Map<Long, List<WbsTreeVO2>> map = wbsTreeVO2s.stream().collect(Collectors.groupingBy(WbsTreeVO2::getParentId));
         this.recursionFnTree(list, map);
         return list;
@@ -102,9 +101,7 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
             List<WbsTreeVO2> childrenList = map.get(wbsTreeVO2.getId());
             wbsTreeVO2.setChildren(childrenList);
             if (childrenList != null && childrenList.size() > 0) {
-                //存在子级
                 wbsTreeVO2.setHasChildren(true);
-                //递归查询
                 recursionFnTree(childrenList, map);
             }
         }
@@ -132,8 +129,7 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
         wbsTree.setStatus(1);
         wbsTree.setIsDeleted(BladeConstant.DB_NOT_DELETED);
 
-        //默认排序
-        //获取当前同级节点List
+        //当前同级节点
         if (wbsTree.getId() == null) {
             List<WbsTree> trees = baseMapper.selectList(Wrappers.<WbsTree>query().lambda()
                     .eq(WbsTree::getParentId, wbsTree.getParentId())
@@ -145,7 +141,7 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
                 }
                 return true;
             }).collect(Collectors.toList());
-            //获取最大sort
+            //最大sort
             Optional<WbsTree> max = collect.stream().max(Comparator.comparingInt(WbsTree::getSort));
             Integer sort = 0;
             if (max.isPresent()) {
@@ -402,10 +398,7 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
     @Override
     public boolean removeTableById(String id) {
         Integer integer = wbsTreeMapper.removeTableById(id);
-        if (integer > 0) {
-            return true;
-        }
-        return false;
+        return integer > 0;
     }
 
     @Override
@@ -418,6 +411,7 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
             if (escapeChar != null) {
                 elements.setEAllowDeviation(escapeChar);
             }
+
             //是否保存过公式
             List<Formula> formulas = formulaMapper.selectList(Wrappers.<Formula>query().lambda()
                     .eq(Formula::getElementId, elements.getId())
@@ -510,7 +504,7 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
                         wbsTreeZi.setSort(sortNumber);
                         sortNumber++;
 
-                        //结果集
+                        //返回结果集
                         allNodeData.add(wbsTreeZi);
 
                     } else {
@@ -535,7 +529,8 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
                     && tree.getWbsId().equals(wbsTreeZi.getWbsId())
                     && tree.getDeptCategory().equals(wbsTreeZi.getDeptCategory())
                     && tree.getProjectNodeId().equals(wbsTreeZi.getProjectNodeId())) {
-                //判断上级节点不同,当前节点名相同的tree选择问题,例如:第X跨的上级有上部构造现场浇筑、上部构造预制和安装,导致下方新增重复节点判断时找到的是前一个
+                //判断上级节点不同,当前节点名相同的tree选择问题
+                //例如:第X跨的上级有上部构造现场浇筑、上部构造预制和安装,导致下方新增重复节点判断时找到的是前一个
                 if (!tree.getAncestors().equals(wbsTreeZi.getAncestors())) {
                     wbsTrees.add(tree);
                     break;
@@ -597,7 +592,6 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
     @Override
     public List<Map> findProjectAndContractList(Long userId) {
         List<SaveUserInfoByProjectVO2> list = baseMapper.selectListByUserId(userId);
-        //用户项目合同段角色返回Map
         Map<String, List<SaveUserInfoByProjectVO2>> collect = list.stream().collect(Collectors.groupingBy(SaveUserInfoByProjectVO2::getProjectName));
         return collect.entrySet().stream().map(entity -> {
             Map<String, Object> resultMap = new HashMap<>();
@@ -619,7 +613,7 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
             wbsTree.setSort(number);
             baseMapper.updateSortById(wbsTree.getId(), wbsTree.getSort());
 
-            //同步项目私有节点排序
+            //私有节点
             wbsTreePrivateMapper.updateSortById3(wbsTree.getId(), wbsTree.getSort());
             number++;
         }
@@ -646,6 +640,7 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    //@AvoidRepeatableCommit //aop
     public Boolean submitWbsTreeInProject1(WbsTreeContractDTO pawDTO) {
         if (StringUtils.isEmpty(pawDTO.getWbsId())) {
             throw new ServiceException("请正确选择一个wbs模板");
@@ -653,7 +648,6 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
         String wbsTreeIds = pawDTO.getWbsTreeIds();
         String[] ids = wbsTreeIds.split(",");
         List<String> idList = Arrays.asList(ids);
-        //入参ids
         List<String> idList1 = idList.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
 
         //当前项目存在的引用ids
@@ -683,9 +677,9 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
             });
         }
 
-        //需要新增节点ids
+        //新增节点ids
         List<String> saveIds = idList1.stream().filter(f -> !idList2.contains(f)).collect(Collectors.toList());
-        //需要删除节点ids
+        //删除节点ids
         List<String> delIds = idList2.stream().filter(f -> !idList1.contains(f)).collect(Collectors.toList());
 
         if (saveIds.size() == 0 && delIds.size() == 0) {
@@ -991,6 +985,12 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
 
             //2.新增
             if (saveIds.size() > 0) {
+                //获取Redis的Value
+                String redisValue = bladeRedis.get("submit-wbs-project:" + pawDTO.getProjectId());
+                if (StringUtils.isNotEmpty(redisValue) && redisValue.equals("1")) {
+                    throw new ServiceException("请勿重复提交,请60秒后再次尝试!");
+                }
+
                 //数据初始化节点-引用公有
                 List<WbsTreePrivate> insertData = new ArrayList<>();
                 //数据初始化节点-引用私有
@@ -1056,8 +1056,11 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
                     projectInfoMapper.updateTemplateIdById(pawDTO.getProjectId(), pawDTO.getPrimaryKeyId(), "private");
                 }
 
+                //设置Redis的Key、value
+                bladeRedis.set("submit-wbs-project:" + pawDTO.getProjectId(), "1");
+                //设置过期时间
+                bladeRedis.expire("submit-wbs-project:" + pawDTO.getProjectId(), 60);
             }
-
         }
         return true;
     }
@@ -1087,7 +1090,6 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
 
     private WbsTreePrivate getWbsTreePrivate1(WbsTree wbsTree, WbsTreeContractDTO pawDTO) {
         WbsTreePrivate wbsTreePrivate = new WbsTreePrivate();
-        //数据初始化
         Long snowId = SnowFlakeUtil.getId();
         wbsTreePrivate.setPKeyId(snowId);
         wbsTreePrivate.setId(wbsTree.getId());
@@ -1127,7 +1129,6 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
 
     private WbsTreePrivate getWbsTreePrivate2(WbsTreePrivate wbsTree, WbsTreeContractDTO pawDTO) {
         WbsTreePrivate wbsTreePrivate = new WbsTreePrivate();
-        //数据初始化
         Long snowId = SnowFlakeUtil.getId();
         wbsTreePrivate.setPKeyId(snowId);
         wbsTreePrivate.setId(wbsTree.getId());
@@ -1170,7 +1171,6 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
 
     private WbsTreeContract getWbsTreeContract(WbsTreePrivate wbsTree, WbsTreeContractDTO pawDTO) {
         WbsTreeContract wbsTreeContract = new WbsTreeContract();
-        //数据初始化-施工合同
         Long snowId = SnowFlakeUtil.getId();
         wbsTreeContract.setPKeyId(snowId);
         wbsTreeContract.setId(wbsTree.getId());
@@ -1217,11 +1217,10 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
     @Override
     public WbsTreeAllListVO findWbsTreeList(Integer type) {
         WbsTreeAllListVO wbsTreeAllListVO = new WbsTreeAllListVO();
-        //公有树
+
         List<WbsInfo> wbsInfos = wbsInfoMapper.selectAll(type);
         wbsTreeAllListVO.setWbsInfos(wbsInfos);
 
-        //私有树
         List<WbsTreePrivate> wbsTreePrivates = wbsTreePrivateMapper.selectAll(type);
         wbsTreeAllListVO.setWbsTreePrivates(wbsTreePrivates);
 
@@ -1252,17 +1251,16 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
             throw new ServiceException("表名长度错误,输入范围1-100个字符长度");
         }
 
-        //初始化表名
         //String realName = getFirstSpell(deptName);
         Long id = SnowFlakeUtil.getId();
         String newTableName = "m_" + DateUtil.time() + "_" + id;
         formElementDTO.setInitTableName(newTableName);
 
-        //创建元素
+        //新增
         boolean b1 = submit2(formElementDTO);
+
         List<WbsFormElement> elementList = formElementDTO.getElementList();
         int i = 1;
-
         for (WbsFormElement wbsFormElement : elementList) {
             if ((wbsFormElement.getEType() == 1 || wbsFormElement.getEType() == 6 || wbsFormElement.getEType() == 7)
                     && (wbsFormElement.getELength() > 1000 || wbsFormElement.getELength() < 10)) {
@@ -1276,7 +1274,6 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
                 throw new ServiceException("请输入正确的长度,该类型范围为10-65");
             }
 
-            //当前元素表中元素对应实体表唯一key值、fId
             wbsFormElement.setEKey("key_" + i++);
             wbsFormElement.setFId(String.valueOf(formElementDTO.getId()));
         }
@@ -1284,10 +1281,9 @@ public class WbsTreeServiceImpl extends BaseServiceImpl<WbsTreeMapper, WbsTree>
         //新增元素
         boolean b2 = wbsFormElementService.saveBatch(elementList, 10000);
 
-        //找到当前元素表中所有元素
         List<WbsFormElement> list = wbsFormElementService.selectElementListByFid(String.valueOf(formElementDTO.getId()));
 
-        //初始化实体表
+        //新增实体表
         Boolean b3 = wbsFormElementService.initTable(list, newTableName);
 
         if (b1 && b2 && b3) {

+ 122 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/unit/ProtoStuffSerializerUtil.java

@@ -0,0 +1,122 @@
+package org.springblade.manager.unit;
+
+import com.dyuproject.protostuff.LinkedBuffer;
+import com.dyuproject.protostuff.ProtostuffIOUtil;
+import com.dyuproject.protostuff.Schema;
+import com.dyuproject.protostuff.runtime.RuntimeSchema;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * ProtoStuffSerializerUtil
+ */
+public class ProtoStuffSerializerUtil {
+    /**
+     * 序列化对象
+     *
+     * @param obj
+     * @return
+     */
+    public static <T> byte[] serialize(T obj) {
+        if (obj == null) {
+            throw new RuntimeException("序列化对象(" + obj + ")!");
+        }
+        @SuppressWarnings("unchecked")
+        Schema<T> schema = (Schema<T>) RuntimeSchema.getSchema(obj.getClass());
+        LinkedBuffer buffer = LinkedBuffer.allocate(1024 * 1024);
+        byte[] protostuff = null;
+        try {
+            protostuff = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
+        } catch (Exception e) {
+            throw new RuntimeException("序列化(" + obj.getClass() + ")对象(" + obj + ")发生异常!", e);
+        } finally {
+            buffer.clear();
+        }
+        return protostuff;
+    }
+
+    /**
+     * 反序列化对象
+     *
+     * @param paramArrayOfByte
+     * @param targetClass
+     * @return
+     */
+    public static <T> T deserialize(byte[] paramArrayOfByte, Class<T> targetClass) {
+        if (paramArrayOfByte == null || paramArrayOfByte.length == 0) {
+            throw new RuntimeException("反序列化对象发生异常,byte序列为空!");
+        }
+        T instance = null;
+        try {
+            instance = targetClass.newInstance();
+        } catch (InstantiationException e1) {
+            throw new RuntimeException("反序列化过程中依据类型创建对象失败!", e1);
+        } catch (IllegalAccessException e2) {
+            throw new RuntimeException("反序列化过程中依据类型创建对象失败!", e2);
+        }
+        Schema<T> schema = RuntimeSchema.getSchema(targetClass);
+        ProtostuffIOUtil.mergeFrom(paramArrayOfByte, instance, schema);
+        return instance;
+    }
+
+    /**
+     * 序列化列表
+     *
+     * @param objList
+     * @return
+     */
+    public static <T> byte[] serializeList(List<T> objList) {
+        if (objList == null || objList.isEmpty()) {
+            throw new RuntimeException("序列化对象列表(" + objList + ")参数异常!");
+        }
+        @SuppressWarnings("unchecked")
+        Schema<T> schema = (Schema<T>) RuntimeSchema.getSchema(objList.get(0).getClass());
+        LinkedBuffer buffer = LinkedBuffer.allocate(1024 * 1024);
+        byte[] protostuff = null;
+        ByteArrayOutputStream bos = null;
+        try {
+            bos = new ByteArrayOutputStream();
+            ProtostuffIOUtil.writeListTo(bos, objList, schema, buffer);
+            protostuff = bos.toByteArray();
+        } catch (Exception e) {
+            throw new RuntimeException("序列化对象列表(" + objList + ")发生异常!", e);
+        } finally {
+            buffer.clear();
+            try {
+                if (bos != null) {
+                    bos.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+
+        return protostuff;
+    }
+
+    /**
+     * 反序列化列表
+     *
+     * @param paramArrayOfByte
+     * @param targetClass
+     * @return
+     */
+    public static <T> List<T> deserializeList(byte[] paramArrayOfByte, Class<T> targetClass) {
+        if (paramArrayOfByte == null || paramArrayOfByte.length == 0) {
+            throw new RuntimeException("反序列化对象发生异常,byte序列为空!");
+        }
+
+        Schema<T> schema = RuntimeSchema.getSchema(targetClass);
+        List<T> result = null;
+        try {
+            result = ProtostuffIOUtil.parseListFrom(new ByteArrayInputStream(paramArrayOfByte), schema);
+        } catch (IOException e) {
+            throw new RuntimeException("反序列化对象列表发生异常!", e);
+        }
+        return result;
+    }
+
+}

+ 1366 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/unit/RedisUtil.java

@@ -0,0 +1,1366 @@
+package org.springblade.manager.unit;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.connection.DataType;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.core.Cursor;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class RedisUtil {
+
+    @Autowired
+    private StringRedisTemplate redisTemplate;
+
+    /** -------------------key相关操作--------------------- */
+
+    /**
+     * 删除key
+     *
+     * @param key
+     */
+    public void delete(String key) {
+        redisTemplate.delete(key);
+    }
+
+    /**
+     * 批量删除key
+     *
+     * @param keys
+     */
+    public void delete(Collection<String> keys) {
+        redisTemplate.delete(keys);
+    }
+
+    /**
+     * 序列化key
+     *
+     * @param key
+     * @return
+     */
+    public byte[] dump(String key) {
+        return redisTemplate.dump(key);
+    }
+
+    /**
+     * 是否存在key
+     *
+     * @param key
+     * @return
+     */
+    public Boolean hasKey(String key) {
+        return redisTemplate.hasKey(key);
+    }
+
+    /**
+     * 设置过期时间
+     *
+     * @param key
+     * @param timeout
+     * @param unit
+     * @return
+     */
+    public Boolean expire(String key, long timeout, TimeUnit unit) {
+        return redisTemplate.expire(key, timeout, unit);
+    }
+
+    /**
+     * 设置过期时间
+     *
+     * @param key
+     * @param date
+     * @return
+     */
+    public Boolean expireAt(String key, Date date) {
+        return redisTemplate.expireAt(key, date);
+    }
+
+    /**
+     * 查找匹配的key
+     *
+     * @param pattern
+     * @return
+     */
+    public Set<String> keys(String pattern) {
+        return redisTemplate.keys(pattern);
+    }
+
+    /**
+     * 将当前数据库的 key 移动到给定的数据库 db 当中
+     *
+     * @param key
+     * @param dbIndex
+     * @return
+     */
+    public Boolean move(String key, int dbIndex) {
+        return redisTemplate.move(key, dbIndex);
+    }
+
+    /**
+     * 移除 key 的过期时间,key 将持久保持
+     *
+     * @param key
+     * @return
+     */
+    public Boolean persist(String key) {
+        return redisTemplate.persist(key);
+    }
+
+    /**
+     * 返回 key 的剩余的过期时间
+     *
+     * @param key
+     * @param unit
+     * @return
+     */
+    public Long getExpire(String key, TimeUnit unit) {
+        return redisTemplate.getExpire(key, unit);
+    }
+
+    /**
+     * 返回 key 的剩余的过期时间
+     *
+     * @param key
+     * @return
+     */
+    public Long getExpire(String key) {
+        return redisTemplate.getExpire(key);
+    }
+
+    /**
+     * 从当前数据库中随机返回一个 key
+     *
+     * @return
+     */
+    public String randomKey() {
+        return redisTemplate.randomKey();
+    }
+
+    /**
+     * 修改 key 的名称
+     *
+     * @param oldKey
+     * @param newKey
+     */
+    public void rename(String oldKey, String newKey) {
+        redisTemplate.rename(oldKey, newKey);
+    }
+
+    /**
+     * 仅当 newkey 不存在时,将 oldKey 改名为 newkey
+     *
+     * @param oldKey
+     * @param newKey
+     * @return
+     */
+    public Boolean renameIfAbsent(String oldKey, String newKey) {
+        return redisTemplate.renameIfAbsent(oldKey, newKey);
+    }
+
+    /**
+     * 返回 key 所储存的值的类型
+     *
+     * @param key
+     * @return
+     */
+    public DataType type(String key) {
+        return redisTemplate.type(key);
+    }
+
+    /** -------------------string相关操作--------------------- */
+
+    /**
+     * 设置指定 key 的值
+     *
+     * @param key
+     * @param value
+     */
+    public void set(String key, String value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 获取指定 key 的值
+     *
+     * @param key
+     * @return
+     */
+    public String get(String key) {
+        return redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 返回 key 中字符串值的子字符
+     *
+     * @param key
+     * @param start
+     * @param end
+     * @return
+     */
+    public String getRange(String key, long start, long end) {
+        return redisTemplate.opsForValue().get(key, start, end);
+    }
+
+    /**
+     * 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public String getAndSet(String key, String value) {
+        return redisTemplate.opsForValue().getAndSet(key, value);
+    }
+
+    /**
+     * 对 key 所储存的字符串值,获取指定偏移量上的位(bit)
+     *
+     * @param key
+     * @param offset
+     * @return
+     */
+    public Boolean getBit(String key, long offset) {
+        return redisTemplate.opsForValue().getBit(key, offset);
+    }
+
+    /**
+     * 批量获取
+     *
+     * @param keys
+     * @return
+     */
+    public List<String> multiGet(Collection<String> keys) {
+        return redisTemplate.opsForValue().multiGet(keys);
+    }
+
+    /**
+     * 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value
+     *
+     * @param key   位置
+     * @param value 值,true1, false0
+     * @return
+     */
+    public boolean setBit(String key, long offset, boolean value) {
+        return redisTemplate.opsForValue().setBit(key, offset, value);
+    }
+
+    /**
+     * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
+     *
+     * @param key
+     * @param value
+     * @param timeout 过期时间
+     * @param unit    时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES
+     *                秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
+     */
+    public void setEx(String key, String value, long timeout, TimeUnit unit) {
+        redisTemplate.opsForValue().set(key, value, timeout, unit);
+    }
+
+    /**
+     * 只有在 key 不存在时设置 key 的值
+     *
+     * @param key
+     * @param value
+     * @return 之前已经存在返回false, 不存在返回true
+     */
+    public boolean setIfAbsent(String key, String value) {
+        return redisTemplate.opsForValue().setIfAbsent(key, value);
+    }
+
+    /**
+     * 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
+     *
+     * @param key
+     * @param value
+     * @param offset 从指定位置开始覆写
+     */
+    public void setRange(String key, String value, long offset) {
+        redisTemplate.opsForValue().set(key, value, offset);
+    }
+
+    /**
+     * 获取字符串的长度
+     *
+     * @param key
+     * @return
+     */
+    public Long size(String key) {
+        return redisTemplate.opsForValue().size(key);
+    }
+
+    /**
+     * 批量添加
+     *
+     * @param maps
+     */
+    public void multiSet(Map<String, String> maps) {
+        redisTemplate.opsForValue().multiSet(maps);
+    }
+
+    /**
+     * 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
+     *
+     * @param maps
+     * @return 之前已经存在返回false, 不存在返回true
+     */
+    public boolean multiSetIfAbsent(Map<String, String> maps) {
+        return redisTemplate.opsForValue().multiSetIfAbsent(maps);
+    }
+
+    /**
+     * 增加(自增长), 负数则为自减
+     *
+     * @param key
+     * @param increment
+     * @return
+     */
+    public Long incrBy(String key, long increment) {
+        return redisTemplate.opsForValue().increment(key, increment);
+    }
+
+    /**
+     * 增加(自增长), 负数则为自减
+     *
+     * @param key
+     * @param increment
+     * @return
+     */
+    public Double incrByFloat(String key, double increment) {
+        return redisTemplate.opsForValue().increment(key, increment);
+    }
+
+    /**
+     * 追加到末尾
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public Integer append(String key, String value) {
+        return redisTemplate.opsForValue().append(key, value);
+    }
+
+    /** -------------------hash相关操作------------------------- */
+
+    /**
+     * 获取存储在哈希表中指定字段的值
+     *
+     * @param key
+     * @param field
+     * @return
+     */
+    public Object hGet(String key, String field) {
+        return redisTemplate.opsForHash().get(key, field);
+    }
+
+    /**
+     * 获取所有给定字段的值
+     *
+     * @param key
+     * @return
+     */
+    public Map<Object, Object> hGetAll(String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 获取所有给定字段的值
+     *
+     * @param key
+     * @param fields
+     * @return
+     */
+    public List<Object> hMultiGet(String key, Collection<Object> fields) {
+        return redisTemplate.opsForHash().multiGet(key, fields);
+    }
+
+    public void hPut(String key, String hashKey, String value) {
+        redisTemplate.opsForHash().put(key, hashKey, value);
+    }
+
+    public void hPutAll(String key, Map<String, String> maps) {
+        redisTemplate.opsForHash().putAll(key, maps);
+    }
+
+    /**
+     * 仅当hashKey不存在时才设置
+     *
+     * @param key
+     * @param hashKey
+     * @param value
+     * @return
+     */
+    public Boolean hPutIfAbsent(String key, String hashKey, String value) {
+        return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
+    }
+
+    /**
+     * 删除一个或多个哈希表字段
+     *
+     * @param key
+     * @param fields
+     * @return
+     */
+    public Long hDelete(String key, Object... fields) {
+        return redisTemplate.opsForHash().delete(key, fields);
+    }
+
+    /**
+     * 查看哈希表 key 中,指定的字段是否存在
+     *
+     * @param key
+     * @param field
+     * @return
+     */
+    public boolean hExists(String key, String field) {
+        return redisTemplate.opsForHash().hasKey(key, field);
+    }
+
+    /**
+     * 为哈希表 key 中的指定字段的整数值加上增量 increment
+     *
+     * @param key
+     * @param field
+     * @param increment
+     * @return
+     */
+    public Long hIncrBy(String key, Object field, long increment) {
+        return redisTemplate.opsForHash().increment(key, field, increment);
+    }
+
+    /**
+     * 为哈希表 key 中的指定字段的整数值加上增量 increment
+     *
+     * @param key
+     * @param field
+     * @param delta
+     * @return
+     */
+    public Double hIncrByFloat(String key, Object field, double delta) {
+        return redisTemplate.opsForHash().increment(key, field, delta);
+    }
+
+    /**
+     * 获取所有哈希表中的字段
+     *
+     * @param key
+     * @return
+     */
+    public Set<Object> hKeys(String key) {
+        return redisTemplate.opsForHash().keys(key);
+    }
+
+    /**
+     * 获取哈希表中字段的数量
+     *
+     * @param key
+     * @return
+     */
+    public Long hSize(String key) {
+        return redisTemplate.opsForHash().size(key);
+    }
+
+    /**
+     * 获取哈希表中所有值
+     *
+     * @param key
+     * @return
+     */
+    public List<Object> hValues(String key) {
+        return redisTemplate.opsForHash().values(key);
+    }
+
+    /**
+     * 迭代哈希表中的键值对
+     *
+     * @param key
+     * @param options
+     * @return
+     */
+    public Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {
+        return redisTemplate.opsForHash().scan(key, options);
+    }
+
+    /** ------------------------list相关操作---------------------------- */
+
+    /**
+     * 通过索引获取列表中的元素
+     *
+     * @param key
+     * @param index
+     * @return
+     */
+    public String lIndex(String key, long index) {
+        return redisTemplate.opsForList().index(key, index);
+    }
+
+    /**
+     * 获取列表指定范围内的元素
+     *
+     * @param key
+     * @param start 开始位置, 0是开始位置
+     * @param end   结束位置, -1返回所有
+     * @return
+     */
+    public List<String> lRange(String key, long start, long end) {
+        return redisTemplate.opsForList().range(key, start, end);
+    }
+
+    /**
+     * 存储在list头部
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public Long lLeftPush(String key, String value) {
+        return redisTemplate.opsForList().leftPush(key, value);
+    }
+
+    /**
+     * @param key
+     * @param value
+     * @return
+     */
+    public Long lLeftPushAll(String key, String... value) {
+        return redisTemplate.opsForList().leftPushAll(key, value);
+    }
+
+    /**
+     * @param key
+     * @param value
+     * @return
+     */
+    public Long lLeftPushAll(String key, Collection<String> value) {
+        return redisTemplate.opsForList().leftPushAll(key, value);
+    }
+
+    /**
+     * 当list存在的时候才加入
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public Long lLeftPushIfPresent(String key, String value) {
+        return redisTemplate.opsForList().leftPushIfPresent(key, value);
+    }
+
+    /**
+     * 如果pivot存在,再pivot前面添加
+     *
+     * @param key
+     * @param pivot
+     * @param value
+     * @return
+     */
+    public Long lLeftPush(String key, String pivot, String value) {
+        return redisTemplate.opsForList().leftPush(key, pivot, value);
+    }
+
+    /**
+     * @param key
+     * @param value
+     * @return
+     */
+    public Long lRightPush(String key, String value) {
+        return redisTemplate.opsForList().rightPush(key, value);
+    }
+
+    /**
+     * @param key
+     * @param value
+     * @return
+     */
+    public Long lRightPushAll(String key, String... value) {
+        return redisTemplate.opsForList().rightPushAll(key, value);
+    }
+
+    /**
+     * @param key
+     * @param value
+     * @return
+     */
+    public Long lRightPushAll(String key, Collection<String> value) {
+        return redisTemplate.opsForList().rightPushAll(key, value);
+    }
+
+    /**
+     * 为已存在的列表添加值
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public Long lRightPushIfPresent(String key, String value) {
+        return redisTemplate.opsForList().rightPushIfPresent(key, value);
+    }
+
+    /**
+     * 在pivot元素的右边添加值
+     *
+     * @param key
+     * @param pivot
+     * @param value
+     * @return
+     */
+    public Long lRightPush(String key, String pivot, String value) {
+        return redisTemplate.opsForList().rightPush(key, pivot, value);
+    }
+
+    /**
+     * 通过索引设置列表元素的值
+     *
+     * @param key
+     * @param index 位置
+     * @param value
+     */
+    public void lSet(String key, long index, String value) {
+        redisTemplate.opsForList().set(key, index, value);
+    }
+
+    /**
+     * 移出并获取列表的第一个元素
+     *
+     * @param key
+     * @return 删除的元素
+     */
+    public String lLeftPop(String key) {
+        return redisTemplate.opsForList().leftPop(key);
+    }
+
+    /**
+     * 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
+     *
+     * @param key
+     * @param timeout 等待时间
+     * @param unit    时间单位
+     * @return
+     */
+    public String lBLeftPop(String key, long timeout, TimeUnit unit) {
+        return redisTemplate.opsForList().leftPop(key, timeout, unit);
+    }
+
+    /**
+     * 移除并获取列表最后一个元素
+     *
+     * @param key
+     * @return 删除的元素
+     */
+    public String lRightPop(String key) {
+        return redisTemplate.opsForList().rightPop(key);
+    }
+
+    /**
+     * 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
+     *
+     * @param key
+     * @param timeout 等待时间
+     * @param unit    时间单位
+     * @return
+     */
+    public String lBRightPop(String key, long timeout, TimeUnit unit) {
+        return redisTemplate.opsForList().rightPop(key, timeout, unit);
+    }
+
+    /**
+     * 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
+     *
+     * @param sourceKey
+     * @param destinationKey
+     * @return
+     */
+    public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
+        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
+                destinationKey);
+    }
+
+    /**
+     * 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
+     *
+     * @param sourceKey
+     * @param destinationKey
+     * @param timeout
+     * @param unit
+     * @return
+     */
+    public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,
+                                        long timeout, TimeUnit unit) {
+        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
+                destinationKey, timeout, unit);
+    }
+
+    /**
+     * 删除集合中值等于value得元素
+     *
+     * @param key
+     * @param index index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素;
+     *              index<0, 从尾部开始删除第一个值等于value的元素;
+     * @param value
+     * @return
+     */
+    public Long lRemove(String key, long index, String value) {
+        return redisTemplate.opsForList().remove(key, index, value);
+    }
+
+    /**
+     * 裁剪list
+     *
+     * @param key
+     * @param start
+     * @param end
+     */
+    public void lTrim(String key, long start, long end) {
+        redisTemplate.opsForList().trim(key, start, end);
+    }
+
+    /**
+     * 获取列表长度
+     *
+     * @param key
+     * @return
+     */
+    public Long lLen(String key) {
+        return redisTemplate.opsForList().size(key);
+    }
+
+    /** --------------------set相关操作-------------------------- */
+
+    /**
+     * set添加元素
+     *
+     * @param key
+     * @param values
+     * @return
+     */
+    public Long sAdd(String key, String... values) {
+        return redisTemplate.opsForSet().add(key, values);
+    }
+
+    /**
+     * set移除元素
+     *
+     * @param key
+     * @param values
+     * @return
+     */
+    public Long sRemove(String key, Object... values) {
+        return redisTemplate.opsForSet().remove(key, values);
+    }
+
+    /**
+     * 移除并返回集合的一个随机元素
+     *
+     * @param key
+     * @return
+     */
+    public String sPop(String key) {
+        return redisTemplate.opsForSet().pop(key);
+    }
+
+    /**
+     * 将元素value从一个集合移到另一个集合
+     *
+     * @param key
+     * @param value
+     * @param destKey
+     * @return
+     */
+    public Boolean sMove(String key, String value, String destKey) {
+        return redisTemplate.opsForSet().move(key, value, destKey);
+    }
+
+    /**
+     * 获取集合的大小
+     *
+     * @param key
+     * @return
+     */
+    public Long sSize(String key) {
+        return redisTemplate.opsForSet().size(key);
+    }
+
+    /**
+     * 判断集合是否包含value
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public Boolean sIsMember(String key, Object value) {
+        return redisTemplate.opsForSet().isMember(key, value);
+    }
+
+    /**
+     * 获取两个集合的交集
+     *
+     * @param key
+     * @param otherKey
+     * @return
+     */
+    public Set<String> sIntersect(String key, String otherKey) {
+        return redisTemplate.opsForSet().intersect(key, otherKey);
+    }
+
+    /**
+     * 获取key集合与多个集合的交集
+     *
+     * @param key
+     * @param otherKeys
+     * @return
+     */
+    public Set<String> sIntersect(String key, Collection<String> otherKeys) {
+        return redisTemplate.opsForSet().intersect(key, otherKeys);
+    }
+
+    /**
+     * key集合与otherKey集合的交集存储到destKey集合中
+     *
+     * @param key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long sIntersectAndStore(String key, String otherKey, String destKey) {
+        return redisTemplate.opsForSet().intersectAndStore(key, otherKey,
+                destKey);
+    }
+
+    /**
+     * key集合与多个集合的交集存储到destKey集合中
+     *
+     * @param key
+     * @param otherKeys
+     * @param destKey
+     * @return
+     */
+    public Long sIntersectAndStore(String key, Collection<String> otherKeys,
+                                   String destKey) {
+        return redisTemplate.opsForSet().intersectAndStore(key, otherKeys,
+                destKey);
+    }
+
+    /**
+     * 获取两个集合的并集
+     *
+     * @param key
+     * @param otherKeys
+     * @return
+     */
+    public Set<String> sUnion(String key, String otherKeys) {
+        return redisTemplate.opsForSet().union(key, otherKeys);
+    }
+
+    /**
+     * 获取key集合与多个集合的并集
+     *
+     * @param key
+     * @param otherKeys
+     * @return
+     */
+    public Set<String> sUnion(String key, Collection<String> otherKeys) {
+        return redisTemplate.opsForSet().union(key, otherKeys);
+    }
+
+    /**
+     * key集合与otherKey集合的并集存储到destKey中
+     *
+     * @param key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long sUnionAndStore(String key, String otherKey, String destKey) {
+        return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
+    }
+
+    /**
+     * key集合与多个集合的并集存储到destKey中
+     *
+     * @param key
+     * @param otherKeys
+     * @param destKey
+     * @return
+     */
+    public Long sUnionAndStore(String key, Collection<String> otherKeys,
+                               String destKey) {
+        return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
+    }
+
+    /**
+     * 获取两个集合的差集
+     *
+     * @param key
+     * @param otherKey
+     * @return
+     */
+    public Set<String> sDifference(String key, String otherKey) {
+        return redisTemplate.opsForSet().difference(key, otherKey);
+    }
+
+    /**
+     * 获取key集合与多个集合的差集
+     *
+     * @param key
+     * @param otherKeys
+     * @return
+     */
+    public Set<String> sDifference(String key, Collection<String> otherKeys) {
+        return redisTemplate.opsForSet().difference(key, otherKeys);
+    }
+
+    /**
+     * key集合与otherKey集合的差集存储到destKey中
+     *
+     * @param key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long sDifference(String key, String otherKey, String destKey) {
+        return redisTemplate.opsForSet().differenceAndStore(key, otherKey,
+                destKey);
+    }
+
+    /**
+     * key集合与多个集合的差集存储到destKey中
+     *
+     * @param key
+     * @param otherKeys
+     * @param destKey
+     * @return
+     */
+    public Long sDifference(String key, Collection<String> otherKeys,
+                            String destKey) {
+        return redisTemplate.opsForSet().differenceAndStore(key, otherKeys,
+                destKey);
+    }
+
+    /**
+     * 获取集合所有元素
+     *
+     * @param key
+     * @return
+     */
+    public Set<String> setMembers(String key) {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 随机获取集合中的一个元素
+     *
+     * @param key
+     * @return
+     */
+    public String sRandomMember(String key) {
+        return redisTemplate.opsForSet().randomMember(key);
+    }
+
+    /**
+     * 随机获取集合中count个元素
+     *
+     * @param key
+     * @param count
+     * @return
+     */
+    public List<String> sRandomMembers(String key, long count) {
+        return redisTemplate.opsForSet().randomMembers(key, count);
+    }
+
+    /**
+     * 随机获取集合中count个元素并且去除重复的
+     *
+     * @param key
+     * @param count
+     * @return
+     */
+    public Set<String> sDistinctRandomMembers(String key, long count) {
+        return redisTemplate.opsForSet().distinctRandomMembers(key, count);
+    }
+
+    /**
+     * @param key
+     * @param options
+     * @return
+     */
+    public Cursor<String> sScan(String key, ScanOptions options) {
+        return redisTemplate.opsForSet().scan(key, options);
+    }
+
+    /**------------------zSet相关操作--------------------------------*/
+
+    /**
+     * 添加元素,有序集合是按照元素的score值由小到大排列
+     *
+     * @param key
+     * @param value
+     * @param score
+     * @return
+     */
+    public Boolean zAdd(String key, String value, double score) {
+        return redisTemplate.opsForZSet().add(key, value, score);
+    }
+
+    /**
+     * @param key
+     * @param values
+     * @return
+     */
+    public Long zAdd(String key, Set<TypedTuple<String>> values) {
+        return redisTemplate.opsForZSet().add(key, values);
+    }
+
+    /**
+     * @param key
+     * @param values
+     * @return
+     */
+    public Long zRemove(String key, Object... values) {
+        return redisTemplate.opsForZSet().remove(key, values);
+    }
+
+    /**
+     * 增加元素的score值,并返回增加后的值
+     *
+     * @param key
+     * @param value
+     * @param delta
+     * @return
+     */
+    public Double zIncrementScore(String key, String value, double delta) {
+        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
+    }
+
+    /**
+     * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
+     *
+     * @param key
+     * @param value
+     * @return 0表示第一位
+     */
+    public Long zRank(String key, Object value) {
+        return redisTemplate.opsForZSet().rank(key, value);
+    }
+
+    /**
+     * 返回元素在集合的排名,按元素的score值由大到小排列
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public Long zReverseRank(String key, Object value) {
+        return redisTemplate.opsForZSet().reverseRank(key, value);
+    }
+
+    /**
+     * 获取集合的元素, 从小到大排序
+     *
+     * @param key
+     * @param start 开始位置
+     * @param end   结束位置, -1查询所有
+     * @return
+     */
+    public Set<String> zRange(String key, long start, long end) {
+        return redisTemplate.opsForZSet().range(key, start, end);
+    }
+
+    /**
+     * 获取集合元素, 并且把score值也获取
+     *
+     * @param key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<TypedTuple<String>> zRangeWithScores(String key, long start,
+                                                    long end) {
+        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
+    }
+
+    /**
+     * 根据Score值查询集合元素
+     *
+     * @param key
+     * @param min 最小值
+     * @param max 最大值
+     * @return
+     */
+    public Set<String> zRangeByScore(String key, double min, double max) {
+        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
+    }
+
+    /**
+     * 根据Score值查询集合元素, 从小到大排序
+     *
+     * @param key
+     * @param min 最小值
+     * @param max 最大值
+     * @return
+     */
+    public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
+                                                           double min, double max) {
+        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
+    }
+
+    /**
+     * @param key
+     * @param min
+     * @param max
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
+                                                           double min, double max, long start, long end) {
+        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
+                start, end);
+    }
+
+    /**
+     * 获取集合的元素, 从大到小排序
+     *
+     * @param key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<String> zReverseRange(String key, long start, long end) {
+        return redisTemplate.opsForZSet().reverseRange(key, start, end);
+    }
+
+    /**
+     * 获取集合的元素, 从大到小排序, 并返回score值
+     *
+     * @param key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<TypedTuple<String>> zReverseRangeWithScores(String key,
+                                                           long start, long end) {
+        return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,
+                end);
+    }
+
+    /**
+     * 根据Score值查询集合元素, 从大到小排序
+     *
+     * @param key
+     * @param min
+     * @param max
+     * @return
+     */
+    public Set<String> zReverseRangeByScore(String key, double min,
+                                            double max) {
+        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
+    }
+
+    /**
+     * 根据Score值查询集合元素, 从大到小排序
+     *
+     * @param key
+     * @param min
+     * @param max
+     * @return
+     */
+    public Set<TypedTuple<String>> zReverseRangeByScoreWithScores(
+            String key, double min, double max) {
+        return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
+                min, max);
+    }
+
+    /**
+     * @param key
+     * @param min
+     * @param max
+     * @param start
+     * @param end
+     * @return
+     */
+    public Set<String> zReverseRangeByScore(String key, double min,
+                                            double max, long start, long end) {
+        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
+                start, end);
+    }
+
+    /**
+     * 根据score值获取集合元素数量
+     *
+     * @param key
+     * @param min
+     * @param max
+     * @return
+     */
+    public Long zCount(String key, double min, double max) {
+        return redisTemplate.opsForZSet().count(key, min, max);
+    }
+
+    /**
+     * 获取集合大小
+     *
+     * @param key
+     * @return
+     */
+    public Long zSize(String key) {
+        return redisTemplate.opsForZSet().size(key);
+    }
+
+    /**
+     * 获取集合大小
+     *
+     * @param key
+     * @return
+     */
+    public Long zZCard(String key) {
+        return redisTemplate.opsForZSet().zCard(key);
+    }
+
+    /**
+     * 获取集合中value元素的score值
+     *
+     * @param key
+     * @param value
+     * @return
+     */
+    public Double zScore(String key, Object value) {
+        return redisTemplate.opsForZSet().score(key, value);
+    }
+
+    /**
+     * 移除指定索引位置的成员
+     *
+     * @param key
+     * @param start
+     * @param end
+     * @return
+     */
+    public Long zRemoveRange(String key, long start, long end) {
+        return redisTemplate.opsForZSet().removeRange(key, start, end);
+    }
+
+    /**
+     * 根据指定的score值的范围来移除成员
+     *
+     * @param key
+     * @param min
+     * @param max
+     * @return
+     */
+    public Long zRemoveRangeByScore(String key, double min, double max) {
+        return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
+    }
+
+    /**
+     * 获取key和otherKey的并集并存储在destKey中
+     *
+     * @param key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long zUnionAndStore(String key, String otherKey, String destKey) {
+        return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
+    }
+
+    /**
+     * @param key
+     * @param otherKeys
+     * @param destKey
+     * @return
+     */
+    public Long zUnionAndStore(String key, Collection<String> otherKeys,
+                               String destKey) {
+        return redisTemplate.opsForZSet()
+                .unionAndStore(key, otherKeys, destKey);
+    }
+
+    /**
+     * 交集
+     *
+     * @param key
+     * @param otherKey
+     * @param destKey
+     * @return
+     */
+    public Long zIntersectAndStore(String key, String otherKey,
+                                   String destKey) {
+        return redisTemplate.opsForZSet().intersectAndStore(key, otherKey,
+                destKey);
+    }
+
+    /**
+     * 交集
+     *
+     * @param key
+     * @param otherKeys
+     * @param destKey
+     * @return
+     */
+    public Long zIntersectAndStore(String key, Collection<String> otherKeys,
+                                   String destKey) {
+        return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys,
+                destKey);
+    }
+
+    /**
+     * @param key
+     * @param options
+     * @return
+     */
+    public Cursor<TypedTuple<String>> zScan(String key, ScanOptions options) {
+        return redisTemplate.opsForZSet().scan(key, options);
+    }
+
+    /**
+     * 获取Redis List 序列化
+     *
+     * @param key
+     * @param targetClass
+     * @param <T>
+     * @return
+     */
+    public <T> List<T> getListCache(final String key, Class<T> targetClass) {
+        byte[] result = redisTemplate.execute(new RedisCallback<byte[]>() {
+            @Override
+            public byte[] doInRedis(RedisConnection connection) throws DataAccessException {
+                return connection.get(key.getBytes());
+            }
+        });
+        if (result == null) {
+            return null;
+        }
+        return ProtoStuffSerializerUtil.deserializeList(result, targetClass);
+    }
+
+    /***
+     * 将List 放进缓存里面
+     * @param key
+     * @param objList
+     * @param expireTime
+     * @param <T>
+     * @return
+     */
+    public <T> boolean putListCacheWithExpireTime(String key, List<T> objList, final long expireTime) {
+        final byte[] bkey = key.getBytes();
+        final byte[] bvalue = ProtoStuffSerializerUtil.serializeList(objList);
+        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
+            @Override
+            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
+                connection.setEx(bkey, expireTime, bvalue);
+                return true;
+            }
+        });
+        return result;
+    }
+}