drew vor 5 Jahren
Commit
4375092595
100 geänderte Dateien mit 5747 neuen und 0 gelöschten Zeilen
  1. 33 0
      .gitignore
  2. 114 0
      .mvn/wrapper/MavenWrapperDownloader.java
  3. BIN
      .mvn/wrapper/maven-wrapper.jar
  4. 1 0
      .mvn/wrapper/maven-wrapper.properties
  5. 7 0
      build.sh
  6. 18 0
      db.sql
  7. 8 0
      install-jar.sh
  8. 286 0
      mvnw
  9. 161 0
      mvnw.cmd
  10. 256 0
      pom.xml
  11. 17 0
      src/main/java/com/izouma/jmrh/Application.java
  12. 12 0
      src/main/java/com/izouma/jmrh/annotations/Searchable.java
  13. 17 0
      src/main/java/com/izouma/jmrh/config/Constants.java
  14. 148 0
      src/main/java/com/izouma/jmrh/config/DateConfig.java
  15. 36 0
      src/main/java/com/izouma/jmrh/config/LocalDateTimeSerializerConfig.java
  16. 44 0
      src/main/java/com/izouma/jmrh/config/RedisConfig.java
  17. 24 0
      src/main/java/com/izouma/jmrh/config/SpringSecurityAuditorAware.java
  18. 75 0
      src/main/java/com/izouma/jmrh/config/WebMvcConfig.java
  19. 30 0
      src/main/java/com/izouma/jmrh/config/WxMaConfiguration.java
  20. 14 0
      src/main/java/com/izouma/jmrh/config/WxMaProperties.java
  21. 74 0
      src/main/java/com/izouma/jmrh/config/WxMpConfiguration.java
  22. 13 0
      src/main/java/com/izouma/jmrh/config/WxMpProperties.java
  23. 47 0
      src/main/java/com/izouma/jmrh/config/WxPayConfiguration.java
  24. 97 0
      src/main/java/com/izouma/jmrh/config/WxPayProperties.java
  25. 26 0
      src/main/java/com/izouma/jmrh/converter/LongArrayConverter.java
  26. 25 0
      src/main/java/com/izouma/jmrh/converter/StringArrayConverter.java
  27. 24 0
      src/main/java/com/izouma/jmrh/converter/StringToMapConverter.java
  28. 21 0
      src/main/java/com/izouma/jmrh/domain/ApplicationField.java
  29. 42 0
      src/main/java/com/izouma/jmrh/domain/Article.java
  30. 24 0
      src/main/java/com/izouma/jmrh/domain/ArticleType.java
  31. 73 0
      src/main/java/com/izouma/jmrh/domain/AuditedEntity.java
  32. 83 0
      src/main/java/com/izouma/jmrh/domain/BaseEntity.java
  33. 35 0
      src/main/java/com/izouma/jmrh/domain/Menu.java
  34. 77 0
      src/main/java/com/izouma/jmrh/domain/OrgInfo.java
  35. 22 0
      src/main/java/com/izouma/jmrh/domain/Product.java
  36. 68 0
      src/main/java/com/izouma/jmrh/domain/Requirement.java
  37. 32 0
      src/main/java/com/izouma/jmrh/domain/SmsRecord.java
  38. 57 0
      src/main/java/com/izouma/jmrh/domain/SuperUser.java
  39. 40 0
      src/main/java/com/izouma/jmrh/domain/SysConfig.java
  40. 79 0
      src/main/java/com/izouma/jmrh/domain/User.java
  41. 15 0
      src/main/java/com/izouma/jmrh/dto/PageQuery.java
  42. 354 0
      src/main/java/com/izouma/jmrh/dto/gen/GenCode.java
  43. 55 0
      src/main/java/com/izouma/jmrh/dto/gen/Subtable.java
  44. 163 0
      src/main/java/com/izouma/jmrh/dto/gen/TableField.java
  45. 17 0
      src/main/java/com/izouma/jmrh/enums/OrgStatus.java
  46. 7 0
      src/main/java/com/izouma/jmrh/exception/AuthenticationException.java
  47. 45 0
      src/main/java/com/izouma/jmrh/exception/BusinessException.java
  48. 149 0
      src/main/java/com/izouma/jmrh/exception/GlobalExceptionHandler.java
  49. 23 0
      src/main/java/com/izouma/jmrh/mpHandler/LogHandler.java
  50. 8 0
      src/main/java/com/izouma/jmrh/repo/ApplicationFieldRepo.java
  51. 8 0
      src/main/java/com/izouma/jmrh/repo/ArticleRepo.java
  52. 8 0
      src/main/java/com/izouma/jmrh/repo/ArticleTypeRepo.java
  53. 7 0
      src/main/java/com/izouma/jmrh/repo/AuthorityRepo.java
  54. 18 0
      src/main/java/com/izouma/jmrh/repo/MenuRepo.java
  55. 8 0
      src/main/java/com/izouma/jmrh/repo/OrgInfoRepo.java
  56. 8 0
      src/main/java/com/izouma/jmrh/repo/RequirementRepo.java
  57. 24 0
      src/main/java/com/izouma/jmrh/repo/SmsRecordRepo.java
  58. 11 0
      src/main/java/com/izouma/jmrh/repo/SysConfigRepo.java
  59. 18 0
      src/main/java/com/izouma/jmrh/repo/UserRepo.java
  60. 57 0
      src/main/java/com/izouma/jmrh/security/Authority.java
  61. 25 0
      src/main/java/com/izouma/jmrh/security/JwtAuthenticationEntryPoint.java
  62. 87 0
      src/main/java/com/izouma/jmrh/security/JwtAuthorizationTokenFilter.java
  63. 12 0
      src/main/java/com/izouma/jmrh/security/JwtConfig.java
  64. 125 0
      src/main/java/com/izouma/jmrh/security/JwtTokenUtil.java
  65. 81 0
      src/main/java/com/izouma/jmrh/security/JwtUser.java
  66. 26 0
      src/main/java/com/izouma/jmrh/security/JwtUserDetailsService.java
  67. 28 0
      src/main/java/com/izouma/jmrh/security/JwtUserFactory.java
  68. 110 0
      src/main/java/com/izouma/jmrh/security/WebSecurityConfig.java
  69. 14 0
      src/main/java/com/izouma/jmrh/service/ApplicationFieldService.java
  70. 14 0
      src/main/java/com/izouma/jmrh/service/ArticleService.java
  71. 14 0
      src/main/java/com/izouma/jmrh/service/ArticleTypeService.java
  72. 181 0
      src/main/java/com/izouma/jmrh/service/GenCodeService.java
  73. 35 0
      src/main/java/com/izouma/jmrh/service/OrgInfoService.java
  74. 14 0
      src/main/java/com/izouma/jmrh/service/RequirementService.java
  75. 39 0
      src/main/java/com/izouma/jmrh/service/SysConfigService.java
  76. 148 0
      src/main/java/com/izouma/jmrh/service/UserService.java
  77. 94 0
      src/main/java/com/izouma/jmrh/service/sms/AliSmsService.java
  78. 20 0
      src/main/java/com/izouma/jmrh/service/sms/SmsService.java
  79. 69 0
      src/main/java/com/izouma/jmrh/service/storage/AliStorageService.java
  80. 63 0
      src/main/java/com/izouma/jmrh/service/storage/LocalStorageService.java
  81. 9 0
      src/main/java/com/izouma/jmrh/service/storage/StorageService.java
  82. 45 0
      src/main/java/com/izouma/jmrh/utils/DateTimeUtils.java
  83. 204 0
      src/main/java/com/izouma/jmrh/utils/FileUtils.java
  84. 37 0
      src/main/java/com/izouma/jmrh/utils/JsonUtils.java
  85. 16 0
      src/main/java/com/izouma/jmrh/utils/NullAwareBeanUtilsBean.java
  86. 35 0
      src/main/java/com/izouma/jmrh/utils/ObjUtils.java
  87. 105 0
      src/main/java/com/izouma/jmrh/utils/PinYinUtil.java
  88. 17 0
      src/main/java/com/izouma/jmrh/utils/SecurityUtils.java
  89. 171 0
      src/main/java/com/izouma/jmrh/utils/SnowflakeIdWorker.java
  90. 36 0
      src/main/java/com/izouma/jmrh/utils/ThreadTask.java
  91. 23 0
      src/main/java/com/izouma/jmrh/utils/excel/ExcelUtils.java
  92. 32 0
      src/main/java/com/izouma/jmrh/utils/excel/LocalDateConverter.java
  93. 32 0
      src/main/java/com/izouma/jmrh/utils/excel/LocalDateTimeConverter.java
  94. 51 0
      src/main/java/com/izouma/jmrh/web/AppErrorController.java
  95. 60 0
      src/main/java/com/izouma/jmrh/web/ApplicationFieldController.java
  96. 60 0
      src/main/java/com/izouma/jmrh/web/ArticleController.java
  97. 60 0
      src/main/java/com/izouma/jmrh/web/ArticleTypeController.java
  98. 95 0
      src/main/java/com/izouma/jmrh/web/AuthenticationController.java
  99. 28 0
      src/main/java/com/izouma/jmrh/web/AuthorityController.java
  100. 169 0
      src/main/java/com/izouma/jmrh/web/BaseController.java

+ 33 - 0
.gitignore

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

+ 114 - 0
.mvn/wrapper/MavenWrapperDownloader.java

@@ -0,0 +1,114 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+*/
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+    /**
+     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+     */
+    private static final String DEFAULT_DOWNLOAD_URL =
+            "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
+
+    /**
+     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+     * use instead of the default one.
+     */
+    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+            ".mvn/wrapper/maven-wrapper.properties";
+
+    /**
+     * Path where the maven-wrapper.jar will be saved to.
+     */
+    private static final String MAVEN_WRAPPER_JAR_PATH =
+            ".mvn/wrapper/maven-wrapper.jar";
+
+    /**
+     * Name of the property which should be used to override the default download url for the wrapper.
+     */
+    private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+    public static void main(String args[]) {
+        System.out.println("- Downloader started");
+        File baseDirectory = new File(args[0]);
+        System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+        // If the maven-wrapper.properties exists, read it and check if it contains a custom
+        // wrapperUrl parameter.
+        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+        String url = DEFAULT_DOWNLOAD_URL;
+        if (mavenWrapperPropertyFile.exists()) {
+            FileInputStream mavenWrapperPropertyFileInputStream = null;
+            try {
+                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+                Properties mavenWrapperProperties = new Properties();
+                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+            } catch (IOException e) {
+                System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+            } finally {
+                try {
+                    if (mavenWrapperPropertyFileInputStream != null) {
+                        mavenWrapperPropertyFileInputStream.close();
+                    }
+                } catch (IOException e) {
+                    // Ignore ...
+                }
+            }
+        }
+        System.out.println("- Downloading from: : " + url);
+
+        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+        if (!outputFile.getParentFile().exists()) {
+            if (!outputFile.getParentFile().mkdirs()) {
+                System.out.println(
+                        "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+            }
+        }
+        System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+        try {
+            downloadFileFromURL(url, outputFile);
+            System.out.println("Done");
+            System.exit(0);
+        } catch (Throwable e) {
+            System.out.println("- Error downloading");
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+        URL website = new URL(urlString);
+        ReadableByteChannel rbc;
+        rbc = Channels.newChannel(website.openStream());
+        FileOutputStream fos = new FileOutputStream(destination);
+        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+        fos.close();
+        rbc.close();
+    }
+
+}

BIN
.mvn/wrapper/maven-wrapper.jar


+ 1 - 0
.mvn/wrapper/maven-wrapper.properties

@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip

+ 7 - 0
build.sh

@@ -0,0 +1,7 @@
+git checkout
+git pull
+#(cd src/main/vue && npm run build)
+mvn clean package
+systemctl stop zmj
+cp target/zhumj-0.0.1-SNAPSHOT.jar /var/www/zmj/zhumj-0.0.1-SNAPSHOT.jar
+systemctl start zmj

+ 18 - 0
db.sql

@@ -0,0 +1,18 @@
+INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (2, NULL, '', NULL, '', b'1', b'1', '', '系统菜单', NULL, '', b'1', 1);
+INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (6, NULL, '', NULL, '', b'1', b'1', 'fas fa-user', '用户管理', 2, '/userList', b'0', 2);
+INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (4, NULL, '', NULL, '', b'1', b'1', 'fas fa-code', '代码生成', 3, '/genCodeList', b'0', 5);
+INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (5, NULL, '', NULL, '', b'1', b'1', 'fas fa-bug', '接口调试', 3, '/api', b'0', 6);
+INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (10, NULL, '', NULL, '', b'1', b'1', '', '菜单配置', 9, '/menus', b'0', 7);
+INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (14, NULL, '', NULL, '', b'1', b'1', '', '参数配置', 9, '/sysConfigList', b'0', 8);
+INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (3, NULL, '', NULL, '', b'1', b'1', 'fas fa-desktop', '开发', 2, '', b'0', 4);
+INSERT INTO `menu`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `active`, `enabled`, `icon`, `name`, `parent`, `path`, `root`, `sort`) VALUES (9, NULL, '', NULL, '', b'1', b'1', 'fas fa-cog', '配置', 2, '', b'0', 3);
+
+INSERT INTO authority (name) VALUES ('ROLE_ADMIN');
+INSERT INTO authority (name) VALUES ('ROLE_USER');
+
+INSERT INTO `user_authority`(`user_id`, `authority_name`) VALUES (1, 'ROLE_ADMIN');
+INSERT INTO `user_authority`(`user_id`, `authority_name`) VALUES (1, 'ROLE_USER');
+
+INSERT INTO `user`(`id`, `created_at`, `created_by`, `modified_at`, `modified_by`, `avatar`, `city`, `country`, `email`, `enabled`, `language`, `nickname`, `open_id`, `password`, `phone`, `province`, `sex`, `username`) VALUES (1, '2020-03-07 18:34:36', 'system', '2020-03-07 18:34:36', 'system', 'https://zhumj.oss-cn-hangzhou.aliyuncs.com/image/user.jpg', NULL, NULL, NULL, b'1', NULL, '管理员', NULL, '$2a$10$F1djCtmdF3T0qsviWw50/utajZSe4EJMMUD.Ey5BhEIpoDhanBMuC', NULL, NULL, NULL, 'root');
+
+update hibernate_sequence set next_val = 1000

+ 8 - 0
install-jar.sh

@@ -0,0 +1,8 @@
+
+mvn org.apache.maven.plugins:maven-install-plugin:2.5.1:install-file \
+-DgroupId=com.dingtalk \
+-DartifactId=client-sdk.api \
+-Dpackaging=jar \
+-Dversion=1.0.2 \
+-Dfile=lib/client-sdk.api-1.0.2.jar \
+-DlocalRepositoryPath=libs

+ 286 - 0
mvnw

@@ -0,0 +1,286 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   M2_HOME - location of maven2's installed home dir
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        export JAVA_HOME="`/usr/libexec/java_home`"
+      else
+        export JAVA_HOME="/Library/Java/Home"
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=`java-config --jre-home`
+  fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+  ## resolve links - $0 may be a link to maven's home
+  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
+
+  saveddir=`pwd`
+
+  M2_HOME=`dirname "$PRG"`/..
+
+  # make it fully qualified
+  M2_HOME=`cd "$M2_HOME" && pwd`
+
+  cd "$saveddir"
+  # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --unix "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME="`(cd "$M2_HOME"; pwd)`"
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+  # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="`which javac`"
+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=`which readlink`
+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+      if $darwin ; then
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+      else
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+      fi
+      javaHome="`dirname \"$javaExecutable\"`"
+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  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
+  else
+    JAVACMD="`which java`"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=`cd "$wdir/.."; pwd`
+    fi
+    # end of workaround
+  done
+  echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    echo "$(tr -s '\n' ' ' < "$1")"
+  fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Found .mvn/wrapper/maven-wrapper.jar"
+    fi
+else
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+    fi
+    jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+    while IFS="=" read key value; do
+      case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+      esac
+    done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+    if [ "$MVNW_VERBOSE" = true ]; then
+      echo "Downloading from: $jarUrl"
+    fi
+    wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        wget "$jarUrl" -O "$wrapperJarPath"
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        curl -o "$wrapperJarPath" "$jarUrl"
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        if [ -e "$javaClass" ]; then
+            if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Compiling MavenWrapperDownloader.java ..."
+                fi
+                # Compiling the Java class
+                ("$JAVA_HOME/bin/javac" "$javaClass")
+            fi
+            if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+                # Running the downloader
+                if [ "$MVNW_VERBOSE" = true ]; then
+                  echo " - Running MavenWrapperDownloader.java ..."
+                fi
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+  echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$M2_HOME" ] &&
+    M2_HOME=`cygpath --path --windows "$M2_HOME"`
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

+ 161 - 0
mvnw.cmd

@@ -0,0 +1,161 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements.  See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership.  The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License.  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,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied.  See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM     e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
+FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
+	IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+    echo Found %WRAPPER_JAR%
+) else (
+    echo Couldn't find %WRAPPER_JAR%, downloading it ...
+	echo Downloading from: %DOWNLOAD_URL%
+    powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
+    echo Finished downloading %WRAPPER_JAR%
+)
+@REM End of extension
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%

+ 256 - 0
pom.xml

