feat: (REQ-3057) 创建群聊校验优化-超过人数/重复建群

feat: (REQ-3057) 创建群聊返回参数优化

feat: (REQ-3057) 创建群聊告警钉钉群/投诉/rpc封装优化

feat: (REQ-3057) 创建群聊告警钉钉群/投诉/rpc封装优化

feat: (REQ-3057) 创建群聊告警钉钉群/投诉/rpc封装优化

feat: (REQ-3057) 监控群主变更逻辑优化

feat: (REQ-3057) 监控群主变更逻辑优化

feat: (REQ-3057) 监控群主变更逻辑优化

feat: (REQ-3057) 监控群主变更逻辑优化

feat: (REQ-3057) 监控群主变更逻辑优化
This commit is contained in:
xudawei 2024-11-05 18:08:36 +08:00
parent be26aa75e9
commit 1b7337b987
72 changed files with 3518 additions and 301 deletions

View File

@ -48,6 +48,10 @@
<groupId>cn.axzo.apollo</groupId>
<artifactId>apollo-api</artifactId>
</exclusion>
<exclusion>
<artifactId>event-hub-api</artifactId>
<groupId>cn.axzo.event-hub</groupId>
</exclusion>
</exclusions>
</dependency>

View File

@ -2,7 +2,9 @@ package cn.axzo.im.center.api.feign;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.ChatGroupQueryReq;
import cn.axzo.im.center.api.vo.resp.ChatGroupCreateResp;
import cn.axzo.im.center.api.vo.resp.ChatGroupQueryResp;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
@ -23,5 +25,11 @@ public interface ChatGroupApi {
@PostMapping("api/im/chat/group/create")
ApiResult<ChatGroupCreateResp> chatGroupCreate(@RequestBody @Validated ChatGroupCreateReq chatGroupCreateReq);
/**
* 获取群聊
*/
@PostMapping("api/im/chat/group/query")
ApiResult<ChatGroupQueryResp> chatGroupQuery(@RequestBody @Validated ChatGroupQueryReq chatGroupQueryReq);
}

View File

@ -2,6 +2,7 @@ package cn.axzo.im.center.api.feign;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.ComplaintCreateReq;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
@ -20,7 +21,7 @@ public interface ComplaintApi {
* 投诉
*/
@PostMapping("api/im/complaint/create")
ApiResult<Void> complaintCreate(@RequestBody @Validated ChatGroupCreateReq chatGroupCreateReq);
ApiResult<Void> complaintCreate(@RequestBody @Validated ComplaintCreateReq req);
}

View File

