feat(ai): 新增AI命令系统功能
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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("撤销成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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> {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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() : "阿里通义千问";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -6,8 +6,8 @@ spring:
|
|||||||
type: com.alibaba.druid.pool.DruidDataSource
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
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
|
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
|
username: root
|
||||||
password: 123456
|
password: Youlai@2025
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
database: 0
|
database: 0
|
||||||
@@ -74,7 +74,7 @@ mybatis-plus:
|
|||||||
security:
|
security:
|
||||||
session:
|
session:
|
||||||
type: jwt # 会话方式 jwt/redis-token
|
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 表示永不过期
|
refresh-token-time-to-live: 604800 # 刷新令牌有效期(单位:秒),默认 7 天,-1 表示永不过期
|
||||||
jwt:
|
jwt:
|
||||||
secret-key: SecretKey012345678901234567890123456789012345678901234567890123456789 # JWT密钥(HS256算法至少32字符)
|
secret-key: SecretKey012345678901234567890123456789012345678901234567890123456789 # JWT密钥(HS256算法至少32字符)
|
||||||
@@ -82,12 +82,12 @@ security:
|
|||||||
allow-multi-login: true # 是否允许多设备登录
|
allow-multi-login: true # 是否允许多设备登录
|
||||||
# 安全白名单路径,仅跳过 AuthorizationFilter 过滤器,还是会走 Spring Security 的其他过滤器(CSRF、CORS等)
|
# 安全白名单路径,仅跳过 AuthorizationFilter 过滤器,还是会走 Spring Security 的其他过滤器(CSRF、CORS等)
|
||||||
ignore-urls:
|
ignore-urls:
|
||||||
- /api/v1/auth/login/** # 登录接口(账号密码登录、手机验证码登录和微信登录)
|
- /api/v1/auth/login/** # 登录接口(账号密码登录、手机验证码登录和微信登录)
|
||||||
- /api/v1/auth/captcha # 验证码获取接口
|
- /api/v1/auth/captcha # 验证码获取接口
|
||||||
- /api/v1/auth/refresh-token # 刷新令牌接口
|
- /api/v1/auth/refresh-token # 刷新令牌接口
|
||||||
- /api/v1/auth/logout # 开放退出登录
|
- /api/v1/auth/logout # 开放退出登录
|
||||||
- /api/v1/auth/wx/miniapp/code-login # 微信小程序code登陆
|
- /api/v1/auth/wx/miniapp/code-login # 微信小程序code登陆
|
||||||
- /ws/** # WebSocket接口
|
- /ws/** # WebSocket接口
|
||||||
# 非安全端点路径,完全绕过 Spring Security 的安全控制
|
# 非安全端点路径,完全绕过 Spring Security 的安全控制
|
||||||
unsecured-urls:
|
unsecured-urls:
|
||||||
- ${springdoc.swagger-ui.path}
|
- ${springdoc.swagger-ui.path}
|
||||||
@@ -153,7 +153,7 @@ springdoc:
|
|||||||
api-docs:
|
api-docs:
|
||||||
path: /v3/api-docs
|
path: /v3/api-docs
|
||||||
group-configs:
|
group-configs:
|
||||||
- group: '系统管理'
|
- group: "系统管理"
|
||||||
paths-to-match: "/**"
|
paths-to-match: "/**"
|
||||||
packages-to-scan:
|
packages-to-scan:
|
||||||
- com.youlai.boot.auth.controller
|
- com.youlai.boot.auth.controller
|
||||||
@@ -165,9 +165,9 @@ springdoc:
|
|||||||
# knife4j 接口文档配置
|
# knife4j 接口文档配置
|
||||||
knife4j:
|
knife4j:
|
||||||
# 是否开启 Knife4j 增强功能
|
# 是否开启 Knife4j 增强功能
|
||||||
enable: true # 设置为 true 表示开启增强功能
|
enable: true # 设置为 true 表示开启增强功能
|
||||||
# 生产环境配置
|
# 生产环境配置
|
||||||
production: false # 设置为 true 表示在生产环境中不显示文档,为 false 表示显示文档(通常在开发环境中使用)
|
production: false # 设置为 true 表示在生产环境中不显示文档,为 false 表示显示文档(通常在开发环境中使用)
|
||||||
setting:
|
setting:
|
||||||
language: zh_cn
|
language: zh_cn
|
||||||
|
|
||||||
@@ -223,3 +223,66 @@ wx:
|
|||||||
miniapp:
|
miniapp:
|
||||||
app-id: xxxxxx
|
app-id: xxxxxx
|
||||||
app-secret: xxxxxx
|
app-secret: xxxxxx
|
||||||
|
|
||||||
|
# ==================== AI 命令系统配置 ====================
|
||||||
|
ai:
|
||||||
|
# 是否启用 AI 功能
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# 当前使用的提供商:qwen、deepseek、openai
|
||||||
|
provider: qwen
|
||||||
|
|
||||||
|
# 所有提供商配置(统一管理,扩展性强)
|
||||||
|
providers:
|
||||||
|
# 阿里通义千问(推荐:有免费额度)
|
||||||
|
qwen:
|
||||||
|
# API Key(https://bailian.console.aliyun.com/ 获取)
|
||||||
|
api-key: ${QWEN_API_KEY:sk-c2941d05bf2f411ca80424fcxxxxxxxx}
|
||||||
|
|
||||||
|
# Base URL(OpenAI 兼容端点)
|
||||||
|
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
|
||||||
|
|||||||
@@ -219,3 +219,67 @@ wx:
|
|||||||
miniapp:
|
miniapp:
|
||||||
app-id: xxxxxx
|
app-id: xxxxxx
|
||||||
app-secret: xxxxxx
|
app-secret: xxxxxx
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== AI 命令系统配置 ====================
|
||||||
|
ai:
|
||||||
|
# 是否启用 AI 功能
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# 当前使用的提供商:qwen、deepseek、openai
|
||||||
|
provider: qwen
|
||||||
|
|
||||||
|
# 所有提供商配置(统一管理,扩展性强)
|
||||||
|
providers:
|
||||||
|
# 阿里通义千问(推荐:有免费额度)
|
||||||
|
qwen:
|
||||||
|
# API Key(https://bailian.console.aliyun.com/ 获取)
|
||||||
|
api-key: ${QWEN_API_KEY:sk-c2941d05bf2f411ca80424fcxxxxxxxx}
|
||||||
|
|
||||||
|
# Base URL(OpenAI 兼容端点)
|
||||||
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user