@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.1.8.RELEASE</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>com.izouma</groupId>
+    <artifactId>jmrh</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>jmrh</name>
+    <description>jmrh</description>
+
+    <repositories>
+        <repository>
+            <id>nexus-aliyun</id>
+            <url>https://maven.aliyun.com/repository/central</url>
+        </repository>
+        <repository>
+            <id>Local repository</id>
+            <url>file://${basedir}/libs</url>
+        </repository>
+    </repositories>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <skipTests>true</skipTests>
+        <poi.verion>3.17</poi.verion>
+        <javawx.version>3.5.0</javawx.version>
+        <aliyun.oss.version>2.8.3</aliyun.oss.version>
+        <aliyun.core.version>4.1.0</aliyun.core.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-envers</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+            <version>1.9.4</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-text</artifactId>
+            <version>1.3</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>annotations</artifactId>
+            <version>3.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-mp</artifactId>
+            <version>${javawx.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-miniapp</artifactId>
+            <version>${javawx.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-pay</artifactId>
+            <version>${javawx.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-open</artifactId>
+            <version>${javawx.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>${aliyun.oss.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>${aliyun.core.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.kevinsawicki</groupId>
+            <artifactId>http-request</artifactId>
+            <version>6.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>${poi.verion}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>${poi.verion}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib</artifactId>
+            <version>3.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>2.2.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <version>0.9.11</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+            <version>1.7</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-tools</artifactId>
+            <version>2.0</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/com.belerweb/pinyin4j -->
+        <dependency>
+            <groupId>com.belerweb</groupId>
+            <artifactId>pinyin4j</artifactId>
+            <version>2.5.1</version>
+        </dependency>
+        <!-- swagger -->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>2.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>2.9.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.37</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ning</groupId>
+            <artifactId>async-http-client</artifactId>
+            <version>1.9.32</version>
+        </dependency>
+        <!-- 钉钉 -->
+
+    </dependencies>
+
+</project>

+ 17 - 0
src/main/java/com/izouma/jmrh/Application.java

@@ -0,0 +1,17 @@
+package com.izouma.jmrh;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+@SpringBootApplication
+@EnableJpaAuditing
+@EnableSwagger2
+public class Application {
+
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+
+}

+ 12 - 0
src/main/java/com/izouma/jmrh/annotations/Searchable.java

@@ -0,0 +1,12 @@
+package com.izouma.jmrh.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Searchable {
+    boolean value() default true;
+}

+ 17 - 0
src/main/java/com/izouma/jmrh/config/Constants.java

@@ -0,0 +1,17 @@
+package com.izouma.jmrh.config;
+
+public interface Constants {
+
+    public interface Regex {
+        String PHONE    = "^1[3-9]\\d{9}$";
+        String USERNAME = "^[_.@A-Za-z0-9-]*$";
+        String CHINESE  = "^[\\u4e00-\\u9fa5]+$";
+        String ID_NO    = "^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0-2]\\d)|3[0-1])\\d{3}$|^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0-2]\\d)|3[0-1])\\d{3}[0-9xX]$";
+    }
+
+    String DEFAULT_AVATAR = "https://jxjmrh.oss-cn-hangzhou.aliyuncs.com/image/avatar.jpg";
+
+    String SMS_SIGN_NAME = "走马信息";
+
+    String SMS_TEMPLATE_CODE_GENERIC = "SMS_175485688";
+}

+ 148 - 0
src/main/java/com/izouma/jmrh/config/DateConfig.java

@@ -0,0 +1,148 @@
+package com.izouma.jmrh.config;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+
+@Configuration
+public class DateConfig {
+
+    /**
+     * 默认日期时间格式
+     */
+    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    /**
+     * 默认日期格式
+     */
+    public static final String DEFAULT_DATE_FORMAT      = "yyyy-MM-dd";
+    /**
+     * 默认时间格式
+     */
+    public static final String DEFAULT_TIME_FORMAT      = "HH:mm:ss";
+
+    /**
+     * LocalDate转换器,用于转换RequestParam和PathVariable参数
+     */
+    @Bean
+    public Converter<String, LocalDate> localDateConverter() {
+        return new Converter<String, LocalDate>() {
+            @Override
+            public LocalDate convert(String source) {
+                return LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT));
+            }
+        };
+    }
+
+    /**
+     * LocalDateTime转换器,用于转换RequestParam和PathVariable参数
+     */
+    @Bean
+    public Converter<String, LocalDateTime> localDateTimeConverter() {
+        return new Converter<String, LocalDateTime>() {
+            @Override
+            public LocalDateTime convert(String source) {
+                return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT));
+            }
+        };
+    }
+
+    /**
+     * LocalTime转换器,用于转换RequestParam和PathVariable参数
+     */
+    @Bean
+    public Converter<String, LocalTime> localTimeConverter() {
+        return new Converter<String, LocalTime>() {
+            @Override
+            public LocalTime convert(String source) {
+                return LocalTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT));
+            }
+        };
+    }
+
+    /**
+     * Date转换器,用于转换RequestParam和PathVariable参数
+     */
+    @Bean
+    public Converter<String, Date> dateConverter() {
+        return new Converter<String, Date>() {
+            @Override
+            public Date convert(String source) {
+                SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
+                try {
+                    return format.parse(source);
+                } catch (ParseException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+    }
+
+
+    /**
+     * Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json
+     */
+    @Bean
+    public ObjectMapper objectMapper() {
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
+
+        //LocalDateTime系列序列化和反序列化模块,继承自jsr310,我们在这里修改了日期格式
+        JavaTimeModule javaTimeModule = new JavaTimeModule();
+        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
+        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
+        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
+        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
+        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
+        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
+
+
+        //Date序列化和反序列化
+        javaTimeModule.addSerializer(Date.class, new JsonSerializer<Date>() {
+            @Override
+            public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+                SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
+                String formattedDate = formatter.format(date);
+                jsonGenerator.writeString(formattedDate);
+            }
+        });
+        javaTimeModule.addDeserializer(Date.class, new JsonDeserializer<Date>() {
+            @Override
+            public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
+                SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
+                String date = jsonParser.getText();
+                try {
+                    return format.parse(date);
+                } catch (ParseException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+
+        objectMapper.registerModule(javaTimeModule);
+        return objectMapper;
+    }
+
+
+}
+

+ 36 - 0
src/main/java/com/izouma/jmrh/config/LocalDateTimeSerializerConfig.java

@@ -0,0 +1,36 @@
+package com.izouma.jmrh.config;
+
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+@Configuration
+public class LocalDateTimeSerializerConfig {
+    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
+    private String pattern;
+
+    @Bean
+    public LocalDateTimeSerializer localDateTimeDeserializer() {
+        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+    }
+
+    @Bean
+    public LocalDateSerializer localDateSerializer() {
+        return new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+    }
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
+        return builder -> {
+            builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer());
+            builder.serializerByType(LocalDate.class, localDateSerializer());
+        };
+    }
+}

+ 44 - 0
src/main/java/com/izouma/jmrh/config/RedisConfig.java

@@ -0,0 +1,44 @@
+package com.izouma.jmrh.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+@AutoConfigureAfter(RedisAutoConfiguration.class)
+@EnableRedisRepositories
+public class RedisConfig {
+
+    @Bean
+    RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(redisConnectionFactory);
+
+        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
+        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class);
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+        serializer.setObjectMapper(mapper);
+
+        template.setValueSerializer(serializer);
+        //使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+        template.afterPropertiesSet();
+        return template;
+    }
+
+}

+ 24 - 0
src/main/java/com/izouma/jmrh/config/SpringSecurityAuditorAware.java

@@ -0,0 +1,24 @@
+package com.izouma.jmrh.config;
+
+import com.izouma.jmrh.domain.User;
+import com.izouma.jmrh.utils.SecurityUtils;
+import org.springframework.data.domain.AuditorAware;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Nonnull;
+import java.util.Optional;
+
+@Component
+public class SpringSecurityAuditorAware implements AuditorAware<String> {
+
+    @Override
+    @Nonnull
+    public Optional<String> getCurrentAuditor() {
+        String auditor = "system";
+        User user = SecurityUtils.getAuthenticatedUser();
+        if (user != null) {
+            auditor = user.getNickname() + "(" + user.getId() + ")";
+        }
+        return Optional.of(auditor);
+    }
+}

+ 75 - 0
src/main/java/com/izouma/jmrh/config/WebMvcConfig.java

@@ -0,0 +1,75 @@
+package com.izouma.jmrh.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+    @Value("${storage.local_path}")
+    private String localPath;
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        // registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
+        // registry.addResourceHandler("webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
+        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
+        registry.addResourceHandler("/admin/**").addResourceLocations("classpath:/static/admin/");
+        registry.addResourceHandler("/files/**").addResourceLocations("file:" + localPath);
+    }
+
+    @Bean
+    public Docket createApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .apiInfo(new ApiInfoBuilder()
+                                 .title("接口文档")
+                                 .version("1.0.0")
+                                 .termsOfServiceUrl("#")
+                                 .description("接口文档")
+                                 .build())
+                .select()
+                .apis(RequestHandlerSelectors.basePackage("com.izouma.jmrh.web"))
+                .paths(PathSelectors.any())
+                .build();
+    }
+
+    // @Bean
+    // public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
+    //     MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
+    //     //设置日期格式
+    //     ObjectMapper objectMapper = new ObjectMapper();
+    //     objectMapper.setDateFormat(CustomDateFormat.instance);
+    //     objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+    //     mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
+    //     //设置中文编码格式
+    //     List<MediaType> list = new ArrayList<>();
+    //     list.add(MediaType.APPLICATION_JSON_UTF8);
+    //     mappingJackson2HttpMessageConverter.setSupportedMediaTypes(list);
+    //     return mappingJackson2HttpMessageConverter;
+    // }
+
+//    @Override
+//    public void addFormatters(FormatterRegistry registry) {
+//        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
+//        registrar.setUseIsoFormat(true);
+//        registrar.registerFormatters(registry);
+//    }
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedHeaders("*")
+                .allowCredentials(true)
+                .allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH")
+                .exposedHeaders("Content-Disposition");
+    }
+
+}

+ 30 - 0
src/main/java/com/izouma/jmrh/config/WxMaConfiguration.java

@@ -0,0 +1,30 @@
+package com.izouma.jmrh.config;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
+import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
+import lombok.AllArgsConstructor;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@AllArgsConstructor
+@Configuration
+@EnableConfigurationProperties(WxMaProperties.class)
+public class WxMaConfiguration {
+    private final WxMaProperties properties;
+
+    @Bean
+    public WxMaService wxMaService() {
+        WxMaService service = new WxMaServiceImpl();
+        WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
+        config.setAppid(properties.getAppId());
+        config.setSecret(properties.getAppSecret());
+        config.setToken(properties.getMsgToken());
+        config.setAesKey(properties.getMsgAesKey());
+        config.setMsgDataFormat(properties.getMsgFormat());
+        service.setWxMaConfig(config);
+        return service;
+    }
+
+}

+ 14 - 0
src/main/java/com/izouma/jmrh/config/WxMaProperties.java

@@ -0,0 +1,14 @@
+package com.izouma.jmrh.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "wx.ma")
+public class WxMaProperties {
+    private String appId;
+    private String appSecret;
+    private String msgToken;
+    private String msgAesKey;
+    private String msgFormat;
+}

+ 74 - 0
src/main/java/com/izouma/jmrh/config/WxMpConfiguration.java

@@ -0,0 +1,74 @@
+package com.izouma.jmrh.config;
+
+import com.izouma.jmrh.mpHandler.LogHandler;
+import lombok.AllArgsConstructor;
+import me.chanjar.weixin.mp.api.WxMpMessageRouter;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
+import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@AllArgsConstructor
+@Configuration
+@EnableConfigurationProperties(WxMpProperties.class)
+public class WxMpConfiguration {
+    private final WxMpProperties properties;
+    private final LogHandler     logHandler;
+
+    @Bean
+    public WxMpService wxMpService() {
+        WxMpDefaultConfigImpl mpConfig = new WxMpDefaultConfigImpl();
+        mpConfig.setAppId(properties.getAppId());
+        mpConfig.setSecret(properties.getAppSecret());
+        mpConfig.setAccessToken(properties.getToken());
+        mpConfig.setAesKey(properties.getAesKey());
+        WxMpService service = new WxMpServiceImpl();
+        service.setWxMpConfigStorage(mpConfig);
+        return service;
+    }
+
+    @Bean
+    public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
+        final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
+
+        // 记录所有事件的日志 (异步执行)
+        newRouter.rule().handler(this.logHandler).next();
+
+        /*// 接收客服会话管理事件
+        newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION).handler(this.kfSessionHandler).end();
+        newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION).handler(this.kfSessionHandler).end();
+        newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION).handler(this.kfSessionHandler).end();
+
+        // 门店审核事件
+        newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end();
+
+        // 自定义菜单事件
+        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.CLICK).handler(this.menuHandler).end();
+
+        // 点击菜单连接事件
+        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.VIEW).handler(this.nullHandler).end();
+
+        // 关注事件
+        newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end();
+
+        // 取消关注事件
+        newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end();
+
+        // 上报地理位置事件
+        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.LOCATION).handler(this.locationHandler).end();
+
+        // 接收地理位置消息
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION).handler(this.locationHandler).end();
+
+        // 扫码事件
+        newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.SCAN).handler(this.scanHandler).end();
+
+        // 默认
+        newRouter.rule().async(false).handler(this.msgHandler).end();*/
+
+        return newRouter;
+    }
+
+}

+ 13 - 0
src/main/java/com/izouma/jmrh/config/WxMpProperties.java

@@ -0,0 +1,13 @@
+package com.izouma.jmrh.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "wx.mp")
+public class WxMpProperties {
+    private String appId;
+    private String appSecret;
+    private String token;
+    private String aesKey;
+}

+ 47 - 0
src/main/java/com/izouma/jmrh/config/WxPayConfiguration.java

@@ -0,0 +1,47 @@
+package com.izouma.jmrh.config;
+
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Binary Wang
+ */
+@Configuration
+@ConditionalOnClass(WxPayService.class)
+@EnableConfigurationProperties(WxPayProperties.class)
+public class WxPayConfiguration {
+    private WxPayProperties properties;
+
+    @Autowired
+    public WxPayConfiguration(WxPayProperties properties) {
+        this.properties = properties;
+    }
+
+    @Bean
+    @ConditionalOnMissingBean
+    public WxPayService wxService() {
+        WxPayConfig payConfig = new WxPayConfig();
+        payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
+        payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
+        payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
+        payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
+        payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
+        payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
+
+        // 可以指定是否使用沙箱环境
+        payConfig.setUseSandboxEnv(false);
+
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(payConfig);
+        return wxPayService;
+    }
+
+}

+ 97 - 0
src/main/java/com/izouma/jmrh/config/WxPayProperties.java

@@ -0,0 +1,97 @@
+package com.izouma.jmrh.config;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * wxpay pay properties
+ *
+ * @author Binary Wang
+ */
+@ConfigurationProperties(prefix = "wx.pay")
+public class WxPayProperties {
+  /**
+   * 设置微信公众号或者小程序等的appid
+   */
+  private String appId;
+
+  /**
+   * 微信支付商户号
+   */
+  private String mchId;
+
+  /**
+   * 微信支付商户密钥
+   */
+  private String mchKey;
+
+  /**
+   * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除
+   */
+  private String subAppId;
+
+  /**
+   * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除
+   */
+  private String subMchId;
+
+  /**
+   * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
+   */
+  private String keyPath;
+
+  public String getAppId() {
+    return this.appId;
+  }
+
+  public void setAppId(String appId) {
+    this.appId = appId;
+  }
+
+  public String getMchId() {
+    return mchId;
+  }
+
+  public void setMchId(String mchId) {
+    this.mchId = mchId;
+  }
+
+  public String getMchKey() {
+    return mchKey;
+  }
+
+  public void setMchKey(String mchKey) {
+    this.mchKey = mchKey;
+  }
+
+  public String getSubAppId() {
+    return subAppId;
+  }
+
+  public void setSubAppId(String subAppId) {
+    this.subAppId = subAppId;
+  }
+
+  public String getSubMchId() {
+    return subMchId;
+  }
+
+  public void setSubMchId(String subMchId) {
+    this.subMchId = subMchId;
+  }
+
+  public String getKeyPath() {
+    return this.keyPath;
+  }
+
+  public void setKeyPath(String keyPath) {
+    this.keyPath = keyPath;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this,
+        ToStringStyle.MULTI_LINE_STYLE);
+  }
+}

+ 26 - 0
src/main/java/com/izouma/jmrh/converter/LongArrayConverter.java

@@ -0,0 +1,26 @@
+package com.izouma.jmrh.converter;
+
+import org.apache.commons.lang3.StringUtils;
+
+import javax.persistence.AttributeConverter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class LongArrayConverter implements AttributeConverter<List<Long>, String> {
+    @Override
+    public String convertToDatabaseColumn(List<Long> longs) {
+        if (longs != null && !longs.isEmpty())
+            return StringUtils.join(longs, ",");
+        return null;
+    }
+
+    @Override
+    public List<Long> convertToEntityAttribute(String s) {
+        if (StringUtils.isNotEmpty(s)) {
+            return Arrays.stream(s.split(",")).map(Long::parseLong).collect(Collectors.toList());
+        }
+        return new ArrayList<>();
+    }
+}

+ 25 - 0
src/main/java/com/izouma/jmrh/converter/StringArrayConverter.java

@@ -0,0 +1,25 @@
+package com.izouma.jmrh.converter;
+
+import org.apache.commons.lang3.StringUtils;
+
+import javax.persistence.AttributeConverter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class StringArrayConverter implements AttributeConverter<List<String>, String> {
+    @Override
+    public String convertToDatabaseColumn(List<String> strings) {
+        if (strings != null && !strings.isEmpty())
+            return StringUtils.join(strings, ",");
+        return null;
+    }
+
+    @Override
+    public List<String> convertToEntityAttribute(String s) {
+        if (StringUtils.isNotEmpty(s)) {
+            return Arrays.asList(s.split(","));
+        }
+        return new ArrayList<>();
+    }
+}

+ 24 - 0
src/main/java/com/izouma/jmrh/converter/StringToMapConverter.java

