This commit is contained in:
jack ning
2025-10-06 08:02:33 +08:00
parent 02f353dffa
commit 79f8b4c30e
13 changed files with 1726 additions and 16 deletions

View File

@@ -0,0 +1,87 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.ai.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import com.bytedesk.core.plugin.AbstractBytedeskPlugin;
import lombok.extern.slf4j.Slf4j;
/**
* AI模块插件
* 提供大模型集成、智能客服、对话生成等功能
*/
@Slf4j
@Component
public class AiPlugin extends AbstractBytedeskPlugin {
@Value("${bytedesk.ai.enabled:true}")
private boolean enabled;
@Value("${bytedesk.ai.version:1.0.0}")
private String version;
@Autowired(required = false)
private HealthIndicator aiHealthIndicator;
@Override
protected HealthIndicator getHealthIndicator() {
return aiHealthIndicator;
}
@Override
public String getPluginId() {
return "ai";
}
@Override
public String getPluginName() {
return "AI Assistant";
}
@Override
public String getDescription() {
return "AI智能助手提供大模型集成、智能客服、对话生成、语义理解等功能";
}
@Override
public String getVersion() {
return version;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public int getPriority() {
return 15; // AI功能优先级较高
}
@Override
public String[] getDependencies() {
return new String[]{"core", "kbase"}; // 依赖核心模块和知识库
}
@Override
public void initialize() {
super.initialize();
log.info("AI Assistant Plugin initialized - Features: LLM Integration, Intelligent Q&A, Semantic Understanding");
}
}

View File

@@ -0,0 +1,87 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.call.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import com.bytedesk.core.plugin.AbstractBytedeskPlugin;
import lombok.extern.slf4j.Slf4j;
/**
* 呼叫中心模块插件
* 提供语音通话、FreeSWITCH集成等功能
*/
@Slf4j
@Component
public class CallPlugin extends AbstractBytedeskPlugin {
@Value("${bytedesk.call.enabled:false}")
private boolean enabled;
@Value("${bytedesk.call.version:1.0.0}")
private String version;
@Autowired(required = false)
private HealthIndicator callHealthIndicator;
@Override
protected HealthIndicator getHealthIndicator() {
return callHealthIndicator;
}
@Override
public String getPluginId() {
return "call";
}
@Override
public String getPluginName() {
return "Call Center";
}
@Override
public String getDescription() {
return "呼叫中心系统提供语音通话、FreeSWITCH集成、通话记录等功能";
}
@Override
public String getVersion() {
return version;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public int getPriority() {
return 40; // 呼叫中心优先级中等
}
@Override
public String[] getDependencies() {
return new String[]{"core"}; // 依赖核心模块
}
@Override
public void initialize() {
super.initialize();
log.info("Call Center Plugin initialized - Features: Voice Call, FreeSWITCH Integration, Call Recording");
}
}

View File

@@ -25,14 +25,10 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.bytedesk.core.base.BaseRestServiceWithExport; import com.bytedesk.core.base.BaseRestServiceWithExport;
import com.bytedesk.core.constant.BytedeskConsts;
import com.bytedesk.core.constant.I18Consts; import com.bytedesk.core.constant.I18Consts;
import com.bytedesk.core.enums.LevelEnum;
import com.bytedesk.core.exception.NotLoginException; import com.bytedesk.core.exception.NotLoginException;
import com.bytedesk.core.rbac.user.UserEntity; import com.bytedesk.core.rbac.user.UserEntity;
import com.bytedesk.core.uid.UidUtils; import com.bytedesk.core.uid.UidUtils;
import com.bytedesk.core.utils.Utils;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -213,18 +209,18 @@ public class CallIvrRestService extends BaseRestServiceWithExport<CallIvrEntity,
public void initCallIvrs(String orgUid) { public void initCallIvrs(String orgUid) {
// log.info("initThreadCallIvr"); // log.info("initThreadCallIvr");
for (String tag : CallIvrInitData.getAllCallIvrs()) { // for (String tag : CallIvrInitData.getAllCallIvrs()) {
CallIvrRequest tagRequest = CallIvrRequest.builder() // CallIvrRequest tagRequest = CallIvrRequest.builder()
.uid(Utils.formatUid(orgUid, tag)) // .uid(Utils.formatUid(orgUid, tag))
.name(tag) // .name(tag)
.order(0) // .order(0)
.type(CallIvrTypeEnum.THREAD.name()) // .type(CallIvrTypeEnum.THREAD.name())
.level(LevelEnum.ORGANIZATION.name()) // .level(LevelEnum.ORGANIZATION.name())
.platform(BytedeskConsts.PLATFORM_BYTEDESK) // .platform(BytedeskConsts.PLATFORM_BYTEDESK)
.orgUid(orgUid) // .orgUid(orgUid)
.build(); // .build();
create(tagRequest); // create(tagRequest);
} // }
} }
} }

