Browse Source

#901 企业微信增加获取用于计算agentConfig签名的应用jsapi_ticket的接口

master
Binary Wang 5 years ago
parent
commit
a8933c5123
6 changed files with 185 additions and 36 deletions
  1. +28
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
  2. +43
    -24
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
  3. +14
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
  4. +27
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java
  5. +42
    -8
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java
  6. +31
    -0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java

+ 28
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java View File

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

/**
* <pre>
* 获取应用的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
* </pre>
*
* @param forceRefresh 强制刷新
*/
String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException;

/**
* <pre>
* 创建调用jsapi时所需要的签名


+ 43
- 24
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java View File

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



+ 14
- 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java View File

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


+ 27
- 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java View File

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


+ 42
- 8
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java View File

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


+ 31
- 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImplTest.java View File

@@ -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.*;

/**
* <pre>
* Created by BinaryWang on 2019/3/31.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@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();
}
}

Loading…
Cancel
Save