├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── tingyugetc520 │ └── ali │ └── dingtalk │ ├── api │ ├── BaseService.java │ ├── DtAgentService.java │ ├── DtCorpConversationMessageService.java │ ├── DtDepartmentService.java │ ├── DtOAuth2Service.java │ ├── DtService.java │ ├── DtUserService.java │ └── impl │ │ ├── BaseDtServiceImpl.java │ │ ├── DtAgentServiceImpl.java │ │ ├── DtCorpConversationMessageServiceImpl.java │ │ ├── DtDepartmentServiceImpl.java │ │ ├── DtOAuth2ServiceImpl.java │ │ ├── DtServiceImpl.java │ │ ├── DtServiceOkHttpImpl.java │ │ └── DtUserServiceImpl.java │ ├── bean │ ├── DtAccessToken.java │ ├── agent │ │ └── DtAgentAuthScope.java │ ├── department │ │ └── DtDepart.java │ ├── message │ │ ├── DtCorpConversationMessage.java │ │ ├── DtCorpConversationMsgSendResult.java │ │ ├── DtCorpConversationRecall.java │ │ ├── DtCorpConversationStatusBarUpdate.java │ │ ├── DtEventMessage.java │ │ ├── DtEventOutMessage.java │ │ ├── DtMessage.java │ │ └── builder │ │ │ ├── ActionCardBuilder.java │ │ │ ├── BaseBuilder.java │ │ │ ├── FileBuilder.java │ │ │ ├── ImageBuilder.java │ │ │ ├── LinkBuilder.java │ │ │ ├── MarkdownBuilder.java │ │ │ ├── OABuilder.java │ │ │ ├── TextBuilder.java │ │ │ └── VoiceBuilder.java │ ├── oauth │ │ └── DtOauth2UserInfo.java │ └── user │ │ ├── DtUnionId2UserId.java │ │ ├── DtUser.java │ │ └── DtUserV2.java │ ├── config │ ├── DtConfigStorage.java │ └── impl │ │ └── DtDefaultConfigImpl.java │ ├── constant │ ├── DtApiPathConstant.java │ └── DtConstant.java │ ├── error │ ├── DtError.java │ ├── DtErrorException.java │ ├── DtErrorMsgEnum.java │ ├── DtRuntimeErrorEnum.java │ └── DtRuntimeException.java │ ├── message │ ├── DtErrorExceptionHandler.java │ ├── DtMessageHandler.java │ ├── DtMessageInterceptor.java │ ├── DtMessageMatcher.java │ ├── DtMessageRouter.java │ ├── DtMessageRouterRule.java │ └── processor │ │ ├── DtCheckUrlMessageHandler.java │ │ └── DtLogExceptionHandler.java │ └── util │ ├── DataUtils.java │ ├── DtConstantUtils.java │ ├── crypto │ └── DtCryptUtil.java │ ├── http │ ├── HttpProxyType.java │ ├── OkHttpProxyInfo.java │ ├── OkHttpSimpleGetRequestExecutor.java │ ├── OkHttpSimplePostRequestExecutor.java │ ├── RequestExecutor.java │ ├── ResponseHandler.java │ └── ResponseHttpStatusInterceptor.java │ └── json │ ├── DtDateAdapter.java │ ├── DtErrorAdapter.java │ ├── DtGsonBuilder.java │ ├── DtUserAdapter.java │ ├── GsonHelper.java │ └── GsonParser.java └── test ├── java └── com │ └── github │ └── tingyugetc520 │ └── ali │ └── dingtalk │ ├── .DS_Store │ ├── bean │ └── user │ │ └── DtUserTest.java │ └── demo │ ├── DtDemoApp.java │ ├── DtDemoConfigStorage.java │ └── servlet │ └── DtEndpointServlet.java └── resources └── test-config.sample.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/java,java-web 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=java,java-web 3 | 4 | ### Java ### 5 | # Compiled class file 6 | *.class 7 | 8 | # Log file 9 | *.log 10 | 11 | # BlueJ files 12 | *.ctxt 13 | 14 | # Mobile Tools for Java (J2ME) 15 | .mtj.tmp/ 16 | 17 | # Package Files # 18 | *.jar 19 | *.war 20 | *.nar 21 | *.ear 22 | *.zip 23 | *.tar.gz 24 | *.rar 25 | 26 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 27 | hs_err_pid* 28 | 29 | ### Java-Web ### 30 | ## ignoring target file 31 | target/ 32 | 33 | # End of https://www.toptal.com/developers/gitignore/api/java,java-web 34 | 35 | ### IntelliJ ### 36 | out/ 37 | .idea/ 38 | *.iml 39 | 40 | test-config.json 41 | !maven-repo/**/*.jar 42 | DtDemoAppTest.java -------------------------------------------------------------------------------- /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 | # DtJava 2 | 3 | ## 介绍 4 | DtJava(DingTalk Java SDK-钉钉SDK) 封装了钉钉凭证、通讯录管理、消息通知等服务端接口,让开发者可以使用简单的配置,提供简洁的 API 以供方便快速地调用钉钉接口。 5 | 6 | 注意:目前SDK主要是以企业内建应用为主,ISV应用后面会陆续支持。 7 | 8 | ## 文档 9 | 查看[wiki首页](https://github.com/tingyugetc520/DtJava/wiki) 10 | 11 | Demo示例可 [查看DEMO项目](https://github.com/tingyugetc520/dtjava-demo) ,包括事件消息回调处理等等。 12 | 13 | ## 安装 14 | * 通过Maven方式安装使用 15 | ```xml 16 | 17 | com.github.tingyugetc520 18 | dt-java 19 | 0.1.2 20 | 21 | ``` 22 | 目前已发布0.1.2版本 23 | 24 | * 直接下载源码使用 25 | ```git 26 | git clone https://github.com/tingyugetc520/DtJava.git 27 | ``` 28 | 源码下载完成后,放置在您的项目中使用,同时请注意相关依赖的问题。 29 | 30 | ## 使用 31 | 可前往[查看DEMO项目](https://github.com/tingyugetc520/dtjava-demo) 32 | ```java 33 | // 钉钉应用配置 34 | DtDefaultConfigImpl config = new DtDefaultConfigImpl(); 35 | config.setCorpId("corpId"); 36 | config.setAgentId(agentId); 37 | config.setAppKey("appKey"); 38 | config.setAppSecret("appSecret"); 39 | 40 | // DtService为SDK使用入口,后续接口使用均需要DtService 41 | DtServiceImpl dtService = new DtServiceImpl(); 42 | dtService.setDtConfigStorage(config); 43 | 44 | // 查询用户 45 | DtUser user = dtService.getUserService().getById(userId); 46 | log.info("dt user:{}", user); 47 | 48 | // 发送工作消息通知 49 | DtCorpConversationMessage message = DtCorpConversationMessage.builder() 50 | .agentId(config.getAgentId()) 51 | .userIds(Lists.newArrayList("userId")) 52 | .msg(DtMessage.TEXT().content("this is content").build()) 53 | .build(); 54 | dtService.getCorpConversationMsgService().send(message); 55 | ``` 56 | 更多的示例可 [查看DEMO项目](https://github.com/tingyugetc520/dtjava-demo) ,包括事件消息回调处理等等。 57 | 58 | ## 封装进度(企业内建应用) 59 | - [x] HTTP代理,支持正向代理与反向代理 60 | - [x] 获取凭证 61 | - [x] 身份验证 62 | - [x] 通讯录管理(只读) 63 | - [x] 消息通知-工作消息通知 64 | - [x] HTTP事件回调 65 | 66 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.tingyugetc520 8 | dt-java 9 | 0.1.2 10 | 11 | DtJava - DingTalk Java SDK 12 | 钉钉Java SDK 13 | jar 14 | https://github.com/tingyugetc520/DtJava 15 | 16 | 17 | 1.8 18 | 1.8 19 | UTF-8 20 | 21 | 4.5 22 | 9.4.35.v20201120 23 | 24 | 25 | 26 | 27 | org.jodd 28 | jodd-http 29 | 5.2.0 30 | provided 31 | 32 | 33 | com.squareup.okhttp3 34 | okhttp 35 | 4.5.0 36 | 37 | 38 | 39 | org.apache.httpcomponents 40 | httpclient 41 | ${httpclient.version} 42 | 43 | 44 | org.apache.httpcomponents 45 | httpmime 46 | ${httpclient.version} 47 | 48 | 49 | commons-codec 50 | commons-codec 51 | 1.10 52 | 53 | 54 | commons-io 55 | commons-io 56 | 2.5 57 | 58 | 59 | org.apache.commons 60 | commons-lang3 61 | 3.10 62 | 63 | 64 | org.apache.commons 65 | commons-collections4 66 | 4.4 67 | 68 | 69 | org.slf4j 70 | slf4j-api 71 | 1.7.30 72 | 73 | 74 | com.thoughtworks.xstream 75 | xstream 76 | 1.4.15 77 | 78 | 79 | com.google.guava 80 | guava 81 | 29.0-jre 82 | 83 | 84 | com.google.code.gson 85 | gson 86 | 2.8.0 87 | 88 | 89 | 90 | 91 | joda-time 92 | joda-time 93 | 2.10.6 94 | test 95 | 96 | 97 | ch.qos.logback 98 | logback-classic 99 | 1.2.3 100 | test 101 | 102 | 103 | com.google.inject 104 | guice 105 | 4.2.3 106 | test 107 | 108 | 109 | org.testng 110 | testng 111 | 7.1.0 112 | test 113 | 114 | 115 | org.mockito 116 | mockito-all 117 | 1.10.19 118 | test 119 | 120 | 121 | org.eclipse.jetty 122 | jetty-server 123 | ${jetty.version} 124 | test 125 | 126 | 127 | org.eclipse.jetty 128 | jetty-servlet 129 | ${jetty.version} 130 | test 131 | 132 | 133 | org.assertj 134 | assertj-guava 135 | 3.0.0 136 | test 137 | 138 | 139 | com.github.dreamhead 140 | moco-runner 141 | 1.1.0 142 | test 143 | 144 | 145 | 146 | redis.clients 147 | jedis 148 | 3.3.0 149 | provided 150 | 151 | 152 | com.github.jedis-lock 153 | jedis-lock 154 | 1.0.0 155 | provided 156 | 157 | 158 | org.redisson 159 | redisson 160 | 3.12.0 161 | true 162 | provided 163 | 164 | 165 | org.projectlombok 166 | lombok 167 | 1.18.8 168 | provided 169 | 170 | 171 | 172 | 173 | 174 | ossrh 175 | https://s01.oss.sonatype.org/content/repositories/snapshots 176 | 177 | 178 | ossrh 179 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 180 | 181 | 182 | 183 | 184 | 185 | The Apache License, Version 2.0 186 | http://www.apache.org/licenses/LICENSE-2.0.txt 187 | 188 | 189 | 190 | 191 | 192 | tingyugetc520 193 | https://github.com/tingyugetc520 194 | 195 | 196 | 197 | 198 | https://github.com/tingyugetc520/DtJava 199 | scm:git:https://github.com/tingyugetc520/DtJava.git 200 | scm:git:https://github.com/tingyugetc520/DtJava.git 201 | 202 | 203 | 204 | 205 | 206 | org.apache.maven.plugins 207 | maven-source-plugin 208 | 3.2.0 209 | 210 | 211 | attach-sources 212 | package 213 | 214 | jar-no-fork 215 | 216 | 217 | 218 | 219 | 220 | org.apache.maven.plugins 221 | maven-javadoc-plugin 222 | 3.2.0 223 | 224 | 225 | attach-javadocs 226 | package 227 | 228 | jar 229 | 230 | 231 | 232 | 233 | 234 | org.apache.maven.plugins 235 | maven-gpg-plugin 236 | 1.6 237 | 238 | 239 | sign-artifacts 240 | verify 241 | 242 | sign 243 | 244 | 245 | 246 | 247 | 248 | org.sonatype.plugins 249 | nexus-staging-maven-plugin 250 | 1.6.7 251 | true 252 | 253 | ossrh 254 | https://s01.oss.sonatype.org/ 255 | true 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | huawei-maven 264 | https://repo.huaweicloud.com/repository/maven 265 | 266 | 267 | ali-maven 268 | https://maven.aliyun.com/repository/public 269 | 270 | 271 | central 272 | https://repo.maven.apache.org/maven2 273 | 274 | 275 | 276 | 277 | 278 | huawei-plugin-maven 279 | https://repo.huaweicloud.com/repository/maven 280 | 281 | 282 | ali-plugin-maven 283 | https://maven.aliyun.com/repository/public 284 | 285 | 286 | central-plugin 287 | https://repo.maven.apache.org/maven2 288 | 289 | 290 | 291 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/BaseService.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 4 | import com.google.gson.JsonObject; 5 | 6 | public interface BaseService { 7 | /** 8 | * 当本Service没有实现某个API的时候,可以用这个,针对所有钉钉API中的GET请求. 9 | * 10 | * @param queryParam 参数 11 | * @param url 请求接口地址 12 | * @return 接口响应字符串 13 | * @throws DtErrorException 异常 14 | */ 15 | String get(String url, String queryParam) throws DtErrorException; 16 | 17 | /** 18 | * 当本Service没有实现某个API的时候,可以用这个,针对所有钉钉API中的POST请求. 19 | * 20 | * @param postData 请求参数json值 21 | * @param url 请求接口地址 22 | * @return 接口响应字符串 23 | * @throws DtErrorException 异常 24 | */ 25 | String post(String url, String postData) throws DtErrorException; 26 | 27 | /** 28 | * 当本Service没有实现某个API的时候,可以用这个,针对所有钉钉API中的POST请求. 29 | * 30 | * @param url 请求接口地址 31 | * @param obj 请求对象 32 | * @return 接口响应字符串 33 | * @throws DtErrorException 异常 34 | */ 35 | String post(String url, Object obj) throws DtErrorException; 36 | 37 | /** 38 | * 当本Service没有实现某个API的时候,可以用这个,针对所有钉钉API中的POST请求. 39 | * 40 | * @param url 请求接口地址 41 | * @param jsonObject 请求对象 42 | * @return 接口响应字符串 43 | * @throws DtErrorException 异常 44 | */ 45 | String post(String url, JsonObject jsonObject) throws DtErrorException; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/DtAgentService.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.agent.DtAgentAuthScope; 4 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 应用相关 10 | */ 11 | public interface DtAgentService { 12 | /** 13 | * 获取应用的通讯录权限范围 14 | * 详情请见: https://ding-doc.dingtalk.com/document/app/obtain-corpsecret-authorization-scope 15 | * @return auth scope 16 | */ 17 | DtAgentAuthScope getAuthScope() throws DtErrorException; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/DtCorpConversationMessageService.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMessage; 4 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMsgSendResult; 5 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationRecall; 6 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationStatusBarUpdate; 7 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 8 | 9 | /** 10 | * 消息通知-工作通知 11 | */ 12 | public interface DtCorpConversationMessageService { 13 | 14 | /** 15 | * 发送工作通知 16 | * https://developers.dingtalk.com/document/app/asynchronous-sending-of-enterprise-session-messages 17 | * 18 | * @param message msg 19 | * @return send result 20 | * @throws DtErrorException error 21 | */ 22 | DtCorpConversationMsgSendResult send(DtCorpConversationMessage message) throws DtErrorException; 23 | 24 | /** 25 | * https://developers.dingtalk.com/document/app/update-work-notification-status-bar 26 | * 更新工作通知状态栏 27 | * 28 | * @param update param 29 | * @return requestId 30 | * @throws DtErrorException error 31 | */ 32 | String updateOaStatusBar(DtCorpConversationStatusBarUpdate update) throws DtErrorException; 33 | 34 | /** 35 | * 撤回工作通知 36 | * https://developers.dingtalk.com/document/app/notification-of-work-withdrawal 37 | * 38 | * @param recall recall msg 39 | * @throws DtErrorException error 40 | */ 41 | void recall(DtCorpConversationRecall recall) throws DtErrorException; 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/DtDepartmentService.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.department.DtDepart; 4 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 部门管理接口 10 | */ 11 | public interface DtDepartmentService { 12 | 13 | /** 14 | * 获取部门列表,递归获取部门及子部门列表 15 | * @see #list(Long, Boolean) 16 | * 17 | * @param id departId 18 | * @return depart 19 | * @throws DtErrorException error 20 | */ 21 | List list(Long id) throws DtErrorException; 22 | 23 | /** 24 | * 获取部门列表 25 | * 详情请见: https://ding-doc.dingtalk.com/document/app/obtain-the-department-list 26 | * 27 | * @param id 部门id。非必需,可为null 28 | * @param fetchChild 是否递归部门的全部子部门。非必填,可为null 29 | * @return 获取的部门列表 30 | * @throws DtErrorException 异常 31 | */ 32 | List list(Long id, Boolean fetchChild) throws DtErrorException; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/DtOAuth2Service.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.oauth.DtOauth2UserInfo; 4 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 5 | 6 | /** 7 | * OAuth2相关管理接口. 8 | */ 9 | public interface DtOAuth2Service { 10 | 11 | /** 12 | * 用oauth2获取用户信息 13 | * 14 | * @param code oauth授权返回的代码 15 | * @return DtOauth2UserInfo 16 | * @throws DtErrorException 异常 17 | */ 18 | DtOauth2UserInfo getUserInfo(String code) throws DtErrorException; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/DtService.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.config.DtConfigStorage; 4 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 5 | import com.github.tingyugetc520.ali.dingtalk.util.http.RequestExecutor; 6 | 7 | public interface DtService extends BaseService { 8 | 9 | /** 10 | *
 11 |      * 验证推送过来的消息的正确性
 12 |      * 