View File

@@ -0,0 +1,127 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.core.plugin;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import lombok.extern.slf4j.Slf4j;
/**
* Bytedesk插件抽象基类
* 提供插件的通用实现
*/
@Slf4j
public abstract class AbstractBytedeskPlugin implements BytedeskPlugin {
private final Instant registerTime = Instant.now();
/**
* 获取插件作者
* 默认返回Bytedesk团队
*/
@Override
public String getAuthor() {
return "Bytedesk Team";
}
/**
* 获取健康指示器
* 子类可以重写此方法提供自定义的HealthIndicator
*/
protected HealthIndicator getHealthIndicator() {
return null;
}
/**
* 获取插件健康状态
* 默认从对应的HealthIndicator获取
*/
@Override
public Map<String, Object> getHealthStatus() {
Map<String, Object> status = new HashMap<>();
try {
HealthIndicator healthIndicator = getHealthIndicator();
if (healthIndicator != null) {
Health health = healthIndicator.health();
status.put("status", health.getStatus().getCode());
status.put("details", health.getDetails());
} else {
status.put("status", "UP");
status.put("message", "No health indicator configured");
}
} catch (Exception e) {
log.error("Failed to get health status for plugin: {}", getPluginId(), e);
status.put("status", "DOWN");
status.put("error", e.getMessage());
}
return status;
}
/**
* 获取插件统计信息
* 包含基本信息和运行时长
*/
@Override
public Map<String, Object> getStatistics() {
Map<String, Object> stats = new HashMap<>();
stats.put("pluginId", getPluginId());
stats.put("pluginName", getPluginName());
stats.put("version", getVersion());
stats.put("enabled", isEnabled());
stats.put("priority", getPriority());
stats.put("dependencies", getDependencies());
// 计算运行时长
Duration uptime = Duration.between(registerTime, Instant.now());
stats.put("uptime-seconds", uptime.getSeconds());
stats.put("uptime-readable", formatDuration(uptime));
stats.put("registerTime", registerTime.toString());
return stats;
}
/**
* 格式化时长
*/
protected String formatDuration(Duration duration) {
long days = duration.toDays();
long hours = duration.toHoursPart();
long minutes = duration.toMinutesPart();
return String.format("%dd %dh %dm", days, hours, minutes);
}
/**
* 插件初始化
*/
@Override
public void initialize() {
log.info("Initializing plugin: {} ({})", getPluginName(), getPluginId());
}
/**
* 插件销毁
*/
@Override
public void destroy() {
log.info("Destroying plugin: {} ({})", getPluginName(), getPluginId());
}
}

View File

@@ -0,0 +1,116 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.core.plugin;
import java.util.Map;
/**
* Bytedesk插件接口
* 所有模块插件必须实现此接口
*/
public interface BytedeskPlugin {
/**
* 获取插件唯一标识符
* @return 插件IDkbase, service, ticket, ai, call, voc
*/
String getPluginId();
/**
* 获取插件名称
* @return 插件显示名称
*/
String getPluginName();
/**
* 获取插件描述
* @return 插件功能描述
*/
String getDescription();
/**
* 获取插件版本
* @return 版本号
*/
String getVersion();
/**
* 获取插件作者
* @return 作者信息
*/
String getAuthor();
/**
* 获取插件官网
* @return 官网URL
*/
default String getWebsite() {
return "https://bytedesk.com";
}
/**
* 插件是否启用
* @return true表示启用false表示禁用
*/
boolean isEnabled();
/**
* 插件优先级(数字越小优先级越高)
* @return 优先级值默认100
*/
default int getPriority() {
return 100;
}
/**
* 获取插件依赖的其他插件ID列表
* @return 依赖的插件ID数组如果没有依赖返回空数组
*/
default String[] getDependencies() {
return new String[0];
}
/**
* 获取插件健康状态
* @return 健康状态信息
*/
Map<String, Object> getHealthStatus();
/**
* 获取插件统计信息
* @return 统计数据
*/
default Map<String, Object> getStatistics() {
return Map.of(
"enabled", isEnabled(),
"version", getVersion()
);
}
/**
* 插件初始化
* 在插件注册后调用
*/
default void initialize() {
// 默认空实现
}
/**
* 插件销毁
* 在应用关闭时调用
*/
default void destroy() {
// 默认空实现
}
}

View File

