docs(server): 添加平台文档总览并实现 IM 服务端 SDK

- 新增 PLATFORM_OVERVIEW.md 文档,包含仓库索引、整体架构、核心概念等
- 实现 XuqmImServerSdk 类,提供完整的 IM 功能接口
- 添加登录认证、消息发送、会话管理、好友关系等核心功能
- 实现群组管理、关键词过滤、全局禁言等高级功能
- 提供配置管理和统计查询等管理功能
这个提交包含在:
XuqmGroup 2026-04-28 17:29:17 +08:00
父节点 ec23b9890b
当前提交 bbf1fd0769
共有 15 个文件被更改,包括 557 次插入4 次删除

查看文件

@ -13,6 +13,8 @@
| [XuqmGroup-RNSDK](./rn-sdk/README.md) | TypeScript / RN 0.76+ | https://xuqinmin.com/xuqinmin12/XuqmGroup-RNSDK | React Native SDK |
| [XuqmGroup-Vue3SDK](./vue3-sdk/README.md) | TypeScript / Vue 3.5 | https://xuqinmin.com/xuqinmin12/XuqmGroup-Vue3SDK | Vue3 Web SDK |
| [XuqmGroup-HarmonySDK](./harmony-sdk/README.md) | ArkTS / HarmonyOS 5 | https://xuqinmin.com/xuqinmin12/XuqmGroup-HarmonySDK | 鸿蒙 SDK |
| [XuqmGroup-ServerSDK-Python](../XuqmGroup-ServerSDK-Python/README.md) | Python 3.11+ | https://xuqinmin.com/xuqinmin12/XuqmGroup-ServerSDK-Python | Python 服务端 SDK |
| [XuqmGroup-ServerSDK-Go](../XuqmGroup-ServerSDK-Go/README.md) | Go 1.22+ | https://xuqinmin.com/xuqinmin12/XuqmGroup-ServerSDK-Go | Go 服务端 SDK |
## 整体架构

查看文件

