View Javadoc
1   package com.foxinmy.weixin4j.mp.api;
2   
3   import java.io.UnsupportedEncodingException;
4   import java.net.URLEncoder;
5   import java.util.ArrayList;
6   import java.util.List;
7   
8   import com.alibaba.fastjson.JSON;
9   import com.alibaba.fastjson.JSONArray;
10  import com.alibaba.fastjson.JSONObject;
11  import com.foxinmy.weixin4j.exception.WeixinException;
12  import com.foxinmy.weixin4j.http.weixin.ApiResult;
13  import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
14  import com.foxinmy.weixin4j.mp.component.WeixinComponentPreCodeCreator;
15  import com.foxinmy.weixin4j.mp.component.WeixinComponentTokenCreator;
16  import com.foxinmy.weixin4j.mp.component.WeixinTokenComponentCreator;
17  import com.foxinmy.weixin4j.mp.model.AuthorizerOption;
18  import com.foxinmy.weixin4j.mp.model.AuthorizerOption.AuthorizerOptionName;
19  import com.foxinmy.weixin4j.mp.model.ComponentAuthorizer;
20  import com.foxinmy.weixin4j.mp.model.ComponentAuthorizerToken;
21  import com.foxinmy.weixin4j.mp.model.OauthToken;
22  import com.foxinmy.weixin4j.token.PerTicketManager;
23  import com.foxinmy.weixin4j.token.TicketManager;
24  import com.foxinmy.weixin4j.token.TokenCreator;
25  import com.foxinmy.weixin4j.token.TokenManager;
26  import com.foxinmy.weixin4j.util.Consts;
27  import com.foxinmy.weixin4j.util.Weixin4jConfigUtil;
28  
29  /**
30   * 第三方应用组件
31   *
32   * @className ComponentApi
33   * @author jinyu(foxinmy@gmail.com)
34   * @date 2015年6月17日
35   * @since JDK 1.6
36   * @see <a href=
37   *      "https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1453779503&token=&lang=zh_CN">
38   *      第三方应用组件概述</a>
39   */
40  public class ComponentApi extends MpApi {
41  	/**
42  	 * 应用套件token
43  	 */
44  	private final TokenManager tokenManager;
45  	/**
46  	 * 应用套件ticket
47  	 */
48  	private final TicketManager ticketManager;
49  	/**
50  	 * 应用套件pre_code
51  	 */
52  	private final TokenManager preCodeManager;
53  
54  	/**
55  	 *
56  	 * @param ticketManager
57  	 *            组件ticket存取
58  	 */
59  	public ComponentApi(TicketManager ticketManager) {
60  		this.ticketManager = ticketManager;
61  		this.tokenManager = new TokenManager(new WeixinComponentTokenCreator(ticketManager),
62  				ticketManager.getCacheStorager());
63  		this.preCodeManager = new TokenManager(
64  				new WeixinComponentPreCodeCreator(tokenManager, ticketManager.getThirdId()),
65  				ticketManager.getCacheStorager());
66  	}
67  
68  	/**
69  	 * 应用组件token
70  	 *
71  	 * @return 应用组件的token管理
72  	 */
73  	public TokenManager getTokenManager() {
74  		return this.tokenManager;
75  	}
76  
77  	/**
78  	 * 应用组件ticket
79  	 *
80  	 * @return 应用组件的ticket管理
81  	 */
82  	public TicketManager getTicketManager() {
83  		return this.ticketManager;
84  	}
85  
86  	/**
87  	 * 应用组件预授权码
88  	 *
89  	 * @return 应用组件的precode管理
90  	 */
91  	public TokenManager getPreCodeManager() {
92  		return this.preCodeManager;
93  	}
94  
95  	/**
96  	 * 应用套组件永久刷新令牌:刷新令牌主要用于公众号第三方平台获取和刷新已授权用户的access_token,只会在授权时刻提供,请妥善保存。
97  	 * 一旦丢失,只能让用户重新授权,才能再次拿到新的刷新令牌
98  	 *
99  	 * @param authAppId
100 	 *            授权方appid
101 	 * @return 应用组件的perticket管理
102 	 */
103 	public PerTicketManager getRefreshTokenManager(String authAppId) {
104 		return new PerTicketManager(authAppId, ticketManager.getThirdId(), ticketManager.getThirdSecret(),
105 				ticketManager.getCacheStorager());
106 	}
107 
108 	/**
109 	 * 第三方组件代替授权公众号发起网页授权:获取code
110 	 * <li>redirectUri默认填写weixin4j.properties#component.user.oauth.redirect.uri
111 	 * <li>scope默认填写snsapi_base
112 	 * <li>state默认填写state
113 	 * 
114 	 * @param authAppId
115 	 *            公众号的appid
116 	 * @see #getAuthorizationURL(String, String, String, String)
117 	 * @see <a href=
118 	 *      "https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590&token=&lang=zh_CN">
119 	 *      第三方组件代替授权公众号发起网页授权</a>
120 	 */
121 	public String getUserAuthorizationURL(String authAppId) {
122 		String redirectUri = Weixin4jConfigUtil.getValue("component.user.oauth.redirect.uri");
123 		return getUserAuthorizationURL(authAppId, redirectUri, "snsapi_base", "state");
124 	}
125 
126 	/**
127 	 * 第三方组件代替授权公众号发起网页授权:获取code
128 	 * 
129 	 * @param authAppId
130 	 *            公众号的appid
131 	 * @param redirectUri
132 	 *            重定向地址,这里填写的应是服务开发方的回调地址
133 	 * @param scope
134 	 *            应用授权作用域,snsapi_base/snsapi_userinfo
135 	 * @param state
136 	 *            重定向后会带上state参数,开发者可以填写任意参数值,最多128字节
137 	 * @return oauth授权URL
138 	 * @see <a href=
139 	 *      "https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590&token=&lang=zh_CN">
140 	 *      第三方组件代替授权公众号发起网页授权</a>
141 	 */
142 	public String getUserAuthorizationURL(String authAppId, String redirectUri, String scope, String state) {
143 		String sns_component_user_auth_uri = getRequestUri("sns_component_user_auth_uri");
144 		try {
145 			return String.format(sns_component_user_auth_uri, authAppId,
146 					URLEncoder.encode(redirectUri, Consts.UTF_8.name()), scope, state, this.ticketManager.getThirdId());
147 		} catch (UnsupportedEncodingException e) {
148 			;
149 		}
150 		return "";
151 	}
152 
153 	/**
154 	 * 第三方组件代替授权公众号发起网页授权:换取token
155 	 * 
156 	 * @param authAppId
157 	 *            公众号的appid
158 	 * @param code
159 	 *            用户同意授权获取的code
160 	 * @return token信息
161 	 * @see #getUserAuthorizationURL(String, String, String, String)
162 	 * @see com.foxinmy.weixin4j.mp.model.OauthToken
163 	 * @throws WeixinException
164 	 */
165 	public OauthToken getAuthorizationToken(String authAppId, String code) throws WeixinException {
166 		String sns_component_user_token_uri = getRequestUri("sns_component_user_token_uri");
167 		String accessToken = tokenManager.getAccessToken();
168 		WeixinResponse response = weixinExecutor.get(
169 				String.format(sns_component_user_token_uri, authAppId, code, ticketManager.getThirdId(), accessToken));
170 		JSONObject result = response.getAsJson();
171 		OauthToken token = new OauthToken(result.getString("access_token"), result.getLongValue("expires_in") * 1000l);
172 		token.setOpenId(result.getString("openid"));
173 		token.setScope(result.getString("scope"));
174 		token.setRefreshToken(result.getString("refresh_token"));
175 		return token;
176 	}
177 
178 	/**
179 	 * 第三方组件代替授权公众号发起网页授权:刷新token
180 	 * 
181 	 * @param authAppId
182 	 *            公众号的appid
183 	 * @param refreshToken
184 	 *            填写通过access_token获取到的refresh_token参数
185 	 * @return token信息
186 	 * @see #getAuthorizationToken(String, String)
187 	 * @see OauthApi#getAuthorizationUser(OauthToken)
188 	 * @see com.foxinmy.weixin4j.mp.model.OauthToken
189 	 * @throws WeixinException
190 	 */
191 	public OauthToken refreshAuthorizationToken(String authAppId, String refreshToken) throws WeixinException {
192 		String sns_component_token_refresh_uri = getRequestUri("sns_component_token_refresh_uri");
193 		String accessToken = tokenManager.getAccessToken();
194 		WeixinResponse response = weixinExecutor.get(String.format(sns_component_token_refresh_uri, authAppId,
195 				ticketManager.getThirdId(), accessToken, refreshToken));
196 		JSONObject result = response.getAsJson();
197 		OauthToken token = new OauthToken(result.getString("access_token"), result.getLongValue("expires_in") * 1000l);
198 		token.setOpenId(result.getString("openid"));
199 		token.setScope(result.getString("scope"));
200 		token.setRefreshToken(result.getString("refresh_token"));
201 		return token;
202 	}
203 
204 	/**
205 	 * 使用授权码换取公众号的接口调用凭据和授权信息:用于使用授权码换取授权公众号的授权信息,
206 	 * 并换取authorizer_access_token和authorizer_refresh_token。
207 	 * 授权码的获取,需要在用户在第三方平台授权页中完成授权流程后
208 	 * ,在回调URI中通过URL参数提供给第三方平台方。请注意,由于现在公众号可以自定义选择部分权限授权给第三方平台
209 	 * ,因此第三方平台开发者需要通过该接口来获取公众号具体授权了哪些权限,而不是简单地认为自己声明的权限就是公众号授权的权限。
210 	 * 
211 	 * @param authCode
212 	 *            授权code
213 	 * @return 第三方组件授权信息
214 	 * @see {@link com.foxinmy.weixin4j.mp.WeixinComponentProxy#getComponentAuthorizeURL(String, String, String)}
215 	 * @see com.foxinmy.weixin4j.mp.model.ComponentAuthorizerToken
216 	 * @throws WeixinException
217 	 */
218 	public ComponentAuthorizerToken exchangeAuthorizerToken(String authCode) throws WeixinException {
219 		String component_exchange_authorizer_uri = getRequestUri("component_query_authorization_uri");
220 		JSONObject obj = new JSONObject();
221 		obj.put("component_appid", ticketManager.getThirdId());
222 		obj.put("authorization_code", authCode);
223 		WeixinResponse response = weixinExecutor.post(
224 				String.format(component_exchange_authorizer_uri, tokenManager.getAccessToken()), obj.toJSONString());
225 		JSONObject authObj = response.getAsJson().getJSONObject("authorization_info");
226 		JSONArray privilegesObj = authObj.getJSONArray("func_info");
227 		List<Integer> privileges = new ArrayList<Integer>(privilegesObj.size());
228 		for (int i = 0; i < privilegesObj.size(); i++) {
229 			privileges.add(privilegesObj.getJSONObject(i).getJSONObject("funcscope_category").getInteger("id"));
230 		}
231 		ComponentAuthorizerToken token = new ComponentAuthorizerToken(authObj.getString("authorizer_access_token"),
232 				authObj.getLongValue("expires_in") * 1000l);
233 		token.setRefreshToken(authObj.getString("authorizer_refresh_token"));
234 		token.setPrivileges(privileges);
235 		token.setAppId(authObj.getString("authorizer_appid"));
236 		// 微信授权公众号的永久刷新令牌
237 		PerTicketManager perTicketManager = getRefreshTokenManager(token.getAppId());
238 		// 缓存微信公众号的access_token
239 		TokenCreator tokenCreator = new WeixinTokenComponentCreator(perTicketManager, tokenManager);
240 		ticketManager.getCacheStorager().caching(tokenCreator.key(), token);
241 		// 缓存微信公众号的永久授权码(refresh_token)
242 		perTicketManager.cachingTicket(token.getRefreshToken());
243 		return token;
244 	}
245 
246 	/**
247 	 * 获取授权方的公众号帐号基本信息:获取授权方的公众号基本信息,包括头像、昵称、帐号类型、认证类型、微信号、原始ID和二维码图片URL。
248 	 * 需要特别记录授权方的帐号类型,在消息及事件推送时,对于不具备客服接口的公众号,需要在5秒内立即响应;而若有客服接口,则可以选择暂时不响应,
249 	 * 而选择后续通过客服接口来发送消息触达粉丝
250 	 * 
251 	 * @param authAppId
252 	 *            授权方appid
253 	 * @return 第三方组件授权信息
254 	 * @see com.foxinmy.weixin4j.mp.model.ComponentAuthorizer
255 	 * @throws WeixinException
256 	 */
257 	public ComponentAuthorizer getAuthorizerInfo(String authAppId) throws WeixinException {
258 		String component_get_authorizer_uri = getRequestUri("component_get_authorizer_uri");
259 		JSONObject obj = new JSONObject();
260 		obj.put("component_appid", ticketManager.getThirdId());
261 		obj.put("authorizer_appid", authAppId);
262 		WeixinResponse response = weixinExecutor
263 				.post(String.format(component_get_authorizer_uri, tokenManager.getAccessToken()), obj.toJSONString());
264 		obj = response.getAsJson();
265 		JSONObject auth = obj.getJSONObject("authorizer_info");
266 		ComponentAuthorizer authorizer = JSON.toJavaObject(auth, ComponentAuthorizer.class);
267 		authorizer.setServiceType(auth.getJSONObject("service_type_info").getIntValue("id"));
268 		authorizer.setVerifyType(auth.getJSONObject("verify_type_info").getIntValue("id"));
269 		auth = obj.getJSONObject("authorization_info");
270 		JSONArray privilegesObj = auth.getJSONArray("func_info");
271 		List<Integer> privileges = new ArrayList<Integer>(privilegesObj.size());
272 		for (int i = 0; i < privilegesObj.size(); i++) {
273 			privileges.add(privilegesObj.getJSONObject(i).getJSONObject("funcscope_category").getInteger("id"));
274 		}
275 		authorizer.setPrivileges(privileges);
276 		authorizer.setAppId(auth.getString("appid"));
277 		return authorizer;
278 	}
279 
280 	/**
281 	 * 获取授权方的公众号的选项设置信息,如:地理位置上报,语音识别开关,多客服开关。注意,获取各项选项设置信息
282 	 * 
283 	 * @param authAppId
284 	 *            授权方appid
285 	 * @param option
286 	 *            选项名称
287 	 * @return 选项信息
288 	 * @see com.foxinmy.weixin4j.mp.model.AuthorizerOption
289 	 * @throws WeixinException
290 	 */
291 	public AuthorizerOption getAuthorizerOption(String authAppId, AuthorizerOptionName optionName)
292 			throws WeixinException {
293 		String component_get_authorizer_option_uri = getRequestUri("component_get_authorizer_option_uri");
294 		JSONObject obj = new JSONObject();
295 		obj.put("component_appid", ticketManager.getThirdId());
296 		obj.put("authorizer_appid", authAppId);
297 		obj.put("option_name", optionName.name());
298 		WeixinResponse response = weixinExecutor.post(
299 				String.format(component_get_authorizer_option_uri, tokenManager.getAccessToken()), obj.toJSONString());
300 		int optionValue = response.getAsJson().getIntValue("option_value");
301 		return AuthorizerOption.parse(optionName, optionValue);
302 	}
303 
304 	/**
305 	 * 设置授权方的公众号的选项信息,如:地理位置上报,语音识别开关,多客服开关。注意,获取各项选项设置信息
306 	 * 
307 	 * @param option
308 	 *            选项信息
309 	 * @return 设置标识
310 	 * @see com.foxinmy.weixin4j.mp.model.AuthorizerOption
311 	 * @throws WeixinException
312 	 */
313 	public ApiResult setAuthorizerOption(String authAppId, AuthorizerOption option) throws WeixinException {
314 		String component_set_authorizer_option_uri = getRequestUri("component_set_authorizer_option_uri");
315 		JSONObject obj = new JSONObject();
316 		obj.put("component_appid", ticketManager.getThirdId());
317 		obj.put("authorizer_appid", authAppId);
318 		obj.put("option_name", option.getName());
319 		obj.put("option_value", option.getValue());
320 		WeixinResponse response = weixinExecutor.post(
321 				String.format(component_set_authorizer_option_uri, tokenManager.getAccessToken()), obj.toJSONString());
322 		return response.getAsResult();
323 	}
324 }