diff --git a/README.md b/README.md index eaa3423..286ae35 100755 --- a/README.md +++ b/README.md @@ -130,6 +130,35 @@ https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONB #### See document in [ColumnUtil](/src/main/java/apijson/column/ColumnUtil.java) and [DemoSQLConfig](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot/src/main/java/apijson/demo/DemoSQLConfig.java), [DemoSQLExecutor](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot/src/main/java/apijson/demo/DemoSQLExecutor.java) in [APIJSONBoot](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot) +```java + static { + // 反选字段配置 + Map> tableColumnMap = new HashMap<>(); + tableColumnMap.put("User", Arrays.asList(StringUtil.split("id,sex,name,tag,head,contactIdList,pictureList,date"))); + // 需要对应方法传参也是这样拼接才行,例如 ColumnUtil.compatInputColumn(column, getSQLDatabase() + "-" + getSQLSchema() + "-" + getTable(), getMethod()); + tableColumnMap.put("MYSQL-sys-Privacy", Arrays.asList(StringUtil.split("id,certified,phone,balance,_password,_payPassword"))); + ColumnUtil.VERSIONED_TABLE_COLUMN_MAP.put(null, tableColumnMap); + + // 字段名映射配置 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + Map> tableKeyColumnMap = new HashMap<>(); + + Map userKeyColumnMap = new HashMap<>(); + userKeyColumnMap.put("gender", "sex"); + userKeyColumnMap.put("createTime", "date"); + tableKeyColumnMap.put("User", userKeyColumnMap); + + Map privacyKeyColumnMap = new HashMap<>(); + privacyKeyColumnMap.put("rest", "balance"); + // 需要对应方法传参也是这样拼接才行,例如 ColumnUtil.compatInputKey(super.getKey(key), getSQLDatabase() + "-" + getSQLSchema() + "-" + getTable(), getMethod()); + tableKeyColumnMap.put("MYSQL-sys-Privacy", privacyKeyColumnMap); + + ColumnUtil.VERSIONED_KEY_COLUMN_MAP.put(null, tableKeyColumnMap); + // 字段名映射配置 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + ColumnUtil.init(); + } +``` +


