liusuyi
2026-05-18 0b8c8d8986a35c3e36db1503125e2dff79d6d10e
优化
4 files added
8 files modified
808 ■■■■ changed files
ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteDeptService.java 9 ●●●●● patch | view | raw | blame | history
ard-api/ard-api-system/src/main/java/com/ard/system/api/RemoteUserService.java 20 ●●●● patch | view | raw | blame | history
ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteDeptFallbackFactory.java 5 ●●●●● patch | view | raw | blame | history
ard-api/ard-api-system/src/main/java/com/ard/system/api/factory/RemoteUserFallbackFactory.java 7 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-agent/pom.xml 6 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/config/AgentConfig.java 75 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/controller/AIController.java 7 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/service/AIChatService.java 88 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/MonitorTools.java 215 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/SystemTools.java 188 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysDeptApiController.java 46 ●●●●● patch | view | raw | blame | history
ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysUserApiController.java 142 ●●●●● patch | view | raw | blame | history
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
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);
}
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());
            }
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());
            }
        };
    }
}
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>
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/config/AgentConfig.java
New file
@@ -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();
    }
}
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);
    }
}
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);
        }
    }
}
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);
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/tool/SystemTools.java
New file
@@ -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);
    }
}
ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysDeptApiController.java
New file
@@ -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);
    }
}
ard-modules/ard-modules-system/src/main/java/com/ard/system/api/SysUserApiController.java
New file
@@ -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;
    }
}