Skip to content

Commit a8933c5

Browse files
committed
#901 企业微信增加获取用于计算agentConfig签名的应用jsapi_ticket的接口
1 parent 7538b8e commit a8933c5

File tree

6 files changed

+185
-36
lines changed

6 files changed

+185
-36
lines changed

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818
public interface WxCpService {
1919
String GET_JSAPI_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket";
20+
String GET_AGENT_CONFIG_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?&type=agent_config";
2021
String MESSAGE_SEND = "https://qyapi.weixin.qq.com/cgi-bin/message/send";
2122
String GET_CALLBACK_IP = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip";
2223
String BATCH_REPLACE_PARTY = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty";
@@ -75,6 +76,33 @@ public interface WxCpService {
7576
*/
7677
String getJsapiTicket(boolean forceRefresh) throws WxErrorException;
7778

79+
/**
80+
* 获得jsapi_ticket,不强制刷新jsapi_ticket
81+
* 应用的jsapi_ticket用于计算agentConfig(参见“通过agentConfig注入应用的权限”)的签名,签名计算方法与上述介绍的config的签名算法完全相同,但需要注意以下区别:
82+
*
83+
* 签名的jsapi_ticket必须使用以下接口获取。且必须用wx.agentConfig中的agentid对应的应用secret去获取access_token。
84+
* 签名用的noncestr和timestamp必须与wx.agentConfig中的nonceStr和timestamp相同。
85+
* @see #getJsapiTicket(boolean)
86+
*/
87+
String getAgentJsapiTicket() throws WxErrorException;
88+
89+
/**
90+
* <pre>
91+
* 获取应用的jsapi_ticket
92+
* 应用的jsapi_ticket用于计算agentConfig(参见“通过agentConfig注入应用的权限”)的签名,签名计算方法与上述介绍的config的签名算法完全相同,但需要注意以下区别:
93+
*
94+
* 签名的jsapi_ticket必须使用以下接口获取。且必须用wx.agentConfig中的agentid对应的应用secret去获取access_token。
95+
* 签名用的noncestr和timestamp必须与wx.agentConfig中的nonceStr和timestamp相同。
96+
*
97+
* 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
98+
*
99+
* 详情请见:https://work.weixin.qq.com/api/doc#10029/%E8%8E%B7%E5%8F%96%E5%BA%94%E7%94%A8%E7%9A%84jsapi_ticket
100+
* </pre>
101+
*
102+
* @param forceRefresh 强制刷新
103+
*/
104+
String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException;
105+
78106
/**
79107
* <pre>
80108
* 创建调用jsapi时所需要的签名

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
package me.chanjar.weixin.cp.api.impl;
22

3-
import java.io.File;
4-
import java.io.IOException;
5-
6-
import org.slf4j.Logger;
7-
import org.slf4j.LoggerFactory;
8-
93
import com.google.gson.JsonArray;
104
import com.google.gson.JsonElement;
115
import com.google.gson.JsonObject;
@@ -23,18 +17,15 @@
2317
import me.chanjar.weixin.common.util.http.RequestHttp;
2418
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
2519
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
26-
import me.chanjar.weixin.cp.api.WxCpAgentService;
27-
import me.chanjar.weixin.cp.api.WxCpChatService;
28-
import me.chanjar.weixin.cp.api.WxCpDepartmentService;
29-
import me.chanjar.weixin.cp.api.WxCpMediaService;
30-
import me.chanjar.weixin.cp.api.WxCpMenuService;
31-
import me.chanjar.weixin.cp.api.WxCpOAuth2Service;
32-
import me.chanjar.weixin.cp.api.WxCpService;
33-
import me.chanjar.weixin.cp.api.WxCpTagService;
34-
import me.chanjar.weixin.cp.api.WxCpUserService;
20+
import me.chanjar.weixin.cp.api.*;
3521
import me.chanjar.weixin.cp.bean.WxCpMessage;
3622
import me.chanjar.weixin.cp.bean.WxCpMessageSendResult;
3723
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
import java.io.File;
28+
import java.io.IOException;
3829

3930
/**
4031
* @author chanjarster
@@ -61,14 +52,19 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
6152
*/
6253
protected final Object globalJsapiTicketRefreshLock = new Object();
6354

55+
/**
56+
* 全局的是否正在刷新agent的jsapi_ticket的锁
57+
*/
58+
protected final Object globalAgentJsapiTicketRefreshLock = new Object();
59+
6460
protected WxCpConfigStorage configStorage;
6561

62+
private WxSessionManager sessionManager = new StandardSessionManager();
6663

67-
protected WxSessionManager sessionManager = new StandardSessionManager();
6864
/**
6965
* 临时文件目录
7066
*/
71-
protected File tmpDirFile;
67+
private File tmpDirFile;
7268
private int retrySleepMillis = 1000;
7369
private int maxRetryTimes = 5;
7470

@@ -88,6 +84,30 @@ public String getAccessToken() throws WxErrorException {
8884
return getAccessToken(false);
8985
}
9086

87+
@Override
88+
public String getAgentJsapiTicket() throws WxErrorException {
89+
return this.getAgentJsapiTicket(false);
90+
}
91+
92+
@Override
93+
public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException {
94+
if (forceRefresh) {
95+
this.configStorage.expireAgentJsapiTicket();
96+
}
97+
98+
if (this.configStorage.isAgentJsapiTicketExpired()) {
99+
synchronized (this.globalAgentJsapiTicketRefreshLock) {
100+
if (this.configStorage.isAgentJsapiTicketExpired()) {
101+
String responseContent = this.get(WxCpService.GET_AGENT_CONFIG_TICKET, null);
102+
JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject();
103+
this.configStorage.updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(),
104+
jsonObject.get("expires_in").getAsInt());
105+
}
106+
}
107+
}
108+
109+
return this.configStorage.getAgentJsapiTicket();
110+
}
91111

92112
@Override
93113
public String getJsapiTicket() throws WxErrorException {
@@ -99,19 +119,18 @@ public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
99119
if (forceRefresh) {
100120
this.configStorage.expireJsapiTicket();
101121
}
122+
102123
if (this.configStorage.isJsapiTicketExpired()) {
103124
synchronized (this.globalJsapiTicketRefreshLock) {
104125
if (this.configStorage.isJsapiTicketExpired()) {
105-
String responseContent = execute(SimpleGetRequestExecutor.create(this), WxCpService.GET_JSAPI_TICKET, null);
106-
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
107-
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
108-
String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
109-
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
110-
this.configStorage.updateJsapiTicket(jsapiTicket,
111-
expiresInSeconds);
126+
String responseContent = this.get(WxCpService.GET_JSAPI_TICKET, null);
127+
JsonObject tmpJsonObject = new JsonParser().parse(responseContent).getAsJsonObject();
128+
this.configStorage.updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(),
129+
tmpJsonObject.get("expires_in").getAsInt());
112130
}
113131
}
114132
}
133+
115134
return this.configStorage.getJsapiTicket();
116135
}
117136

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,23 @@ public interface WxCpConfigStorage {
3636

3737
/**
3838
* 应该是线程安全的
39-
*
40-
* @param jsapiTicket
4139
*/
4240
void updateJsapiTicket(String jsapiTicket, int expiresInSeconds);
4341

42+
String getAgentJsapiTicket();
43+
44+
boolean isAgentJsapiTicketExpired();
45+
46+
/**
47+
* 强制将jsapi ticket过期掉
48+
*/
49+
void expireAgentJsapiTicket();
50+
51+
/**
52+
* 应该是线程安全的
53+
*/
54+
void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds);
55+
4456
String getCorpId();
4557

