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