Browse Source

Merge branch 'master' of lhm/wxservice into master

李慧明 3 years ago
parent
commit
e2f0d91268
72 changed files with 5533 additions and 0 deletions
  1. 39 0
      .gitignore
  2. 35 0
      build.gradle
  3. BIN
      gradle/wrapper/gradle-wrapper.jar
  4. 5 0
      gradle/wrapper/gradle-wrapper.properties
  5. 185 0
      gradlew
  6. 89 0
      gradlew.bat
  7. 1 0
      settings.gradle
  8. 13 0
      src/main/java/com/ematou/wxservice/WechatServiceApplication.java
  9. 54 0
      src/main/java/com/ematou/wxservice/aop/LogAspect.java
  10. 65 0
      src/main/java/com/ematou/wxservice/api/WeChatApi.java
  11. 124 0
      src/main/java/com/ematou/wxservice/api/WeChatApiRestTemplate.java
  12. 14 0
      src/main/java/com/ematou/wxservice/common/constant/ResponseCodeConstant.java
  13. 180 0
      src/main/java/com/ematou/wxservice/common/constant/WeChatConstant.java
  14. 64 0
      src/main/java/com/ematou/wxservice/common/utils/CapturePackageClasses.java
  15. 40 0
      src/main/java/com/ematou/wxservice/common/utils/DateUtil.java
  16. 70 0
      src/main/java/com/ematou/wxservice/common/utils/SignUtil.java
  17. 133 0
      src/main/java/com/ematou/wxservice/common/utils/SmsUtil.java
  18. 74 0
      src/main/java/com/ematou/wxservice/common/utils/StringUtil.java
  19. 92 0
      src/main/java/com/ematou/wxservice/common/utils/XStreamInitializer.java
  20. 100 0
      src/main/java/com/ematou/wxservice/common/utils/XStreamTransformer.java
  21. 71 0
      src/main/java/com/ematou/wxservice/common/web/R.java
  22. 39 0
      src/main/java/com/ematou/wxservice/common/xml/builder/BaseBuilder.java
  23. 38 0
      src/main/java/com/ematou/wxservice/common/xml/builder/NewsBuilder.java
  24. 28 0
      src/main/java/com/ematou/wxservice/common/xml/builder/TextBuilder.java
  25. 17 0
      src/main/java/com/ematou/wxservice/common/xml/converter/XStreamCDataConverter.java
  26. 36 0
      src/main/java/com/ematou/wxservice/config/AppConfig.java
  27. 33 0
      src/main/java/com/ematou/wxservice/config/WeChatGeneralConfig.java
  28. 35 0
      src/main/java/com/ematou/wxservice/controller/UserInfoController.java
  29. 39 0
      src/main/java/com/ematou/wxservice/controller/WeChatController.java
  30. 65 0
      src/main/java/com/ematou/wxservice/controller/WeChatMessageController.java
  31. 47 0
      src/main/java/com/ematou/wxservice/controller/WeChatViewController.java
  32. 248 0
      src/main/java/com/ematou/wxservice/entity/pojo/UserInfo.java
  33. 144 0
      src/main/java/com/ematou/wxservice/entity/vo/AccessToken.java
  34. 51 0
      src/main/java/com/ematou/wxservice/entity/vo/BindingInfo.java
  35. 156 0
      src/main/java/com/ematou/wxservice/entity/vo/TemplateMessage.java
  36. 120 0
      src/main/java/com/ematou/wxservice/interceptor/MybatisInsertAndUpdateInterceptor.java
  37. 23 0
      src/main/java/com/ematou/wxservice/mapper/UserInfoMapper.java
  38. 81 0
      src/main/java/com/ematou/wxservice/mp/handler/WeChatClickEventMessageHandler.java
  39. 22 0
      src/main/java/com/ematou/wxservice/mp/handler/WeChatMessageHandler.java
  40. 26 0
      src/main/java/com/ematou/wxservice/mp/handler/WeChatNewsMessageHandler.java
  41. 36 0
      src/main/java/com/ematou/wxservice/mp/handler/WeChatSubscribeEventHandler.java
  42. 30 0
      src/main/java/com/ematou/wxservice/mp/handler/WeChatTemplateMessageEventHandler.java
  43. 40 0
      src/main/java/com/ematou/wxservice/mp/handler/WeChatTextMessageHandler.java
  44. 26 0
      src/main/java/com/ematou/wxservice/mp/handler/WeChatViewEventMessageHandler.java
  45. 39 0
      src/main/java/com/ematou/wxservice/mp/menu/Button.java
  46. 28 0
      src/main/java/com/ematou/wxservice/mp/menu/ClickButton.java
  47. 94 0
      src/main/java/com/ematou/wxservice/mp/menu/Matchrule.java
  48. 32 0
      src/main/java/com/ematou/wxservice/mp/menu/MenuButton.java
  49. 113 0
      src/main/java/com/ematou/wxservice/mp/menu/MenuButtonManager.java
  50. 50 0
      src/main/java/com/ematou/wxservice/mp/menu/MiniProgramButton.java
  51. 40 0
      src/main/java/com/ematou/wxservice/mp/menu/ViewButton.java
  52. 369 0
      src/main/java/com/ematou/wxservice/mp/message/WeChatMessage.java
  53. 93 0
      src/main/java/com/ematou/wxservice/mp/message/WeChatMpXmlOutMessage.java
  54. 122 0
      src/main/java/com/ematou/wxservice/mp/message/WeChatMpXmlOutNewsMessage.java
  55. 37 0
      src/main/java/com/ematou/wxservice/mp/message/WeChatMpXmlOutTextMessage.java
  56. 50 0
      src/main/java/com/ematou/wxservice/mp/router/WeChatMessageHandlerRouter.java
  57. 38 0
      src/main/java/com/ematou/wxservice/service/UserInfoService.java
  58. 44 0
      src/main/java/com/ematou/wxservice/service/WeChatMessageService.java
  59. 196 0
      src/main/java/com/ematou/wxservice/service/WeChatService.java
  60. 30 0
      src/main/resources/application.yml
  61. 56 0
      src/main/resources/logback.xml
  62. 33 0
      src/main/resources/mybatis/UserInfoMapper.xml
  63. 1 0
      src/main/resources/static/MP_verify_ut2mRugwSXiH7xQy.txt
  64. 303 0
      src/main/resources/static/css/style.css
  65. 338 0
      src/main/resources/static/css/style_return.css
  66. BIN
      src/main/resources/static/images/icon-pitch.png
  67. 151 0
      src/main/resources/static/js/clean.js
  68. 3 0
      src/main/resources/static/js/jquery.min.js
  69. 72 0
      src/main/resources/templates/binding.html
  70. 245 0
      src/main/resources/templates/hello.html
  71. 67 0
      src/main/resources/templates/return.html
  72. 61 0
      src/test/java/com/ematou/wxservice/wechatservice/WechatserviceApplicationTests.java

+ 39 - 0
.gitignore

@@ -0,0 +1,39 @@
+HELP.md
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### gradle ###
+.gradle
+build/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/

+ 35 - 0
build.gradle

@@ -0,0 +1,35 @@
+plugins {
+    id 'org.springframework.boot' version '2.3.10.RELEASE'
+    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
+    id 'java'
+}
+
+group = 'com.ematou.wechatservice'
+version = '0.0.1-SNAPSHOT'
+sourceCompatibility = '1.8'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation 'org.springframework.boot:spring-boot-starter-web'
+    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.4'
+    implementation 'org.springframework.boot:spring-boot-starter-aop'
+    implementation 'org.springframework.boot:spring-boot-starter-logging'
+    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
+    implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.75'
+    implementation group: 'org.apache.httpcomponents', name: 'httpcore', version: '4.4.3'
+    implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.4.1'
+    implementation group: 'org.apache.httpcomponents', name: 'httpmime', version: '4.5.1'
+    implementation group: 'com.yunpian.sdk', name: 'yunpian-java-sdk', version: '1.2.7'
+    implementation group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.15'
+    runtimeOnly 'mysql:mysql-connector-java'
+    testImplementation('org.springframework.boot:spring-boot-starter-test') {
+        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
+    }
+}
+
+test {
+    useJUnitPlatform()
+}

BIN
gradle/wrapper/gradle-wrapper.jar


+ 5 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists

+ 185 - 0
gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 89 - 0
gradlew.bat

@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 1 - 0
settings.gradle

@@ -0,0 +1 @@
+rootProject.name = 'wxservice'

+ 13 - 0
src/main/java/com/ematou/wxservice/WechatServiceApplication.java

@@ -0,0 +1,13 @@
+package com.ematou.wxservice;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class WechatServiceApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(WechatServiceApplication.class, args);
+    }
+
+}

+ 54 - 0
src/main/java/com/ematou/wxservice/aop/LogAspect.java

@@ -0,0 +1,54 @@
+package com.ematou.wxservice.aop;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-04-30 16:30
+ */
+@Aspect
+@Component
+public class LogAspect {
+
+    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
+
+    // TODO
+    @Pointcut("execution(* com.ematou.wxservice.*.*Service.*(..)) ||" +
+              "execution(* com.ematou.wxservice.*.*RestTemplate.*(..)) || " +
+              "execution(* com.ematou.wxservice.*.*Handler.*(..))")
+    public void targetMethod() {}
+
+    @Around("targetMethod()")
+    public Object around(ProceedingJoinPoint joinPoint) {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature ms = (MethodSignature) signature;
+        String targetMethodName = ms.getMethod().getName();
+        String className = joinPoint.getTarget().getClass().getName();
+
+        ObjectMapper objectMapper = new ObjectMapper();
+
+        Object proceed = null;
+        try {
+            logger.info("----------------方法: " + className + "." + targetMethodName + "的参数为: " + objectMapper.writeValueAsString(joinPoint.getArgs()) + "----------------");
+            long start = System.currentTimeMillis();
+            proceed = joinPoint.proceed();
+            long end = System.currentTimeMillis();
+            logger.info("----------------方法: " + className + "." + targetMethodName + "执行的返回值为: " + proceed + "----------------");
+            logger.info("----------------方法: " + className + "." + targetMethodName + "执行的时间为: " + (end-start) + "毫秒----------------");
+        } catch (Throwable throwable) {
+            logger.error("----------------方法: " + className + "." + targetMethodName + "执行出现异常,错误信息为:" + throwable + "----------------");
+        }
+        return proceed;
+    }
+
+}

+ 65 - 0
src/main/java/com/ematou/wxservice/api/WeChatApi.java

@@ -0,0 +1,65 @@
+package com.ematou.wxservice.api;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-04-30 11:54
+ */
+public enum WeChatApi {
+
+    /**
+     * 微信基础服务获取AccessToken
+     */
+    GET_TOKEN("http://61.144.244.114:4040/wxbase/token", "GET"),
+    /**
+     * 微信基础服务生成AccessToken
+     */
+    GENERAL_TOKEN("http://61.144.244.114:4040/wxbase/generate/token", "GET"),
+    /**
+     * 获取用户信息,只能获取到openId,如需要使用unionId,需要将公众号绑定到微信开放平台帐号
+     */
+    GET_USER_INFO("https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN", "GET"),
+    /**
+     * 获取微信OAuth2的AccessToken
+     */
+    WECHAT_OAUTH_TOKEN("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", "GET"),
+    /**
+     * 创建菜单
+     * https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
+     */
+    CREATE_MENU("https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s", "POST"),
+    /**
+     * 获取模板消息ID,请求体参考:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html
+     */
+    GET_TEMPLATE_MSGID("https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token=%s", "POST"),
+    /**
+     * 发送模板消息,请求体参考:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html
+     * 发送成功和失败都有事件的推送
+     */
+    SEND_TEMPLATE_MSG("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s", "POST");
+
+
+    private String url;
+    private String method;
+
+    WeChatApi(String url, String method) {
+        this.url = url;
+        this.method = method;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+}

+ 124 - 0
src/main/java/com/ematou/wxservice/api/WeChatApiRestTemplate.java

@@ -0,0 +1,124 @@
+package com.ematou.wxservice.api;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.ematou.wxservice.common.constant.WeChatConstant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Arrays;
+
+/**
+ * 微信API请求工具类
+ * @author lhm
+ * @version 1.0
+ * 2021-05-13 11:10
+ */
+@Component
+public class WeChatApiRestTemplate {
+
+    private static final Logger logger = LoggerFactory.getLogger(WeChatApiRestTemplate.class);
+
+    @Autowired
+    RestTemplate restTemplate;
+
+    /**
+     * 请求微信API,返回体带有errcode和errmsg字段的调这个方法
+     * @param url       url地址
+     * @param params    参数
+     * @return          响应信息
+     */
+    public String getForRest(String url, Object...params) {
+        String res;
+        Integer errcode;
+        String errmsg;
+        try {
+            res = restTemplate.getForObject(url, String.class, params);
+
+            JSONObject response = JSON.parseObject(res);
+            errcode = response.getObject(WeChatConstant.RESPONSE_ERROR_CODE, Integer.class);
+            errmsg = response.getObject(WeChatConstant.RESPONSE_ERROR_MSG, String.class);
+            if (errcode != 0 || !errmsg.equalsIgnoreCase(WeChatConstant.RESPONSE_OK)) {
+                logger.error("请求微信API失败,请求方法:GET\nurl:\n" + url + "\n请求参数:" + Arrays.toString(params) + "\nerror code:" + errcode + "\nerror message:" + errmsg);
+            }
+        } catch (Exception e) {
+            logger.error("请求微信API失败,请求方法:GET\nurl:\n" + url + "\n请求参数:" + Arrays.toString(params) + "\n异常信息:" + e.getMessage());
+            throw new RuntimeException("请求微信API失败,请求方法:GET\nurl:\n" + url + "\n请求参数:" + Arrays.toString(params) + "\n异常信息:" + e.getMessage());
+        }
+        return res;
+    }
+
+    /**
+     * 返回体不带有errcode和errmsg字段
+     * @param url       url地址
+     * @param params    参数
+     * @return          响应信息
+     */
+    public String getForOther(String url, Object...params) {
+        String res;
+        try {
+            res = restTemplate.getForObject(url, String.class, params);
+        } catch (Exception e) {
+            logger.error("请求微信API失败,请求方法:GET\nurl:\n" + url + "\n请求参数:" + Arrays.toString(params) + "\n异常信息:" + e.getMessage());
+            throw new RuntimeException("请求微信API失败,请求方法:GET\nurl:\n" + url + "\n请求参数:" + Arrays.toString(params) + "\n异常信息:" + e.getMessage());
+        }
+        return res;
+    }
+
+    /**
+     * 请求微信API,返回体带有errcode和errmsg字段的调这个方法
+     * @param url       url地址
+     * @param body      请求体
+     * @return          响应信息
+     */
+    public String postForRest(String url, Object body) {
+        String res;
+        Integer errcode;
+        String errmsg;
+        try {
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
+            HttpEntity<Object> request = new HttpEntity<>(body, headers);
+            res = restTemplate.postForObject(url, request, String.class);
+
+            JSONObject response = JSON.parseObject(res);
+            errcode = response.getObject(WeChatConstant.RESPONSE_ERROR_CODE, Integer.class);
+            errmsg = response.getObject(WeChatConstant.RESPONSE_ERROR_MSG, String.class);
+            if (errcode != 0 || !errmsg.equalsIgnoreCase(WeChatConstant.RESPONSE_OK)) {
+                logger.error("请求微信API失败,请求方法:POST\nurl:\n" + url + "\n请求体:" + body + "\nerror code:" + errcode + "\nerror message:" + errmsg);
+            }
+        } catch (Exception e) {
+            logger.error("请求微信API失败,请求方法:POST\nurl:\n" + url + "\n请求体:" + body + "\n异常信息:" + e.getMessage());
+            throw new RuntimeException("请求微信API失败,请求方法:POST\nurl:\n" + url + "\n请求体:" + body + "\n异常信息:" + e.getMessage());
+        }
+        return res;
+    }
+
+
+    /**
+     * 返回体不带有errcode和errmsg字段
+     * @param url       url地址
+     * @param body      请求体
+     * @return          响应信息
+     */
+    public String postForOther(String url, Object body) {
+        String res;
+        try {
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
+            HttpEntity<Object> request = new HttpEntity<>(body, headers);
+            res = restTemplate.postForObject(url, request, String.class);
+        } catch (Exception e) {
+            logger.error("请求微信API失败,请求方法:POST\nurl:\n" + url + "\n请求体:" + body + "\n异常信息:" + e.getMessage());
+            throw new RuntimeException("请求微信API失败,请求方法:POST\nurl:\n" + url + "\n请求体:" + body + "\n异常信息:" + e.getMessage());
+        }
+        return res;
+    }
+
+}

+ 14 - 0
src/main/java/com/ematou/wxservice/common/constant/ResponseCodeConstant.java

@@ -0,0 +1,14 @@
+package com.ematou.wxservice.common.constant;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-04-30 14:39
+ */
+public class ResponseCodeConstant {
+
+    public static final int code_Success = 0;
+
+    public static final int code_100 = 100;
+
+}

+ 180 - 0
src/main/java/com/ematou/wxservice/common/constant/WeChatConstant.java

@@ -0,0 +1,180 @@
+package com.ematou.wxservice.common.constant;
+
+/**
+ * 微信开发中需要用到的常量
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 15:10
+ */
+public class WeChatConstant {
+
+    public static final String RESPONSE_OK = "ok";
+    public static final String RESPONSE_ERROR_CODE = "errcode";
+    public static final String RESPONSE_ERROR_MSG = "errmsg";
+
+
+    /**
+     * 自定义的模板的编号,可通过模板编号获取模板消息id
+     */
+    public static class TemplateMsgId {
+        /**
+         * 取件通知模板
+         */
+        public static final String TAKE_PARCEL_CODE_TEMPLATE_MSG_ID = "69SkKm9Hqw9MgHzfkazzCf5wiWXOStcs10WUSX1LwdE";
+    }
+
+    /**
+     * 自定义菜单事件的key
+     */
+    public static class CustomEventKey {
+        public static final String CALL_COMPANY = "call_company_button";        // 联系我们
+        public static final String MY_TAKE_PARCEL_CODE = "my_code_button";      // 我的取件码
+        public static final String MY_HISTORY_RECORD = "my_history_button";     // 取件历史
+        public static final String POLICY_SUPPORT = "policy_support_button";    // 政策支持
+    }
+
+    /**
+     * 消息类型
+     */
+    public static class XmlMsgType {
+        public static final String TEXT = "text";
+        public static final String IMAGE = "image";
+        public static final String VOICE = "voice";
+        public static final String SHORTVIDEO = "shortvideo";
+        public static final String VIDEO = "video";
+        public static final String NEWS = "news";
+        public static final String MUSIC = "music";
+        public static final String LOCATION = "location";
+        public static final String LINK = "link";
+        public static final String EVENT = "event";
+        public static final String DEVICE_TEXT = "device_text";
+        public static final String DEVICE_EVENT = "device_event";
+        public static final String DEVICE_STATUS = "device_status";
+        public static final String HARDWARE = "hardware";
+        public static final String TRANSFER_CUSTOMER_SERVICE = "transfer_customer_service";
+    }
+
+    /**
+     * 微信端推送过来的事件类型.
+     */
+    public static class EventType {
+        public static final String SUBSCRIBE = "subscribe";
+        public static final String UNSUBSCRIBE = "unsubscribe";
+        public static final String SCAN = "SCAN";
+        public static final String LOCATION = "LOCATION";
+        public static final String CLICK = "CLICK";
+        public static final String VIEW = "VIEW";
+        public static final String MASS_SEND_JOB_FINISH = "MASSSENDJOBFINISH";
+        /**
+         * 扫码推事件的事件推送
+         */
+        public static final String SCANCODE_PUSH = "scancode_push";
+        /**
+         * 扫码推事件且弹出“消息接收中”提示框的事件推送.
+         */
+        public static final String SCANCODE_WAITMSG = "scancode_waitmsg";
+        /**
+         * 弹出系统拍照发图的事件推送.
+         */
+        public static final String PIC_SYSPHOTO = "pic_sysphoto";
+        /**
+         * 弹出拍照或者相册发图的事件推送.
+         */
+        public static final String PIC_PHOTO_OR_ALBUM = "pic_photo_or_album";
+        /**
+         * 弹出微信相册发图器的事件推送.
+         */
+        public static final String PIC_WEIXIN = "pic_weixin";
+        /**
+         * 弹出地理位置选择器的事件推送.
+         */
+        public static final String LOCATION_SELECT = "location_select";
+
+        public static final String TEMPLATE_SEND_JOB_FINISH = "TEMPLATESENDJOBFINISH";
+        /**
+         * 微信小店 订单付款通知.
+         */
+        public static final String MERCHANT_ORDER = "merchant_order";
+
+        /**
+         * 卡券事件:卡券通过审核
+         */
+        public static final String CARD_PASS_CHECK = "card_pass_check";
+
+        /**
+         * 卡券事件:卡券未通过审核
+         */
+        public static final String CARD_NOT_PASS_CHECK = "card_not_pass_check";
+
+        /**
+         * 卡券事件:用户领取卡券
+         */
+        public static final String CARD_USER_GET_CARD = "user_get_card";
+
+        /**
+         * 卡券事件:用户转赠卡券
+         */
+        public static final String CARD_USER_GIFTING_CARD = "user_gifting_card";
+
+
+        /**
+         * 卡券事件:用户核销卡券
+         */
+        public static final String CARD_USER_CONSUME_CARD = "user_consume_card";
+
+
+        /**
+         * 卡券事件:用户通过卡券的微信买单完成时推送
+         */
+        public static final String CARD_USER_PAY_FROM_PAY_CELL = "user_pay_from_pay_cell";
+
+
+        /**
+         * 卡券事件:用户提交会员卡开卡信息
+         */
+        public static final String CARD_SUBMIT_MEMBERCARD_USER_INFO = "submit_membercard_user_info";
+
+        /**
+         * 卡券事件:用户打开查看卡券
+         */
+        public static final String CARD_USER_VIEW_CARD = "user_view_card";
+
+        /**
+         * 卡券事件:用户删除卡券
+         */
+        public static final String CARD_USER_DEL_CARD = "user_del_card";
+
+        /**
+         * 卡券事件:用户在卡券里点击查看公众号进入会话时(需要用户已经关注公众号)
+         */
+        public static final String CARD_USER_ENTER_SESSION_FROM_CARD = "user_enter_session_from_card";
+
+        /**
+         * 卡券事件:当用户的会员卡积分余额发生变动时
+         */
+        public static final String CARD_UPDATE_MEMBER_CARD = "update_member_card";
+
+        /**
+         * 卡券事件:当某个card_id的初始库存数大于200且当前库存小于等于100时,用户尝试领券会触发发送事件给商户,事件每隔12h发送一次
+         */
+        public static final String CARD_SKU_REMIND = "card_sku_remind";
+
+        /**
+         * 卡券事件:当商户朋友的券券点发生变动时
+         */
+        public static final String CARD_PAY_ORDER = "card_pay_order";
+
+        /**
+         * 小程序审核事件:审核通过
+         */
+        public static final String WEAPP_AUDIT_SUCCESS = "weapp_audit_success";
+
+        /**
+         * 小程序审核事件:审核不通过
+         */
+        public static final String WEAPP_AUDIT_FAIL = "weapp_audit_fail";
+
+    }
+
+
+}

+ 64 - 0
src/main/java/com/ematou/wxservice/common/utils/CapturePackageClasses.java

@@ -0,0 +1,64 @@
+package com.ematou.wxservice.common.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-04-30 15:54
+ */
+@Component
+public class CapturePackageClasses implements ResourceLoaderAware {
+
+    private ResourceLoader resourceLoader;
+
+    private static final Logger logger = LoggerFactory.getLogger(CapturePackageClasses.class);
+
+    public List<Class<?>> getClass(String packageName) {
+
+        List<Class<?>> classes = new ArrayList<>();
+
+        packageName = packageName.replace('.' ,'/');
+
+        ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
+        MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resourceLoader);
+        Resource[] resources = new Resource[0];
+        try {
+            resources = resolver.getResources("classpath*:" + packageName + "/**/*.class");
+        } catch (IOException e) {
+            logger.error("包名格式不对!");
+        }
+
+        for (Resource resource : resources) {
+            MetadataReader reader = null;
+            try {
+                reader = metaReader.getMetadataReader(resource);
+            } catch (IOException e) {
+                logger.error(e.getMessage());
+            }
+            assert reader != null : "reader为null!";
+            classes.add(reader.getClass());
+        }
+
+        return classes;
+    }
+
+    @Override
+    public void setResourceLoader(ResourceLoader resourceLoader) {
+        this.resourceLoader = resourceLoader;
+    }
+}

