me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl Maven / Gradle / Ivy
package me.chanjar.weixin.cp.api.impl;
import com.google.common.base.Joiner;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.CommonUploadParam;
import me.chanjar.weixin.common.bean.ToJson;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.error.WxRuntimeException;
import me.chanjar.weixin.common.executor.CommonUploadRequestExecutor;
import me.chanjar.weixin.common.session.StandardSessionManager;
import me.chanjar.weixin.common.session.WxSession;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.DataUtils;
import me.chanjar.weixin.common.util.RandomUtils;
import me.chanjar.weixin.common.util.crypto.SHA1;
import me.chanjar.weixin.common.util.http.*;
import me.chanjar.weixin.common.util.json.GsonParser;
import me.chanjar.weixin.cp.api.*;
import me.chanjar.weixin.cp.bean.WxCpAgentJsapiSignature;
import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult;
import me.chanjar.weixin.cp.bean.WxCpProviderToken;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.*;
/**
* .
*
* @param the type parameter
* @param the type parameter
* @author chanjarster
*/
@Slf4j
public abstract class BaseWxCpServiceImpl implements WxCpService, RequestHttp {
private WxCpUserService userService = new WxCpUserServiceImpl(this);
private final WxCpChatService chatService = new WxCpChatServiceImpl(this);
private WxCpDepartmentService departmentService = new WxCpDepartmentServiceImpl(this);
private WxCpMediaService mediaService = new WxCpMediaServiceImpl(this);
private WxCpMenuService menuService = new WxCpMenuServiceImpl(this);
private WxCpOAuth2Service oauth2Service = new WxCpOAuth2ServiceImpl(this);
private WxCpTagService tagService = new WxCpTagServiceImpl(this);
private WxCpAgentService agentService = new WxCpAgentServiceImpl(this);
private final WxCpOaService oaService = new WxCpOaServiceImpl(this);
private final WxCpSchoolService schoolService = new WxCpSchoolServiceImpl(this);
private final WxCpSchoolUserService schoolUserService = new WxCpSchoolUserServiceImpl(this);
private final WxCpSchoolHealthService schoolHealthService = new WxCpSchoolHealthServiceImpl(this);
private final WxCpLivingService livingService = new WxCpLivingServiceImpl(this);
private final WxCpOaAgentService oaAgentService = new WxCpOaAgentServiceImpl(this);
private final WxCpOaWeDriveService oaWeDriveService = new WxCpOaWeDriveServiceImpl(this);
private final WxCpMsgAuditService msgAuditService = new WxCpMsgAuditServiceImpl(this);
private final WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this);
private final WxCpExternalContactService externalContactService = new WxCpExternalContactServiceImpl(this);
private final WxCpGroupRobotService groupRobotService = new WxCpGroupRobotServiceImpl(this);
private final WxCpMessageService messageService = new WxCpMessageServiceImpl(this);
private final WxCpOaCalendarService oaCalendarService = new WxCpOaCalendarServiceImpl(this);
private final WxCpOaMeetingRoomService oaMeetingRoomService = new WxCpOaMeetingRoomServiceImpl(this);
private final WxCpOaScheduleService oaScheduleService = new WxCpOaOaScheduleServiceImpl(this);
private final WxCpAgentWorkBenchService workBenchService = new WxCpAgentWorkBenchServiceImpl(this);
private WxCpKfService kfService = new WxCpKfServiceImpl(this);
private WxCpExportService exportService = new WxCpExportServiceImpl(this);
private final WxCpMeetingService meetingService = new WxCpMeetingServiceImpl(this);
private final WxCpCorpGroupService corpGroupService = new WxCpCorpGroupServiceImpl(this);
/**
* 全局的是否正在刷新access token的锁.
*/
protected final Object globalAccessTokenRefreshLock = new Object();
/**
* 全局的是否正在刷新jsapi_ticket的锁.
*/
protected final Object globalJsapiTicketRefreshLock = new Object();
/**
* 全局的是否正在刷新agent的jsapi_ticket的锁.
*/
protected final Object globalAgentJsapiTicketRefreshLock = new Object();
/**
* The Config storage.
*/
protected WxCpConfigStorage configStorage;
private WxSessionManager sessionManager = new StandardSessionManager();
/**
* 临时文件目录.
*/
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) {
log.error("Checking signature failed, and the reason is :" + e.getMessage());
return false;
}
}
@Override
public String getAccessToken() throws WxErrorException {
return getAccessToken(false);
}
@Override
public String getAgentJsapiTicket() throws WxErrorException {
return this.getAgentJsapiTicket(false);
}
@Override
public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireAgentJsapiTicket();
}
if (this.configStorage.isAgentJsapiTicketExpired()) {
synchronized (this.globalAgentJsapiTicketRefreshLock) {
if (this.configStorage.isAgentJsapiTicketExpired()) {
String responseContent = this.get(this.configStorage.getApiUrl(GET_AGENT_CONFIG_TICKET), null);
JsonObject jsonObject = GsonParser.parse(responseContent);
this.configStorage.updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(),
jsonObject.get("expires_in").getAsInt());
}
}
}
return this.configStorage.getAgentJsapiTicket();
}
@Override
public String getJsapiTicket() throws WxErrorException {
return getJsapiTicket(false);
}
@Override
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireJsapiTicket();
}
if (this.configStorage.isJsapiTicketExpired()) {
synchronized (this.globalJsapiTicketRefreshLock) {
if (this.configStorage.isJsapiTicketExpired()) {
String responseContent = this.get(this.configStorage.getApiUrl(GET_JSAPI_TICKET), null);
JsonObject tmpJsonObject = GsonParser.parse(responseContent);
this.configStorage.updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(),
tmpJsonObject.get("expires_in").getAsInt());
}
}
}
return this.configStorage.getJsapiTicket();
}
@Override
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException {
long timestamp = System.currentTimeMillis() / 1000;
String noncestr = RandomUtils.getRandomStr();
String jsapiTicket = getJsapiTicket(false);
String signature = SHA1.genWithAmple(
"jsapi_ticket=" + jsapiTicket,
"noncestr=" + noncestr,
"timestamp=" + timestamp,
"url=" + url
);
WxJsapiSignature jsapiSignature = new WxJsapiSignature();
jsapiSignature.setTimestamp(timestamp);
jsapiSignature.setNonceStr(noncestr);
jsapiSignature.setUrl(url);
jsapiSignature.setSignature(signature);
// Fixed bug
jsapiSignature.setAppId(this.configStorage.getCorpId());
return jsapiSignature;
}
@Override
public WxCpAgentJsapiSignature createAgentJsapiSignature(String url) throws WxErrorException {
long timestamp = System.currentTimeMillis() / 1000;
String noncestr = RandomUtils.getRandomStr();
String jsapiTicket = getAgentJsapiTicket(false);
String signature = SHA1.genWithAmple(
"jsapi_ticket=" + jsapiTicket,
"noncestr=" + noncestr,
"timestamp=" + timestamp,
"url=" + url
);
WxCpAgentJsapiSignature jsapiSignature = new WxCpAgentJsapiSignature();
jsapiSignature.setTimestamp(timestamp);
jsapiSignature.setNonceStr(noncestr);
jsapiSignature.setUrl(url);
jsapiSignature.setSignature(signature);
jsapiSignature.setCorpid(this.configStorage.getCorpId());
jsapiSignature.setAgentid(this.configStorage.getAgentId());
return jsapiSignature;
}
@Override
public WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException {
Map params = new HashMap<>(2);
params.put("js_code", jsCode);
params.put("grant_type", "authorization_code");
final String url = this.configStorage.getApiUrl(JSCODE_TO_SESSION);
return WxCpMaJsCode2SessionResult.fromJson(this.get(url, Joiner.on("&").withKeyValueSeparator("=").join(params)));
}
@Override
public String[] getCallbackIp() throws WxErrorException {
return getIp(GET_CALLBACK_IP);
}
@Override
public String[] getApiDomainIp() throws WxErrorException {
return getIp(GET_API_DOMAIN_IP);
}
/**
* 获取 IP
*
* @param suffixUrl 接口URL 后缀
* @return 返回结果
* @throws WxErrorException 异常信息
*/
private String[] getIp(String suffixUrl) throws WxErrorException {
String responseContent = get(this.configStorage.getApiUrl(suffixUrl), null);
JsonObject tmpJsonObject = GsonParser.parse(responseContent);
JsonArray jsonArray = tmpJsonObject.get("ip_list").getAsJsonArray();
String[] ips = new String[jsonArray.size()];
for (int i = 0; i < jsonArray.size(); i++) {
ips[i] = jsonArray.get(i).getAsString();
}
return ips;
}
@Override
public WxCpProviderToken getProviderToken(String corpId, String providerSecret) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("corpid", corpId);
jsonObject.addProperty("provider_secret", providerSecret);
return WxCpProviderToken.fromJson(this.post(this.configStorage.getApiUrl(Tp.GET_PROVIDER_TOKEN),
jsonObject.toString()));
}
@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);
}
@Override
public String post(String url, JsonObject jsonObject) throws WxErrorException {
return this.post(url, jsonObject.toString());
}
@Override
public String post(String url, ToJson obj) throws WxErrorException {
return this.post(url, obj.toJson());
}
@Override
public String upload(String url, CommonUploadParam param) throws WxErrorException {
RequestExecutor executor = CommonUploadRequestExecutor.create(getRequestHttp());
return this.execute(executor, url, param);
}
@Override
public String post(String url, Object obj) throws WxErrorException {
return this.post(url, obj.toString());
}
@Override
public String postWithoutToken(String url, String postData) throws WxErrorException {
return this.executeNormal(SimplePostRequestExecutor.create(this), url, postData);
}
/**
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求.
*/
@Override
public T execute(RequestExecutor executor, String uri, E data) throws WxErrorException {
int retryTimes = 0;
do {
try {
return this.executeInternal(executor, uri, data, false);
} catch (WxErrorException e) {
if (retryTimes + 1 > this.maxRetryTimes) {
log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
//最后一次重试失败后,直接抛出异常,不再等待
throw new WxRuntimeException("微信服务端异常,超出重试次数");
}
WxError error = e.getError();
/*
* -1 系统繁忙, 1000ms后重试
*/
if (error.getErrorCode() == -1) {
int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
try {
log.debug("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
Thread.sleep(sleepMillis);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
}
} else {
throw e;
}
}
} while (retryTimes++ < this.maxRetryTimes);
log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
throw new WxRuntimeException("微信服务端异常,超出重试次数");
}
/**
* Execute internal t.
*
* @param the type parameter
* @param the type parameter
* @param executor the executor
* @param uri the uri
* @param data the data
* @param doNotAutoRefresh the do not auto refresh
* @return the t
* @throws WxErrorException the wx error exception
*/
protected T executeInternal(RequestExecutor executor, String uri, E data, boolean doNotAutoRefresh) throws WxErrorException {
E dataForLog = DataUtils.handleDataWithSecret(data);
if (uri.contains("access_token=")) {
throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
}
String accessToken = getAccessToken(false);
String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken;
try {
T result = executor.execute(uriWithAccessToken, data, WxType.CP);
log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, result);
return result;
} catch (WxErrorException e) {
WxError error = e.getError();
if (WxConsts.ACCESS_TOKEN_ERROR_CODES.contains(error.getErrorCode())) {
// 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
this.configStorage.expireAccessToken();
if (this.getWxCpConfigStorage().autoRefreshToken() && !doNotAutoRefresh) {
log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
//下一次不再自动重试
//当小程序误调用第三方平台专属接口时,第三方无法使用小程序的access token,如果可以继续自动获取token会导致无限循环重试,直到栈溢出
return this.executeInternal(executor, uri, data, true);
}
}
if (error.getErrorCode() != 0) {
log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error);
throw new WxErrorException(error, e);
}
return null;
} catch (IOException e) {
log.warn("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage());
throw new WxRuntimeException(e);
}
}
/**
* 普通请求,不自动带accessToken
*/
private T executeNormal(RequestExecutor executor, String uri, E data) throws WxErrorException {
try {
T result = executor.execute(uri, data, WxType.CP);
log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uri, data, result);
return result;
} catch (WxErrorException e) {
WxError error = e.getError();
if (error.getErrorCode() != 0) {
log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uri, data, error);
throw new WxErrorException(error, e);
}
return null;
} catch (IOException e) {
log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uri, data, e.getMessage());
throw new WxErrorException(e);
}
}
@Override
public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) {
this.configStorage = wxConfigProvider;
this.initHttp();
}
@Override
public void setRetrySleepMillis(int retrySleepMillis) {
this.retrySleepMillis = retrySleepMillis;
}
@Override
public void setMaxRetryTimes(int maxRetryTimes) {
this.maxRetryTimes = maxRetryTimes;
}
@Override
public WxSession getSession(String id) {
if (this.sessionManager == null) {
return null;
}
return this.sessionManager.getSession(id);
}
@Override
public WxSession getSession(String id, boolean create) {
if (this.sessionManager == null) {
return null;
}
return this.sessionManager.getSession(id, create);
}
@Override
public void setSessionManager(WxSessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public WxSessionManager getSessionManager() {
return this.sessionManager;
}
@Override
public String replaceParty(String mediaId) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("media_id", mediaId);
return post(this.configStorage.getApiUrl(BATCH_REPLACE_PARTY), jsonObject.toString());
}
@Override
public String syncUser(String mediaId) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("media_id", mediaId);
String responseContent = post(this.configStorage.getApiUrl(BATCH_SYNC_USER), jsonObject.toString());
JsonObject tmpJson = GsonParser.parse(responseContent);
return tmpJson.get("jobid").getAsString();
}
@Override
public String replaceUser(String mediaId) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("media_id", mediaId);
return post(this.configStorage.getApiUrl(BATCH_REPLACE_USER), jsonObject.toString());
}
@Override
public String getTaskResult(String jobId) throws WxErrorException {
String url = this.configStorage.getApiUrl(BATCH_GET_RESULT + jobId);
return get(url, null);
}
@Override
public String buildQrConnectUrl(String redirectUri, String state) {
return String.format("https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=%s&agentid=%s&redirect_uri=%s" +
"&state=%s",
this.configStorage.getCorpId(), this.configStorage.getAgentId(),
URIUtil.encodeURIComponent(redirectUri), StringUtils.trimToEmpty(state));
}
/**
* Gets tmp dir file.
*
* @return the tmp dir file
*/
public File getTmpDirFile() {
return this.tmpDirFile;
}
/**
* Sets tmp dir file.
*
* @param tmpDirFile the tmp dir file
*/
public void setTmpDirFile(File tmpDirFile) {
this.tmpDirFile = tmpDirFile;
}
@Override
public WxCpDepartmentService getDepartmentService() {
return departmentService;
}
@Override
public WxCpMediaService getMediaService() {
return mediaService;
}
@Override
public WxCpMenuService getMenuService() {
return menuService;
}
@Override
public WxCpOAuth2Service getOauth2Service() {
return oauth2Service;
}
@Override
public WxCpTagService getTagService() {
return tagService;
}
@Override
public WxCpUserService getUserService() {
return userService;
}
@Override
public WxCpExternalContactService getExternalContactService() {
return externalContactService;
}
@Override
public WxCpChatService getChatService() {
return chatService;
}
@Override
public WxCpOaService getOaService() {
return oaService;
}
@Override
public WxCpSchoolService getSchoolService() {
return schoolService;
}
@Override
public WxCpSchoolUserService getSchoolUserService() {
return schoolUserService;
}
@Override
public WxCpSchoolHealthService getSchoolHealthService() {
return schoolHealthService;
}
@Override
public WxCpLivingService getLivingService() {
return livingService;
}
@Override
public WxCpOaAgentService getOaAgentService() {
return oaAgentService;
}
@Override
public WxCpOaWeDriveService getOaWeDriveService() {
return oaWeDriveService;
}
@Override
public WxCpMsgAuditService getMsgAuditService() {
return msgAuditService;
}
@Override
public WxCpOaCalendarService getOaCalendarService() {
return this.oaCalendarService;
}
@Override
public WxCpOaMeetingRoomService getOaMeetingRoomService() {
return this.oaMeetingRoomService;
}
@Override
public WxCpGroupRobotService getGroupRobotService() {
return groupRobotService;
}
@Override
public WxCpAgentWorkBenchService getWorkBenchService() {
return workBenchService;
}
@Override
public WxCpTaskCardService getTaskCardService() {
return taskCardService;
}
@Override
public RequestHttp, ?> getRequestHttp() {
return this;
}
@Override
public void setUserService(WxCpUserService userService) {
this.userService = userService;
}
@Override
public void setDepartmentService(WxCpDepartmentService departmentService) {
this.departmentService = departmentService;
}
@Override
public void setMediaService(WxCpMediaService mediaService) {
this.mediaService = mediaService;
}
@Override
public void setMenuService(WxCpMenuService menuService) {
this.menuService = menuService;
}
@Override
public void setOauth2Service(WxCpOAuth2Service oauth2Service) {
this.oauth2Service = oauth2Service;
}
@Override
public void setTagService(WxCpTagService tagService) {
this.tagService = tagService;
}
@Override
public WxCpAgentService getAgentService() {
return agentService;
}
@Override
public WxCpMessageService getMessageService() {
return this.messageService;
}
/**
* Sets agent service.
*
* @param agentService the agent service
*/
public void setAgentService(WxCpAgentService agentService) {
this.agentService = agentService;
}
@Override
public WxCpOaScheduleService getOaScheduleService() {
return this.oaScheduleService;
}
@Override
public WxCpKfService getKfService() {
return kfService;
}
@Override
public void setKfService(WxCpKfService kfService) {
this.kfService = kfService;
}
@Override
public WxCpExportService getExportService() {
return exportService;
}
@Override
public void setExportService(WxCpExportService exportService) {
this.exportService = exportService;
}
@Override
public WxCpMeetingService getMeetingService() {
return meetingService;
}
@Override
public WxCpCorpGroupService getCorpGroupService() {
return corpGroupService;
}
}