├── .gitignore ├── LICENSE ├── README.md ├── grace-bom └── pom.xml ├── grace-core ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── minbox │ └── framework │ └── grace │ └── core │ ├── GraceConstants.java │ ├── GraceRecorderAnnotationDataExtractor.java │ ├── GraceRecorderAopBeanDefinitionRegistrar.java │ ├── GraceRecorderMethodInterceptor.java │ ├── GraceRecorderPointcutAdvisor.java │ ├── GraceVariableContext.java │ ├── operator │ └── GraceLoadOperatorService.java │ └── resolve │ └── GraceRecorderResolveProcessor.java ├── grace-expression ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── minbox │ └── framework │ └── grace │ └── expression │ ├── ClassScanner.java │ ├── ExpressionFunctionFactory.java │ ├── ExpressionFunctionPackageObject.java │ ├── ExpressionVariables.java │ ├── GraceCachedExpressionEvaluator.java │ ├── GraceEvaluationContext.java │ ├── GraceRecordContext.java │ ├── annotation │ ├── GraceFunction.java │ ├── GraceFunctionDefiner.java │ └── GraceRecorder.java │ └── exception │ └── GraceExpressionException.java ├── grace-processor ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── minbox │ └── framework │ └── grace │ └── processor │ ├── GraceLogObject.java │ └── GraceLogStorageProcessor.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | !.mvn/wrapper/maven-wrapper.jar 2 | 3 | ### target ### 4 | target/ 5 | ### test ### 6 | */src/test/java 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | /build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | .flattened-pom.xml 34 | -------------------------------------------------------------------------------- /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 [yyyy] [name of copyright owner] 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 | # grace 2 | Grace`[ɡreɪs]`是一款业务操作日志记录框架,让我们使用更优雅方式来记录有效的、可读性高的操作日志。 3 | ## 快速集成 4 | 5 | `Grace`提供了`grace-bom`依赖,定义了全部依赖的统一版本。 6 | 7 | `Maven`项目在`pom.xml`文件内添加依赖如下所示: 8 | 9 | ```xml 10 | 11 | 12 | org.minbox.framework 13 | grace-expression 14 | 15 | 16 | org.minbox.framework 17 | grace-core 18 | 19 | 20 | org.minbox.framework 21 | grace-processor 22 | 23 | 24 | 25 | 26 | 27 | org.minbox.framework 28 | grace-bom 29 | {lastVersion} 30 | import 31 | pom 32 | 33 | 34 | 35 | ``` 36 | 37 | > `{lastVersion}`为最新的版本号,可到Maven中央仓库查看最新版本 [https://search.maven.org/search?q=grace-bom](https://search.maven.org/search?q=grace-bom) 38 | 39 | 40 | 41 | ## 记录日志 42 | 43 | 记录日志主要是依靠`@GraceRecorder`注解来配置,该注解只能在方法上使用。 44 | 45 | `@GraceRecorder`注解的属性定义如下所示: 46 | 47 | - `success`:目标方法成功执行后所使用的日志模板,支持使用`SpEL`表达式方式配置 48 | - `fail`:目标方法执行失败后所使用的文本 49 | - `condition`:判定是否执行记录操作日志,支持使用`SpEL`表达式方式配置 50 | - `bizNo`:业务编号,支持使用`SpEL`表达式方式配置 51 | - `operator`:操作日志所关联的操作人,支持使用`SpEL`表达式方式配置 52 | - `category`:日志分组,可用于对操作日志进行归类 53 | 54 | > 注意:`SpEL`表达式使用模板定义前后缀的方式,只有在`{}`内的字符串才会被解析。 55 | 56 | ```java 57 | @RestController 58 | public class TestController { 59 | @Autowired 60 | private TestService testService; 61 | 62 | @GetMapping 63 | @GraceRecorder(success = "用户:{#name},编号:{#userId} 访问了首页.", category = "index") 64 | public String index(String name, String userId) { 65 | name = testService.getUserName(userId); 66 | testService.getUserList(userId); 67 | return "Hello, " + name; 68 | } 69 | } 70 | ``` 71 | 72 | 73 | 74 | ## 保存日志 75 | 76 | 操作日志根据`AOP`切面解析完成后会调用`org.minbox.framework.grace.processor.GraceLogStorageProcessor#storage`方法进行后续的数据存储处理, 77 | 78 | 需要实现`GraceLogStorageProcessor`接口来自定义进行日志的存储。 79 | 80 | ```java 81 | @Service 82 | @Slf4j 83 | public class GraceLogStorageProcessorService implements GraceLogStorageProcessor { 84 | @Override 85 | public void storage(GraceLogObject graceLogObject) { 86 | log.info("位置:{},日志内容:{}", graceLogObject.getGeneratedLocation(), graceLogObject.getContent()); 87 | } 88 | } 89 | ``` 90 | 91 | 92 | 93 | ## 配置全局操作人 94 | 95 | 如果项目中使用了认证框架,比如:`SpringSecurity`、`OAuth2`,一般会线程安全的存储登录人的相关信息,如果我们再在`@GraceRecorder`注解内重复配置`operator`就显得太过于繁琐。 96 | 97 | 针对这种情况`Grace`提供了全局配置操作人的接口`GraceLoadOperatorService`,我们只需要实现该接口即可,优先级要低于`@GraceRecorder#operator`。 98 | 99 | ```java 100 | @Service 101 | public class GlobalOperatorService implements GraceLoadOperatorService { 102 | @Override 103 | public String getOperatorName() { 104 | return "恒宇少年"; 105 | } 106 | 107 | @Override 108 | public String getOperatorId() { 109 | return "123"; 110 | } 111 | 112 | @Override 113 | public Map getExtra() { 114 | Map map = new HashMap<>(); 115 | map.put("age", 11); 116 | return map; 117 | } 118 | } 119 | ``` 120 | 121 | > `getExtra`方法的返回值会写入到表达式解析上下文变量集合内,可以用于`SpEL`表达式的解析变量。 122 | 123 | ## 使用参数 124 | 125 | 方法参数是格式化`SpEL`表达式数据的重要来源,可以使用方法的全部参数作为格式化日志的变量。 126 | 127 | **基本类型(byte/short/int/long/String)的使用:** 128 | 129 | ```java 130 | @GraceRecorder(category = "User", success = "用户:{#userId} 密码更新完成,更新后的密码:{#newPassword}.") 131 | public void changePwd(String userId, String newPassword) { 132 | // ... 133 | } 134 | ``` 135 | 136 | **封装类型使用:** 137 | 138 | ```java 139 | @GraceRecorder(category = "User", success = "用户:{#request.userId} 密码更新完成,更新后的密码:{#request.newPassword}.") 140 | public void changePwd(ChangeUserPwdRequest request){ 141 | // ... 142 | } 143 | ``` 144 | 145 | **Map类型使用:** 146 | 147 | 请求参数:`http://127.0.0.1:8080/changePassword?userId=123456&newPassword=111111` 148 | 149 | ```java 150 | @GetMapping("/changePassword") 151 | @GraceRecorder(category = "Test", success = "修改用户:{#map.get('userId')}的密码,改后为:{#map.get('newPassword')}") 152 | public String useMap(@RequestParam HashMap map) { 153 | return "The password changed."; 154 | } 155 | ``` 156 | 157 | > 注意事项:JDK1.8及以前的版本反射时无法获取源码参数的名称,可以通过`#p?`的格式化来获取参数对应值,其中`?`为参数的索引,从0开始。 158 | > 159 | > 如:`#p0.get('userId')`、`#p0.userId` 160 | 161 | ## 自定义变量 162 | 163 | 如果格式化日志所需要的变量不是参数也不是返回值,这时我们需要自定义变量并加入到格式化日志的变量集合内,如下所示: 164 | 165 | ```java 166 | @GraceRecorder(category = "User", success = "用户:{#request.userId} 密码由{#oldPassword}改为{#request.newPassword}") 167 | public void changePassword(ChangeUserPwdRequest request) { 168 | GraceVariableContext.setVariable("oldPassword", "admin123"); 169 | // ... 170 | } 171 | ``` 172 | 173 | `GraceVariableContext`内是一个多线程副本的`HashMap`集合,如果相同Key的变量设置多次会被覆盖使用最后一次设置的值。 174 | 175 | ## 使用Bean定义的函数 176 | 177 | `SpEL`表达式支持通过`@bean`的方式来访问`IOC`容器内注册的Bean实例,也可以直接访问Bean定义的方法,如下所示: 178 | 179 | ```java 180 | @Service 181 | public class UserService { 182 | public String getUserRealName(String userId) { 183 | return "恒宇少年"; 184 | } 185 | } 186 | ------- 187 | @GetMapping("/changePassword") 188 | @GraceRecorder(category = "Test", success = "修改用户:{@userService.getUserRealName(#map.get('userId'))}的密码,改后为:{#map.get('newPassword')}") 189 | public String useMap(@RequestParam HashMap map) { 190 | return "The password changed."; 191 | } 192 | ``` 193 | 194 | 格式:`@ + Bean名称`,如果没有特殊处理使用注解注册到`IOC`容器内的Bean名称首字母都为小写,所以`@userService`就代表了`UserService`类的Bean实例。 195 | 196 | ## 表达式函数 197 | 198 | 表达式函数必须是`static`修饰的方法才可以定义,如果不是`static`在访问时会报错,主要是因为反射调用方法时如果不是静态的需要方法所属类的实例才可以。 199 | 200 | **配置ExpressionFunctionFactory类** 201 | 202 | ```java 203 | @Bean 204 | ExpressionFunctionFactory expressionFunctionFactory() { 205 | // 可以传递多个basePackage 206 | return new ExpressionFunctionFactory(Arrays.asList("org.minbox.framework.grace.sample","com.yuqiyu")); 207 | } 208 | ``` 209 | 210 | > 通过配置实例化`ExpressionFunctionFactory`类来加载指定包名下配置`@GraceFunctionDefiner`注解的类。 211 | 212 | ```java 213 | @GraceFunctionDefiner 214 | public class StringUtils { 215 | @GraceFunction 216 | public static String reverseString(String input) { 217 | StringBuilder backwards = new StringBuilder(); 218 | for (int i = 0; i < input.length(); i++) { 219 | backwards.append(input.charAt(input.length() - 1 - i)); 220 | } 221 | return backwards.toString(); 222 | } 223 | } 224 | ``` 225 | 226 | `@GraceFunctionDefiner`注解只是起到了一个过滤表达式函数定义的作用,只要使用该注解的类才可以执行进一步解析表达式函数的逻辑。 227 | 228 | `@GraceFunction`注解则是标识方法为表达式函数,`ExpressionFunctionFactory`在实例化后会把表达式函数缓存到内存集合中,在解析操作日志的`SpEL`表达式时进行注册使用。 229 | 230 | ## 使用返回值 231 | 232 | 每次执行`@GraceRecorder`配置的方法时,AOP拦截器都会在目标方法执行完成后将结果添加到上下文的变量集合内,使用`result`作为Key,如果我们需要使用返回值的内容来格式化日志可以直接使用`#result`来访问数据。 233 | 234 | ```java 235 | @GraceRecorder(category = "User", success = "用户:{#userId} 查询到的昵称为:{#result}") 236 | public String getUserRealName(String userId) { 237 | return "恒宇少年"; 238 | } 239 | 240 | @GraceRecorder(category = "User", success = "用户:{#userId},年龄:{#result.age}") 241 | public User getUserById(String userId) { 242 | return new User(); 243 | } 244 | 245 | @Data 246 | public static class User { 247 | private String userId; 248 | private String userName; 249 | private int age; 250 | } 251 | ``` 252 | 253 | 254 | 255 | ## 条件判断 256 | 257 | `@GraceRecorder`注解有个`condition`条件属性,支持`SpEL`表达式配置,如果配置了该属性,只有表达式解析结果为`true`时才会记录操作日志。 258 | 259 | ```java 260 | @GraceRecorder(category = "User", condition = "{#age>20 and #age<60}", success = "用户:{@userService.getUserRealName(#userId)},年龄超过{#age}") 261 | public void updateAgeById(String userId, int age) { 262 | System.out.println(age); 263 | } 264 | ``` 265 | -------------------------------------------------------------------------------- /grace-bom/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | grace 7 | org.minbox.framework 8 | ${revision} 9 | 10 | 4.0.0 11 | pom 12 | grace-bom 13 | 14 | 15 | 11 16 | 11 17 | 18 | 19 | 20 | 21 | org.minbox.framework 22 | grace-core 23 | ${revision} 24 | 25 | 26 | org.minbox.framework 27 | grace-expression 28 | ${revision} 29 | 30 | 31 | org.minbox.framework 32 | grace-processor 33 | ${revision} 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /grace-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | grace-bom 7 | org.minbox.framework 8 | ${revision} 9 | ../grace-bom 10 | 11 | 4.0.0 12 | jar 13 | grace-core 14 | 15 | 16 | 11 17 | 11 18 | 19 | 20 | 21 | org.springframework 22 | spring-aop 23 | true 24 | 25 | 26 | org.springframework 27 | spring-context 28 | true 29 | 30 | 31 | org.springframework 32 | spring-beans 33 | true 34 | 35 | 36 | org.minbox.framework 37 | grace-expression 38 | true 39 | 40 | 41 | org.minbox.framework 42 | grace-processor 43 | true 44 | 45 | 46 | org.minbox.framework 47 | minbox-core 48 | 49 | 50 | -------------------------------------------------------------------------------- /grace-core/src/main/java/org/minbox/framework/grace/core/GraceConstants.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.core; 2 | 3 | import org.minbox.framework.grace.expression.ExpressionVariables; 4 | 5 | /** 6 | * 常量定义 7 | * 8 | * @author 恒宇少年 9 | */ 10 | public interface GraceConstants { 11 | /** 12 | * 定义方法返回值在{@link ExpressionVariables}变量集合内的Key 13 | */ 14 | String RESULT_VARIABLE_KEY = "result"; 15 | /** 16 | * 定义方法参数索引值在{@link ExpressionVariables}变量集合的格式 17 | */ 18 | String PARAMETER_INDEX_VALUE_FORMAT = "p%d"; 19 | /** 20 | * 日志产生位置的格式 21 | */ 22 | String LOG_GENERATED_LOCATION_FORMAT = "%s#%s"; 23 | } 24 | -------------------------------------------------------------------------------- /grace-core/src/main/java/org/minbox/framework/grace/core/GraceRecorderAnnotationDataExtractor.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.core; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import org.aopalliance.intercept.MethodInvocation; 7 | import org.minbox.framework.grace.core.operator.GraceLoadOperatorService; 8 | import org.minbox.framework.grace.expression.annotation.GraceRecorder; 9 | import org.springframework.aop.support.AopUtils; 10 | import org.springframework.core.annotation.AnnotationUtils; 11 | import org.springframework.util.Assert; 12 | import org.springframework.util.ClassUtils; 13 | import org.springframework.util.ObjectUtils; 14 | 15 | import java.lang.reflect.Method; 16 | import java.lang.reflect.Parameter; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | import static org.minbox.framework.grace.core.GraceConstants.LOG_GENERATED_LOCATION_FORMAT; 21 | 22 | /** 23 | * {@link GraceRecorder}注解数据提取者 24 | *

