home 2 年之前
父節點
當前提交
70f8b24b81

+ 77 - 65
bladex/blade-service-api/blade-system-api/src/main/java/org/springblade/system/entity/Dept.java

@@ -38,71 +38,83 @@ import java.io.Serializable;
 @ApiModel(value = "Dept对象", description = "Dept对象")
 public class Dept implements Serializable {
 
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * 主键
-	 */
-	@JsonSerialize(using = ToStringSerializer.class)
-	@ApiModelProperty(value = "主键")
-	@TableId(value = "id", type = IdType.ASSIGN_ID)
-	private Long id;
-
-	/**
-	 * 租户ID
-	 */
-	@ApiModelProperty(value = "租户ID")
-	private String tenantId;
-
-	/**
-	 * 父主键
-	 */
-	@JsonSerialize(using = ToStringSerializer.class)
-	@ApiModelProperty(value = "父主键")
-	private Long parentId;
-
-	/**
-	 * 机构名
-	 */
-	@ApiModelProperty(value = "机构名")
-	private String deptName;
-
-	/**
-	 * 机构全称
-	 */
-	@ApiModelProperty(value = "机构全称")
-	private String fullName;
-
-	/**
-	 * 祖级机构主键
-	 */
-	@ApiModelProperty(value = "祖级机构主键")
-	private String ancestors;
-
-	/**
-	 * 机构类型
-	 */
-	@ApiModelProperty(value = "机构类型")
-	private Integer deptCategory;
-
-	/**
-	 * 排序
-	 */
-	@ApiModelProperty(value = "排序")
-	private Integer sort;
-
-	/**
-	 * 备注
-	 */
-	@ApiModelProperty(value = "备注")
-	private String remark;
-
-	/**
-	 * 是否已删除
-	 */
-	@TableLogic
-	@ApiModelProperty(value = "是否已删除")
-	private Integer isDeleted;
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 租户ID
+     */
+    @ApiModelProperty(value = "租户ID")
+    private String tenantId;
+
+    /**
+     * 父主键
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "父主键")
+    private Long parentId;
+
+    /**
+     * 机构名
+     */
+    @ApiModelProperty(value = "机构名")
+    private String deptName;
+
+    /**
+     * 机构全称
+     */
+    @ApiModelProperty(value = "机构全称")
+    private String fullName;
+
+    /**
+     * 祖级机构主键
+     */
+    @ApiModelProperty(value = "祖级机构主键")
+    private String ancestors;
+
+    /**
+     * 机构类型
+     */
+    @ApiModelProperty(value = "机构类型")
+    private Integer deptCategory;
+
+    /**
+     * 排序
+     */
+    @ApiModelProperty(value = "排序")
+    private Integer sort;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty(value = "备注")
+    private String remark;
+
+    /**
+     * 总监id
+     */
+    @ApiModelProperty(value = "总监id")
+    private Long chiefInspectorId;
+
+    /**
+     * 负责人id
+     */
+    @ApiModelProperty(value = "负责人id")
+    private Long personInChargeId;
+
+    /**
+     * 是否已删除
+     */
+    @TableLogic
+    @ApiModelProperty(value = "是否已删除")
+    private Integer isDeleted;
 
 
 }

+ 46 - 36
bladex/blade-service-api/blade-system-api/src/main/java/org/springblade/system/vo/DeptVO.java

@@ -37,48 +37,58 @@ import java.util.List;
 @EqualsAndHashCode(callSuper = true)
 @ApiModel(value = "DeptVO对象", description = "DeptVO对象")
 public class DeptVO extends Dept implements INode<DeptVO> {
-	private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = 1L;
 
-	/**
-	 * 主键ID
-	 */
-	@JsonSerialize(using = ToStringSerializer.class)
-	private Long id;
+    /**
+     * 主键ID
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long id;
 
-	/**
-	 * 父节点ID
-	 */
-	@JsonSerialize(using = ToStringSerializer.class)
-	private Long parentId;
+    /**
+     * 父节点ID
+     */
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long parentId;
 
-	/**
-	 * 子孙节点
-	 */
-	@JsonInclude(JsonInclude.Include.NON_EMPTY)
-	private List<DeptVO> children;
+    /**
+     * 子孙节点
+     */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private List<DeptVO> children;
 
-	/**
-	 * 是否有子孙节点
-	 */
-	@JsonInclude(JsonInclude.Include.NON_EMPTY)
-	private Boolean hasChildren;
+    /**
+     * 是否有子孙节点
+     */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    private Boolean hasChildren;
 
-	@Override
-	public List<DeptVO> getChildren() {
-		if (this.children == null) {
-			this.children = new ArrayList<>();
-		}
-		return this.children;
-	}
+    @Override
+    public List<DeptVO> getChildren() {
+        if (this.children == null) {
+            this.children = new ArrayList<>();
+        }
+        return this.children;
+    }
 
-	/**
-	 * 上级机构
-	 */
-	private String parentName;
+    /**
+     * 上级机构
+     */
+    private String parentName;
 
-	/**
-	 * 机构类型名称
-	 */
-	private String deptCategoryName;
+    /**
+     * 机构类型名称
+     */
+    private String deptCategoryName;
+
+    /**
+     * 总监名称
+     */
+    private String chiefInspectorName;
+
+    /**
+     * 负责人名称
+     */
+    private String personInChargeName;
 
 }

+ 32 - 26
bladex/blade-service/blade-system/src/main/java/org/springblade/system/mapper/DeptMapper.xml

@@ -39,17 +39,17 @@
 
     <select id="lazyList" resultMap="deptVOResultMap">
         SELECT
