diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java index c57f8e95..4cff71e6 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java @@ -17,6 +17,7 @@ import me.chanjar.weixin.cp.config.WxCpConfigStorage; */ public interface WxCpService { String GET_JSAPI_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket"; + String GET_AGENT_CONFIG_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?&type=agent_config"; String MESSAGE_SEND = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; String GET_CALLBACK_IP = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip"; String BATCH_REPLACE_PARTY = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty"; @@ -75,6 +76,33 @@ public interface WxCpService { */ String getJsapiTicket(boolean forceRefresh) throws WxErrorException; + /** + * 获得jsapi_ticket,不强制刷新jsapi_ticket + * 应用的jsapi_ticket用于计算agentConfig(参见“通过agentConfig注入应用的权限”)的签名,签名计算方法与上述介绍的config的签名算法完全相同,但需要注意以下区别: + * + * 签名的jsapi_ticket必须使用以下接口获取。且必须用wx.agentConfig中的agentid对应的应用secret去获取access_token。 + * 签名用的noncestr和timestamp必须与wx.agentConfig中的nonceStr和timestamp相同。 + * @see #getJsapiTicket(boolean) + */ + String getAgentJsapiTicket() throws WxErrorException; + + /** + *
+ * 获取应用的jsapi_ticket + * 应用的jsapi_ticket用于计算agentConfig(参见“通过agentConfig注入应用的权限”)的签名,签名计算方法与上述介绍的config的签名算法完全相同,但需要注意以下区别: + * + * 签名的jsapi_ticket必须使用以下接口获取。且必须用wx.agentConfig中的agentid对应的应用secret去获取access_token。 + * 签名用的noncestr和timestamp必须与wx.agentConfig中的nonceStr和timestamp相同。 + * + * 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干 + * + * 详情请见:https://work.weixin.qq.com/api/doc#10029/%E8%8E%B7%E5%8F%96%E5%BA%94%E7%94%A8%E7%9A%84jsapi_ticket + *+ * + * @param forceRefresh 强制刷新 + */ + String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException; + /** *
* 创建调用jsapi时所需要的签名 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java index b8bcf666..e418982e 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java @@ -1,11 +1,5 @@ package me.chanjar.weixin.cp.api.impl; -import java.io.File; -import java.io.IOException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -23,18 +17,15 @@ 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.WxCpAgentService; -import me.chanjar.weixin.cp.api.WxCpChatService; -import me.chanjar.weixin.cp.api.WxCpDepartmentService; -import me.chanjar.weixin.cp.api.WxCpMediaService; -import me.chanjar.weixin.cp.api.WxCpMenuService; -import me.chanjar.weixin.cp.api.WxCpOAuth2Service; -import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.api.WxCpTagService; -import me.chanjar.weixin.cp.api.WxCpUserService; +import me.chanjar.weixin.cp.api.*; import me.chanjar.weixin.cp.bean.WxCpMessage; import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; /** * @author chanjarster @@ -61,14 +52,19 @@ public abstract class BaseWxCpServiceImplimplements WxCpService, RequestH */ protected final Object globalJsapiTicketRefreshLock = new Object(); + /** + * 全局的是否正在刷新agent的jsapi_ticket的锁 + */ + protected final Object globalAgentJsapiTicketRefreshLock = new Object(); + protected WxCpConfigStorage configStorage; + private WxSessionManager sessionManager = new StandardSessionManager(); - protected WxSessionManager sessionManager = new StandardSessionManager(); /** * 临时文件目录 */ - protected File tmpDirFile; + private File tmpDirFile; private int retrySleepMillis = 1000; private int maxRetryTimes = 5; @@ -88,6 +84,30 @@ public abstract class BaseWxCpServiceImpl implements WxCpService, RequestH 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(WxCpService.GET_AGENT_CONFIG_TICKET, null); + JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); + this.configStorage.updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(), + jsonObject.get("expires_in").getAsInt()); + } + } + } + + return this.configStorage.getAgentJsapiTicket(); + } @Override public String getJsapiTicket() throws WxErrorException { @@ -99,19 +119,18 @@ public abstract class BaseWxCpServiceImpl implements WxCpService, RequestH if (forceRefresh) { this.configStorage.expireJsapiTicket(); } + if (this.configStorage.isJsapiTicketExpired()) { synchronized (this.globalJsapiTicketRefreshLock) { if (this.configStorage.isJsapiTicketExpired()) { - String responseContent = execute(SimpleGetRequestExecutor.create(this), WxCpService.GET_JSAPI_TICKET, null); - JsonElement tmpJsonElement = new JsonParser().parse(responseContent); - JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); - String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); - int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); - this.configStorage.updateJsapiTicket(jsapiTicket, - expiresInSeconds); + String responseContent = this.get(WxCpService.GET_JSAPI_TICKET, null); + JsonObject tmpJsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); + this.configStorage.updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(), + tmpJsonObject.get("expires_in").getAsInt()); } } } + return this.configStorage.getJsapiTicket(); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java index 65dd3aff..e1373814 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java @@ -36,11 +36,23 @@ public interface WxCpConfigStorage { /** * 应该是线程安全的 - * - * @param jsapiTicket */ void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); + String getAgentJsapiTicket(); + + boolean isAgentJsapiTicketExpired(); + + /** + * 强制将jsapi ticket过期掉 + */ + void expireAgentJsapiTicket(); + + /** + * 应该是线程安全的 + */ + void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds); + String getCorpId(); String getCorpSecret(); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java index 1b9f8a61..a501edeb 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java @@ -1,11 +1,11 @@ package me.chanjar.weixin.cp.config; -import java.io.File; - 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,在实际生产环境中应该将这些配置持久化 * @@ -32,6 +32,9 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { protected volatile String jsapiTicket; protected volatile long jsapiTicketExpiresTime; + protected volatile String agentJsapiTicket; + protected volatile long agentJsapiTicketExpiresTime; + protected volatile File tmpDirFile; private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; @@ -95,6 +98,28 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; } + @Override + public String getAgentJsapiTicket() { + return this.agentJsapiTicket; + } + + @Override + public boolean isAgentJsapiTicketExpired() { + return System.currentTimeMillis() > this.agentJsapiTicketExpiresTime; + } + + @Override + public void expireAgentJsapiTicket() { + this.agentJsapiTicketExpiresTime = 0; + } + + @Override + public void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds) { + this.agentJsapiTicket = jsapiTicket; + // 预留200秒的时间 + this.agentJsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; + } + @Override public void expireJsapiTicket() { this.jsapiTicketExpiresTime = 0; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java index 6ae48f0e..7f9fcf65 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java @@ -26,6 +26,8 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { private static final String ACCESS_TOKEN_EXPIRES_TIME_KEY = "WX_CP_ACCESS_TOKEN_EXPIRES_TIME"; private static final String JS_API_TICKET_KEY = "WX_CP_JS_API_TICKET"; private static final String JS_API_TICKET_EXPIRES_TIME_KEY = "WX_CP_JS_API_TICKET_EXPIRES_TIME"; + private static final String AGENT_JSAPI_TICKET_KEY = "WX_CP_AGENT_%s_JSAPI_TICKET"; + private static final String AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY = "WX_CP_AGENT_%s_JSAPI_TICKET_EXPIRES_TIME"; /** * Redis clients pool */ @@ -46,7 +48,7 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { public WxCpJedisConfigStorage(JedisPool jedisPool) { this.jedisPool = jedisPool; } - + public WxCpJedisConfigStorage(String host, int port) { jedisPool = new JedisPool(host, port); } @@ -83,8 +85,7 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY); if (expiresTimeStr != null) { - Long expiresTime = Long.parseLong(expiresTimeStr); - return System.currentTimeMillis() > expiresTime; + return System.currentTimeMillis() > Long.parseLong(expiresTimeStr); } return true; @@ -123,17 +124,15 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { @Override public boolean isJsapiTicketExpired() { - try (Jedis jedis = this.jedisPool.getResource()) { String expiresTimeStr = jedis.get(JS_API_TICKET_EXPIRES_TIME_KEY); if (expiresTimeStr != null) { - Long expiresTime = Long.parseLong(expiresTimeStr); + long expiresTime = Long.parseLong(expiresTimeStr); return System.currentTimeMillis() > expiresTime; } return true; - } } @@ -146,16 +145,51 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { @Override public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { - try (Jedis jedis = this.jedisPool.getResource()) { jedis.set(JS_API_TICKET_KEY, jsapiTicket); - jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY, (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + "")); } } + @Override + public String getAgentJsapiTicket() { + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.get(String.format(AGENT_JSAPI_TICKET_KEY, agentId)); + } + } + + @Override + public boolean isAgentJsapiTicketExpired() { + try (Jedis jedis = this.jedisPool.getResource()) { + String expiresTimeStr = jedis.get(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId)); + + if (expiresTimeStr != null) { + return System.currentTimeMillis() > Long.parseLong(expiresTimeStr); + } + + return true; + } + } + + @Override + public void expireAgentJsapiTicket() { + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.set(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId), "0"); + } + } + + @Override + public void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds) { + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.set(String.format(AGENT_JSAPI_TICKET_KEY, agentId), jsapiTicket); + jedis.set(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId), + (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + "")); + } + + } + @Override public String getCorpId() { return this.corpId; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java new file mode 100644 index 00000000..7470430a --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.inject.Inject; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.ApiTestModule; +import me.chanjar.weixin.cp.api.WxCpService; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.*; + +/** + * + * Created by BinaryWang on 2019/3/31. + *+ * + * @author Binary Wang + */ +@Test +@Guice(modules = ApiTestModule.class) +public class BaseWxCpServiceImplTest { + @Inject + protected WxCpService wxService; + + @Test + public void testGetAgentJsapiTicket() throws WxErrorException { + assertThat(this.wxService.getAgentJsapiTicket()).isNotEmpty(); + assertThat(this.wxService.getAgentJsapiTicket(true)).isNotEmpty(); + } +}