@ -250,6 +250,161 @@ public final class XuqmImServerSdk {
return response.data();
}
public List<GroupView> searchGroups(String keyword, int size) {
ApiResponse<List<GroupView>> response = request(
"GET",
buildUri("/api/im/admin/groups/search", queryWithSize("keyword", keyword, size)),
null,
authorizedHeaders(),
new TypeReference<>() {}
);
return response.data();
}
public PageResult<ImMessage> searchMessages(
String keyword,
String chatType,
String msgType,
LocalDateTime startTime,
LocalDateTime endTime,
int page,
int size) {
Map<String, String> query = appQuery();
if (keyword != null) {
query.put("keyword", keyword);
}
if (chatType != null) {
query.put("chatType", chatType);
}
if (msgType != null) {
query.put("msgType", msgType);
}
if (startTime != null) {
query.put("startTime", startTime.toInstant(ZoneOffset.UTC).toString());
}
if (endTime != null) {
query.put("endTime", endTime.toInstant(ZoneOffset.UTC).toString());
}
query.put("page", String.valueOf(page));
query.put("size", String.valueOf(size));
ApiResponse<PageResult<ImMessage>> response = request(
"GET",
buildUri("/api/im/admin/messages/search", query),
null,
authorizedHeaders(),
new TypeReference<>() {}
);
return response.data();
}
public List<WebhookConfigView> listWebhookConfigs() {
ApiResponse<List<WebhookConfigView>> response = request(
"GET",
buildUri("/api/im/admin/webhooks", appQuery()),
null,
authorizedHeaders(),
new TypeReference<>() {}
);
return response.data();
}
public WebhookConfigView createWebhookConfig(String url, String secret, Boolean enabled) {
ApiResponse<WebhookConfigView> response = request(
"POST",
buildUri("/api/im/admin/webhooks", appQuery()),
new WebhookConfigRequest(url, secret, enabled),
authorizedHeaders(),
new TypeReference<>() {}
);
return response.data();
}
public WebhookConfigView updateWebhookConfig(String id, String url, String secret, Boolean enabled) {
ApiResponse<WebhookConfigView> response = request(
"PUT",
buildUri("/api/im/admin/webhooks/" + encode(id), appQuery()),
new WebhookConfigRequest(url, secret, enabled),
authorizedHeaders(),
new TypeReference<>() {}
);
return response.data();
}
public void deleteWebhookConfig(String id) {
request(
"DELETE",
buildUri("/api/im/admin/webhooks/" + encode(id), appQuery()),
null,
authorizedHeaders(),
new TypeReference<ApiResponse<Void>>() {}
);
}
public List<KeywordFilterView> listKeywordFilters() {
ApiResponse<List<KeywordFilterView>> response = request(
"GET",
buildUri("/api/im/admin/keyword-filters", appQuery()),
null,
authorizedHeaders(),
new TypeReference<>() {}
);
return response.data();
}
public KeywordFilterView createKeywordFilter(String pattern, String replacement, String action, Boolean enabled) {
ApiResponse<KeywordFilterView> response = request(
"POST",
buildUri("/api/im/admin/keyword-filters", appQuery()),
new KeywordFilterRequest(pattern, replacement, action, enabled),
authorizedHeaders(),
new TypeReference<>() {}
);
return response.data();
}
public KeywordFilterView updateKeywordFilter(String id, String pattern, String replacement, String action, Boolean enabled) {
ApiResponse<KeywordFilterView> response = request(
"PUT",
buildUri("/api/im/admin/keyword-filters/" + encode(id), appQuery()),
new KeywordFilterRequest(pattern, replacement, action, enabled),
authorizedHeaders(),
new TypeReference<>() {}
);
return response.data();
}
public void deleteKeywordFilter(String id) {
request(
"DELETE",
buildUri("/api/im/admin/keyword-filters/" + encode(id), appQuery()),
null,
authorizedHeaders(),
new TypeReference<ApiResponse<Void>>() {}
);
}
public GlobalMuteView getGlobalMute() {
ApiResponse<GlobalMuteView> response = request(
"GET",
buildUri("/api/im/admin/global-mute", appQuery()),
null,
authorizedHeaders(),
new TypeReference<>() {}
);
return response.data();
}
public GlobalMuteView setGlobalMute(boolean enabled) {
ApiResponse<GlobalMuteView> response = request(
"PUT",
buildUri("/api/im/admin/global-mute", Map.of("appId", appId, "enabled", String.valueOf(enabled))),
null,
authorizedHeaders(),
new TypeReference<>() {}
);
return response.data();
}
public List<String> listFriends() {
ApiResponse<List<String>> response = request(
"GET",
@ -916,6 +1071,33 @@ public final class XuqmImServerSdk {
long todayMessages
) {}
public record WebhookConfigView(
String id,
String appId,
String url,
String secret,
boolean enabled,
Long createdAt
) {}
public record KeywordFilterView(
String id,
String appId,
String pattern,
String replacement,
String action,
boolean enabled,
Long createdAt
) {}
public record GlobalMuteView(
String id,
String appId,
boolean enabled,
Long createdAt,
Long updatedAt
) {}
public record HistoryQuery(
String msgType,
String keyword,
@ -964,6 +1146,19 @@ public final class XuqmImServerSdk {
String mentionedUserIds
) {}
public record WebhookConfigRequest(
String url,
String secret,
Boolean enabled
) {}
public record KeywordFilterRequest(
String pattern,
String replacement,
String action,
Boolean enabled
) {}
public record CreateGroupRequest(
String name,
List<String> memberIds,

查看文件

@ -2,13 +2,20 @@ package com.xuqm.im.controller;
import com.xuqm.common.model.ApiResponse;
import com.xuqm.im.entity.ImAccountEntity;
import com.xuqm.im.entity.ImGlobalMuteEntity;
import com.xuqm.im.entity.ImGroupEntity;
import com.xuqm.im.entity.ImMessageEntity;
import com.xuqm.im.entity.KeywordFilterEntity;
import com.xuqm.im.entity.WebhookConfigEntity;
import com.xuqm.im.repository.ImAccountRepository;
import com.xuqm.im.repository.ImGroupRepository;
import com.xuqm.im.repository.ImMessageRepository;
import com.xuqm.im.service.ImAccountService;
import com.xuqm.im.service.ImGroupService;
import com.xuqm.im.service.GlobalMuteService;
import com.xuqm.im.service.KeywordFilterService;
import com.xuqm.im.service.MessageService;
import com.xuqm.im.service.WebhookConfigService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
@ -28,19 +35,28 @@ public class ImAdminController {
private final ImAccountService accountService;
private final ImGroupService groupService;
private final MessageService messageService;
private final WebhookConfigService webhookConfigService;
private final KeywordFilterService keywordFilterService;
private final GlobalMuteService globalMuteService;
public ImAdminController(ImAccountRepository accountRepository,
ImGroupRepository groupRepository,
ImMessageRepository messageRepository,
ImAccountService accountService,
ImGroupService groupService,
MessageService messageService) {
MessageService messageService,
WebhookConfigService webhookConfigService,
KeywordFilterService keywordFilterService,
GlobalMuteService globalMuteService) {
this.accountRepository = accountRepository;
this.groupRepository = groupRepository;
this.messageRepository = messageRepository;
this.accountService = accountService;
this.groupService = groupService;
this.messageService = messageService;
this.webhookConfigService = webhookConfigService;
this.keywordFilterService = keywordFilterService;
this.globalMuteService = globalMuteService;
}
/** List all registered IM users for the given appId. */
@ -101,6 +117,32 @@ public class ImAdminController {
return ResponseEntity.ok(ApiResponse.success(results));
}
/** Fuzzy search groups by id, name, creator or announcement. */
@GetMapping("/groups/search")
public ResponseEntity<ApiResponse<List<ImGroupEntity>>> searchGroups(
@RequestParam String appId,
@RequestParam String keyword,
@RequestParam(defaultValue = "20") int size) {
List<ImGroupEntity> results = groupRepository.searchByKeyword(appId, keyword, PageRequest.of(0, size));
return ResponseEntity.ok(ApiResponse.success(results));
}
/** Search messages across the application. */
@GetMapping("/messages/search")
public ResponseEntity<ApiResponse<Page<ImMessageEntity>>> searchMessages(
@RequestParam String appId,
@RequestParam(required = false) ImMessageEntity.ChatType chatType,
@RequestParam(required = false) ImMessageEntity.MsgType msgType,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) LocalDateTime startTime,
@RequestParam(required = false) LocalDateTime endTime,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(
messageRepository.searchByKeyword(
appId, chatType, msgType, keyword, startTime, endTime, PageRequest.of(page, size))));
}
/** Message statistics for the given appId. */
@GetMapping("/stats")
public ResponseEntity<ApiResponse<Map<String, Object>>> stats(@RequestParam String appId) {
@ -149,6 +191,80 @@ public class ImAdminController {
return ResponseEntity.ok(ApiResponse.ok());
}
@GetMapping("/webhooks")
public ResponseEntity<ApiResponse<List<WebhookConfigEntity>>> listWebhooks(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(webhookConfigService.list(appId)));
}
@PostMapping("/webhooks")
public ResponseEntity<ApiResponse<WebhookConfigEntity>> createWebhook(
@RequestParam String appId,
@RequestBody WebhookConfigRequest req) {
return ResponseEntity.ok(ApiResponse.success(
webhookConfigService.create(appId, req.url(), req.secret(), req.enabled())));
}
@PutMapping("/webhooks/{id}")
public ResponseEntity<ApiResponse<WebhookConfigEntity>> updateWebhook(
@RequestParam String appId,
@PathVariable String id,
@RequestBody WebhookConfigRequest req) {
return ResponseEntity.ok(ApiResponse.success(
webhookConfigService.update(appId, id, req.url(), req.secret(), req.enabled())));
}
@DeleteMapping("/webhooks/{id}")
public ResponseEntity<ApiResponse<Void>> deleteWebhook(
@RequestParam String appId,
@PathVariable String id) {
webhookConfigService.delete(appId, id);
return ResponseEntity.ok(ApiResponse.ok());
}
@GetMapping("/keyword-filters")
public ResponseEntity<ApiResponse<List<KeywordFilterEntity>>> listKeywordFilters(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(keywordFilterService.list(appId)));
}
@PostMapping("/keyword-filters")
public ResponseEntity<ApiResponse<KeywordFilterEntity>> createKeywordFilter(
@RequestParam String appId,
@RequestBody KeywordFilterRequest req) {
return ResponseEntity.ok(ApiResponse.success(
keywordFilterService.add(appId, req.pattern(), req.replacement(), req.action())));
}
@PutMapping("/keyword-filters/{id}")
public ResponseEntity<ApiResponse<KeywordFilterEntity>> updateKeywordFilter(
@RequestParam String appId,
@PathVariable String id,
@RequestBody KeywordFilterRequest req) {
return ResponseEntity.ok(ApiResponse.success(
keywordFilterService.update(appId, id, req.pattern(), req.replacement(), req.action(), req.enabled())));
}
@DeleteMapping("/keyword-filters/{id}")
public ResponseEntity<ApiResponse<Void>> deleteKeywordFilter(
@RequestParam String appId,
@PathVariable String id) {
keywordFilterService.delete(appId, id);
return ResponseEntity.ok(ApiResponse.ok());
}
@GetMapping("/global-mute")
public ResponseEntity<ApiResponse<ImGlobalMuteEntity>> getGlobalMute(@RequestParam String appId) {
return ResponseEntity.ok(ApiResponse.success(globalMuteService.get(appId)));
}
@PutMapping("/global-mute")
public ResponseEntity<ApiResponse<ImGlobalMuteEntity>> setGlobalMute(
@RequestParam String appId,
@RequestParam boolean enabled) {
return ResponseEntity.ok(ApiResponse.success(globalMuteService.setEnabled(appId, enabled)));
}
public record RegisterUserRequest(String userId, String nickname, String avatar) {}
public record CreateGroupRequest(String name, String creatorId, List<String> memberIds) {}
public record WebhookConfigRequest(String url, String secret, Boolean enabled) {}
public record KeywordFilterRequest(String pattern, String replacement, String action, Boolean enabled) {}
}

查看文件

@ -0,0 +1,49 @@
package com.xuqm.im.entity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.xuqm.im.json.EpochMillisLocalDateTimeSerializer;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
@Entity
@Table(name = "im_global_mute")
public class ImGlobalMuteEntity {
@Id
private String id;
@Column(nullable = false, length = 64, unique = true)
private String appId;
@Column(nullable = false)
private boolean enabled;
@Column(nullable = false)
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
private LocalDateTime createdAt;
@Column(nullable = false)
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
private LocalDateTime updatedAt;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getAppId() { return appId; }
public void setAppId(String appId) { this.appId = appId; }
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}

查看文件

@ -1,5 +1,7 @@
package com.xuqm.im.entity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.xuqm.im.json.EpochMillisLocalDateTimeSerializer;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@ -31,6 +33,7 @@ public class KeywordFilterEntity {
private boolean enabled;
@Column(nullable = false)
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
private LocalDateTime createdAt;
public String getId() { return id; }
@ -51,6 +54,7 @@ public class KeywordFilterEntity {
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

查看文件

@ -1,5 +1,7 @@
package com.xuqm.im.entity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.xuqm.im.json.EpochMillisLocalDateTimeSerializer;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@ -26,6 +28,7 @@ public class WebhookConfigEntity {
private boolean enabled;
@Column(nullable = false)
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
private LocalDateTime createdAt;
public String getId() { return id; }
@ -43,6 +46,7 @@ public class WebhookConfigEntity {
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

查看文件

@ -0,0 +1,10 @@
package com.xuqm.im.repository;
import com.xuqm.im.entity.ImGlobalMuteEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface ImGlobalMuteRepository extends JpaRepository<ImGlobalMuteEntity, String> {
Optional<ImGlobalMuteEntity> findByAppId(String appId);
}

查看文件

@ -2,6 +2,7 @@ package com.xuqm.im.repository;
import com.xuqm.im.entity.ImGroupEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
@ -19,4 +20,19 @@ public interface ImGroupRepository extends JpaRepository<ImGroupEntity, String>
List<ImGroupEntity> findUserGroups(
@Param("appId") String appId,
@Param("userId") String userId);
@Query("""
select g from ImGroupEntity g
where g.appId = :appId
and (:keyword is null or :keyword = '' or
lower(g.id) like lower(concat('%', :keyword, '%')) or
lower(g.name) like lower(concat('%', :keyword, '%')) or
lower(g.creatorId) like lower(concat('%', :keyword, '%')) or
lower(coalesce(g.announcement, '')) like lower(concat('%', :keyword, '%')))
order by g.createdAt desc
""")
List<ImGroupEntity> searchByKeyword(
@Param("appId") String appId,
@Param("keyword") String keyword,
Pageable pageable);
}

查看文件

@ -137,6 +137,29 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
@Param("endTime") LocalDateTime endTime,
Pageable pageable);
@Query("""
select m from ImMessageEntity m
where m.appId = :appId
and (:chatType is null or m.chatType = :chatType)
and (:msgType is null or m.msgType = :msgType)
and (:keyword is null or :keyword = '' or
lower(m.content) like lower(concat('%', :keyword, '%')) or
lower(coalesce(m.mentionedUserIds, '')) like lower(concat('%', :keyword, '%')) or
lower(m.fromUserId) like lower(concat('%', :keyword, '%')) or
lower(m.toId) like lower(concat('%', :keyword, '%')))
and (:startTime is null or m.createdAt >= :startTime)
and (:endTime is null or m.createdAt <= :endTime)
order by m.createdAt desc
""")
Page<ImMessageEntity> searchByKeyword(
@Param("appId") String appId,
@Param("chatType") ImMessageEntity.ChatType chatType,
@Param("msgType") ImMessageEntity.MsgType msgType,
@Param("keyword") String keyword,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime,
Pageable pageable);
@Query("""
select count(m) from ImMessageEntity m
where m.appId = :appId

查看文件

@ -6,4 +6,6 @@ import java.util.List;
public interface KeywordFilterRepository extends JpaRepository<KeywordFilterEntity, String> {
List<KeywordFilterEntity> findByAppIdAndEnabledTrue(String appId);
List<KeywordFilterEntity> findByAppId(String appId);
java.util.Optional<KeywordFilterEntity> findByIdAndAppId(String id, String appId);
}

查看文件

@ -6,4 +6,6 @@ import java.util.List;
public interface WebhookConfigRepository extends JpaRepository<WebhookConfigEntity, String> {
List<WebhookConfigEntity> findByAppIdAndEnabledTrue(String appId);
List<WebhookConfigEntity> findByAppId(String appId);
java.util.Optional<WebhookConfigEntity> findByIdAndAppId(String id, String appId);
}

查看文件

@ -0,0 +1,47 @@
package com.xuqm.im.service;
import com.xuqm.im.entity.ImGlobalMuteEntity;
import com.xuqm.im.repository.ImGlobalMuteRepository;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.UUID;
@Service
public class GlobalMuteService {
private final ImGlobalMuteRepository repository;
public GlobalMuteService(ImGlobalMuteRepository repository) {
this.repository = repository;
}
public boolean isEnabled(String appId) {
return repository.findByAppId(appId).map(ImGlobalMuteEntity::isEnabled).orElse(false);
}
public ImGlobalMuteEntity get(String appId) {
return repository.findByAppId(appId).orElseGet(() -> {
ImGlobalMuteEntity entity = new ImGlobalMuteEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setEnabled(false);
entity.setCreatedAt(LocalDateTime.now());
entity.setUpdatedAt(LocalDateTime.now());
return entity;
});
}
public ImGlobalMuteEntity setEnabled(String appId, boolean enabled) {
ImGlobalMuteEntity entity = repository.findByAppId(appId).orElseGet(() -> {
ImGlobalMuteEntity created = new ImGlobalMuteEntity();
created.setId(UUID.randomUUID().toString());
created.setAppId(appId);
created.setCreatedAt(LocalDateTime.now());
return created;
});
entity.setEnabled(enabled);
entity.setUpdatedAt(LocalDateTime.now());
return repository.save(entity);
}
}

查看文件

@ -1,5 +1,6 @@
package com.xuqm.im.service;
import com.xuqm.common.exception.BusinessException;
import com.xuqm.im.entity.KeywordFilterEntity;
import com.xuqm.im.repository.KeywordFilterRepository;
import org.springframework.stereotype.Service;
@ -51,10 +52,30 @@ public class KeywordFilterService {
}
public List<KeywordFilterEntity> list(String appId) {
return repository.findByAppIdAndEnabledTrue(appId);
return repository.findByAppId(appId);
}
public void delete(String id) {
repository.deleteById(id);
public KeywordFilterEntity update(String appId, String id, String pattern, String replacement, String action, Boolean enabled) {
KeywordFilterEntity entity = repository.findByIdAndAppId(id, appId)
.orElseThrow(() -> new BusinessException(404, "关键词过滤规则不存在"));
if (pattern != null) {
entity.setPattern(pattern);
}
if (replacement != null) {
entity.setReplacement(replacement);
}
if (action != null) {
entity.setAction(action);
}
if (enabled != null) {
entity.setEnabled(enabled);
}
return repository.save(entity);
}
public void delete(String appId, String id) {
KeywordFilterEntity entity = repository.findByIdAndAppId(id, appId)
.orElseThrow(() -> new BusinessException(404, "关键词过滤规则不存在"));
repository.delete(entity);
}
}

查看文件

@ -38,6 +38,7 @@ public class MessageService {
private final ImMessageRepository messageRepository;
private final WebhookConfigRepository webhookRepository;
private final KeywordFilterService keywordFilterService;
private final GlobalMuteService globalMuteService;
private final ImClusterPublisher clusterPublisher;
private final ImGroupService groupService;
private final BlacklistService blacklistService;
@ -53,6 +54,7 @@ public class MessageService {
public MessageService(ImMessageRepository messageRepository,
WebhookConfigRepository webhookRepository,
KeywordFilterService keywordFilterService,
GlobalMuteService globalMuteService,
ImClusterPublisher clusterPublisher,
ImGroupService groupService,
BlacklistService blacklistService,
@ -64,6 +66,7 @@ public class MessageService {
this.messageRepository = messageRepository;
this.webhookRepository = webhookRepository;
this.keywordFilterService = keywordFilterService;
this.globalMuteService = globalMuteService;
this.clusterPublisher = clusterPublisher;
this.groupService = groupService;
this.blacklistService = blacklistService;
@ -75,6 +78,9 @@ public class MessageService {
}
public ImMessageEntity send(String appId, String fromUserId, SendMessageRequest req) {
if (globalMuteService.isEnabled(appId)) {
throw new BusinessException(403, "当前应用已开启全局禁言");
}
String content = req.content();
if (req.msgType() == ImMessageEntity.MsgType.TEXT) {
content = keywordFilterService.filter(appId, content);

查看文件

@ -0,0 +1,56 @@
package com.xuqm.im.service;
import com.xuqm.common.exception.BusinessException;
import com.xuqm.im.entity.WebhookConfigEntity;
import com.xuqm.im.repository.WebhookConfigRepository;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@Service
public class WebhookConfigService {
private final WebhookConfigRepository repository;
public WebhookConfigService(WebhookConfigRepository repository) {
this.repository = repository;
}
public List<WebhookConfigEntity> list(String appId) {
return repository.findByAppId(appId);
}
public WebhookConfigEntity create(String appId, String url, String secret, Boolean enabled) {
WebhookConfigEntity entity = new WebhookConfigEntity();
entity.setId(UUID.randomUUID().toString());
entity.setAppId(appId);
entity.setUrl(url);
entity.setSecret(secret);
entity.setEnabled(enabled == null || enabled);
entity.setCreatedAt(LocalDateTime.now());
return repository.save(entity);
}
public WebhookConfigEntity update(String appId, String id, String url, String secret, Boolean enabled) {
WebhookConfigEntity entity = repository.findByIdAndAppId(id, appId)
.orElseThrow(() -> new BusinessException(404, "回调配置不存在"));
if (url != null) {
entity.setUrl(url);
}
if (secret != null) {
entity.setSecret(secret);
}
if (enabled != null) {
entity.setEnabled(enabled);
}
return repository.save(entity);
}
public void delete(String appId, String id) {
WebhookConfigEntity entity = repository.findByIdAndAppId(id, appId)
.orElseThrow(() -> new BusinessException(404, "回调配置不存在"));
repository.delete(entity);
}
}