@@ -0,0 +1,24 @@
+package com.izouma.jmrh.converter;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.Map;
+
+@Component
+public class StringToMapConverter implements Converter<String, Map<String, Object>> {
+
+    @Override
+    public Map<String, Object> convert(@Nullable String source) {
+        try {
+            return new ObjectMapper().readValue(source, new TypeReference<Map<String, Object>>() {
+            });
+        } catch (IOException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+}

+ 21 - 0
src/main/java/com/izouma/jmrh/domain/ApplicationField.java

@@ -0,0 +1,21 @@
+package com.izouma.jmrh.domain;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@ApiModel("应用领域")
+public class ApplicationField extends BaseEntity {
+    @ApiModelProperty("名称")
+    private String name;
+}

+ 42 - 0
src/main/java/com/izouma/jmrh/domain/Article.java

@@ -0,0 +1,42 @@
+package com.izouma.jmrh.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+
+@Data
+@Entity
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@ApiModel("文章")
+public class Article extends BaseEntity {
+
+    @ApiModelProperty("标题")
+    private String title;
+
+    @ApiModelProperty("封面")
+    private String cover;
+
+    @ApiModelProperty("内容")
+    @Column(columnDefinition = "TEXT")
+    private String content;
+
+    @ApiModelProperty("发布")
+    private boolean publish;
+
+    @ApiModelProperty("类型")
+    private Long typeId;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "typeId", insertable = false, updatable = false)
+    @JsonIgnore
+    private ArticleType type;
+
+}

+ 24 - 0
src/main/java/com/izouma/jmrh/domain/ArticleType.java

@@ -0,0 +1,24 @@
+package com.izouma.jmrh.domain;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@ApiModel("文章类型")
+public class ArticleType extends BaseEntity {
+    @ApiModelProperty("名称")
+    private String name;
+
+    @ApiModelProperty("备注")
+    private String remark;
+}

+ 73 - 0
src/main/java/com/izouma/jmrh/domain/AuditedEntity.java

@@ -0,0 +1,73 @@
+package com.izouma.jmrh.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.hibernate.envers.Audited;
+import org.springframework.data.annotation.CreatedBy;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedBy;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.EntityListeners;
+import javax.persistence.MappedSuperclass;
+import java.time.LocalDateTime;
+
+@MappedSuperclass
+@Audited
+@EntityListeners(AuditingEntityListener.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public abstract class AuditedEntity {
+
+    @JsonIgnore
+    @CreatedBy
+    private String createdBy;
+
+    @JsonIgnore
+    @CreatedDate
+    private LocalDateTime createdAt;
+
+    @JsonIgnore
+    @LastModifiedBy
+    private String modifiedBy;
+
+    @JsonIgnore
+    @LastModifiedDate
+    private LocalDateTime modifiedAt;
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    @JsonProperty("createdAt")
+    public LocalDateTime getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(LocalDateTime createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public String getModifiedBy() {
+        return modifiedBy;
+    }
+
+    public void setModifiedBy(String modifiedBy) {
+        this.modifiedBy = modifiedBy;
+    }
+
+    public LocalDateTime getModifiedAt() {
+        return modifiedAt;
+    }
+
+    public void setModifiedAt(LocalDateTime modifiedAt) {
+        this.modifiedAt = modifiedAt;
+    }
+}

+ 83 - 0
src/main/java/com/izouma/jmrh/domain/BaseEntity.java

@@ -0,0 +1,83 @@
+package com.izouma.jmrh.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.hibernate.envers.Audited;
+import org.springframework.data.annotation.CreatedBy;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedBy;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@MappedSuperclass
+@Audited
+@EntityListeners(AuditingEntityListener.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(value = {"hibernateLazyInitializer"}, ignoreUnknown = true)
+public abstract class BaseEntity {
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private Long id;
+
+    @JsonIgnore
+    @CreatedBy
+    private String createdBy;
+
+    @JsonIgnore
+    @CreatedDate
+    private LocalDateTime createdAt;
+
+    @JsonIgnore
+    @LastModifiedBy
+    private String modifiedBy;
+
+    @JsonIgnore
+    @LastModifiedDate
+    private LocalDateTime modifiedAt;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    @JsonProperty("createdAt")
+    public LocalDateTime getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(LocalDateTime createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public String getModifiedBy() {
+        return modifiedBy;
+    }
+
+    public void setModifiedBy(String modifiedBy) {
+        this.modifiedBy = modifiedBy;
+    }
+
+    public LocalDateTime getModifiedAt() {
+        return modifiedAt;
+    }
+
+    public void setModifiedAt(LocalDateTime modifiedAt) {
+        this.modifiedAt = modifiedAt;
+    }
+}

+ 35 - 0
src/main/java/com/izouma/jmrh/domain/Menu.java

@@ -0,0 +1,35 @@
+package com.izouma.jmrh.domain;
+
+import lombok.Data;
+import org.hibernate.annotations.Where;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+@Entity
+@Where(clause = "active = 1")
+public class Menu extends BaseEntity implements Serializable {
+    private String name;
+
+    private String path;
+
+    private String icon;
+
+    private Integer sort;
+
+    private Long parent;
+
+    private Boolean root;
+
+    private Boolean enabled;
+
+    private Boolean active;
+
+    private String category;
+
+    @OneToMany
+    @JoinColumn(name = "parent", insertable = false, updatable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
+    List<Menu> children;
+}

+ 77 - 0
src/main/java/com/izouma/jmrh/domain/OrgInfo.java

@@ -0,0 +1,77 @@
+package com.izouma.jmrh.domain;
+
+import com.izouma.jmrh.enums.OrgStatus;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Transient;
+
+@Data
+@Entity
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@ApiModel("企业认证")
+public class OrgInfo extends BaseEntity {
+    @ApiModelProperty("单位名称")
+    private String orgName;
+
+    @ApiModelProperty("单位性质")
+    private String orgType;
+
+    @ApiModelProperty("上市公司")
+    private boolean publicCompany;
+
+    @ApiModelProperty("隶属单位")
+    private String parentOrg;
+
+    @ApiModelProperty("所在地")
+    private String address;
+
+    @ApiModelProperty("详细地址")
+    private String addressDetail;
+
+    @ApiModelProperty("企业介绍")
+    private String intro;
+
+    @ApiModelProperty("营业执照")
+    private String license;
+
+    @ApiModelProperty("联系人")
+    private String contactName;
+
+    @ApiModelProperty("身份证号")
+    private String contactIdNo;
+
+    @ApiModelProperty("手机号")
+    private String contactPhone;
+
+    @ApiModelProperty("电子邮箱")
+    private String contactEmail;
+
+    @ApiModelProperty("信息报送承诺书扫描件")
+    private String attach;
+
+    @ApiModelProperty("用户ID")
+    private Long userId;
+
+    @ApiModelProperty("状态")
+    @Enumerated(EnumType.STRING)
+    private OrgStatus status;
+
+    @ApiModelProperty("备注")
+    private String remark;
+
+    @ApiModelProperty("批注")
+    private String comment;
+
+    @Transient
+    private String nickname;
+}

+ 22 - 0
src/main/java/com/izouma/jmrh/domain/Product.java

@@ -0,0 +1,22 @@
+package com.izouma.jmrh.domain;
+
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@ApiModel("产品")
+public class Product extends BaseEntity {
+
+    private String name;
+
+    private String keyword;
+}

+ 68 - 0
src/main/java/com/izouma/jmrh/domain/Requirement.java

@@ -0,0 +1,68 @@
+package com.izouma.jmrh.domain;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import java.time.LocalDateTime;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@ApiModel("需求")
+public class Requirement extends BaseEntity {
+    @ApiModelProperty("需求名称")
+    private String name;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "applicationFieldId")
+    @ApiModelProperty("应用领域")
+    private ApplicationField applicationField;
+
+    @ApiModelProperty("需求单位")
+    private String orgName;
+
+    @ApiModelProperty("单位地址")
+    private String address;
+
+    @ApiModelProperty("详细地址")
+    private String addressDetail;
+
+    @ApiModelProperty("联系人")
+    private String contactName;
+
+    @ApiModelProperty("联系人手机号")
+    private String contactPhone;
+
+    @ApiModelProperty("公开联系方式")
+    private boolean publicContact;
+
+    @ApiModelProperty("信息来源")
+    private String source;
+
+    @ApiModelProperty("转载武器装备采购网")
+    private boolean rePost;
+
+    @ApiModelProperty("有效期")
+    private LocalDateTime expireTime;
+
+    @ApiModelProperty("照片")
+    private String pic;
+
+    @ApiModelProperty("需求描述")
+    private String description;
+
+    @ApiModelProperty("备注")
+    private String remark;
+
+    private Long userId;
+}

+ 32 - 0
src/main/java/com/izouma/jmrh/domain/SmsRecord.java

@@ -0,0 +1,32 @@
+package com.izouma.jmrh.domain;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import java.time.LocalDateTime;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@ApiModel(value = "短信验证码记录", description = "短信验证码记录")
+public class SmsRecord extends BaseEntity {
+    @ApiModelProperty(value = "会话ID", name = "sessionId")
+    private String        sessionId;
+    @ApiModelProperty(value = "手机号", name = "phone;")
+    private String        phone;
+    @ApiModelProperty(value = "验证码", name = "code")
+    private String        code;
+    @ApiModelProperty(value = "过期时间", name = "expiresAt")
+    private LocalDateTime expiresAt;
+    @ApiModelProperty(value = "是否过期", name = "expired")
+    private Boolean       expired;
+    @ApiModelProperty(value = "验证码用途", name = "scope")
+    private String        scope;
+}

+ 57 - 0
src/main/java/com/izouma/jmrh/domain/SuperUser.java

@@ -0,0 +1,57 @@
+package com.izouma.jmrh.domain;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.izouma.jmrh.annotations.Searchable;
+import com.izouma.jmrh.config.Constants;
+import com.izouma.jmrh.security.Authority;
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.BatchSize;
+import org.hibernate.annotations.Where;
+
+import javax.persistence.*;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.util.HashSet;
+import java.util.Set;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Where(clause = "enabled = 1")
+@ApiModel(value = "超级用户", description = "超级用户")
+public class SuperUser extends BaseEntity {
+
+    @Pattern(regexp = Constants.Regex.USERNAME)
+    @Size(min = 1, max = 50)
+    @Column(nullable = false, unique = true)
+    @Searchable
+    private String username;
+
+    @Searchable
+    private String nickname;
+
+    private String avatar;
+
+    @JsonIgnore
+    private String password;
+
+    @Searchable
+    private String phone;
+
+    @Column(nullable = false)
+    private Boolean enabled = true;
+
+    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
+    @JoinTable(
+            name = "super_user_authority",
+            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
+            inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")})
+    @BatchSize(size = 20)
+    @ExcelIgnore
+    private Set<Authority> authorities = new HashSet<>();
+}

+ 40 - 0
src/main/java/com/izouma/jmrh/domain/SysConfig.java

@@ -0,0 +1,40 @@
+package com.izouma.jmrh.domain;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class SysConfig extends AuditedEntity {
+    @Id
+    @Column(length = 25, unique = true)
+    @ApiModelProperty(value = "名称", name = "name")
+    private String name;
+
+    @Column(name = "description")
+    @ApiModelProperty(value = "描述", name = "desc")
+    private String desc;
+
+    @ApiModelProperty(value = "值", name = "value")
+    private String value;
+
+    @Enumerated(EnumType.STRING)
+    private ValueType type;
+
+    public enum ValueType {
+        STRING,
+        TIME,
+        DATE,
+        DATETIME,
+        BOOLEAN,
+        NUMBER
+    }
+}

+ 79 - 0
src/main/java/com/izouma/jmrh/domain/User.java

@@ -0,0 +1,79 @@
+package com.izouma.jmrh.domain;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.izouma.jmrh.annotations.Searchable;
+import com.izouma.jmrh.config.Constants;
+import com.izouma.jmrh.security.Authority;
+import io.swagger.annotations.ApiModel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.BatchSize;
+import org.hibernate.annotations.Where;
+
+import javax.persistence.*;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+@Data
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@Where(clause = "enabled = 1")
+@ApiModel(value = "用户", description = "用户")
+public class User extends BaseEntity implements Serializable {
+
+    @Pattern(regexp = Constants.Regex.USERNAME)
+    @Size(min = 1, max = 50)
+    @Column(nullable = false, unique = true)
+    @Searchable
+    private String username;
+
+    @Searchable
+    private String nickname;
+
+    private String avatar;
+
+    @JsonIgnore
+    private String password;
+
+    @Column(nullable = false)
+    private Boolean enabled = true;
+
+    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.DETACH})
+    @JoinTable(
+            name = "user_authority",
+            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))},
+            inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))})
+    @BatchSize(size = 20)
+    @ExcelIgnore
+    private Set<Authority> authorities = new HashSet<>();
+
+    private String openId;
+
+    private String sex;
+
+    private String language;
+
+    private String city;
+
+    private String province;
+
+    private String country;
+
+    @Searchable
+    private String phone;
+
+    private String email;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "orgInfoId")
+    private OrgInfo orgInfo;
+
+}

+ 15 - 0
src/main/java/com/izouma/jmrh/dto/PageQuery.java

@@ -0,0 +1,15 @@
+package com.izouma.jmrh.dto;
+
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+public class PageQuery {
+    private int                 page  = 0;
+    private int                 size  = 20;
+    private String              sort;
+    private String              search;
+    private Map<String, Object> query = new HashMap<>();
+}

+ 354 - 0
src/main/java/com/izouma/jmrh/dto/gen/GenCode.java