diff --git a/pom.xml b/pom.xml index e5816d0..499174a 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ apijson.column apijson-column - 1.2.9 + 2.1.5 jar apijson-column @@ -15,6 +15,7 @@ UTF-8 UTF-8 1.8 + UTF-8 @@ -29,7 +30,7 @@ com.github.Tencent APIJSON - 5.4.0 + 7.5.5 @@ -40,6 +41,7 @@ org.apache.maven.plugins maven-compiler-plugin + 3.12.1 1.8 1.8 diff --git a/src/main/java/apijson/column/ColumnUtil.java b/src/main/java/apijson/column/ColumnUtil.java index 3508d46..2bc7ed6 100644 --- a/src/main/java/apijson/column/ColumnUtil.java +++ b/src/main/java/apijson/column/ColumnUtil.java @@ -14,12 +14,8 @@ package apijson.column; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; import apijson.RequestMethod; import apijson.StringUtil; @@ -37,21 +33,29 @@ public class ColumnUtil { /**带版本的表和字段一对多对应关系,用来做 反选字段 * Map> */ - public static final Map>> VERSIONED_TABLE_COLUMN_MAP; + public static SortedMap>> VERSIONED_TABLE_COLUMN_MAP; /**带版本的 JSON key 和表字段一对一对应关系,用来做字段名映射 * Map>> */ - public static final Map>> VERSIONED_KEY_COLUMN_MAP; + public static SortedMap>> VERSIONED_KEY_COLUMN_MAP; /**带版本的 JSON key 和表字段一对一对应关系,用来做字段名映射,与 VERSIONED_KEY_COLUMN_MAP 相反 * Map>> */ - private static Map>> VERSIONED_COLUMN_KEY_MAP; + private static SortedMap>> VERSIONED_COLUMN_KEY_MAP; + + public static final Comparator DESC_COMPARATOR = new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o2.compareTo(o1); + } + }; + static { - VERSIONED_TABLE_COLUMN_MAP = new HashMap<>(); - VERSIONED_KEY_COLUMN_MAP = new HashMap<>(); - VERSIONED_COLUMN_KEY_MAP = new HashMap<>(); + VERSIONED_TABLE_COLUMN_MAP = new TreeMap<>(DESC_COMPARATOR); + VERSIONED_KEY_COLUMN_MAP = new TreeMap<>(DESC_COMPARATOR); + VERSIONED_COLUMN_KEY_MAP = new TreeMap<>(DESC_COMPARATOR); } /**初始化 @@ -59,10 +63,11 @@ public class ColumnUtil { public static void init() { VERSIONED_COLUMN_KEY_MAP.clear(); + // 反过来补全 column -> key 的配置,以空间换时间 Set>>> set = VERSIONED_KEY_COLUMN_MAP.entrySet(); if (set != null && set.isEmpty() == false) { - Map>> map = new HashMap<>(); + SortedMap>> map = new TreeMap<>(DESC_COMPARATOR); for (Entry>> entry : set) { @@ -98,6 +103,74 @@ public static void init() { VERSIONED_COLUMN_KEY_MAP = map; } + + + // 补全剩下未定义别名的 key,以空间换时间 + Set>>> allSet = VERSIONED_TABLE_COLUMN_MAP.entrySet(); + if (allSet != null && allSet.isEmpty() == false) { + + for (Entry>> entry : allSet) { + Map> keyColumnMap = VERSIONED_KEY_COLUMN_MAP.get(entry.getKey()); +// 没必要,没特殊配置的就原样返回,没有安全隐患,还能减少性能浪费 Map> columnKeyMap = VERSIONED_COLUMN_KEY_MAP.get(entry.getKey()); + if (keyColumnMap == null) { + keyColumnMap = new LinkedHashMap<>(); + VERSIONED_KEY_COLUMN_MAP.put(entry.getKey(), keyColumnMap); + } +// if (columnKeyMap == null) { +// columnKeyMap = new LinkedHashMap<>(); +// VERSIONED_COLUMN_KEY_MAP.put(entry.getKey(), columnKeyMap); +// } + + Map> tableKeyColumnMap = entry == null ? null : entry.getValue(); + Set>> tableKeyColumnSet = tableKeyColumnMap == null ? null : tableKeyColumnMap.entrySet(); + + if (tableKeyColumnSet != null && tableKeyColumnSet.isEmpty() == false) { + + for (Entry> tableKeyColumnEntry : tableKeyColumnSet) { + + List list = tableKeyColumnEntry == null ? null : tableKeyColumnEntry.getValue(); + + if (list != null && list.isEmpty() == false) { + + Map kcm = keyColumnMap.get(tableKeyColumnEntry.getKey()); +// Map ckm = columnKeyMap.get(tableKeyColumnEntry.getKey()); + if (kcm == null) { + kcm = new LinkedHashMap<>(); + keyColumnMap.put(tableKeyColumnEntry.getKey(), kcm); + } +// if (ckm == null) { +// ckm = new LinkedHashMap<>(); +// columnKeyMap.put(tableKeyColumnEntry.getKey(), ckm); +// } + + for (String column : list) { + if (column == null) { + continue; + } + +// ckm.putIfAbsent(column, column); + //FIXME 对 Comment.toId (多版本) 居然不起作用 +// if (kcm.containsValue(column) == false) { + kcm.putIfAbsent(column, column); +// } + } + +// for (String column : list) { +// if (column == null || ckm.get(column) != null) { +// continue; +// } +// +// kcm.putIfAbsent(column, column); +// } + + } + } + + } + } + + } + } /**适配请求参数 JSON 中 @column:value 的 value 中的 key。支持 !key 反选字段 和 字段名映射 @@ -107,7 +180,7 @@ public static void init() { * @return */ public static List compatInputColumn(List columns, String table, RequestMethod method) { - return compatInputColumn(columns, table, method, null); + return compatInputColumn(columns, table, method, null, false); } /**适配请求参数 JSON 中 @column:value 的 value 中的 key。支持 !key 反选字段 和 字段名映射 @@ -118,15 +191,15 @@ public static List compatInputColumn(List columns, String table, * @return * @see 先提前配置 {@link #VERSIONED_TABLE_COLUMN_MAP},然后在 {@link AbstractSQLConfig} 的子类重写 {@link AbstractSQLConfig#setColumn } 并调用这个方法,例如 *
-	   public AbstractSQLConfig setColumn(List column) { 
- return super.setColumn(ColumnUtil.compatInputColumn(column, getTable(), version));
- } + public AbstractSQLConfig setColumn(List column) {
+ return super.setColumn(ColumnUtil.compatInputColumn(column, getTable(), version));
+ } *
*/ - public static List compatInputColumn(List columns, String table, RequestMethod method, Integer version) { + public static List compatInputColumn(List columns, String table, RequestMethod method, Integer version, boolean throwWhenNoKey) { String[] keys = columns == null ? null : columns.toArray(new String[]{}); // StringUtil.split(c, ";"); - if (keys == null || keys.length <= 0) { - return columns; + if (keys == null || keys.length <= 0) { // JOIN 副表可以设置 @column:"" 来指定不返回字段 + return columns != null ? columns : getClosestValue(VERSIONED_TABLE_COLUMN_MAP, version, table); } // boolean isQueryMethod = RequestMethod.isQueryMethod(method); @@ -134,8 +207,10 @@ public static List compatInputColumn(List columns, String table, List exceptColumns = new ArrayList<>(); // Map exceptColumnMap = new HashMap<>(); List newColumns = new ArrayList<>(); - Map> tableKeyColumnMap = VERSIONED_KEY_COLUMN_MAP == null || VERSIONED_KEY_COLUMN_MAP.isEmpty() ? null : VERSIONED_KEY_COLUMN_MAP.get(version); - Map keyColumnMap = tableKeyColumnMap == null || tableKeyColumnMap.isEmpty() ? null : tableKeyColumnMap.get(table); + Map keyColumnMap = getClosestValue(VERSIONED_KEY_COLUMN_MAP, version, table); + boolean isEmpty = keyColumnMap == null || keyColumnMap.isEmpty(); + + String q = "`"; String expression; //...;fun0(arg0,arg1,...):fun0;fun1(arg0,arg1,...):fun1;... @@ -143,8 +218,37 @@ public static List compatInputColumn(List columns, String table, //!column,column2,!column3,column4:alias4;fun(arg0,arg1,...) expression = keys[i]; - if (expression.contains("(") || expression.contains(")")) { - newColumns.add(expression); + int start = expression.indexOf("("); + int end = expression.lastIndexOf(")"); + if (start >= 0 && start < end) { + String[] ks = StringUtil.split(expression.substring(start + 1, end)); + + String expr = expression.substring(0, start + 1); + for (int j = 0; j < ks.length; j++) { + String ck = ks[j]; + boolean hasQuote = false; + if (ck.endsWith("`")) { + String nck = ck.substring(0, ck.length() - 1); + if (nck.lastIndexOf("`") == 0) { + ck = nck.substring(1); + hasQuote = true; + } + } + + String rc = null; + if (hasQuote || StringUtil.isName(ck)) { + rc = isEmpty ? null : keyColumnMap.get(ck); + if (rc == null && isEmpty == false && throwWhenNoKey) { + throw new NullPointerException(table + ":{ @column: value } 的 value 中 " + ck + " 不合法!不允许传后端未授权访问的字段名!"); + } + } + + expr += (j <= 0 ? "" : ",") + (hasQuote ? q : "") + (rc == null ? ck : rc) + (hasQuote ? q : ""); + } + + newColumns.add(expr + expression.substring(end)); + +// newColumns.add(expression); continue; } @@ -155,27 +259,42 @@ public static List compatInputColumn(List columns, String table, if (ck.startsWith("!")) { if (ck.length() <= 1) { - throw new IllegalArgumentException("@column:value 的 value 中 " + ck + " 不合法! !column 不允许 column 为空字符串!column,!column2,!column3,column4:alias4 中所有 column 必须符合变量名格式!"); + throw new IllegalArgumentException("@column:value 的 value 中 " + ck + + " 不合法! !column 不允许 column 为空字符串!column,!column2,!column3,column4:alias4 中所有 column 必须符合变量名格式!"); } String c = ck.substring(1); if (StringUtil.isName(c) == false) { - throw new IllegalArgumentException("@column:value 的 value 中 " + c + " 不合法! column,!column2,!column3,column4:alias4 中所有 column 必须符合变量名格式!"); + throw new IllegalArgumentException("@column:value 的 value 中 " + c + + " 不合法! column,!column2,!column3,column4:alias4 中所有 column 必须符合变量名格式!"); } - String rc = keyColumnMap == null || keyColumnMap.isEmpty() ? null : keyColumnMap.get(c); + String rc = isEmpty ? null : keyColumnMap.get(c); exceptColumns.add(rc == null ? c : rc); // 不使用数据库别名,以免 JOIN 等复杂查询情况下报错字段不存在 exceptColumnMap.put(nc == null ? c : nc, c); // column:alias - } - else { - String rc = keyColumnMap == null || keyColumnMap.isEmpty() ? null : keyColumnMap.get(ck); + } else { + boolean hasQuote = false; + if (ck.endsWith("`")) { + String nck = ck.substring(0, ck.length() - 1); + if (nck.lastIndexOf("`") == 0) { + ck = nck.substring(1); + hasQuote = true; + } + } + + String rc = null; + if (hasQuote || StringUtil.isName(ck)) { + rc = isEmpty ? null : keyColumnMap.get(ck); + if (rc == null && isEmpty == false && throwWhenNoKey) { + throw new NullPointerException(table + ":{ @column: value } 的 value 中 " + ck + " 不合法!不允许传后端未授权访问的字段名!"); + } + } + newColumns.add(rc == null ? ck : rc); // 不使用数据库别名,以免 JOIN 等复杂查询情况下报错字段不存在 newColumns.add(rc == null ? ck : (isQueryMethod ? (rc + ":" + ck) : rc)); } } } } - boolean isEmpty = exceptColumns == null || exceptColumns.isEmpty(); // exceptColumnMap == null || exceptColumnMap.isEmpty(); - Map> map = isEmpty || VERSIONED_TABLE_COLUMN_MAP == null || VERSIONED_TABLE_COLUMN_MAP.isEmpty() ? null : VERSIONED_TABLE_COLUMN_MAP.get(version); - List allColumns = map == null || map.isEmpty() ? null : map.get(table); + List allColumns = exceptColumns == null || exceptColumns.isEmpty() ? null : getClosestValue(VERSIONED_TABLE_COLUMN_MAP, version, table); if (allColumns != null && allColumns.isEmpty() == false) { @@ -202,8 +321,9 @@ public static List compatInputColumn(List columns, String table, * @return */ public static String compatInputKey(String key, String table, RequestMethod method) { - return compatInputKey(key, table, method, null); + return compatInputKey(key, table, method, null, false); } + /**适配请求参数 JSON 中 条件/赋值 键值对的 key * @param key * @param table @@ -212,18 +332,25 @@ public static String compatInputKey(String key, String table, RequestMethod meth * @return * @see 先提前配置 {@link #VERSIONED_KEY_COLUMN_MAP},然后在 {@link AbstractSQLConfig} 的子类重写 {@link AbstractSQLConfig#getKey } 并调用这个方法,例如 *
-	   public String getKey(String key) { 
- return super.getKey(ColumnUtil.compatInputKey(key, getTable(), version));
- } + public String getKey(String key) {
+ return super.getKey(ColumnUtil.compatInputKey(key, getTable(), version));
+ } *
*/ - public static String compatInputKey(String key, String table, RequestMethod method, Integer version) { - Map> tableKeyColumnMap = VERSIONED_KEY_COLUMN_MAP == null || VERSIONED_KEY_COLUMN_MAP.isEmpty() ? null : VERSIONED_KEY_COLUMN_MAP.get(version); - Map keyColumnMap = tableKeyColumnMap == null || tableKeyColumnMap.isEmpty() ? null : tableKeyColumnMap.get(table); - String alias = keyColumnMap == null || keyColumnMap.isEmpty() ? null : keyColumnMap.get(key); - return alias == null ? key : alias; + public static String compatInputKey(String key, String table, RequestMethod method, Integer version, boolean throwWhenNoKey) { + Map keyColumnMap = getClosestValue(VERSIONED_KEY_COLUMN_MAP, version, table); + boolean isEmpty = keyColumnMap == null || keyColumnMap.isEmpty(); + String alias = isEmpty ? null : keyColumnMap.get(key); + if (alias == null) { + if (isEmpty == false && throwWhenNoKey) { + throw new NullPointerException(table + ":{} 中不允许传 " + key + " !"); + } + return key; + } + + return alias; } - + /**适配返回结果 JSON 中键值对的 key。可能通过不传 @column 等方式来返回原始字段名,这样就达不到隐藏真实字段名的需求了,所以只有最终这个兜底方式靠谱。 * @param key * @param table @@ -233,6 +360,7 @@ public static String compatInputKey(String key, String table, RequestMethod meth public static String compatOutputKey(String key, String table, RequestMethod method) { return compatOutputKey(key, table, method, null); } + /**适配返回结果 JSON 中键值对的 key。可能通过不传 @column 等方式来返回原始字段名,这样就达不到隐藏真实字段名的需求了,所以只有最终这个兜底方式靠谱。 * @param key * @param table @@ -241,19 +369,66 @@ public static String compatOutputKey(String key, String table, RequestMethod met * @return * @see 先提前配置 {@link #VERSIONED_COLUMN_KEY_MAP},然后在 {@link AbstractSQLExecutor} 的子类重写 {@link AbstractSQLExecutor#getKey } 并调用这个方法,例如 *
-		protected String getKey(SQLConfig config, ResultSet rs, ResultSetMetaData rsmd, int tablePosition, JSONObject table,
-				int columnIndex, Map childMap) throws Exception { 
- return ColumnUtil.compatOutputKey(super.getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap), config.getTable(), config.getMethod(), version);
- } + protected String getKey(SQLConfig config, ResultSet rs, ResultSetMetaData rsmd, int tablePosition, JSONObject table, + int columnIndex, Map childMap) throws Exception {
+ return ColumnUtil.compatOutputKey(super.getKey(config, rs, rsmd, tablePosition, table, columnIndex, childMap), config.getTable(), config.getMethod(), version);
+ } *
*/ public static String compatOutputKey(String key, String table, RequestMethod method, Integer version) { - Map> tableColumnKeyMap = VERSIONED_COLUMN_KEY_MAP == null || VERSIONED_COLUMN_KEY_MAP.isEmpty() ? null : VERSIONED_COLUMN_KEY_MAP.get(version); - Map columnKeyMap = tableColumnKeyMap == null || tableColumnKeyMap.isEmpty() ? null : tableColumnKeyMap.get(table); + Map columnKeyMap = getClosestValue(VERSIONED_COLUMN_KEY_MAP, version, table); String alias = columnKeyMap == null || columnKeyMap.isEmpty() ? null : columnKeyMap.get(key); return alias == null ? key : alias; } + public static T getClosestValue(SortedMap> versionedMap, Integer version, String table) { + boolean isEmpty = versionedMap == null || versionedMap.isEmpty(); + + Map map = isEmpty || version == null ? null : versionedMap.get(version); + T m = map == null ? null : map.get(table); + if (isEmpty == false && m == null) { + Set>> set = versionedMap.entrySet(); + + T lm = null; + for (Entry> entry : set) { + Map val = entry.getValue(); + m = val == null ? null : val.get(table); + if (m == null) { + continue; + } + + if (version == null || version == 0) { + // versionedMap.put(null, val); + return m; + } + + Integer key = entry.getKey(); + if (key == null) { + lm = m; + map = val; + continue; + } + + if (version >= key) { + versionedMap.put(version, val); + return m; + } + + break; + } + + if (lm != null) { + m = lm; + } + + if (map != null) { + versionedMap.put(version, map); + } + } + + return m; + } + /**把多个表名相关属性拼接成一个表名 * @param database