1.5.7后端

AI表单设置完成,AI语音识别封装(支持阿里云百炼大模型API);
AI表单快智能捷填报完成
hwmom-htk
xs 4 months ago
parent 428011a780
commit 1db20a93a2

@ -12,4 +12,7 @@ public interface HwMomAiConstants {
*/
public static final String AI_DATABASE_SCHEMA_KEY_PREFIX = "ai:database:schema:";
public static final String AI_FORM_SETTING_FIELD_TYPE_NORMAL = "1";//普通字段
public static final String AI_FORM_SETTING_FIELD_TYPE_RELATE = "2";//关联表
public static final String AI_FORM_SETTING_FIELD_TYPE_DICT = "3";//字典数据
}

@ -83,6 +83,7 @@
<artifactId>ruoyi-common-translation</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sensitive</artifactId>
@ -111,12 +112,7 @@
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>hwmom-common-mom</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
@ -320,6 +316,22 @@
<artifactId>anyline-environment-spring-data-jdbc</artifactId>
<version>${anyline.version}</version>
</dependency>
<!-- 阿里百炼大模型语音识别SDK-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<!-- 请将 'the-latest-version' 替换为最新版本号https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>hwmom-common-mom</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>

@ -1,16 +1,14 @@
package org.dromara.ai;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.dromara.common.properties.MesProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
*
* AI
*
* @author ruoyi
* @author xins
*/
@EnableDubbo
@SpringBootApplication

@ -0,0 +1,90 @@
package org.dromara.ai.asr.controller;
import org.dromara.ai.asr.service.AliAsrService;
import org.dromara.common.core.domain.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
@RestController
@RequestMapping("/asr")
public class AsrController {
@Autowired
private AliAsrService aliAsrService;
// /**
// * 语音识别接口
// * @param audioFile 音频文件
// * @return 识别结果
// */
// @PostMapping("/recognize")
// public ResponseEntity<Map<String, Object>> recognizeSpeech(@RequestParam("audioFile") MultipartFile audioFile) {
// Map<String, Object> response = new HashMap<>();
//
// try {
// if (audioFile.isEmpty()) {
// response.put("success", false);
// response.put("message", "请上传音频文件");
// return ResponseEntity.badRequest().body(response);
// }
//
// // 调用语音识别服务
// String result = aliAsrService.recognizeSpeech(audioFile);
//
// response.put("success", true);
// response.put("result", result);
// return ResponseEntity.ok(response);
//
// } catch (Exception e) {
// response.put("success", false);
// response.put("message", "识别过程中发生错误: " + e.getMessage());
// return ResponseEntity.status(500).body(response);
// }
// }
/**
* URL
*/
@PostMapping("/recognizeSpeechByUrl")
public R<Map<String, Object>> recognizeSpeechByUrl(
@RequestParam("audioUrl") String audioUrl) {
Map<String, Object> response = new HashMap<>();
try {
if (audioUrl == null || audioUrl.trim().isEmpty()) {
response.put("success", false);
response.put("message", "请提供音频文件URL");
return R.ok(response);
}
System.out.println("接收到音频文件URL: {"+audioUrl+"}");
String resultText = aliAsrService.recognizeSpeechByUrl(audioUrl);
response.put("success", true);
response.put("text", resultText);
response.put("message", "语音识别成功");
return R.ok(response);
} catch (Exception e) {
e.printStackTrace();
response.put("success", false);
response.put("message", "语音识别失败: " + e.getMessage());
throw new RuntimeException("语音识别失败: " + e.getMessage());
// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
}

@ -0,0 +1,23 @@
package org.dromara.ai.asr.service;
/**
* @Author xins
* @Date 2025/9/19 15:50
* @Description:
*/
public interface AliAsrService {
/**
*
* @param audioFile
* @return
*/
// String recognizeSpeech(MultipartFile audioFile);
/**
* URL
* @param audioUrl URL
* @return
*/
String recognizeSpeechByUrl(String audioUrl);
}

@ -0,0 +1,175 @@
package org.dromara.ai.asr.service;
import com.alibaba.dashscope.audio.asr.transcription.Transcription;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionParam;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionQueryParam;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionResult;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionTaskResult;
import com.alibaba.dashscope.common.TaskStatus;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
/**
*
*/
@Service
public class AliAsrServiceImpl implements AliAsrService {
private static final Logger logger = LoggerFactory.getLogger(AliAsrServiceImpl.class);
// 从配置文件读取API Key
// @Value("${aliyun.asr.api-key}")
private String apiKey = "sk-ce77518fcbc54b2bbee2a7c793be6492";
// 临时文件存储路径
// @Value("${file.upload.temp-dir}")
// private String tempDir;
// 模型名称
private static final String MODEL_NAME = "sensevoice-v1";
// 语言提示
private static final String[] LANGUAGE_HINTS = {"zh"};
// @Override
// public String recognizeSpeech(MultipartFile audioFile) {
// try {
// // 保存上传的文件到临时目录
// String fileName = UUID.randomUUID().toString() + getFileExtension(audioFile.getOriginalFilename());
// File tempFile = new File(tempDir, fileName);
// FileUtils.copyInputStreamToFile(audioFile.getInputStream(), tempFile);
//
// try {
// // 这里应该上传文件到可公开访问的存储服务然后获取URL
// // 为简化示例这里假设文件已上传到可访问的URL
// String audioUrl = "http://example.com/temp/" + fileName;
//
// // 调用URL方式进行识别
// return recognizeSpeechByUrl(audioUrl);
// } finally {
// // 清理临时文件
// FileUtils.deleteQuietly(tempFile);
// }
// } catch (IOException e) {
// logger.error("处理音频文件失败", e);
// throw new RuntimeException("处理音频文件失败: " + e.getMessage());
// }
// }
@Override
public String recognizeSpeechByUrl(String audioUrl) {
try {
// 构建识别参数
TranscriptionParam param = TranscriptionParam.builder()
.apiKey(apiKey)
.model(MODEL_NAME)
.fileUrls(Arrays.asList(audioUrl))
.parameter("language_hints", LANGUAGE_HINTS)
.build();
Transcription transcription = new Transcription();
// 提交语音识别任务
TranscriptionResult result = transcription.asyncCall(param);
String taskId = result.getTaskId();
logger.info("语音识别任务提交成功TaskId: {}", taskId);
// 循环获取任务执行结果,直到任务结束
while (true) {
result = transcription.fetch(TranscriptionQueryParam.FromTranscriptionParam(param, taskId));
if (result.getTaskStatus() == TaskStatus.SUCCEEDED) {
logger.info("语音识别任务完成TaskId: {}", taskId);
break;
} else if (result.getTaskStatus() == TaskStatus.FAILED) {
logger.error("语音识别任务失败TaskId: {}, 错误信息: {}", taskId, result.getResults().toString());
throw new RuntimeException("语音识别任务失败: " + result.getResults().toString());
}
// 等待1秒后再次查询
Thread.sleep(1000);
}
// 解析识别结果
return parseRecognitionResult(result);
} catch (Exception e) {
logger.error("语音识别过程发生错误", e);
throw new RuntimeException("语音识别失败: " + e.getMessage());
}
}
/**
*
*/
private String parseRecognitionResult(TranscriptionResult result) throws Exception {
List<TranscriptionTaskResult> taskResultList = result.getResults();
if (taskResultList == null || taskResultList.isEmpty()) {
throw new RuntimeException("未获取到识别结果");
}
TranscriptionTaskResult taskResult = taskResultList.get(0);
String transcriptionUrl = taskResult.getTranscriptionUrl();
// 获取识别结果详情
HttpURLConnection connection = (HttpURLConnection) new URL(transcriptionUrl).openConnection();
connection.setRequestMethod("GET");
connection.connect();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
JsonElement jsonElement = gson.fromJson(reader, JsonElement.class);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
JsonArray transcriptsArr = jsonObject.getAsJsonArray("transcripts");
if (transcriptsArr != null && !transcriptsArr.isEmpty()) {
JsonObject jsonObject1 = transcriptsArr.get(0).getAsJsonObject();
String text = jsonObject1.get("text").getAsString();
return extractTextFromTags(text);
}
}
throw new RuntimeException("解析识别结果失败");
} finally {
connection.disconnect();
}
}
/**
*
*/
private String extractTextFromTags(String text) {
if (text == null || text.trim().isEmpty()) {
return "";
}
// 移除所有类似<...>或[...]的标签
return text.replaceAll("<[^>]+>", "")
.replaceAll("\\[[^]]+]", "")
.trim();
}
/**
*
*/
private String getFileExtension(String fileName) {
if (fileName == null || !fileName.contains(".")) {
return ".wav"; // 默认使用wav格式
}
return fileName.substring(fileName.lastIndexOf('.'));
}
}