@@ -0,0 +1,354 @@
+package com.izouma.jmrh.dto.gen;
+
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import java.util.List;
+
+/**
+ * gen_code_model 实体类
+ * Fri May 04 15:57:06 CST 2018  Suo Chen Cheng
+ */
+@JsonAutoDetect
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class GenCode {
+
+    /**
+     * id
+     */
+    private Integer id;
+
+    /**
+     * 表名
+     */
+    private String tableName;
+
+    /**
+     * 类名
+     */
+    private String className;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 是否生成表
+     */
+    private Boolean genTable;
+
+    /**
+     * 是否生成类
+     */
+    private Boolean genClass;
+
+    /**
+     * 是否生成列表页面
+     */
+    private Boolean genList;
+
+    /**
+     * 是否生成表单页面
+     */
+    private Boolean genForm;
+
+    /**
+     * 是否生成路由
+     */
+    private Boolean genRouter;
+
+    /**
+     * 字段表id
+     */
+    private String fieldId;
+
+    /**
+     * java生成路径
+     */
+    private String javaPath;
+
+    /**
+     * vue生成路径
+     */
+    private String viewPath;
+
+    /**
+     * 路由生成路径
+     */
+    private String routerPath;
+
+    /**
+     * resources生成路径
+     */
+    private String resourcesPath;
+
+    /**
+     * 数据库类型:Mysql/SqlServer
+     */
+    private String dataBaseType;
+
+    private List<TableField> fields;    // 表字段
+    private TableField primaryField; //主键字段
+
+    private Boolean readTable;
+
+    /**
+     * 数据库CODE,使用什么数据源S
+     */
+    private String dataSourceCode;
+
+    /**
+     * 生成代码的JSON Str
+     */
+    private String genJson;
+
+    private String searchKey;
+
+    private String delFlag;
+
+    private String typeFlag;
+
+    private List<Subtable> subtables;
+
+    private boolean update;
+
+    private String basePackage;//基础包路径
+
+    private String tablePackage;//对象的包名
+
+    private String genPackage;//子包名
+
+
+    public List<TableField> getFields() {
+        return fields;
+    }
+
+    public void setFields(List<TableField> fields) {
+        this.fields = fields;
+    }
+
+    public TableField getPrimaryField() {
+        return primaryField;
+    }
+
+    public void setPrimaryField(TableField primaryField) {
+        this.primaryField = primaryField;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setTableName(String tableName) {
+        this.tableName = tableName;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public void setClassName(String className) {
+        this.className = className;
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setGenTable(Boolean genTable) {
+        this.genTable = genTable;
+    }
+
+    public Boolean getGenTable() {
+        return genTable;
+    }
+
+    public void setGenClass(Boolean genClass) {
+        this.genClass = genClass;
+    }
+
+    public Boolean getGenClass() {
+        return genClass;
+    }
+
+    public void setGenList(Boolean genList) {
+        this.genList = genList;
+    }
+
+    public Boolean getGenList() {
+        return genList;
+    }
+
+    public void setGenForm(Boolean genForm) {
+        this.genForm = genForm;
+    }
+
+    public Boolean getGenForm() {
+        return genForm;
+    }
+
+    public void setFieldId(String fieldId) {
+        this.fieldId = fieldId;
+    }
+
+    public String getFieldId() {
+        return fieldId;
+    }
+
+    public String getJavaPath() {
+        return javaPath;
+    }
+
+    public void setJavaPath(String javaPath) {
+        this.javaPath = javaPath;
+    }
+
+    public String getViewPath() {
+        return viewPath;
+    }
+
+    public void setViewPath(String viewPath) {
+        this.viewPath = viewPath;
+    }
+
+    public String getDataBaseType() {
+        return dataBaseType;
+    }
+
+    public void setDataBaseType(String dataBaseType) {
+        this.dataBaseType = dataBaseType;
+    }
+
+    public Boolean getReadTable() {
+        return readTable;
+    }
+
+    public void setReadTable(Boolean readTable) {
+        this.readTable = readTable;
+    }
+
+    public String getRouterPath() {
+        return routerPath;
+    }
+
+    public void setRouterPath(String routerPath) {
+        this.routerPath = routerPath;
+    }
+
+    public Boolean getGenRouter() {
+        return genRouter;
+    }
+
+    public void setGenRouter(Boolean genRouter) {
+        this.genRouter = genRouter;
+    }
+
+    public String getDataSourceCode() {
+        return dataSourceCode;
+    }
+
+    public void setDataSourceCode(String dataSourceCode) {
+        this.dataSourceCode = dataSourceCode;
+    }
+
+    public String getGenJson() {
+        return genJson;
+    }
+
+    public void setGenJson(String genJson) {
+        this.genJson = genJson;
+    }
+
+    public String getSearchKey() {
+        return searchKey;
+    }
+
+    public void setSearchKey(String searchKey) {
+        this.searchKey = searchKey;
+    }
+
+    public String getDelFlag() {
+        return delFlag;
+    }
+
+    public void setDelFlag(String delFlag) {
+        this.delFlag = delFlag;
+    }
+
+    public String getTypeFlag() {
+        return typeFlag;
+    }
+
+    public void setTypeFlag(String typeFlag) {
+        this.typeFlag = typeFlag;
+    }
+
+    public List<Subtable> getSubtables() {
+        return subtables;
+    }
+
+    public void setSubtables(List<Subtable> subtables) {
+        this.subtables = subtables;
+    }
+
+    public boolean getUpdate() {
+        return update;
+    }
+
+    public void setUpdate(boolean update) {
+        this.update = update;
+    }
+
+    public String getResourcesPath() {
+        return resourcesPath;
+    }
+
+    public void setResourcesPath(String resourcesPath) {
+        this.resourcesPath = resourcesPath;
+    }
+
+    public boolean isUpdate() {
+        return update;
+    }
+
+    public String getBasePackage() {
+        return basePackage;
+    }
+
+    public void setBasePackage(String basePackage) {
+        this.basePackage = basePackage;
+    }
+
+    public String getTablePackage() {
+        return tablePackage;
+    }
+
+    public void setTablePackage(String tablePackage) {
+        this.tablePackage = tablePackage;
+    }
+
+    public String getGenPackage() {
+        return genPackage;
+    }
+
+    public void setGenPackage(String genPackage) {
+        this.genPackage = genPackage;
+    }
+}
+

+ 55 - 0
src/main/java/com/izouma/jmrh/dto/gen/Subtable.java

@@ -0,0 +1,55 @@
+package com.izouma.jmrh.dto.gen;
+
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+/**
+ * table_field 实体类
+ * Fri May 04 13:38:24 CST 2018  Suo Chen Cheng
+ */
+@JsonAutoDetect
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Subtable {
+
+    private String name;
+
+    private String column;
+
+    private String subColumn;
+
+    private String subCode;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getColumn() {
+        return column;
+    }
+
+    public void setColumn(String column) {
+        this.column = column;
+    }
+
+    public String getSubColumn() {
+        return subColumn;
+    }
+
+    public void setSubColumn(String subColumn) {
+        this.subColumn = subColumn;
+    }
+
+    public String getSubCode() {
+        return subCode;
+    }
+
+    public void setSubCode(String subCode) {
+        this.subCode = subCode;
+    }
+}
+

+ 163 - 0
src/main/java/com/izouma/jmrh/dto/gen/TableField.java

@@ -0,0 +1,163 @@
+package com.izouma.jmrh.dto.gen;
+
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+@Data
+@JsonAutoDetect
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TableField {
+
+    /**
+     * id
+     */
+    private Integer id;
+
+    /**
+     * 字段名
+     */
+    private String name;
+
+    /**
+     * 驼峰式名字
+     */
+    private String modelName;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 数据类型(数据库)
+     */
+    private String jdbcType;
+
+    /**
+     * java类型
+     */
+    private String javaType;
+
+    /**
+     * 长度
+     */
+    private Integer length;
+
+    /**
+     * 小数长度
+     */
+    private Integer decimalPlaces;
+
+    /**
+     * 默认值
+     */
+    private String defaultValue;
+
+    /**
+     * 非空
+     */
+    private Boolean notNull;
+
+    /**
+     * 主键
+     */
+    private Boolean primaryKey;
+
+    /**
+     * 自增
+     */
+    private Boolean autoIncrease;
+
+    /**
+     * 在列表中显示
+     */
+    private Boolean showInList;
+
+    /**
+     * 在表单中显示
+     */
+    private Boolean showInForm;
+
+    /**
+     * 可以排序
+     */
+    private Boolean sortable;
+
+    /**
+     * 表单类型
+     */
+    private String formType;
+
+    /**
+     * 搜索方式
+     */
+    private String searchMethod;
+
+    /**
+     * 表单必填
+     */
+    private Boolean required;
+
+    /**
+     * 表单验证
+     */
+    private Boolean validate;
+
+    /**
+     * 最短长度
+     */
+    private Integer minLength;
+
+    /**
+     * 最大长度
+     */
+    private Integer maxLength;
+
+    /**
+     * 最小值
+     */
+    private String min;
+
+    /**
+     * 最大值
+     */
+    private String max;
+
+    /**
+     * 验证类型
+     */
+    private String validatorType;
+
+    /**
+     * xml里jdbcType
+     */
+    private String sqlType;
+
+    /**
+     * 下拉框类型 1 枚举, 2 接口, 3 读表
+     */
+    private String apiFlag;
+
+    /**
+     * 接口方法/表名
+     */
+    private String optionsMethod;
+
+    /**
+     * 选项值,在枚举是用该值解析
+     */
+    private String optionsValue;
+
+
+    /**
+     * 显示值
+     */
+    private String optionsLabel;
+
+
+}
+

+ 17 - 0
src/main/java/com/izouma/jmrh/enums/OrgStatus.java

@@ -0,0 +1,17 @@
+package com.izouma.jmrh.enums;
+
+public enum OrgStatus {
+    PENDING("待审核"),
+    PASS("审核通过"),
+    DENY("审核失败");
+
+    private final String description;
+
+    OrgStatus(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

+ 7 - 0
src/main/java/com/izouma/jmrh/exception/AuthenticationException.java

@@ -0,0 +1,7 @@
+package com.izouma.jmrh.exception;
+
+public class AuthenticationException extends RuntimeException {
+    public AuthenticationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

+ 45 - 0
src/main/java/com/izouma/jmrh/exception/BusinessException.java

@@ -0,0 +1,45 @@
+package com.izouma.jmrh.exception;
+
+import java.util.function.Supplier;
+
+public class BusinessException extends RuntimeException implements Supplier<BusinessException> {
+    private static final long serialVersionUID = 3779880207424189309L;
+
+    private Integer code = -1;
+    private String  error;
+
+    public BusinessException(String error) {
+        super();
+        this.error = error;
+    }
+
+    public BusinessException(String error, String message) {
+        super(message);
+        this.error = error;
+    }
+
+    public BusinessException(String error, String message, Integer code) {
+        super(message);
+        this.code = code;
+        this.error = error;
+    }
+
+    public BusinessException(String error, int code) {
+        super();
+        this.error = error;
+        this.code = code;
+    }
+
+    @Override
+    public BusinessException get() {
+        return this;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getError() {
+        return error;
+    }
+}

+ 149 - 0
src/main/java/com/izouma/jmrh/exception/GlobalExceptionHandler.java

@@ -0,0 +1,149 @@
+package com.izouma.jmrh.exception;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.transaction.TransactionSystemException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Path;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@ControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(value = BusinessException.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    @ResponseBody
+    public Map<String, Object> serviceExceptionHandler(BusinessException e) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("error", e.getError());
+        map.put("message", e.getMessage());
+        map.put("code", -1);
+        return map;
+    }
+
+    @ExceptionHandler({AuthenticationException.class})
+    @ResponseStatus(HttpStatus.UNAUTHORIZED)
+    @ResponseBody
+    public Map<String, Object> handleAuthenticationException(AuthenticationException e) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("error", e.getMessage());
+        map.put("code", -1);
+        return map;
+    }
+
+    @ExceptionHandler(value = TransactionSystemException.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    @ResponseBody
+    public Map<String, Object> serviceExceptionHandler(TransactionSystemException e) {
+        String message = e.getMessage();
+        try {
+            if (e.getCause().getCause() instanceof ConstraintViolationException) {
+                ConstraintViolationException violationException = (ConstraintViolationException) e.getCause()
+                        .getCause();
+                message = violationException.getConstraintViolations().stream()
+                        .map(constraintViolation -> constraintViolation.getPropertyPath() + constraintViolation.getMessage())
+                        .collect(Collectors.joining(","));
+                log.error(message);
+            }
+        } catch (Exception ignore) {
+        }
+        Map<String, Object> map = new HashMap<>();
+        map.put("error", message);
+        map.put("code", -1);
+        return map;
+    }
+
+    @ExceptionHandler(value = Exception.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    @ResponseBody
+    public Object serviceExceptionHandler(Exception e, HttpServletRequest request) {
+        log.error(e.getMessage(), e);
+        if (request.getHeader("Accept").contains("text/html")) {
+            ModelAndView modelAndView = new ModelAndView("commons/500");
+            StringWriter out = new StringWriter();
+            PrintWriter writer = new PrintWriter(out);
+            e.printStackTrace(writer);
+            String trace = out.toString();
+            trace = trace.replaceAll("\n", "<br>");
+            modelAndView.addObject("trace", trace);
+            return modelAndView;
+        } else {
+            Map<String, Object> map = new HashMap<>();
+            map.put("error", e.getMessage());
+            map.put("code", -1);
+            return map;
+        }
+    }
+
+    @ExceptionHandler({BindException.class, ConstraintViolationException.class, MethodArgumentNotValidException.class})
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    @ResponseBody
+    public Map<String, Object> validateExceptionHandler(Exception e, HttpServletRequest request) {
+        log.error("请求:{}发生异常:{}", request.getRequestURI(), e);
+
+        // 错误信息
+        StringBuilder sb = new StringBuilder("参数校验失败:");
+        // 错误信息map
+        Map<String, String> error = new HashMap<>();
+
+        String msg = "";
+        if (!(e instanceof BindException) && !(e instanceof MethodArgumentNotValidException)) {
+            for (ConstraintViolation cv : ((ConstraintViolationException) e).getConstraintViolations()) {
+                msg = cv.getMessage();
+                sb.append(msg).append(";");
+
+                Iterator<Path.Node> it = cv.getPropertyPath().iterator();
+                Path.Node last = null;
+                while (it.hasNext()) {
+                    last = (Path.Node) it.next();
+                }
+                /*for(last = null; it.hasNext(); last = (Path.Node)it.next()) {
+                }*/
+                error.put(last != null ? last.getName() : "", msg);
+            }
+        } else {
+            List<ObjectError> allErrors = null;
+            if (e instanceof BindException) {
+                allErrors = ((BindException) e).getAllErrors();
+            } else {
+                allErrors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors();
+            }
+            // 拼接错误信息
+            for (ObjectError oe : allErrors) {
+                msg = oe.getDefaultMessage();
+                sb.append(msg).append(";");
+                if (oe instanceof FieldError) {
+                    error.put(((FieldError) oe).getField(), msg);
+                } else {
+                    error.put(oe.getObjectName(), msg);
+                }
+            }
+        }
+
+        Map<String, Object> map = new HashMap<>();
+        map.put("error", "参数校验失败");
+        map.put("message", sb);
+        map.put("code", -1);
+        log.error(e.getMessage());
+        return map;
+    }
+}

+ 23 - 0
src/main/java/com/izouma/jmrh/mpHandler/LogHandler.java

@@ -0,0 +1,23 @@
+package com.izouma.jmrh.mpHandler;
+
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.mp.api.WxMpMessageHandler;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
+import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+@Component
+@Slf4j
+public class LogHandler implements WxMpMessageHandler {
+    @Override
+    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
+                                    Map<String, Object> context, WxMpService wxMpService,
+                                    WxSessionManager sessionManager) {
+        log.info("\n接收到请求消息,内容:{}", wxMessage);
+        return null;
+    }
+}

+ 8 - 0
src/main/java/com/izouma/jmrh/repo/ApplicationFieldRepo.java

@@ -0,0 +1,8 @@
+package com.izouma.jmrh.repo;
+
+import com.izouma.jmrh.domain.ApplicationField;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ApplicationFieldRepo extends JpaRepository<ApplicationField, Long>, JpaSpecificationExecutor<ApplicationField> {
+}

+ 8 - 0
src/main/java/com/izouma/jmrh/repo/ArticleRepo.java

@@ -0,0 +1,8 @@
+package com.izouma.jmrh.repo;
+
+import com.izouma.jmrh.domain.Article;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ArticleRepo extends JpaRepository<Article, Long>, JpaSpecificationExecutor<Article> {
+}

+ 8 - 0
src/main/java/com/izouma/jmrh/repo/ArticleTypeRepo.java

@@ -0,0 +1,8 @@
+package com.izouma.jmrh.repo;
+
+import com.izouma.jmrh.domain.ArticleType;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ArticleTypeRepo extends JpaRepository<ArticleType, Long>, JpaSpecificationExecutor<ArticleType> {
+}

+ 7 - 0
src/main/java/com/izouma/jmrh/repo/AuthorityRepo.java

@@ -0,0 +1,7 @@
+package com.izouma.jmrh.repo;
+
+import com.izouma.jmrh.security.Authority;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface AuthorityRepo extends JpaRepository<Authority, String> {
+}

+ 18 - 0
src/main/java/com/izouma/jmrh/repo/MenuRepo.java

@@ -0,0 +1,18 @@
+package com.izouma.jmrh.repo;
+
+import com.izouma.jmrh.domain.Menu;
+import org.hibernate.annotations.Where;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.List;
+
+public interface MenuRepo extends JpaRepository<Menu, Long> {
+    List<Menu> findByRootTrue();
+
+    @Where(clause = "enabled = 1")
+    List<Menu> findByNameAndRootTrue(String name);
+
+    @Query(nativeQuery = true, value = "SELECT ifnull(max(sort + 1),1) FROM menu")
+    int nextSort();
+}

+ 8 - 0
src/main/java/com/izouma/jmrh/repo/OrgInfoRepo.java

@@ -0,0 +1,8 @@
+package com.izouma.jmrh.repo;
+
+import com.izouma.jmrh.domain.OrgInfo;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface OrgInfoRepo extends JpaRepository<OrgInfo, Long>, JpaSpecificationExecutor<OrgInfo> {
+}

+ 8 - 0
src/main/java/com/izouma/jmrh/repo/RequirementRepo.java

@@ -0,0 +1,8 @@
+package com.izouma.jmrh.repo;
+
+import com.izouma.jmrh.domain.Requirement;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface RequirementRepo extends JpaRepository<Requirement, Long>, JpaSpecificationExecutor<Requirement> {
+}

+ 24 - 0
src/main/java/com/izouma/jmrh/repo/SmsRecordRepo.java

@@ -0,0 +1,24 @@
+package com.izouma.jmrh.repo;
+
+import com.izouma.jmrh.domain.SmsRecord;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+import javax.transaction.Transactional;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+public interface SmsRecordRepo extends JpaRepository<SmsRecord, Long> {
+    Optional<SmsRecord> findLastBySessionIdAndExpiresAtAfterAndExpiredFalse(String sessionId, LocalDateTime time);
+
+    Optional<SmsRecord> findLastByPhoneAndExpiresAtAfterAndExpiredFalse(String phone, LocalDateTime time);
+
+    List<SmsRecord> findAllByPhoneAndExpiresAtAfterAndExpiredFalse(String phone, LocalDateTime time);
+
+    @Query("update SmsRecord s set s.expired = true where s.phone = ?1")
+    @Modifying
+    @Transactional
+    void expire(String phone);
+}

+ 11 - 0
src/main/java/com/izouma/jmrh/repo/SysConfigRepo.java

@@ -0,0 +1,11 @@
+package com.izouma.jmrh.repo;
+
+import com.izouma.jmrh.domain.SysConfig;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+import java.util.Optional;
+
+public interface SysConfigRepo extends JpaRepository<SysConfig, String>, JpaSpecificationExecutor<SysConfig> {
+    Optional<SysConfig> findByName(String name);
+}

+ 18 - 0
src/main/java/com/izouma/jmrh/repo/UserRepo.java

@@ -0,0 +1,18 @@
+package com.izouma.jmrh.repo;
+
+import com.izouma.jmrh.domain.User;
+import com.izouma.jmrh.security.Authority;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+import java.util.List;
+
+public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
+    User findByUsername(String username);
+
+    List<User> findAllByAuthoritiesContains(Authority authority);
+
+    User findByOpenId(String openId);
+
+    User findByPhone(String phone);
+}

+ 57 - 0
src/main/java/com/izouma/jmrh/security/Authority.java

@@ -0,0 +1,57 @@
+package com.izouma.jmrh.security;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Objects;
+
+@Entity
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Authority implements Serializable {
+    public enum NAMES {
+        ROLE_USER, ROLE_DEV, ROLE_ADMIN
+    }
+
+    @Id
+    @Size(max = 50)
+    @NotNull
+    @Column(length = 50)
+    private String name;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        Authority authority = (Authority) o;
+
+        return Objects.equals(name, authority.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return name != null ? name.hashCode() : 0;
+    }
+
+    @Override
+    public String toString() {
+        return "Authority{" +
+                "name='" + name + '\'' +
+                "}";
+    }
+}

+ 25 - 0
src/main/java/com/izouma/jmrh/security/JwtAuthenticationEntryPoint.java

@@ -0,0 +1,25 @@
+package com.izouma.jmrh.security;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.Serializable;
+
+@Component
+public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
+
+    private static final long serialVersionUID = -8970718410437077606L;
+
+    @Override
+    public void commence(HttpServletRequest request,
+                         HttpServletResponse response,
+                         AuthenticationException authException) throws IOException {
+        // This is invoked when user tries to access a secured REST resource without supplying any credentials
+        // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "授权失败");
+    }
+}

+ 87 - 0
src/main/java/com/izouma/jmrh/security/JwtAuthorizationTokenFilter.java

@@ -0,0 +1,87 @@
+package com.izouma.jmrh.security;
+
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.SignatureException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+@Slf4j
+public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
+
+    private final UserDetailsService userDetailsService;
+    private final JwtTokenUtil       jwtTokenUtil;
+    private final String             tokenHeader;
+
+    public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.header}") String tokenHeader) {
+        this.userDetailsService = userDetailsService;
+        this.jwtTokenUtil = jwtTokenUtil;
+        this.tokenHeader = tokenHeader;
+    }
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
+        log.debug("processing authentication for '{}'", request.getRequestURL());
+
+        final String requestHeader = request.getHeader(this.tokenHeader);
+
+        String username = null;
+        String authToken = null;
+        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
+            authToken = requestHeader.substring(7);
+            try {
+                username = jwtTokenUtil.getUsernameFromToken(authToken);
+            } catch (IllegalArgumentException e) {
+                log.error("an error occurred during getting username from token", e);
+            } catch (ExpiredJwtException e) {
+                log.warn("the token is expired and not valid anymore", e);
+            } catch (SignatureException e) {
+                log.error(e.getMessage());
+            }
+        } else {
+            log.warn("couldn't find bearer string, will ignore the header");
+        }
+
+        log.debug("checking authentication for user '{}'", username);
+        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+            log.debug("security context was null, so authorizing user");
+
+            // It is not compelling necessary to load the use details from the database. You could also store the information
+            // in the token and read it from it. It's up to you ;)            
+            UserDetails userDetails;
+            try {
+                userDetails = userDetailsService.loadUserByUsername(username);
+            } catch (Exception e) {
+                //response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
+                chain.doFilter(request, response);
+                return;
+            }
+
+
+            // For simple validation it is completely sufficient to just check the token integrity. You don't have to call
+            // the database compellingly. Again it's up to you ;)
+            if (jwtTokenUtil.validateToken(authToken, userDetails)) {
+                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+                log.info("authorized user '{}', setting security context", username);
+                SecurityContextHolder.getContext().setAuthentication(authentication);
+            }
+        }
+
+        chain.doFilter(request, response);
+    }
+}

