feat(oa): 添加AI数据查询功能
- 新增AI数据查询接口和相关服务 - 实现关键词匹配和AI智能识别表功能 - 添加SQL生成和执行逻辑 - 新增动态数据返回格式和字段信息类 - 优化SQL安全性验证
This commit is contained in:
@@ -20,9 +20,14 @@ public class DynamicDataVo {
|
||||
private Meta meta;
|
||||
|
||||
/**
|
||||
* 实际数据
|
||||
* 响应数据(根据renderType不同而不同)
|
||||
*/
|
||||
private List<Map<String, Object>> data;
|
||||
private Object response;
|
||||
|
||||
/**
|
||||
* 渲染类型:text | table | chart | fix
|
||||
*/
|
||||
private String renderType;
|
||||
|
||||
/**
|
||||
* 元数据类
|
||||
@@ -60,4 +65,36 @@ public class DynamicDataVo {
|
||||
*/
|
||||
private String format;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格数据格式
|
||||
*/
|
||||
@Data
|
||||
public static class TableData {
|
||||
/**
|
||||
* 列信息
|
||||
*/
|
||||
private List<Map<String, Object>> columns;
|
||||
|
||||
/**
|
||||
* 数据源
|
||||
*/
|
||||
private List<Map<String, Object>> datasource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 图表数据格式
|
||||
*/
|
||||
@Data
|
||||
public static class ChartData {
|
||||
/**
|
||||
* 图表配置选项
|
||||
*/
|
||||
private Object options;
|
||||
|
||||
/**
|
||||
* 数据集
|
||||
*/
|
||||
private List<Map<String, Object>> dataset;
|
||||
}
|
||||
}
|
||||
@@ -67,10 +67,16 @@ public class AiDataQueryServiceImpl implements IAiDataQueryService {
|
||||
DynamicDataVo result = databaseQueryService.executeQueryWithMeta(sql, primaryTable, queryBo.getIncludeMeta());
|
||||
|
||||
// 9. 限制返回记录数
|
||||
if (result.getData() != null && result.getData().size() > queryBo.getLimit()) {
|
||||
result.setData(result.getData().subList(0, queryBo.getLimit()));
|
||||
if (result.getResponse() instanceof DynamicDataVo.TableData) {
|
||||
DynamicDataVo.TableData tableData = (DynamicDataVo.TableData) result.getResponse();
|
||||
if (tableData.getDatasource() != null && tableData.getDatasource().size() > queryBo.getLimit()) {
|
||||
tableData.setDatasource(tableData.getDatasource().subList(0, queryBo.getLimit()));
|
||||
}
|
||||
}
|
||||
|
||||
// 10. 根据数据内容智能选择renderType
|
||||
result = determineRenderType(result, queryBo.getQuery());
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -98,7 +104,9 @@ public class AiDataQueryServiceImpl implements IAiDataQueryService {
|
||||
prompt.append("2. 生成的SQL必须语法正确,可以直接执行\n");
|
||||
prompt.append("3. 如果涉及多表查询,请使用适当的JOIN语句\n");
|
||||
prompt.append("4. 请根据用户需求添加适当的WHERE条件、ORDER BY、LIMIT等子句\n");
|
||||
prompt.append("5. 只返回SQL语句,不要包含其他解释文字\n\n");
|
||||
prompt.append("5. 确保查询能返回有意义的数据,避免空结果\n");
|
||||
prompt.append("6. 如果查询可能返回空结果,请添加适当的默认条件或使用COALESCE等函数\n");
|
||||
prompt.append("7. 只返回SQL语句,不要包含其他解释文字\n\n");
|
||||
|
||||
// 用户需求
|
||||
prompt.append("用户需求:").append(userQuery).append("\n\n");
|
||||
@@ -130,7 +138,12 @@ public class AiDataQueryServiceImpl implements IAiDataQueryService {
|
||||
}
|
||||
|
||||
// 输出要求
|
||||
prompt.append("请根据以上信息生成SQL查询语句,只返回SQL语句本身,不要包含任何其他文字。");
|
||||
prompt.append("请根据以上信息生成SQL查询语句,确保查询能返回有意义的数据。如果查询可能返回空结果,请考虑:\n");
|
||||
prompt.append("1. 使用LEFT JOIN而不是INNER JOIN\n");
|
||||
prompt.append("2. 添加适当的默认值或使用COALESCE函数\n");
|
||||
prompt.append("3. 使用LIMIT限制返回记录数(建议50条以内)\n");
|
||||
prompt.append("4. 添加ORDER BY确保结果有序\n");
|
||||
prompt.append("只返回SQL语句本身,不要包含任何其他文字。");
|
||||
|
||||
return prompt.toString();
|
||||
}
|
||||
@@ -347,4 +360,146 @@ public class AiDataQueryServiceImpl implements IAiDataQueryService {
|
||||
log.info("SQL验证通过: {}", cleanedResponse);
|
||||
return cleanedResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数据内容和用户查询智能选择renderType并设置对应的数据格式
|
||||
*/
|
||||
private DynamicDataVo determineRenderType(DynamicDataVo result, String userQuery) {
|
||||
// 默认使用table类型
|
||||
String renderType = "table";
|
||||
|
||||
// 如果用户查询包含特定关键词,选择对应的renderType
|
||||
String lowerQuery = userQuery.toLowerCase();
|
||||
|
||||
if (lowerQuery.contains("统计") || lowerQuery.contains("图表") || lowerQuery.contains("趋势") ||
|
||||
lowerQuery.contains("分析") || lowerQuery.contains("可视化")) {
|
||||
renderType = "chart";
|
||||
} else if (lowerQuery.contains("文本") || lowerQuery.contains("描述") || lowerQuery.contains("说明")) {
|
||||
renderType = "text";
|
||||
} else if (lowerQuery.contains("复合") || lowerQuery.contains("多个") || lowerQuery.contains("组合")) {
|
||||
renderType = "fix";
|
||||
}
|
||||
|
||||
// 根据renderType设置对应的数据格式
|
||||
if (result.getResponse() instanceof DynamicDataVo.TableData) {
|
||||
DynamicDataVo.TableData tableData = (DynamicDataVo.TableData) result.getResponse();
|
||||
|
||||
switch (renderType) {
|
||||
case "text":
|
||||
// 文本格式:返回字符串
|
||||
if (tableData.getDatasource() == null || tableData.getDatasource().isEmpty()) {
|
||||
result.setResponse("暂无相关数据");
|
||||
} else {
|
||||
// 将表格数据转换为文本描述
|
||||
StringBuilder textResponse = new StringBuilder();
|
||||
textResponse.append("查询结果:\n");
|
||||
textResponse.append("共找到 ").append(tableData.getDatasource().size()).append(" 条记录\n\n");
|
||||
|
||||
for (int i = 0; i < Math.min(tableData.getDatasource().size(), 5); i++) {
|
||||
Map<String, Object> row = tableData.getDatasource().get(i);
|
||||
textResponse.append("记录 ").append(i + 1).append(":");
|
||||
for (Map.Entry<String, Object> entry : row.entrySet()) {
|
||||
textResponse.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
|
||||
}
|
||||
textResponse.append("\n");
|
||||
}
|
||||
|
||||
if (tableData.getDatasource().size() > 5) {
|
||||
textResponse.append("... 还有 ").append(tableData.getDatasource().size() - 5).append(" 条记录");
|
||||
}
|
||||
|
||||
result.setResponse(textResponse.toString());
|
||||
}
|
||||
break;
|
||||
|
||||
case "chart":
|
||||
// 图表格式:检查是否有数值数据
|
||||
boolean hasNumericData = false;
|
||||
if (tableData.getDatasource().size() > 0) {
|
||||
Map<String, Object> firstRow = tableData.getDatasource().get(0);
|
||||
for (Object value : firstRow.values()) {
|
||||
if (value instanceof Number) {
|
||||
hasNumericData = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNumericData) {
|
||||
// 转换为图表数据格式
|
||||
DynamicDataVo.ChartData chartData = new DynamicDataVo.ChartData();
|
||||
|
||||
// 图表配置
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
options.put("title", "数据统计图表");
|
||||
options.put("type", "bar");
|
||||
options.put("xAxis", "类别");
|
||||
options.put("yAxis", "数值");
|
||||
chartData.setOptions(options);
|
||||
chartData.setDataset(tableData.getDatasource());
|
||||
|
||||
result.setResponse(chartData);
|
||||
} else {
|
||||
// 如果没有数值数据,转为文本格式
|
||||
renderType = "text";
|
||||
result.setResponse("数据不适合图表展示,转为文本格式显示");
|
||||
}
|
||||
break;
|
||||
|
||||
case "fix":
|
||||
// 复合格式:返回数组格式
|
||||
List<Map<String, Object>> fixData = new ArrayList<>();
|
||||
|
||||
// 添加表格数据
|
||||
Map<String, Object> tableBlock = new HashMap<>();
|
||||
tableBlock.put("meta", result.getMeta());
|
||||
tableBlock.put("response", tableData);
|
||||
tableBlock.put("renderType", "table");
|
||||
fixData.add(tableBlock);
|
||||
|
||||
// 添加统计信息
|
||||
Map<String, Object> statsBlock = new HashMap<>();
|
||||
Map<String, Object> statsMeta = new HashMap<>();
|
||||
|
||||
List<Map<String, Object>> statsFields = new ArrayList<>();
|
||||
Map<String, Object> totalField = new HashMap<>();
|
||||
totalField.put("fieldName", "total");
|
||||
totalField.put("label", "总记录数");
|
||||
totalField.put("type", "number");
|
||||
statsFields.add(totalField);
|
||||
|
||||
Map<String, Object> descField = new HashMap<>();
|
||||
descField.put("fieldName", "description");
|
||||
descField.put("label", "描述");
|
||||
descField.put("type", "string");
|
||||
statsFields.add(descField);
|
||||
|
||||
statsMeta.put("fields", statsFields);
|
||||
statsBlock.put("meta", statsMeta);
|
||||
|
||||
Map<String, Object> statsResponse = new HashMap<>();
|
||||
statsResponse.put("total", tableData.getDatasource().size());
|
||||
statsResponse.put("description", "查询结果统计");
|
||||
statsBlock.put("response", statsResponse);
|
||||
statsBlock.put("renderType", "text");
|
||||
fixData.add(statsBlock);
|
||||
|
||||
result.setResponse(fixData);
|
||||
break;
|
||||
|
||||
case "table":
|
||||
default:
|
||||
// 表格格式:保持原有格式
|
||||
// 如果数据为空,转为文本
|
||||
if (tableData.getDatasource() == null || tableData.getDatasource().isEmpty()) {
|
||||
renderType = "text";
|
||||
result.setResponse("暂无相关数据");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.setRenderType(renderType);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -107,13 +107,19 @@ public class DatabaseQueryServiceImpl implements IDatabaseQueryService {
|
||||
List<Map<String, Object>> data = executeQuery(sql);
|
||||
|
||||
DynamicDataVo result = new DynamicDataVo();
|
||||
result.setData(data);
|
||||
|
||||
// 设置渲染类型为表格
|
||||
result.setRenderType("table");
|
||||
|
||||
// 构建表格数据
|
||||
DynamicDataVo.TableData tableData = new DynamicDataVo.TableData();
|
||||
|
||||
// 如果需要元信息,则生成字段元信息
|
||||
if (includeMeta && tableName != null) {
|
||||
List<TableColumnVo> columns = getTableColumns(tableName);
|
||||
DynamicDataVo.Meta meta = new DynamicDataVo.Meta();
|
||||
List<DynamicDataVo.Field> fields = new ArrayList<>();
|
||||
List<Map<String, Object>> columnsList = new ArrayList<>();
|
||||
|
||||
for (TableColumnVo column : columns) {
|
||||
DynamicDataVo.Field field = new DynamicDataVo.Field();
|
||||
@@ -122,12 +128,25 @@ public class DatabaseQueryServiceImpl implements IDatabaseQueryService {
|
||||
field.setType(getFieldType(column));
|
||||
field.setFormat(getFieldFormat(column));
|
||||
fields.add(field);
|
||||
|
||||
// 构建列信息
|
||||
Map<String, Object> columnInfo = new HashMap<>();
|
||||
columnInfo.put("field", column.getColumnName());
|
||||
columnInfo.put("title", column.getColumnComment() != null ? column.getColumnComment() : column.getColumnName());
|
||||
columnInfo.put("type", getFieldType(column));
|
||||
columnInfo.put("format", getFieldFormat(column));
|
||||
columnsList.add(columnInfo);
|
||||
}
|
||||
|
||||
meta.setFields(fields);
|
||||
result.setMeta(meta);
|
||||
tableData.setColumns(columnsList);
|
||||
}
|
||||
|
||||
// 设置数据源
|
||||
tableData.setDatasource(data);
|
||||
result.setResponse(tableData);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user