+ 40 - 0
src/main/java/com/ematou/wxservice/common/utils/DateUtil.java

@@ -0,0 +1,40 @@
+package com.ematou.wxservice.common.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-04-30 15:23
+ */
+public class DateUtil {
+
+    /**
+     * 时间类型转字符串
+     * @param date 时间类型
+     * @return 时间字符串
+     */
+    public static String formatDate(Date date){
+
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+        return sdf.format(date);
+
+    }
+
+    /**
+     * 将时间字符串转为时间类型
+     * @param str 时间字符串
+     * @return 时间类型
+     */
+    public static Date parseString(String str) throws ParseException {
+
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+        return sdf.parse(str);
+
+    }
+
+}

+ 70 - 0
src/main/java/com/ematou/wxservice/common/utils/SignUtil.java

@@ -0,0 +1,70 @@
+package com.ematou.wxservice.common.utils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/**
+ * 根据服务器配置的令牌校验签名
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 11:49
+ */
+public class SignUtil {
+    private static final String token = "rv978qwd";// 服务器配置中的令牌
+
+    /**
+     * 校验签名
+     * @param signature  签名
+     * @param timestamp 时间戳
+     * @param nonce 随机数
+     * @return true 成功,false 失败
+     */
+    public static boolean checkSignature(String signature,String timestamp, String nonce){
+
+        String checktext = null;
+        if(null != signature){
+            // 对Token,timestamp nonce 按字典排序
+            String [] paramArr = new String[] {token, timestamp, nonce};
+            Arrays.sort(paramArr);
+            // 将排序后的结果拼成一个字符串
+            String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);
+            try {
+                MessageDigest md = MessageDigest.getInstance("SHA-1");
+                // 对接后的字符串进行sha1加密
+                byte[] digest = md.digest(content.toString().getBytes());
+                checktext = byteToStr(digest);
+            } catch (NoSuchAlgorithmException e) {
+                e.printStackTrace();
+            }
+        }
+        // 将加密后的字符串与signature进行对比
+        return checktext != null && checktext.matches(signature.toUpperCase());
+    }
+
+    /**
+     * 将字节数组转化为16进制字符串
+     * @return 字符串
+     */
+    private static String byteToStr(byte[] byteArrays) {
+        StringBuilder str= new StringBuilder();
+        for (byte byteArray : byteArrays) {
+            str.append(byteToHexStr(byteArray));
+        }
+        return str.toString();
+    }
+
+    /**
+     * 将字节转化为十六进制字符串
+     * @param myByte 字节
+     * @return 字符串
+     */
+    private static String byteToHexStr(byte myByte) {
+
+        char[] Digit = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+        char[] tempArr = new char[2];
+        tempArr[0] = Digit[(myByte >>> 4)&0X0F];
+        tempArr[1] = Digit[myByte & 0x0F];
+        return new String(tempArr);
+    }
+}

+ 133 - 0
src/main/java/com/ematou/wxservice/common/utils/SmsUtil.java

@@ -0,0 +1,133 @@
+package com.ematou.wxservice.common.utils;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author huangyaqin
+ * @version 1.0
+ * 2018-10-16 11:27
+ */
+public class SmsUtil {
+    //编码格式。发送编码格式统一用UTF-8
+    private static String ENCODING = "UTF-8";
+
+
+    // 请求地址
+    private static String SMS_URL = "https://sms.yunpian.com/v2/sms/single_send.json";
+
+    // 中网cw apikey
+    private static String API_KEY = "1dd75986321871d706334e60b89c4021";
+
+
+
+    /**
+     * 发送短信消息
+     * 方法说明
+     * @Discription:扩展说明
+     * @param phones
+     * @param text
+     * @return
+     * @return String
+     */
+    @SuppressWarnings("deprecation")
+    public static String sendMsg(String phones,String text) {
+        System.out.println(text);
+        String apikey = API_KEY;
+        String url = SMS_URL;
+        if(apikey != null && url != null){
+            Map<String, String> params = new HashMap<String, String>();
+            params.put("apikey", apikey);
+            //手机号码,多个号码使用英文逗号进行分割
+            params.put("mobile", phones);
+            //将短信内容进行URLEncoder编码
+            params.put("text", text);
+
+            return post(url, params);
+        }else{
+            return "";
+        }
+    }
+
+    /**
+     * 随机生成6位随机验证码
+     * 方法说明
+     * @Discription:扩展说明
+     * @return
+     * @return String
+     */
+    public static String createRandomVcode(){
+        //验证码
+        String vcode = "";
+        for (int i = 0; i < 6; i++) {
+            vcode = vcode + (int)(Math.random() * 9);
+        }
+        return vcode;
+    }
+
+    /**
+     * 基于HttpClient 4.3的通用POST方法
+     *
+     * @param url       提交的URL
+     * @param paramsMap 提交<参数,值>Map
+     * @return 提交响应
+     */
+    public static String post(String url, Map < String, String > paramsMap) {
+        CloseableHttpClient client = HttpClients.createDefault();
+        String responseText = "";
+        CloseableHttpResponse response = null;
+        try {
+            HttpPost method = new HttpPost(url);
+            if (paramsMap != null) {
+                List<NameValuePair> paramList = new ArrayList<NameValuePair>();
+                for (Map.Entry < String, String > param: paramsMap.entrySet()) {
+                    NameValuePair pair = new BasicNameValuePair(param.getKey(),
+                            param.getValue());
+                    paramList.add(pair);
+                }
+                method.setEntity(new UrlEncodedFormEntity(paramList,
+                        ENCODING));
+            }
+            response = client.execute(method);
+            HttpEntity entity = response.getEntity();
+            if (entity != null) {
+                responseText = EntityUtils.toString(entity, ENCODING);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                response.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return responseText;
+    }
+
+
+    /**
+     * 测试
+     * 方法说明
+     * @Discription:扩展说明
+     * @param args
+     * @return void
+     */
+    public static void main(String[] args) {
+//      System.out.println(SendMsgUtil.createRandomVcode());
+//      System.out.println("&ecb=12".substring(1));
+        System.out.println(sendMsg("18316287403", "【CW惠州门店】惠州港惠店 尊敬的CW会员,您购买的订单56151,已清关成功,感谢您的耐心等待。"));
+    }
+}

+ 74 - 0
src/main/java/com/ematou/wxservice/common/utils/StringUtil.java

@@ -0,0 +1,74 @@
+package com.ematou.wxservice.common.utils;
+
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 10:35
+ */
+public class StringUtil {
+
+    public static String encodeUrl(String url){
+
+        return getRealUrl(url);
+
+    }
+
+
+    private static String getRealUrl(String str) {
+        try {
+            int index = str.indexOf("?");
+            if (index < 0) return str;
+            String query = str.substring(0, index);
+            String params = str.substring(index + 1);
+            Map map = getArgs(params);
+            String encodeParams = transMapToString(map);
+            return query + "?" + encodeParams;
+        } catch (Exception ex) {
+            System.out.println(ex.getMessage());
+        }
+        return "";
+    }
+
+    //将url参数格式转化为map
+    private static Map getArgs(String params) throws Exception {
+        Map map = new HashMap();
+        String[] pairs = params.split("&");
+        for (int i = 0; i < pairs.length; i++) {
+            int pos = pairs[i].indexOf("=");
+            if (pos == -1) continue;
+            String argname = pairs[i].substring(0, pos);
+            String value = pairs[i].substring(pos + 1);
+            value = URLEncoder.encode(value, "utf-8");
+            map.put(argname, value);
+        }
+        return map;
+    }
+
+    //将map转化为指定的String类型
+    private static String transMapToString(Map map) {
+        java.util.Map.Entry entry;
+        StringBuffer sb = new StringBuffer();
+        for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext(); ) {
+            entry = (java.util.Map.Entry) iterator.next();
+            sb.append(entry.getKey().toString()).append("=").append(null == entry.getValue() ? "" :
+                    entry.getValue().toString()).append(iterator.hasNext() ? "&" : "");
+        }
+        return sb.toString();
+    }
+
+    //将String类型按一定规则转换为Map
+    private static Map transStringToMap(String mapString) {
+        Map map = new HashMap();
+        java.util.StringTokenizer items;
+        for (StringTokenizer entrys = new StringTokenizer(mapString, "&"); entrys.hasMoreTokens();
+             map.put(items.nextToken(), items.hasMoreTokens() ? ((Object) (items.nextToken())) : null))
+            items = new StringTokenizer(entrys.nextToken(), "=");
+        return map;
+    }
+}

+ 92 - 0
src/main/java/com/ematou/wxservice/common/utils/XStreamInitializer.java

@@ -0,0 +1,92 @@
+package com.ematou.wxservice.common.utils;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.converters.basic.*;
+import com.thoughtworks.xstream.converters.collections.CollectionConverter;
+import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
+import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
+import com.thoughtworks.xstream.core.util.QuickWriter;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
+import com.thoughtworks.xstream.io.xml.XppDriver;
+import com.thoughtworks.xstream.security.NoTypePermission;
+import com.thoughtworks.xstream.security.WildcardTypePermission;
+
+import java.io.Writer;
+
+/**
+ * XStream初始化器
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 16:16
+ */
+public class XStreamInitializer {
+
+    private static final XppDriver XPP_DRIVER = new XppDriver() {
+        @Override
+        public HierarchicalStreamWriter createWriter(Writer out) {
+            return new PrettyPrintWriter(out, getNameCoder()) {
+                private static final String PREFIX_CDATA = "<![CDATA[";
+                private static final String SUFFIX_CDATA = "]]>";
+                private static final String PREFIX_MEDIA_ID = "<MediaId>";
+                private static final String SUFFIX_MEDIA_ID = "</MediaId>";
+
+                @Override
+                protected void writeText(QuickWriter writer, String text) {
+                    if (text.startsWith(PREFIX_CDATA) && text.endsWith(SUFFIX_CDATA)) {
+                        writer.write(text);
+                    } else if (text.startsWith(PREFIX_MEDIA_ID) && text.endsWith(SUFFIX_MEDIA_ID)) {
+                        writer.write(text);
+                    } else {
+                        super.writeText(writer, text);
+                    }
+
+                }
+
+                @Override
+                public String encodeNode(String name) {
+                    //防止将_转换成__
+                    return name;
+                }
+            };
+        }
+    };
+
+    /**
+     * Gets instance.
+     *
+     * @return the instance
+     */
+    public static XStream getInstance() {
+        XStream xstream = new XStream(new PureJavaReflectionProvider(), XPP_DRIVER) {
+            // only register the converters we need; other converters generate a private access warning in the console on Java9+...
+            @Override
+            protected void setupConverters() {
+                registerConverter(new NullConverter(), PRIORITY_VERY_HIGH);
+                registerConverter(new IntConverter(), PRIORITY_NORMAL);
+                registerConverter(new FloatConverter(), PRIORITY_NORMAL);
+                registerConverter(new DoubleConverter(), PRIORITY_NORMAL);
+                registerConverter(new LongConverter(), PRIORITY_NORMAL);
+                registerConverter(new ShortConverter(), PRIORITY_NORMAL);
+                registerConverter(new BooleanConverter(), PRIORITY_NORMAL);
+                registerConverter(new ByteConverter(), PRIORITY_NORMAL);
+                registerConverter(new StringConverter(), PRIORITY_NORMAL);
+                registerConverter(new DateConverter(), PRIORITY_NORMAL);
+                registerConverter(new CollectionConverter(getMapper()), PRIORITY_NORMAL);
+                registerConverter(new ReflectionConverter(getMapper(), getReflectionProvider()), PRIORITY_VERY_LOW);
+            }
+        };
+        xstream.ignoreUnknownElements();
+        xstream.setMode(XStream.NO_REFERENCES);
+        XStream.setupDefaultSecurity(xstream);
+        xstream.autodetectAnnotations(true);
+
+        // 通过限制哪些类可以被XStream加载来设置适当的安全性
+        xstream.addPermission(NoTypePermission.NONE);
+        xstream.addPermission(new WildcardTypePermission(new String[]{
+                "com.ematou.wxservice.entity.dto.*", "com.ematou.wxservice.mp.message.xml.*"
+        }));
+        xstream.setClassLoader(Thread.currentThread().getContextClassLoader());
+        return xstream;
+    }
+}

+ 100 - 0
src/main/java/com/ematou/wxservice/common/utils/XStreamTransformer.java

@@ -0,0 +1,100 @@
+package com.ematou.wxservice.common.utils;
+
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutNewsMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutTextMessage;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.security.AnyTypePermission;
+
+import java.io.InputStream;
+import java.util.*;
+
+/**
+ *  @author lhm
+ * @version 1.0
+ * 2021-05-11 16:14
+ */
+public class XStreamTransformer {
+
+    private static final Map<Class<?>, XStream> CLASS_2_XSTREAM_INSTANCE = new HashMap<>();
+
+    static {
+        // TODO 每新增一个响应方式,都需要注册
+        registerClass(WeChatMessage.class);
+        registerClass(WeChatMpXmlOutMessage.class);
+        registerClass(WeChatMpXmlOutTextMessage.class);
+        registerClass(WeChatMpXmlOutNewsMessage.class);
+    }
+
+    /**
+     * xml -> pojo.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T fromXml(Class<T> clazz, String xml) {
+        T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(xml);
+        return object;
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T fromXml(Class<T> clazz, InputStream is) {
+        T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is);
+        return object;
+    }
+
+    /**
+     * pojo -> xml.
+     */
+    public static <T> String toXml(Class<T> clazz, T object) {
+        return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object);
+    }
+
+    /**
+     * 注册扩展消息的解析器.
+     *
+     * @param clz     类型
+     * @param xStream xml解析器
+     */
+    public 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.addPermission(AnyTypePermission.ANY);
+        xstream.processAnnotations(clz);
+        xstream.processAnnotations(getInnerClasses(clz));
+        if (clz.equals(WeChatMessage.class)) {
+            // 操蛋的微信,模板消息推送成功的消息是MsgID,其他消息推送过来是MsgId
+            xstream.aliasField("MsgID", WeChatMessage.class, "msgId");
+        }
+
+        register(clz, xstream);
+    }
+
+    private static Class<?>[] getInnerClasses(Class<?> clz) {
+        Class<?>[] innerClasses = clz.getClasses();
+        if (innerClasses == null) {
+            return null;
+        }
+
+        List<Class<?>> 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]);
+    }
+}

