Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
Pangea-Agent
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
2
Merge Requests
2
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Gavin-Group
Pangea-Agent
Commits
ae753c87
Commit
ae753c87
authored
Dec 22, 2025
by
ligaowei
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
修复AgentController中getUserAgents方法缺少权限注解的问题
parent
405accc6
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
445 additions
and
29 deletions
+445
-29
TOOL_MANAGEMENT_SCHEME.md
TOOL_MANAGEMENT_SCHEME.md
+200
-0
TimerExecutionHistory.java
...main/java/pangea/hiagent/model/TimerExecutionHistory.java
+0
-1
AgentController.java
...n/java/pangea/hiagent/web/controller/AgentController.java
+1
-0
ToolController.java
...in/java/pangea/hiagent/web/controller/ToolController.java
+59
-2
Login.vue
frontend/src/pages/Login.vue
+9
-5
ToolManagement.vue
frontend/src/pages/ToolManagement.vue
+176
-21
No files found.
TOOL_MANAGEMENT_SCHEME.md
0 → 100644
View file @
ae753c87
# 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
backend/src/main/java/pangea/hiagent/model/TimerExecutionHistory.java
View file @
ae753c87
...
@@ -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
;
...
...
backend/src/main/java/pangea/hiagent/web/controller/AgentController.java
View file @
ae753c87
...
@@ -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
();
...
...
backend/src/main/java/pangea/hiagent/web/controller/ToolController.java
View file @
ae753c87
...
@@ -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
frontend/src/pages/Login.vue
View file @
ae753c87
...
@@ -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
)
ElMessage
.
success
(
'登录成功'
)
if
(
response
.
code
===
200
)
{
router
.
push
(
'/chat'
)
ElMessage
.
success
(
'登录成功'
)
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
||
'登录失败,请检查用户名和密码'
)
...
...
frontend/src/pages/ToolManagement.vue
View file @
ae753c87
...
@@ -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=
"3
2
0"
>
<el-table-column
label=
"操作"
width=
"3
6
0"
>
<
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
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment