fix: 对接ai模块
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -34,7 +34,14 @@
|
||||
<artifactId>luohuo-database-mode</artifactId>
|
||||
<version>${luohuo-project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.luohuo.flex</groupId>
|
||||
<artifactId>luohuo-sa-token-ext</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.luohuo.basic</groupId>
|
||||
<artifactId>luohuo-tenant</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.luohuo.basic</groupId>
|
||||
<artifactId>luohuo-all</artifactId>
|
||||
|
||||
@@ -2,6 +2,13 @@ package com.luohuo.flex.ai.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.luohuo.basic.jackson.LuohuoJacksonModule;
|
||||
import com.luohuo.basic.utils.SpringUtils;
|
||||
import com.luohuo.flex.ai.core.AiModelFactory;
|
||||
import com.luohuo.flex.ai.core.AiModelFactoryImpl;
|
||||
import com.luohuo.flex.ai.core.model.BaiChuanChatModel;
|
||||
@@ -26,10 +33,22 @@ import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
|
||||
import org.springframework.ai.tokenizer.TokenCountEstimator;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static com.luohuo.basic.utils.DateUtils.DEFAULT_DATE_TIME_FORMAT;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({HulaAiProperties.class,
|
||||
@@ -242,4 +261,47 @@ public class AiAutoConfiguration {
|
||||
private static ToolCallingManager getToolCallingManager() {
|
||||
return SpringUtil.getBean(ToolCallingManager.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring 工具类
|
||||
*
|
||||
* @param applicationContext 上下文
|
||||
*/
|
||||
@Bean
|
||||
public SpringUtils getSpringUtils(ApplicationContext applicationContext) {
|
||||
SpringUtils instance = SpringUtils.getInstance();
|
||||
SpringUtils.setApplicationContext(applicationContext);
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnClass(ObjectMapper.class)
|
||||
@ConditionalOnMissingBean
|
||||
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
|
||||
objectMapper
|
||||
.setLocale(Locale.CHINA)
|
||||
//去掉默认的时间戳格式
|
||||
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
|
||||
// 时区
|
||||
.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()))
|
||||
//Date参数日期格式
|
||||
.setDateFormat(new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT, Locale.CHINA))
|
||||
|
||||
//该特性决定parser是否允许JSON字符串包含非引号控制字符(值小于32的ASCII字符,包含制表符和换行符)。 如果该属性关闭,则如果遇到这些字符,则会抛出异常。JSON标准说明书要求所有控制符必须使用引号,因此这是一个非标准的特性
|
||||
.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true)
|
||||
// 忽略不能转义的字符
|
||||
.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true)
|
||||
//在使用spring boot + jpa/hibernate,如果实体字段上加有FetchType.LAZY,并使用jackson序列化为json串时,会遇到SerializationFeature.FAIL_ON_EMPTY_BEANS异常
|
||||
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
|
||||
//忽略未知字段
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
//单引号处理
|
||||
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
|
||||
// 注册自定义模块
|
||||
objectMapper.registerModule(new LuohuoJacksonModule()).findAndRegisterModules();
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +85,12 @@ public class AiChatConversationController {
|
||||
}
|
||||
|
||||
// ========== 对话管理 ==========
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得对话分页", description = "用于【对话管理】菜单")
|
||||
public R<PageResult<AiChatConversationRespVO>> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
|
||||
if(ObjUtil.isNull(pageReqVO.getUid())){
|
||||
pageReqVO.setUid(ContextUtil.getUid());
|
||||
}
|
||||
PageResult<AiChatConversationDO> pageResult = chatConversationService.getChatConversationPage(pageReqVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(PageResult.empty());
|
||||
|
||||
@@ -14,7 +14,7 @@ import static com.luohuo.basic.utils.TimeUtils.DEFAULT_YEAR_FORMAT;
|
||||
public class AiChatConversationPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "用户编号", example = "1024")
|
||||
private Long userId;
|
||||
private Long uid;
|
||||
|
||||
@Schema(description = "对话标题", example = "你好")
|
||||
private String title;
|
||||
|
||||
@@ -98,5 +98,8 @@ public class AiChatConversationDO extends BaseDO {
|
||||
* 上下文的最大 Message 数量
|
||||
*/
|
||||
private Integer maxContexts;
|
||||
|
||||
/**
|
||||
* 租户id
|
||||
*/
|
||||
private Long tenantId;
|
||||
}
|
||||
|
||||
@@ -103,4 +103,8 @@ public class AiChatMessageDO extends BaseDO {
|
||||
@TableField(typeHandler = LongListTypeHandler.class)
|
||||
private List<Long> segmentIds;
|
||||
|
||||
/**
|
||||
* 租户id
|
||||
*/
|
||||
private Long tenantId;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public interface AiChatConversationMapper extends BaseMapperX<AiChatConversation
|
||||
|
||||
default PageResult<AiChatConversationDO> selectChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
|
||||
return selectPage(pageReqVO, new LambdaQueryWrapperX<AiChatConversationDO>()
|
||||
.eqIfPresent(AiChatConversationDO::getUserId, pageReqVO.getUserId())
|
||||
.eq(AiChatConversationDO::getUserId, pageReqVO.getUid())
|
||||
.likeIfPresent(AiChatConversationDO::getTitle, pageReqVO.getTitle())
|
||||
.betweenIfPresent(AiChatConversationDO::getCreateTime, pageReqVO.getCreateTime())
|
||||
.orderByDesc(AiChatConversationDO::getId));
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.luohuo.flex.ai.service.chat;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.luohuo.basic.context.ContextUtil;
|
||||
import com.luohuo.basic.tenant.core.aop.TenantIgnore;
|
||||
import com.luohuo.flex.ai.common.pojo.PageResult;
|
||||
import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessagePageReqVO;
|
||||
import com.luohuo.flex.ai.controller.chat.vo.message.AiChatMessageRespVO;
|
||||
@@ -139,8 +141,9 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<R<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO,
|
||||
Long userId) {
|
||||
public Flux<R<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId) {
|
||||
ContextUtil.setIgnore(true);
|
||||
|
||||
// 1.1 校验对话存在
|
||||
AiChatConversationDO conversation = chatConversationService
|
||||
.validateChatConversationExists(sendReqVO.getConversationId());
|
||||
@@ -195,10 +198,15 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
// 忽略租户,因为 Flux 异步无法透传租户
|
||||
String content = contentBuffer.toString();
|
||||
chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(content));
|
||||
ContextUtil.setIgnore(false);
|
||||
}).doOnError(throwable -> {
|
||||
log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
|
||||
// 忽略租户,因为 Flux 异步无法透传租户
|
||||
}).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)));
|
||||
ContextUtil.setIgnore(false);
|
||||
}).onErrorResume(error -> {
|
||||
ContextUtil.setIgnore(false);
|
||||
return Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR));
|
||||
});
|
||||
}
|
||||
|
||||
private List<AiKnowledgeSegmentSearchRespBO> recallKnowledgeSegment(String content,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.luohuo.flex</groupId>
|
||||
<artifactId>luohuo-ai</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>luohuo-ai-controller</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.luohuo.flex</groupId>
|
||||
<artifactId>luohuo-ai-biz</artifactId>
|
||||
<version>${luohuo-project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -17,7 +17,7 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.luohuo.flex</groupId>
|
||||
<artifactId>luohuo-ai-controller</artifactId>
|
||||
<artifactId>luohuo-ai-biz</artifactId>
|
||||
<version>${luohuo-project.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ luohuo:
|
||||
username: ${NACOS_USERNAME:@nacos.username@}
|
||||
password: ${NACOS_PASSWORD:@nacos.password@}
|
||||
web-port: ${NACOS_WEB_PORT:@nacos.web-port@}
|
||||
local-ip: ${NACOS_LOCAL_IP:@nacos.local-ip@}
|
||||
seata:
|
||||
ip: ${SEATA_IP:@seata.ip@}
|
||||
port: ${SEATA_PORT:@seata.port@}
|
||||
@@ -53,6 +54,7 @@ spring:
|
||||
username: ${luohuo.nacos.username}
|
||||
password: ${luohuo.nacos.password}
|
||||
server-addr: ${luohuo.nacos.ip}:${luohuo.nacos.port}
|
||||
ip: ${luohuo.nacos.local-ip}
|
||||
namespace: ${luohuo.nacos.namespace}
|
||||
metadata: # 元数据,用于权限服务实时获取各个服务的所有接口
|
||||
management.context-path: ${server.servlet.context-path:}${spring.mvc.servlet.path:}${management.endpoints.web.base-path:}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>luohuo-ai-controller</module>
|
||||
<module>luohuo-ai-entity</module>
|
||||
<module>luohuo-ai-facade</module>
|
||||
<module>luohuo-ai-server</module>
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.luohuo.flex.base.service.tenant.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.UUID;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
@@ -216,10 +215,9 @@ public class DefUserServiceImpl extends SuperCacheServiceImpl<DefUserManager, Lo
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean updatePassword(DefUserPasswordUpdateVO data) {
|
||||
// ArgumentAssert.notEmpty(data.getOldPassword(), "请输入旧密码");
|
||||
DefUser user = superManager.getUserByEmail(2, data.getEmail());
|
||||
ArgumentAssert.notNull(user, "用户不存在");
|
||||
ArgumentAssert.equals(user.getId(), ContextUtil.getUid(), "只能修改自己的密码");
|
||||
// ArgumentAssert.notEmpty(data.getOldPassword(), "请输入旧密码");
|
||||
// String oldPassword = SecureUtil.sha256(data.getOldPassword() + user.getSalt());
|
||||
// ArgumentAssert.equals(user.getPassword(), oldPassword, "旧密码错误");
|
||||
CacheKey cacheKey = new CaptchaCacheKeyBuilder().key(data.getEmail(), data.getKey());
|
||||
|
||||
@@ -26,16 +26,16 @@ public class RedisKey {
|
||||
*/
|
||||
public static final String FEED_MEDIA = BASE_KEY + "feedMedia";
|
||||
|
||||
/**
|
||||
* 用户徽章
|
||||
*/
|
||||
public static final String USER_ITEM = BASE_KEY + "userItem";
|
||||
|
||||
/**
|
||||
* 朋友圈权限
|
||||
*/
|
||||
public static final String FEED_TARGET = BASE_KEY + "feedTarget";
|
||||
|
||||
/**
|
||||
* 用户徽章
|
||||
*/
|
||||
public static final String USER_ITEM = BASE_KEY + "userItem";
|
||||
|
||||
/**
|
||||
* DefUser用户信息
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.luohuo.flex.im.core.chat.dao;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.luohuo.flex.im.domain.entity.RoomGroup;
|
||||
import com.luohuo.flex.im.core.chat.mapper.RoomGroupMapper;
|
||||
import com.luohuo.flex.im.domain.vo.response.GroupResp;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
@@ -30,6 +31,10 @@ public class RoomGroupDao extends ServiceImpl<RoomGroupMapper, RoomGroup> {
|
||||
.one();
|
||||
}
|
||||
|
||||
public GroupResp getByRoomIdIgnoreDel(Long roomId) {
|
||||
return baseMapper.getByRoomIdIgnoreDel(roomId);
|
||||
}
|
||||
|
||||
public boolean checkUser(Long uid, Long roomId) {
|
||||
return baseMapper.checkUser(uid,roomId);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.luohuo.flex.im.core.chat.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.luohuo.flex.im.domain.vo.response.GroupResp;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import com.luohuo.flex.im.domain.entity.RoomGroup;
|
||||
|
||||
@@ -15,4 +16,6 @@ import com.luohuo.flex.im.domain.entity.RoomGroup;
|
||||
public interface RoomGroupMapper extends BaseMapper<RoomGroup> {
|
||||
|
||||
boolean checkUser(Long uid, Long roomId);
|
||||
|
||||
GroupResp getByRoomIdIgnoreDel(Long roomId);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.luohuo.flex.im.domain.vo.request.admin.AdminSetReq;
|
||||
import com.luohuo.flex.im.domain.vo.request.contact.ContactAddReq;
|
||||
import com.luohuo.flex.im.domain.vo.request.member.MemberExitReq;
|
||||
import com.luohuo.flex.im.domain.vo.response.GroupResp;
|
||||
import jakarta.validation.Valid;
|
||||
import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq;
|
||||
import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp;
|
||||
@@ -30,6 +31,7 @@ import com.luohuo.flex.im.domain.vo.response.ChatRoomResp;
|
||||
import com.luohuo.flex.im.domain.vo.response.MemberResp;
|
||||
import com.luohuo.flex.im.domain.vo.req.MergeMessageReq;
|
||||
import com.luohuo.flex.model.entity.ws.ChatMemberResp;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -55,7 +57,12 @@ public interface RoomAppService {
|
||||
/**
|
||||
* 获取群组信息
|
||||
*/
|
||||
MemberResp getGroupDetail(Long uid, long roomId);
|
||||
MemberResp getGroupDetail(Long uid, Long roomId);
|
||||
|
||||
/**
|
||||
* 获取群组基础信息
|
||||
*/
|
||||
GroupResp getGroupInfo(Long uid, Long roomId);
|
||||
|
||||
/**
|
||||
* 获取群成员
|
||||
|
||||
@@ -239,7 +239,7 @@ public class ChatServiceImpl implements ChatService {
|
||||
}
|
||||
|
||||
// 2. 如果开启同步功能,那么把最近N天的消息拉回去, 否则获取所有未ack的消息 [要排除屏蔽的房间的消息]
|
||||
if(msgReq.getAsync()){
|
||||
if(true || msgReq.getAsync()){
|
||||
LocalDateTime effectiveStartTime = calculateStartTime(TimeUtils.getTime(LocalDateTime.now().minusDays(14)));
|
||||
messages = messageDao.list(new LambdaQueryWrapper<Message>().in(Message::getRoomId, roomIds)
|
||||
.between(Message::getCreateTime, effectiveStartTime, LocalDateTime.now()));
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.luohuo.flex.im.domain.vo.request.ChatMessageReq;
|
||||
import com.luohuo.flex.im.domain.vo.request.admin.AdminSetReq;
|
||||
import com.luohuo.flex.im.domain.vo.request.member.MemberExitReq;
|
||||
import com.luohuo.flex.im.domain.entity.msg.TextMsgReq;
|
||||
import com.luohuo.flex.im.domain.vo.response.GroupResp;
|
||||
import com.luohuo.flex.model.entity.ws.AdminChangeDTO;
|
||||
import com.luohuo.flex.model.enums.ChatActiveStatusEnum;
|
||||
import com.luohuo.flex.im.domain.vo.request.contact.ContactAddReq;
|
||||
@@ -39,6 +40,7 @@ import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.data.redis.core.ZSetOperations;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -104,6 +106,7 @@ import com.luohuo.flex.im.core.user.service.impl.PushService;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -453,24 +456,6 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户在群里的权限
|
||||
*
|
||||
* @param uid
|
||||
* @return
|
||||
*/
|
||||
public GroupMember verifyGroupPermissions(Long uid, RoomGroup roomGroup) {
|
||||
if (ObjectUtil.isNull(roomGroup)) {
|
||||
throw new RuntimeException("群聊不存在!");
|
||||
}
|
||||
|
||||
GroupMember groupMember = groupMemberDao.getMemberByGroupId(roomGroup.getId(), uid);
|
||||
if (ObjectUtil.isNull(groupMember)) {
|
||||
throw new RuntimeException(StrUtil.format("您不是{}的群成员!", roomGroup.getName()));
|
||||
}
|
||||
return groupMember;
|
||||
}
|
||||
|
||||
/**
|
||||
* 群主,管理员才可以修改
|
||||
*
|
||||
@@ -481,34 +466,55 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
@Override
|
||||
public Boolean updateRoomInfo(Long uid, RoomInfoReq request) {
|
||||
// 1.校验修改权限
|
||||
RoomGroup roomGroup = roomGroupCache.get(request.getId());
|
||||
GroupMember groupMember = verifyGroupPermissions(uid, roomGroup);
|
||||
|
||||
if (GroupRoleEnum.MEMBER.getType().equals(groupMember.getRoleId())) {
|
||||
Triple<RoomGroup, GroupMember, Boolean> permissionCheck = checkGroupPermission(uid, request.getId());
|
||||
if (!permissionCheck.getRight()) {
|
||||
log.warn("用户无权限修改群信息,uid:{}, roomId:{}", uid, request.getId());
|
||||
return false;
|
||||
}
|
||||
RoomGroup roomGroup = permissionCheck.getLeft();
|
||||
|
||||
// 2.修改群信息
|
||||
roomGroup.setAvatar(request.getAvatar());
|
||||
roomGroup.setName(request.getName());
|
||||
roomGroup.setAllowScanEnter(request.getAllowScanEnter());
|
||||
Boolean success = roomService.updateRoomInfo(roomGroup);
|
||||
|
||||
// 3.通知群里所有人群信息修改了
|
||||
if (success) {
|
||||
roomGroupCache.delete(roomGroup.getId());
|
||||
roomGroupCache.evictGroup(roomGroup.getAccount());
|
||||
List<Long> memberUidList = groupMemberCache.getMemberExceptUidList(roomGroup.getRoomId());
|
||||
pushService.sendPushMsg(RoomAdapter.buildRoomGroupChangeWS(roomGroup.getRoomId(), roomGroup.getName(), roomGroup.getAvatar()), memberUidList, uid);
|
||||
}
|
||||
// 3. 通知群里所有人群信息修改了
|
||||
Boolean success = transactionTemplate.execute(status -> {
|
||||
try {
|
||||
boolean updateResult = roomService.updateRoomInfo(roomGroup);
|
||||
if (!updateResult) {
|
||||
status.setRollbackOnly();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 异步清理缓存
|
||||
CompletableFuture.runAsync(() -> {
|
||||
roomGroupCache.delete(roomGroup.getId());
|
||||
roomGroupCache.evictGroup(roomGroup.getAccount());
|
||||
});
|
||||
|
||||
List<Long> memberUidList = groupMemberCache.getMemberExceptUidList(roomGroup.getRoomId());
|
||||
pushService.sendPushMsg(RoomAdapter.buildRoomGroupChangeWS(roomGroup.getRoomId(), roomGroup.getName(), roomGroup.getAvatar()), memberUidList, uid);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
status.setRollbackOnly();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean updateMyRoomInfo(Long uid, RoomMyInfoReq request) {
|
||||
// 1.校验修改权限
|
||||
RoomGroup roomGroup = roomGroupCache.get(request.getId());
|
||||
GroupMember member = verifyGroupPermissions(uid, roomGroup);
|
||||
Triple<RoomGroup, GroupMember, Boolean> permissionCheck = checkGroupPermission(uid, request.getId());
|
||||
if (!permissionCheck.getRight()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RoomGroup roomGroup = permissionCheck.getLeft();
|
||||
GroupMember member = permissionCheck.getMiddle();
|
||||
|
||||
// 2.修改我的信息
|
||||
boolean equals = member.getMyName().equals(StrUtil.isEmpty(request.getMyName()) ? "" : request.getMyName());
|
||||
@@ -540,11 +546,36 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
return contactDao.updateById(contact);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一权限校验方法
|
||||
* @return 返回三元组(roomGroup, groupMember, hasPermission)
|
||||
*/
|
||||
private Triple<RoomGroup, GroupMember, Boolean> checkGroupPermission(Long uid, Long roomId) {
|
||||
RoomGroup roomGroup = roomGroupCache.get(roomId);
|
||||
if (roomGroup == null) {
|
||||
return Triple.of(null, null, false);
|
||||
}
|
||||
|
||||
GroupMember groupMember = groupMemberCache.getMemberDetail(roomId, uid);
|
||||
if (groupMember == null) {
|
||||
return Triple.of(roomGroup, null, false);
|
||||
}
|
||||
|
||||
boolean hasPermission = !GroupRoleEnum.MEMBER.getType().equals(groupMember.getRoleId());
|
||||
return Triple.of(roomGroup, groupMember, hasPermission);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Boolean pushAnnouncement(Long uid, AnnouncementsParam param) {
|
||||
RoomGroup roomGroup = roomGroupCache.get(param.getRoomId());
|
||||
List<Long> uids = roomService.getGroupUsers(roomGroup.getId(), false);
|
||||
// 1. 权限校验
|
||||
Triple<RoomGroup, GroupMember, Boolean> permissionCheck = checkGroupPermission(uid, param.getRoomId());
|
||||
if (!permissionCheck.getRight()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 保存公告
|
||||
List<Long> uids = roomService.getGroupUsers(permissionCheck.getLeft().getId(), false);
|
||||
if (CollUtil.isNotEmpty(uids)) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Announcements announcements = new Announcements();
|
||||
@@ -579,7 +610,14 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
|
||||
@Override
|
||||
public Boolean announcementEdit(Long uid, AnnouncementsParam param) {
|
||||
RoomGroup roomGroup = roomGroupCache.get(param.getRoomId());
|
||||
// 1. 鉴权
|
||||
Triple<RoomGroup, GroupMember, Boolean> permissionCheck = checkGroupPermission(uid, param.getRoomId());
|
||||
if (!permissionCheck.getRight()) {
|
||||
return false;
|
||||
}
|
||||
RoomGroup roomGroup = permissionCheck.getLeft();
|
||||
|
||||
// 2. 置顶公告
|
||||
List<Long> uids = roomService.getGroupUsers(roomGroup.getId(), false);
|
||||
if (CollUtil.isNotEmpty(uids)) {
|
||||
AnnouncementsResp announcement = roomService.getAnnouncement(param.getId());
|
||||
@@ -642,15 +680,14 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
}
|
||||
|
||||
@Override
|
||||
@RedissonLock(prefixKey = "announceDel:", key = "#id")
|
||||
public Boolean announcementDelete(Long uid, Long id) {
|
||||
// 1. 鉴权
|
||||
AnnouncementsResp resp = roomService.getAnnouncement(id);
|
||||
Triple<RoomGroup, GroupMember, Boolean> validation = checkGroupPermission(uid, resp.getRoomId());
|
||||
GroupMember groupMember = validation.getMiddle();
|
||||
|
||||
RoomGroup roomGroup = roomGroupCache.get(resp.getRoomId());
|
||||
GroupMember groupMember = verifyGroupPermissions(uid, roomGroup);
|
||||
|
||||
long count = userBackpackDao.countByUidAndItemId(uid, "HuLa项目贡献者专属徽章");
|
||||
|
||||
long count = userBackpackDao.countByUidAndItemId(uid, DefValConstants.CONTRIBUTOR_ID);
|
||||
if (count == 0 && GroupRoleEnum.MEMBER.getType().equals(groupMember.getRoleId())) {
|
||||
return false;
|
||||
}
|
||||
@@ -716,8 +753,10 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
}
|
||||
|
||||
if (room.getType().equals(RoomTypeEnum.GROUP.getType())) {
|
||||
RoomGroup sourceRoomGroup = roomGroupCache.get(req.getFromRoomId());
|
||||
verifyGroupPermissions(uid, sourceRoomGroup);
|
||||
Triple<RoomGroup, GroupMember, Boolean> permissionCheck = checkGroupPermission(uid, req.getFromRoomId());
|
||||
if(ObjectUtil.isNull(permissionCheck.getMiddle())) {
|
||||
throw new BizException("您不是群成员");
|
||||
}
|
||||
} else {
|
||||
RoomFriend roomFriend = roomFriendCache.get(req.getFromRoomId());
|
||||
if (ObjectUtil.isNull(roomFriend) || !roomFriend.getUid1().equals(uid) && !roomFriend.getUid1().equals(uid)) {
|
||||
@@ -781,7 +820,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemberResp getGroupDetail(Long uid, long roomId) {
|
||||
public MemberResp getGroupDetail(Long uid, Long roomId) {
|
||||
RoomGroup roomGroup = roomGroupCache.get(roomId);
|
||||
Room room = roomCache.get(roomId);
|
||||
AssertUtil.isNotEmpty(roomGroup, "roomId有误");
|
||||
@@ -807,6 +846,11 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupResp getGroupInfo(Long uid, Long roomId) {
|
||||
return roomGroupDao.getByRoomIdIgnoreDel(roomId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChatMemberResp> listMember(MemberReq request) {
|
||||
// 1. 基础校验
|
||||
@@ -870,6 +914,7 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
public void delMember(Long uid, MemberDelReq request) {
|
||||
Room room = roomCache.get(request.getRoomId());
|
||||
AssertUtil.isNotEmpty(room, "房间号有误");
|
||||
AssertUtil.isFalse(DefValConstants.DEF_ROOM_ID.equals(request.getRoomId()), "官方群聊无法移除");
|
||||
RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
|
||||
AssertUtil.isNotEmpty(roomGroup, "房间号有误");
|
||||
GroupMember self = groupMemberDao.getMemberByGroupId(roomGroup.getId(), uid);
|
||||
@@ -887,74 +932,77 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
}
|
||||
|
||||
// 1. 判断被移除的人是否是群主或者管理员 (群主不可以被移除,管理员只能被群主移除)
|
||||
Long removedUid = request.getUid();
|
||||
// 1.1 群主 非法操作
|
||||
AssertUtil.isFalse(groupMemberDao.isLord(roomGroup.getId(), removedUid), GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE, "");
|
||||
// 1.2 管理员 判断是否是群主操作
|
||||
if (groupMemberDao.isManager(roomGroup.getId(), removedUid)) {
|
||||
Boolean isLord = groupMemberDao.isLord(roomGroup.getId(), uid);
|
||||
AssertUtil.isTrue(isLord, GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
|
||||
}
|
||||
// 1.3 普通成员 判断是否有权限操作
|
||||
AssertUtil.isTrue(hasPower(self), GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
|
||||
GroupMember member = groupMemberDao.getMemberByGroupId(roomGroup.getId(), removedUid);
|
||||
AssertUtil.isNotEmpty(member, "用户已经移除");
|
||||
|
||||
// 发送移除事件告知群成员
|
||||
if (transactionTemplate.execute(e -> {
|
||||
groupMemberDao.removeById(member.getId());
|
||||
// 1.5 移除会话
|
||||
contactDao.removeByRoomId(room.getId(), Collections.singletonList(request.getUid()));
|
||||
return true;
|
||||
})) {
|
||||
// 移除群聊缓存
|
||||
CacheKey uKey = PresenceCacheKeyBuilder.userGroupsKey(request.getUid());
|
||||
cachePlusOps.sRem(membersKey, request.getUid());
|
||||
cachePlusOps.sRem(uKey, room.getId());
|
||||
asyncOnline(Arrays.asList(request.getUid()), room.getId(), false);
|
||||
|
||||
// 推送状态到前端
|
||||
List<Long> memberUidList = groupMemberCache.getMemberExceptUidList(roomGroup.getRoomId());
|
||||
if (!memberUidList.contains(request.getUid())) {
|
||||
memberUidList.add(request.getUid());
|
||||
request.getUidList().forEach(removedUid -> {
|
||||
// 1.1 群主 非法操作
|
||||
AssertUtil.isFalse(groupMemberDao.isLord(roomGroup.getId(), removedUid), GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE, "");
|
||||
// 1.2 管理员 判断是否是群主操作
|
||||
if (groupMemberDao.isManager(roomGroup.getId(), removedUid)) {
|
||||
Boolean isLord = groupMemberDao.isLord(roomGroup.getId(), uid);
|
||||
AssertUtil.isTrue(isLord, GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
|
||||
}
|
||||
WsBaseResp<WSMemberChange> ws = MemberAdapter.buildMemberRemoveWS(roomGroup.getRoomId(), (int) (memberNum - 1), Math.toIntExact(cachePlusOps.sCard(PresenceCacheKeyBuilder.onlineGroupMembersKey(room.getId()))), Arrays.asList(member.getUid()), WSMemberChange.CHANGE_TYPE_REMOVE);
|
||||
pushService.sendPushMsg(ws, memberUidList, uid);
|
||||
groupMemberCache.evictMemberList(room.getId());
|
||||
groupMemberCache.evictExceptMemberList(room.getId());
|
||||
groupMemberCache.evictMemberDetail(room.getId(), removedUid);
|
||||
// 1.3 普通成员 判断是否有权限操作
|
||||
AssertUtil.isTrue(hasPower(self), GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE);
|
||||
GroupMember member = groupMemberDao.getMemberByGroupId(roomGroup.getId(), removedUid);
|
||||
AssertUtil.isNotEmpty(member, "用户已经移除");
|
||||
|
||||
long uuid = uidGenerator.getUid();
|
||||
// 保存被删除人的通知
|
||||
noticeService.createNotice(
|
||||
RoomTypeEnum.GROUP,
|
||||
NoticeTypeEnum.GROUP_MEMBER_DELETE,
|
||||
uid,
|
||||
removedUid,
|
||||
uuid,
|
||||
removedUid,
|
||||
roomGroup.getRoomId(),
|
||||
roomGroup.getName()
|
||||
);
|
||||
// 发送移除事件告知群成员
|
||||
if (transactionTemplate.execute(e -> {
|
||||
groupMemberDao.removeById(member.getId());
|
||||
// 1.5 移除会话
|
||||
contactDao.removeByRoomId(room.getId(), Collections.singletonList(removedUid));
|
||||
return true;
|
||||
})) {
|
||||
// 移除群聊缓存
|
||||
CacheKey uKey = PresenceCacheKeyBuilder.userGroupsKey(removedUid);
|
||||
cachePlusOps.sRem(membersKey, removedUid);
|
||||
cachePlusOps.sRem(uKey, room.getId());
|
||||
asyncOnline(Arrays.asList(removedUid), room.getId(), false);
|
||||
|
||||
// 获取所有管理员
|
||||
List<Long> managerIds = groupMemberDao.getGroupUsers(roomGroup.getId(), true);
|
||||
managerIds.forEach(managerId -> noticeService.createNotice(
|
||||
RoomTypeEnum.GROUP,
|
||||
NoticeTypeEnum.GROUP_MEMBER_DELETE,
|
||||
uid,
|
||||
managerId,
|
||||
uuid,
|
||||
removedUid,
|
||||
roomGroup.getRoomId(),
|
||||
roomGroup.getName()
|
||||
));
|
||||
}
|
||||
// 推送状态到前端
|
||||
List<Long> memberUidList = groupMemberCache.getMemberExceptUidList(roomGroup.getRoomId());
|
||||
if (!memberUidList.contains(removedUid)) {
|
||||
memberUidList.add(removedUid);
|
||||
}
|
||||
WsBaseResp<WSMemberChange> ws = MemberAdapter.buildMemberRemoveWS(roomGroup.getRoomId(), (int) (memberNum - 1), Math.toIntExact(cachePlusOps.sCard(PresenceCacheKeyBuilder.onlineGroupMembersKey(room.getId()))), Arrays.asList(member.getUid()), WSMemberChange.CHANGE_TYPE_REMOVE);
|
||||
pushService.sendPushMsg(ws, memberUidList, uid);
|
||||
groupMemberCache.evictMemberList(room.getId());
|
||||
groupMemberCache.evictExceptMemberList(room.getId());
|
||||
groupMemberCache.evictMemberDetail(room.getId(), removedUid);
|
||||
|
||||
long uuid = uidGenerator.getUid();
|
||||
// 保存被删除人的通知
|
||||
noticeService.createNotice(
|
||||
RoomTypeEnum.GROUP,
|
||||
NoticeTypeEnum.GROUP_MEMBER_DELETE,
|
||||
uid,
|
||||
removedUid,
|
||||
uuid,
|
||||
removedUid,
|
||||
roomGroup.getRoomId(),
|
||||
roomGroup.getName()
|
||||
);
|
||||
|
||||
// 获取所有管理员
|
||||
List<Long> managerIds = groupMemberDao.getGroupUsers(roomGroup.getId(),true);
|
||||
managerIds.forEach(managerId -> noticeService.createNotice(
|
||||
RoomTypeEnum.GROUP,
|
||||
NoticeTypeEnum.GROUP_MEMBER_DELETE,
|
||||
uid,
|
||||
managerId,
|
||||
uuid,
|
||||
removedUid,
|
||||
roomGroup.getRoomId(),
|
||||
roomGroup.getName()
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@RedissonLock(key = "#request.roomId")
|
||||
public void addMember(Long uid, MemberAddReq request) {
|
||||
HashSet<Long> inviteUidList = request.getUidList();
|
||||
AssertUtil.isNotEmpty(inviteUidList.contains(DefValConstants.DEF_BOT_ID), "不能拉小管家进群!");
|
||||
// 1. 校验数据
|
||||
Room room = roomCache.get(request.getRoomId());
|
||||
AssertUtil.isNotEmpty(room, "房间号有误");
|
||||
@@ -963,20 +1011,19 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
GroupMember self = groupMemberDao.getMemberByGroupId(roomGroup.getId(), uid);
|
||||
AssertUtil.isNotEmpty(self, "您不是群成员");
|
||||
// 已经进群了的
|
||||
List<Long> memberBatch = groupMemberDao.getMemberBatch(roomGroup.getId(), request.getUidList()).stream().map(GroupMember::getUid).toList();
|
||||
List<Long> memberBatch = groupMemberDao.getMemberBatch(roomGroup.getId(), inviteUidList).stream().map(GroupMember::getUid).toList();
|
||||
// 已经邀请过的数据
|
||||
List<Long> existingUsers = userApplyDao.getExistingUsers(request.getRoomId(), request.getUidList());
|
||||
HashSet<Long> validUidSet = request.getUidList();
|
||||
validUidSet.removeAll(memberBatch);
|
||||
validUidSet.removeAll(existingUsers);
|
||||
List<Long> existingUsers = userApplyDao.getExistingUsers(request.getRoomId(), inviteUidList);
|
||||
inviteUidList.removeAll(memberBatch);
|
||||
inviteUidList.removeAll(existingUsers);
|
||||
|
||||
List<Long> validUids = new ArrayList<>(validUidSet);
|
||||
List<Long> validUids = new ArrayList<>(inviteUidList);
|
||||
if (CollectionUtils.isEmpty(validUids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 创建邀请记录
|
||||
List<UserApply> invites = validUids.stream().map(inviteeUid -> new UserApply(uid, RoomTypeEnum.GROUP.getType(), roomGroup.getRoomId(), inviteeUid, StrUtil.format("{}邀请你加入{}", userSummaryCache.get(uid).getName(), roomGroup.getName()), ApplyStatusEnum.WAIT_APPROVAL.getCode(), UNREAD.getCode(), 0, false)).collect(Collectors.toList());
|
||||
List<UserApply> invites = validUids.stream().map(inviteeUid -> new UserApply(uid, RoomTypeEnum.GROUP.getType(), roomGroup.getRoomId(), inviteeUid, StrUtil.format("{}邀请你加入{}", userSummaryCache.get(uid).getName(), roomGroup.getName()), NoticeStatusEnum.UNTREATED.getStatus(), UNREAD.getCode(), 0, false)).collect(Collectors.toList());
|
||||
transactionTemplate.execute(e -> userApplyDao.saveBatch(invites));
|
||||
|
||||
// 3. 通知被邀请的人进群, 通知时绑定通知id
|
||||
@@ -1122,23 +1169,23 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
@Override
|
||||
@RedissonLock(prefixKey = "addGroup:", key = "#uid")
|
||||
public Long addGroup(Long uid, GroupAddReq request) {
|
||||
Map<Long, SummeryInfoDTO> userMap = userSummaryCache.getBatch(request.getUidList());
|
||||
AssertUtil.isTrue(userMap.size() > 1, "群聊人数应大于2人");
|
||||
Map<Long, SummeryInfoDTO> inviteUserMap = userSummaryCache.getBatch(request.getUidList());
|
||||
inviteUserMap.remove(DefValConstants.DEF_BOT_ID);
|
||||
AssertUtil.isTrue(inviteUserMap.size() > 1, "群聊人数应大于2人");
|
||||
|
||||
userMap.remove(DefValConstants.DEF_BOT_ID);
|
||||
List<Long> uidList = new ArrayList<>(userMap.keySet());
|
||||
List<Long> inviteUidList = new ArrayList<>(inviteUserMap.keySet());
|
||||
AtomicReference<Long> roomIdAtomic = new AtomicReference(0L);
|
||||
|
||||
// 创建群组数据并推送数据到前端
|
||||
if (transactionTemplate.execute(e -> {
|
||||
RoomGroup roomGroup = roomService.createGroupRoom(uid, request);
|
||||
// 批量保存群成员
|
||||
List<GroupMember> groupMembers = RoomAdapter.buildGroupMemberBatch(uidList, roomGroup.getId());
|
||||
List<GroupMember> groupMembers = RoomAdapter.buildGroupMemberBatch(inviteUidList, roomGroup.getId());
|
||||
groupMemberDao.saveBatch(groupMembers);
|
||||
|
||||
// 添加所有人的会话
|
||||
uidList.add(uid);
|
||||
for (Long memberId : uidList) {
|
||||
inviteUidList.add(uid);
|
||||
for (Long memberId : inviteUidList) {
|
||||
contactDao.refreshOrCreate(roomGroup.getRoomId(), memberId);
|
||||
}
|
||||
|
||||
@@ -1154,11 +1201,11 @@ public class RoomAppServiceImpl implements RoomAppService, InitializingBean {
|
||||
// 更新在线缓存
|
||||
CacheKey onlineGroupMembersKey = PresenceCacheKeyBuilder.onlineGroupMembersKey(roomIdAtomic.get());
|
||||
CacheKey gKey = PresenceCacheKeyBuilder.groupMembersKey(roomIdAtomic.get());
|
||||
uidList.forEach(id -> {
|
||||
inviteUidList.forEach(id -> {
|
||||
cachePlusOps.sAdd(gKey, id);
|
||||
cachePlusOps.sAdd(PresenceCacheKeyBuilder.userGroupsKey(id), roomIdAtomic.get());
|
||||
});
|
||||
asyncOnline(uidList, roomIdAtomic.get(), true);
|
||||
asyncOnline(inviteUidList, roomIdAtomic.get(), true);
|
||||
SpringUtils.publishEvent(new GroupMemberAddEvent(this, roomIdAtomic.get(), Math.toIntExact(cachePlusOps.sCard(gKey)), Math.toIntExact(cachePlusOps.sCard(onlineGroupMembersKey)), request.getUidList(), uid));
|
||||
}
|
||||
return roomIdAtomic.get();
|
||||
|
||||
@@ -131,9 +131,13 @@ public class RoomServiceImpl implements RoomService {
|
||||
@Override
|
||||
public AnnouncementsResp getAnnouncement(Long id) {
|
||||
AnnouncementsResp resp = new AnnouncementsResp();
|
||||
BeanUtils.copyProperties(announcementsDao.getById(id), resp);
|
||||
User user = userCache.get(resp.getUid());
|
||||
resp.setUName(user.getName());
|
||||
Announcements announcements = announcementsDao.getById(id);
|
||||
resp.setUid(announcements.getUid());
|
||||
resp.setRoomId(announcements.getRoomId());
|
||||
resp.setTop(announcements.getTop());
|
||||
resp.setContent(announcements.getContent());
|
||||
resp.setCreateTime(announcements.getCreateTime());
|
||||
resp.setId(announcements.getId());
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,13 @@ import com.luohuo.basic.context.ContextUtil;
|
||||
import com.luohuo.basic.utils.SpringUtils;
|
||||
import com.luohuo.basic.utils.TimeUtils;
|
||||
import com.luohuo.flex.im.core.chat.service.cache.GroupMemberCache;
|
||||
import com.luohuo.flex.im.core.chat.service.cache.RoomCache;
|
||||
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
|
||||
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
|
||||
import com.luohuo.flex.im.domain.entity.GroupMember;
|
||||
import com.luohuo.flex.im.domain.entity.Room;
|
||||
import com.luohuo.flex.im.domain.enums.GroupRoleEnum;
|
||||
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
|
||||
import com.luohuo.flex.im.vo.result.tenant.MsgRecallVo;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -31,6 +34,7 @@ import java.util.Objects;
|
||||
@AllArgsConstructor
|
||||
public class RecallMsgHandler extends AbstractMsgHandler<Object> {
|
||||
|
||||
private RoomCache roomCache;
|
||||
private MessageDao messageDao;
|
||||
private UserSummaryCache userSummaryCache;
|
||||
private GroupMemberCache groupMemberCache;
|
||||
@@ -52,8 +56,14 @@ public class RecallMsgHandler extends AbstractMsgHandler<Object> {
|
||||
Long senderUid = msg.getFromUid();
|
||||
Long currentUserUid = ContextUtil.getUid();
|
||||
|
||||
String roleName = "";
|
||||
Room room = roomCache.get(msg.getRoomId());
|
||||
if(room.getType().equals(RoomTypeEnum.GROUP.getType())){
|
||||
GroupMember recallerMember = groupMemberCache.getMemberDetail(msg.getRoomId(), recallerUid);
|
||||
roleName = GroupRoleEnum.get(recallerMember.getRoleId());
|
||||
}
|
||||
|
||||
// 获取撤回者的群成员信息和用户信息
|
||||
GroupMember recallerMember = groupMemberCache.getMemberDetail(msg.getRoomId(), recallerUid);
|
||||
SummeryInfoDTO recallerUserInfo = userSummaryCache.get(recallerUid);
|
||||
|
||||
// 获取被撤回消息发送者的用户信息(用于显示成员名称)
|
||||
@@ -63,7 +73,7 @@ public class RecallMsgHandler extends AbstractMsgHandler<Object> {
|
||||
boolean isRecallerCurrentUser = Objects.equals(recallerUid, currentUserUid);
|
||||
boolean isSenderCurrentUser = Objects.equals(senderUid, currentUserUid);
|
||||
|
||||
String roleName = GroupRoleEnum.get(recallerMember.getRoleId());
|
||||
|
||||
String messageText;
|
||||
|
||||
if (isRecallerCurrentUser) {
|
||||
|
||||
@@ -18,13 +18,13 @@ public class FeedMediaDao extends ServiceImpl<FeedMediaMapper, FeedMedia> {
|
||||
/**
|
||||
* 批量添加朋友圈的资源的数据
|
||||
* @param feedId 朋友圈id
|
||||
* @param urls 素材地址
|
||||
* @param images 素材地址
|
||||
* @param type 0 纯文字 1 图片 2 视频
|
||||
*/
|
||||
public List<FeedMedia> batchSaveMedia(Long feedId, List<String> urls, Integer type){
|
||||
public List<FeedMedia> batchSaveMedia(Long feedId, List<String> images, Integer type){
|
||||
List<FeedMedia> feedMediaList = new ArrayList<>();
|
||||
for (int i = 0; i < urls.size(); i++) {
|
||||
String url = urls.get(i);
|
||||
for (int i = 0; i < images.size(); i++) {
|
||||
String url = images.get(i);
|
||||
FeedMedia feedMedia = new FeedMedia();
|
||||
feedMedia.setSort(i);
|
||||
feedMedia.setFeedId(feedId);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.luohuo.flex.im.core.user.dao;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.luohuo.flex.im.domain.entity.ItemConfig;
|
||||
import com.luohuo.flex.im.core.user.mapper.ItemConfigMapper;
|
||||
@@ -23,9 +22,4 @@ public class ItemConfigDao extends ServiceImpl<ItemConfigMapper, ItemConfig> {
|
||||
.eq(ItemConfig::getType, type)
|
||||
.list();
|
||||
}
|
||||
|
||||
public ItemConfig getByDesc(String desc) {
|
||||
return baseMapper.selectOne(new LambdaQueryWrapper<ItemConfig>()
|
||||
.eq(ItemConfig::getDescribe, desc));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,11 @@ public class NoticeDao extends ServiceImpl<NoticeMapper, Notice> {
|
||||
return wsNotice;
|
||||
}
|
||||
|
||||
public void readNotices(Long uid, List<Long> notices) {
|
||||
public void readNotices(Long uid, Integer type, List<Long> notices) {
|
||||
lambdaUpdate()
|
||||
.set(Notice::getIsRead, READ.getCode())
|
||||
.eq(Notice::getIsRead, UNREAD.getCode())
|
||||
.eq(Notice::getType, type)
|
||||
.in(Notice::getId, notices)
|
||||
.eq(Notice::getReceiverId, uid)
|
||||
.update();
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.luohuo.flex.im.core.user.dao;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.luohuo.flex.im.domain.entity.UserApply;
|
||||
import com.luohuo.flex.im.domain.enums.ApplyDeletedEnum;
|
||||
import com.luohuo.flex.im.domain.enums.ApplyStatusEnum;
|
||||
import com.luohuo.flex.im.domain.enums.NoticeStatusEnum;
|
||||
import com.luohuo.flex.im.core.user.mapper.UserApplyMapper;
|
||||
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -13,7 +13,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.luohuo.flex.im.domain.enums.ApplyStatusEnum.AGREE;
|
||||
import static com.luohuo.flex.im.domain.enums.NoticeStatusEnum.ACCEPTED;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -35,7 +35,7 @@ public class UserApplyDao extends ServiceImpl<UserApplyMapper, UserApply> {
|
||||
public UserApply getFriendApproving(Long uid, Long targetUid, boolean initiator) {
|
||||
return lambdaQuery().eq(UserApply::getUid, uid)
|
||||
.eq(UserApply::getTargetId, targetUid)
|
||||
.eq(UserApply::getStatus, ApplyStatusEnum.WAIT_APPROVAL.getCode())
|
||||
.eq(UserApply::getStatus, NoticeStatusEnum.UNTREATED.getStatus())
|
||||
.eq(UserApply::getType, RoomTypeEnum.FRIEND.getType())
|
||||
.notIn(initiator,UserApply::getDeleted, ApplyDeletedEnum.applyDeleted())
|
||||
.notIn(!initiator,UserApply::getDeleted, ApplyDeletedEnum.targetDeleted())
|
||||
@@ -45,13 +45,13 @@ public class UserApplyDao extends ServiceImpl<UserApplyMapper, UserApply> {
|
||||
public void agree(Long applyId) {
|
||||
lambdaUpdate()
|
||||
.eq(UserApply::getId, applyId)
|
||||
.set(UserApply::getStatus, AGREE.getCode())
|
||||
.set(UserApply::getStatus, ACCEPTED.getStatus())
|
||||
.set(UserApply::getUpdateTime, LocalDateTime.now())
|
||||
.update();
|
||||
}
|
||||
|
||||
public void updateStatus(Long applyId, ApplyStatusEnum statusEnum) {
|
||||
lambdaUpdate().set(UserApply::getStatus, statusEnum.getCode())
|
||||
public void updateStatus(Long applyId, NoticeStatusEnum statusEnum) {
|
||||
lambdaUpdate().set(UserApply::getStatus, statusEnum.getStatus())
|
||||
.set(UserApply::getUpdateTime, LocalDateTime.now())
|
||||
.eq(UserApply::getId,applyId)
|
||||
.update();
|
||||
@@ -67,7 +67,7 @@ public class UserApplyDao extends ServiceImpl<UserApplyMapper, UserApply> {
|
||||
public List<Long> getExistingUsers(Long roomId, HashSet<Long> uidList) {
|
||||
return lambdaQuery()
|
||||
.eq(UserApply::getRoomId, roomId)
|
||||
.eq(UserApply::getStatus, ApplyStatusEnum.WAIT_APPROVAL.getCode())
|
||||
.eq(UserApply::getStatus, NoticeStatusEnum.UNTREATED.getStatus())
|
||||
.in(UserApply::getTargetId, uidList)
|
||||
.list().stream().map(UserApply::getTargetId).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.luohuo.flex.im.core.user.dao;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.luohuo.flex.im.domain.entity.ItemConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.luohuo.flex.im.common.enums.YesOrNoEnum;
|
||||
import com.luohuo.flex.im.domain.entity.UserBackpack;
|
||||
@@ -67,11 +66,10 @@ public class UserBackpackDao extends ServiceImpl<UserBackpackMapper, UserBackpac
|
||||
return lambdaQuery().eq(UserBackpack::getIdempotent, idempotent).one();
|
||||
}
|
||||
|
||||
public long countByUidAndItemId(Long uid, String desc) {
|
||||
ItemConfig itemConfig = itemConfigDao.getByDesc(desc);
|
||||
public long countByUidAndItemId(Long uid, Long itemId) {
|
||||
return baseMapper.selectCount(new LambdaQueryWrapper<UserBackpack>()
|
||||
.eq(UserBackpack::getUid, uid)
|
||||
.eq(UserBackpack::getItemId, itemConfig.getId())
|
||||
.eq(UserBackpack::getItemId, itemId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.luohuo.flex.im.core.user.service;
|
||||
import com.luohuo.flex.im.domain.entity.Notice;
|
||||
import com.luohuo.flex.im.domain.enums.NoticeTypeEnum;
|
||||
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
|
||||
import com.luohuo.flex.im.domain.vo.req.PageBaseReq;
|
||||
import com.luohuo.flex.im.domain.vo.req.NoticeReq;
|
||||
import com.luohuo.flex.im.domain.vo.res.NoticeVO;
|
||||
import com.luohuo.flex.im.domain.vo.res.PageBaseResp;
|
||||
import com.luohuo.flex.model.entity.ws.WSNotice;
|
||||
@@ -44,7 +44,7 @@ public interface NoticeService {
|
||||
/**
|
||||
* 获取用户通知
|
||||
*/
|
||||
PageBaseResp<NoticeVO> getUserNotices(Long userId, PageBaseReq request);
|
||||
PageBaseResp<NoticeVO> getUserNotices(Long userId, NoticeReq request);
|
||||
|
||||
/**
|
||||
* 标记为已读
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import static com.luohuo.flex.im.domain.enums.ApplyReadStatusEnum.UNREAD;
|
||||
import static com.luohuo.flex.im.domain.enums.ApplyStatusEnum.WAIT_APPROVAL;
|
||||
import static com.luohuo.flex.im.domain.enums.NoticeStatusEnum.UNTREATED;
|
||||
|
||||
|
||||
/**
|
||||
@@ -28,7 +28,7 @@ public class FriendAdapter {
|
||||
userApplyNew.setMsg(request.getMsg());
|
||||
userApplyNew.setType(RoomTypeEnum.FRIEND.getType());
|
||||
userApplyNew.setTargetId(request.getTargetUid());
|
||||
userApplyNew.setStatus(WAIT_APPROVAL.getCode());
|
||||
userApplyNew.setStatus(UNTREATED.getStatus());
|
||||
userApplyNew.setReadStatus(UNREAD.getCode());
|
||||
return userApplyNew;
|
||||
}
|
||||
|
||||
@@ -40,9 +40,8 @@ import com.luohuo.flex.im.domain.entity.RoomGroup;
|
||||
import com.luohuo.flex.im.domain.entity.UserApply;
|
||||
import com.luohuo.flex.im.domain.entity.UserFriend;
|
||||
import com.luohuo.flex.im.domain.enums.ApplyDeletedEnum;
|
||||
import com.luohuo.flex.im.domain.enums.ApplyStatusEnum;
|
||||
import com.luohuo.flex.im.domain.enums.GroupRoleEnum;
|
||||
import com.luohuo.flex.im.domain.enums.NoticeStatusEnum;
|
||||
import com.luohuo.flex.im.domain.enums.GroupRoleEnum;
|
||||
import com.luohuo.flex.im.domain.enums.NoticeTypeEnum;
|
||||
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
|
||||
import com.luohuo.flex.im.domain.vo.req.friend.FriendApplyReq;
|
||||
@@ -59,8 +58,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static com.luohuo.flex.im.domain.enums.ApplyStatusEnum.AGREE;
|
||||
import static com.luohuo.flex.im.domain.enums.ApplyStatusEnum.WAIT_APPROVAL;
|
||||
import static com.luohuo.flex.im.domain.enums.NoticeStatusEnum.*;
|
||||
|
||||
/**
|
||||
* 好友申请、群聊邀请
|
||||
@@ -107,7 +105,7 @@ public class ApplyServiceImpl implements ApplyService {
|
||||
// 是否有待审批的申请记录(别人请求自己的)
|
||||
UserApply friendApproving = userApplyDao.getFriendApproving(request.getTargetUid(), uid, false);
|
||||
if (Objects.nonNull(friendApproving)) {
|
||||
handlerApply(uid, new ApplyReq(friendApproving.getId(), AGREE.getCode()));
|
||||
handlerApply(uid, new ApplyReq(friendApproving.getId(), ACCEPTED.getStatus()));
|
||||
return null;
|
||||
}
|
||||
// 申请入库
|
||||
@@ -204,11 +202,11 @@ public class ApplyServiceImpl implements ApplyService {
|
||||
throw new BizException("无效的邀请");
|
||||
}
|
||||
|
||||
if (request.getState().equals(WAIT_APPROVAL.getCode())) {
|
||||
if (request.getState().equals(UNTREATED.getStatus())) {
|
||||
throw new BizException("无效的审批状态");
|
||||
}
|
||||
|
||||
if (!invite.getStatus().equals(WAIT_APPROVAL.getCode())) {
|
||||
if (!invite.getStatus().equals(UNTREATED.getStatus())) {
|
||||
throw new BizException("无效的审批");
|
||||
}
|
||||
|
||||
@@ -217,18 +215,18 @@ public class ApplyServiceImpl implements ApplyService {
|
||||
throw new BizException("通知无法处理");
|
||||
}
|
||||
|
||||
switch (request.getState()){
|
||||
case 0 -> {
|
||||
userApplyDao.updateStatus(request.getApplyId(), ApplyStatusEnum.REJECT);
|
||||
switch (NoticeStatusEnum.get(request.getState())){
|
||||
case REJECTED -> {
|
||||
userApplyDao.updateStatus(request.getApplyId(), NoticeStatusEnum.REJECTED);
|
||||
// 发送通知
|
||||
notice.setStatus(NoticeStatusEnum.REJECTED.getStatus());
|
||||
noticeService.updateNotice(notice);
|
||||
}
|
||||
case 2 -> {
|
||||
case ACCEPTED -> {
|
||||
// 处理加好友
|
||||
invite.setStatus(request.getState());
|
||||
if(invite.getType().equals(RoomTypeEnum.FRIEND.getType())){
|
||||
AssertUtil.equal(invite.getStatus(), AGREE.getCode(), "已同意好友申请");
|
||||
AssertUtil.equal(invite.getStatus(), ACCEPTED.getStatus(), "已同意好友申请");
|
||||
// 同意申请
|
||||
AtomicReference<Long> atomicRoomId = new AtomicReference(0L);
|
||||
AtomicReference<Boolean> atomicIsFromTempSession = new AtomicReference(false);
|
||||
@@ -320,10 +318,10 @@ public class ApplyServiceImpl implements ApplyService {
|
||||
SpringUtils.publishEvent(new GroupMemberAddEvent(this, room.getId(), Math.toIntExact(cachePlusOps.sCard(gKey)), Math.toIntExact(cachePlusOps.sCard(onlineGroupMembersKey)), Arrays.asList(infoUid), uid));
|
||||
}
|
||||
}
|
||||
case 3 -> {
|
||||
case IGNORE -> {
|
||||
checkRecord(request);
|
||||
userApplyDao.updateStatus(request.getApplyId(), ApplyStatusEnum.IGNORE);
|
||||
notice.setStatus(NoticeStatusEnum.IGNORE.getStatus());
|
||||
userApplyDao.updateStatus(request.getApplyId(), IGNORE);
|
||||
notice.setStatus(IGNORE.getStatus());
|
||||
noticeService.updateNotice(notice);
|
||||
}
|
||||
}
|
||||
@@ -354,7 +352,7 @@ public class ApplyServiceImpl implements ApplyService {
|
||||
private UserApply checkRecord(ApplyReq request) {
|
||||
UserApply userApply = userApplyDao.getById(request.getApplyId());
|
||||
AssertUtil.isNotEmpty(userApply, "不存在申请记录");
|
||||
AssertUtil.equal(userApply.getStatus(), WAIT_APPROVAL.getCode(), "对方已是您的好友");
|
||||
AssertUtil.equal(userApply.getStatus(), UNTREATED.getStatus(), "对方已是您的好友");
|
||||
return userApply;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.luohuo.basic.cache.repository.CachePlusOps;
|
||||
import com.luohuo.basic.exception.BizException;
|
||||
import com.luohuo.basic.model.cache.CacheHashKey;
|
||||
import com.luohuo.flex.common.cache.common.FeedMediaRelCacheKeyBuilder;
|
||||
import com.luohuo.flex.im.common.constant.RedisKey;
|
||||
import com.luohuo.flex.common.cache.common.FeedTargetRelCacheKeyBuilder;
|
||||
import com.luohuo.flex.im.domain.vo.req.CursorPageBaseReq;
|
||||
import com.luohuo.flex.im.domain.vo.res.CursorPageBaseResp;
|
||||
import com.luohuo.flex.im.core.chat.service.adapter.MemberAdapter;
|
||||
@@ -68,12 +68,9 @@ public class FeedServiceImpl implements FeedService {
|
||||
CacheHashKey hashKey = FeedMediaRelCacheKeyBuilder.build(feed.getId());
|
||||
CacheResult<List<FeedMedia>> result = cachePlusOps.get(hashKey, t -> feedMediaDao.getMediaByFeedId(feed.getId()));
|
||||
List<FeedMedia> mediaList = result.getValue();
|
||||
|
||||
if(CollUtil.isEmpty(mediaList)){
|
||||
mediaList = feedMediaDao.getMediaByFeedId(feed.getId());
|
||||
cachePlusOps.hSet(hashKey, mediaList);
|
||||
if(CollUtil.isNotEmpty(mediaList)){
|
||||
feedVo.setUrls(mediaList.stream().sorted(Comparator.comparingInt(FeedMedia::getSort)).map(FeedMedia::getUrl).collect(Collectors.toList()));
|
||||
}
|
||||
feedVo.setUrls(mediaList.stream().sorted(Comparator.comparingInt(FeedMedia::getSort)).map(FeedMedia::getUrl).collect(Collectors.toList()));
|
||||
}
|
||||
feedVos.add(feedVo);
|
||||
}
|
||||
@@ -122,7 +119,6 @@ public class FeedServiceImpl implements FeedService {
|
||||
public void saveFeed(FeedParam param, Long uid, Feed feed) {
|
||||
List<Long> pushList = new ArrayList<>();
|
||||
List<FeedTarget> feedTargets = new ArrayList<>();
|
||||
List<FeedMedia> mediaList = new ArrayList<>();
|
||||
switch (FeedPermissionEnum.get(param.getPermission())){
|
||||
case open -> {
|
||||
// 1. 查询所有好友,排除【不让他看我, 他不看我】的好友
|
||||
@@ -169,19 +165,17 @@ public class FeedServiceImpl implements FeedService {
|
||||
switch (FeedEnum.get(param.getMediaType())){
|
||||
case WORD -> log.info("发布了一条纯文字朋友圈~~");
|
||||
case IMAGE, VIDEO -> {
|
||||
List<String> urls = param.getUrls();
|
||||
if (CollUtil.isEmpty(urls)){
|
||||
List<String> images = param.getImages();
|
||||
if (CollUtil.isEmpty(images)){
|
||||
throw new RuntimeException("请至少上传一条素材!");
|
||||
}
|
||||
mediaList = feedMediaDao.batchSaveMedia(feed.getId(), urls, param.getMediaType());
|
||||
feedMediaDao.batchSaveMedia(feed.getId(), images, param.getMediaType());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 缓存权限+素材 告知 pushList 我发布了朋友圈
|
||||
cachePlusOps.hDel(RedisKey.FEED_MEDIA, feed.getId());
|
||||
cachePlusOps.hDel(RedisKey.FEED_TARGET, feed.getId());
|
||||
cachePlusOps.hDel(RedisKey.FEED_MEDIA, feed.getId().toString(), mediaList);
|
||||
cachePlusOps.hDel(RedisKey.FEED_TARGET, feed.getId().toString(), feedTargets);
|
||||
// cachePlusOps.hDel(FeedMediaRelCacheKeyBuilder.build(feed.getId()));
|
||||
// cachePlusOps.hDel(FeedTargetRelCacheKeyBuilder.build(feed.getId()));
|
||||
pushService.sendPushMsg(MemberAdapter.buildFeedPushWS(uid), pushList, uid);
|
||||
}
|
||||
|
||||
@@ -236,8 +230,8 @@ public class FeedServiceImpl implements FeedService {
|
||||
feedDao.removeById(feedId);
|
||||
|
||||
// 2. 清空缓存
|
||||
cachePlusOps.hDel(RedisKey.FEED_TARGET, feedId);
|
||||
cachePlusOps.hDel(RedisKey.FEED_MEDIA, feedId);
|
||||
cachePlusOps.hDel(FeedTargetRelCacheKeyBuilder.build(feedId));
|
||||
cachePlusOps.hDel(FeedMediaRelCacheKeyBuilder.build(feedId));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -284,11 +278,8 @@ public class FeedServiceImpl implements FeedService {
|
||||
|
||||
// 处理朋友圈权限
|
||||
if(feedVo.getPermission().equals(FeedPermissionEnum.partVisible.getType()) || feedVo.getPermission().equals(FeedPermissionEnum.notAnyone.getType())){
|
||||
CacheResult<Object> cacheResult = cachePlusOps.hGet(FeedMediaRelCacheKeyBuilder.build(feedId));
|
||||
CacheResult<Object> cacheResult = cachePlusOps.hGet(FeedTargetRelCacheKeyBuilder.build(feedId), t -> feedTargetDao.selectFeedTargets(feedId));
|
||||
List<FeedTarget> feedTargets = cacheResult.asList();
|
||||
if(CollUtil.isEmpty(feedTargets)){
|
||||
feedTargets = feedTargetDao.selectFeedTargets(feedId);
|
||||
}
|
||||
|
||||
List<Long> taggetList = feedTargets.stream().filter(item -> item.getType().equals(1)).map(FeedTarget::getTargetId).collect(Collectors.toUnmodifiableList());
|
||||
List<Long> userList = feedTargets.stream().filter(item -> item.getType().equals(2)).map(FeedTarget::getTargetId).collect(Collectors.toUnmodifiableList());
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.luohuo.flex.common.constant.DefValConstants;
|
||||
import com.luohuo.flex.im.core.user.service.cache.UserSummaryCache;
|
||||
import com.luohuo.flex.im.domain.dto.SummeryInfoDTO;
|
||||
import com.luohuo.flex.im.domain.enums.ApplyReadStatusEnum;
|
||||
import com.luohuo.flex.im.domain.enums.ApplyStatusEnum;
|
||||
import com.luohuo.flex.im.domain.enums.NoticeStatusEnum;
|
||||
import com.luohuo.flex.model.entity.WSRespTypeEnum;
|
||||
import com.luohuo.flex.model.entity.WsBaseResp;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
@@ -137,7 +137,7 @@ public class FriendServiceImpl implements FriendService, InitializingBean {
|
||||
userApply.setType(type);
|
||||
userApply.setRoomId(roomId);
|
||||
userApply.setTargetId(targetId);
|
||||
userApply.setStatus(ApplyStatusEnum.WAIT_APPROVAL.getCode());
|
||||
userApply.setStatus(NoticeStatusEnum.UNTREATED.getStatus());
|
||||
userApply.setReadStatus(ApplyReadStatusEnum.UNREAD.getCode());
|
||||
userApply.setApplyFor(true);
|
||||
userApplyDao.save(userApply);
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.luohuo.flex.im.domain.entity.Notice;
|
||||
import com.luohuo.flex.im.domain.enums.NoticeStatusEnum;
|
||||
import com.luohuo.flex.im.domain.enums.NoticeTypeEnum;
|
||||
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
|
||||
import com.luohuo.flex.im.domain.vo.req.PageBaseReq;
|
||||
import com.luohuo.flex.im.domain.vo.req.NoticeReq;
|
||||
import com.luohuo.flex.im.domain.vo.res.NoticeVO;
|
||||
import com.luohuo.flex.im.domain.vo.res.PageBaseResp;
|
||||
import com.luohuo.flex.model.entity.WSRespTypeEnum;
|
||||
@@ -89,21 +89,21 @@ public class NoticeServiceImpl implements NoticeService {
|
||||
}
|
||||
}
|
||||
|
||||
private void readNotices(Long uid, IPage<Notice> noticeIPage) {
|
||||
private void readNotices(Long uid, String applyType, IPage<Notice> noticeIPage) {
|
||||
List<Long> notices = noticeIPage.getRecords()
|
||||
.stream().map(Notice::getId)
|
||||
.collect(Collectors.toList());
|
||||
if(CollUtil.isNotEmpty(notices)){
|
||||
noticeDao.readNotices(uid, notices);
|
||||
noticeDao.readNotices(uid, "friend".equals(applyType)? 2: 1, notices);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageBaseResp<NoticeVO> getUserNotices(Long uid, PageBaseReq request) {
|
||||
public PageBaseResp<NoticeVO> getUserNotices(Long uid, NoticeReq request) {
|
||||
IPage<Notice> noticeIPage = noticeDao.getUserNotices(uid, true, request.plusPage());
|
||||
// 将这些通知设为已读
|
||||
if(request.getClick()){
|
||||
readNotices(uid, noticeIPage);
|
||||
readNotices(uid, request.getApplyType(), noticeIPage);
|
||||
}
|
||||
return PageBaseResp.init(noticeIPage, noticeIPage.getRecords().stream().map(notice -> convertToVO(notice)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
on g.id = m.group_id
|
||||
and g.room_id = #{roomId}
|
||||
and m.uid = #{uid}
|
||||
limit 1
|
||||
where g.is_del = 0 limit 1
|
||||
</select>
|
||||
|
||||
<select id="getByRoomIdIgnoreDel" resultType="com.luohuo.flex.im.domain.vo.response.GroupResp">
|
||||
select id as group_id, room_id, account, name, avatar from im_room_group where room_id = #{roomId} limit 1
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.luohuo.basic.base.R;
|
||||
import com.luohuo.basic.context.ContextUtil;
|
||||
import com.luohuo.flex.im.core.user.service.NoticeService;
|
||||
import com.luohuo.flex.im.domain.vo.req.NoticeReadReq;
|
||||
import com.luohuo.flex.im.domain.vo.req.PageBaseReq;
|
||||
import com.luohuo.flex.im.domain.vo.req.NoticeReq;
|
||||
import com.luohuo.flex.im.domain.vo.res.NoticeVO;
|
||||
import com.luohuo.flex.im.domain.vo.res.PageBaseResp;
|
||||
import com.luohuo.flex.model.entity.ws.WSNotice;
|
||||
@@ -24,7 +24,7 @@ public class NoticeController {
|
||||
private NoticeService noticeService;
|
||||
|
||||
@GetMapping("/page")
|
||||
public R<PageBaseResp<NoticeVO>> getNotices(@Valid PageBaseReq request) {
|
||||
public R<PageBaseResp<NoticeVO>> getNotices(@Valid NoticeReq request) {
|
||||
Long uid = ContextUtil.getUid();
|
||||
return R.success(noticeService.getUserNotices(uid, request));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.luohuo.flex.im.domain.vo.req.IdReqVO;
|
||||
import com.luohuo.flex.im.domain.vo.res.IdRespVO;
|
||||
import com.luohuo.flex.im.domain.vo.response.GroupResp;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -47,13 +48,20 @@ public class RoomController {
|
||||
@Resource
|
||||
private RoomAppService roomService;
|
||||
|
||||
@GetMapping("/group")
|
||||
@GetMapping("/group/detail")
|
||||
@Operation(summary ="群组详情")
|
||||
public R<MemberResp> groupDetail(@Valid IdReqVO request) {
|
||||
Long uid = ContextUtil.getUid();
|
||||
return R.success(roomService.getGroupDetail(uid, request.getId()));
|
||||
}
|
||||
|
||||
@GetMapping("/group/info")
|
||||
@Operation(summary ="群组基础信息[没加群的人也可以查]")
|
||||
public R<GroupResp> groupInfo(@Valid IdReqVO request) {
|
||||
Long uid = ContextUtil.getUid();
|
||||
return R.success(roomService.getGroupInfo(uid, request.getId()));
|
||||
}
|
||||
|
||||
@GetMapping("search")
|
||||
@Operation(summary = "查找群聊")
|
||||
public R<List<RoomGroup>> searchGroup(@Valid RoomGroupReq req) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.luohuo.flex.im.controller.user;
|
||||
|
||||
import com.luohuo.flex.im.domain.vo.req.feed.FeedReq;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import com.luohuo.basic.base.R;
|
||||
import com.luohuo.basic.context.ContextUtil;
|
||||
@@ -16,7 +17,6 @@ import jakarta.validation.Valid;
|
||||
import jakarta.validation.groups.Default;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -45,10 +45,10 @@ public class FeedController {
|
||||
return R.success(feedService.pushFeed(ContextUtil.getUid(), param));
|
||||
}
|
||||
|
||||
@GetMapping("getFeedPermission/{feedId}")
|
||||
@GetMapping("getFeedPermission")
|
||||
@Operation(summary = "查看朋友圈权限")
|
||||
public R<FeedPermission> getFeedPermission(@PathVariable("feedId") Long feedId) {
|
||||
return R.success(feedService.getFeedPermission(ContextUtil.getUid(), feedId));
|
||||
public R<FeedPermission> getFeedPermission(FeedReq feedReq) {
|
||||
return R.success(feedService.getFeedPermission(ContextUtil.getUid(), feedReq.getFeedId()));
|
||||
}
|
||||
|
||||
@PostMapping("edit")
|
||||
@@ -57,15 +57,15 @@ public class FeedController {
|
||||
return R.success(feedService.editFeed(ContextUtil.getUid(), param));
|
||||
}
|
||||
|
||||
@PostMapping("del/{feedId}")
|
||||
@PostMapping("del")
|
||||
@Operation(summary = "删除朋友圈")
|
||||
public R<Boolean> delFeed(@PathVariable("feedId") Long feedId) {
|
||||
return R.success(feedService.delFeed(feedId));
|
||||
public R<Boolean> delFeed(@RequestBody FeedReq feedReq) {
|
||||
return R.success(feedService.delFeed(feedReq.getFeedId()));
|
||||
}
|
||||
|
||||
@GetMapping("detail/{feedId}")
|
||||
@GetMapping("detail")
|
||||
@Operation(summary = "用户查看详情")
|
||||
public R<FeedVo> feedDetail(@PathVariable("feedId") Long feedId) {
|
||||
return R.success(feedService.feedDetail(feedId));
|
||||
public R<FeedVo> feedDetail(FeedReq feedReq) {
|
||||
return R.success(feedService.feedDetail(feedReq.getFeedId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.luohuo.flex.im.domain.entity;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.luohuo.basic.base.entity.Entity;
|
||||
import com.luohuo.flex.im.domain.enums.ApplyStatusEnum;
|
||||
import com.luohuo.flex.im.domain.enums.NoticeStatusEnum;
|
||||
import com.luohuo.flex.im.domain.enums.RoomTypeEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -59,7 +59,7 @@ public class UserApply extends Entity<Long> {
|
||||
|
||||
/**
|
||||
* 申请状态
|
||||
* @see ApplyStatusEnum
|
||||
* @see NoticeStatusEnum
|
||||
*/
|
||||
@TableField("status")
|
||||
private Integer status;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.luohuo.flex.im.domain.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author nyh
|
||||
* 申请状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ApplyStatusEnum {
|
||||
|
||||
WAIT_APPROVAL(1, "待审批"),
|
||||
|
||||
AGREE(2, "同意"),
|
||||
|
||||
REJECT(3, "拒绝"),
|
||||
|
||||
IGNORE(4, "忽略");
|
||||
private final Integer code;
|
||||
|
||||
private final String desc;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -34,7 +35,7 @@ public class FeedParam extends OperParam {
|
||||
private Integer mediaType;
|
||||
|
||||
@Schema(description = "发布的图片的url")
|
||||
private List<String> urls;
|
||||
private List<String> images;
|
||||
|
||||
@Schema(description = "发布视频的url")
|
||||
private String videoUrl;
|
||||
@@ -43,7 +44,7 @@ public class FeedParam extends OperParam {
|
||||
* @see FeedPermissionEnum
|
||||
*/
|
||||
@Schema(description = "privacy -> 私密 open -> 公开 partVisible -> 部分可见 notAnyone -> 不给谁看")
|
||||
@Min(value = 0, message = "请选择正确的可见类型")
|
||||
@Pattern(regexp = "^(privacy|open|partVisible|notAnyone)$", message = "请选择正确的可见类型(privacy/open/partVisible/notAnyone)")
|
||||
private String permission;
|
||||
|
||||
@Schema(description = "permission 限制的用户id")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.luohuo.flex.im.domain.vo.request.member;
|
||||
|
||||
import com.luohuo.flex.im.domain.enums.NoticeStatusEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -20,7 +21,9 @@ public class ApplyReq {
|
||||
@Schema(description ="邀请的id")
|
||||
private Long applyId;
|
||||
|
||||
@NotNull(message = "请选择邀请记录")
|
||||
@Schema(description ="0 = 拒绝 2 = 同意 3 = 忽略")
|
||||
/**
|
||||
* @see NoticeStatusEnum
|
||||
*/
|
||||
@NotNull(message = "请选择审批类型")
|
||||
private Integer state;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 移除群成员
|
||||
@@ -23,5 +24,5 @@ public class MemberDelReq {
|
||||
|
||||
@NotNull
|
||||
@Schema(description ="被移除的uid(主动退群填自己)")
|
||||
private Long uid;
|
||||
private List<Long> uidList;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@ public class AnnouncementsResp implements Serializable {
|
||||
@Schema(description = "公告发布人id")
|
||||
private Long uid;
|
||||
|
||||
@Schema(description = "公告发布人")
|
||||
private String uName;
|
||||
|
||||
@Schema(description = "发布内容")
|
||||
private String content;
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 群成员列表的成员信息
|
||||
* @author nyh
|
||||
@@ -14,7 +16,7 @@ import lombok.NoArgsConstructor;
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMemberListResp {
|
||||
public class ChatMemberListResp implements Serializable {
|
||||
@Schema(description ="uid")
|
||||
private Long uid;
|
||||
@Schema(description ="用户名称")
|
||||
|
||||
@@ -6,6 +6,8 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author nyh
|
||||
*/
|
||||
@@ -13,7 +15,7 @@ import lombok.NoArgsConstructor;
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatMessageReadResp {
|
||||
public class ChatMessageReadResp implements Serializable {
|
||||
@Schema(description ="已读或者未读的用户uid")
|
||||
private Long uid;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
@@ -16,7 +17,7 @@ import java.time.LocalDateTime;
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatRoomResp {
|
||||
public class ChatRoomResp implements Serializable {
|
||||
@Schema(description ="会话id")
|
||||
private Long id;
|
||||
@Schema(description ="单聊时对方的id,群聊是groupId")
|
||||
|
||||
@@ -6,6 +6,8 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author nyh
|
||||
*/
|
||||
@@ -13,7 +15,7 @@ import lombok.NoArgsConstructor;
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MemberResp {
|
||||
public class MemberResp implements Serializable {
|
||||
@Schema(description ="群聊id")
|
||||
private Long groupId;
|
||||
@Schema(description ="房间id")
|
||||
|
||||
@@ -145,7 +145,7 @@ public class CaptchaServiceImpl implements CaptchaService {
|
||||
String code = RandomUtil.randomNumbers(6);
|
||||
CacheKey cacheKey = CaptchaCacheKeyBuilder.build(bindEmailReq.getEmail(), bindEmailReq.getTemplateCode());
|
||||
if(cachePlusOps.exists(cacheKey)){
|
||||
return R.success(cachePlusOps.ttl(cacheKey));
|
||||
ArgumentAssert.isFalse(true, "验证码还剩"+cachePlusOps.ttl(cacheKey)+"秒过期,可以继续使用");
|
||||
}
|
||||
|
||||
cachePlusOps.set(cacheKey, code);
|
||||
|
||||
@@ -17,6 +17,8 @@ public interface DefValConstants {
|
||||
Long DEF_ROOM_ID = 1L;
|
||||
/** 内置的群组id */
|
||||
Long DEF_GROUP_ID = 1L;
|
||||
/** 贡献者id */
|
||||
Long CONTRIBUTOR_ID = 6L;
|
||||
/**
|
||||
* 默认的树节点 分隔符
|
||||
*/
|
||||
|
||||
@@ -365,8 +365,10 @@ public class SessionManager {
|
||||
* 清空所有会话
|
||||
*/
|
||||
public void clean() {
|
||||
// 1. 标记服务不可用状态
|
||||
// 0. 标记服务不可用状态
|
||||
setAcceptingNewConnections(false);
|
||||
nacosSessionRegistry.deregisterNode();
|
||||
|
||||
// 1. 收集所有设备信息
|
||||
Map<Long, Set<String>> offlineDevices = new HashMap<>();
|
||||
USER_DEVICE_SESSION_MAP.forEach((uid, deviceMap) -> offlineDevices.put(uid, new HashSet<>(deviceMap.keySet())));
|
||||
@@ -397,8 +399,7 @@ public class SessionManager {
|
||||
USER_DEVICE_SESSION_MAP.clear();
|
||||
|
||||
// 6. 清理路由与节点
|
||||
nacosSessionRegistry.cleanupNodeRoutes();
|
||||
nacosSessionRegistry.deregisterNode();
|
||||
nacosSessionRegistry.cleanupNodeRoutes("");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.luohuo.flex.ws.websocket.nacos;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
|
||||
import com.alibaba.cloud.nacos.NacosServiceManager;
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
@@ -9,6 +10,7 @@ import com.alibaba.nacos.client.naming.utils.CollectionUtils;
|
||||
import com.luohuo.basic.cache.repository.CachePlusOps;
|
||||
import com.luohuo.basic.model.cache.CacheHashKey;
|
||||
import com.luohuo.basic.model.cache.CacheKey;
|
||||
import com.luohuo.flex.common.cache.PresenceCacheKeyBuilder;
|
||||
import com.luohuo.flex.router.RouterCacheKeyBuilder;
|
||||
import com.luohuo.flex.ws.websocket.SessionManager;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
@@ -17,17 +19,24 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.SessionCallback;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Nacos会话注册中心
|
||||
@@ -156,9 +165,10 @@ public class NacosSessionRegistry {
|
||||
/**
|
||||
* 清理节点在Redis中的所有路由信息
|
||||
*/
|
||||
public void cleanupNodeRoutes() {
|
||||
public void cleanupNodeRoutes(String cleanNodeId) {
|
||||
cleanNodeId = StrUtil.isEmpty(cleanNodeId) ? nodeId : cleanNodeId;
|
||||
// 1. 清理节点→设备映射
|
||||
CacheKey cacheKey = RouterCacheKeyBuilder.buildNodeDevices(nodeId);
|
||||
CacheKey cacheKey = RouterCacheKeyBuilder.buildNodeDevices(cleanNodeId);
|
||||
Set<Object> deviceFields = redisTemplate.opsForSet().members(cacheKey.getKey());
|
||||
|
||||
if (!CollectionUtils.isEmpty(deviceFields)) {
|
||||
@@ -169,7 +179,7 @@ public class NacosSessionRegistry {
|
||||
// 清理节点本地映射
|
||||
redisTemplate.delete(cacheKey.getKey());
|
||||
}
|
||||
log.info("节点路由清理完成: nodeId={}, 清理设备数={}", nodeId, deviceFields != null ? deviceFields.size() : 0);
|
||||
log.info("节点路由清理完成: nodeId={}, 清理设备数={}", cleanNodeId, deviceFields != null ? deviceFields.size() : 0);
|
||||
}
|
||||
|
||||
public void deregisterNode() {
|
||||
@@ -202,4 +212,92 @@ public class NacosSessionRegistry {
|
||||
log.error("心跳更新失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getAllActiveNodeIds() {
|
||||
try {
|
||||
List<Instance> instances = namingService.getAllInstances("ws-cluster", "WS_GROUP");
|
||||
return instances.stream()
|
||||
.filter(Instance::isHealthy)
|
||||
.map(instance -> instance.getMetadata().get("nodeId"))
|
||||
.collect(Collectors.toSet());
|
||||
} catch (NacosException e) {
|
||||
log.error("获取活跃节点失败", e);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getAllRedisNodeIds() {
|
||||
// 1. 获取基础key模式
|
||||
String baseKey = RouterCacheKeyBuilder.buildNodeDevices("").getKey();
|
||||
String pattern = baseKey + "*";
|
||||
|
||||
// 2. 使用SCAN命令安全遍历
|
||||
ScanOptions options = ScanOptions.scanOptions()
|
||||
.match(pattern)
|
||||
.count(100)
|
||||
.build();
|
||||
|
||||
Set<String> nodeIds = new HashSet<>();
|
||||
try (Cursor<String> cursor = redisTemplate.scan(options)) {
|
||||
while (cursor.hasNext()) {
|
||||
String key = cursor.next();
|
||||
// 3. 提取纯节点ID
|
||||
if(key.startsWith(baseKey)) {
|
||||
String[] parts = key.split(":");
|
||||
nodeIds.add(parts[parts.length - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodeIds;
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 30000)
|
||||
public void cleanStaleRoutes() {
|
||||
// 1. 获取所有需要对比的ID集合
|
||||
Set<String> activeNodes = getAllActiveNodeIds();
|
||||
Set<String> redisNodes = getAllRedisNodeIds();
|
||||
|
||||
// 2. 计算需要清理的节点ID
|
||||
Set<String> staleNodes = redisNodes.stream()
|
||||
.filter(node -> !activeNodes.contains(node))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 3. 批量清理
|
||||
staleNodes.forEach(node -> {
|
||||
log.info("发现残留节点数据,开始清理: {}", node);
|
||||
try {
|
||||
cleanupNodeRoutes(node);
|
||||
cleanNodeCompletely(node);
|
||||
} catch (Exception e) {
|
||||
log.error("节点清理失败: {}", node, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void cleanNodeCompletely(String nodeId) {
|
||||
// 1. 获取节点所有设备
|
||||
CacheKey nodeDevicesKey = RouterCacheKeyBuilder.buildNodeDevices(nodeId);
|
||||
Set<String> deviceFields = redisTemplate.opsForSet()
|
||||
.members(nodeDevicesKey.getKey())
|
||||
.stream()
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (CollectionUtils.isEmpty(deviceFields)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 准备在线状态清理数据
|
||||
Map<Long, Set<String>> uidToClients = deviceFields.stream()
|
||||
.map(field -> field.split(":"))
|
||||
.filter(parts -> parts.length == 2)
|
||||
.collect(Collectors.groupingBy(
|
||||
parts -> Long.parseLong(parts[0]),
|
||||
Collectors.mapping(parts -> parts[1], Collectors.toSet())
|
||||
));
|
||||
|
||||
// 3. 执行原子化清理
|
||||
uidToClients.forEach((uid, clients) -> clients.forEach(client -> sessionManager.syncOnline(uid, client, false)));
|
||||
log.info("节点完全清理完成: node={}, 设备数={}", nodeId, deviceFields.size());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user