mirror of
https://github.com/alibaba/canal.git
synced 2026-11-04 00:30:15 +08:00
修复rest etl 存在SQL注入风险 (#1685)
* kafka 模式默认开启事务异步发送 * fix adapter etl rest sql inject bug
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
package com.alibaba.otter.canal.client.adapter.support;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.google.common.base.Joiner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public abstract class AbstractEtlService {
|
||||
|
||||
protected Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private String type;
|
||||
private AdapterConfig config;
|
||||
|
||||
public AbstractEtlService(String type, AdapterConfig config){
|
||||
this.type = type;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
protected EtlResult importData(String sql, List<String> params) {
|
||||
EtlResult etlResult = new EtlResult();
|
||||
AtomicLong impCount = new AtomicLong();
|
||||
List<String> errMsg = new ArrayList<>();
|
||||
if (config == null) {
|
||||
logger.warn("{} mapping config is null, etl go end ", type);
|
||||
etlResult.setErrorMessage(type + "mapping config is null, etl go end ");
|
||||
return etlResult;
|
||||
}
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
|
||||
|
||||
List<Object> values = new ArrayList<>();
|
||||
// 拼接条件
|
||||
if (config.getMapping().getEtlCondition() != null && params != null) {
|
||||
String etlCondition = config.getMapping().getEtlCondition();
|
||||
int size = params.size();
|
||||
for (String param : params) {
|
||||
etlCondition = etlCondition.replace("{}", "?");
|
||||
values.add(param);
|
||||
}
|
||||
|
||||
sql += " " + etlCondition;
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("etl sql : {}", sql);
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
|
||||
long cnt = (Long) Util.sqlRS(dataSource, countSql, values, rs -> {
|
||||
Long count = null;
|
||||
try {
|
||||
if (rs.next()) {
|
||||
count = ((Number) rs.getObject(1)).longValue();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
return count == null ? 0L : count;
|
||||
});
|
||||
|
||||
// 当大于1万条记录时开启多线程
|
||||
if (cnt >= 10000) {
|
||||
int threadCount = 3; // 从配置读取默认为3
|
||||
long perThreadCnt = cnt / threadCount;
|
||||
ExecutorService executor = Util.newFixedThreadPool(threadCount, 5000L);
|
||||
List<Future<Boolean>> futures = new ArrayList<>(threadCount);
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
long offset = i * perThreadCnt;
|
||||
Long size = null;
|
||||
if (i != threadCount - 1) {
|
||||
size = perThreadCnt;
|
||||
}
|
||||
String sqlFinal;
|
||||
if (size != null) {
|
||||
sqlFinal = sql + " LIMIT " + offset + "," + size;
|
||||
} else {
|
||||
sqlFinal = sql + " LIMIT " + offset + "," + cnt;
|
||||
}
|
||||
Future<Boolean> future = executor.submit(
|
||||
() -> executeSqlImport(dataSource, sqlFinal, values, config.getMapping(), impCount, errMsg));
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
for (Future<Boolean> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
executor.shutdown();
|
||||
} else {
|
||||
executeSqlImport(dataSource, sql, values, config.getMapping(), impCount, errMsg);
|
||||
}
|
||||
|
||||
logger.info("数据全量导入完成, 一共导入 {} 条数据, 耗时: {}", impCount.get(), System.currentTimeMillis() - start);
|
||||
etlResult.setResultMessage("导入" + type + " 数据:" + impCount.get() + " 条");
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
errMsg.add(type + " 数据导入异常 =>" + e.getMessage());
|
||||
}
|
||||
if (errMsg.isEmpty()) {
|
||||
etlResult.setSucceeded(true);
|
||||
} else {
|
||||
etlResult.setErrorMessage(Joiner.on("\n").join(errMsg));
|
||||
}
|
||||
return etlResult;
|
||||
}
|
||||
|
||||
protected abstract boolean executeSqlImport(DataSource ds, String sql, List<Object> values,
|
||||
AdapterConfig.AdapterMapping mapping, AtomicLong impCount,
|
||||
List<String> errMsg);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.alibaba.otter.canal.client.adapter.support;
|
||||
|
||||
public interface AdapterConfig {
|
||||
String getDataSourceKey();
|
||||
|
||||
AdapterMapping getMapping();
|
||||
|
||||
interface AdapterMapping {
|
||||
String getEtlCondition();
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,11 @@ package com.alibaba.otter.canal.client.adapter.support;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.*;
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
@@ -37,9 +35,9 @@ public class Util {
|
||||
*/
|
||||
public static Object sqlRS(DataSource ds, String sql, Function<ResultSet, Object> fun) {
|
||||
try (Connection conn = ds.getConnection();
|
||||
Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);) {
|
||||
Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
|
||||
stmt.setFetchSize(Integer.MIN_VALUE);
|
||||
try (ResultSet rs = stmt.executeQuery(sql);) {
|
||||
try (ResultSet rs = stmt.executeQuery(sql)) {
|
||||
return fun.apply(rs);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -48,6 +46,26 @@ public class Util {
|
||||
}
|
||||
}
|
||||
|
||||
public static Object sqlRS(DataSource ds, String sql, List<Object> values, Function<ResultSet, Object> fun) {
|
||||
try (Connection conn = ds.getConnection()) {
|
||||
try (PreparedStatement pstmt = conn
|
||||
.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
|
||||
pstmt.setFetchSize(Integer.MIN_VALUE);
|
||||
if (values != null) {
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
pstmt.setObject(i + 1, values.get(i));
|
||||
}
|
||||
}
|
||||
try (ResultSet rs = pstmt.executeQuery()) {
|
||||
return fun.apply(rs);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("sqlRs has error, sql: {} ", sql);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sql执行获取resultSet
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user