13 | * 14 | * @param signature 消息签名 15 | * @param timestamp 时间戳 16 | * @param nonce 随机数 17 | * @param data 传输过来的数据 18 | * @return the boolean 19 | */ 20 | boolean checkSignature(String signature, String timestamp, String nonce, String data); 21 | 22 | /** 23 | * 获取access_token, 不强制刷新access_token 24 | * 25 | * @return the access token 26 | * @throws DtErrorException the error exception 27 | * @see #getAccessToken(boolean) 28 | */ 29 | String getAccessToken() throws DtErrorException; 30 | 31 | /** 32 | * 获取access_token,本方法线程安全 33 | * 且在多线程同时刷新时只刷新一次,避免触发调用次数频繁限制 34 | * 非必要情况下尽量不要主动调用此方法 35 | * 36 | * @param forceRefresh 强制刷新 37 | * @return the access token 38 | * @throws DtErrorException the error exception 39 | */ 40 | String getAccessToken(boolean forceRefresh) throws DtErrorException; 41 | 42 | /** 43 | * Service没有实现某个API的时候,可以用这个, 44 | * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。 45 | * 46 | * @param 请求值类型 47 | * @param 返回值类型 48 | * @param executor 执行器 49 | * @param uri 请求地址 50 | * @param data 参数 51 | * @return the t 52 | * @throws DtErrorException the error exception 53 | */ 54 | T execute(RequestExecutor executor, String uri, E data) throws DtErrorException; 55 | 56 | /** 57 | * 初始化http请求对象 58 | */ 59 | void initHttp(); 60 | 61 | /** 62 | * 获取DtConfigStorage对象 63 | * 64 | * @return DtConfigStorage config storage 65 | */ 66 | DtConfigStorage getDtConfigStorage(); 67 | 68 | /** 69 | * 注入 {@link DtConfigStorage} 的实现 70 | * 71 | * @param configProvider 配置对象 72 | */ 73 | void setDtConfigStorage(DtConfigStorage configProvider); 74 | 75 | /** 76 | * 获取部门相关接口的服务类对象 77 | * @return department service 78 | */ 79 | DtDepartmentService getDepartmentService(); 80 | 81 | /** 82 | * 获取用户相关接口的服务类对象 83 | * 84 | * @return the user service 85 | */ 86 | DtUserService getUserService(); 87 | 88 | /** 89 | * 获取Oauth2相关接口的服务类对象 90 | * 91 | * @return the oauth 2 service 92 | */ 93 | DtOAuth2Service getOauth2Service(); 94 | 95 | /** 96 | * agent service 97 | * @return service 98 | */ 99 | DtAgentService getAgentService(); 100 | 101 | DtCorpConversationMessageService getCorpConversationMsgService(); 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/DtUserService.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.user.DtUnionId2UserId; 4 | import com.github.tingyugetc520.ali.dingtalk.bean.user.DtUser; 5 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 用户管理接口 11 | * 12 | * @author tingyugetc520 13 | */ 14 | public interface DtUserService { 15 | 16 | /** 17 | * 获取部门用户详情 18 | * https://ding-doc.dingtalk.com/document/app/obtain-department-members-details 19 | * 20 | * @param departId 必填。部门id 21 | * @param offset 必填 22 | * @param size 必填 23 | * @param order 选填 24 | * @return the list 25 | * @throws DtErrorException the error exception 26 | */ 27 | List listByDepartment(Long departId, Integer offset, Integer size, String order) throws DtErrorException; 28 | 29 | /** 30 | * 获取部门用户列表 31 | * https://ding-doc.dingtalk.com/document/app/obtain-department-members 32 | * 33 | * @param departId 必填。部门id 34 | * @param offset 必填 35 | * @param size 必填 36 | * @param order 选填 37 | * @return the list 38 | * @throws DtErrorException the error exception 39 | */ 40 | List listSimpleByDepartment(Long departId, Integer offset, Integer size, String order) throws DtErrorException; 41 | 42 | /** 43 | * 获取用户 44 | * 45 | * @param userId 用户id 46 | * @return user 47 | * @throws DtErrorException the error exception 48 | */ 49 | DtUser getById(String userId) throws DtErrorException; 50 | 51 | /** 52 | * 根据手机号获取userid 53 | * https://developers.dingtalk.com/document/app/retrieve-userid-from-mobile-phone-number 54 | * 55 | * @param mobile mobile 56 | * @return userId 57 | * @throws DtErrorException error 58 | */ 59 | String getUserIdByMobile(String mobile) throws DtErrorException; 60 | 61 | /** 62 | * 获取部门用户userid列表 63 | * https://developers.dingtalk.com/document/app/obtain-the-list-of-employee-ids-by-department-id 64 | * 65 | * @param departId departId 66 | * @return userIds 67 | * @throws DtErrorException error 68 | */ 69 | List userIdsByDepartment(Long departId) throws DtErrorException; 70 | 71 | /** 72 | * openid转userid 73 | * https://developers.dingtalk.com/document/app/you-can-call-this-operation-to-retrieve-the-userids-of 74 | * 75 | * @param unionId unionId 76 | * @return userId 77 | * @throws DtErrorException error 78 | */ 79 | DtUnionId2UserId unionId2UserId(String unionId) throws DtErrorException; 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/impl/BaseDtServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api.impl; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.*; 4 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 5 | import com.github.tingyugetc520.ali.dingtalk.config.DtConfigStorage; 6 | import com.github.tingyugetc520.ali.dingtalk.error.DtError; 7 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 8 | import com.github.tingyugetc520.ali.dingtalk.error.DtRuntimeException; 9 | import com.github.tingyugetc520.ali.dingtalk.util.DataUtils; 10 | import com.github.tingyugetc520.ali.dingtalk.util.crypto.DtCryptUtil; 11 | import com.github.tingyugetc520.ali.dingtalk.util.http.RequestExecutor; 12 | import com.google.gson.JsonObject; 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | import java.io.IOException; 16 | 17 | import static com.github.tingyugetc520.ali.dingtalk.error.DtErrorMsgEnum.*; 18 | 19 | @Slf4j 20 | public abstract class BaseDtServiceImpl implements DtService { 21 | private final DtUserService userService = new DtUserServiceImpl(this); 22 | private final DtDepartmentService departmentService = new DtDepartmentServiceImpl(this); 23 | private final DtOAuth2Service oauth2Service = new DtOAuth2ServiceImpl(this); 24 | private final DtAgentService agentService = new DtAgentServiceImpl(this); 25 | private final DtCorpConversationMessageService corpConversationMsgService = new DtCorpConversationMessageServiceImpl(this); 26 | 27 | /** 28 | * 全局的是否正在刷新access token的锁. 29 | */ 30 | protected final Object globalAccessTokenRefreshLock = new Object(); 31 | 32 | protected DtConfigStorage configStorage; 33 | 34 | private final int retrySleepMillis = 1000; 35 | private final int maxRetryTimes = 5; 36 | 37 | @Override 38 | public boolean checkSignature(String signature, String timestamp, String nonce, String data) { 39 | try { 40 | return DtCryptUtil.getSignature( 41 | this.configStorage.getToken(), timestamp, nonce, data 42 | ).equals(signature); 43 | } catch (Exception e) { 44 | log.error("Checking signature failed, and the reason is :" + e.getMessage()); 45 | return false; 46 | } 47 | } 48 | 49 | @Override 50 | public String getAccessToken() throws DtErrorException { 51 | return getAccessToken(false); 52 | } 53 | 54 | @Override 55 | public String get(String url, String queryParam) throws DtErrorException { 56 | RequestExecutor executor = getOkHttpSimpleGetRequestExecutor(); 57 | return execute(executor, url, queryParam); 58 | } 59 | 60 | @Override 61 | public String post(String url, String postData) throws DtErrorException { 62 | RequestExecutor executor = getOkHttpSimplePostRequestExecutor(); 63 | return execute(executor, url, postData); 64 | } 65 | 66 | @Override 67 | public String post(String url, JsonObject jsonObject) throws DtErrorException { 68 | return this.post(url, jsonObject.toString()); 69 | } 70 | 71 | @Override 72 | public String post(String url, Object obj) throws DtErrorException { 73 | return this.post(url, obj.toString()); 74 | } 75 | 76 | /** 77 | * okHttp get请求 78 | * @return executor 79 | */ 80 | protected abstract RequestExecutor getOkHttpSimpleGetRequestExecutor(); 81 | 82 | /** 83 | * okHttp post请求 84 | * @return executor 85 | */ 86 | protected abstract RequestExecutor getOkHttpSimplePostRequestExecutor(); 87 | 88 | /** 89 | * 发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求. 90 | */ 91 | @Override 92 | public T execute(RequestExecutor executor, String uri, E data) throws DtErrorException { 93 | int retryTimes = 0; 94 | do { 95 | try { 96 | return executeInternal(executor, uri, data); 97 | } catch (DtErrorException e) { 98 | if (retryTimes + 1 > maxRetryTimes) { 99 | log.warn("重试达到最大次数【{}】", maxRetryTimes); 100 | //最后一次重试失败后,直接抛出异常,不再等待 101 | throw new DtRuntimeException("钉钉服务端异常,超出重试次数"); 102 | } 103 | 104 | DtError error = e.getError(); 105 | // 系统繁忙, 1000ms后重试 106 | if (error.getErrorCode() == CODE_1.getCode()) { 107 | int sleepMillis = retrySleepMillis * (1 << retryTimes); 108 | try { 109 | log.debug("钉钉系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); 110 | Thread.sleep(sleepMillis); 111 | } catch (InterruptedException e1) { 112 | Thread.currentThread().interrupt(); 113 | } 114 | } else { 115 | throw e; 116 | } 117 | } 118 | } while (retryTimes++ < maxRetryTimes); 119 | 120 | log.warn("重试达到最大次数【{}】", maxRetryTimes); 121 | throw new DtRuntimeException("钉钉服务端异常,超出重试次数"); 122 | } 123 | 124 | protected T executeInternal(RequestExecutor executor, String uri, E data) throws DtErrorException { 125 | E dataForLog = DataUtils.handleDataWithSecret(data); 126 | 127 | if (uri.contains("access_token=")) { 128 | throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri); 129 | } 130 | String accessToken = getAccessToken(false); 131 | String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken; 132 | 133 | try { 134 | T result = executor.execute(uriWithAccessToken, data); 135 | log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, result); 136 | return result; 137 | } catch (DtErrorException e) { 138 | DtError error = e.getError(); 139 | 140 | if (DtConstant.ACCESS_TOKEN_ERROR_CODES.contains(error.getErrorCode())) { 141 | // 强制设置DtConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token 142 | this.configStorage.expireAccessToken(); 143 | if (this.getDtConfigStorage().autoRefreshToken()) { 144 | log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg()); 145 | return this.execute(executor, uri, data); 146 | } 147 | } 148 | 149 | if (error.getErrorCode() != 0) { 150 | log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); 151 | throw new DtErrorException(error, e); 152 | } 153 | return null; 154 | } catch (IOException e) { 155 | log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); 156 | throw new DtRuntimeException(e); 157 | } 158 | } 159 | 160 | @Override 161 | public void setDtConfigStorage(DtConfigStorage dtConfigStorage) { 162 | this.configStorage = dtConfigStorage; 163 | this.initHttp(); 164 | } 165 | 166 | @Override 167 | public DtDepartmentService getDepartmentService() { 168 | return departmentService; 169 | } 170 | 171 | @Override 172 | public DtOAuth2Service getOauth2Service() { 173 | return oauth2Service; 174 | } 175 | 176 | @Override 177 | public DtUserService getUserService() { 178 | return userService; 179 | } 180 | 181 | @Override 182 | public DtAgentService getAgentService() { 183 | return agentService; 184 | } 185 | 186 | @Override 187 | public DtCorpConversationMessageService getCorpConversationMsgService() { 188 | return corpConversationMsgService; 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/impl/DtAgentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api.impl; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtAgentService; 4 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 5 | import com.github.tingyugetc520.ali.dingtalk.bean.agent.DtAgentAuthScope; 6 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | import static com.github.tingyugetc520.ali.dingtalk.constant.DtApiPathConstant.Agent.*; 10 | 11 | 12 | /** 13 | * 应用相关 14 | */ 15 | @RequiredArgsConstructor 16 | public class DtAgentServiceImpl implements DtAgentService { 17 | private final DtService mainService; 18 | 19 | @Override 20 | public DtAgentAuthScope getAuthScope() throws DtErrorException { 21 | final String url = this.mainService.getDtConfigStorage().getApiUrl(AGENT_AUTH_SCOPE); 22 | return DtAgentAuthScope.fromJson(this.mainService.get(url, null)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/impl/DtCorpConversationMessageServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api.impl; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtCorpConversationMessageService; 4 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 5 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMessage; 6 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMsgSendResult; 7 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationRecall; 8 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationStatusBarUpdate; 9 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 10 | import com.github.tingyugetc520.ali.dingtalk.util.json.GsonHelper; 11 | import com.github.tingyugetc520.ali.dingtalk.util.json.GsonParser; 12 | import com.google.gson.JsonObject; 13 | import lombok.RequiredArgsConstructor; 14 | 15 | import static com.github.tingyugetc520.ali.dingtalk.constant.DtApiPathConstant.Message.*; 16 | 17 | /** 18 | * 消息通知-工作通知 19 | */ 20 | @RequiredArgsConstructor 21 | public class DtCorpConversationMessageServiceImpl implements DtCorpConversationMessageService { 22 | private final DtService mainService; 23 | 24 | @Override 25 | public DtCorpConversationMsgSendResult send(DtCorpConversationMessage message) throws DtErrorException { 26 | String url = this.mainService.getDtConfigStorage().getApiUrl(AppCordConversation.SEND); 27 | return DtCorpConversationMsgSendResult.fromJson(this.mainService.post(url, message.toJson())); 28 | } 29 | 30 | @Override 31 | public String updateOaStatusBar(DtCorpConversationStatusBarUpdate update) throws DtErrorException { 32 | String url = this.mainService.getDtConfigStorage().getApiUrl(AppCordConversation.STATUS_BAR_UPDATE); 33 | String res = this.mainService.post(url, update.toJson()); 34 | 35 | JsonObject jsonObject = GsonParser.parse(res); 36 | return GsonHelper.getString(jsonObject, "request_id"); 37 | } 38 | 39 | @Override 40 | public void recall(DtCorpConversationRecall recall) throws DtErrorException { 41 | String url = this.mainService.getDtConfigStorage().getApiUrl(AppCordConversation.RECALL); 42 | this.mainService.post(url, recall.toJson()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/impl/DtDepartmentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api.impl; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtDepartmentService; 4 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 5 | import com.github.tingyugetc520.ali.dingtalk.bean.department.DtDepart; 6 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 7 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 8 | import com.github.tingyugetc520.ali.dingtalk.util.json.GsonParser; 9 | import com.google.gson.JsonObject; 10 | import com.google.gson.reflect.TypeToken; 11 | import lombok.RequiredArgsConstructor; 12 | 13 | import java.util.List; 14 | 15 | import static com.github.tingyugetc520.ali.dingtalk.constant.DtApiPathConstant.Department.*; 16 | 17 | /** 18 | * 部门管理接口 19 | */ 20 | @RequiredArgsConstructor 21 | public class DtDepartmentServiceImpl implements DtDepartmentService { 22 | private final DtService mainService; 23 | 24 | @Override 25 | public List list(Long id) throws DtErrorException { 26 | return list(id, true); 27 | } 28 | 29 | @Override 30 | public List list(Long id, Boolean fetchChild) throws DtErrorException { 31 | String params = ""; 32 | if (id != null) { 33 | params += "&id=" + id; 34 | } 35 | if (fetchChild != null) { 36 | params += "&fetch_child=" + (fetchChild ? "true" : "false"); 37 | } 38 | 39 | String url = this.mainService.getDtConfigStorage().getApiUrl(DEPARTMENT_LIST); 40 | String responseContent = this.mainService.get(url, params); 41 | JsonObject tmpJsonObject = GsonParser.parse(responseContent); 42 | return DtGsonBuilder.create() 43 | .fromJson( 44 | tmpJsonObject.get("department"), 45 | new TypeToken>() {}.getType() 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/impl/DtOAuth2ServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api.impl; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtOAuth2Service; 4 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 5 | import com.github.tingyugetc520.ali.dingtalk.bean.oauth.DtOauth2UserInfo; 6 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 7 | import com.github.tingyugetc520.ali.dingtalk.util.json.GsonHelper; 8 | import com.github.tingyugetc520.ali.dingtalk.util.json.GsonParser; 9 | import com.google.gson.JsonObject; 10 | import lombok.RequiredArgsConstructor; 11 | 12 | import static com.github.tingyugetc520.ali.dingtalk.constant.DtApiPathConstant.OAuth2.*; 13 | 14 | /** 15 | * oauth2相关接口实现类 16 | */ 17 | @RequiredArgsConstructor 18 | public class DtOAuth2ServiceImpl implements DtOAuth2Service { 19 | private final DtService mainService; 20 | 21 | 22 | @Override 23 | public DtOauth2UserInfo getUserInfo(String code) throws DtErrorException { 24 | String responseText = this.mainService.get(String.format(this.mainService.getDtConfigStorage().getApiUrl(GET_USER_INFO), code), null); 25 | 26 | return DtOauth2UserInfo.fromJson(responseText); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/impl/DtServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api.impl; 2 | 3 | public class DtServiceImpl extends DtServiceOkHttpImpl { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/impl/DtServiceOkHttpImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api.impl; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.DtAccessToken; 4 | import com.github.tingyugetc520.ali.dingtalk.config.DtConfigStorage; 5 | import com.github.tingyugetc520.ali.dingtalk.constant.DtApiPathConstant; 6 | import com.github.tingyugetc520.ali.dingtalk.error.DtError; 7 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 8 | import com.github.tingyugetc520.ali.dingtalk.error.DtRuntimeException; 9 | import com.github.tingyugetc520.ali.dingtalk.util.http.*; 10 | import lombok.extern.slf4j.Slf4j; 11 | import okhttp3.Credentials; 12 | import okhttp3.OkHttpClient; 13 | import okhttp3.Request; 14 | import okhttp3.Response; 15 | import org.apache.commons.lang3.StringUtils; 16 | import org.apache.http.HttpStatus; 17 | 18 | import java.io.IOException; 19 | import java.util.Objects; 20 | 21 | /** 22 | * @author . 23 | */ 24 | @Slf4j 25 | public class DtServiceOkHttpImpl extends BaseDtServiceImpl { 26 | private OkHttpClient httpClient; 27 | // private OkHttpProxyInfo httpProxy; 28 | 29 | public OkHttpClient getRequestHttpClient() { 30 | return httpClient; 31 | } 32 | 33 | // public OkHttpProxyInfo getRequestHttpProxy() { 34 | // return httpProxy; 35 | // } 36 | 37 | @Override 38 | public String getAccessToken(boolean forceRefresh) throws DtErrorException { 39 | if (!configStorage.isAccessTokenExpired() && !forceRefresh) { 40 | return configStorage.getAccessToken(); 41 | } 42 | 43 | synchronized (this.globalAccessTokenRefreshLock) { 44 | //得到httpClient 45 | OkHttpClient client = getRequestHttpClient(); 46 | //请求的request 47 | Request request = new Request.Builder() 48 | .url(String.format(configStorage.getApiUrl(DtApiPathConstant.GET_TOKEN), configStorage.getAppKey(), configStorage.getAppSecret())) 49 | .get() 50 | .build(); 51 | String resultContent = null; 52 | try { 53 | Response response = client.newCall(request).execute(); 54 | resultContent = Objects.requireNonNull(response.body()).string(); 55 | } catch (IOException e) { 56 | log.error(e.getMessage(), e); 57 | throw new DtRuntimeException(e); 58 | } 59 | 60 | DtError error = DtError.fromJson(resultContent); 61 | if (error.getErrorCode() != 0) { 62 | throw new DtErrorException(error); 63 | } 64 | DtAccessToken accessToken = DtAccessToken.fromJson(resultContent); 65 | configStorage.updateAccessToken(accessToken.getAccessToken(), 66 | accessToken.getExpiresIn()); 67 | } 68 | return configStorage.getAccessToken(); 69 | } 70 | 71 | @Override 72 | public void initHttp() { 73 | log.debug("DtServiceOkHttpImpl initHttp"); 74 | 75 | OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); 76 | // 设置代理 77 | HttpProxyType httpProxyType = HttpProxyType.findByCode(configStorage.getHttpProxyType()); 78 | if (HttpProxyType.FORWARD.equals(httpProxyType)) { 79 | // 正向代理 80 | initForwardProxy(clientBuilder); 81 | } 82 | httpClient = clientBuilder.addInterceptor(new ResponseHttpStatusInterceptor()).build(); 83 | } 84 | 85 | private void initForwardProxy(OkHttpClient.Builder clientBuilder) { 86 | if (StringUtils.isBlank(configStorage.getHttpProxyHost()) || configStorage.getHttpProxyPort() <= 0) { 87 | return; 88 | } 89 | OkHttpProxyInfo httpProxy = OkHttpProxyInfo.httpProxy( 90 | configStorage.getHttpProxyHost(), 91 | configStorage.getHttpProxyPort(), 92 | configStorage.getHttpProxyUsername(), 93 | configStorage.getHttpProxyPassword() 94 | ); 95 | clientBuilder.proxy(httpProxy.getProxy()); 96 | 97 | //设置授权 98 | clientBuilder.authenticator((route, response) -> { 99 | String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword()); 100 | return response.request().newBuilder() 101 | .header("Authorization", credential) 102 | .build(); 103 | }); 104 | } 105 | 106 | @Override 107 | public DtConfigStorage getDtConfigStorage() { 108 | return this.configStorage; 109 | } 110 | 111 | @Override 112 | protected RequestExecutor getOkHttpSimpleGetRequestExecutor() { 113 | return OkHttpSimpleGetRequestExecutor.create(httpClient); 114 | } 115 | 116 | @Override 117 | protected RequestExecutor getOkHttpSimplePostRequestExecutor() { 118 | return OkHttpSimplePostRequestExecutor.create(httpClient); 119 | } 120 | 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/api/impl/DtUserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.api.impl; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 4 | import com.github.tingyugetc520.ali.dingtalk.api.DtUserService; 5 | import com.github.tingyugetc520.ali.dingtalk.bean.user.DtUnionId2UserId; 6 | import com.github.tingyugetc520.ali.dingtalk.bean.user.DtUser; 7 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 8 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 9 | import com.github.tingyugetc520.ali.dingtalk.util.json.GsonHelper; 10 | import com.github.tingyugetc520.ali.dingtalk.util.json.GsonParser; 11 | import com.google.gson.JsonObject; 12 | import com.google.gson.reflect.TypeToken; 13 | import lombok.RequiredArgsConstructor; 14 | 15 | import java.util.List; 16 | 17 | import static com.github.tingyugetc520.ali.dingtalk.constant.DtApiPathConstant.User.*; 18 | 19 | @RequiredArgsConstructor 20 | public class DtUserServiceImpl implements DtUserService { 21 | private final DtService mainService; 22 | 23 | @Override 24 | public DtUser getById(String userId) throws DtErrorException { 25 | String url = this.mainService.getDtConfigStorage().getApiUrl(USER_GET + userId); 26 | String responseContent = this.mainService.get(url, null); 27 | return DtUser.fromJson(responseContent); 28 | } 29 | 30 | @Override 31 | public String getUserIdByMobile(String mobile) throws DtErrorException { 32 | String url = this.mainService.getDtConfigStorage().getApiUrl(USER_ID_GET + mobile); 33 | String responseContent = this.mainService.get(url, null); 34 | JsonObject jsonObject = GsonParser.parse(responseContent); 35 | return GsonHelper.getString(jsonObject, "userid"); 36 | } 37 | 38 | @Override 39 | public List userIdsByDepartment(Long departId) throws DtErrorException { 40 | String url = this.mainService.getDtConfigStorage().getApiUrl(USER_ID_LIST + departId); 41 | String responseContent = this.mainService.get(url, null); 42 | JsonObject jsonObject = GsonParser.parse(responseContent); 43 | return DtGsonBuilder.create() 44 | .fromJson( 45 | jsonObject.get("userIds"), 46 | new TypeToken>() {}.getType() 47 | ); 48 | } 49 | 50 | @Override 51 | public List listByDepartment(Long departId, Integer offset, Integer size, String order) throws DtErrorException { 52 | String params = ""; 53 | if (order != null) { 54 | params += "&order=" + order; 55 | } 56 | 57 | String url = String.format(this.mainService.getDtConfigStorage().getApiUrl(USER_LIST), departId, offset, size); 58 | String responseContent = this.mainService.get(url, params); 59 | JsonObject jsonObject = GsonParser.parse(responseContent); 60 | return DtGsonBuilder.create() 61 | .fromJson( 62 | jsonObject.get("userlist"), 63 | new TypeToken>() {}.getType() 64 | ); 65 | } 66 | 67 | @Override 68 | public List listSimpleByDepartment(Long departId, Integer offset, Integer size, String order) throws DtErrorException { 69 | String params = ""; 70 | if (order != null) { 71 | params += "&order=" + order; 72 | } 73 | 74 | String url = String.format(this.mainService.getDtConfigStorage().getApiUrl(USER_SIMPLE_LIST), departId, offset, size); 75 | String responseContent = this.mainService.get(url, params); 76 | JsonObject tmpJson = GsonParser.parse(responseContent); 77 | return DtGsonBuilder.create() 78 | .fromJson( 79 | tmpJson.get("userlist"), 80 | new TypeToken>() {}.getType() 81 | ); 82 | } 83 | 84 | @Override 85 | public DtUnionId2UserId unionId2UserId(String unionId) throws DtErrorException { 86 | String url = this.mainService.getDtConfigStorage().getApiUrl(UNION_ID_2_USER_ID + unionId); 87 | String responseContent = this.mainService.get(url, null); 88 | return DtUnionId2UserId.fromJson(responseContent); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/DtAccessToken.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import com.google.gson.annotations.SerializedName; 5 | import lombok.Data; 6 | import lombok.experimental.Accessors; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * access token 12 | */ 13 | @Data 14 | @Accessors(chain = true) 15 | public class DtAccessToken implements Serializable { 16 | private static final long serialVersionUID = -944200774978928518L; 17 | 18 | @SerializedName("access_token") 19 | private String accessToken; 20 | 21 | @SerializedName("expires_in") 22 | private int expiresIn = -1; 23 | 24 | public static DtAccessToken fromJson(String json) { 25 | return DtGsonBuilder.create().fromJson(json, DtAccessToken.class); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/agent/DtAgentAuthScope.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.agent; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import com.google.gson.annotations.SerializedName; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | import java.util.List; 12 | 13 | /** 14 | * 应用通讯录权限信息 15 | */ 16 | @Data 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class DtAgentAuthScope implements Serializable { 21 | private static final long serialVersionUID = 5002894979081127234L; 22 | 23 | @SerializedName("auth_user_field") 24 | private List authUserField; 25 | 26 | @SerializedName("auth_org_scopes") 27 | private AuthOrgScopes authOrgScopes; 28 | 29 | public static DtAgentAuthScope fromJson(String json) { 30 | return DtGsonBuilder.create().fromJson(json, DtAgentAuthScope.class); 31 | } 32 | 33 | public String toJson() { 34 | return DtGsonBuilder.create().toJson(this); 35 | } 36 | 37 | @Data 38 | public static class AuthOrgScopes implements Serializable { 39 | private static final long serialVersionUID = 8801100463558788565L; 40 | @SerializedName("authed_user") 41 | private List users; 42 | @SerializedName("authed_dept") 43 | private List partyIds; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/department/DtDepart.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.department; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import com.google.gson.annotations.SerializedName; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 部门. 11 | */ 12 | @Data 13 | public class DtDepart implements Serializable { 14 | private static final long serialVersionUID = -5028321625140879571L; 15 | 16 | private Long id; 17 | private String name; 18 | @SerializedName("parentid") 19 | private Long parentId; 20 | private Long order; 21 | 22 | public static DtDepart fromJson(String json) { 23 | return DtGsonBuilder.create().fromJson(json, DtDepart.class); 24 | } 25 | 26 | public String toJson() { 27 | return DtGsonBuilder.create().toJson(this); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/DtCorpConversationMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message; 2 | 3 | import com.google.gson.JsonObject; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.apache.commons.collections4.CollectionUtils; 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | import java.io.Serializable; 12 | import java.util.List; 13 | import java.util.Objects; 14 | 15 | /** 16 | * 企业工作通知消息 17 | */ 18 | @Data 19 | @Builder 20 | @NoArgsConstructor 21 | @AllArgsConstructor 22 | public class DtCorpConversationMessage implements Serializable { 23 | private static final long serialVersionUID = 5492729923265382806L; 24 | 25 | private Long agentId; 26 | private List userIds; 27 | private List departIds; 28 | private Boolean toAllUser; 29 | private DtMessage msg; 30 | 31 | public String toJson() { 32 | JsonObject messageJson = new JsonObject(); 33 | if (this.getAgentId() != null) { 34 | messageJson.addProperty("agent_id", this.getAgentId()); 35 | } 36 | 37 | if (CollectionUtils.isNotEmpty(this.userIds)) { 38 | messageJson.addProperty("userid_list", StringUtils.join(this.userIds, ",")); 39 | } 40 | 41 | if (CollectionUtils.isNotEmpty(this.departIds)) { 42 | messageJson.addProperty("dept_id_list", StringUtils.join(this.departIds, ",")); 43 | } 44 | 45 | if (Objects.nonNull(this.toAllUser)) { 46 | messageJson.addProperty("to_all_user", this.toAllUser); 47 | } 48 | 49 | if (msg != null) { 50 | messageJson.add("msg", msg.toJsonObject()); 51 | } 52 | 53 | return messageJson.toString(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/DtCorpConversationMsgSendResult.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import com.google.gson.annotations.SerializedName; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 11 | */ 12 | @Data 13 | public class DtCorpConversationMsgSendResult implements Serializable { 14 | private static final long serialVersionUID = 916455987193190004L; 15 | 16 | @SerializedName("request_id") 17 | private String requestId; 18 | 19 | @SerializedName("task_id") 20 | private String taskId; 21 | 22 | public static DtCorpConversationMsgSendResult fromJson(String json) { 23 | return DtGsonBuilder.create().fromJson(json, DtCorpConversationMsgSendResult.class); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/DtCorpConversationRecall.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import com.google.gson.annotations.SerializedName; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class DtCorpConversationRecall implements Serializable { 17 | private static final long serialVersionUID = -1414188116769589593L; 18 | 19 | @SerializedName("agent_id") 20 | private Long agentId; 21 | @SerializedName("msg_task_id") 22 | private Long taskId; 23 | 24 | public String toJson() { 25 | return DtGsonBuilder.create().toJson(this); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/DtCorpConversationStatusBarUpdate.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import com.google.gson.annotations.SerializedName; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class DtCorpConversationStatusBarUpdate implements Serializable { 17 | private static final long serialVersionUID = -1414188116769589593L; 18 | 19 | @SerializedName("agent_id") 20 | private Long agentId; 21 | @SerializedName("task_id") 22 | private Long taskId; 23 | @SerializedName("status_value") 24 | private String value; 25 | @SerializedName("status_bg") 26 | private String bgColor; 27 | 28 | public String toJson() { 29 | return DtGsonBuilder.create().toJson(this); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/DtEventMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.config.DtConfigStorage; 4 | import com.github.tingyugetc520.ali.dingtalk.error.DtRuntimeException; 5 | import com.github.tingyugetc520.ali.dingtalk.util.crypto.DtCryptUtil; 6 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 7 | import com.github.tingyugetc520.ali.dingtalk.util.json.GsonHelper; 8 | import com.github.tingyugetc520.ali.dingtalk.util.json.GsonParser; 9 | import com.google.gson.JsonObject; 10 | import com.google.gson.annotations.SerializedName; 11 | import com.google.gson.reflect.TypeToken; 12 | import com.google.gson.stream.JsonReader; 13 | import lombok.Data; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.apache.commons.io.IOUtils; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.InputStreamReader; 20 | import java.io.Serializable; 21 | import java.nio.charset.StandardCharsets; 22 | import java.util.Map; 23 | 24 | @Data 25 | @Slf4j 26 | public class DtEventMessage implements Serializable { 27 | private static final long serialVersionUID = 5202339097541307818L; 28 | 29 | /** 30 | * 推送过来的属性和值的map. 31 | */ 32 | private Map allFieldsMap; 33 | 34 | @SerializedName("CorpId") 35 | private String corpId; 36 | 37 | @SerializedName("EventType") 38 | private String eventType; 39 | 40 | @SerializedName("TimeStamp") 41 | private Long timeStamp; 42 | 43 | protected static DtEventMessage fromJson(String json) { 44 | DtEventMessage message = DtGsonBuilder.create().fromJson(json, DtEventMessage.class); 45 | message.setAllFieldsMap(DtGsonBuilder.create().fromJson(json, new TypeToken>() {}.getType())); 46 | return message; 47 | } 48 | 49 | protected static DtEventMessage fromJson(InputStream is) { 50 | JsonReader reader = new JsonReader(new InputStreamReader(is, StandardCharsets.UTF_8)); 51 | return DtGsonBuilder.create().fromJson(reader, DtEventMessage.class); 52 | } 53 | 54 | /** 55 | * 从加密字符串转换. 56 | */ 57 | public static DtEventMessage fromEncrypt(String encrypt, DtConfigStorage configStorage, 58 | String timestamp, String nonce, String msgSignature) { 59 | DtCryptUtil cryptUtil = new DtCryptUtil(configStorage); 60 | String plainText = cryptUtil.getDecryptMsg(msgSignature, timestamp, nonce, encrypt); 61 | log.debug("解密后的原始json消息内容:{}", plainText); 62 | return fromJson(plainText); 63 | } 64 | 65 | public static DtEventMessage fromEncryptedJson(String encryptedJson, DtConfigStorage configStorage, 66 | String timestamp, String nonce, String msgSignature) { 67 | JsonObject jsonObject = GsonParser.parse(encryptedJson); 68 | String encrypt = GsonHelper.getString(jsonObject, "encrypt"); 69 | return fromEncrypt(encrypt, configStorage, timestamp, nonce, msgSignature); 70 | } 71 | 72 | public static DtEventMessage fromEncryptedJson(InputStream is, DtConfigStorage configStorage, 73 | String timestamp, String nonce, String msgSignature) { 74 | try { 75 | return fromEncryptedJson(IOUtils.toString(is, StandardCharsets.UTF_8), configStorage, timestamp, nonce, msgSignature); 76 | } catch (IOException e) { 77 | throw new DtRuntimeException(e); 78 | } 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return DtGsonBuilder.create().toJson(this); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/DtEventOutMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.config.DtConfigStorage; 4 | import com.github.tingyugetc520.ali.dingtalk.util.crypto.DtCryptUtil; 5 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 6 | import com.google.gson.annotations.SerializedName; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.io.Serializable; 12 | import java.util.Map; 13 | 14 | @Builder 15 | @Data 16 | @Slf4j 17 | public class DtEventOutMessage implements Serializable { 18 | private static final long serialVersionUID = 1187214481256075303L; 19 | 20 | /** 21 | * map 22 | */ 23 | private Map allFieldsMap; 24 | 25 | @SerializedName("msg_signature") 26 | private String msgSignature; 27 | 28 | @SerializedName("timeStamp") 29 | private String timestamp; 30 | 31 | private String nonce; 32 | 33 | private String encrypt; 34 | 35 | /** 36 | * 转换成加密的json格式 37 | */ 38 | public String toEncryptedJson() { 39 | return DtGsonBuilder.create().toJson(this); 40 | } 41 | 42 | public static DtEventOutMessage toEncrypted(DtConfigStorage configStorage, Boolean success) { 43 | DtCryptUtil cryptUtil = new DtCryptUtil(configStorage); 44 | 45 | Map encryptedMap; 46 | if (Boolean.TRUE.equals(success)) { 47 | encryptedMap = cryptUtil.getEncryptedMap("success"); 48 | } else { 49 | encryptedMap = cryptUtil.getEncryptedMap("error"); 50 | } 51 | return DtEventOutMessage.builder() 52 | .allFieldsMap(encryptedMap) 53 | .msgSignature(encryptedMap.get("msg_signature")) 54 | .timestamp(encryptedMap.get("timeStamp")) 55 | .nonce(encryptedMap.get("nonce")) 56 | .encrypt(encryptedMap.get("encrypt")) 57 | .build(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/DtMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.builder.*; 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonObject; 6 | import lombok.Data; 7 | import org.apache.commons.collections4.CollectionUtils; 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import java.io.Serializable; 11 | import java.util.List; 12 | 13 | import static com.github.tingyugetc520.ali.dingtalk.constant.DtConstant.AppMsgType.*; 14 | 15 | /** 16 | * 消息. 17 | */ 18 | @Data 19 | public class DtMessage implements Serializable { 20 | private static final long serialVersionUID = -2082278303476631708L; 21 | 22 | private String msgType; 23 | 24 | /** 25 | * text 26 | */ 27 | private String content; 28 | /** 29 | * image file voice 30 | */ 31 | private String mediaId; 32 | /** 33 | * voice 34 | */ 35 | private Integer duration; 36 | /** 37 | * link oa 38 | */ 39 | private String messageUrl; 40 | /** 41 | * link 42 | */ 43 | private String picUrl; 44 | /** 45 | * link md card 46 | */ 47 | private String title; 48 | /** 49 | * link md 50 | */ 51 | private String text; 52 | /** 53 | * card 54 | */ 55 | private String markdown; 56 | /** 57 | * card 58 | */ 59 | private String singleTitle; 60 | /** 61 | * card 62 | */ 63 | private String singleUrl; 64 | 65 | /** 66 | * card 67 | */ 68 | private String btnOrientation; 69 | 70 | /** 71 | * card 72 | * 0:竖直排列 73 | * 1:横向排列 74 | */ 75 | private List btnList; 76 | 77 | /** 78 | * oa 79 | */ 80 | private OABuilder.OAStatusBar statusBar; 81 | 82 | /** 83 | * oa 84 | */ 85 | private OABuilder.OAHead oaHead; 86 | /** 87 | * oa 88 | */ 89 | private OABuilder.OABody oaBody; 90 | 91 | 92 | /** 93 | * 获得文本消息builder. 94 | */ 95 | public static TextBuilder TEXT() { 96 | return new TextBuilder(); 97 | } 98 | 99 | /** 100 | * 获得图片消息builder. 101 | */ 102 | public static ImageBuilder IMAGE() { 103 | return new ImageBuilder(); 104 | } 105 | 106 | /** 107 | * 获得语音消息builder. 108 | */ 109 | public static VoiceBuilder VOICE() { 110 | return new VoiceBuilder(); 111 | } 112 | 113 | /** 114 | * 获得文件消息builder. 115 | */ 116 | public static FileBuilder FILE() { 117 | return new FileBuilder(); 118 | } 119 | 120 | /** 121 | * 获得markdown消息builder. 122 | */ 123 | public static MarkdownBuilder MARKDOWN() { 124 | return new MarkdownBuilder(); 125 | } 126 | 127 | /** 128 | * 获得卡片消息builder. 129 | */ 130 | public static ActionCardBuilder ACTIONCARD() { 131 | return new ActionCardBuilder(); 132 | } 133 | 134 | /** 135 | * 请使用. 136 | * {@link com.github.tingyugetc520.ali.dingtalk.constant.DtConstant.AppMsgType} 137 | * 138 | * @param msgType 消息类型 139 | */ 140 | public void setMsgType(String msgType) { 141 | this.msgType = msgType; 142 | } 143 | 144 | public JsonObject toJsonObject() { 145 | JsonObject messageJson = new JsonObject(); 146 | messageJson.addProperty("msgtype", this.getMsgType()); 147 | 148 | this.handleMsgType(messageJson); 149 | return messageJson; 150 | } 151 | 152 | public String toJson() { 153 | return toJsonObject().toString(); 154 | } 155 | 156 | private void handleMsgType(JsonObject messageJson) { 157 | switch (this.getMsgType()) { 158 | case TEXT: { 159 | JsonObject text = new JsonObject(); 160 | text.addProperty("content", this.getContent()); 161 | messageJson.add("text", text); 162 | break; 163 | } 164 | case IMAGE: { 165 | JsonObject image = new JsonObject(); 166 | image.addProperty("media_id", this.getMediaId()); 167 | messageJson.add("image", image); 168 | break; 169 | } 170 | case VOICE: { 171 | JsonObject voice = new JsonObject(); 172 | voice.addProperty("media_id", this.getMediaId()); 173 | voice.addProperty("duration", this.duration); 174 | messageJson.add("voice", voice); 175 | break; 176 | } 177 | case FILE: { 178 | JsonObject file = new JsonObject(); 179 | file.addProperty("media_id", this.getMediaId()); 180 | messageJson.add("file", file); 181 | break; 182 | } 183 | case LINK: { 184 | JsonObject link = new JsonObject(); 185 | link.addProperty("messageUrl", this.getMessageUrl()); 186 | link.addProperty("picUrl", this.getPicUrl()); 187 | link.addProperty("title", this.getTitle()); 188 | link.addProperty("text", this.getText()); 189 | messageJson.add("link", link); 190 | break; 191 | } 192 | case OA: { 193 | JsonObject oa = new JsonObject(); 194 | oa.addProperty("messageUrl", this.getMessageUrl()); 195 | 196 | if (this.getOaHead() != null) { 197 | JsonObject headJson = new JsonObject(); 198 | headJson.addProperty("bgcolor", this.getOaHead().getBgColor()); 199 | headJson.addProperty("text", this.getOaHead().getText()); 200 | oa.add("head", headJson); 201 | } 202 | 203 | if (this.getStatusBar() != null) { 204 | JsonObject statusJson = new JsonObject(); 205 | statusJson.addProperty("status_value", this.getStatusBar().getValue()); 206 | statusJson.addProperty("status_bg", this.getStatusBar().getBgColor()); 207 | oa.add("status_bar", statusJson); 208 | } 209 | 210 | if (this.getOaBody() != null) { 211 | JsonObject bodyJson = new JsonObject(); 212 | bodyJson.addProperty("title", this.getOaBody().getTitle()); 213 | 214 | if (CollectionUtils.isNotEmpty(this.oaBody.getForm())) { 215 | JsonArray formJsonArray = new JsonArray(); 216 | for (OABuilder.OABodyForm form : this.getOaBody().getForm()) { 217 | if (form == null) { 218 | continue; 219 | } 220 | 221 | JsonObject formJson = new JsonObject(); 222 | formJson.addProperty("key", form.getKey()); 223 | formJson.addProperty("value", form.getValue()); 224 | formJsonArray.add(formJson); 225 | } 226 | bodyJson.add("form", formJsonArray); 227 | } 228 | 229 | if (this.oaBody.getRich() != null) { 230 | JsonObject richJson = new JsonObject(); 231 | richJson.addProperty("num", this.getOaBody().getRich().getNum()); 232 | richJson.addProperty("unit", this.getOaBody().getRich().getUnit()); 233 | bodyJson.add("rich", richJson); 234 | } 235 | 236 | bodyJson.addProperty("content", this.getOaBody().getContent()); 237 | bodyJson.addProperty("image", this.getOaBody().getImage()); 238 | bodyJson.addProperty("file_count", this.getOaBody().getFileCount()); 239 | bodyJson.addProperty("author", this.getOaBody().getAuthor()); 240 | oa.add("body", bodyJson); 241 | } 242 | 243 | messageJson.add("oa", oa); 244 | break; 245 | } 246 | case MARKDOWN: { 247 | JsonObject md = new JsonObject(); 248 | md.addProperty("title", this.getTitle()); 249 | md.addProperty("text", this.getText()); 250 | messageJson.add("markdown", md); 251 | break; 252 | } 253 | case ACTIONCARD: { 254 | JsonObject card = new JsonObject(); 255 | card.addProperty("title", this.getTitle()); 256 | card.addProperty("markdown", this.getMarkdown()); 257 | 258 | if (StringUtils.isNotBlank(this.getSingleTitle())) { 259 | card.addProperty("single_title", this.getSingleTitle()); 260 | } 261 | if (StringUtils.isNotBlank(this.getSingleUrl())) { 262 | card.addProperty("single_url", this.getSingleUrl()); 263 | } 264 | 265 | if (StringUtils.isNotBlank(this.getBtnOrientation())) { 266 | card.addProperty("btn_orientation", this.getBtnOrientation()); 267 | } 268 | 269 | if (CollectionUtils.isNotEmpty(this.getBtnList())) { 270 | JsonArray btnJsonArray = new JsonArray(); 271 | for (ActionCardBuilder.CardBtn btn : this.getBtnList()) { 272 | if (btn == null) { 273 | continue; 274 | } 275 | 276 | JsonObject btnJson = new JsonObject(); 277 | btnJson.addProperty("title", btn.getTitle()); 278 | btnJson.addProperty("action_url", btn.getActionUrl()); 279 | 280 | btnJsonArray.add(btnJson); 281 | } 282 | card.add("btn_json_list", btnJsonArray); 283 | } 284 | 285 | messageJson.add("action_card", card); 286 | break; 287 | } 288 | default: { 289 | // do nothing 290 | } 291 | } 292 | } 293 | 294 | } 295 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/builder/ActionCardBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message.builder; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; 4 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 14 | */ 15 | public class ActionCardBuilder extends BaseBuilder { 16 | private String title; 17 | private String markdown; 18 | 19 | private String singleTitle; 20 | private String singleUrl; 21 | 22 | private String btnOrientation; 23 | private List btnList; 24 | 25 | public ActionCardBuilder() { 26 | this.msgType = DtConstant.AppMsgType.ACTIONCARD; 27 | } 28 | 29 | public ActionCardBuilder title(String title) { 30 | this.title = title; 31 | return this; 32 | } 33 | 34 | public ActionCardBuilder markdown(String markdown) { 35 | this.markdown = markdown; 36 | return this; 37 | } 38 | 39 | public ActionCardBuilder singleTitle(String singleTitle) { 40 | this.singleTitle = singleTitle; 41 | return this; 42 | } 43 | 44 | public ActionCardBuilder singleUrl(String singleUrl) { 45 | this.singleUrl = singleUrl; 46 | return this; 47 | } 48 | 49 | @Override 50 | public DtMessage build() { 51 | DtMessage m = super.build(); 52 | m.setTitle(this.title); 53 | m.setMarkdown(this.markdown); 54 | 55 | m.setSingleTitle(this.singleTitle); 56 | m.setSingleUrl(this.singleUrl); 57 | 58 | m.setBtnOrientation(this.btnOrientation); 59 | m.setBtnList(this.btnList); 60 | return m; 61 | } 62 | 63 | @Data 64 | @NoArgsConstructor 65 | @AllArgsConstructor 66 | @Builder 67 | public static class CardBtn { 68 | private String title; 69 | private String actionUrl; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/builder/BaseBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message.builder; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; 4 | 5 | import java.util.List; 6 | 7 | public class BaseBuilder { 8 | protected Long agentId; 9 | protected List userIds; 10 | protected List departIds; 11 | protected Boolean toAllUser; 12 | protected String msgType; 13 | 14 | public T agentId(Long agentId) { 15 | this.agentId = agentId; 16 | return (T) this; 17 | } 18 | 19 | public T userIds(List userIds) { 20 | this.userIds = userIds; 21 | return (T) this; 22 | } 23 | 24 | public T departIds(List departIds) { 25 | this.departIds = departIds; 26 | return (T) this; 27 | } 28 | 29 | public T toAllUser(Boolean toAllUser) { 30 | this.toAllUser = toAllUser; 31 | return (T) this; 32 | } 33 | 34 | public DtMessage build() { 35 | DtMessage m = new DtMessage(); 36 | m.setMsgType(this.msgType); 37 | return m; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/builder/FileBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message.builder; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; 4 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 5 | 6 | /** 7 | * 8 | */ 9 | public final class FileBuilder extends BaseBuilder { 10 | private String mediaId; 11 | 12 | public FileBuilder() { 13 | this.msgType = DtConstant.AppMsgType.FILE; 14 | } 15 | 16 | public FileBuilder mediaId(String mediaId) { 17 | this.mediaId = mediaId; 18 | return this; 19 | } 20 | 21 | @Override 22 | public DtMessage build() { 23 | DtMessage m = super.build(); 24 | m.setMediaId(this.mediaId); 25 | return m; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/builder/ImageBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message.builder; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; 4 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 5 | 6 | /** 7 | * 8 | */ 9 | public final class ImageBuilder extends BaseBuilder { 10 | private String mediaId; 11 | 12 | public ImageBuilder() { 13 | this.msgType = DtConstant.AppMsgType.IMAGE; 14 | } 15 | 16 | public ImageBuilder mediaId(String mediaId) { 17 | this.mediaId = mediaId; 18 | return this; 19 | } 20 | 21 | @Override 22 | public DtMessage build() { 23 | DtMessage m = super.build(); 24 | m.setMediaId(this.mediaId); 25 | return m; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/builder/LinkBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message.builder; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; 4 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 5 | 6 | /** 7 | * 8 | */ 9 | public final class LinkBuilder extends BaseBuilder { 10 | private String messageUrl; 11 | private String picUrl; 12 | private String title; 13 | private String text; 14 | 15 | public LinkBuilder() { 16 | this.msgType = DtConstant.AppMsgType.LINK; 17 | } 18 | 19 | public LinkBuilder messageUrl(String messageUrl) { 20 | this.messageUrl = messageUrl; 21 | return this; 22 | } 23 | 24 | public LinkBuilder picUrl(String picUrl) { 25 | this.picUrl = picUrl; 26 | return this; 27 | } 28 | 29 | public LinkBuilder title(String title) { 30 | this.title = title; 31 | return this; 32 | } 33 | 34 | public LinkBuilder text(String text) { 35 | this.text = text; 36 | return this; 37 | } 38 | 39 | @Override 40 | public DtMessage build() { 41 | DtMessage m = super.build(); 42 | m.setMessageUrl(this.messageUrl); 43 | m.setPicUrl(this.picUrl); 44 | m.setTitle(this.title); 45 | m.setText(this.text); 46 | return m; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/builder/MarkdownBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message.builder; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; 4 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 5 | 6 | /** 7 | * 8 | */ 9 | public class MarkdownBuilder extends BaseBuilder { 10 | private String title; 11 | private String text; 12 | 13 | public MarkdownBuilder() { 14 | this.msgType = DtConstant.AppMsgType.MARKDOWN; 15 | } 16 | 17 | public MarkdownBuilder content(String title) { 18 | this.title = title; 19 | return this; 20 | } 21 | 22 | public MarkdownBuilder text(String text) { 23 | this.text = text; 24 | return this; 25 | } 26 | 27 | @Override 28 | public DtMessage build() { 29 | DtMessage m = super.build(); 30 | m.setTitle(this.title); 31 | m.setText(this.text); 32 | return m; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/builder/OABuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message.builder; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; 4 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * oa消息 14 | */ 15 | public class OABuilder extends BaseBuilder { 16 | private String messageUrl; 17 | private String picUrl; 18 | private OAStatusBar statusBar; 19 | private OAHead oaHead; 20 | private OABody oaBody; 21 | 22 | public OABuilder() { 23 | this.msgType = DtConstant.AppMsgType.OA; 24 | } 25 | 26 | public OABuilder messageUrl(String messageUrl) { 27 | this.messageUrl = messageUrl; 28 | return this; 29 | } 30 | 31 | public OABuilder picUrl(String picUrl) { 32 | this.picUrl = picUrl; 33 | return this; 34 | } 35 | 36 | public OABuilder statusBar(OAStatusBar statusBar) { 37 | this.statusBar = statusBar; 38 | return this; 39 | } 40 | 41 | public OABuilder oaHead(OAHead oaHead) { 42 | this.oaHead = oaHead; 43 | return this; 44 | } 45 | 46 | public OABuilder messageUrl(OABody oaBody) { 47 | this.oaBody = oaBody; 48 | return this; 49 | } 50 | 51 | @Override 52 | public DtMessage build() { 53 | DtMessage m = super.build(); 54 | m.setMessageUrl(this.messageUrl); 55 | m.setPicUrl(this.picUrl); 56 | m.setOaHead(this.oaHead); 57 | m.setStatusBar(this.statusBar); 58 | m.setOaBody(this.oaBody); 59 | return m; 60 | } 61 | 62 | @Data 63 | @NoArgsConstructor 64 | @AllArgsConstructor 65 | @Builder 66 | public static class OAStatusBar { 67 | private String value; 68 | private String bgColor; 69 | } 70 | 71 | @Data 72 | @NoArgsConstructor 73 | @AllArgsConstructor 74 | @Builder 75 | public static class OAHead { 76 | private String bgColor; 77 | private String text; 78 | } 79 | 80 | @Data 81 | @NoArgsConstructor 82 | @AllArgsConstructor 83 | @Builder 84 | public static class OABody { 85 | private String title; 86 | private List form; 87 | private OABodyRich rich; 88 | private String content; 89 | private String image; 90 | private String fileCount; 91 | private String author; 92 | } 93 | 94 | @Data 95 | @NoArgsConstructor 96 | @AllArgsConstructor 97 | @Builder 98 | public static class OABodyForm { 99 | private String key; 100 | private String value; 101 | } 102 | 103 | @Data 104 | @NoArgsConstructor 105 | @AllArgsConstructor 106 | @Builder 107 | public static class OABodyRich { 108 | private String num; 109 | private String unit; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/builder/TextBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message.builder; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; 4 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 5 | 6 | /** 7 | * 8 | */ 9 | public final class TextBuilder extends BaseBuilder { 10 | private String content; 11 | 12 | public TextBuilder() { 13 | this.msgType = DtConstant.AppMsgType.TEXT; 14 | } 15 | 16 | public TextBuilder content(String content) { 17 | this.content = content; 18 | return this; 19 | } 20 | 21 | @Override 22 | public DtMessage build() { 23 | DtMessage m = super.build(); 24 | m.setContent(this.content); 25 | return m; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/message/builder/VoiceBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.message.builder; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; 4 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 5 | 6 | /** 7 | * 8 | */ 9 | public final class VoiceBuilder extends BaseBuilder { 10 | private String mediaId; 11 | private Integer duration; 12 | 13 | public VoiceBuilder() { 14 | this.msgType = DtConstant.AppMsgType.VOICE; 15 | } 16 | 17 | public VoiceBuilder mediaId(String mediaId) { 18 | this.mediaId = mediaId; 19 | return this; 20 | } 21 | 22 | public VoiceBuilder duration(Integer duration) { 23 | this.duration = duration; 24 | return this; 25 | } 26 | 27 | @Override 28 | public DtMessage build() { 29 | DtMessage m = super.build(); 30 | m.setMediaId(this.mediaId); 31 | m.setDuration(this.duration); 32 | return m; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/oauth/DtOauth2UserInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.oauth; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import com.google.gson.annotations.SerializedName; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | 13 | /** 14 | * 用oauth2获取用户信息的结果类 15 | */ 16 | @Data 17 | @Accessors(chain = true) 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | @Builder 21 | public class DtOauth2UserInfo implements Serializable { 22 | private static final long serialVersionUID = 4259473057696770084L; 23 | 24 | @SerializedName("userid") 25 | private String userId; 26 | private String name; 27 | private String deviceId; 28 | /** 29 | * 是否是管理员 30 | */ 31 | @SerializedName("is_sys") 32 | private Boolean isSys; 33 | /** 34 | * 级别 35 | */ 36 | @SerializedName("sys_level") 37 | private Integer sysLevel; 38 | 39 | 40 | public static DtOauth2UserInfo fromJson(String json) { 41 | return DtGsonBuilder.create().fromJson(json, DtOauth2UserInfo.class); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/user/DtUnionId2UserId.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.user; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * unionId to userId info 14 | */ 15 | @Data 16 | @Accessors(chain = true) 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | @Builder 20 | public class DtUnionId2UserId implements Serializable { 21 | private static final long serialVersionUID = 618690417174457299L; 22 | 23 | private String userId; 24 | private Integer contactType; 25 | 26 | public static DtUnionId2UserId fromJson(String json) { 27 | return DtGsonBuilder.create().fromJson(json, DtUnionId2UserId.class); 28 | } 29 | 30 | public String toJson() { 31 | return DtGsonBuilder.create().toJson(this); 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/user/DtUser.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.user; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import com.google.gson.annotations.SerializedName; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | import java.util.Date; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * 用户信息 18 | */ 19 | @Data 20 | @Accessors(chain = true) 21 | @NoArgsConstructor 22 | @AllArgsConstructor 23 | @Builder 24 | public class DtUser implements Serializable { 25 | private static final long serialVersionUID = -5696099236344075582L; 26 | 27 | /** 28 | * userid 29 | */ 30 | private String userId; 31 | /** 32 | * unionid 33 | */ 34 | private String unionId; 35 | 36 | private String managerUserId; 37 | private Date hiredDate; 38 | private String tel; 39 | 40 | private String remark; 41 | private String workPlace; 42 | 43 | private String name; 44 | private String position; 45 | private String mobile; 46 | private String stateCode; 47 | private String email; 48 | /** 49 | * 员工企业邮箱 50 | */ 51 | private String orgEmail; 52 | 53 | private Boolean isSenior; 54 | /** 55 | * jobnumber 56 | */ 57 | private String jobNumber; 58 | private Boolean active; 59 | private String avatar; 60 | /** 61 | * extattr 62 | * 扩展属性,可以设置多种属性 63 | */ 64 | private Map extAttr; 65 | 66 | private List roles; 67 | 68 | /** 69 | * department 70 | */ 71 | private List departIds; 72 | /** 73 | * orderInDepts 74 | */ 75 | private Map departOrders; 76 | 77 | private Boolean isAdmin; 78 | /** 79 | * isLeaderInDepts 80 | */ 81 | private Map isLeaderInDeparts; 82 | 83 | private Boolean isHide; 84 | private Boolean isBoss; 85 | private Boolean realAuthed; 86 | 87 | public static DtUser fromJson(String json) { 88 | return DtGsonBuilder.create().fromJson(json, DtUser.class); 89 | } 90 | 91 | public String toJson() { 92 | return DtGsonBuilder.create().toJson(this); 93 | } 94 | 95 | @Data 96 | @NoArgsConstructor 97 | @AllArgsConstructor 98 | @Builder 99 | public static class Role { 100 | private Long id; 101 | private String name; 102 | private Integer type; 103 | private String groupName; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/bean/user/DtUserV2.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.user; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | /** 15 | * 用户信息. 16 | */ 17 | @Deprecated 18 | @Data 19 | @Accessors(chain = true) 20 | public class DtUserV2 implements Serializable { 21 | private static final long serialVersionUID = -5696099236344075582L; 22 | 23 | private String userId; 24 | private String unionId; 25 | private String name; 26 | private String avatar; 27 | private String mobile; 28 | private Boolean hideMobile; 29 | private String telephone; 30 | private String jobNumber; 31 | private String title; 32 | private String email; 33 | private String workPlace; 34 | private String remark; 35 | /** 36 | * 所属部门ID列表 37 | */ 38 | private Long[] departIds; 39 | /** 40 | * 员工在对应的部门中的排序 41 | */ 42 | private List orders; 43 | private String extension; 44 | private Date hiredDate; 45 | private Boolean active; 46 | private Boolean realAuthed; 47 | private Boolean senior; 48 | private Boolean admin; 49 | private Boolean boss; 50 | 51 | public static DtUserV2 fromJson(String json) { 52 | return DtGsonBuilder.create().fromJson(json, DtUserV2.class); 53 | } 54 | 55 | public String toJson() { 56 | return DtGsonBuilder.create().toJson(this); 57 | } 58 | 59 | @Data 60 | @Builder 61 | @NoArgsConstructor 62 | @AllArgsConstructor 63 | public static class DepartOrder { 64 | private Long departId; 65 | private Long order; 66 | } 67 | 68 | @Data 69 | @Builder 70 | @NoArgsConstructor 71 | @AllArgsConstructor 72 | public static class DepartLeader { 73 | private Long departId; 74 | private Boolean leader; 75 | } 76 | 77 | @Data 78 | @Builder 79 | @NoArgsConstructor 80 | @AllArgsConstructor 81 | public static class UserRole { 82 | private Long id; 83 | private String name; 84 | private String groupName; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/config/DtConfigStorage.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.config; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.DtAccessToken; 4 | 5 | import java.util.concurrent.locks.Lock; 6 | 7 | /** 8 | * 钉钉应用配置 9 | */ 10 | public interface DtConfigStorage { 11 | 12 | /** 13 | * 设置钉钉服务器 baseUrl. 14 | * 默认值是 https://oapi.dingtalk.com, 如果使用默认值,则不需要调用 setBaseApiUrl 15 | * 16 | * @param baseUrl 钉钉服务器 Url 17 | */ 18 | void setBaseApiUrl(String baseUrl); 19 | 20 | /** 21 | * 获取钉钉 API Url. 22 | */ 23 | String getApiUrl(String path); 24 | 25 | String getAccessToken(); 26 | 27 | Lock getAccessTokenLock(); 28 | 29 | boolean isAccessTokenExpired(); 30 | 31 | /** 32 | * 强制将access token过期掉. 33 | */ 34 | void expireAccessToken(); 35 | 36 | void updateAccessToken(DtAccessToken accessToken); 37 | 38 | void updateAccessToken(String accessToken, int expiresIn); 39 | 40 | String getCorpId(); 41 | 42 | Long getAgentId(); 43 | 44 | String getAppKey(); 45 | 46 | String getAppSecret(); 47 | 48 | String getAesKey(); 49 | 50 | String getToken(); 51 | 52 | String getAppKeyOrCorpId(); 53 | 54 | long getExpiresTime(); 55 | 56 | int getHttpProxyType(); 57 | 58 | String getHttpProxyServer(); 59 | 60 | String getHttpProxyHost(); 61 | 62 | int getHttpProxyPort(); 63 | 64 | String getHttpProxyUsername(); 65 | 66 | String getHttpProxyPassword(); 67 | 68 | /** 69 | * 是否自动刷新token 70 | * 71 | * @return . 72 | */ 73 | boolean autoRefreshToken(); 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/config/impl/DtDefaultConfigImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.config.impl; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.DtAccessToken; 4 | import com.github.tingyugetc520.ali.dingtalk.config.DtConfigStorage; 5 | import com.github.tingyugetc520.ali.dingtalk.util.http.HttpProxyType; 6 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 7 | import com.github.tingyugetc520.ali.dingtalk.constant.DtApiPathConstant; 8 | 9 | import java.io.Serializable; 10 | import java.util.concurrent.locks.Lock; 11 | import java.util.concurrent.locks.ReentrantLock; 12 | 13 | /** 14 | * 基于内存的配置provider,在实际生产环境中应该将这些配置持久化. 15 | */ 16 | public class DtDefaultConfigImpl implements DtConfigStorage, Serializable { 17 | private static final long serialVersionUID = 1154541446729462780L; 18 | 19 | private volatile String corpId; 20 | private volatile Long agentId; 21 | private volatile String appKey; 22 | private volatile String appSecret; 23 | 24 | private volatile String aesKey; 25 | private volatile String token; 26 | /** 27 | * 消息推送的加解密需要使用appKey或者corpId,所以需要指定这个属性是什么 28 | * 当在开发者后台手动配置回调时此属性为appKey 29 | * 当采用HTTP回调注册API配置回调时此属性为corpId 30 | */ 31 | private volatile String appKeyOrCorpId; 32 | 33 | protected volatile String accessToken; 34 | protected transient Lock accessTokenLock = new ReentrantLock(); 35 | private volatile long expiresTime; 36 | 37 | /** 38 | * 反向代理与正向代理 39 | */ 40 | private volatile int httpProxyType; 41 | private volatile String httpProxyServer; 42 | private volatile String httpProxyHost; 43 | private volatile int httpProxyPort; 44 | private volatile String httpProxyUsername; 45 | private volatile String httpProxyPassword; 46 | 47 | private volatile String baseApiUrl; 48 | 49 | @Override 50 | public void setBaseApiUrl(String baseUrl) { 51 | this.baseApiUrl = baseUrl; 52 | } 53 | 54 | @Override 55 | public String getApiUrl(String path) { 56 | if (baseApiUrl == null) { 57 | // 反向代理 58 | HttpProxyType proxyType = HttpProxyType.findByCode(getHttpProxyType()); 59 | if (HttpProxyType.REVERSE.equals(proxyType)) { 60 | baseApiUrl = httpProxyServer; 61 | } else { 62 | baseApiUrl = DtApiPathConstant.DEFAULT_DT_BASE_URL; 63 | } 64 | } 65 | return baseApiUrl + path; 66 | } 67 | 68 | @Override 69 | public String getAccessToken() { 70 | return this.accessToken; 71 | } 72 | 73 | @Override 74 | public Lock getAccessTokenLock() { 75 | return this.accessTokenLock; 76 | } 77 | 78 | public void setAccessToken(String accessToken) { 79 | this.accessToken = accessToken; 80 | } 81 | 82 | @Override 83 | public boolean isAccessTokenExpired() { 84 | return System.currentTimeMillis() > this.expiresTime; 85 | } 86 | 87 | @Override 88 | public void expireAccessToken() { 89 | this.expiresTime = 0; 90 | } 91 | 92 | @Override 93 | public synchronized void updateAccessToken(DtAccessToken accessToken) { 94 | updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); 95 | } 96 | 97 | @Override 98 | public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { 99 | this.accessToken = accessToken; 100 | this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; 101 | } 102 | 103 | @Override 104 | public String getCorpId() { 105 | return this.corpId; 106 | } 107 | 108 | public void setCorpId(String corpId) { 109 | this.corpId = corpId; 110 | } 111 | 112 | @Override 113 | public Long getAgentId() { 114 | return this.agentId; 115 | } 116 | 117 | public void setAgentId(Long agentId) { 118 | this.agentId = agentId; 119 | } 120 | 121 | @Override 122 | public String getAppKey() { 123 | return appKey; 124 | } 125 | 126 | public void setAppKey(String appKey) { 127 | this.appKey = appKey; 128 | } 129 | 130 | @Override 131 | public String getAppSecret() { 132 | return appSecret; 133 | } 134 | 135 | public void setAppSecret(String appSecret) { 136 | this.appSecret = appSecret; 137 | } 138 | 139 | @Override 140 | public String getAesKey() { 141 | return aesKey; 142 | } 143 | 144 | public void setAesKey(String aesKey) { 145 | this.aesKey = aesKey; 146 | } 147 | 148 | @Override 149 | public String getToken() { 150 | return token; 151 | } 152 | 153 | public void setToken(String token) { 154 | this.token = token; 155 | } 156 | 157 | @Override 158 | public String getAppKeyOrCorpId() { 159 | return appKeyOrCorpId; 160 | } 161 | 162 | public void setAppKeyOrCorpId(String appKeyOrCorpId) { 163 | this.appKeyOrCorpId = appKeyOrCorpId; 164 | } 165 | 166 | @Override 167 | public long getExpiresTime() { 168 | return this.expiresTime; 169 | } 170 | 171 | public void setExpiresTime(long expiresTime) { 172 | this.expiresTime = expiresTime; 173 | } 174 | 175 | @Override 176 | public int getHttpProxyType() { 177 | return this.httpProxyType; 178 | } 179 | 180 | public void setHttpProxyType(int httpProxyType) { 181 | this.httpProxyType = httpProxyType; 182 | } 183 | 184 | @Override 185 | public String getHttpProxyServer() { 186 | return this.httpProxyServer; 187 | } 188 | 189 | public void setHttpProxyServer(String httpProxyServer) { 190 | this.httpProxyServer = httpProxyServer; 191 | } 192 | 193 | @Override 194 | public String getHttpProxyHost() { 195 | return this.httpProxyHost; 196 | } 197 | 198 | public void setHttpProxyHost(String httpProxyHost) { 199 | this.httpProxyHost = httpProxyHost; 200 | } 201 | 202 | @Override 203 | public int getHttpProxyPort() { 204 | return this.httpProxyPort; 205 | } 206 | 207 | public void setHttpProxyPort(int httpProxyPort) { 208 | this.httpProxyPort = httpProxyPort; 209 | } 210 | 211 | @Override 212 | public String getHttpProxyUsername() { 213 | return this.httpProxyUsername; 214 | } 215 | 216 | public void setHttpProxyUsername(String httpProxyUsername) { 217 | this.httpProxyUsername = httpProxyUsername; 218 | } 219 | 220 | @Override 221 | public String getHttpProxyPassword() { 222 | return this.httpProxyPassword; 223 | } 224 | 225 | public void setHttpProxyPassword(String httpProxyPassword) { 226 | this.httpProxyPassword = httpProxyPassword; 227 | } 228 | 229 | @Override 230 | public boolean autoRefreshToken() { 231 | return true; 232 | } 233 | 234 | @Override 235 | public String toString() { 236 | return DtGsonBuilder.create().toJson(this); 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/constant/DtApiPathConstant.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.constant; 2 | 3 | import lombok.experimental.UtilityClass; 4 | 5 | /** 6 | * api地址常量类 7 | */ 8 | public final class DtApiPathConstant { 9 | public static final String DEFAULT_DT_BASE_URL = "https://oapi.dingtalk.com"; 10 | 11 | public static final String GET_JSAPI_TICKET = "/get_jsapi_ticket"; 12 | public static final String GET_TOKEN = "/gettoken?appkey=%s&appsecret=%s"; 13 | 14 | /** 15 | * 消息通知相关接口 16 | */ 17 | @UtilityClass 18 | public static class Message { 19 | /** 20 | * 工作通知 21 | */ 22 | public static class AppCordConversation { 23 | public static final String SEND = "/topapi/message/corpconversation/asyncsend_v2"; 24 | public static final String STATUS_BAR_UPDATE = "/topapi/message/corpconversation/status_bar/update"; 25 | public static final String RECALL = "/topapi/message/corpconversation/recall"; 26 | } 27 | } 28 | 29 | /** 30 | * 免登 31 | */ 32 | @UtilityClass 33 | public static class OAuth2 { 34 | public static final String GET_USER_INFO = "/user/getuserinfo?code=%s"; 35 | } 36 | 37 | /** 38 | * 部门管理 39 | */ 40 | @UtilityClass 41 | public static class Department { 42 | public static final String DEPARTMENT_CREATE = "/department/create"; 43 | public static final String DEPARTMENT_UPDATE = "/department/update"; 44 | public static final String DEPARTMENT_DELETE = "/department/delete?id=%d"; 45 | public static final String DEPARTMENT_DETAIL = "/department/detail?id=%d"; 46 | public static final String DEPARTMENT_LIST = "/department/list"; 47 | } 48 | 49 | /** 50 | * 用户管理 51 | */ 52 | @UtilityClass 53 | public static class User { 54 | public static final String USER_CREATE = "/user/create"; 55 | public static final String USER_UPDATE = "/user/update"; 56 | public static final String USER_DELETE = "/user/delete?userid="; 57 | public static final String USER_GET = "/user/get?userid="; 58 | public static final String USER_ID_GET = "/user/get_by_mobile?mobile="; 59 | public static final String USER_ID_LIST = "/user/getdeptmember?deptId="; 60 | public static final String USER_LIST = "/user/listbypage?department_id=%d&offset=%d&size=%d"; 61 | public static final String USER_SIMPLE_LIST = "/user/simplelist?department_id=%d&offset=%d&size=%d"; 62 | public static final String UNION_ID_2_USER_ID = "/user/getuseridbyunionid?unionid="; 63 | } 64 | 65 | @UtilityClass 66 | public static class Agent { 67 | public static final String AGENT_AUTH_SCOPE = "/auth/scopes"; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/constant/DtConstant.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.constant; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.DtConstantUtils; 4 | import com.google.common.collect.Lists; 5 | import lombok.experimental.UtilityClass; 6 | 7 | import java.lang.reflect.Modifier; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | import static com.github.tingyugetc520.ali.dingtalk.error.DtErrorMsgEnum.*; 13 | 14 | /** 15 | * 钉钉常量 16 | */ 17 | public final class DtConstant { 18 | /** 19 | * access_token 相关错误代码 20 | * 21 | * 发生以下情况时尝试刷新access_token 22 | * 40001 获取access_token时AppSecret错误,或者access_token无效 23 | * 42001 access_token超时 24 | * 40014 不合法的access_token 25 | */ 26 | public static final List ACCESS_TOKEN_ERROR_CODES = Arrays.asList( 27 | CODE_40001.getCode(), CODE_40014.getCode(), CODE_42001.getCode() 28 | ); 29 | 30 | /** 31 | * 推送过来的事件类型 32 | */ 33 | @UtilityClass 34 | public static class EventType { 35 | /** 36 | * check url 37 | */ 38 | public static String CHECK_URL = "check_url"; 39 | 40 | /** 41 | * 通讯录变更事件 42 | */ 43 | public static class ChangeContact { 44 | /** 45 | * 用户变更-通讯录用户增加 46 | */ 47 | public static String USER_ADD_ORG = "user_add_org"; 48 | /** 49 | * 用户变更-通讯录用户更改 50 | */ 51 | public static String USER_MODIFY_ORG = "user_modify_org"; 52 | /** 53 | * 用户变更-通讯录用户离职 54 | */ 55 | public static String USER_LEAVE_ORG = "user_leave_org"; 56 | /** 57 | * 用户变更-加入企业后用户激活 58 | */ 59 | public static String USER_ACTIVE_ORG = "user_active_org"; 60 | /** 61 | * 用户变更-通讯录用户被设为管理员 62 | */ 63 | public static String ORG_ADMIN_ADD = "org_admin_add"; 64 | /** 65 | * 用户变更-通讯录用户被取消设置管理员 66 | */ 67 | public static String ORG_ADMIN_REMOVE = "org_admin_remove"; 68 | /** 69 | * 部门变更-通讯录企业部门创建 70 | */ 71 | public static String ORG_DEPT_CREATE = "org_dept_create"; 72 | /** 73 | * 部门变更-通讯录企业部门修改 74 | */ 75 | public static String ORG_DEPT_MODIFY = "org_dept_modify"; 76 | /** 77 | * 部门变更-通讯录企业部门删除 78 | */ 79 | public static String ORG_DEPT_REMOVE = "org_dept_remove"; 80 | /** 81 | * 企业信息变更-企业被解散 82 | */ 83 | public static String ORG_REMOVE = "org_remove"; 84 | /** 85 | * 企业信息变更-企业信息发生变更 86 | */ 87 | public static String ORG_CHANGE = "org_change"; 88 | /** 89 | * 角色变更-员工角色信息发生变更 90 | */ 91 | public static String LABEL_USER_CHANGE = "label_user_change"; 92 | /** 93 | * 角色变更-增加角色或者角色组 94 | */ 95 | public static String LABEL_CONF_ADD = "label_conf_add"; 96 | /** 97 | * 角色变更-删除角色或者角色组 98 | */ 99 | public static String LABEL_CONF_DEL = "label_conf_del"; 100 | /** 101 | * 角色变更-修改角色或者角色组 102 | */ 103 | public static String LABEL_CONF_MODIFY = "label_conf_modify"; 104 | 105 | } 106 | public static List ChangeContactGroup = DtConstantUtils.getEventTypeGroup(ChangeContact.class); 107 | } 108 | 109 | /** 110 | * 消息通知的消息类型. 111 | */ 112 | @UtilityClass 113 | public static class AppMsgType { 114 | /** 115 | * 文本消息. 116 | */ 117 | public static final String TEXT = "text"; 118 | /** 119 | * 图片消息. 120 | */ 121 | public static final String IMAGE = "image"; 122 | /** 123 | * 语音消息. 124 | */ 125 | public static final String VOICE = "voice"; 126 | /** 127 | * 发送文件 128 | */ 129 | public static final String FILE = "file"; 130 | /** 131 | * 链接 132 | */ 133 | public static final String LINK = "link"; 134 | /** 135 | * oa消息 136 | */ 137 | public static final String OA = "OA"; 138 | /** 139 | * markdown消息. 140 | */ 141 | public static final String MARKDOWN = "markdown"; 142 | /** 143 | * 卡片消息 144 | */ 145 | public static final String ACTIONCARD = "action_card"; 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/error/DtError.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.error; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 4 | import com.google.gson.annotations.SerializedName; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * 钉钉错误码. 12 | * 请阅读:https://ding-doc.dingtalk.com/document/app/server-api-error-codes-1 13 | */ 14 | @Data 15 | @Builder 16 | public class DtError implements Serializable { 17 | private static final long serialVersionUID = 7869786563361406291L; 18 | 19 | /** 20 | * 错误代码. 21 | */ 22 | private int errorCode; 23 | 24 | /** 25 | * 错误信息. 26 | */ 27 | private String errorMsg; 28 | 29 | private String json; 30 | 31 | public static DtError fromJson(String json) { 32 | final DtError error = DtGsonBuilder.create().fromJson(json, DtError.class); 33 | // if (error.getErrorCode() == 0) { 34 | // return error; 35 | // } 36 | 37 | // 钉钉的错误码文档特么是乱写的,还是要以实际返回为准 38 | // final String msg = DtErrorMsgEnum.findMsgByCode(error.getErrorCode()); 39 | // if (msg != null) { 40 | // error.setErrorMsg(msg); 41 | // } 42 | 43 | return error; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | if (this.json == null) { 49 | return "错误代码:" + this.errorCode + ", 错误信息:" + this.errorMsg; 50 | } 51 | 52 | return "错误代码:" + this.errorCode + ", 错误信息:" + this.errorMsg + ",钉钉原始报文:" + this.json; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/error/DtErrorException.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.error; 2 | 3 | public class DtErrorException extends Exception { 4 | private static final long serialVersionUID = -6357149550353160810L; 5 | 6 | private final DtError error; 7 | 8 | public DtErrorException(String message) { 9 | this(DtError.builder().errorCode(-1).errorMsg(message).build()); 10 | } 11 | 12 | public DtErrorException(DtError error) { 13 | super(error.toString()); 14 | this.error = error; 15 | } 16 | 17 | public DtErrorException(DtError error, Throwable cause) { 18 | super(error.toString(), cause); 19 | this.error = error; 20 | } 21 | 22 | public DtErrorException(Throwable cause) { 23 | super(cause.getMessage(), cause); 24 | this.error = DtError.builder().errorCode(-1).errorMsg(cause.getMessage()).build(); 25 | } 26 | 27 | public DtError getError() { 28 | return this.error; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/error/DtErrorMsgEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.error; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 全局错误码. 7 | * 参考文档:https://ding-doc.dingtalk.com/document/app/server-api-error-codes-1 8 | */ 9 | @Getter 10 | public enum DtErrorMsgEnum { 11 | /** 12 | * 系统繁忙;服务器暂不可用,建议稍后再重试1次,最多重试3次 13 | */ 14 | CODE_1(-1, "系统繁忙;服务器暂不可用,建议稍后再重试1次,最多重试3次。"), 15 | /** 16 | * 请求成功;接口调用成功. 17 | */ 18 | CODE_0(0, "请求成功;接口调用成功"), 19 | /** 20 | * 鉴权异常;关注返回结果里的subCode和subMsg 21 | */ 22 | CODE_88(88, "鉴权异常;关注返回结果里的subCode和subMsg"), 23 | /** 24 | * 请求的URI地址不存在;地址不存在,检查下url是否和文档里写的一致 25 | */ 26 | CODE_404(404, "请求的URI地址不存在;地址不存在,检查下url是否和文档里写的一致"), 27 | /** 28 | * 29 | */ 30 | CODE_33001(33001, "无效的企业ID;请确认access_token是否正确"), 31 | /** 32 | * 33 | */ 34 | CODE_33002(33002, "无效的微应用的名称;校验微应用的名称字段,不能为空且长度不能超过10个字符。"), 35 | /** 36 | * 37 | */ 38 | CODE_33012(33012, "无效的USERID;请检查userid是否正确,可通过获取部门用户userid列表接口获取。"), 39 | 40 | /** 41 | * 42 | */ 43 | CODE_40001(40001, "获取access_token时Secret错误,或者access_token无效;检查下access_token是否正确"), 44 | /** 45 | * 46 | */ 47 | CODE_40002(40002, "不合法的凭证类型"), 48 | /** 49 | * 无效的UserID. 50 | */ 51 | CODE_40003(40003, "无效的UserID"), 52 | /** 53 | * 54 | */ 55 | CODE_40004(40004, "不合法的媒体文件类型;检查下type字段,只支持image、voice、file"), 56 | /** 57 | * 58 | */ 59 | CODE_40005(40005, "不合法的文件类型;如果是文件类型,检查下是否是支持。目前只支持doc、docx、xls、xlsx、ppt、pptx、zip、pdf、rar。"), 60 | /** 61 | * 62 | */ 63 | CODE_40006(40006, "不合法的文件大小;检查下文件大小,image类型最大1MB,file类型最大10MB,voice类型最大2MB。"), 64 | /** 65 | * 66 | */ 67 | CODE_40007(40007, "不合法的媒体文件id;检查下mediaId是否为空,是否真实存在。可通过上传媒体文件接口获取。"), 68 | /** 69 | * 70 | */ 71 | CODE_40008(40008, "不合法的消息类型;检查下msgtype是否为空,确保它在开放平台定义的几种类型里,msgtype请参考消息类型与数据格式"), 72 | /** 73 | * 74 | */ 75 | CODE_40009(40009, "不合法的部门id;检查下部门id是否为空,是否为数字且大于0。"), 76 | /** 77 | * 78 | */ 79 | CODE_40010(40010, "不合法的父部门id;检查父部门id是否为一个数字。"), 80 | /** 81 | * 82 | */ 83 | CODE_40011(40011, "不合法的排序order;检查下order字段是否为空,是否为数字且大于0。"), 84 | /** 85 | * 86 | */ 87 | CODE_40013(40013, "不合法的CorpID;需确认CorpID是否填写正确"), 88 | /** 89 | * 不合法的access_token. 90 | */ 91 | CODE_40014(40014, "不合法的access_token"), 92 | 93 | /** 94 | * 95 | */ 96 | CODE_40018(40018, "不允许以递归方式查询部门用户列表;检查下fetchChild字段,目前不支持递归查询。"), 97 | 98 | /** 99 | * 不合法的oauth_code. 100 | */ 101 | CODE_40029(40029, "不合法的oauth_code"), 102 | /** 103 | * 104 | */ 105 | CODE_40031(40031, "不合法的UserID列表;指定的UserID列表,至少存在一个UserID不在通讯录中"), 106 | /** 107 | * 108 | */ 109 | CODE_40032(40032, "不合法的UserID列表长度;检查下列表是否为空,且长度合适。创建部门接口的userPerimits最多接收10000个。"), 110 | /** 111 | * 不合法的请求字符;不能包含\\uxxxx格式的字符. 112 | */ 113 | CODE_40033(40033, "不合法的请求字符;不能包含\\uxxxx格式的字符"), 114 | /** 115 | * 不合法的参数. 116 | */ 117 | CODE_40035(40035, "不合法的参数"), 118 | 119 | /** 120 | * 121 | */ 122 | CODE_40068(40068, "不合法的偏移量;偏移量必须大于0。"), 123 | /** 124 | * 125 | */ 126 | CODE_40069(40069, "不合法的分页大小;分页大小不合法,具体参考每个接口的参数定义。"), 127 | /** 128 | * 129 | */ 130 | CODE_40070(40070, "不合法的排序参数;具体参考获取部门成员接口里面对order字段的定义。"), 131 | 132 | /** 133 | * 缺少access_token参数. 134 | */ 135 | CODE_41001(41001, "缺少access_token参数"), 136 | /** 137 | * 缺少corpid参数. 138 | */ 139 | CODE_41002(41002, "缺少corpid参数"), 140 | /** 141 | * 缺少secret参数. 142 | */ 143 | CODE_41004(41004, "缺少secret参数"), 144 | /** 145 | * 146 | */ 147 | CODE_41005(41005, "缺少多媒体文件数据"), 148 | /** 149 | * 150 | */ 151 | CODE_41006(41006, "缺少media_id参数;检查下media_id参数是否为空。"), 152 | /** 153 | * 缺少auth code参数. 154 | */ 155 | CODE_41008(41008, "缺少auth code参数"), 156 | /** 157 | * 缺少userid参数. 158 | */ 159 | CODE_41009(41009, "缺少userid参数"), 160 | /** 161 | * 缺少url参数. 162 | */ 163 | CODE_41010(41010, "缺少url参数"), 164 | /** 165 | * 缺少agentid参数. 166 | */ 167 | CODE_41011(41011, "缺少agentid参数"), 168 | 169 | /** 170 | * 171 | */ 172 | CODE_42001(42001, "access_token已过期"), 173 | 174 | /** 175 | * 176 | */ 177 | CODE_48002(48002, "Api禁用"), 178 | 179 | /** 180 | * 应用已停用 181 | */ 182 | CODE_50003(50003, "应用已停用"), 183 | /** 184 | * 185 | */ 186 | CODE_50005(50005, "企业已禁用"), 187 | /** 188 | * 189 | */ 190 | CODE_52010(52010, "无效的corpid"), 191 | /** 192 | * 193 | */ 194 | CODE_52017(52017, "无效的jsapi列表参数;请检查dd.config中的jsApiList参数是否正确。"), 195 | /** 196 | * 197 | */ 198 | CODE_52018(52018, "无效的时间戳;请检查timestamp参数是否正确。"), 199 | /** 200 | * 201 | */ 202 | CODE_52019(52019, "无效的agentid;请检查agentid参数是否正确。"), 203 | 204 | /** 205 | * 206 | */ 207 | CODE_60001(60001, "不合法的部门名称;请检查部门名称是否正确,长度不能超过64个字符。"), 208 | /** 209 | * 210 | */ 211 | CODE_60002(60002, "部门层级深度超过限制"), 212 | /** 213 | * 214 | */ 215 | CODE_60003(60003, "部门不存在"), 216 | /** 217 | * 父部门不存在 218 | */ 219 | CODE_60004(60004, "父部门不存在"), 220 | /** 221 | * 222 | */ 223 | CODE_60005(60005, "不允许删除有成员的部门"), 224 | /** 225 | * 226 | */ 227 | CODE_60006(60006, "不允许删除有子部门的部门"), 228 | /** 229 | * 230 | */ 231 | CODE_60007(60007, "不允许删除根部门"), 232 | /** 233 | * 234 | */ 235 | CODE_60008(60008, "父部门下该部门名称已存在"), 236 | /** 237 | * 238 | */ 239 | CODE_60009(60009, "部门名称含有非法字符"), 240 | /** 241 | * 242 | */ 243 | CODE_60010(60010, "部门存在循环关系"), 244 | /** 245 | * 246 | */ 247 | CODE_60011(60011, "没有调用该接口的权限;需要修改appkey对应的权限点。请上开放平台 > 应用详情页 > 权限管理 > 添加接口权限 > 接口权限勾选对应的权限点"), 248 | /** 249 | * 250 | */ 251 | CODE_60012(60012, "不允许删除默认应用"), 252 | 253 | /** 254 | * 255 | */ 256 | CODE_60019(60019, "从部门查询人员失败;请检查该成员是否在该部门中。可通过获取部门用户userid列表接口获取。"), 257 | 258 | /** 259 | * 260 | */ 261 | CODE_60102(60102, "UserID在公司中已存在"), 262 | /** 263 | * 手机号码不合法 264 | */ 265 | CODE_60103(60103, "手机号码不合法"), 266 | /** 267 | * 268 | */ 269 | CODE_60104(60104, "手机号码在公司中已存在"), 270 | /** 271 | * 邮箱不合法 272 | */ 273 | CODE_60105(60105, "邮箱不合法"), 274 | /** 275 | * 邮箱已存在 276 | */ 277 | CODE_60106(60106, "邮箱已存在"), 278 | /** 279 | * 280 | */ 281 | CODE_60107(60107, "使用该手机登录钉钉的用户已经在企业中"), 282 | /** 283 | * 284 | */ 285 | CODE_60110(60110, "部门个数超出限制"), 286 | /** 287 | * 288 | */ 289 | CODE_60111(60111, "UserID不存在"), 290 | /** 291 | * 292 | */ 293 | CODE_60112(60112, "成员name不合法"), 294 | /** 295 | * 296 | */ 297 | CODE_70001(70001, "企业不存在或者已经被解散"), 298 | 299 | /** 300 | * 301 | */ 302 | CODE_80001(80001, "可信域名没有IPC备案,后续将不能在该域名下正常使用jssdk"), 303 | 304 | /** 305 | * 306 | */ 307 | CODE_90001(90001, "您的服务器调用钉钉开放平台所有接口的请求都被暂时禁用了"), 308 | /** 309 | * 310 | */ 311 | CODE_90002(90002, "您的服务器调用钉钉开放平台当前接口的所有请求都被暂时禁用了"), 312 | /** 313 | * 314 | */ 315 | CODE_90003(90003, "您的企业调用钉钉开放平台所有接口的请求都被暂时禁用了,仅对企业自己的Accesstoken有效"), 316 | /** 317 | * 318 | */ 319 | CODE_90004(90004, "您当前使用的CorpId及CorpSecret被暂时禁用了,仅对企业自己的Accesstoken有效"), 320 | /** 321 | * 322 | */ 323 | CODE_90005(90005, "您的企业调用当前接口次数过多,请求被暂时禁用了,仅对企业自己的Accesstoken有效"), 324 | /** 325 | * 326 | */ 327 | CODE_90006(90006, "您当前使用的CorpId及CorpSecret调用当前接口次数过多,请求被暂时禁用了,仅对企业自己的Accesstoken有效"), 328 | /** 329 | * 330 | */ 331 | CODE_853003(853003, "accessKey参数不合法,必须是钉钉开放平台存在的appId"), 332 | ; 333 | 334 | private int code; 335 | private String msg; 336 | 337 | DtErrorMsgEnum(int code, String msg) { 338 | this.code = code; 339 | this.msg = msg; 340 | } 341 | 342 | /** 343 | * 通过错误代码查找其中文含义.. 344 | */ 345 | public static String findMsgByCode(int code) { 346 | for (DtErrorMsgEnum value : DtErrorMsgEnum.values()) { 347 | if (value.code == code) { 348 | return value.msg; 349 | } 350 | } 351 | 352 | return null; 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/error/DtRuntimeErrorEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.error; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * dt异常枚举 7 | */ 8 | @Getter 9 | public enum DtRuntimeErrorEnum { 10 | /** 11 | * 12 | */ 13 | DT_HTTP_CALL_FAILED(400, "请求钉钉接口异常,请检查地址是否正确"), 14 | 15 | /** 16 | * 17 | */ 18 | ENCRYPTION_PLAINTEXT_ILLEGAL(900001, "加密明文文本非法"), 19 | ENCRYPTION_TIMESTAMP_ILLEGAL(900002, "加密时间戳参数非法"), 20 | ENCRYPTION_NONCE_ILLEGAL(900003, "加密随机字符串参数非法"), 21 | AES_KEY_ILLEGAL(900004, "签名不匹配"), 22 | SIGNATURE_NOT_MATCH(900005, "签名计算失败"), 23 | COMPUTE_SIGNATURE_ERROR(900006, "不合法的aes key"), 24 | COMPUTE_ENCRYPT_TEXT_ERROR(900007, "计算加密文字错误"), 25 | COMPUTE_DECRYPT_TEXT_ERROR(900008, "计算解密文字错误"), 26 | COMPUTE_DECRYPT_TEXT_LENGTH_ERROR(900009, "计算解密文字长度不匹配"), 27 | COMPUTE_DECRYPT_TEXT_CORPID_ERROR(900010, "计算解密文字corpId不匹配"), 28 | ; 29 | 30 | private int code; 31 | private String msg; 32 | 33 | DtRuntimeErrorEnum(int code, String msg) { 34 | this.code = code; 35 | this.msg = msg; 36 | } 37 | 38 | /** 39 | * 通过错误代码查找其中文含义.. 40 | */ 41 | public static String findMsgByCode(int code) { 42 | for (DtRuntimeErrorEnum value : DtRuntimeErrorEnum.values()) { 43 | if (value.code == code) { 44 | return value.msg; 45 | } 46 | } 47 | 48 | return null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/error/DtRuntimeException.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.error; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | /** 6 | * DtJava专用的runtime exception 7 | */ 8 | public class DtRuntimeException extends RuntimeException { 9 | private static final long serialVersionUID = 4881698471192264412L; 10 | 11 | private long code; 12 | 13 | public DtRuntimeException(Throwable e) { 14 | super(e); 15 | } 16 | 17 | public DtRuntimeException(String msg) { 18 | super(msg); 19 | } 20 | 21 | public DtRuntimeException(String msg, Throwable e) { 22 | super(msg, e); 23 | } 24 | 25 | public DtRuntimeException(@NotNull DtRuntimeErrorEnum errorEnum) { 26 | super(errorEnum.getMsg()); 27 | this.code = errorEnum.getCode(); 28 | } 29 | 30 | public DtRuntimeException(long code, String msg) { 31 | super(msg); 32 | this.code = code; 33 | } 34 | 35 | public long getCode() { 36 | return code; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/message/DtErrorExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 4 | 5 | public interface DtErrorExceptionHandler { 6 | 7 | void handle(DtErrorException e); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/message/DtMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 4 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtEventMessage; 5 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * 处理推送消息的处理器接口 11 | */ 12 | public interface DtMessageHandler { 13 | 14 | /** 15 | * 处理消息,当消息处理成功时可以返回true,是被返回false 16 | * 未处理消息则可以返回null,比如仅仅是记录了一下日志而已 17 | * 此时不论是返回false还是true都会有问题,返回true则会认为消息处理成功返回false则会认为消息处理失败,然而消息实际未处理 18 | * 此时返回null,但依然会返回钉钉失败的响应,在后续业务方可以拉取失败消息来重新处理 19 | * 20 | * @param message the message 21 | * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 22 | * @param dtService the dt service 23 | * @return 处理消息的结果 24 | * @throws DtErrorException the error exception 25 | */ 26 | Boolean handle(DtEventMessage message, 27 | Map context, 28 | DtService dtService) throws DtErrorException; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/message/DtMessageInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 4 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtEventMessage; 5 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * 消息拦截器,可以用来做验证 11 | */ 12 | public interface DtMessageInterceptor { 13 | 14 | /** 15 | * 拦截消息 16 | * 17 | * @param message the message 18 | * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 19 | * @param dtService the dt service 20 | * @return true代表OK ,false代表不OK 21 | * @throws DtErrorException the error exception 22 | */ 23 | boolean intercept(DtEventMessage message, 24 | Map context, 25 | DtService dtService) throws DtErrorException; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/message/DtMessageMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtEventMessage; 4 | 5 | /** 6 | * 消息匹配器,用在消息路由的时候 7 | */ 8 | public interface DtMessageMatcher { 9 | 10 | /** 11 | * 消息是否匹配某种模式 12 | * 13 | * @param message the message 14 | * @return the boolean 15 | */ 16 | boolean match(DtEventMessage message); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/message/DtMessageRouter.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 4 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtEventMessage; 5 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 6 | import com.github.tingyugetc520.ali.dingtalk.error.DtRuntimeException; 7 | import com.github.tingyugetc520.ali.dingtalk.message.processor.DtCheckUrlMessageHandler; 8 | import com.github.tingyugetc520.ali.dingtalk.message.processor.DtLogExceptionHandler; 9 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.util.*; 13 | import java.util.concurrent.*; 14 | 15 | /** 16 | *
 17 |  * 消息路由器,通过代码化的配置,把来自钉钉的消息交给handler处理
 18 |  *
 19 |  * 说明:
 20 |  * 1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理
 21 |  * 2. 默认情况下消息只会被处理一次,除非使用 {@link DtMessageRouterRule#next()}
 22 |  * 3. 规则的结束必须用{@link DtMessageRouterRule#end()}或者{@link DtMessageRouterRule#next()},否则不会生效
 23 |  *
 24 |  * 使用方法:
 25 |  * DtMessageRouter router = new DtMessageRouter();
 26 |  * router
 27 |  *   .rule()
 28 |  *       .eventType("eventType")
 29 |  *       .interceptor(interceptor, ...).handler(handler, ...)
 30 |  *   .end()
 31 |  *   .rule()
 32 |  *       // 另外一个匹配规则
 33 |  *   .end()
 34 |  * ;
 35 |  *
 36 |  * // 将DtMessage交给消息路由器
 37 |  * router.route(message);
 38 |  *
 39 |  * 
40 | */ 41 | @Slf4j 42 | public class DtMessageRouter { 43 | private static final int DEFAULT_THREAD_POOL_SIZE = 100; 44 | private final List rules = new ArrayList<>(); 45 | 46 | private DtService dtService; 47 | private ExecutorService executorService; 48 | private DtErrorExceptionHandler exceptionHandler; 49 | 50 | /** 51 | * 构造方法. 52 | */ 53 | public DtMessageRouter() { 54 | ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("dtMessageRouter-pool-%d").build(); 55 | this.executorService = new ThreadPoolExecutor(DEFAULT_THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_SIZE, 56 | 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), namedThreadFactory); 57 | 58 | this.exceptionHandler = new DtLogExceptionHandler(); 59 | this.rules.add(new DtMessageRouterRule(this).async(false).eventType(DtConstant.EventType.CHECK_URL).handler(new DtCheckUrlMessageHandler())); 60 | } 61 | 62 | /** 63 | * 构造方法. 64 | */ 65 | public DtMessageRouter(DtService dtService) { 66 | this(); 67 | this.dtService = dtService; 68 | } 69 | 70 | /** 71 | *
 72 |      * 设置自定义的 {@link ExecutorService}
 73 |      * 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100)
 74 |      * 
75 | */ 76 | public void setExecutorService(ExecutorService executorService) { 77 | this.executorService = executorService; 78 | } 79 | 80 | /** 81 | *
 82 |      * 设置自定义的{@link DtErrorExceptionHandler}
 83 |      * 如果不调用该方法,默认使用 {@link DtLogExceptionHandler}
 84 |      * 
85 | */ 86 | public void setExceptionHandler(DtErrorExceptionHandler exceptionHandler) { 87 | this.exceptionHandler = exceptionHandler; 88 | } 89 | 90 | List getRules() { 91 | return this.rules; 92 | } 93 | 94 | /** 95 | * 开始一个新的Route规则. 96 | */ 97 | public DtMessageRouterRule rule() { 98 | return new DtMessageRouterRule(this); 99 | } 100 | 101 | /** 102 | * 处理消息. 103 | */ 104 | protected Boolean doRoute(final DtService dtService, final DtEventMessage message, final Map context) { 105 | // 消息为空,则说明回调有问题 106 | if (Objects.isNull(message)) { 107 | throw new DtRuntimeException("回调消息为空"); 108 | } 109 | 110 | final List matchRules = new ArrayList<>(); 111 | // 收集匹配的规则 112 | for (final DtMessageRouterRule rule : this.rules) { 113 | if (rule.test(message)) { 114 | matchRules.add(rule); 115 | if (!rule.isReEnter()) { 116 | break; 117 | } 118 | } 119 | } 120 | 121 | // 没有处理器 122 | if (matchRules.size() == 0) { 123 | return null; 124 | } 125 | 126 | // todo 当存在多条匹配规则时,应该判断返回全部规则是否都正确处理 127 | Boolean res = null; 128 | final List> futures = new ArrayList<>(); 129 | for (final DtMessageRouterRule rule : matchRules) { 130 | // 返回最后一个非异步的rule的执行结果 131 | if (rule.isAsync()) { 132 | futures.add( 133 | this.executorService.submit(() -> 134 | rule.service(message, context, dtService, DtMessageRouter.this.exceptionHandler) 135 | ) 136 | ); 137 | } else { 138 | res = rule.service(message, context, dtService, this.exceptionHandler); 139 | } 140 | } 141 | 142 | if (futures.size() > 0) { 143 | this.executorService.submit(() -> { 144 | for (Future future : futures) { 145 | try { 146 | future.get(); 147 | } catch (InterruptedException e) { 148 | log.error("Error happened when wait task finish", e); 149 | Thread.currentThread().interrupt(); 150 | } catch (ExecutionException e) { 151 | log.error("Error happened when wait task finish", e); 152 | } 153 | } 154 | }); 155 | } 156 | return res; 157 | } 158 | 159 | /** 160 | * 处理消息. 161 | */ 162 | public Boolean route(final DtService dtService, final DtEventMessage message, final Map context) { 163 | return this.doRoute(dtService, message, context); 164 | } 165 | 166 | /** 167 | * 处理消息. 168 | */ 169 | public Boolean route(final DtEventMessage message) { 170 | return this.route(this.dtService, message, new HashMap<>(2)); 171 | } 172 | 173 | /** 174 | * 指定由dtService来处理消息 175 | */ 176 | public Boolean route(final DtService dtService, final DtEventMessage message) { 177 | return this.route(dtService, message, new HashMap<>(2)); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/message/DtMessageRouterRule.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.message; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 4 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtEventMessage; 5 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 6 | import lombok.Data; 7 | import org.apache.commons.collections4.CollectionUtils; 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import java.util.*; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * The message router rule 15 | */ 16 | @Data 17 | public class DtMessageRouterRule { 18 | private final DtMessageRouter routerBuilder; 19 | 20 | private boolean async = true; 21 | 22 | private String corpId; 23 | 24 | private String eventType; 25 | 26 | private List eventTypeGroup; 27 | 28 | private String eventTypeRegex; 29 | 30 | private DtMessageMatcher matcher; 31 | 32 | private boolean reEnter = false; 33 | 34 | 35 | private List handlers = new ArrayList<>(); 36 | 37 | private List interceptors = new ArrayList<>(); 38 | 39 | protected DtMessageRouterRule(DtMessageRouter routerBuilder) { 40 | this.routerBuilder = routerBuilder; 41 | } 42 | 43 | /** 44 | * 设置是否异步执行,默认是true 45 | * 46 | * @param async the async 47 | * @return the message router rule 48 | */ 49 | public DtMessageRouterRule async(boolean async) { 50 | this.async = async; 51 | return this; 52 | } 53 | 54 | /** 55 | * 如果corpId匹配 56 | * 57 | * @param corpId the corp id 58 | * @return the message router rule 59 | */ 60 | public DtMessageRouterRule corpId(String corpId) { 61 | this.corpId = corpId; 62 | return this; 63 | } 64 | 65 | /** 66 | * 如果eventType等于某值 67 | * 68 | * @param eventType the event type 69 | * @return the message router rule 70 | */ 71 | public DtMessageRouterRule eventType(String eventType) { 72 | this.eventType = eventType; 73 | return this; 74 | } 75 | 76 | /** 77 | * 如果是eventGroup中的某一个 78 | * @return rule 79 | */ 80 | public DtMessageRouterRule eventTypeGroup(List eventGroup) { 81 | this.eventTypeGroup = eventGroup; 82 | return this; 83 | } 84 | 85 | /** 86 | * 如果eventKey匹配该正则表达式 87 | * 88 | * @param regex the regex 89 | * @return the message router rule 90 | */ 91 | public DtMessageRouterRule eventTypeRegex(String regex) { 92 | this.eventTypeRegex = regex; 93 | return this; 94 | } 95 | 96 | /** 97 | * 如果消息匹配某个matcher,用在用户需要自定义更复杂的匹配规则的时候 98 | * 99 | * @param matcher the matcher 100 | * @return the message router rule 101 | */ 102 | public DtMessageRouterRule matcher(DtMessageMatcher matcher) { 103 | this.matcher = matcher; 104 | return this; 105 | } 106 | 107 | /** 108 | * 设置消息拦截器 109 | * 110 | * @param interceptor the interceptor 111 | * @return the message router rule 112 | */ 113 | public DtMessageRouterRule interceptor(DtMessageInterceptor interceptor) { 114 | return interceptor(interceptor, (DtMessageInterceptor[]) null); 115 | } 116 | 117 | /** 118 | * 设置消息拦截器 119 | * 120 | * @param interceptor the interceptor 121 | * @param otherInterceptors the other interceptors 122 | * @return the message router rule 123 | */ 124 | public DtMessageRouterRule interceptor(DtMessageInterceptor interceptor, DtMessageInterceptor... otherInterceptors) { 125 | this.interceptors.add(interceptor); 126 | if (otherInterceptors != null && otherInterceptors.length > 0) { 127 | Collections.addAll(this.interceptors, otherInterceptors); 128 | } 129 | return this; 130 | } 131 | 132 | /** 133 | * 设置消息处理器 134 | * 135 | * @param handler the handler 136 | * @return the message router rule 137 | */ 138 | public DtMessageRouterRule handler(DtMessageHandler handler) { 139 | return handler(handler, (DtMessageHandler[]) null); 140 | } 141 | 142 | /** 143 | * 设置消息处理器 144 | * 145 | * @param handler the handler 146 | * @param otherHandlers the other handlers 147 | * @return the message router rule 148 | */ 149 | public DtMessageRouterRule handler(DtMessageHandler handler, DtMessageHandler... otherHandlers) { 150 | this.handlers.add(handler); 151 | if (otherHandlers != null && otherHandlers.length > 0) { 152 | Collections.addAll(this.handlers, otherHandlers); 153 | } 154 | return this; 155 | } 156 | 157 | /** 158 | * 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则 159 | * 160 | * @return the message router 161 | */ 162 | public DtMessageRouter end() { 163 | this.routerBuilder.getRules().add(this); 164 | return this.routerBuilder; 165 | } 166 | 167 | /** 168 | * 规则结束,但是消息还会进入其他规则 169 | * 170 | * @return the message router 171 | */ 172 | public DtMessageRouter next() { 173 | this.reEnter = true; 174 | return end(); 175 | } 176 | 177 | /** 178 | * Test boolean. 179 | * 180 | * @param message the ding talk event message 181 | * @return the boolean 182 | */ 183 | protected boolean test(DtEventMessage message) { 184 | return (this.corpId == null || this.corpId.equals(message.getCorpId())) 185 | && 186 | (this.eventType == null || this.eventType.equalsIgnoreCase(message.getEventType())) 187 | && 188 | (CollectionUtils.isEmpty(this.eventTypeGroup) || this.eventTypeGroup.contains(message.getEventType())) 189 | && 190 | (this.eventTypeRegex == null || Pattern.matches(this.eventTypeRegex, StringUtils.trimToEmpty(message.getEventType()))) 191 | && 192 | (this.matcher == null || this.matcher.match(message)) 193 | ; 194 | } 195 | 196 | /** 197 | * 处理推送过来的消息 198 | * 199 | * @param message the dt message 200 | * @param context the context 201 | * @param dtService the service 202 | * @param exceptionHandler the exception handler 203 | * @return true 代表消息回调成功,false 代表消息回调失败 204 | */ 205 | protected Boolean service(DtEventMessage message, 206 | Map context, 207 | DtService dtService, 208 | DtErrorExceptionHandler exceptionHandler) { 209 | 210 | if (context == null) { 211 | context = new HashMap<>(); 212 | } 213 | 214 | try { 215 | // 如果拦截器不通过 216 | for (DtMessageInterceptor interceptor : this.interceptors) { 217 | if (!interceptor.intercept(message, context, dtService)) { 218 | return false; 219 | } 220 | } 221 | 222 | // 交给handler处理 223 | Boolean res = null; 224 | for (DtMessageHandler handler : this.handlers) { 225 | // 返回最后handler的结果 226 | res = handler.handle(message, context, dtService); 227 | } 228 | return res; 229 | } catch (DtErrorException e) { 230 | exceptionHandler.handle(e); 231 | return false; 232 | } 233 | } 234 | 235 | 236 | } 237 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/message/processor/DtCheckUrlMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.message.processor; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 4 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtEventMessage; 5 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 6 | import com.github.tingyugetc520.ali.dingtalk.message.DtMessageHandler; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.util.Map; 10 | 11 | @Slf4j 12 | public class DtCheckUrlMessageHandler implements DtMessageHandler { 13 | 14 | @Override 15 | public Boolean handle(DtEventMessage message, Map context, DtService dtService) throws DtErrorException { 16 | // check url事件直接返回true,因为已经解析成功了 17 | return true; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/message/processor/DtLogExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.message.processor; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 4 | import com.github.tingyugetc520.ali.dingtalk.message.DtErrorExceptionHandler; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | public class DtLogExceptionHandler implements DtErrorExceptionHandler { 9 | @Override 10 | public void handle(DtErrorException e) { 11 | log.error("Error happens", e); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/DataUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | /** 6 | * 数据处理工具类 7 | */ 8 | public class DataUtils { 9 | /** 10 | * 将数据中包含的secret字符使用星号替换,防止日志打印时被输出 11 | */ 12 | public static E handleDataWithSecret(E data) { 13 | E dataForLog = data; 14 | if(data instanceof String && StringUtils.contains((String)data, "&secret=")){ 15 | dataForLog = (E) StringUtils.replaceAll((String)data,"&secret=\\w+&","&secret=******&"); 16 | } 17 | return dataForLog; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/DtConstantUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import java.lang.reflect.Modifier; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.stream.Collectors; 9 | 10 | public class DtConstantUtils { 11 | 12 | @SuppressWarnings("unchecked") 13 | public static List getEventTypeGroup(Class clazz) { 14 | return Lists.newArrayList(clazz.getDeclaredFields()) 15 | .stream() 16 | .filter(field -> Modifier.isStatic(field.getModifiers())) 17 | .map(field -> { 18 | try { 19 | return (E) field.get(null); 20 | } catch (IllegalAccessException|IllegalArgumentException e) { 21 | e.printStackTrace(); 22 | return null; 23 | } 24 | }).filter(Objects::nonNull).collect(Collectors.toList()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/crypto/DtCryptUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.crypto; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.config.DtConfigStorage; 4 | import com.github.tingyugetc520.ali.dingtalk.error.DtRuntimeErrorEnum; 5 | import com.github.tingyugetc520.ali.dingtalk.error.DtRuntimeException; 6 | import org.apache.commons.codec.binary.Base64; 7 | 8 | import javax.crypto.Cipher; 9 | import javax.crypto.spec.IvParameterSpec; 10 | import javax.crypto.spec.SecretKeySpec; 11 | import java.io.ByteArrayOutputStream; 12 | import java.nio.charset.Charset; 13 | import java.nio.charset.StandardCharsets; 14 | import java.security.MessageDigest; 15 | import java.util.Arrays; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | import java.util.Random; 19 | 20 | /** 21 | * 钉钉开放平台加解密方法 22 | */ 23 | public class DtCryptUtil { 24 | private static final Charset CHARSET = StandardCharsets.UTF_8; 25 | private static final Base64 BASE64 = new Base64(); 26 | 27 | protected byte[] aesKey; 28 | protected String token; 29 | protected String appKeyOrCorpId; 30 | /** 31 | * ask getPaddingBytes key固定长度 32 | **/ 33 | private static final Integer AES_ENCODE_KEY_LENGTH = 43; 34 | /** 35 | * 加密随机字符串字节长度 36 | **/ 37 | private static final Integer RANDOM_LENGTH = 16; 38 | 39 | public DtCryptUtil(DtConfigStorage configStorage) { 40 | this(configStorage.getToken(), configStorage.getAesKey(), configStorage.getAppKeyOrCorpId()); 41 | } 42 | 43 | /** 44 | * 构造函数 45 | * 46 | * @param token 钉钉开放平台上,开发者设置的token 47 | * @param aesKey 钉钉开放台上,开发者设置的EncodingAESKey 48 | * @param appKeyOrCorpId 企业的corpId 49 | */ 50 | public DtCryptUtil(String token, String aesKey, String appKeyOrCorpId) { 51 | if (null == aesKey || aesKey.length() != AES_ENCODE_KEY_LENGTH) { 52 | throw new DtRuntimeException(DtRuntimeErrorEnum.AES_KEY_ILLEGAL); 53 | } 54 | this.token = token; 55 | this.appKeyOrCorpId = appKeyOrCorpId; 56 | this.aesKey = Base64.decodeBase64(aesKey + "="); 57 | } 58 | 59 | public Map getEncryptedMap(String plaintext) { 60 | return getEncryptedMap(plaintext, System.currentTimeMillis(), Utils.getRandomStr(16)); 61 | } 62 | 63 | /** 64 | * 将和钉钉开放平台同步的消息体加密,返回加密Map 65 | * 66 | * @param plaintext 传递的消息体明文 67 | * @param timeStamp 时间戳 68 | * @param nonce 随机字符串 69 | * @return map 70 | */ 71 | public Map getEncryptedMap(String plaintext, Long timeStamp, String nonce) { 72 | if (null == plaintext) { 73 | throw new DtRuntimeException(DtRuntimeErrorEnum.ENCRYPTION_PLAINTEXT_ILLEGAL); 74 | } 75 | if (null == timeStamp) { 76 | throw new DtRuntimeException(DtRuntimeErrorEnum.ENCRYPTION_TIMESTAMP_ILLEGAL); 77 | } 78 | if (null == nonce || nonce.length() != 16) { 79 | throw new DtRuntimeException(DtRuntimeErrorEnum.ENCRYPTION_NONCE_ILLEGAL); 80 | } 81 | // 加密 82 | String encrypt = encrypt(Utils.getRandomStr(RANDOM_LENGTH), plaintext); 83 | String signature = getSignature(token, String.valueOf(timeStamp), nonce, encrypt); 84 | Map resultMap = new HashMap(); 85 | resultMap.put("msg_signature", signature); 86 | resultMap.put("encrypt", encrypt); 87 | resultMap.put("timeStamp", String.valueOf(timeStamp)); 88 | resultMap.put("nonce", nonce); 89 | return resultMap; 90 | } 91 | 92 | /** 93 | * 密文解密 94 | * 95 | * @param msgSignature 签名串 96 | * @param timeStamp 时间戳 97 | * @param nonce 随机串 98 | * @param encryptMsg 密文 99 | * @return 解密后的原文 100 | */ 101 | public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg) { 102 | //校验签名 103 | String signature = getSignature(token, timeStamp, nonce, encryptMsg); 104 | if (!signature.equals(msgSignature)) { 105 | throw new DtRuntimeException(DtRuntimeErrorEnum.COMPUTE_SIGNATURE_ERROR); 106 | } 107 | // 解密 108 | return decrypt(encryptMsg); 109 | } 110 | 111 | /** 112 | * 对明文加密. 113 | * @param plaintext 需要加密的明文 114 | * @return 加密后base64编码的字符串 115 | */ 116 | private String encrypt(String random, String plaintext) { 117 | try { 118 | byte[] randomBytes = random.getBytes(CHARSET); 119 | byte[] plainTextBytes = plaintext.getBytes(CHARSET); 120 | byte[] lengthByte = Utils.int2Bytes(plainTextBytes.length); 121 | byte[] corpIdBytes = appKeyOrCorpId.getBytes(CHARSET); 122 | 123 | ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 124 | byteStream.write(randomBytes); 125 | byteStream.write(lengthByte); 126 | byteStream.write(plainTextBytes); 127 | byteStream.write(corpIdBytes); 128 | byte[] padBytes = PKCS7Padding.getPaddingBytes(byteStream.size()); 129 | byteStream.write(padBytes); 130 | byte[] unencrypted = byteStream.toByteArray(); 131 | byteStream.close(); 132 | Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); 133 | SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); 134 | IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); 135 | cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); 136 | byte[] encrypted = cipher.doFinal(unencrypted); 137 | 138 | return BASE64.encodeToString(encrypted); 139 | } catch (Exception e) { 140 | throw new DtRuntimeException(DtRuntimeErrorEnum.COMPUTE_ENCRYPT_TEXT_ERROR); 141 | } 142 | } 143 | 144 | /** 145 | * 对密文进行解密. 146 | * @param text 需要解密的密文 147 | * @return 解密得到的明文 148 | */ 149 | private String decrypt(String text) { 150 | byte[] originalArr; 151 | try { 152 | // 设置解密模式为AES的CBC模式 153 | Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); 154 | SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); 155 | IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); 156 | cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); 157 | // 使用BASE64对密文进行解码 158 | byte[] encrypted = Base64.decodeBase64(text); 159 | // 解密 160 | originalArr = cipher.doFinal(encrypted); 161 | } catch (Exception e) { 162 | throw new DtRuntimeException(DtRuntimeErrorEnum.COMPUTE_DECRYPT_TEXT_ERROR); 163 | } 164 | 165 | String plainText; 166 | String fromAppKeyOrCorpId; 167 | try { 168 | // 去除补位字符 169 | byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr); 170 | // 分离16位随机字符串,网络字节序和corpId 171 | byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); 172 | int plainTextLength = Utils.bytes2int(networkOrder); 173 | plainText = new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLength), CHARSET); 174 | fromAppKeyOrCorpId = new String(Arrays.copyOfRange(bytes, 20 + plainTextLength, bytes.length), CHARSET); 175 | } catch (Exception e) { 176 | throw new DtRuntimeException(DtRuntimeErrorEnum.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR); 177 | } 178 | 179 | // appKeyOrCorpId不相同的情况 180 | if (!fromAppKeyOrCorpId.equals(appKeyOrCorpId)) { 181 | throw new DtRuntimeException(DtRuntimeErrorEnum.COMPUTE_DECRYPT_TEXT_CORPID_ERROR); 182 | } 183 | return plainText; 184 | } 185 | 186 | /** 187 | * 数字签名 188 | * 189 | * @param token isv token 190 | * @param timestamp 时间戳 191 | * @param nonce 随机串 192 | * @param encrypt 加密文本 193 | * @return 194 | */ 195 | public static String getSignature(String token, String timestamp, String nonce, String encrypt) { 196 | try { 197 | String[] array = new String[] {token, timestamp, nonce, encrypt}; 198 | Arrays.sort(array); 199 | 200 | StringBuilder sb = new StringBuilder(); 201 | for (int i = 0; i < 4; i++) { 202 | sb.append(array[i]); 203 | } 204 | String str = sb.toString(); 205 | 206 | MessageDigest md = MessageDigest.getInstance("SHA-1"); 207 | md.update(str.getBytes()); 208 | byte[] digest = md.digest(); 209 | 210 | StringBuilder hexStr = new StringBuilder(); 211 | String shaHex = ""; 212 | for (byte b : digest) { 213 | shaHex = Integer.toHexString(b & 0xFF); 214 | if (shaHex.length() < 2) { 215 | hexStr.append(0); 216 | } 217 | hexStr.append(shaHex); 218 | } 219 | return hexStr.toString(); 220 | } catch (Exception e) { 221 | throw new DtRuntimeException(DtRuntimeErrorEnum.COMPUTE_SIGNATURE_ERROR); 222 | } 223 | } 224 | 225 | public static class Utils { 226 | public Utils() { 227 | } 228 | 229 | public static String getRandomStr(int count) { 230 | String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 231 | Random random = new Random(); 232 | StringBuilder sb = new StringBuilder(); 233 | 234 | for (int i = 0; i < count; ++i) { 235 | int number = random.nextInt(base.length()); 236 | sb.append(base.charAt(number)); 237 | } 238 | 239 | return sb.toString(); 240 | } 241 | 242 | public static byte[] int2Bytes(int count) { 243 | return new byte[] {(byte)(count >> 24 & 255), (byte)(count >> 16 & 255), (byte)(count >> 8 & 255), 244 | (byte)(count & 255)}; 245 | } 246 | 247 | public static int bytes2int(byte[] byteArr) { 248 | int count = 0; 249 | 250 | for (int i = 0; i < 4; ++i) { 251 | count <<= 8; 252 | count |= byteArr[i] & 255; 253 | } 254 | 255 | return count; 256 | } 257 | } 258 | 259 | public static class PKCS7Padding { 260 | private static final Charset CHARSET = StandardCharsets.UTF_8; 261 | private static final int BLOCK_SIZE = 32; 262 | 263 | public PKCS7Padding() { 264 | } 265 | 266 | public static byte[] getPaddingBytes(int count) { 267 | int amountToPad = 32 - count % 32; 268 | 269 | char padChr = chr(amountToPad); 270 | StringBuilder tmp = new StringBuilder(); 271 | 272 | for (int index = 0; index < amountToPad; ++index) { 273 | tmp.append(padChr); 274 | } 275 | 276 | return tmp.toString().getBytes(CHARSET); 277 | } 278 | 279 | public static byte[] removePaddingBytes(byte[] decrypted) { 280 | int pad = decrypted[decrypted.length - 1]; 281 | if (pad < 1 || pad > 32) { 282 | pad = 0; 283 | } 284 | 285 | return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); 286 | } 287 | 288 | private static char chr(int a) { 289 | byte target = (byte)(a & 255); 290 | return (char)target; 291 | } 292 | } 293 | 294 | } 295 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/http/HttpProxyType.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.http; 2 | 3 | /** 4 | * Http Proxy types 5 | */ 6 | public enum HttpProxyType { 7 | /** 8 | * none 9 | */ 10 | NONE(0), 11 | /** 12 | * 反向代理 13 | */ 14 | REVERSE(1), 15 | /** 16 | * 正向代理 17 | */ 18 | FORWARD(2), 19 | ; 20 | 21 | private int code; 22 | 23 | HttpProxyType(int code) { 24 | this.code = code; 25 | } 26 | 27 | public int getCode() { 28 | return code; 29 | } 30 | 31 | /** 32 | * 通过错误代码查找其中文含义.. 33 | */ 34 | public static HttpProxyType findByCode(int code) { 35 | for (HttpProxyType value : HttpProxyType.values()) { 36 | if (value.code == code) { 37 | return value; 38 | } 39 | } 40 | 41 | return null; 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/http/OkHttpProxyInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.http; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.net.Proxy; 5 | 6 | /** 7 | * Proxy information. 8 | */ 9 | public class OkHttpProxyInfo { 10 | private final String proxyAddress; 11 | private final int proxyPort; 12 | private final String proxyUsername; 13 | private final String proxyPassword; 14 | private final ProxyType proxyType; 15 | 16 | public OkHttpProxyInfo(ProxyType proxyType, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) { 17 | this.proxyType = proxyType; 18 | this.proxyAddress = proxyHost; 19 | this.proxyPort = proxyPort; 20 | this.proxyUsername = proxyUser; 21 | this.proxyPassword = proxyPassword; 22 | } 23 | 24 | /** 25 | * Creates directProxy. 26 | */ 27 | public static OkHttpProxyInfo directProxy() { 28 | return new OkHttpProxyInfo(ProxyType.NONE, null, 0, null, null); 29 | } 30 | 31 | // ---------------------------------------------------------------- factory 32 | 33 | /** 34 | * Creates SOCKS4 proxy. 35 | */ 36 | public static OkHttpProxyInfo socks4Proxy(String proxyAddress, int proxyPort, String proxyUser) { 37 | return new OkHttpProxyInfo(ProxyType.SOCKS4, proxyAddress, proxyPort, proxyUser, null); 38 | } 39 | 40 | /** 41 | * Creates SOCKS5 proxy. 42 | */ 43 | public static OkHttpProxyInfo socks5Proxy(String proxyAddress, int proxyPort, String proxyUser, String proxyPassword) { 44 | return new OkHttpProxyInfo(ProxyType.SOCKS5, proxyAddress, proxyPort, proxyUser, proxyPassword); 45 | } 46 | 47 | /** 48 | * Creates HTTP proxy. 49 | */ 50 | public static OkHttpProxyInfo httpProxy(String proxyAddress, int proxyPort, String proxyUser, String proxyPassword) { 51 | return new OkHttpProxyInfo(ProxyType.HTTP, proxyAddress, proxyPort, proxyUser, proxyPassword); 52 | } 53 | 54 | /** 55 | * Returns proxy type. 56 | */ 57 | public ProxyType getProxyType() { 58 | return proxyType; 59 | } 60 | 61 | // ---------------------------------------------------------------- getter 62 | 63 | /** 64 | * Returns proxy address. 65 | */ 66 | public String getProxyAddress() { 67 | return proxyAddress; 68 | } 69 | 70 | /** 71 | * Returns proxy port. 72 | */ 73 | public int getProxyPort() { 74 | return proxyPort; 75 | } 76 | 77 | /** 78 | * Returns proxy user name or null if 79 | * no authentication required. 80 | */ 81 | public String getProxyUsername() { 82 | return proxyUsername; 83 | } 84 | 85 | /** 86 | * Returns proxy password or null. 87 | */ 88 | public String getProxyPassword() { 89 | return proxyPassword; 90 | } 91 | 92 | /** 93 | * 返回 java.net.Proxy 94 | */ 95 | public Proxy getProxy() { 96 | Proxy proxy = null; 97 | if (getProxyType().equals(ProxyType.SOCKS5)) { 98 | proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(getProxyAddress(), getProxyPort())); 99 | } else if (getProxyType().equals(ProxyType.SOCKS4)) { 100 | proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(getProxyAddress(), getProxyPort())); 101 | } else if (getProxyType().equals(ProxyType.HTTP)) { 102 | proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getProxyAddress(), getProxyPort())); 103 | } else if (getProxyType().equals(ProxyType.NONE)) { 104 | proxy = new Proxy(Proxy.Type.DIRECT, new InetSocketAddress(getProxyAddress(), getProxyPort())); 105 | } 106 | return proxy; 107 | } 108 | 109 | /** 110 | * Proxy types. 111 | */ 112 | public enum ProxyType { 113 | /** 114 | * none 115 | */ 116 | NONE, 117 | /** 118 | * http 119 | */ 120 | HTTP, 121 | /** 122 | * socks4 123 | */ 124 | SOCKS4, 125 | /** 126 | * socks5 127 | */ 128 | SOCKS5 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/http/OkHttpSimpleGetRequestExecutor.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.http; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.error.DtError; 4 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 5 | import okhttp3.OkHttpClient; 6 | import okhttp3.Request; 7 | import okhttp3.Response; 8 | 9 | import java.io.IOException; 10 | import java.util.Objects; 11 | 12 | /** 13 | * 简单的GET请求执行器. 14 | * 请求的参数是String, 返回的结果也是String 15 | */ 16 | public class OkHttpSimpleGetRequestExecutor implements RequestExecutor { 17 | protected OkHttpClient httpClient; 18 | 19 | public OkHttpSimpleGetRequestExecutor(OkHttpClient httpClient) { 20 | this.httpClient = httpClient; 21 | } 22 | 23 | @Override 24 | public String execute(String uri, String queryParam) throws DtErrorException, IOException { 25 | if (queryParam != null) { 26 | if (uri.indexOf('?') == -1) { 27 | uri += '?'; 28 | } 29 | uri += uri.endsWith("?") ? queryParam : '&' + queryParam; 30 | } 31 | 32 | Request request = new Request.Builder().url(uri).build(); 33 | Response response = httpClient.newCall(request).execute(); 34 | return handleResponse(Objects.requireNonNull(response.body()).string()); 35 | } 36 | 37 | protected String handleResponse(String responseContent) throws DtErrorException { 38 | DtError error = DtError.fromJson(responseContent); 39 | if (error.getErrorCode() != 0) { 40 | throw new DtErrorException(error); 41 | } 42 | 43 | return responseContent; 44 | } 45 | 46 | public static RequestExecutor create(OkHttpClient httpClient) { 47 | return new OkHttpSimpleGetRequestExecutor(httpClient); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/http/OkHttpSimplePostRequestExecutor.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.http; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.error.DtError; 4 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 5 | import okhttp3.*; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.io.IOException; 9 | import java.util.Objects; 10 | 11 | /** 12 | * 简单的POST请求执行器,请求的参数是String, 返回的结果也是String 13 | */ 14 | public class OkHttpSimplePostRequestExecutor implements RequestExecutor { 15 | protected OkHttpClient httpClient; 16 | 17 | public OkHttpSimplePostRequestExecutor(OkHttpClient httpClient) { 18 | this.httpClient = httpClient; 19 | } 20 | 21 | @Override 22 | public String execute(String uri, String postEntity) throws DtErrorException, IOException { 23 | RequestBody body = RequestBody.Companion.create(postEntity, MediaType.parse("text/plain; charset=utf-8")); 24 | Request request = new Request.Builder().url(uri).post(body).build(); 25 | Response response = httpClient.newCall(request).execute(); 26 | return this.handleResponse(Objects.requireNonNull(response.body()).string()); 27 | } 28 | 29 | public String handleResponse(String responseContent) throws DtErrorException { 30 | if (responseContent.isEmpty()) { 31 | throw new DtErrorException("无响应内容"); 32 | } 33 | 34 | if (responseContent.startsWith("")) { 35 | //xml格式输出直接返回 36 | return responseContent; 37 | } 38 | 39 | DtError error = DtError.fromJson(responseContent); 40 | if (error.getErrorCode() != 0) { 41 | throw new DtErrorException(error); 42 | } 43 | return responseContent; 44 | } 45 | 46 | public static RequestExecutor create(OkHttpClient httpClient) { 47 | return new OkHttpSimplePostRequestExecutor(httpClient); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/http/RequestExecutor.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.http; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * http请求执行器. 9 | * 10 | * @param 返回值类型 11 | * @param 请求参数类型 12 | */ 13 | public interface RequestExecutor { 14 | 15 | /** 16 | * 执行http请求. 17 | * 18 | * @param uri uri 19 | * @param data 数据 20 | * @return 响应结果 21 | * @throws DtErrorException 自定义异常 22 | * @throws IOException io异常 23 | */ 24 | T execute(String uri, E data) throws DtErrorException, IOException; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/http/ResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.http; 2 | 3 | /** 4 | * http请求响应回调处理接口. 5 | */ 6 | public interface ResponseHandler { 7 | /** 8 | * 响应结果处理. 9 | * 10 | * @param t 要处理的对象 11 | */ 12 | void handle(T t); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/http/ResponseHttpStatusInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.http; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.error.DtRuntimeErrorEnum; 4 | import com.github.tingyugetc520.ali.dingtalk.error.DtRuntimeException; 5 | import lombok.extern.slf4j.Slf4j; 6 | import okhttp3.Interceptor; 7 | import okhttp3.Request; 8 | import okhttp3.Response; 9 | import org.apache.http.HttpStatus; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.io.IOException; 13 | 14 | @Slf4j 15 | public class ResponseHttpStatusInterceptor implements Interceptor { 16 | 17 | @NotNull 18 | @Override 19 | public Response intercept(@NotNull Chain chain) throws IOException { 20 | Request request = chain.request(); 21 | 22 | log.debug(""); 23 | Response response = chain.proceed(request); 24 | if (response.code() != HttpStatus.SC_OK) { 25 | throw new DtRuntimeException(DtRuntimeErrorEnum.DT_HTTP_CALL_FAILED); 26 | } 27 | 28 | return response; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/json/DtDateAdapter.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.json; 2 | 3 | import com.google.gson.*; 4 | 5 | import java.lang.reflect.Type; 6 | import java.util.Date; 7 | 8 | /** 9 | * json date 10 | */ 11 | public class DtDateAdapter implements JsonSerializer, JsonDeserializer { 12 | 13 | @Override 14 | public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { 15 | if (src == null) { 16 | return null; 17 | } 18 | return new JsonPrimitive(src.getTime()); 19 | } 20 | 21 | @Override 22 | public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) 23 | throws JsonParseException { 24 | if (json == null) { 25 | return null; 26 | } 27 | return new Date(json.getAsJsonPrimitive().getAsLong()); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/json/DtErrorAdapter.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.json; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.error.DtError; 4 | import com.google.gson.*; 5 | 6 | import java.lang.reflect.Type; 7 | 8 | /** 9 | * json error 10 | */ 11 | public class DtErrorAdapter implements JsonDeserializer { 12 | 13 | @Override 14 | public DtError deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) 15 | throws JsonParseException { 16 | DtError.DtErrorBuilder errorBuilder = DtError.builder(); 17 | JsonObject jsonObject = json.getAsJsonObject(); 18 | 19 | if (jsonObject.get("errcode") != null && !jsonObject.get("errcode").isJsonNull()) { 20 | errorBuilder.errorCode(GsonHelper.getAsPrimitiveInt(jsonObject.get("errcode"))); 21 | } 22 | if (jsonObject.get("errmsg") != null && !jsonObject.get("errmsg").isJsonNull()) { 23 | errorBuilder.errorMsg(GsonHelper.getAsString(jsonObject.get("errmsg"))); 24 | } 25 | 26 | errorBuilder.json(json.toString()); 27 | 28 | return errorBuilder.build(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/json/DtGsonBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.json; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.user.DtUser; 4 | import com.github.tingyugetc520.ali.dingtalk.error.DtError; 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * 12 | */ 13 | public class DtGsonBuilder { 14 | private static final GsonBuilder INSTANCE = new GsonBuilder(); 15 | 16 | static { 17 | INSTANCE.disableHtmlEscaping(); 18 | INSTANCE.registerTypeAdapter(Date.class, new DtDateAdapter()); 19 | INSTANCE.registerTypeAdapter(DtError.class, new DtErrorAdapter()); 20 | INSTANCE.registerTypeAdapter(DtUser.class, new DtUserAdapter()); 21 | } 22 | 23 | public static Gson create() { 24 | return INSTANCE.create(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/json/DtUserAdapter.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.json; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.bean.user.DtUser; 4 | import com.google.gson.*; 5 | 6 | import java.lang.reflect.Type; 7 | import java.util.*; 8 | 9 | /** 10 | * dt user adapter 11 | */ 12 | public class DtUserAdapter implements JsonDeserializer, JsonSerializer { 13 | 14 | @Override 15 | public DtUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 16 | JsonObject o = json.getAsJsonObject(); 17 | DtUser user = new DtUser(); 18 | 19 | if (o.get("department") != null) { 20 | JsonArray departJsonArray = o.get("department").getAsJsonArray(); 21 | List departIds = new ArrayList<>(departJsonArray.size()); 22 | for (JsonElement jsonElement : departJsonArray) { 23 | departIds.add(jsonElement.getAsLong()); 24 | } 25 | user.setDepartIds(departIds); 26 | } 27 | 28 | if (o.get("orderInDepts") != null) { 29 | String orderString = o.get("orderInDepts").getAsString(); 30 | JsonObject departJsonObject = GsonParser.parse(orderString); 31 | Map departOrders = new HashMap<>(departJsonObject.size()); 32 | for (Map.Entry entry : departJsonObject.entrySet()) { 33 | departOrders.put(Long.parseLong(entry.getKey()), entry.getValue().getAsLong()); 34 | } 35 | user.setDepartOrders(departOrders); 36 | } 37 | 38 | if (o.get("isLeaderInDepts") != null) { 39 | String leaderString = o.get("isLeaderInDepts").getAsString(); 40 | JsonObject leaderJsonObject = GsonParser.parse(leaderString); 41 | Map leaderInDeparts = new HashMap<>(leaderJsonObject.size()); 42 | for (Map.Entry entry : leaderJsonObject.entrySet()) { 43 | leaderInDeparts.put(Long.parseLong(entry.getKey()), entry.getValue().getAsBoolean()); 44 | } 45 | user.setIsLeaderInDeparts(leaderInDeparts); 46 | } 47 | 48 | if (o.get("extattr") != null) { 49 | JsonObject extJsonObject = o.get("extattr").getAsJsonObject(); 50 | Map extAttr = new HashMap<>(extJsonObject.size()); 51 | for (Map.Entry entry : extJsonObject.entrySet()) { 52 | extAttr.put(entry.getKey(), entry.getValue().getAsString()); 53 | } 54 | user.setExtAttr(extAttr); 55 | } 56 | 57 | if (o.get("roles") != null) { 58 | JsonArray roleJsonElements = o.get("roles").getAsJsonArray(); 59 | List roles = new ArrayList<>(roleJsonElements.size()); 60 | for (JsonElement roleJsonElement : roleJsonElements) { 61 | JsonObject jsonObject = roleJsonElement.getAsJsonObject(); 62 | roles.add(DtUser.Role.builder() 63 | .id(GsonHelper.getLong(jsonObject, "id")) 64 | .name(GsonHelper.getString(jsonObject, "name")) 65 | .type(GsonHelper.getInteger(jsonObject, "type")) 66 | .groupName(GsonHelper.getString(jsonObject, "groupName")) 67 | .build()); 68 | } 69 | user.setRoles(roles); 70 | } 71 | 72 | user.setUserId(GsonHelper.getString(o, "userid")); 73 | user.setUnionId(GsonHelper.getString(o, "unionid")); 74 | 75 | user.setManagerUserId(GsonHelper.getString(o, "managerUserId")); 76 | user.setHiredDate(new Date(GsonHelper.getLong(o, "hiredDate"))); 77 | user.setTel(GsonHelper.getString(o, "tel")); 78 | 79 | user.setRemark(GsonHelper.getString(o, "remark")); 80 | user.setWorkPlace(GsonHelper.getString(o, "workPlace")); 81 | 82 | user.setName(GsonHelper.getString(o, "name")); 83 | user.setPosition(GsonHelper.getString(o, "position")); 84 | user.setMobile(GsonHelper.getString(o, "mobile")); 85 | user.setStateCode(GsonHelper.getString(o, "stateCode")); 86 | user.setEmail(GsonHelper.getString(o, "email")); 87 | user.setOrgEmail(GsonHelper.getString(o, "orgEmail")); 88 | 89 | user.setIsSenior(GsonHelper.getBoolean(o, "isSenior")); 90 | user.setJobNumber(GsonHelper.getString(o, "jobnumber")); 91 | user.setActive(GsonHelper.getBoolean(o, "active")); 92 | user.setAvatar(GsonHelper.getString(o, "avatar")); 93 | 94 | user.setIsAdmin(GsonHelper.getBoolean(o, "isAdmin")); 95 | 96 | user.setIsHide(GsonHelper.getBoolean(o, "isHide")); 97 | user.setIsBoss(GsonHelper.getBoolean(o, "isBoss")); 98 | user.setRealAuthed(GsonHelper.getBoolean(o, "realAuthed")); 99 | return user; 100 | } 101 | 102 | @Override 103 | public JsonElement serialize(DtUser user, Type typeOfSrc, JsonSerializationContext context) { 104 | JsonObject o = new JsonObject(); 105 | if (user.getUserId() != null) { 106 | o.addProperty("userid", user.getUserId()); 107 | } 108 | if (user.getUnionId() != null) { 109 | o.addProperty("unionid", user.getUnionId()); 110 | } 111 | 112 | if (user.getManagerUserId() != null) { 113 | o.addProperty("managerUserId", user.getManagerUserId()); 114 | } 115 | if (user.getHiredDate() != null) { 116 | o.addProperty("hiredDate", user.getHiredDate().getTime()); 117 | } 118 | if (user.getTel() != null) { 119 | o.addProperty("tel", user.getTel()); 120 | } 121 | 122 | if (user.getRemark() != null) { 123 | o.addProperty("remark", user.getRemark()); 124 | } 125 | if (user.getWorkPlace() != null) { 126 | o.addProperty("workPlace", user.getWorkPlace()); 127 | } 128 | 129 | if (user.getName() != null) { 130 | o.addProperty("name", user.getName()); 131 | } 132 | if (user.getPosition() != null) { 133 | o.addProperty("position", user.getPosition()); 134 | } 135 | if (user.getMobile() != null) { 136 | o.addProperty("mobile", user.getMobile()); 137 | } 138 | if (user.getStateCode() != null) { 139 | o.addProperty("stateCode", user.getStateCode()); 140 | } 141 | if (user.getEmail() != null) { 142 | o.addProperty("email", user.getEmail()); 143 | } 144 | if (user.getOrgEmail() != null) { 145 | o.addProperty("orgEmail", user.getOrgEmail()); 146 | } 147 | if (user.getIsSenior() != null) { 148 | o.addProperty("isSenior", user.getIsSenior()); 149 | } 150 | if (user.getJobNumber() != null) { 151 | o.addProperty("jobnumber", user.getJobNumber()); 152 | } 153 | if (user.getActive() != null) { 154 | o.addProperty("active", user.getActive()); 155 | } 156 | if (user.getAvatar() != null) { 157 | o.addProperty("avatar", user.getAvatar()); 158 | } 159 | if (user.getExtAttr() != null) { 160 | JsonObject jsonObject = new JsonObject(); 161 | for (Map.Entry entry : user.getExtAttr().entrySet()) { 162 | jsonObject.addProperty(entry.getKey(), entry.getValue()); 163 | } 164 | o.add("extattr", jsonObject); 165 | } 166 | if (user.getRoles() != null) { 167 | JsonArray jsonArray = new JsonArray(); 168 | for (DtUser.Role role : user.getRoles()) { 169 | JsonObject jsonObject = GsonHelper.buildJsonObject( 170 | "id", role.getId(), 171 | "name", role.getName(), 172 | "type", role.getType(), 173 | "groupName", role.getGroupName() 174 | ); 175 | jsonArray.add(jsonObject); 176 | } 177 | o.add("roles", jsonArray); 178 | } 179 | if (user.getDepartIds() != null) { 180 | JsonArray jsonArray = new JsonArray(); 181 | for (Long departId : user.getDepartIds()) { 182 | jsonArray.add(new JsonPrimitive(departId)); 183 | } 184 | o.add("department", jsonArray); 185 | } 186 | if (user.getDepartOrders() != null) { 187 | JsonObject jsonObject = new JsonObject(); 188 | for (Map.Entry entry : user.getDepartOrders().entrySet()) { 189 | jsonObject.addProperty(entry.getKey().toString(), entry.getValue()); 190 | } 191 | o.addProperty("orderInDepts", jsonObject.toString()); 192 | } 193 | if (user.getIsAdmin() != null) { 194 | o.addProperty("isAdmin", user.getIsAdmin()); 195 | } 196 | if (user.getIsLeaderInDeparts() != null) { 197 | JsonObject jsonObject = new JsonObject(); 198 | for (Map.Entry entry : user.getIsLeaderInDeparts().entrySet()) { 199 | jsonObject.addProperty(entry.getKey().toString(), entry.getValue()); 200 | } 201 | o.addProperty("isLeaderInDepts", new Gson().toJson(jsonObject)); 202 | } 203 | if (user.getIsHide() != null) { 204 | o.addProperty("isHide", user.getIsHide()); 205 | } 206 | if (user.getIsBoss() != null) { 207 | o.addProperty("isBoss", user.getIsBoss()); 208 | } 209 | if (user.getRealAuthed() != null) { 210 | o.addProperty("realAuthed", user.getRealAuthed()); 211 | } 212 | 213 | return o; 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/json/GsonHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.json; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.error.DtRuntimeException; 4 | import com.google.common.collect.Lists; 5 | import com.google.gson.JsonArray; 6 | import com.google.gson.JsonElement; 7 | import com.google.gson.JsonObject; 8 | import jodd.util.MathUtil; 9 | 10 | import java.util.List; 11 | 12 | public class GsonHelper { 13 | 14 | public static boolean isNull(JsonElement element) { 15 | return element == null || element.isJsonNull(); 16 | } 17 | 18 | public static boolean isNotNull(JsonElement element) { 19 | return !isNull(element); 20 | } 21 | 22 | public static Long getLong(JsonObject json, String property) { 23 | return getAsLong(json.get(property)); 24 | } 25 | 26 | public static long getPrimitiveLong(JsonObject json, String property) { 27 | return getAsPrimitiveLong(json.get(property)); 28 | } 29 | 30 | public static Integer getInteger(JsonObject json, String property) { 31 | return getAsInteger(json.get(property)); 32 | } 33 | 34 | public static int getPrimitiveInteger(JsonObject json, String property) { 35 | return getAsPrimitiveInt(json.get(property)); 36 | } 37 | 38 | public static Double getDouble(JsonObject json, String property) { 39 | return getAsDouble(json.get(property)); 40 | } 41 | 42 | public static double getPrimitiveDouble(JsonObject json, String property) { 43 | return getAsPrimitiveDouble(json.get(property)); 44 | } 45 | 46 | public static Float getFloat(JsonObject json, String property) { 47 | return getAsFloat(json.get(property)); 48 | } 49 | 50 | public static float getPrimitiveFloat(JsonObject json, String property) { 51 | return getAsPrimitiveFloat(json.get(property)); 52 | } 53 | 54 | public static Boolean getBoolean(JsonObject json, String property) { 55 | return getAsBoolean(json.get(property)); 56 | } 57 | 58 | public static String getString(JsonObject json, String property) { 59 | return getAsString(json.get(property)); 60 | } 61 | 62 | public static String getAsString(JsonElement element) { 63 | return isNull(element) ? null : element.getAsString(); 64 | } 65 | 66 | public static Long getAsLong(JsonElement element) { 67 | return isNull(element) ? null : element.getAsLong(); 68 | } 69 | 70 | public static long getAsPrimitiveLong(JsonElement element) { 71 | Long r = getAsLong(element); 72 | return r == null ? 0L : r; 73 | } 74 | 75 | public static Integer getAsInteger(JsonElement element) { 76 | return isNull(element) ? null : element.getAsInt(); 77 | } 78 | 79 | public static int getAsPrimitiveInt(JsonElement element) { 80 | Integer r = getAsInteger(element); 81 | return r == null ? 0 : r; 82 | } 83 | 84 | public static Boolean getAsBoolean(JsonElement element) { 85 | return isNull(element) ? null : element.getAsBoolean(); 86 | } 87 | 88 | public static boolean getAsPrimitiveBool(JsonElement element) { 89 | Boolean r = getAsBoolean(element); 90 | return r != null && r; 91 | } 92 | 93 | public static Double getAsDouble(JsonElement element) { 94 | return isNull(element) ? null : element.getAsDouble(); 95 | } 96 | 97 | public static double getAsPrimitiveDouble(JsonElement element) { 98 | Double r = getAsDouble(element); 99 | return r == null ? 0d : r; 100 | } 101 | 102 | public static Float getAsFloat(JsonElement element) { 103 | return isNull(element) ? null : element.getAsFloat(); 104 | } 105 | 106 | public static float getAsPrimitiveFloat(JsonElement element) { 107 | Float r = getAsFloat(element); 108 | return r == null ? 0f : r; 109 | } 110 | 111 | public static Integer[] getIntArray(JsonObject o, String string) { 112 | JsonArray jsonArray = getAsJsonArray(o.getAsJsonArray(string)); 113 | if (jsonArray == null) { 114 | return null; 115 | } 116 | 117 | List result = Lists.newArrayList(); 118 | for (int i = 0; i < jsonArray.size(); i++) { 119 | result.add(jsonArray.get(i).getAsInt()); 120 | } 121 | 122 | return result.toArray(new Integer[0]); 123 | } 124 | 125 | public static String[] getStringArray(JsonObject o, String string) { 126 | JsonArray jsonArray = getAsJsonArray(o.getAsJsonArray(string)); 127 | if (jsonArray == null) { 128 | return null; 129 | } 130 | 131 | List result = Lists.newArrayList(); 132 | for (int i = 0; i < jsonArray.size(); i++) { 133 | result.add(jsonArray.get(i).getAsString()); 134 | } 135 | 136 | return result.toArray(new String[0]); 137 | } 138 | 139 | public static Long[] getLongArray(JsonObject o, String string) { 140 | JsonArray jsonArray = getAsJsonArray(o.getAsJsonArray(string)); 141 | if (jsonArray == null) { 142 | return null; 143 | } 144 | 145 | List result = Lists.newArrayList(); 146 | for (int i = 0; i < jsonArray.size(); i++) { 147 | result.add(jsonArray.get(i).getAsLong()); 148 | } 149 | 150 | return result.toArray(new Long[0]); 151 | } 152 | 153 | public static JsonArray getAsJsonArray(JsonElement element) { 154 | return element == null ? null : element.getAsJsonArray(); 155 | } 156 | 157 | /** 158 | * 快速构建JsonObject对象,批量添加一堆属性 159 | * 160 | * @param keyOrValue 包含key或value的数组 161 | * @return JsonObject对象. 162 | */ 163 | public static JsonObject buildJsonObject(Object... keyOrValue) { 164 | JsonObject result = new JsonObject(); 165 | put(result, keyOrValue); 166 | return result; 167 | } 168 | 169 | /** 170 | * 批量向JsonObject对象中添加属性 171 | * 172 | * @param jsonObject 原始JsonObject对象 173 | * @param keyOrValue 包含key或value的数组 174 | */ 175 | public static void put(JsonObject jsonObject, Object... keyOrValue) { 176 | if (MathUtil.isOdd(keyOrValue.length)) { 177 | throw new DtRuntimeException("参数个数必须为偶数"); 178 | } 179 | 180 | for (int i = 0; i < keyOrValue.length / 2; i++) { 181 | final Object key = keyOrValue[2 * i]; 182 | final Object value = keyOrValue[2 * i + 1]; 183 | if (value == null) { 184 | jsonObject.add(key.toString(), null); 185 | continue; 186 | } 187 | 188 | if (value instanceof Boolean) { 189 | jsonObject.addProperty(key.toString(), (Boolean) value); 190 | } else if (value instanceof Character) { 191 | jsonObject.addProperty(key.toString(), (Character) value); 192 | } else if (value instanceof Number) { 193 | jsonObject.addProperty(key.toString(), (Number) value); 194 | } else if (value instanceof JsonElement) { 195 | jsonObject.add(key.toString(), (JsonElement) value); 196 | } else if (value instanceof List) { 197 | JsonArray array = new JsonArray(); 198 | ((List) value).forEach(a -> array.add(a.toString())); 199 | jsonObject.add(key.toString(), array); 200 | } else { 201 | jsonObject.addProperty(key.toString(), value.toString()); 202 | } 203 | } 204 | 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/com/github/tingyugetc520/ali/dingtalk/util/json/GsonParser.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.util.json; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonParser; 5 | import com.google.gson.stream.JsonReader; 6 | 7 | import java.io.Reader; 8 | 9 | /** 10 | * 11 | */ 12 | public class GsonParser { 13 | private static final JsonParser JSON_PARSER = new JsonParser(); 14 | 15 | public static JsonObject parse(String json) { 16 | return JSON_PARSER.parse(json).getAsJsonObject(); 17 | } 18 | 19 | public static JsonObject parse(Reader json) { 20 | return JSON_PARSER.parse(json).getAsJsonObject(); 21 | } 22 | 23 | public static JsonObject parse(JsonReader json) { 24 | return JSON_PARSER.parse(json).getAsJsonObject(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/github/tingyugetc520/ali/dingtalk/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tingyugetc520/DtJava/4d4e286b2a125386f0313381da2595f66f466d19/src/test/java/com/github/tingyugetc520/ali/dingtalk/.DS_Store -------------------------------------------------------------------------------- /src/test/java/com/github/tingyugetc520/ali/dingtalk/bean/user/DtUserTest.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.bean.user; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.testng.annotations.Test; 5 | 6 | import java.util.Date; 7 | 8 | @Test 9 | @Slf4j 10 | public class DtUserTest { 11 | 12 | public void testFromJson() { 13 | String json = "{" + 14 | " \"errcode\":0," + 15 | " \"unionEmpExt\":{}," + 16 | " \"unionid\":\"unionid\"," + 17 | " \"openId\":\"openid\"," + 18 | " \"roles\":[" + 19 | " {" + 20 | " \"groupName\":\"默认\"," + 21 | " \"name\":\"主管理员\"," + 22 | " \"id\":1834797999," + 23 | " \"type\":101" + 24 | " }" + 25 | " ]," + 26 | " \"remark\":\"\"," + 27 | " \"userid\":\"manager6666\"," + 28 | " \"isLeaderInDepts\":\"{1:false}\"," + 29 | " \"isBoss\":false," + 30 | " \"hiredDate\":1613577600000," + 31 | " \"isSenior\":false," + 32 | " \"tel\":\"\"," + 33 | " \"department\":[" + 34 | " 1" + 35 | " ]," + 36 | " \"workPlace\":\"\"," + 37 | " \"email\":\"emial@dtjava.com\"," + 38 | " \"orderInDepts\":\"{1:176305901987690512}\"," + 39 | " \"mobile\":\"1234567890\"," + 40 | " \"errmsg\":\"ok\"," + 41 | " \"active\":true," + 42 | " \"avatar\":\"\"," + 43 | " \"isAdmin\":true," + 44 | " \"tags\":{}," + 45 | " \"isHide\":false," + 46 | " \"jobnumber\":\"\"," + 47 | " \"name\":\"DtJava\"," + 48 | " \"extattr\":{" + 49 | " \"爱好\":\"写代码\"," + 50 | " \"职级\":\"架构师\"," + 51 | " \"花名\":\"花的名字\"" + 52 | " }," + 53 | " \"stateCode\":\"86\"," + 54 | " \"position\":\"\"," + 55 | " \"realAuthed\":true" + 56 | "}"; 57 | DtUser dtUser = DtUser.fromJson(json); 58 | log.info("user: {}", dtUser); 59 | } 60 | 61 | public void testToJson() { 62 | DtUser dtUser = DtUser.builder() 63 | .hiredDate(new Date(1613577600000L)) 64 | .build(); 65 | log.info("json: {}", dtUser.toJson()); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/github/tingyugetc520/ali/dingtalk/demo/DtDemoApp.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.demo; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 4 | import com.github.tingyugetc520.ali.dingtalk.api.impl.DtServiceImpl; 5 | import com.github.tingyugetc520.ali.dingtalk.bean.user.DtUser; 6 | import com.github.tingyugetc520.ali.dingtalk.constant.DtConstant; 7 | import com.github.tingyugetc520.ali.dingtalk.demo.servlet.DtEndpointServlet; 8 | import com.github.tingyugetc520.ali.dingtalk.message.DtMessageRouter; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.eclipse.jetty.server.Server; 11 | import org.eclipse.jetty.servlet.ServletHandler; 12 | import org.eclipse.jetty.servlet.ServletHolder; 13 | 14 | import java.io.InputStream; 15 | 16 | import static com.github.tingyugetc520.ali.dingtalk.constant.DtConstant.EventType.ChangeContactGroup; 17 | 18 | @Slf4j 19 | public class DtDemoApp { 20 | 21 | private static DtDemoConfigStorage dtConfigStorage; 22 | private static DtService dtService; 23 | private static DtMessageRouter dtMessageRouter; 24 | 25 | public static void main(String[] args) throws Exception { 26 | log.info("application start"); 27 | 28 | initDt(); 29 | 30 | initServer(); 31 | 32 | DtUser user = dtService.getUserService().getById(dtConfigStorage.getUserId()); 33 | log.info("dt user:{}", user); 34 | } 35 | 36 | private static void initDt() { 37 | InputStream inputStream = ClassLoader.getSystemResourceAsStream("test-config.json"); 38 | dtConfigStorage = DtDemoConfigStorage.fromJson(inputStream); 39 | 40 | dtService = new DtServiceImpl(); 41 | dtService.setDtConfigStorage(dtConfigStorage); 42 | 43 | dtMessageRouter = new DtMessageRouter(dtService); 44 | dtMessageRouter 45 | .rule().async(false).eventTypeGroup(ChangeContactGroup).eventType(DtConstant.EventType.ChangeContact.USER_ADD_ORG).handler(((message, context, dtService) -> { 46 | log.info("收到消息"); 47 | return true; 48 | })).end(); 49 | } 50 | 51 | private static void initServer() throws Exception { 52 | Server server = new Server(8080); 53 | 54 | ServletHandler servletHandler = new ServletHandler(); 55 | server.setHandler(servletHandler); 56 | 57 | ServletHolder endpointServletHolder = new ServletHolder(new DtEndpointServlet(dtConfigStorage, dtService, dtMessageRouter)); 58 | servletHandler.addServletWithMapping(endpointServletHolder, "/*"); 59 | 60 | server.start(); 61 | server.join(); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/test/java/com/github/tingyugetc520/ali/dingtalk/demo/DtDemoConfigStorage.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.demo; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.config.impl.DtDefaultConfigImpl; 4 | import com.github.tingyugetc520.ali.dingtalk.util.json.DtGsonBuilder; 5 | import com.google.gson.stream.JsonReader; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.ToString; 9 | 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.nio.charset.StandardCharsets; 13 | 14 | @EqualsAndHashCode(callSuper = true) 15 | @Data 16 | @ToString 17 | public class DtDemoConfigStorage extends DtDefaultConfigImpl { 18 | private static final long serialVersionUID = 4554681793802089123L; 19 | private String userId; 20 | 21 | public static DtDemoConfigStorage fromJson(InputStream is) { 22 | JsonReader reader = new JsonReader(new InputStreamReader(is, StandardCharsets.UTF_8)); 23 | return DtGsonBuilder.create().fromJson(reader, DtDemoConfigStorage.class); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/github/tingyugetc520/ali/dingtalk/demo/servlet/DtEndpointServlet.java: -------------------------------------------------------------------------------- 1 | package com.github.tingyugetc520.ali.dingtalk.demo.servlet; 2 | 3 | import com.github.tingyugetc520.ali.dingtalk.api.DtService; 4 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtEventMessage; 5 | import com.github.tingyugetc520.ali.dingtalk.bean.message.DtEventOutMessage; 6 | import com.github.tingyugetc520.ali.dingtalk.config.DtConfigStorage; 7 | import com.github.tingyugetc520.ali.dingtalk.error.DtRuntimeException; 8 | import com.github.tingyugetc520.ali.dingtalk.message.DtMessageRouter; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import javax.servlet.ServletException; 12 | import javax.servlet.http.HttpServlet; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.io.IOException; 16 | 17 | /** 18 | * 19 | */ 20 | @Slf4j 21 | public class DtEndpointServlet extends HttpServlet { 22 | private static final long serialVersionUID = 1L; 23 | protected DtConfigStorage configStorage; 24 | protected DtService dtService; 25 | protected DtMessageRouter dtMessageRouter; 26 | 27 | public DtEndpointServlet(DtConfigStorage configStorage, DtService dtService, 28 | DtMessageRouter dtMessageRouter) { 29 | this.configStorage = configStorage; 30 | this.dtService = dtService; 31 | this.dtMessageRouter = dtMessageRouter; 32 | } 33 | 34 | @Override 35 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 36 | log.info("收到消息"); 37 | response.setContentType("text/html;charset=utf-8"); 38 | response.setStatus(HttpServletResponse.SC_OK); 39 | 40 | String signature = request.getParameter("signature"); 41 | String nonce = request.getParameter("nonce"); 42 | String timestamp = request.getParameter("timestamp"); 43 | DtEventMessage message = null; 44 | try { 45 | message = DtEventMessage.fromEncryptedJson(request.getInputStream(), configStorage, timestamp, nonce, signature); 46 | } catch (DtRuntimeException e) { 47 | log.error("error", e); 48 | } 49 | if (message == null) { 50 | response.getWriter().println("非法请求"); 51 | log.info("非法请求"); 52 | return; 53 | } 54 | 55 | Boolean outMessage = this.dtMessageRouter.route(message); 56 | String res = DtEventOutMessage.toEncrypted(configStorage, outMessage).toEncryptedJson(); 57 | log.info("合法请求 {} 返回响应 {}", message, res); 58 | response.getWriter().println(res); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/test/resources/test-config.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "corpId": "钉钉的corpId", 3 | "agentId": "钉钉应用的agentId", 4 | "appKey": "钉钉应用的appKey", 5 | "appSecret": "钉钉应用的appSecret", 6 | "aesKey": "钉钉应用的aesKey", 7 | "token": "钉钉应用的token", 8 | "httpProxyType": "HTTP代理类型;0:不代理,1:反向代理,2:正向代理", 9 | "httpProxyHost": "HTTP正向代理的HOST", 10 | "httpProxyPort": "HTTP正向代理的Port", 11 | "httpProxyServer": "HTTP反向代理的Server", 12 | "userId": "钉钉应用的userId,主要拿来做test用" 13 | } --------------------------------------------------------------------------------