@@ -0,0 +1,85 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.core.plugin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
* 核心模块插件
* 提供基础功能:用户管理、权限控制、消息系统等
*/
@Slf4j
@Component
public class CorePlugin extends AbstractBytedeskPlugin {
@Value("${bytedesk.core.enabled:true}")
private boolean enabled;
@Value("${bytedesk.core.version:1.0.0}")
private String version;
@Autowired(required = false)
private HealthIndicator coreHealthIndicator;
@Override
protected HealthIndicator getHealthIndicator() {
return coreHealthIndicator;
}
@Override
public String getPluginId() {
return "core";
}
@Override
public String getPluginName() {
return "Core Module";
}
@Override
public String getDescription() {
return "核心模块,提供用户管理、权限控制、消息系统、通知等基础功能";
}
@Override
public String getVersion() {
return version;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public int getPriority() {
return 1; // 核心模块优先级最高
}
@Override
public String[] getDependencies() {
return new String[0]; // 核心模块不依赖其他模块
}
@Override
public void initialize() {
super.initialize();
log.info("Core Module Plugin initialized - Features: User Management, RBAC, Messaging, Notification");
}
}

View File

@@ -0,0 +1,227 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.core.plugin;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 插件管理控制器
* 提供插件信息查询和管理接口
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/plugins")
@RequiredArgsConstructor
@Tag(name = "Plugin Management", description = "插件管理接口")
public class PluginController {
private final PluginRegistry pluginRegistry;
/**
* 获取所有插件列表
* http://127.0.0.1:9003/api/v1/plugins
*/
@GetMapping
@Operation(summary = "获取所有插件", description = "获取系统中所有已注册的插件列表")
public ResponseEntity<Map<String, Object>> getAllPlugins() {
List<BytedeskPlugin> plugins = pluginRegistry.getAllPlugins();
List<Map<String, Object>> pluginList = plugins.stream()
.sorted(Comparator.comparingInt(BytedeskPlugin::getPriority))
.map(this::convertPluginToMap)
.collect(Collectors.toList());
Map<String, Object> response = new LinkedHashMap<>();
response.put("total", plugins.size());
response.put("enabled", pluginRegistry.getEnabledPluginCount());
response.put("plugins", pluginList);
return ResponseEntity.ok(response);
}
/**
* 获取已启用的插件列表
*/
@GetMapping("/enabled")
@Operation(summary = "获取已启用插件", description = "获取系统中所有已启用的插件列表")
public ResponseEntity<Map<String, Object>> getEnabledPlugins() {
List<BytedeskPlugin> plugins = pluginRegistry.getEnabledPlugins();
List<Map<String, Object>> pluginList = plugins.stream()
.map(this::convertPluginToMap)
.collect(Collectors.toList());
Map<String, Object> response = new LinkedHashMap<>();
response.put("total", plugins.size());
response.put("plugins", pluginList);
return ResponseEntity.ok(response);
}
/**
* 获取指定插件信息
*/
@GetMapping("/{pluginId}")
@Operation(summary = "获取插件详情", description = "根据插件ID获取插件详细信息")
public ResponseEntity<Map<String, Object>> getPlugin(@PathVariable String pluginId) {
Optional<BytedeskPlugin> plugin = pluginRegistry.getPlugin(pluginId);
if (plugin.isEmpty()) {
return ResponseEntity.notFound().build();
}
Map<String, Object> response = convertPluginToDetailMap(plugin.get());
return ResponseEntity.ok(response);
}
/**
* 获取插件健康状态
*/
@GetMapping("/{pluginId}/health")
@Operation(summary = "获取插件健康状态", description = "获取指定插件的健康检查状态")
public ResponseEntity<Map<String, Object>> getPluginHealth(@PathVariable String pluginId) {
Optional<BytedeskPlugin> plugin = pluginRegistry.getPlugin(pluginId);
if (plugin.isEmpty()) {
return ResponseEntity.notFound().build();
}
Map<String, Object> health = plugin.get().getHealthStatus();
return ResponseEntity.ok(health);
}
/**
* 获取所有插件的健康状态
*/
@GetMapping("/health")
@Operation(summary = "获取所有插件健康状态", description = "获取系统中所有插件的健康检查状态")
public ResponseEntity<Map<String, Object>> getAllPluginsHealth() {
Map<String, Map<String, Object>> healthStatus = pluginRegistry.getAllPluginsHealthStatus();
Map<String, Object> response = new LinkedHashMap<>();
response.put("timestamp", System.currentTimeMillis());
response.put("total", pluginRegistry.getPluginCount());
response.put("enabled", pluginRegistry.getEnabledPluginCount());
response.put("plugins", healthStatus);
return ResponseEntity.ok(response);
}
/**
* 获取插件统计信息
*/
@GetMapping("/{pluginId}/statistics")
@Operation(summary = "获取插件统计信息", description = "获取指定插件的统计数据")
public ResponseEntity<Map<String, Object>> getPluginStatistics(@PathVariable String pluginId) {
Optional<BytedeskPlugin> plugin = pluginRegistry.getPlugin(pluginId);
if (plugin.isEmpty()) {
return ResponseEntity.notFound().build();
}
Map<String, Object> statistics = plugin.get().getStatistics();
return ResponseEntity.ok(statistics);
}
/**
* 获取所有插件的统计信息
*/
@GetMapping("/statistics")
@Operation(summary = "获取所有插件统计信息", description = "获取系统中所有插件的统计数据")
public ResponseEntity<Map<String, Object>> getAllPluginsStatistics() {
Map<String, Map<String, Object>> statistics = pluginRegistry.getAllPluginsStatistics();
Map<String, Object> response = new LinkedHashMap<>();
response.put("timestamp", System.currentTimeMillis());
response.put("total", pluginRegistry.getPluginCount());
response.put("enabled", pluginRegistry.getEnabledPluginCount());
response.put("plugins", statistics);
return ResponseEntity.ok(response);
}
/**
* 获取插件概览信息
*/
@GetMapping("/overview")
@Operation(summary = "获取插件概览", description = "获取插件系统的概览信息")
public ResponseEntity<Map<String, Object>> getPluginsOverview() {
Map<String, Object> overview = new LinkedHashMap<>();
// 基本统计
overview.put("totalPlugins", pluginRegistry.getPluginCount());
overview.put("enabledPlugins", pluginRegistry.getEnabledPluginCount());
overview.put("disabledPlugins", pluginRegistry.getPluginCount() - pluginRegistry.getEnabledPluginCount());
// 插件列表(简化信息)
List<Map<String, Object>> pluginSummary = pluginRegistry.getAllPlugins().stream()
.sorted(Comparator.comparingInt(BytedeskPlugin::getPriority))
.map(plugin -> {
Map<String, Object> summary = new LinkedHashMap<>();
summary.put("id", plugin.getPluginId());
summary.put("name", plugin.getPluginName());
summary.put("version", plugin.getVersion());
summary.put("enabled", plugin.isEnabled());
summary.put("priority", plugin.getPriority());
return summary;
})
.collect(Collectors.toList());
overview.put("plugins", pluginSummary);
return ResponseEntity.ok(overview);
}
/**
* 转换插件为Map简化版
*/
private Map<String, Object> convertPluginToMap(BytedeskPlugin plugin) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("id", plugin.getPluginId());
map.put("name", plugin.getPluginName());
map.put("description", plugin.getDescription());
map.put("version", plugin.getVersion());
map.put("enabled", plugin.isEnabled());
map.put("priority", plugin.getPriority());
return map;
}
/**
* 转换插件为Map详细版
*/
private Map<String, Object> convertPluginToDetailMap(BytedeskPlugin plugin) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("id", plugin.getPluginId());
map.put("name", plugin.getPluginName());
map.put("description", plugin.getDescription());
map.put("version", plugin.getVersion());
map.put("author", plugin.getAuthor());
map.put("website", plugin.getWebsite());
map.put("enabled", plugin.isEnabled());
map.put("priority", plugin.getPriority());
map.put("dependencies", plugin.getDependencies());
map.put("statistics", plugin.getStatistics());
map.put("health", plugin.getHealthStatus());
return map;
}
}

