├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── client-sdk ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── smjcco │ │ └── wxpusher │ │ └── client │ │ └── sdk │ │ ├── HttpUtils.java │ │ ├── WxPusher.java │ │ └── bean │ │ ├── CreateQrcodeReq.java │ │ ├── CreateQrcodeResp.java │ │ ├── Message.java │ │ ├── MessageResult.java │ │ ├── Page.java │ │ ├── Result.java │ │ ├── ResultCode.java │ │ ├── UserType.java │ │ ├── WxUser.java │ │ └── callback │ │ ├── AppSubscribeBean.java │ │ ├── BaseCallBackReq.java │ │ ├── OrderPayBean.java │ │ └── UpCommandBean.java │ └── test │ └── java │ └── com │ └── smjcco │ └── wxpusher │ └── client │ └── sdk │ └── ClientTest.java ├── demo ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── smjcco │ │ └── wxpusher │ │ └── client │ │ └── sdk │ │ └── demo │ │ ├── AppWebMvcConfigurer.java │ │ ├── CommonService.java │ │ ├── JavaWebApplication.java │ │ ├── controller │ │ ├── CallBackController.java │ │ ├── CommonController.java │ │ ├── DisplayController.java │ │ ├── HealthCheckController.java │ │ └── SendController.java │ │ ├── cron │ │ └── ClearTimeoutDataScheduleTask.java │ │ ├── data │ │ ├── ScanQrocodeDataRepo.java │ │ └── UpCommandDataRepo.java │ │ ├── result │ │ ├── AppException.java │ │ ├── BizException.java │ │ ├── HttpException.java │ │ ├── Result.java │ │ └── ResultCode.java │ │ └── utils │ │ ├── DateUtil.java │ │ ├── RandomUtil.java │ │ └── ThrowableUtils.java │ └── resources │ ├── application-prod.properties │ ├── application-test.properties │ ├── application.properties │ ├── banner.txt │ └── templates │ └── display.ftl ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | out 22 | build 23 | 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | #develop tools file 28 | .idea 29 | *.iml 30 | .gradle 31 | 32 | .DS_Store 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | deploy-demo.sh 37 | publish.sh -------------------------------------------------------------------------------- /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 |

2 | 升级到3.x 的版本,做了很多break change,但是改动不大,2.x版本在version_2.x分支 3 |

4 | 5 | 6 | # WxPusher 7 | 微信消息实时推送服务[WxPusher],可以通过API实时给个人微信推送消息.[http://wxpusher.zjiecode.com/admin](http://wxpusher.zjiecode.com/admin) 8 | 9 | # 功能介绍说明 10 | 11 | 请直接访问官方说明文档[http://wxpusher.zjiecode.com/docs](http://wxpusher.zjiecode.com/docs) 12 | 13 | ## 目录说明 14 | - client-sdk 目录:JAVA SDK的源代码 15 | - demo 目录:[http://wxpusher.zjiecode.com/demo](http://wxpusher.zjiecode.com/demo)的源代码 16 | 17 | # 其他语言SDK 18 | - [Go-SDK](https://github.com/wxpusher/wxpusher-sdk-go) 19 | - [Python-SDK](https://github.com/wxpusher/wxpusher-sdk-python) 20 | 21 | 如果不存在你需要语言的sdk,请你按照[参考文档](http://wxpusher.zjiecode.com/docs)直接使用http调用,另外,欢迎你贡献更多语言的SDK。 22 | # Java版本SDK 23 | 24 | [ ![version](https://img.shields.io/static/v1.svg?label=version&message=3.0.2&color=brightgreen) ](https://bintray.com/zjiecode/maven/wxpusher-java-sdk) 25 | ## 目录模块说明 26 | - demo 27 | 28 | 官网演示demo源码,开发的时候,可以做参考,demo演示网站地址: [http://wxpusher.zjiecode.com/demo](http://wxpusher.zjiecode.com/demo) 29 | - sdk 30 | 31 | Java版本SDK源码,开发的时候可以直接使用。 32 | ## 添加依赖 33 | ### gradle中使用 34 | 35 | 你需要添加Jcenter库,在“build.grade”中配置: 36 | ```groovy 37 | dependencies { 38 | ...... 39 | implementation 'com.smjcco.wxpusher:client-sdk:3.0.0'//使用上面的版本号 40 | ...... 41 | } 42 | ``` 43 | 44 | ### 在maven中使用 45 | 在*pom.xml*文件中添加: 46 | ```xml 47 | 48 | com.smjcco.wxpusher 49 | client-sdk 50 | 3.0.2 51 | 52 | ``` 53 | ## 系统回调 54 | WxPusher会提供一些用户事件,比如用户关注、用户上行消息等,我们会通过回调的方式把消息推送给你。 55 | 具体方式请阅读接入文档:https://wxpusher.zjiecode.com/docs/#/?id=callback 56 | 相关的数据模型,已经定义在:com.smjcco.wxpusher.sdk.bean.callback 包下面,也可以可以参考demo模块的实现。 57 | 58 | ## 功能说明 59 | 具体每个功能的说明,请参考API说明文档,这里不再赘述。 60 | 61 | 不想看文档,可以直接参考[单元测试的用法(client-sdk/src/test/java/com/smjcco/wxpusher/client/sdk/ClientTest.java)](https://github.com/wxpusher/wxpusher-sdk-java/blob/master/client-sdk/src/test/java/com/smjcco/wxpusher/client/sdk/ClientTest.java) 62 | 63 | 64 | 65 | ### 初始化 66 | 如果是你只需要一个实例(一个appToken),可以直接使用default,在使用之前进行初始化; 67 | ```java 68 | WxPusher.initDefaultWxPusher("你自己的应用appToken"); 69 | ``` 70 | 在需要使用WxPusher的地方,世界访问default即可,比如下面这样: 71 | ```java 72 | WxPusher.getDefaultWxPusher().send(xxx); 73 | ``` 74 | 75 | 如果需要同时使用多个实例(appToken)可以直接实例化,自己进行保存: 76 | ```java 77 | new WxPusher("你自己的应用appToken"); 78 | ``` 79 | 80 | ### 发送消息 81 | 说明:调用此接口,即可把消息推送出去。 如果传递了多个uid或者topic,会返回多个结果。 82 | ```java 83 | Message message = new Message(); 84 | message.setContent("扫描成功,你可以使用demo演示程序发送消息"); 85 | message.setContentType(Message.CONTENT_TYPE_TEXT); 86 | message.setUid("UID_XXXX"); 87 | 88 | Result> result = WxPusher.getDefaultWxPusher().send(message); 89 | System.out.println("发送结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 90 | if (result.isSuccess() && result.getData() != null) { 91 | for (MessageResult messageResult : result.getData()) { 92 | System.out.println("发送到UID: " + messageResult.getUid() + ", 结果:" + messageResult.getStatus()); 93 | } 94 | } 95 | ``` 96 | ### 删除消息 97 | 说明:消息发送以后,可以调用次接口删除消息,但是请注意,只能删除用户点击详情查看的落地页面,已经推送到用户的消息记录不可以删除。 98 | ```java 99 | Result result = WxPusher.getDefaultWxPusher().deleteMessage(messageId); 100 | System.out.println("删除结果:" + result.isSuccess() + ", 状态:" + result.getData()); 101 | ``` 102 | 103 | ### 创建带参数的app临时二维码 104 | 说明:可以用于创建一个二维码,拿到扫码的用户,来和自己系统的用户做关联。 105 | 106 | 本接口和下面的queryScanUID配合使用,推荐更推荐回调:https://wxpusher.zjiecode.com/docs/#/?id=callback 107 | ```java 108 | CreateQrcodeReq req = new CreateQrcodeReq(); 109 | req.setExtra("test_extra_data"); 110 | req.setValidTime(1800); // 设置有效期为30分钟 111 | 112 | Result result = WxPusher.getDefaultWxPusher().createAppTempQrcode(req); 113 | System.out.println("创建二维码结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 114 | if (result.isSuccess() && result.getData() != null) { 115 | CreateQrcodeResp resp = result.getData(); 116 | System.out.println("二维码Code: " + resp.getCode()); 117 | System.out.println("二维码URL: " + resp.getUrl()); 118 | System.out.println("二维码短链接: " + resp.getShortUrl()); 119 | System.out.println("过期时间: " + resp.getExpires()); 120 | System.out.println("附加数据: " + resp.getExtra()); 121 | } 122 | ``` 123 | ### 查询扫码用户UID 124 | ```java 125 | String code = "上面 createAppTempQrcode 接口返回的code"; 126 | // 注意:实际使用时,应该使用轮询方式,但是轮训时间不可以小于10秒,此处仅为示例 127 | // 更推荐使用回调的方式 https://wxpusher.zjiecode.com/docs/#/?id=callback 128 | Result result = WxPusher.getDefaultWxPusher().queryScanUID(code); 129 | ``` 130 | 131 | ### 查询用户 132 | 说明:同一个用户关注多个,可能返回多个数据 133 | ```java 134 | Result> result = WxPusher.getDefaultWxPusher().queryWxUserV2(1, 10, null, false, UserType.APP); 135 | System.out.println("查询用户结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 136 | if (result.isSuccess() && result.getData() != null) { 137 | Page page = result.getData(); 138 | System.out.println("总用户数: " + page.getTotal()); 139 | System.out.println("当前页: " + page.getPage()); 140 | System.out.println("页大小: " + page.getPageSize()); 141 | 142 | if (page.getRecords() != null) { 143 | for (WxUser wxUser : page.getRecords()) { 144 | System.out.println("用户UID: " + wxUser.getUid()); 145 | System.out.println("用户ID: " + wxUser.getId()); 146 | System.out.println("应用/主题ID: " + wxUser.getAppOrTopicId()); 147 | System.out.println("关注类型: " + wxUser.getType()); 148 | System.out.println("用户是否启用: " + wxUser.isEnable()); 149 | System.out.println("用户是否拉黑: " + wxUser.isReject()); 150 | System.out.println("创建时间: " + wxUser.getCreateTime()); 151 | System.out.println("---------------------"); 152 | } 153 | } 154 | } 155 | ``` 156 | ### 删除用户 157 | 你可以通过本接口,删除用户对应用,主题的关注。 158 | 159 | 删除以后,用户可以重新关注,如不想让用户再次关注,可以调用拉黑接口,对用户拉黑。 160 | ```java 161 | Result result = wxPusher.deleteUser(userId); 162 | System.out.println("删除用户结果:" + result.isSuccess() + ", 状态:" + result.getData()); 163 | ``` 164 | 165 | ### 拉黑用户 166 | 拉黑以后不能再发送消息,用户也不能再次关注,除非你取消对他的拉黑。调用拉黑接口,不用再调用删除接口。 167 | ```java 168 | // 拉黑用户 169 | Result result = WxPusher.getDefaultWxPusher().rejectUser(userId, true); 170 | System.out.println("拉黑用户结果:" + result.isSuccess() + ", 状态:" + result.getData()); 171 | 172 | //取消拉黑用户 173 | Result result2 = WxPusher.getDefaultWxPusher().rejectUser(userId, false); 174 | System.out.println("取消拉黑用户结果:" + result2.isSuccess() + ", 状态:" + result2.getData()); 175 | ``` 176 | 177 | 使用就是这么简单,有需要就来试试吧。 178 | 179 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group = 'com.smjcco.wxpusher' 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath "org.springframework.boot:spring-boot-gradle-plugin:2.7.18" 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | mavenCentral() 15 | } 16 | } 17 | 18 | tasks.register('clean', Delete) { 19 | delete new File(getRootDir(), "out") 20 | delete new File(getRootDir(), "build") 21 | } -------------------------------------------------------------------------------- /client-sdk/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' // Java 项目 3 | id 'maven-publish' // 应用发布插件 4 | id 'signing' // 用于生成 .asc 文件 5 | id("org.danilopianini.publish-on-central") version "8.0.6" 6 | } 7 | 8 | 9 | //还有很多开发者用1.8,编译成低版本的,方便兼容 10 | java { 11 | sourceCompatibility = JavaVersion.VERSION_1_8 12 | targetCompatibility = JavaVersion.VERSION_1_8 13 | } 14 | 15 | group = 'com.smjcco.wxpusher' 16 | 17 | publishOnCentral { 18 | version = '3.0.2' 19 | repoOwner.set("wxpusher") 20 | projectDescription.set("WxPusher消息推送平台 是一个使用微信公众号作为通道的,实时信息推送平台,你可以通过调用API的方式,把信息推送到微信上,无需安装额外的软件,即可做到信息实时通知。 你可以使用WxPusher来做服务器报警通知、抢课通知、抢票通知,信息更新提示等。") 21 | projectLongName.set("WxPusher消息推送平台 Java SDK") 22 | licenseName.set("Apache License, Version 2.0") 23 | licenseUrl.set("http://www.apache.org/licenses/LICENSE-2.0") 24 | projectUrl.set("https://github.com/wxpusher/wxpusher-sdk-java") 25 | scmConnection.set("git@github.com:wxpusher/wxpusher-sdk-java.git") 26 | } 27 | 28 | publishing { 29 | publications { 30 | OSSRH(MavenPublication) { 31 | pom { 32 | developers { 33 | developer { 34 | id = 'zjiecode' 35 | name = 'zjiecode' 36 | email = 'zjiecode@gmail.com' 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | signing { 45 | useGpgCmd() 46 | sign publishing.publications.OSSRH 47 | } 48 | dependencies { 49 | implementation 'com.alibaba.fastjson2:fastjson2:2.0.56' 50 | testImplementation 'junit:junit:4.13.1' 51 | } 52 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk; 2 | 3 | import com.alibaba.fastjson2.JSON; 4 | import com.alibaba.fastjson2.JSONObject; 5 | import com.alibaba.fastjson2.TypeReference; 6 | import com.smjcco.wxpusher.client.sdk.bean.Result; 7 | import com.smjcco.wxpusher.client.sdk.bean.ResultCode; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | import java.lang.reflect.Type; 14 | import java.net.HttpURLConnection; 15 | import java.net.MalformedURLException; 16 | import java.net.URL; 17 | import java.nio.charset.Charset; 18 | import java.util.Map; 19 | import java.util.Set; 20 | 21 | /** 22 | * 说明:网络请求工具 23 | * 作者:zjiecode 24 | * 时间:2019-09-05 25 | */ 26 | public final class HttpUtils { 27 | private static final String BASE_URL = "https://wxpusher.zjiecode.com"; 28 | private static final String CHARSET_NAME = "UTF-8"; 29 | 30 | private HttpUtils() { 31 | } 32 | 33 | public static Result request(Object body, Map queryMap, String path, String method, Type returnType) { 34 | try { 35 | String url = buildUrl(path); 36 | if (queryMap != null) { 37 | String query = parseMap2Query(queryMap); 38 | if (!query.isEmpty()) { 39 | url = url + "?" + query; 40 | } 41 | } 42 | URL cUrl = new URL(url); 43 | HttpURLConnection urlConnection = getHttpURLConnection(cUrl, method); 44 | if (body != null) { 45 | OutputStream outputStream = urlConnection.getOutputStream(); 46 | String dataStr = JSON.toJSONString(body); 47 | outputStream.write(dataStr.getBytes(Charset.forName(CHARSET_NAME))); 48 | outputStream.flush(); 49 | } 50 | return dealConnect(urlConnection, returnType); 51 | } catch (MalformedURLException e) { 52 | return new Result<>(ResultCode.NETWORK_ERROR, e.getMessage()); 53 | } catch (IOException e) { 54 | return new Result<>(ResultCode.NETWORK_ERROR, e.toString()); 55 | } catch (Throwable e) { 56 | return new Result<>(ResultCode.UNKNOWN_ERROR, e.toString()); 57 | } 58 | } 59 | 60 | public static Result post(Object body, String path, Type returnType) { 61 | return request(body, null, path, "POST", returnType); 62 | } 63 | 64 | public static Result get(Map queryMap, String path, Type returnType) { 65 | return request(null, queryMap, path, "GET", returnType); 66 | } 67 | 68 | 69 | public static Result delete(Map queryData, String path, Type returnType) { 70 | return request(null, queryData, path, "DELETE", returnType); 71 | } 72 | 73 | public static Result put(Map queryData, String path, Type returnType) { 74 | return request(null, queryData, path, "PUT", returnType); 75 | } 76 | 77 | private static HttpURLConnection getHttpURLConnection(URL cUrl, String method) throws IOException { 78 | HttpURLConnection urlConnection = (HttpURLConnection) cUrl.openConnection(); 79 | urlConnection.setConnectTimeout(60000); 80 | urlConnection.setReadTimeout(60000); 81 | urlConnection.setUseCaches(false); 82 | urlConnection.setRequestMethod(method); 83 | urlConnection.setRequestProperty("Content-Type", "application/json"); 84 | urlConnection.setRequestProperty("Charset", CHARSET_NAME); 85 | urlConnection.setDoOutput(true); 86 | urlConnection.connect(); 87 | return urlConnection; 88 | } 89 | 90 | /** 91 | * 把map转成query查询字符串 92 | */ 93 | private static String parseMap2Query(Map data) { 94 | if (data == null || data.size() <= 0) { 95 | return ""; 96 | } 97 | Set> entries = data.entrySet(); 98 | StringBuilder stringBuilder = new StringBuilder(); 99 | for (Map.Entry entry : entries) { 100 | if (stringBuilder.length() > 0) { 101 | stringBuilder.append("&"); 102 | } 103 | stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()); 104 | } 105 | return stringBuilder.toString(); 106 | } 107 | 108 | /** 109 | * 110 | */ 111 | private static String buildUrl(String path) { 112 | String url = BASE_URL; 113 | if (path != null && !path.isEmpty()) { 114 | if (path.startsWith("/")) { 115 | url = BASE_URL + path; 116 | } else { 117 | url = BASE_URL + "/" + path; 118 | } 119 | } 120 | return url; 121 | } 122 | 123 | /** 124 | * 处理连接以后的状态信息 125 | * 126 | * @param urlConnection 打开的连接 127 | * @param type 返回的结果数据类型 128 | * @return 返回发送结果 129 | */ 130 | private static Result dealConnect(HttpURLConnection urlConnection, Type type) throws IOException { 131 | try { 132 | int responseCode = urlConnection.getResponseCode(); 133 | if (responseCode != 200) { 134 | return new Result<>(urlConnection.getResponseCode(), "http请求错误:" + responseCode); 135 | } 136 | InputStream inputStream = urlConnection.getInputStream(); 137 | String res = inputStream2String(inputStream); 138 | if (res == null || res.isEmpty()) { 139 | return new Result<>(ResultCode.INTERNAL_SERVER_ERROR, "服务器返回异常"); 140 | } 141 | // 构造 Result 的完整类型信息 142 | Type resultType = new TypeReference>(type) { 143 | }.getType(); 144 | // 反序列化 145 | Result result = JSONObject.parseObject(res, resultType); 146 | if (result == null) { 147 | return new Result<>(ResultCode.DATA_ERROR, "服务器返回数据解析异常"); 148 | } 149 | return result; 150 | } catch (MalformedURLException e) { 151 | return new Result<>(ResultCode.NETWORK_ERROR, e.getMessage()); 152 | } catch (IOException e) { 153 | return new Result<>(ResultCode.NETWORK_ERROR, e.getMessage()); 154 | } catch (Throwable e) { 155 | return new Result<>(ResultCode.UNKNOWN_ERROR, e.getMessage()); 156 | } 157 | } 158 | 159 | /** 160 | * 从输入流中读取内容到字符串 161 | * 162 | * @param inputStream 输入路 163 | * @return 返回字符串 164 | */ 165 | private static String inputStream2String(InputStream inputStream) { 166 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 167 | int len = 0; 168 | byte[] bytes = new byte[4096]; 169 | try { 170 | while ((len = inputStream.read(bytes)) != -1) { 171 | outputStream.write(bytes, 0, len); 172 | } 173 | return outputStream.toString(CHARSET_NAME); 174 | } catch (IOException e) { 175 | e.printStackTrace(); 176 | } 177 | return null; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/WxPusher.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk; 2 | 3 | import com.alibaba.fastjson2.TypeReference; 4 | import com.smjcco.wxpusher.client.sdk.bean.*; 5 | 6 | import java.lang.reflect.Type; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * 说明:WxPusher的客户端 13 | * 一般通过调用 initDefaultWxPusher(),初始化以后,然后就直接用getDefaultWxPusher()即可 14 | * 如果有多实例需求,可以用构造方法,多次实例化 15 | * 具体接口使用可以参考接口说明文档 16 | * 作者:zjiecode 17 | * 时间:2019-05-03 18 | */ 19 | public final class WxPusher { 20 | 21 | 22 | /** 23 | * 用来保存发送消息用的AppToken 24 | */ 25 | private String appToken; 26 | 27 | private static WxPusher defaultWxPusher; 28 | 29 | public WxPusher(String appToken) { 30 | this.appToken = appToken; 31 | } 32 | 33 | /** 34 | * 初始化WxPusher的默认实力 35 | * 36 | * @param appToken 获取到的AppToken,获取方式AppToken 37 | */ 38 | public static void initDefaultWxPusher(String appToken) { 39 | defaultWxPusher = new WxPusher(appToken); 40 | } 41 | 42 | public static WxPusher getDefaultWxPusher() { 43 | if (defaultWxPusher == null) { 44 | throw new RuntimeException("使用默认的实例前,请先调用initDefaultWxPusher进行初始化"); 45 | } 46 | return defaultWxPusher; 47 | } 48 | 49 | /** 50 | * 发送消息 51 | * 52 | * @param message 发送消息的内容 53 | * @return 发送结果,如果传递了多个uid或者topic,会返回多个结果 54 | */ 55 | public Result> send(Message message) { 56 | message.setAppToken(appToken); 57 | Result> result = verify(message); 58 | if (result != null) { 59 | return result; 60 | } 61 | Type resultType = new TypeReference>() { 62 | }.getType(); 63 | return HttpUtils.post(message, "/api/send/message", resultType); 64 | } 65 | 66 | /** 67 | * 查询消息发送状态 68 | * 69 | * @param messageId 发送接口返回的的messageId 70 | * @return 返回查询的状态 71 | * @deprecated 此接口返回数据可能不准确,只能确保wxpusher已经将消息推送到微信服务器了,用户可能拒绝接收 72 | */ 73 | public Result queryMessageStatus(Long messageId) { 74 | if (messageId == null || messageId <= 0) { 75 | return new Result<>(ResultCode.BIZ_FAIL, "messageId为空"); 76 | } 77 | return HttpUtils.get(null, String.format("/api/send/query/%s", messageId), Integer.class); 78 | } 79 | 80 | 81 | /** 82 | * 删除消息 83 | * 说明:消息发送以后,可以调用次接口删除消息,但是请注意,只能删除用户点击详情查看的落地页面,已经推送到用户的消息记录不可以删除。 84 | * 85 | * @param messageId 发送接口返回的消息内容id,调用一次接口生成一个,如果是发送给多个用户,多个用户共享一个messageContentId,通过messageContentId可以删除内容,删除后本次发送的所有用户都无法再查看本条消息 86 | * @return 是否删除成功 87 | */ 88 | public Result deleteMessage(Long messageId) { 89 | if (messageId == null || messageId <= 0) { 90 | return new Result<>(ResultCode.BIZ_FAIL, "messageId错误"); 91 | } 92 | Map params = new HashMap<>(); 93 | params.put("messageContentId", messageId); 94 | params.put("appToken", appToken); 95 | return HttpUtils.delete(params, "/api/send/message", Boolean.class); 96 | } 97 | 98 | /** 99 | * 创建带参数的app临时二维码 100 | * 101 | * @param createQrcodeReq 创建二维码参数 102 | * @return 返回创建的二维码 103 | */ 104 | public Result createAppTempQrcode(CreateQrcodeReq createQrcodeReq) { 105 | createQrcodeReq.setAppToken(appToken); 106 | return HttpUtils.post(createQrcodeReq, "/api/fun/create/qrcode", CreateQrcodeResp.class); 107 | } 108 | 109 | /** 110 | * 查询用户,同一个用户关注多个,可能返回多个数据 111 | *