25 | * 跟方法切面对象实例{@link MethodInvocation}进行提取解析日志内容所需要的数据 26 | * 27 | * @author 恒宇少年 28 | */ 29 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 30 | public class GraceRecorderAnnotationDataExtractor { 31 | private GraceRecorder graceRecorder; 32 | @Getter 33 | private Class targetClass; 34 | @Getter 35 | private Method specificMethod; 36 | private Object[] arguments; 37 | private Parameter[] parameters; 38 | private GraceLoadOperatorService operatorService; 39 | 40 | public GraceRecorderAnnotationDataExtractor(MethodInvocation invocation, GraceLoadOperatorService operatorService) { 41 | this.graceRecorder = AnnotationUtils.getAnnotation(invocation.getMethod(), GraceRecorder.class); 42 | Assert.notNull(graceRecorder, "@GraceRecorder注解实例不可以为空."); 43 | this.targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); 44 | Assert.notNull(targetClass, "无法获取切面方法所属目标类."); 45 | this.specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass); 46 | this.arguments = invocation.getArguments(); 47 | this.parameters = this.specificMethod.getParameters(); 48 | this.operatorService = operatorService; 49 | } 50 | 51 | /** 52 | * 获取参数定义以及值的映射集合 53 | * 通过{@link MethodInvocation}实例获取切面目标的{@link Method},通过{@link MethodInvocation#getArguments()}获取各个参数的传递值 54 | * 55 | * @return 定义参数的值集合 56 | */ 57 | public Map getParameterValues() { 58 | Map parameterValues = new HashMap<>(); 59 | if (ObjectUtils.isEmpty(parameters)) { 60 | return parameterValues; 61 | } 62 | for (int i = 0; i < parameters.length; i++) { 63 | Parameter parameter = parameters[i]; 64 | Object value = this.arguments[i]; 65 | parameterValues.put(parameter.getName(), value); 66 | String parameterIndexName = String.format(GraceConstants.PARAMETER_INDEX_VALUE_FORMAT, i); 67 | parameterValues.put(parameterIndexName, value); 68 | } 69 | return parameterValues; 70 | } 71 | 72 | public String getSuccessTemplate() { 73 | return this.graceRecorder.success(); 74 | } 75 | 76 | public String getFailText() { 77 | return this.graceRecorder.fail(); 78 | } 79 | 80 | public String getBizNo() { 81 | return this.graceRecorder.bizNo(); 82 | } 83 | 84 | public String getCategory() { 85 | return this.graceRecorder.category(); 86 | } 87 | 88 | public String[] getTags() { 89 | return this.graceRecorder.tags(); 90 | } 91 | 92 | public String getConditionExpression() { 93 | return this.graceRecorder.condition(); 94 | } 95 | 96 | public String getOperator() { 97 | String operator = this.graceRecorder.operator(); 98 | return ObjectUtils.isEmpty(operator) && !ObjectUtils.isEmpty(operatorService) ? operatorService.getOperatorName() 99 | : operator; 100 | } 101 | 102 | public String getOperatorId() { 103 | return !ObjectUtils.isEmpty(this.operatorService) ? this.operatorService.getOperatorId() : null; 104 | } 105 | 106 | public String getGeneratedLocation() { 107 | return String.format(LOG_GENERATED_LOCATION_FORMAT, targetClass.getName(), specificMethod.getName()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /grace-core/src/main/java/org/minbox/framework/grace/core/GraceRecorderAopBeanDefinitionRegistrar.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.core; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.minbox.framework.util.BeanUtils; 5 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 6 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 7 | import org.springframework.core.type.AnnotationMetadata; 8 | 9 | /** 10 | * 配置日志采集注解切面相关类 11 | * 12 | * @author 恒宇少年 13 | * @see GraceRecorderMethodInterceptor 14 | * @see GraceRecorderPointcutAdvisor 15 | */ 16 | @Slf4j 17 | public class GraceRecorderAopBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { 18 | @Override 19 | public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 20 | BeanUtils.registerInfrastructureBeanIfAbsent(registry, GraceRecorderMethodInterceptor.BEAN_NAME, GraceRecorderMethodInterceptor.class); 21 | BeanUtils.registerInfrastructureBeanIfAbsent(registry, GraceRecorderPointcutAdvisor.BEAN_NAME, GraceRecorderPointcutAdvisor.class); 22 | log.info("The registration of the Grace log aspect class is completed."); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /grace-core/src/main/java/org/minbox/framework/grace/core/GraceRecorderMethodInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.core; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.aopalliance.intercept.MethodInterceptor; 5 | import org.aopalliance.intercept.MethodInvocation; 6 | import org.minbox.framework.grace.core.operator.GraceLoadOperatorService; 7 | import org.minbox.framework.grace.core.resolve.GraceRecorderResolveProcessor; 8 | import org.minbox.framework.grace.expression.ExpressionVariables; 9 | import org.minbox.framework.grace.expression.GraceCachedExpressionEvaluator; 10 | import org.minbox.framework.grace.expression.GraceEvaluationContext; 11 | import org.minbox.framework.grace.expression.GraceRecordContext; 12 | import org.minbox.framework.grace.expression.annotation.GraceRecorder; 13 | import org.minbox.framework.grace.processor.GraceLogObject; 14 | import org.minbox.framework.grace.processor.GraceLogStorageProcessor; 15 | import org.minbox.framework.util.StackTraceUtil; 16 | import org.springframework.beans.BeansException; 17 | import org.springframework.beans.factory.BeanFactory; 18 | import org.springframework.beans.factory.BeanFactoryAware; 19 | import org.springframework.beans.factory.ObjectProvider; 20 | import org.springframework.context.expression.AnnotatedElementKey; 21 | import org.springframework.context.expression.BeanFactoryResolver; 22 | import org.springframework.util.Assert; 23 | import org.springframework.util.ObjectUtils; 24 | 25 | import java.util.Map; 26 | 27 | /** 28 | * 日志记录者切点后的方法拦截器 29 | *

30 | * 执行目标方法之前构建表达式函数、变量到上下文{@link GraceEvaluationContext}实例中 31 | * 执行目标方法后进行格式化日志模板内容并处理日志数据 32 | * 33 | * @author 恒宇少年 34 | */ 35 | @Slf4j 36 | public class GraceRecorderMethodInterceptor implements MethodInterceptor, BeanFactoryAware { 37 | /** 38 | * The bean name of the current class registered in the ioc 39 | */ 40 | public static final String BEAN_NAME = GraceRecorderMethodInterceptor.class.getSimpleName(); 41 | private BeanFactoryResolver beanFactoryResolver; 42 | private GraceLogStorageProcessor storageProcessor; 43 | private GraceLoadOperatorService operatorService; 44 | 45 | public GraceRecorderMethodInterceptor(ObjectProvider storageProcessorProvider, 46 | ObjectProvider operatorServiceProvider) { 47 | this.storageProcessor = storageProcessorProvider.getIfAvailable(); 48 | this.operatorService = operatorServiceProvider.getIfAvailable(); 49 | Assert.notNull(storageProcessor, "无法注入GraceLogStorageProcessor接口实现类实例,请实现该接口."); 50 | } 51 | 52 | /** 53 | * 执行目标方法切面业务逻辑处理 54 | *

55 | * 切面前: 56 | * 提取{@link GraceRecorder#bizNo()}、{@link GraceRecorder#category()}、{@link GraceRecorder#operator()}数据 57 | * 提取参数列表并写入到{@link ExpressionVariables}变量集合内 58 | * 目标方法执行: 59 | * 将目标方法返回结果使用"result"变量名写入到{@link ExpressionVariables} 60 | * 切面后: 61 | * 提取{@link GraceRecorder#condition()}配置的条件表达,如果该条件的表达式解析结果为"true"则执行操作日志的解析处理 62 | * 提取{@link GraceRecorder#success()}并解析执行成功后的日志模板 63 | * 如果执行出现异常则提取{@link GraceRecorder#fail()}的文本内容 64 | * 封装{@link GraceLogObject}并交付给"processor"进行后续的数据持久化处理 65 | * 66 | * @param invocation 目标方法 67 | * @return 目标方法执行返回的数据结果 68 | * @throws Throwable 69 | */ 70 | @Override 71 | public Object invoke(MethodInvocation invocation) throws Throwable { 72 | GraceRecorderAnnotationDataExtractor extractor = null; 73 | Map customizeVariables = null; 74 | Object result; 75 | boolean executionSucceed = true; 76 | String exceptionStackTrace = null; 77 | try { 78 | extractor = new GraceRecorderAnnotationDataExtractor(invocation, this.operatorService); 79 | Map parameterValueMap = extractor.getParameterValues(); 80 | ExpressionVariables variables = ExpressionVariables.initialize(); 81 | if (!ObjectUtils.isEmpty(operatorService) && !ObjectUtils.isEmpty(operatorService.getExtra())) { 82 | variables.addVariables(operatorService.getExtra()); 83 | } 84 | variables.addVariables(parameterValueMap); 85 | GraceRecordContext.pushExpressionVariables(variables); 86 | result = invocation.proceed(); 87 | customizeVariables = GraceVariableContext.getCustomizeVariables(); 88 | if (!ObjectUtils.isEmpty(customizeVariables)) { 89 | variables.addVariables(customizeVariables); 90 | } 91 | variables.addVariable(GraceConstants.RESULT_VARIABLE_KEY, result); 92 | } catch (Exception e) { 93 | executionSucceed = false; 94 | exceptionStackTrace = StackTraceUtil.getStackTrace(e); 95 | throw e; 96 | } finally { 97 | // 如果执行目标方法遇到了异常,把遇到异常之前设置的自定义变量读取出来 98 | customizeVariables = ObjectUtils.isEmpty(customizeVariables) ? GraceVariableContext.getCustomizeVariables() : customizeVariables; 99 | GraceVariableContext.remove(); 100 | GraceCachedExpressionEvaluator evaluator = new GraceCachedExpressionEvaluator(); 101 | ExpressionVariables variables = GraceRecordContext.popExpressionVariables(); 102 | GraceEvaluationContext evaluationContext = evaluator.createEvaluationContext(variables); 103 | evaluationContext.setBeanResolver(this.beanFactoryResolver); 104 | AnnotatedElementKey elementKey = new AnnotatedElementKey(extractor.getSpecificMethod(), extractor.getTargetClass()); 105 | boolean conditionExecute = true; 106 | if (!ObjectUtils.isEmpty(extractor.getConditionExpression().trim())) { 107 | conditionExecute = evaluator.parseExpression(Boolean.class, extractor.getConditionExpression(), elementKey, evaluationContext); 108 | } 109 | if (conditionExecute) { 110 | GraceRecorderResolveProcessor resolveProcessor = 111 | new GraceRecorderResolveProcessor(extractor, evaluator, evaluationContext, elementKey, executionSucceed, customizeVariables); 112 | GraceLogObject graceLogObject = resolveProcessor.processing(); 113 | graceLogObject.setExceptionStackTrace(exceptionStackTrace); 114 | storageProcessor.storage(graceLogObject); 115 | } 116 | } 117 | return result; 118 | } 119 | 120 | @Override 121 | public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 122 | if (!ObjectUtils.isEmpty(beanFactory) && ObjectUtils.isEmpty(this.beanFactoryResolver)) { 123 | this.beanFactoryResolver = new BeanFactoryResolver(beanFactory); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /grace-core/src/main/java/org/minbox/framework/grace/core/GraceRecorderPointcutAdvisor.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.core; 2 | 3 | import org.aopalliance.aop.Advice; 4 | import org.minbox.framework.grace.expression.annotation.GraceRecorder; 5 | import org.springframework.aop.Pointcut; 6 | import org.springframework.aop.support.AbstractPointcutAdvisor; 7 | import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; 8 | import org.springframework.beans.BeansException; 9 | import org.springframework.beans.factory.BeanFactory; 10 | import org.springframework.beans.factory.BeanFactoryAware; 11 | import org.springframework.core.Ordered; 12 | import org.springframework.util.Assert; 13 | 14 | /** 15 | * 配置日志采集者支持{@link GraceRecorder}注解的AOP切入点 16 | * 17 | * @author 恒宇少年 18 | */ 19 | public class GraceRecorderPointcutAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware { 20 | /** 21 | * The bean name of the current class registered in the ioc 22 | */ 23 | public static final String BEAN_NAME = GraceRecorderPointcutAdvisor.class.getSimpleName(); 24 | private BeanFactory beanFactory; 25 | private Advice advice; 26 | private Pointcut pointcut; 27 | 28 | /** 29 | * 构建删除初始化全局数据 30 | *

31 | * {@link Pointcut}注解切入点配置使用类上方法的注解,只生效方法注解 32 | * 33 | * @param methodInterceptor {@link GraceRecorder}注解方法拦截器 34 | */ 35 | public GraceRecorderPointcutAdvisor(GraceRecorderMethodInterceptor methodInterceptor) { 36 | this.pointcut = AnnotationMatchingPointcut.forMethodAnnotation(GraceRecorder.class); 37 | this.advice = methodInterceptor; 38 | if (this.advice instanceof BeanFactoryAware) { 39 | ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory); 40 | } 41 | } 42 | 43 | @Override 44 | public Pointcut getPointcut() { 45 | return this.pointcut; 46 | } 47 | 48 | @Override 49 | public Advice getAdvice() { 50 | Assert.notNull(this.advice, "The implementation instance of advice cannot be empty."); 51 | return this.advice; 52 | } 53 | 54 | @Override 55 | public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 56 | this.beanFactory = beanFactory; 57 | } 58 | 59 | @Override 60 | public int getOrder() { 61 | return Ordered.LOWEST_PRECEDENCE; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /grace-core/src/main/java/org/minbox/framework/grace/core/GraceVariableContext.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.core; 2 | 3 | import org.springframework.util.CollectionUtils; 4 | import org.springframework.util.ObjectUtils; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * 操作日志变量上下文 10 | * 11 | * @author 恒宇少年 12 | */ 13 | public class GraceVariableContext { 14 | private static final ThreadLocal> CUSTOMIZE_VARIABLES = new ThreadLocal(); 15 | private static final int VARIABLE_MAP_EXPECTED_SIZE = 1; 16 | 17 | public static void setVariable(String key, Object value) { 18 | boolean isFirstSet = ObjectUtils.isEmpty(CUSTOMIZE_VARIABLES.get()); 19 | Map variables = isFirstSet ? CollectionUtils.newHashMap(VARIABLE_MAP_EXPECTED_SIZE) : CUSTOMIZE_VARIABLES.get(); 20 | variables.put(key, value); 21 | if (isFirstSet) { 22 | CUSTOMIZE_VARIABLES.set(variables); 23 | } 24 | } 25 | 26 | public static void setVariable(Enum key, Object value) { 27 | setVariable(key.name(), value); 28 | } 29 | 30 | public static void putVariables(Map variables) { 31 | if (!ObjectUtils.isEmpty(variables)) { 32 | variables.keySet().stream().forEach(key -> setVariable(key, variables.get(key))); 33 | } 34 | } 35 | 36 | static Map getCustomizeVariables() { 37 | return CUSTOMIZE_VARIABLES.get(); 38 | } 39 | 40 | static void remove() { 41 | if (!ObjectUtils.isEmpty(CUSTOMIZE_VARIABLES.get())) { 42 | CUSTOMIZE_VARIABLES.remove(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /grace-core/src/main/java/org/minbox/framework/grace/core/operator/GraceLoadOperatorService.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.core.operator; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 加载操作人接口定义 7 | * 8 | * @author 恒宇少年 9 | */ 10 | public interface GraceLoadOperatorService { 11 | /** 12 | * 配置加载操作人的名称 13 | * 14 | * @return 操作人名称 15 | */ 16 | String getOperatorName(); 17 | 18 | /** 19 | * 配置加载操作人的编号 20 | * 21 | * @return 操作人编号 22 | */ 23 | String getOperatorId(); 24 | 25 | /** 26 | * 配置扩展的数据集合 27 | *

28 | * 该方法的返回值会写入到SpEL表达式解析的变量集合中 29 | * 30 | * @return 扩展数据列表 31 | * @see org.minbox.framework.grace.expression.ExpressionVariables 32 | */ 33 | default Map getExtra() { 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /grace-core/src/main/java/org/minbox/framework/grace/core/resolve/GraceRecorderResolveProcessor.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.core.resolve; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.minbox.framework.grace.core.GraceRecorderAnnotationDataExtractor; 7 | import org.minbox.framework.grace.expression.GraceCachedExpressionEvaluator; 8 | import org.minbox.framework.grace.expression.GraceEvaluationContext; 9 | import org.minbox.framework.grace.expression.annotation.GraceRecorder; 10 | import org.minbox.framework.grace.processor.GraceLogObject; 11 | import org.springframework.context.expression.AnnotatedElementKey; 12 | import org.springframework.util.ObjectUtils; 13 | 14 | import java.util.Map; 15 | 16 | /** 17 | * 操作日志内容解析处理类 18 | *

19 | * 解析{@link GraceRecorder}操作日志注解所提供的配置内容,封装成{@link GraceLogObject}日志对象实例交付给后续"processor" 20 | * 21 | * @author 恒宇少年 22 | */ 23 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 24 | @Slf4j 25 | public class GraceRecorderResolveProcessor { 26 | private GraceLogObject graceLogObject; 27 | private GraceRecorderAnnotationDataExtractor extractor; 28 | private GraceCachedExpressionEvaluator evaluator; 29 | private GraceEvaluationContext evaluationContext; 30 | private AnnotatedElementKey elementKey; 31 | private boolean executionSucceed; 32 | private Map customizeVariables; 33 | 34 | public GraceRecorderResolveProcessor(GraceRecorderAnnotationDataExtractor extractor, GraceCachedExpressionEvaluator evaluator, 35 | GraceEvaluationContext evaluationContext, AnnotatedElementKey elementKey, 36 | boolean executionSucceed, Map customizeVariables) { 37 | this.extractor = extractor; 38 | this.graceLogObject = GraceLogObject.initialize(); 39 | this.graceLogObject.setExecutionSucceed(executionSucceed); 40 | this.evaluator = evaluator; 41 | this.evaluationContext = evaluationContext; 42 | this.elementKey = elementKey; 43 | this.executionSucceed = executionSucceed; 44 | this.customizeVariables = customizeVariables; 45 | } 46 | 47 | public GraceLogObject processing() { 48 | if (!ObjectUtils.isEmpty(extractor.getSuccessTemplate())) { 49 | this.graceLogObject.setContent(this.executionSucceed ? 50 | evaluator.parseExpression(String.class, extractor.getSuccessTemplate(), elementKey, evaluationContext) : 51 | extractor.getFailText()); 52 | } 53 | if (!ObjectUtils.isEmpty(extractor.getBizNo())) { 54 | String parsedBizNo = evaluator.parseExpression(String.class, extractor.getBizNo(), elementKey, evaluationContext); 55 | this.graceLogObject.setBizNo(parsedBizNo); 56 | } 57 | if (!ObjectUtils.isEmpty(extractor.getOperator())) { 58 | String parsedOperator = evaluator.parseExpression(String.class, extractor.getOperator(), elementKey, evaluationContext); 59 | this.graceLogObject.setOperator(parsedOperator); 60 | } 61 | if (!ObjectUtils.isEmpty(extractor.getTags())) { 62 | this.graceLogObject.setTags(extractor.getTags()); 63 | } 64 | this.graceLogObject.setCategory(extractor.getCategory()) 65 | .setGeneratedLocation(extractor.getGeneratedLocation()) 66 | .setOperatorId(extractor.getOperatorId()) 67 | .setCustomizeVariables(this.customizeVariables); 68 | return this.graceLogObject; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /grace-expression/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | grace-bom 7 | org.minbox.framework 8 | ${revision} 9 | ../grace-bom 10 | 11 | 4.0.0 12 | jar 13 | grace-expression 14 | 15 | 16 | 11 17 | 11 18 | 19 | 20 | 21 | org.springframework 22 | spring-expression 23 | 24 | 25 | org.springframework 26 | spring-context 27 | true 28 | 29 | 30 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/ClassScanner.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.JarURLConnection; 6 | import java.net.URL; 7 | import java.net.URLDecoder; 8 | import java.util.Enumeration; 9 | import java.util.LinkedHashSet; 10 | import java.util.Set; 11 | import java.util.function.Predicate; 12 | import java.util.jar.JarEntry; 13 | import java.util.jar.JarFile; 14 | 15 | /** 16 | * 类扫描器 17 | *

18 | * 根据指定的跟目录进行扫描全部的类 19 | * 支持使用递归扫描子包内的类、支持从Jar包中递归扫描类 20 | * 21 | * @author 恒宇少年 22 | */ 23 | public class ClassScanner { 24 | private String basePackage; 25 | private boolean recursive; 26 | private Predicate packagePredicate; 27 | private Predicate classPredicate; 28 | 29 | public ClassScanner(String basePackage, boolean recursive) { 30 | this.basePackage = basePackage; 31 | this.recursive = recursive; 32 | } 33 | 34 | public ClassScanner(String basePackage, boolean recursive, Predicate packagePredicate, Predicate classPredicate) { 35 | this.basePackage = basePackage; 36 | this.recursive = recursive; 37 | this.packagePredicate = packagePredicate; 38 | this.classPredicate = classPredicate; 39 | } 40 | 41 | public Set> doScanning() throws IOException, ClassNotFoundException { 42 | Set> classes = new LinkedHashSet(); 43 | String packageName = basePackage; 44 | if (packageName.endsWith(".")) { 45 | packageName = packageName.substring(0, packageName.lastIndexOf('.')); 46 | } 47 | String basePackageFilePath = packageName.replace('.', '/'); 48 | Enumeration resources = Thread.currentThread().getContextClassLoader().getResources(basePackageFilePath); 49 | while (resources.hasMoreElements()) { 50 | URL resource = resources.nextElement(); 51 | String protocol = resource.getProtocol(); 52 | if (Protocol.file.toString().equals(protocol)) { 53 | String filePath = URLDecoder.decode(resource.getFile(), "UTF-8"); 54 | doScanPackageClassesByFile(classes, packageName, filePath); 55 | } else if (Protocol.jar.toString().equals(protocol)) { 56 | doScanPackageClassesByJar(packageName, resource, classes); 57 | } 58 | } 59 | 60 | return classes; 61 | } 62 | 63 | private void doScanPackageClassesByJar(String basePackage, URL url, Set> classes) throws IOException, ClassNotFoundException { 64 | String packageName = basePackage; 65 | String basePackageFilePath = packageName.replace('.', '/'); 66 | JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile(); 67 | Enumeration entries = jar.entries(); 68 | while (entries.hasMoreElements()) { 69 | JarEntry entry = entries.nextElement(); 70 | String name = entry.getName(); 71 | if (!name.startsWith(basePackageFilePath) || entry.isDirectory()) { 72 | continue; 73 | } 74 | if (!recursive && name.lastIndexOf('/') != basePackageFilePath.length()) { 75 | continue; 76 | } 77 | if (packagePredicate != null) { 78 | String jarPackageName = name.substring(0, name.lastIndexOf('/')).replace("/", "."); 79 | if (!packagePredicate.test(jarPackageName)) { 80 | continue; 81 | } 82 | } 83 | String className = name.replace('/', '.'); 84 | className = className.substring(0, className.length() - 6); 85 | Class loadClass = Thread.currentThread().getContextClassLoader().loadClass(className); 86 | if (classPredicate == null || classPredicate.test(loadClass)) { 87 | classes.add(loadClass); 88 | } 89 | } 90 | } 91 | 92 | private void doScanPackageClassesByFile(Set> classes, String packageName, String packagePath) throws ClassNotFoundException { 93 | File dir = new File(packagePath); 94 | if (!dir.exists() || !dir.isDirectory()) { 95 | return; 96 | } 97 | File[] dirFiles = dir.listFiles(file -> { 98 | String filename = file.getName(); 99 | if (file.isDirectory()) { 100 | if (!recursive) { 101 | return false; 102 | } 103 | if (packagePredicate != null) { 104 | return packagePredicate.test(packageName + "." + filename); 105 | } 106 | return true; 107 | } 108 | return filename.endsWith(".class"); 109 | }); 110 | if (null == dirFiles) { 111 | return; 112 | } 113 | for (File file : dirFiles) { 114 | if (file.isDirectory()) { 115 | doScanPackageClassesByFile(classes, packageName + "." + file.getName(), file.getAbsolutePath()); 116 | } else { 117 | String className = file.getName().substring(0, file.getName().length() - 6); 118 | Class loadClass = Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className); 119 | if (classPredicate == null || classPredicate.test(loadClass)) { 120 | classes.add(loadClass); 121 | } 122 | } 123 | } 124 | } 125 | 126 | enum Protocol { 127 | file, jar 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/ExpressionFunctionFactory.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.minbox.framework.grace.expression.annotation.GraceFunction; 7 | import org.minbox.framework.grace.expression.annotation.GraceFunctionDefiner; 8 | import org.springframework.beans.factory.InitializingBean; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.core.annotation.AnnotationUtils; 11 | import org.springframework.core.annotation.Order; 12 | import org.springframework.util.Assert; 13 | import org.springframework.util.CollectionUtils; 14 | import org.springframework.util.ObjectUtils; 15 | import org.springframework.util.ReflectionUtils; 16 | 17 | import java.lang.reflect.Method; 18 | import java.util.*; 19 | import java.util.stream.Collectors; 20 | 21 | /** 22 | * 表达式函数工厂类 23 | *

24 | * 会自动扫描并加载项目中使用{@link GraceFunction}注解描述的函数,缓存到该工厂类的全局集合中 25 | * 26 | * @author 恒宇少年 27 | */ 28 | @Order 29 | @Slf4j 30 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 31 | public class ExpressionFunctionFactory implements InitializingBean { 32 | private static final Map CACHED_METHOD_MAP = new HashMap(); 33 | private static final String METHOD_FULL_NAME_FORMAT = "%s#%s"; 34 | private List functionScanPackages; 35 | 36 | public ExpressionFunctionFactory(List functionScanPackages) { 37 | this.functionScanPackages = functionScanPackages; 38 | Assert.notNull(functionScanPackages, "表达式函数扫描包参数不可以为空."); 39 | } 40 | 41 | /** 42 | * 加载项目中使用{@link GraceFunction}定义的函数并进行缓存 43 | *

44 | * 由于从{@link ApplicationContext}应用上下文中加载的类名比较多, 45 | * 所以需要判定类上是否定义了{@link GraceFunctionDefiner},存在该注解再进行表达式函数的解析 46 | */ 47 | private void loadDefineFunction() { 48 | List> filteredBeanClassList = this.getFunctionDefinerClass(); 49 | filteredBeanClassList.stream().forEach(beanClass -> { 50 | Method[] methods = ReflectionUtils.getAllDeclaredMethods(beanClass); 51 | for (Method method : methods) { 52 | try { 53 | if (method.isAnnotationPresent(GraceFunction.class)) { 54 | GraceFunction function = AnnotationUtils.getAnnotation(method, GraceFunction.class); 55 | ExpressionFunctionPackageObject packageObject = 56 | ExpressionFunctionPackageObject.pack(method.getName(), method, function.isBeforeExecute()); 57 | CACHED_METHOD_MAP.put(method.getName(), packageObject); 58 | String methodFullName = String.format(METHOD_FULL_NAME_FORMAT, method.getDeclaringClass().getName(), method.getName()); 59 | log.debug("Grace表达式函数:{},加载完成并已被缓存.", methodFullName); 60 | } 61 | } catch (Exception e) { 62 | log.error("加载Grace表达式函数异常.", e); 63 | } 64 | } 65 | }); 66 | } 67 | 68 | /** 69 | * 获取定义{@link GraceFunctionDefiner}注解的类列表 70 | *

71 | * 根据配置的多个根包名进行扫描符合条件的类 72 | * 73 | * @return 类列表 74 | */ 75 | private List> getFunctionDefinerClass() { 76 | Set> classSet = new HashSet<>(); 77 | functionScanPackages.stream().forEach(scanPackage -> { 78 | try { 79 | ClassScanner scanner = new ClassScanner(scanPackage, true); 80 | classSet.addAll(scanner.doScanning()); 81 | } catch (Exception e) { 82 | log.error(e.getMessage(), e); 83 | } 84 | }); 85 | return classSet.stream() 86 | .filter(beanClass -> !ObjectUtils.isEmpty(AnnotationUtils.findAnnotation(beanClass, GraceFunctionDefiner.class))) 87 | .collect(Collectors.toList()); 88 | } 89 | 90 | /** 91 | * 获取全部缓存的方法列表 92 | * 93 | * @return 缓存方法集合的副本,不允许修改缓存数据 94 | */ 95 | static Map getAllCachedMethod() { 96 | Map tempMethodMap = CollectionUtils.newHashMap(CACHED_METHOD_MAP.size()); 97 | tempMethodMap.putAll(CACHED_METHOD_MAP); 98 | return tempMethodMap; 99 | } 100 | 101 | @Override 102 | public void afterPropertiesSet() throws Exception { 103 | this.loadDefineFunction(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/ExpressionFunctionPackageObject.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.Accessors; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | /** 12 | * 表达式函数封装对象 13 | * 14 | * @author 恒宇少年 15 | */ 16 | @Getter 17 | @Accessors(chain = true) 18 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 19 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 20 | public class ExpressionFunctionPackageObject { 21 | /** 22 | * 函数名称 23 | */ 24 | private String functionName; 25 | /** 26 | * 函数对应绑定的{@link Method}实例 27 | */ 28 | private Method method; 29 | /** 30 | * 是否在业务方法之前执行 31 | */ 32 | private boolean isBeforeExecute; 33 | 34 | static ExpressionFunctionPackageObject pack(String functionName, Method method, boolean isBeforeExecute) { 35 | return new ExpressionFunctionPackageObject(functionName, method, isBeforeExecute); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/ExpressionVariables.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import org.springframework.util.CollectionUtils; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * 表达式变量封装实体 12 | *

13 | * 存储解析表达式所需要的变量列表 14 | * 15 | * @author 恒宇少年 16 | */ 17 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 18 | public class ExpressionVariables { 19 | /** 20 | * 线程安全的变量集合 21 | */ 22 | private final Map VARIABLE_MAP = new HashMap<>(); 23 | 24 | /** 25 | * 添加变量到集合 26 | * 27 | * @param key 变量Key 28 | * @param value 变量值 29 | */ 30 | public void addVariable(String key, Object value) { 31 | VARIABLE_MAP.put(key, value); 32 | } 33 | 34 | /** 35 | * 添加多个变量到集合 36 | * 37 | * @param variables 多个待添加的变量 38 | */ 39 | public void addVariables(Map variables) { 40 | VARIABLE_MAP.putAll(variables); 41 | } 42 | 43 | /** 44 | * 获取全部的变量 45 | * 46 | * @return {@link #VARIABLE_MAP} 47 | */ 48 | public Map getAllVariables() { 49 | Map tempVariables = CollectionUtils.newHashMap(VARIABLE_MAP.size()); 50 | tempVariables.putAll(VARIABLE_MAP); 51 | return tempVariables; 52 | } 53 | 54 | /** 55 | * 获取指定Key的值 56 | * 57 | * @param key 变量Key 58 | * @return 变量值 59 | */ 60 | public Object getVariable(String key) { 61 | return VARIABLE_MAP.get(key); 62 | } 63 | 64 | /** 65 | * 提供一个空的{@link ExpressionVariables}变量集合对象 66 | * 67 | * @return {@link ExpressionVariables} 68 | */ 69 | public static ExpressionVariables initialize() { 70 | return new ExpressionVariables(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/GraceCachedExpressionEvaluator.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression; 2 | 3 | import org.springframework.context.expression.AnnotatedElementKey; 4 | import org.springframework.context.expression.CachedExpressionEvaluator; 5 | import org.springframework.expression.Expression; 6 | import org.springframework.expression.common.TemplateParserContext; 7 | 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * 缓存解析所使用的表达式实例 13 | * 14 | * @author 恒宇少年 15 | */ 16 | public class GraceCachedExpressionEvaluator extends CachedExpressionEvaluator { 17 | private static final Map EXPRESSION_MAP = new ConcurrentHashMap(); 18 | /** 19 | * 表达式前缀字符串 20 | */ 21 | private static final String EXPRESSION_PREFIX = "{"; 22 | /** 23 | * 表达式后缀字符串 24 | *

25 | * SpEL仅解析前缀后缀字符串内的内容 26 | */ 27 | private static final String EXPRESSION_SUFFIX = "}"; 28 | 29 | /** 30 | * 创建解析表达式上下文实例 31 | * 32 | * @param variables 解析表达式变量集合{@link ExpressionVariables} 33 | * @return {@link GraceEvaluationContext} 34 | */ 35 | public GraceEvaluationContext createEvaluationContext(ExpressionVariables variables) { 36 | return new GraceEvaluationContext(variables); 37 | } 38 | 39 | /** 40 | * 解析SpEL表达式模板 41 | *

42 | * 由于expression的内容并非全部解析,所以需要重写该方法使用模板解析的方式来配置仅解析"{}"内的内容 43 | * 44 | * @param expression 表达式模板 45 | * @return 解析后的 {@link Expression}实例 46 | */ 47 | @Override 48 | protected Expression parseExpression(String expression) { 49 | return getParser().parseExpression(expression, new TemplateParserContext(EXPRESSION_PREFIX, EXPRESSION_SUFFIX)); 50 | } 51 | 52 | /** 53 | * 解析表达式 54 | *

55 | * 将{@link ExpressionFunctionFactory}缓存的表达式函数列表注册到解析上下文中{@link GraceEvaluationContext} 56 | * 57 | * @param conditionExpression 等待解析的表达式 58 | * @param context 表达式解析时使用的上下文 59 | * @return 解析后的字符串 60 | */ 61 | public T parseExpression(Class resultType, String conditionExpression, AnnotatedElementKey elementKey, GraceEvaluationContext context) { 62 | Map functionPackageObjectMap = ExpressionFunctionFactory.getAllCachedMethod(); 63 | functionPackageObjectMap.keySet() 64 | .stream().forEach(functionName -> { 65 | ExpressionFunctionPackageObject functionPackageObject = functionPackageObjectMap.get(functionName); 66 | context.registerFunction(functionPackageObject.getFunctionName(), functionPackageObject.getMethod()); 67 | }); 68 | Expression expression = getExpression(EXPRESSION_MAP, elementKey, conditionExpression); 69 | return expression.getValue(context, resultType); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/GraceEvaluationContext.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression; 2 | 3 | import org.springframework.expression.spel.support.StandardEvaluationContext; 4 | 5 | /** 6 | * 用于解析SpEL表达式的上下文实例 7 | * 8 | * @author 恒宇少年 9 | */ 10 | public class GraceEvaluationContext extends StandardEvaluationContext { 11 | /** 12 | * 实例化{@link GraceEvaluationContext} 13 | * 14 | * @param variables 表达式解析所需要的变量 15 | * @see GraceRecordContext 16 | */ 17 | GraceEvaluationContext(ExpressionVariables variables) { 18 | this.setVariables(variables.getAllVariables()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/GraceRecordContext.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import org.minbox.framework.grace.expression.exception.GraceExpressionException; 6 | import org.springframework.util.ObjectUtils; 7 | 8 | import java.util.Stack; 9 | 10 | /** 11 | * 日志采集记录上下文实例 12 | * 13 | * @author 恒宇少年 14 | */ 15 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 16 | public class GraceRecordContext { 17 | /** 18 | * {@link ExpressionVariables}表达式变量栈集合 19 | *

20 | * 使用{@link Stack}来隔离一个线程操作内存在多个方法需要记录操作日志的变量 21 | */ 22 | private static final InheritableThreadLocal> VARIABLES_STACK_MAP = new InheritableThreadLocal(); 23 | 24 | /** 25 | * 向变量栈集合添加一个新的{@link ExpressionVariables}实例 26 | * 27 | * @param variables 表达式变量集合实例 28 | */ 29 | public static void pushExpressionVariables(ExpressionVariables variables) { 30 | boolean isFirstPush = ObjectUtils.isEmpty(VARIABLES_STACK_MAP.get()); 31 | Stack stack = isFirstPush ? new Stack() : VARIABLES_STACK_MAP.get(); 32 | stack.push(variables); 33 | if (isFirstPush) { 34 | VARIABLES_STACK_MAP.set(stack); 35 | } 36 | } 37 | 38 | /** 39 | * 从变量栈集合获取并移除一个{@link ExpressionVariables}实例 40 | * 41 | * @return {@link ExpressionVariables} 表达式变量实例 42 | */ 43 | public static ExpressionVariables popExpressionVariables() { 44 | Stack stack = VARIABLES_STACK_MAP.get(); 45 | if (ObjectUtils.isEmpty(stack)) { 46 | throw new GraceExpressionException("当前线程中并未获取到表达式变量栈集合."); 47 | } 48 | ExpressionVariables variables = stack.pop(); 49 | if (stack.empty()) { 50 | VARIABLES_STACK_MAP.remove(); 51 | } 52 | return variables; 53 | } 54 | 55 | public static boolean isEmpty() { 56 | Stack stack = VARIABLES_STACK_MAP.get(); 57 | return ObjectUtils.isEmpty(stack) || stack.empty(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/annotation/GraceFunction.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression.annotation; 2 | 3 | import org.minbox.framework.grace.expression.ExpressionFunctionFactory; 4 | import org.springframework.expression.spel.support.StandardEvaluationContext; 5 | 6 | import java.lang.annotation.*; 7 | 8 | /** 9 | * 表达式函数注解 10 | *

11 | * 使用该注解标注的函数会在项目启动时自动加载到{@link ExpressionFunctionFactory}进行全局缓存 12 | * 在操作日志的AOP执行目标函数之前将缓存的方法列表注册到{@link StandardEvaluationContext}SpEL表达式上下文实例中 13 | * 14 | * @author 恒宇少年 15 | */ 16 | @Target(ElementType.METHOD) 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Documented 19 | public @interface GraceFunction { 20 | /** 21 | * 是否在目标函数之前执行 22 | * 23 | * @return 默认返回false 24 | */ 25 | boolean isBeforeExecute() default false; 26 | } 27 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/annotation/GraceFunctionDefiner.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression.annotation; 2 | 3 | import org.minbox.framework.grace.expression.ExpressionFunctionFactory; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * 标注扫描类的注解 9 | *

10 | * {@link ExpressionFunctionFactory}在扫描类加载表达式函数时会根据该注解进行过滤, 11 | * 如果类上标注该注解则进行方法定义注解{@link GraceFunction}解析 12 | * 13 | * @author 恒宇少年 14 | */ 15 | @Target(ElementType.TYPE) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Documented 18 | public @interface GraceFunctionDefiner { 19 | //... 20 | } 21 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/annotation/GraceRecorder.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 日志采集注解 7 | * 8 | * @author 恒宇少年 9 | * @see SpEL 10 | */ 11 | @Target(ElementType.METHOD) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Documented 14 | public @interface GraceRecorder { 15 | /** 16 | * 业务处理成功后的操作日志模板 17 | * 18 | * @return 支持SpEL表达式的模板内容 19 | */ 20 | String success(); 21 | 22 | /** 23 | * 业务处理失败后的操作日志文本内容 24 | * 25 | * @return 操作日志失败的文本版本 26 | */ 27 | String fail() default ""; 28 | 29 | /** 30 | * 判定是否采集操作日志的条件 31 | * 32 | * @return 只是SpEL表达式方式配置,如果表达式解析后返回"true"则执行操作日志采集 33 | */ 34 | String condition() default ""; 35 | 36 | /** 37 | * 操作日志绑定的业务对象编号 38 | * 39 | * @return 操作业务对象的唯一编号,支持使用SpEL表达式提取数据 40 | */ 41 | String bizNo() default ""; 42 | 43 | /** 44 | * 操作日志的执行人 45 | * 46 | * @return 支持使用SpEL表达式提取数据,可以全局配置,也支持配置使用全局变量的方式从上下文中提取数据 47 | */ 48 | String operator() default ""; 49 | 50 | /** 51 | * 操作日志类别 52 | * 53 | * @return 用于对操作进行自定义分组,可以根据分组来处理不同的业务逻辑 54 | */ 55 | String category(); 56 | 57 | /** 58 | * 操作日志标签 59 | * 60 | * @return 用于对操作日志进行标签归类 61 | */ 62 | String[] tags() default ""; 63 | } 64 | -------------------------------------------------------------------------------- /grace-expression/src/main/java/org/minbox/framework/grace/expression/exception/GraceExpressionException.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.expression.exception; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | /** 7 | * 表达式运行时异常 8 | * 9 | * @author 恒宇少年 10 | */ 11 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class GraceExpressionException extends RuntimeException { 13 | public GraceExpressionException(String message) { 14 | super(message); 15 | } 16 | 17 | public GraceExpressionException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /grace-processor/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | grace-bom 7 | org.minbox.framework 8 | ${revision} 9 | ../grace-bom 10 | 11 | 4.0.0 12 | jar 13 | grace-processor 14 | 15 | 16 | 11 17 | 11 18 | 19 | 20 | -------------------------------------------------------------------------------- /grace-processor/src/main/java/org/minbox/framework/grace/processor/GraceLogObject.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.processor; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Map; 8 | 9 | /** 10 | * 日志对象类 11 | * 12 | * @author 恒宇少年 13 | */ 14 | @Data 15 | @Accessors(chain = true) 16 | public class GraceLogObject { 17 | /** 18 | * 日志所属分组 19 | */ 20 | private String category; 21 | /** 22 | * 日志标签列表 23 | */ 24 | private String[] tags; 25 | /** 26 | * 解析后的日志内容 27 | */ 28 | private String content; 29 | /** 30 | * 目标方法是否执行成功 31 | */ 32 | private boolean executionSucceed; 33 | /** 34 | * 日志所关联的操作人名称 35 | */ 36 | private String operator; 37 | /** 38 | * 日志所关联的操作人编号 39 | */ 40 | private String operatorId; 41 | /** 42 | * 日志所关联的业务编号 43 | */ 44 | private String bizNo; 45 | /** 46 | * 日志生成的位置 47 | * 格式为:"全限定类名#方法名" 48 | */ 49 | private String generatedLocation; 50 | /** 51 | * 日志生成的时间 52 | */ 53 | private LocalDateTime time = LocalDateTime.now(); 54 | /** 55 | * 执行目标操作方法遇到异常时的堆栈信息 56 | */ 57 | private String exceptionStackTrace; 58 | /** 59 | * 自定义的变量集合 60 | *

61 | * 可用于扩展操作日志存储时所需要的数据 62 | */ 63 | private Map customizeVariables; 64 | 65 | public static GraceLogObject initialize() { 66 | return new GraceLogObject(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /grace-processor/src/main/java/org/minbox/framework/grace/processor/GraceLogStorageProcessor.java: -------------------------------------------------------------------------------- 1 | package org.minbox.framework.grace.processor; 2 | 3 | /** 4 | * 操作日志持久化处理接口定义 5 | * 6 | * @author 恒宇少年 7 | */ 8 | public interface GraceLogStorageProcessor { 9 | /** 10 | * 持久化日志封装对象 11 | * 12 | * @param graceLogObject {@link GraceLogObject} 日志封装对象 13 | */ 14 | void storage(GraceLogObject graceLogObject); 15 | } 16 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.minbox.framework 8 | minbox-parent 9 | 1.1.0 10 | 11 | 12 | org.minbox.framework 13 | grace 14 | pom 15 | ${revision} 16 | 17 | grace-bom 18 | grace-core 19 | grace-processor 20 | grace-expression 21 | 22 | 23 | 24 | 1.0.2 25 | 11 26 | 11 27 | true 28 | 2.7 29 | 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | compile 35 | true 36 | 37 | 38 | org.slf4j 39 | slf4j-api 40 | true 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-source-plugin 48 | ${maven.source.plugin.version} 49 | 50 | 51 | package 52 | 53 | jar-no-fork 54 | 55 | 56 | 57 | 58 | 59 | org.codehaus.mojo 60 | cobertura-maven-plugin 61 | ${cobertura-maven-plugin.version} 62 | 63 | 64 | html 65 | xml 66 | 67 | 68 | 69 | 70 | 71 | 72 | --------------------------------------------------------------------------------