feat(ai): 新增AI命令系统功能

This commit is contained in:
Ray.Hao
2025-11-10 08:03:08 +08:00
parent ffb89e50da
commit 95412501fc
19 changed files with 1307 additions and 12 deletions

View File

@@ -0,0 +1,113 @@
package com.youlai.boot.platform.ai.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* AI 配置属性
*
* 优势:
* 1. 统一管理所有提供商配置
* 2. 添加新提供商只需在 yml 中添加配置,无需修改代码
* 3. 类型安全,支持 IDE 提示
*
* @author Ray.Hao
*/
@Data
@Component
@ConfigurationProperties(prefix = "ai")
public class AiProperties {
/**
* 是否启用 AI 功能
*/
private Boolean enabled = false;
/**
* 当前使用的提供商qwen、deepseek、openai 等)
*/
private String provider = "qwen";
/**
* 所有提供商的配置
* Key: 提供商名称qwen、deepseek、openai
* Value: 提供商配置
*/
private Map<String, ProviderConfig> providers;
/**
* 安全配置
*/
private SecurityConfig security = new SecurityConfig();
/**
* 限流配置
*/
private RateLimitConfig rateLimit = new RateLimitConfig();
/**
* 提供商配置
*/
@Data
public static class ProviderConfig {
/**
* API Key
*/
private String apiKey;
/**
* Base URL统一命名符合行业惯例
*/
private String baseUrl;
/**
* 模型名称
*/
private String model;
/**
* 提供商显示名称(可选)
*/
private String displayName;
/**
* 超时时间(秒)
*/
private Integer timeout = 30;
}
/**
* 安全配置
*/
@Data
public static class SecurityConfig {
private Boolean enableAudit = true;
private Boolean dangerousOperationsConfirm = true;
private java.util.List<String> functionWhitelist;
private java.util.List<String> sensitiveParams;
}
/**
* 限流配置
*/
@Data
public static class RateLimitConfig {
private Integer maxExecutionsPerMinute = 10;
private Integer maxExecutionsPerDay = 100;
}
/**
* 获取当前提供商配置
*/
public ProviderConfig getCurrentProviderConfig() {
if (providers == null || !providers.containsKey(provider)) {
throw new IllegalStateException("未找到提供商配置: " + provider);
}
return providers.get(provider);
}
}

View File

@@ -0,0 +1,99 @@
package com.youlai.boot.platform.ai.controller;
import com.youlai.boot.core.web.Result;
import com.youlai.boot.platform.ai.model.dto.*;
import com.youlai.boot.platform.ai.service.AiCommandService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* AI 命令控制器
*
* @author Ray.Hao
* @since 3.0.0
*/
@Tag(name = "AI命令接口")
@RestController
@RequestMapping("/api/v1/ai/command")
@RequiredArgsConstructor
@Slf4j
public class AiCommandController {
private final AiCommandService aiCommandService;
@Operation(summary = "解析自然语言命令")
@PostMapping("/parse")
public Result<AiCommandResponseDTO> parseCommand(
@RequestBody AiCommandRequestDTO request,
HttpServletRequest httpRequest
) {
log.info("收到AI命令解析请求: {}", request.getCommand());
try {
AiCommandResponseDTO response = aiCommandService.parseCommand(request, httpRequest);
return Result.success(response);
} catch (Exception e) {
log.error("命令解析失败", e);
return Result.success(AiCommandResponseDTO.builder()
.success(false)
.error(e.getMessage())
.build());
}
}
@Operation(summary = "执行已解析的命令")
@PostMapping("/execute")
public Result<AiExecuteResponseDTO> executeCommand(
@RequestBody AiExecuteRequestDTO request,
HttpServletRequest httpRequest
) {
log.info("收到AI命令执行请求: {}", request.getFunctionCall().getName());
try {
AiExecuteResponseDTO response = aiCommandService.executeCommand(request, httpRequest);
return Result.success(response);
} catch (Exception e) {
log.error("命令执行失败", e);
return Result.success(AiExecuteResponseDTO.builder()
.success(false)
.error(e.getMessage())
.build());
}
}
@Operation(summary = "获取命令执行历史")
@GetMapping("/history")
public Result<?> getCommandHistory(
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer size
) {
return Result.success(aiCommandService.getCommandHistory(page, size));
}
@Operation(summary = "获取可用的函数列表")
@GetMapping("/functions")
public Result<?> getAvailableFunctions() {
return Result.success(aiCommandService.getAvailableFunctions());
}
@Operation(summary = "撤销命令执行")
@PostMapping("/rollback/{auditId}")
public Result<?> rollbackCommand(
@Parameter(description = "审计ID") @PathVariable String auditId
) {
aiCommandService.rollbackCommand(auditId);
return Result.success("撤销成功");
}
}

