移动app

This commit is contained in:
2023-09-24 17:55:19 +08:00
parent 736c5376e0
commit 59f7e39791
735 changed files with 80523 additions and 57 deletions

View File

@@ -18,26 +18,26 @@
<dependencies>
<!-- rabbitmq -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-amqp</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.github.sgroschupf</groupId>-->
<!-- <artifactId>zkclient</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
</dependency>
<!-- redis -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-data-redis</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring boot web -->
<dependency>
@@ -45,11 +45,11 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.lld</groupId>-->
<!-- <artifactId>codec</artifactId>-->
<!-- <version>1.0.0-SNAPSHOT</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.lld</groupId>
<artifactId>im-codec</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!-- hutool -->
<dependency>
@@ -87,6 +87,12 @@
<artifactId>common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.lld</groupId>
<artifactId>im-system</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@@ -98,4 +104,4 @@
</plugins>
</build>
</project>
</project>

View File

@@ -4,8 +4,10 @@ import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@SpringBootApplication(scanBasePackages = {"com.lld.im.service",
"com.lld.im.common"})
@MapperScan("com.lld.im.service.*.dao.mapper")
//导入用户资料,删除用户资料,修改用户资料,查询用户资料
public class Application {
public static void main(String[] args) {
@@ -17,4 +19,3 @@ public class Application {

View File

@@ -1,7 +1,22 @@
package com.lld.im.service.config;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.enums.ImUrlRouteWayEnum;
import com.lld.im.common.enums.RouteHashMethodEnum;
import com.lld.im.common.route.RouteHandle;
import com.lld.im.common.route.algorithm.consistenthash.AbstractConsistentHash;
import com.lld.im.common.route.algorithm.consistenthash.ConsistentHashHandle;
import com.lld.im.common.route.algorithm.consistenthash.TreeMapConsistentHash;
import com.lld.im.common.route.algorithm.loop.LoopHandle;
import com.lld.im.common.route.algorithm.random.RandomHandle;
import com.lld.im.service.utils.SnowflakeIdWorker;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
/**
* @description:
* @author: lld
@@ -10,4 +25,50 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig {
@Autowired
AppConfig appConfig;
@Bean
public ZkClient buildZKClient() {
return new ZkClient(appConfig.getZkAddr(),
appConfig.getZkConnectTimeOut());
}
@Bean
public RouteHandle routeHandle() throws Exception {
Integer imRouteWay = appConfig.getImRouteWay();
String routWay = "";
ImUrlRouteWayEnum handler = ImUrlRouteWayEnum.getHandler(imRouteWay);
routWay = handler.getClazz();
RouteHandle routeHandle = (RouteHandle) Class.forName(routWay).newInstance();
if(handler == ImUrlRouteWayEnum.HASH){
Method setHash = Class.forName(routWay).getMethod("setHash", AbstractConsistentHash.class);
Integer consistentHashWay = appConfig.getConsistentHashWay();
String hashWay = "";
RouteHashMethodEnum hashHandler = RouteHashMethodEnum.getHandler(consistentHashWay);
hashWay = hashHandler.getClazz();
AbstractConsistentHash consistentHash
= (AbstractConsistentHash) Class.forName(hashWay).newInstance();
setHash.invoke(routeHandle,consistentHash);
}
return routeHandle;
}
@Bean
public EasySqlInjector easySqlInjector () {
return new EasySqlInjector();
}
@Bean
public SnowflakeIdWorker buildSnowflakeSeq() throws Exception {
return new SnowflakeIdWorker(0);
}
}

View File

@@ -0,0 +1,17 @@
package com.lld.im.service.config;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
import java.util.List;
public class EasySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
methodList.add(new InsertBatchSomeColumn()); // 添加InsertBatchSomeColumn方法
return methodList;
}
}

View File

@@ -0,0 +1,48 @@
package com.lld.im.service.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author: Chackylee
* @description:
**/
@Configuration
public class RedisConfig {
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
//Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
//使用Fastjson2JsonRedisSerializer来序列化和反序列化redis的value值 by zhengkai
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(new StringRedisSerializer());
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
return template;
}
}

View File

@@ -1,7 +1,10 @@
package com.lld.im.service.config;
import com.lld.im.service.interceptor.GateWayInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
@@ -12,6 +15,16 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
GateWayInterceptor gateWayInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(gateWayInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/v1/user/login")
.excludePathPatterns("/v1/message/checkSend");
}
@Override
public void addCorsMappings(CorsRegistry registry) {

View File

@@ -0,0 +1,49 @@
package com.lld.im.service.conversation.controller;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.model.SyncReq;
import com.lld.im.service.conversation.model.DeleteConversationReq;
import com.lld.im.service.conversation.model.UpdateConversationReq;
import com.lld.im.service.conversation.service.ConversationService;
import com.lld.im.service.group.model.req.ImportGroupReq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@RestController
@RequestMapping("v1/conversation")
public class ConversationController {
@Autowired
ConversationService conversationService;
@RequestMapping("/deleteConversation")
public ResponseVO deleteConversation(@RequestBody @Validated DeleteConversationReq
req, Integer appId, String identifier) {
req.setAppId(appId);
// req.setOperater(identifier);
return conversationService.deleteConversation(req);
}
@RequestMapping("/updateConversation")
public ResponseVO updateConversation(@RequestBody @Validated UpdateConversationReq
req, Integer appId, String identifier) {
req.setAppId(appId);
// req.setOperater(identifier);
return conversationService.updateConversation(req);
}
@RequestMapping("/syncConversationList")
public ResponseVO syncFriendShipList(@RequestBody @Validated SyncReq req, Integer appId) {
req.setAppId(appId);
return conversationService.syncConversationSet(req);
}
}

View File

@@ -0,0 +1,33 @@
package com.lld.im.service.conversation.dao;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author: Chackylee
* @description:
**/
@Data
@TableName("im_conversation_set")
public class ImConversationSetEntity {
//会话id 0_fromId_toId
private String conversationId;
//会话类型
private Integer conversationType;
private String fromId;
private String toId;
private int isMute;
private int isTop;
private Long sequence;
private Long readedSequence;
private Integer appId;
}

View File

@@ -0,0 +1,23 @@
package com.lld.im.service.conversation.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lld.im.service.conversation.dao.ImConversationSetEntity;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Repository
public interface ImConversationSetMapper extends BaseMapper<ImConversationSetEntity> {
@Update(" update im_conversation_set set readed_sequence = #{readedSequence},sequence = #{sequence} " +
" where conversation_id = #{conversationId} and app_id = #{appId} AND readed_sequence < #{readedSequence}")
public void readMark(ImConversationSetEntity imConversationSetEntity);
@Select(" select max(sequence) from im_conversation_set where app_id = #{appId} AND from_id = #{userId} ")
Long geConversationSetMaxSeq(Integer appId, String userId);
}

View File

@@ -0,0 +1,22 @@
package com.lld.im.service.conversation.model;
import com.lld.im.common.model.RequestBase;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class DeleteConversationReq extends RequestBase {
@NotBlank(message = "会话id不能为空")
private String conversationId;
@NotBlank(message = "fromId不能为空")
private String fromId;
}

View File

@@ -0,0 +1,23 @@
package com.lld.im.service.conversation.model;
import com.lld.im.common.model.RequestBase;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class UpdateConversationReq extends RequestBase {
private String conversationId;
private Integer isMute;
private Integer isTop;
private String fromId;
}

View File

@@ -0,0 +1,199 @@
package com.lld.im.service.conversation.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.lld.im.codec.pack.conversation.DeleteConversationPack;
import com.lld.im.codec.pack.conversation.UpdateConversationPack;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.ConversationErrorCode;
import com.lld.im.common.enums.ConversationTypeEnum;
import com.lld.im.common.enums.command.ConversationEventCommand;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.common.model.SyncReq;
import com.lld.im.common.model.SyncResp;
import com.lld.im.common.model.message.MessageReadedContent;
import com.lld.im.service.conversation.dao.ImConversationSetEntity;
import com.lld.im.service.conversation.dao.mapper.ImConversationSetMapper;
import com.lld.im.service.conversation.model.DeleteConversationReq;
import com.lld.im.service.conversation.model.UpdateConversationReq;
import com.lld.im.service.friendship.dao.ImFriendShipEntity;
import com.lld.im.service.seq.RedisSeq;
import com.lld.im.service.utils.MessageProducer;
import com.lld.im.service.utils.WriteUserSeq;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Service
public class ConversationService {
@Autowired
ImConversationSetMapper imConversationSetMapper;
@Autowired
MessageProducer messageProducer;
@Autowired
AppConfig appConfig;
@Autowired
RedisSeq redisSeq;
@Autowired
WriteUserSeq writeUserSeq;
public String convertConversationId(Integer type,String fromId,String toId){
return type + "_" + fromId + "_" + toId;
}
public void messageMarkRead(MessageReadedContent messageReadedContent){
String toId = messageReadedContent.getToId();
if(messageReadedContent.getConversationType() == ConversationTypeEnum.GROUP.getCode()){
toId = messageReadedContent.getGroupId();
}
String conversationId = convertConversationId(messageReadedContent.getConversationType(),
messageReadedContent.getFromId(), toId);
QueryWrapper<ImConversationSetEntity> query = new QueryWrapper<>();
query.eq("conversation_id",conversationId);
query.eq("app_id",messageReadedContent.getAppId());
ImConversationSetEntity imConversationSetEntity = imConversationSetMapper.selectOne(query);
if(imConversationSetEntity == null){
imConversationSetEntity = new ImConversationSetEntity();
long seq = redisSeq.doGetSeq(messageReadedContent.getAppId() + ":" + Constants.SeqConstants.Conversation);
imConversationSetEntity.setConversationId(conversationId);
BeanUtils.copyProperties(messageReadedContent,imConversationSetEntity);
imConversationSetEntity.setReadedSequence(messageReadedContent.getMessageSequence());
imConversationSetEntity.setToId(toId);
imConversationSetEntity.setSequence(seq);
imConversationSetMapper.insert(imConversationSetEntity);
writeUserSeq.writeUserSeq(messageReadedContent.getAppId(),
messageReadedContent.getFromId(),Constants.SeqConstants.Conversation,seq);
}else{
long seq = redisSeq.doGetSeq(messageReadedContent.getAppId() + ":" + Constants.SeqConstants.Conversation);
imConversationSetEntity.setSequence(seq);
imConversationSetEntity.setReadedSequence(messageReadedContent.getMessageSequence());
imConversationSetMapper.readMark(imConversationSetEntity);
writeUserSeq.writeUserSeq(messageReadedContent.getAppId(),
messageReadedContent.getFromId(),Constants.SeqConstants.Conversation,seq);
}
}
/**
* @description: 删除会话
* @param
* @return com.lld.im.common.ResponseVO
* @author lld
*/
public ResponseVO deleteConversation(DeleteConversationReq req){
//置顶 有免打扰
// QueryWrapper<ImConversationSetEntity> queryWrapper = new QueryWrapper<>();
// queryWrapper.eq("conversation_id",req.getConversationId());
// queryWrapper.eq("app_id",req.getAppId());
// ImConversationSetEntity imConversationSetEntity = imConversationSetMapper.selectOne(queryWrapper);
// if(imConversationSetEntity != null){
// imConversationSetEntity.setIsMute(0);
// imConversationSetEntity.setIsTop(0);
// imConversationSetMapper.update(imConversationSetEntity,queryWrapper);
// }
if(appConfig.getDeleteConversationSyncMode() == 1){
DeleteConversationPack pack = new DeleteConversationPack();
pack.setConversationId(req.getConversationId());
messageProducer.sendToUserExceptClient(req.getFromId(),
ConversationEventCommand.CONVERSATION_DELETE,
pack,new ClientInfo(req.getAppId(),req.getClientType(),
req.getImei()));
}
return ResponseVO.successResponse();
}
/**
* @description: 更新会话 置顶or免打扰
* @param
* @return com.lld.im.common.ResponseVO
* @author lld
*/
public ResponseVO updateConversation(UpdateConversationReq req){
if(req.getIsTop() == null && req.getIsMute() == null){
return ResponseVO.errorResponse(ConversationErrorCode.CONVERSATION_UPDATE_PARAM_ERROR);
}
QueryWrapper<ImConversationSetEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("conversation_id",req.getConversationId());
queryWrapper.eq("app_id",req.getAppId());
ImConversationSetEntity imConversationSetEntity = imConversationSetMapper.selectOne(queryWrapper);
if(imConversationSetEntity != null){
long seq = redisSeq.doGetSeq(req.getAppId() + ":" + Constants.SeqConstants.Conversation);
if(req.getIsMute() != null){
imConversationSetEntity.setIsTop(req.getIsTop());
}
if(req.getIsMute() != null){
imConversationSetEntity.setIsMute(req.getIsMute());
}
imConversationSetEntity.setSequence(seq);
imConversationSetMapper.update(imConversationSetEntity,queryWrapper);
writeUserSeq.writeUserSeq(req.getAppId(), req.getFromId(),
Constants.SeqConstants.Conversation, seq);
UpdateConversationPack pack = new UpdateConversationPack();
pack.setConversationId(req.getConversationId());
pack.setIsMute(imConversationSetEntity.getIsMute());
pack.setIsTop(imConversationSetEntity.getIsTop());
pack.setSequence(seq);
pack.setConversationType(imConversationSetEntity.getConversationType());
messageProducer.sendToUserExceptClient(req.getFromId(),
ConversationEventCommand.CONVERSATION_UPDATE,
pack,new ClientInfo(req.getAppId(),req.getClientType(),
req.getImei()));
}
return ResponseVO.successResponse();
}
public ResponseVO syncConversationSet(SyncReq req) {
if(req.getMaxLimit() > 100){
req.setMaxLimit(100);
}
SyncResp<ImConversationSetEntity> resp = new SyncResp<>();
//seq > req.getseq limit maxLimit
QueryWrapper<ImConversationSetEntity> queryWrapper =
new QueryWrapper<>();
queryWrapper.eq("from_id",req.getOperater());
queryWrapper.gt("sequence",req.getLastSequence());
queryWrapper.eq("app_id",req.getAppId());
queryWrapper.last(" limit " + req.getMaxLimit());
queryWrapper.orderByAsc("sequence");
List<ImConversationSetEntity> list = imConversationSetMapper
.selectList(queryWrapper);
if(!CollectionUtils.isEmpty(list)){
ImConversationSetEntity maxSeqEntity = list.get(list.size() - 1);
resp.setDataList(list);
//设置最大seq
Long friendShipMaxSeq = imConversationSetMapper.geConversationSetMaxSeq(req.getAppId(), req.getOperater());
resp.setMaxSequence(friendShipMaxSeq);
//设置是否拉取完毕
resp.setCompleted(maxSeqEntity.getSequence() >= friendShipMaxSeq);
return ResponseVO.successResponse(resp);
}
resp.setCompleted(true);
return ResponseVO.successResponse(resp);
}
}

View File

@@ -20,6 +20,7 @@ public interface ImFriendShipMapper extends BaseMapper<ImFriendShipEntity> {
"<foreach collection='toIds' index='index' item='id' separator=',' close = ')' open='(' > " +
"#{id}" +
"</foreach>" +
" and app_id = #{appId} " +
"</script>")
public List<CheckFriendShipResp> checkFriendShip(CheckFriendShipReq req);
@@ -88,4 +89,11 @@ public interface ImFriendShipMapper extends BaseMapper<ImFriendShipEntity> {
)
List<CheckFriendShipResp> checkFriendShipBlackBoth(CheckFriendShipReq toId);
@Select(" select max(friend_sequence) from im_friendship where app_id = #{appId} AND from_id = #{userId} ")
Long getFriendShipMaxSeq(Integer appId,String userId);
@Select(
" select to_id from im_friendship where from_id = #{userId} AND app_id = #{appId} and status = 1 and black = 1 "
)
List<String> getAllFriendId(String userId,Integer appId);
}

View File

@@ -0,0 +1,17 @@
package com.lld.im.service.friendship.model.callback;
import com.lld.im.service.friendship.model.req.FriendDto;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class AddFriendAfterCallbackDto {
private String fromId;
private FriendDto toItem;
}

View File

@@ -0,0 +1,16 @@
package com.lld.im.service.friendship.model.callback;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class AddFriendBlackAfterCallbackDto {
private String fromId;
private String toId;
}

View File

@@ -0,0 +1,17 @@
package com.lld.im.service.friendship.model.callback;
import com.lld.im.service.friendship.model.req.FriendDto;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class DeleteFriendAfterCallbackDto {
private String fromId;
private String toId;
}

View File

@@ -2,6 +2,7 @@ package com.lld.im.service.friendship.service;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.model.RequestBase;
import com.lld.im.common.model.SyncReq;
import com.lld.im.service.friendship.model.req.*;
import java.util.List;
@@ -37,4 +38,7 @@ public interface ImFriendService {
public ResponseVO checkBlck(CheckFriendShipReq req);
public ResponseVO syncFriendshipList(SyncReq req);
public List<String> getAllFriendId(String userId, Integer appId);
}

View File

@@ -17,5 +17,5 @@ public interface ImFriendShipGroupService {
public ResponseVO<ImFriendShipGroupEntity> getGroup(String fromId, String groupName, Integer appId);
public Long updateSeq(String fromId, String groupName, Integer appId);
}

View File

@@ -4,22 +4,36 @@ import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.lld.im.codec.pack.friendship.*;
import com.lld.im.codec.proto.Message;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.AllowFriendTypeEnum;
import com.lld.im.common.enums.CheckFriendShipTypeEnum;
import com.lld.im.common.enums.FriendShipErrorCode;
import com.lld.im.common.enums.FriendShipStatusEnum;
import com.lld.im.common.enums.command.FriendshipEventCommand;
import com.lld.im.common.exception.ApplicationException;
import com.lld.im.common.model.RequestBase;
import com.lld.im.common.model.SyncReq;
import com.lld.im.common.model.SyncResp;
import com.lld.im.service.friendship.dao.ImFriendShipEntity;
import com.lld.im.service.friendship.dao.mapper.ImFriendShipMapper;
import com.lld.im.service.friendship.model.callback.AddFriendAfterCallbackDto;
import com.lld.im.service.friendship.model.callback.AddFriendBlackAfterCallbackDto;
import com.lld.im.service.friendship.model.callback.DeleteFriendAfterCallbackDto;
import com.lld.im.service.friendship.model.req.*;
import com.lld.im.service.friendship.model.resp.CheckFriendShipResp;
import com.lld.im.service.friendship.model.resp.ImportFriendShipResp;
import com.lld.im.service.friendship.service.ImFriendService;
import com.lld.im.service.friendship.service.ImFriendShipRequestService;
import com.lld.im.service.seq.RedisSeq;
import com.lld.im.service.user.dao.ImUserDataEntity;
import com.lld.im.service.user.service.ImUserService;
import com.lld.im.service.utils.CallbackService;
import com.lld.im.service.utils.MessageProducer;
import com.lld.im.service.utils.WriteUserSeq;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -46,12 +60,27 @@ public class ImFriendServiceImpl implements ImFriendService {
@Autowired
ImUserService imUserService;
@Autowired
AppConfig appConfig;
@Autowired
CallbackService callbackService;
@Autowired
MessageProducer messageProducer;
@Autowired
ImFriendService imFriendService;
@Autowired
ImFriendShipRequestService imFriendShipRequestService;
@Autowired
RedisSeq redisSeq;
@Autowired
WriteUserSeq writeUserSeq;
@Override
public ResponseVO importFriendShip(ImporFriendShipReq req) {
@@ -101,6 +130,16 @@ public class ImFriendServiceImpl implements ImFriendService {
return toInfo;
}
if(appConfig.isAddFriendBeforeCallback()){
ResponseVO callbackResp = callbackService
.beforeCallback(req.getAppId(),
Constants.CallbackCommand.AddFriendBefore
,JSONObject.toJSONString(req));
if(!callbackResp.isOk()){
return callbackResp;
}
}
ImUserDataEntity data = toInfo.getData();
if(data.getFriendAllowType() != null && data.getFriendAllowType() == AllowFriendTypeEnum.NOT_NEED.getCode()){
@@ -141,6 +180,23 @@ public class ImFriendServiceImpl implements ImFriendService {
}
ResponseVO responseVO = this.doUpdate(req.getFromId(), req.getToItem(), req.getAppId());
if(responseVO.isOk()){
UpdateFriendPack updateFriendPack = new UpdateFriendPack();
updateFriendPack.setRemark(req.getToItem().getRemark());
updateFriendPack.setToId(req.getToItem().getToId());
messageProducer.sendToUser(req.getFromId(),
req.getClientType(),req.getImei(),FriendshipEventCommand
.FRIEND_UPDATE,updateFriendPack,req.getAppId());
if (appConfig.isModifyFriendAfterCallback()) {
AddFriendAfterCallbackDto callbackDto = new AddFriendAfterCallbackDto();
callbackDto.setFromId(req.getFromId());
callbackDto.setToItem(req.getToItem());
callbackService.beforeCallback(req.getAppId(),
Constants.CallbackCommand.UpdateFriendAfter, JSONObject
.toJSONString(callbackDto));
}
}
return responseVO;
}
@@ -148,9 +204,11 @@ public class ImFriendServiceImpl implements ImFriendService {
public ResponseVO doUpdate(String fromId, FriendDto dto,Integer appId){
long seq = redisSeq.doGetSeq(appId + ":" + Constants.SeqConstants.Friendship);
UpdateWrapper<ImFriendShipEntity> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda().set(ImFriendShipEntity::getAddSource,dto.getAddSource())
.set(ImFriendShipEntity::getExtra,dto.getExtra())
.set(ImFriendShipEntity::getFriendSequence,seq)
.set(ImFriendShipEntity::getRemark,dto.getRemark())
.eq(ImFriendShipEntity::getAppId,appId)
.eq(ImFriendShipEntity::getToId,dto.getToId())
@@ -158,13 +216,22 @@ public class ImFriendServiceImpl implements ImFriendService {
int update = imFriendShipMapper.update(null, updateWrapper);
if(update == 1){
//之后回调
// if (appConfig.isModifyFriendAfterCallback()){
// AddFriendAfterCallbackDto callbackDto = new AddFriendAfterCallbackDto();
// callbackDto.setFromId(fromId);
// callbackDto.setToItem(dto);
// callbackService.beforeCallback(appId,
// Constants.CallbackCommand.UpdateFriendAfter, JSONObject
// .toJSONString(callbackDto));
// }
writeUserSeq.writeUserSeq(appId,fromId,Constants.SeqConstants.Friendship,seq);
return ResponseVO.successResponse();
}
return ResponseVO.errorResponse();
}
@Override
@Transactional
public ResponseVO doAddFriend(RequestBase requestBase,String fromId, FriendDto dto, Integer appId){
@@ -177,10 +244,13 @@ public class ImFriendServiceImpl implements ImFriendService {
query.eq("from_id",fromId);
query.eq("to_id",dto.getToId());
ImFriendShipEntity fromItem = imFriendShipMapper.selectOne(query);
long seq = 0L;
if(fromItem == null){
//走添加逻辑。
fromItem = new ImFriendShipEntity();
seq = redisSeq.doGetSeq(appId+":"+Constants.SeqConstants.Friendship);
fromItem.setAppId(appId);
fromItem.setFriendSequence(seq);
fromItem.setFromId(fromId);
// entity.setToId(to);
BeanUtils.copyProperties(dto,fromItem);
@@ -190,8 +260,10 @@ public class ImFriendServiceImpl implements ImFriendService {
if(insert != 1){
return ResponseVO.errorResponse(FriendShipErrorCode.ADD_FRIEND_ERROR);
}
writeUserSeq.writeUserSeq(appId,fromId,Constants.SeqConstants.Friendship,seq);
} else{
//如果存在则判断状态,如果是已添加,则提示已添加,如果是未添加,则修改状态
if(fromItem.getStatus() == FriendShipStatusEnum.FRIEND_STATUS_NORMAL.getCode()){
return ResponseVO.errorResponse(FriendShipErrorCode.TO_IS_YOUR_FRIEND);
} else{
@@ -208,12 +280,15 @@ public class ImFriendServiceImpl implements ImFriendService {
if(StringUtils.isNotBlank(dto.getExtra())){
update.setExtra(dto.getExtra());
}
seq = redisSeq.doGetSeq(appId+":"+Constants.SeqConstants.Friendship);
update.setFriendSequence(seq);
update.setStatus(FriendShipStatusEnum.FRIEND_STATUS_NORMAL.getCode());
int result = imFriendShipMapper.update(update, query);
if(result != 1){
return ResponseVO.errorResponse(FriendShipErrorCode.ADD_FRIEND_ERROR);
}
writeUserSeq.writeUserSeq(appId,fromId,Constants.SeqConstants.Friendship,seq);
}
}
@@ -229,18 +304,53 @@ public class ImFriendServiceImpl implements ImFriendService {
toItem.setFromId(dto.getToId());
BeanUtils.copyProperties(dto,toItem);
toItem.setToId(fromId);
toItem.setFriendSequence(seq);
toItem.setStatus(FriendShipStatusEnum.FRIEND_STATUS_NORMAL.getCode());
toItem.setCreateTime(System.currentTimeMillis());
// toItem.setBlack(FriendShipStatusEnum.BLACK_STATUS_NORMAL.getCode());
int insert = imFriendShipMapper.insert(toItem);
writeUserSeq.writeUserSeq(appId,dto.getToId(),Constants.SeqConstants.Friendship,seq);
}else{
if(FriendShipStatusEnum.FRIEND_STATUS_NORMAL.getCode() !=
toItem.getStatus()){
ImFriendShipEntity update = new ImFriendShipEntity();
update.setFriendSequence(seq);
update.setStatus(FriendShipStatusEnum.FRIEND_STATUS_NORMAL.getCode());
imFriendShipMapper.update(update,toQuery);
writeUserSeq.writeUserSeq(appId,dto.getToId(),Constants.SeqConstants.Friendship,seq);
}
}
//发送给from
AddFriendPack addFriendPack = new AddFriendPack();
BeanUtils.copyProperties(fromItem,addFriendPack);
addFriendPack.setSequence(seq);
if(requestBase != null){
messageProducer.sendToUser(fromId,requestBase.getClientType(),
requestBase.getImei(), FriendshipEventCommand.FRIEND_ADD,addFriendPack
,requestBase.getAppId());
}else {
messageProducer.sendToUser(fromId,
FriendshipEventCommand.FRIEND_ADD,addFriendPack
,requestBase.getAppId());
}
AddFriendPack addFriendToPack = new AddFriendPack();
BeanUtils.copyProperties(toItem,addFriendPack);
messageProducer.sendToUser(toItem.getFromId(),
FriendshipEventCommand.FRIEND_ADD,addFriendToPack
,requestBase.getAppId());
//之后回调
if (appConfig.isAddFriendAfterCallback()){
AddFriendAfterCallbackDto callbackDto = new AddFriendAfterCallbackDto();
callbackDto.setFromId(fromId);
callbackDto.setToItem(dto);
callbackService.beforeCallback(appId,
Constants.CallbackCommand.AddFriendAfter, JSONObject
.toJSONString(callbackDto));
}
return ResponseVO.successResponse();
}
@@ -258,8 +368,29 @@ public class ImFriendServiceImpl implements ImFriendService {
}else{
if(fromItem.getStatus() != null && fromItem.getStatus() == FriendShipStatusEnum.FRIEND_STATUS_NORMAL.getCode()){
ImFriendShipEntity update = new ImFriendShipEntity();
long seq = redisSeq.doGetSeq(req.getAppId() + ":" + Constants.SeqConstants.Friendship);
update.setFriendSequence(seq);
update.setStatus(FriendShipStatusEnum.FRIEND_STATUS_DELETE.getCode());
imFriendShipMapper.update(update,query);
writeUserSeq.writeUserSeq(req.getAppId(),req.getFromId(),Constants.SeqConstants.Friendship,seq);
DeleteFriendPack deleteFriendPack = new DeleteFriendPack();
deleteFriendPack.setFromId(req.getFromId());
deleteFriendPack.setSequence(seq);
deleteFriendPack.setToId(req.getToId());
messageProducer.sendToUser(req.getFromId(),
req.getClientType(), req.getImei(),
FriendshipEventCommand.FRIEND_DELETE,
deleteFriendPack, req.getAppId());
//之后回调
if (appConfig.isAddFriendAfterCallback()){
DeleteFriendAfterCallbackDto callbackDto = new DeleteFriendAfterCallbackDto();
callbackDto.setFromId(req.getFromId());
callbackDto.setToId(req.getToId());
callbackService.beforeCallback(req.getAppId(),
Constants.CallbackCommand.DeleteFriendAfter, JSONObject
.toJSONString(callbackDto));
}
}else{
return ResponseVO.errorResponse(FriendShipErrorCode.FRIEND_IS_DELETED);
@@ -278,6 +409,12 @@ public class ImFriendServiceImpl implements ImFriendService {
ImFriendShipEntity update = new ImFriendShipEntity();
update.setStatus(FriendShipStatusEnum.FRIEND_STATUS_DELETE.getCode());
imFriendShipMapper.update(update,query);
DeleteAllFriendPack deleteFriendPack = new DeleteAllFriendPack();
deleteFriendPack.setFromId(req.getFromId());
messageProducer.sendToUser(req.getFromId(), req.getClientType(), req.getImei(), FriendshipEventCommand.FRIEND_ALL_DELETE,
deleteFriendPack, req.getAppId());
return ResponseVO.successResponse();
}
@@ -335,6 +472,42 @@ public class ImFriendServiceImpl implements ImFriendService {
return ResponseVO.successResponse(result);
}
@Override
public ResponseVO syncFriendshipList(SyncReq req) {
if(req.getMaxLimit() > 100){
req.setMaxLimit(100);
}
SyncResp<ImFriendShipEntity> resp = new SyncResp<>();
//seq > req.getseq limit maxLimit
QueryWrapper<ImFriendShipEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("from_id",req.getOperater());
queryWrapper.gt("friend_sequence",req.getLastSequence());
queryWrapper.eq("app_id",req.getAppId());
queryWrapper.last(" limit " + req.getMaxLimit());
queryWrapper.orderByAsc("friend_sequence");
List<ImFriendShipEntity> list = imFriendShipMapper.selectList(queryWrapper);
if(!CollectionUtils.isEmpty(list)){
ImFriendShipEntity maxSeqEntity = list.get(list.size() - 1);
resp.setDataList(list);
//设置最大seq
Long friendShipMaxSeq = imFriendShipMapper.getFriendShipMaxSeq(req.getAppId(), req.getOperater());
resp.setMaxSequence(friendShipMaxSeq);
//设置是否拉取完毕
resp.setCompleted(maxSeqEntity.getFriendSequence() >= friendShipMaxSeq);
return ResponseVO.successResponse(resp);
}
resp.setCompleted(true);
return ResponseVO.successResponse(resp);
}
@Override
public List<String> getAllFriendId(String userId, Integer appId) {
return imFriendShipMapper.getAllFriendId(userId,appId);
}
@Override
public ResponseVO addBlack(AddFriendShipBlackReq req) {
@@ -354,11 +527,15 @@ public class ImFriendServiceImpl implements ImFriendService {
query.eq("to_id",req.getToId());
ImFriendShipEntity fromItem = imFriendShipMapper.selectOne(query);
Long seq = 0L;
if(fromItem == null){
//走添加逻辑。
seq = redisSeq.doGetSeq(req.getAppId() + ":" + Constants.SeqConstants.Friendship);
fromItem = new ImFriendShipEntity();
fromItem.setFromId(req.getFromId());
fromItem.setToId(req.getToId());
fromItem.setFriendSequence(seq);
fromItem.setAppId(req.getAppId());
fromItem.setBlack(FriendShipStatusEnum.BLACK_STATUS_BLACKED.getCode());
fromItem.setCreateTime(System.currentTimeMillis());
@@ -366,21 +543,47 @@ public class ImFriendServiceImpl implements ImFriendService {
if(insert != 1){
return ResponseVO.errorResponse(FriendShipErrorCode.ADD_FRIEND_ERROR);
}
writeUserSeq.writeUserSeq(req.getAppId(),req.getFromId(),Constants.SeqConstants.Friendship,seq);
} else{
//如果存在则判断状态,如果是拉黑,则提示已拉黑,如果是未拉黑,则修改状态
if(fromItem.getBlack() != null && fromItem.getBlack() == FriendShipStatusEnum.BLACK_STATUS_BLACKED.getCode()){
return ResponseVO.errorResponse(FriendShipErrorCode.FRIEND_IS_BLACK);
} else {
}
else {
seq = redisSeq.doGetSeq(req.getAppId() + ":" + Constants.SeqConstants.Friendship);
ImFriendShipEntity update = new ImFriendShipEntity();
update.setFriendSequence(seq);
update.setBlack(FriendShipStatusEnum.BLACK_STATUS_BLACKED.getCode());
int result = imFriendShipMapper.update(update, query);
if(result != 1){
return ResponseVO.errorResponse(FriendShipErrorCode.ADD_BLACK_ERROR);
}
writeUserSeq.writeUserSeq(req.getAppId(),req.getFromId(),Constants.SeqConstants.Friendship,seq);
}
}
AddFriendBlackPack addFriendBlackPack = new AddFriendBlackPack();
addFriendBlackPack.setFromId(req.getFromId());
addFriendBlackPack.setSequence(seq);
addFriendBlackPack.setToId(req.getToId());
//发送tcp通知
messageProducer.sendToUser(req.getFromId(), req.getClientType(), req.getImei(),
FriendshipEventCommand.FRIEND_BLACK_ADD, addFriendBlackPack, req.getAppId());
//之后回调
if (appConfig.isAddFriendShipBlackAfterCallback()){
AddFriendBlackAfterCallbackDto callbackDto = new AddFriendBlackAfterCallbackDto();
callbackDto.setFromId(req.getFromId());
callbackDto.setToId(req.getToId());
callbackService.beforeCallback(req.getAppId(),
Constants.CallbackCommand.AddBlackAfter, JSONObject
.toJSONString(callbackDto));
}
return ResponseVO.successResponse();
}
@@ -395,13 +598,32 @@ public class ImFriendServiceImpl implements ImFriendService {
throw new ApplicationException(FriendShipErrorCode.FRIEND_IS_NOT_YOUR_BLACK);
}
long seq = redisSeq.doGetSeq(req.getAppId() + ":" + Constants.SeqConstants.Friendship);
ImFriendShipEntity update = new ImFriendShipEntity();
update.setFriendSequence(seq);
update.setBlack(FriendShipStatusEnum.BLACK_STATUS_NORMAL.getCode());
int update1 = imFriendShipMapper.update(update, queryFrom);
if(update1 == 1){
return ResponseVO.successResponse();
writeUserSeq.writeUserSeq(req.getAppId(),req.getFromId(),Constants.SeqConstants.Friendship,seq);
DeleteBlackPack deleteFriendPack = new DeleteBlackPack();
deleteFriendPack.setFromId(req.getFromId());
deleteFriendPack.setSequence(seq);
deleteFriendPack.setToId(req.getToId());
messageProducer.sendToUser(req.getFromId(), req.getClientType(), req.getImei(), FriendshipEventCommand.FRIEND_BLACK_DELETE,
deleteFriendPack, req.getAppId());
//之后回调
if (appConfig.isAddFriendShipBlackAfterCallback()){
AddFriendBlackAfterCallbackDto callbackDto = new AddFriendBlackAfterCallbackDto();
callbackDto.setFromId(req.getFromId());
callbackDto.setToId(req.getToId());
callbackService.beforeCallback(req.getAppId(),
Constants.CallbackCommand.DeleteBlack, JSONObject
.toJSONString(callbackDto));
}
}
return ResponseVO.errorResponse();
return ResponseVO.successResponse();
}
@Override
@@ -436,4 +658,7 @@ public class ImFriendServiceImpl implements ImFriendService {
return ResponseVO.successResponse(resp);
}
}

View File

@@ -1,7 +1,12 @@
package com.lld.im.service.friendship.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lld.im.codec.pack.friendship.AddFriendGroupMemberPack;
import com.lld.im.codec.pack.friendship.DeleteFriendGroupMemberPack;
import com.lld.im.codec.proto.Message;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.enums.command.FriendshipEventCommand;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.service.friendship.dao.ImFriendShipGroupEntity;
import com.lld.im.service.friendship.dao.ImFriendShipGroupMemberEntity;
import com.lld.im.service.friendship.dao.mapper.ImFriendShipGroupMemberMapper;
@@ -11,6 +16,7 @@ import com.lld.im.service.friendship.service.ImFriendShipGroupMemberService;
import com.lld.im.service.friendship.service.ImFriendShipGroupService;
import com.lld.im.service.user.dao.ImUserDataEntity;
import com.lld.im.service.user.service.ImUserService;
import com.lld.im.service.utils.MessageProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -38,6 +44,9 @@ public class ImFriendShipGroupMemberServiceImpl
@Autowired
ImFriendShipGroupMemberService thisService;
@Autowired
MessageProducer messageProducer;
@Override
@Transactional
public ResponseVO addGroupMember(AddFriendShipGroupMemberReq req) {
@@ -59,6 +68,15 @@ public class ImFriendShipGroupMemberServiceImpl
}
}
Long seq = imFriendShipGroupService.updateSeq(req.getFromId(), req.getGroupName(), req.getAppId());
AddFriendGroupMemberPack pack = new AddFriendGroupMemberPack();
pack.setFromId(req.getFromId());
pack.setGroupName(req.getGroupName());
pack.setToIds(successId);
pack.setSequence(seq);
messageProducer.sendToUserExceptClient(req.getFromId(), FriendshipEventCommand.FRIEND_GROUP_MEMBER_ADD,
pack,new ClientInfo(req.getAppId(),req.getClientType(),req.getImei()));
return ResponseVO.successResponse(successId);
}
@@ -70,17 +88,26 @@ public class ImFriendShipGroupMemberServiceImpl
return group;
}
ArrayList list = new ArrayList();
List<String> successId = new ArrayList<>();
for (String toId : req.getToIds()) {
ResponseVO<ImUserDataEntity> singleUserInfo = imUserService.getSingleUserInfo(toId, req.getAppId());
if(singleUserInfo.isOk()){
int i = deleteGroupMember(group.getData().getGroupId(), toId);
if(i == 1){
list.add(toId);
successId.add(toId);
}
}
}
return ResponseVO.successResponse(list);
Long seq = imFriendShipGroupService.updateSeq(req.getFromId(), req.getGroupName(), req.getAppId());
DeleteFriendGroupMemberPack pack = new DeleteFriendGroupMemberPack();
pack.setFromId(req.getFromId());
pack.setGroupName(req.getGroupName());
pack.setToIds(successId);
pack.setSequence(seq);
messageProducer.sendToUserExceptClient(req.getFromId(), FriendshipEventCommand.FRIEND_GROUP_MEMBER_DELETE,
pack,new ClientInfo(req.getAppId(),req.getClientType(),req.getImei()));
return ResponseVO.successResponse(successId);
}
@Override
@@ -88,6 +115,7 @@ public class ImFriendShipGroupMemberServiceImpl
ImFriendShipGroupMemberEntity imFriendShipGroupMemberEntity = new ImFriendShipGroupMemberEntity();
imFriendShipGroupMemberEntity.setGroupId(groupId);
imFriendShipGroupMemberEntity.setToId(toId);
try {
int insert = imFriendShipGroupMemberMapper.insert(imFriendShipGroupMemberEntity);
return insert;

View File

@@ -2,9 +2,14 @@ package com.lld.im.service.friendship.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lld.im.codec.pack.friendship.AddFriendGroupPack;
import com.lld.im.codec.pack.friendship.DeleteFriendGroupPack;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.DelFlagEnum;
import com.lld.im.common.enums.FriendShipErrorCode;
import com.lld.im.common.enums.command.FriendshipEventCommand;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.service.friendship.dao.ImFriendShipGroupEntity;
import com.lld.im.service.friendship.dao.mapper.ImFriendShipGroupMapper;
import com.lld.im.service.friendship.model.req.AddFriendShipGroupMemberReq;
@@ -12,7 +17,10 @@ import com.lld.im.service.friendship.model.req.AddFriendShipGroupReq;
import com.lld.im.service.friendship.model.req.DeleteFriendShipGroupReq;
import com.lld.im.service.friendship.service.ImFriendShipGroupMemberService;
import com.lld.im.service.friendship.service.ImFriendShipGroupService;
import com.lld.im.service.seq.RedisSeq;
import com.lld.im.service.user.service.ImUserService;
import com.lld.im.service.utils.MessageProducer;
import com.lld.im.service.utils.WriteUserSeq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
@@ -30,6 +38,15 @@ public class ImFriendShipGroupServiceImpl implements ImFriendShipGroupService {
@Autowired
ImUserService imUserService;
@Autowired
RedisSeq redisSeq;
@Autowired
MessageProducer messageProducer;
@Autowired
WriteUserSeq writeUserSeq;
@Override
@Transactional
public ResponseVO addGroup(AddFriendShipGroupReq req) {
@@ -52,6 +69,8 @@ public class ImFriendShipGroupServiceImpl implements ImFriendShipGroupService {
insert.setCreateTime(System.currentTimeMillis());
insert.setDelFlag(DelFlagEnum.NORMAL.getCode());
insert.setGroupName(req.getGroupName());
long seq = redisSeq.doGetSeq(req.getAppId() + ":" + Constants.SeqConstants.FriendshipGroup);
insert.setSequence(seq);
insert.setFromId(req.getFromId());
try {
int insert1 = imFriendShipGroupMapper.insert(insert);
@@ -59,7 +78,6 @@ public class ImFriendShipGroupServiceImpl implements ImFriendShipGroupService {
if (insert1 != 1) {
return ResponseVO.errorResponse(FriendShipErrorCode.FRIEND_SHIP_GROUP_CREATE_ERROR);
}
if (insert1 == 1 && CollectionUtil.isNotEmpty(req.getToIds())) {
AddFriendShipGroupMemberReq addFriendShipGroupMemberReq = new AddFriendShipGroupMemberReq();
addFriendShipGroupMemberReq.setFromId(req.getFromId());
@@ -73,6 +91,16 @@ public class ImFriendShipGroupServiceImpl implements ImFriendShipGroupService {
e.getStackTrace();
return ResponseVO.errorResponse(FriendShipErrorCode.FRIEND_SHIP_GROUP_IS_EXIST);
}
AddFriendGroupPack addFriendGropPack = new AddFriendGroupPack();
addFriendGropPack.setFromId(req.getFromId());
addFriendGropPack.setGroupName(req.getGroupName());
addFriendGropPack.setSequence(seq);
messageProducer.sendToUserExceptClient(req.getFromId(), FriendshipEventCommand.FRIEND_GROUP_ADD,
addFriendGropPack,new ClientInfo(req.getAppId(),req.getClientType(),req.getImei()));
//写入seq
writeUserSeq.writeUserSeq(req.getAppId(), req.getFromId(), Constants.SeqConstants.FriendshipGroup, seq);
return ResponseVO.successResponse();
}
@@ -90,12 +118,22 @@ public class ImFriendShipGroupServiceImpl implements ImFriendShipGroupService {
ImFriendShipGroupEntity entity = imFriendShipGroupMapper.selectOne(query);
if (entity != null) {
long seq = redisSeq.doGetSeq(req.getAppId() + ":" + Constants.SeqConstants.FriendshipGroup);
ImFriendShipGroupEntity update = new ImFriendShipGroupEntity();
update.setSequence(seq);
update.setGroupId(entity.getGroupId());
update.setDelFlag(DelFlagEnum.DELETE.getCode());
imFriendShipGroupMapper.updateById(update);
imFriendShipGroupMemberService.clearGroupMember(entity.getGroupId());
DeleteFriendGroupPack deleteFriendGroupPack = new DeleteFriendGroupPack();
deleteFriendGroupPack.setFromId(req.getFromId());
deleteFriendGroupPack.setGroupName(groupName);
deleteFriendGroupPack.setSequence(seq);
//TCP通知
messageProducer.sendToUserExceptClient(req.getFromId(), FriendshipEventCommand.FRIEND_GROUP_DELETE,
deleteFriendGroupPack,new ClientInfo(req.getAppId(),req.getClientType(),req.getImei()));
//写入seq
writeUserSeq.writeUserSeq(req.getAppId(), req.getFromId(), Constants.SeqConstants.FriendshipGroup, seq);
}
}
return ResponseVO.successResponse();
@@ -116,4 +154,22 @@ public class ImFriendShipGroupServiceImpl implements ImFriendShipGroupService {
return ResponseVO.successResponse(entity);
}
@Override
public Long updateSeq(String fromId, String groupName, Integer appId) {
QueryWrapper<ImFriendShipGroupEntity> query = new QueryWrapper<>();
query.eq("group_name", groupName);
query.eq("app_id", appId);
query.eq("from_id", fromId);
ImFriendShipGroupEntity entity = imFriendShipGroupMapper.selectOne(query);
long seq = redisSeq.doGetSeq(appId + ":" + Constants.SeqConstants.FriendshipGroup);
ImFriendShipGroupEntity group = new ImFriendShipGroupEntity();
group.setGroupId(entity.getGroupId());
group.setSequence(seq);
imFriendShipGroupMapper.updateById(group);
return seq;
}
}

View File

@@ -1,9 +1,14 @@
package com.lld.im.service.friendship.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lld.im.codec.pack.friendship.ApproverFriendRequestPack;
import com.lld.im.codec.pack.friendship.ReadAllFriendRequestPack;
import com.lld.im.codec.proto.Message;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.ApproverFriendRequestStatusEnum;
import com.lld.im.common.enums.FriendShipErrorCode;
import com.lld.im.common.enums.command.FriendshipEventCommand;
import com.lld.im.common.exception.ApplicationException;
import com.lld.im.service.friendship.dao.ImFriendShipRequestEntity;
import com.lld.im.service.friendship.dao.mapper.ImFriendShipRequestMapper;
@@ -12,6 +17,9 @@ import com.lld.im.service.friendship.model.req.FriendDto;
import com.lld.im.service.friendship.model.req.ReadFriendShipRequestReq;
import com.lld.im.service.friendship.service.ImFriendService;
import com.lld.im.service.friendship.service.ImFriendShipRequestService;
import com.lld.im.service.seq.RedisSeq;
import com.lld.im.service.utils.MessageProducer;
import com.lld.im.service.utils.WriteUserSeq;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -29,6 +37,15 @@ public class ImFriendShipRequestServiceImpl implements ImFriendShipRequestServic
@Autowired
ImFriendService imFriendShipService;
@Autowired
MessageProducer messageProducer;
@Autowired
RedisSeq redisSeq;
@Autowired
WriteUserSeq writeUserSeq;
@Override
public ResponseVO getFriendRequest(String fromId, Integer appId) {
@@ -52,10 +69,14 @@ public class ImFriendShipRequestServiceImpl implements ImFriendShipRequestServic
queryWrapper.eq("to_id",dto.getToId());
ImFriendShipRequestEntity request = imFriendShipRequestMapper.selectOne(queryWrapper);
long seq = redisSeq.doGetSeq(appId+":"+
Constants.SeqConstants.FriendshipRequest);
if(request == null){
request = new ImFriendShipRequestEntity();
request.setAddSource(dto.getAddSource());
request.setAddWording(dto.getAddWording());
request.setSequence(seq);
request.setAppId(appId);
request.setFromId(fromId);
request.setToId(dto.getToId());
@@ -76,11 +97,19 @@ public class ImFriendShipRequestServiceImpl implements ImFriendShipRequestServic
if(StringUtils.isNotBlank(dto.getAddWording())){
request.setAddWording(dto.getAddWording());
}
request.setSequence(seq);
request.setApproveStatus(0);
request.setReadStatus(0);
imFriendShipRequestMapper.updateById(request);
}
writeUserSeq.writeUserSeq(appId,dto.getToId(),
Constants.SeqConstants.FriendshipRequest,seq);
//发送好友申请的tcp给接收方
messageProducer.sendToUser(dto.getToId(),
null, "", FriendshipEventCommand.FRIEND_REQUEST,
request, appId);
return ResponseVO.successResponse();
}
@@ -98,12 +127,19 @@ public class ImFriendShipRequestServiceImpl implements ImFriendShipRequestServic
throw new ApplicationException(FriendShipErrorCode.NOT_APPROVER_OTHER_MAN_REQUEST);
}
long seq = redisSeq.doGetSeq(req.getAppId()+":"+
Constants.SeqConstants.FriendshipRequest);
ImFriendShipRequestEntity update = new ImFriendShipRequestEntity();
update.setApproveStatus(req.getStatus());
update.setUpdateTime(System.currentTimeMillis());
update.setSequence(seq);
update.setId(req.getId());
imFriendShipRequestMapper.updateById(update);
writeUserSeq.writeUserSeq(req.getAppId(),req.getOperater(),
Constants.SeqConstants.FriendshipRequest,seq);
if(ApproverFriendRequestStatusEnum.AGREE.getCode() == req.getStatus()){
//同意 ===> 去执行添加好友逻辑
FriendDto dto = new FriendDto();
@@ -121,6 +157,12 @@ public class ImFriendShipRequestServiceImpl implements ImFriendShipRequestServic
}
}
ApproverFriendRequestPack approverFriendRequestPack = new ApproverFriendRequestPack();
approverFriendRequestPack.setId(req.getId());
approverFriendRequestPack.setSequence(seq);
approverFriendRequestPack.setStatus(req.getStatus());
messageProducer.sendToUser(imFriendShipRequestEntity.getToId(),req.getClientType(),req.getImei(), FriendshipEventCommand
.FRIEND_REQUEST_APPROVER,approverFriendRequestPack,req.getAppId());
return ResponseVO.successResponse();
}
@@ -130,9 +172,20 @@ public class ImFriendShipRequestServiceImpl implements ImFriendShipRequestServic
query.eq("app_id", req.getAppId());
query.eq("to_id", req.getFromId());
long seq = redisSeq.doGetSeq(req.getAppId()+":"+
Constants.SeqConstants.FriendshipRequest);
ImFriendShipRequestEntity update = new ImFriendShipRequestEntity();
update.setReadStatus(1);
update.setSequence(seq);
imFriendShipRequestMapper.update(update, query);
writeUserSeq.writeUserSeq(req.getAppId(),req.getOperater(),
Constants.SeqConstants.FriendshipRequest,seq);
//TCP通知
ReadAllFriendRequestPack readAllFriendRequestPack = new ReadAllFriendRequestPack();
readAllFriendRequestPack.setFromId(req.getFromId());
readAllFriendRequestPack.setSequence(seq);
messageProducer.sendToUser(req.getFromId(),req.getClientType(),req.getImei(),FriendshipEventCommand
.FRIEND_REQUEST_READ,readAllFriendRequestPack,req.getAppId());
return ResponseVO.successResponse();
}

View File

@@ -1,7 +1,9 @@
package com.lld.im.service.group.controller;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.model.SyncReq;
import com.lld.im.service.group.model.req.*;
import com.lld.im.service.group.service.GroupMessageService;
import com.lld.im.service.group.service.ImGroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
@@ -21,6 +23,9 @@ public class ImGroupController {
@Autowired
ImGroupService groupService;
@Autowired
GroupMessageService groupMessageService;
@RequestMapping("/importGroup")
public ResponseVO importGroup(@RequestBody @Validated ImportGroupReq req, Integer appId, String identifier) {
req.setAppId(appId);
@@ -77,4 +82,19 @@ public class ImGroupController {
return groupService.muteGroup(req);
}
@RequestMapping("/sendMessage")
public ResponseVO sendMessage(@RequestBody @Validated SendGroupMessageReq
req, Integer appId,
String identifier) {
req.setAppId(appId);
req.setOperater(identifier);
return ResponseVO.successResponse(groupMessageService.send(req));
}
@RequestMapping("/syncJoinedGroup")
public ResponseVO syncJoinedGroup(@RequestBody @Validated SyncReq req, Integer appId, String identifier) {
req.setAppId(appId);
return groupService.syncJoinedGroupList(req);
}
}

View File

@@ -10,4 +10,17 @@ import java.util.Collection;
@Repository
public interface ImGroupMapper extends BaseMapper<ImGroupEntity> {
/**
* @description 获取加入的群的最大seq
* @author chackylee
* @param []
* @return java.lang.Long
*/
@Select(" <script> " +
" select max(sequence) from im_group where app_id = #{appId} and group_id in " +
"<foreach collection='groupId' index='index' item='id' separator=',' close=')' open='('>" +
" #{id} " +
"</foreach>" +
" </script> ")
Long getGroupMaxSeq(Collection<String> groupId, Integer appId);
}

View File

@@ -16,6 +16,8 @@ public interface ImGroupMemberMapper extends BaseMapper<ImGroupMemberEntity> {
@Select("select group_id from im_group_member where app_id = #{appId} AND member_id = #{memberId} ")
public List<String> getJoinedGroupId(Integer appId, String memberId);
@Select("select group_id from im_group_member where app_id = #{appId} AND member_id = #{memberId} and role != #{role}" )
public List<String> syncJoinedGroupId(Integer appId, String memberId, int role);
@Results({

View File

@@ -0,0 +1,19 @@
package com.lld.im.service.group.model.callback;
import com.lld.im.service.group.model.resp.AddMemberResp;
import lombok.Data;
import java.util.List;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class AddMemberAfterCallback {
private String groupId;
private Integer groupType;
private String operater;
private List<AddMemberResp> memberId;
}

View File

@@ -0,0 +1,14 @@
package com.lld.im.service.group.model.callback;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class DestroyGroupCallbackDto {
private String groupId;
}

View File

@@ -0,0 +1,86 @@
package com.lld.im.service.group.mq;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.command.GroupEventCommand;
import com.lld.im.common.enums.command.MessageCommand;
import com.lld.im.common.model.message.GroupChatMessageContent;
import com.lld.im.common.model.message.MessageReadedContent;
import com.lld.im.service.group.service.GroupMessageService;
import com.lld.im.service.message.service.MessageSyncService;
import com.lld.im.service.message.service.P2PMessageService;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Component
public class GroupChatOperateReceiver {
private static Logger logger = LoggerFactory.getLogger(GroupChatOperateReceiver.class);
// @Autowired
// P2PMessageService p2PMessageService;
@Autowired
GroupMessageService groupMessageService;
@Autowired
MessageSyncService messageSyncService;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = Constants.RabbitConstants.Im2GroupService,durable = "true"),
exchange = @Exchange(value = Constants.RabbitConstants.Im2GroupService,durable = "true")
),concurrency = "1"
)
public void onChatMessage(@Payload Message message,
@Headers Map<String,Object> headers,
Channel channel) throws Exception {
String msg = new String(message.getBody(),"utf-8");
logger.info("CHAT MSG FORM QUEUE ::: {}", msg);
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
try {
JSONObject jsonObject = JSON.parseObject(msg);
Integer command = jsonObject.getInteger("command");
if(command.equals(GroupEventCommand.MSG_GROUP.getCommand())){
//处理消息
GroupChatMessageContent messageContent
= jsonObject.toJavaObject(GroupChatMessageContent.class);
// p2PMessageService.process(messageContent);
groupMessageService.process(messageContent);
}else if (command.equals(GroupEventCommand.MSG_GROUP_READED.getCommand())) {
MessageReadedContent messageReaded = JSON.parseObject(msg, new TypeReference<MessageReadedContent>() {
}.getType());
messageSyncService.groupReadMark(messageReaded);
}
channel.basicAck(deliveryTag, false);
}catch (Exception e){
logger.error("处理消息出现异常:{}", e.getMessage());
logger.error("RMQ_CHAT_TRAN_ERROR", e);
logger.error("NACK_MSG:{}", msg);
//第一个false 表示不批量拒绝第二个false表示不重回队列
channel.basicNack(deliveryTag, false, false);
}
}
}

View File

@@ -0,0 +1,164 @@
package com.lld.im.service.group.service;
import com.lld.im.codec.pack.message.ChatMessageAck;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.command.GroupEventCommand;
import com.lld.im.common.enums.command.MessageCommand;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.common.model.message.GroupChatMessageContent;
import com.lld.im.common.model.message.MessageContent;
import com.lld.im.common.model.message.OfflineMessageContent;
import com.lld.im.service.group.model.req.SendGroupMessageReq;
import com.lld.im.service.message.model.resp.SendMessageResp;
import com.lld.im.service.message.service.CheckSendMessageService;
import com.lld.im.service.message.service.MessageStoreService;
import com.lld.im.service.seq.RedisSeq;
import com.lld.im.service.utils.MessageProducer;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Service
public class GroupMessageService {
@Autowired
CheckSendMessageService checkSendMessageService;
@Autowired
MessageProducer messageProducer;
@Autowired
ImGroupMemberService imGroupMemberService;
@Autowired
MessageStoreService messageStoreService;
@Autowired
RedisSeq redisSeq;
private final ThreadPoolExecutor threadPoolExecutor;
{
AtomicInteger num = new AtomicInteger(0);
threadPoolExecutor = new ThreadPoolExecutor(8, 8, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("message-group-thread-" + num.getAndIncrement());
return thread;
}
});
}
public void process(GroupChatMessageContent messageContent){
String fromId = messageContent.getFromId();
String groupId = messageContent.getGroupId();
Integer appId = messageContent.getAppId();
//前置校验
//这个用户是否被禁言 是否被禁用
//发送方和接收方是否是好友
GroupChatMessageContent messageFromMessageIdCache = messageStoreService.getMessageFromMessageIdCache(messageContent.getAppId(),
messageContent.getMessageId(), GroupChatMessageContent.class);
if(messageFromMessageIdCache != null){
threadPoolExecutor.execute(() ->{
//1.回ack成功给自己
ack(messageContent,ResponseVO.successResponse());
//2.发消息给同步在线端
syncToSender(messageContent,messageContent);
//3.发消息给对方在线端
dispatchMessage(messageContent);
});
}
long seq = redisSeq.doGetSeq(messageContent.getAppId() + ":" + Constants.SeqConstants.GroupMessage
+ messageContent.getGroupId());
messageContent.setMessageSequence(seq);
threadPoolExecutor.execute(() ->{
messageStoreService.storeGroupMessage(messageContent);
List<String> groupMemberId = imGroupMemberService.getGroupMemberId(messageContent.getGroupId(),
messageContent.getAppId());
messageContent.setMemberId(groupMemberId);
OfflineMessageContent offlineMessageContent = new OfflineMessageContent();
BeanUtils.copyProperties(messageContent,offlineMessageContent);
offlineMessageContent.setToId(messageContent.getGroupId());
messageStoreService.storeGroupOfflineMessage(offlineMessageContent,groupMemberId);
//1.回ack成功给自己
ack(messageContent,ResponseVO.successResponse());
//2.发消息给同步在线端
syncToSender(messageContent,messageContent);
//3.发消息给对方在线端
dispatchMessage(messageContent);
messageStoreService.setMessageFromMessageIdCache(messageContent.getAppId(),
messageContent.getMessageId(),messageContent);
});
}
private void dispatchMessage(GroupChatMessageContent messageContent){
for (String memberId : messageContent.getMemberId()) {
if(!memberId.equals(messageContent.getFromId())){
messageProducer.sendToUser(memberId,
GroupEventCommand.MSG_GROUP,
messageContent,messageContent.getAppId());
}
}
}
private void ack(MessageContent messageContent,ResponseVO responseVO){
ChatMessageAck chatMessageAck = new ChatMessageAck(messageContent.getMessageId());
responseVO.setData(chatMessageAck);
//發消息
messageProducer.sendToUser(messageContent.getFromId(),
GroupEventCommand.GROUP_MSG_ACK,
responseVO,messageContent
);
}
private void syncToSender(GroupChatMessageContent messageContent, ClientInfo clientInfo){
messageProducer.sendToUserExceptClient(messageContent.getFromId(),
GroupEventCommand.MSG_GROUP,messageContent,messageContent);
}
private ResponseVO imServerPermissionCheck(String fromId, String toId,Integer appId){
ResponseVO responseVO = checkSendMessageService
.checkGroupMessage(fromId, toId,appId);
return responseVO;
}
public SendMessageResp send(SendGroupMessageReq req) {
SendMessageResp sendMessageResp = new SendMessageResp();
GroupChatMessageContent message = new GroupChatMessageContent();
BeanUtils.copyProperties(req,message);
messageStoreService.storeGroupMessage(message);
sendMessageResp.setMessageKey(message.getMessageKey());
sendMessageResp.setMessageTime(System.currentTimeMillis());
//2.发消息给同步在线端
syncToSender(message,message);
//3.发消息给对方在线端
dispatchMessage(message);
return sendMessageResp;
}
}

View File

@@ -40,4 +40,5 @@ public interface ImGroupMemberService {
public ResponseVO speak(SpeaMemberReq req);
ResponseVO<Collection<String>> syncMemberJoinedGroup(String operater, Integer appId);
}

View File

@@ -1,6 +1,7 @@
package com.lld.im.service.group.service;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.model.SyncReq;
import com.lld.im.service.group.dao.ImGroupEntity;
import com.lld.im.service.group.model.req.*;
@@ -29,4 +30,7 @@ public interface ImGroupService {
public ResponseVO muteGroup(MuteGroupReq req);
ResponseVO syncJoinedGroupList(SyncReq req);
Long getUserGroupMaxSeq(String userId, Integer appId);
}

View File

@@ -1,18 +1,30 @@
package com.lld.im.service.group.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lld.im.codec.pack.group.AddGroupMemberPack;
import com.lld.im.codec.pack.group.GroupMemberSpeakPack;
import com.lld.im.codec.pack.group.RemoveGroupMemberPack;
import com.lld.im.codec.pack.group.UpdateGroupMemberPack;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.GroupErrorCode;
import com.lld.im.common.enums.GroupMemberRoleEnum;
import com.lld.im.common.enums.GroupStatusEnum;
import com.lld.im.common.enums.GroupTypeEnum;
import com.lld.im.common.enums.command.GroupEventCommand;
import com.lld.im.common.exception.ApplicationException;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.service.group.dao.ImGroupEntity;
import com.lld.im.service.group.dao.ImGroupMemberEntity;
import com.lld.im.service.group.dao.mapper.ImGroupMemberMapper;
import com.lld.im.service.group.model.callback.AddMemberAfterCallback;
import com.lld.im.service.group.model.callback.DestroyGroupCallbackDto;
import com.lld.im.service.group.model.req.*;
import com.lld.im.service.group.model.resp.AddMemberResp;
import com.lld.im.service.group.model.resp.GetRoleInGroupResp;
@@ -20,6 +32,8 @@ import com.lld.im.service.group.service.ImGroupMemberService;
import com.lld.im.service.group.service.ImGroupService;
import com.lld.im.service.user.dao.ImUserDataEntity;
import com.lld.im.service.user.service.ImUserService;
import com.lld.im.service.utils.CallbackService;
import com.lld.im.service.utils.GroupMessageProducer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
@@ -47,9 +61,18 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
@Autowired
ImGroupMemberService groupMemberService;
@Autowired
AppConfig appConfig;
@Autowired
CallbackService callbackService;
@Autowired
ImUserService imUserService;
@Autowired
GroupMessageProducer groupMessageProducer;
@Override
public ResponseVO importGroupMember(ImportGroupMemberReq req) {
@@ -95,7 +118,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
public ResponseVO addGroupMember(String groupId, Integer appId, GroupMemberDto dto) {
ResponseVO<ImUserDataEntity> singleUserInfo = imUserService.getSingleUserInfo(dto.getMemberId(), appId);
if (!singleUserInfo.isOk()) {
if(!singleUserInfo.isOk()){
return singleUserInfo;
}
@@ -155,7 +178,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
public ResponseVO removeGroupMember(String groupId, Integer appId, String memberId) {
ResponseVO<ImUserDataEntity> singleUserInfo = imUserService.getSingleUserInfo(memberId, appId);
if (!singleUserInfo.isOk()) {
if(!singleUserInfo.isOk()){
return singleUserInfo;
}
@@ -242,14 +265,33 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
}
List<GroupMemberDto> memberDtos = req.getMembers();
if(appConfig.isAddGroupMemberBeforeCallback()){
ResponseVO responseVO = callbackService.beforeCallback(req.getAppId(), Constants.CallbackCommand.GroupMemberAddBefore
, JSONObject.toJSONString(req));
if(!responseVO.isOk()){
return responseVO;
}
try {
memberDtos
= JSONArray.parseArray(JSONObject.toJSONString(responseVO.getData()), GroupMemberDto.class);
}catch (Exception e){
e.printStackTrace();
log.error("GroupMemberAddBefore 回调失败:{}",req.getAppId());
}
}
ImGroupEntity group = groupResp.getData();
/**
* 私有群private 类似普通微信群,创建后仅支持已在群内的好友邀请加群,且无需被邀请方同意或群主审批
* 公开群Public 类似 QQ 群,创建后群主可以指定群管理员,需要群主或管理员审批通过才能入群
* 群类型 1私有群类似微信 2公开群(类似qq
*
*/
if (!isAdmin && GroupTypeEnum.PUBLIC.getCode() == group.getGroupType()) {
throw new ApplicationException(GroupErrorCode.THIS_OPERATE_NEED_APPMANAGER_ROLE);
}
@@ -279,12 +321,30 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
resp.add(addMemberResp);
}
AddGroupMemberPack addGroupMemberPack = new AddGroupMemberPack();
addGroupMemberPack.setGroupId(req.getGroupId());
addGroupMemberPack.setMembers(successId);
groupMessageProducer.producer(req.getOperater(), GroupEventCommand.ADDED_MEMBER, addGroupMemberPack
, new ClientInfo(req.getAppId(), req.getClientType(), req.getImei()));
if(appConfig.isAddGroupMemberAfterCallback()){
AddMemberAfterCallback dto = new AddMemberAfterCallback();
dto.setGroupId(req.getGroupId());
dto.setGroupType(group.getGroupType());
dto.setMemberId(resp);
dto.setOperater(req.getOperater());
callbackService.callback(req.getAppId()
,Constants.CallbackCommand.GroupMemberAddAfter,
JSONObject.toJSONString(dto));
}
return ResponseVO.successResponse(resp);
}
@Override
public ResponseVO removeMember(RemoveGroupMemberReq req) {
List<AddMemberResp> resp = new ArrayList<>();
boolean isAdmin = false;
ResponseVO<ImGroupEntity> groupResp = groupService.getGroup(req.getGroupId(), req.getAppId());
if (!groupResp.isOk()) {
@@ -337,6 +397,20 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
}
}
ResponseVO responseVO = groupMemberService.removeGroupMember(req.getGroupId(), req.getAppId(), req.getMemberId());
if(responseVO.isOk()){
RemoveGroupMemberPack removeGroupMemberPack = new RemoveGroupMemberPack();
removeGroupMemberPack.setGroupId(req.getGroupId());
removeGroupMemberPack.setMember(req.getMemberId());
groupMessageProducer.producer(req.getMemberId(), GroupEventCommand.DELETED_MEMBER, removeGroupMemberPack
, new ClientInfo(req.getAppId(), req.getClientType(), req.getImei()));
if(appConfig.isDeleteGroupMemberAfterCallback()){
callbackService.callback(req.getAppId(),
Constants.CallbackCommand.GroupMemberDeleteAfter,
JSONObject.toJSONString(req));
}
}
return responseVO;
}
@@ -379,25 +453,24 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
if (StringUtils.isBlank(req.getAlias()) && !isMeOperate) {
return ResponseVO.errorResponse(GroupErrorCode.THIS_OPERATE_NEED_ONESELF);
}
//私有群不能设置管理员
if (groupData.getGroupType() == GroupTypeEnum.PRIVATE.getCode() &&
req.getRole() != null && (req.getRole() == GroupMemberRoleEnum.MAMAGER.getCode() ||
req.getRole() == GroupMemberRoleEnum.OWNER.getCode())) {
return ResponseVO.errorResponse(GroupErrorCode.THIS_OPERATE_NEED_MANAGER_ROLE);
}
//如果要修改权限相关的则走下面的逻辑
if (req.getRole() != null) {
//私有群不能设置管理员
if (groupData.getGroupType() == GroupTypeEnum.PRIVATE.getCode() &&
req.getRole() != null && (req.getRole() == GroupMemberRoleEnum.MAMAGER.getCode() ||
req.getRole() == GroupMemberRoleEnum.OWNER.getCode())) {
return ResponseVO.errorResponse(GroupErrorCode.THIS_OPERATE_NEED_APPMANAGER_ROLE);
}
if(req.getRole() != null){
//获取被操作人的是否在群内
ResponseVO<GetRoleInGroupResp> roleInGroupOne = this.getRoleInGroupOne(req.getGroupId(), req.getMemberId(), req.getAppId());
if (!roleInGroupOne.isOk()) {
if(!roleInGroupOne.isOk()){
return roleInGroupOne;
}
//获取操作人权限
ResponseVO<GetRoleInGroupResp> operateRoleInGroupOne = this.getRoleInGroupOne(req.getGroupId(), req.getOperater(), req.getAppId());
if (!operateRoleInGroupOne.isOk()) {
if(!operateRoleInGroupOne.isOk()){
return operateRoleInGroupOne;
}
@@ -407,12 +480,12 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
boolean isManager = roleInfo == GroupMemberRoleEnum.MAMAGER.getCode();
//不是管理员不能修改权限
if (req.getRole() != null && !isOwner && !isManager) {
if(req.getRole() != null && !isOwner && !isManager){
return ResponseVO.errorResponse(GroupErrorCode.THIS_OPERATE_NEED_MANAGER_ROLE);
}
//管理员只有群主能够设置
if (req.getRole() != null && req.getRole() == GroupMemberRoleEnum.MAMAGER.getCode() && !isOwner) {
if(req.getRole() != null && req.getRole() == GroupMemberRoleEnum.MAMAGER.getCode() && !isOwner){
return ResponseVO.errorResponse(GroupErrorCode.THIS_OPERATE_NEED_OWNER_ROLE);
}
@@ -436,6 +509,11 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
objectUpdateWrapper.eq("group_id", req.getGroupId());
imGroupMemberMapper.update(update, objectUpdateWrapper);
UpdateGroupMemberPack pack = new UpdateGroupMemberPack();
BeanUtils.copyProperties(req, pack);
groupMessageProducer.producer(req.getOperater(), GroupEventCommand.UPDATED_MEMBER, pack, new ClientInfo(req.getAppId(), req.getClientType(), req.getImei()));
return ResponseVO.successResponse();
}
@@ -513,7 +591,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
}
ImGroupMemberEntity imGroupMemberEntity = new ImGroupMemberEntity();
if (memberRole == null) {
if(memberRole == null){
//获取被操作的权限
ResponseVO<GetRoleInGroupResp> roleInGroupOne = this.getRoleInGroupOne(req.getGroupId(), req.getMemberId(), req.getAppId());
if (!roleInGroupOne.isOk()) {
@@ -523,15 +601,26 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
}
imGroupMemberEntity.setGroupMemberId(memberRole.getGroupMemberId());
if (req.getSpeakDate() > 0) {
if(req.getSpeakDate() > 0){
imGroupMemberEntity.setSpeakDate(System.currentTimeMillis() + req.getSpeakDate());
} else {
}else{
imGroupMemberEntity.setSpeakDate(req.getSpeakDate());
}
int i = imGroupMemberMapper.updateById(imGroupMemberEntity);
if(i == 1){
GroupMemberSpeakPack pack = new GroupMemberSpeakPack();
BeanUtils.copyProperties(req,pack);
groupMessageProducer.producer(req.getOperater(),GroupEventCommand.SPEAK_GOUP_MEMBER,pack,
new ClientInfo(req.getAppId(),req.getClientType(),req.getImei()));
}
return ResponseVO.successResponse();
}
@Override
public ResponseVO<Collection<String>> syncMemberJoinedGroup(String operater, Integer appId) {
return ResponseVO.successResponse(imGroupMemberMapper.syncJoinedGroupId(appId,operater,GroupMemberRoleEnum.LEAVE.getCode()));
}
}

View File

@@ -1,23 +1,37 @@
package com.lld.im.service.group.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.lld.im.codec.pack.group.CreateGroupPack;
import com.lld.im.codec.pack.group.DestroyGroupPack;
import com.lld.im.codec.pack.group.UpdateGroupInfoPack;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.GroupErrorCode;
import com.lld.im.common.enums.GroupMemberRoleEnum;
import com.lld.im.common.enums.GroupStatusEnum;
import com.lld.im.common.enums.GroupTypeEnum;
import com.lld.im.common.enums.command.GroupEventCommand;
import com.lld.im.common.exception.ApplicationException;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.common.model.SyncReq;
import com.lld.im.common.model.SyncResp;
import com.lld.im.service.conversation.dao.ImConversationSetEntity;
import com.lld.im.service.group.dao.ImGroupEntity;
import com.lld.im.service.group.dao.mapper.ImGroupMapper;
import com.lld.im.service.group.model.callback.DestroyGroupCallbackDto;
import com.lld.im.service.group.model.req.*;
import com.lld.im.service.group.model.resp.GetGroupResp;
import com.lld.im.service.group.model.resp.GetJoinedGroupResp;
import com.lld.im.service.group.model.resp.GetRoleInGroupResp;
import com.lld.im.service.group.service.ImGroupMemberService;
import com.lld.im.service.group.service.ImGroupService;
import com.lld.im.service.seq.RedisSeq;
import com.lld.im.service.utils.CallbackService;
import com.lld.im.service.utils.GroupMessageProducer;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -43,6 +57,17 @@ public class ImGroupServiceImpl implements ImGroupService {
@Autowired
ImGroupMemberService groupMemberService;
@Autowired
AppConfig appConfig;
@Autowired
CallbackService callbackService;
@Autowired
GroupMessageProducer groupMessageProducer;
@Autowired
RedisSeq redisSeq;
@Override
public ResponseVO importGroup(ImportGroupReq req) {
@@ -110,6 +135,8 @@ public class ImGroupServiceImpl implements ImGroupService {
}
ImGroupEntity imGroupEntity = new ImGroupEntity();
long seq = redisSeq.doGetSeq(req.getAppId() + ":" + Constants.SeqConstants.Group);
imGroupEntity.setSequence(seq);
imGroupEntity.setCreateTime(System.currentTimeMillis());
imGroupEntity.setStatus(GroupStatusEnum.NORMAL.getCode());
BeanUtils.copyProperties(req, imGroupEntity);
@@ -126,6 +153,15 @@ public class ImGroupServiceImpl implements ImGroupService {
groupMemberService.addGroupMember(req.getGroupId(), req.getAppId(), dto);
}
if(appConfig.isCreateGroupAfterCallback()){
callbackService.callback(req.getAppId(), Constants.CallbackCommand.CreateGroupAfter,
JSONObject.toJSONString(imGroupEntity));
}
CreateGroupPack createGroupPack = new CreateGroupPack();
BeanUtils.copyProperties(imGroupEntity, createGroupPack);
groupMessageProducer.producer(req.getOperater(), GroupEventCommand.CREATED_GROUP, createGroupPack
, new ClientInfo(req.getAppId(), req.getClientType(), req.getImei()));
return ResponseVO.successResponse();
}
@@ -176,13 +212,26 @@ public class ImGroupServiceImpl implements ImGroupService {
}
ImGroupEntity update = new ImGroupEntity();
long seq = redisSeq.doGetSeq(req.getAppId() + ":" + Constants.SeqConstants.Group);
BeanUtils.copyProperties(req, update);
update.setUpdateTime(System.currentTimeMillis());
update.setSequence(seq);
int row = imGroupDataMapper.update(update, query);
if (row != 1) {
throw new ApplicationException(GroupErrorCode.THIS_OPERATE_NEED_MANAGER_ROLE);
}
if(appConfig.isModifyGroupAfterCallback()){
callbackService.callback(req.getAppId(),Constants.CallbackCommand.UpdateGroupAfter,
JSONObject.toJSONString(imGroupDataMapper.selectOne(query)));
}
UpdateGroupInfoPack pack = new UpdateGroupInfoPack();
BeanUtils.copyProperties(req, pack);
groupMessageProducer.producer(req.getOperater(), GroupEventCommand.UPDATED_GROUP,
pack, new ClientInfo(req.getAppId(), req.getClientType(), req.getImei()));
return ResponseVO.successResponse();
}
@@ -264,12 +313,29 @@ public class ImGroupServiceImpl implements ImGroupService {
}
ImGroupEntity update = new ImGroupEntity();
long seq = redisSeq.doGetSeq(req.getAppId() + ":" + Constants.SeqConstants.Group);
update.setStatus(GroupStatusEnum.DESTROY.getCode());
update.setSequence(seq);
int update1 = imGroupDataMapper.update(update, objectQueryWrapper);
if (update1 != 1) {
throw new ApplicationException(GroupErrorCode.UPDATE_GROUP_BASE_INFO_ERROR);
}
if(appConfig.isModifyGroupAfterCallback()){
DestroyGroupCallbackDto dto = new DestroyGroupCallbackDto();
dto.setGroupId(req.getGroupId());
callbackService.callback(req.getAppId()
,Constants.CallbackCommand.DestoryGroupAfter,
JSONObject.toJSONString(dto));
}
DestroyGroupPack pack = new DestroyGroupPack();
pack.setSequence(seq);
pack.setGroupId(req.getGroupId());
groupMessageProducer.producer(req.getOperater(),
GroupEventCommand.DESTROY_GROUP, pack, new ClientInfo(req.getAppId(), req.getClientType(), req.getImei()));
return ResponseVO.successResponse();
}
@@ -390,4 +456,56 @@ public class ImGroupServiceImpl implements ImGroupService {
return ResponseVO.successResponse();
}
@Override
public ResponseVO syncJoinedGroupList(SyncReq req) {
if(req.getMaxLimit() > 100){
req.setMaxLimit(100);
}
SyncResp<ImGroupEntity> resp = new SyncResp<>();
ResponseVO<Collection<String>> memberJoinedGroup = groupMemberService.syncMemberJoinedGroup(req.getOperater(), req.getAppId());
if(memberJoinedGroup.isOk()){
Collection<String> data = memberJoinedGroup.getData();
QueryWrapper<ImGroupEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("app_id",req.getAppId());
queryWrapper.in("group_id",data);
queryWrapper.gt("sequence",req.getLastSequence());
queryWrapper.last(" limit " + req.getMaxLimit());
queryWrapper.orderByAsc("sequence");
List<ImGroupEntity> list = imGroupDataMapper.selectList(queryWrapper);
if(!CollectionUtils.isEmpty(list)){
ImGroupEntity maxSeqEntity
= list.get(list.size() - 1);
resp.setDataList(list);
//设置最大seq
Long maxSeq =
imGroupDataMapper.getGroupMaxSeq(data, req.getAppId());
resp.setMaxSequence(maxSeq);
//设置是否拉取完毕
resp.setCompleted(maxSeqEntity.getSequence() >= maxSeq);
return ResponseVO.successResponse(resp);
}
}
resp.setCompleted(true);
return ResponseVO.successResponse(resp);
}
@Override
public Long getUserGroupMaxSeq(String userId, Integer appId) {
ResponseVO<Collection<String>> memberJoinedGroup = groupMemberService.syncMemberJoinedGroup(userId, appId);
if(!memberJoinedGroup.isOk()){
throw new ApplicationException(500,"");
}
Long maxSeq =
imGroupDataMapper.getGroupMaxSeq(memberJoinedGroup.getData(),
appId);
return maxSeq;
}
}

View File

@@ -0,0 +1,99 @@
package com.lld.im.service.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.lld.im.common.BaseErrorCode;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.enums.GateWayErrorCode;
import com.lld.im.common.exception.ApplicationExceptionEnum;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Component
public class GateWayInterceptor implements HandlerInterceptor {
@Autowired
IdentityCheck identityCheck;
//appService -》im接口 -》 userSign
//appServicegen userSig
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// if (1 == 1){
// return true;
// }
//获取appId 操作人 userSign
String appIdStr = request.getParameter("appId");
if(StringUtils.isBlank(appIdStr)){
resp(ResponseVO.errorResponse(GateWayErrorCode
.APPID_NOT_EXIST),response);
return false;
}
String identifier = request.getParameter("identifier");
if(StringUtils.isBlank(identifier)){
resp(ResponseVO.errorResponse(GateWayErrorCode
.OPERATER_NOT_EXIST),response);
return false;
}
String userSign = request.getParameter("userSign");
if(StringUtils.isBlank(userSign)){
resp(ResponseVO.errorResponse(GateWayErrorCode
.USERSIGN_NOT_EXIST),response);
return false;
}
//签名和操作人和appid是否匹配
ApplicationExceptionEnum applicationExceptionEnum = identityCheck.checkUserSig(identifier, appIdStr, userSign);
if(applicationExceptionEnum != BaseErrorCode.SUCCESS){
resp(ResponseVO.errorResponse(applicationExceptionEnum),response);
return false;
}
return true;
}
private void resp(ResponseVO respVo ,HttpServletResponse response){
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=utf-8");
try {
String resp = JSONObject.toJSONString(respVo);
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Credentials","true");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Allow-Headers","*");
response.setHeader("Access-Control-Max-Age","3600");
writer = response.getWriter();
writer.write(resp);
} catch (Exception e){
e.printStackTrace();
} finally {
if(writer != null){
writer.checkError();
}
}
}
}

View File

@@ -0,0 +1,135 @@
package com.lld.im.service.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.lld.im.common.BaseErrorCode;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.GateWayErrorCode;
import com.lld.im.common.enums.ImUserTypeEnum;
import com.lld.im.common.exception.ApplicationExceptionEnum;
import com.lld.im.common.utils.SigAPI;
import com.lld.im.service.user.dao.ImUserDataEntity;
import com.lld.im.service.user.service.ImUserService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import sun.rmi.runtime.Log;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Component
public class IdentityCheck {
private static Logger logger = LoggerFactory.getLogger(IdentityCheck.class);
@Autowired
ImUserService imUserService;
//10000 123456 10001 123456789
@Autowired
AppConfig appConfig;
@Autowired
StringRedisTemplate stringRedisTemplate;
public ApplicationExceptionEnum checkUserSig(String identifier,
String appId,String userSig){
String cacheUserSig = stringRedisTemplate.opsForValue()
.get(appId + ":" + Constants.RedisConstants.userSign + ":"
+ identifier + userSig);
if(!StringUtils.isBlank(cacheUserSig) && Long.valueOf(cacheUserSig)
> System.currentTimeMillis() / 1000){
this.setIsAdmin(identifier,Integer.valueOf(appId));
return BaseErrorCode.SUCCESS;
}
//获取秘钥
String privateKey = appConfig.getPrivateKey();
//根据appid + 秘钥创建sigApi
SigAPI sigAPI = new SigAPI(Long.valueOf(appId), privateKey);
//调用sigApi对userSig解密
JSONObject jsonObject = sigAPI.decodeUserSig(userSig);
//取出解密后的appid 和 操作人 和 过期时间做匹配,不通过则提示错误
Long expireTime = 0L;
Long expireSec = 0L;
Long time = 0L;
String decoerAppId = "";
String decoderidentifier = "";
try {
decoerAppId = jsonObject.getString("TLS.appId");
decoderidentifier = jsonObject.getString("TLS.identifier");
String expireStr = jsonObject.get("TLS.expire").toString();
String expireTimeStr = jsonObject.get("TLS.expireTime").toString();
time = Long.valueOf(expireTimeStr);
expireSec = Long.valueOf(expireStr);
expireTime = Long.valueOf(expireTimeStr) + expireSec;
}catch (Exception e){
e.printStackTrace();
logger.error("checkUserSig-error:{}",e.getMessage());
}
if(!decoderidentifier.equals(identifier)){
return GateWayErrorCode.USERSIGN_OPERATE_NOT_MATE;
}
if(!decoerAppId.equals(appId)){
return GateWayErrorCode.USERSIGN_IS_ERROR;
}
if(expireSec == 0L){
return GateWayErrorCode.USERSIGN_IS_EXPIRED;
}
if(expireTime < System.currentTimeMillis() / 1000){
return GateWayErrorCode.USERSIGN_IS_EXPIRED;
}
//appid + "xxx" + userId + sign
String genSig = sigAPI.genUserSig(identifier, expireSec,time,null);
if (genSig.toLowerCase().equals(userSig.toLowerCase()))
{
String key = appId + ":" + Constants.RedisConstants.userSign + ":"
+identifier + userSig;
Long etime = expireTime - System.currentTimeMillis() / 1000;
stringRedisTemplate.opsForValue().set(
key,expireTime.toString(),etime, TimeUnit.SECONDS
);
this.setIsAdmin(identifier,Integer.valueOf(appId));
return BaseErrorCode.SUCCESS;
}
return GateWayErrorCode.USERSIGN_IS_ERROR;
}
/**
* 根据appid,identifier判断是否App管理员,并设置到RequestHolder
* @param identifier
* @param appId
* @return
*/
public void setIsAdmin(String identifier, Integer appId) {
//去DB或Redis中查找, 后面写
ResponseVO<ImUserDataEntity> singleUserInfo = imUserService.getSingleUserInfo(identifier, appId);
if(singleUserInfo.isOk()){
RequestHolder.set(singleUserInfo.getData().getUserType() == ImUserTypeEnum.APP_ADMIN.getCode());
}else{
RequestHolder.set(false);
}
}
}

View File

@@ -0,0 +1,22 @@
package com.lld.im.service.interceptor;
/**
* @author: Chackylee
* @description:
**/
public class RequestHolder {
private final static ThreadLocal<Boolean> requestHolder = new ThreadLocal<>();
public static void set(Boolean isadmin) {
requestHolder.set(isadmin);
}
public static Boolean get() {
return requestHolder.get();
}
public static void remove() {
requestHolder.remove();
}
}

View File

@@ -0,0 +1,49 @@
package com.lld.im.service.message.controller;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.model.SyncReq;
import com.lld.im.common.model.message.CheckSendMessageReq;
import com.lld.im.service.message.model.req.SendMessageReq;
import com.lld.im.service.message.service.MessageSyncService;
import com.lld.im.service.message.service.P2PMessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@RestController
@RequestMapping("v1/message")
public class MessageController {
@Autowired
P2PMessageService p2PMessageService;
@Autowired
MessageSyncService messageSyncService;
@RequestMapping("/send")
public ResponseVO send(@RequestBody @Validated SendMessageReq req, Integer appId) {
req.setAppId(appId);
return ResponseVO.successResponse(p2PMessageService.send(req));
}
@RequestMapping("/checkSend")
public ResponseVO checkSend(@RequestBody @Validated CheckSendMessageReq req) {
return p2PMessageService.imServerPermissionCheck(req.getFromId(),req.getToId()
,req.getAppId());
}
@RequestMapping("/syncOfflineMessage")
public ResponseVO syncOfflineMessage(@RequestBody
@Validated SyncReq req, Integer appId) {
req.setAppId(appId);
return messageSyncService.syncOfflineMessage(req);
}
}

View File

@@ -0,0 +1,32 @@
package com.lld.im.service.message.dao;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author: Chackylee
* @description:
**/
@Data
@TableName("im_message_body")
public class ImMessageBodyEntity {
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,33 @@
package com.lld.im.service.message.dao;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author: Chackylee
* @description:
**/
@Data
@TableName("im_message_history")
public class ImMessageHistoryEntity {
private Integer appId;
private String fromId;
private String toId;
private String ownerId;
/** messageBodyId*/
private Long messageKey;
/** 序列号*/
private Long sequence;
private String messageRandom;
private Long messageTime;
private Long createTime;
}

View File

@@ -0,0 +1,9 @@
package com.lld.im.service.message.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lld.im.service.message.dao.ImMessageBodyEntity;
import org.springframework.stereotype.Repository;
@Repository
public interface ImMessageBodyMapper extends BaseMapper<ImMessageBodyEntity> {
}

View File

@@ -0,0 +1,18 @@
package com.lld.im.service.message.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lld.im.service.message.dao.ImMessageHistoryEntity;
import org.springframework.stereotype.Repository;
import java.util.Collection;
@Repository
public interface ImMessageHistoryMapper extends BaseMapper<ImMessageHistoryEntity> {
/**
* 批量插入mysql
* @param entityList
* @return
*/
Integer insertBatchSomeColumn(Collection<ImMessageHistoryEntity> entityList);
}

View File

@@ -0,0 +1,34 @@
package com.lld.im.service.message.model.req;
import com.lld.im.common.model.RequestBase;
import lombok.Data;
/**
* @author: Chackylee
* @description:
**/
@Data
public class SendMessageReq extends RequestBase {
//客户端传的messageId
private String messageId;
private String fromId;
private String toId;
private int messageRandom;
private long messageTime;
private String messageBody;
/**
* 这个字段缺省或者为 0 表示需要计数,为 1 表示本条消息不需要计数,即右上角图标数字不增加
*/
private int badgeMode;
private Long messageLifeTime;
private Integer appId;
}

View File

@@ -0,0 +1,17 @@
package com.lld.im.service.message.model.resp;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class SendMessageResp {
private Long messageKey;
private Long messageTime;
}

View File

@@ -0,0 +1,97 @@
package com.lld.im.service.message.mq;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.command.MessageCommand;
import com.lld.im.common.model.message.MessageContent;
import com.lld.im.common.model.message.MessageReadedContent;
import com.lld.im.common.model.message.MessageReciveAckContent;
import com.lld.im.common.model.message.RecallMessageContent;
import com.lld.im.service.message.service.MessageSyncService;
import com.lld.im.service.message.service.P2PMessageService;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import sun.nio.cs.ext.MS874;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.Objects;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Component
public class ChatOperateReceiver {
private static Logger logger = LoggerFactory.getLogger(ChatOperateReceiver.class);
@Autowired
P2PMessageService p2PMessageService;
@Autowired
MessageSyncService messageSyncService;
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = Constants.RabbitConstants.Im2MessageService,durable = "true"),
exchange = @Exchange(value = Constants.RabbitConstants.Im2MessageService,durable = "true")
),concurrency = "1"
)
public void onChatMessage(@Payload Message message,
@Headers Map<String,Object> headers,
Channel channel) throws Exception {
String msg = new String(message.getBody(),"utf-8");
logger.info("CHAT MSG FORM QUEUE ::: {}", msg);
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
try {
JSONObject jsonObject = JSON.parseObject(msg);
Integer command = jsonObject.getInteger("command");
if(command.equals(MessageCommand.MSG_P2P.getCommand())){
//处理消息
MessageContent messageContent
= jsonObject.toJavaObject(MessageContent.class);
p2PMessageService.process(messageContent);
}else if(command.equals(MessageCommand.MSG_RECIVE_ACK.getCommand())){
//消息接收确认
MessageReciveAckContent messageContent
= jsonObject.toJavaObject(MessageReciveAckContent.class);
messageSyncService.receiveMark(messageContent);
}else if(command.equals(MessageCommand.MSG_READED.getCommand())){
//消息接收确认
MessageReadedContent messageContent
= jsonObject.toJavaObject(MessageReadedContent.class);
messageSyncService.readMark(messageContent);
}else if (Objects.equals(command, MessageCommand.MSG_RECALL.getCommand())) {
// 撤回消息
RecallMessageContent messageContent = JSON.parseObject(msg, new TypeReference<RecallMessageContent>() {
}.getType());
messageSyncService.recallMessage(messageContent);
}
channel.basicAck(deliveryTag, false);
}catch (Exception e){
logger.error("处理消息出现异常:{}", e.getMessage());
logger.error("RMQ_CHAT_TRAN_ERROR", e);
logger.error("NACK_MSG:{}", msg);
//第一个false 表示不批量拒绝第二个false表示不重回队列
channel.basicNack(deliveryTag, false, false);
}
}
}

View File

@@ -0,0 +1,143 @@
package com.lld.im.service.message.service;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.enums.*;
import com.lld.im.service.friendship.dao.ImFriendShipEntity;
import com.lld.im.service.friendship.model.req.GetRelationReq;
import com.lld.im.service.friendship.service.ImFriendService;
import com.lld.im.service.group.dao.ImGroupEntity;
import com.lld.im.service.group.model.resp.GetRoleInGroupResp;
import com.lld.im.service.group.service.ImGroupMemberService;
import com.lld.im.service.group.service.ImGroupService;
import com.lld.im.service.user.dao.ImUserDataEntity;
import com.lld.im.service.user.service.ImUserService;
import com.sun.org.apache.regexp.internal.RE;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Service
public class CheckSendMessageService {
@Autowired
ImUserService imUserService;
@Autowired
ImFriendService imFriendService;
@Autowired
ImGroupService imGroupService;
@Autowired
ImGroupMemberService imGroupMemberService;
@Autowired
AppConfig appConfig;
public ResponseVO checkSenderForvidAndMute(String fromId,Integer appId){
ResponseVO<ImUserDataEntity> singleUserInfo
= imUserService.getSingleUserInfo(fromId, appId);
if(!singleUserInfo.isOk()){
return singleUserInfo;
}
ImUserDataEntity user = singleUserInfo.getData();
if(user.getForbiddenFlag() == UserForbiddenFlagEnum.FORBIBBEN.getCode()){
return ResponseVO.errorResponse(MessageErrorCode.FROMER_IS_FORBIBBEN);
}else if (user.getSilentFlag() == UserSilentFlagEnum.MUTE.getCode()){
return ResponseVO.errorResponse(MessageErrorCode.FROMER_IS_MUTE);
}
return ResponseVO.successResponse();
}
public ResponseVO checkFriendShip(String fromId,String toId,Integer appId){
if(appConfig.isSendMessageCheckFriend()){
GetRelationReq fromReq = new GetRelationReq();
fromReq.setFromId(fromId);
fromReq.setToId(toId);
fromReq.setAppId(appId);
ResponseVO<ImFriendShipEntity> fromRelation = imFriendService.getRelation(fromReq);
if(!fromRelation.isOk()){
return fromRelation;
}
GetRelationReq toReq = new GetRelationReq();
fromReq.setFromId(toId);
fromReq.setToId(fromId);
fromReq.setAppId(appId);
ResponseVO<ImFriendShipEntity> toRelation = imFriendService.getRelation(fromReq);
if(!toRelation.isOk()){
return toRelation;
}
if(FriendShipStatusEnum.FRIEND_STATUS_NORMAL.getCode()
!= fromRelation.getData().getStatus()){
return ResponseVO.errorResponse(FriendShipErrorCode.FRIEND_IS_DELETED);
}
if(FriendShipStatusEnum.FRIEND_STATUS_NORMAL.getCode()
!= toRelation.getData().getStatus()){
return ResponseVO.errorResponse(FriendShipErrorCode.FRIEND_IS_DELETED);
}
if(appConfig.isSendMessageCheckBlack()){
if(FriendShipStatusEnum.BLACK_STATUS_NORMAL.getCode()
!= fromRelation.getData().getBlack()){
return ResponseVO.errorResponse(FriendShipErrorCode.FRIEND_IS_BLACK);
}
if(FriendShipStatusEnum.BLACK_STATUS_NORMAL.getCode()
!= toRelation.getData().getBlack()){
return ResponseVO.errorResponse(FriendShipErrorCode.TARGET_IS_BLACK_YOU);
}
}
}
return ResponseVO.successResponse();
}
public ResponseVO checkGroupMessage(String fromId,String groupId,Integer appId){
ResponseVO responseVO = checkSenderForvidAndMute(fromId, appId);
if(!responseVO.isOk()){
return responseVO;
}
//判断群逻辑
ResponseVO<ImGroupEntity> group = imGroupService.getGroup(groupId, appId);
if(!group.isOk()){
return group;
}
//判断群成员是否在群内
ResponseVO<GetRoleInGroupResp> roleInGroupOne = imGroupMemberService.getRoleInGroupOne(groupId, fromId, appId);
if(!roleInGroupOne.isOk()){
return roleInGroupOne;
}
GetRoleInGroupResp data = roleInGroupOne.getData();
//判断群是否被禁言
//如果禁言 只有裙管理和群主可以发言
ImGroupEntity groupData = group.getData();
if(groupData.getMute() == GroupMuteTypeEnum.MUTE.getCode()
&& (data.getRole() == GroupMemberRoleEnum.MAMAGER.getCode() ||
data.getRole() == GroupMemberRoleEnum.OWNER.getCode() )){
return ResponseVO.errorResponse(GroupErrorCode.THIS_GROUP_IS_MUTE);
}
if(data.getSpeakDate() != null && data.getSpeakDate() > System.currentTimeMillis()){
return ResponseVO.errorResponse(GroupErrorCode.GROUP_MEMBER_IS_SPEAK);
}
return ResponseVO.successResponse();
}
}

View File

@@ -0,0 +1,226 @@
package com.lld.im.service.message.service;
import com.alibaba.fastjson.JSONObject;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.ConversationTypeEnum;
import com.lld.im.common.enums.DelFlagEnum;
import com.lld.im.common.model.message.*;
import com.lld.im.service.conversation.service.ConversationService;
import com.lld.im.service.group.dao.ImGroupMessageHistoryEntity;
import com.lld.im.service.group.dao.mapper.ImGroupMessageHistoryMapper;
import com.lld.im.service.message.dao.ImMessageBodyEntity;
import com.lld.im.service.message.dao.ImMessageHistoryEntity;
import com.lld.im.service.message.dao.mapper.ImMessageBodyMapper;
import com.lld.im.service.message.dao.mapper.ImMessageHistoryMapper;
import com.lld.im.service.utils.SnowflakeIdWorker;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Service
public class MessageStoreService {
@Autowired
ImMessageHistoryMapper imMessageHistoryMapper;
@Autowired
ImMessageBodyMapper imMessageBodyMapper;
@Autowired
SnowflakeIdWorker snowflakeIdWorker;
@Autowired
ImGroupMessageHistoryMapper imGroupMessageHistoryMapper;
@Autowired
RabbitTemplate rabbitTemplate;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
ConversationService conversationService;
@Autowired
AppConfig appConfig;
@Transactional
public void storeP2PMessage(MessageContent messageContent){
//messageContent 转化成 messageBody
// ImMessageBody imMessageBodyEntity = extractMessageBody(messageContent);
//插入messageBody
// imMessageBodyMapper.insert(imMessageBodyEntity);
// //转化成MessageHistory
// List<ImMessageHistoryEntity> imMessageHistoryEntities = extractToP2PMessageHistory(messageContent, imMessageBodyEntity);
// //批量插入
// imMessageHistoryMapper.insertBatchSomeColumn(imMessageHistoryEntities);
// messageContent.setMessageKey(imMessageBodyEntity.getMessageKey());
ImMessageBody imMessageBodyEntity = extractMessageBody(messageContent);
DoStoreP2PMessageDto dto = new DoStoreP2PMessageDto();
dto.setMessageContent(messageContent);
dto.setMessageBody(imMessageBodyEntity);
messageContent.setMessageKey(imMessageBodyEntity.getMessageKey());
rabbitTemplate.convertAndSend(Constants.RabbitConstants.StoreP2PMessage,"",
JSONObject.toJSONString(dto));
}
public ImMessageBody extractMessageBody(MessageContent messageContent){
ImMessageBody messageBody = new ImMessageBody();
messageBody.setAppId(messageContent.getAppId());
messageBody.setMessageKey(snowflakeIdWorker.nextId());
messageBody.setCreateTime(System.currentTimeMillis());
messageBody.setSecurityKey("");
messageBody.setExtra(messageContent.getExtra());
messageBody.setDelFlag(DelFlagEnum.NORMAL.getCode());
messageBody.setMessageTime(messageContent.getMessageTime());
messageBody.setMessageBody(messageContent.getMessageBody());
return messageBody;
}
public List<ImMessageHistoryEntity> extractToP2PMessageHistory(MessageContent messageContent,
ImMessageBodyEntity imMessageBodyEntity){
List<ImMessageHistoryEntity> list = new ArrayList<>();
ImMessageHistoryEntity fromHistory = new ImMessageHistoryEntity();
BeanUtils.copyProperties(messageContent,fromHistory);
fromHistory.setOwnerId(messageContent.getFromId());
fromHistory.setMessageKey(imMessageBodyEntity.getMessageKey());
fromHistory.setCreateTime(System.currentTimeMillis());
ImMessageHistoryEntity toHistory = new ImMessageHistoryEntity();
BeanUtils.copyProperties(messageContent,toHistory);
toHistory.setOwnerId(messageContent.getToId());
toHistory.setMessageKey(imMessageBodyEntity.getMessageKey());
toHistory.setCreateTime(System.currentTimeMillis());
list.add(fromHistory);
list.add(toHistory);
return list;
}
@Transactional
public void storeGroupMessage(GroupChatMessageContent messageContent){
ImMessageBody imMessageBody = extractMessageBody(messageContent);
DoStoreGroupMessageDto dto = new DoStoreGroupMessageDto();
dto.setMessageBody(imMessageBody);
dto.setGroupChatMessageContent(messageContent);
rabbitTemplate.convertAndSend(Constants.RabbitConstants.StoreGroupMessage,
"",
JSONObject.toJSONString(dto));
messageContent.setMessageKey(imMessageBody.getMessageKey());
}
private ImGroupMessageHistoryEntity extractToGroupMessageHistory(GroupChatMessageContent
messageContent ,ImMessageBodyEntity messageBodyEntity){
ImGroupMessageHistoryEntity result = new ImGroupMessageHistoryEntity();
BeanUtils.copyProperties(messageContent,result);
result.setGroupId(messageContent.getGroupId());
result.setMessageKey(messageBodyEntity.getMessageKey());
result.setCreateTime(System.currentTimeMillis());
return result;
}
public void setMessageFromMessageIdCache(Integer appId,String messageId,Object messageContent){
//appid : cache : messageId
String key =appId + ":" + Constants.RedisConstants.cacheMessage + ":" + messageId;
stringRedisTemplate.opsForValue().set(key,JSONObject.toJSONString(messageContent),300, TimeUnit.SECONDS);
}
public <T> T getMessageFromMessageIdCache(Integer appId,
String messageId,Class<T> clazz){
//appid : cache : messageId
String key = appId + ":" + Constants.RedisConstants.cacheMessage + ":" + messageId;
String msg = stringRedisTemplate.opsForValue().get(key);
if(StringUtils.isBlank(msg)){
return null;
}
return JSONObject.parseObject(msg, clazz);
}
/**
* @description: 存储单人离线消息
* @param
* @return void
* @author lld
*/
public void storeOfflineMessage(OfflineMessageContent offlineMessage){
// 找到fromId的队列
String fromKey = offlineMessage.getAppId() + ":" + Constants.RedisConstants.OfflineMessage + ":" + offlineMessage.getFromId();
// 找到toId的队列
String toKey = offlineMessage.getAppId() + ":" + Constants.RedisConstants.OfflineMessage + ":" + offlineMessage.getToId();
ZSetOperations<String, String> operations = stringRedisTemplate.opsForZSet();
//判断 队列中的数据是否超过设定值
if(operations.zCard(fromKey) > appConfig.getOfflineMessageCount()){
operations.removeRange(fromKey,0,0);
}
offlineMessage.setConversationId(conversationService.convertConversationId(
ConversationTypeEnum.P2P.getCode(),offlineMessage.getFromId(),offlineMessage.getToId()
));
// 插入 数据 根据messageKey 作为分值
operations.add(fromKey,JSONObject.toJSONString(offlineMessage),
offlineMessage.getMessageKey());
//判断 队列中的数据是否超过设定值
if(operations.zCard(toKey) > appConfig.getOfflineMessageCount()){
operations.removeRange(toKey,0,0);
}
offlineMessage.setConversationId(conversationService.convertConversationId(
ConversationTypeEnum.P2P.getCode(),offlineMessage.getToId(),offlineMessage.getFromId()
));
// 插入 数据 根据messageKey 作为分值
operations.add(toKey,JSONObject.toJSONString(offlineMessage),
offlineMessage.getMessageKey());
}
/**
* @description: 存储单人离线消息
* @param
* @return void
* @author lld
*/
public void storeGroupOfflineMessage(OfflineMessageContent offlineMessage
,List<String> memberIds){
ZSetOperations<String, String> operations = stringRedisTemplate.opsForZSet();
//判断 队列中的数据是否超过设定值
offlineMessage.setConversationType(ConversationTypeEnum.GROUP.getCode());
for (String memberId : memberIds) {
// 找到toId的队列
String toKey = offlineMessage.getAppId() + ":" +
Constants.RedisConstants.OfflineMessage + ":" +
memberId;
offlineMessage.setConversationId(conversationService.convertConversationId(
ConversationTypeEnum.GROUP.getCode(),memberId,offlineMessage.getToId()
));
if(operations.zCard(toKey) > appConfig.getOfflineMessageCount()){
operations.removeRange(toKey,0,0);
}
// 插入 数据 根据messageKey 作为分值
operations.add(toKey,JSONObject.toJSONString(offlineMessage),
offlineMessage.getMessageKey());
}
}
}

View File

@@ -0,0 +1,255 @@
package com.lld.im.service.message.service;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.lld.im.codec.pack.message.MessageReadedPack;
import com.lld.im.codec.pack.message.RecallMessageNotifyPack;
import com.lld.im.codec.proto.Message;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.ConversationTypeEnum;
import com.lld.im.common.enums.DelFlagEnum;
import com.lld.im.common.enums.MessageErrorCode;
import com.lld.im.common.enums.command.Command;
import com.lld.im.common.enums.command.GroupEventCommand;
import com.lld.im.common.enums.command.MessageCommand;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.common.model.SyncReq;
import com.lld.im.common.model.SyncResp;
import com.lld.im.common.model.message.MessageReadedContent;
import com.lld.im.common.model.message.MessageReciveAckContent;
import com.lld.im.common.model.message.OfflineMessageContent;
import com.lld.im.common.model.message.RecallMessageContent;
import com.lld.im.service.conversation.service.ConversationService;
import com.lld.im.service.group.service.ImGroupMemberService;
import com.lld.im.service.message.dao.ImMessageBodyEntity;
import com.lld.im.service.message.dao.mapper.ImMessageBodyMapper;
import com.lld.im.service.seq.RedisSeq;
import com.lld.im.service.utils.ConversationIdGenerate;
import com.lld.im.service.utils.GroupMessageProducer;
import com.lld.im.service.utils.MessageProducer;
import com.lld.im.service.utils.SnowflakeIdWorker;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Service
public class MessageSyncService {
@Autowired
MessageProducer messageProducer;
@Autowired
ConversationService conversationService;
@Autowired
RedisTemplate redisTemplate;
@Autowired
ImMessageBodyMapper imMessageBodyMapper;
@Autowired
RedisSeq redisSeq;
@Autowired
SnowflakeIdWorker snowflakeIdWorker;
@Autowired
ImGroupMemberService imGroupMemberService;
@Autowired
GroupMessageProducer groupMessageProducer;
public void receiveMark(MessageReciveAckContent messageReciveAckContent){
messageProducer.sendToUser(messageReciveAckContent.getToId(),
MessageCommand.MSG_RECIVE_ACK,messageReciveAckContent,messageReciveAckContent.getAppId());
}
/**
* @description: 消息已读。更新会话的seq通知在线的同步端发送指定command ,发送已读回执通知对方(消息发起方)我已读
* @param
* @return void
* @author lld
*/
public void readMark(MessageReadedContent messageContent) {
conversationService.messageMarkRead(messageContent);
MessageReadedPack messageReadedPack = new MessageReadedPack();
BeanUtils.copyProperties(messageContent,messageReadedPack);
syncToSender(messageReadedPack,messageContent,MessageCommand.MSG_READED_NOTIFY);
//发送给对方
messageProducer.sendToUser(messageContent.getToId(),
MessageCommand.MSG_READED_RECEIPT,messageReadedPack,messageContent.getAppId());
}
private void syncToSender(MessageReadedPack pack, MessageReadedContent content, Command command){
MessageReadedPack messageReadedPack = new MessageReadedPack();
// BeanUtils.copyProperties(messageReadedContent,messageReadedPack);
//发送给自己的其他端
messageProducer.sendToUserExceptClient(pack.getFromId(),
command,pack,
content);
}
public void groupReadMark(MessageReadedContent messageReaded) {
conversationService.messageMarkRead(messageReaded);
MessageReadedPack messageReadedPack = new MessageReadedPack();
BeanUtils.copyProperties(messageReaded,messageReadedPack);
syncToSender(messageReadedPack,messageReaded, GroupEventCommand.MSG_GROUP_READED_NOTIFY
);
if(!messageReaded.getFromId().equals(messageReaded.getToId())){
messageProducer.sendToUser(messageReadedPack.getToId(),GroupEventCommand.MSG_GROUP_READED_RECEIPT
,messageReaded,messageReaded.getAppId());
}
}
public ResponseVO syncOfflineMessage(SyncReq req) {
SyncResp<OfflineMessageContent> resp = new SyncResp<>();
String key = req.getAppId() + ":" + Constants.RedisConstants.OfflineMessage + ":" + req.getOperater();
//获取最大的seq
Long maxSeq = 0L;
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
Set set = zSetOperations.reverseRangeWithScores(key, 0, 0);
if(!CollectionUtils.isEmpty(set)){
List list = new ArrayList(set);
DefaultTypedTuple o = (DefaultTypedTuple) list.get(0);
maxSeq = o.getScore().longValue();
}
List<OfflineMessageContent> respList = new ArrayList<>();
resp.setMaxSequence(maxSeq);
Set<ZSetOperations.TypedTuple> querySet = zSetOperations.rangeByScoreWithScores(key,
req.getLastSequence(), maxSeq, 0, req.getMaxLimit());
for (ZSetOperations.TypedTuple<String> typedTuple : querySet) {
String value = typedTuple.getValue();
OfflineMessageContent offlineMessageContent = JSONObject.parseObject(value, OfflineMessageContent.class);
respList.add(offlineMessageContent);
}
resp.setDataList(respList);
if(!CollectionUtils.isEmpty(respList)){
OfflineMessageContent offlineMessageContent = respList.get(respList.size() - 1);
resp.setCompleted(maxSeq <= offlineMessageContent.getMessageKey());
}
return ResponseVO.successResponse(resp);
}
//修改历史消息的状态
//修改离线消息的状态
//ack给发送方
//发送给同步端
//分发给消息的接收方
public void recallMessage(RecallMessageContent content) {
Long messageTime = content.getMessageTime();
Long now = System.currentTimeMillis();
RecallMessageNotifyPack pack = new RecallMessageNotifyPack();
BeanUtils.copyProperties(content,pack);
if(120000L < now - messageTime){
recallAck(pack,ResponseVO.errorResponse(MessageErrorCode.MESSAGE_RECALL_TIME_OUT),content);
return;
}
QueryWrapper<ImMessageBodyEntity> query = new QueryWrapper<>();
query.eq("app_id",content.getAppId());
query.eq("message_key",content.getMessageKey());
ImMessageBodyEntity body = imMessageBodyMapper.selectOne(query);
if(body == null){
//TODO ack失败 不存在的消息不能撤回
recallAck(pack,ResponseVO.errorResponse(MessageErrorCode.MESSAGEBODY_IS_NOT_EXIST),content);
return;
}
if(body.getDelFlag() == DelFlagEnum.DELETE.getCode()){
recallAck(pack,ResponseVO.errorResponse(MessageErrorCode.MESSAGE_IS_RECALLED),content);
return;
}
body.setDelFlag(DelFlagEnum.DELETE.getCode());
imMessageBodyMapper.update(body,query);
if(content.getConversationType() == ConversationTypeEnum.P2P.getCode()){
// 找到fromId的队列
String fromKey = content.getAppId() + ":" + Constants.RedisConstants.OfflineMessage + ":" + content.getFromId();
// 找到toId的队列
String toKey = content.getAppId() + ":" + Constants.RedisConstants.OfflineMessage + ":" + content.getToId();
OfflineMessageContent offlineMessageContent = new OfflineMessageContent();
BeanUtils.copyProperties(content,offlineMessageContent);
offlineMessageContent.setDelFlag(DelFlagEnum.DELETE.getCode());
offlineMessageContent.setMessageKey(content.getMessageKey());
offlineMessageContent.setConversationType(ConversationTypeEnum.P2P.getCode());
offlineMessageContent.setConversationId(conversationService.convertConversationId(offlineMessageContent.getConversationType()
,content.getFromId(),content.getToId()));
offlineMessageContent.setMessageBody(body.getMessageBody());
long seq = redisSeq.doGetSeq(content.getAppId() + ":" + Constants.SeqConstants.Message + ":" + ConversationIdGenerate.generateP2PId(content.getFromId(),content.getToId()));
offlineMessageContent.setMessageSequence(seq);
long messageKey = SnowflakeIdWorker.nextId();
redisTemplate.opsForZSet().add(fromKey,JSONObject.toJSONString(offlineMessageContent),messageKey);
redisTemplate.opsForZSet().add(toKey,JSONObject.toJSONString(offlineMessageContent),messageKey);
//ack
recallAck(pack,ResponseVO.successResponse(),content);
//分发给同步端
messageProducer.sendToUserExceptClient(content.getFromId(),
MessageCommand.MSG_RECALL_NOTIFY,pack,content);
//分发给对方
messageProducer.sendToUser(content.getToId(),MessageCommand.MSG_RECALL_NOTIFY,
pack,content.getAppId());
}else{
List<String> groupMemberId = imGroupMemberService.getGroupMemberId(content.getToId(), content.getAppId());
long seq = redisSeq.doGetSeq(content.getAppId() + ":" + Constants.SeqConstants.Message + ":" + ConversationIdGenerate.generateP2PId(content.getFromId(),content.getToId()));
//ack
recallAck(pack,ResponseVO.successResponse(),content);
//发送给同步端
messageProducer.sendToUserExceptClient(content.getFromId(), MessageCommand.MSG_RECALL_NOTIFY, pack
, content);
for (String memberId : groupMemberId) {
String toKey = content.getAppId() + ":" + Constants.SeqConstants.Message + ":" + memberId;
OfflineMessageContent offlineMessageContent = new OfflineMessageContent();
offlineMessageContent.setDelFlag(DelFlagEnum.DELETE.getCode());
BeanUtils.copyProperties(content,offlineMessageContent);
offlineMessageContent.setConversationType(ConversationTypeEnum.GROUP.getCode());
offlineMessageContent.setConversationId(conversationService.convertConversationId(offlineMessageContent.getConversationType()
,content.getFromId(),content.getToId()));
offlineMessageContent.setMessageBody(body.getMessageBody());
offlineMessageContent.setMessageSequence(seq);
redisTemplate.opsForZSet().add(toKey,JSONObject.toJSONString(offlineMessageContent),seq);
groupMessageProducer.producer(content.getFromId(), MessageCommand.MSG_RECALL_NOTIFY, pack,content);
}
}
}
private void recallAck(RecallMessageNotifyPack recallPack, ResponseVO<Object> success, ClientInfo clientInfo) {
ResponseVO<Object> wrappedResp = success;
messageProducer.sendToUser(recallPack.getFromId(),
MessageCommand.MSG_RECALL_ACK, wrappedResp, clientInfo);
}
}

View File

@@ -0,0 +1,238 @@
package com.lld.im.service.message.service;
import com.alibaba.fastjson.JSONObject;
import com.lld.im.codec.pack.message.ChatMessageAck;
import com.lld.im.codec.pack.message.MessageReciveServerAckPack;
import com.lld.im.codec.proto.Message;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.ConversationTypeEnum;
import com.lld.im.common.enums.command.MessageCommand;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.common.model.message.MessageContent;
import com.lld.im.common.model.message.OfflineMessageContent;
import com.lld.im.service.message.model.req.SendMessageReq;
import com.lld.im.service.message.model.resp.SendMessageResp;
import com.lld.im.service.seq.RedisSeq;
import com.lld.im.service.utils.CallbackService;
import com.lld.im.service.utils.ConversationIdGenerate;
import com.lld.im.service.utils.MessageProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Service
public class P2PMessageService {
private static Logger logger = LoggerFactory.getLogger(P2PMessageService.class);
@Autowired
CheckSendMessageService checkSendMessageService;
@Autowired
MessageProducer messageProducer;
@Autowired
MessageStoreService messageStoreService;
@Autowired
RedisSeq redisSeq;
@Autowired
AppConfig appConfig;
@Autowired
CallbackService callbackService;
private final ThreadPoolExecutor threadPoolExecutor;
{
final AtomicInteger num = new AtomicInteger(0);
threadPoolExecutor = new ThreadPoolExecutor(8, 8, 60, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(1000), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("message-process-thread-" + num.getAndIncrement());
return thread;
}
});
}
//离线
//存储介质
//1.mysql
//2.redis
//怎么存?
//list
//历史消息
//发送方客户端时间
//messageKey
//redis 1 2 3
public void process(MessageContent messageContent){
logger.info("消息开始处理:{}",messageContent.getMessageId());
String fromId = messageContent.getFromId();
String toId = messageContent.getToId();
Integer appId = messageContent.getAppId();
MessageContent messageFromMessageIdCache = messageStoreService.getMessageFromMessageIdCache
(messageContent.getAppId(), messageContent.getMessageId(),MessageContent.class);
if (messageFromMessageIdCache != null){
threadPoolExecutor.execute(() ->{
ack(messageContent,ResponseVO.successResponse());
//2.发消息给同步在线端
syncToSender(messageFromMessageIdCache,messageFromMessageIdCache);
//3.发消息给对方在线端
List<ClientInfo> clientInfos = dispatchMessage(messageFromMessageIdCache);
if(clientInfos.isEmpty()){
//发送接收确认给发送方,要带上是服务端发送的标识
reciverAck(messageFromMessageIdCache);
}
});
return;
}
//回调
ResponseVO responseVO = ResponseVO.successResponse();
if(appConfig.isSendMessageAfterCallback()){
responseVO = callbackService.beforeCallback(messageContent.getAppId(), Constants.CallbackCommand.SendMessageBefore
, JSONObject.toJSONString(messageContent));
}
if(!responseVO.isOk()){
ack(messageContent,responseVO);
return;
}
long seq = redisSeq.doGetSeq(messageContent.getAppId() + ":"
+ Constants.SeqConstants.Message+ ":" + ConversationIdGenerate.generateP2PId(
messageContent.getFromId(),messageContent.getToId()
));
messageContent.setMessageSequence(seq);
//前置校验
//这个用户是否被禁言 是否被禁用
//发送方和接收方是否是好友
// ResponseVO responseVO = imServerPermissionCheck(fromId, toId, appId);
// if(responseVO.isOk()){
threadPoolExecutor.execute(() ->{
//appId + Seq + (from + to) groupId
messageStoreService.storeP2PMessage(messageContent);
OfflineMessageContent offlineMessageContent = new OfflineMessageContent();
BeanUtils.copyProperties(messageContent,offlineMessageContent);
offlineMessageContent.setConversationType(ConversationTypeEnum.P2P.getCode());
messageStoreService.storeOfflineMessage(offlineMessageContent);
//插入数据
//1.回ack成功给自己
ack(messageContent,ResponseVO.successResponse());
//2.发消息给同步在线端
syncToSender(messageContent,messageContent);
//3.发消息给对方在线端
List<ClientInfo> clientInfos = dispatchMessage(messageContent);
messageStoreService.setMessageFromMessageIdCache(messageContent.getAppId(),
messageContent.getMessageId(),messageContent);
if(clientInfos.isEmpty()){
//发送接收确认给发送方,要带上是服务端发送的标识
reciverAck(messageContent);
}
if(appConfig.isSendMessageAfterCallback()){
callbackService.callback(messageContent.getAppId(),Constants.CallbackCommand.SendMessageAfter,
JSONObject.toJSONString(messageContent));
}
logger.info("消息处理完成:{}",messageContent.getMessageId());
});
// }else{
// //告诉客户端失败了
// //ack
// ack(messageContent,responseVO);
// }
}
private List<ClientInfo> dispatchMessage(MessageContent messageContent){
List<ClientInfo> clientInfos = messageProducer.sendToUser(messageContent.getToId(), MessageCommand.MSG_P2P,
messageContent, messageContent.getAppId());
return clientInfos;
}
private void ack(MessageContent messageContent,ResponseVO responseVO){
logger.info("msg ack,msgId={},checkResut{}",messageContent.getMessageId(),responseVO.getCode());
ChatMessageAck chatMessageAck = new
ChatMessageAck(messageContent.getMessageId(),messageContent.getMessageSequence());
responseVO.setData(chatMessageAck);
//發消息
messageProducer.sendToUser(messageContent.getFromId(), MessageCommand.MSG_ACK,
responseVO,messageContent
);
}
public void reciverAck(MessageContent messageContent){
MessageReciveServerAckPack pack = new MessageReciveServerAckPack();
pack.setFromId(messageContent.getToId());
pack.setToId(messageContent.getFromId());
pack.setMessageKey(messageContent.getMessageKey());
pack.setMessageSequence(messageContent.getMessageSequence());
pack.setServerSend(true);
messageProducer.sendToUser(messageContent.getFromId(),MessageCommand.MSG_RECIVE_ACK,
pack,new ClientInfo(messageContent.getAppId(),messageContent.getClientType()
,messageContent.getImei()));
}
private void syncToSender(MessageContent messageContent, ClientInfo clientInfo){
messageProducer.sendToUserExceptClient(messageContent.getFromId(),
MessageCommand.MSG_P2P,messageContent,messageContent);
}
public ResponseVO imServerPermissionCheck(String fromId,String toId,
Integer appId){
ResponseVO responseVO = checkSendMessageService.checkSenderForvidAndMute(fromId, appId);
if(!responseVO.isOk()){
return responseVO;
}
responseVO = checkSendMessageService.checkFriendShip(fromId, toId, appId);
return responseVO;
}
public SendMessageResp send(SendMessageReq req) {
SendMessageResp sendMessageResp = new SendMessageResp();
MessageContent message = new MessageContent();
BeanUtils.copyProperties(req,message);
//插入数据
messageStoreService.storeP2PMessage(message);
sendMessageResp.setMessageKey(message.getMessageKey());
sendMessageResp.setMessageTime(System.currentTimeMillis());
//2.发消息给同步在线端
syncToSender(message,message);
//3.发消息给对方在线端
dispatchMessage(message);
return sendMessageResp;
}
}

View File

@@ -0,0 +1,23 @@
package com.lld.im.service.seq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Service
public class RedisSeq {
@Autowired
StringRedisTemplate stringRedisTemplate;
public long doGetSeq(String key){
return stringRedisTemplate.opsForValue().increment(key);
}
}

View File

@@ -1,14 +1,22 @@
package com.lld.im.service.user.controller;
import com.lld.im.common.ClientType;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.route.RouteHandle;
import com.lld.im.common.route.RouteInfo;
import com.lld.im.common.utils.RouteInfoParseUtil;
import com.lld.im.service.user.model.req.*;
import com.lld.im.service.user.service.ImUserService;
import com.lld.im.service.user.service.ImUserStatusService;
import com.lld.im.service.utils.ZKit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* @description:
@@ -21,15 +29,96 @@ public class ImUserController {
@Autowired
ImUserService imUserService;
@Autowired
RouteHandle routeHandle;
@Autowired
ImUserStatusService imUserStatusService;
@Autowired
ZKit zKit;
@RequestMapping("importUser")
public ResponseVO importUser(@RequestBody ImportUserReq req, Integer appId) {
req.setAppId(appId);
return imUserService.importUser(req);
}
@RequestMapping("/deleteUser")
public ResponseVO deleteUser(@RequestBody @Validated DeleteUserReq req, Integer appId) {
req.setAppId(appId);
return imUserService.deleteUser(req);
}
/**
* @param [req]
* @return com.lld.im.common.ResponseVO
* @description im的登录接口返回im地址
* @author chackylee
*/
@RequestMapping("/login")
public ResponseVO login(@RequestBody @Validated LoginReq req, Integer appId) {
req.setAppId(appId);
ResponseVO login = imUserService.login(req);
if (login.isOk()) {
List<String> allNode = new ArrayList<>();
if (req.getClientType() == ClientType.WEB.getCode()) {
allNode = zKit.getAllWebNode();
} else {
allNode = zKit.getAllTcpNode();
}
String s = routeHandle.routeServer(allNode, req
.getUserId());
RouteInfo parse = RouteInfoParseUtil.parse(s);
return ResponseVO.successResponse(parse);
}
return ResponseVO.errorResponse();
}
@RequestMapping("/getUserSequence")
public ResponseVO getUserSequence(@RequestBody @Validated
GetUserSequenceReq req, Integer appId) {
req.setAppId(appId);
return imUserService.getUserSequence(req);
}
@RequestMapping("/subscribeUserOnlineStatus")
public ResponseVO subscribeUserOnlineStatus(@RequestBody @Validated
SubscribeUserOnlineStatusReq req, Integer appId,String identifier) {
req.setAppId(appId);
req.setOperater(identifier);
imUserStatusService.subscribeUserOnlineStatus(req);
return ResponseVO.successResponse();
}
@RequestMapping("/setUserCustomerStatus")
public ResponseVO setUserCustomerStatus(@RequestBody @Validated
SetUserCustomerStatusReq req, Integer appId,String identifier) {
req.setAppId(appId);
req.setOperater(identifier);
imUserStatusService.setUserCustomerStatus(req);
return ResponseVO.successResponse();
}
@RequestMapping("/queryFriendOnlineStatus")
public ResponseVO queryFriendOnlineStatus(@RequestBody @Validated
PullFriendOnlineStatusReq req, Integer appId,String identifier) {
req.setAppId(appId);
req.setOperater(identifier);
return ResponseVO.successResponse(imUserStatusService.queryFriendOnlineStatus(req));
}
@RequestMapping("/queryUserOnlineStatus")
public ResponseVO queryUserOnlineStatus(@RequestBody @Validated
PullUserOnlineStatusReq req, Integer appId,String identifier) {
req.setAppId(appId);
req.setOperater(identifier);
return ResponseVO.successResponse(imUserStatusService.queryUserOnlineStatus(req));
}
}

View File

@@ -0,0 +1,23 @@
package com.lld.im.service.user.model.req;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author: Chackylee
* @description:
**/
@Data
public class LoginReq {
@NotNull(message = "用户id不能位空")
private String userId;
@NotNull(message = "appId不能为空")
private Integer appId;
private Integer clientType;
}

View File

@@ -0,0 +1,13 @@
package com.lld.im.service.user.model.req;
import com.lld.im.common.model.RequestBase;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class PullFriendOnlineStatusReq extends RequestBase {
}

View File

@@ -0,0 +1,18 @@
package com.lld.im.service.user.model.req;
import com.lld.im.common.model.RequestBase;
import lombok.Data;
import java.util.List;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class PullUserOnlineStatusReq extends RequestBase {
private List<String> userList;
}

View File

@@ -0,0 +1,20 @@
package com.lld.im.service.user.model.req;
import com.lld.im.common.model.RequestBase;
import lombok.Data;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class SetUserCustomerStatusReq extends RequestBase {
private String userId;
private String customText;
private Integer customStatus;
}

View File

@@ -0,0 +1,21 @@
package com.lld.im.service.user.model.req;
import com.lld.im.common.model.RequestBase;
import lombok.Data;
import java.util.List;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Data
public class SubscribeUserOnlineStatusReq extends RequestBase {
private List<String> subUserId;
private Long subTime;
}

View File

@@ -22,5 +22,8 @@ public interface ImUserService {
public ResponseVO modifyUserInfo(ModifyUserInfoReq req);
public ResponseVO login(LoginReq req);
ResponseVO getUserSequence(GetUserSequenceReq req);
}

View File

@@ -0,0 +1,29 @@
package com.lld.im.service.user.service;
import com.lld.im.common.ResponseVO;
import com.lld.im.service.user.model.UserStatusChangeNotifyContent;
import com.lld.im.service.user.model.req.PullFriendOnlineStatusReq;
import com.lld.im.service.user.model.req.PullUserOnlineStatusReq;
import com.lld.im.service.user.model.req.SetUserCustomerStatusReq;
import com.lld.im.service.user.model.req.SubscribeUserOnlineStatusReq;
import com.lld.im.service.user.model.resp.UserOnlineStatusResp;
import java.util.Map;
/**
* @description:
* @author: lld
* @version: 1.0
*/
public interface ImUserStatusService {
public void processUserOnlineStatusNotify(UserStatusChangeNotifyContent content);
void subscribeUserOnlineStatus(SubscribeUserOnlineStatusReq req);
void setUserCustomerStatus(SetUserCustomerStatusReq req);
Map<String, UserOnlineStatusResp> queryFriendOnlineStatus(PullFriendOnlineStatusReq req);
Map<String, UserOnlineStatusResp> queryUserOnlineStatus(PullUserOnlineStatusReq req);
}

View File

@@ -0,0 +1,178 @@
package com.lld.im.service.user.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lld.im.codec.pack.user.UserCustomStatusChangeNotifyPack;
import com.lld.im.codec.pack.user.UserStatusChangeNotifyPack;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.command.UserEventCommand;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.common.model.UserSession;
import com.lld.im.service.friendship.service.ImFriendService;
import com.lld.im.service.user.model.UserStatusChangeNotifyContent;
import com.lld.im.service.user.model.req.PullFriendOnlineStatusReq;
import com.lld.im.service.user.model.req.PullUserOnlineStatusReq;
import com.lld.im.service.user.model.req.SetUserCustomerStatusReq;
import com.lld.im.service.user.model.req.SubscribeUserOnlineStatusReq;
import com.lld.im.service.user.model.resp.UserOnlineStatusResp;
import com.lld.im.service.user.service.ImUserStatusService;
import com.lld.im.service.utils.MessageProducer;
import com.lld.im.service.utils.UserSessionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Service
public class ImUserStatusServiceImpl implements ImUserStatusService {
@Autowired
UserSessionUtils userSessionUtils;
@Autowired
MessageProducer messageProducer;
@Autowired
ImFriendService imFriendService;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Override
public void processUserOnlineStatusNotify(UserStatusChangeNotifyContent content) {
List<UserSession> userSession = userSessionUtils.getUserSession(content.getAppId(), content.getUserId());
UserStatusChangeNotifyPack userStatusChangeNotifyPack = new UserStatusChangeNotifyPack();
BeanUtils.copyProperties(content,userStatusChangeNotifyPack);
userStatusChangeNotifyPack.setClient(userSession);
syncSender(userStatusChangeNotifyPack,content.getUserId(),
content);
dispatcher(userStatusChangeNotifyPack,content.getUserId(),
content.getAppId());
}
private void syncSender(Object pack, String userId, ClientInfo clientInfo){
messageProducer.sendToUserExceptClient(userId,
UserEventCommand.USER_ONLINE_STATUS_CHANGE_NOTIFY_SYNC,
pack,clientInfo);
}
private void dispatcher(Object pack,String userId,Integer appId){
List<String> allFriendId = imFriendService.getAllFriendId(userId, appId);
for (String fid : allFriendId) {
messageProducer.sendToUser(fid,UserEventCommand.USER_ONLINE_STATUS_CHANGE_NOTIFY,
pack,appId);
}
String userKey = appId + ":" + Constants.RedisConstants.subscribe + userId;
Set<Object> keys = stringRedisTemplate.opsForHash().keys(userKey);
for (Object key : keys) {
String filed = (String) key;
Long expire = Long.valueOf((String) stringRedisTemplate.opsForHash().get(userKey, filed));
if(expire > 0 && expire > System.currentTimeMillis()){
messageProducer.sendToUser(filed,UserEventCommand.USER_ONLINE_STATUS_CHANGE_NOTIFY,
pack,appId);
}else{
stringRedisTemplate.opsForHash().delete(userKey,filed);
}
}
}
/**
* @description:
* @param
* @return void
* @author lld
*/
@Override
public void subscribeUserOnlineStatus(SubscribeUserOnlineStatusReq req) {
// A
// Z
// A - B C D
// CA Z F
//hash
// B - [A:xxxx,C:xxxx]
// C - []
// D - []
Long subExpireTime = 0L;
if(req != null && req.getSubTime() > 0){
subExpireTime = System.currentTimeMillis() + req.getSubTime();
}
for (String beSubUserId : req.getSubUserId()) {
String userKey = req.getAppId() + ":" + Constants.RedisConstants.subscribe + ":" + beSubUserId;
stringRedisTemplate.opsForHash().put(userKey,req.getOperater(),subExpireTime.toString());
}
}
/**
* @description: 设置自定义状态
* @param
* @return void
* @author lld
*/
@Override
public void setUserCustomerStatus(SetUserCustomerStatusReq req) {
UserCustomStatusChangeNotifyPack userCustomStatusChangeNotifyPack = new UserCustomStatusChangeNotifyPack();
userCustomStatusChangeNotifyPack.setCustomStatus(req.getCustomStatus());
userCustomStatusChangeNotifyPack.setCustomText(req.getCustomText());
userCustomStatusChangeNotifyPack.setUserId(req.getUserId());
stringRedisTemplate.opsForValue().set(req.getAppId()
+":"+ Constants.RedisConstants.userCustomerStatus + ":" + req.getUserId()
,JSONObject.toJSONString(userCustomStatusChangeNotifyPack));
syncSender(userCustomStatusChangeNotifyPack,
req.getUserId(),new ClientInfo(req.getAppId(),req.getClientType(),req.getImei()));
dispatcher(userCustomStatusChangeNotifyPack,req.getUserId(),req.getAppId());
}
@Override
public Map<String, UserOnlineStatusResp> queryFriendOnlineStatus(PullFriendOnlineStatusReq req) {
List<String> allFriendId = imFriendService.getAllFriendId(req.getOperater(), req.getAppId());
return getUserOnlineStatus(allFriendId,req.getAppId());
}
@Override
public Map<String, UserOnlineStatusResp> queryUserOnlineStatus(PullUserOnlineStatusReq req) {
return getUserOnlineStatus(req.getUserList(),req.getAppId());
}
private Map<String, UserOnlineStatusResp> getUserOnlineStatus(List<String> userId,Integer appId){
Map<String, UserOnlineStatusResp> result = new HashMap<>(userId.size());
for (String uid : userId) {
UserOnlineStatusResp resp = new UserOnlineStatusResp();
List<UserSession> userSession = userSessionUtils.getUserSession(appId, uid);
resp.setSession(userSession);
String userKey = appId + ":" + Constants.RedisConstants.userCustomerStatus + ":" + uid;
String s = stringRedisTemplate.opsForValue().get(userKey);
if(StringUtils.isNotBlank(s)){
JSONObject parse = (JSONObject) JSON.parse(s);
resp.setCustomText(parse.getString("customText"));
resp.setCustomStatus(parse.getInteger("customStatus"));
}
result.put(uid,resp);
}
return result;
}
}

View File

@@ -2,9 +2,13 @@ package com.lld.im.service.user.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lld.im.codec.pack.user.UserModifyPack;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.DelFlagEnum;
import com.lld.im.common.enums.UserErrorCode;
import com.lld.im.common.enums.command.UserEventCommand;
import com.lld.im.common.exception.ApplicationException;
import com.lld.im.service.group.service.ImGroupService;
import com.lld.im.service.user.dao.ImUserDataEntity;
@@ -13,8 +17,11 @@ import com.lld.im.service.user.model.req.*;
import com.lld.im.service.user.model.resp.GetUserInfoResp;
import com.lld.im.service.user.model.resp.ImportUserResp;
import com.lld.im.service.user.service.ImUserService;
import com.lld.im.service.utils.CallbackService;
import com.lld.im.service.utils.MessageProducer;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -34,6 +41,17 @@ public class ImUserviceImpl implements ImUserService {
@Autowired
ImUserDataMapper imUserDataMapper;
@Autowired
AppConfig appConfig;
@Autowired
CallbackService callbackService;
@Autowired
MessageProducer messageProducer;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
ImGroupService imGroupService;
@@ -165,9 +183,31 @@ public class ImUserviceImpl implements ImUserService {
update.setUserId(null);
int update1 = imUserDataMapper.update(update, query);
if(update1 == 1){
UserModifyPack pack = new UserModifyPack();
BeanUtils.copyProperties(req,pack);
messageProducer.sendToUser(req.getUserId(),req.getClientType(),req.getImei(),
UserEventCommand.USER_MODIFY,pack,req.getAppId());
if(appConfig.isModifyUserAfterCallback()){
callbackService.callback(req.getAppId(),
Constants.CallbackCommand.ModifyUserAfter,
JSONObject.toJSONString(req));
}
return ResponseVO.successResponse();
}
throw new ApplicationException(UserErrorCode.MODIFY_USER_ERROR);
}
@Override
public ResponseVO login(LoginReq req) {
return ResponseVO.successResponse();
}
@Override
public ResponseVO getUserSequence(GetUserSequenceReq req) {
Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(req.getAppId() + ":" + Constants.RedisConstants.SeqPrefix + ":" + req.getUserId());
Long groupSeq = imGroupService.getUserGroupMaxSeq(req.getUserId(),req.getAppId());
map.put(Constants.SeqConstants.Group,groupSeq);
return ResponseVO.successResponse(map);
}
}

View File

@@ -0,0 +1,64 @@
package com.lld.im.service.utils;
import com.lld.im.common.ResponseVO;
import com.lld.im.common.config.AppConfig;
import com.lld.im.common.utils.HttpRequestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Component
public class CallbackService {
private Logger logger = LoggerFactory.getLogger(CallbackService.class);
@Autowired
HttpRequestUtils httpRequestUtils;
@Autowired
AppConfig appConfig;
@Autowired
ShareThreadPool shareThreadPool;
public void callback(Integer appId,String callbackCommand,String jsonBody){
shareThreadPool.submit(() -> {
try {
httpRequestUtils.doPost(appConfig.getCallbackUrl(),Object.class,builderUrlParams(appId,callbackCommand),
jsonBody,null);
}catch (Exception e){
logger.error("callback 回调{} : {}出现异常 {} ",callbackCommand , appId, e.getMessage());
}
});
}
public ResponseVO beforeCallback(Integer appId,String callbackCommand,String jsonBody){
try {
ResponseVO responseVO = httpRequestUtils.doPost("", ResponseVO.class, builderUrlParams(appId, callbackCommand),
jsonBody, null);
return responseVO;
}catch (Exception e){
logger.error("callback 之前 回调{} : {}出现异常 {} ",callbackCommand , appId, e.getMessage());
return ResponseVO.successResponse();
}
}
public Map builderUrlParams(Integer appId, String command) {
Map map = new HashMap();
map.put("appId", appId);
map.put("command", command);
return map;
}
}

View File

@@ -0,0 +1,21 @@
package com.lld.im.service.utils;
/**
* @author: Chackylee
**/
public class ConversationIdGenerate {
//A|B
//B A
public static String generateP2PId(String fromId,String toId){
int i = fromId.compareTo(toId);
if(i < 0){
return toId+"|"+fromId;
}else if(i > 0){
return fromId+"|"+toId;
}
throw new RuntimeException("");
}
}

View File

@@ -0,0 +1,102 @@
package com.lld.im.service.utils;
import com.alibaba.fastjson.JSONObject;
import com.lld.im.codec.pack.group.AddGroupMemberPack;
import com.lld.im.codec.pack.group.RemoveGroupMemberPack;
import com.lld.im.codec.pack.group.UpdateGroupMemberPack;
import com.lld.im.common.ClientType;
import com.lld.im.common.enums.command.Command;
import com.lld.im.common.enums.command.GroupEventCommand;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.service.group.model.req.GroupMemberDto;
import com.lld.im.service.group.service.ImGroupMemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Component
public class GroupMessageProducer {
@Autowired
MessageProducer messageProducer;
@Autowired
ImGroupMemberService imGroupMemberService;
public void producer(String userId, Command command, Object data,
ClientInfo clientInfo){
JSONObject o = (JSONObject) JSONObject.toJSON(data);
String groupId = o.getString("groupId");
List<String> groupMemberId = imGroupMemberService
.getGroupMemberId(groupId, clientInfo.getAppId());
if(command.equals(GroupEventCommand.ADDED_MEMBER)){
//发送给管理员和被加入人本身
List<GroupMemberDto> groupManager = imGroupMemberService.getGroupManager(groupId, clientInfo.getAppId());
AddGroupMemberPack addGroupMemberPack
= o.toJavaObject(AddGroupMemberPack.class);
List<String> members = addGroupMemberPack.getMembers();
for (GroupMemberDto groupMemberDto : groupManager) {
if(clientInfo.getClientType() != ClientType.WEBAPI.getCode() && groupMemberDto.getMemberId().equals(userId)){
messageProducer.sendToUserExceptClient(groupMemberDto.getMemberId(),command,data,clientInfo);
}else{
messageProducer.sendToUser(groupMemberDto.getMemberId(),command,data,clientInfo.getAppId());
}
}
for (String member : members) {
if(clientInfo.getClientType() != ClientType.WEBAPI.getCode() && member.equals(userId)){
messageProducer.sendToUserExceptClient(member,command,data,clientInfo);
}else{
messageProducer.sendToUser(member,command,data,clientInfo.getAppId());
}
}
}else if(command.equals(GroupEventCommand.DELETED_MEMBER)){
RemoveGroupMemberPack pack = o.toJavaObject(RemoveGroupMemberPack.class);
String member = pack.getMember();
List<String> members = imGroupMemberService.getGroupMemberId(groupId, clientInfo.getAppId());
members.add(member);
for (String memberId : members) {
if(clientInfo.getClientType() != ClientType.WEBAPI.getCode() && member.equals(userId)){
messageProducer.sendToUserExceptClient(memberId,command,data,clientInfo);
}else{
messageProducer.sendToUser(memberId,command,data,clientInfo.getAppId());
}
}
}else if(command.equals(GroupEventCommand.UPDATED_MEMBER)){
UpdateGroupMemberPack pack =
o.toJavaObject(UpdateGroupMemberPack.class);
String memberId = pack.getMemberId();
List<GroupMemberDto> groupManager = imGroupMemberService.getGroupManager(groupId, clientInfo.getAppId());
GroupMemberDto groupMemberDto = new GroupMemberDto();
groupMemberDto.setMemberId(memberId);
groupManager.add(groupMemberDto);
for (GroupMemberDto member : groupManager) {
if(clientInfo.getClientType() != ClientType.WEBAPI.getCode() && member.equals(userId)){
messageProducer.sendToUserExceptClient(member.getMemberId(),command,data,clientInfo);
}else{
messageProducer.sendToUser(member.getMemberId(),command,data,clientInfo.getAppId());
}
}
}else {
for (String memberId : groupMemberId) {
if(clientInfo.getClientType() != null && clientInfo.getClientType() !=
ClientType.WEBAPI.getCode() && memberId.equals(userId)){
messageProducer.sendToUserExceptClient(memberId,command,
data,clientInfo);
}else{
messageProducer.sendToUser(memberId,command,data,clientInfo.getAppId());
}
}
}
}
}

View File

@@ -0,0 +1,149 @@
package com.lld.im.service.utils;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author: Chackylee
* @description: 消息key生成
**/
public class MessageKeyGenerate {
//标识从2020.1.1开始
private static final long T202001010000 = 1577808000000L;
// private Lock lock = new ReentrantLock();
AtomicReference<Thread> owner = new AtomicReference<>();
private static volatile int rotateId = 0;
private static int rotateIdWidth = 15;
private static int rotateIdMask = 32767;
private static volatile long timeId = 0;
private int nodeId = 0;
private static int nodeIdWidth = 6;
private static int nodeIdMask = 63;
public void setNodeId(int nodeId) {
this.nodeId = nodeId;
}
/**
* ID = timestamp(43) + nodeId(6) + rotateId(15)
*
*/
public synchronized long generateId() throws Exception {
// lock.lock();
this.lock();
rotateId = rotateId + 1;
long id = System.currentTimeMillis() - T202001010000;
//不同毫秒数生成的id要重置timeId和自选次数
if (id > timeId) {
timeId = id;
rotateId = 1;
} else if (id == timeId) {
//表示是同一毫秒的请求
if (rotateId == rotateIdMask) {
//一毫秒只能发送32768到这里表示当前毫秒数已经超过了
while (id <= timeId) {
//重新给id赋值
id = System.currentTimeMillis() - T202001010000;
}
this.unLock();
return generateId();
}
}
id <<= nodeIdWidth;
id += (nodeId & nodeIdMask);
id <<= rotateIdWidth;
id += rotateId;
// lock.unlock();
this.unLock();
return id;
}
public static int getSharding(long mid) {
Calendar calendar = Calendar.getInstance();
mid >>= nodeIdWidth;
mid >>= rotateIdWidth;
calendar.setTime(new Date(T202001010000 + mid));
int month = calendar.get(Calendar.MONTH);
int year = calendar.get(Calendar.YEAR);
year %= 3;
return (year * 12 + month);
}
public static long getMsgIdFromTimestamp(long timestamp) {
long id = timestamp - T202001010000;
id <<= rotateIdWidth;
id <<= nodeIdWidth;
return id;
}
public void lock() {
Thread cur = Thread.currentThread();
//lock函数将owner设置为当前线程并且预测原来的值为空。
// unlock函数将owner设置为null并且预测值为当前线程。
// 当有第二个线程调用lock操作时由于owner值不为空导致循环
//一直被执行直至第一个线程调用unlock函数将owner设置为null第二个线程才能进入临界区。
while (!owner.compareAndSet(null, cur)){
}
}
public void unLock() {
Thread cur = Thread.currentThread();
owner.compareAndSet(cur, null);
}
public static void main(String[] args) throws Exception {
// try {
// Calendar calendar = Calendar.getInstance();
// MessageKeyGenerate messageKeyGenerate = new MessageKeyGenerate();
// long msgIdFromTimestamp = getMsgIdFromTimestamp(1678459712000L);
// System.out.println(getSharding(msgIdFromTimestamp));
// } catch (Exception e) {
//
// }
MessageKeyGenerate messageKeyGenerate = new MessageKeyGenerate();
for (int i = 0; i < 10; i++) {
long l = messageKeyGenerate.generateId();
System.out.println(l);
}
// MessageKeyGenerate messageKeyGenerate = new MessageKeyGenerate();
// long l = messageKeyGenerate.generateId();
// System.out.println("生成了一个id" + l);
// int sharding = getSharding(l);
// System.out.println("解密id的时间戳" + sharding);
//im_message_history_12
//10000 10001
//0 1
long msgIdFromTimestamp = getMsgIdFromTimestamp(1734529845000L);
int sharding = getSharding(msgIdFromTimestamp);
System.out.println(sharding);
}
}

View File

@@ -0,0 +1,117 @@
package com.lld.im.service.utils;
import com.alibaba.fastjson.JSONObject;
import com.lld.im.codec.proto.MessagePack;
import com.lld.im.common.ClientType;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.command.Command;
import com.lld.im.common.model.ClientInfo;
import com.lld.im.common.model.UserSession;
import jdk.nashorn.internal.scripts.JO;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Service
public class MessageProducer {
private static Logger logger = LoggerFactory.getLogger(MessageProducer.class);
@Autowired
RabbitTemplate rabbitTemplate;
@Autowired
UserSessionUtils userSessionUtils;
private String queueName = Constants.RabbitConstants.MessageService2Im;
public boolean sendMessage(UserSession session,Object msg){
try {
logger.info("send message == " + msg);
rabbitTemplate.convertAndSend(queueName,session.getBrokerId()+"",msg);
return true;
}catch (Exception e){
logger.error("send error :" + e.getMessage());
return false;
}
}
//包装数据调用sendMessage
public boolean sendPack(String toId, Command command,Object msg,UserSession session){
MessagePack messagePack = new MessagePack();
messagePack.setCommand(command.getCommand());
messagePack.setToId(toId);
messagePack.setClientType(session.getClientType());
messagePack.setAppId(session.getAppId());
messagePack.setImei(session.getImei());
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(msg));
messagePack.setData(jsonObject);
String body = JSONObject.toJSONString(messagePack);
return sendMessage(session, body);
}
//发送给所有端的方法
public List<ClientInfo> sendToUser(String toId,Command command,Object data,Integer appId){
List<UserSession> userSession
= userSessionUtils.getUserSession(appId, toId);
List<ClientInfo> list = new ArrayList<>();
for (UserSession session : userSession) {
boolean b = sendPack(toId, command, data, session);
if(b){
list.add(new ClientInfo(session.getAppId(),session.getClientType(),session.getImei()));
}
}
return list;
}
public void sendToUser(String toId, Integer clientType,String imei, Command command,
Object data, Integer appId){
if(clientType != null && StringUtils.isNotBlank(imei)){
ClientInfo clientInfo = new ClientInfo(appId, clientType, imei);
sendToUserExceptClient(toId,command,data,clientInfo);
}else{
sendToUser(toId,command,data,appId);
}
}
//发送给某个用户的指定客户端
public void sendToUser(String toId, Command command
, Object data, ClientInfo clientInfo){
UserSession userSession = userSessionUtils.getUserSession(clientInfo.getAppId(), toId, clientInfo.getClientType(),
clientInfo.getImei());
sendPack(toId,command,data,userSession);
}
private boolean isMatch(UserSession sessionDto, ClientInfo clientInfo) {
return Objects.equals(sessionDto.getAppId(), clientInfo.getAppId())
&& Objects.equals(sessionDto.getImei(), clientInfo.getImei())
&& Objects.equals(sessionDto.getClientType(), clientInfo.getClientType());
}
//发送给除了某一端的其他端
public void sendToUserExceptClient(String toId, Command command
, Object data, ClientInfo clientInfo){
List<UserSession> userSession = userSessionUtils
.getUserSession(clientInfo.getAppId(),
toId);
for (UserSession session : userSession) {
if(!isMatch(session,clientInfo)){
sendPack(toId,command,data,session);
}
}
}
}

View File

@@ -0,0 +1,73 @@
package com.lld.im.service.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* @description 共享线程池
* @author chackylee
* @param
* @return
*/
@Service
public class ShareThreadPool {
private Logger logger = LoggerFactory.getLogger(ShareThreadPool.class);
private final ThreadPoolExecutor threadPoolExecutor;
{
final AtomicInteger tNum = new AtomicInteger(0);
threadPoolExecutor = new ThreadPoolExecutor(8, 8, 120, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2 << 20), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("SHARE-Processor-" + tNum.getAndIncrement());
return t;
}
});
}
private AtomicLong ind = new AtomicLong(0);
public void submit(Runnable r) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
ind.incrementAndGet();
threadPoolExecutor.submit(() -> {
long start = System.currentTimeMillis();
try {
r.run();
} catch (Exception e) {
logger.error("ShareThreadPool_ERROR", e);
} finally {
long end = System.currentTimeMillis();
long dur = end - start;
long andDecrement = ind.decrementAndGet();
if (dur > 1000) {
logger.warn("ShareThreadPool executed taskDone,remanent num = {},slow task fatal warning,costs time = {},stack: {}", andDecrement, dur, stackTrace);
} else if (dur > 300) {
logger.warn("ShareThreadPool executed taskDone,remanent num = {},slow task warning: {},costs time = {},", andDecrement,r, dur);
} else {
logger.debug("ShareThreadPool executed taskDone,remanent num = {}", andDecrement);
}
}
});
}
}

