Merge branch 'master' of https://axzsource.com/zuoqinbo/im-center into feature/REQ-1899

This commit is contained in:
zuoqinbo 2024-01-04 15:52:56 +08:00
commit 3292b34490
18 changed files with 584 additions and 59 deletions

View File

@ -1,15 +1,16 @@
package cn.axzo.im.center.api.feign;
import cn.axzo.framework.domain.web.result.ApiResult;
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.resp.MessageCustomResp;
import cn.axzo.im.center.api.vo.resp.MessageDispatchResp;
import java.util.List;
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
*
@ -34,4 +35,10 @@ public interface MessageApi {
@PostMapping("api/im/message/dispatch")
ApiResult<List<MessageDispatchResp>> sendMessage(@RequestBody @Validated MessageInfo messageInfo);
/**
* 发送自定义消息
*/
@PostMapping("api/im/custom-message/send")
ApiResult<List<MessageCustomResp>> sendCustomMessage(@RequestBody @Validated CustomMessageInfo messageInfo);
}

View File

@ -0,0 +1,50 @@
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 java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author syl
* @date 2023/12/21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CustomMessageInfo {
/**
* 发送消息到App端
* 工人端企业端服务器
* CMCMPSYSTEM
*
* @See cn.axzo.im.center.common.enums.AppTypeEnum
*/
@NotEmpty(message = "消息接收端类型appTypeList不能为空")
private List<AppTypeEnum> appTypeList;
/**
* 接收用户自然人Id
*/
@NotBlank(message = "接收用户自然人Id不能为空")
private String toPersonId;
/**
* 业务类型
*/
@NotNull(message = "业务类型不能为空")
private BizTypeEnum bizType;
/**
* 推送内容 - 业务数据json格式
*/
private String payload;
}

View File

@ -1,9 +1,11 @@
package cn.axzo.im.center.api.vo.req;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.Map;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 账户信息
@ -13,6 +15,9 @@ import java.util.Map;
* @date 2023/10/9 16:01
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserAccountReq {
/**
@ -45,4 +50,5 @@ public class UserAccountReq {
* 用户账户扩展信息
*/
private Map<String, String> attachments;
}

View File

@ -0,0 +1,38 @@
package cn.axzo.im.center.api.vo.resp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author syl
* @date 2023/12/21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageCustomResp {
/**
* 接收消息端类型
*/
private String appType;
/**
* 接收人IM账户
*/
private String toImAccount;
/**
* 接收人personId
*/
private String personId;
/**
* true - 发送成功
* false - 发送失败
*/
private Boolean isSuccess;
}

View File

@ -1,6 +1,5 @@
package cn.axzo.im.center.api.vo.resp;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

View File