@ -1,8 +1,10 @@
package cn.axzo.im.center.api.vo.req;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
@ -21,7 +23,7 @@ import java.io.Serializable;
public class ChatGroupCreateReq implements Serializable {
/**
* 群类型,项目专属群:workspace
* 群类型,项目专属群:WORKSPACE
*/
@NotEmpty(message = "群类型不能为空")
private String groupType;
@ -46,13 +48,33 @@ public class ChatGroupCreateReq implements Serializable {
private Long workspaceId;
/**
* 人群类型,项目:workspace,单位:ou,班组:team
* 人群类型,项目:WORKSPACE,单位:OU,班组:TEAM
*/
@NotEmpty(message = "人群类型不能为空")
private String crowType;
@NotNull(message = "人群类型不能为空")
private CrowTypeEnum crowType;
/**
* 群主账号accid最大长度 32 位字符
*/
private Long owner;
/**
* 创建人
*/
private Long creator;
private Long ouId;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum CrowTypeEnum {
WORKSPACE("workspace", "项目"),
OU("ou", "单位"),
TEAM("team", "班组"),
;
private String code;
private String desc;
}
}

View File

@ -0,0 +1,22 @@
package cn.axzo.im.center.api.vo.req;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatGroupGenericSearchReq {
private Long workspaceId;
private Long ouId;
private Long teamId;
private ChatGroupCreateReq.CrowTypeEnum crowType;
}

View File

@ -0,0 +1,4 @@
package cn.axzo.im.center.api.vo.req;
public class ChatGroupListReq {
}

View File

@ -0,0 +1,36 @@
package cn.axzo.im.center.api.vo.req;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* 群聊创建请求
* @author xudawei
* @date 2024/11/04
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatGroupQueryReq implements Serializable {
/**
* ID 列表["3083","3084"]一次最多查询 10 个群最大长度 1024 位字符
*/
@NotEmpty(message = "tids不能为空")
private List<String> tids;
/**
* 1表示带上群成员列表0表示不带群成员列表只返回群信息
*/
@NotNull(message = "ope不能为空")
private Integer ope;
}

View File

@ -0,0 +1,59 @@
package cn.axzo.im.center.api.vo.req;
import cn.axzo.im.center.common.enums.ChatGroupStatusEnum;
import cn.axzo.im.center.common.enums.ChatGroupUserTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatGroupUserGenericSearchReq {
/**
* 项目Id
*/
private Long workspaceId;
/**
* 单位Id
*/
private Long ouId;
/**
* 班组Id
*/
private Long teamId;
/**
* 人群
*/
private ChatGroupCreateReq.CrowTypeEnum crowType;
/**
* 群聊Id
*/
private Long chatGroupId;
/**
* 网易云信唯一标识网易返回
*/
private String tid;
/**
* IM账号Id
*/
private String accId;
/**
* 类型,OWNER:群主;USER:普通用户
*/
private ChatGroupUserTypeEnum type;
/**
* 状态,SUCCESS:成功;FAIL:失败
*/
private ChatGroupStatusEnum status;
}

View File

@ -1,8 +1,10 @@
package cn.axzo.im.center.api.vo.req;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
@ -25,14 +27,39 @@ public class ComplaintCreateReq {
private String complaintContent;
/**
* 类型私聊:private,群聊:group
* 类型私聊:PRIVATE,群聊:GROUP
*/
@NotNull(message = "类型不能为空")
private String type;
private ChatTypeEnum type;
/**
* 发送人
*/
@NotNull(message = "发送人不能为空")
private String fromId;
/**
* 群聊Id或者IM账号Id根据type而订
*/
@NotNull(message = "群聊Id或者IM账号Id,不能为空")
private String t_acc_id;
private String taccId;
/**
* 创建人
*/
private Long creator;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ChatTypeEnum {
PRIVATE("private", "私聊"),
GROUP("group", "群聊"),
;
private String code;
private String desc;
}
}

View File

@ -17,8 +17,33 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
public class ChatGroupCreateResp {
/**
* code
*/
private Integer code;
/**
* 网易云信服务器产生群唯一标识
*/
private String tid;
private JSONArray faccid;
/**
* 群名称
*/
private String groupName;
/**
* 群头像
*/
private String avatarUrl;
/**
* 描述
*/
private String desc;
}

View File

@ -0,0 +1,161 @@
package cn.axzo.im.center.api.vo.resp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 创建群聊
* @author xudawei@axzo.cn
* @date 2024/11/04
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatGroupQueryResp {
/**
* 状态码
*/
private Integer code;
/**
* 网易云信服务器产生群唯一标识
*/
private List<TInfo> tinfos;
/**
* 描述
*/
private String desc;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class TInfo {
/**
* 群名称
*/
private String tname;
/**
* 群头像
*/
private String icon;
/**
* 群主用户帐号
*/
private String owner;
/**
* 群成员最大数量
*/
private Integer maxusers;
/**
* ID
*/
private Long tid;
/**
* 当前群成员数量
*/
private Integer size;
/**
* 群公告
*/
private String announcement;
/**
* 群介绍
*/
private String intro;
/**
* 申请入群的验证方式
* 0不用验证1需要验证2不允许任何人加入
*/
private Integer joinmode;
/**
* 群创建完成后邀请入群时是否需要被邀请人的同意
* 0需要同意默认1不需要同意
*/
private Integer beinvitemode;
/**
* 邀请权限即谁可以邀请他人入群
* 0群主和管理员默认1所有人
*/
private Integer invitemode;
/**
* 客户端修改群信息权限即谁可以修改群信息
* 0群主和管理员默认1所有人
*/
private Integer uptinfomode;
/**
* 客户端修改群自定义属性权限即谁可以修改群自定义属性
* 0群主和管理员默认1所有人
*/
private Integer upcustommode;
/**
* 群禁言类型
* 0解除禁言1禁言普通成员3禁言整个群包括群主
*/
private Integer muteType;
/**
* 群通知消息是否关闭在线发送开启该功能才会有该字段
*/
private Boolean isNotifyCloseOnline;
/**
* 群通知消息是否关闭持久化存储开启该功能才会有该字段
*/
private Boolean isNotifyClosePersistent;
/**
* 自定义高级群扩展属性
*/
private String custom;
/**
* 客户端自定义字段
*/
private String clientCustom;
/**
* 是否全员禁言
*/
private Boolean mute;
/**
* 管理员账号
*/
private String admins;
/**
* 群成员列表
*/
private String members;
/**
* 创建时间
*/
private Long createtime;
/**
* 更新时间
*/
private Long updatetime;
}
}

View File

@ -0,0 +1,32 @@
package cn.axzo.im.center.common.enums;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 群聊状态
*
* @author xudawei
* @date 2024/11/12
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ChatGroupStatusEnum {
/**
* 成功
*/
SUCCESS("success", "成功"),
/**
* 失败
*/
FAIL("fail", "失败"),
;
private final String code;
private final String message;
}

View File

@ -0,0 +1,32 @@
package cn.axzo.im.center.common.enums;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 群聊用户类型
*
* @author xudawei
* @date 2024/11/13
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ChatGroupUserTypeEnum {
/**
* 群主
*/
OWNER("owner", "群主"),
/**
* 普通用户
*/
USER("user", "普通用户"),
;
private final String code;
private final String message;
}

View File

@ -0,0 +1,45 @@
package cn.axzo.im.center.common.enums;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 群聊状态
*
* @author xudawei
* @date 2024/11/12
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum OperateLogTypeEnum {
/**
* 群聊新增
*/
GROUP_ADD("groupAdd", "群聊新增"),
/**
* 群聊解散
*/
GROUP_DELETE("groupDelete", "群聊解散"),
/**
* 人员新增群聊
*/
USER_ENTER_CHAT_GROUP("userEnterChatGroup", "人员进入群聊"),
/**
* 人员退出群聊
*/
USER_EXIT_CHAT_GROUP("userExitChatGroup", "人员退出群聊"),
/**
* 更改群主
*/
CHANGE_OWNER("changeOwner", "更改群主"),
;
private final String code;
private final String message;
}

View File

@ -109,6 +109,12 @@
<dependency>
<groupId>cn.axzo.maokai</groupId>
<artifactId>maokai-api</artifactId>
<exclusions>
<exclusion>
<artifactId>event-hub-api</artifactId>
<groupId>cn.axzo.event-hub</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -125,6 +131,12 @@
<groupId>cn.axzo.tyr</groupId>
<artifactId>tyr-api</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,56 @@
package cn.axzo.im;
import cn.axzo.framework.data.mybatisplus.config.MybatisPlusAutoConfiguration;
import cn.axzo.im.config.RocketMQEventConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
@Slf4j
//@SpringBootApplication(scanBasePackages = "cn.axzo", exclude = MybatisPlusAutoConfiguration.class)
//@EnableFeignClients(basePackages = {"cn.axzo"})
//@MapperScan(value = {"cn.axzo.im.dao.mapper"})
//@EnableDiscoveryClient
//@Import(RocketMQEventConfiguration.class)
public class ImCenterDevApplication {
public static void main(String[] args) {
System.setProperty("spring.profiles.active","dev");
System.setProperty("NACOS_HOST","https://dev-nacos.axzo.cn");
System.setProperty("NACOS_PORT","443");
System.setProperty("NACOS_NAMESPACE_ID","35eada10-9574-4db8-9fea-bc6a4960b6c7");
System.setProperty("CUSTOM_ENV","dev");
System.setProperty("spring.redis.port","6379");
System.setProperty("spring.redis.host","172.16.2.219");
System.setProperty("spring.redis.password","!rHV2!fctYtV4vF");
System.setProperty("xxl.job.admin.addresses","http://dev-xxl-job.axzo.cn/xxl-job-admin");
System.setProperty("rocketmq.name-server", "172.16.2.82:9876");
SpringApplication application = new SpringApplication(ImCenterDevApplication.class);
ApplicationContext applicationContext = application.run(args);
Environment env = applicationContext.getEnvironment();
log.info(
"--------------------------------------------------------------------------------------------------------------------\n" +
"Application 【{}】 is running on 【{}】 environment!\n" +
"Api Local: \thttp://127.0.0.1:{}\n" +
"Mysql: \t{}\t username:{}\n" +
"Redis: \t{}:{}\t database:{}\n" +
"RabbitMQ: \t{}\t username:{}",
env.getProperty("spring.application.name"),
env.getProperty("spring.profiles.active"),
env.getProperty("server.port"),
env.getProperty("spring.datasource.url"),
env.getProperty("spring.datasource.username"),
env.getProperty("spring.redis.host"),
env.getProperty("spring.redis.port"),
env.getProperty("spring.redis.database"),
env.getProperty("spring.rabbitmq.addresses"),
env.getProperty("spring.rabbitmq.username") +
"\n----------------------------------------------------------");
}
}

View File

@ -1,7 +1,11 @@
package cn.axzo.im.channel;
import cn.axzo.im.channel.netease.dto.ChangeOwnerRequest;
import cn.axzo.im.channel.netease.dto.ChangeOwnerResponse;
import cn.axzo.im.channel.netease.dto.ChatGroupCreateRequest;
import cn.axzo.im.channel.netease.dto.ChatGroupCreateResponse;
import cn.axzo.im.channel.netease.dto.ChatGroupQueryResponse;
import cn.axzo.im.channel.netease.dto.KickChatGroupRequest;
import cn.axzo.im.channel.netease.dto.MessageBatchDispatchRequest;
import cn.axzo.im.channel.netease.dto.MessageBatchDispatchResponse;
import cn.axzo.im.channel.netease.dto.MessageCustomDispatchRequest;
@ -15,6 +19,7 @@ import cn.axzo.im.channel.netease.dto.UserAddChatGroupRequest;
import cn.axzo.im.channel.netease.dto.UserAddChatGroupResponse;
import javax.validation.Valid;
import java.util.List;
/**
* im-center
@ -80,6 +85,11 @@ public interface IMChannelProvider {
*/
RegisterResponse updateAccountProfile(RegisterUpdateRequest updateProfile);
/**
* 踢人出群
*/
UserAddChatGroupResponse kickChatGroup(KickChatGroupRequest request);
/**
* 创建群聊
*/
@ -89,4 +99,16 @@ public interface IMChannelProvider {
* 用户加入群聊
*/
UserAddChatGroupResponse userAddChatGroup(UserAddChatGroupRequest request);
/**
* 获取群聊
* @param tids ID 列表
* @param ope 1表示带上群成员列表0表示不带群成员列表只返回群信息
*/
ChatGroupQueryResponse chatGroupQueryByTids(List<String> tids, Integer ope);
/**
* 转让群主
*/
ChangeOwnerResponse changeOwner(ChangeOwnerRequest request);
}

View File

@ -3,8 +3,12 @@ package cn.axzo.im.channel.netease;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.im.channel.IMChannelProvider;
import cn.axzo.im.channel.netease.dto.ChangeOwnerRequest;
import cn.axzo.im.channel.netease.dto.ChangeOwnerResponse;
import cn.axzo.im.channel.netease.dto.ChatGroupCreateRequest;
import cn.axzo.im.channel.netease.dto.ChatGroupCreateResponse;
import cn.axzo.im.channel.netease.dto.ChatGroupQueryResponse;
import cn.axzo.im.channel.netease.dto.KickChatGroupRequest;
import cn.axzo.im.channel.netease.dto.MessageBatchDispatchRequest;
import cn.axzo.im.channel.netease.dto.MessageBatchDispatchResponse;
import cn.axzo.im.channel.netease.dto.MessageCustomDispatchRequest;
@ -24,6 +28,7 @@ import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import lombok.extern.slf4j.Slf4j;
@ -34,7 +39,9 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
@ -68,6 +75,21 @@ public class NimChannelService implements IMChannelProvider {
*/
private static final String USER_ADD_CHAT_GROUP = "https://api.netease.im/nimserver/team/add.action";
/**
* 踢人出群
*/
private static final String KICK_CHAT_GROUP = "https://api.netease.im/nimserver/team/kick.action";
/**
* 获取群聊
*/
private static final String CHAT_GROUP_QUERY = "https://api.netease.im/nimserver/team/query.action";
/**
* 转让群主
*/
private static final String CHANGE_OWNER = "https://api.netease.im/nimserver/team/changeOwner.action";
public static final int SUCCESS_CODE = 200;
public static final int RATE_LIMITED_CODE = 416;
@ -75,10 +97,6 @@ public class NimChannelService implements IMChannelProvider {
private static final int NIM_ACCOUNT_ALREADY_REGISTER = 414;
private static final String PROVIDER_NAME = "NIM";
/**
* 消息接收者的网易云信IM账号accid列表上限是500人,但是返回msgid最多100人,故目前设置100人
*/
private static final int NIM_MSG_BATCH_MAX_NUM = 100;
/**
* 目前支持 100自定义消息
*/
@ -359,19 +377,22 @@ public class NimChannelService implements IMChannelProvider {
HttpResponse response = HttpRequest.post(CHAT_GROUP_CREATE).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
String result = response.body();
if (response.getStatus() == SUCCESS_CODE) {
ChatGroupCreateResponse chatGroupCreateResponse = JSONUtil.toBean(result, ChatGroupCreateResponse.class);
if (chatGroupCreateResponse == null) {
return ChatGroupCreateResponse.builder().desc("chatGroupCreate-请求网易云信Server异常[" + result + "],请联系管理员!").build();
}
if (chatGroupCreateResponse.getCode() != SUCCESS_CODE) {
log.warn("chatGroupCreate-请求网易云信Server:{},返回异常:{}", CHAT_GROUP_CREATE, result);
}
return chatGroupCreateResponse;
} else {
log.info("chatGroupCreate-请求网易云信,result:{}", result);
if (response.getStatus() != SUCCESS_CODE) {
log.error("chatGroupCreate-请求网易云信Server:{},异常:{}", CHAT_GROUP_CREATE, result);
ChatGroupCreateResponse chatGroupCreateResponse = JSONUtil.toBean(result, ChatGroupCreateResponse.class);
throw new ServiceException("创建群聊异常," + chatGroupCreateResponse.getDesc());
}
return ChatGroupCreateResponse.builder().desc("chatGroupCreate-请求网易云信Server异常,请联系管理员!").build();
ChatGroupCreateResponse chatGroupCreateResponse = JSONUtil.toBean(result, ChatGroupCreateResponse.class);
if (chatGroupCreateResponse == null) {
log.warn("chatGroupCreate-请求网易云信Server:{},返回异常:{}", CHAT_GROUP_CREATE, result);
throw new ServiceException("创建群聊异常," + chatGroupCreateResponse.getDesc());
}
if (chatGroupCreateResponse.getCode() != SUCCESS_CODE) {
log.warn("chatGroupCreate-请求网易云信Server:{},返回异常:{}", CHAT_GROUP_CREATE, result);
throw new ServiceException("创建群聊异常," + chatGroupCreateResponse.getDesc());
}
return chatGroupCreateResponse;
}
/**
@ -399,6 +420,10 @@ public class NimChannelService implements IMChannelProvider {
*/
@Override
public UserAddChatGroupResponse userAddChatGroup(UserAddChatGroupRequest request) {
if (Objects.isNull(request) || CollectionUtils.isEmpty(request.getMembers())) {
log.info("userAddChatGroup-请求网易云信,members is empty");
return UserAddChatGroupResponse.builder().build();
}
//构建用户加入群聊
HashMap<String, Object> paramMap = this.buildUserAddChatGroup(request);
@ -408,21 +433,67 @@ public class NimChannelService implements IMChannelProvider {
HttpResponse response = HttpRequest.post(USER_ADD_CHAT_GROUP).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
String result = response.body();
if (response.getStatus() == SUCCESS_CODE) {
UserAddChatGroupResponse userAddChatGroupResponse = JSONUtil.toBean(result, UserAddChatGroupResponse.class);
if (userAddChatGroupResponse == null) {
return UserAddChatGroupResponse.builder().desc("userAddChatGroup-请求网易云信Server异常[" + result + "],请联系管理员!").build();
}
if (userAddChatGroupResponse.getCode() != SUCCESS_CODE) {
log.warn("userAddChatGroup-请求网易云信Server:{},返回异常:{}", CHAT_GROUP_CREATE, result);
}
return userAddChatGroupResponse;
} else {
log.info("userAddChatGroup-请求网易云信,result:{}", result);
if (response.getStatus() != SUCCESS_CODE) {
log.error("userAddChatGroup-请求网易云信Server:{},异常:{}", CHAT_GROUP_CREATE, result);
throw new ServiceException("userAddChatGroup-请求网易云信Server异常,result:" + result);
}
return UserAddChatGroupResponse.builder().desc("userAddChatGroup-请求网易云信Server异常,请联系管理员!").build();
UserAddChatGroupResponse userAddChatGroupResponse = JSONUtil.toBean(result, UserAddChatGroupResponse.class);
if (userAddChatGroupResponse == null) {
throw new ServiceException("userAddChatGroup-请求网易云信Server异常[" + result + "],请联系管理员!");
}
if (userAddChatGroupResponse.getCode() != SUCCESS_CODE) {
log.warn("userAddChatGroup-请求网易云信Server:{},返回异常:{}", CHAT_GROUP_CREATE, result);
throw new ServiceException(userAddChatGroupResponse.getCode(), "userAddChatGroup-请求网易云信Server异常,result:" + userAddChatGroupResponse.getDesc());
}
return userAddChatGroupResponse;
}
/**
* 踢人出群
*/
@Override
public UserAddChatGroupResponse kickChatGroup(KickChatGroupRequest request) {
//构建用户加入群聊
HashMap<String, Object> paramMap = this.buildKickChatGroup(request);
Map<String, String> authHeaderMap = buildAuthHeader(getProviderAppKey(), getProviderAppSecret());
log.info("kickChatGroup-请求网易云信,URL:{},Header:{},请求参数:{}", KICK_CHAT_GROUP,
JSONUtil.toJsonStr(authHeaderMap), JSONUtil.toJsonStr(paramMap));
HttpResponse response = HttpRequest.post(KICK_CHAT_GROUP).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
String result = response.body();
log.info("kickChatGroup-请求网易云信,result:{}", result);
if (response.getStatus() != SUCCESS_CODE) {
log.error("kickChatGroup-请求网易云信Server:{},异常:{}", KICK_CHAT_GROUP, result);
throw new ServiceException("kickChatGroup-请求网易云信Server:" + KICK_CHAT_GROUP +",异常:" + result);
}
UserAddChatGroupResponse kickChatGroupResponse = JSONUtil.toBean(result, UserAddChatGroupResponse.class);
if (kickChatGroupResponse == null) {
throw new ServiceException("kickChatGroup-请求网易云信Server异常[" + result + "],请联系管理员!");
}
if (kickChatGroupResponse.getCode() != SUCCESS_CODE) {
log.warn("kickChatGroup-请求网易云信Server:{},返回异常:{}", KICK_CHAT_GROUP, result);
throw new ServiceException(kickChatGroupResponse.getCode(), "kickChatGroup-请求网易云信Server:"+KICK_CHAT_GROUP+",返回异常:" + result);
}
return kickChatGroupResponse;
}
/**
* 构建创建群聊对象
*/
private HashMap<String, Object> buildKickChatGroup(KickChatGroupRequest request) {
HashMap<String, Object> paramMap = Maps.newHashMap();
paramMap.put("tid", request.getTid());
paramMap.put("owner", request.getOwner());
paramMap.put("members", request.getMembers());
paramMap.put("attach",request.getAttach());
return paramMap;
}
/**
* 构建创建群聊对象
*/
@ -430,10 +501,91 @@ public class NimChannelService implements IMChannelProvider {
HashMap<String, Object> paramMap = Maps.newHashMap();
paramMap.put("tid", request.getTid());
paramMap.put("owner", request.getOwner());
paramMap.put("members", request.getMembers());
paramMap.put("members", JSON.toJSONString(request.getMembers()));
paramMap.put("magree", 0);
paramMap.put("msg",request.getMsg());
return paramMap;
}
@Override
public ChatGroupQueryResponse chatGroupQueryByTids(List<String> tids, Integer ope) {
//构建创建群聊对象
HashMap<String, Object> paramMap = this.buildChatGroupQuery(tids, ope);
Map<String, String> authHeaderMap = buildAuthHeader(getProviderAppKey(), getProviderAppSecret());
log.info("chatGroupQuery-请求网易云信,URL:{},Header:{},请求参数:{}", CHAT_GROUP_QUERY,
JSONUtil.toJsonStr(authHeaderMap), JSONUtil.toJsonStr(paramMap));
HttpResponse response = HttpRequest.post(CHAT_GROUP_QUERY).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
String result = response.body();
log.info("chatGroupQuery-请求网易云信,result:{}", result);
if (response.getStatus() != SUCCESS_CODE) {
log.error("chatGroupQuery-请求网易云信Server:{},异常:{}", CHAT_GROUP_QUERY, result);
return ChatGroupQueryResponse.builder().desc("chatGroupQuery-请求网易云信Server异常,请联系管理员!").build();
}
ChatGroupQueryResponse chatGroupQueryResponse = JSONUtil.toBean(result, ChatGroupQueryResponse.class);
if (chatGroupQueryResponse == null) {
return ChatGroupQueryResponse.builder().desc("chatGroupQuery-请求网易云信Server异常[" + result + "],请联系管理员!").build();
}
if (chatGroupQueryResponse.getCode() != SUCCESS_CODE) {
log.warn("chatGroupQuery-请求网易云信Server:{},返回异常:{}", CHAT_GROUP_QUERY, result);
}
return chatGroupQueryResponse;
}
/**
* 构建获取群聊对象
*/
private HashMap<String, Object> buildChatGroupQuery(List<String> tids, Integer ope) {
HashMap<String, Object> paramMap = Maps.newHashMap();
paramMap.put("tids", JSON.toJSONString(tids));
paramMap.put("ope", ope);
return paramMap;
}
/**
* 转让群主
*/
public ChangeOwnerResponse changeOwner(ChangeOwnerRequest request) {
//构建创建群聊对象
HashMap<String, Object> paramMap = this.buildChangeOwner(request);
Map<String, String> authHeaderMap = buildAuthHeader(getProviderAppKey(), getProviderAppSecret());
log.info("changeOwner-请求网易云信,URL:{},Header:{},请求参数:{}", CHANGE_OWNER,
JSONUtil.toJsonStr(authHeaderMap), JSONUtil.toJsonStr(paramMap));
HttpResponse response = HttpRequest.post(CHANGE_OWNER).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
String result = response.body();
log.info("changeOwner-请求网易云信,result:{}", result);
if (response.getStatus() != SUCCESS_CODE) {
log.error("changeOwner-请求网易云信Server:{},异常:{}", CHANGE_OWNER, result);
throw new ServiceException("changeOwner-请求网易云信Server:" + KICK_CHAT_GROUP +",异常:" + result);
}
ChangeOwnerResponse changeOwnerResponse = JSONUtil.toBean(result, ChangeOwnerResponse.class);
if (changeOwnerResponse == null) {
log.error("changeOwner-请求网易云信Server:{},异常:{}", CHANGE_OWNER, result);
throw new ServiceException("changeOwner-请求网易云信Server:" + KICK_CHAT_GROUP +",异常:" + result);
}
if (changeOwnerResponse.getCode() != SUCCESS_CODE) {
log.error("changeOwner-请求网易云信Server:{},异常:{}", CHANGE_OWNER, result);
throw new ServiceException("changeOwner-请求网易云信Server:" + KICK_CHAT_GROUP +",异常:" + result);
}
return changeOwnerResponse;
}
/**
* 构建转让群主
*/
private HashMap<String, Object> buildChangeOwner(ChangeOwnerRequest request) {
HashMap<String, Object> paramMap = Maps.newHashMap();
paramMap.put("tid", request.getTid());
paramMap.put("owner", request.getOwner());
paramMap.put("newowner", request.getNewowner());
paramMap.put("leave", request.getLeave());
paramMap.put("attach", request.getAttach());
return paramMap;
}
}

View File

@ -0,0 +1,41 @@
package cn.axzo.im.channel.netease.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 转让群主
* 文档地址:https://doc.yunxin.163.com/messaging/server-apis/jM0MjQzODA?platform=server
* @author xudawei@axzo.cn
* @date 2024/11/08
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ChangeOwnerRequest {
/**
* 云信服务器产生群组唯一标识创建群时会返回最大长度 64 位长整型
*/
private String tid;
/**
* 邀请人的用户帐号accid最大长度 32 位字符按照群属性 invitemode0只有群主和管理员可以邀请他人入群1所有人都可以邀请配置传入
*/
private String owner;
/**
* 新群主帐号accid最大长度 32 位字符
*/
private String newowner;
/**
* 1群主身份转让后离开群2群主身份转让后成为普通成员
*/
private Integer leave;
/**
* 自定义扩展字段最大长度 512 位字符
*/
private String attach;
}

View File

@ -0,0 +1,32 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.center.api.vo.resp.ChatGroupCreateResp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 转让群主
* @author xudawei@axzo.cn
* @date 2024/11/08
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChangeOwnerResponse {
/**
* 状态码
*/
private Integer code;
/**
* 描述
*/
private String desc;
}

View File

@ -1,6 +1,8 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -47,17 +49,23 @@ public class ChatGroupCreateRequest {
*/
private String icon;
/**
* 自定义扩展字段最大长度 512 位字符
*/
private String attach;
/**
* 对象转换
*/
public static ChatGroupCreateRequest convertRequest(ChatGroupCreateReq req, String owner) {
public static ChatGroupCreateRequest convertRequest(ChatGroupCreateReq req, String owner, String attach) {
return ChatGroupCreateRequest.builder()
.tname(req.getGroupName())
.owner(owner)
.members(owner)
.members(JSON.toJSONString(Lists.newArrayList(owner)))
.msg("进入群聊," + req.getGroupName())
.icon(req.getAvatarUrl())
.attach(attach)
.build();
}

View File

@ -42,9 +42,11 @@ public class ChatGroupCreateResponse {
private String desc;
public static ChatGroupCreateResp convertResp(ChatGroupCreateResponse response) {
public static ChatGroupCreateResp convertResp(ChatGroupCreateResponse response, String groupName, String avatarUrl) {
return ChatGroupCreateResp.builder()
.tid(response.getTid())
.groupName(groupName)
.avatarUrl(avatarUrl)
.build();
}
}

View File

@ -0,0 +1,163 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.center.api.vo.resp.ChatGroupCreateResp;
import com.alibaba.fastjson.JSONArray;
import io.swagger.models.auth.In;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 获取群聊
* @author xudawei@axzo.cn
* @date 2024/11/08
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChatGroupQueryResponse {
/**
* 状态码
*/
private Integer code;
/**
* 网易云信服务器产生群唯一标识
*/
private List<TInfo> tinfos;
/**
* 描述
*/
private String desc;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class TInfo {
/**
* 群名称
*/
private String tname;
/**
* 群头像
*/
private String icon;
/**
* 群主用户帐号
*/
private String owner;
/**
* 群成员最大数量
*/
private Integer maxusers;
/**
* ID
*/
private Long tid;
/**
* 当前群成员数量
*/
private Integer size;
/**
* 群公告
*/
private String announcement;
/**
* 群介绍
*/
private String intro;
/**
* 申请入群的验证方式
* 0不用验证1需要验证2不允许任何人加入
*/
private Integer joinmode;
/**
* 群创建完成后邀请入群时是否需要被邀请人的同意
* 0需要同意默认1不需要同意
*/
private Integer beinvitemode;
/**
* 邀请权限即谁可以邀请他人入群
* 0群主和管理员默认1所有人
*/
private Integer invitemode;
/**
* 客户端修改群信息权限即谁可以修改群信息
* 0群主和管理员默认1所有人
*/
private Integer uptinfomode;
/**
* 客户端修改群自定义属性权限即谁可以修改群自定义属性
* 0群主和管理员默认1所有人
*/
private Integer upcustommode;
/**
* 群禁言类型
* 0解除禁言1禁言普通成员3禁言整个群包括群主
*/
private Integer muteType;
/**
* 群通知消息是否关闭在线发送开启该功能才会有该字段
*/
private Boolean isNotifyCloseOnline;
/**
* 群通知消息是否关闭持久化存储开启该功能才会有该字段
*/
private Boolean isNotifyClosePersistent;
/**
* 自定义高级群扩展属性
*/
private String custom;
/**
* 客户端自定义字段
*/
private String clientCustom;
/**
* 是否全员禁言
*/
private Boolean mute;
/**
* 管理员账号
*/
private String admins;
/**
* 群成员列表
*/
private String members;
/**
* 创建时间
*/
private Long createtime;
/**
* 更新时间
*/
private Long updatetime;
}
}

View File

@ -0,0 +1,43 @@
package cn.axzo.im.channel.netease.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 踢人出群
* 文档地址:https://doc.yunxin.163.com/messaging/server-apis/TY4MDA5ODE?platform=server
* @author xudawei@axzo.cn
* @date 2024/11/08
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class KickChatGroupRequest {
/**
* 云信服务器产生群组唯一标识创建群时会返回最大长度 64 位长整型
*/
private String tid;
/**
* 邀请人的用户帐号accid最大长度 32 位字符按照群属性 invitemode0只有群主和管理员可以邀请他人入群1所有人都可以邀请配置传入
*/
private String owner;
/**
* 当移除单个成员时必填
* 被移除的用户账号 accid最大长度 32 位字符
* 相对于 members优先使用 member 参数
*/
private String member;
/**
* 被邀请入群的用户列表\["aaa","bbb"\](JSONArray 对应的 accid如果解析出错会报 414)一次最多邀请 200 个成员
*/
private String members;
/**
* 自定义扩展字段最大长度 512 位字符
*/
private String attach;
}

View File

@ -5,6 +5,8 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 用户加入群聊
* 文档地址:https://doc.yunxin.163.com/messaging/server-apis/DA2MzQ0MzA?platform=server
@ -27,13 +29,13 @@ public class UserAddChatGroupRequest {
/**
* 被邀请入群的用户列表\["aaa","bbb"\](JSONArray 对应的 accid如果解析出错会报 414)一次最多邀请 200 个成员
*/
private String members;
private List<String> members;
/**
* 邀请发送的文字最大长度 150 位字符
*/
private String msg;
public static UserAddChatGroupRequest buildRequest(String tid, String owner, String members, String msg) {
public static UserAddChatGroupRequest buildRequest(String tid, String owner, List<String> members, String msg) {
return UserAddChatGroupRequest.builder()
.tid(tid)
.owner(owner)

View File

@ -14,7 +14,16 @@ public enum BizResultCode implements ResultCode {
ALL_PERSSON_TYPE_NOT_EMPTY("103", "全员发送时,接收端不能为空"),
ACQUIRE_RATE_LIMITER_FAIL("104", "获取滑动窗口令牌失败"),
MESSAGE_TASK_STATUS_ERROR("105", "更新消息任务失败,状态异常"),
MESSAGE_TASK_NOT_FOUND("106", "消息任务不存在"),;
MESSAGE_TASK_NOT_FOUND("106", "消息任务不存在"),
/**
* 群聊
*/
CHAT_GROUP_ALREADY_EXISTS("501", "群聊已经存在"),
CHAT_GROUP_OVER_MEMBERS_COUNT_LIMIT("502", "超过人数限制"),
;
private String errorCode;

View File

@ -0,0 +1,29 @@
package cn.axzo.im.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 群聊配置
*
* @author xudawei@axzo.cn
* @date 2024/11/12
*/
@Slf4j
@Configuration
@RefreshScope
@Data
public class ChatGroupConfig {
//TODO
@Value("${chatgroup.dingTalkBotAccessToken:1}")
private String dingTalkBotAccessToken;
//TODO
@Value("${chatgroup.dingTalkBotSecret:1}")
private String dingTalkBotSecret;
//TODO
@Value("${chatgroup.dingTalkBotEnabled:false}")
private Boolean dingTalkBotEnabled;
}

View File

@ -92,7 +92,7 @@ public class RocketMQEventConfiguration {
@Slf4j
@Component
@RocketMQMessageListener(topic = "topic_profile_${spring.profiles.active}",
@RocketMQMessageListener(topic = "topic_im_center_${spring.profiles.active}",
consumerGroup = "GID_chat_group_create_${spring.application.name}_${spring.profiles.active}",
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}"
@ -108,4 +108,61 @@ public class RocketMQEventConfiguration {
super.onEvent(message, eventConsumer);
}
}
@Slf4j
@Component
@RocketMQMessageListener(topic = "topic_profile_${spring.profiles.active}",
consumerGroup = "GID_chat_group_create_${spring.application.name}_${spring.profiles.active}",
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}"
)
public static class PersonProfileAvatarUpdateListener extends BaseListener implements RocketMQListener<MessageExt> {
@Autowired
private EventConsumer eventConsumer;
@Override
public void onMessage(MessageExt message) {
log.info("PersonProfileAvatarUpdateListener onMessage,message:{}", JSON.toJSONString(message));
super.onEvent(message, eventConsumer);
}
}
@Slf4j
@Component
@RocketMQMessageListener(topic = "topic_organizational_${spring.profiles.active}",
consumerGroup = "GID_chat_group_create_${spring.application.name}_${spring.profiles.active}",
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}"
)
public static class OrganizationalNodeUserChangeListener extends BaseListener implements RocketMQListener<MessageExt> {
@Autowired
private EventConsumer eventConsumer;
@Override
public void onMessage(MessageExt message) {
log.info("OrganizationalNodeUserChangeListener onMessage,message:{}", JSON.toJSONString(message));
super.onEvent(message, eventConsumer);
}
}
@Slf4j
@Component
@RocketMQMessageListener(topic = "topic_tyr_${spring.profiles.active}",
consumerGroup = "GID_chat_group_change_owner_${spring.application.name}_${spring.profiles.active}",
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}"
)
public static class ChatGroupChangeOwnerListener extends BaseListener implements RocketMQListener<MessageExt> {
@Autowired
private EventConsumer eventConsumer;
@Override
public void onMessage(MessageExt message) {
log.info("ChatGroupChangeOwnerListener onMessage,message:{}", JSON.toJSONString(message));
super.onEvent(message, eventConsumer);
}
}
}

View File

@ -3,13 +3,14 @@ package cn.axzo.im.controller;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.feign.ChatGroupApi;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.ChatGroupQueryReq;
import cn.axzo.im.center.api.vo.resp.ChatGroupCreateResp;
import cn.axzo.im.channel.netease.dto.ChatGroupCreateRequest;
import cn.axzo.im.channel.netease.dto.ChatGroupCreateResponse;
import cn.axzo.im.service.AccountService;
import cn.axzo.im.center.api.vo.resp.ChatGroupQueryResp;
import cn.axzo.im.service.ChatGroupService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@ -29,6 +30,15 @@ public class ChatGroupController implements ChatGroupApi {
@Override
public ApiResult<ChatGroupCreateResp> chatGroupCreate(ChatGroupCreateReq req) {
return ApiResult.ok(chatGroupService.chatGroupCreate(req));
return chatGroupService.chatGroupCreate(req);
}
/**
* 获取群聊
*/
@Override
public ApiResult<ChatGroupQueryResp> chatGroupQuery(@RequestBody @Validated ChatGroupQueryReq req) {
return ApiResult.ok(chatGroupService.chatGroupQuery(req));
}
}

View File

@ -4,9 +4,12 @@ import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.feign.ChatGroupApi;
import cn.axzo.im.center.api.feign.ComplaintApi;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.ComplaintCreateReq;
import cn.axzo.im.center.api.vo.resp.ChatGroupCreateResp;
import cn.axzo.im.service.ComplaintService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
/**
@ -19,9 +22,12 @@ import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
public class ComplaintController implements ComplaintApi {
@Autowired
private ComplaintService complaintService;
@Override
public ApiResult<Void> complaintCreate(ChatGroupCreateReq chatGroupCreateReq) {
return null;
public ApiResult<Void> complaintCreate(ComplaintCreateReq req) {
complaintService.complaintCreate(req);
return ApiResult.ok();
}
}

View File

@ -1,11 +0,0 @@
package cn.axzo.im.dao.mapper;
import cn.axzo.im.entity.BizData;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
/**
* @author yanglin
*/
public interface BizDataMapper extends BaseMapper<BizData> {
}

View File

@ -1,10 +0,0 @@
package cn.axzo.im.dao.mapper;
import cn.axzo.im.entity.BizLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author yanglin
*/
public interface BizLogMapper extends BaseMapper<BizLog> {
}

View File

@ -0,0 +1,12 @@
package cn.axzo.im.dao.mapper;
import cn.axzo.im.entity.ChatGroupUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author xudawei@axzo.cn
* @date 2024/11/05
* @desc 群聊
*/
public interface ChatGroupUserMapper extends BaseMapper<ChatGroupUser> {
}

View File

@ -0,0 +1,13 @@
package cn.axzo.im.dao.mapper;
import cn.axzo.im.entity.ChatGroup;
import cn.axzo.im.entity.Complaint;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author xudawei@axzo.cn
* @date 2024/11/05
* @desc 群聊
*/
public interface ComplaintMapper extends BaseMapper<Complaint> {
}

View File

@ -0,0 +1,12 @@
package cn.axzo.im.dao.mapper;
import cn.axzo.im.entity.OperateLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author xudawei@axzo.cn
* @date 2024/11/12
* @desc 操作日志
*/
public interface OperateLogMapper extends BaseMapper<OperateLog> {
}

View File

@ -1,13 +0,0 @@
package cn.axzo.im.dao.repository;
import cn.axzo.im.dao.mapper.BizDataMapper;
import cn.axzo.im.entity.BizData;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Repository;
/**
* @author yanglin
*/
@Repository("bizDataDao")
public class BizDataDao extends ServiceImpl<BizDataMapper, BizData> {
}

View File

@ -1,13 +0,0 @@
package cn.axzo.im.dao.repository;
import cn.axzo.im.dao.mapper.BizLogMapper;
import cn.axzo.im.entity.BizLog;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Repository;
/**
* @author yanglin
*/
@Repository("bizLogDao")
public class BizLogDao extends ServiceImpl<BizLogMapper, BizLog> {
}

View File

@ -1,47 +0,0 @@
package cn.axzo.im.entity;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.utils.YesNo;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @author yanglin
*/
@Setter
@Getter
@TableName(value = "im_biz_data", autoResultMap = true)
public class BizData {
private Long id;
private String bizId;
private Long taskId;
private Long receiverPersonId;
private Long receiverOuId;
private AppTypeEnum appType;
private YesNo valid;
private String bizMessageId;
private Long initHistoryId;
private Long updateHistoryId;
private Long retryHistoryId;
private JSONObject messageBody;
private JSONObject bizBody;
private Long dataVersion;
private Long ackDataVersion;
private Date ackTime;
private Integer retryCount;
private RecordExt recordExt;
private Long isDelete;
private Date createAt;
private Date updateAt;
@Getter
@Setter
public static class RecordExt {
}
}

View File

@ -1,28 +0,0 @@
package cn.axzo.im.entity;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @author yanglin
*/
@Setter
@Getter
@TableName(value = "im_biz_log", autoResultMap = true)
public class BizLog {
private Long id;
private String bizId;
private String bizMessageId;
private Long initHistoryId;
private Long updateHistoryId;
private JSONObject messageBody;
private JSONObject bizBody;
private Long dataVersion;
private Long isDelete;
private Date createAt;
private Date updateAt;
}

View File

@ -1,5 +1,6 @@
package cn.axzo.im.entity;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@ -47,45 +48,57 @@ public class ChatGroup implements Serializable {
* 群头像
*/
@TableField("avatar_url")
private String avatar_url;
private String avatarUrl;
/**
* 项目Id
*/
@TableField("workspace_id")
private Long workspace_id;
private Long workspaceId;
/**
* 单位Id
*/
@TableField("ou_id")
private Long ouId;
/**
* 班组Id
*/
@TableField("team_id")
private Long teamId;
/**
* 群类型,项目专属群:workspace
*/
@TableField("group_type")
private String group_type;
private String groupType;
/**
* 人群类型,项目:workspace,单位:ou,班组:team
*/
@TableField("crow_type")
private String crow_type;
/**
* 关联id,单位Id或者班组Id或者项目Id
*/
@TableField("relate_id")
private Long relate_id;
/**
* 群主名称
*/
@TableField("group_owner_name")
private String group_owner_name;
private ChatGroupCreateReq.CrowTypeEnum crowType;
/**
* 群主
*/
@TableField("group_owner")
private Long group_owner;
private String groupOwner;
/**
* 状态成功:success;失败:fail
*/
@TableField("status")
private String status;
/**
* 备注目前记录创建群失败原因
*/
@TableField("remark")
private String remark;
/**
* 创建人
@ -97,7 +110,7 @@ public class ChatGroup implements Serializable {
* 是否删除
*/
@TableField("is_delete")
private Integer isDelete;
private Long isDelete;
/**
* 创建时间

View File

@ -0,0 +1,103 @@
package cn.axzo.im.entity;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.common.enums.ChatGroupStatusEnum;
import cn.axzo.im.center.common.enums.ChatGroupUserTypeEnum;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import java.io.Serializable;
import java.util.Date;
/**
* 群聊成员
* @author xudawei@axzo.cn
* @date 2024/11/12
*/
@TableName("im_chat_group_user")
@Data
@SuperBuilder
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ChatGroupUser implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/**
* 群聊Id
*/
@TableField("chat_group_id")
private Long chatGroupId;
/**
* 网易云信唯一标识网易返回
*/
@TableField("tid")
private String tid;
/**
* IM账号Id
*/
@TableField("acc_id")
private String accId;
/**
* 类型,OWNER:群主;USER:普通用户
*/
@TableField("type")
private ChatGroupUserTypeEnum type;
/**
* 状态,success:成功;fail:失败
*/
@TableField("status")
private ChatGroupStatusEnum status;
/**
* 状态,success:成功;fail:失败
*/
@TableField("crow_type")
private ChatGroupCreateReq.CrowTypeEnum crowType;
/**
* 备注目前记录群成员失败加入群原因
*/
@TableField("remark")
private String remark;
/**
* 创建人
*/
@TableField("creator")
private Long creator;
/**
* 是否删除,0:未删除;非0:已删除
*/
@TableField("is_delete")
private Long isDelete;
/**
* 创建时间
*/
@TableField("create_at")
private Date createAt;
/**
* 更新时间
*/
@TableField("update_at")
private Date updateAt;
}

View File

@ -1,5 +1,6 @@
package cn.axzo.im.entity;
import cn.axzo.im.center.api.vo.req.ComplaintCreateReq;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@ -41,31 +42,26 @@ public class Complaint implements Serializable {
* 类型私聊:private,群聊:group
*/
@TableField("type")
private String type;
private ComplaintCreateReq.ChatTypeEnum type;
/**
* 发送人Id
*/
@TableField("from_id")
private Long fromId;
/**
* 发送人名称
*/
@TableField("from_name")
private String fromName;
private String fromId;
/**
* 接收人Id
*/
@TableField("to_id")
private Long toId;
private String toId;
/**
* 接收人名称
* 群Id
*/
@TableField("to_name")
private String toName;
@TableField("tid")
private String tid;
/**
* 创建人
@ -77,7 +73,7 @@ public class Complaint implements Serializable {
* 是否删除
*/
@TableField("is_delete")
private Integer isDelete;
private Long isDelete;
/**
* 创建时间

View File

@ -1,5 +1,6 @@
package cn.axzo.im.entity;
import cn.axzo.im.center.common.enums.OperateLogTypeEnum;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@ -14,47 +15,28 @@ import java.io.Serializable;
import java.util.Date;
/**
* 群聊日志
* 操作日志
* @author xudawei@axzo.cn
* @date 2024/11/05
*/
@TableName("im_chat_group_change_log")
@TableName("im_operate_log")
@Data
@SuperBuilder
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ChatGroupChangeLog implements Serializable {
public class OperateLog implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/**
* 群聊名称
*/
@TableField("name")
private String name;
/**
* 群聊Id
*/
@TableField("group_chat_id")
private String group_chat_id;
/**
* 项目Id
*/
@TableField("workspace_id")
private Long workspace_id;
/**
* 变更类型,群变更:groupAdd;群变更:groupUpdate;人员变更:userAdd;人员变更:userUpdate
*/
@TableField("change_type")
private String change_type;
@TableField("type")
private OperateLogTypeEnum type;
/**
* 变更内容
@ -72,7 +54,7 @@ public class ChatGroupChangeLog implements Serializable {
* 是否删除
*/
@TableField("is_delete")
private Integer isDelete;
private Long isDelete;
/**
* 创建时间

View File

@ -14,6 +14,13 @@ public enum EventTypeEnum {
MESSAGE_HISTORY_CREATED("message-history", "message-history-created", "发送记录创建"),
MESSAGE_HISTORY_UPDATED("message-history", "message-history-updated", "发送记录修改"),
MESSAGE_CHAT_GROUP_CREATE("chat-group", "chat-group-create", "群聊创建"),
UPDATE_AVATAR("profile", "update-avatar", "头像更新"),
NODE_USER_CREATE("node-user", "node-user-create", "节点用户创建"),
NODE_USER_UPDATE("node-user", "node-user-update", "节点用户修改"),
NODE_USER_DELETE("node-user", "node-user-delete", "节点用户删除"),
NODE_USER_UPSERTED("node-user", "node-user-upserted", "节点用户更新"),
SAAS_ROLE_USER_RELATION_REMOVED("saas-role-user-relation", "saas-role-user-relation-removed", "删除用户角色信息"),
SAAS_ROLE_USER_RELATION_UPSERT("saas-role-user-relation", "saas-role-user-relation-upsert", "更新用户角色信息"),
;
EventTypeEnum(String model, String name, String desc) {

View File

@ -20,4 +20,7 @@ public class ChatGroupCreatePayload implements Serializable {
private String owner;
private String imAccountOwner;
private Long chatGroupId;
}

View File

@ -0,0 +1,131 @@
package cn.axzo.im.event.payload;
import cn.axzo.maokai.api.vo.response.NodeUserPersonGroupLike;
import cn.axzo.pokonyan.config.mybatisplus.BaseEntity;
import cn.axzo.trade.datasecurity.core.annotation.CryptField;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.beans.BeanUtils;
import java.io.Serializable;
import java.util.Date;
/**
* 组织人员表表实体类
*
* @author makejava
* @since 2022-06-05 10:59:31
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OrganizationalNodeUserPayload{
private static final long serialVersionUID = 297636361732939329L;
/**
* identity_id
*/
private Long identityId;
/**
* 身份类型 0-无效类型, 1-工人, 2-班组长, 3-从业人员, 4-政务人员5-运营人员
*/
private Integer identityType;
/**
* 自然人id
*/
private Long personId;
/**
* 主电话
*/
private String phone;
/**
* 名字
*/
private String realName;
/**
* 身份证号
*/
private String idNumber;
/**
* 单位id
*/
private Long organizationalUnitId;
/**
* 组织节点id
*/
private Long organizationalNodeId;
/**
* 岗位id
*/
private Long organizationalJobId;
/**
* 是否是主岗位
* 0:普通岗位1:主岗位
*/
private Integer primaryJob;
/**
* 是否允许进入工地 1.允许 2.不允许
*/
private Integer isAllowed;
/**
* 加入时间
*/
private Date joinAt;
/**
* 是否是部门管理员
*/
private Boolean manager;
/**
* 离开时间
*/
private Date leaveAt;
/**
* 迁移数据临时源id
*/
private Long tempSourceId;
/**
* 顶级节点的nodeId
*/
private Long topNodeId;
/** 数据同步ID **/
private Long syncDataId;
/** 工号 - 需要作为查询条件和排序 不使用扩展字段**/
private String jobNumber;
/**
* workspace id
*/
private Long workspaceId;
private String groupNodeIds = "";
private String groupJobIds = "";
private String groupIdentityIds = "";
}

View File

@ -0,0 +1,19 @@
package cn.axzo.im.event.payload;
import cn.axzo.basics.auth.dto.SaasRoleUserRelation;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SaasRoleUserRelationRemovePayload {
private List<SaasRoleUserRelation> values;
}

View File

@ -0,0 +1,30 @@
package cn.axzo.im.event.payload;
import cn.axzo.basics.auth.dto.SaasRoleUserRelation;
import cn.axzo.tyr.client.model.roleuser.dto.SaasRoleUserV2DTO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.BeanUtils;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SaasRoleUserRelationUpsertPayload {
private List<SaasRoleUserRelation> oldValues;
private List<SaasRoleUserRelation> newValues;
public static SaasRoleUserRelation from(SaasRoleUserV2DTO saasRoleUserV2DTO) {
SaasRoleUserRelation result = new SaasRoleUserRelation();
BeanUtils.copyProperties(saasRoleUserV2DTO, result);
result.setNaturalPersonId(saasRoleUserV2DTO.getSaasRoleUser().getPersonId());
return result;
}
}

View File

@ -0,0 +1,29 @@
package cn.axzo.im.gateway;
import cn.axzo.im.utils.BizAssertions;
import cn.axzo.maokai.api.client.OrgJobApi;
import cn.axzo.maokai.api.vo.request.OrgJobListReq;
import cn.axzo.maokai.api.vo.response.OrgJobRes;
import cn.axzo.pokonyan.util.RpcUtil;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("orgJobApiGateway")
@Slf4j
@RequiredArgsConstructor
public class OrgJobApiGateway {
private final OrgJobApi orgJobApi;
public OrgJobRes fetchJobCodeByJobId(Long jobId) {
OrgJobListReq req = new OrgJobListReq();
req.setJobIdList(Lists.newArrayList(jobId));
List<OrgJobRes> orgJobRes = RpcUtil.rpcApiResultProcessor(() -> orgJobApi.list(req), "fetchJobCodeByJobId", jobId);
BizAssertions.assertNotEmpty(orgJobRes, "根据jobId:{}获取Job为空", jobId);
return orgJobRes.get(0);
}
}

View File

@ -0,0 +1,29 @@
package cn.axzo.im.gateway;
import cn.axzo.maokai.api.client.OrganizationalNodeApi;
import cn.axzo.maokai.api.vo.request.OrganizationalNodeBatchQueryVO;
import cn.axzo.maokai.api.vo.response.OrganizationalNodeVO;
import cn.axzo.pokonyan.util.RpcUtil;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("organizationalNodeApiGateway")
@Slf4j
@RequiredArgsConstructor
public class OrganizationalNodeApiGateway {
private final OrganizationalNodeApi organizationalNodeApi;
public List<OrganizationalNodeVO> fetchNodesByNodeIds(List<Long> nodeIdList) {
OrganizationalNodeBatchQueryVO organizationalNodeBatchQueryVO = new OrganizationalNodeBatchQueryVO();
organizationalNodeBatchQueryVO.setNodeIds(nodeIdList);
organizationalNodeBatchQueryVO.setContainsDeleted(false);
List<OrganizationalNodeVO> nodeVOList = organizationalNodeApi.listNode(organizationalNodeBatchQueryVO).getData();
return RpcUtil.rpcApiResultProcessor(() -> organizationalNodeApi.listNode(organizationalNodeBatchQueryVO), "fetchNodesByNodeIds", JSON.toJSONString(nodeVOList));
}
}

View File

@ -0,0 +1,54 @@
package cn.axzo.im.gateway;
import cn.axzo.maokai.api.client.OrganizationalNodeUserApi;
import cn.axzo.maokai.api.vo.request.OrganizationalNodeUserSearchReq;
import cn.axzo.maokai.api.vo.response.OrganizationalNodeUserVO;
import cn.axzo.pokonyan.util.RpcUtil;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
@Service("organizationalNodeUserApiGateway")
@Slf4j
@RequiredArgsConstructor
public class OrganizationalNodeUserApiGateway {
private final OrganizationalNodeUserApi organizationalNodeUserApi;
public List<OrganizationalNodeUserVO> searchNodeUser(Long nodeId, Long workspaceId, Set<String> jobCodes, Long personId) {
OrganizationalNodeUserSearchReq searchReq = new OrganizationalNodeUserSearchReq();
searchReq.setOrganizationNodeId(nodeId);
searchReq.setWorkspaceId(workspaceId);
searchReq.setJobCodes(jobCodes);
searchReq.setPersonId(personId);
return RpcUtil.rpcApiResultProcessor(() -> organizationalNodeUserApi.list(searchReq), "searchNodeUser", JSON.toJSONString(searchReq));
}
public List<OrganizationalNodeUserVO> fetchNodeUsersByWorkspaceNodeIdJobCodes(Long workspaceId, Long nodeId,Set<String> jobCodes) {
OrganizationalNodeUserSearchReq searchReq = new OrganizationalNodeUserSearchReq();
searchReq.setWorkspaceId(workspaceId);
searchReq.setOrganizationNodeId(nodeId);
searchReq.setJobCodes(jobCodes);//班组长/带班长
return RpcUtil.rpcApiResultProcessor(() -> organizationalNodeUserApi.list(searchReq), "fetchNodeUsersByWorkspaceOuIdJobCodes", JSON.toJSONString(searchReq));
}
public List<OrganizationalNodeUserVO> fetchNodeUsersByWorkspaceIdJobCodes(Long workspaceId, Set<String> jobCodes) {
OrganizationalNodeUserSearchReq searchReq = new OrganizationalNodeUserSearchReq();
searchReq.setWorkspaceId(workspaceId);
searchReq.setJobCodes(jobCodes);
return RpcUtil.rpcApiResultProcessor(() -> organizationalNodeUserApi.list(searchReq), "fetchNodeUsersByWorkspaceIdJobCodes", JSON.toJSONString(searchReq));
}
public List<OrganizationalNodeUserVO> fetchNodeUsersByWorkspaceOuIdJobCodes(Long workspaceId, Long ouId, Set<String> jobCodes) {
OrganizationalNodeUserSearchReq searchReq = new OrganizationalNodeUserSearchReq();
searchReq.setWorkspaceId(workspaceId);
searchReq.setOrganizationalUnitId(ouId);
searchReq.setJobCodes(jobCodes);
return RpcUtil.rpcApiResultProcessor(() -> organizationalNodeUserApi.list(searchReq), "fetchNodeUsersByWorkspaceOuIdJobCodes", JSON.toJSONString(searchReq));
}
}

View File

@ -3,11 +3,16 @@ package cn.axzo.im.gateway;
import cn.axzo.basics.profiles.api.UserProfileServiceApi;
import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
import cn.axzo.im.entity.dto.PlatPersonDto;
import cn.axzo.pokonyan.util.RpcUtil;
import cn.azxo.framework.common.logger.MethodAroundLog;
import cn.azxo.framework.common.model.CommonResponse;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import java.util.List;
import java.util.Objects;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
@ -35,4 +40,12 @@ public class ProfilesApiGateway {
}
return BeanUtil.copyProperties(response.getData(), PlatPersonDto.class);
}
public PersonProfileDto getPersonProfileById(Long personId) {
return RpcUtil.rpcCommonProcessor(() -> userProfileServiceApi.getPersonProfile(personId), "getPersonProfileById", personId);
}
public List<PersonProfileDto> getPersonProfilesByIds(List<Long> personIds) {
return RpcUtil.rpcCommonProcessor(() -> userProfileServiceApi.getPersonProfiles(personIds), "getPersonProfilesByIds", JSON.toJSONString(personIds));
}
}

View File

@ -0,0 +1,31 @@
package cn.axzo.im.gateway;
import cn.axzo.pokonyan.util.RpcUtil;
import cn.axzo.tyr.client.common.enums.RoleTypeEnum;
import cn.axzo.tyr.client.feign.TyrSaasRoleUserApi;
import cn.axzo.tyr.client.model.roleuser.dto.SaasRoleUserV2DTO;
import cn.axzo.tyr.client.model.roleuser.req.ListRoleUserRelationParam;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Service("tyrApiGateway")
@Slf4j
@RequiredArgsConstructor
public class TyrApiGateway {
private final TyrSaasRoleUserApi tyrSaasRoleUserApi;
public List<SaasRoleUserV2DTO> fetchSaasRoleUserByWorkspaceOuIdRoleTypes(Long workspaceId, Long ouId, Set<RoleTypeEnum> roleTypeEnumSet) {
ArrayList<ListRoleUserRelationParam.WorkspaceOuPair> workspaceOuPairs = Lists.newArrayList(ListRoleUserRelationParam.WorkspaceOuPair.builder().workspaceId(workspaceId).ouId(ouId).build());
ListRoleUserRelationParam listRoleUserRelationParam = ListRoleUserRelationParam.builder().roleTypes(roleTypeEnumSet).workspaceOuPairs(workspaceOuPairs).build();
return RpcUtil.rpcApiResultProcessor(() -> tyrSaasRoleUserApi.roleUserListV2(listRoleUserRelationParam), "fetchSaasRoleUserByWorkspaceOuIdRoleTypes", JSON.toJSONString(listRoleUserRelationParam));
}
}

View File

@ -0,0 +1,41 @@
package cn.axzo.im.gateway;
import cn.axzo.apollo.core.web.Result;
import cn.axzo.apollo.workspace.api.workspace.WorkspaceApi;
import cn.axzo.apollo.workspace.api.workspace.res.GetDetailRes;
import cn.axzo.pokonyan.util.RpcUtil;
import cn.axzo.tyr.client.common.enums.RoleTypeEnum;
import cn.axzo.tyr.client.feign.TyrSaasRoleUserApi;
import cn.axzo.tyr.client.model.roleuser.dto.SaasRoleUserV2DTO;
import cn.axzo.tyr.client.model.roleuser.req.ListRoleUserRelationParam;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Service("workspaceApiGateway")
@Slf4j
@RequiredArgsConstructor
public class WorkspaceApiGateway {
private final WorkspaceApi workspaceApi;
public GetDetailRes getWorkspaceById(Long workspaceId) {
try {
log.info("getWorkspaceById-param:{}", workspaceId);
Result<GetDetailRes> result = workspaceApi.getById(workspaceId);
log.info("getWorkspaceById-result:{}", JSON.toJSONString(result));
return result.getData();
} catch (Exception e) {
log.error("getWorkspaceById-error", e);
throw e;
}
}
}

View File

@ -0,0 +1,161 @@
package cn.axzo.im.handler.chatgroup;
import cn.axzo.basics.auth.dto.SaasRoleUserRelation;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.im.center.api.vo.req.ChatGroupUserGenericSearchReq;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.ChatGroupUserTypeEnum;
import cn.axzo.im.entity.ChatGroupUser;
import cn.axzo.im.event.inner.EventTypeEnum;
import cn.axzo.im.event.payload.SaasRoleUserRelationRemovePayload;
import cn.axzo.im.event.payload.SaasRoleUserRelationUpsertPayload;
import cn.axzo.im.gateway.TyrApiGateway;
import cn.axzo.im.service.AccountService;
import cn.axzo.im.service.ChatGroupService;
import cn.axzo.im.service.ChatGroupUserService;
import cn.axzo.im.utils.BizAssertions;
import cn.axzo.tyr.client.common.enums.RoleTypeEnum;
import cn.axzo.tyr.client.model.roleuser.dto.SaasRoleUserV2DTO;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* @author xudawei@axzo.cn
* @date 2024/11/07
* @desc 群聊创建MQ消费
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class ChatGroupChangeOwnerEventHandler implements EventHandler, InitializingBean {
@Autowired
private EventConsumer eventConsumer;
@Autowired
private TyrApiGateway tyrApiGateway;
@Autowired
private ChatGroupUserService chatGroupUserService;
@Autowired
private AccountService accountService;
@Autowired
private ChatGroupService chatGroupService;
@Override
public void onEvent(Event event, EventConsumer.Context context) {
if (!EventTypeEnum.SAAS_ROLE_USER_RELATION_REMOVED.getName().equalsIgnoreCase(event.getEventCode().getName())
&& !EventTypeEnum.SAAS_ROLE_USER_RELATION_UPSERT.getName().equalsIgnoreCase(event.getEventCode().getName())) {
return;
}
log.info("ChatGroupChangeOwnerEventHandler-start - handle mq event, event={}", JSON.toJSONString(event));
long start = System.currentTimeMillis();
try {
//角色删除
if (EventTypeEnum.SAAS_ROLE_USER_RELATION_REMOVED.getName().equalsIgnoreCase(event.getEventCode().getName())) {
this.handleMqMessageRoleRemove(event);
log.info("ChatGroupChangeOwnerEventHandler-handle-roleRemove mq event, event={},used={}ms", JSON.toJSONString(event), System.currentTimeMillis() - start);
return;
}
//角色变更
if (EventTypeEnum.SAAS_ROLE_USER_RELATION_UPSERT.getName().equalsIgnoreCase(event.getEventCode().getName())) {
this.handleMqMessageRoleUpsert(event);
log.info("ChatGroupChangeOwnerEventHandler-handle-upsert mq event, event={},used={}ms", JSON.toJSONString(event), System.currentTimeMillis() - start);
}
} catch (Exception e) {
log.warn("ChatGroupChangeOwnerEventHandler-handle error mq event, event={},used={}ms", JSON.toJSONString(event), System.currentTimeMillis() - start, e);
}
}
/**
* 业务逻辑-角色删除
*/
private void handleMqMessageRoleRemove(Event event) {
//解析数据
// SaasRoleUserRelationRemovePayload payload = event.normalizedData(SaasRoleUserRelationRemovePayload.class);
}
/**
* 业务逻辑-角色变更
* 1 用户是群主的群聊集合
* 2 更换群主
*/
private void handleMqMessageRoleUpsert(Event event) {
//解析数据
SaasRoleUserRelationUpsertPayload payload = event.normalizedData(SaasRoleUserRelationUpsertPayload.class);
if (Objects.isNull(payload) || CollectionUtils.isEmpty(payload.getOldValues())) {
log.info("ChatGroupChangeOwnerEventHandler-角色变更mq对象为空");
return;
}
for(SaasRoleUserRelation item : payload.getOldValues()) {
ChatGroupUserGenericSearchReq req = new ChatGroupUserGenericSearchReq();
String oldImAccountOwner = accountService.buildUserIdWrapper(item.getNaturalPersonId().toString(), AppTypeEnum.CMP.getCode(), item.getOuId());
//1 用户是群主的群聊集合
req.setAccId(oldImAccountOwner);
req.setType(ChatGroupUserTypeEnum.OWNER);
List<ChatGroupUser> chatGroupUsers = chatGroupUserService.genericQuery(req);
if (CollectionUtils.isEmpty(chatGroupUsers)) {
log.info("ChatGroupChangeOwnerEventHandler-角色变更,personId:{},没有群是群主", item.getNaturalPersonId());
continue;
}
for(ChatGroupUser chatGroupUser : chatGroupUsers) {
switch (chatGroupUser.getCrowType()) {
case WORKSPACE:
// 2 更换群主
this.changeOwner(item.getWorkspaceId(), null, Sets.newHashSet(RoleTypeEnum.SUPER_ADMIN), item, oldImAccountOwner, chatGroupUser);
break;
case OU:
//2 更换群主
this.changeOwner(item.getWorkspaceId(), item.getOuId(), Sets.newHashSet(RoleTypeEnum.ADMIN), item, oldImAccountOwner, chatGroupUser);
break;
case TEAM:
break;
default:
throw new ServiceException("人群不存在");
}
}
}
}
/**
* 更换群主
*/
private void changeOwner(Long workspaceId, Long ouId, Set<RoleTypeEnum> roleTypeEnumSet,SaasRoleUserRelation item, String oldImAccountOwner, ChatGroupUser chatGroupUser) {
//获取最新群主
List<SaasRoleUserV2DTO> saasRoleUserV2DTOS = tyrApiGateway.fetchSaasRoleUserByWorkspaceOuIdRoleTypes(workspaceId, ouId, roleTypeEnumSet);
if (CollectionUtils.isEmpty(saasRoleUserV2DTOS)) {
this.chatGroupService.sendDingRobot(String.format("创建群聊,personId:%d在workspaceId:%d,ouId:%d,人群:%s,找不到群主信息", item.getNaturalPersonId(), item.getWorkspaceId(), ouId, chatGroupUser.getCrowType()));
}
BizAssertions.assertNotEmpty(saasRoleUserV2DTOS, "personId:{}在workspaceId:{},ouId:%d,找不到群主信息", item.getNaturalPersonId(), item.getWorkspaceId(), ouId);
SaasRoleUserV2DTO saasRoleUserV2DTOWhenOu = saasRoleUserV2DTOS.get(0);
String newImAccountOwnerWhenOu = accountService.buildUserIdWrapper(saasRoleUserV2DTOWhenOu.getSaasRoleUser().getPersonId().toString(), AppTypeEnum.CMP.getCode(), item.getOuId());
this.chatGroupService.changeOwner(chatGroupUser.getChatGroupId(), oldImAccountOwner, newImAccountOwnerWhenOu);
}
@Override
public void afterPropertiesSet() {
Event.EventCode removedEventCode = new Event.EventCode(EventTypeEnum.SAAS_ROLE_USER_RELATION_REMOVED.getModel(), EventTypeEnum.SAAS_ROLE_USER_RELATION_REMOVED.getName());
Event.EventCode upsertEventCode = new Event.EventCode(EventTypeEnum.SAAS_ROLE_USER_RELATION_UPSERT.getModel(), EventTypeEnum.SAAS_ROLE_USER_RELATION_UPSERT.getName());
eventConsumer.registerHandler(removedEventCode, this);
eventConsumer.registerHandler(upsertEventCode, this);
}
}

View File

@ -1,28 +1,42 @@
package cn.axzo.im.handler.chatgroup;
import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.UserAccountReq;
import cn.axzo.im.center.api.vo.resp.UserAccountResp;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.channel.IMChannelProvider;
import cn.axzo.im.channel.netease.INotifyService;
import cn.axzo.im.channel.netease.dto.UserAddChatGroupRequest;
import cn.axzo.im.event.inner.EventTypeEnum;
import cn.axzo.im.event.payload.ChatGroupCreatePayload;
import cn.axzo.im.gateway.ProfilesApiGateway;
import cn.axzo.im.service.AccountService;
import cn.axzo.im.service.ChatGroupService;
import cn.hutool.core.lang.Pair;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author yanglin
* @author xudawei@axzo.cn
* @date 2024/11/07
* @desc 群聊创建MQ消费
*/
@Slf4j
@Component
@ -32,8 +46,6 @@ public class ChatGroupEventHandler implements EventHandler, InitializingBean {
@Autowired
private EventConsumer eventConsumer;
@Autowired
private IMChannelProvider imChannelProvider;
@Autowired
private AccountService accountService;
@ -41,8 +53,17 @@ public class ChatGroupEventHandler implements EventHandler, InitializingBean {
@Resource
private INotifyService iNotifyService;
@Autowired
private ChatGroupService chatGroupService;
@Autowired
private ProfilesApiGateway profilesApiGateway;
@Override
public void onEvent(Event event, EventConsumer.Context context) {
if (!EventTypeEnum.MESSAGE_CHAT_GROUP_CREATE.getName().equalsIgnoreCase(event.getEventCode().getName())) {
return;
}
log.info("ChatGroupEventHandler-start - handle mq event, event={}", JSON.toJSONString(event));
try {
@ -51,27 +72,90 @@ public class ChatGroupEventHandler implements EventHandler, InitializingBean {
long end = System.currentTimeMillis();
log.info("ChatGroupEventHandlerend-handle mq event, used={}ms, event={}", end - start, JSON.toJSONString(event));
} catch (Exception e) {
log.warn("ChatGroupEventHandler-error - handle mq event, event={}", JSON.toJSONString(event));
log.warn("ChatGroupEventHandler-error - handle mq event, event={}", JSON.toJSONString(event), e);
}
}
/**
* 业务逻辑
*/
private void handleMqMessage(Event event) {
//解析数据
ChatGroupCreatePayload payload = event.normalizedData(ChatGroupCreatePayload.class);
ChatGroupCreateReq req = payload.getReq();
String tid = payload.getTid();
String owner = payload.getOwner();
String imAccountOwner = payload.getImAccountOwner();
String members = "";//TODO
UserAccountReq userAccountReq = UserAccountReq.builder().appType(AppTypeEnum.SYSTEM.getCode()).userId("").nickName("").headImageUrl("").organizationalUnitId(1L).build();
accountService.generateAccount(userAccountReq, iNotifyService);
List<String> members = this.chatGroupOfMembers(req);
this.chatGroupService.userAddChatGroup(payload.getChatGroupId(), tid,imAccountOwner,members, req.getGroupName());
}
imChannelProvider.userAddChatGroup(UserAddChatGroupRequest.buildRequest(tid, owner, members,"进入群:" + req.getGroupName()));
/**
* 构建群成员信息
*/
public List<String> chatGroupOfMembers(ChatGroupCreateReq req) {
//Pair,key:管理人员集合;value:工人集合
Pair<Set<Long>, Set<Long>> adminWorkerSet = chatGroupService.fetchUsersByWorkspaceOuId(req.getCrowType(), req.getWorkspaceId(), req.getOuId(), this.chatGroupService.buildJobCodesByCrowType(req.getCrowType()), req.getCreator());
Set<Long> adminSet = adminWorkerSet.getKey();
Set<Long> workerSet = adminWorkerSet.getValue();
Map<Long, PersonProfileDto> personProfileMap = this.buildPersonProfileMap(adminWorkerSet);
List<UserAccountResp> userAccountRespList = Lists.newArrayList();
for (Long item : adminSet) {
PersonProfileDto personProfileDto = personProfileMap.get(item);
if (Objects.isNull(personProfileDto) || Objects.isNull(personProfileDto.getId())) {
log.info("admin-personProfileDto is null");
continue;
}
UserAccountReq userAccountReq = UserAccountReq.builder()
.appType(AppTypeEnum.CMP.getCode())//管理端:AppTypeEnum.CMP.getCode();工人端:AppTypeEnum.CM.getCode()
.userId(item.toString()).nickName(personProfileDto.getRealName())
.headImageUrl(personProfileDto.getAvatarUrl())
.organizationalUnitId(req.getOuId())
.build();
userAccountRespList.add(accountService.generateAccount(userAccountReq, iNotifyService));
}
for (Long worker : workerSet) {
PersonProfileDto personProfileDto = personProfileMap.get(worker);
if (Objects.isNull(personProfileDto) || Objects.isNull(personProfileDto.getId())) {
log.info("worker-personProfileDto is null");
continue;
}
UserAccountReq userAccountReq = UserAccountReq.builder()
.appType(AppTypeEnum.CM.getCode())//管理端:AppTypeEnum.CMP.getCode();工人端:AppTypeEnum.CM.getCode()
.userId(worker.toString()).nickName(personProfileDto.getRealName())
.headImageUrl(personProfileDto.getAvatarUrl())
.organizationalUnitId(req.getOuId())
.build();
userAccountRespList.add(accountService.generateAccount(userAccountReq, iNotifyService));
}
return userAccountRespList.stream().map(UserAccountResp::getImAccount).collect(Collectors.toList());
}
/**
* 构建personProfile的Map
* key:id,value:personProfile
*/
private Map<Long, PersonProfileDto> buildPersonProfileMap(Pair<Set<Long>, Set<Long>> adminWorkerSet) {
Set<Long> adminSet = CollectionUtils.isNotEmpty(adminWorkerSet.getKey()) ? adminWorkerSet.getKey() : Sets.newHashSet();
Set<Long> workerSet = CollectionUtils.isNotEmpty(adminWorkerSet.getValue()) ? adminWorkerSet.getValue() : Sets.newHashSet();
List<Long> adminWorkerList = Lists.newArrayList();
adminWorkerList.addAll(Lists.newArrayList(adminSet));
adminWorkerList.addAll(Lists.newArrayList(workerSet));
List<PersonProfileDto> personProfileDtoList = profilesApiGateway.getPersonProfilesByIds(adminWorkerList);
return personProfileDtoList.stream().collect(Collectors.toMap(PersonProfileDto::getId, Function.identity(), (x, y) -> x));
}
@Override
public void afterPropertiesSet() throws Exception {
public void afterPropertiesSet() {
Event.EventCode eventCode = new Event.EventCode(EventTypeEnum.MESSAGE_CHAT_GROUP_CREATE.getModel(), EventTypeEnum.MESSAGE_CHAT_GROUP_CREATE.getName());
eventConsumer.registerHandler(eventCode, this);
}

View File

@ -0,0 +1,261 @@
package cn.axzo.im.handler.chatgroup;
import cn.axzo.apollo.core.service.ServiceException;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.ChatGroupGenericSearchReq;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.channel.netease.NimChannelService;
import cn.axzo.im.channel.netease.dto.ChatGroupQueryResponse;
import cn.axzo.im.entity.ChatGroup;
import cn.axzo.im.event.inner.EventTypeEnum;
import cn.axzo.im.event.payload.OrganizationalNodeUserPayload;
import cn.axzo.im.gateway.OrgJobApiGateway;
import cn.axzo.im.service.AccountService;
import cn.axzo.im.service.ChatGroupService;
import cn.axzo.im.utils.BizAssertions;
import cn.axzo.im.utils.JobCodeUtils;
import cn.axzo.maokai.api.vo.response.OrgJobRes;
import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
/**
* @author xudawei@axzo.cn
* @date 2024/11/07
* @desc 群聊创建MQ消费-同步网易云信
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class OrganizationalNodeUserChangeEventHandler implements EventHandler, InitializingBean {
@Autowired
private EventConsumer eventConsumer;
@Autowired
private ChatGroupService chatGroupService;
@Autowired
private NimChannelService nimChannelService;
@Autowired
private OrgJobApiGateway orgJobApiGateway;
@Autowired
private AccountService accountService;
@Override
public void onEvent(Event event, EventConsumer.Context context) {
if (!EventTypeEnum.NODE_USER_CREATE.getName().equalsIgnoreCase(event.getEventCode().getName())
&& !EventTypeEnum.NODE_USER_UPDATE.getName().equalsIgnoreCase(event.getEventCode().getName())
&& !EventTypeEnum.NODE_USER_DELETE.getName().equalsIgnoreCase(event.getEventCode().getName())
&& !EventTypeEnum.NODE_USER_UPSERTED.getName().equalsIgnoreCase(event.getEventCode().getName())) {
return;
}
log.info("im-organizationalNodeUserChange-start mq,eventModule:{},eventName:{}, event:{}", event.getEventCode().getModule(), event.getEventCode().getName(), JSON.toJSONString(event));
long start = System.currentTimeMillis();
try {
handleMqMessage(event);
log.info("OrganizationalNodeUserChangeEventHandler-handle mq event, event={},used={}ms", JSON.toJSONString(event), System.currentTimeMillis() - start);
} catch (Exception e) {
log.warn("OrganizationalNodeUserChangeEventHandler-handle error mq event, event={},used={}ms", JSON.toJSONString(event), System.currentTimeMillis() - start, e);
}
}
/**
* 业务逻辑
*/
private void handleMqMessage(Event event) {
//解析数据
OrganizationalNodeUserPayload payload = event.normalizedData(OrganizationalNodeUserPayload.class);
Long workspaceId = payload.getWorkspaceId();
Long ouId = payload.getOrganizationalUnitId();
Long nodeId = payload.getOrganizationalNodeId();
this.enterOrExitChatGroupByCrowType(ChatGroupCreateReq.CrowTypeEnum.WORKSPACE, workspaceId, null, null, payload, event.getEventCode().getName());
this.enterOrExitChatGroupByCrowType(ChatGroupCreateReq.CrowTypeEnum.OU, workspaceId, ouId, null, payload, event.getEventCode().getName());
this.enterOrExitChatGroupByCrowType(ChatGroupCreateReq.CrowTypeEnum.TEAM, workspaceId, null, nodeId, payload, event.getEventCode().getName());
}
/**
* 加入群聊或者退出群聊-通过人群
* @param crowTypeEnum 人群
* @param workspaceId 项目ID
* @param ouId 单位Id
* @param teamId 班组Id
* @param payload mq消息
* @param tag mq的tag
*/
private void enterOrExitChatGroupByCrowType(ChatGroupCreateReq.CrowTypeEnum crowTypeEnum, Long workspaceId,Long ouId, Long teamId, OrganizationalNodeUserPayload payload, String tag) {
List<ChatGroup> workspaceChatGroups = chatGroupService.genericQuery(ChatGroupGenericSearchReq.builder()
.crowType(crowTypeEnum)
.workspaceId(workspaceId)
.ouId(ouId)
.teamId(teamId)
.build());
if (CollectionUtils.isEmpty(workspaceChatGroups)) {
log.info("群聊监控orgNodeUser,人员变更查询不到群聊,workspaceId:{},crowType:{}", workspaceId, crowTypeEnum);
return;
}
//人群类型:项目,同一个项目只存在一个群聊
ChatGroup chatGroup = workspaceChatGroups.get(0);
String jobCode = fetchJobCodeByJobId(payload.getOrganizationalJobId());
if (StringUtils.isBlank(jobCode)) {
log.info("群聊监控orgNodeUser,人员变更根据jobId获取jobCode为空,jobId:{}", payload.getOrganizationalJobId());
chatGroupService.sendDingRobot(String.format("群聊监控orgNodeUser,人员变更根据jobId获取jobCode为空,jobId:%s,nodeId:%d", payload.getOrganizationalJobId(), payload.getOrganizationalNodeId()));
return;
}
String imAccount = this.buildImAccount(payload.getPersonId(), payload.getOrganizationalUnitId(), jobCode, chatGroup.getCrowType());
this.enterOrExitChatGroup(tag, chatGroup, jobCode, imAccount);
}
/**
* 获取岗位code
*/
private String fetchJobCodeByJobId(Long jobId) {
OrgJobRes orgJobRes = orgJobApiGateway.fetchJobCodeByJobId(jobId);
if (Objects.nonNull(orgJobRes)) {
return orgJobRes.getCode();
}
return "";
}
/**
* 加入群聊或者退出群聊
* @param tag MQ的tag
* @param chatGroup 群聊对象
* @param currentJobCode 岗位code
* @param currentImAccount im账号
*/
private void enterOrExitChatGroup(String tag, ChatGroup chatGroup, String currentJobCode, String currentImAccount) {
String tid = chatGroup.getTid();
ChatGroupQueryResponse chatGroupQueryResponse = nimChannelService.chatGroupQueryByTids(Lists.newArrayList(tid), 1);
if (Objects.isNull(chatGroupQueryResponse) || CollectionUtils.isEmpty(chatGroupQueryResponse.getTinfos())) {
log.info("enterOrExitChatGroup-chatGroupQueryByTids response is empty or tinfos is empty");
return;
}
ChatGroupQueryResponse.TInfo tInfo = chatGroupQueryResponse.getTinfos().get(0);
String members = tInfo.getMembers();
switch (chatGroup.getCrowType()) {
case WORKSPACE:
case OU:
//加入群聊或者退出群聊-执行动作
this.doEnterOrExitChatGroup(tag, chatGroup, currentImAccount, members, tInfo.getOwner(), JobCodeUtils.isAdmin(currentJobCode));
break;
case TEAM:
//当处理项目内班组时更改群组
this.changeOwnerWithTeam(chatGroup, currentImAccount, JobCodeUtils.isProjectTeamLeader(currentJobCode));
//加入群聊或者退出群聊-执行动作
this.doEnterOrExitChatGroup(tag, chatGroup, currentImAccount, members, tInfo.getOwner(), JobCodeUtils.isTeam(currentJobCode));
}
}
/**
* 当处理项目内班组时更改群组
* @param chatGroup 群聊对象
* @param currentImAccount 当前Im账号
* @param isProjectTeamLeader 岗位是否时班组长
*/
private void changeOwnerWithTeam(ChatGroup chatGroup, String currentImAccount, boolean isProjectTeamLeader) {
if(!isProjectTeamLeader) {
log.info("岗位不是班组长,不执行更改群主逻辑,currentImAccount:{},chatGroupId:{}", currentImAccount, chatGroup.getId());
return;
}
if (chatGroup.getGroupOwner().equals(currentImAccount)) {
log.info("班组长并未变更,不执行更改群主逻辑,currentImAccount:{},chatGroupId:{}", currentImAccount, chatGroup.getId());
return;
}
this.chatGroupService.changeOwner(chatGroup.getId(), chatGroup.getGroupOwner(), currentImAccount);
}
/**
* 加入群聊或者退出群聊-执行动作
* @param tag MQ的tag
* @param chatGroup 群聊对象
* @param currentImAccount im账号
* @param members 群聊已有会员
* @param owner 群主
* @param containJobCode 包含job
*/
private void doEnterOrExitChatGroup(String tag, ChatGroup chatGroup, String currentImAccount,String members, String owner, boolean containJobCode) {
//orgNodeUser新增人,并且当前群不包含人
if (tag.equals(EventTypeEnum.NODE_USER_CREATE.getName()) && containJobCode &&!members.contains(currentImAccount)) {
//拉人进群
this.chatGroupService.userAddChatGroup(chatGroup.getId(), chatGroup.getTid(), owner, Lists.newArrayList(currentImAccount), chatGroup.getName());
return;
}
//orgNodeUser删除人 并且当前成员不包含人
if (tag.equals(EventTypeEnum.NODE_USER_DELETE.getName()) && members.contains(currentImAccount)) {
//踢出群
this.chatGroupService.kickChatGroup(chatGroup.getId(), chatGroup.getTid(), owner,currentImAccount);
return;
}
//以下orgNodeUser更新
if (!tag.equals(EventTypeEnum.NODE_USER_UPDATE.getName())) {
return;
}
if (containJobCode && !members.contains(currentImAccount)) {
//拉人进群
this.chatGroupService.userAddChatGroup(chatGroup.getId(), chatGroup.getTid(), owner, Lists.newArrayList(currentImAccount), chatGroup.getName());
return;
}
if (!containJobCode && members.contains(currentImAccount)) {
//踢出群
this.chatGroupService.kickChatGroup(chatGroup.getId(), chatGroup.getTid(), owner, currentImAccount);
}
}
/**
* 构建IM账号
*/
private String buildImAccount(Long personId, Long ouId, String jobCode, ChatGroupCreateReq.CrowTypeEnum crowTypeEnum) {
switch (crowTypeEnum) {
case WORKSPACE:
case OU:
return accountService.buildUserIdWrapper(personId.toString(), AppTypeEnum.CMP.getCode(), ouId);
case TEAM:
if (jobCode.equals("projTeamLeader") || jobCode.equals("projectTeamManager")) {
return accountService.buildUserIdWrapper(personId.toString(), AppTypeEnum.CMP.getCode(), ouId);
}
return accountService.buildUserIdWrapper(personId.toString(), AppTypeEnum.CM.getCode(), ouId);
default:
BizAssertions.assertTrue(false, "人群类型不匹配");
}
throw new ServiceException("人群类型不匹配");
}
@Override
public void afterPropertiesSet() {
Event.EventCode nodeUserCreateEventCode = new Event.EventCode(EventTypeEnum.NODE_USER_CREATE.getModel(), EventTypeEnum.NODE_USER_CREATE.getName());
Event.EventCode nodeUserUpdateEventCode = new Event.EventCode(EventTypeEnum.NODE_USER_UPDATE.getModel(), EventTypeEnum.NODE_USER_UPDATE.getName());
Event.EventCode nodeUserDeleteEventCode = new Event.EventCode(EventTypeEnum.NODE_USER_DELETE.getModel(), EventTypeEnum.NODE_USER_DELETE.getName());
Event.EventCode nodeUserUpsertedEventCode = new Event.EventCode(EventTypeEnum.NODE_USER_UPSERTED.getModel(), EventTypeEnum.NODE_USER_UPSERTED.getName());
eventConsumer.registerHandler(nodeUserCreateEventCode, this);
eventConsumer.registerHandler(nodeUserUpdateEventCode, this);
eventConsumer.registerHandler(nodeUserDeleteEventCode, this);
eventConsumer.registerHandler(nodeUserUpsertedEventCode, this);
}
}

View File

@ -0,0 +1,96 @@
package cn.axzo.im.handler.chatgroup;
import cn.axzo.basics.profiles.api.UserProfileServiceApi;
import cn.axzo.basics.profiles.common.enums.ProfileMQEventEnum;
import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
import cn.axzo.basics.profiles.dto.mq.ProfileMQBaseEntity;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.im.channel.netease.dto.GetAccountInfoResponse;
import cn.axzo.im.entity.AccountRegister;
import cn.axzo.im.entity.bo.AccountQueryParam;
import cn.axzo.im.event.inner.EventTypeEnum;
import cn.axzo.im.service.AccountService;
import cn.azxo.framework.common.model.CommonResponse;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
/**
* @author xudawei@axzo.cn
* @date 2024/11/07
* @desc 群聊创建MQ消费-同步网易云信
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class PersonProfileAvatarUpdateEventHandler implements EventHandler, InitializingBean {
@Autowired
private EventConsumer eventConsumer;
@Autowired
private AccountService accountService;
@Autowired
private UserProfileServiceApi userProfileServiceApi;
@Override
public void onEvent(Event event, EventConsumer.Context context) {
if (!ProfileMQEventEnum.UPDATE_AVATAR.getTag().equalsIgnoreCase(event.getEventCode().getName())) {
return;
}
log.info("im-updateAvatar-start mq,eventModule:{},eventName:{}, event:{}", event.getEventCode().getModule(), event.getEventCode().getName(), JSON.toJSONString(event));
long start = System.currentTimeMillis();
try {
handleMqMessage(event);
log.info("PersonProfileAvatarUpdateEventHandler-handle mq event, event={},used={}ms", JSON.toJSONString(event), System.currentTimeMillis() - start);
} catch (Exception e) {
log.warn("PersonProfileAvatarUpdateEventHandler-handle error mq event, event={},used={}ms", JSON.toJSONString(event), System.currentTimeMillis() - start, e);
}
}
/**
* 业务逻辑
*/
private void handleMqMessage(Event event) {
//解析数据
ProfileMQBaseEntity eventData = event.normalizedData(ProfileMQBaseEntity.class);
AccountQueryParam param = new AccountQueryParam();
param.setAccountId(eventData.getId().toString());
//1 获取账号信息
List<AccountRegister> accountRegisters = accountService.listAccount(param);
if (CollectionUtils.isEmpty(accountRegisters)) {
log.info("未找到im账号信息,personId:{}", eventData.getId().toString());
return;
}
//2 获取PersonProfile
CommonResponse<PersonProfileDto> personProfileDtoCommonResponse = userProfileServiceApi.getPersonProfile(eventData.getId());
if (Objects.isNull(personProfileDtoCommonResponse) || Objects.isNull(personProfileDtoCommonResponse.getData())) {
log.info("未找到personProfile信息,personId:{}", eventData.getId().toString());
return;
}
//3 同步网易云信账号
PersonProfileDto personProfileDto = personProfileDtoCommonResponse.getData();
for (AccountRegister accountRegister : accountRegisters) {
GetAccountInfoResponse.AccountInfo accountInfo = accountService.fetchImAccountInfoByImAccount(accountRegister.getImAccount());
accountService.syncImAccount(accountRegister.getAccountId(), accountRegister.getImAccount(), accountInfo.getOrCreateExtObject(),personProfileDto.getAvatarUrl(), personProfileDto.getRealName());
}
}
@Override
public void afterPropertiesSet() {
Event.EventCode eventCode = new Event.EventCode(EventTypeEnum.UPDATE_AVATAR.getModel(), EventTypeEnum.UPDATE_AVATAR.getName());
eventConsumer.registerHandler(eventCode, this);
}
}

View File

@ -6,12 +6,12 @@ import cn.axzo.basics.profiles.dto.basic.BasicDto;
import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
import cn.axzo.im.center.common.enums.AccountTypeEnum;
import cn.axzo.im.channel.netease.client.NimClient;
import cn.axzo.im.channel.netease.dto.GetAccountInfoRequest;
import cn.axzo.im.channel.netease.dto.GetAccountInfoResponse;
import cn.axzo.im.channel.netease.dto.UpdateAccountInfoRequest;
import cn.axzo.im.channel.netease.dto.UpdateAccountInfoResponse;
import cn.axzo.im.dao.repository.AccountRegisterDao;
import cn.axzo.im.entity.AccountRegister;
import cn.axzo.im.service.AccountService;
import cn.axzo.im.utils.BizAssertions;
import cn.axzo.maokai.api.util.Ref;
import com.alibaba.fastjson.JSONObject;
@ -44,6 +44,8 @@ public class UpdateImAccountPersonInfoJob {
private final NimClient nimClient;
private final UserProfileServiceApi userProfileServiceApi;
private final AccountService accountService;
@SuppressWarnings("unused")
@XxlJob("updateImAccountPersonInfoJob")
public ReturnT<String> execute(String jsonStr) throws Exception {
@ -59,66 +61,99 @@ public class UpdateImAccountPersonInfoJob {
}
private void executeImpl() {
RateLimiter rateLimiter = RateLimiter.create(60);
Supplier<List<AccountRegister>> cursor = accountsCursor();
int count = 0;
for (List<AccountRegister> accounts = cursor.get(); !accounts.isEmpty(); accounts = cursor.get()) {
log.info("update account info, count: {}", count += accounts.size());
GetAccountInfoRequest getAccountInfoRequest = new GetAccountInfoRequest();
getAccountInfoRequest.setImAccountIds(accounts.stream()
.map(AccountRegister::getImAccount)
.distinct()
.collect(toList()));
GetAccountInfoResponse getAccountInfoResponse = nimClient.getAccountInfo(getAccountInfoRequest);
if (!getAccountInfoResponse.isSuccess()) {
log.warn("get account info failed, {}", getAccountInfoResponse);
//1 获取IM账号
GetAccountInfoResponse imAccountList = accountService.getImAccount(accounts);
if (!imAccountList.isSuccess()) {
log.warn("get account info failed, {}", imAccountList);
continue;
}
List<Long> personIds = accounts.stream()
.map(AccountRegister::getAccountId)
.filter(Objects::nonNull)
.filter(NumberUtils::isCreatable)
.map(Long::parseLong)
.distinct()
.collect(toList());
Map<Long, PersonProfileDto> id2PersonProfile = BizAssertions.assertResponse(
userProfileServiceApi.getPersonProfiles(personIds)).stream()
.collect(Collectors.toMap(BasicDto::getId, i -> i));
//2 构建PersonProfile
Map<Long, PersonProfileDto> id2PersonProfile = this.buildPersonProfileMap(accounts);
for (AccountRegister account : accounts) {
GetAccountInfoResponse.AccountInfo accountInfo = getAccountInfoResponse
.findImAccountInfo(account.getImAccount()).orElse(null);
if (accountInfo == null) continue;
JSONObject ext = accountInfo.getOrCreateExtObject();
ext.put("personId", account.getAccountId());
UpdateAccountInfoRequest updateAccountInfoRequest = new UpdateAccountInfoRequest();
updateAccountInfoRequest.setImAccountId(accountInfo.getAccid());
updateAccountInfoRequest.addExt(ext);
if (account.getAccountId() != null && NumberUtils.isCreatable(account.getAccountId())) {
long personId = Long.parseLong(account.getAccountId());
PersonProfileDto person = id2PersonProfile.get(personId);
if (person != null) {
if (StringUtils.isNotBlank(person.getAvatarUrl())) {
updateAccountInfoRequest.setIcon(person.getAvatarUrl());
}
if (StringUtils.isNotBlank(person.getRealName())) {
updateAccountInfoRequest.setName(person.getRealName());
}
}
GetAccountInfoResponse.AccountInfo imAccount = imAccountList.findImAccountInfo(account.getImAccount()).orElse(null);
if (imAccount == null) continue;
//3 获取personProfile
PersonProfileDto person = this.fetchPersonById(account.getAccountId(), id2PersonProfile);
if (Objects.isNull(person) || Objects.isNull(person.getId())) {
log.info("personProfile为空personId:{}", account.getAccountId());
continue;
}
//4 更新IM账号
UpdateAccountInfoResponse updateAccountInfoResponse
= accountService.syncImAccount(account.getAccountId(), imAccount.getAccid()
,imAccount.getOrCreateExtObject(), person.getAvatarUrl(), person.getRealName());
//日志打印
this.successLog(updateAccountInfoResponse);
}
}
}
rateLimiter.acquire();
UpdateAccountInfoResponse updateAccountInfoResponse = nimClient
.updateAccountInfo(updateAccountInfoRequest);
if (updateAccountInfoResponse.isSuccess()) {
log.info("update account info success, {}", updateAccountInfoResponse);
} else {
log.warn("update account info failed, {}", updateAccountInfoResponse);
private PersonProfileDto fetchPersonById(String accountId, Map<Long, PersonProfileDto> id2PersonProfile) {
long personId = Long.parseLong(accountId);
PersonProfileDto person = id2PersonProfile.get(personId);
if (Objects.isNull(person) || Objects.isNull(person.getId())) {
log.info("personProfile为空personId:{}", person);
return null;
}
return person;
}
/**
* 日志打印
* @param updateAccountInfoResponse
*/
private void successLog(UpdateAccountInfoResponse updateAccountInfoResponse) {
if (updateAccountInfoResponse.isSuccess()) {
log.info("update account info success, {}", updateAccountInfoResponse);
} else {
log.warn("update account info failed, {}", updateAccountInfoResponse);
}
}
private Map<Long, PersonProfileDto> buildPersonProfileMap(List<AccountRegister> accounts) {
List<Long> personIds = accounts.stream()
.map(AccountRegister::getAccountId)
.filter(Objects::nonNull)
.filter(NumberUtils::isCreatable)
.map(Long::parseLong)
.distinct()
.collect(toList());
return BizAssertions.assertResponse(
userProfileServiceApi.getPersonProfiles(personIds)).stream()
.collect(Collectors.toMap(BasicDto::getId, i -> i));
}
private UpdateAccountInfoResponse updateImAccount(AccountRegister account, GetAccountInfoResponse.AccountInfo accountInfo,Map<Long, PersonProfileDto> id2PersonProfile) {
RateLimiter rateLimiter = RateLimiter.create(60);
JSONObject ext = accountInfo.getOrCreateExtObject();
ext.put("personId", account.getAccountId());
UpdateAccountInfoRequest updateAccountInfoRequest = new UpdateAccountInfoRequest();
updateAccountInfoRequest.setImAccountId(accountInfo.getAccid());
updateAccountInfoRequest.addExt(ext);
if (account.getAccountId() != null && NumberUtils.isCreatable(account.getAccountId())) {
long personId = Long.parseLong(account.getAccountId());
PersonProfileDto person = id2PersonProfile.get(personId);
if (person != null) {
if (StringUtils.isNotBlank(person.getAvatarUrl())) {
updateAccountInfoRequest.setIcon(person.getAvatarUrl());
}
if (StringUtils.isNotBlank(person.getRealName())) {
updateAccountInfoRequest.setName(person.getRealName());
}
}
}
rateLimiter.acquire();
return nimClient
.updateAccountInfo(updateAccountInfoRequest);
}
private Supplier<List<AccountRegister>> accountsCursor() {

View File

@ -14,11 +14,15 @@ import cn.axzo.im.center.common.enums.RobotStatusEnum;
import cn.axzo.im.channel.IMChannelProvider;
import cn.axzo.im.channel.netease.INotifyService;
import cn.axzo.im.channel.netease.client.NimClient;
import cn.axzo.im.channel.netease.dto.GetAccountInfoRequest;
import cn.axzo.im.channel.netease.dto.GetAccountInfoResponse;
import cn.axzo.im.channel.netease.dto.NimAccountInfo;
import cn.axzo.im.channel.netease.dto.RefreshTokenRequest;
import cn.axzo.im.channel.netease.dto.RefreshTokenResponse;
import cn.axzo.im.channel.netease.dto.RegisterRequest;
import cn.axzo.im.channel.netease.dto.RegisterResponse;
import cn.axzo.im.channel.netease.dto.UpdateAccountInfoRequest;
import cn.axzo.im.channel.netease.dto.UpdateAccountInfoResponse;
import cn.axzo.im.dao.mapper.AccountRegisterMapper;
import cn.axzo.im.dao.repository.AccountRegisterDao;
import cn.axzo.im.dao.repository.RobotInfoDao;
@ -27,13 +31,16 @@ import cn.axzo.im.entity.RobotInfo;
import cn.axzo.im.entity.bo.AccountQueryParam;
import cn.axzo.im.utils.MiscUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.RateLimiter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
@ -55,6 +62,7 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import static cn.axzo.im.utils.Queries.query;
import static java.util.stream.Collectors.toList;
/**
* IM账户服务
@ -459,4 +467,61 @@ public class AccountService {
}
return lambdaQuery;
}
/**
* 同步网易云信账号
*/
public UpdateAccountInfoResponse syncImAccount(String accountId, String accid, JSONObject ext, String avatarUrl, String realName) {
RateLimiter rateLimiter = RateLimiter.create(60);
ext.put("personId", accountId);
UpdateAccountInfoRequest updateAccountInfoRequest = new UpdateAccountInfoRequest();
updateAccountInfoRequest.setImAccountId(accid);
updateAccountInfoRequest.addExt(ext);
if (accountId != null && NumberUtils.isCreatable(accountId)) {
if (StringUtils.isNotBlank(avatarUrl)) {
updateAccountInfoRequest.setIcon(avatarUrl);
}
if (StringUtils.isNotBlank(realName)) {
updateAccountInfoRequest.setName(realName);
}
}
rateLimiter.acquire();
return nimClient.updateAccountInfo(updateAccountInfoRequest);
}
public GetAccountInfoResponse getImAccount(List<AccountRegister> accounts) {
if (CollectionUtils.isEmpty(accounts)) {
return null;
}
GetAccountInfoRequest getAccountInfoRequest = new GetAccountInfoRequest();
getAccountInfoRequest.setImAccountIds(accounts.stream().map(item -> item.getImAccount()).distinct().collect(toList()));
return nimClient.getAccountInfo(getAccountInfoRequest);
}
public List<GetAccountInfoResponse.AccountInfo> fetchImAccountsByImAccounts(List<String> accounts) {
if (CollectionUtils.isEmpty(accounts)) {
return null;
}
GetAccountInfoRequest getAccountInfoRequest = new GetAccountInfoRequest();
getAccountInfoRequest.setImAccountIds(accounts);
GetAccountInfoResponse response = nimClient.getAccountInfo(getAccountInfoRequest);
if (Objects.isNull(response) || CollectionUtils.isEmpty(response.getUinfos())) {
return null;
}
return response.getUinfos();
}
public GetAccountInfoResponse.AccountInfo fetchImAccountInfoByImAccount(String imAccount) {
if (StringUtils.isBlank(imAccount)) {
return null;
}
List<GetAccountInfoResponse.AccountInfo> accountInfos = this.fetchImAccountsByImAccounts(Lists.newArrayList(imAccount));
if (CollectionUtils.isEmpty(accountInfos)) {
return null;
}
return accountInfos.get(0);
}
}

View File

@ -1,12 +1,18 @@
package cn.axzo.im.service;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.ChatGroupGenericSearchReq;
import cn.axzo.im.center.api.vo.req.ChatGroupQueryReq;
import cn.axzo.im.center.api.vo.resp.ChatGroupCreateResp;
import cn.axzo.im.channel.netease.dto.ChatGroupCreateRequest;
import cn.axzo.im.channel.netease.dto.ChatGroupCreateResponse;
import cn.axzo.im.center.api.vo.resp.ChatGroupQueryResp;
import cn.axzo.im.entity.ChatGroup;
import cn.hutool.core.lang.Pair;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import java.util.Set;
/**
* @author xudawei@axzo.cn
* @date 2024/11/05
@ -17,6 +23,63 @@ public interface ChatGroupService extends IService<ChatGroup> {
/**
* 创建群聊
*/
ChatGroupCreateResp chatGroupCreate(ChatGroupCreateReq req);
ApiResult<ChatGroupCreateResp> chatGroupCreate(ChatGroupCreateReq req);
/**
* 获取IM账号
*/
Pair<Set<Long>, Set<Long>> fetchUsersByWorkspaceOuId(ChatGroupCreateReq.CrowTypeEnum crowType, Long workspaceId, Long ouId, Set<String> jobCodes, Long personId);
/**
* 通用查询-少条件后期在补
*/
List<ChatGroup> genericQuery(ChatGroupGenericSearchReq req);
/**
* 通过Id查询群聊
*/
ChatGroup getById(Long id);
/**
* 获取群聊
*/
ChatGroupQueryResp chatGroupQuery(ChatGroupQueryReq req);
/**
* 根据人群返回jobCode
*/
Set<String> buildJobCodesByCrowType(ChatGroupCreateReq.CrowTypeEnum crowType);
/**
* 发送钉钉群
*/
void sendDingRobot(String content);
/**
* 拉人进群
* @param tid 群Id
* @param owner 群主
* @param members im账号集合
* @param groupName 群名称
*/
void userAddChatGroup(Long chatGroupId, String tid, String owner, List<String> members, String groupName);
/**
* 踢出群
* @param tid 群Id
* @param owner 群主
* @param imAccount im账号
*/
void kickChatGroup(Long chatGroupId, String tid, String owner, String imAccount);
/**
* 更改群主
* @param chatGroupId 群聊Id
* @param oldAccIdOwner 原群主账号
* @param newAccIdOwner 新群主账号
*/
void changeOwner(Long chatGroupId, String oldAccIdOwner, String newAccIdOwner);
}

View File

@ -0,0 +1,54 @@
package cn.axzo.im.service;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.ChatGroupUserGenericSearchReq;
import cn.axzo.im.center.common.enums.ChatGroupStatusEnum;
import cn.axzo.im.center.common.enums.ChatGroupUserTypeEnum;
import cn.axzo.im.entity.ChatGroup;
import cn.axzo.im.entity.ChatGroupUser;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* @author xudawei@axzo.cn
* @date 2024/11/12
* @desc 群聊成员
*/
public interface ChatGroupUserService extends IService<ChatGroupUser> {
/**
* 创建群聊成员
*/
Boolean chatGroupUserCreate(Long chatGroupId, String tid,String accId, ChatGroupStatusEnum status, String remark, ChatGroupCreateReq.CrowTypeEnum crowType, ChatGroupUserTypeEnum type);
/**
* 批量创建群聊成员
*/
Boolean chatGroupUserBatchCreate(Long chatGroupId, String tid, List<String> members, ChatGroupStatusEnum status, String remark, ChatGroupCreateReq.CrowTypeEnum crowType);
/**
* 删除群聊成员
*/
Boolean chatGroupUserDelete(Long chatGroupId, String accId);
/**
* 通用查询-少条件后期在补
*/
List<ChatGroupUser> genericQuery(ChatGroupUserGenericSearchReq req);
/**
* 通用查询数量
*/
Integer genericQueryCount(ChatGroupUserGenericSearchReq req);
/**
* 更改群主
* @param chatGroupId 群聊Id
* @param oldAccId 原有账号
* @param newAccId 新账号
*/
void changeOwner(Long chatGroupId, String oldAccId, String newAccId);
}

View File

@ -0,0 +1,22 @@
package cn.axzo.im.service;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.ComplaintCreateReq;
import cn.axzo.im.center.api.vo.resp.ChatGroupCreateResp;
import cn.axzo.im.entity.ChatGroup;
import cn.axzo.im.entity.Complaint;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author xudawei@axzo.cn
* @date 2024/11/05
* @desc 群聊
*/
public interface ComplaintService extends IService<Complaint> {
/**
* 创建群聊
*/
void complaintCreate(ComplaintCreateReq req);
}

View File

@ -0,0 +1,10 @@
package cn.axzo.im.service;
public interface DingDingRobotService {
/**
* 发钉钉消息先默认是泰州的后续如果不同三方对应的行为不一致再调整
*
* @param content
*/
void send(String content);
}

View File

@ -0,0 +1,18 @@
package cn.axzo.im.service;
import cn.axzo.im.center.common.enums.OperateLogTypeEnum;
import cn.axzo.im.entity.OperateLog;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author xudawei@axzo.cn
* @date 2024/11/12
* @desc 操作日志
*/
public interface OperateLogService extends IService<OperateLog> {
/**
* 创建操作日志
*/
Boolean create(OperateLogTypeEnum typeEnum, String content);
}

View File

@ -1,20 +1,77 @@
package cn.axzo.im.service.impl;
import cn.axzo.apollo.workspace.api.workspace.res.GetDetailRes;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.ChatGroupGenericSearchReq;
import cn.axzo.im.center.api.vo.req.ChatGroupQueryReq;
import cn.axzo.im.center.api.vo.req.ChatGroupUserGenericSearchReq;
import cn.axzo.im.center.api.vo.req.UserAccountReq;
import cn.axzo.im.center.api.vo.resp.ChatGroupCreateResp;
import cn.axzo.im.center.api.vo.resp.ChatGroupQueryResp;
import cn.axzo.im.center.api.vo.resp.UserAccountResp;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.ChatGroupStatusEnum;
import cn.axzo.im.center.common.enums.ChatGroupUserTypeEnum;
import cn.axzo.im.center.common.enums.OperateLogTypeEnum;
import cn.axzo.im.channel.IMChannelProvider;
import cn.axzo.im.channel.netease.INotifyService;
import cn.axzo.im.channel.netease.NimChannelService;
import cn.axzo.im.channel.netease.dto.ChangeOwnerRequest;
import cn.axzo.im.channel.netease.dto.ChatGroupCreateRequest;
import cn.axzo.im.channel.netease.dto.ChatGroupCreateResponse;
import cn.axzo.im.channel.netease.dto.ChatGroupQueryResponse;
import cn.axzo.im.channel.netease.dto.KickChatGroupRequest;
import cn.axzo.im.channel.netease.dto.UserAddChatGroupRequest;
import cn.axzo.im.config.BizResultCode;
import cn.axzo.im.config.ChatGroupConfig;
import cn.axzo.im.config.MqProducer;
import cn.axzo.im.dao.mapper.ChatGroupMapper;
import cn.axzo.im.entity.ChatGroup;
import cn.axzo.im.event.payload.ChatGroupCreatePayload;
import cn.axzo.im.gateway.OrganizationalNodeApiGateway;
import cn.axzo.im.gateway.OrganizationalNodeUserApiGateway;
import cn.axzo.im.gateway.ProfilesApiGateway;
import cn.axzo.im.gateway.TyrApiGateway;
import cn.axzo.im.gateway.WorkspaceApiGateway;
import cn.axzo.im.service.AccountService;
import cn.axzo.im.service.ChatGroupService;
import cn.axzo.im.service.ChatGroupUserService;
import cn.axzo.im.service.OperateLogService;
import cn.axzo.im.utils.BizAssertions;
import cn.axzo.im.utils.DingTalkUtil;
import cn.axzo.im.utils.JobCodeUtils;
import cn.axzo.maokai.api.vo.request.OrganizationalNodeUserSearchReq;
import cn.axzo.maokai.api.vo.response.OrganizationalNodeUserVO;
import cn.axzo.maokai.api.vo.response.OrganizationalNodeVO;
import cn.axzo.tyr.client.common.enums.RoleTypeEnum;
import cn.axzo.tyr.client.model.roleuser.dto.SaasRoleUserV2DTO;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Sets;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.axzo.im.event.inner.EventTypeEnum.MESSAGE_CHAT_GROUP_CREATE;
@ -25,6 +82,7 @@ import static cn.axzo.im.event.inner.EventTypeEnum.MESSAGE_CHAT_GROUP_CREATE;
*/
@Slf4j
@Service
@RefreshScope
public class ChatGroupServiceImpl extends ServiceImpl<ChatGroupMapper, ChatGroup>
implements ChatGroupService {
@ -34,27 +92,434 @@ public class ChatGroupServiceImpl extends ServiceImpl<ChatGroupMapper, ChatGrou
@Autowired
private MqProducer mqProducer;
@Resource
private AccountService accountService;
@Resource
private INotifyService iNotifyService;
@Autowired
private ChatGroupConfig chatGroupConfig;
@Autowired
private NimChannelService nimChannelService;
@Autowired
private ChatGroupUserService chatGroupUserService;
@Autowired
private OperateLogService operateLogService;
@Autowired
private TyrApiGateway tyrApiGateway;
@Autowired
private ProfilesApiGateway profilesApiGateway;
@Autowired
private WorkspaceApiGateway workspaceApiGateway;
@Autowired
private OrganizationalNodeApiGateway organizationalNodeApiGateway;
@Autowired
private OrganizationalNodeUserApiGateway organizationalNodeUserApiGateway;
@Value("${spring.profiles.active}")
private String env;
@Value("${chatgroup.maxmember.count:199}")
private Integer chatGroupMaxMemberCount;
@Override
public ChatGroupCreateResp chatGroupCreate(ChatGroupCreateReq req) {
String owner = "";//TODO
//1 群聊创建
ChatGroupCreateResponse response = imChannelProvider.chatGroupCreate(ChatGroupCreateRequest.convertRequest(req, owner));
//2 群聊创建成功发送MQ
this.chatGroupCreateSendMQ(req, response);
//3 返回
return ChatGroupCreateResponse.convertResp(response);
@Transactional(rollbackFor = Throwable.class)
public ApiResult<ChatGroupCreateResp> chatGroupCreate(ChatGroupCreateReq req) {
// 超管账号
ChatGroupFetchOwnerResp resp = this.fetchAdminByWorkspaceOuId(req.getCrowType(), req.getWorkspaceId(), req.getOuId(), req.getCreator());
req.setOwner(resp.getOwnerId());
//创建群聊-校验
ApiResult<ChatGroupCreateResp> apiResult = this.checkChatGroupCreate(req, resp.getTeamNodeId());
if (Objects.nonNull(apiResult)) {
return apiResult;
}
// 群聊创建
ChatGroupCreateResponse response = imChannelProvider.chatGroupCreate(ChatGroupCreateRequest.convertRequest(req, resp.getOwnerImAccount(), this.buildAttach(req)));
ChatGroup chatGroup = this.buildChatGroupCreate(req, response, resp.getOwnerImAccount(), req.getOuId(), resp.getTeamNodeId());
// 保存db
this.saveOrUpdate(chatGroup);
this.chatGroupUserService.chatGroupUserCreate(chatGroup.getId(), response.getTid(), resp.getOwnerImAccount(), ChatGroupStatusEnum.SUCCESS, null, req.getCrowType(), ChatGroupUserTypeEnum.OWNER);
// 群聊创建成功发送MQ
this.chatGroupCreateSendMQ(req, response, resp.getOwnerId().toString(), resp.getOwnerImAccount(), chatGroup.getId());
// 返回
return ApiResult.ok(ChatGroupCreateResponse.convertResp(response, req.getGroupName(), req.getAvatarUrl()));
}
/**
* 创建群聊-校验
*/
public ApiResult<ChatGroupCreateResp> checkChatGroupCreate(ChatGroupCreateReq req , Long teamId) {
ChatGroup chatGroup = null;
StringBuilder desc = new StringBuilder();
//1 是否已经创建群聊校验
switch (req.getCrowType()) {
case WORKSPACE:
List<ChatGroup> chatGroups = this.genericQuery(ChatGroupGenericSearchReq.builder().workspaceId(req.getWorkspaceId()).crowType(req.getCrowType()).build());
chatGroup = CollectionUtils.isNotEmpty(chatGroups) ? chatGroups.get(0) : null;
desc.append("人群类型:项目,群聊已存在,workspaceId:").append(req.getWorkspaceId());
break;
case OU:
List<ChatGroup> chatGroupsWhenOu = this.genericQuery(ChatGroupGenericSearchReq.builder().workspaceId(req.getWorkspaceId()).ouId(req.getOuId()).crowType(req.getCrowType()).build());
chatGroup = CollectionUtils.isNotEmpty(chatGroupsWhenOu) ? chatGroupsWhenOu.get(0) : null;
desc.append("人群类型:单位,群聊已存在,workspaceId:").append(req.getWorkspaceId());
break;
case TEAM:
List<ChatGroup> chatGroupsWhenTeam = this.genericQuery(ChatGroupGenericSearchReq.builder().workspaceId(req.getWorkspaceId()).teamId(teamId).crowType(req.getCrowType()).build());
chatGroup = CollectionUtils.isNotEmpty(chatGroupsWhenTeam) ? chatGroupsWhenTeam.get(0) : null;
desc.append("人群类型:班组,群聊已存在,workspaceId:").append(req.getWorkspaceId());
break;
default:
BizAssertions.assertTrue(false, "人群不存在");
}
if (Objects.nonNull(chatGroup)) {
return ApiResult.ok(ChatGroupCreateResp.builder().tid(chatGroup.getTid())
.groupName(chatGroup.getName()).avatarUrl(chatGroup.getAvatarUrl())
.code(Integer.parseInt(BizResultCode.CHAT_GROUP_ALREADY_EXISTS.getErrorCode()))
.desc(desc.toString())
.build());
}
//2 人数超限校验
//Pair,key:管理人员集合;value:工人集合
Pair<Set<Long>, Set<Long>> adminWorkerSet = this.fetchUsersByWorkspaceOuId(req.getCrowType(), req.getWorkspaceId(), req.getOuId(), this.buildJobCodesByCrowType(req.getCrowType()), req.getCreator());
Set<Long> adminSet = adminWorkerSet.getKey();
Set<Long> workerSet = adminWorkerSet.getValue();
if ((adminSet.size() + workerSet.size()) > chatGroupMaxMemberCount) {
String error = String.format("创建群组:%s,超过人数:%d,项目Id:%d,人群类型:%s", req.getGroupName(), chatGroupMaxMemberCount, req.getWorkspaceId(), req.getCrowType());
this.sendDingRobot(error);
return ApiResult.ok(ChatGroupCreateResp.builder()
.code(Integer.parseInt(BizResultCode.CHAT_GROUP_OVER_MEMBERS_COUNT_LIMIT.getErrorCode()))
.desc(BizResultCode.CHAT_GROUP_OVER_MEMBERS_COUNT_LIMIT.getErrorMessage()).build());
}
return null;
}
public List<ChatGroup> genericQuery(ChatGroupGenericSearchReq req) {
return this.lambdaQuery().eq(Objects.nonNull(req.getWorkspaceId()), ChatGroup::getWorkspaceId, req.getWorkspaceId())
.eq(Objects.nonNull(req.getOuId()), ChatGroup::getOuId, req.getOuId())
.eq(Objects.nonNull(req.getTeamId()), ChatGroup::getTeamId, req.getTeamId())
.eq(Objects.nonNull(req.getCrowType()), ChatGroup::getCrowType, req.getCrowType())
.eq(ChatGroup::getIsDelete,0)
.list();
}
/**
* 通过Id查询群聊
*/
public ChatGroup getById(Long id) {
return this.lambdaQuery().eq(ChatGroup::getId, id)
.one();
}
/**
* 构建群聊创建对象
*/
private ChatGroup buildChatGroupCreate(ChatGroupCreateReq req, ChatGroupCreateResponse response, String groupOwner, Long ouId, Long teamNodeId) {
return ChatGroup.builder()
.name(req.getGroupName())
.tid(response.getTid())
.avatarUrl(req.getAvatarUrl())
.workspaceId(req.getWorkspaceId())
.groupType(req.getGroupType())
.crowType(req.getCrowType())
.ouId(req.getCrowType() == ChatGroupCreateReq.CrowTypeEnum.OU ? ouId : null)
.teamId(req.getCrowType() == ChatGroupCreateReq.CrowTypeEnum.TEAM ? teamNodeId : null)
.groupOwner(groupOwner)
.creator(req.getCreator())
.build();
}
/**
* 构建自定义扩展字段
*/
private String buildAttach(ChatGroupCreateReq req) {
GetDetailRes getDetailRes = workspaceApiGateway.getWorkspaceById(req.getWorkspaceId());
String workspaceName = getDetailRes.getName();
Map<String, String> attachMap = new HashMap<>();
attachMap.put("workspaceId", req.getWorkspaceId().toString());
attachMap.put("workspaceName", workspaceName);
return JSONUtil.toJsonStr(attachMap);//TODO
}
/**
* 群聊创建成功发送MQ
*/
private void chatGroupCreateSendMQ(ChatGroupCreateReq req, ChatGroupCreateResponse response) {
private void chatGroupCreateSendMQ(ChatGroupCreateReq req, ChatGroupCreateResponse response, String owner, String imAccountOwner, Long chatGroupId) {
Event event = Event.builder()
.targetId(String.valueOf(response.getTid()))
.targetType(MESSAGE_CHAT_GROUP_CREATE.getModel())
.eventCode(MESSAGE_CHAT_GROUP_CREATE.getEventCode())
.data(ChatGroupCreatePayload.builder().req(req).tid(response.getTid()).build())
.data(ChatGroupCreatePayload.builder().req(req).tid(response.getTid()).owner(owner).imAccountOwner(imAccountOwner).chatGroupId(chatGroupId).build())
.build();
mqProducer.send(event);
}
/**
* 获取超管账号
*/
private ChatGroupFetchOwnerResp fetchAdminByWorkspaceOuId(ChatGroupCreateReq.CrowTypeEnum crowType, Long workspaceId, Long ouId, Long personId) {
ChatGroupFetchOwnerResp resp = new ChatGroupFetchOwnerResp();
Long ownerId = null;
switch (crowType) {
case WORKSPACE:
List<SaasRoleUserV2DTO> saasRoleUserV2DTOS = tyrApiGateway.fetchSaasRoleUserByWorkspaceOuIdRoleTypes(workspaceId, null, Sets.newHashSet(RoleTypeEnum.SUPER_ADMIN));
if (CollectionUtils.isEmpty(saasRoleUserV2DTOS)) {
this.sendDingRobot(String.format("创建群聊,personId:%d在workspaceId:%d,人群:%s,找不到群主信息", personId, workspaceId,crowType));
}
BizAssertions.assertNotEmpty(saasRoleUserV2DTOS, "personId:{}在workspaceId:{},找不到群主信息", personId, workspaceId);
ownerId = saasRoleUserV2DTOS.get(0).getSaasRoleUser().getPersonId();
break;
case OU:
List<SaasRoleUserV2DTO> saasRoleUserV2DTOSWhenOu = tyrApiGateway.fetchSaasRoleUserByWorkspaceOuIdRoleTypes(workspaceId, ouId, Sets.newHashSet(RoleTypeEnum.ADMIN));
if (CollectionUtils.isEmpty(saasRoleUserV2DTOSWhenOu)) {
this.sendDingRobot(String.format("创建群聊,personId:%d在workspaceId:%d,ouId:%d,人群:%s,找不到群主信息", personId, workspaceId,ouId, crowType));
}
BizAssertions.assertNotEmpty(saasRoleUserV2DTOSWhenOu, "personId:{}在workspaceId:{},ouId:{},找不到群主信息", personId, workspaceId, ouId);
ownerId = saasRoleUserV2DTOSWhenOu.get(0).getSaasRoleUser().getPersonId();
break;
case TEAM:
//获取personId在workspaceId下项目班组对象(角色列表班组长/带班长/小组长/工人)
OrganizationalNodeVO projectTeamUser = this.fetchProjectTeamByWorkspacePersonIdJobCodes(workspaceId, personId, JobCodeUtils.CHAT_GROUP_TEAM_MEMBER_JOB_CODES);
resp.setTeamNodeId(projectTeamUser.getId());
//查询项目Id下的班组的班组长
List<OrganizationalNodeUserVO> projectTeamLeaderList = this.organizationalNodeUserApiGateway.searchNodeUser( projectTeamUser.getId(), workspaceId, Sets.newHashSet(JobCodeUtils.WORKSPACE_TEAM_ADMIN_JOB_CODE), null);
BizAssertions.assertNotEmpty(projectTeamLeaderList, "personId:{}在workspaceId:{},找不到班组长信息");
ownerId = projectTeamLeaderList.get(0).getPersonId();
break;
default:
BizAssertions.assertTrue(false, "人群不存在");
}
if (Objects.isNull(ownerId)) {
this.sendDingRobot(String.format("群主用户不存在,workspaceId:%d,ouId:%d,personId:%d,crowType:%s", workspaceId, ouId, personId, crowType));
}
BizAssertions.assertNotNull(ownerId, "群主用户不存在");
PersonProfileDto owner = profilesApiGateway.getPersonProfileById(ownerId);
BizAssertions.assertFalse(Objects.isNull(owner) || Objects.isNull(owner.getId()), "超管用户不存在");
UserAccountReq userAccountReq = UserAccountReq.builder()
.appType(AppTypeEnum.CMP.getCode())//管理端:AppTypeEnum.CMP.getCode();工人端:AppTypeEnum.CM.getCode()
.userId(owner.getId().toString()).nickName(owner.getRealName())
.headImageUrl(owner.getAvatarUrl())
.organizationalUnitId(ouId)
.build();
UserAccountResp userAccountResp = accountService.generateAccount(userAccountReq, iNotifyService);
resp.setOwnerId(owner.getId());
resp.setOwnerImAccount(userAccountResp.getImAccount());
return resp;
}
/**
* 获取IM账号
*/
public Pair<Set<Long>, Set<Long>> fetchUsersByWorkspaceOuId(ChatGroupCreateReq.CrowTypeEnum crowType, Long workspaceId, Long ouId, Set<String> jobCodes,Long personId) {
switch (crowType) {
case WORKSPACE:
List<OrganizationalNodeUserVO> userListWhenWorkspace = organizationalNodeUserApiGateway.fetchNodeUsersByWorkspaceIdJobCodes(workspaceId, jobCodes);
BizAssertions.assertNotEmpty(userListWhenWorkspace, "人群是项目时,管理员为空,workspaceId:{},ouId:{}", workspaceId, ouId);
Set<Long> personListWhenWorkspace = userListWhenWorkspace.stream().map(OrganizationalNodeUserVO::getPersonId).collect(Collectors.toSet());
return Pair.of(personListWhenWorkspace, Sets.newHashSet());
case OU:
List<OrganizationalNodeUserVO> userListWhenOuId = organizationalNodeUserApiGateway.fetchNodeUsersByWorkspaceOuIdJobCodes(workspaceId,ouId,jobCodes);
BizAssertions.assertNotEmpty(userListWhenOuId, "人群是单位时,管理员为空,workspaceId:{},ouId:{}", workspaceId, ouId);
Set<Long> personIdListWhenOuId = userListWhenOuId.stream().map(OrganizationalNodeUserVO::getPersonId).collect(Collectors.toSet());
return Pair.of(personIdListWhenOuId, Sets.newHashSet());
case TEAM:
//获取personId在workspaceId下项目班组对象(角色列表班组长/带班长/小组长/工人)
OrganizationalNodeVO projectTeamUser = this.fetchProjectTeamByWorkspacePersonIdJobCodes(workspaceId, personId, JobCodeUtils.CHAT_GROUP_TEAM_MEMBER_JOB_CODES);
//班组长/带班长
Set<Long> teamerList = this.fetchUsersByWorkspaceIdJobCodes(workspaceId, ouId,projectTeamUser.getId(), Sets.newHashSet("projTeamLeader", "projectTeamManager"), true);
Set<Long> workerList = this.fetchUsersByWorkspaceIdJobCodes(workspaceId, ouId,projectTeamUser.getId(), Sets.newHashSet("projWorker","projectTeamGPLeader"), false);
return Pair.of(teamerList,workerList);
default:
throw new ServiceException("人群不存在");
}
}
/**
* 通过项目Id/人员Id/岗位code集合获取项目内班组的OrganizationalNode组织对象
* @param workspaceId 项目Id
* @param personId 人员Id
* @param jobCodes 岗位code集合
* @return 项目内班组的OrganizationalNode组织对象
*/
private OrganizationalNodeVO fetchProjectTeamByWorkspacePersonIdJobCodes(Long workspaceId,Long personId, Set<String> jobCodes) {
List<OrganizationalNodeUserVO> projectTeamUserList = this.organizationalNodeUserApiGateway.searchNodeUser( null, workspaceId, jobCodes, personId);
BizAssertions.assertNotEmpty(projectTeamUserList, "personId:{}在workspaceId:{},找不到项目内组织用户信息");
List<Long> nodeIdList = projectTeamUserList.stream().map(OrganizationalNodeUserVO::getOrganizationalNodeId).collect(Collectors.toList());
BizAssertions.assertNotEmpty(nodeIdList, "personId:{}在workspaceId:{},找不到项目内组织信息");
List<OrganizationalNodeVO> nodeVOList = organizationalNodeApiGateway.fetchNodesByNodeIds(nodeIdList);
BizAssertions.assertNotEmpty(nodeVOList, "personId:{}在workspaceId:{},找不到项目内组织信息");
//只获取项目内班组节点类型 1.部门 2.平台班组 3.小组 4.项目内班组 5.项目内小组
List<OrganizationalNodeVO> nodeVOTeamList = nodeVOList.stream().filter(item -> item.getNodeType().equals(4)).collect(Collectors.toList());
BizAssertions.assertNotEmpty(nodeVOTeamList, "personId:{}在workspaceId:{},找不到项目内班组信息");
return nodeVOTeamList.get(0);
}
private Set<Long> fetchUsersByWorkspaceIdJobCodes(Long workspaceId, Long ouId, Long nodeId,Set<String> jobCodes, boolean isAdmin) {
List<OrganizationalNodeUserVO> userListWhenTeamer = organizationalNodeUserApiGateway.fetchNodeUsersByWorkspaceNodeIdJobCodes(workspaceId, nodeId, jobCodes);
BizAssertions.assertTrue(CollectionUtils.isNotEmpty(userListWhenTeamer) && isAdmin, "人群是班组时,{}为空,workspaceId:{},ouId:{}", isAdmin ? "班组长/带班长" : "工人", workspaceId, ouId);
return userListWhenTeamer.stream().map(OrganizationalNodeUserVO::getPersonId).collect(Collectors.toSet());
}
/**
* 获取群聊
*/
@Override
public ChatGroupQueryResp chatGroupQuery(ChatGroupQueryReq req) {
ChatGroupQueryResponse chatGroupQueryResponse = this.imChannelProvider.chatGroupQueryByTids(req.getTids(), req.getOpe());
return BeanUtil.copyProperties(chatGroupQueryResponse, ChatGroupQueryResp.class);
}
/**
* 根据人群返回jobCode
*/
@Override
public Set<String> buildJobCodesByCrowType(ChatGroupCreateReq.CrowTypeEnum crowType) {
switch (crowType) {
case WORKSPACE:
return Sets.newHashSet(JobCodeUtils.CHAT_GROUP_ADMIN_JOB_CODES);
case OU:
return Sets.newHashSet(JobCodeUtils.CHAT_GROUP_ADMIN_JOB_CODES);
case TEAM:
return Sets.newHashSet(JobCodeUtils.CHAT_GROUP_TEAM_MEMBER_JOB_CODES);// TODO
default:
throw new ServiceException("人群不存在");
}
}
/**
* 更改群主
* @param chatGroupId 群聊Id
* @param oldAccIdOwner 原群主账号
* @param newAccIdOwner 新群主账号
*/
@Override
public void changeOwner(Long chatGroupId, String oldAccIdOwner, String newAccIdOwner) {
//根据群聊Id获取群聊
ChatGroup chatGroup = this.getById(chatGroupId);
try {
ChangeOwnerRequest changeOwnerRequest = new ChangeOwnerRequest();
changeOwnerRequest.setTid(chatGroup.getTid());
changeOwnerRequest.setOwner(oldAccIdOwner);
changeOwnerRequest.setNewowner(newAccIdOwner);
changeOwnerRequest.setLeave(1);
nimChannelService.changeOwner(changeOwnerRequest);
chatGroupUserService.changeOwner(chatGroupId, oldAccIdOwner, newAccIdOwner);
operateLogService.create(OperateLogTypeEnum.USER_ENTER_CHAT_GROUP, String.format("changeOwner-success,chatGroupId:%d,oldAccIdOwner:%s,newAccIdOwner:%s", chatGroupId, oldAccIdOwner, newAccIdOwner));
} catch (ServiceException serviceException) {
operateLogService.create(OperateLogTypeEnum.CHANGE_OWNER, String.format("changeOwner-fail,chatGroupId:%d,oldAccIdOwner:%s,newAccIdOwner:%s", chatGroupId, oldAccIdOwner, newAccIdOwner));
String error = String.format("更改群主失败,群聊名称:%s,原群主账号:%s,新群主账号:%s", chatGroup.getName(), oldAccIdOwner, newAccIdOwner);
this.sendDingRobot(error);
}
}
/**
* 拉人进群
* @param chatGroupId 群Id
* @param tid 网易云信群Id
* @param owner 群主
* @param members im账号集合
* @param groupName 群名称
*/
@Override
public void userAddChatGroup(Long chatGroupId, String tid, String owner, List<String> members, String groupName) {
ChatGroupUserGenericSearchReq req = new ChatGroupUserGenericSearchReq();
req.setChatGroupId(chatGroupId);
Integer chatGroupCount = this.chatGroupUserService.genericQueryCount(req);
ChatGroup chatGroup = this.getById(chatGroupId);
//超过限制人数校验
if (chatGroupCount > chatGroupMaxMemberCount) {
String error = String.format("群组:%s,超过人数:%d,项目Id:%d,人群类型:%s", groupName, chatGroupMaxMemberCount, chatGroup.getWorkspaceId(), chatGroup.getCrowType());
this.sendDingRobot(error);
return ;
}
UserAddChatGroupRequest request = new UserAddChatGroupRequest();
request.setTid(tid);
request.setOwner(owner);
request.setMembers(members);
request.setMsg("进入群:" + groupName);
try {
nimChannelService.userAddChatGroup(request);
chatGroupUserService.chatGroupUserBatchCreate(chatGroupId,tid, members, ChatGroupStatusEnum.SUCCESS,null, chatGroup.getCrowType());
operateLogService.create(OperateLogTypeEnum.USER_ENTER_CHAT_GROUP, String.format("user:%s,enter chatGroup:%s,owner:%s", JSON.toJSONString(members), tid, owner));
} catch (ServiceException serviceException) {
String error = String.format("errorCode: %s;msg:%s" ,serviceException.getErrorCode(), serviceException.getMessage());
chatGroupUserService.chatGroupUserBatchCreate(chatGroupId,tid, members, ChatGroupStatusEnum.FAIL,error, chatGroup.getCrowType());
operateLogService.create(OperateLogTypeEnum.USER_ENTER_CHAT_GROUP, String.format("user:%s,enter-error chatGroup:%s,owner:%s", JSON.toJSONString(members), tid, owner));
this.sendDingRobot(error);
}
}
/**
* 踢出群
* @param tid 群Id
* @param owner 群主
* @param imAccount im账号
*/
@Override
public void kickChatGroup(Long chatGroupId, String tid, String owner, String imAccount) {
KickChatGroupRequest kickChatGroupRequest = new KickChatGroupRequest();
kickChatGroupRequest.setTid(tid);
kickChatGroupRequest.setOwner(owner);
kickChatGroupRequest.setMember(imAccount);
try {
nimChannelService.kickChatGroup(kickChatGroupRequest);
chatGroupUserService.chatGroupUserDelete(chatGroupId, imAccount);
operateLogService.create(OperateLogTypeEnum.USER_EXIT_CHAT_GROUP, String.format("user:%s,exit chatGroup:%s,owner:%s", imAccount, tid, owner));
} catch (ServiceException serviceException) {
String error = String.format("errorCode: %s;msg:%s" ,serviceException.getErrorCode(), serviceException.getMessage());
operateLogService.create(OperateLogTypeEnum.USER_EXIT_CHAT_GROUP, String.format("user:%s,exit-error chatGroup:%s,owner:%s,error", imAccount, tid, owner, error));
this.sendDingRobot(error);
}
}
@Override
public void sendDingRobot(String content) {
if (BooleanUtils.isNotTrue(chatGroupConfig.getDingTalkBotEnabled())) {
log.info("DingTalkBotEnabled = false,屏蔽钉钉消息发送。content = {}", content);
return;
}
DingTalkUtil.sendMessage("【环境:" + env + "\n" + content, chatGroupConfig.getDingTalkBotAccessToken(), chatGroupConfig.getDingTalkBotSecret());
}
}
@Data
class ChatGroupFetchOwnerResp {
private Long ownerId;
private String ownerImAccount;
private Long teamNodeId;
private String desc;
}

View File

@ -0,0 +1,106 @@
package cn.axzo.im.service.impl;
import cn.axzo.im.center.api.vo.req.ChatGroupCreateReq;
import cn.axzo.im.center.api.vo.req.ChatGroupUserGenericSearchReq;
import cn.axzo.im.center.common.enums.ChatGroupStatusEnum;
import cn.axzo.im.center.common.enums.ChatGroupUserTypeEnum;
import cn.axzo.im.dao.mapper.ChatGroupUserMapper;
import cn.axzo.im.entity.ChatGroup;
import cn.axzo.im.entity.ChatGroupUser;
import cn.axzo.im.service.ChatGroupService;
import cn.axzo.im.service.ChatGroupUserService;
import com.alibaba.excel.util.StringUtils;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author xudawei@axzo.cn
* @date 2024/11/05
* @desc 群聊用户
*/
@Slf4j
@Service
@RefreshScope
public class ChatGroupUserServiceImpl extends ServiceImpl<ChatGroupUserMapper, ChatGroupUser>
implements ChatGroupUserService {
@Autowired
private ChatGroupService chatGroupService;
@Override
public Boolean chatGroupUserCreate(Long chatGroupId, String tid, String accId, ChatGroupStatusEnum status, String remark, ChatGroupCreateReq.CrowTypeEnum crowType, ChatGroupUserTypeEnum type) {
return this.saveOrUpdate(ChatGroupUser.builder()
.chatGroupId(chatGroupId)
.tid(tid)
.accId(accId)
.status(status)
.crowType(crowType)
.type(type)
.remark(remark).build());
}
/**
* 更改群主
* @param chatGroupId 群聊Id
* @param oldAccId 原有账号
* @param newAccId 新账号
*/
@Override
public void changeOwner(Long chatGroupId, String oldAccId, String newAccId) {
this.chatGroupUserDelete(chatGroupId, oldAccId);
ChatGroup chatGroup = chatGroupService.getById(chatGroupId);
this.chatGroupUserCreate(chatGroupId, chatGroup.getTid(), newAccId, ChatGroupStatusEnum.SUCCESS, null, chatGroup.getCrowType(), ChatGroupUserTypeEnum.OWNER);
}
@Override
public Boolean chatGroupUserBatchCreate(Long chatGroupId, String tid, List<String> members, ChatGroupStatusEnum status, String remark, ChatGroupCreateReq.CrowTypeEnum crowType) {
if (CollectionUtils.isEmpty(members)) {
return true;
}
List<ChatGroupUser> chatGroupUsers = members.stream().map(item -> ChatGroupUser.builder().
chatGroupId(chatGroupId)
.tid(tid)
.accId(item)
.status(status)
.crowType(crowType)
.remark(remark).build()).collect(Collectors.toList());
return this.saveBatch(chatGroupUsers);
}
@Override
public Boolean chatGroupUserDelete(Long chatGroupId, String accId) {
return this.lambdaUpdate().eq(ChatGroupUser::getChatGroupId, chatGroupId)
.eq(ChatGroupUser::getAccId, accId)
.eq(ChatGroupUser::getIsDelete, 0)
.setSql("is_delete = id").update();
}
private LambdaQueryChainWrapper<ChatGroupUser> genericQueryWrapper(ChatGroupUserGenericSearchReq req) {
return this.lambdaQuery()
.eq(Objects.nonNull(req.getChatGroupId()), ChatGroupUser::getChatGroupId, req.getChatGroupId())
.eq(StringUtils.isNotBlank(req.getTid()), ChatGroupUser::getTid, req.getTid())
.eq(StringUtils.isNotBlank(req.getAccId()), ChatGroupUser::getAccId, req.getAccId());
}
@Override
public List<ChatGroupUser> genericQuery(ChatGroupUserGenericSearchReq req) {
return this.genericQueryWrapper(req).list();
}
@Override
public Integer genericQueryCount(ChatGroupUserGenericSearchReq req) {
return this.genericQueryWrapper(req).count();
}
}

View File

@ -0,0 +1,59 @@
package cn.axzo.im.service.impl;
import cn.axzo.im.center.api.vo.req.ComplaintCreateReq;
import cn.axzo.im.dao.mapper.ComplaintMapper;
import cn.axzo.im.entity.Complaint;
import cn.axzo.im.service.ChatGroupService;
import cn.axzo.im.service.ComplaintService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author xudawei@axzo.cn
* @date 2024/11/05
* @desc 群聊
*/
@Slf4j
@Service
public class ComplaintServiceImpl extends ServiceImpl<ComplaintMapper, Complaint>
implements ComplaintService {
@Autowired
private ChatGroupService chatGroupService;
@Override
public void complaintCreate(ComplaintCreateReq req) {
chatGroupService.sendDingRobot(String.format("IM投诉,fromId:%s,toId:%s,类型:%s,投诉内容:%s", req.getFromId(), req.getTaccId(), req.getType(), req.getComplaintContent()));
// 保存投诉
this.saveOrUpdate(this.buildComplaint(req));
}
/**
* 构建投诉
*/
private Complaint buildComplaint(ComplaintCreateReq req) {
String toId = "";
String tid = "";
switch (req.getType()) {
case GROUP:
tid = req.getTaccId();
break;
case PRIVATE:
toId = req.getTaccId();
break;
}
return Complaint.builder()
.complaintContent(req.getComplaintContent())
.type(req.getType())
.fromId(req.getFromId())
.toId(toId)
.tid(tid)
.creator(req.getCreator())
.build();
}
}

View File

@ -0,0 +1,28 @@
package cn.axzo.im.service.impl;
import cn.axzo.im.config.ChatGroupConfig;
import cn.axzo.im.service.DingDingRobotService;
import cn.axzo.im.utils.DingTalkUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class DingDingRobotServiceImpl implements DingDingRobotService {
@Value("${spring.profiles.active}")
private String env;
private final ChatGroupConfig chatGroupConfig;
@Override
public void send(String content) {
if (BooleanUtils.isNotTrue(chatGroupConfig.getDingTalkBotEnabled())) {
log.info("DingTalkBotEnabled = false,屏蔽钉钉消息发送。content = {}", content);
return;
}
DingTalkUtil.sendMessage("【环境:" + env + "\n" + content, chatGroupConfig.getDingTalkBotAccessToken(), chatGroupConfig.getDingTalkBotSecret());
}
}

View File

@ -0,0 +1,38 @@
package cn.axzo.im.service.impl;
import cn.axzo.im.center.api.vo.req.ChatGroupUserGenericSearchReq;
import cn.axzo.im.center.common.enums.OperateLogTypeEnum;
import cn.axzo.im.dao.mapper.ChatGroupUserMapper;
import cn.axzo.im.dao.mapper.OperateLogMapper;
import cn.axzo.im.entity.ChatGroup;
import cn.axzo.im.entity.ChatGroupUser;
import cn.axzo.im.entity.OperateLog;
import cn.axzo.im.service.ChatGroupUserService;
import cn.axzo.im.service.OperateLogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author xudawei@axzo.cn
* @date 2024/11/12
* @desc 操作日志
*/
@Slf4j
@Service
@RefreshScope
public class OperateLogServiceImpl extends ServiceImpl<OperateLogMapper, OperateLog>
implements OperateLogService {
/**
* 创建操作日志
*/
@Override
public Boolean create(OperateLogTypeEnum typeEnum, String content) {
return this.saveOrUpdate(OperateLog.builder().type(typeEnum).content(content).build());
}
}

View File

@ -0,0 +1,58 @@
package cn.axzo.im.utils;
import cn.hutool.core.util.StrUtil;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiRobotSendResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Slf4j
public class DingTalkUtil {
public static void sendMessage(String content, String accessToken, String secret) {
Long timestamp = System.currentTimeMillis();
String sign = getSign(timestamp, secret);
if (StrUtil.isBlank(sign)) {
return;
}
String url = StrUtil.format("https://oapi.dingtalk.com/robot/send?access_token={}&sign={}&timestamp={}",
accessToken, sign, String.valueOf(timestamp));
DingTalkClient client = new DefaultDingTalkClient(url);
OapiRobotSendRequest req = new OapiRobotSendRequest();
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(content);
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
at.setIsAtAll(false);
req.setMsgtype("text");
req.setText(text);
req.setAt(at);
try {
OapiRobotSendResponse response = client.execute(req);
log.info("发送钉钉消息结果:{}", response);
} catch (Exception error) {
log.error("发送钉钉消息失败:{}", error.getMessage());
}
}
private static String getSign(Long timestamp, String secret) {
try {
String stringToSign = timestamp + "\n" + secret;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
} catch (Exception ignored) {
return "";
}
}
}

View File

@ -0,0 +1,76 @@
package cn.axzo.im.utils;
import com.google.common.collect.Sets;
import java.util.Set;
/**
* @author xudawei@axzo.cn
* @date 2014/11/07
*/
public class JobCodeUtils {
public static Set<String> CHAT_GROUP_ADMIN_JOB_CODES = Sets.newHashSet("HumanResources","YoElse","YoUserexperiencedesi","YoProdcutmanager","YoHardwareengineer","YoTestengineer","YoOperationmanager","YoOperationandmainte","YoProjectimplementer","YoIndustryresearcher","YoDeveloper","YoTheprojectmanager"
,"YoChieftechnologyoff","YoChieffinancialoffi","YoChiefoperatingoffi","YoChiefexecutiveoffi","cms:ou_staff","cms:ou_archivist","cms:ou_seal_administrator","cms:ou_office_staff","cms:ou_office_chief","cms:ou_legal_staff","EwLegaldepartmentman","cms:ou_hr_staff","EwHumanresourcesmana","cms:ou_accountant_staff"
,"cms:ou_cashier_staff","EwFinancialcontrolle","cms:ou_contract_staff","EwContractdepartment","cms:ou_budget_staff","cms:ou_calculate_engnieer","cms:ou_cost_engnieer","cms:ou_business_manager","cms:ou_buyer","cms:ou_operation_staff","EwBusinessdepartment","cms:ou_business_vice_officer","cms:ou_chief_economic_manager"
,"cms:ou_technology_staff","EwTechnicaldepartmen","EwChiefengineer","cms:ou_machine_staff","cms:ou_securityt_staff","EwSecuritydepartment","cms:ou_quantity_staff","EwQualitydepartmentm","cms:ou_material_staff","cms:ou_professional_engineer","cms:ou_engineering_manager","EwProductiongeneralm","EwThesecretary","EwThegeneralmanager"
,"cms:ou_executive_director","cms:ou_chairman","other","OtProjectManager","scother","scresposible","scinspector","scSafetydesignedprison","cms:jl_project_professional_supervision_engineer","scCostofthedesignedprison","scTherepresentative","scProjectmanager","cuother","cms:js_project_security_chief","cuTheprojectintheindustry","curesposible"
,"cuThecostengineer","cuProfessionalengineers","cuProjectmanager","lsother","istreasurer1","isTreasurer","lsTestsamplingmember","lsQualitativecheckmember","lsThequalitysupervis","cms:lafb_project_machine_staff","lssafe","lsSecuritychief","lsMaterialkeeper","lsGoodsreceiving","lsSuppliestobuyer","lsCompetentmaterials","cms:lafb_project_contract_staff"
,"lsBudgetmember","lsLabormember","cms:lafb_project_business_vice_officer","lssurveyor","ziliaoyuan","lsThetechnician","lsTechnicalmanager","cms:lafb_project_trainee_construction_staff","lsThebuilder","isEngineeringmanager","lsProductionvicemanager","lsTheprojectmanager","psother","pstreasurer1","cms:mafb_project_accountant_staff","PSTreasurer"
,"psresposible","psTestsamplingmember","psQualitativecheckmember","psThequalitysupervisor","pssafe","psSecuritychief","cms:mafb_project_machine_staff","psMaterialwarehousekeeper","psGoodsreceiving","psSuppliestobuyer","psCompetentmaterials","psLabormember","psThecontractadministrator","psBudgetmember","cms:mafb_project_business_vice_officer"
,"cms:mafb_project_standard_staff","pssurveyor","psThetechnician","psTechnicalmanage","cms:mafb_project_technology_president","cms:mafb_project_electricity_manager","cms:mafb_project_trainee_construction_staff","psThebuilder","cms:mafb_project_schedule_manager","psEngineeringmanager","psProductionvicemanager","cms:mafb_project_secretary"
,"cms:mafb_project_manger","psTheprojectmanager","coother","coLogisticsAdministr","coOfficeclerk","cms:zb_project_comperhensive_chief","cotreasurer","coaccounting","cms:zb_project_financial_chief","coresposible","cms:zb_project_referenc_chief","coTheoperator","coQualitativecheckmember","cms:zb_project_quantity_chief","cosafe","cms:zb_project_security_chief"
,"coMechanicaladministrator","coMaterialwarehousekeeper","coGoodsreceiving","coSuppliestobuyer","cms:zb_project_material_chief","coLabormember","cms:zb_project_contract_manager","coBudgetmember","coTheprojectbusinessassistantmanager","coStandardOfficer","cosurveyor","coMeasuringhead","coThetechnician","cms:zb_project_technology_commissioner","coTechnicalprojectgeneral"
,"coNearwatertheelectricityintheadministrator","coTraineebuilder","coThebuilder","coPlanadministrator","comechanicalandelectricalmanager","cms:zb_project_engineering_commissioner","coProductionvicemanageroftheproject","coSecretaryoftheproject","coProjectimplementationmanager","coTheprojectmanager");
/**
* 班组长/带班长/小组长工人
*/
public static Set<String> CHAT_GROUP_TEAM_MEMBER_JOB_CODES = Sets.newHashSet("projTeamLeader","projectTeamManager","projectTeamGPLeader","projWorker");
/**
* 班组长岗位code
*/
private static String PROJECT_TEAM_LEADER_JOB_CODE = "projTeamLeader";
/**
* 项目管理员岗位code
*/
public static final String WORKSPACE_ADMIN_JOB_CODE = "super admin";
/**
* 项目单位下的管理员岗位code
*/
public static final String WORKSPACE_OU_ADMIN_JOB_CODE = "sub admin";
/**
* 项目内班组长
*/
public static final String WORKSPACE_TEAM_ADMIN_JOB_CODE = "projTeamLeader";
/**
* 是否管理岗位
*/
public static boolean isAdmin(String jobCode) {
return CHAT_GROUP_ADMIN_JOB_CODES.contains(jobCode);
}
/**
* 是否项目班组内的岗位(班组长/带班长/小组长工人)
*
*/
public static boolean isTeam(String jobCode) {
return CHAT_GROUP_TEAM_MEMBER_JOB_CODES.contains(jobCode);
}
/**
* 是否是班组长
*/
public static boolean isProjectTeamLeader(String jobCode) {
return PROJECT_TEAM_LEADER_JOB_CODE.equals(jobCode);
}
}