+ 12 - 0
src/main/java/com/izouma/jmrh/security/JwtConfig.java

@@ -0,0 +1,12 @@
+package com.izouma.jmrh.security;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "jwt")
+public class JwtConfig {
+    private String secret;
+    private String header;
+    private Long   expiration;
+}

+ 125 - 0
src/main/java/com/izouma/jmrh/security/JwtTokenUtil.java

@@ -0,0 +1,125 @@
+package com.izouma.jmrh.security;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Clock;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.impl.DefaultClock;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+@Component
+public class JwtTokenUtil implements Serializable {
+
+    private static final long serialVersionUID = -3301605591108950415L;
+
+    static final String CLAIM_KEY_USERNAME = "sub";
+    static final String CLAIM_KEY_CREATED  = "iat";
+
+    @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "It's okay here")
+    private Clock     clock = DefaultClock.INSTANCE;
+    private JwtConfig jwtConfig;
+
+    public JwtTokenUtil(JwtConfig jwtConfig) {
+        this.jwtConfig = jwtConfig;
+    }
+
+    public String getUsernameFromToken(String token) {
+        return getClaimFromToken(token, Claims::getSubject);
+    }
+
+    public Date getIssuedAtDateFromToken(String token) {
+        return getClaimFromToken(token, Claims::getIssuedAt);
+    }
+
+    public Date getExpirationDateFromToken(String token) {
+        return getClaimFromToken(token, Claims::getExpiration);
+    }
+
+    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
+        final Claims claims = getAllClaimsFromToken(token);
+        return claimsResolver.apply(claims);
+    }
+
+    private Claims getAllClaimsFromToken(String token) {
+        return Jwts.parser()
+                .setSigningKey(jwtConfig.getSecret())
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    private Boolean isTokenExpired(String token) {
+        final Date expiration = getExpirationDateFromToken(token);
+        return expiration.before(clock.now());
+    }
+
+    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
+        return (lastPasswordReset != null && created.before(lastPasswordReset));
+    }
+
+    private Boolean ignoreTokenExpiration(String token) {
+        // here you specify tokens, for that the expiration is ignored
+        return false;
+    }
+
+    public String generateToken(UserDetails userDetails) {
+        Map<String, Object> claims = new HashMap<>();
+        return doGenerateToken(claims, userDetails.getUsername());
+    }
+
+    private String doGenerateToken(Map<String, Object> claims, String subject) {
+        final Date createdDate = clock.now();
+        final Date expirationDate = calculateExpirationDate(createdDate);
+
+        return Jwts.builder()
+                .setClaims(claims)
+                .setSubject(subject)
+                .setIssuedAt(createdDate)
+                .setExpiration(expirationDate)
+                .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
+                .compact();
+    }
+
+    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
+        final Date created = getIssuedAtDateFromToken(token);
+        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
+                && (!isTokenExpired(token) || ignoreTokenExpiration(token));
+    }
+
+    public String refreshToken(String token) {
+        final Date createdDate = clock.now();
+        final Date expirationDate = calculateExpirationDate(createdDate);
+
+        final Claims claims = getAllClaimsFromToken(token);
+        claims.setIssuedAt(createdDate);
+        claims.setExpiration(expirationDate);
+
+        return Jwts.builder()
+                .setClaims(claims)
+                .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
+                .compact();
+    }
+
+    public Boolean validateToken(String token, UserDetails userDetails) {
+        JwtUser user = (JwtUser) userDetails;
+        final String username = getUsernameFromToken(token);
+        final Date created = getIssuedAtDateFromToken(token);
+        //final Date expiration = getExpirationDateFromToken(token);
+        return (
+                username.equals(user.getUsername())
+                        && !isTokenExpired(token)
+                        && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
+        );
+    }
+
+    private Date calculateExpirationDate(Date createdDate) {
+        return new Date(createdDate.getTime() + jwtConfig.getExpiration() * 1000);
+    }
+}

+ 81 - 0
src/main/java/com/izouma/jmrh/security/JwtUser.java

@@ -0,0 +1,81 @@
+package com.izouma.jmrh.security;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.izouma.jmrh.domain.User;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.time.ZoneId;
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * Created by stephan on 20.03.16.
+ */
+public class JwtUser implements UserDetails {
+
+    private static final long serialVersionUID = 5803985158027956021L;
+
+    private final Collection<? extends GrantedAuthority> authorities;
+    private final Date                                   lastPasswordResetDate;
+    private       User                                   user;
+
+    public JwtUser(User user, Collection<? extends GrantedAuthority> authorities) {
+        this.authorities = authorities;
+        this.lastPasswordResetDate = Date.from(user.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant());
+        this.user = user;
+    }
+
+    @JsonIgnore
+    public Long getId() {
+        return user.getId();
+    }
+
+    @Override
+    public String getUsername() {
+        return user.getUsername();
+    }
+
+    @JsonIgnore
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @JsonIgnore
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @JsonIgnore
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @JsonIgnore
+    @Override
+    public String getPassword() {
+        return user.getPassword();
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return authorities;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return user.getEnabled();
+    }
+
+    @JsonIgnore
+    public Date getLastPasswordResetDate() {
+        return lastPasswordResetDate;
+    }
+
+    public User getUser() {
+        return user;
+    }
+}

+ 26 - 0
src/main/java/com/izouma/jmrh/security/JwtUserDetailsService.java

@@ -0,0 +1,26 @@
+package com.izouma.jmrh.security;
+
+import com.izouma.jmrh.domain.User;
+import com.izouma.jmrh.repo.UserRepo;
+import lombok.AllArgsConstructor;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+@AllArgsConstructor
+@Service("jwtUserDetailsService")
+public class JwtUserDetailsService implements UserDetailsService {
+    private UserRepo userRepo;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        User user = userRepo.findByUsername(username);
+
+        if (user == null) {
+            throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
+        } else {
+            return JwtUserFactory.create(user);
+        }
+    }
+}

+ 28 - 0
src/main/java/com/izouma/jmrh/security/JwtUserFactory.java

@@ -0,0 +1,28 @@
+package com.izouma.jmrh.security;
+
+import com.izouma.jmrh.domain.User;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class JwtUserFactory {
+
+    private JwtUserFactory() {
+    }
+
+    public static JwtUser create(User user) {
+        return new JwtUser(user, mapToGrantedAuthorities(user.getAuthorities()));
+    }
+
+    private static List<GrantedAuthority> mapToGrantedAuthorities(Set<Authority> authorities) {
+        if (authorities != null) {
+            return authorities.stream()
+                              .map(authority -> new SimpleGrantedAuthority(authority.getName()))
+                              .collect(Collectors.toList());
+        }
+        return null;
+    }
+}

+ 110 - 0
src/main/java/com/izouma/jmrh/security/WebSecurityConfig.java

@@ -0,0 +1,110 @@
+package com.izouma.jmrh.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+@EnableConfigurationProperties({JwtConfig.class})
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Autowired
+    private JwtAuthenticationEntryPoint unauthorizedHandler;
+
+    @Autowired
+    private JwtUserDetailsService jwtUserDetailsService;
+
+    // Custom JWT based security filter
+    @Autowired
+    JwtAuthorizationTokenFilter authenticationTokenFilter;
+
+    @Value("${jwt.header}")
+    private String tokenHeader;
+
+    @Autowired
+    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+        auth.userDetailsService(jwtUserDetailsService)
+                .passwordEncoder(passwordEncoderBean());
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoderBean() {
+        return new BCryptPasswordEncoder();
+    }
+
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+    @Override
+    protected void configure(HttpSecurity httpSecurity) throws Exception {
+        // We don't need CSRF for this example
+        httpSecurity.csrf().disable()
+                .cors().and()
+                // dont authenticate this particular request
+                .authorizeRequests()
+                //swagger-ui放行路径
+                .antMatchers("/v2/api-docs", "/swagger-ui.html", "/swagger-resources/**", "/webjars/**").permitAll()
+                .antMatchers("/user/register").permitAll()
+                .antMatchers("/upload/**").permitAll()
+                .antMatchers("/static/**").permitAll()
+                .antMatchers("/auth/**").permitAll()
+                .antMatchers("/admin/**").permitAll()
+                .antMatchers("/systemVariable/all").permitAll()
+                .antMatchers("/**/excel").permitAll()
+                .antMatchers("/wx/**").permitAll()
+                .antMatchers("/sms/sendVerify").permitAll()
+                .antMatchers("/error").permitAll()
+                .antMatchers("/401").permitAll()
+                .antMatchers("/404").permitAll()
+                .antMatchers("/500").permitAll()
+                .antMatchers("/test500").permitAll()
+                .antMatchers("/article/all").permitAll()
+                .antMatchers("/article/get/*").permitAll()
+                // all other requests need to be authenticated
+                .anyRequest().authenticated().and()
+                // make sure we use stateless session; session won't be used to
+                // store user's state.
+                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
+                .and().sessionManagement()
+                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+        // Add a filter to validate the tokens with every request
+        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+    }
+
+    @Override
+    public void configure(WebSecurity web) throws Exception {
+        // AuthenticationTokenFilter will ignore the below paths
+        web.ignoring()
+                .antMatchers("/auth/**")
+
+                // allow anonymous resource requests
+                .and()
+                .ignoring()
+                .antMatchers(
+                        HttpMethod.GET,
+                        "/",
+                        "/*.html",
+                        "/**/favicon.ico",
+                        "/**/*.html",
+                        "/**/*.css",
+                        "/**/*.js"
+                );
+    }
+}

+ 14 - 0
src/main/java/com/izouma/jmrh/service/ApplicationFieldService.java

@@ -0,0 +1,14 @@
+package com.izouma.jmrh.service;
+
+import com.izouma.jmrh.domain.ApplicationField;
+import com.izouma.jmrh.repo.ApplicationFieldRepo;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class ApplicationFieldService {
+
+    private ApplicationFieldRepo applicationFieldRepo;
+
+}

+ 14 - 0
src/main/java/com/izouma/jmrh/service/ArticleService.java

@@ -0,0 +1,14 @@
+package com.izouma.jmrh.service;
+
+import com.izouma.jmrh.domain.Article;
+import com.izouma.jmrh.repo.ArticleRepo;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class ArticleService {
+
+    private ArticleRepo articleRepo;
+
+}

+ 14 - 0
src/main/java/com/izouma/jmrh/service/ArticleTypeService.java

@@ -0,0 +1,14 @@
+package com.izouma.jmrh.service;
+
+import com.izouma.jmrh.domain.ArticleType;
+import com.izouma.jmrh.repo.ArticleTypeRepo;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class ArticleTypeService {
+
+    private ArticleTypeRepo articleTypeRepo;
+
+}

+ 181 - 0
src/main/java/com/izouma/jmrh/service/GenCodeService.java

@@ -0,0 +1,181 @@
+package com.izouma.jmrh.service;
+
+import com.izouma.jmrh.dto.gen.GenCode;
+import com.izouma.jmrh.dto.gen.TableField;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import jodd.util.StringUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.awt.*;
+import java.awt.font.FontRenderContext;
+import java.awt.geom.AffineTransform;
+import java.io.*;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class GenCodeService {
+    @Autowired
+    private Configuration cfg;
+
+    public void genController(GenCode model) throws IOException, TemplateException {
+
+        Path targetFile = StringUtils.isNotBlank(model.getGenPackage()) ?
+                Paths.get(model.getJavaPath(), "web", model.getGenPackage(), model.getClassName() + "Controller.java")
+                        .toAbsolutePath() :
+                Paths.get(model.getJavaPath(), "web", model.getClassName() + "Controller.java").toAbsolutePath();
+        Map<String, Object> extra = new HashMap<>();
+        if (StringUtil.isNotEmpty(model.getGenPackage())) {
+            List<String> imports = new ArrayList<>();
+            imports.add("import " + model.getBasePackage() + ".web.BaseController;");
+            extra.put("imports", imports);
+        }
+        extra.put("subPackage", StringUtil.isNotEmpty(model.getGenPackage()));
+        extra.put("softDelete", canSoftDelete(model));
+        genFile("ControllerTemplate.ftl", model, extra, targetFile);
+        log.info("成功生成Controller:{}", targetFile.toString());
+    }
+
+    public void genService(GenCode model) throws IOException, TemplateException {
+        Path targetFile = StringUtils.isNotBlank(model.getGenPackage()) ?
+                Paths.get(model.getJavaPath(), "service", model.getGenPackage(), model.getClassName() + "Service.java")
+                        .toAbsolutePath() :
+                Paths.get(model.getJavaPath(), "service", model.getClassName() + "Service.java").toAbsolutePath();
+        Map<String, Object> extra = new HashMap<>();
+        extra.put("softDelete", canSoftDelete(model));
+        genFile("ServiceTemplate.ftl", model, extra, targetFile);
+        log.info("成功生成Service:{}", targetFile.toString());
+    }
+
+    public void genRepo(GenCode model) throws IOException, TemplateException, ClassNotFoundException {
+        Path targetFile = StringUtils.isNotBlank(model.getGenPackage()) ?
+                Paths.get(model.getJavaPath(), "repo", model.getGenPackage(), model.getClassName() + "Repo.java")
+                        .toAbsolutePath() :
+                Paths.get(model.getJavaPath(), "repo", model.getClassName() + "Repo.java").toAbsolutePath();
+        Map<String, Object> extra = new HashMap<>();
+        extra.put("softDelete", canSoftDelete(model));
+        genFile("RepoTemplate.ftl", model, extra, targetFile);
+        log.info("成功生成Repo:{}", targetFile.toString());
+    }
+
+    public void genListView(GenCode model) throws IOException, TemplateException {
+        Path targetFile = Paths.get(model.getViewPath(), model.getClassName() + "List.vue").toAbsolutePath();
+        Map<String, Object> extra = new HashMap<>();
+        extra.put("softDelete", canSoftDelete(model));
+        genFile("ListViewTemplate.ftl", model, extra, targetFile);
+        log.info("成功生成ListView:{}", targetFile.toString());
+    }
+
+    public void genEditView(GenCode model) throws IOException, TemplateException, FontFormatException {
+        Path targetFile = Paths.get(model.getViewPath(), model.getClassName() + "Edit.vue").toAbsolutePath();
+        Map<String, Object> map = new HashMap<>();
+        int maxLabelWidth = 0;
+        for (TableField field : model.getFields()) {
+            String label = StringUtils.isEmpty(field.getRemark()) ? field.getModelName() : field.getRemark();
+            int labelWidth = measureText(label, 14);
+            if (labelWidth > maxLabelWidth) {
+                maxLabelWidth = labelWidth;
+            }
+        }
+        map.put("labelWidth", maxLabelWidth + 25 + "px");
+        map.put("softDelete", canSoftDelete(model));
+        genFile("EditViewTemplate.ftl", model, map, targetFile);
+        log.info("成功生成EditView:{}", targetFile.toString());
+    }
+
+    private void genFile(String template, GenCode model, Map<String, Object> extraData, Path path) throws IOException, TemplateException {
+        Map<String, Object> data = new HashMap<>();
+        data.put("model", model);
+        if (extraData != null) {
+            data.putAll(extraData);
+        }
+
+        Files.createDirectories(path.getParent());
+
+        Template temp = cfg.getTemplate(template);
+        Writer out = new FileWriter(path.toString());
+        temp.process(data, out);
+        out.flush();
+        out.close();
+    }
+
+    private int measureText(String text, float fontSize) throws IOException, FontFormatException {
+        AffineTransform affinetransform = new AffineTransform();
+        FontRenderContext frc = new FontRenderContext(affinetransform, true, true);
+        Font font = Font.createFont(Font.TRUETYPE_FONT, this.getClass()
+                .getResourceAsStream("/font/SourceHanSansCN-Normal.ttf"));
+        return (int) (font.deriveFont(fontSize).getStringBounds(text, frc).getWidth());
+    }
+
+    public void genRouter(GenCode model) {
+        try {
+            File file = new File(model.getRouterPath(), "router.js");
+            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
+            StringBuilder routerJs = new StringBuilder();
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                routerJs.append(line).append("\n");
+            }
+            reader.close();
+
+            int insertLocation = routerJs.indexOf("/**INSERT_LOCATION**/");
+            if (insertLocation > -1) {
+                String remark = model.getRemark();
+                String routeName = StringUtils.capitalize(model.getClassName());
+                String routePath = StringUtils.uncapitalize(model.getClassName());
+                String route = "{\n                    path: '/"
+                        + routePath
+                        + "Edit',\n                    name: '"
+                        + routeName
+                        + "Edit',\n                    component: () => import(/* webpackChunkName: \"" + routePath + "Edit\" */ '@/views/"
+                        + routeName
+                        + "Edit.vue'),\n                    meta: {\n                       title: '" + remark + "编辑',\n"
+                        + "                    },\n                },\n                {\n                    path: '/"
+                        + routePath
+                        + "List',\n                    name: '"
+                        + routeName
+                        + "List',\n                    component: () => import(/* webpackChunkName: \"" + routePath + "List\" */ '@/views/"
+                        + routeName
+                        + "List.vue'),\n                    meta: {\n                       title: '" + remark + "',\n"
+                        + "                    },\n               }\n                ";
+                boolean needComma = !routerJs.toString().substring(0, insertLocation).trim().endsWith(",");
+                if (needComma) {
+                    routerJs.insert(routerJs.toString().substring(0, insertLocation).lastIndexOf("}") + 1, ",");
+                    insertLocation++;
+                }
+                routerJs.insert(insertLocation, route);
+            }
+
+            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file));
+            bufferedOutputStream.write(routerJs.toString().trim().getBytes());
+            bufferedOutputStream.close();
+
+            System.out.println("成功生成路由:" + file.getAbsolutePath());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private boolean canSoftDelete(GenCode model) {
+        try {
+            Field field = Class.forName(model.getTablePackage()).getDeclaredField("enabled");
+            if (field != null && field.getType().equals(Boolean.class)) {
+                return true;
+            }
+        } catch (NoSuchFieldException | ClassNotFoundException ignored) {
+        }
+        return false;
+    }
+}

