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; } }