diff --git a/im-center-api/pom.xml b/im-center-api/pom.xml index 415ad9d..594b434 100644 --- a/im-center-api/pom.xml +++ b/im-center-api/pom.xml @@ -48,6 +48,10 @@ cn.axzo.apollo apollo-api + + event-hub-api + cn.axzo.event-hub + diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/feign/ChatGroupApi.java b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/ChatGroupApi.java index a2b75a2..0bc9aaa 100644 --- a/im-center-api/src/main/java/cn/axzo/im/center/api/feign/ChatGroupApi.java +++ b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/ChatGroupApi.java @@ -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 chatGroupCreate(@RequestBody @Validated ChatGroupCreateReq chatGroupCreateReq); + /** + * 获取群聊 + */ + @PostMapping("api/im/chat/group/query") + ApiResult chatGroupQuery(@RequestBody @Validated ChatGroupQueryReq chatGroupQueryReq); + } diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/feign/ComplaintApi.java b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/ComplaintApi.java index fd995ab..6271754 100644 --- a/im-center-api/src/main/java/cn/axzo/im/center/api/feign/ComplaintApi.java +++ b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/ComplaintApi.java @@ -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 complaintCreate(@RequestBody @Validated ChatGroupCreateReq chatGroupCreateReq); + ApiResult complaintCreate(@RequestBody @Validated ComplaintCreateReq req); } diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupCreateReq.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupCreateReq.java index 94a60de..0bf731a 100644 --- a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupCreateReq.java +++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupCreateReq.java @@ -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; + } } diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupGenericSearchReq.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupGenericSearchReq.java new file mode 100644 index 0000000..7b10bc0 --- /dev/null +++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupGenericSearchReq.java @@ -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; + +} diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupListReq.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupListReq.java new file mode 100644 index 0000000..c99d5e8 --- /dev/null +++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupListReq.java @@ -0,0 +1,4 @@ +package cn.axzo.im.center.api.vo.req; + +public class ChatGroupListReq { +} diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupQueryReq.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupQueryReq.java new file mode 100644 index 0000000..e7f06df --- /dev/null +++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupQueryReq.java @@ -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 tids; + + + /** + * 1,表示带上群成员列表;0,表示不带群成员列表,只返回群信息 + */ + @NotNull(message = "ope不能为空") + private Integer ope; +} diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupUserGenericSearchReq.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupUserGenericSearchReq.java new file mode 100644 index 0000000..e11ae80 --- /dev/null +++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ChatGroupUserGenericSearchReq.java @@ -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; + +} diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ComplaintCreateReq.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ComplaintCreateReq.java index 9e37b6a..f16a8cf 100644 --- a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ComplaintCreateReq.java +++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/ComplaintCreateReq.java @@ -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; + + } + } diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/ChatGroupCreateResp.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/ChatGroupCreateResp.java index 8878b5a..0cbaa7b 100644 --- a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/ChatGroupCreateResp.java +++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/ChatGroupCreateResp.java @@ -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; + + + } diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/ChatGroupQueryResp.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/ChatGroupQueryResp.java new file mode 100644 index 0000000..1ddf4ed --- /dev/null +++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/ChatGroupQueryResp.java @@ -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 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; + } + +} diff --git a/im-center-common/src/main/java/cn/axzo/im/center/common/enums/ChatGroupStatusEnum.java b/im-center-common/src/main/java/cn/axzo/im/center/common/enums/ChatGroupStatusEnum.java new file mode 100644 index 0000000..77b7c4e --- /dev/null +++ b/im-center-common/src/main/java/cn/axzo/im/center/common/enums/ChatGroupStatusEnum.java @@ -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; + +} diff --git a/im-center-common/src/main/java/cn/axzo/im/center/common/enums/ChatGroupUserTypeEnum.java b/im-center-common/src/main/java/cn/axzo/im/center/common/enums/ChatGroupUserTypeEnum.java new file mode 100644 index 0000000..0be9a77 --- /dev/null +++ b/im-center-common/src/main/java/cn/axzo/im/center/common/enums/ChatGroupUserTypeEnum.java @@ -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; + +} diff --git a/im-center-common/src/main/java/cn/axzo/im/center/common/enums/OperateLogTypeEnum.java b/im-center-common/src/main/java/cn/axzo/im/center/common/enums/OperateLogTypeEnum.java new file mode 100644 index 0000000..a11897b --- /dev/null +++ b/im-center-common/src/main/java/cn/axzo/im/center/common/enums/OperateLogTypeEnum.java @@ -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; + +} diff --git a/im-center-server/pom.xml b/im-center-server/pom.xml index cf7e363..b7a35b3 100644 --- a/im-center-server/pom.xml +++ b/im-center-server/pom.xml @@ -109,6 +109,12 @@ cn.axzo.maokai maokai-api + + + event-hub-api + cn.axzo.event-hub + + @@ -125,6 +131,12 @@ cn.axzo.tyr tyr-api + + + com.aliyun + alibaba-dingtalk-service-sdk + 2.0.0 + diff --git a/im-center-server/src/main/java/cn/axzo/im/ImCenterDevApplication.java b/im-center-server/src/main/java/cn/axzo/im/ImCenterDevApplication.java new file mode 100644 index 0000000..002c829 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/ImCenterDevApplication.java @@ -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----------------------------------------------------------"); + } +} diff --git a/im-center-server/src/main/java/cn/axzo/im/channel/IMChannelProvider.java b/im-center-server/src/main/java/cn/axzo/im/channel/IMChannelProvider.java index b0963b6..6a38a0d 100644 --- a/im-center-server/src/main/java/cn/axzo/im/channel/IMChannelProvider.java +++ b/im-center-server/src/main/java/cn/axzo/im/channel/IMChannelProvider.java @@ -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 tids, Integer ope); + + /** + * 转让群主 + */ + ChangeOwnerResponse changeOwner(ChangeOwnerRequest request); } diff --git a/im-center-server/src/main/java/cn/axzo/im/channel/netease/NimChannelService.java b/im-center-server/src/main/java/cn/axzo/im/channel/netease/NimChannelService.java index 652e0eb..e3ec40e 100644 --- a/im-center-server/src/main/java/cn/axzo/im/channel/netease/NimChannelService.java +++ b/im-center-server/src/main/java/cn/axzo/im/channel/netease/NimChannelService.java @@ -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 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 paramMap = this.buildKickChatGroup(request); + + Map 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 buildKickChatGroup(KickChatGroupRequest request) { + HashMap 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 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 tids, Integer ope) { + //构建创建群聊对象 + HashMap paramMap = this.buildChatGroupQuery(tids, ope); + + Map 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 buildChatGroupQuery(List tids, Integer ope) { + HashMap paramMap = Maps.newHashMap(); + paramMap.put("tids", JSON.toJSONString(tids)); + paramMap.put("ope", ope); + return paramMap; + } + + /** + * 转让群主 + */ + public ChangeOwnerResponse changeOwner(ChangeOwnerRequest request) { + //构建创建群聊对象 + HashMap paramMap = this.buildChangeOwner(request); + + Map 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 buildChangeOwner(ChangeOwnerRequest request) { + HashMap 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; + } + } diff --git a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChangeOwnerRequest.java b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChangeOwnerRequest.java new file mode 100644 index 0000000..3be6c2f --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChangeOwnerRequest.java @@ -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 位字符,按照群属性 invitemode(0:只有群主和管理员可以邀请他人入群,1:所有人都可以邀请)配置传入 + */ + private String owner; + /** + * 新群主帐号,accid,最大长度 32 位字符 + */ + private String newowner; + /** + * 1,群主身份转让后离开群;2,群主身份转让后成为普通成员 + */ + private Integer leave; + + /** + * 自定义扩展字段,最大长度 512 位字符 + */ + private String attach; + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChangeOwnerResponse.java b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChangeOwnerResponse.java new file mode 100644 index 0000000..4783691 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChangeOwnerResponse.java @@ -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; + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupCreateRequest.java b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupCreateRequest.java index 6b4f341..31e3770 100644 --- a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupCreateRequest.java +++ b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupCreateRequest.java @@ -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(); } diff --git a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupCreateResponse.java b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupCreateResponse.java index a24d75b..9cc9572 100644 --- a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupCreateResponse.java +++ b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupCreateResponse.java @@ -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(); } } diff --git a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupQueryResponse.java b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupQueryResponse.java new file mode 100644 index 0000000..c550461 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/ChatGroupQueryResponse.java @@ -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 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; + } +} diff --git a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/KickChatGroupRequest.java b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/KickChatGroupRequest.java new file mode 100644 index 0000000..23a7891 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/KickChatGroupRequest.java @@ -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 位字符,按照群属性 invitemode(0:只有群主和管理员可以邀请他人入群,1:所有人都可以邀请)配置传入 + */ + private String owner; + + /** + * 当移除单个成员时必填 + * 被移除的用户账号 accid,最大长度 32 位字符 + * 相对于 members,优先使用 member 参数 + */ + private String member; + /** + * 被邀请入群的用户列表,\["aaa","bbb"\](JSONArray 对应的 accid,如果解析出错会报 414),一次最多邀请 200 个成员 + */ + private String members; + /** + * 自定义扩展字段,最大长度 512 位字符 + */ + private String attach; + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/UserAddChatGroupRequest.java b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/UserAddChatGroupRequest.java index 9259a49..9196ab5 100644 --- a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/UserAddChatGroupRequest.java +++ b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/UserAddChatGroupRequest.java @@ -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 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 members, String msg) { return UserAddChatGroupRequest.builder() .tid(tid) .owner(owner) diff --git a/im-center-server/src/main/java/cn/axzo/im/config/BizResultCode.java b/im-center-server/src/main/java/cn/axzo/im/config/BizResultCode.java index 669b1fa..cdb21c6 100644 --- a/im-center-server/src/main/java/cn/axzo/im/config/BizResultCode.java +++ b/im-center-server/src/main/java/cn/axzo/im/config/BizResultCode.java @@ -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; diff --git a/im-center-server/src/main/java/cn/axzo/im/config/ChatGroupConfig.java b/im-center-server/src/main/java/cn/axzo/im/config/ChatGroupConfig.java new file mode 100644 index 0000000..adefb05 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/config/ChatGroupConfig.java @@ -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; +} diff --git a/im-center-server/src/main/java/cn/axzo/im/config/RocketMQEventConfiguration.java b/im-center-server/src/main/java/cn/axzo/im/config/RocketMQEventConfiguration.java index eb99e40..9c106d7 100644 --- a/im-center-server/src/main/java/cn/axzo/im/config/RocketMQEventConfiguration.java +++ b/im-center-server/src/main/java/cn/axzo/im/config/RocketMQEventConfiguration.java @@ -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 { + + @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 { + + @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 { + + @Autowired + private EventConsumer eventConsumer; + + @Override + public void onMessage(MessageExt message) { + log.info("ChatGroupChangeOwnerListener onMessage,message:{}", JSON.toJSONString(message)); + super.onEvent(message, eventConsumer); + } + } } diff --git a/im-center-server/src/main/java/cn/axzo/im/controller/ChatGroupController.java b/im-center-server/src/main/java/cn/axzo/im/controller/ChatGroupController.java index 2a5a5a1..93781be 100644 --- a/im-center-server/src/main/java/cn/axzo/im/controller/ChatGroupController.java +++ b/im-center-server/src/main/java/cn/axzo/im/controller/ChatGroupController.java @@ -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 chatGroupCreate(ChatGroupCreateReq req) { - return ApiResult.ok(chatGroupService.chatGroupCreate(req)); + return chatGroupService.chatGroupCreate(req); + } + + + /** + * 获取群聊 + */ + @Override + public ApiResult chatGroupQuery(@RequestBody @Validated ChatGroupQueryReq req) { + return ApiResult.ok(chatGroupService.chatGroupQuery(req)); } } diff --git a/im-center-server/src/main/java/cn/axzo/im/controller/ComplaintController.java b/im-center-server/src/main/java/cn/axzo/im/controller/ComplaintController.java index 05697b1..825d8ef 100644 --- a/im-center-server/src/main/java/cn/axzo/im/controller/ComplaintController.java +++ b/im-center-server/src/main/java/cn/axzo/im/controller/ComplaintController.java @@ -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 complaintCreate(ChatGroupCreateReq chatGroupCreateReq) { - return null; + public ApiResult complaintCreate(ComplaintCreateReq req) { + complaintService.complaintCreate(req); + return ApiResult.ok(); } } diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/BizDataMapper.java b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/BizDataMapper.java deleted file mode 100644 index ddded85..0000000 --- a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/BizDataMapper.java +++ /dev/null @@ -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 { -} \ No newline at end of file diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/BizLogMapper.java b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/BizLogMapper.java deleted file mode 100644 index dff7def..0000000 --- a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/BizLogMapper.java +++ /dev/null @@ -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 { -} diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/ChatGroupUserMapper.java b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/ChatGroupUserMapper.java new file mode 100644 index 0000000..7a3fef5 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/ChatGroupUserMapper.java @@ -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 { +} diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/ComplaintMapper.java b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/ComplaintMapper.java new file mode 100644 index 0000000..536eac2 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/ComplaintMapper.java @@ -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 { +} diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/OperateLogMapper.java b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/OperateLogMapper.java new file mode 100644 index 0000000..1e0f2e1 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/OperateLogMapper.java @@ -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 { +} diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/repository/BizDataDao.java b/im-center-server/src/main/java/cn/axzo/im/dao/repository/BizDataDao.java deleted file mode 100644 index 945c1ab..0000000 --- a/im-center-server/src/main/java/cn/axzo/im/dao/repository/BizDataDao.java +++ /dev/null @@ -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 { -} \ No newline at end of file diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/repository/BizLogDao.java b/im-center-server/src/main/java/cn/axzo/im/dao/repository/BizLogDao.java deleted file mode 100644 index 7e8bd4e..0000000 --- a/im-center-server/src/main/java/cn/axzo/im/dao/repository/BizLogDao.java +++ /dev/null @@ -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 { -} \ No newline at end of file diff --git a/im-center-server/src/main/java/cn/axzo/im/entity/BizData.java b/im-center-server/src/main/java/cn/axzo/im/entity/BizData.java deleted file mode 100644 index 7558742..0000000 --- a/im-center-server/src/main/java/cn/axzo/im/entity/BizData.java +++ /dev/null @@ -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 { - } - -} \ No newline at end of file diff --git a/im-center-server/src/main/java/cn/axzo/im/entity/BizLog.java b/im-center-server/src/main/java/cn/axzo/im/entity/BizLog.java deleted file mode 100644 index 9257147..0000000 --- a/im-center-server/src/main/java/cn/axzo/im/entity/BizLog.java +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/im-center-server/src/main/java/cn/axzo/im/entity/ChatGroup.java b/im-center-server/src/main/java/cn/axzo/im/entity/ChatGroup.java index 436e677..574f9b8 100644 --- a/im-center-server/src/main/java/cn/axzo/im/entity/ChatGroup.java +++ b/im-center-server/src/main/java/cn/axzo/im/entity/ChatGroup.java @@ -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; /** * 创建时间 diff --git a/im-center-server/src/main/java/cn/axzo/im/entity/ChatGroupUser.java b/im-center-server/src/main/java/cn/axzo/im/entity/ChatGroupUser.java new file mode 100644 index 0000000..e084477 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/entity/ChatGroupUser.java @@ -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; +} \ No newline at end of file diff --git a/im-center-server/src/main/java/cn/axzo/im/entity/Complaint.java b/im-center-server/src/main/java/cn/axzo/im/entity/Complaint.java index 561b198..9f75e3e 100644 --- a/im-center-server/src/main/java/cn/axzo/im/entity/Complaint.java +++ b/im-center-server/src/main/java/cn/axzo/im/entity/Complaint.java @@ -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; /** * 创建时间 diff --git a/im-center-server/src/main/java/cn/axzo/im/entity/ChatGroupChangeLog.java b/im-center-server/src/main/java/cn/axzo/im/entity/OperateLog.java similarity index 72% rename from im-center-server/src/main/java/cn/axzo/im/entity/ChatGroupChangeLog.java rename to im-center-server/src/main/java/cn/axzo/im/entity/OperateLog.java index 40bb8f7..4307c82 100644 --- a/im-center-server/src/main/java/cn/axzo/im/entity/ChatGroupChangeLog.java +++ b/im-center-server/src/main/java/cn/axzo/im/entity/OperateLog.java @@ -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; /** * 创建时间 diff --git a/im-center-server/src/main/java/cn/axzo/im/event/inner/EventTypeEnum.java b/im-center-server/src/main/java/cn/axzo/im/event/inner/EventTypeEnum.java index e65cd63..a56f97e 100644 --- a/im-center-server/src/main/java/cn/axzo/im/event/inner/EventTypeEnum.java +++ b/im-center-server/src/main/java/cn/axzo/im/event/inner/EventTypeEnum.java @@ -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) { diff --git a/im-center-server/src/main/java/cn/axzo/im/event/payload/ChatGroupCreatePayload.java b/im-center-server/src/main/java/cn/axzo/im/event/payload/ChatGroupCreatePayload.java index 3f71ae3..d484821 100644 --- a/im-center-server/src/main/java/cn/axzo/im/event/payload/ChatGroupCreatePayload.java +++ b/im-center-server/src/main/java/cn/axzo/im/event/payload/ChatGroupCreatePayload.java @@ -20,4 +20,7 @@ public class ChatGroupCreatePayload implements Serializable { private String owner; + private String imAccountOwner; + + private Long chatGroupId; } diff --git a/im-center-server/src/main/java/cn/axzo/im/event/payload/OrganizationalNodeUserPayload.java b/im-center-server/src/main/java/cn/axzo/im/event/payload/OrganizationalNodeUserPayload.java new file mode 100644 index 0000000..bb1b96f --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/event/payload/OrganizationalNodeUserPayload.java @@ -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 = ""; +} + diff --git a/im-center-server/src/main/java/cn/axzo/im/event/payload/SaasRoleUserRelationRemovePayload.java b/im-center-server/src/main/java/cn/axzo/im/event/payload/SaasRoleUserRelationRemovePayload.java new file mode 100644 index 0000000..9b53f02 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/event/payload/SaasRoleUserRelationRemovePayload.java @@ -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 values; + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/event/payload/SaasRoleUserRelationUpsertPayload.java b/im-center-server/src/main/java/cn/axzo/im/event/payload/SaasRoleUserRelationUpsertPayload.java new file mode 100644 index 0000000..1b099f5 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/event/payload/SaasRoleUserRelationUpsertPayload.java @@ -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 oldValues; + + private List newValues; + + public static SaasRoleUserRelation from(SaasRoleUserV2DTO saasRoleUserV2DTO) { + SaasRoleUserRelation result = new SaasRoleUserRelation(); + BeanUtils.copyProperties(saasRoleUserV2DTO, result); + result.setNaturalPersonId(saasRoleUserV2DTO.getSaasRoleUser().getPersonId()); + return result; + } + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/gateway/OrgJobApiGateway.java b/im-center-server/src/main/java/cn/axzo/im/gateway/OrgJobApiGateway.java new file mode 100644 index 0000000..38bc8c3 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/gateway/OrgJobApiGateway.java @@ -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 = RpcUtil.rpcApiResultProcessor(() -> orgJobApi.list(req), "fetchJobCodeByJobId", jobId); + BizAssertions.assertNotEmpty(orgJobRes, "根据jobId:{}获取Job为空", jobId); + return orgJobRes.get(0); + } +} diff --git a/im-center-server/src/main/java/cn/axzo/im/gateway/OrganizationalNodeApiGateway.java b/im-center-server/src/main/java/cn/axzo/im/gateway/OrganizationalNodeApiGateway.java new file mode 100644 index 0000000..01d8ca9 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/gateway/OrganizationalNodeApiGateway.java @@ -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 fetchNodesByNodeIds(List nodeIdList) { + OrganizationalNodeBatchQueryVO organizationalNodeBatchQueryVO = new OrganizationalNodeBatchQueryVO(); + organizationalNodeBatchQueryVO.setNodeIds(nodeIdList); + organizationalNodeBatchQueryVO.setContainsDeleted(false); + List nodeVOList = organizationalNodeApi.listNode(organizationalNodeBatchQueryVO).getData(); + return RpcUtil.rpcApiResultProcessor(() -> organizationalNodeApi.listNode(organizationalNodeBatchQueryVO), "fetchNodesByNodeIds", JSON.toJSONString(nodeVOList)); + } + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/gateway/OrganizationalNodeUserApiGateway.java b/im-center-server/src/main/java/cn/axzo/im/gateway/OrganizationalNodeUserApiGateway.java new file mode 100644 index 0000000..327f004 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/gateway/OrganizationalNodeUserApiGateway.java @@ -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 searchNodeUser(Long nodeId, Long workspaceId, Set 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 fetchNodeUsersByWorkspaceNodeIdJobCodes(Long workspaceId, Long nodeId,Set 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 fetchNodeUsersByWorkspaceIdJobCodes(Long workspaceId, Set jobCodes) { + OrganizationalNodeUserSearchReq searchReq = new OrganizationalNodeUserSearchReq(); + searchReq.setWorkspaceId(workspaceId); + searchReq.setJobCodes(jobCodes); + return RpcUtil.rpcApiResultProcessor(() -> organizationalNodeUserApi.list(searchReq), "fetchNodeUsersByWorkspaceIdJobCodes", JSON.toJSONString(searchReq)); + } + + public List fetchNodeUsersByWorkspaceOuIdJobCodes(Long workspaceId, Long ouId, Set jobCodes) { + OrganizationalNodeUserSearchReq searchReq = new OrganizationalNodeUserSearchReq(); + searchReq.setWorkspaceId(workspaceId); + searchReq.setOrganizationalUnitId(ouId); + searchReq.setJobCodes(jobCodes); + return RpcUtil.rpcApiResultProcessor(() -> organizationalNodeUserApi.list(searchReq), "fetchNodeUsersByWorkspaceOuIdJobCodes", JSON.toJSONString(searchReq)); + } + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/gateway/ProfilesApiGateway.java b/im-center-server/src/main/java/cn/axzo/im/gateway/ProfilesApiGateway.java index fb099a7..91da4b1 100644 --- a/im-center-server/src/main/java/cn/axzo/im/gateway/ProfilesApiGateway.java +++ b/im-center-server/src/main/java/cn/axzo/im/gateway/ProfilesApiGateway.java @@ -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 getPersonProfilesByIds(List personIds) { + return RpcUtil.rpcCommonProcessor(() -> userProfileServiceApi.getPersonProfiles(personIds), "getPersonProfilesByIds", JSON.toJSONString(personIds)); + } } diff --git a/im-center-server/src/main/java/cn/axzo/im/gateway/TyrApiGateway.java b/im-center-server/src/main/java/cn/axzo/im/gateway/TyrApiGateway.java new file mode 100644 index 0000000..9e1f30b --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/gateway/TyrApiGateway.java @@ -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 fetchSaasRoleUserByWorkspaceOuIdRoleTypes(Long workspaceId, Long ouId, Set roleTypeEnumSet) { + ArrayList 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)); + } + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/gateway/WorkspaceApiGateway.java b/im-center-server/src/main/java/cn/axzo/im/gateway/WorkspaceApiGateway.java new file mode 100644 index 0000000..4f6efc9 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/gateway/WorkspaceApiGateway.java @@ -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 result = workspaceApi.getById(workspaceId); + log.info("getWorkspaceById-result:{}", JSON.toJSONString(result)); + return result.getData(); + } catch (Exception e) { + log.error("getWorkspaceById-error", e); + throw e; + } + } + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/ChatGroupChangeOwnerEventHandler.java b/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/ChatGroupChangeOwnerEventHandler.java new file mode 100644 index 0000000..0a3d638 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/ChatGroupChangeOwnerEventHandler.java @@ -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 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 roleTypeEnumSet,SaasRoleUserRelation item, String oldImAccountOwner, ChatGroupUser chatGroupUser) { + //获取最新群主 + List 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); + } +} diff --git a/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/ChatGroupEventHandler.java b/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/ChatGroupEventHandler.java index bffb40e..bda8086 100644 --- a/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/ChatGroupEventHandler.java +++ b/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/ChatGroupEventHandler.java @@ -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 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 chatGroupOfMembers(ChatGroupCreateReq req) { + //Pair,key:管理人员集合;value:工人集合 + Pair, Set> adminWorkerSet = chatGroupService.fetchUsersByWorkspaceOuId(req.getCrowType(), req.getWorkspaceId(), req.getOuId(), this.chatGroupService.buildJobCodesByCrowType(req.getCrowType()), req.getCreator()); + Set adminSet = adminWorkerSet.getKey(); + Set workerSet = adminWorkerSet.getValue(); + + Map personProfileMap = this.buildPersonProfileMap(adminWorkerSet); + + List 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 buildPersonProfileMap(Pair, Set> adminWorkerSet) { + + Set adminSet = CollectionUtils.isNotEmpty(adminWorkerSet.getKey()) ? adminWorkerSet.getKey() : Sets.newHashSet(); + Set workerSet = CollectionUtils.isNotEmpty(adminWorkerSet.getValue()) ? adminWorkerSet.getValue() : Sets.newHashSet(); + + List adminWorkerList = Lists.newArrayList(); + adminWorkerList.addAll(Lists.newArrayList(adminSet)); + adminWorkerList.addAll(Lists.newArrayList(workerSet)); + + List 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); } diff --git a/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/OrganizationalNodeUserChangeEventHandler.java b/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/OrganizationalNodeUserChangeEventHandler.java new file mode 100644 index 0000000..c0ff823 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/OrganizationalNodeUserChangeEventHandler.java @@ -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 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); + } +} diff --git a/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/PersonProfileAvatarUpdateEventHandler.java b/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/PersonProfileAvatarUpdateEventHandler.java new file mode 100644 index 0000000..b48304c --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/PersonProfileAvatarUpdateEventHandler.java @@ -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 accountRegisters = accountService.listAccount(param); + if (CollectionUtils.isEmpty(accountRegisters)) { + log.info("未找到im账号信息,personId:{}", eventData.getId().toString()); + return; + } + //2 获取PersonProfile + CommonResponse 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); + } +} diff --git a/im-center-server/src/main/java/cn/axzo/im/job/UpdateImAccountPersonInfoJob.java b/im-center-server/src/main/java/cn/axzo/im/job/UpdateImAccountPersonInfoJob.java index 73b3edf..e9ec322 100644 --- a/im-center-server/src/main/java/cn/axzo/im/job/UpdateImAccountPersonInfoJob.java +++ b/im-center-server/src/main/java/cn/axzo/im/job/UpdateImAccountPersonInfoJob.java @@ -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 execute(String jsonStr) throws Exception { @@ -59,66 +61,99 @@ public class UpdateImAccountPersonInfoJob { } private void executeImpl() { - RateLimiter rateLimiter = RateLimiter.create(60); + Supplier> cursor = accountsCursor(); int count = 0; for (List 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 personIds = accounts.stream() - .map(AccountRegister::getAccountId) - .filter(Objects::nonNull) - .filter(NumberUtils::isCreatable) - .map(Long::parseLong) - .distinct() - .collect(toList()); - Map id2PersonProfile = BizAssertions.assertResponse( - userProfileServiceApi.getPersonProfiles(personIds)).stream() - .collect(Collectors.toMap(BasicDto::getId, i -> i)); + //2 构建PersonProfile + Map 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 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 buildPersonProfileMap(List accounts) { + List 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 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> accountsCursor() { diff --git a/im-center-server/src/main/java/cn/axzo/im/service/AccountService.java b/im-center-server/src/main/java/cn/axzo/im/service/AccountService.java index 809550d..6808fb6 100644 --- a/im-center-server/src/main/java/cn/axzo/im/service/AccountService.java +++ b/im-center-server/src/main/java/cn/axzo/im/service/AccountService.java @@ -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 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 fetchImAccountsByImAccounts(List 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 accountInfos = this.fetchImAccountsByImAccounts(Lists.newArrayList(imAccount)); + if (CollectionUtils.isEmpty(accountInfos)) { + return null; + } + return accountInfos.get(0); + } } diff --git a/im-center-server/src/main/java/cn/axzo/im/service/ChatGroupService.java b/im-center-server/src/main/java/cn/axzo/im/service/ChatGroupService.java index c511ecb..aa4b107 100644 --- a/im-center-server/src/main/java/cn/axzo/im/service/ChatGroupService.java +++ b/im-center-server/src/main/java/cn/axzo/im/service/ChatGroupService.java @@ -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 { /** * 创建群聊 */ - ChatGroupCreateResp chatGroupCreate(ChatGroupCreateReq req); + ApiResult chatGroupCreate(ChatGroupCreateReq req); + + /** + * 获取IM账号 + */ + Pair, Set> fetchUsersByWorkspaceOuId(ChatGroupCreateReq.CrowTypeEnum crowType, Long workspaceId, Long ouId, Set jobCodes, Long personId); + + /** + * 通用查询-少条件后期在补 + */ + List genericQuery(ChatGroupGenericSearchReq req); + + /** + * 通过Id查询群聊 + */ + ChatGroup getById(Long id); + + /** + * 获取群聊 + */ + ChatGroupQueryResp chatGroupQuery(ChatGroupQueryReq req); + + + /** + * 根据人群,返回jobCode + */ + Set 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 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); + } diff --git a/im-center-server/src/main/java/cn/axzo/im/service/ChatGroupUserService.java b/im-center-server/src/main/java/cn/axzo/im/service/ChatGroupUserService.java new file mode 100644 index 0000000..b8bdd84 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/service/ChatGroupUserService.java @@ -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 { + + /** + * 创建群聊成员 + */ + Boolean chatGroupUserCreate(Long chatGroupId, String tid,String accId, ChatGroupStatusEnum status, String remark, ChatGroupCreateReq.CrowTypeEnum crowType, ChatGroupUserTypeEnum type); + + /** + * 批量创建群聊成员 + */ + Boolean chatGroupUserBatchCreate(Long chatGroupId, String tid, List members, ChatGroupStatusEnum status, String remark, ChatGroupCreateReq.CrowTypeEnum crowType); + + /** + * 删除群聊成员 + */ + Boolean chatGroupUserDelete(Long chatGroupId, String accId); + + + /** + * 通用查询-少条件后期在补 + */ + List genericQuery(ChatGroupUserGenericSearchReq req); + + /** + * 通用查询数量 + */ + Integer genericQueryCount(ChatGroupUserGenericSearchReq req); + + /** + * 更改群主 + * @param chatGroupId 群聊Id + * @param oldAccId 原有账号 + * @param newAccId 新账号 + */ + void changeOwner(Long chatGroupId, String oldAccId, String newAccId); + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/service/ComplaintService.java b/im-center-server/src/main/java/cn/axzo/im/service/ComplaintService.java new file mode 100644 index 0000000..061ac27 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/service/ComplaintService.java @@ -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 { + + /** + * 创建群聊 + */ + void complaintCreate(ComplaintCreateReq req); + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/service/DingDingRobotService.java b/im-center-server/src/main/java/cn/axzo/im/service/DingDingRobotService.java new file mode 100644 index 0000000..70d3bca --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/service/DingDingRobotService.java @@ -0,0 +1,10 @@ +package cn.axzo.im.service; + +public interface DingDingRobotService { + /** + * 发钉钉消息。先默认是泰州的,后续如果不同三方对应的行为不一致,再调整 + * + * @param content + */ + void send(String content); +} diff --git a/im-center-server/src/main/java/cn/axzo/im/service/OperateLogService.java b/im-center-server/src/main/java/cn/axzo/im/service/OperateLogService.java new file mode 100644 index 0000000..68c0ae4 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/service/OperateLogService.java @@ -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 { + /** + * 创建操作日志 + */ + Boolean create(OperateLogTypeEnum typeEnum, String content); + +} diff --git a/im-center-server/src/main/java/cn/axzo/im/service/impl/ChatGroupServiceImpl.java b/im-center-server/src/main/java/cn/axzo/im/service/impl/ChatGroupServiceImpl.java index 99ec915..3d38f9e 100644 --- a/im-center-server/src/main/java/cn/axzo/im/service/impl/ChatGroupServiceImpl.java +++ b/im-center-server/src/main/java/cn/axzo/im/service/impl/ChatGroupServiceImpl.java @@ -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 implements ChatGroupService { @@ -34,27 +92,434 @@ public class ChatGroupServiceImpl extends ServiceImpl chatGroupCreate(ChatGroupCreateReq req) { + // 超管账号 + ChatGroupFetchOwnerResp resp = this.fetchAdminByWorkspaceOuId(req.getCrowType(), req.getWorkspaceId(), req.getOuId(), req.getCreator()); + req.setOwner(resp.getOwnerId()); + //创建群聊-校验 + ApiResult 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 checkChatGroupCreate(ChatGroupCreateReq req , Long teamId) { + + ChatGroup chatGroup = null; + StringBuilder desc = new StringBuilder(); + //1 是否已经创建群聊校验 + switch (req.getCrowType()) { + case WORKSPACE: + List 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 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 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> adminWorkerSet = this.fetchUsersByWorkspaceOuId(req.getCrowType(), req.getWorkspaceId(), req.getOuId(), this.buildJobCodesByCrowType(req.getCrowType()), req.getCreator()); + Set adminSet = adminWorkerSet.getKey(); + Set 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 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 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 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 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 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> fetchUsersByWorkspaceOuId(ChatGroupCreateReq.CrowTypeEnum crowType, Long workspaceId, Long ouId, Set jobCodes,Long personId) { + switch (crowType) { + case WORKSPACE: + List userListWhenWorkspace = organizationalNodeUserApiGateway.fetchNodeUsersByWorkspaceIdJobCodes(workspaceId, jobCodes); + BizAssertions.assertNotEmpty(userListWhenWorkspace, "人群是项目时,管理员为空,workspaceId:{},ouId:{}", workspaceId, ouId); + Set personListWhenWorkspace = userListWhenWorkspace.stream().map(OrganizationalNodeUserVO::getPersonId).collect(Collectors.toSet()); + return Pair.of(personListWhenWorkspace, Sets.newHashSet()); + case OU: + List userListWhenOuId = organizationalNodeUserApiGateway.fetchNodeUsersByWorkspaceOuIdJobCodes(workspaceId,ouId,jobCodes); + BizAssertions.assertNotEmpty(userListWhenOuId, "人群是单位时,管理员为空,workspaceId:{},ouId:{}", workspaceId, ouId); + Set 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 teamerList = this.fetchUsersByWorkspaceIdJobCodes(workspaceId, ouId,projectTeamUser.getId(), Sets.newHashSet("projTeamLeader", "projectTeamManager"), true); + Set 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 jobCodes) { + List projectTeamUserList = this.organizationalNodeUserApiGateway.searchNodeUser( null, workspaceId, jobCodes, personId); + BizAssertions.assertNotEmpty(projectTeamUserList, "personId:{}在workspaceId:{},找不到项目内组织用户信息"); + + List nodeIdList = projectTeamUserList.stream().map(OrganizationalNodeUserVO::getOrganizationalNodeId).collect(Collectors.toList()); + BizAssertions.assertNotEmpty(nodeIdList, "personId:{}在workspaceId:{},找不到项目内组织信息"); + + List nodeVOList = organizationalNodeApiGateway.fetchNodesByNodeIds(nodeIdList); + BizAssertions.assertNotEmpty(nodeVOList, "personId:{}在workspaceId:{},找不到项目内组织信息"); + + //只获取项目内班组,节点类型 1.部门 2.平台班组 3.小组 4.项目内班组 5.项目内小组 + List nodeVOTeamList = nodeVOList.stream().filter(item -> item.getNodeType().equals(4)).collect(Collectors.toList()); + BizAssertions.assertNotEmpty(nodeVOTeamList, "personId:{}在workspaceId:{},找不到项目内班组信息"); + + return nodeVOTeamList.get(0); + } + + private Set fetchUsersByWorkspaceIdJobCodes(Long workspaceId, Long ouId, Long nodeId,Set jobCodes, boolean isAdmin) { + List 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 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 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; } diff --git a/im-center-server/src/main/java/cn/axzo/im/service/impl/ChatGroupUserServiceImpl.java b/im-center-server/src/main/java/cn/axzo/im/service/impl/ChatGroupUserServiceImpl.java new file mode 100644 index 0000000..77645da --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/service/impl/ChatGroupUserServiceImpl.java @@ -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 + 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 members, ChatGroupStatusEnum status, String remark, ChatGroupCreateReq.CrowTypeEnum crowType) { + if (CollectionUtils.isEmpty(members)) { + return true; + } + List 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 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 genericQuery(ChatGroupUserGenericSearchReq req) { + return this.genericQueryWrapper(req).list(); + } + + @Override + public Integer genericQueryCount(ChatGroupUserGenericSearchReq req) { + return this.genericQueryWrapper(req).count(); + } +} \ No newline at end of file diff --git a/im-center-server/src/main/java/cn/axzo/im/service/impl/ComplaintServiceImpl.java b/im-center-server/src/main/java/cn/axzo/im/service/impl/ComplaintServiceImpl.java new file mode 100644 index 0000000..1c6fd96 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/service/impl/ComplaintServiceImpl.java @@ -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 + 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(); + } +} diff --git a/im-center-server/src/main/java/cn/axzo/im/service/impl/DingDingRobotServiceImpl.java b/im-center-server/src/main/java/cn/axzo/im/service/impl/DingDingRobotServiceImpl.java new file mode 100644 index 0000000..2851db1 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/service/impl/DingDingRobotServiceImpl.java @@ -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()); + } +} diff --git a/im-center-server/src/main/java/cn/axzo/im/service/impl/OperateLogServiceImpl.java b/im-center-server/src/main/java/cn/axzo/im/service/impl/OperateLogServiceImpl.java new file mode 100644 index 0000000..b4f83ac --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/service/impl/OperateLogServiceImpl.java @@ -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 + implements OperateLogService { + + + /** + * 创建操作日志 + */ + @Override + public Boolean create(OperateLogTypeEnum typeEnum, String content) { + return this.saveOrUpdate(OperateLog.builder().type(typeEnum).content(content).build()); + } +} \ No newline at end of file diff --git a/im-center-server/src/main/java/cn/axzo/im/utils/DingTalkUtil.java b/im-center-server/src/main/java/cn/axzo/im/utils/DingTalkUtil.java new file mode 100644 index 0000000..c276072 --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/utils/DingTalkUtil.java @@ -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={}×tamp={}", + 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 ""; + } + } +} diff --git a/im-center-server/src/main/java/cn/axzo/im/utils/JobCodeUtils.java b/im-center-server/src/main/java/cn/axzo/im/utils/JobCodeUtils.java new file mode 100644 index 0000000..cfc17cf --- /dev/null +++ b/im-center-server/src/main/java/cn/axzo/im/utils/JobCodeUtils.java @@ -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 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 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); + } + +}