diff --git a/mallinkAdmin/pom.xml b/mallinkAdmin/pom.xml index ec65f306d..820d917af 100644 --- a/mallinkAdmin/pom.xml +++ b/mallinkAdmin/pom.xml @@ -12,6 +12,10 @@ mallinkAdmin + + 3.2.0 + + com.iformall @@ -23,6 +27,11 @@ mybatis-multi-tenancy 1.0 + + com.github.binarywang + weixin-java-open + ${weixin-java-open.version} + diff --git a/mallinkAdmin/src/main/java/com/iformall/config/RedisProperies.java b/mallinkAdmin/src/main/java/com/iformall/config/RedisProperies.java new file mode 100644 index 000000000..21602ae47 --- /dev/null +++ b/mallinkAdmin/src/main/java/com/iformall/config/RedisProperies.java @@ -0,0 +1,124 @@ +package com.iformall.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.Protocol; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + +/** + * Stormeye + */ +@ConfigurationProperties(prefix = "wechat.redis") +public class RedisProperies extends JedisPoolConfig { + private String host = Protocol.DEFAULT_HOST; + private int port = Protocol.DEFAULT_PORT; + private String password; + private int database = 1; + private int connectionTimeout = Protocol.DEFAULT_TIMEOUT; + private int soTimeout = Protocol.DEFAULT_TIMEOUT; + private String clientName; + private boolean ssl; + private SSLSocketFactory sslSocketFactory; + private SSLParameters sslParameters; + private HostnameVerifier hostnameVerifier; + + public boolean isSsl() { + return ssl; + } + + public void setSsl(boolean ssl) { + this.ssl = ssl; + } + + public SSLSocketFactory getSslSocketFactory() { + return sslSocketFactory; + } + + public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + } + + public SSLParameters getSslParameters() { + return sslParameters; + } + + public void setSslParameters(SSLParameters sslParameters) { + this.sslParameters = sslParameters; + } + + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + + public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + if (host == null || "".equals(host)) { + host = Protocol.DEFAULT_HOST; + } + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + if ("".equals(password)) { + password = null; + } + this.password = password; + } + + public int getDatabase() { + return database; + } + + public void setDatabase(int database) { + this.database = database; + } + + public String getClientName() { + return clientName; + } + + public void setClientName(String clientName) { + if ("".equals(clientName)) { + clientName = null; + } + this.clientName = clientName; + } + + public int getConnectionTimeout() { + return connectionTimeout; + } + + public void setConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public int getSoTimeout() { + return soTimeout; + } + + public void setSoTimeout(int soTimeout) { + this.soTimeout = soTimeout; + } +} diff --git a/mallinkAdmin/src/main/java/com/iformall/config/WechatOpenProperties.java b/mallinkAdmin/src/main/java/com/iformall/config/WechatOpenProperties.java new file mode 100644 index 000000000..e19dfc4eb --- /dev/null +++ b/mallinkAdmin/src/main/java/com/iformall/config/WechatOpenProperties.java @@ -0,0 +1,69 @@ +package com.iformall.config; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Stormeye + */ +@ConfigurationProperties(prefix = "wechat.open") +public class WechatOpenProperties { + /** + * 设置微信三方平台的appid + */ + private String componentAppId; + + /** + * 设置微信三方平台的app secret + */ + private String componentSecret; + + /** + * 设置微信三方平台的token + */ + private String componentToken; + + /** + * 设置微信三方平台的EncodingAESKey + */ + private String componentAesKey; + + public String getComponentAppId() { + return componentAppId; + } + + public void setComponentAppId(String componentAppId) { + this.componentAppId = componentAppId; + } + + public String getComponentSecret() { + return componentSecret; + } + + public void setComponentSecret(String componentSecret) { + this.componentSecret = componentSecret; + } + + public String getComponentToken() { + return componentToken; + } + + public void setComponentToken(String componentToken) { + this.componentToken = componentToken; + } + + public String getComponentAesKey() { + return componentAesKey; + } + + public void setComponentAesKey(String componentAesKey) { + this.componentAesKey = componentAesKey; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, + ToStringStyle.MULTI_LINE_STYLE); + } +} diff --git a/mallinkAdmin/src/main/java/com/iformall/controller/WechatApiController.java b/mallinkAdmin/src/main/java/com/iformall/controller/WechatApiController.java new file mode 100644 index 000000000..217a0f509 --- /dev/null +++ b/mallinkAdmin/src/main/java/com/iformall/controller/WechatApiController.java @@ -0,0 +1,72 @@ +package com.iformall.controller; + +import com.iformall.service.WxOpenService; +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.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; + +/** + * Stormeye Wu + */ +@Controller +@RequestMapping("/wtauth") +public class WechatApiController { + private final Logger logger = LoggerFactory.getLogger(getClass()); + @Autowired + private WxOpenService wxOpenService; + + @GetMapping("/goto_auth_url_show") + @ResponseBody + public String gotoPreAuthUrlShow() { + return "go"; + } + + @GetMapping("/goto_auth_url") + public void gotoPreAuthUrl(HttpServletRequest request, HttpServletResponse response) { + String host = request.getHeader("host"); + String url = "http://" + host + "/api/auth/jump"; + try { + url = wxOpenService.getWxOpenComponentService().getPreAuthUrl(url); + response.sendRedirect(url); + } catch (WxErrorException | IOException e) { + logger.error("gotoPreAuthUrl", e); + throw new RuntimeException(e); + } + } + + @GetMapping("/jump") + @ResponseBody + public WxOpenQueryAuthResult jump(@RequestParam("auth_code") String authorizationCode) { + try { + WxOpenQueryAuthResult queryAuthResult = wxOpenService.getWxOpenComponentService().getQueryAuth(authorizationCode); + logger.info("getQueryAuth", queryAuthResult); + return queryAuthResult; + } catch (WxErrorException e) { + logger.error("gotoPreAuthUrl", e); + throw new RuntimeException(e); + } + } + + @GetMapping("/get_authorizer_info") + @ResponseBody + public WxOpenAuthorizerInfoResult getAuthorizerInfo(@RequestParam String appId) { + try { + return wxOpenService.getWxOpenComponentService().getAuthorizerInfo(appId); + } catch (WxErrorException e) { + logger.error("getAuthorizerInfo", e); + throw new RuntimeException(e); + } + } +} diff --git a/mallinkAdmin/src/main/java/com/iformall/controller/WechatCalllbackController.java b/mallinkAdmin/src/main/java/com/iformall/controller/WechatCalllbackController.java new file mode 100644 index 000000000..c0cac16db --- /dev/null +++ b/mallinkAdmin/src/main/java/com/iformall/controller/WechatCalllbackController.java @@ -0,0 +1,108 @@ +package com.iformall.controller; + +import com.iformall.service.WxOpenService; +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.message.WxOpenXmlMessage; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("wxOpen") +public class WechatCalllbackController extends BaseController { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + protected WxOpenService wxOpenService; + + @RequestMapping("/notify") + 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) { + 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()); + try { + String out = wxOpenService.getWxOpenComponentService().route(inMessage); + this.logger.debug("\n组装回复信息:{}", out); + } catch (WxErrorException e) { + this.logger.error("notify", e); + } + + + return "success"; + } + + @RequestMapping("callback/{appId}") + 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) { + logger.info( + "\n接收微信请求:[appId=[{}], openid=[{}], signature=[{}], encType=[{}], msgSignature=[{}]," + + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", + appId, openid, signature, encType, msgSignature, timestamp, nonce, 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()); + // 全网发布测试用例 + if (StringUtils.equalsAnyIgnoreCase(appId, "wxd101a85aa106f53e", "wx570bc396a51b8ff8")) { + 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(), + wxOpenService.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(); + wxOpenService.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(); + wxOpenService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage); + } + } catch (WxErrorException e) { + logger.error("callback", e); + } + } else { + WxMpXmlOutMessage outMessage = wxOpenService.getWxOpenMessageRouter().route(inMessage, appId); + if (outMessage != null) { + out = WxOpenXmlMessage.wxMpOutXmlMessageToEncryptedXml(outMessage, wxOpenService.getWxOpenConfigStorage()); + } + } + return out; + } +} diff --git a/mallinkAdmin/src/main/java/com/iformall/service/WxOpenService.java b/mallinkAdmin/src/main/java/com/iformall/service/WxOpenService.java new file mode 100644 index 000000000..4c428fbb6 --- /dev/null +++ b/mallinkAdmin/src/main/java/com/iformall/service/WxOpenService.java @@ -0,0 +1,65 @@ +package com.iformall.service; + +import com.iformall.config.RedisProperies; +import com.iformall.config.WechatOpenProperties; +import me.chanjar.weixin.open.api.impl.WxOpenInRedisConfigStorage; +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; + +/** + * @author 007 + */ +@Service +@EnableConfigurationProperties({WechatOpenProperties.class, RedisProperies.class}) +public class WxOpenService extends WxOpenServiceImpl { + private Logger logger = LoggerFactory.getLogger(getClass()); + @Autowired + private WechatOpenProperties wechatOpenProperties; + @Autowired + private RedisProperies redisProperies; + private static JedisPool pool; + private WxOpenMessageRouter wxOpenMessageRouter; + + @PostConstruct + public void init() { + WxOpenInRedisConfigStorage inRedisConfigStorage = new WxOpenInRedisConfigStorage(getJedisPool()); + inRedisConfigStorage.setComponentAppId(wechatOpenProperties.getComponentAppId()); + inRedisConfigStorage.setComponentAppSecret(wechatOpenProperties.getComponentSecret()); + inRedisConfigStorage.setComponentToken(wechatOpenProperties.getComponentToken()); + inRedisConfigStorage.setComponentAesKey(wechatOpenProperties.getComponentAesKey()); + setWxOpenConfigStorage(inRedisConfigStorage); + wxOpenMessageRouter = new WxOpenMessageRouter(this); + wxOpenMessageRouter.rule().handler((wxMpXmlMessage, map, wxMpService, wxSessionManager) -> { + logger.info("\n接收到 {} 公众号请求消息,内容:{}", wxMpService.getWxMpConfigStorage().getAppId(), wxMpXmlMessage); + return null; + }).next(); + } + + public WxOpenMessageRouter getWxOpenMessageRouter() { + return wxOpenMessageRouter; + } + + private JedisPool getJedisPool() { + if (pool == null) { + synchronized (WxOpenService.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; + } +} diff --git a/mallinkAdmin/src/main/resources/application.yml b/mallinkAdmin/src/main/resources/application.yml index 8b2dca921..2587cdd4c 100644 --- a/mallinkAdmin/src/main/resources/application.yml +++ b/mallinkAdmin/src/main/resources/application.yml @@ -67,6 +67,27 @@ aws: access: ENC(3gx5ghDFBqGrEhO3Wf8aYmXsnwHO7Cj3HNKJGOeUj0o=) secret: ENC(HVKIJwCJKVXLlUpGlQPwNqJOlnpxn4xYuy91SH0seTSm2uAttIQHvA49fXWWax90v5wloIk0QuU=) +wechat: + open: + componentAppId: "wx897e4673286c915d" + componentSecret: "cdfdfda65c45689beb6766c4c427eed2" + componentToken: "formall2018" + componentAesKey: "htKq8EjBMPNndfZQK9JiFojFaqFwFpw42VfeWFtx7HN" + redis: + host: 202.165.179.86 + port: 6379 + password: ENC(aYJ3Wr2UWtkORRQjjrWWpz2ZeTISsHOA) + timeout: 3600 + expire: 1800 #30分钟 + database: 1 + defaultExpiration: 2592000 # 默认生命周期30天 + jedis: + pool: + max-active: 100 + max-idle: 500 + max-wait: -1 + min-idle: 10 + jasypt: encryptor: password: oRqdnDbK5pj3eMmB