diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 391319beb..8b36d0da4 100644 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ com.github.Tencent APIJSON - 8.0.0 + 8.1.3 jar APIJSONORM diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index c31170c44..0a14e0420 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -674,4 +674,18 @@ public static String getString(Map map, String key) { return value.toString(); } + + public static Object getFromObjOrArr(Object parent, String k) { + if (parent instanceof Map) { + return ((Map) parent).get(k); + } + + if (parent instanceof List) { + int j = Integer.valueOf(k); + return ((List) parent).get(j); + } + + return null; + } + } diff --git a/APIJSONORM/src/main/java/apijson/JSONCreator.java b/APIJSONORM/src/main/java/apijson/JSONCreator.java index fcabe2fe0..df0d9066a 100755 --- a/APIJSONORM/src/main/java/apijson/JSONCreator.java +++ b/APIJSONORM/src/main/java/apijson/JSONCreator.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/JSONList.java b/APIJSONORM/src/main/java/apijson/JSONList.java index 0aa448fcb..092bf9f39 100644 --- a/APIJSONORM/src/main/java/apijson/JSONList.java +++ b/APIJSONORM/src/main/java/apijson/JSONList.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/JSONMap.java b/APIJSONORM/src/main/java/apijson/JSONMap.java index 0bf0b6825..c29130553 100755 --- a/APIJSONORM/src/main/java/apijson/JSONMap.java +++ b/APIJSONORM/src/main/java/apijson/JSONMap.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -109,19 +109,32 @@ public static boolean isTableArray(String key) { public static String KEY_USER_ID = "userId"; public static String KEY_USER_ID_IN = KEY_USER_ID + "{}"; + default String getIdKey() { + return KEY_ID; + } + default String getIdInKey() { + return KEY_ID_IN; + } + default String getUserIdKey() { + return KEY_USER_ID; + } + default String getUserIdInKey() { + return KEY_USER_ID_IN; + } + /**set "id":id in Table layer * @param id * @return */ default JSONMap setId(Long id) { - return puts(KEY_ID, id); + return puts(getIdKey(), id); } /**set "id{}":[] in Table layer * @param list * @return */ default JSONMap setIdIn(List list) { - return puts(KEY_ID_IN, list); + return puts(getIdInKey(), list); } /**set "userId":userId in Table layer @@ -129,14 +142,14 @@ default JSONMap setIdIn(List list) { * @return */ default JSONMap setUserId(Long id) { - return puts(KEY_USER_ID, id); + return puts(getUserIdKey(), id); } /**set "userId{}":[] in Table layer * @param list * @return */ default JSONMap setUserIdIn(List list) { - return puts(KEY_USER_ID_IN, list); + return puts(getUserIdInKey(), list); } @@ -150,36 +163,38 @@ default JSONMap setUserIdIn(List list) { //@key关键字都放这个类 <<<<<<<<<<<<<<<<<<<<<< - String KEY_TRY = "@try"; //尝试,忽略异常 - String KEY_CATCH = "@catch"; //TODO 捕捉到异常后,处理方式 null-不处理;DEFAULT-返回默认值;ORIGIN-返回请求里的原始值 - String KEY_DROP = "@drop"; //丢弃,不返回,TODO 应该通过 fastjson 的 ignore 之类的机制来处理,避免导致下面的对象也不返回 - // String KEY_KEEP = "@keep"; //一定会返回,为 null 或 空对象时,会使用默认值(非空),解决其它对象因为不关联的第一个对为空导致也不返回 - String KEY_DEFULT = "@default"; //TODO 自定义默认值 { "@default":true },@default 可完全替代 @keep - String KEY_NULL = "@null"; //值为 null 的键值对 "@null":"tag,pictureList",允许 is NULL 条件判断, SET tag = NULL 修改值为 NULL 等 - String KEY_CAST = "@cast"; //类型转换 cast(date AS DATE) - - String KEY_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限 - String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL - String KEY_DATASOURCE = "@datasource"; //数据源 - String KEY_NAMESPACE = "@namespace"; //命名空间,Table 在非默认 namespace 内时需要声明 - String KEY_CATALOG = "@catalog"; //目录,Table 在非默认 catalog 内时需要声明 - String KEY_SCHEMA = "@schema"; //数据库,Table 在非默认 schema 内时需要声明 - String KEY_EXPLAIN = "@explain"; //分析 true/false - String KEY_CACHE = "@cache"; //缓存 RAM/ROM/ALL - String KEY_COLUMN = "@column"; //查询的Table字段或SQL函数 - String KEY_FROM = "@from"; //FROM语句 - String KEY_COMBINE = "@combine"; //条件组合,每个条件key前面可以放&,|,!逻辑关系 "id!{},&sex,!name&$" - String KEY_GROUP = "@group"; //分组方式 - String KEY_HAVING = "@having"; //聚合函数条件,一般和@group一起用 - String KEY_HAVING_AND = "@having&"; //聚合函数条件,一般和@group一起用 - String KEY_SAMPLE = "@sample"; //取样方式 - String KEY_LATEST = "@latest"; //最近方式 - String KEY_PARTITION = "@partition"; //分区方式 - String KEY_FILL = "@fill"; //填充方式 - String KEY_ORDER = "@order"; //排序方式 + String KEY_TRY = "@try"; // 尝试,忽略异常 + String KEY_CATCH = "@catch"; // TODO 捕捉到异常后,处理方式 null-不处理;DEFAULT-返回默认值;ORIGIN-返回请求里的原始值 + String KEY_DROP = "@drop"; // 丢弃,不返回,TODO 应该通过 fastjson 的 ignore 之类的机制来处理,避免导致下面的对象也不返回 + // String KEY_KEEP = "@keep"; // 一定会返回,为 null 或 空对象时,会使用默认值(非空),解决其它对象因为不关联的第一个对为空导致也不返回 + String KEY_DEFAULT = "@default"; // TODO 自定义默认值 { "@default":true },@default 可完全替代 @keep + String KEY_NULL = "@null"; // 值为 null 的键值对 "@null":"tag,pictureList",允许 is NULL 条件判断, SET tag = NULL 修改值为 NULL 等 + String KEY_CAST = "@cast"; // 类型转换 cast(date AS DATE) + + String KEY_ROLE = "@role"; // 角色,拥有对某些数据的某些操作的权限 + String KEY_DATABASE = "@database"; // 数据库类型,默认为MySQL + String KEY_DATASOURCE = "@datasource"; // 数据源 + String KEY_NAMESPACE = "@namespace"; // 命名空间,Table 在非默认 namespace 内时需要声明 + String KEY_CATALOG = "@catalog"; // 目录,Table 在非默认 catalog 内时需要声明 + String KEY_SCHEMA = "@schema"; // 数据库,Table 在非默认 schema 内时需要声明 + String KEY_EXPLAIN = "@explain"; // 分析 true/false + String KEY_CACHE = "@cache"; // 缓存 RAM/ROM/ALL + String KEY_COLUMN = "@column"; // 查询的 Table 字段或 SQL 函数 + String KEY_FROM = "@from"; // FROM语句 + String KEY_COMBINE = "@combine"; // 条件组合,每个条件 key 前面可以放 &,|,! 逻辑关系 "id!{},&sex,!name&$" + String KEY_GROUP = "@group"; // 分组方式 + String KEY_HAVING = "@having"; // 聚合函数条件,一般和 @group 一起用 + String KEY_HAVING_AND = "@having&"; // 聚合函数条件,一般和 @group 一起用 + String KEY_SAMPLE = "@sample"; // 取样方式 + String KEY_LATEST = "@latest"; // 最近方式 + String KEY_PARTITION = "@partition"; // 分区方式 + String KEY_FILL = "@fill"; // 填充方式 + String KEY_ORDER = "@order"; // 排序方式 String KEY_KEY = "@key"; // key 映射,year:left(date,4);name_tag:(name,tag) String KEY_RAW = "@raw"; // 自定义原始 SQL 片段 - String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出 + String KEY_JSON = "@json"; // 把字段转为 JSON 输出 + String KEY_STRING = "@string"; // 把字段转为 String 输入 + String KEY_TRIM = "@trim"; // 去除首位空格等空白字符 String KEY_METHOD = "@method"; // json 对象配置操作方法 String KEY_GET = "@get"; // json 对象配置操作方法 String KEY_GETS = "@gets"; // json 对象配置操作方法 @@ -214,6 +229,8 @@ default JSONMap setUserIdIn(List list) { KEY_KEY, KEY_RAW, KEY_JSON, + KEY_STRING, + KEY_TRIM, KEY_METHOD, KEY_GET, KEY_GETS, @@ -255,7 +272,7 @@ default JSONMap setDrop(Boolean drop) { * @return this */ default JSONMap setDefault(Boolean hasDefault) { - return puts(KEY_DEFULT, hasDefault); + return puts(KEY_DEFAULT, hasDefault); } @@ -525,6 +542,22 @@ default JSONMap setJson(String keys) { return puts(KEY_JSON, keys); } + /**set keys to cast to string + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setString(String keys) { + return puts(KEY_STRING, keys); + } + + /**set keys to cast to string + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setTrim(String keys) { + return puts(KEY_TRIM, keys); + } + //JSONObject内关键词 key >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> diff --git a/APIJSONORM/src/main/java/apijson/JSONParser.java b/APIJSONORM/src/main/java/apijson/JSONParser.java index 6762e2bff..7c38a39df 100755 --- a/APIJSONORM/src/main/java/apijson/JSONParser.java +++ b/APIJSONORM/src/main/java/apijson/JSONParser.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/JSONRequest.java b/APIJSONORM/src/main/java/apijson/JSONRequest.java index 0dccbd3e6..c74dfe349 100755 --- a/APIJSONORM/src/main/java/apijson/JSONRequest.java +++ b/APIJSONORM/src/main/java/apijson/JSONRequest.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/JSONResponse.java b/APIJSONORM/src/main/java/apijson/JSONResponse.java index ab0564f99..7527adffb 100755 --- a/APIJSONORM/src/main/java/apijson/JSONResponse.java +++ b/APIJSONORM/src/main/java/apijson/JSONResponse.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -119,7 +119,7 @@ public static String getMsg(Map response) { */ default long getId() { try { - return JSON.getLongValue(this, KEY_ID); + return JSON.getLongValue(this, getIdKey()); } catch (Exception e) { //empty } diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index bd091c4e3..d301cdf0c 100755 --- a/APIJSONORM/src/main/java/apijson/Log.java +++ b/APIJSONORM/src/main/java/apijson/Log.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -7,14 +7,21 @@ import java.text.SimpleDateFormat; -/**测试用Log +/**测试用日志 * @modifier Lemon */ public class Log { + public static boolean DEBUG = false; - public static boolean DEBUG = true; + public static final String LEVEL_VERBOSE = "VERBOSE"; + public static final String LEVEL_INFO = "INFO"; + public static final String LEVEL_DEBUG = "DEBUG"; + public static final String LEVEL_WARN = "WARN"; + public static final String LEVEL_ERROR = "ERROR"; - public static final String VERSION = "8.0.0"; + public static String LEVEL = LEVEL_WARN; + + public static final String VERSION = "8.1.5"; public static final String KEY_SYSTEM_INFO_DIVIDER = "\n---|-----APIJSON SYSTEM INFO-----|---\n"; public static final String OS_NAME; @@ -46,23 +53,26 @@ public static void setDateFormat(String dateFormatString) { * @param msg * @param level */ - public static void logInfo(String TAG, String msg, String level){ - if(level.equals("DEBUG") || level .equals("ERROR") ||level.equals("WARN")){ - System.err.println(DATE_FORMAT.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); + public static void logInfo(String TAG, String msg, String level) { + if (level == null || level.isEmpty()) { + level = LEVEL; } - else if(level.equals("VERBOSE") || level .equals("INFO") ){ + + if (level.equals(LEVEL_VERBOSE) || level.equals(LEVEL_INFO)) { System.out.println(DATE_FORMAT.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); } + else if (level.equals(LEVEL_DEBUG) || level.equals(LEVEL_ERROR) || level.equals(LEVEL_WARN)) { + System.err.println(DATE_FORMAT.format(System.currentTimeMillis()) + ": " + TAG + "." + level + ": " + msg); + } } - /** * @param TAG * @param msg */ public static void d(String TAG, String msg) { if (DEBUG) { - logInfo(TAG,msg,"DEBUG"); + logInfo(TAG, msg, LEVEL_DEBUG); } } @@ -72,7 +82,7 @@ public static void d(String TAG, String msg) { * @param msg debug messages */ public static void fd(String TAG, String msg) { - logInfo(TAG,msg,"DEBUG"); + logInfo(TAG, msg, LEVEL_DEBUG); } /** @@ -81,8 +91,8 @@ public static void fd(String TAG, String msg) { * @param symbol used for generating separation line * @param post postfix */ - public static void sl(String pre,char symbol ,String post) { - System.err.println(pre+new String(new char[48]).replace('\u0000', symbol)+post); + public static void sl(String pre, char symbol, String post) { + System.err.println(pre + new String(new char[48]).replace('\u0000', symbol) + post); } /** @@ -91,7 +101,7 @@ public static void sl(String pre,char symbol ,String post) { */ public static void v(String TAG, String msg) { if (DEBUG) { - logInfo(TAG,msg,"VERBOSE"); + logInfo(TAG, msg, LEVEL_VERBOSE); } } @@ -101,7 +111,7 @@ public static void v(String TAG, String msg) { */ public static void i(String TAG, String msg) { if (DEBUG) { - logInfo(TAG,msg,"INFO"); + logInfo(TAG, msg, LEVEL_INFO); } } @@ -111,7 +121,20 @@ public static void i(String TAG, String msg) { */ public static void e(String TAG, String msg) { if (DEBUG) { - logInfo(TAG,msg,"ERROR"); + logInfo(TAG, msg, LEVEL_ERROR); + } + } + + /** + * @param TAG + * @param msg + */ + public static void e(String TAG, String msg, Throwable e) { + if (DEBUG) { + if (e != null) { + e.printStackTrace(); + } + logInfo(TAG, msg, LEVEL_ERROR); } } @@ -121,7 +144,7 @@ public static void e(String TAG, String msg) { */ public static void w(String TAG, String msg) { if (DEBUG) { - logInfo(TAG,msg,"WARN"); + logInfo(TAG, msg, LEVEL_WARN); } } diff --git a/APIJSONORM/src/main/java/apijson/MethodAccess.java b/APIJSONORM/src/main/java/apijson/MethodAccess.java index 31d45843e..1804f7a7b 100755 --- a/APIJSONORM/src/main/java/apijson/MethodAccess.java +++ b/APIJSONORM/src/main/java/apijson/MethodAccess.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/NotNull.java b/APIJSONORM/src/main/java/apijson/NotNull.java index d10a93691..1265ccac7 100755 --- a/APIJSONORM/src/main/java/apijson/NotNull.java +++ b/APIJSONORM/src/main/java/apijson/NotNull.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/RequestMethod.java b/APIJSONORM/src/main/java/apijson/RequestMethod.java index 875200b7a..27e4cab64 100755 --- a/APIJSONORM/src/main/java/apijson/RequestMethod.java +++ b/APIJSONORM/src/main/java/apijson/RequestMethod.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/SQL.java b/APIJSONORM/src/main/java/apijson/SQL.java index 110ae3d47..868f0d2aa 100755 --- a/APIJSONORM/src/main/java/apijson/SQL.java +++ b/APIJSONORM/src/main/java/apijson/SQL.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index 13b0ff214..179161005 100755 --- a/APIJSONORM/src/main/java/apijson/StringUtil.java +++ b/APIJSONORM/src/main/java/apijson/StringUtil.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index ff2e484df..42831775b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 5a39fd1fe..c49c9cd2a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -61,6 +61,8 @@ public AbstractObjectParser setParser(Parser parser) { * TODO Parser内要不因为 非 TYPE_ITEM_CHILD_0 的Table 为空导致后续中断。 */ protected final boolean drop; + private List stringKeyList; + private List trimKeyList; /**for single object */ @@ -103,7 +105,17 @@ public AbstractObjectParser(@NotNull M request, String parentPath, SQLConfig parse(String name, boolean isReuse) throws // hasOtherKeyNotFun = true; // } + boolean isTrim = (trimKeyList != null && trimKeyList.contains(key)); + boolean toStr = isTrim || (stringKeyList != null && stringKeyList.contains(key)); + if (toStr) { + value = JSON.toJSONString(value); + } + if (isTrim && value != null) { + value = StringUtil.trim(value); + } + if (startsWithAt || key.endsWith("@") || (key.endsWith("<>") && value instanceof Map)) { if (onParse(key, value) == false) { invalidate(); @@ -1171,7 +1192,9 @@ else if (isArrayMainTable && position > 0) { // 数组主表使用专门的缓 // APP JOIN 副表时副表返回了这个字段 rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); String arrayPath = parentPath.substring(0, parentPath.lastIndexOf("[]") + 2); - if (isSimpleArray == false) { + if (isSimpleArray) { + parser.putQueryResult(arrayPath, rawList); // 从数组外部引用该数组内值需要 + } else { long startTime = System.currentTimeMillis(); for (int i = 1; i < rawList.size(); i++) { // 从 1 开始,0 已经处理过 @@ -1196,7 +1219,7 @@ else if (isArrayMainTable && position > 0) { // 数组主表使用专门的缓 if (isSubquery == false && result != null) { parser.putQueryResult(path, result); // 解决获取关联数据时requestObject里不存在需要的关联数据 - if (isSimpleArray && rawList != null) { + if (isSimpleArray) { // FIXME 改为从缓存获取,而不是 result 查 result.put(AbstractSQLExecutor.KEY_RAW_LIST, rawList); } } @@ -1225,7 +1248,12 @@ public void recycle() { if (drop) { request.put(KEY_DROP, drop); } - + if (stringKeyList != null) { // 避免被全局关键词覆盖 && ! stringKeyList.isEmpty()) { + request.put(KEY_STRING, StringUtil.get(stringKeyList.toArray())); + } + if (trimKeyList != null) { // 避免被全局关键词覆盖 && ! trimKeyList.isEmpty()) { + request.put(KEY_TRIM, StringUtil.get(trimKeyList.toArray())); + } method = null; parentPath = null; diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 36f2b4177..e2403122f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -39,7 +39,7 @@ public abstract class AbstractParser, L extends List> implements Parser { protected static final String TAG = "AbstractParser"; - + /** * JSON 对象、数组对应的数据源、版本、角色、method等 */ @@ -62,6 +62,12 @@ public abstract class AbstractParser, L extends */ public static boolean IS_PRINT_REQUEST_ENDTIME_LOG = false; + /** + * 可以通过切换该变量来控制返回 trace:stack 字段,如果是 gson 则不设置为 false,避免序列化报错。 + * 与 {@link Log#DEBUG} 任何一个为 true 返回 trace:stack 字段。 + */ + public static boolean IS_RETURN_STACK_TRACE = true; + /** * 分页页码是否从 1 开始,默认为从 0 开始 @@ -584,7 +590,7 @@ public M parseResponse(M request) { onCommit(); } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, "onObjectParse failed", e); error = e; onRollback(); @@ -622,7 +628,19 @@ public M parseResponse(M request) { // } Throwable t = error instanceof CommonException && error.getCause() != null ? error.getCause() : error; res.put("trace:throw", t.getClass().getName()); - res.put("trace:stack", t.getStackTrace()); + + if (IS_RETURN_STACK_TRACE) { + L list = JSON.createJSONArray(); + + StackTraceElement[] traces = t.getStackTrace(); + if (traces != null) { // && traces.length > 0) { + for (StackTraceElement trace : traces) { + list.add(trace == null ? null : trace.toString()); + } + } + + res.put("trace:stack", list); + } } } @@ -688,7 +706,7 @@ public M parseCorrectRequest(RequestMethod method, String tag, int version, Stri return batchVerify(method, tag, version, name, request, maxUpdateCount, creator); } - + /**自动根据 tag 是否为 TableKey 及是否被包含在 object 内来决定是否包装一层,改为 { tag: object, "tag": tag } * @param object * @param tag @@ -1004,9 +1022,7 @@ public M newErrorResult(Exception e) { */ public M newErrorResult(Exception e, boolean isRoot) { if (e != null) { - // if (Log.DEBUG) { - e.printStackTrace(); - // } + Log.e(TAG, "newErrorResult", e); String msg = CommonException.getMsg(e); int code = CommonException.getCode(e); @@ -1545,7 +1561,39 @@ else if (join != null){ throw new UnsupportedDataTypeException(TAG + ".onJoinParse join 只能是 String 或 Map 类型!"); } - Set> set = joinMap == null ? null : joinMap.entrySet(); + List> slashKeys = new ArrayList<>(); + List> nonSlashKeys = new ArrayList<>(); + Set> entries = joinMap == null ? null : joinMap.entrySet(); + + if (entries == null || entries.isEmpty()) { + Log.e(TAG, "onJoinParse set == null || set.isEmpty() >> return null;"); + return null; + } + for (Entry e : entries) { + String path = e.getKey(); + if (path != null && path.indexOf("/") > 0) { + slashKeys.add(e); // 以 / 开头的 key,例如 whereJoinMap = new LinkedHashMap<>(); + + for (Entry e : nonSlashKeys) { + String tableKey = e.getKey(); // 如 "Location_info" + Object tableObj = e.getValue(); // value 是 Map + + if (request.containsKey(tableKey)) { + whereJoinMap.put(tableKey, tableObj); + } else { + Log.w(TAG, "跳过 join 中 key = " + tableKey + ",因为它不在 request 中"); + } + } + + + Set> set = joinMap == null ? null : new LinkedHashSet<>(slashKeys); + if (set == null || set.isEmpty()) { Log.e(TAG, "onJoinParse set == null || set.isEmpty() >> return null;"); return null; @@ -1741,9 +1789,15 @@ else if (join != null){ j.setAlias(alias); M outerObj = (M) JSON.createJSONObject((Map) outer); - j.setOuter(outerObj); + j.setOn(outerObj); j.setRequest(requestObj); + if (whereJoinMap.containsKey(table)) { + Object rawOuter = whereJoinMap.get(table); + M outerObj1 = (M) JSON.createJSONObject((Map) rawOuter); + j.setOuter(outerObj1); + } + if (arrKey != null) { Integer count = getInteger(parentPathObj, apijson.JSONRequest.KEY_COUNT); j.setCount(count == null ? getDefaultQueryCount() : count); @@ -1846,24 +1900,32 @@ else if (join != null){ * @param pathKeys * @return */ - public static V getValue(Map parent, String[] pathKeys) { - if (parent == null || pathKeys == null || pathKeys.length <= 0) { + public static V getValue(Object parent, String[] pathKeys) { + int len = parent == null || pathKeys == null ? 0 : pathKeys.length; + if (len <= 0) { Log.w(TAG, "getChild parent == null || pathKeys == null || pathKeys.length <= 0 >> return parent;"); return (V) parent; } - //逐层到达child的直接容器JSONObject parent - int last = pathKeys.length - 1; - for (int i = 0; i < last; i++) {//一步一步到达指定位置 - if (parent == null) {//不存在或路径错误(中间的key对应value不是JSONObject) + // 逐层到达child的直接容器JSONObject parent + Object v = parent; + for (int i = 0; i < len; i++) { // 一步一步到达指定位置 + if (v == null) { // 不存在或路径错误(中间的key对应value不是JSONObject) break; } String k = getDecodedKey(pathKeys[i]); - parent = JSON.get(parent, k); + try { + v = getFromObjOrArr(v, k); + } catch (Throwable e) { + if (IS_PRINT_BIG_LOG) { + Log.e(TAG, "getFromObjOrArr failed", e); + } + v = null; + } } - return parent == null ? null : (V) parent.get(getDecodedKey(pathKeys[last])); + return (V) v; } @@ -1969,13 +2031,13 @@ public Object getValueByPath(String valuePath) { } //取出key被valuePath包含的result,再从里面获取key对应的value - Map parent = null; + Object parent = null; String[] keys = null; for (Entry entry : queryResultMap.entrySet()){ String path = entry.getKey(); if (valuePath.startsWith(path + "/")) { try { - parent = (M) entry.getValue(); + parent = entry.getValue(); } catch (Exception e) { Log.e(TAG, "getValueByPath try { parent = (Map) queryResultMap.get(path); } catch { " + "\n parent not instanceof Map!"); @@ -1988,39 +2050,13 @@ public Object getValueByPath(String valuePath) { } } - //逐层到达targetKey的直接容器JSONObject parent - int last = keys == null ? -1 : keys.length - 1; - if (last >= 1) { - for (int i = 0; i < last; i++) {//一步一步到达指定位置parentPath - if (parent == null) {//不存在或路径错误(中间的key对应value不是JSONObject) - break; - } - - String k = getDecodedKey(keys[i]); - Object p = parent.get(k); - parent = p instanceof Map ? (Map) p : null; - } - } - - if (parent != null) { - Log.i(TAG, "getValueByPath >> get from queryResultMap >> return parent.get(keys[keys.length - 1]);"); - target = last < 0 ? parent : parent.get(getDecodedKey(keys[last])); //值为null应该报错NotExistExeption,一般都是id关联,不可为null,否则可能绕过安全机制 - if (target != null) { - Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); - return target; - } - } - - - //从requestObject中取值 - target = getValue(requestObject, StringUtil.splitPath(valuePath)); - if (target != null) { - Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); - return target; + target = getValue(parent, keys); // 逐层到达targetKey的直接容器JSONObject parent + if (target == null) { //从requestObject中取值 + target = getValue(requestObject, StringUtil.splitPath(valuePath)); } - Log.i(TAG, "getValueByPath return null;"); - return null; + Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); + return target; } /**解码 引用赋值 路径中的 key,支持把 URL encode 后的值,转为 decode 后的原始值,例如 %2Fuser%2Flist -> /user/list ; %7B%7D -> [] @@ -2192,7 +2228,7 @@ && getSQLExecutor().getTransactionIsolation() == Connection.TRANSACTION_NONE) { commit(); } catch (SQLException e) { - e.printStackTrace(); + Log.e(TAG, "onCommit failed", e); } } /**回滚事务 @@ -2207,12 +2243,12 @@ protected void onRollback() { rollback(); } catch (SQLException e1) { - e1.printStackTrace(); + Log.e(TAG, "onRollback failed", e1); try { rollback(null); } catch (SQLException e2) { - e2.printStackTrace(); + Log.e(TAG, "onRollback with null failed", e2); } } } @@ -2479,8 +2515,8 @@ protected M batchVerify(RequestMethod method, String tag, int version, String na correctRequest.put(key, obj); } } catch (Exception e) { - e.printStackTrace(); - throw new Exception(e); // 包装一层只是为了打印日志?看起来没必要 + Log.e(TAG, "parseCorrectRequest failed", e); + throw e; } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index a919b2733..d9e9eb016 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1,46 +1,25 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ package apijson.orm; -import java.util.*; -import java.util.Map.Entry; -import java.util.regex.Pattern; - import apijson.*; import apijson.orm.Join.On; import apijson.orm.exception.NotExistException; import apijson.orm.exception.UnsupportedDataTypeException; -import apijson.orm.model.Access; -import apijson.orm.model.AllColumn; -import apijson.orm.model.AllColumnComment; -import apijson.orm.model.AllTable; -import apijson.orm.model.AllTableComment; -import apijson.orm.model.Column; -import apijson.orm.model.Document; -import apijson.orm.model.ExtendedProperty; -import apijson.orm.model.Function; -import apijson.orm.model.PgAttribute; -import apijson.orm.model.PgClass; -import apijson.orm.model.Request; -import apijson.orm.model.SysColumn; -import apijson.orm.model.SysTable; -import apijson.orm.model.Table; -import apijson.orm.model.TestRecord; +import apijson.orm.model.*; + +import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Pattern; import static apijson.JSON.getBoolean; import static apijson.JSON.getString; import static apijson.JSONMap.*; -import static apijson.RequestMethod.DELETE; -import static apijson.RequestMethod.GET; -import static apijson.RequestMethod.POST; -import static apijson.RequestMethod.PUT; -import static apijson.SQL.AND; -import static apijson.SQL.NOT; -import static apijson.SQL.ON; -import static apijson.SQL.OR; +import static apijson.RequestMethod.*; +import static apijson.SQL.*; /**config sql for JSON Request * @author Lemon @@ -190,6 +169,7 @@ public abstract class AbstractSQLConfig, L exte DATABASE_LIST.add(DATABASE_QUESTDB); DATABASE_LIST.add(DATABASE_IOTDB); DATABASE_LIST.add(DATABASE_SNOWFLAKE); + DATABASE_LIST.add(DATABASE_DATABEND); DATABASE_LIST.add(DATABASE_DATABRICKS); DATABASE_LIST.add(DATABASE_REDIS); DATABASE_LIST.add(DATABASE_MONGODB); @@ -1252,6 +1232,14 @@ public static boolean isSnowflake(String db) { return DATABASE_SNOWFLAKE.equals(db); } + @Override + public boolean isDatabend() { + return isDatabend(gainSQLDatabase()); + } + public static boolean isDatabend(String db) { + return DATABASE_DATABEND.equals(db); + } + @Override public boolean isDatabricks() { return isDatabricks(gainSQLDatabase()); @@ -1574,7 +1562,7 @@ public String gainGroupString(boolean hasPrefix) { continue; } - SQLConfig ocfg = join.getOuterConfig(); + SQLConfig ocfg = join.getOnConfig(); SQLConfig cfg = (ocfg != null && ocfg.getGroup() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { @@ -1589,6 +1577,23 @@ public String gainGroupString(boolean hasPrefix) { first = false; } } + + ////先处理左/右关联,内关联忽略 + //SQLConfig outerConfig = join.getOuterConfig(); + //SQLConfig outerConfig2 = (outerConfig != null && outerConfig.getGroup() != null) || join.isLeftOrRightJoin() ? outerConfig : null; + // + //if (outerConfig2 != null) { + // outerConfig2.setMain(false).setKeyPrefix(true); + // //if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // // cfg.setAlias(cfg.getTable()); + // //} + // String c = ((AbstractSQLConfig) outerConfig2).gainGroupString(false); + // + // if (StringUtil.isNotEmpty(c, true)) { + // joinGroup += (first ? "" : ", ") + c; + // first = false; + // } + //} } } @@ -1650,7 +1655,7 @@ public String gainHavingString(boolean hasPrefix) throws Exception { continue; } - SQLConfig ocfg = join.getOuterConfig(); + SQLConfig ocfg = join.getOnConfig(); SQLConfig cfg = (ocfg != null && ocfg.getHaving() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { @@ -1763,7 +1768,7 @@ public String gainSampleString(boolean hasPrefix) { continue; } - SQLConfig ocfg = join.getOuterConfig(); + SQLConfig ocfg = join.getOnConfig(); SQLConfig cfg = (ocfg != null && ocfg.getSample() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { @@ -1833,7 +1838,7 @@ public String gainLatestString(boolean hasPrefix) { continue; } - SQLConfig ocfg = join.getOuterConfig(); + SQLConfig ocfg = join.getOnConfig(); SQLConfig cfg = (ocfg != null && ocfg.getLatest() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { @@ -1898,7 +1903,7 @@ public String gainPartitionString(boolean hasPrefix) { continue; } - SQLConfig ocfg = join.getOuterConfig(); + SQLConfig ocfg = join.getOnConfig(); SQLConfig cfg = (ocfg != null && ocfg.getPartition() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { @@ -1963,7 +1968,7 @@ public String gainFillString(boolean hasPrefix) { continue; } - SQLConfig ocfg = join.getOuterConfig(); + SQLConfig ocfg = join.getOnConfig(); SQLConfig cfg = (ocfg != null && ocfg.getFill() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { @@ -2029,6 +2034,7 @@ public AbstractSQLConfig setOrder(String order) { public String gainOrderString(boolean hasPrefix) { //加上子表的 order String joinOrder = ""; + String joinOuterOrder = ""; if (joinList != null) { boolean first = true; for (Join join : joinList) { @@ -2036,7 +2042,7 @@ public String gainOrderString(boolean hasPrefix) { continue; } - SQLConfig ocfg = join.getOuterConfig(); + SQLConfig ocfg = join.getOnConfig(); SQLConfig cfg = (ocfg != null && ocfg.getOrder() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { @@ -2345,7 +2351,7 @@ public String gainColumnString(boolean inSQLJoin) throws Exception { continue; } - SQLConfig ocfg = join.getOuterConfig(); + SQLConfig ocfg = join.getOnConfig(); boolean isEmpty = ocfg == null || ocfg.getColumn() == null; boolean isLeftOrRightJoin = join.isLeftOrRightJoin(); @@ -3729,8 +3735,9 @@ protected String concatJoinWhereString(String whereString) throws Exception { List pvl = new ArrayList<>(getPreparedValueList()); SQLConfig jc; + SQLConfig outerConfig; String js; - + boolean isWsEmpty = StringUtil.isEmpty(ws, true); boolean changed = false; // 各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? for (Join j : joinList) { @@ -3741,6 +3748,27 @@ protected String concatJoinWhereString(String whereString) throws Exception { case "@": // APP JOIN case "<": // LEFT JOIN case ">": // RIGHT JOIN + outerConfig = j.getOuterConfig(); + if (outerConfig == null){ + break; + } + boolean isMain1 = outerConfig.isMain(); + outerConfig.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); + String outerWhere = outerConfig.gainWhereString(false); + + int logic1 = Logic.getType(jt); + newWs += " ( " + + gainCondition( + Logic.isNot(logic1), + ws + + ( isWsEmpty ? "" : (Logic.isAnd(logic1) ? AND : OR) ) + + " ( " + outerWhere + " ) " + ) + + " ) "; + newPvl.addAll(pvl); + newPvl.addAll(outerConfig.getPreparedValueList()); + + changed = true; break; case "&": // INNER JOIN: A & B @@ -3761,7 +3789,7 @@ protected String concatJoinWhereString(String whereString) throws Exception { boolean isSideJoin = "^".equals(jt); boolean isAntiJoin = "(".equals(jt); boolean isForeignJoin = ")".equals(jt); - boolean isWsEmpty = StringUtil.isEmpty(ws, true); + //boolean isWsEmpty = StringUtil.isEmpty(ws, true); if (isWsEmpty) { if (isOuterJoin) { // ! OUTER JOIN: ! (A | B) @@ -5202,7 +5230,7 @@ public String gainJoinString() throws Exception { ); } - SQLConfig oc = j.getOuterConfig(); + SQLConfig oc = j.getOnConfig(); String ow = null; if (oc != null) { oc.setPrepared(isPrepared()); @@ -6305,13 +6333,22 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { joinConfig.setMain(false).setKeyPrefix(true); + if (join.getOn() != null) { + SQLConfig onConfig = newSQLConfig(method, table, alias, join.getOn(), null, false, callback); + onConfig.setMain(false) + .setKeyPrefix(true) + .setDatabase(joinConfig.getDatabase()) + .setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 + + join.setOnConfig(onConfig); + } + if (join.getOuter() != null) { SQLConfig outerConfig = newSQLConfig(method, table, alias, join.getOuter(), null, false, callback); outerConfig.setMain(false) .setKeyPrefix(true) .setDatabase(joinConfig.getDatabase()) .setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 - join.setOuterConfig(outerConfig); } } @@ -6322,9 +6359,12 @@ LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.m if (RequestMethod.isHeadMethod(method, true)) { List onList = join.getOnList(); List column = onList == null ? null : new ArrayList<>(onList.size()); - if (column != null) { + //解决 pg 如果只查询关联键,会报找不到column的错误 + ///* SELECT count(*) AS count FROM sys.Moment AS Moment + // LEFT JOIN ( SELECT * FROM sys.Comment ) AS Comment ON Comment.momentId = Moment.id LIMIT 1 OFFSET 0 */ + if (column != null && joinConfig.isMSQL()) { // 暂时这样兼容 PostgreSQL 等不支持 SELECT 中不包含对应 key 的隐式 ON 关联字段的数据库 for (On on : onList) { - column.add(on.getKey()); + column.add(on.getKey()); // TODO PostgreSQL 等需要找到具体的 targetTable 对应 targetKey 来加到 SELECT,比直接 SELECT * 性能更好 } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index c9d180bdb..8a2862ebd 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -676,7 +676,7 @@ else if (hasPK) { rs.close(); } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, "close ResultSet failed", e); } } } @@ -947,7 +947,7 @@ protected void executeAppJoin(SQLConfig config, List resultList, Map rs.close(); } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, "close ResultSet failed in executeAppJoin", e); } } } @@ -1125,7 +1125,7 @@ else if (value instanceof Clob) { //SQL Server TEXT 类型 居然走这个 br.close(); } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, "close BufferedReader failed", e); } } @@ -1194,7 +1194,7 @@ public boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData return true; } } catch (SQLException e) { - e.printStackTrace(); + Log.e(TAG, "isJsonColumn failed", e); } // List json = config.getJson(); // return json != null && json.contains(label); @@ -1213,14 +1213,15 @@ public PreparedStatement getStatement(@NotNull SQLConfig config, String sql = config.gainSQL(config.isPrepared()); } + Connection conn = getConnection(config); PreparedStatement statement; //创建Statement对象 if (config.getMethod() == RequestMethod.POST && config.getId() == null) { //自增id if (config.isOracle()) { // 解决 oracle 使用自增主键 插入获取不到id问题 String[] generatedColumns = {config.getIdKey()}; - statement = getConnection(config).prepareStatement(sql, generatedColumns); + statement = conn.prepareStatement(sql, generatedColumns); } else { - statement = getConnection(config).prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + statement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); } } else if (RequestMethod.isGetMethod(config.getMethod(), true)) { @@ -1234,13 +1235,13 @@ else if (RequestMethod.isGetMethod(config.getMethod(), true)) { if (config.isMySQL() || config.isTiDB() || config.isMariaDB() || config.isOracle() || config.isSQLServer() || config.isDb2() || config.isPostgreSQL() || config.isCockroachDB() || config.isOpenGauss() || config.isTimescaleDB() || config.isQuestDB() ) { - statement = getConnection(config).prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); + statement = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); } else { - statement = getConnection(config).prepareStatement(sql); + statement = conn.prepareStatement(sql); } } else { - statement = getConnection(config).prepareStatement(sql); + statement = conn.prepareStatement(sql); } List valueList = config.isPrepared() ? config.getPreparedValueList() : null; @@ -1314,7 +1315,7 @@ public Connection getConnection(@NotNull SQLConfig config) throws Excep } public String getConnectionKey(@NotNull SQLConfig config) { - return getConnectionKey(config.getNamespace(), config.getCatalog(), config.getDatasource(), config.getDatabase()); + return getConnectionKey(config.getDatabase(), config.getDatasource(), config.getNamespace(), config.getCatalog()); } public String getConnectionKey(String database, String datasource, String namespace, String catalog) { return database + "-" + datasource + "-" + namespace + "-" + catalog; @@ -1356,7 +1357,7 @@ public void begin(int transactionIsolation) throws SQLException { } } catch (SQLException e) { - e.printStackTrace(); + Log.e(TAG, "setAutoCommit failed in rollback", e); } } } @@ -1383,7 +1384,7 @@ public void rollback() throws SQLException { } } catch (SQLException e) { - e.printStackTrace(); + Log.e(TAG, "rollback failed", e); } } } @@ -1415,7 +1416,7 @@ public void rollback(Savepoint savepoint) throws SQLException { } } catch (SQLException e) { - e.printStackTrace(); + Log.e(TAG, "rollback with savepoint failed", e); } } } @@ -1441,7 +1442,7 @@ public void commit() throws SQLException { } } catch (SQLException e) { - e.printStackTrace(); + Log.e(TAG, "commit failed", e); } } } @@ -1472,7 +1473,7 @@ public void close() { } } catch (SQLException e) { - e.printStackTrace(); + Log.e(TAG, "close connection failed", e); } } } @@ -1540,3 +1541,4 @@ public int executeUpdate(@NotNull SQLConfig config, String sql) throws } + diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index 0c52dca43..5e665815c 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -6,6 +6,7 @@ package apijson.orm; import static apijson.JSON.*; +import static apijson.JSONMap.*; import static apijson.RequestMethod.DELETE; import static apijson.RequestMethod.GET; import static apijson.RequestMethod.GETS; @@ -13,18 +14,7 @@ import static apijson.RequestMethod.HEADS; import static apijson.RequestMethod.POST; import static apijson.RequestMethod.PUT; -import static apijson.orm.Operation.ALLOW_PARTIAL_UPDATE_FAIL; -import static apijson.orm.Operation.EXIST; -import static apijson.orm.Operation.INSERT; -import static apijson.orm.Operation.MUST; -import static apijson.orm.Operation.REFUSE; -import static apijson.orm.Operation.REMOVE; -import static apijson.orm.Operation.REPLACE; -import static apijson.orm.Operation.TYPE; -import static apijson.orm.Operation.UNIQUE; -import static apijson.orm.Operation.UPDATE; -import static apijson.orm.Operation.VERIFY; -import static apijson.orm.Operation.IF; +import static apijson.orm.Operation.*; //import static apijson.orm.Operation.CODE; import java.net.URL; @@ -219,16 +209,16 @@ public static HashMap getAccessMap(MethodAccess access) @Override public String getVisitorIdKey(SQLConfig config) { - return config.getUserIdKey(); + return config == null ? getUserIdKey(null, null, null, null) : config.getUserIdKey(); } @Override public String getIdKey(String database, String schema, String datasource, String table) { - return JSONMap.KEY_ID; + return KEY_ID; } @Override public String getUserIdKey(String database, String schema, String datasource, String table) { - return JSONMap.KEY_USER_ID; + return KEY_USER_ID; } @SuppressWarnings("unchecked") @@ -346,21 +336,23 @@ public void verifyAllowRole(SQLConfig config, String table, RequestMeth * @throws Exception * @see {@link JSONMap#KEY_ROLE} */ - public void verifyUseRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { + public void verifyUseRole(@NotNull SQLConfig config, String table, RequestMethod method, String role) throws Exception { Log.d(TAG, "verifyUseRole table = " + table + "; method = " + method + "; role = " + role); + Objects.requireNonNull(config); //验证角色,假定真实强制匹配<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - String visitorIdKey = getVisitorIdKey(config); if (table == null) { - table = config == null ? null : config.getTable(); + table = config.getTable(); } if (method == null) { - method = config == null ? GET : config.getMethod(); + method = config.getMethod(); } if (role == null) { - role = config == null ? UNKNOWN : config.getRole(); + role = config.getRole(); } + String visitorIdKey = getVisitorIdKey(config); + Object requestId; switch (role) { case LOGIN://verifyRole通过就行 @@ -381,13 +373,13 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod Collection requestIdArray = (Collection) config.getWhere(visitorIdKey + "{}", true); // 不能是 &{}, |{} 不要传,直接 {} if (requestId != null) { if (requestIdArray == null) { - requestIdArray = JSON.createJSONArray(); + requestIdArray = createJSONArray(); } requestIdArray.add(requestId); } if (requestIdArray == null) { // 可能是 @ 得到 || requestIdArray.isEmpty()) { // 请求未声明 key:id 或 key{}:[...] 条件,自动补全 - config.putWhere(visitorIdKey+"{}", JSON.parseArray(list), true); // key{}:[] 有效,SQLConfig 里 throw NotExistException + config.putWhere(visitorIdKey+"{}", parseArray(list), true); // key{}:[] 有效,SQLConfig 里 throw NotExistException } else { // 请求已声明 key:id 或 key{}:[] 条件,直接验证 for (Object id : requestIdArray) { @@ -414,7 +406,7 @@ else if (id instanceof String) { } break; case OWNER: - if (config.getMethod() == RequestMethod.POST) { + if (config.getMethod() == POST) { List c = config.getColumn(); List> ovs = config.getValues(); if ( (c == null || c.isEmpty()) || (ovs == null || ovs.isEmpty()) ) { @@ -530,17 +522,17 @@ public void verifyRepeat(String table, String key, Object value, long exceptId) throw new UnsupportedDataTypeException(key + ":value 中value的类型不能为JSON!"); } - M tblObj = JSON.createJSONObject(); + M tblObj = createJSONObject(); tblObj.put(key, value); if (exceptId > 0) {//允许修改自己的属性为该属性原来的值 - tblObj.put(JSONMap.KEY_ID + "!", exceptId); // FIXME 这里 id 写死了,不支持自定义 + tblObj.put(KEY_ID + "!", exceptId); // FIXME 这里 id 写死了,不支持自定义 } - M req = JSON.createJSONObject(); + M req = createJSONObject(); req.put(table, tblObj); Map repeat = createParser().setMethod(HEAD).setNeedVerify(true).parseResponse(req); - repeat = repeat == null ? null : JSON.get(repeat, table); + repeat = repeat == null ? null : get(repeat, table); if (repeat == null) { throw new Exception("服务器内部错误 verifyRepeat repeat == null"); } @@ -644,8 +636,8 @@ public static , L extends List> M verif } Log.i(TAG, "verifyRequest method = " + method + "; name = " + name - + "; target = \n" + JSON.toJSONString(target) - + "\n request = \n" + JSON.toJSONString(request)); + + "; target = \n" + toJSONString(target) + + "\n request = \n" + toJSONString(request)); if (target == null || request == null) {// || request.isEmpty()) { Log.i(TAG, "verifyRequest target == null || request == null >> return null;"); @@ -670,10 +662,10 @@ public M onParseJSONObject(String key, M tobj, M robj) throws Exception { if (tobj != null) {//不允许不传Target中指定的Table throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":{} !"); } - } else if (JSONMap.isTableKey(key)) { - String db = getString(request, JSONMap.KEY_DATABASE); - String sh = getString(request, JSONMap.KEY_SCHEMA); - String ds = getString(request, JSONMap.KEY_DATASOURCE); + } else if (isTableKey(key)) { + String db = getString(request, KEY_DATABASE); + String sh = getString(request, KEY_SCHEMA); + String ds = getString(request, KEY_DATASOURCE); if (StringUtil.isEmpty(db, false)) { db = database; } @@ -685,9 +677,9 @@ public M onParseJSONObject(String key, M tobj, M robj) throws Exception { } String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, ds, key); - String finalIdKey = StringUtil.isEmpty(idKey, false) ? JSONMap.KEY_ID : idKey; + String finalIdKey = StringUtil.isEmpty(idKey, false) ? KEY_ID : idKey; - if (method == RequestMethod.POST) { + if (method == POST) { if (robj.containsKey(finalIdKey)) { throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 不能传 " + finalIdKey + " !"); } @@ -697,7 +689,7 @@ public M onParseJSONObject(String key, M tobj, M robj) throws Exception { 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) ? JSONMap.KEY_USER_ID : userIdKey; + String finalUserIdKey = StringUtil.isEmpty(userIdKey, false) ? KEY_USER_ID : userIdKey; verifyId(method.name(), name, key, robj, finalUserIdKey, maxUpdateCount, false); } } @@ -708,7 +700,7 @@ public M onParseJSONObject(String key, M tobj, M robj) throws Exception { @Override protected L onParseJSONArray(String key, L tarray, L rarray) throws Exception { - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONMap.isArrayKey(key)) { + if ((method == POST || method == PUT) && isArrayKey(key)) { if (rarray == null || rarray.isEmpty()) { throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":[{ ... }] " + ",批量新增 Table[]:value 中 value 必须是包含表对象的非空数组!其中每个子项 { ... } 都是" @@ -750,7 +742,7 @@ private static , L extends List> void v String idRefInKey = getString(robj, idKey + "{}@"); L idIn = null; try { - idIn = JSON.get(robj, idInKey); //如果必须传 id{} ,可在Request表中配置NECESSARY + idIn = get(robj, idInKey); //如果必须传 id{} ,可在Request表中配置NECESSARY } catch (Exception e) { throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 里面的 " + idInKey + ":value 中value的类型只能是 [Long] !"); @@ -847,8 +839,8 @@ public static , L extends List> M veri , final IdCallback idKeyCallback, @NotNull Parser parser, OnParseCallback callback) throws Exception { Log.i(TAG, "verifyResponse method = " + method + "; name = " + name - + "; target = \n" + JSON.toJSONString(target) - + "\n response = \n" + JSON.toJSONString(response)); + + "; target = \n" + toJSONString(target) + + "\n response = \n" + toJSONString(response)); if (target == null || response == null) {// || target.isEmpty() { Log.i(TAG, "verifyResponse target == null || response == null >> return response;"); @@ -919,19 +911,6 @@ public static , L extends List> M parse return null; } - // 获取配置<<<<<<<<<<<<<<<<<<<<<<<<<<<< - M type = JSON.get(target, TYPE.name()); - M verify = JSON.get(target, VERIFY.name()); - M insert = JSON.get(target, INSERT.name()); - M update = JSON.get(target, UPDATE.name()); - M replace = JSON.get(target, REPLACE.name()); - - String exist = StringUtil.get(getString(target, EXIST.name())); - String unique = StringUtil.get(getString(target, UNIQUE.name())); - String remove = StringUtil.get(getString(target, REMOVE.name())); - String must = StringUtil.get(getString(target, MUST.name())); - String refuse = StringUtil.get(getString(target, REFUSE.name())); - Object _if = target.get(IF.name()); boolean ifIsStr = _if instanceof String && StringUtil.isNotEmpty(_if, true); M ifObj = ifIsStr == false && _if instanceof Map ? (M) _if : null; @@ -942,10 +921,20 @@ public static , L extends List> M parse throw new IllegalArgumentException(name + ": { " + IF.name() + ": value } 中 value 类型错误!只允许 String, JSONRequest!"); } -// Object code = target.get(CODE.name()); + // 获取配置<<<<<<<<<<<<<<<<<<<<<<<<<<<< + M type = get(target, TYPE.name()); + M verify = get(target, VERIFY.name()); + M insert = get(target, INSERT.name()); + M update = get(target, UPDATE.name()); + M replace = get(target, REPLACE.name()); - String allowPartialUpdateFail = StringUtil.get(getString(target, ALLOW_PARTIAL_UPDATE_FAIL.name())); + String exist = StringUtil.get(getString(target, EXIST.name())); + String unique = StringUtil.get(getString(target, UNIQUE.name())); + String remove = StringUtil.get(getString(target, REMOVE.name())); + String must = StringUtil.get(getString(target, MUST.name())); + String refuse = StringUtil.get(getString(target, REFUSE.name())); +// Object code = target.get(CODE.name()); // 移除字段<<<<<<<<<<<<<<<<<<< String[] removes = StringUtil.split(remove); @@ -972,6 +961,10 @@ public static , L extends List> M parse } // 判断必要字段是否都有>>>>>>>>>>>>>>>>>>> + String[] sks = StringUtil.split(getString(real, KEY_STRING)); + String[] trims = StringUtil.split(getString(real, KEY_TRIM)); + List stringKeyList = sks == null || sks.length <= 0 ? null : Arrays.asList(sks); + List trimKeyList = trims == null || trims.length <= 0 ? null : Arrays.asList(trims); Set objKeySet = new HashSet(); // 不能用tableKeySet,仅判断 Table:{} 会导致 key:{ Table:{} } 绕过判断 @@ -987,6 +980,13 @@ public static , L extends List> M parse } Object tvalue = entry.getValue(); Object rvalue = real.get(key); + if (rvalue != null && stringKeyList != null && stringKeyList.contains(key)) { + rvalue = toJSONString(rvalue); + } + if (rvalue != null && trimKeyList != null && trimKeyList.contains(key)) { + rvalue = StringUtil.trim(rvalue); + } + if (callback.onParse(key, tvalue, rvalue) == false) { continue; } @@ -1004,7 +1004,7 @@ public static , L extends List> M parse } tvalue = callback.onParseJSONArray(key, (L) tvalue, (L) rvalue); - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONMap.isArrayKey(key)) { + if ((method == POST || method == PUT) && isArrayKey(key)) { objKeySet.add(key); } } else { // 其它Object @@ -1087,17 +1087,32 @@ public static , L extends List> M parse // 判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< for (String rk : rkset) { + if (rk == null || KEY_STRING.equals(rk) || KEY_TRIM.equals(rk)) { + // ConcurrentModificationException real.remove(rk); + continue; + } + if (refuseSet.contains(rk)) { // 不允许的字段 throw new IllegalArgumentException(method + "请求," + name + " 里面不允许传 " + rk + " 等" + StringUtil.get(refuseSet) + "内的任何字段!"); } - if (rk == null) { // 无效的key - real.remove(rk); - continue; + if (KEY_COMBINE.equals(rk)) { + throw new UnsupportedOperationException(method + " 请求," + rk + " 不合法!" + + "非开放请求不允许传 " + KEY_COMBINE + ":value !"); + } + if (KEY_KEY.equals(rk)) { + throw new UnsupportedOperationException(method + " 请求," + rk + " 不合法!" + + "非开放请求不允许传 " + KEY_KEY + ":value !"); } Object rv = real.get(rk); + if (rv != null && stringKeyList != null && stringKeyList.contains(rk)) { + rv = toJSONString(rv); + } + if (rv != null && trimKeyList != null && trimKeyList.contains(rk)) { + rv = StringUtil.trim(rv); + } // 不允许传远程函数,只能后端配置 if (rk.endsWith("()") && rv instanceof String) { @@ -1111,8 +1126,8 @@ public static , L extends List> M parse throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许传 " + rk + ":{} !"); } - if ((method == RequestMethod.POST || method == RequestMethod.PUT) - && rv instanceof List && JSONMap.isArrayKey(rk)) { + if ((method == POST || method == PUT) + && rv instanceof List && isArrayKey(rk)) { throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!"); } @@ -1137,9 +1152,9 @@ public static , L extends List> M parse // 校验与修改Request>>>>>>>>>>>>>>>>> - String db = getString(real, JSONMap.KEY_DATABASE); - String sh = getString(real, JSONMap.KEY_SCHEMA); - String ds = getString(real, JSONMap.KEY_DATASOURCE); + String db = getString(real, KEY_DATABASE); + String sh = getString(real, KEY_SCHEMA); + String ds = getString(real, KEY_DATASOURCE); if (StringUtil.isEmpty(db, false)) { db = database; } @@ -1150,7 +1165,7 @@ public static , L extends List> M parse ds = datasource; } String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, ds, name); - String finalIdKey = StringUtil.isEmpty(idKey, false) ? JSONMap.KEY_ID : idKey; + String finalIdKey = StringUtil.isEmpty(idKey, false) ? KEY_ID : idKey; // TODO 放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 // 校验存在<<<<<<<<<<<<<<<<<<< @@ -1179,10 +1194,11 @@ public static , L extends List> M parse // 校验重复>>>>>>>>>>>>>>>>>>> // 校验并配置允许批量增删改部分失败<<<<<<<<<<<<<<<<<<< + String allowPartialUpdateFail = StringUtil.get(getString(target, ALLOW_PARTIAL_UPDATE_FAIL.name())); String[] partialFails = StringUtil.split(allowPartialUpdateFail); if (partialFails != null && partialFails.length > 0) { for (String key : partialFails) { - if (JSONMap.isArrayKey(key) == false) { + if (isArrayKey(key) == false) { throw new IllegalArgumentException("后端 Request 表中 " + ALLOW_PARTIAL_UPDATE_FAIL.name() + ":value 中 " + key + " 不合法!必须以 [] 结尾!"); } @@ -1205,7 +1221,7 @@ public static , L extends List> M parse // 校验并配置允许部分批量增删改失败>>>>>>>>>>>>>>>>>>> - String[] nks = ifObj == null ? null : StringUtil.split(getString(real, JSONMap.KEY_NULL)); + String[] nks = ifObj == null ? null : StringUtil.split(getString(real, KEY_NULL)); Collection nkl = nks == null || nks.length <= 0 ? new HashSet<>() : Arrays.asList(nks); Set> ifSet = ifObj == null ? null : ifObj.entrySet(); @@ -1215,7 +1231,7 @@ public static , L extends List> M parse // , apijson.JSONMap.KEY_USER_ID, apijson.JSONMap.KEY_USER_ID_IN)); // condKeys.addAll(apijson.JSONMap.TABLE_KEY_LIST); - String preCode = "var curObj = " + JSON.toJSONString(real) + ";"; + String preCode = "var curObj = " + toJSONString(real) + ";"; // 未传的 key 在后面 eval 时总是报错 undefined,而且可能有冲突,例如对象里有 "curObj": val 键值对,就会覆盖当前对象定义,还不如都是 curObj.sex 这样取值 // Set> rset = real.entrySet(); @@ -1287,7 +1303,8 @@ public static , L extends List> M parse } } } - Log.i(TAG, "parse return real = " + JSON.toJSONString(real)); + + Log.i(TAG, "parse return real = " + toJSONString(real)); return real; } @@ -1311,7 +1328,7 @@ public static ScriptEngine getScriptEngine(String lang) { * @return * @throws Exception */ - private static , L extends List> M operate(Operation opt, M targetChild + public static , L extends List> M operate(Operation opt, M targetChild , M real, @NotNull Parser parser) throws Exception { if (targetChild == null) { return real; @@ -1502,7 +1519,7 @@ public static void verifyType(@NotNull String tk, @NotNull String tv, Object rv, * @param parser * @throws Exception */ - private static , L extends List> void verifyValue(@NotNull String tk + public static , L extends List> void verifyValue(@NotNull String tk , @NotNull Object tv, @NotNull M real, @NotNull Parser parser) throws Exception { if (tv == null) { throw new IllegalArgumentException("operate operate == VERIFY " + tk + ":" + tv + " , >> tv == null!!!"); @@ -1556,7 +1573,7 @@ else if (tk.endsWith("~")) { // 正则匹配 } } else if (tk.endsWith("{}")) { //rv符合tv条件或在tv内 - if (tv instanceof String) {//TODO >= 0, < 10 + if (tv instanceof String) { //TODO >= 0, < 10 verifyCondition("{}", real, tk, tv, parser); } else if (tv instanceof List) { @@ -1575,26 +1592,6 @@ else if (tv instanceof List) { throw new UnsupportedDataTypeException("服务器Request表verify配置错误!"); } } - else if (tk.endsWith("{L}")) { //字符串长度 - if (tv instanceof String) { - logic = new Logic(tk.substring(0, tk.length() - 3)); - - rk = logic.getKey(); - rv = real.get(rk); - if (rv == null) { - return; - } - String[] tvs = tv.toString().split(","); - for (String tvItem : tvs) { - if (!verifyRV(tvItem,rv.toString())) { - throw new IllegalArgumentException(rk + ":value 中value长度不合法!必须匹配 " + tk + ":" + tv + " !"); - } - } - } - else { - throw new UnsupportedDataTypeException("服务器Request表verify配置错误!"); - } - } else if (tk.endsWith("<>")) { //rv包含tv内的值 logic = new Logic(tk.substring(0, tk.length() - 2)); rk = logic.getKey(); @@ -1635,41 +1632,41 @@ else if (tk.endsWith("<>")) { //rv包含tv内的值 } } - /** - * 校验字符串长度 - * + /**校验字符串长度 * @param rule 规则 - * @param content 内容 + * @param len 长度 * @return * @throws UnsupportedDataTypeException */ - private static boolean verifyRV(String rule,String content) throws UnsupportedDataTypeException { + public static boolean verifyLength(String rule, int len) throws UnsupportedDataTypeException { String first = null; String second = null; Matcher matcher = VERIFY_LENGTH_PATTERN.matcher(rule); while (matcher.find()) { - first = StringUtil.isEmpty(first)?matcher.group("first"):first; - second = StringUtil.isEmpty(second)?matcher.group("second"):second; + first = StringUtil.isEmpty(first) ? matcher.group("first") : first; + second = StringUtil.isEmpty(second) ? matcher.group("second") : second; } // first和second为空表示规则不合法 - if(StringUtil.isEmpty(first) || StringUtil.isEmpty(second)){ + if (StringUtil.isEmpty(first) || StringUtil.isEmpty(second)) { throw new UnsupportedDataTypeException("服务器Request表verify配置错误!"); } int secondNum = Integer.parseInt(second); - switch (Objects.requireNonNull(first)){ - case ">": - return content.length() > secondNum; + switch (Objects.requireNonNull(first)) { case ">=": - return content.length() >= secondNum; - case "<": - return content.length() < secondNum; + return len >= secondNum; case "<=": - return content.length() <= secondNum; - case "<>": - return content.length() != secondNum; - default: + return len <= secondNum; + case "!=": + return len != secondNum; + case ">": + return len > secondNum; + case "<": + return len < secondNum; + case "=": + return len == secondNum; } + // 出现不能识别的符号也认为规则不合法 throw new UnsupportedDataTypeException("服务器Request表verify配置错误!"); } @@ -1682,13 +1679,40 @@ private static boolean verifyRV(String rule,String content) throws UnsupportedDa * @param parser * @throws Exception */ - private static , L extends List> void verifyCondition( + public static , L extends List> void verifyCondition( @NotNull String funChar, @NotNull M real, @NotNull String tk, @NotNull Object tv , @NotNull Parser parser) throws Exception { - //不能用Parser, 0 这种不符合 StringUtil.isName ! - Logic logic = new Logic(tk.substring(0, tk.length() - funChar.length())); + // 不能用Parser, 0 这种不符合 StringUtil.isName ! + + boolean isRange = "{}".equals(funChar); + + String k = tk.substring(0, tk.length() - funChar.length()); + Logic logic = new Logic(k); String rk = logic.getKey(); - Object rv = real.get(rk); + boolean isLen = isRange && k.endsWith("["); + boolean isJSOnLen = isRange && k.endsWith("{"); + k = isLen || isJSOnLen ? rk.substring(0, rk.length() - 1) : rk; + Object rv = real.get(k); + int len = 0; + if (isLen) { + len = StringUtil.length(rv, false); + } + else if (isJSOnLen) { + rv = rv instanceof Map ? ((Map) rv) : (rv instanceof Collection ? ((Collection) rv) : JSON.parse(rv)); + len = rv instanceof Map ? ((Map) rv).size() : (rv instanceof Collection ? ((Collection) rv).size() : StringUtil.length(rv, false)); + } + + if (isLen || isJSOnLen) { + String[] tvs = tv.toString().split(","); + for (String tvItem : tvs) { + if (! verifyLength(tvItem, len)) { + throw new IllegalArgumentException(k + ":value 中value长度不合法!必须匹配 " + tk + ":" + tv + " !"); + } + } + + return; + } + if (rv == null) { return; } @@ -1697,7 +1721,7 @@ private static , L extends List> void v throw new IllegalArgumentException(rk + ":value 中value不合法!value 中不允许有单引号 ' !"); } - SQLConfig config = parser.createSQLConfig().setMethod(RequestMethod.GET).setCount(1).setPage(0); + SQLConfig config = parser.createSQLConfig().setMethod(GET).setCount(1).setPage(0); config.setTest(true); // config.setTable(Test.class.getSimpleName()); // config.setColumn(rv + logic.getChar() + funChar) @@ -1752,7 +1776,7 @@ public static , L extends List> void ve return; } - SQLConfig config = parser.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); + SQLConfig config = parser.createSQLConfig().setMethod(HEAD).setCount(1).setPage(0); config.setTable(table); param.forEach((key,value) -> config.putWhere(key, value, false)); @@ -1835,9 +1859,9 @@ public static , L extends List> void ve return; } - String finalIdKey = StringUtil.isEmpty(idKey, false) ? JSONMap.KEY_ID : idKey; + String finalIdKey = StringUtil.isEmpty(idKey, false) ? KEY_ID : idKey; - SQLConfig config = parser.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); + SQLConfig config = parser.createSQLConfig().setMethod(HEAD).setCount(1).setPage(0); config.setTable(table); if (exceptId > 0) { //允许修改自己的属性为该属性原来的值 config.putWhere(finalIdKey + "!", exceptId, false); diff --git a/APIJSONORM/src/main/java/apijson/orm/Entry.java b/APIJSONORM/src/main/java/apijson/orm/Entry.java index a8aaf7bfd..3e010435b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Entry.java +++ b/APIJSONORM/src/main/java/apijson/orm/Entry.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/FunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/FunctionParser.java index 155d80fae..8af029234 100644 --- a/APIJSONORM/src/main/java/apijson/orm/FunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/FunctionParser.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/JSONRequest.java b/APIJSONORM/src/main/java/apijson/orm/JSONRequest.java index 0b772c238..5ac92429e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/JSONRequest.java +++ b/APIJSONORM/src/main/java/apijson/orm/JSONRequest.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -8,6 +8,7 @@ import java.util.*; import apijson.JSON; +import apijson.Log; import apijson.StringUtil; /**JSONRequest for Server to replace apijson.JSONMap, @@ -99,7 +100,7 @@ public Object put(String key, Object value) { target = JSON.parse(value); } catch (Exception e) { // nothing - e.printStackTrace(); + Log.e(TAG, "JSON.parse failed for key: " + key, e); } // if (target == null) { // "tag":"User" 报错 // return null; diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index 39ca09a61..96d5e2ec7 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Join.java +++ b/APIJSONORM/src/main/java/apijson/orm/Join.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -25,12 +25,14 @@ public class Join, L extends List> { private List onList; // ON User.id = Moment.userId AND ... private M request; // { "id@":"/Moment/userId" } - private M outer; // "join": { " joinConfig; private SQLConfig cacheConfig; - private SQLConfig outerConfig; + private SQLConfig onConfig; + private SQLConfig outerConfig; public String getPath() { return path; @@ -78,13 +80,29 @@ public M getRequest() { public void setRequest(M request) { this.request = request; } - public M getOuter() { - return outer; + public M getOn() { + return on; + } + public void setOn(M on) { + this.on = on; } + public void setOuter(M outer) { this.outer = outer; } + public M getOuter() { + return outer; + } + + public SQLConfig getOuterConfig() { + return outerConfig; + } + + public void setOuterConfig(SQLConfig outerConfig) { + this.outerConfig = outerConfig; + } + public SQLConfig getJoinConfig() { return joinConfig; } @@ -97,11 +115,11 @@ public SQLConfig getCacheConfig() { public void setCacheConfig(SQLConfig cacheConfig) { this.cacheConfig = cacheConfig; } - public SQLConfig getOuterConfig() { - return outerConfig; + public SQLConfig getOnConfig() { + return onConfig; } - public void setOuterConfig(SQLConfig outerConfig) { - this.outerConfig = outerConfig; + public void setOnConfig(SQLConfig onConfig) { + this.onConfig = onConfig; } public boolean isOne2One() { diff --git a/APIJSONORM/src/main/java/apijson/orm/Logic.java b/APIJSONORM/src/main/java/apijson/orm/Logic.java index bb8e806e6..f860aa9a4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Logic.java +++ b/APIJSONORM/src/main/java/apijson/orm/Logic.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java index 8817886d1..5d207f41a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/OnParseCallback.java b/APIJSONORM/src/main/java/apijson/orm/OnParseCallback.java index 243acf048..e02fd90f8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/OnParseCallback.java +++ b/APIJSONORM/src/main/java/apijson/orm/OnParseCallback.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/Operation.java b/APIJSONORM/src/main/java/apijson/orm/Operation.java index 2976d09b0..b69eb9a75 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Operation.java +++ b/APIJSONORM/src/main/java/apijson/orm/Operation.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -41,7 +41,7 @@ public enum Operation { * @see {@link AbstractVerifier#verifyType(String, String, Object, boolean)} */ TYPE, - + /** * 验证是否符合预设的条件,结构是 * { @@ -54,7 +54,8 @@ public enum Operation { * { * "phone~": "PHONE", //phone 必须满足 PHONE 的格式,配置见 {@link AbstractVerifier#COMPILE_MAP} * "status{}": [1,2,3], //status 必须在给出的范围内 - * "content{L}": ">0,<=255", //content的长度 必须在给出的范围内 + * "content[{}": ">0", //content的长度 必须在给出的范围内 + * "pictureList{&{}": ">0,<=10", //pictureList 的 JSON 长度必须在给出的范围内 * "balance&{}":">0,<=10000" //必须满足 balance>0 & balance<=10000 * } */ diff --git a/APIJSONORM/src/main/java/apijson/orm/Pair.java b/APIJSONORM/src/main/java/apijson/orm/Pair.java index 94f2abcc4..7661d07b5 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Pair.java +++ b/APIJSONORM/src/main/java/apijson/orm/Pair.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index ce726eb73..1492dfc11 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Parser.java +++ b/APIJSONORM/src/main/java/apijson/orm/Parser.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java b/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java index d6eb1c7db..f3f8d375b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java +++ b/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index b8c9fad86..5386d160e 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ @@ -35,6 +35,7 @@ public interface SQLConfig, L extends List, L extends List, L extends List> implements ScriptExecutor { + private static final String TAG = "JSR223ScriptExecutor"; + protected ScriptEngine scriptEngine; private final Map compiledScriptMap = new ConcurrentHashMap<>(); @@ -45,7 +48,7 @@ public void load(String name, String script) { CompiledScript compiledScript = ((Compilable) scriptEngine).compile(convertScript(script)); compiledScriptMap.put(name, compiledScript); } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, "compile script failed: " + name, e); } } diff --git a/APIJSONORM/src/main/java/apijson/package-info.java b/APIJSONORM/src/main/java/apijson/package-info.java index 42e240185..a4fcbeb21 100755 --- a/APIJSONORM/src/main/java/apijson/package-info.java +++ b/APIJSONORM/src/main/java/apijson/package-info.java @@ -1,4 +1,4 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +/*Copyright (C) 2020 Tencent. All rights reserved. This source code is licensed under the Apache License Version 2.0.*/ diff --git a/APIJSONORM/src/main/resources/application.properties b/APIJSONORM/src/main/resources/application.properties deleted file mode 100755 index e69de29bb..000000000 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9656e289..f9fab2942 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ - [403f](https://github.com/403f) - [gujiachun](https://github.com/gujiachun) - [gdjs2](https://github.com/gdjs2)(University of California, Riverside) -- [Rkyzzy](https://github.com/Rkyzzy)(SUSTech, University of California, Berkeley) +- [Rkyzzy](https://github.com/Rkyzzy)(理想汽车工程师, SUSTech, University of California, Berkeley) - [kxlv2000](https://github.com/kxlv2000)(SUSTech) - [caohao-go](https://github.com/caohao-go)(腾讯工程师,曾在华为、恒生担任C/C++开发工程师,在wps担任项目经理,在360担任技术专家) - [Wscats](https://github.com/Wscats)(腾讯工程师、腾讯 AlloyTeam 成员、Tencent Creation Camp 成员、知名技术博主) @@ -33,10 +33,10 @@ - [JieJo](https://github.com/JieJo) - [yeyuezhishou](https://github.com/yeyuezhishou)(圆通工程师) - [kenlig](https://github.com/kenlig)(还开源了 apijsondocs) -- [andream7](https://github.com/andream7)(微软工程师,还开源了 apijson-db2) +- [andream7](https://github.com/andream7)(字节跳动、微软 工程师,还开源了 apijson-db2) - [qiujunlin](https://github.com/qiujunlin)(字节跳动工程师,还开源了 APIJSONDemo) - [HANXU2018](https://github.com/HANXU2018)(网易工程师,还开源了 APIJSON-DOC) -- [hclown9804](https://github.com/hclown9804) +- [hclown9804](https://github.com/hclown9804)(Datawhale) - [chenyanlann](https://github.com/chenyanlann)(还开源了 APIJSONDemo_ClickHouse) - [haolingzhang1](https://github.com/haolingzhang1)(腾讯工程师,还开源了 APIJson--demo) - [jerrylususu](https://github.com/jerrylususu)(还开源了 apijson_todo_demo 和 apijson_role_extend) @@ -57,7 +57,7 @@ - [aninZz](https://github.com/aninZz) - [leomiaomiao](https://github.com/leomiaomiao) - [YqxLzx](https://github.com/YqxLzx) -- [hiteshbedre](https://github.com/hiteshbedre) +- [hiteshbedre](https://github.com/hiteshbedre)(privado.ai 印裔工程师) - [wahowaho](https://github.com/wahowaho) - [jarrodquan](https://github.com/jarrodquan) - [gemufeng](https://github.com/gemufeng)(上海麦市工程师) @@ -71,6 +71,10 @@ - [calmcc](https://github.com/calmcc) - [lindaifeng](https://github.com/lindaifeng) - [DenineLu](https://github.com/DenineLu)(小红书工程师) +- [wz11wz](https://github.com/wz11wz) +- [GeXin97](https://github.com/GeXin97) +- [yunjiao-source](https://github.com/yunjiao-source)(还开源了 [apijson-spring-boot](https://gitee.com/yunjiao-source/apijson-spring-boot)) +- [moxixi527](https://github.com/moxixi527)(热门技术博主) #### 其中特别致谢:
cloudAndMonkey 提交的 11 个 Commits, 对 APIJSON 做出了 1,496 增加和 845 处删减(截止 2022/12/15 日);
diff --git a/Document-Chinese.md b/Document-Chinese.md new file mode 100644 index 000000000..11db6fe86 --- /dev/null +++ b/Document-Chinese.md @@ -0,0 +1,426 @@ +[English](https://github.com/Tencent/APIJSON/blob/master/Document-English.md) +
+ +# APIJSON 通用文档 +本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, PHP, Python, TypeScript 等开发语言无关。
+具体开发语言相关的 配置、运行、部署 等文档见各个相关项目的文档,可以在首页点击对应语言的入口来查看。
+https://github.com/Tencent/APIJSON +![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) + + +后端开发者可以先看 [图文入门教程1](http://apijson.cn/doc/zh/) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") + +* ### [1.示例](#1) +* ### [2.对比传统方式](#2) +* [2.1 开发流程](#2.1) +* [2.2 前端请求](#2.2) +* [2.3 后端操作](#2.3) +* [2.4 前端解析](#2.4) +* [2.5 对应不同需求的请求](#2.5) +* [2.6 对应不同请求的结果](#2.6) +* ### [3.设计规范](#3) +* [3.1 操作方法](#3.1) +* [3.2 功能符](#3.2) + + +##

1.示例

+ +#### 获取用户 +请求: +
{
+  "User":{
+    "id":38710
+  }
+}
+
+ +[点击这里测试](http://apijson.cn:8080/get/{"User":{"id":38710}}) + +返回: +
{
+  "User":{
+    "id":38710,
+    "sex":0,
+    "name":"TommyLemon",
+    "tag":"Android&Java",
+    "head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
+    "date":1485948110000,
+    "pictureList":[
+      "http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
+      "http://common.cnblogs.com/images/icon_weibo_24.png"
+    ]
+  },
+  "code":200,
+  "msg":"success"
+}
+
+ +

+ [GIF] APIJSON 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_single.gif) + +
+ +#### 获取用户列表 +请求: +
{
+  "[]":{
+    "count":3, //只要3个
+    "User":{
+      "@column":"id,name" //只要id,name这两个字段
+    }
+  }
+}
+
+ +[点击这里测试](http://apijson.cn:8080/get/{"[]":{"count":3,"User":{"@column":"id,name"}}}) + +返回: +
{
+  "[]":[
+    {
+      "User":{
+        "id":38710,
+        "name":"TommyLemon"
+      }
+    },
+    {
+      "User":{
+        "id":70793,
+        "name":"Strong"
+      }
+    },
+    {
+      "User":{
+        "id":82001,
+        "name":"Android"
+      }
+    }
+  ],
+  "code":200,
+  "msg":"success"
+}
+
+ +

+ [GIF] APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_array.gif) + +
+ +#### 获取动态及发布者用户 +请求: +
{
+  "Moment":{
+  },
+  "User":{
+    "id@":"Moment/userId"  //User.id = Moment.userId
+  }
+}
+
+ +[点击这里测试](http://apijson.cn:8080/get/{"Moment":{},"User":{"id@":"Moment%252FuserId"}}) + +返回: +
{
+  "Moment":{
+    "id":12,
+    "userId":70793,
+    "date":"2017-02-08 16:06:11.0",
+    "content":"1111534034"
+  },
+  "User":{
+    "id":70793,
+    "sex":0,
+    "name":"Strong",
+    "tag":"djdj",
+    "head":"http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000",
+    "contactIdList":[
+      38710,
+      82002
+    ],
+    "date":"2017-02-01 19:21:50.0"
+  },
+  "code":200,
+  "msg":"success"
+}
+
+ +
+ +#### 获取类似微信朋友圈的动态列表 +请求: +
{
+  "[]":{                             //请求一个数组
+    "page":0,                        //数组条件
+    "count":2,
+    "Moment":{                       //请求一个名为Moment的对象
+      "content$":"%a%"               //对象条件,搜索content中包含a的动态
+    },
+    "User":{
+      "id@":"/Moment/userId",  //User.id = Moment.userId  缺省引用赋值路径,从所处容器的父容器路径开始
+      "@column":"id,name,head"       //指定返回字段
+    },
+    "Comment[]":{                    //请求一个名为Comment的数组,并去除Comment包装
+      "count":2,
+      "Comment":{
+        "momentId@":"[]/Moment/id"   //Comment.momentId = Moment.id  完整引用赋值路径
+      }
+    }
+  }
+}
+
+ +[点击这里测试](http://apijson.cn:8080/get/{"[]":{"page":0,"count":2,"Moment":{"content$":"%2525a%2525"},"User":{"id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment[]":{"count":2,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) + +返回: +
{
+  "[]":[
+    {
+      "Moment":{
+        "id":15,
+        "userId":70793,
+        "date":1486541171000,
+        "content":"APIJSON is a JSON Transmission Protocol…",
+        "praiseUserIdList":[
+          82055,
+          82002,
+          82001
+        ],
+        "pictureList":[
+          "http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
+          "http://common.cnblogs.com/images/icon_weibo_24.png"
+        ]
+      },
+      "User":{
+        "id":70793,
+        "name":"Strong",
+        "head":"http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000"
+      },
+      "Comment[]":[
+        {
+          "id":176,
+          "toId":166,
+          "userId":38710,
+          "momentId":15,
+          "date":1490444883000,
+          "content":"thank you"
+        },
+        {
+          "id":1490863469638,
+          "toId":0,
+          "userId":82002,
+          "momentId":15,
+          "date":1490863469000,
+          "content":"Just do it"
+        }
+      ]
+    },
+    {
+      "Moment":{
+        "id":58,
+        "userId":90814,
+        "date":1485947671000,
+        "content":"This is a Content...-435",
+        "praiseUserIdList":[
+          38710,
+          82003,
+          82005,
+          93793,
+          82006,
+          82044,
+          82001
+        ],
+        "pictureList":[
+          "http://static.oschina.net/uploads/img/201604/22172507_aMmH.jpg"
+        ]
+      },
+      "User":{
+        "id":90814,
+        "name":7,
+        "head":"http://static.oschina.net/uploads/user/51/102723_50.jpg?t=1449212504000"
+      },
+      "Comment[]":[
+        {
+          "id":13,
+          "toId":0,
+          "userId":82005,
+          "momentId":58,
+          "date":1485948050000,
+          "content":"This is a Content...-13"
+        },
+        {
+          "id":77,
+          "toId":13,
+          "userId":93793,
+          "momentId":58,
+          "date":1485948050000,
+          "content":"This is a Content...-77"
+        }
+      ]
+    }
+  ],
+  "code":200,
+  "msg":"success"
+}
+
+ +

+ [GIF] APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) + +
+ +

+ [GIF] APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_join.gif) + +
+ +

+ [GIF] APIJSON 各种子查询:@from@ FROM, key@ =, key>@ >, key{}@ IN, key}{@ EXISTS 等 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_subquery.gif) + +
+ +

+ [GIF] APIJSON 部分功能演示集合,由浅入深、由简单到复杂 +

+ +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif) + +
+ +[在线测试](http://apijson.cn/api) + +
+
+ +##

2.对比传统RESTful方式

+ +###

2.1 开发流程

+ 开发流程 | 传统方式 | APIJSON +-------- | ------------ | ------------ + 接口传输 | 等后端编辑接口,然后更新文档,前端再按照文档编辑请求和解析代码 | 前端按照自己的需求编辑请求和解析代码。
没有接口,更不需要文档!前端再也不用和后端沟通接口或文档问题了! + 兼容旧版 | 后端增加新接口,用v2表示第2版接口,然后更新文档 | 什么都不用做! + +
+ +###

2.2 前端请求

+ 前端请求 | 传统方式 | APIJSON +-------- | ------------ | ------------ + 要求 | 前端按照文档在对应URL后面拼接键值对 | 前端按照自己的需求在固定URL后拼接JSON + URL | 不同的请求对应不同的URL,基本上有多少个不同的请求就得有多少个接口URL | 相同的操作方法(增删改查)都用同一个URL,
大部分请求都用7个通用接口URL的其中一个 + 键值对 | key=value | key:value + 结构 | 同一个URL内table_name只能有一个

base_url/get/table_name?
key0=value0&key1=value1... | 同一个URL后TableName可传任意数量个

base_url/get/
{
   TableName0:{
     key0:value0,
     key1:value1,
     ...
   },
   TableName1:{
     ...
   }
   ...
} + +
+ +###

2.3 后端操作

+ 后端操作 | 传统方式 | APIJSON +-------- | ------------ | ------------ + 解析和返回 | 取出键值对,把键值对作为条件用预设的的方式去查询数据库,最后封装JSON并返回给前端 | 把Parser#parse方法的返回值返回给前端就行 + 返回JSON结构的设定方式 | 由后端设定,前端不能修改 | 由前端设定,后端不能修改 + +
+ +###

2.4 前端解析

+ 前端解析 | 传统方式 | APIJSON +-------- | ------------ | ------------ + 查看方式 | 查文档或问后端,或等请求成功后看日志 | 看请求就行,所求即所得,不用查、不用问、不用等。也可以等请求成功后看日志 + 解析方法 | 用JSON解析器来解析JSONObject | 可以用JSONResponse解析JSONObject,或使用传统方式 + +
+ +###

2.5 前端对应不同需求的请求

+ 前端的请求 | 传统方式 | APIJSON +-------- | ------------ | ------------ + User | base_url/get/user?id=38710 | [base_url/get/
{
   "User":{
     "id":38710
   }
}](http://apijson.cn:8080/get/{"User":{"id":38710}}) + Moment和对应的User | 分两次请求
Moment:
base_url/get/moment?userId=38710

User:
base_url/get/user?id=38710 | [base_url/get/
{
   "Moment":{
     "userId":38710
   },
   "User":{
     "id":38710
   }
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id":38710}}) + User列表 | base_url/get/user/list?
page=0&count=3&sex=0 | [base_url/get/
{
   "User[]":{
     "page":0,
     "count":3,
     "User":{
       "sex":0
     }
   }
}](http://apijson.cn:8080/get/{"User[]":{"page":0,"count":3,"User":{"sex":0}}}) + Moment列表,
每个Moment包括
1.发布者User
2.前3条Comment | Moment里必须有
1.User对象
2.Comment数组

base_url/get/moment/list?
page=0&count=3&commentCount=3 | [base_url/get/
{
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) + User发布的Moment列表,
每个Moment包括
1.发布者User
2.前3条Comment | 1.Moment里必须有User对象和Comment数组
2.字段名必须查接口文档,例如评论数量字段名可能是
commentCount,comment_count或者简写cmt_count等各种奇葩写法...

base_url/get/moment/list?
page=0&count=3
&commentCount=3&userId=38710 | 有以下几种方式:

① 把以上请求里的
"Moment":{}, "User":{"id@":"/Moment/userId"}
改为
["Moment":{"userId":38710}, "User":{"id":38710}](http://apijson.cn:8080/get/{"[]":{"page":0,"count":3,"Moment":{"userId":38710},"User":{"id":38710},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})

② 或把User放在上面的最外层省去重复的User
[base_url/get/
{
   "User":{
     "id":38710
   },
   "[]":{
     "page":0,
     "count":3,
     "Moment":{
       "userId":38710
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"User":{"id":38710},"[]":{"page":0,"count":3,"Moment":{"userId":38710},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})

③ 如果User之前已经获取到了,还可以不传User来节省请求和返回数据的流量并提升速度
[base_url/get/
{
   "[]":{
     "page":0,
     "count":3,
     "Moment":{
       "userId":38710
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"[]":{"page":0,"count":3,"Moment":{"userId":38710},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) + +
+ +###

2.6 后端对应不同请求的返回结果

+ 后端的返回结果 | 传统方式 | APIJSON +-------- | ------------ | ------------ + User | {
   "data":{
     "id":38710,
     "name":"xxx",
     ...
   },
   "code":200,
   "msg":"success"
} | {
   "User":{
     "id":38710,
     "name":"xxx",
     ...
   },
   "code":200,
   "msg":"success"
} + Moment和对应的User | 分别返回两次请求的结果,获取到Moment后取出userId作为User的id条件去查询User

Moment:
{
   "data":{
     "id":235,
     "content":"xxx",
     ...
   },
   "code":200,
   "msg":"success"
}

User:
{
   "data":{
     "id":38710,
     "name":"xxx",
     ...
   },
   "code":200,
   "msg":"success"
} | 一次性返回,没有传统方式导致的 长时间等待结果、两次结果间关联、线程多次切换 等问题

{
   "Moment":{
     "id":235,
     "content":"xxx",
     ...
   },
   "User":{
     "id":38710,
     "name":"xxx",
     ...
   },
   "code":200,
   "msg":"success"
} + User列表 | {
   "data":[
     {
       "id":38710,
       "name":"xxx",
       ...
     },
     {
       "id":82001,
       ...
     },
     ...
   ],
   "code":200,
   "msg":"success"
} | {
   "User[]":[
     {
       "id":38710,
       "name":"xxx",
       ...
     },
     {
       "id":82001,
       ...
     },
     ...
   ],
   "code":200,
   "msg":"success"
} + Moment列表,每个Moment包括发布者User和前3条Comment | Moment里必须有
1.User对象
2.Comment数组

{
   "data":[
     {
       "id":235,
       "content":"xxx",
       ...,
       "User":{
         ...
       },
       "Comment":[
         ...
       ]
     },
     {
       "id":301,
       "content":"xxx",
       ...,
       "User":{
         ...
       },
       ...
     },
     ...
   ],
   "code":200,
   "msg":"success"
} | 1.高灵活,可任意组合
2.低耦合,逻辑很清晰

{
   "[]":[
     {
       "Moment":{
         "id":235,
         "content":"xxx",
         ...
       },
       "User":{
         ...
       },
       "Comment[]":[
         ...
       ]
     },
     {
       "Moment":{
         "id":301,
         "content":"xxx",
         ...
       },
       "User":{
         ...
       },
       ...
     },
     ...
   ],
   "code":200,
   "msg":"success"
} + User发布的Moment列表,每个Moment包括发布者User和前3条Comment | 1.大量重复User,浪费流量和服务器性能
2.优化很繁琐,需要后端扩展接口、写好文档,前端/前端再配合优化

{
   "data":[
     {
       "id":235,
       "content":"xxx",
       ...,
       "User":{
         "id":38710,
         "name":"Tommy"
         ...
       },
       "Comment":[
         ...
       ]
       ...
     },
     {
       "id":470,
       "content":"xxx",
       ...,
       "User":{
         "id":38710,
         "name":"Tommy"
         ...
       },
       "Comment":[
         ...
       ]
       ...
     },
     {
       "id":511,
       "content":"xxx",
       ...,
       "User":{
         "id":38710,
         "name":"Tommy"
         ...
       },
       "Comment":[
         ...
       ]
       ...
     },
     {
       "id":595,
       "content":"xxx",
       ...,
       "User":{
         "id":38710,
         "name":"Tommy"
         ...
       },
       "Comment":[
         ...
       ]
       ...
     },
     ...
   ],
   "code":200,
   "msg":"success"
} | 以上不同请求方式的结果:

① 常规请求
{
   "[]":[
     {
       "Moment":{
         "id":235,
         "content":"xxx",
         ...
       },
       "User":{
         "id":38710,
         "name":"Tommy"
         ...
       },
       "Comment[]":[
         ...
       ]
     },
     ...
   ],
   "code":200,
   "msg":"success"
}

② 省去重复的User
{
   "User":{
     "id":38710,
     "name":"Tommy",
     ...
   },
   "[]":[
     {
       "Moment":{
         "id":235,
         "content":"xxx",
         ...
       },
       "Comment[]":[
         ...
       ]
     },
     ...
   ],
   "code":200,
   "msg":"success"
}

③ 不查询已获取到的User
{
   "[]":[
     {
       "Moment":{
         "id":235,
         "content":"xxx",
         ...
       },
       "Comment[]":[
         ...
       ]
     },
     ...
   ],
   "code":200,
   "msg":"success"
} + + +1.base_url指基地址,一般是顶级域名,其它分支url都是在base_url后扩展。如base_url:http://apijson.cn:8080/ ,对应的GET分支url:http://apijson.cn:8080/get/ 。下同。
+2.请求中的key或value任意一个为null值时,这个 key:value键值对 被视为无效。下同。
+3.请求中的 / 需要转义。JSONRequest.java已经用URLEncoder.encode转义,不需要再写;但如果是浏览器或Postman等直接输入url/request,需要把request中的所有 / 都改成 %252F 。下同。
+4.code,指返回结果中的状态码,200表示成功,其它都是错误码,值全部都是HTTP标准状态码。下同。
+5.msg,指返回结果中的状态信息,对成功结果或错误原因的详细说明。下同。
+6.code和msg总是在返回结果的同一层级成对出现。对所有请求的返回结果都会在最外层有一对总结式code和msg。下同。
+7.id等字段对应的值仅供说明,不一定是数据库里存在的,请求里用的是真实存在的值。下同。 + +
+
+ +##

3.设计规范

+ +###

3.1 操作方法

+ + 方法及说明 | URL | Request | Response +------------ | ------------ | ------------ | ------------ +GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fget&type=JSON&json={"Moment"%3A{"id"%3A235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "code":200,
   "msg":"success"
} +HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} +GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}),其它同GET | 同GET +HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD +POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON%20is%20the%20Real-Time%20coding-free,%20powerful%20and%20secure%20ORM."},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON is the real-time coding-free, powerful and secure ORM')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON%20is%20the%20Real-Time%20coding-free,%20powerful%20and%20secure%20ORM."},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON is the real-time coding-free, powerful and secure ORM');`

`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.');` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} +PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON%20is%20the%20Real-Time%20coding-free,%20powerful%20and%20secure%20ORM."},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON is the real-time coding-free, powerful and secure ORM' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST +DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} +以上接口的简单形式:
base_url/{method}/{tag} | GET: 普通获取数据
base_url/get/{tag}

HEAD: 普通获取数量
base_url/head/{tag}

GETS: 安全/私密获取数据
base_url/gets/{tag}

HEADS: 安全/私密获取数量
base_url/heads/{tag}

POST: 新增数据
base_url/post/{tag}

PUT: 修改数据 base_url/put/{tag}

DELETE: 删除数据
base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy:
[base_url/gets/Privacy/
{"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001})
相当于
[base_url/gets/
{"tag":"Privacy", "Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}})

例如批量修改 id = 114, 124 的 Comment 的 content:
[base_url/put/Comemnt[]/
{
   "id{}":[114,124],
   "content":"test multi put"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"})
相当于
[base_url/put/
{
   "tag":"Comment[]",
   "Comment":{
     "id{}":[114,124],
     "content":"test multi put"
   }
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法 + +1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
+2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
+3.GET、HEAD请求是开放请求,可任意组合任意嵌套。其它请求为受限制的安全/私密请求,对应的 方法(method), 标识(tag), 版本(version), 结构(structure) 都必须和 后端Request表中所指定的 一一对应,否则请求将不被通过。version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本。下同。
+4.GETS与GET、HEADS与HEAD分别为同一类型的操作方法,请求稍有不同但返回结果相同。下同。
+5.在HTTP通信中,自动化接口(get,gets,head,heads,post,put,delete) 全用HTTP POST请求。下同。
+6.所有JSONObject都视为容器(或者文件夹),结构为 {...} ,里面可以放普通对象或子容器。下同。
+7.每个对象都有一个唯一的路径(或者叫地址),假设对象名为refKey,则用 key0/key1/.../refKey 表示。下同。 + +
+ +###

3.2 功能符

+ + 功能 | 键值对格式 | 使用示例 +------------ | ------------ | ------------ + 查询数组 | "key[]":{},后面是 JSONObject,key 可省略。当 key 和里面的 Table 名相同时,Table 会被提取出来,即 {Table:{Content}} 会被转化为 {Content} | [{"User[]":{"User":{}}}](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{}}}),查询一个 User 数组。这里 key 和 Table 名都是 User,User 会被提取出来,即 {"User":{"id", ...}} 会被转化为 {"id", ...},如果要进一步提取 User 中的 id,可以把 User[] 改为 User-id[],其中 - 用来分隔路径中涉及的 key + 匹配选项范围 | "key{}":[],后面是 JSONArray,作为 key 可取的值的选项 | ["id{}":[38710,82001,70793]](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":[38710,82001,70793]}}}),对应 SQL 是`id IN(38710,82001,70793)`,查询 id 符合 38710,82001,70793 中任意一个的一个 User 数组 + 匹配条件范围 | "key{}":"条件0,条件1...",条件为 SQL 表达式字符串,可进行数字比较运算等 | ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}}),对应 SQL 是`id<=80000 OR id>90000`,查询 id 符合 id\<=80000 \| id>90000 的一个 User 数组 + 包含选项范围 | "key<\>":value => "key<\>":[value],key 对应值的类型必须为 JSONArray,value 值类型只能为 Boolean, Number, String 中的一种 | ["contactIdList<\>":38710](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"contactIdList<\>":38710}}}),对应SQL是`json_contains(contactIdList,38710)`,查询 contactIdList 包含 38710 的一个 User 数组 + 判断是否存在 | "key}{@":{
   "from":"Table",
   "Table":{ ... }
}
其中:
}{ 表示 EXISTS;
key 用来标识是哪个判断;
@ 后面是 子查询 对象,具体见下方 子查询 的说明。 | ["id}{@":{
   "from":"Comment",
   "Comment":{
      "momentId":15
   }
}](http://apijson.cn:8080/get/{"User":{"id}{@":{"from":"Comment","Comment":{"momentId":15}}}})
WHERE EXISTS(SELECT * FROM Comment WHERE momentId=15) + 远程调用函数 | "key()":"函数表达式",函数表达式为 function(key0,key1...),会调用后端对应的函数 function(JSONObject request, String key0, String key1...),实现 参数校验、数值计算、数据同步、消息推送、字段拼接、结构变换 等特定的业务逻辑处理,
可使用 - 和 + 表示优先级,解析 key-() > 解析当前对象 > 解析 key() > 解析子对象 > 解析 key+() | ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}}),会调用远程函数 [boolean isContain(JSONObject request, String array, String value)](https://github.com/APIJSON/apijson-framework/blob/master/src/main/java/apijson/framework/APIJSONFunctionParser.java#L361-L374) ,然后变为 "isPraised":true 这种(假设点赞用户id列表包含了userId,即这个User点了赞) + 存储过程 | "@key()":"SQL函数表达式",函数表达式为
function(key0,key1...)
会调用后端数据库对应的存储过程 SQL函数
function(String key0, String key1...)
除了参数会提前赋值,其它和 远程函数 一致 | ["@limit":10,
"@offset":0,
"@procedure()":"getCommentByUserId(id,@limit,@offset)"](http://apijson.cn:8080/get/{"User":{"@limit":10,"@offset":0,"@procedure()":"getCommentByUserId(id,@limit,@offset)"}})
会转为
`getCommentByUserId(38710,10,0)`
来调用存储过程 SQL 函数
`getCommentByUserId(IN id bigint, IN limit int, IN offset int)`
然后变为
"procedure":{
   "count":-1,
   "update":false,
   "list":[]
}
其中 count 是指写操作影响记录行数,-1 表示不是写操作;update 是指是否为写操作(增删改);list 为返回结果集 + 引用赋值 | "key@":"key0/key1/.../refKey",引用路径为用/分隔的字符串。以/开头的是缺省引用路径,从声明key所处容器的父容器路径开始;其它是完整引用路径,从最外层开始。
被引用的refKey必须在声明key的上面。如果对refKey的容器指定了返回字段,则被引用的refKey必须写在@column对应的值内,例如 "@column":"refKey,key1,..." | ["Moment":{
   "userId":38710
},
"User":{
   "id@":"/Moment/userId"
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}})
User内的id引用了与User同级的Moment内的userId,
即User.id = Moment.userId,请求完成后
"id@":"/Moment/userId" 会变成 "id":38710 + 子查询 | "key@":{
   "range":"ALL",
   "from":"Table",
   "Table":{ ... }
}
其中:
range 可为 ALL,ANY;
from 为目标表 Table 的名称;
@ 后面的对象类似数组对象,可使用 count 和 join 等功能。 | ["id@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id=(SELECT min(userId) FROM Comment) + 模糊搜索 | `"key$":"SQL搜索表达式"` => `"key$":["SQL搜索表达式"]`,任意SQL搜索表达式字符串,如 %key%(包含key), key%(以key开始), %k%e%y%(包含字母k,e,y) 等,%表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应SQL是`name LIKE '%m%'`,查询name包含"m"的一个User数组 + 正则匹配 | "key~":"正则表达式" => "key~":["正则表达式"],任意正则表达式字符串,如 ^[0-9]+$ ,*~ 忽略大小写,可用于高级搜索 | ["name~":"^[0-9]+$"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name~":"^[0-9]%252B$"}}}),对应SQL是`name REGEXP '^[0-9]+$'`,查询name中字符全为数字的一个User数组 + 连续范围 | "key%":"start,end" => "key%":["start,end"],其中 start 和 end 都只能为 Boolean, Number, String 中的一种,如 "2017-01-01,2019-01-01" ,["1,90000", "82001,100000"] ,可用于连续范围内的筛选 | ["date%":"2017-10-01,2018-10-01"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"date%2525":"2017-10-01,2018-10-01"}}}),对应SQL是`date BETWEEN '2017-10-01' AND '2018-10-01'`,查询在2017-10-01和2018-10-01期间注册的用户的一个User数组 + 新建别名 | ① "name:alias",name 映射为 alias,用 alias 替代 name,可用于 column,Table,SQL 函数 等,只用于 GET 类型、HEAD 类型的请求

② 函数调用映射
"@key": "fun:avg(id);keyA:(keyB)",
"fun>": 1,
"keyA": 1
其中 fun:fun(arg) 把 SQL 函数调用 fun(arg) 作为左侧表达式替代 fun,即 fun(arg) > 1;
keyA:(keyB) 表示将字段 keyA 重命名为 keyB,即实际 SQL 中为 keyB = 1,常用于重命名冲突的多条件同名字段。 | ① ["@column":"toId:parentId"](http://apijson.cn:8080/get/{"Comment":{"@column":"id,toId:parentId","id":51}}),对应 SQL 是 `toId AS parentId`,将查询的字段 toId 变为 parentId 返回

② ["@key": "len:length(content);mid:(momentId)",
"len<=": 10,
"mid": 12,
"momentId": 15,
"@combine": "(len<= \\| mid) & momentId"](http://apijson.cn/api?type=JSON&json={%22Comment%22:{%22@key%22:%22len%3Alength(content)%3Bmid%3A(momentId)%22,%22len%3C=%22:10,%22mid%22:12,%22momentId%22:15,%22@combine%22:%22(len%3C%3D%20%7C%20mid)%20%26%20momentId%22}})
对应 SQL 是 `(length(content) <= 10 OR momentId = 12) AND momentId = 15` + 增加 或 扩展 | "key+":Object,Object的类型由key指定,且类型为Number,String,JSONArray中的一种。如 82001,"apijson",["url0","url1"] 等。只用于PUT请求 | "praiseUserIdList+":[82001],对应SQL是`json_insert(praiseUserIdList,82001)`,添加一个点赞用户id,即这个用户点了赞 + 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 + 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应 SQL 是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) + 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于 "key&{}":"条件"等

② \| 可用于 "key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如 "key!":Object,也可像 &,\| 一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同 "id{}":">90000,<=80000",对应 SQL 是`id>90000 OR id<=80000`,即 id 满足 id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应 SQL 是`id NOT IN(82001,38710)`,即 id 满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 + 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中 {} 内的关键词,Object 的类型由 key 指定

① "count":5,查询数量,0 表示最大值,默认值为 10,默认最大值为 100

② "page":1,查询页码,从 0 开始,默认值为 0,默认最大值为 100,一般和 count 一起用

③ "query":2,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到自定义 key:value 键值对,不传则返回默认键值对,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "compat": true
处理最外层查询字段有特殊处理的情况下 "query":2 返回查询总数不准确的问题:如DISTINCT去重、Aggregate 函数等

⑤ "join":"&/Table0,\"join":{
   "&/Table0":{}, // 支持 ON 多个字段关联,
   "\      "key0":value0, // 其它ON条件
     "key2":value2,
     ...
     "@combine":"...", // 其它ON条件的组合方式
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey` AND 其它ON条件
除了 = 等价关联,也支持 ! 不等关联、\> \< \>= \<= 等比较关联和 $ ~ {} <> 等其它复杂关联方式

⑥ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应 SQL 是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应 SQL 是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total", // 可省略
"info@":"/[]/info" // 可省略](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, // 总数
"info":{ // 分页详情
   "total":139, // 总数
   "count":5, // 每页数量
   "page":0, // 当前页码
   "max":27, // 最大页码
   "more":true, // 是否还有更多
   "first":true, // 是否为首页
   "last":false // 是否为尾页
}

④查询User数组ID唯一情况下的User总数:
["[]":{
   "query":2,
   "compat":"true",
   "User":{
     "@column":"DISTINCT id"
   }
}](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}}})
返回的数据和结构同上

⑤ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" // 主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn/api/?type=JSON&url=http://apijson.cn:8080/get&json=%7B%22%5B%5D%22:%7B%22count%22:5,%22join%22:%22%26%2FUser%2Fid@,%3C%2FComment%22,%22Moment%22:%7B%22@column%22:%22id,userId,content%22,%22@group%22:%22id%22%7D,%22User%22:%7B%22name~%22:%22t%22,%22id@%22:%22%2FMoment%2FuserId%22,%22@column%22:%22id,name,head%22%7D,%22Comment%22:%7B%22momentId@%22:%22%2FMoment%2Fid%22,%22@column%22:%22id,momentId,content%22%7D%7D%7D)

⑥ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", // 自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + 对象关键词,可自定义 | "@key":Object,@key 为 Table:{} 中 {} 内的关键词,Object 的类型由 @key 指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接。注意不要缺少或多余任何一个空格。

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果 @column 里声明了 Table 的 id,则 id 也必须在 @group 中声明;其它情况下必须满足至少一个条件:
1.分组的 key 在 @column 里声明
2.Table 主键在 @group 中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL 函数条件,一般和 @group 一起用,函数一般在 @column 里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防 SQL 注入

⑬ "@null":"key1,key2...",空值键值对,自动插入 key1:null, key2:null ... 并作为有效键值对执行,作为条件时对应 SQL 是 `WHERE tag IS NULL`,作为值时对应 SQL 是 `SET tag = NULL`

⑭ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索 name 或 tag 任何一个字段包含字符 a 的 User 列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询 id,sex,name 这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应 SQL 是`SELECT id,sex,name`

③ 查询按 name 降序、id 默认顺序 排序的 User 数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应 SQL 是`ORDER BY name DESC,id`

④ 查询按 userId 分组的 Moment 数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应 SQL 是`GROUP BY userId,id`

⑤ 查询 按 userId 分组、id 最大值>=100 的 Moment 数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应 SQL 是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应 SQL 是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应 SQL 是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应 SQL 是`EXPLAIN`

⑫ 统计最近一周偶数 userId 的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应 SQL 是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 把用户的标签设置为空
["@null":"tag"](http://apijson.cn/api/?type=JSON&url=http://apijson.cn:8080/put/User&json={%22id%22:82001,%22@null%22:%22tag%22,%22@explain%22:true})

⑭ 从pictureList 获取第 0 张图片:
["@position":0, // 自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":"Table",后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":1,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":true,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) +
diff --git a/Document-English.md b/Document-English.md deleted file mode 100644 index c9a4f2948..000000000 --- a/Document-English.md +++ /dev/null @@ -1,336 +0,0 @@ -### Examples: - -#### Get a User -Request: -
{
-  "User":{
-  }
-}
-
- -[Click here to test](http://apijson.cn:8080/get/{"User":{}}) - -Response: -
{
-  "User":{
-    "id":38710,
-    "sex":0,
-    "name":"TommyLemon",
-    "certified":true,
-    "tag":"Android&Java",
-    "phone":13000038710,
-    "head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
-    "date":1485948110000,
-    "pictureList":[
-      "http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
-      "http://common.cnblogs.com/images/icon_weibo_24.png"
-    ]
-  },
-  "code":200,
-  "msg":"success"
-}
-
- -
- -

- [GIF] APIJSON single objects: simple queries, statistics, groups, orders, aggregations, comparisons, filters, aliases, etc. -

- -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_single.gif) - -#### Get an array of Users - -Request: -
{
-  "[]":{
-    "count":3, //just get 3 results
-    "User":{
-      "@column":"id,name" //just get ids and names
-    }
-  }
-}
-
- -[Click here to test](http://apijson.cn:8080/get/{"[]":{"count":3,"User":{"@column":"id,name"}}}) - -Response: -
{
-  "[]":[
-    {
-      "User":{
-        "id":38710,
-        "name":"TommyLemon"
-      }
-    },
-    {
-      "User":{
-        "id":70793,
-        "name":"Strong"
-      }
-    },
-    {
-      "User":{
-        "id":82001,
-        "name":"Android"
-      }
-    }
-  ],
-  "code":200,
-  "msg":"success"
-}
-
- -
- -

- [GIF] APIJSON single arrays: simple queries, statistics, groups, orders, aggregations, paginations, searches, regexps, combinations, etc. -

- -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_array.gif) - - -#### Get a Moment and its publisher -Request: -
{
-  "Moment":{
-  },
-  "User":{
-    "id@":"Moment/userId"  //User.id = Moment.userId
-  }
-}
-
- -[Click here to test](http://apijson.cn:8080/get/{"Moment":{},"User":{"id@":"Moment%252FuserId"}}) - -Response: -
{
-  "Moment":{
-    "id":12,
-    "userId":70793,
-    "date":"2017-02-08 16:06:11.0",
-    "content":"1111534034"
-  },
-  "User":{
-    "id":70793,
-    "sex":0,
-    "name":"Strong",
-    "tag":"djdj",
-    "head":"http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000",
-    "contactIdList":[
-      38710,
-      82002
-    ],
-    "date":"2017-02-01 19:21:50.0"
-  },
-  "code":200,
-  "msg":"success"
-}
-
- -
- -#### Get a Moment list like Twitter tweets -Request: -
{
-  "[]":{                             //get an array
-    "page":0,                        //pagination
-    "count":2,
-    "Moment":{                       //get a Moment
-      "content$":"%a%"               //filter condition: content contains 'a'
-    },
-    "User":{
-      "id@":"/Moment/userId",        //User.id = Moment.userId, short reference path,starts from grandparents path
-      "@column":"id,name,head"       //get specified keys with the written order 
-    },
-    "Comment[]":{                    //get a Comment array, and unwrap Comment object
-      "count":2,
-      "Comment":{
-        "momentId@":"[]/Moment/id"   //Comment.momentId = Moment.id, full reference path
-      }
-    }
-  }
-}
-
- -[Click here to test](http://apijson.cn:8080/get/{"[]":{"page":0,"count":2,"Moment":{"content$":"%2525a%2525"},"User":{"id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment[]":{"count":2,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) - -Response: -
{
-  "[]":[
-    {
-      "Moment":{
-        "id":15,
-        "userId":70793,
-        "date":1486541171000,
-        "content":"APIJSON is a JSON Transmission Protocol…",
-        "praiseUserIdList":[
-          82055,
-          82002,
-          82001
-        ],
-        "pictureList":[
-          "http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
-          "http://common.cnblogs.com/images/icon_weibo_24.png"
-        ]
-      },
-      "User":{
-        "id":70793,
-        "name":"Strong",
-        "head":"http://static.oschina.net/uploads/user/585/1170143_50.jpg?t=1390226446000"
-      },
-      "Comment[]":[
-        {
-          "id":176,
-          "toId":166,
-          "userId":38710,
-          "momentId":15,
-          "date":1490444883000,
-          "content":"thank you"
-        },
-        {
-          "id":1490863469638,
-          "toId":0,
-          "userId":82002,
-          "momentId":15,
-          "date":1490863469000,
-          "content":"Just do it"
-        }
-      ]
-    },
-    {
-      "Moment":{
-        "id":58,
-        "userId":90814,
-        "date":1485947671000,
-        "content":"This is a Content...-435",
-        "praiseUserIdList":[
-          38710,
-          82003,
-          82005,
-          93793,
-          82006,
-          82044,
-          82001
-        ],
-        "pictureList":[
-          "http://static.oschina.net/uploads/img/201604/22172507_aMmH.jpg"
-        ]
-      },
-      "User":{
-        "id":90814,
-        "name":7,
-        "head":"http://static.oschina.net/uploads/user/51/102723_50.jpg?t=1449212504000"
-      },
-      "Comment[]":[
-        {
-          "id":13,
-          "toId":0,
-          "userId":82005,
-          "momentId":58,
-          "date":1485948050000,
-          "content":"This is a Content...-13"
-        },
-        {
-          "id":77,
-          "toId":13,
-          "userId":93793,
-          "momentId":58,
-          "date":1485948050000,
-          "content":"This is a Content...-77"
-        }
-      ]
-    }
-  ],
-  "code":200,
-  "msg":"success"
-}
-
- -

- [GIF] APIJSON query multi related tables: one to one, one to many, many to one, various conditions, etc. -

- -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) - -
- -

- [GIF] APIJSON joins: < LEFT JOIN, & INNER JOIN, etc. -

- -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_join.gif) - -
- -

- [GIF] APIJSON subqueries:@from@ FROM, key@ =, key>@ >, key{}@ IN, key}{@ EXISTS, etc. -

- -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_subquery.gif) - -
- -

- [GIF] APIJSON: a set of some features, simple to complex -

- -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif) - -
- -[Test it online](http://apijson.cn/api) - -
-
- -## API Design Rules - -### 1. Methods and API endpoints - - Methods | URL | Request | Response ------------- | ------------ | ------------ | ------------ -**GET**:
A general way to get data.
You can use dev tools to make edits in a web browser. | base_url/get/ | {
   TableName:{
     //Add contiditions here.
   }
}

Eg. To get a Moment with `id = 235`:
{
   "Moment":{
     "id":235
   }
} | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
Eg.
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "code":200,
   "msg":"success"
} -**HEAD**:
A general way to get counts.
You can use dev tools to make edits in a web browser. | base_url/head/ | {
   TableName:{
     …
   }
}
{…} are conditions.

Eg. Get the number of Moments posted by the user with `id = 38710`:
{
   "Moment":{
     "userId":38710
   }
} | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
Eg.
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} -**GETS**:
Get data with high security and confidentiality.
Eg. bank accounts, birth date. | base_url/gets/ | You need to add `"tag":tag` with the same level of `Moment:{}`. Others are the same as **GET**. | Same as **GET**. -**HEADS**:
Get counts of confidential data(eg. bank account).| base_url/heads/ | You need to add `"tag":tag` with the same level of `Moment:{}`. Others are the same as **HEAD**. | Same as **HEAD**. -**POST**:
Add new data. | base_url/post/ | {
   TableName:{
     …
   },
   "tag":tag
}
The id in {...} is generated automatically when table is built and can’t be set by the user.

Eg. A user with `id = 38710` posts a new Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "tag":"Moment"
} | {
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
Eg.
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
} -**PUT**:
Make changes to a specific item.
Only change the part sent to server. | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
You can also add multiple id as `id{}`.

Eg. Make changes to Moment's content with id= 235:
{
   "Moment":{
     "id":235,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "tag":"Moment"
} | Same as **POST**. -**DELETE**:
Delete data. | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
You can also add multiple id as `id{}`.

Or Delete contents with multiple id:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
} | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
Eg.
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} - -**Note**:
-1. TableName means the name of the table where you get data. It’ll respond with a JSON Object(the form is {....})with columns inside. -2. `"tag":tag` is needed when methods are not GET or HEAD. The tag after the colon is the key in JSON Object of making requests. Generally, it’s the name of the table you’re looking for. -3. GET, HEAD are methods for general data requests.They support versatile JSON Object structure. Other methods are used for requesting confidential data and the requesting JSON Object needs to be in the same form/order as that in the database. Otherwise, the request shall be denied. -4. GETS and GET, HEADS and HEAD return the same type of data. But the request form is a little different. -5. For HTTP, all API methods (get,gets,head,heads,post,put,delete) make requests with HTTP POST. -6. All JSON Objects here are with {...} form. You can put items or objects in it. -7. Each object in the database has a unique address. - -
- -### 2. Keyswords in URL parameters - - Functions | Key-value pairs | Examples ------------- | ------------ | ------------ - Get data in arrays | `"key[]":{}`
The part after the colon is a JSONObject. *key* is optional. When *key* is the same as the table name , the JSONObject will be in a simplified form. For example, `{Table:{Content}}` will be written as `{Content}`.| [{"User[]":{"User":{}}}](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{}}})
It is used for getting data from a user. Here, key and tablename are all "User", then
`{"User":{"id", ...}}`
will be written as
`{"id", ...}` - Get data that meets specific conditions | `"key{}":[]`
The part after the colon is a JSONArray with conditions inside.| ["id{}":[38710,82001,70793]](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":[38710,82001,70793]}}})
In SQL, this would be `id IN(38710,82001,70793)`.
It means getting data with id equals 38710,82001,70793. - Get data with comparison operation| `"key{}":"condition0,condition1..."`
Conditions can be any SQL comparision operation. Use''to include any non-number characters.| ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}})
In SQL, it'd be
`id<=80000 OR id>90000`,
which means get User array with id\<=80000 \| id>90000 - Get data that contains an element | `"key<>":Object` => `"key<>":[Object]`
*key* must be a JSONArray while *Object* cannot be JSON.| ["contactIdList<\>":38710](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"contactIdList<\>":38710}}})
In SQL, this would be
`json_contains(contactIdList,38710)`.
It means find data of the User whose contactList contains 38710. - See if it exists |`"key}{@":{`
   `"from":"Table",`
   `"Table":{ ... }`
`}`

}{ means EXISTS.
*key* is the one you want to check.
Here is a *Subquery* in it, see specifications below for more information. | ["id}{@":{
   "from":"Comment",
   "Comment":{
      "momentId":15
   }
}](http://apijson.cn:8080/get/{"User":{"id}{@":{"from":"Comment","Comment":{"momentId":15}}}})
WHERE EXISTS(SELECT * FROM Comment WHERE momentId=15) - Include functions in parameters | `"key()":"function (key0,key1...)"`
This will trigger the back-end
`function(JSONObject request, String key0, String key1...)`
to get or testify data.
Use - and + to show the order of priority: analyze key-() > analyze the current object > analyze key() > analyze child object > analyze key+()| ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}})
This will use function boolean isContain(JSONObject request, String array, String value). In this case, client will get "isPraised":true(In this case, client use function to testify if a user clicked ‘like’ button for a Moment.) - Refer a value | `"key@":"key0/key1/.../refKey"`
Use / to show path. The part before the colon is the key that wants to refer. The path after the colon starts with the parent level of the key.| ["Moment":{
   "userId":38710
},
"User":{
   "id@":"/Moment/userId"
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}})
In this example, the value of id in User refer to the *userId* in *Moment*, which means
`User.id = Moment.userId`.
After the request is sent,
`"id@":"/Moment/userId"` will be `"id":38710`. - Subquery | `"key@":{`
   `"range":"ALL",`
   `"from":"Table",`
   `"Table":{ ... }`
`}`
*range* can be ALL, ANY.
*from* means which table you want to query.
It’s very similar to how you query in SQL.
You can also use *count*, *join*, etc. | ["id@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
`WHERE id=(SELECT min(userId) FROM Comment)`. - Fuzzy matching | `"key$":"SQL search expressions"` => `"key$":["SQL search expressions"]`
Any SQL search expressions.Eg.%key%(include key), key%(start with key),%k%e%y%(include k, e, y). % means any characters. | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}})
In SQL, it's
`name LIKE '%m%'`,
meaning that get User with ‘m’ in name. - Regular Expression| `"key~":"regular expression"` => `"key~":["regular expression"]`
It can be any regular expressions.Eg. ^[0-9]+$ ,*~ not case sensitive, advanced search is applicable.| ["name~":"^[0-9]+$"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name~":"^[0-9]%252B$"}}})
In SQL, it's
`name REGEXP '^[0-9]+$'`. - Get data in a range| `"key%":"start,end"` => `"key%":["start,end"]`
The data type of start and end can only be either Boolean, Number or String. Eg. "2017-01-01,2019-01-01" ,["1,90000", "82001,100000"]. It's used for getting data from a specific time range. | ["date%":"2017-10-01,2018-10-01"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"date%2525":"2017-10-01,2018-10-01"}}})
In SQL, it's
`date BETWEEN '2017-10-01' AND '2018-10-01'`,
meaning to get User data that registered between 2017-10-01 and 2018-10-01. - Make an alias | `"name:alias"`
this changes name to alias in returning results. It’s applicable to column, tableName, SQL Functions, etc. but only in GET, HEAD requests. | ["@column":"toId:parentId"](http://apijson.cn:8080/get/{"Comment":{"@column":"id,toId:parentId","id":51}})
In SQL, it's
`toId AS parentId`.
It'll return `parentId` instead of `toId`. - Add / expand an item | `"key+":Object`
The type of Object is decided by *key*. Types can be Number, String, JSONArray. Froms are 82001,"apijson",["url0","url1"] respectively. It’s only applicable to PUT request.| "praiseUserIdList+":[82001]. In SQL, it's
`json_insert(praiseUserIdList,82001)`.
Add an *id* that praised the Moment. - Delete / decrease an item | `"Key-":Object`
It’s the contrary of "key+" | "balance-":100.00. In SQL, it's
`balance = balance - 100.00`,
meaning there's 100 less in balance. - Operations | &, \|, !
They're used in logic operations. It’s the same as AND, OR, NOT in SQL respectively.
By default, for the same key, it’s ‘\|’ (OR)operation among conditions; for different keys, the default operation among conditions is ‘&’(AND).
| ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}})
In SQL, it's
`id>80000 AND id<=90000`,
meaning *id* needs to be id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}})
It's the same as "id{}":">90000,<=80000".
In SQL, it's
`id>80000 OR id<=90000`,
meaning that *id* needs to be id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}})
In SQL, it's
`id NOT IN(82001,38710)`,
meaning id needs to be ! (id=82001 \| id=38710). - Keywords in an Array: It can be self-defined. | As for `"key":Object`, *key* is the keyword of *{}* in *"[]":{}*. The type of *Object* is up to *key*.

① `"count":Integer` It's used to count the number. The default largest number is 100.

② `"page":Integer` It’s used for getting data from which page, starting from 0. The default largest number is 100. It’s usually used with COUNT.

③ `"query":Integer` Get the number of items that match conditions
When to get the object, the integer should be 0; when to get the total number, it’s 1; when both above, it’s 2.
You can get the total number with keyword total. It can be referred to other values.
Eg.
`"total@":"/[]/total"`
Put it as the same level of query.
*Query* and *total* are used in GET requests just for convenience. Generally, HEAD request is for getting numbers like the total number.

④ `"join":"&/Table0,Join tables:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
Where @ APP JOIN is in application layer.It’ll get all the keys in tables that refKeys in result tables are referred to, like refKeys:[value0, value1….]. Then, as the results get data according to `key=$refKey` a number of times (COUNT), it uses key `IN($refKeys)` to put these counts together in just one SQL query, in order to improve the performance.
Other JOIN functions are the same as those in SQL.
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
will return
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ `"otherKey":Object` Self-defined keyword other than those that already in the system. It also returns with self-defined keywords.| ① Get User arrays with maximum of 5:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② Look into User arrays on page 3. Show 5 of them each page.
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ Get User Arrays and count the total number of Users:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal"})
Questions like total page numbers or if there's next page can be solved by total,count,page functions,
Total page number:
`int totalPage = Math.ceil(total / count)`
If this is the last page:
`boolean hasNextPage = total > count*page`
If this is the first page:
`boolean isFirstPage = page <= 0`
If it's the last page:
`boolean isLastPage = total <= count*page`
...

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join": "&/User,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@": "/Moment/userId"
   },
   "Comment":{
     "momentId@": "/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser,\<%252FComment","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ Add the current user to every level:
["User":{},
"[]":{
   "name@":"User/name", //self-defined keyword
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - Keywords in Objects: It can be self-defined. | `"@key":Object` @key is the keyword of {} in Table:{}. The type of Object is decided by @key

① `"@combine":"&key0,&key1,\|key2,key3,`
`!key4,!key5,&key6,key7..."`
First, it’ll group data with same operators. Within one group, it operates from left to right. Then it’ll follow the order of & \| ! to do the operation. Different groups are connected with &. So the expression above will be :
(key0 & key1 & key6 & other key) & (key2 \| key3 \| key7) & !(key4 \| key5)
\| is optional.

② `"@column":"column;function(arg)..."` Return with specific columns.

③ `"@order":"column0+,column1-..."` Decide the order of returning results:

④ `"@group":"column0,column1..."` How to group data. If @column has declared Table id, this id need to be included in @group. In other situations, at least one of the following needs to be done:
1.Group id is declared in @column
2.Primary Key of the table is declared in @group.

⑤ `@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..."` Add conditions on return results with @having. Usually working with@group, it’s declared in @column.

⑥ `"@schema":"sys"` Can be set as default setting.

⑦ `"@database":"POSTGRESQL"` Get data from a different database.Can be set as default setting.

⑧ `"@role":"OWNER"` Get information of the user, including
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
Can be set as default setting.
You can self-define a new role or rewrite a role. Use`Verifier.verify` etc. to self-define validation methods.

⑨ `"@explain":true` Profiling. Can be set as default setting.

⑩ `"@otherKey":Object` Self-define keyword | ① Search *Users* that *name* or *tag* contains the letter "a":
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})

② Only search column id,sex,name and return with the same order:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})

③ Search Users that have descending order of name and default order of id:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})

④ Search Moment grouped with userId:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})

⑤ Search Moments that id equals or less than 100 and group with userId:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
You can also define the name of the returned function:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})

⑥ Check Users table in sys:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})

⑦ Check Users table in PostgreSQL:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL"}})

⑧ Check the current user's activity:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑨ Turn on profiling:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑩ Get the No.0 picture from pictureList:
["@position":0, //self-defined keyword
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) - global keyword. | It is a keyword inside the outermost object {}. Among them, @database, @schema, @datasource, @role, and @explain are basically the same as object keywords, see the above description, the difference is that the global keywords will be automatically inserted in each table object as the default value.

① "tag": String, the following tag is the identifier of the JSON structure matching the request in non-GET or HEAD requests, generally it is the name of the Table to be queried or the array Table[] or Table:[] corresponding to the name, determined by the backend specified in the Request table.

② "version": Integer, the interface version. If the version is not passed, null or <=0, the highest version will be used. If other valid values are passed, the lowest version closest to it will be used, which is specified in the backend Request table.

③ "format": Boolean, formatted to return the key of the Response JSON, generally converting TableName to tableName, TableName[] to tableNameList, Table:alias to alias, TableName-key[] to tableNameKeyList and other camelcase formats. | ① Check private information::
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② Use the version 1 interface to check private information::
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ Format Moments interface to return in JSON key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) - -
- diff --git a/Document.md b/Document.md index a97f7b823..7574e569a 100644 --- a/Document.md +++ b/Document.md @@ -1,48 +1,26 @@ -[English](https://github.com/Tencent/APIJSON/blob/master/Document-English.md) -
- -# APIJSON 通用文档 -本文是通用文档,只和 APIJSON 协议有关,和 C#, Go, Java, JavaScript, PHP, Python, TypeScript 等开发语言无关。
-具体开发语言相关的 配置、运行、部署 等文档见各个相关项目的文档,可以在首页点击对应语言的入口来查看。
-https://github.com/Tencent/APIJSON -![image](https://user-images.githubusercontent.com/5738175/134520081-a63d3817-321c-4e7b-9e03-73c6827a19c1.png) - - -后端开发者可以先看 [图文入门教程1](http://apijson.cn/doc/zh/) 或 [图文入门教程2](https://hanxu2018.github.io/APIJSON-DOC/) (和本文档有出入的点以本文档为准。例如正则匹配 key? 已废弃,用 key~ 替代;例如 "@column":"store_id,sum(amt):totAmt" 中逗号 , 有误,应该用分号 ; 隔开 SQL 函数,改为 "@column":"store_id;sum(amt):totAmt") +#### A better online document is available at https://apijsondocs.readthedocs.io -* ### [1.示例](#1) -* ### [2.对比传统方式](#2) -* [2.1 开发流程](#2.1) -* [2.2 前端请求](#2.2) -* [2.3 后端操作](#2.3) -* [2.4 前端解析](#2.4) -* [2.5 对应不同需求的请求](#2.5) -* [2.6 对应不同请求的结果](#2.6) -* ### [3.设计规范](#3) -* [3.1 操作方法](#3.1) -* [3.2 功能符](#3.2) +### Examples: - -##

1.示例

- -#### 获取用户 -请求: +#### Get a User +Request:
{
   "User":{
-    "id":38710
   }
 }
 
-[点击这里测试](http://apijson.cn:8080/get/{"User":{"id":38710}}) +[Click here to test](http://apijson.cn:8080/get/{"User":{}}) -返回: +Response:
{
   "User":{
     "id":38710,
     "sex":0,
     "name":"TommyLemon",
+    "certified":true,
     "tag":"Android&Java",
+    "phone":13000038710,
     "head":"http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
     "date":1485948110000,
     "pictureList":[
@@ -55,29 +33,30 @@ https://github.com/Tencent/APIJSON
 }
 
+
+

- [GIF] APIJSON 各种单表对象查询:简单查询、统计、分组、排序、聚合、比较、筛选字段、字段别名 等 + [GIF] APIJSON single objects: simple queries, statistics, groups, orders, aggregations, comparisons, filters, aliases, etc.

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_single.gif) - -
-#### 获取用户列表 -请求: +#### Get an array of Users + +Request:
{
   "[]":{
-    "count":3, //只要3个
+    "count":3, //just get 3 results
     "User":{
-      "@column":"id,name" //只要id,name这两个字段
+      "@column":"id,name" //just get ids and names
     }
   }
 }
 
-[点击这里测试](http://apijson.cn:8080/get/{"[]":{"count":3,"User":{"@column":"id,name"}}}) +[Click here to test](http://apijson.cn:8080/get/{"[]":{"count":3,"User":{"@column":"id,name"}}}) -返回: +Response:
{
   "[]":[
     {
@@ -104,16 +83,17 @@ https://github.com/Tencent/APIJSON
 }
 
+
+

- [GIF] APIJSON 各种单表数组查询:简单查询、统计、分组、排序、聚合、分页、比较、搜索、正则、条件组合 等 + [GIF] APIJSON single arrays: simple queries, statistics, groups, orders, aggregations, paginations, searches, regexps, combinations, etc.

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_array.gif) -
-#### 获取动态及发布者用户 -请求: +#### Get a Moment and its publisher +Request:
{
   "Moment":{
   },
@@ -123,9 +103,9 @@ https://github.com/Tencent/APIJSON
 }
 
-[点击这里测试](http://apijson.cn:8080/get/{"Moment":{},"User":{"id@":"Moment%252FuserId"}}) +[Click here to test](http://apijson.cn:8080/get/{"Moment":{},"User":{"id@":"Moment%252FuserId"}}) -返回: +Response:
{
   "Moment":{
     "id":12,
@@ -152,32 +132,32 @@ https://github.com/Tencent/APIJSON
   
 
-#### 获取类似微信朋友圈的动态列表 -请求: +#### Get a Moment list like Twitter tweets +Request:
{
-  "[]":{                             //请求一个数组
-    "page":0,                        //数组条件
+  "[]":{                             //get an array
+    "page":0,                        //pagination
     "count":2,
-    "Moment":{                       //请求一个名为Moment的对象
-      "content$":"%a%"               //对象条件,搜索content中包含a的动态
+    "Moment":{                       //get a Moment
+      "content$":"%a%"               //filter condition: content contains 'a'
     },
     "User":{
-      "id@":"/Moment/userId",  //User.id = Moment.userId  缺省引用赋值路径,从所处容器的父容器路径开始
-      "@column":"id,name,head"       //指定返回字段
+      "id@":"/Moment/userId",        //User.id = Moment.userId, short reference path,starts from grandparents path
+      "@column":"id,name,head"       //get specified keys with the written order 
     },
-    "Comment[]":{                    //请求一个名为Comment的数组,并去除Comment包装
+    "Comment[]":{                    //get a Comment array, and unwrap Comment object
       "count":2,
       "Comment":{
-        "momentId@":"[]/Moment/id"   //Comment.momentId = Moment.id  完整引用赋值路径
+        "momentId@":"[]/Moment/id"   //Comment.momentId = Moment.id, full reference path
       }
     }
   }
 }
 
-[点击这里测试](http://apijson.cn:8080/get/{"[]":{"page":0,"count":2,"Moment":{"content$":"%2525a%2525"},"User":{"id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment[]":{"count":2,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) +[Click here to test](http://apijson.cn:8080/get/{"[]":{"page":0,"count":2,"Moment":{"content$":"%2525a%2525"},"User":{"id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment[]":{"count":2,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) -返回: +Response:
{
   "[]":[
     {
@@ -270,7 +250,7 @@ https://github.com/Tencent/APIJSON
 

- [GIF] APIJSON 各种多表关联查询:一对一、一对多、多对一、各种条件 等 + [GIF] APIJSON query multi related tables: one to one, one to many, many to one, various conditions, etc.

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) @@ -278,7 +258,7 @@ https://github.com/Tencent/APIJSON

- [GIF] APIJSON 各种 JOIN:< LEFT JOIN, & INNER JOIN 等 + [GIF] APIJSON joins: < LEFT JOIN, & INNER JOIN, etc.

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_join.gif) @@ -286,7 +266,7 @@ https://github.com/Tencent/APIJSON

- [GIF] APIJSON 各种子查询:@from@ FROM, key@ =, key>@ >, key{}@ IN, key}{@ EXISTS 等 + [GIF] APIJSON subqueries:@from@ FROM, key@ =, key>@ >, key{}@ IN, key}{@ EXISTS, etc.

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_subquery.gif) @@ -294,133 +274,65 @@ https://github.com/Tencent/APIJSON

- [GIF] APIJSON 部分功能演示集合,由浅入深、由简单到复杂 + [GIF] APIJSON: a set of some features, simple to complex

![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif)
-[在线测试](http://apijson.cn/api) +[Test it online](http://apijson.cn/api)

- -##

2.对比传统RESTful方式

-###

2.1 开发流程

- 开发流程 | 传统方式 | APIJSON --------- | ------------ | ------------ - 接口传输 | 等后端编辑接口,然后更新文档,前端再按照文档编辑请求和解析代码 | 前端按照自己的需求编辑请求和解析代码。
没有接口,更不需要文档!前端再也不用和后端沟通接口或文档问题了! - 兼容旧版 | 后端增加新接口,用v2表示第2版接口,然后更新文档 | 什么都不用做! - -
- -###

2.2 前端请求

- 前端请求 | 传统方式 | APIJSON --------- | ------------ | ------------ - 要求 | 前端按照文档在对应URL后面拼接键值对 | 前端按照自己的需求在固定URL后拼接JSON - URL | 不同的请求对应不同的URL,基本上有多少个不同的请求就得有多少个接口URL | 相同的操作方法(增删改查)都用同一个URL,
大部分请求都用7个通用接口URL的其中一个 - 键值对 | key=value | key:value - 结构 | 同一个URL内table_name只能有一个

base_url/get/table_name?
key0=value0&key1=value1... | 同一个URL后TableName可传任意数量个

base_url/get/
{
   TableName0:{
     key0:value0,
     key1:value1,
     ...
   },
   TableName1:{
     ...
   }
   ...
} - -
- -###

2.3 后端操作

- 后端操作 | 传统方式 | APIJSON --------- | ------------ | ------------ - 解析和返回 | 取出键值对,把键值对作为条件用预设的的方式去查询数据库,最后封装JSON并返回给前端 | 把Parser#parse方法的返回值返回给前端就行 - 返回JSON结构的设定方式 | 由后端设定,前端不能修改 | 由前端设定,后端不能修改 - -
- -###

2.4 前端解析

- 前端解析 | 传统方式 | APIJSON --------- | ------------ | ------------ - 查看方式 | 查文档或问后端,或等请求成功后看日志 | 看请求就行,所求即所得,不用查、不用问、不用等。也可以等请求成功后看日志 - 解析方法 | 用JSON解析器来解析JSONObject | 可以用JSONResponse解析JSONObject,或使用传统方式 - -
- -###

2.5 前端对应不同需求的请求

- 前端的请求 | 传统方式 | APIJSON --------- | ------------ | ------------ - User | base_url/get/user?id=38710 | [base_url/get/
{
   "User":{
     "id":38710
   }
}](http://apijson.cn:8080/get/{"User":{"id":38710}}) - Moment和对应的User | 分两次请求
Moment:
base_url/get/moment?userId=38710

User:
base_url/get/user?id=38710 | [base_url/get/
{
   "Moment":{
     "userId":38710
   },
   "User":{
     "id":38710
   }
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id":38710}}) - User列表 | base_url/get/user/list?
page=0&count=3&sex=0 | [base_url/get/
{
   "User[]":{
     "page":0,
     "count":3,
     "User":{
       "sex":0
     }
   }
}](http://apijson.cn:8080/get/{"User[]":{"page":0,"count":3,"User":{"sex":0}}}) - Moment列表,
每个Moment包括
1.发布者User
2.前3条Comment | Moment里必须有
1.User对象
2.Comment数组

base_url/get/moment/list?
page=0&count=3&commentCount=3 | [base_url/get/
{
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) - User发布的Moment列表,
每个Moment包括
1.发布者User
2.前3条Comment | 1.Moment里必须有User对象和Comment数组
2.字段名必须查接口文档,例如评论数量字段名可能是
commentCount,comment_count或者简写cmt_count等各种奇葩写法...

base_url/get/moment/list?
page=0&count=3
&commentCount=3&userId=38710 | 有以下几种方式:

① 把以上请求里的
"Moment":{}, "User":{"id@":"/Moment/userId"}
改为
["Moment":{"userId":38710}, "User":{"id":38710}](http://apijson.cn:8080/get/{"[]":{"page":0,"count":3,"Moment":{"userId":38710},"User":{"id":38710},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})

② 或把User放在上面的最外层省去重复的User
[base_url/get/
{
   "User":{
     "id":38710
   },
   "[]":{
     "page":0,
     "count":3,
     "Moment":{
       "userId":38710
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"User":{"id":38710},"[]":{"page":0,"count":3,"Moment":{"userId":38710},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}})

③ 如果User之前已经获取到了,还可以不传User来节省请求和返回数据的流量并提升速度
[base_url/get/
{
   "[]":{
     "page":0,
     "count":3,
     "Moment":{
       "userId":38710
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"[]":{"page":0,"count":3,"Moment":{"userId":38710},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) - -
- -###

2.6 后端对应不同请求的返回结果

- 后端的返回结果 | 传统方式 | APIJSON --------- | ------------ | ------------ - User | {
   "data":{
     "id":38710,
     "name":"xxx",
     ...
   },
   "code":200,
   "msg":"success"
} | {
   "User":{
     "id":38710,
     "name":"xxx",
     ...
   },
   "code":200,
   "msg":"success"
} - Moment和对应的User | 分别返回两次请求的结果,获取到Moment后取出userId作为User的id条件去查询User

Moment:
{
   "data":{
     "id":235,
     "content":"xxx",
     ...
   },
   "code":200,
   "msg":"success"
}

User:
{
   "data":{
     "id":38710,
     "name":"xxx",
     ...
   },
   "code":200,
   "msg":"success"
} | 一次性返回,没有传统方式导致的 长时间等待结果、两次结果间关联、线程多次切换 等问题

{
   "Moment":{
     "id":235,
     "content":"xxx",
     ...
   },
   "User":{
     "id":38710,
     "name":"xxx",
     ...
   },
   "code":200,
   "msg":"success"
} - User列表 | {
   "data":[
     {
       "id":38710,
       "name":"xxx",
       ...
     },
     {
       "id":82001,
       ...
     },
     ...
   ],
   "code":200,
   "msg":"success"
} | {
   "User[]":[
     {
       "id":38710,
       "name":"xxx",
       ...
     },
     {
       "id":82001,
       ...
     },
     ...
   ],
   "code":200,
   "msg":"success"
} - Moment列表,每个Moment包括发布者User和前3条Comment | Moment里必须有
1.User对象
2.Comment数组

{
   "data":[
     {
       "id":235,
       "content":"xxx",
       ...,
       "User":{
         ...
       },
       "Comment":[
         ...
       ]
     },
     {
       "id":301,
       "content":"xxx",
       ...,
       "User":{
         ...
       },
       ...
     },
     ...
   ],
   "code":200,
   "msg":"success"
} | 1.高灵活,可任意组合
2.低耦合,逻辑很清晰

{
   "[]":[
     {
       "Moment":{
         "id":235,
         "content":"xxx",
         ...
       },
       "User":{
         ...
       },
       "Comment[]":[
         ...
       ]
     },
     {
       "Moment":{
         "id":301,
         "content":"xxx",
         ...
       },
       "User":{
         ...
       },
       ...
     },
     ...
   ],
   "code":200,
   "msg":"success"
} - User发布的Moment列表,每个Moment包括发布者User和前3条Comment | 1.大量重复User,浪费流量和服务器性能
2.优化很繁琐,需要后端扩展接口、写好文档,前端/前端再配合优化

{
   "data":[
     {
       "id":235,
       "content":"xxx",
       ...,
       "User":{
         "id":38710,
         "name":"Tommy"
         ...
       },
       "Comment":[
         ...
       ]
       ...
     },
     {
       "id":470,
       "content":"xxx",
       ...,
       "User":{
         "id":38710,
         "name":"Tommy"
         ...
       },
       "Comment":[
         ...
       ]
       ...
     },
     {
       "id":511,
       "content":"xxx",
       ...,
       "User":{
         "id":38710,
         "name":"Tommy"
         ...
       },
       "Comment":[
         ...
       ]
       ...
     },
     {
       "id":595,
       "content":"xxx",
       ...,
       "User":{
         "id":38710,
         "name":"Tommy"
         ...
       },
       "Comment":[
         ...
       ]
       ...
     },
     ...
   ],
   "code":200,
   "msg":"success"
} | 以上不同请求方式的结果:

① 常规请求
{
   "[]":[
     {
       "Moment":{
         "id":235,
         "content":"xxx",
         ...
       },
       "User":{
         "id":38710,
         "name":"Tommy"
         ...
       },
       "Comment[]":[
         ...
       ]
     },
     ...
   ],
   "code":200,
   "msg":"success"
}

② 省去重复的User
{
   "User":{
     "id":38710,
     "name":"Tommy",
     ...
   },
   "[]":[
     {
       "Moment":{
         "id":235,
         "content":"xxx",
         ...
       },
       "Comment[]":[
         ...
       ]
     },
     ...
   ],
   "code":200,
   "msg":"success"
}

③ 不查询已获取到的User
{
   "[]":[
     {
       "Moment":{
         "id":235,
         "content":"xxx",
         ...
       },
       "Comment[]":[
         ...
       ]
     },
     ...
   ],
   "code":200,
   "msg":"success"
} - - -1.base_url指基地址,一般是顶级域名,其它分支url都是在base_url后扩展。如base_url:http://apijson.cn:8080/ ,对应的GET分支url:http://apijson.cn:8080/get/ 。下同。
-2.请求中的key或value任意一个为null值时,这个 key:value键值对 被视为无效。下同。
-3.请求中的 / 需要转义。JSONRequest.java已经用URLEncoder.encode转义,不需要再写;但如果是浏览器或Postman等直接输入url/request,需要把request中的所有 / 都改成 %252F 。下同。
-4.code,指返回结果中的状态码,200表示成功,其它都是错误码,值全部都是HTTP标准状态码。下同。
-5.msg,指返回结果中的状态信息,对成功结果或错误原因的详细说明。下同。
-6.code和msg总是在返回结果的同一层级成对出现。对所有请求的返回结果都会在最外层有一对总结式code和msg。下同。
-7.id等字段对应的值仅供说明,不一定是数据库里存在的,请求里用的是真实存在的值。下同。 +## API Design Rules -
-
+### 1. Methods and API endpoints -##

3.设计规范

+ Methods | URL | Request | Response +------------ | ------------ | ------------ | ------------ +**GET**:
A general way to get data.
You can use dev tools to make edits in a web browser. | base_url/get/ | {
   TableName:{
     //Add contiditions here.
   }
}

Eg. To get a Moment with `id = 235`:
{
   "Moment":{
     "id":235
   }
} | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
Eg.
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "code":200,
   "msg":"success"
} +**HEAD**:
A general way to get counts.
You can use dev tools to make edits in a web browser. | base_url/head/ | {
   TableName:{
     …
   }
}
{…} are conditions.

Eg. Get the number of Moments posted by the user with `id = 38710`:
{
   "Moment":{
     "userId":38710
   }
} | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
Eg.
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} +**GETS**:
Get data with high security and confidentiality.
Eg. bank accounts, birth date. | base_url/gets/ | You need to add `"tag":tag` with the same level of `Moment:{}`. Others are the same as **GET**. | Same as **GET**. +**HEADS**:
Get counts of confidential data(eg. bank account).| base_url/heads/ | You need to add `"tag":tag` with the same level of `Moment:{}`. Others are the same as **HEAD**. | Same as **HEAD**. +**POST**:
Add new data. | base_url/post/ | {
   TableName:{
     …
   },
   "tag":tag
}
The id in {...} is generated automatically when table is built and can’t be set by the user.

Eg. A user with `id = 38710` posts a new Moment:
{
   "Moment":{
     "userId":38710,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "tag":"Moment"
} | {
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
Eg.
{
   "Moment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
} +**PUT**:
Make changes to a specific item.
Only change the part sent to server. | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
You can also add multiple id as `id{}`.

Eg. Make changes to Moment's content with id= 235:
{
   "Moment":{
     "id":235,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "tag":"Moment"
} | Same as **POST**. +**DELETE**:
Delete data. | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
You can also add multiple id as `id{}`.

Or Delete contents with multiple id:
{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
} | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
Eg.
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} + +**Note**:
+1. TableName means the name of the table where you get data. It’ll respond with a JSON Object(the form is {....})with columns inside. +2. `"tag":tag` is needed when methods are not GET or HEAD. The tag after the colon is the key in JSON Object of making requests. Generally, it’s the name of the table you’re looking for. +3. GET, HEAD are methods for general data requests.They support versatile JSON Object structure. Other methods are used for requesting confidential data and the requesting JSON Object needs to be in the same form/order as that in the database. Otherwise, the request shall be denied. +4. GETS and GET, HEADS and HEAD return the same type of data. But the request form is a little different. +5. For HTTP, all API methods (get,gets,head,heads,post,put,delete) make requests with HTTP POST. +6. All JSON Objects here are with {...} form. You can put items or objects in it. +7. Each object in the database has a unique address. -###

3.1 操作方法

+
- 方法及说明 | URL | Request | Response ------------- | ------------ | ------------ | ------------ -GET:
普通获取数据,
可用浏览器调试 | base_url/get/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 235 的 Moment:
[{
   "Moment":{
     "id":235
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fget&type=JSON&json={"Moment"%3A{"id"%3A235}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT * FROM Moment WHERE id=235 LIMIT 1` | {
   TableName:{
     ...
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "id":235,
     "userId":38710,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "code":200,
   "msg":"success"
} -HEAD:
普通获取数量,
可用浏览器调试 | base_url/head/ | {
   TableName:{
     …
   }
}
{…}内为限制条件

例如获取一个 id = 38710 的 User 所发布的 Moment 总数:
[{
   "Moment":{
     "userId":38710
   }
}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fhead&type=JSON&json={"Moment"%3A{"userId"%3A38710}})
后端校验通过后自动解析为 SQL 并执行:
`SELECT count(*) FROM Moment WHERE userId=38710 LIMIT 1` | {
   TableName:{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Moment":{
     "code":200,
     "msg":"success",
     "count":10
   },
   "code":200,
   "msg":"success"
} -GETS:
安全/私密获取数据,
用于获取钱包等
对安全性要求高的数据 | base_url/gets/ | 最外层加一个 "tag":tag,例如 ["tag":"Privacy"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}}),其它同GET | 同GET -HEADS:
安全/私密获取数量,
用于获取银行卡数量等
对安全性要求高的数据总数 | base_url/heads/ | 最外层加一个 "tag":tag,例如 ["tag":"Verify"](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fheads&type=JSON&json={"tag"%3A"Verify","Verify"%3A{"phone"%3A13000082001}}),其它同HEAD | 同HEAD -POST:
新增数据 | base_url/post/ | 单个:
{
   TableName:{
     …
   },
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 38710 发布一个新 Comment:
[{
   "Comment":{
     "momentId":12,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON%20is%20the%20Real-Time%20coding-free,%20powerful%20and%20secure%20ORM."},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON is the real-time coding-free, powerful and secure ORM')`

批量:
{
   TableName\[]:\[{
       …
     }, {
       …
     }
     …
   ],
   "tag":tag
}
{…}中id由后端生成,不能传

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
     }, {
     "momentId":15,
     "content":"APIJSON is a JSON transmision protocol."
   }],
   "tag":"Comment:[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment[]":[{"momentId":12,"content":"APIJSON%20is%20the%20Real-Time%20coding-free,%20powerful%20and%20secure%20ORM."},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON is the real-time coding-free, powerful and secure ORM');`

`INSERT INTO Comment(userId,momentId,content) VALUES(82001,15,'APIJSON is a JSON transmision protocol.');` | 单个:
{
   TableName:{
     "code":200,
     "msg":"success",
     "id":38710
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "id":120
   },
   "code":200,
   "msg":"success"
}

批量:
{
   TableName:{
     "code":200,
     "msg":"success",
     "count":5,
     "id[]":[1, 2, 3, 4, 5]
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
     "code":200,
     "msg":"success",
     "count":2,
     "id[]":\[1, 2]
   },
   "code":200,
   "msg":"success"
} -PUT:
修改数据,
只修改所传的字段 | base_url/put/ | {
   TableName:{
     "id":id,
     …
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个

例如当前登录用户 82001 修改 id = 235 的 Moment 的 content:
[{
   "Moment":{
     "id":235,
     "content":"APIJSON is the real-time coding-free, powerful and secure ORM"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON%20is%20the%20Real-Time%20coding-free,%20powerful%20and%20secure%20ORM."},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON is the real-time coding-free, powerful and secure ORM' WHERE id=235 AND userId=82001 LIMIT 1`

批量除了 id{}:\[] 也可类似批量 POST,只是每个 {...} 里面都必须有 id。
"tag":"Comment[]" 对应对象 "Comment":{"id{}":[1,2,3]},表示指定记录全部统一设置;
"tag":"Comment:[]" 多了冒号,对应数组 "Comment[]":[{"id":1},{"id":2},{"id":3}],表示每项单独设置 | 同POST -DELETE:
删除数据 | base_url/delete/ | {
   TableName:{
     "id":id
   },
   "tag":tag
}
{…} 中 id 或 id{} 至少传一个,一般只传 id 或 id{}

例如当前登录用户 82001 批量删除 id = 100,110,120 的 Comment:
[{
   "Comment":{
     "id{}":[100,110,120]
   },
   "tag":"Comment[]"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fdelete&type=JSON&json={"Comment":{"id{}":[100,110,120]},"tag":"Comment[]"})
后端校验通过后自动解析为 SQL 并执行:
`DELETE FROM Comment WHERE id IN(100,110,120) AND userId=82001 LIMIT 3` | {
   TableName:{
     "code":200,
     "msg":"success",
     "id[]":[100,110,120]
      "count":3
   },
   "code":200,
   "msg":"success"
}
例如
{
   "Comment":{
      "code":200,
      "msg":"success",
      "id[]":[100,110,120],
      "count":3
   },
   "code":200,
   "msg":"success"
} -以上接口的简单形式:
base_url/{method}/{tag} | GET: 普通获取数据
base_url/get/{tag}

HEAD: 普通获取数量
base_url/head/{tag}

GETS: 安全/私密获取数据
base_url/gets/{tag}

HEADS: 安全/私密获取数量
base_url/heads/{tag}

POST: 新增数据
base_url/post/{tag}

PUT: 修改数据 base_url/put/{tag}

DELETE: 删除数据
base_url/delete/{tag} | 例如安全/私密获取一个 id = 82001 的 Privacy:
[base_url/gets/Privacy/
{"id":82001}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets%2FPrivacy&type=JSON&json={"id"%3A82001})
相当于
[base_url/gets/
{"tag":"Privacy", "Privacy":{"id":82001}}](http://apijson.cn/api/?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={"tag"%3A"Privacy","Privacy"%3A{"id"%3A82001}})

例如批量修改 id = 114, 124 的 Comment 的 content:
[base_url/put/Comemnt[]/
{
   "id{}":[114,124],
   "content":"test multi put"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput%2FComment[]&type=JSON&json={"id{}"%3A[114,124],"content"%3A"test%20multi%20put"})
相当于
[base_url/put/
{
   "tag":"Comment[]",
   "Comment":{
     "id{}":[114,124],
     "content":"test multi put"
   }
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"tag"%3A"Comment[]","Comment"%3A{"id{}"%3A[114,124],"content"%3A"test%20multi%20put"}}) | 同以上对应的方法 - -1.TableName指要查询的数据库表Table的名称字符串。第一个字符为大写字母,剩下的字符要符合英语字母、数字、下划线中的任何一种。对应的值的类型为JSONObject,结构是 {...},里面放的是Table的字段(列名)。下同。
-2."tag":tag 后面的tag是非GET、HEAD请求中匹配请求的JSON结构的标识,一般是要查询的Table的名称,由后端Request表中指定。下同。
-3.GET、HEAD请求是开放请求,可任意组合任意嵌套。其它请求为受限制的安全/私密请求,对应的 方法(method), 标识(tag), 版本(version), 结构(structure) 都必须和 后端Request表中所指定的 一一对应,否则请求将不被通过。version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本。下同。
-4.GETS与GET、HEADS与HEAD分别为同一类型的操作方法,请求稍有不同但返回结果相同。下同。
-5.在HTTP通信中,自动化接口(get,gets,head,heads,post,put,delete) 全用HTTP POST请求。下同。
-6.所有JSONObject都视为容器(或者文件夹),结构为 {...} ,里面可以放普通对象或子容器。下同。
-7.每个对象都有一个唯一的路径(或者叫地址),假设对象名为refKey,则用 key0/key1/.../refKey 表示。下同。 - -
- -###

3.2 功能符

+### 2. Keyswords in URL parameters - 功能 | 键值对格式 | 使用示例 + Functions | Key-value pairs | Examples ------------ | ------------ | ------------ - 查询数组 | "key[]":{},后面是 JSONObject,key 可省略。当 key 和里面的 Table 名相同时,Table 会被提取出来,即 {Table:{Content}} 会被转化为 {Content} | [{"User[]":{"User":{}}}](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{}}}),查询一个 User 数组。这里 key 和 Table 名都是 User,User 会被提取出来,即 {"User":{"id", ...}} 会被转化为 {"id", ...},如果要进一步提取 User 中的 id,可以把 User[] 改为 User-id[],其中 - 用来分隔路径中涉及的 key - 匹配选项范围 | "key{}":[],后面是 JSONArray,作为 key 可取的值的选项 | ["id{}":[38710,82001,70793]](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":[38710,82001,70793]}}}),对应 SQL 是`id IN(38710,82001,70793)`,查询 id 符合 38710,82001,70793 中任意一个的一个 User 数组 - 匹配条件范围 | "key{}":"条件0,条件1...",条件为 SQL 表达式字符串,可进行数字比较运算等 | ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}}),对应 SQL 是`id<=80000 OR id>90000`,查询 id 符合 id\<=80000 \| id>90000 的一个 User 数组 - 包含选项范围 | "key<\>":value => "key<\>":[value],key 对应值的类型必须为 JSONArray,value 值类型只能为 Boolean, Number, String 中的一种 | ["contactIdList<\>":38710](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"contactIdList<\>":38710}}}),对应SQL是`json_contains(contactIdList,38710)`,查询 contactIdList 包含 38710 的一个 User 数组 - 判断是否存在 | "key}{@":{
   "from":"Table",
   "Table":{ ... }
}
其中:
}{ 表示 EXISTS;
key 用来标识是哪个判断;
@ 后面是 子查询 对象,具体见下方 子查询 的说明。 | ["id}{@":{
   "from":"Comment",
   "Comment":{
      "momentId":15
   }
}](http://apijson.cn:8080/get/{"User":{"id}{@":{"from":"Comment","Comment":{"momentId":15}}}})
WHERE EXISTS(SELECT * FROM Comment WHERE momentId=15) - 远程调用函数 | "key()":"函数表达式",函数表达式为 function(key0,key1...),会调用后端对应的函数 function(JSONObject request, String key0, String key1...),实现 参数校验、数值计算、数据同步、消息推送、字段拼接、结构变换 等特定的业务逻辑处理,
可使用 - 和 + 表示优先级,解析 key-() > 解析当前对象 > 解析 key() > 解析子对象 > 解析 key+() | ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}}),会调用远程函数 [boolean isContain(JSONObject request, String array, String value)](https://github.com/APIJSON/apijson-framework/blob/master/src/main/java/apijson/framework/APIJSONFunctionParser.java#L361-L374) ,然后变为 "isPraised":true 这种(假设点赞用户 id 列表包含了 userId,即这个 User 点了赞) - 存储过程 | "@key()":"SQL函数表达式",函数表达式为
function(key0,key1...)
会调用后端数据库对应的存储过程 SQL 函数
function(String key0, String key1...)
除了参数会提前赋值,其它和 远程函数 一致 | ["@limit":10,
"@offset":0,
"@procedure()":"getCommentByUserId(id,@limit,@offset)"](http://apijson.cn:8080/get/{"User":{"@limit":10,"@offset":0,"@procedure()":"getCommentByUserId(id,@limit,@offset)"}})
会转为
`getCommentByUserId(38710,10,0)`
来调用存储过程 SQL 函数
`getCommentByUserId(IN id bigint, IN limit int, IN offset int)`
然后变为
"procedure":{
   "count":-1,
   "update":false,
   "list":[]
}
其中 count 是指写操作影响记录行数,-1 表示不是写操作;update 是指是否为写操作(增删改);list 为返回结果集 - 引用赋值 | "key@":"key0/key1/.../refKey",引用路径为用 / 分隔的字符串。以 / 开头的是缺省引用路径,从声明 key 所处容器的父容器路径开始;其它是完整引用路径,从最外层开始。
被引用的 refKey 必须在声明 key 的上面。如果对 refKey 的容器指定了返回字段,则被引用的 refKey 必须写在 @column 对应的值内,例如 "@column":"refKey,key1,..." | ["Moment":{
   "userId":38710
},
"User":{
   "id@":"/Moment/userId"
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}})
User 内的 id 引用了与 User 同级的 Moment 内的 userId,
即 User.id = Moment.userId,请求完成后
"id@":"/Moment/userId" 会变成 "id":38710 - 子查询 | "key@":{
   "range":"ALL",
   "from":"Table", // 可省略,默认为首个表对象 key 名
   "Table":{ ... }
}
其中:
range 可为 ALL,ANY;
from 为目标表 Table 的名称;
@ 后面的对象类似数组对象,可使用 count 和 join 等功能。 | ["id@":{
   "from":"Comment", // 可省略
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id=(SELECT min(userId) FROM Comment) - 模糊搜索 | `"key$":"SQL搜索表达式"` => `"key$":["SQL搜索表达式"]`,任意 SQL 搜索表达式字符串,如 %key%(包含 key), key%(以 key 开始), %k%e%y%(包含字母 k,e,y) 等,% 表示任意字符 | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}}),对应 SQL 是`name LIKE '%m%'`,查询 name 包含 "m" 的一个 User 数组 - 正则匹配 | "key~":"正则表达式" => "key~":["正则表达式"],任意正则表达式字符串,如 ^[0-9]+$ ,*~ 忽略大小写,可用于高级搜索 | ["name~":"^[0-9]+$"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name~":"^[0-9]%252B$"}}}),对应 SQL 是`name REGEXP '^[0-9]+$'`,查询 name 中字符全为数字的一个 User 数组 - 连续范围 | "key%":"start,end" => "key%":["start,end"],其中 start 和 end 都只能为 Number, String 中的一种,如 "2017-01-01,2019-01-01" ,["1,90000", "82001,100000"] ,可用于连续范围内的筛选 | ["date%":"2017-10-01,2018-10-01"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"date%2525":"2017-10-01,2018-10-01"}}}),对应SQL是`date BETWEEN '2017-10-01' AND '2018-10-01'`,查询在2017-10-01和2018-10-01期间注册的用户的一个User数组 - 新建别名 | "name:alias",name 映射为 alias,用 alias 替代 name。可用于 column,Table,SQL 函数 等。只用于 GET 类型、HEAD 类型的请求 | ["@column":"toId:parentId"](http://apijson.cn:8080/get/{"Comment":{"@column":"id,toId:parentId","id":51}}),对应 SQL 是`toId AS parentId`,将查询的字段 toId 变为 parentId 返回 - 增加 或 扩展 | "key+":Object,Object的类型由key指定,且类型为 Number,String,JSONArray 中的一种。如 82001,"apijson",["url0","url1"] 等。只用于 PUT 请求 | "praiseUserIdList+":[82001],对应 SQL 是`json_insert(praiseUserIdList,82001)`,添加一个点赞用户 id,即这个用户点了赞 - 减少 或 去除 | "key-":Object,与"key+"相反 | "balance-":100.00,对应SQL是`balance = balance - 100.00`,余额减少100.00,即花费了100元 - 比较运算 | >, <, >=, <= 比较运算符,用于
① 提供 "id{}":"<=90000" 这种条件范围的简化写法

② 实现子查询相关比较运算

不支持 "key=":Object 和 "key!=":Object 这两种写法,直接用更简单的 "key":Object 和 "key!":Object 替代。 | ① ["id<=":90000](http://apijson.cn:8080/get/{"[]":{"User":{"id<=":90000}}}),对应 SQL 是`id<=90000`,查询符合id<=90000的一个User数组

② ["id>@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id>@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
WHERE id>(SELECT min(userId) FROM Comment) - 逻辑运算 | &, \|, ! 逻辑运算符,对应数据库 SQL 中的 AND, OR, NOT。
横或纵与:同一键值对的值内条件默认 \| 或连接,可以在 key 后加逻辑运算符来具体指定;不同键值对的条件默认 & 与连接,可以用下面说明的对象关键词 @combine 来具体指定。

① & 可用于 "key&{}":"条件"等

② \| 可用于 "key\|{}":"条件", "key\|{}":[]等,一般可省略

③ ! 可单独使用,如 "key!":Object,也可像 &,\| 一样配合其他功能符使用
"key!":null 无效,null 值会导致整个键值对被忽略解析,可以用 "key{}":"!=null" 替代,
"key":null 同理,用 "key{}":"=null" 替代。 | ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}}),对应SQL是`id>80000 AND id<=90000`,即id满足id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}}),同 "id{}":">90000,<=80000",对应 SQL 是`id>90000 OR id<=80000`,即 id 满足 id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}}),对应 SQL 是`id NOT IN(82001,38710)`,即 id 满足 ! (id=82001 \| id=38710),可过滤黑名单的消息 - 数组关键词,可自定义 | "key":Object,key为 "[]":{} 中 {} 内的关键词,Object 的类型由 key 指定

① "count":5,查询数量,0 表示最大值,默认值为 10,默认最大值为 100

② "page":1,查询页码,从 0 开始,默认值为 0,默认最大值为 100,一般和 count 一起用

③ "query":2,查询内容
0-对象,1-总数和分页详情,2-数据、总数和分页详情
总数关键词为 total,分页详情关键词为 info,
它们都和 query 同级,通过引用赋值得到自定义 key:value 键值对,不传则返回默认键值对,例如
"total@":"/[]/total", "info@":"/[]/info"
这里query及total仅为GET类型的请求提供方便,
一般可直接用HEAD类型的请求获取总数

④ "join":"&/Table0,\"join":{
   "&/Table0":{}, // 支持 ON 多个字段关联,
   "\      "key0":value0, // 其它ON条件
     "key2":value2,
     ...
     "@combine":"...", // 其它ON条件的组合方式
     "@column":"...", // 外层 SELECT
     "@group":"...", // 外层 GROUP BY
     "@having":"..." // 外层 HAVING
   }
}
多表连接方式:
"@" - APP JOIN
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"*" - CROSS JOIN
"^" - SIDE JOIN
"(" - ANTI JOIN
")" - FOREIGN JOIN
其中 @ APP JOIN 为应用层连表,会从已查出的主表里取得所有副表 key@ 关联的主表内的 refKey 作为一个数组 refKeys: [value0, value1...],然后把原来副表 count 次查询 key=$refKey 的 SQL 用 key IN($refKeys) 的方式合并为一条 SQL 来优化性能;
其它 JOIN 都是 SQL JOIN,具体功能和 MySQL,PostgreSQL 等数据库的 JOIN 一一对应
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
会对应生成
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey` AND 其它ON条件
除了 = 等价关联,也支持 ! 不等关联、\> \< \>= \<= 等比较关联和 $ ~ {} <> 等其它复杂关联方式

⑤ "otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 查询User数组,最多5个:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})
对应 SQL 是`LIMIT 5`

② 查询第3页的User数组,每页5个:
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})
对应 SQL 是`LIMIT 5 OFFSET 15`

③ 查询User数组和对应的User总数:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total", // 可省略
"info@":"/[]/info" // 可省略](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal","info@":"%252F[]%252Finfo"})
返回的数据中,总数及分页详情结构为:
"total":139, // 总数
"info":{ // 分页详情
   "total":139, // 总数
   "count":5, // 每页数量
   "page":0, // 当前页码
   "max":27, // 最大页码
   "more":true, // 是否还有更多
   "first":true, // 是否为首页
   "last":false // 是否为尾页
}

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join":"&/User/id@,\    "Moment":{
     "@group":"id" // 主副表不是一对一,要去除重复数据
   },
   "User":{
     "name~":"t",
     "id@":"/Moment/userId"
   },
   "Comment":{
     "momentId@":"/Moment/id"
   }
}](http://apijson.cn/api/?type=JSON&url=http://apijson.cn:8080/get&json=%7B%22%5B%5D%22:%7B%22count%22:5,%22join%22:%22%26%2FUser%2Fid@,%3C%2FComment%22,%22Moment%22:%7B%22@column%22:%22id,userId,content%22,%22@group%22:%22id%22%7D,%22User%22:%7B%22name~%22:%22t%22,%22id@%22:%22%2FMoment%2FuserId%22,%22@column%22:%22id,name,head%22%7D,%22Comment%22:%7B%22momentId@%22:%22%2FMoment%2Fid%22,%22@column%22:%22id,momentId,content%22%7D%7D%7D)

⑤ 每一层都加当前用户名:
["User":{},
"[]":{
   "name@":"User/name", // 自定义关键词
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) - 对象关键词,可自定义 | "@key":Object,@key 为 Table:{} 中 {} 内的关键词,Object 的类型由 @key 指定

① "@combine":"key0 \| (key1 & (key2 \| !key3))...",条件组合方式,最终按
(其它key条件 AND 连接) AND (key0条件 OR (key1条件 AND (key2条件 OR (NOT key3条件))))
这种方式连接,其中 "其它key" 是指与 @combine 在同一对象,且未被它声明的条件 key,默认都是 & 连接。注意不要缺少或多余任何一个空格。

② "@column":"column;function(arg)...",返回字段

③ "@order":"column0+,column1-...",排序方式

④ "@group":"column0,column1...",分组方式。如果 @column 里声明了 Table 的 id,则 id 也必须在 @group 中声明;其它情况下必须满足至少一个条件:
1.分组的 key 在 @column 里声明
2.Table 主键在 @group 中声明

⑤ "@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // OR 连接,或
"@having&":"function0(...)?value0;function1(...)?value1;function2(...)?value2..." // AND 连接,或
"@having":{
   "h0":"function0(...)?value0",
   "h1":function1(...)?value1",
   "h2":function2(...)?value2...",
   "@combine":"h0 & (h1 \| !h2)" // 任意组合,非必传
}
SQL 函数条件,一般和 @group 一起用,函数一般在 @column 里声明

⑥ "@schema":"sys",集合空间(数据库名/模式),非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑦ "@database":"POSTGRESQL",数据库类型,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑧ "@datasource":"DRUID",跨数据源,非默认的值可通过它来指定,可以在最外层作为全局默认配置

⑨ "@json":"key0,key1...",转为 JSON 格式返回,符合 JSONObject 则转为 {...},符合 JSONArray 则转为 \[...]

⑩ "@role":"OWNER",来访角色,包括
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
可以在最外层作为全局默认配置,
可自定义其它角色并重写 Verifier.verify 等相关方法来自定义校验

⑪ "@explain":true,性能分析,可以在最外层作为全局默认配置

⑫ "@raw":"key0,key1...",其中 key0, key1 都对应有键值对
"key0":"SQL片段或SQL片段的别名",
"key1":"SQL片段或SQL片段的别名"
自定义原始SQL片段,可扩展嵌套SQL函数等复杂语句,必须是后端已配置的,只有其它功能符都做不到才考虑,谨慎使用,注意防 SQL 注入

⑬ "@null":"key1,key2...",空值键值对,自动插入 key1:null, key2:null ... 并作为有效键值对执行,作为条件时对应 SQL 是 `WHERE tag IS NULL`,作为值时对应 SQL 是 `SET tag = NULL`

⑭ "@otherKey":Object,自定义关键词,名称和以上系统关键词不一样,且原样返回上传的值 | ① 搜索 name 或 tag 任何一个字段包含字符 a 的 User 列表:
["name~":"a",
"tag~":"a",
"@combine":"name~ \| tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~%20%7C%20tag~"}}})
对应SQL是`name REGEXP 'a' OR tag REGEXP 'a'`

② 只查询 id,sex,name 这几列并且请求结果也按照这个顺序:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})
对应 SQL 是`SELECT id,sex,name`

③ 查询按 name 降序、id 默认顺序 排序的 User 数组:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})
对应 SQL 是`ORDER BY name DESC,id`

④ 查询按 userId 分组的 Moment 数组:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})
对应 SQL 是`GROUP BY userId,id`

⑤ 查询 按 userId 分组、id 最大值>=100 的 Moment 数组:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
对应 SQL 是`SELECT userId,max(id) ... GROUP BY userId HAVING max(id)>=100`
还可以指定函数返回名:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})
对应 SQL 是`SELECT userId,max(id) AS maxId ... GROUP BY userId HAVING (maxId)>=100`

⑥ 查询 sys 内的 User 表:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})
对应 SQL 是`FROM sys.User`

⑦ 查询 PostgreSQL 数据库的 User 表:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL","@explain":true}})

⑧ 使用 Druid 连接池查询 User 表:
["@datasource":"DRUID"](http://apijson.cn:8080/get/{"User":{"@datasource":"DRUID"}})

⑨ 将 VARCHAR 字符串字段 get 转为 JSONArray 返回:
["@json":"get"](http://apijson.cn:8080/get/{"Access":{"@json":"get"}})

⑩ 查询当前用户的动态:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑪ 开启性能分析:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})
对应 SQL 是`EXPLAIN`

⑫ 统计最近一周偶数 userId 的数量
["@column":"date;left(date,10):day;sum(if(userId%2=0,1,0))",
"@group":"day",
"@having":"to_days(now())-to_days(\`date\`)<=7",
"@raw":"@column,@having"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@column":"date%3bleft(date,10):day%3bsum(if(userId%252=0,1,0))","@group":"day","@having":"to_days(now())-to_days(\`date\`)<=7","@raw":"@column,@having"}}})
对应 SQL 是``SELECT date, left(date,10) AS day, sum(if(userId%2=0,1,0)) ... GROUP BY day HAVING to_days(now())-to_days(`date`)<=7``

⑬ 把用户的标签设置为空
["@null":"tag"](http://apijson.cn/api/?type=JSON&url=http://apijson.cn:8080/put/User&json={%22id%22:82001,%22@null%22:%22tag%22,%22@explain%22:true})

⑭ 从pictureList 获取第 0 张图片:
["@position":0, // 自定义关键词
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) - 全局关键词 | 为最外层对象 {} 内的关键词。其中 @database,@schema, @datasource, @role, @explain 基本同对象关键词,见上方说明,区别是全局关键词会每个表对象中没有时自动放入,作为默认值。

① "tag":"Table",后面的 tag 是非 GET、HEAD 请求中匹配请求的 JSON 结构的标识,一般是要查询的 Table 的名称或该名称对应的数组 Table[] 或 Table:[],由后端 Request 表中指定。

② "version":1,接口版本,version 不传、为 null 或 <=0 都会使用最高版本,传了其它有效值则会使用最接近它的最低版本,由后端 Request 表中指定。

③ "format":true,格式化返回 Response JSON 的 key,一般是将 TableName 转为 tableName, TableName[] 转为 tableNameList, Table:alias 转为 alias, TableName-key[] 转为 tableNameKeyList 等小驼峰格式。 | ① 查隐私信息:
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② 使用第 1 版接口查隐私信息:
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ 格式化朋友圈接口返回 JSON 中的 key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) + Get data in arrays | `"key[]":{}`
The part after the colon is a JSONObject. *key* is optional. When *key* is the same as the table name , the JSONObject will be in a simplified form. For example, `{Table:{Content}}` will be written as `{Content}`.| [{"User[]":{"User":{}}}](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{}}})
It is used for getting data from a user. Here, key and tablename are all "User", then
`{"User":{"id", ...}}`
will be written as
`{"id", ...}` + Get data that meets specific conditions | `"key{}":[]`
The part after the colon is a JSONArray with conditions inside.| ["id{}":[38710,82001,70793]](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":[38710,82001,70793]}}})
In SQL, this would be `id IN(38710,82001,70793)`.
It means getting data with id equals 38710,82001,70793. + Get data with comparison operation| `"key{}":"condition0,condition1..."`
Conditions can be any SQL comparision operation. Use''to include any non-number characters.| ["id{}":"<=80000,\>90000"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"id{}":"<=80000,\>90000"}}})
In SQL, it'd be
`id<=80000 OR id>90000`,
which means get User array with id\<=80000 \| id>90000 + Get data that contains an element | `"key<>":Object` => `"key<>":[Object]`
*key* must be a JSONArray while *Object* cannot be JSON.| ["contactIdList<\>":38710](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"contactIdList<\>":38710}}})
In SQL, this would be
`json_contains(contactIdList,38710)`.
It means find data of the User whose contactList contains 38710. + See if it exists |`"key}{@":{`
   `"from":"Table",`
   `"Table":{ ... }`
`}`

}{ means EXISTS.
*key* is the one you want to check.
Here is a *Subquery* in it, see specifications below for more information. | ["id}{@":{
   "from":"Comment",
   "Comment":{
      "momentId":15
   }
}](http://apijson.cn:8080/get/{"User":{"id}{@":{"from":"Comment","Comment":{"momentId":15}}}})
WHERE EXISTS(SELECT * FROM Comment WHERE momentId=15) + Include functions in parameters | `"key()":"function (key0,key1...)"`
This will trigger the back-end
`function(JSONObject request, String key0, String key1...)`
to get or testify data.
Use - and + to show the order of priority: analyze key-() > analyze the current object > analyze key() > analyze child object > analyze key+()| ["isPraised()":"isContain(praiseUserIdList,userId)"](http://apijson.cn:8080/get/{"Moment":{"id":301,"isPraised()":"isContain(praiseUserIdList,userId)"}})
This will use function boolean isContain(JSONObject request, String array, String value). In this case, client will get "isPraised":true(In this case, client use function to testify if a user clicked ‘like’ button for a Moment.) + Refer a value | `"key@":"key0/key1/.../refKey"`
Use / to show path. The part before the colon is the key that wants to refer. The path after the colon starts with the parent level of the key.| ["Moment":{
   "userId":38710
},
"User":{
   "id@":"/Moment/userId"
}](http://apijson.cn:8080/get/{"Moment":{"userId":38710},"User":{"id@":"%252FMoment%252FuserId"}})
In this example, the value of id in User refer to the *userId* in *Moment*, which means
`User.id = Moment.userId`.
After the request is sent,
`"id@":"/Moment/userId"` will be `"id":38710`. + Subquery | `"key@":{`
   `"range":"ALL",`
   `"from":"Table",`
   `"Table":{ ... }`
`}`
*range* can be ALL, ANY.
*from* means which table you want to query.
It’s very similar to how you query in SQL.
You can also use *count*, *join*, etc. | ["id@":{
   "from":"Comment",
   "Comment":{
      "@column":"min(userId)"
   }
}](http://apijson.cn:8080/get/{"User":{"id@":{"from":"Comment","Comment":{"@column":"min(userId)"}}}})
`WHERE id=(SELECT min(userId) FROM Comment)`. + Fuzzy matching | `"key$":"SQL search expressions"` => `"key$":["SQL search expressions"]`
Any SQL search expressions.Eg.%key%(include key), key%(start with key),%k%e%y%(include k, e, y). % means any characters. | ["name$":"%m%"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name$":"%2525m%2525"}}})
In SQL, it's
`name LIKE '%m%'`,
meaning that get User with ‘m’ in name. + Regular Expression| `"key~":"regular expression"` => `"key~":["regular expression"]`
It can be any regular expressions.Eg. ^[0-9]+$ ,*~ not case sensitive, advanced search is applicable.| ["name~":"^[0-9]+$"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"name~":"^[0-9]%252B$"}}})
In SQL, it's
`name REGEXP '^[0-9]+$'`. + Get data in a range| `"key%":"start,end"` => `"key%":["start,end"]`
The data type of start and end can only be either Boolean, Number or String. Eg. "2017-01-01,2019-01-01" ,["1,90000", "82001,100000"]. It's used for getting data from a specific time range. | ["date%":"2017-10-01,2018-10-01"](http://apijson.cn:8080/get/{"User[]":{"count":3,"User":{"date%2525":"2017-10-01,2018-10-01"}}})
In SQL, it's
`date BETWEEN '2017-10-01' AND '2018-10-01'`,
meaning to get User data that registered between 2017-10-01 and 2018-10-01. + Make an alias | `"name:alias"`
this changes name to alias in returning results. It’s applicable to column, tableName, SQL Functions, etc. but only in GET, HEAD requests. | ["@column":"toId:parentId"](http://apijson.cn:8080/get/{"Comment":{"@column":"id,toId:parentId","id":51}})
In SQL, it's
`toId AS parentId`.
It'll return `parentId` instead of `toId`.

For @key format like "lc_wai6b3vk2:(lc_wai6b3vk)", it means renaming field lc_wai6b3vk2 to lc_wai6b3vk, commonly used for field renaming scenarios. Example:
{
  "lc_sinan_ba074fbb": {
    "lc_wai6b3vk": "11",
    "lc_wai6b3vk2": "22",
    "@combine": "lc_wai6b3vk \\| lc_wai6b3vk2",
    "@key": "lc_wai6b3vk2:(lc_wai6b3vk)"
  }
}
corresponds to SQL `(lc_wai6b3vk = '11' OR lc_wai6b3vk2 = '22')`, but the lc_wai6b3vk2 field will be renamed and displayed as lc_wai6b3vk in the returned result + Add / expand an item | `"key+":Object`
The type of Object is decided by *key*. Types can be Number, String, JSONArray. Froms are 82001,"apijson",["url0","url1"] respectively. It’s only applicable to PUT request.| "praiseUserIdList+":[82001]. In SQL, it's
`json_insert(praiseUserIdList,82001)`.
Add an *id* that praised the Moment. + Delete / decrease an item | `"Key-":Object`
It’s the contrary of "key+" | "balance-":100.00. In SQL, it's
`balance = balance - 100.00`,
meaning there's 100 less in balance. + Operations | &, \|, !
They're used in logic operations. It’s the same as AND, OR, NOT in SQL respectively.
By default, for the same key, it’s ‘\|’ (OR)operation among conditions; for different keys, the default operation among conditions is ‘&’(AND).
| ① ["id&{}":">80000,<=90000"](http://apijson.cn:8080/head/{"User":{"id&{}":">80000,<=90000"}})
In SQL, it's
`id>80000 AND id<=90000`,
meaning *id* needs to be id>80000 & id<=90000

② ["id\|{}":">90000,<=80000"](http://apijson.cn:8080/head/{"User":{"id\|{}":">90000,<=80000"}})
It's the same as "id{}":">90000,<=80000".
In SQL, it's
`id>80000 OR id<=90000`,
meaning that *id* needs to be id>90000 \| id<=80000

③ ["id!{}":[82001,38710]](http://apijson.cn:8080/head/{"User":{"id!{}":[82001,38710]}})
In SQL, it's
`id NOT IN(82001,38710)`,
meaning id needs to be ! (id=82001 \| id=38710). + Keywords in an Array: It can be self-defined. | As for `"key":Object`, *key* is the keyword of *{}* in *"[]":{}*. The type of *Object* is up to *key*.

① `"count":Integer` It's used to count the number. The default largest number is 100.

② `"page":Integer` It’s used for getting data from which page, starting from 0. The default largest number is 100. It’s usually used with COUNT.

③ `"query":Integer` Get the number of items that match conditions
When to get the object, the integer should be 0; when to get the total number, it’s 1; when both above, it’s 2.
You can get the total number with keyword total. It can be referred to other values.
Eg.
`"total@":"/[]/total"`
Put it as the same level of query.
*Query* and *total* are used in GET requests just for convenience. Generally, HEAD request is for getting numbers like the total number.

④ `"join":"&/Table0,Join tables:
"\<" - LEFT JOIN
">" - RIGHT JOIN
"&" - INNER JOIN
"\|" - FULL JOIN
"!" - OUTER JOIN
"@" - APP JOIN
Where @ APP JOIN is in application layer.It’ll get all the keys in tables that refKeys in result tables are referred to, like refKeys:[value0, value1….]. Then, as the results get data according to `key=$refKey` a number of times (COUNT), it uses key `IN($refKeys)` to put these counts together in just one SQL query, in order to improve the performance.
Other JOIN functions are the same as those in SQL.
`"join":"`"MainTable":{},`
`"ViceTable":{"key@":"/MainTable/refKey"}`
will return
`MainTable LEFT JOIN ViceTable`
`ON ViceTable.key=MainTable.refKey`

⑤ `"otherKey":Object` Self-defined keyword other than those that already in the system. It also returns with self-defined keywords.| ① Get User arrays with maximum of 5:
["count":5](http://apijson.cn:8080/get/{"[]":{"count":5,"User":{}}})

② Look into User arrays on page 3. Show 5 of them each page.
["count":5,
"page":3](http://apijson.cn:8080/get/{"[]":{"count":5,"page":3,"User":{}}})

③ Get User Arrays and count the total number of Users:
["[]":{
   "query":2,
   "User":{}
},
"total@":"/[]/total"](http://apijson.cn:8080/get/{"[]":{"query":2,"count":5,"User":{}},"total@":"%252F[]%252Ftotal"})
Questions like total page numbers or if there's next page can be solved by total,count,page functions,
Total page number:
`int totalPage = Math.ceil(total / count)`
If this is the last page:
`boolean hasNextPage = total > count*page`
If this is the first page:
`boolean isFirstPage = page <= 0`
If it's the last page:
`boolean isLastPage = total <= count*page`
...

④ Moment INNER JOIN User LEFT JOIN Comment:
["[]":{
   "join": "&/User,\    "Moment":{},
   "User":{
     "name~":"t",
     "id@": "/Moment/userId"
   },
   "Comment":{
     "momentId@": "/Moment/id"
   }
}](http://apijson.cn:8080/get/{"[]":{"count":5,"join":"&%252FUser,\<%252FComment","Moment":{"@column":"id,userId,content"},"User":{"name~":"t","id@":"%252FMoment%252FuserId","@column":"id,name,head"},"Comment":{"momentId@":"%252FMoment%252Fid","@column":"id,momentId,content"}}})

⑤ Add the current user to every level:
["User":{},
"[]":{
   "name@":"User/name", //self-defined keyword
   "Moment":{}
}](http://apijson.cn:8080/get/{"User":{},"[]":{"name@":"User%252Fname","Moment":{}}}) + Keywords in Objects: It can be self-defined. | `"@key":Object` @key is the keyword of {} in Table:{}. The type of Object is decided by @key

① `"@combine":"&key0,&key1,\|key2,key3,`
`!key4,!key5,&key6,key7..."`
First, it’ll group data with same operators. Within one group, it operates from left to right. Then it’ll follow the order of & \| ! to do the operation. Different groups are connected with &. So the expression above will be :
(key0 & key1 & key6 & other key) & (key2 \| key3 \| key7) & !(key4 \| key5)
\| is optional.

② `"@column":"column;function(arg)..."` Return with specific columns.

③ `"@order":"column0+,column1-..."` Decide the order of returning results:

④ `"@group":"column0,column1..."` How to group data. If @column has declared Table id, this id need to be included in @group. In other situations, at least one of the following needs to be done:
1.Group id is declared in @column
2.Primary Key of the table is declared in @group.

⑤ `@having":"function0(...)?value0;function1(...)?value1;function2(...)?value2..."` Add conditions on return results with @having. Usually working with@group, it’s declared in @column.

⑥ `"@schema":"sys"` Can be set as default setting.

⑦ `"@database":"POSTGRESQL"` Get data from a different database.Can be set as default setting.

⑧ `"@role":"OWNER"` Get information of the user, including
UNKNOWN,LOGIN,CONTACT,CIRCLE,OWNER,ADMIN,
Can be set as default setting.
You can self-define a new role or rewrite a role. Use`Verifier.verify` etc. to self-define validation methods.

⑨ `"@explain":true` Profiling. Can be set as default setting.

⑩ `"@otherKey":Object` Self-define keyword | ① Search *Users* that *name* or *tag* contains the letter "a":
["name~":"a",
"tag~":"a",
"@combine":"name~,tag~"](http://apijson.cn:8080/get/{"User[]":{"count":10,"User":{"@column":"id,name,tag","name~":"a","tag~":"a","@combine":"name~,tag~"}}})

② Only search column id,sex,name and return with the same order:
["@column":"id,sex,name"](http://apijson.cn:8080/get/{"User":{"@column":"id,sex,name","id":38710}})

③ Search Users that have descending order of name and default order of id:
["@order":"name-,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"User":{"@column":"name,id","@order":"name-,id"}}})

④ Search Moment grouped with userId:
["@group":"userId,id"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":%7B"@column":"userId,id","@group":"userId,id"}}})

⑤ Search Moments that id equals or less than 100 and group with userId:
["@column":"userId;max(id)",
"@group":"userId",
"@having":"max(id)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id)","@group":"userId","@having":"max(id)>=100"}}})
You can also define the name of the returned function:
["@column":"userId;max(id):maxId",
"@group":"userId",
"@having":"(maxId)>=100"](http://apijson.cn:8080/get/{"[]":{"count":10,"Moment":{"@column":"userId%253Bmax(id):maxId","@group":"userId","@having":"(maxId)>=100"}}})

⑥ Check Users table in sys:
["@schema":"sys"](http://apijson.cn:8080/get/{"User":{"@schema":"sys"}})

⑦ Check Users table in PostgreSQL:
["@database":"POSTGRESQL"](http://apijson.cn:8080/get/{"User":{"@database":"POSTGRESQL"}})

⑧ Check the current user's activity:
["@role":"OWNER"](http://apijson.cn:8080/get/{"[]":{"Moment":{"@role":"OWNER"}}})

⑨ Turn on profiling:
["@explain":true](http://apijson.cn:8080/get/{"[]":{"Moment":{"@explain":true}}})

⑩ Get the No.0 picture from pictureList:
["@position":0, //self-defined keyword
"firstPicture()":"getFromArray(pictureList,@position)"](http://apijson.cn:8080/get/{"User":{"id":38710,"@position":0,"firstPicture()":"getFromArray(pictureList,@position)"}}) + global keyword. | It is a keyword inside the outermost object {}. Among them, @database, @schema, @datasource, @role, and @explain are basically the same as object keywords, see the above description, the difference is that the global keywords will be automatically inserted in each table object as the default value.

① "tag": String, the following tag is the identifier of the JSON structure matching the request in non-GET or HEAD requests, generally it is the name of the Table to be queried or the array Table[] or Table:[] corresponding to the name, determined by the backend specified in the Request table.

② "version": Integer, the interface version. If the version is not passed, null or <=0, the highest version will be used. If other valid values are passed, the lowest version closest to it will be used, which is specified in the backend Request table.

③ "format": Boolean, formatted to return the key of the Response JSON, generally converting TableName to tableName, TableName[] to tableNameList, Table:alias to alias, TableName-key[] to tableNameKeyList and other camelcase formats. | ① Check private information::
[{"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

② Use the version 1 interface to check private information::
[{"version":1,"tag":"Privacy","Privacy":{"id":82001}}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fgets&type=JSON&json={%22version%22:1,%22tag%22:%22Privacy%22,%22Privacy%22:{%22id%22:82001}})

③ Format Moments interface to return in JSON key:
[{
   "format":true,
   "[]":{
     "page":0,
     "count":3,
     "Moment":{},
     "User":{
       "id@":"/Moment/userId"
     },
     "Comment[]":{
       "count":3,
       "Comment":{
         "momentId@":"[]/Moment/id"
       }
     }
   }
}](http://apijson.cn:8080/get/{"format":true,"[]":{"page":0,"count":3,"Moment":{},"User":{"id@":"%252FMoment%252FuserId"},"Comment[]":{"count":3,"Comment":{"momentId@":"[]%252FMoment%252Fid"}}}}) +
+ diff --git a/LICENSE b/LICENSE index 1a71560c8..9743dd5aa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,18 +1,18 @@ Tencent is pleased to support the open source community by making APIJSON available. -Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. +Copyright (C) 2020 Tencent. All rights reserved. APIJSON is licensed under the Apache License Version 2.0. A copy of the Apache License Version 2.0 is included in this file. +The copyright notice pertaining to the Tencent code in this repo was previously in the name of “THL A29 Limited.” +That entity has now been de-registered. +You should treat all previously distributed copies of the code as if the copyright notice was in the name of “Tencent.” Other dependencies and licenses: Open Source Software Licensed under the Apache License Version 2.0: -------------------------------------------------------------------- -1. fastjson -Copyright 1999-2019 Alibaba Group Holding Ltd. - Terms of Apache License Version 2.0 diff --git a/README-Chinese.md b/README-Chinese.md new file mode 100644 index 000000000..efd1858f8 --- /dev/null +++ b/README-Chinese.md @@ -0,0 +1,856 @@ +Tencent is pleased to support the open source community by making APIJSON available.
+Copyright (C) 2020 Tencent. All rights reserved.
+This source code is licensed under the Apache License Version 2.0
+ +

+ APIJSON +

+ +

🏆 实时 零代码、全功能、强安全 ORM 库 🚀
后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

+ +

+ English + 通用文档 + 视频教程 + 测试用例 + AI 问答 +

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+

+ + + + + + + + + +

+

+ + + + + +

+

+ + + +

+ +

+ +

+ +--- + +导航目录: 项目简介 [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)      完整详细的导航目录 [点这里查看](/Navigation.md)
+ + +APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
+为各种增删改查提供了完全自动化的万能通用接口,零代码实时满足千变万化的各种新增和变更需求。
+能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
+适合中小型前后端分离的项目,尤其是 创业项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
+ +通过万能通用接口,前端可以定制任何数据、任何结构。
+大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
+前端再也不用和后端沟通接口或文档问题了。再也不会被文档各种错误坑了。
+后端再也不用为了兼容旧接口写新版接口和文档了。再也不会被前端随时随地没完没了地烦了。 + +### 特点功能 + +#### 对于后端 +* 提供万能通用接口,大部分 HTTP API 不用再写 +* 零代码增删改查、各种跨库连表、JOIN 嵌套子查询等 +* 自动生成文档,不用再编写和维护,且自动静态检查 +* 自动校验权限、自动管理版本、自动防 SQL 注入 +* 开放 HTTP API 无需划分版本,始终保持兼容 + +#### 对于前端 +* 不用再向后端催接口、求文档 +* 数据和结构完全定制,要啥有啥 +* 看请求知结果,所求即所得 +* 可一次获取任何数据、任何结构 +* 能去除多余数据,节省流量提高速度 + +
+ +### APIJSON 接口展示 +#### Postman 展示 APIJSON +![](https://static.oschina.net/uploads/img/201711/12230359_f7fQ.jpg) +
+ +#### APIAuto 展示 APIJSON +**使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅 减少传参错误、提升联调效率**
+(注意网页工具界面是 APIAuto,里面的 URL+JSON 才是 APIJSON 的 HTTP API):
+
+

+ APIJSON 多表关联查询、结构自由组合,APIAuto 多个测试账号、一键共享测试用例 +

+ +![](https://oscimg.oschina.net/oscnet/up-bbbec4fc5edc472be127c02a4f3cd8f4ec2.JPEG) +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) + +
+

+ APIAuto 自动生成前端(客户端)请求代码 和 Python 测试用例代码,一键下载 +

+ +![](https://oscimg.oschina.net/oscnet/up-637193bbd89b41c3264827786319e842aee.JPEG) + +
+

+ APIAuto 自动保存请求记录、自动生成接口文档,可添加常用请求、快捷查看一键恢复 +

+ +![](https://oscimg.oschina.net/oscnet/up-7dcb4ae71bd3892a909e4ffa37ba7c1d92a.JPEG) + +
+

+ APIAuto 一键自动接口回归测试,不需要写任何代码(注解、注释等全都不要) +

+ +![](https://oscimg.oschina.net/oscnet/up-c1ba774f8e7fcc5adcdb05cad5bd414d766.JPEG) + +
+

+ 一图胜千言 - APIJSON 部分基础功能概览 +

+ +![](https://oscimg.oschina.net/oscnet/up-e21240ef3770326ee6015e052226d0da184.JPEG) +![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif) + + +

+ +### APIJSON App 演示 +使用 APIJSON + ZBLibrary 开发的 Android 客户端 Demo (以下 Gif 图看起来比较卡,实际上运行很流畅): +
+![](https://oscimg.oschina.net/oscnet/up-a3f167e593080e8a3fc09c3d5fc09330c98.gif) +![](https://oscimg.oschina.net/oscnet/up-141abcb5dabc01c890d70c461bd1fdc751f.gif) +![](https://oscimg.oschina.net/oscnet/up-58aecc2701c2c4ea33e53f246e427773b09.gif) + +
+ +### APIJSON 分享演讲 +#### APIJSON-零代码接口与文档 ORM 库(国际开源谷 Gitee Meetup) + +https://www.bilibili.com/video/BV1Tv411t74v + +![image](http://apijson.cn/images/comparison/APIJSON_vs_PreviousWays.jpg) + + +#### APIJSON 和 APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会) + +https://www.bilibili.com/video/BV1yv411p7Y4 + +wecom-temp-377bbd0daf5aed716baf7ebcb003d94c + + +
+ +### 为什么选择 APIJSON? +前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
+https://github.com/Tencent/APIJSON/wiki + +* **解决十大痛点** (可帮前后端开发大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽) +* **开发提速很大** (CRUD 零代码热更新全自动,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) +* **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) +* **社区影响力大** (GitHub 18K+ Star 在 400W Java 项目排名前 100,远超 FLAG, BAT 等国内外绝大部分开源项目) +* **各项荣誉成就** (腾讯内外 5 个奖项、腾讯开源前五、腾讯后端 Star 第一、Trending 日周月榜大满贯 等) +* **多样用户案例** (腾讯内有互娱、音乐、微信、云与智慧,外部有华为、华能、百度、快手、中兴、圆通、传音等) +* **适用场景广泛** (社交聊天、阅读资讯、影音娱乐、办公学习 等各种 App、网站、小程序、公众号 等非金融类项目) +* **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) +* **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) +* **功能丰富强大** (增删改查、分页排序、分组聚合、各种条件、各种 JOIN、各种子查询、跨库连表 等零代码实现) +* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防 SQL 注入) +* **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) +* **高质可靠代码** (代码严谨规范,蚂蚁集团源伞 Pinpoint 代码扫描分析报告平均每行代码 Bug 率低至 0.15%) +* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的示例) +* **工程轻量小巧** (无第三方依赖,Jar 仅 263KB,Java 文件仅 68 个共 14864 行代码,例如 APIJSONORM 8.1.0) +* **多年持续迭代** (自 2016 年起已连续维护 8 年多,70+ 贡献者、100+ 发版、3000+ 提交,不断更新迭代中...) + +**按照一般互联网中小型项目情况可得出以下对比表格:** + +表数量 T | 平均每表字段数 C | SSMH 按快估计 | APIJSONBoot 按慢估计 | APIJSONBoot 提速倍数 +-------- | --------- | --------------------- | ------------------ | ----------- +1 | 3 | 179 min(约一上午) | 11 min(约十分钟) | 15.27 +5 | 4 | 1935 min(约朝九晚六一周) | 70 min(约一小时) | 26.64 +10 | 10 | 8550 min(大小周超半个月) | 320 min(约一下午) | 25.72 +20 | 15 | 31900 min(约 996 两个月) | 940 min(约上班两天) | 32.94 +50 | 20 | 176750 min(11117 超半年) | 3100 min(约上班一周) | 56.02 + +### 用户反馈 +**腾讯 IEG 数据产品开发组负责人 xinlin:** +“腾讯的 APIJSON 开源方案,它可以做到零代码生成接口和文档,并且整个生成过程是自动化。当企业有元数据的时候,马上就可以获得接口” + +**腾讯科技 后台开发高级工程师 雷大锤:** +“可以抽出时间来看apijson了,这个可以为T10做准备,也是业界很火的东西,可以提升个人影响力!” + +**腾讯 bodian520:** +“在调试GET、POST、PUT接口时遇到了一些问题,把个人的摸索经验分享一下,希望作者能梳理下文档,方便我们更好的接入” + +**华为 minshiwu:** +“demo工程,默认使用apijson-framework,可以做到无任何配置即可体验apijson的各种能力。” + +**字节跳动 qiujunlin:** +“初次见到这个项目,觉得太惊艳了,眼前一亮。给我的感受是,项目大大简化了开发流程,开发效率提升了很多倍。” + +**百度智慧城市研发 lpeng:** +“很兴奋的发现APIJSON很适合我们的一个开发场景,作为我们协议定义的一部分” + +**中兴 duyijiang:** +“感谢腾讯大大提供的框架,很好用” + +https://github.com/Tencent/APIJSON/issues/132#issuecomment-1106669540 + +
+ +### 常见问题 +#### 1.如何定制业务逻辑? +在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象、参数名称 等,然后对查到的数据自定义处理
+https://github.com/Tencent/APIJSON/issues/101 + +#### 2.如何控制权限? +在 Access 表配置校验规则,默认不允许访问,需要对 每张表、每种角色、每种操作 做相应的配置,粒度细分到行级
+https://github.com/Tencent/APIJSON/issues/12 + +#### 3.如何校验参数? +在 Request 表配置校验规则 structure,提供 MUST、TYPE、VERIFY 等通用方法,可通过 远程函数 来完全自定义
+https://github.com/Tencent/APIJSON/wiki#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86 + +更多常见问题及提问前必看
+https://github.com/Tencent/APIJSON/issues/36 +
+ +### 注意事项 +**请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
+大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API !** +[#181](https://github.com/Tencent/APIJSON/issues/181) +
+
+
+
+ +导航目录: [项目简介](#--apijson) 上手使用 [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)      完整详细的导航目录 [点这里查看](/Navigation.md)
+ +### 快速上手 + +#### 1.后端上手 +可以跳过这个步骤,直接用APIJSON服务器IP地址 apijson.cn:8080 来测试接口。
+见  [APIJSON后端上手 - Java](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server)
+ +#### 2.前端上手 +可以跳过这个步骤,直接使用 [APIAuto-机器学习HTTP接口工具](https://github.com/TommyLemon/APIAuto) 或 下载客户端App。
+见  [Android](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Android)  或  [iOS](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-iOS)  或  [JavaScript](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-JavaScript)
+ + +### 下载客户端 App + +仿微信朋友圈动态实战项目
+[APIJSONApp.apk](http://files.cnblogs.com/files/tommylemon/APIJSONApp.apk) + +测试及自动生成代码工具
+[APIJSONTest.apk](http://files.cnblogs.com/files/tommylemon/APIJSONTest.apk) + +### 开源许可 +使用 [Apache License 2.0](/LICENSE),对 公司、团队、个人 等 商用、非商用 都自由免费且非常友好,请放心使用和登记 + +### 使用登记 +如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(新的按登记顺序排列、专群优先答疑解惑):
+https://github.com/Tencent/APIJSON/issues/187 +
+ + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + * [腾讯科技有限公司](https://www.tencent.com) + * [腾讯音乐娱乐集团](https://www.tencentmusic.com) + * [深圳市传音通讯有限公司](https://www.transsion.com) + * [社宝信息科技(上海)有限公司](https://shebaochina.com) + * [华能贵诚信托有限公司](https://www.hngtrust.com) + * [投投科技](https://www.toutou.com.cn) + * [圆通速递](https://www.yto.net.cn) + * [乐拼科技](https://www.lepinyongche.com) + * [珠海采筑电子商务有限公司](https://www.aupup.com) + * [爱投斯智能技术(深圳)有限公司](http://www.aiotos.net) + * [邻盛科技(武汉)有限公司](http://www.linksame.com) + * [上海麦市信息科技有限公司](https://www.masscms.com) + * [上海翊丞互联网科技有限公司](http://www.renrencjl.com/home) + * [上海直真君智科技有限公司](http://www.zzjunzhi.com) + * [北明软件有限公司](https://www.bmsoft.com.cn/) + * [上海钰亿环保科技有限公司](#) + +### 贡献者们 +主项目 APIJSON 的贡献者们(6 个腾讯工程师、1 个微软工程师、1 个阿里云工程师、1 个字节跳动工程师、1 个网易工程师、1 个 Zoom 工程师、1 个圆通工程师、1 个知乎基础研发架构师、1 个智联招聘工程师、gorm-plus 作者、1 个美国加州大学学生、3 个 SUSTech 学生等):
+https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +生态周边项目的作者们(2 个腾讯工程师、1 个 BAT 技术专家、1 个微软工程师、2 个字节跳动工程师、1 个神州数码工程师&Apache dubbo2js 作者 等):
+https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
+https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +还有为 APIJSON 扫描代码贡献 Issue 的 [蚂蚁集团源伞](https://www.sourcebrella.com) 和 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) +
+ + +
+
+ +感谢大家的贡献。 + +### 统计分析 +腾讯、华为、阿里巴巴、美团、字节跳动、百度、京东、网易、快手等和 Google, Apple, Microsoft, Amazon, Paypal, IBM, Shopee 等
+数百名知名大厂员工点了 Star,也有腾讯、华为、字节跳动、Microsoft、Zoom 等不少知名大厂员工提了 PR/Issue,感谢大家的支持~
+[![Stargazers over time](https://starchart.cc/Tencent/APIJSON.svg)](https://starchart.cc/Tencent/APIJSON) +image +image +image + +根据开源指南针报告,APIJSON Java 版已经是国内顶级、国际一流的 Java 开源项目了 [#518](https://github.com/Tencent/APIJSON/issues/518)
+image + +### 规划及路线图 +新增功能、强化安全、提高性能、增强稳定、完善文档、丰富周边、推广使用
+https://github.com/Tencent/APIJSON/blob/master/Roadmap.md + +理论上所有支持 SQL 与 JDBC/ODBC 的软件,都可以用本项目对接 CRUD,待测试:
+[OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Spark](https://spark.apache.org/docs/3.3.0/sql-ref-syntax-qry-select.html)(可用 Hive 对接), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase) + +### 我要赞赏 +创作不易,坚持更难,右上角点 ⭐Star 来支持/收藏下吧,谢谢 ^_^
+https://github.com/Tencent/APIJSON + +
+
+
+ +导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) 社区生态      完整详细的导航目录 [点这里查看](/Navigation.md)
+ +### 技术交流 +如果有什么问题或建议可以 [填问卷](https://wj.qq.com/s2/10971431/2a09) 或 [提 Issue](https://github.com/Tencent/APIJSON/issues/36),交流技术,分享经验。
+如果你解决了某些 bug,或者新增了一些功能,欢迎 [贡献代码](https://github.com/Tencent/APIJSON/pulls),感激不尽~
+https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md + +**开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲会友等,也有心情不好和身体病痛,**
+**往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~**
+**少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展!**
+ +**开发者时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
+**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!**
+**如果你已经多次得到帮助,却仍然只索取不贡献,那就别指望大家再帮你!**
+**私聊作者请教技术问题 或者 频繁在互助群 @ 作者 可能会被拉黑/禁言/踢群,请尊重和理解,谢谢!**
+ +如果你 [提 PR 登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加 **企业用户支持专群**,作者亲自且优先答疑,
+作者只有解答完了这个专群里的全部问题,才看情况解答 Issue/问卷 里的问题(对 Issue/问卷 不保证解答、更不保证及时);
+之前的几个互助群,由于大多数问题 在文档/Issue 已有答案却反复提 或者 缺少必要信息要来来回回沟通问清细节 已浪费太多时间,
+甚至有白嫖还把自己当大爷的自私自利伸手党输出情绪,我们不再支持,建议未登记企业的用户 [填问卷](https://wj.qq.com/s2/10971431/2a09) 或 [提 Issue](https://github.com/Tencent/APIJSON/issues/36)。
+ +如果你为 APIJSON 做出了以下任何一个贡献,我们将优先为你答疑解惑:
+[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson),
+Issue/问卷 一般解答顺序:贡献者 > 帮助他人的用户 > 提供任职企业的用户 > 其他用户。 + + +### 相关推荐 +[APIJSON, 接口和文档的终结者!](https://my.oschina.net/tommylemon/blog/805459) + +[腾讯业务百万数据 6s 响应,APIJSON 性能优化背后的故事](https://my.oschina.net/tommylemon/blog/5375645) + +[仿QQ空间和微信朋友圈,高解耦高复用高灵活](https://my.oschina.net/tommylemon/blog/885787) + +[后端开挂:3行代码写出8个接口!](https://my.oschina.net/tommylemon/blog/1574430) + +[后端自动化版本管理,再也不用改URL了!](https://my.oschina.net/tommylemon/blog/1576587) + +[3步创建APIJSON后端新表及配置](https://my.oschina.net/tommylemon/blog/889074) + +[APIJSON对接分布式HTAP数据库TiDB](https://my.oschina.net/tommylemon/blog/3081913) + +[APIJSON教程(一):上手apijson项目,学习apijson语法,并实现持久层配置](https://zhuanlan.zhihu.com/p/375681893) + +[apijson简单demo](https://blog.csdn.net/dmw412724/article/details/113558115) + +[apijson简单使用](https://www.cnblogs.com/greyzeng/p/14311995.html) + +[APIJSON简单部署和使用](https://blog.csdn.net/m450744192/article/details/108462611) + +[学习自动化接口APIJSON](https://www.jianshu.com/p/981a2a630c7b) + +[APIJSON 接口调试实践](https://github.com/Tencent/APIJSON/issues/189) + +[APIJSON-零代码接口和文档 JSON 协议 与 ORM 库](https://cloud.tencent.com/developer/article/2077042) + +[APIJSON使用例子总结](https://blog.csdn.net/weixin_41077841/article/details/110518007) + +[APIJSON 自动化接口和文档的快速开发神器 (一)](https://blog.csdn.net/qq_41829492/article/details/88670940) + +[APIJSON在mac电脑环境下配置去连接SQL Server](https://juejin.im/post/5e16d21ef265da3e2e4f4956) + +[APIJSON复杂业务深入实践(类似12306订票系统)](https://blog.csdn.net/aa330233789/article/details/105309571) + +[新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) + +[使用APIJSON写低代码Crud接口](https://blog.csdn.net/weixin_42375862/article/details/121654264) + +[apijson在同一个接口调用中 使用远程函数写入更新时间和创建时间](https://blog.csdn.net/qietingfengsong/article/details/124097229) + +[APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) + +[APIJSON 代码分析(三:demo主体代码)](https://blog.csdn.net/qq_50861917/article/details/120751630) + +[APIJSON 代码分析(二)AbstractParser类(解析器)](https://blog.csdn.net/weixin_45767055/article/details/120815927) + +[APIJSON 代码分析(四:AbstractObjectParser源码阅读)](https://blog.csdn.net/qq_50861917/article/details/120896381) + +[APIJSON 代码分析 AbstractSQLConfig 第二篇](https://blog.csdn.net/csascscascd/article/details/120684889) + +[APIJSON 代码分析(六)APIJSON—Verifier检查类](https://blog.csdn.net/weixin_45767055/article/details/121321731) + +[APIJSON 代码分析(四)AbstractSQLExecutor—SQL执行器](https://blog.csdn.net/weixin_45767055/article/details/121069887) + +[APIJSON使用](https://juejin.cn/post/7148253873478565902) + +[apijson 初探](https://www.cnblogs.com/x3d/p/apijson-lowcode.html) + +[APIJSON使用介绍](http://api.flyrise.cn:9099/docs/open-docs//1459) + +[MassCMS With APIJSON最佳实践](https://zhuanlan.zhihu.com/p/655826966) + +[APIJSON语法使用,超详细](https://blog.csdn.net/qq_36565607/article/details/139167040) + +[wend看源码-ORM-APIJSON](https://itwend.blog.csdn.net/article/details/143980281) + +[APIJSON – The No-Code API Revolution That Puts Developers in the Fast Lane](https://medevel.com/apijson) + +[APIJSON:17.4k Star!腾讯开源的零代码接口与文档协议及ORM库](https://mp.weixin.qq.com/s/gr84DmWKs4O6lcoT-iaV5w) + +[APIJSON腾讯开源的后端开发神器!!!](https://cloud.tencent.com/developer/article/2372220) + +[apijson 快速上手](https://blog.csdn.net/qq_16381291/article/details/147110737) + +[APIJSON快速入门-零后端代码,接口所见即所得](https://www.toutiao.com/article/7503844050689376783) + +[腾讯开源!零代码,全自动万能API接口](https://mp.weixin.qq.com/s/WWndAa68BqBfflWgL5592A) + +[APIJSON项目实战教程:零代码实现高效JSON接口开发](https://blog.csdn.net/gitblog_00682/article/details/148375065) + +[springboot整合APIJSON——零代码万能通用 API(附源码)](https://blog.csdn.net/longzhutengyue/article/details/150579233) + +[APIJSON:重新定义后端开发体验的零代码ORM框架](https://blog.csdn.net/gitblog_01177/article/details/155216163) + +[API自动生成这么爽?实测腾讯APIJSON,零代码就能玩转后端接口!](https://mp.weixin.qq.com/s/DmMIGHHcZ783KobGecMxGg) + +[腾讯开源的 APIJSON:后端接口不用写了?](https://mp.weixin.qq.com/s/zhkfG4AQEsg0N87lhStwvw) + +[3分钟掌握APIJSON搜索黑科技:从模糊匹配到智能检索](https://blog.csdn.net/gitblog_00009/article/details/152403741) + +### 生态项目 +[APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 + +[apijson-orm](https://github.com/APIJSON/apijson-orm) APIJSON ORM 库,可通过 Maven, Gradle 等远程依赖 + +[apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,通过数据库表配置角色权限、参数校验等,简化使用 + +[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,可控地对公网暴露类 RESTful 简单接口,内部转成 APIJSON 格式请求来执行 + +[apijson-column](https://github.com/APIJSON/apijson-column) APIJSON 的字段插件,支持 字段名映射 和 !key 反选字段 + +[apijson-jackson](https://github.com/APIJSON/apijson-jackson) APIJSON 的 jackson 插件,简化使用 + +[apijson-fastjson2](https://github.com/APIJSON/apijson-fastjson2) APIJSON 的 fastjson2 插件,简化使用 + +[apijson-gson](https://github.com/APIJSON/apijson-gson) APIJSON 的 gson 插件,简化使用 + +[apijson-milvus](https://github.com/APIJSON/apijson-milvus) APIJSON 的 Milvus AI 向量数据库插件 + +[apijson-influxdb](https://github.com/APIJSON/apijson-influxdb) APIJSON 的 InfluxDB 物联网时序数据库插件 + +[apijson-mongodb](https://github.com/APIJSON/apijson-mongodb) APIJSON 的 MongoDB NoSQL 数据库插件 + +[apijson-cassandra](https://github.com/APIJSON/apijson-cassandra) APIJSON 的 Cassandra NoSQL 数据库插件 + +[APIAuto](https://github.com/TommyLemon/APIAuto) ☔ 敏捷开发最强大易用的接口工具,零代码测试与 AI 问答、生成代码与静态检查、生成文档与光标悬浮注释,腾讯、SHEIN、传音 等使用 + +[CVAuto](https://github.com/TommyLemon/CVAuto) 👁 零代码零标注 CV AI 自动化测试平台 🚀 免除大量人工画框和打标签等,直接快速测试 CV 计算机视觉 AI 图像识别算法 + +[UnitAuto](https://github.com/TommyLemon/UnitAuto) 最先进、最省事、ROI 最高的单元测试,机器学习 零代码、全方位、自动化 测试 方法/函数,用户包含腾讯、快手、某 500 强巨头等 + +[SQLAuto](https://github.com/TommyLemon/SQLAuto) 智能零代码自动化测试 SQL 数据库工具,任意增删改查、任意 SQL 模板变量、一键批量生成参数组合、快速构造大量测试数据 + +[UIGO](https://github.com/TommyLemon/UIGO) 📱 零代码快准稳 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.NET](https://github.com/liaozb/APIJSON.NET) C# 版 APIJSON ,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite + +[apijson-go](https://github.com/glennliao/apijson-go) Go 版 APIJSON , 基于Go(>=1.18) + GoFrame2, 支持查询、单表增删改、权限管理等 + +[apijson-go](https://gitee.com/tiangao/apijson-go) Go 版 APIJSON ,支持单表查询、数组查询、多表一对一关联查询、多表一对多关联查询 等 + +[apijson-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL + +[APIJSON-php](https://github.com/xianglong111/APIJSON-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 + +[apijson-php](https://github.com/qq547057827/apijson-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 + +[apijson-node](https://github.com/kevinaskin/apijson-node) 字节跳动工程师开源的 Node.ts 版 APIJSON,提供 nestjs 和 typeorm 的 Demo 及后台管理 + +[uliweb-apijson](https://github.com/zhangchunlin/uliweb-apijson) Python 版 APIJSON,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite 等 + +[apijson-rust](https://gitee.com/APIJSON/panda-base) APIJSON 的 Rust 版,一个优雅、高性能的 Rust 多数据源管理系统,支持 MySQL 和 PostgreSQL + +[APIJSONParser](https://github.com/Zerounary/APIJSONParser) 第三方 APIJSON 解析器,将 JSON 动态解析成 SQL + +[FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 + +[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 + +[apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 + +[apijson-practice](https://github.com/vcoolwind/apijson-practice) BAT 技术专家开源的 APIJSON 参数校验注解 Library 及相关 Demo + +[apijson-db2](https://github.com/andream7/apijson-db2) 微软工程师接入 IBM 数据库 DB2 的 APIJSON 使用 Demo + +[APIJSONDemo](https://github.com/qiujunlin/APIJSONDemo) 字节跳动工程师接入 ClickHouse 的 APIJSON 使用 Demo + +[APIJSONDemo_ClickHouse](https://github.com/chenyanlann/APIJSONDemo_ClickHouse) APIJSON + SpringBoot 连接 ClickHouse 使用的 Demo + +[APIJSONBoot_Hive](https://github.com/chenyanlann/APIJSONBoot_Hive) APIJSON + SpringBoot 连接 Hive 使用的 Demo + +[apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 + +[apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo + +[apijson-ruoyi](https://github.com/daodol/apijson-ruoyi) APIJSON 和 RuoYi 框架整合,实现零代码生成页面模板接口,在线维护 APIJSON 数据库配置等 + +[light4j](https://github.com/xlongwei/light4j) 整合 APIJSON 和微服务框架 light-4j 的 Demo,同时接入了 Redis + +[SpringServer1.2-APIJSON](https://github.com/Airforce-1/SpringServer1.2-APIJSON) 智慧党建服务器端,提供 上传 和 下载 文件的接口 + +[apijson_template](https://github.com/abliger/apijson_template) APIJSON Java 模版,使用 gradle 管理依赖和构建应用 + +[api-json-demo](https://gitee.com/hxdwd/api-json-demo) 基于 APIJSON,实现低代码写 CURD 代码,代替传统 ORM 框架,适配 Oracle 事务 + +[ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo + +[apijson-go-demo](https://github.com/glennliao/apijson-go-demo) apijson-go demos,提供 3 个从简单到复杂的不同场景 Demo + +[apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库 + +[apijson-go-ui](https://github.com/glennliao/apijson-go-ui) apijson-go UI 界面配置, 支持权限管理、请求规则配置等 + +[AbsGrade](https://github.com/APIJSON/AbsGrade) 列表级联算法,支持微信朋友圈单层评论、QQ空间双层评论、百度网盘多层(无限层)文件夹等 + +[APIJSON-Android-RxJava](https://github.com/TommyLemon/APIJSON-Android-RxJava) 仿微信朋友圈动态实战项目,ZBLibrary(UI) + APIJSON(HTTP) + RxJava(Data) + +[Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP 快速开发框架,Demo 全面,注释详细,使用简单,代码严谨 + +[apijson-dynamic-datasource](https://github.com/wb04307201/apijson-dynamic-datasource) 基于APIJSON,动态切换数据源、同一数据源批量操作事务一致性DEMO + +[xyerp](https://gitee.com/yinjg1997/xyerp) 基于ApiJson的低代码ERP + +[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 编写,调整了一些参数和使用方式 + +[lanmuc](https://gitee.com/element-admin/lanmuc) 后端低代码生产接口的平台,兼容配置式接口和编写式接口,可做到快速生产接口,上线项目 + +[review_plan](https://gitee.com/PPXcodeTry/review_plan) 复习提醒Web版(Java技术练习项目) + +[apijson-nutz](https://github.com/vincent109/apijson-nutz) APIJSON + Nutz 框架 + NutzBoot 的 Demo + +[apijson-spring-boot](https://gitee.com/yunjiao-source/apijson-spring-boot) Springboot3 for APIJSON,用 YAML 简化代码配置 + +感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧~ + + +### 腾讯犀牛鸟开源人才培养计划 +https://github.com/Tencent/APIJSON/issues/229 + + +#### qiujunlin **2.接入 presto/hive/clickhouse/db2 任意一个** + +APIJSON 接入 clickhouse 使用demo
+https://github.com/qiujunlin/APIJSONDemo + +#### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 +APIJSON-Demo接入db2
+https://github.com/andream7/apijson-db2 + +#### hanxu 1.完善入门介绍视频 +重构 APIJSON 文档
+https://hanxu2018.github.io/APIJSON-DOC/
+文档源码
+https://github.com/HANXU2018/APIJSON-DOC
+配套评论区 apijson-doc-Comment
+https://github.com/HANXU2018/apijson-doc-Comment + +#### chenyanlan 2.接入 presto/hive/clickhouse/db2 任意一个 +APIJSON + SpringBoot连接ClickHouse使用的Demo
+https://github.com/chenyanlann/APIJSONDemo_ClickHouse + +#### zhaoqiming 1.完善入门介绍视频 +APIJSON 后端教程(1):简介 +https://www.bilibili.com/video/BV1vL411W7yd + +APIJSON 后端教程(2):数据库 +https://www.bilibili.com/video/BV1eB4y1N77s + +APIJSON 后端教程(3):Demo +https://www.bilibili.com/video/BV1FX4y1c7ug + +APIJSON 后端教程(4):Boot +https://www.bilibili.com/video/BV18h411z7FK + +APIJSON 后端教程(5):Final +https://www.bilibili.com/video/BV1GM4y1N7XJ + +APIJSON 后端教程(6):uliweb_apijson +https://www.bilibili.com/video/BV1yb4y1S79v/ + +APIJSON 后端教程(7):问题答疑 +https://www.bilibili.com/video/BV1dQ4y1h7Df + +APIJSON配套文档: +https://github.com/kenlig/apijsondocs + +#### huwen 2.接入 presto/hive/clickhouse/db2 任意一个 +APIJSON-Demo 接入presto +https://github.com/hclown9804/APIJSONDemo_presto + +#### zhanghaoling 1.完善入门介绍视频 +APIJSON结合已有项目,简化开发流程 +https://github.com/haolingzhang1/APIJson--demo + +说明文档 +https://github.com/haolingzhang1/APIJson--demo/tree/main/APIJson集成项目说明 + +(1)官方demo +https://github.com/haolingzhang1/APIJson--demo/blob/main/APIJson集成项目说明/APIJson集成现有项目(1)-%20官方demo.pdf + +(2)单表配置 +https://github.com/haolingzhang1/APIJson--demo/blob/main/APIJson集成项目说明/APIJson集成现有项目(2)-%20单表配置.pdf + +#### zhoukaile 1.完善入门介绍视频 + +视频链接:https://www.bilibili.com/video/BV1Uh411z7kZ/ + +文档链接:https://gitee.com/funkiz/apijson_camp + +#### lintao 1.完善入门介绍视频 + +APIJSON 上手教程:https://www.bilibili.com/video/BV1Pq4y1n7rJ + +### 持续更新 + +https://github.com/Tencent/APIJSON/commits/master + +### 工蜂主页 +https://git.code.tencent.com/Tencent_Open_Source/APIJSON + +### 码云主页 +https://gitee.com/Tencent/APIJSON diff --git a/README-English.md b/README-English.md deleted file mode 100644 index e7c9e6d04..000000000 --- a/README-English.md +++ /dev/null @@ -1,393 +0,0 @@ -Tencent is pleased to support the open source community by making APIJSON available.
-Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
-This source code is licensed under the Apache License Version 2.0
- -

- APIJSON -

- - -

🏆 Tencent Top 6 Open Source Project, Achieved 5 Awards Inside & Outside Tencent 🚀
A JSON Transmission Protocol and an ORM Library for providing APIs and Documents without writing any code.

- -

-  中文版  -  Document  -  Video  -  Test  - Ask AI -

-

- - - - - - - - - - - - - - - - - - - - - -

-

- - - - - - - - -

-

- - - -

-

- - - -

- -

- -

- ---- - -#### A better online document is available at https://apijsondocs.readthedocs.io/ - -* ### [1.About](#1) -* ### [2.Backend usage](#2) -* ### [3.Frontend usage](#3) -* ### [4.Contributing](#4) -* ### [5.Releases](#5) -* ### [6.Author](#6) -* ### [7.Donating](#7) - -
- -##

1. About

- -APIJSON is a JSON based internet communication protocol and an ORM library
-that largely simplifies the process of back-end API development.
-It also allows users to get data more quickly with self-defined form and fewer endpoints requests. - -### Features: -#### For getting data: -You can get any data by defining the specific information you want and send it to the server.
-You can get different types of data by making just one request to the server.
-It's very convenient and flexible, and dosen't require different API endpoints with multiple requests.
-It provides CRUD(read and write), Fuzzy Search, Remote Function Calls, etc.
-You can also save duplicate data, see request history, etc.
- -#### For API design: -APIJSON largely reduces API developers' workload by reducing most api design and documentation work.
-With APIJSON, client developers will no longer be suffered from possible errors in documents,
-and it saves communication between server developers and client developers about APIs or documentations.
-Server developers no longer need to worry about compatibility of APIs and documents with legacy apps.
- -![0F85206E116CCEE74DB68E5B9A3AEDAE](https://user-images.githubusercontent.com/5738175/196148099-d3a9e0ba-93e5-4e1a-a4f8-a714083c6f7e.jpg) -#### Song Firework-Katy Parry(Modified for APIJSON) -Do you ever feel like a backend slave
-Repeating CRUD, wanting to make a change?
-Do you ever feel, APIs' so paper thin
-Like a house of cards, one blow from cavin' in?
-Do you ever feel they always complain?
-Urging doc and feedback bugs, even ask your refactoring
-Do you know that there's still a chance for you?
-'Cause there's a powerful tool
-You just gotta depend and configure
-And let it init
-Just start APIs
-They are so easy to try
-'Cause baby, you're a firework
-Come on, show 'em what you're worth
-Make 'em go, "Oh, oh, oh"
-As you give 'em an A-T-M
-Baby, you're a firework
-Come on, let them serve themselves
-Make 'em go, "Oh, oh, oh"
-You're gonna leave 'em all in awe, awe, awe.
- -
- -**Tired with endless arguments about HTTP API dev or use?**
-**Use APIJSON-the ORM for providing infinity codeless CRUD APIs that fit almost all your needs.**
-**Unfold the Power(In Your Soul) with ⭐Star & Clone.** - -### APIJSON Show -#### Postman test APIJSON -![](https://static.oschina.net/uploads/img/201711/12230359_f7fQ.jpg) -
- -#### APIAuto test APIJSON -Note: The UI is APIAuto, the URL+JSON is APIJSON
-
-

- APIJSON: query multi related tables, flexible data structures. APIAuto: multi test accounts, easily share test cases -

- -![](https://oscimg.oschina.net/oscnet/up-bbbec4fc5edc472be127c02a4f3cd8f4ec2.JPEG) -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_associate.gif) - -
-

- APIAuto: generate request code for frontend/clients or Python test cases, easily download code -

- -![](https://oscimg.oschina.net/oscnet/up-637193bbd89b41c3264827786319e842aee.JPEG) - -
-

- APIAuto: auto save test records, auto generate API doc, requests shortcut, easily replay -

- -![](https://oscimg.oschina.net/oscnet/up-7dcb4ae71bd3892a909e4ffa37ba7c1d92a.JPEG) - -
-

- APIAuto: auto regression test without code, annotation, comment, etc.) -

- -![](https://oscimg.oschina.net/oscnet/up-c1ba774f8e7fcc5adcdb05cad5bd414d766.JPEG) - -
-

- A picture is worth a thousand words - some basic features show for APIJSON -

- -![](https://oscimg.oschina.net/oscnet/up-e21240ef3770326ee6015e052226d0da184.JPEG) -![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif) - - -##

2.Backend usage

-You can skip this step and use 'apijson.cn:8080'.
-See https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/README-English.md - -
- -##

3. Frontend usage

-You can skip this step and use [APIAuto](https://github.com/TommyLemon/APIAuto) or download App.
-See [Android](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Android/README-English.md), [iOS](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-iOS/README-English.md) or [JavaScript](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-JavaScript/README-English.md)
- -### Download App - -Simple demo App for testing APIJSON
-[APIJSONTest.apk](http://files.cnblogs.com/files/tommylemon/APIJSONTest.apk) - -Complex production App like Twitter tweets
-[APIJSONApp.apk](http://files.cnblogs.com/files/tommylemon/APIJSONApp.apk) - -
- -##

4. Contributing

- -We are always looking for more developers to help implementing new features, fix bugs, etc.
-Please have a look at the [open issues](https://github.com/Tencent/APIJSON/issues) before opening a new one.
- -Fork the project and send a pull request.
- -Please also ⭐Star the project! -
- -##

5. Releases

- -See the latest release [here](https://github.com/Tencent/APIJSON/releases/tag/5.2.0). - -
- -##

6. Author

- -Check out the author's [github account](https://github.com/TommyLemon) to see more related projects.
-image - -If you have any questions or suggestions, you can [create an issue](https://github.com/Tencent/APIJSON/issues) or [send me an e-mail](mailto:tommylemon@qq.com). - -
-
- -### Users of APIJSON: - -https://github.com/Tencent/APIJSON/issues/187 -
- - -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) - -### Contributers of APIJSON: -Contributers for the APIJSON core project(6 Tencent engineers, 1 Microsoft engineer, 1 Zhihu architect, 1 Bytedance(TikTok) engineer, 1 NetEase engineer, 1 Zoom engineer, 1 YTO Express engineer, 1 Zhilian engineer, 1 UC student、3 SUSTech students, etc.):
-https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -Authors of other projects for ecosystem of APIJSON(2 Tencent engineers, 1 BAT(Baidu/Alibaba/Tencent) specialist, 1 Microsoft engineer, 2 Bytedance(TikTok) engineers, 1 Digital China engineer & Apache dubbo2js author, etc.):
-https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
-https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -Thanks to all contributers of APIJSON! - -
- -### Statistics -Hundreds of employees from big famous companies(Tencent, Google, Apple, Microsoft, Amazon, Huawei, Alibaba, Paypal, Meituan, Bytedance(TikTok), IBM, Baidu, JD, NetEase, Kuaishou, Shopee, etc.) starred,
-a lot of employees from big famous companies(Tencent, Huawei, Microsoft, Zoom, etc.) created PR/Issue, thank you all~
-[![Stargazers over time](https://starchart.cc/Tencent/APIJSON.svg)](https://starchart.cc/Tencent/APIJSON) -image -image -image - - -
diff --git a/README.md b/README.md index edc821009..b4a56e517 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,22 @@ Tencent is pleased to support the open source community by making APIJSON available.
-Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
+Copyright (C) 2020 Tencent. All rights reserved.
This source code is licensed under the Apache License Version 2.0

APIJSON

-

🏆 实时 零代码、全功能、强安全 ORM 库 🚀
后端接口和文档零代码,前端(客户端) 定制返回 JSON 的数据和结构

+ +

🏆 Real-Time no-code, powerful and secure ORM 🚀
providing APIs and Docs without coding by Backend, and Frontend can customize response JSONs

- English - 通用文档 - 视频教程 - 测试用例 - AI 问答 +  中文版  +  Document  +  Video  +  Test  + Ask AI

+

@@ -50,6 +52,7 @@ This source code is licensed under the Apache License Version 2.0
+

@@ -59,6 +62,7 @@ This source code is licensed under the Apache License Version 2.0
+

@@ -66,6 +70,7 @@ This source code is licensed under the Apache License Version 2.0
+

@@ -79,48 +84,75 @@ This source code is licensed under the Apache License Version 2.0
--- -导航目录: 项目简介 [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)      完整详细的导航目录 [点这里查看](/Navigation.md)
- - -APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这套协议实现的 ORM 库。
-为各种增删改查提供了完全自动化的万能通用接口,零代码实时满足千变万化的各种新增和变更需求。
-能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
-适合中小型前后端分离的项目,尤其是 创业项目、内部项目、低代码/零代码、小程序、BaaS、Serverless 等。
- -通过万能通用接口,前端可以定制任何数据、任何结构。
-大部分 HTTP 请求后端再也不用写接口了,更不用写文档了。
-前端再也不用和后端沟通接口或文档问题了。再也不会被文档各种错误坑了。
-后端再也不用为了兼容旧接口写新版接口和文档了。再也不会被前端随时随地没完没了地烦了。 +* ### [1.About](#1) +* ### [2.Backend usage](#2) +* ### [3.Frontend usage](#3) +* ### [4.Contributing](#4) +* ### [5.Releases](#5) +* ### [6.Creator](#6) +* ### [7.Donating](#7) -### 特点功能 +
-#### 对于后端 -* 提供万能通用接口,大部分 HTTP API 不用再写 -* 零代码增删改查、各种跨库连表、JOIN 嵌套子查询等 -* 自动生成文档,不用再编写和维护,且自动静态检查 -* 自动校验权限、自动管理版本、自动防 SQL 注入 -* 开放 HTTP API 无需划分版本,始终保持兼容 +##

1. About

+ +APIJSON is a JSON based internet communication protocol and an ORM library
+that largely simplifies the process of back-end API development.
+It also allows users to get data more quickly with self-defined form and fewer endpoints requests. + +### Features: +#### For getting data: +You can get any data by defining the specific information you want and send it to the server.
+You can get different types of data by making just one request to the server.
+It's very convenient and flexible, and dosen't require different API endpoints with multiple requests.
+It provides CRUD(read and write), Fuzzy Search, Remote Function Calls, etc.
+You can also save duplicate data, see request history, etc.
+ +#### For API design: +APIJSON largely reduces API developers' workload by reducing most api design and documentation work.
+With APIJSON, client developers will no longer be suffered from possible errors in documents,
+and it saves communication between server developers and client developers about APIs or documentations.
+Server developers no longer need to worry about compatibility of APIs and documents with legacy apps.
+ +![0F85206E116CCEE74DB68E5B9A3AEDAE](https://user-images.githubusercontent.com/5738175/196148099-d3a9e0ba-93e5-4e1a-a4f8-a714083c6f7e.jpg) +#### Song Firework-Katy Parry(Modified for APIJSON) +Do you ever feel like a backend slave
+Repeating CRUD, wanting to make a change?
+Do you ever feel, APIs' so paper thin
+Like a house of cards, one blow from cavin' in?
+Do you ever feel they always complain?
+Urging doc and feedback bugs, even ask your refactoring
+Do you know that there's still a chance for you?
+'Cause there's a powerful tool
+You just gotta depend and configure
+And let it init
+Just start APIs
+They are so easy to try
+'Cause baby, you're a firework
+Come on, show 'em what you're worth
+Make 'em go, "Oh, oh, oh"
+As you give 'em an A-T-M
+Baby, you're a firework
+Come on, let them serve themselves
+Make 'em go, "Oh, oh, oh"
+You're gonna leave 'em all in awe, awe, awe.
-#### 对于前端 -* 不用再向后端催接口、求文档 -* 数据和结构完全定制,要啥有啥 -* 看请求知结果,所求即所得 -* 可一次获取任何数据、任何结构 -* 能去除多余数据,节省流量提高速度 +
-
+**Tired with endless arguments about HTTP API dev or use?**
+**Use APIJSON-the ORM for providing infinity codeless CRUD APIs that fit almost all your needs.**
+**Unfold the Power(In Your Soul) with ⭐Star & Clone.** -### APIJSON 接口展示 -#### Postman 展示 APIJSON +### APIJSON Show +#### Postman test APIJSON ![](https://static.oschina.net/uploads/img/201711/12230359_f7fQ.jpg)
-#### APIAuto 展示 APIJSON -**使用 APIAuto-机器学习接口工具 来管理和测试 HTTP API 可大幅 减少传参错误、提升联调效率**
-(注意网页工具界面是 APIAuto,里面的 URL+JSON 才是 APIJSON 的 HTTP API):
+#### APIAuto test APIJSON +Note: The UI is APIAuto, the URL+JSON is APIJSON

- APIJSON 多表关联查询、结构自由组合,APIAuto 多个测试账号、一键共享测试用例 + APIJSON: query multi related tables, flexible data structures. APIAuto: multi test accounts, easily share test cases

![](https://oscimg.oschina.net/oscnet/up-bbbec4fc5edc472be127c02a4f3cd8f4ec2.JPEG) @@ -128,171 +160,82 @@ APIJSON 是一种专为 API 而生的 JSON 网络传输协议 以及 基于这

- APIAuto 自动生成前端(客户端)请求代码 和 Python 测试用例代码,一键下载 + APIAuto: generate request code for frontend/clients or Python test cases, easily download code

![](https://oscimg.oschina.net/oscnet/up-637193bbd89b41c3264827786319e842aee.JPEG)

- APIAuto 自动保存请求记录、自动生成接口文档,可添加常用请求、快捷查看一键恢复 + APIAuto: auto save test records, auto generate API doc, requests shortcut, easily replay

![](https://oscimg.oschina.net/oscnet/up-7dcb4ae71bd3892a909e4ffa37ba7c1d92a.JPEG)

- APIAuto 一键自动接口回归测试,不需要写任何代码(注解、注释等全都不要) + APIAuto: auto regression test without code, annotation, comment, etc.)

![](https://oscimg.oschina.net/oscnet/up-c1ba774f8e7fcc5adcdb05cad5bd414d766.JPEG)

- 一图胜千言 - APIJSON 部分基础功能概览 + A picture is worth a thousand words - some basic features show for APIJSON

![](https://oscimg.oschina.net/oscnet/up-e21240ef3770326ee6015e052226d0da184.JPEG) ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON/APIJSON_query_summary.gif) -

- -### APIJSON App 演示 -使用 APIJSON + ZBLibrary 开发的 Android 客户端 Demo (以下 Gif 图看起来比较卡,实际上运行很流畅): -
-![](https://oscimg.oschina.net/oscnet/up-a3f167e593080e8a3fc09c3d5fc09330c98.gif) -![](https://oscimg.oschina.net/oscnet/up-141abcb5dabc01c890d70c461bd1fdc751f.gif) -![](https://oscimg.oschina.net/oscnet/up-58aecc2701c2c4ea33e53f246e427773b09.gif) +##

2.Backend usage

+You can skip this step and use 'apijson.cn:8080'.
+See https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/README-English.md
-### APIJSON 分享演讲 -#### APIJSON-零代码接口与文档 ORM 库(国际开源谷 Gitee Meetup) +##

3. Frontend usage

+You can skip this step and use [APIAuto](https://github.com/TommyLemon/APIAuto) or download App.
+See [Android](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Android/README-English.md), [iOS](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-iOS/README-English.md) or [JavaScript](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-JavaScript/README-English.md)
-https://www.bilibili.com/video/BV1Tv411t74v - -![image](http://apijson.cn/images/comparison/APIJSON_vs_PreviousWays.jpg) - - -#### APIJSON 和 APIAuto-零代码开发和测试(QECon 全球软件质量&效能大会) - -https://www.bilibili.com/video/BV1yv411p7Y4 - -wecom-temp-377bbd0daf5aed716baf7ebcb003d94c +### Download App +Simple demo App for testing APIJSON
+[APIJSONTest.apk](http://files.cnblogs.com/files/tommylemon/APIJSONTest.apk) + +Complex production App like Twitter tweets
+[APIJSONApp.apk](http://files.cnblogs.com/files/tommylemon/APIJSONApp.apk)
+ +##

4. Contributing

+ +We are always looking for more developers to help implementing new features, fix bugs, etc.
+Please have a look at the [open issues](https://github.com/Tencent/APIJSON/issues) before opening a new one.
-### 为什么选择 APIJSON? -前后端 关于接口的 开发、文档、联调 等 10 大痛点解析
-https://github.com/Tencent/APIJSON/wiki - -* **解决十大痛点** (可帮前后端开发大幅提振开发效率、强力杜绝联调扯皮、巧妙规避文档缺陷、非常节省流量带宽) -* **开发提速很大** (CRUD 零代码热更新全自动,APIJSONBoot 对比 SSM、SSH 等保守估计可提速 20 倍以上) -* **腾讯官方开源** (使用 GitHub、Gitee、工蜂 等平台的官方账号开源,微信公众号、腾讯云+社区 等官方公告) -* **社区影响力大** (GitHub 17K+ Star 在 400W Java 项目排名前 100,远超 FLAG, BAT 等国内外绝大部分开源项目) -* **各项荣誉成就** (腾讯内外 5 个奖项、腾讯开源前五、腾讯后端 Star 第一、Trending 日周月榜大满贯 等) -* **多样用户案例** (腾讯内有互娱、音乐、微信、云与智慧,外部有华为、华能、百度、快手、中兴、圆通、传音等) -* **适用场景广泛** (社交聊天、阅读资讯、影音娱乐、办公学习 等各种 App、网站、小程序、公众号 等非金融类项目) -* **周边生态丰富** (Android, iOS, Web 等各种 Demo、继承 JSON 的海量生态、零代码 接口测试 和 单元测试 工具等) -* **文档视频齐全** (项目介绍、快速上手、安装部署 等后端、前端、客户端的 图文解说、视频教程、代码注释 等) -* **功能丰富强大** (增删改查、分页排序、分组聚合、各种条件、各种 JOIN、各种子查询、跨库连表 等零代码实现) -* **使用安全简单** (自动增删改查、自动生成文档、自动管理版本、自动控制权限、自动校验参数、自动防 SQL 注入) -* **灵活定制业务** (在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象 等,然后自定义处理) -* **高质可靠代码** (代码严谨规范,蚂蚁集团源伞 Pinpoint 代码扫描分析报告平均每行代码 Bug 率低至 0.15%) -* **兼容各种项目** (协议不限 HTTP,与其它库无冲突,对各类 Web 框架集成友好且提供 SpringBoot, JFinal 的示例) -* **工程轻量小巧** (仅依赖 fastjson,Jar 仅 280KB,Java 文件仅 59 个共 13719 行代码,例如 APIJSONORM 4.3.1) -* **多年持续迭代** (自 2016 年起已连续维护 8 年多,70+ 贡献者、100+ 发版、3000+ 提交,不断更新迭代中...) - -**按照一般互联网中小型项目情况可得出以下对比表格:** - -表数量 T | 平均每表字段数 C | SSMH 按快估计 | APIJSONBoot 按慢估计 | APIJSONBoot 提速倍数 --------- | --------- | --------------------- | ------------------ | ----------- -1 | 3 | 179 min(约一上午) | 11 min(约十分钟) | 15.27 -5 | 4 | 1935 min(约朝九晚六一周) | 70 min(约一小时) | 26.64 -10 | 10 | 8550 min(大小周超半个月) | 320 min(约一下午) | 25.72 -20 | 15 | 31900 min(约 996 两个月) | 940 min(约上班两天) | 32.94 -50 | 20 | 176750 min(11117 超半年) | 3100 min(约上班一周) | 56.02 - -### 用户反馈 -**腾讯 IEG 数据产品开发组负责人 xinlin:** -“腾讯的 APIJSON 开源方案,它可以做到零代码生成接口和文档,并且整个生成过程是自动化。当企业有元数据的时候,马上就可以获得接口” - -**腾讯科技 后台开发高级工程师 雷大锤:** -“可以抽出时间来看apijson了,这个可以为T10做准备,也是业界很火的东西,可以提升个人影响力!” - -**腾讯 bodian520:** -“在调试GET、POST、PUT接口时遇到了一些问题,把个人的摸索经验分享一下,希望作者能梳理下文档,方便我们更好的接入” - -**华为 minshiwu:** -“demo工程,默认使用apijson-framework,可以做到无任何配置即可体验apijson的各种能力。” - -**字节跳动 qiujunlin:** -“初次见到这个项目,觉得太惊艳了,眼前一亮。给我的感受是,项目大大简化了开发流程,开发效率提升了很多倍。” - -**百度智慧城市研发 lpeng:** -“很兴奋的发现APIJSON很适合我们的一个开发场景,作为我们协议定义的一部分” - -**中兴 duyijiang:** -“感谢腾讯大大提供的框架,很好用” - -https://github.com/Tencent/APIJSON/issues/132#issuecomment-1106669540 +Fork the project and send a pull request.
+Please also ⭐Star the project!
-### 常见问题 -#### 1.如何定制业务逻辑? -在后端编写 远程函数,可以拿到 session、version、当前 JSON 对象、参数名称 等,然后对查到的数据自定义处理
-https://github.com/Tencent/APIJSON/issues/101 +##

5. Releases

+ +See the latest release [here](https://github.com/Tencent/APIJSON/releases) -#### 2.如何控制权限? -在 Access 表配置校验规则,默认不允许访问,需要对 每张表、每种角色、每种操作 做相应的配置,粒度细分到行级
-https://github.com/Tencent/APIJSON/issues/12 +
-#### 3.如何校验参数? -在 Request 表配置校验规则 structure,提供 MUST、TYPE、VERIFY 等通用方法,可通过 远程函数 来完全自定义
-https://github.com/Tencent/APIJSON/wiki#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86 +##

6. Creator

+ +https://github.com/TommyLemon
+Screenshot 2026-02-15 at 17 16 46 -更多常见问题及提问前必看
-https://github.com/Tencent/APIJSON/issues/36 -
+If you have any questions or suggestions, you can [create an issue](https://github.com/Tencent/APIJSON/issues) or [send me an e-mail](mailto:tommylemon@qq.com). -### 注意事项 -**请求参数 JSON 中表名、字段名、关键词及对应的值都是大小写敏感、逗号敏感、分号敏感、空格敏感、换行敏感,
-大部分情况都不允许空格和换行,表名以大写字母开头,不要想当然,请严格按照 [设计规范](https://github.com/Tencent/APIJSON/blob/master/Document.md#3) 来调用 API !** -[#181](https://github.com/Tencent/APIJSON/issues/181) -


-
- -导航目录: [项目简介](#--apijson) 上手使用 [社区生态](#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81)      完整详细的导航目录 [点这里查看](/Navigation.md)
- -### 快速上手 - -#### 1.后端上手 -可以跳过这个步骤,直接用APIJSON服务器IP地址 apijson.cn:8080 来测试接口。
-见  [APIJSON后端上手 - Java](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Java-Server)
- -#### 2.前端上手 -可以跳过这个步骤,直接使用 [APIAuto-机器学习HTTP接口工具](https://github.com/TommyLemon/APIAuto) 或 下载客户端App。
-见  [Android](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-Android)  或  [iOS](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-iOS)  或  [JavaScript](https://github.com/APIJSON/APIJSON-Demo/tree/master/APIJSON-JavaScript)
- - -### 下载客户端 App - -仿微信朋友圈动态实战项目
-[APIJSONApp.apk](http://files.cnblogs.com/files/tommylemon/APIJSONApp.apk) - -测试及自动生成代码工具
-[APIJSONTest.apk](http://files.cnblogs.com/files/tommylemon/APIJSONTest.apk) -### 开源许可 -使用 [Apache License 2.0](/LICENSE),对 公司、团队、个人 等 商用、非商用 都自由免费且非常友好,请放心使用和登记 +### Users of APIJSON: -### 使用登记 -如果您在使用 APIJSON,请让我们知道,您的使用对我们非常重要(新的按登记顺序排列、专群优先答疑解惑):
https://github.com/Tencent/APIJSON/issues/187
@@ -350,28 +293,13 @@ https://github.com/Tencent/APIJSON/issues/187 - +
- - * [腾讯科技有限公司](https://www.tencent.com) - * [腾讯音乐娱乐集团](https://www.tencentmusic.com) - * [深圳市传音通讯有限公司](https://www.transsion.com) - * [社宝信息科技(上海)有限公司](https://shebaochina.com) - * [华能贵诚信托有限公司](https://www.hngtrust.com) - * [投投科技](https://www.toutou.com.cn) - * [圆通速递](https://www.yto.net.cn) - * [乐拼科技](https://www.lepinyongche.com) - * [珠海采筑电子商务有限公司](https://www.aupup.com) - * [爱投斯智能技术(深圳)有限公司](http://www.aiotos.net) - * [邻盛科技(武汉)有限公司](http://www.linksame.com) - * [上海麦市信息科技有限公司](https://www.masscms.com) - * [上海翊丞互联网科技有限公司](http://www.renrencjl.com/home) - * [上海直真君智科技有限公司](http://www.zzjunzhi.com) - * [北明软件有限公司](https://www.bmsoft.com.cn/) - * [上海钰亿环保科技有限公司](#) - -### 贡献者们 -主项目 APIJSON 的贡献者们(6 个腾讯工程师、1 个微软工程师、1 个阿里云工程师、1 个字节跳动工程师、1 个网易工程师、1 个 Zoom 工程师、1 个圆通工程师、1 个知乎基础研发架构师、1 个智联招聘工程师、gorm-plus 作者、1 个美国加州大学学生、3 个 SUSTech 学生等):
+ +[More APIJSON Users](https://github.com/Tencent/APIJSON/issues/73) + +### Contributers of APIJSON: +Contributers for the APIJSON core project(6 Tencent engineers, 1 Microsoft engineer, 1 Zhihu architect, 1 Bytedance(TikTok) engineer, 1 NetEase engineer, 1 Zoom engineer, 1 YTO Express engineer, 1 Zhilian engineer, 1 UC student、3 SUSTech students, etc.):
https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md
- - - - - - - - - - - - - - - - - - -

- -生态周边项目的作者们(2 个腾讯工程师、1 个 BAT 技术专家、1 个微软工程师、2 个字节跳动工程师、1 个神州数码工程师&Apache dubbo2js 作者 等):
+ +Authors of other projects for ecosystem of APIJSON(2 Tencent engineers, 1 BAT(Baidu/Alibaba/Tencent) expert, 1 Microsoft engineer, 2 Bytedance(TikTok) engineers, 1 Digital China engineer & Apache dubbo2js author, etc.):
https://github.com/search?o=desc&q=apijson&s=stars&type=Repositories
https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count
@@ -479,357 +388,22 @@ https://search.gitee.com/?skin=rec&type=repository&q=apijson&sort=stars_count -

-还有为 APIJSON 扫描代码贡献 Issue 的 [蚂蚁集团源伞](https://www.sourcebrella.com) 和 [奇安信代码卫士](https://github.com/QiAnXinCodeSafe) -
- - -
-
+Thanks to all contributers of APIJSON! -感谢大家的贡献。 +
-### 统计分析 -腾讯、华为、阿里巴巴、美团、字节跳动、百度、京东、网易、快手等和 Google, Apple, Microsoft, Amazon, Paypal, IBM, Shopee 等
-数百名知名大厂员工点了 Star,也有腾讯、华为、字节跳动、Microsoft、Zoom 等不少知名大厂员工提了 PR/Issue,感谢大家的支持~
+### Statistics +Hundreds of employees from big famous companies(Tencent, Google, Apple, Microsoft, Amazon, Huawei, Alibaba, Paypal, Meituan, Bytedance(TikTok), IBM, Baidu, JD, NetEase, Kuaishou, Shopee, etc.) starred,
+a lot of employees from big famous companies(Tencent, Huawei, Microsoft, Zoom, etc.) created PR/Issue, thank you all~
[![Stargazers over time](https://starchart.cc/Tencent/APIJSON.svg)](https://starchart.cc/Tencent/APIJSON) image image image -根据开源指南针报告,APIJSON Java 版已经是国内顶级、国际一流的 Java 开源项目了 [#518](https://github.com/Tencent/APIJSON/issues/518)
-image - -### 规划及路线图 -新增功能、强化安全、提高性能、增强稳定、完善文档、丰富周边、推广使用
-https://github.com/Tencent/APIJSON/blob/master/Roadmap.md - -理论上所有支持 SQL 与 JDBC/ODBC 的软件,都可以用本项目对接 CRUD,待测试:
-[OceanBase](https://www.oceanbase.com/docs/oceanbase/V2.2.50/ss-sr-select_daur3l), [Spark](https://spark.apache.org/docs/3.3.0/sql-ref-syntax-qry-select.html)(可用 Hive 对接), [Phoenix](http://phoenix.apache.org/language/index.html#select)(延伸支持 HBase) -### 我要赞赏 -创作不易,坚持更难,右上角点 ⭐Star 来支持/收藏下吧,谢谢 ^_^
-https://github.com/Tencent/APIJSON -
-
-
- -导航目录: [项目简介](#--apijson) [上手使用](#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) 社区生态      完整详细的导航目录 [点这里查看](/Navigation.md)
- -### 技术交流 -如果有什么问题或建议可以 [填问卷](https://wj.qq.com/s2/10971431/2a09) 或 [提 Issue](https://github.com/Tencent/APIJSON/issues/36),交流技术,分享经验。
-如果你解决了某些 bug,或者新增了一些功能,欢迎 [贡献代码](https://github.com/Tencent/APIJSON/pulls),感激不尽~
-https://github.com/Tencent/APIJSON/blob/master/CONTRIBUTING.md - -**开发者也是人,也需要工作、休息、恋爱、陪伴家人、走亲会友等,也有心情不好和身体病痛,**
-**往往没有额外的时间精力顾及一些小问题,请理解和支持,开源要大家参与贡献才会更美好~**
-**少数个人的热情终有被耗尽的一天,只有大家共同建设和繁荣社区,才能让开源可持续发展!**
- -**开发者时间精力有限,原则上优先解决 登记用户 和 贡献者 的问题,**
-**不解决 文档/视频/常见问题 已明确说明、描述简陋 或 态度无礼 的问题!**
-**如果你已经多次得到帮助,却仍然只索取不贡献,那就别指望大家再帮你!**
-**私聊作者请教技术问题 或者 频繁在互助群 @ 作者 可能会被拉黑/禁言/踢群,请尊重和理解,谢谢!**
- -如果你 [提 PR 登记了自己使用 APIJSON 的公司](https://github.com/Tencent/APIJSON/issues/187),可以加 **企业用户支持专群**,作者亲自且优先答疑,
-作者只有解答完了这个专群里的全部问题,才看情况解答 Issue/问卷 里的问题(对 Issue/问卷 不保证解答、更不保证及时);
-之前的几个互助群,由于大多数问题 在文档/Issue 已有答案却反复提 或者 缺少必要信息要来来回回沟通问清细节 已浪费太多时间,
-甚至有白嫖还把自己当大爷的自私自利伸手党输出情绪,我们不再支持,建议未登记企业的用户 [填问卷](https://wj.qq.com/s2/10971431/2a09) 或 [提 Issue](https://github.com/Tencent/APIJSON/issues/36)。
- -如果你为 APIJSON 做出了以下任何一个贡献,我们将优先为你答疑解惑:
-[提交了 PR 且被合并](https://github.com/Tencent/APIJSON/pull/92)、[提交了优质 Issue](https://github.com/Tencent/APIJSON/issues/189)、[发表了优质文章](https://blog.csdn.net/qq_41829492/article/details/88670940)、[开发了可用的生态项目](https://github.com/zhangchunlin/uliweb-apijson),
-Issue/问卷 一般解答顺序:贡献者 > 帮助他人的用户 > 提供任职企业的用户 > 其他用户。 - - -### 相关推荐 -[APIJSON, 接口和文档的终结者!](https://my.oschina.net/tommylemon/blog/805459) - -[腾讯业务百万数据 6s 响应,APIJSON 性能优化背后的故事](https://my.oschina.net/tommylemon/blog/5375645) - -[仿QQ空间和微信朋友圈,高解耦高复用高灵活](https://my.oschina.net/tommylemon/blog/885787) - -[后端开挂:3行代码写出8个接口!](https://my.oschina.net/tommylemon/blog/1574430) - -[后端自动化版本管理,再也不用改URL了!](https://my.oschina.net/tommylemon/blog/1576587) - -[3步创建APIJSON后端新表及配置](https://my.oschina.net/tommylemon/blog/889074) - -[APIJSON对接分布式HTAP数据库TiDB](https://asktug.com/t/htap-tidb/395) - -[APIJSON教程(一):上手apijson项目,学习apijson语法,并实现持久层配置](https://zhuanlan.zhihu.com/p/375681893) - -[apijson简单demo](https://blog.csdn.net/dmw412724/article/details/113558115) - -[apijson简单使用](https://www.cnblogs.com/greyzeng/p/14311995.html) - -[APIJSON简单部署和使用](https://blog.csdn.net/m450744192/article/details/108462611) - -[学习自动化接口APIJSON](https://www.jianshu.com/p/981a2a630c7b) - -[APIJSON 接口调试实践](https://github.com/Tencent/APIJSON/issues/189) - -[关于APIJSON远程函数](https://mp.weixin.qq.com/s?__biz=Mzg3NTc1NDUyNA==&mid=2247483950&idx=1&sn=b11e70bdf083c55d72238e107449ae2e&chksm=cf3de75df84a6e4b3a4acd0846531b0bd12bc90379523fbaf6b4f900fc3cdc1b1ce3eff97fd9&scene=178&cur_album_id=2548737392338354178#rd) - -[APIJSON新增方法实例](https://cloud.tencent.com/developer/article/2098890) - -[APIJSON-APIJSON的那些事儿](https://cloud.tencent.com/developer/article/2098888) - -[APIJSON-零代码接口和文档 JSON 协议 与 ORM 库](https://cloud.tencent.com/developer/article/2077042) - -[APIJSON使用例子总结](https://blog.csdn.net/weixin_41077841/article/details/110518007) - -[APIJSON 自动化接口和文档的快速开发神器 (一)](https://blog.csdn.net/qq_41829492/article/details/88670940) - -[APIJSON在mac电脑环境下配置去连接SQL Server](https://juejin.im/post/5e16d21ef265da3e2e4f4956) - -[APIJSON复杂业务深入实践(类似12306订票系统)](https://blog.csdn.net/aa330233789/article/details/105309571) - -[全国行政区划数据抓取与处理](https://www.xlongwei.com/detail/21032616) - -[新手搭建 APIJSON 项目指北](https://github.com/jerrylususu/apijson_todo_demo/blob/master/FULLTEXT.md) - -[使用APIJSON写低代码Crud接口](https://blog.csdn.net/weixin_42375862/article/details/121654264) - -[apijson在同一个接口调用中 使用远程函数写入更新时间和创建时间](https://blog.csdn.net/qietingfengsong/article/details/124097229) - -[APIJSON(一:综述)](https://blog.csdn.net/qq_50861917/article/details/120556168) - -[APIJSON 代码分析(三:demo主体代码)](https://blog.csdn.net/qq_50861917/article/details/120751630) - -[APIJSON 代码分析(二)AbstractParser类(解析器)](https://blog.csdn.net/weixin_45767055/article/details/120815927) - -[APIJSON 代码分析(四:AbstractObjectParser源码阅读)](https://blog.csdn.net/qq_50861917/article/details/120896381) - -[APIJSON 代码分析 AbstractSQLConfig 第二篇](https://blog.csdn.net/csascscascd/article/details/120684889) - -[APIJSON 代码分析(六)APIJSON—Verifier检查类](https://blog.csdn.net/weixin_45767055/article/details/121321731) - -[APIJSON 代码分析(四)AbstractSQLExecutor—SQL执行器](https://blog.csdn.net/weixin_45767055/article/details/121069887) - -[APIJSON使用](https://juejin.cn/post/7148253873478565902) - -[apijson 初探](https://www.cnblogs.com/x3d/p/apijson-lowcode.html) - -[APIJSON使用介绍](http://api.flyrise.cn:9099/docs/open-docs//1459) - -[MassCMS With APIJSON最佳实践](https://zhuanlan.zhihu.com/p/655826966) - -[APIJSON语法使用,超详细](https://blog.csdn.net/qq_36565607/article/details/139167040) - -[wend看源码-ORM-APIJSON](https://itwend.blog.csdn.net/article/details/143980281) - -[APIJSON – The No-Code API Revolution That Puts Developers in the Fast Lane](https://medevel.com/apijson) - -[APIJSON:17.4k Star!腾讯开源的零代码接口与文档协议及ORM库](https://mp.weixin.qq.com/s/gr84DmWKs4O6lcoT-iaV5w) - -[APIJSON腾讯开源的后端开发神器!!!](https://cloud.tencent.com/developer/article/2372220) - -[apijson 快速上手](https://blog.csdn.net/qq_16381291/article/details/147110737) - -[APIJSON快速入门-零后端代码,接口所见即所得](https://www.toutiao.com/article/7503844050689376783) - -### 生态项目 -[APIJSON-Demo](https://github.com/APIJSON/APIJSON-Demo) APIJSON 各种语言、各种框架 的 使用示例项目、上手文档、测试数据 SQL 文件 等 - -[apijson-orm](https://github.com/APIJSON/apijson-orm) APIJSON ORM 库,可通过 Maven, Gradle 等远程依赖 - -[apijson-framework](https://github.com/APIJSON/apijson-framework) APIJSON 服务端框架,通过数据库表配置角色权限、参数校验等,简化使用 - -[apijson-router](https://github.com/APIJSON/apijson-router) APIJSON 的路由插件,可控地对公网暴露类 RESTful 简单接口,内部转成 APIJSON 格式请求来执行 - -[apijson-column](https://github.com/APIJSON/apijson-column) APIJSON 的字段插件,支持 字段名映射 和 !key 反选字段 - -[apijson-milvus](https://github.com/APIJSON/apijson-milvus) APIJSON 的 Milvus AI 向量数据库插件 - -[apijson-influxdb](https://github.com/APIJSON/apijson-influxdb) APIJSON 的 InfluxDB 物联网时序数据库插件 - -[apijson-mongodb](https://github.com/APIJSON/apijson-mongodb) APIJSON 的 MongoDB NoSQL 数据库插件 - -[apijson-cassandra](https://github.com/APIJSON/apijson-cassandra) APIJSON 的 Cassandra NoSQL 数据库插件 - -[APIAuto](https://github.com/TommyLemon/APIAuto) 敏捷开发最强大易用的接口工具,机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释 - -[UnitAuto](https://github.com/TommyLemon/UnitAuto) 最先进、最省事、ROI 最高的单元测试,机器学习 零代码、全方位、自动化 测试 方法/函数,用户包含腾讯、快手、某 500 强巨头等 - -[SQLAuto](https://github.com/TommyLemon/SQLAuto) 智能零代码自动化测试 SQL 语句执行结果的数据库工具,一键批量生成参数组合、快速构造大量测试数据 - -[UIGO](https://github.com/TommyLemon/UIGO) 📱 零代码快准稳 UI 智能录制回放平台 🚀 自动兼容任意宽高比分辨率屏幕、自动精准等待网络请求,录制回放快、准、稳! - -[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.NET](https://github.com/liaozb/APIJSON.NET) C# 版 APIJSON ,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite - -[apijson-go](https://github.com/glennliao/apijson-go) Go 版 APIJSON , 基于Go(>=1.18) + GoFrame2, 支持查询、单表增删改、权限管理等 - -[apijson-go](https://gitee.com/tiangao/apijson-go) Go 版 APIJSON ,支持单表查询、数组查询、多表一对一关联查询、多表一对多关联查询 等 - -[apijson-hyperf](https://github.com/kvnZero/hyperf-APIJSON.git) PHP 版 APIJSON,基于 Hyperf 支持 MySQL - -[APIJSON-php](https://github.com/xianglong111/APIJSON-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 - -[apijson-php](https://github.com/qq547057827/apijson-php) PHP 版 APIJSON,基于 ThinkPHP,支持 MySQL, PostgreSQL, SQL Server, Oracle 等 - -[apijson-node](https://github.com/kevinaskin/apijson-node) 字节跳动工程师开源的 Node.ts 版 APIJSON,提供 nestjs 和 typeorm 的 Demo 及后台管理 - -[uliweb-apijson](https://github.com/zhangchunlin/uliweb-apijson) Python 版 APIJSON,支持 MySQL, PostgreSQL, SQL Server, Oracle, SQLite 等 - -[APIJSONParser](https://github.com/Zerounary/APIJSONParser) 第三方 APIJSON 解析器,将 JSON 动态解析成 SQL - -[FfApiJson](https://gitee.com/own_3_0/ff-api-json) 用 JSON 格式直接生成 SQL,借鉴 APIJSON 支持多数据源 - -[APIJSON-ToDo-Demo](https://github.com/jerrylususu/apijson_todo_demo) 一个简单的 todo 示例项目,精简数据,简化上手流程,带自定义鉴权逻辑 - -[apijson-learn](https://github.com/rainboy-learn/apijson-learn) APIJSON 学习笔记和源码解析 - -[apijson-practice](https://github.com/vcoolwind/apijson-practice) BAT 技术专家开源的 APIJSON 参数校验注解 Library 及相关 Demo - -[apijson-db2](https://github.com/andream7/apijson-db2) 微软工程师接入 IBM 数据库 DB2 的 APIJSON 使用 Demo - -[APIJSONDemo](https://github.com/qiujunlin/APIJSONDemo) 字节跳动工程师接入 ClickHouse 的 APIJSON 使用 Demo - -[APIJSONDemo_ClickHouse](https://github.com/chenyanlann/APIJSONDemo_ClickHouse) APIJSON + SpringBoot 连接 ClickHouse 使用的 Demo - -[APIJSONBoot_Hive](https://github.com/chenyanlann/APIJSONBoot_Hive) APIJSON + SpringBoot 连接 Hive 使用的 Demo - -[apijson-sample](https://gitee.com/greyzeng/apijson-sample) APIJSON 简单使用 Demo 及教程 - -[apijson-examples](https://gitee.com/drone/apijson-examples) APIJSON 的前端、业务后端、管理后端 Demo - -[apijson-ruoyi](https://gitee.com/yxiedd/apijson-ruoyi) APIJSON 和 RuoYi 框架整合,实现零代码生成页面模板接口,在线维护 APIJSON 数据库配置等 - -[light4j](https://github.com/xlongwei/light4j) 整合 APIJSON 和微服务框架 light-4j 的 Demo,同时接入了 Redis - -[SpringServer1.2-APIJSON](https://github.com/Airforce-1/SpringServer1.2-APIJSON) 智慧党建服务器端,提供 上传 和 下载 文件的接口 - -[apijson_template](https://github.com/abliger/apijson_template) APIJSON Java 模版,使用 gradle 管理依赖和构建应用 - -[api-json-demo](https://gitee.com/hxdwd/api-json-demo) 基于 APIJSON,实现低代码写 CURD 代码,代替传统 ORM 框架,适配 Oracle 事务 - -[ApiJsonByJFinal](https://gitee.com/zhiyuexin/ApiJsonByJFinal) 整合 APIJSON 和 JFinal 的 Demo - -[apijson-go-demo](https://github.com/glennliao/apijson-go-demo) apijson-go demos,提供 3 个从简单到复杂的不同场景 Demo - -[apijson-builder](https://github.com/pengxianggui/apijson-builder) 一个方便为 APIJSON 构建 RESTful 请求的 JavaScript 库 - -[apijson-go-ui](https://github.com/glennliao/apijson-go-ui) apijson-go UI 界面配置, 支持权限管理、请求规则配置等 - -[AbsGrade](https://github.com/APIJSON/AbsGrade) 列表级联算法,支持微信朋友圈单层评论、QQ空间双层评论、百度网盘多层(无限层)文件夹等 - -[APIJSON-Android-RxJava](https://github.com/TommyLemon/APIJSON-Android-RxJava) 仿微信朋友圈动态实战项目,ZBLibrary(UI) + APIJSON(HTTP) + RxJava(Data) - -[Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP 快速开发框架,Demo 全面,注释详细,使用简单,代码严谨 - -[apijson-dynamic-datasource](https://github.com/wb04307201/apijson-dynamic-datasource) 基于APIJSON,动态切换数据源、同一数据源批量操作事务一致性DEMO - -[xyerp](https://gitee.com/yinjg1997/xyerp) 基于ApiJson的低代码ERP - -[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 编写,调整了一些参数和使用方式 - -[lanmuc](https://gitee.com/element-admin/lanmuc) 后端低代码生产接口的平台,兼容配置式接口和编写式接口,可做到快速生产接口,上线项目 - -[review_plan](https://gitee.com/PPXcodeTry/review_plan) 复习提醒Web版(Java技术练习项目) - -[apijson-nutz](https://github.com/vincent109/apijson-nutz) APIJSON + Nutz 框架 + NutzBoot 的 Demo - -感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧~ - - -### 腾讯犀牛鸟开源人才培养计划 -https://github.com/Tencent/APIJSON/issues/229 - - -#### qiujunlin **2.接入 presto/hive/clickhouse/db2 任意一个** - -APIJSON 接入 clickhouse 使用demo
-https://github.com/qiujunlin/APIJSONDemo - -#### zhangshukun 2.接入 presto/hive/clickhouse/db2 任意一个 -APIJSON-Demo接入db2
-https://github.com/andream7/apijson-db2 - -#### hanxu 1.完善入门介绍视频 -重构 APIJSON 文档
-https://hanxu2018.github.io/APIJSON-DOC/
-文档源码
-https://github.com/HANXU2018/APIJSON-DOC
-配套评论区 apijson-doc-Comment
-https://github.com/HANXU2018/apijson-doc-Comment - -#### chenyanlan 2.接入 presto/hive/clickhouse/db2 任意一个 -APIJSON + SpringBoot连接ClickHouse使用的Demo
-https://github.com/chenyanlann/APIJSONDemo_ClickHouse - -#### zhaoqiming 1.完善入门介绍视频 -APIJSON 后端教程(1):简介 -https://www.bilibili.com/video/BV1vL411W7yd - -APIJSON 后端教程(2):数据库 -https://www.bilibili.com/video/BV1eB4y1N77s - -APIJSON 后端教程(3):Demo -https://www.bilibili.com/video/BV1FX4y1c7ug - -APIJSON 后端教程(4):Boot -https://www.bilibili.com/video/BV18h411z7FK - -APIJSON 后端教程(5):Final -https://www.bilibili.com/video/BV1GM4y1N7XJ - -APIJSON 后端教程(6):uliweb_apijson -https://www.bilibili.com/video/BV1yb4y1S79v/ - -APIJSON 后端教程(7):问题答疑 -https://www.bilibili.com/video/BV1dQ4y1h7Df - -APIJSON配套文档: -https://github.com/kenlig/apijsondocs - -#### huwen 2.接入 presto/hive/clickhouse/db2 任意一个 -APIJSON-Demo 接入presto -https://github.com/hclown9804/APIJSONDemo_presto - -#### zhanghaoling 1.完善入门介绍视频 -APIJSON结合已有项目,简化开发流程 -https://github.com/haolingzhang1/APIJson--demo - -说明文档 -https://github.com/haolingzhang1/APIJson--demo/tree/main/APIJson集成项目说明 - -(1)官方demo -https://github.com/haolingzhang1/APIJson--demo/blob/main/APIJson集成项目说明/APIJson集成现有项目(1)-%20官方demo.pdf - -(2)单表配置 -https://github.com/haolingzhang1/APIJson--demo/blob/main/APIJson集成项目说明/APIJson集成现有项目(2)-%20单表配置.pdf - -#### zhoukaile 1.完善入门介绍视频 - -视频链接:https://www.bilibili.com/video/BV1Uh411z7kZ/ - -文档链接:https://gitee.com/funkiz/apijson_camp - -#### lintao 1.完善入门介绍视频 - -APIJSON 上手教程:https://www.bilibili.com/video/BV1Pq4y1n7rJ - -### 持续更新 - -https://github.com/Tencent/APIJSON/commits/master - -### 工蜂主页 -https://git.code.tencent.com/Tencent_Open_Source/APIJSON - -### 码云主页 -https://gitee.com/Tencent/APIJSON