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