@ -1,7 +1,9 @@
package cn.axzo.im.center.api.vo.resp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* IM账户信息
@ -12,6 +14,8 @@ import lombok.Data;
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserAccountResp {
/**
* 用户userId

View File

@ -20,7 +20,12 @@ public enum AccountTypeEnum {
/**
* 普通用户
*/
USER("user", "普通用户");
USER("user", "普通用户"),
/**
* 自定义用户
*/
CUSTOM("custom", "自定义用户"),
;
private final String code;

View File

@ -0,0 +1,28 @@
package cn.axzo.im.center.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 业务类型
*/
@Getter
@AllArgsConstructor
public enum BizTypeEnum {
/**
* 积分
*/
POINTS("POINTS", "积分"),
/**
* 待办
*/
PENDING("PENDING", "待办"),
;
private final String code;
private final String message;
}

View File

@ -36,6 +36,12 @@ public interface IMChannelProvider {
*/
MessageBatchDispatchResponse dispatchBatchMessage(@Valid MessageBatchDispatchRequest messageInfo);
/**
* IM 发送自定义系统通知
*
*/
MessageCustomDispatchResponse dispatchCustomMessage(@Valid MessageCustomDispatchRequest param);
/**
* 获取AppKey
*

View File

@ -1,8 +1,10 @@
package cn.axzo.im.channel.netease;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.im.channel.IMChannelProvider;
import cn.axzo.im.channel.netease.dto.*;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
@ -41,6 +43,8 @@ public class NimChannelService implements IMChannelProvider {
private static final String NIM_MESSAGE_BATCH_DISPATCH_URL = "https://api.netease.im/nimserver/msg/sendBatchMsg.action";
private static final String NIM_MESSAGE_ATTACH_URL = "https://api.netease.im/nimserver/msg/sendAttachMsg.action";
private static final int SUCCESS_CODE = 200;
private static final int NIM_ACCOUNT_ALREADY_REGISTER = 414;
@ -214,6 +218,40 @@ public class NimChannelService implements IMChannelProvider {
return MessageBatchDispatchResponse.builder().desc("请求网易云信Server异常,请联系管理员!").build();
}
@Override
public MessageCustomDispatchResponse dispatchCustomMessage(MessageCustomDispatchRequest param) {
AssertUtil.notNull(param, "dispatchCustomMessage param cannot null");
//目前支持 0-点对点自定义通知
param.setMsgtype(0);
HashMap<String, Object> paramMap = Maps.newHashMap();
paramMap.put("from", param.getFrom());
paramMap.put("msgtype", param.getMsgtype());
paramMap.put("to", param.getTo());
paramMap.put("attach", param.getAttach());
paramMap.put("pushcontent", param.getPushcontent());
paramMap.put("payload", param.getPayload());
paramMap.put("isForcePush", ObjectUtil.defaultIfNull(param.getIsForcePush(), false));
Map<String, String> authHeaderMap = buildAuthHeader(getProviderAppKey(), getProviderAppSecret());
log.info("invoke yunxin dispatchCustomMessage,URL: {}, Header:{}, param:{}", NIM_MESSAGE_ATTACH_URL,
JSONUtil.toJsonStr(authHeaderMap), JSONUtil.toJsonStr(paramMap));
HttpResponse response = HttpRequest.post(NIM_MESSAGE_ATTACH_URL).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
log.info("yunxin dispatchMessage return,URL:{} , Header:{}, response:{}", NIM_MESSAGE_ATTACH_URL,
JSONUtil.toJsonStr(authHeaderMap), response.body());
if (response.getStatus() == SUCCESS_CODE) {
MessageCustomDispatchResponse registerResponse = JSONUtil.toBean(response.body(),
MessageCustomDispatchResponse.class);
if (registerResponse.getCode() != SUCCESS_CODE) {
log.warn("invoke yunxin server: {}, biz exception:{}", NIM_MESSAGE_ATTACH_URL, response.body());
}
return registerResponse;
} else {
log.error("invoke yunxin server :{}, error:{}", NIM_MESSAGE_ATTACH_URL, response.body());
}
return null;
}
@Override
public RegisterResponse updateAccountProfile(@Valid RegisterUpdateRequest updateRequest) {
HashMap<String, Object> paramMap = Maps.newHashMap();

View File

@ -0,0 +1,40 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.center.common.enums.BizTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author syl
* @date 2023/12/21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageCustomBody {
/**
* 接收人IM账户
*/
private String toImAccount;
/**
* 接收用户自然人Id
*/
private String personId;
/**
* 业务类型
*/
private BizTypeEnum bizType;
/**
* 推送内容 - 业务数据json格式
*/
private String payload;
}

View File

@ -0,0 +1,62 @@
package cn.axzo.im.channel.netease.dto;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 文档地址:https://doc.yunxin.163.com/messaging/docs/DQ2NTg4ODE?platform=server
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageCustomDispatchRequest {
/**
* 发送者的云信 IM 账号accid最大 32 字符
* 必须保证一个应用内唯一
*/
@NotBlank
private String from;
/**
* 仅可传入 0 1
* 0点对点自定义通知
* 1群消息自定义通知传入其他值都将报错状态码414
*/
@NotNull
private Integer msgtype;
/**
* msgtype=0 时需填入接收系统通知的用户的的云信 IM 账号accid
* msgtype=1 时需填入接收系统通知的群的 ID tid最大 32 字符
*/
@NotBlank
private String to;
/**
* 自定义系统通知的具体内容json
*/
@NotBlank
private String attach;
/**
* 推送文案
*/
private String pushcontent;
/**
* 推送对应的 payload必须是 JSON 格式不能超过 2048 字符
*/
private String payload;
/**
* 发自定义系统通知时是否强制推送默认 false
*/
private Boolean isForcePush;
}

View File

@ -0,0 +1,28 @@
package cn.axzo.im.channel.netease.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;
/**
* 消息返回响应
* 示例
* {
* "code":200
* }
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MessageCustomDispatchResponse {
private Integer code;
public boolean isSuccess () {
return code == HttpStatus.OK.value();
}
}

View File

@ -2,18 +2,24 @@ package cn.axzo.im.controller;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.feign.AccountApi;
import cn.axzo.im.center.api.vo.req.*;
import cn.axzo.im.center.api.vo.req.AccountAbsentQuery;
import cn.axzo.im.center.api.vo.req.AccountQuery;
import cn.axzo.im.center.api.vo.req.BatchAccountReq;
import cn.axzo.im.center.api.vo.req.RobotAccountReq;
import cn.axzo.im.center.api.vo.req.UserAccountReq;
import cn.axzo.im.center.api.vo.resp.UserAccountResp;
import cn.axzo.im.channel.netease.INotifyService;
import cn.axzo.im.service.AccountService;
import com.google.common.collect.Lists;
import java.util.List;
import javax.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* IM账户相关设计
*
@ -71,4 +77,17 @@ public class AccountController implements AccountApi {
}
return ApiResult.ok(respList);
}
/**
* 自定义用户生成IM账户
* 例如 因多终端场景同一个普通用户会申请多个网易云信账户用于不同的app终端
* 例如工人端一个IM账户企业端一个IM账户,根据不同的appType来区分
*
* @return 返回云信IM账户
*/
@PostMapping(value = "/api/im/custom-account/generate")
public ApiResult<UserAccountResp> initRelationTemplateMap(@RequestBody @Validated UserAccountReq userAccountReq) {
UserAccountResp userAccountResp = accountService.registerCustomAccount(userAccountReq, iNotifyService);
return ApiResult.ok(userAccountResp);
}
}