View File

@@ -0,0 +1,20 @@
package com.youlai.boot.platform.ai.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.platform.ai.model.entity.AiCommandAudit;
import org.apache.ibatis.annotations.Mapper;
/**
* AI 命令审计 Mapper
*
* @author Ray.Hao
* @since 3.0.0
*/
@Mapper
public interface AiCommandAuditMapper extends BaseMapper<AiCommandAudit> {
}

View File

@@ -0,0 +1,37 @@
package com.youlai.boot.platform.ai.model.dto;
import lombok.Data;
import java.util.Map;
/**
* AI 命令请求 DTO
*
* @author Ray.Hao
* @since 3.0.0
*/
@Data
public class AiCommandRequestDTO {
/**
* 用户输入的自然语言命令
*/
private String command;
/**
* 当前页面路由(用于上下文)
*/
private String currentRoute;
/**
* 当前激活的组件名称
*/
private String currentComponent;
/**
* 额外上下文信息
*/
private Map<String, Object> context;
}

View File

@@ -0,0 +1,53 @@
package com.youlai.boot.platform.ai.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* AI 命令解析响应 DTO
*
* @author Ray.Hao
* @since 3.0.0
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AiCommandResponseDTO {
/**
* 是否成功解析
*/
private Boolean success;
/**
* 解析后的函数调用列表
*/
private List<FunctionCallDTO> functionCalls;
/**
* AI 的理解和说明
*/
private String explanation;
/**
* 置信度 (0-1)
*/
private Double confidence;
/**
* 错误信息
*/
private String error;
/**
* 原始 LLM 响应(用于调试)
*/
private String rawResponse;
}

View File

@@ -0,0 +1,36 @@
package com.youlai.boot.platform.ai.model.dto;
import lombok.Data;
/**
* AI 命令执行请求 DTO
*
* @author Ray.Hao
* @since 3.0.0
*/
@Data
public class AiExecuteRequestDTO {
/**
* 要执行的函数调用
*/
private FunctionCallDTO functionCall;
/**
* 确认模式auto=自动执行, manual=需要用户确认
*/
private String confirmMode;
/**
* 用户确认标志
*/
private Boolean userConfirmed;
/**
* 幂等性令牌(防止重复执行)
*/
private String idempotencyKey;
}

View File

@@ -0,0 +1,62 @@
package com.youlai.boot.platform.ai.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* AI 命令执行响应 DTO
*
* @author Ray.Hao
* @since 3.0.0
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AiExecuteResponseDTO {
/**
* 是否执行成功
*/
private Boolean success;
/**
* 执行结果数据
*/
private Object data;
/**
* 执行结果说明
*/
private String message;
/**
* 影响的记录数
*/
private Integer affectedRows;
/**
* 错误信息
*/
private String error;
/**
* 审计ID用于追踪
*/
private String auditId;
/**
* 需要用户确认
*/
private Boolean requiresConfirmation;
/**
* 确认提示信息
*/
private String confirmationPrompt;
}

View File

