mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2026-03-10 00:13:40 +08:00
#193 增加小程序模块weixin-java-miniapp,支持小程序后台开发
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user