├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── apijson │ └── router │ ├── APIJSONRouterApplication.java │ ├── APIJSONRouterController.java │ ├── APIJSONRouterVerifier.java │ ├── javax │ ├── APIJSONRouterApplication.java │ ├── APIJSONRouterController.java │ ├── APIJSONRouterVerifier.java │ └── package-info.java │ └── package-info.java └── resources └── application.properties /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | /target/ 25 | .settings 26 | .project 27 | .classpath 28 | 29 | .idea 30 | 31 | /build/ 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright ©2022 APIJSON(https://github.com/APIJSON) 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apijson-router [![](https://jitpack.io/v/APIJSON/apijson-router.svg)](https://jitpack.io/#APIJSON/apijson-router) 2 | 腾讯 [APIJSON](https://github.com/Tencent/APIJSON) 5.1.0+ 的路由插件,可控地对公网暴露类 RESTful 简单接口,内部转成 APIJSON 格式请求来执行。
3 | A router plugin for Tencent [APIJSON](https://github.com/Tencent/APIJSON) 5.1.0+, expose undercontrolled RESTful-like HTTP API, map to APIJSON request and execute.
4 | 5 | 适合在公司外的公网可控地暴露 HTTP 接口,以及方便接入 ZooKeeper, Zuul, Apollo, Nacos, SpringSecurity, Shiro, Sentinel, Hystrix 等各种使用 URL 路由的 配置、鉴权、监控、限流、熔断 等网关/框架/组件。
6 | HTTP APIs can be exposed to public network and be under control, and can easily integrate Configuration/Authentication/Monitoring/Limitation/Isolatation projects(gateway, framework, component) using URL routes like ZooKeeper, Zuul, Apollo, Nacos, SpringSecurity, Shiro, Sentinel, Hystrix. 7 | 8 | ![image](https://user-images.githubusercontent.com/5738175/166560119-c598d3c6-48b6-4f47-85fe-8f36ca332e99.png) 9 | 10 | ## 添加依赖 11 | ## Add Dependency 12 | 13 | ### Maven 14 | #### 1. 在 pom.xml 中添加 JitPack 仓库 15 | #### 1. Add the JitPack repository to pom.xml 16 | ```xml 17 | 18 | 19 | jitpack.io 20 | https://jitpack.io 21 | 22 | 23 | ``` 24 | 25 | ![image](https://user-images.githubusercontent.com/5738175/167263399-339dad4f-2884-461e-9781-f2de6d100340.png) 26 | 27 |
28 | 29 | #### 2. 在 pom.xml 中添加 apijson-router 依赖 30 | #### 2. Add the apijson-router dependency to pom.xml 31 | ```xml 32 | 33 | com.github.APIJSON 34 | apijson-router 35 | LATEST 36 | 37 | ``` 38 | 39 | ![image](https://user-images.githubusercontent.com/5738175/167263390-f3cc8fed-9fd5-4ee1-b8d1-e2d1a695b16c.png) 40 | 41 |
42 | 43 | https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot-MultiDataSource/pom.xml 44 | 45 |
46 |
47 | 48 | ### Gradle 49 | #### 1. 在项目根目录 build.gradle 中最后添加 JitPack 仓库 50 | #### 1. Add the JitPack repository in your root build.gradle at the end of repositories 51 | ```gradle 52 | allprojects { 53 | repositories { 54 | maven { url 'https://jitpack.io' } 55 | } 56 | } 57 | ``` 58 |
59 | 60 | #### 2. 在项目某个 module 目录(例如 `app`) build.gradle 中添加 apijson-router 依赖 61 | #### 2. Add the apijson-router dependency in one of your modules(such as `app`) 62 | ```gradle 63 | dependencies { 64 | implementation 'com.github.APIJSON:apijson-router:latest' 65 | } 66 | ``` 67 | 68 |
69 |
70 | 71 | ## 初始化 72 | ## Initialization 73 | 74 | #### 1.新增一个 @RestController class DemoController extends APIJSONRouterController 75 | #### 1.Add a @RestController class DemoController extends APIJSONRouterController 76 | ```java 77 | @RestController 78 | @RequestMapping("") 79 | public class DemoController extends APIJSONRouterController { 80 | } 81 | ``` 82 | ![image](https://user-images.githubusercontent.com/5738175/167263296-3bfd8782-c163-4461-bbed-f264be529e76.png) 83 | 84 |
85 | 86 | #### 2.在 DemoController 重写 router 方法,加上注解 @PostMapping("router/{method}/{tag}") 87 | #### 2.Override router in DemoController, and add @PostMapping("router/{method}/{tag}") for router method 88 | ```java 89 | @PostMapping("router/{method}/{tag}") 90 | @Override 91 | public String router(@PathVariable String method, @PathVariable String tag, @RequestParam Map params, @RequestBody String request, HttpSession session) { 92 | return super.router(method, tag, params, request, session); 93 | } 94 | ``` 95 | 96 | ![image](https://user-images.githubusercontent.com/5738175/167263339-7c7cce6e-25bf-47b1-86a3-fc2950b8938d.png) 97 | 98 |
99 | 100 | #### 3.在 DemoApplication.main 方法内,APIJSONAppication.init 后调用 APIJSONRouterApplication.init 101 | #### 3.In DemoApplication.main, call APIJSONRouterApplication.init after APIJSONAppication.init 102 | ```java 103 | public static void main(String[] args) throws Exception { 104 | SpringApplication.run(DemoApplication.class, args); 105 | APIJSONApplication.init(); 106 | APIJSONRouterApplication.init(); 107 | } 108 | ``` 109 | 110 | ![image](https://user-images.githubusercontent.com/5738175/167263261-25fc5a02-7980-443f-94d9-76d2b488ce61.png) 111 | 112 |
113 | 114 | 115 | 参考 [APIJSONRouterController](/src/main/java/apijson/router/APIJSONRouterController.java) 的注释及 [APIJSONBoot](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot) 的 [DemoController](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot/src/main/java/apijson/boot/DemoController.java) 和 [DemoApplication](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot/src/main/java/apijson/boot/DemoApplication.java)
116 | 117 | See document in [APIJSONRouterController](/src/main/java/apijson/router/APIJSONRouterController.java) and [DemoController](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot/src/main/java/apijson/boot/DemoController.java), [DemoApplication](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot/src/main/java/apijson/boot/DemoApplication.java) in [APIJSONBoot](https://github.com/APIJSON/APIJSON-Demo/blob/master/APIJSON-Java-Server/APIJSONBoot) 118 | 119 |
120 |
121 | 122 | ## 使用 123 | ## Usage 124 | 125 | #### 以下步骤 1, 2 可改为直接在 APIAuto 参数注入面板点击 \[+ 添加] 按钮,再点击弹窗内 \[发布简单接口] 按钮来自动完成 126 | #### Instead of step 1 and 2, you can use APIAuto to complete them automatically: click \[+ Add], then click \[Release simple API] 127 | 128 | ![image](https://user-images.githubusercontent.com/5738175/166562199-4d96dd16-cf25-4bd4-b574-94a3c5f32685.png) 129 | 130 |
131 | 132 | ### 1.在 Document 表配置请求映射 133 | ### 1.Add mapping rule in table Document 134 | 135 | 例如
136 | E.g.
137 | 138 | name: 查询动态列表 139 | 140 | url: /router/get/momentList // 最后必须为 /{method}/{tag} 格式:method 必须为万能通用路由名;tag 不能为 Table 或 Table\[] 格式 141 | 142 | request: 143 | ```js 144 | { 145 | "Moment[].page": 0, // 以 . 分割路径中的 key,映射以下 "Moment[]": { "page": 0 } 146 | "Moment[].count": 10, // 以 . 分割路径中的 key,映射以下 "Moment[]": { "count": 10 } 147 | "format": false // 映射以下 "format": false 148 | } 149 | ``` 150 | 151 | apijson: 152 | ```js 153 | { 154 | "Moment[]": { 155 | "page": 0, 156 | "count": 10, 157 | "Moment": { 158 | "@column": "id,userId,date" 159 | } 160 | }, 161 | "format": false 162 | } 163 | ``` 164 | 165 | 其它字段可不填,用默认值
166 | Other columns can use default value
167 | 168 | ![image](https://user-images.githubusercontent.com/5738175/166565083-1db03cde-8b59-4048-af6d-78d9efb78f7c.png) 169 | 170 |
171 | 172 | ### 2.在 Request 表配置校验规则 173 | ### 2.Add validation rule in table Request 174 | 175 | 如果不需要校验参数则可跳过。
176 | This step can be ignored if validation is not needed.
177 | 178 | 和普通的 APIJSON 格式请求基本一致,只是不会自动根据符合表名的 tag 来对 structure 包装一层 "Table": structure
179 | The same as common APIJSON requests, but won't wrap structure with tag to "Table": structure
180 | 181 | 例如
182 | E.g.
183 | 184 | method: GET 185 | 186 | tag: momentList 187 | 188 | structure: 189 | ```js 190 | { 191 | "MUST": "Moment[].page", // 必传 Moment[].page 192 | "REFUSE": "!Moment[].count,!format,!", // 不禁传 Moment[].count 和 format,禁传 MUST 之外的其它所有 key 193 | "TYPE": { 194 | "format": "BOOLEAN", // format 类型必须是布尔 Boolean 195 | "Moment[].page": "NUMBER", // Moment[].page 类型必须是整数 Integer 196 | "Moment[].count": "NUMBER" // Moment[].count 类型必须是整数 Integer 197 | } 198 | } 199 | ``` 200 | 201 | ![image](https://user-images.githubusercontent.com/5738175/166563592-e8d3f09f-471a-4ae1-bee9-de78ec16fefe.png) 202 | 203 |
204 | 205 | ### 3.测试已配置的类 RESTful 简单接口 206 | ### 3.Test configured RESTful-like API 207 | 208 | 启动项目后用 APIAuto/Postman 等 HTTP 接口测试工具发起请求
209 | After run project and the server has started, you can use HTTP tools like APIAuto/Postman to send request
210 | 211 | POST {base_url}/router/get/{tag} // tag 可为任意符合变量名格式的字符串 212 | ```js 213 | { 214 | "showKey0": val0, 215 | "showKey1.a1": val1, 216 | "showKey2.a2.b2": val2 217 | ... 218 | } 219 | ``` 220 | 221 | 例如
222 | E.g.
223 | 224 | POST http://localhost:8080/router/get/momentList // 对应 Document 表配置的 url 225 | ```js 226 | { 227 | "Moment[].page": 0, 228 | "Moment[].count": 5, 229 | "format": false 230 | } 231 | ``` 232 | 233 | 如果 parser.isNeedVerifyContent,则会经过 Request 表校验规则来校验,
234 | If parser.isNeedVerifyContent, it will be validated with the rule in table Request
235 | 236 | 最后内部映射为:
237 | Finally it will be mapped to:
238 | 239 | ```js 240 | { 241 | "Moment[]": { 242 | "page": 0, 243 | "count": 5, 244 | "Moment": { 245 | "@order": "date-" 246 | } 247 | }, 248 | "format": false 249 | } 250 | ``` 251 | 252 | 执行完 APIJSON 格式的请求后返回对应结果
253 | Server will execute APIJSON request and response
254 | 255 | ![image](https://user-images.githubusercontent.com/5738175/166560119-c598d3c6-48b6-4f47-85fe-8f36ca332e99.png) 256 | 257 | 258 |
259 | 有问题可以去 Tencent/APIJSON 提 issue
260 | https://github.com/Tencent/APIJSON/issues/36 261 | 262 |

263 | 264 | #### 点右上角 ⭐Star 支持一下,谢谢 ^_^ 265 | #### Please ⭐Star this project ^_^ 266 | https://github.com/APIJSON/apijson-router 267 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | apijson.router 7 | apijson-router 8 | 2.1.7 9 | jar 10 | 11 | APIJSONRouter 12 | APIJSON router for mapping RESTful-like API to APIJSON API 13 | 14 | 15 | UTF-8 16 | UTF-8 17 | 1.8 18 | UTF-8 19 | 20 | 21 | 22 | 23 | 24 | jakarta.servlet 25 | jakarta.servlet-api 26 | 5.0.0 27 | provided 28 | 29 | 30 | 31 | 32 | javax.servlet 33 | javax.servlet-api 34 | 4.0.1 35 | provided 36 | 37 | 38 | 39 | 40 | com.github.Tencent 41 | APIJSON 42 | 8.0.0.1 43 | 44 | 45 | com.github.APIJSON 46 | apijson-framework 47 | 7.2.0.1 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-compiler-plugin 56 | 3.12.1 57 | 58 | 1.8 59 | 1.8 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | jitpack.io 69 | https://jitpack.io 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/java/apijson/router/APIJSONRouterApplication.java: -------------------------------------------------------------------------------- 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; 16 | 17 | import apijson.NotNull; 18 | import apijson.framework.APIJSONApplication; 19 | import apijson.framework.APIJSONCreator; 20 | 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | 25 | /**启动入口 Application 基类 26 | * 右键这个类 > Run As > Java Application 27 | * @author Lemon 28 | */ 29 | public class APIJSONRouterApplication extends APIJSONApplication { 30 | public static final String TAG = "APIJSONRouterApplication"; 31 | 32 | /**初始化,加载所有配置并校验 33 | * @return 34 | * @throws Exception 35 | */ 36 | public static void init() throws Exception { 37 | init(true, DEFAULT_APIJSON_CREATOR); 38 | } 39 | /**初始化,加载所有配置并校验 40 | * @param shutdownWhenServerError 41 | * @return 42 | * @throws Exception 43 | */ 44 | public static void init(boolean shutdownWhenServerError) throws Exception { 45 | init(shutdownWhenServerError, DEFAULT_APIJSON_CREATOR); 46 | } 47 | /**初始化,加载所有配置并校验 48 | * @param creator 49 | * @return 50 | * @throws Exception 51 | */ 52 | public static , L extends List> void init( 53 | @NotNull APIJSONCreator creator) throws Exception { 54 | init(true, creator); 55 | } 56 | /**初始化,加载所有配置并校验 57 | * @param shutdownWhenServerError 58 | * @param creator 59 | * @return 60 | * @throws Exception 61 | */ 62 | public static , L extends List> void init(boolean shutdownWhenServerError 63 | , @NotNull APIJSONCreator creator) throws Exception { 64 | // 避免多个插件重复调用这句 APIJSONApplication.init(shutdownWhenServerError, creator); 65 | System.out.println("\n\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<< APIJSON Router 开始启动 >>>>>>>>>>>>>>>>>>>>>>>>\n"); 66 | 67 | 68 | System.out.println("\n\n\n开始初始化: Document 请求映射配置 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); 69 | try { 70 | APIJSONRouterVerifier.initDocument(shutdownWhenServerError, creator); 71 | } 72 | catch (Throwable e) { 73 | e.printStackTrace(); 74 | if (shutdownWhenServerError) { 75 | onServerError("Document 请求映射配置 初始化失败!", shutdownWhenServerError); 76 | } 77 | } 78 | System.out.println("\n完成初始化: Document 请求映射配置 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 79 | 80 | System.out.println("\n插件地址: https://github.com/APIJSON/apijson-router"); 81 | System.out.println("\n\n<<<<<<<<<<<<<<<<<<<<<<<<< APIJSON Router 启动完成,试试调用类 RESTful API 吧 ^_^ >>>>>>>>>>>>>>>>>>>>>>>>\n"); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/apijson/router/APIJSONRouterController.java: -------------------------------------------------------------------------------- 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; 16 | 17 | import static apijson.JSON.getJSONObject; 18 | import static apijson.JSON.getString; 19 | import static apijson.RequestMethod.GET; 20 | import static apijson.framework.APIJSONConstant.METHODS; 21 | 22 | import java.util.*; 23 | import java.util.Map.Entry; 24 | 25 | import jakarta.servlet.http.HttpSession; 26 | 27 | import apijson.JSON; 28 | import apijson.JSONRequest; 29 | import apijson.Log; 30 | import apijson.RequestMethod; 31 | import apijson.StringUtil; 32 | import apijson.framework.APIJSONConstant; 33 | import apijson.framework.APIJSONController; 34 | import apijson.framework.APIJSONCreator; 35 | import apijson.framework.APIJSONParser; 36 | import apijson.orm.AbstractVerifier; 37 | import apijson.orm.Parser; 38 | import apijson.orm.SQLConfig; 39 | import apijson.orm.Verifier; 40 | 41 | 42 | /**APIJSON router controller,建议在子项目被 @RestController 注解的类继承它或通过它的实例调用相关方法 43 | * @author Lemon 44 | */ 45 | public class APIJSONRouterController, L extends List> 46 | extends APIJSONController { 47 | public static final String TAG = "APIJSONRouterController"; 48 | 49 | //通用接口,非事务型操作 和 简单事务型操作 都可通过这些接口自动化实现<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 50 | 51 | /**增删改查统一的类 RESTful API 入口,牺牲一些路由解析性能来提升一点开发效率 52 | * compatCommonAPI = Log.DEBUG 53 | * @param method 54 | * @param tag 55 | * @param params 56 | * @param request 57 | * @param session 58 | * @return 59 | */ 60 | public String router(String method, String tag, Map params, String request, HttpSession session) { 61 | return router(method, tag, params, request, session, Log.DEBUG); 62 | } 63 | /**增删改查统一的类 RESTful API 入口,牺牲一些路由解析性能来提升一点开发效率 64 | * @param method 65 | * @param tag 66 | * @param params 67 | * @param request 68 | * @param session 69 | * @param compatCommonAPI 兼容万能通用 API,当没有映射 APIJSON 格式请求时,自动转到万能通用 API 70 | * @return 71 | */ 72 | public String router(String method, String tag, Map params, String request, HttpSession session, boolean compatCommonAPI) { 73 | RequestMethod requestMethod = null; 74 | try { 75 | requestMethod = RequestMethod.valueOf(method.toUpperCase()); 76 | } catch (Throwable e) { 77 | // 下方 METHODS.contains(method) 会抛异常 78 | } 79 | Parser parser = newParser(session, requestMethod); 80 | 81 | if (METHODS.contains(method) == false) { 82 | return JSON.toJSONString( 83 | parser.newErrorResult( 84 | new IllegalArgumentException("URL 路径 /{method}/{tag} 中 method 值 " 85 | + method + " 错误!只允许 " + METHODS + " 中的一个!" 86 | ) 87 | ) 88 | ); 89 | } 90 | 91 | String t = compatCommonAPI && tag != null && tag.endsWith("[]") ? tag.substring(0, tag.length() - 2) : tag; 92 | if (StringUtil.isName(t) == false) { 93 | return JSON.toJSONString( 94 | parser.newErrorResult( 95 | new IllegalArgumentException("URL 路径 /" + method + "/{tag} 的 tag 中 " 96 | + t + " 错误!tag 不能为空,且只允许变量命名格式!" 97 | ) 98 | ) 99 | ); 100 | } 101 | 102 | String versionStr = params == null ? null : params.remove(APIJSONConstant.VERSION); 103 | Integer version; 104 | try { 105 | version = StringUtil.isEmpty(versionStr, false) ? null : Integer.valueOf(versionStr); 106 | } 107 | catch (Exception e) { 108 | return JSON.toJSONString( 109 | parser.newErrorResult(new IllegalArgumentException("URL 路径 /" + method + "/" 110 | + tag + "?version=value 中 value 值 " + versionStr + " 错误!必须符合整数格式!") 111 | ) 112 | ); 113 | } 114 | 115 | if (version == null) { 116 | version = 0; 117 | } 118 | 119 | try { 120 | // 从 Document 查这样的接口 121 | String cacheKey = AbstractVerifier.getCacheKeyForRequest(method, tag); 122 | SortedMap> versionedMap = APIJSONRouterVerifier.DOCUMENT_MAP.get(cacheKey); 123 | 124 | Map result = versionedMap == null ? null : versionedMap.get(version); 125 | if (result == null) { // version <= 0 时使用最新,version > 0 时使用 > version 的最接近版本(最小版本) 126 | Set>> set = versionedMap == null ? null : versionedMap.entrySet(); 127 | 128 | if (set != null && set.isEmpty() == false) { 129 | Entry> maxEntry = null; 130 | 131 | for (Entry> entry : set) { 132 | if (entry == null || entry.getKey() == null || entry.getValue() == null) { 133 | continue; 134 | } 135 | 136 | if (version == null || version <= 0 || version == entry.getKey()) { // 这里应该不会出现相等,因为上面 versionedMap.get(Integer.valueOf(version)) 137 | maxEntry = entry; 138 | break; 139 | } 140 | 141 | if (entry.getKey() < version) { 142 | break; 143 | } 144 | 145 | maxEntry = entry; 146 | } 147 | 148 | result = maxEntry == null ? null : maxEntry.getValue(); 149 | } 150 | 151 | if (result != null) { // 加快下次查询,查到值的话组合情况其实是有限的,不属于恶意请求 152 | if (versionedMap == null) { 153 | versionedMap = new TreeMap<>((o1, o2) -> { 154 | return o2 == null ? -1 : o2.compareTo(o1); // 降序 155 | }); 156 | } 157 | 158 | versionedMap.put(version, result); 159 | APIJSONRouterVerifier.DOCUMENT_MAP.put(cacheKey, versionedMap); 160 | } 161 | } 162 | 163 | @SuppressWarnings("unchecked") 164 | APIJSONCreator creator = (APIJSONCreator) APIJSONParser.APIJSON_CREATOR; 165 | if (result == null && Log.DEBUG && APIJSONRouterVerifier.DOCUMENT_MAP.isEmpty()) { 166 | 167 | //获取指定的JSON结构 <<<<<<<<<<<<<< 168 | SQLConfig config = creator.createSQLConfig().setMethod(GET).setTable(APIJSONConstant.DOCUMENT_); 169 | config.setPrepared(false); 170 | config.setColumn(Arrays.asList("request,apijson")); 171 | 172 | Map where = new HashMap(); 173 | where.put("url", "/" + method + "/" + tag); 174 | where.put("apijson{}", "length(apijson)>0"); 175 | 176 | if (version > 0) { 177 | where.put(JSONRequest.KEY_VERSION + ">=", version); 178 | } 179 | config.setWhere(where); 180 | config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); 181 | config.setCount(1); 182 | 183 | //too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 184 | result = creator.createSQLExecutor().execute(config, false); 185 | 186 | // version, method, tag 组合情况太多了,JDK 里又没有 LRUCache,所以要么启动时一次性缓存全部后面只用缓存,要么每次都查数据库 187 | // versionedMap.put(Integer.valueOf(version), result); 188 | // DOCUMENT_MAP.put(cacheKey, versionedMap); 189 | } 190 | 191 | String apijson = result == null ? null : getString(result, "apijson"); 192 | if (StringUtil.isEmpty(apijson, true)) { // 193 | if (compatCommonAPI) { 194 | return crudByTag(method, tag, params, request, session); 195 | } 196 | 197 | throw new IllegalArgumentException("URL 路径 /" + method 198 | + "/" + tag + (versionStr == null ? "" : "?version=" + versionStr) + " 对应的接口不存在!"); 199 | } 200 | 201 | M rawReq = JSON.parseObject(request); 202 | if (rawReq == null) { 203 | rawReq = JSON.createJSONObject(); 204 | } 205 | if (params != null && params.isEmpty() == false) { 206 | rawReq.putAll(params); 207 | } 208 | 209 | if (parser.isNeedVerifyContent()) { 210 | Verifier verifier = creator.createVerifier(); 211 | 212 | //获取指定的JSON结构 <<<<<<<<<<<< 213 | Map target = parser.getStructure("Request", method.toUpperCase(), tag, version); 214 | if (target == null) { //empty表示随意操作 || object.isEmpty()) { 215 | throw new UnsupportedOperationException("找不到 version: " + version + ", method: " + method.toUpperCase() + ", tag: " + tag + " 对应的 structure !" 216 | + "非开放请求必须是后端 Request 表中校验规则允许的操作!如果需要则在 Request 表中新增配置!"); 217 | } 218 | 219 | //M clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} 220 | verifier.verifyRequest(requestMethod, "", JSON.createJSONObject(target), rawReq, 0, null, null, creator); 221 | } 222 | 223 | M apijsonReq = JSON.parseObject(apijson); 224 | if (apijsonReq == null) { 225 | apijsonReq = JSON.createJSONObject(); 226 | } 227 | 228 | Set> rawSet = rawReq.entrySet(); 229 | if (rawSet != null && rawSet.isEmpty() == false) { 230 | for (Entry entry : rawSet) { 231 | String key = entry == null ? null : entry.getKey(); 232 | if (key == null) { // value 为 null 有效 233 | continue; 234 | } 235 | 236 | String[] pathKeys = key.split("\\."); 237 | //逐层到达child的直接容器JSONObject parent 238 | int last = pathKeys.length - 1; 239 | M parent = apijsonReq; 240 | for (int i = 0; i < last; i++) {//一步一步到达指定位置 241 | M p = getJSONObject(parent, pathKeys[i]); 242 | if (p == null) { 243 | p = JSON.createJSONObject(); 244 | parent.put(key, p); 245 | } 246 | parent = p; 247 | } 248 | 249 | parent.put(pathKeys[last], entry.getValue()); 250 | } 251 | } 252 | 253 | // 没必要,已经是预设好的实际参数了,如果要 tag 就在 apijson 字段配置 apijsonReq.put(JSONRequest.KEY_TAG, tag); 254 | 255 | return parser.setNeedVerifyContent(false).parse(apijsonReq); 256 | } 257 | catch (Exception e) { 258 | return JSON.toJSONString(parser.newErrorResult(e)); 259 | } 260 | } 261 | 262 | 263 | } 264 | -------------------------------------------------------------------------------- /src/main/java/apijson/router/APIJSONRouterVerifier.java: -------------------------------------------------------------------------------- 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; 16 | 17 | import static apijson.JSON.*; 18 | import static apijson.JSONRequest.KEY_COUNT; 19 | import static apijson.framework.APIJSONConstant.DOCUMENT_; 20 | import static apijson.framework.APIJSONConstant.METHODS; 21 | 22 | import java.rmi.ServerException; 23 | import java.util.*; 24 | 25 | import apijson.JSON; 26 | import apijson.JSONResponse; 27 | import apijson.Log; 28 | import apijson.RequestMethod; 29 | import apijson.StringUtil; 30 | import apijson.framework.APIJSONConstant; 31 | import apijson.framework.APIJSONCreator; 32 | import apijson.framework.APIJSONVerifier; 33 | import apijson.orm.JSONRequest; 34 | 35 | 36 | /**路由请求映射验证器 37 | * @author Lemon 38 | */ 39 | public class APIJSONRouterVerifier, L extends List> 40 | extends APIJSONVerifier { 41 | public static final String TAG = "APIJSONRouterVerifier"; 42 | 43 | 44 | // method-tag, 45 | public static Map>> DOCUMENT_MAP; 46 | static { 47 | DOCUMENT_MAP = new HashMap<>(); 48 | } 49 | 50 | /**初始化,加载所有请求映射配置和请求校验配置 51 | * @return 52 | * @throws ServerException 53 | */ 54 | public static , L extends List> M init() throws ServerException { 55 | return init(false); 56 | } 57 | /**初始化,加载所有请求映射配置和请求校验配置 58 | * @param shutdownWhenServerError 59 | * @return 60 | * @throws ServerException 61 | */ 62 | public static , L extends List> M init(boolean shutdownWhenServerError) throws ServerException { 63 | return init(shutdownWhenServerError, null); 64 | } 65 | /**初始化,加载所有请求映射配置和请求校验配置 66 | * @param creator 67 | * @return 68 | * @throws ServerException 69 | */ 70 | public static , L extends List> M init(APIJSONCreator creator) throws ServerException { 71 | return init(false, creator); 72 | } 73 | /**初始化,加载所有请求映射配置和请求校验配置 74 | * @param shutdownWhenServerError 75 | * @param creator 76 | * @return 77 | * @throws ServerException 78 | */ 79 | public static, L extends List> M init(boolean shutdownWhenServerError 80 | , APIJSONCreator creator) throws ServerException { 81 | M result = APIJSONVerifier.init(shutdownWhenServerError, creator); 82 | result.put(DOCUMENT_, initDocument(shutdownWhenServerError, creator)); 83 | return result; 84 | } 85 | 86 | 87 | /**初始化,加载所有请求校验配置 88 | * @return 89 | * @throws ServerException 90 | */ 91 | public static > M initDocument() throws ServerException { 92 | return initDocument(false); 93 | } 94 | /**初始化,加载所有请求校验配置 95 | * @param shutdownWhenServerError 96 | * @return 97 | * @throws ServerException 98 | */ 99 | public static > M initDocument(boolean shutdownWhenServerError) throws ServerException { 100 | return initDocument(shutdownWhenServerError, null); 101 | } 102 | /**初始化,加载所有请求校验配置 103 | * @param creator 104 | * @return 105 | * @throws ServerException 106 | */ 107 | public static , L extends List> M initDocument(APIJSONCreator creator) throws ServerException { 108 | return initDocument(false, creator); 109 | } 110 | /**初始化,加载所有请求校验配置 111 | * @param shutdownWhenServerError 112 | * @param creator 113 | * @return 114 | * @throws ServerException 115 | */ 116 | public static , L extends List> M initDocument(boolean shutdownWhenServerError, APIJSONCreator creator) throws ServerException { 117 | return initDocument(shutdownWhenServerError, creator, null); 118 | } 119 | /**初始化,加载所有请求校验配置 120 | * @param shutdownWhenServerError 121 | * @param creator 122 | * @param table 表内自定义数据过滤条件 123 | * @return 124 | * @throws ServerException 125 | */ 126 | @SuppressWarnings("unchecked") 127 | public static , L extends List> M initDocument(boolean shutdownWhenServerError, APIJSONCreator creator, M table) throws ServerException { 128 | if (creator == null) { 129 | creator = (APIJSONCreator) APIJSON_CREATOR; 130 | } 131 | APIJSON_CREATOR = creator; 132 | 133 | 134 | boolean isAll = table == null || table.isEmpty(); 135 | M document = isAll ? JSON.createJSONObject(new JSONRequest().puts("apijson{}", "length(apijson)>0").setOrder("version-,id+")) : table; 136 | if (Log.DEBUG == false) { 137 | document.put(APIJSONConstant.KEY_DEBUG, 0); 138 | } 139 | 140 | M requestItem = JSON.createJSONObject(); 141 | requestItem.put(DOCUMENT_, document); // 方便查找 142 | requestItem.put(KEY_COUNT, 0); 143 | 144 | M request = JSON.createJSONObject(); 145 | request.put(DOCUMENT_ + "[]", requestItem); 146 | 147 | M response = creator.createParser().setMethod(RequestMethod.GET).setNeedVerify(false).parseResponse(request); 148 | if (JSONResponse.isSuccess(response) == false) { 149 | Log.e(TAG, "\n\n\n\n\n !!!! 查询请求映射配置异常 !!!\n" + getString(response, JSONResponse.KEY_MSG) + "\n\n\n\n\n"); 150 | onServerError("查询请求映射配置异常 !", shutdownWhenServerError); 151 | } 152 | 153 | L list = getJSONArray(response, DOCUMENT_ + "[]"); 154 | int size = list == null ? 0 : list.size(); 155 | if (isAll && size <= 0) { 156 | Log.w(TAG, "initDocument isAll && size <= 0,没有可用的请求映射配置"); 157 | return response; 158 | } 159 | 160 | Log.d(TAG, "initDocument < for DOCUMENT_MAP.size() = " + DOCUMENT_MAP.size() + " <<<<<<<<<<<<<<<<<<<<<<<<"); 161 | 162 | 163 | Map>> newMap = new LinkedHashMap<>(); 164 | 165 | for (int i = 0; i < size; i++) { 166 | M item = getJSONObject(list, i); 167 | if (item == null) { 168 | continue; 169 | } 170 | 171 | String version = getString(item, "version"); 172 | if (StringUtil.isEmpty(version, true)) { 173 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") 174 | + " 对应 version 不能为空!", shutdownWhenServerError); 175 | } 176 | 177 | String url = getString(item, "url"); 178 | int index = url == null ? -1 : url.indexOf("/"); 179 | if (index != 0) { 180 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 181 | + " 对应 url 值错误,必须以 / 开头!", shutdownWhenServerError); 182 | } 183 | 184 | String requestStr = getString(item, "request"); 185 | 186 | String apijson = getString(item, "apijson"); 187 | if (StringUtil.isEmpty(apijson)) { 188 | if (StringUtil.isBranchUrl(url) == false) { 189 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 190 | + " 对应 url 值错误!只允许合法的 URL 格式!", shutdownWhenServerError); 191 | } 192 | } 193 | else { 194 | if (StringUtil.isNotEmpty(requestStr)) { 195 | try { 196 | JSON.parseObject(requestStr); 197 | } 198 | catch (Exception e) { 199 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 200 | + " 对应 request 值 " + requestStr + " 错误!只允许合法的 M 格式!" + e.getMessage(), shutdownWhenServerError); 201 | } 202 | } 203 | 204 | try { 205 | JSON.parseObject(apijson); 206 | } 207 | catch (Exception e) { 208 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 209 | + " 对应 apijson 值 " + apijson + " 错误!只允许合法的 M 格式!" + e.getMessage(), shutdownWhenServerError); 210 | } 211 | 212 | index = url.lastIndexOf("/"); 213 | String method = index < 0 ? null : url.substring(0, index); 214 | String tag = index < 0 ? null : url.substring(index + 1); 215 | 216 | index = method == null ? -1 : method.lastIndexOf("/"); 217 | method = index < 0 ? method : method.substring(index + 1); 218 | 219 | if (METHODS.contains(method) == false) { 220 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 221 | + " 对应路径 /{method}/{tag} 中 method 值 " + method + " 错误!apijson 字段不为空时只允许 " + METHODS + " 中的一个!", shutdownWhenServerError); 222 | } 223 | 224 | if (StringUtil.isName(tag) == false) { 225 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 226 | + " 对应路径 /{method}/{tag} 中 tag 值 " + tag + " 错误!apijson 字段不为空时只允许变量命名格式!", shutdownWhenServerError); 227 | } 228 | 229 | String cacheKey = getCacheKeyForRequest(method, tag); 230 | SortedMap> versionedMap = newMap.get(cacheKey); 231 | if (versionedMap == null) { 232 | versionedMap = new TreeMap<>(new Comparator() { 233 | 234 | @Override 235 | public int compare(Integer o1, Integer o2) { 236 | return o2 == null ? -1 : o2.compareTo(o1); // 降序 237 | } 238 | }); 239 | } 240 | versionedMap.put(Integer.valueOf(version), item); 241 | newMap.put(cacheKey, versionedMap); 242 | } 243 | 244 | } 245 | 246 | if (isAll) { // 全量更新 247 | DOCUMENT_MAP = newMap; 248 | } 249 | else { 250 | DOCUMENT_MAP.putAll(newMap); 251 | } 252 | 253 | Log.d(TAG, "initDocument for /> DOCUMENT_MAP.size() = " + DOCUMENT_MAP.size() + " >>>>>>>>>>>>>>>>>>>>>>>"); 254 | 255 | return response; 256 | } 257 | 258 | 259 | protected static void onServerError(String msg, boolean shutdown) throws ServerException { 260 | Log.e(TAG, "\n接口映射配置测试未通过!\n请修改 Document 表里的记录!\n保证前端看到的接口映射配置文档是正确的并且能正常使用!!!\n\n原因:\n" + msg); 261 | 262 | if (shutdown) { 263 | System.exit(1); 264 | } else { 265 | throw new ServerException(msg); 266 | } 267 | } 268 | 269 | 270 | } 271 | -------------------------------------------------------------------------------- /src/main/java/apijson/router/javax/APIJSONRouterApplication.java: -------------------------------------------------------------------------------- 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.NotNull; 18 | import apijson.framework.javax.APIJSONApplication; 19 | import apijson.framework.javax.APIJSONCreator; 20 | 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | 25 | /**启动入口 Application 基类 26 | * 右键这个类 > Run As > Java Application 27 | * @author Lemon 28 | */ 29 | public class APIJSONRouterApplication extends APIJSONApplication { 30 | public static final String TAG = "APIJSONRouterApplication"; 31 | 32 | /**初始化,加载所有配置并校验 33 | * @return 34 | * @throws Exception 35 | */ 36 | public static void init() throws Exception { 37 | init(true, DEFAULT_APIJSON_CREATOR); 38 | } 39 | /**初始化,加载所有配置并校验 40 | * @param shutdownWhenServerError 41 | * @return 42 | * @throws Exception 43 | */ 44 | public static void init(boolean shutdownWhenServerError) throws Exception { 45 | init(shutdownWhenServerError, DEFAULT_APIJSON_CREATOR); 46 | } 47 | /**初始化,加载所有配置并校验 48 | * @param creator 49 | * @return 50 | * @throws Exception 51 | */ 52 | public static , L extends List> void init( 53 | @NotNull APIJSONCreator creator) throws Exception { 54 | init(true, creator); 55 | } 56 | /**初始化,加载所有配置并校验 57 | * @param shutdownWhenServerError 58 | * @param creator 59 | * @return 60 | * @throws Exception 61 | */ 62 | public static , L extends List> void init(boolean shutdownWhenServerError 63 | , @NotNull APIJSONCreator creator) throws Exception { 64 | // 避免多个插件重复调用这句 APIJSONApplication.init(shutdownWhenServerError, creator); 65 | System.out.println("\n\n\n\n\n<<<<<<<<<<<<<<<<<<<<<<<<< APIJSON Router 开始启动 >>>>>>>>>>>>>>>>>>>>>>>>\n"); 66 | 67 | 68 | System.out.println("\n\n\n开始初始化: Document 请求映射配置 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); 69 | try { 70 | APIJSONRouterVerifier.initDocument(shutdownWhenServerError, creator); 71 | } 72 | catch (Throwable e) { 73 | e.printStackTrace(); 74 | if (shutdownWhenServerError) { 75 | onServerError("Document 请求映射配置 初始化失败!", shutdownWhenServerError); 76 | } 77 | } 78 | System.out.println("\n完成初始化: Document 请求映射配置 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 79 | 80 | System.out.println("\n插件地址: https://github.com/APIJSON/apijson-router"); 81 | System.out.println("\n\n<<<<<<<<<<<<<<<<<<<<<<<<< APIJSON Router 启动完成,试试调用类 RESTful API 吧 ^_^ >>>>>>>>>>>>>>>>>>>>>>>>\n"); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/apijson/router/javax/APIJSONRouterController.java: -------------------------------------------------------------------------------- 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 javax.servlet.http.HttpSession; 27 | 28 | import java.util.*; 29 | import java.util.Map.Entry; 30 | 31 | import static apijson.JSON.getJSONObject; 32 | import static apijson.JSON.getString; 33 | import static apijson.RequestMethod.GET; 34 | import static apijson.framework.javax.APIJSONConstant.METHODS; 35 | 36 | 37 | /**APIJSON router controller,建议在子项目被 @RestController 注解的类继承它或通过它的实例调用相关方法 38 | * @author Lemon 39 | */ 40 | public class APIJSONRouterController, L extends List> 41 | extends APIJSONController { 42 | public static final String TAG = "APIJSONRouterController"; 43 | 44 | //通用接口,非事务型操作 和 简单事务型操作 都可通过这些接口自动化实现<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 45 | 46 | /**增删改查统一的类 RESTful API 入口,牺牲一些路由解析性能来提升一点开发效率 47 | * compatCommonAPI = Log.DEBUG 48 | * @param method 49 | * @param tag 50 | * @param params 51 | * @param request 52 | * @param session 53 | * @return 54 | */ 55 | public String router(String method, String tag, Map params, String request, HttpSession session) { 56 | return router(method, tag, params, request, session, Log.DEBUG); 57 | } 58 | /**增删改查统一的类 RESTful API 入口,牺牲一些路由解析性能来提升一点开发效率 59 | * @param method 60 | * @param tag 61 | * @param params 62 | * @param request 63 | * @param session 64 | * @param compatCommonAPI 兼容万能通用 API,当没有映射 APIJSON 格式请求时,自动转到万能通用 API 65 | * @return 66 | */ 67 | public String router(String method, String tag, Map params, String request, HttpSession session, boolean compatCommonAPI) { 68 | RequestMethod requestMethod = null; 69 | try { 70 | requestMethod = RequestMethod.valueOf(method.toUpperCase()); 71 | } catch (Throwable e) { 72 | // 下方 METHODS.contains(method) 会抛异常 73 | } 74 | Parser parser = newParser(session, requestMethod); 75 | 76 | if (METHODS.contains(method) == false) { 77 | return JSON.toJSONString( 78 | parser.newErrorResult( 79 | new IllegalArgumentException("URL 路径 /{method}/{tag} 中 method 值 " 80 | + method + " 错误!只允许 " + METHODS + " 中的一个!" 81 | ) 82 | ) 83 | ); 84 | } 85 | 86 | String t = compatCommonAPI && tag != null && tag.endsWith("[]") ? tag.substring(0, tag.length() - 2) : tag; 87 | if (StringUtil.isName(t) == false) { 88 | return JSON.toJSONString( 89 | parser.newErrorResult( 90 | new IllegalArgumentException("URL 路径 /" + method + "/{tag} 的 tag 中 " 91 | + t + " 错误!tag 不能为空,且只允许变量命名格式!" 92 | ) 93 | ) 94 | ); 95 | } 96 | 97 | String versionStr = params == null ? null : params.remove(APIJSONConstant.VERSION); 98 | Integer version; 99 | try { 100 | version = StringUtil.isEmpty(versionStr, false) ? null : Integer.valueOf(versionStr); 101 | } 102 | catch (Exception e) { 103 | return JSON.toJSONString( 104 | parser.newErrorResult(new IllegalArgumentException("URL 路径 /" + method + "/" 105 | + tag + "?version=value 中 value 值 " + versionStr + " 错误!必须符合整数格式!") 106 | ) 107 | ); 108 | } 109 | 110 | if (version == null) { 111 | version = 0; 112 | } 113 | 114 | try { 115 | // 从 Document 查这样的接口 116 | String cacheKey = AbstractVerifier.getCacheKeyForRequest(method, tag); 117 | SortedMap> versionedMap = APIJSONRouterVerifier.DOCUMENT_MAP.get(cacheKey); 118 | 119 | Map result = versionedMap == null ? null : versionedMap.get(version); 120 | if (result == null) { // version <= 0 时使用最新,version > 0 时使用 > version 的最接近版本(最小版本) 121 | Set>> set = versionedMap == null ? null : versionedMap.entrySet(); 122 | 123 | if (set != null && set.isEmpty() == false) { 124 | Entry> maxEntry = null; 125 | 126 | for (Entry> entry : set) { 127 | if (entry == null || entry.getKey() == null || entry.getValue() == null) { 128 | continue; 129 | } 130 | 131 | if (version == null || version <= 0 || version == entry.getKey()) { // 这里应该不会出现相等,因为上面 versionedMap.get(Integer.valueOf(version)) 132 | maxEntry = entry; 133 | break; 134 | } 135 | 136 | if (entry.getKey() < version) { 137 | break; 138 | } 139 | 140 | maxEntry = entry; 141 | } 142 | 143 | result = maxEntry == null ? null : maxEntry.getValue(); 144 | } 145 | 146 | if (result != null) { // 加快下次查询,查到值的话组合情况其实是有限的,不属于恶意请求 147 | if (versionedMap == null) { 148 | versionedMap = new TreeMap<>((o1, o2) -> { 149 | return o2 == null ? -1 : o2.compareTo(o1); // 降序 150 | }); 151 | } 152 | 153 | versionedMap.put(version, result); 154 | APIJSONRouterVerifier.DOCUMENT_MAP.put(cacheKey, versionedMap); 155 | } 156 | } 157 | 158 | @SuppressWarnings("unchecked") 159 | APIJSONCreator creator = (APIJSONCreator) APIJSONParser.APIJSON_CREATOR; 160 | if (result == null && Log.DEBUG && APIJSONRouterVerifier.DOCUMENT_MAP.isEmpty()) { 161 | 162 | //获取指定的JSON结构 <<<<<<<<<<<<<< 163 | SQLConfig config = creator.createSQLConfig().setMethod(GET).setTable(APIJSONConstant.DOCUMENT_); 164 | config.setPrepared(false); 165 | config.setColumn(Arrays.asList("request,apijson")); 166 | 167 | Map where = new HashMap(); 168 | where.put("url", "/" + method + "/" + tag); 169 | where.put("apijson{}", "length(apijson)>0"); 170 | 171 | if (version > 0) { 172 | where.put(JSONRequest.KEY_VERSION + ">=", version); 173 | } 174 | config.setWhere(where); 175 | config.setOrder(JSONRequest.KEY_VERSION + (version > 0 ? "+" : "-")); 176 | config.setCount(1); 177 | 178 | //too many connections error: 不try-catch,可以让客户端看到是服务器内部异常 179 | result = creator.createSQLExecutor().execute(config, false); 180 | 181 | // version, method, tag 组合情况太多了,JDK 里又没有 LRUCache,所以要么启动时一次性缓存全部后面只用缓存,要么每次都查数据库 182 | // versionedMap.put(Integer.valueOf(version), result); 183 | // DOCUMENT_MAP.put(cacheKey, versionedMap); 184 | } 185 | 186 | String apijson = result == null ? null : getString(result, "apijson"); 187 | if (StringUtil.isEmpty(apijson, true)) { // 188 | if (compatCommonAPI) { 189 | return crudByTag(method, tag, params, request, session); 190 | } 191 | 192 | throw new IllegalArgumentException("URL 路径 /" + method 193 | + "/" + tag + (versionStr == null ? "" : "?version=" + versionStr) + " 对应的接口不存在!"); 194 | } 195 | 196 | M rawReq = JSON.parseObject(request); 197 | if (rawReq == null) { 198 | rawReq = JSON.createJSONObject(); 199 | } 200 | if (params != null && params.isEmpty() == false) { 201 | rawReq.putAll(params); 202 | } 203 | 204 | if (parser.isNeedVerifyContent()) { 205 | Verifier verifier = creator.createVerifier(); 206 | 207 | //获取指定的JSON结构 <<<<<<<<<<<< 208 | Map target = parser.getStructure("Request", method.toUpperCase(), tag, version); 209 | if (target == null) { //empty表示随意操作 || object.isEmpty()) { 210 | throw new UnsupportedOperationException("找不到 version: " + version + ", method: " + method.toUpperCase() + ", tag: " + tag + " 对应的 structure !" 211 | + "非开放请求必须是后端 Request 表中校验规则允许的操作!如果需要则在 Request 表中新增配置!"); 212 | } 213 | 214 | //M clone 浅拷贝没用,Structure.parse 会导致 structure 里面被清空,第二次从缓存里取到的就是 {} 215 | verifier.verifyRequest(requestMethod, "", JSON.createJSONObject(target), rawReq, 0, null, null, creator); 216 | } 217 | 218 | M apijsonReq = JSON.parseObject(apijson); 219 | if (apijsonReq == null) { 220 | apijsonReq = JSON.createJSONObject(); 221 | } 222 | 223 | Set> rawSet = rawReq.entrySet(); 224 | if (rawSet != null && rawSet.isEmpty() == false) { 225 | for (Entry entry : rawSet) { 226 | String key = entry == null ? null : entry.getKey(); 227 | if (key == null) { // value 为 null 有效 228 | continue; 229 | } 230 | 231 | String[] pathKeys = key.split("\\."); 232 | //逐层到达child的直接容器JSONObject parent 233 | int last = pathKeys.length - 1; 234 | M parent = apijsonReq; 235 | for (int i = 0; i < last; i++) {//一步一步到达指定位置 236 | M p = getJSONObject(parent, pathKeys[i]); 237 | if (p == null) { 238 | p = JSON.createJSONObject(); 239 | parent.put(key, p); 240 | } 241 | parent = p; 242 | } 243 | 244 | parent.put(pathKeys[last], entry.getValue()); 245 | } 246 | } 247 | 248 | // 没必要,已经是预设好的实际参数了,如果要 tag 就在 apijson 字段配置 apijsonReq.put(JSONRequest.KEY_TAG, tag); 249 | 250 | return parser.setNeedVerifyContent(false).parse(apijsonReq); 251 | } 252 | catch (Exception e) { 253 | return JSON.toJSONString(parser.newErrorResult(e)); 254 | } 255 | } 256 | 257 | 258 | } 259 | -------------------------------------------------------------------------------- /src/main/java/apijson/router/javax/APIJSONRouterVerifier.java: -------------------------------------------------------------------------------- 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.APIJSONCreator; 20 | import apijson.framework.javax.APIJSONVerifier; 21 | import apijson.orm.JSONRequest; 22 | 23 | import java.rmi.ServerException; 24 | import java.util.*; 25 | 26 | import static apijson.JSON.*; 27 | import static apijson.JSONRequest.KEY_COUNT; 28 | import static apijson.framework.javax.APIJSONConstant.DOCUMENT_; 29 | import static apijson.framework.javax.APIJSONConstant.METHODS; 30 | 31 | 32 | /**路由请求映射验证器 33 | * @author Lemon 34 | */ 35 | public class APIJSONRouterVerifier, L extends List> 36 | extends APIJSONVerifier { 37 | public static final String TAG = "APIJSONRouterVerifier"; 38 | 39 | 40 | // method-tag, 41 | public static Map>> DOCUMENT_MAP; 42 | static { 43 | DOCUMENT_MAP = new HashMap<>(); 44 | } 45 | 46 | /**初始化,加载所有请求映射配置和请求校验配置 47 | * @return 48 | * @throws ServerException 49 | */ 50 | public static , L extends List> M init() throws ServerException { 51 | return init(false); 52 | } 53 | /**初始化,加载所有请求映射配置和请求校验配置 54 | * @param shutdownWhenServerError 55 | * @return 56 | * @throws ServerException 57 | */ 58 | public static , L extends List> M init(boolean shutdownWhenServerError) throws ServerException { 59 | return init(shutdownWhenServerError, null); 60 | } 61 | /**初始化,加载所有请求映射配置和请求校验配置 62 | * @param creator 63 | * @return 64 | * @throws ServerException 65 | */ 66 | public static , L extends List> M init(APIJSONCreator creator) throws ServerException { 67 | return init(false, creator); 68 | } 69 | /**初始化,加载所有请求映射配置和请求校验配置 70 | * @param shutdownWhenServerError 71 | * @param creator 72 | * @return 73 | * @throws ServerException 74 | */ 75 | public static, L extends List> M init(boolean shutdownWhenServerError 76 | , APIJSONCreator creator) throws ServerException { 77 | M result = APIJSONVerifier.init(shutdownWhenServerError, creator); 78 | result.put(DOCUMENT_, initDocument(shutdownWhenServerError, creator)); 79 | return result; 80 | } 81 | 82 | 83 | /**初始化,加载所有请求校验配置 84 | * @return 85 | * @throws ServerException 86 | */ 87 | public static > M initDocument() throws ServerException { 88 | return initDocument(false); 89 | } 90 | /**初始化,加载所有请求校验配置 91 | * @param shutdownWhenServerError 92 | * @return 93 | * @throws ServerException 94 | */ 95 | public static > M initDocument(boolean shutdownWhenServerError) throws ServerException { 96 | return initDocument(shutdownWhenServerError, null); 97 | } 98 | /**初始化,加载所有请求校验配置 99 | * @param creator 100 | * @return 101 | * @throws ServerException 102 | */ 103 | public static , L extends List> M initDocument(APIJSONCreator creator) throws ServerException { 104 | return initDocument(false, creator); 105 | } 106 | /**初始化,加载所有请求校验配置 107 | * @param shutdownWhenServerError 108 | * @param creator 109 | * @return 110 | * @throws ServerException 111 | */ 112 | public static , L extends List> M initDocument(boolean shutdownWhenServerError, APIJSONCreator creator) throws ServerException { 113 | return initDocument(shutdownWhenServerError, creator, null); 114 | } 115 | /**初始化,加载所有请求校验配置 116 | * @param shutdownWhenServerError 117 | * @param creator 118 | * @param table 表内自定义数据过滤条件 119 | * @return 120 | * @throws ServerException 121 | */ 122 | @SuppressWarnings("unchecked") 123 | public static , L extends List> M initDocument(boolean shutdownWhenServerError, APIJSONCreator creator, M table) throws ServerException { 124 | if (creator == null) { 125 | creator = (APIJSONCreator) APIJSON_CREATOR; 126 | } 127 | APIJSON_CREATOR = creator; 128 | 129 | 130 | boolean isAll = table == null || table.isEmpty(); 131 | M document = isAll ? JSON.createJSONObject(new JSONRequest().puts("apijson{}", "length(apijson)>0").setOrder("version-,id+")) : table; 132 | if (Log.DEBUG == false) { 133 | document.put(APIJSONConstant.KEY_DEBUG, 0); 134 | } 135 | 136 | M requestItem = JSON.createJSONObject(); 137 | requestItem.put(DOCUMENT_, document); // 方便查找 138 | requestItem.put(KEY_COUNT, 0); 139 | 140 | M request = JSON.createJSONObject(); 141 | request.put(DOCUMENT_ + "[]", requestItem); 142 | 143 | M response = creator.createParser().setMethod(RequestMethod.GET).setNeedVerify(false).parseResponse(request); 144 | if (JSONResponse.isSuccess(response) == false) { 145 | Log.e(TAG, "\n\n\n\n\n !!!! 查询请求映射配置异常 !!!\n" + getString(response, JSONResponse.KEY_MSG) + "\n\n\n\n\n"); 146 | onServerError("查询请求映射配置异常 !", shutdownWhenServerError); 147 | } 148 | 149 | L list = getJSONArray(response, DOCUMENT_ + "[]"); 150 | int size = list == null ? 0 : list.size(); 151 | if (isAll && size <= 0) { 152 | Log.w(TAG, "initDocument isAll && size <= 0,没有可用的请求映射配置"); 153 | return response; 154 | } 155 | 156 | Log.d(TAG, "initDocument < for DOCUMENT_MAP.size() = " + DOCUMENT_MAP.size() + " <<<<<<<<<<<<<<<<<<<<<<<<"); 157 | 158 | 159 | Map>> newMap = new LinkedHashMap<>(); 160 | 161 | for (int i = 0; i < size; i++) { 162 | M item = getJSONObject(list, i); 163 | if (item == null) { 164 | continue; 165 | } 166 | 167 | String version = getString(item, "version"); 168 | if (StringUtil.isEmpty(version, true)) { 169 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") 170 | + " 对应 version 不能为空!", shutdownWhenServerError); 171 | } 172 | 173 | String url = getString(item, "url"); 174 | int index = url == null ? -1 : url.indexOf("/"); 175 | if (index != 0) { 176 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 177 | + " 对应 url 值错误,必须以 / 开头!", shutdownWhenServerError); 178 | } 179 | 180 | String requestStr = getString(item, "request"); 181 | 182 | String apijson = getString(item, "apijson"); 183 | if (StringUtil.isEmpty(apijson)) { 184 | if (StringUtil.isBranchUrl(url) == false) { 185 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 186 | + " 对应 url 值错误!只允许合法的 URL 格式!", shutdownWhenServerError); 187 | } 188 | } 189 | else { 190 | if (StringUtil.isNotEmpty(requestStr)) { 191 | try { 192 | JSON.parseObject(requestStr); 193 | } 194 | catch (Exception e) { 195 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 196 | + " 对应 request 值 " + requestStr + " 错误!只允许合法的 M 格式!" + e.getMessage(), shutdownWhenServerError); 197 | } 198 | } 199 | 200 | try { 201 | JSON.parseObject(apijson); 202 | } 203 | catch (Exception e) { 204 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 205 | + " 对应 apijson 值 " + apijson + " 错误!只允许合法的 M 格式!" + e.getMessage(), shutdownWhenServerError); 206 | } 207 | 208 | index = url.lastIndexOf("/"); 209 | String method = index < 0 ? null : url.substring(0, index); 210 | String tag = index < 0 ? null : url.substring(index + 1); 211 | 212 | index = method == null ? -1 : method.lastIndexOf("/"); 213 | method = index < 0 ? method : method.substring(index + 1); 214 | 215 | if (METHODS.contains(method) == false) { 216 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 217 | + " 对应路径 /{method}/{tag} 中 method 值 " + method + " 错误!apijson 字段不为空时只允许 " + METHODS + " 中的一个!", shutdownWhenServerError); 218 | } 219 | 220 | if (StringUtil.isName(tag) == false) { 221 | onServerError("服务器内部错误,Document 表中的 id=" + getString(item, "id") + ", name=" + getString(item, "name") + ", url=" + url 222 | + " 对应路径 /{method}/{tag} 中 tag 值 " + tag + " 错误!apijson 字段不为空时只允许变量命名格式!", shutdownWhenServerError); 223 | } 224 | 225 | String cacheKey = getCacheKeyForRequest(method, tag); 226 | SortedMap> versionedMap = newMap.get(cacheKey); 227 | if (versionedMap == null) { 228 | versionedMap = new TreeMap<>(new Comparator() { 229 | 230 | @Override 231 | public int compare(Integer o1, Integer o2) { 232 | return o2 == null ? -1 : o2.compareTo(o1); // 降序 233 | } 234 | }); 235 | } 236 | versionedMap.put(Integer.valueOf(version), item); 237 | newMap.put(cacheKey, versionedMap); 238 | } 239 | 240 | } 241 | 242 | if (isAll) { // 全量更新 243 | DOCUMENT_MAP = newMap; 244 | } 245 | else { 246 | DOCUMENT_MAP.putAll(newMap); 247 | } 248 | 249 | Log.d(TAG, "initDocument for /> DOCUMENT_MAP.size() = " + DOCUMENT_MAP.size() + " >>>>>>>>>>>>>>>>>>>>>>>"); 250 | 251 | return response; 252 | } 253 | 254 | 255 | protected static void onServerError(String msg, boolean shutdown) throws ServerException { 256 | Log.e(TAG, "\n接口映射配置测试未通过!\n请修改 Document 表里的记录!\n保证前端看到的接口映射配置文档是正确的并且能正常使用!!!\n\n原因:\n" + msg); 257 | 258 | if (shutdown) { 259 | System.exit(1); 260 | } else { 261 | throw new ServerException(msg); 262 | } 263 | } 264 | 265 | 266 | } 267 | -------------------------------------------------------------------------------- /src/main/java/apijson/router/javax/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 路由插件,支持类 RESTful API 等传统接口映射到 APIJSON 格式的 HTTP API,兼容 JDK 1.8~16,使用 javax.servlet 3 | */ 4 | /** 5 | * @author Lemon 6 | * 7 | */ 8 | package apijson.router.javax; -------------------------------------------------------------------------------- /src/main/java/apijson/router/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 路由插件,支持类 RESTful API 等传统接口映射到 APIJSON 格式的 HTTP API 3 | */ 4 | /** 5 | * @author Lemon 6 | * 7 | */ 8 | package apijson.router; -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APIJSON/apijson-router/5eb26fc06d87bb2ff8d2a7f454f5dade32da3113/src/main/resources/application.properties --------------------------------------------------------------------------------