feat: 补充后端API对齐腾讯IM
新增API:
- GET /api/im/admin/users/state - 查询用户在线状态
- POST /api/im/admin/users/kick - 强制用户下线
- POST /api/im/admin/messages/batch-send - 批量发消息
- POST /api/im/admin/messages/read - 管理员设置消息已读
- POST /api/im/admin/messages/import - 导入历史消息
- POST /api/im/friends/check - 校验好友关系
- PUT /api/im/groups/{groupId}/members/{userId}/info - 修改群成员资料
新增字段:
- ImGroupEntity.memberInfo - 群成员资料(JSON)
修复编译import错误
这个提交包含在:
父节点
b25b4746e9
当前提交
3f4d78f175
@ -5,6 +5,7 @@ import com.xuqm.common.security.JwtUtil;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
@ -21,6 +22,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private final JwtUtil jwtUtil;
|
private final JwtUtil jwtUtil;
|
||||||
|
|||||||
@ -113,5 +113,21 @@ public class FriendController {
|
|||||||
return friendIds == null ? List.of() : new ArrayList<>(new LinkedHashSet<>(friendIds));
|
return friendIds == null ? List.of() : new ArrayList<>(new LinkedHashSet<>(friendIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/check")
|
||||||
|
public ResponseEntity<ApiResponse<List<FriendCheckResult>>> checkFriends(
|
||||||
|
@AuthenticationPrincipal String userId,
|
||||||
|
@RequestParam String appId,
|
||||||
|
@RequestBody FriendCheckRequest req) {
|
||||||
|
List<FriendCheckResult> results = new ArrayList<>();
|
||||||
|
for (String friendId : req.friendIds() == null ? List.<String>of() : req.friendIds()) {
|
||||||
|
boolean isFriend = friendRepository.existsByAppIdAndUserIdAndFriendId(appId, userId, friendId)
|
||||||
|
|| friendRepository.existsByAppIdAndUserIdAndFriendId(appId, friendId, userId);
|
||||||
|
results.add(new FriendCheckResult(friendId, isFriend));
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(results));
|
||||||
|
}
|
||||||
|
|
||||||
public record FriendBatchRequest(List<String> friendIds) {}
|
public record FriendBatchRequest(List<String> friendIds) {}
|
||||||
|
public record FriendCheckRequest(List<String> friendIds) {}
|
||||||
|
public record FriendCheckResult(String userId, boolean isFriend) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -199,6 +199,16 @@ public class GroupController {
|
|||||||
groupService.rejectJoinRequests(appId, groupId, req.requestIds(), userId)));
|
groupService.rejectJoinRequests(appId, groupId, req.requestIds(), userId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{groupId}/members/{userId}/info")
|
||||||
|
public ResponseEntity<ApiResponse<ImGroupEntity>> modifyMemberInfo(
|
||||||
|
@PathVariable String groupId,
|
||||||
|
@PathVariable String userId,
|
||||||
|
@RequestBody ModifyMemberInfoRequest req,
|
||||||
|
@AuthenticationPrincipal String operatorId) {
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(
|
||||||
|
groupService.modifyMemberInfo(groupId, userId, req.nickName())));
|
||||||
|
}
|
||||||
|
|
||||||
public record CreateGroupRequest(String name, List<String> memberIds, String groupType) {}
|
public record CreateGroupRequest(String name, List<String> memberIds, String groupType) {}
|
||||||
public record UpdateGroupRequest(String name, String groupType, String announcement) {}
|
public record UpdateGroupRequest(String name, String groupType, String announcement) {}
|
||||||
public record MemberRequest(String userId) {}
|
public record MemberRequest(String userId) {}
|
||||||
@ -206,4 +216,5 @@ public class GroupController {
|
|||||||
public record SetRoleRequest(String userId, String role) {}
|
public record SetRoleRequest(String userId, String role) {}
|
||||||
public record MuteMemberRequest(String userId, long minutes) {}
|
public record MuteMemberRequest(String userId, long minutes) {}
|
||||||
public record RequestBatch(List<String> requestIds) {}
|
public record RequestBatch(List<String> requestIds) {}
|
||||||
|
public record ModifyMemberInfoRequest(String nickName) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,11 +25,18 @@ import com.xuqm.im.service.OperationLogService;
|
|||||||
import com.xuqm.im.service.WebhookConfigService;
|
import com.xuqm.im.service.WebhookConfigService;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||||
|
import org.springframework.messaging.simp.user.SimpUserRegistry;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -49,6 +56,9 @@ public class ImAdminController {
|
|||||||
private final KeywordFilterService keywordFilterService;
|
private final KeywordFilterService keywordFilterService;
|
||||||
private final GlobalMuteService globalMuteService;
|
private final GlobalMuteService globalMuteService;
|
||||||
private final OperationLogService operationLogService;
|
private final OperationLogService operationLogService;
|
||||||
|
private final SimpUserRegistry simpUserRegistry;
|
||||||
|
private final SimpMessagingTemplate messagingTemplate;
|
||||||
|
private final StringRedisTemplate redisTemplate;
|
||||||
|
|
||||||
public ImAdminController(ImAccountRepository accountRepository,
|
public ImAdminController(ImAccountRepository accountRepository,
|
||||||
ImGroupRepository groupRepository,
|
ImGroupRepository groupRepository,
|
||||||
@ -61,7 +71,10 @@ public class ImAdminController {
|
|||||||
WebhookConfigService webhookConfigService,
|
WebhookConfigService webhookConfigService,
|
||||||
KeywordFilterService keywordFilterService,
|
KeywordFilterService keywordFilterService,
|
||||||
GlobalMuteService globalMuteService,
|
GlobalMuteService globalMuteService,
|
||||||
OperationLogService operationLogService) {
|
OperationLogService operationLogService,
|
||||||
|
SimpUserRegistry simpUserRegistry,
|
||||||
|
SimpMessagingTemplate messagingTemplate,
|
||||||
|
StringRedisTemplate redisTemplate) {
|
||||||
this.accountRepository = accountRepository;
|
this.accountRepository = accountRepository;
|
||||||
this.groupRepository = groupRepository;
|
this.groupRepository = groupRepository;
|
||||||
this.messageRepository = messageRepository;
|
this.messageRepository = messageRepository;
|
||||||
@ -74,6 +87,9 @@ public class ImAdminController {
|
|||||||
this.keywordFilterService = keywordFilterService;
|
this.keywordFilterService = keywordFilterService;
|
||||||
this.globalMuteService = globalMuteService;
|
this.globalMuteService = globalMuteService;
|
||||||
this.operationLogService = operationLogService;
|
this.operationLogService = operationLogService;
|
||||||
|
this.simpUserRegistry = simpUserRegistry;
|
||||||
|
this.messagingTemplate = messagingTemplate;
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** List all registered IM users for the given appId. */
|
/** List all registered IM users for the given appId. */
|
||||||
@ -528,6 +544,75 @@ public class ImAdminController {
|
|||||||
operationLogService.list(appId, PageRequest.of(page, size))));
|
operationLogService.list(appId, PageRequest.of(page, size))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/users/state")
|
||||||
|
@PreAuthorize("hasAuthority('ROLE_OPS')")
|
||||||
|
public ResponseEntity<ApiResponse<Map<String, Boolean>>> queryUserState(
|
||||||
|
@RequestParam String userIds) {
|
||||||
|
Map<String, Boolean> result = new LinkedHashMap<>();
|
||||||
|
for (String userId : userIds.split(",")) {
|
||||||
|
String trimmed = userId.trim();
|
||||||
|
if (!trimmed.isBlank()) {
|
||||||
|
result.put(trimmed, simpUserRegistry.getUser(trimmed) != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/users/kick")
|
||||||
|
@PreAuthorize("hasAuthority('ROLE_OPS')")
|
||||||
|
public ResponseEntity<ApiResponse<Void>> kickUsers(
|
||||||
|
@RequestParam String appId,
|
||||||
|
@AuthenticationPrincipal String operatorId,
|
||||||
|
@RequestBody KickRequest req) {
|
||||||
|
for (String userId : req.userIds()) {
|
||||||
|
messagingTemplate.convertAndSendToUser(userId, "/queue/kick",
|
||||||
|
Map.of("type", "KICK", "reason", "管理员强制下线"));
|
||||||
|
redisTemplate.opsForValue().set("im:kick:" + appId + ":" + userId, "1", Duration.ofMinutes(5));
|
||||||
|
}
|
||||||
|
operationLogService.record(appId, operatorId, "KICK_USERS", "ACCOUNT", null,
|
||||||
|
String.join(",", req.userIds()));
|
||||||
|
return ResponseEntity.ok(ApiResponse.ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/messages/batch-send")
|
||||||
|
@PreAuthorize("hasAuthority('ROLE_OPS')")
|
||||||
|
public ResponseEntity<ApiResponse<List<ImMessageEntity>>> batchSendMsg(
|
||||||
|
@RequestParam String appId,
|
||||||
|
@AuthenticationPrincipal String operatorId,
|
||||||
|
@RequestBody BatchSendRequest req) {
|
||||||
|
List<ImMessageEntity> result = new ArrayList<>();
|
||||||
|
for (String toId : req.toIds()) {
|
||||||
|
ImMessageEntity sent = messageService.adminSend(appId, operatorId, toId, req.msgType(), req.content());
|
||||||
|
result.add(sent);
|
||||||
|
}
|
||||||
|
operationLogService.record(appId, operatorId, "BATCH_SEND_MESSAGE", "MESSAGE", null,
|
||||||
|
"count=" + result.size());
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/messages/read")
|
||||||
|
@PreAuthorize("hasAuthority('ROLE_OPS')")
|
||||||
|
public ResponseEntity<ApiResponse<Void>> adminSetMsgRead(
|
||||||
|
@RequestParam String appId,
|
||||||
|
@AuthenticationPrincipal String operatorId,
|
||||||
|
@RequestBody SetMsgReadRequest req) {
|
||||||
|
messageService.adminSetMsgRead(appId, req.userId());
|
||||||
|
operationLogService.record(appId, operatorId, "ADMIN_SET_MSG_READ", "MESSAGE", req.userId(), null);
|
||||||
|
return ResponseEntity.ok(ApiResponse.ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/messages/import")
|
||||||
|
@PreAuthorize("hasAuthority('ROLE_OPS')")
|
||||||
|
public ResponseEntity<ApiResponse<List<ImMessageEntity>>> importMessages(
|
||||||
|
@RequestParam String appId,
|
||||||
|
@AuthenticationPrincipal String operatorId,
|
||||||
|
@RequestBody List<MessageService.ImportMessageRequest> req) {
|
||||||
|
List<ImMessageEntity> result = messageService.importMessages(appId, req);
|
||||||
|
operationLogService.record(appId, operatorId, "IMPORT_MESSAGES", "MESSAGE", null,
|
||||||
|
"count=" + result.size());
|
||||||
|
return ResponseEntity.ok(ApiResponse.success(result));
|
||||||
|
}
|
||||||
|
|
||||||
public record RegisterUserRequest(
|
public record RegisterUserRequest(
|
||||||
String userId,
|
String userId,
|
||||||
String nickname,
|
String nickname,
|
||||||
@ -548,4 +633,7 @@ public class ImAdminController {
|
|||||||
public record MemberRequest(String userId) {}
|
public record MemberRequest(String userId) {}
|
||||||
public record SetRoleRequest(String userId, String role) {}
|
public record SetRoleRequest(String userId, String role) {}
|
||||||
public record MuteMemberRequest(String userId, long minutes) {}
|
public record MuteMemberRequest(String userId, long minutes) {}
|
||||||
|
public record KickRequest(List<String> userIds) {}
|
||||||
|
public record BatchSendRequest(List<String> toIds, ImMessageEntity.MsgType msgType, String content) {}
|
||||||
|
public record SetMsgReadRequest(String userId) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,9 @@ public class ImGroupEntity {
|
|||||||
@Column(columnDefinition = "TEXT")
|
@Column(columnDefinition = "TEXT")
|
||||||
private String announcement;
|
private String announcement;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
private String memberInfo;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
|
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
@ -64,6 +67,9 @@ public class ImGroupEntity {
|
|||||||
public String getAnnouncement() { return announcement; }
|
public String getAnnouncement() { return announcement; }
|
||||||
public void setAnnouncement(String announcement) { this.announcement = announcement; }
|
public void setAnnouncement(String announcement) { this.announcement = announcement; }
|
||||||
|
|
||||||
|
public String getMemberInfo() { return memberInfo; }
|
||||||
|
public void setMemberInfo(String memberInfo) { this.memberInfo = memberInfo; }
|
||||||
|
|
||||||
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
|
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
|
||||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
|||||||
@ -203,4 +203,15 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
|||||||
default long countTodayByAppId(String appId) {
|
default long countTodayByAppId(String appId) {
|
||||||
return countByAppIdAndCreatedAtAfter(appId, LocalDateTime.now().toLocalDate().atStartOfDay());
|
return countByAppIdAndCreatedAtAfter(appId, LocalDateTime.now().toLocalDate().atStartOfDay());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
select m from ImMessageEntity m
|
||||||
|
where m.appId = :appId
|
||||||
|
and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.SINGLE
|
||||||
|
and m.toId = :userId
|
||||||
|
and m.status <> com.xuqm.im.entity.ImMessageEntity$MsgStatus.READ
|
||||||
|
""")
|
||||||
|
List<ImMessageEntity> findUnreadByAppIdAndToId(
|
||||||
|
@Param("appId") String appId,
|
||||||
|
@Param("userId") String userId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,10 @@ import org.springframework.data.domain.PageRequest;
|
|||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -419,6 +421,14 @@ public class ImGroupService {
|
|||||||
try { return objectMapper.writeValueAsString(list); } catch (Exception e) { return "[]"; }
|
try { return objectMapper.writeValueAsString(list); } catch (Exception e) { return "[]"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String toJson(Map<?, ?> map) {
|
||||||
|
try { return objectMapper.writeValueAsString(map); } catch (Exception e) { return "{}"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toJson(Object obj) {
|
||||||
|
try { return objectMapper.writeValueAsString(obj); } catch (Exception e) { return "{}"; }
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> fromJson(String json) {
|
private List<String> fromJson(String json) {
|
||||||
try { return objectMapper.readValue(json, new TypeReference<>() {}); } catch (Exception e) { return new ArrayList<>(); }
|
try { return objectMapper.readValue(json, new TypeReference<>() {}); } catch (Exception e) { return new ArrayList<>(); }
|
||||||
}
|
}
|
||||||
@ -562,6 +572,40 @@ public class ImGroupService {
|
|||||||
return joinRequestRepository.findByAppIdAndGroupId(appId, groupId);
|
return joinRequestRepository.findByAppIdAndGroupId(appId, groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public ImGroupEntity modifyMemberInfo(String groupId, String userId, String nickName) {
|
||||||
|
ImGroupEntity group = get(groupId);
|
||||||
|
List<String> members = memberIds(group);
|
||||||
|
if (!members.contains(userId)) {
|
||||||
|
throw new BusinessException(404, "群成员不存在");
|
||||||
|
}
|
||||||
|
Map<String, Map<String, String>> memberInfoMap = parseMemberInfo(group.getMemberInfo());
|
||||||
|
Map<String, String> info = memberInfoMap.computeIfAbsent(userId, k -> new java.util.HashMap<>());
|
||||||
|
if (nickName != null) {
|
||||||
|
info.put("nickName", nickName);
|
||||||
|
}
|
||||||
|
group.setMemberInfo(toJson(memberInfoMap));
|
||||||
|
return groupRepository.save(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public ImGroupEntity adminModifyMemberInfo(String appId, String groupId, String userId, String nickName) {
|
||||||
|
ImGroupEntity group = get(groupId);
|
||||||
|
ensureAppMatches(group, appId);
|
||||||
|
return modifyMemberInfo(groupId, userId, nickName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Map<String, String>> parseMemberInfo(String memberInfoJson) {
|
||||||
|
try {
|
||||||
|
if (memberInfoJson == null || memberInfoJson.isBlank()) {
|
||||||
|
return new java.util.HashMap<>();
|
||||||
|
}
|
||||||
|
return objectMapper.readValue(memberInfoJson, new com.fasterxml.jackson.core.type.TypeReference<>() {});
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new java.util.HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public ImGroupJoinRequestEntity adminAcceptJoinRequest(String appId, String groupId, String requestId) {
|
public ImGroupJoinRequestEntity adminAcceptJoinRequest(String appId, String groupId, String requestId) {
|
||||||
ImGroupEntity group = get(groupId);
|
ImGroupEntity group = get(groupId);
|
||||||
|
|||||||
@ -19,6 +19,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import com.xuqm.im.repository.ImMessageRepository;
|
import com.xuqm.im.repository.ImMessageRepository;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -556,4 +558,74 @@ public class MessageService {
|
|||||||
protected void dispatchWebhooks(String appId, String callbackEvent, Object payload) {
|
protected void dispatchWebhooks(String appId, String callbackEvent, Object payload) {
|
||||||
webhookDispatchService.dispatch(appId, "message", callbackEvent, payload);
|
webhookDispatchService.dispatch(appId, "message", callbackEvent, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImMessageEntity adminSend(String appId, String fromUserId, String toId, ImMessageEntity.MsgType msgType, String content) {
|
||||||
|
ImMessageEntity message = new ImMessageEntity();
|
||||||
|
message.setId(UUID.randomUUID().toString());
|
||||||
|
message.setAppId(appId);
|
||||||
|
message.setFromUserId(fromUserId);
|
||||||
|
message.setToId(toId);
|
||||||
|
message.setChatType(ImMessageEntity.ChatType.SINGLE);
|
||||||
|
message.setMsgType(msgType);
|
||||||
|
message.setContent(content);
|
||||||
|
message.setStatus(ImMessageEntity.MsgStatus.SENT);
|
||||||
|
message.setCreatedAt(LocalDateTime.now());
|
||||||
|
ImMessageEntity saved = messageRepository.save(message);
|
||||||
|
|
||||||
|
clusterPublisher.publish("/user/" + fromUserId + "/queue/messages", saved);
|
||||||
|
clusterPublisher.publish("/user/" + toId + "/queue/messages", saved);
|
||||||
|
conversationStateService.clearHiddenForUsers(appId, toId, ImMessageEntity.ChatType.SINGLE.name(), List.of(fromUserId, toId));
|
||||||
|
imPushBridge.sendOfflinePushToUsers(
|
||||||
|
appId,
|
||||||
|
List.of(toId),
|
||||||
|
"新消息",
|
||||||
|
saved.getContent(),
|
||||||
|
buildPushPayload(saved)
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatchWebhooks(appId, "message.sent", saved);
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void adminSetMsgRead(String appId, String userId) {
|
||||||
|
List<ImMessageEntity> messages = messageRepository.findUnreadByAppIdAndToId(appId, userId);
|
||||||
|
for (ImMessageEntity message : messages) {
|
||||||
|
message.setStatus(ImMessageEntity.MsgStatus.READ);
|
||||||
|
messageRepository.save(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ImMessageEntity> importMessages(String appId, List<ImportMessageRequest> requests) {
|
||||||
|
List<ImMessageEntity> result = new ArrayList<>();
|
||||||
|
for (ImportMessageRequest req : requests == null ? List.<ImportMessageRequest>of() : requests) {
|
||||||
|
if (req == null || req.fromUserId() == null || req.fromUserId().isBlank()
|
||||||
|
|| req.toId() == null || req.toId().isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ImMessageEntity message = new ImMessageEntity();
|
||||||
|
message.setId(req.messageId() != null && !req.messageId().isBlank()
|
||||||
|
? req.messageId()
|
||||||
|
: UUID.randomUUID().toString());
|
||||||
|
message.setAppId(appId);
|
||||||
|
message.setFromUserId(req.fromUserId());
|
||||||
|
message.setToId(req.toId());
|
||||||
|
message.setChatType(req.chatType() != null ? req.chatType() : ImMessageEntity.ChatType.SINGLE);
|
||||||
|
message.setMsgType(req.msgType() != null ? req.msgType() : ImMessageEntity.MsgType.TEXT);
|
||||||
|
message.setContent(req.content() != null ? req.content() : "");
|
||||||
|
message.setStatus(req.status() != null ? req.status() : ImMessageEntity.MsgStatus.READ);
|
||||||
|
message.setCreatedAt(req.createdAt() != null ? req.createdAt() : LocalDateTime.now());
|
||||||
|
result.add(messageRepository.save(message));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ImportMessageRequest(
|
||||||
|
String messageId,
|
||||||
|
String fromUserId,
|
||||||
|
String toId,
|
||||||
|
ImMessageEntity.ChatType chatType,
|
||||||
|
ImMessageEntity.MsgType msgType,
|
||||||
|
String content,
|
||||||
|
ImMessageEntity.MsgStatus status,
|
||||||
|
LocalDateTime createdAt) {}
|
||||||
}
|
}
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户