#193 增加小程序模块weixin-java-miniapp,支持小程序后台开发

This commit is contained in:
Binary Wang
2017-06-16 00:02:45 +08:00
parent 39e79a2c00
commit 1bd9b15c98
50 changed files with 3641 additions and 0 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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());
}
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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";//图片消息
}
}

View File

@@ -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;
}

View File

@@ -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代表OKfalse代表不OK
*/
boolean intercept(WxMaMessage wxMessage,
Map<String, Object> context,
WxMaService wxMaService,
WxSessionManager sessionManager) throws WxErrorException;
}

View File

@@ -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);
}

View File

@@ -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();
}
}
}

View File

@@ -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 代表继续执行别的routerfalse 代表停止执行别的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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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]);
}
}