+ 35 - 0
src/main/java/com/izouma/jmrh/service/OrgInfoService.java

@@ -0,0 +1,35 @@
+package com.izouma.jmrh.service;
+
+import com.izouma.jmrh.domain.OrgInfo;
+import com.izouma.jmrh.domain.User;
+import com.izouma.jmrh.enums.OrgStatus;
+import com.izouma.jmrh.exception.BusinessException;
+import com.izouma.jmrh.repo.OrgInfoRepo;
+import com.izouma.jmrh.repo.UserRepo;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class OrgInfoService {
+
+    private OrgInfoRepo orgInfoRepo;
+    private UserRepo    userRepo;
+
+    public void apply(Long userId, OrgInfo orgInfo) {
+        User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
+        orgInfo.setUserId(userId);
+        orgInfo.setStatus(OrgStatus.PENDING);
+        orgInfoRepo.save(orgInfo);
+        user.setOrgInfo(orgInfo);
+        userRepo.save(user);
+    }
+
+    public void check(Long id, OrgStatus status) {
+        OrgInfo orgInfo = orgInfoRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+        orgInfo.setStatus(status);
+        orgInfoRepo.save(orgInfo);
+    }
+
+
+}

+ 14 - 0
src/main/java/com/izouma/jmrh/service/RequirementService.java

@@ -0,0 +1,14 @@
+package com.izouma.jmrh.service;
+
+import com.izouma.jmrh.domain.Requirement;
+import com.izouma.jmrh.repo.RequirementRepo;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class RequirementService {
+
+    private RequirementRepo requirementRepo;
+
+}

+ 39 - 0
src/main/java/com/izouma/jmrh/service/SysConfigService.java

@@ -0,0 +1,39 @@
+package com.izouma.jmrh.service;
+
+import com.izouma.jmrh.domain.SysConfig;
+import com.izouma.jmrh.exception.BusinessException;
+import com.izouma.jmrh.repo.SysConfigRepo;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+import springfox.documentation.annotations.Cacheable;
+
+import java.math.BigDecimal;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+@Service
+@AllArgsConstructor
+public class SysConfigService {
+    private SysConfigRepo sysConfigRepo;
+
+    @Cacheable("SysConfigServiceGetBigDecimal")
+    public BigDecimal getBigDecimal(String name) {
+        return sysConfigRepo.findByName(name).map(sysConfig -> new BigDecimal(sysConfig.getValue()))
+                .orElse(BigDecimal.ZERO);
+    }
+
+    @Cacheable("SysConfigServiceGetTime")
+    public LocalTime getTime(String name) {
+        String str = sysConfigRepo.findByName(name).map(SysConfig::getValue)
+                .orElseThrow(new BusinessException("配置不存在"));
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm");
+        return LocalTime.from(dateTimeFormatter.parse(str));
+    }
+
+    @Cacheable("SysConfigServiceGetBoolean")
+    public boolean getBoolean(String name) {
+        String str = sysConfigRepo.findByName(name).map(SysConfig::getValue)
+                .orElseThrow(new BusinessException("配置不存在"));
+        return str.equals("1");
+    }
+}

+ 148 - 0
src/main/java/com/izouma/jmrh/service/UserService.java

@@ -0,0 +1,148 @@
+package com.izouma.jmrh.service;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
+import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
+import com.izouma.jmrh.config.Constants;
+import com.izouma.jmrh.domain.User;
+import com.izouma.jmrh.exception.BusinessException;
+import com.izouma.jmrh.repo.UserRepo;
+import com.izouma.jmrh.security.Authority;
+import com.izouma.jmrh.security.JwtTokenUtil;
+import com.izouma.jmrh.security.JwtUserFactory;
+import com.izouma.jmrh.service.sms.SmsService;
+import com.izouma.jmrh.service.storage.StorageService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
+import me.chanjar.weixin.mp.bean.result.WxMpUser;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+@Service
+@Slf4j
+@AllArgsConstructor
+public class UserService {
+    private UserRepo       userRepo;
+    private WxMaService    wxMaService;
+    private WxMpService    wxMpService;
+    private SmsService     smsService;
+    private StorageService storageService;
+    private JwtTokenUtil   jwtTokenUtil;
+
+    public User loginByPhone(String phone) {
+        return userRepo.findByPhone(phone);
+    }
+
+    public User loginMp(String code) throws WxErrorException {
+        WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
+        WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(accessToken, null);
+        User user = userRepo.findByOpenId(wxMpUser.getOpenId());
+        if (user == null) {
+            user = User.builder()
+                    .username(UUID.randomUUID().toString())
+                    .nickname(wxMpUser.getNickname())
+                    .avatar(wxMpUser.getHeadImgUrl())
+                    .sex(wxMpUser.getSexDesc())
+                    .country(wxMpUser.getCountry())
+                    .province(wxMpUser.getProvince())
+                    .city(wxMpUser.getCity())
+                    .openId(wxMpUser.getOpenId())
+                    .language(wxMpUser.getLanguage())
+                    .enabled(true)
+                    .authorities(Collections.singleton(Authority.builder().name("ROLE_USER").build()))
+                    .build();
+            userRepo.save(user);
+        }
+        return user;
+    }
+
+    public User loginMa(String code) {
+        try {
+            WxMaJscode2SessionResult result = wxMaService.jsCode2SessionInfo(code);
+            String openId = result.getOpenid();
+            String sessionKey = result.getSessionKey();
+            User userInfo = userRepo.findByOpenId(openId);
+            if (userInfo != null) {
+                return userInfo;
+            }
+            userInfo = User.builder()
+                    .username(UUID.randomUUID().toString())
+                    .nickname("用户" + RandomStringUtils.randomAlphabetic(6))
+                    .openId(openId)
+                    .avatar(Constants.DEFAULT_AVATAR)
+                    .enabled(true)
+                    .authorities(Collections.singleton(Authority.builder().name("ROLE_USER").build()))
+                    .build();
+            userInfo = userRepo.save(userInfo);
+            return userInfo;
+        } catch (WxErrorException e) {
+            e.printStackTrace();
+        }
+        throw new BusinessException("登录失败");
+    }
+
+    public User getMaUserInfo(String sessionKey, String rawData, String signature,
+                              String encryptedData, String iv) {
+        // 用户信息校验
+        if (!wxMaService.getUserService().checkUserInfo(sessionKey, rawData, signature)) {
+            throw new BusinessException("获取用户信息失败");
+        }
+
+        // 解密用户信息
+        WxMaUserInfo wxUserInfo = wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, iv);
+        User user = userRepo.findByOpenId(wxUserInfo.getOpenId());
+
+        String avatarUrl = Constants.DEFAULT_AVATAR;
+        try {
+            String path = "image/avatar/" +
+                    new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) +
+                    RandomStringUtils.randomAlphabetic(8) +
+                    ".jpg";
+            avatarUrl = storageService.uploadFromUrl(wxUserInfo.getAvatarUrl(), path);
+        } catch (Exception e) {
+            log.error("获取头像失败", e);
+        }
+
+        if (user == null) {
+
+            user = User.builder()
+                    .username(UUID.randomUUID().toString())
+                    .nickname(wxUserInfo.getNickName())
+                    .openId(wxUserInfo.getOpenId())
+                    .avatar(avatarUrl)
+                    .sex(wxUserInfo.getGender())
+                    .country(wxUserInfo.getCountry())
+                    .province(wxUserInfo.getProvince())
+                    .city(wxUserInfo.getCity())
+                    .enabled(true)
+                    .authorities(Collections.singleton(Authority.builder().name("ROLE_USER").build()))
+                    .build();
+            user = userRepo.save(user);
+
+        } else {
+            user.setAvatar(avatarUrl);
+            user.setNickname(wxUserInfo.getNickName());
+            user.setSex(wxUserInfo.getGender());
+            user.setCountry(wxUserInfo.getCountry());
+            user.setProvince(wxUserInfo.getProvince());
+            user.setCity(wxUserInfo.getCity());
+            user = userRepo.save(user);
+        }
+
+        return user;
+    }
+
+    public String setPassword(Long userId, String password) {
+        User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在"));
+        user.setPassword(new BCryptPasswordEncoder().encode(password));
+        user = userRepo.save(user);
+        return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
+    }
+}

+ 94 - 0
src/main/java/com/izouma/jmrh/service/sms/AliSmsService.java

@@ -0,0 +1,94 @@
+package com.izouma.jmrh.service.sms;
+
+import com.aliyuncs.CommonRequest;
+import com.aliyuncs.CommonResponse;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.http.MethodType;
+import com.aliyuncs.profile.DefaultProfile;
+import com.izouma.jmrh.config.Constants;
+import com.izouma.jmrh.domain.SmsRecord;
+import com.izouma.jmrh.exception.BusinessException;
+import com.izouma.jmrh.repo.SmsRecordRepo;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.configurationprocessor.json.JSONException;
+import org.springframework.boot.configurationprocessor.json.JSONObject;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+@Service
+@Slf4j
+public class AliSmsService implements SmsService {
+    @Value("${aliyun.access-key-id}")
+    private String        accessKeyId;
+    @Value("${aliyun.access-key-secret}")
+    private String        accessKeySecret;
+    @Autowired
+    private SmsRecordRepo smsRecordRepo;
+
+    @Override
+    public String sendVerify(String phone) {
+        smsRecordRepo.findLastByPhoneAndExpiresAtAfterAndExpiredFalse(phone, LocalDateTime.now()).ifPresent(record -> {
+            if (record.getCreatedAt().plusMinutes(1L).isAfter(LocalDateTime.now())) {
+                long sec = record.getCreatedAt().plusMinutes(1L).toInstant(ZoneOffset.UTC).getEpochSecond() - LocalDateTime.now().toInstant(ZoneOffset.UTC).getEpochSecond() + 1;
+                throw new BusinessException("请" + sec + "秒后再试");
+            }
+        });
+
+        String code = RandomStringUtils.randomNumeric(4);
+        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
+        IAcsClient client = new DefaultAcsClient(profile);
+
+        CommonRequest request = new CommonRequest();
+        request.setMethod(MethodType.POST);
+        request.setDomain("dysmsapi.aliyuncs.com");
+        request.setVersion("2017-05-25");
+        request.setAction("SendSms");
+        request.putQueryParameter("PhoneNumbers", phone);
+        request.putQueryParameter("SignName", Constants.SMS_SIGN_NAME);
+        request.putQueryParameter("TemplateCode", Constants.SMS_TEMPLATE_CODE_GENERIC);
+        request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");
+        try {
+            CommonResponse response = client.getCommonResponse(request);
+            if (response.getHttpStatus() != 200) {
+                throw new BusinessException("发送失败,请稍后再试", response.getHttpStatus() + "," + response.getData());
+            }
+            log.info("send sms response {}", response.getData());
+            JSONObject jsonObject = new JSONObject(response.getData());
+            if (!"ok".equalsIgnoreCase(jsonObject.getString("Code"))) {
+                throw new BusinessException("发送失败,请稍后再试", jsonObject.getString("Message"));
+            }
+            smsRecordRepo.expire(phone);
+            String sessionId = RandomStringUtils.randomAlphabetic(10);
+            smsRecordRepo.save(SmsRecord.builder()
+                                        .sessionId(sessionId)
+                                        .phone(phone)
+                                        .code(code)
+                                        .expiresAt(LocalDateTime.now().plusMinutes(5))
+                                        .expired(false)
+                                        .build());
+            return sessionId;
+        } catch (ClientException | JSONException e) {
+            e.printStackTrace();
+            throw new BusinessException("发送失败,请稍后再试", e.getMessage());
+        }
+    }
+
+    @Override
+    public void verify(String phone, String code) throws SmsVerifyException {
+        SmsRecord smsRecord = smsRecordRepo.findLastByPhoneAndExpiresAtAfterAndExpiredFalse(phone, LocalDateTime.now()).orElseThrow(new SmsVerifyException("验证码错误"));
+        if (!smsRecord.getCode().equalsIgnoreCase(code)) {
+            throw new BusinessException("验证码错误");
+        }
+        smsRecord.setExpired(true);
+        smsRecordRepo.save(smsRecord);
+    }
+
+
+}

+ 20 - 0
src/main/java/com/izouma/jmrh/service/sms/SmsService.java

@@ -0,0 +1,20 @@
+package com.izouma.jmrh.service.sms;
+
+import java.util.function.Supplier;
+
+public interface SmsService {
+    String sendVerify(String phone);
+
+    void verify(String phone, String code) throws SmsVerifyException;
+
+    class SmsVerifyException extends Exception implements Supplier<SmsVerifyException> {
+        public SmsVerifyException(String msg) {
+            super(msg);
+        }
+
+        @Override
+        public SmsVerifyException get() {
+            return this;
+        }
+    }
+}

+ 69 - 0
src/main/java/com/izouma/jmrh/service/storage/AliStorageService.java

