diff --git a/im-center-api/pom.xml b/im-center-api/pom.xml
index 41578f6..22e0a22 100644
--- a/im-center-api/pom.xml
+++ b/im-center-api/pom.xml
@@ -34,6 +34,17 @@
cn.axzo.framework
axzo-common-web
+
+
+ cn.axzo.basics
+ basics-profiles-api
+
+
+
+ cn.axzo.maokai
+ maokai-api
+
+
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/feign/AccountRegisterApi.java b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/AccountRegisterApi.java
new file mode 100644
index 0000000..3d92235
--- /dev/null
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/AccountRegisterApi.java
@@ -0,0 +1,146 @@
+package cn.axzo.im.center.api.feign;
+
+import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
+import cn.axzo.framework.domain.web.result.ApiListResult;
+import cn.axzo.framework.domain.web.result.ApiPageResult;
+import cn.axzo.im.center.common.enums.AppTypeEnum;
+import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+@FeignClient(name = "im-center", url = "${axzo.service.im-center:http://im-center:8080}")
+public interface AccountRegisterApi {
+
+ @PostMapping("/api/im/account/register/page")
+ ApiPageResult page(@RequestBody PageAccountRegisterParam param);
+
+ @PostMapping("/api/im/account/register/list")
+ ApiListResult list(@RequestBody ListAccountRegisterParam param);
+
+ @Data
+ @SuperBuilder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class AccountRegisterDTO {
+
+ private Long id;
+
+ /**
+ * 账户 机器人robotId、普通用户userId
+ */
+ private String accountId;
+
+ /**
+ * 普通用户,通过appType和ouId包装
+ * 包装以后进行账户注册
+ */
+ private String accountWrapper;
+
+ /**
+ * IM账户
+ */
+ private String imAccount;
+
+ /**
+ * 终端类型
+ *
+ * @see AppTypeEnum
+ */
+ private String appType;
+
+ /**
+ * 网易云信appKey
+ */
+ private String appKey;
+
+ /**
+ * channel 服务提供商
+ */
+ private String channelProvider;
+
+ /**
+ * 账户类型:机器人、普通用户
+ */
+ private String accountType;
+
+ /**
+ * IM注册 token
+ */
+ private String token;
+
+ /**
+ * organizational_unit表的id
+ */
+ private Long ouId;
+
+ private Integer isDelete;
+
+ private Date createAt;
+
+ private Date updateAt;
+
+ private PersonProfileDto personProfile;
+
+ private OrganizationalUnitVO organizationalUnit;
+ }
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class ListAccountRegisterParam {
+
+ private List ids;
+
+ private String appType;
+
+ /**
+ * 注册用户ID唯一
+ * 普通用户personId、机器人robotId
+ */
+ private String accountId;
+
+ private Set accountIds;
+
+ /**
+ * 注册用户ID唯一
+ */
+ private String imAccount;
+
+ /**
+ * appType = AppTypeEnum.CMP时,因为网易云信无法对同一个账号做企业隔离,只能一个企业一个账号,
+ * 所以需要根据organizationalUnitId获取账号
+ */
+ private Long organizationalUnitId;
+
+ private String accountType;
+
+ private boolean needOuInfo;
+
+ private boolean needUserInfo;
+ }
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class PageAccountRegisterParam extends ListAccountRegisterParam {
+ Integer page;
+
+ Integer pageSize;
+
+ /**
+ * 排序:使用示例,createTime__DESC
+ */
+ List sort;
+ }
+}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/feign/MessageApi.java b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/MessageApi.java
index ed2ef8d..6c2a57a 100644
--- a/im-center-api/src/main/java/cn/axzo/im/center/api/feign/MessageApi.java
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/MessageApi.java
@@ -1,16 +1,22 @@
package cn.axzo.im.center.api.feign;
import cn.axzo.framework.domain.web.result.ApiResult;
+import cn.axzo.im.center.api.vo.req.AsyncSendMessageParam;
import cn.axzo.im.center.api.vo.req.CustomMessageInfo;
import cn.axzo.im.center.api.vo.req.MessageInfo;
+import cn.axzo.im.center.api.vo.req.SendCustomMessageParam;
+import cn.axzo.im.center.api.vo.req.SendMessageParam;
+import cn.axzo.im.center.api.vo.req.SendTemplateMessageParam;
import cn.axzo.im.center.api.vo.resp.MessageCustomResp;
import cn.axzo.im.center.api.vo.resp.MessageDispatchResp;
-import java.util.List;
+import cn.axzo.im.center.api.vo.resp.MessageTaskResp;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
+import java.util.List;
+
/**
* IM消息管理API
*
@@ -22,17 +28,41 @@ import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(name = "im-center", url = "${axzo.service.im-center:http://im-center:8080}")
public interface MessageApi {
/**
- * 发送消息,单条消息、批量发送消息统一入口
- * 1.该接口一次请求,接收人支持最大2000人
- * 2.网易云信一分钟支持120次调用,每次调用IM中心设置100个账户(能返回msgId最大支持100人)
- * 3.IM中心接收人有工人端和管理端账户,故当接收人最大2000人时,需要调用网易云信发送4000条消息
- * 4.按照每批次发送100条消息,需要发送40次。
- * 5.因此该接口一分钟内最大支持3次接收人为2000人的请求
+ * 发送消息时只是存储在messageTask中,通过xxlJob或者mq异步去处理
+ * 因为:1、为了提高接口响应性能。2、第三方接口有限流控制,防止被限流后阻塞业务
+ * @param sendMessageParam 发送消息请求参数
+ * @return
+ */
+ @PostMapping("/api/im/message/async/send")
+ ApiResult sendMessageAsync(@RequestBody @Validated AsyncSendMessageParam sendMessageParam);
+
+ /**
+ * 同步发送消息,不建议使用,因为第三方接口有限流,会影响接口性能,只能给最多10个用户发送
+ * @param sendMessageParam
+ * @return
+ */
+ @PostMapping("/api/im/message/send")
+ ApiResult sendMessage(@RequestBody @Validated SendMessageParam sendMessageParam);
+
+
+ /**
+ * 通过消息模板来发送消息
+ * 发送消息时只是存储在messageTask中,通过xxlJob或者mq异步去处理
+ * 因为:1、为了提高接口响应性能。2、第三方接口有限流控制,防止被限流后阻塞业务
+ * @param sendMessageParam
+ * @return
+ */
+ @PostMapping("/api/im/template-message/async/send")
+ ApiResult sendTemplateMessageAsync(@RequestBody @Validated SendTemplateMessageParam sendMessageParam);
+
+ /**
*
+ * 接口已经作废,可以使用sendTemplateMessage来替换
* @param messageInfo 发送消息请求参数
* @return 发送消息请求响应
*/
@PostMapping("api/im/message/dispatch")
+ @Deprecated
ApiResult> sendMessage(@RequestBody @Validated MessageInfo messageInfo);
/**
@@ -41,4 +71,5 @@ public interface MessageApi {
@PostMapping("api/im/custom-message/send")
ApiResult> sendCustomMessage(@RequestBody @Validated CustomMessageInfo messageInfo);
+
}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/feign/MessageHistoryApi.java b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/MessageHistoryApi.java
new file mode 100644
index 0000000..14c705f
--- /dev/null
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/MessageHistoryApi.java
@@ -0,0 +1,133 @@
+package cn.axzo.im.center.api.feign;
+
+import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
+import cn.axzo.framework.domain.web.result.ApiListResult;
+import cn.axzo.framework.domain.web.result.ApiPageResult;
+import cn.axzo.im.center.common.enums.AppTypeEnum;
+import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+@FeignClient(name = "im-center", url = "${axzo.service.im-center:http://im-center:8080}")
+public interface MessageHistoryApi {
+
+ @PostMapping("/api/im/message/history/page")
+ ApiPageResult page(@RequestBody PageMessageHistoryParam param);
+
+ @PostMapping("/api/im/message/history/list")
+ ApiListResult list(@RequestBody ListMessageHistoryParam param);
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class ListMessageHistoryParam {
+
+ private List ids;
+
+ private Long imMessageTaskId;
+
+ private Set receivePersonIds;
+
+ private Set toAccount;
+
+ private Set appTypes;
+
+ private Set statues;
+
+ private boolean needReceiveOuInfo;
+
+ private boolean needReceiveUserInfo;
+ }
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class PageMessageHistoryParam extends ListMessageHistoryParam {
+ Integer page;
+
+ Integer pageSize;
+
+ /**
+ * 排序:使用示例,createTime__DESC
+ */
+ List sort;
+ }
+
+ @Data
+ @SuperBuilder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class MessageHistoryDTO {
+
+ private Long id;
+
+ /**
+ * 上游业务请求ID
+ */
+ private String bizId;
+
+ /**
+ * 普通用户,通过appType包装
+ * 包装以后进行账户注册
+ */
+ private String messageId;
+
+ /**
+ * 发送者IM账户
+ */
+ private String fromAccount;
+
+
+ /**
+ * 发送者IM账户
+ */
+ private String toAccount;
+
+ /**
+ * 终端类型
+ *
+ * @see AppTypeEnum
+ */
+ private String appType;
+
+
+ /**
+ * channel 网易云信
+ */
+ private String channel;
+
+
+ private String messageBody;
+
+ private String result;
+
+ private Long imMessageTaskId;
+
+ private String receivePersonId;
+
+ private Long receiveOuId;
+
+ private String status;
+
+ private Integer isDelete;
+
+ private Date createAt;
+
+ private Date updateAt;
+
+ private PersonProfileDto receivePersonProfile;
+
+ private OrganizationalUnitVO receiveOrganizationalUnit;
+ }
+}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/feign/RobotInfoApi.java b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/RobotInfoApi.java
index c626324..789bfec 100644
--- a/im-center-api/src/main/java/cn/axzo/im/center/api/feign/RobotInfoApi.java
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/feign/RobotInfoApi.java
@@ -6,6 +6,13 @@ import cn.axzo.im.center.api.vo.req.RobotInfoReq;
import cn.axzo.im.center.api.vo.req.RobotPageQuery;
import cn.axzo.im.center.api.vo.req.UpdateRobotInfoReq;
import cn.axzo.im.center.api.vo.resp.RobotInfoResp;
+import cn.axzo.im.center.api.vo.resp.RobotTagResp;
+import cn.axzo.im.center.common.enums.RobotStatusEnum;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
@@ -13,6 +20,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
+import java.util.Date;
import java.util.List;
/**
@@ -59,6 +67,7 @@ public interface RobotInfoApi {
* @return 机器人列表信息
*/
@PostMapping("api/im/robot/basic/page")
+ @Deprecated
ApiPageResult queryRobotList(@RequestBody RobotPageQuery robotPageQuery);
@@ -71,5 +80,87 @@ public interface RobotInfoApi {
@GetMapping("api/im/robot/enabled/list")
ApiResult> queryRunningRobots();
+ @PostMapping("/api/im/robot-info/page")
+ ApiPageResult page(@RequestBody PageRobotInfoParam param);
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class ListRobotInfoParam {
+ private String nickNameLike;
+
+ private RobotStatusEnum status;
+
+ private List imAccounts;
+
+ private boolean needRobotTag;
+ }
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class PageRobotInfoParam extends ListRobotInfoParam {
+ Integer pageNumber;
+
+ Integer pageSize;
+
+ /**
+ * 排序:使用示例,createTime__DESC
+ */
+ List sort;
+ }
+
+ @Builder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class RobotInfoDTO {
+
+ private Long id;
+
+ /**
+ * 机器人ID
+ * 目的是用该字段进行账户注册,如果使用数据库主键,
+ * 那么注册就会出现账户重复问题
+ */
+ private String robotId;
+
+ /**
+ * 机器人昵称
+ */
+ private String nickName;
+
+ /**
+ * 机器人Tag列表
+ * 存放的是robotTag表的id
+ */
+ private List tagNameList;
+
+
+ /**
+ * 机器人头像链接
+ */
+ private String headImageUrl;
+
+ /**
+ * 机器人IM账户
+ */
+ private String imAccount;
+
+ /**
+ * 机器人状态
+ * @see cn.axzo.im.center.common.enums.RobotStatusEnum
+ */
+ private String status;
+
+ private Integer isDelete;
+
+ private Date createAt;
+
+ private Date updateAt;
+
+ private List robotTags;
+ }
}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AccountAbsentQuery.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AccountAbsentQuery.java
index 266228c..b292860 100644
--- a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AccountAbsentQuery.java
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AccountAbsentQuery.java
@@ -26,4 +26,10 @@ public class AccountAbsentQuery {
@NotNull(message = "注册用户personId不能为空")
private String personId;
+ /**
+ * appType = AppTypeEnum.CMP时,因为网易云信无法对同一个账号做企业隔离,只能一个企业一个账号,
+ * 所以需要根据ouId获取账号
+ */
+ private Long ouId;
+
}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AccountQuery.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AccountQuery.java
index 94eed8e..9cc76e6 100644
--- a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AccountQuery.java
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AccountQuery.java
@@ -36,4 +36,10 @@ public class AccountQuery {
*/
private String imAccount;
+ /**
+ * appType = AppTypeEnum.CMP时,因为网易云信无法对同一个账号做企业隔离,只能一个企业一个账号,
+ * 所以需要根据organizationalUnitId获取账号
+ */
+ private Long organizationalUnitId;
+
}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AsyncSendMessageParam.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AsyncSendMessageParam.java
new file mode 100644
index 0000000..3581b23
--- /dev/null
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/AsyncSendMessageParam.java
@@ -0,0 +1,111 @@
+package cn.axzo.im.center.api.vo.req;
+
+import cn.axzo.im.center.common.enums.AppTypeEnum;
+import com.alibaba.fastjson.JSONObject;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AsyncSendMessageParam {
+ /**
+ * 发送者IM账号
+ */
+ @NotBlank(message = "sendImAccount不能为空")
+ private String sendImAccount;
+
+ /**
+ * 消息接收用户信息
+ */
+ private List receivePersons;
+
+ /**
+ * 给全员发送
+ */
+ private boolean allPerson;
+
+ /**
+ * 全员发送时需要指定发送消息到App端
+ * 工人端、企业端、服务器
+ * CM、CMP、SYSTEM
+ *
+ * @See cn.axzo.im.center.common.enums.AppTypeEnum
+ */
+ private List appTypes;
+
+ /**
+ * 消息标题
+ */
+ @NotBlank(message = "消息标题不能为空")
+ private String msgHeader;
+
+ /**
+ * 消息内容
+ */
+ @NotBlank(message = "消息内容不能为空")
+ private String msgContent;
+
+ /**
+ * 跳转配置信息
+ */
+ private List jumpData;
+
+ /**
+ * 封面图
+ */
+ private String cardBannerUrl;
+
+ /**
+ * 消息扩展信息
+ */
+ private JSONObject ext;
+
+ /**
+ * 业务的唯一ID,用于查询发送消息的记录和结果,不验证唯一
+ */
+ private String bizId;
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class ReceivePerson {
+
+ /**
+ * 接收消息的personId
+ */
+ private String personId;
+
+ /**
+ * appType = AppTypeEnum.CMP时,因为网易云信无法对同一个账号做企业隔离,只能一个企业一个账号,
+ * 所以需要根据organizationalUnitId获取账号
+ */
+ private Long ouId;
+
+ /**
+ * 发送消息到App端
+ * 工人端、企业端、服务器
+ * CM、CMP、SYSTEM
+ *
+ * @See cn.axzo.im.center.common.enums.AppTypeEnum
+ */
+ private AppTypeEnum appType;
+
+ /**
+ * im账号,可以personId和imAccount二选一
+ */
+ private String imAccount;
+
+ /**
+ * 因为CMS端做消息跳转时需要这个字段做权限check
+ */
+ private Long workspaceId;
+ }
+}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/CustomMessageInfo.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/CustomMessageInfo.java
index 35e116c..9b2462f 100644
--- a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/CustomMessageInfo.java
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/CustomMessageInfo.java
@@ -47,4 +47,4 @@ public class CustomMessageInfo {
* 推送内容 - 业务数据,json格式
*/
private String payload;
-}
+}
\ No newline at end of file
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/MessageInfo.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/MessageInfo.java
index 7ccb6f3..fd0e568 100644
--- a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/MessageInfo.java
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/MessageInfo.java
@@ -70,4 +70,4 @@ public class MessageInfo {
* 消息扩展信息
*/
private Map extendsInfo = Maps.newHashMap();
-}
+}
\ No newline at end of file
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/RobotPageQuery.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/RobotPageQuery.java
index 7473b64..ffeb1e6 100644
--- a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/RobotPageQuery.java
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/RobotPageQuery.java
@@ -3,6 +3,8 @@ package cn.axzo.im.center.api.vo.req;
import cn.axzo.basics.common.page.PageRequest;
import lombok.Data;
+import java.util.List;
+
/**
* 机器人信息
*
@@ -34,5 +36,8 @@ public class RobotPageQuery extends PageRequest {
*/
private String imAccount;
-
+ /**
+ * todo 待优化替换成cn.axzo.im.center.api.feign.RobotInfoApi#page
+ */
+ private String msgTemplateCode;
}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/SendCustomMessageParam.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/SendCustomMessageParam.java
new file mode 100644
index 0000000..0a1f5cb
--- /dev/null
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/SendCustomMessageParam.java
@@ -0,0 +1,73 @@
+package cn.axzo.im.center.api.vo.req;
+
+import cn.axzo.im.center.common.enums.AppTypeEnum;
+import cn.axzo.im.center.common.enums.BizTypeEnum;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * @author syl
+ * @date 2023/12/21
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SendCustomMessageParam {
+
+ /**
+ * 消息接收用户信息
+ */
+ @NotEmpty(message = "消息接收用户信息不能为空")
+ private List receivePersons;
+
+ /**
+ * 业务类型
+ */
+ @NotNull(message = "业务类型不能为空")
+ private BizTypeEnum bizType;
+
+ /**
+ * 推送内容 - 业务数据,json格式
+ */
+ private String payload;
+
+ /**
+ * 业务的唯一ID,用于查询发送消息的记录和结果
+ */
+ private String bizId;
+
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class ReceivePerson {
+
+ /**
+ * 接收消息的personId
+ */
+ private String personId;
+
+ /**
+ * appType = AppTypeEnum.CMP时,因为网易云信无法对同一个账号做企业隔离,只能一个企业一个账号,
+ * 所以需要根据organizationalUnitId获取账号
+ */
+ private Long ouId;
+
+ /**
+ * 发送消息到App端
+ * 工人端、企业端、服务器
+ * CM、CMP、SYSTEM
+ *
+ * @See cn.axzo.im.center.common.enums.AppTypeEnum
+ */
+ private AppTypeEnum appType;
+ }
+}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/SendMessageParam.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/SendMessageParam.java
new file mode 100644
index 0000000..1b5e347
--- /dev/null
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/SendMessageParam.java
@@ -0,0 +1,111 @@
+package cn.axzo.im.center.api.vo.req;
+
+import cn.axzo.im.center.common.enums.AppTypeEnum;
+import com.alibaba.fastjson.JSONObject;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * IM消息信息
+ *
+ * @author zuoqinbo
+ * @version V1.0
+ * @date 2023/10/9 16:01
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SendMessageParam {
+
+ /**
+ * 发送者IM账号
+ */
+ @NotBlank(message = "sendImAccount不能为空")
+ private String sendImAccount;
+
+ /**
+ * 消息接收用户信息
+ */
+ @NotEmpty(message = "消息接收用户信息不能为空")
+ @Valid
+ private List receivePersons;
+
+ /**
+ * 消息标题
+ */
+ @NotBlank(message = "消息标题不能为空")
+ private String msgHeader;
+
+ /**
+ * 消息内容
+ */
+ @NotBlank(message = "消息内容不能为空")
+ private String msgContent;
+
+ /**
+ * 跳转配置信息
+ */
+ private List jumpData;
+
+ /**
+ * 封面图
+ */
+ private String cardBannerUrl;
+
+ /**
+ * 消息扩展信息
+ */
+ private JSONObject ext;
+
+ /**
+ * 业务的唯一ID,用于查询发送消息的记录和结果,不验证唯一
+ */
+ private String bizId;
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class JumpData {
+
+ private JumpPlatform platform;
+
+ private String url;
+ }
+
+ @Getter
+ @AllArgsConstructor
+ public enum JumpPlatform {
+ PC(null, "WEB"),
+ CM_IOS(AppTypeEnum.CM, "IOS"),
+ CM_ANDROID(AppTypeEnum.CM, "ANDROID"),
+ CMP_IOS(AppTypeEnum.CMP, "IOS"),
+ CMP_ANDROID(AppTypeEnum.CMP, "ANDROID")
+ ;
+
+ private AppTypeEnum appType;
+ private String oldPlatform;
+ }
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class ReceivePerson {
+ /**
+ * im账号,可以personId和imAccount二选一
+ */
+ @NotBlank(message = "imAccount不能为空")
+ private String imAccount;
+ }
+}
+
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/SendTemplateMessageParam.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/SendTemplateMessageParam.java
new file mode 100644
index 0000000..8bd1406
--- /dev/null
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/SendTemplateMessageParam.java
@@ -0,0 +1,92 @@
+package cn.axzo.im.center.api.vo.req;
+
+import cn.axzo.im.center.common.enums.AppTypeEnum;
+import com.alibaba.fastjson.JSONObject;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SendTemplateMessageParam {
+
+ /**
+ * 消息接收用户信息
+ */
+ @NotEmpty(message = "消息接收用户信息不能为空")
+ @Valid
+ private List receivePersons;
+
+ /**
+ * 消息标题
+ */
+ @NotBlank(message = "消息标题不能为空")
+ private String msgHeader;
+
+ /**
+ * 消息内容
+ */
+ @NotBlank(message = "消息内容不能为空")
+ private String msgContent;
+
+ /**
+ * 消息模板ID
+ */
+ @NotBlank(message = "消息模板ID不能为空")
+ private String msgTemplateId;
+
+ /**
+ * 消息模板内容
+ */
+ @NotBlank(message = "消息模板内容不能为空")
+ private String msgTemplateContent;
+
+ /**
+ * 消息扩展信息
+ */
+ private JSONObject ext;
+
+ /**
+ * 业务的唯一ID,用于查询发送消息的记录和结果,不验证唯一
+ */
+ private String bizId;
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class ReceivePerson {
+
+ /**
+ * 接收消息的personId
+ */
+ @NotBlank(message = "personId不能为空")
+ private String personId;
+
+ /**
+ * appType = AppTypeEnum.CMP时,因为网易云信无法对同一个账号做企业隔离,只能一个企业一个账号,
+ * 所以需要根据organizationalUnitId获取账号
+ */
+ private Long ouId;
+
+ /**
+ * 发送消息到App端
+ * 工人端、企业端、服务器
+ * CM、CMP、SYSTEM
+ *
+ * @See cn.axzo.im.center.common.enums.AppTypeEnum
+ */
+ @NotNull(message = "appType不能为空")
+ private AppTypeEnum appType;
+ }
+}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/UserAccountReq.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/UserAccountReq.java
index 43ce96c..9916516 100644
--- a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/UserAccountReq.java
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/req/UserAccountReq.java
@@ -51,4 +51,9 @@ public class UserAccountReq {
*/
private Map attachments;
+ /**
+ * appType = AppTypeEnum.CMP时,因为网易云信无法对同一个账号做企业隔离,只能一个企业一个账号,
+ * 所以需要根据organizationalUnitId获取账号
+ */
+ private Long organizationalUnitId;
}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/MessageTaskResp.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/MessageTaskResp.java
new file mode 100644
index 0000000..c73fc87
--- /dev/null
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/MessageTaskResp.java
@@ -0,0 +1,56 @@
+package cn.axzo.im.center.api.vo.resp;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MessageTaskResp {
+
+ private Long id;
+
+ /**
+ * 业务请求时可以带的排查问题的id
+ */
+ private String bizId;
+
+ /**
+ * IM消息发送personId
+ */
+ private String sendPersonId;
+
+ /**
+ * IM消息接收人person信息
+ */
+ private JSONArray receivePersons;
+
+ private String status;
+
+ private String title;
+
+ private String content;
+
+ private JSONObject bizData;
+
+ private JSONObject ext;
+
+ private Date planStartTime;
+
+ private Date startedTime;
+
+ private Date finishedTime;
+
+ private Integer isDelete;
+
+ private Date createAt;
+
+ private Date updateAt;
+}
diff --git a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/UserAccountResp.java b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/UserAccountResp.java
index 6032d81..00cd970 100644
--- a/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/UserAccountResp.java
+++ b/im-center-api/src/main/java/cn/axzo/im/center/api/vo/resp/UserAccountResp.java
@@ -43,5 +43,9 @@ public class UserAccountResp {
*/
private String appType;
-
+ /**
+ * appType = AppTypeEnum.CMP时,因为网易云信无法对同一个账号做企业隔离,只能一个企业一个账号,
+ * 所以需要根据organizationalUnitId获取账号
+ */
+ private Long ouId;
}
diff --git a/im-center-server/pom.xml b/im-center-server/pom.xml
index 283ac05..1861b19 100644
--- a/im-center-server/pom.xml
+++ b/im-center-server/pom.xml
@@ -98,6 +98,11 @@
axzo-common-rocketmq
+
+ cn.axzo.maokai
+ maokai-api
+
+
org.springframework.boot
spring-boot-starter-test
@@ -107,6 +112,11 @@
cn.axzo.im.center
im-center-api
+
+
+ cn.axzo.tyr
+ tyr-api
+
diff --git a/im-center-server/src/main/java/cn/axzo/im/Application.java b/im-center-server/src/main/java/cn/axzo/im/Application.java
index 2809fad..0f8f986 100644
--- a/im-center-server/src/main/java/cn/axzo/im/Application.java
+++ b/im-center-server/src/main/java/cn/axzo/im/Application.java
@@ -1,6 +1,7 @@
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;
@@ -8,6 +9,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
@Slf4j
@@ -15,8 +17,10 @@ import org.springframework.core.env.Environment;
@EnableFeignClients(basePackages = {"cn.axzo"})
@MapperScan(value = {"cn.axzo.im.dao.mapper"})
@EnableDiscoveryClient
+@Import(RocketMQEventConfiguration.class)
public class Application {
public static void main(String[] args) {
+
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
Environment env = run.getEnvironment();
log.info(
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 e97d2b9..c9b1a71 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
@@ -54,7 +54,7 @@ public class NimChannelService implements IMChannelProvider {
private static final String NIM_MESSAGE_ATTACH_URL = "https://api.netease.im/nimserver/msg/sendAttachMsg.action";
- private static final int SUCCESS_CODE = 200;
+ public static final int SUCCESS_CODE = 200;
private static final int NIM_ACCOUNT_ALREADY_REGISTER = 414;
diff --git a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/MessageBatchDispatchResponse.java b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/MessageBatchDispatchResponse.java
index 2e9c3ec..2734a26 100644
--- a/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/MessageBatchDispatchResponse.java
+++ b/im-center-server/src/main/java/cn/axzo/im/channel/netease/dto/MessageBatchDispatchResponse.java
@@ -3,10 +3,12 @@ package cn.axzo.im.channel.netease.dto;
import lombok.Builder;
import lombok.Data;
-import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
+import static cn.axzo.im.channel.netease.NimChannelService.SUCCESS_CODE;
+
/**
* 批量发送消息返回响应
* 示例:
@@ -37,4 +39,7 @@ public class MessageBatchDispatchResponse {
private String desc;
+ public boolean isSuccess() {
+ return Objects.equals(this.getCode(), SUCCESS_CODE);
+ }
}
diff --git a/im-center-server/src/main/java/cn/axzo/im/config/BaseListTypeHandler.java b/im-center-server/src/main/java/cn/axzo/im/config/BaseListTypeHandler.java
new file mode 100644
index 0000000..531c596
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/config/BaseListTypeHandler.java
@@ -0,0 +1,51 @@
+package cn.axzo.im.config;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+@MappedTypes({List.class})
+@MappedJdbcTypes(JdbcType.VARCHAR)
+public abstract class BaseListTypeHandler extends BaseTypeHandler> {
+
+ private Class type = getGenericType();
+
+ @Override
+ public void setNonNullParameter(PreparedStatement preparedStatement, int i,
+ List list, JdbcType jdbcType) throws SQLException {
+ preparedStatement.setString(i, JSONArray.toJSONString(list, SerializerFeature.WriteMapNullValue,
+ SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteNullStringAsEmpty));
+ }
+
+ @Override
+ public List getNullableResult(ResultSet resultSet, String s) throws SQLException {
+ return JSONArray.parseArray(resultSet.getString(s), type);
+ }
+
+ @Override
+ public List getNullableResult(ResultSet resultSet, int i) throws SQLException {
+ return JSONArray.parseArray(resultSet.getString(i), type);
+ }
+
+ @Override
+ public List getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
+ return JSONArray.parseArray(callableStatement.getString(i), type);
+ }
+
+ private Class getGenericType() {
+ Type t = getClass().getGenericSuperclass();
+ Type[] params = ((ParameterizedType) t).getActualTypeArguments();
+ return (Class) params[0];
+ }
+}
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
new file mode 100644
index 0000000..669b1fa
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/config/BizResultCode.java
@@ -0,0 +1,22 @@
+package cn.axzo.im.config;
+
+import cn.axzo.pokonyan.exception.ResultCode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum BizResultCode implements ResultCode {
+
+ SEND_IM_ACCOUNT_NOT_FOUND("100", "发送者IM账号错误"),
+ SEND_IM_ACCOUNT_MAX("101", "同步接口只支持最多10个IM账号,请选择异步接口发送"),
+ SEND_PERSSON_ERROR("102", "接收人信息和全部接收人不能同时都为空"),
+ ALL_PERSSON_TYPE_NOT_EMPTY("103", "全员发送时,接收端不能为空"),
+ ACQUIRE_RATE_LIMITER_FAIL("104", "获取滑动窗口令牌失败"),
+ MESSAGE_TASK_STATUS_ERROR("105", "更新消息任务失败,状态异常"),
+ MESSAGE_TASK_NOT_FOUND("106", "消息任务不存在"),;
+
+
+ private String errorCode;
+ private String errorMessage;
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/config/GlobalConfig.java b/im-center-server/src/main/java/cn/axzo/im/config/GlobalConfig.java
index e3cc97d..86ff928 100644
--- a/im-center-server/src/main/java/cn/axzo/im/config/GlobalConfig.java
+++ b/im-center-server/src/main/java/cn/axzo/im/config/GlobalConfig.java
@@ -1,5 +1,9 @@
package cn.axzo.im.config;
+import cn.axzo.pokonyan.client.RateLimiterClient;
+import cn.axzo.pokonyan.client.impl.RateLimiterClientImpl;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -9,7 +13,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import static cn.axzo.im.config.GlobalConfig.FeignClientConstant.*;
+import static cn.axzo.im.config.GlobalConfig.FeignClientConstant.MSG_CENTER;
/**
* 全局配置
@@ -45,4 +49,10 @@ public class GlobalConfig {
executor.prestartCoreThread();
return executor;
}
+
+ @Bean
+ public RateLimiterClient rateLimiterClient(RedissonClient redissonClient) {
+ return RateLimiterClientImpl.builder().redissonClient(redissonClient).build();
+ }
+
}
diff --git a/im-center-server/src/main/java/cn/axzo/im/config/MqProducer.java b/im-center-server/src/main/java/cn/axzo/im/config/MqProducer.java
new file mode 100644
index 0000000..5cb5bb1
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/config/MqProducer.java
@@ -0,0 +1,40 @@
+package cn.axzo.im.config;
+
+import cn.axzo.framework.rocketmq.Event;
+import cn.axzo.framework.rocketmq.EventProducer;
+import com.alibaba.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
+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.Component;
+
+import java.util.List;
+
+/**
+ * @author yanglin
+ */
+@Slf4j
+@Component
+@RefreshScope
+public class MqProducer {
+
+ @Autowired
+ private EventProducer> eventProducer;
+
+ @Value("${sendMq}")
+ private Boolean sendMq;
+
+ public void send(Event event){
+ log.info(JSON.toJSONString(event));
+ if(sendMq != null && !sendMq){
+ return;
+ }
+ //生产消息
+ eventProducer.send(event);
+ }
+
+ public void sendBatch(List events){
+ events.forEach(this::send);
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..8363a58
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/config/RocketMQEventConfiguration.java
@@ -0,0 +1,90 @@
+package cn.axzo.im.config;
+
+import cn.axzo.framework.rocketmq.BaseListener;
+import cn.axzo.framework.rocketmq.DefaultEventConsumer;
+import cn.axzo.framework.rocketmq.EventConsumer;
+import cn.axzo.framework.rocketmq.EventHandlerRepository;
+import cn.axzo.framework.rocketmq.EventProducer;
+import cn.axzo.framework.rocketmq.RocketMQEventProducer;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.common.message.MessageExt;
+import org.apache.rocketmq.spring.annotation.ConsumeMode;
+import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.core.RocketMQListener;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+import java.util.function.Consumer;
+
+/**
+ * @Author: liyong.tian
+ * @Date: 2023/7/25 14:43
+ * @Description:
+ */
+@Slf4j
+public class RocketMQEventConfiguration {
+
+ @Value("${spring.application.name}")
+ private String appName;
+
+ @Value("${topic}")
+ private String topic;
+
+ @Bean
+ public RocketMQTemplate ser(){
+ return new RocketMQTemplate();
+ }
+ @Bean
+ EventProducer eventProducer(RocketMQTemplate rocketMQTemplate) {
+ return new RocketMQEventProducer(rocketMQTemplate,
+ "im-center",
+ appName,
+ EventProducer.Context.builder()
+ .meta(RocketMQEventProducer.RocketMQMessageMeta.builder()
+ .topic(topic)
+ .build())
+ .build(),
+ null
+ );
+ }
+
+ @Bean
+ EventConsumer eventConsumer(EventHandlerRepository eventHandlerRepository) {
+ Consumer callback = (eventWrapper) -> {
+ if (eventWrapper.isHandled()) {
+ // 只收集被App真正消费的消息.
+ //String topic = (String) eventWrapper.getExt().get(EVENT_TOPIC_KEY);
+
+ }
+ };
+ return new DefaultEventConsumer(appName, eventHandlerRepository, callback);
+ }
+
+ @Slf4j
+ @Component
+ @RocketMQMessageListener(topic = "topic_im_center_${spring.profiles.active}",
+ consumerGroup = "GID_topic_im_center_${spring.application.name}_${spring.profiles.active}",
+ consumeMode = ConsumeMode.ORDERLY,
+ nameServer = "${rocketmq.name-server}"
+ )
+ public static class DefaultListener extends BaseListener implements RocketMQListener {
+
+ @Autowired
+ private EventConsumer eventConsumer;
+
+ @Override
+ public void onMessage(MessageExt message) {
+ super.onEvent(message, eventConsumer);
+ }
+ }
+
+ @Bean
+ EventHandlerRepository eventHandlerRepository() {
+ return new EventHandlerRepository((ex, logText) -> {
+ log.warn("MQ, handle warning {}", logText, ex);
+ });
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/controller/AccountRegisterController.java b/im-center-server/src/main/java/cn/axzo/im/controller/AccountRegisterController.java
new file mode 100644
index 0000000..068e7d4
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/controller/AccountRegisterController.java
@@ -0,0 +1,53 @@
+package cn.axzo.im.controller;
+
+import cn.axzo.framework.domain.web.result.ApiListResult;
+import cn.axzo.framework.domain.web.result.ApiPageResult;
+import cn.axzo.im.center.api.feign.AccountRegisterApi;
+import cn.axzo.im.service.AccountRegisterService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+public class AccountRegisterController implements AccountRegisterApi {
+
+ @Autowired
+ private AccountRegisterService accountRegisterService;
+
+ @Override
+ public ApiPageResult page(PageAccountRegisterParam param) {
+ AccountRegisterService.PageAccountRegisterParam pageAccountRegisterParam = AccountRegisterService.PageAccountRegisterParam.builder().build();
+ BeanUtils.copyProperties(param, pageAccountRegisterParam);
+
+ Page page = accountRegisterService.page(pageAccountRegisterParam);
+
+ return ApiPageResult.ok(page.convert(record -> {
+ AccountRegisterDTO accountRegisterDTO = AccountRegisterDTO.builder().build();
+ BeanUtils.copyProperties(record, accountRegisterDTO);
+ return accountRegisterDTO;
+ }));
+ }
+
+ @Override
+ public ApiListResult list(ListAccountRegisterParam param) {
+ AccountRegisterService.ListAccountRegisterParam listAccountRegisterParam = AccountRegisterService.ListAccountRegisterParam.builder().build();
+ BeanUtils.copyProperties(param, listAccountRegisterParam);
+
+ List list = accountRegisterService.list(listAccountRegisterParam);
+ return ApiListResult.ok(list.stream()
+ .map(e -> {
+ AccountRegisterDTO accountRegisterDTO = AccountRegisterDTO.builder().build();
+ BeanUtils.copyProperties(e, accountRegisterDTO);
+ return accountRegisterDTO;
+ })
+ .collect(Collectors.toList()));
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/controller/MessageController.java b/im-center-server/src/main/java/cn/axzo/im/controller/MessageController.java
index d629cec..2115fe8 100644
--- a/im-center-server/src/main/java/cn/axzo/im/controller/MessageController.java
+++ b/im-center-server/src/main/java/cn/axzo/im/controller/MessageController.java
@@ -1,22 +1,52 @@
package cn.axzo.im.controller;
+import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.feign.MessageApi;
+import cn.axzo.im.center.api.vo.req.AccountQuery;
+import cn.axzo.im.center.api.vo.req.AsyncSendMessageParam;
import cn.axzo.im.center.api.vo.req.CustomMessageInfo;
import cn.axzo.im.center.api.vo.req.MessageInfo;
+import cn.axzo.im.center.api.vo.req.SendMessageParam;
+import cn.axzo.im.center.api.vo.req.SendTemplateMessageParam;
import cn.axzo.im.center.api.vo.resp.MessageCustomResp;
import cn.axzo.im.center.api.vo.resp.MessageDispatchResp;
+import cn.axzo.im.center.api.vo.resp.MessageTaskResp;
+import cn.axzo.im.center.api.vo.resp.UserAccountResp;
+import cn.axzo.im.center.common.enums.AppTypeEnum;
+import cn.axzo.im.entity.MessageHistory;
+import cn.axzo.im.entity.MessageTask;
+import cn.axzo.im.service.AccountRegisterService;
+import cn.axzo.im.service.AccountService;
+import cn.axzo.im.service.MessageHistoryService;
import cn.axzo.im.service.MessageService;
+import cn.axzo.im.service.MessageTaskService;
+import cn.axzo.im.service.RobotMsgTemplateService;
+import cn.axzo.pokonyan.exception.Aassert;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.collect.Sets;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
-import java.util.List;
-import javax.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
+import java.util.Date;
+import java.util.List;
+
+import static cn.axzo.im.config.BizResultCode.ALL_PERSSON_TYPE_NOT_EMPTY;
+import static cn.axzo.im.config.BizResultCode.SEND_IM_ACCOUNT_MAX;
+import static cn.axzo.im.config.BizResultCode.SEND_IM_ACCOUNT_NOT_FOUND;
+import static cn.axzo.im.config.BizResultCode.SEND_PERSSON_ERROR;
+
/**
* IM消息派发相关
*
@@ -29,13 +59,25 @@ import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
public class MessageController implements MessageApi {
- @Resource
+
+ @Autowired
+ private MessageTaskService messageTaskService;
+ @Autowired
+ private AccountService accountService;
+ @Autowired
+ private RobotMsgTemplateService robotMsgTemplateService;
+ @Autowired
private MessageService messageService;
+ @Autowired
+ private AccountRegisterService accountRegisterService;
+ @Autowired
+ private MessageHistoryService messageHistoryService;
+
@Override
public ApiResult> sendMessage(MessageInfo messageInfo) {
- List messageRespList = messageService.sendMessage(messageInfo);
- return ApiResult.ok(messageRespList);
+// List messageRespList = messageService.sendMessage(messageInfo);
+ return ApiResult.ok(null);
}
@Override
@@ -49,4 +91,169 @@ public class MessageController implements MessageApi {
public ApiResult handleRequestNotPermitted() {
return ApiResult.err("服务器资源繁忙,请求被拒绝!");
}
+
+
+ /**
+ * 发送消息时只是存储在messageTask中,通过xxlJob或者mq异步去处理
+ * 因为:1、为了提高接口响应性能。2、第三方接口有限流控制,防止被限流后阻塞业务
+ * @param sendMessageParam 发送消息请求参数
+ * @return
+ */
+ @Override
+ public ApiResult sendMessageAsync(AsyncSendMessageParam sendMessageParam) {
+ check(sendMessageParam);
+ MessageTask messageTask = messageTaskService.create(toMessageTask(sendMessageParam));
+ return ApiResult.ok(toMessageTaskResp(messageTask));
+ }
+
+ @Override
+ public ApiResult sendMessage(SendMessageParam sendMessageParam) {
+ check(sendMessageParam);
+ MessageTask messageTask = messageTaskService.create(toMessageTask(sendMessageParam));
+
+ messageTaskService.createMessageHistory(messageTask);
+ List messageHistories = messageHistoryService.list(MessageHistoryService.ListMessageHistoryParam.builder()
+ .imMessageTaskId(messageTask.getId())
+ .statues(Sets.newHashSet(MessageHistory.Status.PENDING.name()))
+ .build());
+ if (!CollectionUtils.isEmpty(messageHistories)) {
+ messageHistoryService.sendMessage(messageHistories);
+ }
+ return ApiResult.ok(toMessageTaskResp(messageTask));
+ }
+
+ @Override
+ public ApiResult sendTemplateMessageAsync(SendTemplateMessageParam sendMessageParam) {
+ String sendImAccount = check(sendMessageParam);
+ MessageTask messageTask = messageTaskService.create(toMessageTask(sendMessageParam, sendImAccount));
+ return ApiResult.ok(toMessageTaskResp(messageTask));
+ }
+
+ private void check(SendMessageParam sendMessageParam) {
+ List accountRegisters = accountRegisterService.list(AccountRegisterService.ListAccountRegisterParam.builder()
+ .imAccount(sendMessageParam.getSendImAccount())
+ .build());
+
+ Aassert.checkNotEmpty(accountRegisters, SEND_IM_ACCOUNT_NOT_FOUND);
+
+ Aassert.check(sendMessageParam.getReceivePersons().size() <= 10, SEND_IM_ACCOUNT_MAX);
+ }
+
+ private void check(AsyncSendMessageParam sendMessageParam) {
+ List accountRegisters = accountRegisterService.list(AccountRegisterService.ListAccountRegisterParam.builder()
+ .imAccount(sendMessageParam.getSendImAccount())
+ .build());
+
+ Aassert.checkNotEmpty(accountRegisters, SEND_IM_ACCOUNT_NOT_FOUND);
+
+ if (CollectionUtils.isEmpty(sendMessageParam.getReceivePersons())
+ && BooleanUtils.isNotTrue(sendMessageParam.isAllPerson())) {
+ throw SEND_PERSSON_ERROR.toException();
+ }
+
+ if (BooleanUtils.isTrue(sendMessageParam.isAllPerson())) {
+ Aassert.checkNotEmpty(sendMessageParam.getAppTypes(), ALL_PERSSON_TYPE_NOT_EMPTY);
+ }
+ }
+
+ private String check(SendTemplateMessageParam sendMessageParam) {
+ List robotIdList = robotMsgTemplateService.queryRobotIdByTemplate(sendMessageParam.getMsgTemplateId());
+ if (CollectionUtils.isEmpty(robotIdList)) {
+ throw new ServiceException("消息模板ID[" + sendMessageParam.getMsgTemplateId() + "],还未维护机器人账户!");
+ }
+ if (CollectionUtils.size(robotIdList) > 1) {
+ throw new ServiceException("消息模板ID[" + sendMessageParam.getMsgTemplateId() + "],关联了多个机器人!");
+ }
+ AccountQuery accountQuery = new AccountQuery();
+ String robotId = robotIdList.get(0);
+ accountQuery.setAccountId(robotId);
+ accountQuery.setAppType(AppTypeEnum.SYSTEM.getCode());
+ List robotImAccountList = accountService.queryAccountInfo(accountQuery);
+ if (CollectionUtils.isEmpty(robotImAccountList)) {
+ throw new ServiceException("消息模板ID[" + sendMessageParam.getMsgTemplateId() + "],机器人ID[" + robotId + "]," +
+ "未查询到机器人IM账户注册信息!");
+ }
+ if (CollectionUtils.isNotEmpty(robotImAccountList) && robotImAccountList.size() > 1) {
+ throw new ServiceException("消息模板ID[" + sendMessageParam.getMsgTemplateId() + "],机器人ID[" + robotId + "],存在多个机器人IM账户!");
+ }
+ String robotImAccount = robotImAccountList.get(0).getImAccount();
+ if (StringUtils.isBlank(robotImAccount)) {
+ throw new ServiceException("消息模板ID[" + sendMessageParam.getMsgTemplateId() + "],机器人ID[" + robotId + "],还未生成IM账户!");
+ }
+
+ return robotImAccount;
+ }
+
+ public MessageTaskResp toMessageTaskResp(MessageTask messageTask) {
+ MessageTaskResp messageTaskResp = MessageTaskResp.builder().build();
+ BeanUtils.copyProperties(messageTask, messageTaskResp);
+ return messageTaskResp;
+ }
+
+ private MessageTask toMessageTask(AsyncSendMessageParam sendMessageParam) {
+
+ MessageTask.BizData bizData = MessageTask.BizData.builder()
+ .jumpData(sendMessageParam.getJumpData())
+ // 全员发送是不常用的场景,不应该由业务处理,所以把配置放在bizData里面
+ .allPerson(sendMessageParam.isAllPerson())
+ .appTypes(sendMessageParam.getAppTypes())
+ .build();
+ Date now = new Date();
+ return MessageTask.builder()
+ .bizId(sendMessageParam.getBizId())
+ .sendImAccount(sendMessageParam.getSendImAccount())
+ .receivePersons(JSONArray.parseArray(JSONObject.toJSONString(sendMessageParam.getReceivePersons()), MessageTask.ReceivePerson.class))
+ .status(MessageTask.Status.PENDING)
+ .title(sendMessageParam.getMsgHeader())
+ .content(sendMessageParam.getMsgContent())
+ .bizData(bizData)
+ .ext(sendMessageParam.getExt())
+ .planStartTime(now)
+ .createAt(now)
+ .cardBannerUrl(sendMessageParam.getCardBannerUrl())
+ .build();
+ }
+
+ private MessageTask toMessageTask(SendMessageParam sendMessageParam) {
+
+ MessageTask.BizData bizData = MessageTask.BizData.builder()
+ .jumpData(sendMessageParam.getJumpData())
+ .build();
+ Date now = new Date();
+ return MessageTask.builder()
+ .bizId(sendMessageParam.getBizId())
+ .sendImAccount(sendMessageParam.getSendImAccount())
+ .receivePersons(JSONArray.parseArray(JSONObject.toJSONString(sendMessageParam.getReceivePersons()), MessageTask.ReceivePerson.class))
+ .status(MessageTask.Status.PENDING)
+ .title(sendMessageParam.getMsgHeader())
+ .content(sendMessageParam.getMsgContent())
+ .bizData(bizData)
+ .ext(sendMessageParam.getExt())
+ .planStartTime(now)
+ .createAt(now)
+ .cardBannerUrl(sendMessageParam.getCardBannerUrl())
+ .build();
+ }
+
+ private MessageTask toMessageTask(SendTemplateMessageParam sendMessageParam,
+ String sendImAccount) {
+
+ MessageTask.BizData bizData = MessageTask.BizData.builder()
+ .msgTemplateContent(sendMessageParam.getMsgTemplateContent())
+ .msgTemplateId(sendMessageParam.getMsgTemplateId())
+ .build();
+ Date now = new Date();
+ return MessageTask.builder()
+ .bizId(sendMessageParam.getBizId())
+ .sendImAccount(sendImAccount)
+ .receivePersons(JSONArray.parseArray(JSONObject.toJSONString(sendMessageParam.getReceivePersons()), MessageTask.ReceivePerson.class))
+ .status(MessageTask.Status.PENDING)
+ .title(sendMessageParam.getMsgHeader())
+ .content(sendMessageParam.getMsgContent())
+ .bizData(bizData)
+ .ext(sendMessageParam.getExt())
+ .planStartTime(now)
+ .createAt(now)
+ .build();
+ }
}
diff --git a/im-center-server/src/main/java/cn/axzo/im/controller/MessageHistoryController.java b/im-center-server/src/main/java/cn/axzo/im/controller/MessageHistoryController.java
new file mode 100644
index 0000000..2fb6993
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/controller/MessageHistoryController.java
@@ -0,0 +1,55 @@
+package cn.axzo.im.controller;
+
+
+import cn.axzo.framework.domain.web.result.ApiListResult;
+import cn.axzo.framework.domain.web.result.ApiPageResult;
+import cn.axzo.im.center.api.feign.MessageHistoryApi;
+import cn.axzo.im.service.MessageHistoryService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+public class MessageHistoryController implements MessageHistoryApi {
+
+ @Autowired
+ private MessageHistoryService messageHistoryService;
+
+ @Override
+ public ApiPageResult page(PageMessageHistoryParam param) {
+ MessageHistoryService.PageMessageHistoryParam pageMessageHistoryParam = MessageHistoryService.PageMessageHistoryParam.builder().build();
+ BeanUtils.copyProperties(param, pageMessageHistoryParam);
+
+ Page page = messageHistoryService.page(pageMessageHistoryParam);
+
+ return ApiPageResult.ok(page.convert(record -> {
+ MessageHistoryDTO messageHistoryDTO = MessageHistoryDTO.builder().build();
+ BeanUtils.copyProperties(record, messageHistoryDTO);
+ messageHistoryDTO.setStatus(record.getStatus().name());
+ return messageHistoryDTO;
+ }));
+ }
+
+ @Override
+ public ApiListResult list(ListMessageHistoryParam param) {
+ MessageHistoryService.ListMessageHistoryParam listMessageHistoryParam = MessageHistoryService.ListMessageHistoryParam.builder().build();
+ BeanUtils.copyProperties(param, listMessageHistoryParam);
+
+ List list = messageHistoryService.list(listMessageHistoryParam);
+ return ApiListResult.ok(list.stream()
+ .map(e -> {
+ MessageHistoryDTO messageHistoryDTO = MessageHistoryDTO.builder().build();
+ BeanUtils.copyProperties(e, messageHistoryDTO);
+ return messageHistoryDTO;
+ })
+ .collect(Collectors.toList()));
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/controller/PrivateController.java b/im-center-server/src/main/java/cn/axzo/im/controller/PrivateController.java
index 96aa6b6..0a9545c 100644
--- a/im-center-server/src/main/java/cn/axzo/im/controller/PrivateController.java
+++ b/im-center-server/src/main/java/cn/axzo/im/controller/PrivateController.java
@@ -1,10 +1,16 @@
package cn.axzo.im.controller;
+import cn.axzo.im.center.api.vo.req.SendMessageParam;
import cn.axzo.im.channel.netease.client.NimClient;
import cn.axzo.im.channel.netease.dto.QueryEventRequest;
import cn.axzo.im.channel.netease.dto.QueryMessageRequest;
import cn.axzo.im.channel.netease.dto.RevokeMessageRequest;
+import cn.axzo.im.job.CreateMessageHistoryJob;
import cn.axzo.im.job.RevokeAllMessagesJob;
+import cn.axzo.im.job.SendMessageJob;
+import cn.axzo.im.job.UpdateImAccountOuIdJob;
+import cn.axzo.im.service.AccountRegisterService;
+import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -22,6 +28,11 @@ public class PrivateController {
private final NimClient nimClient;
private final RevokeAllMessagesJob revokeAllMessagesJob;
+ private final UpdateImAccountOuIdJob updateImAccountOuIdJob;
+ private final AccountRegisterService accountRegisterService;
+ private final SendMessageJob sendMessageJob;
+ private final CreateMessageHistoryJob createMessageHistoryJob;
+ private final MessageController messageController;
@PostMapping("/private/revoke")
public Object revoke(@Valid @RequestBody RevokeMessageRequest request) {
@@ -43,4 +54,28 @@ public class PrivateController {
return revokeAllMessagesJob.execute(param);
}
+ @PostMapping("/private/im-account/ou-id/update")
+ public Object updateImAccountOuId(@RequestBody UpdateImAccountOuIdJob.UpdateImAccountOuIdParam param) throws Exception {
+ return updateImAccountOuIdJob.execute(JSONObject.toJSONString(param));
+ }
+
+ @PostMapping("/private/account-register/page")
+ public Object pageAccountRegister(@RequestBody AccountRegisterService.PageAccountRegisterParam param) throws Exception {
+ return accountRegisterService.page(param);
+ }
+
+ @PostMapping("/private/message/history/job/do")
+ public Object doMessageHistory(@RequestBody CreateMessageHistoryJob.CreateMessageHistoryParam param) throws Exception {
+ return createMessageHistoryJob.execute(JSONObject.toJSONString(param));
+ }
+
+ @PostMapping("/private/message/job/do")
+ public Object doMessageJob(@RequestBody SendMessageJob.SendMessageParam param) throws Exception {
+ return sendMessageJob.execute(JSONObject.toJSONString(param));
+ }
+
+ @PostMapping("/private/message/send")
+ public Object sendMessage(@RequestBody SendMessageParam param) throws Exception {
+ return messageController.sendMessage(param);
+ }
}
\ No newline at end of file
diff --git a/im-center-server/src/main/java/cn/axzo/im/controller/RobotInfoController.java b/im-center-server/src/main/java/cn/axzo/im/controller/RobotInfoController.java
index 0d0b2c3..0f01a2f 100644
--- a/im-center-server/src/main/java/cn/axzo/im/controller/RobotInfoController.java
+++ b/im-center-server/src/main/java/cn/axzo/im/controller/RobotInfoController.java
@@ -10,8 +10,13 @@ import cn.axzo.im.center.api.vo.req.UpdateRobotInfoReq;
import cn.axzo.im.center.api.vo.resp.RobotInfoResp;
import cn.axzo.im.channel.netease.INotifyService;
import cn.axzo.im.service.RobotInfoService;
+import cn.axzo.im.service.RobotInfoV2Service;
+import cn.axzo.pokonyan.dao.converter.PageConverter;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@@ -35,6 +40,9 @@ public class RobotInfoController implements RobotInfoApi {
@Resource
private INotifyService iNotifyService;
+ @Autowired
+ private RobotInfoV2Service robotInfoV2Service;
+
@Override
public ApiResult saveRobotInfo(RobotInfoReq robotInfoReq) {
RobotInfoResp robotTagResp = infoService.saveRobotInfo(robotInfoReq);
@@ -54,6 +62,7 @@ public class RobotInfoController implements RobotInfoApi {
}
@Override
+ @Deprecated
public ApiPageResult queryRobotList(RobotPageQuery robotQuery) {
PageResp robotTagRespPage = infoService.queryRobotInfoList(robotQuery);
return ApiPageResult.ok(robotTagRespPage);
@@ -64,4 +73,18 @@ public class RobotInfoController implements RobotInfoApi {
List robotTagResp = infoService.queryRunningRobotList();
return ApiResult.ok(robotTagResp);
}
+
+ @Override
+ public ApiPageResult page(PageRobotInfoParam param) {
+ RobotInfoV2Service.PageRobotInfoParam pageRobotInfoParam = RobotInfoV2Service.PageRobotInfoParam.builder().build();
+ BeanUtils.copyProperties(param, pageRobotInfoParam);
+ Page page = robotInfoV2Service.page(pageRobotInfoParam);
+
+ Page result = PageConverter.convert(page, (record) -> {
+ RobotInfoDTO robotInfoDTO = RobotInfoDTO.builder().build();
+ BeanUtils.copyProperties(record, robotInfoDTO);
+ return robotInfoDTO;
+ });
+ return ApiPageResult.ok(result);
+ }
}
diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/AccountRegisterMapper.java b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/AccountRegisterMapper.java
index df866a6..f0f61ae 100644
--- a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/AccountRegisterMapper.java
+++ b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/AccountRegisterMapper.java
@@ -3,6 +3,7 @@ package cn.axzo.im.dao.mapper;
import cn.axzo.im.entity.AccountRegister;
import cn.axzo.im.entity.RobotInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
/**
@@ -12,6 +13,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
* @version V1.0
* @date 2023/10/10 10:06
*/
+@Repository
public interface AccountRegisterMapper extends BaseMapper {
}
diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/MessageTaskMapper.java b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/MessageTaskMapper.java
new file mode 100644
index 0000000..2afaedc
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/MessageTaskMapper.java
@@ -0,0 +1,9 @@
+package cn.axzo.im.dao.mapper;
+
+import cn.axzo.im.entity.MessageTask;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface MessageTaskMapper extends BaseMapper {
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/RobotInfoMapper.java b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/RobotInfoMapper.java
index 2ff09fa..3ee02d8 100644
--- a/im-center-server/src/main/java/cn/axzo/im/dao/mapper/RobotInfoMapper.java
+++ b/im-center-server/src/main/java/cn/axzo/im/dao/mapper/RobotInfoMapper.java
@@ -2,6 +2,7 @@ package cn.axzo.im.dao.mapper;
import cn.axzo.im.entity.RobotInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
/**
* RobotInfoMapper
@@ -10,6 +11,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
* @version V1.0
* @date 2023/10/10 10:06
*/
+@Repository
public interface RobotInfoMapper extends BaseMapper {
}
diff --git a/im-center-server/src/main/java/cn/axzo/im/dao/repository/RobotInfoDao.java b/im-center-server/src/main/java/cn/axzo/im/dao/repository/RobotInfoDao.java
index 47361f0..4c0d2be 100644
--- a/im-center-server/src/main/java/cn/axzo/im/dao/repository/RobotInfoDao.java
+++ b/im-center-server/src/main/java/cn/axzo/im/dao/repository/RobotInfoDao.java
@@ -28,7 +28,8 @@ public class RobotInfoDao extends ServiceImpl {
* @param robotInfoQuery 机器人标签分页查询条件
* @return 机器人分页查询结果
*/
- public IPage queryRobotInfoOfPage(RobotPageQuery robotInfoQuery) {
+ public IPage queryRobotInfoOfPage(RobotPageQuery robotInfoQuery,
+ List robotIds) {
return lambdaQuery().eq(RobotInfo::getIsDelete, 0)
.like(StringUtils.isNoneBlank(robotInfoQuery.getNickName()),
RobotInfo::getNickName,
@@ -37,6 +38,7 @@ public class RobotInfoDao extends ServiceImpl {
RobotInfo::getStatus, robotInfoQuery.getStatus())
.eq(StringUtils.isNoneBlank(robotInfoQuery.getImAccount()),
RobotInfo::getImAccount, robotInfoQuery.getImAccount())
+ .in(!CollectionUtils.isEmpty(robotIds), RobotInfo::getRobotId, robotIds)
.orderByDesc(RobotInfo::getUpdateAt)
.page(robotInfoQuery.toPage());
}
diff --git a/im-center-server/src/main/java/cn/axzo/im/entity/AccountRegister.java b/im-center-server/src/main/java/cn/axzo/im/entity/AccountRegister.java
index ab7fd63..006c6f4 100644
--- a/im-center-server/src/main/java/cn/axzo/im/entity/AccountRegister.java
+++ b/im-center-server/src/main/java/cn/axzo/im/entity/AccountRegister.java
@@ -1,14 +1,18 @@
package cn.axzo.im.entity;
-import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
import cn.axzo.im.center.common.enums.AppTypeEnum;
+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.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
+import lombok.experimental.SuperBuilder;
import java.io.Serializable;
+import java.util.Date;
/**
* IM账户表
@@ -19,12 +23,17 @@ import java.io.Serializable;
*/
@TableName("im_account_register")
@Data
-@EqualsAndHashCode(callSuper = true)
+@SuperBuilder
@Accessors(chain = true)
-public class AccountRegister extends BaseEntity implements Serializable {
+@NoArgsConstructor
+@AllArgsConstructor
+public class AccountRegister implements Serializable {
private static final long serialVersionUID = 1L;
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
/**
* 账户 机器人robotId、普通用户userId
*/
@@ -32,7 +41,7 @@ public class AccountRegister extends BaseEntity implements Ser
private String accountId;
/**
- * 普通用户,通过appType包装
+ * 普通用户,通过appType和ouId包装
* 包装以后进行账户注册
*/
@TableField("account_wrapper")
@@ -76,4 +85,19 @@ public class AccountRegister extends BaseEntity implements Ser
*/
@TableField("token")
private String token;
+
+ /**
+ * organizational_unit表的id
+ */
+ @TableField("ou_id")
+ private Long ouId;
+
+ @TableField
+ private Integer isDelete;
+
+ @TableField
+ private Date createAt;
+
+ @TableField
+ private Date updateAt;
}
diff --git a/im-center-server/src/main/java/cn/axzo/im/entity/MessageHistory.java b/im-center-server/src/main/java/cn/axzo/im/entity/MessageHistory.java
index 8a29341..a17f138 100644
--- a/im-center-server/src/main/java/cn/axzo/im/entity/MessageHistory.java
+++ b/im-center-server/src/main/java/cn/axzo/im/entity/MessageHistory.java
@@ -2,13 +2,19 @@ package cn.axzo.im.entity;
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
import cn.axzo.im.center.common.enums.AppTypeEnum;
+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.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
+import lombok.experimental.SuperBuilder;
import java.io.Serializable;
+import java.util.Date;
/**
* IM消息模历史表
@@ -19,12 +25,17 @@ import java.io.Serializable;
*/
@TableName("im_message_history")
@Data
-@EqualsAndHashCode(callSuper = true)
+@SuperBuilder
@Accessors(chain = true)
-public class MessageHistory extends BaseEntity implements Serializable {
+@NoArgsConstructor
+@AllArgsConstructor
+public class MessageHistory implements Serializable {
private static final long serialVersionUID = 1L;
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
/**
* 上游业务请求ID
*/
@@ -70,4 +81,33 @@ public class MessageHistory extends BaseEntity implements Seria
@TableField("message_body")
private String messageBody;
+ @TableField("result")
+ private String result;
+
+ @TableField("im_message_task_id")
+ private Long imMessageTaskId;
+
+ @TableField("receive_person_id")
+ private String receivePersonId;
+
+ @TableField("receive_ou_id")
+ private Long receiveOuId;
+
+ private Status status;
+
+ @TableField
+ private Integer isDelete;
+
+ @TableField
+ private Date createAt;
+
+ @TableField
+ private Date updateAt;
+
+ public enum Status {
+ PENDING,
+ SUCCEED,
+ FAILED,
+ ;
+ }
}
diff --git a/im-center-server/src/main/java/cn/axzo/im/entity/MessageTask.java b/im-center-server/src/main/java/cn/axzo/im/entity/MessageTask.java
new file mode 100644
index 0000000..c61d264
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/entity/MessageTask.java
@@ -0,0 +1,223 @@
+package cn.axzo.im.entity;
+
+import cn.axzo.im.center.api.vo.req.SendMessageParam;
+import cn.axzo.im.center.common.enums.AppTypeEnum;
+import cn.axzo.im.center.common.enums.BizTypeEnum;
+import cn.axzo.im.config.BaseListTypeHandler;
+import cn.axzo.maokai.api.client.OrganizationalTeamOuRelationApi;
+import cn.axzo.maokai.api.vo.response.OrganizationalTeamOuRelationResp;
+import com.alibaba.fastjson.JSONObject;
+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 com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import lombok.experimental.SuperBuilder;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.cglib.beans.BeanMap;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static cn.axzo.im.config.BizResultCode.MESSAGE_TASK_STATUS_ERROR;
+
+@Data
+@SuperBuilder
+@Accessors(chain = true)
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName(value = "`im_message_task`", autoResultMap = true)
+public class MessageTask {
+
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 业务请求时可以带的排查问题的id
+ */
+ @TableField(value = "biz_id")
+ private String bizId;
+
+ /**
+ * IM消息发送者IM的账号
+ */
+ @TableField(value = "send_im_account")
+ private String sendImAccount;
+
+ /**
+ * IM消息接收人person信息
+ */
+ @TableField(typeHandler = ListReceivePersonTypeHandler.class)
+ private List receivePersons;
+
+ private Status status;
+
+ @TableField(value = "title")
+ private String title;
+
+ @TableField(value = "content")
+ private String content;
+
+ @TableField(value = "card_banner_url")
+ private String cardBannerUrl;
+
+ @TableField(typeHandler = FastjsonTypeHandler.class)
+ private BizData bizData;
+
+ @TableField(typeHandler = FastjsonTypeHandler.class)
+ private JSONObject ext;
+
+ @TableField
+ private Date planStartTime;
+
+ @TableField
+ private Date startedTime;
+
+ @TableField
+ private Date finishedTime;
+
+ @TableField
+ private Integer isDelete;
+
+ @TableField
+ private Date createAt;
+
+ @TableField
+ private Date updateAt;
+
+
+ public enum Status {
+ PENDING,
+ SUCCEED,
+ FAILED,
+ ;
+ }
+
+ public JSONObject mergeBizData(BizData param) {
+ JSONObject bizData = JSONObject.parseObject(JSONObject.toJSONString(Optional.ofNullable(this.getBizData())
+ .orElseGet(BizData::new)));
+ if (param == null) {
+ return bizData;
+ }
+ return bizData.fluentPutAll(BeanMap.create(param));
+ }
+
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class BizData {
+ private String msgTemplateId;
+
+ /**
+ * 消息模板内容
+ */
+ private String msgTemplateContent;
+
+ /**
+ * 网易云信-自定义消息使用
+ */
+ private BizTypeEnum bizType;
+
+ /**
+ * 网易云信-自定义消息使用
+ * 推送内容 - 业务数据,json格式
+ */
+ private String payload;
+
+ /**
+ * 跳转信息
+ */
+ private List jumpData;
+
+ /**
+ * 给全员发送
+ */
+ private boolean allPerson;
+
+ /**
+ * 全员发送时需要指定发送消息到App端
+ * 工人端、企业端、服务器
+ * CM、CMP、SYSTEM
+ *
+ * @See cn.axzo.im.center.common.enums.AppTypeEnum
+ */
+ private List appTypes;
+ }
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class ReceivePerson {
+
+ /**
+ * 接收消息的personId
+ */
+ private String personId;
+
+ /**
+ * appType = AppTypeEnum.CMP时,因为网易云信无法对同一个账号做企业隔离,只能一个企业一个账号,
+ * 所以需要根据organizationalUnitId获取账号
+ */
+ private Long ouId;
+
+ /**
+ * 发送消息到App端
+ * 工人端、企业端、服务器
+ * CM、CMP、SYSTEM
+ *
+ * @See cn.axzo.im.center.common.enums.AppTypeEnum
+ */
+ private AppTypeEnum appType;
+
+ /**
+ * im账号,可以personId和imAccount二选一
+ */
+ private String imAccount;
+
+ private Long workspaceId;
+
+ public String buildKey(Map ouIdMap) {
+ if (StringUtils.isNotBlank(this.getImAccount())) {
+ return this.getImAccount();
+ }
+
+ // 因为模板消息发给工人端的时候会带ouId
+ if (appType == AppTypeEnum.CM) {
+ return this.getPersonId() + "_" + this.getAppType().getCode();
+ }
+
+ return this.getPersonId() + "_" + this.getAppType().getCode() + "_" + ouIdMap.getOrDefault(this.getOuId(), this.getOuId());
+ }
+ }
+
+ public static class ListReceivePersonTypeHandler extends BaseListTypeHandler {}
+
+ @Getter
+ @AllArgsConstructor
+ public enum ActionEnum {
+ SUCCESS,
+ ;
+
+ private static final Table STATUS_FLOWS = HashBasedTable.create();
+
+ static {
+ STATUS_FLOWS.put(Status.PENDING, SUCCESS, Status.SUCCEED);
+ }
+
+ public Status getNextStatus(Status oldStatus) {
+ return Optional.ofNullable(STATUS_FLOWS.get(oldStatus, this)).orElseThrow(MESSAGE_TASK_STATUS_ERROR::toException);
+ }
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/entity/RobotInfo.java b/im-center-server/src/main/java/cn/axzo/im/entity/RobotInfo.java
index 92278bf..7a22e42 100644
--- a/im-center-server/src/main/java/cn/axzo/im/entity/RobotInfo.java
+++ b/im-center-server/src/main/java/cn/axzo/im/entity/RobotInfo.java
@@ -1,14 +1,19 @@
package cn.axzo.im.entity;
-import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
+import cn.axzo.framework.data.mybatisplus.type.BaseListTypeHandler;
+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 com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.AllArgsConstructor;
import lombok.Data;
-import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
+import lombok.experimental.SuperBuilder;
import java.io.Serializable;
+import java.util.Date;
import java.util.List;
/**
@@ -20,13 +25,17 @@ import java.util.List;
*/
@TableName(value = "im_robot_info",autoResultMap = true)
@Data
-@EqualsAndHashCode(callSuper = true)
+@SuperBuilder
@Accessors(chain = true)
-
-public class RobotInfo extends BaseEntity implements Serializable {
+@NoArgsConstructor
+@AllArgsConstructor
+public class RobotInfo implements Serializable {
private static final long serialVersionUID = 1L;
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
/**
* 机器人ID
* 目的是用该字段进行账户注册,如果使用数据库主键,
@@ -43,8 +52,9 @@ public class RobotInfo extends BaseEntity implements Serializable {
/**
* 机器人Tag列表
+ * 存放的是robotTag表的id
*/
- @TableField(value = "tag_name_list",typeHandler = JacksonTypeHandler.class)
+ @TableField(value = "tag_name_list",typeHandler = ListLongTypeHandler.class)
private List tagNameList;
@@ -67,4 +77,17 @@ public class RobotInfo extends BaseEntity implements Serializable {
@TableField("status")
private String status;
+ @TableField
+ private Integer isDelete;
+
+ @TableField
+ private Date createAt;
+
+ @TableField
+ private Date updateAt;
+
+ /** 使用FastjsonTypeHandler, 因为没有指定类型,有可能反序列化成List,引起后续类型转换异常 */
+ public static class ListLongTypeHandler extends BaseListTypeHandler {
+
+ }
}
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
new file mode 100644
index 0000000..66c921c
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/event/inner/EventTypeEnum.java
@@ -0,0 +1,32 @@
+package cn.axzo.im.event.inner;
+
+import cn.axzo.framework.rocketmq.Event;
+import lombok.Getter;
+
+/**
+ * @Classname EventTypeEnum
+ * @Date 2021/2/7 6:05 下午
+ * @Created by lilong
+ */
+@Getter
+public enum EventTypeEnum {
+
+ MESSAGE_HISTORY_CREATED("message-history", "message-history-created", "发送记录创建"),
+ MESSAGE_HISTORY_UPDATED("message-history", "message-history-updated", "发送记录修改")
+ ;
+
+ EventTypeEnum(String model, String name, String desc) {
+ this.eventCode = Event.EventCode.builder()
+ .module(model)
+ .name(name)
+ .build();
+ this.model = model;
+ this.name = name;
+ this.desc = desc;
+ }
+
+ private String model;
+ private String name;
+ private String desc;
+ private Event.EventCode eventCode;
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/event/payload/MessageHistoryCreatedPayload.java b/im-center-server/src/main/java/cn/axzo/im/event/payload/MessageHistoryCreatedPayload.java
new file mode 100644
index 0000000..e9f6540
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/event/payload/MessageHistoryCreatedPayload.java
@@ -0,0 +1,18 @@
+package cn.axzo.im.event.payload;
+
+import cn.axzo.im.entity.MessageHistory;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MessageHistoryCreatedPayload implements Serializable {
+
+ private MessageHistory messageHistory;
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/event/payload/MessageHistoryUpdatedPayload.java b/im-center-server/src/main/java/cn/axzo/im/event/payload/MessageHistoryUpdatedPayload.java
new file mode 100644
index 0000000..3b06362
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/event/payload/MessageHistoryUpdatedPayload.java
@@ -0,0 +1,19 @@
+package cn.axzo.im.event.payload;
+
+import cn.axzo.im.entity.MessageHistory;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MessageHistoryUpdatedPayload implements Serializable {
+
+ private MessageHistory newMessageHistory;
+ private MessageHistory oldMessageHistory;
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/exception/ExceptionAdviceHandler.java b/im-center-server/src/main/java/cn/axzo/im/exception/ExceptionAdviceHandler.java
index 54cf69e..38718b8 100644
--- a/im-center-server/src/main/java/cn/axzo/im/exception/ExceptionAdviceHandler.java
+++ b/im-center-server/src/main/java/cn/axzo/im/exception/ExceptionAdviceHandler.java
@@ -1,6 +1,7 @@
package cn.axzo.im.exception;
import cn.axzo.basics.common.exception.ServiceException;
+import cn.axzo.pokonyan.exception.BusinessException;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
@@ -36,6 +37,12 @@ public class ExceptionAdviceHandler {
return CommonResponse.fail(e.getMessage());
}
+ @ExceptionHandler(BusinessException.class)
+ public CommonResponse businessExceptionHandler(BusinessException e) {
+ log.warn("业务异常", e);
+ return CommonResponse.fail(e.getMessage());
+ }
+
@ExceptionHandler(BindException.class)
public CommonResponse bindExceptionHandler(BindException e) {
log.warn("业务异常", e);
diff --git a/im-center-server/src/main/java/cn/axzo/im/job/CreateMessageHistoryJob.java b/im-center-server/src/main/java/cn/axzo/im/job/CreateMessageHistoryJob.java
new file mode 100644
index 0000000..cd05ea4
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/job/CreateMessageHistoryJob.java
@@ -0,0 +1,83 @@
+package cn.axzo.im.job;
+
+import cn.axzo.im.entity.MessageTask;
+import cn.axzo.im.service.MessageTaskService;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.IJobHandler;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 查询ImMessageTask中的PEDING数据,添加到messageHistory
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class CreateMessageHistoryJob extends IJobHandler {
+
+ @Autowired
+ private MessageTaskService messageTaskService;
+
+ private static final Integer DEFAULT_PAGE_SIZE = 500;
+
+ @Override
+ @XxlJob("createMessageHistoryJob")
+ public ReturnT execute(String s) throws Exception {
+
+ log.info("start createMessageHistoryJob,s:{}", s);
+ CreateMessageHistoryParam createMessageHistoryParam = Optional.ofNullable(s)
+ .map(e -> JSONObject.parseObject(e, CreateMessageHistoryParam.class))
+ .orElseGet(() -> CreateMessageHistoryParam.builder().build());
+ Integer pageNumber = 1;
+ Date now = new Date();
+ while (true) {
+ MessageTaskService.PageMessageTaskParam req = MessageTaskService.PageMessageTaskParam.builder()
+ .ids(createMessageHistoryParam.getIds())
+ .planStartTimeLE(now)
+ .status(MessageTask.Status.PENDING)
+ .page(pageNumber)
+ .pageSize(DEFAULT_PAGE_SIZE)
+ .build();
+
+ Page page = messageTaskService.page(req);
+ if (CollectionUtils.isNotEmpty(page.getRecords())) {
+ page.getRecords().forEach(messageTask -> {
+ try {
+ messageTaskService.createMessageHistory(messageTask);
+ } catch (Exception exception) {
+ log.warn("messageTask 执行失败,{}, ex", messageTask.getId(), exception);
+ }
+
+ });
+ }
+
+ if (!page.hasNext()) {
+ break;
+ }
+ }
+ log.info("end createMessageHistoryJob");
+ return ReturnT.SUCCESS;
+ }
+
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class CreateMessageHistoryParam {
+ private List ids;
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/job/SendMessageJob.java b/im-center-server/src/main/java/cn/axzo/im/job/SendMessageJob.java
new file mode 100644
index 0000000..846ca5c
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/job/SendMessageJob.java
@@ -0,0 +1,78 @@
+package cn.axzo.im.job;
+
+import cn.axzo.im.entity.MessageHistory;
+import cn.axzo.im.service.MessageHistoryService;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.google.common.collect.Sets;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.IJobHandler;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SendMessageJob extends IJobHandler {
+
+ @Autowired
+ private MessageHistoryService messageHistoryService;
+
+ private static final Integer DEFAULT_PAGE_SIZE = 100;
+
+ @Override
+ @XxlJob("sendMessageJob")
+ public ReturnT execute(String s) throws Exception {
+ log.info("start sendMessageJob,s:{}", s);
+ SendMessageParam sendMessageParam = Optional.ofNullable(s)
+ .map(e -> JSONObject.parseObject(e, SendMessageParam.class))
+ .orElseGet(() -> SendMessageParam.builder().build());
+ Integer pageNumber = 1;
+ Date now = new Date();
+ while (true) {
+ MessageHistoryService.PageMessageHistoryParam req = MessageHistoryService.PageMessageHistoryParam.builder()
+ .ids(sendMessageParam.getIds())
+ .statues(Sets.newHashSet(MessageHistory.Status.PENDING.name()))
+ .page(pageNumber)
+ .pageSize(DEFAULT_PAGE_SIZE)
+ .build();
+
+ Page page = messageHistoryService.page(req);
+ if (CollectionUtils.isNotEmpty(page.getRecords())) {
+ Map> messageHistories = page.getRecords().stream()
+ .collect(Collectors.groupingBy(MessageHistoryService.MessageHistoryDTO::getImMessageTaskId));
+
+ messageHistories.values().forEach(messageHistoryService::sendMessage);
+ }
+
+ if (!page.hasNext()) {
+ break;
+ }
+ }
+ log.info("end sendMessageJob");
+ return ReturnT.SUCCESS;
+ }
+
+
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class SendMessageParam {
+ private List ids;
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/job/UpdateImAccountOuIdJob.java b/im-center-server/src/main/java/cn/axzo/im/job/UpdateImAccountOuIdJob.java
new file mode 100644
index 0000000..dfc0256
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/job/UpdateImAccountOuIdJob.java
@@ -0,0 +1,160 @@
+package cn.axzo.im.job;
+
+import cn.axzo.basics.common.constant.enums.OrganizationalUnitTypeEnum;
+import cn.axzo.im.center.common.enums.AccountTypeEnum;
+import cn.axzo.im.center.common.enums.AppTypeEnum;
+import cn.axzo.im.entity.AccountRegister;
+import cn.axzo.im.service.AccountRegisterService;
+import cn.axzo.maokai.api.client.OrganizationalUnitApi;
+import cn.axzo.maokai.api.vo.request.OrganizationalUnitQuery;
+import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO;
+import cn.axzo.tyr.client.feign.TyrSaasRoleUserApi;
+import cn.axzo.tyr.client.model.roleuser.dto.SaasRoleUserDTO;
+import cn.axzo.tyr.client.model.roleuser.req.RoleUserParam;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.google.common.collect.Lists;
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.IJobHandler;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 把appType = CMP、accountType = 'user'的账号的ouId更新成用户最后加入的企业id
+ * 因为用户在管理版的IM云信消息要按照企业隔离,历史的账号是跟企业没绑定,要保持数据不丢失
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class UpdateImAccountOuIdJob extends IJobHandler {
+
+ @Autowired
+ private AccountRegisterService accountRegisterService;
+ @Autowired
+ private TyrSaasRoleUserApi tyrSaasRoleUserApi;
+ @Autowired
+ private OrganizationalUnitApi organizationalUnitApi;
+
+ private static final Integer DEFAULT_PAGE_SIZE = 500;
+
+ @Override
+ @XxlJob("updateImAccountOuIdJob")
+ public ReturnT execute(String s) throws Exception {
+
+ log.info("start updateImAccountOuIdJob,s:{}", s);
+ UpdateImAccountOuIdParam updateImAccountOuIdParam = Optional.ofNullable(s)
+ .map(e -> JSONObject.parseObject(e, UpdateImAccountOuIdParam.class))
+ .orElseGet(() -> UpdateImAccountOuIdParam.builder().build());
+ Integer pageNumber = 1;
+ while (true) {
+ AccountRegisterService.PageAccountRegisterParam req = AccountRegisterService.PageAccountRegisterParam.builder()
+ .ids(updateImAccountOuIdParam.getIds())
+ .appType(AppTypeEnum.CMP.getCode())
+ .accountType(AccountTypeEnum.USER.getCode())
+ .accountWrapperEW("cmp")
+ .page(pageNumber++)
+ .pageSize(DEFAULT_PAGE_SIZE)
+ .build();
+
+ Page page = accountRegisterService.page(req);
+ if (CollectionUtils.isNotEmpty(page.getRecords())) {
+
+ Map nodeUsers = listNodeUsers(page.getRecords());
+
+ updateAccountRegister(page.getRecords(), nodeUsers);
+ }
+
+ if (!page.hasNext()) {
+ break;
+ }
+ }
+ log.info("end updateImAccountOuIdJob");
+ return ReturnT.SUCCESS;
+ }
+
+ private void updateAccountRegister(List accountRegisters, Map nodeUsers) {
+ List update = accountRegisters.stream()
+ .filter(accountRegister -> nodeUsers.get(accountRegister.getAccountId()) != null)
+ .map(accountRegister -> {
+ Long ouId = nodeUsers.get(accountRegister.getAccountId());
+
+ if (ouId == null) {
+ return null;
+ }
+ AccountRegister result = new AccountRegister();
+ result.setId(accountRegister.getId());
+ result.setOuId(ouId);
+ result.setUpdateAt(new Date());
+ return result;
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ if (CollectionUtils.isEmpty(accountRegisters)) {
+ log.info("updateImAccountOuIdJob: no data update");
+ return;
+ }
+
+ accountRegisterService.updateBatchById(update);
+ }
+
+ private Map listNodeUsers(List accountRegisters) {
+ if (CollectionUtils.isEmpty(accountRegisters)) {
+ return Collections.EMPTY_MAP;
+ }
+ Set accountIds = accountRegisters.stream()
+ .map(AccountRegister::getAccountId)
+ .filter(Objects::nonNull)
+ .filter(StringUtils::isNumeric)
+ .map(Long::valueOf)
+ .collect(Collectors.toSet());
+ if (CollectionUtils.isEmpty(accountIds)) {
+ return Collections.EMPTY_MAP;
+ }
+
+ List saasRoleUsers = accountIds.stream()
+ .flatMap(e -> tyrSaasRoleUserApi.roleUserList(RoleUserParam.builder().personId(e).build()).getData().stream())
+ .collect(Collectors.toList());
+
+ List ouIds = Lists.transform(saasRoleUsers, SaasRoleUserDTO::getOuId);
+
+ if (CollectionUtils.isEmpty(ouIds)) {
+ return Collections.EMPTY_MAP;
+ }
+
+ Set effectOuIds = organizationalUnitApi.list(OrganizationalUnitQuery.builder().unitIds(ouIds).build()).getData()
+ .stream()
+ .filter(e -> !Objects.equals(e.getType(), OrganizationalUnitTypeEnum.PROJECT_OUT_TEAM.getValue()))
+ .map(OrganizationalUnitVO::getId)
+ .collect(Collectors.toSet());
+
+ return saasRoleUsers.stream()
+ .filter(e -> effectOuIds.contains(e.getOuId()))
+ .collect(Collectors.toMap(e -> e.getNaturalPersonId().toString(), SaasRoleUserDTO::getOuId, (f, s) -> f));
+ }
+
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class UpdateImAccountOuIdParam {
+ private List ids;
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/service/AccountRegisterService.java b/im-center-server/src/main/java/cn/axzo/im/service/AccountRegisterService.java
new file mode 100644
index 0000000..c36d01a
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/service/AccountRegisterService.java
@@ -0,0 +1,120 @@
+package cn.axzo.im.service;
+
+import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
+import cn.axzo.im.entity.AccountRegister;
+import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO;
+import cn.axzo.pokonyan.dao.page.IPageParam;
+import cn.axzo.pokonyan.dao.wrapper.CriteriaField;
+import cn.axzo.pokonyan.dao.wrapper.Operator;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.springframework.beans.BeanUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public interface AccountRegisterService extends IService {
+
+ Page page(PageAccountRegisterParam param);
+
+ List list(ListAccountRegisterParam param);
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class ListAccountRegisterParam {
+
+ @CriteriaField(field = "id", operator = Operator.IN)
+ private List ids;
+
+ @CriteriaField(field = "appType", operator = Operator.EQ)
+ private String appType;
+
+ @CriteriaField(field = "appType", operator = Operator.IN)
+ private Set appTypes;
+
+ /**
+ * 注册用户ID唯一
+ * 普通用户personId、机器人robotId
+ */
+ @CriteriaField(field = "accountId", operator = Operator.EQ)
+ private String accountId;
+
+ @CriteriaField(field = "accountId", operator = Operator.IN)
+ private Set accountIds;
+
+ /**
+ * 注册用户ID唯一
+ */
+ @CriteriaField(field = "imAccount", operator = Operator.EQ)
+ private String imAccount;
+
+ @CriteriaField(field = "imAccount", operator = Operator.IN)
+ private Set imAccounts;
+
+ /**
+ * appType = AppTypeEnum.CMP时,因为网易云信无法对同一个账号做企业隔离,只能一个企业一个账号,
+ * 所以需要根据ouId获取账号
+ */
+ @CriteriaField(field = "ouId", operator = Operator.EQ)
+ private Long ouId;
+
+ @CriteriaField(field = "accountType", operator = Operator.EQ)
+ private String accountType;
+
+ @CriteriaField(field = "accountWrapper", operator = Operator.EW)
+ private String accountWrapperEW;
+
+ @CriteriaField(ignore = true)
+ private boolean needOuInfo;
+
+ @CriteriaField(ignore = true)
+ private boolean needUserInfo;
+ }
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class PageAccountRegisterParam extends ListAccountRegisterParam implements IPageParam {
+ @CriteriaField(ignore = true)
+ Integer page;
+
+ @CriteriaField(ignore = true)
+ Integer pageSize;
+
+ /**
+ * 排序:使用示例,createTime__DESC
+ */
+ @CriteriaField(ignore = true)
+ List sort;
+ }
+
+ @Data
+ @SuperBuilder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class AccountRegisterDTO extends AccountRegister {
+
+ private PersonProfileDto personProfile;
+
+ private OrganizationalUnitVO organizationalUnit;
+
+ public static AccountRegisterDTO from(AccountRegister accountRegister,
+ Map personProfiles,
+ Map organizationals) {
+ AccountRegisterDTO accountRegisterDTO = AccountRegisterDTO.builder().build();
+ BeanUtils.copyProperties(accountRegister, accountRegisterDTO);
+
+ accountRegisterDTO.setPersonProfile(personProfiles.get(accountRegisterDTO.getAccountId()));
+ accountRegisterDTO.setOrganizationalUnit(organizationals.get(accountRegisterDTO.getOuId()));
+ return accountRegisterDTO;
+ }
+ }
+}
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 70ca816..4eae664 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
@@ -25,24 +25,24 @@ import cn.axzo.im.entity.bo.AccountQueryParam;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.google.common.collect.Lists;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.env.Environment;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.RestController;
+import javax.annotation.Resource;
+import javax.validation.Valid;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
-import javax.annotation.Resource;
-import javax.validation.Valid;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.core.env.Environment;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.bind.annotation.RestController;
/**
* IM账户服务
@@ -56,6 +56,9 @@ import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
public class AccountService {
+ @Autowired
+ private AccountRegisterService accountRegisterService;
+
@Resource
private IMChannelProvider imChannelProvider;
@@ -116,10 +119,10 @@ public class AccountService {
if (appTypeEnum == null) {
throw new ServiceException("当前appType,服务器不支持该类型!!");
}
- String userIdWrapper = buildUserIdWrapper(userAccountReq.getUserId(), userAccountReq.getAppType());
+ String userIdWrapper = buildUserIdWrapper(userAccountReq.getUserId(), userAccountReq.getAppType(), userAccountReq.getOrganizationalUnitId());
//后续AppKey可能会更换,普通用户通过userId、appType、appKey维度来保证数据库唯一性
UserAccountResp userAccountResp = createAccountRegister(userAccountReq.getUserId(), userIdWrapper, appType,
- AccountTypeEnum.USER.getCode(), userAccountReq.getHeadImageUrl(), userAccountReq.getNickName());
+ AccountTypeEnum.USER.getCode(), userAccountReq.getHeadImageUrl(), userAccountReq.getNickName(), userAccountReq.getOrganizationalUnitId());
if (iNotifyService != null && userAccountResp != null && StringUtils.isNotBlank(userAccountResp.getImAccount())) {
iNotifyService.notifyUserAccountChange(userAccountResp.getImAccount(), userAccountReq.getNickName(), null);
@@ -127,13 +130,17 @@ public class AccountService {
return userAccountResp;
}
- private String buildUserIdWrapper(String userId, String appType) {
+ private String buildUserIdWrapper(String userId, String appType, Long organizationalUnitId) {
String env = environment.getProperty("spring.profiles.active");
StringBuilder buf = new StringBuilder();
if (StringUtils.isNotBlank(liveEnvPrefix)) {
buf.append(liveEnvPrefix).append("_");
}
buf.append(env).append(userId).append("_").append(appType);
+ // 新的用户在使用管理版时,如果有企业,则只能根据企业产生唯一账号
+ if (organizationalUnitId != null) {
+ buf.append("_").append(organizationalUnitId);
+ }
return buf.toString();
}
@@ -147,7 +154,7 @@ public class AccountService {
if (appTypeEnum == null) {
throw new ServiceException("当前appType,服务器不支持该类型!!");
}
- String userIdWrapper = buildUserIdWrapper(userAccountReq.getUserId(), appTypeEnum.getCode());
+ String userIdWrapper = buildUserIdWrapper(userAccountReq.getUserId(), appTypeEnum.getCode(), userAccountReq.getOrganizationalUnitId());
String appKey = imChannelProvider.getProviderAppKey();
AccountRegister customAccountRegister = queryCustomAccount(AccountTypeEnum.CUSTOM,
@@ -163,7 +170,8 @@ public class AccountService {
}
UserAccountResp userAccountResp = createAccountRegister(userAccountReq.getUserId(), userIdWrapper, appType,
- AccountTypeEnum.CUSTOM.getCode(), userAccountReq.getHeadImageUrl(), userAccountReq.getNickName());
+ AccountTypeEnum.CUSTOM.getCode(), userAccountReq.getHeadImageUrl(), userAccountReq.getNickName(),
+ userAccountReq.getOrganizationalUnitId());
if (iNotifyService != null && userAccountResp != null && StringUtils.isNotBlank(userAccountResp.getImAccount())) {
iNotifyService.notifyUserAccountChange(userAccountResp.getImAccount(), userAccountReq.getNickName(), null);
@@ -192,7 +200,7 @@ public class AccountService {
throw new ServiceException("该机器人robotId:{} 还未创建信息!");
}
UserAccountResp userAccountResp = createAccountRegister(robotId, robotId, AppTypeEnum.SYSTEM.getCode(),
- AccountTypeEnum.ROBOT.getCode(), robotAccountReq.getHeadImageUrl(), robotAccountReq.getNickName());
+ AccountTypeEnum.ROBOT.getCode(), robotAccountReq.getHeadImageUrl(), robotAccountReq.getNickName(), null);
if (userAccountResp != null && StringUtils.isNotBlank(userAccountResp.getImAccount())) {
//生成后更新机器人状态和IM账户
robotInfoService.updateRobotStatus(robotId, userAccountResp.getImAccount(), RobotStatusEnum.UN_ENABLE);
@@ -204,7 +212,8 @@ public class AccountService {
}
public UserAccountResp createAccountRegister(String userId, String userIdWrapper, String appType,
- String accountType, String headImageUrl, String nickName) {
+ String accountType, String headImageUrl, String nickName,
+ Long ouId) {
//1.检查账户是否已经创建
String appKey = imChannelProvider.getProviderAppKey();
UserAccountResp userAccountResp = new UserAccountResp();
@@ -230,6 +239,7 @@ public class AccountService {
accountRegister.setChannelProvider(imChannelProvider.getProviderType());
accountRegister.setCreateAt(new Date());
accountRegister.setUpdateAt(new Date());
+ accountRegister.setOuId(ouId);
accountRegisterDao.saveOrUpdate(accountRegister);
} else {
//2.1注册出现异常
@@ -237,6 +247,7 @@ public class AccountService {
userAccountResp.setDesc(accountResp.getDesc());
return userAccountResp;
}
+ userAccountResp.setOuId(ouId);
accountResp.setAppType(appType);
return accountResp;
}
@@ -245,6 +256,7 @@ public class AccountService {
userAccountResp.setUserId(userId);
userAccountResp.setAppType(appType);
userAccountResp.setToken(accountRegister.getToken());
+ userAccountResp.setOuId(ouId);
return userAccountResp;
}
@@ -267,11 +279,18 @@ public class AccountService {
return userAccountResp;
}
+ /**
+ * 建议用更通用的AccountRegisterService.page解耦
+ * @param accountQuery
+ * @return
+ */
+ @Deprecated
public List queryAccountInfo(@Valid AccountQuery accountQuery) {
//如果存在多个appKey,一个账户会有多条数据,分别对应不同的appKey
//机器人的账户 不进行wapper判断
+ // TODO 这块逻辑不通用,新的数据userIdWrapper会增加ouId,历史的数据只会补ouId,不会把userIdWrapper补上ouId,不应该放在通用的query里面
if (!AppTypeEnum.SYSTEM.getCode().equals(accountQuery.getAppType()) && StringUtils.isEmpty(accountQuery.getImAccount())) {
- String userIdWrapper = buildUserIdWrapper(accountQuery.getAccountId(), accountQuery.getAppType());
+ String userIdWrapper = buildUserIdWrapper(accountQuery.getAccountId(), accountQuery.getAppType(), null);
accountQuery.setImAccount(userIdWrapper);
}
List accountRegisterList = accountRegisterDao.lambdaQuery().eq(AccountRegister::getIsDelete, 0)
@@ -316,13 +335,29 @@ public class AccountService {
} else {
target = AppTypeEnum.values();
}
+ // TODO 待优化,兼容移动端,因为原接口会返回cm、cmp两个端的账号,但是cmp这个端查询的时候,如果有传ouId,则返回对应ouId的账号
for (AppTypeEnum appTypeEnum : target) {
- AccountQuery accountQuery = new AccountQuery();
- accountQuery.setAppType(appTypeEnum.getCode());
- accountQuery.setAccountId(accountAbsentQuery.getPersonId());
- List userAccountRespList = queryAccountInfo(accountQuery);
- if (CollectionUtils.isNotEmpty(userAccountRespList)) {
- userAccountAll.addAll(userAccountRespList);
+ AccountRegisterService.ListAccountRegisterParam listAccountRegisterParam = AccountRegisterService.ListAccountRegisterParam.builder()
+ .appType(appTypeEnum.getCode())
+ .accountId(accountAbsentQuery.getPersonId())
+ .build();
+ if (appTypeEnum == AppTypeEnum.CMP && accountAbsentQuery.getOuId() != null && accountAbsentQuery.getOuId() != 0) {
+ listAccountRegisterParam.setOuId(accountAbsentQuery.getOuId());
+ }
+ List accountRegisters = accountRegisterService.list(listAccountRegisterParam);
+
+ if (CollectionUtils.isNotEmpty(accountRegisters)) {
+ userAccountAll.addAll(accountRegisters.stream()
+ .map(accountRegister -> {
+ UserAccountResp userAccountResp = new UserAccountResp();
+ userAccountResp.setImAccount(accountRegister.getImAccount());
+ userAccountResp.setUserId(accountRegister.getAccountId());
+ userAccountResp.setAppType(accountRegister.getAppType());
+ userAccountResp.setToken(accountRegister.getToken());
+ userAccountResp.setOuId(accountRegister.getOuId());
+ return userAccountResp;
+ })
+ .collect(Collectors.toList()));
} else {
if (appTypeEnum == AppTypeEnum.SYSTEM) {
//log.warn("PersonId=[" + accountAbsentQuery.getPersonId() + "],不允许创建AppType=[system]账户!");
@@ -332,6 +367,10 @@ public class AccountService {
userAccountReq.setAppType(appTypeEnum.getCode());
userAccountReq.setUserId(accountAbsentQuery.getPersonId());
userAccountReq.setNickName(DEFAULT_NICK_NAME + accountAbsentQuery.getPersonId());
+ // 管理版需要根据ou注册IM账号,做数据隔离
+ if (appTypeEnum == AppTypeEnum.CMP && accountAbsentQuery.getOuId() != null && accountAbsentQuery.getOuId() != 0) {
+ userAccountReq.setOrganizationalUnitId(accountAbsentQuery.getOuId());
+ }
UserAccountResp accountResp = generateAccount(userAccountReq, iNotifyService);
if (StringUtils.isEmpty(accountResp.getToken())) {
continue;
diff --git a/im-center-server/src/main/java/cn/axzo/im/service/MessageHistoryService.java b/im-center-server/src/main/java/cn/axzo/im/service/MessageHistoryService.java
new file mode 100644
index 0000000..5f9f7d8
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/service/MessageHistoryService.java
@@ -0,0 +1,102 @@
+package cn.axzo.im.service;
+
+import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
+import cn.axzo.im.entity.MessageHistory;
+import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO;
+import cn.axzo.pokonyan.dao.page.IPageParam;
+import cn.axzo.pokonyan.dao.wrapper.CriteriaField;
+import cn.axzo.pokonyan.dao.wrapper.Operator;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.springframework.beans.BeanUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public interface MessageHistoryService extends IService {
+
+ Page page(PageMessageHistoryParam param);
+
+ List list(ListMessageHistoryParam param);
+
+ void sendMessage(List messageHistories);
+
+ void createBatch(List messageHistories);
+
+ void updateBatch(List messageHistories);
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class ListMessageHistoryParam {
+
+ @CriteriaField(field = "id", operator = Operator.IN)
+ private List ids;
+
+ @CriteriaField(field = "imMessageTaskId", operator = Operator.EQ)
+ private Long imMessageTaskId;
+
+ @CriteriaField(field = "receivePersonId", operator = Operator.IN)
+ private Set receivePersonIds;
+
+ @CriteriaField(field = "toAccount", operator = Operator.IN)
+ private Set toAccount;
+
+ @CriteriaField(field = "appType", operator = Operator.IN)
+ private Set appTypes;
+
+ @CriteriaField(field = "status", operator = Operator.IN)
+ private Set statues;
+
+ @CriteriaField(ignore = true)
+ private boolean needReceiveOuInfo;
+
+ @CriteriaField(ignore = true)
+ private boolean needReceiveUserInfo;
+ }
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class PageMessageHistoryParam extends ListMessageHistoryParam implements IPageParam {
+ @CriteriaField(ignore = true)
+ Integer page;
+
+ @CriteriaField(ignore = true)
+ Integer pageSize;
+
+ /**
+ * 排序:使用示例,createTime__DESC
+ */
+ @CriteriaField(ignore = true)
+ List sort;
+ }
+
+ @Data
+ @SuperBuilder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class MessageHistoryDTO extends MessageHistory {
+ private PersonProfileDto receivePersonProfile;
+
+ private OrganizationalUnitVO receiveOrganizationalUnit;
+
+ public static MessageHistoryDTO from(MessageHistory messageHistory,
+ Map personProfiles,
+ Map organizationals) {
+ MessageHistoryDTO messageHistoryDTO = MessageHistoryDTO.builder().build();
+ BeanUtils.copyProperties(messageHistory, messageHistoryDTO);
+
+ messageHistoryDTO.setReceivePersonProfile(personProfiles.get(messageHistoryDTO.getReceivePersonId()));
+ messageHistoryDTO.setReceiveOrganizationalUnit(organizationals.get(messageHistoryDTO.getReceiveOuId()));
+ return messageHistoryDTO;
+ }
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/service/MessageService.java b/im-center-server/src/main/java/cn/axzo/im/service/MessageService.java
index bff1137..d0d2692 100644
--- a/im-center-server/src/main/java/cn/axzo/im/service/MessageService.java
+++ b/im-center-server/src/main/java/cn/axzo/im/service/MessageService.java
@@ -5,7 +5,8 @@ import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.im.center.api.vo.req.AccountQuery;
import cn.axzo.im.center.api.vo.req.CustomMessageInfo;
-import cn.axzo.im.center.api.vo.req.MessageInfo;
+import cn.axzo.im.center.api.vo.req.SendCustomMessageParam;
+import cn.axzo.im.center.api.vo.req.SendMessageParam;
import cn.axzo.im.center.api.vo.resp.MessageCustomResp;
import cn.axzo.im.center.api.vo.resp.MessageDispatchResp;
import cn.axzo.im.center.api.vo.resp.UserAccountResp;
@@ -35,6 +36,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cglib.beans.BeanMap;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@@ -105,32 +107,40 @@ public class MessageService {
@Autowired
private Environment environment;
- @Transactional(rollbackFor = Exception.class)
- public List sendMessage(MessageInfo messageInfo) {
- String msgTemplateId = messageInfo.getMsgTemplateId();
- MessageDispatchRequest messageRequest = buildMessageDispatchRequest(messageInfo);
- buildMsgFromAccount(messageRequest, msgTemplateId);
- //设置IM消息发送者账号
- if (messageInfo.getToPersonIdList().size() > msgReceiverLimit) {
- throw new ServiceException("IM消息接收用户数量超过上限:[" + msgReceiverLimit + "]!");
- }
- List messageDispatchRespList;
- log.info("sendMessage发送消息,msgReceiverLimit:{},msgReceiverThreshold:{},msgSendPersonOfOneBatch:{}"
- , msgReceiverLimit, msgReceiverThreshold, msgSendPersonOfOneBatch);
- int personCount = messageInfo.getToPersonIdList().size();
- //小于阈值就走单个IM消息发送接口
- if (personCount <= msgReceiverThreshold) {
- messageDispatchRespList = sendOneByOneMessage(messageInfo, messageRequest);
- } else {
- log.info("sendBatchMessage批量发送消息:" + JSONUtil.toJsonStr(messageInfo));
- messageDispatchRespList = sendBatchMessage(messageInfo, messageRequest);
- }
- List saveRespList = messageDispatchRespList.stream()
- .filter(i -> StringUtils.isBlank(i.getSendFailCause()))
- .collect(toList());
- insertImMessage(saveRespList, messageRequest.getBody());
- return messageDispatchRespList;
- }
+ /**
+ * 使用xxlJob异步发送第三方接口消息
+ * 1、第三方接口有限流
+ * 2、提高接口的性能
+ * @param sendMessageParam
+ * @return
+ */
+// @Transactional(rollbackFor = Exception.class)
+// public List sendMessage(SendMessageParam sendMessageParam) {
+// String msgTemplateId = sendMessageParam.getMsgTemplateId();
+// MessageDispatchRequest messageRequest = buildMessageDispatchRequest(sendMessageParam);
+// buildMsgFromAccount(messageRequest, msgTemplateId);
+// //设置IM消息发送者账号
+// if (sendMessageParam.getToPersonIdList().size() > msgReceiverLimit) {
+// throw new ServiceException("IM消息接收用户数量超过上限:[" + msgReceiverLimit + "]!");
+// }
+// List messageDispatchRespList;
+// log.info("sendMessage发送消息,msgReceiverLimit:{},msgReceiverThreshold:{},msgSendPersonOfOneBatch:{}"
+// , msgReceiverLimit, msgReceiverThreshold, msgSendPersonOfOneBatch);
+// // 异步发送消息,1、同步发送接口性能不好;2、第三方接口有接口限流处理
+// int personCount = sendMessageParam.getToPersonIdList().size();
+// //小于阈值就走单个IM消息发送接口
+// if (personCount <= msgReceiverThreshold) {
+// messageDispatchRespList = sendOneByOneMessage(sendMessageParam, messageRequest);
+// } else {
+// log.info("sendBatchMessage批量发送消息:" + JSONUtil.toJsonStr(sendMessageParam));
+// messageDispatchRespList = sendBatchMessage(sendMessageParam, messageRequest);
+// }
+// List saveRespList = messageDispatchRespList.stream()
+// .filter(i -> StringUtils.isBlank(i.getSendFailCause()))
+// .collect(toList());
+// insertImMessage(saveRespList, messageRequest.getBody());
+// return messageDispatchRespList;
+// }
/**
* 设置IM消息发送者账户,目前只支持机器人账户发送
@@ -171,115 +181,115 @@ public class MessageService {
}
}
- private MessageDispatchRequest buildMessageDispatchRequest(MessageInfo messageInfo) {
- MessageDispatchRequest messageRequest = new MessageDispatchRequest();
- MessageBody messageBody = new MessageBody();
- messageBody.setMsgType(NimMsgTypeEnum.TEMPLATE.getCode());
- messageBody.setMsgContent(messageInfo.getMsgContent());
- messageBody.setMsgHeader(messageInfo.getMsgHeader());
- messageBody.setMsgBody(messageInfo.getMsgTemplateContent());
- Map defaultExtMap = Maps.newHashMap();
- defaultExtMap.put("msgTemplateId", messageInfo.getMsgTemplateId());
- if (messageInfo.getExtendsInfo() != null) {
- defaultExtMap.putAll(messageInfo.getExtendsInfo());
- }
- messageBody.setMessageExtension(defaultExtMap);
- String body = JSONUtil.toJsonStr(messageBody);
- messageRequest.setBody(body);
- return messageRequest;
- }
+// private MessageDispatchRequest buildMessageDispatchRequest(SendMessageParam sendMessageParam) {
+// MessageDispatchRequest messageRequest = new MessageDispatchRequest();
+// MessageBody messageBody = new MessageBody();
+// messageBody.setMsgType(NimMsgTypeEnum.TEMPLATE.getCode());
+// messageBody.setMsgContent(sendMessageParam.getMsgContent());
+// messageBody.setMsgHeader(sendMessageParam.getMsgHeader());
+// messageBody.setMsgBody(sendMessageParam.getMsgTemplateContent());
+// Map defaultExtMap = Maps.newHashMap();
+// defaultExtMap.put("msgTemplateId", sendMessageParam.getMsgTemplateId());
+// if (sendMessageParam.getExt() != null) {
+// defaultExtMap.putAll(BeanMap.create(sendMessageParam.getExt()));
+// }
+// messageBody.setMessageExtension(defaultExtMap);
+// String body = JSONUtil.toJsonStr(messageBody);
+// messageRequest.setBody(body);
+// return messageRequest;
+// }
- private List sendBatchMessage(MessageInfo messageInfo, MessageDispatchRequest messageRequest) {
- //消息模板是针对多App端,则单个用户有多个IM账户,需要分开进行发送消息
- List appTypeList = messageInfo.getAppTypeList();
- String fromAccId = messageRequest.getFrom();
- List messageDispatchRespList = Lists.newArrayList();
- //1.首先自动添加IM账户进行批量发送,返回其中IM账户未注册部分
- //2.如下是默认IM账户规则
- //TODO 批量这里的账户生成判断有问题,生产环境有两种类型的数据123_cm和master123_cm
- //TODO 先去数据库里面查询判断是否有IM账户
- //消息接收者分页,然后进行批量发送
- for (AppTypeEnum appTypeEnum : appTypeList) {
- String appType = appTypeEnum.getCode();
- if (appType == null || AppTypeEnum.isValidAppType(appType) == null) {
- throw new ServiceException("当前服务器不支持该appType类型!");
- }
- Set sourcePersonList = messageInfo.getToPersonIdList();
- List toPersonIMList = Lists.newArrayList();
- HashMap imAccount2PersonId = new HashMap<>();
- for (String sourcePersonId : sourcePersonList) {
- AccountQuery accountQuery = new AccountQuery();
- accountQuery.setAppType(appType);
- accountQuery.setAccountId(sourcePersonId);
- List userAccountRespList = accountService.queryAccountInfo(accountQuery);
- if (CollectionUtils.isNotEmpty(userAccountRespList)) {
- userAccountRespList.forEach(userAccountResp -> {
- if (StringUtils.isNotEmpty(userAccountResp.getImAccount())
- && StringUtils.isNotEmpty(userAccountResp.getToken())) {
- toPersonIMList.add(userAccountResp.getImAccount());
- imAccount2PersonId.put(userAccountResp.getImAccount(), sourcePersonId);
- }
- });
- } else {
- log.warn("发送IM消息异常,不存在personId=[" + sourcePersonId + "]的IM账户");
- MessageDispatchResp resp = new MessageDispatchResp();
- resp.setAppType(appType);
- resp.setTimetag(System.currentTimeMillis());
- resp.setFromImAccount(messageRequest.getFrom());
- resp.setToImAccount(messageRequest.getTo());
- resp.setPersonId(sourcePersonId);
- resp.setSendFailCause("personId=" + sourcePersonId + ", 未注册IM账户");
- messageDispatchRespList.add(resp);
- }
- }
- List> personPage = Lists.partition(toPersonIMList, msgSendPersonOfOneBatch);
- MessageBatchDispatchRequest batchDispatchRequest = new MessageBatchDispatchRequest();
- batchDispatchRequest.setBody(messageRequest.getBody());
- batchDispatchRequest.setFromAccid(fromAccId);
- personPage.forEach(imAccountList -> {
- batchDispatchRequest.setToAccids(imAccountList);
- MessageBatchDispatchResponse batchResponse = imChannel.dispatchBatchMessage(batchDispatchRequest);
- if (batchResponse != null) {
- Map userMsgResponseMap = batchResponse.getMsgids();
- if (userMsgResponseMap != null) {
- //遍历批量返回中每一个账户对应的MessageId
- userMsgResponseMap.forEach((imAccount, messageId) -> {
- String personId = imAccount2PersonId.get(imAccount);
- MessageDispatchResp messageDispatchResp =
- buildMessageDispatchResp(String.valueOf(messageId), fromAccId,
- imAccount, personId, appType, batchResponse.getTimetag(), null);
- messageDispatchRespList.add(messageDispatchResp);
- });
- } else if (StringUtils.isNotBlank(batchResponse.getDesc())) {
- for (String imAccount : imAccountList) {
- String personId = imAccount2PersonId.get(imAccount);
- MessageDispatchResp messageDispatchResp =
- buildMessageDispatchResp(null, fromAccId,
- imAccount, personId, appType, batchResponse.getTimetag(), batchResponse.getDesc());
- messageDispatchRespList.add(messageDispatchResp);
- }
- } else {
- log.error("dispatchBatchMessage请求返回出现异常:{}", JSONUtil.toJsonStr(batchResponse));
- }
- //返回未注册的IM账户
- Set unregisterAccountSets = batchResponse.getUnregister();
- //字符串转义字符处理
- if (CollectionUtils.isNotEmpty(unregisterAccountSets)) {
- unregisterAccountSets = unregisterAccountSets.stream()
- .map(account -> account.replace("\"", "")).collect(Collectors.toSet());
- unregisterAccountSets.forEach(unregisterAccount -> {
- MessageDispatchResp messageDispatchResp = buildMessageDispatchResp(null, fromAccId,
- unregisterAccount, null, appType, batchResponse.getTimetag(), null);
- });
- }
- } else {
- log.error("dispatchBatchMessage请求返回出现异常");
- }
-
- });
- }
- return messageDispatchRespList;
- }
+// private List sendBatchMessage(SendMessageParam sendMessageParam, MessageDispatchRequest messageRequest) {
+// //消息模板是针对多App端,则单个用户有多个IM账户,需要分开进行发送消息
+// List appTypeList = sendMessageParam.getAppTypeList();
+// String fromAccId = messageRequest.getFrom();
+// List messageDispatchRespList = Lists.newArrayList();
+// //1.首先自动添加IM账户进行批量发送,返回其中IM账户未注册部分
+// //2.如下是默认IM账户规则
+// //TODO 批量这里的账户生成判断有问题,生产环境有两种类型的数据123_cm和master123_cm
+// //TODO 先去数据库里面查询判断是否有IM账户
+// //消息接收者分页,然后进行批量发送
+// for (AppTypeEnum appTypeEnum : appTypeList) {
+// String appType = appTypeEnum.getCode();
+// if (appType == null || AppTypeEnum.isValidAppType(appType) == null) {
+// throw new ServiceException("当前服务器不支持该appType类型!");
+// }
+// Set sourcePersonList = sendMessageParam.getToPersonIdList();
+// List toPersonIMList = Lists.newArrayList();
+// HashMap imAccount2PersonId = new HashMap<>();
+// for (String sourcePersonId : sourcePersonList) {
+// AccountQuery accountQuery = new AccountQuery();
+// accountQuery.setAppType(appType);
+// accountQuery.setAccountId(sourcePersonId);
+// List userAccountRespList = accountService.queryAccountInfo(accountQuery);
+// if (CollectionUtils.isNotEmpty(userAccountRespList)) {
+// userAccountRespList.forEach(userAccountResp -> {
+// if (StringUtils.isNotEmpty(userAccountResp.getImAccount())
+// && StringUtils.isNotEmpty(userAccountResp.getToken())) {
+// toPersonIMList.add(userAccountResp.getImAccount());
+// imAccount2PersonId.put(userAccountResp.getImAccount(), sourcePersonId);
+// }
+// });
+// } else {
+// log.warn("发送IM消息异常,不存在personId=[" + sourcePersonId + "]的IM账户");
+// MessageDispatchResp resp = new MessageDispatchResp();
+// resp.setAppType(appType);
+// resp.setTimetag(System.currentTimeMillis());
+// resp.setFromImAccount(messageRequest.getFrom());
+// resp.setToImAccount(messageRequest.getTo());
+// resp.setPersonId(sourcePersonId);
+// resp.setSendFailCause("personId=" + sourcePersonId + ", 未注册IM账户");
+// messageDispatchRespList.add(resp);
+// }
+// }
+// List> personPage = Lists.partition(toPersonIMList, msgSendPersonOfOneBatch);
+// MessageBatchDispatchRequest batchDispatchRequest = new MessageBatchDispatchRequest();
+// batchDispatchRequest.setBody(messageRequest.getBody());
+// batchDispatchRequest.setFromAccid(fromAccId);
+// personPage.forEach(imAccountList -> {
+// batchDispatchRequest.setToAccids(imAccountList);
+// MessageBatchDispatchResponse batchResponse = imChannel.dispatchBatchMessage(batchDispatchRequest);
+// if (batchResponse != null) {
+// Map userMsgResponseMap = batchResponse.getMsgids();
+// if (userMsgResponseMap != null) {
+// //遍历批量返回中每一个账户对应的MessageId
+// userMsgResponseMap.forEach((imAccount, messageId) -> {
+// String personId = imAccount2PersonId.get(imAccount);
+// MessageDispatchResp messageDispatchResp =
+// buildMessageDispatchResp(String.valueOf(messageId), fromAccId,
+// imAccount, personId, appType, batchResponse.getTimetag(), null);
+// messageDispatchRespList.add(messageDispatchResp);
+// });
+// } else if (StringUtils.isNotBlank(batchResponse.getDesc())) {
+// for (String imAccount : imAccountList) {
+// String personId = imAccount2PersonId.get(imAccount);
+// MessageDispatchResp messageDispatchResp =
+// buildMessageDispatchResp(null, fromAccId,
+// imAccount, personId, appType, batchResponse.getTimetag(), batchResponse.getDesc());
+// messageDispatchRespList.add(messageDispatchResp);
+// }
+// } else {
+// log.error("dispatchBatchMessage请求返回出现异常:{}", JSONUtil.toJsonStr(batchResponse));
+// }
+// //返回未注册的IM账户
+// Set unregisterAccountSets = batchResponse.getUnregister();
+// //字符串转义字符处理
+// if (CollectionUtils.isNotEmpty(unregisterAccountSets)) {
+// unregisterAccountSets = unregisterAccountSets.stream()
+// .map(account -> account.replace("\"", "")).collect(Collectors.toSet());
+// unregisterAccountSets.forEach(unregisterAccount -> {
+// MessageDispatchResp messageDispatchResp = buildMessageDispatchResp(null, fromAccId,
+// unregisterAccount, null, appType, batchResponse.getTimetag(), null);
+// });
+// }
+// } else {
+// log.error("dispatchBatchMessage请求返回出现异常");
+// }
+//
+// });
+// }
+// return messageDispatchRespList;
+// }
private MessageDispatchResp buildMessageDispatchResp(String messageId, String fromImAccount, String toImAccount, String personId,
String appType, Long timeTag, String desc) {
@@ -304,62 +314,62 @@ public class MessageService {
return messageDispatchResp;
}
- private List sendOneByOneMessage(MessageInfo messageInfo, MessageDispatchRequest messageRequest) {
- //如果消息模板是针对多App端,则分开进行发送消息
- List appTypeList = messageInfo.getAppTypeList();
- List messageDispatchRespList = Lists.newArrayList();
- appTypeList.forEach(appType -> {
- if (appType == null || AppTypeEnum.isValidAppType(appType.getCode()) == null) {
- throw new ServiceException("当前服务器不支持该appType类型!");
- }
- List toPersonList = Lists.newArrayList(messageInfo.getToPersonIdList());
- //进行接收用户IM账户校验 目前支持单个用户进行IM消息发送,多个IM用户进行消息接收
- for (String personId : toPersonList) {
- List accountRegisterList = accountRegisterDao.lambdaQuery().eq(AccountRegister::getIsDelete, 0)
- .eq(AccountRegister::getAccountId, personId)
- .eq(AccountRegister::getAppKey, imChannel.getProviderAppKey())
- .isNotNull(AccountRegister::getToken)
- .eq(AccountRegister::getAppType, appType.getCode()).list();
- if (CollectionUtils.isEmpty(accountRegisterList)) {
- //返回未注册的IM账户信息
- MessageDispatchResp messageDispatchResp = buildMessageDispatchResp(null,
- messageRequest.getFrom(), null,
- personId, appType.getCode(), 0L, "unregistered");
- log.warn("用户personId=[" + personId + "],appType[" + appType.getCode() + "],未注册IM账户,不进行消息发送!");
- MessageDispatchResp resp = new MessageDispatchResp();
- resp.setAppType(appType.getCode());
- resp.setTimetag(System.currentTimeMillis());
- resp.setFromImAccount(messageRequest.getFrom());
- resp.setToImAccount(messageRequest.getTo());
- resp.setPersonId(personId);
- resp.setSendFailCause("personId=" + personId + ",未注册IM账户");
- messageDispatchRespList.add(resp);
- continue;
- }
- accountRegisterList.forEach(accountRegister -> {
- if (StringUtils.isNotEmpty(accountRegister.getImAccount()) &&
- StringUtils.isNotEmpty(accountRegister.getToken())) {
- messageRequest.setTo(accountRegister.getImAccount());
- messageRequest.setType(ChannelMsgTypeEnum.CUSTOM.getCode());
- MessageDispatchResponse response = imChannel.dispatchMessage(messageRequest);
- MessageDispatchResp messageDispatchResp = BeanMapper.map(response.getData(), MessageDispatchResp.class);
- if (messageDispatchResp == null) {
- messageDispatchResp = new MessageDispatchResp();
- }
- if (StringUtils.isNotBlank(response.getDesc())) {
- messageDispatchResp.setDesc(response.getDesc());
- }
- messageDispatchResp.setAppType(appType.getCode());
- messageDispatchResp.setFromImAccount(messageRequest.getFrom());
- messageDispatchResp.setToImAccount(accountRegister.getImAccount());
- messageDispatchResp.setPersonId(personId);
- messageDispatchRespList.add(messageDispatchResp);
- }
- });
- }
- });
- return messageDispatchRespList;
- }
+// private List sendOneByOneMessage(SendMessageParam sendMessageParam, MessageDispatchRequest messageRequest) {
+// //如果消息模板是针对多App端,则分开进行发送消息
+// List appTypeList = sendMessageParam.getAppTypeList();
+// List messageDispatchRespList = Lists.newArrayList();
+// appTypeList.forEach(appType -> {
+// if (appType == null || AppTypeEnum.isValidAppType(appType.getCode()) == null) {
+// throw new ServiceException("当前服务器不支持该appType类型!");
+// }
+// List toPersonList = Lists.newArrayList(sendMessageParam.getToPersonIdList());
+// //进行接收用户IM账户校验 目前支持单个用户进行IM消息发送,多个IM用户进行消息接收
+// for (String personId : toPersonList) {
+// List accountRegisterList = accountRegisterDao.lambdaQuery().eq(AccountRegister::getIsDelete, 0)
+// .eq(AccountRegister::getAccountId, personId)
+// .eq(AccountRegister::getAppKey, imChannel.getProviderAppKey())
+// .isNotNull(AccountRegister::getToken)
+// .eq(AccountRegister::getAppType, appType.getCode()).list();
+// if (CollectionUtils.isEmpty(accountRegisterList)) {
+// //返回未注册的IM账户信息
+// MessageDispatchResp messageDispatchResp = buildMessageDispatchResp(null,
+// messageRequest.getFrom(), null,
+// personId, appType.getCode(), 0L, "unregistered");
+// log.warn("用户personId=[" + personId + "],appType[" + appType.getCode() + "],未注册IM账户,不进行消息发送!");
+// MessageDispatchResp resp = new MessageDispatchResp();
+// resp.setAppType(appType.getCode());
+// resp.setTimetag(System.currentTimeMillis());
+// resp.setFromImAccount(messageRequest.getFrom());
+// resp.setToImAccount(messageRequest.getTo());
+// resp.setPersonId(personId);
+// resp.setSendFailCause("personId=" + personId + ",未注册IM账户");
+// messageDispatchRespList.add(resp);
+// continue;
+// }
+// accountRegisterList.forEach(accountRegister -> {
+// if (StringUtils.isNotEmpty(accountRegister.getImAccount()) &&
+// StringUtils.isNotEmpty(accountRegister.getToken())) {
+// messageRequest.setTo(accountRegister.getImAccount());
+// messageRequest.setType(ChannelMsgTypeEnum.CUSTOM.getCode());
+// MessageDispatchResponse response = imChannel.dispatchMessage(messageRequest);
+// MessageDispatchResp messageDispatchResp = BeanMapper.map(response.getData(), MessageDispatchResp.class);
+// if (messageDispatchResp == null) {
+// messageDispatchResp = new MessageDispatchResp();
+// }
+// if (StringUtils.isNotBlank(response.getDesc())) {
+// messageDispatchResp.setDesc(response.getDesc());
+// }
+// messageDispatchResp.setAppType(appType.getCode());
+// messageDispatchResp.setFromImAccount(messageRequest.getFrom());
+// messageDispatchResp.setToImAccount(accountRegister.getImAccount());
+// messageDispatchResp.setPersonId(personId);
+// messageDispatchRespList.add(messageDispatchResp);
+// }
+// });
+// }
+// });
+// return messageDispatchRespList;
+// }
private void insertImMessage(List messageRespList, String messageBody) {
if (CollectionUtils.isEmpty(messageRespList)) {
@@ -487,7 +497,7 @@ public class MessageService {
}
public static MessageCustomBody wrapperCustomMessage(String toImAccount,
- CustomMessageInfo customMessage) {
+ CustomMessageInfo customMessage) {
return MessageCustomBody.builder()
.toImAccount(toImAccount)
.personId(customMessage.getToPersonId())
diff --git a/im-center-server/src/main/java/cn/axzo/im/service/MessageTaskService.java b/im-center-server/src/main/java/cn/axzo/im/service/MessageTaskService.java
new file mode 100644
index 0000000..a55fb71
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/service/MessageTaskService.java
@@ -0,0 +1,85 @@
+package cn.axzo.im.service;
+
+import cn.axzo.im.entity.MessageHistory;
+import cn.axzo.im.entity.MessageTask;
+import cn.axzo.pokonyan.dao.page.IPageParam;
+import cn.axzo.pokonyan.dao.wrapper.CriteriaField;
+import cn.axzo.pokonyan.dao.wrapper.Operator;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+import java.util.Date;
+import java.util.List;
+
+public interface MessageTaskService extends IService {
+
+ MessageTask create(MessageTask param);
+
+ Page page(PageMessageTaskParam param);
+
+ void createMessageHistory(MessageTask messageTask);
+
+ void update(UpdateMessageTaskParam param);
+
+ @Builder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class UpdateMessageTaskParam {
+
+ private Long id;
+
+ private MessageTask.ActionEnum action;
+
+ private Date startedTime;
+
+ private Date finishedTime;
+
+ public MessageTask to() {
+ return MessageTask.builder()
+ .id(this.getId())
+ .startedTime(this.getStartedTime())
+ .finishedTime(this.getFinishedTime())
+ .build();
+ }
+
+ }
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class ListMessageTaskParam {
+
+ @CriteriaField(field = "id", operator = Operator.IN)
+ private List ids;
+
+ @CriteriaField(field = "planStartTime", operator = Operator.LE)
+ private Date planStartTimeLE;
+
+ @CriteriaField(field = "status", operator = Operator.EQ)
+ private MessageTask.Status status;
+ }
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class PageMessageTaskParam extends ListMessageTaskParam implements IPageParam {
+ @CriteriaField(ignore = true)
+ Integer page;
+
+ @CriteriaField(ignore = true)
+ Integer pageSize;
+
+ /**
+ * 排序:使用示例,createTime__DESC
+ */
+ @CriteriaField(ignore = true)
+ List sort;
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/service/RobotInfoService.java b/im-center-server/src/main/java/cn/axzo/im/service/RobotInfoService.java
index 1210dcf..87e02d3 100644
--- a/im-center-server/src/main/java/cn/axzo/im/service/RobotInfoService.java
+++ b/im-center-server/src/main/java/cn/axzo/im/service/RobotInfoService.java
@@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@@ -153,7 +154,14 @@ public class RobotInfoService {
}
public PageResp queryRobotInfoList(RobotPageQuery robotInfoQuery) {
- IPage robotInfoPage = robotInfoDao.queryRobotInfoOfPage(robotInfoQuery);
+
+ List robotIds = resolveRobotIds(robotInfoQuery);
+
+ if (StringUtils.isNotBlank(robotInfoQuery.getMsgTemplateCode()) && CollectionUtils.isEmpty(robotIds)) {
+ return PageResp.zero(robotInfoQuery.getPage(), robotInfoQuery.getPageSize());
+ }
+
+ IPage robotInfoPage = robotInfoDao.queryRobotInfoOfPage(robotInfoQuery, robotIds);
List robotInfoRespList = BeanMapper.copyList(robotInfoPage.getRecords(), RobotInfoResp.class);
PageResp pageOfRobotInfoResp = PageResp.list(robotInfoPage.getCurrent(), robotInfoPage.getSize(),
robotInfoPage.getTotal(), robotInfoRespList);
@@ -178,6 +186,14 @@ public class RobotInfoService {
return pageOfRobotInfoResp;
}
+ private List resolveRobotIds(RobotPageQuery robotInfoQuery) {
+ if (StringUtils.isBlank(robotInfoQuery.getMsgTemplateCode())) {
+ return Collections.emptyList();
+ }
+ // todo 这里查询也需要优化,现在是把所有数据查询出来过滤的,数据量过大的情况下会有性能和内存影响
+ return templateService.queryRobotIdByTemplate(robotInfoQuery.getMsgTemplateCode());
+ }
+
public List queryRunningRobotList() {
List runningRobots = robotInfoDao.queryRunningRobotList();
if (CollectionUtils.isEmpty(runningRobots)) {
diff --git a/im-center-server/src/main/java/cn/axzo/im/service/RobotInfoV2Service.java b/im-center-server/src/main/java/cn/axzo/im/service/RobotInfoV2Service.java
new file mode 100644
index 0000000..74ffe5f
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/service/RobotInfoV2Service.java
@@ -0,0 +1,89 @@
+package cn.axzo.im.service;
+
+import cn.axzo.im.center.api.vo.resp.RobotTagResp;
+import cn.axzo.im.center.common.enums.RobotStatusEnum;
+import cn.axzo.im.entity.RobotInfo;
+import cn.axzo.pokonyan.dao.page.IPageParam;
+import cn.axzo.pokonyan.dao.wrapper.CriteriaField;
+import cn.axzo.pokonyan.dao.wrapper.Operator;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import org.springframework.beans.BeanUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public interface RobotInfoV2Service extends IService {
+
+ Page page(PageRobotInfoParam param);
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class ListRobotInfoParam {
+
+ @CriteriaField(field = "nickName", operator = Operator.LIKE)
+ private String nickNameLike;
+
+ @CriteriaField(field = "status", operator = Operator.EQ)
+ private RobotStatusEnum status;
+
+ @CriteriaField(field = "imAccount", operator = Operator.IN)
+ private List imAccounts;
+
+ @CriteriaField(ignore = true)
+ private boolean needRobotTag;
+ }
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class PageRobotInfoParam extends ListRobotInfoParam implements IPageParam {
+ @CriteriaField(ignore = true)
+ Integer pageNumber;
+
+ @CriteriaField(ignore = true)
+ Integer pageSize;
+
+ /**
+ * 排序:使用示例,createTime__DESC
+ */
+ @CriteriaField(ignore = true)
+ List sort;
+ }
+
+ @SuperBuilder
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ class RobotInfoDTO extends RobotInfo {
+
+ private List robotTags;
+
+ public static RobotInfoDTO from(RobotInfo robotInfo,
+ Map robotTags) {
+ RobotInfoDTO robotInfoDTO = RobotInfoDTO.builder().build();
+ BeanUtils.copyProperties(robotInfo, robotInfoDTO);
+
+
+ List robotTagResps = Optional.ofNullable(robotInfoDTO.getTagNameList())
+ .map(tagIds ->
+ tagIds.stream()
+ .map(robotTags::get)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList())
+ ).orElse(null);
+ robotInfoDTO.setRobotTags(robotTagResps);
+ return robotInfoDTO;
+ }
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/service/impl/AccountRegisterServiceImpl.java b/im-center-server/src/main/java/cn/axzo/im/service/impl/AccountRegisterServiceImpl.java
new file mode 100644
index 0000000..eff3e39
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/service/impl/AccountRegisterServiceImpl.java
@@ -0,0 +1,118 @@
+package cn.axzo.im.service.impl;
+
+import cn.axzo.basics.profiles.api.UserProfileServiceApi;
+import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
+import cn.axzo.im.center.common.enums.AccountTypeEnum;
+import cn.axzo.im.channel.IMChannelProvider;
+import cn.axzo.im.dao.mapper.AccountRegisterMapper;
+import cn.axzo.im.entity.AccountRegister;
+import cn.axzo.im.service.AccountRegisterService;
+import cn.axzo.maokai.api.client.OrganizationalUnitApi;
+import cn.axzo.maokai.api.vo.request.OrganizationalUnitQuery;
+import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO;
+import cn.axzo.pokonyan.dao.converter.PageConverter;
+import cn.axzo.pokonyan.dao.mysql.QueryWrapperHelper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class AccountRegisterServiceImpl extends ServiceImpl
+ implements AccountRegisterService {
+
+ @Autowired
+ private IMChannelProvider imChannelProvider;
+ @Autowired
+ private OrganizationalUnitApi organizationalUnitApi;
+ @Autowired
+ private UserProfileServiceApi userProfileServiceApi;
+
+ @Override
+ public Page page(PageAccountRegisterParam param) {
+ QueryWrapper wrapper = QueryWrapperHelper.fromBean(param, AccountRegister.class);
+ // 只能取配置的appKey的数据,防止接口使用方没传appKey导致获取错乱的数据
+ wrapper.eq("app_key", imChannelProvider.getProviderAppKey());
+ wrapper.eq("is_delete", 0);
+
+ Page page = this.page(PageConverter.convertToMybatis(param, AccountRegister.class), wrapper);
+
+ Map personProfiles = listUserPersonProfile(param, page.getRecords());
+
+ Map organizationals = listOrganizational(param, page.getRecords());
+
+ return PageConverter.convert(page, (record) -> AccountRegisterDTO.from(record,
+ personProfiles,
+ organizationals));
+ }
+
+ @Override
+ public List list(ListAccountRegisterParam param) {
+ return PageConverter.drainAll(pageNumber -> {
+ PageAccountRegisterParam pageParam = PageAccountRegisterParam.builder().build();
+ BeanUtils.copyProperties(param, pageParam);
+ pageParam.setPage(pageNumber);
+ pageParam.setPageSize(500);
+ return page(pageParam);
+ });
+ }
+
+ private Map listUserPersonProfile(PageAccountRegisterParam param,
+ List accountRegisters) {
+ if (CollectionUtils.isEmpty(accountRegisters) || BooleanUtils.isNotTrue(param.isNeedUserInfo())) {
+ return Collections.emptyMap();
+ }
+
+ List personIds = accountRegisters.stream()
+ .filter(e -> Objects.equals(e.getAccountType(), AccountTypeEnum.USER.getCode()))
+ .map(AccountRegister::getAccountId)
+ .filter(Objects::nonNull)
+ .filter(StringUtils::isNumeric)
+ .map(Long::valueOf)
+ .distinct()
+ .collect(Collectors.toList());
+ if (CollectionUtils.isEmpty(personIds)) {
+ return Collections.emptyMap();
+ }
+
+ return userProfileServiceApi.postPersonProfiles(personIds).getData()
+ .stream()
+ .collect(Collectors.toMap(e -> e.getId().toString(), Function.identity()));
+ }
+
+ private Map listOrganizational(PageAccountRegisterParam param,
+ List accountRegisters) {
+ if (CollectionUtils.isEmpty(accountRegisters) || BooleanUtils.isNotTrue(param.isNeedOuInfo())) {
+ return Collections.emptyMap();
+ }
+
+ List ouIds = accountRegisters.stream()
+ .map(AccountRegister::getOuId)
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toList());
+ if (CollectionUtils.isEmpty(ouIds)) {
+ return Collections.emptyMap();
+ }
+
+ return organizationalUnitApi.page(OrganizationalUnitQuery.builder().unitIds(ouIds).build())
+ .getData()
+ .getList()
+ .stream()
+ .collect(Collectors.toMap(OrganizationalUnitVO::getId, Function.identity(), (f, s) -> f));
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/service/impl/MessageHistoryServiceImpl.java b/im-center-server/src/main/java/cn/axzo/im/service/impl/MessageHistoryServiceImpl.java
new file mode 100644
index 0000000..9841be7
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/service/impl/MessageHistoryServiceImpl.java
@@ -0,0 +1,278 @@
+package cn.axzo.im.service.impl;
+
+import cn.axzo.basics.profiles.api.UserProfileServiceApi;
+import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
+import cn.axzo.framework.rocketmq.Event;
+import cn.axzo.im.channel.IMChannelProvider;
+import cn.axzo.im.channel.netease.dto.MessageBatchDispatchRequest;
+import cn.axzo.im.channel.netease.dto.MessageBatchDispatchResponse;
+import cn.axzo.im.config.MqProducer;
+import cn.axzo.im.dao.mapper.MessageHistoryMapper;
+import cn.axzo.im.entity.MessageHistory;
+import cn.axzo.im.event.payload.MessageHistoryCreatedPayload;
+import cn.axzo.im.event.payload.MessageHistoryUpdatedPayload;
+import cn.axzo.im.service.MessageHistoryService;
+import cn.axzo.maokai.api.client.OrganizationalUnitApi;
+import cn.axzo.maokai.api.vo.request.OrganizationalUnitQuery;
+import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO;
+import cn.axzo.pokonyan.client.RateLimiter;
+import cn.axzo.pokonyan.client.RateLimiterClient;
+import cn.axzo.pokonyan.dao.converter.PageConverter;
+import cn.axzo.pokonyan.dao.mysql.QueryWrapperHelper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static cn.axzo.im.config.BizResultCode.ACQUIRE_RATE_LIMITER_FAIL;
+import static cn.axzo.im.event.inner.EventTypeEnum.MESSAGE_HISTORY_CREATED;
+import static cn.axzo.im.event.inner.EventTypeEnum.MESSAGE_HISTORY_UPDATED;
+
+@Slf4j
+@Service
+public class MessageHistoryServiceImpl extends ServiceImpl
+ implements MessageHistoryService, InitializingBean {
+
+ @Autowired
+ private OrganizationalUnitApi organizationalUnitApi;
+ @Autowired
+ private UserProfileServiceApi userProfileServiceApi;
+ @Autowired
+ private RateLimiterClient rateLimiterClient;
+ @Autowired
+ private IMChannelProvider imChannelProvider;
+ @Autowired
+ private MqProducer mqProducer;
+
+ @Value("${send.message.limiter.permits}")
+ private int permits;
+
+ @Value("${send.message.limiter.seconds}")
+ private long seconds;
+
+ /**
+ * 网易云信IM批量发送-每批次发送给多少用户
+ */
+ @Value("${im-center.message.batch.receiver.once:10}")
+ public int msgSendPersonOfOneBatch;
+
+ private RateLimiter rateLimiter;
+
+ private static final String LIMITER_KEY = "im-center:sendMessage";
+
+ /**
+ * 默认超时时间(毫秒)
+ */
+ private static final long DEFAULT_TIME_OUT_MILLIS = 60 * 1000;
+
+ private static final String TARGET_TYPE = "messageHistoryId";
+
+ @Override
+ public Page page(PageMessageHistoryParam param) {
+ QueryWrapper wrapper = QueryWrapperHelper.fromBean(param, MessageHistory.class);
+ // 只能取配置的appKey的数据,防止接口使用方没传appKey导致获取错乱的数据
+ wrapper.eq("is_delete", 0);
+
+ Page page = this.page(PageConverter.convertToMybatis(param, MessageHistory.class), wrapper);
+
+ Map personProfiles = listReceiveUserPersonProfile(param, page.getRecords());
+
+ Map organizationals = listReceiveOrganizational(param, page.getRecords());
+
+ return PageConverter.convert(page, (record) -> MessageHistoryDTO.from(record,
+ personProfiles,
+ organizationals));
+ }
+
+ @Override
+ public List list(ListMessageHistoryParam param) {
+ return PageConverter.drainAll(pageNumber -> {
+ PageMessageHistoryParam pageParam = PageMessageHistoryParam.builder().build();
+ BeanUtils.copyProperties(param, pageParam);
+ pageParam.setPage(pageNumber);
+ pageParam.setPageSize(500);
+ return page(pageParam);
+ });
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void createBatch(List messageHistories) {
+
+ this.saveBatch(messageHistories);
+
+ List events = this.listByIds(Lists.transform(messageHistories, MessageHistory::getId))
+ .stream()
+ .map(messageHistory ->
+ Event.builder()
+ .targetId(String.valueOf(messageHistory.getId()))
+ .targetType(TARGET_TYPE)
+ .eventCode(MESSAGE_HISTORY_CREATED.getEventCode())
+ .data(MessageHistoryCreatedPayload.builder()
+ .messageHistory(messageHistory)
+ .build())
+ .build())
+ .collect(Collectors.toList());
+ mqProducer.sendBatch(events);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateBatch(List messageHistories) {
+
+ Map oldMessageHistories = this.listByIds(Lists.transform(messageHistories, MessageHistory::getId))
+ .stream()
+ .collect(Collectors.toMap(MessageHistory::getId, Function.identity()));
+
+ this.updateBatchById(messageHistories);
+
+ List events = this.listByIds(Lists.transform(messageHistories, MessageHistory::getId))
+ .stream()
+ .map(messageHistory -> Event.builder()
+ .targetId(String.valueOf(messageHistory.getId()))
+ .targetType(TARGET_TYPE)
+ .eventCode(MESSAGE_HISTORY_UPDATED.getEventCode())
+ .data(MessageHistoryUpdatedPayload.builder()
+ .newMessageHistory(messageHistory)
+ .oldMessageHistory(oldMessageHistories.get(messageHistory.getId()))
+ .build())
+ .build())
+ .collect(Collectors.toList());
+ mqProducer.sendBatch(events);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void sendMessage(List messageHistories) {
+
+ if (CollectionUtils.isEmpty(messageHistories)) {
+ log.info("发送的消息记录为空");
+ return;
+ }
+
+ // 三方接口调用频率120次/分,超限将限制1分钟使用,所以抛出异常,等待下一次xxlJob做重试补偿
+ if (!rateLimiter.tryAcquire(DEFAULT_TIME_OUT_MILLIS)) {
+ log.info("未获得令牌");
+ throw ACQUIRE_RATE_LIMITER_FAIL.toException();
+ }
+ log.info("获得令牌");
+ MessageHistoryDTO messageHistoryDTO = messageHistories.stream().findFirst().get();
+ MessageBatchDispatchRequest batchDispatchRequest = new MessageBatchDispatchRequest();
+ batchDispatchRequest.setBody(messageHistoryDTO.getMessageBody());
+ batchDispatchRequest.setFromAccid(messageHistoryDTO.getFromAccount());
+ batchDispatchRequest.setToAccids(Lists.transform(messageHistories, MessageHistoryService.MessageHistoryDTO::getToAccount));
+ MessageBatchDispatchResponse response = imChannelProvider.dispatchBatchMessage(batchDispatchRequest);
+
+ if (response.isSuccess()) {
+ // 发送成功的IMAccountId -> msgId
+ Map msgids = response.getMsgids();
+ // unregister的账号
+ Set unregister = Optional.ofNullable(response.getUnregister())
+ .orElseGet(Sets::newHashSet);
+
+ List updateMessageHistories = messageHistories.stream()
+ .map(e -> {
+ MessageHistory messageHistory = MessageHistory.builder()
+ .id(e.getId())
+ .build();
+ if (unregister.contains(e.getToAccount())) {
+ messageHistory.setStatus(MessageHistory.Status.FAILED);
+ messageHistory.setResult("IM账号未在网易云信注册");
+ } else {
+ messageHistory.setStatus(MessageHistory.Status.SUCCEED);
+ messageHistory.setMessageId(msgids.get(e.getToAccount()).toString());
+ }
+ return messageHistory;
+ })
+ .collect(Collectors.toList());
+ this.updateBatch(updateMessageHistories);
+ return;
+ }
+
+ List failedMessageHistories = messageHistories.stream()
+ .map(e -> MessageHistory.builder()
+ .id(e.getId())
+ .result(response.getDesc())
+ .status(MessageHistory.Status.FAILED)
+ .build())
+ .collect(Collectors.toList());
+ this.updateBatch(failedMessageHistories);
+ }
+
+
+ private Map listReceiveUserPersonProfile(PageMessageHistoryParam param,
+ List messageHistories) {
+ if (CollectionUtils.isEmpty(messageHistories) || BooleanUtils.isNotTrue(param.isNeedReceiveUserInfo())) {
+ return Collections.emptyMap();
+ }
+
+ List personIds = messageHistories.stream()
+ .map(MessageHistory::getReceivePersonId)
+ .filter(StringUtils::isNumeric)
+ .map(Long::valueOf)
+ .distinct()
+ .collect(Collectors.toList());
+ if (CollectionUtils.isEmpty(personIds)) {
+ return Collections.emptyMap();
+ }
+
+ return userProfileServiceApi.postPersonProfiles(personIds).getData()
+ .stream()
+ .collect(Collectors.toMap(e -> e.getId().toString(), Function.identity()));
+ }
+
+ private Map listReceiveOrganizational(PageMessageHistoryParam param,
+ List messageHistories) {
+ if (CollectionUtils.isEmpty(messageHistories) || BooleanUtils.isNotTrue(param.isNeedReceiveOuInfo())) {
+ return Collections.emptyMap();
+ }
+
+ List ouIds = messageHistories.stream()
+ .map(MessageHistory::getReceiveOuId)
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toList());
+ if (CollectionUtils.isEmpty(ouIds)) {
+ return Collections.emptyMap();
+ }
+
+ return organizationalUnitApi.page(OrganizationalUnitQuery.builder().unitIds(ouIds).build())
+ .getData()
+ .getList()
+ .stream()
+ .collect(Collectors.toMap(OrganizationalUnitVO::getId, Function.identity(), (f, s) -> f));
+ }
+
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ rateLimiter = rateLimiterClient.build(RateLimiterClient.RateLimiterReq.builder()
+ .windowType(RateLimiter.WindowType.SLIDING)
+ .limiterKey(LIMITER_KEY)
+ .rule(RateLimiter.LimitRule.builder()
+ .permits(permits)
+ .seconds(seconds)
+ .build())
+ .build());
+ }
+}
diff --git a/im-center-server/src/main/java/cn/axzo/im/service/impl/MessageTaskServiceImpl.java b/im-center-server/src/main/java/cn/axzo/im/service/impl/MessageTaskServiceImpl.java
new file mode 100644
index 0000000..5d44e5e
--- /dev/null
+++ b/im-center-server/src/main/java/cn/axzo/im/service/impl/MessageTaskServiceImpl.java
@@ -0,0 +1,389 @@
+package cn.axzo.im.service.impl;
+
+import cn.axzo.framework.domain.web.result.ApiResult;
+import cn.axzo.im.center.api.vo.req.SendMessageParam;
+import cn.axzo.im.center.common.enums.AccountTypeEnum;
+import cn.axzo.im.center.common.enums.AppTypeEnum;
+import cn.axzo.im.channel.IMChannelProvider;
+import cn.axzo.im.channel.netease.NimMsgTypeEnum;
+import cn.axzo.im.channel.netease.dto.MessageBody;
+import cn.axzo.im.dao.mapper.MessageTaskMapper;
+import cn.axzo.im.entity.AccountRegister;
+import cn.axzo.im.entity.MessageHistory;
+import cn.axzo.im.entity.MessageTask;
+import cn.axzo.im.service.AccountRegisterService;
+import cn.axzo.im.service.MessageHistoryService;
+import cn.axzo.im.service.MessageTaskService;
+import cn.axzo.maokai.api.client.OrganizationalTeamOuRelationApi;
+import cn.axzo.maokai.api.vo.request.OrganizationalTeamOuRelationReq;
+import cn.axzo.maokai.api.vo.response.OrganizationalTeamOuRelationResp;
+import cn.axzo.pokonyan.dao.converter.PageConverter;
+import cn.axzo.pokonyan.dao.mysql.QueryWrapperHelper;
+import cn.axzo.pokonyan.exception.Aassert;
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static cn.axzo.im.config.BizResultCode.MESSAGE_TASK_NOT_FOUND;
+
+@Slf4j
+@Service
+public class MessageTaskServiceImpl extends ServiceImpl
+ implements MessageTaskService {
+
+ @Autowired
+ private IMChannelProvider imChannelProvider;
+ @Autowired
+ private AccountRegisterService accountRegisterService;
+ @Autowired
+ private MessageHistoryService messageHistoryService;
+ @Autowired
+ private OrganizationalTeamOuRelationApi organizationalTeamOuRelationApi;
+
+ private static final Integer DEFAULT_PAGE_SIZE = 500;
+
+ @Override
+ @Transactional
+ public MessageTask create(MessageTask param) {
+ // 未设置计划执行时间,则计划时间为当前时间,xxlJob可立即执行
+ if (param.getPlanStartTime() == null) {
+ param.setPlanStartTime(new Date());
+ }
+
+ this.save(param);
+ return this.getById(param.getId());
+ }
+
+ @Override
+ public Page page(PageMessageTaskParam param) {
+ QueryWrapper wrapper = QueryWrapperHelper.fromBean(param, MessageTask.class);
+ wrapper.eq("is_delete", 0);
+
+ return this.page(PageConverter.convertToMybatis(param, MessageTask.class), wrapper);
+ }
+
+ @Override
+ public void createMessageHistory(MessageTask messageTask) {
+
+ this.update(UpdateMessageTaskParam.builder()
+ .id(messageTask.getId())
+ .startedTime(new Date())
+ .build());
+
+ MessageTask.BizData bizData = messageTask.getBizData();
+ if (bizData.isAllPerson()) {
+ log.info("发送全员消息");
+ doSendAll(messageTask, bizData);
+ } else {
+ log.info("发送非全员消息");
+ doSendNotAll(messageTask);
+ }
+
+ this.update(UpdateMessageTaskParam.builder()
+ .id(messageTask.getId())
+ .action(MessageTask.ActionEnum.SUCCESS)
+ .finishedTime(new Date())
+ .build());
+ }
+
+ @Override
+ public void update(UpdateMessageTaskParam param) {
+ MessageTask oldMessageTask = this.lambdaQuery()
+ .eq(MessageTask::getId, param.getId())
+ .last("for update")
+ .one();
+ Aassert.notNull(oldMessageTask, MESSAGE_TASK_NOT_FOUND);;
+
+
+ MessageTask updateMessageTask = param.to();
+ if (param.getAction() != null) {
+ updateMessageTask.setStatus(param.getAction().getNextStatus(oldMessageTask.getStatus()));
+ }
+
+ this.updateById(updateMessageTask);
+ }
+
+ private void doSendAll(MessageTask messageTask, MessageTask.BizData bizData) {
+ Integer pageNumber = 1;
+ while (true) {
+ Page page = accountRegisterService.page(AccountRegisterService.PageAccountRegisterParam.builder()
+ .accountType(AccountTypeEnum.USER.getCode())
+ .appTypes(bizData.getAppTypes().stream().map(AppTypeEnum::getCode).collect(Collectors.toSet()))
+ .page(pageNumber++)
+ .pageSize(DEFAULT_PAGE_SIZE)
+ .build());
+ if (!CollectionUtils.isEmpty(page.getRecords())) {
+ List receivePersons = page.getRecords().stream()
+ .map(e -> MessageTask.ReceivePerson.builder().imAccount(e.getImAccount()).build())
+ .collect(Collectors.toList());
+ saveMessageHistory(receivePersons, messageTask);
+ }
+
+ if (!page.hasNext()) {
+ break;
+ }
+ }
+ }
+
+ private void doSendNotAll(MessageTask messageTask) {
+ // 防止sql过长
+ List> receivePersons = Lists.partition(messageTask.getReceivePersons(), DEFAULT_PAGE_SIZE);
+ receivePersons.forEach(e -> saveMessageHistory(e, messageTask));
+ }
+
+ private void saveMessageHistory(List receivePersons,
+ MessageTask messageTask) {
+ // 排除已经发送成功的记录,防止重复发送
+ Set