chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xspaceagi</groupId>
<artifactId>app-platform-compose</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>app-platform-compose-spec</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cn.idev.excel</groupId>
<artifactId>fastexcel</artifactId>
</dependency>
<dependency>
<groupId>com.xspaceagi</groupId>
<artifactId>app-platform-compose-sdk</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,18 @@
package com.xspaceagi.compose;
import lombok.Getter;
/**
* mysql,doris 数据库类型
*/
@Getter
public enum DatabaseTypeEnum {
MYSQL("mysql"),
DORIS("doris");
private String type;
DatabaseTypeEnum(String type) {
this.type = type;
}
}

View File

@@ -0,0 +1,125 @@
package com.xspaceagi.compose.spec.constants;
/**
* Doris配置常量
*/
public class DorisConfigContants {
/**
* 默认doris数据库
*/
public static final String DEFAULT_DORIS_DATABASE = "agent_platform";
/**
* 默认doris表前缀
*/
public static final String DEFAULT_DORIS_TABLE_PREFIX = "custom_table_";
/**
* 是否固定字段允许为空,true:固定为可空字段,false:不固定,根据字段定义的nullableFlag决定
*/
public static final boolean FIXED_FIELD_NULLABLE = true;
/**
* 默认字符串长度,短文本默认值最大长度
*/
public static final int DEFAULT_STRING_LENGTH = 255;
/**
* 默认查询限制
*/
public static final Long DEFAULT_QUERY_LIMIT = 1000L;
/**
* 导入excel数据最大行数
*/
public static final int IMPORT_EXCEL_DATA_MAX_ROWS = 1000000;
/**
* 导出excel数据最大行数
*/
public static final int EXPORT_EXCEL_DATA_MAX_ROWS = 1000000;
/**
* 检查excel数据行数是否超过限制最大支持100000行
*/
public static final int IMPORT_EXCEL_DATA_MAX_ROWS_CHECK = 100000;
/**
* 批量导入数据时每批处理的数据条数
*/
public static final int IMPORT_EXCEL_DATA_BATCH_SIZE = 100;
/**
* Excel数值精度限制Excel最大精确显示15位数字
*/
public static final int EXCEL_MAX_PRECISION_DIGITS = 15;
/**
* Excel数值范围限制超过此范围可能显示为科学计数法
*/
public static final double EXCEL_MAX_SAFE_NUMBER = 1e15;
/**
* 是否强制转换所有主键为字符串
*/
public static final boolean FORCE_CONVERT_PRIMARY_KEY = true;
/**
* 是否启用智能长数值转换
*/
public static final boolean ENABLE_SMART_NUMBER_CONVERSION = true;
/**
* Excel数值格式化策略
* AUTO: 自动判断是否需要转换为字符串
* FORCE_STRING: 强制将所有数值转换为字符串
* PRESERVE_NUMBER: 保持数值格式,不进行转换
*/
public static final String EXCEL_NUMBER_FORMAT_STRATEGY = "AUTO";
/**
* 是否为长数值添加单引号前缀Excel中可防止科学计数法
*/
public static final boolean ADD_QUOTE_PREFIX_FOR_LONG_NUMBERS = false;
/**
* 自定义数值格式化阈值
*/
public static final long CUSTOM_NUMBER_THRESHOLD = 1000000000000L; // 1万亿
/**
* JavaScript安全整数最大值 (2^53-1)
*/
public static final long JS_MAX_SAFE_INTEGER = 9007199254740991L;
/**
* JavaScript安全整数最小值 -(2^53-1)
*/
public static final long JS_MIN_SAFE_INTEGER = -9007199254740991L;
/**
* JavaScript数值的最大有效数字位数超过此位数可能丢失精度
*/
public static final int JS_MAX_SAFE_DIGITS = 15;
/**
* JavaScript数值的安全范围阈值考虑小数精度
*/
public static final double JS_SAFE_NUMBER_THRESHOLD = 1e14; // 10^14
/**
* 是否启用Web端智能数值转换防止JavaScript精度丢失
*/
public static final boolean ENABLE_WEB_SMART_NUMBER_CONVERSION = true;
/**
* 根据id获取doris表名
*
* @param id 表id
* @return 表名
*/
public static String obtainTableName(Long id) {
return DEFAULT_DORIS_TABLE_PREFIX + id;
}
}

