| @@ -0,0 +1,169 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.error.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; | |||
| import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; | |||
| import me.chanjar.weixin.cp.bean.WxCpTpCorp; | |||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
| import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; | |||
| /** | |||
| * 微信第三方应用API的Service | |||
| * @author zhenjun cai | |||
| */ | |||
| public interface WxCpTpService { | |||
| String JSCODE_TO_SESSION_URL = "https://qyapi.weixin.qq.com/cgi-bin/service/miniprogram/jscode2session"; | |||
| String GET_CORP_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/service/get_corp_token"; | |||
| String GET_PERMANENT_CODE = "https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code"; | |||
| /** | |||
| * <pre> | |||
| * 验证推送过来的消息的正确性 | |||
| * 详情请见: https://work.weixin.qq.com/api/doc#90000/90139/90968/消息体签名校验 | |||
| * </pre> | |||
| * | |||
| * @param msgSignature 消息签名 | |||
| * @param timestamp 时间戳 | |||
| * @param nonce 随机数 | |||
| * @param data 微信传输过来的数据,有可能是echoStr,有可能是xml消息 | |||
| */ | |||
| boolean checkSignature(String msgSignature, String timestamp, String nonce, String data); | |||
| /** | |||
| * 获取suite_access_token, 不强制刷新suite_access_token | |||
| * | |||
| * @see #getSuiteAccessToken(boolean) | |||
| */ | |||
| String getSuiteAccessToken() throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获取suite_access_token,本方法线程安全 | |||
| * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限 | |||
| * 另:本service的所有方法都会在suite_access_token过期是调用此方法 | |||
| * 程序员在非必要情况下尽量不要主动调用此方法 | |||
| * 详情请见: https://work.weixin.qq.com/api/doc#90001/90143/90600 | |||
| * </pre> | |||
| * | |||
| * @param forceRefresh 强制刷新 | |||
| */ | |||
| String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException; | |||
| /** | |||
| * 获得suite_ticket,不强制刷新suite_ticket | |||
| * | |||
| * @see #getSuiteTicket(boolean) | |||
| */ | |||
| String getSuiteTicket() throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获得suite_ticket | |||
| * 由于suite_ticket是微信服务器定时推送(每10分钟),不能主动获取,如果碰到过期只能抛异常 | |||
| * | |||
| * 详情请见:https://work.weixin.qq.com/api/doc#90001/90143/90628 | |||
| * </pre> | |||
| * | |||
| * @param forceRefresh 强制刷新 | |||
| */ | |||
| String getSuiteTicket(boolean forceRefresh) throws WxErrorException; | |||
| /** | |||
| * 小程序登录凭证校验 | |||
| * | |||
| * @param jsCode 登录时获取的 code | |||
| */ | |||
| WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException; | |||
| /** | |||
| * 获取企业凭证 | |||
| * @param authCorpid 授权方corpid | |||
| * @param permanentCode 永久授权码,通过get_permanent_code获取 | |||
| */ | |||
| WxAccessToken getCorpToken(String authCorpid, String permanentCode) throws WxErrorException; | |||
| /** | |||
| * 获取企业永久授权码 | |||
| * @param authCode | |||
| * @return | |||
| */ | |||
| WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException; | |||
| /** | |||
| * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求 | |||
| * | |||
| * @param url 接口地址 | |||
| * @param queryParam 请求参数 | |||
| */ | |||
| String get(String url, String queryParam) throws WxErrorException; | |||
| /** | |||
| * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求 | |||
| * | |||
| * @param url 接口地址 | |||
| * @param postData 请求body字符串 | |||
| */ | |||
| String post(String url, String postData) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * Service没有实现某个API的时候,可以用这个, | |||
| * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。 | |||
| * 可以参考,{@link MediaUploadRequestExecutor}的实现方法 | |||
| * </pre> | |||
| * | |||
| * @param executor 执行器 | |||
| * @param uri 请求地址 | |||
| * @param data 参数 | |||
| * @param <T> 请求值类型 | |||
| * @param <E> 返回值类型 | |||
| */ | |||
| <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试 | |||
| * 默认:1000ms | |||
| * </pre> | |||
| * | |||
| * @param retrySleepMillis 重试休息时间 | |||
| */ | |||
| void setRetrySleepMillis(int retrySleepMillis); | |||
| /** | |||
| * <pre> | |||
| * 设置当微信系统响应系统繁忙时,最大重试次数 | |||
| * 默认:5次 | |||
| * </pre> | |||
| * | |||
| * @param maxRetryTimes 最大重试次数 | |||
| */ | |||
| void setMaxRetryTimes(int maxRetryTimes); | |||
| /** | |||
| * 初始化http请求对象 | |||
| */ | |||
| void initHttp(); | |||
| /** | |||
| * 获取WxMpConfigStorage 对象 | |||
| * | |||
| * @return WxMpConfigStorage | |||
| */ | |||
| WxCpTpConfigStorage getWxCpTpConfigStorage(); | |||
| /** | |||
| * 注入 {@link WxCpTpConfigStorage} 的实现 | |||
| * | |||
| * @param wxConfigProvider 配置对象 | |||
| */ | |||
| void setWxCpTpConfigStorage(WxCpTpConfigStorage wxConfigProvider); | |||
| /** | |||
| * http请求对象 | |||
| */ | |||
| RequestHttp<?, ?> getRequestHttp(); | |||
| } | |||
| @@ -0,0 +1,244 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import com.google.common.base.Joiner; | |||
| import com.google.gson.JsonObject; | |||
| import com.google.gson.JsonParser; | |||
| import me.chanjar.weixin.common.WxType; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.DataUtils; | |||
| import me.chanjar.weixin.common.util.crypto.SHA1; | |||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||
| import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||
| import me.chanjar.weixin.cp.api.WxCpService; | |||
| import me.chanjar.weixin.cp.api.WxCpTpService; | |||
| import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; | |||
| import me.chanjar.weixin.cp.bean.WxCpTpCorp; | |||
| import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; | |||
| /** | |||
| * @author zhenjun cai | |||
| */ | |||
| public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, RequestHttp<H, P> { | |||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | |||
| /** | |||
| * 全局的是否正在刷新access token的锁 | |||
| */ | |||
| protected final Object globalSuiteAccessTokenRefreshLock = new Object(); | |||
| /** | |||
| * 全局的是否正在刷新jsapi_ticket的锁 | |||
| */ | |||
| protected final Object globalSuiteTicketRefreshLock = new Object(); | |||
| protected WxCpTpConfigStorage configStorage; | |||
| /** | |||
| * 临时文件目录 | |||
| */ | |||
| private File tmpDirFile; | |||
| private int retrySleepMillis = 1000; | |||
| private int maxRetryTimes = 5; | |||
| @Override | |||
| public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | |||
| try { | |||
| return SHA1.gen(this.configStorage.getToken(), timestamp, nonce, data) | |||
| .equals(msgSignature); | |||
| } catch (Exception e) { | |||
| this.log.error("Checking signature failed, and the reason is :" + e.getMessage()); | |||
| return false; | |||
| } | |||
| } | |||
| @Override | |||
| public String getSuiteAccessToken() throws WxErrorException { | |||
| return getSuiteAccessToken(false); | |||
| } | |||
| @Override | |||
| public String getSuiteTicket() throws WxErrorException { | |||
| return getSuiteTicket(false); | |||
| } | |||
| @Override | |||
| public String getSuiteTicket(boolean forceRefresh) throws WxErrorException { | |||
| // suite ticket由微信服务器推送,不能强制刷新 | |||
| // if (forceRefresh) { | |||
| // this.configStorage.expireSuiteTicket(); | |||
| // } | |||
| if (this.configStorage.isSuiteTicketExpired()) { | |||
| // 本地suite ticket 不存在或者过期 | |||
| WxError wxError = WxError.fromJson("{\"errcode\":40085, \"errmsg\":\"invaild suite ticket\"}", WxType.CP); | |||
| throw new WxErrorException(wxError); | |||
| } | |||
| return this.configStorage.getSuiteTicket(); | |||
| } | |||
| @Override | |||
| public WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException { | |||
| Map<String, String> params = new HashMap<>(2); | |||
| params.put("js_code", jsCode); | |||
| params.put("grant_type", "authorization_code"); | |||
| String result = this.get(JSCODE_TO_SESSION_URL, Joiner.on("&").withKeyValueSeparator("=").join(params)); | |||
| return WxCpMaJsCode2SessionResult.fromJson(result); | |||
| } | |||
| @Override | |||
| public WxAccessToken getCorpToken(String authCorpid, String permanentCode) throws WxErrorException { | |||
| JsonObject jsonObject = new JsonObject(); | |||
| jsonObject.addProperty("auth_corpid", authCorpid); | |||
| jsonObject.addProperty("permanent_code", permanentCode); | |||
| String result = post(GET_CORP_TOKEN, jsonObject.toString()); | |||
| return WxAccessToken.fromJson(result); | |||
| } | |||
| @Override | |||
| public WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException { | |||
| JsonObject jsonObject = new JsonObject(); | |||
| jsonObject.addProperty("auth_code", authCode); | |||
| String result = post(GET_PERMANENT_CODE, jsonObject.toString()); | |||
| jsonObject = new JsonParser().parse(result).getAsJsonObject(); | |||
| WxCpTpCorp wxCpTpCorp = WxCpTpCorp.fromJson(jsonObject.get("auth_corp_info").getAsString()); | |||
| wxCpTpCorp.setPermanentCode(jsonObject.get("permanent_code").getAsString()); | |||
| return wxCpTpCorp; | |||
| } | |||
| @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过期时才去刷新,然后重新执行请求,而不是全局定时请求. | |||
| */ | |||
| @Override | |||
| 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("重试达到最大次数【{}】", this.maxRetryTimes); | |||
| //最后一次重试失败后,直接抛出异常,不再等待 | |||
| throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
| } | |||
| WxError error = e.getError(); | |||
| /* | |||
| * -1 系统繁忙, 1000ms后重试 | |||
| */ | |||
| if (error.getErrorCode() == -1) { | |||
| int sleepMillis = this.retrySleepMillis * (1 << retryTimes); | |||
| try { | |||
| this.log.debug("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); | |||
| Thread.sleep(sleepMillis); | |||
| } catch (InterruptedException e1) { | |||
| Thread.currentThread().interrupt(); | |||
| } | |||
| } else { | |||
| throw e; | |||
| } | |||
| } | |||
| } while (retryTimes++ < this.maxRetryTimes); | |||
| this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes); | |||
| throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
| } | |||
| protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
| E dataForLog = DataUtils.handleDataWithSecret(data); | |||
| if (uri.contains("suite_access_token=")) { | |||
| throw new IllegalArgumentException("uri参数中不允许有suite_access_token: " + uri); | |||
| } | |||
| String suiteAccessToken = getSuiteAccessToken(false); | |||
| String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "suite_access_token=" + suiteAccessToken; | |||
| try { | |||
| T result = executor.execute(uriWithAccessToken, data); | |||
| this.log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, result); | |||
| return result; | |||
| } catch (WxErrorException e) { | |||
| WxError error = e.getError(); | |||
| /* | |||
| * 发生以下情况时尝试刷新suite_access_token | |||
| * 42009 suite_access_token已过期 | |||
| */ | |||
| if (error.getErrorCode() == 42009) { | |||
| // 强制设置wxCpTpConfigStorage它的suite access token过期了,这样在下一次请求里就会刷新suite access token | |||
| this.configStorage.expireSuiteAccessToken(); | |||
| return execute(executor, uri, data); | |||
| } | |||
| if (error.getErrorCode() != 0) { | |||
| this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error); | |||
| throw new WxErrorException(error, e); | |||
| } | |||
| return null; | |||
| } catch (IOException e) { | |||
| this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| @Override | |||
| public void setWxCpTpConfigStorage(WxCpTpConfigStorage wxConfigProvider) { | |||
| this.configStorage = wxConfigProvider; | |||
| this.initHttp(); | |||
| } | |||
| @Override | |||
| public void setRetrySleepMillis(int retrySleepMillis) { | |||
| this.retrySleepMillis = retrySleepMillis; | |||
| } | |||
| @Override | |||
| public void setMaxRetryTimes(int maxRetryTimes) { | |||
| this.maxRetryTimes = maxRetryTimes; | |||
| } | |||
| public File getTmpDirFile() { | |||
| return this.tmpDirFile; | |||
| } | |||
| public void setTmpDirFile(File tmpDirFile) { | |||
| this.tmpDirFile = tmpDirFile; | |||
| } | |||
| @Override | |||
| public RequestHttp<?, ?> getRequestHttp() { | |||
| return this; | |||
| } | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.cp.api.WxCpTpService; | |||
| /** | |||
| * <pre> | |||
| * 默认接口实现类,使用apache httpclient实现,配合第三方应用service使用 | |||
| * Created by zhenjun cai. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public class WxCpServiceOnTpImpl extends WxCpServiceApacheHttpClientImpl { | |||
| //第三方应用service | |||
| WxCpTpService wxCpTpService; | |||
| public void setWxCpTpService(WxCpTpService wxCpTpService) { | |||
| this.wxCpTpService = wxCpTpService; | |||
| } | |||
| @Override | |||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||
| if (!this.configStorage.isAccessTokenExpired() && !forceRefresh) { | |||
| return this.configStorage.getAccessToken(); | |||
| } | |||
| //access token通过第三方应用service获取 | |||
| //corpSecret对应企业永久授权码 | |||
| WxAccessToken accessToken = wxCpTpService.getCorpToken(this.configStorage.getCorpId(), this.configStorage.getCorpSecret()); | |||
| this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
| return this.configStorage.getAccessToken(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,114 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| import java.io.IOException; | |||
| import org.apache.http.Consts; | |||
| 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.StringEntity; | |||
| import org.apache.http.impl.client.BasicResponseHandler; | |||
| import org.apache.http.impl.client.CloseableHttpClient; | |||
| import com.google.gson.JsonObject; | |||
| import com.google.gson.JsonParser; | |||
| import me.chanjar.weixin.common.WxType; | |||
| import me.chanjar.weixin.common.error.WxError; | |||
| import me.chanjar.weixin.common.error.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.HttpType; | |||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
| import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | |||
| import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; | |||
| public class WxCpTpServiceApacheHttpClientImpl extends BaseWxCpTpServiceImpl<CloseableHttpClient, HttpHost> { | |||
| protected CloseableHttpClient httpClient; | |||
| protected HttpHost httpProxy; | |||
| @Override | |||
| public CloseableHttpClient getRequestHttpClient() { | |||
| return httpClient; | |||
| } | |||
| @Override | |||
| public HttpHost getRequestHttpProxy() { | |||
| return httpProxy; | |||
| } | |||
| @Override | |||
| public HttpType getRequestType() { | |||
| return HttpType.APACHE_HTTP; | |||
| } | |||
| @Override | |||
| public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException { | |||
| if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) { | |||
| return this.configStorage.getSuiteAccessToken(); | |||
| } | |||
| synchronized (this.globalSuiteAccessTokenRefreshLock) { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token"; | |||
| try { | |||
| HttpPost httpPost = new HttpPost(url); | |||
| if (this.httpProxy != null) { | |||
| RequestConfig config = RequestConfig.custom() | |||
| .setProxy(this.httpProxy).build(); | |||
| httpPost.setConfig(config); | |||
| } | |||
| JsonObject jsonObject = new JsonObject(); | |||
| jsonObject.addProperty("suite_id", this.configStorage.getSuiteId()); | |||
| jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret()); | |||
| jsonObject.addProperty("suite_ticket", this.getSuiteTicket()); | |||
| StringEntity entity = new StringEntity(jsonObject.toString(), Consts.UTF_8); | |||
| httpPost.setEntity(entity); | |||
| String resultContent; | |||
| try (CloseableHttpClient httpclient = getRequestHttpClient(); | |||
| CloseableHttpResponse response = httpclient.execute(httpPost)) { | |||
| resultContent = new BasicResponseHandler().handleResponse(response); | |||
| } finally { | |||
| httpPost.releaseConnection(); | |||
| } | |||
| WxError error = WxError.fromJson(resultContent, WxType.CP); | |||
| if (error.getErrorCode() != 0) { | |||
| throw new WxErrorException(error); | |||
| } | |||
| jsonObject = new JsonParser().parse(resultContent).getAsJsonObject(); | |||
| String suiteAccussToken = jsonObject.get("suite_access_token").getAsString(); | |||
| Integer expiresIn = jsonObject.get("expires_in").getAsInt(); | |||
| this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn); | |||
| } catch (IOException e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| return this.configStorage.getSuiteAccessToken(); | |||
| } | |||
| @Override | |||
| public void initHttp() { | |||
| ApacheHttpClientBuilder apacheHttpClientBuilder = this.configStorage | |||
| .getApacheHttpClientBuilder(); | |||
| if (null == apacheHttpClientBuilder) { | |||
| apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get(); | |||
| } | |||
| apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost()) | |||
| .httpProxyPort(this.configStorage.getHttpProxyPort()) | |||
| .httpProxyUsername(this.configStorage.getHttpProxyUsername()) | |||
| .httpProxyPassword(this.configStorage.getHttpProxyPassword()); | |||
| if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) { | |||
| this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort()); | |||
| } | |||
| this.httpClient = apacheHttpClientBuilder.build(); | |||
| } | |||
| @Override | |||
| public WxCpTpConfigStorage getWxCpTpConfigStorage() { | |||
| return this.configStorage; | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| package me.chanjar.weixin.cp.api.impl; | |||
| /** | |||
| * <pre> | |||
| * 默认接口实现类,使用apache httpclient实现 | |||
| * Created by zhenjun cai. | |||
| * </pre> | |||
| * | |||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
| */ | |||
| public class WxCpTpServiceImpl extends WxCpTpServiceApacheHttpClientImpl { | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| package me.chanjar.weixin.cp.bean; | |||
| import java.io.Serializable; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import lombok.Data; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| /** | |||
| * 微信部门. | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| @Data | |||
| public class WxCpTpCorp implements Serializable { | |||
| private static final long serialVersionUID = -5028321625140879571L; | |||
| @SerializedName("corpid") | |||
| private String corpId; | |||
| @SerializedName("corp_name") | |||
| private String corpName; | |||
| @SerializedName("corp_full_name") | |||
| private String corpFullName; | |||
| @SerializedName("corp_type") | |||
| private String corpType; | |||
| @SerializedName("corp_square_logo_url") | |||
| private String corpSquareLogoUrl; | |||
| @SerializedName("corp_user_max") | |||
| private String corpUserMax; | |||
| @SerializedName("permanent_code") | |||
| private String permanentCode; | |||
| public static WxCpTpCorp fromJson(String json) { | |||
| return WxCpGsonBuilder.create().fromJson(json, WxCpTpCorp.class); | |||
| } | |||
| public String toJson() { | |||
| return WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| } | |||
| @@ -0,0 +1,68 @@ | |||
| package me.chanjar.weixin.cp.bean; | |||
| import java.io.Serializable; | |||
| import java.util.Map; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| import lombok.Data; | |||
| import lombok.extern.slf4j.Slf4j; | |||
| import me.chanjar.weixin.common.util.XmlUtils; | |||
| import me.chanjar.weixin.common.util.crypto.WxCryptUtil; | |||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.ImageBuilder; | |||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.NewsBuilder; | |||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.TextBuilder; | |||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.VideoBuilder; | |||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.VoiceBuilder; | |||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
| import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; | |||
| import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | |||
| import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | |||
| /** | |||
| * 回调推送的message | |||
| * https://work.weixin.qq.com/api/doc#90001/90143/90612 | |||
| * | |||
| * @author zhenjun cai | |||
| */ | |||
| @XStreamAlias("xml") | |||
| @Slf4j | |||
| @Data | |||
| public class WxCpTpXmlMessage implements Serializable { | |||
| private static final long serialVersionUID = 6031833682211475786L; | |||
| /** | |||
| * 使用dom4j解析的存放所有xml属性和值的map. | |||
| */ | |||
| private Map<String, Object> allFieldsMap; | |||
| @XStreamAlias("SuiteId") | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String suiteId; | |||
| @XStreamAlias("InfoType") | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String infoType; | |||
| @XStreamAlias("TimeStamp") | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String timeStamp; | |||
| @XStreamAlias("SuiteTicket") | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String suiteTicket; | |||
| @XStreamAlias("AuthCode") | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String authCode; | |||
| public static WxCpTpXmlMessage fromXml(String xml) { | |||
| //修改微信变态的消息内容格式,方便解析 | |||
| //xml = xml.replace("</PicList><PicList>", ""); | |||
| final WxCpTpXmlMessage xmlPackage = XStreamTransformer.fromXml(WxCpTpXmlMessage.class, xml); | |||
| xmlPackage.setAllFieldsMap(XmlUtils.xml2Map(xml)); | |||
| return xmlPackage; | |||
| } | |||
| } | |||
| @@ -0,0 +1,55 @@ | |||
| package me.chanjar.weixin.cp.bean; | |||
| import java.io.Serializable; | |||
| import java.util.Map; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| import lombok.Data; | |||
| import me.chanjar.weixin.common.util.XmlUtils; | |||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.ImageBuilder; | |||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.NewsBuilder; | |||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.TextBuilder; | |||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.VideoBuilder; | |||
| import me.chanjar.weixin.cp.bean.outxmlbuilder.VoiceBuilder; | |||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
| import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | |||
| import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | |||
| /** | |||
| * 回调消息包. | |||
| * https://work.weixin.qq.com/api/doc#90001/90143/91116 | |||
| * | |||
| * @author zhenjun cai | |||
| */ | |||
| @XStreamAlias("xml") | |||
| @Data | |||
| public class WxCpTpXmlPackage implements Serializable { | |||
| private static final long serialVersionUID = 6031833682211475786L; | |||
| /** | |||
| * 使用dom4j解析的存放所有xml属性和值的map. | |||
| */ | |||
| private Map<String, Object> allFieldsMap; | |||
| @XStreamAlias("ToUserName") | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String toUserName; | |||
| @XStreamAlias("AgentID") | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String agentId; | |||
| @XStreamAlias("Encrypt") | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| protected String msgEncrypt; | |||
| public static WxCpTpXmlPackage fromXml(String xml) { | |||
| //修改微信变态的消息内容格式,方便解析 | |||
| //xml = xml.replace("</PicList><PicList>", ""); | |||
| final WxCpTpXmlPackage xmlPackage = XStreamTransformer.fromXml(WxCpTpXmlPackage.class, xml); | |||
| xmlPackage.setAllFieldsMap(XmlUtils.xml2Map(xml)); | |||
| return xmlPackage; | |||
| } | |||
| } | |||
| @@ -0,0 +1,72 @@ | |||
| package me.chanjar.weixin.cp.config; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
| import java.io.File; | |||
| /** | |||
| * 微信客户端(第三方应用)配置存储 | |||
| * | |||
| * @author zhenjun cai | |||
| */ | |||
| public interface WxCpTpConfigStorage { | |||
| String getSuiteAccessToken(); | |||
| boolean isSuiteAccessTokenExpired(); | |||
| /** | |||
| * 强制将suite access token过期掉 | |||
| */ | |||
| void expireSuiteAccessToken(); | |||
| void updateSuiteAccessToken(WxAccessToken suiteAccessToken); | |||
| void updateSuiteAccessToken(String suiteAccessToken, int expiresIn); | |||
| String getSuiteTicket(); | |||
| boolean isSuiteTicketExpired(); | |||
| /** | |||
| * 强制将suite ticket过期掉 | |||
| */ | |||
| void expireSuiteTicket(); | |||
| /** | |||
| * 应该是线程安全的 | |||
| */ | |||
| void updateSuiteTicket(String suiteTicket, int expiresInSeconds); | |||
| String getCorpId(); | |||
| String getCorpSecret(); | |||
| String getSuiteId(); | |||
| String getSuiteSecret(); | |||
| String getToken(); | |||
| String getAesKey(); | |||
| long getExpiresTime(); | |||
| String getHttpProxyHost(); | |||
| int getHttpProxyPort(); | |||
| String getHttpProxyUsername(); | |||
| String getHttpProxyPassword(); | |||
| File getTmpDirFile(); | |||
| /** | |||
| * http client builder | |||
| * | |||
| * @return ApacheHttpClientBuilder | |||
| */ | |||
| ApacheHttpClientBuilder getApacheHttpClientBuilder(); | |||
| } | |||
| @@ -0,0 +1,230 @@ | |||
| package me.chanjar.weixin.cp.config; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import java.io.File; | |||
| /** | |||
| * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class WxCpTpInMemoryConfigStorage implements WxCpTpConfigStorage { | |||
| protected volatile String corpId; | |||
| protected volatile String corpSecret; | |||
| protected volatile String suiteId; | |||
| protected volatile String suiteSecret; | |||
| protected volatile String token; | |||
| protected volatile String suiteAccessToken; | |||
| protected volatile String aesKey; | |||
| protected volatile long expiresTime; | |||
| protected volatile String oauth2redirectUri; | |||
| protected volatile String httpProxyHost; | |||
| protected volatile int httpProxyPort; | |||
| protected volatile String httpProxyUsername; | |||
| protected volatile String httpProxyPassword; | |||
| protected volatile String suiteTicket; | |||
| protected volatile long suiteTicketExpiresTime; | |||
| protected volatile File tmpDirFile; | |||
| private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; | |||
| @Override | |||
| public String getSuiteAccessToken() { | |||
| return this.suiteAccessToken; | |||
| } | |||
| public void setSuiteAccessToken(String suiteAccessToken) { | |||
| this.suiteAccessToken = suiteAccessToken; | |||
| } | |||
| @Override | |||
| public boolean isSuiteAccessTokenExpired() { | |||
| return System.currentTimeMillis() > this.expiresTime; | |||
| } | |||
| @Override | |||
| public void expireSuiteAccessToken() { | |||
| this.expiresTime = 0; | |||
| } | |||
| @Override | |||
| public synchronized void updateSuiteAccessToken(WxAccessToken suiteAccessToken) { | |||
| updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn()); | |||
| } | |||
| @Override | |||
| public synchronized void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) { | |||
| this.suiteAccessToken = suiteAccessToken; | |||
| this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||
| } | |||
| @Override | |||
| public String getCorpId() { | |||
| return this.corpId; | |||
| } | |||
| public void setCorpId(String corpId) { | |||
| this.corpId = corpId; | |||
| } | |||
| @Override | |||
| public String getCorpSecret() { | |||
| return this.corpSecret; | |||
| } | |||
| public void setCorpSecret(String corpSecret) { | |||
| this.corpSecret = corpSecret; | |||
| } | |||
| @Override | |||
| public String getSuiteTicket() { | |||
| return this.suiteTicket; | |||
| } | |||
| public void setSuiteTicket(String suiteTicket) { | |||
| this.suiteTicket = suiteTicket; | |||
| } | |||
| public long getSuiteTicketExpiresTime() { | |||
| return this.suiteTicketExpiresTime; | |||
| } | |||
| public void setSuiteTicketExpiresTime(long suiteTicketExpiresTime) { | |||
| this.suiteTicketExpiresTime = suiteTicketExpiresTime; | |||
| } | |||
| @Override | |||
| public boolean isSuiteTicketExpired() { | |||
| return System.currentTimeMillis() > this.suiteTicketExpiresTime; | |||
| } | |||
| @Override | |||
| public synchronized void updateSuiteTicket(String suiteTicket, int expiresInSeconds) { | |||
| this.suiteTicket = suiteTicket; | |||
| // 预留200秒的时间 | |||
| this.suiteTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||
| } | |||
| @Override | |||
| public void expireSuiteTicket() { | |||
| this.suiteTicketExpiresTime = 0; | |||
| } | |||
| @Override | |||
| public String getSuiteId() { | |||
| return this.suiteId; | |||
| } | |||
| public void setSuiteId(String corpId) { | |||
| this.suiteId = corpId; | |||
| } | |||
| @Override | |||
| public String getSuiteSecret() { | |||
| return this.suiteSecret; | |||
| } | |||
| public void setSuiteSecret(String corpSecret) { | |||
| this.suiteSecret = corpSecret; | |||
| } | |||
| @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; | |||
| } | |||
| public void setOauth2redirectUri(String oauth2redirectUri) { | |||
| this.oauth2redirectUri = oauth2redirectUri; | |||
| } | |||
| @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 WxCpGsonBuilder.create().toJson(this); | |||
| } | |||
| @Override | |||
| public File getTmpDirFile() { | |||
| return this.tmpDirFile; | |||
| } | |||
| public void setTmpDirFile(File tmpDirFile) { | |||
| this.tmpDirFile = tmpDirFile; | |||
| } | |||
| @Override | |||
| public ApacheHttpClientBuilder getApacheHttpClientBuilder() { | |||
| return this.apacheHttpClientBuilder; | |||
| } | |||
| public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) { | |||
| this.apacheHttpClientBuilder = apacheHttpClientBuilder; | |||
| } | |||
| } | |||
| @@ -0,0 +1,52 @@ | |||
| /** | |||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||
| * | |||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||
| * <p> | |||
| * 针对org.apache.commons.codec.binary.Base64, | |||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||
| * <p> | |||
| * 针对org.apache.commons.codec.binary.Base64, | |||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||
| */ | |||
| // ------------------------------------------------------------------------ | |||
| /** | |||
| * 针对org.apache.commons.codec.binary.Base64, | |||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||
| */ | |||
| package me.chanjar.weixin.cp.util.crypto; | |||
| import org.apache.commons.codec.binary.Base64; | |||
| import me.chanjar.weixin.common.util.crypto.WxCryptUtil; | |||
| import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; | |||
| public class WxCpTpCryptUtil extends WxCryptUtil { | |||
| /** | |||
| * 构造函数 | |||
| * | |||
| * @param wxCpConfigStorage | |||
| */ | |||
| public WxCpTpCryptUtil(WxCpTpConfigStorage wxCpTpConfigStorage) { | |||
| /* | |||
| * @param token 公众平台上,开发者设置的token | |||
| * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey | |||
| * @param appidOrCorpid 公众平台corpId | |||
| */ | |||
| String encodingAesKey = wxCpTpConfigStorage.getAesKey(); | |||
| String token = wxCpTpConfigStorage.getToken(); | |||
| String corpId = wxCpTpConfigStorage.getCorpId(); | |||
| this.token = token; | |||
| this.appidOrCorpid = corpId; | |||
| this.aesKey = Base64.decodeBase64(encodingAesKey + "="); | |||
| } | |||
| } | |||
| @@ -1,117 +1,135 @@ | |||
| package me.chanjar.weixin.cp.util.xml; | |||
| import java.io.InputStream; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| import com.thoughtworks.xstream.XStream; | |||
| import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessage; | |||
| public class XStreamTransformer { | |||
| protected static final Map<Class, XStream> CLASS_2_XSTREAM_INSTANCE = configXStreamInstance(); | |||
| /** | |||
| * 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; | |||
| } | |||
| /** | |||
| * 注册扩展消息的解析器. | |||
| * | |||
| * @param clz 类型 | |||
| * @param xStream xml解析器 | |||
| */ | |||
| public static void register(Class clz, XStream xStream) { | |||
| CLASS_2_XSTREAM_INSTANCE.put(clz, xStream); | |||
| } | |||
| /** | |||
| * pojo -> xml. | |||
| */ | |||
| public static <T> String toXml(Class<T> clazz, T object) { | |||
| return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object); | |||
| } | |||
| private static Map<Class, XStream> configXStreamInstance() { | |||
| Map<Class, XStream> map = new HashMap<>(); | |||
| map.put(WxCpXmlMessage.class, configWxCpXmlMessage()); | |||
| map.put(WxCpXmlOutNewsMessage.class, configWxCpXmlOutNewsMessage()); | |||
| map.put(WxCpXmlOutTextMessage.class, configWxCpXmlOutTextMessage()); | |||
| map.put(WxCpXmlOutImageMessage.class, configWxCpXmlOutImageMessage()); | |||
| map.put(WxCpXmlOutVideoMessage.class, configWxCpXmlOutVideoMessage()); | |||
| map.put(WxCpXmlOutVoiceMessage.class, configWxCpXmlOutVoiceMessage()); | |||
| return map; | |||
| } | |||
| private static XStream configWxCpXmlMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlMessage.class); | |||
| xstream.processAnnotations(WxCpXmlMessage.ScanCodeInfo.class); | |||
| xstream.processAnnotations(WxCpXmlMessage.SendPicsInfo.class); | |||
| xstream.processAnnotations(WxCpXmlMessage.SendPicsInfo.Item.class); | |||
| xstream.processAnnotations(WxCpXmlMessage.SendLocationInfo.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpXmlOutImageMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlOutMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutImageMessage.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpXmlOutNewsMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlOutMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutNewsMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutNewsMessage.Item.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpXmlOutTextMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlOutMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutTextMessage.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpXmlOutVideoMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlOutMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutVideoMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutVideoMessage.Video.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpXmlOutVoiceMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlOutMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutVoiceMessage.class); | |||
| return xstream; | |||
| } | |||
| } | |||
| package me.chanjar.weixin.cp.util.xml; | |||
| import java.io.InputStream; | |||
| import java.util.HashMap; | |||
| import java.util.Map; | |||
| import com.thoughtworks.xstream.XStream; | |||
| import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||
| import me.chanjar.weixin.cp.bean.WxCpTpXmlMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpTpXmlPackage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessage; | |||
| public class XStreamTransformer { | |||
| protected static final Map<Class, XStream> CLASS_2_XSTREAM_INSTANCE = configXStreamInstance(); | |||
| /** | |||
| * 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; | |||
| } | |||
| /** | |||
| * 注册扩展消息的解析器. | |||
| * | |||
| * @param clz 类型 | |||
| * @param xStream xml解析器 | |||
| */ | |||
| public static void register(Class clz, XStream xStream) { | |||
| CLASS_2_XSTREAM_INSTANCE.put(clz, xStream); | |||
| } | |||
| /** | |||
| * pojo -> xml. | |||
| */ | |||
| public static <T> String toXml(Class<T> clazz, T object) { | |||
| return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object); | |||
| } | |||
| private static Map<Class, XStream> configXStreamInstance() { | |||
| Map<Class, XStream> map = new HashMap<>(); | |||
| map.put(WxCpXmlMessage.class, configWxCpXmlMessage()); | |||
| map.put(WxCpXmlOutNewsMessage.class, configWxCpXmlOutNewsMessage()); | |||
| map.put(WxCpXmlOutTextMessage.class, configWxCpXmlOutTextMessage()); | |||
| map.put(WxCpXmlOutImageMessage.class, configWxCpXmlOutImageMessage()); | |||
| map.put(WxCpXmlOutVideoMessage.class, configWxCpXmlOutVideoMessage()); | |||
| map.put(WxCpXmlOutVoiceMessage.class, configWxCpXmlOutVoiceMessage()); | |||
| map.put(WxCpTpXmlPackage.class, configWxCpTpXmlPackage()); | |||
| map.put(WxCpTpXmlMessage.class, configWxCpTpXmlMessage()); | |||
| return map; | |||
| } | |||
| private static XStream configWxCpXmlMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlMessage.class); | |||
| xstream.processAnnotations(WxCpXmlMessage.ScanCodeInfo.class); | |||
| xstream.processAnnotations(WxCpXmlMessage.SendPicsInfo.class); | |||
| xstream.processAnnotations(WxCpXmlMessage.SendPicsInfo.Item.class); | |||
| xstream.processAnnotations(WxCpXmlMessage.SendLocationInfo.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpXmlOutImageMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlOutMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutImageMessage.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpXmlOutNewsMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlOutMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutNewsMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutNewsMessage.Item.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpXmlOutTextMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlOutMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutTextMessage.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpXmlOutVideoMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlOutMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutVideoMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutVideoMessage.Video.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpXmlOutVoiceMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpXmlOutMessage.class); | |||
| xstream.processAnnotations(WxCpXmlOutVoiceMessage.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpTpXmlPackage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpTpXmlPackage.class); | |||
| return xstream; | |||
| } | |||
| private static XStream configWxCpTpXmlMessage() { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxCpTpXmlMessage.class); | |||
| return xstream; | |||
| } | |||
| } | |||