View File

@@ -0,0 +1,175 @@
package com.lld.im.service.utils;
import cn.hutool.core.date.SystemClock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author: Chackylee
* @description:
**/
@Slf4j
public class SnowflakeIdWorker {
/**
* 初始偏移时间戳
*/
private static final long OFFSET = 1546300800L;
/**
* 机器id (0~15 保留 16~31作为备份机器)
*/
private static long WORKER_ID;
/**
* 机器id所占位数 (5bit, 支持最大机器数 2^5 = 32)
*/
private static final long WORKER_ID_BITS = 5L;
/**
* 自增序列所占位数 (16bit, 支持最大每秒生成 2^16 = 65536)
*/
private static final long SEQUENCE_ID_BITS = 16L;
/**
* 机器id偏移位数
*/
private static final long WORKER_SHIFT_BITS = SEQUENCE_ID_BITS;
/**
* 自增序列偏移位数
*/
private static final long OFFSET_SHIFT_BITS = SEQUENCE_ID_BITS + WORKER_ID_BITS;
/**
* 机器标识最大值 (2^5 / 2 - 1 = 15)
*/
private static final long WORKER_ID_MAX = ((1 << WORKER_ID_BITS) - 1) >> 1;
/**
* 备份机器ID开始位置 (2^5 / 2 = 16)
*/
private static final long BACK_WORKER_ID_BEGIN = (1 << WORKER_ID_BITS) >> 1;
/**
* 自增序列最大值 (2^16 - 1 = 65535)
*/
private static final long SEQUENCE_MAX = (1 << SEQUENCE_ID_BITS) - 1;
/**
* 发生时间回拨时容忍的最大回拨时间 (秒)
*/
private static final long BACK_TIME_MAX = 1L;
/**
* 上次生成ID的时间戳 (秒)
*/
private static long lastTimestamp = 0L;
/**
* 当前秒内序列 (2^16)
*/
private static long sequence = 0L;
/**
* 备份机器上次生成ID的时间戳 (秒)
*/
private static long lastTimestampBak = 0L;
/**
* 备份机器当前秒内序列 (2^16)
*/
private static long sequenceBak = 0L;
//==============================Constructors====================
/**
* 构造函数
*
* @param workerId 工作ID (0~31)
*/
public SnowflakeIdWorker(long workerId) {
if (workerId < 0 || workerId > WORKER_ID_MAX) {
throw new IllegalArgumentException(String.format("cmallshop.workerId范围: 0 ~ %d 目前: %d", WORKER_ID_MAX, workerId));
}
WORKER_ID = workerId;
}
// ==============================Methods=================================
public static long nextId() {
return nextId(SystemClock.now() / 1000);
}
/**
* 主机器自增序列
*
* @param timestamp 当前Unix时间戳
* @return long
*/
private static synchronized long nextId(long timestamp) {
// 时钟回拨检查
if (timestamp < lastTimestamp) {
// 发生时钟回拨
log.warn("时钟回拨, 启用备份机器ID: now: [{}] last: [{}]", timestamp, lastTimestamp);
return nextIdBackup(timestamp);
}
// 开始下一秒
if (timestamp != lastTimestamp) {
lastTimestamp = timestamp;
sequence = 0L;
}
if (0L == (++sequence & SEQUENCE_MAX)) {
// 秒内序列用尽
// log.warn("秒内[{}]序列用尽, 启用备份机器ID序列", timestamp);
sequence--;
return nextIdBackup(timestamp);
}
return ((timestamp - OFFSET) << OFFSET_SHIFT_BITS) | (WORKER_ID << WORKER_SHIFT_BITS) | sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
*
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 备份机器自增序列
* @param timestamp timestamp 当前Unix时间戳
* @return long
*/
private static long nextIdBackup(long timestamp) {
if (timestamp < lastTimestampBak) {
if (lastTimestampBak - SystemClock.now() / 1000 <= BACK_TIME_MAX) {
timestamp = lastTimestampBak;
} else {
throw new RuntimeException(String.format("时钟回拨: now: [%d] last: [%d]", timestamp, lastTimestampBak));
}
}
if (timestamp != lastTimestampBak) {
lastTimestampBak = timestamp;
sequenceBak = 0L;
}
if (0L == (++sequenceBak & SEQUENCE_MAX)) {
// 秒内序列用尽
// logger.warn("秒内[{}]序列用尽, 备份机器ID借取下一秒序列", timestamp);
return nextIdBackup(timestamp + 1);
}
return ((timestamp - OFFSET) << OFFSET_SHIFT_BITS) | ((WORKER_ID ^ BACK_WORKER_ID_BEGIN) << WORKER_SHIFT_BITS) | sequenceBak;
}
/**
* 返回以毫秒为单位的当前时间
*
* @return 当前时间(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
}

View File

@@ -0,0 +1,66 @@
package com.lld.im.service.utils;
import com.alibaba.fastjson.JSONObject;
import com.lld.im.common.constant.Constants;
import com.lld.im.common.enums.ImConnectStatusEnum;
import com.lld.im.common.model.UserSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Component
public class UserSessionUtils {
public Object get;
@Autowired
StringRedisTemplate stringRedisTemplate;
//1.获取用户所有的session
public List<UserSession> getUserSession(Integer appId,String userId){
String userSessionKey = appId + Constants.RedisConstants.UserSessionConstants
+ userId;
Map<Object, Object> entries =
stringRedisTemplate.opsForHash().entries(userSessionKey);
List<UserSession> list = new ArrayList<>();
Collection<Object> values = entries.values();
for (Object o : values){
String str = (String) o;
UserSession session =
JSONObject.parseObject(str, UserSession.class);
if(session.getConnectState() == ImConnectStatusEnum.ONLINE_STATUS.getCode()){
list.add(session);
}
}
return list;
}
//2.获取用户除了本端的session
//1.获取用户所有的session
public UserSession getUserSession(Integer appId,String userId
,Integer clientType,String imei){
String userSessionKey = appId + Constants.RedisConstants.UserSessionConstants
+ userId;
String hashKey = clientType + ":" + imei;
Object o = stringRedisTemplate.opsForHash().get(userSessionKey, hashKey);
UserSession session =
JSONObject.parseObject(o.toString(), UserSession.class);
return session;
}
}

View File

@@ -0,0 +1,28 @@
package com.lld.im.service.utils;
import com.lld.im.common.constant.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
/**
* @description:
* @author: lld
* @version: 1.0
*/
@Service
public class WriteUserSeq {
//redis
//uid friend 10
// group 12
// conversation 123
@Autowired
RedisTemplate redisTemplate;
public void writeUserSeq(Integer appId,String userId,String type,Long seq){
String key = appId + ":" + Constants.RedisConstants.SeqPrefix + ":" + userId;
redisTemplate.opsForHash().put(key,type,seq);
}
}

View File

@@ -0,0 +1,44 @@
package com.lld.im.service.utils;
import com.lld.im.common.constant.Constants;
import org.I0Itec.zkclient.ZkClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author: Chackylee
* @description: Zookeeper 工具
**/
@Component
public class ZKit {
private static Logger logger = LoggerFactory.getLogger(ZKit.class);
@Autowired
private ZkClient zkClient;
/**
* get all TCP server node from zookeeper
*
* @return
*/
public List<String> getAllTcpNode() {
List<String> children = zkClient.getChildren(Constants.ImCoreZkRoot + Constants.ImCoreZkRootTcp);
// logger.info("Query all node =[{}] success.", JSON.toJSONString(children));
return children;
}
/**
* get all WEB server node from zookeeper
*
* @return
*/
public List<String> getAllWebNode() {
List<String> children = zkClient.getChildren(Constants.ImCoreZkRoot + Constants.ImCoreZkRootWeb);
// logger.info("Query all node =[{}] success.", JSON.toJSONString(children));
return children;
}
}

View File

@@ -7,17 +7,96 @@ spring:
url: jdbc:mysql://192.168.2.201:3306/im-core?serverTimezone=UTC&useSSL=false&characterEncoding=UTF8
username: root
redis:
host: 43.139.191.204
port: 6379
database: 8
jedis:
pool:
max-active: 100
max-idle: 100
max-wait: 1000
min-idle: 10
password: dSMIXBQrCBXiHHjk123
rabbitmq:
host: 192.168.2.180
port: 5672
addresses: 192.168.2.180
username: guest
password: guest
# virtual-host:
listener:
simple:
concurrency: 5
max-concurrency: 10
acknowledge-mode: MANUAL
prefetch: 1
publisher-confirms: true
publisher-returns: true
template:
mandatory: true
cache:
connection:
mode: channel
channel:
size: 36
checkout-timeout: 0
application:
name: im-core
# logger 配置
logging:
config: classpath:logback-spring.xml
server:
port: 8000
port: 28000
appConfig:
appId: 10000
privateKey: 123456
zkAddr: 192.168.2.180:2181 # zk连接地址
zkConnectTimeOut: 50000 #zk超时时间
imRouteWay: 3 # 路由策略1轮训 2随机 3hash
consistentHashWay: 1 # 如果选用一致性hash的话具体hash算法 1 TreeMap 2 自定义Map
tcpPort: 19000 # tcp端口
webSocketPort: 29000 # webSocket端口
needWebSocket: true #是否需要开启webSocket
loginModel: 1
messageRecallTimeOut : 1200000000 #消息可撤回时间,单位毫秒
# * 多端同步模式1 只允许一端在线,手机/电脑/web 踢掉除了本client+imel的设备
# * 2 允许手机/电脑的一台设备 + web在线 踢掉除了本client+imel的非web端设备
# * 3 允许手机和电脑单设备 + web 同时在线 踢掉非本client+imel的同端设备
# * 4 允许所有端多设备登录 不踢任何设备
groupMaxMemberCount: 500
sendMessageCheckFriend: false # 发送消息是否校验关系链
sendMessageCheckBlack: false # 发送消息是否校验黑名单
callbackUrl: http://127.0.0.1:8000/callback
modifyUserAfterCallback: false # 用户资料变更之后回调开关
addFriendAfterCallback: false # 添加好友之后回调开关
addFriendBeforeCallback: false # 添加好友之前回调开关
modifyFriendAfterCallback: false # 修改好友之后回调开关
deleteFriendAfterCallback: false # 删除好友之后回调开关
addFriendShipBlackAfterCallback: false #添加黑名单之后回调开关
deleteFriendShipBlackAfterCallback: false #删除黑名单之后回调开关
createGroupAfterCallback: false # 创建群聊之后回调开关
modifyGroupAfterCallback: false # 修改群聊之后回调开关
destroyGroupAfterCallback: false # 解散群聊之后回调开关
deleteGroupMemberAfterCallback: false # 删除群成员之后回调
addGroupMemberAfterCallback: false # 拉人入群之后回调
addGroupMemberBeforeCallback: false # 拉人入群之前回调
sendMessageAfterCallback: false # 发送单聊消息之后
sendMessageBeforeCallback: false # 发送单聊消息之前
sendGroupMessageAfterCallback: false # 发送群聊消息之后
sendGroupMessageBeforeCallback: false # 发送群聊消息之前
offlineMessageCount: 1000 #离线消息存储条数
deleteConversationSyncMode: 1 #1多段同步
mqQueueName: 123
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:mapper/*.xml
@@ -36,3 +115,6 @@ httpclient:
connectionRequestTimeout: 2000
socketTimeout: 5000
staleConnectionCheckEnabled: true
mpp:
entityBasePath: com.lld.im.service.friendship.dao