@@ -27,6 +27,12 @@ | |||||
<version>6.4.0</version> | <version>6.4.0</version> | ||||
</dependency> | </dependency> | ||||
--> | --> | ||||
<dependency> | |||||
<groupId>org.jetbrains</groupId> | |||||
<artifactId>annotations</artifactId> | |||||
<version>13.0</version> | |||||
<scope>compile</scope> | |||||
</dependency> | |||||
</dependencies> | </dependencies> | ||||
</project> | </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.setName(null); | ||||
} | } | ||||
} | } | ||||
weappInfo.setPlat(EnumAppPlat.TOUTIAO.getCode()); | |||||
weappInfo.setSortColumns(BaseEntity.SortField.TenantId_ASC,BaseEntity.SortField.Type_ASC,BaseEntity.SortField.Name_ASC); | weappInfo.setSortColumns(BaseEntity.SortField.TenantId_ASC,BaseEntity.SortField.Type_ASC,BaseEntity.SortField.Name_ASC); | ||||
final PageInfo<WxWeappInfo> page = authorizerInfoService.listVoAsPage(weappInfo, pageNum, pageSize); | final PageInfo<WxWeappInfo> page = authorizerInfoService.listVoAsPage(weappInfo, pageNum, pageSize); | ||||
return new ResultData(page); | return new ResultData(page); | ||||
@@ -82,15 +82,11 @@ aws: | |||||
# min-idle: 10 | # min-idle: 10 | ||||
wechat: | wechat: | ||||
web: | |||||
appId: "wx091907dd0bfd3f6b" | |||||
secret: "2a2ca10738998b9ef92c1fe8a4d366a6" | |||||
url: "https://admintest.malls.iformall.com" | |||||
open: | open: | ||||
componentAppId: wxdfc8fb4e62d6b52b | |||||
componentSecret: 98daa62b316dd6feabaad708327ce233 | |||||
componentToken: formall2018 | |||||
componentAesKey: htKq8EjBMPNndfZQK9JiFojFaqFwFpw42VfeWFtx7HN | |||||
componentAppId: tt146862d58b42b88c | |||||
componentSecret: d681a8873b81276dc93fd499bbf0bcbafb82b31c | |||||
componentToken: iformallDouyinAuthorizeToken | |||||
componentAesKey: iformallDouyinEntryKey202204082101028888888 | |||||
redis: | redis: | ||||
host: 101.200.130.134 | host: 101.200.130.134 | ||||
port: 6379 | port: 6379 | ||||
@@ -77,10 +77,10 @@ aws: | |||||
wechat: | wechat: | ||||
open: | 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: | redis: | ||||
host: r-2zeaglwf13qqmnllj5.redis.rds.aliyuncs.com | host: r-2zeaglwf13qqmnllj5.redis.rds.aliyuncs.com | ||||
port: 6379 | port: 6379 | ||||