diff --git a/sql/mysql/youlai_boot.sql b/sql/mysql/youlai_boot.sql index cd5c268a..dc52c6cd 100644 --- a/sql/mysql/youlai_boot.sql +++ b/sql/mysql/youlai_boot.sql @@ -574,53 +574,37 @@ INSERT INTO `sys_user_notice` VALUES (10, 10, 2, 1, NULL, now(), now(), 0); -- ---------------------------- -- AI 命令记录表 -- ---------------------------- -DROP TABLE IF EXISTS `ai_command_record`; -CREATE TABLE `ai_command_record` ( +DROP TABLE IF EXISTS `ai_command_log`; +CREATE TABLE `ai_command_log` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `user_id` bigint DEFAULT NULL COMMENT '用户ID', `username` varchar(64) DEFAULT NULL COMMENT '用户名', `original_command` text COMMENT '原始命令', - -- 解析相关字段 - `provider` varchar(32) DEFAULT NULL COMMENT 'AI供应商(qwen/openai/deepseek/gemini等)', - `model` varchar(64) DEFAULT NULL COMMENT 'AI模型(qwen-plus/qwen-max/gpt-4-turbo等)', - `parse_success` tinyint(1) DEFAULT NULL COMMENT '解析是否成功(0-失败, 1-成功)', + `ai_provider` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'AI 供应商(qwen/openai/deepseek/gemini等)', + `ai_model` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'AI 模型名称(qwen-plus/qwen-max/gpt-4-turbo等)', + `parse_status` tinyint DEFAULT '0' COMMENT '解析是否成功(0-失败, 1-成功)', `function_calls` text COMMENT '解析出的函数调用列表(JSON)', `explanation` varchar(500) DEFAULT NULL COMMENT 'AI的理解说明', `confidence` decimal(3,2) DEFAULT NULL COMMENT '置信度(0.00-1.00)', `parse_error_message` text COMMENT '解析错误信息', `input_tokens` int DEFAULT NULL COMMENT '输入Token数量', `output_tokens` int DEFAULT NULL COMMENT '输出Token数量', - `total_tokens` int DEFAULT NULL COMMENT '总Token数量', - `parse_time` bigint DEFAULT NULL COMMENT '解析耗时(毫秒)', - -- 执行相关字段 + `parse_duration_ms` int DEFAULT NULL COMMENT '解析耗时(毫秒)', `function_name` varchar(255) DEFAULT NULL COMMENT '执行的函数名称', `function_arguments` text COMMENT '函数参数(JSON)', - `execute_status` varchar(20) DEFAULT NULL COMMENT '执行状态(pending-待执行, success-成功, failed-失败)', - `execute_result` text COMMENT '执行结果(JSON)', + `execute_status` tinyint(1) DEFAULT NULL COMMENT '执行状态(0-待执行, 1-成功, -1-失败)', `execute_error_message` text COMMENT '执行错误信息', - `affected_rows` int DEFAULT NULL COMMENT '影响的记录数', - `is_dangerous` tinyint(1) DEFAULT '0' COMMENT '是否危险操作(0-否, 1-是)', - `requires_confirmation` tinyint(1) DEFAULT '0' COMMENT '是否需要确认(0-否, 1-是)', - `user_confirmed` tinyint(1) DEFAULT NULL COMMENT '用户是否确认(0-否, 1-是)', - `idempotency_key` varchar(128) DEFAULT NULL COMMENT '幂等性令牌(防止重复执行)', - `execution_time` bigint DEFAULT NULL COMMENT '执行耗时(毫秒)', - -- 通用字段 `ip_address` varchar(128) DEFAULT NULL COMMENT 'IP地址', - `user_agent` varchar(512) DEFAULT NULL COMMENT '用户代理', - `current_route` varchar(255) DEFAULT NULL COMMENT '当前页面路由', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`), - UNIQUE KEY `uk_idempotency_key` (`idempotency_key`), KEY `idx_user_id` (`user_id`), KEY `idx_create_time` (`create_time`), - KEY `idx_provider` (`provider`), - KEY `idx_model` (`model`), - KEY `idx_parse_success` (`parse_success`), - KEY `idx_execute_status` (`execute_status`), - KEY `idx_is_dangerous` (`is_dangerous`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='AI命令记录表'; + KEY `idx_provider` (`ai_provider`), + KEY `idx_model` (`ai_model`), + KEY `idx_parse_success` (`parse_status`), + KEY `idx_execute_status` (`execute_status`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI 命令记录表'; SET FOREIGN_KEY_CHECKS = 1; diff --git a/src/main/java/com/youlai/boot/platform/ai/controller/AiCommandController.java b/src/main/java/com/youlai/boot/platform/ai/controller/AiCommandController.java index 464dfc19..f64b833f 100644 --- a/src/main/java/com/youlai/boot/platform/ai/controller/AiCommandController.java +++ b/src/main/java/com/youlai/boot/platform/ai/controller/AiCommandController.java @@ -7,8 +7,8 @@ import com.youlai.boot.platform.ai.model.dto.AiExecuteRequestDTO; import com.youlai.boot.platform.ai.model.dto.AiParseRequestDTO; import com.youlai.boot.platform.ai.model.dto.AiParseResponseDTO; import com.youlai.boot.platform.ai.model.query.AiCommandPageQuery; -import com.youlai.boot.platform.ai.model.vo.AiCommandRecordVO; -import com.youlai.boot.platform.ai.service.AiCommandRecordService; +import com.youlai.boot.platform.ai.model.vo.AiCommandLogVO; +import com.youlai.boot.platform.ai.service.AiCommandLogService; import com.youlai.boot.platform.ai.service.AiCommandService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -32,7 +32,7 @@ import org.springframework.web.bind.annotation.*; public class AiCommandController { private final AiCommandService aiCommandService; - private final AiCommandRecordService recordService; + private final AiCommandLogService logService; @Operation(summary = "解析自然语言命令") @PostMapping("/parse") @@ -72,17 +72,17 @@ public class AiCommandController { @Operation(summary = "获取AI命令记录分页列表") @GetMapping("/records") - public PageResult getRecordPage(AiCommandPageQuery queryParams) { - IPage page = recordService.getRecordPage(queryParams); + public PageResult getLogPage(AiCommandPageQuery queryParams) { + IPage page = logService.getLogPage(queryParams); return PageResult.success(page); } @Operation(summary = "撤销命令执行") - @PostMapping("/rollback/{recordId}") + @PostMapping("/rollback/{logId}") public Result rollbackCommand( - @Parameter(description = "记录ID") @PathVariable String recordId + @Parameter(description = "记录ID") @PathVariable String logId ) { - recordService.rollbackCommand(recordId); + logService.rollbackCommand(logId); return Result.success("撤销成功"); } diff --git a/src/main/java/com/youlai/boot/platform/ai/mapper/AiCommandRecordMapper.java b/src/main/java/com/youlai/boot/platform/ai/mapper/AiCommandLogMapper.java similarity index 57% rename from src/main/java/com/youlai/boot/platform/ai/mapper/AiCommandRecordMapper.java rename to src/main/java/com/youlai/boot/platform/ai/mapper/AiCommandLogMapper.java index 40e16903..7f6fbed8 100644 --- a/src/main/java/com/youlai/boot/platform/ai/mapper/AiCommandRecordMapper.java +++ b/src/main/java/com/youlai/boot/platform/ai/mapper/AiCommandLogMapper.java @@ -3,21 +3,23 @@ package com.youlai.boot.platform.ai.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.youlai.boot.platform.ai.model.entity.AiCommandRecord; +import com.youlai.boot.platform.ai.model.entity.AiCommandLog; import com.youlai.boot.platform.ai.model.query.AiCommandPageQuery; -import com.youlai.boot.platform.ai.model.vo.AiCommandRecordVO; +import com.youlai.boot.platform.ai.model.vo.AiCommandLogVO; import org.apache.ibatis.annotations.Mapper; /** * AI 命令记录 Mapper + * + * @author Ray.Hao + * @since 3.0.0 */ @Mapper -public interface AiCommandRecordMapper extends BaseMapper { +public interface AiCommandLogMapper extends BaseMapper { /** * 获取 AI 命令记录分页列表 */ - IPage getRecordPage(Page page, AiCommandPageQuery queryParams); + IPage getLogPage(Page page, AiCommandPageQuery queryParams); } - diff --git a/src/main/java/com/youlai/boot/platform/ai/model/entity/AiCommandRecord.java b/src/main/java/com/youlai/boot/platform/ai/model/entity/AiCommandLog.java similarity index 58% rename from src/main/java/com/youlai/boot/platform/ai/model/entity/AiCommandRecord.java rename to src/main/java/com/youlai/boot/platform/ai/model/entity/AiCommandLog.java index 8fed3d44..47e1f067 100644 --- a/src/main/java/com/youlai/boot/platform/ai/model/entity/AiCommandRecord.java +++ b/src/main/java/com/youlai/boot/platform/ai/model/entity/AiCommandLog.java @@ -8,15 +8,15 @@ import lombok.EqualsAndHashCode; import java.math.BigDecimal; /** - * AI 命令记录实体(合并解析和执行记录) + * AI 命令记录实体 * * @author Ray.Hao * @since 3.0.0 */ @Data @EqualsAndHashCode(callSuper = true) -@TableName("ai_command_record") -public class AiCommandRecord extends BaseEntity { +@TableName("ai_command_log") +public class AiCommandLog extends BaseEntity { /** 用户ID */ private Long userId; @@ -30,13 +30,13 @@ public class AiCommandRecord extends BaseEntity { // ==================== 解析相关字段 ==================== /** AI 供应商(qwen/openai/deepseek等) */ - private String provider; + private String aiProvider; /** AI 模型(qwen-plus/qwen-max/gpt-4-turbo等) */ - private String model; + private String aiModel; - /** 解析是否成功 */ - private Boolean parseSuccess; + /** 解析状态(0-失败, 1-成功) */ + private Integer parseStatus; /** 解析出的函数调用列表(JSON) */ private String functionCalls; @@ -56,11 +56,8 @@ public class AiCommandRecord extends BaseEntity { /** 输出 Token 数量 */ private Integer outputTokens; - /** 总 Token 数量 */ - private Integer totalTokens; - /** 解析耗时(毫秒) */ - private Long parseTime; + private Integer parseDurationMs; // ==================== 执行相关字段 ==================== @@ -70,46 +67,15 @@ public class AiCommandRecord extends BaseEntity { /** 函数参数(JSON) */ private String functionArguments; - /** 执行状态:pending, success, failed */ - private String executeStatus; - - /** 执行结果(JSON) */ - private String executeResult; + /** 执行状态(0-待执行, 1-成功, -1-失败) */ + private Integer executeStatus; /** 执行错误信息 */ private String executeErrorMessage; - /** 影响的记录数 */ - private Integer affectedRows; - - /** 是否危险操作 */ - private Boolean isDangerous; - - /** 是否需要确认 */ - private Boolean requiresConfirmation; - - /** 用户是否确认 */ - private Boolean userConfirmed; - - /** 幂等性令牌(防止重复执行) */ - private String idempotencyKey; - - /** 执行耗时(毫秒) */ - private Long executionTime; - // ==================== 通用字段 ==================== /** IP 地址 */ private String ipAddress; - - /** 用户代理 */ - private String userAgent; - - /** 当前页面路由 */ - private String currentRoute; - - /** 备注 */ - private String remark; } - diff --git a/src/main/java/com/youlai/boot/platform/ai/model/query/AiCommandPageQuery.java b/src/main/java/com/youlai/boot/platform/ai/model/query/AiCommandPageQuery.java index 279b915d..0322b09e 100644 --- a/src/main/java/com/youlai/boot/platform/ai/model/query/AiCommandPageQuery.java +++ b/src/main/java/com/youlai/boot/platform/ai/model/query/AiCommandPageQuery.java @@ -21,19 +21,25 @@ public class AiCommandPageQuery extends BasePageQuery { @Schema(description = "关键字(原始命令/函数名称/用户名)") private String keywords; - @Schema(description = "执行状态(pending-待执行, success-成功, failed-失败)") - private String executeStatus; + @Schema(description = "执行状态(0-待执行, 1-成功, -1-失败)") + private Integer executeStatus; @Schema(description = "用户ID") private Long userId; - @Schema(description = "是否危险操作") - private Boolean isDangerous; + @Schema(description = "解析状态(0-失败, 1-成功)") + private Integer parseStatus; @Schema(description = "创建时间范围") private List createTime; @Schema(description = "函数名称") private String functionName; + + @Schema(description = "AI供应商") + private String aiProvider; + + @Schema(description = "AI模型") + private String aiModel; } diff --git a/src/main/java/com/youlai/boot/platform/ai/model/vo/AiCommandRecordVO.java b/src/main/java/com/youlai/boot/platform/ai/model/vo/AiCommandLogVO.java similarity index 63% rename from src/main/java/com/youlai/boot/platform/ai/model/vo/AiCommandRecordVO.java rename to src/main/java/com/youlai/boot/platform/ai/model/vo/AiCommandLogVO.java index 83df97ff..0481087d 100644 --- a/src/main/java/com/youlai/boot/platform/ai/model/vo/AiCommandRecordVO.java +++ b/src/main/java/com/youlai/boot/platform/ai/model/vo/AiCommandLogVO.java @@ -9,11 +9,14 @@ import java.math.BigDecimal; import java.time.LocalDateTime; /** - * AI命令记录VO(合并解析和执行记录) + * AI命令记录VO + * + * @author Ray.Hao + * @since 3.0.0 */ @Data @Schema(description = "AI命令记录VO") -public class AiCommandRecordVO implements Serializable { +public class AiCommandLogVO implements Serializable { @Schema(description = "主键ID") private String id; @@ -30,13 +33,13 @@ public class AiCommandRecordVO implements Serializable { // ==================== 解析相关字段 ==================== @Schema(description = "AI供应商") - private String provider; + private String aiProvider; @Schema(description = "AI模型") - private String model; + private String aiModel; - @Schema(description = "解析是否成功") - private Boolean parseSuccess; + @Schema(description = "解析状态(0-失败, 1-成功)") + private Integer parseStatus; @Schema(description = "解析出的函数调用列表(JSON)") private String functionCalls; @@ -56,11 +59,8 @@ public class AiCommandRecordVO implements Serializable { @Schema(description = "输出Token数量") private Integer outputTokens; - @Schema(description = "总Token数量") - private Integer totalTokens; - @Schema(description = "解析耗时(毫秒)") - private Long parseTime; + private Integer parseDurationMs; // ==================== 执行相关字段 ==================== @@ -70,41 +70,17 @@ public class AiCommandRecordVO implements Serializable { @Schema(description = "函数参数(JSON)") private String functionArguments; - @Schema(description = "执行状态") - private String executeStatus; - - @Schema(description = "执行结果(JSON)") - private String executeResult; + @Schema(description = "执行状态(0-待执行, 1-成功, -1-失败)") + private Integer executeStatus; @Schema(description = "执行错误信息") private String executeErrorMessage; - @Schema(description = "影响的记录数") - private Integer affectedRows; - - @Schema(description = "是否危险操作") - private Boolean isDangerous; - - @Schema(description = "是否需要确认") - private Boolean requiresConfirmation; - - @Schema(description = "用户是否确认") - private Boolean userConfirmed; - - @Schema(description = "执行耗时(毫秒)") - private Long executionTime; - // ==================== 通用字段 ==================== @Schema(description = "IP地址") private String ipAddress; - @Schema(description = "用户代理") - private String userAgent; - - @Schema(description = "当前页面路由") - private String currentRoute; - @Schema(description = "创建时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; @@ -112,9 +88,5 @@ public class AiCommandRecordVO implements Serializable { @Schema(description = "更新时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime; - - @Schema(description = "备注") - private String remark; } - diff --git a/src/main/java/com/youlai/boot/platform/ai/service/AiCommandRecordService.java b/src/main/java/com/youlai/boot/platform/ai/service/AiCommandLogService.java similarity index 55% rename from src/main/java/com/youlai/boot/platform/ai/service/AiCommandRecordService.java rename to src/main/java/com/youlai/boot/platform/ai/service/AiCommandLogService.java index a4d9543a..4628881d 100644 --- a/src/main/java/com/youlai/boot/platform/ai/service/AiCommandRecordService.java +++ b/src/main/java/com/youlai/boot/platform/ai/service/AiCommandLogService.java @@ -2,14 +2,17 @@ package com.youlai.boot.platform.ai.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; -import com.youlai.boot.platform.ai.model.entity.AiCommandRecord; +import com.youlai.boot.platform.ai.model.entity.AiCommandLog; import com.youlai.boot.platform.ai.model.query.AiCommandPageQuery; -import com.youlai.boot.platform.ai.model.vo.AiCommandRecordVO; +import com.youlai.boot.platform.ai.model.vo.AiCommandLogVO; /** * AI 命令记录服务接口 + * + * @author Ray.Hao + * @since 3.0.0 */ -public interface AiCommandRecordService extends IService { +public interface AiCommandLogService extends IService { /** * 获取命令记录分页列表 @@ -17,14 +20,13 @@ public interface AiCommandRecordService extends IService { * @param queryParams 查询参数 * @return 命令记录分页列表 */ - IPage getRecordPage(AiCommandPageQuery queryParams); + IPage getLogPage(AiCommandPageQuery queryParams); /** * 撤销命令执行 * - * @param recordId 记录ID + * @param logId 记录ID */ - void rollbackCommand(String recordId); + void rollbackCommand(String logId); } - diff --git a/src/main/java/com/youlai/boot/platform/ai/service/impl/AiCommandLogServiceImpl.java b/src/main/java/com/youlai/boot/platform/ai/service/impl/AiCommandLogServiceImpl.java new file mode 100644 index 00000000..05f1143a --- /dev/null +++ b/src/main/java/com/youlai/boot/platform/ai/service/impl/AiCommandLogServiceImpl.java @@ -0,0 +1,49 @@ +package com.youlai.boot.platform.ai.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.youlai.boot.platform.ai.mapper.AiCommandLogMapper; +import com.youlai.boot.platform.ai.model.entity.AiCommandLog; +import com.youlai.boot.platform.ai.model.query.AiCommandPageQuery; +import com.youlai.boot.platform.ai.model.vo.AiCommandLogVO; +import com.youlai.boot.platform.ai.service.AiCommandLogService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * AI 命令记录服务实现类 + * + * @author Ray.Hao + * @since 3.0.0 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class AiCommandLogServiceImpl extends ServiceImpl + implements AiCommandLogService { + + @Override + public IPage getLogPage(AiCommandPageQuery queryParams) { + Page page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize()); + return this.baseMapper.getLogPage(page, queryParams); + } + + @Override + public void rollbackCommand(String logId) { + AiCommandLog log = this.getById(logId); + if (log == null) { + throw new RuntimeException("命令记录不存在"); + } + + if (log.getExecuteStatus() == null || log.getExecuteStatus() != 1) { + throw new RuntimeException("只能撤销成功执行的命令"); + } + + // TODO: 实现具体的回滚逻辑 + log.info("撤销命令执行: logId={}, function={}", logId, log.getFunctionName()); + throw new UnsupportedOperationException("回滚功能尚未实现"); + } +} + diff --git a/src/main/java/com/youlai/boot/platform/ai/service/impl/AiCommandRecordServiceImpl.java b/src/main/java/com/youlai/boot/platform/ai/service/impl/AiCommandRecordServiceImpl.java deleted file mode 100644 index 1a0b87a8..00000000 --- a/src/main/java/com/youlai/boot/platform/ai/service/impl/AiCommandRecordServiceImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.youlai.boot.platform.ai.service.impl; - -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.youlai.boot.platform.ai.mapper.AiCommandRecordMapper; -import com.youlai.boot.platform.ai.model.entity.AiCommandRecord; -import com.youlai.boot.platform.ai.model.query.AiCommandPageQuery; -import com.youlai.boot.platform.ai.model.vo.AiCommandRecordVO; -import com.youlai.boot.platform.ai.service.AiCommandRecordService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -/** - * AI 命令记录服务实现类 - */ -@Service -@Slf4j -@RequiredArgsConstructor -public class AiCommandRecordServiceImpl extends ServiceImpl - implements AiCommandRecordService { - - @Override - public IPage getRecordPage(AiCommandPageQuery queryParams) { - Page page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize()); - return this.baseMapper.getRecordPage(page, queryParams); - } - - @Override - public void rollbackCommand(String recordId) { - AiCommandRecord record = this.getById(recordId); - if (record == null) { - throw new RuntimeException("命令记录不存在"); - } - - if (!"success".equals(record.getExecuteStatus())) { - throw new RuntimeException("只能撤销成功执行的命令"); - } - - // TODO: 实现具体的回滚逻辑 - log.info("撤销命令执行: recordId={}, function={}", recordId, record.getFunctionName()); - throw new UnsupportedOperationException("回滚功能尚未实现"); - } -} - - diff --git a/src/main/java/com/youlai/boot/platform/ai/service/impl/AiCommandServiceImpl.java b/src/main/java/com/youlai/boot/platform/ai/service/impl/AiCommandServiceImpl.java index 72e8eb23..27197fa0 100644 --- a/src/main/java/com/youlai/boot/platform/ai/service/impl/AiCommandServiceImpl.java +++ b/src/main/java/com/youlai/boot/platform/ai/service/impl/AiCommandServiceImpl.java @@ -6,13 +6,12 @@ import cn.hutool.extra.servlet.JakartaServletUtil; import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.youlai.boot.platform.ai.model.dto.AiExecuteRequestDTO; import com.youlai.boot.platform.ai.model.dto.AiFunctionCallDTO; import com.youlai.boot.platform.ai.model.dto.AiParseRequestDTO; import com.youlai.boot.platform.ai.model.dto.AiParseResponseDTO; -import com.youlai.boot.platform.ai.model.entity.AiCommandRecord; -import com.youlai.boot.platform.ai.service.AiCommandRecordService; +import com.youlai.boot.platform.ai.model.entity.AiCommandLog; +import com.youlai.boot.platform.ai.service.AiCommandLogService; import com.youlai.boot.platform.ai.service.AiCommandService; import com.youlai.boot.platform.ai.tools.UserTools; import com.youlai.boot.security.util.SecurityUtils; @@ -51,7 +50,7 @@ public class AiCommandServiceImpl implements AiCommandService { 当无法识别命令时,success=false,并给出 error。 """; - private final AiCommandRecordService recordService; + private final AiCommandLogService logService; private final UserTools userTools; private final ChatClient chatClient; @@ -72,14 +71,13 @@ public class AiCommandServiceImpl implements AiCommandService { String username = SecurityUtils.getUsername(); String ipAddress = JakartaServletUtil.getClientIP(httpRequest); - AiCommandRecord record = new AiCommandRecord(); - record.setUserId(userId); - record.setUsername(username); - record.setOriginalCommand(command); - record.setIpAddress(ipAddress); - record.setCurrentRoute(request.getCurrentRoute()); - record.setProvider("spring-ai"); - record.setModel("auto"); + AiCommandLog log = new AiCommandLog(); + log.setUserId(userId); + log.setUsername(username); + log.setOriginalCommand(command); + log.setIpAddress(ipAddress); + log.setAiProvider("spring-ai"); + log.setAiModel("auto"); String systemPrompt = buildSystemPrompt(); String userPrompt = buildUserPrompt(request); @@ -97,19 +95,20 @@ public class AiCommandServiceImpl implements AiCommandService { ParseResult parseResult = parseAiResponse(rawContent); - record.setProvider(StrUtil.emptyToDefault(parseResult.provider(), "spring-ai")); - record.setModel(StrUtil.emptyToDefault(parseResult.model(), "auto")); - record.setParseSuccess(parseResult.success()); - record.setExplanation(parseResult.explanation()); - record.setFunctionCalls(JSONUtil.toJsonStr(parseResult.functionCalls())); - record.setConfidence(parseResult.confidence() != null ? BigDecimal.valueOf(parseResult.confidence()) : null); - record.setParseErrorMessage(parseResult.success() ? null : StrUtil.emptyToDefault(parseResult.error(), "解析失败")); - record.setParseTime(System.currentTimeMillis() - startTime); + log.setAiProvider(StrUtil.emptyToDefault(parseResult.provider(), "spring-ai")); + log.setAiModel(StrUtil.emptyToDefault(parseResult.model(), "auto")); + log.setParseStatus(parseResult.success() ? 1 : 0); + log.setExplanation(parseResult.explanation()); + log.setFunctionCalls(JSONUtil.toJsonStr(parseResult.functionCalls())); + log.setConfidence(parseResult.confidence() != null ? BigDecimal.valueOf(parseResult.confidence()) : null); + log.setParseErrorMessage(parseResult.success() ? null : StrUtil.emptyToDefault(parseResult.error(), "解析失败")); + long duration = System.currentTimeMillis() - startTime; + log.setParseDurationMs((int) duration); - recordService.save(record); + logService.save(log); AiParseResponseDTO response = AiParseResponseDTO.builder() - .parseLogId(record.getId()) + .parseLogId(log.getId()) .success(parseResult.success()) .functionCalls(parseResult.functionCalls()) .explanation(parseResult.explanation()) @@ -121,17 +120,17 @@ public class AiCommandServiceImpl implements AiCommandService { if (!parseResult.success()) { log.warn("❗️ AI 未能解析命令: {}", parseResult.error()); } else { - log.info("✅ 解析成功,审计记录ID: {}", record.getId()); + log.info("✅ 解析成功,审计记录ID: {}", log.getId()); } return response; } catch (Exception e) { long duration = System.currentTimeMillis() - startTime; - record.setParseSuccess(false); - record.setFunctionCalls(JSONUtil.toJsonStr(Collections.emptyList())); - record.setParseErrorMessage(e.getMessage()); - record.setParseTime(duration); - recordService.save(record); + log.setParseStatus(0); + log.setFunctionCalls(JSONUtil.toJsonStr(Collections.emptyList())); + log.setParseErrorMessage(e.getMessage()); + log.setParseDurationMs((int) duration); + logService.save(log); log.error("❌ 解析命令失败: {}", e.getMessage(), e); throw new RuntimeException("解析命令失败: " + e.getMessage(), e); @@ -232,98 +231,59 @@ public class AiCommandServiceImpl implements AiCommandService { AiFunctionCallDTO functionCall = request.getFunctionCall(); - // 判断是否为危险操作 - boolean isDangerous = isDangerousOperation(functionCall.getName()); - // 根据解析日志ID获取审计记录,如果不存在则创建新记录 - AiCommandRecord record; + AiCommandLog log; if (StrUtil.isNotBlank(request.getParseLogId())) { // 更新已存在的审计记录(解析阶段已创建) - record = recordService.getById(request.getParseLogId()); - if (record == null) { + log = logService.getById(request.getParseLogId()); + if (log == null) { throw new IllegalStateException("未找到对应的解析记录,ID: " + request.getParseLogId()); } } else { // 如果没有解析日志ID,创建新记录(兼容直接执行的情况) - record = new AiCommandRecord(); - record.setUserId(userId); - record.setUsername(username); - record.setOriginalCommand(request.getOriginalCommand()); - record.setIpAddress(ipAddress); - record.setCurrentRoute(request.getCurrentRoute()); - recordService.save(record); + log = new AiCommandLog(); + log.setUserId(userId); + log.setUsername(username); + log.setOriginalCommand(request.getOriginalCommand()); + log.setIpAddress(ipAddress); + logService.save(log); } // 更新执行相关字段 - record.setFunctionName(functionCall.getName()); - record.setFunctionArguments(JSONUtil.toJsonStr(functionCall.getArguments())); - record.setIsDangerous(isDangerous); - record.setRequiresConfirmation(request.getConfirmMode() != null && - "manual".equals(request.getConfirmMode())); - record.setUserConfirmed(request.getUserConfirmed()); - record.setIdempotencyKey(request.getIdempotencyKey()); - record.setUserAgent(httpRequest.getHeader("User-Agent")); - record.setExecuteStatus("pending"); + log.setFunctionName(functionCall.getName()); + log.setFunctionArguments(JSONUtil.toJsonStr(functionCall.getArguments())); + log.setExecuteStatus(0); // 0-待执行 try { - // 幂等性检查 - if (StrUtil.isNotBlank(request.getIdempotencyKey())) { - AiCommandRecord existing = recordService.getOne( - new LambdaQueryWrapper() - .eq(AiCommandRecord::getIdempotencyKey, request.getIdempotencyKey()) - .ne(AiCommandRecord::getId, record.getId()) // 排除当前记录 - ); - if (existing != null) { - log.warn("⚠️ 检测到重复执行,幂等性令牌: {}", request.getIdempotencyKey()); - throw new IllegalStateException("该操作已执行,请勿重复提交"); - } - } - // 🎯 执行具体的函数调用 Object result = executeFunctionCall(functionCall); // 更新执行成功 - record.setExecuteStatus("success"); - record.setExecuteResult(JSONUtil.toJsonStr(result)); - record.setExecutionTime(System.currentTimeMillis() - startTime); + log.setExecuteStatus(1); // 1-成功 + log.setExecuteErrorMessage(null); // 更新审计记录 - recordService.updateById(record); + logService.updateById(log); - log.info("✅ 命令执行成功,审计记录ID: {}", record.getId()); + log.info("✅ 命令执行成功,审计记录ID: {}", log.getId()); return result; } catch (Exception e) { // 更新执行失败 - record.setExecuteStatus("failed"); - record.setExecuteErrorMessage(e.getMessage()); - record.setExecutionTime(System.currentTimeMillis() - startTime); + log.setExecuteStatus(-1); // -1-失败 + log.setExecuteErrorMessage(e.getMessage()); // 更新审计记录 - recordService.updateById(record); + logService.updateById(log); - log.error("❌ 命令执行失败,审计记录ID: {}", record.getId(), e); + log.error("❌ 命令执行失败,审计记录ID: {}", log.getId(), e); // 抛出异常,由 Controller 统一处理 throw e; } } - /** - * 判断是否为危险操作 - */ - private boolean isDangerousOperation(String functionName) { - String[] dangerousKeywords = {"delete", "remove", "drop", "truncate", "clear"}; - String lowerName = functionName.toLowerCase(); - for (String keyword : dangerousKeywords) { - if (lowerName.contains(keyword)) { - return true; - } - } - return false; - } - /** * 执行具体的函数调用 */ diff --git a/src/main/resources/mapper/ai/AiCommandLogMapper.xml b/src/main/resources/mapper/ai/AiCommandLogMapper.xml new file mode 100644 index 00000000..f006c788 --- /dev/null +++ b/src/main/resources/mapper/ai/AiCommandLogMapper.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/ai/AiCommandRecordMapper.xml b/src/main/resources/mapper/ai/AiCommandRecordMapper.xml deleted file mode 100644 index d23d1c26..00000000 --- a/src/main/resources/mapper/ai/AiCommandRecordMapper.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -