feat(oa): 添加AI数据查询功能

- 新增AI数据查询接口和相关服务
- 实现关键词匹配和AI智能识别表功能
- 添加SQL生成和执行逻辑
- 新增动态数据返回格式和字段信息类
- 优化SQL安全性验证
This commit is contained in:
2025-08-05 14:21:14 +08:00
parent 845e8cfb1e
commit 0f9340bcd9
3 changed files with 219 additions and 8 deletions

View File

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

View File

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

View File

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