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 BaseWxCpServiceImpl implements 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(); + } +}