-            dept.* ,
-            (
-                SELECT
-                    CASE WHEN count(1) > 0 THEN 1 ELSE 0 END
-                FROM
-                    blade_dept
-                WHERE
-                    parent_id = dept.id and is_deleted = 0
-            ) AS "has_children"
+        dept.* ,
+        (
+        SELECT
+        CASE WHEN count(1) > 0 THEN 1 ELSE 0 END
         FROM
-            blade_dept dept
+        blade_dept
+        WHERE
+        parent_id = dept.id and is_deleted = 0
+        ) AS "has_children"
+        FROM
+        blade_dept dept
         WHERE dept.is_deleted = 0
         <if test="param1!=null and param1!=''">
             and dept.tenant_id = #{param1}
@@ -63,6 +63,12 @@
         <if test="param3.fullName!=null and param3.fullName!=''">
             and dept.full_name like concat(concat('%', #{param3.fullName}),'%')
         </if>
+        <if test="param3.chiefInspectorId!=null and param3.chiefInspectorId!=''">
+            and dept.chief_inspector_id = #{param3.chiefInspectorId}
+        </if>
+        <if test="param3.personInChargeId!=null and param3.personInChargeId!=''">
+            and dept.person_in_charge_id = #{param3.personInChargeId}
+        </if>
         ORDER BY dept.sort
     </select>
 
@@ -74,25 +80,25 @@
         ORDER BY sort
     </select>
 
-    <select id="lazyTree" resultMap="treeNodeResultMap" >
+    <select id="lazyTree" resultMap="treeNodeResultMap">
+        SELECT
+        dept.id,
+        dept.parent_id,
+        dept.dept_name AS title,
+        dept.id AS "value",
+        dept.id AS "key",
+        (
         SELECT
-            dept.id,
-            dept.parent_id,
-            dept.dept_name AS title,
-            dept.id AS "value",
-            dept.id AS "key",
-            (
-                SELECT
-                    CASE WHEN count(1) > 0 THEN 1 ELSE 0 END
-                FROM
-                    blade_dept
-                WHERE
-                    parent_id = dept.id and is_deleted = 0
-            ) AS "has_children"
+        CASE WHEN count(1) > 0 THEN 1 ELSE 0 END
+        FROM
+        blade_dept
+        WHERE
+        parent_id = dept.id and is_deleted = 0
+        ) AS "has_children"
         FROM
-            blade_dept dept
+        blade_dept dept
         WHERE
-            dept.parent_id = #{param2} AND dept.is_deleted = 0
+        dept.parent_id = #{param2} AND dept.is_deleted = 0
         <if test="param1!=null and param1!=''">
             and dept.tenant_id = #{param1}
         </if>

+ 152 - 124
bladex/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java

@@ -21,6 +21,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.springblade.core.log.exception.ServiceException;
 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.Func;
@@ -29,10 +30,13 @@ import org.springblade.system.cache.SysCache;
 import org.springblade.system.entity.Dept;
 import org.springblade.system.mapper.DeptMapper;
 import org.springblade.system.service.IDeptService;
+import org.springblade.system.user.feign.IUserSearchClient;
 import org.springblade.system.vo.DeptVO;
 import org.springblade.system.wrapper.DeptWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -45,129 +49,153 @@ import java.util.stream.Collectors;
 @Service
 public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements IDeptService {
 
-	private static final String TENANT_ID = "tenantId";
-	private static final String PARENT_ID = "parentId";
-
-	@Override
-	public List<DeptVO> lazyList(String tenantId, Long parentId, Map<String, Object> param) {
-		// 设置租户ID
-		if (AuthUtil.isAdministrator()) {
-			tenantId = StringPool.EMPTY;
-		}
-		String paramTenantId = Func.toStr(param.get(TENANT_ID));
-		if (Func.isNotEmpty(paramTenantId) && AuthUtil.isAdministrator()) {
-			tenantId = paramTenantId;
-		}
-		// 判断点击搜索但是没有查询条件的情况
-		if (Func.isEmpty(param.get(PARENT_ID)) && param.size() == 1) {
-			parentId = 0L;
-		}
-		// 判断数据权限控制,非超管角色只可看到本级及以下数据
-		if (Func.toLong(parentId) == 0L && !AuthUtil.isAdministrator()) {
-			Long deptId = Func.firstLong(AuthUtil.getDeptId());
-			Dept dept = SysCache.getDept(deptId);
-			if (dept.getParentId() != 0) {
-				parentId = dept.getParentId();
-			}
-		}
-		// 判断点击搜索带有查询条件的情况
-		if (Func.isEmpty(param.get(PARENT_ID)) && param.size() > 1 && Func.toLong(parentId) == 0L) {
-			parentId = null;
-		}
-		return baseMapper.lazyList(tenantId, parentId, param);
-	}
-
-
-	@Override
-	public List<DeptVO> tree(String tenantId) {
-		return ForestNodeMerger.merge(baseMapper.tree(tenantId));
-	}
-
-	@Override
-	public List<DeptVO> lazyTree(String tenantId, Long parentId) {
-		if (AuthUtil.isAdministrator()) {
-			tenantId = StringPool.EMPTY;
-		}
-		return ForestNodeMerger.merge(baseMapper.lazyTree(tenantId, parentId));
-	}
-
-	@Override
-	public String getDeptIds(String tenantId, String deptNames) {
-		List<Dept> deptList = baseMapper.selectList(Wrappers.<Dept>query().lambda().eq(Dept::getTenantId, tenantId).in(Dept::getDeptName, Func.toStrList(deptNames)));
-		if (deptList != null && deptList.size() > 0) {
-			return deptList.stream().map(dept -> Func.toStr(dept.getId())).distinct().collect(Collectors.joining(","));
-		}
-		return null;
-	}
-
-	@Override
-	public String getDeptIdsByFuzzy(String tenantId, String deptNames) {
-		LambdaQueryWrapper<Dept> queryWrapper = Wrappers.<Dept>query().lambda().eq(Dept::getTenantId, tenantId);
-		queryWrapper.and(wrapper -> {
-			List<String> names = Func.toStrList(deptNames);
-			names.forEach(name -> wrapper.like(Dept::getDeptName, name).or());
-		});
-		List<Dept> deptList = baseMapper.selectList(queryWrapper);
-		if (deptList != null && deptList.size() > 0) {
-			return deptList.stream().map(dept -> Func.toStr(dept.getId())).distinct().collect(Collectors.joining(","));
-		}
-		return null;
-	}
-
-	@Override
-	public List<String> getDeptNames(String deptIds) {
-		return baseMapper.getDeptNames(Func.toLongArray(deptIds));
-	}
-
-	@Override
-	public List<Dept> getDeptChild(Long deptId) {
-		return baseMapper.selectList(Wrappers.<Dept>query().lambda().like(Dept::getAncestors, deptId));
-	}
-
-	@Override
-	public boolean removeDept(String ids) {
-		Long cnt = baseMapper.selectCount(Wrappers.<Dept>query().lambda().in(Dept::getParentId, Func.toLongList(ids)));
-		if (cnt > 0L) {
-			throw new ServiceException("请先删除子节点!");
-		}
-		return removeByIds(Func.toLongList(ids));
-	}
-
-	@Override
-	public boolean submit(Dept dept) {
-		if (Func.isEmpty(dept.getParentId())) {
-			dept.setTenantId(AuthUtil.getTenantId());
-			dept.setParentId(BladeConstant.TOP_PARENT_ID);
-			dept.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID));
-		}
-		if (dept.getParentId() > 0) {
-			Dept parent = getById(dept.getParentId());
-			if (Func.toLong(dept.getParentId()) == Func.toLong(dept.getId())) {
-				throw new ServiceException("父节点不可选择自身!");
-			}
-			dept.setTenantId(parent.getTenantId());
-			String ancestors = parent.getAncestors() + StringPool.COMMA + dept.getParentId();
-			dept.setAncestors(ancestors);
-		}
-		dept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
-		return saveOrUpdate(dept);
-	}
-
-	@Override
-	public List<DeptVO> search(String deptName, Long parentId) {
-		String tenantId = AuthUtil.getTenantId();
-		LambdaQueryWrapper<Dept> queryWrapper = Wrappers.<Dept>query().lambda();
-		if (Func.isNotEmpty(tenantId)) {
-			queryWrapper.eq(Dept::getTenantId, tenantId);
-		}
-		if (Func.isNotEmpty(deptName)) {
-			queryWrapper.like(Dept::getDeptName, deptName);
-		}
-		if (Func.isNotEmpty(parentId) && parentId > 0L) {
-			queryWrapper.eq(Dept::getParentId, parentId);
-		}
-		List<Dept> deptList = baseMapper.selectList(queryWrapper);
-		return DeptWrapper.build().listNodeVO(deptList);
-	}
+    private static final String TENANT_ID = "tenantId";
+    private static final String PARENT_ID = "parentId";
+
+    @Autowired
+    private IUserSearchClient userSearchClient;
+
+    @Override
+    public List<DeptVO> lazyList(String tenantId, Long parentId, Map<String, Object> param) {
+        // 设置租户ID
+        if (AuthUtil.isAdministrator()) {
+            tenantId = StringPool.EMPTY;
+        }
+        String paramTenantId = Func.toStr(param.get(TENANT_ID));
+        if (Func.isNotEmpty(paramTenantId) && AuthUtil.isAdministrator()) {
+            tenantId = paramTenantId;
+        }
+        // 判断点击搜索但是没有查询条件的情况
+        if (Func.isEmpty(param.get(PARENT_ID)) && param.size() == 1) {
+            parentId = 0L;
+        }
+        // 判断数据权限控制,非超管角色只可看到本级及以下数据
+        if (Func.toLong(parentId) == 0L && !AuthUtil.isAdministrator()) {
+            Long deptId = Func.firstLong(AuthUtil.getDeptId());
+            Dept dept = SysCache.getDept(deptId);
+            if (dept.getParentId() != 0) {
+                parentId = dept.getParentId();
+            }
+        }
+        // 判断点击搜索带有查询条件的情况h
+        if (Func.isEmpty(param.get(PARENT_ID)) && param.size() > 1 && Func.toLong(parentId) == 0L) {
+            parentId = null;
+        }
+
+        List<DeptVO> deptVOS = baseMapper.lazyList(tenantId, parentId, param);
+
+        // 赋值总监名称、负责人名称
+        List<Long> userIdList = new ArrayList<>();
+        for (DeptVO deptVO : deptVOS) {
+            userIdList.add(deptVO.getChiefInspectorId());
+            userIdList.add(deptVO.getPersonInChargeId());
+        }
+        if (userIdList.size() > 0) {
+            return deptVOS;
+        }
+        R<Map<Long, String>> r = userSearchClient.mapByUser(userIdList);
+        if (r.isSuccess()) {
+            Map<Long, String> map = r.getData();
+            for (DeptVO deptVO : deptVOS) {
+                deptVO.setChiefInspectorName(map.get(deptVO.getChiefInspectorId()));
+                deptVO.setPersonInChargeName(map.get(deptVO.getPersonInChargeId()));
+            }
+        }
+
+        return deptVOS;
+    }
+
+
+    @Override
+    public List<DeptVO> tree(String tenantId) {
+        return ForestNodeMerger.merge(baseMapper.tree(tenantId));
+    }
+
+    @Override
+    public List<DeptVO> lazyTree(String tenantId, Long parentId) {
+        if (AuthUtil.isAdministrator()) {
+            tenantId = StringPool.EMPTY;
+        }
+        return ForestNodeMerger.merge(baseMapper.lazyTree(tenantId, parentId));
+    }
+
+    @Override
+    public String getDeptIds(String tenantId, String deptNames) {
+        List<Dept> deptList = baseMapper.selectList(Wrappers.<Dept>query().lambda().eq(Dept::getTenantId, tenantId).in(Dept::getDeptName, Func.toStrList(deptNames)));
+        if (deptList != null && deptList.size() > 0) {
+            return deptList.stream().map(dept -> Func.toStr(dept.getId())).distinct().collect(Collectors.joining(","));
+        }
+        return null;
+    }
+
+    @Override
+    public String getDeptIdsByFuzzy(String tenantId, String deptNames) {
+        LambdaQueryWrapper<Dept> queryWrapper = Wrappers.<Dept>query().lambda().eq(Dept::getTenantId, tenantId);
+        queryWrapper.and(wrapper -> {
+            List<String> names = Func.toStrList(deptNames);
+            names.forEach(name -> wrapper.like(Dept::getDeptName, name).or());
+        });
+        List<Dept> deptList = baseMapper.selectList(queryWrapper);
+        if (deptList != null && deptList.size() > 0) {
+            return deptList.stream().map(dept -> Func.toStr(dept.getId())).distinct().collect(Collectors.joining(","));
+        }
+        return null;
+    }
+
+    @Override
+    public List<String> getDeptNames(String deptIds) {
+        return baseMapper.getDeptNames(Func.toLongArray(deptIds));
+    }
+
+    @Override
+    public List<Dept> getDeptChild(Long deptId) {
+        return baseMapper.selectList(Wrappers.<Dept>query().lambda().like(Dept::getAncestors, deptId));
+    }
+
+    @Override
+    public boolean removeDept(String ids) {
+        Long cnt = baseMapper.selectCount(Wrappers.<Dept>query().lambda().in(Dept::getParentId, Func.toLongList(ids)));
+        if (cnt > 0L) {
+            throw new ServiceException("请先删除子节点!");
+        }
+        return removeByIds(Func.toLongList(ids));
+    }
+
+    @Override
+    public boolean submit(Dept dept) {
+        if (Func.isEmpty(dept.getParentId())) {
+            dept.setTenantId(AuthUtil.getTenantId());
+            dept.setParentId(BladeConstant.TOP_PARENT_ID);
+            dept.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID));
+        }
+        if (dept.getParentId() > 0) {
+            Dept parent = getById(dept.getParentId());
+            if (Func.toLong(dept.getParentId()) == Func.toLong(dept.getId())) {
+                throw new ServiceException("父节点不可选择自身!");
+            }
+            dept.setTenantId(parent.getTenantId());
+            String ancestors = parent.getAncestors() + StringPool.COMMA + dept.getParentId();
+            dept.setAncestors(ancestors);
+        }
+        dept.setIsDeleted(BladeConstant.DB_NOT_DELETED);
+        return saveOrUpdate(dept);
+    }
+
+    @Override
+    public List<DeptVO> search(String deptName, Long parentId) {
+        String tenantId = AuthUtil.getTenantId();
+        LambdaQueryWrapper<Dept> queryWrapper = Wrappers.<Dept>query().lambda();
+        if (Func.isNotEmpty(tenantId)) {
+            queryWrapper.eq(Dept::getTenantId, tenantId);
+        }
+        if (Func.isNotEmpty(deptName)) {
+            queryWrapper.like(Dept::getDeptName, deptName);
+        }
+        if (Func.isNotEmpty(parentId) && parentId > 0L) {
+            queryWrapper.eq(Dept::getParentId, parentId);
+        }
+        List<Dept> deptList = baseMapper.selectList(queryWrapper);
+        return DeptWrapper.build().listNodeVO(deptList);
+    }
 
 }

