JS中执行动态SQL
根据请求参数,动态形成SQL 废话不多说,上代码
源码
js
// 配置参数
const CONFIG = {
dataSourceName: "mysql",// 数据源名称 - 根据您的实际情况调整
tableName: "area",// 表名
allowedFields: ["id", "name", "area_code", "city_code", "lat",
"lon", "level", "parent_id", "per_pin_yin", "pin_yin", "simple_py",
"whole_name", "zip_code"],// 允许查询的字段白名单 - 防止SQL注入
defaultPage: { pageNum: 1, pageSize: 10 },// 默认分页配置
maxPageSize: 100,// 最大每页数量限制
timeout: 30000 // 30秒 // 超时设置(毫秒)
};
console.log("=== 开始执行动态字段查询API(带分页和超时) ===");
console.log("配置信息:", JSON.stringify(CONFIG, null, 2));
try {
// 设置超时时间
ctx.options.timeout = CONFIG.timeout;
console.log(`设置请求超时时间为: ${CONFIG.timeout}ms`);
// 1. 获取请求参数中的meta字段
const requestBody = ctx.request.body || {};
const meta = requestBody.meta || {};
const env = meta.env || ""; // 环境参数
const fieldParam = meta.field || ""; // 动态字段参数
const pageParam = meta.page || null; // 分页参数
console.log("请求参数:", {
env: env,
field: fieldParam,
page: pageParam,
fullBody: requestBody
});
// 2. 处理分页参数
let pageConfig = null;
if (pageParam && typeof pageParam === 'object') {
const pageNum = parseInt(pageParam.pageNum) || CONFIG.defaultPage.pageNum;
let pageSize = parseInt(pageParam.pageSize) || CONFIG.defaultPage.pageSize;
// 限制每页最大数量
if (pageSize > CONFIG.maxPageSize) {
console.warn(`pageSize ${pageSize} 超过最大限制 ${CONFIG.maxPageSize},已自动调整`);
pageSize = CONFIG.maxPageSize;
}
// 验证分页参数
if (pageNum < 1 || pageSize < 1) {
console.warn("无效的分页参数,使用默认分页配置");
pageConfig = CONFIG.defaultPage;
} else {
pageConfig = { pageNum, pageSize };
}
} else {
// 如果没有提供分页参数,使用默认分页
pageConfig = CONFIG.defaultPage;
}
console.log("分页配置:", pageConfig);
// 3. 获取所有可用的数据源名称,用于调试
const allDataSourceNames = ctx.dsHelper.getDataSourceNames();
console.log("当前网关下所有可用数据源:", allDataSourceNames);
// 4. 验证数据源是否存在
if (!allDataSourceNames.includes(CONFIG.dataSourceName)) {
console.error(`配置的数据源 "${CONFIG.dataSourceName}" 不存在`);
console.log("建议使用的数据源名称:", allDataSourceNames.join(', '));
ctx.resultObj.err = {
status: 400,
message: `数据源 "${CONFIG.dataSourceName}" 不存在。可用数据源: ${allDataSourceNames.join(', ')}`
};
return;
}
// 5. 处理动态字段 - 安全验证
let selectFields = "*";
let isCustomField = false;
if (fieldParam && typeof fieldParam === 'string' && fieldParam.trim()) {
// 分割字段并去重、去空格
const fieldArray = fieldParam.split(',')
.map(field => field.trim().toLowerCase()) // 统一转为小写进行验证
.filter(field => field)
.filter((value, index, self) => self.indexOf(value) === index);
console.log("原始字段列表:", fieldArray);
// 验证字段是否在白名单中(不区分大小写)
const validFields = [];
const invalidFields = [];
fieldArray.forEach(field => {
// 在白名单中查找匹配的字段(不区分大小写)
const matchedField = CONFIG.allowedFields.find(allowed =>
allowed.toLowerCase() === field.toLowerCase()
);
if (matchedField) {
validFields.push(matchedField); // 使用白名单中的原始字段名
} else {
invalidFields.push(field);
}
});
console.log("有效字段:", validFields);
console.log("无效字段:", invalidFields);
if (invalidFields.length > 0) {
console.warn(`检测到无效字段: ${invalidFields.join(', ')}`);
// 如果没有有效字段,使用默认字段
if (validFields.length === 0) {
console.warn("没有有效字段,使用默认字段 *");
}
}
if (validFields.length > 0) {
selectFields = validFields.join(', ');
isCustomField = true;
}
}
// 6. 构建SQL语句
const sql = `SELECT ${selectFields} FROM ${CONFIG.tableName}`;
console.log("构建的SQL语句:", sql);
console.log("是否使用自定义字段:", isCustomField);
console.log("分页配置:", pageConfig);
// 7. 获取数据源引擎
console.log(`尝试获取数据源引擎: dsName="${CONFIG.dataSourceName}", envName="${env}"`);
const resultEng = ctx.dsHelper.getDataSourceEngine(CONFIG.dataSourceName, env);
if (resultEng.err) {
console.error("获取数据源引擎失败:", resultEng.err);
// 尝试不指定环境获取引擎
console.log("尝试不指定环境获取引擎...");
const resultEngNoEnv = ctx.dsHelper.getDataSourceEngine(CONFIG.dataSourceName, "");
if (resultEngNoEnv.err) {
console.error("不指定环境获取引擎也失败:", resultEngNoEnv.err);
ctx.resultObj.err = {
status: 500,
message: `获取数据源引擎失败: ${resultEng.err || resultEngNoEnv.err}`
};
return;
}
resultEng.eng = resultEngNoEnv.eng;
console.log("不指定环境获取引擎成功");
}
console.log("成功获取数据源引擎");
// 8. 测试数据库连接
console.log("测试数据库连接...");
const resultTest = await resultEng.eng.testConnect();
if (resultTest.err) {
console.error("数据库连接失败:", resultTest.err);
ctx.resultObj.err = {
status: 503,
message: `数据库连接失败: ${resultTest.err}`
};
return;
}
console.log("数据库连接成功");
// 9. 执行SQL查询
console.log(`执行SQL: ${sql}`);
const sqlObj = {
sql: sql,
// 添加分页配置
page: pageConfig,
// 限制返回行数,防止数据量过大(分页时这个参数限制每页最大行数)
limitRow: CONFIG.maxPageSize,
// 不返回列信息,提高性能
colInfo: 0
};
const resultExec = await resultEng.eng.execSqlObjs(sqlObj);
if (resultExec.err) {
console.error("SQL执行失败:", resultExec.err);
// 如果是字段不存在的错误,提供更友好的错误信息
if (resultExec.err.includes('Unknown column') || resultExec.err.includes('不存在')) {
ctx.resultObj.err = {
status: 400,
message: `SQL执行失败: 字段不存在。可用字段: ${CONFIG.allowedFields.join(', ')}`
};
} else if (resultExec.err.includes('timeout') || resultExec.err.includes('超时')) {
ctx.resultObj.err = {
status: 504,
message: `SQL执行超时(${CONFIG.timeout/1000}秒),请优化查询条件或联系管理员`
};
} else {
ctx.resultObj.err = {
status: 500,
message: `SQL执行失败: ${resultExec.err}`
};
}
return;
}
const resultData = resultExec.result;
const metaInfo = resultData.meta || {};
const rows = resultData.rows || [];
const totalCount = metaInfo.totalCount || rows.length;
const thisCount = metaInfo.thisCount || rows.length;
console.log("SQL执行成功,返回数据行数:", {
thisCount: thisCount,
totalCount: totalCount,
pageMode: metaInfo.pageMode
});
// 10. 构造返回结果
ctx.resultObj.result = {
data: resultData,
code: 200,
message: "查询成功"
};
console.log("=== API执行完成 ===");
} catch (error) {
console.error("执行过程中发生异常:", error);
// 处理不同类型的错误
let errorMessage = error.message || error.toString();
let statusCode = 500;
if (errorMessage.includes('timeout') || errorMessage.includes('超时')) {
statusCode = 504;
errorMessage = `请求超时(${CONFIG.timeout/1000}秒),请稍后重试`;
} else if (errorMessage.includes('connect') || errorMessage.includes('连接')) {
statusCode = 503;
errorMessage = "数据库连接失败";
} else if (errorMessage.includes('ECONNREFUSED')) {
statusCode = 503;
errorMessage = "数据库连接被拒绝,请检查数据库状态";
}
ctx.resultObj.err = {
status: statusCode,
message: `系统异常: ${errorMessage}`
};
}请求参数如下
json
{
"meta": {
"env": "dev",
"field": "id,name,lat,lon",
"page": {
"pageNum": 1,
"pageSize": 100
}
}
}执行结果