View File

@@ -0,0 +1,284 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.core.plugin;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
/**
* 插件注册中心
* 管理所有Bytedesk模块插件的注册、查询和生命周期
*/
@Slf4j
@Component
public class PluginRegistry {
private final Map<String, BytedeskPlugin> plugins = new ConcurrentHashMap<>();
private final List<BytedeskPlugin> pluginList;
public PluginRegistry(List<BytedeskPlugin> pluginList) {
this.pluginList = pluginList;
}
/**
* 初始化:自动注册所有插件
*/
@PostConstruct
public void init() {
log.info("Initializing Plugin Registry...");
// 按优先级排序
List<BytedeskPlugin> sortedPlugins = pluginList.stream()
.sorted(Comparator.comparingInt(BytedeskPlugin::getPriority))
.collect(Collectors.toList());
// 注册所有插件
for (BytedeskPlugin plugin : sortedPlugins) {
registerPlugin(plugin);
}
log.info("Plugin Registry initialized with {} plugins", plugins.size());
logRegisteredPlugins();
}
/**
* 注册插件
*/
public void registerPlugin(BytedeskPlugin plugin) {
if (plugin == null) {
log.warn("Attempted to register null plugin");
return;
}
String pluginId = plugin.getPluginId();
if (pluginId == null || pluginId.trim().isEmpty()) {
log.error("Plugin ID cannot be null or empty");
return;
}
// 检查依赖
if (!checkDependencies(plugin)) {
log.error("Plugin {} has unmet dependencies", pluginId);
return;
}
// 注册插件
plugins.put(pluginId, plugin);
log.info("Registered plugin: {} ({}) - Version: {}, Enabled: {}",
plugin.getPluginName(), pluginId, plugin.getVersion(), plugin.isEnabled());
// 初始化插件
try {
plugin.initialize();
} catch (Exception e) {
log.error("Failed to initialize plugin: {}", pluginId, e);
}
}
/**
* 注销插件
*/
public void unregisterPlugin(String pluginId) {
BytedeskPlugin plugin = plugins.remove(pluginId);
if (plugin != null) {
try {
plugin.destroy();
log.info("Unregistered plugin: {} ({})", plugin.getPluginName(), pluginId);
} catch (Exception e) {
log.error("Failed to destroy plugin: {}", pluginId, e);
}
}
}
/**
* 获取插件
*/
public Optional<BytedeskPlugin> getPlugin(String pluginId) {
return Optional.ofNullable(plugins.get(pluginId));
}
/**
* 获取所有插件
*/
public List<BytedeskPlugin> getAllPlugins() {
return new ArrayList<>(plugins.values());
}
/**
* 获取所有已启用的插件
*/
public List<BytedeskPlugin> getEnabledPlugins() {
return plugins.values().stream()
.filter(BytedeskPlugin::isEnabled)
.sorted(Comparator.comparingInt(BytedeskPlugin::getPriority))
.collect(Collectors.toList());
}
/**
* 获取插件数量
*/
public int getPluginCount() {
return plugins.size();
}
/**
* 获取已启用插件数量
*/
public int getEnabledPluginCount() {
return (int) plugins.values().stream()
.filter(BytedeskPlugin::isEnabled)
.count();
}
/**
* 检查插件是否存在
*/
public boolean hasPlugin(String pluginId) {
return plugins.containsKey(pluginId);
}
/**
* 检查插件是否启用
*/
public boolean isPluginEnabled(String pluginId) {
return getPlugin(pluginId)
.map(BytedeskPlugin::isEnabled)
.orElse(false);
}
/**
* 获取所有插件的健康状态
*/
public Map<String, Map<String, Object>> getAllPluginsHealthStatus() {
Map<String, Map<String, Object>> healthStatus = new LinkedHashMap<>();
plugins.values().stream()
.sorted(Comparator.comparingInt(BytedeskPlugin::getPriority))
.forEach(plugin -> {
try {
healthStatus.put(plugin.getPluginId(), plugin.getHealthStatus());
} catch (Exception e) {
log.error("Failed to get health status for plugin: {}", plugin.getPluginId(), e);
healthStatus.put(plugin.getPluginId(), Map.of(
"status", "ERROR",
"error", e.getMessage()
));
}
});
return healthStatus;
}
/**
* 获取所有插件的统计信息
*/
public Map<String, Map<String, Object>> getAllPluginsStatistics() {
Map<String, Map<String, Object>> statistics = new LinkedHashMap<>();
plugins.values().stream()
.sorted(Comparator.comparingInt(BytedeskPlugin::getPriority))
.forEach(plugin -> {
try {
statistics.put(plugin.getPluginId(), plugin.getStatistics());
} catch (Exception e) {
log.error("Failed to get statistics for plugin: {}", plugin.getPluginId(), e);
}
});
return statistics;
}
/**
* 检查插件依赖是否满足
*/
private boolean checkDependencies(BytedeskPlugin plugin) {
String[] dependencies = plugin.getDependencies();
if (dependencies == null || dependencies.length == 0) {
return true;
}
for (String dependency : dependencies) {
if (!plugins.containsKey(dependency)) {
log.warn("Plugin {} depends on {}, but it's not registered yet",
plugin.getPluginId(), dependency);
return false;
}
}
return true;
}
/**
* 记录已注册的插件信息
*/
private void logRegisteredPlugins() {
if (plugins.isEmpty()) {
log.warn("No plugins registered");
return;
}
log.info("==================================================");
log.info("Registered Plugins ({})", plugins.size());
log.info("==================================================");
plugins.values().stream()
.sorted(Comparator.comparingInt(BytedeskPlugin::getPriority))
.forEach(plugin -> {
log.info(" - {} ({}) v{} [{}] Priority: {}",
plugin.getPluginName(),
plugin.getPluginId(),
plugin.getVersion(),
plugin.isEnabled() ? "ENABLED" : "DISABLED",
plugin.getPriority());
String[] deps = plugin.getDependencies();
if (deps != null && deps.length > 0) {
log.info(" Dependencies: {}", String.join(", ", deps));
}
});
log.info("==================================================");
}
/**
* 销毁:注销所有插件
*/
@PreDestroy
public void destroy() {
log.info("Destroying Plugin Registry...");
// 按优先级逆序销毁
List<BytedeskPlugin> sortedPlugins = new ArrayList<>(plugins.values());
sortedPlugins.sort(Comparator.comparingInt(BytedeskPlugin::getPriority).reversed());
for (BytedeskPlugin plugin : sortedPlugins) {
try {
plugin.destroy();
log.info("Destroyed plugin: {}", plugin.getPluginId());
} catch (Exception e) {
log.error("Failed to destroy plugin: {}", plugin.getPluginId(), e);
}
}
plugins.clear();
log.info("Plugin Registry destroyed");
}
}