@@ -0,0 +1,69 @@
+package com.izouma.jmrh.service.storage;
+
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.model.ObjectMetadata;
+import com.izouma.jmrh.exception.BusinessException;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+@Data
+@Service
+@Slf4j
+@EnableConfigurationProperties
+@ConfigurationProperties(prefix = "aliyun")
+@ConditionalOnProperty(name = "storage.provider", havingValue = "aliyun")
+public class AliStorageService implements StorageService {
+    private String accessKeyId;
+    private String accessKeySecret;
+    private String ossBucketName;
+    private String ossEndPoint;
+    private String ossDomain;
+
+    @Override
+    public String uploadFromInputStream(InputStream inputStream, String path) {
+        log.info("阿里云OSS上传: inputStream -> {}", path);
+        try {
+            String result = upload(inputStream, path);
+            log.info("上传成功 {}", result);
+            return result;
+        } catch (Exception e) {
+            log.error("阿里云OSS上传失败", e);
+            throw new BusinessException("上传失败", e.getMessage());
+        }
+    }
+
+    @Override
+    public String uploadFromUrl(String url, String path) {
+        log.info("阿里云OSS上传: {} -> {}", url, path);
+        try {
+            InputStream inputStream = new URL(url).openStream();
+            String result = upload(inputStream, path);
+            log.info("上传成功 {}", result);
+            return result;
+        } catch (Exception e) {
+            log.error("阿里云OSS上传失败", e);
+            throw new BusinessException("上传失败", e.getMessage());
+        }
+    }
+
+    private String upload(InputStream inputStream, String path) {
+        OSSClient client = new OSSClient(ossEndPoint, accessKeyId, accessKeySecret);
+        client.putObject(ossBucketName, path, inputStream, new ObjectMetadata());
+        client.shutdown();
+        try {
+            inputStream.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return ossDomain + "/" + path;
+    }
+
+}

+ 63 - 0
src/main/java/com/izouma/jmrh/service/storage/LocalStorageService.java

@@ -0,0 +1,63 @@
+package com.izouma.jmrh.service.storage;
+
+import com.izouma.jmrh.exception.BusinessException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+@Service
+@Slf4j
+@ConditionalOnProperty(name = "storage.provider", havingValue = "local")
+public class LocalStorageService implements StorageService {
+
+    @Value("${storage.local_path}")
+    private String localPath;
+
+    @Override
+    public String uploadFromInputStream(InputStream fin, String path) {
+        log.info("本地上传: inputStream -> {}", path);
+        try {
+            return upload(fin, path);
+        } catch (IOException e) {
+            log.error("本地上传失败", e);
+            throw new BusinessException("上传失败", e.getMessage());
+        }
+    }
+
+    @Override
+    public String uploadFromUrl(String url, String path) {
+        log.info("本地上传: {} -> {}", url, path);
+        try {
+            InputStream inputStream = new URL(url).openStream();
+            return upload(inputStream, path);
+        } catch (Exception e) {
+            log.error("本地上传失败", e);
+            throw new BusinessException("上传失败", e.getMessage());
+        }
+    }
+
+    private String upload(InputStream inputStream, String path) throws IOException {
+        Path uploadPath = Paths.get(localPath, path).getParent();
+        if (Files.notExists(uploadPath, LinkOption.values())) {
+            Files.createDirectories(uploadPath);
+        }
+        FileUtils.copyToFile(inputStream, new File(Paths.get(localPath, path).toString()));
+        try {
+            inputStream.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return "files" + "/" + path;
+    }
+}

+ 9 - 0
src/main/java/com/izouma/jmrh/service/storage/StorageService.java

@@ -0,0 +1,9 @@
+package com.izouma.jmrh.service.storage;
+
+import java.io.InputStream;
+
+public interface StorageService {
+    String uploadFromInputStream(InputStream fin, String path);
+
+    String uploadFromUrl(String url, String path);
+}

+ 45 - 0
src/main/java/com/izouma/jmrh/utils/DateTimeUtils.java

@@ -0,0 +1,45 @@
+package com.izouma.jmrh.utils;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAccessor;
+
+public class DateTimeUtils {
+    public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
+    public static final String T_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
+    public static final String DATE_FORMAT      = "yyyy-MM-dd";
+
+    public static LocalDate toLocalDate(long ts) {
+        if (String.valueOf(ts).length() > 10) {
+            return LocalDate.from(Instant.ofEpochMilli(ts));
+        } else {
+            return LocalDate.from(Instant.ofEpochSecond(ts));
+        }
+    }
+
+    public static LocalDateTime toLocalDateTime(long ts) {
+        if (String.valueOf(ts).length() > 10) {
+            return LocalDateTime.from(Instant.ofEpochMilli(ts));
+        } else {
+            return LocalDateTime.from(Instant.ofEpochSecond(ts));
+        }
+    }
+
+    public static LocalDate toLocalDate(String str, String fmt) {
+        return LocalDate.from(DateTimeFormatter.ofPattern(fmt).parse(str));
+    }
+
+    public static LocalDateTime toLocalDateTime(String str, String fmt) {
+        return LocalDateTime.from(DateTimeFormatter.ofPattern(fmt).parse(str));
+    }
+
+    public static String format(TemporalAccessor temporal, String fmt) {
+        return DateTimeFormatter.ofPattern(fmt).format(temporal);
+    }
+
+    public static long toTimestamp(TemporalAccessor temporal) {
+        return Instant.from(temporal).toEpochMilli();
+    }
+}

+ 204 - 0
src/main/java/com/izouma/jmrh/utils/FileUtils.java

@@ -0,0 +1,204 @@
+package com.izouma.jmrh.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Set;
+
+public class FileUtils {
+
+    public static String getExtension(String fileName) {
+        if (StringUtils.INDEX_NOT_FOUND == StringUtils.indexOf(fileName, "."))
+            return StringUtils.EMPTY;
+        String ext = StringUtils.substring(fileName, StringUtils.lastIndexOf(fileName, "."));
+        return StringUtils.trimToEmpty(ext);
+    }
+
+    public static String getFileName(String header) {
+        String[] tempArr1 = header.split(";");
+        String[] tempArr2 = tempArr1[2].split("=");
+        // 获取文件名,兼容各种浏览器的写法
+        return tempArr2[1].substring(tempArr2[1].lastIndexOf("\\") + 1).replaceAll("\"", "");
+
+    }
+
+    /**
+     * 获取文件权限
+     *
+     * <pre>
+     * 当前仅支持posix系统,需要修改为windows格式
+     * </pre>
+     *
+     * @param path
+     * @return
+     * @throws IOException
+     */
+    public static String getPermissions(Path path) throws IOException {
+        PosixFileAttributeView fileAttributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class);
+        PosixFileAttributes readAttributes = fileAttributeView.readAttributes();
+        Set<PosixFilePermission> permissions = readAttributes.permissions();
+        return PosixFilePermissions.toString(permissions);
+    }
+
+    /**
+     * 修改文件权限
+     *
+     * @param file
+     * @param permsCode
+     * @param recursive
+     * @return
+     * @throws IOException
+     */
+    public static String setPermissions(File file, String permsCode, boolean recursive) throws IOException {
+        PosixFileAttributeView fileAttributeView = Files.getFileAttributeView(file.toPath(),
+                PosixFileAttributeView.class);
+        fileAttributeView.setPermissions(PosixFilePermissions.fromString(permsCode));
+        if (file.isDirectory() && recursive && file.listFiles() != null) {
+            for (File f : file.listFiles()) {
+                setPermissions(f, permsCode, true);
+            }
+        }
+        return permsCode;
+    }
+
+    public static boolean write(InputStream inputStream, File f) {
+        boolean ret = false;
+
+        try (OutputStream outputStream = new FileOutputStream(f)) {
+
+            int read;
+            byte[] bytes = new byte[1024];
+
+            while ((read = inputStream.read(bytes)) != -1) {
+                outputStream.write(bytes, 0, read);
+            }
+            ret = true;
+
+        } catch (IOException e) {
+            e.printStackTrace();
+
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return ret;
+    }
+
+    public static void mkFolder(String fileName) {
+        File f = new File(fileName);
+        if (!f.exists()) {
+            f.mkdir();
+        }
+    }
+
+    public static File mkFile(String fileName) {
+        File f = new File(fileName);
+        try {
+            f.createNewFile();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return f;
+    }
+
+    public static void fileProber(File dirFile) {
+
+        File parentFile = dirFile.getParentFile();
+        if (!parentFile.exists()) {
+
+            // 递归寻找上级目录
+            fileProber(parentFile);
+
+            parentFile.mkdir();
+        }
+
+    }
+
+    /**
+     * 将文本文件中的内容读入到buffer中
+     *
+     * @param buffer   buffer
+     * @param filePath 文件路径
+     * @throws IOException 异常
+     * @date 2013-1-7
+     */
+    public static void readToBuffer(StringBuffer buffer, String filePath) throws IOException {
+        InputStream is = new FileInputStream(filePath);
+        String line; // 用来保存每行读取的内容
+        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+        line = reader.readLine(); // 读取第一行
+        while (line != null) { // 如果 line 为空说明读完了
+            buffer.append(line); // 将读到的内容添加到 buffer 中
+            buffer.append("\n"); // 添加换行符
+            line = reader.readLine(); // 读取下一行
+        }
+        reader.close();
+        is.close();
+    }
+
+    /**
+     * 读取文本文件内容
+     *
+     * @param filePath 文件所在路径
+     * @return 文本内容
+     * @throws IOException 异常
+     * @date 2013-1-7
+     */
+    public static String readFile(String filePath) throws IOException {
+        StringBuffer sb = new StringBuffer();
+        FileUtils.readToBuffer(sb, filePath);
+        return sb.toString();
+    }
+
+
+    /**
+     * 功能:Java读取txt文件的内容
+     * 步骤:1:先获得文件句柄
+     * 2:获得文件句柄当做是输入一个字节码流,需要对这个输入流进行读取
+     * 3:读取到输入流后,需要读取生成字节流
+     * 4:一行一行的输出。readline()。
+     * 备注:需要考虑的是异常情况
+     */
+    public static String readFileToString(File file) {
+        try {
+            if (file.isFile() && file.exists()) { //判断文件是否存在
+                Long filelength = file.length(); // 获取文件长度
+                byte[] filecontent = new byte[filelength.intValue()];
+                try {
+                    FileInputStream in = new FileInputStream(file);
+                    in.read(filecontent);
+                    in.close();
+                } catch (FileNotFoundException e) {
+                    e.printStackTrace();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+
+                String fileContentArr = new String(filecontent);
+
+                return fileContentArr;// 返回文件内容,默认编码
+            } else {
+                System.out.println("找不到指定的文件");
+            }
+        } catch (Exception e) {
+            System.out.println("读取文件内容出错");
+            e.printStackTrace();
+        }
+
+        return null;
+
+    }
+
+
+}

+ 37 - 0
src/main/java/com/izouma/jmrh/utils/JsonUtils.java

@@ -0,0 +1,37 @@
+package com.izouma.jmrh.utils;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class JsonUtils {
+    public static class Builder {
+        private Map<String, Object> map = new HashMap<>();
+
+        public String build() {
+            return new JSONObject(map).toJSONString();
+        }
+
+        public Builder add(String key, Object value) {
+            map.put(key, value);
+            return this;
+        }
+    }
+
+    public static class ArrayBuilder {
+        private List<Object> list = new ArrayList<>();
+
+        public String build() {
+            return new JSONArray(list).toString();
+        }
+
+        public ArrayBuilder add(Object object) {
+            list.add(object);
+            return this;
+        }
+    }
+}

+ 16 - 0
src/main/java/com/izouma/jmrh/utils/NullAwareBeanUtilsBean.java

@@ -0,0 +1,16 @@
+package com.izouma.jmrh.utils;
+
+import org.apache.commons.beanutils.BeanUtilsBean;
+
+import java.lang.reflect.InvocationTargetException;
+
+public class NullAwareBeanUtilsBean extends BeanUtilsBean {
+
+    @Override
+    public void copyProperty(Object dest, String name, Object value)
+            throws IllegalAccessException, InvocationTargetException {
+        if (value == null) return;
+        super.copyProperty(dest, name, value);
+    }
+
+}

+ 35 - 0
src/main/java/com/izouma/jmrh/utils/ObjUtils.java

@@ -0,0 +1,35 @@
+package com.izouma.jmrh.utils;
+
+
+import org.apache.commons.beanutils.BeanUtilsBean;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
+
+public class ObjUtils {
+    public static void merge(Object dst, Object src) {
+        Objects.requireNonNull(src);
+        Objects.requireNonNull(dst);
+        if (!dst.getClass().equals(src.getClass())) {
+            throw new RuntimeException("cannot merge different class");
+        }
+        BeanUtilsBean notNull = new NullAwareBeanUtilsBean();
+        try {
+            notNull.copyProperties(dst, src);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static String[] getFields(Class clazz) {
+        Field[] fields = clazz.getDeclaredFields();
+        String[] fieldNames = new String[fields.length];
+        for (int i = 0; i < fields.length; i++) {
+            fieldNames[i] = fields[i].getName();
+        }
+        return fieldNames;
+    }
+
+
+}

+ 105 - 0
src/main/java/com/izouma/jmrh/utils/PinYinUtil.java

@@ -0,0 +1,105 @@
+package com.izouma.jmrh.utils;
+
+import net.sourceforge.pinyin4j.PinyinHelper;
+import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
+import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
+import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
+import org.springframework.stereotype.Component;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * pinyin4j汉字转拼音工具类
+ *
+ * @author
+ */
+@Component
+public class PinYinUtil {
+
+
+    /**
+     * 对单个字进行转换
+     *
+     * @param pinYinStr 需转换的汉字字符串
+     * @return 拼音字符串数组
+     */
+    public static String getCharPinYin(char pinYinStr) {
+
+        if (!isContainChinese(String.valueOf(pinYinStr))) {
+            return String.valueOf(pinYinStr);
+        }
+        //pinyin4j格式类
+        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
+        /*
+         * 设置需要转换的拼音格式
+         * 以天为例
+         * HanyuPinyinToneType.WITHOUT_TONE 转换为tian
+         * HanyuPinyinToneType.WITH_TONE_MARK 转换为tian1
+         * HanyuPinyinVCharType.WITH_U_UNICODE 转换为tiān
+         *
+         */
+
+        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
+        //拼音字符串数组
+        String[] pinyin = null;
+
+        try {
+            //执行转换
+            pinyin = PinyinHelper.toHanyuPinyinStringArray(pinYinStr, format);
+
+        } catch (BadHanyuPinyinOutputFormatCombination e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+        //pinyin4j规则,当转换的符串不是汉字,就返回null
+        if (pinyin == null) {
+            return null;
+        }
+
+        //多音字会返回一个多音字拼音的数组,pinyiin4j并不能有效判断该字的读音
+        return pinyin[0];
+    }
+
+    /**
+     * 对单个字进行转换
+     *
+     * @param pinYinStr
+     * @return
+     */
+    public static String getStringPinYin(String pinYinStr) {
+        StringBuffer sb = new StringBuffer();
+        String tempStr = null;
+        //循环字符串
+        for (int i = 0; i < pinYinStr.length(); i++) {
+
+            tempStr = getCharPinYin(pinYinStr.charAt(i));
+            if (tempStr == null) {
+                //非汉字直接拼接
+                sb.append(pinYinStr.charAt(i));
+            } else {
+                sb.append(tempStr);
+            }
+        }
+
+        return sb.toString();
+
+    }
+
+    /**
+     * 判断字符串中是否包含中文
+     *
+     * @param str 待校验字符串
+     * @return 是否为中文
+     * @warn 不能校验是否为中文标点符号
+     */
+    public static boolean isContainChinese(String str) {
+        Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
+        Matcher m = p.matcher(str);
+        if (m.find()) {
+            return true;
+        }
+        return false;
+    }
+}

+ 17 - 0
src/main/java/com/izouma/jmrh/utils/SecurityUtils.java

@@ -0,0 +1,17 @@
+package com.izouma.jmrh.utils;
+
+import com.izouma.jmrh.domain.User;
+import com.izouma.jmrh.security.JwtUser;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+public class SecurityUtils {
+    public static User getAuthenticatedUser() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        User user = null;
+        if (authentication != null && authentication.getPrincipal() instanceof JwtUser) {
+            user = ((JwtUser) authentication.getPrincipal()).getUser();
+        }
+        return user;
+    }
+}

+ 171 - 0
src/main/java/com/izouma/jmrh/utils/SnowflakeIdWorker.java

@@ -0,0 +1,171 @@
+package com.izouma.jmrh.utils;
+
+
+/**
+ * Created by dujinkai on 17/11/02.
+ * <p>
+ * Twitter_Snowflake<br>
+ * SnowFlake的结构如下(每部分用-分开):<br>
+ * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
+ * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
+ * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
+ * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
+ * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
+ * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
+ * 加起来刚好64位,为一个Long型。<br>
+ * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
+ */
+public class SnowflakeIdWorker {
+    /**
+     * 开始时间截 (2015-01-01)
+     */
+    private final long twepoch = 1420041600000L;
+
+    /**
+     * 机器id所占的位数
+     */
+    private final long workerIdBits = 5L;
+
+    /**
+     * 数据标识id所占的位数
+     */
+    private final long datacenterIdBits = 5L;
+
+    /**
+     * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
+     */
+    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
+
+    /**
+     * 支持的最大数据标识id,结果是31
+     */
+    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
+
+    /**
+     * 序列在id中占的位数
+     */
+    private final long sequenceBits = 12L;
+
+    /**
+     * 机器ID向左移12位
+     */
+    private final long workerIdShift = sequenceBits;
+
+    /**
+     * 数据标识id向左移17位(12+5)
+     */
+    private final long datacenterIdShift = sequenceBits + workerIdBits;
+
+    /**
+     * 时间截向左移22位(5+5+12)
+     */
+    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
+
+    /**
+     * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
+     */
+    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
+
+    /**
+     * 工作机器ID(0~31)
+     */
+    private long workerId;
+
+    /**
+     * 数据中心ID(0~31)
+     */
+    private long datacenterId;
+
+    /**
+     * 毫秒内序列(0~4095)
+     */
+    private long sequence = 0L;
+
+    /**
+     * 上次生成ID的时间截
+     */
+    private long lastTimestamp = -1L;
+
+    //==============================Constructors=====================================
+
+    /**
+     * 构造函数
+     *
+     * @param workerId     工作ID (0~31)
+     * @param datacenterId 数据中心ID (0~31)
+     */
+    public SnowflakeIdWorker(long workerId, long datacenterId) {
+        if (workerId > maxWorkerId || workerId < 0) {
+            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
+        }
+        if (datacenterId > maxDatacenterId || datacenterId < 0) {
+            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
+        }
+        this.workerId = workerId;
+        this.datacenterId = datacenterId;
+    }
+
+    // ==============================Methods==========================================
+
+    /**
+     * 获得下一个ID (该方法是线程安全的)
+     *
+     * @return SnowflakeId
+     */
+    public synchronized long nextId() {
+        long timestamp = timeGen();
+
+        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
+        if (timestamp < lastTimestamp) {
+            throw new RuntimeException(
+                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
+        }
+
+        //如果是同一时间生成的,则进行毫秒内序列
+        if (lastTimestamp == timestamp) {
+            sequence = (sequence + 1) & sequenceMask;
+            //毫秒内序列溢出
+            if (sequence == 0) {
+                //阻塞到下一个毫秒,获得新的时间戳
+                timestamp = tilNextMillis(lastTimestamp);
+            }
+        }
+        //时间戳改变,毫秒内序列重置
+        else {
+            sequence = 0L;
+        }
+
+        //上次生成ID的时间截
+        lastTimestamp = timestamp;
+
+        //移位并通过或运算拼到一起组成64位的ID
+        return ((timestamp - twepoch) << timestampLeftShift) //
+                | (datacenterId << datacenterIdShift) //
+                | (workerId << workerIdShift) //
+                | sequence;
+    }
+
+    /**
+     * 阻塞到下一个毫秒,直到获得新的时间戳
+     *
+     * @param lastTimestamp 上次生成ID的时间截
+     * @return 当前时间戳
+     */
+    protected long tilNextMillis(long lastTimestamp) {
+        long timestamp = timeGen();
+        while (timestamp <= lastTimestamp) {
+            timestamp = timeGen();
+        }
+        return timestamp;
+    }
+
+    /**
+     * 返回以毫秒为单位的当前时间
+     *
+     * @return 当前时间(毫秒)
+     */
+    protected long timeGen() {
+        return System.currentTimeMillis();
+    }
+
+}

+ 36 - 0
src/main/java/com/izouma/jmrh/utils/ThreadTask.java

@@ -0,0 +1,36 @@
+package com.izouma.jmrh.utils;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * com.izouma.zhumj.utils
+ * 异步线程池
+ * @author Pine
+ * @email 771190883@qq.com
+ * @date 2019/10/12
+ */
+public class ThreadTask {
+    private static final ThreadTask threadTask = new ThreadTask();
+
+    private ThreadTask() {
+    }
+
+    public static ThreadTask getInstance() {
+        return threadTask;
+    }
+
+    /**
+     * 固定线程池
+     */
+    private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+
+    /**
+     * 加入线程池执行
+     *
+     * @param runnable 执行线程
+     */
+    public void addTask(Runnable runnable) {
+        executorService.execute(runnable);
+    }
+}

+ 23 - 0
src/main/java/com/izouma/jmrh/utils/excel/ExcelUtils.java

@@ -0,0 +1,23 @@
+package com.izouma.jmrh.utils.excel;
+
+import com.alibaba.excel.EasyExcel;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+public class ExcelUtils<T> {
+    public static <T> void export(HttpServletResponse response, List<T> data) throws IOException {
+        String fileName = "data.xlsx";
+        if (data != null && !data.isEmpty()) {
+            fileName = data.get(0).getClass().getSimpleName() + ".xlsx";
+        }
+        response.setContentType("application/vnd.ms-excel");
+        response.setCharacterEncoding("utf-8");
+        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
+        EasyExcel.write(response.getOutputStream(), data.get(0).getClass()).sheet("sheet")
+                 .registerConverter(new LocalDateConverter())
+                 .registerConverter(new LocalDateTimeConverter())
+                 .doWrite(data);
+    }
+}

+ 32 - 0
src/main/java/com/izouma/jmrh/utils/excel/LocalDateConverter.java

@@ -0,0 +1,32 @@
+package com.izouma.jmrh.utils.excel;
+
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.CellData;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+public class LocalDateConverter implements Converter<LocalDate> {
+    @Override
+    public Class supportJavaTypeKey() {
+        return LocalDate.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public LocalDate convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
+        return LocalDate.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+    }
+
+    @Override
+    public CellData convertToExcelData(LocalDate localDate, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
+        return new CellData(DateTimeFormatter.ofPattern("yyyy-MM-dd").format(localDate));
+    }
+}

+ 32 - 0
src/main/java/com/izouma/jmrh/utils/excel/LocalDateTimeConverter.java

@@ -0,0 +1,32 @@
+package com.izouma.jmrh.utils.excel;
+
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.CellData;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class LocalDateTimeConverter implements Converter<LocalDateTime> {
+    @Override
+    public Class supportJavaTypeKey() {
+        return LocalDateTime.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public LocalDateTime convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
+        return LocalDateTime.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+    }
+
+    @Override
+    public CellData convertToExcelData(LocalDateTime localDateTime, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
+        return new CellData(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(localDateTime));
+    }
+}

+ 51 - 0
src/main/java/com/izouma/jmrh/web/AppErrorController.java

@@ -0,0 +1,51 @@
+package com.izouma.jmrh.web;
+
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletRequest;
+
+@Controller
+public class AppErrorController implements ErrorController {
+    @Override
+    public String getErrorPath() {
+        return "/error";
+    }
+
+    @RequestMapping("/error")
+    public String handleError(HttpServletRequest request, Model model) {
+        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
+
+        if (status != null) {
+            int statusCode = Integer.parseInt(status.toString());
+
+            if (statusCode == HttpStatus.NOT_FOUND.value()) {
+                return "commons/404";
+            } else if (statusCode == HttpStatus.UNAUTHORIZED.value()) {
+                return "commons/401";
+            } else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
+                return "commons/500";
+            }
+        }
+        return "commons/error";
+    }
+
+    @RequestMapping("/401")
+    public String error401() {
+        return "commons/401";
+    }
+
+    @RequestMapping("/404")
+    public String error404() {
+        return "commons/404";
+    }
+
+    @RequestMapping("/500")
+    public String error500() {
+        return "commons/500";
+    }
+}

+ 60 - 0
src/main/java/com/izouma/jmrh/web/ApplicationFieldController.java

@@ -0,0 +1,60 @@
+package com.izouma.jmrh.web;
+import com.izouma.jmrh.domain.ApplicationField;
+import com.izouma.jmrh.service.ApplicationFieldService;
+import com.izouma.jmrh.dto.PageQuery;
+import com.izouma.jmrh.exception.BusinessException;
+import com.izouma.jmrh.repo.ApplicationFieldRepo;
+import com.izouma.jmrh.utils.ObjUtils;
+import com.izouma.jmrh.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/applicationField")
+@AllArgsConstructor
+public class ApplicationFieldController extends BaseController {
+    private ApplicationFieldService applicationFieldService;
+    private ApplicationFieldRepo applicationFieldRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public ApplicationField save(@RequestBody ApplicationField record) {
+        if (record.getId() != null) {
+            ApplicationField orig = applicationFieldRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return applicationFieldRepo.save(orig);
+        }
+        return applicationFieldRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @GetMapping("/all")
+    public Page<ApplicationField> all(PageQuery pageQuery) {
+        return applicationFieldRepo.findAll(toSpecification(pageQuery,ApplicationField.class), toPageRequest(pageQuery));
+    }
+
+    @GetMapping("/get/{id}")
+    public ApplicationField get(@PathVariable Long id) {
+        return applicationFieldRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        applicationFieldRepo.deleteById(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<ApplicationField> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 60 - 0
src/main/java/com/izouma/jmrh/web/ArticleController.java

@@ -0,0 +1,60 @@
+package com.izouma.jmrh.web;
+import com.izouma.jmrh.domain.Article;
+import com.izouma.jmrh.service.ArticleService;
+import com.izouma.jmrh.dto.PageQuery;
+import com.izouma.jmrh.exception.BusinessException;
+import com.izouma.jmrh.repo.ArticleRepo;
+import com.izouma.jmrh.utils.ObjUtils;
+import com.izouma.jmrh.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/article")
+@AllArgsConstructor
+public class ArticleController extends BaseController {
+    private ArticleService articleService;
+    private ArticleRepo articleRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Article save(@RequestBody Article record) {
+        if (record.getId() != null) {
+            Article orig = articleRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return articleRepo.save(orig);
+        }
+        return articleRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @GetMapping("/all")
+    public Page<Article> all(PageQuery pageQuery) {
+        return articleRepo.findAll(toSpecification(pageQuery,Article.class), toPageRequest(pageQuery));
+    }
+
+    @GetMapping("/get/{id}")
+    public Article get(@PathVariable Long id) {
+        return articleRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        articleRepo.deleteById(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<Article> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 60 - 0
src/main/java/com/izouma/jmrh/web/ArticleTypeController.java

@@ -0,0 +1,60 @@
+package com.izouma.jmrh.web;
+import com.izouma.jmrh.domain.ArticleType;
+import com.izouma.jmrh.service.ArticleTypeService;
+import com.izouma.jmrh.dto.PageQuery;
+import com.izouma.jmrh.exception.BusinessException;
+import com.izouma.jmrh.repo.ArticleTypeRepo;
+import com.izouma.jmrh.utils.ObjUtils;
+import com.izouma.jmrh.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/articleType")
+@AllArgsConstructor
+public class ArticleTypeController extends BaseController {
+    private ArticleTypeService articleTypeService;
+    private ArticleTypeRepo articleTypeRepo;
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public ArticleType save(@RequestBody ArticleType record) {
+        if (record.getId() != null) {
+            ArticleType orig = articleTypeRepo.findById(record.getId()).orElseThrow(new BusinessException("无记录"));
+            ObjUtils.merge(orig, record);
+            return articleTypeRepo.save(orig);
+        }
+        return articleTypeRepo.save(record);
+    }
+
+
+    //@PreAuthorize("hasRole('ADMIN')")
+    @GetMapping("/all")
+    public Page<ArticleType> all(PageQuery pageQuery) {
+        return articleTypeRepo.findAll(toSpecification(pageQuery,ArticleType.class), toPageRequest(pageQuery));
+    }
+
+    @GetMapping("/get/{id}")
+    public ArticleType get(@PathVariable Long id) {
+        return articleTypeRepo.findById(id).orElseThrow(new BusinessException("无记录"));
+    }
+
+    @PostMapping("/del/{id}")
+    public void del(@PathVariable Long id) {
+        articleTypeRepo.deleteById(id);
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<ArticleType> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}
+

+ 95 - 0
src/main/java/com/izouma/jmrh/web/AuthenticationController.java

@@ -0,0 +1,95 @@
+package com.izouma.jmrh.web;
+
+import com.izouma.jmrh.domain.User;
+import com.izouma.jmrh.exception.AuthenticationException;
+import com.izouma.jmrh.security.JwtTokenUtil;
+import com.izouma.jmrh.security.JwtUserDetailsService;
+import com.izouma.jmrh.security.JwtUserFactory;
+import com.izouma.jmrh.service.UserService;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Objects;
+
+@Slf4j
+@AllArgsConstructor
+@RestController
+@RequestMapping("/auth")
+public class AuthenticationController {
+    private AuthenticationManager authenticationManager;
+    private JwtTokenUtil          jwtTokenUtil;
+    private JwtUserDetailsService userDetailsService;
+    private UserService           userService;
+
+    @PostMapping("/login")
+    public String loginByUserPwd(String username, String password, Integer expiration) {
+        try {
+            authenticate(username, password);
+            final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+            return jwtTokenUtil.generateToken(userDetails);
+        } catch (Exception e) {
+            log.error("loginByUserPwd", e);
+            throw new AuthenticationException("用户名或密码错误", e);
+        }
+    }
+
+    @PostMapping("/phoneLogin")
+    @ApiOperation(value = "手机号登录")
+    public String phoneLogin(String phone) {
+        try {
+            User user = userService.loginByPhone(phone);
+            return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
+        } catch (Exception e) {
+            log.error("loginByPhone", e);
+            throw new AuthenticationException("登陆错误", e);
+        }
+    }
+
+    @PostMapping("/mpLogin")
+    @ApiOperation(value = "公众号登录")
+    public String mpLogin(String code) {
+        try {
+            User user = userService.loginMp(code);
+            return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
+        } catch (Exception e) {
+            log.error("loginByCode", e);
+            throw new AuthenticationException("登陆错误", e);
+        }
+    }
+
+    @PostMapping("/maLogin")
+    @ApiOperation(value = "小程序登录")
+    public String maLogin(String code) {
+        try {
+            User user = userService.loginMa(code);
+            return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
+        } catch (Exception e) {
+            log.error("loginByCode", e);
+            throw new AuthenticationException("登陆错误", e);
+        }
+    }
+
+    /**
+     * Authenticates the user. If something is wrong, an {@link AuthenticationException} will be thrown
+     */
+    private void authenticate(String username, String password) {
+        Objects.requireNonNull(username);
+        Objects.requireNonNull(password);
+        try {
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
+        } catch (DisabledException e) {
+            throw new AuthenticationException("User is disabled!", e);
+        } catch (BadCredentialsException e) {
+            throw new AuthenticationException("Bad credentials!", e);
+        }
+    }
+}

+ 28 - 0
src/main/java/com/izouma/jmrh/web/AuthorityController.java

@@ -0,0 +1,28 @@
+package com.izouma.jmrh.web;
+
+import com.izouma.jmrh.repo.AuthorityRepo;
+import com.izouma.jmrh.security.Authority;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/authority")
+@AllArgsConstructor
+public class AuthorityController extends BaseController {
+    private AuthorityRepo authorityRepo;
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @GetMapping("/all")
+    public List<Authority> all() {
+        return authorityRepo.findAll();
+    }
+
+    @PreAuthorize("hasRole('ADMIN')")
+    @PostMapping("/save")
+    public Authority save(Authority authority) {
+        return authorityRepo.save(authority);
+    }
+}

+ 169 - 0
src/main/java/com/izouma/jmrh/web/BaseController.java

@@ -0,0 +1,169 @@
+package com.izouma.jmrh.web;
+
+import com.izouma.jmrh.annotations.Searchable;
+import com.izouma.jmrh.dto.PageQuery;
+import com.izouma.jmrh.utils.DateTimeUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+
+import javax.persistence.criteria.Predicate;
+import java.lang.reflect.Field;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Pattern;
+
+@SuppressWarnings("ALL")
+@Slf4j
+public class BaseController {
+    public PageRequest toPageRequest(PageQuery pageQuery) {
+        PageRequest pageRequest;
+        if (StringUtils.isNotEmpty(pageQuery.getSort())) {
+            List<Sort.Order> orders = new ArrayList<>();
+            for (String sortStr : pageQuery.getSort().split(";")) {
+                String direction = "asc";
+                String prop = sortStr;
+                if (sortStr.contains(",asc") || sortStr.contains(",desc")) {
+                    prop = sortStr.split(",")[0];
+                    direction = sortStr.split(",")[1];
+                }
+                orders.add("asc".equals(direction) ? Sort.Order.asc(prop) : Sort.Order.desc(prop));
+            }
+            pageRequest = PageRequest.of(pageQuery.getPage(), pageQuery.getSize(), Sort.by(orders));
+        } else {
+            pageRequest = PageRequest.of(pageQuery.getPage(), pageQuery.getSize());
+        }
+        return pageRequest;
+    }
+
+    public <T> Specification<T> toSpecification(PageQuery pageQuery, Class<?> queryClass) {
+        return (Specification<T>) (root, criteriaQuery, criteriaBuilder) -> {
+            List<Predicate> and = new ArrayList<>();
+            pageQuery.getQuery().forEach((property, value) -> {
+                if (value == null) {
+                    return;
+                }
+                if (String.class.equals(value.getClass())) {
+                    if (StringUtils.isEmpty((String) value)) {
+                        return;
+                    }
+                }
+                Field field = getDeclaredField(queryClass, property);
+                if (field == null) return;
+
+                Class fieldType = field.getType();
+                if (Enum.class.isAssignableFrom(fieldType)) {
+                    if (value instanceof Collection) {
+                        if (!((Collection) value).isEmpty()) {
+                            List list = new ArrayList();
+                            for (Object o : ((Collection) value)) {
+                                list.add(Enum.valueOf(fieldType, String.valueOf(o)));
+                            }
+                            and.add(root.get(property).in(list));
+                        }
+                    } else if (value instanceof String && StringUtils.isNotEmpty((String) value)) {
+                        if (((String) value).contains(",")) {
+                            String[] arr = ((String) value).split(",");
+                            List list = new ArrayList();
+                            for (String s : arr) {
+                                list.add(Enum.valueOf(fieldType, s));
+                            }
+                            and.add(root.get(property).in(list));
+                        } else {
+                            and.add(criteriaBuilder.and(criteriaBuilder
+                                    .equal(root.get(property), Enum.valueOf(fieldType, String.valueOf(value)))));
+                        }
+                    }
+                } else if (LocalDateTime.class == fieldType) {
+                    if (value instanceof List) {
+                        List list = (List) value;
+                        if (list.size() == 1) {
+                            LocalDateTime start = DateTimeUtils
+                                    .toLocalDateTime((String) list.get(0), "yyyy-MM-dd HH:mm:ss");
+                            and.add(criteriaBuilder.greaterThanOrEqualTo(root.get(property), start));
+                        } else if (list.size() == 2) {
+                            LocalDateTime end = DateTimeUtils
+                                    .toLocalDateTime((String) list.get(1), "yyyy-MM-dd HH:mm:ss");
+                            and.add(criteriaBuilder.lessThanOrEqualTo(root.get(property), end));
+                        }
+                    } else if (value instanceof String && Pattern
+                            .matches("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$", (String) value)) {
+                        String[] arr = ((String) value).split(",");
+                        LocalDateTime start = DateTimeUtils.toLocalDateTime(arr[0], "yyyy-MM-dd HH:mm:ss");
+                        and.add(criteriaBuilder.greaterThanOrEqualTo(root.get(property), start));
+                        LocalDateTime end = DateTimeUtils.toLocalDateTime(arr[1], "yyyy-MM-dd HH:mm:ss");
+                        and.add(criteriaBuilder.lessThanOrEqualTo(root.get(property), end));
+                    } else {
+                        and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get(property), DateTimeUtils
+                                .toLocalDateTime((String) value, "yyyy-MM-dd HH:mm:ss"))));
+                    }
+                } else if (LocalDate.class == fieldType) {
+                    if (value instanceof List) {
+                        List list = (List) value;
+                        if (list.size() == 1) {
+                            LocalDate start = DateTimeUtils
+                                    .toLocalDate((String) list.get(0), "yyyy-MM-dd");
+                            and.add(criteriaBuilder.greaterThanOrEqualTo(root.get(property), start));
+                        } else if (list.size() == 2) {
+                            LocalDate end = DateTimeUtils
+                                    .toLocalDate((String) list.get(1), "yyyy-MM-dd");
+                            and.add(criteriaBuilder.lessThanOrEqualTo(root.get(property), end));
+                        }
+                    } else if (value instanceof String && Pattern
+                            .matches("^\\d{4}-\\d{2}-\\d{2},\\d{4}-\\d{2}-\\d{2}$", (String) value)) {
+                        String[] arr = ((String) value).split(",");
+                        LocalDate start = DateTimeUtils.toLocalDate(arr[0], "yyyy-MM-dd");
+                        and.add(criteriaBuilder.greaterThanOrEqualTo(root.get(property), start));
+                        LocalDate end = DateTimeUtils.toLocalDate(arr[1], "yyyy-MM-dd");
+                        and.add(criteriaBuilder.lessThanOrEqualTo(root.get(property), end));
+                    } else {
+                        and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get(property), DateTimeUtils
+                                .toLocalDateTime((String) value, "yyyy-MM-dd"))));
+                    }
+                } else {
+                    and.add(criteriaBuilder.and(criteriaBuilder.equal(root.get(property), value)));
+                }
+            });
+            if (StringUtils.isNotEmpty(pageQuery.getSearch())) {
+                Field[] fields = queryClass.getDeclaredFields();
+                List<Predicate> or = new ArrayList<>();
+                try {
+                    if (StringUtils.isNumeric(pageQuery.getSearch())) {
+                        or.add(criteriaBuilder.equal(root.get("id"), Long.parseLong(pageQuery.getSearch())));
+                    }
+                } catch (Exception ignored) {
+                }
+                for (Field field : fields) {
+                    Searchable annotation = field.getAnnotation(Searchable.class);
+                    if (annotation == null) {
+                        continue;
+                    }
+                    if (!annotation.value()) {
+                        continue;
+                    }
+                    or.add(criteriaBuilder.like(root.get(field.getName()), "%" + pageQuery.getSearch() + "%"));
+                }
+                and.add(criteriaBuilder.or(or.toArray(new Predicate[0])));
+            }
+            return criteriaBuilder.and(and.toArray(new Predicate[0]));
+        };
+    }
+
+    private Field getDeclaredField(Class<?> clazz, String property) {
+        String className = clazz.getName();
+        while (clazz != null && clazz != Object.class) {
+            try {
+                return clazz.getDeclaredField(property);
+            } catch (NoSuchFieldException ignored) {
+            }
+            clazz = clazz.getSuperclass();
+        }
+        log.error("no such field [{}] in class [{}]", property, className);
+        return null;
+    }
+}

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.