first commit

This commit is contained in:
2023-09-07 00:56:03 +08:00
commit c0ca154d31
718 changed files with 56107 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="Spring" name="Spring">
<configuration />
</facet>
</component>
</module>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>im-system</artifactId>
<groupId>com.lld</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,31 @@
package com.lld.im.common;
import com.lld.im.common.exception.ApplicationExceptionEnum;
public enum BaseErrorCode implements ApplicationExceptionEnum {
SUCCESS(200,"success"),
SYSTEM_ERROR(90000,"服务器内部错误,请联系管理员"),
PARAMETER_ERROR(90001,"参数校验错误"),
;
private int code;
private String error;
BaseErrorCode(int code, String error){
this.code = code;
this.error = error;
}
public int getCode() {
return this.code;
}
public String getError() {
return this.error;
}
}

View File

@@ -0,0 +1,34 @@
package com.lld.im.common;
/**
* @author: Chackylee
* @description:
**/
public enum ClientType {
WEBAPI(0,"webApi"),
WEB(1,"web"),
IOS(2,"ios"),
ANDROID(3,"android"),
WINDOWS(4,"windows"),
MAC(5,"mac"),
;
private int code;
private String error;
ClientType(int code, String error){
this.code = code;
this.error = error;
}
public int getCode() {
return this.code;
}
public String getError() {
return this.error;
}
}

View File

@@ -0,0 +1,66 @@
package com.lld.im.common;
import com.lld.im.common.exception.ApplicationExceptionEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ResponseVO<T> {
private int code;
private String msg;
private T data;
public static ResponseVO successResponse(Object data) {
return new ResponseVO(200, "success", data);
}
public static ResponseVO successResponse() {
return new ResponseVO(200, "success");
}
public static ResponseVO errorResponse() {
return new ResponseVO(500, "系统内部异常");
}
public static ResponseVO errorResponse(int code, String msg) {
return new ResponseVO(code, msg);
}
public static ResponseVO errorResponse(ApplicationExceptionEnum enums) {
return new ResponseVO(enums.getCode(), enums.getError());
}
public boolean isOk(){
return this.code == 200;
}
public ResponseVO(int code, String msg) {
this.code = code;
this.msg = msg;
// this.data = null;
}
public ResponseVO success(){
this.code = 200;
this.msg = "success";
return this;
}
public ResponseVO success(T data){
this.code = 200;
this.msg = "success";
this.data = data;
return this;
}
}

View File

@@ -0,0 +1,70 @@
package com.lld.im.common.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author: Chackylee
* @description:
**/
@Data
@Component
@ConfigurationProperties(prefix = "appconfig")
public class AppConfig {
private String privateKey;
/** zk连接地址*/
private String zkAddr;
/** zk连接超时时间*/
private Integer zkConnectTimeOut;
/** im管道地址路由策略*/
private Integer imRouteWay;
private boolean sendMessageCheckFriend; //发送消息是否校验关系链
private boolean sendMessageCheckBlack; //发送消息是否校验黑名单
/** 如果选用一致性hash的话具体hash算法*/
private Integer consistentHashWay;
private String callbackUrl;
private boolean modifyUserAfterCallback; //用户资料变更之后回调开关
private boolean addFriendAfterCallback; //添加好友之后回调开关
private boolean addFriendBeforeCallback; //添加好友之前回调开关
private boolean modifyFriendAfterCallback; //修改好友之后回调开关
private boolean deleteFriendAfterCallback; //删除好友之后回调开关
private boolean addFriendShipBlackAfterCallback; //添加黑名单之后回调开关
private boolean deleteFriendShipBlackAfterCallback; //删除黑名单之后回调开关
private boolean createGroupAfterCallback; //创建群聊之后回调开关
private boolean modifyGroupAfterCallback; //修改群聊之后回调开关
private boolean destroyGroupAfterCallback;//解散群聊之后回调开关
private boolean deleteGroupMemberAfterCallback;//删除群成员之后回调
private boolean addGroupMemberBeforeCallback;//拉人入群之前回调
private boolean addGroupMemberAfterCallback;//拉人入群之后回调
private boolean sendMessageAfterCallback;//发送单聊消息之后
private boolean sendMessageBeforeCallback;//发送单聊消息之前
private Integer deleteConversationSyncMode;
private Integer offlineMessageCount;//离线消息最大条数
}

View File

@@ -0,0 +1,150 @@
package com.lld.im.common.config;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "httpclient")
public class GlobalHttpClientConfig {
private Integer maxTotal; // 最大连接数
private Integer defaultMaxPerRoute; // 最大并发链接数
private Integer connectTimeout; // 创建链接的最大时间
private Integer connectionRequestTimeout; // 链接获取超时时间
private Integer socketTimeout; // 数据传输最长时间
private boolean staleConnectionCheckEnabled; // 提交时检查链接是否可用
PoolingHttpClientConnectionManager manager = null;
HttpClientBuilder httpClientBuilder = null;
// 定义httpClient链接池
@Bean(name = "httpClientConnectionManager")
public PoolingHttpClientConnectionManager getPoolingHttpClientConnectionManager() {
return getManager();
}
private PoolingHttpClientConnectionManager getManager() {
if (manager != null) {
return manager;
}
manager = new PoolingHttpClientConnectionManager();
manager.setMaxTotal(maxTotal); // 设定最大链接数
manager.setDefaultMaxPerRoute(defaultMaxPerRoute); // 设定并发链接数
return manager;
}
/**
* 实例化连接池,设置连接池管理器。 这里需要以参数形式注入上面实例化的连接池管理器
*
* @Qualifier 指定bean标签进行注入
*/
@Bean(name = "httpClientBuilder")
public HttpClientBuilder getHttpClientBuilder(
@Qualifier("httpClientConnectionManager") PoolingHttpClientConnectionManager httpClientConnectionManager) {
// HttpClientBuilder中的构造方法被protected修饰所以这里不能直接使用new来实例化一个HttpClientBuilder,可以使用HttpClientBuilder提供的静态方法create()来获取HttpClientBuilder对象
httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setConnectionManager(httpClientConnectionManager);
return httpClientBuilder;
}
/**
* 注入连接池用于获取httpClient
*
* @param httpClientBuilder
* @return
*/
@Bean
public CloseableHttpClient getCloseableHttpClient(
@Qualifier("httpClientBuilder") HttpClientBuilder httpClientBuilder) {
return httpClientBuilder.build();
}
public CloseableHttpClient getCloseableHttpClient() {
if (httpClientBuilder != null) {
return httpClientBuilder.build();
}
httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setConnectionManager(getManager());
return httpClientBuilder.build();
}
/**
* Builder是RequestConfig的一个内部类 通过RequestConfig的custom方法来获取到一个Builder对象
* 设置builder的连接信息
*
* @return
*/
@Bean(name = "builder")
public RequestConfig.Builder getBuilder() {
RequestConfig.Builder builder = RequestConfig.custom();
return builder.setConnectTimeout(connectTimeout).setConnectionRequestTimeout(connectionRequestTimeout)
.setSocketTimeout(socketTimeout).setStaleConnectionCheckEnabled(staleConnectionCheckEnabled);
}
/**
* 使用builder构建一个RequestConfig对象
*
* @param builder
* @return
*/
@Bean
public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder) {
return builder.build();
}
public Integer getMaxTotal() {
return maxTotal;
}
public void setMaxTotal(Integer maxTotal) {
this.maxTotal = maxTotal;
}
public Integer getDefaultMaxPerRoute() {
return defaultMaxPerRoute;
}
public void setDefaultMaxPerRoute(Integer defaultMaxPerRoute) {
this.defaultMaxPerRoute = defaultMaxPerRoute;
}
public Integer getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(Integer connectTimeout) {
this.connectTimeout = connectTimeout;
}
public Integer getConnectionRequestTimeout() {
return connectionRequestTimeout;
}
public void setConnectionRequestTimeout(Integer connectionRequestTimeout) {
this.connectionRequestTimeout = connectionRequestTimeout;
}
public Integer getSocketTimeout() {
return socketTimeout;
}
public void setSocketTimeout(Integer socketTimeout) {
this.socketTimeout = socketTimeout;
}
public boolean isStaleConnectionCheckEnabled() {
return staleConnectionCheckEnabled;
}
public void setStaleConnectionCheckEnabled(boolean staleConnectionCheckEnabled) {
this.staleConnectionCheckEnabled = staleConnectionCheckEnabled;
}
}