+ 26 - 0
hx-common/common-im/pom.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>hx-common</artifactId>
+        <groupId>com.fjhx</groupId>
+        <version>3.2.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>common-im</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.fjhx</groupId>
+            <artifactId>common-tool</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 199 - 0
hx-common/common-im/src/main/java/com/fjhx/ImClient.java

@@ -0,0 +1,199 @@
+package com.fjhx;
+
+import cn.hutool.core.util.RandomUtil;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.fjhx.utils.Assert;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.log.exception.ServiceException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.*;
+
+@Slf4j
+public class ImClient {
+
+    private static final RestTemplate restTemplate = new RestTemplate();
+    private static final String baseUrl = "https://console.tim.qq.com/";
+    private static final TLSSigAPIv2 tlsSigAPIv2 = new TLSSigAPIv2(ImConfig.sdkappid, ImConfig.key);
+
+//    public static void main(String[] args) {
+//        getGroup("test1");
+
+//        setPortrait("test3","测试nickName3");
+
+//        createUsers("test4", "test5");
+
+//        addFriends("test1", Arrays.asList("test4", "test5"), "group1", "group2");
+
+//        System.err.println(getFriends("test1", Arrays.asList("test2", "test3", "test4", "test5")).toJSONString());
+//    }
+
+    /**
+     * 导入帐号
+     * <p>
+     *
+     * @param userId  (必填)用户id,长度不超过32个字节
+     * @param nick    用户昵称
+     * @param faceUrl 用户头像 URL
+     * @link { https://cloud.tencent.com/document/product/269/1608 }
+     */
+    public static void createUser(String userId, String nick, String faceUrl) {
+        Map<String, String> requestBody = new HashMap<>();
+        requestBody.put("UserID", userId);
+        requestBody.put("Nick", nick);
+        requestBody.put("FaceUrl", faceUrl);
+
+        JSONObject result = call(getUrl("v4/im_open_login_svc/account_import"), requestBody);
+        validation(result, "导入帐号");
+    }
+
+    /**
+     * 导入多个账号
+     * <p>
+     *
+     * @param userIds (必填)用户id列表
+     * @link { https://cloud.tencent.com/document/product/269/4919 }
+     */
+    public static void createUsers(String... userIds) {
+        Assert.eqTrue(userIds.length <= 100, "批量添加数量不能大于100");
+
+        Map<Object, Object> requestBody = new HashMap<>();
+        requestBody.put("Accounts", Arrays.asList(userIds));
+
+        JSONObject result = call(getUrl("v4/im_open_login_svc/multiaccount_import"), requestBody);
+        validation(result, "导入多个账号");
+    }
+
+    /**
+     * 设置资料
+     * <p>
+     *
+     * @param fromAccount (必填)账号列表
+     * @link { https://cloud.tencent.com/document/product/269/1640 }
+     * @link ProfileItem字段 { https://cloud.tencent.com/document/product/269/1500#.E6.A0.87.E9.85.8D.E8.B5.84.E6.96.99.E5.AD.97.E6.AE.B5 }
+     */
+    public static void setPortrait(String fromAccount, String nickName) {
+
+        HashMap<String, String> item = new HashMap<>();
+        item.put("Tag", "Tag_Profile_IM_Nick");
+        item.put("Value", nickName);
+
+        Map<Object, Object> requestBody = new HashMap<>();
+        requestBody.put("From_Account", fromAccount);
+        requestBody.put("ProfileItem", Collections.singletonList(item));
+
+        JSONObject result = call(getUrl("v4/profile/portrait_set"), requestBody);
+        validation(result, "设置资料");
+    }
+
+
+    /**
+     * 导入好友
+     * <p>
+     *
+     * @link { https://cloud.tencent.com/document/product/269/8301 }
+     */
+    public static void addFriends(String fromAccount, List<String> toAccountList, String groupName) {
+
+        List<Map<String, Object>> addFriendItem = new ArrayList<>();
+
+        for (String toAccount : toAccountList) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("To_Account", toAccount);
+            item.put("AddSource", "AddSource_Type_service");
+            item.put("GroupName", Collections.singletonList(groupName));
+            addFriendItem.add(item);
+        }
+
+        HashMap<String, Object> requestBody = new HashMap<>();
+        requestBody.put("From_Account", fromAccount);
+        requestBody.put("AddFriendItem", addFriendItem);
+
+        JSONObject result = call(getUrl("v4/sns/friend_import"), requestBody);
+        validation(result, "导入好友");
+    }
+
+    /**
+     * 拉取分组
+     * <p>
+     *
+     * @link { https://cloud.tencent.com/document/product/269/54763 }
+     */
+    private static JSONArray getGroup(String fromAccount) {
+        String url = getUrl("v4/sns/group_get");
+        HashMap<String, Object> requestBody = new HashMap<>();
+        requestBody.put("From_Account", fromAccount);
+        requestBody.put("NeedFriend", "Need_Friend_Type_Yes");
+
+        JSONObject result = call(url, requestBody);
+        validation(result, "拉取分组");
+
+        return result.getJSONArray("result");
+    }
+
+    /**
+     * 拉取指定好友
+     * <p>
+     *
+     * @link { https://cloud.tencent.com/document/product/269/8609 }
+     * @link TagList字段: { https://cloud.tencent.com/document/product/269/1500 }
+     */
+    private static JSONArray getFriends(String fromAccount, String... toAccount) {
+        String url = getUrl("v4/sns/friend_get_list");
+
+        HashMap<String, Object> requestBody = new HashMap<>();
+        requestBody.put("From_Account", fromAccount);
+        requestBody.put("To_Account", Arrays.asList(toAccount));
+        // 额外查询昵称字段
+        requestBody.put("TagList", Collections.singletonList("Tag_Profile_IM_Nick"));
+
+        JSONObject result = call(url, requestBody);
+        validation(result, "拉取指定好友");
+
+        return result.getJSONArray("InfoItem");
+    }
+
+    /**
+     * 获取请求url
+     *
+     * @param suffix 请求连接后缀
+     * @return 请求url
+     */
+    private static String getUrl(String suffix) {
+        return baseUrl + suffix + String.format("?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=json",
+                ImConfig.sdkappid, ImConfig.identifier, tlsSigAPIv2.genUserSig(ImConfig.identifier, ImConfig.expire),
+                RandomUtil.randomNumbers(32));
+    }
+
+    /**
+     * 调用im
+     *
+     * @param url         请求url
+     * @param requestBody 请求体
+     * @return 请求结果
+     */
+    private static JSONObject call(String url, Object requestBody) {
+        ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(url, requestBody, JSONObject.class);
+        JSONObject body = Objects.requireNonNull(responseEntity.getBody());
+        log.info(body.toJSONString());
+        return body;
+    }
+
+    /**
+     * 验证调用是否成功
+     *
+     * @param result  返回值
+     * @param explain 提示
+     */
+    private static void validation(JSONObject result, String explain) {
+        Integer errorCode = result.getInteger("ErrorCode");
+        if (errorCode != 0) {
+            String error = explain + "失败,错误码:" + errorCode;
+            log.error(error);
+            throw new ServiceException(error);
+        }
+    }
+
+}

