From f93d1f3eaf55b34b2c800c1268ff0b67d9b5b631 Mon Sep 17 00:00:00 2001 From: jack ning Date: Sun, 5 Oct 2025 11:59:02 +0800 Subject: [PATCH] update --- .../bytedesk/call/call/CallController.java | 30 ++--- .../CallManagementController.java | 6 +- .../forum/config/ForumHealthIndicator.java | 108 +++++++++++++++++ .../kbase/config/KbaseHealthIndicator.java | 107 +++++++++++++++++ .../social/config/SocialHealthIndicator.java | 104 +++++++++++++++++ .../team/config/TeamHealthIndicator.java | 109 ++++++++++++++++++ 6 files changed, 448 insertions(+), 16 deletions(-) rename modules/call/src/main/java/com/bytedesk/call/{config => call}/CallManagementController.java (98%) create mode 100644 modules/forum/src/main/java/com/bytedesk/forum/config/ForumHealthIndicator.java create mode 100644 modules/kbase/src/main/java/com/bytedesk/kbase/config/KbaseHealthIndicator.java create mode 100644 modules/social/src/main/java/com/bytedesk/social/config/SocialHealthIndicator.java create mode 100644 modules/team/src/main/java/com/bytedesk/team/config/TeamHealthIndicator.java diff --git a/modules/call/src/main/java/com/bytedesk/call/call/CallController.java b/modules/call/src/main/java/com/bytedesk/call/call/CallController.java index 3a2cd6270b..d9dfe242ab 100644 --- a/modules/call/src/main/java/com/bytedesk/call/call/CallController.java +++ b/modules/call/src/main/java/com/bytedesk/call/call/CallController.java @@ -35,7 +35,7 @@ import lombok.extern.slf4j.Slf4j; @ConditionalOnProperty(prefix = "bytedesk.call.freeswitch", name = "enabled", havingValue = "true", matchIfMissing = false) public class CallController { - private final CallService freeSwitchService; + private final CallService callService; /** * http://127.0.0.1:9003/test/api/freeswitch/status @@ -46,11 +46,11 @@ public class CallController { Map result = new HashMap<>(); try { - boolean connected = freeSwitchService.isConnected(); - String status = freeSwitchService.getStatus(); + boolean connected = callService.isConnected(); + String status = callService.getStatus(); // 获取配置信息并过滤敏感信息 - CallFreeswitchProperties properties = freeSwitchService.getProperties(); + CallFreeswitchProperties properties = callService.getProperties(); Map safeProperties = new HashMap<>(); safeProperties.put("enabled", properties.isEnabled()); safeProperties.put("server", properties.getServer()); @@ -88,7 +88,7 @@ public class CallController { Map result = new HashMap<>(); try { - String response = freeSwitchService.originate(caller, destination, context); + String response = callService.originate(caller, destination, context); result.put("success", response != null); result.put("response", response); @@ -114,7 +114,7 @@ public class CallController { Map result = new HashMap<>(); try { - String response = freeSwitchService.hangup(uuid, cause); + String response = callService.hangup(uuid, cause); result.put("success", response != null); result.put("response", response); @@ -136,7 +136,7 @@ public class CallController { Map result = new HashMap<>(); try { - String response = freeSwitchService.answer(uuid); + String response = callService.answer(uuid); result.put("success", response != null); result.put("response", response); @@ -162,7 +162,7 @@ public class CallController { Map result = new HashMap<>(); try { - String response = freeSwitchService.transfer(uuid, destination, context); + String response = callService.transfer(uuid, destination, context); result.put("success", response != null); result.put("response", response); @@ -188,7 +188,7 @@ public class CallController { Map result = new HashMap<>(); try { - String response = freeSwitchService.playback(uuid, filePath); + String response = callService.playback(uuid, filePath); result.put("success", response != null); result.put("response", response); @@ -214,7 +214,7 @@ public class CallController { Map result = new HashMap<>(); try { - String response = freeSwitchService.record(uuid, filePath); + String response = callService.record(uuid, filePath); result.put("success", response != null); result.put("response", response); @@ -240,7 +240,7 @@ public class CallController { Map result = new HashMap<>(); try { - String response = freeSwitchService.stopRecord(uuid, filePath); + String response = callService.stopRecord(uuid, filePath); result.put("success", response != null); result.put("response", response); @@ -266,7 +266,7 @@ public class CallController { Map result = new HashMap<>(); try { - String response = freeSwitchService.sendDtmf(uuid, digits); + String response = callService.sendDtmf(uuid, digits); result.put("success", response != null); result.put("response", response); @@ -289,7 +289,7 @@ public class CallController { Map result = new HashMap<>(); try { - String channels = freeSwitchService.showChannels(); + String channels = callService.showChannels(); result.put("success", true); result.put("channels", channels); @@ -310,7 +310,7 @@ public class CallController { Map result = new HashMap<>(); try { - String calls = freeSwitchService.showCalls(); + String calls = callService.showCalls(); result.put("success", true); result.put("calls", calls); @@ -334,7 +334,7 @@ public class CallController { Map result = new HashMap<>(); try { - String response = freeSwitchService.executeApiCommand(command, args); + String response = callService.executeApiCommand(command, args); result.put("success", response != null); result.put("response", response); diff --git a/modules/call/src/main/java/com/bytedesk/call/config/CallManagementController.java b/modules/call/src/main/java/com/bytedesk/call/call/CallManagementController.java similarity index 98% rename from modules/call/src/main/java/com/bytedesk/call/config/CallManagementController.java rename to modules/call/src/main/java/com/bytedesk/call/call/CallManagementController.java index 5a13b62a9f..8488342f1f 100644 --- a/modules/call/src/main/java/com/bytedesk/call/config/CallManagementController.java +++ b/modules/call/src/main/java/com/bytedesk/call/call/CallManagementController.java @@ -11,7 +11,7 @@ * * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. */ -package com.bytedesk.call.config; +package com.bytedesk.call.call; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.ResponseEntity; @@ -20,6 +20,10 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import com.bytedesk.call.config.CallConnectionTester; +import com.bytedesk.call.config.CallFreeswitchProperties; +import com.bytedesk.call.config.CallHealthIndicator; + import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; diff --git a/modules/forum/src/main/java/com/bytedesk/forum/config/ForumHealthIndicator.java b/modules/forum/src/main/java/com/bytedesk/forum/config/ForumHealthIndicator.java new file mode 100644 index 0000000000..2477a8dc3b --- /dev/null +++ b/modules/forum/src/main/java/com/bytedesk/forum/config/ForumHealthIndicator.java @@ -0,0 +1,108 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-10-05 12:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-10-05 12:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.forum.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +import javax.sql.DataSource; +import java.sql.Connection; + +/** + * Forum模块健康检查 + * 监控论坛系统相关服务:数据库、Redis缓存、搜索索引、审核队列等 + */ +@Slf4j +@Component +public class ForumHealthIndicator implements HealthIndicator { + + @Value("${bytedesk.forum.post-moderation.enabled:false}") + private boolean moderationEnabled; + + @Value("${bytedesk.forum.hot-topics.cache-ttl:3600}") + private int hotTopicsCacheTtl; + + @Value("${bytedesk.forum.search.enabled:true}") + private boolean searchEnabled; + + @Autowired(required = false) + private DataSource dataSource; + + @Autowired(required = false) + private RedisTemplate redisTemplate; + + @Override + public Health health() { + try { + Health.Builder builder = Health.up(); + + // 检查数据库连接 + if (dataSource != null) { + try (Connection connection = dataSource.getConnection()) { + builder.withDetail("database-status", "Connected") + .withDetail("database-catalog", connection.getCatalog()); + } catch (Exception e) { + log.error("Forum database health check failed", e); + builder.down() + .withDetail("database-status", "Connection Failed") + .withDetail("database-error", e.getMessage()); + } + } + + // 检查Redis缓存(用于热帖、统计等) + if (redisTemplate != null) { + try { + // 检查Redis连接 + redisTemplate.opsForValue().get("health:check"); + + // 获取论坛相关统计 + Long hotTopicsCount = redisTemplate.opsForZSet().size("forum:hot:topics"); + Long pendingModeration = redisTemplate.opsForList().size("forum:moderation:queue"); + + builder.withDetail("redis-status", "Connected") + .withDetail("hot-topics-cached", hotTopicsCount != null ? hotTopicsCount : 0) + .withDetail("pending-moderation", moderationEnabled ? (pendingModeration != null ? pendingModeration : 0) : "N/A"); + } catch (Exception e) { + log.error("Forum Redis health check failed", e); + builder.down() + .withDetail("redis-status", "Connection Failed") + .withDetail("redis-error", e.getMessage()); + } + } else { + builder.withDetail("redis-status", "Not Configured"); + } + + // 论坛功能配置信息 + builder.withDetail("post-moderation", moderationEnabled ? "Enabled" : "Disabled") + .withDetail("search-enabled", searchEnabled) + .withDetail("hot-topics-cache-ttl", hotTopicsCacheTtl + "s") + .withDetail("forum-features", "Active") + .withDetail("supported-content", "Post, Thread, Comment, Tag"); + + return builder.build(); + + } catch (Exception e) { + log.error("Forum health check failed", e); + return Health.down() + .withDetail("error", e.getMessage()) + .build(); + } + } +} diff --git a/modules/kbase/src/main/java/com/bytedesk/kbase/config/KbaseHealthIndicator.java b/modules/kbase/src/main/java/com/bytedesk/kbase/config/KbaseHealthIndicator.java new file mode 100644 index 0000000000..0adb087999 --- /dev/null +++ b/modules/kbase/src/main/java/com/bytedesk/kbase/config/KbaseHealthIndicator.java @@ -0,0 +1,107 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-10-05 12:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-10-05 12:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.kbase.config; + +import org.springframework.ai.vectorstore.elasticsearch.ElasticsearchVectorStore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +import javax.sql.DataSource; +import java.sql.Connection; + +/** + * Kbase模块健康检查 + * 监控知识库系统相关服务:数据库、向量存储(Elasticsearch)、批处理等 + */ +@Slf4j +@Component +public class KbaseHealthIndicator implements HealthIndicator { + + @Value("${spring.ai.vectorstore.elasticsearch.enabled:false}") + private boolean vectorStoreEnabled; + + @Value("${spring.ai.vectorstore.elasticsearch.index-name:bytedesk_vector}") + private String vectorStoreIndexName; + + @Value("${spring.batch.job.enabled:true}") + private boolean batchJobEnabled; + + @Autowired(required = false) + private DataSource dataSource; + + @Autowired(required = false) + private ElasticsearchVectorStore elasticsearchVectorStore; + + @Override + public Health health() { + try { + Health.Builder builder = Health.up(); + + // 检查数据库连接 + if (dataSource != null) { + try (Connection connection = dataSource.getConnection()) { + builder.withDetail("database-status", "Connected") + .withDetail("database-catalog", connection.getCatalog()); + } catch (Exception e) { + log.error("Kbase database health check failed", e); + builder.down() + .withDetail("database-status", "Connection Failed") + .withDetail("database-error", e.getMessage()); + } + } + + // 检查向量存储(Elasticsearch) + if (vectorStoreEnabled) { + if (elasticsearchVectorStore != null) { + try { + // 尝试简单的检查 + builder.withDetail("vector-store-status", "Connected") + .withDetail("vector-store-index", vectorStoreIndexName) + .withDetail("vector-store-type", "Elasticsearch"); + } catch (Exception e) { + log.error("Elasticsearch vector store health check failed", e); + builder.down() + .withDetail("vector-store-status", "Connection Failed") + .withDetail("vector-store-error", e.getMessage()); + } + } else { + builder.withDetail("vector-store-status", "Configured but not initialized") + .withDetail("vector-store-warning", "ElasticsearchVectorStore bean not available"); + } + } else { + builder.withDetail("vector-store-status", "Disabled"); + } + + // 批处理任务状态 + builder.withDetail("batch-job-enabled", batchJobEnabled); + + // 知识库特定功能状态 + builder.withDetail("knowledge-base-features", "Active") + .withDetail("supported-content-types", "Article, FAQ, Text, Chunk, File"); + + return builder.build(); + + } catch (Exception e) { + log.error("Kbase health check failed", e); + return Health.down() + .withDetail("error", e.getMessage()) + .build(); + } + } +} diff --git a/modules/social/src/main/java/com/bytedesk/social/config/SocialHealthIndicator.java b/modules/social/src/main/java/com/bytedesk/social/config/SocialHealthIndicator.java new file mode 100644 index 0000000000..79ac0cf689 --- /dev/null +++ b/modules/social/src/main/java/com/bytedesk/social/config/SocialHealthIndicator.java @@ -0,0 +1,104 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-10-05 12:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-10-05 12:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.social.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +import javax.sql.DataSource; +import java.sql.Connection; + +/** + * Social模块健康检查 + * 监控社交功能相关服务:数据库、Redis缓存、用户关系图谱等 + */ +@Slf4j +@Component +public class SocialHealthIndicator implements HealthIndicator { + + @Value("${bytedesk.social.max-connections:5000}") + private int maxConnections; + + @Value("${bytedesk.social.cache.enabled:true}") + private boolean cacheEnabled; + + @Autowired(required = false) + private DataSource dataSource; + + @Autowired(required = false) + private RedisTemplate redisTemplate; + + @Override + public Health health() { + try { + Health.Builder builder = Health.up(); + + // 检查数据库连接 + if (dataSource != null) { + try (Connection connection = dataSource.getConnection()) { + builder.withDetail("database-status", "Connected") + .withDetail("database-catalog", connection.getCatalog()); + } catch (Exception e) { + log.error("Social database health check failed", e); + builder.down() + .withDetail("database-status", "Connection Failed") + .withDetail("database-error", e.getMessage()); + } + } + + // 检查Redis缓存(用于社交关系缓存) + if (cacheEnabled && redisTemplate != null) { + try { + // 检查Redis连接 + redisTemplate.opsForValue().get("health:check"); + + // 获取社交关系缓存统计(示例) + Long followCount = redisTemplate.opsForHash().size("social:follows"); + Long friendCount = redisTemplate.opsForHash().size("social:friends"); + + builder.withDetail("redis-status", "Connected") + .withDetail("cache-enabled", true) + .withDetail("cached-follows", followCount != null ? followCount : 0) + .withDetail("cached-friends", friendCount != null ? friendCount : 0); + } catch (Exception e) { + log.error("Social Redis health check failed", e); + builder.down() + .withDetail("redis-status", "Connection Failed") + .withDetail("redis-error", e.getMessage()); + } + } else { + builder.withDetail("cache-status", cacheEnabled ? "Enabled (Redis not available)" : "Disabled"); + } + + // 社交功能配置信息 + builder.withDetail("max-connections", maxConnections) + .withDetail("social-features", "Active") + .withDetail("supported-relations", "Follow, Friend, Block"); + + return builder.build(); + + } catch (Exception e) { + log.error("Social health check failed", e); + return Health.down() + .withDetail("error", e.getMessage()) + .build(); + } + } +} diff --git a/modules/team/src/main/java/com/bytedesk/team/config/TeamHealthIndicator.java b/modules/team/src/main/java/com/bytedesk/team/config/TeamHealthIndicator.java new file mode 100644 index 0000000000..f0fb061192 --- /dev/null +++ b/modules/team/src/main/java/com/bytedesk/team/config/TeamHealthIndicator.java @@ -0,0 +1,109 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-10-05 12:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-10-05 12:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.team.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +import javax.sql.DataSource; +import java.sql.Connection; + +/** + * Team模块健康检查 + * 监控团队协作相关服务:数据库、Redis缓存、成员管理、权限系统等 + */ +@Slf4j +@Component +public class TeamHealthIndicator implements HealthIndicator { + + @Value("${bytedesk.team.max-members-per-team:100}") + private int maxMembersPerTeam; + + @Value("${bytedesk.team.invitation.expiry-hours:72}") + private int invitationExpiryHours; + + @Value("${bytedesk.team.cache.enabled:true}") + private boolean cacheEnabled; + + @Autowired(required = false) + private DataSource dataSource; + + @Autowired(required = false) + private RedisTemplate redisTemplate; + + @Override + public Health health() { + try { + Health.Builder builder = Health.up(); + + // 检查数据库连接 + if (dataSource != null) { + try (Connection connection = dataSource.getConnection()) { + builder.withDetail("database-status", "Connected") + .withDetail("database-catalog", connection.getCatalog()); + } catch (Exception e) { + log.error("Team database health check failed", e); + builder.down() + .withDetail("database-status", "Connection Failed") + .withDetail("database-error", e.getMessage()); + } + } + + // 检查Redis缓存(用于团队成员、权限缓存) + if (cacheEnabled && redisTemplate != null) { + try { + // 检查Redis连接 + redisTemplate.opsForValue().get("health:check"); + + // 获取团队相关统计 + Long activeTeams = redisTemplate.opsForSet().size("team:active"); + Long pendingInvitations = redisTemplate.opsForHash().size("team:invitations:pending"); + + builder.withDetail("redis-status", "Connected") + .withDetail("cache-enabled", true) + .withDetail("active-teams-cached", activeTeams != null ? activeTeams : 0) + .withDetail("pending-invitations", pendingInvitations != null ? pendingInvitations : 0); + } catch (Exception e) { + log.error("Team Redis health check failed", e); + builder.down() + .withDetail("redis-status", "Connection Failed") + .withDetail("redis-error", e.getMessage()); + } + } else { + builder.withDetail("cache-status", cacheEnabled ? "Enabled (Redis not available)" : "Disabled"); + } + + // 团队功能配置信息 + builder.withDetail("max-members-per-team", maxMembersPerTeam) + .withDetail("invitation-expiry-hours", invitationExpiryHours) + .withDetail("team-features", "Active") + .withDetail("supported-roles", "Owner, Admin, Member, Guest") + .withDetail("permission-system", "Enabled"); + + return builder.build(); + + } catch (Exception e) { + log.error("Team health check failed", e); + return Health.down() + .withDetail("error", e.getMessage()) + .build(); + } + } +}