View File

@@ -0,0 +1,155 @@
package com.lld.im.common.constant;
/**
* @description:
* @author: lld
* @version: 1.0
*/
public class Constants {
/** channel绑定的userId Key*/
public static final String UserId = "userId";
/** channel绑定的appId */
public static final String AppId = "appId";
public static final String ClientType = "clientType";
public static final String Imei = "imei";
/** channel绑定的clientType 和 imel Key*/
public static final String ClientImei = "clientImei";
public static final String ReadTime = "readTime";
public static final String ImCoreZkRoot = "/im-coreRoot";
public static final String ImCoreZkRootTcp = "/tcp";
public static final String ImCoreZkRootWeb = "/web";
public static class RedisConstants{
/**
* userSign格式appId:userSign:
*/
public static final String userSign = "userSign";
/**
* 用户上线通知channel
*/
public static final String UserLoginChannel
= "signal/channel/LOGIN_USER_INNER_QUEUE";
/**
* 用户sessionappId + UserSessionConstants + 用户id 例如10000userSessionlld
*/
public static final String UserSessionConstants = ":userSession:";
/**
* 缓存客户端消息防重,格式: appId + :cacheMessage: + messageId
*/
public static final String cacheMessage = "cacheMessage";
public static final String OfflineMessage = "offlineMessage";
/**
* seq 前缀
*/
public static final String SeqPrefix = "seq";
/**
* 用户订阅列表,格式 appId + :subscribe: + userId。Hash结构filed为订阅自己的人
*/
public static final String subscribe = "subscribe";
/**
* 用户自定义在线状态,格式 appId + :userCustomerStatus: + userId。setvalue为用户id
*/
public static final String userCustomerStatus = "userCustomerStatus";
}
public static class RabbitConstants{
public static final String Im2UserService = "pipeline2UserService";
public static final String Im2MessageService = "pipeline2MessageService";
public static final String Im2GroupService = "pipeline2GroupService";
public static final String Im2FriendshipService = "pipeline2FriendshipService";
public static final String MessageService2Im = "messageService2Pipeline";
public static final String GroupService2Im = "GroupService2Pipeline";
public static final String FriendShip2Im = "friendShip2Pipeline";
public static final String StoreP2PMessage = "storeP2PMessage";
public static final String StoreGroupMessage = "storeGroupMessage";
}
public static class CallbackCommand{
public static final String ModifyUserAfter = "user.modify.after";
public static final String CreateGroupAfter = "group.create.after";
public static final String UpdateGroupAfter = "group.update.after";
public static final String DestoryGroupAfter = "group.destory.after";
public static final String TransferGroupAfter = "group.transfer.after";
public static final String GroupMemberAddBefore = "group.member.add.before";
public static final String GroupMemberAddAfter = "group.member.add.after";
public static final String GroupMemberDeleteAfter = "group.member.delete.after";
public static final String AddFriendBefore = "friend.add.before";
public static final String AddFriendAfter = "friend.add.after";
public static final String UpdateFriendBefore = "friend.update.before";
public static final String UpdateFriendAfter = "friend.update.after";
public static final String DeleteFriendAfter = "friend.delete.after";
public static final String AddBlackAfter = "black.add.after";
public static final String DeleteBlack = "black.delete";
public static final String SendMessageAfter = "message.send.after";
public static final String SendMessageBefore = "message.send.before";
}
public static class SeqConstants {
public static final String Message = "messageSeq";
public static final String GroupMessage = "groupMessageSeq";
public static final String Friendship = "friendshipSeq";
// public static final String FriendshipBlack = "friendshipBlackSeq";
public static final String FriendshipRequest = "friendshipRequestSeq";
public static final String FriendshipGroup = "friendshipGrouptSeq";
public static final String Group = "groupSeq";
public static final String Conversation = "conversationSeq";
}
}

View File

