123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- package me.chanjar.weixin.mp.api;
- import com.google.gson.JsonElement;
- import com.google.gson.JsonObject;
- import com.google.gson.internal.Streams;
- import com.google.gson.reflect.TypeToken;
- import com.google.gson.stream.JsonReader;
- import me.chanjar.weixin.common.bean.WxAccessToken;
- import me.chanjar.weixin.common.bean.WxMenu;
- import me.chanjar.weixin.common.bean.result.WxError;
- import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
- import me.chanjar.weixin.common.exception.WxErrorException;
- import me.chanjar.weixin.common.session.StandardSessionManager;
- import me.chanjar.weixin.common.session.WxSessionManager;
- import me.chanjar.weixin.common.util.StringUtils;
- import me.chanjar.weixin.common.util.crypto.SHA1;
- import me.chanjar.weixin.common.util.fs.FileUtils;
- import me.chanjar.weixin.common.util.http.*;
- import me.chanjar.weixin.common.util.json.GsonHelper;
- import me.chanjar.weixin.mp.bean.*;
- import me.chanjar.weixin.mp.bean.result.*;
- import me.chanjar.weixin.mp.util.http.QrCodeRequestExecutor;
- import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
- import org.apache.http.HttpHost;
- import org.apache.http.auth.AuthScope;
- import org.apache.http.auth.UsernamePasswordCredentials;
- import org.apache.http.client.ClientProtocolException;
- import org.apache.http.client.CredentialsProvider;
- import org.apache.http.client.config.RequestConfig;
- import org.apache.http.client.methods.CloseableHttpResponse;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.impl.client.BasicCredentialsProvider;
- import org.apache.http.impl.client.BasicResponseHandler;
- import org.apache.http.impl.client.CloseableHttpClient;
- import org.apache.http.impl.client.HttpClients;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.StringReader;
- import java.security.NoSuchAlgorithmException;
- import java.util.List;
- import java.util.UUID;
- public class WxMpServiceImpl implements WxMpService {
- protected final Logger log = LoggerFactory.getLogger(WxMpServiceImpl.class);
- /**
- * 全局的是否正在刷新access token的锁
- */
- protected final Object globalAccessTokenRefreshLock = new Object();
- /**
- * 全局的是否正在刷新jsapi_ticket的锁
- */
- protected final Object globalJsapiTicketRefreshLock = new Object();
- protected WxMpConfigStorage wxMpConfigStorage;
-
- protected CloseableHttpClient httpClient;
- protected HttpHost httpProxy;
- private int retrySleepMillis = 1000;
- private int maxRetryTimes = 5;
- protected WxSessionManager sessionManager = new StandardSessionManager();
- public boolean checkSignature(String timestamp, String nonce, String signature) {
- try {
- return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature);
- } catch (Exception e) {
- return false;
- }
- }
- public String getAccessToken() throws WxErrorException {
- return getAccessToken(false);
- }
- public String getAccessToken(boolean forceRefresh) throws WxErrorException {
- if (forceRefresh) {
- wxMpConfigStorage.expireAccessToken();
- }
- if (wxMpConfigStorage.isAccessTokenExpired()) {
- synchronized (globalAccessTokenRefreshLock) {
- if (wxMpConfigStorage.isAccessTokenExpired()) {
- String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
- + "&appid=" + wxMpConfigStorage.getAppId()
- + "&secret=" + wxMpConfigStorage.getSecret()
- ;
- try {
- HttpGet httpGet = new HttpGet(url);
- if (httpProxy != null) {
- RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
- httpGet.setConfig(config);
- }
- CloseableHttpClient httpclient = getHttpclient();
- CloseableHttpResponse response = httpclient.execute(httpGet);
- String resultContent = new BasicResponseHandler().handleResponse(response);
- WxError error = WxError.fromJson(resultContent);
- if (error.getErrorCode() != 0) {
- throw new WxErrorException(error);
- }
- WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
- wxMpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
- } catch (ClientProtocolException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- }
- }
- return wxMpConfigStorage.getAccessToken();
- }
- public String getJsapiTicket() throws WxErrorException {
- return getJsapiTicket(false);
- }
- public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
- if (forceRefresh) {
- wxMpConfigStorage.expireJsapiTicket();
- }
- if (wxMpConfigStorage.isJsapiTicketExpired()) {
- synchronized (globalJsapiTicketRefreshLock) {
- if (wxMpConfigStorage.isJsapiTicketExpired()) {
- String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi";
- String responseContent = execute(new SimpleGetRequestExecutor(), url, null);
- JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
- JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
- String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
- int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
- wxMpConfigStorage.updateJsapiTicket(jsapiTicket, expiresInSeconds);
- }
- }
- }
- return wxMpConfigStorage.getJsapiTicket();
- }
- public String createJsapiSignature(long timestamp, String noncestr, String url) throws WxErrorException {
- String jsapiTicket = getJsapiTicket(false);
- try {
- return SHA1.genWithAmple(
- "jsapi_ticket=" + jsapiTicket,
- "noncestr=" + noncestr,
- "timestamp=" + timestamp,
- "url=" + url
- );
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
- public void customMessageSend(WxMpCustomMessage message) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send";
- execute(new SimplePostRequestExecutor(), url, message.toJson());
- }
-
- public void menuCreate(WxMenu menu) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/menu/create";
- execute(new SimplePostRequestExecutor(), url, menu.toJson());
- }
-
- public void menuDelete() throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/menu/delete";
- execute(new SimpleGetRequestExecutor(), url, null);
- }
- public WxMenu menuGet() throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/menu/get";
- try {
- String resultContent = execute(new SimpleGetRequestExecutor(), url, null);
- return WxMenu.fromJson(resultContent);
- } catch (WxErrorException e) {
- // 46003 不存在的菜单数据
- if (e.getError().getErrorCode() == 46003) {
- return null;
- }
- throw e;
- }
- }
- public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) throws WxErrorException, IOException {
- return mediaUpload(mediaType,FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType));
- }
-
- public WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException {
- String url = "http://file.api.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType;
- return execute(new MediaUploadRequestExecutor(), url, file);
- }
-
- public File mediaDownload(String media_id) throws WxErrorException {
- String url = "http://file.api.weixin.qq.com/cgi-bin/media/get";
- return execute(new MediaDownloadRequestExecutor(), url, "media_id=" + media_id);
- }
- public WxMpMassUploadResult massNewsUpload(WxMpMassNews news) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/media/uploadnews";
- String responseContent = execute(new SimplePostRequestExecutor(), url, news.toJson());
- return WxMpMassUploadResult.fromJson(responseContent);
- }
-
- public WxMpMassUploadResult massVideoUpload(WxMpMassVideo video) throws WxErrorException {
- String url = "http://file.api.weixin.qq.com/cgi-bin/media/uploadvideo";
- String responseContent = execute(new SimplePostRequestExecutor(), url, video.toJson());
- return WxMpMassUploadResult.fromJson(responseContent);
- }
-
- public WxMpMassSendResult massGroupMessageSend(WxMpMassGroupMessage message) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall";
- String responseContent = execute(new SimplePostRequestExecutor(), url, message.toJson());
- return WxMpMassSendResult.fromJson(responseContent);
- }
- public WxMpMassSendResult massOpenIdsMessageSend(WxMpMassOpenIdsMessage message) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/message/mass/send";
- String responseContent = execute(new SimplePostRequestExecutor(), url, message.toJson());
- return WxMpMassSendResult.fromJson(responseContent);
- }
-
- public WxMpGroup groupCreate(String name) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/groups/create";
- JsonObject json = new JsonObject();
- JsonObject groupJson = new JsonObject();
- json.add("group", groupJson);
- groupJson.addProperty("name", name);
-
- String responseContent = execute(
- new SimplePostRequestExecutor(),
- url,
- json.toString());
- return WxMpGroup.fromJson(responseContent);
- }
- public List<WxMpGroup> groupGet() throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/groups/get";
- String responseContent = execute(new SimpleGetRequestExecutor(), url, null);
- /*
- * 操蛋的微信API,创建时返回的是 { group : { id : ..., name : ...} }
- * 查询时返回的是 { groups : [ { id : ..., name : ..., count : ... }, ... ] }
- */
- JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
- return WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement.getAsJsonObject().get("groups"), new TypeToken<List<WxMpGroup>>(){}.getType());
- }
-
- public long userGetGroup(String openid) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/groups/getid";
- JsonObject o = new JsonObject();
- o.addProperty("openid", openid);
- String responseContent = execute(new SimplePostRequestExecutor(), url, o.toString());
- JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
- return GsonHelper.getAsLong(tmpJsonElement.getAsJsonObject().get("groupid"));
- }
-
- public void groupUpdate(WxMpGroup group) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/groups/update";
- execute(new SimplePostRequestExecutor(), url, group.toJson());
- }
-
- public void userUpdateGroup(String openid, long to_groupid) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/groups/members/update";
- JsonObject json = new JsonObject();
- json.addProperty("openid", openid);
- json.addProperty("to_groupid", to_groupid);
- execute(new SimplePostRequestExecutor(), url, json.toString());
- }
-
- public void userUpdateRemark(String openid, String remark) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/user/info/updateremark";
- JsonObject json = new JsonObject();
- json.addProperty("openid", openid);
- json.addProperty("remark", remark);
- execute(new SimplePostRequestExecutor(), url, json.toString());
- }
-
- public WxMpUser userInfo(String openid, String lang) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/user/info";
- lang = lang == null ? "zh_CN" : lang;
- String responseContent = execute(new SimpleGetRequestExecutor(), url, "openid=" + openid + "&lang=" + lang);
- return WxMpUser.fromJson(responseContent);
- }
-
- public WxMpUserList userList(String next_openid) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/user/get";
- String responseContent = execute(new SimpleGetRequestExecutor(), url, next_openid == null ? null : "next_openid=" + next_openid);
- return WxMpUserList.fromJson(responseContent);
- }
-
- public WxMpQrCodeTicket qrCodeCreateTmpTicket(int scene_id, Integer expire_seconds) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create";
- JsonObject json = new JsonObject();
- json.addProperty("action_name", "QR_SCENE");
- if(expire_seconds != null) {
- json.addProperty("expire_seconds", expire_seconds);
- }
- JsonObject actionInfo = new JsonObject();
- JsonObject scene = new JsonObject();
- scene.addProperty("scene_id", scene_id);
- actionInfo.add("scene", scene);
- json.add("action_info", actionInfo);
- String responseContent = execute(new SimplePostRequestExecutor(), url, json.toString());
- return WxMpQrCodeTicket.fromJson(responseContent);
- }
-
- public WxMpQrCodeTicket qrCodeCreateLastTicket(int scene_id) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create";
- JsonObject json = new JsonObject();
- json.addProperty("action_name", "QR_LIMIT_SCENE");
- JsonObject actionInfo = new JsonObject();
- JsonObject scene = new JsonObject();
- scene.addProperty("scene_id", scene_id);
- actionInfo.add("scene", scene);
- json.add("action_info", actionInfo);
- String responseContent = execute(new SimplePostRequestExecutor(), url, json.toString());
- return WxMpQrCodeTicket.fromJson(responseContent);
- }
-
- public File qrCodePicture(WxMpQrCodeTicket ticket) throws WxErrorException {
- String url = "https://mp.weixin.qq.com/cgi-bin/showqrcode";
- return execute(new QrCodeRequestExecutor(), url, ticket);
- }
-
- public String shortUrl(String long_url) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/shorturl";
- JsonObject o = new JsonObject();
- o.addProperty("action", "long2short");
- o.addProperty("long_url", long_url);
- String responseContent = execute(new SimplePostRequestExecutor(), url, o.toString());
- JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
- return tmpJsonElement.getAsJsonObject().get("short_url").getAsString();
- }
- public String templateSend(WxMpTemplateMessage templateMessage) throws WxErrorException {
- String url = "https://api.weixin.qq.com/cgi-bin/message/template/send";
- String responseContent = execute(new SimplePostRequestExecutor(), url, templateMessage.toJson());
- JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
- return tmpJsonElement.getAsJsonObject().get("msgid").getAsString();
- }
- public WxMpSemanticQueryResult semanticQuery(WxMpSemanticQuery semanticQuery) throws WxErrorException {
- String url = "https://api.weixin.qq.com/semantic/semproxy/search";
- String responseContent = execute(new SimplePostRequestExecutor(), url, semanticQuery.toJson());
- return WxMpSemanticQueryResult.fromJson(responseContent);
- }
- @Override
- public String oauth2buildAuthorizationUrl(String scope, String state) {
- String url = "https://open.weixin.qq.com/connect/oauth2/authorize?" ;
- url += "appid=" + wxMpConfigStorage.getAppId();
- url += "&redirect_uri=" + URIUtil.encodeURIComponent(wxMpConfigStorage.getOauth2redirectUri());
- url += "&response_type=code";
- url += "&scope=" + scope;
- if (state != null) {
- url += "&state=" + state;
- }
- url += "#wechat_redirect";
- return url;
- }
- @Override
- public WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException {
- String url = "https://api.weixin.qq.com/sns/oauth2/access_token?";
- url += "appid=" + wxMpConfigStorage.getAppId();
- url += "&secret=" + wxMpConfigStorage.getSecret();
- url += "&code=" + code;
- url += "&grant_type=authorization_code";
- try {
- RequestExecutor<String, String> executor = new SimpleGetRequestExecutor();
- String responseText = executor.execute(getHttpclient(), httpProxy, url, null);
- return WxMpOAuth2AccessToken.fromJson(responseText);
- } catch (ClientProtocolException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- @Override
- public WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException {
- String url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?";
- url += "appid=" + wxMpConfigStorage.getAppId();
- url += "&grant_type=refresh_token";
- url += "&refresh_token=" + refreshToken;
- try {
- RequestExecutor<String, String> executor = new SimpleGetRequestExecutor();
- String responseText = executor.execute(getHttpclient(), httpProxy, url, null);
- return WxMpOAuth2AccessToken.fromJson(responseText);
- } catch (ClientProtocolException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- @Override
- public WxMpUser oauth2getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException {
- String url = "https://api.weixin.qq.com/sns/userinfo?";
- url += "access_token=" + oAuth2AccessToken.getAccessToken();
- url += "&openid=" + oAuth2AccessToken.getOpenId();
- if (lang == null) {
- url += "&lang=zh_CN";
- } else {
- url += "&lang=" + lang;
- }
- try {
- RequestExecutor<String, String> executor = new SimpleGetRequestExecutor();
- String responseText = executor.execute(getHttpclient(), httpProxy, url, null);
- return WxMpUser.fromJson(responseText);
- } catch (ClientProtocolException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- @Override
- public boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken) {
- String url = "https://api.weixin.qq.com/sns/auth?";
- url += "access_token=" + oAuth2AccessToken;
- url += "&openid=" + oAuth2AccessToken.getOpenId();
- try {
- RequestExecutor<String, String> executor = new SimpleGetRequestExecutor();
- executor.execute(getHttpclient(), httpProxy, url, null);
- } catch (ClientProtocolException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- } catch (WxErrorException e) {
- return false;
- }
- return true;
- }
- public String get(String url, String queryParam) throws WxErrorException {
- return execute(new SimpleGetRequestExecutor(), url, queryParam);
- }
- public String post(String url, String postData) throws WxErrorException {
- return execute(new SimplePostRequestExecutor(), url, postData);
- }
- /**
- * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
- * @param executor
- * @param uri
- * @param data
- * @return
- * @throws WxErrorException
- */
- public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
- int retryTimes = 0;
- do {
- try {
- return executeInternal(executor, uri, data);
- } catch (WxErrorException e) {
- WxError error = e.getError();
- /**
- * -1 系统繁忙, 1000ms后重试
- */
- if (error.getErrorCode() == -1) {
- int sleepMillis = retrySleepMillis * (1 << retryTimes);
- try {
- log.debug("微信系统繁忙,{}ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
- Thread.sleep(sleepMillis);
- } catch (InterruptedException e1) {
- throw new RuntimeException(e1);
- }
- } else {
- throw e;
- }
- }
- } while(++retryTimes < maxRetryTimes);
- throw new RuntimeException("微信服务端异常,超出重试次数");
- }
- protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
- String accessToken = getAccessToken(false);
-
- String uriWithAccessToken = uri;
- uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
-
- try {
- return executor.execute(getHttpclient(), httpProxy, uriWithAccessToken, data);
- } catch (WxErrorException e) {
- WxError error = e.getError();
- /*
- * 发生以下情况时尝试刷新access_token
- * 40001 获取access_token时AppSecret错误,或者access_token无效
- * 42001 access_token超时
- */
- if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
- // 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
- wxMpConfigStorage.expireAccessToken();
- return execute(executor, uri, data);
- }
- if (error.getErrorCode() != 0) {
- throw new WxErrorException(error);
- }
- return null;
- } catch (ClientProtocolException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- protected CloseableHttpClient getHttpclient() {
- return httpClient;
- }
- public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) {
- this.wxMpConfigStorage = wxConfigProvider;
- String http_proxy_host = wxMpConfigStorage.getHttp_proxy_host();
- int http_proxy_port = wxMpConfigStorage.getHttp_proxy_port();
- String http_proxy_username = wxMpConfigStorage.getHttp_proxy_username();
- String http_proxy_password = wxMpConfigStorage.getHttp_proxy_password();
- if(StringUtils.isNotBlank(http_proxy_host)) {
- // 使用代理服务器
- if(StringUtils.isNotBlank(http_proxy_username)) {
- // 需要用户认证的代理服务器
- CredentialsProvider credsProvider = new BasicCredentialsProvider();
- credsProvider.setCredentials(
- new AuthScope(http_proxy_host, http_proxy_port),
- new UsernamePasswordCredentials(http_proxy_username, http_proxy_password));
- httpClient = HttpClients
- .custom()
- .setDefaultCredentialsProvider(credsProvider)
- .build();
- } else {
- // 无需用户认证的代理服务器
- httpClient = HttpClients.createDefault();
- }
- httpProxy = new HttpHost(http_proxy_host, http_proxy_port);
- } else {
- httpClient = HttpClients.createDefault();
- }
- }
- @Override
- public void setRetrySleepMillis(int retrySleepMillis) {
- this.retrySleepMillis = retrySleepMillis;
- }
- @Override
- public void setMaxRetryTimes(int maxRetryTimes) {
- this.maxRetryTimes = maxRetryTimes;
- }
- }
|