112 | * 查询用户信息 113 | *

114 | * 115 | * @param page 请求数据的页码 116 | * @param pageSize 分页大小,不能超过100 117 | * @param uid 用户的uid,可选,如果不传就是查询所有用户,传uid就是查某个用户的信息。 118 | * @param isBlock 查询拉黑用户,可选,不传查询所有用户,true查询拉黑用户,false查询没有拉黑的用户 119 | * @param type 关注的类型,可选,不传查询所有用户,0是应用,1是主题 120 | * @return 返回查询到的用户分页数据 121 | */ 122 | public Result> queryWxUserV2(Integer page, Integer pageSize, 123 | String uid, boolean isBlock, UserType type) { 124 | if (appToken == null || appToken.isEmpty()) { 125 | return new Result<>(ResultCode.BIZ_FAIL, "appToken不能为空"); 126 | } 127 | if (page == null || page <= 0) { 128 | return new Result<>(ResultCode.BIZ_FAIL, "page不合法"); 129 | } 130 | if (pageSize == null || pageSize <= 0 || pageSize > 100) { 131 | return new Result<>(ResultCode.BIZ_FAIL, "pageSize不合法"); 132 | } 133 | Map params = new HashMap<>(); 134 | params.put("appToken", appToken); 135 | params.put("page", page); 136 | params.put("pageSize", pageSize); 137 | params.put("isBlock", isBlock); 138 | params.put("type", type.getType()); 139 | if (uid != null && !uid.isEmpty()) { 140 | params.put("uid", uid); 141 | } 142 | 143 | Type resultType = new TypeReference>() { 144 | }.getType(); 145 | return HttpUtils.get(params, "/api/fun/wxuser/v2", resultType); 146 | } 147 | 148 | /** 149 | * 查询扫码用户UID 150 | * 用户扫描参数二维码后,设置了回调地址,我们会通过回调地址把用户的UID推送给你的服务,具体见回调说明,推荐使用这种回调的方式。 151 | * 但是部分用户场景简单,或者没有后端服务,比如客户端软件,使用很不方便,因此我们增加了这个查询接口,通过上面的创建参数二维码接口创建一个二维码,你会拿到一个二维码的code,用此code配合这个接口,可以查询到最后一次扫描参数二维码用户的UID。 152 | * 【轮训时间间隔不能小于10秒!!禁止死循环轮训,用户退出后,必须关闭轮训,否则封号。】 153 | * 154 | * @param code 创建参数二维码接口返回的code参数。 155 | * @return 扫码用户的UID 156 | */ 157 | public Result queryScanUID(String code) { 158 | if (appToken == null || appToken.isEmpty()) { 159 | return new Result<>(ResultCode.BIZ_FAIL, "appToken不能为空"); 160 | } 161 | if (code == null || code.isEmpty()) { 162 | return new Result<>(ResultCode.BIZ_FAIL, "code不能为空"); 163 | } 164 | Map params = new HashMap<>(); 165 | params.put("appToken", appToken); 166 | params.put("code", code); 167 | return HttpUtils.get(params, "/api/fun/scan-qrcode-uid", String.class); 168 | } 169 | 170 | /** 171 | * 删除用户 172 | * 你可以通过本接口,删除用户对应用,主题的关注。 173 | * 说明:你可以删除用户对应用、主题的关注,删除以后,用户可以重新关注,如不想让用户再次关注,可以调用拉黑接口,对用户拉黑。 174 | * 175 | * @param id 用户id,通过用户查询接口可以获取 176 | * @return 操作是否成功 177 | */ 178 | public Result deleteUser(Long id) { 179 | if (id == null || id <= 0) { 180 | return new Result<>(ResultCode.BIZ_FAIL, "id错误"); 181 | } 182 | Map params = new HashMap<>(); 183 | params.put("id", id); 184 | params.put("appToken", appToken); 185 | Result respResult = HttpUtils.delete(params, "/api/fun/remove", String.class); 186 | //兼容一下返回数据 187 | Result result = new Result<>(respResult.getCode(), respResult.getMsg()); 188 | result.setData(respResult.isSuccess()); 189 | return result; 190 | } 191 | 192 | /** 193 | * 你可以通过本接口,可以拉黑用户 194 | * 说明:拉黑以后不能再发送消息,用户也不能再次关注,除非你取消对他的拉黑。调用拉黑接口,不用再调用删除接口。 195 | * 196 | * @param id 通过用户查询接口可以获取 197 | * @param reject 是否拉黑,true表示拉黑,false表示取消拉黑 198 | * @return 操作结果 199 | */ 200 | public Result rejectUser(Long id, Boolean reject) { 201 | if (id == null || id <= 0) { 202 | return new Result<>(ResultCode.BIZ_FAIL, "id错误"); 203 | } 204 | if (reject == null) { 205 | return new Result<>(ResultCode.BIZ_FAIL, "reject错误"); 206 | } 207 | Map params = new HashMap<>(); 208 | params.put("id", id); 209 | params.put("appToken", appToken); 210 | params.put("reject", reject); 211 | Result respResult = HttpUtils.put(params, "/api/fun/reject", String.class); 212 | //兼容一下返回数据 213 | Result result = new Result<>(respResult.getCode(), respResult.getMsg()); 214 | result.setData(respResult.isSuccess()); 215 | return result; 216 | } 217 | 218 | /** 219 | * 验证消息合法性,客户端验证比较宽松,主要在服务端进行校验 220 | */ 221 | private static Result verify(Message message) { 222 | if (message == null) { 223 | return new Result<>(ResultCode.BIZ_FAIL, "消息不能为空"); 224 | } 225 | if (message.getAppToken() == null || message.getAppToken().length() <= 0) { 226 | return new Result<>(ResultCode.BIZ_FAIL, "appToken不能为空"); 227 | } 228 | if (message.getContent() == null || message.getContent().length() <= 0) { 229 | return new Result<>(ResultCode.BIZ_FAIL, "content内容不能为空"); 230 | } 231 | return null; 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/CreateQrcodeReq.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean; 2 | 3 | /** 4 | * 说明:创建带参数的app临时二维码 5 | * 作者:zjiecode 6 | * 时间:2019-09-29 7 | */ 8 | public class CreateQrcodeReq { 9 | //应用的apptoken 10 | private String appToken; 11 | //附带的数据 12 | private String extra; 13 | //二维码有效时间,s为单位,最大30天。 14 | private Integer validTime; 15 | 16 | public String getAppToken() { 17 | return appToken; 18 | } 19 | 20 | public void setAppToken(String appToken) { 21 | this.appToken = appToken; 22 | } 23 | 24 | public String getExtra() { 25 | return extra; 26 | } 27 | 28 | public void setExtra(String extra) { 29 | this.extra = extra; 30 | } 31 | 32 | public Integer getValidTime() { 33 | return validTime; 34 | } 35 | 36 | public void setValidTime(Integer validTime) { 37 | this.validTime = validTime; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/CreateQrcodeResp.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean; 2 | 3 | /** 4 | * 说明:创建带参数的app临时二维码 5 | * 作者:zjiecode 6 | * 时间:2019-09-29 7 | */ 8 | public class CreateQrcodeResp { 9 | private long expires; 10 | private String code; 11 | private String shortUrl; 12 | private String url; 13 | private String extra; 14 | 15 | public long getExpires() { 16 | return expires; 17 | } 18 | 19 | public void setExpires(long expires) { 20 | this.expires = expires; 21 | } 22 | 23 | public String getCode() { 24 | return code; 25 | } 26 | 27 | public void setCode(String code) { 28 | this.code = code; 29 | } 30 | 31 | public String getShortUrl() { 32 | return shortUrl; 33 | } 34 | 35 | public void setShortUrl(String shortUrl) { 36 | this.shortUrl = shortUrl; 37 | } 38 | 39 | public String getUrl() { 40 | return url; 41 | } 42 | 43 | public void setUrl(String url) { 44 | this.url = url; 45 | } 46 | 47 | public String getExtra() { 48 | return extra; 49 | } 50 | 51 | public void setExtra(String extra) { 52 | this.extra = extra; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/Message.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * 说明: 8 | * 作者:zjiecode 9 | * 时间:2019-09-05 10 | */ 11 | public class Message { 12 | /** 13 | * 1:text,可以直接显示在卡片里面 14 | * 2:html,点击以后查看,支持html 15 | * 3:md,markdown格式,和html类似 16 | */ 17 | public static final int CONTENT_TYPE_TEXT = 1; 18 | public static final int CONTENT_TYPE_HTML = 2; 19 | public static final int CONTENT_TYPE_MD = 3; 20 | 21 | /** 22 | * verifyPayType=0,表示本条消息,不验证付费状态,发送给所有用户 23 | * verifyPayType=1,表示本条消息,只发送给付费订阅期内的用户 24 | * verifyPayType=2,表示本条消息,只发送给未订阅或者付费订阅过期的用户 25 | */ 26 | public static final int VERIFY_PAY_TYPE_IGNORE = 0; 27 | public static final int VERIFY_PAY_TYPE_IN_PAY = 1; 28 | public static final int VERIFY_PAY_TYPE_OUT_PAY = 2; 29 | 30 | private String appToken; 31 | 32 | //发送的目标 33 | private Set uids; 34 | private Set topicIds; 35 | 36 | private Integer contentType; 37 | 38 | private String content; 39 | 40 | private String summary; 41 | 42 | private Integer verifyPayType; 43 | 44 | private String url; 45 | 46 | public String getAppToken() { 47 | return appToken; 48 | } 49 | 50 | /** 51 | * 不需要外部设置,后面会删除这个字段 52 | * @param appToken 应用token 53 | * @deprecated 不要使用,内部接口依赖,后面会删除。 54 | */ 55 | public void setAppToken(String appToken) { 56 | this.appToken = appToken; 57 | } 58 | 59 | public Set getUids() { 60 | return uids; 61 | } 62 | 63 | public void setUid(String uid) { 64 | this.uids = new HashSet<>(1); 65 | this.uids.add(uid); 66 | } 67 | 68 | public void setTopicId(Long topicId) { 69 | this.topicIds = new HashSet<>(1); 70 | this.topicIds.add(topicId); 71 | } 72 | 73 | public void setUids(Set uids) { 74 | this.uids = uids; 75 | } 76 | 77 | public Set getTopicIds() { 78 | return topicIds; 79 | } 80 | 81 | public void setTopicIds(Set topicIds) { 82 | this.topicIds = topicIds; 83 | } 84 | 85 | public Integer getContentType() { 86 | return contentType; 87 | } 88 | 89 | public void setContentType(Integer contentType) { 90 | this.contentType = contentType; 91 | } 92 | 93 | public String getContent() { 94 | return content; 95 | } 96 | 97 | public String getSummary() { 98 | return summary; 99 | } 100 | 101 | public void setSummary(String summary) { 102 | this.summary = summary; 103 | } 104 | 105 | /** 106 | * 只需要 body 标签内部的内容。 107 | * @param content 内容 108 | */ 109 | public void setContent(String content) { 110 | this.content = content; 111 | } 112 | 113 | public String getUrl() { 114 | return url; 115 | } 116 | 117 | public void setUrl(String url) { 118 | this.url = url; 119 | } 120 | 121 | public void setVerifyPayType(Integer verifyPayType) { 122 | this.verifyPayType = verifyPayType; 123 | } 124 | 125 | public Integer getVerifyPayType() { 126 | return verifyPayType; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/MessageResult.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean; 2 | 3 | /** 4 | * 说明: 5 | * 作者:zjiecode 6 | * 时间:2019-09-23 7 | */ 8 | public class MessageResult { 9 | private String uid; 10 | private String status; 11 | private Integer code; 12 | private Long messageId; 13 | 14 | /** 15 | * 请求服务端是否成功,这个判断成功 以后,再判断业务状态 16 | * @return 结果 17 | */ 18 | public boolean isSuccess() { 19 | return code == ResultCode.SUCCESS.getCode(); 20 | } 21 | 22 | public String getUid() { 23 | return uid; 24 | } 25 | 26 | public void setUid(String uid) { 27 | this.uid = uid; 28 | } 29 | 30 | public String getStatus() { 31 | return status; 32 | } 33 | 34 | public void setStatus(String status) { 35 | this.status = status; 36 | } 37 | 38 | public Integer getCode() { 39 | return code; 40 | } 41 | 42 | public void setCode(Integer code) { 43 | this.code = code; 44 | } 45 | 46 | public Long getMessageId() { 47 | return messageId; 48 | } 49 | 50 | public void setMessageId(Long messageId) { 51 | this.messageId = messageId; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/Page.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 说明:分页数据 7 | * 作者:zjiecode 8 | * 时间:2019-10-28 9 | */ 10 | public class Page { 11 | private Integer total; 12 | private Integer page; 13 | private Integer pageSize; 14 | private List records; 15 | 16 | public Integer getTotal() { 17 | return total; 18 | } 19 | 20 | public void setTotal(Integer total) { 21 | this.total = total; 22 | } 23 | 24 | public Integer getPage() { 25 | return page; 26 | } 27 | 28 | public void setPage(Integer page) { 29 | this.page = page; 30 | } 31 | 32 | public Integer getPageSize() { 33 | return pageSize; 34 | } 35 | 36 | public void setPageSize(Integer pageSize) { 37 | this.pageSize = pageSize; 38 | } 39 | 40 | public List getRecords() { 41 | return records; 42 | } 43 | 44 | public void setRecords(List records) { 45 | this.records = records; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/Result.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean; 2 | 3 | public class Result { 4 | private Integer code; 5 | private String msg; 6 | private T data; 7 | 8 | public Result() { 9 | } 10 | 11 | public Result(Integer code, String msg) { 12 | this.code = code; 13 | this.msg = msg; 14 | } 15 | 16 | public Result(ResultCode resultCode, String msg) { 17 | this.code = resultCode.getCode(); 18 | this.msg = msg; 19 | } 20 | 21 | public boolean isSuccess() { 22 | return code == ResultCode.SUCCESS.getCode(); 23 | } 24 | 25 | public T getData() { 26 | return data; 27 | } 28 | 29 | public void setData(T data) { 30 | this.data = data; 31 | } 32 | 33 | public Integer getCode() { 34 | return code; 35 | } 36 | 37 | public void setCode(Integer code) { 38 | this.code = code; 39 | } 40 | 41 | public String getMsg() { 42 | return msg; 43 | } 44 | 45 | public void setMsg(String msg) { 46 | this.msg = msg; 47 | } 48 | } -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/ResultCode.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean; 2 | 3 | /** 4 | * 返回编码,参考http语义 5 | */ 6 | public enum ResultCode { 7 | SUCCESS(1000),//成功 8 | BIZ_FAIL(1001),//业务异常错误 9 | UNAUTHORIZED(1002),//未认证 10 | SIGN_FAIL(1003),//签名错误 11 | NOT_FOUND(1004),//接口不存在 12 | INTERNAL_SERVER_ERROR(1005),//服务器内部错误 13 | WEIXIN_ERROR(1006),//和微信交互的过程中发生异常 14 | NETWORK_ERROR(1007),//网络异常 15 | DATA_ERROR(1008),//数据异常 16 | UNKNOWN_ERROR(1009),//未知异常 17 | ; 18 | 19 | private final int code; 20 | 21 | ResultCode(int code) { 22 | this.code = code; 23 | } 24 | 25 | public int getCode() { 26 | return code; 27 | } 28 | } -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/UserType.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean; 2 | 3 | public enum UserType { 4 | APP(0), 5 | TOPIC(1), 6 | ; 7 | private final Integer type; 8 | 9 | UserType(Integer type) { 10 | this.type = type; 11 | } 12 | 13 | public Integer getType() { 14 | return type; 15 | } 16 | } -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/WxUser.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean; 2 | 3 | /** 4 | * 说明:微信用户数据 5 | * 作者:zjiecode 6 | * 时间:2019-10-28 7 | */ 8 | public class WxUser { 9 | 10 | //id,如果调用删除或者拉黑接口,需要这个id 11 | private long id; 12 | //UID,用户标志 13 | private String uid; 14 | /** 15 | * 用户是否打开接收消息 16 | */ 17 | private boolean enable; 18 | //用户关注的应用或者主题id,根据type来区分 19 | private Long appOrTopicId; 20 | // 关注的应用或者主题名字 21 | private String target; 22 | /** 23 | * 昵称 24 | * 25 | * @deprecated 微信已经不再返回这个字段,如果后台后备注,会返回备注 26 | */ 27 | private String nickName; 28 | 29 | /** 30 | * 是否拉黑用户 31 | */ 32 | private boolean reject; 33 | /** 34 | * 关注类型,0:关注应用,1:关注topic 35 | * @see UserType 36 | */ 37 | private int type; 38 | //关注的应用或者主题名字 39 | private String name; 40 | 41 | /** 42 | * /0表示用户不是付费用户,大于0表示用户付费订阅到期时间,毫秒级时间戳 43 | */ 44 | private long payEndTime; 45 | //用户关注应用的时间 46 | private long createTime; 47 | 48 | public long getId() { 49 | return id; 50 | } 51 | 52 | public void setId(long id) { 53 | this.id = id; 54 | } 55 | 56 | public String getUid() { 57 | return uid; 58 | } 59 | 60 | public void setUid(String uid) { 61 | this.uid = uid; 62 | } 63 | 64 | public boolean isEnable() { 65 | return enable; 66 | } 67 | 68 | public void setEnable(boolean enable) { 69 | this.enable = enable; 70 | } 71 | 72 | public Long getAppOrTopicId() { 73 | return appOrTopicId; 74 | } 75 | 76 | public void setAppOrTopicId(Long appOrTopicId) { 77 | this.appOrTopicId = appOrTopicId; 78 | } 79 | 80 | public String getTarget() { 81 | return target; 82 | } 83 | 84 | public void setTarget(String target) { 85 | this.target = target; 86 | } 87 | 88 | public String getNickName() { 89 | return nickName; 90 | } 91 | 92 | public void setNickName(String nickName) { 93 | this.nickName = nickName; 94 | } 95 | 96 | public boolean isReject() { 97 | return reject; 98 | } 99 | 100 | public void setReject(boolean reject) { 101 | this.reject = reject; 102 | } 103 | 104 | public int getType() { 105 | return type; 106 | } 107 | 108 | public void setType(int type) { 109 | this.type = type; 110 | } 111 | 112 | public String getName() { 113 | return name; 114 | } 115 | 116 | public void setName(String name) { 117 | this.name = name; 118 | } 119 | 120 | public long getPayEndTime() { 121 | return payEndTime; 122 | } 123 | 124 | public void setPayEndTime(long payEndTime) { 125 | this.payEndTime = payEndTime; 126 | } 127 | 128 | public long getCreateTime() { 129 | return createTime; 130 | } 131 | 132 | public void setCreateTime(long createTime) { 133 | this.createTime = createTime; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/callback/AppSubscribeBean.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean.callback; 2 | 3 | /** 4 | * 说明:二维码被扫描的时候的回调数据结构 5 | * 作者:zjiecode 6 | * 时间:2019-10-05 7 | */ 8 | public class AppSubscribeBean { 9 | public static final String SOURCE_SCAN = "scan"; 10 | public static final String SOURCE_LINK = "link"; 11 | public static final String SOURCE_COMMAND = "command"; 12 | public static final String SOURCE_ORDER = "order"; 13 | 14 | private String uid; 15 | private Long appId; 16 | private String appName; 17 | private String userName; 18 | private String userHeadImg; 19 | private Long time; 20 | //来源:scan:扫码订阅,link:通过链接订阅 21 | private String source; 22 | //附加信息 23 | private String extra; 24 | 25 | public String getUid() { 26 | return uid; 27 | } 28 | 29 | public void setUid(String uid) { 30 | this.uid = uid; 31 | } 32 | 33 | public String getAppName() { 34 | return appName; 35 | } 36 | 37 | public void setAppName(String appName) { 38 | this.appName = appName; 39 | } 40 | 41 | public Long getTime() { 42 | return time; 43 | } 44 | 45 | public void setTime(Long time) { 46 | this.time = time; 47 | } 48 | 49 | public String getSource() { 50 | return source; 51 | } 52 | 53 | public void setSource(String source) { 54 | this.source = source; 55 | } 56 | 57 | public String getExtra() { 58 | return extra; 59 | } 60 | 61 | public void setExtra(String extra) { 62 | this.extra = extra; 63 | } 64 | 65 | public Long getAppId() { 66 | return appId; 67 | } 68 | 69 | public void setAppId(Long appId) { 70 | this.appId = appId; 71 | } 72 | 73 | public String getUserName() { 74 | return userName; 75 | } 76 | 77 | public void setUserName(String userName) { 78 | this.userName = userName; 79 | } 80 | 81 | public String getUserHeadImg() { 82 | return userHeadImg; 83 | } 84 | 85 | public void setUserHeadImg(String userHeadImg) { 86 | this.userHeadImg = userHeadImg; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/callback/BaseCallBackReq.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean.callback; 2 | 3 | /** 4 | * 说明:wxpusher回调的数据结构 5 | * 作者:zjiecode 6 | * 时间:2019-10-05 7 | */ 8 | public class BaseCallBackReq { 9 | //二维码被扫描的时候 10 | public static final String ACTION_APP_SUBSCRIBE="app_subscribe"; 11 | //上行指令的KEY 12 | public static final String ACTION_SEND_UP_CMD = "send_up_cmd"; 13 | //支付事件 14 | public static final String ACTION_ORDER_PAY = "order_pay"; 15 | //回调的事件 16 | private String action; 17 | private Object data; 18 | 19 | public String getAction() { 20 | return action; 21 | } 22 | 23 | public void setAction(String action) { 24 | this.action = action; 25 | } 26 | 27 | public Object getData() { 28 | return data; 29 | } 30 | 31 | public void setData(Object data) { 32 | this.data = data; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/callback/OrderPayBean.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean.callback; 2 | 3 | public class OrderPayBean { 4 | //付费增加的时间,毫秒。退款是负数,表示减少的订阅时间。 5 | private long addTime; 6 | //金额,单位分,退款是负数 7 | private long amount; 8 | //发生的应用id 9 | private long appId; 10 | //发生时间,毫秒级时间戳 11 | private long createTime; 12 | //产品id 13 | private int prodId; 14 | //支付或者退款的交易号,和用户微信账单中的商户号对应 15 | private String tradeNo; 16 | //1表示付款,2表示退款 17 | private int type; 18 | //发生用户的uid 19 | private String uid; 20 | 21 | public long getAddTime() { 22 | return addTime; 23 | } 24 | 25 | public void setAddTime(long addTime) { 26 | this.addTime = addTime; 27 | } 28 | 29 | public long getAmount() { 30 | return amount; 31 | } 32 | 33 | public void setAmount(long amount) { 34 | this.amount = amount; 35 | } 36 | 37 | public long getAppId() { 38 | return appId; 39 | } 40 | 41 | public void setAppId(long appId) { 42 | this.appId = appId; 43 | } 44 | 45 | public long getCreateTime() { 46 | return createTime; 47 | } 48 | 49 | public void setCreateTime(long createTime) { 50 | this.createTime = createTime; 51 | } 52 | 53 | public int getProdId() { 54 | return prodId; 55 | } 56 | 57 | public void setProdId(int prodId) { 58 | this.prodId = prodId; 59 | } 60 | 61 | public String getTradeNo() { 62 | return tradeNo; 63 | } 64 | 65 | public void setTradeNo(String tradeNo) { 66 | this.tradeNo = tradeNo; 67 | } 68 | 69 | public int getType() { 70 | return type; 71 | } 72 | 73 | public void setType(int type) { 74 | this.type = type; 75 | } 76 | 77 | public String getUid() { 78 | return uid; 79 | } 80 | 81 | public void setUid(String uid) { 82 | this.uid = uid; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /client-sdk/src/main/java/com/smjcco/wxpusher/client/sdk/bean/callback/UpCommandBean.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.bean.callback; 2 | 3 | /** 4 | * 说明:上行消息内容 5 | * 作者:zjiecode 6 | * 时间:2020-10-17 7 | */ 8 | public class UpCommandBean { 9 | private String uid; 10 | private Long appId; 11 | private String appKey; 12 | private String appName; 13 | private String userName; 14 | private Long time; 15 | //消息内容 16 | private String content; 17 | 18 | public String getUid() { 19 | return uid; 20 | } 21 | 22 | public void setUid(String uid) { 23 | this.uid = uid; 24 | } 25 | 26 | public Long getAppId() { 27 | return appId; 28 | } 29 | 30 | public void setAppId(Long appId) { 31 | this.appId = appId; 32 | } 33 | 34 | public String getAppKey() { 35 | return appKey; 36 | } 37 | 38 | public void setAppKey(String appKey) { 39 | this.appKey = appKey; 40 | } 41 | 42 | public String getAppName() { 43 | return appName; 44 | } 45 | 46 | public void setAppName(String appName) { 47 | this.appName = appName; 48 | } 49 | 50 | public String getUserName() { 51 | return userName; 52 | } 53 | 54 | public void setUserName(String userName) { 55 | this.userName = userName; 56 | } 57 | 58 | public Long getTime() { 59 | return time; 60 | } 61 | 62 | public void setTime(Long time) { 63 | this.time = time; 64 | } 65 | 66 | public String getContent() { 67 | return content; 68 | } 69 | 70 | public void setContent(String content) { 71 | this.content = content; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /client-sdk/src/test/java/com/smjcco/wxpusher/client/sdk/ClientTest.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk; 2 | 3 | import com.smjcco.wxpusher.client.sdk.bean.*; 4 | import org.junit.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | /** 13 | * 说明:接口测试 14 | * 作者:zjiecode 15 | * 时间:2019-05-03 16 | */ 17 | public class ClientTest { 18 | 19 | private static final String TEST_APP_TOKEN = "AT_xxx"; // 测试用的APP_TOKEN,请替换为自己的 20 | private static final String TEST_UID = "UID_xx"; // 测试用的UID,请替换为自己的 21 | private static final Long TEST_TOPIC_ID = 6950L; // 测试用的主题ID,请替换为自己的 22 | 23 | 24 | @Before 25 | public void setup() { 26 | // 也可以使用静态方法初始化默认实例 27 | WxPusher.initDefaultWxPusher(TEST_APP_TOKEN); 28 | } 29 | 30 | /** 31 | * 测试发送文本消息到单个用户 32 | */ 33 | @Test 34 | public void testSendTextToUser() { 35 | Message message = new Message(); 36 | message.setContentType(Message.CONTENT_TYPE_TEXT); 37 | message.setContent("这是一条测试文本消息"); 38 | message.setUid(TEST_UID); 39 | 40 | Result> result = WxPusher.getDefaultWxPusher().send(message); 41 | System.out.println("发送结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 42 | if (result.isSuccess() && result.getData() != null) { 43 | for (MessageResult messageResult : result.getData()) { 44 | System.out.println("发送到UID: " + messageResult.getUid() + ", 结果:" + messageResult.getStatus()); 45 | } 46 | } 47 | 48 | // 添加断言 49 | Assert.assertTrue("发送消息应该成功", result.isSuccess()); 50 | Assert.assertEquals("处理成功", result.getMsg()); 51 | Assert.assertNotNull("返回数据不应为空", result.getData()); 52 | Assert.assertFalse("返回结果列表不应为空", result.getData().isEmpty()); 53 | 54 | MessageResult messageResult = result.getData().get(0); 55 | Assert.assertEquals("UID应匹配", TEST_UID, messageResult.getUid()); 56 | Assert.assertTrue("消息发送状态应包含'创建发送任务成功'", messageResult.getStatus().contains("创建发送任务成功")); 57 | Assert.assertTrue("消息发送码应为成功", messageResult.isSuccess()); 58 | } 59 | 60 | /** 61 | * 测试发送HTML消息到单个用户 62 | */ 63 | @Test 64 | public void testSendHtmlToUser() { 65 | Message message = new Message(); 66 | message.setContentType(Message.CONTENT_TYPE_HTML); 67 | message.setContent("

HTML消息测试

这是一条红色的测试消息

"); 68 | message.setUid(TEST_UID); 69 | message.setSummary("HTML消息测试"); 70 | 71 | Result> result = WxPusher.getDefaultWxPusher().send(message); 72 | System.out.println("发送结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 73 | if (result.isSuccess() && result.getData() != null) { 74 | for (MessageResult messageResult : result.getData()) { 75 | System.out.println("发送到UID: " + messageResult.getUid() + ", 结果:" + messageResult.getStatus()); 76 | } 77 | } 78 | 79 | // 添加断言 80 | Assert.assertTrue("发送HTML消息应该成功", result.isSuccess()); 81 | Assert.assertNotNull("返回数据不应为空", result.getData()); 82 | Assert.assertFalse("返回结果列表不应为空", result.getData().isEmpty()); 83 | 84 | MessageResult messageResult = result.getData().get(0); 85 | Assert.assertEquals("UID应匹配", TEST_UID, messageResult.getUid()); 86 | } 87 | 88 | /** 89 | * 测试发送Markdown消息到单个用户 90 | */ 91 | @Test 92 | public void testSendMarkdownToUser() { 93 | Message message = new Message(); 94 | message.setContentType(Message.CONTENT_TYPE_MD); 95 | message.setContent("# Markdown消息测试\n\n**这是加粗文本**\n\n*这是斜体文本*"); 96 | message.setUid(TEST_UID); 97 | message.setSummary("Markdown消息测试"); 98 | 99 | Result> result = WxPusher.getDefaultWxPusher().send(message); 100 | System.out.println("发送结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 101 | if (result.isSuccess() && result.getData() != null) { 102 | for (MessageResult messageResult : result.getData()) { 103 | System.out.println("发送到UID: " + messageResult.getUid() + ", 结果:" + messageResult.getStatus()); 104 | } 105 | } 106 | 107 | // 添加断言 108 | Assert.assertTrue("发送Markdown消息应该成功", result.isSuccess()); 109 | Assert.assertNotNull("返回数据不应为空", result.getData()); 110 | Assert.assertFalse("返回结果列表不应为空", result.getData().isEmpty()); 111 | } 112 | 113 | /** 114 | * 测试发送消息到主题 115 | */ 116 | @Test 117 | public void testSendToTopic() { 118 | Message message = new Message(); 119 | message.setContentType(Message.CONTENT_TYPE_TEXT); 120 | message.setContent("这是一条发送到主题的测试消息"); 121 | message.setTopicId(TEST_TOPIC_ID); 122 | message.setSummary("主题消息测试"); 123 | 124 | Result> result = WxPusher.getDefaultWxPusher().send(message); 125 | System.out.println("发送结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 126 | if (result.isSuccess() && result.getData() != null) { 127 | for (MessageResult messageResult : result.getData()) { 128 | System.out.println("发送结果:" + messageResult.getStatus()); 129 | } 130 | } 131 | 132 | // 添加断言 133 | Assert.assertTrue("发送主题消息应该成功", result.isSuccess()); 134 | Assert.assertNotNull("返回数据不应为空", result.getData()); 135 | Assert.assertFalse("返回结果列表不应为空", result.getData().isEmpty()); 136 | } 137 | 138 | /** 139 | * 测试发送消息到多个用户 140 | */ 141 | @Test 142 | public void testSendToMultiUsers() { 143 | Message message = new Message(); 144 | message.setContentType(Message.CONTENT_TYPE_TEXT); 145 | message.setContent("这是一条发送到多个用户的测试消息"); 146 | 147 | // 添加多个UID 148 | Set uids = new HashSet<>(); 149 | uids.add(TEST_UID); 150 | // 添加更多用户... 151 | // uids.add("UID_xxx2"); 152 | message.setUids(uids); 153 | 154 | Result> result = WxPusher.getDefaultWxPusher().send(message); 155 | System.out.println("发送结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 156 | if (result.isSuccess() && result.getData() != null) { 157 | for (MessageResult messageResult : result.getData()) { 158 | System.out.println("发送到UID: " + messageResult.getUid() + ", 结果:" + messageResult.getStatus()); 159 | } 160 | } 161 | 162 | // 添加断言 163 | Assert.assertTrue("发送多用户消息应该成功", result.isSuccess()); 164 | Assert.assertNotNull("返回数据不应为空", result.getData()); 165 | Assert.assertFalse("返回结果列表不应为空", result.getData().isEmpty()); 166 | 167 | MessageResult messageResult = result.getData().get(0); 168 | Assert.assertEquals("UID应匹配", TEST_UID, messageResult.getUid()); 169 | Assert.assertTrue("消息发送状态应包含'创建发送任务成功'", messageResult.getStatus().contains("创建发送任务成功")); 170 | } 171 | 172 | /** 173 | * 测试发送带URL的消息 174 | */ 175 | @Test 176 | public void testSendWithUrl() { 177 | Message message = new Message(); 178 | message.setContentType(Message.CONTENT_TYPE_TEXT); 179 | message.setContent("这是一条带链接的测试消息"); 180 | message.setUid(TEST_UID); 181 | message.setUrl("https://wxpusher.zjiecode.com"); 182 | 183 | Result> result = WxPusher.getDefaultWxPusher().send(message); 184 | System.out.println("发送结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 185 | if (result.isSuccess() && result.getData() != null) { 186 | for (MessageResult messageResult : result.getData()) { 187 | System.out.println("发送到UID: " + messageResult.getUid() + ", 结果:" + messageResult.getStatus()); 188 | } 189 | } 190 | 191 | // 添加断言 192 | Assert.assertTrue("发送带URL消息应该成功", result.isSuccess()); 193 | Assert.assertEquals("处理成功", result.getMsg()); 194 | Assert.assertNotNull("返回数据不应为空", result.getData()); 195 | Assert.assertFalse("返回结果列表不应为空", result.getData().isEmpty()); 196 | 197 | MessageResult messageResult = result.getData().get(0); 198 | Assert.assertEquals("UID应匹配", TEST_UID, messageResult.getUid()); 199 | Assert.assertTrue("消息发送状态应包含'创建发送任务成功'", messageResult.getStatus().contains("创建发送任务成功")); 200 | } 201 | 202 | /** 203 | * 测试查询消息状态 204 | */ 205 | @Test 206 | public void testQueryMessageStatus() { 207 | // 先发送一条消息获取消息ID 208 | Message message = new Message(); 209 | message.setContentType(Message.CONTENT_TYPE_TEXT); 210 | message.setContent("这是一条用于测试查询状态的消息"); 211 | message.setUid(TEST_UID); 212 | 213 | Result> sendResult = WxPusher.getDefaultWxPusher().send(message); 214 | if (sendResult.isSuccess() && sendResult.getData() != null && !sendResult.getData().isEmpty()) { 215 | Long messageId = sendResult.getData().get(0).getMessageId(); 216 | if (messageId != null) { 217 | Result result = WxPusher.getDefaultWxPusher().queryMessageStatus(messageId); 218 | System.out.println("查询结果:" + result.isSuccess() + ", 消息状态:" + result.getData()); 219 | 220 | // 添加断言 221 | Assert.assertTrue("查询消息状态应该成功", result.isSuccess()); 222 | Assert.assertNotNull("状态码不应为空", result.getData()); 223 | Assert.assertEquals("消息状态应为1", 1, result.getData().intValue()); 224 | } else { 225 | System.out.println("消息ID为空,无法查询状态"); 226 | Assert.fail("消息ID不应为空"); 227 | } 228 | } else { 229 | System.out.println("发送消息失败,无法测试查询状态"); 230 | Assert.fail("发送消息应该成功"); 231 | } 232 | } 233 | 234 | /** 235 | * 测试删除消息 236 | */ 237 | @Test 238 | public void testDeleteMessage() { 239 | // 先发送一条消息获取消息ID 240 | Message message = new Message(); 241 | message.setContentType(Message.CONTENT_TYPE_TEXT); 242 | message.setContent("这是一条用于测试删除的消息"); 243 | message.setUid(TEST_UID); 244 | 245 | Result> sendResult = WxPusher.getDefaultWxPusher().send(message); 246 | if (sendResult.isSuccess() && sendResult.getData() != null && !sendResult.getData().isEmpty()) { 247 | Long messageId = sendResult.getData().get(0).getMessageId(); 248 | if (messageId != null) { 249 | Result result = WxPusher.getDefaultWxPusher().deleteMessage(messageId); 250 | System.out.println("删除结果:" + result.isSuccess() + ", 状态:" + result.getData()); 251 | 252 | // 根据实际情况,可能删除失败是正常的,因为消息已经推送给用户 253 | // 这里我们只检查API调用是否返回了结果,而不检查删除是否成功 254 | Assert.assertNotNull("应该返回结果", result); 255 | } else { 256 | System.out.println("消息ID为空,无法删除消息"); 257 | Assert.fail("消息ID不应为空"); 258 | } 259 | } else { 260 | System.out.println("发送消息失败,无法测试删除消息"); 261 | Assert.fail("发送消息应该成功"); 262 | } 263 | } 264 | 265 | /** 266 | * 测试创建带参数的临时二维码 267 | */ 268 | @Test 269 | public void testCreateTempQrcode() { 270 | CreateQrcodeReq req = new CreateQrcodeReq(); 271 | req.setExtra("test_extra_data"); 272 | req.setValidTime(1800); // 设置有效期为30分钟 273 | 274 | Result result = WxPusher.getDefaultWxPusher().createAppTempQrcode(req); 275 | System.out.println("创建二维码结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 276 | if (result.isSuccess() && result.getData() != null) { 277 | CreateQrcodeResp resp = result.getData(); 278 | System.out.println("二维码Code: " + resp.getCode()); 279 | System.out.println("二维码URL: " + resp.getUrl()); 280 | System.out.println("二维码短链接: " + resp.getShortUrl()); 281 | System.out.println("过期时间: " + resp.getExpires()); 282 | System.out.println("附加数据: " + resp.getExtra()); 283 | } 284 | 285 | // 添加断言 286 | Assert.assertTrue("创建二维码应该成功", result.isSuccess()); 287 | Assert.assertEquals("处理成功", result.getMsg()); 288 | Assert.assertNotNull("返回数据不应为空", result.getData()); 289 | 290 | CreateQrcodeResp resp = result.getData(); 291 | Assert.assertNotNull("二维码Code不应为空", resp.getCode()); 292 | Assert.assertNotNull("二维码URL不应为空", resp.getUrl()); 293 | Assert.assertTrue("二维码URL应包含正确格式", resp.getUrl().contains(".jpg")); 294 | Assert.assertEquals("附加数据应匹配", "test_extra_data", resp.getExtra()); 295 | Assert.assertTrue("过期时间应大于当前时间", resp.getExpires() > System.currentTimeMillis()); 296 | } 297 | 298 | /** 299 | * 测试查询用户列表 300 | */ 301 | @Test 302 | public void testQueryWxUser() { 303 | Result> result = WxPusher.getDefaultWxPusher().queryWxUserV2(1, 10, null, false, UserType.APP); 304 | System.out.println("查询用户结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 305 | if (result.isSuccess() && result.getData() != null) { 306 | Page page = result.getData(); 307 | System.out.println("总用户数: " + page.getTotal()); 308 | System.out.println("当前页: " + page.getPage()); 309 | System.out.println("页大小: " + page.getPageSize()); 310 | 311 | if (page.getRecords() != null) { 312 | for (WxUser wxUser : page.getRecords()) { 313 | System.out.println("用户UID: " + wxUser.getUid()); 314 | System.out.println("用户ID: " + wxUser.getId()); 315 | System.out.println("应用/主题ID: " + wxUser.getAppOrTopicId()); 316 | System.out.println("关注类型: " + wxUser.getType()); 317 | System.out.println("用户是否启用: " + wxUser.isEnable()); 318 | System.out.println("用户是否拉黑: " + wxUser.isReject()); 319 | System.out.println("创建时间: " + wxUser.getCreateTime()); 320 | System.out.println("---------------------"); 321 | } 322 | } 323 | } 324 | 325 | // 添加断言 326 | Assert.assertTrue("查询用户应该成功", result.isSuccess()); 327 | Assert.assertEquals("处理成功", result.getMsg()); 328 | Assert.assertNotNull("返回数据不应为空", result.getData()); 329 | 330 | Page page = result.getData(); 331 | Assert.assertNotNull("用户列表不应为空", page.getRecords()); 332 | Assert.assertFalse("用户列表不应为空", page.getRecords().isEmpty()); 333 | Assert.assertEquals("当前页应为1", Integer.valueOf(1), page.getPage()); 334 | Assert.assertEquals("页大小应为10", Integer.valueOf(10), page.getPageSize()); 335 | Assert.assertTrue("总用户数应大于0", page.getTotal() > 0); 336 | 337 | // 检查是否包含测试用户 338 | boolean containsTestUser = false; 339 | for (WxUser user : page.getRecords()) { 340 | if (TEST_UID.equals(user.getUid())) { 341 | containsTestUser = true; 342 | Assert.assertEquals("应用ID应匹配", Long.valueOf(141), user.getAppOrTopicId()); 343 | Assert.assertEquals("关注类型应为APP", 0, user.getType()); 344 | break; 345 | } 346 | } 347 | Assert.assertTrue("用户列表应包含测试用户", containsTestUser); 348 | } 349 | 350 | /** 351 | * 测试查询指定UID用户信息 352 | */ 353 | @Test 354 | public void testQuerySpecificUser() { 355 | Result> result = WxPusher.getDefaultWxPusher().queryWxUserV2(1, 10, TEST_UID, false, UserType.APP); 356 | System.out.println("查询指定用户结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 357 | if (result.isSuccess() && result.getData() != null) { 358 | Page page = result.getData(); 359 | if (page.getRecords() != null && !page.getRecords().isEmpty()) { 360 | WxUser wxUser = page.getRecords().get(0); 361 | System.out.println("用户UID: " + wxUser.getUid()); 362 | System.out.println("用户ID: " + wxUser.getId()); 363 | System.out.println("应用/主题ID: " + wxUser.getAppOrTopicId()); 364 | System.out.println("关注类型: " + wxUser.getType()); 365 | System.out.println("用户是否启用: " + wxUser.isEnable()); 366 | System.out.println("用户是否拉黑: " + wxUser.isReject()); 367 | System.out.println("创建时间: " + wxUser.getCreateTime()); 368 | } else { 369 | System.out.println("未找到指定UID的用户"); 370 | } 371 | } 372 | 373 | // 添加断言 374 | Assert.assertTrue("查询指定用户应该成功", result.isSuccess()); 375 | Assert.assertEquals("处理成功", result.getMsg()); 376 | Assert.assertNotNull("返回数据不应为空", result.getData()); 377 | 378 | Page page = result.getData(); 379 | Assert.assertNotNull("用户记录不应为空", page.getRecords()); 380 | Assert.assertFalse("用户记录不应为空", page.getRecords().isEmpty()); 381 | 382 | WxUser wxUser = page.getRecords().get(0); 383 | Assert.assertEquals("用户UID应匹配", TEST_UID, wxUser.getUid()); 384 | Assert.assertEquals("应用ID应匹配", Long.valueOf(141), wxUser.getAppOrTopicId()); 385 | Assert.assertEquals("关注类型应为APP", 0, wxUser.getType()); 386 | } 387 | 388 | /** 389 | * 测试查询扫码用户UID 390 | * 注意:此方法需要用户扫描二维码才能查询到结果 391 | */ 392 | @Test 393 | public void testQueryScanUID() { 394 | // 先创建一个带参数的临时二维码 395 | CreateQrcodeReq req = new CreateQrcodeReq(); 396 | req.setExtra("test_scan_uid"); 397 | req.setValidTime(300); // 设置有效期为5分钟 398 | 399 | Result createResult = WxPusher.getDefaultWxPusher().createAppTempQrcode(req); 400 | if (createResult.isSuccess() && createResult.getData() != null) { 401 | String code = createResult.getData().getCode(); 402 | System.out.println("请扫描这个二维码URL: " + createResult.getData().getUrl()); 403 | System.out.println("等待用户扫描..."); 404 | 405 | // 注意:实际使用时,应该使用轮询方式,此处仅为示例 406 | Result result = WxPusher.getDefaultWxPusher().queryScanUID(code); 407 | System.out.println("查询扫码用户结果:" + result.isSuccess() + ", 消息:" + result.getMsg()); 408 | if (result.isSuccess() && result.getData() != null) { 409 | System.out.println("扫码用户UID: " + result.getData()); 410 | } 411 | 412 | // 添加断言 - 注意这里可能失败是正常的,因为没有用户扫码 413 | Assert.assertNotNull("查询结果不应为空", result); 414 | } else { 415 | System.out.println("创建二维码失败,无法测试查询扫码用户"); 416 | Assert.fail("创建二维码应该成功"); 417 | } 418 | } 419 | 420 | /** 421 | * 测试删除用户 422 | * 注意:此方法会真实删除用户,谨慎使用 423 | */ 424 | @Test 425 | public void testDeleteUser() { 426 | // 先查询出用户ID 427 | Result> queryResult = WxPusher.getDefaultWxPusher().queryWxUserV2(1, 10, TEST_UID, false, UserType.APP); 428 | if (queryResult.isSuccess() && queryResult.getData() != null 429 | && queryResult.getData().getRecords() != null 430 | && !queryResult.getData().getRecords().isEmpty()) { 431 | 432 | Long userId = queryResult.getData().getRecords().get(0).getId(); 433 | System.out.println("找到用户ID: " + userId); 434 | 435 | // 谨慎执行以下代码,会真实删除用户关注 436 | // Result result = wxPusher.deleteUser(userId); 437 | // System.out.println("删除用户结果:" + result.isSuccess() + ", 状态:" + result.getData()); 438 | 439 | // 添加断言 440 | Assert.assertTrue("查询用户应该成功", queryResult.isSuccess()); 441 | Assert.assertNotNull("用户ID不应为空", userId); 442 | Assert.assertEquals("用户ID应匹配预期", Long.valueOf(28134), userId); 443 | 444 | System.out.println("为避免真实删除用户,该测试方法已被注释"); 445 | } else { 446 | System.out.println("未找到指定UID的用户,无法测试删除"); 447 | Assert.fail("应该能找到测试用户"); 448 | } 449 | } 450 | 451 | /** 452 | * 测试拉黑用户 453 | * 注意:此方法会真实拉黑用户,谨慎使用 454 | */ 455 | @Test 456 | public void testRejectUser() { 457 | // 先查询出用户ID 458 | Result> queryResult = WxPusher.getDefaultWxPusher().queryWxUserV2(1, 10, TEST_UID, false, UserType.APP); 459 | if (queryResult.isSuccess() && queryResult.getData() != null 460 | && queryResult.getData().getRecords() != null 461 | && !queryResult.getData().getRecords().isEmpty()) { 462 | 463 | Long userId = queryResult.getData().getRecords().get(0).getId(); 464 | System.out.println("找到用户ID: " + userId); 465 | 466 | // 拉黑和取消拉黑用户的完整测试 467 | Result result = WxPusher.getDefaultWxPusher().rejectUser(userId, true); 468 | System.out.println("拉黑用户结果:" + result.isSuccess() + ", 状态:" + result.getData()); 469 | 470 | // 添加断言 471 | Assert.assertTrue("拉黑用户应该成功", result.isSuccess()); 472 | Assert.assertTrue("操作状态应为true", result.getData()); 473 | 474 | Result result2 = WxPusher.getDefaultWxPusher().rejectUser(userId, false); 475 | System.out.println("取消拉黑用户结果:" + result2.isSuccess() + ", 状态:" + result2.getData()); 476 | 477 | // 添加断言 478 | Assert.assertTrue("取消拉黑用户应该成功", result2.isSuccess()); 479 | Assert.assertTrue("操作状态应为true", result2.getData()); 480 | } else { 481 | System.out.println("未找到指定UID的用户,无法测试拉黑"); 482 | Assert.fail("应该能找到测试用户"); 483 | } 484 | } 485 | 486 | } 487 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'org.springframework.boot' 3 | apply plugin: 'io.spring.dependency-management' 4 | 5 | //还有很多开发者用1.8,编译成低版本的,方便兼容 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | } 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | //lombok 17 | compileOnly 'org.projectlombok:lombok:1.18.8' 18 | annotationProcessor 'org.projectlombok:lombok:1.18.8' 19 | //spring boot 20 | implementation 'org.springframework.boot:spring-boot-starter-web:2.1.4.RELEASE' 21 | 22 | //freemarker html模版渲染引擎 23 | implementation 'org.springframework.boot:spring-boot-starter-freemarker:2.1.4.RELEASE' 24 | 25 | implementation 'com.alibaba.fastjson2:fastjson2:2.0.56' 26 | 27 | //实际开发中,请直接通过jar包依赖,在本demo中,直接工程依赖的sdk源码 28 | implementation project(":client-sdk") 29 | 30 | } 31 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/AppWebMvcConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.smjcco.wxpusher.client.sdk.demo.result.*; 5 | import com.smjcco.wxpusher.demo.result.*; 6 | 7 | import com.smjcco.wxpusher.client.sdk.demo.utils.ThrowableUtils; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.validation.BindException; 10 | import org.springframework.validation.ObjectError; 11 | import org.springframework.web.HttpRequestMethodNotSupportedException; 12 | import org.springframework.web.servlet.HandlerExceptionResolver; 13 | import org.springframework.web.servlet.HandlerInterceptor; 14 | import org.springframework.web.servlet.ModelAndView; 15 | import org.springframework.web.servlet.NoHandlerFoundException; 16 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 17 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 18 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 19 | 20 | import java.io.IOException; 21 | import java.util.List; 22 | 23 | import javax.servlet.ServletException; 24 | import javax.servlet.http.HttpServletRequest; 25 | import javax.servlet.http.HttpServletResponse; 26 | 27 | import lombok.extern.slf4j.Slf4j; 28 | 29 | /** 30 | * 应用程序配置,包括配置拦截器,异常等; 31 | */ 32 | @Configuration 33 | @Slf4j 34 | public class AppWebMvcConfigurer implements WebMvcConfigurer { 35 | 36 | @Override 37 | public void addInterceptors(InterceptorRegistry registry) { 38 | //打印请求日志 39 | registry.addInterceptor(new HandlerInterceptor() { 40 | @Override 41 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { 42 | log.info(getIpAddress(request) + "-" + request.getMethod() + "[" + request.getRequestURI() + "]"); 43 | return true; 44 | } 45 | }); 46 | } 47 | 48 | @Override 49 | public void configureHandlerExceptionResolvers(List resolvers) { 50 | resolvers.add((request, response, handler, ex) -> { 51 | Throwable throwable = ThrowableUtils.getRootThrowable(ex); 52 | Result result; 53 | if (throwable instanceof BizException) { 54 | //业务异常,不需要打印堆栈 55 | result = new Result(((BizException) throwable).getResultCode(), throwable.getMessage()); 56 | responseResult(response, result); 57 | log.warn("{}-{}", request.getRequestURI(), throwable.getMessage()); 58 | } else if (throwable instanceof AppException) { 59 | //应用异常 60 | result = new Result(((AppException) throwable).getResultCode(), "服务器出现应用异常:" + throwable.getMessage()); 61 | responseResult(response, result); 62 | log.error("{}-{}", request, throwable.getMessage()); 63 | } else if (throwable instanceof NoHandlerFoundException || throwable instanceof HttpRequestMethodNotSupportedException) { 64 | //不是服务器的异常,不需要打印堆栈 65 | result = new Result(ResultCode.NOT_FOUND, "接口[(" + request.getMethod() + ")" + request.getRequestURI() + "]不存在"); 66 | responseResult(response, result); 67 | log.warn("{}-{}", result, throwable.getMessage()); 68 | } else if (throwable instanceof BindException) { 69 | //参数不合法 70 | List errors = ((BindException) throwable).getAllErrors(); 71 | if (!errors.isEmpty()) { 72 | result = new Result(ResultCode.BIZ_FAIL, errors.get(0).getDefaultMessage()); 73 | } else { 74 | result = new Result(ResultCode.BIZ_FAIL, "数据验证错误"); 75 | } 76 | responseResult(response, result); 77 | log.warn("参数错误", throwable); 78 | } else if (throwable instanceof ServletException) { 79 | result = new Result(ResultCode.INTERNAL_SERVER_ERROR, "服务器错误:" + throwable.getMessage()); 80 | responseResult(response, result); 81 | log.error(result.toString(), throwable); 82 | }else if (throwable instanceof HttpException){ 83 | //返回原始的http status code 84 | result = new Result(ResultCode.INTERNAL_SERVER_ERROR, "服务器错误:" + throwable.getMessage()); 85 | responseResult(response, result,((HttpException) throwable).getHttpStatus()); 86 | } else { 87 | //其他错误 88 | String message = String.format("接口 [%s] 出现异常", request.getRequestURI()); 89 | result = new Result(ResultCode.INTERNAL_SERVER_ERROR, message); 90 | responseResult(response, result); 91 | log.error(result.toString(), throwable); 92 | } 93 | return new ModelAndView(); 94 | }); 95 | } 96 | 97 | @Override 98 | public void addCorsMappings(CorsRegistry registry) { 99 | //配置跨域请求 100 | registry.addMapping("/**") 101 | .allowedOrigins("https://wxpusher.zjiecode.com") 102 | .allowedOrigins("*") 103 | .allowedHeaders("*") 104 | .allowCredentials(false) 105 | .allowedMethods("*"); 106 | } 107 | 108 | private void responseResult(HttpServletResponse response, Result result) { 109 | responseResult(response, result,200); 110 | } 111 | /** 112 | * 遇到错误,拦截以后输出响应到客户端 113 | */ 114 | private void responseResult(HttpServletResponse response, Result result,int httpCode) { 115 | response.setCharacterEncoding("UTF-8"); 116 | response.setHeader("Content-type", "application/json;charset=UTF-8"); 117 | response.setHeader("Access-Control-Allow-Credentials", "true"); 118 | response.setHeader("Access-Control-Allow-Origin", "*"); 119 | response.setHeader("Access-Control-Allow-Headers", "*"); 120 | response.setHeader("Access-Control-Allow-Methods", "*"); 121 | response.setStatus(httpCode); 122 | try { 123 | ObjectMapper mapper = new ObjectMapper(); 124 | response.getWriter().write(mapper.writeValueAsString(result)); 125 | } catch (IOException ex) { 126 | log.error(ex.getMessage()); 127 | } 128 | } 129 | 130 | /** 131 | * 获取客户端ip 132 | */ 133 | private String getIpAddress(HttpServletRequest request) { 134 | String ip = request.getHeader("x-forwarded-for"); 135 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 136 | ip = request.getHeader("Proxy-Client-IP"); 137 | } 138 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 139 | ip = request.getHeader("WL-Proxy-Client-IP"); 140 | } 141 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 142 | ip = request.getHeader("HTTP_CLIENT_IP"); 143 | } 144 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 145 | ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 146 | } 147 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 148 | ip = request.getRemoteAddr(); 149 | } 150 | // 如果是多级代理,那么取第一个ip为客户端ip 151 | if (ip != null && ip.indexOf(",") != -1) { 152 | ip = ip.substring(0, ip.indexOf(",")).trim(); 153 | } 154 | return ip; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/CommonService.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo; 2 | 3 | import com.smjcco.wxpusher.client.sdk.WxPusher; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.annotation.PostConstruct; 8 | 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Component 12 | @Slf4j 13 | public class CommonService { 14 | @Value("${spring.profiles.active}") 15 | String activeEnv; 16 | @Value("${wxpusher.biz.apptoken}") 17 | private String appToken; 18 | 19 | @PostConstruct 20 | private void init() { 21 | log.info("运行环境:" + activeEnv); 22 | //初始化默认的WxPusher 23 | WxPusher.initDefaultWxPusher(appToken); 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/JavaWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo; 2 | 3 | 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | public class JavaWebApplication { 9 | 10 | public static void main(String[] args) { 11 | SpringApplication.run(JavaWebApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/controller/CallBackController.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.controller; 2 | 3 | import com.alibaba.fastjson2.JSON; 4 | import com.smjcco.wxpusher.client.sdk.demo.data.ScanQrocodeDataRepo; 5 | import com.smjcco.wxpusher.client.sdk.demo.data.UpCommandDataRepo; 6 | import com.smjcco.wxpusher.client.sdk.WxPusher; 7 | import com.smjcco.wxpusher.client.sdk.bean.Message; 8 | import com.smjcco.wxpusher.client.sdk.bean.callback.AppSubscribeBean; 9 | import com.smjcco.wxpusher.client.sdk.bean.callback.BaseCallBackReq; 10 | import com.smjcco.wxpusher.client.sdk.bean.callback.UpCommandBean; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.util.StringUtils; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | import org.springframework.web.bind.annotation.RequestBody; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | /** 20 | * 说明:接收来自WxPusher服务的回调 21 | * 当用户扫码关注的时候会触发 22 | * 作者:zjiecode 23 | * 时间:2019-10-05 24 | */ 25 | @RestController 26 | @RequestMapping("/demo") 27 | @Slf4j 28 | public class CallBackController { 29 | @Value("${wxpusher.biz.apptoken}") 30 | private String appToken; 31 | 32 | @PostMapping("/callback") 33 | public String callback(@RequestBody BaseCallBackReq callBackReq) { 34 | log.info("收到wxpusher回调:{}", JSON.toJSONString(callBackReq)); 35 | if (BaseCallBackReq.ACTION_APP_SUBSCRIBE.equalsIgnoreCase(callBackReq.getAction())) { 36 | AppSubscribeBean appSubscribeBean = JSON.parseObject(JSON.toJSONString(callBackReq.getData()), AppSubscribeBean.class); 37 | if (!StringUtils.isEmpty(appSubscribeBean.getExtra())) { 38 | //这里的extra 就是创建二维码的时候,携带的数据,也就是qrcodeId, 39 | ScanQrocodeDataRepo.put(appSubscribeBean.getExtra(), appSubscribeBean); 40 | log.info("存储回调数据:{}", JSON.toJSONString(appSubscribeBean)); 41 | //扫码以后,发送一条消息给用户 42 | Message message = new Message(); 43 | message.setContent("扫描成功,你可以使用demo演示程序发送消息"); 44 | message.setContentType(Message.CONTENT_TYPE_TEXT); 45 | message.setUid(appSubscribeBean.getUid()); 46 | WxPusher.getDefaultWxPusher().send(message); 47 | } else { 48 | //无参数二维码(默认二维码),不需要发送提醒,会自动发送后台设置的 49 | } 50 | return ""; 51 | } 52 | //上行命令消息 53 | if (BaseCallBackReq.ACTION_SEND_UP_CMD.equalsIgnoreCase(callBackReq.getAction())) { 54 | UpCommandBean upCommandBean = JSON.parseObject(JSON.toJSONString(callBackReq.getData()), UpCommandBean.class); 55 | UpCommandDataRepo.add(upCommandBean); 56 | //收到上行消息后,发送一条消息给用户 57 | Message message = new Message(); 58 | message.setContent("Demo程序收到上行消息:" + upCommandBean.getContent()); 59 | message.setContentType(Message.CONTENT_TYPE_TEXT); 60 | message.setUid(upCommandBean.getUid()); 61 | message.setAppToken(appToken); 62 | WxPusher.getDefaultWxPusher().send(message); 63 | return ""; 64 | } 65 | 66 | //直接返回 空串 即可 67 | return ""; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/controller/CommonController.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.controller; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | /** 8 | * 说明: 9 | * 作者:zjiecode 10 | * 时间:2019-10-05 11 | */ 12 | @RestController 13 | @RequestMapping("/demo") 14 | public class CommonController { 15 | /** 16 | * 服务状态判断,返回OK表示服务状态OK 17 | */ 18 | @GetMapping("/alive") 19 | public String alive() { 20 | return "OK"; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/controller/DisplayController.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.controller; 2 | 3 | import com.smjcco.wxpusher.client.sdk.demo.data.ScanQrocodeDataRepo; 4 | import com.smjcco.wxpusher.client.sdk.demo.data.UpCommandDataRepo; 5 | import com.smjcco.wxpusher.client.sdk.demo.result.BizException; 6 | import com.smjcco.wxpusher.client.sdk.demo.utils.RandomUtil; 7 | import com.smjcco.wxpusher.client.sdk.WxPusher; 8 | import com.smjcco.wxpusher.client.sdk.bean.CreateQrcodeReq; 9 | import com.smjcco.wxpusher.client.sdk.bean.CreateQrcodeResp; 10 | import com.smjcco.wxpusher.client.sdk.bean.Result; 11 | import com.smjcco.wxpusher.client.sdk.bean.ResultCode; 12 | import com.smjcco.wxpusher.client.sdk.bean.callback.AppSubscribeBean; 13 | import com.smjcco.wxpusher.client.sdk.bean.callback.UpCommandBean; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.util.StringUtils; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RestController; 20 | import org.springframework.web.servlet.ModelAndView; 21 | 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | /** 27 | * 说明:演示发送消息 28 | * 作者:zjiecode 29 | * 时间:2019-10-05 30 | */ 31 | @RestController 32 | @RequestMapping("/demo") 33 | @Slf4j 34 | public class DisplayController { 35 | 36 | /** 37 | * 发送普通文本 38 | */ 39 | @GetMapping("") 40 | public ModelAndView display() { 41 | //生成一个随机字符串来当作二维码标志,实际使用的时候,可以使用用户ID,避免重复。最大64位 42 | String qrcodeId = RandomUtil.getRandomStr(32); 43 | //创建一个参数二维码 44 | CreateQrcodeReq createQrcodeReq = new CreateQrcodeReq(); 45 | createQrcodeReq.setValidTime(3600);//二维码有效时间 46 | createQrcodeReq.setExtra(qrcodeId); 47 | Result tempQrcode = WxPusher.getDefaultWxPusher().createAppTempQrcode(createQrcodeReq); 48 | if (!tempQrcode.isSuccess()) { 49 | throw new BizException(tempQrcode.getMsg()); 50 | } 51 | Map data = new HashMap<>(); 52 | data.put("qrcodeUrl", tempQrcode.getData().getUrl()); 53 | data.put("qrcodeId", qrcodeId); 54 | return new ModelAndView("display", data); 55 | } 56 | 57 | @GetMapping("/getuid/{qrcodeId}") 58 | public Result> getUidByQrcodeId(@PathVariable("qrcodeId") String qrcodeId) { 59 | if (StringUtils.isEmpty(qrcodeId)) { 60 | return new Result<>(ResultCode.BIZ_FAIL, "二维码错误"); 61 | } 62 | AppSubscribeBean appSubscribeBean = ScanQrocodeDataRepo.get(qrcodeId); 63 | List commandBeanList = UpCommandDataRepo.getData(); 64 | Map data = new HashMap<>(); 65 | data.put("scan", appSubscribeBean); 66 | data.put("upCommand", commandBeanList); 67 | Result> result = new Result<>(ResultCode.SUCCESS, "处理成功"); 68 | result.setData(data); 69 | ScanQrocodeDataRepo.remove(qrcodeId); 70 | return result; 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/controller/HealthCheckController.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.controller; 2 | 3 | import com.alibaba.fastjson2.JSON; 4 | import com.smjcco.wxpusher.client.sdk.demo.result.HttpException; 5 | import com.smjcco.wxpusher.client.sdk.demo.result.Result; 6 | import com.smjcco.wxpusher.client.sdk.demo.result.ResultCode; 7 | import com.smjcco.wxpusher.client.sdk.demo.utils.DateUtil; 8 | import com.smjcco.wxpusher.client.sdk.WxPusher; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | @Slf4j 18 | @RestController 19 | @RequestMapping("/status") 20 | public class HealthCheckController { 21 | private final long startTime = System.currentTimeMillis(); 22 | 23 | /** 24 | * 返回服务状态 25 | */ 26 | @GetMapping("/health") 27 | public Result alive() { 28 | long now = System.currentTimeMillis(); 29 | Map data = new HashMap<>(); 30 | data.put("系统启动时间", DateUtil.formatTimeLong(now - startTime)); 31 | com.smjcco.wxpusher.client.sdk.bean.Result result = WxPusher.getDefaultWxPusher().queryMessageStatus(1L); 32 | boolean connectWxPusher = (result.getCode() == ResultCode.SUCCESS.getCode()) || 33 | (result.getCode() == ResultCode.BIZ_FAIL.getCode()); 34 | data.put("到WxPuhser的链接状态", connectWxPusher); 35 | data.put("响应时间", DateUtil.formatTimeLong(System.currentTimeMillis() - now)); 36 | if (connectWxPusher) { 37 | return Result.getSuccess(data); 38 | } 39 | throw new HttpException(JSON.toJSONString(data), 500); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/controller/SendController.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.controller; 2 | 3 | import com.smjcco.wxpusher.client.sdk.WxPusher; 4 | import com.smjcco.wxpusher.client.sdk.bean.Message; 5 | import com.smjcco.wxpusher.client.sdk.bean.Result; 6 | 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | /** 14 | * 说明:发送消息的演示 15 | * 作者:zjiecode 16 | * 时间:2019-10-05 17 | */ 18 | @RestController 19 | @RequestMapping("/demo/send") 20 | public class SendController { 21 | 22 | @Value("${wxpusher.biz.apptoken}") 23 | private String appToken; 24 | 25 | private String sign = "本消息来自WxPusher演示程序 http://wxpusher.zjiecode.com/demo"; 26 | 27 | /** 28 | * 发送普通文本 29 | */ 30 | @GetMapping("/text/{uid}") 31 | public Result sendText(@PathVariable("uid") String uid) { 32 | Message message = new Message(); 33 | message.setContentType(Message.CONTENT_TYPE_TEXT); 34 | message.setUid(uid); 35 | message.setAppToken(appToken); 36 | message.setContent("WxPusher演示消息,这里是一条普通的演示消息\n\n\n" + sign); 37 | return WxPusher.getDefaultWxPusher().send(message); 38 | } 39 | 40 | /** 41 | * 发送html文本 42 | */ 43 | @GetMapping("/html/{uid}") 44 | public Result sendHtml(@PathVariable("uid") String uid) { 45 | Message message = new Message(); 46 | message.setContentType(Message.CONTENT_TYPE_HTML); 47 | message.setUid(uid); 48 | message.setAppToken(appToken); 49 | message.setContent("WxPusher演示消息,这是一个html消息
标题:这是标题
状态:成功" 50 | + "


" + sign); 51 | return WxPusher.getDefaultWxPusher().send(message); 52 | } 53 | 54 | 55 | /** 56 | * 发送markdown 57 | */ 58 | @GetMapping("/markdown/{uid}") 59 | public Result sendMarkdown(@PathVariable("uid") String uid) { 60 | Message message = new Message(); 61 | message.setContentType(Message.CONTENT_TYPE_MD); 62 | message.setUid(uid); 63 | message.setAppToken(appToken); 64 | message.setContent("WxPusher演示消息,这是一个Markdown消息\n# 目录\n- 什么是Wxpusher\n- Wxpusher可好用了\n## 发送状态:_成功_" 65 | + "\n\n


" + sign); 66 | return WxPusher.getDefaultWxPusher().send(message); 67 | } 68 | 69 | /** 70 | * 发送markdown 71 | */ 72 | @GetMapping("/custom/{uid}") 73 | public Result sendCustom(@PathVariable("uid") String uid, String content) { 74 | Message message = new Message(); 75 | message.setContentType(Message.CONTENT_TYPE_TEXT); 76 | message.setUid(uid); 77 | message.setAppToken(appToken); 78 | message.setContent(content + "\n\n\n" + sign); 79 | return WxPusher.getDefaultWxPusher().send(message); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/cron/ClearTimeoutDataScheduleTask.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.cron; 2 | 3 | import com.smjcco.wxpusher.client.sdk.demo.data.ScanQrocodeDataRepo; 4 | import com.smjcco.wxpusher.client.sdk.demo.data.UpCommandDataRepo; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.scheduling.annotation.EnableScheduling; 8 | import org.springframework.scheduling.annotation.Scheduled; 9 | 10 | /** 11 | * 说明:删除登陆的时候的二维码 12 | * 作者:zhangjie33 13 | * 时间:2020/5/5 14 | */ 15 | @Slf4j 16 | @Configuration 17 | @EnableScheduling 18 | public class ClearTimeoutDataScheduleTask { 19 | 20 | 21 | //每天凌晨3点运行 22 | @Scheduled(cron = "0 */10 * * * ?") 23 | private void clearTimeOutData() { 24 | log.info("清理数据-开始"); 25 | ScanQrocodeDataRepo.clearTimeout(); 26 | UpCommandDataRepo.clearTimeout(); 27 | log.info("清理数据-完成"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/data/ScanQrocodeDataRepo.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.data; 2 | 3 | import java.util.*; 4 | 5 | import com.smjcco.wxpusher.client.sdk.bean.callback.AppSubscribeBean; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /** 9 | * 说明:缓存一下数据,demo就不用数据库了,正式产品的时候,可以使用db存储 10 | * 作者:zjiecode 11 | * 时间:2019-10-05 12 | */ 13 | @Slf4j 14 | public class ScanQrocodeDataRepo { 15 | 16 | private static final long TimeOut = 1000 * 60 * 60;//1小时 17 | 18 | private static final Map data = new HashMap<>(); 19 | 20 | public static synchronized void put(String key, AppSubscribeBean value) { 21 | data.put(key, value); 22 | } 23 | 24 | public static synchronized AppSubscribeBean get(String key) { 25 | return data.get(key); 26 | } 27 | 28 | public static synchronized void remove(String key) { 29 | data.remove(key); 30 | } 31 | 32 | /** 33 | * 删除超时的数据 34 | */ 35 | public static synchronized void clearTimeout() { 36 | Set> entries = data.entrySet(); 37 | Iterator> iterator = entries.iterator(); 38 | while (iterator.hasNext()) { 39 | Map.Entry entry = iterator.next(); 40 | if (System.currentTimeMillis() - entry.getValue().getTime() > TimeOut) { 41 | data.remove(entry.getKey()); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/data/UpCommandDataRepo.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.data; 2 | 3 | import com.smjcco.wxpusher.client.sdk.bean.callback.UpCommandBean; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * 说明:上行的用户数据 11 | * 作者:zjiecode 12 | * 时间:2020-10-17 13 | */ 14 | @Slf4j 15 | public class UpCommandDataRepo { 16 | 17 | private static final long TimeOut = 1000 * 60 * 10;//1小时 18 | 19 | private static final List data = new ArrayList<>(); 20 | 21 | public static synchronized void add(UpCommandBean commandBean) { 22 | data.add(commandBean); 23 | } 24 | 25 | public static synchronized List getData() { 26 | return data; 27 | } 28 | 29 | /** 30 | * 删除超时的数据 31 | */ 32 | public static synchronized void clearTimeout() { 33 | data.removeIf(commandBean -> (System.currentTimeMillis() - commandBean.getTime()) > TimeOut); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/result/AppException.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.result; 2 | 3 | 4 | 5 | /** 6 | * 应用程序异常异常,比如bug之类的,需要关注 7 | */ 8 | public class AppException extends RuntimeException { 9 | private ResultCode resultCode;//错误代码 10 | 11 | public AppException() { 12 | } 13 | 14 | public AppException(String message) { 15 | super(message); 16 | this.resultCode = ResultCode.INTERNAL_SERVER_ERROR; 17 | } 18 | 19 | public ResultCode getResultCode() { 20 | return resultCode; 21 | } 22 | 23 | public void setCode(ResultCode code) { 24 | this.resultCode = code; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/result/BizException.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.result; 2 | 3 | /** 4 | * 业务异常,一般不是应用的错误,比如:用户登陆没有输入账号 5 | */ 6 | public class BizException extends RuntimeException { 7 | private ResultCode resultCode;//错误代码 8 | 9 | public BizException() { 10 | } 11 | 12 | public BizException(String message) { 13 | super(message); 14 | this.resultCode = ResultCode.BIZ_FAIL; 15 | } 16 | 17 | public BizException(String message, ResultCode resultCode) { 18 | super(message); 19 | this.resultCode = resultCode; 20 | } 21 | 22 | public ResultCode getResultCode() { 23 | return resultCode; 24 | } 25 | 26 | public void setCode(ResultCode code) { 27 | this.resultCode = code; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/result/HttpException.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.result; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 返回http错误,对外暴露http code 7 | * 主要用于网关健康检查等 8 | */ 9 | @Getter 10 | public class HttpException extends RuntimeException { 11 | private int httpStatus;//http返回密 12 | 13 | public HttpException(String message, int httpStatus) { 14 | super(message); 15 | this.httpStatus = httpStatus; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/result/Result.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.result; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | /** 7 | * 返回结果数据结构 8 | */ 9 | @Setter 10 | @Getter 11 | public class Result { 12 | private Integer code; 13 | private String msg; 14 | private T data; 15 | 16 | public Result(ResultCode code, String msg, T data) { 17 | this.code = code.getCode(); 18 | this.msg = msg; 19 | this.data = data; 20 | } 21 | 22 | public Result(ResultCode code, String msg) { 23 | this.code = code.getCode(); 24 | this.msg = msg; 25 | } 26 | public static Result getSuccess(T data) { 27 | return new Result<>(ResultCode.SUCCESS, "处理成功", data); 28 | } 29 | 30 | public static Result getSuccess() { 31 | return new Result<>(ResultCode.SUCCESS, "处理成功"); 32 | } 33 | 34 | public static Result getBizFail(String msg) { 35 | return new Result<>(ResultCode.BIZ_FAIL, msg); 36 | } 37 | 38 | public boolean isSuccess() { 39 | return code == ResultCode.SUCCESS.getCode(); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "[" + getCode() + "]" + getMsg(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/result/ResultCode.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.result; 2 | 3 | /** 4 | * 返回编码,参考http语义 5 | */ 6 | public enum ResultCode { 7 | SUCCESS(1000),//成功 8 | BIZ_FAIL(1001),//业务异常错误 9 | UNAUTHORIZED(1002),//未认证 10 | SIGN_FAIL(1003),//签名错误 11 | NOT_FOUND(1004),//接口不存在 12 | INTERNAL_SERVER_ERROR(1005),//服务器内部错误 13 | WEIXIN_ERROR(1006),//和微信交互的过程中发生异常 14 | BIZ_WAIT_SCAN_LOGIN(10000),//等待用户扫码登录 15 | ; 16 | 17 | private final int code; 18 | 19 | ResultCode(int code) { 20 | this.code = code; 21 | } 22 | 23 | public int getCode() { 24 | return code; 25 | } 26 | } -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/utils/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | /** 7 | * 日期工具 8 | */ 9 | public class DateUtil { 10 | /** 11 | * 获取当前时间 12 | */ 13 | public static String getNowDateTime() { 14 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 15 | return format.format(new Date()); 16 | } 17 | 18 | /** 19 | * 获取当前日志 20 | * 21 | * @return 22 | */ 23 | public static String getNewDate() { 24 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); 25 | return format.format(new Date()); 26 | } 27 | 28 | /** 29 | * 把时间转成 方便阅读的 30 | * 31 | * @param time 时间戳 ms 32 | */ 33 | public static String getDateTime(long time) { 34 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 35 | return format.format(new Date(time)); 36 | } 37 | 38 | /** 39 | * 获取当前的秒级时间戳 40 | */ 41 | public static String getNowSecondStamp() { 42 | return String.valueOf(System.currentTimeMillis() / 1000); 43 | } 44 | 45 | /** 46 | * 把毫秒转成方便阅读的时长 47 | */ 48 | public static String formatTimeLong(Long time) { 49 | long day = time / 86400000; 50 | time = time % 86400000; 51 | long hours = time / 3600000; 52 | time = time % 3600000; 53 | long minutes = time / 60000; 54 | time = time % 60000; 55 | long second = time / 1000; 56 | long milliseconds = time % 1000; 57 | StringBuilder result = new StringBuilder(); 58 | if (day > 0) { 59 | result.append(day).append("天"); 60 | } 61 | if (hours > 0) { 62 | result.append(hours).append("小时"); 63 | } 64 | if (minutes > 0) { 65 | result.append(minutes).append("分钟"); 66 | } 67 | if (second > 0) { 68 | result.append(second).append("秒"); 69 | } 70 | if (milliseconds > 0) { 71 | result.append(second).append("毫秒"); 72 | } 73 | return result.toString(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/utils/RandomUtil.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.utils; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | /** 6 | * 说明:获取随机数 7 | * 作者:zjiecode 8 | * 时间:2019-05-11 9 | */ 10 | public class RandomUtil { 11 | private static String STRING = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 12 | 13 | /** 14 | * 获取随机的数字 15 | */ 16 | public static String getRandomNumber(int len) { 17 | StringBuilder stringBuilder = new StringBuilder(len); 18 | ThreadLocalRandom random = ThreadLocalRandom.current(); 19 | for (int i = 0; i < len; i++) { 20 | stringBuilder.append(random.nextInt(10)); 21 | } 22 | return stringBuilder.toString(); 23 | } 24 | 25 | /** 26 | * 随机生成字符串 27 | */ 28 | public static String getRandomStr(int len) { 29 | StringBuilder stringBuilder = new StringBuilder(len); 30 | ThreadLocalRandom random = ThreadLocalRandom.current(); 31 | for (int i = 0; i < len; i++) { 32 | stringBuilder.append(STRING.charAt(random.nextInt(STRING.length()))); 33 | } 34 | return stringBuilder.toString(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /demo/src/main/java/com/smjcco/wxpusher/client/sdk/demo/utils/ThrowableUtils.java: -------------------------------------------------------------------------------- 1 | package com.smjcco.wxpusher.client.sdk.demo.utils; 2 | 3 | 4 | import org.springframework.lang.Nullable; 5 | 6 | public class ThrowableUtils { 7 | /** 8 | * 获取根错误 9 | */ 10 | public static @Nullable Throwable getRootThrowable(Throwable throwable){ 11 | if(throwable==null){ 12 | return null; 13 | } 14 | while (throwable.getCause()!=null){ 15 | throwable = throwable.getCause(); 16 | } 17 | return throwable; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demo/src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | #端口 2 | server.port=6002 3 | logging.level.com.zjiecode.wxpusher.demo=info 4 | #关闭缓存,及时刷新,上线生产环境需要修改为true 5 | spring.freemarker.cache=true 6 | #异常处理方式 7 | spring.freemarker.settings.template_exception_handler=rethrow 8 | #发送的应用的apptoken, 在 http://wxpusher.zjiecode.com/admin/ 注册应用后,可以获取到应用的apptoken 9 | wxpusher.biz.apptoken=xxxx 10 | -------------------------------------------------------------------------------- /demo/src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | #端口 2 | server.port=6102 3 | logging.level.com.zjiecode.wxpusher.demo=debug 4 | #关闭缓存,及时刷新,上线生产环境需要修改为true 5 | spring.freemarker.cache=false 6 | #异常处理方式 7 | spring.freemarker.settings.template_exception_handler=rethrow 8 | #发送的应用的apptoken, 在 http://wxpusher.zjiecode.com/admin/ 注册应用后,可以获取到应用的apptoken 9 | wxpusher.biz.apptoken=xxxx 10 | -------------------------------------------------------------------------------- /demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=prod 2 | # 404 交给异常处理器处理 3 | spring.mvc.throw-exception-if-no-handler-found=true 4 | spring.resources.add-mappings=false 5 | #设定ftl文件路径 6 | spring.freemarker.tempalte-loader-path=classpath:/templates/ 7 | spring.freemarker.charset=UTF-8 8 | spring.freemarker.check-template-location=true 9 | spring.freemarker.content-type=text/html 10 | spring.freemarker.expose-request-attributes=true 11 | spring.freemarker.expose-session-attributes=true 12 | spring.freemarker.request-context-attribute=request 13 | spring.freemarker.suffix=.ftl 14 | -------------------------------------------------------------------------------- /demo/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | ===============系统启动中================= 3 | 4 | Wxpusher Demo程序启动中 5 | 6 | ===============系统启动中================= -------------------------------------------------------------------------------- /demo/src/main/resources/templates/display.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | WxPusher-消息推送服务-演示程序 10 | 89 | 90 | 91 |
92 |

WxPusher消息推送平台 - 演示程序

93 | 使用WxPusher免费推送消息到个人微信上,更快更便捷。
94 | 本程序已经开源,你可以点击这里参考源码。
95 | 101 | 102 |
103 |
104 |

发送消息(下行消息)

105 | 说明:调用API就可以发送消息到个人微信上。 106 |
1、请使用微信扫描下方二维码获取你的UID;
107 | 108 | 115 | 122 |
2、发送消息:
123 |
124 |
125 | 2.1、快捷发送(点击即可发送测试消息) 126 |
127 |
128 | 发送文本消息发送HTML消息发送Markdown消息 132 |
133 | 2.2、发送自定义消息 134 | 135 | 发送消息 136 |
137 |
138 |
139 |
140 |

接收消息(上行消息)

141 |
说明:个人微信发送消息,服务器可以收到回调。
142 |
1、请使用微信关注公众号 WxPusher (公众号名字:新消息服务)或者扫左侧二维码也可以关注
143 |
2、在公众号里面发送「#97 自定义参数」,收到的消息将在下方显示:
144 |
145 |
146 |
147 |
148 | 149 | 150 | 151 | 246 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 03 12:39:40 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'wxpusher-sdk-java' 2 | include ":demo" 3 | include ":client-sdk" 4 | --------------------------------------------------------------------------------