diff --git a/pom.xml b/pom.xml index 7eb78c33..38d24fd7 100644 --- a/pom.xml +++ b/pom.xml @@ -1,381 +1,387 @@ - - - 4.0.0 - com.github.binarywang - weixin-java-parent - 2.8.6.BETA - pom - WeiXin Java Tools - Parent - 微信公众号、企业号上级POM - https://github.com/wechat-group/weixin-java-tools - - - - The Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - - - - - Daniel Qian - chanjarster@gmail.com - https://github.com/chanjarster - - - Binary Wang - binarywang@gmail.com - https://github.com/binarywang - - - gaigeshen - gaigeshen@qq.com - https://github.com/gaigeshen - - - Liu Mingbo - liumingbo2008@gmail.com - https://github.com/FirenzesEagle - - - kakotor - kakotor@gmail.com - https://github.com/kakotor - - - xiong - zhaoxiong.tan@gmail.com - https://github.com/ZhaoxiongTan - - - LiuJunGuang - aimilin@yeah.net - https://github.com/aimilin6688 - - - Eric.Tsai - xiaodong.cai.ks@gmail.com - https://github.com/iwareserictsai - - - withinthefog - withinthefog@gmail.com - https://github.com/withinthefog - - - Keung - dongfuqiang1988@163.com - https://github.com/johnnytung - - - Jonk - aimilin@yeah.net - https://github.com/aimilin6688 - - - ecoolper - crskyp@gmail.com - https://github.com/crskyp - - - - - scm:git:https://github.com/wechat-group/weixin-java-tools.git - scm:git:git@github.com:wechat-group/weixin-java-tools.git - https://github.com/wechat-group/weixin-java-tools - - - - weixin-java-common - weixin-java-cp - weixin-java-mp - weixin-java-pay - weixin-java-miniapp - - - - - 1.7 - 1.7 - - UTF-8 - true - true - 4.5 - 9.3.0.RC0 - - - - - - com.github.binarywang - qrcode-utils - 1.1 - - - - org.jodd - jodd-http - 3.7.1 - provided - - - com.squareup.okhttp3 - okhttp - 3.7.0 - provided - - - - org.apache.httpcomponents - httpclient - ${httpclient.version} - - - org.apache.httpcomponents - httpmime - ${httpclient.version} - - - commons-codec - commons-codec - 1.10 - - - commons-io - commons-io - 2.5 - - - org.apache.commons - commons-lang3 - 3.5 - - - org.slf4j - slf4j-api - 1.7.24 - - - com.thoughtworks.xstream - xstream - 1.4.9 - - - - com.google.guava - guava - 20.0 - - - com.google.code.gson - gson - 2.8.0 - - - - - joda-time - joda-time - 2.9.7 - test - - - ch.qos.logback - logback-classic - 1.1.11 - test - - - com.google.inject - guice - 3.0 - test - - - org.testng - testng - 6.10 - test - - - org.mockito - mockito-all - 1.9.5 - test - - - org.eclipse.jetty - jetty-server - ${jetty.version} - test - - - org.eclipse.jetty - jetty-servlet - ${jetty.version} - test - - - redis.clients - jedis - 2.9.0 - provided - - - org.projectlombok - lombok - 1.16.18 - compile - - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - doclint-java8-disable - - [1.8,) - - - -Xdoclint:none - - - - - release - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - - jar - - - - - ${javadoc.opts} - UTF-8 - zh_CN - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.17 - - true - - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 - true - - ossrh - https://oss.sonatype.org/ - true - - - - org.apache.maven.plugins - maven-release-plugin - 2.5.1 - - true - false - release - deploy - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.6.0 - - UTF-8 - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.17 - - quality-checks/google_checks.xml - true - true - true - - - - verify - - check - - - - - - - - + + + 4.0.0 + com.github.binarywang + weixin-java-parent + 2.8.6.BETA + pom + WeiXin Java Tools - Parent + 微信公众号、企业号上级POM + https://github.com/wechat-group/weixin-java-tools + + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + Daniel Qian + chanjarster@gmail.com + https://github.com/chanjarster + + + Binary Wang + binarywang@gmail.com + https://github.com/binarywang + + + gaigeshen + gaigeshen@qq.com + https://github.com/gaigeshen + + + Liu Mingbo + liumingbo2008@gmail.com + https://github.com/FirenzesEagle + + + kakotor + kakotor@gmail.com + https://github.com/kakotor + + + xiong + zhaoxiong.tan@gmail.com + https://github.com/ZhaoxiongTan + + + LiuJunGuang + aimilin@yeah.net + https://github.com/aimilin6688 + + + Eric.Tsai + xiaodong.cai.ks@gmail.com + https://github.com/iwareserictsai + + + withinthefog + withinthefog@gmail.com + https://github.com/withinthefog + + + Keung + dongfuqiang1988@163.com + https://github.com/johnnytung + + + Jonk + aimilin@yeah.net + https://github.com/aimilin6688 + + + ecoolper + crskyp@gmail.com + https://github.com/crskyp + + + 007 + 007gzs@gmail.com + https://github.com/007gzs + + + + + scm:git:https://github.com/wechat-group/weixin-java-tools.git + scm:git:git@github.com:wechat-group/weixin-java-tools.git + https://github.com/wechat-group/weixin-java-tools + + + + weixin-java-common + weixin-java-cp + weixin-java-mp + weixin-java-pay + weixin-java-miniapp + weixin-java-open + + + + + 1.7 + 1.7 + + UTF-8 + true + true + 4.5 + 9.3.0.RC0 + + + + + + com.github.binarywang + qrcode-utils + 1.1 + + + + org.jodd + jodd-http + 3.7.1 + provided + + + com.squareup.okhttp3 + okhttp + 3.7.0 + provided + + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + org.apache.httpcomponents + httpmime + ${httpclient.version} + + + commons-codec + commons-codec + 1.10 + + + commons-io + commons-io + 2.5 + + + org.apache.commons + commons-lang3 + 3.5 + + + org.slf4j + slf4j-api + 1.7.24 + + + com.thoughtworks.xstream + xstream + 1.4.9 + + + + com.google.guava + guava + 20.0 + + + com.google.code.gson + gson + 2.8.0 + + + + + joda-time + joda-time + 2.9.7 + test + + + ch.qos.logback + logback-classic + 1.1.11 + test + + + com.google.inject + guice + 3.0 + test + + + org.testng + testng + 6.10 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + org.eclipse.jetty + jetty-server + ${jetty.version} + test + + + org.eclipse.jetty + jetty-servlet + ${jetty.version} + test + + + redis.clients + jedis + 2.9.0 + provided + + + org.projectlombok + lombok + 1.16.18 + compile + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + doclint-java8-disable + + [1.8,) + + + -Xdoclint:none + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + + jar + + + + + ${javadoc.opts} + UTF-8 + zh_CN + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.17 + + true + + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.3 + true + + ossrh + https://oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.1 + + true + false + release + deploy + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.0 + + UTF-8 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + quality-checks/google_checks.xml + true + true + true + + + + verify + + check + + + + + + + + diff --git a/weixin-java-open/README.md b/weixin-java-open/README.md new file mode 100644 index 00000000..d99b7706 --- /dev/null +++ b/weixin-java-open/README.md @@ -0,0 +1,62 @@ +消息机制未实现,下面为通知回调中设置的代码部分 +``` +@RestController +@RequestMapping("notify") +public class NotifyController extends WechatThridBaseController { + @Autowired + protected WxOpenServiceDemo wxOpenService; + @RequestMapping("receive_ticket") + public Object receiveTicket(@RequestBody(required = false) String requestBody, @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, @RequestParam("signature") String signature, + @RequestParam(name = "encrypt_type", required = false) String encType, + @RequestParam(name = "msg_signature", required = false) String msgSignature) { + this.logger.info( + "\n接收微信请求:[signature=[{}], encType=[{}], msgSignature=[{}]," + + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", + signature, encType, msgSignature, timestamp, nonce, requestBody); + + if (!StringUtils.equalsIgnoreCase("aes", encType) || !wxOpenService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) { + throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); + } + + // aes加密的消息 + WxOpenXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedXml(requestBody, wxOpenService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature); + this.logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); + String out = null; + try { + out = wxOpenService.getWxOpenComponentService().route(inMessage); + } catch (WxErrorException e) { + throw new ResponseException(ErrorCodeEnum.ERROR, e); + } + + this.logger.debug("\n组装回复信息:{}", out); + + return out; + } + @RequestMapping("{appId}/callback") + public Object callback(@RequestBody(required = false)String requestBody, + @PathVariable ("appId") String appId, + @RequestParam("signature") String signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestParam("openid") String openid, + @RequestParam("encrypt_type") String encType, + @RequestParam("msg_signature") String msgSignature) { + this.logger.info( + "\n接收微信请求:[appId=[{}], openid=[{}], signature=[{}], encType=[{}], msgSignature=[{}]," + + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", + appId, openid, signature, encType, msgSignature, timestamp, nonce, requestBody); + logger.info("query:"+getHttpServletRequest().getQueryString()+"\nbody:"+requestBody); + if (!StringUtils.equalsIgnoreCase("aes", encType) || !wxOpenService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) { + throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); + } + + String out = ""; + // aes加密的消息 + WxMpXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedMpXml(requestBody, wxOpenService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature); + this.logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); + //wxOpenService.getWxOpenComponentService().getWxMpServiceByAppid(appId); + return out; + } +} +``` diff --git a/weixin-java-open/pom.xml b/weixin-java-open/pom.xml new file mode 100644 index 00000000..d451dce1 --- /dev/null +++ b/weixin-java-open/pom.xml @@ -0,0 +1,97 @@ + + + + 4.0.0 + + com.github.binarywang + weixin-java-parent + 2.8.6.BETA + + weixin-java-open + WeiXin Java Tools - Open + 微信开放平台Java SDK + + + 007 + 007gzs@gmail.com + https://github.com/007gzs + + + + + com.github.binarywang + weixin-java-common + ${project.version} + + + com.github.binarywang + weixin-java-mp + ${project.version} + + + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + provided + + + + org.testng + testng + test + + + com.google.inject + guice + test + + + org.eclipse.jetty + jetty-server + test + + + org.eclipse.jetty + jetty-servlet + test + + + joda-time + joda-time + test + + + redis.clients + jedis + + + ch.qos.logback + logback-classic + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/resources/testng.xml + + + + + + + + diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java new file mode 100644 index 00000000..ded17248 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java @@ -0,0 +1,82 @@ +package me.chanjar.weixin.open.api; + +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; +import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage; +import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult; +import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerOptionResult; +import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult; + +/** + * @author 007 + */ +public interface WxOpenComponentService { + + String API_COMPONENT_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/component/api_component_token"; + String API_CREATE_PREAUTHCODE_URL = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode"; + String API_QUERY_AUTH_URL = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth"; + String API_AUTHORIZER_TOKEN_URL = "https://api.weixin.qq.com /cgi-bin/component/api_authorizer_token"; + String API_GET_AUTHORIZER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info"; + 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 COMPONENT_LOGIN_PAGE_URL = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=%s&pre_auth_code=%s&redirect_uri=%s"; + 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/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/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s&component_appid==%s"; + WxMpService getWxMpServiceByAppid(String appid); + + WxOpenConfigStorage getWxOpenConfigStorage(); + + boolean checkSignature(String timestamp, String nonce, String signature); + + String getComponentAccessToken(boolean forceRefresh) throws WxErrorException; + + /** + * 获取用户授权页URL(来路URL和成功跳转URL 的域名都需要为三方平台设置的 登录授权的发起页域名) + */ + String getPreAuthUrl(String redirectURI) throws WxErrorException; + + String route(WxOpenXmlMessage wxMessage) throws WxErrorException; + + /** + * 使用授权码换取公众号或小程序的接口调用凭据和授权信息 + */ + WxOpenQueryAuthResult getQueryAuth(String authorizationCode) throws WxErrorException; + + /** + * 获取授权方的帐号基本信息 + */ + WxOpenAuthorizerInfoResult getAuthorizerInfo(String authorizerAppid) throws WxErrorException; + + /** + * 获取授权方的选项设置信息 + */ + WxOpenAuthorizerOptionResult getAuthorizerOption(String authorizerAppid, String optionName) throws WxErrorException; + + /** + * 设置授权方的选项信息 + */ + WxError 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; + + boolean checkSignature(String appId, String timestamp, String nonce, String signature); + + WxMpOAuth2AccessToken oauth2refreshAccessToken(String appid, String refreshToken) throws WxErrorException; + + String oauth2buildAuthorizationUrl(String appid, String redirectURI, String scope, String state); + +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java new file mode 100644 index 00000000..8f6173b7 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenConfigStorage.java @@ -0,0 +1,106 @@ +package me.chanjar.weixin.open.api; + +import me.chanjar.weixin.mp.api.WxMpConfigStorage; +import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; +import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; + +/** + * @author 007 + */ +public interface WxOpenConfigStorage { + + void setComponentAppId(String componentAppId); + + void setComponentAppSecret(String componentAppSecret); + + void setComponentToken(String componentToken); + + void setComponentAesKey(String componentAesKey); + + String getComponentAppId(); + String getComponentAppSecret(); + String getComponentToken(); + String getComponentAesKey(); + String getComponentVerifyTicket(); + void setComponentVerifyTicket(String componentVerifyTicket); + String getComponentAccessToken(); + boolean isComponentAccessTokenExpired(); + void updateComponentAccessTokent(WxOpenComponentAccessToken componentAccessToken); + WxMpConfigStorage getWxMpConfigStorage(String appId); + /** + * 应该是线程安全的 + * + * @param componentAccessToken 新的accessToken值 + * @param expiresInSeconds 过期时间,以秒为单位 + */ + void updateComponentAccessTokent(String componentAccessToken, int expiresInSeconds); + + /** + * 是否自动刷新token + */ + boolean autoRefreshToken(); + + + String getAuthorizerRefreshToken(String appId); + void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken); + String getAuthorizerAccessToken(String appId); + + + boolean isAuthorizerAccessTokenExpired(String appId); + + /** + * 强制将access token过期掉 + */ + void expireAuthorizerAccessToken(String appId); + + /** + * 应该是线程安全的 + * + * @param authorizerAccessToken 要更新的WxAccessToken对象 + */ + void updateAuthorizerAccessToken(String appId, WxOpenAuthorizerAccessToken authorizerAccessToken); + + /** + * 应该是线程安全的 + * + * @param authorizerAccessToken 新的accessToken值 + * @param expiresInSeconds 过期时间,以秒为单位 + */ + void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds); + + String getJsapiTicket(String appId); + + boolean isJsapiTicketExpired(String appId); + + /** + * 强制将jsapi ticket过期掉 + */ + void expireJsapiTicket(String appId); + + /** + * 应该是线程安全的 + * + * @param jsapiTicket 新的jsapi ticket值 + * @param expiresInSeconds 过期时间,以秒为单位 + */ + void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds); + + String getCardApiTicket(String appId); + + + boolean isCardApiTicketExpired(String appId); + + /** + * 强制将卡券api ticket过期掉 + */ + void expireCardApiTicket(String appId); + + /** + * 应该是线程安全的 + * + * @param cardApiTicket 新的cardApi ticket值 + * @param expiresInSeconds 过期时间,以秒为单位 + */ + void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds); + +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenService.java new file mode 100644 index 00000000..c0214d46 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenService.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.open.api; + +import me.chanjar.weixin.common.exception.WxErrorException; + +/** + * @author 007 + */ +public interface WxOpenService { + WxOpenComponentService getWxOpenComponentService(); + 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; + +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java new file mode 100644 index 00000000..9f797e2f --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java @@ -0,0 +1,218 @@ +package me.chanjar.weixin.open.api.impl; + +import com.google.gson.JsonObject; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.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.api.WxMpService; +import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; +import me.chanjar.weixin.open.api.WxOpenComponentService; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.WxOpenService; +import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; +import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; +import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage; +import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult; +import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerOptionResult; +import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult; +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.Hashtable; +import java.util.Map; + +/** + * @author 007 + */ +public class WxOpenComponentServiceImpl implements WxOpenComponentService { + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + private WxOpenService wxOpenService; + private static final Map wxOpenMpServiceMap = new Hashtable<>(); + + public WxOpenComponentServiceImpl(WxOpenService wxOpenService) { + this.wxOpenService = wxOpenService; + } + + @Override + public WxMpService getWxMpServiceByAppid(String appId) { + WxMpService wxMpService = wxOpenMpServiceMap.get(appId); + if (wxMpService == null) { + synchronized (wxOpenMpServiceMap) { + wxMpService = wxOpenMpServiceMap.get(appId); + if (wxMpService == null) { + wxMpService = new WxOpenMpServiceImpl(this, appId, getWxOpenConfigStorage().getWxMpConfigStorage(appId)); + + wxOpenMpServiceMap.put(appId, wxMpService); + } + } + } + return wxMpService; + } + + public WxOpenService getWxOpenService() { + return wxOpenService; + } + + @Override + public WxOpenConfigStorage getWxOpenConfigStorage() { + return wxOpenService.getWxOpenConfigStorage(); + } + @Override + public boolean checkSignature(String timestamp, String nonce, String signature) { + try { + return SHA1.gen(getWxOpenConfigStorage().getComponentToken(), timestamp, nonce) + .equals(signature); + } catch (Exception e) { + this.log.error("Checking signature failed, and the reason is :" + e.getMessage()); + return false; + } + } + @Override + public String getComponentAccessToken(boolean forceRefresh) throws WxErrorException { + + if (this.getWxOpenConfigStorage().isComponentAccessTokenExpired() || forceRefresh) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); + jsonObject.addProperty("component_appsecret", getWxOpenConfigStorage().getComponentAppSecret()); + jsonObject.addProperty("component_verify_ticket", getWxOpenConfigStorage().getComponentVerifyTicket()); + + String responseContent = this.getWxOpenService().post(API_COMPONENT_TOKEN_URL, jsonObject.toString()); + WxOpenComponentAccessToken componentAccessToken = WxOpenComponentAccessToken.fromJson(responseContent); + getWxOpenConfigStorage().updateComponentAccessTokent(componentAccessToken); + } + return this.getWxOpenConfigStorage().getComponentAccessToken(); + } + + private String post(String uri, String postData) throws WxErrorException { + String componentAccessToken = getComponentAccessToken(false); + String uriWithComponentAccessToken = uri + (uri.contains("?") ? "&" : "?") + "component_access_token=" + componentAccessToken; + return getWxOpenService().post(uriWithComponentAccessToken, postData); + } + + private String get(String uri) throws WxErrorException { + String componentAccessToken = getComponentAccessToken(false); + String uriWithComponentAccessToken = uri + (uri.contains("?") ? "&" : "?") + "component_access_token=" + componentAccessToken; + return getWxOpenService().get(uriWithComponentAccessToken, null); + } + + @Override + public String getPreAuthUrl(String redirectURI) throws WxErrorException { + + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); + String responseContent = post(API_CREATE_PREAUTHCODE_URL, jsonObject.toString()); + jsonObject = WxGsonBuilder.create().fromJson(responseContent, JsonObject.class); + return String.format(COMPONENT_LOGIN_PAGE_URL, getWxOpenConfigStorage().getComponentAppId(), jsonObject.get("pre_auth_code").getAsString(), URIUtil.encodeURIComponent(redirectURI)); + } + + @Override + public String route(final WxOpenXmlMessage wxMessage) throws WxErrorException { + if (wxMessage == null) { + throw new NullPointerException("message is empty"); + } + if (StringUtils.equalsIgnoreCase(wxMessage.getInfoType(), "component_verify_ticket")) { + getWxOpenConfigStorage().setComponentVerifyTicket(wxMessage.getComponentVerifyTicket()); + return "success"; + } + //新增、跟新授权 + if (StringUtils.equalsAnyIgnoreCase(wxMessage.getInfoType(), "authorized", "updateauthorized")) { + WxOpenQueryAuthResult queryAuth = wxOpenService.getWxOpenComponentService().getQueryAuth(wxMessage.getAuthorizationCode()); + if (queryAuth == null || queryAuth.getAuthorizationInfo() == null || queryAuth.getAuthorizationInfo().getAuthorizerAppid() == null) { + throw new NullPointerException("getQueryAuth"); + } + WxOpenAuthorizationInfo authorizationInfo = queryAuth.getAuthorizationInfo(); + if (authorizationInfo.getAuthorizerAccessToken() != null) { + getWxOpenConfigStorage().updateAuthorizerAccessToken(authorizationInfo.getAuthorizerAppid(), + authorizationInfo.getAuthorizerAccessToken(), authorizationInfo.getExpiresIn()); + } + if (authorizationInfo.getAuthorizerRefreshToken() != null) { + getWxOpenConfigStorage().setAuthorizerRefreshToken(authorizationInfo.getAuthorizerAppid(), authorizationInfo.getAuthorizerRefreshToken()); + } + return "success"; + } + return null; + } + @Override + public WxOpenQueryAuthResult getQueryAuth(String authorizationCode) throws WxErrorException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); + jsonObject.addProperty("authorization_code", authorizationCode); + String responseContent = post(API_QUERY_AUTH_URL, jsonObject.toString()); + return WxOpenGsonBuilder.create().fromJson(responseContent, WxOpenQueryAuthResult.class); + } + @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()); + return WxOpenGsonBuilder.create().fromJson(responseContent, WxOpenAuthorizerInfoResult.class); + } + + @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()); + return WxOpenGsonBuilder.create().fromJson(responseContent, WxOpenAuthorizerOptionResult.class); + } + @Override + public WxError 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); + String responseContent = post(API_SET_AUTHORIZER_OPTION_URL, jsonObject.toString()); + return WxGsonBuilder.create().fromJson(responseContent, WxError.class); + } + + @Override + public String getAuthorizerAccessToken(String appId, boolean forceRefresh) throws WxErrorException { + + if (this.getWxOpenConfigStorage().isAuthorizerAccessTokenExpired(appId) || forceRefresh) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); + jsonObject.addProperty("authorizer_appid", appId); + jsonObject.addProperty("authorizer_refresh_token", getWxOpenConfigStorage().getAuthorizerRefreshToken(appId)); + String responseContent = post(API_AUTHORIZER_TOKEN_URL, jsonObject.toString()); + + WxOpenAuthorizerAccessToken wxOpenAuthorizerAccessToken = WxOpenAuthorizerAccessToken.fromJson(responseContent); + getWxOpenConfigStorage().updateAuthorizerAccessToken(appId, wxOpenAuthorizerAccessToken); + } + return this.getWxOpenConfigStorage().getAuthorizerAccessToken(appId); + } + + @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); + return WxMpOAuth2AccessToken.fromJson(responseContent); + } + + @Override + public boolean checkSignature(String appid, String timestamp, String nonce, String signature) { + return false; + } + + @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); + 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()); + } + +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java new file mode 100644 index 00000000..dc10ee18 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInMemoryConfigStorage.java @@ -0,0 +1,386 @@ +package me.chanjar.weixin.open.api.impl; + + +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.util.ToStringUtils; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import me.chanjar.weixin.mp.api.WxMpConfigStorage; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; +import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; + +import java.io.File; +import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 + * + * @author 007 + */ +public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { + private String componentAppId; + private String componentAppSecret; + private String componentToken; + private String componentAesKey; + private String componentVerifyTicket; + private String componentAccessToken; + private long componentExpiresTime; + + private Map authorizerRefreshTokens = new Hashtable<>(); + private Map authorizerAccessTokens = new Hashtable<>(); + private Map jsapiTickets = new Hashtable<>(); + private Map cardApiTickets = new Hashtable<>(); + + @Override + public void setComponentAppId(String componentAppId) { + this.componentAppId = componentAppId; + } + @Override + public void setComponentAppSecret(String componentAppSecret) { + this.componentAppSecret = componentAppSecret; + } + @Override + public void setComponentToken(String componentToken) { + this.componentToken = componentToken; + } + @Override + public void setComponentAesKey(String componentAesKey) { + this.componentAesKey = componentAesKey; + } + + @Override + public String getComponentAppId() { + return componentAppId; + } + + @Override + public String getComponentAppSecret() { + return componentAppSecret; + } + + @Override + public String getComponentToken() { + return componentToken; + } + + @Override + public String getComponentAesKey() { + return componentAesKey; + } + + @Override + public String getComponentVerifyTicket() { + return componentVerifyTicket; + } + + @Override + public void setComponentVerifyTicket(String componentVerifyTicket) { + this.componentVerifyTicket = componentVerifyTicket; + } + + @Override + public String getComponentAccessToken() { + return componentAccessToken; + } + + @Override + public boolean isComponentAccessTokenExpired() { + return System.currentTimeMillis() > componentExpiresTime; + } + + @Override + public void updateComponentAccessTokent(WxOpenComponentAccessToken componentAccessToken) { + updateComponentAccessTokent(componentAccessToken.getComponentAccessToken(), componentAccessToken.getExpiresIn()); + } + + @Override + public WxMpConfigStorage getWxMpConfigStorage(String appId) { + return new WxOpenMpConfigStorage(this, appId); + } + + @Override + public void updateComponentAccessTokent(String componentAccessToken, int expiresInSeconds) { + this.componentAccessToken = componentAccessToken; + this.componentExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; + } + + @Override + public boolean autoRefreshToken() { + return true; + } + private String getTokenString(Map map, String key){ + Token token = map.get(key); + if(token == null || (token.expiresTime != null && System.currentTimeMillis() > token.expiresTime)){ + return null; + } + return token.token; + } + private void expireToken(Map map, String key){ + Token token = map.get(key); + if(token != null){ + token.expiresTime = 0L; + } + } + private void updateToken(Map map, String key, String tokenString, Integer expiresInSeconds){ + Token token = map.get(key); + if(token == null){ + token = new Token(); + map.put(key, token); + } + token.token = tokenString; + if(expiresInSeconds != null) { + token.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; + } + } + @Override + public String getAuthorizerRefreshToken(String appId) { + return getTokenString(authorizerRefreshTokens, appId); + } + + @Override + public void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken) { + updateToken(authorizerRefreshTokens, appId, authorizerRefreshToken, null); + } + + @Override + public String getAuthorizerAccessToken(String appId) { + return getTokenString(authorizerAccessTokens, appId); + } + + + @Override + public boolean isAuthorizerAccessTokenExpired(String appId) { + return getTokenString(authorizerAccessTokens, appId) == null; + } + + @Override + public void expireAuthorizerAccessToken(String appId) { + expireToken(authorizerAccessTokens, appId); + } + + @Override + public void updateAuthorizerAccessToken(String appId, WxOpenAuthorizerAccessToken authorizerAccessToken) { + updateAuthorizerAccessToken(appId, authorizerAccessToken.getAuthorizerAccessToken(), authorizerAccessToken.getExpiresIn()); + } + + @Override + public void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds) { + updateToken(authorizerAccessTokens, appId, authorizerAccessToken, expiresInSeconds); + } + + @Override + public String getJsapiTicket(String appId) { + return getTokenString(jsapiTickets, appId); + } + + @Override + public boolean isJsapiTicketExpired(String appId) { + return getTokenString(jsapiTickets, appId) == null; + } + + @Override + public void expireJsapiTicket(String appId) { + expireToken(jsapiTickets, appId); + } + + @Override + public void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds) { + updateToken(jsapiTickets, appId, jsapiTicket, expiresInSeconds); + } + + @Override + public String getCardApiTicket(String appId) { + return getTokenString(cardApiTickets, appId); + } + + @Override + public boolean isCardApiTicketExpired(String appId) { + return getTokenString(cardApiTickets, appId) == null; + } + + @Override + public void expireCardApiTicket(String appId) { + expireToken(cardApiTickets, appId); + } + + @Override + public void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds) { + updateToken(cardApiTickets, appId, cardApiTicket, expiresInSeconds); + } + + private static class Token{ + private String token; + private Long expiresTime; + } + private static class WxOpenMpConfigStorage implements WxMpConfigStorage{ + private WxOpenConfigStorage wxOpenConfigStorage; + private String appId; + private WxOpenMpConfigStorage(WxOpenConfigStorage wxOpenConfigStorage, String appId){ + this.wxOpenConfigStorage = wxOpenConfigStorage; + this.appId = appId; + } + + private Lock accessTokenLock = new ReentrantLock(); + private Lock jsapiTicketLock = new ReentrantLock(); + private Lock cardApiTicketLock = new ReentrantLock(); + + + @Override + public String getAccessToken() { + return wxOpenConfigStorage.getAuthorizerAccessToken(appId); + } + + @Override + public Lock getAccessTokenLock() { + return this.accessTokenLock; + } + + @Override + public boolean isAccessTokenExpired() { + return wxOpenConfigStorage.isAuthorizerAccessTokenExpired(appId); + } + + @Override + public synchronized void updateAccessToken(WxAccessToken accessToken) { + updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } + + @Override + public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { + wxOpenConfigStorage.updateAuthorizerAccessToken(appId, accessToken, expiresInSeconds); + } + + @Override + public void expireAccessToken() { + wxOpenConfigStorage.expireAuthorizerAccessToken(appId); + } + + @Override + public String getJsapiTicket() { + return wxOpenConfigStorage.getJsapiTicket(appId); + } + + @Override + public Lock getJsapiTicketLock() { + return this.jsapiTicketLock; + } + + @Override + public boolean isJsapiTicketExpired() { + return wxOpenConfigStorage.isJsapiTicketExpired(appId); + } + + @Override + public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { + wxOpenConfigStorage.updateJsapiTicket(appId, jsapiTicket, expiresInSeconds); + } + + @Override + public void expireJsapiTicket() { + wxOpenConfigStorage.expireJsapiTicket(appId); + } + + /** + * 卡券api_ticket + */ + @Override + public String getCardApiTicket() { + return wxOpenConfigStorage.getCardApiTicket(appId); + } + + @Override + public Lock getCardApiTicketLock() { + return this.cardApiTicketLock; + } + + @Override + public boolean isCardApiTicketExpired() { + return wxOpenConfigStorage.isCardApiTicketExpired(appId); + } + + @Override + public synchronized void updateCardApiTicket(String cardApiTicket, int expiresInSeconds) { + wxOpenConfigStorage.updateCardApiTicket(appId, cardApiTicket, expiresInSeconds); + } + + @Override + public void expireCardApiTicket() { + wxOpenConfigStorage.expireCardApiTicket(appId); + } + + @Override + public String getAppId() { + return this.appId; + } + + @Override + public String getSecret() { + return null; + } + + @Override + public String getToken() { + return null; + } + + @Override + public long getExpiresTime() { + return 0; + } + + + @Override + public String getAesKey() { + return null; + } + + @Override + public String getOauth2redirectUri() { + return null; + } + + @Override + public String getHttpProxyHost() { + return null; + } + + @Override + public int getHttpProxyPort() { + return 0; + } + + @Override + public String getHttpProxyUsername() { + return null; + } + + @Override + public String getHttpProxyPassword() { + return null; + } + + @Override + public String toString() { + return ToStringUtils.toSimpleString(this); + } + + @Override + public File getTmpDirFile() { + return null; + } + + @Override + public ApacheHttpClientBuilder getApacheHttpClientBuilder() { + return null; + } + + + @Override + public boolean autoRefreshToken() { + return true; + } + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java new file mode 100644 index 00000000..faaa95d8 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenInRedisConfigStorage.java @@ -0,0 +1,164 @@ +package me.chanjar.weixin.open.api.impl; + +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; + +/** + * @author 007 + */ +public class WxOpenInRedisConfigStorage extends WxOpenInMemoryConfigStorage { + + private final static String COMPONENT_VERIFY_TICKET_KEY = "wechat_component_verify_ticket:"; + private final static String COMPONENT_ACCESS_TOKEN_KEY = "wechat_component_access_token:"; + + private final static String AUTHORIZER_REFRESH_TOKEN_KEY = "wechat_authorizer_refresh_token:"; + private final static String AUTHORIZER_ACCESS_TOKEN_KEY = "wechat_authorizer_access_token:"; + private final static String JSAPI_TICKET_KEY = "wechat_jsapi_ticket:"; + private final static String CARD_API_TICKET_KEY = "wechat_card_api_ticket:"; + + + protected final JedisPool jedisPool; + private String componentVerifyTicketKey; + private String componentAccessTokenKey; + private String authorizerRefreshTokenKey; + private String authorizerAccessTokenKey; + private String jsapiTicketKey; + private String cardApiTicket; + public WxOpenInRedisConfigStorage(JedisPool jedisPool) { + this.jedisPool = jedisPool; + } + + @Override + public void setComponentAppId(String componentAppId) { + super.setComponentAppId(componentAppId); + componentVerifyTicketKey = COMPONENT_VERIFY_TICKET_KEY.concat(componentAppId); + componentAccessTokenKey = COMPONENT_ACCESS_TOKEN_KEY.concat(componentAppId); + authorizerRefreshTokenKey = AUTHORIZER_REFRESH_TOKEN_KEY.concat(componentAppId); + authorizerAccessTokenKey = AUTHORIZER_ACCESS_TOKEN_KEY.concat(componentAppId); + jsapiTicketKey = JSAPI_TICKET_KEY.concat(componentAppId); + cardApiTicket = CARD_API_TICKET_KEY.concat(componentAppId); + } + @Override + public String getComponentVerifyTicket(){ + try(Jedis jedis = jedisPool.getResource()){ + return jedis.get(componentVerifyTicketKey); + } + } + @Override + public void setComponentVerifyTicket(String componentVerifyTicket){ + try(Jedis jedis = jedisPool.getResource()){ + jedis.set(componentVerifyTicketKey, componentVerifyTicket); + } + } + @Override + public String getComponentAccessToken(){ + try(Jedis jedis = jedisPool.getResource()){ + return jedis.get(componentAccessTokenKey); + } + } + @Override + public boolean isComponentAccessTokenExpired(){ + try(Jedis jedis = jedisPool.getResource()){ + return jedis.ttl(componentAccessTokenKey) < 2; + } + } + @Override + public void updateComponentAccessTokent(String componentAccessToken, int expiresInSeconds){ + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.setex(componentAccessTokenKey, expiresInSeconds - 200, componentAccessToken); + } + } + 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(getKey(authorizerRefreshTokenKey, appId)); + } + } + @Override + public void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken){ + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.set(getKey(authorizerRefreshTokenKey, appId), authorizerRefreshToken); + } + } + @Override + public String getAuthorizerAccessToken(String appId){ + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.get(getKey(authorizerAccessTokenKey, appId)); + } + } + + + @Override + public boolean isAuthorizerAccessTokenExpired(String appId){ + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.ttl(getKey(authorizerAccessTokenKey, appId)) < 2; + } + } + @Override + public void expireAuthorizerAccessToken(String appId){ + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.expire(getKey(authorizerAccessTokenKey, appId), 0); + } + } + @Override + public void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds){ + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.setex(getKey(authorizerAccessTokenKey, appId), expiresInSeconds - 200, authorizerAccessToken); + } + } + + @Override + public String getJsapiTicket(String appId){ + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.get(getKey(jsapiTicketKey, appId)); + } + } + + @Override + public boolean isJsapiTicketExpired(String appId){ + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.ttl(getKey(jsapiTicketKey, appId)) < 2; + } + } + @Override + public void expireJsapiTicket(String appId){ + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.expire(getKey(jsapiTicketKey, appId), 0); + } + } + @Override + public void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds){ + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.setex(getKey(jsapiTicketKey, appId), expiresInSeconds - 200, jsapiTicket); + } + } + + @Override + public String getCardApiTicket(String appId){ + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.get(getKey(jsapiTicketKey, appId)); + } + } + @Override + public boolean isCardApiTicketExpired(String appId){ + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.ttl(getKey(cardApiTicket, appId)) < 2; + } + } + @Override + public void expireCardApiTicket(String appId){ + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.expire(getKey(cardApiTicket, appId), 0); + } + } + @Override + public void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds){ + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.setex(getKey(cardApiTicket, appId), expiresInSeconds - 200, cardApiTicket); + } + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java new file mode 100644 index 00000000..b4baf585 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java @@ -0,0 +1,49 @@ +package me.chanjar.weixin.open.api.impl; + +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpConfigStorage; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; +import me.chanjar.weixin.open.api.WxOpenComponentService; + +/** + * @author 007 + */ +public class WxOpenMpServiceImpl extends WxMpServiceImpl { + private WxOpenComponentService wxOpenComponentService; + private WxMpConfigStorage wxMpConfigStorage; + private String appId; + public WxOpenMpServiceImpl(WxOpenComponentService wxOpenComponentService, String appId, WxMpConfigStorage wxMpConfigStorage){ + this.wxOpenComponentService = wxOpenComponentService; + this.appId = appId; + this.wxMpConfigStorage = wxMpConfigStorage; + } + @Override + public WxMpConfigStorage getWxMpConfigStorage(){ + return wxMpConfigStorage; + } + @Override + public String getAccessToken(boolean forceRefresh) throws WxErrorException { + return wxOpenComponentService.getAuthorizerAccessToken(appId, forceRefresh); + } + + @Override + public WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException { + return wxOpenComponentService.oauth2getAccessToken(appId, code); + } + +// @Override +// public boolean checkSignature(String timestamp, String nonce, String signature) { +// return wxOpenComponentService.checkSignature(appId, timestamp, nonce, signature); +// } + + @Override + public WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException { + return wxOpenComponentService.oauth2refreshAccessToken(appId, refreshToken); + } + + @Override + public String oauth2buildAuthorizationUrl(String redirectURI, String scope, String state) { + return wxOpenComponentService.oauth2buildAuthorizationUrl(appId, redirectURI, scope, state); + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java new file mode 100644 index 00000000..5f937dc5 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java @@ -0,0 +1,63 @@ +package me.chanjar.weixin.open.api.impl; + +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.open.api.WxOpenComponentService; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.api.WxOpenService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +/** + * @author 007 + */ +public abstract class WxOpenServiceAbstractImpl implements WxOpenService, RequestHttp { + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + private WxOpenConfigStorage wxOpenConfigStorage; + protected WxOpenComponentService wxOpenComponentService = new WxOpenComponentServiceImpl(this); + @Override + public WxOpenComponentService getWxOpenComponentService() { + return wxOpenComponentService; + } + @Override + public WxOpenConfigStorage getWxOpenConfigStorage(){ + return wxOpenConfigStorage; + } + public void setWxOpenConfigStorage(WxOpenConfigStorage wxOpenConfigStorage){ + this.wxOpenConfigStorage = wxOpenConfigStorage; + } + + protected synchronized T execute(RequestExecutor executor, String uri, E data) throws WxErrorException { + try { + T result = executor.execute(uri, data); + this.log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uri, data, result); + return result; + } 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) { +// // 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token +// this.configStorage.expireAccessToken(); +// return execute(executor, uri, data); +// } + + 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); + } + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceApacheHttpClientImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceApacheHttpClientImpl.java new file mode 100644 index 00000000..e5b62b34 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceApacheHttpClientImpl.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.open.api.impl; + +import me.chanjar.weixin.common.exception.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.DefaultApacheHttpClientBuilder; +import org.apache.http.HttpHost; +import org.apache.http.impl.client.CloseableHttpClient; + +/** + * apache-http方式实现 + * @author 007 + */ +public class WxOpenServiceApacheHttpClientImpl extends WxOpenServiceAbstractImpl { + private CloseableHttpClient httpClient = DefaultApacheHttpClientBuilder.get().build(); + private HttpHost httpProxy = null; + + @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); + } + +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceImpl.java new file mode 100644 index 00000000..c807ccdf --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceImpl.java @@ -0,0 +1,8 @@ +package me.chanjar.weixin.open.api.impl; + +/** + * @author 007 + */ +public class WxOpenServiceImpl extends WxOpenServiceApacheHttpClientImpl { + +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/WxOpenAuthorizerAccessToken.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/WxOpenAuthorizerAccessToken.java new file mode 100644 index 00000000..429aad17 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/WxOpenAuthorizerAccessToken.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.open.bean; + +import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder; + +import java.io.Serializable; + +/** + * @author 007 + */ +public class WxOpenAuthorizerAccessToken implements Serializable{ + + private String authorizerAccessToken; + + private int expiresIn = -1; + + public static WxOpenAuthorizerAccessToken fromJson(String json) { + return WxOpenGsonBuilder.create().fromJson(json, WxOpenAuthorizerAccessToken.class); + } + + public String getAuthorizerAccessToken() { + return authorizerAccessToken; + } + + public void setAuthorizerAccessToken(String authorizerAccessToken) { + this.authorizerAccessToken = authorizerAccessToken; + } + + public int getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(int expiresIn) { + this.expiresIn = expiresIn; + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/WxOpenComponentAccessToken.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/WxOpenComponentAccessToken.java new file mode 100644 index 00000000..31f79b75 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/WxOpenComponentAccessToken.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.open.bean; + +import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder; + +import java.io.Serializable; + +/** + * @author 007 + */ +public class WxOpenComponentAccessToken implements Serializable{ + + private String componentAccessToken; + + private int expiresIn = -1; + + public static WxOpenComponentAccessToken fromJson(String json) { + return WxOpenGsonBuilder.create().fromJson(json, WxOpenComponentAccessToken.class); + } + + public String getComponentAccessToken() { + return componentAccessToken; + } + + public void setComponentAccessToken(String componentAccessToken) { + this.componentAccessToken = componentAccessToken; + } + + public int getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(int expiresIn) { + this.expiresIn = expiresIn; + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/auth/WxOpenAuthorizationInfo.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/auth/WxOpenAuthorizationInfo.java new file mode 100644 index 00000000..687e31ed --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/auth/WxOpenAuthorizationInfo.java @@ -0,0 +1,18 @@ +package me.chanjar.weixin.open.bean.auth; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @author 007 + */ +@Data +public class WxOpenAuthorizationInfo implements Serializable{ + private String authorizerAppid; + private String authorizerAccessToken; + private int expiresIn; + private String authorizerRefreshToken; + private List funcInfo; +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/auth/WxOpenAuthorizerInfo.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/auth/WxOpenAuthorizerInfo.java new file mode 100644 index 00000000..b9f63a3e --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/auth/WxOpenAuthorizerInfo.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.open.bean.auth; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Map; + +/** + * @author 007 + */ +@Data +public class WxOpenAuthorizerInfo implements Serializable{ + private String nickName; + private String headImg; + private Integer serviceTypeInfo; + private Integer verifyTypeInfo; + private String userName; + private String principalName; + private Map businessInfo; + private String alias; + private String qrcodeUrl; +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java new file mode 100644 index 00000000..afcbd5f1 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java @@ -0,0 +1,100 @@ +package me.chanjar.weixin.open.bean.message; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamConverter; +import lombok.Data; +import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import me.chanjar.weixin.open.util.WxOpenCryptUtil; +import me.chanjar.weixin.open.util.xml.XStreamTransformer; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; + +/** + * @author 007 + */ +@XStreamAlias("xml") +@Data +public class WxOpenXmlMessage implements Serializable{ + @XStreamAlias("AppId") + @XStreamConverter(value = XStreamCDataConverter.class) + private String appId; + + @XStreamAlias("CreateTime") + private Long createTime; + + @XStreamAlias("InfoType") + @XStreamConverter(value = XStreamCDataConverter.class) + private String infoType; + + @XStreamAlias("ComponentVerifyTicket") + @XStreamConverter(value = XStreamCDataConverter.class) + private String componentVerifyTicket; + + @XStreamAlias("AuthorizerAppid") + @XStreamConverter(value = XStreamCDataConverter.class) + private String authorizerAppid; + + @XStreamAlias("AuthorizationCode") + @XStreamConverter(value = XStreamCDataConverter.class) + private String authorizationCode; + + @XStreamAlias("AuthorizationCodeExpiredTime") + @XStreamConverter(value = XStreamCDataConverter.class) + private Long authorizationCodeExpiredTime; + + @XStreamAlias("PreAuthCode") + @XStreamConverter(value = XStreamCDataConverter.class) + private String preAuthCode; + + public static WxOpenXmlMessage fromXml(String xml) { + //修改微信变态的消息内容格式,方便解析 + xml = xml.replace("", ""); + return XStreamTransformer.fromXml(WxOpenXmlMessage.class, xml); + } + + public static WxOpenXmlMessage fromXml(InputStream is) { + return XStreamTransformer.fromXml(WxOpenXmlMessage.class, is); + } + + /** + * 从加密字符串转换 + * + * @param encryptedXml 密文 + * @param wxOpenConfigStorage 配置存储器对象 + * @param timestamp 时间戳 + * @param nonce 随机串 + * @param msgSignature 签名串 + */ + public static WxOpenXmlMessage fromEncryptedXml(String encryptedXml, + WxOpenConfigStorage wxOpenConfigStorage, String timestamp, String nonce, + String msgSignature) { + WxOpenCryptUtil cryptUtil = new WxOpenCryptUtil(wxOpenConfigStorage); + String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, + encryptedXml); + return fromXml(plainText); + } + public static WxMpXmlMessage fromEncryptedMpXml(String encryptedXml, + WxOpenConfigStorage wxOpenConfigStorage, String timestamp, String nonce, + String msgSignature) { + WxOpenCryptUtil cryptUtil = new WxOpenCryptUtil(wxOpenConfigStorage); + String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, + encryptedXml); + return WxMpXmlMessage.fromXml(plainText); + } + + public static WxOpenXmlMessage fromEncryptedXml(InputStream is, + WxOpenConfigStorage wxOpenConfigStorage, String timestamp, String nonce, + String msgSignature) { + try { + return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxOpenConfigStorage, + timestamp, nonce, msgSignature); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenAuthorizerInfoResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenAuthorizerInfoResult.java new file mode 100644 index 00000000..040c9f02 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenAuthorizerInfoResult.java @@ -0,0 +1,16 @@ +package me.chanjar.weixin.open.bean.result; + +import lombok.Data; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizerInfo; + +import java.io.Serializable; + +/** + * @author 007 + */ +@Data +public class WxOpenAuthorizerInfoResult implements Serializable{ + private WxOpenAuthorizationInfo authorizationInfo; + private WxOpenAuthorizerInfo authorizerInfo; +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenAuthorizerOptionResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenAuthorizerOptionResult.java new file mode 100644 index 00000000..d7bf7aa1 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenAuthorizerOptionResult.java @@ -0,0 +1,15 @@ +package me.chanjar.weixin.open.bean.result; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author 007 + */ +@Data +public class WxOpenAuthorizerOptionResult implements Serializable{ + String authorizerAppid; + String optionName; + String optionValue; +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenQueryAuthResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenQueryAuthResult.java new file mode 100644 index 00000000..02dab988 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/result/WxOpenQueryAuthResult.java @@ -0,0 +1,14 @@ +package me.chanjar.weixin.open.bean.result; + +import lombok.Data; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; + +import java.io.Serializable; + +/** + * @author 007 + */ +@Data +public class WxOpenQueryAuthResult implements Serializable{ + private WxOpenAuthorizationInfo authorizationInfo; +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/WxOpenCryptUtil.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/WxOpenCryptUtil.java new file mode 100644 index 00000000..0adcdbb3 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/WxOpenCryptUtil.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.open.util; + +import me.chanjar.weixin.open.api.WxOpenConfigStorage; +import org.apache.commons.codec.binary.Base64; +/** + * @author 007 + */ +public class WxOpenCryptUtil extends me.chanjar.weixin.common.util.crypto.WxCryptUtil { + /** + * 构造函数 + * + * @param wxOpenConfigStorage + */ + public WxOpenCryptUtil(WxOpenConfigStorage wxOpenConfigStorage) { + /* + * @param token 公众平台上,开发者设置的token + * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey + * @param appId 公众平台appid + */ + String encodingAesKey = wxOpenConfigStorage.getComponentAesKey(); + String token = wxOpenConfigStorage.getComponentToken(); + String appId = wxOpenConfigStorage.getComponentAppId(); + + this.token = token; + this.appidOrCorpid = appId; + this.aesKey = Base64.decodeBase64(encodingAesKey + "="); + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizationInfoGsonAdapter.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizationInfoGsonAdapter.java new file mode 100644 index 00000000..7aa0c00c --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizationInfoGsonAdapter.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.open.util.json; + +import com.google.gson.*; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * @author 007 + */ +public class WxOpenAuthorizationInfoGsonAdapter implements JsonDeserializer { + @Override + public WxOpenAuthorizationInfo deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + WxOpenAuthorizationInfo authorizationInfo = new WxOpenAuthorizationInfo(); + 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")); + List funcInfo = new ArrayList<>(); + JsonArray jsonArray = GsonHelper.getAsJsonArray(jsonObject.get("func_info")); + if(jsonArray != null && !jsonArray.isJsonNull()){ + for(int i = 0; i < jsonArray.size(); i++){ + jsonObject = jsonArray.get(i).getAsJsonObject(); + if(jsonObject == null || jsonObject.isJsonNull()){ + continue; + } + jsonObject = jsonObject.getAsJsonObject("funcscope_category"); + if(jsonObject == null || jsonObject.isJsonNull()){ + continue; + } + Integer id = GsonHelper.getInteger(jsonObject, "id"); + if(id == null) { + continue; + } + funcInfo.add(id); + } + } + authorizationInfo.setFuncInfo(funcInfo); + return authorizationInfo; + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerAccessTokenGsonAdapter.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerAccessTokenGsonAdapter.java new file mode 100644 index 00000000..82a04b9b --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerAccessTokenGsonAdapter.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.open.util.json; + +import com.google.gson.*; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; + +import java.lang.reflect.Type; + +/** + * @author 007 + */ +public class WxOpenAuthorizerAccessTokenGsonAdapter implements JsonDeserializer { + @Override + public WxOpenAuthorizerAccessToken deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + WxOpenAuthorizerAccessToken authorizerAccessToken = new WxOpenAuthorizerAccessToken(); + JsonObject jsonObject = jsonElement.getAsJsonObject(); + authorizerAccessToken.setAuthorizerAccessToken(GsonHelper.getString(jsonObject, "authorizer_access_token")); + authorizerAccessToken.setExpiresIn(GsonHelper.getPrimitiveInteger(jsonObject, "expires_in")); + return authorizerAccessToken; + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerInfoGsonAdapter.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerInfoGsonAdapter.java new file mode 100644 index 00000000..5e1fb6d3 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerInfoGsonAdapter.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.open.util.json; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizerInfo; + +import java.lang.reflect.Type; +import java.util.Map; + +/** + * @author 007 + */ +public class WxOpenAuthorizerInfoGsonAdapter implements JsonDeserializer { + @Override + public WxOpenAuthorizerInfo deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + WxOpenAuthorizerInfo authorizationInfo = new WxOpenAuthorizerInfo(); + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + authorizationInfo.setNickName(GsonHelper.getString(jsonObject, "nick_name")); + authorizationInfo.setHeadImg(GsonHelper.getString(jsonObject, "head_img")); + authorizationInfo.setUserName(GsonHelper.getString(jsonObject, "user_name")); + authorizationInfo.setPrincipalName(GsonHelper.getString(jsonObject, "principal_name")); + authorizationInfo.setAlias(GsonHelper.getString(jsonObject, "alias")); + authorizationInfo.setQrcodeUrl(GsonHelper.getString(jsonObject, "qrcode_url")); + if(jsonObject.has("service_type_info")) { + authorizationInfo.setServiceTypeInfo(GsonHelper.getInteger(jsonObject.getAsJsonObject("service_type_info"), "id")); + } + if(jsonObject.has("verify_type_info")) { + authorizationInfo.setVerifyTypeInfo(GsonHelper.getInteger(jsonObject.getAsJsonObject("verify_type_info"), "id")); + } + Map businessInfo = WxOpenGsonBuilder.create().fromJson(jsonObject.get("business_info"), + new TypeToken>() { + }.getType()); + authorizationInfo.setBusinessInfo(businessInfo); + return authorizationInfo; + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerInfoResultGsonAdapter.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerInfoResultGsonAdapter.java new file mode 100644 index 00000000..1cef0ff4 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerInfoResultGsonAdapter.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.open.util.json; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizerInfo; +import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult; + +import java.lang.reflect.Type; + +/** + * @author 007 + */ +public class WxOpenAuthorizerInfoResultGsonAdapter implements JsonDeserializer { + @Override + public WxOpenAuthorizerInfoResult deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + WxOpenAuthorizerInfoResult authorizerInfoResult = new WxOpenAuthorizerInfoResult(); + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + WxOpenAuthorizationInfo authorizationInfo = WxOpenGsonBuilder.INSTANCE.create().fromJson(jsonObject.get("authorization_info"), + new TypeToken() { + }.getType()); + + authorizerInfoResult.setAuthorizationInfo(authorizationInfo); + WxOpenAuthorizerInfo authorizerInfo = WxOpenGsonBuilder.INSTANCE.create().fromJson(jsonObject.get("authorizer_info"), + new TypeToken() { + }.getType()); + + authorizerInfoResult.setAuthorizerInfo(authorizerInfo); + return authorizerInfoResult; + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerOptionResultGsonAdapter.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerOptionResultGsonAdapter.java new file mode 100644 index 00000000..8a2d379c --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenAuthorizerOptionResultGsonAdapter.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.open.util.json; + +import com.google.gson.*; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerOptionResult; + +import java.lang.reflect.Type; + +/** + * @author 007 + */ +public class WxOpenAuthorizerOptionResultGsonAdapter implements JsonDeserializer { + @Override + public WxOpenAuthorizerOptionResult deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + WxOpenAuthorizerOptionResult authorizerOptionResult = new WxOpenAuthorizerOptionResult(); + JsonObject jsonObject = jsonElement.getAsJsonObject(); + authorizerOptionResult.setAuthorizerAppid(GsonHelper.getString(jsonObject, "authorizer_appid")); + authorizerOptionResult.setOptionName(GsonHelper.getString(jsonObject, "option_name")); + authorizerOptionResult.setOptionValue(GsonHelper.getString(jsonObject, "option_value")); + return authorizerOptionResult; + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenComponentAccessTokenGsonAdapter.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenComponentAccessTokenGsonAdapter.java new file mode 100644 index 00000000..10ed8508 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenComponentAccessTokenGsonAdapter.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.open.util.json; + +import com.google.gson.*; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; + +import java.lang.reflect.Type; + +/** + * @author 007 + */ +public class WxOpenComponentAccessTokenGsonAdapter implements JsonDeserializer { + @Override + public WxOpenComponentAccessToken deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + WxOpenComponentAccessToken componentAccessToken = new WxOpenComponentAccessToken(); + JsonObject jsonObject = jsonElement.getAsJsonObject(); + componentAccessToken.setComponentAccessToken(GsonHelper.getString(jsonObject, "component_access_token")); + componentAccessToken.setExpiresIn(GsonHelper.getPrimitiveInteger(jsonObject, "expires_in")); + return componentAccessToken; + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java new file mode 100644 index 00000000..2b390ade --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenGsonBuilder.java @@ -0,0 +1,36 @@ +package me.chanjar.weixin.open.util.json; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; +import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizerInfo; +import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult; +import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerOptionResult; +import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult; + +/** + * @author 007 + */ +public class WxOpenGsonBuilder { + + public static final GsonBuilder INSTANCE = new GsonBuilder(); + + static { + INSTANCE.disableHtmlEscaping(); + INSTANCE.registerTypeAdapter(WxOpenComponentAccessToken.class, new WxOpenComponentAccessTokenGsonAdapter()); + INSTANCE.registerTypeAdapter(WxOpenAuthorizerAccessToken.class, new WxOpenAuthorizerAccessTokenGsonAdapter()); + INSTANCE.registerTypeAdapter(WxOpenAuthorizationInfo.class, new WxOpenAuthorizationInfoGsonAdapter()); + INSTANCE.registerTypeAdapter(WxOpenAuthorizerInfo.class, new WxOpenAuthorizerInfoGsonAdapter()); + INSTANCE.registerTypeAdapter(WxOpenQueryAuthResult.class, new WxOpenQueryAuthResultGsonAdapter()); + INSTANCE.registerTypeAdapter(WxOpenAuthorizerInfoResult.class, new WxOpenAuthorizerInfoResultGsonAdapter()); + INSTANCE.registerTypeAdapter(WxOpenAuthorizerOptionResult.class, new WxOpenAuthorizerOptionResultGsonAdapter()); + + } + + public static Gson create() { + return INSTANCE.create(); + } + +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenQueryAuthResultGsonAdapter.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenQueryAuthResultGsonAdapter.java new file mode 100644 index 00000000..8868d0fe --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/json/WxOpenQueryAuthResultGsonAdapter.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.open.util.json; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; +import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult; + +import java.lang.reflect.Type; + +/** + * @author 007 + */ +public class WxOpenQueryAuthResultGsonAdapter implements JsonDeserializer { + @Override + public WxOpenQueryAuthResult deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + WxOpenQueryAuthResult queryAuthResult = new WxOpenQueryAuthResult(); + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + WxOpenAuthorizationInfo authorizationInfo = WxOpenGsonBuilder.INSTANCE.create().fromJson(jsonObject.get("authorization_info"), + new TypeToken() { + }.getType()); + + queryAuthResult.setAuthorizationInfo(authorizationInfo); + return queryAuthResult; + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/xml/XStreamTransformer.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/xml/XStreamTransformer.java new file mode 100644 index 00000000..515c90de --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/util/xml/XStreamTransformer.java @@ -0,0 +1,90 @@ +package me.chanjar.weixin.open.util.xml; + +import com.thoughtworks.xstream.XStream; +import me.chanjar.weixin.common.util.xml.XStreamInitializer; +import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage; + +import java.io.InputStream; +import java.util.*; + +/** + * @author 007 + */ +public class XStreamTransformer { + private static final Map, XStream> CLASS_2_XSTREAM_INSTANCE = new HashMap<>(); + + static { + registerClass(WxOpenXmlMessage.class); + } + + /** + * xml -> pojo + */ + @SuppressWarnings("unchecked") + public static T fromXml(Class clazz, String xml) { + T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(xml); + return object; + } + + @SuppressWarnings("unchecked") + public static T fromXml(Class clazz, InputStream is) { + T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is); + return object; + } + + /** + * pojo -> xml + */ + public static String toXml(Class clazz, T object) { + return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object); + } + + /** + * 注册扩展消息的解析器 + * + * @param clz 类型 + * @param xStream xml解析器 + */ + private static void register(Class clz, XStream xStream) { + CLASS_2_XSTREAM_INSTANCE.put(clz, xStream); + } + + /** + * 会自动注册该类及其子类 + * + * @param clz 要注册的类 + */ + private static void registerClass(Class clz) { + XStream xstream = XStreamInitializer.getInstance(); + xstream.setClassLoader(Thread.currentThread().getContextClassLoader()); + + xstream.processAnnotations(clz); + xstream.processAnnotations(getInnerClasses(clz)); + if (clz.equals(WxOpenXmlMessage.class)) { + // 操蛋的微信,模板消息推送成功的消息是MsgID,其他消息推送过来是MsgId + xstream.aliasField("MsgID", WxOpenXmlMessage.class, "msgId"); + } + + register(clz, xstream); + } + + private static Class[] getInnerClasses(Class clz) { + Class[] innerClasses = clz.getClasses(); + if (innerClasses == null) { + return null; + } + + List> result = new ArrayList<>(); + result.addAll(Arrays.asList(innerClasses)); + for (Class inner : innerClasses) { + Class[] innerClz = getInnerClasses(inner); + if (innerClz == null) { + continue; + } + + result.addAll(Arrays.asList(innerClz)); + } + + return result.toArray(new Class[0]); + } +}