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