diff --git a/ruoyi-common/hwmom-common-mom/src/main/java/org/dromara/common/constant/HwMomAiConstants.java b/ruoyi-common/hwmom-common-mom/src/main/java/org/dromara/common/constant/HwMomAiConstants.java new file mode 100644 index 00000000..c484cdf8 --- /dev/null +++ b/ruoyi-common/hwmom-common-mom/src/main/java/org/dromara/common/constant/HwMomAiConstants.java @@ -0,0 +1,15 @@ +package org.dromara.common.constant; + +/** + * MES常量信息 + * + * @author xins + */ +public interface HwMomAiConstants { + + /** + * redis存储数据库表结构key + */ + public static final String AI_DATABASE_SCHEMA_KEY_PREFIX = "ai:database:schema:"; + +} diff --git a/ruoyi-modules/hwmom-ai/pom.xml b/ruoyi-modules/hwmom-ai/pom.xml index b93f97e7..c2fd184d 100644 --- a/ruoyi-modules/hwmom-ai/pom.xml +++ b/ruoyi-modules/hwmom-ai/pom.xml @@ -190,6 +190,15 @@ 0.18.0 + + + com.alibaba + dashscope-sdk-java + + 2.18.2 + + + org.tensorflow @@ -300,6 +309,17 @@ 2.13.0 + + org.anyline + anyline-data-jdbc-mssql + ${anyline.version} + + + + org.anyline + anyline-environment-spring-data-jdbc + ${anyline.version} + diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiAssistantController.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiAssistantController.java index 2ae4dc46..ab8ce136 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiAssistantController.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiAssistantController.java @@ -131,6 +131,16 @@ public class AiAssistantController extends BaseController { } + /** + * 根绝用户输入生成sql,传入参数包括用户输入、AIModelID, AIPLATFORMID + * @param request + * @return + */ + @PostMapping(value = "/generateSql") + public String generateSql(AIRequest request) { + return aiAssistantService.generateSQL(request); + } + diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiModelController.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiModelController.java index 9dca7076..80807ccf 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiModelController.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiModelController.java @@ -142,7 +142,7 @@ public class AiModelController extends BaseController { /** - * 测试配置是否正确 + * 测试配置是否正确,主要验证AI模型的apikey,apisecret等信息 */ @PostMapping("/testAIModel") public R testAIModel(@RequestParam("provider") String provider, @RequestBody AIRequest request) { diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiSqlTableController.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiSqlTableController.java new file mode 100644 index 00000000..7f7ae647 --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiSqlTableController.java @@ -0,0 +1,181 @@ +package org.dromara.ai.controller; + +import java.util.List; + +import cn.hutool.core.convert.Convert; +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.dromara.ai.domain.AiSqlTable; +import org.dromara.ai.domain.dto.AiSqlTableDelete; +import org.dromara.common.mybatis.helper.DataBaseHelper; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.ai.domain.vo.AiSqlTableVo; +import org.dromara.ai.domain.bo.AiSqlTableBo; +import org.dromara.ai.service.IAiSqlTableService; +import org.dromara.common.mybatis.core.page.TableDataInfo; + +/** + * AI生成sql信息 + * 前端访问路由地址为:/ai/aiSqlTable + * + * @author xins + * @date 2025-09-04 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/aiSqlTable") +public class AiSqlTableController extends BaseController { + + private final IAiSqlTableService aiSqlTableService; + + /** + * 查询AI生成sql信息列表 + */ + @SaCheckPermission("ai:aiSqlTable:list") + @GetMapping("/list") + public TableDataInfo list(AiSqlTableBo bo, PageQuery pageQuery) { + return aiSqlTableService.queryPageList(bo, pageQuery); + } + + /** + * 导出AI生成sql信息列表 + */ + @SaCheckPermission("ai:aiSqlTable:export") + @Log(title = "AI生成sql信息", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(AiSqlTableBo bo, HttpServletResponse response) { + List list = aiSqlTableService.queryList(bo); + ExcelUtil.exportExcel(list, "AI生成sql信息", AiSqlTableVo.class, response); + } + + /** + * 获取AI生成sql信息详细信息 + * + * @param tableId 主键 + */ + @SaCheckPermission("ai:aiSqlTable:query") + @GetMapping("/{tableId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long tableId) { + return R.ok(aiSqlTableService.queryById(tableId)); + } + + /** + * 新增AI生成sql信息 + */ + @SaCheckPermission("ai:aiSqlTable:add") + @Log(title = "AI生成sql信息", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody AiSqlTableBo bo) { + return toAjax(aiSqlTableService.insertByBo(bo)); + } + + /** + * 修改AI生成sql信息 + */ + @SaCheckPermission("ai:aiSqlTable:edit") + @Log(title = "AI生成sql信息", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody AiSqlTableBo bo) { + return toAjax(aiSqlTableService.updateByBo(bo)); + } + + /** + * 删除AI生成sql信息 + * + * @param tableIds 主键串 + */ + @SaCheckPermission("ai:aiSqlTable:remove") + @Log(title = "AI生成sql信息", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] tableIds) { + return toAjax(aiSqlTableService.deleteWithValidByIds(List.of(tableIds), true)); + } + + + /** + * 下拉框查询AI生成sql信息列表 + */ + + @GetMapping("/getAiSqlTableList") + public R> getAiSqlTableList(AiSqlTableBo bo) { + List list = aiSqlTableService.queryList(bo); + return R.ok(list); + } + + + /** + * 查询数据源名称列表 + */ + @SaCheckPermission("ai:aiSqlTable:list") + @GetMapping(value = "/getDataNames") + public R getCurrentDataSourceNameList() { + return R.ok(DataBaseHelper.getDataSourceNameList()); + } + + /** + * 查询数据库列表 + */ + @SaCheckPermission("ai:aiSqlTable:list") + @GetMapping("/db/list") + public TableDataInfo dbTableList(AiSqlTableBo aiSqlTableBo, PageQuery pageQuery) { + return aiSqlTableService.selectPageDbTableList(aiSqlTableBo, pageQuery); + } + + /** + * 导入表结构(保存) + * + * @param alSqlTableList 表名串 + */ + @SaCheckPermission("ai:aiSqlTable:add") + @Log(title = "导入表结构", businessType = BusinessType.IMPORT) + @PostMapping("/importTable") + public R importTableSave(@RequestBody + List alSqlTableList) { + System.out.println(alSqlTableList); + return toAjax(aiSqlTableService.importAiSqlTable(alSqlTableList)); + } + + + /** + * 删除AI生成sql信息 + * + * @param aiSqlTableDeleteList + */ + @SaCheckPermission("ai:aiSqlTable:remove") + @Log(title = "AI生成sql信息", businessType = BusinessType.DELETE) + @DeleteMapping("/deleteAiSqlTable") + public R deleteAiSqlTable(@Validated(EditGroup.class) @RequestBody List aiSqlTableDeleteList) { + aiSqlTableService.deleteAiSqlTable(aiSqlTableDeleteList); + return R.ok(); + } + + /** + * 刷新缓存 + * + */ + @SaCheckPermission("ai:aiSqlTable:refresh") + @PostMapping("/refreshCache") + public R refreshCache() { + aiSqlTableService.refreshCache(); + return R.ok(); + } + + +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/AiSqlTable.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/AiSqlTable.java new file mode 100644 index 00000000..46f9c4bb --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/AiSqlTable.java @@ -0,0 +1,62 @@ +package org.dromara.ai.domain; + +import org.dromara.common.tenant.core.TenantEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * AI生成sql信息对象 ai_sql_table + * + * @author xins + * @date 2025-09-04 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("ai_sql_table") +public class AiSqlTable extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 编号 + */ + @TableId(value = "table_id", type = IdType.AUTO) + private Long tableId; + + /** + * 数据源 + */ + private String dataName; + + /** + * 表名称 + */ + private String tableName; + + /** + * 表描述 + */ + private String tableComment; + + /** + * 关联子表的表名 + */ + private String subTableName; + + /** + * 子表关联的外键名 + */ + private String subTableFkName; + + /** + * 删除标志(0代表存在 2代表删除) + */ + @TableLogic + private String delFlag; + + +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/bo/AiSqlTableBo.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/bo/AiSqlTableBo.java new file mode 100644 index 00000000..8b1f971a --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/bo/AiSqlTableBo.java @@ -0,0 +1,59 @@ +package org.dromara.ai.domain.bo; + +import org.dromara.ai.domain.AiSqlTable; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; + +/** + * AI生成sql信息业务对象 ai_sql_table + * + * @author xins + * @date 2025-09-04 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = AiSqlTable.class, reverseConvertGenerate = false) +public class AiSqlTableBo extends BaseEntity { + + /** + * 编号 + */ + @NotNull(message = "编号不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long tableId; + + /** + * 数据源 + */ + private String dataName; + + /** + * 表名称 + */ + @NotBlank(message = "表名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String tableName; + + /** + * 表描述 + */ + @NotBlank(message = "表描述不能为空", groups = { AddGroup.class, EditGroup.class }) + private String tableComment; + + /** + * 关联子表的表名 + */ + @NotBlank(message = "关联子表的表名不能为空", groups = { AddGroup.class, EditGroup.class }) + private String subTableName; + + /** + * 子表关联的外键名 + */ + @NotBlank(message = "子表关联的外键名不能为空", groups = { AddGroup.class, EditGroup.class }) + private String subTableFkName; + + +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/dto/AiSqlTableDelete.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/dto/AiSqlTableDelete.java new file mode 100644 index 00000000..a07d3106 --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/dto/AiSqlTableDelete.java @@ -0,0 +1,35 @@ +package org.dromara.ai.domain.dto; + +/** + * @description AiSqlTable Delete dto + * @author xins + * @date 2025/9/5 14:50 + */ + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.dromara.common.core.validate.EditGroup; + +@Data +public class AiSqlTableDelete { + /** + * 表ID + */ + @NotNull(message = "表ID不能为空", groups = { EditGroup.class }) + private Long tableId; + + /** + * 表名称 + */ + @NotBlank(message = "表名不能为空", groups = { EditGroup.class }) + private String tableName; + + /** + * 数据源名称 + */ + @NotBlank(message = "数据源名称不能为空", groups = { EditGroup.class }) + private String dataName; + +} + diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/vo/AiSqlTableVo.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/vo/AiSqlTableVo.java new file mode 100644 index 00000000..43fca478 --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/vo/AiSqlTableVo.java @@ -0,0 +1,67 @@ +package org.dromara.ai.domain.vo; + +import org.dromara.ai.domain.AiSqlTable; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * AI生成sql信息视图对象 ai_sql_table + * + * @author xins + * @date 2025-09-04 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = AiSqlTable.class) +public class AiSqlTableVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 编号 + */ + @ExcelProperty(value = "编号") + private Long tableId; + + /** + * 数据源 + */ + private String dataName; + + /** + * 表名称 + */ + @ExcelProperty(value = "表名称") + private String tableName; + + /** + * 表描述 + */ + @ExcelProperty(value = "表描述") + private String tableComment; + + /** + * 关联子表的表名 + */ + @ExcelProperty(value = "关联子表的表名") + private String subTableName; + + /** + * 子表关联的外键名 + */ + @ExcelProperty(value = "子表关联的外键名") + private String subTableFkName; + + +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/AiSqlTableMapper.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/AiSqlTableMapper.java new file mode 100644 index 00000000..7837d5f0 --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/AiSqlTableMapper.java @@ -0,0 +1,28 @@ +package org.dromara.ai.mapper; + +import com.baomidou.dynamic.datasource.annotation.DS; +import org.dromara.ai.domain.AiSqlTable; +import org.dromara.ai.domain.vo.AiSqlTableVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +import java.util.List; + +/** + * AI生成sql信息Mapper接口 + * + * @author xins + * @date 2025-09-04 + */ +public interface AiSqlTableMapper extends BaseMapperPlus { + + /** + * 查询指定数据源下的所有表名列表 + * + * @param dataName 数据源名称,用于选择不同的数据源 + * @return 当前数据库中的表名列表 + * + * @DS("") 使用默认数据源执行查询操作 + */ + @DS("") + List selectTableNameList(String dataName); +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/DatabaseMetaMapper.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/SQLServerDatabaseMetaMapper.java similarity index 85% rename from ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/DatabaseMetaMapper.java rename to ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/SQLServerDatabaseMetaMapper.java index 3181c3d9..848b16cd 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/DatabaseMetaMapper.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/SQLServerDatabaseMetaMapper.java @@ -11,11 +11,11 @@ import java.util.Map; /** * @Author xins * @Date 2025/7/8 9:22 - * @Description: + * @Description: SQLServer数据库操作sql mapper */ @InterceptorIgnore(dataPermission = "true", tenantLine = "true") @Mapper -public interface DatabaseMetaMapper { +public interface SQLServerDatabaseMetaMapper { /** * 获取所有用户表名 @@ -41,4 +41,7 @@ public interface DatabaseMetaMapper { "INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id " + "WHERE i.is_primary_key = 1 AND OBJECT_NAME(ic.object_id) = #{tableName}") List getPrimaryKeys(@Param("tableName") String tableName); + + + List> getTablesStructureByTableNames(@Param("tableNames") String[] tableNames); } diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/dto/AIRequest.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/dto/AIRequest.java index f048631b..456c6918 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/dto/AIRequest.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/dto/AIRequest.java @@ -19,7 +19,7 @@ public class AIRequest { private AIProviderEnum aiModelEnum; /** - * 模型名称/ID(例如deepseek-chat、tongyi-qianwen、tencent-lke、wenxin-yiyan) + * 对话模型名称(例如deepseek-chat、tongyi-qianwen) */ private String model; @@ -28,6 +28,12 @@ public class AIRequest { */ private Long modelId; + /** + * AI平台ID + */ + private Long platformId; + + /** * 会话ID */ @@ -80,7 +86,7 @@ public class AIRequest { private String apiSecretEncryptFlag; /** - * 需转化向量的文字 + * 需转化向量的文字或者提问的问题(prompt) */ private String text; @@ -98,6 +104,23 @@ public class AIRequest { private String messageTopic; + private String questionContent; + + /** + * 向量模型ID(ai_model) + */ + private Long embeddingModelId; + + /** + * 知识库检索数量限制 + */ + private Integer retrieveLimit; + + /** + * 数据源名称 + */ + private String dataName; + /** * 其他自定义参数 */ diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/enums/AIProviderEnum.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/enums/AIProviderEnum.java index 77ab4402..ba8bbff6 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/enums/AIProviderEnum.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/enums/AIProviderEnum.java @@ -7,7 +7,7 @@ public enum AIProviderEnum { private final String name; - private final Long code;//等于ai_base_model表的base_model_id + private final Long code;//等于ai_platform表的platform_id AIProviderEnum(String name, Long code) { this.name = name; diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/AIProviderProcessorFactory.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/AIProviderProcessorFactory.java index 223bffc4..21c7da01 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/AIProviderProcessorFactory.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/AIProviderProcessorFactory.java @@ -40,10 +40,10 @@ public class AIProviderProcessorFactory { } /** - * 根据base_model_id获取对应的API实现 + * 根据platformId获取对应的AI平台 */ - public IUnifiedAIProviderProcessor getProcessor(Long baseModelId) { - AIProviderEnum provider = AIProviderEnum.fromCode(baseModelId); + public IUnifiedAIProviderProcessor getProcessorByPlatformId(Long platformId) { + AIProviderEnum provider = AIProviderEnum.fromCode(platformId); return getProcessor(provider); } } diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/DeepSeekProcessor.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/DeepSeekProcessor.java index 5cd9ee63..d6a78a20 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/DeepSeekProcessor.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/DeepSeekProcessor.java @@ -91,6 +91,9 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor { } String requestBody = objectMapper.writeValueAsString(rootNode); + + configureApiKey(request); + return standardRequest(API_URL, requestBody, request.getApiKey()); } catch (IOException e) { return buildErrorResponse("构建请求失败: " + e.getMessage()); @@ -114,7 +117,7 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor { StringBuilder fullResponseBuilder = new StringBuilder(); String requestBody = objectMapper.writeValueAsString(rootNode); - setApiKey(request); + configureApiKey(request); return executeStreamRequest(API_URL, requestBody, request.getApiKey()).doOnNext(chunk -> { // 收集每个chunk @@ -220,7 +223,7 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor { return ""; } - private void setApiKey(AIRequest request) { + private void configureApiKey(AIRequest request) { AiModelVo aiModelVo = aiModelMapper.selectVoById(request.getModelId()); if (aiModelVo != null) { if (aiModelVo.getApiKey() == null) { diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/IAIAssistantService.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/IAIAssistantService.java index f25281ef..139b96fa 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/IAIAssistantService.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/IAIAssistantService.java @@ -1,7 +1,9 @@ package org.dromara.ai.service; import org.dromara.ai.process.dto.AIRequest; +import org.dromara.ai.process.dto.AIResponse; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; public interface IAIAssistantService { @@ -13,6 +15,13 @@ public interface IAIAssistantService { */ Flux chatStream(String provider, AIRequest aiRequest); + /** + * 生成sql语句 + * @param aiRequest + * @return String + */ + public String generateSQL(AIRequest aiRequest); - public boolean testAIModel(String provider, AIRequest aiRequest); + +// public boolean testAIModel(String provider, AIRequest aiRequest); } diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/IAiSqlTableService.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/IAiSqlTableService.java new file mode 100644 index 00000000..831b914f --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/IAiSqlTableService.java @@ -0,0 +1,101 @@ +package org.dromara.ai.service; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import org.dromara.ai.domain.AiSqlTable; +import org.dromara.ai.domain.dto.AiSqlTableDelete; +import org.dromara.ai.domain.vo.AiSqlTableVo; +import org.dromara.ai.domain.bo.AiSqlTableBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * AI生成sql信息Service接口 + * + * @author xins + * @date 2025-09-04 + */ +public interface IAiSqlTableService { + + /** + * 查询AI生成sql信息 + * + * @param tableId 主键 + * @return AI生成sql信息 + */ + AiSqlTableVo queryById(Long tableId); + + /** + * 分页查询AI生成sql信息列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return AI生成sql信息分页列表 + */ + TableDataInfo queryPageList(AiSqlTableBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的AI生成sql信息列表 + * + * @param bo 查询条件 + * @return AI生成sql信息列表 + */ + List queryList(AiSqlTableBo bo); + + /** + * 新增AI生成sql信息 + * + * @param bo AI生成sql信息 + * @return 是否新增成功 + */ + Boolean insertByBo(AiSqlTableBo bo); + + /** + * 修改AI生成sql信息 + * + * @param bo AI生成sql信息 + * @return 是否修改成功 + */ + Boolean updateByBo(AiSqlTableBo bo); + + /** + * 校验并批量删除AI生成sql信息信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 查询数据库列表 + * + * @param aiSqlTableBo AiSqlTableBo + * @param pageQuery 包含分页信息的PageQuery对象 + * @return 包含分页结果的TableDataInfo对象 + */ + public TableDataInfo selectPageDbTableList(AiSqlTableBo aiSqlTableBo, PageQuery pageQuery); + + + /** + * 导入表结构 + * + * @param aiSqlTableList 导入表列表 + */ + public boolean importAiSqlTable(List aiSqlTableList); + + /** + * 刷新缓存 + */ + public void refreshCache(); + + /** + * 删除AI生成sql信息 + * @param aiSqlTableDeleteList + */ + public void deleteAiSqlTable(List aiSqlTableDeleteList); + +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AIAssistantServiceImpl.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AIAssistantServiceImpl.java index 8983bf2c..1b9b65b7 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AIAssistantServiceImpl.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AIAssistantServiceImpl.java @@ -1,27 +1,33 @@ package org.dromara.ai.service.impl; +import com.alibaba.excel.util.StringUtils; +import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.core.JsonProcessingException; import org.dromara.ai.domain.AiModel; +import org.dromara.ai.domain.vo.AiModelVo; import org.dromara.ai.mapper.AiModelMapper; -import org.dromara.ai.test.vectorization.process.IEmbeddingProcessor; +import org.dromara.ai.process.dto.AIResponse; +import org.dromara.ai.test.ChatRequest; import org.dromara.ai.mapper.SQLServerDatabaseMetaMapper; import org.dromara.ai.process.dto.AIMessage; import org.dromara.ai.process.dto.AIRequest; -import org.dromara.ai.process.dto.AIResponse; import org.dromara.ai.process.provider.processor.AIProviderProcessorFactory; import org.dromara.ai.process.provider.processor.IUnifiedAIProviderProcessor; import org.dromara.ai.service.IAIAssistantService; import org.dromara.ai.vectordb.service.IVectorDBService; +import org.dromara.common.constant.HwMomAiConstants; import org.dromara.common.encrypt.utils.EncryptUtils; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.system.api.model.LoginUser; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import com.fasterxml.jackson.databind.ObjectMapper; +import reactor.core.publisher.Mono; -import java.time.Duration; import java.util.*; +import java.util.stream.Collectors; /** * @Author xins @@ -40,9 +46,6 @@ public class AIAssistantServiceImpl implements IAIAssistantService { @Autowired private Map aiProviderProcessorMap; - @Autowired - private Map embeddingProcessorMap; - @Autowired private AIProviderProcessorFactory aiProviderProcessorFactory; @@ -52,6 +55,9 @@ public class AIAssistantServiceImpl implements IAIAssistantService { @Autowired private ObjectMapper objectMapper; + @Autowired + private StringRedisTemplate redisTemplate; + /** * 流式聊天接口 * @@ -129,7 +135,7 @@ public class AIAssistantServiceImpl implements IAIAssistantService { //todo:后续从redis中获取 aiRequest.setApiKey(EncryptUtils.decryptByBase64(aiModel.getApiKey())); aiRequest.setApiSecret(EncryptUtils.decryptByBase64(aiModel.getApiSecret())); - IUnifiedAIProviderProcessor tencentLkeProcessor = aiProviderProcessorFactory.getProcessor(aiModel.getBaseModelId()); + IUnifiedAIProviderProcessor tencentLkeProcessor = aiProviderProcessorFactory.getProcessorByPlatformId(aiModel.getPlatformId()); List queryEmbedding = tencentLkeProcessor.getEmbedding(aiRequest); int topK = aiRequest.getRetrieveLimit() == null || aiRequest.getRetrieveLimit() <= 0 ? @@ -188,30 +194,206 @@ public class AIAssistantServiceImpl implements IAIAssistantService { } } + /** + * 生成sql语句 + * + * @param aiRequest + * @return String + */ @Override - public boolean testAIModel(String provider, AIRequest aiRequest) { - IUnifiedAIProviderProcessor processor = aiProviderProcessorMap.get(provider); - if (processor == null) { - throw new IllegalArgumentException("Unsupported processor type"); + public String generateSQL(AIRequest aiRequest) { + String naturalLanguageQuery = aiRequest.getText(); + // 1. 获取数据库结构 + String dataName = StringUtils.isNotBlank(aiRequest.getDataName()) ? aiRequest.getDataName() : "master"; + String schemaDescription = redisTemplate.opsForValue().get(HwMomAiConstants.AI_DATABASE_SCHEMA_KEY_PREFIX + dataName); + if (schemaDescription == null) { + throw new RuntimeException("请先设置AI生成SQL表信息或刷新缓存后在生成"); } - aiRequest.setModel("deepseek-chat"); - String decryptApiKey = EncryptUtils.decryptByBase64(aiRequest.getApiKey()); - aiRequest.setApiKey(decryptApiKey); + + JSONObject schemaJson = JSONObject.parseObject(schemaDescription); + StringBuilder sb = new StringBuilder("SQL Server 数据库结构:\n\n"); + schemaJson.entrySet().forEach(entry -> { +// sb.append(entry.getKey()).append("\n"); + sb.append(entry.getValue()).append("\n\n"); + }); +// sb.append(schemaDescription); + + // 2. 构建 AI 提示 + String prompt = String.format( + "你是一个专业的 SQL Server 数据库专家。仅根据以下数据库结构(如果以下数据库结构中没有则返回select * from):\n\n%s\n\n" + + "请将以下自然语言查询转换为优化的 SQL Server T-SQL 语句:\n" + + "---\n%s\n---\n\n" + + "要求:\n" + + "1. 只返回 SQL 语句,不要包含解释\n" + + "2. 使用 SQL Server 特有的语法(如 TOP 而不是 LIMIT)\n" + + "3. 考虑性能优化\n" + + "4. 使用合适的索引提示(如果需要)\n" + + "5. 包含必要的 WITH(NOLOCK) 提示(适用于高并发环境)\n" + + "6. 使用 ANSI 标准的 JOIN 语法 \n", + sb.toString(), naturalLanguageQuery + ); + + IUnifiedAIProviderProcessor processor = aiProviderProcessorFactory.getProcessorByPlatformId(aiRequest.getPlatformId()); AIMessage aiMessage = new AIMessage(); aiMessage.setRole("user"); - aiMessage.setContent("这是一个测试请求,请回复'测试成功'"); - aiRequest.setMessages(Collections.singletonList( - aiMessage - )); - AIResponse aiResponse = processor.chat(aiRequest).block(Duration.ofSeconds(10)); - if (aiResponse != null && aiResponse.isSuccess()) { - System.out.println(aiResponse.getContent()); - System.out.println(aiResponse.getUsage()); + aiMessage.setContent(prompt); + aiRequest.setMessages(Collections.singletonList(aiMessage)); + Mono response = processor.chat(aiRequest); + if (Objects.requireNonNull(response.block()).isSuccess()) { + String content = response.block().getContent().toString(); + //content内容需要转换,通过deepseek返回的如: +// ```sql +// SELECT +// dept_id, +// tenant_id, +// parent_id, +// ancestors, +// dept_name, +// dept_category, +// order_num, +// leader, +// phone, +// email, +// status, +// del_flag, +// create_dept, +// create_by, +// create_time, +// update_by, +// update_time +// FROM sys_dept WITH(NOLOCK) +// WHERE del_flag = '0' +// ORDER BY order_num, dept_id; +//``` + return extractSqlFromContent(content); + + } else { + throw new RuntimeException("生成sql语句失败" + response.block().getErrorMessage()); } - return true; } + /** + * 获取格式化的数据库结构描述 + */ + private String getFormattedSchema() { + List> allColumns = databaseMetaMapper.getAllTablesStructure(); + + Map>> tables = allColumns.stream() + .collect(Collectors.groupingBy( + col -> (String) col.get("tableName"), + LinkedHashMap::new, + Collectors.toList() + )); + + StringBuilder sb = new StringBuilder("SQL Server 数据库结构:\n\n"); + + + for (Map.Entry>> entry : tables.entrySet()) { + String tableName = entry.getKey(); + List primaryKeys = databaseMetaMapper.getPrimaryKeys(tableName); + + sb.append("- 表名: ").append(tableName).append("\n"); + sb.append(" 主键: ").append(String.join(", ", primaryKeys)).append("\n"); + sb.append(" 字段:\n"); + + for (Map column : entry.getValue()) { + sb.append(" * ") + .append(column.get("columnName")) + .append(" (").append(column.get("dataType")); + + if (column.get("maxLength") != null && (short) column.get("maxLength") > 0) { + sb.append(", 长度: ").append(column.get("maxLength")); + } + if (column.get("precision") != null && (short) column.get("precision") > 0) { + sb.append(", 精度: ").append(column.get("precision")); + } + if (column.get("scale") != null && (short) column.get("scale") > 0) { + sb.append(", 小数位: ").append(column.get("scale")); + } + + sb.append(column.get("nullable").equals(1) ? ", 可空" : ", 非空"); + + if (!((String) column.get("defaultValue")).isEmpty()) { + sb.append(", 默认值: ").append(column.get("defaultValue")); + } + + if (!((String) column.get("description")).isEmpty()) { + sb.append(", 描述: ").append(column.get("description")); + } + + sb.append(")\n"); + } + sb.append("\n"); + } + + return sb.toString(); + } + + + // 专门处理 Markdown SQL 代码块 + private static String extractSqlFromContent(String content) { + // 情况1:如果包含 ```sql ... ``` 格式 + if (content.contains("```sql")) { + int start = content.indexOf("```sql") + 6; // 跳过 ```sql + int end = content.lastIndexOf("```"); + if (end > start) { + content = content.substring(start, end).trim(); + } + } + + // 情况2:如果只有 ``` ... ```(没有 sql 标注) + if (content.contains("```")) { + int start = content.indexOf("```") + 3; + int end = content.lastIndexOf("```"); + if (end > start) { + content = content.substring(start, end).trim(); + } + } + + // content = extractSqlFromText(content); + content = content.replace("\\n", "\n") // 处理转义的换行符 + .replace("\n", " "); + + content = content.trim(); + if ((content.startsWith("\"") && content.endsWith("\"")) || + (content.startsWith("'") && content.endsWith("'"))) { + content = content.substring(1, content.length() - 1); + } + + return content.trim(); + } + + +// /** +// * 在新建AI模型或修改AI模型时,测试AI模型是否可用,验证apikey和apisecret等信息 +// * @param provider +// * @param aiRequest +// * @return +// */ +// @Override +// public boolean testAIModel(String provider, AIRequest aiRequest) { +// IUnifiedAIProviderProcessor processor = aiProviderProcessorMap.get(provider); +// if (processor == null) { +// throw new IllegalArgumentException("Unsupported processor type"); +// } +// aiRequest.setModel("deepseek-chat"); +// String decryptApiKey = EncryptUtils.decryptByBase64(aiRequest.getApiKey()); +// aiRequest.setApiKey(decryptApiKey); +// AIMessage aiMessage = new AIMessage(); +// aiMessage.setRole("user"); +// aiMessage.setContent("这是一个测试请求,请仅回复'测试成功'"); +// aiRequest.setMessages(Collections.singletonList( +// aiMessage +// )); +// AIResponse aiResponse = processor.chat(aiRequest).block(Duration.ofSeconds(10)); +// if (aiResponse != null && aiResponse.isSuccess()) { +// System.out.println(aiResponse.getContent()); +// System.out.println(aiResponse.getUsage()); +// } +// +// return true; +// } public String aiFillForm() { @@ -225,5 +407,4 @@ public class AIAssistantServiceImpl implements IAIAssistantService { } - } diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiSqlTableServiceImpl.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiSqlTableServiceImpl.java new file mode 100644 index 00000000..90626c0c --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiSqlTableServiceImpl.java @@ -0,0 +1,333 @@ +package org.dromara.ai.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.dromara.ai.domain.dto.AiSqlTableDelete; +import org.dromara.ai.mapper.SQLServerDatabaseMetaMapper; +import org.dromara.ai.utils.DbSchemaUtils; +import org.dromara.common.constant.HwMomAiConstants; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.github.yulichang.toolkit.JoinWrappers; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.dromara.ai.domain.bo.AiSqlTableBo; +import org.dromara.ai.domain.vo.AiSqlTableVo; +import org.dromara.ai.domain.AiSqlTable; +import org.dromara.ai.mapper.AiSqlTableMapper; +import org.dromara.ai.service.IAiSqlTableService; + +import java.util.*; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.anyline.metadata.Column; +import org.anyline.metadata.Table; +import org.anyline.proxy.ServiceProxy; +import org.dromara.common.core.constant.Constants; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.file.FileUtils; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * AI生成sql信息Service业务层处理 + * + * @author xins + * @date 2025-09-04 + */ +@RequiredArgsConstructor +@Service +public class AiSqlTableServiceImpl implements IAiSqlTableService { + private static final String[] TABLE_IGNORE = new String[]{"sj_", "act_", "flw_", "gen_"}; + + private final AiSqlTableMapper baseMapper; + + private final SQLServerDatabaseMetaMapper sqlServerDatabaseMetaMapper; + + private final StringRedisTemplate redisTemplate; + + /** + * 查询AI生成sql信息 + * + * @param tableId 主键 + * @return AI生成sql信息 + */ + @Override + public AiSqlTableVo queryById(Long tableId) { + return baseMapper.selectVoById(tableId); + } + + /** + * 分页查询AI生成sql信息列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return AI生成sql信息分页列表 + */ + @Override + public TableDataInfo queryPageList(AiSqlTableBo bo, PageQuery pageQuery) { + MPJLambdaWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的AI生成sql信息列表 + * + * @param bo 查询条件 + * @return AI生成sql信息列表 + */ + @Override + public List queryList(AiSqlTableBo bo) { + MPJLambdaWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private MPJLambdaWrapper buildQueryWrapper(AiSqlTableBo bo) { + Map params = bo.getParams(); + MPJLambdaWrapper lqw = JoinWrappers.lambda(AiSqlTable.class) + .selectAll(AiSqlTable.class) + .eq(bo.getTableId() != null, AiSqlTable::getTableId, bo.getTableId()) + .like(StringUtils.isNotBlank(bo.getTableName()), AiSqlTable::getTableName, bo.getTableName()) + .like(StringUtils.isNotBlank(bo.getTableComment()), AiSqlTable::getTableComment, bo.getTableComment()) + .like(StringUtils.isNotBlank(bo.getSubTableName()), AiSqlTable::getSubTableName, bo.getSubTableName()) + .like(StringUtils.isNotBlank(bo.getSubTableFkName()), AiSqlTable::getSubTableFkName, bo.getSubTableFkName()) + .orderByDesc(AiSqlTable::getCreateTime); + return lqw; + } + + /** + * 新增AI生成sql信息 + * + * @param bo AI生成sql信息 + * @return 是否新增成功 + */ + @Override + public Boolean insertByBo(AiSqlTableBo bo) { + AiSqlTable add = MapstructUtils.convert(bo, AiSqlTable.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setTableId(add.getTableId()); + } + return flag; + } + + /** + * 修改AI生成sql信息 + * + * @param bo AI生成sql信息 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(AiSqlTableBo bo) { + AiSqlTable update = MapstructUtils.convert(bo, AiSqlTable.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(AiSqlTable entity) { + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除AI生成sql信息信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } + + + /** + * 查询数据库列表 + * + * @param aiSqlTableBo AiSqlTableBo + * @param pageQuery 包含分页信息的PageQuery对象 + * @return 包含分页结果的TableDataInfo对象 + */ + @DS("#aiSqlTableBo.dataName") + @Override + public TableDataInfo selectPageDbTableList(AiSqlTableBo aiSqlTableBo, PageQuery pageQuery) { + // 获取查询条件 + String tableName = aiSqlTableBo.getTableName(); + String tableComment = aiSqlTableBo.getTableComment(); + + LinkedHashMap> tablesMap = ServiceProxy.metadata().tables(); + if (CollUtil.isEmpty(tablesMap)) { + return TableDataInfo.build(); + } + List tableNames = baseMapper.selectTableNameList(aiSqlTableBo.getDataName()); + String[] tableArrays; + if (CollUtil.isNotEmpty(tableNames)) { + tableArrays = tableNames.toArray(new String[0]); + } else { + tableArrays = new String[0]; + } + // 过滤并转换表格数据 + List tables = tablesMap.values().stream() + .filter(x -> !startWithAnyIgnoreCase(x.getName(), TABLE_IGNORE)) + .filter(x -> { + if (CollUtil.isEmpty(tableNames)) { + return true; + } + return !StringUtils.equalsAnyIgnoreCase(x.getName(), tableArrays); + }) + .filter(x -> { + boolean nameMatches = true; + boolean commentMatches = true; + // 进行表名称的模糊查询 + if (StringUtils.isNotBlank(tableName)) { + nameMatches = StringUtils.containsIgnoreCase(x.getName(), tableName); + } + // 进行表描述的模糊查询 + if (StringUtils.isNotBlank(tableComment)) { + commentMatches = StringUtils.containsIgnoreCase(x.getComment(), tableComment); + } + // 同时匹配名称和描述 + return nameMatches && commentMatches; + }) + .map(x -> { + AiSqlTable gen = new AiSqlTable(); + gen.setTableName(x.getName()); + gen.setTableComment(x.getComment()); + gen.setCreateTime(x.getCreateTime()); + gen.setUpdateTime(x.getUpdateTime()); + return gen; + }).toList(); + + IPage page = pageQuery.build(); + page.setTotal(tables.size()); + // 手动分页 set数据 + page.setRecords(CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), tables)); + return TableDataInfo.build(page); + } + + + public static boolean startWithAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) { + // 判断是否是以指定字符串开头 + for (CharSequence searchCharSequence : searchCharSequences) { + if (StringUtils.startsWithIgnoreCase(cs, searchCharSequence)) { + return true; + } + } + return false; + } + + /** + * 导入表结构 + * + * @param aiSqlTableList 导入表列表 + */ + @DSTransactional + @Override + public boolean importAiSqlTable(List aiSqlTableList) { + AiSqlTable aiSqlTable = aiSqlTableList.get(0); + String dbSchema = redisTemplate.opsForValue().get(HwMomAiConstants.AI_DATABASE_SCHEMA_KEY_PREFIX + aiSqlTable.getDataName()); + JSONObject dbSchemaJson; + if (dbSchema == null) { + dbSchemaJson = new JSONObject(); + } else { + dbSchemaJson = JSON.parseObject(dbSchema); + } + + getTableSchema(dbSchemaJson, aiSqlTableList); + + return baseMapper.insertBatch(aiSqlTableList); + } + + + /** + * 刷新缓存 + */ + @Override + public void refreshCache() { + JSONObject dbSchemaJson = new JSONObject(); + List aiSqlTableList = baseMapper.selectList(); + getTableSchema(dbSchemaJson, aiSqlTableList); + } + + /** + * 删除AI生成sql信息 + * @param aiSqlTableDeleteList + */ + @Override + public void deleteAiSqlTable(List aiSqlTableDeleteList) { + List tableIds = aiSqlTableDeleteList.stream().map(AiSqlTableDelete::getTableId).collect(Collectors.toList()); + baseMapper.deleteByIds(tableIds); + String dataName = aiSqlTableDeleteList.get(0).getDataName(); + String dbSchema = redisTemplate.opsForValue().get(HwMomAiConstants.AI_DATABASE_SCHEMA_KEY_PREFIX + dataName); + if (StringUtils.isNotBlank(dbSchema)) { + JSONObject dbSchemaJson = JSON.parseObject(dbSchema); + for (AiSqlTableDelete aiSqlTableDelete : aiSqlTableDeleteList) { + String tableName = aiSqlTableDelete.getTableName(); + dbSchemaJson.remove(tableName); + } + + redisTemplate.opsForValue().set(HwMomAiConstants.AI_DATABASE_SCHEMA_KEY_PREFIX + dataName,dbSchemaJson.toJSONString()); + } + + } + + + private void getTableSchema(JSONObject dbSchemaJson, List aiSqlTableList) { + String dataName = aiSqlTableList.get(0).getDataName(); + for (AiSqlTable aiSqlTable : aiSqlTableList) { + String tableName = aiSqlTable.getTableName(); + List> tableStructures = sqlServerDatabaseMetaMapper.getTableStructure(tableName); + List primaryKeys = sqlServerDatabaseMetaMapper.getPrimaryKeys(tableName); + String schema = DbSchemaUtils.getFormatSchema(tableName, aiSqlTable.getTableComment(), tableStructures, primaryKeys); + dbSchemaJson.put(tableName, schema); + } + + redisTemplate.opsForValue().set(HwMomAiConstants.AI_DATABASE_SCHEMA_KEY_PREFIX + dataName, dbSchemaJson.toJSONString()); + } + +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/DatabaseMetaServiceImpl.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/DatabaseMetaServiceImpl.java index 0012eedc..3cfc469a 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/DatabaseMetaServiceImpl.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/DatabaseMetaServiceImpl.java @@ -1,7 +1,6 @@ package org.dromara.ai.service.impl; -import org.dromara.ai.mapper.DatabaseMetaMapper; -import org.dromara.ai.process.provider.processor.impl.DeepSeekProcessor; +import org.dromara.ai.mapper.SQLServerDatabaseMetaMapper; import org.dromara.ai.test.ChatRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -20,10 +19,7 @@ import java.util.stream.Collectors; public class DatabaseMetaServiceImpl { @Autowired - private DatabaseMetaMapper databaseMetaMapper; - - @Autowired - private DeepSeekProcessor deepSeekService; + private SQLServerDatabaseMetaMapper databaseMetaMapper; /** * 获取格式化的数据库结构描述 @@ -81,20 +77,6 @@ public class DatabaseMetaServiceImpl { return sb.toString(); } - /** - * 获取表的结构信息 - */ - public Map getTableDetail(String tableName) { - Map result = new LinkedHashMap<>(); - result.put("tableName", tableName); - result.put("primaryKeys", databaseMetaMapper.getPrimaryKeys(tableName)); - result.put("columns", databaseMetaMapper.getTableStructure(tableName)); - return result; - } - - - - public String generateSQL(String naturalLanguageQuery) { // 1. 获取数据库结构 @@ -118,52 +100,25 @@ public class DatabaseMetaServiceImpl { ChatRequest chatRequest = new ChatRequest(); chatRequest.setPrompt(prompt); -// Mono dd = deepSeekService.getStandardResponse(chatRequest); -// dd.subscribe(System.out::println); -// dd.subscribe( -// response -> { -// System.out.println("\n=== 完整响应 ==="); -// if (response.getChoices() != null && !response.getChoices().isEmpty()) { -// System.out.println(response.getChoices().get(0).getText()); -// } -// System.out.println("=== 响应结束 ==="); -// }, -// error -> System.err.println("调用失败: " + error.getMessage()) -// ); - -// System.out.println(d); -// d.subscribe( -// data -> System.out.println("Received: " + data), -// error -> System.err.println("Error: " + error), -// () -> System.out.println("Stream completed") -// ); return prompt; -// // 3. 调用 DeepSeek API -// HttpHeaders headers = new HttpHeaders(); -// headers.setContentType(MediaType.APPLICATION_JSON); -// headers.set("Authorization", "Bearer " + apiKey); -// -// Map request = new HashMap<>(); -// request.put("model", "deepseek-chat"); -// request.put("messages", List.of(Map.of("role", "user", "content", prompt))); -// request.put("temperature", 0.3); // 降低随机性以获得更确定的 SQL -// -// HttpEntity> entity = new HttpEntity<>(request, headers); -// -// try { -// ResponseEntity response = restTemplate.exchange( -// apiUrl, HttpMethod.POST, entity, Map.class); -// -// if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { -// return extractSqlFromResponse(response.getBody()); -// } -// throw new RuntimeException("API 调用失败,状态码: " + response.getStatusCode()); -// } catch (Exception e) { -// throw new RuntimeException("生成 SQL 失败: " + e.getMessage(), e); -// } + } + /** + * 获取表的结构信息 + */ + public Map getTableDetail(String tableName) { + Map result = new LinkedHashMap<>(); + result.put("tableName", tableName); + result.put("primaryKeys", databaseMetaMapper.getPrimaryKeys(tableName)); + result.put("columns", databaseMetaMapper.getTableStructure(tableName)); + return result; + } + + + + public String generateSQL1(String naturalLanguageQuery) { // 1. 获取数据库结构 diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/utils/DbSchemaUtils.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/utils/DbSchemaUtils.java new file mode 100644 index 00000000..d76656df --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/utils/DbSchemaUtils.java @@ -0,0 +1,54 @@ +package org.dromara.ai.utils; + +import java.util.List; +import java.util.Map; + +/** + * @Author xins + * @Date 2025/9/5 13:30 + * @Description: 数据库表结构格式处理 + */ +public class DbSchemaUtils { + + public static String getFormatSchema(String tableName, String tableComment,List> columns, List primaryKeys) { + StringBuilder sb = new StringBuilder(); + + sb.append("- 表名: ").append(tableName).append("\n"); + sb.append(" 主键: ").append(String.join(", ", primaryKeys)).append("\n"); + sb.append(" 表描述: ").append(tableComment).append("\n"); + sb.append(" 字段:\n"); + + for (Map column : columns) { + sb.append(" * ") + .append(column.get("columnName")) + .append(" (").append(column.get("dataType")); + + if (column.get("maxLength") != null && (short) column.get("maxLength") > 0) { + sb.append(", 长度: ").append(column.get("maxLength")); + } + if (column.get("precision") != null && (short) column.get("precision") > 0) { + sb.append(", 精度: ").append(column.get("precision")); + } + if (column.get("scale") != null && (short) column.get("scale") > 0) { + sb.append(", 小数位: ").append(column.get("scale")); + } + + sb.append(column.get("nullable").equals(1) ? ", 可空" : ", 非空"); + + if (!((String) column.get("defaultValue")).isEmpty()) { + sb.append(", 默认值: ").append(column.get("defaultValue")); + } + + if (!((String) column.get("description")).isEmpty()) { + sb.append(", 描述: ").append(column.get("description")); + } + + sb.append(")\n"); + } + sb.append("\n"); + + return sb.toString(); + } + + +} diff --git a/ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/AiSqlTableMapper.xml b/ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/AiSqlTableMapper.xml new file mode 100644 index 00000000..30b0913a --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/AiSqlTableMapper.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/DatabaseMetaMapper.xml b/ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/SQLServerDatabaseMetaMapper.xml similarity index 59% rename from ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/DatabaseMetaMapper.xml rename to ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/SQLServerDatabaseMetaMapper.xml index 349a183a..7f95f9b6 100644 --- a/ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/DatabaseMetaMapper.xml +++ b/ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/SQLServerDatabaseMetaMapper.xml @@ -2,11 +2,12 @@ - + + + + + + diff --git a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/WmsInventory.java b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/WmsInventory.java index 7b3f4375..93f071c4 100644 --- a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/WmsInventory.java +++ b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/WmsInventory.java @@ -123,4 +123,12 @@ public class WmsInventory { */ @TableField(exist = false) private Long aoDId;//字段映射 + + /** + * 最大停放时间(wms用天) + */ + @TableField(exist = false) + private Long maxParkingTime; + + } diff --git a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/vo/WmsInventoryVo.java b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/vo/WmsInventoryVo.java index 059bfedf..06fe26e0 100644 --- a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/vo/WmsInventoryVo.java +++ b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/domain/vo/WmsInventoryVo.java @@ -135,4 +135,9 @@ public class WmsInventoryVo implements Serializable { * 仓库名称 */ private String warehouseName; + + /** + * 最大停放时间(wms用天) + */ + private Long maxParkingTime; } diff --git a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/service/impl/WmsInventoryServiceImpl.java b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/service/impl/WmsInventoryServiceImpl.java index 4a44a576..6e7f3ace 100644 --- a/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/service/impl/WmsInventoryServiceImpl.java +++ b/ruoyi-modules/hwmom-wms/src/main/java/org/dromara/wms/service/impl/WmsInventoryServiceImpl.java @@ -85,7 +85,7 @@ public class WmsInventoryServiceImpl implements IWmsInventoryService { .selectAll(WmsInventory.class) // 关联表查询物料 - .select(BaseMaterialInfo::getMaterialCode, BaseMaterialInfo::getMaterialName) + .select(BaseMaterialInfo::getMaterialCode, BaseMaterialInfo::getMaterialName, BaseMaterialInfo::getMaxParkingTime) .leftJoin(BaseMaterialInfo.class, BaseMaterialInfo::getMaterialId, WmsInventory::getMaterialId) // 关联表查询物料大类名称 .select(BaseMaterialCategory::getMaterialCategoryName) @@ -113,7 +113,7 @@ public class WmsInventoryServiceImpl implements IWmsInventoryService { .selectAll(WmsInventory.class) // 关联表查询物料 - .select(BaseMaterialInfo::getMaterialCode, BaseMaterialInfo::getMaterialName) + .select(BaseMaterialInfo::getMaterialCode, BaseMaterialInfo::getMaterialName, BaseMaterialInfo::getMaxParkingTime) .leftJoin(BaseMaterialInfo.class, BaseMaterialInfo::getMaterialId, WmsInventory::getMaterialId) // 关联表查询物料大类名称 .select(BaseMaterialCategory::getMaterialCategoryName) diff --git a/ruoyi-modules/hwmom-wms/src/main/resources/mapper/wms/WmsInventoryMapper.xml b/ruoyi-modules/hwmom-wms/src/main/resources/mapper/wms/WmsInventoryMapper.xml index 1ff1c5e0..201afa4d 100644 --- a/ruoyi-modules/hwmom-wms/src/main/resources/mapper/wms/WmsInventoryMapper.xml +++ b/ruoyi-modules/hwmom-wms/src/main/resources/mapper/wms/WmsInventoryMapper.xml @@ -29,6 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" MAX(c.warehouse_name) warehouse_name, MAX(b.material_code) material_code, MAX(b.material_name) material_name, + MAX(b.max_parking_time) max_parking_time, MAX(x.lock_state) lock_state, MAX(x.material_categories) material_categories, MAX(x.location_code) locationCode, @@ -70,6 +71,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" MAX(b.max_stock_amount) max_stock_amount, MAX(b.material_code) material_code, MAX(b.material_name) material_name, + MAX(b.max_parking_time) max_parking_time, -- MAX(x.material_categories) material_categories, -- MAX(bmc.material_category_name) material_category_name, MAX(x.lock_state) lock_state @@ -158,6 +160,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" bmi.material_name, bmi.material_spec, bmi.material_unit, + bmi.max_parking_time, wbw.warehouse_code, wbw.warehouse_name, wbw.warehouse_id, @@ -212,6 +215,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" bmi.material_name, bmi.material_spec, bmi.material_unit, + bmi.max_parking_time, wbw.warehouse_code, wbw.warehouse_name, woo.warehouse_id, @@ -270,6 +274,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" icr.material_name, bmi.material_spec, bmi.material_unit, + bmi.max_parking_time, '' as warehouse_code, '' as warehouse_name, NULL as warehouse_id @@ -321,6 +326,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" bmi.material_name, bmi.material_spec, bmi.material_unit, + bmi.max_parking_time, wbw.warehouse_code, wbw.warehouse_name, ro.warehouse_id, @@ -376,6 +382,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" bmi.material_name, bmi.material_spec, bmi.material_unit, + bmi.max_parking_time, '' as warehouse_code, '' as warehouse_name, NULL as warehouse_id