|
| 1 | +/*Copyright ©2022 APIJSON(https://github.com/APIJSON) |
| 2 | +
|
| 3 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +you may not use this file except in compliance with the License. |
| 5 | +You may obtain a copy of the License at |
| 6 | +
|
| 7 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +
|
| 9 | +Unless required by applicable law or agreed to in writing, software |
| 10 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +See the License for the specific language governing permissions and |
| 13 | +limitations under the License.*/ |
| 14 | + |
| 15 | +package apijson.router.javax; |
| 16 | + |
| 17 | +import apijson.*; |
| 18 | +import apijson.framework.javax.APIJSONConstant; |
| 19 | +import apijson.framework.javax.APIJSONController; |
| 20 | +import apijson.framework.javax.APIJSONCreator; |
| 21 | +import apijson.framework.javax.APIJSONParser; |
| 22 | +import apijson.orm.AbstractVerifier; |
| 23 | +import apijson.orm.Parser; |
| 24 | +import apijson.orm.SQLConfig; |
| 25 | +import apijson.orm.Verifier; |
| 26 | +import com.alibaba.fastjson.JSONObject; |
| 27 | +import javax.servlet.http.HttpSession; |
| 28 | + |
| 29 | +import java.util.*; |
| 30 | +import java.util.Map.Entry; |
| 31 | + |
| 32 | +import static apijson.RequestMethod.GET; |
| 33 | +import static apijson.framework.javax.APIJSONConstant.METHODS; |
| 34 | + |
| 35 | + |
| 36 | +/**APIJSON router controller,建议在子项目被 @RestController 注解的类继承它或通过它的实例调用相关方法 |
| 37 | + * @author Lemon |
| 38 | + */ |
| 39 | +public class APIJSONRouterController<T extends Object> extends APIJSONController<T> { |
| 40 | + public static final String TAG = "APIJSONRouterController"; |
| 41 | + |
| 42 | + //通用接口,非事务型操作 和 简单事务型操作 都可通过这些接口自动化实现<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
| 43 | + |
| 44 | + /**增删改查统一的类 RESTful API 入口,牺牲一些路由解析性能来提升一点开发效率 |
| 45 | + * compatCommonAPI = Log.DEBUG |
| 46 | + * @param method |
| 47 | + * @param tag |
| 48 | + * @param params |
| 49 | + * @param request |
| 50 | + * @param session |
| 51 | + * @return |
| 52 | + */ |
| 53 | + public String router(String method, String tag, Map<String, String> params, String request, HttpSession session) { |
| 54 | + return router(method, tag, params, request, session, Log.DEBUG); |
| 55 | + } |
| 56 | + /**增删改查统一的类 RESTful API 入口,牺牲一些路由解析性能来提升一点开发效率 |
| 57 | + * @param method |
| 58 | + * @param tag |
| 59 | + * @param params |
| 60 | + * @param request |
| 61 | + * @param session |
| 62 | + * @param compatCommonAPI 兼容万能通用 API,当没有映射 APIJSON 格式请求时,自动转到万能通用 API |
| 63 | + * @return |
| 64 | + */ |
| 65 | + public String router(String method, String tag, Map<String, String> params, String request, HttpSession session, boolean compatCommonAPI) { |
| 66 | + if (METHODS.contains(method) == false) { |
| 67 | + return APIJSONParser.newErrorResult(new IllegalArgumentException("URL 路径 /{method}/{tag} 中 method 值 " + method |
| 68 | + + " 错误!只允许 " + METHODS + " 中的一个!")).toJSONString(); |
| 69 | + } |
| 70 | + |
| 71 | + String t = compatCommonAPI && tag != null && tag.endsWith("[]") ? tag.substring(0, tag.length() - 2) : tag; |
| 72 | + if (StringUtil.isName(t) == false) { |
| 73 | + return APIJSONParser.newErrorResult(new IllegalArgumentException("URL 路径 /" + method + "/{tag} 的 tag 中 " + t |
| 74 | + + " 错误!tag 不能为空,且只允许变量命名格式!")).toJSONString(); |
| 75 | + } |
| 76 | + |
| 77 | + String versionStr = params == null ? null : params.remove(APIJSONConstant.VERSION); |
| 78 | + Integer version; |
| 79 | + try { |
| 80 | + version = StringUtil.isEmpty(versionStr, false) ? null : Integer.valueOf(versionStr); |
| 81 | + } |
| 82 | + catch (Exception e) { |
| 83 | + return APIJSONParser.newErrorResult(new IllegalArgumentException("URL 路径 /" + method |
| 84 | + + "/" + tag + "?version=value 中 value 值 " + versionStr + " 错误!必须符合整数格式!")).toJSONString(); |
| 85 | + } |
| 86 | + |
| 87 | + if (version == null) { |
| 88 | + version = 0; |
| 89 | + } |
| 90 | + |
| 91 | + try { |
| 92 | + // 从 Document 查这样的接口 |
| 93 | + String cacheKey = AbstractVerifier.getCacheKeyForRequest(method, tag); |
| 94 | + SortedMap<Integer, JSONObject> versionedMap = APIJSONRouterVerifier.DOCUMENT_MAP.get(cacheKey); |
| 95 | + |
| 96 | + JSONObject result = versionedMap == null ? null : versionedMap.get(version); |
| 97 | + if (result == null) { // version <= 0 时使用最新,version > 0 时使用 > version 的最接近版本(最小版本) |
| 98 | + Set<Entry<Integer, JSONObject>> set = versionedMap == null ? null : versionedMap.entrySet(); |
| 99 | + |
| 100 | + if (set != null && set.isEmpty() == false) { |
| 101 | + Entry<Integer, JSONObject> maxEntry = null; |
| 102 | + |
| 103 | + for (Entry<Integer, JSONObject> entry : set) { |
| 104 | + if (entry == null || entry.getKey() == null || entry.getValue() == null) { |
| 105 | + continue; |
| 106 | + } |
| 107 | + |
| 108 | + if (version == null || version <= 0 || version == entry.getKey()) { // 这里应该不会出现相等,因为上面 versionedMap.get(Integer.valueOf(version)) |
| 109 | + maxEntry = entry; |
| 110 | + break; |
| 111 | + } |
| 112 | + |
| 113 | + if (entry.getKey() < version) { |
| 114 | + break; |
| 115 | + } |
| 116 | + |
| 117 | + maxEntry = entry; |
| 118 | + } |
| 119 | + |
| 120 | + result = maxEntry == null ? null : maxEntry.getValue(); |
| 121 | + } |
| 122 | + |
| 123 | + if (result != null) { // 加快下次查询,查到值的话组合情况其实是有限的,不属于恶意请求 |
| 124 | + if (versionedMap == null) { |
| 125 | + versionedMap = new TreeMap<>((o1, o2) -> { |
| 126 | + return o2 == null ? -1 : o2.compareTo(o1); // 降序 |
| 127 | + }); |
| 128 | + } |
| 129 | + |
| 130 | + versionedMap.put(version, result); |
| 131 | + APIJSONRouterVerifier.DOCUMENT_MAP.put(cacheKey, versionedMap); |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + @SuppressWarnings("unchecked") |
| 136 | + APIJSONCreator<T> creator = (APIJSONCreator<T>) APIJSONParser.APIJSON_CREATOR; |
| 137 | + if (result == null && Log.DEBUG && APIJSONRouterVerifier.DOCUMENT_MAP.isEmpty()) { |
| 138 | + |
| 139 | + //获取指定的JSON结构 <<<<<<<<<<<<<< |
| 140 | + SQLConfig config = creator.createSQLConfig().setMethod(GET).setTable(APIJSONConstant.DOCUMENT_); |
| 141 | + config.setPrepared(false); |
| 142 | + config.setColumn(Arrays.asList("request,apijson")); |
| 143 | + |
| 144 | + Map<String, Object> where = new HashMap<String, Object>(); |
| 145 | + where.put("url", "/" + method + "/" + tag); |
| 146 | + where.put("apijson{}", "length(apijson)>0"); |
| 147 | + |
| 148 | + if (version > 0) { |
| 149 | + where.put(JSONRequest.KEY_VERSION + ">=", version); |
| 150 | + } |
| 151 | + config.setWhere(where); |
| 152 | + config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); |
| 153 | + config.setCount(1); |
| 154 | + |
| 155 | + //too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 |
| 156 | + result = creator.createSQLExecutor().execute(config, false); |
| 157 | + |
| 158 | + // version, method, tag 组合情况太多了,JDK 里又没有 LRUCache,所以要么启动时一次性缓存全部后面只用缓存,要么每次都查数据库 |
| 159 | + // versionedMap.put(Integer.valueOf(version), result); |
| 160 | + // DOCUMENT_MAP.put(cacheKey, versionedMap); |
| 161 | + } |
| 162 | + |
| 163 | + String apijson = result == null ? null : result.getString("apijson"); |
| 164 | + if (StringUtil.isEmpty(apijson, true)) { // |
| 165 | + if (compatCommonAPI) { |
| 166 | + return crudByTag(method, tag, params, request, session); |
| 167 | + } |
| 168 | + |
| 169 | + throw new IllegalArgumentException("URL 路径 /" + method |
| 170 | + + "/" + tag + (versionStr == null ? "" : "?version=" + versionStr) + " 对应的接口不存在!"); |
| 171 | + } |
| 172 | + |
| 173 | + JSONObject rawReq = JSON.parseObject(request); |
| 174 | + if (rawReq == null) { |
| 175 | + rawReq = new JSONObject(true); |
| 176 | + } |
| 177 | + if (params != null && params.isEmpty() == false) { |
| 178 | + rawReq.putAll(params); |
| 179 | + } |
| 180 | + |
| 181 | + RequestMethod requestMethod = RequestMethod.valueOf(method.toUpperCase()); |
| 182 | + Parser<T> parser = newParser(session, requestMethod); |
| 183 | + |
| 184 | + if (parser.isNeedVerifyContent()) { |
| 185 | + Verifier<T> verifier = creator.createVerifier(); |
| 186 | + |
| 187 | + //获取指定的JSON结构 <<<<<<<<<<<< |
| 188 | + JSONObject object; |
| 189 | + object = parser.getStructure("Request", method.toUpperCase(), tag, version); |
| 190 | + if (object == null) { //empty表示随意操作 || object.isEmpty()) { |
| 191 | + throw new UnsupportedOperationException("找不到 version: " + version + ", method: " + method.toUpperCase() + ", tag: " + tag + " 对应的 structure !" |
| 192 | + + "非开放请求必须是后端 Request 表中校验规则允许的操作!如果需要则在 Request 表中新增配置!"); |
| 193 | + } |
| 194 | + |
| 195 | + JSONObject target = object; |
| 196 | + |
| 197 | + //JSONObject clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} |
| 198 | + verifier.verifyRequest(requestMethod, "", target, rawReq, 0, null, null, creator); |
| 199 | + } |
| 200 | + |
| 201 | + JSONObject apijsonReq = JSON.parseObject(apijson); |
| 202 | + if (apijsonReq == null) { |
| 203 | + apijsonReq = new JSONObject(true); |
| 204 | + } |
| 205 | + |
| 206 | + Set<Entry<String, Object>> rawSet = rawReq.entrySet(); |
| 207 | + if (rawSet != null && rawSet.isEmpty() == false) { |
| 208 | + for (Entry<String, Object> entry : rawSet) { |
| 209 | + String key = entry == null ? null : entry.getKey(); |
| 210 | + if (key == null) { // value 为 null 有效 |
| 211 | + continue; |
| 212 | + } |
| 213 | + |
| 214 | + String[] pathKeys = key.split("\\."); |
| 215 | + //逐层到达child的直接容器JSONObject parent |
| 216 | + int last = pathKeys.length - 1; |
| 217 | + JSONObject parent = apijsonReq; |
| 218 | + for (int i = 0; i < last; i++) {//一步一步到达指定位置 |
| 219 | + JSONObject p = parent.getJSONObject(pathKeys[i]); |
| 220 | + if (p == null) { |
| 221 | + p = new JSONObject(true); |
| 222 | + parent.put(key, p); |
| 223 | + } |
| 224 | + parent = p; |
| 225 | + } |
| 226 | + |
| 227 | + parent.put(pathKeys[last], entry.getValue()); |
| 228 | + } |
| 229 | + } |
| 230 | + |
| 231 | + // 没必要,已经是预设好的实际参数了,如果要 tag 就在 apijson 字段配置 apijsonReq.put(JSONRequest.KEY_TAG, tag); |
| 232 | + |
| 233 | + return parser.setNeedVerifyContent(false).parse(apijsonReq); |
| 234 | + } |
| 235 | + catch (Exception e) { |
| 236 | + return APIJSONParser.newErrorResult(e).toJSONString(); |
| 237 | + } |
| 238 | + } |
| 239 | + |
| 240 | + |
| 241 | +} |
0 commit comments