@@ -0,0 +1,38 @@
package com.youlai.boot.platform.ai.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* 函数调用 DTO
*
* @author Ray.Hao
* @since 3.0.0
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FunctionCallDTO {
/**
* 函数名称
*/
private String name;
/**
* 函数描述
*/
private String description;
/**
* 参数对象
*/
private Map<String, Object> arguments;
}

View File

@@ -0,0 +1,121 @@
package com.youlai.boot.platform.ai.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* AI 命令审计记录
*
* @author Ray.Hao
* @since 3.0.0
*/
@Data
@TableName("ai_command_audit")
public class AiCommandAudit {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_UUID)
private String id;
/**
* 用户ID
*/
private Long userId;
/**
* 用户名
*/
private String username;
/**
* 原始命令
*/
private String originalCommand;
/**
* 解析后的函数名称
*/
private String functionName;
/**
* 函数参数JSON
*/
private String functionArguments;
/**
* 执行状态pending, success, failed
*/
private String executeStatus;
/**
* 执行结果JSON
*/
private String executeResult;
/**
* 错误信息
*/
private String errorMessage;
/**
* 影响的记录数
*/
private Integer affectedRows;
/**
* 是否危险操作
*/
private Boolean isDangerous;
/**
* 是否需要确认
*/
private Boolean requiresConfirmation;
/**
* 用户是否确认
*/
private Boolean userConfirmed;
/**
* 幂等性令牌
*/
private String idempotencyKey;
/**
* IP 地址
*/
private String ipAddress;
/**
* 用户代理
*/
private String userAgent;
/**
* 当前路由
*/
private String currentRoute;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 执行时间(毫秒)
*/
private Long executionTime;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,101 @@
package com.youlai.boot.platform.ai.provider;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.youlai.boot.platform.ai.config.AiProperties;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
/**
* OpenAI 兼容协议的抽象提供商
*
* 适用于通义千问、DeepSeek、OpenAI、ChatGLM 等兼容 OpenAI API 的模型
*
* @author Ray.Hao
*/
@Slf4j
public abstract class AbstractOpenAiCompatibleProvider implements AiProvider {
protected final AiProperties.ProviderConfig config;
public AbstractOpenAiCompatibleProvider(AiProperties.ProviderConfig config) {
this.config = config;
}
@Override
public String call(String systemPrompt, String userPrompt) {
if (!isConfigValid()) {
throw new IllegalStateException(getProviderName() + " 配置无效");
}
try {
// 构建请求体OpenAI 标准格式)
JSONObject requestBody = JSONUtil.createObj()
.set("model", config.getModel())
.set("messages", JSONUtil.createArray()
.put(JSONUtil.createObj()
.set("role", "system")
.set("content", systemPrompt))
.put(JSONUtil.createObj()
.set("role", "user")
.set("content", userPrompt))
)
.set("temperature", 0.7);
log.info("📤 调用 {} API: {}/chat/completions", getProviderName(), config.getBaseUrl());
log.debug("请求参数: {}", requestBody);
// 发送 HTTP 请求
HttpResponse response = HttpRequest.post(config.getBaseUrl() + "/chat/completions")
.header("Authorization", "Bearer " + config.getApiKey())
.header("Content-Type", "application/json")
.body(requestBody.toString())
.timeout((int) TimeUnit.SECONDS.toMillis(config.getTimeout()))
.execute();
// 检查响应状态
if (!response.isOk()) {
String errorMsg = String.format("%s API 调用失败: HTTP %d - %s",
getProviderName(), response.getStatus(), response.body());
log.error(errorMsg);
throw new RuntimeException(errorMsg);
}
// 解析响应
JSONObject responseJson = JSONUtil.parseObj(response.body());
String content = responseJson.getByPath("choices[0].message.content", String.class);
// 记录 Token 使用情况
JSONObject usage = responseJson.getJSONObject("usage");
if (usage != null) {
Integer inputTokens = usage.getInt("prompt_tokens");
Integer outputTokens = usage.getInt("completion_tokens");
Integer totalTokens = usage.getInt("total_tokens");
log.info("✅ {} 响应成功tokens: 输入={}, 输出={}, 总计={}",
getProviderName(), inputTokens, outputTokens, totalTokens);
}
log.debug("📥 {} 返回内容: {}", getProviderName(), content);
return content;
} catch (Exception e) {
String errorMsg = String.format("%s API 调用失败: %s", getProviderName(), e.getMessage());
log.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
}
@Override
public boolean isConfigValid() {
return config != null
&& StrUtil.isNotBlank(config.getApiKey())
&& StrUtil.isNotBlank(config.getBaseUrl())
&& StrUtil.isNotBlank(config.getModel());
}
}

View File

@@ -0,0 +1,32 @@
package com.youlai.boot.platform.ai.provider;
/**
* AI 提供商接口
*
* 策略模式:不同提供商实现各自的调用逻辑
*
* @author Ray.Hao
*/
public interface AiProvider {
/**
* 调用 AI API
*
* @param systemPrompt 系统提示词
* @param userPrompt 用户提示词
* @return AI 响应内容
*/
String call(String systemPrompt, String userPrompt);
/**
* 获取提供商名称
*/
String getProviderName();
/**
* 检查配置是否有效
*/
boolean isConfigValid();
}

View File

@@ -0,0 +1,51 @@
package com.youlai.boot.platform.ai.provider;
import com.youlai.boot.platform.ai.config.AiProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* AI 提供商工厂
*
* 职责:根据配置获取对应的提供商实例
*
* @author Ray.Hao
*/
@Component
@RequiredArgsConstructor
public class AiProviderFactory {
private final AiProperties aiProperties;
/**
* Spring 自动注入所有 AiProvider 实现类
* Key: Bean 名称qwen、deepseek、openai
* Value: 提供商实例
*/
private final Map<String, AiProvider> providers;
/**
* 获取当前配置的提供商
*/
public AiProvider getCurrentProvider() {
String providerName = aiProperties.getProvider();
if (!providers.containsKey(providerName)) {
throw new IllegalStateException("不支持的 AI 提供商: " + providerName
+ ",可用提供商: " + providers.keySet());
}
AiProvider provider = providers.get(providerName);
if (!provider.isConfigValid()) {
throw new IllegalStateException(provider.getProviderName()
+ " 配置无效,请检查 API Key、Base URL 和 Model 是否配置");
}
return provider;
}
}

View File

@@ -0,0 +1,25 @@
package com.youlai.boot.platform.ai.provider.impl;
import com.youlai.boot.platform.ai.config.AiProperties;
import com.youlai.boot.platform.ai.provider.AbstractOpenAiCompatibleProvider;
import org.springframework.stereotype.Component;
/**
* DeepSeek 提供商
*
* @author Ray.Hao
*/
@Component("deepseek")
public class DeepSeekProvider extends AbstractOpenAiCompatibleProvider {
public DeepSeekProvider(AiProperties aiProperties) {
super(aiProperties.getProviders().get("deepseek"));
}
@Override
public String getProviderName() {
return config.getDisplayName() != null ? config.getDisplayName() : "DeepSeek";
}
}

View File

@@ -0,0 +1,30 @@
package com.youlai.boot.platform.ai.provider.impl;
import com.youlai.boot.platform.ai.config.AiProperties;
import com.youlai.boot.platform.ai.provider.AbstractOpenAiCompatibleProvider;
import org.springframework.stereotype.Component;
/**
* OpenAI 提供商GPT-4、GPT-3.5 等)
*
* 添加新提供商只需:
* 1. 继承 AbstractOpenAiCompatibleProvider
* 2. 实现 getProviderName()
* 3. 在配置文件中添加配置
*
* @author Ray.Hao
*/
@Component("openai")
public class OpenAiProvider extends AbstractOpenAiCompatibleProvider {
public OpenAiProvider(AiProperties aiProperties) {
super(aiProperties.getProviders().get("openai"));
}
@Override
public String getProviderName() {
return config.getDisplayName() != null ? config.getDisplayName() : "OpenAI";
}
}

View File

@@ -0,0 +1,25 @@
package com.youlai.boot.platform.ai.provider.impl;
import com.youlai.boot.platform.ai.config.AiProperties;
import com.youlai.boot.platform.ai.provider.AbstractOpenAiCompatibleProvider;
import org.springframework.stereotype.Component;
/**
* 阿里通义千问提供商
*
* @author Ray.Hao
*/
@Component("qwen")
public class QwenProvider extends AbstractOpenAiCompatibleProvider {
public QwenProvider(AiProperties aiProperties) {
super(aiProperties.getProviders().get("qwen"));
}
@Override
public String getProviderName() {
return config.getDisplayName() != null ? config.getDisplayName() : "阿里通义千问";
}
}

View File

@@ -0,0 +1,63 @@
package com.youlai.boot.platform.ai.service;
import com.youlai.boot.platform.ai.model.dto.*;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
/**
* AI 命令服务接口
*
* @author Ray.Hao
* @since 3.0.0
*/
public interface AiCommandService {
/**
* 解析自然语言命令
*
* @param request 命令请求
* @param httpRequest HTTP 请求
* @return 解析结果
*/
AiCommandResponseDTO parseCommand(AiCommandRequestDTO request, HttpServletRequest httpRequest);
/**
* 执行已解析的命令
*
* @param request 执行请求
* @param httpRequest HTTP 请求
* @return 执行结果
*/
AiExecuteResponseDTO executeCommand(AiExecuteRequestDTO request, HttpServletRequest httpRequest);
/**
* 获取命令执行历史
*
* @param page 页码
* @param size 每页数量
* @return 历史记录
*/
Map<String, Object> getCommandHistory(Integer page, Integer size);
/**
* 获取可用的函数列表
*
* @return 函数列表
*/
List<Map<String, Object>> getAvailableFunctions();
/**
* 撤销命令执行
*
* @param auditId 审计ID
*/
void rollbackCommand(String auditId);
}

View File

@@ -0,0 +1,262 @@
package com.youlai.boot.platform.ai.service.impl;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.youlai.boot.platform.ai.config.AiProperties;
import com.youlai.boot.platform.ai.model.dto.*;
import com.youlai.boot.platform.ai.model.entity.AiCommandAudit;
import com.youlai.boot.platform.ai.provider.AiProvider;
import com.youlai.boot.platform.ai.provider.AiProviderFactory;
import com.youlai.boot.platform.ai.service.AiCommandService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* AI 命令服务实现类(重构版)
*
* 重构改进:
* 1. ✅ 使用策略模式 + 工厂模式管理提供商,消除 switch-case
* 2. ✅ 配置映射化,添加新提供商只需配置,无需修改代码
* 3. ✅ 统一命名为 base-url符合行业惯例
* 4. ✅ Service 层直接返回 DTO不包装 Result由 Controller 统一处理)
* 5. ✅ 职责清晰,扩展性强
*
* @author Ray.Hao
* @since 3.0.0
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class AiCommandServiceImpl implements AiCommandService {
private final AiProperties aiProperties;
private final AiProviderFactory providerFactory;
// 审计日志存储(简化实现,实际应使用数据库)
private final Map<String, AiCommandAudit> auditStore = new HashMap<>();
/**
* 解析自然语言命令
*
* 注意:直接返回 DTO不包装 Result
* Controller 负责统一包装成 Result
*/
@Override
public AiCommandResponseDTO parseCommand(AiCommandRequestDTO request, HttpServletRequest httpRequest) {
// 检查 AI 功能是否启用
if (!aiProperties.getEnabled()) {
throw new IllegalStateException("AI 功能未启用,请在配置文件中设置 ai.enabled=true");
}
try {
// 获取当前提供商(自动校验配置)
AiProvider provider = providerFactory.getCurrentProvider();
log.info("📤 使用 {} 解析命令: {}", provider.getProviderName(), request.getCommand());
// 构建提示词
String systemPrompt = buildSystemPrompt();
String userPrompt = buildUserPrompt(request);
// 调用 AI API
String response = provider.call(systemPrompt, userPrompt);
// 解析响应
return parseAiResponse(response);
} catch (IllegalStateException e) {
// 配置错误,抛出让 Controller 处理
throw e;
} catch (Exception e) {
log.error("解析命令失败", e);
throw new RuntimeException("解析命令失败: " + e.getMessage(), e);
}
}
/**
* 执行已解析的命令
*/
@Override
public AiExecuteResponseDTO executeCommand(AiExecuteRequestDTO request, HttpServletRequest httpRequest) {
// TODO: 实现命令执行逻辑
throw new UnsupportedOperationException("待实现");
}
/**
* 获取命令执行历史
*/
@Override
public Map<String, Object> getCommandHistory(Integer page, Integer size) {
List<AiCommandAudit> allAudits = new ArrayList<>(auditStore.values());
allAudits.sort(Comparator.comparing(AiCommandAudit::getCreateTime).reversed());
int total = allAudits.size();
int start = (page - 1) * size;
int end = Math.min(start + size, total);
List<AiCommandAudit> pageData = start < total ? allAudits.subList(start, end) : new ArrayList<>();
Map<String, Object> result = new HashMap<>();
result.put("list", pageData);
result.put("total", total);
result.put("page", page);
result.put("size", size);
return result;
}
/**
* 获取可用的函数列表
*/
@Override
public List<Map<String, Object>> getAvailableFunctions() {
List<Map<String, Object>> functions = new ArrayList<>();
// 用户管理函数
functions.add(createFunctionDef(
"deleteUser",
"删除用户",
Map.of("name", "String - 用户姓名", "id", "Long - 用户ID可选")
));
functions.add(createFunctionDef(
"updateUser",
"更新用户信息",
Map.of("id", "Long - 用户ID", "nickname", "String - 昵称", "status", "Integer - 状态")
));
functions.add(createFunctionDef(
"queryUsers",
"查询用户列表",
Map.of("name", "String - 姓名(可选)", "status", "Integer - 状态(可选)")
));
// 角色管理函数
functions.add(createFunctionDef(
"assignRole",
"分配角色给用户",
Map.of("userId", "Long - 用户ID", "roleIds", "List<Long> - 角色ID列表")
));
return functions;
}
/**
* 撤销命令执行
*/
@Override
public void rollbackCommand(String auditId) {
AiCommandAudit audit = auditStore.get(auditId);
if (audit == null) {
throw new RuntimeException("审计记录不存在");
}
if (!"success".equals(audit.getExecuteStatus())) {
throw new RuntimeException("只能撤销成功执行的命令");
}
// TODO: 实现具体的回滚逻辑
log.info("撤销命令执行: auditId={}, function={}", auditId, audit.getFunctionName());
throw new UnsupportedOperationException("回滚功能尚未实现");
}
// ==================== 私有方法 ====================
/**
* 构建系统提示词(包含可用函数定义)
*/
private String buildSystemPrompt() {
return """
你是一个专业的命令解析助手。你的任务是将用户的自然语言命令转换为结构化的函数调用。
可用函数:
1. queryUsers - 查询用户列表
参数keywords(搜索关键字), status(状态), deptId(部门ID)
2. deleteUser - 删除用户
参数userId(用户ID)
3. updateUser - 更新用户信息
参数userId(用户ID), nickname(昵称), mobile(手机号)
请将命令解析为以下 JSON 格式:
{
"functionCalls": [
{
"function": "函数名",
"parameters": { "参数名": "参数值" },
"description": "操作说明"
}
]
}
""";
}
/**
* 构建用户提示词
*/
private String buildUserPrompt(AiCommandRequestDTO request) {
return "请解析以下命令:" + request.getCommand();
}
/**
* 解析 AI 响应
*/
private AiCommandResponseDTO parseAiResponse(String response) {
try {
// 提取 JSON
int jsonStart = response.indexOf("{");
int jsonEnd = response.lastIndexOf("}") + 1;
if (jsonStart == -1 || jsonEnd == 0) {
throw new IllegalArgumentException("AI 返回格式错误:未找到 JSON");
}
String jsonStr = response.substring(jsonStart, jsonEnd);
JSONObject json = JSONUtil.parseObj(jsonStr);
// 解析函数调用列表
List<FunctionCallDTO> functionCalls = new ArrayList<>();
JSONArray callsArray = json.getJSONArray("functionCalls");
if (callsArray != null) {
for (int i = 0; i < callsArray.size(); i++) {
JSONObject call = callsArray.getJSONObject(i);
functionCalls.add(FunctionCallDTO.builder()
.name(call.getStr("function"))
.arguments(call.getJSONObject("parameters") != null ?
call.getJSONObject("parameters").toBean(Map.class) : new HashMap<>())
.description(call.getStr("description"))
.build());
}
}
return AiCommandResponseDTO.builder()
.success(true)
.functionCalls(functionCalls)
.rawResponse(response)
.build();
} catch (Exception e) {
log.error("解析 AI 响应失败", e);
throw new RuntimeException("解析响应失败: " + e.getMessage(), e);
}
}
/**
* 创建函数定义
*/
private Map<String, Object> createFunctionDef(String name, String description, Map<String, String> parameters) {
Map<String, Object> func = new HashMap<>();
func.put("name", name);
func.put("description", description);
func.put("parameters", parameters);
return func;
}
}