@ -7,6 +7,7 @@ package org.dromara.ai.controller;
*/
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.alibaba.fastjson.JSONObject;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.dromara.ai.domain.bo.AiChatMessageBo;
@ -14,6 +15,7 @@ import org.dromara.ai.domain.bo.AiChatMessageTopicBo;
import org.dromara.ai.domain.bo.AiModelBo;
import org.dromara.ai.domain.vo.AiChatMessageVo;
import org.dromara.ai.domain.vo.AiModelVo;
import org.dromara.ai.process.dto.AIFillFormRequest;
import org.dromara.ai.test.vectorization.factory.EmbeddingServiceFactory;
import org.dromara.ai.test.vectorization.process.IEmbeddingProcessor;
import org.dromara.ai.process.dto.AIMessage;
@ -132,7 +134,8 @@ public class AiAssistantController extends BaseController {
/**
* sqlAIModelID, AIPLATFORMID
* sqlAIModelID, AIPLATFORMID
*
* @param request
* @return
*/
@ -142,7 +145,21 @@ public class AiAssistantController extends BaseController {
}
/**
*
*
* @param aiFillFormRequest
* @return
*/
@PostMapping(value = "/aiFillForm")
public JSONObject aiFillForm(@Validated(EditGroup.class) @RequestBody AIFillFormRequest aiFillFormRequest) {
try {
return aiAssistantService.aiFillForm(aiFillFormRequest);
} catch (Exception e) {
throw new RuntimeException("请重试:" + e.getMessage());
}
}
// /**
@ -158,8 +175,4 @@ public class AiAssistantController extends BaseController {
// }
}

@ -8,6 +8,7 @@ import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.dromara.ai.domain.AiFormSetting;
import org.dromara.ai.domain.AiSqlTable;
import org.dromara.ai.domain.bo.AiFormSettingDetailBo;
import org.dromara.ai.domain.bo.AiSqlTableBo;
import org.dromara.ai.domain.vo.AiFormSettingDetailVo;
import org.dromara.common.mybatis.helper.DataBaseHelper;
@ -150,5 +151,15 @@ public class AiFormSettingController extends BaseController {
aiFormSettingBo.getDataName()));
}
/**
* AI
*/
@GetMapping("/getAiFormSettingDetailList")
public R<List<AiFormSettingDetailVo>> getAiFormSettingDetailList(AiFormSettingDetailBo bo) {
List<AiFormSettingDetailVo> list = aiFormSettingService.selectFormSettingDetailList(bo);
return R.ok(list);
}
}

