├── .github └── workflows │ └── main.yml ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── Image ├── common.png ├── ding0.png ├── ding1.png ├── ding2.png ├── ding3.png ├── ding4.png ├── ding5.png ├── ding6.png ├── ding7.png ├── ding8-1.png └── web.png ├── LICENSE ├── README.md ├── _config.yml ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── marvin │ │ ├── common │ │ ├── constant │ │ │ └── ExceptionConstant.java │ │ ├── enumeration │ │ │ ├── ExceptionType.java │ │ │ ├── ListenType.java │ │ │ └── ProjectEnvironment.java │ │ └── exception │ │ │ ├── CalmaRuntimeException.java │ │ │ ├── IGException.java │ │ │ ├── NoSuchHttpRequestMethodException.java │ │ │ └── internal │ │ │ └── TypeConvertException.java │ │ ├── config │ │ ├── CommonNoticeContextConfiguration.java │ │ ├── DingComponentConfiguration.java │ │ ├── DingDingClientConfiguration.java │ │ ├── ListenerConfiguration.java │ │ ├── MarkdownExceptionNoticeSendConfiguration.java │ │ ├── SmsClientConfiguration.java │ │ ├── SmsComponentConfiguration.java │ │ ├── TextExceptionNoticeSendConfiguration.java │ │ ├── WebNoticeContextConfiguration.java │ │ ├── anno │ │ │ ├── CalmaExceptionListener.java │ │ │ ├── ConditionOnCalmaExceptionNotice.java │ │ │ ├── ConditionOnMarkdownExceptionNotice.java │ │ │ ├── ConditionOnTextExceptionNotice.java │ │ │ ├── ConditionOnWebAutoConfigure.java │ │ │ └── WebIGExceptionListener.java │ │ ├── aop │ │ │ └── CommonAop.java │ │ ├── condition │ │ │ ├── OnCalmaExceptionNoticeCondition.java │ │ │ ├── OnMarkdownExceptionNoticeCondition.java │ │ │ ├── OnTextExceptionNoticeCondition.java │ │ │ └── OnWebAutoConfigureCondition.java │ │ ├── notice │ │ │ ├── AopConfiguration.java │ │ │ └── CalmaNoticeConfiguration.java │ │ └── web │ │ │ ├── WebExceptionListenConfiguration.java │ │ │ └── load │ │ │ └── WebAutoLoadConfiguration.java │ │ ├── context │ │ ├── AbstractNoticeContext.java │ │ ├── DefaultNoticeContext.java │ │ ├── HttpNoticeContext.java │ │ ├── aware │ │ │ └── StatisticallyAware.java │ │ └── support │ │ │ ├── AbstractConditionPostProcessor.java │ │ │ ├── IGAnnoPostProcessor.java │ │ │ ├── IGExceptionPostProcessor.java │ │ │ ├── client │ │ │ ├── Client.java │ │ │ ├── DingClient.java │ │ │ └── SmsClient.java │ │ │ └── component │ │ │ ├── DingNoticeSendComponent.java │ │ │ ├── NoticeSendComponent.java │ │ │ └── SmsNoticeSendComponent.java │ │ ├── event │ │ ├── ExceptionEvent.java │ │ └── listener │ │ │ ├── AbstractCalmaNotifier.java │ │ │ └── CalmaNotifier.java │ │ ├── factory │ │ ├── DingMessageFactory.java │ │ └── ExceptionStatisticDtoFactory.java │ │ ├── model │ │ ├── message │ │ │ ├── DingMessage.java │ │ │ ├── Message.java │ │ │ ├── SmsMessage.java │ │ │ └── ding │ │ │ │ ├── DingAt.java │ │ │ │ ├── DingDingProperty.java │ │ │ │ ├── DingMarkdown.java │ │ │ │ └── DingText.java │ │ ├── notice │ │ │ ├── CommonNotice.java │ │ │ ├── HttpNotice.java │ │ │ └── Notice.java │ │ └── web │ │ │ └── WebNoticeInfo.java │ │ ├── resolver │ │ └── CalmaValueResolver.java │ │ ├── statistic │ │ ├── cache │ │ │ └── StatisticHelper.java │ │ └── dto │ │ │ └── ExceptionStatisticDto.java │ │ ├── util │ │ ├── DingClientFeign.java │ │ ├── JsonExpander.java │ │ ├── NumberUtils.java │ │ ├── StreamUtils.java │ │ ├── factory │ │ │ └── SupportYamlPropertyFactory.java │ │ └── web │ │ │ └── bridge │ │ │ └── CacheBridge.java │ │ └── web │ │ ├── interceptor │ │ └── ClearBodyInterceptor.java │ │ └── resolver │ │ ├── CalmaExceptionHandlerResolver.java │ │ ├── CurrentRequestBodyResolver.java │ │ ├── CurrentRequestHeaderResolver.java │ │ ├── DefaultRequestBodyResolver.java │ │ └── DefaultRequestHeaderResolver.java └── resources │ └── META-INF │ └── spring.factories └── test └── java └── com └── marvin ├── CommonTestProjectApplicationTests.java └── testModel ├── TestCalma.java └── TestController.java /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action for generating a contribution graph with a snake eating your contributions. 2 | 3 | name: Generate Snake 4 | 5 | on: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2.3.4 17 | 18 | - name: Generate Snake 19 | uses: Platane/snk@master 20 | id: snake-gif 21 | with: 22 | github_user_name: ${{ github.repository_owner }} 23 | gif_out_path: ./assets/github-contribution-grid-snake.gif 24 | svg_out_path: ./assets/github-contribution-grid-snake.svg 25 | 26 | - name: Push to GitHub 27 | uses: EndBug/add-and-commit@v7.2.1 28 | with: 29 | branch: main 30 | message: 'Generate Contribution Snake' 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /Image/common.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/common.png -------------------------------------------------------------------------------- /Image/ding0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/ding0.png -------------------------------------------------------------------------------- /Image/ding1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/ding1.png -------------------------------------------------------------------------------- /Image/ding2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/ding2.png -------------------------------------------------------------------------------- /Image/ding3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/ding3.png -------------------------------------------------------------------------------- /Image/ding4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/ding4.png -------------------------------------------------------------------------------- /Image/ding5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/ding5.png -------------------------------------------------------------------------------- /Image/ding6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/ding6.png -------------------------------------------------------------------------------- /Image/ding7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/ding7.png -------------------------------------------------------------------------------- /Image/ding8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/ding8-1.png -------------------------------------------------------------------------------- /Image/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marvinSpring/spring-boot-starter-calma/27e4a7e62a63ac1e73ab08ba2de5d5f434f5b2ae/Image/web.png -------------------------------------------------------------------------------- /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 | #### 异常通知spring-boot-starter 2 | 3 | **介绍** 4 | 5 | 本项目是适用于企业开发,个人开发者,在平时开发中这个项目可能一点忙都帮不上你,但是当你的项目开发完成并且上线之后,它的作用就发挥出来了,因为它将代替你监控你的工程 6 | ,在你不想去24小时不停盯着日志看是否报错的时候,在你下班或者休息的时间帮你照看你的项目, 直到它发生异常的时候,它将精准的把异常通过手机短信/钉钉来通知你项目的异常情况,这让你可以 7 | 在项目上线之后无需在让人一直守着它,甚至如果项目出问题的时候可以将锅直接甩到负责人身上, 而无需在它出异常的时候再去定位异常找出是谁写的,然后再通知他,这样效率很低,而本项目可以 自动化的将所有这一切令人不爽的工作自动化的处理,省时省力。 8 | 9 | 系统需求: ![Java Version](https://img.shields.io/badge/Java%20Version-1.8%2B-brightgreen?style=flat-square&logo=appveyor) ![Maven](https://img.shields.io/badge/Maven-3.2.5%2B-brightgreen?style=flat-square&logo=appveyor) ![SpringBoot](https://img.shields.io/badge/SpringBoot-2.3.6.RELEASE-brightgreen?style=flat-square&logo=appveyor) 10 | 11 | **快速入门** 12 | 13 | 1.在你的工程的pom文件中将以下依赖加入 14 | 15 | ```xml 16 | 17 | io.github.marvinspring 18 | spring-boot-starter-calma 19 | 1.0.6.RELEASE 20 | 21 | ``` 22 | 23 | 2.在SpringBoot的配置文件:application.yml中做如下的配置: 24 | 25 | ```yaml 26 | spring: 27 | application: 28 | name: #项目名称,这里和project-name都可以给项目配置名称 29 | calma: 30 | exceptionnotice: 31 | enabled: true #开启异常通知 32 | listen-type: common #普通模式 33 | project-name: a example project #项目名称 34 | #Sms配置 35 | # sms: 36 | # enable: true #开启短信通知 37 | # regionId: 阿里云短信的regionId 38 | # accessKey: 阿里云短信的accessKey 39 | # secret: 阿里云短信的密钥 40 | # phoneNumbers: 你的手机号 41 | # signName: 阿里云短信的签名 42 | # templateCode: 阿里云短信的模板码 43 | #钉钉 44 | dingding: 45 | enable: true #开启钉钉通知 46 | msgtype: text #发送的消息格式,text将通过普通文本方式发送,markdown将会将异常信息用markdown语法转化后发送 47 | phoneNumbers: #要@的人的手机号,格式为:136xxxxxxxx,159xxxxxxxx 48 | userIds: #要@的人的钉钉id,格式为:xxx,xxxx 49 | isAtAll: true #true 通知全体成员,false则不 50 | access_token: #钉钉机器人的token 51 | secret: #钉钉机器人的密钥 52 | ``` 53 | 54 | 3.首先在你的项目中写如下案例 55 | 56 | ```java 57 | 58 | @Component//由于common方式通知实现是由spring aop方式实现,这里它必须被装载到spring中 59 | @CalmaExceptionListener // 异常通知的监控来自这个注解 60 | public class ExceptionTest { 61 | 62 | //@CalmaExceptionListener当然标注在某个方法上面就只监控被标注的方法 63 | public void testException(String param) { 64 | System.out.println("参数:" + param); 65 | throw new IllegalArgumentException("异常"); 66 | } 67 | 68 | } 69 | 70 | ``` 71 | 72 | 4.测试 73 | 74 | ```java 75 | 76 | @RunWith(SpringRunner.class) 77 | @SpringBootTest 78 | public class DemoApplicationTests { 79 | 80 | @Autowired 81 | private ExceptionTest exceptionTest; 82 | 83 | @Test 84 | public void contextLoads() { 85 | exceptionTest.testException("冲冲冲!"); 86 | } 87 | } 88 | ``` 89 | 然后你的钉钉就会钉d=====( ̄▽ ̄*)b的一声 90 | ![img.png](Image/common.png) 91 | 92 | 到这里测试就完成了,可以快乐的当一个帅锅侠了 93 | 94 | -------------------------------------------------------------------- 95 | 96 | 从这开始讲解如何在web模式下使用本框架 97 | 98 | 1.在你的工程的pom文件中将以下依赖加入 99 | 100 | ```xml 101 | 102 | io.github.marvinspring 103 | spring-boot-starter-calma 104 | 1.0.6.RELEASE 105 | 106 | ``` 107 | 108 | 2.在SpringBoot的配置文件:application.yml中做如下的配置: 109 | 110 | ```yaml 111 | spring: 112 | application: 113 | name: #这里和project-name都可以配置项目名称 114 | calma: 115 | exceptionnotice: 116 | enabled: true #开启异常通知 117 | listen-type: web #区别是这里是web,开启了web模式的征途了 118 | project-name: a example project #项目名称 119 | #Sms配置 120 | # sms: 121 | # enable: true #开启短信通知 122 | # regionId: 阿里云短信的regionId 123 | # accessKey: 阿里云短信的accessKey 124 | # secret: 阿里云短信的密钥 125 | # phoneNumbers: 你的手机号 126 | # signName: 阿里云短信的签名 127 | # templateCode: 阿里云短信的模板码 128 | #钉钉 129 | dingding: 130 | enable: true #开启钉钉通知 131 | msgtype: text #发送的消息格式,text将通过普通文本方式发送,markdown将会将异常信息用markdown语法转化后发送 132 | phoneNumbers: #要@的人的手机号,格式为:136xxxxxxxx,159xxxxxxxx 133 | userIds: #要@的人的钉钉id,格式为:xxx,xxxx 134 | isAtAll: true #true 通知全体成员,false则不 135 | access_token: #钉钉机器人的token 136 | secret: #钉钉机器人的密钥 137 | ``` 138 | 3.当然是像上面一样写个模拟的控制器了,你还在期待什么 139 | ```java 140 | @RestController//web方式只需要将其声明为控制器就可以例如:@Controller 141 | @CalmaExceptionListener//写在这里可以让所有的方法都被监控 142 | public class ExampleController{ 143 | 144 | @GetMapping("/testFoo") 145 | //@CalmaExceptionListener当然标注在某个方法上面就只监控被标注的方法 146 | public void foo(String param){ 147 | int i = 1/0; 148 | } 149 | 150 | @PostMapping("/testFoo") 151 | public void foo(Integer param){ 152 | param = param/0; 153 | } 154 | 155 | } 156 | ``` 157 | 158 | 4.请求它,无所谓用httpclient或者postman或者其他方式,这里由于我懒得截图,,就没图了 159 | 160 | 然后你的钉钉再一次钉d=====( ̄▽ ̄*)b 161 | ![img_1.png](Image/web.png) 162 | 到这里为止,web模式的这个测试也完了,还不快点去试试? 163 | 164 | -------------------------------------------------------------------- 165 | #### web模式下开启自动加载controller 166 | 167 | 该功能背景是由于某些项目需要监听的接口太多,一个一个标注@CalmaExceptionListener太过于繁琐,不利于开发者,本着约定大于配置的约束,有了本功能以及以下介绍: 168 | 169 | 1.首先,看完上面你应该已经知道如何配置web模式并成功通知了,所以假定你已经可以正常的发通知了 170 | 171 | 2.只需要在此基础上将配置文件(application.yml也可能在你的项目是其他指定名称)中添加 172 | 173 | ```yaml 174 | calma: 175 | exceptionnotice: 176 | listen-type: web #区别是这里是web,开启了web模式的征途了 177 | auto: true #注意该值不为true或者缺省都不会进行自动通知,尤其是listen-type不为web时,该配置无意义,只有在web模式下才会自动扫描控制器并将自动通知 178 | ``` 179 | 180 | 3.写一个控制器类似于 181 | 182 | ```java 183 | @RestController//web方式只需要将其声明为控制器就可以例如:@Controller 184 | //@CalmaExceptionListener//这时候该注解就可有可无了 185 | public class ExampleController{ 186 | 187 | @GetMapping("/testFoo") 188 | //@CalmaExceptionListener这时候该注解就可有可无了 189 | public void foo(String param){ 190 | int i = 1/0; 191 | } 192 | 193 | @PostMapping("/testFoo") 194 | public void foo(Integer param){ 195 | param = param/0; 196 | } 197 | 198 | } 199 | ``` 200 | 在此类中一共有俩个接口被扫描到,分别是GET /testFoo以及POST /testFoo,这俩接口都将被calma扫描并当异常发生时通知到你 201 | 202 | 4.这里由于考虑到某些特定的接口不需要被通知,你还可以使用@WebIGExceptionListener注解对不需要通知的接口标注,也可以标注在对应的类身上,例如 203 | 204 | 205 | ```java 206 | @RestController//web方式只需要将其声明为控制器就可以例如:@Controller 207 | public class ExampleController{ 208 | 209 | @GetMapping("/testFoo") 210 | @WebIGExceptionListener 211 | public void foo(String param){ 212 | int i = 1/0; 213 | } 214 | 215 | @PostMapping("/testFoo") 216 | public void foo(Integer param){ 217 | param = param/0; 218 | } 219 | 220 | } 221 | ``` 222 | 223 | 这里GET /testFoo接口中发生的任何异常calma不会将其纳入通知范围 224 | 225 | ------------ 226 | #### 全模式支持IGException异常。 227 | 该异常为忽略异常,如果被监听的方法/接口抛出了该异常将不会发起通知。 228 | 229 | 该功能背景是作用于某些特定的逻辑下,异常是必须抛出的,而又不需要去监听,因为开发者不关心他,那么你可以抛出IGException or extends它,它设定为RuntimeException并且是非final异常,你的所有业务异常可以继承它,当然这样就有一定侵入性(当我后期对这个异常进行更改名称/异常层次等,当然不会将其变更为final异常)这样你的项目会有麻烦。 230 | 231 | 在web模式下: 232 | 233 | ```java 234 | @RestController//web方式只需要将其声明为控制器就可以例如:@Controller 235 | public class ExampleController{ 236 | 237 | @GetMapping("/testFoo") 238 | public void foo(String param){ 239 | int i = 1/0; 240 | } 241 | 242 | @PostMapping("/testFoo") 243 | public void foo(Integer param){ 244 | if(xxxCondition){ 245 | throw new IGException("我不想被通知"); 246 | } 247 | param = param/0; 248 | } 249 | 250 | } 251 | ``` 252 | 如上述代码中POST /testFoo接口如果走到了xxxCondition逻辑内并成功抛出IGException异常,那么此次请求将不会发起通知流程 253 | 254 | 同样的,在普通模式下: 255 | 256 | ```java 257 | @RunWith(SpringRunner.class) 258 | @SpringBootTest 259 | public class DemoApplicationTests { 260 | 261 | @Autowired 262 | private ExceptionTest exceptionTest; 263 | 264 | @Test 265 | public void contextLoads() { 266 | if(xxxCondition){ 267 | throw new IGException("我不想被通知"); 268 | } 269 | exceptionTest.testException("冲冲冲!"); 270 | } 271 | } 272 | ``` 273 | contextLoads方法如果走到了xxxCondition逻辑内并成功抛出IGException异常,那么此次请求将不会发起通知流程。 274 | 275 | ------------ 276 | ###### 关于钉钉机器人的配置: 277 | 278 | ###### 1.找到你所需要通知的群 279 | 280 | ![ding0.png](Image/ding0.png) 281 | 282 | ###### 2.点击群设置,在pc端,它通常在右上角的位置 283 | 284 | ![ding1.png](Image/ding1.png) 285 | 286 | ###### 3.在群设置中找到机器人选项,进入 287 | 288 | ![ding2.png](Image/ding2.png) 289 | 290 | ###### 4.点击添加机器人 291 | 292 | ![ding3.png](Image/ding3.png) 293 | 294 | ###### 5.继续点击机器人 295 | 296 | ![ding4.png](Image/ding4.png) 297 | 298 | ###### 6.选中自定义机器人 299 | 300 | ![ding5.png](Image/ding5.png) 301 | 302 | ###### 7.给你的机器人取个名字并找到安全设置,勾选加签作为认证方式,点击复制,将签名复制到你的项目中的ding.secret的值的位置 303 | 304 | ![ding6.png](Image/ding6.png) 305 | 306 | ###### 8.在上一步中,点击完成后,你需要将钉钉的access token同样复制到你的配置文件中的ding.access_token的值的位置中 307 | 308 | ![ding7.png](Image/ding7.png) 309 | 310 | ###### 9.到这一步钉钉机器人的配置就完成了 311 | 312 | ![ding8-1.png](Image/ding8-1.png) 313 | 314 | ------------ 315 | 316 | 317 | 未完待续............. 318 | 319 | (有问题请联系我的Email:1261626796@qq.com,我会很快回复的) 320 | 321 | 下个版本的期待: 322 | 323 | 1.支持微服务的监控通知 324 | 325 | _ps:终于吧markdown补充好了,之前一直有其他事情没空搞 326 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-merlot -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.springframework.boot 6 | spring-boot-starter-parent 7 | 2.3.6.RELEASE 8 | 9 | 10 | io.github.marvinspring 11 | spring-boot-starter-calma 12 | 1.0.6.RELEASE 13 | spring-boot-starter-calma 14 | 一个异常通知框架 15 | 16 | 17 | 8 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-configuration-processor 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | com.aliyun 39 | aliyun-java-sdk-core 40 | 4.5.3 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-aop 45 | 46 | 47 | com.google.code.gson 48 | gson 49 | 50 | 51 | io.github.openfeign 52 | feign-core 53 | 9.7.0 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-test 58 | test 59 | 60 | 61 | org.junit.vintage 62 | junit-vintage-engine 63 | 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-starter 69 | 70 | 71 | org.projectlombok 72 | lombok 73 | true 74 | 75 | 76 | commons-codec 77 | commons-codec 78 | 79 | 80 | org.springframework.boot 81 | spring-boot-configuration-processor 82 | true 83 | 84 | 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-jar-plugin 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/common/constant/ExceptionConstant.java: -------------------------------------------------------------------------------- 1 | package com.marvin.common.constant; 2 | 3 | public class ExceptionConstant { 4 | 5 | private static final String TYPE_CANNOT_BE_CONVERT = "type {%s} cannot convert to {%s},please check your configuration\n The following information may be help you:\n {%s}"; 6 | 7 | public static final String VARIABLE_IS_NULL = "variable {%s} cannot convert be null"; 8 | 9 | public static String typeConvertErrorText(String old, String newest, String varName) { 10 | return String.format(TYPE_CANNOT_BE_CONVERT, old, newest, varName); 11 | } 12 | 13 | static public class Internal { 14 | 15 | private static final String EXCEPTION = "error code is {%s},Please contact me if you see this Exception.\n mail: {%s}"; 16 | 17 | //框架全局联系人邮件 18 | private static final String MAIL = "1261626796@qq.com"; 19 | 20 | public static String internalException(String code) { 21 | return String.format(EXCEPTION, code, MAIL); 22 | } 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/common/enumeration/ExceptionType.java: -------------------------------------------------------------------------------- 1 | package com.marvin.common.enumeration; 2 | 3 | public enum ExceptionType { 4 | 5 | ERROR_OBTAINING_METHOD(9521,"Request method not found or empty."), 6 | UNABLE_FIND_CONTROLLER(9522,"Controller not found."),; 7 | 8 | private final Integer value; 9 | private final String message; 10 | 11 | public Integer getValue() { 12 | return value; 13 | } 14 | 15 | public String getMessage() { 16 | return message; 17 | } 18 | 19 | ExceptionType(Integer value, String message) { 20 | this.value = value; 21 | this.message = message; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/common/enumeration/ListenType.java: -------------------------------------------------------------------------------- 1 | package com.marvin.common.enumeration; 2 | 3 | public enum ListenType { 4 | 5 | COMMON,WEB; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/common/enumeration/ProjectEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.marvin.common.enumeration; 2 | 3 | //项目环境 4 | public enum ProjectEnvironment { 5 | 6 | DEVELOP("develop"),PREVIEW("preview"),ROLLBACK("rollback"),RELEASE("release"),TEST("test"); 7 | 8 | private String name; 9 | 10 | ProjectEnvironment(String name) { 11 | this.name = name; 12 | } 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | public void setName(String name) { 19 | this.name = name; 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/common/exception/CalmaRuntimeException.java: -------------------------------------------------------------------------------- 1 | package com.marvin.common.exception; 2 | 3 | 4 | import com.marvin.common.enumeration.ExceptionType; 5 | 6 | import java.io.Serializable; 7 | 8 | //本stater的内部异常,用来处理内部问题,在别的项目引用本项目时,可能会有本项目报错的问题,这个时候它就不能很好的吧问题抛出来,我在想这种时候怎么能吧问题让开发者知道,再去套娃抛出来还是 9 | public class CalmaRuntimeException extends RuntimeException implements Serializable { 10 | 11 | private static final Long serializableId = 0L; 12 | 13 | public CalmaRuntimeException(ExceptionType type, Throwable throwable) { 14 | super(type.getMessage(),throwable); 15 | } 16 | 17 | public CalmaRuntimeException(ExceptionType type) { 18 | super(type.getMessage()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/common/exception/IGException.java: -------------------------------------------------------------------------------- 1 | package com.marvin.common.exception; 2 | 3 | import com.marvin.config.anno.WebIGExceptionListener; 4 | import org.springframework.lang.Nullable; 5 | 6 | /** 7 | * 标记不需要通知的异常,该异常被抛出的时候,calma将会忽略不纳入通知范围 8 | * 而只将其计入统计(以方便在您需要的时候将其情况叙述出来), 9 | * 用于项目中您觉得不重要的异常的情况. 10 | * 11 | * 例如: 12 | *
{@code
13 |  * public void bizMethod() throws IGException{
14 |  *     try {
15 |  *       //你可能出问题的业务代码并且该问题你不想去通知
16 |  *       ...;
17 |  *     } catch(BizException e) {
18 |  *         throw new IGException(e.getMessage());
19 |  *     }
20 |  * }
21 |  * }
22 |  * 
23 | * 24 | * @see WebIGExceptionListener 25 | * @author Marvin 26 | */ 27 | public class IGException extends RuntimeException { 28 | 29 | public IGException(String msg) { 30 | super(msg); 31 | } 32 | 33 | public IGException(@Nullable String msg, @Nullable Throwable cause) { 34 | super(msg, cause); 35 | } 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/common/exception/NoSuchHttpRequestMethodException.java: -------------------------------------------------------------------------------- 1 | package com.marvin.common.exception; 2 | 3 | 4 | import com.marvin.common.enumeration.ExceptionType; 5 | 6 | import java.io.Serializable; 7 | 8 | public class NoSuchHttpRequestMethodException extends CalmaRuntimeException implements Serializable { 9 | 10 | public NoSuchHttpRequestMethodException(ExceptionType type) { 11 | super(type); 12 | } 13 | 14 | public NoSuchHttpRequestMethodException(ExceptionType type, Throwable throwable) { 15 | super(type, throwable); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/common/exception/internal/TypeConvertException.java: -------------------------------------------------------------------------------- 1 | package com.marvin.common.exception.internal; 2 | 3 | public class TypeConvertException extends RuntimeException{ 4 | 5 | public TypeConvertException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/CommonNoticeContextConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config; 2 | 3 | import com.marvin.context.DefaultNoticeContext; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.context.ApplicationEventPublisher; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | import javax.annotation.Resource; 11 | 12 | @Configuration 13 | @ConditionalOnProperty(prefix = "calma", value = "exceptionnotice.enbaled", matchIfMissing = true) 14 | public class CommonNoticeContextConfiguration { 15 | 16 | @Bean 17 | @ConditionalOnMissingBean 18 | @Resource 19 | public DefaultNoticeContext noticeContext(ApplicationEventPublisher publisher) { 20 | return new DefaultNoticeContext(publisher); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/DingComponentConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config; 2 | 3 | import com.marvin.context.support.client.Client; 4 | import com.marvin.context.support.component.DingNoticeSendComponent; 5 | import com.marvin.context.support.component.NoticeSendComponent; 6 | import com.marvin.model.notice.CommonNotice; 7 | import com.marvin.resolver.CalmaValueResolver; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | @Configuration 15 | @Slf4j 16 | @ConditionalOnProperty(prefix = "calma.exceptionnotice",name = "enabled",havingValue = "true") 17 | public class DingComponentConfiguration { 18 | 19 | @Bean// 注入通知的组件 20 | @ConditionalOnMissingBean 21 | @ConditionalOnProperty(prefix = "calma.dingding",name = "enable",havingValue = "true") 22 | public NoticeSendComponent registerSendComponent(CalmaValueResolver resolver, 23 | Client client) { 24 | if (log.isDebugEnabled()){ 25 | log.info("-----------------》》》》》钉钉通知开启《《《《《《《-------------------------"); 26 | } 27 | return new DingNoticeSendComponent<>(resolver, client); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/DingDingClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config; 2 | 3 | import com.marvin.context.support.client.Client; 4 | import com.marvin.context.support.client.DingClient; 5 | import com.marvin.model.message.ding.DingDingProperty; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 9 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | @ConditionalOnProperty(prefix = "calma.dingding",name = "enable",havingValue = "true") 15 | @EnableConfigurationProperties(DingDingProperty.class) 16 | @Slf4j 17 | public class DingDingClientConfiguration {//注入发送钉钉信息的客户端 18 | 19 | @Bean 20 | @ConditionalOnMissingBean 21 | public Client dingDingClient(DingDingProperty dingProperty) { 22 | if (log.isDebugEnabled()){ 23 | log.info("-----------------》》》》》钉钉组件客户端注入《《《《《《《-------------------------"); 24 | } 25 | return new DingClient(dingProperty); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/ListenerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config; 2 | 3 | import com.marvin.context.support.component.NoticeSendComponent; 4 | import com.marvin.event.listener.CalmaNotifier; 5 | import com.marvin.model.notice.CommonNotice; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | @Configuration 13 | @Slf4j 14 | @ConditionalOnProperty(prefix = "calma.exceptionnotice",name = "enabled",havingValue = "true") 15 | public class ListenerConfiguration { 16 | 17 | @Bean 18 | @ConditionalOnMissingBean 19 | public CalmaNotifier calmaNotifier(NoticeSendComponent component) { 20 | if (log.isDebugEnabled()) { 21 | log.info("-----------------》》》》》监听开启《《《《《《《-------------------------"); 22 | } 23 | return new CalmaNotifier(component); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/MarkdownExceptionNoticeSendConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config; 2 | 3 | import com.marvin.config.anno.ConditionOnMarkdownExceptionNotice; 4 | import com.marvin.model.notice.CommonNotice; 5 | import com.marvin.resolver.CalmaValueResolver; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | @ConditionOnMarkdownExceptionNotice 13 | @ConditionalOnProperty(prefix = "calma.exceptionnotice",name = "enabled",havingValue = "true") 14 | public class MarkdownExceptionNoticeSendConfiguration { 15 | 16 | @Bean 17 | @ConditionalOnMissingBean 18 | public CalmaValueResolver exceptionNotice() { 19 | return CommonNotice::createMarkdown; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/SmsClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config; 2 | 3 | import com.marvin.model.message.SmsMessage; 4 | import com.marvin.context.support.client.SmsClient; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | @Configuration 13 | @ConditionalOnProperty(prefix = "calma.sms",name = "enable",havingValue = "true") 14 | @EnableConfigurationProperties(SmsMessage.class) 15 | @Slf4j 16 | public class SmsClientConfiguration { 17 | 18 | @Bean 19 | @ConditionalOnMissingBean 20 | public SmsClient clientForSms(SmsMessage noticeStruct) { 21 | if (log.isDebugEnabled()) { 22 | log.info("-----------------》》》》》短信组件客户端注入《《《《《《《-------------------------"); 23 | } 24 | return new SmsClient(noticeStruct); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/SmsComponentConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config; 2 | 3 | import com.marvin.context.support.client.Client; 4 | import com.marvin.context.support.component.SmsNoticeSendComponent; 5 | import com.marvin.model.notice.CommonNotice; 6 | import com.marvin.resolver.CalmaValueResolver; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | @ConditionalOnProperty(prefix = "calma.sms", name = "enable", havingValue = "true") 15 | @Slf4j 16 | public class SmsComponentConfiguration { 17 | 18 | @Bean 19 | @ConditionalOnMissingBean 20 | public SmsNoticeSendComponent smsNoticeComponent(CalmaValueResolver resolver, 21 | Client client) { 22 | if (log.isDebugEnabled()) { 23 | log.info("-----------------》》》》》短信通知开启《《《《《《《-------------------------"); 24 | } 25 | return new SmsNoticeSendComponent<>(resolver, client); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/TextExceptionNoticeSendConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config; 2 | 3 | import com.marvin.config.anno.ConditionOnTextExceptionNotice; 4 | import com.marvin.model.notice.CommonNotice; 5 | import com.marvin.resolver.CalmaValueResolver; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | @ConditionOnTextExceptionNotice 13 | @ConditionalOnProperty(prefix = "calma.exceptionnotice",name = "enabled",havingValue = "true") 14 | public class TextExceptionNoticeSendConfiguration { 15 | 16 | @Bean 17 | @ConditionalOnMissingBean 18 | public CalmaValueResolver exceptionNotice() { 19 | return CommonNotice::createText; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/WebNoticeContextConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config; 2 | 3 | import com.marvin.context.HttpNoticeContext; 4 | import com.marvin.model.web.WebNoticeInfo; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.context.ApplicationEventPublisher; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | import javax.annotation.Resource; 13 | 14 | @Configuration 15 | @ConditionalOnProperty(prefix = "calma", value = "exceptionnotice.listen-type", havingValue = "WEB") 16 | public class WebNoticeContextConfiguration { 17 | 18 | @Autowired(required = false) 19 | private WebNoticeInfo noticeInfo; 20 | 21 | @Bean 22 | @ConditionalOnMissingBean 23 | @Resource 24 | public HttpNoticeContext noticeContext(ApplicationEventPublisher publisher) { 25 | return new HttpNoticeContext(publisher,noticeInfo); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/anno/CalmaExceptionListener.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.anno; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Documented 6 | @Target({ElementType.TYPE,ElementType.METHOD}) 7 | @Retention(RetentionPolicy.RUNTIME) 8 | public @interface CalmaExceptionListener {//异常监听注解,用来监听这个注解标注的类所产生的运行时异常 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/anno/ConditionOnCalmaExceptionNotice.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.anno; 2 | 3 | import com.marvin.config.condition.OnCalmaExceptionNoticeCondition; 4 | import org.springframework.context.annotation.Conditional; 5 | 6 | import java.lang.annotation.*; 7 | 8 | 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target({ElementType.TYPE,ElementType.METHOD}) 12 | @Documented 13 | @Conditional(value = {OnCalmaExceptionNoticeCondition.class}) 14 | public @interface ConditionOnCalmaExceptionNotice { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/anno/ConditionOnMarkdownExceptionNotice.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.anno; 2 | 3 | import com.marvin.config.condition.OnMarkdownExceptionNoticeCondition; 4 | import org.springframework.context.annotation.Conditional; 5 | 6 | import java.lang.annotation.*; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE,ElementType.METHOD}) 10 | @Documented 11 | @Conditional(value = {OnMarkdownExceptionNoticeCondition.class}) 12 | public @interface ConditionOnMarkdownExceptionNotice { 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/anno/ConditionOnTextExceptionNotice.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.anno; 2 | 3 | import com.marvin.config.condition.OnTextExceptionNoticeCondition; 4 | import org.springframework.context.annotation.Conditional; 5 | 6 | import java.lang.annotation.*; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE,ElementType.METHOD}) 10 | @Documented 11 | @Conditional(value = {OnTextExceptionNoticeCondition.class}) 12 | public @interface ConditionOnTextExceptionNotice { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/anno/ConditionOnWebAutoConfigure.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.anno; 2 | 3 | import com.marvin.config.condition.OnWebAutoConfigureCondition; 4 | import org.springframework.context.annotation.Conditional; 5 | 6 | import java.lang.annotation.*; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE}) 10 | @Documented 11 | @Conditional(value = {OnWebAutoConfigureCondition.class}) 12 | public @interface ConditionOnWebAutoConfigure { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/anno/WebIGExceptionListener.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.anno; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * 把它放在你不像被监听的controller上, 7 | * 他将像医学中的抗生素一般,将你不会去将该controller中产生的任何异常进行通知, 8 | * 但它会偷偷的在本地记录(为了方便后续你需要的时候使用) 9 | * 10 | * 例如: 11 | *
{@code
12 |  * @IGExceptionListener
13 |  * @Controller
14 |  * public class FooController{
15 |  *
16 |  *     @XxxMapping
17 |  *     public Foo fooMethod(){
18 |  *         return new Foo();
19 |  *     }
20 |  * }
21 |  * }
22 |  * 
23 | * 24 | * @see com.marvin.common.exception.IGException 25 | * @author marvin 26 | */ 27 | @Documented 28 | @Target({ElementType.TYPE,ElementType.METHOD}) 29 | @Retention(RetentionPolicy.RUNTIME) 30 | public @interface WebIGExceptionListener { 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/aop/CommonAop.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.aop; 2 | 3 | import com.marvin.config.anno.CalmaExceptionListener; 4 | import com.marvin.context.DefaultNoticeContext; 5 | import com.marvin.model.notice.CommonNotice; 6 | import com.marvin.statistic.cache.StatisticHelper; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.aspectj.lang.JoinPoint; 9 | import org.aspectj.lang.annotation.AfterThrowing; 10 | import org.aspectj.lang.annotation.Aspect; 11 | import org.springframework.beans.factory.annotation.Value; 12 | 13 | 14 | @Aspect 15 | @Slf4j 16 | /** 17 | * @Describe:核心,通过Aop中的在方法执行抛异常后切入的这个方式来将异常进行捕获,然后对捕获到的异常进行处理 18 | * @Date:2021/03/01 19 | * @Author:Marvin 20 | */ 21 | public class CommonAop { 22 | 23 | private final DefaultNoticeContext noticeContext; 24 | 25 | @Value("${spring.application.name}") 26 | private String projectName; 27 | 28 | public CommonAop(DefaultNoticeContext noticeContext) { 29 | this.noticeContext = noticeContext; 30 | } 31 | 32 | @AfterThrowing(value = "@within(listener)", throwing = "e", argNames = "joinPoint,listener,e") 33 | public void classExceptionNotifier(JoinPoint joinPoint, CalmaExceptionListener listener, RuntimeException e) { 34 | createNotice(joinPoint.getArgs(), e, projectName); 35 | } 36 | 37 | @AfterThrowing(value = "@annotation(listener)", throwing = "e", argNames = "joinPoint,listener,e") 38 | public void methodExceptionNotifier(JoinPoint joinPoint, CalmaExceptionListener listener, RuntimeException e) { 39 | createNotice(joinPoint.getArgs(), e, projectName); 40 | } 41 | 42 | public void createNotice(Object[] objArgs, RuntimeException e, String projectName) { 43 | CommonNotice commonNotice = noticeContext.createNotice(objArgs, e, projectName); 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/condition/OnCalmaExceptionNoticeCondition.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.condition; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 5 | import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 6 | import org.springframework.context.annotation.ConditionContext; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.core.type.AnnotatedTypeMetadata; 9 | import org.springframework.util.StringUtils; 10 | 11 | 12 | @Order(0) 13 | @Slf4j 14 | public class OnCalmaExceptionNoticeCondition extends SpringBootCondition{ 15 | 16 | @Override 17 | public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { 18 | String msgType = context.getEnvironment().getProperty("calma.exceptionnotice.enabled"); 19 | if (!StringUtils.isEmpty(msgType) && msgType.equalsIgnoreCase("true")) { 20 | log.info("-------------Calma异常通知启动----------------"); 21 | return ConditionOutcome.match(msgType); 22 | } 23 | return ConditionOutcome.noMatch("calma.exceptionnotice.enabled is false"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/condition/OnMarkdownExceptionNoticeCondition.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.condition; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 5 | import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 6 | import org.springframework.context.annotation.ConditionContext; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.core.type.AnnotatedTypeMetadata; 9 | import org.springframework.util.StringUtils; 10 | 11 | @Order(1) 12 | @Slf4j 13 | public class OnMarkdownExceptionNoticeCondition extends SpringBootCondition { 14 | 15 | @Override 16 | public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { 17 | String msgType = context.getEnvironment().getProperty("calma.dingding.msgtype"); 18 | if (!StringUtils.isEmpty(msgType) && msgType.equalsIgnoreCase("markdown")) { 19 | log.info("-------------Markdown模式通知----------------"); 20 | return ConditionOutcome.match(msgType); 21 | } 22 | return ConditionOutcome.noMatch(msgType); 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/condition/OnTextExceptionNoticeCondition.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.condition; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 5 | import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 6 | import org.springframework.context.annotation.ConditionContext; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.core.type.AnnotatedTypeMetadata; 9 | import org.springframework.util.StringUtils; 10 | 11 | @Order(1) 12 | @Slf4j 13 | public class OnTextExceptionNoticeCondition extends SpringBootCondition { 14 | 15 | @Override 16 | public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { 17 | String msgType = context.getEnvironment().getProperty("calma.dingding.msgtype"); 18 | if (!StringUtils.isEmpty(msgType) && msgType.equalsIgnoreCase("text")) { 19 | log.info("-------------文本模式通知----------------"); 20 | return ConditionOutcome.match(msgType); 21 | } 22 | return ConditionOutcome.noMatch(msgType); 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/condition/OnWebAutoConfigureCondition.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.condition; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 5 | import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 6 | import org.springframework.context.annotation.ConditionContext; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.core.type.AnnotatedTypeMetadata; 9 | import org.springframework.util.StringUtils; 10 | 11 | @Order(3) 12 | @Slf4j 13 | public class OnWebAutoConfigureCondition extends SpringBootCondition { 14 | 15 | @Override 16 | public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { 17 | String auto = context.getEnvironment().getProperty("calma.exceptionnotice.auto"); 18 | if (!StringUtils.isEmpty(auto) && auto.equalsIgnoreCase("true")) { 19 | log.info("-------------加载web忽略模块----------------"); 20 | return ConditionOutcome.match(auto); 21 | } 22 | return ConditionOutcome.noMatch(auto); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/notice/AopConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.notice; 2 | 3 | import com.marvin.config.aop.CommonAop; 4 | import com.marvin.context.DefaultNoticeContext; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | @ConditionalOnProperty(name = "calma.exceptionnotice.listen-type", havingValue = "COMMON", matchIfMissing = true) 13 | @Slf4j 14 | public class AopConfiguration { 15 | 16 | @Bean 17 | @ConditionalOnMissingBean 18 | @ConditionalOnProperty(prefix = "calma.exceptionnotice",name = "enabled",havingValue = "true") 19 | public CommonAop calmaAop(DefaultNoticeContext dispatcherHandler ){ 20 | if (log.isDebugEnabled()){ 21 | log.info("-----------------》》》》》普通通知模式开启《《《《《《《-------------------------"); 22 | } 23 | return new CommonAop(dispatcherHandler); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/notice/CalmaNoticeConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.notice; 2 | 3 | import com.marvin.config.anno.ConditionOnCalmaExceptionNotice; 4 | import com.marvin.event.listener.AbstractCalmaNotifier; 5 | import com.marvin.event.listener.CalmaNotifier; 6 | import com.marvin.context.support.component.NoticeSendComponent; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | @ConditionOnCalmaExceptionNotice 13 | public class CalmaNoticeConfiguration { 14 | 15 | @Bean 16 | @ConditionalOnMissingBean 17 | public AbstractCalmaNotifier abstractCalmaNotifier(NoticeSendComponent noticeSendComponent){ 18 | return new CalmaNotifier(noticeSendComponent); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/web/WebExceptionListenConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.web; 2 | 3 | import com.marvin.config.anno.ConditionOnCalmaExceptionNotice; 4 | import com.marvin.web.interceptor.ClearBodyInterceptor; 5 | import com.marvin.web.resolver.*; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 11 | import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.web.servlet.HandlerExceptionResolver; 15 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 16 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 17 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; 18 | 19 | import javax.annotation.Resource; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | @Configuration 24 | @ConditionalOnWebApplication 25 | @ConditionOnCalmaExceptionNotice 26 | @ConditionalOnProperty(name = "calma.exceptionnotice.listen-type", havingValue = "WEB") 27 | @Slf4j 28 | public class WebExceptionListenConfiguration implements WebMvcConfigurer, WebMvcRegistrations { 29 | 30 | @Autowired 31 | @Resource 32 | private com.marvin.context.HttpNoticeContext HttpNoticeContext; 33 | 34 | //添加自定义异常处理解析器 35 | @Override 36 | public void extendHandlerExceptionResolvers(List resolvers) { 37 | if (log.isDebugEnabled()) { 38 | log.info("----------------------进入web模式----------------------"); 39 | } 40 | resolvers.add(0,calmaExceptionHandlerResolver()); 41 | } 42 | 43 | private CalmaExceptionHandlerResolver calmaExceptionHandlerResolver() { 44 | return new CalmaExceptionHandlerResolver(HttpNoticeContext, currentRequestHeaderResolver(), currentRequestBodyResolver()); 45 | } 46 | 47 | //------------ 48 | //注入请求头和请求体组件 49 | //----------- 50 | @Bean 51 | public CurrentRequestBodyResolver currentRequestBodyResolver() { 52 | return new DefaultRequestBodyResolver(); 53 | } 54 | 55 | @Bean 56 | @ConditionalOnMissingBean(value = CurrentRequestHeaderResolver.class) 57 | public CurrentRequestHeaderResolver currentRequestHeaderResolver() { 58 | return new DefaultRequestHeaderResolver(); 59 | } 60 | 61 | @Bean 62 | public ClearBodyInterceptor clearBodyInterceptor() { 63 | return new ClearBodyInterceptor(currentRequestBodyResolver()); 64 | } 65 | 66 | //添加自定义拦截器 67 | @Override 68 | public void addInterceptors(InterceptorRegistry registry) { 69 | registry.addInterceptor(clearBodyInterceptor()); 70 | } 71 | 72 | //给处理器适配器设置自定义的请求体解析器 73 | @Override 74 | public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() { 75 | RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter(); 76 | requestMappingHandlerAdapter.setRequestBodyAdvice(Collections.singletonList(currentRequestBodyResolver())); 77 | return requestMappingHandlerAdapter; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/config/web/load/WebAutoLoadConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.marvin.config.web.load; 2 | 3 | import com.marvin.config.anno.WebIGExceptionListener; 4 | import com.marvin.model.web.WebNoticeInfo; 5 | import com.marvin.util.web.bridge.CacheBridge; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.web.context.WebApplicationContext; 10 | import org.springframework.web.method.HandlerMethod; 11 | import org.springframework.web.servlet.mvc.method.RequestMappingInfo; 12 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 13 | 14 | import java.util.HashSet; 15 | import java.util.Map; 16 | 17 | @Configuration 18 | @EnableConfigurationProperties(WebNoticeInfo.class) 19 | //@ConditionOnWebAutoConfigure 20 | public class WebAutoLoadConfiguration { 21 | 22 | @Autowired 23 | private WebApplicationContext webApplicationContext; 24 | 25 | public void init(){ 26 | RequestMappingHandlerMapping requestMappingHandlerMapping = 27 | webApplicationContext.getBean(RequestMappingHandlerMapping.class); 28 | Map handlerMethods = requestMappingHandlerMapping.getHandlerMethods(); 29 | 30 | //忽略IGExceptionListener注解加在方法上的的请求 31 | handlerMethods.forEach((path, method) -> { 32 | WebIGExceptionListener declaredAnnotation = method.getBeanType().getDeclaredAnnotation(WebIGExceptionListener.class); 33 | if (declaredAnnotation == null && method.getMethodAnnotation(WebIGExceptionListener.class) == null) { 34 | CacheBridge.addAll(new HashSet<>(path.getPatternsCondition().getPatterns())); 35 | } 36 | }); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/AbstractNoticeContext.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context; 2 | 3 | import com.marvin.context.aware.StatisticallyAware; 4 | import com.marvin.context.support.AbstractConditionPostProcessor; 5 | import com.marvin.context.support.IGExceptionPostProcessor; 6 | import com.marvin.event.ExceptionEvent; 7 | import org.springframework.beans.factory.InitializingBean; 8 | 9 | /** 10 | * @Describe: 异常信息发布的上下文 11 | * @Date: 2021/03/01 12 | * @Author: Marvin 13 | */ 14 | public abstract class AbstractNoticeContext implements StatisticallyAware { 15 | 16 | public final void publishEventNotice(ExceptionEvent event) { 17 | //条件判断植入 18 | AbstractConditionPostProcessor igExceptionPostProcessor = new IGExceptionPostProcessor(event.getNotice().getE()); 19 | setNextPostProcessor(igExceptionPostProcessor,event); 20 | 21 | if (igExceptionPostProcessor.pass()) { 22 | doPublishEventNotice(event); 23 | } 24 | } 25 | 26 | /** 27 | * hook method , 可以让其他的增强器在此处植入 28 | * @param abstractPostProcessor 决策是否发通知 29 | * @param event 异常事件 30 | */ 31 | public abstract void setNextPostProcessor(AbstractConditionPostProcessor abstractPostProcessor, ExceptionEvent event); 32 | 33 | /** 34 | * 发布事件通知 35 | * @param event 事件 36 | */ 37 | public abstract void doPublishEventNotice(ExceptionEvent event); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/DefaultNoticeContext.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context; 2 | 3 | import com.marvin.config.anno.ConditionOnCalmaExceptionNotice; 4 | import com.marvin.context.support.AbstractConditionPostProcessor; 5 | import com.marvin.event.ExceptionEvent; 6 | import com.marvin.model.notice.CommonNotice; 7 | import com.marvin.statistic.cache.StatisticHelper; 8 | import org.springframework.context.ApplicationEventPublisher; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * 默认通知上下午 13 | * @author marvin 14 | */ 15 | @Configuration 16 | @ConditionOnCalmaExceptionNotice 17 | public class DefaultNoticeContext extends AbstractNoticeContext { 18 | 19 | private final ApplicationEventPublisher applicationEventPublisher; 20 | 21 | public DefaultNoticeContext(ApplicationEventPublisher applicationEventPublisher) { 22 | this.applicationEventPublisher = applicationEventPublisher; 23 | } 24 | 25 | public CommonNotice createNotice(Object[] objArgs, RuntimeException e, String projectName) { 26 | CommonNotice notice = new CommonNotice(e, objArgs, projectName); 27 | statistic(notice); 28 | publishEventNotice(new ExceptionEvent(this, notice)); 29 | return notice; 30 | } 31 | 32 | @Override 33 | public void statistic(CommonNotice notice) { 34 | StatisticHelper.common(notice); 35 | } 36 | 37 | @Override 38 | public void setNextPostProcessor(AbstractConditionPostProcessor abstractPostProcessor, ExceptionEvent event) { 39 | //something to do,but at there just ok 40 | } 41 | 42 | @Override 43 | public void doPublishEventNotice(ExceptionEvent event) { 44 | applicationEventPublisher.publishEvent(event); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/HttpNoticeContext.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context; 2 | 3 | import com.marvin.config.anno.ConditionOnCalmaExceptionNotice; 4 | import com.marvin.config.web.load.WebAutoLoadConfiguration; 5 | import com.marvin.context.support.AbstractConditionPostProcessor; 6 | import com.marvin.context.support.IGAnnoPostProcessor; 7 | import com.marvin.event.ExceptionEvent; 8 | import com.marvin.model.notice.CommonNotice; 9 | import com.marvin.model.notice.HttpNotice; 10 | import com.marvin.model.web.WebNoticeInfo; 11 | import com.marvin.statistic.cache.StatisticHelper; 12 | import com.marvin.util.web.bridge.CacheBridge; 13 | import org.springframework.beans.factory.InitializingBean; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.beans.factory.annotation.Value; 16 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 17 | import org.springframework.context.ApplicationEventPublisher; 18 | import org.springframework.context.annotation.Configuration; 19 | import org.springframework.context.annotation.Lazy; 20 | 21 | import javax.annotation.Resource; 22 | import java.util.Map; 23 | import java.util.Objects; 24 | import java.util.Set; 25 | 26 | @Configuration 27 | @ConditionOnCalmaExceptionNotice 28 | @ConditionalOnProperty(name = "calma.exceptionnotice.listen-type", havingValue = "WEB") 29 | public class HttpNoticeContext extends AbstractNoticeContext implements InitializingBean { 30 | 31 | @Value("${calma.exceptionnotice.exproject-name:${spring.application.name:project}}") 32 | private String projectName; 33 | 34 | private boolean auto; 35 | 36 | @Autowired(required = false) 37 | private WebNoticeInfo noticeInfo; 38 | 39 | @Lazy 40 | @Autowired 41 | @Resource 42 | private WebAutoLoadConfiguration webAutoLoadConfiguration; 43 | 44 | private final ApplicationEventPublisher applicationEventPublisher; 45 | 46 | @SuppressWarnings("all") 47 | public HttpNoticeContext(ApplicationEventPublisher publisher, WebNoticeInfo noticeInfo) { 48 | this.noticeInfo = noticeInfo; 49 | this.applicationEventPublisher = publisher; 50 | } 51 | 52 | @Override 53 | public void statistic(CommonNotice notice) { 54 | //http通知方式统计 55 | StatisticHelper.common(notice); 56 | } 57 | 58 | public HttpNotice createNotice(RuntimeException ex, String url, Map param, 59 | String requestBody, Map headers, String requestMethod) { 60 | HttpNotice httpExceptionNotice = new HttpNotice(ex, String.join( 61 | projectName, "的异常通知"), url, param, requestBody, headers, requestMethod); 62 | statistic(httpExceptionNotice); 63 | publishEventNotice(new ExceptionEvent(this, httpExceptionNotice)); 64 | //不通知,只统计 65 | return httpExceptionNotice; 66 | } 67 | 68 | @Override 69 | public void setNextPostProcessor(AbstractConditionPostProcessor conditionPostProcessor, ExceptionEvent event) { 70 | AbstractConditionPostProcessor igAnnoPostProcessor = null; 71 | igAnnoPostProcessor = new IGAnnoPostProcessor(((HttpNotice) event.getNotice()).getUrl(),getNotifiedMethods()); 72 | conditionPostProcessor.setNextPostProcessor(igAnnoPostProcessor); 73 | } 74 | 75 | @Override 76 | public void doPublishEventNotice(ExceptionEvent event) { 77 | applicationEventPublisher.publishEvent(event); 78 | } 79 | 80 | public boolean isAuto() { 81 | return auto; 82 | } 83 | 84 | @Override 85 | public void afterPropertiesSet() throws Exception { 86 | this.auto = Objects.nonNull(this.noticeInfo) && this.noticeInfo.isAuto(); 87 | } 88 | 89 | private Set getNotifiedMethods() { 90 | if (!CacheBridge.init()) { 91 | webAutoLoadConfiguration.init(); 92 | } 93 | return CacheBridge.getNotifiedMethods(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/aware/StatisticallyAware.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context.aware; 2 | 3 | import com.marvin.model.notice.CommonNotice; 4 | 5 | /** 6 | * 统计策略组件 7 | * @author marvin 8 | */ 9 | public interface StatisticallyAware { 10 | 11 | /** 12 | * 统计策略能力 13 | * @param notice 通知 14 | */ 15 | void statistic(CommonNotice notice); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/support/AbstractConditionPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context.support; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * 后置条件增强处理器,决定是否具体发通知 7 | * 8 | * @author marvin 9 | */ 10 | public abstract class AbstractConditionPostProcessor { 11 | 12 | /** 13 | * 下个校验节点 14 | */ 15 | protected AbstractConditionPostProcessor nextPostProcessor; 16 | 17 | /** 18 | * 是否有下个节点 19 | * @return true 有/false 无 20 | */ 21 | public boolean haveNextPostProcessor() { 22 | return nextPostProcessor != null; 23 | } 24 | 25 | /** 26 | * 设置下个节点 27 | * @param nextPostProcessor 下个节点 28 | */ 29 | public void setNextPostProcessor(AbstractConditionPostProcessor nextPostProcessor) { 30 | this.nextPostProcessor = nextPostProcessor; 31 | } 32 | 33 | /** 34 | * 根据条件判断是否放行 35 | * @return true 放行/false不放行 36 | */ 37 | public boolean pass() { 38 | return condition() && (Objects.isNull(nextPostProcessor) || nextPostProcessor.pass()); 39 | } 40 | 41 | /** 42 | * hook method 当前节点条件是否放行,根据自己的需求决定 43 | * @return true 放行/false 不放行 44 | */ 45 | abstract protected boolean condition(); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/support/IGAnnoPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context.support; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * 如果本次通知的异常所在类或者分发是由{@link com.marvin.config.anno.WebIGExceptionListener}标注的就忽略不进行通知 7 | */ 8 | public class IGAnnoPostProcessor extends AbstractConditionPostProcessor { 9 | 10 | private final String url; 11 | 12 | private final Set notifiedMethods; 13 | 14 | public IGAnnoPostProcessor(String url, Set notifiedMethods) { 15 | this.url = url; 16 | this.notifiedMethods = notifiedMethods; 17 | } 18 | 19 | @Override 20 | protected boolean condition() { 21 | return notifiedMethods.contains(url); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/support/IGExceptionPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context.support; 2 | 3 | import com.marvin.common.exception.IGException; 4 | 5 | /** 6 | * 如果本次通知的异常是{@link IGException}就忽略不进行通知 7 | */ 8 | public class IGExceptionPostProcessor extends AbstractConditionPostProcessor { 9 | 10 | private final Throwable throwable; 11 | 12 | public IGExceptionPostProcessor(Throwable ex) { 13 | this.throwable = ex; 14 | } 15 | 16 | @Override 17 | protected boolean condition() { 18 | boolean b = throwable instanceof IGException; 19 | return !b; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/support/client/Client.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context.support.client; 2 | 3 | import com.marvin.model.message.Message; 4 | 5 | /** 6 | * @Describe: 真正的发送组件 7 | * @Date: 2021/03/01 8 | * @Author: Marvin 9 | */ 10 | @FunctionalInterface 11 | public interface Client { 12 | 13 | void doSend(Message message); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/support/client/DingClient.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context.support.client; 2 | 3 | import com.google.gson.Gson; 4 | import com.marvin.util.DingClientFeign; 5 | import com.marvin.model.message.DingMessage; 6 | import com.marvin.model.message.Message; 7 | import com.marvin.model.message.ding.DingDingProperty; 8 | import feign.Feign; 9 | import feign.FeignException; 10 | import feign.RequestTemplate; 11 | import feign.Response; 12 | import feign.codec.DecodeException; 13 | import feign.codec.Decoder; 14 | import feign.codec.EncodeException; 15 | import feign.codec.Encoder; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.apache.commons.codec.binary.Base64; 18 | 19 | import javax.crypto.Mac; 20 | import javax.crypto.spec.SecretKeySpec; 21 | import java.io.IOException; 22 | import java.lang.reflect.Type; 23 | import java.nio.charset.StandardCharsets; 24 | import java.security.InvalidKeyException; 25 | import java.security.NoSuchAlgorithmException; 26 | import java.util.HashMap; 27 | 28 | @Slf4j 29 | public class DingClient implements Client {// 发送钉钉通知的客户端 30 | 31 | private final DingClientFeign client = Feign.builder().encoder(new GsonEncoder()).decoder(new GsonDecoder()).target(DingClientFeign.class, "https://oapi.dingtalk.com/robot"); 32 | 33 | private final Gson gson = new Gson();// json 34 | 35 | private final DingDingProperty dingProperty; 36 | 37 | public DingClient(DingDingProperty dingProperty) { 38 | this.dingProperty = dingProperty; 39 | } 40 | 41 | @Override // 预请求钉钉接口 42 | public void doSend(Message dingMessage) { 43 | long timeStamp = System.currentTimeMillis(); 44 | HashMap map = new HashMap<>(); 45 | map.put("sign", generateSign(timeStamp, dingProperty.getSecret())); 46 | map.put("timestamp", timeStamp); 47 | client.post(dingProperty.getAccess_token(), (DingMessage) dingMessage, map); 48 | } 49 | 50 | private String generateSign(long timeStamp, String signSecret) {// 生成sign码 51 | String strForSign = String.format("%d\n%s", timeStamp, signSecret); 52 | try { 53 | Mac mac = Mac.getInstance("HmacSHA256"); 54 | try { 55 | mac.init(new SecretKeySpec(signSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); 56 | byte[] signData = mac.doFinal(strForSign.getBytes(StandardCharsets.UTF_8)); 57 | return Base64.encodeBase64String(signData); 58 | } catch (InvalidKeyException e) { 59 | e.printStackTrace(); 60 | } 61 | } catch (NoSuchAlgorithmException e) { 62 | e.printStackTrace(); 63 | } 64 | return null; 65 | } 66 | 67 | class GsonDecoder implements Decoder { 68 | 69 | @Override 70 | public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException { 71 | return gson.fromJson(response.body().asReader(), type); 72 | } 73 | 74 | } 75 | 76 | class GsonEncoder implements Encoder { 77 | 78 | @Override 79 | public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { 80 | template.body(gson.toJson(object).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); 81 | } 82 | 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/support/client/SmsClient.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context.support.client; 2 | 3 | import com.aliyuncs.CommonRequest; 4 | import com.aliyuncs.CommonResponse; 5 | import com.aliyuncs.DefaultAcsClient; 6 | import com.aliyuncs.IAcsClient; 7 | import com.aliyuncs.exceptions.ClientException; 8 | import com.aliyuncs.http.MethodType; 9 | import com.aliyuncs.profile.DefaultProfile; 10 | import com.marvin.model.message.Message; 11 | import com.marvin.model.message.SmsMessage; 12 | 13 | import java.util.Map; 14 | 15 | //阿里云sms短信发送组件 16 | public class SmsClient implements Client { 17 | 18 | private SmsMessage smsMessage; 19 | 20 | public SmsClient(SmsMessage smsMessage) { 21 | this.setNoticeStruct(smsMessage); 22 | } 23 | 24 | public String send(SmsMessage notice) { 25 | DefaultProfile profile = DefaultProfile.getProfile(smsMessage.getRegionId(), smsMessage.getAccessKey(), 26 | smsMessage.getSecret()); 27 | IAcsClient client = new DefaultAcsClient(profile); 28 | CommonRequest request = new CommonRequest(); 29 | request.setSysMethod(MethodType.POST); 30 | request.setSysDomain("dysmsapi.aliyuncs.com"); 31 | request.setSysVersion("2017-05-25"); 32 | request.setSysAction("SendSms"); 33 | request.putQueryParameter("RegionId", this.smsMessage.getRegionId()); 34 | request.putQueryParameter("PhoneNumbers", this.smsMessage.getPhoneNumbers());// 手机号 35 | request.putQueryParameter("SignName", this.smsMessage.getSignName());// 签名 36 | request.putQueryParameter("TemplateCode", this.smsMessage.getTemplateCode());// 模板码 37 | Map param = notice.getParam(); 38 | request.putQueryParameter("TemplateParam", 39 | "{\"project\":\"" + param.get("project") + "\",\"time\":\"" 40 | + param.get("time").toString().replaceAll("T"," ").subSequence(0, param.get("time").toString().length() - 7) 41 | + "\",\"causeBy\":\"" + param.get("causeBy") + "\"}"); 42 | try { 43 | CommonResponse response = client.getCommonResponse(request); 44 | return response.getData(); 45 | } catch (ClientException e) { 46 | e.printStackTrace(); 47 | } 48 | return null; 49 | } 50 | 51 | @Override 52 | public void doSend(Message smsMessage) { 53 | send((SmsMessage) smsMessage); 54 | } 55 | 56 | public SmsMessage getNoticeStruct() { 57 | return smsMessage; 58 | } 59 | 60 | public void setNoticeStruct(SmsMessage smsMessage) { 61 | this.smsMessage = smsMessage; 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/support/component/DingNoticeSendComponent.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context.support.component; 2 | 3 | import com.marvin.context.support.client.Client; 4 | import com.marvin.factory.DingMessageFactory; 5 | import com.marvin.model.notice.CommonNotice; 6 | import com.marvin.resolver.CalmaValueResolver; 7 | import com.marvin.model.message.ding.DingDingProperty; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | /** 13 | * @Describe: 发送钉钉通知的组件 14 | * @Date: 2021/03/01 15 | * @Author: Marvin 16 | */ 17 | @Component 18 | @Slf4j 19 | public class DingNoticeSendComponent implements NoticeSendComponent { 20 | 21 | @Autowired 22 | private DingDingProperty dingDingProperty; 23 | 24 | private final CalmaValueResolver resolver; 25 | 26 | private final Client client; 27 | 28 | @SuppressWarnings("all") 29 | public DingNoticeSendComponent(CalmaValueResolver resolver, Client client) { 30 | this.client = client; 31 | this.resolver = resolver; 32 | } 33 | 34 | //将异常结构体组装好 35 | @Override 36 | public void send(T exceptionNotice) { 37 | try { 38 | if (log.isDebugEnabled()) { 39 | log.info("--------------->>>>>ding<<<<<<<<-----------------------"); 40 | } 41 | String text = resolver.resolve(exceptionNotice); 42 | client.doSend(DingMessageFactory.createDingMessage(dingDingProperty,text)); 43 | } catch (Exception e) { 44 | log.info(e.getCause().toString() + "\n \n"); 45 | e.printStackTrace(); 46 | } 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/support/component/NoticeSendComponent.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context.support.component; 2 | 3 | import com.marvin.model.notice.CommonNotice; 4 | 5 | /** 6 | * @Describe: 异常通知的发送组件 7 | * @Date: 2021/03/01 8 | * @Author: Marvin 9 | */ 10 | @FunctionalInterface 11 | public interface NoticeSendComponent { 12 | 13 | public void send(T entity); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/context/support/component/SmsNoticeSendComponent.java: -------------------------------------------------------------------------------- 1 | package com.marvin.context.support.component; 2 | 3 | import com.marvin.context.support.client.Client; 4 | import com.marvin.context.support.client.SmsClient; 5 | import com.marvin.model.notice.CommonNotice; 6 | import com.marvin.model.message.SmsMessage; 7 | import com.marvin.resolver.CalmaValueResolver; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | 11 | import java.time.LocalDateTime; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * @Describe: Sms的发送组件 17 | * @Date: 2021/03/01 18 | * @Author: Marvin 19 | */ 20 | @Slf4j 21 | public class SmsNoticeSendComponent implements NoticeSendComponent {// 短信发送的组件 22 | 23 | private final CalmaValueResolver resolver; 24 | 25 | private final SmsClient smsClient; 26 | 27 | @Autowired 28 | private SmsMessage smsNotice; 29 | 30 | public SmsNoticeSendComponent(CalmaValueResolver resolver, Client client) { 31 | this.smsClient = (SmsClient) client; 32 | this.resolver = resolver; 33 | } 34 | 35 | @Override 36 | public void send(CommonNotice calmaNotice) { 37 | if (log.isDebugEnabled()){ 38 | log.info("--------------->>>>>sms<<<<<<<<-----------------------"); 39 | } 40 | Map map = this.toSmsMap(calmaNotice);// 将异常转换为短信模板结构 41 | smsNotice.setParam(map); 42 | smsClient.doSend(smsNotice); 43 | } 44 | 45 | private Map toSmsMap(CommonNotice calmaNotice) { 46 | Map map = null; 47 | try { 48 | map = new HashMap<>(); 49 | map.put("project",calmaNotice.getProjectName()); 50 | LocalDateTime time = calmaNotice.getCreateTime(); 51 | map.put("time",time.getYear()+"-"+time.getMonthValue()+"-"+time.getDayOfMonth()+" "+time.getHour()+":"+time.getMinute()+":"+time.getSecond()); 52 | for (String x : calmaNotice.getExceptionMessage()) { 53 | map.put("causeBy",x.substring(x.indexOf(":")+1)); 54 | } 55 | } catch (Exception e) { 56 | e.printStackTrace(); 57 | } 58 | return map; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/event/ExceptionEvent.java: -------------------------------------------------------------------------------- 1 | package com.marvin.event; 2 | 3 | import com.marvin.model.notice.CommonNotice; 4 | import org.springframework.context.ApplicationEvent; 5 | 6 | 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | 10 | @Getter 11 | @Setter 12 | /** 13 | * @Describe: 异常事件,将异常事件包装成异常事件 14 | * @Date: 2021/03/01 15 | * @Author: Marvin 16 | */ 17 | public class ExceptionEvent extends ApplicationEvent{//异常事件驱动器 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | private CommonNotice notice; 22 | 23 | public ExceptionEvent(Object source, CommonNotice calmaNotice) { 24 | super(source); 25 | this.notice = calmaNotice; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/event/listener/AbstractCalmaNotifier.java: -------------------------------------------------------------------------------- 1 | package com.marvin.event.listener; 2 | 3 | import com.marvin.event.ExceptionEvent; 4 | import com.marvin.model.notice.CommonNotice; 5 | import org.springframework.context.ApplicationListener; 6 | 7 | import com.marvin.context.support.component.NoticeSendComponent; 8 | 9 | public abstract class AbstractCalmaNotifier implements ApplicationListener{ 10 | 11 | private final NoticeSendComponent noticeSendComponent;//发送通知的组件 12 | 13 | public AbstractCalmaNotifier(NoticeSendComponent noticeSendComponent) { 14 | this.noticeSendComponent = noticeSendComponent;//钉钉或者SMS 15 | } 16 | 17 | @Override 18 | public void onApplicationEvent(ExceptionEvent event) {//CalmaEvent事件发布到applicationContext后,ApplicationListener一旦监听到该事件发布就调用本方法 19 | send(event.getNotice()); 20 | } 21 | 22 | private void send(CommonNotice notice) { 23 | noticeSendComponent.send(notice); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/event/listener/CalmaNotifier.java: -------------------------------------------------------------------------------- 1 | package com.marvin.event.listener; 2 | 3 | import com.marvin.model.notice.CommonNotice; 4 | import com.marvin.context.support.component.NoticeSendComponent; 5 | 6 | /** 7 | * @Describe: 将异常信息监听 8 | * @Date: 2021/03/01 9 | * @Author: Marvin 10 | */ 11 | public class CalmaNotifier extends AbstractCalmaNotifier{//异常监听器 12 | 13 | public CalmaNotifier(NoticeSendComponent noticeSendComponent ) { 14 | super(noticeSendComponent); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/factory/DingMessageFactory.java: -------------------------------------------------------------------------------- 1 | package com.marvin.factory; 2 | 3 | import com.marvin.model.message.DingMessage; 4 | import com.marvin.model.message.ding.DingAt; 5 | import com.marvin.model.message.ding.DingDingProperty; 6 | import com.marvin.model.message.ding.DingMarkdown; 7 | import com.marvin.model.message.ding.DingText; 8 | import org.springframework.util.StringUtils; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.stream.Collectors; 14 | 15 | public class DingMessageFactory { 16 | 17 | /** 18 | * dingDingProperty and text -> to fill DingMessage 19 | * @param dingDingProperty dingDingProperty 20 | * @param text notify content 21 | * @return {@link DingMessage} 22 | */ 23 | public static DingMessage createDingMessage(DingDingProperty dingDingProperty, String text){ 24 | DingMessage dingMessage = null; 25 | boolean isMarkDown = false; 26 | boolean isText = false; 27 | if (!StringUtils.isEmpty(dingDingProperty.getMsgtype()) && 28 | dingDingProperty.getMsgtype().equalsIgnoreCase("markdown")){ 29 | DingMarkdown markdown = new DingMarkdown(); 30 | markdown.setText(text); 31 | dingMessage = dingDingProperty.createDingdingMarkdownNotice(markdown); 32 | dingMessage.setMarkdown(markdown); 33 | isMarkDown = true; 34 | } 35 | if (!StringUtils.isEmpty(dingDingProperty.getMsgtype()) && 36 | dingDingProperty.getMsgtype().equalsIgnoreCase("text")) { 37 | DingText content = new DingText(); 38 | content.setContent(text); 39 | dingMessage = dingDingProperty.createDingdingTextNotice(content); 40 | dingMessage.setText(content); 41 | isText = true; 42 | } 43 | 44 | if (Objects.nonNull(dingMessage)){ 45 | 46 | String phoneNumbers = dingDingProperty.getPhoneNumbers(); 47 | String userIds = dingDingProperty.getUserIds(); 48 | 49 | DingAt dingAt = DingAt.builder() 50 | .isAtAll(dingDingProperty.isAtAll()) 51 | .atMobiles(!StringUtils.isEmpty(phoneNumbers)?dingDingProperty.getPhoneNumbers().split(","):null) 52 | .atUserIds(!StringUtils.isEmpty(userIds)?dingDingProperty.getUserIds().split(","):null) 53 | .build(); 54 | dingMessage.setAt(dingAt); 55 | 56 | if (isMarkDown){ 57 | DingMarkdown markdown = dingMessage.getMarkdown(); 58 | if (Objects.nonNull(markdown)){ 59 | StringBuilder stringBuilder = new StringBuilder(markdown.getText()); 60 | if (!StringUtils.isEmpty(phoneNumbers)) { 61 | Arrays.stream(phoneNumbers.split(",")).forEach(phone -> { 62 | stringBuilder.append("\r\n").append("@").append(phone); 63 | }); 64 | } 65 | if (!StringUtils.isEmpty(userIds)) { 66 | Arrays.stream(userIds.split(",")).forEach(userId -> { 67 | stringBuilder.append("\r\n").append("@").append(userId); 68 | }); 69 | } 70 | markdown.setText(stringBuilder.toString()); 71 | } 72 | } 73 | if (isText){ 74 | DingText dingText = dingMessage.getText(); 75 | if (Objects.nonNull(dingText)){ 76 | StringBuilder stringBuilder = new StringBuilder(dingText.getContent()); 77 | if (!StringUtils.isEmpty(phoneNumbers)) { 78 | Arrays.stream(dingDingProperty.getPhoneNumbers().split(",")).forEach(phone -> { 79 | stringBuilder.append("\r\n").append("@").append(phone); 80 | }); 81 | } 82 | if (!StringUtils.isEmpty(userIds)){ 83 | Arrays.stream(userIds.split(",")).forEach(userId->{ 84 | stringBuilder.append("\r\n").append("@").append(userId); 85 | }); 86 | } 87 | 88 | dingText.setContent(stringBuilder.toString()); 89 | } 90 | } 91 | } 92 | 93 | return dingMessage; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/factory/ExceptionStatisticDtoFactory.java: -------------------------------------------------------------------------------- 1 | package com.marvin.factory; 2 | 3 | import com.marvin.model.notice.CommonNotice; 4 | import com.marvin.statistic.dto.ExceptionStatisticDto; 5 | 6 | public class ExceptionStatisticDtoFactory { 7 | 8 | public static ExceptionStatisticDto createExceptionStatisticDto(CommonNotice commonNotice){ 9 | // ExceptionStatisticDto.builder(). 10 | return null; 11 | } 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/message/DingMessage.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.message; 2 | 3 | import com.marvin.model.message.ding.DingAt; 4 | import com.marvin.util.factory.SupportYamlPropertyFactory; 5 | import com.marvin.model.message.ding.DingText; 6 | import com.marvin.model.message.ding.DingMarkdown; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.boot.context.properties.ConfigurationProperties; 10 | import org.springframework.context.annotation.PropertySource; 11 | import org.springframework.stereotype.Component; 12 | 13 | import lombok.Data; 14 | 15 | @EqualsAndHashCode(callSuper = true) 16 | @Component 17 | @ConfigurationProperties(prefix = "calma.dingding") 18 | @PropertySource(value = "classpath:application.yml",factory = SupportYamlPropertyFactory.class) 19 | @Data 20 | @Slf4j 21 | /** 22 | * @Describe: 钉钉异常结构体 23 | * @Date: 2021/03/01 24 | * @Author: Marvin 25 | */ 26 | public class DingMessage extends Message{//最终给钉钉发送的结构体 27 | 28 | protected String msgtype;//发送的文本类型 29 | 30 | public DingMessage() { 31 | } 32 | 33 | public DingMessage(DingText text, String msgtype) { 34 | this.text = text; 35 | this.msgtype = msgtype; 36 | } 37 | 38 | public DingMessage(DingMarkdown markdown, String msgtype) { 39 | this.markdown = markdown; 40 | this.msgtype = msgtype; 41 | } 42 | 43 | 44 | private DingText text; 45 | 46 | private DingMarkdown markdown; 47 | 48 | private DingAt at; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/message/Message.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.message; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Message { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/message/SmsMessage.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.message; 2 | 3 | import com.marvin.util.factory.SupportYamlPropertyFactory; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.context.annotation.PropertySource; 8 | 9 | import java.util.Map; 10 | 11 | @Getter 12 | @Setter 13 | @ConfigurationProperties(prefix = "calma.sms") 14 | @PropertySource(value = "classpath:application.yml",factory = SupportYamlPropertyFactory.class) 15 | /** 16 | * @Describe: Sms异常结构体 17 | * @Date: 2021/03/01 18 | * @Author: Marvin 19 | */ 20 | public class SmsMessage extends Message{//最终发送短信的结构体 21 | 22 | private boolean enable; 23 | 24 | private String regionId;//阿里云的地区id 25 | 26 | private String accessKey;//阿里云账户的accessKey 27 | 28 | private String secret;//阿里云账户accessKey对应的密钥 29 | 30 | private String phoneNumbers;//接收短信的手机号 31 | 32 | private String signName;//阿里云短信的签名 33 | 34 | private String templateCode;//阿里云短信的模板码 35 | 36 | private Map param;//发送短信的参数 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/message/ding/DingAt.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.message.ding; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.util.List; 8 | 9 | @Data 10 | @Builder 11 | @Accessors(chain = true) 12 | public class DingAt { 13 | 14 | private String[] atMobiles; 15 | 16 | private String[] atUserIds; 17 | 18 | private boolean isAtAll; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/message/ding/DingDingProperty.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.message.ding; 2 | 3 | import com.marvin.util.factory.SupportYamlPropertyFactory; 4 | import com.marvin.model.message.DingMessage; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.PropertySource; 9 | import org.springframework.util.StringUtils; 10 | 11 | //@Component 12 | @ConfigurationProperties(prefix = "calma.dingding") 13 | @PropertySource(value = "classpath:application.yml",factory = SupportYamlPropertyFactory.class) 14 | @Data 15 | @Slf4j 16 | public class DingDingProperty { 17 | 18 | public DingDingProperty() { 19 | } 20 | 21 | public DingDingProperty(String access_token, String secret) { 22 | this.access_token = access_token; 23 | this.secret = secret; 24 | } 25 | 26 | public String getAccess_token() { 27 | return access_token; 28 | } 29 | 30 | public void setAccess_token(String access_token) { 31 | this.access_token = access_token; 32 | } 33 | 34 | public String getSecret() { 35 | return secret; 36 | } 37 | 38 | public void setSecret(String secret) { 39 | this.secret = secret; 40 | } 41 | 42 | private String access_token;//钉钉的accessToken 43 | 44 | private String secret;//密钥 45 | 46 | protected String phoneNumbers;//手机号 47 | 48 | protected String userIds;//手机号 49 | 50 | protected String isAtAll;//是否是通知所有的手机号 51 | 52 | protected boolean enable; 53 | 54 | protected String msgtype; 55 | 56 | public boolean isAtAll(){ 57 | return !StringUtils.isEmpty(isAtAll) && (isAtAll.equalsIgnoreCase("true")); 58 | } 59 | 60 | public DingMessage createDingdingTextNotice(DingText content) { 61 | return new DingMessage(content,msgtype); 62 | } 63 | 64 | //加入MarkDown 65 | public DingMessage createDingdingMarkdownNotice(DingMarkdown markdown) { 66 | return new DingMessage(markdown,msgtype); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/message/ding/DingMarkdown.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.message.ding; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class DingMarkdown { 9 | 10 | public DingMarkdown() { 11 | this.title = "项目异常通知"; 12 | } 13 | 14 | private String title; 15 | 16 | private String text; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/message/ding/DingText.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.message.ding; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | //@Component 9 | public class DingText {//给钉钉发送的内容消息 10 | 11 | public DingText() { 12 | } 13 | 14 | private String content; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/notice/CommonNotice.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.notice; 2 | 3 | import com.marvin.statistic.dto.ExceptionStatisticDto; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import org.springframework.format.annotation.DateTimeFormat; 7 | import org.springframework.util.CollectionUtils; 8 | import org.springframework.util.DigestUtils; 9 | 10 | import java.time.LocalDateTime; 11 | import java.time.format.DateTimeFormatter; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Objects; 16 | import java.util.stream.Collectors; 17 | 18 | /** 19 | * @Describe: 异常信息 20 | * @Date: 2021/03/01 21 | * @Author: Marvin 22 | */ 23 | @EqualsAndHashCode(callSuper = true) 24 | @Data 25 | public class CommonNotice extends Notice{//异常的结构体 26 | 27 | String title;//异常类名称 28 | 29 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 30 | LocalDateTime createTime = LocalDateTime.now();//通知时间 31 | 32 | String uid;//异常标识码 33 | 34 | String methodName;//方法名称 35 | 36 | String classPath;//类路径 37 | 38 | List params;//出现异常的方法参数 39 | 40 | List exceptionMessage;//出现异常的异常原因信息 41 | 42 | List traceInfos;//异常的追踪栈 43 | 44 | String projectName;//工程名称 45 | 46 | Throwable e; 47 | 48 | ExceptionStatisticDto exceptionStatisticDto; 49 | 50 | public CommonNotice(Throwable e, Object [] args, String projectName) { 51 | this.e = e; 52 | this.exceptionMessage = giveMeExceptionMessage(e); 53 | this.params = args==null?null:Arrays.stream(args).collect(Collectors.toList()); 54 | List list = stackTrace(e); 55 | if(list.size()>0) { 56 | this.traceInfos=list.stream().map(StackTraceElement::toString).collect(Collectors.toList()); 57 | this.methodName = list.get(0).getMethodName(); 58 | this.classPath =list.get(0).getClassName(); 59 | } 60 | this.projectName=projectName!=null&&projectName.length()>0?projectName:"工程名称未正确获取"; 61 | this.uid = generateUid(); 62 | } 63 | 64 | private String generateUid() {//生成异常的唯一标识码 65 | return DigestUtils.md5DigestAsHex(String.format("%s-%s",exceptionMessage,traceInfos.size()>0?traceInfos.get(0):"").getBytes()); 66 | } 67 | 68 | private List stackTrace(Throwable e) {//追踪异常栈信息 69 | ArrayList list = new ArrayList(); 70 | addStackTrace(list,e); 71 | Throwable cause = e.getCause(); 72 | while(cause!=null) { 73 | addStackTrace(list,cause); 74 | cause = cause.getCause(); 75 | } 76 | return list; 77 | } 78 | 79 | private void addStackTrace(ArrayList list, Throwable e) {//添加异常栈,过滤掉cglib动态生成的文件—— 80 | list.addAll(0,Arrays.stream(e.getStackTrace()).filter(x->!x.getFileName().equals("")).collect(Collectors.toList())); 81 | } 82 | 83 | private List giveMeExceptionMessage(Throwable exception) {//获取异常原因信息 84 | ArrayList list = new ArrayList(); 85 | giveMeExceptionMessage(exception,list); 86 | return list; 87 | } 88 | 89 | private void giveMeExceptionMessage(Throwable exception, ArrayList list) {//拼接异常字符串 异常 异常信息 90 | list.add(String.format("%s:%s",exception.getClass(),exception.getMessage())); 91 | Throwable cause = exception.getCause(); 92 | if(cause!=null) { 93 | giveMeExceptionMessage(cause,list); 94 | } 95 | } 96 | 97 | public String createText() {//将异常格式化返回——梦的美容院 98 | StringBuilder builder = new StringBuilder(); 99 | builder.append("工程名称:").append(projectName).append("\r\n"); 100 | builder.append("类路径:").append(classPath).append("\r\n"); 101 | builder.append("方法名称:").append(methodName).append("\r\n"); 102 | if(params!=null&&!CollectionUtils.isEmpty(params)&&//如果有参数追加参数到内容中 103 | params.stream().anyMatch(Objects::nonNull)) {//这里是为了防止有参的参数为null 104 | builder.append("参数信息:").append(String.join(",",params.stream().map(Object::toString).collect(Collectors.toList()))).append("\r\n"); 105 | } 106 | builder.append("异常信息:").append(String.join("cause by : \r\n", exceptionMessage)).append("\r\n"); 107 | builder.append("异常追踪:").append(String.join("\r\n",traceInfos)).append("\r\n"); 108 | builder.append("首次出现时间:").append(exceptionStatisticDto.getFirstAppearTime()).append("\r\n"); 109 | builder.append("出现次数:").append(exceptionStatisticDto.getCount()).append("\r\n"); 110 | builder.append("出现频率:").append(exceptionStatisticDto.getFrequency()).append("%").append("\r\n"); 111 | builder.append("出现时间:").append(createTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append("\r\n"); 112 | return builder.toString(); 113 | } 114 | 115 | public String createMarkdown(){ 116 | StringBuilder builder = new StringBuilder(); 117 | builder.append("#### 工程名称:").append(projectName).append("\r\n"); 118 | builder.append("##### 类路径:`").append(classPath).append("`\r\n"); 119 | builder.append("##### 方法名称:").append(methodName).append("\r\n"); 120 | if(params!=null&&!CollectionUtils.isEmpty(params)&&//如果有参数追加参数到内容中 121 | params.stream().anyMatch(Objects::nonNull)) {//这里是为了防止有参的参数为null 122 | builder.append("##### 参数信息:").append(params.stream().map(Object::toString).collect(Collectors.joining(","))).append("\r\n"); 123 | } 124 | builder.append("##### 异常信息:\r\n`").append(String.join("cause by : \r\n", exceptionMessage)).append("`\r\n"); 125 | builder.append("##### 异常追踪:\r\n`").append(String.join("`\r\n`",traceInfos)).append("`\r\n"); 126 | builder.append("###### 首次出现时间:").append(exceptionStatisticDto.getFirstAppearTime()).append("\r\n"); 127 | builder.append("###### 出现次数:").append(exceptionStatisticDto.getCount()).append("\r\n"); 128 | builder.append("###### 出现频率:").append(exceptionStatisticDto.getFrequency()).append("%").append("\r\n"); 129 | builder.append("###### 出现时间:").append(createTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append("\r\n"); 130 | return builder.toString(); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/notice/HttpNotice.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.notice; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.util.StringUtils; 6 | 7 | import java.time.format.DateTimeFormatter; 8 | import java.util.Map; 9 | import java.util.Objects; 10 | import java.util.stream.Collectors; 11 | 12 | import static java.util.stream.Collectors.toList; 13 | 14 | @Setter 15 | @Getter 16 | public class HttpNotice extends CommonNotice { 17 | protected String url; 18 | 19 | protected Map paramInfo; 20 | 21 | protected String requestBody; 22 | 23 | protected Map headers; 24 | 25 | protected String requestMethod; 26 | 27 | public HttpNotice(Throwable e, String projectName, String url, 28 | Map paramInfo, String requestBody, Map headers, String requestMethod) { 29 | super(e, null, projectName); 30 | this.url = url; 31 | this.paramInfo = paramInfo; 32 | this.requestBody = requestBody; 33 | this.headers = headers; 34 | this.requestMethod = requestMethod; 35 | } 36 | 37 | @Override 38 | public String createText() { 39 | StringBuilder stringBuilder = new StringBuilder(); 40 | stringBuilder.append("工程信息:").append(projectName).append("\r\n"); 41 | stringBuilder.append("接口地址:").append(url).append("\r\n"); 42 | if (paramInfo != null && paramInfo.size() > 0) { 43 | stringBuilder.append("接口参数:").append("\r\n") 44 | .append(String.join("\r\r", paramInfo.entrySet().stream() 45 | .map(x -> String.format("%s::%s", x.getKey(), x.getValue())).collect(toList()))) 46 | .append("\r\n"); 47 | } 48 | if (!StringUtils.isEmpty(requestBody)) { 49 | stringBuilder.append("请求体数据:").append(requestBody).append("\r\n"); 50 | } 51 | if (headers != null && headers.size() > 0) { 52 | stringBuilder.append("请求头:").append("\r\n"); 53 | stringBuilder.append(String.join(",\t", headers.entrySet().stream() 54 | .map(x -> String.format("%s::%s", x.getKey(), x.getValue())).collect(toList()))); 55 | stringBuilder.append("\r\n"); 56 | } 57 | stringBuilder.append("请求方法:").append(requestMethod).append("\r\n"); 58 | stringBuilder.append("类路径:").append(classPath).append("\r\n"); 59 | stringBuilder.append("方法名:").append(methodName).append("\r\n"); 60 | if (params != null && params.size() > 0 && 61 | params.stream().anyMatch(Objects::nonNull)) {//这里是为了防止有参请求的参数为null 62 | stringBuilder.append("参数信息:") 63 | .append(String.join("\t,\t", params.stream().map(x -> x.toString()).collect(toList()))) 64 | .append("\r\n"); 65 | } 66 | stringBuilder.append("异常信息:").append(String.join("\r\n caused by: ", exceptionMessage)).append("\r\n"); 67 | stringBuilder.append("异常追踪:").append("\r\n").append(String.join("\r\n", traceInfos)).append("\r\n"); 68 | stringBuilder.append("首次出现时间:").append(exceptionStatisticDto.getFirstAppearTime()).append("\r\n"); 69 | stringBuilder.append("出现次数:").append(exceptionStatisticDto.getCount()).append("\r\n"); 70 | stringBuilder.append("出现频率:").append(exceptionStatisticDto.getFrequency()).append("%").append("\r\n"); 71 | stringBuilder.append("最后一次出现时间:").append(createTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) 72 | .append("\r\n"); 73 | return stringBuilder.toString(); 74 | } 75 | 76 | public String createMarkdown() { 77 | StringBuilder stringBuilder = new StringBuilder(); 78 | stringBuilder.append("#### 工程信息:").append(projectName).append("\r\n"); 79 | stringBuilder.append("##### 接口地址:").append(url).append("\r\n"); 80 | if (paramInfo != null && paramInfo.size() > 0) { 81 | stringBuilder.append("##### 接口参数:").append("\r\n") 82 | .append(paramInfo.entrySet().stream() 83 | .map(x -> String.format("` %s : %s `", x.getKey(), x.getValue())).collect(Collectors.joining("\r\r"))) 84 | .append("\r\n"); 85 | } 86 | if (!StringUtils.isEmpty(requestBody)) { 87 | stringBuilder.append("##### 请求体数据:").append("\r\n`").append(requestBody).append("`\r\n"); 88 | } 89 | if (headers != null && headers.size() > 0) { 90 | stringBuilder.append("##### 请求头:").append("\r\n"); 91 | stringBuilder.append(String.join("\r\n", headers.entrySet().stream() 92 | .map(x -> String.format("` %s : %s `", x.getKey(), x.getValue())).collect(toList()))); 93 | stringBuilder.append("\r\n"); 94 | } 95 | stringBuilder.append("##### 请求方法:").append(requestMethod).append("\r\n"); 96 | stringBuilder.append("##### 类路径:`").append(classPath).append("`\r\n"); 97 | stringBuilder.append("##### 方法名:").append(methodName).append("\r\n"); 98 | if (params != null && params.size() > 0 && 99 | params.stream().anyMatch(Objects::nonNull)) {//这里是为了防止有参请求的参数为null 100 | stringBuilder.append("##### 参数信息:") 101 | .append(String.join("\t,\t", params.stream().map(Object::toString).collect(toList()))) 102 | .append("\r\n"); 103 | } 104 | stringBuilder.append("##### 异常信息:`").append(String.join("\r\n caused by: ", exceptionMessage)).append("`\r\n"); 105 | stringBuilder.append("##### 异常追踪:").append("\r\n`").append(String.join("`\r\n`", traceInfos)).append("`\r\n"); 106 | stringBuilder.append("###### 首次出现时间:").append(exceptionStatisticDto.getFirstAppearTime()).append("\r\n"); 107 | stringBuilder.append("###### 出现次数:").append(exceptionStatisticDto.getCount()).append("\r\n"); 108 | stringBuilder.append("###### 出现频率:").append(exceptionStatisticDto.getFrequency()).append("%").append("\r\n"); 109 | stringBuilder.append("###### 最后一次出现时间:").append(createTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) 110 | .append("\r\n"); 111 | return stringBuilder.toString(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/notice/Notice.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.notice; 2 | 3 | /** 4 | * @Describe: 异常信息父类,以后能不能在这里面抽取一些公共的结构 5 | * @Date: 2021/03/01 6 | * @Author: Marvin 7 | */ 8 | public class Notice { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/model/web/WebNoticeInfo.java: -------------------------------------------------------------------------------- 1 | package com.marvin.model.web; 2 | 3 | import com.marvin.util.factory.SupportYamlPropertyFactory; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.PropertySource; 6 | 7 | @ConfigurationProperties(prefix = "calma.exceptionnotice") 8 | @PropertySource(value = "classpath:application.yml",factory = SupportYamlPropertyFactory.class) 9 | public class WebNoticeInfo { 10 | 11 | public boolean isEnabled() { 12 | return enabled; 13 | } 14 | 15 | public void setEnabled(boolean enabled) { 16 | this.enabled = enabled; 17 | } 18 | 19 | public boolean isAuto() { 20 | return auto; 21 | } 22 | 23 | public void setAuto(boolean auto) { 24 | this.auto = auto; 25 | } 26 | 27 | private boolean enabled; 28 | 29 | private boolean auto; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/resolver/CalmaValueResolver.java: -------------------------------------------------------------------------------- 1 | package com.marvin.resolver; 2 | 3 | import com.marvin.model.notice.CommonNotice; 4 | 5 | @FunctionalInterface 6 | public interface CalmaValueResolver { 7 | 8 | public String resolve(T exceptionNotice); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/statistic/cache/StatisticHelper.java: -------------------------------------------------------------------------------- 1 | package com.marvin.statistic.cache; 2 | 3 | import com.marvin.model.notice.CommonNotice; 4 | import com.marvin.statistic.dto.ExceptionStatisticDto; 5 | import lombok.Data; 6 | 7 | import java.math.BigDecimal; 8 | import java.math.RoundingMode; 9 | import java.time.format.DateTimeFormatter; 10 | import java.util.Map; 11 | import java.util.Objects; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | @Data 15 | public class StatisticHelper { 16 | 17 | /** 18 | * 异常持续时长 19 | */ 20 | private final long keepDuration; 21 | 22 | public StatisticHelper(long keepDuration) { 23 | this.keepDuration = keepDuration; 24 | } 25 | 26 | /** 27 | * Exception all place name,val:dto 28 | */ 29 | private static final Map CACHE = new ConcurrentHashMap<>(); 30 | 31 | // private static final Map HTTP = new ConcurrentHashMap<>(); 32 | 33 | // private static final ThreadLocal> MICRO_SERVICE = new ThreadLocal<>(); 34 | 35 | /** 36 | * 按指定天 统计异常 37 | * @param key 异常名称 38 | * @return 异常dto 39 | */ 40 | public static ExceptionStatisticDto get(String key){ 41 | return CACHE.get(key); 42 | } 43 | 44 | public static void common(CommonNotice commonNotice) { 45 | String className = commonNotice.getE().getClass().getName(); 46 | CACHE.put(className, statisticCommon(className, commonNotice)); 47 | commonNotice.setExceptionStatisticDto(StatisticHelper.get(commonNotice.getE().getClass().getName())); 48 | } 49 | 50 | /** 51 | * 统计通用通知信息 52 | * @param className 类名称 53 | * @param commonNotice 通用通知 54 | * @return 统计dto 55 | */ 56 | private static ExceptionStatisticDto statisticCommon(String className, CommonNotice commonNotice) { 57 | ExceptionStatisticDto.ExceptionStatisticDtoBuilder builder = ExceptionStatisticDto.builder(); 58 | ExceptionStatisticDto exceptionStatisticDto = CACHE.get(className); 59 | long count = CACHE.values().stream().mapToLong(ExceptionStatisticDto::getCount).sum(); 60 | BigDecimal frequency; 61 | if (Objects.isNull(exceptionStatisticDto)) { 62 | builder.firstAppearTime(commonNotice.getCreateTime()); 63 | builder.count(1); 64 | frequency = new BigDecimal(1).divide(new BigDecimal(count+1), 2, RoundingMode.HALF_UP); 65 | }else { 66 | builder.firstAppearTime(exceptionStatisticDto.getFirstAppearTime()); 67 | builder.count(exceptionStatisticDto.getCount()+1); 68 | frequency = new BigDecimal(exceptionStatisticDto.getCount()+1).divide(new BigDecimal(count+1), 2, RoundingMode.HALF_UP); 69 | } 70 | frequency = frequency.multiply(new BigDecimal(100)); 71 | builder.frequency(frequency); 72 | builder.throwable(commonNotice.getE()); 73 | return builder.build(); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/statistic/dto/ExceptionStatisticDto.java: -------------------------------------------------------------------------------- 1 | package com.marvin.statistic.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.math.BigDecimal; 8 | import java.time.LocalDateTime; 9 | 10 | /** 11 | * 异常统计dto 12 | * @author marvin 13 | */ 14 | @Data 15 | @Builder 16 | @Accessors(chain = true) 17 | public class ExceptionStatisticDto { 18 | 19 | /** 20 | * 异常 21 | */ 22 | private Throwable throwable; 23 | 24 | /** 25 | * 当前异常出现频率 26 | */ 27 | private BigDecimal frequency; 28 | 29 | /** 30 | * 当前异常出现次数 31 | */ 32 | private long count; 33 | 34 | /** 35 | * 当前异常第一次出现时间 36 | */ 37 | private LocalDateTime firstAppearTime; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/util/DingClientFeign.java: -------------------------------------------------------------------------------- 1 | package com.marvin.util; 2 | 3 | import java.util.Map; 4 | 5 | import com.marvin.model.message.DingMessage; 6 | import com.marvin.util.JsonExpander; 7 | 8 | import feign.Body; 9 | import feign.Headers; 10 | import feign.Param; 11 | import feign.QueryMap; 12 | import feign.RequestLine; 13 | 14 | public interface DingClientFeign { 15 | 16 | @RequestLine("POST /send?access_token={accessToken}") 17 | @Headers("Content-Type: application/json; charset=utf-8") 18 | @Body("{body}") 19 | Object post(@Param("accessToken") String accessToken, 20 | @Param(value = "body", expander = JsonExpander.class) DingMessage body, 21 | @QueryMap Map map);//调钉钉机器人的接口 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/util/JsonExpander.java: -------------------------------------------------------------------------------- 1 | package com.marvin.util; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import feign.Param.Expander; 6 | 7 | public class JsonExpander implements Expander{ 8 | 9 | private final Gson gson = new Gson(); 10 | 11 | @Override 12 | public String expand(Object value) { 13 | return gson.toJson(value); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/util/NumberUtils.java: -------------------------------------------------------------------------------- 1 | package com.marvin.util; 2 | 3 | import com.marvin.common.constant.ExceptionConstant; 4 | import com.marvin.common.exception.internal.TypeConvertException; 5 | import org.springframework.util.StringUtils; 6 | 7 | public class NumberUtils { 8 | 9 | public static long strToLong(String number, String varName) { 10 | if (StringUtils.isEmpty(varName)) { 11 | throw new RuntimeException(ExceptionConstant.Internal.internalException("101718")); 12 | } 13 | if (StringUtils.isEmpty(number)) { 14 | throw new NullPointerException(String.format(ExceptionConstant.VARIABLE_IS_NULL, varName)); 15 | } 16 | Long value = null; 17 | try { 18 | value = Long.valueOf(number); 19 | } catch (NumberFormatException e) { 20 | throw new TypeConvertException(ExceptionConstant.typeConvertErrorText(String.class.getName(), Long.class.getName(),varName)); 21 | } 22 | return value; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/util/StreamUtils.java: -------------------------------------------------------------------------------- 1 | package com.marvin.util; 2 | 3 | import java.util.Collection; 4 | import java.util.function.Function; 5 | import java.util.stream.Collectors; 6 | 7 | public class StreamUtils { 8 | 9 | public static Collection toList(Collection collection, Function function){ 10 | return collection.stream().map(function).collect(Collectors.toList()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/util/factory/SupportYamlPropertyFactory.java: -------------------------------------------------------------------------------- 1 | package com.marvin.util.factory; 2 | 3 | import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; 4 | import org.springframework.core.env.PropertiesPropertySource; 5 | import org.springframework.core.env.PropertySource; 6 | import org.springframework.core.io.support.EncodedResource; 7 | import org.springframework.core.io.support.PropertySourceFactory; 8 | 9 | import java.io.IOException; 10 | import java.util.Properties; 11 | 12 | /** 13 | * 啊,我佛了,SpringBoot都支持yml文件了但是这个@PropertySource不支持,,,还得自己实现 14 | * 本类是为了让这个注解支持读取yaml文件 15 | */ 16 | public class SupportYamlPropertyFactory implements PropertySourceFactory { 17 | 18 | @Override 19 | public PropertySource createPropertySource(String name, EncodedResource resource) throws IOException { 20 | YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean(); 21 | factoryBean.setResources(resource.getResource()); 22 | factoryBean.afterPropertiesSet(); 23 | Properties properties = factoryBean.getObject(); 24 | String propertiesName = name != null ? name : resource.getResource().getFilename(); 25 | assert propertiesName != null; 26 | assert properties != null; 27 | return new PropertiesPropertySource(propertiesName, properties); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/util/web/bridge/CacheBridge.java: -------------------------------------------------------------------------------- 1 | package com.marvin.util.web.bridge; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | public class CacheBridge { 7 | 8 | private static final Set canBeNotifiedMethods = new HashSet<>(); 9 | 10 | private static boolean IS_NOT_INITIALED = false; 11 | 12 | public static void addAll(Set set) { 13 | canBeNotifiedMethods.addAll(set); 14 | } 15 | 16 | public static Set getNotifiedMethods() { 17 | IS_NOT_INITIALED = true; 18 | return canBeNotifiedMethods; 19 | } 20 | 21 | public static boolean init() { 22 | return IS_NOT_INITIALED; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/web/interceptor/ClearBodyInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.marvin.web.interceptor; 2 | 3 | import com.marvin.web.resolver.CurrentRequestBodyResolver; 4 | import lombok.Data; 5 | import org.springframework.web.servlet.HandlerInterceptor; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | @Data 11 | public class ClearBodyInterceptor implements HandlerInterceptor { 12 | 13 | private CurrentRequestBodyResolver currentRequestBodyResolver; 14 | 15 | public ClearBodyInterceptor(CurrentRequestBodyResolver currentRequestBodyResolver) { 16 | this.currentRequestBodyResolver = currentRequestBodyResolver; 17 | } 18 | 19 | @Override 20 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 21 | currentRequestBodyResolver.remove(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/web/resolver/CalmaExceptionHandlerResolver.java: -------------------------------------------------------------------------------- 1 | package com.marvin.web.resolver; 2 | 3 | import com.marvin.common.enumeration.ExceptionType; 4 | import com.marvin.common.exception.NoSuchHttpRequestMethodException; 5 | import com.marvin.config.anno.CalmaExceptionListener; 6 | import com.marvin.context.DefaultNoticeContext; 7 | import com.marvin.context.HttpNoticeContext; 8 | import com.marvin.model.notice.HttpNotice; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.method.HandlerMethod; 11 | import org.springframework.web.servlet.HandlerExceptionResolver; 12 | import org.springframework.web.servlet.ModelAndView; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.util.Arrays; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | import java.util.Objects; 20 | 21 | import static com.marvin.common.enumeration.ExceptionType.UNABLE_FIND_CONTROLLER; 22 | 23 | 24 | @Component 25 | public class CalmaExceptionHandlerResolver implements HandlerExceptionResolver { 26 | 27 | private final HttpNoticeContext httpNoticeContext; 28 | 29 | private final CurrentRequestHeaderResolver currentRequestHeaderResolver; 30 | 31 | private final CurrentRequestBodyResolver currentRequestBodyResolver; 32 | 33 | public CalmaExceptionHandlerResolver(HttpNoticeContext httpNoticeContext, 34 | CurrentRequestHeaderResolver currentRequestHeaderResolver, 35 | CurrentRequestBodyResolver currentRequestBodyResolver) { 36 | this.httpNoticeContext = httpNoticeContext; 37 | this.currentRequestHeaderResolver = currentRequestHeaderResolver; 38 | this.currentRequestBodyResolver = currentRequestBodyResolver; 39 | } 40 | 41 | @Override 42 | public ModelAndView resolveException(HttpServletRequest request, 43 | HttpServletResponse response, 44 | Object handler, Exception ex) { 45 | RuntimeException e = null; 46 | if (ex instanceof RuntimeException) { 47 | e = (RuntimeException) ex; 48 | } 49 | HandlerMethod handlerMethod = null; 50 | if (handler instanceof HandlerMethod) { 51 | handlerMethod = (HandlerMethod) handler; 52 | } 53 | CalmaExceptionListener listener = getListener(handlerMethod); 54 | //创建通知 55 | if (e != null ) { 56 | if (listener != null || httpNoticeContext.isAuto()) { 57 | HttpNotice httpNotice = httpNoticeContext.createNotice(e, request.getRequestURI(), getParams(request), getRequestBody(), getHeader(request), getMethod(request)); 58 | 59 | } 60 | } 61 | return null; 62 | } 63 | 64 | private Map getHeader(HttpServletRequest request) { 65 | return currentRequestHeaderResolver.headers(request); 66 | } 67 | 68 | private String getRequestBody() { 69 | return currentRequestBodyResolver.getRequestBody(); 70 | } 71 | 72 | private Map getParams(HttpServletRequest request) { 73 | Map headers = new HashMap<>(); 74 | request.getParameterMap().forEach((x, y) -> 75 | headers.put(x, String.join(" , ", Arrays.asList(y))) 76 | ); 77 | return headers; 78 | } 79 | 80 | private String getMethod(HttpServletRequest request) { 81 | String method; 82 | try { 83 | method = request.getMethod(); 84 | } catch (Exception e) { 85 | throw new NoSuchHttpRequestMethodException(ExceptionType.ERROR_OBTAINING_METHOD, e); 86 | } 87 | return method; 88 | } 89 | 90 | private CalmaExceptionListener getListener(HandlerMethod handlerMethod) { 91 | if (Objects.isNull(handlerMethod)) { 92 | throw new NoSuchHttpRequestMethodException(UNABLE_FIND_CONTROLLER); 93 | } 94 | //获取请求方法上的注解 95 | CalmaExceptionListener listener = handlerMethod.getMethodAnnotation(CalmaExceptionListener.class); 96 | //如果请求方法上获取不到,那么去看看是不是在类级别 97 | if (Objects.isNull(listener)) { 98 | listener = handlerMethod.getBeanType().getAnnotation(CalmaExceptionListener.class); 99 | } 100 | return listener; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/web/resolver/CurrentRequestBodyResolver.java: -------------------------------------------------------------------------------- 1 | package com.marvin.web.resolver; 2 | 3 | import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice; 4 | 5 | public interface CurrentRequestBodyResolver extends RequestBodyAdvice { 6 | 7 | default String getRequestBody() { 8 | return ""; 9 | } 10 | 11 | void remove(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/web/resolver/CurrentRequestHeaderResolver.java: -------------------------------------------------------------------------------- 1 | package com.marvin.web.resolver; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import java.util.Enumeration; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public interface CurrentRequestHeaderResolver { 9 | 10 | default Map headers(HttpServletRequest request) { 11 | Map headers = new HashMap<>(); 12 | Enumeration enumeration = request.getHeaderNames(); 13 | while (enumeration.hasMoreElements()) { 14 | String header = enumeration.nextElement(); 15 | headers.put(header, request.getHeader(header)); 16 | } 17 | return headers; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/web/resolver/DefaultRequestBodyResolver.java: -------------------------------------------------------------------------------- 1 | package com.marvin.web.resolver; 2 | 3 | import com.marvin.config.anno.CalmaExceptionListener; 4 | import org.springframework.core.MethodParameter; 5 | import org.springframework.http.HttpInputMessage; 6 | import org.springframework.http.converter.HttpMessageConverter; 7 | import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter; 8 | 9 | import java.lang.reflect.Type; 10 | 11 | public class DefaultRequestBodyResolver extends RequestBodyAdviceAdapter implements CurrentRequestBodyResolver { 12 | 13 | private ThreadLocal currentRequestBodyInfo = ThreadLocal.withInitial(() -> ""); 14 | 15 | @Override 16 | public void remove() { 17 | currentRequestBodyInfo.remove(); 18 | } 19 | 20 | @Override 21 | public String getRequestBody() { 22 | return currentRequestBodyInfo.get(); 23 | } 24 | 25 | @Override 26 | public boolean supports(MethodParameter methodParameter, Type tragetType, 27 | Class> converterType) { 28 | return methodParameter.hasMethodAnnotation(CalmaExceptionListener.class) || 29 | methodParameter.getContainingClass().isAnnotationPresent(CalmaExceptionListener.class); 30 | } 31 | 32 | @Override 33 | public Object afterBodyRead(Object requestBody, HttpInputMessage httpInputMessage, 34 | MethodParameter methodParameter, Type targetType, 35 | Class> converterType) { 36 | StringBuilder sb = new StringBuilder(requestBody.toString()); 37 | String body = ""; 38 | if (sb.length() > 500) { 39 | body = sb.substring(0, 500) + "..."; 40 | } else { 41 | body = sb.toString(); 42 | } 43 | currentRequestBodyInfo.set(body); 44 | return body; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/marvin/web/resolver/DefaultRequestHeaderResolver.java: -------------------------------------------------------------------------------- 1 | package com.marvin.web.resolver; 2 | 3 | public class DefaultRequestHeaderResolver implements CurrentRequestHeaderResolver{ 4 | } 5 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.marvin.config.CommonNoticeContextConfiguration,\ 3 | com.marvin.config.notice.AopConfiguration,\ 4 | com.marvin.config.notice.CalmaNoticeConfiguration,\ 5 | com.marvin.config.ListenerConfiguration,\ 6 | com.marvin.config.web.WebExceptionListenConfiguration,\ 7 | com.marvin.config.SmsClientConfiguration,\ 8 | com.marvin.config.SmsComponentConfiguration,\ 9 | com.marvin.config.DingComponentConfiguration,\ 10 | com.marvin.config.TextExceptionNoticeSendConfiguration,\ 11 | com.marvin.config.DingDingClientConfiguration,\ 12 | com.marvin.config.MarkdownExceptionNoticeSendConfiguration,\ 13 | com.marvin.config.web.load.WebAutoLoadConfiguration,\ 14 | com.marvin.config.WebNoticeContextConfiguration -------------------------------------------------------------------------------- /src/test/java/com/marvin/CommonTestProjectApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.marvin; 2 | 3 | import com.marvin.testModel.TestCalma; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | 9 | @SpringBootTest 10 | @EnableAutoConfiguration 11 | class CommonTestProjectApplicationTests { 12 | 13 | // @Autowired 14 | // private TestCalma testCalma; 15 | // 16 | // @Test 17 | // public void test1() { 18 | // testCalma.test11("11"); 19 | // } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/marvin/testModel/TestCalma.java: -------------------------------------------------------------------------------- 1 | package com.marvin.testModel; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | 6 | @Component 7 | public class TestCalma { 8 | // @CalmaExceptionListener 9 | // public void test11(String name) { 10 | // System.out.println("name:"+name); 11 | // new IException(); 12 | // } 13 | // 14 | // class IException{ 15 | // public IException() { 16 | // get(); 17 | // } 18 | // public void get() { 19 | // int i = 5/0; 20 | // } 21 | // } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/marvin/testModel/TestController.java: -------------------------------------------------------------------------------- 1 | package com.marvin.testModel; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | @RequestMapping("/") 10 | public class TestController { 11 | 12 | // @Autowired 13 | // private TestCalma testCalma; 14 | // 15 | // @GetMapping("/test") 16 | // public void testControl(){ 17 | // testCalma.test11("dsa"); 18 | // } 19 | } 20 | --------------------------------------------------------------------------------