@@ -27,6 +27,12 @@ | |||
<version>6.4.0</version> | |||
</dependency> | |||
--> | |||
<dependency> | |||
<groupId>org.jetbrains</groupId> | |||
<artifactId>annotations</artifactId> | |||
<version>13.0</version> | |||
<scope>compile</scope> | |||
</dependency> | |||
</dependencies> | |||
</project> |
@@ -0,0 +1,295 @@ | |||
package com.iformall.service.toutiao; | |||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | |||
import com.iformall.common.FmHttpClientBuilder; | |||
import com.iformall.domain.po.WxAuthorizerInfo; | |||
import com.iformall.domain.po.WxComponentVerifyTicket; | |||
import com.iformall.mapper.WxAuthorizerInfoMapper; | |||
import com.iformall.mapper.WxComponentVerifyTicketMapper; | |||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage; | |||
import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.scheduling.annotation.Async; | |||
import redis.clients.jedis.Jedis; | |||
import redis.clients.jedis.JedisPool; | |||
import redis.clients.jedis.util.Pool; | |||
import java.util.Date; | |||
public class FmTtOpenInRedisDBConfigStorage extends WxOpenInMemoryConfigStorage { | |||
private final static String COMPONENT_VERIFY_TICKET_KEY = "tt_component_verify_ticket:"; | |||
private final static String COMPONENT_ACCESS_TOKEN_KEY = "tt_component_access_token:"; | |||
private final static String AUTHORIZER_REFRESH_TOKEN_KEY = "tt_authorizer_refresh_token:"; | |||
private final static String AUTHORIZER_ACCESS_TOKEN_KEY = "tt_authorizer_access_token:"; | |||
private final static String JSAPI_TICKET_KEY = "tt_jsapi_ticket:"; | |||
private final static String CARD_API_TICKET_KEY = "tt_card_api_ticket:"; | |||
protected final Pool<Jedis> jedisPool; | |||
private WxComponentVerifyTicketMapper wxComponentVerifyTicketMapper; | |||
private WxAuthorizerInfoMapper wxAuthorizerInfoMapper; | |||
/** | |||
* redis 存储的 key 的前缀,可为空 | |||
*/ | |||
private String keyPrefix; | |||
private String componentVerifyTicketKey; | |||
private String componentAccessTokenKey; | |||
private String authorizerRefreshTokenKey; | |||
private String authorizerAccessTokenKey; | |||
private String jsapiTicketKey; | |||
private String cardApiTicket; | |||
private final Logger logger = LoggerFactory.getLogger(this.getClass()); | |||
public FmTtOpenInRedisDBConfigStorage(Pool<Jedis> jedisPool) { | |||
this.jedisPool = jedisPool; | |||
} | |||
public FmTtOpenInRedisDBConfigStorage(Pool<Jedis> jedisPool, String keyPrefix) { | |||
this.jedisPool = jedisPool; | |||
this.keyPrefix = keyPrefix; | |||
} | |||
public FmTtOpenInRedisDBConfigStorage(JedisPool jedisPool, WxComponentVerifyTicketMapper componentVerifyTicketMapper, WxAuthorizerInfoMapper authorizerInfoMapper) { | |||
this.jedisPool = jedisPool; | |||
this.wxComponentVerifyTicketMapper = componentVerifyTicketMapper; | |||
this.wxAuthorizerInfoMapper = authorizerInfoMapper; | |||
} | |||
@Override | |||
public void setComponentAppId(String componentAppId) { | |||
super.setComponentAppId(componentAppId); | |||
String prefix = StringUtils.isBlank(keyPrefix) ? "" : | |||
(StringUtils.endsWith(keyPrefix, ":") ? keyPrefix : (keyPrefix + ":")); | |||
componentVerifyTicketKey = prefix + COMPONENT_VERIFY_TICKET_KEY.concat(componentAppId); | |||
componentAccessTokenKey = prefix + COMPONENT_ACCESS_TOKEN_KEY.concat(componentAppId); | |||
authorizerRefreshTokenKey = prefix + AUTHORIZER_REFRESH_TOKEN_KEY.concat(componentAppId); | |||
authorizerAccessTokenKey = prefix + AUTHORIZER_ACCESS_TOKEN_KEY.concat(componentAppId); | |||
this.jsapiTicketKey = JSAPI_TICKET_KEY.concat(componentAppId); | |||
this.cardApiTicket = CARD_API_TICKET_KEY.concat(componentAppId); | |||
} | |||
@Override | |||
public String getComponentVerifyTicket() { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
logger.info("getComponentVerifyTicket " + this.componentVerifyTicketKey); | |||
String ticket = jedis.get(this.componentVerifyTicketKey); | |||
if(StringUtils.isBlank(ticket)) { | |||
String appId; | |||
String [] keys = componentAccessTokenKey.split(":"); | |||
if(StringUtils.isBlank(keyPrefix)) { | |||
appId = keys[1]; | |||
} else { | |||
appId = keys[2]; | |||
} | |||
WxComponentVerifyTicket wxComponentVerifyTicket = new WxComponentVerifyTicket(); | |||
wxComponentVerifyTicket.setComponentAppid(getComponentAppId()); | |||
try { | |||
wxComponentVerifyTicket = wxComponentVerifyTicketMapper.selectOne(new QueryWrapper<>(wxComponentVerifyTicket)); | |||
if(wxComponentVerifyTicket != null) { | |||
ticket = wxComponentVerifyTicket.getComponentVerifyTicket(); | |||
} | |||
}catch (Exception e) { | |||
logger.error(e.getMessage()); | |||
} | |||
} | |||
return ticket; | |||
} | |||
} | |||
@Override | |||
public void setComponentVerifyTicket(String componentVerifyTicket) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.set(this.componentVerifyTicketKey, componentVerifyTicket); | |||
logger.info("setComponentVerifyTicket " + componentVerifyTicket); | |||
} | |||
} | |||
@Override | |||
public String getComponentAccessToken() { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
return jedis.get(this.componentAccessTokenKey); | |||
} | |||
} | |||
@Override | |||
public boolean isComponentAccessTokenExpired() { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
return jedis.ttl(this.componentAccessTokenKey) < 2; | |||
} | |||
} | |||
@Override | |||
public void expireComponentAccessToken(){ | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.expire(this.componentAccessTokenKey, 0); | |||
} | |||
} | |||
@Override | |||
public void updateComponentAccessToken(String componentAccessToken, int expiresInSeconds) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.setex(this.componentAccessTokenKey, expiresInSeconds - 200, componentAccessToken); | |||
logger.info("updateComponentVerifyTicketAccessToken " + componentAccessToken); | |||
saveComponentVerifyTicket(componentAccessToken, expiresInSeconds); | |||
} | |||
} | |||
@Async | |||
public void saveComponentVerifyTicket(String componentAccessToken, int expiresInSeconds) { | |||
WxComponentVerifyTicket ticket = new WxComponentVerifyTicket(); | |||
ticket.setComponentAppid(getComponentAppId()); | |||
ticket.setAccessToken(componentAccessToken); | |||
ticket.setAccessTokenExpire(new Date(Long.valueOf(System.currentTimeMillis() + (expiresInSeconds * 1000)))); | |||
int num = wxComponentVerifyTicketMapper.updateAccessToken(ticket); | |||
logger.info("updateComponentVerifyTicketAccessToken rows: " + num); | |||
} | |||
private String getKey(String prefix, String appId) { | |||
return prefix.endsWith(":") ? prefix.concat(appId) : prefix.concat(":").concat(appId); | |||
} | |||
@Override | |||
public String getAuthorizerRefreshToken(String appId) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
return jedis.get(this.getKey(this.authorizerRefreshTokenKey, appId)); | |||
} | |||
} | |||
@Override | |||
public void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.set(this.getKey(this.authorizerRefreshTokenKey, appId), authorizerRefreshToken); | |||
logger.info("setAuthorizerRefreshToken " + appId); | |||
saveAuthorizerRefreshToken(appId, authorizerRefreshToken); | |||
} | |||
} | |||
@Async | |||
public void saveAuthorizerRefreshToken(String appId, String authorizerRefreshToken) { | |||
WxAuthorizerInfo authorizerInfo = new WxAuthorizerInfo(); | |||
authorizerInfo.setAuthorizerAppid(appId); | |||
authorizerInfo.setRefreshToken(authorizerRefreshToken); | |||
int num = wxAuthorizerInfoMapper.updateRefreshToken(authorizerInfo); | |||
} | |||
@Override | |||
public String getAuthorizerAccessToken(String appId) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
return jedis.get(this.getKey(this.authorizerAccessTokenKey, appId)); | |||
} | |||
} | |||
@Override | |||
public boolean isAuthorizerAccessTokenExpired(String appId) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
return jedis.ttl(this.getKey(this.authorizerAccessTokenKey, appId)) < 2; | |||
} | |||
} | |||
@Override | |||
public void expireAuthorizerAccessToken(String appId) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.expire(this.getKey(this.authorizerAccessTokenKey, appId), 0); | |||
} | |||
} | |||
@Override | |||
public void updateAuthorizerAccessToken(String appId, WxOpenAuthorizerAccessToken authorizerAccessToken) { | |||
this.updateAuthorizerAccessToken(appId, authorizerAccessToken.getAuthorizerAccessToken(), authorizerAccessToken.getExpiresIn()); | |||
logger.info("updateAuthorizerAccessToken " + appId); | |||
dbUpdateAccessToken(appId, authorizerAccessToken); | |||
} | |||
@Async | |||
public void dbUpdateAccessToken(String appId, WxOpenAuthorizerAccessToken authorizerAccessToken) { | |||
WxAuthorizerInfo authorizerInfo = new WxAuthorizerInfo(); | |||
authorizerInfo.setAuthorizerAppid(appId); | |||
authorizerInfo.setAccessToken(authorizerAccessToken.getAuthorizerAccessToken()); | |||
authorizerInfo.setAccessTokenExpire(new Date(Long.valueOf(System.currentTimeMillis() + (authorizerAccessToken.getExpiresIn() * 1000)))); | |||
int num = wxAuthorizerInfoMapper.updateAccessToken(authorizerInfo); | |||
logger.info("updateAuthorizerAccessToken " + appId + " rows: " + num); | |||
} | |||
@Override | |||
public void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.setex(this.getKey(this.authorizerAccessTokenKey, appId), expiresInSeconds - 200, authorizerAccessToken); | |||
} | |||
} | |||
@Override | |||
public String getJsapiTicket(String appId) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
return jedis.get(this.getKey(this.jsapiTicketKey, appId)); | |||
} | |||
} | |||
@Override | |||
public boolean isJsapiTicketExpired(String appId) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
return jedis.ttl(this.getKey(this.jsapiTicketKey, appId)) < 2; | |||
} | |||
} | |||
@Override | |||
public void expireJsapiTicket(String appId) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.expire(this.getKey(this.jsapiTicketKey, appId), 0); | |||
} | |||
} | |||
@Override | |||
public void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.setex(this.getKey(this.jsapiTicketKey, appId), expiresInSeconds - 200, jsapiTicket); | |||
} | |||
// TODO update to DB | |||
} | |||
@Override | |||
public String getCardApiTicket(String appId) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
return jedis.get(this.getKey(this.cardApiTicket, appId)); | |||
} | |||
} | |||
@Override | |||
public boolean isCardApiTicketExpired(String appId) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
return jedis.ttl(this.getKey(this.cardApiTicket, appId)) < 2; | |||
} | |||
} | |||
@Override | |||
public void expireCardApiTicket(String appId) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.expire(this.getKey(this.cardApiTicket, appId), 0); | |||
} | |||
} | |||
@Override | |||
public void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.setex(this.getKey(this.cardApiTicket, appId), expiresInSeconds - 200, cardApiTicket); | |||
} | |||
// TODO update to DB | |||
} | |||
@Override | |||
public ApacheHttpClientBuilder getApacheHttpClientBuilder() { | |||
return FmHttpClientBuilder.get(); | |||
} | |||
} |
@@ -0,0 +1,71 @@ | |||
package com.iformall.service.toutiao; | |||
import com.iformall.config.WechatOpenProperties; | |||
import com.iformall.config.WechatRedisProperies; | |||
import com.iformall.mapper.WxAuthorizerInfoMapper; | |||
import com.iformall.mapper.WxComponentVerifyTicketMapper; | |||
import com.iformall.service.toutiao.api.impl.TtOpenServiceImpl; | |||
import com.iformall.service.wechat.FmOpenInRedisDBConfigStorage; | |||
import com.iformall.service.wechat.handler.*; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.mp.constant.WxMpEventConstants; | |||
import me.chanjar.weixin.open.api.impl.WxOpenMessageRouter; | |||
import me.chanjar.weixin.open.api.impl.WxOpenServiceImpl; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | |||
import org.springframework.stereotype.Service; | |||
import redis.clients.jedis.JedisPool; | |||
import javax.annotation.PostConstruct; | |||
/** | |||
* Stormeye WU | |||
*/ | |||
@Service | |||
@EnableConfigurationProperties({WechatOpenProperties.class, WechatRedisProperies.class}) | |||
public class FmTtOpenService extends TtOpenServiceImpl { | |||
private Logger logger = LoggerFactory.getLogger(getClass()); | |||
@Autowired | |||
private WechatOpenProperties wechatProperties; | |||
@Autowired | |||
private WechatRedisProperies redisProperies; | |||
@Autowired | |||
private WxComponentVerifyTicketMapper componentVerifyTicketMapper; | |||
@Autowired | |||
private WxAuthorizerInfoMapper authorizerInfoMapper; | |||
private static JedisPool pool; | |||
@PostConstruct | |||
public void init() { | |||
FmTtOpenInRedisDBConfigStorage configStorage = new FmTtOpenInRedisDBConfigStorage(getJedisPool(), componentVerifyTicketMapper, authorizerInfoMapper); | |||
configStorage.setComponentAppId(wechatProperties.getComponentAppId()); | |||
configStorage.setComponentAppSecret(wechatProperties.getComponentSecret()); | |||
configStorage.setComponentToken(wechatProperties.getComponentToken()); | |||
configStorage.setComponentAesKey(wechatProperties.getComponentAesKey()); | |||
setWxOpenConfigStorage(configStorage); | |||
} | |||
private JedisPool getJedisPool() { | |||
if (pool == null) { | |||
synchronized (FmTtOpenService.class) { | |||
if (pool == null) { | |||
pool = new JedisPool(redisProperies, redisProperies.getHost(), | |||
redisProperies.getPort(), redisProperies.getConnectionTimeout(), | |||
redisProperies.getSoTimeout(), redisProperies.getPassword(), | |||
redisProperies.getDatabase(), redisProperies.getClientName(), | |||
redisProperies.isSsl(), redisProperies.getSslSocketFactory(), | |||
redisProperies.getSslParameters(), redisProperies.getHostnameVerifier()); | |||
} | |||
} | |||
} | |||
return pool; | |||
} | |||
} |
@@ -0,0 +1,261 @@ | |||
package com.iformall.service.toutiao.api; | |||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
import com.iformall.service.toutiao.api.bean.TtOpenAuthorizationInfo; | |||
import com.iformall.service.toutiao.api.bean.TtOpenTicket; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | |||
import me.chanjar.weixin.open.api.WxOpenConfigStorage; | |||
import me.chanjar.weixin.open.bean.WxOpenCreateResult; | |||
import me.chanjar.weixin.open.bean.WxOpenGetResult; | |||
import me.chanjar.weixin.open.bean.WxOpenMaCodeTemplate; | |||
import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage; | |||
import me.chanjar.weixin.open.bean.result.*; | |||
import java.util.List; | |||
/** | |||
* . | |||
* | |||
* @author | |||
*/ | |||
public interface TtOpenComponentService { | |||
String API_COMPONENT_TOKEN_URL = "https://open.microapp.bytedance.com/openapi/v1/auth/tp/token"; | |||
String API_CREATE_PREAUTHCODE_URL = "https://open.microapp.bytedance.com/openapi/v2/auth/pre_auth_code"; | |||
String COMPONENT_LOGIN_PAGE_URL = "https://open.microapp.bytedance.com/mappconsole/tp/authorization?component_appid=%s&pre_auth_code=%s&redirect_uri=%s"; | |||
String API_QUERY_AUTH_URL = "https://open.microapp.bytedance.com/openapi/v1/oauth/token"; | |||
String API_AUTHORIZER_TOKEN_URL = "https://open.microapp.bytedance.com/openapi/v1/oauth/token"; | |||
String RETRIEVE_AUTHORIZER_TOKEN_URL = "https://open.microapp.bytedance.com/openapi/v1/auth/retrieve"; | |||
// String API_GET_AUTHORIZER_INFO_URL = "https://open.microapp.bytedance.com/openapi/v1/auth/retrieve"; | |||
String API_GET_AUTHORIZER_OPTION_URL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_option"; | |||
String API_SET_AUTHORIZER_OPTION_URL = "https://api.weixin.qq.com/cgi-bin/component/api_set_authorizer_option"; | |||
String API_GET_AUTHORIZER_LIST = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_list"; | |||
/** | |||
* 手机端打开授权链接. | |||
*/ | |||
String CONNECT_OAUTH2_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s&component_appid=%s#wechat_redirect"; | |||
/** | |||
* 用code换取oauth2的access token. | |||
*/ | |||
String OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/component/access_token?appid=%s&code=%s&grant_type=authorization_code&component_appid=%s"; | |||
/** | |||
* 刷新oauth2的access token. | |||
*/ | |||
String OAUTH2_REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/component/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s&component_appid=%s"; | |||
String MINIAPP_JSCODE_2_SESSION = "https://api.weixin.qq.com/sns/component/jscode2session?appid=%s&js_code=%s&grant_type=authorization_code&component_appid=%s"; | |||
String CREATE_OPEN_URL = "https://api.weixin.qq.com/cgi-bin/open/create"; | |||
String BIND_OPEN_URL = "https://api.weixin.qq.com/cgi-bin/open/bind"; | |||
String UNBIND_OPEN_URL = "https://api.weixin.qq.com/cgi-bin/open/unbind"; | |||
String GET_OPEN_URL = "https://api.weixin.qq.com/cgi-bin/open/get"; | |||
/** | |||
* 快速创建小程序接口. | |||
*/ | |||
String FAST_REGISTER_WEAPP_URL = "https://api.weixin.qq.com/cgi-bin/component/fastregisterweapp?action=create"; | |||
String FAST_REGISTER_WEAPP_SEARCH_URL = "https://api.weixin.qq.com/cgi-bin/component/fastregisterweapp?action=search"; | |||
/** | |||
* 获取指定appid的开放平台小程序服务(继承一般小程序服务能力). | |||
* | |||
* @param appid . | |||
* @return . | |||
*/ | |||
TtOpenMaService getTtMaServiceByAppid(String appid); | |||
WxOpenConfigStorage getWxOpenConfigStorage(); | |||
boolean checkSignature(String timestamp, String nonce, String encrypt, String signature); | |||
TtOpenTicket decrypt(String encrypt); | |||
String getComponentAccessToken(boolean forceRefresh) throws WxErrorException; | |||
String post(String uri, String postData,String tokenKeyName) throws WxErrorException; | |||
String postByAppAccessToken(String appId,String uri, String postData,String tokenKeyName) throws WxErrorException; | |||
String get(String uri,String tokenKeyName) throws WxErrorException; | |||
String getByAppAccessToken(String appId,String uri,String tokenKeyName) throws WxErrorException; | |||
/** | |||
* 获取用户授权页URL(来路URL和成功跳转URL 的域名都需要为三方平台设置的 登录授权的发起页域名). | |||
*/ | |||
String getPreAuthUrl(String redirectUri) throws WxErrorException; | |||
String route(TtOpenTicket openTicket) throws WxErrorException; | |||
/** | |||
* 使用授权码换取公众号或小程序的接口调用凭据和授权信息. | |||
*/ | |||
TtOpenAuthorizationInfo getQueryAuth(String authorizationCode) throws WxErrorException; | |||
/** | |||
* 找回授权码. | |||
*/ | |||
String retrieveCode(String authorizerAppid) throws WxErrorException; | |||
// /** | |||
// * 获取授权方的帐号基本信息. | |||
// */ | |||
// WxOpenAuthorizerInfoResult getAuthorizerInfo(String authorizerAppid) throws WxErrorException; | |||
/** | |||
* 获取授权方的选项设置信息. | |||
*/ | |||
WxOpenAuthorizerOptionResult getAuthorizerOption(String authorizerAppid, String optionName) throws WxErrorException; | |||
/** | |||
* 获取所有授权方列表. | |||
*/ | |||
WxOpenAuthorizerListResult getAuthorizerList(int begin, int len) throws WxErrorException; | |||
/** | |||
* 设置授权方的选项信息. | |||
*/ | |||
void setAuthorizerOption(String authorizerAppid, String optionName, String optionValue) throws WxErrorException; | |||
String getAuthorizerAccessToken(String appid, boolean forceRefresh) throws WxErrorException; | |||
WxMpOAuth2AccessToken oauth2getAccessToken(String appid, String code) throws WxErrorException; | |||
WxMpOAuth2AccessToken oauth2refreshAccessToken(String appid, String refreshToken) throws WxErrorException; | |||
String oauth2buildAuthorizationUrl(String appid, String redirectUri, String scope, String state); | |||
WxMaJscode2SessionResult miniappJscode2Session(String appId, String jsCode) throws WxErrorException; | |||
/** | |||
* 代小程序实现业务. | |||
* 小程序代码模版库管理:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1506504150_nMMh6&token=&lang=zh_CN | |||
* access_token 为 component_access_token | |||
*/ | |||
String GET_TEMPLATE_DRAFT_LIST_URL = "https://api.weixin.qq.com/wxa/gettemplatedraftlist"; | |||
String GET_TEMPLATE_LIST_URL = "https://api.weixin.qq.com/wxa/gettemplatelist"; | |||
String ADD_TO_TEMPLATE_URL = "https://api.weixin.qq.com/wxa/addtotemplate"; | |||
String DELETE_TEMPLATE_URL = "https://api.weixin.qq.com/wxa/deletetemplate"; | |||
/** | |||
* 获取草稿箱内的所有临时代码草稿. | |||
* | |||
* @return 草稿箱代码模板列表(draftId) | |||
* @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档 | |||
*/ | |||
List<WxOpenMaCodeTemplate> getTemplateDraftList() throws WxErrorException; | |||
/** | |||
* 获取代码模版库中的所有小程序代码模版. | |||
* | |||
* @return 小程序代码模版列表(templateId) | |||
* @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档 | |||
*/ | |||
List<WxOpenMaCodeTemplate> getTemplateList() throws WxErrorException; | |||
/** | |||
* 将草稿箱的草稿选为小程序代码模版. | |||
* | |||
* @param draftId 草稿ID,本字段可通过“获取草稿箱内的所有临时代码草稿”接口获得 | |||
* @throws WxErrorException 操作失败时抛出,具体错误码请看此接口的注释文档 | |||
* @see #getTemplateDraftList | |||
*/ | |||
void addToTemplate(long draftId) throws WxErrorException; | |||
/** | |||
* 删除指定小程序代码模版. | |||
* | |||
* @param templateId 要删除的模版ID | |||
* @throws WxErrorException 操作失败时抛出,具体错误码请看此接口的注释文档 | |||
* @see #getTemplateList | |||
*/ | |||
void deleteTemplate(long templateId) throws WxErrorException; | |||
/** | |||
* https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1498704199_1bcax&token=6df5e3650041eff2cd3ec3662425ad8d7beec8d9&lang=zh_CN | |||
* 创建 开放平台帐号并绑定公众号/小程序. | |||
* https://api.weixin.qq.com/cgi-bin/open/create | |||
* | |||
* @param appId 公众号/小程序的appId | |||
* @return . | |||
*/ | |||
WxOpenCreateResult createOpenAccount(String appId) throws WxErrorException; | |||
/** | |||
* https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/api/account/bind.html | |||
* 将公众号/小程序绑定到开放平台帐号下 | |||
* | |||
* @param appId 公众号/小程序的appId | |||
* @param openAppid 开放平台帐号 appid,由创建开发平台帐号接口返回 | |||
*/ | |||
Boolean bindOpenAccount(String appId, String openAppid) throws WxErrorException; | |||
/** | |||
* https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/api/account/unbind.html | |||
* 将公众号/小程序从开放平台帐号下解绑 | |||
* | |||
* @param appId 公众号/小程序的appId | |||
* @param openAppid 开放平台帐号 appid,由创建开发平台帐号接口返回 | |||
*/ | |||
Boolean unbindOpenAccount(String appId, String openAppid) throws WxErrorException; | |||
/** | |||
* https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/api/account/get.html | |||
* 获取公众号/小程序所绑定的开放平台帐号 | |||
* | |||
* @param appId 公众号/小程序的appId | |||
* @return 开放平台帐号 appid,由创建开发平台帐号接口返回 | |||
*/ | |||
WxOpenGetResult getOpenAccount(String appId) throws WxErrorException; | |||
/** | |||
* https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=21538208049W8uwq&token=&lang=zh_CN | |||
* 第三方平台快速创建小程序. | |||
* 注意:创建任务逻辑串行,单次任务结束后才可以使用相同信息下发第二次任务,请注意规避任务阻塞 | |||
* | |||
* @param name 企业名(需与工商部门登记信息一致) | |||
* @param code 企业代码 | |||
* @param codeType 企业代码类型 1:统一社会信用代码(18位) 2:组织机构代码(9位xxxxxxxx-x) 3:营业执照注册号(15位) | |||
* @param legalPersonaWechat 法人微信号 | |||
* @param legalPersonaName 法人姓名(绑定银行卡) | |||
* @param componentPhone 第三方联系电话(方便法人与第三方联系) | |||
* @return . | |||
* @throws WxErrorException . | |||
*/ | |||
WxOpenResult fastRegisterWeapp(String name, String code, String codeType, String legalPersonaWechat, String legalPersonaName, String componentPhone) throws WxErrorException; | |||
/** | |||
* https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=21538208049W8uwq&token=&lang=zh_CN | |||
* 查询第三方平台快速创建小程序的任务状态 | |||
* 注意:该接口只提供当下任务结果查询,不建议过分依赖该接口查询所创建小程序。 | |||
* 小程序的成功状态可在第三方服务器中自行对账、查询。 | |||
* 不要频繁调用search接口,消息接收需通过服务器查看。调用search接口会消耗接口整体调用quato | |||
* | |||
* @param name 企业名(需与工商部门登记信息一致) | |||
* @param legalPersonaWechat 法人微信号 | |||
* @param legalPersonaName 法人姓名(绑定银行卡) | |||
* @throws WxErrorException . | |||
*/ | |||
WxOpenResult fastRegisterWeappSearch(String name, String legalPersonaWechat, String legalPersonaName) throws WxErrorException; | |||
} |
@@ -0,0 +1,486 @@ | |||
package com.iformall.service.toutiao.api; | |||
import com.iformall.service.toutiao.miniapp.TtMaService; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.open.bean.ma.WxMaOpenCommitExtInfo; | |||
import me.chanjar.weixin.open.bean.message.WxOpenMaSubmitAuditMessage; | |||
import me.chanjar.weixin.open.bean.result.*; | |||
import java.io.File; | |||
import java.util.List; | |||
import java.util.Map; | |||
/** | |||
* <pre> | |||
* 微信开放平台代小程序实现服务能力 | |||
* https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489144594_DhNoV&token=&lang=zh_CN | |||
* </pre> | |||
* | |||
* @author yqx | |||
* @date 2018/9/12 | |||
*/ | |||
public interface TtOpenMaService extends TtMaService { | |||
/** | |||
* 设置小程序服务器域名. | |||
* | |||
* <pre> | |||
* 授权给第三方的小程序,其服务器域名只可以为第三方的服务器,当小程序通过第三方发布代码上线后,小程序原先自己配置的服务器域名将被删除, | |||
* 只保留第三方平台的域名,所以第三方平台在代替小程序发布代码之前,需要调用接口为小程序添加第三方自身的域名。 | |||
* 提示:需要先将域名登记到第三方平台的小程序服务器域名中,才可以调用接口进行配置 | |||
* </pre> | |||
*/ | |||
String API_MODIFY_DOMAIN = "https://api.weixin.qq.com/wxa/modify_domain"; | |||
/** | |||
* 设置小程序业务域名(仅供第三方代小程序调用) | |||
* <pre> | |||
* 授权给第三方的小程序,其业务域名只可以为第三方的服务器,当小程序通过第三方发布代码上线后,小程序原先自己配置的业务域名将被删除, | |||
* 只保留第三方平台的域名,所以第三方平台在代替小程序发布代码之前,需要调用接口为小程序添加业务域名。 | |||
* 提示: | |||
* 1、需要先将域名登记到第三方平台的小程序业务域名中,才可以调用接口进行配置。 | |||
* 2、为授权的小程序配置域名时支持配置子域名,例如第三方登记的业务域名如为qq.com,则可以直接将qq.com及其子域名(如xxx.qq.com)也配置到授权的小程序中。 | |||
* </pre> | |||
*/ | |||
String API_SET_WEBVIEW_DOMAIN = "https://api.weixin.qq.com/wxa/setwebviewdomain"; | |||
/** | |||
* 获取帐号基本信息 | |||
* <pre> | |||
* GET请求 | |||
* 注意:需要使用1.3环节获取到的新创建小程序appid及authorization_code换取authorizer_refresh_token进而得到authorizer_access_token。 | |||
* </pre> | |||
*/ | |||
String API_GET_ACCOUNT_BASICINFO = "https://api.weixin.qq.com/cgi-bin/account/getaccountbasicinfo"; | |||
/** | |||
* 绑定微信用户为小程序体验者 | |||
*/ | |||
String API_BIND_TESTER = "https://api.weixin.qq.com/wxa/bind_tester"; | |||
/** | |||
* 解除绑定微信用户为小程序体验者 | |||
*/ | |||
String API_UNBIND_TESTER = "https://api.weixin.qq.com/wxa/unbind_tester"; | |||
/** | |||
* 获取体验者列表 | |||
*/ | |||
String API_GET_TESTERLIST = "https://api.weixin.qq.com/wxa/memberauth"; | |||
/** | |||
* 以下接口基础信息设置 | |||
* <p> | |||
* https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=21517799059ZSEMr&token=6f965b5daf30a98a6bbd2a386faea5c934e929bf&lang=zh_CN | |||
* </p> | |||
*/ | |||
/** | |||
* 1. 设置小程序隐私设置(是否可被搜索) | |||
*/ | |||
String API_CHANGE_WXA_SEARCH_STATUS = "https://api.weixin.qq.com/wxa/changewxasearchstatus"; | |||
/** | |||
* 2. 查询小程序当前隐私设置(是否可被搜索) | |||
*/ | |||
String API_GET_WXA_SEARCH_STATUS = "https://api.weixin.qq.com/wxa/getwxasearchstatus"; | |||
/** | |||
* 3.1. 获取展示的公众号信息 | |||
*/ | |||
String API_GET_SHOW_WXA_ITEM = "https://api.weixin.qq.com/wxa/getshowwxaitem"; | |||
/** | |||
* 3.2 设置展示的公众号 | |||
*/ | |||
String API_UPDATE_SHOW_WXA_ITEM = "https://api.weixin.qq.com/wxa/updateshowwxaitem"; | |||
/** | |||
* 以下接口为三方平台代小程序实现的代码管理功能 | |||
* <p> | |||
* https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140610_Uavc4&token=fe774228c66725425675810097f9e48d0737a4bf&lang=zh_CN | |||
* </p> | |||
*/ | |||
/** | |||
* 1. 为授权的小程序帐号上传小程序代码 | |||
*/ | |||
String API_CODE_COMMIT = "https://api.weixin.qq.com/wxa/commit"; | |||
/** | |||
* 2. 获取体验小程序的体验二维码 | |||
*/ | |||
String API_TEST_QRCODE = "https://api.weixin.qq.com/wxa/get_qrcode"; | |||
/** | |||
* 3. 获取授权小程序帐号的可选类目 | |||
*/ | |||
String API_GET_CATEGORY = "https://api.weixin.qq.com/wxa/get_category"; | |||
/** | |||
* 4. 获取小程序的第三方提交代码的页面配置(仅供第三方开发者代小程序调用) | |||
*/ | |||
String API_GET_PAGE = "https://api.weixin.qq.com/wxa/get_page"; | |||
/** | |||
* 5. 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用) | |||
*/ | |||
String API_SUBMIT_AUDIT = "https://api.weixin.qq.com/wxa/submit_audit"; | |||
/** | |||
* 7. 查询某个指定版本的审核状态(仅供第三方代小程序调用) | |||
*/ | |||
String API_GET_AUDIT_STATUS = "https://api.weixin.qq.com/wxa/get_auditstatus"; | |||
/** | |||
* 8. 查询最新一次提交的审核状态(仅供第三方代小程序调用) | |||
*/ | |||
String API_GET_LATEST_AUDIT_STATUS = "https://api.weixin.qq.com/wxa/get_latest_auditstatus"; | |||
/** | |||
* 9. 发布已通过审核的小程序(仅供第三方代小程序调用) | |||
*/ | |||
String API_RELEASE = "https://api.weixin.qq.com/wxa/release"; | |||
/** | |||
* 10. 修改小程序线上代码的可见状态(仅供第三方代小程序调用) | |||
*/ | |||
String API_CHANGE_VISITSTATUS = "https://api.weixin.qq.com/wxa/change_visitstatus"; | |||
/** | |||
* 11.小程序版本回退(仅供第三方代小程序调用) | |||
*/ | |||
String API_REVERT_CODE_RELEASE = "https://api.weixin.qq.com/wxa/revertcoderelease"; | |||
/** | |||
* 12.查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用) | |||
*/ | |||
String API_GET_WEAPP_SUPPORT_VERSION = "https://api.weixin.qq.com/cgi-bin/wxopen/getweappsupportversion"; | |||
/** | |||
* 13.设置最低基础库版本(仅供第三方代小程序调用) | |||
*/ | |||
String API_SET_WEAPP_SUPPORT_VERSION = "https://api.weixin.qq.com/cgi-bin/wxopen/setweappsupportversion"; | |||
/** | |||
* 14.设置小程序“扫普通链接二维码打开小程序”能力 | |||
* | |||
* https://mp.weixin.qq.com/debug/wxadoc/introduction/qrcode.html | |||
*/ | |||
/** | |||
* 14.1 增加或修改二维码规则 | |||
*/ | |||
String API_QRCODE_JUMP_ADD = "https://api.weixin.qq.com/cgi-bin/wxopen/qrcodejumpadd"; | |||
/** | |||
* 14.2 获取已设置的二维码规则 | |||
*/ | |||
String API_QRCODE_JUMP_GET = "https://api.weixin.qq.com/cgi-bin/wxopen/qrcodejumpget"; | |||
/** | |||
* 14.3 获取校验文件名称及内容 | |||
*/ | |||
String API_QRCODE_JUMP_DOWNLOAD = "https://api.weixin.qq.com/cgi-bin/wxopen/qrcodejumpdownload"; | |||
/** | |||
* 14.4 删除已设置的二维码规则 | |||
*/ | |||
String API_QRCODE_JUMP_DELETE = "https://api.weixin.qq.com/cgi-bin/wxopen/qrcodejumpdelete"; | |||
/** | |||
* 14.5 发布已设置的二维码规则 | |||
*/ | |||
String API_QRCODE_JUMP_PUBLISH = "https://api.weixin.qq.com/cgi-bin/wxopen/qrcodejumppublish"; | |||
/** | |||
* 15.小程序审核撤回 | |||
* <p> | |||
* 单个帐号每天审核撤回次数最多不超过1次,一个月不超过10次。 | |||
* </p> | |||
*/ | |||
String API_UNDO_CODE_AUDIT = "https://api.weixin.qq.com/wxa/undocodeaudit"; | |||
/** | |||
* 16.1 小程序分阶段发布-分阶段发布接口 | |||
*/ | |||
String API_GRAY_RELEASE = "https://api.weixin.qq.com/wxa/grayrelease"; | |||
/** | |||
* 16.2 小程序分阶段发布-取消分阶段发布 | |||
*/ | |||
String API_REVERT_GRAY_RELEASE = "https://api.weixin.qq.com/wxa/revertgrayrelease"; | |||
/** | |||
* 16.3 小程序分阶段发布-查询当前分阶段发布详情 | |||
*/ | |||
String API_GET_GRAY_RELEASE_PLAN = "https://api.weixin.qq.com/wxa/getgrayreleaseplan"; | |||
/** | |||
* 查询服务商的当月提审限额和加急次数(Quota) | |||
*/ | |||
String API_QUERY_QUOTA = "https://api.weixin.qq.com/wxa/queryquota"; | |||
/** | |||
* 加急审核申请 | |||
*/ | |||
String API_SPEED_AUDIT = "https://api.weixin.qq.com/wxa/speedupaudit"; | |||
/** | |||
* 获得小程序的域名配置信息 | |||
*/ | |||
WxOpenMaDomainResult getDomain() throws WxErrorException; | |||
/** | |||
* 修改域名 | |||
* | |||
* @param action delete删除, set覆盖, get获取 | |||
*/ | |||
WxOpenMaDomainResult modifyDomain(String action, List<String> requestdomainList, List<String> wsrequestdomainList, List<String> uploaddomainList, List<String> downloaddomainList) throws WxErrorException; | |||
/** | |||
* 获取小程序的业务域名 | |||
* | |||
* @return 直接返回字符串 | |||
*/ | |||
String getWebViewDomain() throws WxErrorException; | |||
/** | |||
* 获取小程序的业务域名 | |||
* | |||
* @return | |||
*/ | |||
public WxOpenMaWebDomainResult getWebViewDomainInfo() throws WxErrorException; | |||
/** | |||
* 设置小程序的业务域名 | |||
* | |||
* @param action add添加, delete删除, set覆盖 | |||
* @return 直接返回字符串 | |||
*/ | |||
String setWebViewDomain(String action, List<String> domainList) throws WxErrorException; | |||
/** | |||
* 设置小程序的业务域名 | |||
* | |||
* @param action add添加, delete删除, set覆盖 | |||
* @param domainList | |||
* @return | |||
*/ | |||
WxOpenMaWebDomainResult setWebViewDomainInfo(String action, List<String> domainList) throws WxErrorException; | |||
/** | |||
* 获取小程序的信息 | |||
*/ | |||
String getAccountBasicInfo() throws WxErrorException; | |||
/** | |||
* 绑定小程序体验者 | |||
* | |||
* @param wechatid 体验者微信号(不是openid) | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
WxOpenMaBindTesterResult bindTester(String wechatid) throws WxErrorException; | |||
/** | |||
* 解除绑定小程序体验者 | |||
* | |||
* @param wechatid 体验者微信号(不是openid) | |||
*/ | |||
WxOpenResult unbindTester(String wechatid) throws WxErrorException; | |||
/** | |||
* 解除绑定小程序体验者,其他平台绑定的体验者无法获取到wechatid,可用此方法解绑,详见文档 | |||
* https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/unbind_tester.html | |||
* | |||
* @param userstr 人员对应的唯一字符串, 可通过获取已绑定的体验者列表获取人员对应的字符串 | |||
*/ | |||
WxOpenResult unbindTesterByUserstr(String userstr) throws WxErrorException; | |||
/** | |||
* 获得体验者列表 | |||
*/ | |||
WxOpenMaTesterListResult getTesterList() throws WxErrorException; | |||
/** | |||
* 设置小程序隐私设置(是否可被搜索) | |||
* | |||
* @param status 1表示不可搜索,0表示可搜索 | |||
*/ | |||
public WxOpenResult changeWxaSearchStatus(Integer status) throws WxErrorException; | |||
/** | |||
* 2. 查询小程序当前隐私设置(是否可被搜索) | |||
*/ | |||
public WxOpenMaSearchStatusResult getWxaSearchStatus() throws WxErrorException; | |||
/** | |||
* 3.1 获取展示的公众号信息 | |||
*/ | |||
public WxOpenMaShowItemResult getShowWxaItem() throws WxErrorException; | |||
/** | |||
* 3.2 设置展示的公众号 | |||
* | |||
* @param flag 0 关闭,1 开启 | |||
* @param mpappid 如果开启,需要传新的公众号appid | |||
*/ | |||
public WxOpenResult updateShowwxaitem(Integer flag, String mpappid) throws WxErrorException; | |||
/** | |||
* 1、为授权的小程序帐号上传小程序代码 | |||
* | |||
* @param templateId 代码模板ID | |||
* @param userVersion 用户定义版本 | |||
* @param userDesc 用户定义版本描述 | |||
* @param extInfo 第三方自定义的配置 | |||
*/ | |||
WxOpenResult codeCommit(Long templateId, String userVersion, String userDesc, WxMaOpenCommitExtInfo extInfo) throws WxErrorException; | |||
/** | |||
* 获取体验小程序的体验二维码 | |||
*/ | |||
File getTestQrcode(String pagePath, Map<String, String> params) throws WxErrorException; | |||
/** | |||
* 获取授权小程序帐号的可选类目 | |||
* <p> | |||
* 注意:该接口可获取已设置的二级类目及用于代码审核的可选三级类目。 | |||
* </p> | |||
*/ | |||
WxOpenMaCategoryListResult getCategoryList() throws WxErrorException; | |||
/** | |||
* 获取小程序的第三方提交代码的页面配置(仅供第三方开发者代小程序调用) | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
WxOpenMaPageListResult getPageList() throws WxErrorException; | |||
/** | |||
* 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用) | |||
*/ | |||
WxOpenMaSubmitAuditResult submitAudit(WxOpenMaSubmitAuditMessage submitAuditMessage) throws WxErrorException; | |||
/** | |||
* 查询某个指定版本的审核状态(仅供第三方代小程序调用) | |||
*/ | |||
WxOpenMaQueryAuditResult getAuditStatus(Long auditid) throws WxErrorException; | |||
/** | |||
* 查询最新一次提交的审核状态(仅供第三方代小程序调用). | |||
*/ | |||
WxOpenMaQueryAuditResult getLatestAuditStatus() throws WxErrorException; | |||
/** | |||
* 发布已通过审核的小程序(仅供第三方代小程序调用). | |||
*/ | |||
WxOpenResult releaesAudited() throws WxErrorException; | |||
/** | |||
* 10. 修改小程序线上代码的可见状态(仅供第三方代小程序调用) | |||
*/ | |||
public WxOpenResult changeVisitstatus(String action) throws WxErrorException; | |||
/** | |||
* 11. 小程序版本回退(仅供第三方代小程序调用) | |||
*/ | |||
WxOpenResult revertCodeReleaes() throws WxErrorException; | |||
/** | |||
* 15. 小程序审核撤回 | |||
* 单个帐号每天审核撤回次数最多不超过1次,一个月不超过10次。 | |||
*/ | |||
WxOpenResult undoCodeAudit() throws WxErrorException; | |||
/** | |||
* 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用) | |||
*/ | |||
String getSupportVersion() throws WxErrorException; | |||
/** | |||
* 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用) | |||
*/ | |||
WxOpenMaWeappSupportVersionResult getSupportVersionInfo() throws WxErrorException; | |||
/** | |||
* 设置最低基础库版本(仅供第三方代小程序调用) | |||
*/ | |||
String setSupportVersion(String version) throws WxErrorException; | |||
/** | |||
* 设置最低基础库版本(仅供第三方代小程序调用) | |||
*/ | |||
WxOpenResult setSupportVersionInfo(String version) throws WxErrorException; | |||
/** | |||
* 16. 小程序分阶段发布 - 1)分阶段发布接口 | |||
*/ | |||
WxOpenResult grayrelease(Integer grayPercentage) throws WxErrorException; | |||
/** | |||
* 16. 小程序分阶段发布 - 2)取消分阶段发布 | |||
*/ | |||
WxOpenResult revertgrayrelease() throws WxErrorException; | |||
/** | |||
* 16. 小程序分阶段发布 - 3)查询当前分阶段发布详情 | |||
*/ | |||
WxOpenMaGrayReleasePlanResult getgrayreleaseplan() throws WxErrorException; | |||
/** | |||
* 查询服务商的当月提审限额和加急次数(Quota) | |||
* https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/query_quota.html | |||
*/ | |||
WxOpenMaQueryQuotaResult queryQuota() throws WxErrorException; | |||
/** | |||
* 加急审核申请 | |||
* 有加急次数的第三方可以通过该接口,对已经提审的小程序进行加急操作,加急后的小程序预计2-12小时内审完。 | |||
*/ | |||
Boolean speedAudit(Long auditid) throws WxErrorException; | |||
/** | |||
* (1)增加或修改二维码规则 | |||
*/ | |||
WxOpenResult addQrcodeJump(WxQrcodeJumpRule wxQrcodeJumpRule) throws WxErrorException; | |||
/** | |||
* (2)获取已设置的二维码规则 | |||
*/ | |||
WxGetQrcodeJumpResult getQrcodeJump() throws WxErrorException; | |||
/** | |||
* (3)获取校验文件名称及内容 | |||
*/ | |||
WxDownlooadQrcodeJumpResult downloadQrcodeJump() throws WxErrorException; | |||
/** | |||
* (4)删除已设置的二维码规则 | |||
*/ | |||
WxOpenResult deleteQrcodeJump(String prefix) throws WxErrorException; | |||
/** | |||
* (5)发布已设置的二维码规则 | |||
*/ | |||
WxOpenResult publishQrcodeJump(String prefix) throws WxErrorException; | |||
// /** | |||
// * 小程序用户隐私保护指引服务 | |||
// * | |||
// * @return 小程序用户隐私保护指引服务 | |||
// */ | |||
// WxOpenMaPrivacyService getPrivacyService(); | |||
} |
@@ -0,0 +1,23 @@ | |||
package com.iformall.service.toutiao.api; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.open.api.WxOpenConfigStorage; | |||
public interface TtOpenService { | |||
TtOpenComponentService getTtOpenComponentService(); | |||
WxOpenConfigStorage getWxOpenConfigStorage(); | |||
void setWxOpenConfigStorage(WxOpenConfigStorage wxOpenConfigStorage); | |||
/** | |||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求 | |||
*/ | |||
String get(String url, String queryParam) throws WxErrorException; | |||
/** | |||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求 | |||
*/ | |||
String post(String url, String postData) throws WxErrorException; | |||
} |
@@ -0,0 +1,23 @@ | |||
package com.iformall.service.toutiao.api.bean; | |||
import lombok.Data; | |||
import java.io.Serializable; | |||
import java.util.List; | |||
/** | |||
* | |||
*/ | |||
@Data | |||
public class TtOpenAuthorizationInfo implements Serializable { | |||
private static final long serialVersionUID = -8354370210668120305L; | |||
private String authorizerAppid; | |||
private String authorizerAccessToken; | |||
private int expiresIn; | |||
private String authorizerRefreshToken; | |||
private int refreshExpiresIn; | |||
private List<Integer> authorizePermission; | |||
} |
@@ -0,0 +1,43 @@ | |||
package com.iformall.service.toutiao.api.bean; | |||
import com.google.gson.*; | |||
import me.chanjar.weixin.common.util.json.GsonHelper; | |||
import java.lang.reflect.Type; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
/** | |||
* @author <a href="https://github.com/007gzs">007</a> | |||
*/ | |||
public class TtOpenAuthorizationInfoGsonAdapter implements JsonDeserializer<TtOpenAuthorizationInfo> { | |||
@Override | |||
public TtOpenAuthorizationInfo deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { | |||
TtOpenAuthorizationInfo authorizationInfo = new TtOpenAuthorizationInfo(); | |||
JsonObject jsonObject = jsonElement.getAsJsonObject(); | |||
authorizationInfo.setAuthorizerAppid(GsonHelper.getString(jsonObject, "authorizer_appid")); | |||
authorizationInfo.setAuthorizerAccessToken(GsonHelper.getString(jsonObject, "authorizer_access_token")); | |||
authorizationInfo.setExpiresIn(GsonHelper.getPrimitiveInteger(jsonObject, "expires_in")); | |||
authorizationInfo.setAuthorizerRefreshToken(GsonHelper.getString(jsonObject, "authorizer_refresh_token")); | |||
authorizationInfo.setRefreshExpiresIn(GsonHelper.getPrimitiveInteger(jsonObject, "refresh_expires_in")); | |||
List<Integer> funcInfo = new ArrayList<>(); | |||
JsonArray jsonArray = GsonHelper.getAsJsonArray(jsonObject.get("authorize_permission")); | |||
if (jsonArray != null && !jsonArray.isJsonNull()) { | |||
for (int i = 0; i < jsonArray.size(); i++) { | |||
jsonObject = jsonArray.get(i).getAsJsonObject(); | |||
if (jsonObject == null || jsonObject.isJsonNull()) { | |||
continue; | |||
} | |||
Integer id = GsonHelper.getInteger(jsonObject, "id"); | |||
if (id == null) { | |||
continue; | |||
} | |||
funcInfo.add(id); | |||
} | |||
} | |||
authorizationInfo.setAuthorizePermission(funcInfo); | |||
return authorizationInfo; | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
package com.iformall.service.toutiao.api.bean; | |||
import com.google.gson.Gson; | |||
import com.google.gson.GsonBuilder; | |||
import com.iformall.service.toutiao.miniapp.bean.TtMaSubscribeMessage; | |||
import com.iformall.service.toutiao.miniapp.bean.TtMaSubscribeMessageGsonAdapter; | |||
/** | |||
* @author | |||
*/ | |||
public class TtOpenGsonBuilder { | |||
private static final GsonBuilder INSTANCE = new GsonBuilder(); | |||
static { | |||
INSTANCE.disableHtmlEscaping(); | |||
INSTANCE.registerTypeAdapter(TtOpenTicket.class, new TtOpenTicketGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(TtOpenAuthorizationInfo.class, new TtOpenAuthorizationInfoGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(TtOpenRetrieveCode.class, new TtOpenRetrieveCodeGsonAdapter()); | |||
} | |||
public static Gson create() { | |||
return INSTANCE.create(); | |||
} | |||
} |
@@ -0,0 +1,19 @@ | |||
package com.iformall.service.toutiao.api.bean; | |||
import lombok.Data; | |||
import java.io.Serializable; | |||
import java.util.List; | |||
/** | |||
* | |||
*/ | |||
@Data | |||
public class TtOpenRetrieveCode implements Serializable { | |||
private static final long serialVersionUID = -2169528610814376819L; | |||
private String authorizationCode; | |||
private int expiresIn; | |||
} |
@@ -0,0 +1,24 @@ | |||
package com.iformall.service.toutiao.api.bean; | |||
import com.google.gson.*; | |||
import me.chanjar.weixin.common.util.json.GsonHelper; | |||
import java.lang.reflect.Type; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
/** | |||
* @author <a href="https://github.com/007gzs">007</a> | |||
*/ | |||
public class TtOpenRetrieveCodeGsonAdapter implements JsonDeserializer<TtOpenRetrieveCode> { | |||
@Override | |||
public TtOpenRetrieveCode deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { | |||
TtOpenRetrieveCode retrieveCode = new TtOpenRetrieveCode(); | |||
JsonObject jsonObject = jsonElement.getAsJsonObject(); | |||
retrieveCode.setAuthorizationCode(GsonHelper.getString(jsonObject, "authorization_code")); | |||
retrieveCode.setExpiresIn(GsonHelper.getPrimitiveInteger(jsonObject, "expires_in")); | |||
return retrieveCode; | |||
} | |||
} |
@@ -0,0 +1,40 @@ | |||
package com.iformall.service.toutiao.api.bean; | |||
import com.iformall.service.toutiao.miniapp.bean.TtMaGsonBuilder; | |||
import lombok.*; | |||
import java.io.Serializable; | |||
/** | |||
* @author S | |||
*/ | |||
@Getter | |||
@Setter | |||
@NoArgsConstructor | |||
@AllArgsConstructor | |||
@Builder | |||
public class TtOpenTicket implements Serializable { | |||
private static final long serialVersionUID = -44544979421396117L; | |||
private String accessToken; | |||
private String ticket; | |||
private String fromUserName; | |||
//2019-01-14 12:45:10 | |||
private String createTime; | |||
private String msgType; | |||
private String event; | |||
private String componentAppId; | |||
public static TtOpenTicket fromJson(String json) { | |||
return TtMaGsonBuilder.create().fromJson(json, TtOpenTicket.class); | |||
} | |||
} |
@@ -0,0 +1,34 @@ | |||
package com.iformall.service.toutiao.api.bean; | |||
import com.google.gson.*; | |||
import com.iformall.service.toutiao.miniapp.bean.TtMaSubscribeMessage; | |||
import me.chanjar.weixin.common.util.json.GsonHelper; | |||
import java.lang.reflect.Type; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
/** | |||
* . | |||
* | |||
* @author S | |||
*/ | |||
public class TtOpenTicketGsonAdapter implements JsonDeserializer<TtOpenTicket> { | |||
@Override | |||
public TtOpenTicket deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||
return convertFromJson(json.getAsJsonObject()); | |||
} | |||
protected TtOpenTicket convertFromJson(JsonObject json) { | |||
TtOpenTicket openTicket = new TtOpenTicket(); | |||
openTicket.setTicket(GsonHelper.getString(json,"Ticket")); | |||
openTicket.setFromUserName(GsonHelper.getString(json,"FromUserName")); | |||
openTicket.setCreateTime(GsonHelper.getString(json,"CreateTime")); | |||
openTicket.setMsgType(GsonHelper.getString(json,"MsgType")); | |||
openTicket.setEvent(GsonHelper.getString(json,"Event")); | |||
return openTicket; | |||
} | |||
} |
@@ -0,0 +1,517 @@ | |||
package com.iformall.service.toutiao.api.impl; | |||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import com.google.gson.reflect.TypeToken; | |||
import com.iformall.service.toutiao.api.TtOpenComponentService; | |||
import com.iformall.service.toutiao.api.TtOpenMaService; | |||
import com.iformall.service.toutiao.api.TtOpenService; | |||
import com.iformall.service.toutiao.api.bean.TtOpenAuthorizationInfo; | |||
import com.iformall.service.toutiao.api.bean.TtOpenGsonBuilder; | |||
import com.iformall.service.toutiao.api.bean.TtOpenRetrieveCode; | |||
import com.iformall.service.toutiao.api.bean.TtOpenTicket; | |||
import com.iformall.service.toutiao.utils.MsgDecrypt; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.http.URIUtil; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | |||
import me.chanjar.weixin.open.api.WxOpenConfigStorage; | |||
import me.chanjar.weixin.open.bean.*; | |||
import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; | |||
import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage; | |||
import me.chanjar.weixin.open.bean.result.*; | |||
import me.chanjar.weixin.open.util.WxOpenCryptUtil; | |||
import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.locks.Lock; | |||
/** | |||
* @author | |||
*/ | |||
public class TtOpenComponentServiceImpl implements TtOpenComponentService { | |||
private static final JsonParser JSON_PARSER = new JsonParser(); | |||
private static final Map<String, TtOpenMaService> TT_OPEN_MA_SERVICE_MAP = new ConcurrentHashMap<>(); | |||
protected final Logger log = LoggerFactory.getLogger(this.getClass()); | |||
private TtOpenService ttOpenService; | |||
public TtOpenComponentServiceImpl(TtOpenService ttOpenService) { | |||
this.ttOpenService = ttOpenService; | |||
} | |||
enum RequestMethod { | |||
POST,GET | |||
} | |||
@Override | |||
public TtOpenMaService getTtMaServiceByAppid(String appId) { | |||
TtOpenMaService ttOpenMaService = TT_OPEN_MA_SERVICE_MAP.get(appId); | |||
if (ttOpenMaService == null) { | |||
synchronized (TT_OPEN_MA_SERVICE_MAP) { | |||
ttOpenMaService = TT_OPEN_MA_SERVICE_MAP.get(appId); | |||
if (ttOpenMaService == null) { | |||
ttOpenMaService = new TtOpenMaServiceImpl(this, appId, getWxOpenConfigStorage().getWxMaConfig(appId)); | |||
TT_OPEN_MA_SERVICE_MAP.put(appId, ttOpenMaService); | |||
} | |||
} | |||
} | |||
return ttOpenMaService; | |||
} | |||
public TtOpenService getTtOpenService() { | |||
return ttOpenService; | |||
} | |||
@Override | |||
public WxOpenConfigStorage getWxOpenConfigStorage() { | |||
return ttOpenService.getWxOpenConfigStorage(); | |||
} | |||
@Override | |||
public boolean checkSignature(String timestamp, String nonce, String encrypt, String signature) { | |||
try { | |||
return SHA1.gen(getWxOpenConfigStorage().getComponentToken(), timestamp, nonce, encrypt) | |||
.equals(signature); | |||
} catch (Exception e) { | |||
this.log.error("Checking signature failed, and the reason is :" + e.getMessage()); | |||
return false; | |||
} | |||
} | |||
@Override | |||
public TtOpenTicket decrypt(String encrypt) { | |||
try { | |||
MsgDecrypt msgDecrypt = new MsgDecrypt(getWxOpenConfigStorage().getComponentAesKey()); | |||
String plainText = msgDecrypt.decrypt(encrypt); | |||
log.info("解密信息----------------------------------------"+ plainText); | |||
TtOpenTicket openTicket = TtOpenTicket.fromJson(plainText); | |||
openTicket.setComponentAppId(getWxOpenConfigStorage().getComponentAppId()); | |||
return openTicket; | |||
} catch (Exception e) { | |||
this.log.error("Checking signature failed, and the reason is :" + e.getMessage()); | |||
return null; | |||
} | |||
} | |||
@Override | |||
public String getComponentAccessToken(boolean forceRefresh) throws WxErrorException { | |||
final WxOpenConfigStorage config = this.getWxOpenConfigStorage(); | |||
if (!config.isComponentAccessTokenExpired() && !forceRefresh) { | |||
return config.getComponentAccessToken(); | |||
} | |||
Lock lock = config.getComponentAccessTokenLock(); | |||
lock.lock(); | |||
try { | |||
if (!config.isComponentAccessTokenExpired() && !forceRefresh) { | |||
return config.getComponentAccessToken(); | |||
} | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||
jsonObject.addProperty("component_appsecret", getWxOpenConfigStorage().getComponentAppSecret()); | |||
jsonObject.addProperty("component_ticket", getWxOpenConfigStorage().getComponentVerifyTicket()); | |||
String responseContent = this.getTtOpenService().get(API_COMPONENT_TOKEN_URL, jsonObject.toString()); | |||
WxOpenComponentAccessToken componentAccessToken = WxOpenComponentAccessToken.fromJson(responseContent); | |||
config.updateComponentAccessToken(componentAccessToken); | |||
return config.getComponentAccessToken(); | |||
} finally { | |||
lock.unlock(); | |||
} | |||
} | |||
@Override | |||
public String post(String uri, String postData,String tokenKeyName) throws WxErrorException { | |||
//"component_access_token" | |||
return postByCommonAccessToken(uri, postData, tokenKeyName); | |||
} | |||
@Override | |||
public String get(String uri,String tokenKeyName) throws WxErrorException { | |||
//"component_access_token" | |||
return getByCommonAccessToken(uri, tokenKeyName); | |||
} | |||
private String getByCommonAccessToken(String uri, String accessTokenKey) throws WxErrorException { | |||
String componentAccessToken = getComponentAccessToken(false); | |||
return excuteRequet(uri, accessTokenKey, componentAccessToken, null, true, RequestMethod.GET); | |||
} | |||
private String postByCommonAccessToken(String uri, String postData ,String accessTokenKey) throws WxErrorException { | |||
String componentAccessToken = getComponentAccessToken(false); | |||
return excuteRequet(uri, accessTokenKey, componentAccessToken, postData, true, RequestMethod.POST); | |||
} | |||
@Override | |||
public String getByAppAccessToken(String appId,String uri, String accessTokenKey) throws WxErrorException { | |||
String accessToken = getAuthorizerAccessToken(appId,false); | |||
return excuteRequet(uri, accessTokenKey, accessToken, null, true, RequestMethod.GET); | |||
} | |||
@Override | |||
public String postByAppAccessToken(String appId,String uri,String postData, String accessTokenKey) throws WxErrorException { | |||
String accessToken = getAuthorizerAccessToken(appId, false); | |||
return excuteRequet(uri, accessTokenKey, accessToken, postData, true, RequestMethod.POST); | |||
} | |||
private String post(String uri, String postData, String accessTokenKey,String accessTokenValue, boolean isCommpontAccessToken) throws WxErrorException { | |||
return excuteRequet(uri, accessTokenKey, accessTokenValue, postData, isCommpontAccessToken, RequestMethod.POST); | |||
} | |||
private String get(String uri, String accessTokenKey,String accessTokenValue, boolean isCommpontAccessToken) throws WxErrorException { | |||
return excuteRequet(uri, accessTokenKey, accessTokenValue, null, isCommpontAccessToken, RequestMethod.GET); | |||
} | |||
@Override | |||
public String getPreAuthUrl(String redirectUri) throws WxErrorException { | |||
return createPreAuthUrl(redirectUri); | |||
} | |||
/** | |||
* 创建预授权链接 | |||
* | |||
* @param redirectURI | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
private String createPreAuthUrl(String redirectURI) throws WxErrorException { | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||
String responseContent = post(API_CREATE_PREAUTHCODE_URL, jsonObject.toString(),"component_access_token"); | |||
jsonObject = WxGsonBuilder.create().fromJson(responseContent, JsonObject.class); | |||
StringBuilder preAuthUrl = new StringBuilder(String.format(COMPONENT_LOGIN_PAGE_URL, | |||
getWxOpenConfigStorage().getComponentAppId(), | |||
jsonObject.get("pre_auth_code").getAsString(), | |||
URIUtil.encodeURIComponent(redirectURI))); | |||
String preAuthUrlStr = preAuthUrl.toString(); | |||
return preAuthUrlStr; | |||
} | |||
@Override | |||
public String route(final TtOpenTicket ticket) throws WxErrorException { | |||
if (ticket == null) { | |||
throw new NullPointerException("message is empty"); | |||
} | |||
if (StringUtils.equalsIgnoreCase(ticket.getMsgType(), "Ticket")) { | |||
getWxOpenConfigStorage().setComponentVerifyTicket(ticket.getTicket()); | |||
return "success"; | |||
} | |||
return ""; | |||
} | |||
@Override | |||
public TtOpenAuthorizationInfo getQueryAuth(String authorizationCode) throws WxErrorException { | |||
String uri = API_QUERY_AUTH_URL + "?component_appid=" + getWxOpenConfigStorage().getComponentAppId() + | |||
"&authorization_code=" + authorizationCode + | |||
"&grant_type=app_to_tp_authorization_code"; | |||
String responseContent = get(uri,"component_access_token"); | |||
TtOpenAuthorizationInfo queryAuth = TtOpenGsonBuilder.create().fromJson(responseContent, TtOpenAuthorizationInfo.class); | |||
if (queryAuth == null) { | |||
return queryAuth; | |||
} | |||
if (queryAuth.getAuthorizerAccessToken() != null) { | |||
getWxOpenConfigStorage().updateAuthorizerAccessToken(queryAuth.getAuthorizerAppid(), | |||
queryAuth.getAuthorizerAccessToken(), queryAuth.getExpiresIn()); | |||
} | |||
if (queryAuth.getAuthorizerRefreshToken() != null) { | |||
getWxOpenConfigStorage().setAuthorizerRefreshToken(queryAuth.getAuthorizerAppid(), queryAuth.getAuthorizerRefreshToken()); | |||
} | |||
return queryAuth; | |||
} | |||
@Override | |||
public String retrieveCode(String authorizationCode) throws WxErrorException { | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||
jsonObject.addProperty("authorization_appid", authorizationCode); | |||
String responseContent = post(RETRIEVE_AUTHORIZER_TOKEN_URL, jsonObject.toString(), "component_access_token"); | |||
TtOpenRetrieveCode retrieveCode = TtOpenGsonBuilder.create().fromJson(responseContent, TtOpenRetrieveCode.class); | |||
if(retrieveCode == null){ | |||
return null; | |||
} | |||
return retrieveCode.getAuthorizationCode(); | |||
} | |||
// @Override | |||
// public WxOpenAuthorizerInfoResult getAuthorizerInfo(String authorizerAppid) throws WxErrorException { | |||
// JsonObject jsonObject = new JsonObject(); | |||
// jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||
// jsonObject.addProperty("authorizer_appid", authorizerAppid); | |||
// String responseContent = post(API_GET_AUTHORIZER_INFO_URL, jsonObject.toString(),"component_access_token"); | |||
// return WxOpenGsonBuilder.create().fromJson(responseContent, WxOpenAuthorizerInfoResult.class); | |||
// } | |||
@Override | |||
public WxOpenAuthorizerListResult getAuthorizerList(int begin, int len) throws WxErrorException { | |||
begin = begin < 0 ? 0 : begin; | |||
len = len == 0 ? 10 : len; | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||
jsonObject.addProperty("offset", begin); | |||
jsonObject.addProperty("count", len); | |||
String responseContent = post(API_GET_AUTHORIZER_LIST, jsonObject.toString(),"component_access_token"); | |||
WxOpenAuthorizerListResult ret = WxOpenGsonBuilder.create().fromJson(responseContent, WxOpenAuthorizerListResult.class); | |||
if (ret != null && ret.getList() != null) { | |||
for (Map<String, String> data : ret.getList()) { | |||
String authorizerAppid = data.get("authorizer_appid"); | |||
String refreshToken = data.get("refresh_token"); | |||
if (authorizerAppid != null && refreshToken != null) { | |||
this.getWxOpenConfigStorage().setAuthorizerRefreshToken(authorizerAppid, refreshToken); | |||
} | |||
} | |||
} | |||
return ret; | |||
} | |||
@Override | |||
public WxOpenAuthorizerOptionResult getAuthorizerOption(String authorizerAppid, String optionName) throws WxErrorException { | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||
jsonObject.addProperty("authorizer_appid", authorizerAppid); | |||
jsonObject.addProperty("option_name", optionName); | |||
String responseContent = post(API_GET_AUTHORIZER_OPTION_URL, jsonObject.toString(),"component_access_token"); | |||
return WxOpenGsonBuilder.create().fromJson(responseContent, WxOpenAuthorizerOptionResult.class); | |||
} | |||
@Override | |||
public void setAuthorizerOption(String authorizerAppid, String optionName, String optionValue) throws WxErrorException { | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||
jsonObject.addProperty("authorizer_appid", authorizerAppid); | |||
jsonObject.addProperty("option_name", optionName); | |||
jsonObject.addProperty("option_value", optionValue); | |||
post(API_SET_AUTHORIZER_OPTION_URL, jsonObject.toString(),"component_access_token"); | |||
} | |||
@Override | |||
public String getAuthorizerAccessToken(String appId, boolean forceRefresh) throws WxErrorException { | |||
WxOpenConfigStorage config = getWxOpenConfigStorage(); | |||
if (!config.isAuthorizerAccessTokenExpired(appId) && !forceRefresh) { | |||
return config.getAuthorizerAccessToken(appId); | |||
} | |||
Lock lock = config.getWxMpConfigStorage(appId).getAccessTokenLock(); | |||
lock.lock(); | |||
try { | |||
if (!config.isAuthorizerAccessTokenExpired(appId) && !forceRefresh) { | |||
return config.getAuthorizerAccessToken(appId); | |||
} | |||
String uri = API_AUTHORIZER_TOKEN_URL + "?component_appid=" + getWxOpenConfigStorage().getComponentAppId() + | |||
"&authorizer_refresh_token=" + getWxOpenConfigStorage().getAuthorizerRefreshToken(appId) + | |||
"&grant_type=app_to_tp_refresh_token"; | |||
String responseContent = get(uri,"component_access_token"); | |||
TtOpenAuthorizationInfo queryAuth = TtOpenGsonBuilder.create().fromJson(responseContent, TtOpenAuthorizationInfo.class); | |||
if (queryAuth.getAuthorizerAccessToken() != null) { | |||
config.updateAuthorizerAccessToken(queryAuth.getAuthorizerAppid(), | |||
queryAuth.getAuthorizerAccessToken(), queryAuth.getExpiresIn()); | |||
} | |||
if (queryAuth.getAuthorizerRefreshToken() != null) { | |||
config.setAuthorizerRefreshToken(queryAuth.getAuthorizerAppid(), queryAuth.getAuthorizerRefreshToken()); | |||
} | |||
return config.getAuthorizerAccessToken(appId); | |||
} finally { | |||
lock.unlock(); | |||
} | |||
} | |||
@Override | |||
public WxMpOAuth2AccessToken oauth2getAccessToken(String appId, String code) throws WxErrorException { | |||
String url = String.format(OAUTH2_ACCESS_TOKEN_URL, appId, code, getWxOpenConfigStorage().getComponentAppId()); | |||
String responseContent = get(url,"component_access_token"); | |||
return WxMpOAuth2AccessToken.fromJson(responseContent); | |||
} | |||
@Override | |||
public WxMpOAuth2AccessToken oauth2refreshAccessToken(String appId, String refreshToken) throws WxErrorException { | |||
String url = String.format(OAUTH2_REFRESH_TOKEN_URL, appId, refreshToken, getWxOpenConfigStorage().getComponentAppId()); | |||
String responseContent = get(url,"component_access_token"); | |||
return WxMpOAuth2AccessToken.fromJson(responseContent); | |||
} | |||
@Override | |||
public String oauth2buildAuthorizationUrl(String appId, String redirectURI, String scope, String state) { | |||
return String.format(CONNECT_OAUTH2_AUTHORIZE_URL, | |||
appId, URIUtil.encodeURIComponent(redirectURI), scope, StringUtils.trimToEmpty(state), getWxOpenConfigStorage().getComponentAppId()); | |||
} | |||
@Override | |||
public WxMaJscode2SessionResult miniappJscode2Session(String appId, String jsCode) throws WxErrorException { | |||
String url = String.format(MINIAPP_JSCODE_2_SESSION, appId, jsCode, getWxOpenConfigStorage().getComponentAppId()); | |||
String responseContent = get(url,"component_access_token"); | |||
return WxMaJscode2SessionResult.fromJson(responseContent); | |||
} | |||
@Override | |||
public List<WxOpenMaCodeTemplate> getTemplateDraftList() throws WxErrorException { | |||
String responseContent = get(GET_TEMPLATE_DRAFT_LIST_URL, "access_token"); | |||
JsonObject response = JSON_PARSER.parse(StringUtils.defaultString(responseContent, "{}")).getAsJsonObject(); | |||
boolean hasDraftList = response.has("draft_list"); | |||
if (hasDraftList) { | |||
return WxOpenGsonBuilder.create().fromJson(response.getAsJsonArray("draft_list"), | |||
new TypeToken<List<WxOpenMaCodeTemplate>>() { | |||
}.getType()); | |||
} else { | |||
return null; | |||
} | |||
} | |||
@Override | |||
public List<WxOpenMaCodeTemplate> getTemplateList() throws WxErrorException { | |||
String responseContent = get(GET_TEMPLATE_LIST_URL, "access_token"); | |||
JsonObject response = JSON_PARSER.parse(StringUtils.defaultString(responseContent, "{}")).getAsJsonObject(); | |||
boolean hasTemplateList = response.has("template_list"); | |||
if (hasTemplateList) { | |||
return WxOpenGsonBuilder.create().fromJson(response.getAsJsonArray("template_list"), | |||
new TypeToken<List<WxOpenMaCodeTemplate>>() { | |||
}.getType()); | |||
} else { | |||
return null; | |||
} | |||
} | |||
@Override | |||
public void addToTemplate(long draftId) throws WxErrorException { | |||
JsonObject param = new JsonObject(); | |||
param.addProperty("draft_id", draftId); | |||
post(ADD_TO_TEMPLATE_URL, param.toString(), "access_token"); | |||
} | |||
@Override | |||
public void deleteTemplate(long templateId) throws WxErrorException { | |||
JsonObject param = new JsonObject(); | |||
param.addProperty("template_id", templateId); | |||
post(DELETE_TEMPLATE_URL, param.toString(), "access_token"); | |||
} | |||
@Override | |||
public WxOpenCreateResult createOpenAccount(String appId) throws WxErrorException { | |||
JsonObject param = new JsonObject(); | |||
param.addProperty("appid", appId); | |||
String json = postByAppAccessToken(appId, CREATE_OPEN_URL, param.toString(), "access_token"); | |||
return WxOpenCreateResult.fromJson(json); | |||
} | |||
private String excuteRequet(String uri,String tokenKey,String tokenValue,String postData,boolean isCompontAccessToken,RequestMethod method) throws WxErrorException { | |||
String uriWithComponentAccessToken = uri + (uri.contains("?") ? "&" : "?") + tokenKey + "=" + tokenValue; | |||
try { | |||
if (method == RequestMethod.POST) { | |||
return getTtOpenService().post(uriWithComponentAccessToken, postData); | |||
}else { | |||
return getTtOpenService().get(uriWithComponentAccessToken, null); | |||
} | |||
} catch (WxErrorException e) { | |||
WxError error = e.getError(); | |||
/* | |||
* 发生以下情况时尝试刷新access_token | |||
* 40001 获取access_token时AppSecret错误,或者access_token无效 | |||
* 42001 access_token超时 | |||
* 40014 不合法的access_token,请开发者认真比对access_token的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口 | |||
*/ | |||
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) { | |||
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||
Lock lock = this.getWxOpenConfigStorage().getComponentAccessTokenLock(); | |||
lock.lock(); | |||
try { | |||
if (isCompontAccessToken && StringUtils.equals(tokenValue, this.getWxOpenConfigStorage().getComponentAccessToken())) { | |||
this.getWxOpenConfigStorage().expireComponentAccessToken(); | |||
} | |||
} catch (Exception ex) { | |||
if (isCompontAccessToken) { | |||
this.getWxOpenConfigStorage().expireComponentAccessToken(); | |||
} | |||
} finally { | |||
lock.unlock(); | |||
} | |||
if (isCompontAccessToken && this.getWxOpenConfigStorage().autoRefreshToken()) { | |||
if (method == RequestMethod.POST) { | |||
return this.post(uri, postData, tokenKey); | |||
}else { | |||
return this.get(uri, tokenKey); | |||
} | |||
} | |||
} | |||
if (error.getErrorCode() != 0) { | |||
throw new WxErrorException(error, e); | |||
} | |||
return null; | |||
} | |||
} | |||
@Override | |||
public Boolean bindOpenAccount(String appId, String openAppid) throws WxErrorException { | |||
JsonObject param = new JsonObject(); | |||
param.addProperty("appid", appId); | |||
param.addProperty("open_appid", openAppid); | |||
String json = postByAppAccessToken(appId, BIND_OPEN_URL, param.toString(), "access_token"); | |||
return WxOpenResult.fromJson(json).isSuccess(); | |||
} | |||
@Override | |||
public Boolean unbindOpenAccount(String appId, String openAppid) throws WxErrorException { | |||
JsonObject param = new JsonObject(); | |||
param.addProperty("appid", appId); | |||
param.addProperty("open_appid", openAppid); | |||
String json = postByAppAccessToken(appId, UNBIND_OPEN_URL, param.toString(), "access_token"); | |||
return WxOpenResult.fromJson(json).isSuccess(); | |||
} | |||
@Override | |||
public WxOpenGetResult getOpenAccount(String appId) throws WxErrorException { | |||
JsonObject param = new JsonObject(); | |||
param.addProperty("appid", appId); | |||
String json = postByAppAccessToken(appId, GET_OPEN_URL, param.toString(), "access_token"); | |||
return WxOpenGetResult.fromJson(json); | |||
} | |||
@Override | |||
public WxOpenResult fastRegisterWeapp(String name, String code, String codeType, String legalPersonaWechat, String legalPersonaName, String componentPhone) throws WxErrorException { | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("name", name); | |||
jsonObject.addProperty("code", code); | |||
jsonObject.addProperty("code_type", codeType); | |||
jsonObject.addProperty("legal_persona_wechat", legalPersonaWechat); | |||
jsonObject.addProperty("legal_persona_name", legalPersonaName); | |||
jsonObject.addProperty("component_phone", componentPhone); | |||
String response = post(FAST_REGISTER_WEAPP_URL, jsonObject.toString(), "component_access_token"); | |||
return WxOpenGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
@Override | |||
public WxOpenResult fastRegisterWeappSearch(String name, String legalPersonaWechat, String legalPersonaName) throws WxErrorException { | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("name", name); | |||
jsonObject.addProperty("legal_persona_wechat", legalPersonaWechat); | |||
jsonObject.addProperty("legal_persona_name", legalPersonaName); | |||
String response = post(FAST_REGISTER_WEAPP_SEARCH_URL, jsonObject.toString(), "component_access_token"); | |||
return WxOpenGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
} |
@@ -0,0 +1,643 @@ | |||
package com.iformall.service.toutiao.api.impl; | |||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; | |||
import com.google.gson.JsonArray; | |||
import com.google.gson.JsonObject; | |||
import com.iformall.service.toutiao.api.TtOpenComponentService; | |||
import com.iformall.service.toutiao.api.TtOpenMaService; | |||
import com.iformall.service.toutiao.miniapp.impl.TtMaServiceImpl; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.open.bean.ma.WxMaOpenCommitExtInfo; | |||
import me.chanjar.weixin.open.bean.ma.WxMaQrcodeParam; | |||
import me.chanjar.weixin.open.bean.message.WxOpenMaSubmitAuditMessage; | |||
import me.chanjar.weixin.open.bean.result.*; | |||
import me.chanjar.weixin.open.executor.MaQrCodeRequestExecutor; | |||
import java.io.File; | |||
import java.util.List; | |||
import java.util.Map; | |||
/** | |||
* @author <a href="https://github.com/007gzs">007</a> | |||
* <pre> | |||
* 增加开放平台代小程序管理服务能力 | |||
* 说明:这里让这个服务公开便于调用者模拟本地测试服务 | |||
* </pre> | |||
* @author yqx | |||
* @date 2018-09-12 | |||
*/ | |||
public class TtOpenMaServiceImpl extends TtMaServiceImpl implements TtOpenMaService { | |||
private TtOpenComponentService ttOpenComponentService; | |||
private WxMaConfig wxMaConfig; | |||
private String appId; | |||
public TtOpenMaServiceImpl(TtOpenComponentService ttOpenComponentService, String appId, WxMaConfig wxMaConfig) { | |||
this.ttOpenComponentService = ttOpenComponentService; | |||
this.appId = appId; | |||
this.wxMaConfig = wxMaConfig; | |||
initHttp(); | |||
} | |||
@Override | |||
public WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException { | |||
return ttOpenComponentService.miniappJscode2Session(appId, jsCode); | |||
} | |||
@Override | |||
public WxMaConfig getWxMaConfig() { | |||
return wxMaConfig; | |||
} | |||
@Override | |||
public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||
return ttOpenComponentService.getAuthorizerAccessToken(appId, forceRefresh); | |||
} | |||
/** | |||
* 获得小程序的域名配置信息 | |||
* | |||
* @return | |||
*/ | |||
@Override | |||
public WxOpenMaDomainResult getDomain() throws WxErrorException { | |||
return modifyDomain("get", null, null, null, null); | |||
} | |||
/** | |||
* 修改服务器域名 | |||
* | |||
* @param action delete删除, set覆盖, get获取 | |||
* @param requestdomainList | |||
* @param wsrequestdomainList | |||
* @param uploaddomainList | |||
* @param downloaddomainList | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenMaDomainResult modifyDomain(String action, List<String> requestdomainList, List<String> wsrequestdomainList, List<String> uploaddomainList, List<String> downloaddomainList) throws WxErrorException { | |||
// if (!"get".equals(action) && (requestdomainList == null || wsrequestdomainList == null || uploaddomainList == null || downloaddomainList == null)) { | |||
// throw new WxErrorException(WxError.builder().errorCode(44004).errorMsg("域名参数不能为空").build()); | |||
// } | |||
JsonObject requestJson = new JsonObject(); | |||
requestJson.addProperty("action", action); | |||
if (!"get".equals(action)) { | |||
requestJson.add("requestdomain", toJsonArray(requestdomainList)); | |||
requestJson.add("wsrequestdomain", toJsonArray(wsrequestdomainList)); | |||
requestJson.add("uploaddomain", toJsonArray(uploaddomainList)); | |||
requestJson.add("downloaddomain", toJsonArray(downloaddomainList)); | |||
} | |||
String response = post(API_MODIFY_DOMAIN, GSON.toJson(requestJson)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaDomainResult.class); | |||
} | |||
/** | |||
* 获取小程序的业务域名 | |||
* | |||
* @return | |||
*/ | |||
@Override | |||
public String getWebViewDomain() throws WxErrorException { | |||
return setWebViewDomain("get", null); | |||
} | |||
/** | |||
* 获取小程序的业务域名 | |||
* | |||
* @return | |||
*/ | |||
@Override | |||
public WxOpenMaWebDomainResult getWebViewDomainInfo() throws WxErrorException { | |||
return setWebViewDomainInfo("get", null); | |||
} | |||
/** | |||
* 设置小程序的业务域名 | |||
* | |||
* @param action add添加, delete删除, set覆盖 | |||
* @param domainList | |||
* @return | |||
*/ | |||
@Override | |||
public String setWebViewDomain(String action, List<String> domainList) throws WxErrorException { | |||
JsonObject requestJson = new JsonObject(); | |||
requestJson.addProperty("action", action); | |||
if (!"get".equals(action)) { | |||
requestJson.add("webviewdomain", toJsonArray(domainList)); | |||
} | |||
String response = post(API_SET_WEBVIEW_DOMAIN, GSON.toJson(requestJson)); | |||
//TODO 转化为对象返回 | |||
return response; | |||
} | |||
/** | |||
* 设置小程序的业务域名 | |||
* | |||
* @param action add添加, delete删除, set覆盖 | |||
* @return | |||
*/ | |||
@Override | |||
public WxOpenMaWebDomainResult setWebViewDomainInfo(String action, List<String> domainList) throws WxErrorException { | |||
String response = this.setWebViewDomain(action, domainList); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaWebDomainResult.class); | |||
} | |||
/** | |||
* 获取小程序的信息,GET请求 | |||
* <pre> | |||
* 注意:这里不能直接用小程序的access_token | |||
* //TODO 待调整 | |||
* </pre> | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public String getAccountBasicInfo() throws WxErrorException { | |||
String response = get(API_GET_ACCOUNT_BASICINFO, ""); | |||
return response; | |||
} | |||
/** | |||
* 绑定小程序体验者 | |||
* | |||
* @param wechatid 体验者微信号(不是openid) | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenMaBindTesterResult bindTester(String wechatid) throws WxErrorException { | |||
JsonObject paramJson = new JsonObject(); | |||
paramJson.addProperty("wechatid", wechatid); | |||
String response = post(API_BIND_TESTER, GSON.toJson(paramJson)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaBindTesterResult.class); | |||
} | |||
/** | |||
* 解除绑定小程序体验者 | |||
* | |||
* @param wechatid 体验者微信号(不是openid) | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult unbindTester(String wechatid) throws WxErrorException { | |||
JsonObject paramJson = new JsonObject(); | |||
paramJson.addProperty("wechatid", wechatid); | |||
String response = post(API_UNBIND_TESTER, GSON.toJson(paramJson)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 解除绑定小程序体验者 | |||
* @param userstr 人员对应的唯一字符串, 可通过获取已绑定的体验者列表获取人员对应的字符串 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult unbindTesterByUserstr(String userstr) throws WxErrorException { | |||
JsonObject paramJson = new JsonObject(); | |||
paramJson.addProperty("userstr", userstr); | |||
String response = post(API_UNBIND_TESTER, GSON.toJson(paramJson)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 获得体验者列表 | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenMaTesterListResult getTesterList() throws WxErrorException { | |||
JsonObject paramJson = new JsonObject(); | |||
paramJson.addProperty("action", "get_experiencer"); | |||
String response = post(API_GET_TESTERLIST, GSON.toJson(paramJson)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaTesterListResult.class); | |||
} | |||
/** | |||
* 设置小程序隐私设置(是否可被搜索) | |||
* | |||
* @param status 1表示不可搜索,0表示可搜索 | |||
*/ | |||
@Override | |||
public WxOpenResult changeWxaSearchStatus(Integer status) throws WxErrorException { | |||
JsonObject paramJson = new JsonObject(); | |||
paramJson.addProperty("status", status); | |||
String response = post(API_CHANGE_WXA_SEARCH_STATUS, GSON.toJson(paramJson)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 2. 查询小程序当前隐私设置(是否可被搜索) | |||
*/ | |||
@Override | |||
public WxOpenMaSearchStatusResult getWxaSearchStatus() throws WxErrorException { | |||
String response = get(API_GET_WXA_SEARCH_STATUS, null); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaSearchStatusResult.class); | |||
} | |||
/** | |||
* 3.1 获取展示的公众号信息 | |||
*/ | |||
@Override | |||
public WxOpenMaShowItemResult getShowWxaItem() throws WxErrorException { | |||
String response = get(API_GET_SHOW_WXA_ITEM, null); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaShowItemResult.class); | |||
} | |||
/** | |||
* 3.2 设置展示的公众号 | |||
* | |||
* @param flag 0 关闭,1 开启 | |||
* @param mpappid 如果开启,需要传新的公众号appid | |||
*/ | |||
@Override | |||
public WxOpenResult updateShowwxaitem(Integer flag, String mpappid) throws WxErrorException { | |||
JsonObject paramJson = new JsonObject(); | |||
paramJson.addProperty("wxa_subscribe_biz_flag", flag); | |||
paramJson.addProperty("appid", mpappid); | |||
String response = post(API_UPDATE_SHOW_WXA_ITEM, GSON.toJson(paramJson)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 1、为授权的小程序帐号上传小程序代码 | |||
* | |||
* @param templateId 代码模板ID | |||
* @param userVersion 用户定义版本 | |||
* @param userDesc 用户定义版本描述 | |||
* @param extInfo 第三方自定义的配置 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult codeCommit(Long templateId, String userVersion, String userDesc, WxMaOpenCommitExtInfo extInfo) throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
params.addProperty("template_id", templateId); | |||
params.addProperty("user_version", userVersion); | |||
params.addProperty("user_desc", userDesc); | |||
//注意:ext_json必须是字符串类型 | |||
params.addProperty("ext_json", GSON.toJson(extInfo)); | |||
String response = post(API_CODE_COMMIT, GSON.toJson(params)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 获取体验小程序的体验二维码 | |||
* | |||
* @param pagePath | |||
* @param params | |||
* @return | |||
*/ | |||
@Override | |||
public File getTestQrcode(String pagePath, Map<String, String> params) throws WxErrorException { | |||
WxMaQrcodeParam qrcodeParam = WxMaQrcodeParam.create(pagePath); | |||
qrcodeParam.addPageParam(params); | |||
return execute(MaQrCodeRequestExecutor.create(getRequestHttp()), API_TEST_QRCODE, qrcodeParam); | |||
} | |||
/** | |||
* 获取授权小程序帐号的可选类目 | |||
* <p> | |||
* 注意:该接口可获取已设置的二级类目及用于代码审核的可选三级类目。 | |||
* </p> | |||
* | |||
* @return WxOpenMaCategoryListResult | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenMaCategoryListResult getCategoryList() throws WxErrorException { | |||
String response = get(API_GET_CATEGORY, null); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaCategoryListResult.class); | |||
} | |||
/** | |||
* 获取小程序的第三方提交代码的页面配置(仅供第三方开发者代小程序调用) | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenMaPageListResult getPageList() throws WxErrorException { | |||
String response = get(API_GET_PAGE, null); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaPageListResult.class); | |||
} | |||
/** | |||
* 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用) | |||
* | |||
* @param submitAuditMessage | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenMaSubmitAuditResult submitAudit(WxOpenMaSubmitAuditMessage submitAuditMessage) throws WxErrorException { | |||
String response = post(API_SUBMIT_AUDIT, GSON.toJson(submitAuditMessage)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaSubmitAuditResult.class); | |||
} | |||
/** | |||
* 7. 查询某个指定版本的审核状态(仅供第三方代小程序调用) | |||
* | |||
* @param auditid | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenMaQueryAuditResult getAuditStatus(Long auditid) throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
params.addProperty("auditid", auditid); | |||
String response = post(API_GET_AUDIT_STATUS, GSON.toJson(params)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaQueryAuditResult.class); | |||
} | |||
/** | |||
* 8. 查询最新一次提交的审核状态(仅供第三方代小程序调用) | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenMaQueryAuditResult getLatestAuditStatus() throws WxErrorException { | |||
String response = get(API_GET_LATEST_AUDIT_STATUS, null); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaQueryAuditResult.class); | |||
} | |||
/** | |||
* 9. 发布已通过审核的小程序(仅供第三方代小程序调用) | |||
* <p> | |||
* 请填写空的数据包,POST的json数据包为空即可。 | |||
* </p> | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult releaesAudited() throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
String response = post(API_RELEASE, GSON.toJson(params)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 10. 修改小程序线上代码的可见状态(仅供第三方代小程序调用) | |||
* | |||
* @param action 设置可访问状态,发布后默认可访问,close为不可见,open为可见 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult changeVisitstatus(String action) throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
params.addProperty("action", action); | |||
String response = post(API_CHANGE_VISITSTATUS, GSON.toJson(params)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 11. 小程序版本回退(仅供第三方代小程序调用) | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult revertCodeReleaes() throws WxErrorException { | |||
String response = get(API_REVERT_CODE_RELEASE, null); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 15. 小程序审核撤回 | |||
* <p> | |||
* 单个帐号每天审核撤回次数最多不超过1次,一个月不超过10次。 | |||
* </p> | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult undoCodeAudit() throws WxErrorException { | |||
String response = get(API_UNDO_CODE_AUDIT, null); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 12. 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用) | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public String getSupportVersion() throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
String response = post(API_GET_WEAPP_SUPPORT_VERSION, GSON.toJson(params)); | |||
return response; | |||
} | |||
/** | |||
* 12. 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用) | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenMaWeappSupportVersionResult getSupportVersionInfo() throws WxErrorException { | |||
String response = this.getSupportVersion(); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaWeappSupportVersionResult.class); | |||
} | |||
/** | |||
* 13. 设置最低基础库版本(仅供第三方代小程序调用) | |||
* | |||
* @param version | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public String setSupportVersion(String version) throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
params.addProperty("version", version); | |||
String response = post(API_SET_WEAPP_SUPPORT_VERSION, GSON.toJson(params)); | |||
return response; | |||
} | |||
/** | |||
* 13. 设置最低基础库版本(仅供第三方代小程序调用) | |||
* | |||
* @param version | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult setSupportVersionInfo(String version) throws WxErrorException { | |||
String response = this.setSupportVersion(version); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 16. 小程序分阶段发布 - 1)分阶段发布接口 | |||
* | |||
* @param grayPercentage 灰度的百分比,1到100的整数 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult grayrelease(Integer grayPercentage) throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
params.addProperty("gray_percentage", grayPercentage); | |||
String response = post(API_GRAY_RELEASE, GSON.toJson(params)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 16. 小程序分阶段发布 - 2)取消分阶段发布 | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult revertgrayrelease() throws WxErrorException { | |||
String response = get(API_REVERT_GRAY_RELEASE, null); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 16. 小程序分阶段发布 - 3)查询当前分阶段发布详情 | |||
* | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenMaGrayReleasePlanResult getgrayreleaseplan() throws WxErrorException { | |||
String response = get(API_GET_GRAY_RELEASE_PLAN, null); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaGrayReleasePlanResult.class); | |||
} | |||
/** | |||
* 查询服务商的当月提审限额和加急次数(Quota) | |||
* https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Mini_Programs/code/query_quota.html | |||
*/ | |||
@Override | |||
public WxOpenMaQueryQuotaResult queryQuota() throws WxErrorException { | |||
String response = get(API_QUERY_QUOTA, null); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenMaQueryQuotaResult.class); | |||
} | |||
/** | |||
* 加急审核申请 | |||
* 有加急次数的第三方可以通过该接口,对已经提审的小程序进行加急操作,加急后的小程序预计2-12小时内审完。 | |||
*/ | |||
@Override | |||
public Boolean speedAudit(Long auditid) throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
params.addProperty("auditid", auditid); | |||
String response = post(API_SPEED_AUDIT, GSON.toJson(params)); | |||
WxOpenResult result = WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
return result.isSuccess(); | |||
} | |||
/** | |||
* (1)增加或修改二维码规则 | |||
* @param wxQrcodeJumpRule | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult addQrcodeJump(WxQrcodeJumpRule wxQrcodeJumpRule) throws WxErrorException { | |||
String response = post(API_QRCODE_JUMP_ADD, GSON.toJson(wxQrcodeJumpRule)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* (2)获取已设置的二维码规则 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxGetQrcodeJumpResult getQrcodeJump() throws WxErrorException { | |||
String response = post(API_QRCODE_JUMP_GET, "{}"); | |||
return WxMaGsonBuilder.create().fromJson(response, WxGetQrcodeJumpResult.class); | |||
} | |||
/** | |||
* (3)获取校验文件名称及内容 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxDownlooadQrcodeJumpResult downloadQrcodeJump() throws WxErrorException { | |||
String response = post(API_QRCODE_JUMP_DOWNLOAD, "{}"); | |||
return WxMaGsonBuilder.create().fromJson(response, WxDownlooadQrcodeJumpResult.class); | |||
} | |||
/** | |||
* (4)删除已设置的二维码规则 | |||
* @param prefix | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult deleteQrcodeJump(String prefix) throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
params.addProperty("prefix", prefix); | |||
String response = post(API_QRCODE_JUMP_DELETE, GSON.toJson(params)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* (5)发布已设置的二维码规则 | |||
* @param prefix | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
@Override | |||
public WxOpenResult publishQrcodeJump(String prefix) throws WxErrorException { | |||
JsonObject params = new JsonObject(); | |||
params.addProperty("prefix", prefix); | |||
String response = post(API_QRCODE_JUMP_PUBLISH, GSON.toJson(params)); | |||
return WxMaGsonBuilder.create().fromJson(response, WxOpenResult.class); | |||
} | |||
/** | |||
* 将字符串对象转化为GsonArray对象 | |||
* | |||
* @param strList | |||
* @return | |||
*/ | |||
private JsonArray toJsonArray(List<String> strList) { | |||
JsonArray jsonArray = new JsonArray(); | |||
if (strList != null && !strList.isEmpty()) { | |||
for (String str : strList) { | |||
jsonArray.add(str); | |||
} | |||
} | |||
return jsonArray; | |||
} | |||
} |
@@ -0,0 +1,62 @@ | |||
package com.iformall.service.toutiao.api.impl; | |||
import com.iformall.service.toutiao.api.TtOpenComponentService; | |||
import com.iformall.service.toutiao.api.TtOpenService; | |||
import me.chanjar.weixin.common.WxType; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.common.util.http.RequestHttp; | |||
import me.chanjar.weixin.open.api.WxOpenConfigStorage; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import java.io.IOException; | |||
/** | |||
* @author | |||
*/ | |||
public abstract class TtOpenServiceAbstractImpl<H, P> implements TtOpenService, RequestHttp<H, P> { | |||
private final Logger log = LoggerFactory.getLogger(this.getClass()); | |||
private TtOpenComponentService ttOpenComponentService = new TtOpenComponentServiceImpl(this); | |||
private WxOpenConfigStorage wxOpenConfigStorage; | |||
@Override | |||
public TtOpenComponentService getTtOpenComponentService() { | |||
return ttOpenComponentService; | |||
} | |||
@Override | |||
public WxOpenConfigStorage getWxOpenConfigStorage() { | |||
return wxOpenConfigStorage; | |||
} | |||
@Override | |||
public void setWxOpenConfigStorage(WxOpenConfigStorage wxOpenConfigStorage) { | |||
this.wxOpenConfigStorage = wxOpenConfigStorage; | |||
this.initHttp(); | |||
} | |||
/** | |||
* 初始化 RequestHttp. | |||
*/ | |||
public abstract void initHttp(); | |||
protected <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
try { | |||
T result = executor.execute(uri, data, WxType.Open); | |||
this.log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uri, data, result); | |||
return result; | |||
} catch (WxErrorException e) { | |||
WxError error = e.getError(); | |||
if (error.getErrorCode() != 0) { | |||
this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uri, data, error); | |||
throw new WxErrorException(error, e); | |||
} | |||
return null; | |||
} catch (IOException e) { | |||
this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uri, data, e.getMessage()); | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
} |
@@ -0,0 +1,67 @@ | |||
package com.iformall.service.toutiao.api.impl; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.http.HttpType; | |||
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | |||
import me.chanjar.weixin.open.api.WxOpenConfigStorage; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
/** | |||
* apache-http方式实现 | |||
* | |||
* @author <a href="https://github.com/007gzs">007</a> | |||
*/ | |||
public class TtOpenServiceApacheHttpClientImpl extends TtOpenServiceAbstractImpl<CloseableHttpClient, HttpHost> { | |||
private CloseableHttpClient httpClient; | |||
private HttpHost httpProxy; | |||
@Override | |||
public void initHttp() { | |||
WxOpenConfigStorage configStorage = this.getWxOpenConfigStorage(); | |||
ApacheHttpClientBuilder apacheHttpClientBuilder = configStorage.getApacheHttpClientBuilder(); | |||
if (null == apacheHttpClientBuilder) { | |||
apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get(); | |||
} | |||
apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost()) | |||
.httpProxyPort(configStorage.getHttpProxyPort()) | |||
.httpProxyUsername(configStorage.getHttpProxyUsername()) | |||
.httpProxyPassword(configStorage.getHttpProxyPassword()); | |||
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) { | |||
this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort()); | |||
} | |||
this.httpClient = apacheHttpClientBuilder.build(); | |||
} | |||
@Override | |||
public CloseableHttpClient getRequestHttpClient() { | |||
return httpClient; | |||
} | |||
@Override | |||
public HttpHost getRequestHttpProxy() { | |||
return httpProxy; | |||
} | |||
@Override | |||
public HttpType getRequestType() { | |||
return HttpType.APACHE_HTTP; | |||
} | |||
@Override | |||
public String get(String url, String queryParam) throws WxErrorException { | |||
return execute(SimpleGetRequestExecutor.create(this), url, queryParam); | |||
} | |||
@Override | |||
public String post(String url, String postData) throws WxErrorException { | |||
return execute(SimplePostRequestExecutor.create(this), url, postData); | |||
} | |||
} |
@@ -0,0 +1,8 @@ | |||
package com.iformall.service.toutiao.api.impl; | |||
/** | |||
* @author | |||
*/ | |||
public class TtOpenServiceImpl extends TtOpenServiceApacheHttpClientImpl { | |||
} |
@@ -0,0 +1,28 @@ | |||
package com.iformall.service.toutiao.miniapp; | |||
import com.iformall.service.toutiao.miniapp.bean.TtMaSubscribeMessage; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
/** | |||
* <pre> | |||
* 消息发送接口 | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public interface TtMaMsgService { | |||
String SUBSCRIBE_MSG_SEND_URL = "https://developer.toutiao.com/api/apps/subscribe_notification/developer/v1/notify"; | |||
/** | |||
* <pre> | |||
* 发送订阅消息 | |||
* https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/subscribe-notification/notify/ | |||
* </pre> | |||
* | |||
* @param subscribeMessage 订阅消息 | |||
* @throws WxErrorException . | |||
*/ | |||
void sendSubscribeMsg(TtMaSubscribeMessage subscribeMessage) throws WxErrorException; | |||
} |
@@ -0,0 +1,39 @@ | |||
package com.iformall.service.toutiao.miniapp; | |||
import cn.binarywang.wx.miniapp.bean.AbstractWxMaQrcodeWrapper; | |||
import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; | |||
import lombok.Data; | |||
import lombok.EqualsAndHashCode; | |||
import java.io.Serializable; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
@Data | |||
@EqualsAndHashCode(callSuper = false) | |||
public class TtMaQrcode extends AbstractWxMaQrcodeWrapper implements Serializable { | |||
private static final long serialVersionUID = 5777119669111011584L; | |||
private String access_token; | |||
private String appname; | |||
private String path; | |||
private int width = 430; | |||
public TtMaQrcode(String appname, String path, int width) { | |||
this.appname = appname; | |||
this.path = path; | |||
this.width = width; | |||
} | |||
public static TtMaQrcode fromJson(String json) { | |||
return WxMaGsonBuilder.create().fromJson(json, TtMaQrcode.class); | |||
} | |||
@Override | |||
public String toString() { | |||
return WxMaGsonBuilder.create().toJson(this); | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
package com.iformall.service.toutiao.miniapp; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import java.io.File; | |||
/** | |||
* <pre> | |||
* 二维码相关操作接口. | |||
* | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public interface TtMaQrcodeService { | |||
String GET_TTACODE_URL = "https://developer.toutiao.com/api/apps/qrcode"; | |||
/** | |||
* 接口A: 获取小程序码. | |||
* | |||
* @param path 不能为空,最大长度 128 字节 | |||
* @param width 默认430 二维码的宽度 | |||
* @return 文件对象 | |||
* @throws WxErrorException 异常 | |||
*/ | |||
File createTtaCode(String appname, String path, int width) throws WxErrorException; | |||
} |
@@ -0,0 +1,132 @@ | |||
package com.iformall.service.toutiao.miniapp; | |||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.common.util.http.RequestHttp; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public interface TtMaService { | |||
/** | |||
* 获取access_token. | |||
*/ | |||
String GET_ACCESS_TOKEN_URL = "https://developer.toutiao.com/api/apps/token?grant_type=client_credential&appid=%s&secret=%s"; | |||
String JSCODE_TO_SESSION_URL = "https://developer.toutiao.com/api/apps/jscode2session"; | |||
String ORDER_PUSH = "https://developer.toutiao.com/api/apps/order/v2/push"; | |||
/** | |||
* 获取登录后的session信息. | |||
* | |||
* @param jsCode 登录时获取的 code | |||
*/ | |||
WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 验证消息的确来自微信服务器. | |||
* 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN | |||
* </pre> | |||
*/ | |||
boolean checkSignature(String timestamp, String nonce, String signature); | |||
/** | |||
* <pre> | |||
* 获取access_token,本方法线程安全. | |||
* 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限 | |||
* | |||
* 另:本service的所有方法都会在access_token过期是调用此方法 | |||
* | |||
* 程序员在非必要情况下尽量不要主动调用此方法 | |||
* | |||
* 详情请见: https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/server/interface-request-credential/get-access-token | |||
* </pre> | |||
* | |||
* @param forceRefresh 强制刷新 | |||
*/ | |||
String getAccessToken(boolean forceRefresh) throws WxErrorException; | |||
/** | |||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. | |||
*/ | |||
String get(String url, String queryParam) throws WxErrorException; | |||
/** | |||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. | |||
*/ | |||
String post(String url, String postData) throws WxErrorException; | |||
/** | |||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. | |||
*/ | |||
String post(String url, Object obj) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* Service没有实现某个API的时候,可以用这个, | |||
* 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。 | |||
* 可以参考,{@link MediaUploadRequestExecutor}的实现方法 | |||
* </pre> | |||
*/ | |||
<T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试. | |||
* 默认:1000ms | |||
* </pre> | |||
*/ | |||
void setRetrySleepMillis(int retrySleepMillis); | |||
/** | |||
* <pre> | |||
* 设置当微信系统响应系统繁忙时,最大重试次数. | |||
* 默认:5次 | |||
* </pre> | |||
*/ | |||
void setMaxRetryTimes(int maxRetryTimes); | |||
/** | |||
* 获取WxMaConfig 对象. | |||
* | |||
* @return WxMaConfig | |||
*/ | |||
WxMaConfig getWxMaConfig(); | |||
/** | |||
* 注入 {@link WxMaConfig} 的实现. | |||
*/ | |||
void setWxMaConfig(WxMaConfig wxConfigProvider); | |||
/** | |||
* 初始化http请求对象. | |||
*/ | |||
void initHttp(); | |||
/** | |||
* 请求http请求相关信息. | |||
*/ | |||
RequestHttp getRequestHttp(); | |||
/** | |||
* 返回用户相关接口方法的实现类对象,以方便调用其各个接口. | |||
* | |||
* @return TtMaUserService | |||
*/ | |||
TtMaUserService getUserService(); | |||
/** | |||
* 返回用户相关接口方法的实现类对象,以方便调用其各个接口. | |||
* | |||
* @return TtMaUserService | |||
*/ | |||
TtMaQrcodeService getQrcodeService(); | |||
TtMaMsgService getMsgService(); | |||
} |
@@ -0,0 +1,52 @@ | |||
package com.iformall.service.toutiao.miniapp; | |||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
/** | |||
* 用户信息相关操作接口. | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public interface TtMaUserService { | |||
/** | |||
* 获取登录后的session信息. | |||
* | |||
* @param jsCode 登录时获取的 code | |||
* @return . | |||
* @throws WxErrorException . | |||
*/ | |||
WxMaJscode2SessionResult getSessionInfo(String jsCode) throws WxErrorException; | |||
/** | |||
* 解密用户敏感数据. | |||
* | |||
* @param sessionKey 会话密钥 | |||
* @param encryptedData 消息密文 | |||
* @param ivStr 加密算法的初始向量 | |||
*/ | |||
WxMaUserInfo getUserInfo(String sessionKey, String encryptedData, String ivStr); | |||
/** | |||
* 解密用户手机号信息. | |||
* | |||
* @param sessionKey 会话密钥 | |||
* @param encryptedData 消息密文 | |||
* @param ivStr 加密算法的初始向量 | |||
* @return . | |||
*/ | |||
WxMaPhoneNumberInfo getPhoneNoInfo(String sessionKey, String encryptedData, String ivStr); | |||
/** | |||
* 验证用户信息完整性. | |||
* | |||
* @param sessionKey 会话密钥 | |||
* @param rawData 微信用户基本信息 | |||
* @param signature 数据签名 | |||
* @return . | |||
*/ | |||
boolean checkUserInfo(String sessionKey, String rawData, String signature); | |||
} |
@@ -0,0 +1,69 @@ | |||
package com.iformall.service.toutiao.miniapp; | |||
import cn.binarywang.wx.miniapp.bean.AbstractWxMaQrcodeWrapper; | |||
import me.chanjar.weixin.common.WxType; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.fs.FileUtils; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.common.util.http.RequestHttp; | |||
import me.chanjar.weixin.common.util.http.ResponseHandler; | |||
import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler; | |||
import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; | |||
import org.apache.http.Header; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.client.config.RequestConfig; | |||
import org.apache.http.client.methods.CloseableHttpResponse; | |||
import org.apache.http.client.methods.HttpPost; | |||
import org.apache.http.entity.ContentType; | |||
import org.apache.http.entity.StringEntity; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.UUID; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public class TtQrcodeRequestExecutor implements RequestExecutor<File, AbstractWxMaQrcodeWrapper> { | |||
protected RequestHttp<CloseableHttpClient, HttpHost> requestHttp; | |||
public TtQrcodeRequestExecutor(RequestHttp requestHttp) { | |||
this.requestHttp = requestHttp; | |||
} | |||
@Override | |||
public void execute(String uri, AbstractWxMaQrcodeWrapper data, ResponseHandler<File> handler, WxType wxType) throws WxErrorException, IOException { | |||
handler.handle(this.execute(uri, data, wxType)); | |||
} | |||
@Override | |||
public File execute(String uri, AbstractWxMaQrcodeWrapper qrcodeWrapper, WxType wxType) throws WxErrorException, IOException { | |||
HttpPost httpPost = new HttpPost(uri); | |||
if (requestHttp.getRequestHttpProxy() != null) { | |||
httpPost.setConfig( | |||
RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build() | |||
); | |||
} | |||
httpPost.addHeader("Content-Type","application/json"); | |||
httpPost.setEntity(new StringEntity(qrcodeWrapper.toJson())); | |||
try (final CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost); | |||
final InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { | |||
Header[] contentTypeHeader = response.getHeaders("Content-Type"); | |||
if (contentTypeHeader != null && contentTypeHeader.length > 0 | |||
&& ContentType.APPLICATION_JSON.getMimeType() | |||
.equals(ContentType.parse(contentTypeHeader[0].getValue()).getMimeType())) { | |||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||
throw new WxErrorException(WxError.fromJson(responseContent, wxType)); | |||
} | |||
return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg"); | |||
} finally { | |||
httpPost.releaseConnection(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
package com.iformall.service.toutiao.miniapp.bean; | |||
import com.google.gson.Gson; | |||
import com.google.gson.GsonBuilder; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
public class TtMaGsonBuilder { | |||
private static final GsonBuilder INSTANCE = new GsonBuilder(); | |||
static { | |||
INSTANCE.disableHtmlEscaping(); | |||
INSTANCE.registerTypeAdapter(TtMaSubscribeMessage.class, new TtMaSubscribeMessageGsonAdapter()); | |||
} | |||
public static Gson create() { | |||
return INSTANCE.create(); | |||
} | |||
} |
@@ -0,0 +1,70 @@ | |||
package com.iformall.service.toutiao.miniapp.bean; | |||
import com.google.gson.JsonObject; | |||
import lombok.*; | |||
import java.io.Serializable; | |||
/** | |||
* 订阅消息. | |||
* @author S | |||
*/ | |||
@Getter | |||
@Setter | |||
@NoArgsConstructor | |||
@AllArgsConstructor | |||
@Builder | |||
public class TtMaSubscribeMessage implements Serializable { | |||
private static final long serialVersionUID = -5931206947637944325L; | |||
private String accessToken; | |||
private String appId; | |||
/** | |||
* 接收者(用户)的 openid. | |||
* <pre> | |||
* 参数:touser | |||
* 是否必填: 是 | |||
* 描述: 接收者(用户)的 openid | |||
* </pre> | |||
*/ | |||
private String openId; | |||
/** | |||
* 所需下发的模板消息的id. | |||
* <pre> | |||
* 参数:template_id | |||
* 是否必填: 是 | |||
* 描述: 所需下发的模板消息的id | |||
* </pre> | |||
*/ | |||
private String tplId; | |||
/** | |||
* 点击模板卡片后的跳转页面,仅限本小程序内的页面. | |||
* <pre> | |||
* 参数:page | |||
* 是否必填: 否 | |||
* 描述: 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。 | |||
* </pre> | |||
*/ | |||
private String page; | |||
/** | |||
* 模板内容,不填则下发空模板. | |||
* <pre> | |||
* 参数:data | |||
* 是否必填: 是 | |||
* 描述: 模板内容,不填则下发空模板 | |||
* </pre> | |||
*/ | |||
private JsonObject data; | |||
public String toJson() { | |||
return TtMaGsonBuilder.create().toJson(this); | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
package com.iformall.service.toutiao.miniapp.bean; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonSerializationContext; | |||
import com.google.gson.JsonSerializer; | |||
import java.lang.reflect.Type; | |||
/** | |||
* . | |||
* | |||
* @author S | |||
*/ | |||
public class TtMaSubscribeMessageGsonAdapter implements JsonSerializer<TtMaSubscribeMessage> { | |||
@Override | |||
public JsonElement serialize(TtMaSubscribeMessage message, Type typeOfSrc, JsonSerializationContext context) { | |||
JsonObject messageJson = new JsonObject(); | |||
messageJson.addProperty("access_token", message.getAccessToken()); | |||
messageJson.addProperty("app_id", message.getAppId()); | |||
messageJson.addProperty("tpl_id", message.getTplId()); | |||
messageJson.addProperty("open_id", message.getOpenId()); | |||
if (message.getPage() != null) { | |||
messageJson.addProperty("page", message.getPage()); | |||
} | |||
messageJson.add("data", message.getData()); | |||
return messageJson; | |||
} | |||
} |
@@ -0,0 +1,30 @@ | |||
package com.iformall.service.toutiao.miniapp.impl; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import com.iformall.service.toutiao.miniapp.TtMaMsgService; | |||
import com.iformall.service.toutiao.miniapp.TtMaService; | |||
import com.iformall.service.toutiao.miniapp.bean.TtMaSubscribeMessage; | |||
import lombok.AllArgsConstructor; | |||
import me.chanjar.weixin.common.WxType; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
@AllArgsConstructor | |||
public class TtMaMsgServiceImpl implements TtMaMsgService { | |||
private static final JsonParser JSON_PARSER = new JsonParser(); | |||
private TtMaService ttMaService; | |||
@Override | |||
public void sendSubscribeMsg(TtMaSubscribeMessage subscribeMessage) throws WxErrorException { | |||
String responseContent = this.ttMaService.post(SUBSCRIBE_MSG_SEND_URL, subscribeMessage); | |||
JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject(); | |||
if (jsonObject.get("err_no").getAsInt() != 0) { | |||
throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp)); | |||
} | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
package com.iformall.service.toutiao.miniapp.impl; | |||
import com.iformall.service.toutiao.miniapp.TtMaQrcode; | |||
import com.iformall.service.toutiao.miniapp.TtMaQrcodeService; | |||
import com.iformall.service.toutiao.miniapp.TtMaService; | |||
import com.iformall.service.toutiao.miniapp.TtQrcodeRequestExecutor; | |||
import lombok.AllArgsConstructor; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import java.io.File; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
@AllArgsConstructor | |||
public class TtMaQrcodeServiceImpl implements TtMaQrcodeService { | |||
private TtMaService ttMaService; | |||
@Override | |||
public File createTtaCode(String appname, String path, int width) throws WxErrorException { | |||
final TtQrcodeRequestExecutor executor = new TtQrcodeRequestExecutor(this.ttMaService.getRequestHttp()); | |||
return this.ttMaService.execute(executor, GET_TTACODE_URL, new TtMaQrcode(appname,path, width)); | |||
} | |||
} |
@@ -0,0 +1,312 @@ | |||
package com.iformall.service.toutiao.miniapp.impl; | |||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||
import com.google.common.base.Joiner; | |||
import com.google.gson.Gson; | |||
import com.google.gson.JsonParser; | |||
import com.iformall.service.toutiao.miniapp.TtMaMsgService; | |||
import com.iformall.service.toutiao.miniapp.TtMaQrcodeService; | |||
import com.iformall.service.toutiao.miniapp.TtMaService; | |||
import com.iformall.service.toutiao.miniapp.TtMaUserService; | |||
import com.iformall.service.toutiao.miniapp.bean.TtMaGsonBuilder; | |||
import lombok.extern.slf4j.Slf4j; | |||
import me.chanjar.weixin.common.WxType; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.DataUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.http.*; | |||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.client.config.RequestConfig; | |||
import org.apache.http.client.methods.CloseableHttpResponse; | |||
import org.apache.http.client.methods.HttpGet; | |||
import org.apache.http.impl.client.BasicResponseHandler; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import java.io.IOException; | |||
import java.lang.reflect.Field; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.concurrent.locks.Lock; | |||
import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*; | |||
/** | |||
* @author | |||
*/ | |||
@Slf4j | |||
public class TtMaServiceImpl implements TtMaService, RequestHttp<CloseableHttpClient, HttpHost> { | |||
private static final JsonParser JSON_PARSER = new JsonParser(); | |||
private CloseableHttpClient httpClient; | |||
private HttpHost httpProxy; | |||
private WxMaConfig wxMaConfig; | |||
private TtMaUserService userService = new TtMaUserServiceImpl(this); | |||
private TtMaQrcodeService qrcodeService = new TtMaQrcodeServiceImpl(this); | |||
private TtMaMsgService msgService = new TtMaMsgServiceImpl(this); | |||
private int retrySleepMillis = 1000; | |||
private int maxRetryTimes = 5; | |||
protected static final Gson GSON = new Gson(); | |||
@Override | |||
public CloseableHttpClient getRequestHttpClient() { | |||
return httpClient; | |||
} | |||
@Override | |||
public HttpHost getRequestHttpProxy() { | |||
return httpProxy; | |||
} | |||
@Override | |||
public HttpType getRequestType() { | |||
return HttpType.APACHE_HTTP; | |||
} | |||
@Override | |||
public void initHttp() { | |||
WxMaConfig configStorage = this.getWxMaConfig(); | |||
ApacheHttpClientBuilder apacheHttpClientBuilder = configStorage.getApacheHttpClientBuilder(); | |||
if (null == apacheHttpClientBuilder) { | |||
apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get(); | |||
} | |||
apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost()) | |||
.httpProxyPort(configStorage.getHttpProxyPort()) | |||
.httpProxyUsername(configStorage.getHttpProxyUsername()) | |||
.httpProxyPassword(configStorage.getHttpProxyPassword()); | |||
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) { | |||
this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort()); | |||
} | |||
this.httpClient = apacheHttpClientBuilder.build(); | |||
} | |||
@Override | |||
public RequestHttp getRequestHttp() { | |||
return this; | |||
} | |||
@Override | |||
public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||
if (!this.getWxMaConfig().isAccessTokenExpired() && !forceRefresh) { | |||
return this.getWxMaConfig().getAccessToken(); | |||
} | |||
Lock lock = this.getWxMaConfig().getAccessTokenLock(); | |||
lock.lock(); | |||
try { | |||
String url = String.format(this.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(), | |||
this.getWxMaConfig().getSecret()); | |||
try { | |||
HttpGet httpGet = new HttpGet(url); | |||
if (this.getRequestHttpProxy() != null) { | |||
RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build(); | |||
httpGet.setConfig(config); | |||
} | |||
try (CloseableHttpResponse response = getRequestHttpClient().execute(httpGet)) { | |||
String resultContent = new BasicResponseHandler().handleResponse(response); | |||
WxError error = WxError.fromJson(resultContent, WxType.MiniApp); | |||
if (error.getErrorCode() != 0) { | |||
throw new WxErrorException(error); | |||
} | |||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||
this.getWxMaConfig().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
return this.getWxMaConfig().getAccessToken(); | |||
} finally { | |||
httpGet.releaseConnection(); | |||
} | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} finally { | |||
lock.unlock(); | |||
} | |||
} | |||
@Override | |||
public WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException { | |||
final WxMaConfig config = getWxMaConfig(); | |||
Map<String, String> params = new HashMap<>(8); | |||
params.put("appid", config.getAppid()); | |||
params.put("secret", config.getSecret()); | |||
params.put("code", jsCode); | |||
String result = get(JSCODE_TO_SESSION_URL, Joiner.on("&").withKeyValueSeparator("=").join(params)); | |||
return WxMaJscode2SessionResult.fromJson(result); | |||
} | |||
@Override | |||
public boolean checkSignature(String timestamp, String nonce, String signature) { | |||
try { | |||
return SHA1.gen(this.getWxMaConfig().getToken(), timestamp, nonce).equals(signature); | |||
} catch (Exception e) { | |||
log.error("Checking signature failed, and the reason is :" + e.getMessage()); | |||
return false; | |||
} | |||
} | |||
@Override | |||
public String get(String url, String queryParam) throws WxErrorException { | |||
if (url.contains("access_token=")) { | |||
throw new IllegalArgumentException("uri参数中不允许有access_token: " + url); | |||
} | |||
String accessToken = getAccessToken(false); | |||
String uriWithAccessToken = url + (url.contains("?") ? "&" : "?") + "access_token=" + accessToken; | |||
return execute(SimpleGetRequestExecutor.create(this), uriWithAccessToken, queryParam); | |||
} | |||
@Override | |||
public String post(String url, String postData) throws WxErrorException { | |||
if (url.contains("access_token=")) { | |||
throw new IllegalArgumentException("uri参数中不允许有access_token: " + url); | |||
} | |||
String accessToken = getAccessToken(false); | |||
String uriWithAccessToken = url + (url.contains("?") ? "&" : "?") + "access_token=" + accessToken; | |||
return execute(SimplePostRequestExecutor.create(this), uriWithAccessToken, postData); | |||
} | |||
@Override | |||
public String post(String url, Object obj) throws WxErrorException { | |||
if (url.contains("access_token=")) { | |||
throw new IllegalArgumentException("uri参数中不允许有access_token: " + url); | |||
} | |||
try { | |||
String accessToken = getAccessToken(false); | |||
Class<?> aClass = obj.getClass(); | |||
if(!aClass.equals(String.class)){ | |||
if(aClass.equals(HashMap.class)){ | |||
((HashMap) obj).put("access_token",accessToken); | |||
}else{ | |||
Field accessToken1 = aClass.getDeclaredField("accessToken"); | |||
accessToken1.setAccessible(true); | |||
accessToken1.set(obj,accessToken); | |||
} | |||
} | |||
} catch (Exception e) { | |||
e.printStackTrace(); | |||
log.error("【请求参数】:{}添加accessToken失败" + obj.getClass()); | |||
} | |||
return this.execute(SimplePostRequestExecutor.create(this), url, TtMaGsonBuilder.create().toJson(obj)); | |||
} | |||
/** | |||
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | |||
*/ | |||
@Override | |||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
int retryTimes = 0; | |||
do { | |||
try { | |||
return this.executeInternal(executor, uri, data); | |||
} catch (WxErrorException e) { | |||
if (retryTimes + 1 > this.maxRetryTimes) { | |||
log.warn("重试达到最大次数【{}】", maxRetryTimes); | |||
//最后一次重试失败后,直接抛出异常,不再等待 | |||
throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
} | |||
WxError error = e.getError(); | |||
// -1 系统繁忙, 1000ms后重试 | |||
if (error.getErrorCode() == -1) { | |||
int sleepMillis = this.retrySleepMillis * (1 << retryTimes); | |||
try { | |||
log.warn("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); | |||
Thread.sleep(sleepMillis); | |||
} catch (InterruptedException e1) { | |||
Thread.currentThread().interrupt(); | |||
} | |||
} else { | |||
throw e; | |||
} | |||
} | |||
} while (retryTimes++ < this.maxRetryTimes); | |||
log.warn("重试达到最大次数【{}】", this.maxRetryTimes); | |||
throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
} | |||
private <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
E dataForLog = DataUtils.handleDataWithSecret(data); | |||
try { | |||
T result = executor.execute(uri, data, WxType.MiniApp); | |||
log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uri, data, result); | |||
return result; | |||
} catch (WxErrorException e) { | |||
WxError error = e.getError(); | |||
/* | |||
* 发生以下情况时尝试刷新access_token | |||
*/ | |||
if (error.getErrorCode() == ERR_40001 | |||
|| error.getErrorCode() == ERR_42001 | |||
|| error.getErrorCode() == ERR_40014) { | |||
// 强制设置WxMaConfig的access token过期了,这样在下一次请求里就会刷新access token | |||
this.getWxMaConfig().expireAccessToken(); | |||
if (this.getWxMaConfig().autoRefreshToken()) { | |||
return this.execute(executor, uri, data); | |||
} | |||
} | |||
if (error.getErrorCode() != 0) { | |||
log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uri, dataForLog, error); | |||
throw new WxErrorException(error, e); | |||
} | |||
return null; | |||
} catch (IOException e) { | |||
log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uri, dataForLog, e.getMessage()); | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
@Override | |||
public WxMaConfig getWxMaConfig() { | |||
return this.wxMaConfig; | |||
} | |||
@Override | |||
public void setWxMaConfig(WxMaConfig wxConfigProvider) { | |||
this.wxMaConfig = wxConfigProvider; | |||
this.initHttp(); | |||
} | |||
@Override | |||
public void setRetrySleepMillis(int retrySleepMillis) { | |||
this.retrySleepMillis = retrySleepMillis; | |||
} | |||
@Override | |||
public void setMaxRetryTimes(int maxRetryTimes) { | |||
this.maxRetryTimes = maxRetryTimes; | |||
} | |||
@Override | |||
public TtMaUserService getUserService() { | |||
return this.userService; | |||
} | |||
@Override | |||
public TtMaQrcodeService getQrcodeService() { | |||
return this.qrcodeService; | |||
} | |||
@Override | |||
public TtMaMsgService getMsgService() { | |||
return this.msgService; | |||
} | |||
} |
@@ -0,0 +1,41 @@ | |||
package com.iformall.service.toutiao.miniapp.impl; | |||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | |||
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; | |||
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils; | |||
import com.iformall.service.toutiao.miniapp.TtMaService; | |||
import com.iformall.service.toutiao.miniapp.TtMaUserService; | |||
import lombok.AllArgsConstructor; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
/** | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
@AllArgsConstructor | |||
public class TtMaUserServiceImpl implements TtMaUserService { | |||
private TtMaService service; | |||
@Override | |||
public WxMaJscode2SessionResult getSessionInfo(String jsCode) throws WxErrorException { | |||
return service.jsCode2SessionInfo(jsCode); | |||
} | |||
@Override | |||
public WxMaUserInfo getUserInfo(String sessionKey, String encryptedData, String ivStr) { | |||
return WxMaUserInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr)); | |||
} | |||
@Override | |||
public WxMaPhoneNumberInfo getPhoneNoInfo(String sessionKey, String encryptedData, String ivStr) { | |||
return WxMaPhoneNumberInfo.fromJson(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr)); | |||
} | |||
@Override | |||
public boolean checkUserInfo(String sessionKey, String rawData, String signature) { | |||
final String generatedSignature = DigestUtils.sha1Hex(rawData + sessionKey); | |||
return generatedSignature.equals(signature); | |||
} | |||
} |
@@ -0,0 +1,88 @@ | |||
package com.iformall.service.toutiao.utils; | |||
import java.nio.ByteBuffer; | |||
import java.nio.ByteOrder; | |||
import java.nio.charset.StandardCharsets; | |||
import java.util.Arrays; | |||
import javax.crypto.Cipher; | |||
import javax.crypto.spec.IvParameterSpec; | |||
import javax.crypto.spec.SecretKeySpec; | |||
import org.apache.commons.codec.binary.Base64; | |||
public class MsgDecrypt { | |||
private Cipher cipher; | |||
static int RANDOM_BYTES_POS = 32; | |||
public MsgDecrypt(String encodingAesKey) throws Exception { | |||
//AES key长度固定 | |||
if (encodingAesKey.length() != 43) { | |||
throw new Exception("AES key 长度不合法"); | |||
} | |||
byte[] aesKey = Base64.decodeBase64(encodingAesKey + "="); | |||
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); | |||
IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); | |||
Cipher encCipher = Cipher.getInstance("AES/CBC/NoPadding"); | |||
encCipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); | |||
cipher = Cipher.getInstance("AES/CBC/NoPadding"); | |||
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); | |||
} | |||
//将四个byte解析为一个32位的数字,数字的值也就是消息体的String格式下的长度 | |||
private int getLength(byte[] orderBytes) { | |||
ByteBuffer buf = ByteBuffer.wrap(orderBytes); | |||
buf.order(ByteOrder.BIG_ENDIAN); | |||
return buf.getInt(); | |||
} | |||
private byte[] decode(byte[] decrypted) { | |||
int pad = decrypted[decrypted.length - 1]; | |||
if (pad < 1 || pad > 32) { | |||
pad = 0; | |||
} | |||
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); | |||
} | |||
public String decrypt(String text) throws Exception { | |||
byte[] original; | |||
try { | |||
byte[] encrypted = Base64.decodeBase64(text); | |||
original = cipher.doFinal(encrypted); | |||
} catch (Exception e) { | |||
e.printStackTrace(); | |||
throw new Exception("解码异常"); | |||
} | |||
String Content; | |||
String AppId; | |||
try { | |||
byte[] bytes = this.decode(original); | |||
//byte数组的第32、33、34、35个元素代表了消息体的真实字符个数,也就是长度 | |||
byte[] pos = Arrays.copyOfRange(bytes, RANDOM_BYTES_POS, RANDOM_BYTES_POS + 4); | |||
int msgLength = getLength(pos); | |||
//根据解析出来的消息体长度值,截取真实的消息体 | |||
Content = new String(Arrays.copyOfRange(bytes, RANDOM_BYTES_POS + 4, RANDOM_BYTES_POS + 4 + msgLength), StandardCharsets.UTF_8); | |||
//byte数组截去真实消息后,末尾剩下的字符就是appid | |||
AppId = new String(Arrays.copyOfRange(bytes, RANDOM_BYTES_POS + 4 + msgLength, bytes.length), StandardCharsets.UTF_8); | |||
// System.out.println("Content: " + Content); | |||
// System.out.println("ThirdParty AppID: " + AppId); | |||
} catch (Exception e) { | |||
e.printStackTrace(); | |||
throw new Exception("Buffer异常"); | |||
} | |||
return Content; | |||
} | |||
public static void main(String[] args) throws Exception { | |||
MsgDecrypt test = new MsgDecrypt("XXX"); | |||
test.decrypt("XXX"); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
package com.iformall.service.toutiao.utils; | |||
import java.util.Arrays; | |||
import java.security.MessageDigest; | |||
public class ServerVerification { | |||
private String getMsgSignature(String tpToken, String timestamp, String nonce, String encrypt) throws Exception { | |||
String[] values = new String[] {tpToken, timestamp, nonce, encrypt}; | |||
Arrays.sort(values); | |||
StringBuilder sb = new StringBuilder(); | |||
for (String value : values) { | |||
sb.append(value); | |||
} | |||
String str = sb.toString(); | |||
try { | |||
//指定sha1算法 | |||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); | |||
messageDigest.update(str.getBytes()); | |||
//获取字节数组 | |||
byte[] messageDigestByte = messageDigest.digest(); | |||
StringBuilder hexStr = new StringBuilder(); | |||
// 字节数组转换为十六进制数 | |||
for (byte b : messageDigestByte) { | |||
String shaHex = Integer.toHexString(b & 0xFF); | |||
if (shaHex.length() < 2) { | |||
hexStr.append(0); | |||
} | |||
hexStr.append(shaHex); | |||
} | |||
return hexStr.toString(); | |||
} catch (Exception e) { | |||
e.printStackTrace(); | |||
throw new Exception("不能生成签名"); | |||
} | |||
} | |||
private void verify(String msgSignature, String newMsgSignature) { | |||
boolean res = msgSignature.equals(newMsgSignature); | |||
System.out.println(res); | |||
} | |||
public static void main(String[] args) throws Exception { | |||
ServerVerification test = new ServerVerification(); | |||
String msgSignature = "XXX"; | |||
String newMsgSignature = test.getMsgSignature("XXX", "XXX", "XXX", "XXX"); | |||
test.verify(msgSignature, newMsgSignature); | |||
} | |||
} |
@@ -0,0 +1,131 @@ | |||
package com.iformall.controller; | |||
import com.iformall.common.ResultData; | |||
import com.iformall.domain.po.WxAuthorizerInfo; | |||
import com.iformall.domain.vo.WxWeappInfo; | |||
import com.iformall.enums.EnumAppPlat; | |||
import com.iformall.enums.EnumWxAuthorizationStatus; | |||
import com.iformall.service.WxAuthorizerInfoService; | |||
import com.iformall.service.toutiao.FmTtOpenService; | |||
import com.iformall.service.toutiao.api.bean.TtOpenAuthorizationInfo; | |||
import io.swagger.annotations.Api; | |||
import io.swagger.annotations.ApiOperation; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Controller; | |||
import org.springframework.web.bind.annotation.GetMapping; | |||
import org.springframework.web.bind.annotation.RequestMapping; | |||
import org.springframework.web.bind.annotation.RequestParam; | |||
import org.springframework.web.bind.annotation.ResponseBody; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.IOException; | |||
import java.util.Date; | |||
import java.util.List; | |||
/** | |||
* Stormeye Wu | |||
*/ | |||
@Controller | |||
@RequestMapping("/wt_auth") | |||
@Api(description = "微信第三方开放平台授权相关接口") | |||
public class TtAuthController { | |||
private final Logger logger = LoggerFactory.getLogger(getClass()); | |||
@Autowired | |||
protected FmTtOpenService openService; | |||
@Autowired | |||
private WxAuthorizerInfoService authorizerInfoService; | |||
@ApiOperation(value = "获取授权跳转页", notes = "") | |||
@GetMapping("/goto_auth_url_show") | |||
@ResponseBody | |||
public String gotoPreAuthUrlShow() { | |||
return "<html>" + | |||
"<body>" + | |||
"<a href='/T/wt_auth/goto_auth_url'>go</a>" + | |||
"</body>" + | |||
"</html>"; | |||
} | |||
@ApiOperation(value = "授权跳转页", notes = "") | |||
@GetMapping("/goto_auth_url") | |||
public void gotoPreAuthUrl(HttpServletRequest request, HttpServletResponse response) { | |||
String host = request.getHeader("host"); | |||
String url = "https://" + host + "/T/wt_auth/jump"; | |||
try { | |||
url = openService.getTtOpenComponentService().getPreAuthUrl(url); | |||
response.sendRedirect(url); | |||
} catch (WxErrorException | IOException e) { | |||
logger.error("gotoPreAuthUrl", e); | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
@ApiOperation(value = "授权", notes = "") | |||
@GetMapping("/jump") | |||
@ResponseBody | |||
public void jump(@RequestParam("auth_code") String authorizationCode) { | |||
try { | |||
TtOpenAuthorizationInfo queryAuthResult = openService.getTtOpenComponentService().getQueryAuth(authorizationCode); | |||
logger.info("getQueryAuth", queryAuthResult); | |||
// WxOpenAuthorizerInfoResult openAuthorizerInfoResult = openService.getTtOpenComponentService().getAuthorizerInfo( | |||
// queryAuthResult.getAuthorizerAppid()); | |||
// logger.info(openAuthorizerInfoResult.toString()); | |||
// save auth info | |||
authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo(){ | |||
{ | |||
setAuthorizerAppid(queryAuthResult.getAuthorizerAppid()); | |||
setRefreshToken(queryAuthResult.getAuthorizerRefreshToken()); | |||
setAccessToken(queryAuthResult.getAuthorizerAccessToken()); | |||
setAccessTokenExpire(new Date(Long.valueOf(System.currentTimeMillis() + queryAuthResult.getExpiresIn()*1000))); | |||
setAuthorizationStatus(EnumWxAuthorizationStatus.AUTHORIZED.getCode()); | |||
setAuthTime(new Date()); | |||
} | |||
}); | |||
} catch (WxErrorException e) { | |||
logger.error("gotoPreAuthUrl", e); | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
@ApiOperation(value = "刷新", notes = "") | |||
@GetMapping("/retrieve") | |||
@ResponseBody | |||
public ResultData retrieve() { | |||
WxWeappInfo weappInfo = new WxWeappInfo(); | |||
weappInfo.setPlat(EnumAppPlat.TOUTIAO.getCode()); | |||
List<WxWeappInfo> list = authorizerInfoService.getList(weappInfo); | |||
if(list != null && list.size() > 0){ | |||
for (WxWeappInfo info: list) { | |||
try { | |||
String code = openService.getTtOpenComponentService().retrieveCode(info.getAuthorizerAppid()); | |||
TtOpenAuthorizationInfo queryAuthResult = openService.getTtOpenComponentService().getQueryAuth(code); | |||
logger.info("getQueryAuth", queryAuthResult); | |||
// save auth info | |||
authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo(){ | |||
{ | |||
setAuthorizerAppid(queryAuthResult.getAuthorizerAppid()); | |||
setRefreshToken(queryAuthResult.getAuthorizerRefreshToken()); | |||
setAccessToken(queryAuthResult.getAuthorizerAccessToken()); | |||
setAccessTokenExpire(new Date(Long.valueOf(System.currentTimeMillis() + queryAuthResult.getExpiresIn()*1000))); | |||
setAuthorizationStatus(EnumWxAuthorizationStatus.AUTHORIZED.getCode()); | |||
setAuthTime(new Date()); | |||
} | |||
}); | |||
} catch (WxErrorException e) { | |||
logger.error(info.getAuthorizerAppid()+"找回授权码", e); | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
} | |||
return new ResultData(); | |||
} | |||
} |
@@ -0,0 +1,388 @@ | |||
package com.iformall.controller; | |||
import com.google.gson.Gson; | |||
import com.google.gson.GsonBuilder; | |||
import com.iformall.common.Result; | |||
import com.iformall.common.ResultData; | |||
import com.iformall.controller.base.BaseController; | |||
import com.iformall.domain.po.WxAuthorizerInfo; | |||
import com.iformall.domain.po.WxComponentVerifyTicket; | |||
import com.iformall.enums.EnumWxAuthorizationStatus; | |||
import com.iformall.service.WxAuthorizerInfoService; | |||
import com.iformall.service.WxComponentVerifyTicketService; | |||
import com.iformall.service.toutiao.FmTtOpenService; | |||
import com.iformall.service.toutiao.api.bean.TtOpenTicket; | |||
import com.iformall.utils.DateUtils; | |||
import io.swagger.annotations.Api; | |||
import io.swagger.annotations.ApiOperation; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; | |||
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
import me.chanjar.weixin.open.bean.WxOpenCreateResult; | |||
import me.chanjar.weixin.open.bean.WxOpenGetResult; | |||
import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage; | |||
import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult; | |||
import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerListResult; | |||
import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Controller; | |||
import org.springframework.web.bind.annotation.*; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.PrintWriter; | |||
import java.nio.charset.Charset; | |||
import java.util.Date; | |||
import java.util.Map; | |||
@Controller | |||
@RequestMapping("/ttOpen") | |||
@Api(description = "头条第三方开放平台回调相关接口") | |||
public class TtCalllbackController extends BaseController { | |||
private final Logger logger = LoggerFactory.getLogger(this.getClass()); | |||
@Autowired | |||
protected FmTtOpenService openService; | |||
@Autowired | |||
private WxComponentVerifyTicketService componentVerifyTicketService; | |||
@Autowired | |||
private WxAuthorizerInfoService authorizerInfoService; | |||
@PostMapping(value = "/notify") | |||
@ApiOperation("接收ticket回调") | |||
@ResponseBody | |||
public String receiveTicket(HttpServletRequest request) throws IOException { | |||
logger.info("[" + getIpAddr() + "]接收微信回调 /ttOpen/notify"); | |||
String nonce = request.getParameter("Nonce"); | |||
String timestamp = request.getParameter("TimeStamp"); | |||
// String signature = request.getParameter("signature"); | |||
String encrypt = request.getParameter("Encrypt"); | |||
String msgSignature = request.getParameter("MsgSignature"); | |||
if (!openService.getTtOpenComponentService().checkSignature(timestamp, nonce, encrypt, msgSignature)) { | |||
throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); | |||
} | |||
// aes加密的消息, 解密 | |||
TtOpenTicket openTicket = openService.getTtOpenComponentService().decrypt(encrypt); | |||
// 处理 | |||
handleTicket(openTicket); | |||
return "success"; | |||
} | |||
/** | |||
* 处理ticket消息 | |||
* @param | |||
* @return | |||
*/ | |||
private void handleTicket(TtOpenTicket ticket) { | |||
try { | |||
String out = openService.getTtOpenComponentService().route(ticket); | |||
this.logger.info("组装回复信息:{}", out); | |||
} catch (WxErrorException e) { | |||
this.logger.error("notify", e); | |||
} | |||
if (StringUtils.equalsIgnoreCase(ticket.getMsgType(), "Ticket")) { | |||
// 保存component_verify_ticket | |||
componentVerifyTicketService.saveOrUpdate(new WxComponentVerifyTicket() { | |||
{ | |||
setComponentVerifyTicket(ticket.getTicket()); | |||
setCreateTime(DateUtils.stringToDate(ticket.getCreateTime(),"yyyy-MM-dd HH:mm:ss")); | |||
setComponentAppid(ticket.getComponentAppId()); | |||
setDeadline(new Date(Long.valueOf(System.currentTimeMillis() + (60 * 60 * 1000)))); | |||
} | |||
}); | |||
} | |||
} | |||
// /** | |||
// * 处理小程序审核消息 | |||
// * @param inMessage | |||
// * @return | |||
// */ | |||
// private boolean handleWeappAuditedMessage(WxOpenXmlMessage inMessage) { | |||
// // 审核通过 | |||
// try { | |||
// WxOpenQueryAuthResult queryAuthResult = openService.getWxOpenComponentService().getQueryAuth(inMessage.getAuthorizationCode()); | |||
// logger.info("getQueryAuth", queryAuthResult); | |||
// | |||
// WxOpenAuthorizerInfoResult openAuthorizerInfoResult = openService.getWxOpenComponentService().getAuthorizerInfo( | |||
// queryAuthResult.getAuthorizationInfo().getAuthorizerAppid()); | |||
// logger.info(openAuthorizerInfoResult.toString()); | |||
// // save auth info | |||
// authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo() { | |||
// { | |||
// setAuthorizerAppid(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerAppid()); | |||
// setAlias(openAuthorizerInfoResult.getAuthorizerInfo().getAlias()); | |||
// setHeadImg(openAuthorizerInfoResult.getAuthorizerInfo().getHeadImg()); | |||
// setQrcodeUrl(openAuthorizerInfoResult.getAuthorizerInfo().getQrcodeUrl()); | |||
// setRefreshToken(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerRefreshToken()); | |||
// setAccessToken(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerAccessToken()); | |||
// setAccessTokenExpire(new Date(Long.valueOf(System.currentTimeMillis() + openAuthorizerInfoResult.getAuthorizationInfo().getExpiresIn()*1000))); | |||
// setAuthorizationStatus(EnumWxAuthorizationStatus.AUTHORIZED.getCode()); | |||
// setAuthTime(new Date()); | |||
// } | |||
// }); | |||
// return true; | |||
// } catch (WxErrorException e) { | |||
// logger.error(e.getMessage()); | |||
// } | |||
// return false; | |||
// } | |||
@RequestMapping(value = "/{appId}/callback") | |||
@ApiOperation("消息回调") | |||
public void callback(@PathVariable("appId") String appId, HttpServletRequest request, HttpServletResponse response) throws IOException { | |||
logger.info("[" + getIpAddr() + "]接收微信APP回调 /{appId}/callback"); | |||
String timestamp = request.getParameter("timestamp"); | |||
String nonce = request.getParameter("nonce"); | |||
String signature = request.getParameter("signature"); | |||
String encType = request.getParameter("encrypt_type"); | |||
String openid = request.getParameter("openid"); | |||
String msgSignature = request.getParameter("msg_signature"); | |||
// 第三方平台一定接收的是加密的消息 | |||
if (!StringUtils.equalsIgnoreCase("aes", encType) | |||
|| !openService.getTtOpenComponentService().checkSignature(timestamp, nonce,encType, signature)) { | |||
throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); | |||
} | |||
// String resultxml = getRequestXML(request); | |||
// logger.info("接收微信APP回调:[appId=[{}], openid=[{}], signature=[{}], encType=[{}], msgSignature=[{}]," | |||
// + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", | |||
// appId, openid, signature, encType, msgSignature, timestamp, nonce, resultxml); | |||
// String out = ""; | |||
// // aes加密的消息, 解密 | |||
// WxMpXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedMpXml(resultxml, | |||
// openService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature); | |||
// logger.info("接收微信APP回调,消息解密后内容为:\n{}", inMessage.toString()); | |||
// // 实际 消息 处理 | |||
// //handleCallbackMessage(appId, response, inMessage); | |||
// WxMpXmlOutMessage outMessage = openService.getWxOpenMessageRouter().route(inMessage, appId); | |||
// if(outMessage == null) { | |||
// PrintWriter printWriter = response.getWriter(); | |||
// wechatWriteSuccess(printWriter); | |||
// } | |||
PrintWriter printWriter = response.getWriter(); | |||
wechatWriteSuccess(printWriter); | |||
} | |||
private void wechatWriteSuccess(PrintWriter printWriter) { | |||
printWriter.print("success"); | |||
printWriter.flush(); | |||
printWriter.close(); | |||
} | |||
// /** | |||
// * 测试账号特殊处理 | |||
// * @param appId | |||
// * @param response | |||
// * @param inMessage | |||
// * @throws IOException | |||
// */ | |||
// private void handleWechatTest(@PathVariable("appId") String appId, HttpServletResponse response, WxMpXmlMessage inMessage) throws IOException { | |||
// String out; | |||
// try { | |||
// if (StringUtils.equals(inMessage.getMsgType(), "text")) { | |||
// if (StringUtils.equals(inMessage.getContent(), "TESTCOMPONENT_MSG_TYPE_TEXT")) { | |||
// out = WxOpenXmlMessage.wxMpOutXmlMessageToEncryptedXml( | |||
// WxMpXmlOutMessage.TEXT().content("TESTCOMPONENT_MSG_TYPE_TEXT_callback") | |||
// .fromUser(inMessage.getToUser()) | |||
// .toUser(inMessage.getFromUser()) | |||
// .build(), | |||
// openService.getWxOpenConfigStorage() | |||
// ); | |||
// } else if (StringUtils.startsWith(inMessage.getContent(), "QUERY_AUTH_CODE:")) { | |||
// String msg = inMessage.getContent().replace("QUERY_AUTH_CODE:", "") + "_from_api"; | |||
// WxMpKefuMessage kefuMessage = WxMpKefuMessage.TEXT().content(msg).toUser(inMessage.getFromUser()).build(); | |||
// openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage); | |||
// } | |||
// } else if (StringUtils.equals(inMessage.getMsgType(), "event")) { | |||
// WxMpKefuMessage kefuMessage = WxMpKefuMessage.TEXT().content(inMessage.getEvent() + "from_callback").toUser(inMessage.getFromUser()).build(); | |||
// openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage); | |||
// } | |||
// } catch (WxErrorException e) { | |||
// logger.error("callback", e); | |||
// } | |||
// } | |||
// | |||
// private String getRequestXML(HttpServletRequest request) throws IOException { | |||
// InputStream inStream = request.getInputStream(); | |||
// ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); | |||
// byte[] buffer = new byte[1024]; | |||
// int len = 0; | |||
// while ((len = inStream.read(buffer)) != -1) { | |||
// outSteam.write(buffer, 0, len); | |||
// } | |||
// String resultxml = new String(outSteam.toByteArray(), Charset.forName("UTF-8")); | |||
// logger.info(resultxml); | |||
// | |||
// outSteam.close(); | |||
// inStream.close(); | |||
// return resultxml; | |||
// } | |||
// | |||
// private void getThreadCount( int i) { | |||
// ThreadGroup group = Thread.currentThread().getThreadGroup(); | |||
// ThreadGroup topGroup = group; | |||
// // 遍历线程组树,获取根线程组 | |||
// while (group != null) { | |||
// topGroup = group; | |||
// group = group.getParent(); | |||
// } | |||
// // 激活的线程数加倍 | |||
// int activeCount = topGroup.activeCount(); | |||
// // copy into a list that is the exact size | |||
// logger.info("Thread list size == " + i + " : " + activeCount); | |||
// | |||
// } | |||
// | |||
// | |||
// @GetMapping(value = "/createOpenPlatform") | |||
// @ApiOperation("创建微信开放平台") | |||
// public ResultData createOpenPlatform(@RequestParam(value = "appId") String appId) { | |||
// Gson gson = new GsonBuilder().create(); | |||
// try { | |||
// WxOpenCreateResult ret = openService.getWxOpenComponentService().createOpenAccount(appId); | |||
// logger.info(ret.toString()); | |||
// if (ret.isSuccess()) { | |||
// WxAuthorizerInfo authorizerInfo = authorizerInfoService.getByAppId(appId); | |||
// if (authorizerInfo != null) { | |||
// authorizerInfo.setId(authorizerInfo.getId()); | |||
// authorizerInfo.setOpenAppid(ret.getOpenAppid()); | |||
// authorizerInfoService.updateOpenAppid(authorizerInfo); | |||
// } | |||
// } | |||
// return new ResultData(ret.toString()); | |||
// } catch (WxErrorException e) { | |||
// logger.error(e.getMessage()); | |||
// return new ResultData(Result.ERROR, e.getMessage()); | |||
// } | |||
// } | |||
// | |||
// @GetMapping(value = "/bindOpenPlatform") | |||
// @ApiOperation("绑定微信开放平台") | |||
// public ResultData bindOpenPlatform(@RequestParam(value = "appId") String appId, @RequestParam(value = "openAppId") String openAppId) { | |||
// Gson gson = new GsonBuilder().create(); | |||
// try { | |||
// Boolean ret = openService.getWxOpenComponentService().bindOpenAccount(appId, openAppId); | |||
// if (ret) { | |||
// WxAuthorizerInfo authorizerInfo = authorizerInfoService.getByAppId(appId); | |||
// if (authorizerInfo != null) { | |||
// authorizerInfo.setId(authorizerInfo.getId()); | |||
// authorizerInfo.setOpenAppid(openAppId); | |||
// authorizerInfoService.updateOpenAppid(authorizerInfo); | |||
// } | |||
// return new ResultData(); | |||
// } | |||
// return new ResultData(Result.ERROR, ret.toString()); | |||
// } catch (WxErrorException e) { | |||
// logger.error(e.getMessage()); | |||
// return new ResultData(Result.ERROR, e.getMessage()); | |||
// } | |||
// } | |||
// | |||
// @GetMapping(value = "/unbindOpenPlatform") | |||
// @ApiOperation("解绑微信开放平台") | |||
// public ResultData unbindOpenPlatform(@RequestParam(value = "appId") String appId, @RequestParam(value = "openAppId") String openAppId) { | |||
// Gson gson = new GsonBuilder().create(); | |||
// try { | |||
// Boolean ret = openService.getWxOpenComponentService().unbindOpenAccount(appId, openAppId); | |||
// if (ret) { | |||
// WxAuthorizerInfo authorizerInfo = new WxAuthorizerInfo(); | |||
// authorizerInfo.setAuthorizerAppid(appId); | |||
// authorizerInfoService.removeOpenAppid(authorizerInfo); | |||
// return new ResultData(); | |||
// } | |||
// return new ResultData(Result.ERROR, ret.toString()); | |||
// } catch (WxErrorException e) { | |||
// logger.error(e.getMessage()); | |||
// return new ResultData(Result.ERROR, e.getMessage()); | |||
// } | |||
// } | |||
// | |||
// @GetMapping(value = "/getOpenPlatform") | |||
// @ApiOperation("获取微信开放平台appId") | |||
// public ResultData getOpenPlatform(@RequestParam(value = "appId") String appId) { | |||
// Gson gson = new GsonBuilder().create(); | |||
// try { | |||
// WxOpenGetResult ret = openService.getWxOpenComponentService().getOpenAccount(appId); | |||
// logger.info(ret.toString()); | |||
// if (ret.isSuccess()) { | |||
// WxAuthorizerInfo authorizerInfo = authorizerInfoService.getByAppId(appId); | |||
// if (authorizerInfo != null) { | |||
// if(StringUtils.isBlank(authorizerInfo.getOpenAppid()) && StringUtils.isNotBlank(ret.getOpenAppid())) { | |||
// authorizerInfo.setId(authorizerInfo.getId()); | |||
// authorizerInfo.setOpenAppid(ret.getOpenAppid()); | |||
// authorizerInfoService.updateOpenAppid(authorizerInfo); | |||
// } | |||
// } | |||
// return new ResultData(ret.getOpenAppid()); | |||
// } | |||
// return new ResultData(Result.ERROR, ret.toString()); | |||
// } catch (WxErrorException e) { | |||
// logger.error(e.getMessage()); | |||
// return new ResultData(Result.ERROR, e.getMessage()); | |||
// } | |||
// } | |||
// | |||
// @GetMapping(value = "/clearQuota") | |||
// @ApiOperation("代公众号清零quota,每个公众号每个月有10次清零机会,包括在微信公众平台上的清零以及调用API进行清零") | |||
// public ResultData clearQuota(@RequestParam(value = "appId") String appId) { | |||
// try { | |||
// openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).clearQuota(appId); | |||
// return new ResultData(); | |||
// } catch (WxErrorException e) { | |||
// logger.error(e.getMessage()); | |||
// return new ResultData(Result.ERROR, e.getMessage()); | |||
// } | |||
// } | |||
// | |||
// @GetMapping(value = "/getAuthorizerList") | |||
// @ApiOperation("拉取当前所有已授权的帐号基本信息") | |||
// public ResultData getAuthorizerList() { | |||
// try { | |||
// WxOpenAuthorizerListResult authorizerListResult = openService.getWxOpenComponentService().getAuthorizerList(0, 100); | |||
// logger.info("getAuthorizerList: " + authorizerListResult); | |||
// if(authorizerListResult.getTotalCount() > 0) { | |||
// for (Map<String, String> data : authorizerListResult.getList()) { | |||
// String appId = data.get("authorizer_appid"); | |||
// String refreshToken = data.get("refresh_token"); | |||
// Long authTime = Long.valueOf(data.get("auth_time")); | |||
// Date authDate = new Date(authTime*1000); | |||
// | |||
// if(StringUtils.isBlank(refreshToken)) { | |||
// continue; | |||
// } | |||
// | |||
// WxAuthorizerInfo authorizerInfo = new WxAuthorizerInfo(); | |||
// authorizerInfo.setAuthorizerAppid(appId); | |||
// authorizerInfo.setRefreshToken(refreshToken); | |||
// authorizerInfo.setAuthTime(authDate); | |||
// authorizerInfoService.updateAuthAppidInfo(authorizerInfo); | |||
// | |||
// openService.getWxOpenComponentService().getWxOpenConfigStorage().setAuthorizerRefreshToken(appId, refreshToken); | |||
// } | |||
// } | |||
// return new ResultData(authorizerListResult.toString()); | |||
// } catch (WxErrorException e) { | |||
// logger.error(e.getMessage()); | |||
// return new ResultData(Result.ERROR, e.getMessage()); | |||
// } | |||
// } | |||
} |
@@ -1,106 +0,0 @@ | |||
package com.iformall.controller; | |||
import com.iformall.domain.po.WxAuthorizerInfo; | |||
import com.iformall.enums.EnumWxAuthorizationStatus; | |||
import com.iformall.service.WxAuthorizerInfoService; | |||
import com.iformall.service.wechat.FmOpenService; | |||
import io.swagger.annotations.Api; | |||
import io.swagger.annotations.ApiOperation; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult; | |||
import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Controller; | |||
import org.springframework.web.bind.annotation.*; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.IOException; | |||
import java.util.Date; | |||
/** | |||
* Stormeye Wu | |||
*/ | |||
@Controller | |||
@RequestMapping("/wt_auth") | |||
@Api(description = "微信第三方开放平台授权相关接口") | |||
public class WechatAuthController { | |||
private final Logger logger = LoggerFactory.getLogger(getClass()); | |||
@Autowired | |||
private FmOpenService openService; | |||
@Autowired | |||
private WxAuthorizerInfoService authorizerInfoService; | |||
@ApiOperation(value = "获取授权跳转页", notes = "") | |||
@GetMapping("/goto_auth_url_show") | |||
@ResponseBody | |||
public String gotoPreAuthUrlShow() { | |||
return "<html>" + | |||
"<body>" + | |||
"<a href='/O/wt_auth/goto_auth_url'>go</a>" + | |||
"</body>" + | |||
"</html>"; | |||
} | |||
@ApiOperation(value = "授权跳转页", notes = "") | |||
@GetMapping("/goto_auth_url") | |||
public void gotoPreAuthUrl(HttpServletRequest request, HttpServletResponse response) { | |||
String host = request.getHeader("host"); | |||
String url = "https://" + host + "/O/wt_auth/jump"; | |||
try { | |||
url = openService.getWxOpenComponentService().getPreAuthUrl(url); | |||
response.sendRedirect(url); | |||
} catch (WxErrorException | IOException e) { | |||
logger.error("gotoPreAuthUrl", e); | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
@ApiOperation(value = "授权", notes = "") | |||
@GetMapping("/jump") | |||
@ResponseBody | |||
public void jump(@RequestParam("auth_code") String authorizationCode) { | |||
try { | |||
WxOpenQueryAuthResult queryAuthResult = openService.getWxOpenComponentService().getQueryAuth(authorizationCode); | |||
logger.info("getQueryAuth", queryAuthResult); | |||
WxOpenAuthorizerInfoResult openAuthorizerInfoResult = openService.getWxOpenComponentService().getAuthorizerInfo( | |||
queryAuthResult.getAuthorizationInfo().getAuthorizerAppid()); | |||
logger.info(openAuthorizerInfoResult.toString()); | |||
// save auth info | |||
authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo(){ | |||
{ | |||
setAuthorizerAppid(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerAppid()); | |||
setAlias(openAuthorizerInfoResult.getAuthorizerInfo().getAlias()); | |||
setHeadImg(openAuthorizerInfoResult.getAuthorizerInfo().getHeadImg()); | |||
setQrcodeUrl(openAuthorizerInfoResult.getAuthorizerInfo().getQrcodeUrl()); | |||
setRefreshToken(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerRefreshToken()); | |||
setAccessToken(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerAccessToken()); | |||
setAccessTokenExpire(new Date(Long.valueOf(System.currentTimeMillis() + openAuthorizerInfoResult.getAuthorizationInfo().getExpiresIn()*1000))); | |||
setAuthorizationStatus(EnumWxAuthorizationStatus.AUTHORIZED.getCode()); | |||
setAuthTime(new Date()); | |||
} | |||
}); | |||
} catch (WxErrorException e) { | |||
logger.error("gotoPreAuthUrl", e); | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
@ApiOperation(value = "手机跳转链接url", notes = "") | |||
@GetMapping("/getMobilePreAuthUrl") | |||
@ResponseBody | |||
public String getMobilePreAuthUrl(HttpServletRequest request, HttpServletResponse response) { | |||
String host = request.getHeader("host"); | |||
String url = "https://" + host + "/O/wt_auth/jump"; | |||
try { | |||
url = openService.getWxOpenComponentService().getMobilePreAuthUrl(url); | |||
return url; | |||
} catch (WxErrorException e) { | |||
logger.error("getMobilePreAuthUrl", e); | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
} |
@@ -1,415 +0,0 @@ | |||
package com.iformall.controller; | |||
import com.alibaba.fastjson.JSON; | |||
import com.alibaba.fastjson.JSONArray; | |||
import com.alibaba.fastjson.JSONObject; | |||
import com.google.gson.Gson; | |||
import com.google.gson.GsonBuilder; | |||
import com.iformall.common.Result; | |||
import com.iformall.common.ResultData; | |||
import com.iformall.controller.base.BaseController; | |||
import com.iformall.domain.po.*; | |||
import com.iformall.enums.*; | |||
import com.iformall.service.*; | |||
import com.iformall.service.wechat.FmOpenService; | |||
import io.swagger.annotations.Api; | |||
import io.swagger.annotations.ApiOperation; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; | |||
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
import me.chanjar.weixin.open.bean.WxOpenCreateResult; | |||
import me.chanjar.weixin.open.bean.WxOpenGetResult; | |||
import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage; | |||
import me.chanjar.weixin.open.bean.result.*; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Controller; | |||
import org.springframework.web.bind.annotation.*; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.PrintWriter; | |||
import java.nio.charset.Charset; | |||
import java.util.Date; | |||
import java.util.Map; | |||
@Controller | |||
@RequestMapping("/wxOpen") | |||
@Api(description = "微信第三方开放平台回调相关接口") | |||
public class WechatCalllbackController extends BaseController { | |||
private final Logger logger = LoggerFactory.getLogger(this.getClass()); | |||
@Autowired | |||
protected FmOpenService openService; | |||
@Autowired | |||
private WxComponentVerifyTicketService componentVerifyTicketService; | |||
@Autowired | |||
private WxAuthorizerInfoService authorizerInfoService; | |||
@PostMapping(value = "/notify") | |||
@ApiOperation("接收ticket回调") | |||
@ResponseBody | |||
public String receiveTicket(HttpServletRequest request) throws IOException { | |||
logger.info("[" + getIpAddr() + "]接收微信回调 /wxOpen/notify"); | |||
String timestamp = request.getParameter("timestamp"); | |||
String nonce = request.getParameter("nonce"); | |||
String signature = request.getParameter("signature"); | |||
String encType = request.getParameter("encrypt_type"); | |||
String msgSignature = request.getParameter("msg_signature"); | |||
if (!StringUtils.equalsIgnoreCase("aes", encType) | |||
|| !openService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) { | |||
throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); | |||
} | |||
String resultxml = getRequestXML(request); | |||
logger.info("\n接收微信回调:[signature=[{}], encType=[{}], msgSignature=[{}]," | |||
+ " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", | |||
signature, encType, msgSignature, timestamp, nonce, resultxml); | |||
// aes加密的消息, 解密 | |||
WxOpenXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedXml(resultxml, | |||
openService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature); | |||
this.logger.info("接收微信回调,消息解密后内容为:{} ", inMessage.toString()); | |||
// 处理 | |||
handleTicket(inMessage); | |||
return "success"; | |||
} | |||
/** | |||
* 处理ticket消息 | |||
* @param inMessage | |||
* @return | |||
*/ | |||
private void handleTicket(WxOpenXmlMessage inMessage) { | |||
try { | |||
String out = openService.getWxOpenComponentService().route(inMessage); | |||
this.logger.info("组装回复信息:{}", out); | |||
} catch (WxErrorException e) { | |||
this.logger.error("notify", e); | |||
} | |||
if (inMessage.getInfoType().equalsIgnoreCase(EnumWxAuthorizationInfoType.COMPONENT_VERIFY_TICKET.getMessage())) { | |||
// 保存component_verify_ticket | |||
componentVerifyTicketService.saveOrUpdate(new WxComponentVerifyTicket() { | |||
{ | |||
setComponentVerifyTicket(inMessage.getComponentVerifyTicket()); | |||
setCreateTime(new Date(Long.valueOf(inMessage.getCreateTime() * 1000))); | |||
setComponentAppid(inMessage.getAppId()); | |||
setDeadline(new Date(Long.valueOf(System.currentTimeMillis() + (60 * 60 * 1000)))); | |||
} | |||
}); | |||
} else if (inMessage.getInfoType().equalsIgnoreCase(EnumWxAuthorizationInfoType.AUTHORIZED.getMessage()) || | |||
inMessage.getInfoType().equalsIgnoreCase(EnumWxAuthorizationInfoType.UPDATEAUTHORIZED.getMessage())) { | |||
// 审核通过 | |||
handleWeappAuditedMessage(inMessage); | |||
} else if (inMessage.getInfoType().equals(EnumWxAuthorizationInfoType.UNAUTHORIZED.getMessage())) { | |||
// 授权取消 | |||
authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo() { | |||
{ | |||
setAuthorizerAppid(inMessage.getAuthorizerAppid()); | |||
setAuthorizationStatus(EnumWxAuthorizationStatus.UNAUTHORIZED.getCode()); | |||
setAuthTime(new Date()); | |||
} | |||
}); | |||
} | |||
} | |||
/** | |||
* 处理小程序审核消息 | |||
* @param inMessage | |||
* @return | |||
*/ | |||
private boolean handleWeappAuditedMessage(WxOpenXmlMessage inMessage) { | |||
// 审核通过 | |||
try { | |||
WxOpenQueryAuthResult queryAuthResult = openService.getWxOpenComponentService().getQueryAuth(inMessage.getAuthorizationCode()); | |||
logger.info("getQueryAuth", queryAuthResult); | |||
WxOpenAuthorizerInfoResult openAuthorizerInfoResult = openService.getWxOpenComponentService().getAuthorizerInfo( | |||
queryAuthResult.getAuthorizationInfo().getAuthorizerAppid()); | |||
logger.info(openAuthorizerInfoResult.toString()); | |||
// save auth info | |||
authorizerInfoService.saveOrUpdate(new WxAuthorizerInfo() { | |||
{ | |||
setAuthorizerAppid(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerAppid()); | |||
setAlias(openAuthorizerInfoResult.getAuthorizerInfo().getAlias()); | |||
setHeadImg(openAuthorizerInfoResult.getAuthorizerInfo().getHeadImg()); | |||
setQrcodeUrl(openAuthorizerInfoResult.getAuthorizerInfo().getQrcodeUrl()); | |||
setRefreshToken(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerRefreshToken()); | |||
setAccessToken(openAuthorizerInfoResult.getAuthorizationInfo().getAuthorizerAccessToken()); | |||
setAccessTokenExpire(new Date(Long.valueOf(System.currentTimeMillis() + openAuthorizerInfoResult.getAuthorizationInfo().getExpiresIn()*1000))); | |||
setAuthorizationStatus(EnumWxAuthorizationStatus.AUTHORIZED.getCode()); | |||
setAuthTime(new Date()); | |||
} | |||
}); | |||
return true; | |||
} catch (WxErrorException e) { | |||
logger.error(e.getMessage()); | |||
} | |||
return false; | |||
} | |||
@RequestMapping(value = "/{appId}/callback") | |||
@ApiOperation("消息回调") | |||
public void callback(@PathVariable("appId") String appId, HttpServletRequest request, HttpServletResponse response) throws IOException { | |||
logger.info("[" + getIpAddr() + "]接收微信APP回调 /{appId}/callback"); | |||
String timestamp = request.getParameter("timestamp"); | |||
String nonce = request.getParameter("nonce"); | |||
String signature = request.getParameter("signature"); | |||
String encType = request.getParameter("encrypt_type"); | |||
String openid = request.getParameter("openid"); | |||
String msgSignature = request.getParameter("msg_signature"); | |||
// 第三方平台一定接收的是加密的消息 | |||
if (!StringUtils.equalsIgnoreCase("aes", encType) | |||
|| !openService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) { | |||
throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); | |||
} | |||
String resultxml = getRequestXML(request); | |||
logger.info("接收微信APP回调:[appId=[{}], openid=[{}], signature=[{}], encType=[{}], msgSignature=[{}]," | |||
+ " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", | |||
appId, openid, signature, encType, msgSignature, timestamp, nonce, resultxml); | |||
String out = ""; | |||
// aes加密的消息, 解密 | |||
WxMpXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedMpXml(resultxml, | |||
openService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature); | |||
logger.info("接收微信APP回调,消息解密后内容为:\n{}", inMessage.toString()); | |||
// 全网发布测试用例 | |||
if (StringUtils.equalsAnyIgnoreCase(appId, "wx570bc396a51b8ff8", "wxd101a85aa106f53e")) { | |||
handleWechatTest(appId, response, inMessage); | |||
PrintWriter printWriter = response.getWriter(); | |||
wechatWriteSuccess(printWriter); | |||
return; | |||
} | |||
// 实际 消息 处理 | |||
//handleCallbackMessage(appId, response, inMessage); | |||
WxMpXmlOutMessage outMessage = openService.getWxOpenMessageRouter().route(inMessage, appId); | |||
if(outMessage == null) { | |||
PrintWriter printWriter = response.getWriter(); | |||
wechatWriteSuccess(printWriter); | |||
} | |||
PrintWriter printWriter = response.getWriter(); | |||
wechatWriteSuccess(printWriter); | |||
} | |||
private void wechatWriteSuccess(PrintWriter printWriter) { | |||
printWriter.print("success"); | |||
printWriter.flush(); | |||
printWriter.close(); | |||
} | |||
/** | |||
* 测试账号特殊处理 | |||
* @param appId | |||
* @param response | |||
* @param inMessage | |||
* @throws IOException | |||
*/ | |||
private void handleWechatTest(@PathVariable("appId") String appId, HttpServletResponse response, WxMpXmlMessage inMessage) throws IOException { | |||
String out; | |||
try { | |||
if (StringUtils.equals(inMessage.getMsgType(), "text")) { | |||
if (StringUtils.equals(inMessage.getContent(), "TESTCOMPONENT_MSG_TYPE_TEXT")) { | |||
out = WxOpenXmlMessage.wxMpOutXmlMessageToEncryptedXml( | |||
WxMpXmlOutMessage.TEXT().content("TESTCOMPONENT_MSG_TYPE_TEXT_callback") | |||
.fromUser(inMessage.getToUser()) | |||
.toUser(inMessage.getFromUser()) | |||
.build(), | |||
openService.getWxOpenConfigStorage() | |||
); | |||
} else if (StringUtils.startsWith(inMessage.getContent(), "QUERY_AUTH_CODE:")) { | |||
String msg = inMessage.getContent().replace("QUERY_AUTH_CODE:", "") + "_from_api"; | |||
WxMpKefuMessage kefuMessage = WxMpKefuMessage.TEXT().content(msg).toUser(inMessage.getFromUser()).build(); | |||
openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage); | |||
} | |||
} else if (StringUtils.equals(inMessage.getMsgType(), "event")) { | |||
WxMpKefuMessage kefuMessage = WxMpKefuMessage.TEXT().content(inMessage.getEvent() + "from_callback").toUser(inMessage.getFromUser()).build(); | |||
openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage); | |||
} | |||
} catch (WxErrorException e) { | |||
logger.error("callback", e); | |||
} | |||
} | |||
private String getRequestXML(HttpServletRequest request) throws IOException { | |||
InputStream inStream = request.getInputStream(); | |||
ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); | |||
byte[] buffer = new byte[1024]; | |||
int len = 0; | |||
while ((len = inStream.read(buffer)) != -1) { | |||
outSteam.write(buffer, 0, len); | |||
} | |||
String resultxml = new String(outSteam.toByteArray(), Charset.forName("UTF-8")); | |||
logger.info(resultxml); | |||
outSteam.close(); | |||
inStream.close(); | |||
return resultxml; | |||
} | |||
private void getThreadCount( int i) { | |||
ThreadGroup group = Thread.currentThread().getThreadGroup(); | |||
ThreadGroup topGroup = group; | |||
// 遍历线程组树,获取根线程组 | |||
while (group != null) { | |||
topGroup = group; | |||
group = group.getParent(); | |||
} | |||
// 激活的线程数加倍 | |||
int activeCount = topGroup.activeCount(); | |||
// copy into a list that is the exact size | |||
logger.info("Thread list size == " + i + " : " + activeCount); | |||
} | |||
@GetMapping(value = "/createOpenPlatform") | |||
@ApiOperation("创建微信开放平台") | |||
public ResultData createOpenPlatform(@RequestParam(value = "appId") String appId) { | |||
Gson gson = new GsonBuilder().create(); | |||
try { | |||
WxOpenCreateResult ret = openService.getWxOpenComponentService().createOpenAccount(appId); | |||
logger.info(ret.toString()); | |||
if (ret.isSuccess()) { | |||
WxAuthorizerInfo authorizerInfo = authorizerInfoService.getByAppId(appId); | |||
if (authorizerInfo != null) { | |||
authorizerInfo.setId(authorizerInfo.getId()); | |||
authorizerInfo.setOpenAppid(ret.getOpenAppid()); | |||
authorizerInfoService.updateOpenAppid(authorizerInfo); | |||
} | |||
} | |||
return new ResultData(ret.toString()); | |||
} catch (WxErrorException e) { | |||
logger.error(e.getMessage()); | |||
return new ResultData(Result.ERROR, e.getMessage()); | |||
} | |||
} | |||
@GetMapping(value = "/bindOpenPlatform") | |||
@ApiOperation("绑定微信开放平台") | |||
public ResultData bindOpenPlatform(@RequestParam(value = "appId") String appId, @RequestParam(value = "openAppId") String openAppId) { | |||
Gson gson = new GsonBuilder().create(); | |||
try { | |||
Boolean ret = openService.getWxOpenComponentService().bindOpenAccount(appId, openAppId); | |||
if (ret) { | |||
WxAuthorizerInfo authorizerInfo = authorizerInfoService.getByAppId(appId); | |||
if (authorizerInfo != null) { | |||
authorizerInfo.setId(authorizerInfo.getId()); | |||
authorizerInfo.setOpenAppid(openAppId); | |||
authorizerInfoService.updateOpenAppid(authorizerInfo); | |||
} | |||
return new ResultData(); | |||
} | |||
return new ResultData(Result.ERROR, ret.toString()); | |||
} catch (WxErrorException e) { | |||
logger.error(e.getMessage()); | |||
return new ResultData(Result.ERROR, e.getMessage()); | |||
} | |||
} | |||
@GetMapping(value = "/unbindOpenPlatform") | |||
@ApiOperation("解绑微信开放平台") | |||
public ResultData unbindOpenPlatform(@RequestParam(value = "appId") String appId, @RequestParam(value = "openAppId") String openAppId) { | |||
Gson gson = new GsonBuilder().create(); | |||
try { | |||
Boolean ret = openService.getWxOpenComponentService().unbindOpenAccount(appId, openAppId); | |||
if (ret) { | |||
WxAuthorizerInfo authorizerInfo = new WxAuthorizerInfo(); | |||
authorizerInfo.setAuthorizerAppid(appId); | |||
authorizerInfoService.removeOpenAppid(authorizerInfo); | |||
return new ResultData(); | |||
} | |||
return new ResultData(Result.ERROR, ret.toString()); | |||
} catch (WxErrorException e) { | |||
logger.error(e.getMessage()); | |||
return new ResultData(Result.ERROR, e.getMessage()); | |||
} | |||
} | |||
@GetMapping(value = "/getOpenPlatform") | |||
@ApiOperation("获取微信开放平台appId") | |||
public ResultData getOpenPlatform(@RequestParam(value = "appId") String appId) { | |||
Gson gson = new GsonBuilder().create(); | |||
try { | |||
WxOpenGetResult ret = openService.getWxOpenComponentService().getOpenAccount(appId); | |||
logger.info(ret.toString()); | |||
if (ret.isSuccess()) { | |||
WxAuthorizerInfo authorizerInfo = authorizerInfoService.getByAppId(appId); | |||
if (authorizerInfo != null) { | |||
if(StringUtils.isBlank(authorizerInfo.getOpenAppid()) && StringUtils.isNotBlank(ret.getOpenAppid())) { | |||
authorizerInfo.setId(authorizerInfo.getId()); | |||
authorizerInfo.setOpenAppid(ret.getOpenAppid()); | |||
authorizerInfoService.updateOpenAppid(authorizerInfo); | |||
} | |||
} | |||
return new ResultData(ret.getOpenAppid()); | |||
} | |||
return new ResultData(Result.ERROR, ret.toString()); | |||
} catch (WxErrorException e) { | |||
logger.error(e.getMessage()); | |||
return new ResultData(Result.ERROR, e.getMessage()); | |||
} | |||
} | |||
@GetMapping(value = "/clearQuota") | |||
@ApiOperation("代公众号清零quota,每个公众号每个月有10次清零机会,包括在微信公众平台上的清零以及调用API进行清零") | |||
public ResultData clearQuota(@RequestParam(value = "appId") String appId) { | |||
try { | |||
openService.getWxOpenComponentService().getWxMpServiceByAppid(appId).clearQuota(appId); | |||
return new ResultData(); | |||
} catch (WxErrorException e) { | |||
logger.error(e.getMessage()); | |||
return new ResultData(Result.ERROR, e.getMessage()); | |||
} | |||
} | |||
@GetMapping(value = "/getAuthorizerList") | |||
@ApiOperation("拉取当前所有已授权的帐号基本信息") | |||
public ResultData getAuthorizerList() { | |||
try { | |||
WxOpenAuthorizerListResult authorizerListResult = openService.getWxOpenComponentService().getAuthorizerList(0, 100); | |||
logger.info("getAuthorizerList: " + authorizerListResult); | |||
if(authorizerListResult.getTotalCount() > 0) { | |||
for (Map<String, String> data : authorizerListResult.getList()) { | |||
String appId = data.get("authorizer_appid"); | |||
String refreshToken = data.get("refresh_token"); | |||
Long authTime = Long.valueOf(data.get("auth_time")); | |||
Date authDate = new Date(authTime*1000); | |||
if(StringUtils.isBlank(refreshToken)) { | |||
continue; | |||
} | |||
WxAuthorizerInfo authorizerInfo = new WxAuthorizerInfo(); | |||
authorizerInfo.setAuthorizerAppid(appId); | |||
authorizerInfo.setRefreshToken(refreshToken); | |||
authorizerInfo.setAuthTime(authDate); | |||
authorizerInfoService.updateAuthAppidInfo(authorizerInfo); | |||
openService.getWxOpenComponentService().getWxOpenConfigStorage().setAuthorizerRefreshToken(appId, refreshToken); | |||
} | |||
} | |||
return new ResultData(authorizerListResult.toString()); | |||
} catch (WxErrorException e) { | |||
logger.error(e.getMessage()); | |||
return new ResultData(Result.ERROR, e.getMessage()); | |||
} | |||
} | |||
} |
@@ -98,6 +98,7 @@ public class WxWeappInfoController extends BaseController { | |||
weappInfo.setName(null); | |||
} | |||
} | |||
weappInfo.setPlat(EnumAppPlat.TOUTIAO.getCode()); | |||
weappInfo.setSortColumns(BaseEntity.SortField.TenantId_ASC,BaseEntity.SortField.Type_ASC,BaseEntity.SortField.Name_ASC); | |||
final PageInfo<WxWeappInfo> page = authorizerInfoService.listVoAsPage(weappInfo, pageNum, pageSize); | |||
return new ResultData(page); | |||
@@ -82,15 +82,11 @@ aws: | |||
# min-idle: 10 | |||
wechat: | |||
web: | |||
appId: "wx091907dd0bfd3f6b" | |||
secret: "2a2ca10738998b9ef92c1fe8a4d366a6" | |||
url: "https://admintest.malls.iformall.com" | |||
open: | |||
componentAppId: wxdfc8fb4e62d6b52b | |||
componentSecret: 98daa62b316dd6feabaad708327ce233 | |||
componentToken: formall2018 | |||
componentAesKey: htKq8EjBMPNndfZQK9JiFojFaqFwFpw42VfeWFtx7HN | |||
componentAppId: tt146862d58b42b88c | |||
componentSecret: d681a8873b81276dc93fd499bbf0bcbafb82b31c | |||
componentToken: iformallDouyinAuthorizeToken | |||
componentAesKey: iformallDouyinEntryKey202204082101028888888 | |||
redis: | |||
host: 101.200.130.134 | |||
port: 6379 | |||
@@ -77,10 +77,10 @@ aws: | |||
wechat: | |||
open: | |||
componentAppId: ENC(b3JG0MUZgQfz5Zj+DC2ZM1zDeLOiGTmmeokfe2O8kaM=) | |||
componentSecret: ENC(QVyc4BPGdnSjXGs2ivgpDBE1v8wPWHLLRMYI7Vv8uYwC0SiZHQz0QpyyQV/b48Pb) | |||
componentToken: ENC(rkxj0733WxFFDLgA9x01m2s5Fi2L+0PC) | |||
componentAesKey: ENC(EIbJUBpbYOrLb4YQ/HXLQmxlxgAqIp2ZmpnGICC8pu5xiTz3Cqfkbwd2S8raCcK/IvYcX2GmedI=) | |||
componentAppId: tt146862d58b42b88c | |||
componentSecret: d681a8873b81276dc93fd499bbf0bcbafb82b31c | |||
componentToken: iformallDouyinAuthorizeToken | |||
componentAesKey: iformallDouyinEntryKey202204082101028888888 | |||
redis: | |||
host: r-2zeaglwf13qqmnllj5.redis.rds.aliyuncs.com | |||
port: 6379 | |||