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
|
||||
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 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
|
||||
|
||||
@@ -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 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