@@ -0,0 +1,27 @@
package com.lld.im.common.enums;
public enum AllowFriendTypeEnum {
/**
* 验证
*/
NEED(2),
/**
* 不需要验证
*/
NOT_NEED(1),
;
private int code;
AllowFriendTypeEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,22 @@
package com.lld.im.common.enums;
public enum ApproverFriendRequestStatusEnum {
/**
* 1 同意2 拒绝。
*/
AGREE(1),
REJECT(2),
;
private int code;
ApproverFriendRequestStatusEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,22 @@
package com.lld.im.common.enums;
public enum CheckFriendShipTypeEnum {
/**
* 1 单方校验2双方校验。
*/
SINGLE(1),
BOTH(2),
;
private int type;
CheckFriendShipTypeEnum(int type){
this.type=type;
}
public int getType() {
return type;
}
}

View File

@@ -0,0 +1,31 @@
package com.lld.im.common.enums;
import com.lld.im.common.exception.ApplicationExceptionEnum;
/**
* @author: Chackylee
* @description:
**/
public enum ConversationErrorCode implements ApplicationExceptionEnum {
CONVERSATION_UPDATE_PARAM_ERROR(50000,"會話修改參數錯誤"),
;
private int code;
private String error;
ConversationErrorCode(int code, String error){
this.code = code;
this.error = error;
}
public int getCode() {
return this.code;
}
public String getError() {
return this.error;
}
}

View File

@@ -0,0 +1,24 @@
package com.lld.im.common.enums;
public enum ConversationTypeEnum {
/**
* 0 单聊 1群聊 2机器人 3公众号
*/
P2P(0),
GROUP(1),
ROBOT(2),
;
private int code;
ConversationTypeEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,22 @@
package com.lld.im.common.enums;
public enum DelFlagEnum {
/**
* 0 正常1 删除。
*/
NORMAL(0),
DELETE(1),
;
private int code;
DelFlagEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,63 @@
package com.lld.im.common.enums;
public enum DeviceMultiLoginEnum {
/**
* 单端登录 仅允许 Windows、Web、Android 或 iOS 单端登录。
*/
ONE(1,"DeviceMultiLoginEnum_ONE"),
/**
* 双端登录 允许 Windows、Mac、Android 或 iOS 单端登录,同时允许与 Web 端同时在线。
*/
TWO(2,"DeviceMultiLoginEnum_TWO"),
/**
* 三端登录 允许 Android 或 iOS 单端登录(互斥)Windows 或者 Mac 单聊登录(互斥),同时允许 Web 端同时在线
*/
THREE(3,"DeviceMultiLoginEnum_THREE"),
/**
* 多端同时在线 允许 Windows、Mac、Web、Android 或 iOS 多端或全端同时在线登录
*/
ALL(4,"DeviceMultiLoginEnum_ALL");
private int loginMode;
private String loginDesc;
/**
* 不能用 默认的 enumType b= enumType.values()[i]; 因为本枚举是类形式封装
* @param ordinal
* @return
*/
public static DeviceMultiLoginEnum getMember(int ordinal) {
for (int i = 0; i < DeviceMultiLoginEnum.values().length; i++) {
if (DeviceMultiLoginEnum.values()[i].getLoginMode() == ordinal) {
return DeviceMultiLoginEnum.values()[i];
}
}
return THREE;
}
DeviceMultiLoginEnum(int loginMode, String loginDesc){
this.loginMode=loginMode;
this.loginDesc=loginDesc;
}
public int getLoginMode() {
return loginMode;
}
public void setLoginMode(int loginMode) {
this.loginMode = loginMode;
}
public String getLoginDesc() {
return loginDesc;
}
public void setLoginDesc(String loginDesc) {
this.loginDesc = loginDesc;
}
}

View File

@@ -0,0 +1,57 @@
package com.lld.im.common.enums;
import com.lld.im.common.exception.ApplicationExceptionEnum;
public enum FriendShipErrorCode implements ApplicationExceptionEnum {
IMPORT_SIZE_BEYOND(30000,"导入數量超出上限"),
ADD_FRIEND_ERROR(30001,"添加好友失败"),
TO_IS_YOUR_FRIEND(30002,"对方已经是你的好友"),
TO_IS_NOT_YOUR_FRIEND(30003,"对方不是你的好友"),
FRIEND_IS_DELETED(30004,"好友已被删除"),
FRIEND_IS_BLACK(30006,"好友已被拉黑"),
TARGET_IS_BLACK_YOU(30007,"对方把你拉黑"),
REPEATSHIP_IS_NOT_EXIST(30008,"关系链记录不存在"),
ADD_BLACK_ERROR(30009,"添加黑名單失败"),
FRIEND_IS_NOT_YOUR_BLACK(30010,"好友已經不在你的黑名單内"),
NOT_APPROVER_OTHER_MAN_REQUEST(30011,"无法审批其他人的好友请求"),
FRIEND_REQUEST_IS_NOT_EXIST(30012,"好友申请不存在"),
FRIEND_SHIP_GROUP_CREATE_ERROR(30014,"好友分组创建失败"),
FRIEND_SHIP_GROUP_IS_EXIST(30015,"好友分组已存在"),
FRIEND_SHIP_GROUP_IS_NOT_EXIST(30016,"好友分组不存在"),
;
private int code;
private String error;
FriendShipErrorCode(int code, String error){
this.code = code;
this.error = error;
}
public int getCode() {
return this.code;
}
public String getError() {
return this.error;
}
}

View File

@@ -0,0 +1,31 @@
package com.lld.im.common.enums;
public enum FriendShipStatusEnum {
/**
* 0未添加 1正常 2删除
*/
FRIEND_STATUS_NO_FRIEND(0),
FRIEND_STATUS_NORMAL(1),
FRIEND_STATUS_DELETE(2),
/**
* 0未添加 1正常 2删除
*/
BLACK_STATUS_NORMAL(1),
BLACK_STATUS_BLACKED(2),
;
private int code;
FriendShipStatusEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,40 @@
package com.lld.im.common.enums;
import com.lld.im.common.exception.ApplicationExceptionEnum;
/**
* @author: Chackylee
* @description: 6
**/
public enum GateWayErrorCode implements ApplicationExceptionEnum {
USERSIGN_NOT_EXIST(60000,"用户签名不存在"),
APPID_NOT_EXIST(60001,"appId不存在"),
OPERATER_NOT_EXIST(60002,"操作人不存在"),
USERSIGN_IS_ERROR(60003,"用户签名不正确"),
USERSIGN_OPERATE_NOT_MATE(60005,"用户签名与操作人不匹配"),
USERSIGN_IS_EXPIRED(60004,"用户签名已过期"),
;
private int code;
private String error;
GateWayErrorCode(int code, String error){
this.code = code;
this.error = error;
}
public int getCode() {
return this.code;
}
public String getError() {
return this.error;
}
}

View File

@@ -0,0 +1,66 @@
package com.lld.im.common.enums;
import com.lld.im.common.exception.ApplicationExceptionEnum;
/**
* @author: Chackylee
* @description:
**/
public enum GroupErrorCode implements ApplicationExceptionEnum {
GROUP_IS_NOT_EXIST(40000,"群不存在"),
GROUP_IS_EXIST(40001,"群已存在"),
GROUP_IS_HAVE_OWNER(40002,"群已存在群主"),
USER_IS_JOINED_GROUP(40003,"该用户已经进入该群"),
USER_JOIN_GROUP_ERROR(40004,"群成员添加失败"),
GROUP_MEMBER_IS_BEYOND(40005,"群成员已达到上限"),
MEMBER_IS_NOT_JOINED_GROUP(40006,"该用户不在群内"),
THIS_OPERATE_NEED_MANAGER_ROLE(40007,"该操作只允许群主/管理员操作"),
THIS_OPERATE_NEED_APPMANAGER_ROLE(40008,"该操作只允许APP管理员操作"),
THIS_OPERATE_NEED_OWNER_ROLE(40009,"该操作只允许群主操作"),
GROUP_OWNER_IS_NOT_REMOVE(40010,"群主无法移除"),
UPDATE_GROUP_BASE_INFO_ERROR(40011,"更新群信息失败"),
THIS_GROUP_IS_MUTE(40012,"该群禁止发言"),
IMPORT_GROUP_ERROR(40013,"导入群组失败"),
THIS_OPERATE_NEED_ONESELF(40014,"该操作只允许自己操作"),
PRIVATE_GROUP_CAN_NOT_DESTORY(40015,"私有群不允许解散"),
PUBLIC_GROUP_MUST_HAVE_OWNER(40016,"公开群必须指定群主"),
GROUP_MEMBER_IS_SPEAK(40017,"群成员被禁言"),
GROUP_IS_DESTROY(40018,"群组已解散"),
;
private int code;
private String error;
GroupErrorCode(int code, String error){
this.code = code;
this.error = error;
}
public int getCode() {
return this.code;
}
public String getError() {
return this.error;
}
}

View File

@@ -0,0 +1,50 @@
package com.lld.im.common.enums;
public enum GroupMemberRoleEnum {
/**
* 普通成员
*/
ORDINARY(0),
/**
* 管理员
*/
MAMAGER(1),
/**
* 群主
*/
OWNER(2),
/**
* 离开
*/
LEAVE(3);
;
private int code;
/**
* 不能用 默认的 enumType b= enumType.values()[i]; 因为本枚举是类形式封装
* @param ordinal
* @return
*/
public static GroupMemberRoleEnum getItem(int ordinal) {
for (int i = 0; i < GroupMemberRoleEnum.values().length; i++) {
if (GroupMemberRoleEnum.values()[i].getCode() == ordinal) {
return GroupMemberRoleEnum.values()[i];
}
}
return null;
}
GroupMemberRoleEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,43 @@
package com.lld.im.common.enums;
public enum GroupMuteTypeEnum {
/**
* 是否全员禁言0 不禁言1 全员禁言。
*/
NOT_MUTE(0),
MUTE(1),
;
/**
* 不能用 默认的 enumType b= enumType.values()[i]; 因为本枚举是类形式封装
* @param ordinal
* @return
*/
public static GroupMuteTypeEnum getEnum(Integer ordinal) {
if(ordinal == null){
return null;
}
for (int i = 0; i < GroupMuteTypeEnum.values().length; i++) {
if (GroupMuteTypeEnum.values()[i].getCode() == ordinal) {
return GroupMuteTypeEnum.values()[i];
}
}
return null;
}
private int code;
GroupMuteTypeEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,42 @@
package com.lld.im.common.enums;
public enum GroupStatusEnum {
/**
* 1正常 2解散 其他待定比如封禁...
*/
NORMAL(1),
DESTROY(2),
;
/**
* 不能用 默认的 enumType b= enumType.values()[i]; 因为本枚举是类形式封装
* @param ordinal
* @return
*/
public static GroupStatusEnum getEnum(Integer ordinal) {
if(ordinal == null){
return null;
}
for (int i = 0; i < GroupStatusEnum.values().length; i++) {
if (GroupStatusEnum.values()[i].getCode() == ordinal) {
return GroupStatusEnum.values()[i];
}
}
return null;
}
private int code;
GroupStatusEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,42 @@
package com.lld.im.common.enums;
public enum GroupTypeEnum {
/**
* 群类型 1私有群类似微信 2公开群(类似qq
*/
PRIVATE(1),
PUBLIC(2),
;
/**
* 不能用 默认的 enumType b= enumType.values()[i]; 因为本枚举是类形式封装
* @param ordinal
* @return
*/
public static GroupTypeEnum getEnum(Integer ordinal) {
if(ordinal == null){
return null;
}
for (int i = 0; i < GroupTypeEnum.values().length; i++) {
if (GroupTypeEnum.values()[i].getCode() == ordinal) {
return GroupTypeEnum.values()[i];
}
}
return null;
}
private int code;
GroupTypeEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,22 @@
package com.lld.im.common.enums;
public enum ImConnectStatusEnum {
/**
* 管道链接状态,1=在线2=离线。。
*/
ONLINE_STATUS(1),
OFFLINE_STATUS(2),
;
private Integer code;
ImConnectStatusEnum(Integer code){
this.code=code;
}
public Integer getCode() {
return code;
}
}

View File

@@ -0,0 +1,52 @@
package com.lld.im.common.enums;
public enum ImUrlRouteWayEnum {
/**
* 随机
*/
RAMDOM(1,"com.lld.im.common.route.algorithm.random.RandomHandle"),
/**
* 1.轮训
*/
LOOP(2,"com.lld.im.common.route.algorithm.loop.LoopHandle"),
/**
* HASH
*/
HASH(3,"com.lld.im.common.route.algorithm.consistenthash.ConsistentHashHandle"),
;
private int code;
private String clazz;
/**
* 不能用 默认的 enumType b= enumType.values()[i]; 因为本枚举是类形式封装
* @param ordinal
* @return
*/
public static ImUrlRouteWayEnum getHandler(int ordinal) {
for (int i = 0; i < ImUrlRouteWayEnum.values().length; i++) {
if (ImUrlRouteWayEnum.values()[i].getCode() == ordinal) {
return ImUrlRouteWayEnum.values()[i];
}
}
return null;
}
ImUrlRouteWayEnum(int code, String clazz){
this.code=code;
this.clazz=clazz;
}
public String getClazz() {
return clazz;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,19 @@
package com.lld.im.common.enums;
public enum ImUserTypeEnum {
IM_USER(1),
APP_ADMIN(100),
;
private int code;
ImUserTypeEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,36 @@
package com.lld.im.common.enums;
import com.lld.im.common.exception.ApplicationExceptionEnum;
public enum MessageErrorCode implements ApplicationExceptionEnum {
FROMER_IS_MUTE(50002,"发送方被禁言"),
FROMER_IS_FORBIBBEN(50003,"发送方被禁用"),
MESSAGEBODY_IS_NOT_EXIST(50003,"消息体不存在"),
MESSAGE_RECALL_TIME_OUT(50004,"消息已超过可撤回时间"),
MESSAGE_IS_RECALLED(50005,"消息已被撤回"),
;
private int code;
private String error;
MessageErrorCode(int code, String error){
this.code = code;
this.error = error;
}
public int getCode() {
return this.code;
}
public String getError() {
return this.error;
}
}

View File

@@ -0,0 +1,48 @@
package com.lld.im.common.enums;
public enum RouteHashMethodEnum {
/**
* TreeMap
*/
TREE(1,"com.lld.im.common.route.algorithm.consistenthash" +
".TreeMapConsistentHash"),
/**
* 自定义map
*/
CUSTOMER(2,"com.lld.im.common.route.algorithm.consistenthash.xxxx"),
;
private int code;
private String clazz;
/**
* 不能用 默认的 enumType b= enumType.values()[i]; 因为本枚举是类形式封装
* @param ordinal
* @return
*/
public static RouteHashMethodEnum getHandler(int ordinal) {
for (int i = 0; i < RouteHashMethodEnum.values().length; i++) {
if (RouteHashMethodEnum.values()[i].getCode() == ordinal) {
return RouteHashMethodEnum.values()[i];
}
}
return null;
}
RouteHashMethodEnum(int code, String clazz){
this.code=code;
this.clazz=clazz;
}
public String getClazz() {
return clazz;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,30 @@
package com.lld.im.common.enums;
import com.lld.im.common.exception.ApplicationExceptionEnum;
public enum UserErrorCode implements ApplicationExceptionEnum {
IMPORT_SIZE_BEYOND(20000,"导入數量超出上限"),
USER_IS_NOT_EXIST(20001,"用户不存在"),
SERVER_GET_USER_ERROR(20002,"服务获取用户失败"),
MODIFY_USER_ERROR(20003,"更新用户失败"),
SERVER_NOT_AVAILABLE(71000, "没有可用的服务"),
;
private int code;
private String error;
UserErrorCode(int code, String error){
this.code = code;
this.error = error;
}
public int getCode() {
return this.code;
}
public String getError() {
return this.error;
}
}

View File

@@ -0,0 +1,22 @@
package com.lld.im.common.enums;
public enum UserForbiddenFlagEnum {
/**
* 0 正常1 禁用。
*/
NORMAL(0),
FORBIBBEN(1),
;
private int code;
UserForbiddenFlagEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,22 @@
package com.lld.im.common.enums;
public enum UserSilentFlagEnum {
/**
* 0 正常1 禁言。
*/
NORMAL(0),
MUTE(1),
;
private int code;
UserSilentFlagEnum(int code){
this.code=code;
}
public int getCode() {
return code;
}
}

View File

@@ -0,0 +1,10 @@
package com.lld.im.common.enums.command;
/**
* @description:
* @author: lld
* @version: 1.0
*/
public interface Command {
public int getCommand();
}

View File

@@ -0,0 +1,35 @@
package com.lld.im.common.enums.command;
public enum CommandType {
USER("4"),
FRIEND("3"),
GROUP("2"),
MESSAGE("1"),
;
private String commandType;
public String getCommandType() {
return commandType;
}
CommandType(String commandType) {
this.commandType = commandType;
}
public static CommandType getCommandType(String ordinal) {
for (int i = 0; i < CommandType.values().length; i++) {
if (CommandType.values()[i].getCommandType().equals(ordinal)) {
return CommandType.values()[i];
}
}
return null;
}
}

View File

@@ -0,0 +1,24 @@
package com.lld.im.common.enums.command;
public enum ConversationEventCommand implements Command {
//删除会话
CONVERSATION_DELETE(5000),
//删除会话
CONVERSATION_UPDATE(5001),
;
private int command;
ConversationEventCommand(int command){
this.command=command;
}
@Override
public int getCommand() {
return command;
}
}

View File

@@ -0,0 +1,58 @@
package com.lld.im.common.enums.command;
public enum FriendshipEventCommand implements Command {
//添加好友
FRIEND_ADD(3000),
//更新好友
FRIEND_UPDATE(3001),
//删除好友
FRIEND_DELETE(3002),
//好友申请
FRIEND_REQUEST(3003),
//好友申请已读
FRIEND_REQUEST_READ(3004),
//好友申请审批
FRIEND_REQUEST_APPROVER(3005),
//添加黑名单
FRIEND_BLACK_ADD(3010),
//移除黑名单
FRIEND_BLACK_DELETE(3011),
//新建好友分组
FRIEND_GROUP_ADD(3012),
//删除好友分组
FRIEND_GROUP_DELETE(3013),
//好友分组添加成员
FRIEND_GROUP_MEMBER_ADD(3014),
//好友分组移除成员
FRIEND_GROUP_MEMBER_DELETE(3015),
//删除所有好友
FRIEND_ALL_DELETE(3016),
;
private int command;
FriendshipEventCommand(int command){
this.command=command;
}
@Override
public int getCommand() {
return command;
}
}

View File

@@ -0,0 +1,91 @@
package com.lld.im.common.enums.command;
//2
public enum GroupEventCommand implements Command {
/**
* 推送申请入群通知 2023
*/
JOIN_GROUP(2000),
/**
* 推送添加群成员 2001通知给所有管理员和本人
*/
ADDED_MEMBER(2001),
/**
* 推送创建群组通知 2002通知给所有人
*/
CREATED_GROUP(2002),
/**
* 推送更新群组通知 2003通知给所有人
*/
UPDATED_GROUP(2003),
/**
* 推送退出群组通知 2004通知给管理员和操作人
*/
EXIT_GROUP(2004),
/**
* 推送修改群成员通知 2005通知给管理员和被操作人
*/
UPDATED_MEMBER(2005),
/**
* 推送删除群成员通知 2006通知给所有群成员和被踢人
*/
DELETED_MEMBER(2006),
/**
* 推送解散群通知 2007通知所有人
*/
DESTROY_GROUP(2007),
/**
* 推送转让群主 2008通知所有人
*/
TRANSFER_GROUP(2008),
/**
* 禁言群 2009通知所有人
*/
MUTE_GROUP(2009),
/**
* 禁言/解禁 群成员 2010通知管理员和被操作人
*/
SPEAK_GOUP_MEMBER(2010),
//群聊消息收发 2104
MSG_GROUP(0x838),
//发送消息已读 2106
MSG_GROUP_READED(0x83a),
//消息已读通知给同步端 2053
MSG_GROUP_READED_NOTIFY(0x805),
//消息已读回执,给原消息发送方 2054
MSG_GROUP_READED_RECEIPT(0x806),
//群聊消息ack 2047
GROUP_MSG_ACK(0x7ff),
;
private Integer command;
GroupEventCommand(int command) {
this.command = command;
}
public int getCommand() {
return command;
}
}

View File

@@ -0,0 +1,46 @@
package com.lld.im.common.enums.command;
public enum MediaEventCommand implements Command {
//6000 向对方拨打语音 notify ack
CALL_VOICE(6000),
//6001 向对方拨打视频 notify ack
CALL_VIDEO(6001),
//6002 同意请求 notify ack
ACCEPT_CALL(6002),
//6003 同步ice
// TRANSMIT_ICE(6003),
// //6004 发送offer
// TRANSMIT_OFFER(6004),
// //6005 发送ANSWER
// TRANSMIT_ANSWER(6005),
//6006 hangup 挂断 notify ack
HANG_UP(6006),
//6007 拒绝 notify ack
REJECT_CALL(6007),
//6008 取消呼叫 notify ack
CANCEL_CALL(6008),
;
private Integer command;
MediaEventCommand(int command) {
this.command = command;
}
@Override
public int getCommand() {
return command;
}
}

View File

@@ -0,0 +1,45 @@
package com.lld.im.common.enums.command;
public enum MessageCommand implements Command {
//单聊消息 1103
MSG_P2P(0x44F),
//单聊消息ACK 1046
MSG_ACK(0x416),
//消息收到ack 1107
MSG_RECIVE_ACK(1107),
//发送消息已读 1106
MSG_READED(0x452),
//消息已读通知给同步端 1053
MSG_READED_NOTIFY(0x41D),
//消息已读回执,给原消息发送方 1054
MSG_READED_RECEIPT(0x41E),
//消息撤回 1050
MSG_RECALL(0x41A),
//消息撤回通知 1052
MSG_RECALL_NOTIFY(0x41C),
//消息撤回回报 1051
MSG_RECALL_ACK(0x41B),
;
private int command;
MessageCommand(int command){
this.command=command;
}
@Override
public int getCommand() {
return command;
}
}

View File

@@ -0,0 +1,35 @@
package com.lld.im.common.enums.command;
public enum SystemCommand implements Command {
//心跳 9999
PING(0x270f),
/**
* 登录 9000
*/
LOGIN(0x2328),
//登录ack 9001
LOGINACK(0x2329),
//登出 9003
LOGOUT(0x232b),
//下线通知 用于多端互斥 9002
MUTUALLOGIN(0x232a),
;
private int command;
SystemCommand(int command){
this.command=command;
}
@Override
public int getCommand() {
return command;
}
}

View File

@@ -0,0 +1,32 @@
package com.lld.im.common.enums.command;
public enum UserEventCommand implements Command {
//用户修改command 4000
USER_MODIFY(4000),
//4001
USER_ONLINE_STATUS_CHANGE(4001),
//4004 用户在线状态通知报文
USER_ONLINE_STATUS_CHANGE_NOTIFY(4004),
//4005 用户在线状态通知同步报文
USER_ONLINE_STATUS_CHANGE_NOTIFY_SYNC(4005),
;
private int command;
UserEventCommand(int command){
this.command=command;
}
@Override
public int getCommand() {
return command;
}
}

View File

@@ -0,0 +1,44 @@
package com.lld.im.common.exception;
/**
* @author: Chackylee
* @description:
**/
public class ApplicationException extends RuntimeException {
private int code;
private String error;
public ApplicationException(int code, String message) {
super(message);
this.code = code;
this.error = message;
}
public ApplicationException(ApplicationExceptionEnum exceptionEnum) {
super(exceptionEnum.getError());
this.code = exceptionEnum.getCode();
this.error = exceptionEnum.getError();
}
public int getCode() {
return code;
}
public String getError() {
return error;
}
/**
* avoid the expensive and useless stack trace for api exceptions
* @see Throwable#fillInStackTrace()
*/
@Override
public Throwable fillInStackTrace() {
return this;
}
}

View File

@@ -0,0 +1,8 @@
package com.lld.im.common.exception;
public interface ApplicationExceptionEnum {
int getCode();
String getError();
}

View File

@@ -0,0 +1,25 @@
package com.lld.im.common.model;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author: Chackylee
* @description:
**/
@Data
@NoArgsConstructor
public class ClientInfo {
private Integer appId;
private Integer clientType;
private String imei;
public ClientInfo(Integer appId, Integer clientType, String imei) {
this.appId = appId;
this.clientType = clientType;
this.imei = imei;
}
}

View File

@@ -0,0 +1,19 @@
package com.lld.im.common.model;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class RequestBase {
private Integer appId;
private String operater;
private Integer clientType;
private String imei;
}

View File

@@ -0,0 +1,17 @@
package com.lld.im.common.model;
import lombok.Data;
/**
* @author: Chackylee
* @description:
**/
@Data
public class SyncReq extends RequestBase {
//客户端最大seq
private Long lastSequence;
//一次拉取多少
private Integer maxLimit;
}

View File

@@ -0,0 +1,20 @@
package com.lld.im.common.model;
import lombok.Data;
import java.util.List;
/**
* @author: Chackylee
* @description:
**/
@Data
public class SyncResp<T> {
private Long maxSequence;
private boolean isCompleted;
private List<T> dataList;
}

View File

@@ -0,0 +1,21 @@
package com.lld.im.common.model;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class UserClientDto {
private Integer appId;
private Integer clientType;
private String userId;
private String imei;
}

View File

@@ -0,0 +1,37 @@
package com.lld.im.common.model;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class UserSession {
private String userId;
/**
* 应用ID
*/
private Integer appId;
/**
* 端的标识
*/
private Integer clientType;
//sdk 版本号
private Integer version;
//连接状态 1=在线 2=离线
private Integer connectState;
private Integer brokerId;
private String brokerHost;
private String imei;
}

View File

@@ -0,0 +1,22 @@
package com.lld.im.common.model.message;
import lombok.Data;
import sun.dc.pr.PRError;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class CheckSendMessageReq {
private String fromId;
private String toId;
private Integer appId;
private Integer command;
}

View File

@@ -0,0 +1,17 @@
package com.lld.im.common.model.message;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class DoStoreGroupMessageDto {
private GroupChatMessageContent groupChatMessageContent;
private ImMessageBody messageBody;
}

View File

@@ -0,0 +1,17 @@
package com.lld.im.common.model.message;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class DoStoreP2PMessageDto {
private MessageContent messageContent;
private ImMessageBody messageBody;
}

View File

@@ -0,0 +1,19 @@
package com.lld.im.common.model.message;
import lombok.Data;
import java.util.List;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class GroupChatMessageContent extends MessageContent {
private String groupId;
private List<String> memberId;
}

View File

@@ -0,0 +1,29 @@
package com.lld.im.common.model.message;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class ImMessageBody {
private Integer appId;
/** messageBodyId*/
private Long messageKey;
/** messageBody*/
private String messageBody;
private String securityKey;
private Long messageTime;
private Long createTime;
private String extra;
private Integer delFlag;
}

View File

@@ -0,0 +1,30 @@
package com.lld.im.common.model.message;
import com.lld.im.common.model.ClientInfo;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class MessageContent extends ClientInfo {
private String messageId;
private String fromId;
private String toId;
private String messageBody;
private Long messageTime;
private String extra;
private Long messageKey;
private long messageSequence;
}

View File

@@ -0,0 +1,24 @@
package com.lld.im.common.model.message;
import com.lld.im.common.model.ClientInfo;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class MessageReadedContent extends ClientInfo {
private long messageSequence;
private String fromId;
private String groupId;
private String toId;
private Integer conversationType;
}

View File

@@ -0,0 +1,24 @@
package com.lld.im.common.model.message;
import com.lld.im.common.model.ClientInfo;
import lombok.Data;
import sun.dc.pr.PRError;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class MessageReciveAckContent extends ClientInfo {
private Long messageKey;
private String fromId;
private String toId;
private Long messageSequence;
}

View File

@@ -0,0 +1,40 @@
package com.lld.im.common.model.message;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class OfflineMessageContent {
private Integer appId;
/** messageBodyId*/
private Long messageKey;
/** messageBody*/
private String messageBody;
private Long messageTime;
private String extra;
private Integer delFlag;
private String fromId;
private String toId;
/** 序列号*/
private Long messageSequence;
private String messageRandom;
private Integer conversationType;
private String conversationId;
}

View File

@@ -0,0 +1,37 @@
package com.lld.im.common.model.message;
import com.lld.im.common.model.ClientInfo;
import lombok.Data;
/**
* @author: Chackylee
* @description:
**/
@Data
public class RecallMessageContent extends ClientInfo {
private Long messageKey;
private String fromId;
private String toId;
private Long messageTime;
private Long messageSequence;
private Integer conversationType;
// {
// "messageKey":419455774914383872,
// "fromId":"lld",
// "toId":"lld4",
// "messageTime":"1665026849851",
// "messageSequence":2,
// "appId": 10000,
// "clientType": 1,
// "imei": "web",
// "conversationType":0
// }
}

View File

@@ -0,0 +1,14 @@
package com.lld.im.common.route;
import java.util.List;
/**
* @description:
* @author: lld
* @version: 1.0
*/
public interface RouteHandle {
public String routeServer(List<String> values,String key);
}

View File

@@ -0,0 +1,19 @@
package com.lld.im.common.route;
import lombok.Data;
/**
* @since JDK 1.8
*/
@Data
public final class RouteInfo {
private String ip;
private Integer port;
public RouteInfo(String ip, Integer port) {
this.ip = ip;
this.port = port;
}
}

View File

@@ -0,0 +1,79 @@
package com.lld.im.common.route.algorithm.consistenthash;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
/**
* @description: 一致性hash 抽象类
* @author: lld
* @version: 1.0
*/
public abstract class AbstractConsistentHash {
//add
protected abstract void add(long key,String value);
//sort
protected void sort(){}
//获取节点 get
protected abstract String getFirstNodeValue(String value);
/**
* 处理之前事件
*/
protected abstract void processBefore();
/**
* 传入节点列表以及客户端信息获取一个服务节点
* @param values
* @param key
* @return
*/
public synchronized String process(List<String> values, String key){
processBefore();
for (String value : values) {
add(hash(value), value);
}
sort();
return getFirstNodeValue(key) ;
}
//hash
/**
* hash 运算
* @param value
* @return
*/
public Long hash(String value){
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 not supported", e);
}
md5.reset();
byte[] keyBytes = null;
try {
keyBytes = value.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unknown string :" + value, e);
}
md5.update(keyBytes);
byte[] digest = md5.digest();
// hash code, Truncate to 32-bits
long hashCode = ((long) (digest[3] & 0xFF) << 24)
| ((long) (digest[2] & 0xFF) << 16)
| ((long) (digest[1] & 0xFF) << 8)
| (digest[0] & 0xFF);
long truncateHashCode = hashCode & 0xffffffffL;
return truncateHashCode;
}
}

View File

@@ -0,0 +1,25 @@
package com.lld.im.common.route.algorithm.consistenthash;
import com.lld.im.common.route.RouteHandle;
import java.util.List;
/**
* @description:
* @author: lld
* @version: 1.0
*/
public class ConsistentHashHandle implements RouteHandle {
//TreeMap
private AbstractConsistentHash hash;
public void setHash(AbstractConsistentHash hash) {
this.hash = hash;
}
@Override
public String routeServer(List<String> values, String key) {
return hash.process(values,key);
}
}

View File

@@ -0,0 +1,49 @@
package com.lld.im.common.route.algorithm.consistenthash;
import com.lld.im.common.enums.UserErrorCode;
import com.lld.im.common.exception.ApplicationException;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* @description:
* @author: lld
* @version: 1.0
*/
public class TreeMapConsistentHash extends AbstractConsistentHash {
private TreeMap<Long,String> treeMap = new TreeMap<>();
private static final int NODE_SIZE = 2;
@Override
protected void add(long key, String value) {
for (int i = 0; i < NODE_SIZE; i++) {
treeMap.put(super.hash("node" + key +i),value);
}
treeMap.put(key,value);
}
@Override
protected String getFirstNodeValue(String value) {
Long hash = super.hash(value);
SortedMap<Long, String> last = treeMap.tailMap(hash);
if(!last.isEmpty()){
return last.get(last.firstKey());
}
if (treeMap.size() == 0){
throw new ApplicationException(UserErrorCode.SERVER_NOT_AVAILABLE) ;
}
return treeMap.firstEntry().getValue();
}
@Override
protected void processBefore() {
treeMap.clear();
}
}

View File

@@ -0,0 +1,31 @@
package com.lld.im.common.route.algorithm.loop;
import com.lld.im.common.enums.UserErrorCode;
import com.lld.im.common.exception.ApplicationException;
import com.lld.im.common.route.RouteHandle;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
* @description:
* @author: lld
* @version: 1.0
*/
public class LoopHandle implements RouteHandle {
private AtomicLong index = new AtomicLong();
@Override
public String routeServer(List<String> values, String key) {
int size = values.size();
if(size == 0){
throw new ApplicationException(UserErrorCode.SERVER_NOT_AVAILABLE);
}
Long l = index.incrementAndGet() % size;
if(l < 0){
l = 0L;
}
return values.get(l.intValue());
}
}

View File

@@ -0,0 +1,25 @@
package com.lld.im.common.route.algorithm.random;
import com.lld.im.common.enums.UserErrorCode;
import com.lld.im.common.exception.ApplicationException;
import com.lld.im.common.route.RouteHandle;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* @description:
* @author: lld
* @version: 1.0
*/
public class RandomHandle implements RouteHandle {
@Override
public String routeServer(List<String> values, String key) {
int size = values.size();
if(size == 0){
throw new ApplicationException(UserErrorCode.SERVER_NOT_AVAILABLE);
}
int i = ThreadLocalRandom.current().nextInt(size);
return values.get(i);
}
}

View File

@@ -0,0 +1,88 @@
package com.lld.im.common.utils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.io.IOException;
import java.nio.charset.Charset;
/**
* @description:
* @author: lld
* @version: 1.0
*/
public class Base64URL {
public static byte[] base64EncodeUrl(byte[] input) {
byte[] base64 = new BASE64Encoder().encode(input).getBytes();
for (int i = 0; i < base64.length; ++i)
switch (base64[i]) {
case '+':
base64[i] = '*';
break;
case '/':
base64[i] = '-';
break;
case '=':
base64[i] = '_';
break;
default:
break;
}
return base64;
}
public static byte[] base64EncodeUrlNotReplace(byte[] input) {
byte[] base64 = new BASE64Encoder().encode(input).getBytes(Charset.forName("UTF-8"));
for (int i = 0; i < base64.length; ++i)
switch (base64[i]) {
case '+':
base64[i] = '*';
break;
case '/':
base64[i] = '-';
break;
case '=':
base64[i] = '_';
break;
default:
break;
}
return base64;
}
public static byte[] base64DecodeUrlNotReplace(byte[] input) throws IOException {
for (int i = 0; i < input.length; ++i)
switch (input[i]) {
case '*':
input[i] = '+';
break;
case '-':
input[i] = '/';
break;
case '_':
input[i] = '=';
break;
default:
break;
}
return new BASE64Decoder().decodeBuffer(new String(input,"UTF-8"));
}
public static byte[] base64DecodeUrl(byte[] input) throws IOException {
byte[] base64 = input.clone();
for (int i = 0; i < base64.length; ++i)
switch (base64[i]) {
case '*':
base64[i] = '+';
break;
case '-':
base64[i] = '/';
break;
case '_':
base64[i] = '=';
break;
default:
break;
}
return new BASE64Decoder().decodeBuffer(base64.toString());
}
}

View File

@@ -0,0 +1,278 @@
package com.lld.im.common.utils;
import com.alibaba.fastjson.JSON;
import com.lld.im.common.config.GlobalHttpClientConfig;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
/**
* @author: Chackylee
* @description:
**/
@Component
public class HttpRequestUtils {
@Autowired
private CloseableHttpClient httpClient;
@Autowired
private RequestConfig requestConfig;
@Autowired
GlobalHttpClientConfig httpClientConfig;
public String doGet(String url, Map<String, Object> params, String charset) throws Exception {
return doGet(url,params,null,charset);
}
/**
* 通过给的url地址获取服务器数据
*
* @param url 服务器地址
* @param params 封装用户参数
* @param charset 设定字符编码
* @return
*/
public String doGet(String url, Map<String, Object> params, Map<String, Object> header, String charset) throws Exception {
if (StringUtils.isEmpty(charset)) {
charset = "utf-8";
}
URIBuilder uriBuilder = new URIBuilder(url);
// 判断是否有参数
if (params != null) {
// 遍历map,拼接请求参数
for (Map.Entry<String, Object> entry : params.entrySet()) {
uriBuilder.setParameter(entry.getKey(), entry.getValue().toString());
}
}
// 声明 http get 请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.setConfig(requestConfig);
if (header != null) {
// 遍历map,拼接header参数
for (Map.Entry<String, Object> entry : header.entrySet()) {
httpGet.addHeader(entry.getKey(),entry.getValue().toString());
}
}
String result = "";
try {
// 发起请求
CloseableHttpResponse response = httpClient.execute(httpGet);
// 判断状态码是否为200
if (response.getStatusLine().getStatusCode() == 200) {
// 返回响应体的内容
result = EntityUtils.toString(response.getEntity(), charset);
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return result;
}
/**
* GET请求 含URL 参数
*
* @param url
* @param params
* @return 如果状态码为200则返回body如果不为200则返回null
* @throws Exception
*/
public String doGet(String url, Map<String, Object> params) throws Exception {
return doGet(url, params, null);
}
/**
* GET 请求不含URL参数
*
* @param url
* @return
* @throws Exception
*/
public String doGet(String url) throws Exception {
return doGet(url, null, null);
}
public String doPost(String url, Map<String, Object> params, String jsonBody, String charset) throws Exception {
return doPost(url,params,null,jsonBody,charset);
}
/**
* 带参数的post请求
*
* @param url
* @return
* @throws Exception
*/
public String doPost(String url, Map<String, Object> params, Map<String, Object> header, String jsonBody, String charset) throws Exception {
if (StringUtils.isEmpty(charset)) {
charset = "utf-8";
}
URIBuilder uriBuilder = new URIBuilder(url);
// 判断是否有参数
if (params != null) {
// 遍历map,拼接请求参数
for (Map.Entry<String, Object> entry : params.entrySet()) {
uriBuilder.setParameter(entry.getKey(), entry.getValue().toString());
}
}
// 声明httpPost请求
HttpPost httpPost = new HttpPost(uriBuilder.build());
// 加入配置信息
httpPost.setConfig(requestConfig);
// 判断map是否为空不为空则进行遍历封装from表单对象
if (StringUtils.isNotEmpty(jsonBody)) {
StringEntity s = new StringEntity(jsonBody, charset);
s.setContentEncoding(charset);
s.setContentType("application/json");
// 把json body放到post里
httpPost.setEntity(s);
}
if (header != null) {
// 遍历map,拼接header参数
for (Map.Entry<String, Object> entry : header.entrySet()) {
httpPost.addHeader(entry.getKey(),entry.getValue().toString());
}
}
String result = "";
// CloseableHttpClient httpClient = HttpClients.createDefault(); // 单个
CloseableHttpResponse response = null;
try {
// 发起请求
response = httpClient.execute(httpPost);
// 判断状态码是否为200
if (response.getStatusLine().getStatusCode() == 200) {
// 返回响应体的内容
result = EntityUtils.toString(response.getEntity(), charset);
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return result;
}
/**
* 不带参数post请求
* @param url
* @return
* @throws Exception
*/
public String doPost(String url) throws Exception {
return doPost(url, null,null,null);
}
/**
* get 方法调用的通用方式
* @param url
* @param tClass
* @param map
* @param charSet
* @return
* @throws Exception
*/
public <T> T doGet(String url, Class<T> tClass, Map<String, Object> map, String charSet) throws Exception {
String result = doGet(url, map, charSet);
if (StringUtils.isNotEmpty(result))
return JSON.parseObject(result, tClass);
return null;
}
/**
* get 方法调用的通用方式
* @param url
* @param tClass
* @param map
* @param charSet
* @return
* @throws Exception
*/
public <T> T doGet(String url, Class<T> tClass, Map<String, Object> map, Map<String, Object> header, String charSet) throws Exception {
String result = doGet(url, map, header, charSet);
if (StringUtils.isNotEmpty(result))
return JSON.parseObject(result, tClass);
return null;
}
/**
* post 方法调用的通用方式
* @param url
* @param tClass
* @param map
* @param jsonBody
* @param charSet
* @return
* @throws Exception
*/
public <T> T doPost(String url, Class<T> tClass, Map<String, Object> map, String jsonBody, String charSet) throws Exception {
String result = doPost(url, map,jsonBody,charSet);
if (StringUtils.isNotEmpty(result))
return JSON.parseObject(result, tClass);
return null;
}
public <T> T doPost(String url, Class<T> tClass, Map<String, Object> map, Map<String, Object> header, String jsonBody, String charSet) throws Exception {
String result = doPost(url, map, header,jsonBody,charSet);
if (StringUtils.isNotEmpty(result))
return JSON.parseObject(result, tClass);
return null;
}
/**
* post 方法调用的通用方式
* @param url
* @param map
* @param jsonBody
* @param charSet
* @return
* @throws Exception
*/
public String doPostString(String url, Map<String, Object> map, String jsonBody, String charSet) throws Exception {
return doPost(url, map,jsonBody,charSet);
}
/**
* post 方法调用的通用方式
* @param url
* @param map
* @param jsonBody
* @param charSet
* @return
* @throws Exception
*/
public String doPostString(String url, Map<String, Object> map, Map<String, Object> header, String jsonBody, String charSet) throws Exception {
return doPost(url, map, header, jsonBody,charSet);
}
}

View File

@@ -0,0 +1,23 @@
package com.lld.im.common.utils;
import com.lld.im.common.BaseErrorCode;
import com.lld.im.common.exception.ApplicationException;
import com.lld.im.common.route.RouteInfo;
/**
*
* @since JDK 1.8
*/
public class RouteInfoParseUtil {
public static RouteInfo parse(String info){
try {
String[] serverInfo = info.split(":");
RouteInfo routeInfo = new RouteInfo(serverInfo[0], Integer.parseInt(serverInfo[1])) ;
return routeInfo ;
}catch (Exception e){
throw new ApplicationException(BaseErrorCode.PARAMETER_ERROR) ;
}
}
}

View File

@@ -0,0 +1,194 @@
package com.lld.im.common.utils;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
/**
* @description: app用户 后台管理员
*
* 10000 xxx 10001 bbbb
* @author: lld
* @version: 1.0
*/
public class SigAPI {
final private long appId;
final private String key;
public SigAPI(long appId, String key) {
this.appId = appId;
this.key = key;
}
public static void main(String[] args) throws InterruptedException {
SigAPI asd = new SigAPI(10000, "123456");
String sign = asd.genUserSig("lld", 100000000);
// Thread.sleep(2000L);
JSONObject jsonObject = decodeUserSig(sign);
System.out.println("sign:" + sign);
System.out.println("decoder:" + jsonObject.toString());
}
/**
* @description: 解密方法
* @param
* @return com.alibaba.fastjson.JSONObject
* @author lld
*/
public static JSONObject decodeUserSig(String userSig) {
JSONObject sigDoc = new JSONObject(true);
try {
byte[] decodeUrlByte = Base64URL.base64DecodeUrlNotReplace(userSig.getBytes());
byte[] decompressByte = decompress(decodeUrlByte);
String decodeText = new String(decompressByte, "UTF-8");
if (StringUtils.isNotBlank(decodeText)) {
sigDoc = JSONObject.parseObject(decodeText);
}
} catch (Exception ex) {
ex.printStackTrace();
}
return sigDoc;
}
/**
* 解压缩
*
* @param data 待压缩的数据
* @return byte[] 解压缩后的数据
*/
public static byte[] decompress(byte[] data) {
byte[] output = new byte[0];
Inflater decompresser = new Inflater();
decompresser.reset();
decompresser.setInput(data);
ByteArrayOutputStream o = new ByteArrayOutputStream(data.length);
try {
byte[] buf = new byte[1024];
while (!decompresser.finished()) {
int i = decompresser.inflate(buf);
o.write(buf, 0, i);
}
output = o.toByteArray();
} catch (Exception e) {
output = data;
e.printStackTrace();
} finally {
try {
o.close();
} catch (IOException e) {
e.printStackTrace();
}
}
decompresser.end();
return output;
}
/**
* 【功能说明】用于签发 IM 服务中必须要使用的 UserSig 鉴权票据
* <p>
* 【参数说明】
*/
public String genUserSig(String userid, long expire) {
return genUserSig(userid, expire, null);
}
private String hmacsha256(String identifier, long currTime, long expire, String base64Userbuf) {
String contentToBeSigned = "TLS.identifier:" + identifier + "\n"
+ "TLS.appId:" + appId + "\n"
+ "TLS.expireTime:" + currTime + "\n"
+ "TLS.expire:" + expire + "\n";
if (null != base64Userbuf) {
contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";
}
try {
byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");
hmac.init(keySpec);
byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes(StandardCharsets.UTF_8));
return (Base64.getEncoder().encodeToString(byteSig)).replaceAll("\\s*", "");
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
return "";
}
}
private String genUserSig(String userid, long expire, byte[] userbuf) {
long currTime = System.currentTimeMillis() / 1000;
JSONObject sigDoc = new JSONObject();
sigDoc.put("TLS.identifier", userid);
sigDoc.put("TLS.appId", appId);
sigDoc.put("TLS.expire", expire);
sigDoc.put("TLS.expireTime", currTime);
String base64UserBuf = null;
if (null != userbuf) {
base64UserBuf = Base64.getEncoder().encodeToString(userbuf).replaceAll("\\s*", "");
sigDoc.put("TLS.userbuf", base64UserBuf);
}
String sig = hmacsha256(userid, currTime, expire, base64UserBuf);
if (sig.length() == 0) {
return "";
}
sigDoc.put("TLS.sig", sig);
Deflater compressor = new Deflater();
compressor.setInput(sigDoc.toString().getBytes(StandardCharsets.UTF_8));
compressor.finish();
byte[] compressedBytes = new byte[2048];
int compressedBytesLength = compressor.deflate(compressedBytes);
compressor.end();
return (new String(Base64URL.base64EncodeUrl(Arrays.copyOfRange(compressedBytes,
0, compressedBytesLength)))).replaceAll("\\s*", "");
}
public String genUserSig(String userid, long expire, long time,byte [] userbuf) {
JSONObject sigDoc = new JSONObject();
sigDoc.put("TLS.identifier", userid);
sigDoc.put("TLS.appId", appId);
sigDoc.put("TLS.expire", expire);
sigDoc.put("TLS.expireTime", time);
String base64UserBuf = null;
if (null != userbuf) {
base64UserBuf = Base64.getEncoder().encodeToString(userbuf).replaceAll("\\s*", "");
sigDoc.put("TLS.userbuf", base64UserBuf);
}
String sig = hmacsha256(userid, time, expire, base64UserBuf);
if (sig.length() == 0) {
return "";
}
sigDoc.put("TLS.sig", sig);
Deflater compressor = new Deflater();
compressor.setInput(sigDoc.toString().getBytes(StandardCharsets.UTF_8));
compressor.finish();
byte[] compressedBytes = new byte[2048];
int compressedBytesLength = compressor.deflate(compressedBytes);
compressor.end();
return (new String(Base64URL.base64EncodeUrl(Arrays.copyOfRange(compressedBytes,
0, compressedBytesLength)))).replaceAll("\\s*", "");
}
}