diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 00000000..6c465492 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,32 @@ +name: Android CI + +on: + push: + branches: [ "main" ] + paths: + - 'APIJSON-Android/APIJSONApp/**' + pull_request: + branches: [ "main" ] + paths: + - 'APIJSON-Android/APIJSONApp/**' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'temurin' + cache: gradle + + - name: Grant execute permission for gradlew + working-directory: APIJSON-Android/APIJSONApp + run: chmod +x gradlew + - name: Build with Gradle + working-directory: APIJSON-Android/APIJSONApp + run: ./gradlew build diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 00000000..94a4df45 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,39 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Java CI with Maven + +on: + push: + branches: [ "main" ] + paths: + - 'APIJSON-Java-Server/APIJSONBoot-MultiDataSource/**' + pull_request: + branches: [ "main" ] + paths: + - 'APIJSON-Java-Server/APIJSONBoot-MultiDataSource/**' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: cd APIJSON-Java-Server/APIJSONBoot-MultiDataSource && mvn -B package --file pom.xml + + # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive + # - name: Update dependency graph + # uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml new file mode 100644 index 00000000..f5365221 --- /dev/null +++ b/.github/workflows/swift.yml @@ -0,0 +1,28 @@ +# This workflow will build a Swift project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift + +name: Swift + +on: + push: + branches: [ "main" ] + paths: + - 'APIJSON-iOS/APIJSON-Swift/**' + pull_request: + branches: [ "main" ] + paths: + - 'APIJSON-iOS/APIJSON-Swift/**' + +jobs: + build: + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + - name: Build + working-directory: APIJSON-iOS/APIJSON-Swift + run: swift build -v + - name: Run tests + working-directory: APIJSON-iOS/APIJSON-Swift + run: swift test -v diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/boot/DemoApplication.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/boot/DemoApplication.java index f2206c13..0c3e95f0 100755 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/boot/DemoApplication.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/boot/DemoApplication.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/boot/DemoController.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/boot/DemoController.java index 590aa4d2..def21656 100644 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/boot/DemoController.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/boot/DemoController.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoFunctionParser.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoFunctionParser.java index 6b641ede..09a22252 100644 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoFunctionParser.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoFunctionParser.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoObjectParser.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoObjectParser.java index c789fc2b..f162a8f1 100644 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoObjectParser.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoObjectParser.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoParser.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoParser.java index 9ed3491e..b209f9f5 100644 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoParser.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoParser.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoSQLConfig.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoSQLConfig.java index 0192b96b..e8ef0de7 100755 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoSQLConfig.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoSQLConfig.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoSQLExecutor.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoSQLExecutor.java index 1d48c5fe..3df01e5a 100644 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoSQLExecutor.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoSQLExecutor.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoVerifier.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoVerifier.java index 1a0ee189..6a47e364 100644 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoVerifier.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/DemoVerifier.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/Privacy.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/Privacy.java index 930c4ed1..8427f99e 100644 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/Privacy.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/Privacy.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/User.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/User.java index a535d28f..baf250a4 100644 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/User.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/User.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/Verify.java b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/Verify.java index fa5fc135..64025c4b 100644 --- a/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/Verify.java +++ b/APIJSON-Java-Server/APIJSONBoot-BigData/src/main/java/apijson/demo/model/Verify.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/SQLUtil.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/SQLUtil.java new file mode 100644 index 00000000..356cea86 --- /dev/null +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/SQLUtil.java @@ -0,0 +1,70 @@ +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.*/ + +package apijson; + +public class SQLUtil { + public static boolean isRead(String sql) { + return isRead(sql, false); + } + public static boolean isRead(String sql, boolean isRedis) { + return ! isWrite(sql, isRedis); + } + + public static boolean isWrite(String sql) { + return isWrite(sql, false); + } + public static boolean isWrite(String sql, boolean isRedis) { + return isInsert(sql, isRedis) || isUpdate(sql, isRedis) || isDelete(sql, isRedis); + } + + public static boolean isSelect(String sql) { + return isSelect(sql, false); + } + public static boolean isSelect(String sql, boolean isRedis) { + return sql != null && ! (isInsert(sql, isRedis) || isUpdate(sql, isRedis) || isDelete(sql, isRedis)); + } + + public static boolean isInsert(String sql) { + return isInsert(sql, false); + } + public static boolean isInsert(String sql, boolean isRedis) { + return sql != null && ((sql.startsWith("INSERT ") && ! isRedis) || (isRedis && ( + sql.startsWith("SADD ") || sql.startsWith("ZADD ") + || sql.startsWith("LPUSH ") || sql.startsWith("RPUSH ") + ))); + } + + public static boolean isUpdate(String sql) { + return isUpdate(sql, false); + } + public static boolean isUpdate(String sql, boolean isRedis) { + return sql != null && ((sql.startsWith("UPDATE ") && ! isRedis) || (isRedis && (sql.startsWith("SET ") + || sql.startsWith("HSET ") || sql.startsWith("MSET ") + ))); + } + + public static boolean isDelete(String sql) { + return isDelete(sql, false); + } + public static boolean isDelete(String sql, boolean isRedis) { + return sql != null && ((sql.startsWith("DELETE ") && ! isRedis) + || (isRedis && (sql.startsWith("DEL ") || sql.startsWith("HDEL ") || sql.startsWith("MDEL ") + || sql.startsWith("SREM ") || sql.startsWith("ZREM ") + || sql.startsWith("LPOP ") || sql.startsWith("RPOP ") + || sql.startsWith("FLUSHDB ") || sql.startsWith("FLUSHALL ") + ))); + } + +} diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/AutoTestNodeProcessSupervisor.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/AutoTestNodeProcessSupervisor.java index 5673e7f6..79533b00 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/AutoTestNodeProcessSupervisor.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/AutoTestNodeProcessSupervisor.java @@ -1,3 +1,17 @@ +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.*/ + package apijson.boot; import apijson.Log; diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/AutoTestRegressionScheduler.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/AutoTestRegressionScheduler.java index a1d41d01..3d2658be 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/AutoTestRegressionScheduler.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/AutoTestRegressionScheduler.java @@ -1,3 +1,17 @@ +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.*/ + package apijson.boot; import apijson.Log; diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoApplication.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoApplication.java index d9f4b1a6..f00a4cb5 100755 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoApplication.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoApplication.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoController.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoController.java index cf11d01d..fa518c0c 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoController.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoController.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,12 +14,11 @@ package apijson.boot; +import apijson.SQLUtil; import apijson.fastjson2.APIJSONController; import apijson.fastjson2.APIJSONParser; import apijson.fastjson2.JSONRequest; -import apijson.framework.APIJSONApplication; -import apijson.framework.APIJSONConstant; -import apijson.framework.APIJSONCreator; +import apijson.framework.*; import apijson.orm.exception.*; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; @@ -68,7 +67,6 @@ import apijson.demo.model.Privacy; import apijson.demo.model.User; import apijson.demo.model.Verify; -import apijson.framework.BaseModel; import unitauto.MethodUtil; import static apijson.JSON.getString; @@ -95,7 +93,7 @@ * 具体见 SpringBoot 文档 * https://www.springcloud.cc/spring-boot.html#boot-features-spring-mvc * 以及 APIJSON 通用文档 3.设计规范 3.1 操作方法 - * https://github.com/Tencent/APIJSON/blob/master/Document.md#3.1 + * https://github.com/APIJSON/APIJSON/blob/main/Document.md#3.1 *
建议全通过HTTP POST来请求: *
1.减少代码 - 客户端无需写HTTP GET,PUT等各种方式的请求代码 *
2.提高性能 - 无需URL encode和decode @@ -2373,7 +2371,6 @@ public String execute(@RequestBody String request, HttpSession session) { JSONArray arg = req.getJSONArray("args"); try { if (StringUtil.isEmpty(database, true)) { - int start = uri.indexOf("://"); String prefix = uri.substring(0, start); int mid = prefix.lastIndexOf(":"); @@ -2381,7 +2378,9 @@ public String execute(@RequestBody String request, HttpSession session) { prefix = prefix.substring(mid + 1); } - if (DemoSQLConfig.DATABASE_INFLUXDB.equalsIgnoreCase(prefix) || DemoSQLConfig.DATABASE_CASSANDRA.equalsIgnoreCase(prefix)) { + boolean isCassandra = DemoSQLConfig.DATABASE_CASSANDRA.equalsIgnoreCase(prefix); + boolean isRedis = (! isCassandra) && DemoSQLConfig.DATABASE_REDIS.equalsIgnoreCase(prefix); + if (isCassandra || isRedis || DemoSQLConfig.DATABASE_INFLUXDB.equalsIgnoreCase(prefix)) { database = prefix.toUpperCase(); int end = uri.lastIndexOf("/"); @@ -2398,141 +2397,145 @@ public String execute(@RequestBody String request, HttpSession session) { // password = "apijson@123"; // } - if (DemoSQLConfig.DATABASE_CASSANDRA.equalsIgnoreCase(prefix)) { // unknown protocol: jdbc + if (isCassandra) { // unknown protocol: jdbc account = "test"; // "peter"; password = "test"; // null; } } } - String trimmedSQL = sql.trim(); - String sqlPrefix = trimmedSQL.length() < 7 ? "" : trimmedSQL.substring(0, 7).toUpperCase(); - boolean isInsert = sqlPrefix.startsWith("INSERT "); - boolean isEdit = (! isInsert) && sqlPrefix.startsWith("UPDATE "); - boolean isWrite = isInsert || isEdit || sqlPrefix.startsWith("DELETE "); - DemoSQLConfig config = new DemoSQLConfig(); config.setDatabase(database); config.setDatasource(datasource); config.setDBUri(uri); config.setSchema(schema); - if (WRITE_STRICTLY && isWrite && ! isInsert) { - String sqlRest = trimmedSQL.replaceAll("\\s+", " "); - sqlRest = sqlRest.replaceAll("\f", " "); - sqlRest = sqlRest.replaceAll("\n", " "); - sqlRest = sqlRest.replaceAll("\t", " "); - sqlRest = sqlRest.replaceAll("\r", " "); - int fromInd = isEdit ? 0 : findKeyIndex(sqlRest, "FROM"); - if (fromInd < 0) { - throw new IllegalArgumentException("SQL 缺 FORM 关键词!"); - } + boolean isRedis = config.isRedis(); - sqlRest = sqlRest.substring(fromInd + (isEdit ? "UPDATE " : " FROM ").length()).trim(); - int blankRest = sqlRest.indexOf(" "); - if (blankRest < 0) { - throw new IllegalArgumentException("SQL 缺少 表名!"); - } + String trimmedSQL = sql.trim(); + boolean isInsert = SQLUtil.isInsert(trimmedSQL, isRedis); + boolean isEdit = (! isInsert) && SQLUtil.isUpdate(trimmedSQL, isRedis); + boolean isDelete = (! (isInsert || isEdit)) && SQLUtil.isDelete(trimmedSQL, isRedis); + boolean isWrite = isInsert || isEdit || isDelete; + + if (WRITE_STRICTLY && isWrite && ! isInsert) { + if (! isRedis) { + String sqlRest = trimmedSQL.replaceAll("\\s+", " "); + sqlRest = sqlRest.replaceAll("\f", " "); + sqlRest = sqlRest.replaceAll("\n", " "); + sqlRest = sqlRest.replaceAll("\t", " "); + sqlRest = sqlRest.replaceAll("\r", " "); + int fromInd = isEdit ? 0 : findKeyIndex(sqlRest, "FROM"); + if (fromInd < 0) { + throw new IllegalArgumentException("SQL 缺 FORM 关键词!"); + } - String tblPath = sqlRest.substring(0, blankRest).trim(); - sqlRest = sqlRest.substring(blankRest); // + 1).trim(); - String[] tblArr = tblPath.indexOf(".") >= 0 ? tblPath.split("\\.") : new String[]{tblPath}; // StringUtil.split(tblPath.Pattern.quota("."), true); - int len = tblArr == null ? 0 : tblArr.length; - String sch = len < 2 ? null : tblArr[len - 2]; - if (StringUtil.isNotEmpty(sch)){ - if ((sch.startsWith("`") && sch.endsWith("`")) || (sch.startsWith("[") && sch.endsWith("]")) || (sch.startsWith("\"") && sch.endsWith("\""))) { - sch = sch.substring(1, sch.length() - 1); + sqlRest = sqlRest.substring(fromInd + (isEdit ? "UPDATE " : " FROM ").length()).trim(); + int blankRest = sqlRest.indexOf(" "); + if (blankRest < 0) { + throw new IllegalArgumentException("SQL 缺少 表名!"); } + String tblPath = sqlRest.substring(0, blankRest).trim(); + sqlRest = sqlRest.substring(blankRest); // + 1).trim(); + String[] tblArr = tblPath.indexOf(".") >= 0 ? tblPath.split("\\.") : new String[]{tblPath}; // StringUtil.split(tblPath.Pattern.quota("."), true); + int len = tblArr == null ? 0 : tblArr.length; + String sch = len < 2 ? null : tblArr[len - 2]; if (StringUtil.isNotEmpty(sch)) { - if (StringUtil.isNotEmpty(schema) && ! schema.equals(sch)) { - throw new ConflictException("传参 schema: " + schema + " 和 SQL 中" + tblPath + " 里的" + sch + "不一致!"); + if ((sch.startsWith("`") && sch.endsWith("`")) || (sch.startsWith("[") && sch.endsWith("]")) || (sch.startsWith("\"") && sch.endsWith("\""))) { + sch = sch.substring(1, sch.length() - 1); } - config.setSchema(sch); + if (StringUtil.isNotEmpty(sch)) { + if (StringUtil.isNotEmpty(schema) && !schema.equals(sch)) { + throw new ConflictException("传参 schema: " + schema + " 和 SQL 中" + tblPath + " 里的" + sch + "不一致!"); + } + + config.setSchema(sch); + } } - } - - String tbl = len < 1 ? null : tblArr[len - 1]; - if ((tbl.startsWith("`") && tbl.endsWith("`")) || (tbl.startsWith("[") & tbl.endsWith("]")) || (tbl.startsWith("\"") && tbl.endsWith("\""))) { - tbl = tbl.substring(1, tbl.length() -1); - } - - config.setTable(tbl); - String idKey = config.getIdKey(); - String userIdKey = config.getUserIdKey(); - int whereInd = findKeyIndex(sqlRest, "WHERE"); - if (whereInd < 0) { - throw new IllegalArgumentException("SQL 缺少 WHERE 关键词!"); - } - sqlRest = sqlRest.substring(whereInd + " WHERE ".length()).trim(); - String key; - if (sqlRest.startsWith(idKey)) { - key = idKey; - } else if (sqlRest.startsWith("`" + idKey + "`") || sqlRest.startsWith("[" + idKey + "]") || sqlRest.startsWith("\"" + idKey + "\"")) { - key = "`" + idKey + "`"; - } else if (sqlRest.startsWith(userIdKey)) { - key = userIdKey; - } else if (sqlRest.startsWith("`" + userIdKey + "`") || sqlRest.startsWith("[" + userIdKey + "]") || sqlRest.startsWith("\"" + userIdKey + "\"")) { - key = "`" + userIdKey + "`"; - } else { - throw new IllegalArgumentException("SQL WHERE 后必须接着 "+ idKey + " 或 " + userIdKey + " = ? 或 IN(?, ? ...) !"); - } + String tbl = len < 1 ? null : tblArr[len - 1]; + if ((tbl.startsWith("`") && tbl.endsWith("`")) || (tbl.startsWith("[") & tbl.endsWith("]")) || (tbl.startsWith("\"") && tbl.endsWith("\""))) { + tbl = tbl.substring(1, tbl.length() - 1); + } - sqlRest = sqlRest.substring(key.length()).trim(); - boolean isEq = sqlRest.startsWith("="); - if (! (isEq || sqlRest.startsWith("IN(") || sqlRest.startsWith("in("))) { - throw new IllegalArgumentException("SQL WHERE " + key + " 后必须接着 = ? 或 IN(?, ? ...) ! "); - } + config.setTable(tbl); + String idKey = config.getIdKey(); + String userIdKey = config.getUserIdKey(); + int whereInd = findKeyIndex(sqlRest, "WHERE"); + if (whereInd < 0) { + throw new IllegalArgumentException("SQL 缺少 WHERE 关键词!"); + } - sqlRest = sqlRest.substring(isEq ? 1 : "IN(".length()).trim(); - int endInd = -1; // isEq && ! sqlRest.startsWith("'") ? -1 : sqlRest.indexOf(isEq ? "'" : ")", 1); - if (isEq && ! sqlRest.startsWith("'")) { - for (int i = 0; i < sqlRest.length(); i++) { - char c = sqlRest.charAt(i); - if (c == '?' || c == '+' || c == '-' || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { - endInd ++; - } else { - break; + sqlRest = sqlRest.substring(whereInd + " WHERE ".length()).trim(); + String key; + if (sqlRest.startsWith(idKey)) { + key = idKey; + } else if (sqlRest.startsWith("`" + idKey + "`") || sqlRest.startsWith("[" + idKey + "]") || sqlRest.startsWith("\"" + idKey + "\"")) { + key = "`" + idKey + "`"; + } else if (sqlRest.startsWith(userIdKey)) { + key = userIdKey; + } else if (sqlRest.startsWith("`" + userIdKey + "`") || sqlRest.startsWith("[" + userIdKey + "]") || sqlRest.startsWith("\"" + userIdKey + "\"")) { + key = "`" + userIdKey + "`"; + } else { + throw new IllegalArgumentException("SQL WHERE 后必须接着 " + idKey + " 或 " + userIdKey + " = ? 或 IN(?, ? ...) !"); + } + + sqlRest = sqlRest.substring(key.length()).trim(); + boolean isEq = sqlRest.startsWith("="); + if (!(isEq || sqlRest.startsWith("IN(") || sqlRest.startsWith("in("))) { + throw new IllegalArgumentException("SQL WHERE " + key + " 后必须接着 = ? 或 IN(?, ? ...) ! "); + } + + sqlRest = sqlRest.substring(isEq ? 1 : "IN(".length()).trim(); + int endInd = -1; // isEq && ! sqlRest.startsWith("'") ? -1 : sqlRest.indexOf(isEq ? "'" : ")", 1); + if (isEq && !sqlRest.startsWith("'")) { + for (int i = 0; i < sqlRest.length(); i++) { + char c = sqlRest.charAt(i); + if (c == '?' || c == '+' || c == '-' || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { + endInd++; + } else { + break; + } } + } else { + endInd = sqlRest.indexOf(isEq ? "'" : ")", isEq ? 1 : 0); } - } else { - endInd = sqlRest.indexOf(isEq ? "'" : ")", isEq ? 1 : 0); - } - sqlRest = endInd < 0 ? sqlRest : sqlRest.substring(endInd + 1).trim(); - if (sqlRest.startsWith("OR ") || sqlRest.startsWith("or ") || sqlRest.startsWith("NOT ") || sqlRest.startsWith("not ")) { - throw new IllegalArgumentException("SQL WHERE " + key + (isEq ? " = val" : " IN(val0, val1 ...)") + " 后条件不允许 OR/NOT 连接,必须 AND 连接 ! "); - } + sqlRest = endInd < 0 ? sqlRest : sqlRest.substring(endInd + 1).trim(); + if (sqlRest.startsWith("OR ") || sqlRest.startsWith("or ") || sqlRest.startsWith("NOT ") || sqlRest.startsWith("not ")) { + throw new IllegalArgumentException("SQL WHERE " + key + (isEq ? " = val" : " IN(val0, val1 ...)") + " 后条件不允许 OR/NOT 连接,必须 AND 连接 ! "); + } - int orInd = findKeyIndex(sqlRest, "OR"); + int orInd = findKeyIndex(sqlRest, "OR"); - if (orInd >= 0) { // TODO 兼容 AND ( OR ) ? - throw new IllegalArgumentException("SQL WHERE " + key + (isEq ? " = val" : " IN(val0, val1 ...)") + " 后条件不允许 OR 连接,必须 AND 连接 ! "); - } + if (orInd >= 0) { // TODO 兼容 AND ( OR ) ? + throw new IllegalArgumentException("SQL WHERE " + key + (isEq ? " = val" : " IN(val0, val1 ...)") + " 后条件不允许 OR 连接,必须 AND 连接 ! "); + } - //int andInd = sqlRest.startsWith("AND ") || sqlRest.startsWith("and ") ? 1 : sqlRest.indexOf(" AND "); - //if (andInd < 0 && StringUtil.isNotEmpty(sqlRest, true)) { - // andInd = sqlRest.indexOf(" and "); - // - // if (andInd < 0) { - // //int groupInd = findLastKeyIndex(sqlRest, "GROUP BY"); - // int havingInd = -1; - // //int orderInd = -1; - // int limitInd = -1; - // - // //if (groupInd < 0) { - // havingInd = findLastKeyIndex(sqlRest, "HAVING"); - // //orderInd = findLastKeyIndex(sqlRest, "ORDER BY"); - // limitInd = findLastKeyIndex(sqlRest, "LIMIT"); - // //} - // - // if (havingInd >= 0 || limitInd >= 0) { - // throw new IllegalArgumentException("SQL WHERE " + key + (isEq ? " = val" : " IN(val0, val1 ...)") + " 后条件必须 AND 连接 ! "); - // } - // } - //} + //int andInd = sqlRest.startsWith("AND ") || sqlRest.startsWith("and ") ? 1 : sqlRest.indexOf(" AND "); + //if (andInd < 0 && StringUtil.isNotEmpty(sqlRest, true)) { + // andInd = sqlRest.indexOf(" and "); + // + // if (andInd < 0) { + // //int groupInd = findLastKeyIndex(sqlRest, "GROUP BY"); + // int havingInd = -1; + // //int orderInd = -1; + // int limitInd = -1; + // + // //if (groupInd < 0) { + // havingInd = findLastKeyIndex(sqlRest, "HAVING"); + // //orderInd = findLastKeyIndex(sqlRest, "ORDER BY"); + // limitInd = findLastKeyIndex(sqlRest, "LIMIT"); + // //} + // + // if (havingInd >= 0 || limitInd >= 0) { + // throw new IllegalArgumentException("SQL WHERE " + key + (isEq ? " = val" : " IN(val0, val1 ...)") + " 后条件必须 AND 连接 ! "); + // } + // } + //} + } // 只包含筛选出 apijson 字段的 Map>> documentMap = DemoVerifier.DOCUMENT_MAP; Map>> sqlautoMap = SQLAUTO_MAP; @@ -2560,7 +2563,7 @@ public String execute(@RequestBody String request, HttpSession session) { APIJSONParser parser = newParser(session, RequestMethod.PUT); // : DELETE); int maxCount = isWrite ? parser.getMaxUpdateCount() : parser.getMaxQueryCount(); - if (isInsert == false && (config.isMySQL() || config.isMariaDB())) { + if (isRedis == false && isInsert == false && (config.isMySQL() || config.isMariaDB())) { int limitIndex = findLastKeyIndex(trimmedSQL, "LIMIT"); if (limitIndex < 0) { @@ -2613,7 +2616,21 @@ public String execute(@RequestBody String request, HttpSession session) { boolean isAutoCommit = true; try { - if (DemoSQLConfig.DATABASE_INFLUXDB.equals(database) || DemoSQLConfig.DATABASE_CASSANDRA.equals(database)) { + if (isRedis || config.isInfluxDB() || config.isCassandra()) { + int size = arg == null ? 0 : arg.size(); + if (size > 0 && trimmedSQL.contains("?")) { + Object[] params = new Object[size]; + for (int i = 0; i < size; i++) { + Object a = arg.get(i); + params[i] = (isRedis ? (a == null ? "null" : ("".equals(a) ? "\"\"" : (JSON.isBoolOrNumOrStr(a) + ? a : JSON.toJSONString(a)) + )) : JSON.toJSONString(a)); + } + + trimmedSQL = String.format (trimmedSQL.replace("?", "%s"), params); + config.setSql(trimmedSQL); + } + JSONObject result = executor.execute(config, false); executeDuration = System.currentTimeMillis() - executeStartTime; @@ -3320,10 +3337,16 @@ public String au() { //} @PostMapping("method/list") - public Map listMethod(@RequestBody String request) { + public Map listMethod(@RequestBody String request, HttpSession session) { if (Log.DEBUG == false) { return MethodUtil.JSON_CALLBACK.newErrorResult(new IllegalAccessException("非 DEBUG 模式下不允许使用 UnitAuto 单元测试!")); } + try { + COMMON_PARSER.getVerifier().setVisitor(APIJSONVerifier.getVisitor(session)).verifyLogin(); + } catch (Exception e) { + return MethodUtil.JSON_CALLBACK.newErrorResult(e); + } + return MethodUtil.listMethod(request); } @@ -3351,9 +3374,19 @@ public void complete(com.alibaba.fastjson.JSONObject data, Method method, Method } }; - if (Log.DEBUG == false) { + Exception err = Log.DEBUG ? null : new IllegalAccessException("非 DEBUG 模式下不允许使用 UnitAuto 单元测试!"); + if (err == null) { + try { + HttpSession session = servletRequest.getSession(); + COMMON_PARSER.getVerifier().setVisitor(APIJSONVerifier.getVisitor(session)).verifyLogin(); + } catch (Exception e) { + err = e; + } + } + + if (err != null) { try { - listener.complete(MethodUtil.JSON_CALLBACK.newErrorResult(new IllegalAccessException("非 DEBUG 模式下不允许使用 UnitAuto 单元测试!"))); + listener.complete(MethodUtil.JSON_CALLBACK.newErrorResult(err)); } catch (Exception e1) { e1.printStackTrace(); @@ -3384,6 +3417,12 @@ public void complete(com.alibaba.fastjson.JSONObject data, Method method, Method @GetMapping("api/test/start") public String startApiTest(HttpSession session) { + try { + COMMON_PARSER.getVerifier().setVisitor(APIJSONVerifier.getVisitor(session)).verifyLogin(); + } catch (Exception e) { + return JSON.toJSONString(COMMON_PARSER.newErrorResult(e)); + } + //response.sendRedirect("http://localhost:3000/test/start"); long id = 100000 + Math.round(899999*Math.random()); DemoParser.KEY_MAP.put(String.valueOf(id), session); // 调这个接口一般是前端/CI/CD,调查询接口的是 Node,Session 不同 session.setAttribute("key", id); @@ -3391,6 +3430,12 @@ public String startApiTest(HttpSession session) { } @GetMapping("api/test/status") public String getApiTestStatus(@RequestParam(value = "key", required = false) String key, HttpSession session) { + try { + COMMON_PARSER.getVerifier().setVisitor(APIJSONVerifier.getVisitor(session)).verifyLogin(); + } catch (Exception e) { + return JSON.toJSONString(COMMON_PARSER.newErrorResult(e)); + } + //response.sendRedirect("http://localhost:3000/test/status"); DemoParser.KEY_MAP.remove(key); return delegate("http://localhost:3000/test/status", session); @@ -3398,30 +3443,60 @@ public String getApiTestStatus(@RequestParam(value = "key", required = false) St @GetMapping("unit/test/start") public String startUnitTest(HttpSession session) { + try { + COMMON_PARSER.getVerifier().setVisitor(APIJSONVerifier.getVisitor(session)).verifyLogin(); + } catch (Exception e) { + return JSON.toJSONString(COMMON_PARSER.newErrorResult(e)); + } + long id = 100000 + Math.round(899999*Math.random()); DemoParser.KEY_MAP.put(String.valueOf(id), session); // 调这个接口一般是前端/CI/CD,调查询接口的是 Node,Session 不同 session.setAttribute("key", id); return delegate("http://localhost:3001/test/start?key=" + id, session); } @GetMapping("unit/test/status") public String getUnitTestStatus(@RequestParam(value = "key", required = false) String key, HttpSession session) { + try { + COMMON_PARSER.getVerifier().setVisitor(APIJSONVerifier.getVisitor(session)).verifyLogin(); + } catch (Exception e) { + return JSON.toJSONString(COMMON_PARSER.newErrorResult(e)); + } + DemoParser.KEY_MAP.remove(key); return delegate("http://localhost:3001/test/status", session); } @GetMapping("sql/test/start") public String startSQLTest(HttpSession session) { + try { + COMMON_PARSER.getVerifier().setVisitor(APIJSONVerifier.getVisitor(session)).verifyLogin(); + } catch (Exception e) { + return JSON.toJSONString(COMMON_PARSER.newErrorResult(e)); + } + long id = 100000 + Math.round(899999*Math.random()); DemoParser.KEY_MAP.put(String.valueOf(id), session); // 调这个接口一般是前端/CI/CD,调查询接口的是 Node,Session 不同 session.setAttribute("key", id); return delegate("http://localhost:3002/test/start?key=" + id, session); } @GetMapping("sql/test/status") public String getSQLTestStatus(@RequestParam("key") String key, HttpSession session) { + try { + COMMON_PARSER.getVerifier().setVisitor(APIJSONVerifier.getVisitor(session)).verifyLogin(); + } catch (Exception e) { + return JSON.toJSONString(COMMON_PARSER.newErrorResult(e)); + } + DemoParser.KEY_MAP.remove(key); return delegate("http://localhost:3002/test/status", session); } @GetMapping("cv/test/start") public String startCvTest(HttpSession session) { + try { + COMMON_PARSER.getVerifier().setVisitor(APIJSONVerifier.getVisitor(session)).verifyLogin(); + } catch (Exception e) { + return JSON.toJSONString(COMMON_PARSER.newErrorResult(e)); + } + //response.sendRedirect("http://localhost:3003/test/start"); long id = 100000 + Math.round(899999*Math.random()); DemoParser.KEY_MAP.put(String.valueOf(id), session); // 调这个接口一般是前端/CI/CD,调查询接口的是 Node,Session 不同 session.setAttribute("key", id); @@ -3429,6 +3504,12 @@ public String startCvTest(HttpSession session) { } @GetMapping("cv/test/status") public String getCvTestStatus(@RequestParam(value = "key", required = false) String key, HttpSession session) { + try { + COMMON_PARSER.getVerifier().setVisitor(APIJSONVerifier.getVisitor(session)).verifyLogin(); + } catch (Exception e) { + return JSON.toJSONString(COMMON_PARSER.newErrorResult(e)); + } + //response.sendRedirect("http://localhost:3003/test/status"); DemoParser.KEY_MAP.remove(key); return delegate("http://localhost:3003/test/status", session); diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoDataSourceConfig.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoDataSourceConfig.java index b68402ec..ef3388a1 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoDataSourceConfig.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/boot/DemoDataSourceConfig.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoFunctionParser.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoFunctionParser.java index 773837ab..f7098026 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoFunctionParser.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoFunctionParser.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ /**可远程调用的函数类,用于自定义业务逻辑处理 - * 具体见 https://github.com/Tencent/APIJSON/issues/101 + * 具体见 https://github.com/APIJSON/APIJSON/issues/101 * @author Lemon */ public class DemoFunctionParser extends APIJSONFunctionParser { diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoObjectParser.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoObjectParser.java index 960cfba3..7e0a0afc 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoObjectParser.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoObjectParser.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoParser.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoParser.java index 81a49043..acab53f4 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoParser.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoParser.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ /**请求解析器 - * 具体见 https://github.com/Tencent/APIJSON/issues/38 + * 具体见 https://github.com/APIJSON/APIJSON/issues/38 * @author Lemon */ public class DemoParser extends APIJSONParser { diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java index 1ba6de56..e94176ee 100755 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLConfig.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ /**SQL配置 * TiDB 用法和 MySQL 一致 * 具体见详细的说明文档 C.开发说明 C-1-1.修改数据库链接 - * https://github.com/Tencent/APIJSON/blob/master/%E8%AF%A6%E7%BB%86%E7%9A%84%E8%AF%B4%E6%98%8E%E6%96%87%E6%A1%A3.md#c-1-1%E4%BF%AE%E6%94%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E9%93%BE%E6%8E%A5 + * https://github.com/APIJSON/APIJSON/blob/main/%E8%AF%A6%E7%BB%86%E7%9A%84%E8%AF%B4%E6%98%8E%E6%96%87%E6%A1%A3.md#c-1-1%E4%BF%AE%E6%94%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E9%93%BE%E6%8E%A5 * @author Lemon */ public class DemoSQLConfig extends APIJSONSQLConfig { diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLExecutor.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLExecutor.java index 3fcd7658..b7375b55 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLExecutor.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoSQLExecutor.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -28,14 +28,18 @@ import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONArray; //import org.duckdb.JsonNode; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericToStringSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import javax.sql.DataSource; import java.io.Serializable; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; @@ -52,7 +56,7 @@ /** * SQL 执行器,支持连接池及多数据源 - * 具体见 https://github.com/Tencent/APIJSON/issues/151 + * 具体见 https://github.com/APIJSON/APIJSON/issues/151 * * @author Lemon */ @@ -64,7 +68,10 @@ public class DemoSQLExecutor extends APIJSONSQLExecutor { static { REDIS_TEMPLATE = new RedisTemplate<>(); try { - REDIS_TEMPLATE.setConnectionFactory(new JedisConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379))); + RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration("127.0.0.1", 6379); + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(redisConfig); + connectionFactory.afterPropertiesSet(); + REDIS_TEMPLATE.setConnectionFactory(connectionFactory); REDIS_TEMPLATE.setKeySerializer(new StringRedisSerializer()); REDIS_TEMPLATE.setHashValueSerializer(new GenericToStringSerializer<>(Serializable.class)); REDIS_TEMPLATE.setValueSerializer(new GenericToStringSerializer<>(Serializable.class)); @@ -180,29 +187,29 @@ public JSONObject execute(@NotNull SQLConfig config boolean isInfluxDB = config.isInfluxDB(); // DemoSQLConfig.DATABASE_INFLUXDB.equals(config.getDatabase()); // boolean isIoTDB = config.isIoTDB(); // DemoSQLConfig.DATABASE_IOTDB.equals(config.getDatabase()); // boolean isSurrealDB = config.isSurrealDB(); // DemoSQLConfig.DATABASE_SURREALDB.equals(config.getDatabase()); + boolean isRedis = config.isRedis(); // DemoSQLConfig.DATABASE_REDIS.equals(config.getDatabase()); - if (isMilvus || isCassandra || isInfluxDB) { // || isIoTDB || isSurrealDB) { + if (isMilvus || isCassandra || isInfluxDB || isRedis) { // || isIoTDB || isSurrealDB) { // TODO 把 execute 内与缓存无关只与数据库读写逻辑相关的代码抽取到 executeSQL 函数 String sql = config.gainSQL(false); // config.isPrepared()); if (sql != null && config.getMethod() == null) { String trimmedSQL = sql.trim(); - String sqlPrefix = trimmedSQL.length() < 7 ? "" : trimmedSQL.substring(0, 7).toUpperCase(); - if (sqlPrefix.startsWith("INSERT ")) { + if (SQLUtil.isInsert(trimmedSQL, isRedis)) { config.setMethod(RequestMethod.POST); } - else if (sqlPrefix.startsWith("UPDATE ")) { + else if (SQLUtil.isUpdate(trimmedSQL, isRedis)) { config.setMethod(RequestMethod.PUT); } - else if (sqlPrefix.startsWith("DELETE ")) { + else if (SQLUtil.isDelete(trimmedSQL, isRedis)) { config.setMethod(RequestMethod.DELETE); } } boolean isWrite = ! RequestMethod.isQueryMethod(config.getMethod()); - List cache = isWrite ? null : getCache(sql, config); + List cache = isWrite || isRedis ? null : getCache(sql, config); int position = config.getPosition(); - JSONObject result = getCacheItem(cache, position, config); + JSONObject result = isRedis ? null : getCacheItem(cache, position, config); if (result != null) { if (position == 0 && cache != null && cache.size() > 1) { result.put(KEY_RAW_LIST, cache); @@ -213,6 +220,69 @@ else if (sqlPrefix.startsWith("DELETE ")) { List resultList = new ArrayList<>(); + if (isRedis) { + String trimmedSQL = StringUtil.trim(sql); + int index = trimmedSQL.indexOf(" "); + + String cmd; + byte[][] args; + if (index < 0) { + cmd = trimmedSQL; + args = new byte[0][0]; + } else { + cmd = trimmedSQL.substring(0, index); + String val = trimmedSQL.substring(index + 1).trim(); + String[] splitArgs = val.split(" "); + args = new byte[splitArgs.length][]; + for (int i = 0; i < splitArgs.length; i++) { + args[i] = StringUtil.trim(splitArgs[i]).getBytes(StandardCharsets.UTF_8); + } + } + + RedisTemplate redisTemp = new RedisTemplate<>(); + try { + String uri = config.gainDBUri(); + int ind = uri.indexOf("://"); + uri = ind < 0 ? uri : uri.substring(ind + 3); + int ind2 = uri.indexOf(":"); + String host = ind2 < 0 ? uri : uri.substring(0, ind2); + uri = ind2 < 0 ? "" : uri.substring(ind2 + 1); + int ind3 = uri.indexOf("?"); + String port = ind3 < 0 ? uri : uri.substring(0, ind3); + + RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration(host, StringUtil.isEmpty(port) ? 6379 : Integer.valueOf(port)); + redisConfig.setPassword(config.gainDBPassword()); + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(redisConfig); + connectionFactory.afterPropertiesSet(); + redisTemp.setConnectionFactory(connectionFactory); + redisTemp.setKeySerializer(new StringRedisSerializer()); + redisTemp.setHashValueSerializer(new GenericToStringSerializer<>(Serializable.class)); + redisTemp.setValueSerializer(new GenericToStringSerializer<>(Serializable.class)); + redisTemp.afterPropertiesSet(); + } catch (Throwable e) { + e.printStackTrace(); + } + + Object ret = redisTemp.execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) throws DataAccessException { + return connection.execute(cmd, args); + } + }); + + if (ret != null && ret.getClass().isAssignableFrom(byte[].class)) { + try { + ret = new String((byte[]) ret, StandardCharsets.UTF_8); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + result = new JSONObject(); + result.put(isWrite ? JSONResponse.KEY_COUNT : "value", ret); + return result; + } + // if (isMilvus) { // if (isWrite) { // return MilvusUtil.executeUpdate(config, sql); diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoVerifier.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoVerifier.java index 60d3fbff..ffb50d5c 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoVerifier.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/DemoVerifier.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ /**安全校验器,校验请求参数、角色与权限等 - * 具体见 https://github.com/Tencent/APIJSON/issues/12 + * 具体见 https://github.com/APIJSON/APIJSON/issues/12 * @author Lemon */ public class DemoVerifier extends APIJSONVerifier { diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/Privacy.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/Privacy.java index 2f5b82c0..e8d7bc74 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/Privacy.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/Privacy.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/User.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/User.java index 138bbd86..084feca3 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/User.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/User.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/Verify.java b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/Verify.java index 7050add9..1988eff7 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/Verify.java +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/java/apijson/demo/model/Verify.java @@ -1,4 +1,4 @@ -/*Copyright ©2016 TommyLemon(https://github.com/TommyLemon/APIJSON) +/*Copyright ©2025 APIJSON(https://github.com/APIJSON) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/README.md b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/README.md index 1eb96416..d2ecf08e 100755 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/README.md +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/README.md @@ -13,7 +13,7 @@

- +

--- @@ -21,7 +21,7 @@ 集合 文档、测试、Mock、调试、管理 的一站式体验,还有 **AI 问答** 和一键 格式化、注释/取消注释 等高效快捷键。
在常用功能上远超 Postman, Swagger, YApi 等各种 开源、商业 的 API 文档/测试 工具,并能一键导入用例和文档。
支持 GET, POST, PUT, PATCH, DELETE, HEAD 等各种 HTTP Method 及 Content-Type, URL /{Path}/{Variable}。
-不仅适用于 RESTful、类 RESTful、GRPC 的 API,还是腾讯 [APIJSON](https://github.com/Tencent/APIJSON) 官方建议的文档与测试工具。
+不仅适用于 RESTful、类 RESTful、GRPC 的 API,还是 [APIJSON](https://github.com/APIJSON/APIJSON) 官方建议的文档与测试工具。
腾讯内部用户包括 IEG 互动娱乐事业群、TEG 技术工程事业群、CSIG 云与智慧事业群 的多个部门及团队,
外部用户包含 华为、工商银行某地分行、500 强上市公司传音、跨境电商巨头 SHEIN、行业领头羊社保科技 等。 @@ -35,7 +35,7 @@ 腾讯 AI 测试圈子演讲(部分) -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIAuto/APIAuto_Tencent_TEG_AITestCircle_quick_and_slow.gif) +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/main/APIAuto/APIAuto_Tencent_TEG_AITestCircle_quick_and_slow.gif) #### 腾讯内先后被 TEG 工具开发组、微信支付部门、IEG 工具开发组、CDG 金融支付组、IEG PC 游戏平台部 邀请分享了 @@ -60,8 +60,8 @@ https://testwo.cn1.quickconnect.cn/vs/sharing/iiP8VK1C#!aG9tZV92aWRlby0xMQ== https://www.bilibili.com/video/BV1yv411p7Y4
-![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIAuto/APIAuto_mltesting.gif) -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIAuto/APIAuto_mltest_check_result.gif) +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/main/APIAuto/APIAuto_mltesting.gif) +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/main/APIAuto/APIAuto_mltest_check_result.gif) ![image](https://user-images.githubusercontent.com/5738175/145665614-f9208e35-9dc2-4a02-a8c9-0d7c9f4a87bc.png) @@ -104,13 +104,11 @@ https://www.bilibili.com/video/BV1yv411p7Y4 * 自动给请求JSON加注释,一键切换 * 自动保存历史请求记录,一键恢复 -以上是简略图,机器学习测试、自动生成代码、自动静态检查、自动生成注释 等详细的功能介绍见
- -[https://github.com/TommyLemon/APIAuto/blob/master/apijson/README.md](https://github.com/TommyLemon/APIAuto/blob/master/apijson/README.md) +以上是简略图,机器学习测试、自动生成代码、自动静态检查、自动生成注释 等详细的功能介绍见 [apijson/README.md](/apijson/README.md) ### 演讲稿件 -[APIAuto-机器学习 HTTP 接口工具](https://github.com/TommyLemon/StaticResources/tree/master/APIAuto/Share)
-[QECon 大会-腾讯 Tommy-零代码开发和测试](https://github.com/TommyLemon/StaticResources/tree/master/APIAuto/Share) +[APIAuto-机器学习 HTTP 接口工具](https://github.com/TommyLemon/StaticResources/tree/main/APIAuto/Share)
+[QECon 大会-腾讯 Tommy-零代码开发和测试](https://github.com/TommyLemon/StaticResources/tree/main/APIAuto/Share) ### 视频教程 Bilibili:https://search.bilibili.com/all?keyword=APIAuto @@ -127,6 +125,8 @@ Bilibili:https://search.bilibili.com/all?keyword=APIAuto [APIAuto 开源项目教程](https://blog.csdn.net/gitblog_00138/article/details/142802330)
[Github 2.2k Star!一款开源的机器学习 HTTP 接口工具!](https://mp.weixin.qq.com/s/4ZS80QP-WN-holFQkrxK-w)
[APIAuto:机器学习驱动的零代码HTTP接口智能测试平台深度解析](https://blog.csdn.net/weixin_33940469/article/details/162085502) +[APIAuto:零代码接口测试与文档管理一体化平台实战指南](https://blog.csdn.net/weixin_31061699/article/details/162536896) +[APIAuto: an HTTP interface tool that treats test cases as a database-backed workflow](https://hysenlabs.com/projects/tommylemon-apiauto) ### 百度、搜狗、抖音公网接口调用演示
因为这些接口不支持 CORS 跨域,所以需要开启托管服务代理。
@@ -157,7 +157,7 @@ https://www.bilibili.com/video/BV1JZ4y1d7c8 本项目是纯静态 SPA 网页,下载源码解压后:
可以用浏览器打开 index.html,建议用 [Chrome](https://www.google.com/intl/zh-CN/chrome) 或 [Firefox](https://www.mozilla.org/zh-CN/firefox) (Safari、Edge、IE 等可能有兼容问题),注意此方法不显示 svg 图标。
也可以用 [Webstorm](https://www.jetbrains.com/webstorm/), [IntelliJ IDEA](https://www.jetbrains.com/idea/), [Eclipse](https://www.eclipse.org/) 等 IDE 来打开。
-也可以部署到服务器并用 [Nginx](https://www.jianshu.com/p/11fa3a1a6d65) 或 [Node](https://segmentfault.com/a/1190000039744899) 反向代理,或者 [把源码放到 SpringBoot 项目的 resources/static 目录](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server)。
+也可以部署到服务器(内网能访问即可,不建议公网访问)并用 [Nginx](https://www.jianshu.com/p/11fa3a1a6d65) 或 [Node](https://segmentfault.com/a/1190000039744899) 反向代理,或者 [把源码放到 SpringBoot 项目的 resources/static 目录](https://github.com/APIJSON/APIJSON-Demo/tree/main/APIJSON-Java-Server)。
还可以直接访问官方网站 http://apijson.cn/api 或 http://apijson.cn:8080

把左侧 URL 输入框内基地址改为你主机的地址(例如 http://localhost:8080 ),
@@ -167,9 +167,9 @@ https://www.bilibili.com/video/BV1JZ4y1d7c8 右侧上方中间 3 个标签是默认的测试用户账号,点击登录/退出,左侧 - 删除,右侧 + 新增。

**自动生成文档、自动管理测试用例 这两个功能 需要部署 APIJSON 后端,建议用 APIJSONBoot 系列之一 Demo,见**
-https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server +https://github.com/APIJSON/APIJSON-Demo/tree/main/APIJSON-Java-Server -**建议使用已 [内置 APIAuto](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static) 的 [APIJSONBoot-MultiDataSource](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource),可以避免以下常见问题 1, 3, 4** +**建议使用已 [内置 APIAuto](https://github.com/APIJSON/APIJSON-Demo/tree/main/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static) 的 [APIJSONBoot-MultiDataSource](https://github.com/APIJSON/APIJSON-Demo/tree/main/APIJSON-Java-Server/APIJSONBoot-MultiDataSource),可以避免以下常见问题 1, 3, 4**
@@ -181,10 +181,10 @@ https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server 点右上角登录 > 点右上角设置 > 导入第三方文档(平台 URL) > 如果默认设置不符你的需求,可以在弹窗内修改 > 点上传按钮 #### 2.从浏览器 Network 接口信息界面或 Charles 等抓包工具复制后粘贴到 URL 输入框 -https://github.com/TommyLemon/APIAuto#%E7%99%BE%E5%BA%A6%E6%90%9C%E7%8B%97%E6%8A%96%E9%9F%B3%E5%85%AC%E7%BD%91%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%BC%94%E7%A4%BA +[README.md#百度搜狗抖音公网接口调用演示](README.md#%E7%99%BE%E5%BA%A6%E6%90%9C%E7%8B%97%E6%8A%96%E9%9F%B3%E5%85%AC%E7%BD%91%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%BC%94%E7%A4%BA) #### 3.调用 /delegate 代理接口来录制请求的方法、参数、Header、响应等信息 -https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource#%E4%BB%A3%E7%90%86%E6%8E%A5%E5%8F%A3%E5%8F%8A%E5%BD%95%E5%88%B6%E6%B5%81%E9%87%8F +https://github.com/APIJSON/APIJSON-Demo/tree/main/APIJSON-Java-Server/APIJSONBoot-MultiDataSource#%E4%BB%A3%E7%90%86%E6%8E%A5%E5%8F%A3%E5%8F%8A%E5%BD%95%E5%88%B6%E6%B5%81%E9%87%8F #### 4.打开分享链接来自动填充 URL、参数 JSON、请求头、参数注入配置、设置项 等 例如: @@ -225,7 +225,7 @@ npm i xxx 然后再启动 HTTP 服务。
启动成功后会有提示,点击链接或者复制到浏览器输入框打开即可。

-如果托管服务是用 [APIJSONBoot-MultiDataSource](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource) 部署的,
+如果托管服务是用 [APIJSONBoot-MultiDataSource](https://github.com/APIJSON/APIJSON-Demo/tree/main/APIJSON-Java-Server/APIJSONBoot-MultiDataSource) 部署的,
链接 host 后可以加上 /api,例如 http://localhost:3000/api/test/start,
通过这个接口来放宽前端执行时查询测试用例、参数配置等列表的条数,一次可批量执行更多用例。 @@ -244,11 +244,11 @@ https://github.com/TommyLemon/APIAuto/issues/9 如果是其它接口,则一般是以上原因或者被接口不支持 CORS 跨域,可以改为支持,
或者在 APIAuto 右上角设置开启托管服务器代理,通过后端代理访问接口,
注意默认是官网的托管服务器 http://apijson.cn:9090 ,仅支持公网,
-如果是贵公司内网,请按以上 [部署方法](https://github.com/TommyLemon/APIAuto#%E9%83%A8%E7%BD%B2%E6%96%B9%E6%B3%95) 文档来部署 APIJSON 后端到内网,并修改托管服务器地址。 +如果是贵公司内网,请按以上 [部署方法](README.md#%E9%83%A8%E7%BD%B2%E6%96%B9%E6%B3%95) 文档来部署 APIJSON 后端到内网,并修改托管服务器地址。 #### 2.没有生成文档 右上角设置项与数据库实际配置不一致 等
-https://github.com/Tencent/APIJSON/issues/85 +https://github.com/APIJSON/APIJSON/issues/85 #### 3.托管服务器访问不了 不能代理接口、不能展示文档、不能对断言结果纠错 等
@@ -295,13 +295,15 @@ https://github.com/TommyLemon/APIAuto/issues ![image](https://github.com/user-attachments/assets/c4aa8573-f8b3-4973-8c37-29677c06ac3b) 如果有什么问题或建议可以 [提 issue](https://github.com/TommyLemon/APIAuto/issues),交流技术,分享经验。
-如果你解决了某些 bug,或者新增了一些功能,欢迎 [提 PR 贡献代码](https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md),感激不尽。 +如果你解决了某些 bug,或者新增了一些功能,欢迎 [提 PR 贡献代码](https://github.com/APIJSON/APIJSON/blob/main/CONTRIBUTING.md),感激不尽。

### 生态项目 -[APIJSON](https://github.com/Tencent/APIJSON) 🏆 腾讯实时 零代码、全功能、强安全 ORM 库 🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构 +[APIJSON](https://github.com/APIJSON/APIJSON) 🏆 实时 零代码、全功能、强安全 ORM 库 🚀 后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构 + +[AutoUI](https://github.com/TommyLemon/AutoUI) 📱 AI 零代码快准稳 UI 自动化测试平台 🚀 3 像素内精准定位,2 毫秒内精准等待,自动断言 UI 和数据、界定前/后端 bug,用户包含腾讯,微信团队邀请分享 [CVAuto](https://github.com/TommyLemon/CVAuto) 👁 零代码零标注 CV AI 自动化测试平台 🚀 免除大量人工画框和打标签等,直接快速测试 CV 计算机视觉 AI 图像识别算法 @@ -309,15 +311,11 @@ https://github.com/TommyLemon/APIAuto/issues [SQLAuto](https://github.com/TommyLemon/SQLAuto) 智能零代码自动化测试 SQL 数据库工具,任意增删改查、任意 SQL 模板变量、一键批量生成参数组合、快速构造大量测试数据 -[UIGO](https://github.com/TommyLemon/UIGO) 📱 零代码快准稳 UI 智能录制回放平台 🚀 3 像素内自动精准定位,2 毫秒内自动精准等待,用户包含腾讯,微信团队邀请分享 - -[UIGOX](https://github.com/TommyLemon/UIGOX) UIGO 的 AndroidX 版 📱 零代码快准稳 UI 智能录制回放平台 🚀 3 像素内自动精准定位,2 毫秒内自动精准等待 - [apijson-doc](https://github.com/vincentCheng/apijson-doc) APIJSON 官方文档,提供排版清晰、搜索方便的文档内容展示,包括设计规范、图文教程等 [APIJSONdocs](https://github.com/ruoranw/APIJSONdocs) APIJSON 英文文档,提供排版清晰的文档内容展示,包括详细介绍、设计规范、使用方式等 -[apijson.org](https://github.com/APIJSON/apijson.org) APIJSON 官方网站,提供 APIJSON 的 功能简介、登记用户、作者与贡献者、相关链接 等 +[apijson.org](https://github.com/APIJSON/apijson.cn) APIJSON 官方网站,提供 APIJSON 的 功能简介、登记用户、作者与贡献者、相关链接 等 [APIJSON.NET](https://github.com/liaozb/APIJSON.NET) C# 版 APIJSON ,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite @@ -335,13 +333,15 @@ https://github.com/TommyLemon/APIAuto/issues [uliweb-apijson](https://github.com/zhangchunlin/uliweb-apijson) Python 版 APIJSON,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite 等 +[apijson-rust](https://github.com/APIJSON/apijson-rust) Rust 版 APIJSON,包含一个优雅、高性能的多数据源管理系统,支持 MySQL, PostgreSQL 等统一管理和操作 + [apijson-practice](https://github.com/vcoolwind/apijson-practice) BAT 技术专家开源的 APIJSON 参数校验注解 Library 及相关 Demo [Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP 快速开发框架,Demo 全面,注释详细,使用简单,代码严谨 ### 持续更新 -https://github.com/TommyLemon/APIAuto/commits/master +https://github.com/TommyLemon/APIAuto/commits/main ### 我要赞赏 diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/apijson/JSONResponse.js b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/apijson/JSONResponse.js index 2abab0c2..58af86b0 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/apijson/JSONResponse.js +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/apijson/JSONResponse.js @@ -185,6 +185,10 @@ var JSONResponse = { KEY_MSG: 'msg', KEY_THROW: 'throw', CODE_SUCCESS: 200, + IGNORE_KEYS: ['traceId', 'trace:stack', 'debug:info|help'], + UPGRADE_KEYS: ['price', 'amount', 'money', 'cash', 'spend', 'cost', 'income', 'outgoing', 'borrow', 'lend', 'gold', 'coin', 'diamond', 'credit', 'budget', 'quantity', 'balance', 'type', 'status', 'state', 'stage', 'mode', 'currency', 'percent', 'percentage', 'rate', 'ratio', 'cent', 'vip', 'tax', 'change', 'exchange', 'role', 'permission', 'access', 'wealth', 'fee', 'pay', 'sku', 'spu', 'level', 'reward', 'award', 'bonus', 'consume', 'produce', 'gift', 'order', 'buy', 'purchase', 'salary', 'earn', 'coupon', 'discount', 'phone', 'email', 'card', 'idcard', 'account'], + DOWNGRADE_KEYS: ['traceId', 'trace_id', 'date', 'time', 'datetime', 'date_time', 'dateTime', 'timestamp', 'create_time', 'update_time', 'created_at', 'updated_at', 'createTime', 'updateTime', 'createdAt', 'updatedAt', 'delete_time', 'deleteTime', 'deleted_at', 'cur_time', 'curTime', 'cur_timestamp', 'curTimestamp', 'cur_time_stamp', 'current_time', 'current_time_stamp', 'currentTime', 'currentTimestamp', 'server_time', 'serverTime', 'start_time', 'started_at', 'startTime', 'startedAt', 'begin_time', 'begun_at', 'beginTime', 'begunAt', 'end_time', 'ended_at', 'endTime', 'endedAt', 'title', 'subtitle', 'content', 'comment', 'emoji', 'name', 'nickname', 'detail', 'desc', 'describe', 'description'], + /**是否成功 * @param code * @return @@ -463,8 +467,10 @@ var JSONResponse = { COMPARE_KEY_MORE: 1, COMPARE_VALUE_MORE: 2, COMPARE_EQUAL_EXCEPTION: 3, + COMPARE_SORT_CHANGE: 4, // FIXME 不重复,后面都额外加判断? COMPARE_LENGTH_CHANGE: 4, COMPARE_VALUE_CHANGE: 5, + COMPARE_FILTER_CHANGE: 5, // FIXME 不重复,后面都额外加判断? COMPARE_VALUE_EMPTY: 6, COMPARE_KEY_LESS: 7, COMPARE_FORMAT_CHANGE: 8, @@ -521,6 +527,56 @@ var JSONResponse = { return it; }, + isUpgradeCompare: function (key, folder) { + if (StringUtil.isEmpty(key)) { + return false; + } + + var keys = JSONResponse.UPGRADE_KEYS || []; + if (keys.includes(key) || keys.includes(JSONResponse.getAbstractPath(folder, key))) { + return true; + } + + for (var i = 0; i < keys.length; i ++) { + var k = keys[i]; + if (StringUtil.isNotEmpty(k) && StringUtil.isKeyOfCategory(key, StringUtil.firstCase(k, true))) { + return true; + } + } + + return StringUtil.isMoneyKey(key) || StringUtil.isBalanceKey(key) || StringUtil.isAmountKey(key) || StringUtil.isLoanKey(key) + || StringUtil.isPriceKey(key) || StringUtil.isPercentKey(key) || StringUtil.isRevenueKey(key) || StringUtil.isProfitKey(key) + || StringUtil.isCashKey(key) || StringUtil.isDiscountKey(key) || StringUtil.isGradeKey(key) || StringUtil.isLevelKey(key) + || StringUtil.isRankKey(key) || StringUtil.isScoreKey(key) || StringUtil.isSexKey(key) || StringUtil.isGenderKey(key); + // || StringUtil.isIncomeKey(key) || StringUtil.isOutgoingKey(key) || StringUtil.isGoldKey(key) || StringUtil.isCoinKey(key) + // || StringUtil.isBuyKey(key) || StringUtil.isPurchaseKey(key) || StringUtil.isPayKey(key) || StringUtil.isEarnKey(key) + // || StringUtil.isSalaryKey(key) || StringUtil.isCostKey(key) || StringUtil.isConsumeKey(key) || StringUtil.isProduceKey(key) + // || StringUtil.isFeeKey(key) || StringUtil.isWealthKey(key) || StringUtil.isRewardKey(key) || StringUtil.isAwardKey(key) + // || StringUtil.isVipKey(key) || StringUtil.isDiamondKey(key) || StringUtil.isTaxKey(key) || StringUtil.isCurrencyKey(key) + // || StringUtil.isChangeKey(key) || StringUtil.isExchangeKey(key) || StringUtil.isBorrowKey(key) || StringUtil.isLendKey(key); + }, + isDowngradeCompare: function (key, folder) { + if (StringUtil.isEmpty(key)) { + return false; + } + + var keys = JSONResponse.DOWNGRADE_KEYS || []; + if (keys.includes(key) || keys.includes(JSONResponse.getAbstractPath(folder, key))) { + return true; + } + + for (var i = 0; i < keys.length; i ++) { + var k = keys[i]; + if (StringUtil.isNotEmpty(k) && StringUtil.isKeyOfCategory(key, StringUtil.firstCase(k, true))) { + return true; + } + } + + return StringUtil.isDateKey(key) || StringUtil.isTimeKey(key) || StringUtil.isNameKey(key) || StringUtil.isDetailKey(key) + || StringUtil.isTitleKey(key) || StringUtil.isTextKey(key) || StringUtil.isCommentKey(key) || StringUtil.isContentKey(key) + || StringUtil.isDescribeKey(key) || StringUtil.isMessageKey(key); + }, + /**测试compare: 对比 新的请求与上次请求的结果 0-相同,无颜色; 1-对象新增字段或数组新增值,绿色; @@ -853,7 +909,7 @@ var JSONResponse = { 3-缺少字段/整数变小数,黄色; 4-类型/code 改变,红色; */ - compareWithStandard: function(target, real, folder, exceptKeys, ignoreTrend, callback) { + compareWithStandard: function(target, real, folder, exceptKeys, ignoreTrend, preReal, reqSearches, reqSorts, reqLimits, callback) { folder = folder == null ? '' : folder; if (target == null) { @@ -917,7 +973,9 @@ var JSONResponse = { }; } - if (notEmpty == true && typeof real != 'boolean' && typeof real != 'number' && StringUtil.isEmpty(real, true)) { // 空 + var realType = JSONResponse.getType(real); + + if (notEmpty == true && (real === 0 || (typeof real != 'boolean' && CodeUtil.isTypeMatch('number', realType) && StringUtil.isEmpty(real, true)))) { // 空 log('compareWithStandard notEmpty == true && StringUtil.isEmpty(real, true) >> return COMPARE_VALUE_EMPTY'); return { code: JSONResponse.COMPARE_VALUE_EMPTY, @@ -934,7 +992,6 @@ var JSONResponse = { value: null //导致正确时也显示 real }; - var realType = JSONResponse.getType(real); if (StringUtil.isEmpty(realType) || ['null', 'undefined'].indexOf(realType) >= 0) { realType = null; } @@ -956,6 +1013,64 @@ var JSONResponse = { max.code -= 1; } + var isStr = type == 'string' + var isNum = (! isStr) && CodeUtil.isTypeMatch('number', type); + preReal = preReal != null ? preReal : (isNum ? 0 : (isStr ? '' : preReal)); + + var keys = StringUtil.splitPath(folder, false); + var key = StringUtil.isEmpty(keys) ? '' : (keys[keys.length - 1] || ''); + + var reqSearchObj = reqSearches == null || max.code >= JSONResponse.COMPARE_FILTER_CHANGE || ! isStr ? null : reqSearches[key]; + var reqSearch = reqSearchObj == null ? null : reqSearchObj.same; + if (StringUtil.isNotEmpty(reqSearch) && ! real.includes(reqSearch)) { + log('compareWithStandard StringUtil.isNotEmpty(reqSearch) && ! real.includes(reqSearch) >> return COMPARE_FILTER_CHANGE'); + max = { + code: JSONResponse.COMPARE_FILTER_CHANGE, + msg: '未包含传参 ' + (StringUtil.isEmpty(reqSearchObj.path) ? '' : reqSearchObj.path + ' 筛选 ') + reqSearch, + path: folder, + value: real + }; + } + + var same = max.code >= JSONResponse.COMPARE_FILTER_CHANGE || ! isStr ? null : target.same; + if (StringUtil.isNotEmpty(same) && ! real.includes(same)) { + log('compareWithStandard StringUtil.isNotEmpty(same) && ! real.includes(same) >> return COMPARE_FILTER_CHANGE'); + max = { + code: JSONResponse.COMPARE_FILTER_CHANGE, + msg: '未包含 ' + same, + path: folder, + value: real + }; + } + + var notCmpSort = reqSorts == null || max.code >= JSONResponse.COMPARE_SORT_CHANGE || ! (isStr || isNum) + var reqSortObj = notCmpSort ? null : reqSorts[key]; + var reqSort = reqSortObj == null ? null : reqSortObj.sort; + + var sort = notCmpSort ? null : target.sort; + if (StringUtil.isNotEmpty(reqSort) || StringUtil.isNotEmpty(sort)) { + var srt = (real === preReal ? '=' : (real > preReal ? '>' : '<')); + + if (! reqSort.includes(srt)) { + log('compareWithStandard StringUtil.isNotEmpty(reqSort) && ! ' + reqSort + '.includes(' + srt + ') >> return COMPARE_SORT_CHANGE'); + max = { + code: JSONResponse.COMPARE_SORT_CHANGE, + msg: '排序 ' + srt + ' 不符合传参 ' + (StringUtil.isEmpty(reqSortObj.path) ? '' : reqSortObj.path + ' ') + reqSort, + path: folder, + value: real + }; + } + + if (max.code < JSONResponse.COMPARE_SORT_CHANGE && ! sort.includes(srt)) { + log('compareWithStandard StringUtil.isNotEmpty(sort) && ! ' + sort + '.includes(' + srt + ') >> return COMPARE_SORT_CHANGE'); + max = { + code: JSONResponse.COMPARE_SORT_CHANGE, + msg: '排序 ' + sort + ' 变成 ' + srt, + path: folder, + value: real + }; + } + } var each; @@ -965,7 +1080,7 @@ var JSONResponse = { for (var i = 0; i < real.length; i ++) { //检查real的每一项 log('compareWithStandard for i = ' + i + ' >> '); - each = JSONResponse.compareWithStandard(firstVal, real[i], JSONResponse.getAbstractPath(folder, i), exceptKeys, ignoreTrend); + each = JSONResponse.compareWithStandard(firstVal, real[i], JSONResponse.getAbstractPath(folder, i), exceptKeys, ignoreTrend, i <= 0 ? null : real[i - 1], reqSearches, reqSorts, reqLimits); if (max.code < each.code) { max = each; @@ -1000,7 +1115,8 @@ var JSONResponse = { } log('compareWithStandard for tk = ' + tk + ' >> '); - each = JSONResponse.compareWithStandard(firstVal[tk], real[tk], JSONResponse.getAbstractPath(folder, tk), exceptKeys); + each = JSONResponse.compareWithStandard(firstVal[tk], real[tk], JSONResponse.getAbstractPath(folder, tk), exceptKeys + , ignoreTrend, JSONResponse.isObject(preReal) ? preReal[tk] : null, reqSearches, reqSorts, reqLimits); if (max.code < each.code) { max = each; } @@ -1050,7 +1166,14 @@ var JSONResponse = { else if (format instanceof Array == false && format instanceof Object) { try { var realObj = parseJSON(real); - var result = JSONResponse.compareWithStandard(format, realObj, folder, exceptKeys, ignoreTrend); + var preRealObj = null; + try { // FIXME 是否暴露? + preRealObj = parseJSON(preReal); + } catch (e) { + log(e); + } + + var result = JSONResponse.compareWithStandard(format, realObj, folder, exceptKeys, ignoreTrend, preRealObj); // , reqSearches, reqSorts, reqLimits); // TODO: 是否必要在字符串里再解析排序? if (guess == true) { result.code -= 1; } @@ -1059,21 +1182,53 @@ var JSONResponse = { max = result; } } catch (e) { - log(e) + log(e); } } } - var valueCompare = max.code >= JSONResponse.COMPARE_VALUE_CHANGE + var ignore = false; + var isFolderMeaningful = StringUtil.isNotEmpty(folder) && ! StringUtil.isNumber(folder); + if (isFolderMeaningful && (isNum || isStr)) { + if (JSONResponse.IGNORE_KEYS.includes(folder)) { + ignore = true; + } else if (folder.includes('/')) { + var keys = StringUtil.splitPath(folder); + var key = keys == null || keys.length <= 0 ? null : keys[keys.length - 1]; + if (StringUtil.isNotEmpty(key) && ! StringUtil.isNumber(key)) { + ignore = JSONResponse.IGNORE_KEYS.includes(key); + } + } + } + + var valueCompare = ignore || max.code >= JSONResponse.COMPARE_VALUE_CHANGE ? 0 : JSONResponse.compareValue(valueLevel, values, real, target.trend, target.repeat); if (valueCompare > 0) { - max.code = valueCompare; + var cmp = valueCompare; + if (isNum && isFolderMeaningful && cmp > JSONResponse.COMPARE_VALUE_MORE && cmp < JSONResponse.COMPARE_VALUE_EMPTY) { + if (JSONResponse.UPGRADE_KEYS.includes(folder)) { + cmp = JSONResponse.COMPARE_VALUE_EMPTY; + } else if (JSONResponse.DOWNGRADE_KEYS.includes(folder)) { + cmp = JSONResponse.COMPARE_VALUE_MORE; + } else if (folder.includes('/')) { + var keys = StringUtil.splitPath(folder); + var key = keys == null || keys.length <= 0 ? null : keys[keys.length - 1]; + if (StringUtil.isNotEmpty(key) && ! StringUtil.isNumber(key)) { + var fd = keys.slice(0, keys.length - 1).join('/'); + if (JSONResponse.isUpgradeCompare(key, fd)) { + cmp = JSONResponse.COMPARE_VALUE_EMPTY; + } else if (JSONResponse.isDowngradeCompare(key, fd)) { + cmp = JSONResponse.COMPARE_VALUE_MORE; + } + } + } + } + + max.code = cmp; max.path = folder; max.value = real; - var isNum = CodeUtil.isTypeMatch('number', type) - if (isNum && valueCompare == JSONResponse.COMPARE_VALUE_REPEAT && (target.repeat == null || target.repeat <= 0) && values != null && values.indexOf(real) >= 0) { max.msg = '值与历史值重复:' + real; @@ -1113,6 +1268,15 @@ var JSONResponse = { var realLength = JSONResponse.getLength(real); log('compareWithStandard realLength = ' + realLength + ' >> '); + var reqLimitObj = reqLimits == null || type !== 'array' ? null : reqLimits[key]; + var reqLimit = reqLimitObj == null ? null : reqLimitObj.limit; + if (Number.isSafeInteger(reqLimit) && reqLimit > 0 && reqLimit < realLength) { + max.code = JSONResponse.COMPARE_LENGTH_CHANGE; + max.msg = '长度 ' + realLength + ' > 传参限制 ' + reqLimit + ' 个 ' + StringUtil.get(reqLimitObj.path); + max.path = folder; + max.value = realLength; + } + if (realLength != null && JSONResponse.isValueCorrect(target.lengthLevel, target.lengths, realLength) != true) { var lengths = target.lengths var maxVal = lengths == null || lengths.length <= 0 ? null : lengths[0]; @@ -1353,7 +1517,7 @@ var JSONResponse = { /**更新测试标准,通过原来的标准与最新的数据合并来实现 */ - updateStandard: function(target, real, exceptKeys, ignoreTrend, key) { + updateStandard: function(target, real, exceptKeys, ignoreTrend, folder, key, preReal) { if (target instanceof Array) { // JSONArray throw new Error("Standard 语法错误,不应该有 array!"); } @@ -1377,8 +1541,14 @@ var JSONResponse = { var notEmpty = target.notEmpty; log('updateStandard notEmpty = target.notEmpty = ' + notEmpty + ' >>'); - if (notEmpty !== false && real != null && typeof real != 'boolean' && typeof real != 'number') { - notEmpty = target.notEmpty = StringUtil.isNotEmpty(real, true); + var rtype = JSONResponse.getType(real); + + if (notEmpty !== false) { + if (CodeUtil.isTypeMatch('number', rtype)) { // real === 0) { + notEmpty = target.notEmpty = real === 0 ? false : (real < -1 || real > 1 ? true : notEmpty); + } else if (real != null && typeof real != 'boolean') { + notEmpty = target.notEmpty = StringUtil.isNotEmpty(real, true); + } } var type = target.type; @@ -1386,7 +1556,6 @@ var JSONResponse = { target.type = type = null; } - var rtype = JSONResponse.getType(real); if ((rtype == null || real == null) && StringUtil.isEmpty(type, true) && StringUtil.isNotEmpty(key, true)) { target.guess = true; if (StringUtil.isBoolKey(key)) { @@ -1484,19 +1653,19 @@ var JSONResponse = { return target; } + var fd = JSONResponse.getAbstractPath(folder, key); + log('updateStandard type = target.type = ' + type + ' >>'); if (type == null || CodeUtil.isTypeMatch(target.type, rtype) != true) { //强制用real的类型替代 type = target.type = rtype; } log('updateStandard type = target.type = getType(real) = ' + type + ' >>'); - var lengthLevel = target.lengthLevel; var lengths = target.lengths; log('updateStandard lengthLevel = target.lengthLevel = ' + lengthLevel + ' >>'); log('updateStandard lengths = target.lengths = ' + lengths + ' >>'); - var valueLevel = target.valueLevel; var values = target.values; log('updateStandard valueLevel = target.valueLevel = ' + valueLevel + ' >>'); @@ -1507,6 +1676,8 @@ var JSONResponse = { valueLevel = target.valueLevel = 0; } + var sort = target.sort; + var same = target.same; if (type == 'array') { log('updateStandard type == array >> '); @@ -1519,11 +1690,41 @@ var JSONResponse = { } var child = values[0]; - for (var i = 0; i < real.length; i ++) { + var len = real.length; + for (var i = 0; i < len; i ++) { + var v = real[i]; log('updateStandard for i = ' + i + '; child = ' - + JSON.stringify(child, null, ' ') + ';\n real[i] = ' + JSON.stringify(real[i], null, ' ') + ' >>'); + + JSON.stringify(child, null, ' ') + ';\n real[i] = ' + JSON.stringify(v, null, ' ') + ' >>'); + if (i === 0 && len >= 2 && (sort !== '' || same !== '')) { + var isStr = JSONResponse.isString(v); + var isNum = (! isStr) && JSONResponse.isNumber(v); + if (isStr || (isNum && sort !== '')) { + var last = null; + for (let j = len - 1; j > 2; j--) { + last = real[len - 1]; + if ((JSONResponse.isNumber(last) && last !== 0) || (JSONResponse.isString(last) && last !== '')) { + break + } + } - child = JSONResponse.updateStandard(child, real[i], exceptKeys, true, key == null ? 'item' : key + 'Item'); //FIXME ignoreTrend 固定取 true 导致批量创建后多个 id [1,2,3] -> [3,4,5] 漏报趋势异常 + if (last == null) { + last = isNum ? 0 : ''; + } + var first = v != null ? v : (isNum ? 0 : ''); + + if (sort !== '') { // >, >=, ''; <, <=, ''; =, >=, ''; =, <=, '' + var srt = (last === first ? '=' : (last > first ? '>' : '<')); + target.sort = sort = sort == null || srt === sort ? srt : (sort.includes(srt) ? sort : (srt === '=' ? sort + '=' : '')); + } + + if (isStr && same !== '' && (StringUtil.isNotEmpty(first) || StringUtil.isNotEmpty(last))) { + var sm = StringUtil.isEmpty(first) ? last : (StringUtil.isEmpty(last) ? first : StringUtil.longestSubstr(first, last)); + target.same = same = same == null ? sm : (StringUtil.isEmpty(sm) ? same : StringUtil.longestSubstr(same, sm)); + } + } + } + + child = JSONResponse.updateStandard(child, v, exceptKeys, true, fd, '' + i, i <= 0 ? null : real[i - 1]); //FIXME ignoreTrend 固定取 true 导致批量创建后多个 id [1,2,3] -> [3,4,5] 漏报趋势异常 } if (child == null) { log('updateStandard child == null >> child = {}'); @@ -1531,7 +1732,7 @@ var JSONResponse = { } values = [child]; - target = JSONResponse.setValue(target, real.length, lengthLevel == null ? 1 : lengthLevel, lengths, true, true); + target = JSONResponse.setValue(target, len, lengthLevel == null ? 1 : lengthLevel, lengths, true, true); target = JSONResponse.setValue(target, null, valueLevel, values, false, ignoreTrend); } else if (type == 'object') { @@ -1570,15 +1771,34 @@ var JSONResponse = { continue } + var fv = firstVal[k]; + var rv = real[k]; log('updateStandard for k in real = ' + k + '; firstVal[k] = ' - + JSON.stringify(firstVal[k], null, ' ') + ';\n real[k] = ' + JSON.stringify(real[k], null, ' ') + ' >>'); - firstVal[k] = JSONResponse.updateStandard(firstVal[k], real[k], exceptKeys, ignoreTrend, k); + + JSON.stringify(fv, null, ' ') + ';\n real[k] = ' + JSON.stringify(rv, null, ' ') + ' >>'); + firstVal[k] = JSONResponse.updateStandard(fv, rv, exceptKeys, ignoreTrend, fd, k, JSONResponse.isObject(preReal) ? preReal[k] : null); } target.values = values; } else { - log('updateStandard type == other >> '); + log('updateStandard type == ' + type + ' >> '); + if (StringUtil.isNumber(key) && key >= '1' && (sort !== '' || same !== '') && type === JSONResponse.getType(preReal)) { + var isStr = JSONResponse.isString(real); + var isNum = (! isStr) && JSONResponse.isNumber(real); + if (isStr || (isNum && sort !== '')) { + if (sort !== '') { // >, >=, ''; <, <=, ''; =, >=, ''; =, <=, '' + preReal = preReal != null ? preReal : (isNum ? 0 : (isStr ? '' : preReal)); + var srt = (real === preReal ? '=' : (real > preReal ? '>' : '<')); + target.sort = sort = sort == null || srt === sort ? srt : (sort.includes(srt) ? sort : (srt === '=' ? sort + '=' : '')); + } + + if (isStr && same !== '' && StringUtil.isNotEmpty(real)) { + var sm = same == null ? real : StringUtil.longestSubstr(real, same); // preReal); + target.same = same = sm; // StringUtil.longestSubstr(same, sm); + } + } + } + if (type == 'string') { var format = target.format; try { @@ -1618,7 +1838,14 @@ var JSONResponse = { log(e) try { var realObj = parseJSON(real); - var format2 = JSONResponse.updateStandard(target.format, realObj, exceptKeys, ignoreTrend, key); + var preRealObj = null; + try { + preRealObj = parseJSON(preReal); + } catch (e2) { + log(e2) + } + + var format2 = JSONResponse.updateStandard(target.format, realObj, exceptKeys, ignoreTrend, folder, key, preRealObj); if (format2 != null) { target.format = format2; } @@ -1629,16 +1856,18 @@ var JSONResponse = { } } - if (values == null) { - values = []; - } - if (valueLevel < 1 && type == 'number' && ! Number.isSafeInteger(real)) { //double 1.23 - valueLevel = 1; - } - target.values = values; + if (! [null, 0, ''].includes(real)) { + if (values == null) { + values = []; + } + if (valueLevel < 1 && CodeUtil.isTypeMatch('number', type) && ! Number.isSafeInteger(real)) { //double 1.23 + valueLevel = 1; + } + target.values = values; - target = JSONResponse.setValue(target, JSONResponse.getLength(real), lengthLevel == null ? 1 : lengthLevel, lengths, true, true); - target = JSONResponse.setValue(target, real, valueLevel, values, false, ignoreTrend); + target = JSONResponse.setValue(target, JSONResponse.getLength(real), lengthLevel == null ? 1 : lengthLevel, lengths, true, true); + target = JSONResponse.setValue(target, real, valueLevel, values, false, ignoreTrend); + } } log('\nupdateStandard >> return target = ' + JSON.stringify(target, null, ' ') + '\n >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n\n\n\n\n'); diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/apijson/StringUtil.js b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/apijson/StringUtil.js index 1d5a40ef..441b255b 100644 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/apijson/StringUtil.js +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/apijson/StringUtil.js @@ -727,6 +727,9 @@ var StringUtil = { isScoreKey: function (key) { return StringUtil.isKeyOfCategory(key, 'Score'); }, + isRankKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Rank'); + }, isSexKey: function (key) { return StringUtil.isKeyOfCategory(key, 'Sex'); }, @@ -773,6 +776,28 @@ var StringUtil = { var l = StringUtil.length(key); return l >= 2 && l <= 30 && StringUtil.isKeyOfCategory(key, 'Name'); }, + isTitleKey: function (key) { + var l = StringUtil.length(key); + return l >= 2 && l <= 30 && StringUtil.isKeyOfCategory(key, 'Title'); + }, + isTextKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Text') || StringUtil.isKeyOfCategory(key, 'Txt'); + }, + isContentKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Content'); + }, + isMessageKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Message') || StringUtil.isKeyOfCategory(key, 'Msg'); + }, + isCommentKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Comment'); + }, + isDetailKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Detail'); + }, + isDescribeKey: function (key) { + return StringUtil.isKeyOfCategory(key, 'Desc') || StringUtil.isKeyOfCategory(key, 'Describe') || StringUtil.isKeyOfCategory(key, 'Description'); + }, isPathKey: function (key) { return StringUtil.isKeyOfCategory(key, 'Path'); }, @@ -783,10 +808,14 @@ var StringUtil = { return StringUtil.isKeyOfCategory(key, 'Uri'); }, isDateKey: function (key) { - return ['createat', 'createdat', 'updateat', 'updatedat'].indexOf(StringUtil.get(key).toLowerCase()) >= 0 || StringUtil.isKeyOfCategory(key, 'Date'); + return ['createat', 'createdat', 'updateat', 'updatedat', 'deletdat', 'deletedat', 'addat', 'addedat', 'editat' + , 'editedat', 'removeat', 'removedat', 'sendat', 'sentat', 'sendedat', 'receiveat', 'receivedat'].indexOf(StringUtil.get(key).toLowerCase()) >= 0 + || StringUtil.isKeyOfCategory(key, 'Date'); }, isTimeKey: function (key) { - return ['createat', 'createdat', 'updateat', 'updatedat'].indexOf(StringUtil.get(key).toLowerCase()) >= 0 || StringUtil.isKeyOfCategory(key, 'Time'); + return ['createat', 'createdat', 'updateat', 'updatedat', 'deletdat', 'deletedat', 'addat', 'addedat', 'editat' + , 'editedat', 'removeat', 'removedat', 'sendat', 'sentat', 'sendedat', 'receiveat', 'receivedat'].indexOf(StringUtil.get(key).toLowerCase()) >= 0 + || StringUtil.isKeyOfCategory(key, 'Time') || StringUtil.isKeyOfCategory(key, 'Timestamp'); }, isKeyOfCategory: function (key, category) { if (StringUtil.isEmpty(key, true) || StringUtil.isEmpty(category, true) || key.length < category.length) { @@ -849,6 +878,44 @@ var StringUtil = { } return StringUtil.trim(newStr); + }, + + longestSubstr: function(str1, str2, trim) { + let m = StringUtil.isString(str1) ? StringUtil.length(str1, trim) : 0; + let n = m <= 0 || ! StringUtil.isString(str2) ? 0 : StringUtil.length(str2, trim); + if (n <= 0) { + return ''; + } + + if (str1 === str2) { + return StringUtil.get(str1); + } + + // 创建一个二维数组,dp[i][j] 记录以 str1[i-1] 和 str2[j-1] 结尾的公共子串长度 + let dp = Array.from(Array(m + 1), () => Array(n + 1).fill(0)); + + let maxLength = 0; // 记录最长公共子串的长度 + let endIndex = 0; // 记录最长公共子串在 str1 中结束的位置 + + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + // 如果当前字符相等 + if (str1[i - 1] === str2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + + // 更新最大长度和结束索引 + if (dp[i][j] > maxLength) { + maxLength = dp[i][j]; + endIndex = i; // 这里的 i 是基于 1 的索引,对应 str1 的 endIndex + } + } else { + dp[i][j] = 0; // 不连续则清零 + } + } + } + + // 根据结束索引和最大长度,从 str1 中截取出最长公共子串 + return str1.substring(endIndex - maxLength, endIndex); } }; diff --git a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/index.html b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/index.html index 6288b889..46298f4b 100755 --- a/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/index.html +++ b/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/src/main/resources/static/api/index.html @@ -89,20 +89,19 @@ 2s - - +