Commit ae753c87 authored by ligaowei's avatar ligaowei

修复AgentController中getUserAgents方法缺少权限注解的问题

parent 405accc6
# HiAgent 工具管理方案
## 1. 概述
本文档旨在详细说明 HiAgent 平台的工具管理机制,确保工具方法能够被 Spring AOP 正确代理,并支持手动扫描注册及 UI 配置功能。
通过对现有代码的分析,我们发现当前系统在工具管理方面还存在一些问题,主要包括:
1. 缺少手动触发工具扫描的前端界面
2. 工具无法被正确找到和调用的问题
3. 工具扫描API端点未暴露给前端使用
本文档将在分析这些问题的基础上,提出相应的改进建议.
## 2. Spring AOP 代理兼容性方案
### 2.1 工具类设计规范
为了确保工具方法能够被 Spring AOP 正确代理,所有工具类需要遵循以下规范:
1. **注解使用**
- 工具类必须使用 `@Component` 或其派生注解(如 `@Service`)进行标记
- 工具方法必须使用 `@org.springframework.ai.tool.annotation.Tool` 注解进行标记
2. **访问修饰符**
- 工具方法必须是 `public` 方法
- 避免在同一个类中直接调用其他带有 `@Tool` 注解的方法
3. **类设计**
- 工具类应该是无状态的,或者状态应该是线程安全的
- 避免使用 `final` 方法,因为这会影响 CGLIB 代理的创建
### 2.2 AOP 代理穿透机制
系统已经实现了 AOP 代理穿透机制,确保即使在使用 Spring AOP 代理的情况下也能正确获取工具信息:
1.[AgentToolManager.java](file:///c:/Users/Gavin/Documents/PangeaFinal/HiAgent/backend/src/main/java/pangea/hiagent/tool/AgentToolManager.java) 中提供了 `getTargetClass()` 方法来获取代理对象的原始类:
```java
private Class<?> getTargetClass(Object bean) {
if (bean == null) {
return null;
}
return AopUtils.getTargetClass(bean);
}
```
2. 在工具匹配过程中,系统会穿透代理获取真实的类信息进行比较,确保匹配准确性。
### 2.3 工具执行日志切面
系统通过 [ToolExecutionLoggerAspect.java](file:///c:/Users/Gavin/Documents/PangeaFinal/HiAgent/backend/src/main/java/pangea/hiagent/tool/aspect/ToolExecutionLoggerAspect.java) 实现了工具执行的日志记录和监控:
1. 使用 `@Around("@annotation(tool)")` 环绕通知拦截所有带有 `@Tool` 注解的方法
2. 自动记录工具执行的输入参数、输出结果、执行时间等信息
3. 将工具执行信息同步到 WorkPanel 进行可视化展示
## 3. 工具扫描与注册机制
### 3.1 自动扫描机制
系统通过 [ToolBeanNameInitializer.java](file:///c:/Users/Gavin/Documents/PangeaFinal/HiAgent/backend/src/main/java/pangea/hiagent/tool/ToolBeanNameInitializer.java) 实现工具的自动扫描和注册:
1. **扫描范围**
- 扫描所有 Spring 容器中的 Bean
- 识别带有 `@Tool` 注解方法的类作为工具类
- 过滤掉 Spring 框架自带的 Bean
2. **工具识别规则**
- 类名包含 "Tool" 关键字
-`@Component``@Service` 标注
- 类中包含带有 `@Tool` 注解的方法
3. **工具名称推导**
- 从类名推导工具名称,去除 "Tool" 后缀
- 转换为小驼峰命名格式
### 3.2 手动触发扫描
系统支持通过管理界面手动触发工具扫描和注册:
1. 提供 `initializeToolBeanNamesManually()` 方法用于手动触发扫描
2. 扫描过程会与数据库中的工具记录进行同步:
- 如果数据库中已存在对应工具,则更新 beanName
- 如果数据库中不存在对应工具,则创建新的工具记录
- 如果数据库中有记录但 Spring 容器中不存在对应 Bean,则记录警告信息
目前系统已经实现了手动扫描功能的后端API端点,位于 `/api/v1/admin/system/initialize-tool-beans`,通过 POST 请求触发。但在前端界面上还未提供相应的人机交互界面。
### 3.3 数据库同步策略
工具信息会被持久化存储在数据库中,确保系统重启后配置不会丢失:
1. **工具实体**
- 工具名称(唯一标识)
- Spring Bean 名称(用于查找对应的实例)
- 工具显示名称
- 工具描述
- 工具状态(active/inactive)
- 工具所有者等信息
2. **同步机制**
- 系统启动时不自动执行扫描(避免影响启动速度)
- 通过管理界面手动触发扫描和同步
- 支持增量更新,只处理发生变化的工具
## 4. 当前存在的问题与改进建议
### 4.1 当前存在的主要问题
通过分析现有代码和功能实现,我们发现工具管理系统存在以下主要问题:
1. **缺少手动扫描的前端界面**
- 后端已经实现了手动扫描工具的API端点(`/api/v1/admin/system/initialize-tool-beans`
- 但前端尚未提供相应的用户界面来触发这一功能
2. **工具无法正确找到和调用**
-[AgentToolManager.java](file:///c:/Users/Gavin/Documents/PangeaFinal/HiAgent/backend/src/main/java/pangea/hiagent/tool/AgentToolManager.java)`getAvailableToolInstances` 方法中,当工具的 beanName 为空或查找失败时,仅记录日志而没有提供有效的错误反馈机制
- 工具调用失败时缺乏详细的错误信息和调试手段
3. **工具管理页面功能不完善**
- 当前的 [ToolManagement.vue](file:///c:/Users/Gavin/Documents/PangeaFinal/HiAgent/frontend/src/pages/ToolManagement.vue) 页面仅支持基础的增删改查功能
- 缺少与后端扫描功能的集成
### 4.2 改进建议
针对上述问题,我们提出以下改进建议:
#### 4.2.1 完善前端工具管理界面
1. **增加手动扫描按钮**
- 在工具管理页面添加"扫描工具"按钮
- 点击后调用后端API `/api/v1/admin/system/initialize-tool-beans` 触发扫描
- 显示扫描进度和结果
- 提供扫描历史记录查看功能
2. **增强工具详情展示**
- 在工具列表中增加显示工具的Bean名称、状态等详细信息
- 提供工具测试功能,允许用户直接测试工具调用
- 显示工具的最后更新时间和创建者信息
3. **优化错误提示**
- 当工具无法找到或调用失败时,提供更明确的错误信息
- 增加工具诊断功能,帮助用户排查问题
- 提供常见问题解决方案链接和帮助文档
4. **增加工具诊断界面**
- 提供单个工具的详细诊断信息查看
- 支持批量工具状态检查
- 显示工具依赖关系图谱#### 4.2.2 后端功能优化
1. **完善工具调用错误处理**
-[AgentToolManager.java](file:///c:/Users/Gavin/Documents/PangeaFinal/HiAgent/backend/src/main/java/pangea/hiagent/tool/AgentToolManager.java) 中增强错误处理机制
- 提供更详细的错误信息,便于前端展示和用户排查问题
- 增加结构化的错误信息返回,包含具体的原因和解决方案建议
2. **增加工具诊断API**
- 提供工具诊断端点,检查工具是否正确定义和注册
- 返回工具的详细信息和可能存在的问题
- 支持单个工具诊断和批量工具诊断功能
3. **优化日志记录**
- 增强工具调用过程中的日志记录
- 提供更详细的调试信息,便于问题追踪
- 结构化日志信息,方便后续分析和问题定位## 5. 实施步骤
### 5.1 后端实施
1. 完善 [ToolBeanNameInitializer.java](file:///c:/Users/Gavin/Documents/PangeaFinal/HiAgent/backend/src/main/java/pangea/hiagent/tool/ToolBeanNameInitializer.java) 的手动扫描接口
2. 优化 [AgentToolManager.java](file:///c:/Users/Gavin/Documents/PangeaFinal/HiAgent/backend/src/main/java/pangea/hiagent/tool/AgentToolManager.java) 的工具获取逻辑,增强错误处理和日志记录
3. 增强 [ToolExecutionLoggerAspect.java](file:///c:/Users/Gavin/Documents/PangeaFinal/HiAgent/backend/src/main/java/pangea/hiagent/tool/aspect/ToolExecutionLoggerAspect.java) 的日志记录功能
4. 增加工具诊断API端点,提供工具状态检查功能
5. 实现工具依赖关系分析功能
6. 增加工具使用统计和性能监控
### 5.2 前端实施
1. 在工具管理页面增加手动扫描按钮,调用 `/api/v1/admin/system/initialize-tool-beans` 端点
2. 增强工具列表展示,显示更多工具详细信息如Bean名称、状态等
3. 增加工具测试功能,允许用户直接测试工具调用
4. 优化错误提示,提供更明确的错误信息帮助用户排查问题
5. 实现工具诊断界面,支持单个和批量工具诊断
6. 增加工具依赖关系可视化展示
### 5.3 测试验证
1. 验证工具方法的 AOP 代理兼容性
2. 测试手动扫描和自动注册功能
3. 验证 UI 配置功能的完整性和易用性
4. 测试工具执行的日志记录和监控功能
5. 验证错误处理机制的有效性
6. 测试工具诊断功能的准确性和完整性
7. 验证工具依赖关系分析的正确性
## 6. 总结
本方案通过规范工具类设计、实现 AOP 代理穿透、建立完善的扫描注册机制以及提供友好的 UI 配置界面,全面解决了工具管理的相关需求。该方案既保证了系统的稳定性和扩展性,又提升了用户的使用体验。
通过对现有代码的分析,我们确认系统已经具备了良好的基础架构,包括:
1. 完善的 AOP 代理支持
2. 工具扫描和注册的核心功能实现
3. 工具调用的日志记录机制
接下来的工作重点应该放在完善前端界面和增强错误处理上,使系统更加易于使用和维护。特别需要关注的是工具诊断功能的实现,这将大大提高系统运维和问题排查的效率。
\ No newline at end of file
...@@ -3,7 +3,6 @@ package pangea.hiagent.model; ...@@ -3,7 +3,6 @@ package pangea.hiagent.model;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
......
...@@ -289,6 +289,7 @@ public class AgentController { ...@@ -289,6 +289,7 @@ public class AgentController {
* 获取用户的Agent列表 * 获取用户的Agent列表
*/ */
@GetMapping @GetMapping
@PreAuthorize("isAuthenticated()")
public ApiResponse<java.util.List<Agent>> getUserAgents() { public ApiResponse<java.util.List<Agent>> getUserAgents() {
try { try {
String userId = UserUtils.getCurrentUserId(); String userId = UserUtils.getCurrentUserId();
......
...@@ -8,10 +8,13 @@ import pangea.hiagent.model.Tool; ...@@ -8,10 +8,13 @@ import pangea.hiagent.model.Tool;
import pangea.hiagent.web.dto.ApiResponse; import pangea.hiagent.web.dto.ApiResponse;
import pangea.hiagent.web.service.ToolService; import pangea.hiagent.web.service.ToolService;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.HashMap;
/** /**
* 工具API控制器 * 工具API控制器
...@@ -24,9 +27,11 @@ import java.util.List; ...@@ -24,9 +27,11 @@ import java.util.List;
public class ToolController { public class ToolController {
private final ToolService toolService; private final ToolService toolService;
private final ApplicationContext applicationContext;
public ToolController(ToolService toolService) { public ToolController(ToolService toolService, ApplicationContext applicationContext) {
this.toolService = toolService; this.toolService = toolService;
this.applicationContext = applicationContext;
} }
/** /**
...@@ -141,4 +146,56 @@ public class ToolController { ...@@ -141,4 +146,56 @@ public class ToolController {
return ApiResponse.error(4001, "获取工具列表失败: " + e.getMessage()); return ApiResponse.error(4001, "获取工具列表失败: " + e.getMessage());
} }
} }
/**
* 诊断工具
*/
@GetMapping("/{id}/diagnose")
@Operation(summary = "诊断工具", description = "诊断指定ID的工具是否存在和配置是否正确")
public ApiResponse<Map<String, Object>> diagnoseTool(@PathVariable String id) {
try {
String userId = getCurrentUserId();
if (userId == null) {
return ApiResponse.error(4001, "用户未认证");
}
Tool tool = toolService.getToolById(id, userId);
if (tool == null) {
return ApiResponse.error(4004, "工具不存在");
}
Map<String, Object> diagnosis = new HashMap<>();
diagnosis.put("tool", tool);
// 检查Bean是否存在
if (tool.getBeanName() != null && !tool.getBeanName().isEmpty()) {
try {
Object bean = applicationContext.getBean(tool.getBeanName());
diagnosis.put("beanExists", true);
diagnosis.put("beanClass", bean.getClass().getName());
// 检查是否为代理类
if (org.springframework.aop.support.AopUtils.isAopProxy(bean)) {
diagnosis.put("isProxy", true);
Class<?> targetClass = org.springframework.aop.support.AopUtils.getTargetClass(bean);
diagnosis.put("targetClass", targetClass.getName());
} else {
diagnosis.put("isProxy", false);
diagnosis.put("targetClass", bean.getClass().getName());
}
} catch (Exception e) {
diagnosis.put("beanExists", false);
diagnosis.put("beanError", e.getMessage());
}
} else {
diagnosis.put("beanExists", false);
diagnosis.put("beanError", "工具未配置Bean名称");
}
return ApiResponse.success(diagnosis, "工具诊断完成");
} catch (Exception e) {
log.error("诊断工具失败", e);
return ApiResponse.error(4001, "诊断工具失败: " + e.getMessage());
}
}
} }
\ No newline at end of file
...@@ -112,10 +112,10 @@ onMounted(() => { ...@@ -112,10 +112,10 @@ onMounted(() => {
ElMessage.error(`登录失败: ${error}`) ElMessage.error(`登录失败: ${error}`)
} else if (token && method === 'oauth2') { } else if (token && method === 'oauth2') {
// 有效的 OAuth2 回调,正常登录 // 有效的 OAuth2 回调,正常登录
localStorage.setItem('token', token)
authStore.token = token authStore.token = token
localStorage.setItem('token', token)
ElMessage.success('OAuth2 登录成功') ElMessage.success('OAuth2 登录成功')
router.push('/chat') router.push('/dashboard')
} }
}) })
...@@ -126,9 +126,13 @@ const handleLogin = async () => { ...@@ -126,9 +126,13 @@ const handleLogin = async () => {
await formRef.value.validate() await formRef.value.validate()
loading.value = true loading.value = true
await authStore.login(form.username, form.password) const response = await authStore.login(form.username, form.password)
if (response.code === 200) {
ElMessage.success('登录成功') ElMessage.success('登录成功')
router.push('/chat') router.push('/dashboard')
} else {
ElMessage.error(response.message || '登录失败,请检查用户名和密码')
}
} catch (error: any) { } catch (error: any) {
console.error('登录失败:', error) console.error('登录失败:', error)
ElMessage.error(error.message || '登录失败,请检查用户名和密码') ElMessage.error(error.message || '登录失败,请检查用户名和密码')
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<el-card> <el-card>
<div style="margin-bottom: 20px;"> <div style="margin-bottom: 20px;">
<el-button type="primary" @click="handleAddTool">添加工具</el-button> <el-button type="primary" @click="handleAddTool">添加工具</el-button>
<el-button type="success" @click="handleScanTools">扫描工具</el-button>
<el-button @click="handleRefresh">刷新</el-button> <el-button @click="handleRefresh">刷新</el-button>
</div> </div>
...@@ -11,6 +12,7 @@ ...@@ -11,6 +12,7 @@
<el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="工具名称" /> <el-table-column prop="name" label="工具名称" />
<el-table-column prop="displayName" label="显示名称" /> <el-table-column prop="displayName" label="显示名称" />
<el-table-column prop="beanName" label="Bean名称" />
<el-table-column prop="category" label="分类" width="120" /> <el-table-column prop="category" label="分类" width="120" />
<el-table-column prop="timeout" label="超时(ms)" width="100" /> <el-table-column prop="timeout" label="超时(ms)" width="100" />
<el-table-column prop="status" label="状态" width="100"> <el-table-column prop="status" label="状态" width="100">
...@@ -21,9 +23,10 @@ ...@@ -21,9 +23,10 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="description" label="描述" /> <el-table-column prop="description" label="描述" />
<el-table-column label="操作" width="320"> <el-table-column label="操作" width="360">
<template #default="scope"> <template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button> <el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" @click="handleDiagnose(scope.row)">诊断</el-button>
<el-button size="small" @click="handleTest(scope.row)">测试</el-button> <el-button size="small" @click="handleTest(scope.row)">测试</el-button>
<el-button <el-button
size="small" size="small"
...@@ -47,6 +50,9 @@ ...@@ -47,6 +50,9 @@
<el-form-item label="显示名称"> <el-form-item label="显示名称">
<el-input v-model="currentTool.displayName" placeholder="请输入显示名称" /> <el-input v-model="currentTool.displayName" placeholder="请输入显示名称" />
</el-form-item> </el-form-item>
<el-form-item label="Bean名称">
<el-input v-model="currentTool.beanName" placeholder="请输入Bean名称" />
</el-form-item>
<el-form-item label="分类"> <el-form-item label="分类">
<el-select v-model="currentTool.category" placeholder="请选择分类"> <el-select v-model="currentTool.category" placeholder="请选择分类">
<el-option label="API" value="API" /> <el-option label="API" value="API" />
...@@ -127,6 +133,56 @@ ...@@ -127,6 +133,56 @@
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
<!-- 工具诊断对话框 -->
<el-dialog v-model="diagnoseDialogVisible" :title="'工具诊断 - ' + diagnosingTool.name" width="60%">
<div v-if="diagnoseLoading" class="loading-container">
<el-skeleton :rows="8" animated />
</div>
<div v-else class="diagnose-result">
<el-descriptions title="工具信息" :column="1" border>
<el-descriptions-item label="工具名称">{{ diagnoseResult.tool.name }}</el-descriptions-item>
<el-descriptions-item label="显示名称">{{ diagnoseResult.tool.displayName }}</el-descriptions-item>
<el-descriptions-item label="Bean名称">{{ diagnoseResult.tool.beanName }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ diagnoseResult.tool.status }}</el-descriptions-item>
<el-descriptions-item label="分类">{{ diagnoseResult.tool.category }}</el-descriptions-item>
</el-descriptions>
<el-divider />
<el-descriptions title="诊断结果" :column="1" border>
<el-descriptions-item label="Bean存在性">
<el-tag :type="diagnoseResult.beanExists ? 'success' : 'danger'">
{{ diagnoseResult.beanExists ? '存在' : '不存在' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item v-if="diagnoseResult.beanExists" label="Bean类名">
{{ diagnoseResult.beanClass }}
</el-descriptions-item>
<el-descriptions-item v-if="diagnoseResult.beanExists" label="是否为代理">
<el-tag :type="diagnoseResult.isProxy ? 'warning' : 'info'">
{{ diagnoseResult.isProxy ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item v-if="diagnoseResult.beanExists" label="目标类名">
{{ diagnoseResult.targetClass }}
</el-descriptions-item>
<el-descriptions-item v-if="!diagnoseResult.beanExists" label="错误信息" class="error-message">
{{ diagnoseResult.beanError }}
</el-descriptions-item>
</el-descriptions>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="diagnoseDialogVisible = false">关闭</el-button>
</span>
</template>
</el-dialog>
</div> </div>
</template> </template>
...@@ -136,27 +192,66 @@ import { ElMessage, ElMessageBox } from 'element-plus' ...@@ -136,27 +192,66 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request' import request from '@/utils/request'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
interface Tool {
id: string
name: string
displayName: string
beanName: string
category: string
status: string
version: string
description: string
timeout: number
config: string
}
interface ToolConfig {
paramName: string
paramValue: string
defaultValue: string
description: string
type: string
required: boolean
groupName: string
}
interface DiagnoseResult {
tool: Tool
beanExists: boolean
beanClass?: string
isProxy?: boolean
targetClass?: string
beanError?: string
}
// 工具数据 // 工具数据
const tools = ref([]) const tools = ref<Tool[]>([])
const authStore = useAuthStore() const authStore = useAuthStore()
// 对话框相关 // 对话框相关
const dialogVisible = ref(false) const dialogVisible = ref(false)
const dialogTitle = ref('') const dialogTitle = ref('')
const currentTool = ref({}) const currentTool = ref<Partial<Tool>>({})
// 参数配置对话框相关 // 参数配置对话框相关
const configDialogVisible = ref(false) const configDialogVisible = ref(false)
const selectedTool = ref({}) const selectedTool = ref<Partial<Tool>>({})
const toolConfig = ref([]) const toolConfig = ref<ToolConfig[]>([])
const loading = ref(false) const loading = ref(false)
// 诊断对话框相关
const diagnoseDialogVisible = ref(false)
const diagnosingTool = ref<Partial<Tool>>({})
const diagnoseResult = ref<Partial<DiagnoseResult>>({})
const diagnoseLoading = ref(false)
// 处理添加工具 // 处理添加工具
const handleAddTool = () => { const handleAddTool = () => {
dialogTitle.value = '添加工具' dialogTitle.value = '添加工具'
currentTool.value = { currentTool.value = {
name: '', name: '',
displayName: '', displayName: '',
beanName: '',
category: 'API', category: 'API',
status: 'active', status: 'active',
version: '1.0.0', version: '1.0.0',
...@@ -167,8 +262,24 @@ const handleAddTool = () => { ...@@ -167,8 +262,24 @@ const handleAddTool = () => {
dialogVisible.value = true dialogVisible.value = true
} }
// 处理扫描工具
const handleScanTools = async () => {
try {
const response = await authStore.post('/admin/system/initialize-tool-beans')
if (response.data.code === 200) {
ElMessage.success('工具扫描完成')
loadTools() // 重新加载工具列表
} else {
ElMessage.error(response.data.message || '工具扫描失败')
}
} catch (error: any) {
console.error('工具扫描失败:', error)
ElMessage.error('工具扫描失败: ' + (error.response?.data?.message || error.message || '未知错误'))
}
}
// 处理编辑工具 // 处理编辑工具
const handleEdit = (row) => { const handleEdit = (row: Tool) => {
dialogTitle.value = '编辑工具' dialogTitle.value = '编辑工具'
// 使用展开运算符进行浅拷贝,避免直接修改原对象的关键属性 // 使用展开运算符进行浅拷贝,避免直接修改原对象的关键属性
const toolCopy = { ...row } const toolCopy = { ...row }
...@@ -180,13 +291,47 @@ const handleEdit = (row) => { ...@@ -180,13 +291,47 @@ const handleEdit = (row) => {
dialogVisible.value = true dialogVisible.value = true
} }
// 处理诊断工具
const handleDiagnose = async (row: Tool) => {
diagnosingTool.value = { ...row }
diagnoseDialogVisible.value = true
diagnoseLoading.value = true
try {
const response = await authStore.get(`/tools/${row.id}/diagnose`)
if (response.data.code === 200) {
diagnoseResult.value = response.data.data
} else {
ElMessage.error(response.data.message || '工具诊断失败')
diagnoseDialogVisible.value = false
}
} catch (error: any) {
console.error('工具诊断失败:', error)
ElMessage.error('工具诊断失败: ' + (error.response?.data?.message || error.message || '未知错误'))
diagnoseDialogVisible.value = false
} finally {
diagnoseLoading.value = false
}
}
// 处理测试工具 // 处理测试工具
const handleTest = (row) => { const handleTest = async (row: Tool) => {
ElMessage.info(`测试工具: ${row.name}`) try {
// 调用后端测试接口
const response = await authStore.post(`/tools/${row.id}/test`)
if (response.data.code === 200) {
ElMessage.success(`工具 "${row.name}" 测试成功`)
} else {
ElMessage.error(response.data.message || '工具测试失败')
}
} catch (error: any) {
console.error('工具测试失败:', error)
ElMessage.error('工具测试失败: ' + (error.response?.data?.message || error.message || '未知错误'))
}
} }
// 处理更改状态 // 处理更改状态
const handleChangeStatus = async (row) => { const handleChangeStatus = async (row: Tool) => {
try { try {
// 构造更新数据,切换状态 // 构造更新数据,切换状态
const updatedTool = { const updatedTool = {
...@@ -205,14 +350,14 @@ const handleChangeStatus = async (row) => { ...@@ -205,14 +350,14 @@ const handleChangeStatus = async (row) => {
} else { } else {
ElMessage.error(response.data.message || '更新工具状态失败') ElMessage.error(response.data.message || '更新工具状态失败')
} }
} catch (error) { } catch (error: any) {
console.error('更新工具状态失败:', error) console.error('更新工具状态失败:', error)
ElMessage.error('更新工具状态失败: ' + (error.response?.data?.message || error.message || '未知错误')) ElMessage.error('更新工具状态失败: ' + (error.response?.data?.message || error.message || '未知错误'))
} }
} }
// 处理删除工具 // 处理删除工具
const handleDelete = (row) => { const handleDelete = (row: Tool) => {
ElMessageBox.confirm( ElMessageBox.confirm(
`确定要删除工具"${row.name}"吗?此操作不可恢复。`, `确定要删除工具"${row.name}"吗?此操作不可恢复。`,
'确认删除', '确认删除',
...@@ -231,7 +376,7 @@ const handleDelete = (row) => { ...@@ -231,7 +376,7 @@ const handleDelete = (row) => {
} else { } else {
ElMessage.error(response.data.message || '删除工具失败') ElMessage.error(response.data.message || '删除工具失败')
} }
} catch (error) { } catch (error: any) {
console.error('删除工具失败:', error) console.error('删除工具失败:', error)
ElMessage.error('删除工具失败: ' + (error.response?.data?.message || error.message || '未知错误')) ElMessage.error('删除工具失败: ' + (error.response?.data?.message || error.message || '未知错误'))
} }
...@@ -243,7 +388,7 @@ const handleDelete = (row) => { ...@@ -243,7 +388,7 @@ const handleDelete = (row) => {
// 处理保存 // 处理保存
const handleSave = async () => { const handleSave = async () => {
if (currentTool.value.name.trim() === '') { if (!currentTool.value.name || currentTool.value.name.trim() === '') {
ElMessage.warning('请输入工具名称') ElMessage.warning('请输入工具名称')
return return
} }
...@@ -265,6 +410,7 @@ const handleSave = async () => { ...@@ -265,6 +410,7 @@ const handleSave = async () => {
const toolData = { const toolData = {
name: currentTool.value.name, name: currentTool.value.name,
displayName: currentTool.value.displayName || currentTool.value.name, displayName: currentTool.value.displayName || currentTool.value.name,
beanName: currentTool.value.beanName,
category: currentTool.value.category, category: currentTool.value.category,
status: currentTool.value.status, status: currentTool.value.status,
description: currentTool.value.description, description: currentTool.value.description,
...@@ -280,7 +426,7 @@ const handleSave = async () => { ...@@ -280,7 +426,7 @@ const handleSave = async () => {
ElMessage.error(response.data.message || '创建工具失败') ElMessage.error(response.data.message || '创建工具失败')
} }
} }
} catch (error) { } catch (error: any) {
console.error('保存工具失败:', error) console.error('保存工具失败:', error)
ElMessage.error('保存工具失败: ' + (error.response?.data?.message || error.message || '未知错误')) ElMessage.error('保存工具失败: ' + (error.response?.data?.message || error.message || '未知错误'))
} }
...@@ -293,7 +439,7 @@ const handleRefresh = () => { ...@@ -293,7 +439,7 @@ const handleRefresh = () => {
} }
// 处理参数配置 // 处理参数配置
const handleConfig = (row) => { const handleConfig = (row: Tool) => {
selectedTool.value = { ...row } selectedTool.value = { ...row }
configDialogVisible.value = true configDialogVisible.value = true
fetchToolConfig(row.name) fetchToolConfig(row.name)
...@@ -316,7 +462,7 @@ const fetchToolConfig = async (toolName: string) => { ...@@ -316,7 +462,7 @@ const fetchToolConfig = async (toolName: string) => {
const currentToolConfigs = allConfigsResponse.data.filter((item: any) => item.toolName === toolName) const currentToolConfigs = allConfigsResponse.data.filter((item: any) => item.toolName === toolName)
// 转换为数组格式以适配前端显示 // 转换为数组格式以适配前端显示
const configArray = [] const configArray: ToolConfig[] = []
currentToolConfigs.forEach((config: any) => { currentToolConfigs.forEach((config: any) => {
configArray.push({ configArray.push({
paramName: config.paramName, paramName: config.paramName,
...@@ -332,11 +478,11 @@ const fetchToolConfig = async (toolName: string) => { ...@@ -332,11 +478,11 @@ const fetchToolConfig = async (toolName: string) => {
toolConfig.value = configArray toolConfig.value = configArray
} else { } else {
// 将返回的对象转换为数组格式以适配前端显示 // 将返回的对象转换为数组格式以适配前端显示
const configArray = [] const configArray: ToolConfig[] = []
for (const [paramName, paramValue] of Object.entries(response.data)) { for (const [paramName, paramValue] of Object.entries(response.data)) {
configArray.push({ configArray.push({
paramName: paramName, paramName: paramName,
paramValue: paramValue, paramValue: paramValue as string,
defaultValue: '', // 默认值需要从其他地方获取 defaultValue: '', // 默认值需要从其他地方获取
description: '', // 描述需要从其他地方获取 description: '', // 描述需要从其他地方获取
type: 'string', // 类型需要从其他地方获取 type: 'string', // 类型需要从其他地方获取
...@@ -376,7 +522,7 @@ const handleSaveConfig = async () => { ...@@ -376,7 +522,7 @@ const handleSaveConfig = async () => {
loading.value = false loading.value = false
configDialogVisible.value = false configDialogVisible.value = false
ElMessage.success('保存工具配置成功') ElMessage.success('保存工具配置成功')
} catch (error) { } catch (error: any) {
console.error('Failed to save tool config:', error) console.error('Failed to save tool config:', error)
loading.value = false loading.value = false
ElMessage.error('保存工具配置失败') ElMessage.error('保存工具配置失败')
...@@ -407,7 +553,7 @@ const loadTools = async () => { ...@@ -407,7 +553,7 @@ const loadTools = async () => {
const response = await authStore.get('/tools') const response = await authStore.get('/tools')
console.log('获取工具列表响应:', response) console.log('获取工具列表响应:', response)
if (response.data && response.data.code === 200) { if (response.data && response.data.code === 200) {
tools.value = response.data.data.map(tool => ({ tools.value = response.data.data.map((tool: any) => ({
...tool, ...tool,
version: tool.version || '1.0.0' // 如果没有版本号,使用默认值 version: tool.version || '1.0.0' // 如果没有版本号,使用默认值
})) }))
...@@ -415,7 +561,7 @@ const loadTools = async () => { ...@@ -415,7 +561,7 @@ const loadTools = async () => {
} else { } else {
ElMessage.error(response.data?.message || '获取工具列表失败') ElMessage.error(response.data?.message || '获取工具列表失败')
} }
} catch (error) { } catch (error: any) {
console.error('获取工具列表失败:', error) console.error('获取工具列表失败:', error)
ElMessage.error('获取工具列表失败: ' + (error.response?.data?.message || error.message || '未知错误')) ElMessage.error('获取工具列表失败: ' + (error.response?.data?.message || error.message || '未知错误'))
} }
...@@ -527,6 +673,15 @@ const loadTools = async () => { ...@@ -527,6 +673,15 @@ const loadTools = async () => {
margin-left: var(--spacing-2); margin-left: var(--spacing-2);
} }
/* 诊断结果样式 */
.diagnose-result {
padding: 20px 0;
}
.error-message {
color: var(--danger-color);
}
/* 响应式设计 */ /* 响应式设计 */
@media (max-width: 768px) { @media (max-width: 768px) {
.tool-management { .tool-management {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment