소스 검색

#493 企业微信增加第三方应用开发接口的支持

master
feye 5 년 전
committed by Binary Wang
부모
커밋
001a2742c6
12개의 변경된 파일1230개의 추가작업 그리고 117개의 파일을 삭제
  1. +169
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java
  2. +244
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java
  3. +37
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOnTpImpl.java
  4. +114
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceApacheHttpClientImpl.java
  5. +12
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceImpl.java
  6. +42
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java
  7. +68
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java
  8. +55
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlPackage.java
  9. +72
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
  10. +230
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpInMemoryConfigStorage.java
  11. +52
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpTpCryptUtil.java
  12. +135
    -117
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java

+ 169
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java 파일 보기

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

+ 244
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java 파일 보기

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

+ 37
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOnTpImpl.java 파일 보기

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

+ 114
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceApacheHttpClientImpl.java 파일 보기

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

+ 12
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceImpl.java 파일 보기

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

+ 42
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java 파일 보기

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

+ 68
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java 파일 보기

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

+ 55
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlPackage.java 파일 보기

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

+ 72
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java 파일 보기

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

+ 230
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpInMemoryConfigStorage.java 파일 보기

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

+ 52
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpTpCryptUtil.java 파일 보기

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

+ 135
- 117
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java 파일 보기

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

불러오는 중...
취소
저장