+ 13 - 0
hx-common/common-im/src/main/java/com/fjhx/ImConfig.java

@@ -0,0 +1,13 @@
+package com.fjhx;
+
+public interface ImConfig {
+
+    Long expire = 604800L;
+
+    Long sdkappid = 1400729082L;
+
+    String key = "c3cdb2c12b71332503bb7d2693e6b0e5e077d537d60558ff96cea55ab547d907";
+
+    String identifier = "administrator";
+
+}

+ 317 - 0
hx-common/common-im/src/main/java/com/fjhx/TLSSigAPIv2.java

@@ -0,0 +1,317 @@
+package com.fjhx;
+
+import com.alibaba.fastjson.JSONObject;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.zip.Deflater;
+
+
+public class TLSSigAPIv2 {
+    final private long sdkappid;
+    final private String key;
+
+    public TLSSigAPIv2(long sdkappid, String key) {
+        this.sdkappid = sdkappid;
+        this.key = key;
+    }
+
+    /**
+     * 【功能说明】用于签发 TRTC 和 IM 服务中必须要使用的 UserSig 鉴权票据
+     * <p>
+     * 【参数说明】
+     *
+     * @param userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
+     * @param expire - UserSig 票据的过期时间,单位是秒,比如 86400 代表生成的 UserSig 票据在一天后就无法再使用了。
+     * @return usersig -生成的签名
+     */
+
+    /**
+     * Function: Used to issue UserSig that is required by the TRTC and IM services.
+     * <p>
+     * Parameter description:
+     *
+     * @param userid - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-).
+     * @param expire - UserSig expiration time, in seconds. For example, 86400 indicates that the generated UserSig will expire one day after being generated.
+     * @return usersig - Generated signature.
+     */
+    public String genUserSig(String userid, long expire) {
+        return genUserSig(userid, expire, null);
+    }
+
+    /**
+     * 【功能说明】
+     * 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。
+     * PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:
+     * - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。
+     * - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。
+     * 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】/【应用管理】/【应用信息】中打开“启动权限密钥”开关。
+     * <p>
+     * 【参数说明】
+     *
+     * @param userid       - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
+     * @param expire       - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。
+     * @param roomid       - 房间号,用于指定该 userid 可以进入的房间号
+     * @param privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:
+     *                     - 第 1 位:0000 0001 = 1,创建房间的权限
+     *                     - 第 2 位:0000 0010 = 2,加入房间的权限
+     *                     - 第 3 位:0000 0100 = 4,发送语音的权限
+     *                     - 第 4 位:0000 1000 = 8,接收语音的权限
+     *                     - 第 5 位:0001 0000 = 16,发送视频的权限
+     *                     - 第 6 位:0010 0000 = 32,接收视频的权限
+     *                     - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限
+     *                     - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限
+     *                     - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。
+     *                     - privilegeMap == 0010 1010 == 42  代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。
+     * @return usersig - 生成带userbuf的签名
+     */
+
+    /**
+     * Function:
+     * Used to issue PrivateMapKey that is optional for room entry.
+     * PrivateMapKey must be used together with UserSig but with more powerful permission control capabilities.
+     * - UserSig can only control whether a UserID has permission to use the TRTC service. As long as the UserSig is correct, the user with the corresponding UserID can enter or leave any room.
+     * - PrivateMapKey specifies more stringent permissions for a UserID, including whether the UserID can be used to enter a specific room and perform audio/video upstreaming in the room.
+     * To enable stringent PrivateMapKey permission bit verification, you need to enable permission key in TRTC console > Application Management > Application Info.
+     * <p>
+     * Parameter description:
+     *
+     * @param userid       - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-).
+     * @param roomid       - ID of the room to which the specified UserID can enter.
+     * @param expire       - PrivateMapKey expiration time, in seconds. For example, 86400 indicates that the generated PrivateMapKey will expire one day after being generated.
+     * @param privilegeMap - Permission bits. Eight bits in the same byte are used as the permission switches of eight specific features:
+     *                     - Bit 1: 0000 0001 = 1, permission for room creation
+     *                     - Bit 2: 0000 0010 = 2, permission for room entry
+     *                     - Bit 3: 0000 0100 = 4, permission for audio sending
+     *                     - Bit 4: 0000 1000 = 8, permission for audio receiving
+     *                     - Bit 5: 0001 0000 = 16, permission for video sending
+     *                     - Bit 6: 0010 0000 = 32, permission for video receiving
+     *                     - Bit 7: 0100 0000 = 64, permission for substream video sending (screen sharing)
+     *                     - Bit 8: 1000 0000 = 200, permission for substream video receiving (screen sharing)
+     *                     - privilegeMap == 1111 1111 == 255: Indicates that the UserID has all feature permissions of the room specified by roomid.
+     *                     - privilegeMap == 0010 1010 == 42: Indicates that the UserID has only the permissions to enter the room and receive audio/video data.
+     * @return usersig - Generate signature with userbuf
+     */
+    public String genPrivateMapKey(String userid, long expire, long roomid, long privilegeMap) {
+        byte[] userbuf = genUserBuf(userid, roomid, expire, privilegeMap, 0, "");  //生成userbuf
+        return genUserSig(userid, expire, userbuf);
+    }
+
+    /**
+     * 【功能说明】
+     * 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。
+     * PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:
+     * - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。
+     * - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。
+     * 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】/【应用管理】/【应用信息】中打开“启动权限密钥”开关。
+     * <p>
+     * 【参数说明】
+     *
+     * @param userid       - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
+     * @param expire       - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。
+     * @param roomstr      - 字符串房间号,用于指定该 userid 可以进入的房间号
+     * @param privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:
+     *                     - 第 1 位:0000 0001 = 1,创建房间的权限
+     *                     - 第 2 位:0000 0010 = 2,加入房间的权限
+     *                     - 第 3 位:0000 0100 = 4,发送语音的权限
+     *                     - 第 4 位:0000 1000 = 8,接收语音的权限
+     *                     - 第 5 位:0001 0000 = 16,发送视频的权限
+     *                     - 第 6 位:0010 0000 = 32,接收视频的权限
+     *                     - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限
+     *                     - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限
+     *                     - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。
+     *                     - privilegeMap == 0010 1010 == 42  代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。
+     * @return usersig - 生成带userbuf的签名
+     */
+
+    /**
+     * Function:
+     * Used to issue PrivateMapKey that is optional for room entry.
+     * PrivateMapKey must be used together with UserSig but with more powerful permission control capabilities.
+     * - UserSig can only control whether a UserID has permission to use the TRTC service. As long as the UserSig is correct, the user with the corresponding UserID can enter or leave any room.
+     * - PrivateMapKey specifies more stringent permissions for a UserID, including whether the UserID can be used to enter a specific room and perform audio/video upstreaming in the room.
+     * To enable stringent PrivateMapKey permission bit verification, you need to enable permission key in TRTC console > Application Management > Application Info.
+     * <p>
+     * Parameter description:
+     *
+     * @param userid       - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-).
+     * @param roomstr      - ID of the room to which the specified UserID can enter.
+     * @param expire       - PrivateMapKey expiration time, in seconds. For example, 86400 indicates that the generated PrivateMapKey will expire one day after being generated.
+     * @param privilegeMap - Permission bits. Eight bits in the same byte are used as the permission switches of eight specific features:
+     *                     - Bit 1: 0000 0001 = 1, permission for room creation
+     *                     - Bit 2: 0000 0010 = 2, permission for room entry
+     *                     - Bit 3: 0000 0100 = 4, permission for audio sending
+     *                     - Bit 4: 0000 1000 = 8, permission for audio receiving
+     *                     - Bit 5: 0001 0000 = 16, permission for video sending
+     *                     - Bit 6: 0010 0000 = 32, permission for video receiving
+     *                     - Bit 7: 0100 0000 = 64, permission for substream video sending (screen sharing)
+     *                     - Bit 8: 1000 0000 = 200, permission for substream video receiving (screen sharing)
+     *                     - privilegeMap == 1111 1111 == 255: Indicates that the UserID has all feature permissions of the room specified by roomid.
+     *                     - privilegeMap == 0010 1010 == 42: Indicates that the UserID has only the permissions to enter the room and receive audio/video data.
+     * @return usersig - Generate signature with userbuf
+     */
+    public String genPrivateMapKeyWithStringRoomID(String userid, long expire, String roomstr, long privilegeMap) {
+        byte[] userbuf = genUserBuf(userid, 0, expire, privilegeMap, 0, roomstr);  //生成userbuf
+        return genUserSig(userid, expire, userbuf);
+    }
+
+    private String hmacsha256(String identifier, long currTime, long expire, String base64Userbuf) {
+        String contentToBeSigned = "TLS.identifier:" + identifier + "\n"
+                + "TLS.sdkappid:" + sdkappid + "\n"
+                + "TLS.time:" + currTime + "\n"
+                + "TLS.expire:" + expire + "\n";
+        if (null != base64Userbuf) {
+            contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";
+        }
+        try {
+            byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
+            Mac hmac = Mac.getInstance("HmacSHA256");
+            SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");
+            hmac.init(keySpec);
+            byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes(StandardCharsets.UTF_8));
+            return (Base64.getEncoder().encodeToString(byteSig)).replaceAll("\\s*", "");
+        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+            return "";
+        }
+    }
+
+    private String genUserSig(String userid, long expire, byte[] userbuf) {
+
+        long currTime = System.currentTimeMillis() / 1000;
+
+        JSONObject sigDoc = new JSONObject();
+        sigDoc.put("TLS.ver", "2.0");
+        sigDoc.put("TLS.identifier", userid);
+        sigDoc.put("TLS.sdkappid", sdkappid);
+        sigDoc.put("TLS.expire", expire);
+        sigDoc.put("TLS.time", currTime);
+
+        String base64UserBuf = null;
+        if (null != userbuf) {
+            base64UserBuf = Base64.getEncoder().encodeToString(userbuf).replaceAll("\\s*", "");
+            sigDoc.put("TLS.userbuf", base64UserBuf);
+        }
+        String sig = hmacsha256(userid, currTime, expire, base64UserBuf);
+        if (sig.length() == 0) {
+            return "";
+        }
+        sigDoc.put("TLS.sig", sig);
+        Deflater compressor = new Deflater();
+        compressor.setInput(sigDoc.toString().getBytes(StandardCharsets.UTF_8));
+        compressor.finish();
+        byte[] compressedBytes = new byte[2048];
+        int compressedBytesLength = compressor.deflate(compressedBytes);
+        compressor.end();
+
+        return Base64.getEncoder().encodeToString(
+                        Arrays.copyOfRange(compressedBytes, 0, compressedBytesLength))
+                .replaceAll("\\s*", "");
+    }
+
+    public byte[] genUserBuf(String account, long dwAuthID, long dwExpTime,
+                             long dwPrivilegeMap, long dwAccountType, String RoomStr) {
+        //视频校验位需要用到的字段,按照网络字节序放入buf中
+        /*
+         cVer    unsigned char/1 版本号,填0
+         wAccountLen unsigned short /2   第三方自己的帐号长度
+         account wAccountLen 第三方自己的帐号字符
+         dwSdkAppid  unsigned int/4  sdkappid
+         dwAuthID    unsigned int/4  群组号码
+         dwExpTime   unsigned int/4  过期时间 ,直接使用填入的值
+         dwPrivilegeMap  unsigned int/4  权限位,主播0xff,观众0xab
+         dwAccountType   unsigned int/4  第三方帐号类型
+         */
+
+        //The fields required for the video check digit are placed in buf according to the network byte order.
+        /*
+         cVer    unsigned char/1 Version number, fill in 0
+         wAccountLen unsigned short /2   Third party's own account length
+         account wAccountLen Third party's own account characters
+         dwSdkAppid  unsigned int/4  sdkappid
+         dwAuthID    unsigned int/4  group number
+         dwExpTime   unsigned int/4  Expiration time , use the filled value directly
+         dwPrivilegeMap  unsigned int/4  Permission bits, host 0xff, audience 0xab
+         dwAccountType   unsigned int/4  Third-party account type
+        */
+        int accountLength = account.length();
+        int roomStrLength = RoomStr.length();
+        int offset = 0;
+        int bufLength = 1 + 2 + accountLength + 20;
+        if (roomStrLength > 0) {
+            bufLength = bufLength + 2 + roomStrLength;
+        }
+        byte[] userbuf = new byte[bufLength];
+
+        //cVer
+        if (roomStrLength > 0) {
+            userbuf[offset++] = 1;
+        } else {
+            userbuf[offset++] = 0;
+        }
+
+        //wAccountLen
+        userbuf[offset++] = (byte) ((accountLength & 0xFF00) >> 8);
+        userbuf[offset++] = (byte) (accountLength & 0x00FF);
+
+        //account
+        for (; offset < 3 + accountLength; ++offset) {
+            userbuf[offset] = (byte) account.charAt(offset - 3);
+        }
+
+        //dwSdkAppid
+        userbuf[offset++] = (byte) ((sdkappid & 0xFF000000) >> 24);
+        userbuf[offset++] = (byte) ((sdkappid & 0x00FF0000) >> 16);
+        userbuf[offset++] = (byte) ((sdkappid & 0x0000FF00) >> 8);
+        userbuf[offset++] = (byte) (sdkappid & 0x000000FF);
+
+        //dwAuthId,房间号
+        //dwAuthId, room number
+        userbuf[offset++] = (byte) ((dwAuthID & 0xFF000000) >> 24);
+        userbuf[offset++] = (byte) ((dwAuthID & 0x00FF0000) >> 16);
+        userbuf[offset++] = (byte) ((dwAuthID & 0x0000FF00) >> 8);
+        userbuf[offset++] = (byte) (dwAuthID & 0x000000FF);
+
+        //expire,过期时间,当前时间 + 有效期(单位:秒)
+        //expire,Expiration time, current time + validity period (unit: seconds)
+        long currTime = System.currentTimeMillis() / 1000;
+        long expire = currTime + dwExpTime;
+        userbuf[offset++] = (byte) ((expire & 0xFF000000) >> 24);
+        userbuf[offset++] = (byte) ((expire & 0x00FF0000) >> 16);
+        userbuf[offset++] = (byte) ((expire & 0x0000FF00) >> 8);
+        userbuf[offset++] = (byte) (expire & 0x000000FF);
+
+        //dwPrivilegeMap,权限位
+        //dwPrivilegeMap,Permission bits
+        userbuf[offset++] = (byte) ((dwPrivilegeMap & 0xFF000000) >> 24);
+        userbuf[offset++] = (byte) ((dwPrivilegeMap & 0x00FF0000) >> 16);
+        userbuf[offset++] = (byte) ((dwPrivilegeMap & 0x0000FF00) >> 8);
+        userbuf[offset++] = (byte) (dwPrivilegeMap & 0x000000FF);
+
+        //dwAccountType,账户类型
+        //dwAccountType,account type
+        userbuf[offset++] = (byte) ((dwAccountType & 0xFF000000) >> 24);
+        userbuf[offset++] = (byte) ((dwAccountType & 0x00FF0000) >> 16);
+        userbuf[offset++] = (byte) ((dwAccountType & 0x0000FF00) >> 8);
+        userbuf[offset++] = (byte) (dwAccountType & 0x000000FF);
+
+
+        if (roomStrLength > 0) {
+            //roomStrLen
+            userbuf[offset++] = (byte) ((roomStrLength & 0xFF00) >> 8);
+            userbuf[offset++] = (byte) (roomStrLength & 0x00FF);
+
+            //roomStr
+            for (; offset < bufLength; ++offset) {
+                userbuf[offset] = (byte) RoomStr.charAt(offset - (bufLength - roomStrLength));
+            }
+        }
+        return userbuf;
+    }
+
+
+}

+ 1 - 0
hx-common/pom.xml

@@ -23,6 +23,7 @@
         <module>library-storage</module>
         <module>library-supply</module>
         <module>common-websocket</module>
+        <module>common-im</module>
     </modules>
 
     <properties>