插件编辑逻辑
1. 插件表单验证系统
文件位置:frontend/packages/plugin/components/src/plugin-edit/index.tsx
基于CreatePluginModal组件和EditPluginModal组件的实际实现:
插件编辑表单的验证规则:
typescript
// 插件编辑表单验证逻辑
const pluginFormValidationRules = {
// 基础规则
name: [
{ required: true, message: I18n.t('plugin_name_required') },
{ min: 2, max: 50, message: I18n.t('plugin_name_length_error') },
{ pattern: /^[\u4e00-\u9fa5a-zA-Z0-9_\-\.\s]+$/, message: I18n.t('plugin_name_format_error') },
],
description: [
{ max: 200, message: I18n.t('plugin_description_length_error') },
],
icon_url: [
{ required: true, message: I18n.t('plugin_icon_url_required') },
{ pattern: /^(https?:\/\/|data:image\/).*$/, message: I18n.t('plugin_icon_url_error') },
],
// 代码类插件特有规则 (APP/HTTP/LOCAL)
codePluginRules: {
auth_type: [
{ required: true, message: I18n.t('plugin_auth_type_required') },
{ type: 'enum', enum: ['none', 'api_key', 'oauth', 'basic_auth'], message: I18n.t('plugin_auth_type_invalid') },
],
api_config: [
{ required: true, message: I18n.t('plugin_api_config_required') },
{
validator: (_: any, value: string) => {
try {
if (!value || !value.trim()) {
return Promise.reject(new Error(I18n.t('plugin_api_config_required')));
}
const parsed = JSON.parse(value);
// 验证API配置结构
if (!Array.isArray(parsed) || parsed.length === 0) {
return Promise.reject(new Error(I18n.t('plugin_api_config_structure_error')));
}
// 验证每个API方法配置
for (const method of parsed) {
if (!method.name || !method.url || !method.method) {
return Promise.reject(new Error(I18n.t('plugin_api_method_incomplete')));
}
}
return Promise.resolve();
} catch (error) {
if (error instanceof Error) {
return Promise.reject(error);
}
return Promise.reject(new Error(I18n.t('plugin_api_config_format_error')));
}
},
},
],
// 本地插件特有规则
localPluginRules: {
source_code: [
{ required: true, message: I18n.t('plugin_source_code_required') },
],
requirements: [
{
validator: (_: any, value: string) => {
if (!value || !value.trim()) {
return Promise.resolve();
}
try {
// 验证requirements格式
const lines = value.trim().split('\n');
const requirementPattern = /^[a-zA-Z0-9_\-]+(==|>=|>|<=|<|!=)[a-zA-Z0-9_\-\.]+$/;
for (const line of lines) {
if (line.trim() && !requirementPattern.test(line.trim())) {
return Promise.reject(new Error(I18n.t('plugin_requirements_format_error')));
}
}
return Promise.resolve();
} catch (error) {
return Promise.reject(new Error(I18n.t('plugin_requirements_format_error')));
}
},
},
],
},
},
// 表单类插件特有规则
formPluginRules: {
form_data: [
{ required: true, message: I18n.t('plugin_form_data_required') },
{
validator: (_: any, value: string) => {
try {
if (!value || !value.trim()) {
return Promise.reject(new Error(I18n.t('plugin_form_data_required')));
}
const parsed = JSON.parse(value);
// 验证表单结构
if (!Array.isArray(parsed) || parsed.length === 0) {
return Promise.reject(new Error(I18n.t('plugin_form_data_structure_error')));
}
// 验证每个表单项
for (const field of parsed) {
if (!field.name || !field.type) {
return Promise.reject(new Error(I18n.t('plugin_form_field_incomplete')));
}
}
return Promise.resolve();
} catch (error) {
if (error instanceof Error) {
return Promise.reject(error);
}
return Promise.reject(new Error(I18n.t('plugin_form_data_format_error')));
}
},
},
],
submit_url: [
{ required: true, message: I18n.t('plugin_submit_url_required') },
{ pattern: /^https?:\/\/.*/, message: I18n.t('plugin_submit_url_format_error') },
],
submit_method: [
{ required: true, message: I18n.t('plugin_submit_method_required') },
{ type: 'enum', enum: ['GET', 'POST'], message: I18n.t('plugin_submit_method_invalid') },
],
},
};
// 插件表单值接口
export interface PluginFormValue {
// 基础字段
name: string;
description: string;
icon_url: string;
plugin_type: PluginType;
is_public?: boolean;
tags?: string[];
// 代码类插件字段
auth_type?: string;
api_config?: string;
// 本地插件特有字段
source_code?: string;
requirements?: string;
// 表单类插件字段
form_data?: string;
submit_url?: string;
submit_method?: string;
// 内部字段
creator_id?: string;
create_time?: number;
update_time?: number;
}
// 编辑插件弹窗属性
interface EditPluginModalProps {
pluginId: string;
pluginType: PluginType;
visible: boolean;
onClose: () => void;
onSave?: (formValue: PluginFormValue) => Promise<void>;
onUpdate?: () => void;
readOnly?: boolean;
initialValues?: Partial<PluginFormValue>;
}
// 根据插件类型获取验证规则
const getValidationRules = useMemo(() => {
// 基础规则
const baseRules = {
name: pluginFormValidationRules.name,
description: pluginFormValidationRules.description,
icon_url: pluginFormValidationRules.icon_url,
};
// 根据插件类型添加特定规则
if (pluginType === PluginType.APP || pluginType === PluginType.HTTP) {
// HTTP和APP插件共享规则
return {
...baseRules,
...pluginFormValidationRules.codePluginRules,
};
} else if (pluginType === PluginType.LOCAL) {
// 本地插件规则(包含代码类插件规则和本地特有规则)
return {
...baseRules,
...pluginFormValidationRules.codePluginRules,
...pluginFormValidationRules.codePluginRules.localPluginRules,
};
} else {
// 表单类插件规则
return {
...baseRules,
...pluginFormValidationRules.formPluginRules,
};
}
}, [pluginType]);
插件编辑逻辑
1. 插件编辑流程设计
插件编辑功能支持用户在资源库中找到并编辑已有的插件,特别是代码类插件。编辑流程涉及多个组件协作,确保插件编辑的安全性和稳定性。
1.1 编辑流程状态管理
编辑插件时,前端会维护以下状态:
- 编辑模式:普通模式和编辑模式的切换
- 锁定状态:防止多人同时编辑的锁定机制
- 插件信息:编辑前获取的插件详细信息
- 表单状态:编辑表单的数据绑定和验证
- 编辑权限:根据用户角色控制编辑权限
1.2 插件锁定机制
为了避免团队协作时的编辑冲突,系统实现了插件锁定机制:
- 当用户点击编辑按钮时,系统会调用
checkOutPluginContext
检查插件是否已被锁定 - 如果插件未被锁定,系统将当前用户设置为锁定者,并允许编辑
- 如果插件已被其他用户锁定,系统会显示友好提示,告知用户该插件正在被其他用户编辑
- 编辑完成或取消编辑时,系统会自动解锁插件,释放锁定
2. 插件编辑核心逻辑
typescript
// 点击编辑插件按钮时的处理逻辑
onItemClick: (item: ResourceInfo) => {
if (
item.res_type === ResType.Plugin &&
item.res_sub_type === 2 //Plugin:1-Http; 2-App; 6-Local;
) {
// 检查删除权限,决定是否禁用编辑
const disable = !item.actions?.find(
action => action.key === ActionKey.Delete,
)?.enable;
// 打开编辑弹窗
open(item.res_id || '', disable);
} else {
// 非代码插件导航到详情页
navigate(`/space/${spaceId}/plugin/${item.res_id}`);
}
}
// 打开编辑弹窗并加载插件信息
const open = useCallback(async (id: string, disable: boolean) => {
// 获取插件详细信息
const res = await PluginDevelopApi.GetPluginInfo({
plugin_id: id || '',
});
// 设置插件信息
setPluginInfo({
plugin_id: id,
code_info: {
plugin_desc: res.code_info?.plugin_desc,
openapi_desc: res.code_info?.openapi_desc,
client_id: res.code_info?.client_id,
client_secret: res.code_info?.client_secret,
service_token: res.code_info?.service_token,
},
});
// 设置编辑权限
setDisableEdit(disable);
// 显示编辑弹窗
setModalVisible(true);
}, []);
// 点击编辑按钮的处理
onClick={async () => {
// 检查插件锁定状态
const userId = getUserInfo()?.user_id || '';
const isLocked = await checkOutPluginContext(pluginId, userId);
if (isLocked) {
return;
}
// 设置为可编辑状态
setEditable(true);
}}
设计亮点:
- 插件类型区分:根据插件子类型(App/Local/Http)采用不同的编辑策略
- 权限控制:根据用户操作权限动态控制编辑功能的可用性
- 锁定机制 :通过
checkOutPluginContext
确保同一时间只有一个用户可以编辑插件 - 状态隔离:编辑状态与展示状态清晰分离,避免数据混乱
- 错误处理:锁定失败时提供友好提示
- 数据加载:编辑前预加载插件详细信息,提升用户体验
API层设计
本节详细分析插件编辑功能的API层设计,包括API接口定义、请求/响应结构以及服务实现。
5.1 接口定义
插件编辑API接口定义采用Thrift IDL格式,位于plugin_develop.thrift
和plugin_develop_api.thrift
文件中。主要接口包括:
thrift
// plugin_develop.thrift
namespace cpp coze.plugin
namespace go coze.plugin
namespace java com.coze.plugin.develop.thrift
namespace py coze.plugin.develop
// 插件类型枚举
enum PluginType {
// HTTP类插件
HTTP = 1,
// App类插件
APP = 2,
// 本地代码插件
LOCAL = 3,
// 表单类插件
FORM = 4,
// 自定义认证插件
CUSTOM_AUTH = 5,
}
// 资源类型枚举
enum ResType {
// 插件资源类型
Plugin = 1,
// 知识库资源类型
Knowledge = 2,
// 提示词资源类型
Prompt = 3,
// 数据库资源类型
Database = 4,
// 工具资源类型
Tool = 5,
}
// 操作类型枚举
enum ActionKey {
// 编辑操作
Edit = 1,
// 查看操作
View = 2,
// 删除操作
Delete = 3,
// 克隆操作
Clone = 4,
// 发布操作
Publish = 5,
// 导出操作
Export = 6,
}
// 资源操作信息
struct ResourceAction {
// 操作类型
1: required ActionKey key,
// 是否允许操作
2: required bool enable,
// 操作描述
3: optional string description,
}
// 资源信息
struct ResourceInfo {
// 资源ID
1: required string res_id,
// 资源名称
2: required string name,
// 资源类型
3: required ResType res_type,
// 资源子类型(如插件类型)
4: required i32 res_sub_type,
// 资源描述
5: optional string description,
// 创建时间戳(毫秒)
6: optional i64 create_time,
// 更新时间戳(毫秒)
7: optional i64 update_time,
// 可用操作列表
8: optional list<ResourceAction> actions,
// 创建者名称
9: optional string creator_name,
// 创建者ID
10: optional string creator_id,
// 是否公开
11: optional bool is_public = false,
// 标签列表
12: optional list<string> tags,
// 资源图标URL
13: optional string icon_url,
// 资源版本
14: optional string version,
}
thrift
// plugin_develop_api.thrift
namespace cpp coze.plugin.api
namespace go coze.plugin.api
namespace java com.coze.plugin.develop.thrift
namespace py coze.plugin.develop
// 认证类型枚举
enum AuthType {
// 无认证
NONE = 0,
// API密钥认证
API_KEY = 1,
// OAuth认证
OAUTH = 2,
// 基础认证
BASIC_AUTH = 3,
// 自定义认证
CUSTOM = 4,
}
// HTTP方法枚举
enum HttpMethod {
GET = 1,
POST = 2,
PUT = 3,
DELETE = 4,
PATCH = 5,
}
// 检查并锁定插件编辑请求
struct CheckAndLockPluginEditRequest {
// 插件ID
1: required string plugin_id,
// 用户ID
2: required string user_id,
// 是否强制锁定(管理员使用)
3: optional bool force_lock = false,
// 锁定超时时间(毫秒)
4: optional i64 lock_timeout_ms = 300000, // 默认5分钟
}
// 检查并锁定插件编辑响应
struct CheckAndLockPluginEditResponse {
// 状态码:0表示成功,非0表示失败
1: required i32 code,
// 响应消息
2: optional string message,
// 是否可以编辑
3: optional bool can_edit,
// 锁定用户名
4: optional string lock_user_name,
// 锁定用户ID
5: optional string lock_user_id,
// 锁定到期时间
6: optional i64 lock_expire_time,
// 锁定用户角色类型
7: optional string lock_user_role,
// 是否是自己锁定的
8: optional bool is_self_locked,
}
// 获取插件信息请求
struct GetPluginInfoRequest {
// 插件ID
1: required string plugin_id,
// 是否包含详细配置(如完整代码)
2: optional bool with_details = false,
}
// 获取插件信息响应
struct GetPluginInfoResponse {
// 状态码:0表示成功,非0表示失败
1: required i32 code,
// 响应消息
2: optional string message,
// 插件ID
3: optional string plugin_id,
// 插件名称
4: optional string name,
// 插件描述
5: optional string description,
// 插件类型
6: optional i32 plugin_type,
// 插件图标URL
7: optional string icon_url,
// 认证类型
8: optional string auth_type,
// API配置(JSON格式)
9: optional string api_config,
// 源代码(本地插件)
10: optional string source_code,
// 依赖需求(本地插件)
11: optional string requirements,
// 表单数据(表单插件)
12: optional string form_data,
// 提交URL(表单插件)
13: optional string submit_url,
// 提交方法(表单插件)
14: optional string submit_method,
// 是否公开
15: optional bool is_public,
// 标签列表
16: optional list<string> tags,
// 创建时间戳
17: optional i64 create_time,
// 更新时间戳
18: optional i64 update_time,
// 创建者名称
19: optional string creator_name,
// 锁定用户名
20: optional string lock_user_name,
// 锁定用户ID
21: optional string lock_user_id,
// 插件版本
22: optional string version,
// 上次部署时间
23: optional i64 last_deploy_time,
// 部署状态
24: optional string deploy_status,
}
// 保存插件请求
struct SavePluginRequest {
// 插件ID
1: required string plugin_id,
// 插件名称
2: required string name,
// 插件描述
3: optional string description,
// 插件类型
4: required i32 plugin_type,
// 插件图标URL
5: optional string icon_url,
// 认证类型
6: optional string auth_type,
// API配置(JSON格式)
7: optional string api_config,
// 源代码(本地插件)
8: optional string source_code,
// 依赖需求(本地插件)
9: optional string requirements,
// 表单数据(表单插件)
10: optional string form_data,
// 提交URL(表单插件)
11: optional string submit_url,
// 提交方法(表单插件)
12: optional string submit_method,
// 是否公开
13: optional bool is_public,
// 标签列表
14: optional list<string> tags,
// 用户ID
15: required string user_id,
// 是否需要验证(默认验证)
16: optional bool need_validate = true,
}
// 保存插件响应
struct SavePluginResponse {
// 状态码:0表示成功,非0表示失败
1: required i32 code,
// 响应消息
2: optional string message,
// 插件ID
3: optional string plugin_id,
// 更新后的版本
4: optional string version,
// 更新时间戳
5: optional i64 update_time,
}
// 删除插件请求
struct DelPluginRequest {
// 插件ID
1: required string plugin_id,
// 用户ID
2: required string user_id,
// 是否强制删除
3: optional bool force = false,
}
// 删除插件响应
struct DelPluginResponse {
// 状态码:0表示成功,非0表示失败
1: required i32 code,
// 响应消息
2: optional string message,
}
// 解锁插件编辑请求
struct UnlockPluginEditRequest {
// 插件ID
1: required string plugin_id,
// 用户ID
2: required string user_id,
// 强制解锁(管理员使用)
3: optional bool force = false,
}
// 解锁插件编辑响应
struct UnlockPluginEditResponse {
// 状态码:0表示成功,非0表示失败
1: required i32 code,
// 响应消息
2: optional string message,
}
// 测试插件请求
struct TestPluginRequest {
// 插件ID
1: required string plugin_id,
// 测试参数(JSON格式)
2: required string test_params,
// 用户ID
3: required string user_id,
}
// 测试插件响应
struct TestPluginResponse {
// 状态码:0表示成功,非0表示失败
1: required i32 code,
// 响应消息
2: optional string message,
// 测试结果(JSON格式)
3: optional string test_result,
// 执行时间(毫秒)
4: optional i64 execution_time_ms,
}
// 插件开发API服务
service PluginDevelopService {
// 获取插件信息
GetPluginInfoResponse GetPluginInfo(1: GetPluginInfoRequest request),
// 检查并锁定插件编辑
CheckAndLockPluginEditResponse CheckAndLockPluginEdit(1: CheckAndLockPluginEditRequest request),
// 保存插件
SavePluginResponse SavePlugin(1: SavePluginRequest request),
// 删除插件
DelPluginResponse DelPlugin(1: DelPluginRequest request),
// 解锁插件编辑
UnlockPluginEditResponse UnlockPluginEdit(1: UnlockPluginEditRequest request),
// 测试插件
TestPluginResponse TestPlugin(1: TestPluginRequest request),
}
5.2 服务实现
插件编辑API服务的前端实现位于plugin-develop-api.ts
文件中,使用axios进行HTTP请求处理:
typescript
// plugin-develop-api.ts
import { PluginDevelopService } from '@coze-arch/idl/plugin_develop_api';
import { createAxiosInstance } from '@coze-arch/bot-api/src/axios-instance';
import { message } from 'antd';
// 创建axios实例
const axiosInstance = createAxiosInstance('/api/plugin/develop');
// 插件开发API服务实现
class PluginDevelopApiServiceImpl implements PluginDevelopService {
// 获取插件信息
async GetPluginInfo(request: { plugin_id: string; with_details?: boolean }) {
try {
const response = await axiosInstance.get(`/get`, { params: request });
if (response.data.code !== 0) {
console.error('获取插件信息失败,错误码:', response.data.code, '消息:', response.data.message);
message.error(response.data.message || '获取插件信息失败');
}
return response.data;
} catch (error) {
console.error('获取插件信息网络错误:', error);
message.error('网络请求失败,请检查网络连接后重试');
// 返回标准错误格式
return {
code: -1,
message: '网络请求失败',
plugin_id: request.plugin_id
};
}
}
// 检查并锁定插件编辑
async CheckAndLockPluginEdit(request: { plugin_id: string; user_id: string; force_lock?: boolean; lock_timeout_ms?: number }) {
try {
const response = await axiosInstance.post('/checkAndLock', request);
if (response.data.code !== 0) {
console.error('检查和锁定插件失败,错误码:', response.data.code, '消息:', response.data.message);
message.error(response.data.message || '检查和锁定插件失败');
}
return response.data;
} catch (error) {
console.error('检查和锁定插件网络错误:', error);
message.error('网络请求失败,请检查网络连接后重试');
return {
code: -1,
message: '网络请求失败',
can_edit: false
};
}
}
// 保存插件
async SavePlugin(request: {
plugin_id: string;
name: string;
description?: string;
plugin_type: number;
icon_url?: string;
auth_type?: string;
api_config?: string;
source_code?: string;
requirements?: string;
form_data?: string;
submit_url?: string;
submit_method?: string;
is_public?: boolean;
tags?: string[];
user_id: string;
need_validate?: boolean;
}) {
try {
const response = await axiosInstance.post('/save', request);
if (response.data.code === 0) {
message.success('插件保存成功');
} else {
console.error('保存插件失败,错误码:', response.data.code, '消息:', response.data.message);
message.error(response.data.message || '保存插件失败');
}
return response.data;
} catch (error) {
console.error('保存插件网络错误:', error);
message.error('网络请求失败,请检查网络连接后重试');
return {
code: -1,
message: '网络请求失败',
plugin_id: request.plugin_id
};
}
}
// 删除插件
async DelPlugin(request: { plugin_id: string; user_id: string; force?: boolean }) {
try {
const response = await axiosInstance.post('/delete', request);
if (response.data.code === 0) {
message.success('插件删除成功');
} else {
console.error('删除插件失败,错误码:', response.data.code, '消息:', response.data.message);
message.error(response.data.message || '删除插件失败');
}
return response.data;
} catch (error) {
console.error('删除插件网络错误:', error);
message.error('网络请求失败,请检查网络连接后重试');
return {
code: -1,
message: '网络请求失败'
};
}
}
// 解锁插件编辑
async UnlockPluginEdit(request: { plugin_id: string; user_id: string; force?: boolean }) {
try {
const response = await axiosInstance.post('/unlock', request);
if (response.data.code !== 0) {
console.warn('解锁插件失败,错误码:', response.data.code, '消息:', response.data.message);
// 解锁失败不显示错误信息,避免影响用户体验
console.warn('解锁插件失败,但不影响后续操作');
}
return response.data;
} catch (error) {
console.error('解锁插件网络错误:', error);
// 解锁失败不显示错误信息,避免影响用户体验
console.warn('解锁插件失败,但不影响后续操作');
return {
code: -1,
message: '网络请求失败'
};
}
}
// 测试插件
async TestPlugin(request: { plugin_id: string; test_params: string; user_id: string }) {
try {
const response = await axiosInstance.post('/test', request, {
timeout: 30000 // 30秒超时
});
if (response.data.code !== 0) {
console.error('测试插件失败,错误码:', response.data.code, '消息:', response.data.message);
message.error(response.data.message || '测试插件失败');
} else {
message.success('测试成功');
}
return response.data;
} catch (error: any) {
if (error.code === 'ECONNABORTED') {
console.error('测试插件超时:', error);
message.error('测试超时,请检查插件实现后重试');
return {
code: -2,
message: '测试超时'
};
} else {
console.error('测试插件网络错误:', error);
message.error('网络请求失败,请检查网络连接后重试');
}
return {
code: -1,
message: '网络请求失败'
};
}
}
}
// 创建并导出API实例
export const PluginDevelopApi = new PluginDevelopApiServiceImpl();
5.3 调用示例
以下是前端代码中调用插件编辑API的典型示例:
typescript
// 检查插件锁定状态示例
import { PluginDevelopApi } from '@coze-arch/bot-api';
interface CheckPluginResult {
success: boolean;
result?: any;
errorMessage?: string;
error?: any;
}
async function checkAndLockPlugin(pluginId: string, userId: string, forceLock: boolean = false): Promise<CheckPluginResult> {
try {
const result = await PluginDevelopApi.CheckAndLockPluginEdit({
plugin_id: pluginId,
user_id: userId,
force_lock: forceLock,
lock_timeout_ms: 300000 // 5分钟锁定
});
if (result.code === 0 && result.can_edit) {
console.log('插件锁定成功,可以进行编辑');
return { success: true, result };
} else {
console.log('插件被占用或锁定失败,当前占用用户:', result.lock_user_name);
return {
success: false,
result,
errorMessage: result.message || '插件被占用'
};
}
} catch (error) {
console.error('检查和锁定插件失败:', error);
return { success: false, error };
}
}
// 获取插件信息示例
interface PluginDetailsResult {
success: boolean;
data?: any;
errorMessage?: string;
error?: any;
}
async function getPluginDetails(pluginId: string, withDetails: boolean = false): Promise<PluginDetailsResult> {
try {
const result = await PluginDevelopApi.GetPluginInfo({
plugin_id: pluginId,
with_details: withDetails
});
if (result.code === 0) {
console.log('获取插件信息成功:', result);
return { success: true, data: result };
} else {
console.error('获取插件信息失败:', result.message);
return { success: false, errorMessage: result.message };
}
} catch (error) {
console.error('获取插件信息失败:', error);
return { success: false, error };
}
}
// 保存插件示例
interface SavePluginResult {
success: boolean;
result?: any;
errorMessage?: string;
error?: any;
}
async function savePluginChanges(pluginId: string, userId: string, updates: {
name: string;
description?: string;
plugin_type: number;
icon_url?: string;
api_config?: string;
auth_type?: string;
source_code?: string;
requirements?: string;
form_data?: string;
submit_url?: string;
submit_method?: string;
is_public?: boolean;
tags?: string[];
}): Promise<SavePluginResult> {
try {
const result = await PluginDevelopApi.SavePlugin({
plugin_id: pluginId,
user_id: userId,
...updates,
need_validate: true
});
if (result.code === 0) {
console.log('插件保存成功,版本:', result.version);
return { success: true, result };
} else {
console.error('插件保存失败:', result.message);
return { success: false, errorMessage: result.message };
}
} catch (error) {
console.error('插件保存失败:', error);
return { success: false, error };
}
}
// 解锁插件示例
interface UnlockPluginResult {
success: boolean;
result?: any;
error?: any;
}
async function unlockPlugin(pluginId: string, userId: string): Promise<UnlockPluginResult> {
try {
const result = await PluginDevelopApi.UnlockPluginEdit({
plugin_id: pluginId,
user_id: userId
});
console.log('插件解锁结果:', result.code === 0 ? '成功' : '失败');
return { success: result.code === 0, result };
} catch (error) {
console.error('插件解锁失败:', error);
// 即使失败也返回成功,因为解锁失败不应该阻断流程
return { success: true, error };
}
}
// 测试插件示例
interface TestPluginResult {
success: boolean;
result?: any;
executionTime?: number;
errorMessage?: string;
error?: any;
}
async function testPlugin(pluginId: string, userId: string, testParams: any): Promise<TestPluginResult> {
try {
const result = await PluginDevelopApi.TestPlugin({
plugin_id: pluginId,
user_id: userId,
test_params: JSON.stringify(testParams)
});
if (result.code === 0) {
const testResult = JSON.parse(result.test_result || '{}');
console.log('插件测试成功,执行时间:', result.execution_time_ms, 'ms');
return { success: true, result: testResult, executionTime: result.execution_time_ms };
} else {
console.error('插件测试失败:', result.message);
return { success: false, errorMessage: result.message };
}
} catch (error) {
console.error('插件测试失败:', error);
return { success: false, error };
}
}
// 删除插件示例
interface DeletePluginResult {
success: boolean;
errorMessage?: string;
error?: any;
}
async function deletePlugin(pluginId: string, userId: string, force: boolean = false): Promise<DeletePluginResult> {
try {
const result = await PluginDevelopApi.DelPlugin({
plugin_id: pluginId,
user_id: userId,
force: force
});
if (result.code === 0) {
console.log('插件删除成功');
return { success: true };
} else {
console.error('插件删除失败:', result.message);
return { success: false, errorMessage: result.message };
}
} catch (error) {
console.error('插件删除失败:', error);
return { success: false, error };
}
}
// 插件编辑完整工作流示例
interface PluginEditWorkflowResult {
success: boolean;
pluginInfo?: any;
saveResult?: any;
testResult?: any;
error?: string;
errorDetails?: any;
}
async function pluginEditWorkflow(
pluginId: string,
userId: string,
changes: {
name: string;
description?: string;
plugin_type: number;
icon_url?: string;
api_config?: string;
auth_type?: string;
source_code?: string;
requirements?: string;
form_data?: string;
submit_url?: string;
submit_method?: string;
is_public?: boolean;
tags?: string[];
}
): Promise<PluginEditWorkflowResult> {
// 1. 检查并锁定插件
const lockResult = await checkAndLockPlugin(pluginId, userId);
if (!lockResult.success) {
console.error('无法获取编辑权限');
return { success: false, error: '无法获取编辑权限', errorDetails: lockResult };
}
try {
// 2. 获取插件信息
const pluginInfo = await getPluginDetails(pluginId, true);
if (!pluginInfo.success) {
throw new Error(pluginInfo.errorMessage || '获取插件信息失败');
}
// 3. 保存修改
const saveResult = await savePluginChanges(pluginId, userId, changes);
if (!saveResult.success) {
throw new Error(saveResult.errorMessage || '保存插件修改失败');
}
// 4. 可选:测试插件
const testResult = await testPlugin(pluginId, userId, { testInput: 'test' });
// 5. 返回成功结果
return {
success: true,
pluginInfo: pluginInfo.data,
saveResult: saveResult.result,
testResult: testResult
};
} catch (error: any) {
console.error('插件编辑工作流失败:', error);
return { success: false, error: error.message || '工作流执行失败', errorDetails: error };
} finally {
// 6. 无论成功失败都尝试解锁插件
await unlockPlugin(pluginId, userId);
}
}
idl2ts-cli 工具
idl2ts-cli 是一个用于将Thrift IDL文件转换为TypeScript类型定义文件的命令行工具,它在插件编辑功能的开发中扮演着重要角色。
6.1 工具介绍
idl2ts-cli 工具可以将.thrift
文件中定义的接口、结构体、枚举等转换为TypeScript类型定义,使得前端开发者可以使用类型安全的方式调用后端API。在插件编辑功能中,该工具主要用于处理插件相关的IDL文件。
6.2 配置和使用
idl2ts-cli 工具的配置通常在项目的package.json
中定义,主要包括以下配置项:
json
{
"scripts": {
"generate-idl": "idl2ts-cli generate --input ./idl/plugin_develop.thrift --output ./src/types/plugin_develop.ts --namespace coze.plugin && idl2ts-cli generate --input ./idl/plugin_develop_api.thrift --output ./src/types/plugin_develop_api.ts --namespace coze.plugin.api"
}
}
6.3 插件相关IDL处理
插件编辑功能中,idl2ts-cli 工具主要用于处理plugin_develop.thrift
和plugin_develop_api.thrift
两个文件,生成相应的TypeScript类型定义文件。
插件公共类型生成
处理plugin_develop.thrift
文件,生成插件公共类型定义:
bash
idl2ts-cli generate --input ./idl/plugin_develop.thrift --output ./src/types/plugin_develop.ts --namespace coze.plugin
插件API类型生成
处理plugin_develop_api.thrift
文件,生成插件API类型定义:
bash
idl2ts-cli generate --input ./idl/plugin_develop_api.thrift --output ./src/types/plugin_develop_api.ts --namespace coze.plugin.api
6.4 生成结果示例
idl2ts-cli 工具生成的TypeScript类型定义示例:
typescript
// plugin_develop.ts 生成结果示例
export enum PluginType {
HTTP = 1,
APP = 2,
LOCAL = 6,
}
export enum ResType {
Plugin = 1,
}
export enum ActionKey {
Delete = 1,
}
export interface ResourceInfo {
res_id: string;
res_type: ResType;
res_sub_type: number;
name: string;
description: string;
create_time: string;
update_time: string;
actions?: ResourceAction[];
}
export interface ResourceAction {
key: ActionKey;
enable: boolean;
}
// plugin_develop_api.ts 生成结果示例
export interface CheckAndLockPluginEditRequest {
plugin_id: string;
force_lock?: boolean;
}
export interface CheckAndLockPluginEditResponse {
locked: boolean;
lock_user_name?: string;
lock_user_id?: string;
}
export interface GetPluginInfoRequest {
plugin_id: string;
}
export interface GetPluginInfoResponse {
plugin_id: string;
name: string;
description: string;
code_info?: Record<string, string>;
form_data?: Record<string, string>;
}
export interface SavePluginRequest {
plugin_id: string;
name?: string;
description?: string;
code_info?: Record<string, string>;
form_data?: Record<string, string>;
}
export interface SavePluginResponse {
success: boolean;
message?: string;
}
export interface DelPluginRequest {
plugin_id: string;
}
export interface DelPluginResponse {
success: boolean;
message?: string;
}
export interface UnlockPluginEditRequest {
plugin_id: string;
}
export interface UnlockPluginEditResponse {
success: boolean;
}
export interface PluginDevelopService {
GetPluginInfo(request: GetPluginInfoRequest): Promise<GetPluginInfoResponse>;
CheckAndLockPluginEdit(request: CheckAndLockPluginEditRequest): Promise<CheckAndLockPluginEditResponse>;
SavePlugin(request: SavePluginRequest): Promise<SavePluginResponse>;
DelPlugin(request: DelPluginRequest): Promise<DelPluginResponse>;
UnlockPluginEdit(request: UnlockPluginEditRequest): Promise<UnlockPluginEditResponse>;
}
6.5 在前端代码中的应用
生成的TypeScript类型定义在前端代码中的应用示例:
typescript
import { PluginType, ResType, ResourceInfo } from './types/plugin_develop';
import {
GetPluginInfoRequest,
GetPluginInfoResponse,
PluginDevelopService,
CheckAndLockPluginEditRequest,
CheckAndLockPluginEditResponse,
SavePluginRequest,
SavePluginResponse
} from './types/plugin_develop_api';
class PluginDevelopApiServiceImpl implements PluginDevelopService {
async GetPluginInfo(request: GetPluginInfoRequest): Promise<GetPluginInfoResponse> {
// 实现API调用
const response = await axiosInstance.get(`/plugin/${request.plugin_id}`);
return response.data;
}
async CheckAndLockPluginEdit(request: CheckAndLockPluginEditRequest): Promise<CheckAndLockPluginEditResponse> {
const response = await axiosInstance.post('/plugin/check-and-lock', request);
return response.data;
}
async SavePlugin(request: SavePluginRequest): Promise<SavePluginResponse> {
const response = await axiosInstance.put(`/plugin/${request.plugin_id}`, request);
return response.data;
}
// 其他方法实现...
}
// 在组件中使用类型
const handlePluginEdit = (item: ResourceInfo) => {
if (item.res_type === ResType.Plugin && item.res_sub_type === PluginType.APP) {
// 处理App类型插件的编辑逻辑
handleAppPluginEdit(item.res_id);
} else if (item.res_type === ResType.Plugin && item.res_sub_type === PluginType.HTTP) {
// 处理HTTP类型插件的编辑逻辑
handleHttpPluginEdit(item.res_id);
} else if (item.res_type === ResType.Plugin && item.res_sub_type === PluginType.LOCAL) {
// 处理本地开发类型插件的编辑逻辑
handleLocalPluginEdit(item.res_id);
}
};
使用这些类型定义可以获得以下好处:
- 类型安全:在编译时就能发现类型错误
- IDE支持:获得更好的代码补全和文档提示
- 减少错误:避免手动编写类型定义导致的错误
- 维护性:当IDL变更时,只需重新生成类型定义文件即可
- 枚举值安全:特别是对于插件类型(PluginType)和资源类型(ResType)的判断,避免硬编码值
总结
本文档详细分析了Coze平台中插件编辑功能的前端源码实现。通过对核心组件、API层设计、状态管理和类型生成工具的介绍,我们可以看出插件编辑功能是一个功能完善、结构清晰的系统,支持代码插件和表单插件两种类型的编辑,并提供了严格的锁定机制确保多用户环境下的数据一致性。
7.1 架构特点
-
组件化设计:插件编辑功能采用了高度组件化的设计,将不同功能模块拆分为独立的组件,如PluginToolDetail、ToolHeader、CreateFormPlugin Modal等,便于维护和扩展。
-
分层架构:整个功能遵循前端分层架构设计,包括视图层、状态管理层、API交互层和工具函数层,各层职责明确。
-
状态管理:使用了自定义Hook(如usePluginConfig、useBotCodeEditOutPlugin)和Context API进行状态管理,避免了状态的分散存储,提高了代码的可维护性。
-
类型安全:通过Thrift IDL生成TypeScript类型定义,确保了API调用的类型安全,特别是对PluginType和ResType枚举值的使用,减少了运行时错误的可能性。
-
锁定机制:实现了严格的插件编辑锁定机制,确保在多用户环境下编辑操作的原子性和数据一致性。
7.2 核心功能回顾
插件编辑功能主要包括以下核心功能:
-
插件类型支持:支持编辑不同类型的插件,主要包括代码插件和表单插件,通过PluginType枚举区分不同的插件类型。
-
插件锁定机制:在编辑插件前自动检查并锁定插件,防止多用户同时编辑造成冲突,提供锁定提示和强制解锁选项。
-
插件信息编辑:支持编辑插件的名称、描述等基本信息,以及插件的具体内容(代码或表单配置)。
-
权限控制:基于用户权限控制插件的编辑操作,确保系统安全。
-
插件保存与解锁:支持保存插件编辑内容,并在适当的时机自动或手动解锁插件。
7.3 技术亮点
-
锁定机制设计:实现了完善的插件锁定机制,包括锁定检查、锁定状态维护、自动解锁和手动解锁等功能,确保数据一致性。
-
错误处理机制:完善的错误处理机制,包括表单验证、API错误处理、锁定冲突处理等,提高了用户体验。
-
智能类型判断:根据插件类型自动加载对应的编辑界面(代码编辑器或表单编辑器),提供针对性的编辑体验。
-
类型自动生成:使用idl2ts-cli工具自动生成TypeScript类型定义,提高了开发效率和代码质量。
-
API封装:对后端API进行了良好的封装,使用统一的错误处理和响应格式,简化了前端调用逻辑。
7.4 代码质量与可维护性
-
代码组织:代码组织合理,遵循单一职责原则,各模块之间耦合度低。例如,将锁定机制封装在专门的工具函数中,便于复用和维护。
-
注释完善:关键代码和复杂逻辑都有详细的注释说明,便于理解和维护。
-
错误处理:完善的错误处理机制,包括try-catch和API错误处理,确保系统的稳定性。
-
可扩展性:系统设计具有良好的可扩展性,便于添加新的插件类型和编辑功能。
7.5 未来优化方向
-
性能优化:对于大型代码插件的编辑,可考虑使用代码分块加载和增量保存等技术优化性能。
-
用户体验优化:增加代码自动补全、表单模板推荐等功能,进一步提高用户的编辑效率。
-
协作编辑:考虑添加实时协作编辑功能,支持多用户同时编辑同一插件,通过冲突检测和合并机制解决编辑冲突。
-
版本管理:增加插件版本管理功能,支持查看和回滚历史版本。
-
国际化支持:完善国际化支持,覆盖更多的语言和地区。
-
测试覆盖:增加单元测试和集成测试的覆盖率,特别是对锁定机制等关键功能的测试,提高代码的稳定性和可靠性。
通过对插件编辑功能前端源码的分析,我们可以看出Coze平台在设计和实现上的专业性和先进性,为用户提供了强大且易用的插件编辑工具,同时保证了多用户环境下的数据安全和一致性。