@ -1,5 +1,6 @@
package org.dromara.ai.domain;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@ -54,6 +55,11 @@ public class AiFormSetting extends TenantEntity {
*/
private String formName;
/**
* (12)
*/
private String formLoadType;
/**
* (sys_menucomponent/mes/info/index)
*/

@ -1,5 +1,8 @@
package org.dromara.ai.domain;
import jakarta.validation.constraints.NotBlank;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@ -58,4 +61,39 @@ public class AiFormSettingDetail {
private String settingFlag;
/**
* 123
*/
private String fieldType;
/**
*
*/
private String relateTableName;
/**
*
*/
private String relateTableDesc;
/**
* {fieldName:'dept_name',formProp:'deptName',fieldType:'2'}]
*/
private String relateTableFields;
/**
* SQL
*/
private String sqlSample;
/**
* (123)
*/
private String fieldStyle;
/**
*[{fieldKey:'delFlag',fieldValue:'0',conditionSymbol:'='}]
*/
private String relateFilterCondition;
}

@ -57,6 +57,12 @@ public class AiFormSettingBo extends BaseEntity {
@NotBlank(message = "表单名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String formName;
/**
* (12)
*/
@NotBlank(message = "表单加载类型(1表单直接加载表单2菜单打开菜单弹窗)不能为空", groups = { AddGroup.class, EditGroup.class })
private String formLoadType;
/**
* (sys_menucomponent/mes/info/index)
*/

@ -63,4 +63,40 @@ public class AiFormSettingDetailBo extends BaseEntity {
private String settingFlag;
/**
* 123
*/
@NotBlank(message = "字段类型1普通字段2关联表3字典数据不能为空", groups = { AddGroup.class, EditGroup.class })
private String fieldType;
/**
*
*/
private String relateTableName;
/**
*
*/
private String relateTableDesc;
/**
* dept_id as deptId,dept_name as deptName
*/
private String relateTableFields;
/**
* SQL
*/
private String sqlSample;
/**
* (123)
*/
@NotBlank(message = "字段样式(1普通输入框2下拉列表框3弹出选择)不能为空", groups = { AddGroup.class, EditGroup.class })
private String fieldStyle;
/**
*[{fieldKey:'delFlag',fieldValue:'0',conditionSymbol:'='}]
*/
private String relateFilterCondition;
}

@ -0,0 +1,15 @@
package org.dromara.ai.domain.dto;
/**
* @Author xins
* @Date 2025/9/25 8:50
* @Description:便MyBatis
*/
import lombok.Data;
import java.util.List;
@Data
public class AiTableConditionWrapper {
private List<AiTableQueryCondition> aiTableQueryConditions;
}

@ -0,0 +1,32 @@
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 AiTableData {
/**
* key
*/
private String fieldKey;
/**
*
*/
private Object fieldValue;
/**
*
*/
private String fieldType;
}

@ -0,0 +1,15 @@
package org.dromara.ai.domain.dto;
import lombok.Data;
/**
* @description AiForSettingDetaildto
* @author xins
* @date 2025/9/25 8:51
*/
@Data
public class AiTableQueryCondition {
private String fieldKey;
private String fieldValue;
private String conditionSymbol;
}

@ -1,8 +1,11 @@
package org.dromara.ai.domain.vo;
import jakarta.validation.constraints.NotBlank;
import org.dromara.ai.domain.AiFormSettingDetail;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
@ -73,4 +76,39 @@ public class AiFormSettingDetailVo implements Serializable {
private String settingFlag;
/**
* 123
*/
private String fieldType;
/**
*
*/
private String relateTableName;
/**
*
*/
private String relateTableDesc;
/**
* dept_id as deptId,dept_name as deptName
*/
private String relateTableFields;
/**
* SQL
*/
private String sqlSample;
/**
* (123)
*/
private String fieldStyle;
/**
*[{fieldKey:'delFlag',fieldValue:'0',conditionSymbol:'='}]
*/
private String relateFilterCondition;
}

@ -1,9 +1,13 @@
package org.dromara.ai.domain.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import jakarta.validation.constraints.NotBlank;
import org.dromara.ai.domain.AiFormSetting;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.ai.domain.AiFormSettingDetail;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
@ -65,6 +69,13 @@ public class AiFormSettingVo implements Serializable {
@ExcelProperty(value = "表单名称")
private String formName;
/**
* (12)
*/
@ExcelProperty(value = "表单加载类型(1表单直接加载表单2菜单打开菜单弹窗)不能为空")
private String formLoadType;
/**
* (sys_menucomponent/mes/info/index)
*/

@ -0,0 +1,55 @@
package org.dromara.ai.knowledge.parsing;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.ai.knowledge.parsing.enums.FileTypeEnum;
import org.dromara.ai.knowledge.split.CharacterTextSplitter;
import org.dromara.ai.knowledge.split.ITextSplitter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
/**
* @Author xins
* @Date 2025/8/19 11:10
* @Description:
*/
@Component
@Slf4j
public class MarkdownFileParsing implements IFileParsing{
private final ITextSplitter textSplitter;
public MarkdownFileParsing() {
this.textSplitter = new CharacterTextSplitter();
}
@Override
public String getFileContent(InputStream inputStream) {
StringBuffer stringBuffer = new StringBuffer();
try (InputStreamReader reader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(reader)){
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuffer.append(line).append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}
return stringBuffer.toString();
}
@Override
public List<String> getContentChunkList(String content, String knowledgeBaseSeparator, int textBlockSize, int overlapChar) {
return textSplitter.splitContent(content, knowledgeBaseSeparator,textBlockSize,overlapChar);
}
@Override
public FileTypeEnum supportedFileType() {
return FileTypeEnum.MD;
}
}

@ -1,9 +1,11 @@
package org.dromara.ai.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.dromara.ai.domain.dto.AiTableConditionWrapper;
import java.util.List;
import java.util.Map;
@ -44,4 +46,22 @@ public interface SQLServerDatabaseMetaMapper {
List<Map<String,Object>> getTablesStructureByTableNames(@Param("tableNames") String[] tableNames);
/**
* SELECT
* @param tableName
* @param fields
* @param conditions
* @param orderBy
* @param groupBy
* @return
*/
List<Map<String, Object>> dynamicSelect(@Param("tableName") String tableName,
@Param("fields") List<String> fields,
@Param("conditions") Map<String, Object> conditions,
@Param("wrapper") AiTableConditionWrapper wrapper,
@Param("orderBy") String orderBy,
@Param("groupBy") String groupBy);
}

@ -0,0 +1,45 @@
package org.dromara.ai.process.dto;
/**
* @description AI request dto
* @author xins
* @date 2025/7/29 14:44
*/
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.dromara.ai.domain.AiFormSettingDetail;
import org.dromara.ai.process.enums.AIProviderEnum;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import java.util.List;
@Data
public class AIFillFormRequest {
/**
* AIID
*/
@NotNull(message = "AI表单设置ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long formSettingId;
/**
*
*/
@NotNull(message = "用户提问内容不能为空", groups = { AddGroup.class, EditGroup.class })
private String naturalLanguageQuery;
/**
* AI
*/
@NotNull(message = "AI表单设置属性详情列表不能为空", groups = { AddGroup.class, EditGroup.class })
private List<AiFormSettingDetail> formSettingDetailList;
/**
*
*/
private Object customParams;
}

@ -1,5 +1,7 @@
package org.dromara.ai.service;
import com.alibaba.fastjson.JSONObject;
import org.dromara.ai.process.dto.AIFillFormRequest;
import org.dromara.ai.process.dto.AIRequest;
import org.dromara.ai.process.dto.AIResponse;
import reactor.core.publisher.Flux;
@ -23,5 +25,12 @@ public interface IAIAssistantService {
public String generateSQL(AIRequest aiRequest);
/**
* ai
*
* @param aiFillFormRequest
* @return String
*/
public JSONObject aiFillForm(AIFillFormRequest aiFillFormRequest);
// public boolean testAIModel(String provider, AIRequest aiRequest);
}

@ -2,6 +2,7 @@ package org.dromara.ai.service;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.dromara.ai.domain.AiFormSetting;
import org.dromara.ai.domain.bo.AiFormSettingDetailBo;
import org.dromara.ai.domain.vo.AiFormSettingDetailVo;
import org.dromara.ai.domain.vo.AiFormSettingVo;
import org.dromara.ai.domain.bo.AiFormSettingBo;
@ -87,4 +88,11 @@ public interface IAiFormSettingService {
* @return
*/
public List<AiFormSettingDetailVo> selectDbTableColumnsByName(String tableName, String dataName);
/**
* AI
* @param aiFormSettingDetailBo
* @return
*/
public List<AiFormSettingDetailVo> selectFormSettingDetailList(AiFormSettingDetailBo aiFormSettingDetailBo);
}

@ -1,13 +1,18 @@
package org.dromara.ai.service.impl;
import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.dromara.ai.domain.AiFormSettingDetail;
import org.dromara.ai.domain.AiModel;
import org.dromara.ai.domain.vo.AiModelVo;
import org.dromara.ai.domain.dto.AiTableConditionWrapper;
import org.dromara.ai.domain.dto.AiTableData;
import org.dromara.ai.domain.dto.AiTableQueryCondition;
import org.dromara.ai.mapper.AiModelMapper;
import org.dromara.ai.process.dto.AIFillFormRequest;
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;
@ -43,9 +48,6 @@ public class AIAssistantServiceImpl implements IAIAssistantService {
@Autowired
private AiModelMapper aiModelMapper;
@Autowired
private Map<String, IUnifiedAIProviderProcessor> aiProviderProcessorMap;
@Autowired
private AIProviderProcessorFactory aiProviderProcessorFactory;
@ -58,6 +60,9 @@ public class AIAssistantServiceImpl implements IAIAssistantService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private SQLServerDatabaseMetaMapper sQLServerDatabaseMetaMapper;
/**
*
*
@ -365,7 +370,191 @@ public class AIAssistantServiceImpl implements IAIAssistantService {
}
// /**
/**
* ai
*
* @param aiFillFormRequest
* @return String
*/
@Override
public JSONObject aiFillForm(AIFillFormRequest aiFillFormRequest) {
// List<String> fields = Arrays.asList("dept_id as deptId", "dept_name as deptName");
// Map<String, Object> condition = new HashMap<>();
// condition.put("dept_name", "测试");
// List<Map<String, Object>> tableData = sQLServerDatabaseMetaMapper.dynamicSelect("sys_dept", fields, condition, "", "");
//
// if (tableData != null) {
// return "";
// }
String naturalLanguageQuery = aiFillFormRequest.getNaturalLanguageQuery();
List<AiFormSettingDetail> aiFormSettingDetailList = aiFillFormRequest.getFormSettingDetailList();
StringBuilder sb = new StringBuilder("你是一个智能表单填充助手。请根据我的要求、提供的数据库表字段信息生成一份用于直接填充Vue3前端表单的JSON数据。\n\n");
sb.append("【要求】\n");
sb.append(naturalLanguageQuery).append(",\n\n");
sb.append("【数据库表信息】\n");
sb.append(getFormattedFormSettingDetails(aiFormSettingDetailList));
sb.append("\n【输出要求】\n");
sb.append("1. 请根据我的核心要求,智能推断并填充所有相关字段的值\n");
sb.append("2. 生成一个纯净的JSON对象不要任何额外的解释、文本或markdown代码块标记键是字段名值是根据我的要求推断出来的值\n");
// sb.append("3. 请返回**纯净的JSON格式**数据不要任何额外的解释、文本或markdown代码块标记\n");
sb.append("3. 仅返回一个JSON对象不要返回JSON数组,不要返回任何测试数据和推测的数据\n");
sb.append("4. 其他没有要求返回的数据返回空字符串\n");
IUnifiedAIProviderProcessor processor = aiProviderProcessorFactory.getProcessorByPlatformId(1L);
AIMessage aiMessage = new AIMessage();
aiMessage.setRole("user");
aiMessage.setContent(sb.toString());
AIRequest aiRequest = new AIRequest();
aiRequest.setMessages(Collections.singletonList(aiMessage));
aiRequest.setModelId(1L);
Mono<AIResponse> response = processor.chat(aiRequest);
if (Objects.requireNonNull(response.block()).isSuccess()) {
String content = response.block().getContent().toString();
JSONObject contentJson = JSONObject.parseObject(content);
parseRelateTable(aiFormSettingDetailList, contentJson);
System.out.println(contentJson.toJSONString());
return contentJson;
} else {
throw new RuntimeException("生成sql语句失败" + response.block().getErrorMessage());
}
}
private void parseRelateTable(List<AiFormSettingDetail> aiFormSettingDetailList, JSONObject contentJson) {
for (AiFormSettingDetail aiFormSettingDetail : aiFormSettingDetailList) {
if (aiFormSettingDetail.getSettingFlag().equals("1")) {
String fieldType = StringUtils.isBlank(aiFormSettingDetail.getFieldType()) ? HwMomAiConstants.AI_FORM_SETTING_FIELD_TYPE_NORMAL : aiFormSettingDetail.getFieldType();
String relateTableName = aiFormSettingDetail.getRelateTableName();
String relateTableFields = aiFormSettingDetail.getRelateTableFields();
if (fieldType.equals(HwMomAiConstants.AI_FORM_SETTING_FIELD_TYPE_RELATE) &&
StringUtils.isNotBlank(relateTableName) && StringUtils.isNotBlank(relateTableFields)) {
Object value = contentJson.get(aiFormSettingDetail.getFormProp());
if (value != null && value instanceof JSONArray) {
JSONArray valueArr = (JSONArray) value;
if (valueArr.size() > 0) {
JSONObject conditionJson = valueArr.getJSONObject(0);
Set<String> conditionKeys = conditionJson.keySet();
Map<String, Object> conditionMap = new HashMap<>();
for (String conditionKey : conditionKeys) {
conditionMap.put(camelToSnakeEnhanced(conditionKey), conditionJson.get(conditionKey));
}
JSONArray relateTableFieldArr = JSONArray.parseArray(relateTableFields);
// List<String> relateTableFieldList = Arrays.asList(relateTableFields.split(","));
List<String> relateTableFieldList = relateTableFieldArr.stream()
.map(obj -> {
JSONObject jsonObj = (JSONObject) obj;
return jsonObj.getString("fieldName") + " as " + jsonObj.getString("formProp");
})
.collect(Collectors.toList());
String relateFilterCondition = aiFormSettingDetail.getRelateFilterCondition();
//[{fieldKey:'del_flag',fieldValue:'0',conditionSymbol:'='}]
// 创建条件包装类
AiTableConditionWrapper wrapper = new AiTableConditionWrapper();
if(StringUtils.isNotBlank(relateFilterCondition)){
// 解析JSON字符串为条件列表
List<AiTableQueryCondition> conditions = JSON.parseArray(relateFilterCondition, AiTableQueryCondition.class);
wrapper.setAiTableQueryConditions(conditions);
// JSONArray relateFilterConditionArr = JSONArray.parseArray(relateFilterCondition);
}
List<Map<String, Object>> tableData = sQLServerDatabaseMetaMapper.dynamicSelect(relateTableName, relateTableFieldList, conditionMap, wrapper,"", "");
JSONArray relateTableDataArr = new JSONArray();
for (Map<String, Object> tableD : tableData) {
// JSONObject tableDataJson = new JSONObject();
JSONArray tableFiledDataArr = new JSONArray();
// tableDataJson.put("relateTableDesc",aiFormSettingDetail.getRelateTableDesc());
AiTableData aiTableData = new AiTableData();
aiTableData.setFieldKey("relateTableDesc");
aiTableData.setFieldValue(aiFormSettingDetail.getRelateTableDesc());
aiTableData.setFieldType("3");//在选择时显示的title
tableFiledDataArr.add(aiTableData);
Set<String> tableDataKeys = tableD.keySet();
for (String tableDataKey : tableDataKeys) {
// tableDataJson.put(tableDataKey,tableD.get(tableDataKey));
aiTableData = new AiTableData();
if (tableDataKey.contains("Id")) {
aiTableData.setFieldType("1");//主键,赋值使用
} else {
aiTableData.setFieldType("2");//显示使用
}
aiTableData.setFieldKey(tableDataKey);
aiTableData.setFieldValue(tableD.get(tableDataKey));
tableFiledDataArr.add(aiTableData);
}
relateTableDataArr.add(tableFiledDataArr);
}
contentJson.put(aiFormSettingDetail.getFormProp(), relateTableDataArr);
System.out.println(tableData);
}
}
}
}
}
}
private String getFormattedFormSettingDetails(List<AiFormSettingDetail> aiFormSettingDetailList) {
StringBuilder sb = new StringBuilder("字段列表:\n");
for (AiFormSettingDetail aiFormSettingDetail : aiFormSettingDetailList) {
if (aiFormSettingDetail.getSettingFlag().equals("1")) {
sb.append("- 字段名:").append(aiFormSettingDetail.getFieldName())
.append("返回的JSON数据Key值").append(aiFormSettingDetail.getFormProp())
.append(",字段描述:").append(aiFormSettingDetail.getFieldDesc());
String fieldType = StringUtils.isBlank(aiFormSettingDetail.getFieldType()) ? HwMomAiConstants.AI_FORM_SETTING_FIELD_TYPE_NORMAL : aiFormSettingDetail.getFieldType();
if (fieldType.equals(HwMomAiConstants.AI_FORM_SETTING_FIELD_TYPE_RELATE)) {
sb.append(" 备注:如果需要从关联表获取数据,则返回此关联表的查询条件即可,返回示例如下:[{'fieldName':'fieldValue'}]fieldName从字段名获取如果不需要从关联表获取数据则返回空");
} else if (fieldType.equals(HwMomAiConstants.AI_FORM_SETTING_FIELD_TYPE_DICT)) {
sb.append(" 备注:如果有符合条件的数据,则根据要求返回字典的数值");
}
sb.append("\n");
}
}
return sb.toString();
}
/**
* 线
*/
public String camelToSnakeEnhanced(String camelCase) {
if (camelCase == null || camelCase.isEmpty()) {
return camelCase;
}
// 处理全大写的情况(如"URL" -> "url"
if (camelCase.equals(camelCase.toUpperCase())) {
return camelCase.toLowerCase();
}
String result = camelCase.replaceAll("([a-z])([A-Z])", "$1_$2")
.replaceAll("([A-Z])([A-Z][a-z])", "$1_$2")
.toLowerCase();
return result;
}
// /**
// * 在新建AI模型或修改AI模型时测试AI模型是否可用验证apikey和apisecret等信息
// * @param provider
// * @param aiRequest
@ -394,17 +583,4 @@ public class AIAssistantServiceImpl implements IAIAssistantService {
//
// return true;
// }
public String aiFillForm() {
String aiClient = "deepSeek";
IUnifiedAIProviderProcessor service = aiProviderProcessorMap.get(aiClient);
if (service == null) {
throw new IllegalArgumentException("Unsupported payment type");
}
return "";
}
}

@ -10,6 +10,7 @@ import org.anyline.metadata.Table;
import org.anyline.proxy.ServiceProxy;
import org.dromara.ai.domain.AiChatMessage;
import org.dromara.ai.domain.AiFormSettingDetail;
import org.dromara.ai.domain.bo.AiFormSettingDetailBo;
import org.dromara.ai.domain.vo.AiFormSettingDetailVo;
import org.dromara.ai.mapper.AiFormSettingDetailMapper;
import org.dromara.common.core.utils.MapstructUtils;
@ -53,19 +54,23 @@ public class AiFormSettingServiceImpl implements IAiFormSettingService {
* @return AI
*/
@Override
public AiFormSettingVo queryById(Long formSettingId){
public AiFormSettingVo queryById(Long formSettingId) {
MPJLambdaWrapper<AiFormSettingDetail> lqw = JoinWrappers.lambda(AiFormSettingDetail.class)
.select(AiFormSettingDetail::getSettingDetailId)
.select(AiFormSettingDetail::getFieldName)
.select(AiFormSettingDetail::getFieldDesc)
.select(AiFormSettingDetail::getFormProp)
.select(AiFormSettingDetail::getSettingFlag)
.select(AiFormSettingDetail::getFieldStyle)
.select(AiFormSettingDetail::getFieldType)
.select(AiFormSettingDetail::getRelateTableFields)
.select(AiFormSettingDetail::getRelateFilterCondition)
.eq(AiFormSettingDetail::getFormSettingId, formSettingId);
List<AiFormSettingDetail> aiFormSettingDetailList = aiFormSettingDetailMapper.selectList(lqw);
AiFormSettingVo aiFormSetting = baseMapper.selectVoById(formSettingId);
aiFormSetting.setAiFormSettingDetailList(aiFormSettingDetailList);
return aiFormSetting;
return aiFormSetting;
}
/**
@ -97,17 +102,17 @@ public class AiFormSettingServiceImpl implements IAiFormSettingService {
private MPJLambdaWrapper<AiFormSetting> buildQueryWrapper(AiFormSettingBo bo) {
Map<String, Object> params = bo.getParams();
MPJLambdaWrapper<AiFormSetting> lqw = JoinWrappers.lambda(AiFormSetting.class)
.selectAll(AiFormSetting.class)
.eq(bo.getFormSettingId() != null, AiFormSetting::getFormSettingId, bo.getFormSettingId())
.eq(StringUtils.isNotBlank(bo.getFormType()), AiFormSetting::getFormType, bo.getFormType())
.like(StringUtils.isNotBlank(bo.getTableName()), AiFormSetting::getTableName, bo.getTableName())
.like(StringUtils.isNotBlank(bo.getFormName()), AiFormSetting::getFormName, bo.getFormName())
.like(StringUtils.isNotBlank(bo.getFormPath()), AiFormSetting::getFormPath, bo.getFormPath())
.eq(StringUtils.isNotBlank(bo.getDialogVisibleVariable()), AiFormSetting::getDialogVisibleVariable, bo.getDialogVisibleVariable())
.like(StringUtils.isNotBlank(bo.getSubTableName()), AiFormSetting::getSubTableName, bo.getSubTableName())
.like(StringUtils.isNotBlank(bo.getSubTableFkName()), AiFormSetting::getSubTableFkName, bo.getSubTableFkName())
.eq(StringUtils.isNotBlank(bo.getClientType()), AiFormSetting::getClientType, bo.getClientType())
.orderByDesc(AiFormSetting::getCreateTime);
.selectAll(AiFormSetting.class)
.eq(bo.getFormSettingId() != null, AiFormSetting::getFormSettingId, bo.getFormSettingId())
.eq(StringUtils.isNotBlank(bo.getFormType()), AiFormSetting::getFormType, bo.getFormType())
.like(StringUtils.isNotBlank(bo.getTableName()), AiFormSetting::getTableName, bo.getTableName())
.like(StringUtils.isNotBlank(bo.getFormName()), AiFormSetting::getFormName, bo.getFormName())
.like(StringUtils.isNotBlank(bo.getFormPath()), AiFormSetting::getFormPath, bo.getFormPath())
.eq(StringUtils.isNotBlank(bo.getDialogVisibleVariable()), AiFormSetting::getDialogVisibleVariable, bo.getDialogVisibleVariable())
.like(StringUtils.isNotBlank(bo.getSubTableName()), AiFormSetting::getSubTableName, bo.getSubTableName())
.like(StringUtils.isNotBlank(bo.getSubTableFkName()), AiFormSetting::getSubTableFkName, bo.getSubTableFkName())
.eq(StringUtils.isNotBlank(bo.getClientType()), AiFormSetting::getClientType, bo.getClientType())
.orderByDesc(AiFormSetting::getCreateTime);
return lqw;
}
@ -151,7 +156,7 @@ public class AiFormSettingServiceImpl implements IAiFormSettingService {
/**
*
*/
private void validEntityBeforeSave(AiFormSetting entity){
private void validEntityBeforeSave(AiFormSetting entity) {
//TODO 做一些数据校验,如唯一约束
}
@ -165,7 +170,7 @@ public class AiFormSettingServiceImpl implements IAiFormSettingService {
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
if (isValid) {
//TODO 做一些业务上的校验,判断是否需要校验
}
ids.forEach(id -> {
@ -177,16 +182,11 @@ public class AiFormSettingServiceImpl implements IAiFormSettingService {
}
/**
*
*
* @param aiFormSettingBo AiFormSettingBo
* @param pageQuery PageQuery
* @param pageQuery PageQuery
* @return TableDataInfo
*/
@DS("#aiFormSettingBo.dataName")
@ -281,9 +281,32 @@ public class AiFormSettingServiceImpl implements IAiFormSettingService {
return aiFormSettingDetailList;
}
/**
* AI
*
* @param aiFormSettingDetailBo
* @return
*/
@Override
public List<AiFormSettingDetailVo> selectFormSettingDetailList(AiFormSettingDetailBo aiFormSettingDetailBo) {
MPJLambdaWrapper<AiFormSettingDetail> lqw = JoinWrappers.lambda(AiFormSettingDetail.class)
.select(AiFormSettingDetail::getSettingDetailId)
.select(AiFormSettingDetail::getFieldName)
.select(AiFormSettingDetail::getFieldDesc)
.select(AiFormSettingDetail::getFormProp)
.select(AiFormSettingDetail::getSettingFlag)
.select(AiFormSettingDetail::getFieldType)
.select(AiFormSettingDetail::getRelateTableFields)
.select(AiFormSettingDetail::getRelateTableName)
.select(AiFormSettingDetail::getRelateTableDesc)
.select(AiFormSettingDetail::getRelateFilterCondition)
.eq(AiFormSettingDetail::getFormSettingId, aiFormSettingDetailBo.getFormSettingId())
.eq(AiFormSettingDetail::getSettingFlag, aiFormSettingDetailBo.getSettingFlag());
List<AiFormSettingDetailVo> aiFormSettingDetailList = aiFormSettingDetailMapper.selectVoList(lqw);
return aiFormSettingDetailList;
}
/**
*
* @param snakeCaseStr
* @return
*/

@ -27,44 +27,11 @@ 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;
/**
* AIsqlService

@ -31,4 +31,5 @@ spring:
import:
- optional:nacos:application-common.yml
- optional:nacos:datasource.yml
- optional:nacos:ruoyi-resource.yml
- optional:nacos:${spring.application.name}.yml

@ -5,10 +5,10 @@
<mapper namespace="org.dromara.ai.mapper.AiFormSettingDetailMapper">
<insert id="insertFormSettingDetailBatch" parameterType="java.util.List">
insert into ai_form_setting_detail (form_setting_id, table_name, field_name, field_desc, form_prop,setting_flag)
insert into ai_form_setting_detail (form_setting_id, table_name, field_name, field_desc, form_prop,setting_flag,field_type,field_style,sql_sample)
values
<foreach collection="list" item="item" separator=",">
(#{item.formSettingId}, #{item.tableName}, #{item.fieldName}, #{item.fieldDesc}, #{item.formProp},#{item.settingFlag})
(#{item.formSettingId}, #{item.tableName}, #{item.fieldName}, #{item.fieldDesc}, #{item.formProp},#{item.settingFlag},#{item.fieldType},#{item.fieldStyle},#{item.sqlSample})
</foreach>
</insert>
</mapper>

@ -75,4 +75,74 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<!-- 完整的动态SELECT语句 -->
<select id="dynamicSelect" resultType="java.util.Map">
SELECT
<choose>
<when test="fields != null and fields.size() > 0">
<foreach collection="fields" item="field" separator=",">
${field}
</foreach>
</when>
<otherwise>*</otherwise>
</choose>
FROM ${tableName}
<where>
<foreach collection="conditions" item="value" index="key">
<if test="value != null">
AND ${key} LIKE CONCAT('%', #{value}, '%')
</if>
<!-- 支持特殊条件处理 -->
<!-- <if test="key.endsWith('Like') and value != null">-->
<!-- AND ${key.replace('Like', '')} LIKE CONCAT('%', #{value}, '%')-->
<!-- </if>-->
<!-- <if test="key.endsWith('Gt') and value != null">-->
<!-- AND ${key.replace('Gt', '')} &gt; #{value}-->
<!-- </if>-->
<!-- <if test="key.endsWith('Lt') and value != null">-->
<!-- AND ${key.replace('Lt', '')} &lt; #{value}-->
<!-- </if>-->
<!-- <if test="key.endsWith('In') and value != null">-->
<!-- AND ${key.replace('In', '')} IN-->
<!-- <foreach collection="value" item="item" open="(" separator="," close=")">-->
<!-- #{item}-->
<!-- </foreach>-->
<!-- </if>-->
</foreach>
<if test="wrapper.aiTableQueryConditions != null and wrapper.aiTableQueryConditions.size() > 0">
<foreach collection="wrapper.aiTableQueryConditions" item="condition" separator=" AND " open="" close="">
<!-- 使用_parameter来引用当前参数避免类型解析问题 -->
<choose>
<when test="'EQUAL' == condition.conditionSymbol">
and ${condition.fieldKey} = #{condition.fieldValue}
</when>
<when test="'LIKE' == condition.conditionSymbol">
and ${condition.fieldKey} LIKE CONCAT('%', #{condition.fieldValue}, '%')
</when>
<when test="'IN' == condition.conditionSymbol">
and ${condition.fieldKey} IN
<foreach collection="condition.fieldValue.split(',')" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</when>
<!-- 默认使用等于条件 -->
<otherwise>
and ${condition.fieldKey} = #{condition.fieldValue}
</otherwise>
</choose>
</foreach>
</if>
</where>
<if test="groupBy != null and groupBy != ''">
GROUP BY ${groupBy}
</if>
<if test="orderBy != null and orderBy != ''">
ORDER BY ${orderBy}
</if>
</select>
</mapper>

Loading…
Cancel
Save