View File

@ -2,10 +2,14 @@ package cn.axzo.im.controller;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.feign.MessageApi;
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.resp.MessageCustomResp;
import cn.axzo.im.center.api.vo.resp.MessageDispatchResp;
import cn.axzo.im.service.MessageService;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import java.util.List;
import javax.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@ -13,9 +17,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* IM消息派发相关
*
@ -37,6 +38,12 @@ public class MessageController implements MessageApi {
return ApiResult.ok(messageRespList);
}
@Override
public ApiResult<List<MessageCustomResp>> sendCustomMessage(CustomMessageInfo customMessage) {
List<MessageCustomResp> messageRespList = messageService.sendCustomMessage(customMessage);
return ApiResult.ok(messageRespList);
}
@ExceptionHandler({ RequestNotPermitted.class })
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public ApiResult<String> handleRequestNotPermitted() {

View File

@ -2,37 +2,38 @@ package cn.axzo.im.service;
import cn.axzo.basics.common.BeanMapper;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.im.center.api.vo.req.*;
import cn.axzo.im.center.api.vo.req.AccountAbsentQuery;
import cn.axzo.im.center.api.vo.req.AccountQuery;
import cn.axzo.im.center.api.vo.req.RobotAccountReq;
import cn.axzo.im.center.api.vo.req.UserAccountReq;
import cn.axzo.im.center.api.vo.resp.UserAccountResp;
import cn.axzo.im.center.common.enums.AccountTypeEnum;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.RobotStatusEnum;
import cn.axzo.im.channel.IMChannelProvider;
import cn.axzo.im.channel.netease.INotifyService;
import cn.axzo.im.channel.netease.dto.NimAccountInfo;
import cn.axzo.im.channel.netease.dto.RegisterRequest;
import cn.axzo.im.channel.netease.dto.RegisterResponse;
import cn.axzo.im.dao.repository.AccountRegisterDao;
import cn.axzo.im.dao.repository.RobotInfoDao;
import cn.axzo.im.entity.AccountRegister;
import cn.axzo.im.channel.netease.dto.RegisterResponse;
import cn.axzo.im.channel.netease.dto.RegisterRequest;
import cn.axzo.im.channel.netease.dto.NimAccountInfo;
import cn.axzo.im.center.common.enums.AccountTypeEnum;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.entity.RobotInfo;
import com.google.common.collect.Lists;
import java.util.Date;
import java.util.List;
import java.util.Objects;
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.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.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* IM账户服务
*
@ -90,6 +91,52 @@ public class AccountService {
}
/**
* 创建IM账户 - 用于自定义通知
*/
@Transactional(rollbackFor = Exception.class)
public UserAccountResp registerCustomAccount(@Valid UserAccountReq userAccountReq, INotifyService iNotifyService) {
String appType = userAccountReq.getAppType();
AppTypeEnum appTypeEnum = AppTypeEnum.isValidAppType(appType);
if (appTypeEnum == null) {
throw new ServiceException("当前appType,服务器不支持该类型!!");
}
String env = environment.getProperty("spring.profiles.active");
String userIdWrapper = env + userAccountReq.getUserId() + "_" + appTypeEnum.getCode();
String appKey = imChannelProvider.getProviderAppKey();
AccountRegister customAccountRegister = queryCustomAccount(AccountTypeEnum.CUSTOM,
AppTypeEnum.SYSTEM, appKey);
if (Objects.nonNull(customAccountRegister)) {
log.info("generateCustomAccount exists custom account ");
return UserAccountResp.builder()
.userId(customAccountRegister.getAccountId())
.imAccount(customAccountRegister.getImAccount())
.token(customAccountRegister.getToken())
.appType(customAccountRegister.getAppType())
.build();
}
UserAccountResp userAccountResp = createAccountRegister(userAccountReq.getUserId(), userIdWrapper, appType,
AccountTypeEnum.CUSTOM.getCode(), userAccountReq.getHeadImageUrl(), userAccountReq.getNickName());
if (iNotifyService != null && userAccountResp != null && StringUtils.isNotBlank(userAccountResp.getImAccount())) {
iNotifyService.notifyUserAccountChange(userAccountResp.getImAccount(), userAccountReq.getNickName(), null);
}
return userAccountResp;
}
public AccountRegister queryCustomAccount(AccountTypeEnum accountType, AppTypeEnum appType, String appKey) {
return accountRegisterDao.lambdaQuery()
.eq(AccountRegister::getIsDelete, 0)
.eq(AccountRegister::getAccountType, accountType.getCode())
.eq(AccountRegister::getAppType, appType.getCode())
.eq(AccountRegister::getAppKey, appKey)
.last(" LIMIT 1 ")
.one();
}
@Transactional(rollbackFor = Exception.class)
public UserAccountResp generateRobotAccount(@Valid RobotAccountReq robotAccountReq, INotifyService iNotifyService) {
//后续AppKey可能会更换机器人通过robotIdappKey维度来保证唯一性
@ -105,7 +152,7 @@ public class AccountService {
//生成后更新机器人状态和IM账户
robotInfoService.updateRobotStatus(robotId, userAccountResp.getImAccount(), RobotStatusEnum.UN_ENABLE);
if (iNotifyService != null) {
iNotifyService.notifyUserAccountChange(userAccountResp.getImAccount(), robotInfo.getNickName(), null);
iNotifyService.notifyRobotAccountChange(robotId);
}
}
return userAccountResp;
@ -115,6 +162,7 @@ public class AccountService {
String accountType, String headImageUrl, String nickName) {
//1.检查账户是否已经创建
String appKey = imChannelProvider.getProviderAppKey();
UserAccountResp userAccountResp = new UserAccountResp();
AccountRegister accountRegister = accountRegisterDao.lambdaQuery().eq(AccountRegister::getIsDelete, 0)
.eq(AccountRegister::getAccountWrapper, userIdWrapper)
.eq(AccountRegister::getAppKey, appKey).one();
@ -141,20 +189,18 @@ public class AccountService {
} else {
//2.1注册出现异常
assert accountResp != null;
return UserAccountResp.builder()
.desc(accountResp.getDesc())
.build();
userAccountResp.setDesc(accountResp.getDesc());
return userAccountResp;
}
accountResp.setAppType(appType);
return accountResp;
}
//1.1 如果已经创建直接返回
return UserAccountResp.builder()
.imAccount(accountRegister.getImAccount())
.userId(userId)
.appType(appType)
.token(accountRegister.getToken())
.build();
userAccountResp.setImAccount(accountRegister.getImAccount());
userAccountResp.setUserId(userId);
userAccountResp.setAppType(appType);
userAccountResp.setToken(accountRegister.getToken());
return userAccountResp;
}
@ -163,17 +209,17 @@ public class AccountService {
register.setAccid(userWapperId);
register.setIcon(headImageUrl);
register.setName(name);
UserAccountResp userAccountResp = new UserAccountResp();
//3.调用公共的网易云信IM接口创建账户 网易云信只是一种IM实现
RegisterResponse registerResponse = imChannelProvider.registerAccount(register);
if (registerResponse.getInfo() != null) {
NimAccountInfo userAccount = BeanMapper.map(registerResponse.getInfo(), NimAccountInfo.class);
return UserAccountResp.builder()
.imAccount(userAccount.getAccid())
.userId(userId)
.token(userAccount.getToken())
.build();
userAccountResp.setImAccount(userAccount.getAccid());
userAccountResp.setUserId(userId);
userAccountResp.setToken(userAccount.getToken());
}
return UserAccountResp.builder().desc(registerResponse.getDesc()).build();
userAccountResp.setDesc(registerResponse.getDesc());
return userAccountResp;
}
public List<UserAccountResp> queryAccountInfo(@Valid AccountQuery accountQuery) {
@ -189,11 +235,15 @@ public class AccountService {
.isNotNull(AccountRegister::getToken)
.list();
if (CollectionUtils.isNotEmpty(accountRegisterList)) {
return accountRegisterList.stream().map(accountRegister -> UserAccountResp.builder()
.userId(accountRegister.getAccountId())
.token(accountRegister.getToken())
.appType(accountRegister.getAppType())
.imAccount(accountRegister.getImAccount()).build()).collect(Collectors.toList());
return accountRegisterList.stream().map(accountRegister ->
{
UserAccountResp userAccountResp = new UserAccountResp();
userAccountResp.setImAccount(accountRegister.getImAccount());
userAccountResp.setUserId(accountRegister.getAccountId());
userAccountResp.setAppType(accountRegister.getAppType());
userAccountResp.setToken(accountRegister.getToken());
return userAccountResp;
}).collect(Collectors.toList());
}
return Lists.newArrayList();
}

View File

@ -2,14 +2,25 @@ package cn.axzo.im.service;
import cn.axzo.basics.common.BeanMapper;
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.resp.MessageCustomResp;
import cn.axzo.im.center.api.vo.resp.MessageDispatchResp;
import cn.axzo.im.center.api.vo.resp.UserAccountResp;
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.*;
import cn.axzo.im.channel.netease.dto.MessageBatchDispatchRequest;
import cn.axzo.im.channel.netease.dto.MessageBatchDispatchResponse;
import cn.axzo.im.channel.netease.dto.MessageBody;
import cn.axzo.im.channel.netease.dto.MessageCustomBody;
import cn.axzo.im.channel.netease.dto.MessageCustomDispatchRequest;
import cn.axzo.im.channel.netease.dto.MessageCustomDispatchResponse;
import cn.axzo.im.channel.netease.dto.MessageDispatchRequest;
import cn.axzo.im.channel.netease.dto.MessageDispatchResponse;
import cn.axzo.im.dao.repository.AccountRegisterDao;
import cn.axzo.im.dao.repository.MessageHistoryDao;
import cn.axzo.im.entity.AccountRegister;
@ -17,6 +28,15 @@ import cn.axzo.im.entity.MessageHistory;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
@ -29,13 +49,6 @@ import org.springframework.core.env.Environment;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* im-center
*
@ -343,4 +356,107 @@ public class MessageService {
}
}));
}
@Transactional(rollbackFor = Exception.class)
public List<MessageCustomResp> sendCustomMessage(CustomMessageInfo customMessage) {
MessageCustomDispatchRequest messageRequest = new MessageCustomDispatchRequest();
String appKey = imChannel.getProviderAppKey();
AccountRegister customSendAccount = accountService.queryCustomAccount(
AccountTypeEnum.CUSTOM, AppTypeEnum.SYSTEM, appKey);
AssertUtil.notNull(customSendAccount, String.format("appKey=%s自定义用户没有注册im账号", appKey));
messageRequest.setFrom(customSendAccount.getImAccount());
//如果消息模板是针对多App端则分开进行发送消息
List<AppTypeEnum> appTypeList = customMessage.getAppTypeList();
List<MessageCustomResp> messageCustomRespList = Lists.newArrayList();
appTypeList.forEach(appType -> {
if (appType == null || AppTypeEnum.isValidAppType(appType.getCode()) == null) {
throw new ServiceException("当前服务器不支持该appType类型!");
}
String personId = customMessage.getToPersonId();
//进行接收用户IM账户校验
List<AccountRegister> 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账户信息
MessageCustomResp messageDispatchResp = MessageCustomResp.builder()
.appType(appType.getCode())
.personId(personId)
.toImAccount(null)
.isSuccess(false)
.build();
log.warn("user personId=[" + personId + "],appType[" + appType.getCode() + "],"
+ " Do not send messages if you have not registered an IM account!");
return;
}
accountRegisterList.stream()
.filter(a -> StringUtils.isNoneBlank(a.getImAccount(), a.getToken()))
.forEach(a -> {
messageRequest.setTo(a.getImAccount());
String body = JSONUtil.toJsonStr(wrapperCustomMessage(a.getImAccount(),
customMessage));
messageRequest.setAttach(body);
messageRequest.setPayload(body);
MessageCustomDispatchResponse response = imChannel.dispatchCustomMessage(messageRequest);
MessageCustomResp customResp = MessageCustomResp.builder()
.appType(appType.getCode())
.personId(personId)
.toImAccount(a.getImAccount())
.isSuccess(response.isSuccess())
.build();
messageCustomRespList.add(customResp);
});
});
insertImCustomMessage(messageCustomRespList, messageRequest, customSendAccount.getImAccount());
return messageCustomRespList;
}
private void insertImCustomMessage(List<MessageCustomResp> messageRespList,
MessageCustomDispatchRequest messageRequest, String fromImAccount) {
if (CollectionUtils.isEmpty(messageRespList)) {
return;
}
String requestId = UUID.randomUUID().toString().replace("-", "");
log.info("IM custom message sent successfully insert DB: {}, param payload: {}",
JSONUtil.toJsonStr(messageRespList), messageRequest.getAttach());
CompletableFuture.runAsync(() -> {
List<MessageHistory> histories = messageRespList.stream().map(messageDispatchResp -> {
MessageHistory messageHistory = new MessageHistory();
messageHistory.setBizId(requestId);
messageHistory.setFromAccount(fromImAccount);
if (StringUtils.isNotBlank(messageDispatchResp.getToImAccount())) {
messageHistory.setToAccount(messageDispatchResp.getToImAccount());
} else {
messageHistory.setToAccount("unregistered");
}
messageHistory.setChannel(imChannel.getProviderType());
messageHistory.setAppType(messageDispatchResp.getAppType());
messageHistory.setMessageBody(messageRequest.getPayload());
messageHistory.setCreateAt(new Date());
messageHistory.setUpdateAt(new Date());
return messageHistory;
}).collect(Collectors.toList());
try {
messageHistoryDao.saveOrUpdateBatch(histories);
} catch (Exception e) {
log.error("custom message sent successfully insert failed :{},",
JSONUtil.toJsonStr(histories), e);
}
});
}
public static MessageCustomBody wrapperCustomMessage(String toImAccount,
CustomMessageInfo customMessage) {
return MessageCustomBody.builder()
.toImAccount(toImAccount)
.personId(customMessage.getToPersonId())
.bizType(customMessage.getBizType())
.payload(Optional.ofNullable(customMessage.getPayload()).orElse("{}"))
.build();
}
}

