diff --git a/APIJSONORM/README.md b/APIJSONORM/README.md index 8007c02c3..0cb431e27 100644 --- a/APIJSONORM/README.md +++ b/APIJSONORM/README.md @@ -1,4 +1,4 @@ -# APIJSONORM [![](https://jitpack.io/v/Tencent/APIJSON.svg)](https://jitpack.io/#Tencent/APIJSON) +# APIJSONORM [![](https://jitpack.io/v/Tencent/APIJSON.svg)](https://jitpack.io/#Tencent/APIJSON) [Ask DeepWiki.com](https://deepwiki.com/Tencent/APIJSON) 腾讯 [APIJSON](https://github.com/Tencent/APIJSON) ORM 库,可通过 Maven, Gradle 等远程依赖。
Tencent [APIJSON](https://github.com/Tencent/APIJSON) ORM library for remote dependencies with Maven, Gradle, etc. diff --git a/APIJSONORM/pom.xml b/APIJSONORM/pom.xml index 2d0768fff..8b36d0da4 100644 --- a/APIJSONORM/pom.xml +++ b/APIJSONORM/pom.xml @@ -5,7 +5,7 @@ com.github.Tencent APIJSON - 7.8.0 + 8.1.3 jar APIJSONORM @@ -21,11 +21,6 @@ - - com.alibaba - fastjson - 1.2.83 - diff --git a/APIJSONORM/src/main/java/apijson/JSON.java b/APIJSONORM/src/main/java/apijson/JSON.java index d7854aae1..0a14e0420 100755 --- a/APIJSONORM/src/main/java/apijson/JSON.java +++ b/APIJSONORM/src/main/java/apijson/JSON.java @@ -1,272 +1,691 @@ -/*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; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.parser.Feature; -import com.alibaba.fastjson.serializer.SerializerFeature; - +import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; -/**阿里FastJSON封装类 防止解析时异常 +/**JSON工具类 防止解析时异常 * @author Lemon */ public class JSON { - private static final String TAG = "JSON"; - /**判断json格式是否正确 - * @param s - * @return - */ - public static boolean isJsonCorrect(String s) { - //太长 Log.i(TAG, "isJsonCorrect <<<< " + s + " >>>>>>>"); - if (s == null - // || s.equals("[]") - // || s.equals("{}") - || s.equals("") - || s.equals("[null]") - || s.equals("{null}") - || s.equals("null")) { - return false; - } - return true; + static final String TAG = "JSON"; + + public static JSONParser, ? extends List> DEFAULT_JSON_PARSER; + + static { + //DEFAULT_JSON_PARSER = new JSONParser, List>() { + // + // @Override + // public LinkedHashMap createJSONObject() { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public List createJSONArray() { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public String toJSONString(Object obj, boolean format) { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public Object parse(Object json) { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public LinkedHashMap parseObject(Object json) { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public T parseObject(Object json, Class clazz) { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public List parseArray(Object json) { + // throw new UnsupportedOperationException(); + // } + // + // @Override + // public List parseArray(Object json, Class clazz) { + // throw new UnsupportedOperationException(); + // } + // + //}; + } - /**获取有效的json - * @param s - * @return - */ - public static String getCorrectJson(String s) { - return getCorrectJson(s, false); +// public static JSONCreator, ? extends List> DEFAULT_JSON_CREATOR = DEFAULT_JSON_PARSER; +// public static > M newObj() { +// return createJSONObject(); +// } +// public static > M newObj(String key, Object value) { +// return createJSONObject(key, value); +// } +// public static > M newObj(Map map) { +// return createJSONObject(map); +// } + + public static > M createJSONObject() { + return (M) DEFAULT_JSON_PARSER.createJSONObject(); } - /**获取有效的json - * @param s - * @param isArray - * @return - */ - public static String getCorrectJson(String s, boolean isArray) { - s = StringUtil.getTrimedString(s); - // if (isArray) { - // while (s.startsWith("\"")) { - // s = s.substring(1); - // } - // while (s.endsWith("\"")) { - // s = s.substring(0, s.length() - 1); - // } - // } - return s;//isJsonCorrect(s) ? s : null; + public static > M createJSONObject(String key, Object value) { + return (M) DEFAULT_JSON_PARSER.createJSONObject(key, value); + } + public static > M createJSONObject(Map map) { + return (M) DEFAULT_JSON_PARSER.createJSONObject(map); + } + + //public static > L newArr() { + // return createJSONArray(); + //} + //public static > L newArr(Object obj) { + // return createJSONArray(obj); + //} + //public static > L newArr(List list) { + // return createJSONArray(list); + //} + + public static > L createJSONArray() { + return (L) DEFAULT_JSON_PARSER.createJSONArray(); + } + public static > L createJSONArray(Object obj) { + return (L) DEFAULT_JSON_PARSER.createJSONArray(obj); + } + public static > L createJSONArray(Collection list) { + return (L) DEFAULT_JSON_PARSER.createJSONArray(list); + } + + public static Object parse(Object json) { + return DEFAULT_JSON_PARSER.parse(json); + } + + + public static > M parseObject(Object json) { + String s = toJSONString(json); + if (StringUtil.isEmpty(s, true)) { + return null; + } + + return (M) DEFAULT_JSON_PARSER.parseObject(s); + } + + public static T parseObject(Object json, Class clazz) { + String s = toJSONString(json); + if (StringUtil.isEmpty(s, true)) { + return null; + } + + return DEFAULT_JSON_PARSER.parseObject(s, clazz); } /** * @param json * @return */ - private static final Feature[] DEFAULT_FASTJSON_FEATURES = {Feature.OrderedField, Feature.UseBigDecimal}; - public static Object parse(Object obj) { + public static > L parseArray(Object json) { + String s = toJSONString(json); + if (StringUtil.isEmpty(s, true)) { + return null; + } + try { - return com.alibaba.fastjson.JSON.parse(obj instanceof String ? (String) obj : toJSONString(obj), DEFAULT_FASTJSON_FEATURES); + L arr = (L) DEFAULT_JSON_PARSER.parseArray(s); + return arr; } catch (Exception e) { - Log.i(TAG, "parse catch \n" + e.getMessage()); + Log.i(TAG, "parseArray catch \n" + e.getMessage()); } return null; } - /**obj转JSONObject - * @param obj - * @return - */ - public static JSONObject parseObject(Object obj) { - if (obj instanceof JSONObject) { - return (JSONObject) obj; + public static List parseArray(Object json, Class clazz) { + String s = toJSONString(json); + if (StringUtil.isEmpty(s, true)) { + return null; + } + + try { + return DEFAULT_JSON_PARSER.parseArray(s, clazz); + } catch (Exception e) { + Log.i(TAG, "parseArray catch \n" + e.getMessage()); } - return parseObject(toJSONString(obj)); + return null; } - /**json转JSONObject - * @param json + + /** + * @param obj * @return */ - public static JSONObject parseObject(String json) { - return parseObject(json, JSONObject.class); + public static String format(Object obj) { + return toJSONString(obj, true); } - /**json转实体类 - * @param json - * @param clazz + /** + * @param obj * @return */ - public static T parseObject(String json, Class clazz) { - if (clazz == null || StringUtil.isEmpty(json, true)) { - Log.e(TAG, "parseObject clazz == null || StringUtil.isEmpty(json, true) >> return null;"); - } else { - try { - return com.alibaba.fastjson.JSON.parseObject(getCorrectJson(json), clazz, DEFAULT_FASTJSON_FEATURES); - } catch (Exception e) { - Log.i(TAG, "parseObject catch \n" + e.getMessage()); - } + public static String toJSONString(Object obj) { + return toJSONString(obj, false); + } + public static String toJSONString(Object obj, boolean format) { + if (obj == null) { + return null; } - return null; + + if (obj instanceof String) { + return (String) obj; + } + + //if (obj instanceof Map) { + // // Simple JSON object format + // StringBuilder sb = new StringBuilder("{"); + // @SuppressWarnings("unchecked") + // Map map = (Map) obj; + // boolean first = true; + // for (Map.Entry entry : map.entrySet()) { + // if (! first) { + // sb.append(","); + // } + // + // first = false; + // sb.append("\"").append(entry.getKey()).append("\":"); + // Object value = entry.getValue(); + // if (value instanceof String) { + // sb.append("\"").append(value).append("\""); + // } else { + // sb.append(toJSONString(value)); + // } + // } + // sb.append("}"); + // return sb.toString(); + //} + // + //if (obj instanceof List) { + // StringBuilder sb = new StringBuilder("["); + // @SuppressWarnings("unchecked") + // List list = (List) obj; + // boolean first = true; + // for (Object item : list) { + // if (! first) { + // sb.append(","); + // } + // first = false; + // if (item instanceof String) { + // sb.append("\"").append(item).append("\""); + // } else { + // sb.append(toJSONString(item)); + // } + // } + // sb.append("]"); + // return sb.toString(); + //} + + return DEFAULT_JSON_PARSER.toJSONString(obj, format); } - /**list转JSONArray - * @param list + + /**判断是否为JSONObject或JSONArray的isXxx方法名 + * @param key * @return */ - public static JSONArray parseArray(List list) { - return new JSONArray(list); + public static boolean isJSONType(String key) { + return key != null && key.startsWith("is") && key.length() > 2 && key.contains("JSON"); } - /**obj转JSONArray - * @param obj - * @return + + public static boolean isBoolOrNumOrStr(Object obj) { + return obj instanceof Boolean || obj instanceof Number || obj instanceof String; + } + + /** + * Get a value from a Map and convert to the specified type + * @param map Source map + * @param key The key + * @param Target type + * @return The converted value + */ + @SuppressWarnings("unchecked") + public static T get(Map map, String key) { + return map == null || key == null ? null : (T) map.get(key); + } + + /** + * Get a value from a Map and convert to the specified type + * @param map Source map + * @param key The key + * @param Target type + * @return The converted value + */ + @SuppressWarnings("unchecked") + public static > M getJSONObject(Map map, String key) { + Object obj = get(map, key); + return (M) obj; + } + + /** + * Get a value from a Map and convert to the specified type + * @param map Source map + * @param key The key + * @param Target type + * @return The converted value + */ + @SuppressWarnings("unchecked") + public static > L getJSONArray(Map map, String key) { + Object obj = get(map, key); + return (L) obj; + } + + /** + * Get a value from a Map and convert to the specified type + * @param list Source map + * @param index The key + * @param Target type + * @return The converted value + */ + @SuppressWarnings("unchecked") + public static T get(List list, int index) { + return list == null || index < 0 || index >= list.size() ? null : (T) list.get(index); + } + + @SuppressWarnings("unchecked") + public static > M getJSONObject(List list, int index) { + Object obj = get(list, index); + return (M) obj; + } + + @SuppressWarnings("unchecked") + public static > L getJSONArray(List list, int index) { + Object obj = get(list, index); + return (L) obj; + } + +// /** +// * Get a value from a Map and convert to the specified type +// * @param map Source map +// * @param key The key +// * @param Target type +// * @return The converted value +// */ +// @SuppressWarnings("unchecked") +// public static T get(List list, int index) { +// return list == null || index < 0 || index >= list.size() ? null : list.get(index); +// } + + /** + * Get a Map value from a Map + * @param map Source map + * @param key The key + * @return The Map value + * @throws IllegalArgumentException If value is not a Map and cannot be converted */ - public static JSONArray parseArray(Object obj) { - if (obj instanceof JSONArray) { - return (JSONArray) obj; + @SuppressWarnings("unchecked") + public static Map getMap(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + if (value instanceof Map) { + return (Map) value; } - return parseArray(toJSONString(obj)); + + throw new IllegalArgumentException("Value for key '" + key + "' is not a Map: " + value.getClass().getName()); } - /**json转JSONArray - * @param json - * @return + + /** + * Get a List value from a Map + * @param map Source map + * @param key The key + * @return The List value + * @throws IllegalArgumentException If value is not a List and cannot be converted */ - public static JSONArray parseArray(String json) { - if (StringUtil.isEmpty(json, true)) { - Log.e(TAG, "parseArray StringUtil.isEmpty(json, true) >> return null;"); - } else { + @SuppressWarnings("unchecked") + public static List getList(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + if (value instanceof List) { + return (List) value; + } + + throw new IllegalArgumentException("Value for key '" + key + "' is not a List: " + value.getClass().getName()); + } + + /** + * Get an int value from a Map + * @param map Source map + * @param key The key + * @return The int value + * @throws IllegalArgumentException If value cannot be converted to int + */ + public static Integer getInteger(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).intValue(); + } + + if (value instanceof String) { try { - return com.alibaba.fastjson.JSON.parseArray(getCorrectJson(json, true)); - } catch (Exception e) { - Log.i(TAG, "parseArray catch \n" + e.getMessage()); + return Integer.parseInt((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to int: " + e.getMessage()); } } - return null; + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to int"); } - /**JSONArray转实体类列表 - * @param array - * @param clazz - * @return + + /** + * Get an int value from a Map + * @param map Source map + * @param key The key + * @return The int value + * @throws IllegalArgumentException If value cannot be converted to int */ - public static List parseArray(JSONArray array, Class clazz) { - return parseArray(toJSONString(array), clazz); + public static int getIntValue(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return 0; + } + + if (value instanceof Number) { + return ((Number) value).intValue(); + } + + if (value instanceof String) { + try { + return Integer.parseInt((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to int: " + e.getMessage()); + } + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to int"); } - /**json转实体类列表 - * @param json - * @param clazz - * @return + + /** + * Get an int value from a Map + * @param map Source map + * @param key The key + * @return The int value + * @throws IllegalArgumentException If value cannot be converted to int */ - public static List parseArray(String json, Class clazz) { - if (clazz == null || StringUtil.isEmpty(json, true)) { - Log.e(TAG, "parseArray clazz == null || StringUtil.isEmpty(json, true) >> return null;"); - } else { + public static Long getLong(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).longValue(); + } + + if (value instanceof String) { try { - return com.alibaba.fastjson.JSON.parseArray(getCorrectJson(json, true), clazz); - } catch (Exception e) { - Log.i(TAG, "parseArray catch \n" + e.getMessage()); + return Long.parseLong((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to int: " + e.getMessage()); } } - return null; + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to int"); } - /**实体类转json - * @param obj - * @return + /** + * Get a long value from a Map + * @param map Source map + * @param key The key + * @return The long value + * @throws IllegalArgumentException If value cannot be converted to long */ - public static String toJSONString(Object obj) { - if (obj == null) { - return null; + public static long getLongValue(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return 0; } - if (obj instanceof String) { - return (String) obj; + + if (value instanceof Number) { + return ((Number) value).longValue(); } - try { - return com.alibaba.fastjson.JSON.toJSONString(obj); - } catch (Exception e) { - Log.e(TAG, "toJSONString catch \n" + e.getMessage()); + + if (value instanceof String) { + try { + return Long.parseLong((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to long: " + e.getMessage()); + } } - return null; + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to long"); } - /**实体类转json - * @param obj - * @param features - * @return + /** + * Get a double value from a Map + * @param map Source map + * @param key The key + * @return The double value + * @throws IllegalArgumentException If value cannot be converted to double */ - public static String toJSONString(Object obj, SerializerFeature... features) { - if (obj == null) { + public static Float getFloat(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { return null; } - if (obj instanceof String) { - return (String) obj; + + if (value instanceof Number) { + return ((Number) value).floatValue(); } - try { - return com.alibaba.fastjson.JSON.toJSONString(obj, features); - } catch (Exception e) { - Log.e(TAG, "toJSONString catch \n" + e.getMessage()); + + if (value instanceof String) { + try { + return Float.parseFloat((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to double: " + e.getMessage()); + } } - return null; + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to double"); } - /**格式化,显示更好看 - * @param json - * @return + /** + * Get a double value from a Map + * @param map Source map + * @param key The key + * @return The double value + * @throws IllegalArgumentException If value cannot be converted to double */ - public static String format(String json) { - return format(parse(json)); + public static float getFloatValue(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return 0; + } + + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + + if (value instanceof String) { + try { + return Float.parseFloat((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to double: " + e.getMessage()); + } + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to double"); } - /**格式化,显示更好看 - * @param object - * @return + + + /** + * Get a double value from a Map + * @param map Source map + * @param key The key + * @return The double value + * @throws IllegalArgumentException If value cannot be converted to double */ - public static String format(Object object) { - return toJSONString(object, SerializerFeature.PrettyFormat); + public static Double getDouble(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + + if (value instanceof String) { + try { + return Double.parseDouble((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to double: " + e.getMessage()); + } + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to double"); } - /**判断是否为JSONObject - * @param obj instanceof String ? parseObject - * @return + /** + * Get a double value from a Map + * @param map Source map + * @param key The key + * @return The double value + * @throws IllegalArgumentException If value cannot be converted to double */ - public static boolean isJSONObject(Object obj) { - if (obj instanceof JSONObject) { - return true; + public static double getDoubleValue(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return 0; } - if (obj instanceof String) { + + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + + if (value instanceof String) { try { - JSONObject json = parseObject((String) obj); - return json != null && json.isEmpty() == false; - } catch (Exception e) { - Log.e(TAG, "isJSONObject catch \n" + e.getMessage()); + return Double.parseDouble((String) value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to double: " + e.getMessage()); } } - return false; + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to double"); } - /**判断是否为JSONArray - * @param obj instanceof String ? parseArray - * @return + + + /** + * Get a boolean value from a Map + * @param map Source map + * @param key The key + * @return The boolean value + * @throws IllegalArgumentException If value cannot be converted to boolean */ - public static boolean isJSONArray(Object obj) { - if (obj instanceof JSONArray) { - return true; + public static Boolean getBoolean(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; } - if (obj instanceof String) { - try { - JSONArray json = parseArray((String) obj); - return json != null && json.isEmpty() == false; - } catch (Exception e) { - Log.e(TAG, "isJSONArray catch \n" + e.getMessage()); + + if (value instanceof Boolean) { + return (Boolean) value; + } + + if (value instanceof String) { + String str = ((String) value).toLowerCase(); + if (str.equals("true") || str.equals("false")) { + return Boolean.parseBoolean(str); + } + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to boolean"); + } + + if (value instanceof Number) { + int intValue = ((Number) value).intValue(); + if (intValue == 0 || intValue == 1) { + return intValue != 0; } + throw new IllegalArgumentException("Cannot convert Number value '" + value + "' to boolean. Only 0 and 1 are supported."); } - return false; + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to boolean"); } - /**判断是否为 Boolean,Number,String 中的一种 - * @param obj - * @return + /** + * Get a boolean value from a Map + * @param map Source map + * @param key The key + * @return The boolean value + * @throws IllegalArgumentException If value cannot be converted to boolean */ - public static boolean isBooleanOrNumberOrString(Object obj) { - return obj instanceof Boolean || obj instanceof Number || obj instanceof String; + public static boolean getBooleanValue(Map map, String key) throws IllegalArgumentException { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return false; + } + + if (value instanceof Boolean) { + return (Boolean) value; + } + + if (value instanceof String) { + String str = ((String) value).toLowerCase(); + if (str.equals("true") || str.equals("false")) { + return Boolean.parseBoolean(str); + } + throw new IllegalArgumentException("Cannot convert String value '" + value + "' to boolean"); + } + + if (value instanceof Number) { + int intValue = ((Number) value).intValue(); + if (intValue == 0 || intValue == 1) { + return intValue != 0; + } + throw new IllegalArgumentException("Cannot convert Number value '" + value + "' to boolean. Only 0 and 1 are supported."); + } + + throw new IllegalArgumentException("Cannot convert value of type " + value.getClass().getName() + " to boolean"); + } + + /** + * Get a string value from a Map + * @param map Source map + * @param key The key + * @return The string value + */ + public static String getString(Map map, String key) { + Object value = map == null || key == null ? null : map.get(key); + if (value == null) { + return null; + } + + 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 new file mode 100755 index 000000000..df0d9066a --- /dev/null +++ b/APIJSONORM/src/main/java/apijson/JSONCreator.java @@ -0,0 +1,54 @@ +/*Copyright (C) 2020 Tencent. All rights reserved. + +This source code is licensed under the Apache License Version 2.0.*/ + + +package apijson; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/**JSON相关创建器 + * @author Lemon + */ +public interface JSONCreator, L extends List> { + + @NotNull + M createJSONObject(); + + @NotNull + default M createJSONObject(String key, Object value) { + M obj = createJSONObject(); + obj.put(key, value); + return obj; + } + + @NotNull + default M createJSONObject(Map map) { + M obj = createJSONObject(); + if (map != null && ! map.isEmpty()) { + obj.putAll(map); + } + return obj; + } + + @NotNull + L createJSONArray(); + + @NotNull + default L createJSONArray(Object obj){ + L arr = createJSONArray(); + arr.add(obj); + return arr; + } + + @NotNull + default L createJSONArray(Collection list){ + L arr = createJSONArray(); + if (list != null && ! list.isEmpty()) { + arr.addAll(list); + } + return arr; + } +} diff --git a/APIJSONORM/src/main/java/apijson/JSONList.java b/APIJSONORM/src/main/java/apijson/JSONList.java new file mode 100644 index 000000000..092bf9f39 --- /dev/null +++ b/APIJSONORM/src/main/java/apijson/JSONList.java @@ -0,0 +1,312 @@ +/*Copyright (C) 2020 Tencent. All rights reserved. + +This source code is licensed under the Apache License Version 2.0.*/ + +package apijson; + +import java.util.*; + +/** + * Custom JSONList implementation based on ArrayList to replace com.alibaba.fastjson.JSONList + * Maintains same API as fastjson but uses standard Java List implementation + * @author Lemon + */ +public interface JSONList, L extends List> extends List { + public static final String TAG = "JSONList"; + + ///** + // * Create an empty JSONList + // */ + //default JSONList() { + // super(); + //} + // + //private int initialCapacity = 10; + ///** + // * Create a JSONList with initial capacity + // * @param initialCapacity the initial capacity + // */ + //default JSONList(int initialCapacity) { + // super(initialCapacity); + //} + // + ///** + // * Create a JSONList from a Collection + // * @param collection the collection to copy from + // */ + //default JSONList(Collection collection) { + // super(collection); + //} + // + ///** + // * Create a JSONList from a JSON string + // * @param json JSON string + // */ + //default JSONList(String json) { + // this(); + // List list = JSON.parseArray(json); + // if (list != null) { + // addAll(list); + // } + //} + // + /** + * Get a JSONMap at the specified index + * @param index the index + * @return the JSONMap or null if not a JSONMap + */ + default M getJSONObject(int index) { + if (index < 0 || index >= size()) { + return null; + } + + Object obj = get(index); + if (obj instanceof Map) { + return JSON.createJSONObject((Map) obj); + } + + return null; + } + + /** + * Get a JSONList at the specified index + * @param index the index + * @return the JSONList or null if not a JSONList + */ + default L getJSONArray(int index) { + if (index < 0 || index >= size()) { + return null; + } + + Object obj = get(index); + if (obj instanceof List) { + return JSON.createJSONArray((List) obj); + } + + return null; + } + + /** + * Get a boolean value at the specified index + * @param index the index + * @return the boolean value or false if not found + */ + default boolean getBooleanValue(int index) { + if (index < 0 || index >= size()) { + return false; + } + + Object obj = get(index); + if (obj instanceof Boolean) { + return (Boolean) obj; + } else if (obj instanceof Number) { + return ((Number) obj).intValue() != 0; + } else if (obj instanceof String) { + return Boolean.parseBoolean((String) obj); + } + + return false; + } + + /** + * Get an integer value at the specified index + * @param index the index + * @return the integer value or 0 if not found + */ + default int getIntValue(int index) { + if (index < 0 || index >= size()) { + return 0; + } + + Object obj = get(index); + if (obj instanceof Number) { + return ((Number) obj).intValue(); + } else if (obj instanceof String) { + try { + return Integer.parseInt((String) obj); + } catch (NumberFormatException e) { + // Ignore + } + } + return 0; + } + + /** + * Get a long value at the specified index + * @param index the index + * @return the long value or 0 if not found + */ + default long getLongValue(int index) { + if (index < 0 || index >= size()) { + return 0L; + } + + Object obj = get(index); + if (obj instanceof Number) { + return ((Number) obj).longValue(); + } else if (obj instanceof String) { + try { + return Long.parseLong((String) obj); + } catch (NumberFormatException e) { + // Ignore + } + } + return 0L; + } + + /** + * Get a double value at the specified index + * @param index the index + * @return the double value or 0 if not found + */ + default double getDoubleValue(int index) { + if (index < 0 || index >= size()) { + return 0.0; + } + + Object obj = get(index); + if (obj instanceof Number) { + return ((Number) obj).doubleValue(); + } else if (obj instanceof String) { + try { + return Double.parseDouble((String) obj); + } catch (NumberFormatException e) { + // Ignore + } + } + return 0.0; + } + + /** + * Get a string value at the specified index + * @param index the index + * @return the string value or null if not found + */ + default String getString(int index) { + if (index < 0 || index >= size()) { + return null; + } + + Object obj = get(index); + return obj != null ? obj.toString() : null; + } + + + default String toJSONString() { + return JSON.toJSONString(this); + } + + //@Override + //default boolean containsAll(Collection c) { + // if (c == null || c.isEmpty()) { + // return true; + // } + // return super.containsAll(c); + //} + // + //@Override + //default boolean addAll(Collection c) { + // if (c == null || c.isEmpty()) { + // return true; + // } + // return super.addAll(c); + //} + // + //@Override + //default boolean addAll(int index, Collection c) { + // if (c == null || c.isEmpty()) { + // return true; + // } + // + // int sz = size(); + // if (index < 0 || index >= sz) { + // index += sz; + // } + // + // return super.addAll(index, c); + //} + // + //@Override + //default boolean removeAll(Collection c) { + // if (c == null || c.isEmpty()) { + // return true; + // } + // return super.removeAll(c); + //} + // + //@Override + //default boolean retainAll(Collection c) { + // if (c == null || c.isEmpty()) { + // return true; + // } + // return super.retainAll(c); + //} + // + // + //@Override + //default Object get(int index) { + // int sz = size(); + // if (index < 0 || index >= sz) { + // index += sz; + // } + // + // return super.get(index); + //} + // + //@Override + //default Object set(int index, Object element) { + // int sz = size(); + // if (index < 0 || index >= sz) { + // index += sz; + // } + // + // return super.set(index, element); + //} + // + //@Override + //default void add(int index, Object element) { + // int sz = size(); + // if (index < 0 || index >= sz) { + // index += sz; + // } + // + // super.add(index, element); + //} + // + //@Override + //default Object remove(int index) { + // int sz = size(); + // if (index < 0 && index >= -sz) { + // index += sz; + // } + // if (index < 0 || index >= sz) { + // return null; + // } + // + // return super.remove(index); + //} + // + //@Override + //default ListIterator listIterator(int index) { + // int sz = size(); + // if (index < 0 && index >= -sz) { + // index += sz; + // } + // + // return super.listIterator(index); + //} + // + //@Override + //default List subList(int fromIndex, int toIndex) { + // int sz = size(); + // if (fromIndex < 0 && fromIndex >= -sz) { + // fromIndex += sz; + // } + // if (toIndex < 0 && toIndex >= -sz) { + // toIndex += sz; + // } + // + // return super.subList(fromIndex, toIndex); + //} + +} \ No newline at end of file diff --git a/APIJSONORM/src/main/java/apijson/JSONMap.java b/APIJSONORM/src/main/java/apijson/JSONMap.java new file mode 100755 index 000000000..3b93d69d2 --- /dev/null +++ b/APIJSONORM/src/main/java/apijson/JSONMap.java @@ -0,0 +1,833 @@ +/*Copyright (C) 2020 Tencent. All rights reserved. + +This source code is licensed under the Apache License Version 2.0.*/ + + +package apijson; + +import java.util.*; + + +/**use this class instead of com.alibaba.fastjson.JSONMap + * @author Lemon + * @see #put + * @see #puts + * @see #putsAll + */ +//default class JSONMap extends LinkedHashMap { +public interface JSONMap, L extends List> extends Map { + static final String TAG = "JSONMap"; + + // 只能是 static public Map map = new LinkedHashMap<>(); + + ///**ordered + // */ + //default JSONMap() { + // super(); + //} + ///**transfer Object to JSONMap + // * @param object + // * @see {@link #JSONMap(Object)} + // */ + //default JSONMap(Object object) { + // this(); + // if (object instanceof Map) { + // @SuppressWarnings("unchecked") + // Map map = (Map) object; + // putAll(map); + // } else if (object != null) { + // String json = JSON.toJSONString(object); + // if (json != null) { + // Map map = JSON.parseObject(json); + // if (map != null) { + // putAll(map); + // } + // } + // } + //} + ///**parse JSONMap with JSON String + // * @param json + // * @see {@link #JSONMap(String)} + // */ + //default JSONMap(String json) { + // this(); + // Map map = JSON.parseObject(json); + // if (map != null) { + // putAll(map); + // } + //} + ///**transfer com.alibaba.fastjson.JSONMap to JSONMap + // * @param object + // * @see {@link #putsAll(Map)} + // */ + //default JSONMap(Map object) { + // this(); + // putsAll(object); + //} + + //public static JSONMap valueOf(Object obj) { + // JSONMap req = new JSONMap() {}; + // Map m = JSON.parseObject(obj); + // if (m != null && ! m.isEmpty()) { + // req.map.putAll(m); + // } + // return req; + //} + + //judge <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + String KEY_ARRAY = "[]"; + + /**判断是否为Array的key + * @param key + * @return + */ + public static boolean isArrayKey(String key) { + return key != null && key.endsWith(KEY_ARRAY); + } + /**判断是否为对应Table的key + * @param key + * @return + */ + public static boolean isTableKey(String key) { + return StringUtil.isBigName(key); + } + /**判断是否为对应Table数组的 key + * @param key + * @return + */ + public static boolean isTableArray(String key) { + return isArrayKey(key) && isTableKey(key.substring(0, key.length() - KEY_ARRAY.length())); + } + //judge >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + //JSONObject内关键词 key <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + public static String KEY_ID = "id"; + public static String KEY_ID_IN = KEY_ID + "{}"; + 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(getIdKey(), id); + } + /**set "id{}":[] in Table layer + * @param list + * @return + */ + default JSONMap setIdIn(List list) { + return puts(getIdInKey(), list); + } + + /**set "userId":userId in Table layer + * @param id + * @return + */ + default JSONMap setUserId(Long id) { + return puts(getUserIdKey(), id); + } + /**set "userId{}":[] in Table layer + * @param list + * @return + */ + default JSONMap setUserIdIn(List list) { + return puts(getUserIdInKey(), list); + } + + + int CACHE_ALL = 0; + int CACHE_ROM = 1; + int CACHE_RAM = 2; + + String CACHE_ALL_STRING = "ALL"; + String CACHE_ROM_STRING = "ROM"; + String CACHE_RAM_STRING = "RAM"; + + + //@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_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"; // 把字段转为 JSON 输出 + String KEY_STRING = "@string"; // 把字段转为 String 输入 + String KEY_METHOD = "@method"; // json 对象配置操作方法 + String KEY_GET = "@get"; // json 对象配置操作方法 + String KEY_GETS = "@gets"; // json 对象配置操作方法 + String KEY_HEAD = "@head"; // json 对象配置操作方法 + String KEY_HEADS = "@heads"; // json 对象配置操作方法 + String KEY_POST = "@post"; // json 对象配置操作方法 + String KEY_PUT = "@put"; // json 对象配置操作方法 + String KEY_DELETE = "@delete"; // json 对象配置操作方法 + + List TABLE_KEY_LIST = new ArrayList<>(Arrays.asList( + KEY_ROLE, + KEY_DATABASE, + KEY_DATASOURCE, + KEY_NAMESPACE, + KEY_CATALOG, + KEY_SCHEMA, + KEY_EXPLAIN, + KEY_CACHE, + KEY_COLUMN, + KEY_FROM, + KEY_NULL, + KEY_CAST, + KEY_COMBINE, + KEY_GROUP, + KEY_HAVING, + KEY_HAVING_AND, + KEY_SAMPLE, + KEY_LATEST, + KEY_PARTITION, + KEY_FILL, + KEY_ORDER, + KEY_KEY, + KEY_RAW, + KEY_JSON, + KEY_STRING, + KEY_METHOD, + KEY_GET, + KEY_GETS, + KEY_HEAD, + KEY_HEADS, + KEY_POST, + KEY_PUT, + KEY_DELETE + )); + + //@key关键字都放这个类 >>>>>>>>>>>>>>>>>>>>>> + + + /**set try, ignore exceptions + * @param tri + * @return this + */ + default JSONMap setTry(Boolean tri) { + return puts(KEY_TRY, tri); + } + + /**set catch + * @param isCatch + * @return this + */ + default JSONMap setCatch(String isCatch) { + return puts(KEY_CATCH, isCatch); + } + /**set drop, data dropped will not return + * @param drop + * @return this + */ + default JSONMap setDrop(Boolean drop) { + return puts(KEY_DROP, drop); + } + + /**set if has default + * @param hasDefault + * @return this + */ + default JSONMap setDefault(Boolean hasDefault) { + return puts(KEY_DEFAULT, hasDefault); + } + + + /**set role of request sender + * @param role + * @return this + */ + default JSONMap setRole(String role) { + return puts(KEY_ROLE, role); + } + /**set database where table was puts + * @param database + * @return this + */ + default JSONMap setDatabase(String database) { + return puts(KEY_DATABASE, database); + } + /**set datasource where table was puts + * @param datasource + * @return this + */ + default JSONMap setDatasource(String datasource) { + return puts(KEY_DATASOURCE, datasource); + } + /**set namespace where table was puts + * @param namespace + * @return this + */ + default JSONMap setNamespace(String namespace) { + return puts(KEY_NAMESPACE, namespace); + } + /**set catalog where table was puts + * @param catalog + * @return this + */ + default JSONMap setCatalog(String catalog) { + return puts(KEY_CATALOG, catalog); + } + /**set schema where table was puts + * @param schema + * @return this + */ + default JSONMap setSchema(String schema) { + return puts(KEY_SCHEMA, schema); + } + /**set if return explain informations + * @param explain + * @return + */ + default JSONMap setExplain(Boolean explain) { + return puts(KEY_EXPLAIN, explain); + } + /**set cache type + * @param cache + * @return + * @see {@link #CACHE_ALL} + * @see {@link #CACHE_RAM} + * @see {@link #CACHE_ROM} + */ + default JSONMap setCache(Integer cache) { + return puts(KEY_CACHE, cache); + } + /**set cache type + * @param cache + * @return + * @see {@link #CACHE_ALL_STRING} + * @see {@link #CACHE_RAM_STRING} + * @see {@link #CACHE_ROM_STRING} + */ + default JSONMap setCache(String cache) { + return puts(KEY_CACHE, cache); + } + + /**set keys need to be returned + * @param keys key0, key1, key2 ... + * @return {@link #setColumn(String)} + */ + default JSONMap setColumn(String... keys) { + return setColumn(StringUtil.get(keys, true)); + } + /**set keys need to be returned + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setColumn(String keys) { + return puts(KEY_COLUMN, keys); + } + + /**set keys whose value is null + * @param keys key0, key1, key2 ... + * @return {@link #setNull(String)} + */ + default JSONMap setNull(String... keys) { + return setNull(StringUtil.get(keys, true)); + } + /**set keys whose value is null + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setNull(String keys) { + return puts(KEY_NULL, keys); + } + + /**set keys and types whose value should be cast to type, cast(value AS DATE) + * @param keyTypes key0:type0, key1:type1, key2:type2 ... + * @return {@link #setCast(String)} + */ + default JSONMap setCast(String... keyTypes) { + return setCast(StringUtil.get(keyTypes, true)); + } + /**set keys and types whose value should be cast to type, cast(value AS DATE) + * @param keyTypes "key0:type0,key1:type1,key2:type2..." + * @return + */ + default JSONMap setCast(String keyTypes) { + return puts(KEY_CAST, keyTypes); + } + + /**set combination of keys for conditions + * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... + * @return {@link #setColumn(String)} + */ + default JSONMap setCombine(String... keys) { + return setCombine(StringUtil.get(keys, true)); + } + /**set combination of keys for conditions + * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... + * @return + */ + default JSONMap setCombine(String keys) { + return puts(KEY_COMBINE, keys); + } + + /**set keys for group by + * @param keys key0, key1, key2 ... + * @return {@link #setGroup(String)} + */ + default JSONMap setGroup(String... keys) { + return setGroup(StringUtil.get(keys, true)); + } + /**set keys for group by + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setGroup(String keys) { + return puts(KEY_GROUP, keys); + } + + /**set keys for having + * @param keys count(key0) > 1, sum(key1) <= 5, function2(key2) ? value2 ... + * @return {@link #setHaving(String)} + */ + default JSONMap setHaving(String... keys) { + return setHaving(StringUtil.get(keys, true)); + } + /**set keys for having + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setHaving(String keys) { + return setHaving(keys, false); + } + /**set keys for having + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setHaving(String keys, boolean isAnd) { + return puts(isAnd ? KEY_HAVING_AND : KEY_HAVING, keys); + } + + /**set keys for sample by + * @param keys key0, key1, key2 ... + * @return {@link #setSample(String)} + */ + default JSONMap setSample(String... keys) { + return setSample(StringUtil.get(keys, true)); + } + /**set keys for sample by + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setSample(String keys) { + return puts(KEY_SAMPLE, keys); + } + + /**set keys for latest on + * @param keys key0, key1, key2 ... + * @return {@link #setLatest(String)} + */ + default JSONMap setLatest(String... keys) { + return setLatest(StringUtil.get(keys, true)); + } + /**set keys for latest on + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setLatest(String keys) { + return puts(KEY_LATEST, keys); + } + + /**set keys for partition by + * @param keys key0, key1, key2 ... + * @return {@link #setPartition(String)} + */ + default JSONMap setPartition(String... keys) { + return setPartition(StringUtil.get(keys, true)); + } + /**set keys for partition by + * @param keys key0, key1, key2 ... + * @return + */ + default JSONMap setPartition(String keys) { + return puts(KEY_PARTITION, keys); + } + + /**set keys for fill(key): fill(null), fill(linear), fill(prev) + * @param keys key0, key1, key2 ... + * @return {@link #setFill(String)} + */ + default JSONMap setFill(String... keys) { + return setFill(StringUtil.get(keys, true)); + } + /**set keys for fill(key): fill(null), fill(linear), fill(prev) + * @param keys key0, key1, key2 ... + * @return + */ + default JSONMap setFill(String keys) { + return puts(KEY_FILL, keys); + } + + /**set keys for order by + * @param keys key0, key1+, key2- ... + * @return {@link #setOrder(String)} + */ + default JSONMap setOrder(String... keys) { + return setOrder(StringUtil.get(keys, true)); + } + /**set keys for order by + * @param keys "key0,key1+,key2-..." + * @return + */ + default JSONMap setOrder(String keys) { + return puts(KEY_ORDER, keys); + } + + /**set key map + * @param keyMap "name_tag:(name,tag);year:left(date,1,5)..." + * @return + */ + default JSONMap setKey(String keyMap) { + return puts(KEY_KEY, keyMap); + } + + /**set keys to raw + * @param keys "key0,key1,key2..." + * @return + */ + default JSONMap setRaw(String keys) { + return puts(KEY_RAW, keys); + } + + /**set keys to cast to json + * @param keys "key0,key1,key2..." + * @return + */ + 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); + } + + //JSONObject内关键词 key >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + + + //Request <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + /** + * @param key + * @param keys path = keys[0] + "/" + keys[1] + "/" + keys[2] + ... + * @return {@link #puts(String, Object)} + */ + default JSONMap putsPath(String key, String... keys) { + return puts(key+"@", StringUtil.get(keys, "/")); + } + + /** + * @param key + * @param isNull + * @return {@link #puts(String, Object)} + */ + default JSONMap putsNull(String key, boolean isNull) { + return puts(key+"{}", SQL.isNull(isNull)); + } + /** + * trim = false + * @param key + * @param isEmpty + * @return {@link #putsEmpty(String, boolean, boolean)} + */ + default JSONMap putsEmpty(String key, boolean isEmpty) { + return putsEmpty(key, isEmpty, false); + } + /** + * @param key + * @param isEmpty + * @return {@link #puts(String, Object)} + */ + default JSONMap putsEmpty(String key, boolean isEmpty, boolean trim) { + return puts(key+"{}", SQL.isEmpty(key, isEmpty, trim)); + } + /** + * @param key + * @param compare <=0, >5 ... + * @return {@link #puts(String, Object)} + */ + default JSONMap putsLength(String key, String compare) { + return puts(key+"{}", SQL.length(key) + compare); + } + /** + * @param key + * @param compare <=, > ... + * @param value 1, 5, 3.14, -99 ... + * @return {@link #puts(String, Object)} + */ + default JSONMap putsLength(String key, String compare, Object value) { + return puts(key+"["+(StringUtil.isEmpty(compare) || "=".equals(compare) ? "" : ("!=".equals(compare) ? "!" : compare)), value); + } + /** + * @param key + * @param compare <=0, >5 ... + * @return {@link #puts(String, Object)} + */ + default JSONMap putsJSONLength(String key, String compare) { + return puts(key+"{}", SQL.json_length(key) + compare); + } + /** + * @param key + * @param compare <=0, >5 ... + * @return {@link #puts(String, Object)} + */ + default JSONMap putsJSONLength(String key, String compare, Object value) { + return puts(key + "{" + (StringUtil.isEmpty(compare) || "=".equals(compare) ? "" : ("!=".equals(compare) ? "!" : compare)), value); + } + + /**设置搜索 + * type = SEARCH_TYPE_CONTAIN_FULL + * @param key + * @param value + * @return {@link #putsSearch(String, String, int)} + */ + default JSONMap putsSearch(String key, String value) { + return putsSearch(key, value, SQL.SEARCH_TYPE_CONTAIN_FULL); + } + /**设置搜索 + * @param key + * @param value + * @param type + * @return {@link #puts(String, Object)} + */ + default JSONMap putsSearch(String key, String value, int type) { + return puts(key+"$", SQL.search(value, type)); + } + + //Request >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + /**put and return this + * @param value must be annotated by {@link MethodAccess} + * @return {@link #puts(String, Object)} + */ + default JSONMap puts(Object value) { + put(value); + return this; + } + /**put and return this + * @param key + * @param value + * @return this + */ + default JSONMap puts(String key, Object value) { + put(key, value); + return this; + } + + /**put and return value + * @param value must be annotated by {@link MethodAccess} + */ + default Object put(Object value) { + Class clazz = value.getClass(); //should not return null + if (clazz.getAnnotation(MethodAccess.class) == null) { + throw new IllegalArgumentException("puts StringUtil.isEmpty(key, true)" + + " clazz.getAnnotation(MethodAccess.class) == null" + + " \n key为空时仅支持 类型被@MethodAccess注解 的value !!!" + + " \n 如果一定要这么用,请对 " + clazz.getName() + " 注解!" + + " \n 如果是类似 key[]:{} 结构的请求,建议用 putsAll(...) !"); + } + return put(clazz.getSimpleName(), value); + } + + /**puts key-value in object into this + * @param map + * @return this + */ + default JSONMap putsAll(Map map) { + putAll(map); + return this; + } + + + /** + * Get a boolean value from the JSONMap + * @param key the key + * @return the boolean value or false if not found + */ + default boolean getBooleanValue(String key) { + return JSON.getBooleanValue(this, key); + } + + /** + * Get an integer value from the JSONMap + * @param key the key + * @return the integer value or 0 if not found + */ + default int getIntValue(String key) { + return JSON.getIntValue(this, key); + } + + /** + * Get a long value from the JSONMap + * @param key the key + * @return the long value or 0 if not found + */ + default long getLongValue(String key) { + return JSON.getLongValue(this, key); + } + + /** + * Get a double value from the JSONMap + * @param key the key + * @return the double value or 0 if not found + */ + default double getDoubleValue(String key) { + return JSON.getDoubleValue(this, key); + } + + /** + * Get a string value from the JSONMap + * @param key the key + * @return the string value or null if not found + */ + default String getString(String key) { + Object value = get(key); + return value != null ? value.toString() : null; + } + + /** + * Get a JSONMap value from the JSONMap + * @param key the key + * @return the JSONMap value or null if not found + */ + default M getJSONObject(String key) { + Map map = JSON.getMap(this, key); + return map != null ? JSON.createJSONObject(map) : null; + } + + /** + * Get a JSONList value from the JSONMap + * @param key the key + * @return the JSONList value or null if not found + */ + default L getJSONArray(String key) { + List list = JSON.getList(this, key); + return list != null ? JSON.createJSONArray(list) : null; + } + + @Override + default void putAll(Map map) { + Set> set = map == null ? null : map.entrySet(); + if (set != null || set.isEmpty()) { + return; + } + + for (Map.Entry entry : set) { + put(entry.getKey(), entry.getValue()); + } + } + + default String toJSONString() { + return JSON.toJSONString(this); + } + + //@Override + //default int size() { + // return map.size(); + //} + // + //@Override + //default boolean isEmpty() { + // return map.isEmpty(); + //} + // + //@Override + //default boolean containsKey(Object key) { + // return map.containsKey(key); + //} + // + //@Override + //default boolean containsValue(Object value) { + // return map.containsValue(value); + //} + // + //@Override + //default Object get(Object key) { + // return map.get(key); + //} + // + //@Override + //default Object put(String key, Object value) { + // return map.put(key, value); + //} + // + //@Override + //default Object remove(Object key) { + // return map.remove(key); + //} + + + //@Override + //default void clear() { + // map.clear(); + //} + // + //@Override + //default Set keySet() { + // return map.keySet(); + //} + // + //@Override + //default Collection values() { + // return map.values(); + //} + // + //@Override + //default Set> entrySet() { + // return map.entrySet(); + //} + + //@Override + //default String toString() { + // return JSON.toJSONString(this); + //} + +} diff --git a/APIJSONORM/src/main/java/apijson/JSONObject.java b/APIJSONORM/src/main/java/apijson/JSONObject.java deleted file mode 100755 index 6f4359019..000000000 --- a/APIJSONORM/src/main/java/apijson/JSONObject.java +++ /dev/null @@ -1,619 +0,0 @@ -/*Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. - -This source code is licensed under the Apache License Version 2.0.*/ - - -package apijson; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/**use this class instead of com.alibaba.fastjson.JSONObject - * @author Lemon - * @see #put - * @see #puts - * @see #putsAll - */ -public class JSONObject extends com.alibaba.fastjson.JSONObject { - private static final long serialVersionUID = 1L; - - private static final String TAG = "JSONObject"; - - - /**ordered - */ - public JSONObject() { - super(true); - } - /**transfer Object to JSONObject - * @param object - * @see {@link #JSONObject(Object)} - */ - public JSONObject(Object object) { - this(toJSONString(object)); - } - /**parse JSONObject with JSON String - * @param json - * @see {@link #JSONObject(String)} - */ - public JSONObject(String json) { - this(parseObject(json)); - } - /**transfer com.alibaba.fastjson.JSONObject to JSONObject - * @param object - * @see {@link #putsAll(Map)} - */ - public JSONObject(com.alibaba.fastjson.JSONObject object) { - this(); - putsAll(object); - } - - - - - //judge <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - public static final String KEY_ARRAY = "[]"; - - /**判断是否为Array的key - * @param key - * @return - */ - public static boolean isArrayKey(String key) { - return key != null && key.endsWith(KEY_ARRAY); - } - /**判断是否为对应Table的key - * @param key - * @return - */ - public static boolean isTableKey(String key) { - return StringUtil.isBigName(key); - } - /**判断是否为对应Table数组的 key - * @param key - * @return - */ - public static boolean isTableArray(String key) { - return isArrayKey(key) && isTableKey(key.substring(0, key.length() - KEY_ARRAY.length())); - } - //judge >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - //JSONObject内关键词 key <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - - public static String KEY_ID = "id"; - public static String KEY_ID_IN = KEY_ID + "{}"; - public static String KEY_USER_ID = "userId"; - public static String KEY_USER_ID_IN = KEY_USER_ID + "{}"; - - /**set "id":id in Table layer - * @param id - * @return - */ - public JSONObject setId(Long id) { - return puts(KEY_ID, id); - } - /**set "id{}":[] in Table layer - * @param list - * @return - */ - public JSONObject setIdIn(List list) { - return puts(KEY_ID_IN, list); - } - - /**set "userId":userId in Table layer - * @param id - * @return - */ - public JSONObject setUserId(Long id) { - return puts(KEY_USER_ID, id); - } - /**set "userId{}":[] in Table layer - * @param list - * @return - */ - public JSONObject setUserIdIn(List list) { - return puts(KEY_USER_ID_IN, list); - } - - - public static final int CACHE_ALL = 0; - public static final int CACHE_ROM = 1; - public static final int CACHE_RAM = 2; - - public static final String CACHE_ALL_STRING = "ALL"; - public static final String CACHE_ROM_STRING = "ROM"; - public static final String CACHE_RAM_STRING = "RAM"; - - - //@key关键字都放这个类 <<<<<<<<<<<<<<<<<<<<<< - public static final String KEY_TRY = "@try"; //尝试,忽略异常 - public static final String KEY_CATCH = "@catch"; //TODO 捕捉到异常后,处理方式 null-不处理;DEFAULT-返回默认值;ORIGIN-返回请求里的原始值 - public static final String KEY_DROP = "@drop"; //丢弃,不返回,TODO 应该通过 fastjson 的 ignore 之类的机制来处理,避免导致下面的对象也不返回 - // public static final String KEY_KEEP = "@keep"; //一定会返回,为 null 或 空对象时,会使用默认值(非空),解决其它对象因为不关联的第一个对为空导致也不返回 - public static final String KEY_DEFULT = "@default"; //TODO 自定义默认值 { "@default":true },@default 可完全替代 @keep - public static final String KEY_NULL = "@null"; //TODO 值为 null 的键值对 "@null":"tag,pictureList",允许 is NULL 条件判断, SET tag = NULL 修改值为 NULL 等 - public static final String KEY_CAST = "@cast"; //TODO 类型转换 cast(date AS DATE) - - public static final String KEY_ROLE = "@role"; //角色,拥有对某些数据的某些操作的权限 - public static final String KEY_DATABASE = "@database"; //数据库类型,默认为MySQL - public static final String KEY_DATASOURCE = "@datasource"; //数据源 - public static final String KEY_NAMESPACE = "@namespace"; //命名空间,Table 在非默认 namespace 内时需要声明 - public static final String KEY_CATALOG = "@catalog"; //目录,Table 在非默认 catalog 内时需要声明 - public static final String KEY_SCHEMA = "@schema"; //数据库,Table 在非默认 schema 内时需要声明 - public static final String KEY_EXPLAIN = "@explain"; //分析 true/false - public static final String KEY_CACHE = "@cache"; //缓存 RAM/ROM/ALL - public static final String KEY_COLUMN = "@column"; //查询的Table字段或SQL函数 - public static final String KEY_FROM = "@from"; //FROM语句 - public static final String KEY_COMBINE = "@combine"; //条件组合,每个条件key前面可以放&,|,!逻辑关系 "id!{},&sex,!name&$" - public static final String KEY_GROUP = "@group"; //分组方式 - public static final String KEY_HAVING = "@having"; //聚合函数条件,一般和@group一起用 - public static final String KEY_HAVING_AND = "@having&"; //聚合函数条件,一般和@group一起用 - public static final String KEY_ORDER = "@order"; //排序方式 - public static final String KEY_KEY = "@key"; // key 映射,year:left(date,4);name_tag:(name,tag) - public static final String KEY_RAW = "@raw"; // 自定义原始 SQL 片段 - public static final String KEY_JSON = "@json"; //SQL Server 把字段转为 JSON 输出 - public static final String KEY_METHOD = "@method"; // json 对象配置操作方法 - public static final String KEY_GET = "@get"; // json 对象配置操作方法 - public static final String KEY_GETS = "@gets"; // json 对象配置操作方法 - public static final String KEY_HEAD = "@head"; // json 对象配置操作方法 - public static final String KEY_HEADS = "@heads"; // json 对象配置操作方法 - public static final String KEY_POST = "@post"; // json 对象配置操作方法 - public static final String KEY_PUT = "@put"; // json 对象配置操作方法 - public static final String KEY_DELETE = "@delete"; // json 对象配置操作方法 - - public static final Map KEY_METHOD_ENUM_MAP; - - public static final List TABLE_KEY_LIST; - static { - TABLE_KEY_LIST = new ArrayList(); - TABLE_KEY_LIST.add(KEY_ROLE); - TABLE_KEY_LIST.add(KEY_DATABASE); - TABLE_KEY_LIST.add(KEY_DATASOURCE); - TABLE_KEY_LIST.add(KEY_NAMESPACE); - TABLE_KEY_LIST.add(KEY_CATALOG); - TABLE_KEY_LIST.add(KEY_SCHEMA); - TABLE_KEY_LIST.add(KEY_EXPLAIN); - TABLE_KEY_LIST.add(KEY_CACHE); - TABLE_KEY_LIST.add(KEY_COLUMN); - TABLE_KEY_LIST.add(KEY_FROM); - TABLE_KEY_LIST.add(KEY_NULL); - TABLE_KEY_LIST.add(KEY_CAST); - TABLE_KEY_LIST.add(KEY_COMBINE); - TABLE_KEY_LIST.add(KEY_GROUP); - TABLE_KEY_LIST.add(KEY_HAVING); - TABLE_KEY_LIST.add(KEY_HAVING_AND); - TABLE_KEY_LIST.add(KEY_ORDER); - TABLE_KEY_LIST.add(KEY_KEY); - TABLE_KEY_LIST.add(KEY_RAW); - TABLE_KEY_LIST.add(KEY_JSON); - TABLE_KEY_LIST.add(KEY_METHOD); - TABLE_KEY_LIST.add(KEY_GET); - TABLE_KEY_LIST.add(KEY_GETS); - TABLE_KEY_LIST.add(KEY_HEAD); - TABLE_KEY_LIST.add(KEY_HEADS); - TABLE_KEY_LIST.add(KEY_POST); - TABLE_KEY_LIST.add(KEY_PUT); - TABLE_KEY_LIST.add(KEY_DELETE); - - KEY_METHOD_ENUM_MAP = new LinkedHashMap<>(); - KEY_METHOD_ENUM_MAP.put(KEY_GET, RequestMethod.GET); - KEY_METHOD_ENUM_MAP.put(KEY_GETS, RequestMethod.GETS); - KEY_METHOD_ENUM_MAP.put(KEY_HEAD, RequestMethod.HEAD); - KEY_METHOD_ENUM_MAP.put(KEY_HEADS, RequestMethod.HEADS); - KEY_METHOD_ENUM_MAP.put(KEY_POST, RequestMethod.POST); - KEY_METHOD_ENUM_MAP.put(KEY_PUT, RequestMethod.PUT); - KEY_METHOD_ENUM_MAP.put(KEY_DELETE, RequestMethod.DELETE); - } - - //@key关键字都放这个类 >>>>>>>>>>>>>>>>>>>>>> - - - /**set try, ignore exceptions - * @param tri - * @return this - */ - public JSONObject setTry(Boolean tri) { - return puts(KEY_TRY, tri); - } - - /**set catch - * @param isCatch - * @return this - */ - public JSONObject setCatch(String isCatch) { - return puts(KEY_CATCH, isCatch); - } - /**set drop, data dropped will not return - * @param drop - * @return this - */ - public JSONObject setDrop(Boolean drop) { - return puts(KEY_DROP, drop); - } - - /**set if has default - * @param hasDefault - * @return this - */ - public JSONObject setDefault(Boolean hasDefault) { - return puts(KEY_DEFULT, hasDefault); - } - - - /**set role of request sender - * @param role - * @return this - */ - public JSONObject setRole(String role) { - return puts(KEY_ROLE, role); - } - /**set database where table was puts - * @param database - * @return this - */ - public JSONObject setDatabase(String database) { - return puts(KEY_DATABASE, database); - } - /**set datasource where table was puts - * @param datasource - * @return this - */ - public JSONObject setDatasource(String datasource) { - return puts(KEY_DATASOURCE, datasource); - } - /**set namespace where table was puts - * @param namespace - * @return this - */ - public JSONObject setNamespace(String namespace) { - return puts(KEY_NAMESPACE, namespace); - } - /**set catalog where table was puts - * @param catalog - * @return this - */ - public JSONObject setCatalog(String catalog) { - return puts(KEY_CATALOG, catalog); - } - /**set schema where table was puts - * @param schema - * @return this - */ - public JSONObject setSchema(String schema) { - return puts(KEY_SCHEMA, schema); - } - /**set if return explain informations - * @param explain - * @return - */ - public JSONObject setExplain(Boolean explain) { - return puts(KEY_EXPLAIN, explain); - } - /**set cache type - * @param cache - * @return - * @see {@link #CACHE_ALL} - * @see {@link #CACHE_RAM} - * @see {@link #CACHE_ROM} - */ - public JSONObject setCache(Integer cache) { - return puts(KEY_CACHE, cache); - } - /**set cache type - * @param cache - * @return - * @see {@link #CACHE_ALL_STRING} - * @see {@link #CACHE_RAM_STRING} - * @see {@link #CACHE_ROM_STRING} - */ - public JSONObject setCache(String cache) { - return puts(KEY_CACHE, cache); - } - - /**set keys need to be returned - * @param keys key0, key1, key2 ... - * @return {@link #setColumn(String)} - */ - public JSONObject setColumn(String... keys) { - return setColumn(StringUtil.getString(keys, true)); - } - /**set keys need to be returned - * @param keys "key0,key1,key2..." - * @return - */ - public JSONObject setColumn(String keys) { - return puts(KEY_COLUMN, keys); - } - - /**set keys whose value is null - * @param keys key0, key1, key2 ... - * @return {@link #setNull(String)} - */ - public JSONObject setNull(String... keys) { - return setNull(StringUtil.getString(keys, true)); - } - /**set keys whose value is null - * @param keys "key0,key1,key2..." - * @return - */ - public JSONObject setNull(String keys) { - return puts(KEY_NULL, keys); - } - - /**set keys and types whose value should be cast to type, cast(value AS DATE) - * @param keyTypes key0:type0, key1:type1, key2:type2 ... - * @return {@link #setCast(String)} - */ - public JSONObject setCast(String... keyTypes) { - return setCast(StringUtil.getString(keyTypes, true)); - } - /**set keys and types whose value should be cast to type, cast(value AS DATE) - * @param keyTypes "key0:type0,key1:type1,key2:type2..." - * @return - */ - public JSONObject setCast(String keyTypes) { - return puts(KEY_CAST, keyTypes); - } - - /**set combination of keys for conditions - * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... - * @return {@link #setColumn(String)} - */ - public JSONObject setCombine(String... keys) { - return setCombine(StringUtil.getString(keys, true)); - } - /**set combination of keys for conditions - * @param keys key0,&key1,|key2,!key3 ... TODO or key0> | (key1{} & !key2)... - * @return - */ - public JSONObject setCombine(String keys) { - return puts(KEY_COMBINE, keys); - } - - /**set keys for group by - * @param keys key0, key1, key2 ... - * @return {@link #setGroup(String)} - */ - public JSONObject setGroup(String... keys) { - return setGroup(StringUtil.getString(keys, true)); - } - /**set keys for group by - * @param keys "key0,key1,key2..." - * @return - */ - public JSONObject setGroup(String keys) { - return puts(KEY_GROUP, keys); - } - - /**set keys for having - * @param keys count(key0) > 1, sum(key1) <= 5, function2(key2) ? value2 ... - * @return {@link #setHaving(String)} - */ - public JSONObject setHaving(String... keys) { - return setHaving(StringUtil.getString(keys, true)); - } - /**set keys for having - * @param keys "key0,key1,key2..." - * @return - */ - public JSONObject setHaving(String keys) { - return setHaving(keys, false); - } - /**set keys for having - * @param keys "key0,key1,key2..." - * @return - */ - public JSONObject setHaving(String keys, boolean isAnd) { - return puts(isAnd ? KEY_HAVING_AND : KEY_HAVING, keys); - } - - /**set keys for order by - * @param keys key0, key1+, key2- ... - * @return {@link #setOrder(String)} - */ - public JSONObject setOrder(String... keys) { - return setOrder(StringUtil.getString(keys, true)); - } - /**set keys for order by - * @param keys "key0,key1+,key2-..." - * @return - */ - public JSONObject setOrder(String keys) { - return puts(KEY_ORDER, keys); - } - - /**set key map - * @param keyMap "name_tag:(name,tag);year:left(date,1,5)..." - * @return - */ - public JSONObject setKey(String keyMap) { - return puts(KEY_KEY, keyMap); - } - - /**set keys to raw - * @param keys "key0,key1,key2..." - * @return - */ - public JSONObject setRaw(String keys) { - return puts(KEY_RAW, keys); - } - - /**set keys to cast to json - * @param keys "key0,key1,key2..." - * @return - */ - public JSONObject setJson(String keys) { - return puts(KEY_JSON, keys); - } - - //JSONObject内关键词 key >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - //Request <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - - /** - * @param key - * @param keys path = keys[0] + "/" + keys[1] + "/" + keys[2] + ... - * @return {@link #puts(String, Object)} - */ - public JSONObject putsPath(String key, String... keys) { - return puts(key+"@", StringUtil.getString(keys, "/")); - } - - /** - * @param key - * @param isNull - * @return {@link #puts(String, Object)} - */ - public JSONObject putsNull(String key, boolean isNull) { - return puts(key+"{}", SQL.isNull(isNull)); - } - /** - * trim = false - * @param key - * @param isEmpty - * @return {@link #putsEmpty(String, boolean, boolean)} - */ - public JSONObject putsEmpty(String key, boolean isEmpty) { - return putsEmpty(key, isEmpty, false); - } - /** - * @param key - * @param isEmpty - * @return {@link #puts(String, Object)} - */ - public JSONObject putsEmpty(String key, boolean isEmpty, boolean trim) { - return puts(key+"{}", SQL.isEmpty(key, isEmpty, trim)); - } - /** - * @param key - * @param compare <=0, >5 ... - * @return {@link #puts(String, Object)} - */ - public JSONObject putsLength(String key, String compare) { - return puts(key+"{}", SQL.length(key) + compare); - } - /** - * @param key - * @param compare <=, > ... - * @param value 1, 5, 3.14, -99 ... - * @return {@link #puts(String, Object)} - */ - public JSONObject putsLength(String key, String compare, Object value) { - return puts(key+"["+(StringUtil.isEmpty(compare) || "=".equals(compare) ? "" : ("!=".equals(compare) ? "!" : compare)), value); - } - /** - * @param key - * @param compare <=0, >5 ... - * @return {@link #puts(String, Object)} - */ - public JSONObject putsJSONLength(String key, String compare) { - return puts(key+"{}", SQL.json_length(key) + compare); - } - /** - * @param key - * @param compare <=0, >5 ... - * @return {@link #puts(String, Object)} - */ - public JSONObject putsJSONLength(String key, String compare, Object value) { - return puts(key + "{" + (StringUtil.isEmpty(compare) || "=".equals(compare) ? "" : ("!=".equals(compare) ? "!" : compare)), value); - } - - /**设置搜索 - * type = SEARCH_TYPE_CONTAIN_FULL - * @param key - * @param value - * @return {@link #putsSearch(String, String, int)} - */ - public JSONObject putsSearch(String key, String value) { - return putsSearch(key, value, SQL.SEARCH_TYPE_CONTAIN_FULL); - } - /**设置搜索 - * @param key - * @param value - * @param type - * @return {@link #puts(String, Object)} - */ - public JSONObject putsSearch(String key, String value, int type) { - return puts(key+"$", SQL.search(value, type)); - } - - //Request >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - - /**puts key-value in object into this - * @param map - * @return this - */ - public JSONObject putsAll(Map map) { - putAll(map); - return this; - } - @Override - public void putAll(Map map) { - if (map != null && map.isEmpty() == false) { - super.putAll(map); - } - } - - - - /**put and return this - * @param value must be annotated by {@link MethodAccess} - * @return {@link #puts(String, Object)} - */ - public JSONObject puts(Object value) { - return puts(null, value); - } - /**put and return this - * @param key - * @param value - * @return this - * @see {@link #put(String, Object)} - */ - public JSONObject puts(String key, Object value) { - put(key, value); - return this; - } - - /**put and return value - * @param value must be annotated by {@link MethodAccess} - * @return {@link #put(String, Object)} - */ - public Object put(Object value) { - return put(null, value); - } - /**put and return value - * @param key StringUtil.isEmpty(key, true) ? key = value.getClass().getSimpleName(); - * @param value - * @return value - */ - @Override - public Object put(String key, Object value) { - if (value == null) { - Log.e(TAG, "put value == null >> return null;"); - return null; - } - if (StringUtil.isEmpty(key, true)) { - Class clazz = value.getClass(); //should not return null - if (clazz.getAnnotation(MethodAccess.class) == null) { - throw new IllegalArgumentException("puts StringUtil.isEmpty(key, true)" + - " clazz.getAnnotation(MethodAccess.class) == null" + - " \n key为空时仅支持 类型被@MethodAccess注解 的value !!!" + - " \n 如果一定要这么用,请对 " + clazz.getName() + " 注解!" + - " \n 如果是类似 key[]:{} 结构的请求,建议用 putsAll(...) !"); - } - key = value.getClass().getSimpleName(); - } - return super.put(key, value); - } - - - -} diff --git a/APIJSONORM/src/main/java/apijson/JSONParser.java b/APIJSONORM/src/main/java/apijson/JSONParser.java new file mode 100755 index 000000000..7c38a39df --- /dev/null +++ b/APIJSONORM/src/main/java/apijson/JSONParser.java @@ -0,0 +1,33 @@ +/*Copyright (C) 2020 Tencent. All rights reserved. + +This source code is licensed under the Apache License Version 2.0.*/ + + +package apijson; + +import java.util.List; +import java.util.Map; + +/**JSON 相关解析器 + * @author Lemon + */ +public interface JSONParser, L extends List> extends JSONCreator { + + Object parse(Object json); + + M parseObject(Object json); + + T parseObject(Object json, Class clazz); + + L parseArray(Object json); + + List parseArray(Object json, Class clazz); + + default String format(Object obj) { + return toJSONString(obj, true); + } + default String toJSONString(Object obj) { + return toJSONString(obj, false); + } + String toJSONString(Object obj, boolean format); +} diff --git a/APIJSONORM/src/main/java/apijson/JSONRequest.java b/APIJSONORM/src/main/java/apijson/JSONRequest.java index 62d724199..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.*/ @@ -6,6 +6,7 @@ package apijson; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -15,35 +16,40 @@ * @author Lemon * @see #puts * @see #toArray - * @use JSONRequest request = new JSONRequest(...); + * @use JSONRequest request = JSON.createJSONObject(...); *
request.puts(...);//not a must *
request.toArray(...);//not a must */ -public class JSONRequest extends JSONObject { - private static final long serialVersionUID = 1L; - - public JSONRequest() { - super(); - } - /** - * @param object must be annotated by {@link MethodAccess} - * @see {@link #JSONRequest(String, Object)} - */ - public JSONRequest(Object object) { - this(null, object); - } - /** - * @param name - * @param object - * @see {@link #puts(String, Object)} - */ - public JSONRequest(String name, Object object) { - this(); - puts(name, object); - } - - - +public interface JSONRequest, L extends List> extends JSONMap { + + //default JSONRequest() { + // super(); + //} + ///** + // * @param object must be annotated by {@link MethodAccess} + // * @see {@link #JSONRequest(String, Object)} + // */ + //default JSONRequest(Object object) { + // this(null, object); + //} + ///** + // * @param name + // * @param object + // * @see {@link #puts(String, Object)} + // */ + //default JSONRequest(String name, Object object) { + // this(); + // puts(name, object); + //} + + //public static JSONRequest valueOf(Object obj) { + // JSONRequest req = new JSONRequest() {}; + // Map m = JSON.parseObject(obj); + // if (m != null && ! m.isEmpty()) { + // req.map.putAll(m); + // } + // return req; + //} public static final String KEY_TAG = "tag";//只在最外层,最外层用JSONRequest public static final String KEY_VERSION = "version";//只在最外层,最外层用JSONRequest @@ -54,23 +60,25 @@ public JSONRequest(String name, Object object) { * @param tag * @return */ - public JSONRequest setTag(String tag) { + default JSONRequest setTag(String tag) { return puts(KEY_TAG, tag); } + /**set "version":version in outermost layer * for target version of request * @param version * @return */ - public JSONRequest setVersion(Integer version) { + default JSONRequest setVersion(Integer version) { return puts(KEY_VERSION, version); } + /**set "format":format in outermost layer * for format APIJSON special keys to normal keys of response * @param format * @return */ - public JSONRequest setFormat(Boolean format) { + default JSONRequest setFormat(Boolean format) { return puts(KEY_FORMAT, format); } @@ -80,14 +88,14 @@ public JSONRequest setFormat(Boolean format) { public static final int QUERY_TABLE = 0; public static final int QUERY_TOTAL = 1; public static final int QUERY_ALL = 2; - + public static final String QUERY_TABLE_STRING = "TABLE"; public static final String QUERY_TOTAL_STRING = "TOTAL"; public static final String QUERY_ALL_STRING = "ALL"; public static final String SUBQUERY_RANGE_ALL = "ALL"; public static final String SUBQUERY_RANGE_ANY = "ANY"; - + public static final String KEY_QUERY = "query"; public static final String KEY_COMPAT = "compat"; public static final String KEY_COUNT = "count"; @@ -96,17 +104,9 @@ public JSONRequest setFormat(Boolean format) { public static final String KEY_SUBQUERY_RANGE = "range"; public static final String KEY_SUBQUERY_FROM = "from"; - public static final List ARRAY_KEY_LIST; - static { - ARRAY_KEY_LIST = new ArrayList(); - ARRAY_KEY_LIST.add(KEY_QUERY); - ARRAY_KEY_LIST.add(KEY_COMPAT); - ARRAY_KEY_LIST.add(KEY_COUNT); - ARRAY_KEY_LIST.add(KEY_PAGE); - ARRAY_KEY_LIST.add(KEY_JOIN); - ARRAY_KEY_LIST.add(KEY_SUBQUERY_RANGE); - ARRAY_KEY_LIST.add(KEY_SUBQUERY_FROM); - } + public static final List ARRAY_KEY_LIST = new ArrayList<>(Arrays.asList( + KEY_QUERY, KEY_COMPAT ,KEY_COUNT, KEY_PAGE, KEY_JOIN, KEY_SUBQUERY_RANGE, KEY_SUBQUERY_FROM + )); /**set what to query in Array layer * @param query what need to query, Table,total,ALL? @@ -115,86 +115,95 @@ public JSONRequest setFormat(Boolean format) { * @see {@link #QUERY_TOTAL} * @see {@link #QUERY_ALL} */ - public JSONRequest setQuery(int query) { + default JSONRequest setQuery(int query) { return puts(KEY_QUERY, query); } + /**set maximum count of Tables to query in Array layer * @param count <= 0 || >= max ? max : count * @return */ - public JSONRequest setCount(int count) { + default JSONRequest setCount(int count) { return puts(KEY_COUNT, count); } + /**set page of Tables to query in Array layer * @param page <= 0 ? 0 : page * @return */ - public JSONRequest setPage(int page) { + default JSONRequest setPage(int page) { return puts(KEY_PAGE, page); } - + /**set joins of Main Table and it's Vice Tables in Array layer * @param joins "@/User/id@", "&/User/id@,>/Comment/momentId@" ... * @return */ - public JSONRequest setJoin(String... joins) { - return puts(KEY_JOIN, StringUtil.getString(joins)); + default JSONRequest setJoin(String... joins) { + return setJson(this, StringUtil.get(joins)); } - + + public static > M setJson(M m, String... joins) { + m.put(KEY_JOIN, StringUtil.get(joins)); + return m; + } + /**set range for Subquery * @param range * @return * @see {@link #SUBQUERY_RANGE_ALL} * @see {@link #SUBQUERY_RANGE_ANY} */ - public JSONRequest setSubqueryRange(String range) { + default JSONRequest setSubqueryRange(String range) { return puts(KEY_SUBQUERY_RANGE, range); } - + /**set from for Subquery * @param from * @return */ - public JSONRequest setSubqueryFrom(String from) { + default JSONRequest setSubqueryFrom(String from) { return puts(KEY_SUBQUERY_FROM, from); } - - //array object >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + //array object >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - /**create a parent JSONObject named KEY_ARRAY + /**create a parent JSONMap named KEY_ARRAY * @param count * @param page * @return {@link #toArray(int, int)} */ - public JSONRequest toArray(int count, int page) { + default M toArray(int count, int page) { return toArray(count, page, null); } - /**create a parent JSONObject named name+KEY_ARRAY. + + /**create a parent JSONMap named name+KEY_ARRAY. * @param count * @param page * @param name * @return {name+KEY_ARRAY : this}. if needs to be put, use {@link #putsAll(Map)} instead */ - public JSONRequest toArray(int count, int page, String name) { - return new JSONRequest(StringUtil.getString(name) + KEY_ARRAY, this.setCount(count).setPage(page)); + default M toArray(int count, int page, String name) { + return JSON.createJSONObject(StringUtil.get(name) + KEY_ARRAY, this.setCount(count).setPage(page)); } @Override - public JSONObject putsAll(Map map) { - super.putsAll(map); + default JSONRequest putsAll(Map map) { + putAll(map); return this; } @Override - public JSONRequest puts(Object value) { - return puts(null, value); + default JSONRequest puts(Object value) { + put(value); + return this; } + @Override - public JSONRequest puts(String key, Object value) { - super.puts(key, value); + default JSONRequest puts(String key, Object value) { + put(key, value); return this; } diff --git a/APIJSONORM/src/main/java/apijson/JSONResponse.java b/APIJSONORM/src/main/java/apijson/JSONResponse.java index 21f3fe8f6..7527adffb 100755 --- a/APIJSONORM/src/main/java/apijson/JSONResponse.java +++ b/APIJSONORM/src/main/java/apijson/JSONResponse.java @@ -1,15 +1,11 @@ -/*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; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import java.util.List; -import java.util.Set; +import java.util.*; /**parser for response * @author Lemon @@ -19,8 +15,8 @@ *
User user = response.getObject(User.class);//not a must *
List commenntList = response.getList("Comment[]", Comment.class);//not a must */ -public class JSONResponse extends apijson.JSONObject { - private static final long serialVersionUID = 1L; +public interface JSONResponse, L extends List> extends JSONMap { + static final String TAG = "JSONResponse"; // 节约性能和减少 bug,除了关键词 @key ,一般都符合变量命名规范,不符合也原样返回便于调试 /**格式化带 - 中横线的单词 @@ -33,17 +29,22 @@ public class JSONResponse extends apijson.JSONObject { */ public static boolean IS_FORMAT_DOLLAR = false; - private static final String TAG = "JSONResponse"; - public JSONResponse() { - super(); - } - public JSONResponse(String json) { - this(parseObject(json)); - } - public JSONResponse(JSONObject object) { - super(format(object)); - } + //default JSONResponse() { + // super(); + //} + //default JSONResponse(Object json) { + // this(parseObject(json)); + //} + //default JSONResponse(Object json, JSONParser parser) { + // this(parseObject(json, parser)); + //} + //default JSONResponse(Map object) { + // super(format(object)); + //} + //default JSONResponse(M object, JSONCreator creator) { + // super(format(object, creator)); + //} //状态信息,非GET请求获得的信息<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @@ -81,9 +82,9 @@ public JSONResponse(JSONObject object) { /**获取状态 * @return */ - public int getCode() { + default int getCode() { try { - return getIntValue(KEY_CODE); + return JSON.getIntValue(this, KEY_CODE); } catch (Exception e) { //empty } @@ -92,9 +93,9 @@ public int getCode() { /**获取状态 * @return */ - public static int getCode(JSONObject reponse) { + public static int getCode(Map reponse) { try { - return reponse.getIntValue(KEY_CODE); + return JSON.getIntValue(reponse, KEY_CODE); } catch (Exception e) { //empty } @@ -103,22 +104,22 @@ public static int getCode(JSONObject reponse) { /**获取状态描述 * @return */ - public String getMsg() { - return getString(KEY_MSG); + default String getMsg() { + return JSON.getString(this, KEY_MSG); } /**获取状态描述 - * @param reponse + * @param response * @return */ - public static String getMsg(JSONObject reponse) { - return reponse == null ? null : reponse.getString(KEY_MSG); + public static String getMsg(Map response) { + return response == null ? null : JSON.getString(response, KEY_MSG); } /**获取id * @return */ - public long getId() { + default long getId() { try { - return getLongValue(KEY_ID); + return JSON.getLongValue(this, getIdKey()); } catch (Exception e) { //empty } @@ -127,9 +128,9 @@ public long getId() { /**获取数量 * @return */ - public int getCount() { + default int getCount() { try { - return getIntValue(KEY_COUNT); + return JSON.getIntValue(this, KEY_COUNT); } catch (Exception e) { //empty } @@ -138,9 +139,9 @@ public int getCount() { /**获取总数 * @return */ - public int getTotal() { + default int getTotal() { try { - return getIntValue(KEY_TOTAL); + return JSON.getIntValue(this, KEY_TOTAL); } catch (Exception e) { //empty } @@ -151,7 +152,7 @@ public int getTotal() { /**是否成功 * @return */ - public boolean isSuccess() { + default boolean isSuccess() { return isSuccess(getCode()); } /**是否成功 @@ -165,21 +166,21 @@ public static boolean isSuccess(int code) { * @param response * @return */ - public static boolean isSuccess(JSONResponse response) { + public static boolean isSuccess(JSONResponse response) { return response != null && response.isSuccess(); } /**是否成功 * @param response * @return */ - public static boolean isSuccess(JSONObject response) { - return response != null && isSuccess(response.getIntValue(KEY_CODE)); - } + public static boolean isSuccess(Map response) { + return response != null && isSuccess(JSON.getIntValue(response, KEY_CODE)); + } /**校验服务端是否存在table * @return */ - public boolean isExist() { + default boolean isExist() { return isExist(getCount()); } /**校验服务端是否存在table @@ -193,24 +194,28 @@ public static boolean isExist(int count) { * @param response * @return */ - public static boolean isExist(JSONResponse response) { + public static boolean isExist(JSONResponse response) { return response != null && response.isExist(); } + public static boolean isExist(Map response) { + return response != null && isExist(JSON.getIntValue(response, KEY_COUNT)); + } /**获取内部的JSONResponse * @param key * @return */ - public JSONResponse getJSONResponse(String key) { + default JSONResponse getJSONResponse(String key) { return getObject(key, JSONResponse.class); } + //cannot get javaBeanDeserizer // /**获取内部的JSONResponse // * @param response // * @param key // * @return // */ - // public static JSONResponse getJSONResponse(JSONObject response, String key) { + // public static JSONResponse getJSONResponse(JSONRequest response, String key) { // return response == null ? null : response.getObject(key, JSONResponse.class); // } //状态信息,非GET请求获得的信息>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -221,7 +226,7 @@ public JSONResponse getJSONResponse(String key) { * @param clazz * @return */ - public T getObject(Class clazz) { + default T getObject(Class clazz) { return getObject(clazz == null ? "" : clazz.getSimpleName(), clazz); } /** @@ -229,7 +234,7 @@ public T getObject(Class clazz) { * @param clazz * @return */ - public T getObject(String key, Class clazz) { + default T getObject(String key, Class clazz) { return getObject(this, key, clazz); } /** @@ -238,55 +243,47 @@ public T getObject(String key, Class clazz) { * @param clazz * @return */ - public static T getObject(JSONObject object, String key, Class clazz) { - return toObject(object == null ? null : object.getJSONObject(formatObjectKey(key)), clazz); + public static T getObject( + Map object, String key, Class clazz) { + return toObject(object == null ? null : JSON.get(object, formatObjectKey(key)), clazz); } /** * @param clazz * @return */ - public T toObject(Class clazz) { + default T toObject(Class clazz) { return toObject(this, clazz); } + /** * @param object * @param clazz * @return */ - public static T toObject(JSONObject object, Class clazz) { - return JSON.parseObject(JSON.toJSONString(object), clazz); + public static , L extends List> T toObject( + Map object, Class clazz) { + return JSON.parseObject(object, clazz); } - - /** - * key = KEY_ARRAY - * @param clazz - * @return - */ - public List getList(Class clazz) { - return getList(KEY_ARRAY, clazz); - } /** * arrayObject = this * @param key - * @param clazz * @return */ - public List getList(String key, Class clazz) { - return getList(this, key, clazz); + default List getList(String key) { + return JSON.getList(this, key); } /** * key = KEY_ARRAY * @param object - * @param clazz * @return */ - public static List getList(JSONObject object, Class clazz) { - return getList(object, KEY_ARRAY, clazz); + public static List getList(Map object) { + return JSON.getList(object, KEY_ARRAY); } /** * @param object @@ -294,29 +291,29 @@ public static List getList(JSONObject object, Class clazz) { * @param clazz * @return */ - public static List getList(JSONObject object, String key, Class clazz) { - return object == null ? null : JSON.parseArray(object.getString(formatArrayKey(key)), clazz); + public static > List getList(Map object, String key, Class clazz) { + return object == null ? null : JSON.parseArray(JSON.getString(object, formatArrayKey(key)), clazz); } /** * key = KEY_ARRAY * @return */ - public JSONArray getArray() { + default > L getArray() { return getArray(KEY_ARRAY); } /** * @param key * @return */ - public JSONArray getArray(String key) { + default > L getArray(String key) { return getArray(this, key); } /** * @param object * @return */ - public static JSONArray getArray(JSONObject object) { + public static > L getArray(Map object) { return getArray(object, KEY_ARRAY); } /** @@ -325,28 +322,29 @@ public static JSONArray getArray(JSONObject object) { * @param key * @return */ - public static JSONArray getArray(JSONObject object, String key) { - return object == null ? null : object.getJSONArray(formatArrayKey(key)); + public static > L getArray(Map object, String key) { + return object == null ? null : JSON.get(object, formatArrayKey(key)); } // /** // * @return // */ - // public JSONObject format() { + // default JSONRequest format() { // return format(this); // } /**格式化key名称 * @param object * @return */ - public static JSONObject format(final JSONObject object) { + public static , L extends List> M format(final M object) { //太长查看不方便,不如debug Log.i(TAG, "format object = \n" + JSON.toJSONString(object)); if (object == null || object.isEmpty()) { Log.i(TAG, "format object == null || object.isEmpty() >> return object;"); return object; } - JSONObject formatedObject = new JSONObject(true); + + M formatedObject = JSON.createJSONObject(); Set set = object.keySet(); if (set != null) { @@ -355,11 +353,11 @@ public static JSONObject format(final JSONObject object) { for (String key : set) { value = object.get(key); - if (value instanceof JSONArray) {//JSONArray,遍历来format内部项 - formatedObject.put(formatArrayKey(key), format((JSONArray) value)); + if (value instanceof List) {//JSONList,遍历来format内部项 + formatedObject.put(formatArrayKey(key), format((L) value)); } - else if (value instanceof JSONObject) {//JSONObject,往下一级提取 - formatedObject.put(formatObjectKey(key), format((JSONObject) value)); + else if (value instanceof Map) {//JSONRequest,往下一级提取 + formatedObject.put(formatObjectKey(key), format((M) value)); } else {//其它Object,直接填充 formatedObject.put(formatOtherKey(key), value); @@ -375,30 +373,30 @@ else if (value instanceof JSONObject) {//JSONObject,往下一级提取 * @param array * @return */ - public static JSONArray format(final JSONArray array) { + public static , L extends List> L format(final L array) { //太长查看不方便,不如debug Log.i(TAG, "format array = \n" + JSON.toJSONString(array)); if (array == null || array.isEmpty()) { Log.i(TAG, "format array == null || array.isEmpty() >> return array;"); return array; } - JSONArray formatedArray = new JSONArray(); + L formattedArray = JSON.createJSONArray(); Object value; for (int i = 0; i < array.size(); i++) { value = array.get(i); - if (value instanceof JSONArray) {//JSONArray,遍历来format内部项 - formatedArray.add(format((JSONArray) value)); + if (value instanceof List) {//JSONList,遍历来format内部项 + formattedArray.add(format((L) value)); } - else if (value instanceof JSONObject) {//JSONObject,往下一级提取 - formatedArray.add(format((JSONObject) value)); + else if (value instanceof Map) {//JSONRequest,往下一级提取 + formattedArray.add(format((M) value)); } else {//其它Object,直接填充 - formatedArray.add(value); + formattedArray.add(value); } } - //太长查看不方便,不如debug Log.i(TAG, "format return formatedArray = " + JSON.toJSONString(formatedArray)); - return formatedArray; + //太长查看不方便,不如debug Log.i(TAG, "format return formattedArray = " + JSON.toJSONString(formattedArray)); + return formattedArray; } @@ -414,10 +412,10 @@ public static String getTableName(String fullName) { /**获取变量名 * @param fullName - * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, boolean)} formatColon = true, formatAt = true, formatHyphen = true, firstCase = true + * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, Boolean)} formatColon = true, formatAt = true, formatHyphen = true, firstCase = true */ public static String getVariableName(String fullName) { - if (isArrayKey(fullName)) { + if (JSONMap.isArrayKey(fullName)) { fullName = StringUtil.addSuffix(fullName.substring(0, fullName.length() - 2), "list"); } return formatKey(fullName, true, true, true, true, false, true); @@ -425,10 +423,10 @@ public static String getVariableName(String fullName) { /**格式化数组的名称 key[] => keyList; key:alias[] => aliasList; Table-column[] => tableColumnList * @param key empty ? "list" : key + "List" 且首字母小写 - * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = true, firstCase = true + * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, Boolean)} formatColon = false, formatAt = true, formatHyphen = true, firstCase = true */ public static String formatArrayKey(String key) { - if (isArrayKey(key)) { + if (JSONMap.isArrayKey(key)) { key = StringUtil.addSuffix(key.substring(0, key.length() - 2), "list"); } int index = key == null ? -1 : key.indexOf(":"); @@ -441,7 +439,7 @@ public static String formatArrayKey(String key) { /**格式化对象的名称 name => name; name:alias => alias * @param key name 或 name:alias - * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = true + * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, Boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = true */ public static String formatObjectKey(String key) { int index = key == null ? -1 : key.indexOf(":"); @@ -454,7 +452,7 @@ public static String formatObjectKey(String key) { /**格式化普通值的名称 name => name; name:alias => alias * @param fullName name 或 name:alias - * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = false + * @return {@link #formatKey(String, boolean, boolean, boolean, boolean, boolean, Boolean)} formatColon = false, formatAt = true, formatHyphen = false, firstCase = false */ public static String formatOtherKey(String fullName) { return formatKey(fullName, false, true, IS_FORMAT_HYPHEN, IS_FORMAT_UNDERLINE, IS_FORMAT_DOLLAR diff --git a/APIJSONORM/src/main/java/apijson/Log.java b/APIJSONORM/src/main/java/apijson/Log.java index 86c2e61cc..9cbacd0ac 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.*/ @@ -14,7 +14,7 @@ public class Log { public static boolean DEBUG = true; - public static final String VERSION = "7.8.0"; + public static final String VERSION = "8.1.3"; public static final String KEY_SYSTEM_INFO_DIVIDER = "\n---|-----APIJSON SYSTEM INFO-----|---\n"; public static final String OS_NAME; diff --git a/APIJSONORM/src/main/java/apijson/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 6cec79bd2..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.*/ @@ -242,7 +242,7 @@ public static String toLowerCase(String s) { * @return column.isEmpty() ? "*" : column; */ public static String column(String column) { - column = StringUtil.getTrimedString(column); + column = StringUtil.trim(column); return column.isEmpty() ? "*" : column; } /**有别名的字段 diff --git a/APIJSONORM/src/main/java/apijson/StringUtil.java b/APIJSONORM/src/main/java/apijson/StringUtil.java index c6caf21e7..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.*/ @@ -53,171 +53,317 @@ public StringUtil() { public static final String YUAN = "元"; - private static String currentString = ""; - /**获取刚传入处理后的string + private static String current = ""; + /**获取刚传入处理后的 string + * @must 上个影响 current 的方法 和 这个方法都应该在同一线程中,否则返回值可能不对 + * @return + */ + public static String cur() { + return get(current); + } + + /**FIXME 改用 cur * @must 上个影响currentString的方法 和 这个方法都应该在同一线程中,否则返回值可能不对 * @return */ + @Deprecated public static String getCurrentString() { - return currentString == null ? "" : currentString; + return cur(); } //获取string,为null时返回"" <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**获取string,为null则返回"" + * @param obj + * @return + */ + public static String get(Object obj) { + return obj == null ? "" : obj.toString(); + } + /**获取string,为null则返回"" + * @param s + * @return + */ + public static String get(String s) { + return s == null ? "" : s; + } + /**获取string,为null则返回"" + * ignoreEmptyItem = false; + * split = "," + * @param arr + * @return {@link #get(Object[], boolean)} + */ + public static String get(Object[] arr) { + return get(arr, false); + } + /**获取string,为null则返回"" + * split = "," + * @param arr + * @param ignoreEmptyItem + * @return {@link #get(Object[], boolean)} + */ + public static String get(Object[] arr, boolean ignoreEmptyItem) { + return get(arr, null, ignoreEmptyItem); + } + /**获取string,为null则返回"" + * ignoreEmptyItem = false; + * @param arr + * @param split + * @return {@link #get(Object[], String, boolean)} + */ + public static String get(Object[] arr, String split) { + return get(arr, split, false); + } + //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 + /**获取string,为null则返回"" + * @param arr -the str arr given + * @param split -the token used to split + * @param ignoreEmptyItem -whether to ignore empty item or not + * @return {@link #get(Object[], String, boolean)} + *

Here we replace the simple "+" way of concatenating with Stringbuilder 's append

+ */ + public static String get(Object[] arr, String split, boolean ignoreEmptyItem) { + StringBuilder s = new StringBuilder(""); + if (arr != null) { + if (split == null) { + split = ","; + } + for (int i = 0; i < arr.length; i++) { + if (ignoreEmptyItem && isEmpty(arr[i], true)) { + continue; + } + s.append(((i > 0 ? split : "") + arr[i])); + } + } + return get(s.toString()); + } + + /**FIXME 用 get 替代 * @param object * @return */ + @Deprecated public static String getString(Object object) { return object == null ? "" : object.toString(); } - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * @param cs * @return */ + @Deprecated public static String getString(CharSequence cs) { return cs == null ? "" : cs.toString(); } - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * @param s * @return */ + @Deprecated public static String getString(String s) { return s == null ? "" : s; } - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * ignoreEmptyItem = false; * split = "," * @param array - * @return {@link #getString(Object[], boolean)} + * @return {@link #get(Object[], boolean)} */ + @Deprecated public static String getString(Object[] array) { - return getString(array, false); + return get(array, false); } - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * split = "," * @param array * @param ignoreEmptyItem - * @return {@link #getString(Object[], boolean)} + * @return {@link #get(Object[], boolean)} */ + @Deprecated public static String getString(Object[] array, boolean ignoreEmptyItem) { - return getString(array, null, ignoreEmptyItem); + return get(array, null, ignoreEmptyItem); } - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * ignoreEmptyItem = false; * @param array * @param split - * @return {@link #getString(Object[], String, boolean)} + * @return {@link #get(Object[], String, boolean)} */ + @Deprecated public static String getString(Object[] array, String split) { - return getString(array, split, false); + return get(array, split, false); } //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/182 - /**获取string,为null则返回"" + /**FIXME 用 get 替代 * @param array -the str array given * @param split -the token used to split * @param ignoreEmptyItem -whether to ignore empty item or not - * @return {@link #getString(Object[], String, boolean)} + * @return {@link #get(Object[], String, boolean)} *

Here we replace the simple "+" way of concatenating with Stringbuilder 's append

*/ + @Deprecated public static String getString(Object[] array, String split, boolean ignoreEmptyItem) { - StringBuilder s = new StringBuilder(""); - if (array != null) { - if (split == null) { - split = ","; - } - for (int i = 0; i < array.length; i++) { - if (ignoreEmptyItem && isEmpty(array[i], true)) { - continue; - } - s.append(((i > 0 ? split : "") + array[i])); - } - } - return getString(s.toString()); + return get(array, split, ignoreEmptyItem); } //获取string,为null时返回"" >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //获取去掉前后空格后的string<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**获取去掉前后空格后的string,为null则返回"" + * @param obj + * @return + */ + public static String trim(Object obj) { + return trim(get(obj)); + } + /**获取去掉前后空格后的string,为null则返回"" + * @param cs + * @return + */ + public static String trim(CharSequence cs) { + return trim(get(cs)); + } + /**获取去掉前后空格后的string,为null则返回"" + * @param s + * @return + */ + public static String trim(String s) { + return get(s).trim(); + } + + + /**FIXME 用 trim 替代 * @param object * @return */ + @Deprecated public static String getTrimedString(Object object) { - return getTrimedString(getString(object)); + return trim(object); } - /**获取去掉前后空格后的string,为null则返回"" + /**FIXME 用 trim 替代 * @param cs * @return */ + @Deprecated public static String getTrimedString(CharSequence cs) { - return getTrimedString(getString(cs)); + return trim(cs); } - /**获取去掉前后空格后的string,为null则返回"" + /**FIXME 用 trim 替代 * @param s * @return */ + @Deprecated public static String getTrimedString(String s) { - return getString(s).trim(); + return trim(s); } //获取去掉前后空格后的string>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //获取去掉所有空格后的string <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**获取去掉所有空格后的string,为null则返回"" + * @param obj + * @return + */ + public static String noBlank(Object obj) { + return noBlank(get(obj)); + } + /**获取去掉所有空格后的string,为null则返回"" + * @param cs + * @return + */ + public static String noBlank(CharSequence cs) { + return noBlank(get(cs)); + } + /**获取去掉所有空格后的string,为null则返回"" + * @param s + * @return + */ + public static String noBlank(String s) { + return get(s).replaceAll("\\s", ""); + } + + /**FIXME 用 noBlank 替代 * @param object * @return */ + @Deprecated public static String getNoBlankString(Object object) { - return getNoBlankString(getString(object)); + return noBlank(object); } - /**获取去掉所有空格后的string,为null则返回"" + /**FIXME 用 noBlank 替代 * @param cs * @return */ + @Deprecated public static String getNoBlankString(CharSequence cs) { - return getNoBlankString(getString(cs)); + return noBlank(cs); } - /**获取去掉所有空格后的string,为null则返回"" + /**FIXME 用 noBlank 替代 * @param s * @return */ + @Deprecated public static String getNoBlankString(String s) { - return getString(s).replaceAll("\\s", ""); + return noBlank(s); } //获取去掉所有空格后的string >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //获取string的长度<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - /**获取string的长度,为null则返回0 * @param object * @param trim * @return */ - public static int getLength(Object object, boolean trim) { - return getLength(getString(object), trim); + public static int length(Object object, boolean trim) { + return length(get(object), trim); } /**获取string的长度,为null则返回0 * @param cs * @param trim * @return */ - public static int getLength(CharSequence cs, boolean trim) { - return getLength(getString(cs), trim); + public static int length(CharSequence cs, boolean trim) { + return length(get(cs), trim); } /**获取string的长度,为null则返回0 * @param s * @param trim * @return */ + public static int length(String s, boolean trim) { + s = trim ? trim(s) : s; + return get(s).length(); + } + + + /**FIXME 用 length 替代 + * @param object + * @param trim + * @return + */ + @Deprecated + public static int getLength(Object object, boolean trim) { + return length(object, trim); + } + /**FIXME 用 length 替代 + * @param cs + * @param trim + * @return + */ + @Deprecated + public static int getLength(CharSequence cs, boolean trim) { + return length(cs, trim); + } + /**FIXME 用 length 替代 + * @param s + * @param trim + * @return + */ + @Deprecated public static int getLength(String s, boolean trim) { - s = trim ? getTrimedString(s) : s; - return getString(s).length(); + return length(s, trim); } //获取string的长度>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -238,7 +384,7 @@ public static boolean isEmpty(Object obj) { * @return */ public static boolean isEmpty(Object obj, boolean trim) { - return isEmpty(getString(obj), trim); + return isEmpty(get(obj), trim); } /**判断字符是否为空 trim = true * @param cs @@ -253,7 +399,7 @@ public static boolean isEmpty(CharSequence cs) { * @return */ public static boolean isEmpty(CharSequence cs, boolean trim) { - return isEmpty(getString(cs), trim); + return isEmpty(get(cs), trim); } /**判断字符是否为空 trim = true * @param s @@ -268,7 +414,7 @@ public static boolean isEmpty(String s) { * @return */ public static boolean isEmpty(String s, boolean trim) { - // Log.i(TAG, "getTrimedString s = " + s); + // Log.i(TAG, "isEmpty s = " + s); if (s == null) { return true; } @@ -279,7 +425,7 @@ public static boolean isEmpty(String s, boolean trim) { return true; } - currentString = s; + current = s; return false; } @@ -289,7 +435,7 @@ public static boolean isEmpty(String s, boolean trim) { //判断字符是否非空 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /**判断字符是否非空 trim = true - * @param object + * @param obj * @return */ public static boolean isNotEmpty(Object obj) { @@ -343,6 +489,7 @@ public static boolean isNotEmpty(String s, boolean trim) { public static final Pattern PATTERN_PHONE; public static final Pattern PATTERN_EMAIL; public static final Pattern PATTERN_ID_CARD; + public static final Pattern PATTERN_NUM_OR_ALPHA; public static final Pattern PATTERN_ALPHA; public static final Pattern PATTERN_PASSWORD; //TODO public static final Pattern PATTERN_NAME; @@ -351,6 +498,7 @@ public static boolean isNotEmpty(String s, boolean trim) { public static final Pattern PATTERN_BRANCH_URL; static { PATTERN_NUMBER = Pattern.compile("^[0-9]+$"); + PATTERN_NUM_OR_ALPHA = Pattern.compile("^[0-9a-zA-Z_.:]+$"); PATTERN_ALPHA = Pattern.compile("^[a-zA-Z]+$"); PATTERN_ALPHA_BIG = Pattern.compile("^[A-Z]+$"); PATTERN_ALPHA_SMALL = Pattern.compile("^[a-z]+$"); @@ -372,7 +520,7 @@ public static boolean isPhone(String phone) { return false; } - currentString = phone; + current = phone; return PATTERN_PHONE.matcher(phone).matches(); } /**判断手机格式是否正确 @@ -380,14 +528,14 @@ public static boolean isPhone(String phone) { * @return */ public static boolean isPassword(String s) { - return getLength(s, false) >= 6 && PATTERN_PASSWORD.matcher(s).matches(); + return length(s, false) >= 6 && PATTERN_PASSWORD.matcher(s).matches(); } /**判断是否全是数字密码 * @param s * @return */ public static boolean isNumberPassword(String s) { - return getLength(s, false) == 6 && isNumer(s); + return length(s, false) == 6 && isNumber(s); } /**判断email格式是否正确 * @param email @@ -398,7 +546,7 @@ public static boolean isEmail(String email) { return false; } - currentString = email; + current = email; return PATTERN_EMAIL.matcher(email).matches(); } @@ -408,18 +556,18 @@ public static boolean isEmail(String email) { * @return */ public static boolean isVerify(String s) { - return getLength(s, false) >= 4 && isNumer(s); + return length(s, false) >= 4 && isNumber(s); } /**判断是否全是数字 * @param s * @return */ - public static boolean isNumer(String s) { - if (isNotEmpty(s, true) == false) { + public static boolean isNumber(String s) { + if (isEmpty(s, true)) { return false; } - currentString = s; + current = s; return PATTERN_NUMBER.matcher(s).matches(); } /**判断是否全是字母 @@ -431,7 +579,7 @@ public static boolean isAlpha(String s) { return false; } - currentString = s; + current = s; return PATTERN_ALPHA.matcher(s).matches(); } /**判断是否全是数字或字母 @@ -439,7 +587,20 @@ public static boolean isAlpha(String s) { * @return */ public static boolean isNumberOrAlpha(String s) { - return isNumer(s) || isAlpha(s); + return isNumber(s) || isAlpha(s); + } + + /**判断是否全是数字或字母 + * @param s + * @return + */ + public static boolean isCombineOfNumOrAlpha(String s) { + if (isEmpty(s, true)) { + return false; + } + + current = s; + return PATTERN_NUM_OR_ALPHA.matcher(s).matches(); } /**判断是否为代码名称,只能包含字母,数字或下划线 @@ -485,17 +646,17 @@ public static boolean isSmallName(String s) { * @return */ public static boolean isIDCard(String number) { - if (isNumberOrAlpha(number) == false) { + if (isCombineOfNumOrAlpha(number) == false) { return false; } - number = getString(number); + number = get(number); if (number.length() == 15) { Log.i(TAG, "isIDCard number.length() == 15 old IDCard"); - currentString = number; + current = number; return true; } if (number.length() == 18) { - currentString = number; + current = number; return true; } @@ -505,8 +666,6 @@ public static boolean isIDCard(String number) { public static final String HTTP = "http"; public static final String URL_PREFIX = "http://"; public static final String URL_PREFIXs = "https://"; - public static final String URL_STAFFIX = URL_PREFIX; - public static final String URL_STAFFIXs = URL_PREFIXs; /**判断字符类型是否是网址 * @param url * @return @@ -519,7 +678,7 @@ public static boolean isUrl(String url) { return false; } - currentString = url; + current = url; return true; } @@ -564,7 +723,7 @@ public static boolean isFilePath(String path) { return false; } - currentString = path; + current = path; return true; } @@ -579,14 +738,14 @@ public static boolean isFilePath(String path) { * @return */ public static String getNumber(Object object) { - return getNumber(getString(object)); + return getNumber(get(object)); } /**去掉string内所有非数字类型字符 * @param cs * @return */ public static String getNumber(CharSequence cs) { - return getNumber(getString(cs)); + return getNumber(get(cs)); } /**去掉string内所有非数字类型字符 * @param s @@ -604,7 +763,7 @@ public static String getNumber(String s) { *

Here we replace the simple "+" way of concatenating with Stringbuilder 's append

*/ public static String getNumber(String s, boolean onlyStart) { - if (isNotEmpty(s, true) == false) { + if (isEmpty(s, true)) { return ""; } @@ -612,7 +771,7 @@ public static String getNumber(String s, boolean onlyStart) { String single; for (int i = 0; i < s.length(); i++) { single = s.substring(i, i + 1); - if (isNumer(single)) { + if (isNumber(single)) { numberString.append(single); } else { if (onlyStart) { @@ -656,7 +815,7 @@ public static String getCorrectPhone(String phone) { return ""; } - phone = getNoBlankString(phone); + phone = noBlank(phone); phone = phone.replaceAll("-", ""); if (phone.startsWith("+86")) { phone = phone.substring(3); @@ -674,7 +833,7 @@ public static String getCorrectEmail(String email) { return ""; } - email = getNoBlankString(email); + email = noBlank(email); if (isEmail(email) == false && ! email.endsWith(".com")) { email += ".com"; } @@ -717,7 +876,7 @@ public static String getPrice(String price, int formatType) { String s; for (int i = 0; i < price.length(); i++) { s = price.substring(i, i + 1); - if (".".equals(s) || isNumer(s)) { + if (".".equals(s) || isNumber(s)) { correctPriceBuilder.append(s); } } @@ -844,7 +1003,7 @@ public static String[] split(String s, boolean trim) { * @return */ public static String[] split(String s, String split, boolean trim) { - s = getString(s); + s = get(s); if (s.isEmpty()) { return null; } @@ -868,7 +1027,7 @@ public static String[] split(String s, String split, boolean trim) { * @return key + suffix,第一个字母小写 */ public static String addSuffix(String key, String suffix) { - key = getNoBlankString(key); + key = noBlank(key); if (key.isEmpty()) { return firstCase(suffix); } @@ -886,7 +1045,7 @@ public static String firstCase(String key) { * @return */ public static String firstCase(String key, boolean upper) { - key = getString(key); + key = get(key); if (key.isEmpty()) { return ""; } @@ -910,7 +1069,7 @@ public static String toUpperCase(String s) { * @return */ public static String toUpperCase(String s, boolean trim) { - s = trim ? getTrimedString(s) : getString(s); + s = trim ? trim(s) : get(s); return s.toUpperCase(); } /**全部小写 @@ -925,7 +1084,7 @@ public static String toLowerCase(String s) { * @return */ public static String toLowerCase(String s, boolean trim) { - s = trim ? getTrimedString(s) : getString(s); + s = trim ? trim(s) : get(s); return s.toLowerCase(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractFunctionParser.java index 25739bf17..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.*/ @@ -8,10 +8,6 @@ import apijson.*; import apijson.orm.exception.UnsupportedDataTypeException; import apijson.orm.script.ScriptExecutor; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.parser.ParserConfig; -import com.alibaba.fastjson.util.TypeUtils; import java.lang.invoke.WrongMethodTypeException; import java.lang.reflect.InvocationTargetException; @@ -20,12 +16,12 @@ import java.util.*; import static apijson.orm.AbstractSQLConfig.PATTERN_SCHEMA; -import static apijson.orm.SQLConfig.TYPE_ITEM; /**可远程调用的函数类 * @author Lemon */ -public class AbstractFunctionParser implements FunctionParser { +public abstract class AbstractFunctionParser, L extends List> + implements FunctionParser { private static final String TAG = "AbstractFunctionParser"; /**是否解析参数 key 的对应的值,不用手动编码 curObj.getString(key) @@ -39,52 +35,57 @@ public class AbstractFunctionParser implements FunctionParser< */ public static boolean ENABLE_SCRIPT_FUNCTION = true; - // + // // > - public static Map SCRIPT_EXECUTOR_MAP; - public static Map FUNCTION_MAP; + public static Map, ? extends List>> SCRIPT_EXECUTOR_MAP; + public static Map> FUNCTION_MAP; static { FUNCTION_MAP = new HashMap<>(); SCRIPT_EXECUTOR_MAP = new HashMap<>(); } + private Parser parser; private RequestMethod method; private String tag; private int version; - private JSONObject request; + private String key; + private String parentPath; + private String currentName; + private M request; + private M current; public AbstractFunctionParser() { this(null, null, 0, null); } - public AbstractFunctionParser(RequestMethod method, String tag, int version, @NotNull JSONObject request) { + public AbstractFunctionParser(RequestMethod method, String tag, int version, @NotNull M request) { setMethod(method == null ? RequestMethod.GET : method); setTag(tag); setVersion(version); setRequest(request); } - private Parser parser; - + @NotNull @Override - public Parser getParser() { + public Parser getParser() { return parser; } @Override - public AbstractFunctionParser setParser(Parser parser) { + public AbstractFunctionParser setParser(Parser parser) { this.parser = parser; return this; } + @NotNull @Override public RequestMethod getMethod() { - return method; + return method == null ? RequestMethod.GET : method; } @Override - public AbstractFunctionParser setMethod(RequestMethod method) { + public AbstractFunctionParser setMethod(RequestMethod method) { this.method = method; return this; } @@ -95,7 +96,7 @@ public String getTag() { } @Override - public AbstractFunctionParser setTag(String tag) { + public AbstractFunctionParser setTag(String tag) { this.tag = tag; return this; } @@ -106,73 +107,65 @@ public int getVersion() { } @Override - public AbstractFunctionParser setVersion(int version) { + public AbstractFunctionParser setVersion(int version) { this.version = version; return this; } - private String key; - @Override public String getKey() { return key; } @Override - public AbstractFunctionParser setKey(String key) { + public AbstractFunctionParser setKey(String key) { this.key = key; return this; } - private String parentPath; - @Override public String getParentPath() { return parentPath; } @Override - public AbstractFunctionParser setParentPath(String parentPath) { + public AbstractFunctionParser setParentPath(String parentPath) { this.parentPath = parentPath; return this; } - private String currentName; - @Override public String getCurrentName() { return currentName; } @Override - public AbstractFunctionParser setCurrentName(String currentName) { + public AbstractFunctionParser setCurrentName(String currentName) { this.currentName = currentName; return this; } @NotNull @Override - public JSONObject getRequest() { + public M getRequest() { return request; } @Override - public AbstractFunctionParser setRequest(@NotNull JSONObject request) { + public AbstractFunctionParser setRequest(@NotNull M request) { this.request = request; return this; } - private JSONObject currentObject; - @NotNull @Override - public JSONObject getCurrentObject() { - return currentObject; + public M getCurrentObject() { + return current; } @Override - public AbstractFunctionParser setCurrentObject(@NotNull JSONObject currentObject) { - this.currentObject = currentObject; + public AbstractFunctionParser setCurrentObject(@NotNull M current) { + this.current = current; return this; } @@ -241,20 +234,20 @@ public String getArgStr(String path) { return JSON.toJSONString(obj); } - /**根据路径取 JSONObject 值 + /**根据路径取 JSONMap 值 * @param path * @return */ - public JSONObject getArgObj(String path) { - return getArgVal(path, JSONObject.class); + public Map getArgObj(String path) { + return getArgVal(path, Map.class); } - /**根据路径取 JSONArray 值 + /**根据路径取 JSONList 值 * @param path * @return */ - public JSONArray getArgArr(String path) { - return getArgVal(path, JSONArray.class); + public List getArgArr(String path) { + return getArgVal(path, List.class); } /**根据路径取 List 值 @@ -289,22 +282,22 @@ public T getArgVal(String path) { * @param */ public T getArgVal(String path, Class clazz) { - return getArgVal(path, clazz, true); + return getArgVal(getCurrentObject(), path, clazz, true); } /**根据路径取值 * @param path * @param clazz - * @param tryAll false-仅当前对象,true-本次请求的全局对象以及 Parser 缓存值 + * @param tryAll false-仅当前对象,true-本次请求的全局对象以及 Parser 缓存值 * @return * @param */ - public T getArgVal(String path, Class clazz, boolean tryAll) { - T val = getArgVal(getCurrentObject(), path, clazz); + public T getArgVal(@NotNull M req, String path, Class clazz, boolean tryAll) { + T val = getArgValue(req, path, clazz); if (tryAll == false || val != null) { return val; } - Parser p = getParser(); + Parser p = getParser(); String targetPath = AbstractParser.getValuePath(getParentPath(), path); return p == null ? null : (T) p.getValueByPath(targetPath); } @@ -314,55 +307,110 @@ public T getArgVal(String path, Class clazz, boolean tryAl * @return * @param */ - public static T getArgVal(JSONObject obj, String path) { - return getArgVal(obj, path, null); + public static T getArgVal(Map obj, String path) { + return getArgValue(obj, path, null); } - public static T getArgVal(JSONObject obj, String path, Class clazz) { + + public static T getArgValue(Map obj, String path, Class clazz) { Object v = AbstractParser.getValue(obj, StringUtil.splitPath(path)); - return clazz == null ? (T) v : TypeUtils.cast(v, clazz, ParserConfig.getGlobalInstance()); + + if (clazz == null) { + return (T) v; + } + + // Simple type conversion + try { + if (v == null) { + return null; + } + if (clazz.isInstance(v)) { + return (T) v; + } + if (clazz == String.class) { + return (T) String.valueOf(v); + } + if (clazz == Boolean.class || clazz == boolean.class) { + return (T) Boolean.valueOf(String.valueOf(v)); + } + if (clazz == Integer.class || clazz == int.class) { + return (T) Integer.valueOf(String.valueOf(v)); + } + if (clazz == Long.class || clazz == long.class) { + return (T) Long.valueOf(String.valueOf(v)); + } + if (clazz == Double.class || clazz == double.class) { + return (T) Double.valueOf(String.valueOf(v)); + } + if (clazz == Float.class || clazz == float.class) { + return (T) Float.valueOf(String.valueOf(v)); + } + if (Map.class.isAssignableFrom(clazz)) { + if (v instanceof Map) { + return (T) v; + } + return (T) JSON.parseObject(v); + } + if (List.class.isAssignableFrom(clazz)) { + if (v instanceof List) { + return (T) v; + } + return (T) JSON.parseArray(v); + } + // Fallback to string conversion + return (T) v; + } catch (Exception e) { + return null; + } } /**反射调用 * @param function 例如get(object,key),参数只允许引用,不能直接传值 - * @param currentObject 不作为第一个参数,就不能远程调用invoke,避免死循环 - * @return {@link #invoke(String, JSONObject, boolean)} + * @param current 不作为第一个参数,就不能远程调用invoke,避免死循环 + * @return {@link #invoke(String, M, boolean)} */ @Override - public Object invoke(@NotNull String function, @NotNull JSONObject currentObject) throws Exception { - return invoke(function, currentObject, false); - } + public Object invoke(@NotNull String function, @NotNull M current) throws Exception { + return invoke(function, current, false); + } /**反射调用 * @param function 例如get(object,key),参数只允许引用,不能直接传值 - * @param currentObject 不作为第一个参数,就不能远程调用invoke,避免死循环 + * @param current 不作为第一个参数,就不能远程调用invoke,避免死循环 * @param containRaw 包含原始 SQL 片段 - * @return {@link #invoke(AbstractFunctionParser, String, JSONObject, boolean)} + * @return {@link #invoke(AbstractFunctionParser, String, M, boolean)} */ @Override - public Object invoke(@NotNull String function, @NotNull JSONObject currentObject, boolean containRaw) throws Exception { - return invoke(this, function, currentObject, containRaw); + public Object invoke(@NotNull String function, @NotNull M current, boolean containRaw) throws Exception { + if (StringUtil.isEmpty(function, true)) { + throw new IllegalArgumentException("字符 " + function + " 不合法!"); + } + + return invoke(this, function, current, containRaw); } /**反射调用 * @param parser * @param function 例如get(Map:map,key),参数只允许引用,不能直接传值 - * @param currentObject + * @param current * @return {@link #invoke(AbstractFunctionParser, String, Class[], Object[])} */ - public static Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String function, @NotNull JSONObject currentObject, boolean containRaw) throws Exception { + @SuppressWarnings({"unchecked", "rawtypes"}) + public static , L extends List> Object invoke( + @NotNull AbstractFunctionParser parser, @NotNull String function + , @NotNull Map current, boolean containRaw) throws Exception { if (ENABLE_REMOTE_FUNCTION == false) { throw new UnsupportedOperationException("AbstractFunctionParser.ENABLE_REMOTE_FUNCTION" + " == false 时不支持远程函数!如需支持则设置 AbstractFunctionParser.ENABLE_REMOTE_FUNCTION = true !"); } - FunctionBean fb = parseFunction(function, currentObject, false, containRaw); + FunctionBean fb = parseFunction(function, current, false, containRaw); - JSONObject row = FUNCTION_MAP.get(fb.getMethod()); //FIXME fb.getSchema() + "." + fb.getMethod() + Map row = FUNCTION_MAP.get(fb.getMethod()); //FIXME fb.getSchema() + "." + fb.getMethod() if (row == null) { throw new UnsupportedOperationException("不允许调用远程函数 " + fb.getMethod() + " !"); } - String language = row.getString("language"); + String language = (String) row.get("language"); String lang = "java".equalsIgnoreCase(language) ? null : language; if (ENABLE_SCRIPT_FUNCTION == false && lang != null) { @@ -371,25 +419,25 @@ public static Object invoke(@NotNull AbstractFunctionParser 中注册!"); + throw new ClassNotFoundException("找不到脚本语言 " + lang + " 对应的执行引擎!请先依赖相关库并在后端 APIJSONFunctionParser 中注册!"); } - int version = row.getIntValue("version"); + int version = row.get("version") != null ? Integer.parseInt(row.get("version").toString()) : 0; if (parser.getVersion() < version) { throw new UnsupportedOperationException("不允许 version = " + parser.getVersion() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 version >= " + version + " !"); } - String tag = row.getString("tag"); // TODO 改为 tags,类似 methods 支持多个 tag。或者干脆不要?因为目前非开放请求全都只能后端指定 + String tag = (String) row.get("tag"); // TODO 改为 tags,类似 methods 支持多个 tag。或者干脆不要?因为目前非开放请求全都只能后端指定 if (tag != null && tag.equals(parser.getTag()) == false) { throw new UnsupportedOperationException("不允许 tag = " + parser.getTag() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 tag = " + tag + " !"); } - String[] methods = StringUtil.split(row.getString("methods")); + String[] methods = StringUtil.split((String) row.get("methods")); List ml = methods == null || methods.length <= 0 ? null : Arrays.asList(methods); if (ml != null && ml.contains(parser.getMethod().toString()) == false) { throw new UnsupportedOperationException("不允许 method = " + parser.getMethod() + " 的请求调用远程函数 " + fb.getMethod() + " ! 必须满足 method 在 " + Arrays.toString(methods) + "内 !"); } try { - return invoke(parser, fb.getMethod(), fb.getTypes(), fb.getValues(), row.getString("returnType"), currentObject, SCRIPT_EXECUTOR_MAP.get(lang)); + return invoke(parser, fb.getMethod(), fb.getTypes(), fb.getValues(), (String) row.get("returnType"), current, SCRIPT_EXECUTOR_MAP.get(lang)); } catch (Exception e) { if (e instanceof NoSuchMethodException) { @@ -419,10 +467,12 @@ public static Object invoke(@NotNull AbstractFunctionParser Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String methodName + @SuppressWarnings({"unchecked", "rawtypes"}) + public static , L extends List> Object invoke( + @NotNull AbstractFunctionParser parser, @NotNull String methodName , @NotNull Class[] parameterTypes, @NotNull Object[] args) throws Exception { return invoke(parser, methodName, parameterTypes, args, null, null, null); } @@ -432,19 +482,22 @@ public static Object invoke(@NotNull AbstractFunctionParser Object invoke(@NotNull AbstractFunctionParser parser, @NotNull String methodName + @SuppressWarnings({"unchecked", "rawtypes"}) + public static , L extends List> Object invoke( + @NotNull AbstractFunctionParser parser, @NotNull String methodName , @NotNull Class[] parameterTypes, @NotNull Object[] args, String returnType - , JSONObject currentObject, ScriptExecutor scriptExecutor) throws Exception { + , Map current, ScriptExecutor scriptExecutor) throws Exception { if (scriptExecutor != null) { - return invokeScript(parser, methodName, parameterTypes, args, returnType, currentObject, scriptExecutor); + return invokeScript(parser, methodName, parameterTypes, args, returnType, current, scriptExecutor); } - Method m = parser.getClass().getMethod(methodName, parameterTypes); // 不用判空,拿不到就会抛异常 + Class cls = parser.getClass(); + Method m = cls.getMethod(methodName, parameterTypes); // 不用判空,拿不到就会抛异常 if (Log.DEBUG) { String rt = Log.DEBUG && m.getReturnType() != null ? m.getReturnType().getSimpleName() : null; @@ -470,13 +523,16 @@ public static Object invoke(@NotNull AbstractFunctionParser Object invokeScript(@NotNull AbstractFunctionParser parser, @NotNull String methodName - , @NotNull Class[] parameterTypes, @NotNull Object[] args, String returnType, JSONObject currentObject, ScriptExecutor scriptExecutor) throws Exception { - Object result = scriptExecutor.execute(parser, currentObject, methodName, args); + @SuppressWarnings({"unchecked", "rawtypes"}) + public static , L extends List> Object invokeScript( + @NotNull AbstractFunctionParser parser, @NotNull String methodName + , @NotNull Class[] parameterTypes, @NotNull Object[] args, String returnType + , Map current, ScriptExecutor scriptExecutor) throws Exception { + Object result = scriptExecutor.execute(parser, current, methodName, args); if (Log.DEBUG && result != null) { Class rt = result.getClass(); // 作为远程函数的 js 类型应该只有 JSON 的几种类型 String fullReturnType = (StringUtil.isSmallName(returnType) @@ -516,7 +572,7 @@ public static Object invokeScript(@NotNull AbstractFunctionPa * @throws Exception */ @NotNull - public static FunctionBean parseFunction(@NotNull String function, @NotNull JSONObject request, boolean isSQLFunction) throws Exception { + public static FunctionBean parseFunction(@NotNull String function, @NotNull Map request, boolean isSQLFunction) throws Exception { return parseFunction(function, request, isSQLFunction, false); } /**解析函数,自动解析的值类型只支持 Boolean, Number, String, Map, List @@ -527,7 +583,7 @@ public static FunctionBean parseFunction(@NotNull String function, @NotNull JSON * @return * @throws Exception */ - public static FunctionBean parseFunction(@NotNull String function, @NotNull JSONObject request, boolean isSQLFunction, boolean containRaw) throws Exception { + public static FunctionBean parseFunction(@NotNull String function, @NotNull Map request, boolean isSQLFunction, boolean containRaw) throws Exception { int start = function.indexOf("("); int end = function.lastIndexOf(")"); @@ -572,7 +628,7 @@ public static FunctionBean parseFunction(@NotNull String function, @NotNull JSON } if (v instanceof Boolean) { - types[i] = Boolean.class; //只支持JSON的几种类型 + types[i] = Boolean.class; //只支持JSON的几种类型 } // 怎么都有 bug,如果是引用的值,很多情况下无法指定 // 用 1L 指定为 Long ? 其它的默认按长度分配为 Integer 或 Long? //else if (v instanceof Long || v instanceof Integer || v instanceof Short) { // types[i] = Long.class; @@ -583,25 +639,28 @@ else if (v instanceof Number) { else if (v instanceof String) { types[i] = String.class; } - else if (v instanceof Map) { // 泛型兼容? // JSONObject + else if (v instanceof Map) { // 泛型兼容? // JSONMap types[i] = Map.class; //性能比较差 //values[i] = TypeUtils.cast(v, Map.class, ParserConfig.getGlobalInstance()); } - else if (v instanceof Collection) { // 泛型兼容? // JSONArray + else if (v instanceof Collection) { // 泛型兼容? // JSONList types[i] = List.class; //性能比较差 - values[i] = TypeUtils.cast(v, List.class, ParserConfig.getGlobalInstance()); + List list = new ArrayList<>((Collection) v); + values[i] = list; // TypeUtils.cast(v, List.class, ParserConfig.getGlobalInstance()); } else { throw new UnsupportedDataTypeException(keys[i] + ":value 中value不合法!远程函数 key():" - + function + " 中的 arg 对应的值类型只能是 [Boolean, Number, String, JSONObject, JSONArray] 中的一种!"); + + function + " 中的 arg 对应的值类型只能是 [Boolean, Number, String, JSONMap, JSONList] 中的一种!"); } } } else { + Class cls = JSON.createJSONObject().getClass(); types = new Class[length + 1]; - types[0] = JSONObject.class; + //types[0] = Object.class; // 泛型擦除 JSON.JSON_OBJECT_CLASS; + types[0] = cls; values = new Object[length + 1]; values[0] = request; @@ -668,7 +727,7 @@ public static String extractSchema(String sch, String table) { * @return */ public static String getFunction(String method, String[] keys) { - String f = method + "(JSONObject request"; + String f = method + "(JSONMap request"; if (keys != null) { for (int i = 0; i < keys.length; i++) { @@ -681,17 +740,17 @@ public static String getFunction(String method, String[] keys) { return f; } - public static T getArgValue(@NotNull JSONObject currentObject, String keyOrValue) { - return getArgValue(currentObject, keyOrValue, false); + public static T getArgValue(@NotNull Map current, String keyOrValue) { + return getArgValue(current, keyOrValue, false); } - public static T getArgValue(@NotNull JSONObject currentObject, String keyOrValue, boolean containRaw) { + public static T getArgValue(@NotNull Map current, String keyOrValue, boolean containRaw) { if (keyOrValue == null) { return null; } if (keyOrValue.endsWith("`") && keyOrValue.substring(1).indexOf("`") == keyOrValue.length() - 2) { - return (T) currentObject.get(keyOrValue.substring(1, keyOrValue.length() - 1)); + return (T) current.get(keyOrValue.substring(1, keyOrValue.length() - 1)); } if (keyOrValue.endsWith("'") && keyOrValue.substring(1).indexOf("'") == keyOrValue.length() - 2) { @@ -705,7 +764,7 @@ public static T getArgValue(@NotNull JSONObject currentObject, String keyOrV } if (StringUtil.isName(keyOrValue.startsWith("@") ? keyOrValue.substring(1) : keyOrValue)) { - return (T) currentObject.get(keyOrValue); + return (T) current.get(keyOrValue); } if ("true".equals(keyOrValue)) { @@ -717,7 +776,7 @@ public static T getArgValue(@NotNull JSONObject currentObject, String keyOrV // 性能更好,但居然非法格式也不报错 //try { - // val = Boolean.valueOf(keyOrValue); // JSON.parse(keyOrValue); + // val = Boolean.valueOf(keyOrValue); // parseJSON(keyOrValue); // return (T) val; //} //catch (Throwable e) { @@ -727,7 +786,7 @@ public static T getArgValue(@NotNull JSONObject currentObject, String keyOrV //} try { - val = Double.valueOf(keyOrValue); // JSON.parse(keyOrValue); + val = Double.valueOf(keyOrValue); // parseJSON(keyOrValue); return (T) val; } catch (Throwable e) { @@ -736,7 +795,7 @@ public static T getArgValue(@NotNull JSONObject currentObject, String keyOrV "} catch (Throwable e) = " + e.getMessage()); } - return (T) currentObject.get(keyOrValue); + return (T) current.get(keyOrValue); } public static class FunctionBean { @@ -825,4 +884,81 @@ public String toFunctionCallString(boolean useValue, String quote) { } + /** + * 获取JSON对象 + * @param TODO + * @param req + * @param key + * @param clazz + * @return + * @throws Exception + */ + public V getArgVal(@NotNull M req, String key, Class clazz) throws Exception { + // Convert to JSONMap for backward compatibility, replace with proper implementation later + return getArgVal(req, key, clazz, false); + } + + /** + * 获取参数值 + * @param key + * @param clazz 如果有clazz就返回对应的类型,否则返回原始类型 + * @param defaultValue + * @return + * @throws Exception + */ + public V getArgVal(String key, Class clazz, boolean defaultValue) throws Exception { + Object obj = parser != null && JSONMap.isArrayKey(key) ? AbstractParser.getValue(request, key.split("\\,")) : request.get(key); + + if (clazz == null) { + return (V) obj; + } + + // Replace TypeUtils with appropriate casting method + try { + if (obj == null) { + return null; + } + if (clazz.isInstance(obj)) { + return (V) obj; + } + if (clazz == String.class) { + return (V) String.valueOf(obj); + } + if (clazz == Boolean.class || clazz == boolean.class) { + return (V) Boolean.valueOf(String.valueOf(obj)); + } + if (clazz == Integer.class || clazz == int.class) { + return (V) Integer.valueOf(String.valueOf(obj)); + } + if (clazz == Long.class || clazz == long.class) { + return (V) Long.valueOf(String.valueOf(obj)); + } + if (clazz == Double.class || clazz == double.class) { + return (V) Double.valueOf(String.valueOf(obj)); + } + if (clazz == Float.class || clazz == float.class) { + return (V) Float.valueOf(String.valueOf(obj)); + } + if (Map.class.isAssignableFrom(clazz)) { + if (obj instanceof Map) { + return (V) obj; + } + return (V) JSON.parseObject(obj); + } + if (List.class.isAssignableFrom(clazz)) { + if (obj instanceof List) { + return (V) obj; + } + return (V) JSON.parseArray(obj); + } + // Fallback to string conversion + return (V) obj; + } catch (Exception e) { + if (defaultValue) { + return null; + } + throw e; + } + } + } \ No newline at end of file diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java index 0a35761cb..0e6aaa784 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractObjectParser.java @@ -1,33 +1,26 @@ -/*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 apijson.JSONResponse; -import apijson.Log; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.StringUtil; +import apijson.*; import apijson.orm.AbstractFunctionParser.FunctionBean; import apijson.orm.exception.ConflictException; import apijson.orm.exception.CommonException; import apijson.orm.exception.NotExistException; import apijson.orm.exception.UnsupportedDataTypeException; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.serializer.SerializerFeature; import java.rmi.ServerException; import java.util.*; import java.util.Map.Entry; -import static apijson.JSONObject.KEY_COMBINE; -import static apijson.JSONObject.KEY_DROP; -import static apijson.JSONObject.KEY_TRY; -import static apijson.JSONRequest.KEY_QUERY; +import static apijson.JSON.*; +import static apijson.JSONMap.KEY_COMBINE; +import static apijson.JSONMap.KEY_DROP; +import static apijson.JSONMap.KEY_TRY; +import static apijson.JSONRequest.*; import static apijson.RequestMethod.POST; import static apijson.RequestMethod.PUT; import static apijson.orm.SQLConfig.TYPE_ITEM; @@ -36,29 +29,30 @@ /**简化Parser,getObject和getArray(getArrayConfig)都能用 * @author Lemon */ -public abstract class AbstractObjectParser implements ObjectParser { +public abstract class AbstractObjectParser, L extends List> + implements ObjectParser { private static final String TAG = "AbstractObjectParser"; @NotNull - protected AbstractParser parser; + protected AbstractParser parser; @Override - public AbstractParser getParser() { + public AbstractParser getParser() { return parser; } @Override - public AbstractObjectParser setParser(Parser parser) { - this.parser = (AbstractParser) parser; + public AbstractObjectParser setParser(Parser parser) { + this.parser = (AbstractParser) parser; return this; } - protected JSONObject request;//不用final是为了recycle + protected M request;//不用final是为了recycle protected String parentPath;//不用final是为了recycle - protected SQLConfig arrayConfig;//不用final是为了recycle + protected SQLConfig arrayConfig;//不用final是为了recycle protected boolean isSubquery; protected final int type; protected final String arrayTable; - protected final List joinList; + protected final List> joinList; protected final boolean isTable; protected final boolean isArrayMainTable; @@ -67,13 +61,14 @@ public AbstractObjectParser setParser(Parser parser) { * TODO Parser内要不因为 非 TYPE_ITEM_CHILD_0 的Table 为空导致后续中断。 */ protected final boolean drop; + private List stringKeyList; /**for single object */ - public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLConfig arrayConfig + public AbstractObjectParser(@NotNull M request, String parentPath, SQLConfig arrayConfig , boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception { if (request == null) { - throw new IllegalArgumentException(TAG + ".ObjectParser request == null!!!"); + throw new IllegalArgumentException(TAG + ".ObjectParser request == null!!!"); } this.request = request; this.parentPath = parentPath; @@ -85,7 +80,7 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC this.arrayTable = arrayConfig == null ? null : arrayConfig.getTable(); this.joinList = arrayConfig == null ? null : arrayConfig.getJoinList(); - this.isTable = isTable; // apijson.JSONObject.isTableKey(table); + this.isTable = isTable; // apijson.JSONMap.isTableKey(table); this.isArrayMainTable = isArrayMainTable; // isSubquery == false && this.isTable && this.type == SQLConfig.TYPE_ITEM_CHILD_0 && RequestMethod.isGetMethod(method, true); // this.isReuse = isReuse; // isArrayMainTable && arrayConfig != null && arrayConfig.getPosition() > 0; @@ -98,18 +93,23 @@ public AbstractObjectParser(@NotNull JSONObject request, String parentPath, SQLC this.drop = false; } else { - this.tri = request.getBooleanValue(KEY_TRY); - this.drop = request.getBooleanValue(KEY_DROP); + this.tri = getBooleanValue(request, KEY_TRY); + this.drop = getBooleanValue(request, KEY_DROP); request.remove(KEY_TRY); request.remove(KEY_DROP); } if (isTable) { - String raw = request.getString(JSONRequest.KEY_RAW); + String raw = getString(request, JSONMap.KEY_RAW); String[] rks = StringUtil.split(raw); rawKeyList = rks == null || rks.length <= 0 ? null : Arrays.asList(rks); - } + + String str = getString(request, KEY_STRING); + String[] sks = StringUtil.split(str); + stringKeyList = sks == null || sks.length <= 0 ? null : Arrays.asList(sks); + request.remove(KEY_STRING); + } } @Override @@ -118,19 +118,19 @@ public String getParentPath() { } @Override - public AbstractObjectParser setParentPath(String parentPath) { + public AbstractObjectParser setParentPath(String parentPath) { this.parentPath = parentPath; return this; } - protected JSONObject cache; + protected M cache; @Override - public JSONObject getCache() { + public M getCache() { return cache; } @Override - public AbstractObjectParser setCache(JSONObject cache) { + public AbstractObjectParser setCache(M cache) { this.cache = cache; return this; } @@ -139,7 +139,7 @@ public AbstractObjectParser setCache(JSONObject cache) { public int getPosition() { return position; } - public AbstractObjectParser setPosition(int position) { + public AbstractObjectParser setPosition(int position) { this.position = position; return this; } @@ -167,9 +167,9 @@ public boolean isBreakParse() { protected boolean isReuse; protected String path; - protected JSONObject response; - protected JSONObject sqlRequest; - protected JSONObject sqlResponse; + protected M response; + protected M sqlRequest; + protected M sqlResponse; /** * 自定义关键词 */ @@ -185,7 +185,7 @@ public boolean isBreakParse() { /** * 子对象 */ - protected Map childMap; + protected Map childMap; private int objectCount; private int arrayCount; @@ -197,7 +197,7 @@ public boolean isBreakParse() { * @throws Exception */ @Override - public AbstractObjectParser parse(String name, boolean isReuse) throws Exception { + public AbstractObjectParser parse(String name, boolean isReuse) throws Exception { if (isInvalidate() == false) { this.isReuse = isReuse; this.name = name; @@ -207,17 +207,17 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception this.table = tentry.getKey(); this.alias = tentry.getValue(); - Log.d(TAG, "AbstractObjectParser parentPath = " + parentPath + "; name = " + name + "; table = " + table + "; alias = " + alias); - Log.d(TAG, "AbstractObjectParser type = " + type + "; isTable = " + isTable + "; isArrayMainTable = " + isArrayMainTable); - Log.d(TAG, "AbstractObjectParser isEmpty = " + request.isEmpty() + "; tri = " + tri + "; drop = " + drop); + Log.d(TAG, "AbstractObjectParser parentPath = " + parentPath + "; name = " + name + "; table = " + table + "; alias = " + alias); + Log.d(TAG, "AbstractObjectParser type = " + type + "; isTable = " + isTable + "; isArrayMainTable = " + isArrayMainTable); + Log.d(TAG, "AbstractObjectParser isEmpty = " + request.isEmpty() + "; tri = " + tri + "; drop = " + drop); breakParse = false; - response = new JSONObject(true); // must init + response = JSON.createJSONObject(); // must init sqlResponse = null; // must init if (isReuse == false) { - sqlRequest = new JSONObject(true); // must init + sqlRequest = JSON.createJSONObject(); // must init customMap = null; // must init functionMap = null; // must init @@ -227,14 +227,14 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception if (set != null && set.isEmpty() == false) { // 判断换取少几个变量的初始化是否值得? if (isTable) { // 非Table下必须保证原有顺序!否则 count,page 会丢, total@:"/[]/total" 会在[]:{}前执行! customMap = new LinkedHashMap(); - childMap = new LinkedHashMap(); + childMap = new LinkedHashMap(); } functionMap = new LinkedHashMap>();//必须执行 // 条件 <<<<<<<<<<<<<<<<<<< List whereList = null; if (method == PUT) { // 这里只有PUTArray需要处理 || method == DELETE) { - String[] combine = StringUtil.split(request.getString(KEY_COMBINE)); + String[] combine = StringUtil.split(getString(request, KEY_COMBINE)); if (combine != null) { String w; for (int i = 0; i < combine.length; i++) { // 去除 &,|,! 前缀 @@ -246,23 +246,26 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception } // Arrays.asList() 返回值不支持 add 方法! whereList = new ArrayList(Arrays.asList(combine != null ? combine : new String[]{})); - whereList.add(apijson.JSONRequest.KEY_ID); - whereList.add(apijson.JSONRequest.KEY_ID_IN); - // whereList.add(apijson.JSONRequest.KEY_USER_ID); - // whereList.add(apijson.JSONRequest.KEY_USER_ID_IN); + whereList.add(JSONMap.KEY_ID); + whereList.add(JSONMap.KEY_ID_IN); + // whereList.add(apijson.JSONMap.KEY_USER_ID); + // whereList.add(apijson.JSONMap.KEY_USER_ID_IN); } // 条件>>>>>>>>>>>>>>>>>>> int index = 0; // hasOtherKeyNotFun = false; - JSONObject viceItem = null; + M viceItem = null; for (Entry entry : set) { if (isBreakParse()) { break; } - String key = entry == null ? null : entry.getKey(); + // key 可能为 JSONList,需要进行手动转换(fastjson 为低版本时允许自动转换,如 1.2.21) + // 例如 request json为 "{[]:{"page": 2, "table1":{}}}" + Object field = entry == null ? null : entry.getKey(); + String key = field instanceof Map ? toJSONString(field) : field.toString(); Object value = key == null ? null : entry.getValue(); if (value == null) { continue; @@ -273,8 +276,8 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception // 没有执行校验流程的情况,比如url head, sql@子查询, sql@ method=GET Object obj = key.endsWith("@") ? request.get(key) : null; - if (obj instanceof JSONObject) { - ((JSONObject) obj).put(apijson.JSONObject.KEY_METHOD, GET); + if (obj instanceof Map) { + ((Map) obj).put(JSONMap.KEY_METHOD, GET); } try { @@ -283,36 +286,42 @@ public AbstractObjectParser parse(String name, boolean isReuse) throws Exception // hasOtherKeyNotFun = true; // } - if (startsWithAt || key.endsWith("@") || (key.endsWith("<>") && value instanceof JSONObject)) { + if (stringKeyList != null && stringKeyList.contains(key)) { + // 统一格式 String val = value == null || value instanceof String ? (String) value : JSON.toJSONString(value); + if (onParse(key, JSON.toJSONString(value)) == false) { + invalidate(); + } + } + else if (startsWithAt || key.endsWith("@") || (key.endsWith("<>") && value instanceof Map)) { if (onParse(key, value) == false) { invalidate(); } } - else if (value instanceof JSONObject) { // JSONObject,往下一级提取 + else if (value instanceof Map) { // JSONRequest,往下一级提取 if (childMap != null) { // 添加到childMap,最后再解析 - childMap.put(key, (JSONObject) value); + childMap.put(key, (M) value); } else { // 直接解析并替换原来的,[]:{} 内必须直接解析,否则会因为丢掉count等属性,并且total@:"/[]/total"必须在[]:{} 后! - JSON cache = index <= 0 || type != TYPE_ITEM || viceItem == null ? null : viceItem.getJSONObject(key); - JSON result = onChildParse(index, key, (JSONObject) value, cache); + Object cache = index <= 0 || type != TYPE_ITEM || viceItem == null ? null : JSON.get(viceItem, key); + Object result = onChildParse(index, key, (M) value, cache); if (index <= 0 && type == TYPE_ITEM) { - JSONObject mainItem = (JSONObject) result; - viceItem = result == null ? null : (JSONObject) mainItem.remove(AbstractSQLExecutor.KEY_VICE_ITEM); + M mainItem = (M) result; + viceItem = result == null ? null : (M) mainItem.remove(AbstractSQLExecutor.KEY_VICE_ITEM); } response.put(key, result); index ++; } } - else if ((_method == POST || _method == PUT) && value instanceof JSONArray - && JSONRequest.isTableArray(key)) { // JSONArray,批量新增或修改,往下一级提取 - onTableArrayParse(key, (JSONArray) value); + else if ((_method == POST || _method == PUT) && value instanceof List + && JSONMap.isTableArray(key)) { // L,批量新增或修改,往下一级提取 + onTableArrayParse(key, (L) value); } - else if (_method == PUT && value instanceof JSONArray && (whereList == null || whereList.contains(key) == false) - && StringUtil.isName(key.replaceFirst("[+-]$", ""))) { // PUT JSONArray - onPUTArrayParse(key, (JSONArray) value); + else if (_method == PUT && value instanceof List && (whereList == null || whereList.contains(key) == false) + && StringUtil.isName(key.replaceFirst("[+-]$", ""))) { // PUT L + onPUTArrayParse(key, (L) value); } - else { // JSONArray 或其它 Object,直接填充 + else { // L 或其它 Object,直接填充 if (onParse(key, value) == false) { invalidate(); } @@ -332,38 +341,38 @@ else if (_method == PUT && value instanceof JSONArray && (whereList == null || w String db = parser.getGlobalDatabase(); if (db != null) { - sqlRequest.putIfAbsent(JSONRequest.KEY_DATABASE, db); + sqlRequest.putIfAbsent(JSONMap.KEY_DATABASE, db); } String ds = parser.getGlobalDatasource(); if (ds != null) { - sqlRequest.putIfAbsent(JSONRequest.KEY_DATASOURCE, ds); + sqlRequest.putIfAbsent(JSONMap.KEY_DATASOURCE, ds); } String ns = parser.getGlobalNamespace(); if (ns != null) { - sqlRequest.putIfAbsent(JSONRequest.KEY_NAMESPACE, ns); + sqlRequest.putIfAbsent(JSONMap.KEY_NAMESPACE, ns); } String cl = parser.getGlobalCatalog(); if (cl != null) { - sqlRequest.putIfAbsent(JSONRequest.KEY_CATALOG, cl); + sqlRequest.putIfAbsent(JSONMap.KEY_CATALOG, cl); } String sch = parser.getGlobalSchema(); if (sch != null) { - sqlRequest.putIfAbsent(JSONRequest.KEY_SCHEMA, sch); + sqlRequest.putIfAbsent(JSONMap.KEY_SCHEMA, sch); } if (isSubquery == false) { // 解决 SQL 语法报错,子查询不能 EXPLAIN Boolean exp = parser.getGlobalExplain(); if (sch != null) { - sqlRequest.putIfAbsent(JSONRequest.KEY_EXPLAIN, exp); + sqlRequest.putIfAbsent(JSONMap.KEY_EXPLAIN, exp); } String cache = parser.getGlobalCache(); if (cache != null) { - sqlRequest.putIfAbsent(JSONRequest.KEY_CACHE, cache); + sqlRequest.putIfAbsent(JSONMap.KEY_CACHE, cache); } } } @@ -396,36 +405,35 @@ else if (_method == PUT && value instanceof JSONArray && (whereList == null || w @Override public boolean onParse(@NotNull String key, @NotNull Object value) throws Exception { if (key.endsWith("@")) { // StringUtil.isPath((String) value)) { - // [] 内主表 position > 0 时,用来生成 SQLConfig 的键值对全都忽略,不解析 - if (value instanceof JSONObject) { // key{}@ getRealKey, SQL 子查询对象,JSONObject -> SQLConfig.getSQL + // [] 内主表 position > 0 时,用来生成 SQLConfig 的键值对全都忽略,不解析 + if (value instanceof Map) { // key{}@ getRealKey, SQL 子查询对象,JSONRequest -> SQLConfig.getSQL String replaceKey = key.substring(0, key.length() - 1); - JSONObject subquery = (JSONObject) value; - String range = subquery.getString(JSONRequest.KEY_SUBQUERY_RANGE); - if (range != null && JSONRequest.SUBQUERY_RANGE_ALL.equals(range) == false - && JSONRequest.SUBQUERY_RANGE_ANY.equals(range) == false) { + M subquery = (M) value; + String range = getString(subquery, KEY_SUBQUERY_RANGE); + if (range != null && SUBQUERY_RANGE_ALL.equals(range) == false && SUBQUERY_RANGE_ANY.equals(range) == false) { throw new IllegalArgumentException("子查询 " + path + "/" + key + ":{ range:value } 中 value 只能为 [" - + JSONRequest.SUBQUERY_RANGE_ALL + ", " + JSONRequest.SUBQUERY_RANGE_ANY + "] 中的一个!"); + + SUBQUERY_RANGE_ALL + ", " + SUBQUERY_RANGE_ANY + "] 中的一个!"); } - JSONArray arr = parser.onArrayParse(subquery, path, key, true, null); + L arr = parser.onArrayParse(subquery, path, key, true, null); - JSONObject obj = arr == null || arr.isEmpty() ? null : arr.getJSONObject(0); + M obj = arr == null || arr.isEmpty() ? null : JSON.get(arr, 0); if (obj == null) { throw new Exception("服务器内部错误,解析子查询 " + path + "/" + key + ":{ } 为 Subquery 对象失败!"); } - String from = subquery.getString(JSONRequest.KEY_SUBQUERY_FROM); + String from = getString(subquery, apijson.JSONRequest.KEY_SUBQUERY_FROM); boolean isEmpty = StringUtil.isEmpty(from); - JSONObject arrObj = isEmpty ? null : obj.getJSONObject(from); + M arrObj = isEmpty ? null : JSON.get(obj, from); if (isEmpty) { Set> set = obj.entrySet(); for (Entry e : set) { String k = e == null ? null : e.getKey(); Object v = k == null ? null : e.getValue(); - if (v instanceof JSONObject && JSONRequest.isTableKey(k)) { + if (v instanceof Map && JSONMap.isTableKey(k)) { from = k; - arrObj = (JSONObject) v; + arrObj = (M) v; break; } } @@ -436,7 +444,7 @@ public boolean onParse(@NotNull String key, @NotNull Object value) throws Except + key + ":{ from:value } 中 value 对应的主表对象 " + from + ":{} 不存在!"); } - SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); + SQLConfig cfg = (SQLConfig) arrObj.get(AbstractParser.KEY_CONFIG); if (cfg == null) { throw new NotExistException(TAG + ".onParse cfg == null"); } @@ -474,9 +482,9 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 } // 非查询关键词 @key 不影响查询,直接跳过 - if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { + if (isTable && (key.startsWith("@") == false || JSONMap.TABLE_KEY_LIST.contains(key))) { Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" - + " || JSONRequest.TABLE_KEY_LIST.contains(key)) >> return null;"); + + " || apijson.JSONMap.TABLE_KEY_LIST.contains(key)) >> return null;"); // FIXME getCache() != null 时 return true,解决 RIGHT/OUTER/FOREIGN JOIN 主表无数据导致副表数据也不返回 return false; // 获取不到就不用再做无效的 query 了。不考虑 Table:{Table:{}} 嵌套 } @@ -488,18 +496,18 @@ else if (value instanceof String) { // //key{}@ getRealKey, 引用赋值路径 // if (target instanceof Map) { // target 可能是从 requestObject 里取出的 {} // if (isTable || targetPath.endsWith("[]/" + JSONResponse.KEY_INFO) == false) { // Log.d(TAG, "onParse target instanceof Map >> return false;"); -// return false; // FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONObject ?以前可能因为防止二次遍历再解析,现在只有一次遍历 +// return false; // FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONRequest ?以前可能因为防止二次遍历再解析,现在只有一次遍历 // } // } // -// // FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONObject ?以前可能因为防止二次遍历再解析,现在只有一次遍历 +// // FIXME 这个判断现在来看是否还有必要?为啥不允许为 JSONRequest ?以前可能因为防止二次遍历再解析,现在只有一次遍历 // if (targetPath.equals(target)) { // 必须 valuePath 和保证 getValueByPath 传进去的一致! // Log.d(TAG, "onParse targetPath.equals(target) >>"); // // //非查询关键词 @key 不影响查询,直接跳过 -// if (isTable && (key.startsWith("@") == false || JSONRequest.TABLE_KEY_LIST.contains(key))) { +// if (isTable && (key.startsWith("@") == false || apijson.JSONMap.TABLE_KEY_LIST.contains(key))) { // Log.e(TAG, "onParse isTable && (key.startsWith(@) == false" -// + " || JSONRequest.TABLE_KEY_LIST.contains(key)) >> return null;"); +// + " || apijson.JSONMap.TABLE_KEY_LIST.contains(key)) >> return null;"); // return false;//获取不到就不用再做无效的query了。不考虑 Table:{Table:{}}嵌套 // } else { // Log.d(TAG, "onParse isTable(table) == false >> return true;"); @@ -554,7 +562,7 @@ else if (isPlus) { functionMap.put(type, map); } } - else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(key) == false) { + else if (isTable && key.startsWith("@") && JSONMap.TABLE_KEY_LIST.contains(key) == false) { customMap.put(key, value); } else { @@ -575,14 +583,14 @@ else if (isTable && key.startsWith("@") && JSONRequest.TABLE_KEY_LIST.contains(k * @throws Exception */ @Override - public JSON onChildParse(int index, String key, JSONObject value, JSON cache) throws Exception { + public Object onChildParse(int index, String key, M value, Object cache) throws Exception { boolean isFirst = index <= 0; boolean isMain = isFirst && type == TYPE_ITEM; - JSON child; + Object child; boolean isEmpty; - if (apijson.JSONObject.isArrayKey(key)) { // APIJSON Array + if (JSONMap.isArrayKey(key)) { // APIJSON Array if (isMain) { throw new IllegalArgumentException(parentPath + "/" + key + ":{} 不合法!" + "数组 []:{} 中第一个 key:{} 必须是主表 TableKey:{} !不能为 arrayKey[]:{} !"); @@ -597,9 +605,9 @@ public JSON onChildParse(int index, String key, JSONObject value, JSON cache) th } } - String query = value.getString(KEY_QUERY); - child = parser.onArrayParse(value, path, key, isSubquery, cache instanceof JSONArray ? (JSONArray) cache : null); - isEmpty = child == null || ((JSONArray) child).isEmpty(); + String query = getString(value, KEY_QUERY); + child = parser.onArrayParse(value, path, key, isSubquery, cache instanceof List ? (L) cache : null); + isEmpty = child == null || ((List) child).isEmpty(); if ("2".equals(query) || "ALL".equals(query)) { // 不判断 isEmpty,因为分页数据可能只是某页没有 String totalKey = JSONResponse.formatArrayKey(key) + "Total"; @@ -619,7 +627,7 @@ public JSON onChildParse(int index, String key, JSONObject value, JSON cache) th } } else { //APIJSON Object - boolean isTableKey = JSONRequest.isTableKey(Pair.parseEntry(key, true).getKey()); + boolean isTableKey = JSONMap.isTableKey(Pair.parseEntry(key, true).getKey()); if (type == TYPE_ITEM && isTableKey == false) { throw new IllegalArgumentException(parentPath + "/" + key + ":{} 不合法!" + "数组 []:{} 中每个 key:{} 都必须是表 TableKey:{} 或 数组 arrayKey[]:{} !"); @@ -636,16 +644,16 @@ public JSON onChildParse(int index, String key, JSONObject value, JSON cache) th } child = parser.onObjectParse(value, path, key, isMain ? arrayConfig.setType(SQLConfig.TYPE_ITEM_CHILD_0) : null - , isSubquery, cache instanceof JSONObject ? (JSONObject) cache : null); + , isSubquery, cache instanceof Map ? (M) cache : null); - isEmpty = child == null || ((JSONObject) child).isEmpty(); + isEmpty = child == null || ((Map) child).isEmpty(); if (isFirst && isEmpty) { invalidate(); } } // Log.i(TAG, "onChildParse ObjectParser.onParse key = " + key + "; child = " + child); - return isEmpty ? null : child;//只添加! isChildEmpty的值,可能数据库返回数据不够count + return isEmpty ? null : child; // 只添加! isChildEmpty的值,可能数据库返回数据不够count } @@ -657,7 +665,7 @@ public JSON onChildParse(int index, String key, JSONObject value, JSON cache) th * @throws Exception */ @Override - public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throws Exception { + public void onPUTArrayParse(@NotNull String key, @NotNull L array) throws Exception { if (isTable == false || array.isEmpty()) { sqlRequest.put(key, array); Log.e(TAG, "onPUTArrayParse isTable == false || array == null || array.isEmpty() >> return;"); @@ -673,15 +681,15 @@ public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throw sqlRequest.put(key, array); return; } - String realKey = AbstractSQLConfig.getRealKey(method, key, false, false); + String realKey = AbstractSQLConfig.gainRealKey(method, key, false, false); //GET > add all 或 remove all > PUT > remove key //GET <<<<<<<<<<<<<<<<<<<<<<<<< - JSONObject rq = new JSONObject(true); - rq.put(JSONRequest.KEY_ID, request.get(JSONRequest.KEY_ID)); - rq.put(JSONRequest.KEY_COLUMN, realKey); - JSONObject rp = parseResponse(RequestMethod.GET, table, null, rq, null, false); + M rq = JSON.createJSONObject(); + rq.put(JSONMap.KEY_ID, request.get(JSONMap.KEY_ID)); + rq.put(JSONMap.KEY_COLUMN, realKey); + M rp = parseResponse(RequestMethod.GET, table, null, rq, null, false); //GET >>>>>>>>>>>>>>>>>>>>>>>>> @@ -689,22 +697,22 @@ public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throw Object target = rp == null ? null : rp.get(realKey); if (target instanceof String) { try { - target = JSON.parse((String) target); + target = JSON.parse(target); } catch (Throwable e) { if (Log.DEBUG) { Log.e(TAG, "try {\n" + - "\t\t\t\ttarget = JSON.parse((String) target);\n" + + "\t\t\t\ttarget = parseJSON((String) target);\n" + "\t\t\t}\n" + "\t\t\tcatch (Throwable e) = " + e.getMessage()); } } } - if (apijson.JSON.isBooleanOrNumberOrString(target)) { + if (apijson.JSON.isBoolOrNumOrStr(target)) { throw new NullPointerException("PUT " + path + ", " + realKey + " 类型为 " + target.getClass().getSimpleName() + "," + "不支持 Boolean, String, Number 等类型字段使用 'key+': [] 或 'key-': [] !" - + "对应字段在数据库的值必须为 JSONArray, JSONObject 中的一种!" - + "值为 JSONObject 类型时传参必须是 'key+': [{'key': value, 'key2': value2}] 或 'key-': ['key', 'key2'] !" + + "对应字段在数据库的值必须为 L, JSONRequest 中的一种!" + + "值为 JSONRequest 类型时传参必须是 'key+': [{'key': value, 'key2': value2}] 或 'key-': ['key', 'key2'] !" ); } @@ -717,12 +725,12 @@ public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throw if (isAdd == false) { throw new NullPointerException("PUT " + path + ", " + realKey + (target == null ? " 值为 null,不支持移除!" : " 类型为 " + target.getClass().getSimpleName() + ",不支持这样移除!") - + "对应字段在数据库的值必须为 JSONArray, JSONObject 中的一种,且 key- 移除时,本身的值不能为 null!" - + "值为 JSONObject 类型时传参必须是 'key+': [{'key': value, 'key2': value2}] 或 'key-': ['key', 'key2'] !" + + "对应字段在数据库的值必须为 L, JSONRequest 中的一种,且 key- 移除时,本身的值不能为 null!" + + "值为 JSONRequest 类型时传参必须是 'key+': [{'key': value, 'key2': value2}] 或 'key-': ['key', 'key2'] !" ); } - targetArray = new JSONArray(); + targetArray = JSON.createJSONArray(); } for (int i = 0; i < array.size(); i++) { @@ -739,7 +747,7 @@ public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throw targetArray.add(obj); } else { if (obj != null && obj instanceof Map == false) { - throw new ConflictException("PUT " + path + ", " + key + "/" + i + " 必须为 JSONObject {} !"); + throw new ConflictException("PUT " + path + ", " + key + "/" + i + " 必须为 JSONRequest {} !"); } targetObj.putAll((Map) obj); } @@ -764,23 +772,23 @@ public void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throw //add all 或 remove all >>>>>>>>>>>>>>>>>>>>>>>>> //PUT <<<<<<<<<<<<<<<<<<<<<<<<< - sqlRequest.put(realKey, targetArray != null ? targetArray : JSON.toJSONString(targetObj, SerializerFeature.WriteMapNullValue)); + sqlRequest.put(realKey, targetArray != null ? targetArray : JSON.toJSONString(targetObj)); // FIXME, SerializerFeature.WriteMapNullValue)); //PUT >>>>>>>>>>>>>>>>>>>>>>>>> } @Override - public void onTableArrayParse(String key, JSONArray valueArray) throws Exception { - String childKey = key.substring(0, key.length() - JSONRequest.KEY_ARRAY.length()); + public void onTableArrayParse(String key, L valueArray) throws Exception { + String childKey = key.substring(0, key.length() - JSONMap.KEY_ARRAY.length()); int allCount = 0; - JSONArray ids = new JSONArray(); + L ids = JSON.createJSONArray(); int version = parser.getVersion(); int maxUpdateCount = parser.getMaxUpdateCount(); - SQLConfig cfg = null; // 不能污染当前的配置 getSQLConfig(); + SQLConfig cfg = null; // 不能污染当前的配置 getSQLConfig(); if (cfg == null) { // TODO 每次都创建成本比较高,是否新增 defaultInstance 或者 configInstance 用来专门 getIdKey 等? cfg = parser.createSQLConfig(); } @@ -790,16 +798,16 @@ public void onTableArrayParse(String key, JSONArray valueArray) throws Exception cfg.setTable(childKey); // Request 表 structure 中配置 "ALLOW_PARTIAL_UPDATE_FAILED": "Table[],key[],key:alias[]" 自动配置 boolean allowPartialFailed = cfg.allowPartialUpdateFailed(); - JSONArray failedIds = allowPartialFailed ? new JSONArray() : null; + L failedIds = allowPartialFailed ? JSON.createJSONArray() : null; int firstFailIndex = -1; - JSONObject firstFailReq = null; + M firstFailReq = null; Throwable firstFailThrow = null; for (int i = 0; i < valueArray.size(); i++) { //只要有一条失败,则抛出异常,全部失败 //TODO 改成一条多 VALUES 的 SQL 性能更高,报错也更会更好处理,更人性化 - JSONObject item; + M item; try { - item = valueArray.getJSONObject(i); + item = JSON.get(valueArray, i); if (item == null) { throw new NullPointerException(); } @@ -811,14 +819,15 @@ public void onTableArrayParse(String key, JSONArray valueArray) throws Exception } Object id = item.get(idKey); - JSONObject req = new JSONRequest(childKey, item); - JSONObject result = null; + M req = JSON.createJSONObject(childKey, item); + + M result = null; try { if (isNeedVerifyContent) { req = parser.parseCorrectRequest(method, childKey, version, "", req, maxUpdateCount, parser); } //parser.getMaxSQLCount() ? 可能恶意调用接口,把数据库拖死 - result = (JSONObject) onChildParse(0, "" + i, req, null); + result = (M) onChildParse(0, "" + i, req, null); } catch (Exception e) { if (allowPartialFailed == false) { @@ -827,14 +836,14 @@ public void onTableArrayParse(String key, JSONArray valueArray) throws Exception if (firstFailThrow == null) { firstFailThrow = e; - firstFailReq = valueArray.getJSONObject(i); // item + firstFailReq = JSON.get(valueArray, i); // item } } - result = result == null ? null : result.getJSONObject(childKey); + result = result == null ? null : JSON.get(result, childKey); boolean success = JSONResponse.isSuccess(result); - int count = result == null ? 0 : result.getIntValue(JSONResponse.KEY_COUNT); + int count = result == null ? 0 : getIntValue(result, JSONResponse.KEY_COUNT); if (id == null && result != null) { id = result.get(idKey); } @@ -849,7 +858,7 @@ public void onTableArrayParse(String key, JSONArray valueArray) throws Exception else { throw new ServerException( "批量新增/修改失败!" + key + "/" + i + ":" + (success ? "成功但 count != 1 !" - : (result == null ? "null" : result.getString(JSONResponse.KEY_MSG)) + : (result == null ? "null" : getString(result, JSONResponse.KEY_MSG)) )); } } @@ -864,19 +873,19 @@ public void onTableArrayParse(String key, JSONArray valueArray) throws Exception + "第 " + firstFailIndex + " 项失败原因:" + (firstFailThrow == null ? "" : firstFailThrow.getMessage())); } - JSONObject allResult = AbstractParser.newSuccessResult(); + M allResult = getParser().newSuccessResult(); if (failedCount > 0) { allResult.put("failedCount", failedCount); allResult.put("failedIdList", failedIds); - JSONObject failObj = new JSONObject(true); + M failObj = JSON.createJSONObject(); failObj.put("index", firstFailIndex); failObj.put(childKey, firstFailReq); if (firstFailThrow instanceof CommonException && firstFailThrow.getCause() != null) { firstFailThrow = firstFailThrow.getCause(); } - JSONObject obj = firstFailThrow == null ? failObj : AbstractParser.extendErrorResult(failObj, firstFailThrow, parser.isRoot()); + M obj = firstFailThrow == null ? failObj : getParser().extendErrorResult(failObj, firstFailThrow, parser.isRoot()); if (Log.DEBUG && firstFailThrow != null) { obj.put("trace:throw", firstFailThrow.getClass().getName()); obj.put("trace:stack", firstFailThrow.getStackTrace()); @@ -892,19 +901,20 @@ public void onTableArrayParse(String key, JSONArray valueArray) throws Exception @Override - public JSONObject parseResponse(RequestMethod method, String table, String alias - , JSONObject request, List joinList, boolean isProcedure) throws Exception { - SQLConfig config = newSQLConfig(method, table, alias, request, joinList, isProcedure) - .setParser(parser) + public M parseResponse(RequestMethod method, String table, String alias + , M request, List> joinList, boolean isProcedure) throws Exception { + SQLConfig config = newSQLConfig(method, table, alias, request, joinList, isProcedure) + .setParser(getParser()) .setObjectParser(this); return parseResponse(config, isProcedure); } @Override - public JSONObject parseResponse(SQLConfig config, boolean isProcedure) throws Exception { + public M parseResponse(SQLConfig config, boolean isProcedure) throws Exception { + parser = getParser(); if (parser.getSQLExecutor() == null) { parser.createSQLExecutor(); } - if (parser != null && config.getParser() == null) { + if (config.gainParser() == null) { config.setParser(parser); } return parser.getSQLExecutor().execute(config, isProcedure); @@ -912,8 +922,8 @@ public JSONObject parseResponse(SQLConfig config, boolean isProcedure) throws @Override - public SQLConfig newSQLConfig(boolean isProcedure) throws Exception { - String raw = Log.DEBUG == false || sqlRequest == null ? null : sqlRequest.getString(apijson.JSONRequest.KEY_RAW); + public SQLConfig newSQLConfig(boolean isProcedure) throws Exception { + String raw = Log.DEBUG == false || sqlRequest == null ? null : getString(sqlRequest, JSONMap.KEY_RAW); String[] keys = raw == null ? null : StringUtil.split(raw); if (keys != null && keys.length > 0) { boolean allow = AbstractSQLConfig.ALLOW_MISSING_KEY_4_COMBINE; @@ -931,7 +941,7 @@ public SQLConfig newSQLConfig(boolean isProcedure) throws Exception { } if (parser instanceof AbstractParser) { - ((AbstractParser) parser).putWarnIfNeed(JSONRequest.KEY_RAW, msg); + ((AbstractParser) parser).putWarnIfNeed(JSONMap.KEY_RAW, msg); } break; } @@ -947,12 +957,12 @@ public SQLConfig newSQLConfig(boolean isProcedure) throws Exception { * @throws Exception */ @Override - public AbstractObjectParser setSQLConfig() throws Exception { + public AbstractObjectParser setSQLConfig() throws Exception { return setSQLConfig(RequestMethod.isQueryMethod(method) ? 1 : 0, 0, 0); } @Override - public AbstractObjectParser setSQLConfig(int count, int page, int position) throws Exception { + public AbstractObjectParser setSQLConfig(int count, int page, int position) throws Exception { if (isTable == false || isReuse) { return setPosition(position); } @@ -978,16 +988,17 @@ public AbstractObjectParser setSQLConfig(int count, int page, int position) thro - protected SQLConfig sqlConfig = null;//array item复用 + protected SQLConfig sqlConfig = null;//array item复用 /**SQL查询,for array item * @return this * @throws Exception */ @Override - public AbstractObjectParser executeSQL() throws Exception { + public AbstractObjectParser executeSQL() throws Exception { //执行SQL操作数据库 if (isTable == false) {//提高性能 - sqlResponse = new JSONObject(sqlRequest); + sqlResponse = JSON.createJSONObject(); + sqlResponse.putAll(sqlRequest); } else { try { @@ -1025,7 +1036,7 @@ public AbstractObjectParser executeSQL() throws Exception { * @throws Exception */ @Override - public JSONObject response() throws Exception { + public M response() throws Exception { if (sqlResponse == null || sqlResponse.isEmpty()) { if (isTable) {//Table自身都获取不到值,则里面的Child都无意义,不需要再解析 return null; // response; @@ -1061,7 +1072,7 @@ public void onFunctionResponse(String type) throws Exception { Set> functionSet = map == null ? null : map.entrySet(); if (functionSet != null && functionSet.isEmpty() == false) { boolean isMinus = "-".equals(type); - JSONObject json = isMinus ? sqlRequest : response; // key-():function 是实时执行,而不是在这里批量执行 + M json = isMinus ? sqlRequest : response; // key-():function 是实时执行,而不是在这里批量执行 for (Entry entry : functionSet) { parseFunction(entry.getKey(), entry.getKey(), entry.getValue(), this.type == TYPE_ITEM ? path : parentPath, name, json, isMinus); @@ -1070,11 +1081,11 @@ public void onFunctionResponse(String type) throws Exception { } - //public void parseFunction(String key, String value, String parentPath, String currentName, JSONObject currentObject) throws Exception { + //public void parseFunction(String key, String value, String parentPath, String currentName, JSONRequest currentObject) throws Exception { // parseFunction(key, value, parentPath, currentName, currentObject, false); //} public void parseFunction(String rawKey, String key, String value, String parentPath - , String currentName, JSONObject currentObject, boolean isMinus) throws Exception { + , String currentName, M currentObject, boolean isMinus) throws Exception { Object result; boolean containRaw = rawKeyList != null && rawKeyList.contains(rawKey); @@ -1082,7 +1093,7 @@ public void parseFunction(String rawKey, String key, String value, String parent if (isProcedure) { FunctionBean fb = AbstractFunctionParser.parseFunction(value, currentObject, true, containRaw); - SQLConfig config = newSQLConfig(true); + SQLConfig config = newSQLConfig(true); String sch = fb.getSchema(); if (StringUtil.isNotEmpty(sch, true)) { config.setSchema(sch); @@ -1096,7 +1107,7 @@ public void parseFunction(String rawKey, String key, String value, String parent result = parser.onFunctionParse(key, value, parentPath, currentName, currentObject, containRaw); } - String k = AbstractSQLConfig.getRealKey(method, key, false, false); + String k = AbstractSQLConfig.gainRealKey(method, key, false, false); if (isProcedure == false && isMinus) { if (result != null) { @@ -1118,14 +1129,14 @@ public void parseFunction(String rawKey, String key, String value, String parent @Override public void onChildResponse() throws Exception { //把isTable时取出去child解析后重新添加回来 - Set> set = childMap == null ? null : childMap.entrySet(); + Set> set = childMap == null ? null : childMap.entrySet(); if (set != null) { int index = 0; - for (Entry entry : set) { + for (Entry entry : set) { Object child = entry == null ? null : onChildParse(index, entry.getKey(), entry.getValue(), null); if (child == null - || (child instanceof JSONObject && ((JSONObject) child).isEmpty()) - || (child instanceof JSONArray && ((JSONArray) child).isEmpty()) + || (child instanceof Map && ((M) child).isEmpty()) + || (child instanceof List && ((L) child).isEmpty()) ) { continue; } @@ -1145,10 +1156,10 @@ public Object onReferenceParse(@NotNull String path) { @SuppressWarnings("unchecked") @Override - public JSONObject onSQLExecute() throws Exception { + public M onSQLExecute() throws Exception { int position = getPosition(); - JSONObject result = getCache(); + M result = getCache(); if (result != null) { parser.putQueryResult(path, result); } @@ -1160,7 +1171,7 @@ else if (isArrayMainTable && position > 0) { // 数组主表使用专门的缓 boolean isSimpleArray = false; // 提取并缓存数组主表的列表数据 - List rawList = result == null ? null : (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); + List rawList = result == null ? null : (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (isArrayMainTable && position == 0 && rawList != null) { @@ -1169,14 +1180,16 @@ else if (isArrayMainTable && position > 0) { // 数组主表使用专门的缓 && (childMap == null || childMap.isEmpty()) && (table.equals(arrayTable)); - // APP JOIN 副表时副表返回了这个字段 rawList = (List) result.remove(AbstractSQLExecutor.KEY_RAW_LIST); + // 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 已经处理过 - JSONObject obj = rawList.get(i); + M obj = rawList.get(i); if (obj != null) { // obj.remove(AbstractSQLExecutor.KEY_VICE_ITEM); @@ -1197,7 +1210,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); } } @@ -1226,7 +1239,9 @@ public void recycle() { if (drop) { request.put(KEY_DROP, drop); } - + if (stringKeyList != null) { // 避免被全局关键词覆盖 && ! stringKeyList.isEmpty()) { + request.put(KEY_STRING, StringUtil.get(stringKeyList.toArray())); + } method = null; parentPath = null; @@ -1249,12 +1264,9 @@ public void recycle() { - - - protected RequestMethod method; @Override - public AbstractObjectParser setMethod(RequestMethod method) { + public AbstractObjectParser setMethod(RequestMethod method) { if (this.method != method) { this.method = method; sqlConfig = null; @@ -1268,8 +1280,6 @@ public RequestMethod getMethod() { } - - @Override public boolean isTable() { return isTable; @@ -1286,27 +1296,27 @@ public String getTable() { public String getAlias() { return alias; } + @Override - public SQLConfig getArrayConfig() { + public SQLConfig getArrayConfig() { return arrayConfig; } - @Override - public SQLConfig getSQLConfig() { + public SQLConfig getSQLConfig() { return sqlConfig; } @Override - public JSONObject getResponse() { + public M getResponse() { return response; } @Override - public JSONObject getSqlRequest() { + public M getSQLRequest() { return sqlRequest; } @Override - public JSONObject getSqlResponse() { + public M getSQLResponse() { return sqlResponse; } @@ -1319,9 +1329,8 @@ public Map> getFunctionMap() { return functionMap; } @Override - public Map getChildMap() { + public Map getChildMap() { return childMap; } - } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java index 624830374..cd0b2c7b8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractParser.java @@ -1,20 +1,18 @@ -/*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 apijson.*; import apijson.orm.exception.ConflictException; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; import java.io.UnsupportedEncodingException; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.URLDecoder; import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.SQLException; import java.sql.Savepoint; @@ -26,27 +24,22 @@ import javax.management.Query; -import apijson.JSON; -import apijson.JSONRequest; -import apijson.JSONResponse; -import apijson.Log; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.StringUtil; import apijson.orm.exception.CommonException; import apijson.orm.exception.UnsupportedDataTypeException; -import static apijson.JSONObject.KEY_COMBINE; -import static apijson.JSONObject.KEY_EXPLAIN; +import static apijson.JSON.*; +import static apijson.JSONMap.*; +import static apijson.JSONRequest.*; import static apijson.RequestMethod.CRUD; import static apijson.RequestMethod.GET; -/**Parser for parsing request to JSONObject +/**Parser for parsing request to JSONRequest * @author Lemon */ -public abstract class AbstractParser implements Parser, ParserCreator, VerifierCreator, SQLCreator { +public abstract class AbstractParser, L extends List> + implements Parser { protected static final String TAG = "AbstractParser"; - + /** * JSON 对象、数组对应的数据源、版本、角色、method等 */ @@ -69,6 +62,12 @@ public abstract class AbstractParser implements Parser, Par */ 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 开始 @@ -123,7 +122,6 @@ public int getMaxQueryDepth() { return MAX_QUERY_DEPTH; } - /** * method = null */ @@ -153,7 +151,7 @@ public AbstractParser(RequestMethod method, boolean needVerify) { public boolean isRoot() { return isRoot; } - public AbstractParser setRoot(boolean isRoot) { + public AbstractParser setRoot(boolean isRoot) { this.isRoot = isRoot; return this; } @@ -167,7 +165,7 @@ public AbstractParser setRoot(boolean isRoot) { public String getWarn(String type) { return warnMap == null ? null : warnMap.get(type); } - public AbstractParser putWarnIfNeed(String type, String warn) { + public AbstractParser putWarnIfNeed(String type, String warn) { if (Log.DEBUG) { String w = getWarn(type); if (StringUtil.isEmpty(w, true)) { @@ -176,7 +174,7 @@ public AbstractParser putWarnIfNeed(String type, String warn) { } return this; } - public AbstractParser putWarn(String type, String warn) { + public AbstractParser putWarn(String type, String warn) { if (warnMap == null) { warnMap = new LinkedHashMap<>(); } @@ -232,7 +230,7 @@ public List getContactIdList() { return visitor; } @Override - public AbstractParser setVisitor(@NotNull Visitor visitor) { + public AbstractParser setVisitor(@NotNull Visitor visitor) { this.visitor = visitor; return this; } @@ -245,7 +243,7 @@ public RequestMethod getMethod() { } @NotNull @Override - public AbstractParser setMethod(RequestMethod method) { + public AbstractParser setMethod(RequestMethod method) { this.requestMethod = method == null ? GET : method; this.transactionIsolation = RequestMethod.isQueryMethod(method) ? Connection.TRANSACTION_NONE : Connection.TRANSACTION_REPEATABLE_READ; return this; @@ -257,7 +255,7 @@ public int getVersion() { return version; } @Override - public AbstractParser setVersion(int version) { + public AbstractParser setVersion(int version) { this.version = version; return this; } @@ -268,7 +266,7 @@ public String getTag() { return tag; } @Override - public AbstractParser setTag(String tag) { + public AbstractParser setTag(String tag) { this.tag = tag; return this; } @@ -277,24 +275,24 @@ public AbstractParser setTag(String tag) { public String getRequestURL() { return requestURL; } - public AbstractParser setRequestURL(String requestURL) { + public AbstractParser setRequestURL(String requestURL) { this.requestURL = requestURL; return this; } - protected JSONObject requestObject; + protected M requestObject; @Override - public JSONObject getRequest() { + public M getRequest() { return requestObject; } @Override - public AbstractParser setRequest(JSONObject request) { + public AbstractParser setRequest(M request) { this.requestObject = request; return this; } protected Boolean globalFormat; - public AbstractParser setGlobalFormat(Boolean globalFormat) { + public AbstractParser setGlobalFormat(Boolean globalFormat) { this.globalFormat = globalFormat; return this; } @@ -303,7 +301,7 @@ public Boolean getGlobalFormat() { return globalFormat; } protected String globalRole; - public AbstractParser setGlobalRole(String globalRole) { + public AbstractParser setGlobalRole(String globalRole) { this.globalRole = globalRole; return this; } @@ -312,7 +310,7 @@ public String getGlobalRole() { return globalRole; } protected String globalDatabase; - public AbstractParser setGlobalDatabase(String globalDatabase) { + public AbstractParser setGlobalDatabase(String globalDatabase) { this.globalDatabase = globalDatabase; return this; } @@ -326,13 +324,13 @@ public String getGlobalDatabase() { public String getGlobalDatasource() { return globalDatasource; } - public AbstractParser setGlobalDatasource(String globalDatasource) { + public AbstractParser setGlobalDatasource(String globalDatasource) { this.globalDatasource = globalDatasource; return this; } protected String globalNamespace; - public AbstractParser setGlobalNamespace(String globalNamespace) { + public AbstractParser setGlobalNamespace(String globalNamespace) { this.globalNamespace = globalNamespace; return this; } @@ -342,7 +340,7 @@ public String getGlobalNamespace() { } protected String globalCatalog; - public AbstractParser setGlobalCatalog(String globalCatalog) { + public AbstractParser setGlobalCatalog(String globalCatalog) { this.globalCatalog = globalCatalog; return this; } @@ -352,7 +350,7 @@ public String getGlobalCatalog() { } protected String globalSchema; - public AbstractParser setGlobalSchema(String globalSchema) { + public AbstractParser setGlobalSchema(String globalSchema) { this.globalSchema = globalSchema; return this; } @@ -362,7 +360,7 @@ public String getGlobalSchema() { } protected Boolean globalExplain; - public AbstractParser setGlobalExplain(Boolean globalExplain) { + public AbstractParser setGlobalExplain(Boolean globalExplain) { this.globalExplain = globalExplain; return this; } @@ -371,7 +369,7 @@ public Boolean getGlobalExplain() { return globalExplain; } protected String globalCache; - public AbstractParser setGlobalCache(String globalCache) { + public AbstractParser setGlobalCache(String globalCache) { this.globalCache = globalCache; return this; } @@ -381,7 +379,7 @@ public String getGlobalCache() { } @Override - public AbstractParser setNeedVerify(boolean needVerify) { + public AbstractParser setNeedVerify(boolean needVerify) { setNeedVerifyLogin(needVerify); setNeedVerifyRole(needVerify); setNeedVerifyContent(needVerify); @@ -394,7 +392,7 @@ public boolean isNeedVerifyLogin() { return needVerifyLogin; } @Override - public AbstractParser setNeedVerifyLogin(boolean needVerifyLogin) { + public AbstractParser setNeedVerifyLogin(boolean needVerifyLogin) { this.needVerifyLogin = needVerifyLogin; return this; } @@ -404,7 +402,7 @@ public boolean isNeedVerifyRole() { return needVerifyRole; } @Override - public AbstractParser setNeedVerifyRole(boolean needVerifyRole) { + public AbstractParser setNeedVerifyRole(boolean needVerifyRole) { this.needVerifyRole = needVerifyRole; return this; } @@ -414,32 +412,47 @@ public boolean isNeedVerifyContent() { return needVerifyContent; } @Override - public AbstractParser setNeedVerifyContent(boolean needVerifyContent) { + public AbstractParser setNeedVerifyContent(boolean needVerifyContent) { this.needVerifyContent = needVerifyContent; return this; } - - protected SQLExecutor sqlExecutor; - protected Verifier verifier; + protected SQLExecutor sqlExecutor; + protected Verifier verifier; protected Map queryResultMap;//path-result @Override - public SQLExecutor getSQLExecutor() { + public SQLExecutor getSQLExecutor() { if (sqlExecutor == null) { sqlExecutor = createSQLExecutor(); - sqlExecutor.setParser(this); } + sqlExecutor.setParser(this); return sqlExecutor; } @Override - public Verifier getVerifier() { + public Verifier getVerifier() { if (verifier == null) { verifier = createVerifier().setVisitor(getVisitor()); } + verifier.setParser(this); return verifier; } + /**解析请求JSONObject + * @param request => URLDecoder.decode(request, UTF_8); + * @return + * @throws Exception + */ + public static > M parseRequest(String request) throws Exception { + try { + M req = JSON.parseObject(request); + Objects.requireNonNull(req); + return req; + } catch (Throwable e) { + throw new UnsupportedEncodingException("JSON格式不合法!" + e.getMessage() + "! " + request); + } + } + /**解析请求json并获取对应结果 * @param request * @return @@ -454,7 +467,7 @@ public String parse(String request) { */ @NotNull @Override - public String parse(JSONObject request) { + public String parse(M request) { return JSON.toJSONString(parseResponse(request)); } @@ -464,12 +477,15 @@ public String parse(JSONObject request) { */ @NotNull @Override - public JSONObject parseResponse(String request) { + public M parseResponse(String request) { Log.d(TAG, "\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" + requestMethod + "/parseResponse request = \n" + request + "\n\n"); try { - requestObject = parseRequest(request); + requestObject = JSON.parseObject(request); + if (requestObject == null) { + throw new UnsupportedEncodingException("JSON格式不合法!"); + } } catch (Exception e) { return newErrorResult(e, isRoot); } @@ -486,19 +502,26 @@ public JSONObject parseResponse(String request) { */ @NotNull @Override - public JSONObject parseResponse(JSONObject request) { + public M parseResponse(M request) { long startTime = System.currentTimeMillis(); Log.d(TAG, "parseResponse startTime = " + startTime + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n\n "); requestObject = request; try { - setVersion(requestObject.getIntValue(JSONRequest.KEY_VERSION)); - requestObject.remove(JSONRequest.KEY_VERSION); + setGlobalFormat(getBoolean(requestObject, KEY_FORMAT)); + requestObject.remove(KEY_FORMAT); + } catch (Exception e) { + return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); + } + + try { + setVersion(getIntValue(requestObject, KEY_VERSION)); + requestObject.remove(KEY_VERSION); if (getMethod() != RequestMethod.CRUD) { - setTag(requestObject.getString(JSONRequest.KEY_TAG)); - requestObject.remove(JSONRequest.KEY_TAG); + setTag(getString(requestObject, KEY_TAG)); + requestObject.remove(KEY_TAG); } } catch (Exception e) { return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); @@ -522,40 +545,37 @@ public JSONObject parseResponse(JSONObject request) { //必须在parseCorrectRequest后面,因为parseCorrectRequest可能会添加 @role if (isNeedVerifyRole() && globalRole == null) { try { - setGlobalRole(requestObject.getString(JSONRequest.KEY_ROLE)); - requestObject.remove(JSONRequest.KEY_ROLE); + setGlobalRole(getString(requestObject, KEY_ROLE)); + requestObject.remove(KEY_ROLE); } catch (Exception e) { return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); } } try { - setGlobalDatabase(requestObject.getString(JSONRequest.KEY_DATABASE)); - setGlobalDatasource(requestObject.getString(JSONRequest.KEY_DATASOURCE)); - setGlobalNamespace(requestObject.getString(JSONRequest.KEY_NAMESPACE)); - setGlobalCatalog(requestObject.getString(JSONRequest.KEY_CATALOG)); - setGlobalSchema(requestObject.getString(JSONRequest.KEY_SCHEMA)); - - setGlobalExplain(requestObject.getBoolean(JSONRequest.KEY_EXPLAIN)); - setGlobalCache(requestObject.getString(JSONRequest.KEY_CACHE)); - setGlobalFormat(requestObject.getBoolean(JSONRequest.KEY_FORMAT)); - - requestObject.remove(JSONRequest.KEY_DATABASE); - requestObject.remove(JSONRequest.KEY_DATASOURCE); - requestObject.remove(JSONRequest.KEY_NAMESPACE); - requestObject.remove(JSONRequest.KEY_CATALOG); - requestObject.remove(JSONRequest.KEY_SCHEMA); - - requestObject.remove(JSONRequest.KEY_EXPLAIN); - requestObject.remove(JSONRequest.KEY_CACHE); - requestObject.remove(JSONRequest.KEY_FORMAT); + setGlobalDatabase(getString(requestObject, KEY_DATABASE)); + setGlobalDatasource(getString(requestObject, KEY_DATASOURCE)); + setGlobalNamespace(getString(requestObject, KEY_NAMESPACE)); + setGlobalCatalog(getString(requestObject, KEY_CATALOG)); + setGlobalSchema(getString(requestObject, KEY_SCHEMA)); + + setGlobalExplain(getBoolean(requestObject, KEY_EXPLAIN)); + setGlobalCache(getString(requestObject, KEY_CACHE)); + + requestObject.remove(KEY_DATABASE); + requestObject.remove(KEY_DATASOURCE); + requestObject.remove(KEY_NAMESPACE); + requestObject.remove(KEY_CATALOG); + requestObject.remove(KEY_SCHEMA); + + requestObject.remove(KEY_EXPLAIN); + requestObject.remove(KEY_CACHE); } catch (Exception e) { return extendErrorResult(requestObject, e, requestMethod, getRequestURL(), isRoot); } final String requestString = JSON.toJSONString(request);//request传进去解析后已经变了 - queryResultMap = new HashMap(); Exception error = null; @@ -580,14 +600,22 @@ public JSONObject parseResponse(JSONObject request) { requestObject = error == null ? extendSuccessResult(requestObject, warn, isRoot) : extendErrorResult(requestObject, error, requestMethod, getRequestURL(), isRoot); - JSONObject res = (globalFormat != null && globalFormat) && JSONResponse.isSuccess(requestObject) ? new JSONResponse(requestObject) : requestObject; + // FIXME 暂时先直接移除,后续排查是在哪里 put 进来 + requestObject.remove(KEY_DATABASE); + requestObject.remove(KEY_DATASOURCE); + requestObject.remove(KEY_NAMESPACE); + requestObject.remove(KEY_CATALOG); + requestObject.remove(KEY_SCHEMA); + + M res = (globalFormat != null && globalFormat) && JSONResponse.isSuccess(requestObject) ? JSONResponse.format(requestObject) : requestObject; long endTime = System.currentTimeMillis(); long duration = endTime - startTime; res.putIfAbsent("time", endTime); if (Log.DEBUG) { - res.put("sql:generate|cache|execute|maxExecute", getSQLExecutor().getGeneratedSQLCount() + "|" + getSQLExecutor().getCachedSQLCount() + "|" + getSQLExecutor().getExecutedSQLCount() + "|" + getMaxSQLCount()); + sqlExecutor = getSQLExecutor(); + res.put("sql:generate|cache|execute|maxExecute", sqlExecutor.getGeneratedSQLCount() + "|" + sqlExecutor.getCachedSQLCount() + "|" + sqlExecutor.getExecutedSQLCount() + "|" + getMaxSQLCount()); res.put("depth:count|max", queryDepth + "|" + getMaxQueryDepth()); executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration(); @@ -600,7 +628,19 @@ public JSONObject parseResponse(JSONObject 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); + } } } @@ -618,6 +658,7 @@ public JSONObject parseResponse(JSONObject request) { Log.fd(TAG, requestMethod + "/parseResponse endTime = " + endTime + "; duration = " + duration); Log.sl("", '>', "\n\n\n"); } + return res; } @@ -636,7 +677,7 @@ public void onVerifyContent() throws Exception { * @throws Exception */ @Override - public void onVerifyRole(@NotNull SQLConfig config) throws Exception { + public void onVerifyRole(@NotNull SQLConfig config) throws Exception { if (Log.DEBUG) { Log.i(TAG, "onVerifyRole config = " + JSON.toJSONString(config)); } @@ -655,23 +696,9 @@ public void onVerifyRole(@NotNull SQLConfig config) throws Exception { } - /**解析请求JSONObject - * @param request => URLDecoder.decode(request, UTF_8); - * @return - * @throws Exception - */ - @NotNull - public static JSONObject parseRequest(String request) throws Exception { - JSONObject obj = JSON.parseObject(request); - if (obj == null) { - throw new UnsupportedEncodingException("JSON格式不合法!"); - } - return obj; - } - @Override - public JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request - , int maxUpdateCount, SQLCreator creator) throws Exception { + public M parseCorrectRequest(RequestMethod method, String tag, int version, String name, @NotNull M request + , int maxUpdateCount, SQLCreator creator) throws Exception { if (RequestMethod.isPublicMethod(method)) { return request;//需要指定JSON结构的get请求可以改为post请求。一般只有对安全性要求高的才会指定,而这种情况用明文的GET方式几乎肯定不安全 @@ -679,44 +706,44 @@ public JSONObject parseCorrectRequest(RequestMethod method, String tag, int vers return batchVerify(method, tag, version, name, request, maxUpdateCount, creator); } - + /**自动根据 tag 是否为 TableKey 及是否被包含在 object 内来决定是否包装一层,改为 { tag: object, "tag": tag } * @param object * @param tag * @return */ - public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObject object, boolean isStructure) { + public M wrapRequest(RequestMethod method, String tag, M object, boolean isStructure) { boolean putTag = ! isStructure; if (object == null || object.containsKey(tag)) { //tag 是 Table 名或 Table[] if (putTag) { if (object == null) { - object = new JSONObject(true); + object = JSON.createJSONObject(); } - object.put(JSONRequest.KEY_TAG, tag); + object.put(KEY_TAG, tag); } return object; } boolean isDiffArrayKey = tag.endsWith(":[]"); - boolean isArrayKey = isDiffArrayKey || JSONRequest.isArrayKey(tag); + boolean isArrayKey = isDiffArrayKey || isArrayKey(tag); String key = isArrayKey ? tag.substring(0, tag.length() - (isDiffArrayKey ? 3 : 2)) : tag; - JSONObject target = object; - if (apijson.JSONObject.isTableKey(key)) { + M target = object; + if (isTableKey(key)) { if (isDiffArrayKey) { //自动为 tag = Comment:[] 的 { ... } 新增键值对为 { "Comment[]":[], "TYPE": { "Comment[]": "OBJECT[]" } ... } if (isStructure && (method == RequestMethod.POST || method == RequestMethod.PUT)) { String arrKey = key + "[]"; if (target.containsKey(arrKey) == false) { - target.put(arrKey, new JSONArray()); + target.put(arrKey, JSON.createJSONArray()); } try { - JSONObject type = target.getJSONObject(Operation.TYPE.name()); + Map type = JSON.get(target, Operation.TYPE.name()); if (type == null || (type.containsKey(arrKey) == false)) { if (type == null) { - type = new JSONObject(true); + type = new LinkedHashMap(); } type.put(arrKey, "OBJECT[]"); @@ -724,24 +751,24 @@ public static JSONObject wrapRequest(RequestMethod method, String tag, JSONObjec } } catch (Throwable e) { - Log.w(TAG, "wrapRequest try { JSONObject type = target.getJSONObject(Operation.TYPE.name()); } catch (Exception e) = " + e.getMessage()); + Log.w(TAG, "wrapRequest try { Map type = target.getJSONObject(Operation.TYPE.name()); } catch (Exception e) = " + e.getMessage()); } } } else { //自动为 tag = Comment 的 { ... } 包一层为 { "Comment": { ... } } if (isArrayKey == false || RequestMethod.isGetMethod(method, true)) { - target = new JSONObject(true); + target = JSON.createJSONObject(); target.put(tag, object); } else if (target.containsKey(key) == false) { - target = new JSONObject(true); + target = JSON.createJSONObject(); target.put(key, object); } } } if (putTag) { - target.put(JSONRequest.KEY_TAG, tag); + target.put(KEY_TAG, tag); } return target; @@ -753,7 +780,7 @@ else if (target.containsKey(key) == false) { * @param msg * @return */ - public static JSONObject newResult(int code, String msg) { + public M newResult(int code, String msg) { return newResult(code, msg, null); } @@ -765,7 +792,7 @@ public static JSONObject newResult(int code, String msg) { * @param warn * @return */ - public static JSONObject newResult(int code, String msg, String warn) { + public M newResult(int code, String msg, String warn) { return newResult(code, msg, warn, false); } @@ -778,7 +805,7 @@ public static JSONObject newResult(int code, String msg, String warn) { * @param isRoot * @return */ - public static JSONObject newResult(int code, String msg, String warn, boolean isRoot) { + public M newResult(int code, String msg, String warn, boolean isRoot) { return extendResult(null, code, msg, warn, isRoot); } @@ -790,7 +817,7 @@ public static JSONObject newResult(int code, String msg, String warn, boolean is * @param msg * @return */ - public static JSONObject extendResult(JSONObject object, int code, String msg, String warn, boolean isRoot) { + public M extendResult(M object, int code, String msg, String warn, boolean isRoot) { int index = Log.DEBUG == false || isRoot == false || msg == null ? -1 : msg.lastIndexOf(Log.KEY_SYSTEM_INFO_DIVIDER); String debug = Log.DEBUG == false || isRoot == false ? null : (index >= 0 ? msg.substring(index + Log.KEY_SYSTEM_INFO_DIVIDER.length()).trim() : " \n提 bug 请发请求和响应的【完整截屏】,没图的自行解决!" @@ -809,7 +836,7 @@ public static JSONObject extendResult(JSONObject object, int code, String msg, S msg = index >= 0 ? msg.substring(0, index) : msg; if (object == null) { - object = new JSONObject(true); + object = JSON.createJSONObject(); } if (object.get(JSONResponse.KEY_OK) == null) { @@ -819,9 +846,9 @@ public static JSONObject extendResult(JSONObject object, int code, String msg, S object.put(JSONResponse.KEY_CODE, code); } - String m = StringUtil.getString(object.getString(JSONResponse.KEY_MSG)); + String m = StringUtil.get(getString(object, JSONResponse.KEY_MSG)); if (m.isEmpty() == false) { - msg = m + " ;\n " + StringUtil.getString(msg); + msg = m + " ;\n " + StringUtil.get(msg); } object.put(JSONResponse.KEY_MSG, msg); @@ -842,11 +869,11 @@ public static JSONObject extendResult(JSONObject object, int code, String msg, S * @param object * @return */ - public static JSONObject extendSuccessResult(JSONObject object) { + public M extendSuccessResult(M object) { return extendSuccessResult(object, false); } - public static JSONObject extendSuccessResult(JSONObject object, boolean isRoot) { + public M extendSuccessResult(M object, boolean isRoot) { return extendSuccessResult(object, null, isRoot); } @@ -855,14 +882,14 @@ public static JSONObject extendSuccessResult(JSONObject object, boolean isRoot) * @param isRoot * @return */ - public static JSONObject extendSuccessResult(JSONObject object, String warn, boolean isRoot) { + public M extendSuccessResult(M object, String warn, boolean isRoot) { return extendResult(object, JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, warn, isRoot); } /**获取请求成功的状态内容 * @return */ - public static JSONObject newSuccessResult() { + public M newSuccessResult() { return newSuccessResult(null); } @@ -870,7 +897,7 @@ public static JSONObject newSuccessResult() { * @param warn * @return */ - public static JSONObject newSuccessResult(String warn) { + public M newSuccessResult(String warn) { return newSuccessResult(warn, false); } @@ -879,7 +906,7 @@ public static JSONObject newSuccessResult(String warn) { * @param isRoot * @return */ - public static JSONObject newSuccessResult(String warn, boolean isRoot) { + public M newSuccessResult(String warn, boolean isRoot) { return newResult(JSONResponse.CODE_SUCCESS, JSONResponse.MSG_SUCCEED, warn, isRoot); } @@ -888,7 +915,7 @@ public static JSONObject newSuccessResult(String warn, boolean isRoot) { * @param e * @return */ - public static JSONObject extendErrorResult(JSONObject object, Throwable e) { + public M extendErrorResult(M object, Throwable e) { return extendErrorResult(object, e, false); } /**添加请求成功的状态内容 @@ -897,14 +924,14 @@ public static JSONObject extendErrorResult(JSONObject object, Throwable e) { * @param isRoot * @return */ - public static JSONObject extendErrorResult(JSONObject object, Throwable e, boolean isRoot) { + public M extendErrorResult(M object, Throwable e, boolean isRoot) { return extendErrorResult(object, e, null, null, isRoot); } /**添加请求成功的状态内容 * @param object * @return */ - public static JSONObject extendErrorResult(JSONObject object, Throwable e, RequestMethod requestMethod, String url, boolean isRoot) { + public M extendErrorResult(M object, Throwable e, RequestMethod requestMethod, String url, boolean isRoot) { String msg = CommonException.getMsg(e); if (Log.DEBUG && isRoot) { @@ -985,7 +1012,7 @@ public static JSONObject extendErrorResult(JSONObject object, Throwable e, Reque * @param e * @return */ - public static JSONObject newErrorResult(Exception e) { + public M newErrorResult(Exception e) { return newErrorResult(e, false); } /**新建错误状态内容 @@ -993,14 +1020,14 @@ public static JSONObject newErrorResult(Exception e) { * @param isRoot * @return */ - public static JSONObject newErrorResult(Exception e, boolean isRoot) { + public M newErrorResult(Exception e, boolean isRoot) { if (e != null) { // if (Log.DEBUG) { e.printStackTrace(); // } String msg = CommonException.getMsg(e); - Integer code = CommonException.getCode(e); + int code = CommonException.getCode(e); return newResult(code, msg, null, isRoot); } @@ -1014,7 +1041,7 @@ public static JSONObject newErrorResult(Exception e, boolean isRoot) { * @throws Exception */ @Override - public JSONObject parseCorrectRequest() throws Exception { + public M parseCorrectRequest() throws Exception { return parseCorrectRequest(requestMethod, tag, version, "", requestObject, getMaxUpdateCount(), this); } @@ -1028,18 +1055,18 @@ public JSONObject parseCorrectRequest() throws Exception { * @throws Exception */ @Override - public JSONObject getStructure(@NotNull String table, String method, String tag, int version) throws Exception { + public M getStructure(@NotNull String table, String method, String tag, int version) throws Exception { String cacheKey = AbstractVerifier.getCacheKeyForRequest(method, tag); - SortedMap versionedMap = AbstractVerifier.REQUEST_MAP.get(cacheKey); + SortedMap> versionedMap = (SortedMap>) AbstractVerifier.REQUEST_MAP.get(cacheKey); - JSONObject result = versionedMap == null ? null : versionedMap.get(Integer.valueOf(version)); + Map result = versionedMap == null ? null : versionedMap.get(Integer.valueOf(version)); if (result == null) { // version <= 0 时使用最新,version > 0 时使用 > version 的最接近版本(最小版本) - Set> set = versionedMap == null ? null : versionedMap.entrySet(); + Set>> set = versionedMap == null ? null : versionedMap.entrySet(); if (set != null && set.isEmpty() == false) { - Entry maxEntry = null; + Entry> maxEntry = null; - for (Entry entry : set) { + for (Entry> entry : set) { if (entry == null || entry.getKey() == null || entry.getValue() == null) { continue; } @@ -1077,19 +1104,20 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, } // 获取指定的JSON结构 <<<<<<<<<<<<<< - SQLConfig config = createSQLConfig().setMethod(GET).setTable(table); + SQLConfig config = createSQLConfig().setMethod(GET).setTable(table); + config.setParser(this); config.setPrepared(false); config.setColumn(Arrays.asList("structure")); Map where = new HashMap(); where.put("method", method); - where.put(JSONRequest.KEY_TAG, tag); + where.put(KEY_TAG, tag); if (version > 0) { - where.put(JSONRequest.KEY_VERSION + ">=", version); + where.put(KEY_VERSION + ">=", version); } config.setWhere(where); - config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); + config.setOrder(KEY_VERSION + (version > 0 ? "+" : "-")); config.setCount(1); // too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 @@ -1100,14 +1128,14 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, // AbstractVerifier.REQUEST_MAP.put(cacheKey, versionedMap); } - return getJSONObject(result, "structure"); //解决返回值套了一层 "structure":{} + return JSON.get(result, "structure"); //解决返回值套了一层 "structure":{} } - protected Map arrayObjectParserCacheMap = new HashMap<>(); + protected Map> arrayObjectParserCacheMap = new HashMap<>(); - // protected SQLConfig itemConfig; + // protected SQLConfig itemConfig; /**获取单个对象,该对象处于parentObject内 * @param request parentObject 的 value * @param parentPath parentObject 的路径 @@ -1119,8 +1147,8 @@ public JSONObject getStructure(@NotNull String table, String method, String tag, * @throws Exception */ @Override - public JSONObject onObjectParse(final JSONObject request, String parentPath, String name - , final SQLConfig arrayConfig, boolean isSubquery, JSONObject cache) throws Exception { + public M onObjectParse(final M request, String parentPath, String name + , final SQLConfig arrayConfig, boolean isSubquery, M cache) throws Exception { if (Log.DEBUG) { Log.i(TAG, "\ngetObject: parentPath = " + parentPath @@ -1149,11 +1177,11 @@ public JSONObject onObjectParse(final JSONObject request, String parentPath, Str String table = entry.getKey(); //Comment // String alias = entry.getValue(); //to - boolean isTable = apijson.JSONObject.isTableKey(table); + boolean isTable = isTableKey(table); boolean isArrayMainTable = isSubquery == false && isTable && type == SQLConfig.TYPE_ITEM_CHILD_0 && arrayConfig != null && RequestMethod.isGetMethod(arrayConfig.getMethod(), true); boolean isReuse = isArrayMainTable && position > 0; - ObjectParser op = null; + ObjectParser op = null; if (isReuse) { // 数组主表使用专门的缓存数据 op = arrayObjectParserCacheMap.get(parentPath.substring(0, parentPath.lastIndexOf("[]") + 2)); op.setParentPath(parentPath); @@ -1168,7 +1196,7 @@ public JSONObject onObjectParse(final JSONObject request, String parentPath, Str op.setCache(cache); op = op.parse(name, isReuse); - JSONObject response = null; + M response = null; if (op != null) {//SQL查询结果为空时,functionMap和customMap没有意义 if (arrayConfig == null) { //Common @@ -1178,16 +1206,16 @@ public JSONObject onObjectParse(final JSONObject request, String parentPath, Str int query = arrayConfig.getQuery(); //total 这里不能用arrayConfig.getType(),因为在createObjectParser.onChildParse传到onObjectParse时已被改掉 - if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != JSONRequest.QUERY_TABLE && position == 0) { + if (type == SQLConfig.TYPE_ITEM_CHILD_0 && query != apijson.JSONRequest.QUERY_TABLE && position == 0) { //TODO 应在这里判断 @column 中是否有聚合函数,而不是 AbstractSQLConfig.getColumnString - JSONObject rp; + Map rp; Boolean compat = arrayConfig.getCompat(); if (compat != null && compat) { // 解决对聚合函数字段通过 query:2 分页查总数返回值错误 // 这里可能改变了内部的一些数据,下方通过 arrayConfig 还原 - SQLConfig cfg = op.setSQLConfig(0, 0, 0).getSQLConfig(); + SQLConfig cfg = op.setSQLConfig(0, 0, 0).getSQLConfig(); boolean isExplain = cfg.isExplain(); cfg.setExplain(false); @@ -1195,7 +1223,7 @@ public JSONObject onObjectParse(final JSONObject request, String parentPath, Str subqy.setFrom(cfg.getTable()); subqy.setConfig(cfg); - SQLConfig countSQLCfg = createSQLConfig(); + SQLConfig countSQLCfg = createSQLConfig(); countSQLCfg.setColumn(Arrays.asList("count(*):count")); countSQLCfg.setFrom(subqy); @@ -1206,14 +1234,14 @@ public JSONObject onObjectParse(final JSONObject request, String parentPath, Str else { // 对聚合函数字段通过 query:2 分页查总数返回值错误 RequestMethod method = op.getMethod(); - rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSqlResponse(); + rp = op.setMethod(RequestMethod.HEAD).setSQLConfig().executeSQL().getSQLResponse(); op.setMethod(method); } if (rp != null) { int index = parentPath.lastIndexOf("]/"); if (index >= 0) { - int total = rp.getIntValue(JSONResponse.KEY_COUNT); + int total = getIntValue(rp, JSONResponse.KEY_COUNT); String pathPrefix = parentPath.substring(0, index) + "]/"; putQueryResult(pathPrefix + JSONResponse.KEY_TOTAL, total); @@ -1230,15 +1258,15 @@ public JSONObject onObjectParse(final JSONObject request, String parentPath, Str page += min; max += min; - JSONObject pagination = new JSONObject(true); + M pagination = JSON.createJSONObject(); Object explain = rp.get(JSONResponse.KEY_EXPLAIN); - if (explain instanceof JSONObject) { + if (explain instanceof Map) { pagination.put(JSONResponse.KEY_EXPLAIN, explain); } pagination.put(JSONResponse.KEY_TOTAL, total); - pagination.put(JSONRequest.KEY_COUNT, count); - pagination.put(JSONRequest.KEY_PAGE, page); + pagination.put(apijson.JSONRequest.KEY_COUNT, count); + pagination.put(apijson.JSONRequest.KEY_PAGE, page); pagination.put(JSONResponse.KEY_MAX, max); pagination.put(JSONResponse.KEY_MORE, page < max); pagination.put(JSONResponse.KEY_FIRST, page == min); @@ -1247,7 +1275,7 @@ public JSONObject onObjectParse(final JSONObject request, String parentPath, Str putQueryResult(pathPrefix + JSONResponse.KEY_INFO, pagination); if (total <= count*(page - min)) { - query = JSONRequest.QUERY_TOTAL;//数量不够了,不再往后查询 + query = apijson.JSONRequest.QUERY_TOTAL;//数量不够了,不再往后查询 } } } @@ -1256,7 +1284,7 @@ public JSONObject onObjectParse(final JSONObject request, String parentPath, Str } //Table - if (query == JSONRequest.QUERY_TOTAL) { + if (query == apijson.JSONRequest.QUERY_TOTAL) { response = null;//不再往后查询 } else { response = op @@ -1291,14 +1319,14 @@ public JSONObject onObjectParse(final JSONObject request, String parentPath, Str * @throws Exception */ @Override - public JSONArray onArrayParse(JSONObject request, String parentPath, String name, boolean isSubquery, JSONArray cache) throws Exception { + public L onArrayParse(M request, String parentPath, String name, boolean isSubquery, L cache) throws Exception { if (Log.DEBUG) { Log.i(TAG, "\n\n\n onArrayParse parentPath = " + parentPath + "; name = " + name + "; request = " + JSON.toJSONString(request)); } //不能允许GETS,否则会被通过"[]":{"@role":"ADMIN"},"Table":{},"tag":"Table"绕过权限并能批量查询 - RequestMethod _method = request.get(apijson.JSONObject.KEY_METHOD) == null ? requestMethod : RequestMethod.valueOf(request.getString(apijson.JSONObject.KEY_METHOD)); + RequestMethod _method = request.get(KEY_METHOD) == null ? requestMethod : RequestMethod.valueOf(getString(request, KEY_METHOD)); if (isSubquery == false && RequestMethod.isGetMethod(_method, true) == false) { throw new UnsupportedOperationException("key[]:{} 只支持 GET, GETS 方法!其它方法不允许传 " + name + ":{} 等这种 key[]:{} 格式!"); } @@ -1309,32 +1337,32 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name //不能改变,因为后面可能继续用到,导致1以上都改变 []:{0:{Comment[]:{0:{Comment:{}},1:{...},...}},1:{...},...} - final String query = request.getString(JSONRequest.KEY_QUERY); - final Boolean compat = request.getBoolean(JSONRequest.KEY_COMPAT); - final Integer count = request.getInteger(JSONRequest.KEY_COUNT); //TODO 如果不想用默认数量可以改成 getIntValue(JSONRequest.KEY_COUNT); - final Integer page = request.getInteger(JSONRequest.KEY_PAGE); - final Object join = request.get(JSONRequest.KEY_JOIN); + final String query = getString(request, apijson.JSONRequest.KEY_QUERY); + final Boolean compat = getBoolean(request, apijson.JSONRequest.KEY_COMPAT); + final Integer count = getInteger(request, apijson.JSONRequest.KEY_COUNT); //TODO 如果不想用默认数量可以改成 getIntValue(apijson.JSONRequest.KEY_COUNT); + final Integer page = getInteger(request, apijson.JSONRequest.KEY_PAGE); + final Object join = request.get(apijson.JSONRequest.KEY_JOIN); int query2; if (query == null) { - query2 = JSONRequest.QUERY_TABLE; + query2 = apijson.JSONRequest.QUERY_TABLE; } else { switch (query) { case "0": - case JSONRequest.QUERY_TABLE_STRING: - query2 = JSONRequest.QUERY_TABLE; + case apijson.JSONRequest.QUERY_TABLE_STRING: + query2 = apijson.JSONRequest.QUERY_TABLE; break; case "1": - case JSONRequest.QUERY_TOTAL_STRING: - query2 = JSONRequest.QUERY_TOTAL; + case apijson.JSONRequest.QUERY_TOTAL_STRING: + query2 = apijson.JSONRequest.QUERY_TOTAL; break; case "2": - case JSONRequest.QUERY_ALL_STRING: - query2 = JSONRequest.QUERY_ALL; + case apijson.JSONRequest.QUERY_ALL_STRING: + query2 = apijson.JSONRequest.QUERY_ALL; break; default: - throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_QUERY + ":value 中 value 的值不合法!必须在 [0, 1, 2] 或 [TABLE, TOTAL, ALL] 内 !"); + throw new IllegalArgumentException(path + "/" + apijson.JSONRequest.KEY_QUERY + ":value 中 value 的值不合法!必须在 [0, 1, 2] 或 [TABLE, TOTAL, ALL] 内 !"); } } @@ -1343,7 +1371,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name int maxPage = getMaxQueryPage(); if (page2 < 0 || page2 > maxPage) { - throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_PAGE + ":value 中 value 的值不合法!必须在 " + minPage + "-" + maxPage + " 内 !"); + throw new IllegalArgumentException(path + "/" + apijson.JSONRequest.KEY_PAGE + ":value 中 value 的值不合法!必须在 " + minPage + "-" + maxPage + " 内 !"); } //不用total限制数量了,只用中断机制,total只在query = 1,2的时候才获取 @@ -1351,14 +1379,14 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name int max = isSubquery ? count2 : getMaxQueryCount(); if (count2 < 0 || count2 > max) { - throw new IllegalArgumentException(path + "/" + JSONRequest.KEY_COUNT + ":value 中 value 的值不合法!必须在 0-" + max + " 内 !"); + throw new IllegalArgumentException(path + "/" + apijson.JSONRequest.KEY_COUNT + ":value 中 value 的值不合法!必须在 0-" + max + " 内 !"); } - request.remove(JSONRequest.KEY_QUERY); - request.remove(JSONRequest.KEY_COMPAT); - request.remove(JSONRequest.KEY_COUNT); - request.remove(JSONRequest.KEY_PAGE); - request.remove(JSONRequest.KEY_JOIN); + request.remove(apijson.JSONRequest.KEY_QUERY); + request.remove(apijson.JSONRequest.KEY_COMPAT); + request.remove(apijson.JSONRequest.KEY_COUNT); + request.remove(apijson.JSONRequest.KEY_PAGE); + request.remove(apijson.JSONRequest.KEY_JOIN); Log.d(TAG, "onArrayParse query = " + query + "; count = " + count + "; page = " + page + "; join = " + join); if (request.isEmpty()) { // 如果条件成立,说明所有的 parentPath/name:request 中request都无效!!! 后续都不执行,没必要还原数组关键词浪费性能 @@ -1366,7 +1394,7 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name return null; } - JSONArray response = null; + L response = null; try { int size = count2 == 0 ? max : count2; //count为每页数量,size为第page页实际数量,max(size) = count Log.d(TAG, "onArrayParse size = " + size + "; page = " + page2); @@ -1382,15 +1410,15 @@ public JSONArray onArrayParse(JSONObject request, String parentPath, String name if (childKeys == null || childKeys.length <= 0 || request.containsKey(childKeys[0]) == false) { childKeys = null; } - else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // 可能无需提取,直接返回 rawList 即可 + else if (childKeys.length == 1 && isTableKey(childKeys[0])) { // 可能无需提取,直接返回 rawList 即可 arrTableKey = childKeys[0]; } //Table<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - List joinList = onJoinParse(join, request); - SQLConfig config = createSQLConfig() + List> joinList = onJoinParse(join, request); + SQLConfig config = createSQLConfig() .setMethod(requestMethod) .setCount(size) .setPage(page2) @@ -1399,11 +1427,11 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // .setTable(arrTableKey) .setJoinList(joinList); - JSONObject parent; + Map parent; boolean isExtract = true; - response = new JSONArray(); + response = JSON.createJSONArray(); //生成size个 for (int i = 0; i < (isSubquery ? 1 : size); i++) { parent = onObjectParse(request, isSubquery ? parentPath : path, isSubquery ? name : "" + i, config.setType(SQLConfig.TYPE_ITEM).setPosition(i), isSubquery, null); @@ -1414,18 +1442,18 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // long startTime = System.currentTimeMillis(); /* 这里优化了 Table[]: { Table:{} } 这种情况下的性能 - * 如果把 List 改成 JSONArray 来减少以下 addAll 一次复制,则会导致 AbstractSQLExecutor 等其它很多地方 get 要改为 getJSONObject, - * 修改类型会导致不兼容旧版依赖 ORM 的项目,而且整体上性能只有特殊情况下性能提升,其它非特殊情况下因为多出很多 instanceof JSONObject 的判断而降低了性能。 + * 如果把 List> 改成 L 来减少以下 addAll 一次复制,则会导致 AbstractSQLExecutor 等其它很多地方 get 要改为 getJSONObject, + * 修改类型会导致不兼容旧版依赖 ORM 的项目,而且整体上性能只有特殊情况下性能提升,其它非特殊情况下因为多出很多 instanceof Map 的判断而降低了性能。 */ - JSONObject fo = i != 0 || arrTableKey == null ? null : parent.getJSONObject(arrTableKey); + Map fo = i != 0 || arrTableKey == null ? null : JSON.get(parent, arrTableKey); @SuppressWarnings("unchecked") - List list = fo == null ? null : (List) fo.remove(AbstractSQLExecutor.KEY_RAW_LIST); + List> list = fo == null ? null : (List>) fo.remove(AbstractSQLExecutor.KEY_RAW_LIST); if (list != null && list.isEmpty() == false && (joinList == null || joinList.isEmpty())) { isExtract = false; list.set(0, fo); // 不知道为啥第 0 项也加了 @RAW@LIST - response.addAll(list); // List cannot match List response = new JSONArray(list); + response.addAll(list); // List> cannot match List response = JSON.createJSONArray(list); long endTime = System.currentTimeMillis(); // 0ms Log.d(TAG, "\n onArrayParse <<<<<<<<<<<<<<<<<<<<<<<<<<<<\n for (int i = 0; i < (isSubquery ? 1 : size); i++) " @@ -1470,11 +1498,11 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // } finally { //后面还可能用到,要还原 - request.put(JSONRequest.KEY_QUERY, query); - request.put(JSONRequest.KEY_COMPAT, compat); - request.put(JSONRequest.KEY_COUNT, count); - request.put(JSONRequest.KEY_PAGE, page); - request.put(JSONRequest.KEY_JOIN, join); + request.put(apijson.JSONRequest.KEY_QUERY, query); + request.put(apijson.JSONRequest.KEY_COMPAT, compat); + request.put(apijson.JSONRequest.KEY_COUNT, count); + request.put(apijson.JSONRequest.KEY_PAGE, page); + request.put(apijson.JSONRequest.KEY_JOIN, join); } if (Log.DEBUG) { @@ -1488,22 +1516,26 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // private static final List JOIN_COPY_KEY_LIST; static { // TODO 不全 JOIN_COPY_KEY_LIST = new ArrayList(); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ROLE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATABASE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_NAMESPACE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_CATALOG); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_SCHEMA); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_DATASOURCE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COLUMN); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_NULL); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_CAST); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_COMBINE); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_GROUP); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_HAVING_AND); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_ORDER); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_KEY); - JOIN_COPY_KEY_LIST.add(JSONRequest.KEY_RAW); + JOIN_COPY_KEY_LIST.add(KEY_ROLE); + JOIN_COPY_KEY_LIST.add(KEY_DATABASE); + JOIN_COPY_KEY_LIST.add(KEY_NAMESPACE); + JOIN_COPY_KEY_LIST.add(KEY_CATALOG); + JOIN_COPY_KEY_LIST.add(KEY_SCHEMA); + JOIN_COPY_KEY_LIST.add(KEY_DATASOURCE); + JOIN_COPY_KEY_LIST.add(KEY_COLUMN); + JOIN_COPY_KEY_LIST.add(KEY_NULL); + JOIN_COPY_KEY_LIST.add(KEY_CAST); + JOIN_COPY_KEY_LIST.add(KEY_COMBINE); + JOIN_COPY_KEY_LIST.add(KEY_GROUP); + JOIN_COPY_KEY_LIST.add(KEY_HAVING); + JOIN_COPY_KEY_LIST.add(KEY_HAVING_AND); + JOIN_COPY_KEY_LIST.add(KEY_SAMPLE); + JOIN_COPY_KEY_LIST.add(KEY_LATEST); + JOIN_COPY_KEY_LIST.add(KEY_PARTITION); + JOIN_COPY_KEY_LIST.add(KEY_FILL); + JOIN_COPY_KEY_LIST.add(KEY_ORDER); + JOIN_COPY_KEY_LIST.add(KEY_KEY); + JOIN_COPY_KEY_LIST.add(KEY_RAW); } /**JOIN 多表同时筛选 @@ -1512,46 +1544,78 @@ else if (childKeys.length == 1 && JSONRequest.isTableKey(childKeys[0])) { // * @return * @throws Exception */ - private List onJoinParse(Object join, JSONObject request) throws Exception { - JSONObject joinMap = null; + private List> onJoinParse(Object join, M request) throws Exception { + Map joinMap = null; - if (join instanceof JSONObject) { - joinMap = (JSONObject) join; + if (join instanceof Map) { + joinMap = (M) join; } else if (join instanceof String) { String[] sArr = request == null || request.isEmpty() ? null : StringUtil.split((String) join); if (sArr != null && sArr.length > 0) { - joinMap = new JSONObject(true); //注意:这里必须要保证join连接顺序,保证后边遍历是按照join参数的顺序生成的SQL + joinMap = new LinkedHashMap(); //注意:这里必须要保证join连接顺序,保证后边遍历是按照join参数的顺序生成的SQL for (int i = 0; i < sArr.length; i++) { - joinMap.put(sArr[i], new JSONObject()); + joinMap.put(sArr[i], new LinkedHashMap()); } } } else if (join != null){ - throw new UnsupportedDataTypeException(TAG + ".onJoinParse join 只能是 String 或 JSONObject 类型!"); + throw new UnsupportedDataTypeException(TAG + ".onJoinParse join 只能是 String 或 Map 类型!"); + } + + 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 : joinMap.entrySet(); + + 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; } - List joinList = new ArrayList<>(); + List> joinList = new ArrayList<>(); for (Entry e : set) { // { &/User:{}, == false) { + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":value 中value不合法!" + "必须为 &/Table0/key0, ( ) <> () * @@ -1564,8 +1628,8 @@ else if (join != null){ String tableKey = index < 0 ? path : path.substring(0, index); // User:owner int index2 = tableKey.lastIndexOf("/"); String arrKey = index2 < 0 ? null : tableKey.substring(0, index2); - if (arrKey != null && JSONRequest.isArrayKey(arrKey) == false) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + " 不是合法的数组 key[] !" + + if (arrKey != null && isArrayKey(arrKey) == false) { + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + " 不是合法的数组 key[] !" + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中不能有 join: value 键值对!"); } @@ -1574,75 +1638,75 @@ else if (join != null){ apijson.orm.Entry entry = Pair.parseEntry(tableKey, true); String table = entry.getKey(); // User if (StringUtil.isName(table) == false) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":value 中 value 的 Table 值 " + table + " 不合法!" + "必须为 &/Table0, 格式!" + e2.getMessage()); } if (arrKey != null) { - if (parentPathObj.get(JSONRequest.KEY_JOIN) != null) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + ":{ join: value } 中 value 不合法!" + + if (parentPathObj.get(apijson.JSONRequest.KEY_JOIN) != null) { + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + ":{ join: value } 中 value 不合法!" + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中不能有 join: value 键值对!"); } - Integer subPage = parentPathObj.getInteger(JSONRequest.KEY_PAGE); + Integer subPage = getInteger(parentPathObj, apijson.JSONRequest.KEY_PAGE); if (subPage != null && subPage != 0) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + ":{ page: value } 中 value 不合法!" + + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 对应的 " + arrKey + ":{ page: value } 中 value 不合法!" + "@ APP JOIN 最多允许跨 1 层,只能是子数组,且数组对象中 page 值只能为 null 或 0 !"); } } boolean isAppJoin = "@".equals(joinType); - JSONObject refObj = new JSONObject(tableObj.size(), true); + M refObj = JSON.createJSONObject(); String key = index < 0 ? null : path.substring(index + 1); // id@ if (key != null) { // 指定某个 key 为 JOIN ON 条件 if (key.indexOf("@") != key.length() - 1) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + key + " 不合法!" + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + key + " 不合法!" + "必须为 &/Table0,> tableSet = tableObj.entrySet(); // 取出所有 join 条件 - JSONObject requestObj = new JSONObject(true); // (JSONObject) obj.clone(); + M requestObj = JSON.createJSONObject(); // (Map) obj.clone(); boolean matchSingle = false; for (Entry tableEntry : tableSet) { @@ -1667,15 +1731,15 @@ else if (join != null){ apijson.orm.Entry te = tk == null || p.substring(ind2 + 1).indexOf("/") >= 0 ? null : Pair.parseEntry(tk, true); - if (te != null && JSONRequest.isTableKey(te.getKey()) && request.get(tk) instanceof JSONObject) { + if (te != null && isTableKey(te.getKey()) && request.get(tk) instanceof Map) { if (isAppJoin) { if (refObj.size() >= 1) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + k + " 不合法!" + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":" + e.getKey() + " 中 " + k + " 不合法!" + "@ APP JOIN 必须有且只有一个引用赋值键值对!"); } if (StringUtil.isName(k.substring(0, k.length() - 1)) == false) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 中 " + k + " 不合法 !" + + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":'" + e.getKey() + "' 中 " + k + " 不合法 !" + "@ APP JOIN 只允许 key@:/Table/refKey 这种 = 等价连接!"); } } @@ -1691,7 +1755,7 @@ else if (join != null){ continue; } - throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + JSONRequest.KEY_JOIN + " 关联的 Table 中," + throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + apijson.JSONRequest.KEY_JOIN + " 关联的 Table 中," + "join: ?/Table/key 时只能有 1 个 key@:value;join: ?/Table 时所有 key@:value 要么是符合 join 格式,要么能直接解析成具体值!"); // TODO 支持 join on } @@ -1702,7 +1766,7 @@ else if (join != null){ } else { if (k.endsWith("@")) { - throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + JSONRequest.KEY_JOIN + " 关联的 Table 中," + throw new UnsupportedOperationException(table + "/" + k + " 不合法!" + apijson.JSONRequest.KEY_JOIN + " 关联的 Table 中," + "join: ?/Table/key 时只能有 1 个 key@:value;join: ?/Table 时所有 key@:value 要么是符合 join 格式,要么能直接解析成具体值!"); // TODO 支持 join on } @@ -1714,21 +1778,30 @@ else if (join != null){ Set> refSet = refObj.entrySet(); if (refSet.isEmpty() && "*".equals(joinType) == false) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 alias 值 " + alias + " 不合法!" + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":value 中 value 的 alias 值 " + alias + " 不合法!" + "必须为 &/Table0, j = new Join<>(); j.setPath(e.getKey()); j.setJoinType(joinType); j.setTable(table); j.setAlias(alias); - j.setOuter((JSONObject) outer); + + M outerObj = (M) JSON.createJSONObject((Map) outer); + 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 = parentPathObj.getInteger(JSONRequest.KEY_COUNT); + Integer count = getInteger(parentPathObj, apijson.JSONRequest.KEY_COUNT); j.setCount(count == null ? getDefaultQueryCount() : count); } @@ -1770,22 +1843,22 @@ else if (join != null){ } //对引用的JSONObject添加条件 - JSONObject targetObj; + Map targetObj; try { - targetObj = request.getJSONObject(targetTableKey); + targetObj = JSON.get(request, targetTableKey); } catch (Exception e2) { - throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的 '" + targetTableKey + "':value 中 value 类型不合法!必须是 {} 这种 JSONObject 格式!" + e2.getMessage()); + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的 '" + targetTableKey + "':value 中 value 类型不合法!必须是 {} 这种 Map 格式!" + e2.getMessage()); } if (targetObj == null) { - throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 JSONObject 格式!"); + throw new IllegalArgumentException(e.getKey() + ":'/targetTable/targetKey' 中路径对应的对象 '" + targetTableKey + "':{} 不存在或值为 null !必须是 {} 这种 Map 格式!"); } Join.On on = new Join.On(); on.setKeyAndType(j.getJoinType(), j.getTable(), originKey); if (StringUtil.isName(on.getKey()) == false) { - throw new IllegalArgumentException(JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + on.getKey() + " 不合法!必须满足英文单词变量名格式!"); + throw new IllegalArgumentException(apijson.JSONRequest.KEY_JOIN + ":value 中 value 的 key@ 中 key 值 " + on.getKey() + " 不合法!必须满足英文单词变量名格式!"); } on.setOriginKey(originKey); @@ -1804,7 +1877,7 @@ else if (join != null){ // onList.add(table + "." + key + " = " + targetTable + "." + targetKey); // ON User.id = Moment.userId // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 <<<<<<<<< - // AbstractSQLConfig.newSQLConfig 中强制把 id, id{}, userId, userId{} 放到了最前面 tableObj.put(key, tableObj.remove(key)); + // AbstractSQLConfig.newSQLConfig 中强制把 id, id{}, userId, userId{} 放到了最前面 tableObj.put(key, tableObj.remove(key)); if (refObj.size() != tableObj.size()) { // 把 key 强制放最前,AbstractSQLExcecutor 中 config.putWhere 也是放尽可能最前 refObj.putAll(tableObj); @@ -1816,8 +1889,8 @@ else if (join != null){ // 保证和 SQLExcecutor 缓存的 Config 里 where 顺序一致,生成的 SQL 也就一致 >>>>>>>>> } - //拼接多个 SQLConfig 的SQL语句,然后执行,再把结果分别缓存(Moment, User等)到 SQLExecutor 的 cacheMap - // AbstractSQLConfig config0 = null; + //拼接多个 SQLConfig 的SQL语句,然后执行,再把结果分别缓存(Moment, User等)到 SQLExecutor 的 cacheMap + // AbstractSQLConfig config0 = null; // String sql = "SELECT " + config0.getColumnString() + " FROM " + config0.getTable() + " INNER JOIN " + targetTable + " ON " // + onList.get(0) + config0.getGroupString() + config0.getHavingString() + config0.getOrderString(); @@ -1829,24 +1902,32 @@ else if (join != null){ * @param pathKeys * @return */ - public static V getValue(JSONObject 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 = getJSONObject(parent, k); + try { + v = getFromObjOrArr(v, k); + } catch (Throwable e) { + if (IS_PRINT_BIG_LOG) { + e.printStackTrace(); + } + v = null; + } } - return parent == null ? null : (V) parent.get(getDecodedKey(pathKeys[last])); + return (V) v; } @@ -1871,8 +1952,8 @@ public static String getValuePath(String parentPath, String valuePath) { */ public static String getAbsPath(String path, String name) { Log.i(TAG, "getPath path = " + path + "; name = " + name + " <<<<<<<<<<<<<"); - path = StringUtil.getString(path); - name = StringUtil.getString(name); + path = StringUtil.get(path); + name = StringUtil.get(name); if (StringUtil.isNotEmpty(path, false)) { if (StringUtil.isNotEmpty(name, false)) { path += ((name.startsWith("/") ? "" : "/") + name); @@ -1913,7 +1994,7 @@ public static String replaceArrayChildPath(String parentPath, String valuePath) vs[i+1] = pos + "/" + vs[i+1]; } } - return StringUtil.getString(vs, "]/"); + return StringUtil.get(vs, "]/"); } } return valuePath; @@ -1952,16 +2033,16 @@ public Object getValueByPath(String valuePath) { } //取出key被valuePath包含的result,再从里面获取key对应的value - JSONObject parent = null; + Object parent = null; String[] keys = null; - for (Entry entry : queryResultMap.entrySet()){ + for (Entry entry : queryResultMap.entrySet()){ String path = entry.getKey(); if (valuePath.startsWith(path + "/")) { try { - parent = (JSONObject) entry.getValue(); + parent = entry.getValue(); } catch (Exception e) { - Log.e(TAG, "getValueByPath try { parent = (JSONObject) queryResultMap.get(path); } catch { " - + "\n parent not instanceof JSONObject!"); + Log.e(TAG, "getValueByPath try { parent = (Map) queryResultMap.get(path); } catch { " + + "\n parent not instanceof Map!"); parent = null; } if (parent != null) { @@ -1971,38 +2052,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]); - parent = getJSONObject(parent, k); - } - } - - 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; - } + target = getValue(parent, keys); // 逐层到达targetKey的直接容器JSONObject parent + if (target == null) { //从requestObject中取值 + target = getValue(requestObject, StringUtil.splitPath(valuePath)); } - - //从requestObject中取值 - target = getValue(requestObject, StringUtil.splitPath(valuePath)); - if (target != null) { - Log.i(TAG, "getValueByPath >> getValue >> return target = " + target); - return target; - } - - 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 -> [] @@ -2020,44 +2076,31 @@ public static String getDecodedKey(String key) { //依赖引用关系 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - - public static JSONObject getJSONObject(JSONObject object, String key) { - try { - return object.getJSONObject(key); - } catch (Exception e) { - Log.i(TAG, "getJSONObject try { return object.getJSONObject(key);" - + " } catch (Exception e) { \n" + e.getMessage()); - } - return null; - } - - public static final String KEY_CONFIG = "config"; public static final String KEY_SQL = "sql"; - protected Map> arrayMainCacheMap = new HashMap<>(); - public void putArrayMainCache(String arrayPath, List mainTableDataList) { + protected Map> arrayMainCacheMap = new HashMap<>(); + public void putArrayMainCache(String arrayPath, List mainTableDataList) { arrayMainCacheMap.put(arrayPath, mainTableDataList); } - public List getArrayMainCache(String arrayPath) { + public List getArrayMainCache(String arrayPath) { return arrayMainCacheMap.get(arrayPath); } - public JSONObject getArrayMainCacheItem(String arrayPath, int position) { - List list = getArrayMainCache(arrayPath); + public M getArrayMainCacheItem(String arrayPath, int position) { + List list = getArrayMainCache(arrayPath); return list == null || position >= list.size() ? null : list.get(position); } - /**执行 SQL 并返回 JSONObject + /**执行 SQL 并返回 Map * @param config * @return * @throws Exception */ @Override - public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Exception { + public M executeSQL(SQLConfig config, boolean isSubquery) throws Exception { if (config == null) { Log.d(TAG, "executeSQL config == null >> return null;"); return null; @@ -2068,44 +2111,43 @@ public JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Exc config.setTag(getTag()); if (isSubquery) { - JSONObject sqlObj = new JSONObject(true); + M sqlObj = JSON.createJSONObject(); sqlObj.put(KEY_CONFIG, config); return sqlObj;//容易丢失信息 JSON.parseObject(config); } try { - JSONObject result; + M result; boolean explain = config.isExplain(); if (explain) { //如果先执行 explain,则 execute 会死循环,所以只能先执行非 explain config.setExplain(false); //对下面 config.getSQL(false); 生效 - JSONObject res = getSQLExecutor().execute(config, false); + M res = getSQLExecutor().execute(config, false); //如果是查询方法,才能执行explain if (RequestMethod.isQueryMethod(config.getMethod()) && config.isElasticsearch() == false){ config.setExplain(explain); - JSONObject explainResult = config.isMain() && config.getPosition() != 0 ? null : getSQLExecutor().execute(config, false); + Map explainResult = config.isMain() && config.getPosition() != 0 ? null : getSQLExecutor().execute(config, false); if (explainResult == null) { result = res; } else { - result = new JSONObject(true); + result = JSON.createJSONObject(); result.put(KEY_EXPLAIN, explainResult); result.putAll(res); } } else {//如果是更新请求,不执行explain,但可以返回sql - result = new JSONObject(true); - result.put(KEY_SQL, config.getSQL(false)); + result = JSON.createJSONObject(); + result.put(KEY_SQL, config.gainSQL(false)); result.putAll(res); } } else { - sqlExecutor = getSQLExecutor(); - result = sqlExecutor.execute(config, false); - // FIXME 改为直接在 sqlExecutor 内加好,最后 Parser 取结果,可以解决并发执行导致内部计算出错 + result = getSQLExecutor().execute(config, false); + // FIXME 改为直接在 sqlExecutor 内加好,最后 Parser 取结果,可以解决并发执行导致内部计算出错 // executedSQLDuration += sqlExecutor.getExecutedSQLDuration() + sqlExecutor.getSqlResultDuration(); } @@ -2226,8 +2268,8 @@ protected void onClose() { queryResultMap = null; } - private void setOpMethod(JSONObject request, ObjectParser op, String key) { - String _method = key == null ? null : request.getString(apijson.JSONObject.KEY_METHOD); + private void setOpMethod(Map request, ObjectParser op, String key) { + String _method = key == null ? null : getString(request, KEY_METHOD); if (_method != null) { RequestMethod method = RequestMethod.valueOf(_method); // 必须精准匹配,避免缓存命中率低 this.setMethod(method); @@ -2235,9 +2277,9 @@ private void setOpMethod(JSONObject request, ObjectParser op, String key) { } } - protected JSONObject getRequestStructure(RequestMethod method, String tag, int version) throws Exception { + protected M getRequestStructure(RequestMethod method, String tag, int version) throws Exception { // 获取指定的JSON结构 <<<<<<<<<<<< - JSONObject object = null; + M object = null; String error = ""; try { object = getStructure("Request", method.name(), tag, version); @@ -2251,8 +2293,20 @@ protected JSONObject getRequestStructure(RequestMethod method, String tag, int v return object; } - protected JSONObject batchVerify(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request, int maxUpdateCount, SQLCreator creator) throws Exception { - JSONObject correctRequest = new JSONObject(true); + public static final Map KEY_METHOD_ENUM_MAP; + static { + KEY_METHOD_ENUM_MAP = new LinkedHashMap<>(); + KEY_METHOD_ENUM_MAP.put(KEY_GET, RequestMethod.GET); + KEY_METHOD_ENUM_MAP.put(KEY_GETS, RequestMethod.GETS); + KEY_METHOD_ENUM_MAP.put(KEY_HEAD, RequestMethod.HEAD); + KEY_METHOD_ENUM_MAP.put(KEY_HEADS, RequestMethod.HEADS); + KEY_METHOD_ENUM_MAP.put(KEY_POST, RequestMethod.POST); + KEY_METHOD_ENUM_MAP.put(KEY_PUT, RequestMethod.PUT); + KEY_METHOD_ENUM_MAP.put(KEY_DELETE, RequestMethod.DELETE); + } + + protected M batchVerify(RequestMethod method, String tag, int version, String name, @NotNull M request, int maxUpdateCount, SQLCreator creator) throws Exception { + M correctRequest = JSON.createJSONObject(); List removeTmpKeys = new ArrayList<>(); // 请求json里面的临时变量,不需要带入后面的业务中,比如 @post、@get等 Set reqSet = request == null ? null : request.keySet(); @@ -2262,38 +2316,38 @@ protected JSONObject batchVerify(RequestMethod method, String tag, int version, for (String key : reqSet) { // key 重复直接抛错(xxx:alias, xxx:alias[]) - if (correctRequest.containsKey(key) || correctRequest.containsKey(key + apijson.JSONObject.KEY_ARRAY)) { + if (correctRequest.containsKey(key) || correctRequest.containsKey(key + KEY_ARRAY)) { throw new IllegalArgumentException("对象名重复,请添加别名区分 ! 重复对象名为: " + key); } - boolean isPost = apijson.orm.JSONRequest.KEY_POST.equals(key); + boolean isPost = KEY_POST.equals(key); // @post、@get 等 RequestMethod try { - RequestMethod keyMethod = isPost ? RequestMethod.POST : JSONRequest.KEY_METHOD_ENUM_MAP.get(key); + RequestMethod keyMethod = isPost ? RequestMethod.POST : KEY_METHOD_ENUM_MAP.get(key); if (keyMethod != null) { // 如果不匹配,异常不处理即可 removeTmpKeys.add(key); Object val = request.get(key); - JSONObject obj = val instanceof JSONObject ? request.getJSONObject(key) : null; + Map obj = val instanceof Map ? JSON.get(request, key) : null; if (obj == null) { if (val instanceof String) { String[] tbls = StringUtil.split((String) val); if (tbls != null && tbls.length > 0) { - obj = new JSONObject(true); + obj = new LinkedHashMap(); for (int i = 0; i < tbls.length; i++) { String tbl = tbls[i]; if (obj.containsKey(tbl)) { throw new ConflictException(key + ": value 中 " + tbl + " 已经存在,不能重复!"); } - obj.put(tbl, isPost && JSONRequest.isTableArray(tbl) + obj.put(tbl, isPost && isTableArray(tbl) ? tbl.substring(0, tbl.length() - 2) + ":[]" : ""); } } } else { - throw new IllegalArgumentException(key + ": value 中 value 类型错误,只能是 String 或 JSONObject {} !"); + throw new IllegalArgumentException(key + ": value 中 value 类型错误,只能是 String 或 Map {} !"); } } @@ -2306,17 +2360,17 @@ protected JSONObject batchVerify(RequestMethod method, String tag, int version, } Map objAttrMap = new HashMap<>(); - objAttrMap.put(apijson.JSONObject.KEY_METHOD, keyMethod); + objAttrMap.put(KEY_METHOD, keyMethod); keyObjectAttributesMap.put(objKey, objAttrMap); Object objVal = objEntry.getValue(); - JSONObject objAttrJson = objVal instanceof JSONObject ? obj.getJSONObject(objKey) : null; + Map objAttrJson = objVal instanceof Map ? JSON.getMap(obj, objKey) : null; if (objAttrJson == null) { if (objVal instanceof String) { - objAttrMap.put(JSONRequest.KEY_TAG, "".equals(objVal) ? objKey : objVal); + objAttrMap.put(KEY_TAG, "".equals(objVal) ? objKey : objVal); } else { - throw new IllegalArgumentException(key + ": { " + objKey + ": value 中 value 类型错误,只能是 String 或 JSONObject {} !"); + throw new IllegalArgumentException(key + ": { " + objKey + ": value 中 value 类型错误,只能是 String 或 Map {} !"); } } else { @@ -2330,14 +2384,14 @@ protected JSONObject batchVerify(RequestMethod method, String tag, int version, } switch (objAttrKey) { - case apijson.JSONObject.KEY_DATASOURCE: - case apijson.JSONObject.KEY_SCHEMA: - case apijson.JSONObject.KEY_DATABASE: - case JSONRequest.KEY_VERSION: - case apijson.JSONObject.KEY_ROLE: + case KEY_DATASOURCE: + case KEY_SCHEMA: + case KEY_DATABASE: + case KEY_VERSION: + case KEY_ROLE: objAttrMap.put(objAttrKey, entry.getValue()); break; - case JSONRequest.KEY_TAG: + case KEY_TAG: hasTag = true; objAttrMap.put(objAttrKey, entry.getValue()); break; @@ -2347,7 +2401,7 @@ protected JSONObject batchVerify(RequestMethod method, String tag, int version, } if (hasTag == false) { - objAttrMap.put(JSONRequest.KEY_TAG, isPost && JSONRequest.isTableArray(objKey) + objAttrMap.put(KEY_TAG, isPost && isTableArray(objKey) ? objKey.substring(0, objKey.length() - 2) + ":[]" : objKey); } } @@ -2357,42 +2411,42 @@ protected JSONObject batchVerify(RequestMethod method, String tag, int version, // 1、非crud,对于没有显式声明操作方法的,直接用 URL(/get, /post 等) 对应的默认操作方法 // 2、crud, 没有声明就用 GET - // 3、兼容 sql@ JSONObject,设置 GET方法 + // 3、兼容 sql@ Map,设置 GET方法 // 将method 设置到每个object, op执行会解析 Object obj = request.get(key); - if (obj instanceof JSONObject) { + if (obj instanceof Map) { Map attrMap = keyObjectAttributesMap.get(key); if (attrMap == null) { // 数组会解析为对象进行校验,做一下兼容 - if (keyObjectAttributesMap.get(key + apijson.JSONObject.KEY_ARRAY) == null) { + if (keyObjectAttributesMap.get(key + KEY_ARRAY) == null) { if (method == RequestMethod.CRUD || key.endsWith("@")) { - ((JSONObject) obj).put(apijson.JSONObject.KEY_METHOD, GET); + ((Map) obj).put(KEY_METHOD, GET); Map objAttrMap = new HashMap<>(); - objAttrMap.put(apijson.JSONObject.KEY_METHOD, GET); + objAttrMap.put(KEY_METHOD, GET); keyObjectAttributesMap.put(key, objAttrMap); } else { - ((JSONObject) obj).put(apijson.JSONObject.KEY_METHOD, method); + ((Map) obj).put(KEY_METHOD, method); Map objAttrMap = new HashMap<>(); - objAttrMap.put(apijson.JSONObject.KEY_METHOD, method); + objAttrMap.put(KEY_METHOD, method); keyObjectAttributesMap.put(key, objAttrMap); } } else { - setRequestAttribute(key, true, apijson.JSONObject.KEY_METHOD, request); - setRequestAttribute(key, true, apijson.JSONObject.KEY_DATASOURCE, request); - setRequestAttribute(key, true, apijson.JSONObject.KEY_SCHEMA, request); - setRequestAttribute(key, true, apijson.JSONObject.KEY_DATABASE, request); - setRequestAttribute(key, true, apijson.JSONObject.VERSION, request); - setRequestAttribute(key, true, apijson.JSONObject.KEY_ROLE, request); + setRequestAttribute(key, true, KEY_METHOD, request); + setRequestAttribute(key, true, KEY_DATASOURCE, request); + setRequestAttribute(key, true, KEY_SCHEMA, request); + setRequestAttribute(key, true, KEY_DATABASE, request); + setRequestAttribute(key, true, KEY_VERSION, request); + setRequestAttribute(key, true, KEY_ROLE, request); } } else { - setRequestAttribute(key, false, apijson.JSONObject.KEY_METHOD, request); - setRequestAttribute(key, false, apijson.JSONObject.KEY_DATASOURCE, request); - setRequestAttribute(key, false, apijson.JSONObject.KEY_SCHEMA, request); - setRequestAttribute(key, false, apijson.JSONObject.KEY_DATABASE, request); - setRequestAttribute(key, false, apijson.JSONObject.VERSION, request); - setRequestAttribute(key, false, apijson.JSONObject.KEY_ROLE, request); + setRequestAttribute(key, false, KEY_METHOD, request); + setRequestAttribute(key, false, KEY_DATASOURCE, request); + setRequestAttribute(key, false, KEY_SCHEMA, request); + setRequestAttribute(key, false, KEY_DATABASE, request); + setRequestAttribute(key, false, KEY_VERSION, request); + setRequestAttribute(key, false, KEY_ROLE, request); } } @@ -2401,13 +2455,13 @@ protected JSONObject batchVerify(RequestMethod method, String tag, int version, continue; } - if (obj instanceof JSONObject || obj instanceof JSONArray) { + if (obj instanceof Map || obj instanceof List) { RequestMethod _method; - if (obj instanceof JSONObject) { - JSONObject tblObj = request.getJSONObject(key); - String mn = tblObj == null ? null : tblObj.getString(apijson.JSONObject.KEY_METHOD); + if (obj instanceof Map) { + Map tblObj = JSON.getMap(request, key); + String mn = tblObj == null ? null : getString(tblObj, KEY_METHOD); _method = mn == null ? null : RequestMethod.valueOf(mn); - String combine = _method == null ? null : tblObj.getString(KEY_COMBINE); + String combine = _method == null ? null : getString(tblObj, KEY_COMBINE); if (combine != null && RequestMethod.isPublicMethod(_method) == false) { throw new IllegalArgumentException(key + ":{} 里的 @combine:value 不合法!开放请求 GET、HEAD 才允许传 @combine:value !"); } @@ -2418,16 +2472,16 @@ protected JSONObject batchVerify(RequestMethod method, String tag, int version, if (method == RequestMethod.CRUD) { _method = GET; Map objAttrMap = new HashMap<>(); - objAttrMap.put(apijson.JSONObject.KEY_METHOD, GET); + objAttrMap.put(KEY_METHOD, GET); keyObjectAttributesMap.put(key, objAttrMap); } else { _method = method; Map objAttrMap = new HashMap<>(); - objAttrMap.put(apijson.JSONObject.KEY_METHOD, method); + objAttrMap.put(KEY_METHOD, method); keyObjectAttributesMap.put(key, objAttrMap); } } else { - _method = (RequestMethod) attrMap.get(apijson.JSONObject.KEY_METHOD); + _method = (RequestMethod) attrMap.get(KEY_METHOD); } } @@ -2443,18 +2497,18 @@ protected JSONObject batchVerify(RequestMethod method, String tag, int version, } if (tag != null && ! tag.contains(":")) { - JSONObject object = getRequestStructure(_method, tag, version); - JSONObject ret = objectVerify(_method, tag, version, name, request, maxUpdateCount, creator, object); + M object = getRequestStructure(_method, tag, version); + M ret = objectVerify(_method, tag, version, name, request, maxUpdateCount, creator, object); correctRequest.putAll(ret); break; } String _tag = buildTag(request, key, method, tag); - JSONObject object = getRequestStructure(_method, _tag, version); + M object = getRequestStructure(_method, _tag, version); if (method == RequestMethod.CRUD && StringUtil.isEmpty(tag, true)) { - JSONObject requestItem = new JSONObject(); + M requestItem = JSON.createJSONObject(); requestItem.put(key, obj); - JSONObject ret = objectVerify(_method, _tag, version, name, requestItem, maxUpdateCount, creator, object); + Map ret = objectVerify(_method, _tag, version, name, requestItem, maxUpdateCount, creator, object); correctRequest.put(key, ret.get(key)); } else { return objectVerify(_method, _tag, version, name, request, maxUpdateCount, creator, object); @@ -2487,10 +2541,10 @@ public static > E getEnum(final Class enumClass, final Stri } } - protected void setRequestAttribute(String key, boolean isArray, String attrKey, @NotNull JSONObject request) { - Map attrMap = keyObjectAttributesMap.get(isArray ? key + apijson.JSONObject.KEY_ARRAY : key); + protected void setRequestAttribute(String key, boolean isArray, String attrKey, @NotNull Map request) { + Map attrMap = keyObjectAttributesMap.get(isArray ? key + KEY_ARRAY : key); Object attrVal = attrMap == null ? null : attrMap.get(attrKey); - JSONObject obj = attrVal == null ? null : request.getJSONObject(key); + Map obj = attrVal == null ? null : JSON.get(request, key); if (obj != null && obj.get(attrKey) == null) { // 如果对象内部已经包含该属性,不覆盖 @@ -2498,10 +2552,10 @@ protected void setRequestAttribute(String key, boolean isArray, String attrKey, } } - protected String buildTag(JSONObject request, String key, RequestMethod method, String tag) { + protected String buildTag(Map request, String key, RequestMethod method, String tag) { if (method == RequestMethod.CRUD) { Map attrMap = keyObjectAttributesMap.get(key); - Object _tag = attrMap == null ? null : attrMap.get(JSONRequest.KEY_TAG); + Object _tag = attrMap == null ? null : attrMap.get(KEY_TAG); return _tag != null ? _tag.toString() : StringUtil.isEmpty(tag) ? key : tag; } else { if (StringUtil.isEmpty(tag, true)) { @@ -2512,12 +2566,12 @@ protected String buildTag(JSONObject request, String key, RequestMethod method, } - protected JSONObject objectVerify(RequestMethod method, String tag, int version, String name, @NotNull JSONObject request - , int maxUpdateCount, SQLCreator creator, JSONObject object) throws Exception { + protected M objectVerify(RequestMethod method, String tag, int version, String name, @NotNull M request + , int maxUpdateCount, SQLCreator creator, M object) throws Exception { // 获取指定的JSON结构 >>>>>>>>>>>>>> - JSONObject target = wrapRequest(method, tag, object, true); - // JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} - return getVerifier().verifyRequest(method, name, target, request, maxUpdateCount, getGlobalDatabase(), getGlobalSchema(), creator); + M target = wrapRequest(method, tag, object, true); + // Map clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} + return getVerifier().setParser(this).verifyRequest(method, name, target, request, maxUpdateCount, getGlobalDatabase(), getGlobalSchema()); } /*** @@ -2527,9 +2581,9 @@ protected JSONObject objectVerify(RequestMethod method, String tag, int version, * @return */ public RequestMethod getRealMethod(RequestMethod method, String key, Object value) { - if (method == CRUD && (value instanceof JSONObject || value instanceof JSONArray)) { + if (method == CRUD && (value instanceof Map || value instanceof List)) { Map attrMap = keyObjectAttributesMap.get(key); - Object _method = attrMap == null ? null : attrMap.get(apijson.JSONObject.KEY_METHOD); + Object _method = attrMap == null ? null : attrMap.get(KEY_METHOD); if (_method instanceof RequestMethod) { return (RequestMethod) _method; } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java index 19446e69e..d9e9eb016 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLConfig.java @@ -1,59 +1,31 @@ -/*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 com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.annotation.JSONField; +import apijson.*; +import apijson.orm.Join.On; +import apijson.orm.exception.NotExistException; +import apijson.orm.exception.UnsupportedDataTypeException; +import apijson.orm.model.*; import java.util.*; import java.util.Map.Entry; import java.util.regex.Pattern; -import apijson.JSON; -import apijson.JSONResponse; -import apijson.Log; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.SQL; -import apijson.StringUtil; -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 static apijson.JSONObject.*; -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.JSON.getBoolean; +import static apijson.JSON.getString; +import static apijson.JSONMap.*; +import static apijson.RequestMethod.*; +import static apijson.SQL.*; /**config sql for JSON Request * @author Lemon */ -public abstract class AbstractSQLConfig implements SQLConfig { +public abstract class AbstractSQLConfig, L extends List> + implements SQLConfig { private static final String TAG = "AbstractSQLConfig"; /** @@ -194,8 +166,10 @@ public abstract class AbstractSQLConfig implements SQLConfig implements SQLConfig(); // 保证顺序,避免配置冲突等意外情况 @@ -304,6 +279,7 @@ public abstract class AbstractSQLConfig implements SQLConfig implements SQLConfig parser; + private Parser parser; @Override - public Parser getParser() { + public Parser gainParser() { if (parser == null && objectParser != null) { parser = objectParser.getParser(); } return parser; } @Override - public AbstractSQLConfig setParser(Parser parser) { + public AbstractSQLConfig setParser(Parser parser) { this.parser = parser; return this; } - public AbstractSQLConfig putWarnIfNeed(String type, String warn) { + public AbstractSQLConfig putWarnIfNeed(String type, String warn) { if (Log.DEBUG && parser instanceof AbstractParser) { - ((AbstractParser) parser).putWarnIfNeed(type, warn); + ((AbstractParser) parser).putWarnIfNeed(type, warn); } return this; } - public AbstractSQLConfig putWarn(String type, String warn) { + public AbstractSQLConfig putWarn(String type, String warn) { if (Log.DEBUG && parser instanceof AbstractParser) { - ((AbstractParser) parser).putWarn(type, warn); + ((AbstractParser) parser).putWarn(type, warn); } return this; } - private ObjectParser objectParser; + private ObjectParser objectParser; @Override - public ObjectParser getObjectParser() { + public ObjectParser gainObjectParser() { return objectParser; } @Override - public AbstractSQLConfig setObjectParser(ObjectParser objectParser) { + public AbstractSQLConfig setObjectParser(ObjectParser objectParser) { this.objectParser = objectParser; return this; } @@ -860,7 +836,7 @@ public int getVersion() { return version; } @Override - public AbstractSQLConfig setVersion(int version) { + public AbstractSQLConfig setVersion(int version) { this.version = version; return this; } @@ -874,19 +850,19 @@ public String getTag() { return tag; } @Override - public AbstractSQLConfig setTag(String tag) { + public AbstractSQLConfig setTag(String tag) { this.tag = tag; return this; } // mysql8版本以上,子查询支持with as表达式 - private List withAsExprSqlList = null; + private List withAsExprSQLList = null; protected List withAsExprPreparedValueList = new ArrayList<>(); private int[] dbVersionNums = null; @Override - public int[] getDBVersionNums() { + public int[] gainDBVersionNums() { if (dbVersionNums == null || dbVersionNums.length <= 0) { - dbVersionNums = SQLConfig.super.getDBVersionNums(); + dbVersionNums = SQLConfig.super.gainDBVersionNums(); } return dbVersionNums; } @@ -914,7 +890,6 @@ public String getUserIdKey() { return KEY_USER_ID; } - private RequestMethod method; //操作方法 private boolean prepared = true; //预编译 private boolean main = true; @@ -939,11 +914,16 @@ public String getUserIdKey() { private String group; //分组方式的字符串数组,','分隔 private String havingCombine; //聚合函数的字符串数组,','分隔 private Map having; //聚合函数的字符串数组,','分隔 + private String sample; //取样方式的字符串数组,','分隔 + private String latest; //最近方式的字符串数组,','分隔 + private String partition; //分区方式的字符串数组,','分隔 + private String fill; //填充方式的字符串数组,','分隔 private String order; //排序方式的字符串数组,','分隔 + private Map keyMap; //字段名映射,支持 name_tag:(name,tag) 多字段 IN,year:left(date,4) 截取日期年份等 private List raw; //需要保留原始 SQL 的字段,','分隔 private List json; //需要转为 JSON 的字段,','分隔 - private Subquery from; //子查询临时表 + private Subquery from; //子查询临时表 private List column; //表内字段名(或函数名,仅查询操作可用)的字符串数组,','分隔 private List> values; //对应表内字段的值的字符串数组,','分隔 private List nulls; @@ -957,19 +937,19 @@ public String getUserIdKey() { private int count; //Table数量 private int page; //Table所在页码 private int position; //Table在[]中的位置 - private int query; //JSONRequest.query - private Boolean compat; //JSONRequest.compat query total + private int query; //apijson.JSONRequest.QUERY + private Boolean compat; //apijson.JSONMap.compat query total private int type; //ObjectParser.type private int cache; private boolean explain; - private List joinList; //连表 配置列表 + private List> joinList; //连表 配置列表 //array item >>>>>>>>>> private boolean test; //测试 private String procedure; - public AbstractSQLConfig setProcedure(String procedure) { + public AbstractSQLConfig setProcedure(String procedure) { this.procedure = procedure; return this; } @@ -999,7 +979,7 @@ public RequestMethod getMethod() { return method; } @Override - public AbstractSQLConfig setMethod(RequestMethod method) { + public AbstractSQLConfig setMethod(RequestMethod method) { this.method = method; return this; } @@ -1008,7 +988,7 @@ public boolean isPrepared() { return prepared && ! isMongoDB(); // MongoDB JDBC 还不支持预编译; } @Override - public AbstractSQLConfig setPrepared(boolean prepared) { + public AbstractSQLConfig setPrepared(boolean prepared) { this.prepared = prepared; return this; } @@ -1017,7 +997,7 @@ public boolean isMain() { return main; } @Override - public AbstractSQLConfig setMain(boolean main) { + public AbstractSQLConfig setMain(boolean main) { this.main = main; return this; } @@ -1028,7 +1008,7 @@ public Object getId() { return id; } @Override - public AbstractSQLConfig setId(Object id) { + public AbstractSQLConfig setId(Object id) { this.id = id; return this; } @@ -1038,7 +1018,7 @@ public Object getIdIn() { return idIn; } @Override - public AbstractSQLConfig setIdIn(Object idIn) { + public AbstractSQLConfig setIdIn(Object idIn) { this.idIn = idIn; return this; } @@ -1049,7 +1029,7 @@ public Object getUserId() { return userId; } @Override - public AbstractSQLConfig setUserId(Object userId) { + public AbstractSQLConfig setUserId(Object userId) { this.userId = userId; return this; } @@ -1059,7 +1039,7 @@ public Object getUserIdIn() { return userIdIn; } @Override - public AbstractSQLConfig setUserIdIn(Object userIdIn) { + public AbstractSQLConfig setUserIdIn(Object userIdIn) { this.userIdIn = userIdIn; return this; } @@ -1070,7 +1050,7 @@ public String getRole() { return role; } @Override - public AbstractSQLConfig setRole(String role) { + public AbstractSQLConfig setRole(String role) { this.role = role; return this; } @@ -1080,7 +1060,7 @@ public boolean isDistinct() { return distinct; } @Override - public AbstractSQLConfig setDistinct(boolean distinct) { + public AbstractSQLConfig setDistinct(boolean distinct) { this.distinct = distinct; return this; } @@ -1090,7 +1070,7 @@ public String getDatabase() { return database; } @Override - public AbstractSQLConfig setDatabase(String database) { + public AbstractSQLConfig setDatabase(String database) { this.database = database; return this; } @@ -1098,14 +1078,27 @@ public AbstractSQLConfig setDatabase(String database) { * @return db == null ? DEFAULT_DATABASE : db */ @NotNull - public String getSQLDatabase() { + public String gainSQLDatabase() { String db = getDatabase(); return db == null ? DEFAULT_DATABASE : db; // "" 表示已设置,不需要用全局默认的 StringUtil.isEmpty(db, false)) { } + @Override + public boolean isTSQL() { // 兼容 TSQL 语法 + return isOracle() || isSQLServer() || isDb2(); + } + @Override + public boolean isMSQL() { // 兼容 MySQL 语法,但不一定可以使用它的 JDBC/ODBC + return isMySQL() || isTiDB() || isMariaDB() || isSQLite() || isTDengine(); + } + @Override + public boolean isPSQL() { // 兼容 PostgreSQL 语法,但不一定可以使用它的 JDBC/ODBC + return isPostgreSQL() || isCockroachDB() || isOpenGauss() || isInfluxDB() || isTimescaleDB() || isQuestDB() || isDuckDB(); + } + @Override public boolean isMySQL() { - return isMySQL(getSQLDatabase()); + return isMySQL(gainSQLDatabase()); } public static boolean isMySQL(String db) { return DATABASE_MYSQL.equals(db); @@ -1113,7 +1106,7 @@ public static boolean isMySQL(String db) { @Override public boolean isPostgreSQL() { - return isPostgreSQL(getSQLDatabase()); + return isPostgreSQL(gainSQLDatabase()); } public static boolean isPostgreSQL(String db) { return DATABASE_POSTGRESQL.equals(db); @@ -1121,7 +1114,7 @@ public static boolean isPostgreSQL(String db) { @Override public boolean isSQLServer() { - return isSQLServer(getSQLDatabase()); + return isSQLServer(gainSQLDatabase()); } public static boolean isSQLServer(String db) { return DATABASE_SQLSERVER.equals(db); @@ -1129,7 +1122,7 @@ public static boolean isSQLServer(String db) { @Override public boolean isOracle() { - return isOracle(getSQLDatabase()); + return isOracle(gainSQLDatabase()); } public static boolean isOracle(String db) { return DATABASE_ORACLE.equals(db); @@ -1137,7 +1130,7 @@ public static boolean isOracle(String db) { @Override public boolean isDb2() { - return isDb2(getSQLDatabase()); + return isDb2(gainSQLDatabase()); } public static boolean isDb2(String db) { return DATABASE_DB2.equals(db); @@ -1145,7 +1138,7 @@ public static boolean isDb2(String db) { @Override public boolean isMariaDB() { - return isMariaDB(getSQLDatabase()); + return isMariaDB(gainSQLDatabase()); } public static boolean isMariaDB(String db) { return DATABASE_MARIADB.equals(db); @@ -1153,7 +1146,7 @@ public static boolean isMariaDB(String db) { @Override public boolean isTiDB() { - return isTiDB(getSQLDatabase()); + return isTiDB(gainSQLDatabase()); } public static boolean isTiDB(String db) { return DATABASE_TIDB.equals(db); @@ -1161,7 +1154,7 @@ public static boolean isTiDB(String db) { @Override public boolean isCockroachDB() { - return isCockroachDB(getSQLDatabase()); + return isCockroachDB(gainSQLDatabase()); } public static boolean isCockroachDB(String db) { return DATABASE_COCKROACHDB.equals(db); @@ -1169,7 +1162,7 @@ public static boolean isCockroachDB(String db) { @Override public boolean isDameng() { - return isDameng(getSQLDatabase()); + return isDameng(gainSQLDatabase()); } public static boolean isDameng(String db) { return DATABASE_DAMENG.equals(db); @@ -1177,7 +1170,7 @@ public static boolean isDameng(String db) { @Override public boolean isKingBase() { - return isKingBase(getSQLDatabase()); + return isKingBase(gainSQLDatabase()); } public static boolean isKingBase(String db) { return DATABASE_KINGBASE.equals(db); @@ -1185,7 +1178,7 @@ public static boolean isKingBase(String db) { @Override public boolean isElasticsearch() { - return isElasticsearch(getSQLDatabase()); + return isElasticsearch(gainSQLDatabase()); } public static boolean isElasticsearch(String db) { return DATABASE_ELASTICSEARCH.equals(db); @@ -1193,7 +1186,7 @@ public static boolean isElasticsearch(String db) { @Override public boolean isManticore() { - return isManticore(getSQLDatabase()); + return isManticore(gainSQLDatabase()); } public static boolean isManticore(String db) { return DATABASE_MANTICORE.equals(db); @@ -1201,7 +1194,7 @@ public static boolean isManticore(String db) { @Override public boolean isClickHouse() { - return isClickHouse(getSQLDatabase()); + return isClickHouse(gainSQLDatabase()); } public static boolean isClickHouse(String db) { return DATABASE_CLICKHOUSE.equals(db); @@ -1209,7 +1202,7 @@ public static boolean isClickHouse(String db) { @Override public boolean isHive() { - return isHive(getSQLDatabase()); + return isHive(gainSQLDatabase()); } public static boolean isHive(String db) { return DATABASE_HIVE.equals(db); @@ -1217,7 +1210,7 @@ public static boolean isHive(String db) { @Override public boolean isPresto() { - return isPresto(getSQLDatabase()); + return isPresto(gainSQLDatabase()); } public static boolean isPresto(String db) { return DATABASE_PRESTO.equals(db); @@ -1225,7 +1218,7 @@ public static boolean isPresto(String db) { @Override public boolean isTrino() { - return isTrino(getSQLDatabase()); + return isTrino(gainSQLDatabase()); } public static boolean isTrino(String db) { return DATABASE_TRINO.equals(db); @@ -1233,15 +1226,23 @@ public static boolean isTrino(String db) { @Override public boolean isSnowflake() { - return isSnowflake(getSQLDatabase()); + return isSnowflake(gainSQLDatabase()); } 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(getSQLDatabase()); + return isDatabricks(gainSQLDatabase()); } public static boolean isDatabricks(String db) { return DATABASE_DATABRICKS.equals(db); @@ -1249,7 +1250,7 @@ public static boolean isDatabricks(String db) { @Override public boolean isCassandra() { - return isCassandra(getSQLDatabase()); + return isCassandra(gainSQLDatabase()); } public static boolean isCassandra(String db) { return DATABASE_CASSANDRA.equals(db); @@ -1257,7 +1258,7 @@ public static boolean isCassandra(String db) { @Override public boolean isMilvus() { - return isMilvus(getSQLDatabase()); + return isMilvus(gainSQLDatabase()); } public static boolean isMilvus(String db) { return DATABASE_MILVUS.equals(db); @@ -1265,7 +1266,7 @@ public static boolean isMilvus(String db) { @Override public boolean isInfluxDB() { - return isInfluxDB(getSQLDatabase()); + return isInfluxDB(gainSQLDatabase()); } public static boolean isInfluxDB(String db) { return DATABASE_INFLUXDB.equals(db); @@ -1273,7 +1274,7 @@ public static boolean isInfluxDB(String db) { @Override public boolean isTDengine() { - return isTDengine(getSQLDatabase()); + return isTDengine(gainSQLDatabase()); } public static boolean isTDengine(String db) { return DATABASE_TDENGINE.equals(db); @@ -1281,12 +1282,20 @@ public static boolean isTDengine(String db) { @Override public boolean isTimescaleDB() { - return isTimescaleDB(getSQLDatabase()); + return isTimescaleDB(gainSQLDatabase()); } public static boolean isTimescaleDB(String db) { return DATABASE_TIMESCALEDB.equals(db); } + @Override + public boolean isQuestDB() { + return isQuestDB(gainSQLDatabase()); + } + public static boolean isQuestDB(String db) { + return DATABASE_QUESTDB.equals(db); + } + public boolean isIoTDB() { return isIoTDB(getDatabase()); @@ -1298,7 +1307,7 @@ public static boolean isIoTDB(String db) { @Override public boolean isRedis() { - return isRedis(getSQLDatabase()); + return isRedis(gainSQLDatabase()); } public static boolean isRedis(String db) { return DATABASE_REDIS.equals(db); @@ -1306,7 +1315,7 @@ public static boolean isRedis(String db) { @Override public boolean isMongoDB() { - return isMongoDB(getSQLDatabase()); + return isMongoDB(gainSQLDatabase()); } public static boolean isMongoDB(String db) { return DATABASE_MONGODB.equals(db); @@ -1314,7 +1323,7 @@ public static boolean isMongoDB(String db) { @Override public boolean isKafka() { - return isKafka(getSQLDatabase()); + return isKafka(gainSQLDatabase()); } public static boolean isKafka(String db) { return DATABASE_KAFKA.equals(db); @@ -1322,7 +1331,7 @@ public static boolean isKafka(String db) { @Override public boolean isMQ() { - return isMQ(getSQLDatabase()); + return isMQ(gainSQLDatabase()); } public static boolean isMQ(String db) { return DATABASE_MQ.equals(db) || isKafka(db); @@ -1330,7 +1339,7 @@ public static boolean isMQ(String db) { @Override public boolean isSQLite() { - return isSQLite(getSQLDatabase()); + return isSQLite(gainSQLDatabase()); } public static boolean isSQLite(String db) { return DATABASE_SQLITE.equals(db); @@ -1338,7 +1347,7 @@ public static boolean isSQLite(String db) { @Override public boolean isDuckDB() { - return isDuckDB(getSQLDatabase()); + return isDuckDB(gainSQLDatabase()); } public static boolean isDuckDB(String db) { return DATABASE_DUCKDB.equals(db); @@ -1346,7 +1355,7 @@ public static boolean isDuckDB(String db) { @Override public boolean isSurrealDB() { - return isSurrealDB(getSQLDatabase()); + return isSurrealDB(gainSQLDatabase()); } public static boolean isSurrealDB(String db) { return DATABASE_SURREALDB.equals(db); @@ -1354,18 +1363,26 @@ public static boolean isSurrealDB(String db) { @Override public boolean isOpenGauss() { - return isOpenGauss(getSQLDatabase()); + return isOpenGauss(gainSQLDatabase()); } public static boolean isOpenGauss(String db) { return DATABASE_OPENGAUSS.equals(db); } + @Override + public boolean isDoris() { + return isDoris(gainSQLDatabase()); + } + public static boolean isDoris(String db) { + return DATABASE_DORIS.equals(db); + } + @Override public String getQuote() { // MongoDB 同时支持 `tbl` 反引号 和 "col" 双引号 if(isElasticsearch() || isManticore() || isIoTDB() || isSurrealDB()) { return ""; } - return isMySQL() || isMariaDB() || isTiDB() || isClickHouse() || isTDengine() || isMilvus() ? "`" : "\""; + return isMySQL() || isMariaDB() || isTiDB() || isClickHouse() || isTDengine() || isMilvus() || isDoris() ? "`" : "\""; } public String quote(String s) { @@ -1385,14 +1402,14 @@ public String getNamespace() { } @Override - public AbstractSQLConfig setNamespace(String namespace) { + public AbstractSQLConfig setNamespace(String namespace) { this.namespace = namespace; return this; } @Override - public String getSQLCatalog() { + public String gainSQLCatalog() { String catalog = getCatalog(); // 前端传参 @catalog 优先 return catalog == null ? DEFAULT_CATALOG : catalog; // 最后代码默认兜底配置 } @@ -1403,14 +1420,14 @@ public String getCatalog() { } @Override - public AbstractSQLConfig setCatalog(String catalog) { + public AbstractSQLConfig setCatalog(String catalog) { this.catalog = catalog; return this; } @NotNull @Override - public String getSQLSchema() { + public String gainSQLSchema() { String table = getTable(); // FIXME 全部默认填充判断是 系统表 则不填充 // 强制,避免因为全局默认的 @schema 自动填充进来,导致这几个类的 schema 为 sys 等其它值 if (Table.TAG.equals(table) || Column.TAG.equals(table)) { @@ -1440,7 +1457,7 @@ public String getSchema() { } @Override - public AbstractSQLConfig setSchema(String schema) { + public AbstractSQLConfig setSchema(String schema) { if (schema != null) { AbstractFunctionParser.verifySchema(schema, getTable()); } @@ -1453,14 +1470,14 @@ public String getDatasource() { return datasource; } @Override - public AbstractSQLConfig setDatasource(String datasource) { + public AbstractSQLConfig setDatasource(String datasource) { this.datasource = datasource; return this; } /**请求传进来的Table名 * @return - * @see {@link #getSQLTable()} + * @see {@link #gainSQLTable()} */ @Override public String getTable() { @@ -1470,9 +1487,8 @@ public String getTable() { * 通过 {@link #TABLE_KEY_MAP} 映射 * @return */ - @JSONField(serialize = false) @Override - public String getSQLTable() { + public String gainSQLTable() { // 如果要强制小写,则可在子类重写这个方法再 toLowerCase // return DATABASE_POSTGRESQL.equals(getDatabase()) ? t.toLowerCase() : t; String ot = getTable(); @@ -1481,28 +1497,27 @@ public String getSQLTable() { } - @JSONField(serialize = false) @Override - public String getTablePath() { + public String gainTablePath() { String q = getQuote(); String ns = isSurrealDB() ? getSQLNamespace() : null; - String cl = isPostgreSQL() || isCockroachDB() || isOpenGauss() || isDuckDB() ? getSQLCatalog() : null; - String sch = getSQLSchema(); - String sqlTable = getSQLTable(); + String cl = isPSQL() ? gainSQLCatalog() : null; + String sch = gainSQLSchema(); + String sqlTable = gainSQLTable(); return (StringUtil.isEmpty(ns, true) ? "" : q + ns + q + ".") + (StringUtil.isEmpty(cl, true) ? "" : q + cl + q + ".") + (StringUtil.isEmpty(sch, true) ? "" : q + sch + q + ".") - + q + sqlTable + q + (isKeyPrefix() ? getAs() + q + getSQLAlias() + q : ""); + + q + sqlTable + q + (isKeyPrefix() ? gainAs() + q + gainSQLAlias() + q : ""); } @Override - public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 + public AbstractSQLConfig setTable(String table) { //Table已经在Parser中校验,所以这里不用防SQL注入 this.table = table; return this; } - public String getAs() { + public String gainAs() { return isOracle() || isManticore() ? " " : " AS "; } @@ -1511,12 +1526,12 @@ public String getAlias() { return alias; } @Override - public AbstractSQLConfig setAlias(String alias) { + public AbstractSQLConfig setAlias(String alias) { this.alias = alias; return this; } - public String getSQLAliasWithQuote() { - String a = getSQLAlias(); + public String gainSQLAliasWithQuote() { + String a = gainSQLAlias(); String q = getQuote(); // getTable 不能小写,因为Verifier用大小写敏感的名称判断权限 // 如果要强制小写,则可在子类重写这个方法再 toLowerCase @@ -1528,45 +1543,62 @@ public String getSQLAliasWithQuote() { public String getGroup() { return group; } - public AbstractSQLConfig setGroup(String... keys) { - return setGroup(StringUtil.getString(keys)); + public AbstractSQLConfig setGroup(String... keys) { + return setGroup(StringUtil.get(keys)); } @Override - public AbstractSQLConfig setGroup(String group) { + public AbstractSQLConfig setGroup(String group) { this.group = group; return this; } - @JSONField(serialize = false) - public String getGroupString(boolean hasPrefix) { + + public String gainGroupString(boolean hasPrefix) { //加上子表的 group String joinGroup = ""; if (joinList != null) { boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { + for (Join join : joinList) { + if (join.isAppJoin()) { continue; } - SQLConfig ocfg = j.getOuterConfig(); - SQLConfig cfg = (ocfg != null && ocfg.getGroup() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getGroup() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); //if (StringUtil.isEmpty(cfg.getAlias(), true)) { // cfg.setAlias(cfg.getTable()); //} - String c = ((AbstractSQLConfig) cfg).getGroupString(false); + String c = ((AbstractSQLConfig) cfg).gainGroupString(false); - if (StringUtil.isEmpty(c, true) == false) { + if (StringUtil.isNotEmpty(c, true)) { joinGroup += (first ? "" : ", ") + c; 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; + // } + //} } } - group = StringUtil.getTrimedString(group); + group = StringUtil.trim(group); String[] keys = StringUtil.split(group); if (keys == null || keys.length <= 0) { return StringUtil.isEmpty(joinGroup, true) ? "" : (hasPrefix ? " GROUP BY " : "") + joinGroup; @@ -1574,16 +1606,16 @@ public String getGroupString(boolean hasPrefix) { for (int i = 0; i < keys.length; i++) { if (isPrepared()) { - // 不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! + // 不能通过 ? 来代替,因为SQLExecutor statement.setString后 GROUP BY 'userId' 有单引号,只能返回一条数据,必须去掉单引号才行! if (StringUtil.isName(keys[i]) == false) { throw new IllegalArgumentException("@group:value 中 value里面用 , 分割的每一项都必须是1个单词!并且不要有空格!"); } } - keys[i] = getKey(keys[i]); + keys[i] = gainKey(keys[i]); } - return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinGroup, ", "); + return (hasPrefix ? " GROUP BY " : "") + StringUtil.concat(StringUtil.get(keys), joinGroup, ", "); } @Override @@ -1591,7 +1623,7 @@ public String getHavingCombine() { return havingCombine; } @Override - public AbstractSQLConfig setHavingCombine(String havingCombine) { + public AbstractSQLConfig setHavingCombine(String havingCombine) { this.havingCombine = havingCombine; return this; } @@ -1601,40 +1633,39 @@ public Map getHaving() { return having; } @Override - public AbstractSQLConfig setHaving(Map having) { + public AbstractSQLConfig setHaving(Map having) { this.having = having; return this; } - public AbstractSQLConfig setHaving(String... conditions) { - return setHaving(StringUtil.getString(conditions)); + public AbstractSQLConfig setHaving(String... conditions) { + return setHaving(StringUtil.get(conditions)); } /**TODO @having 改为默认 | 或连接,且支持 @having: { "key1>": 1, "key{}": "length(key2)>0", "@combine": "key1,key2" } * @return HAVING conditoin0 AND condition1 OR condition2 ... * @throws Exception */ - @JSONField(serialize = false) - public String getHavingString(boolean hasPrefix) throws Exception { + public String gainHavingString(boolean hasPrefix) throws Exception { //加上子表的 having String joinHaving = ""; if (joinList != null) { boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { + for (Join join : joinList) { + if (join.isAppJoin()) { continue; } - SQLConfig ocfg = j.getOuterConfig(); - SQLConfig cfg = (ocfg != null && ocfg.getHaving() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getHaving() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); //if (StringUtil.isEmpty(cfg.getAlias(), true)) { // cfg.setAlias(cfg.getTable()); //} - String c = ((AbstractSQLConfig) cfg).getHavingString(false); + String c = ((AbstractSQLConfig) cfg).gainHavingString(false); - if (StringUtil.isEmpty(c, true) == false) { + if (StringUtil.isNotEmpty(c, true)) { joinHaving += (first ? "" : ", ") + c; first = false; } @@ -1665,11 +1696,11 @@ public String getHavingString(boolean hasPrefix) throws Exception { return (hasPrefix ? " HAVING " : "") + StringUtil.concat(havingString, joinHaving, AND); } - protected String getHavingItem(String quote, String table, String alias + protected String gainHavingItem(String quote, String table, String alias , String key, String expression, boolean containRaw) throws Exception { //fun(arg0,arg1,...) if (containRaw) { - String rawSQL = getRawSQL(KEY_HAVING, expression); + String rawSQL = gainRawSQL(KEY_HAVING, expression); if (rawSQL != null) { return rawSQL; } @@ -1715,40 +1746,313 @@ else if (SQL_FUNCTION_MAP.containsKey(method) == false) { return method + parseSQLExpression(KEY_HAVING, expression.substring(start), containRaw, false, null); } + @Override + public String getSample() { + return sample; + } + public AbstractSQLConfig setSample(String... conditions) { + return setSample(StringUtil.get(conditions)); + } + @Override + public AbstractSQLConfig setSample(String sample) { + this.sample = sample; + return this; + } + public String gainSampleString(boolean hasPrefix) { + //加上子表的 sample + String joinSample = ""; + if (joinList != null) { + boolean first = true; + for (Join join : joinList) { + if (join.isAppJoin()) { + continue; + } + + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getSample() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); + + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + // if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + // } + String c = ((AbstractSQLConfig) cfg).gainSampleString(false); + + if (StringUtil.isNotEmpty(c, true)) { + joinSample += (first ? "" : ", ") + c; + first = false; + } + } + } + } + + String sample = StringUtil.trim(getSample()); + + String[] keys = StringUtil.split(sample); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinSample, true) ? "" : (hasPrefix ? " SAMPLE BY " : "") + joinSample; + } + + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + + String origin = item; + + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin)) {} + else if (StringUtil.isCombineOfNumOrAlpha(origin)) { + continue; + } + else { + throw new IllegalArgumentException("预编译模式下 @sample:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 column 且其中 column 必须是 数字或英语字母组合!并且不要有多余的空格!"); + } + } + + keys[i] = gainKey(origin); + } + + return (hasPrefix ? " SAMPLE BY " : "") + StringUtil.concat(StringUtil.get(keys), joinSample, ", "); + } + + @Override + public String getLatest() { + return latest; + } + public AbstractSQLConfig setLatest(String... conditions) { + return setLatest(StringUtil.get(conditions)); + } + @Override + public AbstractSQLConfig setLatest(String latest) { + this.latest = latest; + return this; + } + public String gainLatestString(boolean hasPrefix) { + //加上子表的 latest + String joinLatest = ""; + if (joinList != null) { + boolean first = true; + for (Join join : joinList) { + if (join.isAppJoin()) { + continue; + } + + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getLatest() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); + + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + // if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + // } + String c = ((AbstractSQLConfig) cfg).gainLatestString(false); + + if (StringUtil.isNotEmpty(c, true)) { + joinLatest += (first ? "" : ", ") + c; + first = false; + } + } + } + } + + String latest = StringUtil.trim(getLatest()); + + String[] keys = StringUtil.split(latest); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinLatest, true) ? "" : (hasPrefix ? " LATEST ON " : "") + joinLatest; + } + + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + String origin = item; + + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("预编译模式下 @latest:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 column 且其中 column 必须是 英语单词!并且不要有多余的空格!"); + } + } + + keys[i] = gainKey(origin); + } + + return (hasPrefix ? " LATEST ON " : "") + StringUtil.concat(StringUtil.get(keys), joinLatest, ", "); + } + + @Override + public String getPartition() { + return partition; + } + public AbstractSQLConfig setPartition(String... conditions) { + return setPartition(StringUtil.get(conditions)); + } + @Override + public AbstractSQLConfig setPartition(String partition) { + this.partition = partition; + return this; + } + public String gainPartitionString(boolean hasPrefix) { + //加上子表的 partition + String joinPartition = ""; + if (joinList != null) { + boolean first = true; + for (Join join : joinList) { + if (join.isAppJoin()) { + continue; + } + + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getPartition() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); + + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + // if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + // } + String c = ((AbstractSQLConfig) cfg).gainPartitionString(false); + + if (StringUtil.isNotEmpty(c, true)) { + joinPartition += (first ? "" : ", ") + c; + first = false; + } + } + } + } + + String partition = StringUtil.trim(getPartition()); + + String[] keys = StringUtil.split(partition); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinPartition, true) ? "" : (hasPrefix ? " PARTITION BY " : "") + joinPartition; + } + + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + String origin = item; + + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin) == false) { + throw new IllegalArgumentException("预编译模式下 @partition:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 column 且其中 column 必须是 英语单词!并且不要有多余的空格!"); + } + } + + keys[i] = gainKey(origin); + } + + return (hasPrefix ? " PARTITION BY " : "") + StringUtil.concat(StringUtil.get(keys), joinPartition, ", "); + } + + @Override + public String getFill() { + return fill; + } + public AbstractSQLConfig setFill(String... conditions) { + return setFill(StringUtil.get(conditions)); + } + @Override + public AbstractSQLConfig setFill(String fill) { + this.fill = fill; + return this; + } + public String gainFillString(boolean hasPrefix) { + //加上子表的 fill + String joinFill = ""; + if (joinList != null) { + boolean first = true; + for (Join join : joinList) { + if (join.isAppJoin()) { + continue; + } + + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getFill() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); + + if (cfg != null) { + cfg.setMain(false).setKeyPrefix(true); + // if (StringUtil.isEmpty(cfg.getAlias(), true)) { + // cfg.setAlias(cfg.getTable()); + // } + String c = ((AbstractSQLConfig) cfg).gainFillString(false); + + if (StringUtil.isNotEmpty(c, true)) { + joinFill += (first ? "" : ", ") + c; + first = false; + } + } + } + } + + String fill = StringUtil.trim(getFill()); + + String[] keys = StringUtil.split(fill); + if (keys == null || keys.length <= 0) { + return StringUtil.isEmpty(joinFill, true) ? "" : (hasPrefix ? " FILL(" : "") + joinFill + ")"; + } + + for (int i = 0; i < keys.length; i++) { + String item = keys[i]; + if ("NULL".equals(item) || "LINEAR".equals(item) || "PREV".equals(item) || "PREVIOUS".equals(item)) { + continue; + } + + String origin = item; + + if (isPrepared()) { //不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! + //这里既不对origin trim,也不对 ASC/DESC ignoreCase,希望前端严格传没有任何空格的字符串过来,减少传输数据量,节约服务器性能 + if (StringUtil.isName(origin)) {} + else if (StringUtil.isCombineOfNumOrAlpha(origin)) { + continue; + } + else { + throw new IllegalArgumentException("预编译模式下 @fill:value 中 " + item + " 不合法! value 里面用 , 分割的" + + "每一项必须是 column 且其中 column 必须是 数字或英语字母组合!并且不要有多余的空格!"); + } + } + + keys[i] = gainKey(origin); + } + + return (hasPrefix ? " FILL(" : "") + StringUtil.concat(StringUtil.get(keys), joinFill, ", ") + ")"; + } + @Override public String getOrder() { return order; } - public AbstractSQLConfig setOrder(String... conditions) { - return setOrder(StringUtil.getString(conditions)); + public AbstractSQLConfig setOrder(String... conditions) { + return setOrder(StringUtil.get(conditions)); } @Override - public AbstractSQLConfig setOrder(String order) { + public AbstractSQLConfig setOrder(String order) { this.order = order; return this; } - @JSONField(serialize = false) - public String getOrderString(boolean hasPrefix) { + public String gainOrderString(boolean hasPrefix) { //加上子表的 order String joinOrder = ""; + String joinOuterOrder = ""; if (joinList != null) { boolean first = true; - for (Join j : joinList) { - if (j.isAppJoin()) { + for (Join join : joinList) { + if (join.isAppJoin()) { continue; } - SQLConfig ocfg = j.getOuterConfig(); - SQLConfig cfg = (ocfg != null && ocfg.getOrder() != null) || j.isLeftOrRightJoin() ? ocfg : j.getJoinConfig(); + SQLConfig ocfg = join.getOnConfig(); + SQLConfig cfg = (ocfg != null && ocfg.getOrder() != null) || join.isLeftOrRightJoin() ? ocfg : join.getJoinConfig(); if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); //if (StringUtil.isEmpty(cfg.getAlias(), true)) { // cfg.setAlias(cfg.getTable()); //} - String c = ((AbstractSQLConfig) cfg).getOrderString(false); + String c = ((AbstractSQLConfig) cfg).gainOrderString(false); - if (StringUtil.isEmpty(c, true) == false) { + if (StringUtil.isNotEmpty(c, true)) { joinOrder += (first ? "" : ", ") + c; first = false; } @@ -1757,7 +2061,7 @@ public String getOrderString(boolean hasPrefix) { } - String order = StringUtil.getTrimedString(getOrder()); + String order = StringUtil.trim(getOrder()); // SELECT * FROM sys.Moment ORDER BY userId ASC, rand(); 前面的 userId ASC 和后面的 rand() 都有效 // if ("rand()".equals(order)) { // return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(order, joinOrder, ", "); @@ -1827,10 +2131,10 @@ public String getOrderString(boolean hasPrefix) { } } - keys[i] = getKey(origin) + sort; + keys[i] = gainKey(origin) + sort; } - return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.getString(keys), joinOrder, ", "); + return (hasPrefix ? " ORDER BY " : "") + StringUtil.concat(StringUtil.get(keys), joinOrder, ", "); } @Override @@ -1838,7 +2142,7 @@ public Map getKeyMap() { return keyMap; } @Override - public AbstractSQLConfig setKeyMap(Map keyMap) { + public AbstractSQLConfig setKeyMap(Map keyMap) { this.keyMap = keyMap; return this; } @@ -1848,7 +2152,7 @@ public List getRaw() { return raw; } @Override - public AbstractSQLConfig setRaw(List raw) { + public AbstractSQLConfig setRaw(List raw) { this.raw = raw; return this; } @@ -1860,8 +2164,8 @@ public AbstractSQLConfig setRaw(List raw) { * @throws Exception */ @Override - public String getRawSQL(String key, Object value) throws Exception { - return getRawSQL(key, value, ! ALLOW_MISSING_KEY_4_COMBINE); + public String gainRawSQL(String key, Object value) throws Exception { + return gainRawSQL(key, value, ! ALLOW_MISSING_KEY_4_COMBINE); } /**获取原始 SQL 片段 * @param key @@ -1871,7 +2175,7 @@ public String getRawSQL(String key, Object value) throws Exception { * @throws Exception */ @Override - public String getRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception { + public String gainRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception { if (value == null) { return null; } @@ -1891,7 +2195,7 @@ public String getRawSQL(String key, Object value, boolean throwWhenMissing) thro + "对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); } - putWarnIfNeed(JSONRequest.KEY_RAW, "@raw:value 的 value 中 " + putWarnIfNeed(JSONMap.KEY_RAW, "@raw:value 的 value 中 " + key + " 不合法!对应的 " + key + ":value 中 value 值 " + value + " 未在后端 RAW_MAP 中配置 !"); } else if (rawSQL.isEmpty()) { @@ -1908,18 +2212,18 @@ public List getJson() { return json; } @Override - public AbstractSQLConfig setJson(List json) { + public AbstractSQLConfig setJson(List json) { this.json = json; return this; } @Override - public Subquery getFrom() { + public Subquery getFrom() { return from; } @Override - public AbstractSQLConfig setFrom(Subquery from) { + public AbstractSQLConfig setFrom(Subquery from) { this.from = from; return this; } @@ -1929,18 +2233,16 @@ public List getColumn() { return column; } @Override - public AbstractSQLConfig setColumn(List column) { + public AbstractSQLConfig setColumn(List column) { this.column = column; return this; } - @JSONField(serialize = false) - public String getColumnString() throws Exception { - return getColumnString(false); + public String gainColumnString() throws Exception { + return gainColumnString(false); } - @JSONField(serialize = false) - public String getColumnString(boolean inSQLJoin) throws Exception { + public String gainColumnString(boolean inSQLJoin) throws Exception { List column = getColumn(); - String as = getAs(); + String as = gainAs(); String q = getQuote(); switch (getMethod()) { @@ -1953,7 +2255,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { for (String c : column) { if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 + if ("".equals(RAW_MAP.get(c)) || RAW_MAP.containsValue(c)) { // newSQLConfig 提前处理好的 //排除@raw中的值,以避免使用date_format(date,'%Y-%m-%d %H:%i:%s') 时,冒号的解析出错 //column.remove(c); continue; @@ -2019,7 +2321,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } - return "count(" + (onlyOne ? getKey(c0) : "*") + ")" + as + q + JSONResponse.KEY_COUNT + q; + return "count(" + (onlyOne ? gainKey(c0) : "*") + ")" + as + q + JSONResponse.KEY_COUNT + q; // return SQL.count(onlyOne && StringUtil.isName(column.get(0)) ? getKey(column.get(0)) : "*"); case POST: if (column == null || column.isEmpty()) { @@ -2033,7 +2335,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { // 不能通过 ? 来代替,SELECT 'id','name' 返回的就是 id:"id", name:"name",而不是数据库里的值! throw new IllegalArgumentException("POST请求: 每一个 key:value 中的key都必须是1个单词!"); } - s += ((pfirst ? "" : ",") + getKey(c)); + s += ((pfirst ? "" : ",") + gainKey(c)); pfirst = false; } @@ -2044,12 +2346,12 @@ public String getColumnString(boolean inSQLJoin) throws Exception { String joinColumn = ""; if (joinList != null) { boolean first = true; - for (Join join : joinList) { + for (Join join : joinList) { if (join.isAppJoin()) { continue; } - SQLConfig ocfg = join.getOuterConfig(); + SQLConfig ocfg = join.getOnConfig(); boolean isEmpty = ocfg == null || ocfg.getColumn() == null; boolean isLeftOrRightJoin = join.isLeftOrRightJoin(); @@ -2057,18 +2359,18 @@ public String getColumnString(boolean inSQLJoin) throws Exception { // 改为 SELECT ViceTable.* 解决 SELECT sum(ViceTable.id) // LEFT/RIGHT JOIN (SELECT sum(id) FROM ViceTable...) AS ViceTable // 不仅导致 SQL 函数重复计算,还有时导致 SQL 报错或对应字段未返回 - joinColumn += (first ? "" : ", ") + q + SQLConfig.getSQLAlias(join.getTable(), join.getAlias()) + q + ".*"; + joinColumn += (first ? "" : ", ") + q + SQLConfig.gainSQLAlias(join.getTable(), join.getAlias()) + q + ".*"; first = false; } else { - SQLConfig cfg = isLeftOrRightJoin == false && isEmpty ? join.getJoinConfig() : ocfg; + SQLConfig cfg = isLeftOrRightJoin == false && isEmpty ? join.getJoinConfig() : ocfg; if (cfg != null) { cfg.setMain(false).setKeyPrefix(true); //if (StringUtil.isEmpty(cfg.getAlias(), true)) { // cfg.setAlias(cfg.getTable()); //} - String c = ((AbstractSQLConfig) cfg).getColumnString(true); - if (StringUtil.isEmpty(c, true) == false) { + String c = ((AbstractSQLConfig) cfg).gainColumnString(true); + if (StringUtil.isNotEmpty(c, true)) { joinColumn += (first ? "" : ", ") + c; first = false; } @@ -2079,14 +2381,14 @@ public String getColumnString(boolean inSQLJoin) throws Exception { } } - String tableAlias = q + getSQLAlias() + q; + String tableAlias = q + gainSQLAlias() + q; // String c = StringUtil.getString(column); //id,name;json_length(contactIdList):contactCount;... String[] keys = column == null ? null : column.toArray(new String[]{}); //StringUtil.split(c, ";"); if (keys == null || keys.length <= 0) { boolean noColumn = column != null && inSQLJoin; - String mc = isKeyPrefix() == false ? (noColumn ? "" : "*") : (noColumn ? "" : tableAlias + ".*"); + String mc = isKeyPrefix() ? (noColumn ? "" : tableAlias + ".*") : (noColumn ? "" : "*"); return StringUtil.concat(mc, joinColumn, ", ", true); } @@ -2099,7 +2401,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { String expression = keys[i]; //fun(arg0,arg1,...) if (containRaw) { // 由于 HashMap 对 key 做了 hash 处理,所以 get 比 containsValue 更快 - if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 + if ("".equals(RAW_MAP.get(expression)) || RAW_MAP.containsValue(expression)) { // newSQLConfig 提前处理好的 continue; } @@ -2108,8 +2410,8 @@ public String getColumnString(boolean inSQLJoin) throws Exception { String alias = expression.substring(index+1); boolean hasAlias = StringUtil.isName(alias); String pre = index > 0 && hasAlias ? expression.substring(0, index) : expression; - if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 - keys[i] = pre + (hasAlias ? getAs() + q + alias + q : ""); + if (RAW_MAP.containsValue(pre) || "".equals(RAW_MAP.get(pre))) { // newSQLConfig 提前处理好的 + keys[i] = pre + (hasAlias ? gainAs() + q + alias + q : ""); continue; } } @@ -2122,7 +2424,7 @@ public String getColumnString(boolean inSQLJoin) throws Exception { , "@column:\"column0,column1:alias1;function0(arg0,arg1,...);function1(...):alias2...\""); } - String c = StringUtil.getString(keys); + String c = StringUtil.get(keys); c = c + (StringUtil.isEmpty(joinColumn, true) ? "" : ", " + joinColumn);//不能在这里改,后续还要用到: return isMain() && isDistinct() ? PREFIX_DISTINCT + c : c; default: @@ -2167,7 +2469,7 @@ public String parseSQLExpression(String key, String expression, boolean containR if (start < 0) { //没有函数 ,可能是字段,也可能是 DISTINCT xx String[] cks = parseArgsSplitWithComma(expression, true, containRaw, allowAlias); - expression = StringUtil.getString(cks); + expression = StringUtil.get(cks); } else { // FIXME 用括号断开? 如果少的话,用关键词加括号断开,例如 )OVER( 和 )AGAINST( // 窗口函数 rank() OVER (PARTITION BY id ORDER BY userId ASC) // 全文索引 math(name,tag) AGAINST ('a b +c -d' IN NATURALE LANGUAGE MODE) // IN BOOLEAN MODE @@ -2223,7 +2525,7 @@ public String parseSQLExpression(String key, String expression, boolean containR // 解析函数内的参数 String ckeys[] = parseArgsSplitWithComma(s, false, containRaw, allowAlias); - String suffix = expression.substring(end + 1, expression.length()); //:contactCount + String suffix = expression.substring(end + 1); //:contactCount String alias = null; if (allowAlias) { int index = suffix.lastIndexOf(":"); @@ -2236,15 +2538,15 @@ public String parseSQLExpression(String key, String expression, boolean containR } } - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") - || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + if (suffix.isEmpty() == false && (suffix.contains("--") || suffix.contains("/*") + || PATTERN_RANGE.matcher(suffix).matches() == false)) { throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!预编译模式下 " + key + ":\"column?value;function(arg0,arg1,...)?value...\"" + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); } - String origin = fun + "(" + (distinct ? PREFIX_DISTINCT : "") + StringUtil.getString(ckeys) + ")" + suffix; - expression = origin + (StringUtil.isEmpty(alias, true) ? "" : getAs() + quote + alias + quote); + String origin = fun + "(" + (distinct ? PREFIX_DISTINCT : "") + StringUtil.get(ckeys) + ")" + suffix; + expression = origin + (StringUtil.isEmpty(alias, true) ? "" : gainAs() + quote + alias + quote); } else { //是窗口函数 fun(arg0,agr1) OVER (agr0 agr1 ...) @@ -2282,7 +2584,7 @@ else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { int index2 = s2.indexOf("("); // 后半部分 “(”的起始位置 String argString2 = s2.substring(index2 + 1, end); // 后半部分的参数 // 别名 - int aliasIndex = allowAlias == false ? -1 : s2.lastIndexOf(":"); + int aliasIndex = allowAlias ? s2.lastIndexOf(":") : -1; String alias = aliasIndex < 0 ? "" : s2.substring(aliasIndex + 1); if (alias.isEmpty() == false && StringUtil.isName(alias) == false) { throw new IllegalArgumentException("字符串 " + alias + " 不合法!预编译模式下 " @@ -2291,18 +2593,18 @@ else if (SQL_FUNCTION_MAP.containsKey(fun) == false) { } String suffix = s2.substring(end + 1, aliasIndex < 0 ? s2.length() : aliasIndex); - if (suffix.isEmpty() == false && (((String) suffix).contains("--") || ((String) suffix).contains("/*") - || PATTERN_RANGE.matcher((String) suffix).matches() == false)) { + if (suffix.isEmpty() == false && (suffix.contains("--") || suffix.contains("/*") + || PATTERN_RANGE.matcher(suffix).matches() == false)) { throw new UnsupportedOperationException("字符串 " + suffix + " 不合法!预编译模式下 " + key + ":\"column?value;function(arg0,arg1,...)?value...\"" + " 中 ?value 必须符合正则表达式 " + PATTERN_RANGE + " 且不包含连续减号 -- 或注释符 /* !不允许多余的空格!"); } // 获取后半部分的参数解析 (agr0 agr1 ...) - String argsString2[] = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias); - expression = fun + "(" + StringUtil.getString(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") - + StringUtil.getString(argsString2) + ")" + suffix // 传参不传空格,拼接带空格 - + (StringUtil.isEmpty(alias, true) ? "" : getAs() + quote + alias + quote); + String[] argsString2 = parseArgsSplitWithComma(argString2, false, containRaw, allowAlias); + expression = fun + "(" + StringUtil.get(agrsString1) + (containOver ? ") OVER (" : ") AGAINST (") + + StringUtil.get(argsString2) + ")" + suffix // 传参不传空格,拼接带空格 + + (StringUtil.isEmpty(alias, true) ? "" : gainAs() + quote + alias + quote); } } @@ -2321,8 +2623,8 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean // 以"," 分割参数 String quote = getQuote(); boolean isKeyPrefix = isKeyPrefix(); - String tableAlias = quote + getSQLAlias() + quote; - String ckeys[] = StringUtil.split(param); // 以","分割参数 + String tableAlias = quote + gainSQLAlias() + quote; + String[] ckeys = StringUtil.split(param); // 以","分割参数 if (ckeys != null && ckeys.length > 0) { for (int i = 0; i < ckeys.length; i++) { @@ -2341,7 +2643,7 @@ private String[] parseArgsSplitWithComma(String param, boolean isColumn, boolean + " 中所有字符串 column 都必须必须为1个单词 !"); } - origin = getKey(origin).toString(); + origin = gainKey(origin); } else if (ck.startsWith("'") && ck.endsWith("'")) { origin = ck.substring(1, ck.length() - 1); @@ -2352,7 +2654,7 @@ else if (ck.startsWith("'") && ck.endsWith("'")) { } // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 - origin = getValue(origin).toString(); + origin = gainValue(origin).toString(); } else { // 参数不包含",",即不是字符串 @@ -2399,11 +2701,11 @@ else if ("!=null".equals(ck)) { origin = parseArgsSplitWithSpace(mkes); } else { String mk = RAW_MAP.get(origin); - if (mk != null) { // newSQLConfig 提前处理好的 + if (mk != null) { // newSQLConfig 提前处理好的 if (mk.length() > 0) { origin = mk; } - } else if (StringUtil.isNumer(origin)) { + } else if (StringUtil.isNumber(origin)) { //do nothing } else { String[] keys = origin.split("[.]"); @@ -2427,12 +2729,12 @@ else if ("!=null".equals(ck)) { if (StringUtil.isNotEmpty(s, true)) { origin = (len == 1 && isKeyPrefix ? tableAlias + "." : "") + s; } else { - origin = getValue(origin).toString(); + origin = gainValue(origin).toString(); } } - if (isColumn && StringUtil.isEmpty(alias, true) == false) { - origin += getAs() + quote + alias + quote; + if (isColumn && StringUtil.isNotEmpty(alias, true)) { + origin += gainAs() + quote + alias + quote; } } } @@ -2452,10 +2754,10 @@ else if ("!=null".equals(ck)) { * @param mkes * @return */ - private String parseArgsSplitWithSpace(String mkes[]) { + private String parseArgsSplitWithSpace(String[] mkes) { String quote = getQuote(); boolean isKeyPrefix = isKeyPrefix(); - String tableAlias = quote + getSQLAlias() + quote; + String tableAlias = quote + gainSQLAlias() + quote; // 包含空格的参数 肯定不包含别名 不用处理别名 if (mkes != null && mkes.length > 0) { @@ -2464,7 +2766,7 @@ private String parseArgsSplitWithSpace(String mkes[]) { String origin = mkes[j]; String mk = RAW_MAP.get(origin); - if (mk != null) { // newSQLConfig 提前处理好的 + if (mk != null) { // newSQLConfig 提前处理好的 if (mk.length() > 0) { mkes[j] = mk; } @@ -2482,7 +2784,7 @@ private String parseArgsSplitWithSpace(String mkes[]) { + " 中所有字符串 column 都必须必须为1个单词 !"); } - mkes[j] = getKey(origin); + mkes[j] = gainKey(origin); continue; } else if (ck.startsWith("'") && ck.endsWith("'")) { @@ -2494,7 +2796,7 @@ else if (ck.startsWith("'") && ck.endsWith("'")) { } // 1.字符串不是字段也没有别名,所以不解析别名 2. 是字符串,进行预编译,使用getValue() ,对字符串进行截取 - mkes[j] = getValue(origin).toString(); + mkes[j] = gainValue(origin).toString(); continue; } else if (ck.contains("`") || ck.contains("'") || origin.startsWith("_") || origin.contains("--")) { @@ -2505,7 +2807,7 @@ else if (ck.contains("`") || ck.contains("'") || origin.startsWith("_") || origi + " 且不包含连续减号 -- !DISTINCT 必须全大写,且后面必须有且只有 1 个空格!其它情况不允许空格!"); } - if (StringUtil.isNumer(origin)) { + if (StringUtil.isNumber(origin)) { //do nothing } else { String[] keys = origin.split("[.]"); @@ -2529,7 +2831,7 @@ else if (ck.contains("`") || ck.contains("'") || origin.startsWith("_") || origi if (StringUtil.isNotEmpty(s, true)) { origin = (len == 1 && isKeyPrefix ? tableAlias + "." : "") + s; } else { - origin = getValue(origin).toString(); + origin = gainValue(origin).toString(); } } @@ -2545,7 +2847,6 @@ else if (ck.contains("`") || ck.contains("'") || origin.startsWith("_") || origi public List> getValues() { return values; } - @JSONField(serialize = false) public String getValuesString() { String s = ""; if (values != null && values.size() > 0) { @@ -2559,16 +2860,16 @@ public String getValuesString() { items[i] = "("; for (int j = 0; j < vs.size(); j++) { - items[i] += ((j <= 0 ? "" : ",") + getValue(vs.get(j))); + items[i] += ((j <= 0 ? "" : ",") + gainValue(vs.get(j))); } items[i] += ")"; } - s = StringUtil.getString(items); + s = StringUtil.get(items); } return s; } @Override - public AbstractSQLConfig setValues(List> valuess) { + public AbstractSQLConfig setValues(List> valuess) { this.values = valuess; return this; } @@ -2578,7 +2879,7 @@ public Map getContent() { return content; } @Override - public AbstractSQLConfig setContent(Map content) { + public AbstractSQLConfig setContent(Map content) { this.content = content; return this; } @@ -2588,7 +2889,7 @@ public int getCount() { return count; } @Override - public AbstractSQLConfig setCount(int count) { + public AbstractSQLConfig setCount(int count) { this.count = count; return this; } @@ -2597,7 +2898,7 @@ public int getPage() { return page; } @Override - public AbstractSQLConfig setPage(int page) { + public AbstractSQLConfig setPage(int page) { this.page = page; return this; } @@ -2606,7 +2907,7 @@ public int getPosition() { return position; } @Override - public AbstractSQLConfig setPosition(int position) { + public AbstractSQLConfig setPosition(int position) { this.position = position; return this; } @@ -2616,7 +2917,7 @@ public int getQuery() { return query; } @Override - public AbstractSQLConfig setQuery(int query) { + public AbstractSQLConfig setQuery(int query) { this.query = query; return this; } @@ -2625,7 +2926,7 @@ public Boolean getCompat() { return compat; } @Override - public AbstractSQLConfig setCompat(Boolean compat) { + public AbstractSQLConfig setCompat(Boolean compat) { this.compat = compat; return this; } @@ -2635,7 +2936,7 @@ public int getType() { return type; } @Override - public AbstractSQLConfig setType(int type) { + public AbstractSQLConfig setType(int type) { this.type = type; return this; } @@ -2645,39 +2946,39 @@ public int getCache() { return cache; } @Override - public AbstractSQLConfig setCache(int cache) { + public AbstractSQLConfig setCache(int cache) { this.cache = cache; return this; } - public AbstractSQLConfig setCache(String cache) { + public AbstractSQLConfig setCache(String cache) { return setCache(getCache(cache)); } public static int getCache(String cache) { int cache2; if (cache == null) { - cache2 = JSONRequest.CACHE_ALL; + cache2 = JSONMap.CACHE_ALL; } else { // if (isSubquery) { - // throw new IllegalArgumentException("子查询内不支持传 " + JSONRequest.KEY_CACHE + "!"); + // throw new IllegalArgumentException("子查询内不支持传 " + apijson.JSONMap.KEY_CACHE + "!"); // } switch (cache) { case "0": - case JSONRequest.CACHE_ALL_STRING: - cache2 = JSONRequest.CACHE_ALL; + case JSONMap.CACHE_ALL_STRING: + cache2 = JSONMap.CACHE_ALL; break; case "1": - case JSONRequest.CACHE_ROM_STRING: - cache2 = JSONRequest.CACHE_ROM; + case JSONMap.CACHE_ROM_STRING: + cache2 = JSONMap.CACHE_ROM; break; case "2": - case JSONRequest.CACHE_RAM_STRING: - cache2 = JSONRequest.CACHE_RAM; + case JSONMap.CACHE_RAM_STRING: + cache2 = JSONMap.CACHE_RAM; break; default: - throw new IllegalArgumentException(JSONRequest.KEY_CACHE + throw new IllegalArgumentException(JSONMap.KEY_CACHE + ":value 中 value 的值不合法!必须在 [0,1,2] 或 [ALL, ROM, RAM] 内 !"); } } @@ -2689,17 +2990,17 @@ public boolean isExplain() { return explain; } @Override - public AbstractSQLConfig setExplain(boolean explain) { + public AbstractSQLConfig setExplain(boolean explain) { this.explain = explain; return this; } @Override - public List getJoinList() { + public List> getJoinList() { return joinList; } @Override - public AbstractSQLConfig setJoinList(List joinList) { + public AbstractSQLConfig setJoinList(List> joinList) { this.joinList = joinList; return this; } @@ -2714,7 +3015,7 @@ public boolean isTest() { return test; } @Override - public AbstractSQLConfig setTest(boolean test) { + public AbstractSQLConfig setTest(boolean test) { this.test = test; return this; } @@ -2722,7 +3023,6 @@ public AbstractSQLConfig setTest(boolean test) { /**获取初始位置offset * @return */ - @JSONField(serialize = false) public int getOffset() { return getOffset(getPage(), getCount()); } @@ -2737,39 +3037,37 @@ public static int getOffset(int page, int count) { /**获取限制数量 * @return */ - @JSONField(serialize = false) - public String getLimitString() { + public String gainLimitString() { int count = getCount(); + int page = getPage(); - if (isMilvus()) { - if (count == 0) { - Parser parser = getParser(); - count = parser == null ? AbstractParser.MAX_QUERY_COUNT : parser.getMaxQueryCount(); - } + boolean isMilvus = isMilvus(); + if ((count <= 0 && ! (isMilvus && isMain())) || RequestMethod.isHeadMethod(getMethod(), true)) { // TODO HEAD 真的不需要 LIMIT ? + return ""; + } - int offset = getOffset(getPage(), count); - return " LIMIT " + offset + ", " + count; // 目前 moql-transx 的限制 - } else if (isSurrealDB()) { + boolean isSurrealDB = isSurrealDB(); + boolean isQuestDB = isQuestDB(); + if (isSurrealDB || isQuestDB || isMilvus) { if (count == 0) { - Parser parser = getParser(); + Parser parser = gainParser(); count = parser == null ? AbstractParser.MAX_QUERY_COUNT : parser.getMaxQueryCount(); } - int offset = getOffset(getPage(), count); - return " START " + offset + " LIMIT " + count; - } - - if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { // TODO HEAD 真的不需要 LIMIT ? - return ""; + int offset = getOffset(page, count); + if (isQuestDB()) { + return " LIMIT " + offset + ", " + (offset + count); + } + else if (isSurrealDB()) { + return " START " + offset + " LIMIT " + count; + } + else { + return " LIMIT " + offset + ", " + count; // 目前 moql-transx 的限制 + } } - return getLimitString( - getPage() - , getCount() - , isOracle() || isSQLServer() || isDb2() - , isOracle() || isDameng() || isKingBase() - , isPresto() || isTrino() - ); + boolean isOracle = isOracle(); + return gainLimitString(page, count, isTSQL(), isOracle || isDameng() || isKingBase(), isPresto() || isTrino()); } /**获取限制数量及偏移量 * @param page @@ -2778,8 +3076,8 @@ public String getLimitString() { * @param isOracle * @return */ - public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle) { - return getLimitString(page, count, isTSQL, isOracle, false); + public static String gainLimitString(int page, int count, boolean isTSQL, boolean isOracle) { + return gainLimitString(page, count, isTSQL, isOracle, false); } /**获取限制数量及偏移量 * @param page @@ -2789,7 +3087,7 @@ public static String getLimitString(int page, int count, boolean isTSQL, boolean * @param isPresto * @return */ - public static String getLimitString(int page, int count, boolean isTSQL, boolean isOracle, boolean isPresto) { + public static String gainLimitString(int page, int count, boolean isTSQL, boolean isOracle, boolean isPresto) { int offset = getOffset(page, count); if (isOracle) { // TODO 判断版本,高版本可以用 OFFSET FETCH @@ -2812,7 +3110,7 @@ public List getNull() { return nulls; } @Override - public AbstractSQLConfig setNull(List nulls) { + public AbstractSQLConfig setNull(List nulls) { this.nulls = nulls; return this; } @@ -2822,7 +3120,7 @@ public Map getCast() { return cast; } @Override - public AbstractSQLConfig setCast(Map cast) { + public AbstractSQLConfig setCast(Map cast) { this.cast = cast; return this; } @@ -2854,7 +3152,7 @@ public String getCombine() { return combine; } @Override - public AbstractSQLConfig setCombine(String combine) { + public AbstractSQLConfig setCombine(String combine) { this.combine = combine; return this; } @@ -2873,7 +3171,7 @@ public Map> getCombineMap() { return combineMap; } @Override - public AbstractSQLConfig setCombineMap(Map> combineMap) { + public AbstractSQLConfig setCombineMap(Map> combineMap) { this.combineMap = combineMap; return this; } @@ -2883,7 +3181,7 @@ public Map getWhere() { return where; } @Override - public AbstractSQLConfig setWhere(Map where) { + public AbstractSQLConfig setWhere(Map where) { this.where = where; return this; } @@ -2893,7 +3191,6 @@ public AbstractSQLConfig setWhere(Map where) { * @param key * @return */ - @JSONField(serialize = false) @Override public Object getWhere(String key) { return getWhere(key, false); @@ -2905,7 +3202,6 @@ public Object getWhere(String key) { * @return *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

*/ - @JSONField(serialize = false) @Override public Object getWhere(String key, boolean exactMatch) { if (exactMatch) { @@ -2928,10 +3224,10 @@ public Object getWhere(String key, boolean exactMatch) { return null; } @Override - public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { + public AbstractSQLConfig putWhere(String key, Object value, boolean prior) { if (key != null) { if (where == null) { - where = new LinkedHashMap(); + where = new LinkedHashMap<>(); } if (value == null) { where.remove(key); @@ -3017,9 +3313,8 @@ else if (key.equals(userIdInKey)) { * @return * @throws Exception */ - @JSONField(serialize = false) @Override - public String getWhereString(boolean hasPrefix) throws Exception { + public String gainWhereString(boolean hasPrefix) throws Exception { String combineExpr = getCombine(); if (StringUtil.isEmpty(combineExpr, false)) { return getWhereString(hasPrefix, getMethod(), getWhere(), getCombineMap(), getJoinList(), ! isTest()); @@ -3032,9 +3327,8 @@ public String getWhereString(boolean hasPrefix) throws Exception { * @return * @throws Exception */ - @JSONField(serialize = false) public String getWhereString(boolean hasPrefix, RequestMethod method, Map where - , String combine, List joinList, boolean verifyName) throws Exception { + , String combine, List> joinList, boolean verifyName) throws Exception { String whereString = parseCombineExpression(method, getQuote(), getTable(), getAlias() , where, combine, verifyName, false, false); whereString = concatJoinWhereString(whereString); @@ -3064,7 +3358,7 @@ protected String parseCombineExpression(RequestMethod method, String quote, Stri , Map conditionMap, String combine, boolean verifyName, boolean containRaw, boolean isHaving) throws Exception { String errPrefix = table + (isHaving ? ":{ @having:{ " : ":{ ") + "@combine:'" + combine + (isHaving ? "' } }" : "' }"); - String s = StringUtil.getString(combine); + String s = StringUtil.get(combine); if (s.startsWith(" ") || s.endsWith(" ") ) { throw new IllegalArgumentException(errPrefix + " 中字符 '" + s + "' 不合法!不允许首尾有空格,也不允许连续空格!空格不能多也不能少!" @@ -3152,8 +3446,8 @@ protected String parseCombineExpression(RequestMethod method, String quote, Stri + key + "' 对应的条件键值对 " + column + ":value 不存在!"); } } else { - wi = isHaving ? getHavingItem(quote, table, alias, column, (String) value, containRaw) - : getWhereItem(column, value, method, verifyName); + wi = isHaving ? gainHavingItem(quote, table, alias, column, (String) value, containRaw) + : gainWhereItem(column, value, method, verifyName); } if (1.0f*allCount/size > maxCombineRatio && maxCombineRatio > 0) { @@ -3176,7 +3470,7 @@ protected String parseCombineExpression(RequestMethod method, String quote, Stri } usedKeyCountMap.put(column, count); - result += "( " + getCondition(isNot, wi) + " )"; + result += "( " + gainCondition(isNot, wi) + " )"; isNot = false; first = false; } @@ -3315,8 +3609,8 @@ else if (c == ')') { continue; } - String wi = isHaving ? getHavingItem(quote, table, alias, key, (String) entry.getValue(), containRaw) - : getWhereItem(key, entry.getValue(), method, verifyName); + String wi = isHaving ? gainHavingItem(quote, table, alias, key, (String) entry.getValue(), containRaw) + : gainWhereItem(key, entry.getValue(), method, verifyName); if (StringUtil.isEmpty(wi, true)) {//避免SQL条件连接错误 continue; } @@ -3334,7 +3628,7 @@ else if (c == ')') { } else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面,否则 prepared 值顺序错误 if (isHaving) { - // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList + // HAVING 前 WHERE 已经有条件 ? 占位,不能反过来,想优化 AND 连接在最前,需要多遍历一次内部的 key,也可以 newSQLConfig 时存到 andList result = "( " + result + " )" + AND + andCond; } else { @@ -3363,7 +3657,7 @@ else if (StringUtil.isNotEmpty(andCond, true)) { // andCond 必须放后面, * @throws Exception */ public String getWhereString(boolean hasPrefix, RequestMethod method, Map where - , Map> combine, List joinList, boolean verifyName) throws Exception { + , Map> combine, List> joinList, boolean verifyName) throws Exception { Set>> combineSet = combine == null ? null : combine.entrySet(); if (combineSet == null || combineSet.isEmpty()) { Log.w(TAG, "getWhereString combineSet == null || combineSet.isEmpty() >> return \"\";"); @@ -3400,7 +3694,7 @@ else if ("!".equals(ce.getKey())) { isItemFirst = true; cs = ""; for (String key : keyList) { - c = getWhereItem(key, where.get(key), method, verifyName); + c = gainWhereItem(key, where.get(key), method, verifyName); if (StringUtil.isEmpty(c, true)) {//避免SQL条件连接错误 continue; @@ -3431,7 +3725,7 @@ else if ("!".equals(ce.getKey())) { protected String concatJoinWhereString(String whereString) throws Exception { - List joinList = getJoinList(); + List> joinList = getJoinList(); if (joinList != null) { String newWs = ""; @@ -3440,12 +3734,13 @@ protected String concatJoinWhereString(String whereString) throws Exception { List newPvl = new ArrayList<>(); List pvl = new ArrayList<>(getPreparedValueList()); - SQLConfig jc; + SQLConfig jc; + SQLConfig outerConfig; String js; - + boolean isWsEmpty = StringUtil.isEmpty(ws, true); boolean changed = false; // 各种 JOIN 没办法统一用 & | !连接,只能按优先级,和 @combine 一样? - for (Join j : joinList) { + for (Join j : joinList) { String jt = j.getJoinType(); switch (jt) { @@ -3453,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 @@ -3462,17 +3778,18 @@ protected String concatJoinWhereString(String whereString) throws Exception { case "^": // SIDE JOIN: ! (A & B) case "(": // ANTI JOIN: A & ! B case ")": // FOREIGN JOIN: B & ! A + case "~": // ASOF JOIN: B ~= A jc = j.getJoinConfig(); boolean isMain = jc.isMain(); jc.setMain(false).setPrepared(isPrepared()).setPreparedValueList(new ArrayList()); - js = jc.getWhereString(false); + js = jc.gainWhereString(false); jc.setMain(isMain); boolean isOuterJoin = "!".equals(jt); 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) @@ -3498,7 +3815,7 @@ protected String concatJoinWhereString(String whereString) throws Exception { } else { if (isSideJoin || isForeignJoin) { - newWs += " ( " + getCondition(true, ws) + " ) "; + newWs += " ( " + gainCondition(true, ws) + " ) "; newPvl.addAll(pvl); newPvl.addAll(jc.getPreparedValueList()); @@ -3509,7 +3826,7 @@ protected String concatJoinWhereString(String whereString) throws Exception { continue; } - if (StringUtil.isEmpty(newWs, true) == false) { + if (StringUtil.isNotEmpty(newWs, true)) { newWs += AND; } @@ -3521,7 +3838,7 @@ else if (isForeignJoin) { // ) FOREIGN JOIN: (! A) & B // preparedValueList.add } else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) //MySQL 因为 NULL 值处理问题,(A & ! B) | (B & ! A) 与 ! (A & B) 返回结果不一样,后者往往更多 - newWs += " ( " + getCondition( + newWs += " ( " + gainCondition( true, ( isWsEmpty ? "" : ws + AND ) + " ( " + js + " ) " ) + " ) "; @@ -3529,7 +3846,7 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) else { // & INNER JOIN: A & B; | FULL JOIN: A | B; OUTER JOIN: ! (A | B) int logic = Logic.getType(jt); newWs += " ( " - + getCondition( + + gainCondition( Logic.isNot(logic), ws + ( isWsEmpty ? "" : (Logic.isAnd(logic) ? AND : OR) ) @@ -3547,7 +3864,7 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) throw new UnsupportedOperationException( "join:value 中 value 里的 " + jt + "/" + j.getPath() + "错误!不支持 " + jt + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN, ~ ASOF ] 之外的 JOIN 类型 !" ); } } @@ -3570,7 +3887,7 @@ else if (isSideJoin) { // ^ SIDE JOIN: ! (A & B) * @return * @throws Exception */ - protected String getWhereItem(String key, Object value, RequestMethod method, boolean verifyName) throws Exception { + protected String gainWhereItem(String key, Object value, RequestMethod method, boolean verifyName) throws Exception { Log.d(TAG, "getWhereItem key = " + key); // 避免筛选到全部 value = key == null ? null : where.get(key); if (key == null || key.endsWith("()") || key.startsWith("@")) { //关键字||方法, +或-直接报错 @@ -3620,42 +3937,41 @@ else if (key.endsWith("<")) { keyType = 0; } - String column = getRealKey(method, key, false, true, verifyName); + String column = gainRealKey(method, key, false, true, verifyName); // 原始 SQL 片段 - String rawSQL = getRawSQL(key, value); + String rawSQL = gainRawSQL(key, value); switch (keyType) { case 1: - return getSearchString(key, column, value, rawSQL); + return gainSearchString(key, column, value, rawSQL); case -2: case 2: - return getRegExpString(key, column, value, keyType < 0, rawSQL); + return gainRegExpString(key, column, value, keyType < 0, rawSQL); case 3: - return getBetweenString(key, column, value, rawSQL); + return gainBetweenString(key, column, value, rawSQL); case 4: - return getRangeString(key, column, value, rawSQL); + return gainRangeString(key, column, value, rawSQL); case 5: - return getExistsString(key, column, value, rawSQL); + return gainExistsString(key, column, value, rawSQL); case 6: - return getContainString(key, column, value, rawSQL); + return gainContainString(key, column, value, rawSQL); case 7: - return getCompareString(key, column, value, ">=", rawSQL); + return gainCompareString(key, column, value, ">=", rawSQL); case 8: - return getCompareString(key, column, value, "<=", rawSQL); + return gainCompareString(key, column, value, "<=", rawSQL); case 9: - return getCompareString(key, column, value, ">", rawSQL); + return gainCompareString(key, column, value, ">", rawSQL); case 10: - return getCompareString(key, column, value, "<", rawSQL); + return gainCompareString(key, column, value, "<", rawSQL); default: // TODO MySQL JSON类型的字段对比 key='[]' 会无结果! key LIKE '[1, 2, 3]' //TODO MySQL , 后面有空格! - return getEqualString(key, column, value, rawSQL); + return gainEqualString(key, column, value, rawSQL); } } - @JSONField(serialize = false) - public String getEqualString(String key, String column, Object value, String rawSQL) throws Exception { - if (value != null && JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + public String gainEqualString(String key, String column, Object value, String rawSQL) throws Exception { + if (value != null && JSON.isBoolOrNumOrStr(value) == false && value instanceof Subquery == false) { throw new IllegalArgumentException(key + ":value 中value不合法!非PUT请求只支持 [Boolean, Number, String] 内的类型 !"); } @@ -3670,13 +3986,12 @@ public String getEqualString(String key, String column, Object value, String raw } String logic = value == null && rawSQL == null ? (not ? SQL.IS_NOT : SQL.IS) : (not ? " != " : " = "); - return getKey(column) + logic + (value instanceof Subquery ? getSubqueryString((Subquery) value) - : (rawSQL != null ? rawSQL : getValue(key, column, value))); + return gainKey(column) + logic + (value instanceof Subquery ? gainSubqueryString((Subquery) value) + : (rawSQL != null ? rawSQL : gainValue(key, column, value))); } - @JSONField(serialize = false) - public String getCompareString(String key, String column, Object value, String type, String rawSQL) throws Exception { - if (value != null && JSON.isBooleanOrNumberOrString(value) == false && value instanceof Subquery == false) { + public String gainCompareString(String key, String column, Object value, String type, String rawSQL) throws Exception { + if (value != null && JSON.isBoolOrNumOrStr(value) == false && value instanceof Subquery == false) { throw new IllegalArgumentException(key + ":value 中 value 不合法!比较运算 [>, <, >=, <=] 只支持 [Boolean, Number, String] 内的类型 !"); } @@ -3685,11 +4000,11 @@ public String getCompareString(String key, String column, Object value, String t throw new IllegalArgumentException(key + ":value 中 key 不合法!比较运算 [>, <, >=, <=] 不支持 [&, !, |] 中任何逻辑运算符 !"); } - return getKey(column) + " " + type + " " + (value instanceof Subquery ? getSubqueryString((Subquery) value) - : (rawSQL != null ? rawSQL : getValue(key, column, value))); + return gainKey(column) + " " + type + " " + (value instanceof Subquery ? gainSubqueryString((Subquery) value) + : (rawSQL != null ? rawSQL : gainValue(key, column, value))); } - public String getKey(String key) { + public String gainKey(@NotNull String key) { String lenFun = ""; if (key.endsWith("[")) { lenFun = isSQLServer() ? "datalength" : "length"; @@ -3703,7 +4018,7 @@ else if (isTest()) { if (key.contains("'")) { // || key.contains("#") || key.contains("--")) { throw new IllegalArgumentException("参数 " + key + " 不合法!key 中不允许有单引号 ' !"); } - return getSQLValue(key).toString(); + return gainSQLValue(key).toString(); } Map keyMap = getKeyMap(); @@ -3714,7 +4029,7 @@ else if (isTest()) { String sqlKey; if (expression == null) { - sqlKey = getSQLKey(key); + sqlKey = gainSQLKey(key); } else { // (name,tag) left(date,4) 等 @@ -3724,20 +4039,20 @@ else if (isTest()) { return lenFun.isEmpty() ? sqlKey : lenFun + "(" + sqlKey + ")"; } - public String getSQLKey(String key) { + public String gainSQLKey(String key) { String q = getQuote(); - return (isKeyPrefix() ? q + getSQLAlias() + q + "." : "") + q + key + q; + return (isKeyPrefix() ? q + gainSQLAlias() + q + "." : "") + q + key + q; } /** * 使用prepareStatement预编译,值为 ? ,后续动态set进去 */ - protected Object getValue(@NotNull Object value) { - return getValue(null, null, value); + protected Object gainValue(@NotNull Object value) { + return gainValue(null, null, value); } protected List preparedValueList = new ArrayList<>(); - protected Object getValue(String key, String column, Object value) { + protected Object gainValue(String key, String column, Object value) { if (isPrepared()) { if (value == null) { return null; @@ -3767,16 +4082,16 @@ protected Object getValue(String key, String column, Object value) { return StringUtil.isEmpty(type, true) ? "?" : "cast(?" + SQL.AS + type + ")"; } - return key == null ? getSQLValue(value) : getSQLValue(key, column, value); + return key == null ? gainSQLValue(value) : gainSQLValue(key, column, value); } - public Object getSQLValue(String key, String column, @NotNull Object value) { + public Object gainSQLValue(String key, String column, @NotNull Object value) { Map castMap = getCast(); String type = key == null || castMap == null ? null : castMap.get(key); - Object val = getSQLValue(value); + Object val = gainSQLValue(value); return StringUtil.isEmpty(type, true) ? val : "cast(" + val + SQL.AS + type + ")"; } - public Object getSQLValue(@NotNull Object value) { + public Object gainSQLValue(@NotNull Object value) { if (value == null) { return SQL.NULL; } @@ -3791,7 +4106,7 @@ public List getPreparedValueList() { return preparedValueList; } @Override - public AbstractSQLConfig setPreparedValueList(List preparedValueList) { + public AbstractSQLConfig setPreparedValueList(List preparedValueList) { this.preparedValueList = preparedValueList; return this; } @@ -3802,11 +4117,10 @@ public AbstractSQLConfig setPreparedValueList(List preparedValueList) * @param column * @param value * @param rawSQL - * @return {@link #getSearchString(String, String, Object[], int)} + * @return {@link #gainSearchString(String, String, Object[], int)} * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getSearchString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { + public String gainSearchString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key$ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); @@ -3819,11 +4133,11 @@ public String getSearchString(String key, String column, Object value, String ra column = logic.getKey(); Log.i(TAG, "getSearchString column = " + column); - JSONArray arr = newJSONArray(value); + List arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getSearchString(key, column, arr.toArray(), logic.getType()); + return gainSearchString(key, column, arr.toArray(), logic.getType()); } /**search key match values * @param key @@ -3833,8 +4147,7 @@ public String getSearchString(String key, String column, Object value, String ra * @return LOGIC [ key LIKE 'values[i]' ] * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getSearchString(String key, String column, Object[] values, int type) throws IllegalArgumentException { + public String gainSearchString(String key, String column, Object[] values, int type) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -3852,10 +4165,10 @@ public String getSearchString(String key, String column, Object[] values, int ty // throw new IllegalArgumentException(key + "$:value 中 value 值 " + v + " 中包含 %% !不允许有连续的 % !"); // } - condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + getLikeString(key, column, (String) v); + condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) + gainLikeString(key, column, (String) v); } - return getCondition(Logic.isNot(type), condition); + return gainCondition(Logic.isNot(type), condition); } /**WHERE key LIKE 'value' @@ -3864,8 +4177,7 @@ public String getSearchString(String key, String column, Object[] values, int ty * @param value * @return key LIKE 'value' */ - @JSONField(serialize = false) - public String getLikeString(@NotNull String key, @NotNull String column, String value) { + public String gainLikeString(@NotNull String key, @NotNull String column, String value) { String k = key.substring(0, key.length() - 1); char r = k.charAt(k.length() - 1); @@ -3914,7 +4226,7 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { } } - return getKey(column) + " LIKE " + getValue(key, column, value); + return gainKey(column) + " LIKE " + gainValue(key, column, value); } //$ search >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -3927,11 +4239,10 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { * @param column * @param value * @param ignoreCase - * @return {@link #getRegExpString(String, String, Object[], int, boolean)} + * @return {@link #gainRegExpString(String, String, Object[], int, boolean)} * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getRegExpString(String key, String column, Object value, boolean ignoreCase, String rawSQL) + public String gainRegExpString(String key, String column, Object value, boolean ignoreCase, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key~ 这种功能符 !" + @@ -3945,11 +4256,11 @@ public String getRegExpString(String key, String column, Object value, boolean i column = logic.getKey(); Log.i(TAG, "getRegExpString column = " + column); - JSONArray arr = newJSONArray(value); + L arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getRegExpString(key, column, arr.toArray(), logic.getType(), ignoreCase); + return gainRegExpString(key, column, arr.toArray(), logic.getType(), ignoreCase); } /**search key match RegExp values * @param key @@ -3959,8 +4270,7 @@ public String getRegExpString(String key, String column, Object value, boolean i * @return LOGIC [ key REGEXP 'values[i]' ] * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getRegExpString(String key, String column, Object[] values, int type, boolean ignoreCase) + public String gainRegExpString(String key, String column, Object[] values, int type, boolean ignoreCase) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; @@ -3972,10 +4282,10 @@ public String getRegExpString(String key, String column, Object[] values, int ty throw new IllegalArgumentException(key + ":value 中value的类型只能为String或String[]!"); } condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) - + getRegExpString(key, column, (String) values[i], ignoreCase); + + gainRegExpString(key, column, (String) values[i], ignoreCase); } - return getCondition(Logic.isNot(type), condition); + return gainCondition(Logic.isNot(type), condition); } /**WHERE key REGEXP 'value' @@ -3984,30 +4294,29 @@ public String getRegExpString(String key, String column, Object[] values, int ty * @param ignoreCase * @return key REGEXP 'value' */ - @JSONField(serialize = false) - public String getRegExpString(String key, String column, String value, boolean ignoreCase) { - if (isPostgreSQL() || isCockroachDB() || isInfluxDB()) { - return getKey(column) + " ~" + (ignoreCase ? "* " : " ") + getValue(key, column, value); + public String gainRegExpString(String key, String column, String value, boolean ignoreCase) { + if (isPSQL()) { + return gainKey(column) + " ~" + (ignoreCase ? "* " : " ") + gainValue(key, column, value); } - if (isOracle() || isDameng() || isKingBase() || (isMySQL() && getDBVersionNums()[0] >= 8)) { - return "regexp_like(" + getKey(column) + ", " + getValue(key, column, value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; + if (isOracle() || isDameng() || isKingBase() || (isMySQL() && gainDBVersionNums()[0] >= 8)) { + return "regexp_like(" + gainKey(column) + ", " + gainValue(key, column, value) + (ignoreCase ? ", 'i'" : ", 'c'") + ")"; } if (isPresto() || isTrino()) { - return "regexp_like(" + (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") - + ", " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; + return "regexp_like(" + (ignoreCase ? "lower(" : "") + gainKey(column) + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + gainValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; } if (isClickHouse()) { - return "match(" + (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") - + ", " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; + return "match(" + (ignoreCase ? "lower(" : "") + gainKey(column) + (ignoreCase ? ")" : "") + + ", " + (ignoreCase ? "lower(" : "") + gainValue(key, column, value) + (ignoreCase ? ")" : "") + ")"; } if (isElasticsearch()) { - return getKey(column) + " RLIKE " + getValue(key, column, value); + return gainKey(column) + " RLIKE " + gainValue(key, column, value); } if (isHive()) { - return (ignoreCase ? "lower(" : "") + getKey(column) + (ignoreCase ? ")" : "") - + " REGEXP " + (ignoreCase ? "lower(" : "") + getValue(key, column, value) + (ignoreCase ? ")" : ""); + return (ignoreCase ? "lower(" : "") + gainKey(column) + (ignoreCase ? ")" : "") + + " REGEXP " + (ignoreCase ? "lower(" : "") + gainValue(key, column, value) + (ignoreCase ? ")" : ""); } - return getKey(column) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + getValue(key, column, value); + return gainKey(column) + " REGEXP " + (ignoreCase ? "" : "BINARY ") + gainValue(key, column, value); } @@ -4023,8 +4332,7 @@ public String getRegExpString(String key, String column, String value, boolean i * @return LOGIC [ key BETWEEN 'start' AND 'end' ] * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getBetweenString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { + public String gainBetweenString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key% 这种功能符 !" + "只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); @@ -4037,11 +4345,11 @@ public String getBetweenString(String key, String column, Object value, String r column = logic.getKey(); Log.i(TAG, "getBetweenString column = " + column); - JSONArray arr = newJSONArray(value); + L arr = newJSONArray(value); if (arr.isEmpty()) { return ""; } - return getBetweenString(key, column, arr.toArray(), logic.getType()); + return gainBetweenString(key, column, arr.toArray(), logic.getType()); } /**WHERE key BETWEEN 'start' AND 'end' @@ -4052,8 +4360,7 @@ public String getBetweenString(String key, String column, Object value, String r * @return LOGIC [ key BETWEEN 'start' AND 'end' ] * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getBetweenString(String key, String column, Object[] values, int type) throws IllegalArgumentException { + public String gainBetweenString(String key, String column, Object[] values, int type) throws IllegalArgumentException { if (values == null || values.length <= 0) { return ""; } @@ -4072,10 +4379,10 @@ public String getBetweenString(String key, String column, Object[] values, int t } condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)) - + "(" + getBetweenString(key, column, vs[0], (Object) vs[1]) + ")"; + + "(" + gainBetweenString(key, column, vs[0], (Object) vs[1]) + ")"; } - return getCondition(Logic.isNot(type), condition); + return gainCondition(Logic.isNot(type), condition); } /**WHERE key BETWEEN 'start' AND 'end' @@ -4086,13 +4393,12 @@ public String getBetweenString(String key, String column, Object[] values, int t * @return LOGIC [ key BETWEEN 'start' AND 'end' ] * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getBetweenString(String key, String column, Object start, Object end) throws IllegalArgumentException { - if (JSON.isBooleanOrNumberOrString(start) == false || JSON.isBooleanOrNumberOrString(end) == false) { + public String gainBetweenString(String key, String column, Object start, Object end) throws IllegalArgumentException { + if (JSON.isBoolOrNumOrStr(start) == false || JSON.isBoolOrNumOrStr(end) == false) { throw new IllegalArgumentException(key + ":value 中 value 不合法!类型为 String 时必须包括1个逗号 , " + "且左右两侧都有值!类型为 String[] 里面每个元素要符合前面类型为 String 的规则 !"); } - return getKey(column) + " BETWEEN " + getValue(key, column, start) + AND + getValue(key, column, end); + return gainKey(column) + " BETWEEN " + gainValue(key, column, start) + AND + gainValue(key, column, end); } @@ -4109,8 +4415,7 @@ public String getBetweenString(String key, String column, Object start, Object e * @return key condition0 AND key condition1 AND ... * @throws Exception */ - @JSONField(serialize = false) - public String getRangeString(String key, String column, Object range, String rawSQL) throws Exception { + public String gainRangeString(String key, String column, Object range, String rawSQL) throws Exception { Log.i(TAG, "getRangeString column = " + column); if (range == null) {//依赖的对象都没有给出有效值,这个存在无意义。如果是客户端传的,那就能在客户端确定了。 throw new NotExistException(TAG + "getRangeString(" + column + ", " + range + ") range == null"); @@ -4131,7 +4436,7 @@ public String getRangeString(String key, String column, Object range, String raw if (logic.isNot() && l.isEmpty()) { return ""; // key!{}: [] 这个条件无效,加到 SQL 语句中 key IN() 会报错,getInString 里不好处理 } - return getKey(k) + getInString(k, column, l.toArray(), logic.isNot()); + return gainKey(k) + gainInString(k, column, l.toArray(), logic.isNot()); } throw new IllegalArgumentException(key + ":[] 中 {} 前面的逻辑运算符错误!只能用'|','!'中的一种 !"); } @@ -4141,7 +4446,7 @@ else if (range instanceof String) {//非Number类型需要客户端拼接成 < ' if (rawSQL != null) { int index = rawSQL.indexOf("("); - condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : getKey(k) + " ") + rawSQL; + condition = (index >= 0 && index < rawSQL.lastIndexOf(")") ? "" : gainKey(k) + " ") + rawSQL; } if (cs != null) { @@ -4163,7 +4468,7 @@ else if (range instanceof String) {//非Number类型需要客户端拼接成 < ' , key + ":\"!=null;+3*2<=10;function0(arg0,arg1,...)>1;function1(...)%5<=3...\""); } else { - String fk = getKey(k) + " "; + String fk = gainKey(k) + " "; String[] ccs = StringUtil.split(expr, false); expr = ""; @@ -4193,11 +4498,11 @@ else if (isPrepared() && (c.contains("--") || PATTERN_RANGE.matcher(c).matches() return ""; } - return getCondition(logic.isNot(), condition); + return gainCondition(logic.isNot(), condition); } else if (range instanceof Subquery) { - // 如果在 Parser 解析成 SQL 字符串再引用,没法保证安全性,毕竟可以再通过远程函数等方式来拼接再替代,最后引用的字符串就能注入 - return getKey(k) + (logic.isNot() ? NOT : "") + " IN " + getSubqueryString((Subquery) range); + // 如果在 Parser 解析成 SQL 字符串再引用,没法保证安全性,毕竟可以再通过远程函数等方式来拼接再替代,最后引用的字符串就能注入 + return gainKey(k) + (logic.isNot() ? NOT : "") + " IN " + gainSubqueryString((Subquery) range); } throw new IllegalArgumentException(key + ":range 类型为" + range.getClass().getSimpleName() @@ -4208,12 +4513,11 @@ else if (range instanceof Subquery) { * @return IN ('key0', 'key1', ... ) * @throws NotExistException */ - @JSONField(serialize = false) - public String getInString(String key, String column, Object[] in, boolean not) throws NotExistException { + public String gainInString(String key, String column, Object[] in, boolean not) throws NotExistException { String condition = ""; if (in != null) {//返回 "" 会导致 id:[] 空值时效果和没有筛选id一样! for (int i = 0; i < in.length; i++) { - condition += ((i > 0 ? "," : "") + getValue(key, column, in[i])); + condition += ((i > 0 ? "," : "") + gainValue(key, column, in[i])); } } if (condition.isEmpty()) {//条件如果存在必须执行,不能忽略。条件为空会导致出错,又很难保证条件不为空(@:条件),所以还是这样好 @@ -4233,8 +4537,7 @@ public String getInString(String key, String column, Object[] in, boolean not) t * @return EXISTS ALL(SELECT ...) * @throws NotExistException */ - @JSONField(serialize = false) - public String getExistsString(String key, String column, Object value, String rawSQL) throws Exception { + public String gainExistsString(String key, String column, Object value, String rawSQL) throws Exception { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!" + "@raw 不支持 key}{ 这种功能符 !只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); @@ -4251,7 +4554,7 @@ public String getExistsString(String key, String column, Object value, String ra column = logic.getKey(); Log.i(TAG, "getExistsString column = " + column); - return (logic.isNot() ? NOT : "") + " EXISTS " + getSubqueryString((Subquery) value); + return (logic.isNot() ? NOT : "") + " EXISTS " + gainSubqueryString((Subquery) value); } //}{ exists >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -4259,11 +4562,10 @@ public String getExistsString(String key, String column, Object value, String ra /**WHERE key contains value * @param key * @param value - * @return {@link #getContainString(String, String, Object[], int)} + * @return {@link #gainContainString(String, String, Object[], int)} * @throws NotExistException */ - @JSONField(serialize = false) - public String getContainString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { + public String gainContainString(String key, String column, Object value, String rawSQL) throws IllegalArgumentException { if (rawSQL != null) { throw new UnsupportedOperationException("@raw:value 中 " + key + " 不合法!@raw 不支持 key<> 这种功能符 !" + "只支持 key, key!, key<, key{} 等比较运算 和 @column, @having !"); @@ -4273,7 +4575,7 @@ public String getContainString(String key, String column, Object value, String r column = logic.getKey(); Log.i(TAG, "getContainString column = " + column); - return getContainString(key, column, newJSONArray(value).toArray(), logic.getType()); + return gainContainString(key, column, newJSONArray(value).toArray(), logic.getType()); } /**WHERE key contains childs TODO 支持 key<>: { "path":"$[0].name", "value": 82001 } * 或者 key<$[0].name>:82001 或者 key$[0].name<>:82001 ? 还是前者好,key 一旦复杂了, @@ -4285,15 +4587,14 @@ public String getContainString(String key, String column, Object value, String r * OR key LIKE '%, " + childs[i] + ", %' OR key LIKE '%, " + childs[i] + "]' ) ] * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getContainString(String key, String column, Object[] childs, int type) throws IllegalArgumentException { + public String gainContainString(String key, String column, Object[] childs, int type) throws IllegalArgumentException { boolean not = Logic.isNot(type); String condition = ""; if (childs != null) { for (int i = 0; i < childs.length; i++) { Object c = childs[i]; if (c instanceof Collection) { - throw new IllegalArgumentException(key + ":value 中 value 类型不能为 [JSONArray, Collection] 中的任何一个 !"); + throw new IllegalArgumentException(key + ":value 中 value 类型不能为 [JSONList, Collection] 中的任何一个 !"); } Object path = ""; @@ -4307,50 +4608,50 @@ public String getContainString(String key, String column, Object[] childs, int t c = ((Map) c).get("value"); if (c instanceof Collection || c instanceof Map) { throw new IllegalArgumentException(key + ":{ path:path, value:value } 中 value 类型" + - "不能为 [JSONObject, JSONArray, Collection, Map] 中的任何一个 !"); + "不能为 [JSONMap, JSONList, Collection, Map] 中的任何一个 !"); } } condition += (i <= 0 ? "" : (Logic.isAnd(type) ? AND : OR)); - if (isPostgreSQL() || isCockroachDB() || isInfluxDB()) { - condition += (getKey(column) + " @> " + getValue(key, column, newJSONArray(c))); + if (isPSQL()) { + condition += (gainKey(column) + " @> " + gainValue(key, column, newJSONArray(c))); // operator does not exist: jsonb @> character varying "[" + c + "]"); } else if (isOracle() || isDameng() || isKingBase()) { - condition += ("json_textcontains(" + getKey(column) + ", " + (StringUtil.isEmpty(path, true) - ? "'$'" : getValue(key, column, path)) + ", " + getValue(key, column, c == null ? null : c.toString()) + ")"); + condition += ("json_textcontains(" + gainKey(column) + ", " + (StringUtil.isEmpty(path, true) + ? "'$'" : gainValue(key, column, path)) + ", " + gainValue(key, column, c == null ? null : c.toString()) + ")"); } else if (isPresto() || isTrino()) { - condition += ("json_array_contains(cast(" + getKey(column) + " AS VARCHAR), " - + getValue(key, column, c) + (StringUtil.isEmpty(path, true) - ? "" : ", " + getValue(key, column, path)) + ")"); + condition += ("json_array_contains(cast(" + gainKey(column) + " AS VARCHAR), " + + gainValue(key, column, c) + (StringUtil.isEmpty(path, true) + ? "" : ", " + gainValue(key, column, path)) + ")"); } else { String v = c == null ? "null" : (c instanceof Boolean || c instanceof Number ? c.toString() : "\"" + c + "\""); if (isClickHouse()) { - condition += (condition + "has(JSONExtractArrayRaw(assumeNotNull(" + getKey(column) + "))" - + ", " + getValue(key, column, v) + (StringUtil.isEmpty(path, true) - ? "" : ", " + getValue(key, column, path)) + ")"); + condition += (condition + "has(JSONExtractArrayRaw(assumeNotNull(" + gainKey(column) + "))" + + ", " + gainValue(key, column, v) + (StringUtil.isEmpty(path, true) + ? "" : ", " + gainValue(key, column, path)) + ")"); } else { - condition += ("json_contains(" + getKey(column) + ", " + getValue(key, column, v) - + (StringUtil.isEmpty(path, true) ? "" : ", " + getValue(key, column, path)) + ")"); + condition += ("json_contains(" + gainKey(column) + ", " + gainValue(key, column, v) + + (StringUtil.isEmpty(path, true) ? "" : ", " + gainValue(key, column, path)) + ")"); } } } if (condition.isEmpty()) { - condition = getKey(column) + SQL.isNull(true) + OR + getLikeString(key, column, "[]"); // key = '[]' 无结果! + condition = gainKey(column) + SQL.isNull(true) + OR + gainLikeString(key, column, "[]"); // key = '[]' 无结果! } else { - condition = getKey(column) + SQL.isNull(false) + AND + "(" + condition + ")"; + condition = gainKey(column) + SQL.isNull(false) + AND + "(" + condition + ")"; } } if (condition.isEmpty()) { return ""; } - return getCondition(not, condition); + return gainCondition(not, condition); } //<> contain >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -4358,6 +4659,27 @@ else if (isPresto() || isTrino()) { //key@:{} Subquery <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + public List getWithAsExprSQLList() { + return withAsExprSQLList; + } + private void clearWithAsExprListIfNeed() { + // mysql8版本以上,子查询支持with as表达式 + if(this.isMySQL() && this.gainDBVersionNums()[0] >= 8) { + this.withAsExprSQLList = new ArrayList<>(); + } + } + + @Override + public List getWithAsExprPreparedValueList() { + return this.withAsExprPreparedValueList; + } + + @Override + public AbstractSQLConfig setWithAsExprPreparedValueList(List list) { + this.withAsExprPreparedValueList = list; + return this; + } + /** * 只要 method != RequestMethod.POST 就都支持 with-as表达式 * @param cfg @@ -4365,20 +4687,20 @@ else if (isPresto() || isTrino()) { * @return * @throws Exception */ - private String withAsExpreSubqueryString(SQLConfig cfg, Subquery subquery) throws Exception { + private String withAsExprSubqueryString(SQLConfig cfg, Subquery subquery) throws Exception { boolean isWithAsEnable = isWithAsEnable(); - List list = isWithAsEnable ? getWithAsExprSqlList() : null; + List list = isWithAsEnable ? getWithAsExprSQLList() : null; if (cfg.getMethod() != RequestMethod.POST && list == null) { clearWithAsExprListIfNeed(); } String quote = getQuote(); - String as = getAs(); + String as = gainAs(); String withAsExpreSql; if (list != null) { - String withQuoteName = quote + subquery.getKey() + quote; - list.add(" " + withQuoteName + as + "(" + cfg.getSQL(isPrepared()) + ") "); + String withQuoteName = quote + subquery.gainKey() + quote; + list.add(" " + withQuoteName + as + "(" + cfg.gainSQL(isPrepared()) + ") "); withAsExpreSql = " SELECT * FROM " + withQuoteName; // 预编译参数 FIXME 这里重复添加了,导致子查询都报错参数超过 ? 数量 Parameter index out of range (5 > number of parameters, which is 4) @@ -4394,10 +4716,10 @@ private String withAsExpreSubqueryString(SQLConfig cfg, Subquery subquery) throw cfg.setPreparedValueList(new ArrayList<>()); } } else { - withAsExpreSql = cfg.getSQL(isPrepared()); + withAsExpreSql = cfg.gainSQL(isPrepared()); // mysql 才存在这个问题, 主表和子表是一张表 - if (isWithAsEnable && isMySQL() && StringUtil.equals(getTable(), subquery.getFrom())) { - withAsExpreSql = " SELECT * FROM (" + withAsExpreSql + ")" + as + quote + subquery.getKey() + quote; + if (isWithAsEnable && isMySQL() && StringUtil.equals(getTable(), subquery.gainFrom())) { + withAsExpreSql = " SELECT * FROM (" + withAsExpreSql + ")" + as + quote + subquery.gainKey() + quote; } } @@ -4405,20 +4727,20 @@ private String withAsExpreSubqueryString(SQLConfig cfg, Subquery subquery) throw } @Override - public String getSubqueryString(Subquery subquery) throws Exception { + public String gainSubqueryString(Subquery subquery) throws Exception { if (subquery == null) { return ""; } - String range = subquery.getRange(); - SQLConfig cfg = subquery.getConfig(); + String range = subquery.gainRange(); + SQLConfig cfg = subquery.gainConfig(); // 子查询 = 主语句 datasource - if(StringUtil.equals(this.getTable(), subquery.getFrom() ) == false && cfg.hasJoin() == false) { + if (StringUtil.equals(this.getTable(), subquery.gainFrom()) == false && cfg.hasJoin() == false) { cfg.setDatasource(this.getDatasource()); } cfg.setPreparedValueList(new ArrayList<>()); - String withAsExprSql = withAsExpreSubqueryString(cfg, subquery); + String withAsExprSql = withAsExprSubqueryString(cfg, subquery); String sql = (range == null || range.isEmpty() ? "" : range) + "(" + withAsExprSql + ") "; //// SELECT .. FROM(SELECT ..) .. WHERE .. 格式需要把子查询中的预编译值提前 @@ -4459,8 +4781,8 @@ public String getSubqueryString(Subquery subquery) throws Exception { * @param condition * @return */ - public static String getCondition(boolean not, String condition) { - return getCondition(not, condition, false); + public static String gainCondition(boolean not, String condition) { + return gainCondition(not, condition, false); } /**拼接条件 * @param not @@ -4468,7 +4790,7 @@ public static String getCondition(boolean not, String condition) { * @param addOuterBracket * @return */ - public static String getCondition(boolean not, String condition, boolean addOuterBracket) { + public static String gainCondition(boolean not, String condition, boolean addOuterBracket) { String s = not ? NOT + "(" + condition + ")" : condition; return addOuterBracket ? "( " + s + " )" : s; } @@ -4479,8 +4801,8 @@ public static String getCondition(boolean not, String condition, boolean addOute * @return */ @NotNull - public static JSONArray newJSONArray(Object obj) { - JSONArray array = new JSONArray(); + public static > L newJSONArray(Object obj) { + L array = JSON.createJSONArray(); if (obj != null) { if (obj instanceof Collection) { array.addAll((Collection) obj); @@ -4499,9 +4821,8 @@ public static JSONArray newJSONArray(Object obj) { * @return * @throws Exception */ - @JSONField(serialize = false) - public String getSetString() throws Exception { - return getSetString(getMethod(), getContent(), ! isTest()); + public String gainSetString() throws Exception { + return gainSetString(getMethod(), getContent(), ! isTest()); } //CS304 Issue link: https://github.com/Tencent/APIJSON/issues/48 /**获取SET @@ -4511,8 +4832,7 @@ public String getSetString() throws Exception { * @throws Exception *

use entrySet+getValue() to replace keySet+get() to enhance efficiency

*/ - @JSONField(serialize = false) - public String getSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { + public String gainSetString(RequestMethod method, Map content, boolean verifyName) throws Exception { Set set = content == null ? null : content.keySet(); String setString = ""; @@ -4537,11 +4857,11 @@ public String getSetString(RequestMethod method, Map content, bo keyType = 0; //注意重置类型,不然不该加减的字段会跟着加减 } value = entry.getValue(); - String column = getRealKey(method, key, false, true, verifyName); + String column = gainRealKey(method, key, false, true, verifyName); - setString += (isFirst ? "" : ", ") + (getKey(column) + " = " - + (keyType == 1 ? getAddString(key, column, value) : (keyType == 2 - ? getRemoveString(key, column, value) : getValue(key, column, value)) ) + setString += (isFirst ? "" : ", ") + (gainKey(column) + " = " + + (keyType == 1 ? gainAddString(key, column, value) : (keyType == 2 + ? gainRemoveString(key, column, value) : gainValue(key, column, value)) ) ); isFirst = false; @@ -4560,13 +4880,12 @@ public String getSetString(RequestMethod method, Map content, bo * @return concat(key, 'value') * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getAddString(String key, String column, Object value) throws IllegalArgumentException { + public String gainAddString(String key, String column, Object value) throws IllegalArgumentException { if (value instanceof Number) { - return getKey(column) + " + " + value; + return gainKey(column) + " + " + value; } if (value instanceof String) { - return SQL.concat(getKey(column), (String) getValue(key, column, value)); + return SQL.concat(gainKey(column), (String) gainValue(key, column, value)); } throw new IllegalArgumentException(key + ":value 中 value 类型错误,必须是 Number,String,Array 中的任何一种!"); } @@ -4576,13 +4895,12 @@ public String getAddString(String key, String column, Object value) throws Illeg * @return REPLACE (key, 'value', '') * @throws IllegalArgumentException */ - @JSONField(serialize = false) - public String getRemoveString(String key, String column, Object value) throws IllegalArgumentException { + public String gainRemoveString(String key, String column, Object value) throws IllegalArgumentException { if (value instanceof Number) { - return getKey(column) + " - " + value; + return gainKey(column) + " - " + value; } if (value instanceof String) { - return SQL.replace(getKey(column), (String) getValue(key, column, value), "''"); + return SQL.replace(gainKey(column), (String) gainValue(key, column, value), "''"); // " replace(" + column + ", '" + value + "', '') "; } throw new IllegalArgumentException(key + ":value 中 value 类型错误,必须是 Number,String,Array 中的任何一种!"); @@ -4603,15 +4921,14 @@ public Map onFakeDelete(Map map) { * @return * @throws Exception */ - @JSONField(serialize = false) @Override - public String getSQL(boolean prepared) throws Exception { + public String gainSQL(boolean prepared) throws Exception { boolean isPrepared = isPrepared(); if (isPrepared == prepared) { - return getSQL(this); + return gainSQL(this); } - String sql = getSQL(this.setPrepared(prepared)); + String sql = gainSQL(this.setPrepared(prepared)); setPrepared(isPrepared); return sql; } @@ -4620,7 +4937,7 @@ public String getSQL(boolean prepared) throws Exception { * @return * @throws Exception */ - public static String getSQL(AbstractSQLConfig config) throws Exception { + public static , L extends List> String gainSQL(AbstractSQLConfig config) throws Exception { if (config == null) { Log.i(TAG, "getSQL config == null >> return null;"); return null; @@ -4628,7 +4945,7 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { // TODO procedure 改为 List procedureList; behind : true; function: callFunction(); String key; ... // for (...) { Call procedure1();\n SQL \n; Call procedure2(); ... } - // 貌似不需要,因为 ObjectParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 + // 貌似不需要,因为 ObjectParser 里就已经处理的顺序等,只是这里要解决下 Schema 问题。 String procedure = config.getProcedure(); if (StringUtil.isNotEmpty(procedure, true)) { @@ -4636,38 +4953,43 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { boolean hasPrefix = ind >= 0 && ind < procedure.indexOf("("); String sch = hasPrefix ? AbstractFunctionParser.extractSchema( procedure.substring(0, ind), config.getTable() - ) : config.getSQLSchema(); + ) : config.gainSQLSchema(); String q = config.getQuote(); return "CALL " + q + sch + q + "." + (hasPrefix ? procedure.substring(ind + 1) : procedure); } - String tablePath = config.getTablePath(); - if (StringUtil.isNotEmpty(tablePath, true) == false) { - Log.i(TAG, "getSQL StringUtil.isNotEmpty(tablePath, true) == false >> return null;"); + String tablePath = config.gainTablePath(); + if (StringUtil.isEmpty(tablePath, true)) { + Log.i(TAG, "getSQL StringUtil.isEmpty(tablePath, true) >> return null;"); return null; } // 解决重复添加导致报错:Parameter index out of range (6 > number of parameters, which is 5) config.setPreparedValueList(new ArrayList<>()); + RequestMethod method = config.getMethod(); + if (method == null) { + method = GET; + } + String cSql = null; - switch (config.getMethod()) { + switch (method) { case POST: - return "INSERT INTO " + tablePath + config.getColumnString() + " VALUES" + config.getValuesString(); + return "INSERT INTO " + tablePath + config.gainColumnString() + " VALUES" + config.getValuesString(); case PUT: if(config.isClickHouse()){ - return "ALTER TABLE " + tablePath + " UPDATE" + config.getSetString() + config.getWhereString(true); + return "ALTER TABLE " + tablePath + " UPDATE" + config.gainSetString() + config.gainWhereString(true); } - cSql = "UPDATE " + tablePath + config.getSetString() + config.getWhereString(true) - + (config.isMySQL() ? config.getLimitString() : ""); + cSql = "UPDATE " + tablePath + config.gainSetString() + config.gainWhereString(true) + + (config.isMySQL() ? config.gainLimitString() : ""); cSql = buildWithAsExprSql(config, cSql); return cSql; case DELETE: if(config.isClickHouse()){ - return "ALTER TABLE " + tablePath + " DELETE" + config.getWhereString(true); + return "ALTER TABLE " + tablePath + " DELETE" + config.gainWhereString(true); } - cSql = "DELETE FROM " + tablePath + config.getWhereString(true) - + (config.isMySQL() ? config.getLimitString() : ""); // PostgreSQL 不允许 LIMIT + cSql = "DELETE FROM " + tablePath + config.gainWhereString(true) + + (config.isMySQL() ? config.gainLimitString() : ""); // PostgreSQL 不允许 LIMIT cSql = buildWithAsExprSql(config, cSql); return cSql; default: @@ -4675,27 +4997,27 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { : (config.isOracle() || config.isDameng() || config.isKingBase() ? "EXPLAIN PLAN FOR " : "EXPLAIN ")) : ""; if (config.isTest() && RequestMethod.isGetMethod(config.getMethod(), true)) { // FIXME 为啥是 code 而不是 count ? String q = config.getQuote(); // 生成 SELECT ( (24 >=0 AND 24 <3) ) AS `code` LIMIT 1 OFFSET 0 - return explain + "SELECT " + config.getWhereString(false) - + config.getAs() + q + JSONResponse.KEY_COUNT + q + config.getLimitString(); + return explain + "SELECT " + config.gainWhereString(false) + + config.gainAs() + q + JSONResponse.KEY_COUNT + q + config.gainLimitString(); } config.setPreparedValueList(new ArrayList()); - String column = config.getColumnString(); + String column = config.gainColumnString(); if (config.isOracle() || config.isDameng() || config.isKingBase()) { //When config's database is oracle,Using subquery since Oracle12 below does not support OFFSET FETCH paging syntax. //针对oracle分组后条数的统计 if (StringUtil.isNotEmpty(config.getGroup(),true) && RequestMethod.isHeadMethod(config.getMethod(), true)){ - return explain + "SELECT count(*) FROM (SELECT " + (config.getCache() == JSONRequest.CACHE_RAM - ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(tablePath, config) + ") " + config.getLimitString(); + return explain + "SELECT count(*) FROM (SELECT " + (config.getCache() == JSONMap.CACHE_RAM + ? "SQL_NO_CACHE " : "") + column + " FROM " + gainConditionString(tablePath, config) + ") " + config.gainLimitString(); } - String sql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM - ? "SQL_NO_CACHE " : "") + column + " FROM " + getConditionString(tablePath, config); - return explain + config.getOraclePageSql(sql); + String sql = "SELECT " + (config.getCache() == JSONMap.CACHE_RAM + ? "SQL_NO_CACHE " : "") + column + " FROM " + gainConditionString(tablePath, config); + return explain + config.gainOraclePageSQL(sql); } - cSql = "SELECT " + (config.getCache() == JSONRequest.CACHE_RAM ? "SQL_NO_CACHE " : "") - + column + " FROM " + getConditionString(tablePath, config) + config.getLimitString(); + cSql = "SELECT " + (config.getCache() == JSONMap.CACHE_RAM ? "SQL_NO_CACHE " : "") + + column + " FROM " + gainConditionString(tablePath, config) + config.gainLimitString(); cSql = buildWithAsExprSql(config, cSql); if(config.isElasticsearch()) { // elasticSearch 不支持 explain return cSql; @@ -4704,12 +5026,12 @@ public static String getSQL(AbstractSQLConfig config) throws Exception { } } - private static String buildWithAsExprSql(@NotNull AbstractSQLConfig config, String cSql) throws Exception { + private static , L extends List> String buildWithAsExprSql(@NotNull AbstractSQLConfig config, String cSql) throws Exception { if (config.isWithAsEnable() == false) { return cSql; } - List list = config.getWithAsExprSqlList(); + List list = config.getWithAsExprSQLList(); int size = list == null ? 0 : list.size(); if (size > 0) { String withAsExpreSql = "WITH "; @@ -4724,21 +5046,21 @@ private static String buildWithAsExprSql(@NotNull AbstractSQLConfig config, Stri @Override public boolean isWithAsEnable() { - return ENABLE_WITH_AS && (isMySQL() == false || getDBVersionNums()[0] >= 8); + return ENABLE_WITH_AS && (isMySQL() == false || gainDBVersionNums()[0] >= 8); } /**Oracle的分页获取 * @param sql * @return */ - protected String getOraclePageSql(String sql) { + protected String gainOraclePageSQL(String sql) { int count = getCount(); if (count <= 0 || RequestMethod.isHeadMethod(getMethod(), true)) { // TODO HEAD 真的不需要 LIMIT ? return sql; } int offset = getOffset(getPage(), count); String quote = getQuote(); - String alias = quote + getSQLAlias() + quote; + String alias = quote + gainSQLAlias() + quote; return "SELECT * FROM (SELECT " + alias + ".*, ROWNUM "+ quote + "RN" + quote +" FROM (" + sql + ") " + alias + " WHERE ROWNUM <= " + (offset + count) + ") WHERE "+ quote + "RN" + quote +" > " + offset; } @@ -4749,28 +5071,33 @@ protected String getOraclePageSql(String sql) { * @return * @throws Exception */ - private static String getConditionString(String table, AbstractSQLConfig config) throws Exception { - Subquery from = config.getFrom(); + private static , L extends List> String gainConditionString( + String table, AbstractSQLConfig config) throws Exception { + Subquery from = config.getFrom(); if (from != null) { - table = config.getSubqueryString(from) + config.getAs() + config.getSQLAliasWithQuote() + " "; + table = config.gainSubqueryString(from) + config.gainAs() + config.gainSQLAliasWithQuote() + " "; } - String join = config.getJoinString(); + String join = config.gainJoinString(); - String where = config.getWhereString(true); + String where = config.gainWhereString(true); //根据方法不同,聚合语句不同。GROUP BY 和 HAVING 可以加在 HEAD 上, HAVING 可以加在 PUT, DELETE 上,GET 全加,POST 全都不加 String aggregation; if (RequestMethod.isGetMethod(config.getMethod(), true)) { - aggregation = config.getGroupString(true) + config.getHavingString(true) - + config.getOrderString(true); + aggregation = config.gainGroupString(true) + config.gainHavingString(true) + + config.gainSampleString(true) + config.gainLatestString(true) + + config.gainPartitionString(true) + config.gainFillString(true) + + config.gainOrderString(true); } else if (RequestMethod.isHeadMethod(config.getMethod(), true)) { // TODO 加参数 isPagenation 判断是 GET 内分页 query:2 查总数,不用加这些条件 - aggregation = config.getGroupString(true) + config.getHavingString(true) ; + aggregation = config.gainGroupString(true) + config.gainHavingString(true) + + config.gainSampleString(true) + config.gainLatestString(true) + + config.gainPartitionString(true) + config.gainFillString(true); } else if (config.getMethod() == PUT || config.getMethod() == DELETE) { - aggregation = config.getHavingString(true) ; + aggregation = config.gainHavingString(true) ; } else { aggregation = ""; @@ -4821,13 +5148,13 @@ public boolean isKeyPrefix() { return keyPrefix; } @Override - public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { + public AbstractSQLConfig setKeyPrefix(boolean keyPrefix) { this.keyPrefix = keyPrefix; return this; } - public String getJoinString() throws Exception { + public String gainJoinString() throws Exception { String joinOns = ""; if (joinList != null) { @@ -4837,7 +5164,7 @@ public String getJoinString() throws Exception { // 主表不用别名 String ta; for (Join j : joinList) { - onGetJoinString(j); + onGainJoinString(j); if (j.isAppJoin()) { // APP JOIN,只是作为一个标记,执行完主表的查询后自动执行副表的查询 User.id IN($commentIdList) continue; @@ -4846,11 +5173,11 @@ public String getJoinString() throws Exception { //LEFT JOIN sys.apijson_user AS User ON User.id = Moment.userId, 都是用 = ,通过relateType处理缓存 // <"INNER JOIN User ON User.id = Moment.userId", UserConfig> TODO AS 放 getSQLTable 内 - SQLConfig jc = j.getJoinConfig(); + SQLConfig jc = j.getJoinConfig(); jc.setPrepared(isPrepared()); // 将关联表所属数据源配置为主表数据源 jc.setDatasource(this.getDatasource()); - String jt = jc.getSQLAlias(); + String jt = jc.gainSQLAlias(); List onList = j.getOnList(); //如果要强制小写,则可在子类重写这个方法再 toLowerCase @@ -4866,12 +5193,12 @@ public String getJoinString() throws Exception { // continue; case "*": // CROSS JOIN - onGetCrossJoinString(j); + onGainCrossJoinString(j); case "<": // LEFT JOIN case ">": // RIGHT JOIN jc.setMain(true).setKeyPrefix(false); sql = ( "<".equals(type) ? " LEFT" : (">".equals(type) ? " RIGHT" : " CROSS") ) - + " JOIN ( " + jc.getSQL(isPrepared()) + " ) " + getAs() + quote + jt + quote; + + " JOIN ( " + jc.gainSQL(isPrepared()) + " ) " + gainAs() + quote + jt + quote; sql = concatJoinOn(sql, quote, j, jt, onList); jc.setMain(false).setKeyPrefix(true); @@ -4887,25 +5214,29 @@ public String getJoinString() throws Exception { case "^": // SIDE JOIN: ! (A & B) case "(": // ANTI JOIN: A & ! B case ")": // FOREIGN JOIN: B & ! A - sql = " INNER JOIN " + jc.getTablePath(); + sql = " INNER JOIN " + jc.gainTablePath(); + sql = concatJoinOn(sql, quote, j, jt, onList); + break; + case "~": // ASOF JOIN: B ~= A + sql = " ASOF JOIN " + jc.gainTablePath(); sql = concatJoinOn(sql, quote, j, jt, onList); break; default: - String k = jc.getTableKey(); + String k = jc.gainTableKey(); throw new UnsupportedOperationException( "join:value 中 value 里的 " + k + "/" + j.getPath() + "错误!不支持 " + k + " 等 [ @ APP, < LEFT, > RIGHT, * CROSS" - + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN ] 之外的 JOIN 类型 !" + + ", & INNER, | FULL, ! OUTER, ^ SIDE, ( ANTI, ) FOREIGN, ~ ASOF ] 之外的 JOIN 类型 !" ); } - SQLConfig oc = j.getOuterConfig(); + SQLConfig oc = j.getOnConfig(); String ow = null; if (oc != null) { oc.setPrepared(isPrepared()); oc.setPreparedValueList(new ArrayList<>()); oc.setMain(false).setKeyPrefix(true); - ow = oc.getWhereString(false); + ow = oc.gainWhereString(false); pvl.addAll(oc.getPreparedValueList()); //changed = true; @@ -4928,10 +5259,9 @@ public String getJoinString() throws Exception { return StringUtil.isEmpty(joinOns, true) ? "" : joinOns + " \n"; } - - protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNull Join join, @NotNull String jt, List onList) { + protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNull Join join, @NotNull String jt, List onList) { if (onList != null) { - SQLConfig jc = join.getJoinConfig(); + SQLConfig jc = join.getJoinConfig(); Map castMap = jc == null ? null : jc.getCast(); boolean first = true; @@ -4950,7 +5280,7 @@ protected String concatJoinOn(@NotNull String sql, @NotNull String quote, @NotNu String rt = on.getRelateType(); - String rk = quote + SQLConfig.getSQLAlias(on.getTargetTable(), on.getTargetAlias()) + quote + "." + quote + on.getTargetKey() + quote; + String rk = quote + SQLConfig.gainSQLAlias(on.getTargetTable(), on.getTargetAlias()) + quote + "." + quote + on.getTargetKey() + quote; if (StringUtil.isEmpty(rt, false)) { sql += (first ? ON : AND) + lk + (isNot ? " != " : " = ") + rk; @@ -5018,7 +5348,7 @@ else if (l > 0 && StringUtil.isName(String.valueOf(l))) { } else if (rt.endsWith("~")) { boolean ignoreCase = "*~".equals(rt); - if (isPostgreSQL() || isCockroachDB() || isInfluxDB()) { + if (isPSQL()) { sql += (first ? ON : AND) + lk + (isNot ? NOT : "") + " ~" + (ignoreCase ? "* " : " ") + rk; } else if (isOracle() || isDameng() || isKingBase()) { @@ -5053,7 +5383,7 @@ else if ("{}".equals(rt) || "<>".equals(rt)) { } else { boolean find = false; - for (Join jn : joinList) { + for (Join jn : joinList) { if (tt.equals(jn.getTable()) && Objects.equals(ta, jn.getAlias())) { cast = getCast(); find = true; @@ -5073,27 +5403,27 @@ else if ("{}".equals(rt) || "<>".equals(rt)) { String arrKeyPath = isIn ? rk : lk; String itemKeyPath = isIn ? lk : rk; - if (isPostgreSQL() || isCockroachDB() || isInfluxDB()) { //operator does not exist: jsonb @> character varying "[" + c + "]"); - sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + if (isPSQL()) { //operator does not exist: jsonb @> character varying "[" + c + "]"); + sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(isNot, arrKeyPath + " IS NOT NULL AND " + arrKeyPath + " @> " + itemKeyPath) + (isNot ? ") " : ""); } else if (isOracle() || isDameng() || isKingBase()) { - sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(isNot, arrKeyPath + " IS NOT NULL AND json_textcontains(" + arrKeyPath + ", '$', " + itemKeyPath + ")") + (isNot ? ") " : ""); } else if (isPresto() || isTrino()) { - sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(isNot, arrKeyPath + " IS NOT NULL AND json_array_contains(cast(" + arrKeyPath + " AS VARCHAR), " + itemKeyPath + ")") + (isNot ? ") " : ""); } else if (isClickHouse()) { - sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(isNot, arrKeyPath + " IS NOT NULL AND has(JSONExtractArrayRaw(assumeNotNull(" + arrKeyPath + "))" + ", " + itemKeyPath + ")") + (isNot ? ") " : ""); } else { - sql += (first ? ON : AND) + (isNot ? "( " : "") + getCondition(isNot, arrKeyPath + sql += (first ? ON : AND) + (isNot ? "( " : "") + gainCondition(isNot, arrKeyPath + " IS NOT NULL AND json_contains(" + arrKeyPath + (isBoolOrNum ? ", cast(" + itemKeyPath + " AS CHAR), '$')" : ", concat('\"', " + itemKeyPath + ", '\"'), '$')" @@ -5114,17 +5444,17 @@ else if (isClickHouse()) { return sql; } - protected void onJoinNotRelation(String sql, String quote, Join join, String table, List onList, On on) { + protected void onJoinNotRelation(String sql, String quote, Join join, String table, List onList, On on) { throw new UnsupportedOperationException("JOIN 已禁用 '!' 非逻辑连接符 !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } - protected void onJoinComplexRelation(String sql, String quote, Join join, String table, List onList, On on) { + protected void onJoinComplexRelation(String sql, String quote, Join join, String table, List onList, On on) { throw new UnsupportedOperationException("JOIN 已禁用 $, ~, {}, <>, >, <, >=, <= 等复杂关联 !" + "性能很差、需求极少,默认只允许 = 等价关联,如要取消禁用可在后端重写相关方法!"); } - protected void onGetJoinString(Join join) throws UnsupportedOperationException { + protected void onGainJoinString(Join join) throws UnsupportedOperationException { } - protected void onGetCrossJoinString(Join join) throws UnsupportedOperationException { + protected void onGainCrossJoinString(Join join) throws UnsupportedOperationException { throw new UnsupportedOperationException("已禁用 * CROSS JOIN !性能很差、需求极少,如要取消禁用可在后端重写相关方法!"); } @@ -5137,29 +5467,30 @@ protected void onGetCrossJoinString(Join join) throws UnsupportedOperationExcept * @return * @throws Exception */ - public static SQLConfig newSQLConfig(RequestMethod method, String table, String alias - , JSONObject request, List joinList, boolean isProcedure, Callback callback) throws Exception { + public static , L extends List> SQLConfig newSQLConfig( + RequestMethod method, String table, String alias + , M request, List> joinList, boolean isProcedure, Callback callback) throws Exception { if (request == null) { // User:{} 这种空内容在查询时也有效 - throw new NullPointerException(TAG + ": newSQLConfig request == null!"); + throw new NullPointerException(TAG + ": newSQLConfig request == null!"); } - Boolean explain = request.getBoolean(KEY_EXPLAIN); + Boolean explain = getBoolean(request, KEY_EXPLAIN); if (explain != null && explain && Log.DEBUG == false) { // 不在 config.setExplain 抛异常,一方面处理更早性能更好,另一方面为了内部调用可以绕过这个限制 throw new UnsupportedOperationException("非DEBUG模式, 不允许传 " + KEY_EXPLAIN + " !"); } - String database = request.getString(KEY_DATABASE); - if (StringUtil.isEmpty(database, false) == false && DATABASE_LIST.contains(database) == false) { + String database = getString(request, KEY_DATABASE); + if (StringUtil.isNotEmpty(database, false) && DATABASE_LIST.contains(database) == false) { throw new UnsupportedDataTypeException("@database:value 中 value 错误,只能是 [" - + StringUtil.getString(DATABASE_LIST.toArray()) + "] 中的一种!"); + + StringUtil.get(DATABASE_LIST.toArray()) + "] 中的一种!"); } - String datasource = request.getString(KEY_DATASOURCE); - String namespace = request.getString(KEY_NAMESPACE); - String catalog = request.getString(KEY_CATALOG); - String schema = request.getString(KEY_SCHEMA); + String datasource = getString(request, KEY_DATASOURCE); + String namespace = getString(request, KEY_NAMESPACE); + String catalog = getString(request, KEY_CATALOG); + String schema = getString(request, KEY_SCHEMA); - SQLConfig config = callback.getSQLConfig(method, database, schema, datasource, table); + SQLConfig config = (SQLConfig) callback.getSQLConfig(method, database, schema, datasource, table); config.setAlias(alias); config.setDatabase(database); // 不删,后面表对象还要用的,必须放在 parseJoin 前 @@ -5197,7 +5528,7 @@ public static SQLConfig newSQLConfig(RequestMethod method, } } if (newIdIn.isEmpty()) { - throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); + throw new NotExistException(TAG + ": newSQLConfig idIn instanceof List >> 去掉无效 id 后 newIdIn.isEmpty()"); } idIn = newIdIn; @@ -5214,12 +5545,12 @@ public static SQLConfig newSQLConfig(RequestMethod method, if (id != null) { // null 无效 if (id instanceof Number) { if (((Number) id).longValue() <= 0) { // 一定没有值 - throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0"); + throw new NotExistException(TAG + ": newSQLConfig " + table + ".id <= 0"); } } else if (id instanceof String) { if (StringUtil.isEmpty(id, true)) { // 一定没有值 - throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); + throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".id, true)"); } } else if (id instanceof Subquery) {} @@ -5237,7 +5568,7 @@ else if (id instanceof Subquery) {} } } if (contains == false) { // empty有效 BaseModel.isEmpty(idIn) == false) { - throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); + throw new NotExistException(TAG + ": newSQLConfig idIn != null && (((List) idIn).contains(id) == false"); } } @@ -5259,7 +5590,7 @@ else if (id instanceof Subquery) {} } } if (newUserIdIn.isEmpty()) { - throw new NotExistException(TAG + ": newSQLConfig userIdIn instanceof List >> 去掉无效 userId 后 newIdIn.isEmpty()"); + throw new NotExistException(TAG + ": newSQLConfig userIdIn instanceof List >> 去掉无效 userId 后 newIdIn.isEmpty()"); } userIdIn = newUserIdIn; } @@ -5268,12 +5599,12 @@ else if (id instanceof Subquery) {} if (userId != null) { // null 无效 if (userId instanceof Number) { if (((Number) userId).longValue() <= 0) { // 一定没有值 - throw new NotExistException(TAG + ": newSQLConfig " + table + ".userId <= 0"); + throw new NotExistException(TAG + ": newSQLConfig " + table + ".userId <= 0"); } } else if (userId instanceof String) { if (StringUtil.isEmpty(userId, true)) { // 一定没有值 - throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".userId, true)"); + throw new NotExistException(TAG + ": newSQLConfig StringUtil.isEmpty(" + table + ".userId, true)"); } } else if (userId instanceof Subquery) {} @@ -5291,28 +5622,32 @@ else if (userId instanceof Subquery) {} } } if (contains == false) { // empty有效 BaseModel.isEmpty(userIdIn) == false) { - throw new NotExistException(TAG + ": newSQLConfig userIdIn != null && (((List) userIdIn).contains(userId) == false"); + throw new NotExistException(TAG + ": newSQLConfig userIdIn != null && (((List) userIdIn).contains(userId) == false"); } } } // 对 id, id{}, userId, userId{} 处理,这些只要不为 null 就一定会作为 AND 条件 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - String role = request.getString(KEY_ROLE); - String cache = request.getString(KEY_CACHE); - Subquery from = (Subquery) request.get(KEY_FROM); - String column = request.getString(KEY_COLUMN); - String nulls = request.getString(KEY_NULL); - String cast = request.getString(KEY_CAST); - String combine = request.getString(KEY_COMBINE); - String group = request.getString(KEY_GROUP); + String role = getString(request, KEY_ROLE); + String cache = getString(request, KEY_CACHE); + Subquery from = (Subquery) request.get(KEY_FROM); + String column = getString(request, KEY_COLUMN); + String nulls = getString(request, KEY_NULL); + String cast = getString(request, KEY_CAST); + String combine = getString(request, KEY_COMBINE); + String group = getString(request, KEY_GROUP); Object having = request.get(KEY_HAVING); - String havingAnd = request.getString(KEY_HAVING_AND); - String order = request.getString(KEY_ORDER); + String havingAnd = getString(request, KEY_HAVING_AND); + String sample = getString(request, KEY_SAMPLE); + String latest = getString(request, KEY_LATEST); + String partition = getString(request, KEY_PARTITION); + String fill = getString(request, KEY_FILL); + String order = getString(request, KEY_ORDER); Object keyMap = request.get(KEY_KEY); - String raw = request.getString(KEY_RAW); - String json = request.getString(KEY_JSON); - String mthd = request.getString(KEY_METHOD); + String raw = getString(request, KEY_RAW); + String json = getString(request, KEY_JSON); + String mthd = getString(request, KEY_METHOD); try { // 强制作为条件且放在最前面优化性能 @@ -5337,6 +5672,10 @@ else if (userId instanceof Subquery) {} request.remove(KEY_GROUP); request.remove(KEY_HAVING); request.remove(KEY_HAVING_AND); + request.remove(KEY_SAMPLE); + request.remove(KEY_LATEST); + request.remove(KEY_PARTITION); + request.remove(KEY_FILL); request.remove(KEY_ORDER); request.remove(KEY_KEY); request.remove(KEY_RAW); @@ -5424,11 +5763,11 @@ else if (userId instanceof Subquery) {} if (values == null || values.length != columns.length) { throw new Exception("服务器内部错误:\n" + TAG - + " newSQLConfig values == null || values.length != columns.length !"); + + " newSQLConfig values == null || values.length != columns.length !"); } column = (id == null ? "" : idKey + ",") + (userId == null ? "" : userIdKey + ",") - + StringUtil.getString(columns); //set已经判断过不为空 + + StringUtil.get(columns); //set已经判断过不为空 int idCount = id == null ? (userId == null ? 0 : 1) : (userId == null ? 1 : 2); int size = idCount + columns.length; // 以 key 数量为准 @@ -5493,21 +5832,21 @@ else if (userId instanceof Subquery) {} Object deletedKey = accessFakeDeleteMap == null ? null : accessFakeDeleteMap.get(KEY_DELETED_KEY); boolean hasKey = deletedKey instanceof String && StringUtil.isNotEmpty(deletedKey, true); Object deletedValue = hasKey ? accessFakeDeleteMap.get(KEY_DELETED_VALUE) : null; - boolean containNotDeletedValue = hasKey ? accessFakeDeleteMap.containsKey(KEY_NOT_DELETED_VALUE) : false; + boolean containNotDeletedValue = hasKey && accessFakeDeleteMap.containsKey(KEY_NOT_DELETED_VALUE); Object notDeletedValue = containNotDeletedValue ? accessFakeDeleteMap.get(KEY_NOT_DELETED_VALUE) : null; if (deletedValue != null || containNotDeletedValue) { boolean isFakeDelete = true; if (from != null) { // 兼容 JOIN 外层 SELECT 重复生成 deletedKey - SQLConfig cfg = from.getConfig(); + SQLConfig cfg = from.gainConfig(); if (cfg != null && StringUtil.equals(table, cfg.getTable())) { isFakeDelete = false; } - List jl = isFakeDelete && cfg != null ? cfg.getJoinList() : null; + List> jl = isFakeDelete && cfg != null ? cfg.getJoinList() : null; if (jl != null) { - for (Join join : jl) { + for (Join join : jl) { if (join != null && StringUtil.equals(table, join.getTable())) { isFakeDelete = false; break; @@ -5586,11 +5925,11 @@ else if (w.startsWith("!")) { // 可重写回调方法自定义处理 // 动态设置的场景似乎很少,而且去掉后不方便用户排错! // 去掉判断,有时候不在没关系,如果是对增删改等非开放请求强制要求传对应的条件,可以用 Operation.NECESSARY - if (request.containsKey(w) == false) { // 和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null + if (request.containsKey(w) == false) { // 和 request.get(w) == null 没区别,前面 Parser 已经过滤了 null // throw new IllegalArgumentException(table + ":{} 里的 @combine:value 中的value里 " + ws[i] + " 对应的 " + w + " 不在它里面!"); callback.onMissingKey4Combine(table, request, combine, ws[i], w); if (config instanceof AbstractSQLConfig) { - ((AbstractSQLConfig) config).putWarnIfNeed(KEY_COMBINE, table + ":{} 里的 @combine:value 中的 value 里 " + ((AbstractSQLConfig) config).putWarnIfNeed(KEY_COMBINE, table + ":{} 里的 @combine:value 中的 value 里 " + ws[i] + " 对应的条件 " + w + ":value 中 value 必须存在且不能为 null!"); } } @@ -5609,7 +5948,7 @@ else if (w.startsWith("!")) { if (key.endsWith("<>") == false && value instanceof Map) { // 只允许常规 Object throw new IllegalArgumentException(table + ":{ " + key + ":value } 中 value 类型错误!除了 key<>:{} 外,不允许 " - + key + " 等其它任何 key 对应 value 的类型为 JSONObject {} !"); + + key + " 等其它任何 key 对应 value 的类型为 JSONMap {} !"); } // 兼容 PUT @combine @@ -5623,7 +5962,7 @@ else if (w.startsWith("!")) { } else if (whereList.contains(key)) { tableWhere.put(key, value); } else { - tableContent.put(key, value); // 一样 instanceof JSONArray ? JSON.toJSONString(value) : value); + tableContent.put(key, value); // 一样 instanceof List ? JSON.toJSONString(value) : value); } } @@ -5675,7 +6014,7 @@ else if (w.startsWith("!")) { boolean containColumnRaw = rawList != null && rawList.contains(KEY_COLUMN); String rawColumnSQL = null; if (containColumnRaw) { - rawColumnSQL = config.getRawSQL(KEY_COLUMN, column); + rawColumnSQL = config.gainRawSQL(KEY_COLUMN, column); if (rawColumnSQL != null) { cs.add(rawColumnSQL); } @@ -5688,7 +6027,7 @@ else if (w.startsWith("!")) { if (fks != null) { for (String fk : fks) { if (containColumnRaw) { - String rawSQL = config.getRawSQL(KEY_COLUMN, fk); + String rawSQL = config.gainRawSQL(KEY_COLUMN, fk); if (rawSQL != null) { cs.add(rawSQL); continue; @@ -5761,13 +6100,13 @@ else if (w.startsWith("!")) { } } } - else if (newHaving instanceof JSONObject) { + else if (newHaving instanceof Map) { if (isHavingAnd) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 类型不合法!" - + "@having&:value 中 value 只能是 String,@having:value 中 value 只能是 String 或 JSONObject !"); + + "@having&:value 中 value 只能是 String,@having:value 中 value 只能是 String 或 JSONMap !"); } - JSONObject havingObj = (JSONObject) newHaving; + M havingObj = JSON.createJSONObject((Map) newHaving); Set> havingSet = havingObj.entrySet(); for (Entry entry : havingSet) { String k = entry == null ? null : entry.getKey(); @@ -5797,7 +6136,7 @@ else if (StringUtil.isName(k) == false) { } else if (newHaving != null) { throw new IllegalArgumentException(table + ":{ " + havingKey + ":value } 里的 value 类型不合法!" - + "@having:value 中 value 只能是 String 或 JSONObject,@having&:value 中 value 只能是 String !"); + + "@having:value 中 value 只能是 String 或 JSONMap,@having&:value 中 value 只能是 String !"); } // @having, @haivng& >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -5819,7 +6158,7 @@ else if (keyMap instanceof String) { } } else if (keyMap != null) { - throw new UnsupportedDataTypeException("@key:value 中 value 错误,只能是 String, JSONObject 中的一种!"); + throw new UnsupportedDataTypeException("@key:value 中 value 错误,只能是 String, JSONMap 中的一种!"); } @@ -5841,6 +6180,10 @@ else if (keyMap != null) { config.setGroup(group); config.setHaving(havingMap); config.setHavingCombine(havingCombine); + config.setSample(sample); + config.setLatest(latest); + config.setPartition(partition); + config.setFill(fill); config.setOrder(order); String[] jsons = StringUtil.split(json); @@ -5905,6 +6248,18 @@ else if (keyMap != null) { if (havingAnd != null) { request.put(KEY_HAVING_AND, havingAnd); } + if (sample != null) { + request.put(KEY_SAMPLE, sample); + } + if (latest != null) { + request.put(KEY_LATEST, latest); + } + if (partition != null) { + request.put(KEY_PARTITION, partition); + } + if (fill != null) { + request.put(KEY_FILL, fill); + } if (order != null) { request.put(KEY_ORDER, order); } @@ -5935,12 +6290,12 @@ else if (keyMap != null) { * @return * @throws Exception */ - public static SQLConfig parseJoin(RequestMethod method, SQLConfig config - , List joinList, Callback callback) throws Exception { + public static , L extends List> SQLConfig parseJoin( + RequestMethod method, SQLConfig config, List> joinList, Callback callback) throws Exception { boolean isQuery = RequestMethod.isQueryMethod(method); config.setKeyPrefix(isQuery && config.isMain() == false); - //TODO 解析出 SQLConfig 再合并 column, order, group 等 + //TODO 解析出 SQLConfig 再合并 column, order, group 等 if (joinList == null || joinList.isEmpty() || RequestMethod.isQueryMethod(method) == false) { return config; } @@ -5948,15 +6303,15 @@ public static SQLConfig parseJoin(RequestMethod method, SQ String table; String alias; - for (Join j : joinList) { - table = j.getTable(); - alias = j.getAlias(); + for (Join join : joinList) { + table = join.getTable(); + alias = join.getAlias(); //JOIN子查询不能设置LIMIT,因为ON关系是在子查询后处理的,会导致结果会错误 - SQLConfig joinConfig = newSQLConfig(method, table, alias, j.getRequest(), null, false, callback); - SQLConfig cacheConfig = j.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias - , j.getRequest(), null, false, callback).setCount(j.getCount()); + SQLConfig joinConfig = newSQLConfig(method, table, alias, join.getRequest(), null, false, callback); + SQLConfig cacheConfig = join.canCacheViceTable() == false ? null : newSQLConfig(method, table, alias + , join.getRequest(), null, false, callback).setCount(join.getCount()); - if (j.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 + if (join.isAppJoin() == false) { //除了 @ APP JOIN,其它都是 SQL JOIN,则副表要这样配置 if (joinConfig.getDatabase() == null) { joinConfig.setDatabase(config.getDatabase()); //解决主表 JOIN 副表,引号不一致 } @@ -5972,21 +6327,29 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { cacheConfig.setDatabase(joinConfig.getDatabase()).setSchema(joinConfig.getSchema()); //解决主表 JOIN 副表,引号不一致 } - if (isQuery) { config.setKeyPrefix(true); } joinConfig.setMain(false).setKeyPrefix(true); - if (j.getOuter() != null) { - SQLConfig outerConfig = newSQLConfig(method, table, alias, j.getOuter(), null, false, callback); - outerConfig.setMain(false) + 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 副表,引号不一致 - j.setOuterConfig(outerConfig); + 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); } } @@ -5994,11 +6357,14 @@ else if (joinConfig.getDatabase().equals(config.getDatabase()) == false) { /* SELECT count(*) AS count FROM sys.Moment AS Moment LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.momentId = Moment.id LIMIT 1 OFFSET 0 */ if (RequestMethod.isHeadMethod(method, true)) { - List onList = j.getOnList(); + 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 * 性能更好 } } @@ -6011,8 +6377,8 @@ LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.m } } - j.setJoinConfig(joinConfig); - j.setCacheConfig(cacheConfig); + join.setJoinConfig(joinConfig); + join.setCacheConfig(cacheConfig); } config.setJoinList(joinList); @@ -6030,9 +6396,9 @@ LEFT JOIN ( SELECT count(*) AS count FROM sys.Comment ) AS Comment ON Comment.m * @param saveLogic 保留逻辑运算符 & | ! * @return */ - public static String getRealKey(RequestMethod method, String originKey + public static String gainRealKey(RequestMethod method, String originKey , boolean isTableKey, boolean saveLogic) throws Exception { - return getRealKey(method, originKey, isTableKey, saveLogic, true); + return gainRealKey(method, originKey, isTableKey, saveLogic, true); } /**获取客户端实际需要的key * @param method @@ -6042,11 +6408,11 @@ public static String getRealKey(RequestMethod method, String originKey * @param verifyName 验证key名是否符合代码变量/常量名 * @return */ - public static String getRealKey(RequestMethod method, String originKey + public static String gainRealKey(RequestMethod method, String originKey , boolean isTableKey, boolean saveLogic, boolean verifyName) throws Exception { Log.i(TAG, "getRealKey saveLogic = " + saveLogic + "; originKey = " + originKey); - if (originKey == null || apijson.JSONObject.isArrayKey(originKey)) { - Log.w(TAG, "getRealKey originKey == null || apijson.JSONObject.isArrayKey(originKey) >> return originKey;"); + if (originKey == null || JSONMap.isArrayKey(originKey)) { + Log.w(TAG, "getRealKey originKey == null || apijson.JSONMap.isArrayKey(originKey) >> return originKey;"); return originKey; } @@ -6162,7 +6528,7 @@ else if (key.endsWith("-")) {//缩减,PUT查询时处理 } - public static interface IdCallback { + public static interface IdCallback { /**为 post 请求新建 id, 只能是 Long 或 String * @param method * @param database @@ -6190,22 +6556,22 @@ public static interface IdCallback { String getUserIdKey(String database, String schema, String datasource, String table); } - public static interface Callback extends IdCallback { - /**获取 SQLConfig 的实例 + public static interface Callback, L extends List> extends IdCallback { + /**获取 SQLConfig 的实例 * @param method * @param database * @param schema * @param table * @return */ - SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String datasource, String table); + SQLConfig getSQLConfig(RequestMethod method, String database, String schema, String datasource, String table); /**combine 里的 key 在 request 中 value 为 null 或不存在,即 request 中缺少用来作为 combine 条件的 key: value * @param combine * @param key * @param request */ - void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception; + void onMissingKey4Combine(String name, M request, String combine, String item, String key) throws Exception; } public static Long LAST_ID; @@ -6213,7 +6579,7 @@ public static interface Callback extends IdCallback { LAST_ID = System.currentTimeMillis(); } - public static abstract class SimpleCallback implements Callback { + public static abstract class SimpleCallback, L extends List> implements Callback { @SuppressWarnings("unchecked") @Override @@ -6238,7 +6604,7 @@ public String getUserIdKey(String database, String schema, String datasource, St } @Override - public void onMissingKey4Combine(String name, JSONObject request, String combine, String item, String key) throws Exception { + public void onMissingKey4Combine(String name, M request, String combine, String item, String key) throws Exception { if (ALLOW_MISSING_KEY_4_COMBINE) { return; } @@ -6271,24 +6637,4 @@ private static boolean isKeyInCombineExpr(String combineExpr, String key) { } - public List getWithAsExprSqlList() { - return withAsExprSqlList; - } - private void clearWithAsExprListIfNeed() { - // mysql8版本以上,子查询支持with as表达式 - if(this.isMySQL() && this.getDBVersionNums()[0] >= 8) { - this.withAsExprSqlList = new ArrayList<>(); - } - } - - @Override - public List getWithAsExprPreparedValueList() { - return this.withAsExprPreparedValueList; - } - - @Override - public AbstractSQLConfig setWithAsExprPreparedValueList(List list) { - this.withAsExprPreparedValueList = list; - return this; - } } diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/AbstractSQLExecutor.java index 9393f43be..797ac3dee 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.*/ @@ -8,8 +8,6 @@ import apijson.*; import apijson.orm.Join.On; import apijson.orm.exception.NotExistException; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; import java.io.BufferedReader; import java.math.BigDecimal; @@ -27,20 +25,21 @@ /**executor for query(read) or update(write) MySQL database * @author Lemon */ -public abstract class AbstractSQLExecutor implements SQLExecutor { +public abstract class AbstractSQLExecutor, L extends List> + implements SQLExecutor { private static final String TAG = "AbstractSQLExecutor"; //是否返回 值为null的字段 public static boolean ENABLE_OUTPUT_NULL_COLUMN = false; public static String KEY_RAW_LIST = "@RAW@LIST"; // 避免和字段命名冲突,不用 $RAW@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 public static String KEY_VICE_ITEM = "@VICE@ITEM"; // 避免和字段命名冲突,不用 $VICE@LIST$ 是因为 $ 会在 fastjson 内部转义,浪费性能 - private Parser parser; + private Parser parser; @Override - public Parser getParser() { + public Parser getParser() { return parser; } @Override - public AbstractSQLExecutor setParser(Parser parser) { + public AbstractSQLExecutor setParser(Parser parser) { this.parser = parser; return this; } @@ -78,15 +77,15 @@ public long getSqlResultDuration() { /** * 缓存 Map */ - protected Map> cacheMap = new HashMap<>(); + protected Map> cacheMap = new HashMap<>(); /**保存缓存 * @param sql key * @param list value - * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override - public void putCache(String sql, List list, SQLConfig config) { + public void putCache(String sql, List list, SQLConfig config) { if (sql == null || list == null) { // 空 list 有效,说明查询过 sql 了 || list.isEmpty()) { Log.i(TAG, "saveList sql == null || list == null >> return;"); return; @@ -97,45 +96,43 @@ public void putCache(String sql, List list, SQLConfig config) { /**获取缓存 * @param sql key - * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null */ @Override - public List getCache(String sql, SQLConfig config) { + public List getCache(String sql, SQLConfig config) { return cacheMap.get(sql); } /**获取缓存 * @param sql key * @param position - * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null + * @param config 一般主表 SQLConfig 不为 null,JOIN 副表的为 null * @return */ @Override - public JSONObject getCacheItem(String sql, int position, SQLConfig config) { - List list = getCache(sql, config); + public M getCacheItem(String sql, int position, SQLConfig config) { + List list = getCache(sql, config); return getCacheItem(list, position, config); } - public JSONObject getCacheItem(List list, int position, SQLConfig config) { + public M getCacheItem(List list, int position, SQLConfig config) { // 只要 list 不为 null,则如果 list.get(position) == null,则返回 {} ,避免再次 SQL 查询 if (list == null) { return null; } - JSONObject result = position >= list.size() ? null : list.get(position); - return result != null ? result : new JSONObject(); + M result = position >= list.size() ? null : list.get(position); + return result != null ? result : JSON.createJSONObject(); } - - /**移除缓存 * @param sql key * @param config */ @Override - public void removeCache(String sql, SQLConfig config) { + public void removeCache(String sql, SQLConfig config) { if (sql == null) { Log.i(TAG, "removeList sql == null >> return;"); return; @@ -167,20 +164,23 @@ public ResultSet execute(@NotNull Statement statement, String sql) throws Except * @throws Exception */ @Override - public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) throws Exception { + public M execute(@NotNull SQLConfig config, boolean unknownType) throws Exception { long executedSQLStartTime = System.currentTimeMillis(); - final String sql = config.getSQL(false); + final String sql = config.gainSQL(false); if (StringUtil.isEmpty(sql, true)) { Log.e(TAG, "execute StringUtil.isEmpty(sql, true) >> return null;"); return null; } + Parser parser2 = config.gainParser(); + parser = parser2 != null ? parser2 : getParser();; + boolean isExplain = config.isExplain(); boolean isHead = RequestMethod.isHeadMethod(config.getMethod(), true); final int position = config.getPosition(); - JSONObject result; + M result; if (isExplain == false) { generatedSQLCount ++; @@ -190,14 +190,15 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) thr Log.d(TAG, "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + "\n已生成 " + generatedSQLCount + " 条 SQL" + "\nexecute startTime = " + startTime - + "\ndatabase = " + StringUtil.getString(config.getDatabase()) - + "; schema = " + StringUtil.getString(config.getSchema()) + + "\ndatabase = " + StringUtil.get(config.getDatabase()) + + "; schema = " + StringUtil.get(config.getSchema()) + "; sql = \n" + sql + "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); ResultSet rs = null; - List resultList = null; - Map childMap = null; + List resultList = null; + Map childMap = null; + Map keyMap = null; try { if (unknownType) { @@ -212,7 +213,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) thr executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } - result = new JSONObject(true); + result = JSON.createJSONObject(); result.put(JSONResponse.KEY_COUNT, updateCount); result.put("update", updateCount >= 0); //导致后面 rs.getMetaData() 报错 Operation not allowed after ResultSet closed result.put("moreResults", statement.getMoreResults()); @@ -237,7 +238,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) thr } // updateCount>0时收集结果。例如更新操作成功时,返回count(affected rows)、id字段 - result = AbstractParser.newSuccessResult(); // TODO 对 APIAuto 及其它现有的前端/客户端影响比较大,暂时还是返回 code 和 msg,5.0 再移除 new JSONObject(true); + result = parser.newSuccessResult(); // TODO 对 APIAuto 及其它现有的前端/客户端影响比较大,暂时还是返回 code 和 msg,5.0 再移除 JSON.createJSONObject(); //id,id{}至少一个会有,一定会返回,不用抛异常来阻止关联写操作时前面错误导致后面无条件执行! result.put(JSONResponse.KEY_COUNT, updateCount);//返回修改的记录数 @@ -252,7 +253,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) thr if (method == RequestMethod.PUT || method == RequestMethod.DELETE) { config.setMethod(RequestMethod.GET); - removeCache(config.getSQL(false), config); + removeCache(config.gainSQL(false), config); config.setMethod(method); } @@ -262,7 +263,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) thr case GETS: case HEAD: case HEADS: - List cache = getCache(sql, config); + List cache = getCache(sql, config); result = getCacheItem(cache, position, config); Log.i(TAG, ">>> execute result = getCache('" + sql + "', " + position + ") = " + result); if (result != null) { @@ -293,13 +294,12 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) thr } } - if (isExplain == false && isHead) { if (rs.next() == false) { - return AbstractParser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); + return parser.newErrorResult(new SQLException("数据库错误, rs.next() 失败!")); } - result = AbstractParser.newSuccessResult(); + result = parser.newSuccessResult(); // 兼容nosql,比如 elasticSearch-sql if(config.isElasticsearch()) { result.put(JSONResponse.KEY_COUNT, rs.getObject(1)); @@ -388,11 +388,13 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) thr // WHERE id = ? AND ... 或 WHERE ... AND id = ? 强制排序 remove 再 put,还是重新 getSQL吧 - List joinList = config.getJoinList(); + List> joinList = config.getJoinList(); boolean hasJoin = config.hasJoin() && joinList != null && ! joinList.isEmpty(); // 直接用数组存取更快 Map columnIndexAndJoinMap = isExplain || ! hasJoin ? null : new HashMap<>(length); Join[] columnIndexAndJoinMap = isExplain || ! hasJoin ? null : new Join[length]; + Map repeatMap = columnIndexAndJoinMap == null || ! config.isQuestDB() ? null : new HashMap<>(); + keyMap = repeatMap == null ? null : new HashMap<>(); // int viceColumnStart = length + 1; //第一个副表字段的index @@ -406,9 +408,9 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) thr index ++; Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n execute while (rs.next()){ index = " + index + "\n\n"); - JSONObject item = new JSONObject(true); - JSONObject viceItem = null; - JSONObject curItem = item; + M item = JSON.createJSONObject(); + M viceItem = null; + M curItem = item; boolean isMain = true; boolean reseted = false; @@ -424,57 +426,92 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) thr // 为什么 isExplain == false 不用判断?因为所有字段都在一张 Query Plan 表 if (index <= 0 && columnIndexAndJoinMap != null) { // && viceColumnStart > length) { - SQLConfig curConfig = curJoin == null || ! curJoin.isSQLJoin() ? null : curJoin.getCacheConfig(); + SQLConfig curConfig = curJoin == null || ! curJoin.isSQLJoin() ? null : curJoin.getCacheConfig(); List curColumn = curConfig == null ? null : curConfig.getColumn(); - String sqlTable = curConfig == null ? null : curConfig.getSQLTable(); + String sqlTable = curConfig == null ? null : curConfig.gainSQLTable(); String sqlAlias = curConfig == null ? null : curConfig.getAlias(); List column = config.getColumn(); int mainColumnSize = column == null ? 0 : column.size(); - // FIXME 主副表同名导致主表数据当成副表数据 { "[]": { "join": "": 0 }, "Comment:to": { "@column": "id,content", "id@": "/Comment/toId" } }, "@explain": true } boolean toFindJoin = mainColumnSize <= 0 || i > mainColumnSize; // 主表就不用找 JOIN 配置 if (StringUtil.isEmpty(sqlTable, true)) { + //sqlTable = null; + if (toFindJoin) { // 在主表字段数量内的都归属主表 long startTime3 = System.currentTimeMillis(); sqlTable = rsmd.getTableName(i); // SQL 函数甚至部分字段都不返回表名,当然如果没传 @column 生成的 Table.* 则返回的所有字段都会带表名 //if (StringUtil.isEmpty(sqlTable, true)) { - // boolean isEmpty = curItem == null || curItem.isEmpty(); - String label = getKey(config, rs, rsmd, index, curItem, i, childMap); - if (i > 1 && ( (curItem != null && curItem.containsKey(label)) - || (StringUtil.isNotEmpty(label) && StringUtil.equals(label, curConfig == null ? null : curConfig.getIdKey()))) + // boolean isEmpty = curItem == null || curItem.isEmpty(); + String key = getKey(config, rs, rsmd, index, curItem, i, childMap, keyMap); + char last = repeatMap == null ? 0 : key.charAt(key.length() - 1); + String repeatKey = last < '0' || last > '9' ? null : key.substring(0, key.length() - 1); + Integer repeatCount = repeatKey == null ? null : repeatMap.get(repeatKey); + int nc = repeatCount == null ? 1 : repeatCount + 1; + if (last == nc + '0') { + keyMap.put(key, repeatKey); + repeatMap.put(repeatKey, nc); + key = repeatKey; // QuestDB 会自动把副表与主表同名的字段重命名,例如 id 改为 id1, date 改为 date1 + } + + if (i > 1 && ( (curItem != null && curItem.containsKey(key)) + || (StringUtil.isNotEmpty(key) && StringUtil.equals(key, curConfig == null ? null : curConfig.getIdKey()))) ) { // Presto 等引擎 JDBC 返回 rsmd.getTableName(i) 为空,主表如果一个字段都没有会导致 APISJON 主副表所有字段都不返回 sqlTable = null; if (reseted) { - lastViceTableStart ++; - - SQLConfig lastCfg = lastJoin == null ? null : lastJoin.getCacheConfig(); + SQLConfig lastCfg = lastJoin == null ? null : lastJoin.getCacheConfig(); List lastColumn = lastCfg == null ? null : lastCfg.getColumn(); + + lastViceTableStart ++; lastViceColumnStart += lastColumn == null ? 1 : lastColumn.size(); } + else if (isMain) { + for (int j = 0; j < joinList.size(); j++) { + Join join = joinList.get(j); + SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig(); + List c = cfg == null ? null : cfg.getColumn(); + + if (cfg != null) { + sqlTable = cfg.gainSQLTable(); + sqlAlias = cfg.getAlias(); + lastViceTableStart = j; // 避免后面的空 @column 表内字段被放到之前的空 @column 表 + lastViceColumnStart = i + 1; + + curJoin = join; + curConfig = cfg; + curColumn = c; + + toFindJoin = false; + isMain = false; + break; + } + } + } + reseted = true; } //} sqlResultDuration += System.currentTimeMillis() - startTime3; - if (StringUtil.isEmpty(sqlTable, true)) { // hasJoin 已包含这个判断 && joinList != null) { + if (toFindJoin && StringUtil.isEmpty(sqlTable, true)) { // hasJoin 已包含这个判断 && joinList != null) { + //sqlTable = null; // QuestDB 等 rsmd.getTableName(i) 返回 "" 导致以下 StringUtil.equalsIgnoreCase 对比失败 int nextViceColumnStart = lastViceColumnStart; // 主表没有 @column 时会偏小 lastViceColumnStart int joinCount = joinList.size(); for (int j = lastViceTableStart; j < joinCount; j++) { // 查找副表 @column,定位字段所在表 - Join join = joinList.get(j); - SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig(); + Join join = joinList.get(j); + SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig(); List c = cfg == null ? null : cfg.getColumn(); nextViceColumnStart += (c != null && ! c.isEmpty() ? c.size() : ( - StringUtil.equalsIgnoreCase(sqlTable, lastTableName) + StringUtil.equalsIgnoreCase(sqlTable, lastTableName) && StringUtil.equals(sqlAlias, lastAliasName) ? 1 : 0 - ) - ); - if (i < nextViceColumnStart || j >= joinCount - 1) { - sqlTable = cfg.getSQLTable(); + ) + ); + if (i < nextViceColumnStart) { // 导致只 JOIN 一张副表时主表数据放到副表 || j >= joinCount - 1) { + sqlTable = cfg.gainSQLTable(); sqlAlias = cfg.getAlias(); lastViceTableStart = j; // 避免后面的空 @column 表内字段被放到之前的空 @column 表 @@ -496,8 +533,7 @@ public JSONObject execute(@NotNull SQLConfig config, boolean unknownType) thr toFindJoin = false; } } - } - else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWith("\""))){ + } else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWith("\""))){ sqlTable = sqlTable.substring(1, sqlTable.length() - 1); } @@ -509,10 +545,10 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi if (toFindJoin) { // 找到对应的副表 JOIN 配置 for (int j = lastViceTableStart; j < joinList.size(); j++) { // 查找副表 @column,定位字段所在表 Join join = joinList.get(j); - SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig(); + SQLConfig cfg = join == null || ! join.isSQLJoin() ? null : join.getJoinConfig(); - if (cfg != null && StringUtil.equalsIgnoreCase(sqlTable, cfg.getSQLTable()) - ) { // FIXME 导致副表字段错放到主表 && StringUtil.equals(sqlAlias, cfg.getAlias())) { + if (cfg != null && StringUtil.equalsIgnoreCase(sqlTable, cfg.gainSQLTable()) + ) { // FIXME 导致副表字段错放到主表 && StringUtil.equals(sqlAlias, cfg.getAlias())) { lastViceTableStart = j; // 避免后面的空 @column 表内字段被放到之前的空 @column 表 curJoin = join; @@ -554,14 +590,14 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi // 如果是主表则直接用主表对应的 item,否则缓存副表数据到 childMap Join prevJoin = columnIndexAndJoinMap == null || i < 2 ? null : columnIndexAndJoinMap[i - 2]; if (curJoin != prevJoin) { // 前后字段不在同一个表对象,即便后面出现 null,也不该是主表数据,而是逻辑 bug 导致 - SQLConfig viceConfig = curJoin != null && curJoin.isSQLJoin() ? curJoin.getCacheConfig() : null; + SQLConfig viceConfig = curJoin != null && curJoin.isSQLJoin() ? curJoin.getCacheConfig() : null; boolean hasPK = false; if (viceConfig != null) { //FIXME 只有和主表关联才能用 item,否则应该从 childMap 查其它副表数据 List onList = curJoin.getOnList(); int size = onList == null ? 0 : onList.size(); if (size > 0) { String idKey = viceConfig.getIdKey(); - String tblKey = config.getTableKey(); + String tblKey = config.gainTableKey(); for (int j = size - 1; j >= 0; j--) { On on = onList.get(j); String ok = on == null ? null : on.getOriginKey(); @@ -572,7 +608,7 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi String k = ok.substring(0, ok.length() - 1); String ttk = on.getTargetTableKey(); - JSONObject target = StringUtil.equals(ttk, tblKey) ? item : (viceItem == null ? null : viceItem.getJSONObject(ttk)); + M target = StringUtil.equals(ttk, tblKey) ? item : (viceItem == null ? null : JSON.get(viceItem, ttk)); Object v = target == null ? null : target.get(on.getTargetKey()); hasPK = hasPK || (k.equals(idKey) && v != null); @@ -588,21 +624,21 @@ else if (config.isClickHouse() && (sqlTable.startsWith("`") || sqlTable.startsWi else if (curJoin.isOuterJoin() || curJoin.isAntiJoin()) { Log.i(TAG, "execute curJoin.isOuterJoin() || curJoin.isAntiJoin() >> item = null; >> "); curItem = null; // 肯定没有数据,缓存也无意义 - // 副表是按常规条件查询,缓存会导致其它同表同条件对象查询结果集为空 childMap.put(viceSql, new JSONObject()); // 缓存固定空数据,避免后续多余查询 + // 副表是按常规条件查询,缓存会导致其它同表同条件对象查询结果集为空 childMap.put(viceSql, JSON.createJSONObject()); // 缓存固定空数据,避免后续多余查询 } else { - String viceName = viceConfig.getTableKey(); + String viceName = viceConfig.gainTableKey(); if (viceItem == null) { - viceItem = new JSONObject(true); + viceItem = JSON.createJSONObject(); } - curItem = viceItem.getJSONObject(viceName); + curItem = JSON.get(viceItem, viceName); - String viceSql = hasPK ? viceConfig.getSQL(false) : null; // TODO 在 SQLConfig 缓存 SQL,减少大量的重复生成 - JSONObject curCache = hasPK ? childMap.get(viceSql) : null; + String viceSql = hasPK ? viceConfig.gainSQL(false) : null; // TODO 在 SQLConfig 缓存 SQL,减少大量的重复生成 + M curCache = hasPK ? childMap.get(viceSql) : null; if (curItem == null || curItem.isEmpty()) { - // 导致前面判断重复 key 出错 curItem = curCache != null ? curCache : new JSONObject(true); - curItem = new JSONObject(true); + // 导致前面判断重复 key 出错 curItem = curCache != null ? curCache : JSON.createJSONObject(); + curItem = JSON.createJSONObject(); viceItem.put(viceName, curItem); if (hasPK && curCache == null) { childMap.put(viceSql, curItem); @@ -621,7 +657,7 @@ else if (hasPK) { } } - curItem = onPutColumn(config, rs, rsmd, index, curItem, i, curJoin, childMap); // isExplain == false && hasJoin && i >= viceColumnStart ? childMap : null); + curItem = (M) onPutColumn(config, rs, rsmd, index, curItem, i, curJoin, childMap, keyMap); // isExplain == false && hasJoin && i >= viceColumnStart ? childMap : null); } if (viceItem != null) { @@ -652,10 +688,10 @@ else if (hasPK) { if (unknownType || isExplain) { if (isExplain) { if (result == null) { - result = new JSONObject(true); + result = JSON.createJSONObject(); } config.setExplain(false); - result.put("sql", config.getSQL(false)); + result.put("sql", config.gainSQL(false)); config.setExplain(isExplain); } result.put("list", resultList); @@ -669,31 +705,31 @@ else if (hasPK) { if (isHead == false) { // @ APP JOIN 查询副表并缓存到 childMap <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - Map> appJoinChildMap = new HashMap<>(); + Map> appJoinChildMap = new HashMap<>(); childMap.forEach((viceSql, item) -> appJoinChildMap.put(viceSql, Arrays.asList(item))); - executeAppJoin(config, resultList, appJoinChildMap); + executeAppJoin(config, resultList, appJoinChildMap, keyMap); // @ APP JOIN 查询副表并缓存到 childMap >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //子查询 SELECT Moment.*, Comment.id 中的 Comment 内字段 - Set>> set = appJoinChildMap.entrySet(); + Set>> set = appJoinChildMap.entrySet(); // - for (Entry> entry : set) { + for (Entry> entry : set) { putCache(entry.getKey(), entry.getValue(), null); } Log.i(TAG, ">>> execute putCache('" + sql + "', resultList); resultList.size() = " + resultList.size()); - // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 + // 数组主表对象额外一次返回全部,方便 Parser 缓存来提高性能 - result = position >= resultList.size() ? new JSONObject() : resultList.get(position); + result = position >= resultList.size() ? JSON.createJSONObject() : resultList.get(position); if (position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false) { // 不是 main 不会直接执行,count=1 返回的不会超过 1 && config.isMain() && config.getCount() != 1 Log.i(TAG, ">>> execute position == 0 && resultList.size() > 1 && result != null && result.isEmpty() == false" - + " >> result = new JSONObject(result); result.put(KEY_RAW_LIST, resultList);"); + + " >> result = JSON.createJSONObject(result); result.put(KEY_RAW_LIST, resultList);"); - result = new JSONObject(result); + result = (M) JSON.createJSONObject(result); result.put(KEY_RAW_LIST, resultList); } } @@ -713,17 +749,17 @@ else if (hasPK) { * @param childMap * @throws Exception */ - protected void executeAppJoin(SQLConfig config, List resultList, Map> childMap) throws Exception { - List joinList = config.getJoinList(); + protected void executeAppJoin(SQLConfig config, List resultList, Map> childMap, Map keyMap) throws Exception { + List> joinList = config.getJoinList(); if (joinList != null) { - for (Join join : joinList) { + for (Join join : joinList) { if (join.isAppJoin() == false) { Log.i(TAG, "executeAppJoin for (Join j : joinList) >> j.isAppJoin() == false >> continue;"); continue; } - SQLConfig cc = join.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好 + SQLConfig cc = join.getCacheConfig(); //这里用config改了getSQL后再还原很麻烦,所以提前给一个config2更好 if (cc == null) { if (Log.DEBUG) { throw new NullPointerException("服务器内部错误, executeAppJoin cc == null ! 导致不能缓存 @ APP JOIN 的副表数据!"); @@ -731,33 +767,33 @@ protected void executeAppJoin(SQLConfig config, List resultList, continue; } - SQLConfig jc = join.getJoinConfig(); + SQLConfig jc = join.getJoinConfig(); List onList = join.getOnList(); On on = onList == null || onList.isEmpty() ? null : onList.get(0); // APP JOIN 应该有且只有一个 ON 条件 String originKey = on == null ? null : on.getOriginKey(); if (originKey == null) { - throw new NullPointerException("服务器内部错误,List 中 Join.onList[0" + (on == null ? "] = null!" : ".getOriginKey() = null!")); + throw new NullPointerException("服务器内部错误,List 中 Join.onList[0" + (on == null ? "] = null!" : ".getOriginKey() = null!")); } String key = on.getKey(); if (key == null) { - throw new NullPointerException("服务器内部错误,List 中 Join.onList[0" + (on == null ? "] = null!" : ".getKey() = null!")); + throw new NullPointerException("服务器内部错误,List 中 Join.onList[0" + (on == null ? "] = null!" : ".getKey() = null!")); } // 取出 "id@": "@/User/userId" 中所有 userId 的值 List targetValueList = new ArrayList<>(); for (int i = 0; i < resultList.size(); i++) { - JSONObject mainTable = resultList.get(i); - Object targetValue = mainTable == null ? null : mainTable.get(on.getTargetKey()); + M mainTable = resultList.get(i); + Object targetValue = mainTable == null ? null : mainTable.get(on.getTargetKey()); - if (targetValue != null && targetValueList.contains(targetValue) == false) { - targetValueList.add(targetValue); - } + if (targetValue != null && targetValueList.contains(targetValue) == false) { + targetValueList.add(targetValue); + } } if (targetValueList.isEmpty() && config.isExplain() == false) { - throw new NotExistException("targetValueList.isEmpty() && config.isExplain() == false"); + throw new NotExistException("targetValueList.isEmpty() && config.isExplain() == false"); } // 替换为 "id{}": [userId1, userId2, userId3...] @@ -788,46 +824,46 @@ protected void executeAppJoin(SQLConfig config, List resultList, // } boolean prepared = jc.isPrepared(); - String sql = jc.getSQL(false); + String sql = jc.gainSQL(false); if (StringUtil.isEmpty(sql, true)) { throw new NullPointerException(TAG + ".executeAppJoin StringUtil.isEmpty(sql, true) >> return null;"); } String sql2 = null; - if (childCount > 0 && isOne2Many && (jc.isMySQL() == false || jc.getDBVersionNums()[0] >= 8)) { - // 加 row_number 字段并不会导致 count 等聚合函数统计出错,结果偏大,SQL JOIN 才会,之前没发现是因为缓存失效 bug - // boolean noAggrFun = true; - // List column = jc.getColumn(); - // if (column != null) { - // for (String c : column) { - // int start = c == null ? -1 : c.indexOf("("); - // int end = start <= 0 ? -1 : c.lastIndexOf(")"); - // if (start > 0 && end > start) { - // String fun = c.substring(0, start); - // if (AbstractSQLConfig.SQL_AGGREGATE_FUNCTION_MAP.containsKey(fun)) { - // noAggrFun = false; - // break; - // } - // } - // } - // } - // - // if (noAggrFun) { // 加 row_number 字段会导致 count 等聚合函数统计出错,结果偏大? - String q = jc.getQuote(); - sql2 = prepared && jc.isTDengine() == false ? jc.getSQL(true) : sql; - - String prefix = "SELECT * FROM("; - String rnStr = ", row_number() OVER (PARTITION BY " + q + key + q + ((AbstractSQLConfig) jc).getOrderString(true) + ") _row_num_ FROM "; - String suffix = ") _t WHERE ( (_row_num_ <= " + childCount + ") )" + (allChildCount > 0 ? " LIMIT " + allChildCount : ""); - - sql2 = prefix - // 放一块逻辑更清晰,也避免解析 * 等不支持或性能开销 + sql - + sql2.replaceFirst(" FROM ", rnStr) // * 居然只能放在 row_number() 前面,放后面就报错 "SELECT ", rnStr) - + suffix; - - sql = prepared ? (prefix + sql.replaceFirst(" FROM ", rnStr) + suffix) : sql2; - // } + if (childCount > 0 && isOne2Many && (jc.isMySQL() == false || jc.gainDBVersionNums()[0] >= 8)) { + // 加 row_number 字段并不会导致 count 等聚合函数统计出错,结果偏大,SQL JOIN 才会,之前没发现是因为缓存失效 bug + // boolean noAggrFun = true; + // List column = jc.getColumn(); + // if (column != null) { + // for (String c : column) { + // int start = c == null ? -1 : c.indexOf("("); + // int end = start <= 0 ? -1 : c.lastIndexOf(")"); + // if (start > 0 && end > start) { + // String fun = c.substring(0, start); + // if (AbstractSQLConfig.SQL_AGGREGATE_FUNCTION_MAP.containsKey(fun)) { + // noAggrFun = false; + // break; + // } + // } + // } + // } + // + // if (noAggrFun) { // 加 row_number 字段会导致 count 等聚合函数统计出错,结果偏大? + String q = jc.getQuote(); + sql2 = prepared && jc.isTDengine() == false ? jc.gainSQL(true) : sql; + + String prefix = "SELECT * FROM("; + String rnStr = ", row_number() OVER (PARTITION BY " + q + key + q + ((AbstractSQLConfig) jc).gainOrderString(true) + ") _row_num_ FROM "; + String suffix = ") _t WHERE ( (_row_num_ <= " + childCount + ") )" + (allChildCount > 0 ? " LIMIT " + allChildCount : ""); + + sql2 = prefix + // 放一块逻辑更清晰,也避免解析 * 等不支持或性能开销 + sql + + sql2.replaceFirst(" FROM ", rnStr) // * 居然只能放在 row_number() 前面,放后面就报错 "SELECT ", rnStr) + + suffix; + + sql = prepared ? (prefix + sql.replaceFirst(" FROM ", rnStr) + suffix) : sql2; + // } } boolean isExplain = jc.isExplain(); @@ -846,12 +882,12 @@ protected void executeAppJoin(SQLConfig config, List resultList, try { long executedSQLStartTime = 0; if (isExplain == false) { //只有 SELECT 才能 EXPLAIN - executedSQLCount ++; - executedSQLStartTime = System.currentTimeMillis(); + executedSQLCount ++; + executedSQLStartTime = System.currentTimeMillis(); } rs = executeQuery(jc, sql2); if (isExplain == false) { - executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; + executedSQLDuration += System.currentTimeMillis() - executedSQLStartTime; } int count = 0; @@ -873,10 +909,10 @@ protected void executeAppJoin(SQLConfig config, List resultList, index ++; Log.d(TAG, "\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n executeAppJoin while (rs.next()){ index = " + index + "\n\n"); - JSONObject result = new JSONObject(true); + M result = JSON.createJSONObject(); for (int i = 1; i <= length; i++) { - result = onPutColumn(jc, rs, rsmd, index, result, i, null, null); + result = onPutColumn(jc, rs, rsmd, index, result, i, null, null, keyMap); } //每个 result 都要用新的 SQL 来存 childResultMap = onPutTable(config, rs, rsmd, childResultMap, index, result); @@ -886,24 +922,24 @@ protected void executeAppJoin(SQLConfig config, List resultList, //TODO 兼容复杂关联 cc.putWhere(key, result.get(key), true); // APP JOIN 应该有且只有一个 ON 条件 - String cacheSql = cc.getSQL(false); - List results = childMap.get(cacheSql); + String cacheSql = cc.gainSQL(false); + List results = childMap.get(cacheSql); if (results == null || skipMap.get(cacheSql) == null) { // 避免添加重复数据 - results = new ArrayList<>(childCount); - childMap.put(cacheSql, results); - skipMap.put(cacheSql, Boolean.TRUE); + results = new ArrayList<>(childCount); + childMap.put(cacheSql, results); + skipMap.put(cacheSql, Boolean.TRUE); } if (childCount <= 0 || results.size() < childCount) { // 避免超过子数组每页数量 - // if (count == 1 && results.isEmpty() == false) { // 避免添加重复数据 - // results.clear(); - // } - results.add(result); //缓存到 childMap - count ++; - Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size()); + // if (count == 1 && results.isEmpty() == false) { // 避免添加重复数据 + // results.clear(); + // } + results.add(result); //缓存到 childMap + count ++; + Log.d(TAG, ">>> executeAppJoin childMap.put('" + cacheSql + "', result); childMap.size() = " + childMap.size()); } - } + } } finally { if (rs != null) { @@ -931,37 +967,32 @@ protected void executeAppJoin(SQLConfig config, List resultList, * @param config * @param rs * @param rsmd - * @param tablePosition 从0开始 + * @param row 从0开始 * @param table * @param columnIndex 从1开始 * @param childMap * @return result * @throws Exception */ - protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd - , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Join join, Map childMap) throws Exception { + protected M onPutColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd + , final int row, @NotNull M table, final int columnIndex, Join join, Map childMap + , Map keyMap) throws Exception { if (table == null) { // 对应副表 viceSql 不能生成正常 SQL, 或者是 ! - Outer, ( - ANTI JOIN 的副表这种不需要缓存及返回的数据 Log.i(TAG, "onPutColumn table == null >> return table;"); return table; } - if (isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap)) { - Log.i(TAG, "onPutColumn isHideColumn(config, rs, rsmd, tablePosition, table, columnIndex, childMap) >> return table;"); + if (isHideColumn(config, rs, rsmd, row, table, columnIndex, childMap, keyMap)) { + Log.i(TAG, "onPutColumn isHideColumn(config, rs, rsmd, row, table, columnIndex, childMap) >> return table;"); return table; } - String label = getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap); - Object value = getValue(config, rs, rsmd, tablePosition, table, columnIndex, label, childMap); + String label = getKey(config, rs, rsmd, row, table, columnIndex, childMap, keyMap); + Object value = getValue(config, rs, rsmd, row, table, columnIndex, label, childMap, keyMap); // 主表必须 put 至少一个 null 进去,否则全部字段为 null 都不 put 会导致中断后续正常返回值 - if (value != null) { + if (value != null || ENABLE_OUTPUT_NULL_COLUMN || (join == null && table.isEmpty())) { table.put(label, value); - } else { - if (join == null && table.isEmpty()) { - table.put(label, null); - } else if (ENABLE_OUTPUT_NULL_COLUMN) { - table.put(label, null); - } } return table; @@ -971,15 +1002,16 @@ protected JSONObject onPutColumn(@NotNull SQLConfig config, @NotNull ResultSe * @param config * @param rs * @param rsmd - * @param tablePosition + * @param row * @param table * @param columnIndex * @param childMap * @return * @throws SQLException */ - protected boolean isHideColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd - , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws SQLException { + protected boolean isHideColumn(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd + , final int row, @NotNull M table, final int columnIndex, Map childMap + , Map keyMap) throws SQLException { return rsmd.getColumnName(columnIndex).startsWith("_"); } @@ -992,19 +1024,18 @@ protected boolean isHideColumn(@NotNull SQLConfig config, @NotNull ResultSet * @param table * @return resultList */ - protected List onPutTable(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd - , @NotNull List resultList, int position, @NotNull JSONObject table) { + protected List onPutTable(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd + , @NotNull List resultList, int position, @NotNull M table) { resultList.add(table); return resultList; } - - - protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd - , final int tablePosition, @NotNull JSONObject table, final int columnIndex, Map childMap) throws Exception { + protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd + , final int row, @NotNull M table, final int columnIndex, Map childMap + , Map keyMap) throws Exception { long startTime = System.currentTimeMillis(); - String key = rsmd.getColumnLabel(columnIndex); // dotIndex < 0 ? lable : lable.substring(dotIndex + 1); + String key = rsmd.getColumnLabel(columnIndex); // dotIndex < 0 ? label : label.substring(dotIndex + 1); sqlResultDuration += System.currentTimeMillis() - startTime; if (config.isHive()) { @@ -1018,18 +1049,26 @@ protected String getKey(@NotNull SQLConfig config, @NotNull ResultSet rs, @No } } + if (keyMap != null && ! keyMap.isEmpty()) { + String nk = keyMap.get(key); + if (StringUtil.isNotEmpty(nk, true)) { + key = nk; // QuestDB 会自动把副表与主表同名的字段重命名,例如 id 改为 id1, date 改为 date1 + } + } + return key; } - protected Object getValue(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd - , final int tablePosition, @NotNull JSONObject table, final int columnIndex, String lable, Map childMap) throws Exception { + protected Object getValue(@NotNull SQLConfig config, @NotNull ResultSet rs, @NotNull ResultSetMetaData rsmd + , final int row, @NotNull M table, final int columnIndex, String label + , Map childMap, Map keyMap) throws Exception { long startTime = System.currentTimeMillis(); Object value = rs.getObject(columnIndex); sqlResultDuration += System.currentTimeMillis() - startTime; // Log.d(TAG, "name:" + rsmd.getColumnName(i)); - // Log.d(TAG, "lable:" + rsmd.getColumnLabel(i)); + // Log.d(TAG, "label:" + rsmd.getColumnLabel(i)); // Log.d(TAG, "type:" + rsmd.getColumnType(i)); // Log.d(TAG, "typeName:" + rsmd.getColumnTypeName(i)); @@ -1063,7 +1102,7 @@ else if (value instanceof Month) { else if (value instanceof DayOfWeek) { value = ((DayOfWeek) value).getValue(); } - else if (value instanceof String && isJSONType(config, rsmd, columnIndex, lable)) { //json String + else if (value instanceof String && isJSONType(config, rsmd, columnIndex, label)) { //json String castToJson = true; } else if (value instanceof Blob) { //FIXME 存的是 abcde,取出来直接就是 [97, 98, 99, 100, 101] 这种 byte[] 类型,没有经过以下处理,但最终序列化后又变成了字符串 YWJjZGU= @@ -1092,13 +1131,13 @@ else if (value instanceof Clob) { //SQL Server TEXT 类型 居然走这个 if (castToJson == false) { List json = config.getJson(); - castToJson = json != null && json.contains(lable); + castToJson = json != null && json.contains(label); } if (castToJson) { try { - value = JSON.parse((String) value); + value = JSON.parse(value); } catch (Exception e) { - Log.e(TAG, "getValue try { value = JSON.parse((String) value); } catch (Exception e) { \n" + e.getMessage()); + Log.e(TAG, "getValue try { value = parseJSON((String) value); } catch (Exception e) { \n" + e.getMessage()); } } @@ -1133,13 +1172,13 @@ public Object getNumVal(Number value) { /**判断是否为JSON类型 * @param config - * @param lable + * @param label * @param rsmd * @param position * @return */ @Override - public boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData rsmd, int position, String lable) { + public boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData rsmd, int position, String label) { try { long startTime = System.currentTimeMillis(); String column = rsmd.getColumnTypeName(position); @@ -1158,46 +1197,51 @@ public boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData rsmd, e.printStackTrace(); } // List json = config.getJson(); - // return json != null && json.contains(lable); + // return json != null && json.contains(label); return false; } - @Override // 重写是为了返回类型从 Statement 改为 PreparedStatement,避免其它方法出错 - public PreparedStatement getStatement(@NotNull SQLConfig config) throws Exception { + public PreparedStatement getStatement(@NotNull SQLConfig config) throws Exception { return getStatement(config, null); } + @Override - public PreparedStatement getStatement(@NotNull SQLConfig config, String sql) throws Exception { + public PreparedStatement getStatement(@NotNull SQLConfig config, String sql) throws Exception { if (StringUtil.isEmpty(sql)) { - sql = config.getSQL(config.isPrepared()); + 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)) { - //if (config.isPresto() || config.isTrino()) { + // if (config.isPresto() || config.isTrino()) { // statement = getConnection(config).prepareStatement(sql); // , ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); - //} else { + // } else { // statement = getConnection(config).prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); - //} - if (config.isMySQL() || config.isPostgreSQL() || config.isCockroachDB() || config.isOracle() || config.isSQLServer() || config.isDb2()) { - statement = getConnection(config).prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); + // } + + // TODO 补充各种支持 TYPE_SCROLL_SENSITIVE 和 CONCUR_UPDATABLE 的数据库 + if (config.isMySQL() || config.isTiDB() || config.isMariaDB() || config.isOracle() || config.isSQLServer() || config.isDb2() + || config.isPostgreSQL() || config.isCockroachDB() || config.isOpenGauss() || config.isTimescaleDB() || config.isQuestDB() + ) { + 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; @@ -1221,9 +1265,9 @@ else if (RequestMethod.isGetMethod(config.getMethod(), true)) { return statement; } - public PreparedStatement setArgument(@NotNull SQLConfig config, @NotNull PreparedStatement statement, int index, Object value) throws SQLException { - //JSON.isBooleanOrNumberOrString(v) 解决 PostgreSQL: Can't infer the SQL type to use for an instance of com.alibaba.fastjson.JSONArray - if (apijson.JSON.isBooleanOrNumberOrString(value)) { + public PreparedStatement setArgument(@NotNull SQLConfig config, @NotNull PreparedStatement statement, int index, Object value) throws SQLException { + //JSON.isBooleanOrNumberOrString(v) 解决 PostgreSQL: Can't infer the SQL type to use for an instance of com.alibaba.fastjson.JSONList + if (apijson.JSON.isBoolOrNumOrStr(value)) { statement.setObject(index + 1, value); //PostgreSQL JDBC 不支持隐式类型转换 tinyint = varchar 报错 } else { @@ -1233,17 +1277,32 @@ public PreparedStatement setArgument(@NotNull SQLConfig config, @NotNull Prep } protected Map connectionMap = new HashMap<>(); + public Map getConnectionMap() { + if (connectionMap == null) { + connectionMap = new HashMap<>(); + } + return connectionMap; + } + protected Connection connection; + @NotNull + public Connection getConnection(String key) throws Exception { + return getConnectionMap().get(key); + } + public Connection putConnection(String key, Connection connection) throws Exception { + return getConnectionMap().put(key, connection); + } + @NotNull @Override - public Connection getConnection(@NotNull SQLConfig config) throws Exception { + public Connection getConnection(@NotNull SQLConfig config) throws Exception { String connectionKey = getConnectionKey(config); - connection = connectionMap.get(connectionKey); + connection = getConnection(connectionKey); if (connection == null || connection.isClosed()) { Log.i(TAG, "select connection " + (connection == null ? " = null" : ("isClosed = " + connection.isClosed()))) ; // PostgreSQL 不允许 cross-database - connection = DriverManager.getConnection(config.getDBUri(), config.getDBAccount(), config.getDBPassword()); - connectionMap.put(connectionKey, connection); + connection = DriverManager.getConnection(config.gainDBUri(), config.gainDBAccount(), config.gainDBPassword()); + putConnection(connectionKey, connection); } // TDengine 驱动内部事务处理方法都是空实现,手动 commit 无效 @@ -1255,8 +1314,8 @@ public Connection getConnection(@NotNull SQLConfig config) throws Exception { return connection; } - public String getConnectionKey(@NotNull SQLConfig config) { - return getConnectionKey(config.getNamespace(), config.getCatalog(), config.getDatasource(), config.getDatabase()); + public String getConnectionKey(@NotNull SQLConfig config) { + 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; @@ -1283,7 +1342,7 @@ public void begin(int transactionIsolation) throws SQLException { // } // 将所有连接设置隔离级别,且禁止自动提交,需要以下代码来 commit/rollback - Collection connections = connectionMap.values(); + Collection connections = connectionMap == null ? null : connectionMap.values(); if (connections != null) { for (Connection connection : connections) { try { @@ -1313,7 +1372,7 @@ public void rollback() throws SQLException { // } // 将所有连接进行回滚 - Collection connections = connectionMap.values(); + Collection connections = connectionMap == null ? null : connectionMap.values(); if (connections != null) { for (Connection connection : connections) { try { @@ -1345,7 +1404,7 @@ public void rollback(Savepoint savepoint) throws SQLException { // } // 将所有连接进行回滚 - Collection connections = connectionMap.values(); + Collection connections = connectionMap == null ? null : connectionMap.values(); if (connections != null) { for (Connection connection : connections) { try { @@ -1372,7 +1431,7 @@ public void commit() throws SQLException { // } // 将所有连接进行提交 - Collection connections = connectionMap.values(); + Collection connections = connectionMap == null ? null : connectionMap.values(); if (connections != null) { for (Connection connection : connections) { try { @@ -1401,7 +1460,7 @@ public void close() { cachedSQLCount = 0; executedSQLCount = 0; - if (connectionMap == null) { + if (connectionMap == null || connectionMap.isEmpty()) { return; } @@ -1424,7 +1483,7 @@ public void close() { } @Override - public ResultSet executeQuery(@NotNull SQLConfig config, String sql) throws Exception { + public ResultSet executeQuery(@NotNull SQLConfig config, String sql) throws Exception { if (config.isPrepared() == false || config.isTDengine() // TDengine JDBC 不支持 PreparedStatement || (config.isExplain() && (config.isPresto() || config.isTrino()))) { // Presto JDBC 0.277 在 EXPLAIN 模式下预编译值不会替代 ? 占位导致报错 @@ -1434,7 +1493,7 @@ public ResultSet executeQuery(@NotNull SQLConfig config, String sql) throws E // ? conn.createStatement() // fix Presto: ResultSet: Exception: set type is TYPE_FORWARD_ONLY, Result set concurrency must be CONCUR_READ_ONLY // : conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); - return executeQuery(stt, StringUtil.isEmpty(sql) ? config.getSQL(false) : sql); + return executeQuery(stt, StringUtil.isEmpty(sql) ? config.gainSQL(false) : sql); } // Presto JDBC 0.277 在 EXPLAIN 模式下预编译值不会替代 ? 占位导致报错 @@ -1449,7 +1508,7 @@ public ResultSet executeQuery(@NotNull SQLConfig config, String sql) throws E @Override - public int executeUpdate(@NotNull SQLConfig config, String sql) throws Exception { + public int executeUpdate(@NotNull SQLConfig config, String sql) throws Exception { Statement stt; int count; if (config.isTDengine()) { @@ -1459,7 +1518,7 @@ public int executeUpdate(@NotNull SQLConfig config, String sql) throws Except // ? conn.createStatement() // fix Presto: ResultSet: Exception: set type is TYPE_FORWARD_ONLY, Result set concurrency must be CONCUR_READ_ONLY // : conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); - count = stt.executeUpdate(StringUtil.isEmpty(sql) ? config.getSQL(false) : sql); + count = stt.executeUpdate(StringUtil.isEmpty(sql) ? config.gainSQL(false) : sql); } else { stt = getStatement(config); @@ -1482,3 +1541,4 @@ public int executeUpdate(@NotNull SQLConfig config, String sql) throws Except } + diff --git a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java index b2eca8bc8..55eeff408 100755 --- a/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/AbstractVerifier.java @@ -1,10 +1,12 @@ -/*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 static apijson.JSON.*; +import static apijson.JSONMap.*; import static apijson.RequestMethod.DELETE; import static apijson.RequestMethod.GET; import static apijson.RequestMethod.GETS; @@ -34,18 +36,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import apijson.orm.script.JavaScriptExecutor; -import apijson.orm.script.ScriptExecutor; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import apijson.JSON; -import apijson.JSONResponse; -import apijson.Log; -import apijson.MethodAccess; -import apijson.NotNull; -import apijson.RequestMethod; -import apijson.StringUtil; +import apijson.*; + import apijson.orm.AbstractSQLConfig.IdCallback; import apijson.orm.exception.ConflictException; import apijson.orm.exception.NotLoggedInException; @@ -69,7 +61,6 @@ import apijson.orm.model.TestRecord; import javax.script.ScriptEngine; -import javax.script.ScriptEngineFactory; import javax.script.ScriptEngineManager; /**校验器(权限、请求参数、返回结果等) @@ -77,7 +68,8 @@ * @author Lemon * @param id 与 userId 的类型,一般为 Long */ -public abstract class AbstractVerifier implements Verifier, IdCallback { +public abstract class AbstractVerifier, L extends List> + implements Verifier, IdCallback { private static final String TAG = "AbstractVerifier"; /**为 PUT, DELETE 强制要求必须有 id/id{}/id{}@ 条件 @@ -114,7 +106,7 @@ public abstract class AbstractVerifier implements Verifier, */ public static final String ADMIN = "ADMIN"; - public static ParserCreator PARSER_CREATOR; +// public static ParserCreator PARSER_CREATOR; public static ScriptEngineManager SCRIPT_ENGINE_MANAGER; public static ScriptEngine SCRIPT_ENGINE; @@ -136,7 +128,7 @@ public abstract class AbstractVerifier implements Verifier, // > // > @NotNull - public static Map> REQUEST_MAP; + public static Map>> REQUEST_MAP; private static String VERIFY_LENGTH_RULE = "(?[>=<]*)(?[0-9]*)"; private static Pattern VERIFY_LENGTH_PATTERN = Pattern.compile(VERIFY_LENGTH_RULE); @@ -227,17 +219,17 @@ public static HashMap getAccessMap(MethodAccess access) @Override - public String getVisitorIdKey(SQLConfig config) { - return config.getUserIdKey(); + public String getVisitorIdKey(SQLConfig config) { + return config == null ? getUserIdKey(null, null, null, null) : config.getUserIdKey(); } @Override public String getIdKey(String database, String schema, String datasource, String table) { - return apijson.JSONObject.KEY_ID; + return JSONMap.KEY_ID; } @Override public String getUserIdKey(String database, String schema, String datasource, String table) { - return apijson.JSONObject.KEY_USER_ID; + return JSONMap.KEY_USER_ID; } @SuppressWarnings("unchecked") @@ -257,7 +249,7 @@ public Visitor getVisitor() { return visitor; } @Override - public AbstractVerifier setVisitor(Visitor visitor) { + public AbstractVerifier setVisitor(Visitor visitor) { this.visitor = visitor; this.visitorId = visitor == null ? null : visitor.getId(); @@ -276,7 +268,7 @@ public AbstractVerifier setVisitor(Visitor visitor) { * @throws Exception */ @Override - public boolean verifyAccess(SQLConfig config) throws Exception { + public boolean verifyAccess(SQLConfig config) throws Exception { if (ENABLE_VERIFY_ROLE == false) { throw new UnsupportedOperationException("AbstractVerifier.ENABLE_VERIFY_ROLE == false " + "时不支持校验角色权限!如需支持则设置 AbstractVerifier.ENABLE_VERIFY_ROLE = true !"); @@ -295,7 +287,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { if (ROLE_MAP.containsKey(role) == false) { Set NAMES = ROLE_MAP.keySet(); throw new IllegalArgumentException("角色 " + role + " 不存在!" + - "只能是[" + StringUtil.getString(NAMES.toArray()) + "]中的一种!"); + "只能是[" + StringUtil.get(NAMES.toArray()) + "]中的一种!"); } if (role.equals(UNKNOWN) == false) { //未登录的角色 @@ -310,7 +302,7 @@ public boolean verifyAccess(SQLConfig config) throws Exception { } @Override - public void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { + public void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { verifyAllowRole(config, table, method, role); //验证允许的角色 verifyUseRole(config, table, method, role); //验证使用的角色 } @@ -322,9 +314,9 @@ public void verifyRole(SQLConfig config, String table, RequestMethod method, Str * @param role * @return * @throws Exception - * @see {@link apijson.JSONObject#KEY_ROLE} + * @see {@link JSONMap#KEY_ROLE} */ - public void verifyAllowRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { + public void verifyAllowRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception { Log.d(TAG, "verifyAllowRole table = " + table + "; method = " + method + "; role = " + role); if (table == null) { table = config == null ? null : config.getTable(); @@ -353,23 +345,25 @@ public void verifyAllowRole(SQLConfig config, String table, RequestMethod method * @param role * @return * @throws Exception - * @see {@link apijson.JSONObject#KEY_ROLE} + * @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通过就行 @@ -390,13 +384,13 @@ public void verifyUseRole(SQLConfig config, String table, RequestMethod method, Collection requestIdArray = (Collection) config.getWhere(visitorIdKey + "{}", true); // 不能是 &{}, |{} 不要传,直接 {} if (requestId != null) { if (requestIdArray == null) { - requestIdArray = new JSONArray(); + requestIdArray = JSON.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+"{}", JSON.parseArray(list), true); // key{}:[] 有效,SQLConfig 里 throw NotExistException } else { // 请求已声明 key:id 或 key{}:[] 条件,直接验证 for (Object id : requestIdArray) { @@ -435,7 +429,7 @@ else if (id instanceof String) { Object oid; for (List ovl : ovs) { oid = ovl == null || index >= ovl.size() ? null : ovl.get(index); - if (oid == null || StringUtil.getString(oid).equals("" + visitorId) == false) { + if (oid == null || StringUtil.get(oid).equals("" + visitorId) == false) { throw new IllegalAccessException(visitorIdKey + " = " + oid + " 的 " + table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } @@ -459,7 +453,7 @@ else if (id instanceof String) { } else { requestId = config.getWhere(visitorIdKey, true);//JSON里数值不能保证是Long,可能是Integer - if (requestId != null && StringUtil.getString(requestId).equals(StringUtil.getString(visitorId)) == false) { + if (requestId != null && StringUtil.get(requestId).equals(StringUtil.get(visitorId)) == false) { throw new IllegalAccessException(visitorIdKey + " = " + requestId + " 的 " + table + " 不允许 " + role + " 用户的 " + method.name() + " 请求!"); } @@ -539,18 +533,21 @@ public void verifyRepeat(String table, String key, Object value, long exceptId) throw new UnsupportedDataTypeException(key + ":value 中value的类型不能为JSON!"); } - JSONRequest request = new JSONRequest(key, value); + M tblObj = JSON.createJSONObject(); + tblObj.put(key, value); if (exceptId > 0) {//允许修改自己的属性为该属性原来的值 - request.put(JSONRequest.KEY_ID + "!", exceptId); // FIXME 这里 id 写死了,不支持自定义 + tblObj.put(JSONMap.KEY_ID + "!", exceptId); // FIXME 这里 id 写死了,不支持自定义 } - JSONObject repeat = createParser().setMethod(HEAD).setNeedVerify(true).parseResponse( - new JSONRequest(table, request) - ); - repeat = repeat == null ? null : repeat.getJSONObject(table); + + M req = JSON.createJSONObject(); + req.put(table, tblObj); + Map repeat = createParser().setMethod(HEAD).setNeedVerify(true).parseResponse(req); + + repeat = repeat == null ? null : JSON.get(repeat, table); if (repeat == null) { throw new Exception("服务器内部错误 verifyRepeat repeat == null"); } - if (repeat.getIntValue(JSONResponse.KEY_COUNT) > 0) { + if (getIntValue(repeat, JSONResponse.KEY_COUNT) > 0) { throw new ConflictException(key + ": " + value + " 已经存在,不能重复!"); } } @@ -565,15 +562,13 @@ public void verifyRepeat(String table, String key, Object value, long exceptId) * @param maxUpdateCount * @param database * @param schema - * @param creator * @return * @throws Exception */ @Override - public JSONObject verifyRequest(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject request, final int maxUpdateCount - , final String database, final String schema, final SQLCreator creator) throws Exception { - return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, this, creator); + public M verifyRequest(@NotNull final RequestMethod method, final String name, final M target, final M request, final int maxUpdateCount + , final String database, final String schema) throws Exception { + return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, this, getParser()); } /**从request提取target指定的内容 @@ -581,13 +576,14 @@ public JSONObject verifyRequest(@NotNull final RequestMethod method, final Strin * @param name * @param target * @param request - * @param creator + * @param parser * @return * @throws Exception */ - public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject request, final SQLCreator creator) throws Exception { - return verifyRequest(method, name, target, request, AbstractParser.MAX_UPDATE_COUNT, creator); + public static , L extends List> M verifyRequest( + @NotNull final RequestMethod method, final String name, final M target, final M request + , @NotNull Parser parser) throws Exception { + return verifyRequest(method, name, target, request, AbstractParser.MAX_UPDATE_COUNT, parser); } /**从request提取target指定的内容 * @param method @@ -595,15 +591,15 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina * @param target * @param request * @param maxUpdateCount - * @param creator + * @param parser * @return * @throws Exception */ - public static JSONObject verifyRequest(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject request - , final int maxUpdateCount, final SQLCreator creator) throws Exception { - return verifyRequest(method, name, target, request, maxUpdateCount - , null, null, null, creator); + public static , L extends List> M verifyRequest( + @NotNull final RequestMethod method, final String name, final M target, final M request + , final int maxUpdateCount, @NotNull Parser parser) throws Exception { + + return verifyRequest(method, name, target, request, maxUpdateCount, null, null, null, parser); } /**从request提取target指定的内容 @@ -615,17 +611,16 @@ public static JSONObject verifyRequest(@NotNull final RequestMethod method, fina * @param database * @param schema * @param idCallback - * @param creator + * @param parser * @return * @param * @throws Exception */ - public static JSONObject verifyRequest(@NotNull final RequestMethod method - , final String name, final JSONObject target, final JSONObject request - , final int maxUpdateCount, final String database, final String schema - , final IdCallback idCallback, final SQLCreator creator) throws Exception { - return verifyRequest(method, name, target, request, maxUpdateCount, database, schema - , null, idCallback, creator); + public static , L extends List> M verifyRequest( + @NotNull RequestMethod method, String name, M target, M request, int maxUpdateCount, String database + , String schema, IdCallback idCallback, @NotNull Parser parser) throws Exception { + + return verifyRequest(method, name, target, request, maxUpdateCount, database, schema, null, idCallback, parser); } /**从request提取target指定的内容 * @param method @@ -637,15 +632,15 @@ public static JSONObject verifyRequest(@NotNull final Request * @param schema * @param datasource * @param idCallback - * @param creator + * @param parser * @return * @param * @throws Exception */ - public static JSONObject verifyRequest(@NotNull final RequestMethod method - , final String name, final JSONObject target, final JSONObject request + public static , L extends List> M verifyRequest( + @NotNull final RequestMethod method, final String name, final M target, final M request , final int maxUpdateCount, final String database, final String schema, final String datasource - , final IdCallback idCallback, final SQLCreator creator) throws Exception { + , final IdCallback idCallback, @NotNull Parser parser) throws Exception { if (ENABLE_VERIFY_CONTENT == false) { throw new UnsupportedOperationException("AbstractVerifier.ENABLE_VERIFY_CONTENT == false" + " 时不支持校验请求传参内容!如需支持则设置 AbstractVerifier.ENABLE_VERIFY_CONTENT = true !"); @@ -661,27 +656,27 @@ public static JSONObject verifyRequest(@NotNull final Request } //已在 Verifier 中处理 - // if (get(request.getString(JSONRequest.KEY_ROLE)) == ADMIN) { + // if (get(getString(request, apijson.JSONMap.KEY_ROLE)) == ADMIN) { // throw new IllegalArgumentException("角色设置错误!不允许在写操作Request中传 " + name + - // ":{ " + JSONRequest.KEY_ROLE + ":admin } !"); + // ":{ " + apijson.JSONMap.KEY_ROLE + ":admin } !"); // } //解析 - return parse(method, name, target, request, database, schema, idCallback, creator, new OnParseCallback() { + return parse(method, name, target, request, database, schema, idCallback, parser, new OnParseCallback() { @Override - public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception { + public M onParseJSONObject(String key, M tobj, M robj) throws Exception { // Log.i(TAG, "verifyRequest.parse.onParseJSONObject key = " + key + "; robj = " + robj); if (robj == null) { if (tobj != null) {//不允许不传Target中指定的Table throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":{} !"); } - } else if (apijson.JSONObject.isTableKey(key)) { - String db = request.getString(apijson.JSONObject.KEY_DATABASE); - String sh = request.getString(apijson.JSONObject.KEY_SCHEMA); - String ds = request.getString(apijson.JSONObject.KEY_DATASOURCE); + } 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); if (StringUtil.isEmpty(db, false)) { db = database; } @@ -693,30 +688,30 @@ public JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj } String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, ds, key); - String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + String finalIdKey = StringUtil.isEmpty(idKey, false) ? JSONMap.KEY_ID : idKey; if (method == RequestMethod.POST) { if (robj.containsKey(finalIdKey)) { throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 不能传 " + finalIdKey + " !"); } } else { - Boolean atLeastOne = tobj == null ? null : tobj.getBoolean(Operation.IS_ID_CONDITION_MUST.name()); + Boolean atLeastOne = tobj == null ? null : getBoolean(tobj, Operation.IS_ID_CONDITION_MUST.name()); if (Boolean.TRUE.equals(atLeastOne) || RequestMethod.isUpdateMethod(method)) { verifyId(method.name(), name, key, robj, finalIdKey, maxUpdateCount, atLeastOne != null ? atLeastOne : IS_UPDATE_MUST_HAVE_ID_CONDITION); String userIdKey = idCallback == null ? null : idCallback.getUserIdKey(db, sh, ds, key); - String finalUserIdKey = StringUtil.isEmpty(userIdKey, false) ? apijson.JSONObject.KEY_USER_ID : userIdKey; + String finalUserIdKey = StringUtil.isEmpty(userIdKey, false) ? JSONMap.KEY_USER_ID : userIdKey; verifyId(method.name(), name, key, robj, finalUserIdKey, maxUpdateCount, false); } } } - return verifyRequest(method, key, tobj, robj, maxUpdateCount, database, schema, idCallback, creator); + return verifyRequest(method, key, tobj, robj, maxUpdateCount, database, schema, idCallback, parser); } @Override - protected JSONArray onParseJSONArray(String key, JSONArray tarray, JSONArray rarray) throws Exception { - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { + protected L onParseJSONArray(String key, L tarray, L rarray) throws Exception { + if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONMap.isArrayKey(key)) { if (rarray == null || rarray.isEmpty()) { throw new IllegalArgumentException(method + "请求,请在 " + name + " 内传 " + key + ":[{ ... }] " + ",批量新增 Table[]:value 中 value 必须是包含表对象的非空数组!其中每个子项 { ... } 都是" @@ -741,8 +736,9 @@ protected JSONArray onParseJSONArray(String key, JSONArray tarray, JSONArray rar * @param idKey * @param atLeastOne 至少有一个不为null */ - private static void verifyId(@NotNull String method, @NotNull String name, @NotNull String key - , @NotNull JSONObject robj, @NotNull String idKey, final int maxUpdateCount, boolean atLeastOne) { + private static , L extends List> void verifyId( + @NotNull String method, @NotNull String name, @NotNull String key + , @NotNull M robj, @NotNull String idKey, final int maxUpdateCount, boolean atLeastOne) throws Exception { //单个修改或删除 Object id = robj.get(idKey); //如果必须传 id ,可在Request表中配置NECESSARY if (id != null && id instanceof Number == false && id instanceof String == false) { @@ -754,10 +750,10 @@ private static void verifyId(@NotNull String method, @NotNull String name, @NotN //批量修改或删除 String idInKey = idKey + "{}"; // id引用, 格式: "id{}@": "sql" - String idRefInKey = robj.getString(idKey + "{}@"); - JSONArray idIn = null; + String idRefInKey = getString(robj, idKey + "{}@"); + L idIn = null; try { - idIn = robj.getJSONArray(idInKey); //如果必须传 id{} ,可在Request表中配置NECESSARY + idIn = JSON.get(robj, idInKey); //如果必须传 id{} ,可在Request表中配置NECESSARY } catch (Exception e) { throw new IllegalArgumentException(method + "请求," + name + "/" + key + " 里面的 " + idInKey + ":value 中value的类型只能是 [Long] !"); @@ -810,16 +806,15 @@ else if (o instanceof String) { * @param response * @param database * @param schema - * @param creator + * @param parser * @param callback * @return * @throws Exception */ @Override - public JSONObject verifyResponse(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject response, final String database, final String schema - , SQLCreator creator, OnParseCallback callback) throws Exception { - return verifyResponse(method, name, target, response, database, schema, this, creator, callback); + public M verifyResponse(@NotNull final RequestMethod method, final String name, final M target, final M response + , final String database, final String schema, @NotNull Parser parser, OnParseCallback callback) throws Exception { + return verifyResponse(method, name, target, response, database, schema, this, parser, callback); } /**校验并将response转换为指定的内容和结构 @@ -827,14 +822,14 @@ public JSONObject verifyResponse(@NotNull final RequestMethod method, final Stri * @param name * @param target * @param response - * @param creator + * @param parser * @param callback * @return * @throws Exception */ - public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject response, SQLCreator creator, OnParseCallback callback) throws Exception { - return verifyResponse(method, name, target, response, null, null, null, creator, callback); + public static , L extends List> M verifyResponse(@NotNull final RequestMethod method, final String name + , final M target, final M response, @NotNull Parser parser, OnParseCallback callback) throws Exception { + return verifyResponse(method, name, target, response, null, null, null, parser, callback); } /**校验并将response转换为指定的内容和结构 * @param method @@ -844,15 +839,15 @@ public static JSONObject verifyResponse(@NotNull final RequestMethod method, fin * @param database * @param schema * @param idKeyCallback - * @param creator + * @param parser * @param callback * @return * @param * @throws Exception */ - public static JSONObject verifyResponse(@NotNull final RequestMethod method, final String name - , final JSONObject target, final JSONObject response, final String database, final String schema - , final IdCallback idKeyCallback, SQLCreator creator, OnParseCallback callback) throws Exception { + public static , L extends List> M verifyResponse(@NotNull final RequestMethod method + , final String name, final M target, final M response, final String database, final String schema + , final IdCallback idKeyCallback, @NotNull Parser parser, OnParseCallback callback) throws Exception { Log.i(TAG, "verifyResponse method = " + method + "; name = " + name + "; target = \n" + JSON.toJSONString(target) @@ -865,10 +860,10 @@ public static JSONObject verifyResponse(@NotNull final Reques //解析 return parse(method, name, target, response, database, schema - , idKeyCallback, creator, callback != null ? callback : new OnParseCallback() { + , idKeyCallback, parser, callback != null ? callback : new OnParseCallback() { @Override - protected JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception { - return verifyResponse(method, key, tobj, robj, database, schema, idKeyCallback, creator, callback); + protected M onParseJSONObject(String key, M tobj, M robj) throws Exception { + return verifyResponse(method, key, tobj, robj, database, schema, idKeyCallback, parser, callback); } }); } @@ -879,14 +874,14 @@ protected JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject r * @param name * @param target * @param real - * @param creator + * @param parser * @param callback * @return * @throws Exception */ - public static JSONObject parse(@NotNull final RequestMethod method, String name, JSONObject target, JSONObject real - , SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { - return parse(method, name, target, real, null, null, null, creator, callback); + public static , L extends List> M parse(@NotNull final RequestMethod method + , String name, M target, M real, @NotNull Parser parser, @NotNull OnParseCallback callback) throws Exception { + return parse(method, name, target, real, null, null, null, parser, callback); } /**对request和response不同的解析用callback返回 * @param method @@ -896,15 +891,15 @@ public static JSONObject parse(@NotNull final RequestMethod method, String name, * @param database * @param schema * @param idCallback - * @param creator + * @param parser * @param callback * @return * @throws Exception */ - public static JSONObject parse(@NotNull final RequestMethod method, String name - , JSONObject target, JSONObject real, final String database, final String schema - , final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { - return parse(method, name, target, real, database, schema, null, idCallback, creator, callback); + public static , L extends List> M parse( + @NotNull final RequestMethod method, String name, M target, M real, final String database, final String schema + , final IdCallback idCallback, @NotNull Parser parser, @NotNull OnParseCallback callback) throws Exception { + return parse(method, name, target, real, database, schema, null, idCallback, parser, callback); } /**对request和response不同的解析用callback返回 * @param method @@ -915,45 +910,42 @@ public static JSONObject parse(@NotNull final RequestMethod m * @param schema * @param datasource * @param idCallback - * @param creator + * @param parser * @param callback * @return * @throws Exception */ - public static JSONObject parse(@NotNull final RequestMethod method, String name - , JSONObject target, JSONObject real, final String database, final String schema, final String datasource - , final IdCallback idCallback, SQLCreator creator, @NotNull OnParseCallback callback) throws Exception { + public static , L extends List> M parse(@NotNull final RequestMethod method + , String name, M target, M real, final String database, final String schema, final String datasource + , final IdCallback idCallback, @NotNull Parser parser, @NotNull OnParseCallback callback) throws Exception { if (target == null) { return null; } - // 获取配置<<<<<<<<<<<<<<<<<<<<<<<<<<<< - JSONObject type = target.getJSONObject(TYPE.name()); - JSONObject verify = target.getJSONObject(VERIFY.name()); - JSONObject insert = target.getJSONObject(INSERT.name()); - JSONObject update = target.getJSONObject(UPDATE.name()); - JSONObject replace = target.getJSONObject(REPLACE.name()); - - String exist = StringUtil.getString(target.getString(EXIST.name())); - String unique = StringUtil.getString(target.getString(UNIQUE.name())); - String remove = StringUtil.getString(target.getString(REMOVE.name())); - String must = StringUtil.getString(target.getString(MUST.name())); - String refuse = StringUtil.getString(target.getString(REFUSE.name())); - Object _if = target.get(IF.name()); boolean ifIsStr = _if instanceof String && StringUtil.isNotEmpty(_if, true); - JSONObject ifObj = ifIsStr == false && _if instanceof JSONObject ? (JSONObject) _if : null; -// : (_if instanceof String ? new apijson.JSONRequest((String) _if, "" /* "throw new Error('')" */ ) : null); + M ifObj = ifIsStr == false && _if instanceof Map ? (M) _if : null; +// : (_if instanceof String ? new apijson.JSONMap((String) _if, "" /* "throw new Error('')" */ ) : null); if (ifObj == null && _if != null && ifIsStr == false) { -// if (_if instanceof JSONArray) { +// if (_if instanceof List) { // } - throw new IllegalArgumentException(name + ": { " + IF.name() + ": value } 中 value 类型错误!只允许 String, JSONObject!"); + throw new IllegalArgumentException(name + ": { " + IF.name() + ": value } 中 value 类型错误!只允许 String, JSONRequest!"); } -// Object code = target.get(CODE.name()); - - String allowPartialUpdateFail = StringUtil.getString(target.getString(ALLOW_PARTIAL_UPDATE_FAIL.name())); + // 获取配置<<<<<<<<<<<<<<<<<<<<<<<<<<<< + 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 code = target.get(CODE.name()); // 移除字段<<<<<<<<<<<<<<<<<<< String[] removes = StringUtil.split(remove); @@ -980,6 +972,8 @@ public static JSONObject parse(@NotNull final RequestMethod m } // 判断必要字段是否都有>>>>>>>>>>>>>>>>>>> + String[] sks = StringUtil.split(getString(real, KEY_STRING)); + List stringKeyList = sks == null || sks.length <= 0 ? null : Arrays.asList(sks); Set objKeySet = new HashSet(); // 不能用tableKeySet,仅判断 Table:{} 会导致 key:{ Table:{} } 绕过判断 @@ -995,24 +989,28 @@ public static JSONObject parse(@NotNull final RequestMethod m } Object tvalue = entry.getValue(); Object rvalue = real.get(key); + if (rvalue != null && stringKeyList != null && stringKeyList.contains(key)) { + rvalue = JSON.toJSONString(rvalue); + } + if (callback.onParse(key, tvalue, rvalue) == false) { continue; } - if (tvalue instanceof JSONObject) { // JSONObject,往下一级提取 - if (rvalue != null && rvalue instanceof JSONObject == false) { + if (tvalue instanceof Map) { // JSONRequest,往下一级提取 + if (rvalue != null && rvalue instanceof Map == false) { throw new UnsupportedDataTypeException(key + ":value 的 value 不合法!类型必须是 OBJECT ,结构为 {} !"); } - tvalue = callback.onParseJSONObject(key, (JSONObject) tvalue, (JSONObject) rvalue); + tvalue = callback.onParseJSONObject(key, (M) tvalue, (M) rvalue); objKeySet.add(key); - } else if (tvalue instanceof JSONArray) { // JSONArray - if (rvalue != null && rvalue instanceof JSONArray == false) { + } else if (tvalue instanceof List) { // L + if (rvalue != null && rvalue instanceof List == false) { throw new UnsupportedDataTypeException(key + ":value 的 value 不合法!类型必须是 ARRAY ,结构为 [] !"); } - tvalue = callback.onParseJSONArray(key, (JSONArray) tvalue, (JSONArray) rvalue); + tvalue = callback.onParseJSONArray(key, (L) tvalue, (L) rvalue); - if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONRequest.isArrayKey(key)) { + if ((method == RequestMethod.POST || method == RequestMethod.PUT) && JSONMap.isArrayKey(key)) { objKeySet.add(key); } } else { // 其它Object @@ -1095,17 +1093,29 @@ public static JSONObject parse(@NotNull final RequestMethod m // 判断不允许传的key<<<<<<<<<<<<<<<<<<<<<<<<< for (String rk : rkset) { + if (rk == null || KEY_STRING.equals(rk)) { + // ConcurrentModificationException real.remove(rk); + continue; + } + if (refuseSet.contains(rk)) { // 不允许的字段 throw new IllegalArgumentException(method + "请求," + name - + " 里面不允许传 " + rk + " 等" + StringUtil.getString(refuseSet) + "内的任何字段!"); + + " 里面不允许传 " + 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 = JSON.toJSONString(rv); + } // 不允许传远程函数,只能后端配置 if (rk.endsWith("()") && rv instanceof String) { @@ -1115,12 +1125,12 @@ public static JSONObject parse(@NotNull final RequestMethod m // 不在target内的 key:{} if (rk.startsWith("@") == false && rk.endsWith("@") == false && objKeySet.contains(rk) == false) { - if (rv instanceof JSONObject) { + if (rv instanceof Map) { throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许传 " + rk + ":{} !"); } if ((method == RequestMethod.POST || method == RequestMethod.PUT) - && rv instanceof JSONArray && JSONRequest.isArrayKey(rk)) { + && rv instanceof List && JSONMap.isArrayKey(rk)) { throw new UnsupportedOperationException(method + " 请求," + name + " 里面不允许 " + rk + ":[] 等未定义的 Table[]:[{}] 批量操作键值对!"); } @@ -1137,17 +1147,17 @@ public static JSONObject parse(@NotNull final RequestMethod m // 校验与修改Request<<<<<<<<<<<<<<<<< // 在tableKeySet校验后操作,避免 导致put/add进去的Table 被当成原Request的内容 - real = operate(TYPE, type, real, creator); - real = operate(VERIFY, verify, real, creator); - real = operate(INSERT, insert, real, creator); - real = operate(UPDATE, update, real, creator); - real = operate(REPLACE, replace, real, creator); + real = operate(TYPE, type, real, parser); + real = operate(VERIFY, verify, real, parser); + real = operate(INSERT, insert, real, parser); + real = operate(UPDATE, update, real, parser); + real = operate(REPLACE, replace, real, parser); // 校验与修改Request>>>>>>>>>>>>>>>>> - String db = real.getString(apijson.JSONObject.KEY_DATABASE); - String sh = real.getString(apijson.JSONObject.KEY_SCHEMA); - String ds = real.getString(apijson.JSONObject.KEY_DATASOURCE); + String db = getString(real, JSONMap.KEY_DATABASE); + String sh = getString(real, JSONMap.KEY_SCHEMA); + String ds = getString(real, JSONMap.KEY_DATASOURCE); if (StringUtil.isEmpty(db, false)) { db = database; } @@ -1158,18 +1168,18 @@ public static JSONObject parse(@NotNull final RequestMethod m ds = datasource; } String idKey = idCallback == null ? null : idCallback.getIdKey(db, sh, ds, name); - String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + String finalIdKey = StringUtil.isEmpty(idKey, false) ? JSONMap.KEY_ID : idKey; // TODO 放在operate前?考虑性能、operate修改后再验证的值是否和原来一样 // 校验存在<<<<<<<<<<<<<<<<<<< String[] exists = StringUtil.split(exist); if (exists != null && exists.length > 0) { - long exceptId = real.getLongValue(finalIdKey); + long exceptId = getLongValue(real, finalIdKey); Map map = new HashMap<>(); for (String e : exists) { map.put(e,real.get(e)); } - verifyExist(name, map, exceptId, creator); + verifyExist(name, map, exceptId, parser); } // 校验存在>>>>>>>>>>>>>>>>>>> @@ -1177,20 +1187,21 @@ public static JSONObject parse(@NotNull final RequestMethod m // 校验重复<<<<<<<<<<<<<<<<<<< String[] uniques = StringUtil.split(unique); if (uniques != null && uniques.length > 0) { - long exceptId = real.getLongValue(finalIdKey); + long exceptId = getLongValue(real, finalIdKey); Map map = new HashMap<>(); for (String u : uniques) { map.put(u, real.get(u)); } - verifyRepeat(name, map, exceptId, finalIdKey, creator); + verifyRepeat(name, map, exceptId, finalIdKey, parser); } // 校验重复>>>>>>>>>>>>>>>>>>> // 校验并配置允许批量增删改部分失败<<<<<<<<<<<<<<<<<<< + 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 (apijson.JSONObject.isArrayKey(key) == false) { + if (JSONMap.isArrayKey(key) == false) { throw new IllegalArgumentException("后端 Request 表中 " + ALLOW_PARTIAL_UPDATE_FAIL.name() + ":value 中 " + key + " 不合法!必须以 [] 结尾!"); } @@ -1213,17 +1224,17 @@ public static JSONObject parse(@NotNull final RequestMethod m // 校验并配置允许部分批量增删改失败>>>>>>>>>>>>>>>>>>> - String[] nks = ifObj == null ? null : StringUtil.split(real.getString(JSONRequest.KEY_NULL)); + String[] nks = ifObj == null ? null : StringUtil.split(getString(real, JSONMap.KEY_NULL)); Collection nkl = nks == null || nks.length <= 0 ? new HashSet<>() : Arrays.asList(nks); Set> ifSet = ifObj == null ? null : ifObj.entrySet(); if (ifIsStr || (ifSet != null && ifSet.isEmpty() == false)) { // 没必要限制,都是后端配置的,安全可控,而且可能确实有特殊需求,需要 id, @column 等 -// List condKeys = new ArrayList<>(Arrays.asList(apijson.JSONRequest.KEY_ID, apijson.JSONRequest.KEY_ID_IN -// , apijson.JSONRequest.KEY_USER_ID, apijson.JSONRequest.KEY_USER_ID_IN)); -// condKeys.addAll(JSONRequest.TABLE_KEY_LIST); +// List condKeys = new ArrayList<>(Arrays.asList(apijson.JSONMap.KEY_ID, apijson.JSONMap.KEY_ID_IN +// , apijson.JSONMap.KEY_USER_ID, apijson.JSONMap.KEY_USER_ID_IN)); +// condKeys.addAll(apijson.JSONMap.TABLE_KEY_LIST); - String preCode = "var curObj = " + JSON.format(real) + ";"; + String preCode = "var curObj = " + JSON.toJSONString(real) + ";"; // 未传的 key 在后面 eval 时总是报错 undefined,而且可能有冲突,例如对象里有 "curObj": val 键值对,就会覆盖当前对象定义,还不如都是 curObj.sex 这样取值 // Set> rset = real.entrySet(); @@ -1284,17 +1295,18 @@ public static JSONObject parse(@NotNull final RequestMethod m continue; } - if (v instanceof JSONObject == false) { + if (v instanceof Map == false) { throw new IllegalArgumentException("Request 表 structure 配置的 " + IF.name() - + ":{ " + k + ":value } 中 value 不合法,必须是 JSONObject {} !"); + + ":{ " + k + ":value } 中 value 不合法,必须是 JSONRequest {} !"); } if (nkl.contains(k) || real.get(k) != null) { - real = parse(method, name, (JSONObject) v, real, database, schema, datasource, idCallback, creator, callback); + real = parse(method, name, (M) v, real, database, schema, datasource, idCallback, parser, callback); } } } } + Log.i(TAG, "parse return real = " + JSON.toJSONString(real)); return real; } @@ -1315,12 +1327,12 @@ public static ScriptEngine getScriptEngine(String lang) { * @param opt * @param targetChild * @param real - * @param creator + * @param parser * @return * @throws Exception */ - private static JSONObject operate(Operation opt, JSONObject targetChild - , JSONObject real, SQLCreator creator) throws Exception { + private static , L extends List> M operate(Operation opt, M targetChild + , M real, @NotNull Parser parser) throws Exception { if (targetChild == null) { return real; } @@ -1341,7 +1353,7 @@ private static JSONObject operate(Operation opt, JSONObject targetChild verifyType(tk, tv, real); } else if (opt == VERIFY) { - verifyValue(tk, tv, real, creator); + verifyValue(tk, tv, real, parser); } else if (opt == UPDATE) { real.put(tk, tv); @@ -1370,7 +1382,7 @@ else if (opt == UPDATE) { * @param real * @throws Exception */ - public static void verifyType(@NotNull String tk, Object tv, @NotNull JSONObject real) + public static void verifyType(@NotNull String tk, Object tv, @NotNull Map real) throws UnsupportedDataTypeException { if (tv instanceof String == false) { throw new UnsupportedDataTypeException("服务器内部错误," + tk + ":value 的value不合法!" @@ -1414,8 +1426,8 @@ public static void verifyType(@NotNull String tk, @NotNull String tv, Object rv, //这里不抽取 enum,因为 enum 不能满足扩展需求,子类需要可以自定义,而且 URL[] 这种也不符合命名要求,得用 constructor + getter + setter switch (tv) { - case "BOOLEAN": //Boolean.parseBoolean(real.getString(tk)); 只会判断null和true - if (rv instanceof Boolean == false) { //JSONObject.getBoolean 可转换Number类型 + case "BOOLEAN": //Boolean.parseBoolean(getString(real, tk)); 只会判断null和true + if (rv instanceof Boolean == false) { //apijson.JSONMap.getBoolean 可转换Number类型 throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 BOOLEAN" + (isInArray ? "[] !" : " !")); } break; @@ -1435,7 +1447,7 @@ public static void verifyType(@NotNull String tk, @NotNull String tv, Object rv, } break; case "STRING": - if (rv instanceof String == false) { //JSONObject.getString 可转换任何类型 + if (rv instanceof String == false) { //apijson.JSONMap.getString 可转换任何类型 throw new UnsupportedDataTypeException(tk + ":value 的value不合法!" + "类型必须是 STRING" + (isInArray ? "[] !" : " !")); } @@ -1473,13 +1485,13 @@ public static void verifyType(@NotNull String tk, @NotNull String tv, Object rv, } break; case "OBJECT": - if (rv instanceof Map == false) { //JSONObject.getJSONObject 可转换String类型 + if (rv instanceof Map == false) { //apijson.JSONMap.getJSONObject 可转换String类型 throw new UnsupportedDataTypeException(tk + ":value 的value不合法!" + "类型必须是 OBJECT" + (isInArray ? "[] !" : " !") + " OBJECT 结构为 {} !"); } break; case "ARRAY": - if (rv instanceof Collection == false) { //JSONObject.getJSONArray 可转换String类型 + if (rv instanceof Collection == false) { //apijson.JSONMap.getJSONArray 可转换String类型 throw new UnsupportedDataTypeException(tk + ":value 的value不合法!" + "类型必须是 ARRAY" + (isInArray ? "[] !" : " !") + " ARRAY 结构为 [] !"); } @@ -1487,7 +1499,7 @@ public static void verifyType(@NotNull String tk, @NotNull String tv, Object rv, //目前在业务表中还用不上,单一的类型校验已经够用 // case "JSON": // try { - // com.alibaba.fastjson.JSON.parse(rv.toString()); + // com.alibaba.fastjson.parseJSON(rv.toString()); // } catch (Exception e) { // throw new UnsupportedDataTypeException(tk + ":value 的value不合法!类型必须是 JSON !" // + "也就是 {Object}, [Array] 或 它们对应的字符串 '{Object}', '[Array]' 4种中的一个 !"); @@ -1507,10 +1519,11 @@ public static void verifyType(@NotNull String tk, @NotNull String tv, Object rv, * @param tk * @param tv * @param real - * @param creator + * @param parser * @throws Exception */ - private static void verifyValue(@NotNull String tk, @NotNull Object tv, @NotNull JSONObject real, SQLCreator creator) throws Exception { + private 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!!!"); } @@ -1519,7 +1532,7 @@ private static void verifyValue(@NotNull String tk, @NotNull Object tv, @NotNull Object rv; Logic logic; if (tk.endsWith("$")) { // 模糊搜索 - verifyCondition("$", real, tk, tv, creator); + verifyCondition("$", real, tk, tv, parser); } else if (tk.endsWith("~")) { // 正则匹配 logic = new Logic(tk.substring(0, tk.length() - 1)); @@ -1529,7 +1542,7 @@ else if (tk.endsWith("~")) { // 正则匹配 return; } - JSONArray array = AbstractSQLConfig.newJSONArray(tv); + L array = AbstractSQLConfig.newJSONArray(tv); boolean m; boolean isOr = false; @@ -1564,9 +1577,9 @@ else if (tk.endsWith("~")) { // 正则匹配 } else if (tk.endsWith("{}")) { //rv符合tv条件或在tv内 if (tv instanceof String) {//TODO >= 0, < 10 - verifyCondition("{}", real, tk, tv, creator); + verifyCondition("{}", real, tk, tv, parser); } - else if (tv instanceof JSONArray) { + else if (tv instanceof List) { logic = new Logic(tk.substring(0, tk.length() - 2)); rk = logic.getKey(); rv = real.get(rk); @@ -1574,7 +1587,7 @@ else if (tv instanceof JSONArray) { return; } - if (((JSONArray) tv).contains(rv) == logic.isNot()) { + if (((L) tv).contains(rv) == logic.isNot()) { throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); } } @@ -1610,15 +1623,15 @@ else if (tk.endsWith("<>")) { //rv包含tv内的值 return; } - if (rv instanceof JSONArray == false) { + if (rv instanceof Collection == false) { throw new UnsupportedDataTypeException("服务器Request表verify配置错误!"); } - JSONArray array = AbstractSQLConfig.newJSONArray(tv); + L array = AbstractSQLConfig.newJSONArray(tv); boolean isOr = false; for (Object o : array) { - if (((JSONArray) rv).contains(o)) { + if (((L) rv).contains(o)) { if (logic.isNot()) { throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 " + tk + ":" + tv + " !"); } @@ -1686,11 +1699,12 @@ private static boolean verifyRV(String rule,String content) throws UnsupportedDa * @param real * @param tk * @param tv - * @param creator + * @param parser * @throws Exception */ - private static void verifyCondition(@NotNull String funChar, @NotNull JSONObject real, @NotNull String tk, @NotNull Object tv - , @NotNull SQLCreator creator) throws Exception { + private 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())); String rk = logic.getKey(); @@ -1703,7 +1717,7 @@ private static void verifyCondition(@NotNull String funChar, @NotNull JSONObject throw new IllegalArgumentException(rk + ":value 中value不合法!value 中不允许有单引号 ' !"); } - SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.GET).setCount(1).setPage(0); + SQLConfig config = parser.createSQLConfig().setMethod(RequestMethod.GET).setCount(1).setPage(0); config.setTest(true); // config.setTable(Test.class.getSimpleName()); // config.setColumn(rv + logic.getChar() + funChar) @@ -1711,15 +1725,16 @@ private static void verifyCondition(@NotNull String funChar, @NotNull JSONObject config.putWhere(rv + logic.getChar() + funChar, tv, false); config.setCount(1); - SQLExecutor executor = creator.createSQLExecutor(); - JSONObject result = null; + SQLExecutor executor = parser.createSQLExecutor(); // close 后复用导致不好修复的 NPE getSQLExecutor(); + executor.setParser(parser); + M result; try { result = executor.execute(config, false); } finally { executor.close(); } - if (result != null && JSONResponse.isExist(result.getIntValue(JSONResponse.KEY_COUNT)) == false) { + if (result != null && JSONResponse.isExist(result) == false) { throw new IllegalArgumentException(rk + ":value 中value不合法!必须匹配 '" + tk + "': '" + tv + "' !"); } } @@ -1731,7 +1746,8 @@ private static void verifyCondition(@NotNull String funChar, @NotNull JSONObject * @param value * @throws Exception */ - public static void verifyExist(String table, String key, Object value, long exceptId, @NotNull SQLCreator creator) throws Exception { + public static , L extends List>void verifyExist(String table, String key + , Object value, long exceptId, @NotNull Parser parser) throws Exception { if (key == null || value == null) { Log.e(TAG, "verifyExist key == null || value == null >> return;"); return; @@ -1741,7 +1757,7 @@ public static void verifyExist(String table, String key, Object value, long exce } Map map = new HashMap<>(); map.put(key,value); - verifyExist(table,map,exceptId,creator); + verifyExist(table,map,exceptId,parser); } /**验证是否存在 @@ -1749,23 +1765,24 @@ public static void verifyExist(String table, String key, Object value, long exce * @param param * @throws Exception */ - public static void verifyExist(String table, Map param, long exceptId, @NotNull SQLCreator creator) throws Exception { + public static , L extends List> void verifyExist(String table + , Map param, long exceptId, @NotNull Parser parser) throws Exception { if (param.isEmpty()) { Log.e(TAG, "verifyExist is empty >> return;"); return; } - SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); + SQLConfig config = parser.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); config.setTable(table); param.forEach((key,value) -> config.putWhere(key, value, false)); - SQLExecutor executor = creator.createSQLExecutor(); + SQLExecutor executor = parser.getSQLExecutor(); try { - JSONObject result = executor.execute(config, false); + M result = executor.execute(config, false); if (result == null) { throw new Exception("服务器内部错误 verifyExist result == null"); } - if (result.getIntValue(JSONResponse.KEY_COUNT) <= 0) { + if (getIntValue(result, JSONResponse.KEY_COUNT) <= 0) { StringBuilder sb = new StringBuilder(); param.forEach((key,value) -> sb.append("key:").append(key).append(" value:").append(value).append(" ")); throw new ConflictException(sb + "的数据不存在!如果必要请先创建!"); @@ -1781,8 +1798,9 @@ public static void verifyExist(String table, Map param, long exce * @param value * @throws Exception */ - public static void verifyRepeat(String table, String key, Object value, @NotNull SQLCreator creator) throws Exception { - verifyRepeat(table, key, value, 0, creator); + public static , L extends List> void verifyRepeat(String table, String key + , Object value, @NotNull Parser parser) throws Exception { + verifyRepeat(table, key, value, 0, parser); } /**验证是否重复 @@ -1792,8 +1810,9 @@ public static void verifyRepeat(String table, String key, Object value, @NotNull * @param exceptId 不包含id * @throws Exception */ - public static void verifyRepeat(String table, String key, Object value, long exceptId, @NotNull SQLCreator creator) throws Exception { - verifyRepeat(table, key, value, exceptId, null, creator); + public static , L extends List> void verifyRepeat(String table, String key + , Object value, long exceptId, @NotNull Parser parser) throws Exception { + verifyRepeat(table, key, value, exceptId, null, parser); } /**验证是否重复 @@ -1803,11 +1822,11 @@ public static void verifyRepeat(String table, String key, Object value, long exc * @param value * @param exceptId 不包含id * @param idKey - * @param creator + * @param parser * @throws Exception */ - public static void verifyRepeat(String table, String key, Object value - , long exceptId, String idKey, @NotNull SQLCreator creator) throws Exception { + public static , L extends List>void verifyRepeat(String table, String key + , Object value, long exceptId, String idKey, @NotNull Parser parser) throws Exception { if (key == null || value == null) { Log.e(TAG, "verifyRepeat key == null || value == null >> return;"); return; @@ -1817,7 +1836,7 @@ public static void verifyRepeat(String table, String key, Object value } Map map = new HashMap<>(); map.put(key,value); - verifyRepeat(table,map,exceptId,idKey,creator); + verifyRepeat(table, map, exceptId, idKey, parser); } /**验证是否重复 @@ -1826,31 +1845,32 @@ public static void verifyRepeat(String table, String key, Object value * @param param * @param exceptId 不包含id * @param idKey - * @param creator + * @param parser * @throws Exception */ - public static void verifyRepeat(String table, Map param, long exceptId, String idKey, @NotNull SQLCreator creator) throws Exception { + public static , L extends List> void verifyRepeat(String table + , Map param, long exceptId, String idKey, @NotNull Parser parser) throws Exception { if (param.isEmpty()) { Log.e(TAG, "verifyRepeat is empty >> return;"); return; } - String finalIdKey = StringUtil.isEmpty(idKey, false) ? apijson.JSONObject.KEY_ID : idKey; + String finalIdKey = StringUtil.isEmpty(idKey, false) ? JSONMap.KEY_ID : idKey; - SQLConfig config = creator.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); + SQLConfig config = parser.createSQLConfig().setMethod(RequestMethod.HEAD).setCount(1).setPage(0); config.setTable(table); if (exceptId > 0) { //允许修改自己的属性为该属性原来的值 config.putWhere(finalIdKey + "!", exceptId, false); } param.forEach((key,value) -> config.putWhere(key,value, false)); - SQLExecutor executor = creator.createSQLExecutor(); + SQLExecutor executor = parser.getSQLExecutor(); try { - JSONObject result = executor.execute(config, false); + M result = executor.execute(config, false); if (result == null) { throw new Exception("服务器内部错误 verifyRepeat result == null"); } - if (result.getIntValue(JSONResponse.KEY_COUNT) > 0) { + if (getIntValue(result, JSONResponse.KEY_COUNT) > 0) { StringBuilder sb = new StringBuilder(); param.forEach((key,value) -> sb.append("key:").append(key).append(" value:").append(value).append(" ")); throw new ConflictException(sb + "的数据已经存在,不能重复!"); @@ -1865,5 +1885,4 @@ public static String getCacheKeyForRequest(String method, String tag) { return method + "/" + tag; } - } 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 ec5aefbd6..8af029234 100644 --- a/APIJSONORM/src/main/java/apijson/orm/FunctionParser.java +++ b/APIJSONORM/src/main/java/apijson/orm/FunctionParser.java @@ -1,55 +1,54 @@ -/*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 com.alibaba.fastjson.JSONObject; +import java.util.List; +import java.util.Map; -import apijson.NotNull; -import apijson.RequestMethod; +import apijson.*; /**远程函数解析器 * @author Lemon */ -public interface FunctionParser { +public interface FunctionParser, L extends List> { - Object invoke(@NotNull String function, @NotNull JSONObject currentObject) throws Exception; - Object invoke(@NotNull String function, @NotNull JSONObject currentObject, boolean containRaw) throws Exception; + Object invoke(@NotNull String function, @NotNull M currentObject) throws Exception; + Object invoke(@NotNull String function, @NotNull M currentObject, boolean containRaw) throws Exception; - Parser getParser(); + Parser getParser(); - FunctionParser setParser(Parser parser); + FunctionParser setParser(Parser parser); RequestMethod getMethod(); - FunctionParser setMethod(RequestMethod method); + FunctionParser setMethod(RequestMethod method); String getTag(); - FunctionParser setTag(String tag); + FunctionParser setTag(String tag); int getVersion(); - FunctionParser setVersion(int version); + FunctionParser setVersion(int version); @NotNull - JSONObject getRequest(); - FunctionParser setRequest(@NotNull JSONObject request); + M getRequest(); + FunctionParser setRequest(@NotNull M request); String getKey(); - FunctionParser setKey(String key); + FunctionParser setKey(String key); String getParentPath(); - FunctionParser setParentPath(String parentPath); + FunctionParser setParentPath(String parentPath); String getCurrentName(); - FunctionParser setCurrentName(String currentName); - - @NotNull - JSONObject getCurrentObject(); - FunctionParser setCurrentObject(@NotNull JSONObject currentObject); + FunctionParser setCurrentName(String currentName); + @NotNull + M getCurrentObject(); + FunctionParser setCurrentObject(@NotNull M currentObject); } diff --git a/APIJSONORM/src/main/java/apijson/orm/JSONRequest.java b/APIJSONORM/src/main/java/apijson/orm/JSONRequest.java index 89d17f7d2..40bfa147a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/JSONRequest.java +++ b/APIJSONORM/src/main/java/apijson/orm/JSONRequest.java @@ -1,23 +1,23 @@ -/*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.Map; +import java.util.*; import apijson.JSON; import apijson.StringUtil; -/**JSONRequest for Server to replace apijson.JSONRequest, - * put JSON.parseObject(value) and not encode in default cases +/**JSONRequest for Server to replace apijson.JSONMap, + * put JSON.parseObject(value) and not encode in public cases * @author Lemon - * @see #put(String, Object, boolean) + * @see #put(String, Object) */ -public class JSONRequest extends apijson.JSONRequest { - private static final long serialVersionUID = 1L; - +public class JSONRequest implements apijson.JSONRequest, ArrayList> { + + protected Map map = new LinkedHashMap<>(); public JSONRequest() { super(); } @@ -27,21 +27,40 @@ public JSONRequest() { * @param object */ public JSONRequest(Object object) { - super(object); + super(); + put(object); } /** * @param name * @param object */ public JSONRequest(String name, Object object) { - super(name, object); + super(); + put(name, object); } - - + ///**create a parent JSONMap named KEY_ARRAY + // * @param count + // * @param page + // * @return {@link #toArray(int, int)} + // */ + //public LinkedHashMap toArray(int count, int page) { + // return toArray(count, page, null); + //} + // + ///**create a parent JSONMap named name+KEY_ARRAY. + // * @param count + // * @param page + // * @param name + // * @return {name+KEY_ARRAY : this}. if needs to be put, use {@link #putsAll(Map)} instead + // */ + //public LinkedHashMap toArray(int count, int page, String name) { + // return new JSONRequest(StringUtil.get(name) + KEY_ARRAY, this.setCount(count).setPage(page)); + //} + @Override - public JSONRequest putsAll(Map map) { - super.putsAll(map); + public JSONRequest putsAll(Map m) { + putAll(m); return this; } @@ -51,7 +70,8 @@ public JSONRequest putsAll(Map map) { */ @Override public JSONRequest puts(Object value) { - return puts(null, value); + put(value); + return this; } /** * @param key @@ -65,14 +85,7 @@ public JSONRequest puts(String key, Object value) { return this; } - /** - * @param value - * @return {@link #put(String, Object)} - */ - @Override - public Object put(Object value) { - return put(null, value); - } + /**自定义类型必须转为JSONObject或JSONArray,否则RequestParser解析不了 */ @Override @@ -81,12 +94,79 @@ public Object put(String key, Object value) { return null; } - Object target = JSON.parse(value); - // if (target == null) { // "tag":"User" 报错 + Object target = null; + try { + target = JSON.parse(value); + } catch (Exception e) { + // nothing + e.printStackTrace(); + } + // if (target == null) { // "tag":"User" 报错 // return null; // } - return super.put(StringUtil.isNotEmpty(key, true) ? key : value.getClass().getSimpleName() //must handle key here + return map.put(StringUtil.isNotEmpty(key, true) ? key : value.getClass().getSimpleName() //must handle key here , target == null ? value : target); } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public Object get(Object key) { + return map.get(key); + } + + @Override + public Object remove(Object key) { + return map.remove(key); + } + + + @Override + public void clear() { + map.clear(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + + @Override + public String toString() { + return JSON.toJSONString(map); + } + + public String toJSONString() { + return JSON.toJSONString(map); + } + } diff --git a/APIJSONORM/src/main/java/apijson/orm/Join.java b/APIJSONORM/src/main/java/apijson/orm/Join.java index f648ff8bf..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.*/ @@ -6,8 +6,7 @@ package apijson.orm; import java.util.List; - -import com.alibaba.fastjson.JSONObject; +import java.util.Map; import apijson.NotNull; import apijson.StringUtil; @@ -15,23 +14,25 @@ /**连表 配置 * @author Lemon */ -public class Join { +public class Join, L extends List> { private String path; // /User/id@ - private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN + private String joinType; // "@" - APP, "<" - LEFT, ">" - RIGHT, "*" - CROSS, "&" - INNER, "|" - FULL, "!" - OUTER, "^" - SIDE, "(" - ANTI, ")" - FOREIGN, "~" ASOF private String table; // User private String alias; // owner private int count = 1; // 当app join子表,需要返回子表的行数,默认1行; private List onList; // ON User.id = Moment.userId AND ... - private JSONObject request; // { "id@":"/Moment/userId" } - private JSONObject outer; // "join": { " joinConfig; + private SQLConfig cacheConfig; + private SQLConfig onConfig; + private SQLConfig outerConfig; public String getPath() { return path; @@ -73,36 +74,52 @@ public void setOnList(List onList) { this.onList = onList; } - public JSONObject getRequest() { + public M getRequest() { return request; } - public void setRequest(JSONObject request) { + public void setRequest(M request) { this.request = request; } - public JSONObject getOuter() { - return outer; + public M getOn() { + return on; } - public void setOuter(JSONObject outer) { + public void setOn(M on) { + this.on = on; + } + + public void setOuter(M outer) { this.outer = outer; } - public SQLConfig getJoinConfig() { + public M getOuter() { + return outer; + } + + public SQLConfig getOuterConfig() { + return outerConfig; + } + + public void setOuterConfig(SQLConfig outerConfig) { + this.outerConfig = outerConfig; + } + + public SQLConfig getJoinConfig() { return joinConfig; } - public void setJoinConfig(SQLConfig joinConfig) { + public void setJoinConfig(SQLConfig joinConfig) { this.joinConfig = joinConfig; } - public SQLConfig getCacheConfig() { + public SQLConfig getCacheConfig() { return cacheConfig; } - public void setCacheConfig(SQLConfig cacheConfig) { + 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() { @@ -143,6 +160,9 @@ public boolean isAntiJoin() { public boolean isForeignJoin() { return ")".equals(getJoinType()); } + public boolean isAsofJoin() { + return "~".equals(getJoinType()); + } public boolean isLeftOrRightJoin() { String jt = getJoinType(); @@ -159,15 +179,15 @@ public boolean isSQLJoin() { return ! isAppJoin(); } - public static boolean isSQLJoin(Join j) { + public static boolean isSQLJoin(Join j) { return j != null && j.isSQLJoin(); } - public static boolean isAppJoin(Join j) { + public static boolean isAppJoin(Join j) { return j != null && j.isAppJoin(); } - public static boolean isLeftOrRightJoin(Join j) { + public static boolean isLeftOrRightJoin(Join j) { return j != null && j.isLeftOrRightJoin(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/Logic.java b/APIJSONORM/src/main/java/apijson/orm/Logic.java index cfc08d016..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.*/ @@ -42,7 +42,7 @@ public Logic(int type) { } public Logic(String key) { this.originKey = key; - key = StringUtil.getString(key); + key = StringUtil.get(key); int type = getType(key.isEmpty() ? "" : key.substring(key.length() - 1)); diff --git a/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java b/APIJSONORM/src/main/java/apijson/orm/ObjectParser.java index 205126908..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.*/ @@ -8,26 +8,21 @@ import java.util.List; import java.util.Map; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import apijson.NotNull; -import apijson.RequestMethod; +import apijson.*; /**简化Parser,getObject和getArray(getArrayConfig)都能用 * @author Lemon */ -public interface ObjectParser { +public interface ObjectParser, L extends List> { - Parser getParser(); - ObjectParser setParser(Parser parser); + Parser getParser(); + ObjectParser setParser(Parser parser); String getParentPath(); - ObjectParser setParentPath(String parentPath); + ObjectParser setParentPath(String parentPath); - ObjectParser setCache(JSONObject cache); - JSONObject getCache(); + ObjectParser setCache(M cache); + M getCache(); /**解析成员 @@ -37,7 +32,7 @@ public interface ObjectParser { * @return null or this * @throws Exception */ - ObjectParser parse(String name, boolean isReuse) throws Exception; + ObjectParser parse(String name, boolean isReuse) throws Exception; /**调用 parser 的 sqlExecutor 来解析结果 * @param method @@ -49,14 +44,14 @@ public interface ObjectParser { * @return * @throws Exception */ - JSONObject parseResponse(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure) throws Exception; + M parseResponse(RequestMethod method, String table, String alias, M request, List> joinList, boolean isProcedure) throws Exception; /**调用 parser 的 sqlExecutor 来解析结果 * @param config * @param isProcedure * @return * @throws Exception */ - JSONObject parseResponse(SQLConfig config, boolean isProcedure) throws Exception; + M parseResponse(SQLConfig config, boolean isProcedure) throws Exception; @@ -75,7 +70,7 @@ public interface ObjectParser { * @return * @throws Exception */ - JSON onChildParse(int index, String key, JSONObject value, JSON cache) throws Exception; + Object onChildParse(int index, String key, M value, Object cache) throws Exception; /**解析赋值引用 * @param path @@ -89,55 +84,55 @@ public interface ObjectParser { * @param array * @throws Exception */ - void onPUTArrayParse(@NotNull String key, @NotNull JSONArray array) throws Exception; + void onPUTArrayParse(@NotNull String key, @NotNull L array) throws Exception; /**批量新增或修改 POST or PUT Table[]:[{}] * @param key * @param array * @throws Exception */ - void onTableArrayParse(@NotNull String key, @NotNull JSONArray array) throws Exception; + void onTableArrayParse(@NotNull String key, @NotNull L array) throws Exception; /**SQL 配置,for single object * @return {@link #setSQLConfig(int, int, int)} * @throws Exception */ - ObjectParser setSQLConfig() throws Exception; + ObjectParser setSQLConfig() throws Exception; /**SQL 配置 * @return * @throws Exception */ - ObjectParser setSQLConfig(int count, int page, int position) throws Exception; + ObjectParser setSQLConfig(int count, int page, int position) throws Exception; /**执行 SQL * @return * @throws Exception */ - ObjectParser executeSQL() throws Exception; + ObjectParser executeSQL() throws Exception; /** * @return * @throws Exception */ - JSONObject onSQLExecute() throws Exception; + M onSQLExecute() throws Exception; /** * @return response * @throws Exception */ - JSONObject response() throws Exception; + M response() throws Exception; void onFunctionResponse(String type) throws Exception; void onChildResponse() throws Exception; - SQLConfig newSQLConfig(boolean isProcedure) throws Exception; - SQLConfig newSQLConfig(RequestMethod method, String table, String alias, JSONObject request, List joinList, boolean isProcedure) throws Exception; + SQLConfig newSQLConfig(boolean isProcedure) throws Exception; + SQLConfig newSQLConfig(RequestMethod method, String table, String alias, M request, List> joinList, boolean isProcedure) throws Exception; /** * response has the final value after parse (and query if isTableKey) @@ -150,7 +145,7 @@ public interface ObjectParser { void recycle(); - ObjectParser setMethod(RequestMethod method); + ObjectParser setMethod(RequestMethod method); RequestMethod getMethod(); @@ -158,15 +153,15 @@ public interface ObjectParser { String getPath(); String getTable(); String getAlias(); - SQLConfig getArrayConfig(); + SQLConfig getArrayConfig(); - SQLConfig getSQLConfig(); - JSONObject getResponse(); - JSONObject getSqlRequest(); - JSONObject getSqlResponse(); + SQLConfig getSQLConfig(); + M getResponse(); + M getSQLRequest(); + M getSQLResponse(); Map getCustomMap(); Map> getFunctionMap(); - Map getChildMap(); + Map getChildMap(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/OnParseCallback.java b/APIJSONORM/src/main/java/apijson/orm/OnParseCallback.java index 952c41e3b..e02fd90f8 100755 --- a/APIJSONORM/src/main/java/apijson/orm/OnParseCallback.java +++ b/APIJSONORM/src/main/java/apijson/orm/OnParseCallback.java @@ -1,17 +1,17 @@ -/*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 com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; +import java.util.List; +import java.util.Map; /** * @author Lemon */ -public abstract class OnParseCallback { +public abstract class OnParseCallback, L extends List> { /** @@ -43,7 +43,7 @@ protected Object onParseObject(String key, Object to, Object ro) throws Exceptio * @return * @throws Exception */ - protected JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject robj) throws Exception { + protected M onParseJSONObject(String key, M tobj, M robj) throws Exception { return robj; } @@ -54,7 +54,7 @@ protected JSONObject onParseJSONObject(String key, JSONObject tobj, JSONObject r * @return * @throws Exception */ - protected JSONArray onParseJSONArray(String key, JSONArray tarray, JSONArray rarray) throws Exception { + protected L onParseJSONArray(String key, L tarray, L rarray) throws Exception { return rarray; } diff --git a/APIJSONORM/src/main/java/apijson/orm/Operation.java b/APIJSONORM/src/main/java/apijson/orm/Operation.java index 2976d09b0..1c4f2dc5f 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.*/ diff --git a/APIJSONORM/src/main/java/apijson/orm/Pair.java b/APIJSONORM/src/main/java/apijson/orm/Pair.java index a1f471c42..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.*/ @@ -9,9 +9,6 @@ import java.util.HashMap; import java.util.Map; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - import apijson.StringUtil; /**key:value @@ -37,8 +34,8 @@ public class Pair extends Entry { CLASS_MAP.put(String.class.getSimpleName(), String.class); CLASS_MAP.put(Collection.class.getSimpleName(), Collection.class);//不允许指定 CLASS_MAP.put(Map.class.getSimpleName(), Map.class);//不允许指定 - CLASS_MAP.put(JSONObject.class.getSimpleName(), JSONObject.class);//必须有,Map中没有getLongValue等方法 - CLASS_MAP.put(JSONArray.class.getSimpleName(), JSONArray.class);//必须有,Collection中没有getJSONObject等方法 +// CLASS_MAP.put(JSONMap.class.getSimpleName(), JSONMap.class);//必须有,Map中没有getLongValue等方法 +// CLASS_MAP.put(JSONList.class.getSimpleName(), JSONList.class);//必须有,Collection中没有getJSONObject等方法 } @@ -60,14 +57,14 @@ public static boolean isCorrect(Entry pair) { } /** - * @param pair * @return */ public String toPairString() { return toPairString(getKey(), getValue()); } /** - * @param pair + * @param typeKey + * @param valueKey * @return */ public static String toPairString(String typeKey, String valueKey) { @@ -79,7 +76,7 @@ public static String toPairString(String typeKey, String valueKey) { * @return */ public static String toPairString(Class type, Object value) { - return toPairString(type == null ? null : type.getSimpleName(), StringUtil.getString(value)); + return toPairString(type == null ? null : type.getSimpleName(), StringUtil.get(value)); } /** @@ -109,7 +106,7 @@ public static Entry parseEntry(String pair, boolean isRightValue * @return @NonNull */ public static Entry parseEntry(String pair, boolean isRightValueDefault, String defaultValue) { - pair = StringUtil.getString(pair);//让客户端去掉所有空格 getNoBlankString(pair); + pair = StringUtil.get(pair);//让客户端去掉所有空格 getNoBlankString(pair); Entry entry = new Entry(); if (pair.isEmpty() == false) { int index = pair.indexOf(":"); @@ -137,7 +134,7 @@ public static Entry parseVariableEntry(String pair) { * @return */ public static Entry, Object> parseVariableEntry(String pair, Map valueMap) { - pair = StringUtil.getString(pair);//让客户端去掉所有空格 getNoBlankString(pair); + pair = StringUtil.get(pair);//让客户端去掉所有空格 getNoBlankString(pair); Entry, Object> entry = new Entry, Object>(); if (pair.isEmpty() == false) { int index = pair.contains(":") ? pair.indexOf(":") : -1; diff --git a/APIJSONORM/src/main/java/apijson/orm/Parser.java b/APIJSONORM/src/main/java/apijson/orm/Parser.java index a272fc27a..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.*/ @@ -7,68 +7,67 @@ import java.sql.SQLException; import java.sql.Savepoint; +import java.util.List; +import java.util.Map; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; - -import apijson.NotNull; -import apijson.RequestMethod; +import apijson.*; /**解析器 * @author Lemon */ -public interface Parser { +public interface Parser, L extends List> + extends ParserCreator, VerifierCreator, SQLCreator { @NotNull Visitor getVisitor(); - Parser setVisitor(@NotNull Visitor visitor); + Parser setVisitor(@NotNull Visitor visitor); @NotNull RequestMethod getMethod(); - Parser setMethod(@NotNull RequestMethod method); + Parser setMethod(@NotNull RequestMethod method); int getVersion(); - Parser setVersion(int version); + Parser setVersion(int version); String getTag(); - Parser setTag(String tag); + Parser setTag(String tag); - JSONObject getRequest(); - Parser setRequest(JSONObject request); + M getRequest(); + Parser setRequest(M request); - Parser setNeedVerify(boolean needVerify); + Parser setNeedVerify(boolean needVerify); boolean isNeedVerifyLogin(); - Parser setNeedVerifyLogin(boolean needVerifyLogin); + Parser setNeedVerifyLogin(boolean needVerifyLogin); boolean isNeedVerifyRole(); - Parser setNeedVerifyRole(boolean needVerifyRole); + Parser setNeedVerifyRole(boolean needVerifyRole); boolean isNeedVerifyContent(); - Parser setNeedVerifyContent(boolean needVerifyContent); + Parser setNeedVerifyContent(boolean needVerifyContent); String parse(String request); - String parse(JSONObject request); + String parse(M request); - JSONObject parseResponse(String request); - JSONObject parseResponse(JSONObject request); + M parseResponse(String request); + M parseResponse(M request); - // 没必要性能还差 JSONObject parseCorrectResponse(String table, JSONObject response) throws Exception; + // 没必要性能还差 JSONRequest parseCorrectResponse(String table, JSONRequest response) throws Exception; - JSONObject parseCorrectRequest() throws Exception; - - JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, String name, JSONObject request, - int maxUpdateCount, SQLCreator creator) throws Exception; - + M parseCorrectRequest() throws Exception; - JSONObject getStructure(String table, String method, String tag, int version) throws Exception; + M parseCorrectRequest(RequestMethod method, String tag, int version, String name, M request, + int maxUpdateCount, SQLCreator creator) throws Exception; + + + Map getStructure(String table, String method, String tag, int version) throws Exception; - JSONObject onObjectParse(JSONObject request, String parentPath, String name, SQLConfig arrayConfig, boolean isSubquery, JSONObject cache) throws Exception; + M onObjectParse(M request, String parentPath, String name, SQLConfig arrayConfig, boolean isSubquery, M cache) throws Exception; - JSONArray onArrayParse(JSONObject request, String parentPath, String name, boolean isSubquery, JSONArray cache) throws Exception; + L onArrayParse(M request, String parentPath, String name, boolean isSubquery, L cache) throws Exception; /**解析远程函数 * @param key @@ -79,9 +78,9 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St * @return * @throws Exception */ - Object onFunctionParse(String key, String function, String parentPath, String currentName, JSONObject currentObject, boolean containRaw) throws Exception; + Object onFunctionParse(String key, String function, String parentPath, String currentName, M currentObject, boolean containRaw) throws Exception; - ObjectParser createObjectParser(JSONObject request, String parentPath, SQLConfig arrayConfig, boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception; + ObjectParser createObjectParser(M request, String parentPath, SQLConfig arrayConfig, boolean isSubquery, boolean isTable, boolean isArrayMainTable) throws Exception; int getMinQueryPage(); int getMaxQueryPage(); @@ -101,12 +100,12 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St void onVerifyLogin() throws Exception; void onVerifyContent() throws Exception; - void onVerifyRole(SQLConfig config) throws Exception; + void onVerifyRole(SQLConfig config) throws Exception; - JSONObject executeSQL(SQLConfig config, boolean isSubquery) throws Exception; + M executeSQL(SQLConfig config, boolean isSubquery) throws Exception; - SQLExecutor getSQLExecutor(); - Verifier getVerifier(); + SQLExecutor getSQLExecutor(); + Verifier getVerifier(); Boolean getGlobalFormat(); @@ -129,5 +128,7 @@ JSONObject parseCorrectRequest(RequestMethod method, String tag, int version, St void commit() throws SQLException; void close(); + M newSuccessResult(); + M newErrorResult(Exception e); } diff --git a/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java b/APIJSONORM/src/main/java/apijson/orm/ParserCreator.java index 4da410b46..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.*/ @@ -7,14 +7,17 @@ import apijson.NotNull; +import java.util.List; +import java.util.Map; + /**SQL相关创建器 * @author Lemon */ -public interface ParserCreator { +public interface ParserCreator, L extends List> { @NotNull - Parser createParser(); + Parser createParser(); @NotNull - FunctionParser createFunctionParser(); + FunctionParser createFunctionParser(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java b/APIJSONORM/src/main/java/apijson/orm/SQLConfig.java index 64e6273ee..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.*/ @@ -15,7 +15,7 @@ /**SQL配置 * @author Lemon */ -public interface SQLConfig { +public interface SQLConfig, L extends List> { String DATABASE_MYSQL = "MYSQL"; // https://www.mysql.com String DATABASE_POSTGRESQL = "POSTGRESQL"; // https://www.postgresql.org @@ -33,13 +33,16 @@ public interface SQLConfig { String DATABASE_HIVE = "HIVE"; // https://hive.apache.org String DATABASE_PRESTO = "PRESTO"; // Facebook PrestoDB https://prestodb.io String DATABASE_TRINO = "TRINO"; // PrestoSQL https://trino.io + String DATABASE_DORIS = "DORIS"; // https://doris.apache.org String DATABASE_SNOWFLAKE = "SNOWFLAKE"; // https://www.snowflake.com + String DATABASE_DATABEND = "DATABEND"; // https://www.databend.com String DATABASE_DATABRICKS = "DATABRICKS"; // https://www.databricks.com String DATABASE_CASSANDRA = "CASSANDRA"; // https://cassandra.apache.org String DATABASE_MILVUS = "MILVUS"; // https://milvus.io String DATABASE_INFLUXDB = "INFLUXDB"; // https://www.influxdata.com/products/influxdb-overview String DATABASE_TDENGINE = "TDENGINE"; // https://tdengine.com String DATABASE_TIMESCALEDB = "TIMESCALEDB"; // https://www.timescale.com + String DATABASE_QUESTDB = "QUESTDB"; // https://questdb.com String DATABASE_IOTDB = "IOTDB"; // https://iotdb.apache.org/zh/UserGuide/latest/API/Programming-JDBC.html String DATABASE_REDIS = "REDIS"; // https://redisql.com @@ -61,21 +64,25 @@ public interface SQLConfig { int TYPE_ITEM = 1; int TYPE_ITEM_CHILD_0 = 2; - Parser getParser(); + Parser gainParser(); - SQLConfig setParser(Parser parser); + SQLConfig setParser(Parser parser); - ObjectParser getObjectParser(); + ObjectParser gainObjectParser(); - SQLConfig setObjectParser(ObjectParser objectParser); + SQLConfig setObjectParser(ObjectParser objectParser); int getVersion(); - SQLConfig setVersion(int version); + SQLConfig setVersion(int version); String getTag(); - SQLConfig setTag(String tag); + SQLConfig setTag(String tag); + + boolean isTSQL(); + boolean isMSQL(); + boolean isPSQL(); boolean isMySQL(); boolean isPostgreSQL(); @@ -94,12 +101,14 @@ public interface SQLConfig { boolean isPresto(); boolean isTrino(); boolean isSnowflake(); + boolean isDatabend(); boolean isDatabricks(); boolean isCassandra(); boolean isMilvus(); boolean isInfluxDB(); boolean isTDengine(); boolean isTimescaleDB(); + boolean isQuestDB(); boolean isIoTDB(); boolean isRedis(); boolean isMongoDB(); @@ -109,6 +118,7 @@ public interface SQLConfig { boolean isDuckDB(); boolean isSurrealDB(); boolean isOpenGauss(); + boolean isDoris(); // 暂时只兼容以上几种 @@ -141,11 +151,11 @@ public interface SQLConfig { * MYSQL: 8.0, 5.7, 5.6 等; PostgreSQL: 11, 10, 9.6 等 * @return */ - String getDBVersion(); + String gainDBVersion(); @NotNull - default int[] getDBVersionNums() { - String dbVersion = StringUtil.getNoBlankString(getDBVersion()); + default int[] gainDBVersionNums() { + String dbVersion = StringUtil.noBlank(gainDBVersion()); if (dbVersion.isEmpty()) { return new int[]{0}; } @@ -167,158 +177,170 @@ default int[] getDBVersionNums() { /**获取数据库地址 * @return */ - String getDBUri(); + String gainDBUri(); /**获取数据库账号 * @return */ - String getDBAccount(); + String gainDBAccount(); /**获取数据库密码 * @return */ - String getDBPassword(); + String gainDBPassword(); /**获取SQL语句 * @return * @throws Exception */ - String getSQL(boolean prepared) throws Exception; + String gainSQL(boolean prepared) throws Exception; boolean isTest(); - SQLConfig setTest(boolean test); + SQLConfig setTest(boolean test); int getType(); - SQLConfig setType(int type); + SQLConfig setType(int type); int getCount(); - SQLConfig setCount(int count); + SQLConfig setCount(int count); int getPage(); - SQLConfig setPage(int page); + SQLConfig setPage(int page); int getQuery(); - SQLConfig setQuery(int query); + SQLConfig setQuery(int query); Boolean getCompat(); - SQLConfig setCompat(Boolean compat); + SQLConfig setCompat(Boolean compat); int getPosition(); - SQLConfig setPosition(int position); + SQLConfig setPosition(int position); int getCache(); - SQLConfig setCache(int cache); + SQLConfig setCache(int cache); boolean isExplain(); - SQLConfig setExplain(boolean explain); + SQLConfig setExplain(boolean explain); RequestMethod getMethod(); - SQLConfig setMethod(RequestMethod method); + SQLConfig setMethod(RequestMethod method); Object getId(); - SQLConfig setId(Object id); + SQLConfig setId(Object id); Object getIdIn(); - SQLConfig setIdIn(Object idIn); + SQLConfig setIdIn(Object idIn); Object getUserId(); - SQLConfig setUserId(Object userId); + SQLConfig setUserId(Object userId); Object getUserIdIn(); - SQLConfig setUserIdIn(Object userIdIn); + SQLConfig setUserIdIn(Object userIdIn); String getRole(); - SQLConfig setRole(String role); + SQLConfig setRole(String role); public boolean isDistinct(); - public SQLConfig setDistinct(boolean distinct); + public SQLConfig setDistinct(boolean distinct); String getDatabase(); - SQLConfig setDatabase(String database); + SQLConfig setDatabase(String database); String getSQLNamespace(); String getNamespace(); - SQLConfig setNamespace(String namespace); + SQLConfig setNamespace(String namespace); - String getSQLCatalog(); + String gainSQLCatalog(); String getCatalog(); - SQLConfig setCatalog(String catalog); + SQLConfig setCatalog(String catalog); - String getSQLSchema(); + String gainSQLSchema(); String getSchema(); - SQLConfig setSchema(String schema); + SQLConfig setSchema(String schema); String getDatasource(); - SQLConfig setDatasource(String datasource); + SQLConfig setDatasource(String datasource); String getQuote(); List getJson(); - SQLConfig setJson(List json); + SQLConfig setJson(List json); /**请求传进来的Table名 * @return - * @see {@link #getSQLTable()} + * @see {@link #gainSQLTable()} */ String getTable(); - SQLConfig setTable(String table); + SQLConfig setTable(String table); /**数据库里的真实Table名 * 通过 {@link AbstractSQLConfig.TABLE_KEY_MAP} 映射 * @return */ - String getSQLTable(); + String gainSQLTable(); - String getTablePath(); + String gainTablePath(); Map getKeyMap(); - SQLConfig setKeyMap(Map keyMap); + SQLConfig setKeyMap(Map keyMap); List getRaw(); - SQLConfig setRaw(List raw); + SQLConfig setRaw(List raw); - Subquery getFrom(); - SQLConfig setFrom(Subquery from); + Subquery getFrom(); + SQLConfig setFrom(Subquery from); List getColumn(); - SQLConfig setColumn(List column); + SQLConfig setColumn(List column); List> getValues(); - SQLConfig setValues(List> values); + SQLConfig setValues(List> values); Map getContent(); - SQLConfig setContent(Map content); + SQLConfig setContent(Map content); Map> getCombineMap(); - SQLConfig setCombineMap(Map> combineMap); + SQLConfig setCombineMap(Map> combineMap); String getCombine(); - SQLConfig setCombine(String combine); + SQLConfig setCombine(String combine); Map getCast(); - SQLConfig setCast(Map cast); + SQLConfig setCast(Map cast); List getNull(); - SQLConfig setNull(List nulls); + SQLConfig setNull(List nulls); Map getWhere(); - SQLConfig setWhere(Map where); + SQLConfig setWhere(Map where); String getGroup(); - SQLConfig setGroup(String group); + SQLConfig setGroup(String group); Map getHaving(); - SQLConfig setHaving(Map having); + SQLConfig setHaving(Map having); String getHavingCombine(); - SQLConfig setHavingCombine(String havingCombine); + SQLConfig setHavingCombine(String havingCombine); + + String getSample(); + SQLConfig setSample(String order); + + String getLatest(); + SQLConfig setLatest(String latest); + + String getPartition(); + SQLConfig setPartition(String partition); + + String getFill(); + SQLConfig setFill(String fill); String getOrder(); - SQLConfig setOrder(String order); + SQLConfig setOrder(String order); /** * exactMatch = false @@ -337,65 +359,62 @@ default int[] getDBVersionNums() { * @param value * @return */ - SQLConfig putWhere(String key, Object value, boolean prior); + SQLConfig putWhere(String key, Object value, boolean prior); boolean isPrepared(); - SQLConfig setPrepared(boolean prepared); + SQLConfig setPrepared(boolean prepared); boolean isMain(); - SQLConfig setMain(boolean main); + SQLConfig setMain(boolean main); List getPreparedValueList(); - SQLConfig setPreparedValueList(List preparedValueList); + SQLConfig setPreparedValueList(List preparedValueList); String getAlias(); - SQLConfig setAlias(String alias); + SQLConfig setAlias(String alias); - default String getTableKey() { + default String gainTableKey() { String alias = getAlias(); return getTable() + (StringUtil.isEmpty(alias) ? "" : ":" + alias); } - default String getSQLAlias() { - return getSQLAlias(getTable(), getAlias()); + default String gainSQLAlias() { + return gainSQLAlias(getTable(), getAlias()); } - static String getSQLAlias(@NotNull String table, String alias) { + static String gainSQLAlias(@NotNull String table, String alias) { // 这里不用 : $ 等符号,因为部分数据库/引擎似乎不支持 `key`, "key", [key] 等避免关键词冲突的方式,只能使用符合变量命名的表别名 return StringUtil.isEmpty(alias) ? table : table + "__" + alias; // 带上原表名,避免 alias 和其它表名/字段名冲突 } - String getWhereString(boolean hasPrefix) throws Exception; + String gainWhereString(boolean hasPrefix) throws Exception; - String getRawSQL(String key, Object value) throws Exception; - String getRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception; + String gainRawSQL(String key, Object value) throws Exception; + String gainRawSQL(String key, Object value, boolean throwWhenMissing) throws Exception; boolean isKeyPrefix(); - SQLConfig setKeyPrefix(boolean keyPrefix); - + SQLConfig setKeyPrefix(boolean keyPrefix); - List getJoinList(); - - SQLConfig setJoinList(List joinList); + List> getJoinList(); + SQLConfig setJoinList(List> joinList); boolean hasJoin(); - String getSubqueryString(Subquery subquery) throws Exception; - - SQLConfig setProcedure(String procedure); + String gainSubqueryString(Subquery subquery) throws Exception; + SQLConfig setProcedure(String procedure); List getWithAsExprPreparedValueList(); - SQLConfig setWithAsExprPreparedValueList(List withAsExprePreparedValueList); + SQLConfig setWithAsExprPreparedValueList(List withAsExprePreparedValueList); boolean isFakeDelete(); diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLCreator.java b/APIJSONORM/src/main/java/apijson/orm/SQLCreator.java index 11d7d2c89..77625cfa6 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLCreator.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLCreator.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,17 @@ import apijson.NotNull; +import java.util.List; +import java.util.Map; + /**SQL相关创建器 * @author Lemon */ -public interface SQLCreator { +public interface SQLCreator, L extends List> { @NotNull - SQLConfig createSQLConfig(); + SQLConfig createSQLConfig(); @NotNull - SQLExecutor createSQLExecutor(); + SQLExecutor createSQLExecutor(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java index 2805682e8..6540771d7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/SQLExecutor.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.*/ @@ -12,31 +12,30 @@ import java.sql.Savepoint; import java.sql.Statement; import java.util.List; +import java.util.Map; -import com.alibaba.fastjson.JSONObject; - -import apijson.NotNull; +import apijson.*; /**executor for query(read) or update(write) MySQL database * @author Lemon */ -public interface SQLExecutor { - Parser getParser(); - SQLExecutor setParser(Parser parser); +public interface SQLExecutor, L extends List> { + Parser getParser(); + SQLExecutor setParser(Parser parser); /**保存缓存 * @param sql * @param list * @param config */ - void putCache(String sql, List list, SQLConfig config); + void putCache(String sql, List list, SQLConfig config); /**获取缓存 * @param sql * @param config * @return */ - List getCache(String sql, SQLConfig config); + List getCache(String sql, SQLConfig config); /**获取缓存 * @param sql @@ -44,13 +43,13 @@ public interface SQLExecutor { * @param config * @return */ - JSONObject getCacheItem(String sql, int position, SQLConfig config); + M getCacheItem(String sql, int position, SQLConfig config); /**移除缓存 * @param sql * @param config */ - void removeCache(String sql, SQLConfig config); + void removeCache(String sql, SQLConfig config); /**执行SQL * @param config @@ -58,7 +57,7 @@ public interface SQLExecutor { * @return * @throws Exception */ - JSONObject execute(@NotNull SQLConfig config, boolean unknownType) throws Exception; + M execute(@NotNull SQLConfig config, boolean unknownType) throws Exception; //executeQuery和executeUpdate这两个函数因为返回类型不同,所以不好合并 /**执行查询 @@ -66,37 +65,37 @@ public interface SQLExecutor { * @return * @throws SQLException */ - default ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { + default ResultSet executeQuery(@NotNull SQLConfig config) throws Exception { return executeQuery(config, null); } - ResultSet executeQuery(@NotNull SQLConfig config, String sql) throws Exception; + ResultSet executeQuery(@NotNull SQLConfig config, String sql) throws Exception; /**执行增、删、改 * @param config * @return * @throws SQLException */ - default int executeUpdate(@NotNull SQLConfig config) throws Exception { + default int executeUpdate(@NotNull SQLConfig config) throws Exception { return executeUpdate(config, null); } - int executeUpdate(@NotNull SQLConfig config, String sql) throws Exception; + int executeUpdate(@NotNull SQLConfig config, String sql) throws Exception; /**判断是否为JSON类型 * @param config * @param rsmd * @param position - * @param lable + * @param label * @return */ - boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData rsmd, int position, String lable); + boolean isJSONType(@NotNull SQLConfig config, ResultSetMetaData rsmd, int position, String label); - Connection getConnection(@NotNull SQLConfig config) throws Exception; - default Statement getStatement(@NotNull SQLConfig config) throws Exception { + Connection getConnection(@NotNull SQLConfig config) throws Exception; + default Statement getStatement(@NotNull SQLConfig config) throws Exception { return getStatement(config, null); } - Statement getStatement(@NotNull SQLConfig config, String sql) throws Exception; + Statement getStatement(@NotNull SQLConfig config, String sql) throws Exception; int getTransactionIsolation(); void setTransactionIsolation(int transactionIsolation); diff --git a/APIJSONORM/src/main/java/apijson/orm/Subquery.java b/APIJSONORM/src/main/java/apijson/orm/Subquery.java index de8603b6d..ce6e72445 100644 --- a/APIJSONORM/src/main/java/apijson/orm/Subquery.java +++ b/APIJSONORM/src/main/java/apijson/orm/Subquery.java @@ -1,83 +1,74 @@ -/*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 com.alibaba.fastjson.JSONObject; -import com.alibaba.fastjson.annotation.JSONField; +import java.util.List; +import java.util.Map; /**子查询 配置 * @author Lemon */ -public class Subquery { +public class Subquery, L extends List> { private String path; // []/0/User private String originKey; //id{}@ - private JSONObject originValue; // { "from": "Comment", "Comment": {...} } + private M originValue; // { "from": "Comment", "Comment": {...} } private String from; // Comment private String range; // ANY, ALL private String key; //id{} - private SQLConfig config; + private SQLConfig config; - @JSONField(serialize = false) //解决泄漏 SQLConfig 里的 dbPassword 等 - public String getPath() { + public String gainPath() { return path; } public void setPath(String path) { this.path = path; } - @JSONField(serialize = false) //解决泄漏 SQLConfig 里的 dbPassword 等 - public String getOriginKey() { + public String gainOriginKey() { return originKey; } public void setOriginKey(String originKey) { this.originKey = originKey; } - @JSONField(serialize = false) //解决泄漏 SQLConfig 里的 dbPassword 等 - public JSONObject getOriginValue() { + public M gainOriginValue() { return originValue; } - public void setOriginValue(JSONObject originValue) { + public void setOriginValue(M originValue) { this.originValue = originValue; } - @JSONField(serialize = false) //解决泄漏 SQLConfig 里的 dbPassword 等 - public String getFrom() { + public String gainFrom() { return from; } public void setFrom(String from) { this.from = from; } - @JSONField(serialize = false) //解决泄漏 SQLConfig 里的 dbPassword 等 - public String getRange() { + public String gainRange() { return range; } public void setRange(String range) { this.range = range; } - @JSONField(serialize = false) //解决泄漏 SQLConfig 里的 dbPassword 等 - public String getKey() { + public String gainKey() { return key; } public void setKey(String key) { this.key = key; } - @JSONField(serialize = false) //解决泄漏 SQLConfig 里的 dbPassword 等 - public SQLConfig getConfig() { + public SQLConfig gainConfig() { return config; } - public void setConfig(SQLConfig config) { + public void setConfig(SQLConfig config) { this.config = config; } - - } diff --git a/APIJSONORM/src/main/java/apijson/orm/Verifier.java b/APIJSONORM/src/main/java/apijson/orm/Verifier.java index a4ab72055..4b926519a 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Verifier.java +++ b/APIJSONORM/src/main/java/apijson/orm/Verifier.java @@ -1,19 +1,19 @@ -/*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 com.alibaba.fastjson.JSONObject; +import java.util.List; +import java.util.Map; -import apijson.NotNull; -import apijson.RequestMethod; +import apijson.*; /**校验器(权限、请求参数、返回结果等) * @author Lemon */ -public interface Verifier { +public interface Verifier, L extends List> { /**验证权限是否通过 @@ -21,7 +21,7 @@ public interface Verifier { * @return * @throws Exception */ - boolean verifyAccess(SQLConfig config) throws Exception; + boolean verifyAccess(SQLConfig config) throws Exception; /**校验请求使用的角色,角色不好判断,让访问者发过来角色名,OWNER,CONTACT,ADMIN等 @@ -31,9 +31,9 @@ public interface Verifier { * @param role * @return * @throws Exception - * @see {@link apijson.JSONObject#KEY_ROLE} + * @see {@link JSONMap#KEY_ROLE} */ - void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception; + void verifyRole(SQLConfig config, String table, RequestMethod method, String role) throws Exception; /**登录校验 * @throws Exception @@ -75,8 +75,8 @@ public interface Verifier { * @return * @throws Exception */ - JSONObject verifyRequest(RequestMethod method, String name, JSONObject target, JSONObject request, - int maxUpdateCount, String globalDatabase, String globalSchema, SQLCreator creator) throws Exception; + M verifyRequest(RequestMethod method, String name, M target, M request, + int maxUpdateCount, String globalDatabase, String globalSchema) throws Exception; /**验证返回结果的数据和结构 * @param method @@ -90,20 +90,22 @@ JSONObject verifyRequest(RequestMethod method, String name, JSONObject target, J * @return * @throws Exception */ - JSONObject verifyResponse( - RequestMethod method, String name, JSONObject target, JSONObject response, - String database, String schema, SQLCreator creator, OnParseCallback callback + M verifyResponse( + RequestMethod method, String name, M target, M response, + String database, String schema, @NotNull Parser parser, OnParseCallback callback ) throws Exception; @NotNull - Parser createParser(); + Parser createParser(); + + Parser getParser(); + Verifier setParser(AbstractParser parser); @NotNull Visitor getVisitor(); - Verifier setVisitor(@NotNull Visitor visitor); + Verifier setVisitor(@NotNull Visitor visitor); - String getVisitorIdKey(SQLConfig config); - + String getVisitorIdKey(SQLConfig config); } diff --git a/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java b/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java index 0caa6f8b1..374290989 100644 --- a/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.java +++ b/APIJSONORM/src/main/java/apijson/orm/VerifierCreator.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,11 +7,14 @@ import apijson.NotNull; +import java.util.List; +import java.util.Map; + /**验证器相关创建器 * @author Lemon */ -public interface VerifierCreator { +public interface VerifierCreator, L extends List> { @NotNull - Verifier createVerifier(); + Verifier createVerifier(); } diff --git a/APIJSONORM/src/main/java/apijson/orm/Visitor.java b/APIJSONORM/src/main/java/apijson/orm/Visitor.java index f474bd91e..5d1b6063d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/Visitor.java +++ b/APIJSONORM/src/main/java/apijson/orm/Visitor.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/exception/CommonException.java b/APIJSONORM/src/main/java/apijson/orm/exception/CommonException.java index 9dab4ea16..7e2884ef7 100755 --- a/APIJSONORM/src/main/java/apijson/orm/exception/CommonException.java +++ b/APIJSONORM/src/main/java/apijson/orm/exception/CommonException.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.*/ @@ -147,7 +147,7 @@ public CommonException(Throwable t, String environment) { } - public static Exception wrap(Exception e, SQLConfig config) { + public static Exception wrap(Exception e, SQLConfig config) { if (Log.DEBUG == false && e instanceof SQLException) { return new SQLException("数据库驱动执行异常SQLException,非 Log.DEBUG 模式下不显示详情,避免泄漏真实模式名、表名等隐私信息", e); } @@ -158,10 +158,10 @@ public static Exception wrap(Exception e, SQLConfig config) { // msg != null && msg.contains(Log.KEY_SYSTEM_INFO_DIVIDER) == false) { try { String db = config == null ? AbstractSQLConfig.DEFAULT_DATABASE : (config instanceof AbstractSQLConfig - ? ((AbstractSQLConfig) config).getSQLDatabase() : config.getDatabase() + ? ((AbstractSQLConfig) config).gainSQLDatabase() : config.getDatabase() ); - String dbVersion = config == null ? null : config.getDBVersion(); + String dbVersion = config == null ? null : config.gainDBVersion(); if (StringUtil.isEmpty(dbVersion)) { dbVersion = ""; } @@ -229,6 +229,9 @@ else if (config.isTDengine()) { else if (config.isTimescaleDB()) { db = SQLConfig.DATABASE_TIMESCALEDB + " " + dbVersion; } + else if (config.isQuestDB()) { + db = SQLConfig.DATABASE_QUESTDB + " " + dbVersion; + } else if (config.isIoTDB()) { db = SQLConfig.DATABASE_IOTDB + " " + dbVersion; } @@ -244,9 +247,15 @@ else if (config.isPresto()) { else if (config.isTrino()) { db = SQLConfig.DATABASE_TRINO + " " + dbVersion; } + else if (config.isDoris()) { + db = SQLConfig.DATABASE_DORIS + " " + dbVersion; + } else if (config.isSnowflake()) { db = SQLConfig.DATABASE_SNOWFLAKE + " " + dbVersion; } + else if (config.isDatabend()) { + db = SQLConfig.DATABASE_DATABEND + " " + dbVersion; + } else if (config.isDatabricks()) { db = SQLConfig.DATABASE_DATABRICKS + " " + dbVersion; } diff --git a/APIJSONORM/src/main/java/apijson/orm/exception/ConditionErrorException.java b/APIJSONORM/src/main/java/apijson/orm/exception/ConditionErrorException.java index fe2ccd761..44c43a29f 100755 --- a/APIJSONORM/src/main/java/apijson/orm/exception/ConditionErrorException.java +++ b/APIJSONORM/src/main/java/apijson/orm/exception/ConditionErrorException.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/exception/ConflictException.java b/APIJSONORM/src/main/java/apijson/orm/exception/ConflictException.java index ee258eee9..a39d88d3b 100755 --- a/APIJSONORM/src/main/java/apijson/orm/exception/ConflictException.java +++ b/APIJSONORM/src/main/java/apijson/orm/exception/ConflictException.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/exception/NotExistException.java b/APIJSONORM/src/main/java/apijson/orm/exception/NotExistException.java index 5c5a42bc8..69e34a809 100755 --- a/APIJSONORM/src/main/java/apijson/orm/exception/NotExistException.java +++ b/APIJSONORM/src/main/java/apijson/orm/exception/NotExistException.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/exception/NotLoggedInException.java b/APIJSONORM/src/main/java/apijson/orm/exception/NotLoggedInException.java index 2fbbb89ba..1e16b5796 100755 --- a/APIJSONORM/src/main/java/apijson/orm/exception/NotLoggedInException.java +++ b/APIJSONORM/src/main/java/apijson/orm/exception/NotLoggedInException.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/exception/OutOfRangeException.java b/APIJSONORM/src/main/java/apijson/orm/exception/OutOfRangeException.java index bde6fa42c..043ba5a65 100755 --- a/APIJSONORM/src/main/java/apijson/orm/exception/OutOfRangeException.java +++ b/APIJSONORM/src/main/java/apijson/orm/exception/OutOfRangeException.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/exception/UnsupportedDataTypeException.java b/APIJSONORM/src/main/java/apijson/orm/exception/UnsupportedDataTypeException.java index e272141c9..1c7d7c27a 100644 --- a/APIJSONORM/src/main/java/apijson/orm/exception/UnsupportedDataTypeException.java +++ b/APIJSONORM/src/main/java/apijson/orm/exception/UnsupportedDataTypeException.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/exception/package-info.java b/APIJSONORM/src/main/java/apijson/orm/exception/package-info.java index 70d3bb96e..073c6bdac 100755 --- a/APIJSONORM/src/main/java/apijson/orm/exception/package-info.java +++ b/APIJSONORM/src/main/java/apijson/orm/exception/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/java/apijson/orm/model/Access.java b/APIJSONORM/src/main/java/apijson/orm/model/Access.java index e114f5654..ab44f8664 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/Access.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/Access.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/model/AllColumn.java b/APIJSONORM/src/main/java/apijson/orm/model/AllColumn.java index 90da2664e..02906c6ec 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/AllColumn.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/AllColumn.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/model/AllColumnComment.java b/APIJSONORM/src/main/java/apijson/orm/model/AllColumnComment.java index fe98fa51d..81e2c9fe9 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/AllColumnComment.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/AllColumnComment.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/model/AllTable.java b/APIJSONORM/src/main/java/apijson/orm/model/AllTable.java index 215fde316..2934ad0ba 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/AllTable.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/AllTable.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/model/AllTableComment.java b/APIJSONORM/src/main/java/apijson/orm/model/AllTableComment.java index 5dd4b0a4d..49a4dee3a 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/AllTableComment.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/AllTableComment.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/model/Column.java b/APIJSONORM/src/main/java/apijson/orm/model/Column.java index d38d4fda3..573ab7fc4 100755 --- a/APIJSONORM/src/main/java/apijson/orm/model/Column.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/Column.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/model/Document.java b/APIJSONORM/src/main/java/apijson/orm/model/Document.java index 6f2a8bba2..2e8db19c2 100755 --- a/APIJSONORM/src/main/java/apijson/orm/model/Document.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/Document.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/model/ExtendedProperty.java b/APIJSONORM/src/main/java/apijson/orm/model/ExtendedProperty.java index 81227eb13..393a7e0d1 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/ExtendedProperty.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/ExtendedProperty.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/model/Function.java b/APIJSONORM/src/main/java/apijson/orm/model/Function.java index b02175f4a..da6c3f53e 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/Function.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/Function.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/model/PgAttribute.java b/APIJSONORM/src/main/java/apijson/orm/model/PgAttribute.java index 7c85f9f42..dbd4b4b69 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/PgAttribute.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/PgAttribute.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/model/PgClass.java b/APIJSONORM/src/main/java/apijson/orm/model/PgClass.java index 3a47b2791..199a7ef86 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/PgClass.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/PgClass.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/model/Request.java b/APIJSONORM/src/main/java/apijson/orm/model/Request.java index 8e83a2707..5d0d04092 100755 --- a/APIJSONORM/src/main/java/apijson/orm/model/Request.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/Request.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/model/Script.java b/APIJSONORM/src/main/java/apijson/orm/model/Script.java index ddb608432..53cda4323 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/Script.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/Script.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/model/SysColumn.java b/APIJSONORM/src/main/java/apijson/orm/model/SysColumn.java index 3e2c5c2b0..a4d7ca881 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/SysColumn.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/SysColumn.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/model/SysTable.java b/APIJSONORM/src/main/java/apijson/orm/model/SysTable.java index 221807c48..bb6142310 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/SysTable.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/SysTable.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/model/Table.java b/APIJSONORM/src/main/java/apijson/orm/model/Table.java index 0135fd947..1ab004cce 100755 --- a/APIJSONORM/src/main/java/apijson/orm/model/Table.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/Table.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/model/TestRecord.java b/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java index b1ceaa77c..32ad98f9e 100644 --- a/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/TestRecord.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/model/package-info.java b/APIJSONORM/src/main/java/apijson/orm/model/package-info.java index af556632a..a0b5641c9 100755 --- a/APIJSONORM/src/main/java/apijson/orm/model/package-info.java +++ b/APIJSONORM/src/main/java/apijson/orm/model/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/java/apijson/orm/package-info.java b/APIJSONORM/src/main/java/apijson/orm/package-info.java index e0f9a3d51..76569c93d 100755 --- a/APIJSONORM/src/main/java/apijson/orm/package-info.java +++ b/APIJSONORM/src/main/java/apijson/orm/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/java/apijson/orm/script/JSR223ScriptExecutor.java b/APIJSONORM/src/main/java/apijson/orm/script/JSR223ScriptExecutor.java index a657075d3..6a3a6c4f4 100644 --- a/APIJSONORM/src/main/java/apijson/orm/script/JSR223ScriptExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/script/JSR223ScriptExecutor.java @@ -1,6 +1,7 @@ package apijson.orm.script; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -11,20 +12,18 @@ import javax.script.ScriptEngineManager; import javax.script.SimpleBindings; -import com.alibaba.fastjson.JSONObject; - import apijson.orm.AbstractFunctionParser; /** * JSR223 script engine的统一实现抽象类 */ -public abstract class JSR223ScriptExecutor implements ScriptExecutor { +public abstract class JSR223ScriptExecutor, L extends List> implements ScriptExecutor { protected ScriptEngine scriptEngine; private final Map compiledScriptMap = new ConcurrentHashMap<>(); @Override - public ScriptExecutor init() { + public ScriptExecutor init() { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); scriptEngine = scriptEngineManager.getEngineByName(scriptEngineName()); return this; @@ -32,7 +31,7 @@ public ScriptExecutor init() { protected abstract String scriptEngineName(); - protected abstract Object extendParameter(AbstractFunctionParser parser, JSONObject currentObject, String methodName, Object[] args); + protected abstract Object extendParameter(AbstractFunctionParser parser, Map currentObject, String methodName, Object[] args); protected abstract boolean isLockScript(String methodName); @@ -52,11 +51,11 @@ public void load(String name, String script) { } @Override - public Object execute(AbstractFunctionParser parser, JSONObject currentObject, String methodName, Object[] args) throws Exception { + public Object execute(AbstractFunctionParser parser, Map currentObject, String methodName, Object[] args) throws Exception { CompiledScript compiledScript = compiledScriptMap.get(methodName); Bindings bindings = new SimpleBindings(); // 往脚本上下文里放入元数据 - // 把 RequestMethod method, String tag, int version, @NotNull JSONObject request, + // 把 RequestMethod method, String tag, int version, @NotNull JSONMap request, // HttpSession session 等参数作为全局参数传进去供脚本使用 // 加载扩展属性 diff --git a/APIJSONORM/src/main/java/apijson/orm/script/JavaScriptExecutor.java b/APIJSONORM/src/main/java/apijson/orm/script/JavaScriptExecutor.java index 893ac6d11..3f636b64a 100644 --- a/APIJSONORM/src/main/java/apijson/orm/script/JavaScriptExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/script/JavaScriptExecutor.java @@ -1,13 +1,14 @@ package apijson.orm.script; -import com.alibaba.fastjson.JSONObject; +import java.util.List; +import java.util.Map; import apijson.orm.AbstractFunctionParser; /** * JavaScript脚本语言的执行器实现 */ -public class JavaScriptExecutor extends JSR223ScriptExecutor { +public class JavaScriptExecutor, L extends List> extends JSR223ScriptExecutor { @Override protected String scriptEngineName() { @@ -15,7 +16,7 @@ protected String scriptEngineName() { } @Override - protected Object extendParameter(AbstractFunctionParser parser, JSONObject currentObject, String methodName, Object[] args) { + protected Object extendParameter(AbstractFunctionParser parser, Map currentObject, String methodName, Object[] args) { return null; } diff --git a/APIJSONORM/src/main/java/apijson/orm/script/ScriptExecutor.java b/APIJSONORM/src/main/java/apijson/orm/script/ScriptExecutor.java index b9296d153..c90ce7b15 100644 --- a/APIJSONORM/src/main/java/apijson/orm/script/ScriptExecutor.java +++ b/APIJSONORM/src/main/java/apijson/orm/script/ScriptExecutor.java @@ -1,16 +1,17 @@ package apijson.orm.script; -import com.alibaba.fastjson.JSONObject; +import java.util.List; +import java.util.Map; import apijson.orm.AbstractFunctionParser; -public interface ScriptExecutor { +public interface ScriptExecutor, L extends List> { - ScriptExecutor init(); + ScriptExecutor init(); void load(String name, String script); - Object execute(AbstractFunctionParser parser, JSONObject currentObject, String methodName, Object[] args) throws Exception; + Object execute(AbstractFunctionParser parser, Map currentObject, String methodName, Object[] args) throws Exception; void cleanCache(); 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 06596081b..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,13 +57,24 @@ - [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)(上海麦市工程师) - [komiblog](https://github.com/komiblog) - [ostrichManX](https://github.com/ostrichManX) - [jia199807](https://github.com/jia199807) +- [zxcwindy](https://github.com/zxcwindy) +- [afumu](https://github.com/afumu)(gorm-plus 作者) +- [alittle-yu](https://github.com/alittle-yu) +- [Damon Nicola](https://github.com/Reynold3D) +- [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 8d1fab8a4..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 Structure 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,let interfaces and documents go to hell !"
   },
   "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,let interfaces and documents go to hell !"
   },
   "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,let interfaces and documents go to hell !"
   },
   "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 e20bfbb27..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") - -* ### [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) +#### A better online document is available at https://apijsondocs.readthedocs.io +### 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个
-    "User":{
-      "@column":"id,name"  //只要id,name这两个字段
-    }
+    "count":3, //just get 3 results
+    "User":{
+      "@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:
{
   "[]":[
     {
@@ -185,7 +165,7 @@ https://github.com/Tencent/APIJSON
         "id":15,
         "userId":70793,
         "date":1486541171000,
-        "content":"APIJSON is a JSON Transmission Structure Protocol…",
+        "content":"APIJSON is a JSON Transmission Protocol…",
         "praiseUserIdList":[
           82055,
           82002,
@@ -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,let interfaces and documents go to hell !"
   },
   "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,let interfaces and documents go to hell !"
   },
   "tag":"Comment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fpost&type=JSON&json={"Comment":{"momentId":12,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Comment"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(38710,12,'APIJSON,let interfaces and documents go to hell !')`

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

例如当前登录用户 82001 发布 2 个 Comment:
[{
   "Comment[]":[{
     "momentId":12,
     "content":"APIJSON,let interfaces and documents go to hell !"
     }, {
     "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,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},{"momentId":15,"content":"APIJSON%20is%20a%20JSON%20transmision%20protocol."}],"tag":"Comment:[]"})
后端校验通过后自动解析为 SQL 并执行:
`INSERT INTO Comment(userId,momentId,content) VALUES(82001,12,'APIJSON,let interfaces and documents go to hell !');`

`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,let interfaces and documents go to hell !"
   },
   "tag":"Moment"
}](http://apijson.cn/api?url=http%3A%2F%2Fapijson.cn%3A8080%2Fput&type=JSON&json={"Moment":{"id":235,"content":"APIJSON,let%20interfaces%20and%20documents%20go%20to%20hell%20!"},"tag":"Moment"})
后端校验通过后自动解析为 SQL 并执行:
`UPDATE Moment SET content='APIJSON,let interfaces and documents go to hell !' 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..95ad64c2d --- /dev/null +++ b/README-Chinese.md @@ -0,0 +1,854 @@ +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) + +### 生态项目 +[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 9329faf0e..000000000 --- a/README-English.md +++ /dev/null @@ -1,392 +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  -

-

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

-

- - - -   -   -   - - -

-

- -   -   -

-

- -   -   -

- -

- -

- ---- - -#### 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-extend.md b/README-extend.md index 09301ddca..98b60ac2c 100644 --- a/README-extend.md +++ b/README-extend.md @@ -6,13 +6,23 @@ https://github.com/Tencent/APIJSON/issues/468 #### 使用说明 -json支持多种方式定义method +json 支持多种方式定义 method -第一种: +#### 第一种: - "@post","@put","@delete","@head","@get","@gets","@head","@heads" +"@post","@put","@delete","@head","@get","@gets","@head","@heads" -"@post": ["Moment","Comment[]"] , 值为数组格式, 每个value = key +"@post": "Moment,Comment[]" , 值为 String 或 JSONObject 格式, 为 String 时每个 value = key,为 JSONObject 时: +```json +"@post": { + "Moment": "Moment", // 只指定 tag,为 "" 则和 key 一致 + "Comment[]": { // 同时指定多个全局关键词 + "tag": "Comment[]", + "version": 2 + // 其它全局关键词 + } +} +``` 需要保证每个key唯一, 唯一判断标准: @@ -24,7 +34,7 @@ key= Moment[] ``` { - "@post": ["Moment","Comment:cArray[]","User:u"], // 分发到 POST 请求对应的解析处理 + "@post": "Moment,Comment:cArray[],User:u", // 分发到 POST 请求对应的解析处理 "Moment": { // TODO 其它字段 }, @@ -33,7 +43,7 @@ key= Moment[] // TODO 其它字段 } ], - "@get": ["User"], // 分发到 GET 请求对应的解析处理 + "@get": "User", // 分发到 GET 请求对应的解析处理 "User:u": { // TODO 其它字段 }, @@ -46,19 +56,19 @@ key= Moment[] ``` -第二种: +#### 第二种: @Deprecated 即将弃用,请使用第一种 对象内定义"@method": "GET", value大写 ``` { - "sql@": { + "sql@": { "@method": "GET", "with": true, "from": "Sys_role", "Sys_role": { - "@column": "id", - "role_name": "角色1" + "@column": "id", + "role_name": "角色1" } }, "Sys_user_role:sur[]": { @@ -152,14 +162,14 @@ Comment:cArray[] 并将method 添加到 json对象属性中. -``` +```json "Sys_role": { - "@method": "PUT", - "id": "6aedce0d-2a29-4fbe-aeed-0ba935ca6b41", - "id{}@": "sql", - "role_code": "code-subrange-4", - "role_name": "角色-subrange-4" - } + "@method": "PUT", + "id": "6aedce0d-2a29-4fbe-aeed-0ba935ca6b41", + "id{}@": "sql", + "role_code": "code-subrange-4", + "role_name": "角色-subrange-4" +} ``` 2、对象解析 @@ -741,6 +751,7 @@ AbstractVerifier.IS_UPDATE_MUST_HAVE_ID_CONDITION = true; // true: 必须有 ``` // 条件删除 +```json { "User:del": { "username": "test3" @@ -748,8 +759,10 @@ AbstractVerifier.IS_UPDATE_MUST_HAVE_ID_CONDITION = true; // true: 必须有 "tag": "User", "explain": true } +``` // 引用id{}@删除 +```json { "sql@": { "@method": "GET", @@ -766,8 +779,11 @@ AbstractVerifier.IS_UPDATE_MUST_HAVE_ID_CONDITION = true; // true: 必须有 }, "explan": true } +``` + // 子查询条件删除 http://localhost:8675/lowCodePlatform/forms/api/delete +```json { "sql@": { "@method": "GET", @@ -783,8 +799,10 @@ http://localhost:8675/lowCodePlatform/forms/api/delete }, "explan": true } +``` 第二种写法: +```json { "@get": ["sql@"], "sql@": { @@ -800,23 +818,21 @@ http://localhost:8675/lowCodePlatform/forms/api/delete }, "explan": true } - - ``` 开启id删除, 删除失败: -``` +```json { - "@get": ["sql@"], - "sql@": { + "@get": ["sql@"], + "sql@": { "with": true, "from": "User", "User": { - "@column": "username", - "username": "test4" + "@column": "username", + "username": "test4" } }, "User": { @@ -830,7 +846,7 @@ http://localhost:8675/lowCodePlatform/forms/api/delete 开启id删除、id引用 删除成功 -``` +```json { "sql@": { "@method": "GET", @@ -848,19 +864,20 @@ http://localhost:8675/lowCodePlatform/forms/api/delete "explan": true } ``` + ![image](https://user-images.githubusercontent.com/12228225/204080050-e6f04fe6-319e-45b7-b1b2-bf4cda4ab2db.png) PUT 子查询 修改 -``` +```json { "sql@": { - "@method": "GET", + "@method": "GET", "with": true, "from": "Sys_role_permission", "Sys_role_permission": { - "@column": "role_id", - "id{}": ["ba2634f8-0bdc-4b50-9c5e-47786b1536ef"] + "@column": "role_id", + "id{}": ["ba2634f8-0bdc-4b50-9c5e-47786b1536ef"] } }, "Sys_role": { @@ -892,15 +909,15 @@ WHERE ( (`username` IN (SELECT * FROM (SELECT `username` FROM `housekeeping`.`Us ### must、refuses判断、delete、PUT支持 ref -``` +```json { - "sql@": { - "@method": "GET", + "sql@": { + "@method": "GET", "with": true, "from": "Sys_role_permission", "Sys_role_permission": { - "@column": "id", - "role_id{}": ["94f79f0b-331b-4cc5-bfc0-ebfc47d00f13"] + "@column": "id", + "role_id{}": ["94f79f0b-331b-4cc5-bfc0-ebfc47d00f13"] } }, "Sys_role_permission": { diff --git a/README.md b/README.md index a5f474067..84bf92cc9 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,26 @@ 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 coding-free, powerful and secure ORM 🚀
providing APIs and Docs without coding by Backend, and the response JSON can be customized by Frontend(Client) users

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

+

-   -   + + @@ -49,27 +52,30 @@ This source code is licensed under the Apache License Version 2.0
+

-   -   -   + + + +

-   -   -   + + + +

-   -   + +

@@ -78,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) @@ -127,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)
+### Users of APIJSON: -### 下载客户端 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
@@ -349,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) 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
@@ -478,347 +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-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