Merge branch 'feature/REQ-3345'

# Conflicts:
#	im-center-server/src/main/java/cn/axzo/im/handler/chatgroup/ChatGroupChangeOwnerEventHandler.java
This commit is contained in:
yanglin 2025-02-28 13:52:49 +08:00
commit 389047223d
146 changed files with 4442 additions and 366 deletions

View File

@ -1,4 +1,4 @@
package cn.axzo.im.event.inner;
package cn.axzo.im.center.api.enums;
import cn.axzo.framework.rocketmq.Event;
import lombok.Getter;
@ -9,11 +9,15 @@ import lombok.Getter;
* @Created by lilong
*/
@Getter
public enum EventTypeEnum {
public enum MqEventType {
MESSAGE_HISTORY_CREATED("message-history", "message-history-created", "发送记录创建"),
MESSAGE_HISTORY_UPDATED("message-history", "message-history-updated", "发送记录修改"),
MESSAGE_CHAT_GROUP_CREATE("chat-group", "chat-group-create", "群聊创建"),
MESSAGE_CHAT_GROUP_CREATE("chat-group", "chat-group-create", "群聊创建[项目]"),
GROUP_CREATED("group", "group-created", "创建群聊"),
GROUP_DISMISSED("group", "group-dismissed", "群解散"),
GROUP_ADD_MEMBERS("group", "group-add-members", "添加群聊成员"),
GROUP_REMOVE_MEMBERS("group", "group-remove-members", "移除群聊成员"),
UPDATE_AVATAR("profile", "update-avatar", "头像更新"),
NODE_USER_CREATE("node-user", "node-user-create", "节点用户创建"),
NODE_USER_UPDATE("node-user", "node-user-update", "节点用户修改"),
@ -23,7 +27,7 @@ public enum EventTypeEnum {
SAAS_ROLE_USER_RELATION_UPSERT("saas-role-user-relation", "saas-role-user-relation-upsert", "更新用户角色信息"),
;
EventTypeEnum(String model, String name, String desc) {
MqEventType(String model, String name, String desc) {
this.eventCode = Event.EventCode.builder()
.module(model)
.name(name)
@ -33,8 +37,8 @@ public enum EventTypeEnum {
this.desc = desc;
}
private String model;
private String name;
private String desc;
private Event.EventCode eventCode;
private final String model;
private final String name;
private final String desc;
private final Event.EventCode eventCode;
}

View File

@ -0,0 +1,77 @@
package cn.axzo.im.center.api.feign;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.vo.group.GroupInfo;
import cn.axzo.im.center.api.vo.req.GroupAddMembersRequest;
import cn.axzo.im.center.api.vo.req.GroupCreateRequest;
import cn.axzo.im.center.api.vo.req.GroupDismissRequest;
import cn.axzo.im.center.api.vo.req.GroupFindInfoRequest;
import cn.axzo.im.center.api.vo.req.GroupGetInfoRequest;
import cn.axzo.im.center.api.vo.req.GroupGetMembersRequest;
import cn.axzo.im.center.api.vo.req.GroupGetOwnerRequest;
import cn.axzo.im.center.api.vo.req.GroupRemoveMembersRequest;
import cn.axzo.im.center.api.vo.resp.GroupAddMembersResponse;
import cn.axzo.im.center.api.vo.resp.GroupCreateResponse;
import cn.axzo.im.center.api.vo.resp.GroupGetInfoResponse;
import cn.axzo.im.center.api.vo.resp.GroupGetMembersResponse;
import cn.axzo.im.center.api.vo.resp.GroupGetOwnerResponse;
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;
/**
* @author yanglin
*/
@FeignClient(name = "im-center", url = "${axzo.service.im-center:http://im-center:8080}")
public interface GroupApi {
/**
* 创建群
*/
@PostMapping("/api/im/group/createGroup")
ApiResult<GroupCreateResponse> createGroup(@RequestBody @Validated GroupCreateRequest request);
/**
* 解散群
*/
@PostMapping("/api/im/group/dismissGroup")
ApiResult<Void> dismissGroup(@RequestBody @Validated GroupDismissRequest request);
/**
* 添加群成员
*/
@PostMapping("/api/im/group/addMembers")
ApiResult<GroupAddMembersResponse> addMembers(@RequestBody @Validated GroupAddMembersRequest request);
/**
* 移除群成员
*/
@PostMapping("/api/im/group/removeMembers")
ApiResult<Void> removeMembers(@RequestBody @Validated GroupRemoveMembersRequest request);
/**
* 获取群成员列表
*/
@PostMapping("/api/im/group/getMembers")
ApiResult<GroupGetMembersResponse> getMembers(@RequestBody @Validated GroupGetMembersRequest request);
/**
* 获取群主信息
*/
@PostMapping("/api/im/group/getOwner")
ApiResult<GroupGetOwnerResponse> getOwner(@RequestBody @Validated GroupGetOwnerRequest request);
/**
* 获取群信息
*/
@PostMapping("/api/im/group/getGroupInfo")
ApiResult<GroupGetInfoResponse> getGroupInfo(@RequestBody @Validated GroupGetInfoRequest request);
/**
* 查询群信息
*/
@PostMapping("/api/im/group/findGroupInfo")
ApiResult<GroupGetInfoResponse> findGroupInfo(@RequestBody @Validated GroupFindInfoRequest request);
}

View File

@ -0,0 +1,27 @@
package cn.axzo.im.center.api.feign;
import cn.axzo.framework.domain.web.result.ApiPageResult;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.vo.req.GroupCreateRequest;
import cn.axzo.im.center.api.vo.req.GroupMessagePageQueryRequest;
import cn.axzo.im.center.api.vo.resp.GroupCreateResponse;
import cn.axzo.im.center.api.vo.resp.GroupMessagePageQueryResponse;
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;
/**
* @author yanglin
*/
@FeignClient(name = "im-center", url = "${axzo.service.im-center:http://im-center:8080}")
public interface GroupMessageApi {
/**
* 查询群聊历史消息
*/
@PostMapping("/api/im/group/message/pageQuery")
ApiPageResult<GroupMessagePageQueryResponse> pageQuery(
@RequestBody @Validated GroupMessagePageQueryRequest request);
}

View File

@ -6,6 +6,7 @@ import cn.axzo.im.center.api.vo.req.CustomMessageInfo;
import cn.axzo.im.center.api.vo.req.FetchUpdatableMessageRequest;
import cn.axzo.im.center.api.vo.req.GetMessageDetailRequest;
import cn.axzo.im.center.api.vo.req.MessageInfo;
import cn.axzo.im.center.api.vo.req.SendChatMessageRequest;
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.req.UpdatableMessageAckRequest;
@ -64,6 +65,10 @@ public interface MessageApi {
ApiResult<MessageTaskResp> sendTemplateMessageAsync(
@RequestBody @Validated SendTemplateMessageParam sendMessageParam);
@PostMapping("/api/im/template-message/async/send/chatMessage")
ApiResult<Long> sendChatMessage(
@RequestBody @Validated SendChatMessageRequest request);
/**
* 更新消息
*/

View File

@ -11,6 +11,7 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class SendPriority {
public static final SendPriority CHAT_MESSAGE = create(900);
public static final SendPriority TEMPLATE_MESSAGE = create(1000);
public static final SendPriority SYSTEM_CUSTOM_MESSAGE = create(5000);
public static final SendPriority UPDATE_MESSAGE = create(5500);

View File

@ -1,14 +1,17 @@
package cn.axzo.im.center.api.vo;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.math.NumberUtils;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Objects;
/**
* @author yanglin
@ -17,10 +20,11 @@ import javax.validation.constraints.NotNull;
@Builder
@AllArgsConstructor
@NoArgsConstructor
// IMPORTANT: 不要删除这个注解, 避免@Data被@Setter, @Getter取代
@EqualsAndHashCode
public class PersonAccountAttribute {
private static final PersonAccountAttribute ROBOT =
new PersonAccountAttribute("0", 0L, 0L, AppTypeEnum.NONE);
/**
* 接收消息的personId
*/
@ -28,24 +32,75 @@ public class PersonAccountAttribute {
private String personId;
/**
* appType = AppTypeEnum.CMP时因为网易云信无法对同一个账号做企业隔离只能一个企业一个账号
* 所以需要根据organizationalUnitId获取账号
* 单位id, 管理端人员必填
*/
private Long ouId;
/**
* 项目id
* 项目id, 选填
*/
private Long workspaceId;
/**
* 发送消息到App端
* 工人端企业端服务器
* CMCMPSYSTEM
*
* @See cn.axzo.im.center.common.enums.AppTypeEnum
* 发送消息到App端. CM: 工人端, CMP: 企业端
*/
@NotNull(message = "appType不能为空")
private AppTypeEnum appType;
}
public static PersonAccountAttribute robot() {
return ROBOT;
}
public static PersonAccountAttribute cmp(Long personId, Long ouId) {
return PersonAccountAttribute.builder()
.personId(String.valueOf(personId))
.ouId(ouId)
.appType(AppTypeEnum.CMP)
.build();
}
public static PersonAccountAttribute cm(Long personId) {
return PersonAccountAttribute.builder()
.personId(String.valueOf(personId))
.appType(AppTypeEnum.CM)
.build();
}
public Long personIdAsLong() {
if (NumberUtils.isDigits(personId))
return Long.valueOf(personId);
return 0L;
}
public Long ouIdOrDefault() {
return getOuIdOrDefault(ouId);
}
public static Long getOuIdOrDefault(Long ouId) {
return ouId == null ? 0L : ouId;
}
@JsonIgnore @JSONField(serialize = false)
public boolean isRobot() {
return this == ROBOT;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof PersonAccountAttribute)) return false;
PersonAccountAttribute that = (PersonAccountAttribute) o;
return Objects.equals(personId, that.personId)
&& Objects.equals(ouIdOrDefault(), that.ouIdOrDefault())
&& appType == that.appType;
}
@Override
public int hashCode() {
return Objects.hash(personId, ouIdOrDefault(), appType);
}
@Override
public String toString() {
return String.format("[personId:%s,ouId:%s,appType:%s]", personId, ouId, appType);
}
}

View File

@ -0,0 +1,69 @@
package cn.axzo.im.center.api.vo.group;
import cn.axzo.im.center.common.enums.GroupType;
import cn.axzo.im.center.common.enums.YesOrNo;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import java.util.Map;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupInfo {
/**
* 群id
*/
private Long tid;
/**
* 群名称
*/
private String name;
/**
* 群业务id
*/
private String bizCode;
/**
* 群类型. VISA: 变洽签, WORKSPACE: 项目群, WORKSPACE_OU: 项目单位群, WORKSPACE_TEAM: 项目班组群
*/
private GroupType type;
/**
* 群聊业务扩展信息
*/
private Map<String, Object> bizGroupInfo;
/**
* 群头像
*/
private String avatar;
/**
* 群成员数量
*/
private Long memberCount;
/**
* 群成员上限
*/
private Long memberLimit;
/**
* 群主账号
*/
private String ownerAccount;
/**
* 群主personId
*/
private Long ownerPersonId;
/**
* 群创建人ouId
*/
private Long createPersonId;
/**
* 是否解散
*/
private YesOrNo isDismissed;
/**
* 创建时间
*/
private Date createAt;
}

View File

@ -0,0 +1,30 @@
package cn.axzo.im.center.api.vo.group;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.GroupMemberType;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupMemberInfo {
/**
* 群成员personId
*/
private Long personId;
/**
* 群成员ouId
*/
private Long personOuId;
/**
* 群成员端类型
*/
private AppTypeEnum appType;
/**
* 群成员类型. MEMBER: 普通成员, OWNER: 群主
*/
private GroupMemberType memberType;
}

View File

@ -0,0 +1,19 @@
package cn.axzo.im.center.api.vo.mq;
import cn.axzo.im.center.api.vo.group.GroupInfo;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupChangedMessage extends MqMessage {
/**
* 群信息
*/
private GroupInfo group;
}

View File

@ -0,0 +1,24 @@
package cn.axzo.im.center.api.vo.mq;
import cn.axzo.im.center.api.vo.group.GroupInfo;
import cn.axzo.im.center.api.vo.group.GroupMemberInfo;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter @Getter
public class GroupMembersChangeMessage extends MqMessage {
/**
* 群信息
*/
private GroupInfo group;
/**
* 群成员信息
*/
private GroupMemberInfo member;
}

View File

@ -0,0 +1,33 @@
package cn.axzo.im.center.api.vo.mq;
import cn.hutool.core.lang.UUID;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author yanglin
*/
@Setter
@Getter
public abstract class MqMessage implements Serializable {
/**
* 消息唯一id
*/
private String messageId = UUID.randomUUID().toString();
/**
* 消息发送时间
*/
private Date messageSendTime = new Date();
/**
* 消息发送时间, 字符串格式
*/
private String messageSendTimeStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(messageSendTime);
}

View File

@ -0,0 +1,46 @@
package cn.axzo.im.center.api.vo.req;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.HashSet;
import java.util.Set;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupAddMembersRequest {
@NotNull(message = "群ID不能为空")
private Long tid;
/**
* 邀请者
*/
@NotNull(message = "inviter不能为空")
private String inviter;
/**
* 群成员, 不包含群主. members数量不能超过199
*/
@NotEmpty(message = "群成员不能为空")
private Set<PersonAccountAttribute> members;
public void addMember(PersonAccountAttribute member) {
if (members == null)
members = new HashSet<>();
members.add(member);
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,97 @@
package cn.axzo.im.center.api.vo.req;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import cn.axzo.im.center.common.enums.GroupType;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author yanglin
*/
@Setter @Getter
public class GroupCreateRequest {
/**
* 群类型. groupType和bizCode一起做幂等控制
*/
@NotNull(message = "群类型不能为空")
private GroupType groupType;
/**
* 业务编码. groupType和bizCode一起做幂等控制
*/
@NotBlank(message = "业务码不能为空")
private String bizCode;
/**
* 群名称
*/
@NotBlank(message = "群名称不能为空")
private String name;
/**
* 群主账号
*/
@NotNull(message = "群主账号不能为空")
private PersonAccountAttribute owner;
/**
* 群成员, 不包含群主. members数量不能超过199
*/
@NotEmpty(message = "群成员不能为空")
private Set<PersonAccountAttribute> members;
/**
* 群成员上限, 不传默认为 499
*/
private Long memberLimit;
/**
* 群头像最大长度 1024 位字符
*/
private String avatar;
/**
* 群聊业务扩展信息, 透传到群属性中. 不能传太多属性或较长字段云信有长度限制
*/
private Map<String, Object> bizGroupInfo;
public Map<String, Object> bizGroupInfoOrEmpty() {
return bizGroupInfo == null ? Collections.emptyMap() : bizGroupInfo;
}
@JsonIgnore @JSONField(serialize = false, deserialize = false)
public Set<PersonAccountAttribute> getPeople() {
Set<PersonAccountAttribute> ownerAndMembers = new HashSet<>(members);
ownerAndMembers.add(owner);
return ownerAndMembers;
}
public void addMembers(Set<PersonAccountAttribute> members) {
if (this.members == null)
this.members = new HashSet<>();
this.members.addAll(members);
}
public void addMember(PersonAccountAttribute member) {
if (members == null)
members = new HashSet<>();
members.add(member);
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,18 @@
package cn.axzo.im.center.api.vo.req;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupDismissRequest {
@NotNull(message = "群id不能为空")
private Long tid;
}

View File

@ -0,0 +1,26 @@
package cn.axzo.im.center.api.vo.req;
import cn.axzo.im.center.common.enums.GroupType;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter @Getter
public class GroupFindInfoRequest {
/**
* 群类型
*/
@NotNull(message = "群类型不能为空")
private GroupType groupType;
/**
* 业务码
*/
@NotBlank(message = "业务码不能为空")
private String bizCode;
}

View File

@ -0,0 +1,18 @@
package cn.axzo.im.center.api.vo.req;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupGetInfoRequest {
@NotNull(message = "群ID不能为空")
private Long tid;
}

View File

@ -0,0 +1,18 @@
package cn.axzo.im.center.api.vo.req;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupGetMembersRequest {
@NotNull(message = "群ID不能为空")
private Long tid;
}

View File

@ -0,0 +1,18 @@
package cn.axzo.im.center.api.vo.req;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupGetOwnerRequest {
@NotNull(message = "群ID不能为空")
private Long tid;
}

View File

@ -0,0 +1,19 @@
package cn.axzo.im.center.api.vo.req;
import cn.axzo.basics.common.page.PageRequest;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupMessagePageQueryRequest extends PageRequest {
@NotNull(message = "群ID不能为空")
private Long tid;
}

View File

@ -0,0 +1,32 @@
package cn.axzo.im.center.api.vo.req;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Set;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupRemoveMembersRequest {
@NotNull(message = "群ID不能为空")
private Long tid;
/**
* 一次性最多传200个
*/
@NotEmpty(message = "群成员不能为空")
private Set<PersonAccountAttribute> members;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,84 @@
package cn.axzo.im.center.api.vo.req;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import cn.axzo.im.center.common.enums.NimMessageType;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author yanglin
*/
@Setter
@Getter
public class SendChatMessageRequest extends SendMessageRequest {
/**
* 消息类型
*/
@NotNull(message = "消息类型不能为空")
private NimMessageType messageType;
/**
* 消息内容
*/
@NotEmpty(message = "消息体不能为空")
private Map<String, Object> messageBody;
/**
* 尝试同步发送
* trySyncSend=true, 接收人只有一个的情况下就会异步发送
*/
private boolean trySyncSend;
/**
* 发送文本消息
*
* @param text 消息内容
*/
public void setAsTextMessage(String text) {
messageType = NimMessageType.TEXT;
addToMessageBody("msg", text);
}
/**
* 发送图片消息
*
* @param name 图片名称
* @param md5 图片文件 md5按照字节流加密
* @param url url
* @param ext 图片后缀
* @param width 单位为像素
* @param height 单位为像素
* @param size 图片文件大小单位为字节Byte
*/
public void setAsImageMessage(String name,
String md5,
String url,
String ext,
int width,
int height,
int size) {
messageType = NimMessageType.IMAGE;
addToMessageBody("name", name);
addToMessageBody("md5", md5);
addToMessageBody("url", url);
addToMessageBody("ext", ext);
addToMessageBody("w", width);
addToMessageBody("h", height);
addToMessageBody("size", size);
}
private void addToMessageBody(String key, Object value) {
if (messageBody == null)
messageBody = new java.util.HashMap<>();
messageBody.put(key, value);
}
}

View File

@ -0,0 +1,71 @@
package cn.axzo.im.center.api.vo.req;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import lombok.Getter;
import lombok.Setter;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author yanglin
*/
@Setter
@Getter
public class SendMessageRequest {
/**
* 业务id
*/
@NotBlank(message = "业务id不能为空")
private String bizId;
/**
* 发送人
*/
@NotNull(message = "发送人不能为空")
private PersonAccountAttribute sender;
/**
* 消息接收用户信息
*/
@Valid
private Set<PersonAccountAttribute> receivePersons;
/**
* 消息接收IM账号或群id
*/
private Set<String> imReceiveAccounts;
public Long determineSenderPersonId() {
if (sender != null)
return Long.parseLong(sender.getPersonId());
return 0L;
}
public boolean isSendByRobot() {
return sender.getAppType() == null;
}
public Set<PersonAccountAttribute> receivePersonsOrEmpty() {
if (receivePersons == null)
return Collections.emptySet();
return new HashSet<>(receivePersons);
}
public Set<String> imReceiveAccountsOrEmpty() {
return imReceiveAccounts == null ? Collections.emptySet() : imReceiveAccounts;
}
public Long determineSenderOuId() {
if (sender != null)
return sender.ouIdOrDefault();
return 0L;
}
}

View File

@ -1,40 +1,17 @@
package cn.axzo.im.center.api.vo.req;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import cn.axzo.im.center.common.enums.TemplatedMsgType;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SendTemplateMessageParam {
/**
* 发送人
*/
@NotNull(message = "发送人不能为空")
private PersonAccountAttribute sender;
/**
* 消息接收用户信息
*/
@NotEmpty(message = "消息接收用户信息不能为空")
@Valid
private List<PersonAccountAttribute> receivePersons;
@Setter
@Getter
public class SendTemplateMessageParam extends SendMessageRequest {
/**
* 消息标题
@ -63,11 +40,6 @@ public class SendTemplateMessageParam {
*/
private JSONObject ext;
/**
* 业务的唯一ID用于查询发送消息的记录和结果不验证唯一
*/
private String bizId;
private Integer sendPriority;
private TemplatedMsgType templatedMsgType = TemplatedMsgType.TEMPLATE;
@ -77,34 +49,14 @@ public class SendTemplateMessageParam {
*/
private boolean isUpdatable;
public boolean isSendByRobot() {
return sender.getAppType() == null;
}
private PushContent pushContent;
private List<ExcludePushPayload> excludePushPayloads;
public Long determineSenderPersonId() {
if (sender != null)
return Long.parseLong(sender.getPersonId());
return 0L;
}
public Long determineSenderOuId() {
if (sender != null)
return sender.getOuId();
return 0L;
}
public boolean isUpdatable() {
return isUpdatable;
}
public Set<PersonAccountAttribute> uniqueReceivePersons() {
return new HashSet<>(receivePersons);
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -0,0 +1,30 @@
package cn.axzo.im.center.api.vo.resp;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import java.util.HashSet;
import java.util.Set;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupAddMembersResponse {
/**
* 未找到IM账号的列表
*/
private Set<PersonAccountAttribute> accountsNotFound = new HashSet<>();
private JSONObject faccid;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,30 @@
package cn.axzo.im.center.api.vo.resp;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import java.util.Set;
/**
* @author yanglin
*/
@Setter @Getter
public class GroupCreateResponse {
/**
* 群id
*/
private Long tid;
/**
* 未找到IM账号的列表
*/
private Set<PersonAccountAttribute> accountsNotFound;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,20 @@
package cn.axzo.im.center.api.vo.resp;
import cn.axzo.im.center.api.vo.group.GroupInfo;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupGetInfoResponse {
private GroupInfo group;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,19 @@
package cn.axzo.im.center.api.vo.resp;
import cn.axzo.im.center.api.vo.group.GroupMemberInfo;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupGetMembersResponse {
/**
* 群成员信息列表
*/
private List<GroupMemberInfo> members;
}

View File

@ -0,0 +1,19 @@
package cn.axzo.im.center.api.vo.resp;
import cn.axzo.im.center.api.vo.group.GroupMemberInfo;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupGetOwnerResponse {
/**
* 群主信息
*/
private GroupMemberInfo owner;
}

View File

@ -0,0 +1,86 @@
package cn.axzo.im.center.api.vo.resp;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.NimFromClientType;
import cn.axzo.im.center.common.enums.NimMessageType;
import cn.axzo.im.center.common.enums.YesOrNo;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @author yanglin
*/
@Setter @Getter
public class GroupMessagePageQueryResponse {
/**
* 服务器消息id
*/
private Long id;
/**
* 群id
*/
private Long tid;
/**
* 发送者IM账号
*/
private String fromAccount;
/**
* 发送者自然人ID
*/
private Long fromPersonId;
/**
* 发送者OU ID
*/
private Long fromPersonOuId;
/**
* 发送者应用类型
*/
private AppTypeEnum fromPersonAppType;
/**
* 是否来自机器人
*/
private YesOrNo isFromRobot;
/**
* 云信消息ID
*/
private String messageId;
/**
* 消息类型. TEXT: 文本, IMAGE: 图片, SPEECH: 语音, VIDEO: 视频, POSITION: 地理位置, FILE: 文件, CUSTOM: 自定义消息
*/
private NimMessageType messageType;
/**
* 发送者的客户端类型. ANDROID, IOS, PC, WEB, REST, MAC
*/
private NimFromClientType fromClientType;
/**
* 发送时间
*/
private Long sendTime;
/**
* 消息体. 具体格式根据messageType来定, 字段参考云信文档的body部分: https://doc.yunxin.163.com/messaging/server-apis/DE0MTk0OTY?platform=server#%E5%8E%86%E5%8F%B2%E6%B6%88%E6%81%AF%E6%9F%A5%E8%AF%A2%E8%BF%94%E5%9B%9E%E7%9A%84%E6%B6%88%E6%81%AF%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E
*/
private JSONObject body;
/**
* 单位名称
*/
private String unitName;
/**
* 发送者名称
*/
private String fromPersonName;
/**
* 发送者头像
*/
private String fromPersonAvatar;
/**
* 客户端id
*/
private String messageIdClient;
}

View File

@ -11,5 +11,6 @@ import lombok.Setter;
@Getter
public class UpdatableMessageSendResult {
private String bizMessageId;
private PersonAccountAttribute account;
private PersonAccountAttribute person;
private String imAccount;
}

View File

@ -1,6 +1,7 @@
package cn.axzo.im.center.common.enums;
import cn.axzo.basics.common.constant.enums.CodeDefinition;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
@ -26,9 +27,14 @@ public enum AppTypeEnum implements CodeDefinition<String> {
/**
* 服务器 机器人
*/
SYSTEM("system", "服务器");
SYSTEM("system", "服务器"),
/**
* 没有端信息
*/
NONE("none", "没有端信息");
@EnumValue
private final String code;
private final String message;

View File

@ -0,0 +1,18 @@
package cn.axzo.im.center.common.enums;
import cn.axzo.basics.common.constant.enums.CodeDefinition;
/**
* @author yanglin
*/
public enum GroupMemberType implements CodeDefinition<String> {
OWNER,
ADMIN,
MEMBER;
@Override
public String getCode() {
return name();
}
}

View File

@ -0,0 +1,27 @@
package cn.axzo.im.center.common.enums;
import cn.axzo.basics.common.constant.enums.CodeDefinition;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author yanglin
*/
@Getter
@RequiredArgsConstructor
public enum GroupType implements CodeDefinition<String> {
NONE("未指定"),
VISA("变洽签"),
WORKSPACE("项目群"),
WORKSPACE_OU("项目单位群"),
WORKSPACE_TEAM("项目班组群"),
;
private final String description;
@Override
public String getCode() {
return name();
}
}

View File

@ -0,0 +1,25 @@
package cn.axzo.im.center.common.enums;
import cn.axzo.basics.common.constant.enums.CodeDefinition;
import lombok.RequiredArgsConstructor;
/**
* @author yanglin
*/
@RequiredArgsConstructor
public enum NimFromClientType implements CodeDefinition<Integer> {
ANDROID(1),
IOS(2),
PC(4),
WEB(16),
REST(32),
MAC(64)
;
private final Integer nimCode;
@Override
public Integer getCode() {
return nimCode;
}
}

View File

@ -0,0 +1,29 @@
package cn.axzo.im.center.common.enums;
import cn.axzo.basics.common.constant.enums.CodeDefinition;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @author yanglin
*/
@Getter
@RequiredArgsConstructor
public enum NimMessageType implements CodeDefinition<Integer> {
TEXT("文本", 0),
IMAGE("图片", 1),
SPEECH("语音", 2),
VIDEO("视频", 3),
POSITION("地理位置", 4),
FILE("文件", 6),
CUSTOM("自定义", 100),
;
private final String name;
private final int nimCode;
@Override
public Integer getCode() {
return nimCode;
}
}

View File

@ -20,4 +20,11 @@ public enum YesOrNo implements CodeDefinition<String> {
private final String code;
private final String desc;
public static YesOrNo valueOf(boolean value) {
return value ? YES : NO;
}
public boolean booleanValue() {
return this == YES;
}
}

View File

@ -27,6 +27,7 @@ import cn.axzo.im.channel.netease.dto.RegisterResponse;
import cn.axzo.im.channel.netease.dto.RegisterUpdateRequest;
import cn.axzo.im.channel.netease.dto.UserAddChatGroupRequest;
import cn.axzo.im.channel.netease.dto.UserAddChatGroupResponse;
import cn.axzo.im.group.support.GroupRateLimiter;
import cn.axzo.im.utils.ImProperties;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpRequest;
@ -133,6 +134,9 @@ public class NimChannelService implements IMChannelProvider {
@Resource
private ImProperties props;
@Resource
private GroupRateLimiter rateLimiter;
@Override
public String getProviderAppKey() {
return appKeyUtil.getAppKey();
@ -213,8 +217,6 @@ public class NimChannelService implements IMChannelProvider {
if (messageInfo == null) {
throw new ServiceException("发送单聊消息请求,请求参数不能为空!");
}
//目前支持的默认值 0单聊消息 100自定义消息
messageInfo.setOpe(MSG_OPE);
HashMap<String, Object> paramMap = Maps.newHashMap();
paramMap.put("from", messageInfo.getFrom());
paramMap.put("body", messageInfo.getBody());
@ -397,6 +399,7 @@ public class NimChannelService implements IMChannelProvider {
Map<String, String> authHeaderMap = buildAuthHeader(getProviderAppKey(), getProviderAppSecret());
log.warn("chatGroupCreate-请求网易云信,URL:{},Header:{},请求参数:{}", CHAT_GROUP_CREATE,
JSONUtil.toJsonStr(authHeaderMap), JSONUtil.toJsonStr(paramMap));
rateLimiter.requireCreateGroup();
HttpResponse response = HttpRequest.post(CHAT_GROUP_CREATE).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
String result = response.body();
@ -454,6 +457,7 @@ public class NimChannelService implements IMChannelProvider {
Map<String, String> authHeaderMap = buildAuthHeader(getProviderAppKey(), getProviderAppSecret());
log.warn("userAddChatGroup-请求网易云信,URL:{},Header:{},请求参数:{}", USER_ADD_CHAT_GROUP,
JSONUtil.toJsonStr(authHeaderMap), JSONUtil.toJsonStr(paramMap));
rateLimiter.requireAddMember();
HttpResponse response = HttpRequest.post(USER_ADD_CHAT_GROUP).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
String result = response.body();
@ -486,6 +490,7 @@ public class NimChannelService implements IMChannelProvider {
Map<String, String> authHeaderMap = buildAuthHeader(getProviderAppKey(), getProviderAppSecret());
log.warn("kickChatGroup-请求网易云信,URL:{},Header:{},请求参数:{}", KICK_CHAT_GROUP,
JSONUtil.toJsonStr(authHeaderMap), JSONUtil.toJsonStr(paramMap));
rateLimiter.requireRemoveMember();
HttpResponse response = HttpRequest.post(KICK_CHAT_GROUP).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
String result = response.body();
@ -542,6 +547,7 @@ public class NimChannelService implements IMChannelProvider {
Map<String, String> authHeaderMap = buildAuthHeader(getProviderAppKey(), getProviderAppSecret());
log.warn("chatGroupQuery-请求网易云信,URL:{},Header:{},请求参数:{}", CHAT_GROUP_QUERY,
JSONUtil.toJsonStr(authHeaderMap), JSONUtil.toJsonStr(paramMap));
rateLimiter.requireGetGroupInfo();
HttpResponse response = HttpRequest.post(CHAT_GROUP_QUERY).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
String result = response.body();
@ -582,6 +588,7 @@ public class NimChannelService implements IMChannelProvider {
Map<String, String> authHeaderMap = buildAuthHeader(getProviderAppKey(), getProviderAppSecret());
log.warn("changeOwner-请求网易云信,URL:{},Header:{},请求参数:{}", CHANGE_OWNER,
JSONUtil.toJsonStr(authHeaderMap), JSONUtil.toJsonStr(paramMap));
rateLimiter.requireChangeOwner();
HttpResponse response = HttpRequest.post(CHANGE_OWNER).addHeaders(authHeaderMap)
.form(paramMap).timeout(5000).execute();
String result = response.body();

View File

@ -1,24 +1,32 @@
package cn.axzo.im.channel.netease.client;
import cn.axzo.im.channel.netease.dto.BatchSendCustomMessageRequest;
import cn.axzo.im.channel.netease.dto.BatchSendCustomMessageResponse;
import cn.axzo.im.channel.netease.dto.DismissGroupRequest;
import cn.axzo.im.channel.netease.dto.DismissGroupResponse;
import cn.axzo.im.channel.netease.dto.GetAccountInfoRequest;
import cn.axzo.im.channel.netease.dto.GetAccountInfoResponse;
import cn.axzo.im.channel.netease.dto.GetGroupInfoRequest;
import cn.axzo.im.channel.netease.dto.GetGroupInfoResponse;
import cn.axzo.im.channel.netease.dto.QueryEventRequest;
import cn.axzo.im.channel.netease.dto.QueryEventResponse;
import cn.axzo.im.channel.netease.dto.QueryMessageRequest;
import cn.axzo.im.channel.netease.dto.QueryMessageResponse;
import cn.axzo.im.channel.netease.dto.RefreshTokenRequest;
import cn.axzo.im.channel.netease.dto.RefreshTokenResponse;
import cn.axzo.im.channel.netease.dto.RevokeMessageRequest;
import cn.axzo.im.channel.netease.dto.SendCustomMessageRequest;
import cn.axzo.im.channel.netease.dto.SendCustomMessageResponse;
import cn.axzo.im.channel.netease.dto.UpdateAccountInfoRequest;
import cn.axzo.im.channel.netease.dto.UpdateAccountInfoResponse;
import cn.axzo.im.channel.netease.dto.NimBatchSendCustomMessageRequest;
import cn.axzo.im.channel.netease.dto.NimBatchSendCustomMessageResponse;
import cn.axzo.im.channel.netease.dto.NimGetAccountInfoRequest;
import cn.axzo.im.channel.netease.dto.NimGetAccountInfoResponse;
import cn.axzo.im.channel.netease.dto.NimGroupAddMembersRequest;
import cn.axzo.im.channel.netease.dto.NimGroupAddMembersResponse;
import cn.axzo.im.channel.netease.dto.NimGroupCreateRequest;
import cn.axzo.im.channel.netease.dto.NimGroupCreateResponse;
import cn.axzo.im.channel.netease.dto.NimGroupDismissRequest;
import cn.axzo.im.channel.netease.dto.NimGroupDismissResponse;
import cn.axzo.im.channel.netease.dto.NimGroupGetInfoRequest;
import cn.axzo.im.channel.netease.dto.NimGroupGetInfoResponse;
import cn.axzo.im.channel.netease.dto.NimGroupGetMessagesRequest;
import cn.axzo.im.channel.netease.dto.NimGroupGetMessagesResponse;
import cn.axzo.im.channel.netease.dto.NimGroupRemoveMembersRequest;
import cn.axzo.im.channel.netease.dto.NimGroupRemoveMembersResponse;
import cn.axzo.im.channel.netease.dto.NimQueryEventRequest;
import cn.axzo.im.channel.netease.dto.NimQueryEventResponse;
import cn.axzo.im.channel.netease.dto.NimQueryMessageRequest;
import cn.axzo.im.channel.netease.dto.NimQueryMessageResponse;
import cn.axzo.im.channel.netease.dto.NimRefreshTokenRequest;
import cn.axzo.im.channel.netease.dto.NimRefreshTokenResponse;
import cn.axzo.im.channel.netease.dto.NimRevokeMessageRequest;
import cn.axzo.im.channel.netease.dto.NimSendCustomMessageRequest;
import cn.axzo.im.channel.netease.dto.NimSendCustomMessageResponse;
import cn.axzo.im.channel.netease.dto.NimUpdateAccountInfoRequest;
import cn.axzo.im.channel.netease.dto.NimUpdateAccountInfoResponse;
import lombok.Data;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
@ -37,37 +45,49 @@ public interface NimClient {
String URL = "https://api.netease.im/nimserver";
@PostMapping(value = "/msg/delMsgOneWay.action")
CodeResponse revoke(RevokeMessageRequest request);
NimCodeResponse revoke(NimRevokeMessageRequest request);
@PostMapping(value = "/history/queryUserEvents.action")
QueryEventResponse queryEvents(QueryEventRequest request);
NimQueryEventResponse queryEvents(NimQueryEventRequest request);
@PostMapping(value = "/history/querySessionMsg.action")
QueryMessageResponse queryMessages(QueryMessageRequest request);
NimQueryMessageResponse queryMessages(NimQueryMessageRequest request);
@PostMapping(value = "/user/refreshToken.action")
RefreshTokenResponse refreshToken(RefreshTokenRequest request);
NimRefreshTokenResponse refreshToken(NimRefreshTokenRequest request);
@PostMapping(value = "/msg/sendAttachMsg.action")
SendCustomMessageResponse sendCustomMessage(SendCustomMessageRequest request);
NimSendCustomMessageResponse sendCustomMessage(NimSendCustomMessageRequest request);
@PostMapping(value = "/msg/sendBatchAttachMsg.action")
BatchSendCustomMessageResponse batchSendCustomMessage(BatchSendCustomMessageRequest request);
NimBatchSendCustomMessageResponse batchSendCustomMessage(NimBatchSendCustomMessageRequest request);
@PostMapping(value = "/user/getUinfos.action")
GetAccountInfoResponse getAccountInfo(GetAccountInfoRequest request);
NimGetAccountInfoResponse getAccountInfo(NimGetAccountInfoRequest request);
@PostMapping(value = "/user/updateUinfo.action")
UpdateAccountInfoResponse updateAccountInfo(UpdateAccountInfoRequest request);
NimUpdateAccountInfoResponse updateAccountInfo(NimUpdateAccountInfoRequest request);
@PostMapping(value = "/team/remove.action")
DismissGroupResponse dismissGroup(DismissGroupRequest request);
NimGroupDismissResponse dismissGroup(NimGroupDismissRequest request);
@PostMapping(value = "/team/queryDetail.action")
GetGroupInfoResponse getGroupInfo(GetGroupInfoRequest request);
NimGroupGetInfoResponse getGroupInfo(NimGroupGetInfoRequest request);
@PostMapping(value = "/team/create.action")
NimGroupCreateResponse createGroup(NimGroupCreateRequest request);
@PostMapping(value = "/team/add.action")
NimGroupAddMembersResponse addGroupMembers(NimGroupAddMembersRequest request);
@PostMapping(value = "/team/kick.action")
NimGroupRemoveMembersResponse removeGroupMembers(NimGroupRemoveMembersRequest request);
@PostMapping(value = "/history/queryTeamMsg.action")
NimGroupGetMessagesResponse getGroupMessages(NimGroupGetMessagesRequest request);
@Data
class CodeResponse {
class NimCodeResponse {
private Integer code;
private String desc;
private int size;

View File

@ -1,13 +0,0 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.NimClient;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class DismissGroupResponse extends NimClient.CodeResponse {
}

View File

@ -1,16 +0,0 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.NimClient;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class GetGroupInfoResponse extends NimClient.CodeResponse {
private Object tinfo;
}

View File

@ -0,0 +1,27 @@
package cn.axzo.im.channel.netease.dto;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
/**
* @author yanglin
*/
@Setter
@Getter
public class GroupMemberRequest {
private String members;
public void addMembers(Collection<String> members) {
if (StringUtils.isBlank(this.members))
this.members = "[]";
this.members = JSON.parseArray(this.members)
.fluentAddAll(members)
.toJSONString();
}
}

View File

@ -1,5 +1,6 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.group.controller.GroupMessageController;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
@ -38,7 +39,9 @@ public class MessageBody {
private String msgContent;
/**
* 消息通知结构体
* @see GroupMessageController 有字符串引用
*
* 消息通知结构体, 名称不能随便调整
*/
private String msgBody;

View File

@ -9,7 +9,7 @@ import lombok.Data;
*/
@Data
@FormRequest
public class BatchSendCustomMessageRequest implements CustomPushRequest {
public class NimBatchSendCustomMessageRequest implements CustomPushRequest {
@JSONField(name = "fromAccid")
private String fromAccount;
// ["aaa","bbb"]

View File

@ -13,7 +13,7 @@ import java.util.Set;
*/
@Setter
@Getter
public class BatchSendCustomMessageResponse extends NimClient.CodeResponse {
public class NimBatchSendCustomMessageResponse extends NimClient.NimCodeResponse {
private String desc;
private Set<String> unregister = Collections.emptySet();

View File

@ -3,10 +3,8 @@ package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.FormRequest;
import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -15,7 +13,7 @@ import java.util.List;
*/
@Data
@FormRequest
public class GetAccountInfoRequest {
public class NimGetAccountInfoRequest {
/**
* 用户帐号 ID 列表例如["id1", "id2", "id3"]格式错误会返回 414 参数错误

View File

@ -15,7 +15,7 @@ import java.util.Optional;
*/
@Setter
@Getter
public class GetAccountInfoResponse extends NimClient.CodeResponse {
public class NimGetAccountInfoResponse extends NimClient.NimCodeResponse {
private List<AccountInfo> uinfos;

View File

@ -0,0 +1,24 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.FormRequest;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
@FormRequest
public class NimGroupAddMembersRequest extends GroupMemberRequest {
private Long tid;
private String owner;
private String msg;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,22 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.NimClient;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class NimGroupAddMembersResponse extends NimClient.NimCodeResponse {
private JSONObject faccid;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,109 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.FormRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
/**
* @author yanglin
*/
@Setter @Getter
@FormRequest
public class NimGroupCreateRequest extends GroupMemberRequest {
/**
* 群名称最大长度 64 位字符
*/
@JSONField(name = "tname")
private String name;
/**
* 群主账号accid最大长度 32 位字符
*/
private String owner;
/**
* 群描述最大长度 512 位字符
*/
@JSONField(name = "intro")
private String description;
/**
* 邀请发送的文字最大长度 150 位字符
*/
@JSONField(name = "msg")
private String introduceMessage;
/**
* 创建群时 members 不为空那么邀请其入群是否需要同意
* 0: 不需要被邀请人同意加入群默认
* 1: 需要被邀请人同意才可以加入群
* 只有当 beinvitemode = 0 magree 才能设为 1 magree =1 才生效
*/
@JSONField(name = "magree")
private Integer memberAgree;
/**
* 群创建完成后通过 SDK 侧操作申请入群的验证方式
* 0: 不用验证
* 1: 需要群主或管理员的验证
* 2: 不允许任何人加入
*/
@JSONField(name = "joinmode")
private Integer joinMode = 0;
/**
* 自定义高级群扩展属性第三方可以跟据此属性自定义扩展自己的群属性建议为 JSON最大长度 1024 位字符
*/
private String custom;
/**
* 群头像最大长度 1024 位字符
*/
private String icon;
/**
* 群创建完成后邀请入群时是否需要被邀请人的同意
* 0: 需要同意默认
* 1: 不需要同意
*/
@JSONField(name = "beinvitemode")
private Integer beInviteMode = 1;
/**
* 邀请权限即谁可以邀请他人入群
* 0: 群主和管理员默认
* 1: 所有人
*/
@JSONField(name = "invitemode")
private Integer inviteMode = 1;
/**
* 自定义扩展字段最大长度 512 位字符
*/
private String attach;
public void addAttachment(String key, Object value) {
if (StringUtils.isBlank(this.attach))
this.attach = "{}";
this.attach = JSON.parseObject(this.attach)
.fluentPut(key, value)
.toJSONString();
}
public void addCustom(String key, Object value) {
if (StringUtils.isBlank(this.custom))
this.custom = "{}";
this.custom = JSON.parseObject(this.custom)
.fluentPut(key, value)
.toJSONString();
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,32 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.NimClient;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author yanglin
*/
@Setter
@Getter
public class NimGroupCreateResponse extends NimClient.NimCodeResponse {
/**
* 群id
*/
private Long tid;
/**
* 入群失败的账号accid列表
* 如果创建时邀请的成员中存在加群数量超过限制的情况会返回入群失败的 accid 以及附言msg
*/
private List<String> faccid;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -10,9 +10,9 @@ import javax.validation.constraints.NotBlank;
*/
@Data
@FormRequest
public class DismissGroupRequest {
public class NimGroupDismissRequest {
@NotBlank(message = "群id不能为空")
private String tid;
private Long tid;
@NotBlank(message = "群主id不能为空")
private String owner;
}

View File

@ -0,0 +1,22 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.NimClient;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class NimGroupDismissResponse extends NimClient.NimCodeResponse {
public boolean isNotGroupOwnerError() {
return getCode() == 403;
}
public boolean isGroupNotFoundError() {
return getCode() == 803;
}
}

View File

@ -1,6 +1,7 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.FormRequest;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@ -10,9 +11,13 @@ import javax.validation.constraints.NotBlank;
*/
@Data
@FormRequest
public class GetGroupInfoRequest {
public class NimGroupGetInfoRequest {
@NotBlank(message = "群id不能为空")
private String tid;
private Long tid;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,25 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.NimClient;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class NimGroupGetInfoResponse extends NimClient.NimCodeResponse {
private NimGroupInfo tinfo;
public boolean isGroupNotFoundError() {
return getCode() == 414;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,34 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.FormRequest;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
@FormRequest
public class NimGroupGetMessagesRequest {
private Long tid;
private String accid;
private Long begintime;
private Long endtime;
private Long limit;
// 1 按时间正序排列2 按时间降序排列其它返回参数 414 错误默认是按降序排列即时间戳最晚的消息排在最前面
private Integer reverse;
// 查询指定的多个消息类型类型之间用","分割不设置该参数则查询全部类型消息格式示例0,1,2,3
//类型支持0:文本1:图片2:语音3:视频4:地理位置5:通知6:文件10:提示11:Robot100:自定义
private String type;
private Boolean checkTeamValid;
private Boolean includeNoSenseMsg;
// 结束查询的最后一条消息的 msgid不包含在查询结果中用于定位锚点
private Long excludeMsgid;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,35 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.NimClient;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author yanglin
*/
@Setter
@Getter
public class NimGroupGetMessagesResponse extends NimClient.NimCodeResponse {
private List<NimGroupMessage> msgs;
@Setter
@Getter
public static class NimGroupMessage {
private String from;
private String msgid;
private Long sendtime;
private Integer type;
private Integer fromclienttype;
private String msgidclient;
private JSONObject body;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,32 @@
package cn.axzo.im.channel.netease.dto;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Getter;
import lombok.Setter;
import java.util.HashSet;
import java.util.Set;
/**
* @author yanglin
*/
@Setter
@Getter
public class NimGroupInfo {
private NimGroupMemberInfo owner;
private Set<NimGroupMemberInfo> admins;
private Set<NimGroupMemberInfo> members;
@JSONField(serialize = false, deserialize = false)
public Set<NimGroupMemberInfo> getPeople() {
HashSet<NimGroupMemberInfo> people = new HashSet<>();
people.add(owner);
if (admins != null)
people.addAll(admins);
if (this.members != null)
people.addAll(this.members);
return people;
}
}

View File

@ -0,0 +1,32 @@
package cn.axzo.im.channel.netease.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import java.util.Objects;
/**
* @author yanglin
*/
@Setter
@Getter
public class NimGroupMemberInfo {
private String nick;
private Date createtime;
private Date updatetime;
private String accid;
private boolean mute;
@Override
public boolean equals(Object o) {
if (!(o instanceof NimGroupMemberInfo)) return false;
NimGroupMemberInfo that = (NimGroupMemberInfo) o;
return Objects.equals(accid, that.accid);
}
@Override
public int hashCode() {
return Objects.hashCode(accid);
}
}

View File

@ -0,0 +1,22 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.FormRequest;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
@FormRequest
public class NimGroupRemoveMembersRequest extends GroupMemberRequest {
private Long tid;
private String owner;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,21 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.NimClient;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class NimGroupRemoveMembersResponse extends NimClient.NimCodeResponse {
private JSONObject faccid;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -14,7 +14,7 @@ import java.text.SimpleDateFormat;
*/
@Data
@FormRequest
public class QueryEventRequest {
public class NimQueryEventRequest {
// 要查询用户的accid
@JSONField(name = "accid")
@NotBlank

View File

@ -11,7 +11,7 @@ import java.util.List;
*/
@Setter
@Getter
public class QueryEventResponse extends NimClient.CodeResponse {
public class NimQueryEventResponse extends NimClient.NimCodeResponse {
// 总共记录数
private int size;
private List<EventItem> events;

View File

@ -14,7 +14,7 @@ import java.text.SimpleDateFormat;
*/
@Data
@FormRequest
public class QueryMessageRequest {
public class NimQueryMessageRequest {
// 发送者accid
@NotBlank
private String from;

View File

@ -1,7 +1,6 @@
package cn.axzo.im.channel.netease.dto;
import cn.axzo.im.channel.netease.client.NimClient;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@ -13,7 +12,7 @@ import java.util.Map;
*/
@Setter
@Getter
public class QueryMessageResponse extends NimClient.CodeResponse {
public class NimQueryMessageResponse extends NimClient.NimCodeResponse {
// 消息集合
private List<Map<String, ?>> msgs;
}

View File

@ -11,7 +11,7 @@ import javax.validation.constraints.NotBlank;
*/
@Data
@FormRequest
public class RefreshTokenRequest {
public class NimRefreshTokenRequest {
@NotBlank
@JSONField(name = "accid")
private String accid;

View File

@ -10,7 +10,7 @@ import lombok.Setter;
*/
@Setter
@Getter
public class RefreshTokenResponse extends NimClient.CodeResponse {
public class NimRefreshTokenResponse extends NimClient.NimCodeResponse {
private Info info;

View File

@ -9,7 +9,7 @@ import lombok.Data;
*/
@Data
@FormRequest
public class RevokeMessageRequest {
public class NimRevokeMessageRequest {
@JSONField(name = "deleteMsgid")
private String messageId;
// 消息发送者的云信 IM 账号accid

View File

@ -9,7 +9,7 @@ import lombok.Data;
*/
@Data
@FormRequest
public class SendCustomMessageRequest implements CustomPushRequest {
public class NimSendCustomMessageRequest implements CustomPushRequest {
@JSONField(name = "from")
private String fromAccount;
@JSONField(name = "to")

View File

@ -10,7 +10,7 @@ import lombok.Setter;
*/
@Setter
@Getter
public class SendCustomMessageResponse extends NimClient.CodeResponse {
public class NimSendCustomMessageResponse extends NimClient.NimCodeResponse {
private String desc;

View File

@ -11,7 +11,7 @@ import lombok.Data;
*/
@Data
@FormRequest
public class UpdateAccountInfoRequest {
public class NimUpdateAccountInfoRequest {
@JSONField(name = "accid")
private String imAccountId;

View File

@ -10,7 +10,7 @@ import lombok.Setter;
*/
@Setter
@Getter
public class UpdateAccountInfoResponse extends NimClient.CodeResponse {
public class NimUpdateAccountInfoResponse extends NimClient.NimCodeResponse {
private NimAccountInfo info;
@Override

View File

@ -31,6 +31,7 @@ public class MqProducer {
}
//生产消息
eventProducer.send(event);
log.info("send mq:{}", JSON.toJSONString(event));
}
public void sendBatch(List<Event> events){

View File

@ -13,6 +13,7 @@ import cn.axzo.im.center.api.vo.req.CustomMessageInfo;
import cn.axzo.im.center.api.vo.req.FetchUpdatableMessageRequest;
import cn.axzo.im.center.api.vo.req.GetMessageDetailRequest;
import cn.axzo.im.center.api.vo.req.MessageInfo;
import cn.axzo.im.center.api.vo.req.SendChatMessageRequest;
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.req.UpdatableMessageAckRequest;
@ -27,19 +28,23 @@ import cn.axzo.im.center.api.vo.resp.UpdatableMessageSendResult;
import cn.axzo.im.center.api.vo.resp.UserAccountResp;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.entity.AccountRegister;
import cn.axzo.im.entity.MessageHistory;
import cn.axzo.im.entity.MessageTask;
import cn.axzo.im.enums.MessageHistoryStatus;
import cn.axzo.im.enums.MessageTaskStatus;
import cn.axzo.im.send.handler.CommonSendOneHandler;
import cn.axzo.im.service.AccountRegisterService;
import cn.axzo.im.service.AccountService;
import cn.axzo.im.service.CustomMessageService;
import cn.axzo.im.service.MessageHistoryService;
import cn.axzo.im.service.MessageTaskService;
import cn.axzo.im.service.RobotMsgTemplateService;
import cn.axzo.im.service.impl.MessageHistoryServiceImpl;
import cn.axzo.im.updatable.UpdatableMessageManager;
import cn.axzo.im.updatable.UpdatableMessageQueryService;
import cn.axzo.im.updatable.UpdateSupport;
import cn.axzo.im.utils.BizAssertions;
import cn.axzo.pokonyan.exception.Aassert;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Sets;
@ -53,10 +58,12 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@ -68,7 +75,7 @@ import static cn.axzo.im.config.BizResultCode.SEND_IM_ACCOUNT_NOT_FOUND;
import static cn.axzo.im.config.BizResultCode.SEND_PERSSON_ERROR;
/**
* IM消息派发相关
* IM消息派发相关
*
* @author zuoqinbo
* @version V1.0
@ -89,15 +96,17 @@ public class MessageController implements MessageApi {
@Autowired
private AccountRegisterService accountRegisterService;
@Autowired
private MessageHistoryService messageHistoryService;
private MessageHistoryServiceImpl messageHistoryService;
@Autowired
private CustomMessageService customMessageService;
@Autowired
private UpdatableMessageManager updatableMessageManager;
@Autowired
private UpdateSupport updateSupport;
@Autowired
private UpdatableMessageQueryService updatableMessageQueryService;
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private CommonSendOneHandler commonSendOneHandler;
@Override
@ -112,7 +121,7 @@ public class MessageController implements MessageApi {
return ApiResult.ok(messageRespList);
}
@ExceptionHandler({ RequestNotPermitted.class })
@ExceptionHandler({RequestNotPermitted.class})
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public ApiResult<String> handleRequestNotPermitted() {
return ApiResult.err("服务器资源繁忙,请求被拒绝!");
@ -122,6 +131,7 @@ public class MessageController implements MessageApi {
/**
* 发送消息时只是存储在messageTask中通过xxlJob或者mq异步去处理
* 因为1为了提高接口响应性能2第三方接口有限流控制防止被限流后阻塞业务
*
* @param sendMessageParam 发送消息请求参数
* @return
*/
@ -184,6 +194,7 @@ public class MessageController implements MessageApi {
if (sender.getAppType() != null) {
sendImAccount = accountService.registerAccountIfAbsent(
sender.getPersonId(), sender.getOuId(), sender.getAppType());
BizAssertions.assertNotNull(sendImAccount, "创建账号失败");
} else {
sendImAccount = check(request);
}
@ -198,8 +209,17 @@ public class MessageController implements MessageApi {
.senderPersonId(request.determineSenderPersonId())
.build();
Date now = new Date();
List<MessageTask.ReceivePerson> receivePersons = JSONArray.parseArray(
JSONObject.toJSONString(request.uniqueReceivePersons()), MessageTask.ReceivePerson.class);
List<MessageTask.ReceivePerson> requestReceivePersons = JSONArray.parseArray(
JSONObject.toJSONString(request.receivePersonsOrEmpty()), MessageTask.ReceivePerson.class);
List<MessageTask.ReceivePerson> receivePersons = new ArrayList<>(requestReceivePersons);
if (CollectionUtils.isNotEmpty(request.getImReceiveAccounts())) {
for (String account : request.getImReceiveAccounts()) {
ensureImAccountNotBlank(account);
receivePersons.add(MessageTask.ReceivePerson.builder()
.imAccount(account)
.build());
}
}
MessageTask messageTask = messageTaskService.create(MessageTask.builder()
.bizId(request.getBizId())
.sendImAccount(sendImAccount)
@ -217,13 +237,72 @@ public class MessageController implements MessageApi {
.build());
List<UpdatableMessageSendResult> updatableMessageSendResults = Collections.emptyList();
if (request.isUpdatable()) {
updatableMessageSendResults = updatableMessageManager.createUpdatableMessage(messageTask, request, receivePersons);
updatableMessageSendResults = updatableMessageManager.createUpdatableMessage(
messageTask, request, requestReceivePersons, request.imReceiveAccountsOrEmpty());
}
MessageTaskResp messageTaskResp = toMessageTaskResp(messageTask);
messageTaskResp.setUpdatableMessageSendResults(updatableMessageSendResults);
return ApiResult.ok(messageTaskResp);
}
@Override
public ApiResult<Long> sendChatMessage(SendChatMessageRequest request) {
log.info("sendChatMessage, request={}", request);
PersonAccountAttribute sender = request.getSender();
BizAssertions.assertNotNull(sender.getAppType(), "发送人appType不能为空");
String sendImAccount = accountService.registerAccountIfAbsent(
sender.getPersonId(), sender.getOuId(), sender.getAppType());
BizAssertions.assertNotNull(sendImAccount, "创建账号失败");
Date now = new Date();
List<MessageTask.ReceivePerson> requestReceivePersons = JSONArray.parseArray(
JSONObject.toJSONString(request.receivePersonsOrEmpty()), MessageTask.ReceivePerson.class);
List<MessageTask.ReceivePerson> receivePersons = new ArrayList<>(requestReceivePersons);
if (CollectionUtils.isNotEmpty(request.getImReceiveAccounts())) {
for (String account : request.getImReceiveAccounts()) {
ensureImAccountNotBlank(account);
receivePersons.add(MessageTask.ReceivePerson.builder()
.imAccount(account)
.build());
}
}
int receiverSize = request.receivePersonsOrEmpty().size() + request.imReceiveAccountsOrEmpty().size();
BizAssertions.assertTrue(receiverSize >= 1, "接收人不能为空");
boolean syncSend = request.isTrySyncSend() && receiverSize == 1;
MessageTask.BizData bizData = MessageTask.BizData.builder()
.messageBody(JSON.toJSONString(request.getMessageBody()))
.isSenderRobot(false)
.syncSend(syncSend)
.senderPersonId(request.determineSenderPersonId())
.nimMessageType(request.getMessageType())
.build();
Long taskId = transactionTemplate.execute(unused -> {
MessageTask task = messageTaskService.create(MessageTask.builder()
.bizId(request.getBizId())
.sendImAccount(sendImAccount)
.receivePersons(receivePersons)
.status(MessageTaskStatus.PENDING)
.bizData(bizData)
.planStartTime(now)
.createAt(now)
.sendPriority(SendPriority.CHAT_MESSAGE.getPriority())
.apiChannel(ApiChannel.COMMON_MESSAGE)
.build());
if (syncSend) {
task = messageTaskService.getById(task.getId());
List<Long> historyIds = messageTaskService.createMessageHistory(task);
MessageHistory history = messageHistoryService.getById(historyIds.get(0));
commonSendOneHandler.updateSyncSendState(history, commonSendOneHandler.send(history));
}
return task.getId();
});
return ApiResult.ok(taskId);
}
private void ensureImAccountNotBlank(String imAccount) {
BizAssertions.assertTrue(StringUtils.isNotBlank(imAccount), "接收人IM账号不能为空");
BizAssertions.assertFalse("null".equalsIgnoreCase(imAccount), "接收人IM账号不能为空");
}
@Override
public ApiResult<MessageUpdateResponse> updateMessage(UpdateMessageRequest request) {
log.info("updateMessage, request={}", request);

View File

@ -1,12 +1,21 @@
package cn.axzo.im.controller;
import cn.axzo.im.center.api.vo.req.GroupAddMembersRequest;
import cn.axzo.im.center.api.vo.req.GroupCreateRequest;
import cn.axzo.im.center.api.vo.req.GroupDismissRequest;
import cn.axzo.im.center.api.vo.req.GroupRemoveMembersRequest;
import cn.axzo.im.center.api.vo.req.SendMessageParam;
import cn.axzo.im.channel.netease.client.NimClient;
import cn.axzo.im.channel.netease.dto.DismissGroupRequest;
import cn.axzo.im.channel.netease.dto.GetGroupInfoRequest;
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.channel.netease.dto.NimGroupDismissRequest;
import cn.axzo.im.channel.netease.dto.NimGroupGetInfoRequest;
import cn.axzo.im.channel.netease.dto.NimGroupGetInfoResponse;
import cn.axzo.im.channel.netease.dto.NimQueryEventRequest;
import cn.axzo.im.channel.netease.dto.NimQueryMessageRequest;
import cn.axzo.im.channel.netease.dto.NimRevokeMessageRequest;
import cn.axzo.im.dao.repository.GroupDao;
import cn.axzo.im.entity.Group;
import cn.axzo.im.group.GroupManager;
import cn.axzo.im.group.GroupMemberSyncer;
import cn.axzo.im.job.CreateMessageHistoryJob;
import cn.axzo.im.job.ExpungeImTaskJob;
import cn.axzo.im.job.RevokeAllMessagesJob;
@ -34,19 +43,22 @@ public class PrivateController {
private final CreateMessageHistoryJob createMessageHistoryJob;
private final MessageController messageController;
private final ExpungeImTaskJob expungeImTaskJob;
private final GroupManager groupManager;
private final GroupDao groupDao;
private final GroupMemberSyncer groupMemberSyncer;
@PostMapping("/private/revoke")
public Object revoke(@Valid @RequestBody RevokeMessageRequest request) {
public Object revoke(@Valid @RequestBody NimRevokeMessageRequest request) {
return nimClient.revoke(request);
}
@PostMapping("/private/queryEvents")
public Object queryEvents(@Valid @RequestBody QueryEventRequest request) {
public Object queryEvents(@Valid @RequestBody NimQueryEventRequest request) {
return nimClient.queryEvents(request);
}
@PostMapping("/private/queryMessages")
public Object queryMessages(@Valid @RequestBody QueryMessageRequest request) {
public Object queryMessages(@Valid @RequestBody NimQueryMessageRequest request) {
return nimClient.queryMessages(request);
}
@ -76,14 +88,51 @@ public class PrivateController {
return CommonResponse.success(count);
}
@PostMapping("/private/group/createGroup")
public Object createGroup(@Valid @RequestBody GroupCreateRequest request) {
return CommonResponse.success(groupManager.createGroup(request));
}
@PostMapping("/private/group/dismissGroup")
public Object dismissGroup(@Valid @RequestBody DismissGroupRequest request) {
return CommonResponse.success(nimClient.dismissGroup(request));
public Object dismissGroup(@Valid @RequestBody GroupDismissRequest request) {
groupManager.dismissGroup(request);
return CommonResponse.success();
}
@PostMapping("/private/group/dismissGroupDirectly")
public Object dismissGroupDirectly(@Valid @RequestBody GroupDismissRequest request) {
NimGroupGetInfoRequest req1 = new NimGroupGetInfoRequest();
req1.setTid(request.getTid());
NimGroupGetInfoResponse res1 = nimClient.getGroupInfo(req1);
NimGroupDismissRequest req2 = new NimGroupDismissRequest();
req2.setOwner(res1.getTinfo().getOwner().getAccid());
req2.setTid(request.getTid());
return CommonResponse.success(nimClient.dismissGroup(req2));
}
@PostMapping("/private/group/addMembers")
public Object addMembers(@Valid @RequestBody GroupAddMembersRequest request) {
groupManager.addMembers(request);
return CommonResponse.success();
}
@PostMapping("/private/group/removeMembers")
public Object removeMembers(@Valid @RequestBody GroupRemoveMembersRequest request) {
groupManager.removeMembers(request);
return CommonResponse.success();
}
@PostMapping("/private/group/getGroupInfo")
public Object getGroupInfo(@Valid @RequestBody GetGroupInfoRequest request) {
public Object getGroupInfo(@Valid @RequestBody NimGroupGetInfoRequest request) {
return CommonResponse.success(nimClient.getGroupInfo(request));
}
@PostMapping("/private/group/syncGroupMembers")
public Object syncGroupMembers(@RequestParam("tid") Long tid) {
Group group = groupDao.findByTid(tid, false).orElse(null);
if (group == null) return "group not found";
groupMemberSyncer.syncMembers(group);
return CommonResponse.success("done");
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,58 @@
package cn.axzo.im.dao.repository;
import cn.axzo.im.center.common.enums.GroupType;
import cn.axzo.im.center.common.enums.YesOrNo;
import cn.axzo.im.dao.mapper.GroupMapper;
import cn.axzo.im.entity.Group;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Repository;
import java.util.Date;
import java.util.Optional;
/**
* @author yanglin
*/
@Repository("groupDao")
public class GroupDao extends ServiceImpl<GroupMapper, Group> {
public void updateMembersCount(Long tid, Integer count) {
lambdaUpdate()
.eq(Group::getTid, tid)
.set(Group::getMemberCount, count)
.update();
}
public void updateTid(Long id, Long tid) {
lambdaUpdate()
.eq(Group::getId, id)
.set(Group::getTid, tid)
.update();
}
public Optional<Group> findByTid(Long tid, boolean forUpdate) {
Group group = lambdaQuery()
.eq(Group::getTid, tid)
.last(forUpdate, "FOR UPDATE")
.one();
return Optional.ofNullable(group);
}
public void setDismissed(Long tid) {
lambdaUpdate()
.eq(Group::getTid, tid)
.set(Group::getIsDismissed, YesOrNo.YES)
.set(Group::getDismissedAt, new Date())
.update();
}
public Optional<Group> findByBizCode(String bizCode, GroupType groupType, boolean forUpdate) {
return lambdaQuery()
.eq(Group::getBizCode, bizCode)
.eq(Group::getType, groupType)
.eq(Group::getIsDismissed, YesOrNo.NO)
.last(forUpdate, "FOR UPDATE")
.oneOpt();
}
}

View File

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

View File

@ -0,0 +1,94 @@
package cn.axzo.im.dao.repository;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import cn.axzo.im.center.common.enums.GroupMemberType;
import cn.axzo.im.dao.mapper.GroupMapperMapper;
import cn.axzo.im.entity.GroupMember;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static java.util.stream.Collectors.toSet;
/**
* @author yanglin
*/
@Repository("groupMemberDao")
public class GroupMemberDao extends ServiceImpl<GroupMapperMapper, GroupMember> {
public Optional<GroupMember> findByAccount(Long tid, String imAccount) {
return lambdaQuery()
.eq(GroupMember::getTid, tid)
.eq(GroupMember::getImAccount, imAccount)
.oneOpt();
}
public void deleteAccounts(Long tid) {
lambdaUpdate()
.eq(GroupMember::getTid, tid)
.remove();
}
public Set<PersonAccountAttribute> getAsPersons(Long tid) {
return getByTid(tid).stream()
.map(GroupMember::asPerson)
.collect(toSet());
}
public List<GroupMember> getByTid(Long tid) {
return lambdaQuery()
.eq(GroupMember::getTid, tid)
.list();
}
public void deleteByPersons(Long tid, Collection<PersonAccountAttribute> persons) {
if (CollectionUtils.isEmpty(persons))
return;
lambdaUpdate()
.eq(GroupMember::getTid, tid)
.nested(wrapper -> {
for (PersonAccountAttribute person : persons) {
wrapper.or()
.eq(GroupMember::getPersonId, Long.parseLong(person.getPersonId()))
.eq(GroupMember::getPersonOuId, person.ouIdOrDefault())
.eq(GroupMember::getAppType, person.getAppType());
}
})
.remove();
}
public Optional<GroupMember> findByPerson(Long tid, PersonAccountAttribute person) {
List<GroupMember> members = getByPersons(tid, Collections.singletonList(person));
return CollectionUtils.isEmpty(members) ? Optional.empty() : Optional.of(members.get(0));
}
public List<GroupMember> getByPersons(
Long tid, Collection<PersonAccountAttribute> persons) {
if (CollectionUtils.isEmpty(persons))
return Collections.emptyList();
return lambdaQuery()
.eq(GroupMember::getTid, tid)
.nested(wrapper -> {
for (PersonAccountAttribute person : persons) {
wrapper.or()
.eq(GroupMember::getPersonId, Long.parseLong(person.getPersonId()))
.eq(GroupMember::getPersonOuId, person.ouIdOrDefault())
.eq(GroupMember::getAppType, person.getAppType());
}
})
.list();
}
public GroupMember getOwner(Long tid) {
return lambdaQuery()
.eq(GroupMember::getTid, tid)
.eq(GroupMember::getMemberType, GroupMemberType.OWNER)
.one();
}
}

View File

@ -0,0 +1,33 @@
package cn.axzo.im.dao.repository;
import cn.axzo.im.dao.mapper.GroupMessageMapper;
import cn.axzo.im.entity.GroupMessage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author yanglin
*/
@Repository("groupMessageDao")
public class GroupMessageDao extends ServiceImpl<GroupMessageMapper, GroupMessage> {
public List<GroupMessage> reloadByNimId(Collection<GroupMessage> messages) {
if (CollectionUtils.isEmpty(messages))
return Collections.emptyList();
return lambdaQuery()
.nested(wrapper -> {
for (GroupMessage message : messages) {
wrapper.or().nested(nested -> nested
.eq(GroupMessage::getTid, message.getTid())
.eq(GroupMessage::getMessageId, message.getMessageId()));
}
})
.list();
}
}

View File

@ -1,6 +1,7 @@
package cn.axzo.im.entity;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@ -100,4 +101,10 @@ public class AccountRegister implements Serializable {
@TableField
private Date updateAt;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

View File

@ -0,0 +1,66 @@
package cn.axzo.im.entity;
import cn.axzo.im.center.common.enums.GroupType;
import cn.axzo.im.center.common.enums.YesOrNo;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import java.util.Map;
/**
* @author yanglin
*/
@Setter
@Getter
@TableName(value = "im_group", autoResultMap = true)
public class Group {
public static final String CUSTOM_GROUP_TYPE = "groupType";
public static final String CUSTOM_BIZ_CODE = "bizCode";
public static final String CUSTOM_BIZ_GROUP_INFO = "bizGroupInfo";
private Long id;
private Long tid;
private String name;
private String bizCode;
private GroupType type;
@TableField(typeHandler = FastjsonTypeHandler.class)
private Map<String, Object> bizGroupInfo;
private String avatar;
private Long memberCount;
private Long memberLimit;
private String ownerAccount;
private Long ownerPersonId;
private Long createPersonId;
private Date dismissedAt;
private Long isDelete;
private YesOrNo isDismissed;
private Date createAt;
private Date updateAt;
@TableField(typeHandler = FastjsonTypeHandler.class)
private RecordExt recordExt;
public boolean addMoreMembers(int memberCount) {
return memberLimit <= 0 || memberCount <= memberLimit;
}
public boolean isDismissed() {
return getIsDismissed() == YesOrNo.YES;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
@Setter
@Getter
public static class RecordExt {
}
}

View File

@ -0,0 +1,36 @@
package cn.axzo.im.entity;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @author yanglin
*/
@Setter
@Getter
@TableName(value = "im_group_log", autoResultMap = true)
public class GroupLog {
private Long id;
private String context;
private Long tid;
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject content;
@TableField(typeHandler = FastjsonTypeHandler.class)
private Object request;
@TableField(typeHandler = FastjsonTypeHandler.class)
private RecordExt recordExt;
private Long isDelete;
private Date createAt;
private Date updateAt;
@Setter
@Getter
public static class RecordExt {
}
}

View File

@ -0,0 +1,41 @@
package cn.axzo.im.entity;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.GroupMemberType;
import cn.axzo.im.center.common.enums.GroupType;
import cn.axzo.im.center.common.enums.YesOrNo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @author yanglin
*/
@Setter
@Getter
@TableName(value = "im_group_member", autoResultMap = true)
public class GroupMember {
private Long id;
private Long tid;
private GroupType groupType;
private String imAccount;
private Long personId;
private Long personOuId;
private AppTypeEnum appType;
private GroupMemberType memberType;
private YesOrNo isRobot;
private Long isDelete;
private Date createAt;
private Date updateAt;
public PersonAccountAttribute asPerson() {
PersonAccountAttribute person = new PersonAccountAttribute();
person.setPersonId(personId + "");
person.setOuId(personOuId);
person.setAppType(appType);
return person;
}
}

View File

@ -0,0 +1,53 @@
package cn.axzo.im.entity;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.NimFromClientType;
import cn.axzo.im.center.common.enums.NimMessageType;
import cn.axzo.im.center.common.enums.YesOrNo;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @author yanglin
*/
@Setter
@Getter
@TableName(value = "im_group_message", autoResultMap = true)
public class GroupMessage {
private Long id;
private Long tid;
private String fromAccount;
private Long fromPersonId;
private Long fromPersonOuId;
private AppTypeEnum fromPersonAppType;
private YesOrNo isFromRobot;
private String messageId;
private NimMessageType messageType;
private NimFromClientType fromClientType;
private String messageIdClient;
private Long sendTime;
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject body;
@TableField(typeHandler = FastjsonTypeHandler.class)
private RecordExt recordExt;
private Long isDelete;
private Date createAt;
private Date updateAt;
public RecordExt getOrCreateRecordExt() {
if (recordExt == null)
recordExt = new RecordExt();
return recordExt;
}
@Setter
@Getter
public static class RecordExt {
}
}

View File

@ -35,4 +35,7 @@ public class HistoryRecordExt {
private Long updateRetryCount;
private Map<String, String> initMessageExt;
private Long workspaceId;
private Integer nimMessageType;
private boolean syncSend;
}

View File

@ -14,6 +14,7 @@ import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import java.io.Serializable;
import java.util.Date;
@ -160,6 +161,8 @@ public class MessageHistory implements Serializable, NimMessageHistory {
public Optional<String> determineBatchNo() {
if (isUpdatableMessage())
return Optional.empty();
if (isSendToGroup())
return Optional.empty();
String batchNo = this.batchNo;
// 兼容在途数据
if (StringUtils.isBlank(batchNo) && imMessageTaskId != null)
@ -168,4 +171,9 @@ public class MessageHistory implements Serializable, NimMessageHistory {
batchNo = null;
return Optional.ofNullable(batchNo);
}
public boolean isSendToGroup() {
return AppTypeEnum.NONE.is(getAppType())
&& NumberUtils.isDigits(getToAccount());
}
}

View File

@ -6,9 +6,11 @@ import cn.axzo.im.center.api.vo.req.PushContent;
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.center.common.enums.NimMessageType;
import cn.axzo.im.center.common.enums.TemplatedMsgType;
import cn.axzo.im.config.BaseListTypeHandler;
import cn.axzo.im.enums.MessageTaskStatus;
import cn.axzo.im.utils.UUIDUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
@ -121,6 +123,8 @@ public class MessageTask {
@AllArgsConstructor
public static class BizData {
private final String requestNo = UUIDUtil.uuidString();
private TemplatedMsgType templatedMsgType = TemplatedMsgType.TEMPLATE;
private Boolean isOpMessage = false;
@ -132,6 +136,11 @@ public class MessageTask {
*/
private String msgTemplateContent;
/**
* 直接发送的消息内容
*/
private String messageBody;
/**
* 网易云信-自定义消息使用
*/
@ -164,6 +173,10 @@ public class MessageTask {
private Boolean isSenderRobot;
private NimMessageType nimMessageType;
private boolean syncSend;
public boolean determineIsSenderRobot() {
return isSenderRobot != null && isSenderRobot;
}

View File

@ -113,6 +113,7 @@ public class UpdatableMessage implements MessageUpdateInfo {
@Setter
public static class RecordExt {
private Long receiverWorkspaceId;
private boolean isSendByImAccountDirectly;
}
}

View File

@ -9,6 +9,12 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Service("dingDingMsgApiGateway")
@Slf4j
@RequiredArgsConstructor
@ -16,10 +22,22 @@ public class DingDingMsgApiGateway {
private final DingDingMsgApi dingDingMsgApi;
private final ExecutorService executor = new ThreadPoolExecutor(2, 2,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(10));
public void sendMsg(DingTalkMsgTypeEnum msgType, String title, String text, String dingDingScene) {
try {
executor.execute(() -> sendMsgImpl(msgType, title, text, dingDingScene));
} catch (Exception e) {
log.warn("DingDingMsgApiGateway-sendMsg Exception, msgType:{}, title:{}, text:{}, dingDingScene:{}", msgType, title, text, dingDingScene, e);
}
}
/**
* 发送消息
*/
public void sendMsg(DingTalkMsgTypeEnum msgType, String title, String text, String dingDingScene) {
private void sendMsgImpl(DingTalkMsgTypeEnum msgType, String title, String text, String dingDingScene) {
DingDingSendRebootGroupMsgReq req = new DingDingSendRebootGroupMsgReq();
req.setMsgType(msgType);
req.setDingDingJson(new SampleMarkdown(title, text).toJson());

View File

@ -0,0 +1,35 @@
package cn.axzo.im.gateway;
import cn.axzo.im.utils.BizAssertions;
import cn.axzo.maokai.api.client.OrganizationalUnitApi;
import cn.axzo.maokai.api.vo.request.OrganizationalUnitQuery;
import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class OrgGateway {
private final OrganizationalUnitApi organizationalUnitApi;
public List<OrganizationalUnitVO> getUnits(List<Long> ouIdList) {
if (ouIdList.isEmpty())
return Collections.emptyList();
OrganizationalUnitQuery req = new OrganizationalUnitQuery();
req.setPageSize((long) ouIdList.size());
req.setUnitIds(Lists.newArrayList(Sets.newHashSet(ouIdList)));
return BizAssertions.assertResponse(organizationalUnitApi.list(req));
}
}

View File

@ -0,0 +1,26 @@
package cn.axzo.im.gateway.domain;
import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import java.util.Collection;
import java.util.Optional;
/**
* @author yanglin
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class OrgUnits {
private final Collection<OrganizationalUnitVO> units;
public static OrgUnits wrap(Collection<OrganizationalUnitVO> units) {
return new OrgUnits(units);
}
public Optional<OrganizationalUnitVO> findUnit(Long ouId) {
return units.stream().filter(unit -> unit.getId().equals(ouId)).findFirst();
}
}

View File

@ -0,0 +1,27 @@
package cn.axzo.im.gateway.domain;
import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import java.util.List;
import java.util.Optional;
/**
* @author yanglin
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class PersonProfiles {
private final List<PersonProfileDto> profiles;
public static PersonProfiles wrap(List<PersonProfileDto> profiles) {
return new PersonProfiles(profiles);
}
public Optional<PersonProfileDto> findByUserId(Long personId) {
return profiles.stream()
.filter(p -> p.getId().equals(personId))
.findFirst();
}
}

View File

@ -0,0 +1,63 @@
package cn.axzo.im.group;
import cn.axzo.basics.common.BeanMapper;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.im.center.api.enums.MqEventType;
import cn.axzo.im.center.api.vo.group.GroupInfo;
import cn.axzo.im.center.api.vo.group.GroupMemberInfo;
import cn.axzo.im.center.api.vo.mq.GroupChangedMessage;
import cn.axzo.im.center.api.vo.mq.GroupMembersChangeMessage;
import cn.axzo.im.config.MqProducer;
import cn.axzo.im.dao.repository.GroupDao;
import cn.axzo.im.entity.Group;
import cn.axzo.im.entity.GroupMember;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
/**
* @author yanglin
*/
@Service
@RequiredArgsConstructor
class GroupBroadcaster {
private final MqProducer mqProducer;
private final GroupDao groupDao;
void fireGroupChanged(Group group, MqEventType eventType) {
Group effectiveGroup = groupDao.getById(group.getId());
GroupChangedMessage message = new GroupChangedMessage();
message.setGroup(BeanMapper.copyBean(effectiveGroup, GroupInfo.class));
Event event = Event.builder()
.targetId(effectiveGroup.getTid() + "")
.targetType(eventType.getModel())
.eventCode(eventType.getEventCode())
.shardingKey(effectiveGroup.getTid() + "")
.data(message)
.build();
mqProducer.send(event);
}
void fireMembersChanged(Group group,
Collection<GroupMember> members,
MqEventType eventType) {
if (members.isEmpty()) return;
Group effectiveGroup = groupDao.getById(group.getId());
for (GroupMember account : members) {
GroupMembersChangeMessage message = new GroupMembersChangeMessage();
message.setGroup(BeanMapper.copyBean(effectiveGroup, GroupInfo.class));
message.setMember(BeanMapper.copyBean(account, GroupMemberInfo.class));
Event event = Event.builder()
.targetId(effectiveGroup.getTid() + "")
.targetType(eventType.getModel())
.eventCode(eventType.getEventCode())
.shardingKey(effectiveGroup.getTid() + "")
.data(message)
.build();
mqProducer.send(event);
}
}
}

View File

@ -0,0 +1,230 @@
package cn.axzo.im.group;
import cn.axzo.im.center.api.enums.MqEventType;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import cn.axzo.im.center.api.vo.req.GroupAddMembersRequest;
import cn.axzo.im.center.api.vo.req.GroupCreateRequest;
import cn.axzo.im.center.api.vo.req.GroupDismissRequest;
import cn.axzo.im.center.api.vo.req.GroupRemoveMembersRequest;
import cn.axzo.im.center.api.vo.resp.GroupAddMembersResponse;
import cn.axzo.im.center.api.vo.resp.GroupCreateResponse;
import cn.axzo.im.channel.netease.client.NimClient;
import cn.axzo.im.channel.netease.dto.NimGroupAddMembersRequest;
import cn.axzo.im.channel.netease.dto.NimGroupAddMembersResponse;
import cn.axzo.im.channel.netease.dto.NimGroupCreateRequest;
import cn.axzo.im.channel.netease.dto.NimGroupCreateResponse;
import cn.axzo.im.channel.netease.dto.NimGroupDismissRequest;
import cn.axzo.im.channel.netease.dto.NimGroupDismissResponse;
import cn.axzo.im.channel.netease.dto.NimGroupRemoveMembersRequest;
import cn.axzo.im.channel.netease.dto.NimGroupRemoveMembersResponse;
import cn.axzo.im.dao.repository.GroupDao;
import cn.axzo.im.dao.repository.GroupMemberDao;
import cn.axzo.im.entity.Group;
import cn.axzo.im.entity.GroupMember;
import cn.axzo.im.group.support.GroupRateLimiter;
import cn.axzo.im.service.AccountService;
import cn.axzo.im.service.domain.ImAccounts;
import cn.axzo.im.utils.BizAssertions;
import cn.axzo.im.utils.Notification;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static java.util.stream.Collectors.toSet;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class GroupManager {
private final NimClient nimClient;
private final GroupDao groupDao;
private final GroupMemberDao groupMemberDao;
private final GroupSupport groupSupport;
private final AccountService accountService;
private final GroupBroadcaster groupBroadcaster;
private final GroupMemberSyncer groupMemberSyncer;
private final GroupRateLimiter rateLimiter;
private final Notification notification;
private final TransactionTemplate transactionTemplate;
public GroupCreateResponse createGroup(GroupCreateRequest request) {
BizAssertions.assertTrue(request.getPeople().size() > 1, "群成员数量(含群主)不能少于2");
groupSupport.log(0L, "create-group:preparing", request);
// DON'T delete this line
accountService.maybeCreateImAccounts(request.getPeople());
return transactionTemplate.execute(unused -> {
Group savedGroup = groupDao
.findByBizCode(request.getBizCode(), request.getGroupType(), true)
.orElse(null);
BizAssertions.assertTrue(savedGroup == null, String.format("群已经存在: %s", request.getName()));
ImAccounts imAccounts = accountService.getAccountsByPersons(request.getPeople());
String owner = imAccounts.findAccount(request.getOwner()).orElse(null);
BizAssertions.assertNotNull(owner, "群主没有IM账号, 无法创建群. {}", request.getOwner());
Group group = groupSupport.buildNewGroup(request, imAccounts);
BizAssertions.assertTrue(group.addMoreMembers(
request.getPeople().size()), "无法创建群, 群成员数量超过上限" + group.getMemberLimit());
groupDao.save(group);
NimGroupCreateRequest nimRequest = groupSupport
.buildNimCreateGroupRequest(request, imAccounts);
rateLimiter.requireCreateGroup();
NimGroupCreateResponse nimResponse = nimClient.createGroup(nimRequest);
log.info("创建群, request={}, response={}", nimRequest, nimResponse);
BizAssertions.assertTrue(nimResponse.isSuccess(), "创建群失败: {}", nimResponse.getDesc());
groupDao.updateTid(group.getId(), nimResponse.getTid());
group = groupDao.getById(group.getId());
groupSupport.log(group.getTid(), "create-group", request);
groupMemberSyncer.syncMembers(group);
// 同步完成员后再发消息, 因为接收方可能会查询群成员
groupBroadcaster.fireGroupChanged(group, MqEventType.GROUP_CREATED);
groupBroadcaster.fireMembersChanged(group,
groupMemberDao.getByTid(nimResponse.getTid()),
MqEventType.GROUP_ADD_MEMBERS);
GroupCreateResponse response = new GroupCreateResponse();
response.setTid(nimResponse.getTid());
response.setAccountsNotFound(getAccountNotFoundPersons(
"创建群", imAccounts, group, request.getMembers()));
return response;
});
}
@Transactional
public void dismissGroup(GroupDismissRequest request) {
Group group = getGroupForUpdateOrThrow(request.getTid());
groupSupport.log(group.getTid(), "dismiss-group", request);
if (group.isDismissed()) return;
NimGroupDismissRequest nimRequest = groupSupport
.buildNimDismissGroupRequest(group.getOwnerAccount(), group);
rateLimiter.requireDismissGroup();
NimGroupDismissResponse nimResponse = nimClient.dismissGroup(nimRequest);
log.info("解散群, request={}, response={}", nimRequest, nimResponse);
if (!nimResponse.isGroupNotFoundError())
BizAssertions.assertTrue(nimResponse.isSuccess(), "解散群失败: {}", nimResponse.getDesc());
groupDao.setDismissed(group.getTid());
group = groupDao.getById(group.getId());
groupBroadcaster.fireGroupChanged(group, MqEventType.GROUP_DISMISSED);
}
public GroupAddMembersResponse addMembers(GroupAddMembersRequest request) {
// DON'T delete this line
accountService.maybeCreateImAccounts(request.getMembers());
return transactionTemplate.execute(unused -> {
Group group = getGroupForUpdateOrThrow(request.getTid());
groupSupport.log(group.getTid(), "add-members", request);
BizAssertions.assertFalse(group.isDismissed(), "群已经解散");
// sync members 1
groupMemberSyncer.syncMembers(group);
// prepare add members
Set<PersonAccountAttribute> prePersons = groupMemberDao.getAsPersons(group.getTid());
Set<PersonAccountAttribute> addPersons = request.getMembers().stream()
.filter(member -> !prePersons.contains(member))
.collect(toSet());
if (addPersons.isEmpty())
return new GroupAddMembersResponse();
BizAssertions.assertTrue(group.addMoreMembers(prePersons.size() + addPersons.size()),
"群聊人数上限{}人, 请删除部分已选人员", group.getMemberLimit());
ImAccounts imAccounts = accountService.getAccountsByPersons(addPersons);
if (imAccounts.isAccountEmpty()) {
notification.send("添加群成员[{},{}], 有效群成员IM账号列表为空. 请求成员信息: {}",
group.getTid(), group.getName(), JSON.toJSONString(request.getMembers()));
return new GroupAddMembersResponse();
}
GroupMember inviter = groupMemberDao
.findByAccount(group.getTid(), request.getInviter())
.orElse(null);
BizAssertions.assertNotNull(inviter, "邀请者不在群中");
//noinspection DataFlowIssue
NimGroupAddMembersRequest nimRequest = groupSupport
.buildAddMembersRequest(group, inviter.getImAccount(), imAccounts);
// add members
rateLimiter.requireAddMember();
NimGroupAddMembersResponse nimResponse = nimClient.addGroupMembers(nimRequest);
log.info("添加群成员, request={}, response={}", nimRequest, nimResponse);
BizAssertions.assertTrue(nimResponse.isSuccess(), "添加群成员失败: {}", nimResponse.getDesc());
// sync members 2
groupMemberSyncer.syncMembers(group);
groupBroadcaster.fireMembersChanged(group,
groupMemberDao.getByPersons(group.getTid(), addPersons),
MqEventType.GROUP_ADD_MEMBERS);
GroupAddMembersResponse response = new GroupAddMembersResponse();
response.setAccountsNotFound(getAccountNotFoundPersons(
"添加群成员", imAccounts, group, addPersons));
return response;
});
}
@Transactional
public void removeMembers(GroupRemoveMembersRequest request) {
Group group = getGroupForUpdateOrThrow(request.getTid());
BizAssertions.assertFalse(group.isDismissed(), "群已经解散");
groupSupport.log(group.getTid(), "remove-members", request);
ImAccounts imAccounts = accountService.getAccountsByPersons(request.getMembers());
if (imAccounts.isAccountEmpty()) {
notification.send("移除群成员[{},{}], 有效群成员IM账号列表为空. 请求成员信息: {}",
group.getTid(), group.getName(), JSON.toJSONString(request.getMembers()));
return;
}
// sync members 1
groupMemberSyncer.syncMembers(group);
Set<PersonAccountAttribute> groupPersons = groupMemberDao
.getAsPersons(group.getTid());
Set<PersonAccountAttribute> removePersons = request.getMembers().stream()
.filter(groupPersons::contains)
.collect(toSet());
if (CollectionUtils.isEmpty(removePersons)) {
log.info("移除群成员, 成员不在群中, request={}", request);
return;
}
BizAssertions.assertTrue(
groupPersons.size() - removePersons.size() >= 1, "无法移除, 群聊人数不能少于1人");
PersonAccountAttribute owner = groupMemberDao.getOwner(request.getTid()).asPerson();
BizAssertions.assertFalse(removePersons.contains(owner), "不能移除群主");
NimGroupRemoveMembersRequest nimRequest = groupSupport
.buildRemoveMembersRequest(group, group.getOwnerAccount(), imAccounts);
rateLimiter.requireRemoveMember();
NimGroupRemoveMembersResponse nimResponse = nimClient.removeGroupMembers(nimRequest);
log.info("移除群成员, request={}, response={}", nimRequest, nimResponse);
BizAssertions.assertTrue(nimResponse.isSuccess(), "移除群成员失败: {}", nimResponse.getDesc());
// 同步前进行查询, 不然查询不到了
List<GroupMember> removeMembers = groupMemberDao
.getByPersons(group.getTid(), removePersons);
// sync members 2
groupMemberSyncer.syncMembers(group);
groupBroadcaster.fireMembersChanged(group,
removeMembers, MqEventType.GROUP_REMOVE_MEMBERS);
}
@NotNull
private Group getGroupForUpdateOrThrow(Long tid) {
Group group = groupDao.findByTid(tid, true).orElse(null);
BizAssertions.assertNotNull(group, "群不存在: {}", tid);
return group;
}
public Set<PersonAccountAttribute> getAccountNotFoundPersons(
String operation, ImAccounts accounts, Group group,
Set<PersonAccountAttribute> persons) {
if (org.apache.commons.collections.CollectionUtils.isEmpty(persons))
return Collections.emptySet();
Set<PersonAccountAttribute> notFound = persons.stream()
.filter(person -> !accounts.findAccount(person).isPresent())
.collect(toSet());
if (!notFound.isEmpty())
this.notification.send("{}[{},{}], IM账号不存在列表: {}", operation,
group.getTid(), group.getName(),
cn.axzo.framework.jackson.utility.JSON.toJSONString(notFound));
return notFound;
}
}

View File

@ -0,0 +1,110 @@
package cn.axzo.im.group;
import cn.axzo.im.center.api.vo.PersonAccountAttribute;
import cn.axzo.im.center.common.enums.GroupMemberType;
import cn.axzo.im.center.common.enums.YesOrNo;
import cn.axzo.im.channel.netease.client.NimClient;
import cn.axzo.im.channel.netease.dto.NimGroupGetInfoRequest;
import cn.axzo.im.channel.netease.dto.NimGroupGetInfoResponse;
import cn.axzo.im.channel.netease.dto.NimGroupInfo;
import cn.axzo.im.channel.netease.dto.NimGroupMemberInfo;
import cn.axzo.im.dao.repository.GroupDao;
import cn.axzo.im.dao.repository.GroupMemberDao;
import cn.axzo.im.entity.Group;
import cn.axzo.im.entity.GroupMember;
import cn.axzo.im.group.support.GroupRateLimiter;
import cn.axzo.im.utils.BizAssertions;
import cn.axzo.im.utils.ImAccountParser;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static cn.axzo.im.center.api.vo.PersonAccountAttribute.robot;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class GroupMemberSyncer {
private final GroupMemberDao groupMemberDao;
private final GroupDao groupDao;
private final NimClient nimClient;
private final GroupRateLimiter rateLimiter;
private final TransactionTemplate transactionTemplate;
public void syncMembers(Group group) {
NimGroupInfo groupInfo = fetchGroupInfo(group).orElse(null);
if (groupInfo == null) return;
transactionTemplate.executeWithoutResult(unused -> {
List<GroupMember> newMembers = parseGroupMembers(group, groupInfo);
List<GroupMember> oldMembers = groupMemberDao.getByTid(group.getTid());
Set<PersonAccountAttribute> newPersons = newMembers.stream().map(GroupMember::asPerson).collect(toSet());
Set<PersonAccountAttribute> oldPersons = oldMembers.stream().map(GroupMember::asPerson).collect(toSet());
Sets.SetView<PersonAccountAttribute> removedPersons = Sets.difference(oldPersons, newPersons);
groupMemberDao.deleteByPersons(group.getTid(), removedPersons);
Sets.SetView<PersonAccountAttribute> addedPersons = Sets.difference(newPersons, oldPersons);
List<GroupMember> addMembers = newMembers.stream()
.filter(newMember -> addedPersons.contains(newMember.asPerson()))
.collect(toList());
if (!addMembers.isEmpty())
groupMemberDao.saveBatch(addMembers);
groupDao.updateMembersCount(group.getTid(), newMembers.size());
});
}
private Optional<NimGroupInfo> fetchGroupInfo(Group group) {
NimGroupGetInfoRequest nimRequest = new NimGroupGetInfoRequest();
nimRequest.setTid(group.getTid());
long start = System.currentTimeMillis();
rateLimiter.requireGetGroupInfo();
NimGroupGetInfoResponse nimResponse = nimClient.getGroupInfo(nimRequest);
log.info("获取群信息, request={}, response={}, timeUsed={}",
nimRequest, nimResponse, System.currentTimeMillis() - start);
if (nimResponse.isGroupNotFoundError())
return Optional.empty();
BizAssertions.assertTrue(nimResponse.isSuccess(),
"获取群信息失败: {}", nimResponse.getDesc());
return Optional.of(nimResponse.getTinfo());
}
private List<GroupMember> parseGroupMembers(Group group, NimGroupInfo groupInfo) {
ArrayList<GroupMember> members = new ArrayList<>();
Set<NimGroupMemberInfo> admins = groupInfo.getAdmins();
if (admins == null)
admins = Collections.emptySet();
for (NimGroupMemberInfo member : groupInfo.getPeople()) {
PersonAccountAttribute person = ImAccountParser
.parsePerson(member.getAccid())
.orElse(robot());
GroupMember account = new GroupMember();
members.add(account);
account.setTid(group.getTid());
account.setImAccount(member.getAccid());
account.setGroupType(group.getType());
account.setMemberType(
groupInfo.getOwner().equals(member)
? GroupMemberType.OWNER
: (admins.contains(member) ? GroupMemberType.ADMIN : GroupMemberType.MEMBER));
account.setPersonId(person.personIdAsLong());
account.setPersonOuId(person.ouIdOrDefault());
account.setAppType(person.getAppType());
account.setIsRobot(person.isRobot() ? YesOrNo.YES : YesOrNo.NO);
}
return members;
}
}

View File

@ -0,0 +1,107 @@
package cn.axzo.im.group;
import cn.axzo.im.center.api.vo.req.GroupCreateRequest;
import cn.axzo.im.center.common.enums.YesOrNo;
import cn.axzo.im.channel.netease.dto.NimGroupAddMembersRequest;
import cn.axzo.im.channel.netease.dto.NimGroupCreateRequest;
import cn.axzo.im.channel.netease.dto.NimGroupDismissRequest;
import cn.axzo.im.channel.netease.dto.NimGroupRemoveMembersRequest;
import cn.axzo.im.dao.repository.GroupLogDao;
import cn.axzo.im.entity.Group;
import cn.axzo.im.entity.GroupLog;
import cn.axzo.im.group.support.GroupProps;
import cn.axzo.im.service.domain.ImAccounts;
import cn.axzo.im.utils.BizAssertions;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class GroupSupport {
private static final String INTRODUCE_MESSAGE = "邀请您加入群聊";
private final GroupProps groupProps;
private final GroupLogDao groupLogDao;
Group buildNewGroup(GroupCreateRequest request, ImAccounts imAccounts) {
String owner = imAccounts.findAccount(request.getOwner()).orElse(null);
BizAssertions.assertNotNull(owner, "群主没有IM账号, 无法创建群. {}", request.getOwner());
Group group = new Group();
group.setName(request.getName());
group.setBizCode(request.getBizCode());
group.setType(request.getGroupType());
group.setBizGroupInfo(request.bizGroupInfoOrEmpty());
group.setAvatar(request.getAvatar());
group.setMemberCount((long) request.getPeople().size());
Long memberLimit = request.getMemberLimit();
if (memberLimit == null)
memberLimit = groupProps.getDefaultMemberLimit();
group.setMemberLimit(memberLimit);
group.setOwnerAccount(owner);
group.setOwnerPersonId(request.getOwner().personIdAsLong());
group.setCreatePersonId(request.getOwner().personIdAsLong());
group.setIsDismissed(YesOrNo.NO);
return group;
}
NimGroupCreateRequest buildNimCreateGroupRequest(
GroupCreateRequest request, ImAccounts imAccounts) {
String owner = imAccounts.findAccount(request.getOwner()).orElse(null);
BizAssertions.assertNotNull(owner, "群主没有IM账号, 无法创建群. {}", request.getOwner());
BizAssertions.assertTrue(imAccounts.getAccountSize() > 1, "无法创建群. 存在的群成员IM账号数量需要大于等于2");
NimGroupCreateRequest nimRequest = new NimGroupCreateRequest();
nimRequest.setName(request.getName());
nimRequest.setOwner(owner);
nimRequest.addMembers(imAccounts.filterAccounts(account -> !account.equals(owner)));
nimRequest.setIntroduceMessage(INTRODUCE_MESSAGE);
nimRequest.setIcon(request.getAvatar());
nimRequest.addCustom(Group.CUSTOM_GROUP_TYPE, request.getGroupType());
nimRequest.addCustom(Group.CUSTOM_BIZ_CODE, request.getBizCode());
nimRequest.addCustom(Group.CUSTOM_BIZ_GROUP_INFO, request.bizGroupInfoOrEmpty());
return nimRequest;
}
NimGroupDismissRequest buildNimDismissGroupRequest(String owner, Group group) {
NimGroupDismissRequest nimRequest = new NimGroupDismissRequest();
nimRequest.setTid(group.getTid());
nimRequest.setOwner(owner);
return nimRequest;
}
NimGroupAddMembersRequest buildAddMembersRequest(
Group group, String inviter, ImAccounts accounts) {
BizAssertions.assertNotEmpty(accounts.getAccounts(), "有效群成员IM账号列表为空");
NimGroupAddMembersRequest nimRequest = new NimGroupAddMembersRequest();
nimRequest.setTid(group.getTid());
nimRequest.setOwner(inviter);
nimRequest.setMsg(INTRODUCE_MESSAGE);
nimRequest.addMembers(accounts.getAccounts());
return nimRequest;
}
NimGroupRemoveMembersRequest buildRemoveMembersRequest(
Group group, String owner, ImAccounts accounts) {
BizAssertions.assertNotEmpty(accounts.getAccounts(), "有效群成员IM账号列表为空");
NimGroupRemoveMembersRequest nimRequest = new NimGroupRemoveMembersRequest();
nimRequest.setTid(group.getTid());
nimRequest.setOwner(owner);
nimRequest.addMembers(accounts.getAccounts());
return nimRequest;
}
public void log(Long tid, String context, Object request) {
GroupLog log = new GroupLog();
log.setTid(tid == null ? 0 : tid);
log.setContext(context);
log.setRequest(request);
groupLogDao.save(log);
}
}

View File

@ -0,0 +1,67 @@
package cn.axzo.im.group;
import cn.axzo.im.dao.mapper.ChatGroupMapper;
import cn.axzo.im.dao.repository.GroupDao;
import cn.axzo.im.entity.ChatGroup;
import cn.axzo.im.entity.Group;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static cn.axzo.im.utils.Queries.query;
import static java.util.stream.Collectors.toList;
/**
* @author yanglin
*/
@Component
@RequiredArgsConstructor
public class LegacyGroupSupport {
private final GroupDao groupDao;
private final ChatGroupMapper chatGroupMapper;
public Set<String> getMergedAccounts(Set<String> groupIds) {
Set<String> accounts = Sets.newHashSet(getGroupAccounts(groupIds));
accounts.addAll(getGroupCatAccounts(groupIds));
return accounts;
}
private List<String> getGroupAccounts(Set<String> groupIds) {
if (CollectionUtils.isEmpty(groupIds))
return Collections.emptyList();
if (parseLongs(groupIds).isEmpty())
return Collections.emptyList();
return groupDao.lambdaQuery()
.in(Group::getTid, parseLongs(groupIds))
.list().stream()
.map(Group::getTid)
.map(String::valueOf)
.collect(toList());
}
private List<String> getGroupCatAccounts(Set<String> groupIds) {
if (CollectionUtils.isEmpty(groupIds))
return Collections.emptyList();
return chatGroupMapper.selectList(
query(ChatGroup.class)
.in(ChatGroup::getTid, groupIds)).stream()
.map(ChatGroup::getTid)
.collect(toList());
}
private List<Long> parseLongs(Collection<String> groupIds) {
return groupIds.stream()
.filter(NumberUtils::isDigits)
.map(Long::parseLong)
.collect(toList());
}
}

Some files were not shown because too many files have changed in this diff Show More