diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index f656d471..f3d63376 100644 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ com.github.Tencent APIJSON-spring-boot3 - 6.2.0 + 6.4.0 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index a1fe3425..bc127594 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -14,7 +14,7 @@ public class Log { public static boolean DEBUG = true; - public static final String VERSION = "6.2.1"; + public static final String VERSION = "6.4.0"; public static final String KEY_SYSTEM_INFO_DIVIDER = "\n---|-----APIJSON SYSTEM INFO-----|---\n"; public static final String OS_NAME; diff --git a/APIJSONORM/src/main/java/apijson/RequestMethod.java b/APIJSONORM/src/main/java/apijson/RequestMethod.java index 410775c1..875200b7 100755 --- a/APIJSONORM/src/main/java/apijson/RequestMethod.java +++ b/APIJSONORM/src/main/java/apijson/RequestMethod.java @@ -83,6 +83,14 @@ public static boolean isHeadMethod(RequestMethod method, boolean containPrivate) public static boolean isQueryMethod(RequestMethod method) { return isGetMethod(method, true) || isHeadMethod(method, true); } + + /**是否为更新(增删改)的请求方法 + * @param method + * @return 读操作(GET型或HEAD型) - false, 写操作(POST,PUT,DELETE) - true + */ + public static boolean isUpdateMethod(RequestMethod method) { + return ! isQueryMethod(method); + } /**是否为开放(不限制请求的结构或内容;明文,浏览器能直接访问及查看)的请求方法 * @param method @@ -92,6 +100,14 @@ public static boolean isPublicMethod(RequestMethod method) { return method == null || method == GET || method == HEAD; } + /**是否为私有(限制请求的结构或内容)的请求方法 + * @param method + * @return + */ + public static boolean isPrivateMethod(RequestMethod method) { + return ! isPublicMethod(method); + } + public static String getName(RequestMethod method) { return method == null ? GET.name() : method.name(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 0b8c8607..bfb3b1e1 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -157,7 +157,7 @@ public abstract class AbstractSQLConfig implements SQLConfig\\|\\[\\]\\{\\} /\\.\\+\\-\\*\\^\\?\\(\\)\\$]+$"); - TABLE_KEY_MAP = new HashMap(); + TABLE_KEY_MAP = new HashMap<>(); TABLE_KEY_MAP.put(Table.class.getSimpleName(), Table.TABLE_NAME); TABLE_KEY_MAP.put(Column.class.getSimpleName(), Column.TABLE_NAME); TABLE_KEY_MAP.put(PgClass.class.getSimpleName(), PgClass.TABLE_NAME); @@ -195,9 +195,15 @@ public abstract class AbstractSQLConfig implements SQLConfig(); // 保证顺序,避免配置冲突等意外情况 @@ -1189,6 +1195,14 @@ public static boolean isPresto(String db) { return DATABASE_PRESTO.equals(db); } + @Override + public boolean isTrino() { + return isTrino(getSQLDatabase()); + } + public static boolean isTrino(String db) { + return DATABASE_TRINO.equals(db); + } + @Override public boolean isSnowflake() { return isSnowflake(getSQLDatabase()); @@ -1214,11 +1228,11 @@ public static boolean isCassandra(String db) { } @Override - public boolean isTrino() { - return isTrino(getSQLDatabase()); + public boolean isMilvus() { + return isMilvus(getSQLDatabase()); } - public static boolean isTrino(String db) { - return DATABASE_TRINO.equals(db); + public static boolean isMilvus(String db) { + return DATABASE_MILVUS.equals(db); } @Override @@ -1245,12 +1259,28 @@ public static boolean isRedis(String db) { return DATABASE_REDIS.equals(db); } + @Override + public boolean isMongoDB() { + return isMongoDB(getSQLDatabase()); + } + public static boolean isMongoDB(String db) { + return DATABASE_MONGODB.equals(db); + } + + @Override + public boolean isKafka() { + return isKafka(getSQLDatabase()); + } + public static boolean isKafka(String db) { + return DATABASE_KAFKA.equals(db); + } + @Override public boolean isMQ() { return isMQ(getSQLDatabase()); } public static boolean isMQ(String db) { - return DATABASE_MQ.equals(db); + return DATABASE_MQ.equals(db) || isKafka(db); } @Override @@ -1258,7 +1288,7 @@ public String getQuote() { if(isElasticsearch()) { return ""; } - return isMySQL() || isMariaDB() || isTiDB() || isClickHouse() || isTDengine() ? "`" : "\""; + return isMySQL() || isMariaDB() || isTiDB() || isClickHouse() || isTDengine() || isMilvus() ? "`" : "\""; } public String quote(String s) { @@ -1272,6 +1302,7 @@ public String getSchema() { } @NotNull + @Override public String getSQLSchema() { String table = getTable(); //强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 @@ -1953,14 +1984,14 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } // 简单点, 后台配置就带上 AS - // int index = expression.lastIndexOf(":"); - // String alias = expression.substring(index+1); - // boolean hasAlias = StringUtil.isName(alias); - // String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; - // if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 - // expression = pre + (hasAlias ? " AS " + alias : ""); - // continue; - // } + int index = expression.lastIndexOf(":"); + String alias = expression.substring(index+1); + boolean hasAlias = StringUtil.isName(alias); + String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; + if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 + keys[i] = pre + (hasAlias ? " AS " + alias : ""); + continue; + } } if (expression.length() > 100) { @@ -2001,6 +2032,16 @@ public String parseSQLExpression(String key, String expression, boolean containR * @return */ public String parseSQLExpression(String key, String expression, boolean containRaw, boolean allowAlias, String example) { + if (containRaw) { + String s = RAW_MAP.get(expression); + if ("".equals(s)) { + return expression; + } + if (s != null) { + return s; + } + } + String quote = getQuote(); int start = expression.indexOf('('); if (start < 0) { @@ -2578,9 +2619,22 @@ public static int getOffset(int page, int count) { */ @JSONField(serialize = false) public String getLimitString() { - if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { + int count = getCount(); + + if (isMilvus()) { + if (count == 0) { + Parser parser = getParser(); + count = parser == null ? AbstractParser.MAX_QUERY_COUNT : parser.getMaxQueryCount(); + } + + int offset = getOffset(getPage(), count); + return " LIMIT " + offset + ", " + count; // 目前 moql-transx 的限制 + } + + if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { // TODO HEAD 真的不需要 LIMIT ? return ""; } + return getLimitString( getPage() , getCount() diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 79c1d119..b2eca8bc 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -80,7 +80,7 @@ public abstract class AbstractVerifier implements Verifier, IdCallback { private static final String TAG = "AbstractVerifier"; - /**为 PUT, DELETE 强制要求必须有 id/id{} 条件 + /**为 PUT, DELETE 强制要求必须有 id/id{}/id{}@ 条件 */ public static boolean IS_UPDATE_MUST_HAVE_ID_CONDITION = true; /**开启校验请求角色权限 @@ -700,8 +700,9 @@ public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 不能传 " + finalIdKey + " !"); } } else { - if (RequestMethod.isQueryMethod(method) == false) { - verifyId(method.name(), name, key, robj, finalIdKey, maxUpdateCount, IS_UPDATE_MUST_HAVE_ID_CONDITION); + Boolean atLeastOne = tobj == null ? null : tobj.getBoolean(Operation.IS_ID_CONDITION_MUST.name()); + if (Boolean.TRUE.equals(atLeastOne) || RequestMethod.isUpdateMethod(method)) { + verifyId(method.name(), name, key, robj, finalIdKey, maxUpdateCount, atLeastOne != null ? atLeastOne : IS_UPDATE_MUST_HAVE_ID_CONDITION); String userIdKey = idCallback == null ? null : idCallback.getUserIdKey(db, sh, ds, key); String finalUserIdKey = StringUtil.isEmpty(userIdKey, false) ? apijson.JSONObject.KEY_USER_ID : userIdKey; @@ -746,7 +747,7 @@ private static void verifyId(@NotNull String method, @NotNull String name, @NotN Object id = robj.get(idKey); //如果必须传 id ,可在Request表中配置NECESSARY if (id != null && id instanceof Number == false && id instanceof String == false) { throw new IllegalArgumentException(method + "请求," + name + "/" + key - + " 里面的 " + idKey + ":value 中value的类型只能是 Long 或 String !"); + + " 里面的 " + idKey + ":value 中value的类型只能是 Long 或 String !"); } @@ -795,7 +796,7 @@ else if (o instanceof String) { } else { throw new IllegalArgumentException(method + "请求," + name + "/" + key - + " 里面的 " + idInKey + ":[] 中所有项的类型都只能是 Long 或 String !"); + + " 里面的 " + idInKey + ":[] 中所有项的类型都只能是 Long 或 String !"); } } } diff --git a/APIJSONORM/src/main/java/apijson/orm/Operation.java b/APIJSONORM/src/main/java/apijson/orm/Operation.java index 6a588a94..2976d09b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Operation.java +++ b/APIJSONORM/src/main/java/apijson/orm/Operation.java @@ -145,8 +145,13 @@ public enum Operation { /** * 允许批量增删改部分失败,结构是 * "Table[],key[],key:alias[]" - * 自动 ALLOW_PARTIAL_UPDATE_FAILED_TABLE_MAP.put + * 自动 ALLOW_PARTIAL_UPDATE_FAILED_TABLE_MAP.put,结构是 Boolean,例如 true */ - ALLOW_PARTIAL_UPDATE_FAIL; + ALLOW_PARTIAL_UPDATE_FAIL, + + /** + * 强制要求必须有 id/id{}/id{}@ 条件,结构是 Boolean,例如 true + */ + IS_ID_CONDITION_MUST; } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index a453d9a8..7372630c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -34,10 +34,13 @@ public interface SQLConfig { String DATABASE_SNOWFLAKE = "SNOWFLAKE"; // https://www.snowflake.com String DATABASE_DATABRICKS = "DATABRICKS"; // https://www.databricks.com String DATABASE_CASSANDRA = "CASSANDRA"; // https://cassandra.apache.org + String DATABASE_MILVUS = "MILVUS"; // https://milvus.io String DATABASE_INFLUXDB = "INFLUXDB"; // https://www.influxdata.com/products/influxdb-overview String DATABASE_TDENGINE = "TDENGINE"; // https://tdengine.com - String DATABASE_REDIS = "REDIS"; - String DATABASE_MQ = "MQ"; + String DATABASE_REDIS = "REDIS"; // https://redisql.com + String DATABASE_MONGODB = "MONGODB"; // https://www.mongodb.com/docs/atlas/data-federation/query/query-with-sql + String DATABASE_KAFKA = "KAFKA"; // https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server/APIJSONDemo-MultiDataSource-Kafka + String DATABASE_MQ = "MQ"; // String SCHEMA_INFORMATION = "information_schema"; //MySQL, PostgreSQL, SQL Server 都有的系统模式 String SCHEMA_SYS = "sys"; //SQL Server 系统模式 @@ -50,19 +53,19 @@ public interface SQLConfig { Parser getParser(); - AbstractSQLConfig setParser(Parser parser); + SQLConfig setParser(Parser parser); ObjectParser getObjectParser(); - AbstractSQLConfig setObjectParser(ObjectParser objectParser); + SQLConfig setObjectParser(ObjectParser objectParser); int getVersion(); - AbstractSQLConfig setVersion(int version); + SQLConfig setVersion(int version); String getTag(); - AbstractSQLConfig setTag(String tag); + SQLConfig setTag(String tag); boolean isMySQL(); boolean isPostgreSQL(); @@ -77,17 +80,20 @@ public interface SQLConfig { boolean isClickHouse(); boolean isHive(); boolean isPresto(); + boolean isTrino(); boolean isSnowflake(); boolean isDatabricks(); boolean isCassandra(); - boolean isTrino(); + boolean isMilvus(); boolean isInfluxDB(); boolean isTDengine(); boolean isRedis(); + boolean isMongoDB(); + boolean isKafka(); boolean isMQ(); - //暂时只兼容以上几种 + // 暂时只兼容以上几种 // boolean isSQL(); // boolean isTSQL(); // boolean isPLSQL(); @@ -215,6 +221,7 @@ default int[] getDBVersionNums() { String getDatabase(); SQLConfig setDatabase(String database); + String getSQLSchema(); String getSchema(); SQLConfig setSchema(String schema); @@ -243,7 +250,7 @@ default int[] getDBVersionNums() { String getTablePath(); Map getKeyMap(); - AbstractSQLConfig setKeyMap(Map keyMap); + SQLConfig setKeyMap(Map keyMap); List getRaw(); SQLConfig setRaw(List raw); diff --git a/APIJSONORM/src/main/java/apijson/orm/Verifier.java b/APIJSONORM/src/main/java/apijson/orm/Verifier.java index 366adb07..a4ab7205 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Verifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/Verifier.java @@ -18,7 +18,6 @@ public interface Verifier { /**验证权限是否通过 * @param config - * @param visitor * @return * @throws Exception */ @@ -37,12 +36,10 @@ public interface Verifier { void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception; /**登录校验 - * @param config * @throws Exception */ void verifyLogin() throws Exception; /**管理员角色校验 - * @param config * @throws Exception */ void verifyAdmin() throws Exception; @@ -67,24 +64,36 @@ public interface Verifier { void verifyRepeat(String table, String key, Object value, long exceptId) throws Exception; /**验证请求参数的数据和结构 - * @param table - * @param key - * @param value - * @param exceptId 不包含id + * @param method + * @param name + * @param target + * @param request + * @param maxUpdateCount + * @param globalDatabase + * @param globalSchema + * @param creator + * @return * @throws Exception */ JSONObject verifyRequest(RequestMethod method, String name, JSONObject target, JSONObject request, int maxUpdateCount, String globalDatabase, String globalSchema, SQLCreator creator) throws Exception; /**验证返回结果的数据和结构 - * @param table - * @param key - * @param value - * @param exceptId 不包含id + * @param method + * @param name + * @param target + * @param response + * @param database + * @param schema + * @param creator + * @param callback + * @return * @throws Exception */ - JSONObject verifyResponse(RequestMethod method, String name, JSONObject target, JSONObject response, - String database, String schema, SQLCreator creator, OnParseCallback callback) throws Exception; + JSONObject verifyResponse( + RequestMethod method, String name, JSONObject target, JSONObject response, + String database, String schema, SQLCreator creator, OnParseCallback callback + ) throws Exception; @NotNull diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ffcbf272..f9c33a7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ - [gdjs2](https://github.com/gdjs2)(University of California, Riverside) - [Rkyzzy](https://github.com/Rkyzzy)(SUSTech, University of California, Berkeley) - [kxlv2000](https://github.com/kxlv2000)(SUSTech) -- [caohao-php](https://github.com/caohao-php)(腾讯工程师) +- [caohao-go](https://github.com/caohao-go)(腾讯工程师,曾在华为、恒生担任C/C++开发工程师,在wps担任项目经理,在360担任技术专家) - [Wscats](https://github.com/Wscats)(腾讯工程师、腾讯 AlloyTeam 成员、Tencent Creation Camp 成员、知名技术博主) - [jun0315](https://github.com/jun0315)(腾讯工程师) - [JieJo](https://github.com/JieJo) diff --git a/README.md b/README.md index 76721b69..fc9b199a 100644 --- a/README.md +++ b/README.md @@ -24,17 +24,19 @@ This source code is licensed under the Apache License Version 2.0
- - - + - + + + + +

@@ -351,6 +353,7 @@ https://github.com/Tencent/APIJSON/issues/187 * [爱投斯智能技术(深圳)有限公司](http://www.aiotos.net) * [邻盛科技(武汉)有限公司](http://www.linksame.com) * [上海麦市信息科技有限公司](https://www.masscms.com) + * [上海翊丞互联网科技有限公司](http://www.renrencjl.com/home) ### 贡献者们 主项目 APIJSON 的贡献者们(6 个腾讯工程师、1 个微软工程师、1 个阿里云工程师、1 个字节跳动工程师、1 个网易工程师、1 个 Zoom 工程师、1 个圆通工程师、1 个知乎基础研发架构师、1 个智联招聘工程师、1 个美国加州大学学生、3 个 SUSTech 学生等):
@@ -680,7 +683,8 @@ Issue/问卷 一般解答顺序:贡献者 > 帮助他人的用户 > 提供任 [quick-boot](https://github.com/csx-bill/quick-boot) 基于 Spring Cloud 2022、Spring Boot 3、AMIS 和 APIJSON 的低代码系统。 [apijson-query-spring-boot-starter](https://gitee.com/mingbaobaba/apijson-query-spring-boot-starter) 一个快速构建 APIJSON 查询条件的插件 - + +[apijson-builder](https://github.com/yeli19950109/apijson-builder) 简单包装 APIJSON,相比直接构造查询 JSON 更好记,ts 编写,调整了一些参数和使用方式 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧~