From 0b8c8d8986a35c3e36db1503125e2dff79d6d10e Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: Mon, 18 May 2026 10:39:37 +0800
Subject: [PATCH] 优化
---
ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteUserFallbackFactory.java | 7
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/SystemTools.java | 188 +++++++++++++
ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteDeptService.java | 9
ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysDeptApiController.java | 46 +++
ard-modules/ard-modules-agent/pom.xml | 6
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/config/AgentConfig.java | 75 +++++
ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteUserService.java | 20 +
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/service/AIChatService.java | 88 ++++++
ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteDeptFallbackFactory.java | 5
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/controller/AIController.java | 7
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/MonitorTools.java | 215 ++++++--------
ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysUserApiController.java | 142 ++++++++++
12 files changed, 684 insertions(+), 124 deletions(-)
diff --git a/ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteDeptService.java b/ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteDeptService.java
index e09bccf..0de714b 100644
--- a/ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteDeptService.java
+++ b/ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteDeptService.java
@@ -5,6 +5,7 @@
import com.ard.common.core.domain.R;
import com.ard.common.core.web.domain.TreeDeptWell;
import com.ard.system.api.domain.SysDept;
+import com.ard.system.api.domain.SysUser;
import com.ard.system.api.factory.RemoteDeptFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
@@ -21,6 +22,14 @@
public interface RemoteDeptService
{
/**
+ * 查询所有部门信息
+ *
+ * @param sysDept 部门信息
+ * @return 用户对象信息列表
+ */
+ @PostMapping(value ="/api/dept/list")
+ public R<List<SysDept>> list(@RequestBody SysDept sysDept, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
+ /**
* 根据deptList查询出用户及以下的所有部门信息
*
* @param deptIds deptIds
diff --git a/ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteUserService.java b/ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteUserService.java
index ef9aa0d..1128330 100644
--- a/ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteUserService.java
+++ b/ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteUserService.java
@@ -1,6 +1,7 @@
package com.ard.system.api;
import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -13,6 +14,8 @@
import com.ard.system.api.domain.SysUser;
import com.ard.system.api.factory.RemoteUserFallbackFactory;
import com.ard.system.api.model.LoginUser;
+
+import java.util.List;
/**
* 用户服务
@@ -29,7 +32,7 @@
* @param source 请求来源
* @return 结果
*/
- @GetMapping("/user/info/{username}")
+ @GetMapping("/api/user/info/{username}")
public R<LoginUser> getUserInfo(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
@@ -39,7 +42,7 @@
* @param source 请求来源
* @return 结果
*/
- @PostMapping("/user/register")
+ @PostMapping("/api/user/register")
public R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
@@ -49,7 +52,7 @@
* @param source 请求来源
* @return 结果
*/
- @PutMapping("/user/recordlogin")
+ @PutMapping("/api/user/recordlogin")
public R<Boolean> recordUserLogin(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
@@ -59,6 +62,15 @@
* @param source 请求来源
* @return 用户对象
*/
- @GetMapping("/user/info1/{userId}")
+ @GetMapping("/api/user/{userId}")
public R<SysUser> getUserInfoByUserId(@PathVariable("userId") Long userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
+
+ /**
+ * 通过用户ID查询用户
+ *
+ * @param source 请求来源
+ * @return 用户对象
+ */
+ @PostMapping("/api/user/list")
+ public R<List<SysUser>> list(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}
diff --git a/ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteDeptFallbackFactory.java b/ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteDeptFallbackFactory.java
index 486b410..7db1ab4 100644
--- a/ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteDeptFallbackFactory.java
+++ b/ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteDeptFallbackFactory.java
@@ -29,6 +29,11 @@
return new RemoteDeptService()
{
@Override
+ public R<List<SysDept>> list(SysDept sysDept, String source) {
+ return R.fail("获取所有部门信息失败:" + throwable.getMessage());
+ }
+
+ @Override
public R<List<SysDept>> getAllByUser(Long[] deptList, String source) {
return R.fail("获取当前用户及以下的所有部门信息失败:" + throwable.getMessage());
}
diff --git a/ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteUserFallbackFactory.java b/ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteUserFallbackFactory.java
index 70372aa..6f1d668 100644
--- a/ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteUserFallbackFactory.java
+++ b/ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteUserFallbackFactory.java
@@ -9,6 +9,8 @@
import com.ard.system.api.domain.SysUser;
import com.ard.system.api.model.LoginUser;
+import java.util.List;
+
/**
* 用户服务降级处理
*
@@ -47,6 +49,11 @@
public R<SysUser> getUserInfoByUserId(Long userId, String source) {
return R.fail("获取用户失败:" + throwable.getMessage());
}
+
+ @Override
+ public R<List<SysUser>> list(SysUser sysUser, String source) {
+ return R.fail("获取用户列表失败:" + throwable.getMessage());
+ }
};
}
}
diff --git a/ard-modules/ard-modules-agent/pom.xml b/ard-modules/ard-modules-agent/pom.xml
index 42934e4..caf72ba 100644
--- a/ard-modules/ard-modules-agent/pom.xml
+++ b/ard-modules/ard-modules-agent/pom.xml
@@ -59,6 +59,12 @@
<artifactId>ard-common-swagger</artifactId>
</dependency>
+ <!-- 1. ReactAgent 核心框架 -->
+ <dependency>
+ <groupId>com.alibaba.cloud.ai</groupId>
+ <artifactId>spring-ai-alibaba-agent-framework</artifactId>
+ </dependency>
+
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
diff --git a/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/config/AgentConfig.java b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/config/AgentConfig.java
new file mode 100644
index 0000000..c5e3433
--- /dev/null
+++ b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/config/AgentConfig.java
@@ -0,0 +1,75 @@
+package com.ard.agent.config;
+
+import com.alibaba.cloud.ai.graph.agent.ReactAgent;
+import com.alibaba.cloud.ai.graph.agent.hook.modelcalllimit.ModelCallLimitHook;
+import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
+import com.ard.agent.tool.MonitorTools;
+import jakarta.annotation.Resource;
+import org.springframework.ai.chat.memory.ChatMemory;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.support.ToolCallbacks;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class AgentConfig {
+
+ @Resource
+ @Qualifier("ollamaChatModel")
+ private ChatModel chatModel;
+ @Resource
+ private MonitorTools monitorTools;
+ @Resource
+ private ChatMemory chatMemory; // 注入聊天记忆
+
+
+ // 增强版的系统提示词,专门为 ReAct 模式优化
+ private static final String SYSTEM_PROMPT = """
+ 你是摄像头管理助手,拥有调用工具和查询知识库的能力。
+
+ ## 工作方式
+ 你需要通过「思考 → 行动 → 观察」的循环来完成任务:
+
+ 1. **思考 (Thought)**:分析用户需求,判断下一步该做什么
+ 2. **行动 (Action)**:决定调用哪个工具
+ - getCameraDetail: 查询单个摄像机详情
+ - searchCameras: 按条件搜索摄像机
+ - updateCameraInfo: 修改摄像机信息
+ - 知识库查询: 获取操作指南/故障排查手册
+ 3. **观察 (Observation)**:分析工具返回的结果
+ 4. **重复**:直到获得足够信息回答用户
+
+ ## 工具使用原则
+ - 涉及摄像机实时状态、修改操作 → 必须调用工具
+ - 询问操作指南、故障排查 → 使用知识库
+ - 复杂任务需要多个步骤时,自动规划并依次调用工具
+
+ ## 输出格式
+ 使用 Markdown 格式输出:
+ - ## 标题
+ - - 列表项
+ - **加粗** 强调
+ - 表格用于数据展示
+
+ ## 重要提醒
+ 不要编造信息,所有数据必须来自工具调用或知识库。
+ """;
+ ModelCallLimitHook hook = ModelCallLimitHook.builder()
+ .runLimit(5) // 限制最多调用 5 次
+ .exitBehavior(ModelCallLimitHook.ExitBehavior.ERROR) // 超出限制时抛出异常
+ .build();
+
+ @Bean
+ public ReactAgent cameraAgent() {
+ return ReactAgent.builder()
+ .name("cameraAgent") // Agent 的名称
+ .model(chatModel) // 设置底层的大语言模型
+ .hooks(hook)
+ .instruction(SYSTEM_PROMPT) // 系统提示词,定义角色和行为
+ .tools(ToolCallbacks.from(monitorTools)) // 注册可用的工具
+ .saver(new MemorySaver()) // 2. 核心:用 MemorySaver 实现记忆
+ .enableLogging(true) // 开启日志,方便调试观察思考过程
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/controller/AIController.java b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/controller/AIController.java
index ef3cc18..c5b6f86 100644
--- a/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/controller/AIController.java
+++ b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/controller/AIController.java
@@ -1,9 +1,11 @@
package com.ard.agent.controller;
+import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import com.ard.agent.service.AIChatService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
+import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
@@ -20,4 +22,9 @@
public Flux<String> streamChat(@RequestParam String message) {
return aiChatService.streamChat(message);
}
+ @Operation(summary = "ReactAgent聊天接口")
+ @GetMapping(value = "/chat")
+ public String chat(@RequestParam String message) throws GraphRunnerException {
+ return aiChatService.chat(message);
+ }
}
\ No newline at end of file
diff --git a/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/service/AIChatService.java b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/service/AIChatService.java
index 84a8ba6..e03a52a 100644
--- a/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/service/AIChatService.java
+++ b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/service/AIChatService.java
@@ -1,24 +1,45 @@
package com.ard.agent.service;
+import com.alibaba.cloud.ai.graph.RunnableConfig;
+import com.alibaba.cloud.ai.graph.agent.ReactAgent;
+import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
+import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import com.ard.agent.tool.KnowledgeTools;
import com.ard.agent.tool.MonitorTools;
+import com.ard.agent.tool.SystemTools;
import com.ard.common.security.utils.SecurityUtils;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.content.Content;
+import org.springframework.ai.support.ToolCallbacks;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
+@Slf4j
@Service
public class AIChatService {
@Resource
private ChatClient chatClient;
@Resource
+ @Qualifier("ollamaChatModel")
+ private ChatModel chatModel;
+ @Resource
+ private SystemTools systemTools; // 注入工具类
+ @Resource
private MonitorTools monitorTools; // 注入工具类
@Resource
private KnowledgeTools knowledgeTools;
+ @Resource
+ private ReactAgent cameraAgent; // 注入配置好的 Agent
private static final String SYSTEM_PROMPT = """
你是摄像头管理助手。请严格遵守以下规则:
@@ -48,8 +69,73 @@
.system(SYSTEM_PROMPT)
.user(userMessage)
.advisors(advisor -> advisor.param("chat_memory_conversation_id", userId))
- .tools(monitorTools, knowledgeTools)
+ .tools(systemTools,monitorTools, knowledgeTools)
.stream()
.content();
}
+
+ /**
+ * 对话服务入口
+ */
+ public String chat(String userMessage) throws GraphRunnerException {
+ Long userId = SecurityUtils.getUserId();
+ // 1. 为每个用户创建唯一的 threadId
+ RunnableConfig config = RunnableConfig.builder()
+ .threadId(userId.toString()) // 比如从 SecurityUtils.getUserId() 获取
+ .build();
+ // 3. 调用 Agent 的 call 方法并执行
+ AssistantMessage response = cameraAgent.call(userMessage,config);
+ // 4. 从响应中提取文本内容
+ return response.getText();
+ }
+
+ private static final String SYSTEM_PROMPT1 = """
+ 你是智能管理助手,拥有以下能力:
+
+ ## 可用工具
+ ### 摄像头管理
+ - getCameraDetail: 查询摄像机详情
+ - searchCameras: 搜索摄像机
+ - updateCameraInfo: 修改摄像机信息
+
+ ### 系统管理
+ - searchUsers: 搜索用户
+ - searchDepts: 搜索部门
+ - searchRoles: 搜索角色
+ - getUserRoles: 查询用户角色
+
+ ### 知识库
+ - searchKnowledgeBase: 查询知识文档
+
+ ## 原则
+ 1. 根据问题自动选择工具
+ 2. 数据必须来自工具调用
+ 3. 用 Markdown 格式回复
+ """;
+
+ /**
+ * 流式聊天
+ */
+ public Flux<String> streamChat1(String userMessage) {
+ Long userId = SecurityUtils.getUserId();
+ RunnableConfig config = RunnableConfig.builder()
+ .threadId(userId.toString())
+ .build();
+ ReactAgent agent = ReactAgent.builder()
+ .name("UnifiedAgent")
+ .model(chatModel)
+ .instruction(SYSTEM_PROMPT1)
+ .tools(ToolCallbacks.from(monitorTools, systemTools, knowledgeTools))
+ .saver(new MemorySaver())
+ .build();
+
+ try {
+ return agent.streamMessages(userMessage, config)
+ .filter(msg -> msg instanceof AssistantMessage)
+ .map(Content::getText)
+ .doOnError(error -> log.error("Agent执行出错: {}", error.getMessage()));
+ } catch (GraphRunnerException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
\ No newline at end of file
diff --git a/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/MonitorTools.java b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/MonitorTools.java
index a5569da..c6c8cd0 100644
--- a/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/MonitorTools.java
+++ b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/MonitorTools.java
@@ -3,6 +3,7 @@
import com.alibaba.fastjson2.JSON;
import com.ard.common.core.constant.SecurityConstants;
import com.ard.common.core.domain.R;
+import com.ard.common.redis.service.RedisService;
import com.ard.system.api.RemoteDictService;
import com.ard.system.api.domain.SysDictData;
import com.ard.work.api.RemoteCameraService;
@@ -12,8 +13,8 @@
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
+
import java.util.List;
-import java.util.Map;
import java.util.Objects;
@Slf4j
@@ -24,6 +25,54 @@
private RemoteCameraService remoteCameraService;
@Resource
private RemoteDictService remoteDictService;
+ @Resource
+ private RedisService redisService; // 若依的RedisCache
+ // 字典缓存Key前缀(若依默认)
+ private static final String DICT_KEY_PREFIX = "sys_dict:";
+
+ /**
+ * 获取厂商名称(带Redis缓存)
+ */
+ private String getFactoryName(String factoryCode) {
+ return getDictLabel("factory", factoryCode);
+ }
+
+ /**
+ * 获取相机类型名称(带Redis缓存)
+ */
+ private String getCameraTypeName(String typeCode) {
+ return getDictLabel("camera_type", typeCode);
+ }
+ /**
+ * 获取字典标签(直接从Redis缓存读取)
+ */
+ private String getDictLabel(String dictType, String dictValue) {
+ if (dictValue == null || dictValue.isEmpty()) {
+ return "未知";
+ }
+
+ // 若依框架:从缓存获取字典数据
+ List<SysDictData> dictDatas = redisService.getCacheObject("sys_dict:" + dictType);
+ if (dictDatas == null || dictDatas.isEmpty()) {
+ // 如果缓存为空,可以从远程服务加载一次
+ R<List<SysDictData>> result = remoteDictService.dictTypeRpc(dictType, SecurityConstants.INNER);
+ if (result != null && result.getCode() == R.SUCCESS) {
+ dictDatas = result.getData();
+ // 重新放入缓存
+ redisService.setCacheObject("sys_dict:" + dictType, dictDatas);
+ }
+ }
+
+ if (dictDatas != null) {
+ return dictDatas.stream()
+ .filter(dict -> dictValue.equals(dict.getDictValue()))
+ .map(SysDictData::getDictLabel)
+ .findFirst()
+ .orElse("未知(" + dictValue + ")");
+ }
+
+ return "未知(" + dictValue + ")";
+ }
@Tool(description = "获取指定摄像机的详细信息,包括IP、名称、状态等")
public String getCameraDetail(@ToolParam(description = "摄像机ID,如CAM-001") String cameraId) {
@@ -75,55 +124,23 @@
}
List<ArdCamera> filteredCameras = cameras;
-// if (keyword != null && !keyword.isEmpty()) {
-// filteredCameras = cameras.stream()
-// .filter(c -> (c.getName() != null && c.getName().contains(keyword)) ||
-// (c.getId() != null && c.getId().contains(keyword)))
-// .toList();
-// }
-
if (filteredCameras.isEmpty()) {
return "未找到符合条件的摄像机";
}
- R<List<SysDictData>> getFactoryResult =
- remoteDictService.dictTypeRpc("factory", SecurityConstants.INNER);
- if (getFactoryResult == null || getFactoryResult.getCode() != R.SUCCESS) {
- return String.format("获取厂商信息失败:%s",
- getFactoryResult != null ? getFactoryResult.getMsg() : "服务返回空结果");
- }
- R<List<SysDictData>> getCameraTypeResult =
- remoteDictService.dictTypeRpc("camera_type", SecurityConstants.INNER);
- if (getCameraTypeResult == null || getCameraTypeResult.getCode() != R.SUCCESS) {
- return String.format("获取相机类型失败:%s",
- getCameraTypeResult != null ? getCameraTypeResult.getMsg() : "服务返回空结果");
- }
+
StringBuilder resultStr = new StringBuilder();
resultStr.append(String.format("找到%d台摄像机:\n", filteredCameras.size()));
for (ArdCamera camera : filteredCameras) {
- String factoryName = getFactoryResult.getData().stream()
- .filter(dict -> camera.getFactory().equals(dict.getDictValue()))
- .findFirst()
- .map(SysDictData::getDictLabel)
- .orElseGet(() -> {
- log.warn("未找到厂商代码对应的字典项: {}", camera.getFactory());
- return "未知厂商(" + camera.getFactory() + ")";
- });
- String cameraTypeName = getCameraTypeResult.getData().stream()
- .filter(dict -> camera.getType().equals(dict.getDictValue()))
- .findFirst()
- .map(SysDictData::getDictLabel)
- .orElseGet(() -> {
- log.warn("未找到相机类型对应的字典项: {}", camera.getType());
- return "未知厂商(" + camera.getType() + ")";
- });
+
resultStr.append(String.format(" - ID: %s, 名称: %s, IP: %s, 厂商: %s,状态: %s, 类型: %s\n",
camera.getId(),
camera.getName(),
camera.getIp(),
- factoryName,
+ getFactoryName(camera.getFactory()),
Objects.equals(camera.getState(), "1") ? "在线" : "离线",
- cameraTypeName));
+ getCameraTypeName(camera.getType())
+ ));
}
return resultStr.toString();
} catch (Exception e) {
@@ -131,95 +148,55 @@
return "系统异常:" + e.getMessage();
}
}
-
- @Tool(description = "修改摄像机信息,支持修改名称、IP地址、端口、位置、状态等字段。注意:摄像机ID不能修改")
+ @Tool(description = """
+ 修改摄像机信息。传入摄像机ID和需要修改的字段,不传的字段保持原值不变。
+
+ 使用示例:
+ - "把CAM-001的名字改成大门摄像头" → 传入 {id:"CAM-001", name:"大门摄像头"}
+ - "将CAM-002设置为离线" → 传入 {id:"CAM-002", state:"0"}
+
+ 注意:id为必填项。
+ """)
public String updateCameraInfo(
- @ToolParam(description = "摄像机ID,如CAM-001") String cameraId,
- @ToolParam(description = "摄像机名称,如不需要修改可不传") String name,
- @ToolParam(description = "摄像机IP地址,如不需要修改可不传") String ip,
- @ToolParam(description = "摄像机位置/区域,如不需要修改可不传") String location,
- @ToolParam(description = "摄像机状态:1-在线,0-离线,如不需要修改可不传") String state,
- @ToolParam(description = "摄像机类型,如不需要修改可不传") String type,
- @ToolParam(description = "摄像机厂家/品牌,如不需要修改可不传") String factory
- ) {
- log.info("AI调用updateCameraInfo工具,参数: cameraId={}, name={}, ip={}, location={}, state={}, type={}, factory={}",
- cameraId, name, ip, location, state, type, factory);
+ @ToolParam(description = "摄像机对象,包含id和需要修改的字段") ArdCamera updateParams) {
- // 参数校验
- if (cameraId == null || cameraId.isEmpty()) {
+ log.info("AI调用updateCameraInfo工具,参数: {}", updateParams);
+
+ // 必填校验
+ if (updateParams == null || updateParams.getId() == null || updateParams.getId().isEmpty()) {
return "错误:摄像机ID不能为空";
}
+ String cameraId = updateParams.getId();
+
try {
- // 1. 先获取当前摄像机信息
- R<ArdCamera> getResult = remoteCameraService.getCameraInfoRpc(cameraId, SecurityConstants.INNER);
- if (getResult == null || getResult.getCode() != R.SUCCESS) {
- return String.format("获取摄像机信息失败:%s",
- getResult != null ? getResult.getMsg() : "服务返回空结果");
+ // 3. 直接调用 Feign 接口
+ R<Boolean> updateResult = remoteCameraService.updateCamera(updateParams, SecurityConstants.INNER);
+
+ if (updateResult == null || updateResult.getCode() != R.SUCCESS) {
+ return "修改失败:" + (updateResult != null ? updateResult.getMsg() : "服务返回空");
}
-
- ArdCamera camera = getResult.getData();
- if (camera == null) {
- return "错误:未找到摄像机ID为 " + cameraId + " 的摄像机";
- }
-
- // 2. 构建更新对象(只更新有值的字段)
- ArdCamera updateCamera = new ArdCamera();
- updateCamera.setId(cameraId);
-
- if (name != null && !name.isEmpty()) {
- updateCamera.setName(name);
- } else {
- updateCamera.setName(camera.getName());
- }
-
- if (ip != null && !ip.isEmpty()) {
- updateCamera.setIp(ip);
- } else {
- updateCamera.setIp(camera.getIp());
- }
-
- if (state != null && !state.isEmpty()) {
- updateCamera.setState(state);
- }
-
- if (type != null && !type.isEmpty()) {
- updateCamera.setType(type);
- }
-
- if (factory != null && !factory.isEmpty()) {
- updateCamera.setFactory(factory);
- }
-
- // 3. 调用更新接口(假设有 updateCamera 方法)
- R<Boolean> updateResult = remoteCameraService.updateCamera(updateCamera, SecurityConstants.INNER);
-
- if (updateResult == null) {
- return "错误:服务返回空结果";
- }
-
- if (updateResult.getCode() == R.SUCCESS) {
- // 返回更新后的信息
- R<ArdCamera> newResult = remoteCameraService.getCameraInfoRpc(cameraId, SecurityConstants.INNER);
- ArdCamera newCamera = newResult.getData();
- return String.format("摄像机信息修改成功!\n更新后的信息:\n" +
- " - ID: %s\n" +
- " - 名称: %s\n" +
- " - IP地址: %s\n" +
- " - 状态: %s\n" +
- " - 类型: %s\n" +
- " - 厂家: %s",
- newCamera.getId(),
- newCamera.getName(),
- newCamera.getIp(),
- Objects.equals(newCamera.getState(), "1") ? "在线" : "离线",
- newCamera.getType(),
- newCamera.getFactory());
- } else {
- return String.format("修改摄像机信息失败:%s (code: %d)",
- updateResult.getMsg(), updateResult.getCode());
- }
+ // 4. 返回最新信息
+ R<ArdCamera> newResult = remoteCameraService.getCameraInfoRpc(cameraId, SecurityConstants.INNER);
+ ArdCamera newCamera = newResult.getData();
+ return String.format("✅ 摄像机信息修改成功!\n\n" +
+ "| 字段 | 修改后的值 |\n" +
+ "|------|-----------|\n" +
+ "| ID | %s |\n" +
+ "| 名称 | %s |\n" +
+ "| IP | %s |\n" +
+ "| 位置 | %s, %s |\n" +
+ "| 状态 | %s |\n" +
+ "| 类型 | %s |\n" +
+ "| 厂家 | %s |",
+ newCamera.getId(),
+ newCamera.getName(),
+ newCamera.getIp(),
+ newCamera.getLongitude(), newCamera.getLatitude(),
+ "1".equals(newCamera.getState()) ? "在线" : "离线",
+ getCameraTypeName(newCamera.getType()),
+ getFactoryName(newCamera.getFactory()));
} catch (Exception e) {
log.error("调用updateCamera异常", e);
diff --git a/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/SystemTools.java b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/SystemTools.java
new file mode 100644
index 0000000..b12edf5
--- /dev/null
+++ b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/SystemTools.java
@@ -0,0 +1,188 @@
+package com.ard.agent.tool;
+
+
+import com.ard.common.core.constant.SecurityConstants;
+import com.ard.common.core.domain.R;
+import com.ard.system.api.RemoteDeptService;
+import com.ard.system.api.RemoteUserService;
+import com.ard.system.api.domain.SysDept;
+import com.ard.system.api.domain.SysUser;
+import com.ard.work.api.domian.ArdCamera;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.ai.tool.annotation.ToolParam;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Component
+@Slf4j
+public class SystemTools {
+
+ @Resource
+ private RemoteUserService userService;
+
+ @Resource
+ private RemoteDeptService deptService;
+
+
+
+ /**
+ * 搜索用户信息
+ */
+ @Tool(description = """
+ 检索系统用户信息。根据用户名、手机号、真实姓名等条件查询用户。
+ 返回用户的详细信息,包括所属部门、角色等。
+ 适用场景:
+ - 查询某个用户的基本信息
+ - 根据手机号查找用户
+ - 列出某部门下的所有用户
+ """)
+ public String searchUsers(
+ @ToolParam(description = "真实姓名(模糊匹配)", required = false) String nickName,
+ @ToolParam(description = "用户名(模糊匹配)", required = false) String userName,
+ @ToolParam(description = "手机号(模糊匹配)", required = false) String phone,
+ @ToolParam(description = "部门名称(模糊匹配)", required = false) String deptName,
+ @ToolParam(description = "最大返回数量,默认10") Integer limit) {
+
+ log.info("AI调用searchUsers工具,参数: nickName={}, userName={}, phone={}, deptName={}, limit={}",
+ nickName, userName, phone, deptName, limit);
+
+ if (limit == null) limit = 10;
+
+ // 构造查询条件
+ SysUser query = new SysUser();
+ if (userName != null && !userName.isEmpty()) {
+ query.setUserName(userName);
+ }
+ if (phone != null && !phone.isEmpty()) {
+ query.setPhonenumber(phone);
+ }
+ if (deptName != null && !deptName.isEmpty()) {
+ SysDept dept = new SysDept();
+ dept.setDeptName(deptName);
+ query.setDept(dept);
+ }
+
+
+ // 调用若依的 Service 查询
+ R<List<SysUser>> result = userService.list(query, SecurityConstants.INNER);
+ if (result == null) {
+ return "错误:服务返回空结果";
+ }
+
+ if (result.getCode() != R.SUCCESS) {
+ return String.format("获取用户列表失败:%s", result.getMsg());
+ }
+
+ List<SysUser> users = result.getData();
+ // 限制返回数量
+ if (users.size() > limit) {
+ users = users.subList(0, limit);
+ }
+
+ if (users.isEmpty()) {
+ return "未找到匹配的用户";
+ }
+
+ // 格式化返回结果
+ return users.stream()
+ .map(user -> String.format("""
+ 用户ID: %s
+ 用户名: %s
+ 真实姓名: %s
+ 手机号: %s
+ 邮箱: %s
+ 部门: %s
+ 状态: %s
+ 创建时间: %s
+ """,
+ user.getUserId(),
+ user.getUserName(),
+ user.getNickName(),
+ maskPhone(user.getPhonenumber()),
+ user.getEmail(),
+ user.getDept() != null ? user.getDept().getDeptName() : "未分配",
+ "0".equals(user.getStatus()) ? "正常" : "禁用",
+ user.getCreateTime()))
+ .collect(Collectors.joining("\n---\n"));
+ }
+
+ /**
+ * 搜索部门信息
+ */
+ @Tool(description = """
+ 检索系统部门信息。根据部门名称或部门ID查询部门详情。
+ 返回部门的层级结构、负责人、状态等信息。
+ 适用场景:
+ - 查询某个部门的详细信息
+ - 列出所有部门
+ - 查询部门的层级关系
+ """)
+ public String searchDepts(
+ @ToolParam(description = "部门名称(支持模糊搜索),留空则返回所有部门") String deptName,
+ @ToolParam(description = "最大返回数量,默认20") Integer limit) {
+
+ log.info("AI调用searchDepts工具,参数: deptName={}, limit={}", deptName, limit);
+
+ if (limit == null) limit = 20;
+
+ SysDept query = new SysDept();
+ if (deptName != null && !deptName.isEmpty()) {
+ query.setDeptName(deptName);
+ }
+
+ R<List<SysDept>> result = deptService.list(query, SecurityConstants.INNER);
+ if (result == null) {
+ return "错误:服务返回空结果";
+ }
+
+ if (result.getCode() != R.SUCCESS) {
+ return String.format("获取部门列表失败:%s", result.getMsg());
+ }
+
+ List<SysDept> depts = result.getData();
+ if (depts.size() > limit) {
+ depts = depts.subList(0, limit);
+ }
+
+ if (depts.isEmpty()) {
+ return "未找到匹配的部门";
+ }
+
+ return depts.stream()
+ .map(dept -> String.format("""
+ 部门ID: %s
+ 部门名称: %s
+ 父部门ID: %s
+ 负责人: %s
+ 联系电话: %s
+ 邮箱: %s
+ 状态: %s
+ """,
+ dept.getDeptId(),
+ dept.getDeptName(),
+ dept.getParentId(),
+ dept.getLeader(),
+ dept.getPhone(),
+ dept.getEmail(),
+ "0".equals(dept.getStatus()) ? "正常" : "停用"))
+ .collect(Collectors.joining("\n---\n"));
+ }
+
+
+ /**
+ * 手机号脱敏
+ */
+ private String maskPhone(String phone) {
+ if (phone == null || phone.length() < 7) {
+ return phone;
+ }
+ return phone.substring(0, 3) + "****" + phone.substring(7);
+ }
+
+}
\ No newline at end of file
diff --git a/ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysDeptApiController.java b/ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysDeptApiController.java
new file mode 100644
index 0000000..6fb116d
--- /dev/null
+++ b/ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysDeptApiController.java
@@ -0,0 +1,46 @@
+package com.ard.system.api;
+
+import com.ard.common.core.constant.UserConstants;
+import com.ard.common.core.domain.R;
+import com.ard.common.core.utils.StringUtils;
+import com.ard.common.core.web.controller.BaseController;
+import com.ard.common.core.web.domain.AjaxResult;
+import com.ard.common.log.annotation.Log;
+import com.ard.common.log.enums.BusinessType;
+import com.ard.common.security.annotation.InnerAuth;
+import com.ard.common.security.annotation.RequiresPermissions;
+import com.ard.common.security.utils.SecurityUtils;
+import com.ard.system.api.domain.SysDept;
+import com.ard.system.api.domain.SysUser;
+import com.ard.system.service.ISysDeptService;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 部门信息
+ *
+ * @author ard
+ */
+@RestController
+@RequestMapping("/api/dept")
+public class SysDeptApiController extends BaseController
+{
+ @Autowired
+ private ISysDeptService deptService;
+
+ /**
+ * 获取部门列表
+ */
+ @InnerAuth
+ @PostMapping("/list")
+ public R<List<SysDept>> list(@RequestBody SysDept dept)
+ {
+ List<SysDept> depts = deptService.selectDeptList(dept);
+ return R.ok(depts);
+ }
+}
diff --git a/ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysUserApiController.java b/ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysUserApiController.java
new file mode 100644
index 0000000..a4d02d6
--- /dev/null
+++ b/ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysUserApiController.java
@@ -0,0 +1,142 @@
+package com.ard.system.api;
+
+import com.ard.common.core.domain.R;
+import com.ard.common.core.text.Convert;
+import com.ard.common.core.utils.DateUtils;
+import com.ard.common.core.utils.StringUtils;
+import com.ard.common.core.utils.poi.ExcelUtil;
+import com.ard.common.core.web.controller.BaseController;
+import com.ard.common.core.web.domain.AjaxResult;
+import com.ard.common.core.web.page.TableDataInfo;
+import com.ard.common.log.annotation.Log;
+import com.ard.common.log.enums.BusinessType;
+import com.ard.common.security.annotation.InnerAuth;
+import com.ard.common.security.annotation.RequiresPermissions;
+import com.ard.common.security.service.TokenService;
+import com.ard.common.security.utils.SecurityUtils;
+import com.ard.system.api.domain.SysDept;
+import com.ard.system.api.domain.SysRole;
+import com.ard.system.api.domain.SysUser;
+import com.ard.system.api.model.LoginUser;
+import com.ard.system.service.*;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 用户信息
+ *
+ * @author ard
+ */
+
+@RestController
+@RequestMapping("/api/user")
+public class SysUserApiController extends BaseController {
+ @Autowired
+ private ISysUserService userService;
+
+ @Autowired
+ private ISysRoleService roleService;
+
+ @Autowired
+ private ISysPostService postService;
+
+ @Autowired
+ private ISysPermissionService permissionService;
+
+ @Autowired
+ private ISysConfigService configService;
+
+
+
+ /**
+ * 获取用户列表
+ */
+ @InnerAuth
+ @PostMapping("/list")
+ public R<List<SysUser>> list(@RequestBody SysUser user) {
+ List<SysUser> list = userService.selectUserList(user);
+ return R.ok(list);
+ }
+
+
+ /**
+ * 获取当前用户信息
+ */
+ @InnerAuth
+ @GetMapping("/info/{username}")
+ public R<LoginUser> info(@PathVariable("username") String username) {
+ SysUser sysUser = userService.selectUserByUserName(username);
+ if (StringUtils.isNull(sysUser)) {
+ return R.fail("用户名或密码错误");
+ }
+ // 角色集合
+ Set<String> roles = permissionService.getRolePermission(sysUser);
+ // 权限集合
+ Set<String> permissions = permissionService.getMenuPermission(sysUser);
+ LoginUser sysUserVo = new LoginUser();
+ sysUserVo.setSysUser(sysUser);
+ sysUserVo.setRoles(roles);
+ sysUserVo.setPermissions(permissions);
+ return R.ok(sysUserVo);
+ }
+
+ /**
+ * 注册用户信息
+ */
+ @InnerAuth
+ @PostMapping("/register")
+ public R<Boolean> register(@RequestBody SysUser sysUser) {
+ String username = sysUser.getUserName();
+ if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) {
+ return R.fail("当前系统没有开启注册功能!");
+ }
+ if (!userService.checkUserNameUnique(sysUser)) {
+ return R.fail("保存用户'" + username + "'失败,注册账号已存在");
+ }
+ return R.ok(userService.registerUser(sysUser));
+ }
+
+ /**
+ * 记录用户登录IP地址和登录时间
+ */
+ @InnerAuth
+ @PutMapping("/recordlogin")
+ public R<Boolean> recordlogin(@RequestBody SysUser sysUser) {
+ return R.ok(userService.updateLoginInfo(sysUser));
+ }
+
+
+ /**
+ * 根据用户编号获取详细信息
+ */
+ @InnerAuth
+ @GetMapping(value = {"/", "/{userId}"})
+ public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId) {
+ AjaxResult ajax = AjaxResult.success();
+ if (StringUtils.isNotNull(userId)) {
+ userService.checkUserDataScope(userId);
+ SysUser sysUser = userService.selectUserById(userId);
+ ajax.put(AjaxResult.DATA_TAG, sysUser);
+ ajax.put("postIds", postService.selectPostListByUserId(userId));
+ ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList()));
+ }
+ List<SysRole> roles = roleService.selectRoleAll();
+ ajax.put("roles", SecurityUtils.isAdmin(userId) ? roles :
+ roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
+ ajax.put("posts", postService.selectPostAll());
+ return ajax;
+ }
+
+}
--
Gitblit v1.9.3