Skip to content

Commit 758b32a

Browse files
committed
Java:MultiDataSource 中 SQLAuto: 强校验 WHERE LIMIT 及 affected row count 超过上限自动回滚
1 parent 973ae99 commit 758b32a

1 file changed

Lines changed: 218 additions & 14 deletions

File tree

‎APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoController.java‎

Lines changed: 218 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,7 @@
4444
import java.net.URLEncoder;
4545
import java.nio.charset.StandardCharsets;
4646
import java.rmi.ServerException;
47-
import java.sql.PreparedStatement;
48-
import java.sql.ResultSet;
49-
import java.sql.ResultSetMetaData;
50-
import java.sql.Statement;
47+
import java.sql.*;
5148
import java.util.*;
5249
import java.util.Map.Entry;
5350
import java.util.concurrent.TimeoutException;
@@ -2315,6 +2312,8 @@ public String execute(@RequestBody String request, HttpSession session) {
23152312
if (StringUtil.isEmpty(database, true) && StringUtil.isEmpty(uri, true)) {
23162313
throw new NullPointerException("database 和 uri 不能同时为空!至少其中一个要传有效值!");
23172314
}
2315+
2316+
String datasource = req.getString("datasource");
23182317
String schema = req.getString("schema");
23192318

23202319
String account = req.getString("account");
@@ -2355,12 +2354,130 @@ public String execute(@RequestBody String request, HttpSession session) {
23552354
}
23562355

23572356
String trimmedSQL = sql.trim();
2358-
23592357
String sqlPrefix = trimmedSQL.length() < 7 ? "" : trimmedSQL.substring(0, 7).toUpperCase();
23602358
boolean isInsert = sqlPrefix.startsWith("INSERT ");
2361-
boolean isWrite = isInsert || sqlPrefix.startsWith("UPDATE ") || sqlPrefix.startsWith("DELETE ");
2359+
boolean isEdit = (! isInsert) && sqlPrefix.startsWith("UPDATE ");
2360+
boolean isWrite = isInsert || isEdit || sqlPrefix.startsWith("DELETE ");
2361+
2362+
DemoSQLConfig config = new DemoSQLConfig();
2363+
config.setDatabase(database);
2364+
config.setDatasource(datasource);
2365+
config.setDBUri(uri);
2366+
config.setSchema(schema);
2367+
23622368
if (WRITE_STRICTLY && isWrite && ! isInsert) {
2363-
// 只包含筛选出 apijson 字段的 Map<String, SortedMap<Integer, Map<String, Object>>> documemtMap = DemoVerifier.DOCUMENT_MAP;
2369+
int fromInd = isEdit ? 0 : findKeyIndex(trimmedSQL, "FROM");
2370+
if (fromInd < 0) {
2371+
throw new IllegalArgumentException("SQL 缺 FORM 关键词!");
2372+
}
2373+
2374+
String sqlRest = trimmedSQL.substring(fromInd + (isEdit ? "UPDATE " : " FROM ").length()).trim();
2375+
int blankRest = sqlRest.indexOf(" ");
2376+
if (blankRest < 0) {
2377+
throw new IllegalArgumentException("SQL 缺少 表名!");
2378+
}
2379+
2380+
String tblPath = sqlRest.substring(0, blankRest).trim();
2381+
sqlRest = sqlRest.substring(blankRest + 1).trim();
2382+
String[] tblArr = tblPath.indexOf(".") >= 0 ? tblPath.split("\\.") : new String[]{tblPath}; // StringUtil.split(tblPath.Pattern.quota("."), true);
2383+
int len = tblArr == null ? 0 : tblArr.length;
2384+
String sch = len < 2 ? null : tblArr[len - 2];
2385+
if (StringUtil.isNotEmpty(sch)){
2386+
if ((sch.startsWith("`") && sch.endsWith("`")) || (sch.startsWith("[") && sch.endsWith("]")) || (sch.startsWith("\"") && sch.endsWith("\""))) {
2387+
sch = sch.substring(1, sch.length() - 1);
2388+
}
2389+
2390+
if (StringUtil.isNotEmpty(sch)) {
2391+
if (StringUtil.isNotEmpty(schema) && ! schema.equals(sch)) {
2392+
throw new ConflictException("传参 schema: " + schema + " 和 SQL 中" + tblPath + " 里的" + sch + "不一致!");
2393+
}
2394+
2395+
config.setSchema(sch);
2396+
}
2397+
}
2398+
2399+
String tbl = len < 1 ? null : tblArr[len - 1];
2400+
if ((tbl.startsWith("`") && tbl.endsWith("`")) || (tbl.startsWith("[") & tbl.endsWith("]")) || (tbl.startsWith("\"") && tbl.endsWith("\""))) {
2401+
tbl = tbl.substring(1, tbl.length() -1);
2402+
}
2403+
2404+
config.setTable(tbl);
2405+
String idKey = config.getIdKey();
2406+
String userIdKey = config.getUserIdKey();
2407+
int whereInd = findKeyIndex(sqlRest, "WHERE");
2408+
if (whereInd < 0) {
2409+
throw new IllegalArgumentException("SQL 缺少 WHERE 关键词!");
2410+
}
2411+
2412+
sqlRest = sqlRest.substring(whereInd + " WHERE ".length()).trim();
2413+
String key;
2414+
if (sqlRest.startsWith(idKey)) {
2415+
key = idKey;
2416+
} else if (sqlRest.startsWith("`" + idKey + "`") || sqlRest.startsWith("[" + idKey + "]") || sqlRest.startsWith("\"" + idKey + "\"")) {
2417+
key = "`" + idKey + "`";
2418+
} else if (sqlRest.startsWith(userIdKey)) {
2419+
key = userIdKey;
2420+
} else if (sqlRest.startsWith("`" + userIdKey + "`") || sqlRest.startsWith("[" + userIdKey + "]") || sqlRest.startsWith("\"" + userIdKey + "\"")) {
2421+
key = "`" + userIdKey + "`";
2422+
} else {
2423+
throw new IllegalArgumentException("SQL WHERE 后必须接着 "+ idKey + " 或 " + idKey + " = ? 或 IN(?,?..) !");
2424+
}
2425+
2426+
sqlRest = sqlRest.substring(key. length()).trim();
2427+
boolean isEq = sqlRest.startsWith("=");
2428+
if (! (isEq || sqlRest.startsWith("IN(") || sqlRest.startsWith("in("))) {
2429+
throw new IllegalArgumentException("SQL WHERE "+ key + " 后必须接着 = ? 或 IN(?, ? ...) ! ");
2430+
}
2431+
2432+
sqlRest = sqlRest.substring(isEq ? 1 : "IN(".length()).trim();
2433+
int endInd = isEq && ! sqlRest.startsWith("'") ? -1 : sqlRest.indexOf(isEq ? "'" : ")", 1);
2434+
if (isEq && ! sqlRest.startsWith("'")) {
2435+
for (int i = 0; i < sqlRest.length(); i++) {
2436+
char c = sqlRest.charAt(i);
2437+
if (c == '+' || c == '-' || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
2438+
continue;
2439+
}
2440+
2441+
endInd = i;
2442+
}
2443+
} else {
2444+
endInd = sqlRest.indexOf(isEq ? "'" : ")", isEq ? 1 : 0);
2445+
}
2446+
2447+
sqlRest = endInd < 0 ? sqlRest : sqlRest.substring(endInd + 1).trim();
2448+
if (sqlRest.startsWith("OR ") || sqlRest.startsWith("or ") || sqlRest.startsWith("NOT ") || sqlRest.startsWith("not ")) {
2449+
throw new IllegalArgumentException("SQL WHERE " + key + (isEq ? " = val" : " IN(val0, val1 ...)") + " 后条件不允许 OR/NOT 连接,必须 AND 连接 ! ");
2450+
}
2451+
2452+
int orInd = findKeyIndex(sqlRest, "OR");
2453+
2454+
if (orInd >= 0) { // TODO 兼容 AND ( OR ) ?
2455+
throw new IllegalArgumentException("SQL WHERE " + key + (isEq ? " = val" : " IN(val0, val1 ...)") + " 后条件不允许 OR 连接,必须 AND 连接 ! ");
2456+
}
2457+
2458+
//int andInd = sqlRest.startsWith("AND ") || sqlRest.startsWith("and ") ? 1 : sqlRest.indexOf(" AND ");
2459+
//if (andInd < 0 && StringUtil.isNotEmpty(sqlRest, true)) {
2460+
// andInd = sqlRest.indexOf(" and ");
2461+
//
2462+
// if (andInd < 0) {
2463+
// //int groupInd = findLastKeyIndex(sqlRest, "GROUP BY");
2464+
// int havingInd = -1;
2465+
// //int orderInd = -1;
2466+
// int limitInd = -1;
2467+
//
2468+
// //if (groupInd < 0) {
2469+
// havingInd = findLastKeyIndex(sqlRest, "HAVING");
2470+
// //orderInd = findLastKeyIndex(sqlRest, "ORDER BY");
2471+
// limitInd = findLastKeyIndex(sqlRest, "LIMIT");
2472+
// //}
2473+
//
2474+
// if (havingInd >= 0 || limitInd >= 0) {
2475+
// throw new IllegalArgumentException("SQL WHERE " + key + (isEq ? " = val" : " IN(val0, val1 ...)") + " 后条件必须 AND 连接 ! ");
2476+
// }
2477+
// }
2478+
//}
2479+
2480+
// 只包含筛选出 apijson 字段的 Map<String, SortedMap<Integer, Map<String, Object>>> documentMap = DemoVerifier.DOCUMENT_MAP;
23642481
Map<String, List<Map<String, Object>>> sqlautoMap = SQLAUTO_MAP;
23652482
//if (sqlautoMap == null || sqlautoMap.isEmpty()) {
23662483
//
@@ -2385,19 +2502,46 @@ public String execute(@RequestBody String request, HttpSession session) {
23852502
}
23862503
}
23872504

2505+
APIJSONParser<Long> parser = newParser(session, RequestMethod.PUT); // : DELETE);
2506+
int maxCount = isWrite ? parser.getMaxUpdateCount() : parser.getMaxQueryCount();
2507+
if (isInsert == false && (config.isMySQL() || config.isMariaDB())) {
2508+
int limitIndex = findLastKeyIndex(trimmedSQL, "LIMIT");
2509+
2510+
if (limitIndex < 0) {
2511+
trimmedSQL += " LIMIT " + parser.getDefaultQueryCount();
2512+
} else {
2513+
String sqlRest = trimmedSQL.substring(limitIndex + " LIMIT ".length()).trim();
2514+
int ind = sqlRest.indexOf(" ");
2515+
String s = ind < 0 ? sqlRest : sqlRest.substring(0, ind).trim();
2516+
String[] arr = StringUtil.split(s);
2517+
2518+
for (int i = 0; i < arr.length; i++) {
2519+
String as = arr[i];
2520+
if ("?".endsWith(as)) {
2521+
continue; // FIXME 不知道在第几个 as = arg.getString();
2522+
}
2523+
2524+
try {
2525+
int limit = Integer.parseInt(as);
2526+
if (limit <= 0 || limit > maxCount) {
2527+
throw new IllegalArgumentException("SQL LIMIT " + s + " 超出范围 1~" + maxCount + " !");
2528+
}
2529+
} catch (Throwable e) {
2530+
throw new IllegalArgumentException("SQL LIMIT " + s + " 不合法!" + e.getMessage(), e);
2531+
}
2532+
}
2533+
}
2534+
}
2535+
23882536
List<Object> valueList = arg;
23892537

23902538
DemoSQLExecutor executor = new DemoSQLExecutor();
2391-
DemoSQLConfig config = new DemoSQLConfig();
23922539

2393-
config.setDatabase(database);
2394-
config.setSchema(schema);
2395-
config.setDBUri(uri);
23962540
config.setDBAccount(account);
23972541
config.setDBPassword(password);
23982542
config.setPrepared(true);
23992543
config.setPreparedValueList(valueList);
2400-
config.setSql(sql);
2544+
config.setSql(trimmedSQL);
24012545

24022546
JSONArray arr = null;
24032547

@@ -2423,11 +2567,22 @@ public String execute(@RequestBody String request, HttpSession session) {
24232567
}
24242568
}
24252569
} else {
2570+
Connection connection = executor.getConnection(config);
24262571
Statement statement = executor.getStatement(config, trimmedSQL);
24272572
if (statement instanceof PreparedStatement) {
24282573
if (EXECUTE_STRICTLY) {
24292574
if (isWrite) {
2430-
((PreparedStatement) statement).executeUpdate();
2575+
try {
2576+
connection.setAutoCommit(false);
2577+
// connection.beginRequest();
2578+
int rows = ((PreparedStatement) statement).executeUpdate();
2579+
if (rows >= maxCount) {
2580+
throw new UnsupportedOperationException("实际影响数量超过上限 " + maxCount + " ! 已回滚变更");
2581+
}
2582+
} catch (Throwable e) {
2583+
connection.rollback();
2584+
throw e;
2585+
}
24312586
} else {
24322587
((PreparedStatement) statement).executeQuery();
24332588
}
@@ -2441,7 +2596,17 @@ public String execute(@RequestBody String request, HttpSession session) {
24412596

24422597
if (EXECUTE_STRICTLY) {
24432598
if (isWrite) {
2444-
statement.executeUpdate(sql);
2599+
try {
2600+
connection.setAutoCommit(false);
2601+
// connection.beginRequest();
2602+
int rows = statement.executeUpdate(sql);
2603+
if (rows >= maxCount) {
2604+
throw new UnsupportedOperationException("实际影响数量超过上限 " + maxCount + " ! 已回滚变更");
2605+
}
2606+
} catch (Throwable e) {
2607+
connection.rollback();
2608+
throw e;
2609+
}
24452610
} else {
24462611
statement.executeQuery(sql);
24472612
}
@@ -2519,6 +2684,45 @@ public String execute(@RequestBody String request, HttpSession session) {
25192684

25202685
}
25212686

2687+
// TODO 抽取到 SQL 或 StringUitl
2688+
private static int findKeyIndex(String sql, String key) {
2689+
return findKeyIndex(sql, key, false, false);
2690+
}
2691+
private static int findLastKeyIndex(String sql, String key) {
2692+
return findKeyIndex(sql, key, true, false);
2693+
}
2694+
2695+
private static final String[] LEFT_CHARS = new String[]{" ", "\n", ")"};
2696+
private static final String[] RIGHT_CHARS = new String[]{" ", "\n", "("};
2697+
private static int findKeyIndex(String sql, String key, boolean last, boolean cased) {
2698+
for (int i = 0; i < LEFT_CHARS.length; i++) {
2699+
String l = LEFT_CHARS[i];
2700+
for (int j = 0; j < RIGHT_CHARS.length; j++) {
2701+
String r = RIGHT_CHARS[j];
2702+
int ind = last ? sql.lastIndexOf(l + key + r) : sql.indexOf(l + key + r);
2703+
if (ind >= 0) {
2704+
return ind;
2705+
}
2706+
}
2707+
}
2708+
2709+
if (cased) {
2710+
return -1;
2711+
}
2712+
2713+
String lower = key.toLowerCase();
2714+
if (! lower.equals(key)) {
2715+
return findKeyIndex(sql, lower, last, true);
2716+
}
2717+
2718+
String upper = key.toUpperCase();
2719+
if (! upper.equals(key)) {
2720+
return findKeyIndex(sql, upper, last, true);
2721+
}
2722+
2723+
return -1;
2724+
}
2725+
25222726

25232727
/**Swagger 文档 Demo,供 APIAuto 测试导入 Swagger 文档到数据库用
25242728
* @return

0 commit comments

Comments
 (0)