+ 71 - 0
src/main/java/com/ematou/wxservice/common/web/R.java

@@ -0,0 +1,71 @@
+package com.ematou.wxservice.common.web;
+
+import com.ematou.wxservice.common.constant.ResponseCodeConstant;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-04-30 14:25
+ */
+public class R<T> {
+
+    private int code;
+
+    private String message;
+
+    private T data;
+
+    public R() {
+    }
+
+    public R(int code, String message, T data) {
+        this.code = code;
+        this.message = message;
+        this.data = data;
+    }
+
+    public R<T> success(T data) {
+        this.setCode(ResponseCodeConstant.code_Success);
+        this.setMessage("success");
+        this.setData(data);
+        return this;
+    }
+
+    public R<T> success() {
+        this.setCode(ResponseCodeConstant.code_Success);
+        this.setMessage("success");
+        this.setData(null);
+        return this;
+    }
+
+    public R<?> error(String message) {
+        this.setCode(ResponseCodeConstant.code_100);
+        this.setMessage(message);
+        this.setData(null);
+        return this;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+}

+ 39 - 0
src/main/java/com/ematou/wxservice/common/xml/builder/BaseBuilder.java

@@ -0,0 +1,39 @@
+package com.ematou.wxservice.common.xml.builder;
+
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+
+/**
+ * 构建消息基类
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 15:01
+ */
+public abstract class BaseBuilder<BuilderType, ValueType> {
+
+    protected String toUserName;
+
+    protected String fromUserName;
+
+    @SuppressWarnings("unchecked")
+    public BuilderType toUser(String touser) {
+        this.toUserName = touser;
+        return (BuilderType) this;
+    }
+
+    @SuppressWarnings("unchecked")
+    public BuilderType fromUser(String fromusername) {
+        this.fromUserName = fromusername;
+        return (BuilderType) this;
+    }
+
+    public abstract ValueType build();
+
+    public void setCommon(WeChatMpXmlOutMessage m) {
+        m.setToUserName(this.toUserName);
+        m.setFromUserName(this.fromUserName);
+        m.setCreateTime(System.currentTimeMillis() / 1000L);
+    }
+
+
+
+}

+ 38 - 0
src/main/java/com/ematou/wxservice/common/xml/builder/NewsBuilder.java

@@ -0,0 +1,38 @@
+package com.ematou.wxservice.common.xml.builder;
+
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutNewsMessage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-12 11:52
+ */
+public class NewsBuilder extends BaseBuilder<NewsBuilder, WeChatMpXmlOutNewsMessage> {
+
+    private List<WeChatMpXmlOutNewsMessage.Item> articles = new ArrayList<>();
+
+    public NewsBuilder addArticle(WeChatMpXmlOutNewsMessage.Item... items) {
+        Collections.addAll(this.articles, items);
+        return this;
+    }
+
+    public NewsBuilder articles(List<WeChatMpXmlOutNewsMessage.Item> articles){
+        this.articles = articles;
+        return this;
+    }
+
+    @Override
+    public WeChatMpXmlOutNewsMessage build() {
+        WeChatMpXmlOutNewsMessage m = new WeChatMpXmlOutNewsMessage();
+        for (WeChatMpXmlOutNewsMessage.Item item : this.articles) {
+            m.addArticle(item);
+        }
+        setCommon(m);
+        return m;
+    }
+
+}

+ 28 - 0
src/main/java/com/ematou/wxservice/common/xml/builder/TextBuilder.java

@@ -0,0 +1,28 @@
+package com.ematou.wxservice.common.xml.builder;
+
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutTextMessage;
+
+/**
+ * 构建文本消息
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 15:03
+ */
+public class TextBuilder extends BaseBuilder<TextBuilder, WeChatMpXmlOutTextMessage> {
+
+    private String content;
+
+    public TextBuilder content(String content) {
+        this.content = content;
+        return this;
+    }
+
+    @Override
+    public WeChatMpXmlOutTextMessage build() {
+        WeChatMpXmlOutTextMessage weChatMpXmlOutTextMessage = new WeChatMpXmlOutTextMessage();
+        setCommon(weChatMpXmlOutTextMessage);
+        weChatMpXmlOutTextMessage.setContent(this.content);
+        return weChatMpXmlOutTextMessage;
+    }
+}

+ 17 - 0
src/main/java/com/ematou/wxservice/common/xml/converter/XStreamCDataConverter.java

@@ -0,0 +1,17 @@
+package com.ematou.wxservice.common.xml.converter;
+
+import com.thoughtworks.xstream.converters.basic.StringConverter;
+
+/**
+ * CDATA转换器,加上CDATA标签
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 14:58
+ */
+public class XStreamCDataConverter extends StringConverter {
+
+    @Override
+    public String toString(Object obj) {
+        return "<![CDATA[" + super.toString(obj) + "]]>";
+    }
+}

+ 36 - 0
src/main/java/com/ematou/wxservice/config/AppConfig.java

@@ -0,0 +1,36 @@
+package com.ematou.wxservice.config;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-04-30 14:02
+ */
+@Configuration
+@EnableAspectJAutoProxy
+@MapperScan("com.ematou.wxservice.mapper")
+@ComponentScan("com.ematou.wxservice.*")
+public class AppConfig {
+
+    @Bean
+    public WeChatGeneralConfig wxGeneralConfig() {
+        return new WeChatGeneralConfig();
+    }
+
+    @Bean
+    public RestTemplate restTemplate() {
+        // 可发送HTTPS请求的RestTemplate
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        factory.setReadTimeout(60000);
+        factory.setConnectTimeout(15000);
+        return new RestTemplate(factory);
+    }
+
+}

+ 33 - 0
src/main/java/com/ematou/wxservice/config/WeChatGeneralConfig.java

@@ -0,0 +1,33 @@
+package com.ematou.wxservice.config;
+
+import org.springframework.beans.factory.annotation.Value;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-04-30 11:22
+ */
+public class WeChatGeneralConfig {
+
+    @Value("${wx.general.appId}")
+    private String appId;
+
+    @Value("${wx.general.appSecret}")
+    private String appSecret;
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getAppSecret() {
+        return appSecret;
+    }
+
+    public void setAppSecret(String appSecret) {
+        this.appSecret = appSecret;
+    }
+}

+ 35 - 0
src/main/java/com/ematou/wxservice/controller/UserInfoController.java

@@ -0,0 +1,35 @@
+package com.ematou.wxservice.controller;
+
+import com.ematou.wxservice.common.web.R;
+import com.ematou.wxservice.entity.pojo.UserInfo;
+import com.ematou.wxservice.service.UserInfoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-10 19:55
+ */
+@RestController
+public class UserInfoController {
+
+    @Autowired
+    UserInfoService userInfoService;
+
+    @GetMapping("/user")
+    public R<?> getUserInfo(String openId, String phoneNumber) {
+        UserInfo userInfo = userInfoService.get(openId, phoneNumber);
+        if (null != userInfo) {
+            return new R<>().success(userInfo);
+        } else {
+            return new R<>().error("查询用户数据时,openId和phoneNumber必须要带有有其中一个!");
+        }
+
+    }
+
+}

+ 39 - 0
src/main/java/com/ematou/wxservice/controller/WeChatController.java

@@ -0,0 +1,39 @@
+package com.ematou.wxservice.controller;
+
+import com.ematou.wxservice.common.web.R;
+import com.ematou.wxservice.entity.vo.BindingInfo;
+import com.ematou.wxservice.entity.vo.TemplateMessage;
+import com.ematou.wxservice.service.WeChatService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-13 11:56
+ */
+@RestController
+public class WeChatController {
+
+    @Autowired
+    WeChatService weChatService;
+
+    @PostMapping("/template/message")
+    public R<?> sendTemplateMsg(@RequestBody TemplateMessage templateMessage){
+        Integer i = weChatService.sendTemplateMsg(templateMessage);
+        if (i==0) {
+            return new R<String>().error("推送消息失败!");
+        } else {
+            return new R<String>().success();
+        }
+    }
+
+    @GetMapping("/oauth2")
+    public R<?> oauth2(@RequestParam("code") String code){
+        Map<String, Object> map = weChatService.oauth2(code);
+        return new R<>().success(map);
+    }
+}

+ 65 - 0
src/main/java/com/ematou/wxservice/controller/WeChatMessageController.java

@@ -0,0 +1,65 @@
+package com.ematou.wxservice.controller;
+
+import com.ematou.wxservice.common.utils.SignUtil;
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutTextMessage;
+import com.ematou.wxservice.service.WeChatMessageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 11:48
+ */
+@RestController
+public class WeChatMessageController {
+
+    @Autowired
+    WeChatMessageService weChatMessageService;
+
+    /**
+     * 微信公众号消息回调 用于微信鉴权
+     * @param signature 微信的签名,需要跟自己的签名比对,相同才成功
+     * @param timestamp 时间戳
+     * @param nonce     随机数字
+     * @param echostr   随机字符串
+     * @param request   请求
+     * @param response  响应
+     * @return          字符串
+     */
+    @GetMapping("/message")
+    public String sign(@RequestParam(name = "signature",required = false) String signature,
+                       @RequestParam(name = "timestamp",required = false) String timestamp,
+                       @RequestParam(name = "nonce",required = false) String nonce,
+                       @RequestParam(name = "echostr",required = false) String echostr,
+                       HttpServletRequest request, HttpServletResponse response){
+        if(SignUtil.checkSignature(signature, timestamp, nonce)) {
+            return echostr;
+        }
+        return "";
+    }
+
+    /**
+     * 消息入口,由于微信平台在5s内无返回信息会进行重试,返回空字符串则不会重试
+     */
+    @PostMapping("/message")
+    public String handleMessage(HttpServletRequest request, HttpServletResponse response){
+        try {
+            ServletInputStream inputStream = request.getInputStream();
+
+            WeChatMessage weChatMessage = WeChatMessage.fromXml(inputStream);
+            WeChatMpXmlOutMessage outTextMessage = weChatMessageService.handleMessage(weChatMessage);
+            return null == outTextMessage ? "" : outTextMessage.toXml();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+}

+ 47 - 0
src/main/java/com/ematou/wxservice/controller/WeChatViewController.java

@@ -0,0 +1,47 @@
+package com.ematou.wxservice.controller;
+
+import com.ematou.wxservice.entity.vo.BindingInfo;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-12 16:00
+ */
+@Controller
+public class WeChatViewController {
+
+    @GetMapping("/view/")
+    public String main(){
+        return "hello";
+    }
+
+    @GetMapping("/view/binding")
+    public String binding(){
+        return "binding";
+    }
+
+
+    @PostMapping("/binding")
+    public String binding(HttpServletRequest request) {
+        String phone = request.getParameter("phone1");
+        String openid = request.getParameter("btnSendCode2");
+        String code1 = request.getParameter("code1");
+
+        BindingInfo bindingInfo = new BindingInfo();
+        bindingInfo.setOpenId(openid);
+        bindingInfo.setTellPhoneNumber(phone);
+        bindingInfo.setValidateCode(code1);
+        System.out.println(bindingInfo);
+
+        return "return";
+    }
+
+}

+ 248 - 0
src/main/java/com/ematou/wxservice/entity/pojo/UserInfo.java

@@ -0,0 +1,248 @@
+/*
+ * Copyright 2021 json.cn
+ */
+package com.ematou.wxservice.entity.pojo;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+/**
+ * Auto-generated: 2021-05-10 18:15:32
+ *
+ * @author json.cn (i@json.cn)
+ * @website http://www.json.cn/java2pojo/
+ */
+public class UserInfo {
+
+    private Integer subscribe;
+    private String openId;
+    private String tellPhoneNumber;
+    private String nickName;
+    private Integer sex;
+    private String language;
+    private String city;
+    private String province;
+    private String country;
+    private String headimgurl;
+    private Long subscribeTime;
+    private String unionId;
+    private String remark;
+    private Integer groupId;
+    private List<Integer> tagIdList;
+    private String subscribeScene;
+    private Long qrScene;
+    private String qrSceneStr;
+    private Integer creatorId;
+    private String createTime;
+    private Integer moderId;
+    private String modTime;
+    private Timestamp tmst;
+
+    public Integer getSubscribe() {
+        return subscribe;
+    }
+
+    public void setSubscribe(Integer subscribe) {
+        this.subscribe = subscribe;
+    }
+
+    public String getOpenId() {
+        return openId;
+    }
+
+    public void setOpenId(String openId) {
+        this.openId = openId;
+    }
+
+    public String getTellPhoneNumber() {
+        return tellPhoneNumber;
+    }
+
+    public void setTellPhoneNumber(String tellPhoneNumber) {
+        this.tellPhoneNumber = tellPhoneNumber;
+    }
+
+    public String getNickName() {
+        return nickName;
+    }
+
+    public void setNickName(String nickName) {
+        this.nickName = nickName;
+    }
+
+    public Integer getSex() {
+        return sex;
+    }
+
+    public void setSex(Integer sex) {
+        this.sex = sex;
+    }
+
+    public String getLanguage() {
+        return language;
+    }
+
+    public void setLanguage(String language) {
+        this.language = language;
+    }
+
+    public String getCity() {
+        return city;
+    }
+
+    public void setCity(String city) {
+        this.city = city;
+    }
+
+    public String getProvince() {
+        return province;
+    }
+
+    public void setProvince(String province) {
+        this.province = province;
+    }
+
+    public String getCountry() {
+        return country;
+    }
+
+    public void setCountry(String country) {
+        this.country = country;
+    }
+
+    public String getHeadimgurl() {
+        return headimgurl;
+    }
+
+    public void setHeadimgurl(String headimgurl) {
+        this.headimgurl = headimgurl;
+    }
+
+    public Long getSubscribeTime() {
+        return subscribeTime;
+    }
+
+    public void setSubscribeTime(Long subscribeTime) {
+        this.subscribeTime = subscribeTime;
+    }
+
+    public String getUnionId() {
+        return unionId;
+    }
+
+    public void setUnionId(String unionId) {
+        this.unionId = unionId;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public Integer getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(Integer groupId) {
+        this.groupId = groupId;
+    }
+
+    public List<Integer> getTagIdList() {
+        return tagIdList;
+    }
+
+    public void setTagIdList(List<Integer> tagIdList) {
+        this.tagIdList = tagIdList;
+    }
+
+    public String getSubscribeScene() {
+        return subscribeScene;
+    }
+
+    public void setSubscribeScene(String subscribeScene) {
+        this.subscribeScene = subscribeScene;
+    }
+
+    public Long getQrScene() {
+        return qrScene;
+    }
+
+    public void setQrScene(Long qrScene) {
+        this.qrScene = qrScene;
+    }
+
+    public String getQrSceneStr() {
+        return qrSceneStr;
+    }
+
+    public void setQrSceneStr(String qrSceneStr) {
+        this.qrSceneStr = qrSceneStr;
+    }
+
+    public Integer getCreatorId() {
+        return creatorId;
+    }
+
+    public void setCreatorId(Integer creatorId) {
+        this.creatorId = creatorId;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public Integer getModerId() {
+        return moderId;
+    }
+
+    public void setModerId(Integer moderId) {
+        this.moderId = moderId;
+    }
+
+    public String getModTime() {
+        return modTime;
+    }
+
+    public void setModTime(String modTime) {
+        this.modTime = modTime;
+    }
+
+    public Timestamp getTmst() {
+        return tmst;
+    }
+
+    public void setTmst(Timestamp tmst) {
+        this.tmst = tmst;
+    }
+
+    @Override
+    public String toString() {
+        return "UserInfo{" +
+                "subscribe=" + subscribe +
+                ", openId='" + openId + '\'' +
+                ", tellPhoneNumber='" + tellPhoneNumber + '\'' +
+                ", nickName='" + nickName + '\'' +
+                ", sex=" + sex +
+                ", language='" + language + '\'' +
+                ", city='" + city + '\'' +
+                ", province='" + province + '\'' +
+                ", country='" + country + '\'' +
+                ", headimgurl='" + headimgurl + '\'' +
+                ", subscribeTime=" + subscribeTime +
+                ", unionId='" + unionId + '\'' +
+                ", remark='" + remark + '\'' +
+                ", groupId=" + groupId +
+                ", tagIdList=" + tagIdList +
+                ", subscribeScene='" + subscribeScene + '\'' +
+                ", qrScene=" + qrScene +
+                ", qrSceneStr='" + qrSceneStr + '\'' +
+                '}';
+    }
+}

+ 144 - 0
src/main/java/com/ematou/wxservice/entity/vo/AccessToken.java

@@ -0,0 +1,144 @@
+package com.ematou.wxservice.entity.vo;
+
+import java.sql.Timestamp;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-04-30 11:45
+ */
+public class AccessToken {
+
+    private Integer id;
+
+    private String accessToken;
+
+    private String expiresTime;
+
+    private String effectTime;
+
+    private Integer expiresIn;
+
+    private Integer opId;
+
+    private Integer isValid;
+
+    private String createrSn;
+
+    private String createTime;
+
+    private String moderSn;
+
+    private String modTime;
+
+    private Timestamp tstm;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
+
+    public String getExpiresTime() {
+        return expiresTime;
+    }
+
+    public void setExpiresTime(String expiresTime) {
+        this.expiresTime = expiresTime;
+    }
+
+    public String getEffectTime() {
+        return effectTime;
+    }
+
+    public void setEffectTime(String effectTime) {
+        this.effectTime = effectTime;
+    }
+
+    public Integer getExpiresIn() {
+        return expiresIn;
+    }
+
+    public void setExpiresIn(Integer expiresIn) {
+        this.expiresIn = expiresIn;
+    }
+
+    public Integer getOpId() {
+        return opId;
+    }
+
+    public void setOpId(Integer opId) {
+        this.opId = opId;
+    }
+
+    public Integer getIsValid() {
+        return isValid;
+    }
+
+    public void setIsValid(Integer isValid) {
+        this.isValid = isValid;
+    }
+
+    public String getCreaterSn() {
+        return createrSn;
+    }
+
+    public void setCreaterSn(String createrSn) {
+        this.createrSn = createrSn;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getModerSn() {
+        return moderSn;
+    }
+
+    public void setModerSn(String moderSn) {
+        this.moderSn = moderSn;
+    }
+
+    public String getModTime() {
+        return modTime;
+    }
+
+    public void setModTime(String modTime) {
+        this.modTime = modTime;
+    }
+
+    public Timestamp getTstm() {
+        return tstm;
+    }
+
+    public void setTstm(Timestamp tstm) {
+        this.tstm = tstm;
+    }
+
+    @Override
+    public String toString() {
+        return "AccessToken{" +
+                "id=" + id +
+                ", accessToken='" + accessToken + '\'' +
+                ", expiresTime='" + expiresTime + '\'' +
+                ", effectTime='" + effectTime + '\'' +
+                ", expiresIn=" + expiresIn +
+                ", opId=" + opId +
+                ", isValid=" + isValid +
+                '}';
+    }
+}

+ 51 - 0
src/main/java/com/ematou/wxservice/entity/vo/BindingInfo.java

@@ -0,0 +1,51 @@
+package com.ematou.wxservice.entity.vo;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 绑定信息
+ * @author lhm
+ * @version 1.0
+ * 2021-05-15 11:28
+ */
+public class BindingInfo {
+
+    private String tellPhoneNumber;
+
+    private String openId;
+
+    private String validateCode;
+
+    public String getTellPhoneNumber() {
+        return tellPhoneNumber;
+    }
+
+    public void setTellPhoneNumber(String tellPhoneNumber) {
+        this.tellPhoneNumber = tellPhoneNumber;
+    }
+
+    public String getOpenId() {
+        return openId;
+    }
+
+    public void setOpenId(String openId) {
+        this.openId = openId;
+    }
+
+    public String getValidateCode() {
+        return validateCode;
+    }
+
+    public void setValidateCode(String validateCode) {
+        this.validateCode = validateCode;
+    }
+
+    @Override
+    public String toString() {
+        return "BindingInfo{" +
+                "tellPhoneNumber='" + tellPhoneNumber + '\'' +
+                ", openId='" + openId + '\'' +
+                ", validateCode='" + validateCode + '\'' +
+                '}';
+    }
+}

+ 156 - 0
src/main/java/com/ematou/wxservice/entity/vo/TemplateMessage.java

@@ -0,0 +1,156 @@
+package com.ematou.wxservice.entity.vo;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+import java.util.List;
+
+/**
+ * 需要发送的模板消息
+ * @author lhm
+ * @version 1.0
+ * 2021-05-13 09:45
+ */
+public class TemplateMessage {
+
+    @JSONField(alternateNames = "openid")
+    private String openId;
+
+    /**
+     * 预留,可选择的发送消息
+     */
+    @JSONField(alternateNames = "template_id")
+    private String templateMsgId;
+
+    private List<TemplateData> data;
+
+    /**
+     * 模板跳转链接(海外帐号没有跳转能力)
+     */
+    private String url;
+
+    /**
+     * 跳小程序所需数据,不需跳小程序可不用传该数据,非必填
+     */
+    private MiniProgramInfo miniProgram;
+
+
+    public static class TemplateData {
+
+        /**
+         * 模板数据内容
+         */
+        private String value;
+
+        /**
+         * 模板内容字体颜色(16进制值),不填默认为黑色
+         */
+        private String color;
+
+        public String getValue() {
+            return value;
+        }
+
+        public void setValue(String value) {
+            this.value = value;
+        }
+
+        public String getColor() {
+            return color;
+        }
+
+        public void setColor(String color) {
+            this.color = color;
+        }
+
+        @Override
+        public String toString() {
+            return "TemplateData{" +
+                    "value='" + value + '\'' +
+                    ", color='" + color + '\'' +
+                    '}';
+        }
+    }
+
+    public static class MiniProgramInfo {
+
+        private String appId;
+
+        private String pagePath;
+
+        public String getAppId() {
+            return appId;
+        }
+
+        public void setAppId(String appId) {
+            this.appId = appId;
+        }
+
+        public String getPagePath() {
+            return pagePath;
+        }
+
+        public void setPagePath(String pagePath) {
+            this.pagePath = pagePath;
+        }
+
+        @Override
+        public String toString() {
+            return "MiniProgramInfo{" +
+                    "appId='" + appId + '\'' +
+                    ", pagePath='" + pagePath + '\'' +
+                    '}';
+        }
+    }
+
+
+    public String getOpenId() {
+        return openId;
+    }
+
+    public void setOpenId(String openId) {
+        this.openId = openId;
+    }
+
+    public String getTemplateMsgId() {
+        return templateMsgId;
+    }
+
+    public void setTemplateMsgId(String templateMsgId) {
+        this.templateMsgId = templateMsgId;
+    }
+
+    public List<TemplateData> getData() {
+        return data;
+    }
+
+    public void setData(List<TemplateData> data) {
+        this.data = data;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public MiniProgramInfo getMiniProgram() {
+        return miniProgram;
+    }
+
+    public void setMiniProgram(MiniProgramInfo miniProgram) {
+        this.miniProgram = miniProgram;
+    }
+
+    @Override
+    public String toString() {
+        return "TemplateMessage{" +
+                "openId='" + openId + '\'' +
+                ", templateMsgId='" + templateMsgId + '\'' +
+                ", data=" + data +
+                ", url='" + url + '\'' +
+                ", miniProgram=" + miniProgram +
+                '}';
+    }
+}

+ 120 - 0
src/main/java/com/ematou/wxservice/interceptor/MybatisInsertAndUpdateInterceptor.java

@@ -0,0 +1,120 @@
+package com.ematou.wxservice.interceptor;
+
+import com.ematou.wxservice.common.utils.DateUtil;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.plugin.*;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Field;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-04-30 15:30
+ */
+@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
+@Component
+public class MybatisInsertAndUpdateInterceptor implements Interceptor {
+
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+
+        Object[] args = invocation.getArgs();
+        MappedStatement mappedStatement = (MappedStatement) args[0];
+
+        if (mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
+            Object arg = args[1];
+            if (arg instanceof Map) {
+                Map<?, ?> map = (Map<?, ?>) arg;
+                for (Object key : map.keySet()) {
+                    if (key.toString().contains("param")) {
+                        handleUpdateData(map.get(key));
+                    }
+                }
+            } else {
+                handleInsertData(arg);
+            }
+        } else if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
+            Object arg = args[1];
+            if (arg instanceof Map) {
+                Map<?, ?> map = (Map<?, ?>) arg;
+                for (Object key : map.keySet()) {
+                    if (key.toString().contains("param")) {
+                        handleInsertData(map.get(key));
+                    }
+                }
+            } else {
+                handleInsertData(arg);
+            }
+        }
+
+        return invocation.proceed();
+    }
+
+    private void handleInsertData(Object o) throws IllegalAccessException {
+        Field[] fields = o.getClass().getDeclaredFields();
+
+        for (Field field : fields) {
+            switch (field.getName()) {
+                case "createTime":
+                    field.setAccessible(true);
+                    field.set(o, DateUtil.formatDate(new Date()));
+                    break;
+                case "modTime":
+                    field.setAccessible(true);
+                    field.set(o, DateUtil.formatDate(new Date()));
+                    break;
+                case "tstm":
+                    field.setAccessible(true);
+                    field.set(o, new Timestamp(System.currentTimeMillis()));
+                    break;
+                case "createrSn":
+                    // TODO
+                    field.setAccessible(true);
+                    field.set(o, "admin");
+                    break;
+            }
+        }
+
+    }
+
+    private void handleUpdateData(Object o) throws IllegalAccessException {
+        Field[] fields = o.getClass().getDeclaredFields();
+
+        for (Field field : fields) {
+            switch (field.getName()) {
+                case "modTime":
+                    field.setAccessible(true);
+                    field.set(o, DateUtil.formatDate(new Date()));
+                    break;
+                case "tstm":
+                    field.setAccessible(true);
+                    field.set(o, new Timestamp(System.currentTimeMillis()));
+                    break;
+                case "moderSn":
+                    // TODO
+                    field.setAccessible(true);
+                    field.set(o, "admin");
+                    break;
+            }
+        }
+
+    }
+
+    @Override
+    public Object plugin(Object target) {
+        return Plugin.wrap(target, this);
+    }
+
+    @Override
+    public void setProperties(Properties properties) {
+
+    }
+}

+ 23 - 0
src/main/java/com/ematou/wxservice/mapper/UserInfoMapper.java

@@ -0,0 +1,23 @@
+package com.ematou.wxservice.mapper;
+
+import com.ematou.wxservice.entity.pojo.UserInfo;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Map;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-10 18:48
+ */
+@Mapper
+@Repository
+public interface UserInfoMapper {
+
+    int insertUserInfo(@Param("userInfo") UserInfo userInfo);
+
+    UserInfo queryUserInfoByOpenIdOrPhoneNumber(@Param("param") Map<String, String> param);
+
+}

+ 81 - 0
src/main/java/com/ematou/wxservice/mp/handler/WeChatClickEventMessageHandler.java

@@ -0,0 +1,81 @@
+package com.ematou.wxservice.mp.handler;
+
+import com.ematou.wxservice.common.constant.WeChatConstant;
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutNewsMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutTextMessage;
+import org.springframework.stereotype.Component;
+
+/**
+ * CLICK事件处理器
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 19:28
+ */
+@Component
+public class WeChatClickEventMessageHandler implements WeChatMessageHandler {
+
+    /**
+     * 处理消息,并封装返回消息
+     *
+     * @param weChatMessage 接收到的消息
+     * @return 响应消息
+     */
+    @Override
+    public WeChatMpXmlOutMessage handlerMessage(WeChatMessage weChatMessage) {
+        WeChatMpXmlOutMessage response = null;
+        // TODO 处理菜单点击事件
+        switch (weChatMessage.getEventKey()) {
+            case WeChatConstant.CustomEventKey.POLICY_SUPPORT:
+                response = handlerPolicySupportEvent(weChatMessage);
+                break;
+            case WeChatConstant.CustomEventKey.CALL_COMPANY:
+                response = handlerCallCompanyEvent(weChatMessage);
+                break;
+            case WeChatConstant.CustomEventKey.MY_TAKE_PARCEL_CODE:
+                // TODO 我的取件码
+                break;
+            case WeChatConstant.CustomEventKey.MY_HISTORY_RECORD:
+                // TODO 取件记录
+                break;
+            default:
+                break;
+        }
+
+        return response;
+    }
+
+    /**
+     * 处理联系我们按钮点击事件
+     * @param weChatMessage 事件
+     * @return 响应
+     */
+    private WeChatMpXmlOutTextMessage handlerCallCompanyEvent(WeChatMessage weChatMessage){
+
+        String content = "【深圳前海电商供应链管理有限公司】\n" +
+                "\n" +
+                "我们在这里!↓↓↓mo-[嘿哈]\n" +
+                "地址:深圳市南山区前海湾临海大道59号招商海运中心主塔楼17楼1701室\n" +
+                "\n" +
+                "或者直接拨打电话 0755-21628282mo-[握手]\n" +
+                "我们热情的等着您~";
+        return WeChatMpXmlOutMessage.TEXT().content(content).toUser(weChatMessage.getFromUser()).fromUser(weChatMessage.getToUser()).build();
+    }
+
+    /**
+     * 处理政策支持按钮点击事件
+     * @param weChatMessage 事件
+     * @return 响应
+     */
+    private WeChatMpXmlOutNewsMessage handlerPolicySupportEvent(WeChatMessage weChatMessage){
+
+        WeChatMpXmlOutNewsMessage.Item item = new WeChatMpXmlOutNewsMessage.Item();
+        item.setDescription("【11月8日上午,深圳前海国检领导和业务科长一行莅临e码头进行企业调研。】e码头董事长沈琦雅向来访领导介绍了");
+        item.setTitle("前海国检领导和业务科长莅临e码头 ——就企业创新需求及模式进行调研");
+        item.setPicUrl("http://mmbiz.qpic.cn/mmbiz_png/Vsr05SEyic67Jj1G2pnbuJSfc0I3qoZ6mV3KXKtrhjicK1jZvicHH431laAMWskiamd4KibLkTvk1I5YT8aEt6RPmfg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1");
+        item.setUrl("https://mp.weixin.qq.com/s?__biz=MzIwMTI2MTg1MA==&tempkey=MTExM185eTh5aE1DSkRjRWFqa1FGRElaWmNGa1pZLWpTbG5va0tNTnJxaXE0SE04WWVLMmYzc1hteU82THN3Rm5QamtSQUtXS0N2Zm4ybTBBZnhaOUZOZnl2eTVNVjZ5MzRvSXFPZ3lEcFNnUVdpUzV5aVRHZHJocXNLTV9RNUJYSUhTeTBVdFcxM2pxZU9INWJHSDVOUmRDWTdHcmlQSVR4TGNSQ2I3bHpnfn4%3D&chksm=0ef9ac39398e252f2791f410f2c92def335d61bfb8d7bc8563ae4dc4d91eb6b9d1b5c72ed4f4#rd");
+        return WeChatMpXmlOutMessage.NEWS().addArticle(item).toUser(weChatMessage.getFromUser()).fromUser(weChatMessage.getToUser()).build();
+    }
+
+}

+ 22 - 0
src/main/java/com/ematou/wxservice/mp/handler/WeChatMessageHandler.java

@@ -0,0 +1,22 @@
+package com.ematou.wxservice.mp.handler;
+
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutTextMessage;
+
+/**
+ * 用户消息处理器接口
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 16:22
+ */
+public interface WeChatMessageHandler {
+
+    /**
+     * 处理消息,并封装返回消息
+     * @param weChatMessage 接收到的消息
+     * @return 响应消息
+     */
+    public WeChatMpXmlOutMessage handlerMessage(WeChatMessage weChatMessage);
+
+}

+ 26 - 0
src/main/java/com/ematou/wxservice/mp/handler/WeChatNewsMessageHandler.java

@@ -0,0 +1,26 @@
+package com.ematou.wxservice.mp.handler;
+
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+import org.springframework.stereotype.Component;
+
+/**
+ * 图文消息处理器
+ * @author lhm
+ * @version 1.0
+ * 2021-05-12 11:26
+ */
+@Component
+public class WeChatNewsMessageHandler implements WeChatMessageHandler {
+
+    /**
+     * 处理消息,并封装返回消息
+     *
+     * @param weChatMessage 接收到的消息
+     * @return 响应消息
+     */
+    @Override
+    public WeChatMpXmlOutMessage handlerMessage(WeChatMessage weChatMessage) {
+        return null;
+    }
+}

+ 36 - 0
src/main/java/com/ematou/wxservice/mp/handler/WeChatSubscribeEventHandler.java

@@ -0,0 +1,36 @@
+package com.ematou.wxservice.mp.handler;
+
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+import org.springframework.stereotype.Component;
+
+/**
+ * 关注事件
+ * @author lhm
+ * @version 1.0
+ * 2021-05-14 17:04
+ */
+@Component
+public class WeChatSubscribeEventHandler implements WeChatMessageHandler {
+
+    /**
+     * 处理消息,并封装返回消息
+     *
+     * @param weChatMessage 接收到的消息
+     * @return 响应消息
+     */
+    @Override
+    public WeChatMpXmlOutMessage handlerMessage(WeChatMessage weChatMessage) {
+        return WeChatMpXmlOutMessage.TEXT().content(content(weChatMessage.getFromUser())).toUser(weChatMessage.getFromUser()).fromUser(weChatMessage.getToUser()).build();
+    }
+
+    private String content(String openId) {
+        // TODO openId需要做加密,传到
+        return "哈喽!欢迎关注【e码头智能柜】!\n" +
+                "\n" +
+                "关注e码头,第一时间接收您的快件物流提醒,提供24H自助寄取快递服务!\n" +
+                "\n" +
+                "<a link='http://f3dhion.nat.ipyingshe.com/view/binding?openid="+ openId +"' >>>点击绑定手机号,立即取件/寄件!</a>\n";
+    }
+
+}

+ 30 - 0
src/main/java/com/ematou/wxservice/mp/handler/WeChatTemplateMessageEventHandler.java

@@ -0,0 +1,30 @@
+package com.ematou.wxservice.mp.handler;
+
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+
+/**
+ * 模板消息推送事件
+ * @author lhm
+ * @version 1.0
+ * 2021-05-13 14:47
+ */
+public class WeChatTemplateMessageEventHandler implements WeChatMessageHandler {
+
+
+    /**
+     * 处理消息,并封装返回消息
+     *
+     * @param weChatMessage 接收到的消息
+     * @return 响应消息
+     */
+    @Override
+    public WeChatMpXmlOutMessage handlerMessage(WeChatMessage weChatMessage) {
+
+        String status = weChatMessage.getStatus();
+
+
+
+        return null;
+    }
+}

+ 40 - 0
src/main/java/com/ematou/wxservice/mp/handler/WeChatTextMessageHandler.java

@@ -0,0 +1,40 @@
+package com.ematou.wxservice.mp.handler;
+
+import com.alibaba.fastjson.JSON;
+import com.ematou.wxservice.api.WeChatApi;
+import com.ematou.wxservice.api.WeChatApiRestTemplate;
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import com.ematou.wxservice.entity.pojo.UserInfo;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutTextMessage;
+import com.ematou.wxservice.service.WeChatService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 文本信息处理器
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 16:27
+ */
+@Component
+public class WeChatTextMessageHandler implements WeChatMessageHandler {
+
+    @Autowired
+    WeChatApiRestTemplate weChatApiRestTemplate;
+
+    @Autowired
+    WeChatService weChatService;
+
+    @Override
+    public WeChatMpXmlOutTextMessage handlerMessage(WeChatMessage weChatMessage) {
+
+        String openId = weChatMessage.getFromUser();
+
+        String response = weChatApiRestTemplate.getForOther(String.format(WeChatApi.GET_USER_INFO.getUrl(), weChatService.getAccessToken().getAccessToken(), openId));
+        // TODO 构建响应数据,需要修改响应数据,目前只是测试
+        UserInfo userInfo = JSON.parseObject(response, UserInfo.class);
+        System.out.println(userInfo.toString());
+        return WeChatMpXmlOutMessage.TEXT().content(response).toUser(weChatMessage.getFromUser()).fromUser(weChatMessage.getToUser()).build();
+    }
+}

+ 26 - 0
src/main/java/com/ematou/wxservice/mp/handler/WeChatViewEventMessageHandler.java

@@ -0,0 +1,26 @@
+package com.ematou.wxservice.mp.handler;
+
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+import org.springframework.stereotype.Component;
+
+/**
+ * VIEW事件处理器
+ * @author lhm
+ * @version 1.0
+ * 2021-05-12 11:13
+ */
+@Component
+public class WeChatViewEventMessageHandler implements WeChatMessageHandler {
+
+    /**
+     * 处理消息,并封装返回消息
+     *
+     * @param weChatMessage 接收到的消息
+     * @return 响应消息
+     */
+    @Override
+    public WeChatMpXmlOutMessage handlerMessage(WeChatMessage weChatMessage) {
+        return null;
+    }
+}

+ 39 - 0
src/main/java/com/ematou/wxservice/mp/menu/Button.java

@@ -0,0 +1,39 @@
+package com.ematou.wxservice.mp.menu;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * @link https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Personalized_menu_interface.html
+ * 2021-05-11 19:39
+ */
+public class Button {
+
+    protected String name;
+
+    protected String type;
+
+    public Button(String name, String type) {
+        this.name = name;
+        this.type = type;
+    }
+
+    public Button() {
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+}

+ 28 - 0
src/main/java/com/ematou/wxservice/mp/menu/ClickButton.java

@@ -0,0 +1,28 @@
+package com.ematou.wxservice.mp.menu;
+
+/**
+ * 点击类型
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 19:39
+ */
+public class ClickButton extends Button {
+
+    private String key;
+
+    public ClickButton(String name, String type, String key) {
+        super(name, type);
+        this.key = key;
+    }
+
+    public ClickButton() {
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+}

+ 94 - 0
src/main/java/com/ematou/wxservice/mp/menu/Matchrule.java

@@ -0,0 +1,94 @@
+package com.ematou.wxservice.mp.menu;
+
+/**
+ * 匹配规则
+ * @author lhm
+ * @version 1.0
+ * @link https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Personalized_menu_interface.html
+ * 2021-05-11 20:18
+ */
+public class Matchrule {
+
+    private String tag_id;
+
+    private String sex;
+
+    private String country;
+
+    private String province;
+
+    private String city;
+
+    private String client_platform_type;
+
+    private String language;
+
+    public Matchrule(String tag_id, String sex, String country, String province, String city, String client_platform_type, String language) {
+        this.tag_id = tag_id;
+        this.sex = sex;
+        this.country = country;
+        this.province = province;
+        this.city = city;
+        this.client_platform_type = client_platform_type;
+        this.language = language;
+    }
+
+    public Matchrule() {
+    }
+
+    public String getTag_id() {
+        return tag_id;
+    }
+
+    public void setTag_id(String tag_id) {
+        this.tag_id = tag_id;
+    }
+
+    public String getSex() {
+        return sex;
+    }
+
+    public void setSex(String sex) {
+        this.sex = sex;
+    }
+
+    public String getCountry() {
+        return country;
+    }
+
+    public void setCountry(String country) {
+        this.country = country;
+    }
+
+    public String getProvince() {
+        return province;
+    }
+
+    public void setProvince(String province) {
+        this.province = province;
+    }
+
+    public String getCity() {
+        return city;
+    }
+
+    public void setCity(String city) {
+        this.city = city;
+    }
+
+    public String getClient_platform_type() {
+        return client_platform_type;
+    }
+
+    public void setClient_platform_type(String client_platform_type) {
+        this.client_platform_type = client_platform_type;
+    }
+
+    public String getLanguage() {
+        return language;
+    }
+
+    public void setLanguage(String language) {
+        this.language = language;
+    }
+}

+ 32 - 0
src/main/java/com/ematou/wxservice/mp/menu/MenuButton.java

@@ -0,0 +1,32 @@
+package com.ematou.wxservice.mp.menu;
+
+import java.util.List;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 19:41
+ */
+public class MenuButton extends Button {
+
+    /**
+     * 子菜单列表
+     */
+    private List<Button> sub_button;
+
+    public MenuButton(String name){
+        super();
+        super.name = name;
+    }
+
+    public MenuButton() {
+    }
+
+    public List<Button> getSub_button() {
+        return sub_button;
+    }
+
+    public void setSub_button(List<Button> sub_button) {
+        this.sub_button = sub_button;
+    }
+}

+ 113 - 0
src/main/java/com/ematou/wxservice/mp/menu/MenuButtonManager.java

@@ -0,0 +1,113 @@
+package com.ematou.wxservice.mp.menu;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.ematou.wxservice.api.WeChatApi;
+import com.ematou.wxservice.api.WeChatApiRestTemplate;
+import com.ematou.wxservice.common.constant.WeChatConstant;
+import com.ematou.wxservice.service.WeChatService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+import javax.annotation.PostConstruct;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 菜单按钮管理类
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 20:46
+ */
+@Component
+public class MenuButtonManager {
+
+    public static final Logger logger = LoggerFactory.getLogger(MenuButtonManager.class);
+
+    @Autowired
+    WeChatApiRestTemplate weChatApiRestTemplate;
+
+    @Autowired
+    WeChatService weChatService;
+
+    @PostConstruct
+    public void init() {
+        // TODO 初始化菜单按钮
+        ViewButton testBtn = new ViewButton("测试按钮", WeChatConstant.EventType.VIEW.toLowerCase(), "http://f3dhion.nat.ipyingshe.com/view/main");
+        ClickButton myCodeBtn = new ClickButton("我的取件码", WeChatConstant.EventType.CLICK.toLowerCase(), WeChatConstant.CustomEventKey.MY_TAKE_PARCEL_CODE);
+        ClickButton myHistoryBtn = new ClickButton("取件历史", WeChatConstant.EventType.CLICK.toLowerCase(), WeChatConstant.CustomEventKey.MY_HISTORY_RECORD);
+        MenuButton takeParcelMenuButton = new MenuButton("取件服务");
+        takeParcelMenuButton.setSub_button(Arrays.asList(myCodeBtn, myHistoryBtn, testBtn));
+
+        ViewButton companyState = new ViewButton("公司状态", WeChatConstant.EventType.VIEW.toLowerCase(), "http://mp.weixin.qq.com/mp/homepage?__biz=MzIwMTI2MTg1MA==&hid=3&sn=da0db13971baf7a27bf639f2f7eaebbe&scene=18#wechat_redirect");
+        ViewButton companyInfo = new ViewButton("公司简介", WeChatConstant.EventType.VIEW.toLowerCase(), "http://mp.weixin.qq.com/s?__biz=MzIwMTI2MTg1MA==&mid=503159833&idx=1&sn=fbddc8c56f960d7912cee60c959c42e8&chksm=0ef9aa6c398e237a862143c4f4c7314c0159a77e87ad4fd39a89d22181bcb05aedc9a816c8b2&scene=18#wechat_redirect");
+        ViewButton showNews = new ViewButton("展会快讯", WeChatConstant.EventType.VIEW.toLowerCase(), "http://mp.weixin.qq.com/mp/homepage?__biz=MzIwMTI2MTg1MA==&hid=4&sn=89a19c85a4742d13eb3e4ceab2179ba8&scene=18#wechat_redirect");
+        ViewButton industryReport = new ViewButton("行业周报", WeChatConstant.EventType.VIEW.toLowerCase(), "http://mp.weixin.qq.com/mp/homepage?__biz=MzIwMTI2MTg1MA==&hid=1&sn=f7becf0ef818d4a57a4ca4acfde72613&scene=18#wechat_redirect");
+        ClickButton policySupport = new ClickButton("政策支持", WeChatConstant.EventType.CLICK.toLowerCase(), WeChatConstant.CustomEventKey.POLICY_SUPPORT);
+        MenuButton companyMenuButton = new MenuButton("公司信息");
+        companyMenuButton.setSub_button(Arrays.asList(companyState, companyInfo, showNews, policySupport, industryReport));
+
+        ViewButton supplier = new ViewButton("供应商拓展", WeChatConstant.EventType.VIEW.toLowerCase(), "http://mp.weixin.qq.com/mp/homepage?__biz=MzIwMTI2MTg1MA==&hid=2&sn=cd51f61ada3218168fe3af6c3318b6ca&scene=18#wechat_redirect");
+        ViewButton showGoods = new ViewButton("货源展示区", WeChatConstant.EventType.VIEW.toLowerCase(), "https://mp.weixin.qq.com/bizmall/mallshelf?id=&t=mall/list&biz=MzIwMTI2MTg1MA==&shelf_id=5&showwxpaytitle=1#wechat_redirect");
+        ClickButton callCompanyBtn = new ClickButton("联系我们", WeChatConstant.EventType.CLICK.toLowerCase(), WeChatConstant.CustomEventKey.CALL_COMPANY);
+        MenuButton goodsMenuButton = new MenuButton("货源展示");
+        goodsMenuButton.setSub_button(Arrays.asList(supplier, showGoods, callCompanyBtn));
+
+        HashMap<String, List<Button>> map = new HashMap<>();
+        map.put("button", Arrays.asList(companyMenuButton, goodsMenuButton, takeParcelMenuButton));
+
+        String menu = JSON.toJSONString(map);
+
+        logger.info("初始化菜单信息:" + menu);
+
+        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
+            int retry = 0;
+            while (retry < 3) {
+                try {
+                    String response = weChatApiRestTemplate.postForOther(String.format(WeChatApi.CREATE_MENU.getUrl(), weChatService.getAccessToken().getAccessToken()), menu);
+
+                    if (!StringUtils.hasLength(response)) {
+                        retry++;
+                        logger.error("创建菜单失败,返回为空!等待5秒继续重试中。。");
+                        if (retry == 3) {
+                            logger.error("创建菜单已经失败,请检查微信公众号服务器配置是否开启,或检查服务器配置是否正确!");
+                        }
+                        TimeUnit.SECONDS.sleep(5);
+                        continue;
+                    }
+
+                    JSONObject jsonObject = JSON.parseObject(response);
+                    logger.info("创建菜单结果:" + response);
+                    Integer errcode = jsonObject.getObject(WeChatConstant.RESPONSE_ERROR_CODE, Integer.class);
+                    String errmsg = jsonObject.getObject(WeChatConstant.RESPONSE_ERROR_MSG, String.class);
+                    if (errcode != 0 && !errmsg.equalsIgnoreCase(WeChatConstant.RESPONSE_OK)) {
+                        retry++;
+                        logger.error("创建菜单失败,error code:" + errcode + ",error message:" + errmsg);
+                        TimeUnit.SECONDS.sleep(5);
+                        continue;
+                    }
+                    break;
+                } catch (Throwable e) {
+                    retry++;
+                }
+            }
+        });
+
+        // 不阻塞主线程
+//        try {
+//            runAsync.get(20, TimeUnit.SECONDS);
+//        } catch (ExecutionException | TimeoutException e) {
+//            logger.error("等待创建菜单执行结果超时或执行出错:" + e.getMessage());
+//        }
+
+    }
+
+}

+ 50 - 0
src/main/java/com/ematou/wxservice/mp/menu/MiniProgramButton.java

@@ -0,0 +1,50 @@
+package com.ematou.wxservice.mp.menu;
+
+/**
+ * 小程序类型
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 19:50
+ */
+public class MiniProgramButton extends Button {
+
+    private String url;
+
+    private String appid;
+
+    private String pagepath;
+
+    public MiniProgramButton(String name, String type, String url, String appid, String pagepath) {
+        super(name, type);
+        this.url = url;
+        this.appid = appid;
+        this.pagepath = pagepath;
+    }
+
+    public MiniProgramButton() {
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getAppid() {
+        return appid;
+    }
+
+    public void setAppid(String appid) {
+        this.appid = appid;
+    }
+
+    public String getPagepath() {
+        return pagepath;
+    }
+
+    public void setPagepath(String pagepath) {
+        this.pagepath = pagepath;
+    }
+}

+ 40 - 0
src/main/java/com/ematou/wxservice/mp/menu/ViewButton.java

@@ -0,0 +1,40 @@
+package com.ematou.wxservice.mp.menu;
+
+import java.util.List;
+
+/**
+ * 网页类型
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 19:48
+ */
+public class ViewButton extends Button {
+
+    private String url;
+
+    private List<Button> sub_button;
+
+    public ViewButton(String name, String type, String url) {
+        super(name, type);
+        this.url = url;
+    }
+
+    public ViewButton() {
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public List<Button> getSub_button() {
+        return sub_button;
+    }
+
+    public void setSub_button(List<Button> sub_button) {
+        this.sub_button = sub_button;
+    }
+}

+ 369 - 0
src/main/java/com/ematou/wxservice/mp/message/WeChatMessage.java

@@ -0,0 +1,369 @@
+package com.ematou.wxservice.mp.message;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.ematou.wxservice.common.utils.XStreamTransformer;
+import com.ematou.wxservice.common.xml.converter.XStreamCDataConverter;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamConverter;
+
+import java.io.InputStream;
+import java.io.Serializable;
+
+/**
+ * 用户发过来的消息
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 16:08
+ */
+@XStreamAlias("xml")
+public class WeChatMessage implements Serializable {
+    private static final long serialVersionUID = -3586245291677274914L;
+
+    @JSONField(name = "Encrypt")
+    @XStreamAlias("Encrypt")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String encrypt;
+
+    @JSONField(name = "ToUserName")
+    @XStreamAlias("ToUserName")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String toUser;
+
+    @JSONField(name = "FromUserName")
+    @XStreamAlias("FromUserName")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String fromUser;
+
+    @JSONField(name = "CreateTime")
+    @XStreamAlias("CreateTime")
+    private Integer createTime;
+
+    @JSONField(name = "MsgType")
+    @XStreamAlias("MsgType")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String msgType;
+
+    @JSONField(name = "MsgDataFormat")
+    @XStreamAlias("MsgDataFormat")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String msgDataFormat;
+
+    @JSONField(name = "Content")
+    @XStreamAlias("Content")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String content;
+
+    @JSONField(name = "MsgId")
+    @XStreamAlias("MsgId")
+    private Long msgId;
+
+    @JSONField(name = "PicUrl")
+    @XStreamAlias("PicUrl")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String picUrl;
+
+    @JSONField(name = "MediaId")
+    @XStreamAlias("MediaId")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String mediaId;
+
+    @JSONField(name = "Event")
+    @XStreamAlias("Event")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String event;
+
+    @JSONField(name = "EventKey")
+    @XStreamAlias("EventKey")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String eventKey;
+
+    @JSONField(name = "Title")
+    @XStreamAlias("Title")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String title;
+
+    @JSONField(name = "AppId")
+    @XStreamAlias("AppId")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String appId;
+
+    @JSONField(name = "PagePath")
+    @XStreamAlias("PagePath")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String pagePath;
+
+    @JSONField(name = "ThumbUrl")
+    @XStreamAlias("ThumbUrl")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String thumbUrl;
+
+    @JSONField(name = "ThumbMediaId")
+    @XStreamAlias("ThumbMediaId")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String thumbMediaId;
+
+    @JSONField(name = "SessionFrom")
+    @XStreamAlias("SessionFrom")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String sessionFrom;
+
+    /**
+     * 以下是异步校验图片/音频是否含有违法违规内容的异步检测结果推送报文中的参数
+     */
+    @JSONField(name = "isrisky")
+    @XStreamAlias("isrisky")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String isRisky;
+
+    @JSONField(name = "extra_info_json")
+    @XStreamAlias("extra_info_json")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String extraInfoJson;
+
+    @JSONField(name = "appid")
+    @XStreamAlias("appid")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String appid;
+
+    @JSONField(name = "trace_id")
+    @XStreamAlias("trace_id")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String traceId;
+
+    @JSONField(name = "status_code")
+    @XStreamAlias("status_code")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String statusCode;
+
+    @JSONField(name = "Status")
+    @XStreamAlias("Status")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String status;
+
+    @JSONField(name = "Scene")
+    @XStreamAlias("Scene")
+    private Integer scene;
+
+    @JSONField(name = "Query")
+    @XStreamAlias("Query")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String query;
+
+
+    public static WeChatMessage fromXml(String xml) {
+        return XStreamTransformer.fromXml(WeChatMessage.class, xml);
+    }
+
+    public static WeChatMessage fromXml(InputStream is) {
+        return XStreamTransformer.fromXml(WeChatMessage.class, is);
+    }
+
+
+    public String getEncrypt() {
+        return encrypt;
+    }
+
+    public void setEncrypt(String encrypt) {
+        this.encrypt = encrypt;
+    }
+
+    public String getToUser() {
+        return toUser;
+    }
+
+    public void setToUser(String toUser) {
+        this.toUser = toUser;
+    }
+
+    public String getFromUser() {
+        return fromUser;
+    }
+
+    public void setFromUser(String fromUser) {
+        this.fromUser = fromUser;
+    }
+
+    public Integer getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Integer createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getMsgType() {
+        return msgType;
+    }
+
+    public void setMsgType(String msgType) {
+        this.msgType = msgType;
+    }
+
+    public String getMsgDataFormat() {
+        return msgDataFormat;
+    }
+
+    public void setMsgDataFormat(String msgDataFormat) {
+        this.msgDataFormat = msgDataFormat;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Long getMsgId() {
+        return msgId;
+    }
+
+    public void setMsgId(Long msgId) {
+        this.msgId = msgId;
+    }
+
+    public String getPicUrl() {
+        return picUrl;
+    }
+
+    public void setPicUrl(String picUrl) {
+        this.picUrl = picUrl;
+    }
+
+    public String getMediaId() {
+        return mediaId;
+    }
+
+    public void setMediaId(String mediaId) {
+        this.mediaId = mediaId;
+    }
+
+    public String getEvent() {
+        return event;
+    }
+
+    public void setEvent(String event) {
+        this.event = event;
+    }
+
+    public String getEventKey() {
+        return eventKey;
+    }
+
+    public void setEventKey(String eventKey) {
+        this.eventKey = eventKey;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getPagePath() {
+        return pagePath;
+    }
+
+    public void setPagePath(String pagePath) {
+        this.pagePath = pagePath;
+    }
+
+    public String getThumbUrl() {
+        return thumbUrl;
+    }
+
+    public void setThumbUrl(String thumbUrl) {
+        this.thumbUrl = thumbUrl;
+    }
+
+    public String getThumbMediaId() {
+        return thumbMediaId;
+    }
+
+    public void setThumbMediaId(String thumbMediaId) {
+        this.thumbMediaId = thumbMediaId;
+    }
+
+    public String getSessionFrom() {
+        return sessionFrom;
+    }
+
+    public void setSessionFrom(String sessionFrom) {
+        this.sessionFrom = sessionFrom;
+    }
+
+    public String getIsRisky() {
+        return isRisky;
+    }
+
+    public void setIsRisky(String isRisky) {
+        this.isRisky = isRisky;
+    }
+
+    public String getExtraInfoJson() {
+        return extraInfoJson;
+    }
+
+    public void setExtraInfoJson(String extraInfoJson) {
+        this.extraInfoJson = extraInfoJson;
+    }
+
+    public String getAppid() {
+        return appid;
+    }
+
+    public void setAppid(String appid) {
+        this.appid = appid;
+    }
+
+    public String getTraceId() {
+        return traceId;
+    }
+
+    public void setTraceId(String traceId) {
+        this.traceId = traceId;
+    }
+
+    public String getStatusCode() {
+        return statusCode;
+    }
+
+    public void setStatusCode(String statusCode) {
+        this.statusCode = statusCode;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public Integer getScene() {
+        return scene;
+    }
+
+    public void setScene(Integer scene) {
+        this.scene = scene;
+    }
+
+    public String getQuery() {
+        return query;
+    }
+
+    public void setQuery(String query) {
+        this.query = query;
+    }
+}

+ 93 - 0
src/main/java/com/ematou/wxservice/mp/message/WeChatMpXmlOutMessage.java

@@ -0,0 +1,93 @@
+package com.ematou.wxservice.mp.message;
+
+import com.ematou.wxservice.common.utils.XStreamTransformer;
+import com.ematou.wxservice.common.xml.builder.NewsBuilder;
+import com.ematou.wxservice.common.xml.builder.TextBuilder;
+import com.ematou.wxservice.common.xml.converter.XStreamCDataConverter;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamConverter;
+
+import java.io.Serializable;
+
+/**
+ * 响应消息基类,没新增一种响应消息类型,都需要将其注册到XStreamTransformer类的Map中。
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 14:51
+ */
+@XStreamAlias("xml")
+public abstract class WeChatMpXmlOutMessage implements Serializable {
+
+    private static final long serialVersionUID = -381382011286216263L;
+
+    @XStreamAlias("ToUserName")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    protected String toUserName;
+
+    @XStreamAlias("FromUserName")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    protected String fromUserName;
+
+    @XStreamAlias("CreateTime")
+    protected Long createTime;
+
+    @XStreamAlias("MsgType")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    protected String msgType;
+
+    @XStreamAlias("MsgId")
+    protected Long msgId;
+
+    /**
+     * 获得文本消息builder
+     */
+    public static TextBuilder TEXT() {
+        return new TextBuilder();
+    }
+
+    public static NewsBuilder NEWS() { return new NewsBuilder(); }
+
+    public String toXml() {
+        return XStreamTransformer.toXml((Class<WeChatMpXmlOutMessage>) this.getClass(), this);
+    }
+
+    public String getToUserName() {
+        return toUserName;
+    }
+
+    public void setToUserName(String toUserName) {
+        this.toUserName = toUserName;
+    }
+
+    public String getFromUserName() {
+        return fromUserName;
+    }
+
+    public void setFromUserName(String fromUserName) {
+        this.fromUserName = fromUserName;
+    }
+
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getMsgType() {
+        return msgType;
+    }
+
+    public void setMsgType(String msgType) {
+        this.msgType = msgType;
+    }
+
+    public Long getMsgId() {
+        return msgId;
+    }
+
+    public void setMsgId(Long msgId) {
+        this.msgId = msgId;
+    }
+}

+ 122 - 0
src/main/java/com/ematou/wxservice/mp/message/WeChatMpXmlOutNewsMessage.java

@@ -0,0 +1,122 @@
+package com.ematou.wxservice.mp.message;
+
+import com.ematou.wxservice.common.constant.WeChatConstant;
+import com.ematou.wxservice.common.xml.converter.XStreamCDataConverter;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamConverter;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 图文信息响应
+ * @author lhm
+ * @version 1.0
+ * 2021-05-12 11:14
+ */
+@XStreamAlias("xml")
+public class WeChatMpXmlOutNewsMessage extends WeChatMpXmlOutMessage {
+
+    private static final long serialVersionUID = -7878689073807947544L;
+
+    /**
+     * 图文消息信息.
+     * 注意,如果图文数超过限制,则将只发限制内的条数
+     */
+    @XStreamAlias("Articles")
+    protected final List<Item> articles = new ArrayList<>();
+    /**
+     * 图文消息个数.
+     * 当用户发送文本、图片、视频、图文、地理位置这五种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息
+     */
+    @XStreamAlias("ArticleCount")
+    protected int articleCount;
+
+    public WeChatMpXmlOutNewsMessage() {
+        this.msgType = WeChatConstant.XmlMsgType.NEWS;
+    }
+
+    public void addArticle(Item item) {
+        this.articles.add(item);
+        this.articleCount = this.articles.size();
+    }
+
+    @XStreamAlias("item")
+    public static class Item implements Serializable {
+        private static final long serialVersionUID = -4971456355028904754L;
+
+        /**
+         * 图文消息标题.
+         */
+        @XStreamAlias("Title")
+        @XStreamConverter(value = XStreamCDataConverter.class)
+        private String title;
+
+        /**
+         * 图文消息描述.
+         */
+        @XStreamAlias("Description")
+        @XStreamConverter(value = XStreamCDataConverter.class)
+        private String description;
+
+        /**
+         * 图片链接.
+         * 支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
+         */
+        @XStreamAlias("PicUrl")
+        @XStreamConverter(value = XStreamCDataConverter.class)
+        private String picUrl;
+
+        /**
+         * 点击图文消息跳转链接.
+         */
+        @XStreamAlias("Url")
+        @XStreamConverter(value = XStreamCDataConverter.class)
+        private String url;
+
+        public String getTitle() {
+            return title;
+        }
+
+        public void setTitle(String title) {
+            this.title = title;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        public void setDescription(String description) {
+            this.description = description;
+        }
+
+        public String getPicUrl() {
+            return picUrl;
+        }
+
+        public void setPicUrl(String picUrl) {
+            this.picUrl = picUrl;
+        }
+
+        public String getUrl() {
+            return url;
+        }
+
+        public void setUrl(String url) {
+            this.url = url;
+        }
+    }
+
+    public List<Item> getArticles() {
+        return articles;
+    }
+
+    public int getArticleCount() {
+        return articleCount;
+    }
+
+    public void setArticleCount(int articleCount) {
+        this.articleCount = articleCount;
+    }
+}

+ 37 - 0
src/main/java/com/ematou/wxservice/mp/message/WeChatMpXmlOutTextMessage.java

@@ -0,0 +1,37 @@
+package com.ematou.wxservice.mp.message;
+
+import com.ematou.wxservice.common.constant.WeChatConstant;
+import com.ematou.wxservice.common.xml.converter.XStreamCDataConverter;
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+import com.thoughtworks.xstream.annotations.XStreamConverter;
+
+/**
+ * 文本消息响应
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 15:06
+ */
+@XStreamAlias("xml")
+public class WeChatMpXmlOutTextMessage extends WeChatMpXmlOutMessage {
+
+    private static final long serialVersionUID = 4348440573714051104L;
+
+    @XStreamAlias("Content")
+    @XStreamConverter(value = XStreamCDataConverter.class)
+    private String content;
+
+    public WeChatMpXmlOutTextMessage(){
+        this.msgType = WeChatConstant.XmlMsgType.TEXT;
+    }
+
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+
+}

+ 50 - 0
src/main/java/com/ematou/wxservice/mp/router/WeChatMessageHandlerRouter.java

@@ -0,0 +1,50 @@
+package com.ematou.wxservice.mp.router;
+
+import com.alibaba.fastjson.JSON;
+import com.ematou.wxservice.common.constant.WeChatConstant;
+import com.ematou.wxservice.mp.handler.*;
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 根据消息类型找到对应的处理类
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 16:23
+ */
+@Component
+public class WeChatMessageHandlerRouter {
+
+    private static final Map<String, WeChatMessageHandler> map = new ConcurrentHashMap<>();
+
+    public WeChatMessageHandlerRouter(
+            WeChatTextMessageHandler weChatTextMessageHandler,
+            WeChatClickEventMessageHandler weChatClickEventMessageHandler,
+            WeChatViewEventMessageHandler weChatViewEventMessageHandler,
+            WeChatNewsMessageHandler weChatNewsMessageHandler,
+            WeChatSubscribeEventHandler weChatSubscribeEventHandler
+    ){
+        map.put(WeChatConstant.XmlMsgType.TEXT, weChatTextMessageHandler);
+        map.put(WeChatConstant.EventType.CLICK.toLowerCase(), weChatClickEventMessageHandler);
+        map.put(WeChatConstant.EventType.VIEW.toLowerCase(), weChatViewEventMessageHandler);
+        map.put(WeChatConstant.XmlMsgType.NEWS, weChatNewsMessageHandler);
+        map.put(WeChatConstant.EventType.SUBSCRIBE, weChatSubscribeEventHandler);
+    }
+
+    public static WeChatMessageHandler router(WeChatMessage weChatMessage){
+
+        String msgType = weChatMessage.getMsgType();
+        // 如果是事件类型
+        if (WeChatConstant.XmlMsgType.EVENT.equalsIgnoreCase(msgType)) {
+            return map.get(weChatMessage.getEvent().toLowerCase());
+        }
+
+        return map.get(msgType);
+    }
+
+}

+ 38 - 0
src/main/java/com/ematou/wxservice/service/UserInfoService.java

@@ -0,0 +1,38 @@
+package com.ematou.wxservice.service;
+
+import com.ematou.wxservice.entity.pojo.UserInfo;
+import com.ematou.wxservice.mapper.UserInfoMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.HashMap;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-15 17:51
+ */
+@Service
+public class UserInfoService {
+
+    private static final Logger logger = LoggerFactory.getLogger(UserInfoService.class);
+
+    @Autowired
+    UserInfoMapper userInfoMapper;
+
+    public UserInfo get(String openId, String phoneNumber){
+
+        if (!StringUtils.hasLength(openId) && !StringUtils.hasLength(phoneNumber)) {
+            logger.error("查询用户数据时,openId和phoneNumber必须要带有有其中一个!");
+            return null;
+        }
+        HashMap<String, String> map = new HashMap<>();
+        map.put("openId", openId);
+        map.put("phoneNumber", phoneNumber);
+        return userInfoMapper.queryUserInfoByOpenIdOrPhoneNumber(map);
+    }
+
+}

+ 44 - 0
src/main/java/com/ematou/wxservice/service/WeChatMessageService.java

@@ -0,0 +1,44 @@
+package com.ematou.wxservice.service;
+
+import com.alibaba.fastjson.JSON;
+import com.ematou.wxservice.mp.message.WeChatMessage;
+import com.ematou.wxservice.mp.handler.WeChatMessageHandler;
+import com.ematou.wxservice.mp.message.WeChatMpXmlOutMessage;
+import com.ematou.wxservice.mp.router.WeChatMessageHandlerRouter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-11 16:19
+ */
+@Service
+public class WeChatMessageService {
+
+    private static final Logger logger = LoggerFactory.getLogger(WeChatMessageHandlerRouter.class);
+
+    /**
+     * 处理消息
+     * @param weChatMessage 微信推送的消息
+     * @return  响应给用户的消息
+     */
+    public WeChatMpXmlOutMessage handleMessage(WeChatMessage weChatMessage) {
+        logger.info("wxservice接收到微信平台推送的消息:" + JSON.toJSONString(weChatMessage));
+
+        WeChatMessageHandler handler = WeChatMessageHandlerRouter.router(weChatMessage);
+
+        if (null == handler) {
+            logger.warn("暂不支持" + weChatMessage.getMsgType() + "类型或事件的处理");
+            return null;
+        }
+
+        WeChatMpXmlOutMessage outMessage = handler.handlerMessage(weChatMessage);
+
+        logger.info("wxservice处理完响应给用户的消息:" + JSON.toJSONString(outMessage));
+        return outMessage;
+
+    }
+
+}

+ 196 - 0
src/main/java/com/ematou/wxservice/service/WeChatService.java

@@ -0,0 +1,196 @@
+package com.ematou.wxservice.service;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.ematou.wxservice.api.WeChatApi;
+import com.ematou.wxservice.api.WeChatApiRestTemplate;
+import com.ematou.wxservice.common.constant.WeChatConstant;
+import com.ematou.wxservice.config.WeChatGeneralConfig;
+import com.ematou.wxservice.entity.vo.AccessToken;
+import com.ematou.wxservice.entity.vo.TemplateMessage;
+import com.ematou.wxservice.entity.pojo.UserInfo;
+import com.ematou.wxservice.mapper.UserInfoMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author lhm
+ * @version 1.0
+ * 2021-05-10 18:04
+ */
+@Service
+public class WeChatService {
+
+    private final static Logger logger = LoggerFactory.getLogger(WeChatService.class);
+
+    @Autowired
+    WeChatGeneralConfig weChatGeneralConfig;
+
+    @Autowired
+    WeChatApiRestTemplate weChatApiRestTemplate;
+
+    @Autowired
+    UserInfoMapper userInfoMapper;
+
+    /**
+     * 获取普通AccessToken
+     * @return AccessToken
+     * @throws RuntimeException 异常
+     */
+    public AccessToken getAccessToken() throws RuntimeException {
+        String response = weChatApiRestTemplate.getForOther(WeChatApi.GET_TOKEN.getUrl());
+        AccessToken accessToken = JSON.parseObject(response).getObject("data", AccessToken.class);
+
+        if (null == accessToken) {
+            response = weChatApiRestTemplate.getForOther(WeChatApi.GENERAL_TOKEN.getUrl());
+            accessToken = JSON.parseObject(response).getObject("data", AccessToken.class);
+            if (null == accessToken) {
+                logger.error("微信基础服务可能已经宕机!response:" + response);
+                throw new RuntimeException("获取AccessToken失败,请稍后再试!");
+            }
+        }
+
+        return accessToken;
+    }
+
+    /**
+     * 获取用户信息
+     * @param openId openId
+     * @return 用户信息
+     * @throws RuntimeException 异常
+     */
+    public UserInfo getUserInfo(String openId) throws RuntimeException {
+        if (!StringUtils.hasLength(openId)) {
+            logger.error("获取用户信息的openId为空");
+            return null;
+        }
+        String accessToken = getAccessToken().getAccessToken();
+
+        UserInfo userInfo;
+        String response = null;
+        try {
+            response = weChatApiRestTemplate.getForOther(String.format(WeChatApi.GET_USER_INFO.getUrl(), accessToken, openId));
+            userInfo = JSON.parseObject(response, UserInfo.class);
+            userInfoMapper.insertUserInfo(userInfo);
+        } catch (RuntimeException e) {
+            logger.error("请求微信平台获取用户信息出错!response:" + response + ",error message:" + e.getMessage());
+            throw new RuntimeException("请求微信平台获取用户信息出错!");
+        }
+        return userInfo;
+    }
+
+    /**
+     * 根据模板编号获取模板消息ID
+     * @param templateNum 模板编号
+     * @see WeChatConstant.TemplateMsgId
+     * @return 模板消息id
+     */
+    public String getTemplateMsgId(String templateNum){
+
+        String url = String.format(WeChatApi.GET_TEMPLATE_MSGID.getUrl(), getAccessToken().getAccessToken());
+
+        String requestBody = "{ \"template_id_short\": \"" + templateNum + "\" }";
+
+        String res = weChatApiRestTemplate.postForRest(url, requestBody);
+
+        if (StringUtils.hasLength(res)) {
+            JSONObject response = JSON.parseObject(res);
+            String templateId = response.getObject("template_id", String.class);
+            logger.info("获取模板消息ID成功,template_id:" + templateId);
+            return templateId;
+        }
+        return "";
+    }
+
+    /**
+     * 发送取件模板信息
+     * @param msg 模板信息数据
+     * @return 是否发送成功
+     */
+    public Integer sendTemplateMsg(TemplateMessage msg){
+
+        String accessToken = getAccessToken().getAccessToken();
+        if (!StringUtils.hasLength(accessToken)) {
+            logger.error("发送模板消息时获取AccessToken值为空");
+            return 0;
+        }
+        // TODO 这里先写死,后期可以通过请求参数传过来,由客户端决定发送什么消息
+        String templateMsgId = WeChatConstant.TemplateMsgId.TAKE_PARCEL_CODE_TEMPLATE_MSG_ID;
+
+        String url = String.format(WeChatApi.SEND_TEMPLATE_MSG.getUrl(), accessToken);
+
+        // 组装消息数据
+        HashMap<String, Object> body = new HashMap<>();
+        body.put("touser", msg.getOpenId());
+        body.put("template_id", templateMsgId);
+        if (StringUtils.hasLength(msg.getUrl())) {
+            body.put("url", msg.getUrl());
+        }
+        if (null != msg.getMiniProgram()) {
+            body.put("miniprogram", msg.getMiniProgram());
+        }
+        HashMap<String, Object> data = new HashMap<>();
+        // 组装data数据
+        for (int i = 0; i < msg.getData().size(); i++) {
+            TemplateMessage.TemplateData templateData = msg.getData().get(i);
+            if (i == 0) {
+                data.put("first", templateData);
+                continue;
+            }
+            if (i == msg.getData().size()-1) {
+                data.put("remark", templateData);
+                break;
+            }
+            data.put("keyword" + i, templateData);
+        }
+        body.put("data", data);
+
+        String res;
+        try {
+            res = weChatApiRestTemplate.postForRest(url, body);
+
+            if (!StringUtils.hasLength(res)) {
+                return 0;
+            }
+        } catch (Exception e) {
+            logger.error("推送模板消息失败,exception message:" + e.getMessage());
+            return 0;
+        }
+
+        return 1;
+    }
+
+    /**
+     * 微信OAuth2.0授权,静默授权只获取openid
+     * @param code 请求参数, 临时凭证
+     * @return openid
+     */
+    public Map<String, Object> oauth2(String code){
+
+        HashMap<String, Object> map = new HashMap<>();
+        String url = String.format(WeChatApi.WECHAT_OAUTH_TOKEN.getUrl(), weChatGeneralConfig.getAppId(), weChatGeneralConfig.getAppSecret(), code);
+
+        String res = weChatApiRestTemplate.getForOther(url);
+
+        if (StringUtils.hasLength(res)) {
+
+            JSONObject jsonObject = JSON.parseObject(res);
+
+            Object openid = jsonObject.get("openid");
+
+            if (null != openid && StringUtils.hasLength(openid.toString())) {
+                map.put("openid", openid);
+            } else {
+                logger.error("授权出现错误! 错误信息: " + res);
+            }
+        }
+
+        return map;
+    }
+}

+ 30 - 0
src/main/resources/application.yml

@@ -0,0 +1,30 @@
+server:
+  port: 3030
+
+wx:
+  general:
+    appId: wxf9360d70bc1406ee
+    appSecret: 78413a82d0332ecbf7fdf475d0a8b08e
+#    appId: wx2215996b5fe1ec10
+#    appSecret: 8622b470fe3b779ffafe3baeb204fe41
+spring:
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: tuser
+    password: Qq!123
+    url: jdbc:mysql://120.76.84.45:3306/wx_base?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
+  thymeleaf:
+    cache: false
+    prefix: classpath:/templates/
+    suffix: .html
+    check-template-location: true
+    encoding: UTF-8
+    mode: HTML
+    servlet:
+      content-type: text/html
+mybatis:
+  mapper-locations: classpath:mybatis/*.xml
+  configuration:
+    map-underscore-to-camel-case: true
+logging:
+  config: classpath:logback.xml

+ 56 - 0
src/main/resources/logback.xml

@@ -0,0 +1,56 @@
+<configuration>
+    <!-- %m输出的信息, %p日志级别, %t线程名, %d日期, %c类的全名, %i索引 -->
+    <!-- appender是configuration的子节点,是负责写日志的组件 -->
+    <!-- ConsoleAppender把日志输出到控制台 -->
+    <!--    <property name="CONSOLE_LOG_PATTERN" -->
+    <!--               value="%date{yyyy-MM-dd HH:mm:ss} | %highlight(%-5level) | %boldYellow(%thread) | %boldGreen(%logger) | %msg%n"/> -->
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <!--<pattern>${CONSOLE_LOG_PATTERN}</pattern> -->
+            <pattern>%date{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) (%file:%line\)- %m%n</pattern>
+            <!-- 控制台也要使用utf-8,不要使用gbk -->
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
+    <!-- 1.先按日期存日志,日期变了,将前一天的日志文件名重命名为xxx%日期%索引,新的日志仍然是sys.log -->
+    <!-- 2.如果日期没有变化,但是当前日志文件的大小超过1kb时,对当前日志进行分割 重名名 -->
+    <appender name="syslog" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!--<File>${catalina.base}/mylog/sys.log</File>-->
+<!--        <File>/app/project/wx_base/logs/wxbase.log</File>-->
+        <File>logs/wxservice.log</File>
+        <!-- rollingPolicy:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 -->
+        <!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
+            <!--<fileNamePattern>${catalina.base}/mylog/sys.%d.%i.log</fileNamePattern>-->
+<!--            <fileNamePattern>${catalina.base}/%d/wxservice.%d.%i.log</fileNamePattern>-->
+            <fileNamePattern>logs/wxservice.%d.%i.log</fileNamePattern>
+            <!-- 每产生一个日志文件,该日志文件的保存期限为30天 -->
+            <maxHistory>30</maxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy  class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
+                <maxFileSize>10MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+        <encoder>
+            <!-- pattern节点,用来设置日志的输入格式 -->
+            <pattern>
+                %d %p (%file:%line\)- %m%n
+            </pattern>
+            <!-- 记录日志的编码 -->
+            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
+        </encoder>
+    </appender>
+    <!-- 控制台日志输出级别 -->
+    <root level="INFO">
+        <appender-ref ref="STDOUT" />
+    </root>
+    <!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
+    <!-- com.appley为根包,也就是只要是发生在这个根包下面的所有日志操作行为的权限都是DEBUG -->
+    <!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE  -->
+    <logger name="com.ematou.wxservice" level="INFO">
+        <appender-ref ref="syslog" />
+    </logger>
+</configuration>

+ 33 - 0
src/main/resources/mybatis/UserInfoMapper.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ematou.wxservice.mapper.UserInfoMapper">
+
+    <sql id="Base_Column_List">
+        open_id,union_id,group_id,nick_name,sex,city,country,province,language,head_img_url,subscribe_time,tagid_list,
+        subscribe,remark,subscribe_scene,qr_scene,qr_scene_str,creator_id,create_time,moder_id,mod_time,tmst
+    </sql>
+
+    <insert id="insertUserInfo" >
+        insert into user_info ( <include refid="Base_Column_List"/> )
+        values (#{userInfo.openId}, #{userInfo.unionId}, #{userInfo.groupId}, #{userInfo.nickName}, #{userInfo.sex}, #{userInfo.city},
+        #{userInfo.country}, #{userInfo.province}, #{userInfo.language}, #{userInfo.headImgUrl}, #{userInfo.subscribeTime}, #{userInfo.tagIdList},
+        #{userInfo.subscribe}, #{userInfo.remark}, #{userInfo.subscribeScene}, #{userInfo.qrScene}, #{userInfo.qrSceneStr}, #{userInfo.creatorId},
+        #{userInfo.createTime},  #{userInfo.moderId}, #{userInfo.modTime}, #{userInfo.tmst})
+    </insert>
+
+    <select id="queryUserInfoByOpenIdOrPhoneNumber" resultType="com.ematou.wxservice.entity.pojo.UserInfo">
+        select
+        <include refid="Base_Column_List"/>
+        from user_info
+        where 1=1
+        <if test="param.openId != null">
+            and open_id = #{param.openId}
+        </if>
+        <if test="param.phoneNumber != null">
+            and tell_phone_number = #{param.phoneNumber}
+        </if>
+    </select>
+
+</mapper>

+ 1 - 0
src/main/resources/static/MP_verify_ut2mRugwSXiH7xQy.txt

@@ -0,0 +1 @@
+ut2mRugwSXiH7xQy

+ 303 - 0
src/main/resources/static/css/style.css

@@ -0,0 +1,303 @@
+/* 公共样式表css */
+html,body {
+    color: #333;
+    margin: 0;
+    height: 100%;
+    font-family: "Myriad Set Pro","Helvetica Neue",Helvetica,Arial,Verdana,sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    font-weight: normal;
+}
+
+* {
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+
+a {
+    text-decoration: none;
+    color: #000;
+}
+
+a, label, button, input, select {
+    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+img {
+    border: 0;
+}
+
+body {
+    background: #f7f7f7;
+    color: #666;
+}
+
+html, body, div, dl, dt, dd, ol, ul, li, h1, h2, h3, h4, h5, h6, p, blockquote, pre, button, fieldset, form, input, legend, textarea, th, td {
+    margin: 0;
+    padding: 0;
+}
+
+a {
+    text-decoration: none;
+    color: #08acee;
+}
+
+button {
+    outline: 0;
+}
+
+img {
+    border: 0;
+}
+
+button,input,optgroup,select,textarea {
+    margin: 0;
+    font: inherit;
+    color: inherit;
+    outline: none;
+}
+
+li {
+    list-style: none;
+}
+
+a {
+    color: #666;
+}
+
+.clearfix::after {
+    clear: both;
+    content: ".";
+    display: block;
+    height: 0;
+    visibility: hidden;
+}
+
+.clearfix {
+}
+
+/* 必要布局样式css */
+.aui-flexView {
+    width: 100%;
+    height: 100%;
+    margin: 0 auto;
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-box-direction: normal;
+    -webkit-flex-direction: column;
+    -ms-flex-direction: column;
+    flex-direction: column;
+}
+
+.aui-scrollView {
+    width: 100%;
+    height: 100%;
+    -webkit-box-flex: 1;
+    -webkit-flex: 1;
+    -ms-flex: 1;
+    flex: 1;
+    overflow-y: auto;
+    overflow-x: hidden;
+    -webkit-overflow-scrolling: touch;
+    /* position: relative; */
+    margin-bottom: -1px;
+}
+
+.aui-navBar {
+    height: 44px;
+    position: relative;
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+    background-color: rgba(255, 255, 255, 0.98);
+}
+
+.aui-navBar-item {
+    height: 44px;
+    min-width: 25%;
+    -webkit-box-flex: 0;
+    -webkit-flex: 0 0 25%;
+    -ms-flex: 0 0 25%;
+    flex: 0 0 25%;
+    padding: 0 0.9rem;
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-align: center;
+    -webkit-align-items: center;
+    -ms-flex-align: center;
+    align-items: center;
+    font-size: 0.3rem;
+    white-space: nowrap;
+    overflow: hidden;
+    color: #5C5C5C;
+    position: relative;
+}
+
+.aui-navBar-item:first-child {
+    -webkit-box-ordinal-group: 2;
+    -webkit-order: 1;
+    -ms-flex-order: 1;
+    order: 1;
+    margin-right: -25%;
+}
+
+.aui-navBar-item:last-child {
+    -webkit-box-ordinal-group: 4;
+    -webkit-order: 3;
+    -ms-flex-order: 3;
+    order: 3;
+    -webkit-box-pack: end;
+    -webkit-justify-content: flex-end;
+    -ms-flex-pack: end;
+    justify-content: flex-end;
+}
+
+.aui-center {
+    -webkit-box-ordinal-group: 3;
+    -webkit-order: 2;
+    -ms-flex-order: 2;
+    order: 2;
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-pack: center;
+    -webkit-justify-content: center;
+    -ms-flex-pack: center;
+    justify-content: center;
+    -webkit-box-align: center;
+    -webkit-align-items: center;
+    -ms-flex-align: center;
+    align-items: center;
+    height: 44px;
+    width: 50%;
+    margin-left: 25%;
+}
+
+.aui-center-title {
+    text-align: center;
+    width: 100%;
+    white-space: nowrap;
+    overflow: hidden;
+    display: block;
+    text-overflow: ellipsis;
+    font-size: 0.95rem;
+    color: #232326;
+}
+
+.icon {
+    width: 20px;
+    height: 20px;
+    display: block;
+    border: none;
+    float: left;
+    background-size: 20px;
+    background-repeat: no-repeat;
+}
+
+.icon-return {
+    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAACqklEQVRoQ+XbP4sTQRQA8PfWS5EvI6KdIMJhE8I8UOysLE6wk7O5b2B5nIiF4BVqoQH/MLN9PoCgNtb6DQ4uqbR4x+guLCG72dudnfd2N1WKTJJf3nuTtzOzCMofy+Vyb7VanSDiI2ZmAHhhjHmKiP75pR946RERB3jser3+CABU/FhmPiSi4yZfRS14sVhcmU6nnzaxHsnM34no+mDAGfY9ANwvQX02xtwbBHgXlplXk8nk6mw2+917cA3sGgD2iehrE6wfo6aGY2DVgJk5SdP0Q1nNMnPryOYZIR7hDPsGAB6UpOk5M99pk8bF9xUF18EmSXJ7Pp//aFqzm+PEwBJYsRqWwoqAmRnTNH1bVbOh01ishjPsawB4WDZBdYmNGmEN2GhgLdgoYE3YKGDn3KlkzUb9H7bWvkTEx1IT1LbP7azx2IE9S5JkP2QHVbcT6wS8C8vMt4joZ90vGfJ1wcGascEnLe3YoGBr7TEiPilJvzPJNA7eWvYFGyTCfcK2BvcN2wrsnHtXcYmnpmaDdFrW2meIeKR9ggrWaVlr/yDiZMsb+gW3m1JNRZ0GpVHj4Zz7CwB7YwK/AoCDHPx/FxMA8d/vp7Z+W01a1tpTRCxbqlGLbpTSeWRH9bfUV3SrCPcRHQTs4X1J72DgDF21pKNiIgsK7gM6OFg7uhOwZnRn4DpoiZXLTsEePaqFeA8e3VaLNnTnKV24ovIb4aJ7w62ulupcbG++Rnr3Pzo4S29/JqvymFKXpwCipXQx2qM61FKoaZFIi0RYEi0KLtR06TlLADgPWdPiYI/edZI2JFoFOCZaDTgWWhW4gN56c0c22fndjWtE9KtJ86MO7BFlt+8UgF+MMXcHA66B/maMuTEocBV6kDdq5dHLbsV7jogH+a14RHTYJLp+zAV6HRFbKTyUfAAAAABJRU5ErkJggg==");
+}
+
+.aui-code-box {
+    padding: 40px;
+    text-align: center;
+}
+
+.aui-code-box button {
+    background: #39bc30;
+    height: 45px;
+    line-height: 45px;
+    border: none;
+    color: #c5e9b3;
+    border-radius: 30px;
+    width: 100%;
+    font-size: 0.98rem;
+}
+
+.aui-code-box h2 {
+    color: #333333;
+    font-size: 1.4rem;
+    font-weight: normal;
+}
+
+.aui-code-box p {
+    color: #cccccc;
+    font-size: 0.85rem;
+    /* position: relative; */
+    width: 100%;
+    /* padding-top: 10px; */
+}
+
+.aui-code-line {
+    position: relative;
+}
+
+.aui-code-line:after {
+    content: '';
+    position: absolute;
+    z-index: 0;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 1px;
+    border-bottom: 1px solid #D9D9D9;
+    -webkit-transform: scaleY(0.5);
+    transform: scaleY(0.5);
+    -webkit-transform-origin: 0 100%;
+    transform-origin: 0 100%;
+}
+
+.aui-code-line-input {
+    padding: 10px 0;
+    height: 50px;
+    width: 100%;
+    font-size: 14px;
+    background: none;
+    border: none;
+    color: #333;
+}
+
+.aui-btn-default {
+    float: right;
+    position: absolute;
+    right: -9px;
+    top: 39px;
+    z-index: 10002;
+    color: #39bc30;
+    background: none;
+    border: none;
+    margin-top: -35px;
+    width:100px;
+    height:40px;
+}
+
+.aui-code-line em {
+    position: absolute;
+    left: 0;
+    top: 17px;
+    color: #333;
+    font-style: normal;
+    font-size: 14px;
+}
+
+.aui-code-btn {
+    padding-top: 30px;
+}
+
+.aui-code-box form {
+    padding-top: 50px;
+    position: relative;
+}
+
+
+.aui-code-line-clear span a{
+    text-indent:-999px;
+}

+ 338 - 0
src/main/resources/static/css/style_return.css

@@ -0,0 +1,338 @@
+/*
+<!--
+
+* 17素材vip建站专区模块代码
+* 详尽信息请看官网:http://www.17sucai.com/pins/vip
+*
+* Copyright , 温州易站网络科技有限公司版权所有
+*
+* 请尊重原创,未经允许请勿转载。
+* 在保留版权的前提下可应用于个人或商业用途
+
+-->
+*/
+html,body {
+    color: #333;
+    margin: 0;
+    height: 100%;
+    font-family: "Myriad Set Pro","Helvetica Neue",Helvetica,Arial,Verdana,sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    font-weight: normal;
+}
+
+* {
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+
+a {
+    text-decoration: none;
+    color: #000;
+}
+
+a, label, button, input, select {
+    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+img {
+    border: 0;
+}
+
+body {
+    background: #fff;
+    color: #666;
+}
+
+html, body, div, dl, dt, dd, ol, ul, li, h1, h2, h3, h4, h5, h6, p, blockquote, pre, button, fieldset, form, input, legend, textarea, th, td {
+    margin: 0;
+    padding: 0;
+}
+
+a {
+    text-decoration: none;
+    color: #08acee;
+}
+
+button {
+    outline: 0;
+}
+
+img {
+    border: 0;
+}
+
+button,input,optgroup,select,textarea {
+    margin: 0;
+    font: inherit;
+    color: inherit;
+    outline: none;
+}
+
+li {
+    list-style: none;
+}
+
+a {
+    color: #666;
+}
+
+.clearfix::after {
+    clear: both;
+    content: ".";
+    display: block;
+    height: 0;
+    visibility: hidden;
+}
+
+.clearfix {
+}
+
+
+.divHeight {
+    width: 100%;
+    height: 10px;
+    background: #f5f5f5;
+    position: relative;
+    overflow: hidden;
+}
+
+.r-line {
+    position: relative;
+}
+
+.r-line:after {
+    content: '';
+    position: absolute;
+    z-index: 0;
+    top: 0;
+    right: 0;
+    height: 100%;
+    border-right: 1px solid #D9D9D9;
+    -webkit-transform: scaleX(0.5);
+    transform: scaleX(0.5);
+    -webkit-transform-origin: 100% 0;
+    transform-origin: 100% 0;
+}
+
+.b-line {
+    position: relative;
+}
+
+.b-line:after {
+    content: '';
+    position: absolute;
+    z-index: 2;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 1px;
+    border-bottom: 1px solid #e2e2e2;
+    -webkit-transform: scaleY(0.5);
+    transform: scaleY(0.5);
+    -webkit-transform-origin: 0 100%;
+    transform-origin: 0 100%;
+}
+
+.aui-flex {
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-align: center;
+    -webkit-align-items: center;
+    align-items: center;
+    padding: 15px;
+    position: relative;
+}
+
+.aui-flex-box {
+    -webkit-box-flex: 1;
+    -webkit-flex: 1;
+    flex: 1;
+    min-width: 0;
+    font-size: 14px;
+    color: #333;
+}
+
+
+/* 必要布局样式css */
+.aui-flexView {
+    width: 100%;
+    height: 100%;
+    margin: 0 auto;
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-box-direction: normal;
+    -webkit-flex-direction: column;
+    -ms-flex-direction: column;
+    flex-direction: column;
+}
+
+.aui-scrollView {
+    width: 100%;
+    height: 100%;
+    -webkit-box-flex: 1;
+    -webkit-flex: 1;
+    -ms-flex: 1;
+    flex: 1;
+    overflow-y: auto;
+    overflow-x: hidden;
+    -webkit-overflow-scrolling: touch;
+    position: relative;
+    margin-top:0;
+}
+
+.aui-navBar {
+    height: 44px;
+    position: relative;
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+    z-index: 1002;
+    background: #fff;
+}
+
+
+.aui-navBar-item {
+    height: 44px;
+    min-width: 25%;
+    -webkit-box-flex: 0;
+    -webkit-flex: 0 0 25%;
+    -ms-flex: 0 0 25%;
+    flex: 0 0 25%;
+    padding: 0 0.9rem;
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-align: center;
+    -webkit-align-items: center;
+    -ms-flex-align: center;
+    align-items: center;
+    font-size: 0.7rem;
+    white-space: nowrap;
+    overflow: hidden;
+    color: #808080;
+    position: relative;
+}
+
+.aui-navBar-item:first-child {
+    -webkit-box-ordinal-group: 2;
+    -webkit-order: 1;
+    -ms-flex-order: 1;
+    order: 1;
+    margin-right: -25%;
+    font-size: 0.9rem;
+    font-weight: bold;
+}
+
+.aui-navBar-item:last-child {
+    -webkit-box-ordinal-group: 4;
+    -webkit-order: 3;
+    -ms-flex-order: 3;
+    order: 3;
+    -webkit-box-pack: end;
+    -webkit-justify-content: flex-end;
+    -ms-flex-pack: end;
+    justify-content: flex-end;
+}
+
+.aui-center {
+    -webkit-box-ordinal-group: 3;
+    -webkit-order: 2;
+    -ms-flex-order: 2;
+    order: 2;
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-pack: center;
+    -webkit-justify-content: center;
+    -ms-flex-pack: center;
+    justify-content: center;
+    -webkit-box-align: center;
+    -webkit-align-items: center;
+    -ms-flex-align: center;
+    align-items: center;
+    height: 44px;
+    width: 50%;
+    margin-left: 25%;
+}
+
+.aui-center-title {
+    text-align: center;
+    width: 100%;
+    white-space: nowrap;
+    overflow: hidden;
+    display: block;
+    text-overflow: ellipsis;
+    font-size: 0.95rem;
+    color: #333;
+}
+
+.icon {
+    width: 20px;
+    height: 20px;
+    display: block;
+    border: none;
+    float: left;
+    background-size: 20px;
+    background-repeat: no-repeat;
+}
+
+.icon-return{
+    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAACh0lEQVRoQ+3ZMW8TMRQH8PcOsTBQISExVCIRCwgWFvZ+hZYBpIrEcTYkBsTGlI2VDamL3YEBpWJoR1BJaOlXYEzyDYp0bMRGRjnJoDuWs/1e3EbKeHf55b33vzsb4YJ98IJ54RKce8XZV1gIcQ8AviDiD2vtC631pzZFYQ2WUt611p4CwM0VcqqU2soSXIN1ztdKqTfZgRuw75RSz9tg3bHsWjomlh04NpYVOAWWDbjf7z8oimLipbH7bUopJdvO7L/Hk8+wwyLiCSLe8H6cww4BwGYFTo0lbWkKLBmYCksCrsNaa99rrZ/FmFnS0GrCdrvd3mg0MqEDqu58yVKaAzZZSwshHgLAsX/rcW2csrJVtaNXeIWdIuL16qJU2OgVrsMCwEGn03mSamaThVYTtizLp+PxeJkioJKFFldslJbmjA0O5o4NCm7AHpZluUM5s1FCSwjxCAA++7ceADiczWaPJ5PJL6qAihJaUspdY8w+Il7xLsASG6SlhRA/EfGa91BxNJ/Pd7hVNtiT1mAwOAeADXdCa637Hi0Wi3zBUsptY8wHRLzqwIh/nlbzbWmna0roLEOrmo11QQd9W1oHdFDwOrR3cLCH/uuFn0uQRQE7dMPKJHl6RwNzRUcF/w9N9VIRHVyhazbLDihWP5KAHbphOzQ5OhmYCzopmAM6OZgaTQL20FMAuOUtHESfaTLwCn3HWnuWEk0KpkCTg1OjWYArtDHmKyJu+jMdeh+KDdghh8NhZ7lcfvPRoXcaWYFToNmBY6NZgj20m+nb3pr3W631yzY7GWzBDtXr9TaLojjz0N+VUvezBXvoPQBw0Fda649Zg9vg6o5l3dKhse58l+AY/yqnc/4GvNDoTFOq8FwAAAAASUVORK5CYII=");
+
+}
+
+.aui-back-pitch{
+    width:50px;
+    height:50px;
+    margin:0 auto;
+}
+
+.aui-back-pitch img{
+    width:50px;
+    height:50px;
+    display:block;
+    border:none;
+}
+
+.aui-back-box{
+    height:auto;
+    position:relative;
+    overflow:hidden;
+    text-align:center;
+    padding-top:40px;
+    padding-left:20px;
+    padding-right:20px;
+}
+
+.aui-back-title{
+    padding-top:30px;
+    padding-bottom:40px;
+}
+.aui-back-title h2{
+    font-size:16px;
+    color:#333;
+    padding-bottom:5px;
+}
+
+.aui-back-title p{
+    font-size:14px;
+}
+
+.aui-back-button button{
+    width:100%;
+    height:40px;
+    line-height:40px;
+    background:#39bc30;
+    color:#fff;
+    border:none;
+    border-radius:3px;
+}

BIN
src/main/resources/static/images/icon-pitch.png


+ 151 - 0
src/main/resources/static/js/clean.js

@@ -0,0 +1,151 @@
+(function($) {
+    $.fn.extend({
+        addClear: function(options) {
+            var options = $.extend({
+                closeSymbol: "&#10006;",
+                color: "#CCC",
+                top: 1,
+                right: 4,
+                returnFocus: true,
+                showOnLoad: false,
+                onClear: null
+            }, options);
+
+            $(this).wrap("<span style='position:relative;' class='add-clear-span'>");
+            $(this).after("<a href='#clear'>" + options.closeSymbol + "</a>");
+            $("a[href='#clear']").css({
+                color: options.color,
+                'text-decoration': 'none',
+                display: 'none',
+                'line-height': 1,
+                overflow: 'hidden',
+                position: 'absolute',
+                right: options.right,
+                top: options.top
+            }, this);
+
+            if ($(this).val().length >= 1 && options.showOnLoad === true) {
+                $(this).siblings("a[href='#clear']").show();
+            }
+
+            $(this).keyup(function() {
+                if ($(this).val().length >= 1) {
+                    $(this).siblings("a[href='#clear']").show();
+                } else {
+                    $(this).siblings("a[href='#clear']").hide();
+                }
+            });
+
+            $("a[href='#clear']").click(function() {
+                $(this).siblings("input").val("");
+                $(this).hide();
+                if (options.returnFocus === true) {
+                    $(this).siblings("input").focus();
+                }
+                if (options.onClear) {
+                    options.onClear($(this).siblings("input"));
+                }
+                return false;
+            });
+            return this;
+        }
+    });
+}
+)(jQuery);
+
+var phoneReg = /(^1[3|4|5|7|8]\d{9}$)|(^09\d{8}$)/;
+var count = 60;
+var InterValObj1;
+var curCount1;
+function sendMessage1() {
+    curCount1 = count;
+    var phone = $.trim($('#phone1').val());
+    if (!phoneReg.test(phone)) {
+        alert(" 请输入有效的手机号码");
+        return false;
+    }
+    $("#btnSendCode1").attr("disabled", "true");
+    $("#btnSendCode1").val(+curCount1 + "秒再获取");
+    InterValObj1 = window.setInterval(SetRemainTime1, 1000);
+
+}
+function SetRemainTime1() {
+    if (curCount1 == 0) {
+        window.clearInterval(InterValObj1);
+        $("#btnSendCode1").removeAttr("disabled");
+        $("#btnSendCode1").val("重新发送");
+    } else {
+        curCount1--;
+        $("#btnSendCode1").val(+curCount1 + "秒再获取");
+    }
+}
+
+submitForm = function () {
+    var flag = checkInput();
+
+    if (flag) {
+        var openid = getUrlParam("openid");
+        $("#btnSendCode2").val(openid);
+        $("#submit_form").submit();
+    }
+}
+checkInput = function (){
+    var a = true;
+    var code = $("#code1").val;
+    if ($.trim(code) === '') {
+        a = false;
+        alert("请输入验证码!");
+        return a;
+    }
+
+    return a;
+}
+
+
+// 强制关注公众号,获取openid
+getCode = function () {
+    if (sessionStorage.getItem("openid")&&sessionStorage.getItem("openid")!="undefined") {
+        return false;
+    }
+    var code = getUrlParam('code') // 截取路径中的code,如果没有就去微信授权,如果已经获取到了就直接传code给后台获取openId
+    var local = window.location.href;
+    var APPID = 'wxf9360d70bc1406ee';
+    if (code == null || code === '') {
+        window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' + APPID + '&redirect_uri=' + encodeURIComponent(local) + '&response_type=code&scope=snsapi_base&state=#wechat_redirect'
+    } else {
+        getOpenId(code) //把code传给后台获取用户信息
+    }
+}
+//把code传给后台,得到openid
+getOpenId = function (code) {
+    $.ajax({
+        type: 'GET',
+        dataType: 'text',
+        url: 'http://f3dhion.nat.ipyingshe.com/oauth2?code='+code,
+        success: function (res) {
+            if (res.status == -1) {
+                // 提示没有关注公众号 没有关注公众号跳转到关注公众号页面
+                console.log('您还未关注公众号喔');
+                //二维码弹窗
+                $('.openPopup').click();
+                return;
+            } else {
+                // 本地存储这个openid,并刷新页面
+                sessionStorage.setItem("openid", res.data.openid);
+                location.reload();
+            }
+        }
+    });
+}
+//获取地址栏的参数
+getUrlParam= function (name) {
+    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+    var r = window.location.search.substr(1).match(reg);
+    if (r != null) return unescape(r[2]); return null;
+}
+//页面执行调用
+getCode();
+window.scroll(0, 0);
+function binding() {
+    alert(1)
+}

File diff suppressed because it is too large
+ 3 - 0
src/main/resources/static/js/jquery.min.js


+ 72 - 0
src/main/resources/templates/binding.html

@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8">
+        <title>绑定手机号</title>
+        <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" name="viewport"/>
+        <meta content="yes" name="apple-mobile-web-app-capable"/>
+        <meta content="black" name="apple-mobile-web-app-status-bar-style"/>
+        <meta content="telephone=no" name="format-detection"/>
+        <link href="http://f3dhion.nat.ipyingshe.com/css/style.css" rel="stylesheet" type="text/css"/>
+        <script type="text/javascript" src="http://f3dhion.nat.ipyingshe.com/js/jquery.min.js"></script>
+        <script type="text/javascript" src="http://f3dhion.nat.ipyingshe.com/js/clean.js"></script>
+    </head>
+    <body>
+
+
+    <!--
+
+     * 17素材vip建站专区模块代码
+     * 详尽信息请看官网:http://www.17sucai.com/pins/vip
+     *
+     * Copyright , 温州易站网络科技有限公司版权所有
+     *
+     * 请尊重原创,未经允许请勿转载。
+     * 在保留版权的前提下可应用于个人或商业用途
+
+    -->
+
+        <section class="aui-flexView">
+            <header class="aui-navBar aui-navBar-fixed">
+                <a href="javascript:;" class="aui-navBar-item">
+                    <i class="icon icon-return"></i>
+                </a>
+                <div class="aui-center">
+                    <span class="aui-center-title"></span>
+                </div>
+                <a href="javascript:;" class="aui-navBar-item">
+                    <i class="icon icon-news"></i>
+                </a>
+            </header>
+            <section class="aui-scrollView">
+                <div class="aui-code-box">
+                    <h2>绑定手机</h2>
+                    <p>登录及找回密码的途径</p>
+                    <form id="submit_form" action="http://f3dhion.nat.ipyingshe.com/binding" method="post">
+                        <p class="aui-code-line">
+                            <em>+86</em>
+                            <input type="text" class="aui-code-line-input" name="phone1" value="" id="phone1" autocomplete="off" placeholder="请输入已绑定的手机号" style="padding-left:28px;"/>
+                        </p>
+                        <p class="aui-code-line aui-code-line-clear">
+                            <input id="code1" name="code1" type="text" class="aui-code-line-input" autocomplete="off" placeholder="短信验证码"/>
+                            <input id="btnSendCode1" name="btnSendCode1" type="button" class="aui-btn-default" value="获取验证码" onClick="sendMessage1()"/>
+                            <input id="btnSendCode2" name="btnSendCode2" type="hidden" value=""/>
+                        </p>
+                        <p class="aui-code-btn">
+                            <button onclick="submitForm()" type="button" id="submit_btn">绑定</button>
+                        </p>
+                    </form>
+                </div>
+            </section>
+        </section>
+        <script type="text/javascript" charset="utf-8">
+            $(function(){
+
+                $(".aui-code-line-input").addClear();
+
+
+            });
+        </script>
+    </body>
+</html>
+

+ 245 - 0
src/main/resources/templates/hello.html

@@ -0,0 +1,245 @@
+<!DOCTYPE html>
+<html lang="en" >
+<head>
+    <meta charset="UTF-8">
+
+    <style type="text/css" >
+        .nav {
+            padding-left: 0;
+            margin-bottom: 0;
+            list-style: none;
+        }
+        .nav > li {
+            position: relative;
+            display: block;
+        }
+        .nav > li > a {
+            position: relative;
+            display: block;
+            padding: 10px 15px;
+        }
+        .nav > li > a:hover,
+        .nav > li > a:focus {
+            text-decoration: none;
+            background-color: #eeeeee;
+        }
+        .nav > li.disabled > a {
+            color: #777777;
+        }
+        .nav > li.disabled > a:hover,
+        .nav > li.disabled > a:focus {
+            color: #777777;
+            text-decoration: none;
+            cursor: not-allowed;
+            background-color: transparent;
+        }
+        .nav .open > a,
+        .nav .open > a:hover,
+        .nav .open > a:focus {
+            background-color: #eeeeee;
+            border-color: #337ab7;
+        }
+        .nav .nav-divider {
+            height: 1px;
+            margin: 9px 0;
+            overflow: hidden;
+            background-color: #e5e5e5;
+        }
+        .nav > li > a > img {
+            max-width: none;
+        }
+        .nav-tabs {
+            border-bottom: 1px solid #ddd;
+        }
+        .nav-tabs > li {
+            float: left;
+            margin-bottom: -1px;
+        }
+        .nav-tabs > li > a {
+            margin-right: 2px;
+            line-height: 1.42857143;
+            border: 1px solid transparent;
+            border-radius: 4px 4px 0 0;
+        }
+        .nav-tabs > li > a:hover {
+            border-color: #eeeeee #eeeeee #ddd;
+        }
+        .nav-tabs > li.active > a,
+        .nav-tabs > li.active > a:hover,
+        .nav-tabs > li.active > a:focus {
+            color: #555555;
+            cursor: default;
+            background-color: #fff;
+            border: 1px solid #ddd;
+            border-bottom-color: transparent;
+        }
+        .nav-tabs.nav-justified {
+            width: 100%;
+            border-bottom: 0;
+        }
+        .nav-tabs.nav-justified > li {
+            float: none;
+        }
+        .nav-tabs.nav-justified > li > a {
+            margin-bottom: 5px;
+            text-align: center;
+        }
+        .nav-tabs.nav-justified > .dropdown .dropdown-menu {
+            top: auto;
+            left: auto;
+        }
+        @media (min-width: 768px) {
+            .nav-tabs.nav-justified > li {
+                display: table-cell;
+                width: 1%;
+            }
+            .nav-tabs.nav-justified > li > a {
+                margin-bottom: 0;
+            }
+        }
+        .nav-tabs.nav-justified > li > a {
+            margin-right: 0;
+            border-radius: 4px;
+        }
+        .nav-tabs.nav-justified > .active > a,
+        .nav-tabs.nav-justified > .active > a:hover,
+        .nav-tabs.nav-justified > .active > a:focus {
+            border: 1px solid #ddd;
+        }
+        @media (min-width: 768px) {
+            .nav-tabs.nav-justified > li > a {
+                border-bottom: 1px solid #ddd;
+                border-radius: 4px 4px 0 0;
+            }
+            .nav-tabs.nav-justified > .active > a,
+            .nav-tabs.nav-justified > .active > a:hover,
+            .nav-tabs.nav-justified > .active > a:focus {
+                border-bottom-color: #fff;
+            }
+        }
+        .nav-pills > li {
+            float: left;
+        }
+        .nav-pills > li > a {
+            border-radius: 4px;
+        }
+        .nav-pills > li + li {
+            margin-left: 2px;
+        }
+        .nav-pills > li.active > a,
+        .nav-pills > li.active > a:hover,
+        .nav-pills > li.active > a:focus {
+            color: #fff;
+            background-color: #337ab7;
+        }
+        .nav-stacked > li {
+            float: none;
+        }
+        .nav-stacked > li + li {
+            margin-top: 2px;
+            margin-left: 0;
+        }
+        .nav-justified {
+            width: 100%;
+        }
+        .nav-justified > li {
+            float: none;
+        }
+        .nav-justified > li > a {
+            margin-bottom: 5px;
+            text-align: center;
+        }
+        .nav-justified > .dropdown .dropdown-menu {
+            top: auto;
+            left: auto;
+        }
+        @media (min-width: 768px) {
+            .nav-justified > li {
+                display: table-cell;
+                width: 1%;
+            }
+            .nav-justified > li > a {
+                margin-bottom: 0;
+            }
+        }
+        .nav-tabs-justified {
+            border-bottom: 0;
+        }
+        .nav-tabs-justified > li > a {
+            margin-right: 0;
+            border-radius: 4px;
+        }
+        .nav-tabs-justified > .active > a,
+        .nav-tabs-justified > .active > a:hover,
+        .nav-tabs-justified > .active > a:focus {
+            border: 1px solid #ddd;
+        }
+        @media (min-width: 768px) {
+            .nav-tabs-justified > li > a {
+                border-bottom: 1px solid #ddd;
+                border-radius: 4px 4px 0 0;
+            }
+            .nav-tabs-justified > .active > a,
+            .nav-tabs-justified > .active > a:hover,
+            .nav-tabs-justified > .active > a:focus {
+                border-bottom-color: #fff;
+            }
+        }
+        .nav:before,
+        .nav:after {
+            display: table;
+            content: " ";
+        }
+        .nav:after {
+            clear: both;
+        }
+        nav {
+            display: none;
+        }
+        .nav-tabs .dropdown-menu {
+            margin-top: -1px;
+            border-top-left-radius: 0;
+            border-top-right-radius: 0;
+        }
+        .nav-tabs-justified {
+            border-bottom: 0;
+        }
+        .nav-tabs-justified > li > a {
+            margin-right: 0;
+            border-radius: 4px;
+        }
+        .nav-tabs-justified > .active > a,
+        .nav-tabs-justified > .active > a:hover,
+        .nav-tabs-justified > .active > a:focus {
+            border: 1px solid #ddd;
+        }
+        @media (min-width: 768px) {
+            .nav-tabs-justified > li > a {
+                border-bottom: 1px solid #ddd;
+                border-radius: 4px 4px 0 0;
+            }
+            .nav-tabs-justified > .active > a,
+            .nav-tabs-justified > .active > a:hover,
+            .nav-tabs-justified > .active > a:focus {
+                border-bottom-color: #fff;
+            }
+        }
+        a {
+            text-decoration-line: none;
+        }
+        a:visited {
+            color: #c4e3f3;
+        }
+
+    </style>
+
+    <title>Title</title>
+</head>
+<body>
+<ul class="nav nav-tabs">
+    <li role="presentation" class="active"><a href="#">已取件</a></li>
+    <li role="presentation"><a href="#">未取件</a></li>
+    <li role="presentation"><a href="#">已滞留</a></li>
+</ul>
+</body>
+</html>

+ 67 - 0
src/main/resources/templates/return.html

@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <title>绑定结果页</title>
+        <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" name="viewport"/>
+        <meta content="yes" name="apple-mobile-web-app-capable"/>
+        <meta content="black" name="apple-mobile-web-app-status-bar-style"/>
+        <meta content="telephone=no" name="format-detection"/>
+        <link href="http://f3dhion.nat.ipyingshe.com/css/style_return.css" rel="stylesheet" type="text/css"/>
+        <script type="text/javascript" src="http://f3dhion.nat.ipyingshe.com/js/jquery.min.js"></script>
+    </head>
+    <body>
+
+        <!--
+
+         * 17素材vip建站专区模块代码
+         * 详尽信息请看官网:http://www.17sucai.com/pins/vip
+         *
+         * Copyright , 温州易站网络科技有限公司版权所有
+         *
+         * 请尊重原创,未经允许请勿转载。
+         * 在保留版权的前提下可应用于个人或商业用途
+
+        -->
+
+        <section class="aui-flexView">
+            <header class="aui-navBar aui-navBar-fixed b-line">
+                <a href="javascript:;" class="aui-navBar-item">
+                    <i class="icon icon-return"></i>
+                </a>
+                <div class="aui-center">
+                    <span class="aui-center-title"></span>
+                </div>
+                <a href="javascript:;" class="aui-navBar-item">
+                    <i class="icon icon-sys"></i>
+                </a>
+            </header>
+            <section class="aui-scrollView">
+                <div class="aui-back-box">
+                    <div class="aui-back-pitch">
+                        <img src="http://f3dhion.nat.ipyingshe.com/images/icon-pitch.png" alt="">
+                    </div>
+                    <div class="aui-back-title">
+                        <h2>绑定成功</h2>
+                        <p>点击返回按钮到公众号查询取件码</p>
+                    </div>
+                    <div class="aui-back-button">
+                        <button type="button" onclick="returnMethod()" id="return_btn">返  回</button>
+                    </div>
+                </div>
+            </section>
+        </section>
+
+    </body>
+
+    <script type="text/javascript">
+
+        $(function () {
+            returnMethod = function  (){
+                WeixinJSBridge.call('closeWindow');
+                window.close();
+            }
+        });
+
+    </script>
+</html>

+ 61 - 0
src/test/java/com/ematou/wxservice/wechatservice/WechatserviceApplicationTests.java

@@ -0,0 +1,61 @@
+package com.ematou.wxservice.wechatservice;
+
+import com.ematou.wxservice.config.WeChatGeneralConfig;
+import com.ematou.wxservice.entity.vo.AccessToken;
+import com.ematou.wxservice.mp.menu.MenuButtonManager;
+import com.ematou.wxservice.service.WeChatService;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
+
+@SpringBootTest
+class WechatserviceApplicationTests {
+
+    private static final Logger logger = LoggerFactory.getLogger(WechatserviceApplicationTests.class);
+
+    private static String getAccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
+
+    @Autowired
+    WeChatGeneralConfig weChatGeneralConfig;
+
+    @Autowired
+    RestTemplate restTemplate;
+
+    @Test
+    void contextLoads() {
+
+        // 调用接口获取access_token
+        Map map = restTemplate.getForObject(String.format(getAccessTokenUrl, weChatGeneralConfig.getAppId(), weChatGeneralConfig.getAppSecret()), Map.class);
+
+        System.out.println(map);
+
+
+
+    }
+
+
+    @Autowired
+    WeChatService weChatService;
+
+    @Test
+    void testGetGlobalAccessToken(){
+        AccessToken accessToken = weChatService.getAccessToken();
+        System.out.println(accessToken);
+    }
+
+    @Autowired
+    MenuButtonManager manager;
+
+    @Test
+    void testCreateMenu() throws InterruptedException {
+
+        manager.init();
+
+    }
+
+}

Some files were not shown because too many files changed in this diff