View File

@ -5,6 +5,7 @@ import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.framework.domain.page.PageResp;
import cn.axzo.im.center.api.vo.req.RobotMsgTemplatePageQuery;
import cn.axzo.im.center.api.vo.req.RobotMsgTemplateReq;
import cn.axzo.im.center.api.vo.resp.RobotInfoResp;
import cn.axzo.im.center.api.vo.resp.RobotMsgTemplateResp;
import cn.axzo.im.dao.repository.RobotInfoDao;
import cn.axzo.im.dao.repository.RobotMsgTemplateDao;
@ -41,6 +42,9 @@ public class RobotMsgTemplateService {
@Resource
private RobotInfoDao robotInfoDao;
@Resource
private RobotInfoService robotInfoService;
public List<String> queryRobotMsgTemplateIds(String robotId) {
RobotMsgTemplate robotMsgTemplate = robotMsgTemplateDao.lambdaQuery().eq(RobotMsgTemplate::getIsDelete, 0)
.eq(RobotMsgTemplate::getRobotId, robotId).one();
@ -85,9 +89,12 @@ public class RobotMsgTemplateService {
List<String> robotIdList = Lists.newArrayList();
robotMsgTemplateList.forEach(robotInfo -> {
List<String> msgTemplateList = robotInfo.getMsgTemplateList();
if (msgTemplateList.contains(templateId)) {
robotIdList.add(robotInfo.getRobotId());
if (CollectionUtils.isNotEmpty(msgTemplateList)) {
if (msgTemplateList.contains(templateId)) {
robotIdList.add(robotInfo.getRobotId());
}
}
});
return robotIdList;
}
@ -109,6 +116,21 @@ public class RobotMsgTemplateService {
robotMsgTemplate.setRobotId(robotId);
robotMsgTemplate.setCreateAt(new Date());
}
//进行校验
for (String msgTemplateId : robotMsgTemplateReq.getMsgTemplateList()) {
List<String> robotIdList = this.queryRobotIdByTemplate(msgTemplateId);
if (CollectionUtils.isNotEmpty(robotIdList)) {
if (robotIdList.size() > 1 || !robotIdList.get(0).equals(robotId)) {
robotIdList.remove(robotId);
RobotInfoResp robotInfoResp = robotInfoService.queryRobotInfo(robotIdList.get(0));
if (robotInfoResp == null) {
throw new ServiceException("消息模板ID[" + msgTemplateId + "],已关联机器人!");
}
throw new ServiceException("消息模板ID[" + msgTemplateId + "],已关联机器人[" + robotInfoResp.getNickName() + "]!");
}
}
}
robotMsgTemplate.setUpdateAt(new Date());
robotMsgTemplate.setMsgTemplateList(robotMsgTemplateReq.getMsgTemplateList());
robotMsgTemplateDao.saveOrUpdate(robotMsgTemplate);