View File

@@ -6,8 +6,8 @@ spring:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://www.youlai.tech:3306/youlai_boot?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true
username: youlai
password: 123456
username: root
password: Youlai@2025
data:
redis:
database: 0
@@ -74,7 +74,7 @@ mybatis-plus:
security:
session:
type: jwt # 会话方式 jwt/redis-token
access-token-time-to-live: 7200 # 访问令牌 有效期(单位:秒),默认 2 小时,-1 表示永不过期
access-token-time-to-live: 7200 # 访问令牌 有效期(单位:秒),默认 2 小时,-1 表示永不过期
refresh-token-time-to-live: 604800 # 刷新令牌有效期(单位:秒),默认 7 天,-1 表示永不过期
jwt:
secret-key: SecretKey012345678901234567890123456789012345678901234567890123456789 # JWT密钥(HS256算法至少32字符)
@@ -82,12 +82,12 @@ security:
allow-multi-login: true # 是否允许多设备登录
# 安全白名单路径,仅跳过 AuthorizationFilter 过滤器,还是会走 Spring Security 的其他过滤器(CSRF、CORS等)
ignore-urls:
- /api/v1/auth/login/** # 登录接口(账号密码登录、手机验证码登录和微信登录)
- /api/v1/auth/captcha # 验证码获取接口
- /api/v1/auth/refresh-token # 刷新令牌接口
- /api/v1/auth/logout # 开放退出登录
- /api/v1/auth/login/** # 登录接口(账号密码登录、手机验证码登录和微信登录)
- /api/v1/auth/captcha # 验证码获取接口
- /api/v1/auth/refresh-token # 刷新令牌接口
- /api/v1/auth/logout # 开放退出登录
- /api/v1/auth/wx/miniapp/code-login # 微信小程序code登陆
- /ws/** # WebSocket接口
- /ws/** # WebSocket接口
# 非安全端点路径,完全绕过 Spring Security 的安全控制
unsecured-urls:
- ${springdoc.swagger-ui.path}
@@ -153,7 +153,7 @@ springdoc:
api-docs:
path: /v3/api-docs
group-configs:
- group: '系统管理'
- group: "系统管理"
paths-to-match: "/**"
packages-to-scan:
- com.youlai.boot.auth.controller
@@ -165,9 +165,9 @@ springdoc:
# knife4j 接口文档配置
knife4j:
# 是否开启 Knife4j 增强功能
enable: true # 设置为 true 表示开启增强功能
enable: true # 设置为 true 表示开启增强功能
# 生产环境配置
production: false # 设置为 true 表示在生产环境中不显示文档,为 false 表示显示文档(通常在开发环境中使用)
production: false # 设置为 true 表示在生产环境中不显示文档,为 false 表示显示文档(通常在开发环境中使用)
setting:
language: zh_cn
@@ -223,3 +223,66 @@ wx:
miniapp:
app-id: xxxxxx
app-secret: xxxxxx
# ==================== AI 命令系统配置 ====================
ai:
# 是否启用 AI 功能
enabled: false
# 当前使用的提供商qwen、deepseek、openai
provider: qwen
# 所有提供商配置(统一管理,扩展性强)
providers:
# 阿里通义千问(推荐:有免费额度)
qwen:
# API Keyhttps://bailian.console.aliyun.com/ 获取)
api-key: ${QWEN_API_KEY:sk-c2941d05bf2f411ca80424fcxxxxxxxx}
# Base URLOpenAI 兼容端点)
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
# 模型qwen-plus推荐、qwen-turbo、qwen-max、qwen-long
model: qwen-plus
# 显示名称
display-name: 阿里通义千问
# 超时时间(秒)
timeout: 30
# DeepSeek
deepseek:
api-key: ${DEEPSEEK_API_KEY:}
base-url: https://api.deepseek.com/v1
model: deepseek-chat
display-name: DeepSeek
timeout: 30
# OpenAI添加新提供商只需配置无需修改代码
openai:
api-key: ${OPENAI_API_KEY:}
base-url: https://api.openai.com/v1
model: gpt-4
display-name: OpenAI GPT-4
timeout: 60
# 安全配置
security:
enable-audit: true
dangerous-operations-confirm: true
function-whitelist:
- deleteUser
- updateUser
- queryUsers
- assignRole
sensitive-params:
- password
- idCard
- bankCard
- token
# 限流配置
rate-limit:
max-executions-per-minute: 10
max-executions-per-day: 100

View File

@@ -218,4 +218,68 @@ captcha:
wx:
miniapp:
app-id: xxxxxx
app-secret: xxxxxx
app-secret: xxxxxx
# ==================== AI 命令系统配置 ====================
ai:
# 是否启用 AI 功能
enabled: false
# 当前使用的提供商qwen、deepseek、openai
provider: qwen
# 所有提供商配置(统一管理,扩展性强)
providers:
# 阿里通义千问(推荐:有免费额度)
qwen:
# API Keyhttps://bailian.console.aliyun.com/ 获取)
api-key: ${QWEN_API_KEY:sk-c2941d05bf2f411ca80424fcxxxxxxxx}
# Base URLOpenAI 兼容端点)
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
# 模型qwen-plus推荐、qwen-turbo、qwen-max、qwen-long
model: qwen-plus
# 显示名称
display-name: 阿里通义千问
# 超时时间(秒)
timeout: 30
# DeepSeek
deepseek:
api-key: ${DEEPSEEK_API_KEY:}
base-url: https://api.deepseek.com/v1
model: deepseek-chat
display-name: DeepSeek
timeout: 30
# OpenAI添加新提供商只需配置无需修改代码
openai:
api-key: ${OPENAI_API_KEY:}
base-url: https://api.openai.com/v1
model: gpt-4
display-name: OpenAI GPT-4
timeout: 60
# 安全配置
security:
enable-audit: true
dangerous-operations-confirm: true
function-whitelist:
- deleteUser
- updateUser
- queryUsers
- assignRole
sensitive-params:
- password
- idCard
- bankCard
- token
# 限流配置
rate-limit:
max-executions-per-minute: 10
max-executions-per-day: 100