├── .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 | [  ](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