View File

@@ -0,0 +1,212 @@
package com.xspaceagi.compose.spec.utils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
/**
* 组件异常处理工具
*/
@Slf4j
public class ComposeExceptionUtils {
// 数据库错误匹配正则表达式
private static final Pattern DATA_TOO_LONG_PATTERN = Pattern.compile(
"Data truncation: Data too long for column '([^']+)' at row (\\d+)",
Pattern.CASE_INSENSITIVE);
private static final Pattern DUPLICATE_ENTRY_PATTERN = Pattern.compile(
"Duplicate entry '([^']+)' for key '([^']+)'",
Pattern.CASE_INSENSITIVE);
private static final Pattern CONSTRAINT_VIOLATION_PATTERN = Pattern.compile(
"cannot be null|Column '([^']+)' cannot be null",
Pattern.CASE_INSENSITIVE);
private static final Pattern INVALID_DECIMAL_PATTERN = Pattern.compile(
"Out of range value for column '([^']+)' at row (\\d+)",
Pattern.CASE_INSENSITIVE);
/**
* 获取根错误信息
*
* @param e 异常
* @return 根错误信息
*/
public static String getRootErrorMessage(Throwable e) {
Throwable cause = e;
while (cause.getCause() != null) {
cause = cause.getCause();
}
return cause.getMessage();
}
/**
* 获取用户友好的错误信息
* 将技术性的数据库错误转换为易于理解的提示
*
* @param e 异常
* @return 用户友好的错误信息
*/
public static String getUserFriendlyErrorMessage(Throwable e) {
String rootMessage = getRootErrorMessage(e);
if (rootMessage == null || rootMessage.trim().isEmpty()) {
return "数据处理失败";
}
// 尝试解析和转换常见的数据库错误
String friendlyMessage = parseDataTooLongError(rootMessage);
if (friendlyMessage != null) {
return friendlyMessage;
}
friendlyMessage = parseDuplicateEntryError(rootMessage);
if (friendlyMessage != null) {
return friendlyMessage;
}
friendlyMessage = parseConstraintViolationError(rootMessage);
if (friendlyMessage != null) {
return friendlyMessage;
}
friendlyMessage = parseInvalidDecimalError(rootMessage);
if (friendlyMessage != null) {
return friendlyMessage;
}
// 如果无法识别特定错误类型,返回简化但有用的信息
return extractMeaningfulError(rootMessage);
}
/**
* 解析"数据过长"错误
*/
private static String parseDataTooLongError(String errorMessage) {
Matcher matcher = DATA_TOO_LONG_PATTERN.matcher(errorMessage);
if (matcher.find()) {
String columnName = matcher.group(1);
String rowNumber = matcher.group(2);
return String.format("第%s行字段\"%s\"内容过长,请减少字符数量",
rowNumber, columnName);
}
return null;
}
/**
* 解析"重复条目"错误
*/
private static String parseDuplicateEntryError(String errorMessage) {
Matcher matcher = DUPLICATE_ENTRY_PATTERN.matcher(errorMessage);
if (matcher.find()) {
String duplicateValue = matcher.group(1);
return String.format("数据\"%s\"已存在,请修改为其他值", duplicateValue);
}
return null;
}
/**
* 解析"约束违反"错误(如非空约束)
*/
private static String parseConstraintViolationError(String errorMessage) {
Matcher matcher = CONSTRAINT_VIOLATION_PATTERN.matcher(errorMessage);
if (matcher.find()) {
String columnName = matcher.group(1);
if (columnName != null) {
return String.format("字段\"%s\"为必填项,请填写内容", columnName);
} else {
return "存在必填字段未填写,请检查并补充";
}
}
return null;
}
/**
* 解析"数值超出范围"错误
*/
private static String parseInvalidDecimalError(String errorMessage) {
Matcher matcher = INVALID_DECIMAL_PATTERN.matcher(errorMessage);
if (matcher.find()) {
String columnName = matcher.group(1);
String rowNumber = matcher.group(2);
return String.format("第%s行字段\"%s\"数值过大,请填写较小的数字",
rowNumber, columnName);
}
return null;
}
/**
* 提取有意义的错误信息
* 保留关键信息,去掉技术细节
*/
private static String extractMeaningfulError(String errorMessage) {
// 移除技术前缀和无用信息
String cleaned = errorMessage
.replaceAll("^(java\\.lang\\.|com\\.mysql\\.|org\\.springframework\\.|com\\.mysql\\.cj\\.).*?:", "")
.replaceAll(
"^(SQLException|DataAccessException|RuntimeException|MySQLIntegrityConstraintViolationException):",
"")
.replaceAll("^.*?Exception:", "")
.replaceAll("\\b(MySQL|Doris|JDBC|SQL|database|table|constraint|varchar|decimal|int|bigint)\\b", "")
.replaceAll("\\s+", " ")
.trim();
// 如果清理后还有有用信息且不包含过多技术术语,保留它
if (cleaned.length() >= 15 && !containsOnlyTechnicalJargon(cleaned)) {
// 进一步优化常见的错误信息
cleaned = optimizeCommonErrors(cleaned);
return cleaned;
}
// 否则返回通用但有指导意义的提示
return "数据格式不正确,请检查输入内容";
}
/**
* 优化常见错误信息
*/
private static String optimizeCommonErrors(String message) {
String lower = message.toLowerCase();
if (lower.contains("too long") || lower.contains("length")) {
return "内容长度超出限制,请缩短文本";
}
if (lower.contains("duplicate") || lower.contains("already exists")) {
return "数据重复,请修改为不同的值";
}
if (lower.contains("null") || lower.contains("empty")) {
return "必填字段未填写,请补充完整";
}
if (lower.contains("format") || lower.contains("invalid")) {
return "数据格式错误,请检查并修正";
}
if (lower.contains("number") || lower.contains("numeric")) {
return "数字格式错误,请输入有效数字";
}
if (lower.contains("date") || lower.contains("time")) {
return "日期格式错误,请使用正确的日期格式";
}
return message;
}
/**
* 检查是否只包含技术术语
*/
private static boolean containsOnlyTechnicalJargon(String message) {
String[] technicalOnlyTerms = {
"syntax error", "connection", "timeout", "deadlock", "transaction",
"foreign key", "primary key", "index", "timestamp", "datetime",
"rollback", "commit", "lock", "session", "driver"
};
String lowerMessage = message.toLowerCase();
for (String term : technicalOnlyTerms) {
if (lowerMessage.contains(term)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,314 @@
package com.xspaceagi.compose.spec.utils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.util.StringUtils;
import com.xspaceagi.compose.sdk.vo.doris.DorisTableDefinitionVo;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.create.table.CreateTable;
@Slf4j
public class DDLSqlParseUtil {
/**
* 解析建表语句,提取表的基本信息 (使用 JSqlParser 结合字符串处理)
*
* @param createTableDdl SHOW CREATE TABLE 返回的 DDL 字符串
* @param definition 待填充的 DorisTableDefinitionVo 对象
*/
public static void parseCreateTableDdl(String createTableDdl, DorisTableDefinitionVo definition) {
if (!StringUtils.hasText(createTableDdl)) {
log.warn("createTableDdl is empty, cannot parse.");
return;
}
try {
Statement statement = CCJSqlParserUtil.parse(createTableDdl);
if (statement instanceof CreateTable createTable) {
// 1. 使用 JSqlParser 提取表名 (主要是验证) 和标准选项
// definition.setTable(createTable.getTable().getName()); // 通常由调用者设置
List<String> options = createTable.getTableOptionsStrings();
if (options != null) {
boolean commentFound = false;
boolean engineFound = false;
for (int i = 0; i < options.size(); i++) {
String option = options.get(i).toUpperCase();
// 提取 COMMENT (标准方式)
if (!commentFound && option.equals("COMMENT") && i + 1 < options.size()) {
// JSqlParser 通常会将带引号的值作为下一个元素
String comment = options.get(i + 1);
// 去除可能的引号
if (comment.startsWith("'") && comment.endsWith("'")) {
comment = comment.substring(1, comment.length() - 1);
}
definition.setComment(comment);
commentFound = true;
i++; // 跳过值
}
// 提取 ENGINE (标准方式) - Doris 通常是 ENGINE=OLAP可能不在此处
else if (!engineFound && option.equals("ENGINE") && i + 2 < options.size()
&& options.get(i + 1).equals("=")) {
definition.setEngine(options.get(i + 2));
engineFound = true;
i += 2; // 跳过 '=' 和值
}
// JSqlParser v5 might parse ENGINE=OLAP directly in some cases
else if (!engineFound && option.startsWith("ENGINE=")) {
definition.setEngine(option.substring(7).trim());
engineFound = true;
}
}
if (!commentFound) {
log.debug("JSqlParser: no COMMENT in TableOptions, falling back to string parse.");
}
if (!engineFound) {
log.debug("JSqlParser: no ENGINE in TableOptions, falling back to string parse.");
}
} else {
log.debug("JSqlParser: TableOptions not found.");
}
// JSqlParser 提取列定义 (主要用于未来扩展,当前 V_o 不存储详细列信息)
/* ... (注释掉的代码) ... */
// JSqlParser 提取索引 (可能不适用于 Doris 的 DUPLICATE/AGGREGATE KEY)
/* ... (注释掉的代码) ... */
} else {
log.warn("Statement is not CREATE TABLE: {}", statement.getClass().getName());
// 如果不是 CreateTable后续的字符串解析可能也无效
// 但仍然尝试字符串解析,以防是其他 CREATE 语句变体
}
} catch (JSQLParserException e) {
log.warn("JSqlParser DDL parse failed: {}, full string fallback.", e.getMessage());
// 不再抛出异常,继续尝试字符串解析
} catch (Exception e) {
log.warn("JSqlParser unexpected error: {}, full string fallback.", e.getMessage(), e);
// 不再抛出异常,继续尝试字符串解析
}
// 2. 使用字符串处理补充或覆盖 JSqlParser 未能解析的 Doris 特定信息
// (即使 JSqlParser 成功,也执行这部分以确保 Doris 特有信息被提取)
// 提取表注释 (覆盖或补充)
try {
// 使用更健壮的查找方式,匹配 COMMENT '...' 后跟换行或 PROPERTIES
int commentIndex = createTableDdl.indexOf("COMMENT '");
int propertiesIndexForComment = createTableDdl.indexOf("\nPROPERTIES");
int newLineAfterComment = createTableDdl.indexOf("'\n", commentIndex + 9); // 查找 COMMENT '...' \n
int endQuoteIndex = -1;
if (commentIndex > 0) {
if (newLineAfterComment > commentIndex + 8) { // 优先匹配 ' 后直接换行的
endQuoteIndex = newLineAfterComment;
} else if (propertiesIndexForComment > commentIndex + 8) { // 其次匹配 ' 后跟 PROPERTIES 的
int quoteBeforeProp = createTableDdl.lastIndexOf("'", propertiesIndexForComment -1);
if (quoteBeforeProp > commentIndex + 8) {
endQuoteIndex = quoteBeforeProp;
}
} else { // 最后尝试普通匹配 '...') 或 '...'
int endParenIndex = createTableDdl.indexOf("')", commentIndex + 9);
int endAloneIndex = createTableDdl.indexOf("'", commentIndex + 9);
if (endParenIndex > 0 && (endAloneIndex == -1 || endParenIndex < endAloneIndex)) {
endQuoteIndex = endParenIndex;
} else {
endQuoteIndex = endAloneIndex;
}
}
if (endQuoteIndex > commentIndex + 8) {
definition.setComment(createTableDdl.substring(commentIndex + 9, endQuoteIndex));
} else if (definition.getComment() == null){ // 只有当 JSqlParser 也没解析到时才告警
log.warn("String parse: cannot parse table comment. DDL: {}", createTableDdl);
}
} else if (definition.getComment() == null) {
log.warn("String parse: COMMENT not found. DDL: {}", createTableDdl);
}
} catch (Exception e) {
log.warn("String parse table comment error: {}", e.getMessage());
}
// 提取表引擎 (覆盖或补充)
try {
// Doris 通常是 ENGINE=OLAP 后跟换行
String enginePattern = "ENGINE=OLAP";
int engineIndex = createTableDdl.indexOf(enginePattern);
if (engineIndex > 0) {
// 确认后面是换行符或空格
int engineEndIndex = engineIndex + enginePattern.length();
if (engineEndIndex >= createTableDdl.length() ||
createTableDdl.charAt(engineEndIndex) == '\n' ||
createTableDdl.charAt(engineEndIndex) == ' ' ) {
definition.setEngine("OLAP");
} else if (definition.getEngine() == null) {
log.warn("String parse: ENGINE=OLAP but unexpected tail. DDL: {}", createTableDdl);
}
} else if (definition.getEngine() == null){
log.debug("String parse: no ENGINE=OLAP, trying MySQL engine. DDL: {}", createTableDdl);
// 尝试解析 ENGINE = MySQL 引擎
int mysqlEngineIndex = createTableDdl.indexOf("ENGINE=");
if (mysqlEngineIndex > 0) {
int engineEndIndex = createTableDdl.indexOf("\n", mysqlEngineIndex);
if (engineEndIndex == -1) engineEndIndex = createTableDdl.indexOf(" ", mysqlEngineIndex);
if (engineEndIndex == -1) engineEndIndex = createTableDdl.length();
if(engineEndIndex > mysqlEngineIndex + 7) {
definition.setEngine(createTableDdl.substring(mysqlEngineIndex + 7, engineEndIndex).trim());
log.info("Parsed MySQL engine: {}", definition.getEngine());
}
}
}
} catch (Exception e) {
log.warn("String parse table engine error: {}", e.getMessage());
}
// 提取分桶数
try {
int bucketsIndex = createTableDdl.indexOf("BUCKETS ");
if (bucketsIndex > 0) {
int bucketsEndIndex = createTableDdl.indexOf("\n", bucketsIndex);
if (bucketsEndIndex == -1) {
bucketsEndIndex = createTableDdl.indexOf("PROPERTIES", bucketsIndex);
if (bucketsEndIndex == -1) {
bucketsEndIndex = createTableDdl.length();
}
}
if (bucketsEndIndex > bucketsIndex + 8) {
String bucketsStr = createTableDdl.substring(bucketsIndex + 8, bucketsEndIndex).trim();
try {
definition.setBuckets(Integer.parseInt(bucketsStr));
} catch (NumberFormatException nfe) {
log.warn("String parse: invalid bucket count '{}'. DDL: {}", bucketsStr, createTableDdl);
}
} else {
log.warn("String parse: bucket count end not found. DDL: {}", createTableDdl);
}
}
} catch (Exception e) {
log.warn("String parse bucket count error: {}", e.getMessage());
}
// 提取表属性 (包括副本数)
try {
Map<String, String> properties = new HashMap<>();
int propertiesIndex = createTableDdl.indexOf("PROPERTIES (");
if (propertiesIndex > 0) {
int propertiesEndIndex = createTableDdl.lastIndexOf(")"); // 找到最后一个 ')' 作为结束
if (propertiesEndIndex > propertiesIndex + 11) {
String propertiesStr = createTableDdl.substring(propertiesIndex + 12, propertiesEndIndex).trim();
for (String property : propertiesStr.split(",")) {
String trimmedProperty = property.trim();
if (StringUtils.hasText(trimmedProperty)) {
// 处理 key = value 对,去除引号
int equalsIndex = trimmedProperty.indexOf("=");
if (equalsIndex > 0) {
String key = trimmedProperty.substring(0, equalsIndex).trim().replace("\"", "");
String value = trimmedProperty.substring(equalsIndex + 1).trim().replace("\"", "");
if (StringUtils.hasText(key)) {
properties.put(key, value);
// 特别提取副本数
if ("replication_num".equalsIgnoreCase(key)) {
try {
definition.setReplicationNum(Integer.parseInt(value));
} catch (NumberFormatException nfe) {
log.warn("String parse: invalid replication_num '{}'.", value);
}
}
}
} else {
log.warn("String parse: invalid PROPERTIES attr format: {}", trimmedProperty);
}
}
}
} else {
log.warn("String parse: PROPERTIES block invalid. DDL: {}", createTableDdl);
}
}
if (!properties.isEmpty()) {
definition.setProperties(properties);
}
} catch (Exception e) {
log.warn("String parse PROPERTIES error: {}", e.getMessage());
}
// 提取分布键
try {
String distributedPattern = "DISTRIBUTED BY HASH(";
int distributedIndex = createTableDdl.indexOf(distributedPattern);
if (distributedIndex > 0) {
int distributedEndIndex = createTableDdl.indexOf(")", distributedIndex + distributedPattern.length());
if (distributedEndIndex > distributedIndex + distributedPattern.length()) {
String keysStr = createTableDdl
.substring(distributedIndex + distributedPattern.length(), distributedEndIndex)
.replace("`", "");
String[] keys = keysStr.split(",");
List<String> keyList = Arrays.stream(keys).map(String::trim).filter(StringUtils::hasText)
.collect(Collectors.toList());
if (!keyList.isEmpty()) {
definition.setDistributedKeys(keyList);
} else {
log.warn("String parse: empty distribution keys. Keys: '{}', DDL: {}", keysStr, createTableDdl);
}
} else {
log.warn("String parse: distribution keys end not found. DDL: {}", createTableDdl);
}
}
} catch (Exception e) {
log.warn("String parse distribution keys error: {}", e.getMessage());
}
// 提取重复键/聚合键/唯一键 (Doris 支持多种 Key 类型)
try {
String keyPattern = null;
int keyIndex = -1;
int keyNameLength = 0;
if ((keyIndex = createTableDdl.indexOf("DUPLICATE KEY(")) > 0) {
keyPattern = "DUPLICATE KEY(";
keyNameLength = keyPattern.length();
} else if ((keyIndex = createTableDdl.indexOf("AGGREGATE KEY(")) > 0) {
keyPattern = "AGGREGATE KEY(";
keyNameLength = keyPattern.length();
} else if ((keyIndex = createTableDdl.indexOf("UNIQUE KEY(")) > 0) {
keyPattern = "UNIQUE KEY(";
keyNameLength = keyPattern.length();
}
if (keyPattern != null && keyIndex > 0) {
int keyEndIndex = createTableDdl.indexOf(")", keyIndex + keyNameLength);
if (keyEndIndex > keyIndex + keyNameLength) {
String keysStr = createTableDdl.substring(keyIndex + keyNameLength, keyEndIndex).replace("`", "");
String[] keys = keysStr.split(",");
List<String> keyList = Arrays.stream(keys).map(String::trim).filter(StringUtils::hasText)
.collect(Collectors.toList());
if (!keyList.isEmpty()) {
// 统一存到 DuplicateKeys 里,或者需要扩展 V_o 以区分 Key 类型
definition.setDuplicateKeys(keyList);
} else {
log.warn("String parse: {} is empty. Keys: '{}', DDL: {}", keyPattern, keysStr, createTableDdl);
}
} else {
log.warn("String parse: cannot parse {}: end paren not found. DDL: {}", keyPattern, createTableDdl);
}
}
} catch (Exception e) {
log.warn("String parse table key error: {}", e.getMessage());
}
}
}