4658
String getCorpSecret();

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package me.chanjar.weixin.cp.config;
22

3-
import java.io.File;
4-
53
import me.chanjar.weixin.common.bean.WxAccessToken;
64
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
75
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
86

7+
import java.io.File;
8+
99
/**
1010
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化
1111
*
@@ -32,6 +32,9 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
3232
protected volatile String jsapiTicket;
3333
protected volatile long jsapiTicketExpiresTime;
3434

35+
protected volatile String agentJsapiTicket;
36+
protected volatile long agentJsapiTicketExpiresTime;
37+
3538
protected volatile File tmpDirFile;
3639

3740
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
@@ -95,6 +98,28 @@ public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeco
9598
this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
9699
}
97100

101+
@Override
102+
public String getAgentJsapiTicket() {
103+
return this.agentJsapiTicket;
104+
}
105+
106+
@Override
107+
public boolean isAgentJsapiTicketExpired() {
108+
return System.currentTimeMillis() > this.agentJsapiTicketExpiresTime;
109+
}
110+
111+
@Override
112+
public void expireAgentJsapiTicket() {
113+
this.agentJsapiTicketExpiresTime = 0;
114+
}
115+
116+
@Override
117+
public void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds) {
118+
this.agentJsapiTicket = jsapiTicket;
119+
// 预留200秒的时间
120+
this.agentJsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
121+
}
122+
98123
@Override
99124
public void expireJsapiTicket() {
100125
this.jsapiTicketExpiresTime = 0;

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage {
2626
private static final String ACCESS_TOKEN_EXPIRES_TIME_KEY = "WX_CP_ACCESS_TOKEN_EXPIRES_TIME";
2727
private static final String JS_API_TICKET_KEY = "WX_CP_JS_API_TICKET";
2828
private static final String JS_API_TICKET_EXPIRES_TIME_KEY = "WX_CP_JS_API_TICKET_EXPIRES_TIME";
29+
private static final String AGENT_JSAPI_TICKET_KEY = "WX_CP_AGENT_%s_JSAPI_TICKET";
30+
private static final String AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY = "WX_CP_AGENT_%s_JSAPI_TICKET_EXPIRES_TIME";
2931
/**
3032
* Redis clients pool
3133
*/
@@ -46,7 +48,7 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage {
4648
public WxCpJedisConfigStorage(JedisPool jedisPool) {
4749
this.jedisPool = jedisPool;
4850
}
49-
51+
5052
public WxCpJedisConfigStorage(String host, int port) {
5153
jedisPool = new JedisPool(host, port);
5254
}
@@ -83,8 +85,7 @@ public boolean isAccessTokenExpired() {
8385
String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY);
8486

8587
if (expiresTimeStr != null) {
86-
Long expiresTime = Long.parseLong(expiresTimeStr);
87-
return System.currentTimeMillis() > expiresTime;
88+
return System.currentTimeMillis() > Long.parseLong(expiresTimeStr);
8889
}
8990

9091
return true;
@@ -123,17 +124,15 @@ public String getJsapiTicket() {
123124

124125
@Override
125126
public boolean isJsapiTicketExpired() {
126-
127127
try (Jedis jedis = this.jedisPool.getResource()) {
128128
String expiresTimeStr = jedis.get(JS_API_TICKET_EXPIRES_TIME_KEY);
129129

130130
if (expiresTimeStr != null) {
131-
Long expiresTime = Long.parseLong(expiresTimeStr);
131+
long expiresTime = Long.parseLong(expiresTimeStr);
132132
return System.currentTimeMillis() > expiresTime;
133133
}
134134

135135
return true;
136-
137136
}
138137
}
139138

@@ -146,16 +145,51 @@ public void expireJsapiTicket() {
146145

147146
@Override
148147
public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) {
149-
150148
try (Jedis jedis = this.jedisPool.getResource()) {
151149
jedis.set(JS_API_TICKET_KEY, jsapiTicket);
152-
153150
jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY,
154151
(System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + ""));
155152
}
156153

157154
}
158155

156+
@Override
157+
public String getAgentJsapiTicket() {
158+
try (Jedis jedis = this.jedisPool.getResource()) {
159+
return jedis.get(String.format(AGENT_JSAPI_TICKET_KEY, agentId));
160+
}
161+
}
162+
163+
@Override
164+
public boolean isAgentJsapiTicketExpired() {
165+
try (Jedis jedis = this.jedisPool.getResource()) {
166+
String expiresTimeStr = jedis.get(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId));
167+
168+
if (expiresTimeStr != null) {
169+
return System.currentTimeMillis() > Long.parseLong(expiresTimeStr);
170+
}
171+
172+
return true;
173+
}
174+
}
175+
176+
@Override
177+
public void expireAgentJsapiTicket() {
178+
try (Jedis jedis = this.jedisPool.getResource()) {
179+
jedis.set(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId), "0");
180+
}
181+
}
182+
183+
@Override
184+
public void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds) {
185+
try (Jedis jedis = this.jedisPool.getResource()) {
186+
jedis.set(String.format(AGENT_JSAPI_TICKET_KEY, agentId), jsapiTicket);
187+
jedis.set(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId),
188+
(System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + ""));
189+
}
190+
191+
}
192+
159193
@Override
160194
public String getCorpId() {
161195
return this.corpId;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package me.chanjar.weixin.cp.api.impl;
2+
3+
import com.google.inject.Inject;
4+
import me.chanjar.weixin.common.error.WxErrorException;
5+
import me.chanjar.weixin.cp.api.ApiTestModule;
6+
import me.chanjar.weixin.cp.api.WxCpService;
7+
import org.testng.annotations.Guice;
8+
import org.testng.annotations.Test;
9+
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
import static org.testng.Assert.*;
12+
13+
/**
14+
* <pre>
15+
* Created by BinaryWang on 2019/3/31.
16+
* </pre>
17+
*
18+
* @author <a href="https://github.com/binarywang">Binary Wang</a>
19+
*/
20+
@Test
21+
@Guice(modules = ApiTestModule.class)
22+
public class BaseWxCpServiceImplTest {
23+
@Inject
24+
protected WxCpService wxService;
25+
26+
@Test
27+
public void testGetAgentJsapiTicket() throws WxErrorException {
28+
assertThat(this.wxService.getAgentJsapiTicket()).isNotEmpty();
29+
assertThat(this.wxService.getAgentJsapiTicket(true)).isNotEmpty();
30+
}
31+
}

0 commit comments

Comments
 (0)