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