mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2025-10-15 18:55:13 +08:00
#193 增加小程序模块weixin-java-miniapp,支持小程序后台开发
This commit is contained in:
1
pom.xml
1
pom.xml
@@ -93,6 +93,7 @@
|
||||
<module>weixin-java-cp</module>
|
||||
<module>weixin-java-mp</module>
|
||||
<module>weixin-java-pay</module>
|
||||
<module>weixin-java-miniapp</module>
|
||||
<!--module>weixin-java-osgi</module-->
|
||||
</modules>
|
||||
|
||||
|
84
weixin-java-miniapp/pom.xml
Normal file
84
weixin-java-miniapp/pom.xml
Normal file
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0"?>
|
||||
<project
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-parent</artifactId>
|
||||
<version>2.6.3.BETA</version>
|
||||
</parent>
|
||||
<artifactId>weixin-java-miniapp</artifactId>
|
||||
<name>WeiXin Java Tools - MiniApp</name>
|
||||
<description>微信小程序Java SDK</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jodd</groupId>
|
||||
<artifactId>jodd-http</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<suiteXmlFiles>
|
||||
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
|
||||
</suiteXmlFiles>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@@ -0,0 +1,65 @@
|
||||
package cn.binarywang.wx.miniapp.api;
|
||||
|
||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 临时素材接口
|
||||
* Created by Binary Wang on 2016/7/21.
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public interface WxMaMediaService {
|
||||
String MEDIA_UPLOAD_URL = "https://api.weixin.qq.com/cgi-bin/media/upload?type=%s";
|
||||
String MEDIA_GET_URL = "https://api.weixin.qq.com/cgi-bin/media/get";
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 新增临时素材
|
||||
* 小程序可以使用本接口把媒体文件(目前仅支持图片)上传到微信服务器,用户发送客服消息或被动回复用户消息。
|
||||
* 详情请见: <a href="https://mp.weixin.qq.com/debug/wxadoc/dev/api/custommsg/material.html#新增临时素材">新增临时素材</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE
|
||||
* </pre>
|
||||
*
|
||||
* @param mediaType 媒体类型,
|
||||
* @param file 文件对象
|
||||
* @see #uploadMedia(String, String, InputStream)
|
||||
*/
|
||||
WxMediaUploadResult uploadMedia(String mediaType, File file) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 新增临时素材
|
||||
* 小程序可以使用本接口把媒体文件(目前仅支持图片)上传到微信服务器,用户发送客服消息或被动回复用户消息。
|
||||
*
|
||||
* 详情请见: <a href="https://mp.weixin.qq.com/debug/wxadoc/dev/api/custommsg/material.html#新增临时素材">新增临时素材</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE
|
||||
* </pre>
|
||||
*
|
||||
* @param mediaType 媒体类型
|
||||
* @param fileType 文件类型
|
||||
* @param inputStream 输入流
|
||||
* @see #uploadMedia(java.lang.String, java.io.File)
|
||||
*/
|
||||
WxMediaUploadResult uploadMedia(String mediaType, String fileType, InputStream inputStream) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 获取临时素材
|
||||
* 小程序可以使用本接口获取客服消息内的临时素材(即下载临时的多媒体文件)。目前小程序仅支持下载图片文件。
|
||||
*
|
||||
* 详情请见: <a href="https://mp.weixin.qq.com/debug/wxadoc/dev/api/custommsg/material.html#获取临时素材">获取临时素材</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
|
||||
* </pre>
|
||||
*
|
||||
* @param mediaId 媒体Id
|
||||
* @return 保存到本地的临时文件
|
||||
*/
|
||||
File getMedia(String mediaId) throws WxErrorException;
|
||||
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package cn.binarywang.wx.miniapp.api;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 消息发送接口
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public interface WxMaMsgService {
|
||||
String KEFU_MESSAGE_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send";
|
||||
String TEMPLATE_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send";
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 发送客服消息
|
||||
* 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140547&token=&lang=zh_CN">发送客服消息</a>
|
||||
* 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN
|
||||
* </pre>
|
||||
*/
|
||||
boolean sendKefuMsg(WxMaKefuMessage message) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 发送模板消息
|
||||
* 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277&token=&lang=zh_CN
|
||||
* </pre>
|
||||
*
|
||||
* @return 消息Id
|
||||
*/
|
||||
String sendTemplateMsg(WxMaTemplateMessage templateMessage) throws WxErrorException;
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package cn.binarywang.wx.miniapp.api;
|
||||
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 二维码相关操作接口
|
||||
* 文档地址:https://mp.weixin.qq.com/debug/wxadoc/dev/api/qrcode.html
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public interface WxMaQrcodeService {
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 获取小程序页面二维码
|
||||
* 适用于需要的码数量较少的业务场景
|
||||
* 通过该接口,仅能生成已发布的小程序的二维码。
|
||||
* 可以在开发者工具预览时生成开发版的带参二维码。
|
||||
* 带参二维码只有 100000 个,请谨慎调用。
|
||||
* </pre>
|
||||
*
|
||||
* @param path 不能为空,最大长度 128 字节
|
||||
* @param width 默认430 二维码的宽度
|
||||
*/
|
||||
File createQrcode(String path, int width) throws WxErrorException;
|
||||
}
|
@@ -0,0 +1,135 @@
|
||||
package cn.binarywang.wx.miniapp.api;
|
||||
|
||||
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
|
||||
import me.chanjar.weixin.common.util.http.RequestExecutor;
|
||||
import me.chanjar.weixin.common.util.http.RequestHttp;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public interface WxMaService {
|
||||
/**
|
||||
* 获取access_token
|
||||
*/
|
||||
String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 验证消息的确来自微信服务器
|
||||
* 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN
|
||||
* </pre>
|
||||
*/
|
||||
boolean checkSignature(String timestamp, String nonce, String signature);
|
||||
|
||||
/**
|
||||
* 获取access_token, 不强制刷新access_token
|
||||
*
|
||||
* @see #getAccessToken(boolean)
|
||||
*/
|
||||
String getAccessToken() throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 获取access_token,本方法线程安全
|
||||
* 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
|
||||
*
|
||||
* 另:本service的所有方法都会在access_token过期是调用此方法
|
||||
*
|
||||
* 程序员在非必要情况下尽量不要主动调用此方法
|
||||
*
|
||||
* 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183&token=&lang=zh_CN
|
||||
* </pre>
|
||||
*
|
||||
* @param forceRefresh 强制刷新
|
||||
*/
|
||||
String getAccessToken(boolean forceRefresh) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求
|
||||
*/
|
||||
String get(String url, String queryParam) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求
|
||||
*/
|
||||
String post(String url, String postData) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Service没有实现某个API的时候,可以用这个,
|
||||
* 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
|
||||
* 可以参考,{@link MediaUploadRequestExecutor}的实现方法
|
||||
* </pre>
|
||||
*/
|
||||
<T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试
|
||||
* 默认:1000ms
|
||||
* </pre>
|
||||
*/
|
||||
void setRetrySleepMillis(int retrySleepMillis);
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 设置当微信系统响应系统繁忙时,最大重试次数
|
||||
* 默认:5次
|
||||
* </pre>
|
||||
*/
|
||||
void setMaxRetryTimes(int maxRetryTimes);
|
||||
|
||||
/**
|
||||
* 获取WxMaConfig 对象
|
||||
*
|
||||
* @return WxMaConfig
|
||||
*/
|
||||
WxMaConfig getWxMaConfig();
|
||||
|
||||
/**
|
||||
* 注入 {@link WxMaConfig} 的实现
|
||||
*/
|
||||
void setWxMaConfig(WxMaConfig wxConfigProvider);
|
||||
|
||||
/**
|
||||
* 返回消息(客服消息和模版消息)发送接口方法实现类,以方便调用其各个接口
|
||||
*
|
||||
* @return WxMaMsgService
|
||||
*/
|
||||
WxMaMsgService getMsgService();
|
||||
|
||||
/**
|
||||
* 返回素材相关接口方法的实现类对象,以方便调用其各个接口
|
||||
*
|
||||
* @return WxMaMediaService
|
||||
*/
|
||||
WxMaMediaService getMediaService();
|
||||
|
||||
/**
|
||||
* 返回用户相关接口方法的实现类对象,以方便调用其各个接口
|
||||
*
|
||||
* @return WxMaUserService
|
||||
*/
|
||||
WxMaUserService getUserService();
|
||||
|
||||
/**
|
||||
* 返回二维码相关接口方法的实现类对象,以方便调用其各个接口
|
||||
*
|
||||
* @return WxMaQrcodeService
|
||||
*/
|
||||
WxMaQrcodeService getQrcodeService();
|
||||
|
||||
/**
|
||||
* 初始化http请求对象
|
||||
*/
|
||||
void initHttp();
|
||||
|
||||
/**
|
||||
* 请求http请求相关信息
|
||||
*/
|
||||
RequestHttp getRequestHttp();
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package cn.binarywang.wx.miniapp.api;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
|
||||
/**
|
||||
* 用户信息相关操作接口
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public interface WxMaUserService {
|
||||
String JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session";
|
||||
|
||||
/**
|
||||
* 获取登录后的session信息
|
||||
*
|
||||
* @param jsCode 登录时获取的 code
|
||||
*/
|
||||
WxMaJscode2SessionResult getSessionInfo(String jsCode) throws WxErrorException;
|
||||
|
||||
/**
|
||||
* 解密用户敏感数据
|
||||
*
|
||||
* @param sessionKey 会话密钥
|
||||
* @param encryptedData 消息密文
|
||||
* @param ivStr 加密算法的初始向量
|
||||
*/
|
||||
WxMaUserInfo getUserInfo(String sessionKey, String encryptedData, String ivStr);
|
||||
|
||||
/**
|
||||
* 验证用户信息完整性
|
||||
*
|
||||
* @param sessionKey 会话密钥
|
||||
* @param rawData 微信用户基本信息
|
||||
* @param signature 数据签名
|
||||
*/
|
||||
boolean checkUserInfo(String sessionKey, String rawData, String signature);
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaMediaService;
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import me.chanjar.weixin.common.bean.result.WxError;
|
||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.common.util.fs.FileUtils;
|
||||
import me.chanjar.weixin.common.util.http.MediaDownloadRequestExecutor;
|
||||
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
|
||||
import me.chanjar.weixin.common.util.http.RequestExecutor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaMediaServiceImpl implements WxMaMediaService {
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private WxMaService wxMaService;
|
||||
|
||||
public WxMaMediaServiceImpl(WxMaService wxMaService) {
|
||||
this.wxMaService = wxMaService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMediaUploadResult uploadMedia(String mediaType, String fileType, InputStream inputStream) throws WxErrorException {
|
||||
try {
|
||||
return this.uploadMedia(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new WxErrorException(WxError.newBuilder().setErrorMsg(e.getMessage()).build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMediaUploadResult uploadMedia(String mediaType, File file) throws WxErrorException {
|
||||
String url = String.format(MEDIA_UPLOAD_URL, mediaType);
|
||||
return this.wxMaService.execute(MediaUploadRequestExecutor.create(this.wxMaService.getRequestHttp()), url, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getMedia(String mediaId) throws WxErrorException {
|
||||
try {
|
||||
RequestExecutor<File, String> executor = MediaDownloadRequestExecutor
|
||||
.create(this.wxMaService.getRequestHttp(), Files.createTempDirectory("wxma").toFile());
|
||||
return this.wxMaService.execute(executor, MEDIA_GET_URL, "media_id=" + mediaId);
|
||||
} catch (IOException e) {
|
||||
this.log.error(e.getMessage(), e);
|
||||
throw new WxErrorException(WxError.newBuilder().setErrorMsg(e.getMessage()).build());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaMsgService;
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import me.chanjar.weixin.common.bean.result.WxError;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaMsgServiceImpl implements WxMaMsgService {
|
||||
private static final JsonParser JSON_PARSER = new JsonParser();
|
||||
private WxMaService wxMaService;
|
||||
|
||||
public WxMaMsgServiceImpl(WxMaService wxMaService) {
|
||||
this.wxMaService = wxMaService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendKefuMsg(WxMaKefuMessage message) throws WxErrorException {
|
||||
String responseContent = this.wxMaService.post(KEFU_MESSAGE_SEND_URL, message.toJson());
|
||||
return responseContent != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sendTemplateMsg(WxMaTemplateMessage templateMessage) throws WxErrorException {
|
||||
String responseContent = this.wxMaService.post(TEMPLATE_MSG_SEND_URL, templateMessage.toJson());
|
||||
JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject();
|
||||
if (jsonObject.get("errcode").getAsInt() == 0) {
|
||||
return jsonObject.get("msgid").getAsString();
|
||||
}
|
||||
|
||||
throw new WxErrorException(WxError.fromJson(responseContent));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaQrcodeService;
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaQrcode;
|
||||
import cn.binarywang.wx.miniapp.util.http.QrCodeRequestExecutor;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaQrcodeServiceImpl implements WxMaQrcodeService {
|
||||
private WxMaService wxMaService;
|
||||
|
||||
public WxMaQrcodeServiceImpl(WxMaService wxMaService) {
|
||||
this.wxMaService = wxMaService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File createQrcode(String path, int width) throws WxErrorException {
|
||||
String url = "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode";
|
||||
return this.wxMaService.execute(new QrCodeRequestExecutor(this.wxMaService.getRequestHttp()),
|
||||
url, new WxMaQrcode(path, width));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,265 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.*;
|
||||
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||
import com.google.gson.JsonParser;
|
||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||
import me.chanjar.weixin.common.bean.result.WxError;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.common.util.crypto.SHA1;
|
||||
import me.chanjar.weixin.common.util.http.*;
|
||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
||||
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.BasicResponseHandler;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpClient, HttpHost> {
|
||||
private static final JsonParser JSON_PARSER = new JsonParser();
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private CloseableHttpClient httpClient;
|
||||
private HttpHost httpProxy;
|
||||
private WxMaConfig wxMaConfig;
|
||||
|
||||
private WxMaMsgService kefuService = new WxMaMsgServiceImpl(this);
|
||||
private WxMaMediaService materialService = new WxMaMediaServiceImpl(this);
|
||||
private WxMaUserService userService = new WxMaUserServiceImpl(this);
|
||||
private WxMaQrcodeService qrCodeService = new WxMaQrcodeServiceImpl(this);
|
||||
|
||||
private int retrySleepMillis = 1000;
|
||||
private int maxRetryTimes = 5;
|
||||
|
||||
@Override
|
||||
public CloseableHttpClient getRequestHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHost getRequestHttpProxy() {
|
||||
return httpProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpType getRequestType() {
|
||||
return HttpType.APACHE_HTTP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initHttp() {
|
||||
WxMaConfig configStorage = this.getWxMaConfig();
|
||||
ApacheHttpClientBuilder apacheHttpClientBuilder = configStorage.getApacheHttpClientBuilder();
|
||||
if (null == apacheHttpClientBuilder) {
|
||||
apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get();
|
||||
}
|
||||
|
||||
apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
|
||||
.httpProxyPort(configStorage.getHttpProxyPort())
|
||||
.httpProxyUsername(configStorage.getHttpProxyUsername())
|
||||
.httpProxyPassword(configStorage.getHttpProxyPassword());
|
||||
|
||||
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
|
||||
this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
|
||||
}
|
||||
|
||||
this.httpClient = apacheHttpClientBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestHttp getRequestHttp() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
|
||||
Lock lock = this.getWxMaConfig().getAccessTokenLock();
|
||||
try {
|
||||
lock.lock();
|
||||
|
||||
if (forceRefresh) {
|
||||
this.getWxMaConfig().expireAccessToken();
|
||||
}
|
||||
|
||||
if (this.getWxMaConfig().isAccessTokenExpired()) {
|
||||
String url = String.format(WxMaService.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(),
|
||||
this.getWxMaConfig().getSecret());
|
||||
try {
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
if (this.getRequestHttpProxy() != null) {
|
||||
RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
|
||||
httpGet.setConfig(config);
|
||||
}
|
||||
try (CloseableHttpResponse response = getRequestHttpClient().execute(httpGet)) {
|
||||
String resultContent = new BasicResponseHandler().handleResponse(response);
|
||||
WxError error = WxError.fromJson(resultContent);
|
||||
if (error.getErrorCode() != 0) {
|
||||
throw new WxErrorException(error);
|
||||
}
|
||||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
|
||||
this.getWxMaConfig().updateAccessToken(accessToken.getAccessToken(),
|
||||
accessToken.getExpiresIn());
|
||||
} finally {
|
||||
httpGet.releaseConnection();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
return this.getWxMaConfig().getAccessToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkSignature(String timestamp, String nonce, String signature) {
|
||||
try {
|
||||
return SHA1.gen(this.getWxMaConfig().getToken(), timestamp, nonce).equals(signature);
|
||||
} catch (Exception e) {
|
||||
this.log.error("Checking signature failed, and the reason is :" + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessToken() throws WxErrorException {
|
||||
return getAccessToken(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(String url, String queryParam) throws WxErrorException {
|
||||
return execute(SimpleGetRequestExecutor.create(this), url, queryParam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String post(String url, String postData) throws WxErrorException {
|
||||
return execute(SimplePostRequestExecutor.create(this), url, postData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
|
||||
*/
|
||||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
|
||||
int retryTimes = 0;
|
||||
do {
|
||||
try {
|
||||
return this.executeInternal(executor, uri, data);
|
||||
} catch (WxErrorException e) {
|
||||
if (retryTimes + 1 > this.maxRetryTimes) {
|
||||
this.log.warn("重试达到最大次数【{}】", maxRetryTimes);
|
||||
//最后一次重试失败后,直接抛出异常,不再等待
|
||||
throw new RuntimeException("微信服务端异常,超出重试次数");
|
||||
}
|
||||
|
||||
WxError error = e.getError();
|
||||
// -1 系统繁忙, 1000ms后重试
|
||||
if (error.getErrorCode() == -1) {
|
||||
int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
|
||||
try {
|
||||
this.log.warn("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
|
||||
Thread.sleep(sleepMillis);
|
||||
} catch (InterruptedException e1) {
|
||||
throw new RuntimeException(e1);
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} while (retryTimes++ < this.maxRetryTimes);
|
||||
|
||||
this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
|
||||
throw new RuntimeException("微信服务端异常,超出重试次数");
|
||||
}
|
||||
|
||||
public synchronized <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
|
||||
if (uri.contains("access_token=")) {
|
||||
throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
|
||||
}
|
||||
String accessToken = getAccessToken(false);
|
||||
|
||||
String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken;
|
||||
|
||||
try {
|
||||
T result = executor.execute(uriWithAccessToken, data);
|
||||
this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", uriWithAccessToken, data, result);
|
||||
return result;
|
||||
} catch (WxErrorException e) {
|
||||
WxError error = e.getError();
|
||||
/*
|
||||
* 发生以下情况时尝试刷新access_token
|
||||
* 40001 获取access_token时AppSecret错误,或者access_token无效
|
||||
* 42001 access_token超时
|
||||
* 40014 不合法的access_token,请开发者认真比对access_token的有效性(如是否过期)
|
||||
*/
|
||||
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) {
|
||||
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
|
||||
this.getWxMaConfig().expireAccessToken();
|
||||
if (this.getWxMaConfig().autoRefreshToken()) {
|
||||
return this.execute(executor, uri, data);
|
||||
}
|
||||
}
|
||||
|
||||
if (error.getErrorCode() != 0) {
|
||||
this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", uriWithAccessToken, data, error);
|
||||
throw new WxErrorException(error);
|
||||
}
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[EXCEPTION]: {}", uriWithAccessToken, data, e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaConfig getWxMaConfig() {
|
||||
return this.wxMaConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWxMaConfig(WxMaConfig wxConfigProvider) {
|
||||
this.wxMaConfig = wxConfigProvider;
|
||||
this.initHttp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRetrySleepMillis(int retrySleepMillis) {
|
||||
this.retrySleepMillis = retrySleepMillis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxRetryTimes(int maxRetryTimes) {
|
||||
this.maxRetryTimes = maxRetryTimes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaMsgService getMsgService() {
|
||||
return this.kefuService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaMediaService getMediaService() {
|
||||
return this.materialService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaUserService getUserService() {
|
||||
return this.userService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaQrcodeService getQrcodeService() {
|
||||
return this.qrCodeService;
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.api.WxMaUserService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
|
||||
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
|
||||
import com.google.common.base.Joiner;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaUserServiceImpl implements WxMaUserService {
|
||||
private WxMaService service;
|
||||
|
||||
WxMaUserServiceImpl(WxMaService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaJscode2SessionResult getSessionInfo(String jsCode) throws WxErrorException {
|
||||
final WxMaConfig config = service.getWxMaConfig();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("appid", config.getAppid());
|
||||
params.put("secret", config.getSecret());
|
||||
params.put("js_code", jsCode);
|
||||
params.put("grant_type", "authorization_code");
|
||||
|
||||
String result = this.service.get(JSCODE_TO_SESSION_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
|
||||
return WxMaJscode2SessionResult.fromJson(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaUserInfo getUserInfo(String sessionKey, String encryptedData, String ivStr) {
|
||||
return WxMaUserInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkUserInfo(String sessionKey, String rawData, String signature) {
|
||||
final String generatedSignature = DigestUtils.sha1Hex(rawData + sessionKey);
|
||||
System.out.println(generatedSignature);
|
||||
return generatedSignature.equals(signature);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package cn.binarywang.wx.miniapp.bean;
|
||||
|
||||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* {"session_key":"nzoqhc3OnwHzeTxJs+inbQ==","expires_in":2592000,"openid":"oVBkZ0aYgDMDIywRdgPW8-joxXc4"}
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaJscode2SessionResult {
|
||||
@SerializedName("session_key")
|
||||
private String sessionKey;
|
||||
|
||||
@SerializedName("expires_in")
|
||||
private Integer expiresin;
|
||||
|
||||
@SerializedName("openid")
|
||||
private String openid;
|
||||
|
||||
public String getSessionKey() {
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
public void setSessionKey(String sessionKey) {
|
||||
this.sessionKey = sessionKey;
|
||||
}
|
||||
|
||||
public Integer getExpiresin() {
|
||||
return expiresin;
|
||||
}
|
||||
|
||||
public void setExpiresin(Integer expiresin) {
|
||||
this.expiresin = expiresin;
|
||||
}
|
||||
|
||||
public String getOpenid() {
|
||||
return openid;
|
||||
}
|
||||
|
||||
public void setOpenid(String openid) {
|
||||
this.openid = openid;
|
||||
}
|
||||
|
||||
public static WxMaJscode2SessionResult fromJson(String json) {
|
||||
return WxMaGsonBuilder.create().fromJson(json, WxMaJscode2SessionResult.class);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
package cn.binarywang.wx.miniapp.bean;
|
||||
|
||||
import cn.binarywang.wx.miniapp.builder.ImageBuilder;
|
||||
import cn.binarywang.wx.miniapp.builder.TextBuilder;
|
||||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 客服消息
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaKefuMessage implements Serializable {
|
||||
private static final long serialVersionUID = -9196732086954365246L;
|
||||
|
||||
private String toUser;
|
||||
private String msgType;
|
||||
private String content;
|
||||
private String mediaId;
|
||||
private String thumbMediaId;
|
||||
private String title;
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 获得文本消息builder
|
||||
*/
|
||||
public static TextBuilder TEXT() {
|
||||
return new TextBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得图片消息builder
|
||||
*/
|
||||
public static ImageBuilder IMAGE() {
|
||||
return new ImageBuilder();
|
||||
}
|
||||
|
||||
public String getToUser() {
|
||||
return this.toUser;
|
||||
}
|
||||
|
||||
public void setToUser(String toUser) {
|
||||
this.toUser = toUser;
|
||||
}
|
||||
|
||||
public String getMsgType() {
|
||||
return this.msgType;
|
||||
}
|
||||
|
||||
public void setMsgType(String msgType) {
|
||||
this.msgType = msgType;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getMediaId() {
|
||||
return this.mediaId;
|
||||
}
|
||||
|
||||
public void setMediaId(String mediaId) {
|
||||
this.mediaId = mediaId;
|
||||
}
|
||||
|
||||
public String getThumbMediaId() {
|
||||
return this.thumbMediaId;
|
||||
}
|
||||
|
||||
public void setThumbMediaId(String thumbMediaId) {
|
||||
this.thumbMediaId = thumbMediaId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return WxMaGsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,236 @@
|
||||
package cn.binarywang.wx.miniapp.bean;
|
||||
|
||||
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
|
||||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
|
||||
import cn.binarywang.wx.miniapp.util.xml.XStreamTransformer;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
import com.thoughtworks.xstream.annotations.XStreamConverter;
|
||||
import me.chanjar.weixin.common.util.ToStringUtils;
|
||||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@XStreamAlias("xml")
|
||||
public class WxMaMessage implements Serializable {
|
||||
private static final long serialVersionUID = -3586245291677274914L;
|
||||
|
||||
@SerializedName("Encrypt")
|
||||
@XStreamAlias("Encrypt")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String encrypt;
|
||||
|
||||
@SerializedName("ToUserName")
|
||||
@XStreamAlias("ToUserName")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String toUser;
|
||||
|
||||
@SerializedName("FromUserName")
|
||||
@XStreamAlias("FromUserName")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String fromUser;
|
||||
|
||||
@SerializedName("CreateTime")
|
||||
@XStreamAlias("CreateTime")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private Integer createTime;
|
||||
|
||||
@SerializedName("MsgDataFormat")
|
||||
@XStreamAlias("MsgDataFormat")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String msgType;
|
||||
|
||||
// 文本消息
|
||||
@SerializedName("Content")
|
||||
@XStreamAlias("Content")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String content;
|
||||
|
||||
@SerializedName("MsgId")
|
||||
@XStreamAlias("MsgId")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private Long msgId;
|
||||
|
||||
// 图片消息
|
||||
@SerializedName("PicUrl")
|
||||
@XStreamAlias("PicUrl")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String picUrl;
|
||||
|
||||
@SerializedName("MediaId")
|
||||
@XStreamAlias("MediaId")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String mediaId;
|
||||
|
||||
// 事件消息
|
||||
@SerializedName("Event")
|
||||
@XStreamAlias("Event")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String event;
|
||||
|
||||
@SerializedName("SessionFrom")
|
||||
@XStreamAlias("SessionFrom")
|
||||
@XStreamConverter(value = XStreamCDataConverter.class)
|
||||
private String sessionFrom;
|
||||
|
||||
public static WxMaMessage fromXml(String xml) {
|
||||
return XStreamTransformer.fromXml(WxMaMessage.class, xml);
|
||||
}
|
||||
|
||||
public static WxMaMessage fromXml(InputStream is) {
|
||||
return XStreamTransformer.fromXml(WxMaMessage.class, is);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从加密字符串转换
|
||||
*
|
||||
* @param encryptedXml 密文
|
||||
* @param wxMaConfig 配置存储器对象
|
||||
* @param timestamp 时间戳
|
||||
* @param nonce 随机串
|
||||
* @param msgSignature 签名串
|
||||
*/
|
||||
public static WxMaMessage fromEncryptedXml(String encryptedXml,
|
||||
WxMaConfig wxMaConfig, String timestamp, String nonce,
|
||||
String msgSignature) {
|
||||
String plainText = new WxMaCryptUtils(wxMaConfig).decrypt(msgSignature, timestamp, nonce, encryptedXml);
|
||||
return fromXml(plainText);
|
||||
}
|
||||
|
||||
public static WxMaMessage fromEncryptedXml(InputStream is, WxMaConfig wxMaConfig, String timestamp,
|
||||
String nonce, String msgSignature) {
|
||||
try {
|
||||
return fromEncryptedXml(IOUtils.toString(is, StandardCharsets.UTF_8), wxMaConfig,
|
||||
timestamp, nonce, msgSignature);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static WxMaMessage fromJson(String json) {
|
||||
return WxMaGsonBuilder.create().fromJson(json, WxMaMessage.class);
|
||||
}
|
||||
|
||||
public static WxMaMessage fromEncryptedJson(String encryptedJson, WxMaConfig config) {
|
||||
try {
|
||||
WxMaMessage encryptedMessage = fromJson(encryptedJson);
|
||||
String plainText = new WxMaCryptUtils(config).decrypt(encryptedMessage.getEncrypt());
|
||||
return fromJson(plainText);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static WxMaMessage fromEncryptedJson(InputStream inputStream, WxMaConfig config) {
|
||||
try {
|
||||
return fromEncryptedJson(IOUtils.toString(inputStream, StandardCharsets.UTF_8), config);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ToStringUtils.toSimpleString(this);
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return WxMaGsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
public String getToUser() {
|
||||
return toUser;
|
||||
}
|
||||
|
||||
public void setToUser(String toUser) {
|
||||
this.toUser = toUser;
|
||||
}
|
||||
|
||||
public String getFromUser() {
|
||||
return fromUser;
|
||||
}
|
||||
|
||||
public void setFromUser(String fromUser) {
|
||||
this.fromUser = fromUser;
|
||||
}
|
||||
|
||||
public Integer getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Integer createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public String getMsgType() {
|
||||
return msgType;
|
||||
}
|
||||
|
||||
public void setMsgType(String msgType) {
|
||||
this.msgType = msgType;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Long getMsgId() {
|
||||
return msgId;
|
||||
}
|
||||
|
||||
public void setMsgId(Long msgId) {
|
||||
this.msgId = msgId;
|
||||
}
|
||||
|
||||
public String getPicUrl() {
|
||||
return picUrl;
|
||||
}
|
||||
|
||||
public void setPicUrl(String picUrl) {
|
||||
this.picUrl = picUrl;
|
||||
}
|
||||
|
||||
public String getMediaId() {
|
||||
return mediaId;
|
||||
}
|
||||
|
||||
public void setMediaId(String mediaId) {
|
||||
this.mediaId = mediaId;
|
||||
}
|
||||
|
||||
public String getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public void setEvent(String event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public String getSessionFrom() {
|
||||
return sessionFrom;
|
||||
}
|
||||
|
||||
public void setSessionFrom(String sessionFrom) {
|
||||
this.sessionFrom = sessionFrom;
|
||||
}
|
||||
|
||||
public String getEncrypt() {
|
||||
return encrypt;
|
||||
}
|
||||
|
||||
public void setEncrypt(String encrypt) {
|
||||
this.encrypt = encrypt;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
package cn.binarywang.wx.miniapp.bean;
|
||||
|
||||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaQrcode implements Serializable {
|
||||
private static final long serialVersionUID = 5777119669111011584L;
|
||||
private String path;
|
||||
private int width = 430;
|
||||
|
||||
public WxMaQrcode(String path, int width) {
|
||||
this.path = path;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public static WxMaQrcode fromJson(String json) {
|
||||
return WxMaGsonBuilder.create().fromJson(json, WxMaQrcode.class);
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(int width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return WxMaGsonBuilder.create().toJson(this);
|
||||
}
|
||||
}
|
@@ -0,0 +1,214 @@
|
||||
package cn.binarywang.wx.miniapp.bean;
|
||||
|
||||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 参考 https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#接口说明 模板消息部分
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaTemplateMessage implements Serializable {
|
||||
private static final long serialVersionUID = 5063374783759519418L;
|
||||
/**
|
||||
* touser 是 接收者(用户)的 openid
|
||||
*/
|
||||
private String toUser;
|
||||
/**
|
||||
* template_id 是 所需下发的模板消息的id
|
||||
*/
|
||||
private String templateId;
|
||||
/**
|
||||
* <pre>
|
||||
* page 否 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。
|
||||
* </pre>
|
||||
*/
|
||||
private String page;
|
||||
/**
|
||||
* form_id 是 表单提交场景下,为 submit 事件带上的 formId;支付场景下,为本次支付的 prepay_id
|
||||
*/
|
||||
private String formId;
|
||||
/**
|
||||
* data 是 模板内容,不填则下发空模板
|
||||
*/
|
||||
private List<Data> data = new ArrayList<>();
|
||||
/**
|
||||
* color 否 模板内容字体的颜色,不填默认黑色
|
||||
*/
|
||||
private String color;
|
||||
/**
|
||||
* emphasis_keyword 否 模板需要放大的关键词,不填则默认无放大
|
||||
*/
|
||||
private String emphasisKeyword;
|
||||
|
||||
private WxMaTemplateMessage(Builder builder) {
|
||||
setToUser(builder.toUser);
|
||||
setTemplateId(builder.templateId);
|
||||
setPage(builder.page);
|
||||
setFormId(builder.formId);
|
||||
setData(builder.data);
|
||||
setColor(builder.color);
|
||||
setEmphasisKeyword(builder.emphasisKeyword);
|
||||
}
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return WxMaGsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
public String getToUser() {
|
||||
return toUser;
|
||||
}
|
||||
|
||||
public void setToUser(String toUser) {
|
||||
this.toUser = toUser;
|
||||
}
|
||||
|
||||
public String getTemplateId() {
|
||||
return templateId;
|
||||
}
|
||||
|
||||
public void setTemplateId(String templateId) {
|
||||
this.templateId = templateId;
|
||||
}
|
||||
|
||||
public String getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(String page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public String getFormId() {
|
||||
return formId;
|
||||
}
|
||||
|
||||
public void setFormId(String formId) {
|
||||
this.formId = formId;
|
||||
}
|
||||
|
||||
public List<Data> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(List<Data> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(String color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public String getEmphasisKeyword() {
|
||||
return emphasisKeyword;
|
||||
}
|
||||
|
||||
public void setEmphasisKeyword(String emphasisKeyword) {
|
||||
this.emphasisKeyword = emphasisKeyword;
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
private String name;
|
||||
private String value;
|
||||
private String color;
|
||||
|
||||
public Data(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Data(String name, String value, String color) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
public void setColor(String color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private String toUser;
|
||||
private String templateId;
|
||||
private String page;
|
||||
private String formId;
|
||||
private List<Data> data;
|
||||
private String color;
|
||||
private String emphasisKeyword;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder toUser(String toUser) {
|
||||
this.toUser = toUser;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder templateId(String templateId) {
|
||||
this.templateId = templateId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder page(String page) {
|
||||
this.page = page;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder formId(String formId) {
|
||||
this.formId = formId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder data(List<Data> data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder color(String color) {
|
||||
this.color = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder emphasisKeyword(String emphasisKeyword) {
|
||||
this.emphasisKeyword = emphasisKeyword;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WxMaTemplateMessage build() {
|
||||
return new WxMaTemplateMessage(this);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,131 @@
|
||||
package cn.binarywang.wx.miniapp.bean;
|
||||
|
||||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaUserInfo {
|
||||
private String openId;
|
||||
private String nickName;
|
||||
private String gender;
|
||||
private String language;
|
||||
private String city;
|
||||
private String province;
|
||||
private String country;
|
||||
private String avatarUrl;
|
||||
private String unionId;
|
||||
private Watermark watermark;
|
||||
|
||||
public static WxMaUserInfo fromJson(String json) {
|
||||
return WxMaGsonBuilder.create().fromJson(json, WxMaUserInfo.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
|
||||
}
|
||||
|
||||
public String getOpenId() {
|
||||
return openId;
|
||||
}
|
||||
|
||||
public void setOpenId(String openId) {
|
||||
this.openId = openId;
|
||||
}
|
||||
|
||||
public String getNickName() {
|
||||
return nickName;
|
||||
}
|
||||
|
||||
public void setNickName(String nickName) {
|
||||
this.nickName = nickName;
|
||||
}
|
||||
|
||||
public String getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(String gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public String getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public void setProvince(String province) {
|
||||
this.province = province;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public void setCountry(String country) {
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
public String getUnionId() {
|
||||
return unionId;
|
||||
}
|
||||
|
||||
public void setUnionId(String unionId) {
|
||||
this.unionId = unionId;
|
||||
}
|
||||
|
||||
public Watermark getWatermark() {
|
||||
return watermark;
|
||||
}
|
||||
|
||||
public void setWatermark(Watermark watermark) {
|
||||
this.watermark = watermark;
|
||||
}
|
||||
|
||||
public static class Watermark {
|
||||
private String timestamp;
|
||||
private String appid;
|
||||
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(String timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getAppid() {
|
||||
return appid;
|
||||
}
|
||||
|
||||
public void setAppid(String appid) {
|
||||
this.appid = appid;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package cn.binarywang.wx.miniapp.builder;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class BaseBuilder<T> {
|
||||
protected String msgType;
|
||||
protected String toUser;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T toUser(String toUser) {
|
||||
this.toUser = toUser;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public WxMaKefuMessage build() {
|
||||
WxMaKefuMessage m = new WxMaKefuMessage();
|
||||
m.setMsgType(this.msgType);
|
||||
m.setToUser(this.toUser);
|
||||
return m;
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package cn.binarywang.wx.miniapp.builder;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
|
||||
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public final class ImageBuilder extends BaseBuilder<ImageBuilder> {
|
||||
private String mediaId;
|
||||
|
||||
public ImageBuilder() {
|
||||
this.msgType = WxMaConstants.KefuMsgType.IMAGE;
|
||||
}
|
||||
|
||||
public ImageBuilder mediaId(String media_id) {
|
||||
this.mediaId = media_id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaKefuMessage build() {
|
||||
WxMaKefuMessage m = super.build();
|
||||
m.setMediaId(this.mediaId);
|
||||
return m;
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package cn.binarywang.wx.miniapp.builder;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
|
||||
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public final class TextBuilder extends BaseBuilder<TextBuilder> {
|
||||
private String content;
|
||||
|
||||
public TextBuilder() {
|
||||
this.msgType = WxMaConstants.KefuMsgType.TEXT;
|
||||
}
|
||||
|
||||
public TextBuilder content(String content) {
|
||||
this.content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WxMaKefuMessage build() {
|
||||
WxMaKefuMessage m = super.build();
|
||||
m.setContent(this.content);
|
||||
return m;
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
package cn.binarywang.wx.miniapp.config;
|
||||
|
||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
/**
|
||||
* 小程序配置
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public interface WxMaConfig {
|
||||
|
||||
String getAccessToken();
|
||||
|
||||
Lock getAccessTokenLock();
|
||||
|
||||
boolean isAccessTokenExpired();
|
||||
|
||||
/**
|
||||
* 强制将access token过期掉
|
||||
*/
|
||||
void expireAccessToken();
|
||||
|
||||
/**
|
||||
* 应该是线程安全的
|
||||
*
|
||||
* @param accessToken 要更新的WxAccessToken对象
|
||||
*/
|
||||
void updateAccessToken(WxAccessToken accessToken);
|
||||
|
||||
/**
|
||||
* 应该是线程安全的
|
||||
*
|
||||
* @param accessToken 新的accessToken值
|
||||
* @param expiresInSeconds 过期时间,以秒为单位
|
||||
*/
|
||||
void updateAccessToken(String accessToken, int expiresInSeconds);
|
||||
|
||||
String getAppid();
|
||||
|
||||
String getSecret();
|
||||
|
||||
String getToken();
|
||||
|
||||
String getAesKey();
|
||||
|
||||
String getMsgDataFormat();
|
||||
|
||||
long getExpiresTime();
|
||||
|
||||
String getHttpProxyHost();
|
||||
|
||||
int getHttpProxyPort();
|
||||
|
||||
String getHttpProxyUsername();
|
||||
|
||||
String getHttpProxyPassword();
|
||||
|
||||
/**
|
||||
* http client builder
|
||||
*
|
||||
* @return ApacheHttpClientBuilder
|
||||
*/
|
||||
ApacheHttpClientBuilder getApacheHttpClientBuilder();
|
||||
|
||||
/**
|
||||
* 是否自动刷新token
|
||||
*/
|
||||
boolean autoRefreshToken();
|
||||
|
||||
}
|
@@ -0,0 +1,185 @@
|
||||
package cn.binarywang.wx.miniapp.config;
|
||||
|
||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||
import me.chanjar.weixin.common.util.ToStringUtils;
|
||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaInMemoryConfig implements WxMaConfig {
|
||||
protected volatile String msgDataFormat;
|
||||
protected volatile String appid;
|
||||
protected volatile String secret;
|
||||
protected volatile String token;
|
||||
protected volatile String accessToken;
|
||||
protected volatile String aesKey;
|
||||
protected volatile long expiresTime;
|
||||
|
||||
protected volatile String httpProxyHost;
|
||||
protected volatile int httpProxyPort;
|
||||
protected volatile String httpProxyUsername;
|
||||
protected volatile String httpProxyPassword;
|
||||
|
||||
protected Lock accessTokenLock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* 临时文件目录
|
||||
*/
|
||||
protected volatile File tmpDirFile;
|
||||
|
||||
protected volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lock getAccessTokenLock() {
|
||||
return this.accessTokenLock;
|
||||
}
|
||||
|
||||
public void setAccessTokenLock(Lock accessTokenLock) {
|
||||
this.accessTokenLock = accessTokenLock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccessTokenExpired() {
|
||||
return System.currentTimeMillis() > this.expiresTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void updateAccessToken(WxAccessToken accessToken) {
|
||||
updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) {
|
||||
this.accessToken = accessToken;
|
||||
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireAccessToken() {
|
||||
this.expiresTime = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSecret() {
|
||||
return this.secret;
|
||||
}
|
||||
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToken() {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getExpiresTime() {
|
||||
return this.expiresTime;
|
||||
}
|
||||
|
||||
public void setExpiresTime(long expiresTime) {
|
||||
this.expiresTime = expiresTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAesKey() {
|
||||
return this.aesKey;
|
||||
}
|
||||
|
||||
public void setAesKey(String aesKey) {
|
||||
this.aesKey = aesKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsgDataFormat() {
|
||||
return this.msgDataFormat;
|
||||
}
|
||||
|
||||
public void setMsgDataFormat(String msgDataFormat) {
|
||||
this.msgDataFormat = msgDataFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHttpProxyHost() {
|
||||
return this.httpProxyHost;
|
||||
}
|
||||
|
||||
public void setHttpProxyHost(String httpProxyHost) {
|
||||
this.httpProxyHost = httpProxyHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHttpProxyPort() {
|
||||
return this.httpProxyPort;
|
||||
}
|
||||
|
||||
public void setHttpProxyPort(int httpProxyPort) {
|
||||
this.httpProxyPort = httpProxyPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHttpProxyUsername() {
|
||||
return this.httpProxyUsername;
|
||||
}
|
||||
|
||||
public void setHttpProxyUsername(String httpProxyUsername) {
|
||||
this.httpProxyUsername = httpProxyUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHttpProxyPassword() {
|
||||
return this.httpProxyPassword;
|
||||
}
|
||||
|
||||
public void setHttpProxyPassword(String httpProxyPassword) {
|
||||
this.httpProxyPassword = httpProxyPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ToStringUtils.toSimpleString(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
|
||||
return this.apacheHttpClientBuilder;
|
||||
}
|
||||
|
||||
public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) {
|
||||
this.apacheHttpClientBuilder = apacheHttpClientBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean autoRefreshToken() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getAppid() {
|
||||
return appid;
|
||||
}
|
||||
|
||||
public void setAppid(String appid) {
|
||||
this.appid = appid;
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package cn.binarywang.wx.miniapp.constant;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 小程序常量
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaConstants {
|
||||
/**
|
||||
* 消息格式
|
||||
*/
|
||||
public static class MsgDataFormat {
|
||||
public static final String XML = "XML";
|
||||
public static final String JSON = "JSON";
|
||||
}
|
||||
|
||||
/**
|
||||
* 客服消息的消息类型
|
||||
*/
|
||||
public static class KefuMsgType {
|
||||
public static final String TEXT = "text";//文本消息
|
||||
public static final String IMAGE = "image";//图片消息
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package cn.binarywang.wx.miniapp.message;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 处理小程序推送消息的处理器接口
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public interface WxMaMessageHandler {
|
||||
|
||||
void handle(WxMaMessage message, Map<String, Object> context,
|
||||
WxMaService service, WxSessionManager sessionManager) throws WxErrorException;
|
||||
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package cn.binarywang.wx.miniapp.message;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信消息拦截器,可以用来做验证
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public interface WxMaMessageInterceptor {
|
||||
|
||||
/**
|
||||
* 拦截微信消息
|
||||
*
|
||||
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个
|
||||
* @return true代表OK,false代表不OK
|
||||
*/
|
||||
boolean intercept(WxMaMessage wxMessage,
|
||||
Map<String, Object> context,
|
||||
WxMaService wxMaService,
|
||||
WxSessionManager sessionManager) throws WxErrorException;
|
||||
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package cn.binarywang.wx.miniapp.message;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
|
||||
|
||||
/**
|
||||
* 消息匹配器,用在消息路由的时候
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public interface WxMaMessageMatcher {
|
||||
|
||||
/**
|
||||
* 消息是否匹配某种模式
|
||||
*/
|
||||
boolean match(WxMaMessage message);
|
||||
|
||||
}
|
@@ -0,0 +1,176 @@
|
||||
package cn.binarywang.wx.miniapp.message;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
|
||||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler;
|
||||
import me.chanjar.weixin.common.api.WxMessageDuplicateChecker;
|
||||
import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker;
|
||||
import me.chanjar.weixin.common.session.InternalSession;
|
||||
import me.chanjar.weixin.common.session.InternalSessionManager;
|
||||
import me.chanjar.weixin.common.session.StandardSessionManager;
|
||||
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||
import me.chanjar.weixin.common.util.LogExceptionHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaMessageRouter {
|
||||
private static final int DEFAULT_THREAD_POOL_SIZE = 100;
|
||||
private final Logger log = LoggerFactory.getLogger(WxMaMessageRouter.class);
|
||||
private final List<WxMaMessageRouterRule> rules = new ArrayList<>();
|
||||
|
||||
private final WxMaService wxMaService;
|
||||
|
||||
private ExecutorService executorService;
|
||||
|
||||
private WxMessageDuplicateChecker messageDuplicateChecker;
|
||||
|
||||
private WxSessionManager sessionManager;
|
||||
|
||||
private WxErrorExceptionHandler exceptionHandler;
|
||||
|
||||
public WxMaMessageRouter(WxMaService wxMaService) {
|
||||
this.wxMaService = wxMaService;
|
||||
this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE);
|
||||
this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker();
|
||||
this.sessionManager = new StandardSessionManager();
|
||||
this.exceptionHandler = new LogExceptionHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 设置自定义的 {@link ExecutorService}
|
||||
* 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100)
|
||||
* </pre>
|
||||
*/
|
||||
public void setExecutorService(ExecutorService executorService) {
|
||||
this.executorService = executorService;
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 设置自定义的 {@link me.chanjar.weixin.common.api.WxMessageDuplicateChecker}
|
||||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker}
|
||||
* </pre>
|
||||
*/
|
||||
public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) {
|
||||
this.messageDuplicateChecker = messageDuplicateChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager}
|
||||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.session.StandardSessionManager}
|
||||
* </pre>
|
||||
*/
|
||||
public void setSessionManager(WxSessionManager sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* 设置自定义的{@link me.chanjar.weixin.common.api.WxErrorExceptionHandler}
|
||||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.LogExceptionHandler}
|
||||
* </pre>
|
||||
*/
|
||||
public void setExceptionHandler(WxErrorExceptionHandler exceptionHandler) {
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
List<WxMaMessageRouterRule> getRules() {
|
||||
return this.rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始一个新的Route规则
|
||||
*/
|
||||
public WxMaMessageRouterRule rule() {
|
||||
return new WxMaMessageRouterRule(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理微信消息
|
||||
*/
|
||||
public void route(final WxMaMessage wxMessage, final Map<String, Object> context) {
|
||||
final List<WxMaMessageRouterRule> matchRules = new ArrayList<>();
|
||||
// 收集匹配的规则
|
||||
for (final WxMaMessageRouterRule rule : this.rules) {
|
||||
if (rule.test(wxMessage)) {
|
||||
matchRules.add(rule);
|
||||
if (!rule.isReEnter()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchRules.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Future<?>> futures = new ArrayList<>();
|
||||
for (final WxMaMessageRouterRule rule : matchRules) {
|
||||
// 返回最后一个非异步的rule的执行结果
|
||||
if (rule.isAsync()) {
|
||||
futures.add(
|
||||
this.executorService.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
rule.service(wxMessage, context, WxMaMessageRouter.this.wxMaService, WxMaMessageRouter.this.sessionManager, WxMaMessageRouter.this.exceptionHandler);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
rule.service(wxMessage, context, this.wxMaService, this.sessionManager, this.exceptionHandler);
|
||||
// 在同步操作结束,session访问结束
|
||||
this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUser());
|
||||
sessionEndAccess(wxMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (futures.size() > 0) {
|
||||
this.executorService.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Future<?> future : futures) {
|
||||
try {
|
||||
future.get();
|
||||
WxMaMessageRouter.this.log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUser());
|
||||
// 异步操作结束,session访问结束
|
||||
sessionEndAccess(wxMessage);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
WxMaMessageRouter.this.log.error("Error happened when wait task finish", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void route(final WxMaMessage wxMessage) {
|
||||
this.route(wxMessage, new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对session的访问结束
|
||||
*/
|
||||
protected void sessionEndAccess(WxMaMessage wxMessage) {
|
||||
|
||||
InternalSession session = ((InternalSessionManager) this.sessionManager).findSession(wxMessage.getFromUser());
|
||||
if (session != null) {
|
||||
session.endAccess();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,316 @@
|
||||
package cn.binarywang.wx.miniapp.message;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
|
||||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaMessageRouterRule {
|
||||
|
||||
private final WxMaMessageRouter routerBuilder;
|
||||
|
||||
private boolean async = true;
|
||||
|
||||
private String fromUser;
|
||||
|
||||
private String msgType;
|
||||
|
||||
private String event;
|
||||
|
||||
private String eventKey;
|
||||
|
||||
private String content;
|
||||
|
||||
private String rContent;
|
||||
|
||||
private WxMaMessageMatcher matcher;
|
||||
|
||||
private boolean reEnter = false;
|
||||
|
||||
private List<WxMaMessageHandler> handlers = new ArrayList<>();
|
||||
|
||||
private List<WxMaMessageInterceptor> interceptors = new ArrayList<>();
|
||||
|
||||
public WxMaMessageRouterRule(WxMaMessageRouter routerBuilder) {
|
||||
this.routerBuilder = routerBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否异步执行,默认是true
|
||||
*/
|
||||
public WxMaMessageRouterRule async(boolean async) {
|
||||
this.async = async;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果msgType等于某值
|
||||
*/
|
||||
public WxMaMessageRouterRule msgType(String msgType) {
|
||||
this.msgType = msgType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果event等于某值
|
||||
*/
|
||||
public WxMaMessageRouterRule event(String event) {
|
||||
this.event = event;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果eventKey等于某值
|
||||
*/
|
||||
public WxMaMessageRouterRule eventKey(String eventKey) {
|
||||
this.eventKey = eventKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果content等于某值
|
||||
*/
|
||||
public WxMaMessageRouterRule content(String content) {
|
||||
this.content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果content匹配该正则表达式
|
||||
*/
|
||||
public WxMaMessageRouterRule rContent(String regex) {
|
||||
this.rContent = regex;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果fromUser等于某值
|
||||
*/
|
||||
public WxMaMessageRouterRule fromUser(String fromUser) {
|
||||
this.fromUser = fromUser;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果消息匹配某个matcher,用在用户需要自定义更复杂的匹配规则的时候
|
||||
*/
|
||||
public WxMaMessageRouterRule matcher(WxMaMessageMatcher matcher) {
|
||||
this.matcher = matcher;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置微信消息拦截器
|
||||
*/
|
||||
public WxMaMessageRouterRule interceptor(WxMaMessageInterceptor interceptor) {
|
||||
return interceptor(interceptor, (WxMaMessageInterceptor[]) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置微信消息拦截器
|
||||
*/
|
||||
public WxMaMessageRouterRule interceptor(WxMaMessageInterceptor interceptor, WxMaMessageInterceptor... otherInterceptors) {
|
||||
this.interceptors.add(interceptor);
|
||||
if (otherInterceptors != null && otherInterceptors.length > 0) {
|
||||
for (WxMaMessageInterceptor i : otherInterceptors) {
|
||||
this.interceptors.add(i);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置微信消息处理器
|
||||
*/
|
||||
public WxMaMessageRouterRule handler(WxMaMessageHandler handler) {
|
||||
return handler(handler, (WxMaMessageHandler[]) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置微信消息处理器
|
||||
*/
|
||||
public WxMaMessageRouterRule handler(WxMaMessageHandler handler, WxMaMessageHandler... otherHandlers) {
|
||||
this.handlers.add(handler);
|
||||
if (otherHandlers != null && otherHandlers.length > 0) {
|
||||
for (WxMaMessageHandler i : otherHandlers) {
|
||||
this.handlers.add(i);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则
|
||||
*/
|
||||
public WxMaMessageRouter end() {
|
||||
this.routerBuilder.getRules().add(this);
|
||||
return this.routerBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则结束,但是消息还会进入其他规则
|
||||
*/
|
||||
public WxMaMessageRouter next() {
|
||||
this.reEnter = true;
|
||||
return end();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将微信自定义的事件修正为不区分大小写,
|
||||
* 比如框架定义的事件常量为click,但微信传递过来的却是CLICK
|
||||
*/
|
||||
protected boolean test(WxMaMessage wxMessage) {
|
||||
return
|
||||
(this.fromUser == null || this.fromUser.equals(wxMessage.getFromUser()))
|
||||
&&
|
||||
(this.msgType == null || this.msgType.toLowerCase().equals((wxMessage.getMsgType() == null ? null : wxMessage.getMsgType().toLowerCase())))
|
||||
&&
|
||||
(this.event == null || this.event.toLowerCase().equals((wxMessage.getEvent() == null ? null : wxMessage.getEvent().toLowerCase())))
|
||||
&&
|
||||
(this.content == null || this.content
|
||||
.equals(wxMessage.getContent() == null ? null : wxMessage.getContent().trim()))
|
||||
&&
|
||||
(this.rContent == null || Pattern
|
||||
.matches(this.rContent, wxMessage.getContent() == null ? "" : wxMessage.getContent().trim()))
|
||||
&&
|
||||
(this.matcher == null || this.matcher.match(wxMessage))
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理微信推送过来的消息
|
||||
*
|
||||
* @return true 代表继续执行别的router,false 代表停止执行别的router
|
||||
*/
|
||||
protected void service(WxMaMessage wxMessage,
|
||||
Map<String, Object> context,
|
||||
WxMaService wxMaService,
|
||||
WxSessionManager sessionManager,
|
||||
WxErrorExceptionHandler exceptionHandler) {
|
||||
if (context == null) {
|
||||
context = new HashMap<>();
|
||||
}
|
||||
|
||||
try {
|
||||
// 如果拦截器不通过
|
||||
for (WxMaMessageInterceptor interceptor : this.interceptors) {
|
||||
if (!interceptor.intercept(wxMessage, context, wxMaService, sessionManager)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 交给handler处理
|
||||
for (WxMaMessageHandler handler : this.handlers) {
|
||||
// 返回最后handler的结果
|
||||
if (handler == null) {
|
||||
continue;
|
||||
}
|
||||
handler.handle(wxMessage, context, wxMaService, sessionManager);
|
||||
}
|
||||
} catch (WxErrorException e) {
|
||||
exceptionHandler.handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WxMaMessageRouter getRouterBuilder() {
|
||||
return this.routerBuilder;
|
||||
}
|
||||
|
||||
public boolean isAsync() {
|
||||
return this.async;
|
||||
}
|
||||
|
||||
public void setAsync(boolean async) {
|
||||
this.async = async;
|
||||
}
|
||||
|
||||
public String getFromUser() {
|
||||
return this.fromUser;
|
||||
}
|
||||
|
||||
public void setFromUser(String fromUser) {
|
||||
this.fromUser = fromUser;
|
||||
}
|
||||
|
||||
public String getMsgType() {
|
||||
return this.msgType;
|
||||
}
|
||||
|
||||
public void setMsgType(String msgType) {
|
||||
this.msgType = msgType;
|
||||
}
|
||||
|
||||
public String getEvent() {
|
||||
return this.event;
|
||||
}
|
||||
|
||||
public void setEvent(String event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public String getEventKey() {
|
||||
return this.eventKey;
|
||||
}
|
||||
|
||||
public void setEventKey(String eventKey) {
|
||||
this.eventKey = eventKey;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getrContent() {
|
||||
return this.rContent;
|
||||
}
|
||||
|
||||
public void setrContent(String rContent) {
|
||||
this.rContent = rContent;
|
||||
}
|
||||
|
||||
public WxMaMessageMatcher getMatcher() {
|
||||
return this.matcher;
|
||||
}
|
||||
|
||||
public void setMatcher(WxMaMessageMatcher matcher) {
|
||||
this.matcher = matcher;
|
||||
}
|
||||
|
||||
public boolean isReEnter() {
|
||||
return this.reEnter;
|
||||
}
|
||||
|
||||
public void setReEnter(boolean reEnter) {
|
||||
this.reEnter = reEnter;
|
||||
}
|
||||
|
||||
public List<WxMaMessageHandler> getHandlers() {
|
||||
return this.handlers;
|
||||
}
|
||||
|
||||
public void setHandlers(List<WxMaMessageHandler> handlers) {
|
||||
this.handlers = handlers;
|
||||
}
|
||||
|
||||
public List<WxMaMessageInterceptor> getInterceptors() {
|
||||
return this.interceptors;
|
||||
}
|
||||
|
||||
public void setInterceptors(List<WxMaMessageInterceptor> interceptors) {
|
||||
this.interceptors = interceptors;
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package cn.binarywang.wx.miniapp.util.crypt;
|
||||
|
||||
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||
import me.chanjar.weixin.common.util.crypto.PKCS7Encoder;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.AlgorithmParameters;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaCryptUtils extends me.chanjar.weixin.common.util.crypto.WxCryptUtil {
|
||||
public WxMaCryptUtils(WxMaConfig config) {
|
||||
this.appidOrCorpid = config.getAppid();
|
||||
this.token = config.getToken();
|
||||
this.aesKey = Base64.decodeBase64(config.getAesKey() + "=");
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
*
|
||||
* @param encryptedData 消息密文
|
||||
* @param ivStr iv字符串
|
||||
*/
|
||||
public static String decrypt(String sessionKey, String encryptedData, String ivStr) {
|
||||
try {
|
||||
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
|
||||
params.init(new IvParameterSpec(Base64.decodeBase64(ivStr)));
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(sessionKey), "AES"), params);
|
||||
|
||||
return new String(PKCS7Encoder.decode(cipher.doFinal(Base64.decodeBase64(encryptedData))), StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("AES解密失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package cn.binarywang.wx.miniapp.util.http;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaQrcode;
|
||||
import me.chanjar.weixin.common.bean.result.WxError;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.common.util.fs.FileUtils;
|
||||
import me.chanjar.weixin.common.util.http.RequestExecutor;
|
||||
import me.chanjar.weixin.common.util.http.RequestHttp;
|
||||
import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler;
|
||||
import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class QrCodeRequestExecutor implements RequestExecutor<File, WxMaQrcode> {
|
||||
protected RequestHttp<CloseableHttpClient, HttpHost> requestHttp;
|
||||
|
||||
public QrCodeRequestExecutor(RequestHttp requestHttp) {
|
||||
this.requestHttp = requestHttp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File execute(String uri, WxMaQrcode ticket) throws WxErrorException, IOException {
|
||||
HttpPost httpPost = new HttpPost(uri);
|
||||
if (requestHttp.getRequestHttpProxy() != null) {
|
||||
httpPost
|
||||
.setConfig(RequestConfig.custom()
|
||||
.setProxy(requestHttp.getRequestHttpProxy())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
httpPost.setEntity(new StringEntity(ticket.toString()));
|
||||
|
||||
try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost);
|
||||
InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response);) {
|
||||
Header[] contentTypeHeader = response.getHeaders("Content-Type");
|
||||
if (contentTypeHeader != null && contentTypeHeader.length > 0
|
||||
&& ContentType.TEXT_PLAIN.getMimeType().equals(contentTypeHeader[0].getValue())) {
|
||||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
|
||||
throw new WxErrorException(WxError.fromJson(responseContent));
|
||||
}
|
||||
|
||||
return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg");
|
||||
} finally {
|
||||
httpPost.releaseConnection();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package cn.binarywang.wx.miniapp.util.json;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaGsonBuilder {
|
||||
private static final GsonBuilder INSTANCE = new GsonBuilder();
|
||||
|
||||
static {
|
||||
INSTANCE.disableHtmlEscaping();
|
||||
INSTANCE.registerTypeAdapter(WxMaKefuMessage.class, new WxMaKefuMessageGsonAdapter());
|
||||
INSTANCE.registerTypeAdapter(WxMaTemplateMessage.class, new WxMaTemplateMessageGsonAdapter());
|
||||
}
|
||||
|
||||
public static Gson create() {
|
||||
return INSTANCE.create();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved.
|
||||
*
|
||||
* This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended
|
||||
* only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction
|
||||
* arose from modification of the original source, or other redistribution of this source
|
||||
* is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD.
|
||||
*/
|
||||
package cn.binarywang.wx.miniapp.util.json;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
|
||||
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaKefuMessageGsonAdapter implements JsonSerializer<WxMaKefuMessage> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(WxMaKefuMessage message, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject messageJson = new JsonObject();
|
||||
messageJson.addProperty("touser", message.getToUser());
|
||||
messageJson.addProperty("msgtype", message.getMsgType());
|
||||
|
||||
if (WxMaConstants.KefuMsgType.TEXT.equals(message.getMsgType())) {
|
||||
JsonObject text = new JsonObject();
|
||||
text.addProperty("content", message.getContent());
|
||||
messageJson.add("text", text);
|
||||
}
|
||||
|
||||
if (WxMaConstants.KefuMsgType.IMAGE.equals(message.getMsgType())) {
|
||||
JsonObject image = new JsonObject();
|
||||
image.addProperty("media_id", message.getMediaId());
|
||||
messageJson.add("image", image);
|
||||
}
|
||||
|
||||
return messageJson;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
package cn.binarywang.wx.miniapp.util.json;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaTemplateMessageGsonAdapter implements JsonSerializer<WxMaTemplateMessage> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(WxMaTemplateMessage message, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject messageJson = new JsonObject();
|
||||
messageJson.addProperty("touser", message.getToUser());
|
||||
messageJson.addProperty("template_id", message.getTemplateId());
|
||||
if (message.getPage() != null) {
|
||||
messageJson.addProperty("page", message.getPage());
|
||||
}
|
||||
|
||||
if (message.getFormId() != null) {
|
||||
messageJson.addProperty("form_id", message.getFormId());
|
||||
}
|
||||
|
||||
if (message.getPage() != null) {
|
||||
messageJson.addProperty("page", message.getPage());
|
||||
}
|
||||
|
||||
if (message.getColor() != null) {
|
||||
messageJson.addProperty("color", message.getColor());
|
||||
}
|
||||
|
||||
if (message.getEmphasisKeyword() != null) {
|
||||
messageJson.addProperty("emphasis_keyword", message.getEmphasisKeyword());
|
||||
}
|
||||
|
||||
JsonObject data = new JsonObject();
|
||||
messageJson.add("data", data);
|
||||
|
||||
if (message.getData() == null) {
|
||||
return messageJson;
|
||||
}
|
||||
|
||||
for (WxMaTemplateMessage.Data datum : message.getData()) {
|
||||
JsonObject dataJson = new JsonObject();
|
||||
dataJson.addProperty("value", datum.getValue());
|
||||
if (datum.getColor() != null) {
|
||||
dataJson.addProperty("color", datum.getColor());
|
||||
}
|
||||
data.add(datum.getName(), dataJson);
|
||||
}
|
||||
|
||||
return messageJson;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
package cn.binarywang.wx.miniapp.util.xml;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
|
||||
import com.thoughtworks.xstream.XStream;
|
||||
import me.chanjar.weixin.common.util.xml.XStreamInitializer;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class XStreamTransformer {
|
||||
private static final Map<Class<?>, XStream> CLASS_2_XSTREAM_INSTANCE = new HashMap<>();
|
||||
|
||||
static {
|
||||
registerClass(WxMaMessage.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* xml -> pojo
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T fromXml(Class<T> clazz, String xml) {
|
||||
T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(xml);
|
||||
return object;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T fromXml(Class<T> clazz, InputStream is) {
|
||||
T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is);
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* pojo -> xml
|
||||
*/
|
||||
public static <T> String toXml(Class<T> clazz, T object) {
|
||||
return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册扩展消息的解析器
|
||||
*
|
||||
* @param clz 类型
|
||||
* @param xStream xml解析器
|
||||
*/
|
||||
private static void register(Class<?> clz, XStream xStream) {
|
||||
CLASS_2_XSTREAM_INSTANCE.put(clz, xStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会自动注册该类及其子类
|
||||
*
|
||||
* @param clz 要注册的类
|
||||
*/
|
||||
private static void registerClass(Class<?> clz) {
|
||||
XStream xstream = XStreamInitializer.getInstance();
|
||||
xstream.processAnnotations(clz);
|
||||
xstream.processAnnotations(getInnerClasses(clz));
|
||||
if (clz.equals(WxMaMessage.class)) {
|
||||
// 操蛋的微信,模板消息推送成功的消息是MsgID,其他消息推送过来是MsgId
|
||||
xstream.aliasField("MsgID", WxMaMessage.class, "msgId");
|
||||
}
|
||||
|
||||
register(clz, xstream);
|
||||
}
|
||||
|
||||
private static Class<?>[] getInnerClasses(Class<?> clz) {
|
||||
Class<?>[] innerClasses = clz.getClasses();
|
||||
if (innerClasses == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Class<?>> result = new ArrayList<>();
|
||||
result.addAll(Arrays.asList(innerClasses));
|
||||
for (Class<?> inner : innerClasses) {
|
||||
Class<?>[] innerClz = getInnerClasses(inner);
|
||||
if (innerClz == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.addAll(Arrays.asList(innerClz));
|
||||
}
|
||||
|
||||
return result.toArray(new Class<?>[0]);
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.test.ApiTestModule;
|
||||
import com.google.inject.Inject;
|
||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import org.testng.annotations.Guice;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* 临时素材接口的测试
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@Test
|
||||
@Guice(modules = ApiTestModule.class)
|
||||
public class WxMaMediaServiceImplTest {
|
||||
@Inject
|
||||
protected WxMaService wxService;
|
||||
|
||||
private String mediaId;
|
||||
|
||||
@Test
|
||||
public void testUploadMedia() throws WxErrorException, IOException {
|
||||
String mediaType = "image";
|
||||
String fileType = "png";
|
||||
String fileName = "tmp.png";
|
||||
try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(fileName)) {
|
||||
WxMediaUploadResult res = this.wxService.getMediaService().uploadMedia(mediaType, fileType, inputStream);
|
||||
assertNotNull(res.getType());
|
||||
assertNotNull(res.getCreatedAt());
|
||||
assertTrue(res.getMediaId() != null || res.getThumbMediaId() != null);
|
||||
this.mediaId = res.getMediaId();
|
||||
System.out.println(res);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(dependsOnMethods = {"testUploadMedia"})
|
||||
public void testGetMedia() throws WxErrorException {
|
||||
File file = this.wxService.getMediaService().getMedia(this.mediaId);
|
||||
assertNotNull(file);
|
||||
System.out.println(file.getAbsolutePath());
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
|
||||
import cn.binarywang.wx.miniapp.test.ApiTestModule;
|
||||
import cn.binarywang.wx.miniapp.test.TestConfig;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import me.chanjar.weixin.common.api.WxConsts;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Guice;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 测试客服相关接口
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@Test
|
||||
@Guice(modules = ApiTestModule.class)
|
||||
public class WxMaMsgServiceImplTest {
|
||||
|
||||
@Inject
|
||||
protected WxMaService wxService;
|
||||
|
||||
public void testSendKefuMpNewsMessage() throws WxErrorException {
|
||||
TestConfig configStorage = (TestConfig) this.wxService
|
||||
.getWxMaConfig();
|
||||
WxMaKefuMessage message = new WxMaKefuMessage();
|
||||
message.setMsgType(WxConsts.CUSTOM_MSG_MPNEWS);
|
||||
message.setToUser(configStorage.getOpenid());
|
||||
|
||||
this.wxService.getMsgService().sendKefuMsg(message);
|
||||
}
|
||||
|
||||
public void testSendKefuMessage() throws WxErrorException {
|
||||
TestConfig config = (TestConfig) this.wxService
|
||||
.getWxMaConfig();
|
||||
WxMaKefuMessage message = new WxMaKefuMessage();
|
||||
message.setMsgType(WxConsts.CUSTOM_MSG_TEXT);
|
||||
message.setToUser(config.getOpenid());
|
||||
message.setContent(
|
||||
"欢迎欢迎,热烈欢迎\n换行测试\n超链接:<a href=\"http://www.baidu.com\">Hello World</a>");
|
||||
|
||||
this.wxService.getMsgService().sendKefuMsg(message);
|
||||
}
|
||||
|
||||
@Test(invocationCount = 5, threadPoolSize = 3)
|
||||
public void testSendTemplateMsg() throws WxErrorException {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
TestConfig config = (TestConfig) this.wxService.getWxMaConfig();
|
||||
|
||||
WxMaTemplateMessage templateMessage = WxMaTemplateMessage.newBuilder()
|
||||
.toUser(config.getOpenid())
|
||||
.formId("FORMID")
|
||||
.page("index")
|
||||
.data(Lists.newArrayList(
|
||||
new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177"),
|
||||
new WxMaTemplateMessage.Data("keyword2", dateFormat.format(new Date()), "#173177"),
|
||||
new WxMaTemplateMessage.Data("keyword3", "粤海喜来登酒店", "#173177"),
|
||||
new WxMaTemplateMessage.Data("keyword4", "广州市天河区天河路208号", "#173177")))
|
||||
.templateId(config.getTemplateId())
|
||||
.emphasisKeyword("keyword1.DATA")
|
||||
.build();
|
||||
|
||||
String msgId = this.wxService.getMsgService().sendTemplateMsg(templateMessage);
|
||||
Assert.assertNotNull(msgId);
|
||||
System.out.println(msgId);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.test.ApiTestModule;
|
||||
import com.google.inject.Inject;
|
||||
import org.testng.annotations.Guice;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@Test
|
||||
@Guice(modules = ApiTestModule.class)
|
||||
public class WxMaQrcodeServiceImplTest {
|
||||
@Inject
|
||||
protected WxMaService wxService;
|
||||
|
||||
@Test
|
||||
public void testCreateQrCode() throws Exception {
|
||||
final File qrCode = this.wxService.getQrcodeService().createQrcode("111", 122);
|
||||
System.out.println(qrCode);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||
import cn.binarywang.wx.miniapp.test.ApiTestModule;
|
||||
import com.google.inject.Inject;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.testng.annotations.Guice;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.assertNotEquals;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@Test
|
||||
@Guice(modules = ApiTestModule.class)
|
||||
public class WxMaServiceImplTest {
|
||||
|
||||
@Inject
|
||||
private WxMaService wxService;
|
||||
|
||||
public void testRefreshAccessToken() throws WxErrorException {
|
||||
WxMaConfig configStorage = this.wxService.getWxMaConfig();
|
||||
String before = configStorage.getAccessToken();
|
||||
this.wxService.getAccessToken(false);
|
||||
|
||||
String after = configStorage.getAccessToken();
|
||||
assertNotEquals(before, after);
|
||||
assertTrue(StringUtils.isNotBlank(after));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package cn.binarywang.wx.miniapp.api.impl;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
|
||||
import cn.binarywang.wx.miniapp.test.ApiTestModule;
|
||||
import com.google.inject.Inject;
|
||||
import org.testng.annotations.Guice;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* 测试用户相关的接口
|
||||
*
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@Test
|
||||
@Guice(modules = ApiTestModule.class)
|
||||
public class WxMaUserServiceImplTest {
|
||||
|
||||
@Inject
|
||||
private WxMaService wxService;
|
||||
|
||||
@Test
|
||||
public void testGetSessionKey() throws Exception {
|
||||
assertNotNull(this.wxService.getUserService().getSessionInfo("aaa"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserInfo() throws Exception {
|
||||
WxMaUserInfo userInfo = this.wxService.getUserService().getUserInfo("tiihtNczf5v6AKRyjwEUhQ==",
|
||||
"CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZMQmRzooG2xrDcvSnxIMXFufNstNGTyaGS9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+3hVbJSRgv+4lGOETKUQz6OYStslQ142dNCuabNPGBzlooOmB231qMM85d2/fV6ChevvXvQP8Hkue1poOFtnEtpyxVLW1zAo6/1Xx1COxFvrc2d7UL/lmHInNlxuacJXwu0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn/Hz7saL8xz+W//FRAUid1OksQaQx4CMs8LOddcQhULW4ucetDf96JcR3g0gfRK4PC7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns/8wR2SiRS7MNACwTyrGvt9ts8p12PKFdlqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYVoKlaRv85IfVunYzO0IKXsyl7JCUjCpoG20f0a04COwfneQAGGwd5oa+T8yO5hzuyDb/XcxxmK01EpqOyuxINew==",
|
||||
"r7BXXKkLb8qrSNn05n0qiA==");
|
||||
assertNotNull(userInfo);
|
||||
System.out.println(userInfo.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckUserInfo() throws Exception {
|
||||
assertTrue(this.wxService.getUserService().checkUserInfo("HyVFkGl5F5OQWJZZaNzBBg==",
|
||||
"{\"nickName\":\"Band\",\"gender\":1,\"language\":\"zh_CN\",\"city\":\"Guangzhou\",\"province\":\"Guangdong\",\"country\":\"CN\",\"avatarUrl\":\"http://wx.qlogo.cn/mmopen/vi_32/1vZvI39NWFQ9XM4LtQpFrQJ1xlgZxx3w7bQxKARol6503Iuswjjn6nIGBiaycAjAtpujxyzYsrztuuICqIM5ibXQ/0\"}",
|
||||
"75e81ceda165f4ffa64f4068af58c64b8f54b88c"));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package cn.binarywang.wx.miniapp.bean;
|
||||
|
||||
import me.chanjar.weixin.common.api.WxConsts;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@Test
|
||||
public class WxMaKefuMessageTest {
|
||||
|
||||
public void testTextReply() {
|
||||
WxMaKefuMessage reply = new WxMaKefuMessage();
|
||||
reply.setToUser("OPENID");
|
||||
reply.setMsgType(WxConsts.CUSTOM_MSG_TEXT);
|
||||
reply.setContent("sfsfdsdf");
|
||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"sfsfdsdf\"}}");
|
||||
}
|
||||
|
||||
public void testTextBuild() {
|
||||
WxMaKefuMessage reply = WxMaKefuMessage.TEXT().toUser("OPENID").content("sfsfdsdf").build();
|
||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"sfsfdsdf\"}}");
|
||||
}
|
||||
|
||||
public void testImageReply() {
|
||||
WxMaKefuMessage reply = new WxMaKefuMessage();
|
||||
reply.setToUser("OPENID");
|
||||
reply.setMsgType(WxConsts.CUSTOM_MSG_IMAGE);
|
||||
reply.setMediaId("MEDIA_ID");
|
||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"image\",\"image\":{\"media_id\":\"MEDIA_ID\"}}");
|
||||
}
|
||||
|
||||
public void testImageBuild() {
|
||||
WxMaKefuMessage reply = WxMaKefuMessage.IMAGE().toUser("OPENID").mediaId("MEDIA_ID").build();
|
||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"image\",\"image\":{\"media_id\":\"MEDIA_ID\"}}");
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
package cn.binarywang.wx.miniapp.bean;
|
||||
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
|
||||
import me.chanjar.weixin.common.api.WxConsts;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@Test
|
||||
public class WxMaMessageTest {
|
||||
|
||||
public void testFromXml() {
|
||||
String xml = "<xml>"
|
||||
+ "<ToUserName><![CDATA[toUser]]></ToUserName>"
|
||||
+ "<FromUserName><![CDATA[fromUser]]></FromUserName> "
|
||||
+ "<CreateTime>1348831860</CreateTime>"
|
||||
+ "<MsgDataFormat><![CDATA[text]]></MsgDataFormat>"
|
||||
+ "<Content><![CDATA[this is a test]]></Content>"
|
||||
+ "<MsgId>1234567890123456</MsgId>"
|
||||
+ "<PicUrl><![CDATA[this is a url]]></PicUrl>"
|
||||
+ "<MediaId><![CDATA[media_id]]></MediaId>"
|
||||
+ "<Format><![CDATA[Format]]></Format>"
|
||||
+ "<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>"
|
||||
+ "<Location_X>23.134521</Location_X>"
|
||||
+ "<Location_Y>113.358803</Location_Y>"
|
||||
+ "<Scale>20</Scale>"
|
||||
+ "<Label><![CDATA[位置信息]]></Label>"
|
||||
+ "<Description><![CDATA[公众平台官网链接]]></Description>"
|
||||
+ "<Url><![CDATA[url]]></Url>"
|
||||
+ "<Title><![CDATA[公众平台官网链接]]></Title>"
|
||||
+ "<Event><![CDATA[subscribe]]></Event>"
|
||||
+ "<EventKey><![CDATA[qrscene_123123]]></EventKey>"
|
||||
+ "<Ticket><![CDATA[TICKET]]></Ticket>"
|
||||
+ "<Latitude>23.137466</Latitude>"
|
||||
+ "<Longitude>113.352425</Longitude>"
|
||||
+ "<Precision>119.385040</Precision>"
|
||||
+ "<ScanCodeInfo>"
|
||||
+ " <ScanType><![CDATA[qrcode]]></ScanType>"
|
||||
+ " <ScanResult><![CDATA[1]]></ScanResult>"
|
||||
+ "</ScanCodeInfo>"
|
||||
+ "<SendPicsInfo>"
|
||||
+ " <Count>1</Count>\n"
|
||||
+ " <PicList>"
|
||||
+ " <item>"
|
||||
+ " <PicMd5Sum><![CDATA[1b5f7c23b5bf75682a53e7b6d163e185]]></PicMd5Sum>"
|
||||
+ " </item>"
|
||||
+ " </PicList>"
|
||||
+ "</SendPicsInfo>"
|
||||
+ "<SendLocationInfo>"
|
||||
+ " <Location_X><![CDATA[23]]></Location_X>\n"
|
||||
+ " <Location_Y><![CDATA[113]]></Location_Y>\n"
|
||||
+ " <Scale><![CDATA[15]]></Scale>\n"
|
||||
+ " <Label><![CDATA[ 广州市海珠区客村艺苑路 106号]]></Label>\n"
|
||||
+ " <Poiname><![CDATA[wo de poi]]></Poiname>\n"
|
||||
+ "</SendLocationInfo>"
|
||||
+ "</xml>";
|
||||
WxMaMessage wxMessage = WxMaMessage.fromXml(xml);
|
||||
assertEquals(wxMessage.getToUser(), "toUser");
|
||||
assertEquals(wxMessage.getFromUser(), "fromUser");
|
||||
assertEquals(wxMessage.getCreateTime(), new Long(1348831860L));
|
||||
assertEquals(wxMessage.getMsgType(), WxConsts.XML_MSG_TEXT);
|
||||
assertEquals(wxMessage.getContent(), "this is a test");
|
||||
assertEquals(wxMessage.getMsgId(), new Long(1234567890123456L));
|
||||
assertEquals(wxMessage.getPicUrl(), "this is a url");
|
||||
assertEquals(wxMessage.getMediaId(), "media_id");
|
||||
assertEquals(wxMessage.getEvent(), "subscribe");
|
||||
}
|
||||
|
||||
public void testFromXml2() {
|
||||
|
||||
String xml = "<xml>"
|
||||
+ "<ToUserName><![CDATA[toUser]]></ToUserName>"
|
||||
+ "<FromUserName><![CDATA[fromUser]]></FromUserName> "
|
||||
+ "<CreateTime>1348831860</CreateTime>"
|
||||
+ "<MsgDataFormat><![CDATA[text]]></MsgDataFormat>"
|
||||
+ "<Content><![CDATA[this is a test]]></Content>"
|
||||
+ "<MsgID>1234567890123456</MsgID>"
|
||||
+ "<PicUrl><![CDATA[this is a url]]></PicUrl>"
|
||||
+ "<MediaId><![CDATA[media_id]]></MediaId>"
|
||||
+ "<Format><![CDATA[Format]]></Format>"
|
||||
+ "<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>"
|
||||
+ "<Location_X>23.134521</Location_X>"
|
||||
+ "<Location_Y>113.358803</Location_Y>"
|
||||
+ "<Scale>20</Scale>"
|
||||
+ "<Label><![CDATA[位置信息]]></Label>"
|
||||
+ "<Description><![CDATA[公众平台官网链接]]></Description>"
|
||||
+ "<Url><![CDATA[url]]></Url>"
|
||||
+ "<Title><![CDATA[公众平台官网链接]]></Title>"
|
||||
+ "<Event><![CDATA[subscribe]]></Event>"
|
||||
+ "<EventKey><![CDATA[qrscene_123123]]></EventKey>"
|
||||
+ "<Ticket><![CDATA[TICKET]]></Ticket>"
|
||||
+ "<Latitude>23.137466</Latitude>"
|
||||
+ "<Longitude>113.352425</Longitude>"
|
||||
+ "<Precision>119.385040</Precision>"
|
||||
+ "<ScanCodeInfo>"
|
||||
+ " <ScanType><![CDATA[qrcode]]></ScanType>"
|
||||
+ " <ScanResult><![CDATA[1]]></ScanResult>"
|
||||
+ "</ScanCodeInfo>"
|
||||
+ "<SendPicsInfo>"
|
||||
+ " <Count>1</Count>\n"
|
||||
+ " <PicList>"
|
||||
+ " <item>"
|
||||
+ " <PicMd5Sum><![CDATA[1b5f7c23b5bf75682a53e7b6d163e185]]></PicMd5Sum>"
|
||||
+ " </item>"
|
||||
+ " </PicList>"
|
||||
+ "</SendPicsInfo>"
|
||||
+ "<SendLocationInfo>"
|
||||
+ " <Location_X><![CDATA[23]]></Location_X>\n"
|
||||
+ " <Location_Y><![CDATA[113]]></Location_Y>\n"
|
||||
+ " <Scale><![CDATA[15]]></Scale>\n"
|
||||
+ " <Label><![CDATA[ 广州市海珠区客村艺苑路 106号]]></Label>\n"
|
||||
+ " <Poiname><![CDATA[wo de poi]]></Poiname>\n"
|
||||
+ "</SendLocationInfo>"
|
||||
+ "</xml>";
|
||||
WxMaMessage wxMessage = WxMaMessage.fromXml(xml);
|
||||
assertEquals(wxMessage.getToUser(), "toUser");
|
||||
assertEquals(wxMessage.getFromUser(), "fromUser");
|
||||
assertEquals(wxMessage.getCreateTime(), new Integer(1348831860));
|
||||
assertEquals(wxMessage.getMsgType(), WxConsts.XML_MSG_TEXT);
|
||||
assertEquals(wxMessage.getContent(), "this is a test");
|
||||
assertEquals(wxMessage.getMsgId(), new Long(1234567890123456L));
|
||||
assertEquals(wxMessage.getPicUrl(), "this is a url");
|
||||
assertEquals(wxMessage.getMediaId(), "media_id");
|
||||
assertEquals(wxMessage.getEvent(), "subscribe");
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package cn.binarywang.wx.miniapp.bean;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.AssertJUnit.assertEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaTemplateMessageTest {
|
||||
@Test
|
||||
public void testToJson() throws Exception {
|
||||
WxMaTemplateMessage tm = WxMaTemplateMessage.newBuilder()
|
||||
.toUser("OPENID")
|
||||
//.color("aaaaa")
|
||||
.formId("FORMID")
|
||||
.page("index")
|
||||
.data(Lists.newArrayList(
|
||||
new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177"),
|
||||
new WxMaTemplateMessage.Data("keyword2", "2015年01月05日12:30", "#173177"),
|
||||
new WxMaTemplateMessage.Data("keyword3", "粤海喜来登酒店", "#173177"),
|
||||
new WxMaTemplateMessage.Data("keyword4", "广州市天河区天河路208号", "#173177")))
|
||||
.templateId("TEMPLATE_ID")
|
||||
.emphasisKeyword("keyword1.DATA")
|
||||
.build();
|
||||
|
||||
assertEquals(tm.toJson(), "{\"touser\":\"OPENID\",\"template_id\":\"TEMPLATE_ID\",\"page\":\"index\",\"form_id\":\"FORMID\",\"emphasis_keyword\":\"keyword1.DATA\",\"data\":{\"keyword1\":{\"value\":\"339208499\",\"color\":\"#173177\"},\"keyword2\":{\"value\":\"2015年01月05日12:30\",\"color\":\"#173177\"},\"keyword3\":{\"value\":\"粤海喜来登酒店\",\"color\":\"#173177\"},\"keyword4\":{\"value\":\"广州市天河区天河路208号\",\"color\":\"#173177\"}}}");
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,144 @@
|
||||
package cn.binarywang.wx.miniapp.demo;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
|
||||
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||
import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
|
||||
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
|
||||
import cn.binarywang.wx.miniapp.test.TestConfig;
|
||||
import com.google.common.collect.Lists;
|
||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
|
||||
import me.chanjar.weixin.common.exception.WxErrorException;
|
||||
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaDemoServer {
|
||||
|
||||
private static final WxMaMessageHandler logHandler = new WxMaMessageHandler() {
|
||||
@Override
|
||||
public void handle(WxMaMessage wxMessage, Map<String, Object> context,
|
||||
WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
|
||||
System.out.println("收到消息:" + wxMessage.toString());
|
||||
service.getMsgService().sendKefuMsg(WxMaKefuMessage.TEXT().content("收到信息为:" + wxMessage.toJson())
|
||||
.toUser(wxMessage.getFromUser()).build());
|
||||
}
|
||||
};
|
||||
private static final WxMaMessageHandler textHandler = new WxMaMessageHandler() {
|
||||
@Override
|
||||
public void handle(WxMaMessage wxMessage, Map<String, Object> context,
|
||||
WxMaService service, WxSessionManager sessionManager)
|
||||
throws WxErrorException {
|
||||
service.getMsgService().sendKefuMsg(WxMaKefuMessage.TEXT().content("回复文本消息")
|
||||
.toUser(wxMessage.getFromUser()).build());
|
||||
}
|
||||
|
||||
};
|
||||
private static final WxMaMessageHandler picHandler = new WxMaMessageHandler() {
|
||||
@Override
|
||||
public void handle(WxMaMessage wxMessage, Map<String, Object> context,
|
||||
WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
|
||||
try {
|
||||
WxMediaUploadResult uploadResult = service.getMediaService()
|
||||
.uploadMedia("image", "png",
|
||||
ClassLoader.getSystemResourceAsStream("tmp.png"));
|
||||
service.getMsgService().sendKefuMsg(
|
||||
WxMaKefuMessage
|
||||
.IMAGE()
|
||||
.mediaId(uploadResult.getMediaId())
|
||||
.toUser(wxMessage.getFromUser())
|
||||
.build());
|
||||
} catch (WxErrorException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
private static final WxMaMessageHandler qrcodeHandler = new WxMaMessageHandler() {
|
||||
@Override
|
||||
public void handle(WxMaMessage wxMessage, Map<String, Object> context,
|
||||
WxMaService service, WxSessionManager sessionManager) throws WxErrorException {
|
||||
try {
|
||||
final File file = service.getQrcodeService().createQrcode("123", 430);
|
||||
WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file);
|
||||
service.getMsgService().sendKefuMsg(
|
||||
WxMaKefuMessage
|
||||
.IMAGE()
|
||||
.mediaId(uploadResult.getMediaId())
|
||||
.toUser(wxMessage.getFromUser())
|
||||
.build());
|
||||
} catch (WxErrorException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static final WxMaMessageHandler templateMsgHandler = new WxMaMessageHandler() {
|
||||
@Override
|
||||
public void handle(WxMaMessage wxMessage, Map<String, Object> context,
|
||||
WxMaService service, WxSessionManager sessionManager)
|
||||
throws WxErrorException {
|
||||
service.getMsgService().sendTemplateMsg(WxMaTemplateMessage.newBuilder()
|
||||
.templateId(templateId).data(Lists.newArrayList(
|
||||
new WxMaTemplateMessage.Data("keyword1", "339208499", "#173177")))
|
||||
.toUser(wxMessage.getFromUser())
|
||||
.formId("自己替换可用的formid")
|
||||
.build());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private static WxMaConfig config;
|
||||
private static WxMaService service;
|
||||
private static WxMaMessageRouter router;
|
||||
private static String templateId;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
init();
|
||||
|
||||
Server server = new Server(8080);
|
||||
|
||||
ServletHandler servletHandler = new ServletHandler();
|
||||
server.setHandler(servletHandler);
|
||||
|
||||
ServletHolder endpointServletHolder = new ServletHolder(new WxMaPortalServlet(config, service, router));
|
||||
servletHandler.addServletWithMapping(endpointServletHolder, "/*");
|
||||
|
||||
server.start();
|
||||
server.join();
|
||||
}
|
||||
|
||||
private static void init() {
|
||||
try (InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml")) {
|
||||
TestConfig config = TestConfig.fromXml(is1);
|
||||
config.setAccessTokenLock(new ReentrantLock());
|
||||
templateId = config.getTemplateId();
|
||||
|
||||
WxMaDemoServer.config = config;
|
||||
service = new WxMaServiceImpl();
|
||||
service.setWxMaConfig(config);
|
||||
|
||||
router = new WxMaMessageRouter(service);
|
||||
|
||||
router.rule().handler(logHandler).next()
|
||||
.rule().async(false).content("模板").handler(templateMsgHandler).end()
|
||||
.rule().async(false).content("文本").handler(textHandler).end()
|
||||
.rule().async(false).content("图片").handler(picHandler).end()
|
||||
.rule().async(false).content("二维码").handler(qrcodeHandler).end();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
package cn.binarywang.wx.miniapp.demo;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaMessage;
|
||||
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||
import cn.binarywang.wx.miniapp.constant.WxMaConstants;
|
||||
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class WxMaPortalServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private WxMaConfig wxMaConfig;
|
||||
private WxMaService wxMaService;
|
||||
private WxMaMessageRouter wxMaMessageRouter;
|
||||
|
||||
WxMaPortalServlet(WxMaConfig wxMaConfig, WxMaService wxMaService,
|
||||
WxMaMessageRouter wxMaMessageRouter) {
|
||||
this.wxMaConfig = wxMaConfig;
|
||||
this.wxMaService = wxMaService;
|
||||
this.wxMaMessageRouter = wxMaMessageRouter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException {
|
||||
response.setContentType("text/html;charset=utf-8");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
|
||||
String signature = request.getParameter("signature");
|
||||
String nonce = request.getParameter("nonce");
|
||||
String timestamp = request.getParameter("timestamp");
|
||||
|
||||
if (!this.wxMaService.checkSignature(timestamp, nonce, signature)) {
|
||||
// 消息签名不正确,说明不是公众平台发过来的消息
|
||||
response.getWriter().println("非法请求");
|
||||
return;
|
||||
}
|
||||
|
||||
String echoStr = request.getParameter("echostr");
|
||||
if (StringUtils.isNotBlank(echoStr)) {
|
||||
// 说明是一个仅仅用来验证的请求,回显echostr
|
||||
response.getWriter().println(echoStr);
|
||||
return;
|
||||
}
|
||||
|
||||
String encryptType = request.getParameter("encrypt_type");
|
||||
final boolean isJson = Objects.equals(this.wxMaConfig.getMsgDataFormat(), WxMaConstants.MsgDataFormat.JSON);
|
||||
if (StringUtils.isBlank(encryptType)) {
|
||||
// 明文传输的消息
|
||||
WxMaMessage inMessage;
|
||||
if (isJson) {
|
||||
inMessage = WxMaMessage.fromJson(IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8));
|
||||
} else {//xml
|
||||
inMessage = WxMaMessage.fromXml(request.getInputStream());
|
||||
}
|
||||
|
||||
this.wxMaMessageRouter.route(inMessage);
|
||||
response.getWriter().write("success");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("aes".equals(encryptType)) {
|
||||
// 是aes加密的消息
|
||||
String msgSignature = request.getParameter("msg_signature");
|
||||
WxMaMessage inMessage;
|
||||
if (isJson) {
|
||||
inMessage = WxMaMessage.fromEncryptedJson(request.getInputStream(), this.wxMaConfig);
|
||||
} else {//xml
|
||||
inMessage = WxMaMessage.fromEncryptedXml(request.getInputStream(), this.wxMaConfig, timestamp, nonce, msgSignature);
|
||||
}
|
||||
|
||||
this.wxMaMessageRouter.route(inMessage);
|
||||
response.getWriter().write("success");
|
||||
return;
|
||||
}
|
||||
|
||||
response.getWriter().println("不可识别的加密类型");
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package cn.binarywang.wx.miniapp.test;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.config.WxMaConfig;
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Module;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
public class ApiTestModule implements Module {
|
||||
|
||||
@Override
|
||||
public void configure(Binder binder) {
|
||||
try (InputStream inputStream = ClassLoader.getSystemResourceAsStream("test-config.xml")) {
|
||||
TestConfig config = TestConfig.fromXml(inputStream);
|
||||
config.setAccessTokenLock(new ReentrantLock());
|
||||
|
||||
WxMaService wxService = new cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl();
|
||||
wxService.setWxMaConfig(config);
|
||||
|
||||
binder.bind(WxMaService.class).toInstance(wxService);
|
||||
binder.bind(WxMaConfig.class).toInstance(config);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package cn.binarywang.wx.miniapp.test;
|
||||
|
||||
import cn.binarywang.wx.miniapp.config.WxMaInMemoryConfig;
|
||||
import com.thoughtworks.xstream.XStream;
|
||||
import com.thoughtworks.xstream.annotations.XStreamAlias;
|
||||
import me.chanjar.weixin.common.util.xml.XStreamInitializer;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@XStreamAlias("xml")
|
||||
public class TestConfig extends WxMaInMemoryConfig {
|
||||
|
||||
private String openid;
|
||||
private String kfAccount;
|
||||
private String templateId;
|
||||
|
||||
public static TestConfig fromXml(InputStream is) {
|
||||
XStream xstream = XStreamInitializer.getInstance();
|
||||
xstream.processAnnotations(TestConfig.class);
|
||||
return (TestConfig) xstream.fromXML(is);
|
||||
}
|
||||
|
||||
public String getOpenid() {
|
||||
return this.openid;
|
||||
}
|
||||
|
||||
public void setOpenid(String openid) {
|
||||
this.openid = openid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ToStringBuilder.reflectionToString(this);
|
||||
}
|
||||
|
||||
public String getKfAccount() {
|
||||
return this.kfAccount;
|
||||
}
|
||||
|
||||
public void setKfAccount(String kfAccount) {
|
||||
this.kfAccount = kfAccount;
|
||||
}
|
||||
|
||||
public String getTemplateId() {
|
||||
return this.templateId;
|
||||
}
|
||||
|
||||
public void setTemplateId(String templateId) {
|
||||
this.templateId = templateId;
|
||||
}
|
||||
|
||||
public void setAccessTokenLock(Lock lock) {
|
||||
super.accessTokenLock = lock;
|
||||
}
|
||||
|
||||
}
|
14
weixin-java-miniapp/src/test/resources/logback-test.xml
Normal file
14
weixin-java-miniapp/src/test/resources/logback-test.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %replace(%caller{1}){'Caller', ''} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="cn.binarywang.wx.miniapp" level="debug"/>
|
||||
</configuration>
|
@@ -0,0 +1,11 @@
|
||||
<xml>
|
||||
<msgDataFormat>JSON或者XML</msgDataFormat>
|
||||
<appid>appid</appid>
|
||||
<secret>secret</secret>
|
||||
<token>Token</token>
|
||||
<aesKey>EncodingAESKey</aesKey>
|
||||
<accessToken>可以不填写</accessToken>
|
||||
<expiresTime>可以不填写</expiresTime>
|
||||
<openid>某个用户的openId</openid>
|
||||
<templateId>模版消息的模版ID</templateId>
|
||||
</xml>
|
BIN
weixin-java-miniapp/src/test/resources/tmp.png
Normal file
BIN
weixin-java-miniapp/src/test/resources/tmp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Reference in New Issue
Block a user