View File

@@ -0,0 +1,353 @@
# Bytedesk 插件系统
## 概述
Bytedesk 插件系统提供了一个统一的框架来管理各个功能模块,实现模块化架构和集中管理。
## 架构设计
### 核心组件
1. **BytedeskPlugin 接口** - 定义插件的基本契约
2. **AbstractBytedeskPlugin** - 提供插件的通用实现
3. **PluginRegistry** - 插件注册中心,管理所有插件
4. **PluginController** - REST API 接口,提供插件信息查询
### 插件生命周期
```
注册 -> 初始化 -> 运行 -> 销毁
```
## 已注册插件
| 插件ID | 名称 | 描述 | 优先级 | 依赖 |
|--------|------|------|--------|------|
| service | Customer Service | 在线客服系统 | 10 | core |
| ai | AI Assistant | AI智能助手 | 15 | core, kbase |
| kbase | Knowledge Base | 知识库管理 | 20 | core |
| ticket | Ticket System | 工单管理系统 | 30 | core |
| call | Call Center | 呼叫中心 | 40 | core |
| voc | Voice of Customer | 客户之声 | 50 | core |
## API 接口
### 1. 获取所有插件
```http
GET /api/v1/plugins
```
**响应示例:**
```json
{
"total": 6,
"enabled": 5,
"plugins": [
{
"id": "service",
"name": "Customer Service",
"description": "在线客服系统,提供实时聊天、会话管理、客服分配、消息队列等功能",
"version": "1.0.0",
"enabled": true,
"priority": 10
}
]
}
```
### 2. 获取插件概览
```http
GET /api/v1/plugins/overview
```
### 3. 获取插件详情
```http
GET /api/v1/plugins/{pluginId}
```
### 4. 获取插件健康状态
```http
GET /api/v1/plugins/{pluginId}/health
```
### 5. 获取所有插件健康状态
```http
GET /api/v1/plugins/health
```
### 6. 获取插件统计信息
```http
GET /api/v1/plugins/{pluginId}/statistics
```
### 7. 获取所有插件统计信息
```http
GET /api/v1/plugins/statistics
```
## 创建自定义插件
### 步骤 1: 创建插件类
```java
package com.bytedesk.yourmodule.plugin;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import com.bytedesk.core.plugin.AbstractBytedeskPlugin;
@Component
public class YourModulePlugin extends AbstractBytedeskPlugin {
@Value("${bytedesk.yourmodule.enabled:true}")
private boolean enabled;
@Value("${bytedesk.yourmodule.version:1.0.0}")
private String version;
@Autowired(required = false)
private HealthIndicator yourModuleHealthIndicator;
@Override
protected HealthIndicator getHealthIndicator() {
return yourModuleHealthIndicator;
}
@Override
public String getPluginId() {
return "yourmodule";
}
@Override
public String getPluginName() {
return "Your Module Name";
}
@Override
public String getDescription() {
return "Your module description";
}
@Override
public String getVersion() {
return version;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public int getPriority() {
return 100; // 设置优先级
}
@Override
public String[] getDependencies() {
return new String[]{"core"}; // 设置依赖
}
}
```
### 步骤 2: 配置文件
`application.yml` 中添加:
```yaml
bytedesk:
yourmodule:
enabled: true
version: 1.0.0
```
### 步骤 3: 创建健康检查器(可选)
```java
@Slf4j
@Component
public class YourModuleHealthIndicator implements HealthIndicator {
@Override
public Health health() {
try {
// 执行健康检查逻辑
return Health.up()
.withDetail("status", "Running")
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}
```
## 插件优先级
优先级数值越小,优先级越高。建议范围:
- **1-10**: 核心功能(如 service
- **11-20**: 重要功能(如 ai
- **21-30**: 常规功能(如 kbase
- **31-50**: 辅助功能(如 ticket, call, voc
- **51-100**: 扩展功能
## 插件依赖
插件可以声明依赖其他插件:
```java
@Override
public String[] getDependencies() {
return new String[]{"core", "kbase"};
}
```
插件注册中心会在注册时检查依赖是否满足。
## 配置选项
### 启用/禁用插件
```yaml
bytedesk:
kbase:
enabled: true # 启用知识库插件
call:
enabled: false # 禁用呼叫中心插件
```
### 设置插件版本
```yaml
bytedesk:
service:
version: 1.2.0
```
## 监控和管理
### 通过 Actuator 端点
```bash
# 查看应用健康状态(包含所有模块)
curl http://localhost:9003/actuator/health
# 查看特定模块健康状态
curl http://localhost:9003/actuator/health/kbase
curl http://localhost:9003/actuator/health/service
```
### 通过插件 API
```bash
# 查看插件概览
curl http://localhost:9003/api/v1/plugins/overview
# 查看所有插件健康状态
curl http://localhost:9003/api/v1/plugins/health
# 查看特定插件详情
curl http://localhost:9003/api/v1/plugins/kbase
```
## 最佳实践
1. **命名规范**
- 插件ID使用小写字母`kbase`, `service`
- 类名:使用 PascalCase + Plugin 后缀,如 `KbasePlugin`
2. **版本管理**
- 使用语义化版本号:`major.minor.patch`
- 在配置文件中集中管理版本
3. **健康检查**
- 为每个插件提供健康检查器
- 检查关键资源(数据库、缓存、外部服务等)
4. **依赖管理**
- 明确声明插件依赖
- 避免循环依赖
5. **错误处理**
- 插件初始化失败不应影响其他插件
- 提供详细的错误信息和日志
## 故障排查
### 插件未注册
**问题:** 插件列表中看不到某个插件
**排查步骤:**
1. 检查插件类是否添加了 `@Component` 注解
2. 检查插件类所在包是否被扫描
3. 查看启动日志中的插件注册信息
4. 检查插件是否被配置为禁用状态
### 依赖检查失败
**问题:** 插件因依赖问题注册失败
**排查步骤:**
1. 检查依赖的插件是否已注册
2. 查看插件注册顺序(按优先级)
3. 确认依赖关系是否正确
### 健康检查失败
**问题:** 插件健康状态为 DOWN
**排查步骤:**
1. 检查 HealthIndicator 实现
2. 查看健康检查错误日志
3. 确认相关资源数据库、Redis 等)是否正常
## 示例代码
### 获取插件信息
```java
@Autowired
private PluginRegistry pluginRegistry;
// 获取所有插件
List<BytedeskPlugin> plugins = pluginRegistry.getAllPlugins();
// 获取特定插件
Optional<BytedeskPlugin> plugin = pluginRegistry.getPlugin("kbase");
// 检查插件是否启用
boolean isEnabled = pluginRegistry.isPluginEnabled("service");
// 获取插件健康状态
Map<String, Object> health = plugin.get().getHealthStatus();
```
## 未来扩展
- [ ] 插件热加载/卸载
- [ ] 插件配置动态更新
- [ ] 插件间通信机制
- [ ] 插件市场
- [ ] 插件权限管理
## 相关文档
- [Health Indicator 文档](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.health)
- [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)
- [模块化架构设计](./ARCHITECTURE.md)
## 许可证
Business Source License 1.1 - https://github.com/Bytedesk/bytedesk/blob/main/LICENSE

View File

@@ -0,0 +1,87 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import com.bytedesk.core.plugin.AbstractBytedeskPlugin;
import lombok.extern.slf4j.Slf4j;
/**
* 知识库模块插件
* 提供知识库、文章、FAQ、向量检索等功能
*/
@Slf4j
@Component
public class KbasePlugin extends AbstractBytedeskPlugin {
@Value("${bytedesk.kbase.enabled:true}")
private boolean enabled;
@Value("${bytedesk.kbase.version:1.0.0}")
private String version;
@Autowired(required = false)
private HealthIndicator kbaseHealthIndicator;
@Override
protected HealthIndicator getHealthIndicator() {
return kbaseHealthIndicator;
}
@Override
public String getPluginId() {
return "kbase";
}
@Override
public String getPluginName() {
return "Knowledge Base";
}
@Override
public String getDescription() {
return "知识库管理系统提供文章、FAQ、智能问答、向量检索等功能";
}
@Override
public String getVersion() {
return version;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public int getPriority() {
return 20; // 知识库优先级较高
}
@Override
public String[] getDependencies() {
return new String[]{"core"}; // 依赖核心模块
}
@Override
public void initialize() {
super.initialize();
log.info("Knowledge Base Plugin initialized - Features: Article, FAQ, Vector Search, AI Integration");
}
}

View File

@@ -0,0 +1,87 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.service.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import com.bytedesk.core.plugin.AbstractBytedeskPlugin;
import lombok.extern.slf4j.Slf4j;
/**
* 在线客服模块插件
* 提供实时聊天、会话管理、客服分配等功能
*/
@Slf4j
@Component
public class ServicePlugin extends AbstractBytedeskPlugin {
@Value("${bytedesk.service.enabled:true}")
private boolean enabled;
@Value("${bytedesk.service.version:1.0.0}")
private String version;
@Autowired(required = false)
private HealthIndicator serviceHealthIndicator;
@Override
protected HealthIndicator getHealthIndicator() {
return serviceHealthIndicator;
}
@Override
public String getPluginId() {
return "service";
}
@Override
public String getPluginName() {
return "Customer Service";
}
@Override
public String getDescription() {
return "在线客服系统,提供实时聊天、会话管理、客服分配、消息队列等功能";
}
@Override
public String getVersion() {
return version;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public int getPriority() {
return 10; // 客服是核心功能,优先级最高
}
@Override
public String[] getDependencies() {
return new String[]{"core"}; // 依赖核心模块
}
@Override
public void initialize() {
super.initialize();
log.info("Customer Service Plugin initialized - Features: Live Chat, Session Management, Agent Assignment");
}
}

View File

@@ -0,0 +1,87 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.ticket.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import com.bytedesk.core.plugin.AbstractBytedeskPlugin;
import lombok.extern.slf4j.Slf4j;
/**
* 工单系统模块插件
* 提供工单管理、工单流转、SLA等功能
*/
@Slf4j
@Component
public class TicketPlugin extends AbstractBytedeskPlugin {
@Value("${bytedesk.ticket.enabled:true}")
private boolean enabled;
@Value("${bytedesk.ticket.version:1.0.0}")
private String version;
@Autowired(required = false)
private HealthIndicator ticketHealthIndicator;
@Override
protected HealthIndicator getHealthIndicator() {
return ticketHealthIndicator;
}
@Override
public String getPluginId() {
return "ticket";
}
@Override
public String getPluginName() {
return "Ticket System";
}
@Override
public String getDescription() {
return "工单管理系统提供工单创建、分配、流转、SLA管理等功能";
}
@Override
public String getVersion() {
return version;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public int getPriority() {
return 30; // 工单系统优先级中等
}
@Override
public String[] getDependencies() {
return new String[]{"core"}; // 依赖核心模块
}
@Override
public void initialize() {
super.initialize();
log.info("Ticket System Plugin initialized - Features: Ticket Management, Workflow, SLA");
}
}

View File

@@ -0,0 +1,87 @@
/*
* @Author: jackning 270580156@qq.com
* @Date: 2025-10-06 10:00:00
* @LastEditors: jackning 270580156@qq.com
* @LastEditTime: 2025-10-06 10: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.voc.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import com.bytedesk.core.plugin.AbstractBytedeskPlugin;
import lombok.extern.slf4j.Slf4j;
/**
* 客户之声模块插件
* 提供客户反馈、满意度调查、数据分析等功能
*/
@Slf4j
@Component
public class VocPlugin extends AbstractBytedeskPlugin {
@Value("${bytedesk.voc.enabled:true}")
private boolean enabled;
@Value("${bytedesk.voc.version:1.0.0}")
private String version;
@Autowired(required = false)
private HealthIndicator vocHealthIndicator;
@Override
protected HealthIndicator getHealthIndicator() {
return vocHealthIndicator;
}
@Override
public String getPluginId() {
return "voc";
}
@Override
public String getPluginName() {
return "Voice of Customer";
}
@Override
public String getDescription() {
return "客户之声系统,提供客户反馈收集、满意度调查、数据分析、报表生成等功能";
}
@Override
public String getVersion() {
return version;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public int getPriority() {
return 50; // VOC优先级较低
}
@Override
public String[] getDependencies() {
return new String[]{"core"}; // 依赖核心模块
}
@Override
public void initialize() {
super.initialize();
log.info("Voice of Customer Plugin initialized - Features: Feedback Collection, Satisfaction Survey, Analytics");
}
}