xiannian.mu пре 1 година
комит
8612e517bc
100 измењених фајлова са 6977 додато и 0 уклоњено
  1. 33 0
      .gitignore
  2. 79 0
      README.md
  3. BIN
      doc/image-20220209161928914.png
  4. BIN
      doc/image-20220209162513167.png
  5. 310 0
      mvnw
  6. 182 0
      mvnw.cmd
  7. 1 0
      out/artifacts/translate_tools_jar/cm_translate_v2.conf
  8. BIN
      out/artifacts/translate_tools_jar/翻译同步工具V1.jar
  9. 73 0
      pom.xml
  10. 55 0
      translate-tools/pom.xml
  11. 3 0
      translate-tools/src/main/META-INF/MANIFEST.MF
  12. 93 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/TranslateDataHelper.kt
  13. 421 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/TranslateFormatHelper.kt
  14. 77 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/TranslateMain.kt
  15. 416 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/TranslateUI.kt
  16. 18 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/config/Config.kt
  17. 92 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/config/ConfigHelper.kt
  18. 16 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/entity/ConfigJson.kt
  19. 105 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/entity/TranslateDownloadBean.java
  20. 65 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/entity/TranslateUploadBean.java
  21. 41 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/listener/JTextFieldHintListener.java
  22. 15 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/utils/GsonGet.kt
  23. 130 0
      translate-tools/src/main/kotlin/com/cretin/translatetools/utils/RestTemplateUtils.java
  24. 33 0
      web-core/.gitignore
  25. 118 0
      web-core/.mvn/wrapper/MavenWrapperDownloader.java
  26. BIN
      web-core/.mvn/wrapper/maven-wrapper.jar
  27. 2 0
      web-core/.mvn/wrapper/maven-wrapper.properties
  28. 310 0
      web-core/mvnw
  29. 182 0
      web-core/mvnw.cmd
  30. 103 0
      web-core/pom.xml
  31. 15 0
      web-core/src/main/kotlin/com/cretin/webcore/annotation/UserAuthCheck.java
  32. 16 0
      web-core/src/main/kotlin/com/cretin/webcore/annotation/UserLoginToken.java
  33. 178 0
      web-core/src/main/kotlin/com/cretin/webcore/config/AppConstants.kt
  34. 45 0
      web-core/src/main/kotlin/com/cretin/webcore/config/CommonConfig.java
  35. 51 0
      web-core/src/main/kotlin/com/cretin/webcore/config/SwaggerConfig.java
  36. 81 0
      web-core/src/main/kotlin/com/cretin/webcore/exception/GlobalControllerAdvice.java
  37. 48 0
      web-core/src/main/kotlin/com/cretin/webcore/exception/WholeException.java
  38. 22 0
      web-core/src/main/kotlin/com/cretin/webcore/helper/JokesImageHelper.kt
  39. 47 0
      web-core/src/main/kotlin/com/cretin/webcore/helper/NumFormatHelper.kt
  40. 46 0
      web-core/src/main/kotlin/com/cretin/webcore/helper/SpringContextUtil.java
  41. 77 0
      web-core/src/main/kotlin/com/cretin/webcore/helper/UserExperienceHelper.kt
  42. 27 0
      web-core/src/main/kotlin/com/cretin/webcore/mapping/UserExperienceMp.kt
  43. 101 0
      web-core/src/main/kotlin/com/cretin/webcore/mapping/UserExperienceTypeMp.kt
  44. 125 0
      web-core/src/main/kotlin/com/cretin/webcore/mapping/UserMsgTypeMp.kt
  45. 108 0
      web-core/src/main/kotlin/com/cretin/webcore/oss/OssUrlAuthHelper.kt
  46. 109 0
      web-core/src/main/kotlin/com/cretin/webcore/oss/QiniuAuthHelper.kt
  47. 55 0
      web-core/src/main/kotlin/com/cretin/webcore/redis/RedisHelper.kt
  48. 68 0
      web-core/src/main/kotlin/com/cretin/webcore/sms/SMSHelper.kt
  49. 162 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/CreateNamePicture.java
  50. 82 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/DEStool.java
  51. 60 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/ExtendHelper.kt
  52. 15 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/GsonGet.kt
  53. 82 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/IpUtils.java
  54. 121 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/LotteryUtils.java
  55. 73 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/MD5Utils.java
  56. 229 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/StringUtils.java
  57. 227 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/TimeUtils.kt
  58. 41 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/UUIDUtils.java
  59. 24 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/UserAuthHelper.kt
  60. 49 0
      web-core/src/main/kotlin/com/cretin/webcore/utils/VerificationCodeHelper.kt
  61. 1 0
      web-core/src/main/resources/application.properties
  62. 33 0
      web-db/.gitignore
  63. 118 0
      web-db/.mvn/wrapper/MavenWrapperDownloader.java
  64. BIN
      web-db/.mvn/wrapper/maven-wrapper.jar
  65. 2 0
      web-db/.mvn/wrapper/maven-wrapper.properties
  66. 310 0
      web-db/mvnw
  67. 182 0
      web-db/mvnw.cmd
  68. 57 0
      web-db/pom.xml
  69. 20 0
      web-db/src/main/kotlin/com/cretin/webdb/controller/TbGlobalConfigController.java
  70. 20 0
      web-db/src/main/kotlin/com/cretin/webdb/controller/TbStrTranslateItemController.java
  71. 20 0
      web-db/src/main/kotlin/com/cretin/webdb/controller/TbStrTranslateLogController.java
  72. 20 0
      web-db/src/main/kotlin/com/cretin/webdb/controller/TbStrTranslateModuleController.java
  73. 20 0
      web-db/src/main/kotlin/com/cretin/webdb/controller/TbStrTranslateUserController.java
  74. 20 0
      web-db/src/main/kotlin/com/cretin/webdb/controller/TbV2HandleLogController.java
  75. 20 0
      web-db/src/main/kotlin/com/cretin/webdb/controller/TbV2StrTranslateItemController.java
  76. 20 0
      web-db/src/main/kotlin/com/cretin/webdb/controller/TbV2StrTranslateProjectController.java
  77. 65 0
      web-db/src/main/kotlin/com/cretin/webdb/entity/TbGlobalConfig.java
  78. 92 0
      web-db/src/main/kotlin/com/cretin/webdb/entity/TbStrTranslateItem.java
  79. 45 0
      web-db/src/main/kotlin/com/cretin/webdb/entity/TbStrTranslateLog.java
  80. 42 0
      web-db/src/main/kotlin/com/cretin/webdb/entity/TbStrTranslateModule.java
  81. 55 0
      web-db/src/main/kotlin/com/cretin/webdb/entity/TbStrTranslateUser.java
  82. 45 0
      web-db/src/main/kotlin/com/cretin/webdb/entity/TbV2HandleLog.java
  83. 62 0
      web-db/src/main/kotlin/com/cretin/webdb/entity/TbV2StrTranslateItem.java
  84. 89 0
      web-db/src/main/kotlin/com/cretin/webdb/entity/TbV2StrTranslateProject.java
  85. 41 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/CustomHomeService.kt
  86. 15 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/CustomRecommendService.kt
  87. 14 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/CustomUserService.kt
  88. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/TbGlobalConfigMapper.java
  89. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/TbStrTranslateItemMapper.java
  90. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/TbStrTranslateLogMapper.java
  91. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/TbStrTranslateModuleMapper.java
  92. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/TbStrTranslateUserMapper.java
  93. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/TbV2HandleLogMapper.java
  94. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/TbV2StrTranslateItemMapper.java
  95. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/mapper/TbV2StrTranslateProjectMapper.java
  96. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/service/ITbGlobalConfigService.java
  97. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/service/ITbStrTranslateItemService.java
  98. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/service/ITbStrTranslateLogService.java
  99. 16 0
      web-db/src/main/kotlin/com/cretin/webdb/service/ITbStrTranslateModuleService.java
  100. 0 0
      web-db/src/main/kotlin/com/cretin/webdb/service/ITbStrTranslateUserService.java

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### 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/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

Разлика између датотеке није приказан због своје велике величине
+ 79 - 0
README.md


BIN
doc/image-20220209161928914.png


BIN
doc/image-20220209162513167.png


+ 310 - 0
mvnw

@@ -0,0 +1,310 @@
+#!/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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven 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)`"
+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
+    if [ -n "$MVNW_REPOURL" ]; then
+      jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    else
+      jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    fi
+    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 $cygwin; then
+      wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+    fi
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget "$jarUrl" -O "$wrapperJarPath"
+        else
+            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl -o "$wrapperJarPath" "$jarUrl" -f
+        else
+            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+        fi
+
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaClass=`cygpath --path --windows "$javaClass"`
+        fi
+        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
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+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 "$@"

+ 182 - 0
mvnw.cmd

@@ -0,0 +1,182 @@
+@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 Maven 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 keystroke 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 by 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.5.6/maven-wrapper-0.5.6.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% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %DOWNLOAD_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%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%

+ 1 - 0
out/artifacts/translate_tools_jar/cm_translate_v2.conf

@@ -0,0 +1 @@
+{"clientType":0,"rootPath":"/Users/cretin/Desktop/BLEkey1.90","token":"7a008d45e254422d9a4170c2b8d80456","version":"v1.1.0","host":"https://cretinzp.com/project_translate_api/","admin":"https://cretinzp.com/translate/#/console"}

BIN
out/artifacts/translate_tools_jar/翻译同步工具V1.jar


+ 73 - 0
pom.xml

@@ -0,0 +1,73 @@
+<?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.4.3</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <packaging>pom</packaging>
+    <groupId>com.cretin</groupId>
+    <artifactId>webroot</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>WebRoot</name>
+    <description>Demo project for Spring Boot</description>
+    <properties>
+        <java.version>1.8</java.version>
+        <kotlin.version>1.4.32</kotlin.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>com.fasterxml.jackson.module</groupId>
+            <artifactId>jackson-module-kotlin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-reflect</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-stdlib-jdk8</artifactId>
+        </dependency>
+    </dependencies>
+
+    <!--声明你有四个儿子 -->
+    <modules>
+        <module>web-core</module>
+        <module>web-db</module>
+        <module>web-redis</module>
+        <module>web-services</module>
+        <module>web-entity</module>
+        <module>webapi</module>
+        <module>translate-tools</module>
+    </modules>
+
+    <build>
+        <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
+        <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
+        <plugins>
+            <plugin>
+                <groupId>org.jetbrains.kotlin</groupId>
+                <artifactId>kotlin-maven-plugin</artifactId>
+                <configuration>
+                    <args>
+                        <arg>-Xjsr305=strict</arg>
+                    </args>
+                    <compilerPlugins>
+                        <plugin>spring</plugin>
+                    </compilerPlugins>
+                </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.jetbrains.kotlin</groupId>
+                        <artifactId>kotlin-maven-allopen</artifactId>
+                        <version>${kotlin.version}</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 55 - 0
translate-tools/pom.xml

@@ -0,0 +1,55 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.cretin</groupId>
+        <artifactId>webroot</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>translate-tools</artifactId>
+
+    <dependencies>
+<!--        <dependency>-->
+<!--            <groupId>com.cretin</groupId>-->
+<!--            <artifactId>web-core</artifactId>-->
+<!--            <version>0.0.1-SNAPSHOT</version>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <version>5.2.2.RELEASE</version>
+        </dependency>
+
+        <dependency>
+            <groupId>dom4j</groupId>
+            <artifactId>dom4j</artifactId>
+            <version>1.6.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>jaxen</groupId>
+            <artifactId>jaxen</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.7</version>
+        </dependency>
+        <!--gson-->
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.6</version>
+        </dependency>
+
+    </dependencies>
+
+
+
+</project>

+ 3 - 0
translate-tools/src/main/META-INF/MANIFEST.MF

@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: com.cretin.translatetools.TranslateUI
+

+ 93 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/TranslateDataHelper.kt

@@ -0,0 +1,93 @@
+package com.cretin.translatetools
+
+import com.cretin.translatetools.config.Config
+import com.cretin.translatetools.config.ConfigHelper
+import com.cretin.translatetools.entity.TranslateDownloadBean
+import com.cretin.translatetools.entity.TranslateUploadBean
+import com.cretin.translatetools.utils.GsonGet
+import com.cretin.translatetools.utils.RestTemplateUtils
+import com.google.gson.JsonObject
+import org.springframework.web.client.RestTemplate
+
+/**
+ * 翻译数据帮助类
+ */
+object TranslateDataHelper {
+    private val restTemplate by lazy { RestTemplate() }
+
+    /**
+     * 下载翻译数据
+     */
+    fun downloadTranslate(token: String): TranslateDownloadBean? {
+        try {
+            val url = ConfigHelper.getConfigHost() + "/admin/translate/client/list"
+            val params = mutableMapOf<String, Any>()
+            params["clientType"] = 0
+            params["token"] = token
+            val result = RestTemplateUtils.doPost(restTemplate, url, params)
+            return GsonGet.getGson().fromJson(result, TranslateDownloadBean::class.java)
+        } catch (e: Throwable) {
+            return TranslateDownloadBean().apply {
+                this.code = 0
+                this.msg = e.message
+            }
+        }
+    }
+
+    /**
+     * 下载翻译数据
+     */
+    fun downloadTranslateIOS(token: String): TranslateDownloadBean? {
+        try {
+            val url = ConfigHelper.getConfigHost() + "/admin/translate/client/list"
+            val params = mutableMapOf<String, Any>()
+            params["clientType"] = 1
+            params["token"] = token
+            val result = RestTemplateUtils.doPost(restTemplate, url, params)
+            return GsonGet.getGson().fromJson(result, TranslateDownloadBean::class.java)
+        } catch (e: Throwable) {
+            return TranslateDownloadBean().apply {
+                this.code = 0
+                this.msg = e.message
+            }
+        }
+    }
+
+
+    /**
+     * 上传本地翻译
+     */
+    fun uploadTranslate(data: TranslateUploadBean): String? {
+        val url = ConfigHelper.getConfigHost() + "/admin/translate/client/list/upload"
+        try {
+            return RestTemplateUtils.doPost(restTemplate, url, data)
+        } catch (e: Throwable) {
+            return e.message
+        }
+    }
+
+    /**
+     * 上传本地英文翻译
+     */
+    fun uploadEnTranslate(data: TranslateUploadBean): String? {
+        val url = ConfigHelper.getConfigHost() + "/admin/translate/client/list/upload/en"
+        try {
+            return RestTemplateUtils.doPost(restTemplate, url, data)
+        } catch (e: Throwable) {
+            return e.message
+        }
+    }
+}
+
+fun main(args: Array<String>) {
+    var code = 1
+    var start = 2021001
+    while (code == 1){
+        val result = RestTemplateUtils.doPost(RestTemplate(), "https://www.mxnzp.com/api/lottery/common/aim_lottery?expect=${start}&code=qlc&app_id=ixssxqertpltndez&app_secret=QUF5S2JLZkNqSHdyeVVLczdCNSt1QT09", null)
+        val map = GsonGet.getGson().fromJson<MutableMap<String,Any>>(result, MutableMap::class.java)
+        code = map["code"].toString().toDouble().toInt()
+        start++
+        println("正在执行 $start")
+    }
+    println("结束了")
+}

+ 421 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/TranslateFormatHelper.kt

@@ -0,0 +1,421 @@
+package com.cretin.translatetools
+
+import com.cretin.translatetools.entity.TranslateDownloadBean
+import com.cretin.translatetools.entity.TranslateUploadBean
+import org.apache.http.util.TextUtils
+import java.io.File
+import java.lang.RuntimeException
+
+/**
+ * 转换帮助类
+ */
+object TranslateFormatHelper {
+
+    //获取内容
+    private fun getContent(line: String): String {
+        val realLine = line.trim()
+        if (realLine.startsWith("<!--") && realLine.endsWith("-->")) return ""
+        if (line.contains("</string>"))
+            return line.trim().replace("</string>", "").replace("<string name=", "")?.let {
+                it.substring(it.indexOf(">") + 1)
+            }
+        return ""
+    }
+
+    /**
+     * 获取文件包名
+     */
+    private fun getPackageName(file: File, rootAddress: String): String {
+        return file.parent.replace(rootAddress, "")
+    }
+
+    //获取key     <string name="photo_title" >Turn on the camera</string>
+    fun getKey(line: String): String {
+        if (line.contains("</string>")) {
+            line.trim().apply {
+                if (this.startsWith("<!--") && this.endsWith("-->")) return ""
+                this.substring(this.indexOf("\"") + 1, this.indexOf(">")).apply {
+                    return this.substring(0, this.lastIndexOf("\""))
+                }
+            }
+        }
+        return ""
+    }
+
+    //获取key
+    private fun getKeyIOS(line: String): String {
+        if (line.trim().startsWith("\"") && line.trim().endsWith(";") && line.trim().contains("="))
+            line.trim().split("=")?.apply {
+                if (this.size == 2) {
+                    this[0].trim().apply {
+                        if (this.startsWith("\"") && this.endsWith("\"")) {
+                            return this.substring(1, this.length - 1)
+                        }
+                    }
+                }
+            }
+        return ""
+    }
+
+    //获取内容
+    private fun getContentIOS(line: String): String {
+        if (line.trim().startsWith("\"") && line.trim().endsWith(";") && line.trim().contains("="))
+            line.trim().split("=")?.apply {
+                if (this.size == 2) {
+                    this[1].trim().apply {
+                        if (this.endsWith(";")) {
+                            this.substring(0, this.length - 1).trim().apply {
+                                if (this.startsWith("\"") && this.endsWith("\"")) {
+                                    return this.substring(1, this.length - 1)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        return ""
+    }
+
+    /**
+     * 替换内容
+     */
+    private fun replaceContent(line: String, append: String): String {
+        if (line.contains("</string>"))
+            return line.substring(0, line.indexOf(">") + 1) + append + line.substring(line.indexOf("</string>"))
+        return line
+    }
+
+    /**
+     * 转换云端数据到本地
+     *
+     * projectRoot 项目地址
+     */
+    fun convertRemoteDataToLocal(downloadTranslate: TranslateDownloadBean?, projectAddress: String) {
+        val projectRoot = projectAddress.trim()
+        if (TextUtils.isEmpty(projectRoot)) throw RuntimeException("项目地址不能为空")
+        val rootFile = File(projectRoot)
+        if (!rootFile.exists()) throw RuntimeException("项目地址不存在")
+        val fileTree = rootFile.walk()
+        fileTree.maxDepth(100)//遍历目录层级为100
+                .filter { it.path.endsWith("/values/strings.xml") }
+                .filter { it.isFile } //只挑选出文件,不处理文件夹
+                .forEach { //循环处理符合条件的文件
+                    convertSingleFileToLocal(downloadTranslate, it, projectRoot)
+                }
+    }
+
+    /**
+     * 转换云端数据到本地
+     *
+     * projectRoot 项目地址
+     */
+    fun convertRemoteDataToLocalIOS(downloadTranslate: TranslateDownloadBean?, projectAddress: String): String {
+        var projectRoot = projectAddress.trim()
+        if (TextUtils.isEmpty(projectRoot)) throw RuntimeException("项目地址不能为空")
+        while (projectRoot.endsWith("/")) {
+            projectRoot = projectRoot.substring(0, projectRoot.length - 1)
+        }
+        if (projectRoot.endsWith("zh-Hans.lproj")) {
+            //正确
+            val rootFile = File(projectRoot)
+            val originFile = File(projectRoot, "Localizable.strings")
+            if (!originFile.exists()) throw RuntimeException("中文翻译文件不存在")
+            if (!rootFile.exists()) throw RuntimeException("项目地址不存在")
+            val newFile = File(projectRoot.replace("zh-Hans.lproj", "en.lproj"))
+            if (!newFile.exists()) {
+                newFile.mkdirs()
+            }
+            val aimFile = File(newFile.path, "Localizable.strings")
+            if (aimFile.exists()) {
+                //备份 2022年01月18日17:31:11 去掉备份逻辑 删除文件
+//                aimFile.renameTo(File(newFile, "Localizable.strings-" + System.currentTimeMillis()))
+                aimFile.delete()
+            }
+            aimFile.createNewFile()
+
+            for (line in originFile.readLines()) {
+                val realLine = line.trim()
+                if (realLine.startsWith("\"") && realLine.endsWith(";") && realLine.contains("=")) {
+                    //合法
+                    //是标准的string数据
+                    val key = getKeyIOS(line)
+                    val item = downloadTranslate?.data?.items?.find { temp -> temp.key == key }
+                    println(key + "  " + item?.translateEn)
+                    if (item != null)
+                        aimFile.appendText("\"$key\" = \"" + item?.translateEn + "\";\n")
+                } else {
+                    aimFile.appendText(line + "\n")
+                }
+            }
+
+            return "生成的英文文件位于:" + aimFile.absolutePath
+        } else {
+            throw RuntimeException("项目地址必须以 zh-Hans.lproj 结尾")
+        }
+    }
+
+    /**
+     * 收集本地数据进行上传
+     */
+    fun convertLocalDataToRemote(projectAddress: String): String? {
+        val projectRoot = projectAddress.trim()
+        if (TextUtils.isEmpty(projectRoot)) throw RuntimeException("项目地址不能为空")
+        val rootFile = File(projectRoot)
+        if (!rootFile.exists()) throw RuntimeException("项目地址不存在")
+        val fileTree = rootFile.walk()
+        val list = mutableListOf<TranslateUploadBean.ItemsBean>()
+        fileTree.maxDepth(100)//遍历目录层级为100
+                .filter { it.path.endsWith("/values/strings.xml") }
+                .filter { it.isFile } //只挑选出文件,不处理文件夹
+                .forEach { //循环处理符合条件的文件
+                    list.addAll(convertSingleFileToRemote(it, projectRoot))
+                }
+        val data = TranslateUploadBean()
+        data.clientType = 0
+        data.items = list
+        return TranslateDataHelper.uploadTranslate(data)
+    }
+
+    /**
+     * 收集本地英文数据进行上传
+     */
+    fun convertLocalEnDataToRemote(projectAddress: String): String? {
+        val projectRoot = projectAddress.trim()
+        if (TextUtils.isEmpty(projectRoot)) throw RuntimeException("项目地址不能为空")
+        val rootFile = File(projectRoot)
+        if (!rootFile.exists()) throw RuntimeException("项目地址不存在")
+        val fileTree = rootFile.walk()
+        val list = mutableListOf<TranslateUploadBean.ItemsBean>()
+        fileTree.maxDepth(100)//遍历目录层级为100
+                .filter { it.path.endsWith("/values-en/strings.xml") }
+                .filter { it.isFile } //只挑选出文件,不处理文件夹
+                .forEach { //循环处理符合条件的文件
+                    list.addAll(convertSingleFileToRemote(it, projectRoot))
+                }
+        val data = TranslateUploadBean()
+        data.clientType = 0
+        data.items = list
+        return TranslateDataHelper.uploadTranslate(data)
+    }
+
+    /**
+     * 收集本地数据进行上传
+     */
+    fun preConvertLocalDataToRemote(projectAddress: String): TranslateUploadBean {
+        val projectRoot = projectAddress.trim()
+        if (TextUtils.isEmpty(projectRoot)) throw RuntimeException("项目地址不能为空")
+        val rootFile = File(projectRoot)
+        if (!rootFile.exists()) throw RuntimeException("项目地址不存在")
+        val fileTree = rootFile.walk()
+        val list = mutableListOf<TranslateUploadBean.ItemsBean>()
+        fileTree.maxDepth(100)//遍历目录层级为100
+                .filter { it.path.endsWith("/values/strings.xml") }
+                .filter { it.isFile } //只挑选出文件,不处理文件夹
+                .forEach { //循环处理符合条件的文件
+                    list.addAll(convertSingleFileToRemote(it, projectRoot))
+                }
+        val data = TranslateUploadBean()
+        data.clientType = 0
+        data.items = list
+        return data
+    }
+
+    /**
+     * 收集本地英文数据进行上传
+     */
+    fun preConvertLocalEnDataToRemote(projectAddress: String): TranslateUploadBean {
+        val projectRoot = projectAddress.trim()
+        if (TextUtils.isEmpty(projectRoot)) throw RuntimeException("项目地址不能为空")
+        val rootFile = File(projectRoot)
+        if (!rootFile.exists()) throw RuntimeException("项目地址不存在")
+        val fileTree = rootFile.walk()
+        val list = mutableListOf<TranslateUploadBean.ItemsBean>()
+        fileTree.maxDepth(100)//遍历目录层级为100
+                .filter { it.path.endsWith("/values-en/strings.xml") }
+                .filter { it.isFile } //只挑选出文件,不处理文件夹
+                .forEach { //循环处理符合条件的文件
+                    list.addAll(convertSingleFileToRemote(it, projectRoot))
+                }
+        val data = TranslateUploadBean()
+        data.clientType = 0
+        data.items = list
+        return data
+    }
+
+    /**
+     * 收集本地数据进行上传 IOS
+     */
+    fun preConvertLocalDataToRemoteIOS(projectPath: String): TranslateUploadBean {
+        val projectRoot = projectPath.trim()
+        if (TextUtils.isEmpty(projectRoot)) throw RuntimeException("项目地址不能为空")
+        if (!projectRoot.endsWith("zh-Hans.lproj")) {
+            throw RuntimeException("项目地址必须以 zh-Hans.lproj 结尾")
+        }
+        val rootFile = File(projectRoot)
+        if (!rootFile.exists()) throw RuntimeException("项目地址不存在")
+        val fileTree = rootFile.walk()
+        val list = mutableListOf<TranslateUploadBean.ItemsBean>()
+        fileTree.maxDepth(100)//遍历目录层级为100
+                .filter { it.isFile } //只挑选出文件,不处理文件夹
+                .forEach { //循环处理符合条件的文件
+                    list.addAll(convertSingleFileToRemoteIOS(it, projectRoot))
+                }
+        val data = TranslateUploadBean()
+        data.clientType = 1
+        data.items = list
+        return data
+    }
+
+    /**
+     * 收集本地英文数据进行上传 IOS
+     */
+    fun preConvertLocalEnDataToRemoteIOS(projectPath: String): TranslateUploadBean {
+        val projectRoot = projectPath.trim()
+        if (TextUtils.isEmpty(projectRoot)) throw RuntimeException("项目地址不能为空")
+        if (!projectRoot.endsWith("zh-Hans.lproj")) {
+            throw RuntimeException("项目地址必须以 zh-Hans.lproj 结尾")
+        }
+        val rootFile = File(projectRoot)
+        if (!rootFile.exists()) throw RuntimeException("项目地址不存在")
+        val enRootFile = File(projectRoot.replace("zh-Hans.lproj", "en.lproj"))
+        if (!enRootFile.exists()) throw RuntimeException("未在同级目录中找到en.lproj文件夹")
+        val fileTree = enRootFile.walk()
+        val list = mutableListOf<TranslateUploadBean.ItemsBean>()
+        fileTree.maxDepth(100)//遍历目录层级为100
+                .filter { it.isFile } //只挑选出文件,不处理文件夹
+                .forEach { //循环处理符合条件的文件
+                    list.addAll(convertSingleFileToRemoteIOS(it, projectRoot))
+                }
+        val data = TranslateUploadBean()
+        data.clientType = 1
+        data.items = list
+        return data
+    }
+
+    /**
+     * 从本地处理单个文件 手机文件中的数据
+     */
+    private fun convertSingleFileToRemoteIOS(file: File, projectRoot: String): MutableList<TranslateUploadBean.ItemsBean> {
+        if (!file.exists()) return mutableListOf()
+        val list = mutableListOf<TranslateUploadBean.ItemsBean>()
+        for (line in file.readLines()) {
+            val realLine = line.trim()
+            if (realLine.startsWith("\"") && realLine.endsWith(";") && realLine.contains("=")) {
+                //合法
+                //是标准的string数据
+                val key = getKeyIOS(line)
+                val content = getContentIOS(line)
+                val item = TranslateUploadBean.ItemsBean()
+                item.content = content
+                item.key = key
+                item.packageName = getPackageName(file, projectRoot)
+                list.add(item)
+            }
+        }
+        return list
+    }
+
+    /**
+     * 从本地处理单个文件 手机文件中的数据
+     */
+    private fun convertSingleFileToRemote(file: File, projectRoot: String): MutableList<TranslateUploadBean.ItemsBean> {
+        if (!file.exists()) return mutableListOf()
+        val list = mutableListOf<TranslateUploadBean.ItemsBean>()
+        for (line in file.readLines()) {
+            val realLine = line.trim()
+            if (realLine.startsWith("<!--") && realLine.endsWith("-->")){
+
+            } else{
+                if (line.contains("</string>") && line.contains("name=")) {
+                    //是标准的string数据
+                    val key = getKey(line)
+                    val content = getContent(line)
+                    val item = TranslateUploadBean.ItemsBean()
+                    item.content = content
+                    item.key = key
+                    item.packageName = getPackageName(file, projectRoot)
+                    list.add(item)
+                }
+            }
+        }
+        return list
+    }
+
+    /**
+     * 从本地处理单个文件 生成其他语言版本
+     */
+    private fun convertSingleFileToLocal(downloadTranslate: TranslateDownloadBean?, file: File, projectRoot: String) {
+        if (!file.exists()) return
+        val enFile = File(file.parent + "-en/strings.xml")
+        if (enFile.exists()) {
+            //Users/cretin/code_project/codemao/octopuslibrary/app/src/main/res/values-en/strings.xml
+            //如果存在说明之前有
+            val pre = if (projectRoot.endsWith("/")) projectRoot.substring(0, projectRoot.length - 1) else projectRoot
+            val end = file.path.replace(projectRoot, "")
+            val aimFile = File("$pre/translate-cache$end"?.let {
+                it.substring(0, it.length - "/strings.xml".length) + "-en"
+            })
+            if (!aimFile.exists()) {
+                aimFile.mkdirs()
+            }
+            enFile.renameTo(File(aimFile.path + "/strings.xml-" + System.currentTimeMillis()))
+        } else {
+            File(file.parent + "-en")?.apply {
+                if (!this.exists()) {
+                    this.mkdirs()
+                }
+            }
+        }
+
+        //创建文件
+        val newFile = File(enFile.path)
+        if (newFile.exists()) {
+            newFile.createNewFile()
+        }
+
+        //遍历文件
+        for (line in file.readLines()) {
+            if (line.contains("</string>") && line.contains("name=")) {
+                //是字符串内容 看云端有没有翻译
+                val key = getKey(line)
+                val translate = downloadTranslate?.data?.items?.find { it.key == key }
+                if (translate != null) {
+                    //云端有翻译
+                    val content = translate.translateEn.replace("'", "\\'").replace("\\\\'", "\\'")
+                    newFile.appendText(replaceContent(line, content) + "\n")
+                } else {
+                    //云端没有翻译 忽略
+                }
+            } else {
+                //不是 直接添加
+                newFile.appendText(line + "\n")
+            }
+        }
+    }
+}
+
+//fun main(args: Array<String>) {
+////    val data = TranslateFormatHelper.preConvertLocalDataToRemoteIOS("/Users/cretin/Downloads/ios_translate/zh-Hans.lproj")
+////    println(data)
+////    val data = TranslateUploadBean()
+////    data.clientType = 1
+////    data.items = mutableListOf()
+////    val itemsBean = TranslateUploadBean.ItemsBean()
+////    itemsBean.key = "ori_tip_deny_recording_permission"
+////    itemsBean.content = "拒绝录音权限了,无法正常使用声音控制"
+////    itemsBean.packageName = ""
+////    data.items.add(itemsBean)
+////    TranslateDataHelper.uploadTranslate(data)
+////    val datas = TranslateDataHelper.downloadTranslateIOS()
+////    TranslateFormatHelper.convertRemoteDataToLocalIOS(datas, "/Users/cretin/Downloads/ios_translate/zh-Hans.lproj")
+//
+//    val data = TranslateFormatHelper.preConvertLocalEnDataToRemote("/Users/cretin/code_project/codemao/temp/codemao")
+//    val data1 = TranslateFormatHelper.preConvertLocalDataToRemote("/Users/cretin/code_project/codemao/temp/codemao")
+//    println(data)
+
+//    println(TranslateFormatHelper.getKey("    <string name=\"before_day\">Days ago</string>"))
+//    println(TranslateFormatHelper.getKey("    <string name=\"photo_title\" >Turn on the camera</string>"))
+//    println(TranslateFormatHelper.getKey("    <string name=\"bcm_version_higher_current\">\\\" 该作品bcm版本高于当前app,请前往更新,或者下载编程猫Nemo\\\"</string>"))
+//    println(TranslateFormatHelper.getKey("    <string name=\"miao_copy_text\"><![CDATA[]吧,复制这段文本,打开编程猫即可浏览,内容为:\$&]]></string>"))
+//    println(TranslateFormatHelper.getKey("    <string name=\"my_test_txt\">\\\"我的-草稿箱 \\\"</string>"))
+//}

+ 77 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/TranslateMain.kt

@@ -0,0 +1,77 @@
+package com.cretin.translatetools
+
+//fun getContent(line: String): String {
+//    return line.trim().replace("</string>", "").replace("<string name=", "")?.let {
+//        it.substring(it.indexOf(">") + 1)
+//    }
+//}
+
+//fun replaceContent(line: String, append: String): String {
+//    if (line.contains("</string>"))
+//        return line.substring(0, line.indexOf("</string>")) + append + line.substring(line.indexOf("</string>"))
+//    return line
+//}
+
+//fun main(args: Array<String>) {
+//    val root = "/Users/cretin/code_project/codemao/octopuslibrary"
+//
+////    val downloadTranslate = TranslateDataHelper.downloadTranslate()
+////    TranslateFormatHelper.convertRemoteDataToLocal(downloadTranslate,root)
+//
+//    val log = TranslateFormatHelper.convertLocalDataToRemote(root)
+//    println(log)
+//
+////    val fileTree = File(root).walk()
+////    val fileNames = mutableListOf<String>()
+////    fileTree.maxDepth(100)//遍历目录层级为1,即无需检查子目录
+////            .filter { it.isFile } //只挑选出文件,不处理文件夹
+////            .filter { it.path.endsWith("/values/strings.xml") }
+////            .forEach {//循环处理符合条件的文件
+////                fileNames.add(it.path)
+////            }
+////
+////    //复制文件
+////    fileNames.forEach {
+////        val file = File(it)
+////        val enFile = File(file.parent + "-en/strings.xml")
+////        if (enFile.exists()) {
+////            //如果存在说明之前有
+////            enFile.renameTo(File(root + "/temp/" + "values-en/" + System.currentTimeMillis() + "-strings.xml"))
+////        }else {
+////            File(file.parent + "-en")?.apply {
+////                if(!this.exists()){
+////                    this.mkdirs()
+////                }
+////            }
+////        }
+////
+////        //创建文件
+////        val newFile = File(enFile.path)
+////        if(newFile.exists()){
+////            newFile.createNewFile()
+////        }
+////
+////        for (readLine in file.readLines()) {
+////            newFile.appendText(replaceContent(readLine, "-english") + "\n", )
+////        }
+////
+////    }
+//
+////    var size = 0
+////    fileNames.forEach {
+////        File(it).readLines().forEach {
+////            if (it.trim().startsWith("<string")) {
+////                it.trim()?.apply {
+////                    val content = this.replace("</string>", "").replace("<string name=", "")?.let {
+////                        it.substring(it.indexOf(">") + 1)
+////                    }
+////                    val key = this.substring(0, this.indexOf(content) - 2).replace("<string name=\"", "")
+////
+////                    println(this + "   " + key+"   "+content)
+////                }
+////                size++
+////            }
+////        }
+////    }
+////    println(size.toString() + "次")
+//}

+ 416 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/TranslateUI.kt

@@ -0,0 +1,416 @@
+package com.cretin.translatetools
+
+import com.cretin.translatetools.config.Config
+import com.cretin.translatetools.config.Config.Companion.CAN_UPLAOD_EN
+import com.cretin.translatetools.config.ConfigHelper
+import com.cretin.translatetools.entity.ConfigJson
+import com.cretin.translatetools.entity.TranslateDownloadBean
+import com.cretin.translatetools.entity.TranslateUploadBean
+import com.cretin.translatetools.utils.GsonGet
+import org.apache.http.util.TextUtils
+import java.awt.Color
+import java.awt.Desktop
+import java.awt.event.FocusEvent
+import java.awt.event.FocusListener
+import java.io.File
+import java.net.URI
+import java.text.SimpleDateFormat
+import java.util.*
+import javax.swing.*
+
+class TranslateUI : JFrame() {
+    private val mWidth = 800
+    private val mHeight = 525
+    private val panel: JPanel
+    private val buttonSelect: JButton
+    private var buttonUploadEn: JButton? = null
+    private val buttonUpload: JButton
+    private val buttonUploadPre: JButton
+    private val buttonDownload: JButton
+    private val buttonDownloadPre: JButton
+    private val buttonTranslate: JButton
+    private val buttonClear: JButton
+    private val jCheckBox0: JCheckBox
+    private val jCheckBox: JCheckBox
+    private val textField: JTextField
+    private val tokenField: JTextField
+    private val textArea: JTextArea
+    private val jLabel: JLabel
+    private val jsp: JScrollPane
+    private var currPathString: String? = null/**/
+    private val separator: String
+    private var localToken = ""
+    private var currentClientType: Int = 0
+
+    private var hintText = "请输入您的专属登录令牌,没有请找管理员获取"
+
+    //data
+    private var uploadData: TranslateUploadBean? = null
+    private var downData: TranslateDownloadBean? = null
+
+
+
+
+
+
+
+    companion object {
+        @JvmStatic
+        fun main(args: Array<String>) {
+            TranslateUI()
+        }
+    }
+
+    init {
+        title = "移动端国际化翻译同步助手"
+        setSize(mWidth, mHeight)
+        setLocationRelativeTo(null)
+        isResizable = false
+        defaultCloseOperation = EXIT_ON_CLOSE
+
+        panel = JPanel()
+        buttonSelect = JButton("选择本地项目地址")
+
+        buttonUploadPre = JButton("预加载本地数据")
+        buttonUpload = JButton("上传本地数据到云端")
+        buttonDownloadPre = JButton("远程下载数据到本地")
+        buttonDownload = JButton("合并远程数据到本地")
+        buttonTranslate = JButton("访问翻译后台")
+        buttonClear = JButton("清空控制台")
+        jCheckBox0 = JCheckBox("Android项目")
+        jCheckBox = JCheckBox("iOS项目")
+        jLabel = JLabel("项目地址")
+        if (CAN_UPLAOD_EN) {
+            buttonUploadEn = JButton("上传本地英文翻译")
+            buttonUploadEn?.setForeground(java.awt.Color.RED);
+            textField = JTextField(30)
+            tokenField = JTextField(27)
+        } else {
+            textField = JTextField(42)
+            tokenField = JTextField(39)
+        }
+
+        tokenField.addFocusListener(object : FocusListener {
+            override fun focusGained(e: FocusEvent?) {
+                val temp: String = tokenField.text
+                if (temp == hintText) {
+                    tokenField.text = ""
+                    tokenField.foreground = Color.BLACK
+                }
+            }
+
+            override fun focusLost(e: FocusEvent?) {
+                val temp: String = tokenField.text
+                if (temp == "") {
+                    tokenField.foreground = Color.GRAY
+                    tokenField.text = hintText
+                } else {
+                    //有内容
+                    localToken = temp.trim()
+                    ConfigHelper.writeConfigToken(localToken)
+                }
+            }
+        })
+        textField.text = "请选择本地项目地址"
+        textField.isEnabled = false
+        textArea = JTextArea(24, 64)
+        jsp = JScrollPane(textArea)
+        jsp.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
+        textArea.lineWrap = true // 设置文本区的换行策略
+        textArea.isEditable = false
+        separator = System.getProperties().getProperty("file.separator")
+        panel.add(jLabel)
+        panel.add(textField)
+        panel.add(jCheckBox0)
+        panel.add(jCheckBox)
+        if (CAN_UPLAOD_EN) {
+            panel.add(buttonUploadEn)
+        }
+        panel.add(buttonSelect)
+        panel.add(buttonUploadPre)
+        panel.add(buttonUpload)
+        panel.add(buttonDownloadPre)
+        panel.add(buttonDownload)
+        panel.add(JLabel("登录令牌"))
+        panel.add(tokenField)
+        panel.add(buttonClear)
+        panel.add(buttonTranslate)
+        panel.add(jsp)
+
+        ConfigHelper.getConfig()?.apply {
+            textField.text = this.rootPath
+            currPathString = this.rootPath
+
+            currentClientType = this.clientType
+            localToken = this.token
+
+            if (this.clientType == 0) {
+                jCheckBox0.isSelected = true
+                jCheckBox.isSelected = false
+            } else {
+                jCheckBox0.isSelected = false
+                jCheckBox.isSelected = true
+            }
+
+            if (!TextUtils.isEmpty(localToken)) {
+                tokenField.text = localToken
+            }
+
+            try {
+                val localVersion = this.version.replace(".", "").replace("v","").toInt()
+                val newVersion = Config.VERSION.replace(".", "").replace("v","").toInt()
+                if (newVersion > localVersion) {
+                    JOptionPane.showMessageDialog(contentPane, Config.UPDATE_TIPS, "当前版本更新内容",
+                            JOptionPane.WARNING_MESSAGE);
+                    ConfigHelper.writeConfigVersion(Config.VERSION)
+                }
+            } catch (e: Throwable) {
+
+            }
+        }
+
+        // 讲述使用说明
+        initText()
+        add(panel)
+        jCheckBox.addItemListener { e ->
+            val jcb = e.item as JCheckBox // 将得到的事件强制转化为JCheckBox类
+            if (jcb.isSelected) { // 判断是否被选择
+                jCheckBox0.isSelected = false
+
+                currentClientType = 1
+                ConfigHelper.writeConfigClient(1)
+            }
+        }
+        jCheckBox0.addItemListener { e ->
+            val jcb = e.item as JCheckBox // 将得到的事件强制转化为JCheckBox类
+            if (jcb.isSelected) { // 判断是否被选择
+                jCheckBox.isSelected = false
+
+                currentClientType = 0
+                ConfigHelper.writeConfigClient(0)
+            }
+        }
+
+        // 选择文件夹
+        buttonSelect.addActionListener {
+            val jfc = JFileChooser()
+            jfc.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
+            jfc.showDialog(JLabel(), "选择文件夹")
+            val file = jfc.selectedFile
+            if (file != null) {
+                appendText("已选择文件夹:${file.absolutePath}")
+                currPathString = file.absolutePath
+                textField.text = currPathString
+                //写入文件
+                ConfigHelper.writeConfigRootPath(currPathString ?: "")
+            } else {
+                appendText("取消选择文件夹......")
+            }
+        }
+
+        buttonUploadEn?.addActionListener {
+            if (TextUtils.isEmpty(currPathString)) {
+                appendText("请先选择项目地址!")
+                JOptionPane.showMessageDialog(contentPane, "请先选择项目地址", "系统信息",
+                        JOptionPane.WARNING_MESSAGE);
+                return@addActionListener
+            }
+            buttonUploadEn?.isEnabled = false
+            appendText("正在收集数据中...")
+            Thread {
+                try {
+                    val data = if (currentClientType == 0) TranslateFormatHelper.preConvertLocalEnDataToRemote(currPathString
+                            ?: "") else TranslateFormatHelper.preConvertLocalEnDataToRemoteIOS(currPathString ?: "")
+                    val map = mutableMapOf<String, Int>()
+                    data.token = localToken
+                    data.items.forEach {
+                        if (map.containsKey(it.key)) {
+                            map[it.key] = map[it.key]!! + 1
+                        } else {
+                            map[it.key] = 1
+                        }
+                    }
+                    val tempMap = map.filter { it.value > 1 }
+                    if (!tempMap.isEmpty()) {
+                        val result = StringBuilder()
+                        tempMap.forEach { t, u ->
+                            result.append(t + " 出现了 " + u + " 次\n")
+                        }
+                        val newResult = result.substring(0, result.length - 2)
+                        JOptionPane.showMessageDialog(contentPane, "系统检测到以下KEY中存在多次提交,请检查并删除重复KEY方可上传:\n" + newResult, "提交重复KEY",
+                                JOptionPane.WARNING_MESSAGE);
+                        appendText("收集完成,系统检测到以下KEY中存在多次提交,请检查并删除重复KEY:\n" + newResult)
+                    } else {
+                        appendText("共收集本地翻译 ${data.items.size} 条,已准备好上传!")
+                        val select = JOptionPane.showConfirmDialog(contentPane, "共收集本地英文翻译 ${data.items.size} 条,已准备好上传!点击确定即可上传", "上传英文翻译", JOptionPane.YES_NO_CANCEL_OPTION)
+                        if (select == JOptionPane.YES_OPTION) {
+                            val result = TranslateDataHelper.uploadEnTranslate(data)
+                            appendText("本地英文上传数据完成,上传结果为 $result")
+                        }
+                    }
+                } catch (e: Throwable) {
+                    appendText(e.message.toString())
+                }
+                buttonUploadEn?.isEnabled = true
+            }.start()
+        }
+
+        buttonClear.addActionListener {
+            textArea.text = ""
+            initText()
+            val bar = jsp.verticalScrollBar
+            bar.value = bar.maximum + 100
+        }
+
+        buttonUploadPre.addActionListener {
+            if (TextUtils.isEmpty(currPathString)) {
+                appendText("请先选择项目地址!")
+                JOptionPane.showMessageDialog(contentPane, "请先选择项目地址", "系统信息",
+                        JOptionPane.WARNING_MESSAGE);
+                return@addActionListener
+            }
+            buttonUploadPre.isEnabled = false
+            appendText("正在收集数据中...")
+            Thread {
+                try {
+                    val data = if (currentClientType == 0) TranslateFormatHelper.preConvertLocalDataToRemote(currPathString
+                            ?: "") else TranslateFormatHelper.preConvertLocalDataToRemoteIOS(currPathString ?: "")
+                    val map = mutableMapOf<String, Int>()
+                    data.items.forEach {
+                        if (map.containsKey(it.key)) {
+                            map[it.key] = map[it.key]!! + 1
+                        } else {
+                            map[it.key] = 1
+                        }
+                    }
+                    val tempMap = map.filter { it.value > 1 }
+                    if (!tempMap.isEmpty()) {
+                        val result = StringBuilder()
+                        tempMap.forEach { t, u ->
+                            result.append(t + " 出现了 " + u + " 次\n")
+                        }
+                        val newResult = result.substring(0, result.length - 2)
+                        JOptionPane.showMessageDialog(contentPane, "系统检测到以下KEY中存在多次提交,请检查并删除重复KEY方可上传:\n" + newResult, "提交重复KEY",
+                                JOptionPane.WARNING_MESSAGE);
+                        appendText("收集完成,系统检测到以下KEY中存在多次提交,请检查并删除重复KEY:\n" + newResult)
+                    } else {
+                        uploadData = data
+                        appendText("共收集本地翻译 ${data.items.size} 条,已准备好上传!")
+                    }
+                } catch (e: Throwable) {
+                    appendText(e.message.toString())
+                }
+                buttonUploadPre.isEnabled = true
+            }.start()
+        }
+
+        buttonUpload.addActionListener {
+            if (uploadData == null) {
+                appendText("请先预加载本地数据!")
+                JOptionPane.showMessageDialog(contentPane, "请先预加载本地数据", "系统信息",
+                        JOptionPane.WARNING_MESSAGE);
+                return@addActionListener
+            }
+            uploadData?.token = localToken
+            buttonUpload.isEnabled = false
+            appendText("正在上传本地数据,请耐心等待...")
+            Thread {
+                val result = TranslateDataHelper.uploadTranslate(uploadData!!)
+                uploadData = null
+                appendText("本地上传数据完成,上传结果为 $result")
+                buttonUpload.isEnabled = true
+            }.start()
+        }
+
+        buttonDownloadPre.addActionListener {
+            appendText("正在下载远程数据,请耐心等待...")
+            buttonDownloadPre.isEnabled = false
+            Thread {
+                val data = if (currentClientType == 0) TranslateDataHelper.downloadTranslate(localToken) else TranslateDataHelper.downloadTranslateIOS(localToken)
+                if (data?.code == 200) {
+                    appendText("远程数据下载成功,共拉取远程数据" + data.data.count + "条")
+                    this.downData = data
+                } else {
+                    appendText("远程数据下载失败,失败原因为:" + data?.msg)
+                }
+                buttonDownloadPre.isEnabled = true
+            }.start()
+        }
+
+        buttonDownload.addActionListener {
+            if (TextUtils.isEmpty(currPathString)) {
+                appendText("请先选择项目地址!")
+                JOptionPane.showMessageDialog(contentPane, "请先选择项目地址", "系统信息",
+                        JOptionPane.WARNING_MESSAGE);
+                return@addActionListener
+            }
+            if (downData == null) {
+                appendText("请先拉取远程数据!")
+                JOptionPane.showMessageDialog(contentPane, "请先拉取远程数据", "系统信息",
+                        JOptionPane.WARNING_MESSAGE);
+                return@addActionListener
+            }
+            buttonDownload.isEnabled = false
+            appendText("正在将远程数据合并到本地!")
+            Thread {
+                try {
+                    appendText("本地数据已合并完成!")
+                    if (currentClientType == 0) {
+                        TranslateFormatHelper.convertRemoteDataToLocal(downData, currPathString!!)
+                    } else {
+                        appendText(TranslateFormatHelper.convertRemoteDataToLocalIOS(downData, currPathString!!))
+                    }
+                    downData = null
+                } catch (e: Throwable) {
+                    appendText(e.message.toString())
+                }
+                buttonDownload.isEnabled = true
+            }.start()
+        }
+
+        buttonTranslate.addActionListener {
+            openBroswer()
+        }
+
+        isVisible = true
+    }
+
+    private fun initText() {
+        textArea.append("""
+    欢迎使用【移动端国际化翻译同步助手】 
+    1、初次使用工具,需要选择项目根目录,请点击【选择项目】按钮选择需要翻译的项目工程根目录
+    2、如果你需要上传翻译,请先点击【预加载本地数据】按钮收集本地数据,再点击【上传本地数据到云端】上传本地数据
+    3、上传翻译成功之后,请访问 ${ConfigHelper.getConfigAdmin()} 查看上传结果
+    4、如果你需要同步云端翻译,请先点击【远程下载数据到本地】将数据下载到本地,再点击【合并远程数据到本地】将数据合并到项目中
+    5、使用中有什么问题可联系:mxnzp_life@163.com
+    6、当前版本号:${Config.VERSION}
+    
+    """.trimIndent())
+        textArea.append("-----------------------------------------\n")
+    }
+
+    private var formator = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+
+    private fun appendText(text: String) {
+        textArea?.append(formator.format(Date()) + ":" + text + "\n")
+        val bar = jsp.verticalScrollBar
+        bar.value = bar.maximum + 100
+    }
+
+    private fun openBroswer() {
+        if (java.awt.Desktop.isDesktopSupported()) {
+            try {
+                val uri = URI.create(ConfigHelper.getConfigAdmin())
+                val desktop = Desktop.getDesktop()
+                if (desktop.isSupported(java.awt.Desktop.Action.BROWSE)) {
+                    desktop.browse(uri);
+                    appendText("${ConfigHelper.getConfigAdmin()} 链接打开成功")
+                } else {
+                    appendText("${ConfigHelper.getConfigAdmin()} 链接打开失败,系统不支持")
+                }
+            } catch (e: Throwable) {
+                appendText("${ConfigHelper.getConfigAdmin()} 链接打开失败,系统不支持")
+            }
+        }
+    }
+}

+ 18 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/config/Config.kt

@@ -0,0 +1,18 @@
+package com.cretin.translatetools.config
+
+class Config {
+    companion object {
+        val IS_DEBUG = false
+        val CAN_UPLAOD_EN = false // 是否可以上传英文版本
+        val VERSION = "v1.1.0"
+//        val HOST = "http://127.0.0.1:9003"//测试环境
+        val HOST = if(IS_DEBUG) "http://127.0.0.1:8050" else "https://cretinzp.com/project_translate_api/"
+        val HOST_ADMIN = if(IS_DEBUG) "http://127.0.0.1:8080/#/home" else "https://cretinzp.com/translate/#/console"
+        val UPDATE_TIPS = """
+V1.1.0更新内容如下:
+1、新增身份验证,更加安全
+2、可清空控制台内容输出
+3、iOS取消英文文件的备份
+        """.trimMargin()
+    }
+}

+ 92 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/config/ConfigHelper.kt

@@ -0,0 +1,92 @@
+package com.cretin.translatetools.config
+
+import com.cretin.translatetools.entity.ConfigJson
+import com.cretin.translatetools.utils.GsonGet
+import org.apache.http.util.TextUtils
+import java.io.File
+
+object ConfigHelper {
+    /**
+     * 获取配置文件
+     */
+    private fun getConfigFile(): File? {
+        return try {
+            var jarPath = System.getProperty("java.class.path")
+            jarPath = jarPath.substring(0, jarPath.lastIndexOf("/"))
+            val file = File("$jarPath/cm_translate_v2.conf")
+            if (!file.exists()) {
+                file.createNewFile()
+            }
+            file
+        } catch (e: Exception) {
+            null
+        }
+    }
+
+    fun getConfigHost(): String {
+        if (Config.IS_DEBUG) {
+            return Config.HOST
+        }
+        return getConfig().host
+    }
+
+    fun getConfigAdmin(): String {
+        if (Config.IS_DEBUG) {
+            return Config.HOST_ADMIN
+        }
+        return getConfig().admin
+    }
+
+    fun writeConfigClient(clientType: Int) {
+        if (!Config.IS_DEBUG)
+            getConfig()?.apply {
+                this.clientType = clientType
+                getConfigFile()?.writeText(GsonGet.getGson().toJson(this))
+            }
+    }
+
+    fun writeConfigVersion(version: String) {
+        if (!Config.IS_DEBUG)
+            getConfig()?.apply {
+                this.version = version
+                getConfigFile()?.writeText(GsonGet.getGson().toJson(this))
+            }
+    }
+
+    fun writeConfigToken(token: String) {
+        if (!Config.IS_DEBUG)
+            getConfig()?.apply {
+                this.token = token
+                getConfigFile()?.writeText(GsonGet.getGson().toJson(this))
+            }
+    }
+
+    fun writeConfigRootPath(rootPath: String) {
+        if (!Config.IS_DEBUG)
+            getConfig()?.apply {
+                this.rootPath = rootPath
+                getConfigFile()?.writeText(GsonGet.getGson().toJson(this))
+            }
+    }
+
+    /**
+     * 获取配置
+     */
+    fun getConfig(): ConfigJson {
+        if (!Config.IS_DEBUG) {
+            val file = getConfigFile()
+            val content = file?.readText() ?: ""
+            return if (TextUtils.isEmpty(content)) {
+                ConfigJson.createNew()
+            } else {
+                try {
+                    GsonGet.getGson().fromJson(content, ConfigJson::class.java)
+                } catch (e: Throwable) {
+                    ConfigJson.createNew()
+                }
+            }
+        } else {
+            return ConfigJson(1, "/Users/cretin/Downloads/ios_translate/zh-Hans.lproj", "43822f8e981141ec9bf322f5aa6bbacb")
+        }
+    }
+}

+ 16 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/entity/ConfigJson.kt

@@ -0,0 +1,16 @@
+package com.cretin.translatetools.entity
+
+data class ConfigJson(
+        var clientType: Int = 0,
+        var rootPath: String = "",
+        var token: String = "",
+        var version: String = "1.0.0",
+        var host: String = "https://cretinzp.com/project_translate_api/",
+        var admin: String = "https://cretinzp.com/translate/#/console",
+) {
+    companion object {
+        fun createNew(): ConfigJson {
+            return ConfigJson(0, "", "", "1.0.0")
+        }
+    }
+}

+ 105 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/entity/TranslateDownloadBean.java

@@ -0,0 +1,105 @@
+package com.cretin.translatetools.entity;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.List;
+
+public class TranslateDownloadBean {
+
+    private DataBean data;
+    private String msg;
+    private int code;
+
+    public DataBean getData() {
+        return data;
+    }
+
+    public void setData(DataBean data) {
+        this.data = data;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public static class DataBean {
+        private List<ItemsBean> items;
+        private int count;
+
+        public List<ItemsBean> getItems() {
+            return items;
+        }
+
+        public void setItems(List<ItemsBean> items) {
+            this.items = items;
+        }
+
+        public int getCount() {
+            return count;
+        }
+
+        public void setCount(int count) {
+            this.count = count;
+        }
+
+        public static class ItemsBean {
+            private String key;
+            private String packageName;
+            private String content;
+            private String translateEn;
+            private String translateTw;
+
+            public String getKey() {
+                return key;
+            }
+
+            public void setKey(String key) {
+                this.key = key;
+            }
+
+            public String getPackageName() {
+                return packageName;
+            }
+
+            public void setPackageName(String packageName) {
+                this.packageName = packageName;
+            }
+
+            public String getContent() {
+                return content;
+            }
+
+            public void setContent(String content) {
+                this.content = content;
+            }
+
+            public String getTranslateEn() {
+                return translateEn;
+            }
+
+            public void setTranslateEn(String translateEn) {
+                this.translateEn = translateEn;
+            }
+
+            public String getTranslateTw() {
+                return translateTw;
+            }
+
+            public void setTranslateTw(String translateTw) {
+                this.translateTw = translateTw;
+            }
+        }
+    }
+}

+ 65 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/entity/TranslateUploadBean.java

@@ -0,0 +1,65 @@
+package com.cretin.translatetools.entity;
+
+import java.util.List;
+
+public class TranslateUploadBean {
+
+    private int clientType;
+    private String token;
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    private List<ItemsBean> items;
+
+    public int getClientType() {
+        return clientType;
+    }
+
+    public void setClientType(int clientType) {
+        this.clientType = clientType;
+    }
+
+    public List<ItemsBean> getItems() {
+        return items;
+    }
+
+    public void setItems(List<ItemsBean> items) {
+        this.items = items;
+    }
+
+    public static class ItemsBean {
+        private String key;
+        private String packageName;
+        private String content;
+
+        public String getKey() {
+            return key;
+        }
+
+        public void setKey(String key) {
+            this.key = key;
+        }
+
+        public String getPackageName() {
+            return packageName;
+        }
+
+        public void setPackageName(String packageName) {
+            this.packageName = packageName;
+        }
+
+        public String getContent() {
+            return content;
+        }
+
+        public void setContent(String content) {
+            this.content = content;
+        }
+    }
+}

+ 41 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/listener/JTextFieldHintListener.java

@@ -0,0 +1,41 @@
+package com.cretin.translatetools.listener;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+
+public class JTextFieldHintListener implements FocusListener {
+    private String mHintText;
+    private JTextField mTextField;
+
+    public JTextFieldHintListener(String hintText, JTextField textField) {
+        this.mHintText = hintText;
+        this.mTextField = textField;
+        textField.setForeground(Color.GRAY);
+
+        String temp = mTextField.getText();
+        if (temp.equals("")) {
+            mTextField.setForeground(Color.GRAY);
+            mTextField.setText(mHintText);
+        }
+    }
+
+    @Override
+    public void focusGained(FocusEvent e) {
+        String temp = mTextField.getText();
+        if (temp.equals(mHintText)) {
+            mTextField.setText("");
+            mTextField.setForeground(Color.BLACK);
+        }
+    }
+
+    @Override
+    public void focusLost(FocusEvent e) {
+        String temp = mTextField.getText();
+        if (temp.equals("")) {
+            mTextField.setForeground(Color.GRAY);
+            mTextField.setText(mHintText);
+        }
+    }
+}

+ 15 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/utils/GsonGet.kt

@@ -0,0 +1,15 @@
+package com.cretin.translatetools.utils
+
+import com.google.gson.Gson
+
+object GsonGet {
+
+    private var gson: Gson = Gson()
+
+    /**
+     * 获取gson
+     */
+    fun getGson(): Gson {
+        return gson
+    }
+}

+ 130 - 0
translate-tools/src/main/kotlin/com/cretin/translatetools/utils/RestTemplateUtils.java

@@ -0,0 +1,130 @@
+/**
+ * Copyright (C), 2015-2019, XXX有限公司
+ * FileName: RestTemplateUtils
+ * Author:   cretin
+ * Date:     8/6/19 21:09
+ * Description: 网络的工具了
+ * History:
+ * <author>          <time>          <version>          <desc>
+ * 作者姓名           修改时间           版本号              描述
+ */
+package com.cretin.translatetools.utils;
+
+import com.cretin.translatetools.utils.GsonGet;
+import org.springframework.core.io.Resource;
+import org.springframework.http.*;
+import org.springframework.web.client.RestTemplate;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 〈网络的工具〉
+ *
+ * @author cretin
+ * @create 8/6/19
+ * @since 1.0.0
+ */
+public class RestTemplateUtils {
+
+    /**
+     * RestTemplate POST 请求
+     *
+     * @param restTemplate
+     * @param url
+     * @param map
+     * @return
+     */
+    public static String doPost(RestTemplate restTemplate, String url, Map<String, Object> map) throws UnsupportedEncodingException {
+//        MultiValueMap<String, Object> postParameters = new LinkedMultiValueMap<>();
+//        if ( map != null ) {
+//            for ( Map.Entry<String, Object> entry : map.entrySet() ) {
+//                postParameters.add(entry.getKey(), entry.getValue());
+//            }
+//        }
+//        //拿到header信息
+//        HttpHeaders requestHeaders = new HttpHeaders();
+//        requestHeaders.setContentType(MediaType.APPLICATION_JSON);
+//        requestHeaders.setAccept(Arrays.asList(MediaType.ALL));
+//        requestHeaders.setConnection("Keep-Alive");
+//        requestHeaders.add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+//        HttpEntity requestEntity = new HttpEntity(postParameters, requestHeaders);
+//        return restTemplate.postForObject(url, requestEntity, String.class);
+        HttpHeaders headers = new HttpHeaders();
+        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
+        headers.setContentType(type);
+        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
+        HttpEntity<String> formEntity = new HttpEntity<String>(GsonGet.INSTANCE.getGson().toJson(map), headers);
+        return new String(restTemplate.postForEntity(url, formEntity, String.class).getBody().getBytes("ISO-8859-1"),"utf-8");
+    }
+
+    public static String doPost(RestTemplate restTemplate, String url, Object object) throws UnsupportedEncodingException {
+//        MultiValueMap<String, Object> postParameters = new LinkedMultiValueMap<>();
+//        if ( map != null ) {
+//            for ( Map.Entry<String, Object> entry : map.entrySet() ) {
+//                postParameters.add(entry.getKey(), entry.getValue());
+//            }
+//        }
+//        //拿到header信息
+//        HttpHeaders requestHeaders = new HttpHeaders();
+//        requestHeaders.setContentType(MediaType.APPLICATION_JSON);
+//        requestHeaders.setAccept(Arrays.asList(MediaType.ALL));
+//        requestHeaders.setConnection("Keep-Alive");
+//        requestHeaders.add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
+//        HttpEntity requestEntity = new HttpEntity(postParameters, requestHeaders);
+//        return restTemplate.postForObject(url, requestEntity, String.class);
+
+        HttpHeaders headers = new HttpHeaders();
+        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
+        headers.setContentType(type);
+        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
+        HttpEntity<String> formEntity = new HttpEntity<String>(object == null ? "{}" : GsonGet.INSTANCE.getGson().toJson(object), headers);
+        return new String(restTemplate.postForEntity(url, formEntity, String.class).getBody().getBytes("ISO-8859-1"),"utf-8");
+    }
+
+    /**
+     * 下载文件
+     *
+     * @param restTemplate
+     * @param url
+     * @return
+     */
+    public static byte[] downloadFile(RestTemplate restTemplate, String url) {
+        HttpHeaders headers = new HttpHeaders();
+        List list = new ArrayList<>();
+        list.add(MediaType.IMAGE_GIF);
+        list.add(MediaType.IMAGE_JPEG);
+        list.add(MediaType.IMAGE_PNG);
+        headers.setAccept(list);
+
+        ResponseEntity<byte[]> response = restTemplate.exchange(
+                url,
+                HttpMethod.GET,
+                new HttpEntity<byte[]>(headers),
+                byte[].class);
+
+        return response.getBody();
+    }
+
+    public static InputStream downloadFileByStream(RestTemplate restTemplate, String url) throws IOException {
+        HttpHeaders headers = new HttpHeaders();
+        List list = new ArrayList<>();
+        list.add(MediaType.IMAGE_GIF);
+        list.add(MediaType.IMAGE_JPEG);
+        list.add(MediaType.IMAGE_PNG);
+        headers.setAccept(list);
+
+        ResponseEntity<Resource> response = restTemplate.exchange(
+                url,
+                HttpMethod.GET,
+                new HttpEntity<byte[]>(headers),
+                Resource.class);
+
+        return response.getBody().getInputStream();
+    }
+}

+ 33 - 0
web-core/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### 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/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 118 - 0
web-core/.mvn/wrapper/MavenWrapperDownloader.java

@@ -0,0 +1,118 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+    private static final String WRAPPER_VERSION = "0.5.6";
+    /**
+     * 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/"
+            + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".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 directory '" + 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 {
+        if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+            String username = System.getenv("MVNW_USERNAME");
+            char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+            Authenticator.setDefault(new Authenticator() {
+                @Override
+                protected PasswordAuthentication getPasswordAuthentication() {
+                    return new PasswordAuthentication(username, password);
+                }
+            });
+        }
+        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
web-core/.mvn/wrapper/maven-wrapper.jar


+ 2 - 0
web-core/.mvn/wrapper/maven-wrapper.properties

@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

+ 310 - 0
web-core/mvnw

@@ -0,0 +1,310 @@
+#!/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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven 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)`"
+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
+    if [ -n "$MVNW_REPOURL" ]; then
+      jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    else
+      jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    fi
+    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 $cygwin; then
+      wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+    fi
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget "$jarUrl" -O "$wrapperJarPath"
+        else
+            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl -o "$wrapperJarPath" "$jarUrl" -f
+        else
+            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+        fi
+
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaClass=`cygpath --path --windows "$javaClass"`
+        fi
+        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
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+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 "$@"

+ 182 - 0
web-core/mvnw.cmd

@@ -0,0 +1,182 @@
+@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 Maven 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 keystroke 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 by 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.5.6/maven-wrapper-0.5.6.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% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %DOWNLOAD_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%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%

+ 103 - 0
web-core/pom.xml

@@ -0,0 +1,103 @@
+<?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>com.cretin</groupId>
+        <artifactId>webroot</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>web-core</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.cretin</groupId>
+            <artifactId>web-redis</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+
+        <!--接口文档-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>2.9.2</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>2.9.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+            <version>3.3.3</version>
+        </dependency>
+
+        <!--gson-->
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.6</version>
+        </dependency>
+
+        <!--oss-->
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.10.2</version>
+        </dependency>
+
+        <!--七牛云-->
+        <dependency>
+            <groupId>com.qiniu</groupId>
+            <artifactId>qiniu-java-sdk</artifactId>
+            <version>[7.4.0, 7.4.99]</version>
+        </dependency>
+
+        <!--aliyun 短信sdk-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.5.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+            <version>2.0.1</version>
+        </dependency>
+
+        <!--时间神器-->
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+            <version>2.10</version>
+        </dependency>
+
+        <!--token-->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>3.4.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.16</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 15 - 0
web-core/src/main/kotlin/com/cretin/webcore/annotation/UserAuthCheck.java

@@ -0,0 +1,15 @@
+package com.cretin.webcore.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 检查用户身份是否过期
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UserAuthCheck {
+    boolean required() default true;
+}

+ 16 - 0
web-core/src/main/kotlin/com/cretin/webcore/annotation/UserLoginToken.java

@@ -0,0 +1,16 @@
+package com.cretin.webcore.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author jinbin
+ * @date 2018-07-08 20:40
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UserLoginToken {
+    boolean required() default true;
+}

+ 178 - 0
web-core/src/main/kotlin/com/cretin/webcore/config/AppConstants.kt

@@ -0,0 +1,178 @@
+/**
+ * Copyright (C), 2015-2018, XXX有限公司
+ * FileName: AppConstants
+ * Author:   cretin
+ * Date:     11/15/18 11:43
+ * Description: 应用通用数据
+ * History:
+ * <author>          <time>          <version>          <desc>
+ * 作者姓名           修改时间           版本号              描述
+</desc></version></time></author> */
+package com.cretin.webcore.config
+
+import com.cretin.webcore.helper.SpringContextUtil
+
+/**
+ * 〈应用通用数据〉
+ *
+ * @author cretin
+ * @create 11/15/18
+ * @since 1.0.0
+ */
+class AppConstants {
+
+    companion object {
+        /**
+         * 通用错误信息
+         */
+        const val COMM_ERROR = "请求超时,请稍后再试"
+
+        /**
+         * 默认分页 数据条数
+         */
+        const val DEFAULT_PAGE_SIZE = 10
+
+        /**
+         * 抽奖花费积分数量
+         */
+        const val LOTTERY_COST = 30;
+
+        /**
+         * 常用时间格式器
+         */
+        const val COMM_TIME_FORMATER = "yyyy-MM-dd HH:mm:ss"
+
+        /**
+         * SIGNIN date format
+         */
+        const val SIGNIN_TIME_FORMATER = "yyyy-MM-dd"
+
+        /**
+         * aliyun 短信sdk accessKeyId
+         */
+        const val ALIYUN_SDK_AK = "xxxxx"
+
+        /**
+         * aliyun 短信sdk accessKeySecret
+         */
+        const val ALIYUN_SDK_SK = "xxxxx"
+
+        /**
+         * aliyun oss endpoint
+         */
+        const val ALIYUN_OSS_ENDPOINT = "http://oss-cn-beijing.aliyuncs.com"
+
+        /**
+         * 阿里云存放头像的前缀
+         */
+        const val ALIYUM_AVATAR_HOST = "https://jokes-avatar.oss-cn-beijing.aliyuncs.com/"
+
+        /**
+         * aliyun sms endpoint
+         */
+        const val ALIYUN_SMS_ENDPOINT = "dysmsapi.aliyuncs.com"
+
+        /**
+         * 免费api的凭证信息 请去 https://mp.weixin.qq.com/s/UvKr0SG73_Py63ICUnLBPw 申请
+         */
+        const val MXNZP_API_ID = "xxxxxx"
+        const val MXNZP_API_SECRET = "xxxxxxx"
+
+        /**
+         * 七牛云ak
+         */
+        const val QINIU_AK = "xxxxxx"
+
+        /**
+         * 七牛云sk
+         */
+        const val QINIU_SK = "xxxxxxx"
+
+        /**
+         * aliyun sms template code
+         */
+        const val ALIYUN_SMS_CODE_TEMOCODE = "SMS_110450018"
+
+        /**
+         * aliyun sms sign name
+         */
+        const val ALIYUN_SMS_CODE_SIGN_NAME = "段子乐"
+        const val ALIYUN_SMS_CODE_SIGN_NAME_V2 = "RollApi"
+
+        /**
+         * aliyun sms sign name
+         */
+        const val APP_DEFAULT_SIGNATURE = "他正在想一个爆炸的签名..."
+
+        /**
+         * 极光 appkey srcret
+         */
+        const val JG_APP_KEY = "xxxx"
+        const val JG_APP_SECRET = "xxxx"
+
+        //qq群信息
+        const val QQ_NUM = "xxxx"
+        const val QQ_KEY = "xxxx"
+
+        /**
+         * token 过期时间
+         */
+        const val USER_TOKEN_EXPIRED_TIME = 15 * 24 * 3600L
+
+        /**
+         * 获取rpc请求host
+         */
+        fun getRecommendedSystemRpcHost(): String {
+            if (isDebug()) {
+                return "http://localhost:9005/rpc_service"
+//                return "https://www.mxnzp.com/jokes-temp/rpc_service"
+            } else {
+                return "http://localhost:9006/rpc_service"
+//            return "http://localhost:9006/rpc_service"
+            }
+        }
+
+        /**
+         * 获取
+         */
+        fun getWebHostPre(): String {
+            if (isDebug()) {
+                return "https://www.mxnzp.com/jokes-web-dev"
+            }
+            return "https://www.mxnzp.com/jokes-web"
+        }
+
+        /**
+         * 获取默认头像
+         */
+        fun getUserDefaultAvatar(nickname: String = "", userId: String = ""): String {
+            return "aliyun/jokes/avatar/default_avatar.png";
+        }
+
+        /**
+         * 是否校验参数 建议上线再开启
+         */
+        fun isSignRequest(): Boolean {
+            if (isDebug()) {
+                return false
+            }
+            return true
+        }
+
+        /**
+         * 是否是测试环境
+         */
+        fun isDebug(): Boolean {
+            return SpringContextUtil.getActiveProfile() == "dev"
+        }
+
+        /**
+         * 获取IM前缀
+         */
+        fun getImPre(): String {
+            return if (isDebug()) "test_" else "prod_"
+        }
+    }
+
+
+}

+ 45 - 0
web-core/src/main/kotlin/com/cretin/webcore/config/CommonConfig.java

@@ -0,0 +1,45 @@
+package com.cretin.webcore.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestFactory;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.web.client.RestTemplate;
+
+import java.nio.charset.Charset;
+import java.util.List;
+
+/**
+ * @date: on 11/14/18
+ * @author: cretin
+ * @email: mxnzp_life@163.com
+ * @desc: RestTemplate 配置
+ */
+@Configuration
+@EnableTransactionManagement
+class CommonConfig {
+    @Bean
+    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
+        RestTemplate restTemplate = new RestTemplate(factory);
+        List<HttpMessageConverter<?>> list = restTemplate.getMessageConverters();
+        for ( HttpMessageConverter<?> httpMessageConverter : list ) {
+            if ( httpMessageConverter instanceof StringHttpMessageConverter) {
+                ((StringHttpMessageConverter) httpMessageConverter).setDefaultCharset(Charset.forName("utf-8"));
+                break;
+            }
+        }
+        return restTemplate;
+    }
+
+    @Bean
+    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        factory.setReadTimeout(15000);//单位为ms
+        factory.setConnectTimeout(15000);//单位为ms
+        return factory;
+    }
+
+}

+ 51 - 0
web-core/src/main/kotlin/com/cretin/webcore/config/SwaggerConfig.java

@@ -0,0 +1,51 @@
+package com.cretin.webcore.config;
+
+import io.swagger.annotations.ApiOperation;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/** 
+ * @author cretin
+ * @date 2020/4/19 16:00
+ * @description
+ */
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig {
+    @Bean
+    public Docket createRestApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                // 指定构建api文档的详细信息的方法:apiInfo()
+                .apiInfo(apiInfo())
+                .select()
+                //使用了 @ApiOperation 注解的方法生成api接口文档
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                .paths(PathSelectors.any())
+                //可以根据url路径设置哪些请求加入文档,忽略哪些请求
+                .build();
+    }
+
+    /**
+     * 设置api文档的详细信息
+     */
+    private ApiInfo apiInfo() {
+        return new ApiInfoBuilder()
+                // 标题
+                .title("Spring Boot集成Swagger2")
+                // 接口描述
+                .description("swagger")
+                // 联系方式
+                .contact("mxnzp_life@163.com")
+                // 版本信息
+                .version("1.0")
+                // 构建
+                .build();
+    }
+}

+ 81 - 0
web-core/src/main/kotlin/com/cretin/webcore/exception/GlobalControllerAdvice.java

@@ -0,0 +1,81 @@
+package com.cretin.webcore.exception;
+
+import com.cretin.webcore.config.AppConstants;
+import org.apache.catalina.connector.ClientAbortException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.RedisConnectionFailureException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import org.springframework.web.multipart.MultipartException;
+import org.springframework.web.multipart.support.MissingServletRequestPartException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * controller 增强器
+ *
+ * @author sam
+ * @since 2017/7/17
+ */
+@RestControllerAdvice
+public class GlobalControllerAdvice {
+    private final Logger logger = LoggerFactory.getLogger(GlobalControllerAdvice.class);
+
+    /**
+     * 全局异常捕捉处理
+     *
+     * @param ex
+     * @return
+     */
+    @ResponseBody
+    @ExceptionHandler( value = Exception.class )
+    public Map errorHandler(Exception ex) {
+        if ( ex instanceof ClientAbortException) {
+            return null;
+        }
+        logger.error("程序抛出系统异常啦!" + ex.getMessage());
+        if(AppConstants.Companion.isDebug()){
+            ex.printStackTrace();
+        }
+        Map map = new HashMap();
+        map.put("code", 0);
+        if ( ex instanceof MethodArgumentTypeMismatchException) {
+            map.put("msg", "参数" + ((MethodArgumentTypeMismatchException) ex).getName() + "格式异常");
+        } else if ( ex instanceof MissingServletRequestParameterException) {
+            map.put("msg", "参数" + ((MissingServletRequestParameterException) ex).getParameterName() + "为必传项");
+        } else if ( ex instanceof MissingServletRequestPartException) {
+            map.put("msg", "参数" + ((MissingServletRequestPartException) ex).getRequestPartName() + "为必传项");
+        } else if ( ex instanceof HttpRequestMethodNotSupportedException) {
+            map.put("msg", "此方法不支持" + ((HttpRequestMethodNotSupportedException) ex).getMethod() + "请求");
+        } else if ( ex instanceof MultipartException) {
+            map.put("msg", "上传的文件不能为空");
+        } else if ( ex instanceof RedisConnectionFailureException ) {
+            map.put("msg", "redis未启动");
+        } else {
+            map.put("msg", "请求超时,请稍后再试");
+        }
+        return map;
+    }
+
+    /**
+     * 拦截捕捉自定义异常 MyException.class
+     *
+     * @param ex
+     * @return
+     */
+    @ResponseBody
+    @ExceptionHandler( value = WholeException.class )
+    public Map myErrorHandler(WholeException ex) {
+        Map map = new HashMap();
+        map.put("code", ex.getCode());
+        map.put("msg", ex.getMsg());
+        return map;
+    }
+}

+ 48 - 0
web-core/src/main/kotlin/com/cretin/webcore/exception/WholeException.java

@@ -0,0 +1,48 @@
+package com.cretin.webcore.exception;
+
+import com.cretin.webcore.config.AppConstants;
+
+/**
+ * @author cretin
+ * @since 2018-12-13
+ */
+public class WholeException extends RuntimeException {
+
+    public WholeException() {
+        this.msg = AppConstants.COMM_ERROR;
+        this.code = 0;
+    }
+
+    public WholeException(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public WholeException(String msg) {
+        this.msg = msg;
+        this.code = 0;
+    }
+
+    public static WholeException createLoginExpire(){
+        return new WholeException(202,"用户登录过期");
+    }
+
+    private int code;
+    private String msg;
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+}

+ 22 - 0
web-core/src/main/kotlin/com/cretin/webcore/helper/JokesImageHelper.kt

@@ -0,0 +1,22 @@
+package com.cretin.webcore.helper
+
+import com.cretin.webcore.oss.OssUrlAuthHelper
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: JokesImageHelper
+ * Author: cretin
+ * Date: 2021/3/31 4:17 下午
+ * Description: 处理段子的图片
+ */
+object JokesImageHelper {
+
+    /**
+     * 处理段子的图片
+     */
+    fun handlerJokesImageUrls(imageUrls: String): String {
+        return imageUrls?.split(",")?.map {
+            OssUrlAuthHelper.urlGet(it)
+        }.joinToString(",") { it ?: "" }
+    }
+}

+ 47 - 0
web-core/src/main/kotlin/com/cretin/webcore/helper/NumFormatHelper.kt

@@ -0,0 +1,47 @@
+package com.cretin.webcore.helper
+
+import java.math.BigDecimal
+
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: NumHelper
+ * Author: cretin
+ * Date: 2021/3/24 1:41 下午
+ * Description: 数据帮助类
+ */
+object NumFormatHelper {
+
+    /**
+     * 格式化数字
+     */
+    fun formatNum(num: Int): String {
+        if (num < 1000) {
+            return num.toString()
+        }
+        if (num < 10000) {
+            return calculateNum(num, 1000) + "k"
+        }
+        return calculateNum(num, 10000) + "w"
+    }
+
+    private fun calculateNum(num: Int, beishu: Int): String {
+        val b = BigDecimal(num.toString())
+        val temp = StringBuilder(b.divide(BigDecimal(beishu.toString())).setScale(2, BigDecimal.ROUND_HALF_UP).toString())
+        while (temp.endsWith("0") || temp.endsWith(".")) {
+            temp.delete(temp.length - 1, temp.length)
+        }
+        if (temp.length == 0) {
+            return "0"
+        }
+        return temp.toString()
+    }
+
+    @JvmStatic
+    fun main(args: Array<String>) {
+        println(calculateNum(6427, 1000))
+        println(calculateNum(0, 1000))
+        println(calculateNum(6467, 1000))
+        println(calculateNum(6422, 1000))
+    }
+}

+ 46 - 0
web-core/src/main/kotlin/com/cretin/webcore/helper/SpringContextUtil.java

@@ -0,0 +1,46 @@
+package com.cretin.webcore.helper;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+import java.util.Locale;
+
+@Component
+public class SpringContextUtil implements ApplicationContextAware {
+ 
+    private static ApplicationContext context = null;
+ 
+    /* (non Javadoc)
+     * @Title: setApplicationContext
+     * @Description: spring获取bean工具类
+     * @param applicationContext
+     * @throws BeansException
+     * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
+     */
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext)
+            throws BeansException {
+        this.context = applicationContext;
+    }
+
+    public static <T> T getBean(Class<T> clazz) {
+        return context.getBean(clazz);
+    }
+ 
+    // 传入线程中
+    public static <T> T getBean(String beanName) {
+        return (T) context.getBean(beanName);
+    }
+ 
+    // 国际化使用
+    public static String getMessage(String key) {
+        return context.getMessage(key, null, Locale.getDefault());
+    }
+ 
+    /// 获取当前环境
+    public static String getActiveProfile() {
+        return context.getEnvironment().getActiveProfiles()[0];
+    }
+}

+ 77 - 0
web-core/src/main/kotlin/com/cretin/webcore/helper/UserExperienceHelper.kt

@@ -0,0 +1,77 @@
+package com.cretin.webcore.helper
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: UserExperienceHelper
+ * Author: cretin
+ * Date: 2021/3/17 5:43 下午
+ * Description:用户经验帮助类
+ */
+object UserExperienceHelper {
+
+    /**
+     * 根据用户经验获取用户等级
+     */
+    fun getUserRankByExperience(experience: Int): Int {
+        if(experience<=0){
+            return 1
+        }
+        when (experience) {
+            in 0..20 -> {
+                return 1
+            }
+            in 21..60 -> {
+                return 2
+            }
+            in 61..120 -> {
+                return 3
+            }
+            in 121..200 -> {
+                return 4
+            }
+            in 201..400 -> {
+                return 5
+            }
+            in 401..800 -> {
+                return 6
+            }
+            in 801..1500 -> {
+                return 7
+            }
+            in 1501..3000 -> {
+                return 8
+            }
+            in 3001..6000 -> {
+                return 9
+            }
+            in 6001..9000 -> {
+                return 10
+            }
+            in 9001..18000 -> {
+                return 11
+            }
+            in 18001..30000 -> {
+                return 12
+            }
+            in 30001..54000 -> {
+                return 13
+            }
+            in 54001..90000 -> {
+                return 14
+            }
+            in 90001..120000 -> {
+                return 15
+            }
+            in 120001..200000 -> {
+                return 16
+            }
+            in 200001..700000 -> {
+                return 17
+            }
+            else -> {
+                return 18
+            }
+        }
+    }
+
+}

+ 27 - 0
web-core/src/main/kotlin/com/cretin/webcore/mapping/UserExperienceMp.kt

@@ -0,0 +1,27 @@
+package com.cretin.webcore.mapping
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: UserExperienceMapping
+ * Author: cretin
+ * Date: 2021/3/17 5:13 下午
+ * Description: 用户经验值映射表
+ */
+object UserExperienceMp {
+
+    /**
+     * 点赞获取经验值的数量
+     */
+    fun getLikeJokesExperience(): Int {
+        return 2;
+    }
+
+    /**
+     * 踩段子获取经验值的数量
+     */
+    fun getUnlikeJokesExperience(): Int {
+        return 2;
+    }
+
+
+}

+ 101 - 0
web-core/src/main/kotlin/com/cretin/webcore/mapping/UserExperienceTypeMp.kt

@@ -0,0 +1,101 @@
+package com.cretin.webcore.mapping
+
+import com.cretin.webcore.config.AppConstants
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: UserExperienceTypeMp
+ * Author: cretin
+ * Date: 2021/3/17 5:18 下午
+ * Description: 用户获取经验值的类型mapping
+ */
+object UserExperienceTypeMp {
+    private val mapping = mutableMapOf<Int, Pair<Int, String>>()
+
+    //登录成功获取经验值
+    const val EX_TYPE_LOGIN = 1
+
+    //点赞
+    const val EX_TYPE_LIKE = 2
+
+    //踩
+    const val EX_TYPE_UNLIKE = 3
+
+    //关乎
+    const val EX_TYPE_ATTENTION = 4
+
+    //评论
+    const val EX_TYPE_COMMENT = 5
+
+    //子评论
+    const val EX_TYPE_COMMENT_ITEM = 6
+
+    //回复子评论
+    const val EX_TYPE_COMMENT_ITEM_REPLY = 61
+
+    //点赞评论
+    const val EX_TYPE_COMMENT_LIKE = 7
+
+    //绑定邀请码
+    const val EX_TYPE_BIND_CODE_INVITER = 8 //邀请者的奖励
+
+    const val EX_TYPE_BIND_CODE = 9//被邀请者的奖励
+
+    const val EX_TYPE_SIGNIN = 10//签到
+
+    const val EX_TYPE_LOTTERY = 11//抽奖
+
+    const val EX_TYPE_LOTTERY_COST = 12//抽奖消费
+
+    const val EX_TYPE_POST_JOKES = 13//段子审核通过
+
+    const val EX_TYPE_OLD_DATA = 14//数据迁移
+
+    init {
+        mapping[EX_TYPE_LOGIN] = Pair(6, "每日登录奖励")
+        mapping[EX_TYPE_LIKE] = Pair(2, "点赞帖子")
+        mapping[EX_TYPE_UNLIKE] = Pair(2, "踩帖子")
+        mapping[EX_TYPE_ATTENTION] = Pair(2, "关注用户")
+        mapping[EX_TYPE_COMMENT] = Pair(2, "发表评论")
+        mapping[EX_TYPE_COMMENT_ITEM] = Pair(2, "回复评论")
+        mapping[EX_TYPE_COMMENT_ITEM_REPLY] = Pair(2, "回复子评论")
+        mapping[EX_TYPE_COMMENT_LIKE] = Pair(2, "点赞评论")
+        mapping[EX_TYPE_BIND_CODE] = Pair(30, "绑定邀请码")
+        mapping[EX_TYPE_BIND_CODE_INVITER] = Pair(80, "邀请用户奖励")
+        mapping[EX_TYPE_SIGNIN] = Pair(6, "每日签到奖励")
+        mapping[EX_TYPE_LOTTERY] = Pair(-1, "转盘抽奖获奖")
+        mapping[EX_TYPE_LOTTERY_COST] = Pair(AppConstants.LOTTERY_COST, "转盘抽奖消费")
+        mapping[EX_TYPE_POST_JOKES] = Pair(8, "帖子审核通过")
+        mapping[EX_TYPE_OLD_DATA] = Pair(-1, "旧数据迁移")
+    }
+
+    /**
+     * 根据类型和数量获取描述
+     */
+    fun getDescByTypeAndNum(type: Int, num: Int): String {
+        val desc = mapping[type]?.second
+        if(type == EX_TYPE_LOTTERY_COST){
+            return desc?:""
+        }
+        if (num < 0) {
+            return "取消" + desc
+        } else {
+            return desc?:""
+        }
+    }
+
+    fun getNumByType(type: Int): Int {
+        if (mapping.containsKey(type)) {
+            return mapping[type]?.first ?: 0
+        }
+        return 0
+    }
+
+    fun getDescByType(type: Int): String {
+        if (mapping.containsKey(type)) {
+            return mapping[type]?.second ?: ""
+        }
+        return ""
+    }
+
+}

+ 125 - 0
web-core/src/main/kotlin/com/cretin/webcore/mapping/UserMsgTypeMp.kt

@@ -0,0 +1,125 @@
+package com.cretin.webcore.mapping
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: UserMsgTypeMp
+ * Author: cretin
+ * Date: 2021/3/27 2:31 下午
+ * Description: 用户消息类型mp
+ */
+object UserMsgTypeMp {
+
+    /**
+     * 通过经验类型获取消息类型
+     */
+    fun getMsgTypeByExperienceType(experienceType: Int): Int {
+        return when (experienceType) {
+            UserExperienceTypeMp.EX_TYPE_LIKE,
+            UserExperienceTypeMp.EX_TYPE_UNLIKE -> {
+                1
+            }
+            UserExperienceTypeMp.EX_TYPE_COMMENT,
+            UserExperienceTypeMp.EX_TYPE_COMMENT_ITEM_REPLY,
+            UserExperienceTypeMp.EX_TYPE_COMMENT_ITEM -> {
+                2
+            }
+            UserExperienceTypeMp.EX_TYPE_ATTENTION -> {
+                3
+            }
+            else -> {
+                0
+            }
+        }
+    }
+
+    /**
+     * 通过经验类型获取子消息类型
+     */
+    fun getMsgItemTypeByExperienceType(experienceType: Int): Int {
+        return when (experienceType) {
+            UserExperienceTypeMp.EX_TYPE_LIKE -> {
+                10
+            }
+            UserExperienceTypeMp.EX_TYPE_UNLIKE -> {
+                11
+            }
+            UserExperienceTypeMp.EX_TYPE_COMMENT_LIKE -> {
+                12
+            }
+            UserExperienceTypeMp.EX_TYPE_COMMENT -> {
+                20
+            }
+            UserExperienceTypeMp.EX_TYPE_COMMENT_ITEM -> {
+                21
+            }
+            UserExperienceTypeMp.EX_TYPE_COMMENT_ITEM_REPLY -> {
+                22
+            }
+            UserExperienceTypeMp.EX_TYPE_ATTENTION -> {
+                30
+            }
+            else -> {
+                0
+            }
+        }
+    }
+
+    /**
+     * 根据子类型获取子类型的描述
+     *
+     * isCommentList 是否是用户的评论列表
+     */
+    fun getMsgItemTypeDescByType(msgItemType: Int, isCommentList: Boolean = false): String {
+        return when (msgItemType) {
+            10 -> {
+                "赞了你的帖子"
+            }
+            11 -> {
+                "踩了你的帖子"
+            }
+            12 -> {
+                "赞了你的评论"
+            }
+            20 -> {
+                if (isCommentList) {
+                    "你评论了%s的帖子"
+                } else {
+                    "评论了你的帖子"
+                }
+            }
+            21, 22 -> {
+                if (isCommentList) {
+                    "你回复了%s的评论"
+                } else {
+                    "回复了你的评论"
+                }
+            }
+            30 -> {
+                "关注了你"
+            }
+            else -> {
+                ""
+            }
+        }
+    }
+
+    /**
+     * 根据类型获取类型的描述
+     */
+    fun getMsgTypeDescByType(msgMainType: Int): String {
+        return when (msgMainType) {
+            1 -> {
+                "赞 · 踩"
+            }
+            2 -> {
+                "评论"
+            }
+            3 -> {
+                "关注"
+            }
+            else -> {
+                ""
+            }
+        }
+    }
+}

+ 108 - 0
web-core/src/main/kotlin/com/cretin/webcore/oss/OssUrlAuthHelper.kt

@@ -0,0 +1,108 @@
+package com.cretin.webcore.oss
+
+import com.aliyun.oss.HttpMethod
+import com.aliyun.oss.OSS
+import com.aliyun.oss.OSSClientBuilder
+import com.aliyun.oss.model.GeneratePresignedUrlRequest
+import com.cretin.webcore.config.AppConstants
+import com.cretin.webcore.utils.DEStool
+import com.cretin.webcore.utils.StringUtils
+import org.apache.http.util.TextUtils
+import java.util.*
+
+
+object OssUrlAuthHelper {
+
+    private var ossClient: OSS? = null
+
+    init {
+        // Endpoint以杭州为例,其它Region请按实际情况填写。
+        val endpoint = AppConstants.ALIYUN_OSS_ENDPOINT
+        // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。
+        val accessKeyId = AppConstants.ALIYUN_SDK_AK
+        val accessKeySecret = AppConstants.ALIYUN_SDK_SK
+
+        // 创建OSSClient实例。
+        ossClient = OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret)
+    }
+
+    /**
+     * 获取默认的仓库的资源
+     */
+    fun urlGet(objectName: String?, bucketName: String = "jokes-bucket"): String? {
+        val url = urlGet(objectName ?: "", bucketName, Date(Date().getTime() + 24 * 3600 * 1000))
+        if (TextUtils.isEmpty(url)) {
+            return ""
+        }
+        if (url?.startsWith("ftp://") == true) {
+            return url
+        }
+        return "ftp://" + DEStool.encrptText(url)
+    }
+
+    /**
+     * 获取云端完整头像
+     */
+    fun getAvatarUrl(avatar: String?): String {
+        if (avatar?.startsWith("aliyun") == true) {
+            return AppConstants.ALIYUM_AVATAR_HOST + avatar;
+        } else if (avatar?.startsWith("qiniu") == true) {
+            return QiniuAuthHelper.avatarGet(avatar ?: "") ?: ""
+        } else {
+            return avatar ?: ""
+        }
+    }
+
+    /**
+     * 获取默认的仓库的资源
+     */
+    fun urlGet(objectName: String?, bucketName: String = "jokes-bucket", expiration: Date): String? {
+        // 生成以GET方法访问的签名URL,访客可以直接通过浏览器访问相关内容。
+        if (StringUtils.isEmpty(objectName)) {
+            return ""
+        }
+        if (objectName?.startsWith("ftp://") == true) {
+            return objectName
+        }
+        if (objectName?.startsWith("http") == true) {
+            return objectName
+        }
+        if (objectName?.startsWith("qiniu") == true) {
+            return QiniuAuthHelper.urlGet(objectName ?: "")
+        }
+        return ossClient?.generatePresignedUrl(bucketName, objectName, expiration, HttpMethod.GET)?.toString()
+    }
+
+    /**
+     * 获取查询信息的接口
+     */
+    fun infoUrlGet(objectName: String, bucketName: String = "jokes-bucket"): String? {
+        val req = GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.GET)
+        // 设置URL过期时间为1小时。
+        val expiration = Date(Date().getTime() + 24 * 3600 * 1000)
+        req.expiration = expiration
+        req.process = "image/info"
+        return ossClient?.generatePresignedUrl(req)?.toString()
+    }
+
+    /**
+     * 获取缩略图
+     */
+    fun urlThumbnailGet(objectName: String, bucketName: String = "jokes-bucket"): String? {
+        val req = GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.GET)
+        // 设置URL过期时间为1小时。
+        val expiration = Date(Date().getTime() + 24 * 3600 * 1000)
+        req.expiration = expiration
+        req.process = "resize,w_400"
+        return ossClient?.generatePresignedUrl(req)?.toString()
+    }
+
+    /**
+     * 调用不到
+     */
+    fun onDestrpy() {
+        // 关闭OSSClient。
+        ossClient?.shutdown()
+    }
+
+}

+ 109 - 0
web-core/src/main/kotlin/com/cretin/webcore/oss/QiniuAuthHelper.kt

@@ -0,0 +1,109 @@
+package com.cretin.webcore.oss
+
+import com.cretin.webcore.config.AppConstants
+import com.google.gson.Gson
+import com.qiniu.common.QiniuException
+import com.qiniu.http.Response
+import com.qiniu.storage.Configuration
+import com.qiniu.storage.Region
+import com.qiniu.storage.UploadManager
+import com.qiniu.storage.model.DefaultPutRet
+import com.qiniu.util.Auth
+import java.io.File
+
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: QiniuAuthHelper
+ * Author: cretin
+ * Date: 2021/3/30 10:00 上午
+ * Description: 七牛云的helper
+ */
+object QiniuAuthHelper {
+    //本地存储的auth
+    private var auth: Auth? = null
+    private val preHost = "http://jokes-img.cretinzp.com/"
+    private val preHostDev = "http://jokes-pic-dev.cretinzp.com/"
+    private val preAvatarHost = "http://jokes-avatar.cretinzp.com/"
+    private val preAvatarHostDev = "http://jokes-avatar-dev.cretinzp.com/"
+
+    init {
+        val accessKey = AppConstants.QINIU_AK
+        val secretKey = AppConstants.QINIU_SK
+        auth = Auth.create(accessKey, secretKey)
+    }
+
+    /**
+     * 获取token
+     */
+    fun getToken(): String? {
+        val bucket = if (AppConstants.isDebug()) "jokes-pic-dev" else "jokes-pic"
+        return auth?.uploadToken(bucket, null, 3600L, null)
+    }
+
+    /**
+     * 上传文件
+     */
+    fun uploadFile(localFilePath: File) {
+        val bucket = "jokes-avatar"
+        //构造一个带指定 Region 对象的配置类
+        //构造一个带指定 Region 对象的配置类
+        val cfg = Configuration(Region.region2())
+        //...其他参数参考类注释
+        val uploadManager = UploadManager(cfg)
+        //...生成上传凭证,然后准备上传
+        //默认不指定key的情况下,以文件内容的hash值作为文件名
+        val key: String? = "qiniu/jokes/avatar/default/"+localFilePath.name
+        val auth = Auth.create(AppConstants.QINIU_AK, AppConstants.QINIU_SK)
+        val upToken = auth.uploadToken(bucket)
+        try {
+            val response: Response = uploadManager.put(localFilePath, key, upToken)
+            //解析上传成功的结果
+            val putRet: DefaultPutRet = Gson().fromJson(response.bodyString(), DefaultPutRet::class.java)
+            println(putRet.key)
+            println(putRet.hash)
+        } catch (ex: QiniuException) {
+            val r: Response = ex.response
+            System.err.println(r.toString())
+            try {
+                System.err.println(r.bodyString())
+            } catch (ex2: QiniuException) {
+                //ignore
+            }
+        }
+    }
+
+    /**
+     * 获取头像token
+     */
+    fun getAvatarToken(): String? {
+        val bucket = if (AppConstants.isDebug()) "jokes-avatar-dev" else "jokes-avatar"
+        return auth?.uploadToken(bucket, null, 3600L, null)
+    }
+
+    /**
+     * 获取加密之后的连接
+     */
+    fun urlGet(url: String): String? {
+        return auth?.privateDownloadUrl((if (AppConstants.isDebug()) preHostDev else preHost) + url, 3600)
+    }
+
+    /**
+     * 获取头像
+     */
+    fun avatarGet(url: String): String? {
+        return (if (AppConstants.isDebug()) preAvatarHostDev else preAvatarHost) + url
+    }
+
+    @JvmStatic
+    fun main(args: Array<String>) {
+        val map = mutableMapOf<String, String>()
+        map.put("file", "dsdsds")
+        map.put("ok", "ss")
+        val list = mutableListOf<MutableMap<String, String>>()
+        list.add(map)
+
+        val result = list?.map { it["file"] }?.joinToString(separator = ",") { it.toString() }
+        println(result)
+    }
+}

+ 55 - 0
web-core/src/main/kotlin/com/cretin/webcore/redis/RedisHelper.kt

@@ -0,0 +1,55 @@
+package com.cretin.webcore.redis
+
+import com.cretin.webredis.RedisUtils
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+import java.util.concurrent.TimeUnit
+
+@Service
+class RedisHelper {
+
+    @Autowired
+    private var redisUtils: RedisUtils? = null
+
+    /**
+     * 获取图片验证码的值
+     */
+    fun getPicCode(ip: String): String {
+        return redisUtils?.get("translate_pro_cache_pic_code_$ip") ?: ""
+    }
+
+    /**
+     * 设置图片验证码
+     */
+    fun setPicCode(ip: String, code: String) {
+        redisUtils?.setEx("translate_pro_cache_pic_code_$ip", code, 10, TimeUnit.MINUTES)
+    }
+
+    /**
+     * 清理图片验证码
+     */
+    fun clearPicCode(ip: String) {
+        redisUtils?.delete("translate_pro_cache_pic_code_$ip")
+    }
+
+    /**
+     * 设置手机验证码
+     */
+    fun setPhoneCode(phone: String, phoneCode: String) {
+        redisUtils?.setEx("translate_pro_cache_phone_$phone", phoneCode + "#" + System.currentTimeMillis(), 10, TimeUnit.MINUTES)
+    }
+
+    /**
+     * 获取手机验证码
+     */
+    fun getPhoneCode(phone: String): String {
+        return redisUtils?.get("translate_pro_cache_phone_$phone") ?: ""
+    }
+
+    /**
+     * 清理手机验证码
+     */
+    fun clearPhoneCode(phone: String) {
+        redisUtils?.delete("translate_pro_cache_phone_$phone")
+    }
+}

+ 68 - 0
web-core/src/main/kotlin/com/cretin/webcore/sms/SMSHelper.kt

@@ -0,0 +1,68 @@
+package com.cretin.webcore.sms
+
+import com.aliyun.dysmsapi20170525.Client
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest
+import com.aliyun.teaopenapi.models.Config
+import com.cretin.webcore.config.AppConstants
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+
+
+/**
+ * Copyright (C), 2015-2021, 编程猫有限公司
+ * FileName: SMSHelper
+ * Author: cretin
+ * Date: 2021/3/11 9:13 上午
+ * Description:短信工具类
+ */
+@Service
+class SMSHelper {
+
+    @Autowired
+    private var appConstants: AppConstants? = null
+
+    private var client: Client? = null
+
+    /**
+     * 创建客户端
+     */
+    private fun createClient(): Client {
+        val config = Config() // 您的AccessKey ID
+                .setAccessKeyId(AppConstants.ALIYUN_SDK_AK) // 您的AccessKey Secret
+                .setAccessKeySecret(AppConstants.ALIYUN_SDK_SK)
+        // 访问的域名
+        config.endpoint = AppConstants.ALIYUN_SMS_ENDPOINT
+        return Client(config)
+    }
+
+    /**
+     * 发送验证码
+     */
+    fun sendCodeMsg(phone: String, code: String): MutableMap<String, Any> {
+        if (AppConstants.isDebug() == true) {
+            return mutableMapOf<String, Any>(Pair("success", true), Pair("msg", "验证码发送成功"), Pair("logMsg", ""), Pair("logCode", "OK"))
+        }
+        val sendSmsRequest = SendSmsRequest()
+                .setPhoneNumbers(phone)
+                .setSignName(AppConstants.ALIYUN_SMS_CODE_SIGN_NAME_V2)
+                .setTemplateCode(AppConstants.ALIYUN_SMS_CODE_TEMOCODE)
+                .setTemplateParam("{\"code\":\"" + code + "\"}")
+        (client ?: createClient()).let {
+            val sendResp = it.sendSms(sendSmsRequest)
+            if (sendResp.body.code == "OK") {
+                //发送成功
+                return mutableMapOf<String, Any>(Pair("success", true), Pair("msg", "验证码发送成功"), Pair("logMsg", ""), Pair("logCode", "OK"))
+            } else {
+                //发送失败
+                when (sendResp.body.code) {
+                    "BUSINESS_LIMIT_CONTROL" -> {
+                        return mutableMapOf<String, Any>(Pair("success", false), Pair("msg", "验证码获取过快"), Pair("logMsg", sendResp.body.message), Pair("logCode", sendResp.body.code))
+                    }
+                    else -> {
+                        return mutableMapOf<String, Any>(Pair("success", false), Pair("msg", "验证码发送失败"), Pair("logMsg", sendResp.body.message), Pair("logCode", sendResp.body.code))
+                    }
+                }
+            }
+        }
+    }
+}

+ 162 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/CreateNamePicture.java

@@ -0,0 +1,162 @@
+package com.cretin.webcore.utils;
+
+import java.awt.*;
+import java.awt.geom.RoundRectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.imageio.ImageIO;
+/**
+ * 根据姓名创建图片
+ */
+public class CreateNamePicture {
+    /**
+     * @throws IOException
+     * @throws
+     **/
+    public static void main(String[] args) throws IOException {
+        String name = "SDDSD";
+        generateImg(name, "/Users/cretin/Desktop/avatar_temp", name);
+    }
+    /**
+     * 绘制字体头像
+     * 如果是英文名,只显示首字母大写
+     * 如果是中文名,只显示最后两个字
+     * @param name
+     * @param outputPath
+     * @param outputName
+     * @throws IOException
+     */
+    public static void generateImg(String name, String outputPath, String outputName)
+            throws IOException {
+        int width = 100;
+        int height = 100;
+        int nameLen = name.length();
+        String nameWritten;
+        //如果用户输入的姓名少于等于2个字符,不用截取
+        if (nameLen <= 2) {
+            nameWritten = name;
+        } else {
+            //如果用户输入的姓名大于等于3个字符,截取后面两位
+            String first = name.substring(0, 1);
+            if (isChinese(first)) {
+                //截取倒数两位汉字
+                nameWritten = name.substring(nameLen - 2);
+            } else {
+                //截取前面的两个英文字母
+                nameWritten = name.substring(0, 2).toUpperCase();
+            }
+        }
+        String filename = outputPath + File.separator + outputName + ".jpg";
+        File file = new File(filename);
+        //Font font = new Font("微软雅黑", Font.PLAIN, 30);
+        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        Graphics2D g2 = (Graphics2D) bi.getGraphics();
+        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        g2.setBackground(getRandomColor());
+        g2.clearRect(0, 0, width, height);
+        g2.setPaint(Color.WHITE);
+        Font font = null;
+        //两个字及以上
+        if(nameWritten.length() >= 2) {
+            font = new Font("微软雅黑", Font.PLAIN, 30);
+            g2.setFont(font);
+            String firstWritten = nameWritten.substring(0, 1);
+            String secondWritten = nameWritten.substring(1, 2);
+            //两个中文 如 言曌
+            if (isChinese(firstWritten) && isChinese(secondWritten)) {
+                g2.drawString(nameWritten, 20, 60);
+            }
+            //首中次英 如 罗Q
+            else if (isChinese(firstWritten) && !isChinese(secondWritten)) {
+                g2.drawString(nameWritten, 27, 60);
+                //首英,如 AB
+            } else {
+                nameWritten = nameWritten.substring(nameWritten.length()-1);
+            }
+        }
+        //一个字
+        if(nameWritten.length() ==1) {
+            //中文
+            if(isChinese(nameWritten)) {
+                font = new Font("微软雅黑", Font.PLAIN, 50);
+                g2.setFont(font);
+                g2.drawString(nameWritten, 25, 70);
+            }
+            //英文
+            else {
+                font = new Font("微软雅黑", Font.PLAIN, 55);
+                g2.setFont(font);
+                g2.drawString(nameWritten.toUpperCase(), 33, 67);
+            }
+        }
+        BufferedImage rounded = makeRoundedCorner(bi, 99);
+        ImageIO.write(rounded, "png", file);
+    }
+    /**
+     * 判断字符串是否为中文
+     * @param str
+     * @return
+     */
+    public static boolean isChinese(String str) {
+        String regEx = "[\\u4e00-\\u9fa5]+";
+        Pattern p = Pattern.compile(regEx);
+        Matcher m = p.matcher(str);
+        if (m.find())
+            return true;
+        else
+            return false;
+    }
+    /**
+     * 获得随机颜色
+     * @return
+     */
+    private static Color getRandomColor() {
+        String[] beautifulColors =
+                new String[]{"232,221,203", "205,179,128", "3,101,100", "3,54,73", "3,22,52",
+                        "237,222,139", "251,178,23", "96,143,159", "1,77,103", "254,67,101", "252,157,154",
+                        "249,205,173", "200,200,169", "131,175,155", "229,187,129", "161,23,21", "34,8,7",
+                        "118,77,57", "17,63,61", "60,79,57", "95,92,51", "179,214,110", "248,147,29",
+                        "227,160,93", "178,190,126", "114,111,238", "56,13,49", "89,61,67", "250,218,141",
+                        "3,38,58", "179,168,150", "222,125,44", "20,68,106", "130,57,53", "137,190,178",
+                        "201,186,131", "222,211,140", "222,156,83", "23,44,60", "39,72,98", "153,80,84",
+                        "217,104,49", "230,179,61", "174,221,129", "107,194,53", "6,128,67", "38,157,128",
+                        "178,200,187", "69,137,148", "117,121,71", "114,83,52", "87,105,60", "82,75,46",
+                        "171,92,37", "100,107,48", "98,65,24", "54,37,17", "137,157,192", "250,227,113",
+                        "29,131,8", "220,87,18", "29,191,151", "35,235,185", "213,26,33", "160,191,124",
+                        "101,147,74", "64,116,52", "255,150,128", "255,94,72", "38,188,213", "167,220,224",
+                        "1,165,175", "179,214,110", "248,147,29", "230,155,3", "209,73,78", "62,188,202",
+                        "224,160,158", "161,47,47", "0,90,171", "107,194,53", "174,221,129", "6,128,67",
+                        "38,157,128", "201,138,131", "220,162,151", "137,157,192", "175,215,237", "92,167,186",
+                        "255,66,93", "147,224,255", "247,68,97", "185,227,217"};
+        int len = beautifulColors.length;
+        Random random = new Random();
+        String[] color = beautifulColors[random.nextInt(len)].split(",");
+        return new Color(Integer.parseInt(color[0]), Integer.parseInt(color[1]),
+                Integer.parseInt(color[2]));
+    }
+    /**
+     * 图片做圆角处理
+     * @param image
+     * @param cornerRadius
+     * @return
+     */
+    public static BufferedImage makeRoundedCorner(BufferedImage image, int cornerRadius) {
+        int w = image.getWidth();
+        int h = image.getHeight();
+        BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2 = output.createGraphics();
+        g2.setComposite(AlphaComposite.Src);
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g2.setColor(Color.WHITE);
+        g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));
+        g2.setComposite(AlphaComposite.SrcAtop);
+        g2.drawImage(image, 0, 0, null);
+        g2.dispose();
+        return output;
+    }
+}

+ 82 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/DEStool.java

@@ -0,0 +1,82 @@
+package com.cretin.webcore.utils;
+
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+
+public class DEStool {
+
+    private static DEStool deStool;
+
+    private String sKey;
+
+    private DEStool() {
+        //默认构造函数提供默认密钥
+        sKey = "cretin**273846";
+    }
+
+    /**
+     * 加密
+     *
+     * @param text
+     * @return
+     */
+    public static String encrptText(String text) {
+        if (deStool == null) {
+            deStool = new DEStool();
+        }
+        try {
+            return deStool.encrypt(text);
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+    /**
+     * 解密
+     *
+     * @param text
+     * @return
+     */
+    public static String decryptText(String text) {
+        if (deStool == null) {
+            deStool = new DEStool();
+        }
+        try {
+            return deStool.decrypt(text);
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+    private Cipher makeCipher() throws Exception {
+        return Cipher.getInstance("DES");
+    }
+
+    private SecretKey makeKeyFactory() throws Exception {
+        SecretKeyFactory des = SecretKeyFactory.getInstance("DES");
+        SecretKey secretKey = des.generateSecret(new DESKeySpec(sKey.getBytes()));
+        return secretKey;
+    }
+
+    public String encrypt(String text) throws Exception {
+        Cipher cipher = makeCipher();
+        SecretKey secretKey = makeKeyFactory();
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+        return new String(Base64.encodeBase64(cipher.doFinal(text.getBytes())));
+    }
+
+    public String decrypt(String text) throws Exception {
+        Cipher cipher = makeCipher();
+        SecretKey secretKey = makeKeyFactory();
+        cipher.init(Cipher.DECRYPT_MODE, secretKey);
+        return new String(cipher.doFinal(Base64.decodeBase64(text.getBytes())));
+    }
+
+    public static void main(String[] args) {
+        System.out.println(decryptText("6o4pmAeqOKgoVyhFuxgF0N0ySA8QIHQ96F8KBsBVdxcMalL5wN5fnuuoS9dW/FXiq9EjQsoVrlqhZ2umWsDMMtE4SRcAagpv"));
+    }
+}

+ 60 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/ExtendHelper.kt

@@ -0,0 +1,60 @@
+package com.cretin.webcore.utils
+
+import java.time.LocalDateTime
+import java.time.ZoneOffset
+import javax.servlet.http.HttpServletRequest
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: ExtendHelper
+ * Author: cretin
+ * Date: 2021/3/12 8:19 下午
+ * Description: 扩展
+ */
+
+/**
+ * 获取秒
+ */
+fun LocalDateTime.getSeconds(): Long {
+    return this.toEpochSecond(ZoneOffset.of("+8"))
+}
+
+/**
+ * 获取毫秒
+ */
+fun LocalDateTime.getMillis(): Long {
+    return this.toInstant(ZoneOffset.of("+8")).toEpochMilli()
+}
+
+/**
+ * 获取渠道
+ */
+fun HttpServletRequest.getChannel(): String? {
+    return this.getHeader("channel")
+}
+
+/**
+ * 获取唯一id
+ */
+fun HttpServletRequest.getUniqueId(): String? {
+    return this.getHeader("uk")
+}
+
+/**
+ * 获取app信息
+ */
+fun HttpServletRequest.getAppInfo(): String? {
+    return this.getHeader("app") +" "+this.getHeader("device")
+}
+
+/**
+ * 获取当前用户版本号
+ */
+fun HttpServletRequest.getVersionCode(): Int {
+    val appInfos = this.getHeader("app")?.split(";")
+    if (appInfos?.size == 3) {
+        return appInfos[1].toInt()
+    }
+    return -1
+}
+

+ 15 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/GsonGet.kt

@@ -0,0 +1,15 @@
+package com.cretin.webcore.utils
+
+import com.google.gson.Gson
+
+object GsonGet {
+
+    private var gson: Gson = Gson()
+
+    /**
+     * 获取gson
+     */
+    fun getGson(): Gson {
+        return gson
+    }
+}

+ 82 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/IpUtils.java

@@ -0,0 +1,82 @@
+/**
+ * Copyright (C), 2015-2018, XXX有限公司
+ * FileName: IpUtils
+ * Author:   cretin
+ * Date:     11/24/18 23:43
+ * Description: 获取ip的工具类
+ * History:
+ * <author>          <time>          <version>          <desc>
+ * 作者姓名           修改时间           版本号              描述
+ */
+package com.cretin.webcore.utils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+
+/**
+ * 〈获取ip的工具类〉
+ *
+ * @author cretin
+ * @create 11/24/18
+ * @since 1.0.0
+ */
+public class IpUtils {
+    /**
+     * @Description: 获取客户端IP地址
+     */
+    public static String getIpAddr(HttpServletRequest request) {
+        String ip = request.getHeader("x-forwarded-for");
+        if ( ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip) ) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if ( ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip) ) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if ( ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip) ) {
+            ip = request.getHeader("X-Real-IP");
+        }
+        if ( ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip) ) {
+            ip = request.getRemoteAddr();
+            if ( ip.equals("127.0.0.1") ) {
+                //根据网卡取本机配置的IP
+                InetAddress inet = null;
+                try {
+                    inet = InetAddress.getLocalHost();
+                } catch ( Exception e ) {
+                    e.printStackTrace();
+                }
+                ip = inet.getHostAddress();
+            }
+        }
+        // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
+        if ( ip != null && ip.length() > 15 ) {
+            if ( ip.indexOf(",") > 0 ) {
+                ip = ip.substring(0, ip.indexOf(","));
+            }
+        }
+        return ip;
+    }
+
+
+    /**
+     * 判断IP地址的合法性,这里采用了正则表达式的方法来判断
+     * return true,合法
+     */
+    public static boolean ipCheck(String text) {
+        if ( text != null && !text.isEmpty() ) { // 定义正则表达式
+            String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\." +
+                    "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." +
+                    "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." +
+                    "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
+            // 判断ip地址是否与正则表达式匹配
+            if ( text.matches(regex) ) {
+                // 返回判断信息
+                return true;
+            } else {
+                // 返回判断信息
+                return false;
+            }
+        }
+        return false;
+    }
+}

+ 121 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/LotteryUtils.java

@@ -0,0 +1,121 @@
+package com.cretin.webcore.utils;
+
+import java.util.Random;
+
+/**
+ * <p>
+ * Title: LotteryUtils
+ * </p>
+ * <p>
+ * Description: 抽奖工具类
+ * </p>
+ * <p>
+ * Company: www.cretin.com
+ * </p>
+ * 
+ * @author cretin
+ * @date 2017年12月28日
+ */
+public class LotteryUtils {
+	/**
+	 * 获取抽奖结果位置
+	 * 
+	 * @return
+	 */
+	public static Integer lotteryResultPosition() {
+		// * 1 68乐豆 5%
+		// * 2 5QB 0.1%
+		// * 3 10元话费 0.1%
+		// * 4 18乐豆 10%
+		// * 5 谢谢参与 74.8%
+		// * 6 LOL皮肤 0
+		// * 7 38乐豆 10%
+		// * 8 王者皮肤 0
+		float gailv = new Random().nextFloat();
+		if (gailv < 0.05) {
+			return 1;
+		} else if (gailv < 0.0501) {
+			return 2;
+		} else if (gailv < 0.0502) {
+			return 3;
+		} else if (gailv < 0.152) {
+			return 4;
+		} else if (gailv < 0.8) {
+			return 5;
+		} else {
+			return 7;
+		}
+	}
+
+	/**
+	 * 获取抽奖结果描述
+	 * 
+	 * @param position
+	 * @return
+	 */
+	public static String lotteryResultDes(int position) {
+		// * 1 300乐豆 5%
+		// * 2 5QB 0.1%
+		// * 3 10元话费 0.1%
+		// * 4 28乐豆 10%
+		// * 5 谢谢参与 74.8%
+		// * 6 LOL皮肤 0
+		// * 7 168乐豆 10%
+		// * 8 王者皮肤 0
+		switch (position) {
+		case 1:
+			return "恭喜获得68乐豆,乐豆已经到账";
+		case 2:
+			return "恭喜获得5QB,请与客服联系领取";
+		case 3:
+			return "恭喜获得10元话费,请与客服联系领取";
+		case 4:
+			return "恭喜获得18乐豆,乐豆已经到账";
+		case 5:
+			return "再接再厉";
+		case 6:
+			return "恭喜获得LOL皮肤,请与客服联系领取";
+		case 7:
+			return "恭喜获得38乐豆,乐豆已经到账";
+		case 8:
+			return "恭喜获得王者皮肤,请与客服联系领取";
+		}
+		return "感谢您的参与";
+	}
+
+	/**
+	 * 获取抽奖结果简单描述
+	 * 
+	 * @param position
+	 * @return
+	 */
+	public static String lotteryResultEasyDes(int position) {
+		// * 1 300乐豆 5%
+		// * 2 5QB 0.1%
+		// * 3 10元话费 0.1%
+		// * 4 28乐豆 10%
+		// * 5 谢谢参与 74.8%
+		// * 6 LOL皮肤 0
+		// * 7 168乐豆 10%
+		// * 8 王者皮肤 0
+		switch (position) {
+		case 1:
+			return "68乐豆";
+		case 2:
+			return "5QB";
+		case 3:
+			return "10元话费";
+		case 4:
+			return "18乐豆";
+		case 5:
+			return "再接再厉";
+		case 6:
+			return "LOL皮肤";
+		case 7:
+			return "38乐豆";
+		case 8:
+			return "王者皮肤";
+		}
+		return "感谢您的参与";
+	}
+}

+ 73 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/MD5Utils.java

@@ -0,0 +1,73 @@
+package com.cretin.webcore.utils;
+
+import sun.misc.BASE64Encoder;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class MD5Utils {
+
+	/**
+	 * 利用MD5进行加密
+	 *
+	 * @param str 待加密的字符串
+	 * @return 加密后的字符串
+	 * @throws NoSuchAlgorithmException     没有这种产生消息摘要的算法
+	 * @throws UnsupportedEncodingException
+	 */
+	public static String EncoderByMd5(String str) {
+		try {
+			// 确定计算方法
+			MessageDigest md5 = MessageDigest.getInstance("MD5");
+			BASE64Encoder base64en = new BASE64Encoder();
+			// 加密后的字符串
+			return base64en.encode(md5.digest(str.getBytes("utf-8")));
+		} catch ( Exception e ) {
+
+		}
+		return str;
+	}
+
+	/** MD5 */
+	private static final String hexDigIts[] = { "0", "1", "2", "3", "4", "5",
+			"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
+
+	/** MD5加密 */
+	public static String encode(String origin, String charsetname) {
+		String resultString = null;
+		try {
+			resultString = new String(origin);
+			MessageDigest md = MessageDigest.getInstance("MD5");
+			if (null == charsetname || "".equals(charsetname)) {
+				resultString = byteArrayToHexString(md.digest(resultString
+						.getBytes()));
+			} else {
+				resultString = byteArrayToHexString(md.digest(resultString
+						.getBytes(charsetname)));
+			}
+		} catch (Exception e) {
+		}
+		return resultString;
+	}
+
+	public static String byteArrayToHexString(byte b[]) {
+		StringBuffer resultSb = new StringBuffer();
+		for (int i = 0; i < b.length; i++) {
+			resultSb.append(byteToHexString(b[i]));
+		}
+		return resultSb.toString();
+	}
+
+	public static String byteToHexString(byte b) {
+		int n = b;
+		if (n < 0) {
+			n += 256;
+		}
+		int d1 = n / 16;
+		int d2 = n % 16;
+		return hexDigIts[d1] + hexDigIts[d2];
+	}
+
+
+}

+ 229 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/StringUtils.java

@@ -0,0 +1,229 @@
+package com.cretin.webcore.utils;
+
+import sun.misc.BASE64Decoder;
+import sun.misc.BASE64Encoder;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Created by ctetin on 2018/11/21.
+ */
+public class StringUtils {
+    //校验手机是否合规 2020年最全的国内手机号格式
+    private static final String REGEX_MOBILE = "^((13[0-9])|(14(0|[5-7]|9))|(15([0-3]|[5-9]))|(16(2|[5-7]))|(17[0-8])|(18[0-9])|(19([0-3]|[5-9])))\\\\d{8}$\"";
+
+    private static final String FORMAT_RANDOM = "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";
+
+    public static void main(String[] args) {
+        System.out.println(isMobile("13227293721"));
+    }
+
+    /**
+     * 校验手机号
+     *
+     * @param phone 手机号
+     * @return boolean true:是  false:否
+     */
+    public static boolean isMobile(String phone) {
+        if (StringUtils.isEmpty(phone)) {
+            return false;
+        }
+        String regex = "^((13[0-9])|(14(0|[5-7]|9))|(15([0-3]|[5-9]))|(16(2|[5-7]))|(17[0-8])|(18[0-9])|(19([0-3]|[5-9])))\\d{8}$";
+        Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+        Matcher m = p.matcher(phone);
+        return m.matches();
+    }
+
+    /**
+     * 是否为空
+     *
+     * @param text
+     * @return
+     */
+    public static boolean isEmpty(String text) {
+        if (text == null || text.isEmpty()) {
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean isDeepEmpty(String text) {
+        if (text == null)
+            return true;
+        return isEmpty(text.trim());
+    }
+
+    //加密
+    public static String getBase64(String str) {
+        byte[] b = null;
+        String s = null;
+        try {
+            b = str.getBytes("utf-8");
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        }
+        if (b != null) {
+            s = new BASE64Encoder().encode(b);
+        }
+        return s;
+    }
+
+    //判断Email合法性
+    public static boolean isEmail(String email) {
+        if (email == null)
+            return false;
+        String rule = "[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?";
+        Pattern pattern;
+        Matcher matcher;
+        pattern = Pattern.compile(rule);
+        matcher = pattern.matcher(email);
+        if (matcher.matches())
+            return true;
+        else
+            return false;
+    }
+
+    /**
+     * 格式化数字
+     *
+     * @param num
+     * @return
+     */
+    public static String formatNum(int num) {
+        if (num < 10) {
+            return "0" + num;
+        }
+        return num + "";
+    }
+
+    /**
+     * 是否所有的都是空字符串
+     *
+     * @param args
+     * @return
+     */
+    public static boolean isAllEmpty(String... args) {
+        for (String arg : args) {
+            if (StringUtils.isEmpty(arg)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 转码成Base64
+     *
+     * @param content
+     * @return
+     */
+    public static String encodeToBase64(String content) {
+        return new BASE64Encoder().encode(content.getBytes());
+    }
+
+    /**
+     * Base64位解码
+     *
+     * @param content
+     * @return
+     */
+    public static String decodeToBase64(String content) {
+        BASE64Decoder decoder = new BASE64Decoder();
+        try {
+            return new String(decoder.decodeBuffer(content));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
+    /**
+     * 格式化手机号
+     *
+     * @param telephone
+     * @return
+     */
+    public static String formatPhone(String telephone) {
+        if (telephone == null || telephone.isEmpty()) {
+            return "";
+        }
+        StringBuilder stringBuilder = new StringBuilder();
+        for (int i = 0; i < telephone.toCharArray().length; i++) {
+            if (i < 3 || i > telephone.length() - 5) {
+                stringBuilder.append(telephone.charAt(i));
+            } else
+                stringBuilder.append("*");
+        }
+        return stringBuilder.toString();
+    }
+
+    /**
+     * 获取最大值max以内的不重复size个数字
+     *
+     * @param max
+     * @param size
+     * @return
+     */
+    public static List<Integer> getRandomIntegerList(int max, int size) {
+        List<Integer> list = new ArrayList<>();
+        Random random = new Random();
+        int index = 0;
+        while (true) {
+            int randomIndex = random.nextInt(max) + 1;
+            if (!list.contains(randomIndex)) {
+                list.add(randomIndex);
+                index++;
+            }
+            if (index >= size) {
+                break;
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 编码id
+     *
+     * @return
+     */
+    public static String encodeId(int id) {
+        return getRandomStr(10) + id + getRandomStr(10);
+    }
+
+    private static String getRandomStr(int num) {
+        StringBuilder stringBuilder = new StringBuilder();
+        Random random = new Random();
+        while (stringBuilder.length() < num) {
+            stringBuilder.append(FORMAT_RANDOM.charAt(random.nextInt(FORMAT_RANDOM.length())));
+        }
+        return stringBuilder.toString();
+    }
+
+    public static String getRandomNumStr(int num) {
+        StringBuilder stringBuilder = new StringBuilder();
+        Random random = new Random();
+        for (int i = 0; i < num; i++) {
+            stringBuilder.append(random.nextInt(10));
+        }
+        return stringBuilder.toString();
+    }
+
+    /**
+     * 解码id
+     *
+     * @return
+     */
+    public static int decodeId(String str) {
+        try {
+            return Integer.parseInt(str.substring(10, str.length() - 10));
+        } catch (Exception e) {
+            return -1;
+        }
+    }
+}

+ 227 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/TimeUtils.kt

@@ -0,0 +1,227 @@
+package com.cretin.webcore.utils
+
+import org.joda.time.DateTime
+import org.joda.time.Duration
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * author: deemons
+ * date:    2018/4/24
+ * desc:    此类是对 Joda Time 的扩展
+ * Joda Time 使用参考:https://www.ibm.com/developerworks/cn/java/j-jodatime.html#artdownload
+ * http://ylq365.iteye.com/blog/1769680
+ * Joda Time 本身非常强大,此处只是针对本项目做了一下扩展
+ */
+object TimeUtils {
+
+    private val CALENDAR_OF_INSTANCE = Calendar.getInstance()
+    private const val SECONDS_OF_1MINUTE = 60
+    private const val SECONDS_OF_1HOUR = 60 * 60
+    private const val SECONDS_OF_1DAY = 24 * 60 * 60
+    private var SECONDS_OF_2DAY = SECONDS_OF_1DAY + CALENDAR_OF_INSTANCE.get(Calendar.HOUR) * SECONDS_OF_1HOUR +
+            CALENDAR_OF_INSTANCE.get(Calendar.MINUTE) * SECONDS_OF_1MINUTE + CALENDAR_OF_INSTANCE.get(Calendar.SECOND)
+
+    /**
+     * 友好显示时间,包含国际化
+     */
+    fun convert(time: Long): String {
+        var long: Long = time
+        if (long.toString().length <= 10) {
+            long = time * 1000
+        }
+        val duration = Duration(long, System.currentTimeMillis())
+        val durationSec = duration.standardSeconds.toInt()
+
+        val now = DateTime()
+        val startOfToday = DateTime(now.year, now.monthOfYear, now.dayOfMonth, 0, 0, 0)
+        val endOfToday = startOfToday.plusDays(1)
+        val startOfYesterday = startOfToday.minusDays(1)
+
+        val date = Date(long)
+
+        return when {
+            durationSec < SECONDS_OF_1MINUTE -> "刚刚"
+            durationSec < SECONDS_OF_1HOUR -> "${duration.standardMinutes.toInt()}分钟前"
+            date.after(startOfToday.toDate()) && date.before(endOfToday.toDate()) -> "${duration.standardHours.toInt()}小时前"
+            date.after(startOfYesterday.toDate()) && date.before(startOfToday.toDate()) -> "昨天 ${millis2String(time, "HH:mm")}"
+            else -> {
+                val dateYear = DateTime(long)
+                //一年内 显示时间
+                return if (DateTime.now().year == dateYear.year) {
+                    millis2String(long, "MM/dd HH:mm")
+                    //一年外
+                } else {
+                    millis2String(long, "yyyy/MM/dd HH:mm")
+                }
+            }
+        }
+    }
+
+    /**
+     * 将注册时间转化成入驻时间
+     */
+    fun formatRegisterTimeToTimeLength(time: Long): String {
+        var long: Long = time
+        if (long.toString().length <= 10) {
+            long = time * 1000
+        }
+        val duration = Duration(long, System.currentTimeMillis())
+        val durationSec = duration.standardSeconds.toInt()
+
+        val now = DateTime()
+        val startOfToday = DateTime(now.year, now.monthOfYear, now.dayOfMonth, 0, 0, 0)
+        val endOfToday = startOfToday.plusDays(1)
+        val startOfYesterday = startOfToday.minusDays(1)
+
+        val date = Date(long)
+
+        return when {
+            durationSec < SECONDS_OF_1MINUTE -> "刚刚"
+            durationSec < SECONDS_OF_1HOUR -> "${duration.standardMinutes.toInt()}分钟前"
+            date.after(startOfToday.toDate()) && date.before(endOfToday.toDate()) -> "${duration.standardHours.toInt()}小时前"
+            date.after(startOfYesterday.toDate()) && date.before(startOfToday.toDate()) -> "昨天"
+            durationSec < 30 * 24 * 3600 -> "${durationSec / (24 * 3600)}天"
+            durationSec < 365 * 24 * 3600 -> "${durationSec / (30 * 24 * 3600)}个月"
+            else -> {
+                "${durationSec / (365 * 24 * 3600)}年" + "${if (durationSec % (365 * 24 * 3600) / (30 * 24 * 3600) == 0) 1 else durationSec % (365 * 24 * 3600) / (30 * 24 * 3600)}个月"
+            }
+        }
+    }
+
+    /**
+     * 显示日期,不包含时间
+     */
+    fun convertWithoutTime(time: Long): String {
+        var long: Long = time
+        if (long.toString().length <= 10) {
+            long = time * 1000
+        }
+        val duration = Duration(long, System.currentTimeMillis())
+        val durationSec = duration.standardSeconds.toInt()
+
+        val now = DateTime()
+        val startOfToday = DateTime(now.year, now.monthOfYear, now.dayOfMonth, 0, 0, 0)
+        val endOfToday = startOfToday.plusDays(1)
+        val startOfYesterday = startOfToday.minusDays(1)
+        val date = Date(long)
+
+        return when {
+            durationSec < SECONDS_OF_1MINUTE -> "刚刚"
+            durationSec < SECONDS_OF_1HOUR -> "${duration.standardMinutes.toInt()}分钟前"
+            date.after(startOfToday.toDate()) && date.before(endOfToday.toDate()) -> "${duration.standardHours.toInt()}小时前"
+            date.after(startOfYesterday.toDate()) && date.before(startOfToday.toDate()) -> "月"
+            else -> {
+                val dateYear = DateTime(long)
+                //一年内
+                return if (DateTime.now().year == dateYear.year) {
+                    millis2String(long, "MM/dd HH:mm")
+                    //一年外
+                } else {
+                    millis2String(long, "yyyy/MM/dd HH:mm")
+                }
+            }
+        }
+    }
+
+    fun millis2String(millis: Long, pattern: String): String {
+        return millis2String(millis, pattern, Locale.getDefault())
+    }
+
+    /**
+     * 将时间戳转为时间字符串
+     * @param millis 毫秒时间戳
+     * @return 时间字符串
+     */
+    private fun millis2String(millis: Long, pattern: String, locale: Locale): String {
+        return SimpleDateFormat(pattern, locale).format(Date(millis))
+    }
+
+    /**
+     * 是否在七天内
+     * 传入比对的时间
+     */
+    fun isSevenDays(time: Long): Boolean {
+        val duration = Duration(time, System.currentTimeMillis())
+        val durationSec = duration.standardSeconds.toInt()
+        return durationSec < SECONDS_OF_1DAY * 7
+    }
+
+    /**
+     * 获取本周的时间起始时间和终止时间 每一周从周一开始计算到周日 返回的是两个时间节点 单位秒
+     */
+    fun thisWeekTimeRangeSecond(): LongArray {
+        val now = DateTime.now()
+        var todayMorning = DateTime(now.year, now.monthOfYear, now.dayOfMonth, 0, 0, 0)
+        var index = now.dayOfWeek - 1
+        val beforeDay = todayMorning.minusDays(index)
+        return longArrayOf(beforeDay.millis / 1000, now.millis / 1000)
+    }
+
+    /**
+     * 获取上周的时间起始时间和终止时间 每一周从周一开始计算到周日 返回的是两个时间节点 单位秒
+     */
+    fun lastWeekTimeRangeSecond(): LongArray {
+        val now = DateTime.now().minusWeeks(1)
+        var todayMorning = DateTime(now.year, now.monthOfYear, now.dayOfMonth, 0, 0, 0)
+        var index = now.dayOfWeek - 1
+        val beforeDay = todayMorning.minusDays(index)
+        val afterDays = beforeDay.plusDays(7)
+        return longArrayOf(beforeDay.millis / 1000, afterDays.millis / 1000 - 1)
+    }
+
+    /**
+     * 获取本月的时间起始时间和终止时间 返回的是两个时间节点 单位秒
+     */
+    fun thisMonthTimeRangeSecond(): LongArray {
+        val now = DateTime.now()
+        var todayMorning = DateTime(now.year, now.monthOfYear, now.dayOfMonth, 0, 0, 0)
+        var index = now.dayOfMonth - 1
+        val beforeDay = todayMorning.minusDays(index)
+        return longArrayOf(beforeDay.millis / 1000, now.millis / 1000)
+    }
+
+    /**
+     * 获取本月的时间起始时间和终止时间 返回的是两个时间节点 单位秒
+     */
+    fun lastMonthTimeRangeSecond(): LongArray {
+        val now = DateTime.now().minusMonths(1)
+        var lastMonthFirstDay = DateTime(now.year, now.monthOfYear, 1, 0, 0, 0)
+        return longArrayOf(lastMonthFirstDay.millis / 1000, lastMonthFirstDay.plusMonths(1).millis / 1000 - 1)
+    }
+
+    /**
+     * 获取今日的时间起始时间和终止时间 返回的是两个时间节点 单位秒
+     */
+    fun todayTimeRangeSecond(): LongArray {
+        val now = DateTime.now()
+        var todayMorning = DateTime(now.year, now.monthOfYear, now.dayOfMonth, 0, 0, 0)
+        return longArrayOf(todayMorning.millis / 1000, now.millis / 1000)
+    }
+
+    /**
+     * 获取昨日的时间起始时间和终止时间 返回的是两个时间节点 单位秒
+     */
+    fun yesterdayTimeRangeSecond(): LongArray {
+        val now = DateTime.now()
+        var todayMorning = DateTime(now.year, now.monthOfYear, now.dayOfMonth, 0, 0, 0)
+        return longArrayOf(todayMorning.minusDays(1).millis / 1000, todayMorning.millis / 1000 - 1)
+    }
+
+    @JvmStatic
+    fun main(args: Array<String>) {
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusSeconds(10).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusHours(1).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusDays(1).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusWeeks(1).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusMonths(1).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusDays(50).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusDays(300).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusDays(400).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusDays(500).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusDays(600).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusDays(700).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusDays(800).millis))
+        println(formatRegisterTimeToTimeLength(DateTime.now().minusDays(365 * 3).millis))
+    }
+}

+ 41 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/UUIDUtils.java

@@ -0,0 +1,41 @@
+package com.cretin.webcore.utils;
+
+import java.util.UUID;
+
+public class UUIDUtils {
+    /**
+     * 生成一个32位的不带-的不唯一的uuid
+     *
+     * @return
+     */
+    public static String getUuid() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+    public static String[] chars = new String[]{"a", "b", "c", "d", "e", "f",
+            "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
+            "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
+            "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
+            "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
+            "W", "X", "Y", "Z"};
+
+
+    /**
+     * 获取16位的UUID
+     *
+     * @return
+     */
+    public static String getShortUuid() {
+        int length = 16;
+        StringBuffer shortBuffer = new StringBuffer();
+        String uuid = UUID.randomUUID().toString().replace("-", "");
+//        System.out.println(uuid);
+        int index = 32 / length;
+        for ( int i = 0; i < length; i++ ) {
+            String str = uuid.substring(i * index, i * index + index);
+            int x = Integer.parseInt(str, length * 2);
+            shortBuffer.append(chars[x % 0x3E]);
+        }
+        return shortBuffer.toString().toLowerCase();
+    }
+}

+ 24 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/UserAuthHelper.kt

@@ -0,0 +1,24 @@
+package com.cretin.webcore.utils
+
+import com.cretin.webcore.exception.WholeException
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: UserAuthHelper
+ * Author: cretin
+ * Date: 2021/4/3 10:20 下午
+ * Description: 验证用户身份
+ */
+object UserAuthHelper {
+
+    /**
+     * 检查用户的状态
+     */
+    fun checkUser(status: Int) {
+        if (status == 1) {
+            throw WholeException(203, "此账号已被封禁")
+        } else if (status == 2) {
+            throw WholeException(204, "此账户已被注销")
+        }
+    }
+}

+ 49 - 0
web-core/src/main/kotlin/com/cretin/webcore/utils/VerificationCodeHelper.kt

@@ -0,0 +1,49 @@
+package com.cretin.webcore.utils
+
+import java.lang.StringBuilder
+import kotlin.random.Random
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: VerificationCodeHelper
+ * Author: cretin
+ * Date: 2021/3/11 10:39 上午
+ * Description: 验证码生成帮助类
+ */
+object VerificationCodeHelper {
+    private var cahrsWithNum = "abcdefghijklmnopqrstuvwxyz0123456789"
+    private var cahrs = "abcdefghijklmnopqrstuvwxyz"
+
+    /**
+     * 生成指定位数的随机数字验证码
+     */
+    fun createNumCode(num: Int = 6): String {
+        val code = StringBuilder()
+        for (i in 0 until num) {
+            code.append(Random.nextInt(10))
+        }
+        return code.toString()
+    }
+
+    /**
+     * 创建指定数量的字母加数字
+     */
+    fun createCharNumCode(num: Int = 6): String {
+        val code = StringBuilder()
+        for (i in 0 until 6) {
+            code.append(cahrsWithNum[Random.nextInt(cahrsWithNum.length)])
+        }
+        return code.toString().toUpperCase()
+    }
+
+    /**
+     * 创建指定数量的字母
+     */
+    fun createCharCode(num: Int = 6): String {
+        val code = StringBuilder()
+        for (i in 0 until 6) {
+            code.append(cahrs[Random.nextInt(cahrs.length)])
+        }
+        return code.toString().toUpperCase()
+    }
+}

+ 1 - 0
web-core/src/main/resources/application.properties

@@ -0,0 +1 @@
+

+ 33 - 0
web-db/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### 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/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 118 - 0
web-db/.mvn/wrapper/MavenWrapperDownloader.java

@@ -0,0 +1,118 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+    private static final String WRAPPER_VERSION = "0.5.6";
+    /**
+     * 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/"
+            + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".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 directory '" + 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 {
+        if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+            String username = System.getenv("MVNW_USERNAME");
+            char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+            Authenticator.setDefault(new Authenticator() {
+                @Override
+                protected PasswordAuthentication getPasswordAuthentication() {
+                    return new PasswordAuthentication(username, password);
+                }
+            });
+        }
+        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
web-db/.mvn/wrapper/maven-wrapper.jar


+ 2 - 0
web-db/.mvn/wrapper/maven-wrapper.properties

@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

+ 310 - 0
web-db/mvnw

@@ -0,0 +1,310 @@
+#!/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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven 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)`"
+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
+    if [ -n "$MVNW_REPOURL" ]; then
+      jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    else
+      jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    fi
+    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 $cygwin; then
+      wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+    fi
+
+    if command -v wget > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found wget ... using wget"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget "$jarUrl" -O "$wrapperJarPath"
+        else
+            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Found curl ... using curl"
+        fi
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl -o "$wrapperJarPath" "$jarUrl" -f
+        else
+            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+        fi
+
+    else
+        if [ "$MVNW_VERBOSE" = true ]; then
+          echo "Falling back to using Java to download"
+        fi
+        javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaClass=`cygpath --path --windows "$javaClass"`
+        fi
+        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
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+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 "$@"

+ 182 - 0
web-db/mvnw.cmd

@@ -0,0 +1,182 @@
+@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 Maven 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 keystroke 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 by 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.5.6/maven-wrapper-0.5.6.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% (
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Found %WRAPPER_JAR%
+    )
+) else (
+    if not "%MVNW_REPOURL%" == "" (
+        SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+    )
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Couldn't find %WRAPPER_JAR%, downloading it ...
+        echo Downloading from: %DOWNLOAD_URL%
+    )
+
+    powershell -Command "&{"^
+		"$webclient = new-object System.Net.WebClient;"^
+		"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+		"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+		"}"^
+		"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+		"}"
+    if "%MVNW_VERBOSE%" == "true" (
+        echo Finished downloading %WRAPPER_JAR%
+    )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%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%

+ 57 - 0
web-db/pom.xml

@@ -0,0 +1,57 @@
+<?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>com.cretin</groupId>
+        <artifactId>webroot</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
+    </parent>
+    <artifactId>web-db</artifactId>
+    <dependencies>
+        <!--mybatis插件-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.4.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+            <version>3.4.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+            <version>2.3.28</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <!--mysql start-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>1.1.21</version>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.cretin</groupId>
+            <artifactId>web-entity</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.cretin</groupId>
+            <artifactId>web-core</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 20 - 0
web-db/src/main/kotlin/com/cretin/webdb/controller/TbGlobalConfigController.java

@@ -0,0 +1,20 @@
+package com.cretin.webdb.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 全局配置表 前端控制器
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-06
+ */
+@RestController
+@RequestMapping("/webdb/tb-global-config")
+public class TbGlobalConfigController {
+
+}

+ 20 - 0
web-db/src/main/kotlin/com/cretin/webdb/controller/TbStrTranslateItemController.java

@@ -0,0 +1,20 @@
+package com.cretin.webdb.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * APP国际化翻译表 前端控制器
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-06
+ */
+@RestController
+@RequestMapping("/webdb/tb-str-translate-item")
+public class TbStrTranslateItemController {
+
+}

+ 20 - 0
web-db/src/main/kotlin/com/cretin/webdb/controller/TbStrTranslateLogController.java

@@ -0,0 +1,20 @@
+package com.cretin.webdb.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * APP国际化翻译表 操作日志 前端控制器
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-06
+ */
+@RestController
+@RequestMapping("/webdb/tb-str-translate-log")
+public class TbStrTranslateLogController {
+
+}

+ 20 - 0
web-db/src/main/kotlin/com/cretin/webdb/controller/TbStrTranslateModuleController.java

@@ -0,0 +1,20 @@
+package com.cretin.webdb.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * APP国际化翻译 模块对应表 前端控制器
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-06
+ */
+@RestController
+@RequestMapping("/webdb/tb-str-translate-module")
+public class TbStrTranslateModuleController {
+
+}

+ 20 - 0
web-db/src/main/kotlin/com/cretin/webdb/controller/TbStrTranslateUserController.java

@@ -0,0 +1,20 @@
+package com.cretin.webdb.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * APP国际化翻译用户表 前端控制器
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-06
+ */
+@RestController
+@RequestMapping("/webdb/tb-str-translate-user")
+public class TbStrTranslateUserController {
+
+}

+ 20 - 0
web-db/src/main/kotlin/com/cretin/webdb/controller/TbV2HandleLogController.java

@@ -0,0 +1,20 @@
+package com.cretin.webdb.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 用户操作日志 前端控制器
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-06
+ */
+@RestController
+@RequestMapping("/webdb/tb-v2-handle-log")
+public class TbV2HandleLogController {
+
+}

+ 20 - 0
web-db/src/main/kotlin/com/cretin/webdb/controller/TbV2StrTranslateItemController.java

@@ -0,0 +1,20 @@
+package com.cretin.webdb.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * APP国际化翻译表 前端控制器
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-06
+ */
+@RestController
+@RequestMapping("/webdb/tb-v2-str-translate-item")
+public class TbV2StrTranslateItemController {
+
+}

+ 20 - 0
web-db/src/main/kotlin/com/cretin/webdb/controller/TbV2StrTranslateProjectController.java

@@ -0,0 +1,20 @@
+package com.cretin.webdb.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * APP国际化翻译项目配置表 前端控制器
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-06
+ */
+@RestController
+@RequestMapping("/webdb/tb-v2-str-translate-project")
+public class TbV2StrTranslateProjectController {
+
+}

+ 65 - 0
web-db/src/main/kotlin/com/cretin/webdb/entity/TbGlobalConfig.java

@@ -0,0 +1,65 @@
+package com.cretin.webdb.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 全局配置表
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class TbGlobalConfig implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 配置标题
+     */
+    private String configTitle;
+
+    /**
+     * 配置描述
+     */
+    private String configDesc;
+
+    /**
+     * 附加配置1
+     */
+    private String configExtra1;
+
+    /**
+     * 附加配置2
+     */
+    private String configExtra2;
+
+    /**
+     * 附加配置3
+     */
+    private String configExtra3;
+
+    /**
+     * 附加配置4
+     */
+    private String configExtra4;
+
+    private LocalDateTime updateTime;
+
+    /**
+     * 0 翻译工具jar 1 jdk 下载工具地址
+     */
+    private Integer type;
+
+
+}

+ 92 - 0
web-db/src/main/kotlin/com/cretin/webdb/entity/TbStrTranslateItem.java

@@ -0,0 +1,92 @@
+package com.cretin.webdb.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * APP国际化翻译表
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class TbStrTranslateItem implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * Android key
+     */
+    private String androidKey;
+
+    /**
+     * Android 包名 用于区分字符串来源
+     */
+    private String androidPackage;
+
+    /**
+     * Android 源内容
+     */
+    private String androidSource;
+
+    /**
+     * iOS key
+     */
+    private String iosKey;
+
+    /**
+     * iOS 包名 用于区分字符串来源
+     */
+    private String iosPackage;
+
+    /**
+     * iOS 源内容
+     */
+    private String iosSource;
+
+    /**
+     * 翻译前的源中文内容
+     */
+    private String translateSource;
+
+    /**
+     * 翻译后的英文内容
+     */
+    private String translateEn;
+
+    /**
+     * 翻译后的繁体内容
+     */
+    private String translateTw;
+
+    private LocalDateTime addTime;
+
+    private LocalDateTime updateTime;
+
+    /**
+     * 合并状态 1 正常 0 有冲突
+     */
+    private Integer mergeStatus;
+
+    /**
+     * app_id 字符串 不是数字
+     */
+    private String appId;
+
+    /**
+     * 确认状态 0 未确定 1 已确定
+     */
+    private Integer confirmStatus;
+
+
+}

+ 45 - 0
web-db/src/main/kotlin/com/cretin/webdb/entity/TbStrTranslateLog.java

@@ -0,0 +1,45 @@
+package com.cretin.webdb.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * APP国际化翻译表 操作日志
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class TbStrTranslateLog implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 操作者
+     */
+    private String translateUser;
+
+    /**
+     * 翻译内容
+     */
+    private String translateEn;
+
+    /**
+     * 翻译条目id
+     */
+    private Integer translateId;
+
+    private LocalDateTime updateTime;
+
+
+}

+ 42 - 0
web-db/src/main/kotlin/com/cretin/webdb/entity/TbStrTranslateModule.java

@@ -0,0 +1,42 @@
+package com.cretin.webdb.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * APP国际化翻译 模块对应表
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-10-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class TbStrTranslateModule implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 路径值
+     */
+    private String path;
+
+    /**
+     * 路径标签
+     */
+    private String pathLabel;
+
+    private String appId;
+
+    private LocalDateTime addTime;
+
+
+}

+ 55 - 0
web-db/src/main/kotlin/com/cretin/webdb/entity/TbStrTranslateUser.java

@@ -0,0 +1,55 @@
+package com.cretin.webdb.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * APP国际化翻译用户表
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class TbStrTranslateUser implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 用户账号
+     */
+    private String username;
+
+    /**
+     * 用户密码
+     */
+    private String password;
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 状态  0 正常 1 不可用
+     */
+    private Integer status;
+
+    /**
+     * 用户token
+     */
+    private String token;
+
+    private LocalDateTime updateTime;
+
+
+}

+ 45 - 0
web-db/src/main/kotlin/com/cretin/webdb/entity/TbV2HandleLog.java

@@ -0,0 +1,45 @@
+package com.cretin.webdb.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 用户操作日志
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-06
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class TbV2HandleLog implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 项目所属用户id
+     */
+    private Integer userId;
+
+    /**
+     * 操作的url
+     */
+    private String requestUrl;
+
+    /**
+     * ip
+     */
+    private String ip;
+
+    private LocalDateTime addTime;
+
+
+}

+ 62 - 0
web-db/src/main/kotlin/com/cretin/webdb/entity/TbV2StrTranslateItem.java

@@ -0,0 +1,62 @@
+package com.cretin.webdb.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * APP国际化翻译表
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class TbV2StrTranslateItem implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * key
+     */
+    private String itemKey;
+
+    /**
+     * 内容
+     */
+    private String itemSource;
+
+    /**
+     * 渠道内容 比如 values
+     */
+    private String itemChannel;
+
+    private LocalDateTime addTime;
+
+    private LocalDateTime updateTime;
+
+    /**
+     * app_id
+     */
+    private Integer appId;
+
+    /**
+     * 内容所处模块
+     */
+    private String itemModule;
+
+    /**
+     * 是否可用
+     */
+    private Boolean deleteFlag;
+
+
+}

+ 89 - 0
web-db/src/main/kotlin/com/cretin/webdb/entity/TbV2StrTranslateProject.java

@@ -0,0 +1,89 @@
+package com.cretin.webdb.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * APP国际化翻译项目配置表
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-19
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class TbV2StrTranslateProject implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 项目id
+     */
+    private String projectName;
+
+    /**
+     * 项目配置 json
+     */
+    private String projectLocationConfig;
+
+    /**
+     * 渠道配置 字符串数组
+     */
+    private String channelConfig;
+
+    /**
+     * 主要渠道
+     */
+    private String projectChannel;
+
+    /**
+     * 项目所属用户id
+     */
+    private Integer userId;
+
+    private LocalDateTime addTime;
+
+    private LocalDateTime updateTime;
+
+    /**
+     * 被隐藏的渠道 字符串数组
+     */
+    private String channelHideConfig;
+
+    /**
+     * 描述
+     */
+    private String shortDesc;
+
+    /**
+     * 渠道名称配置
+     */
+    private String channelConfigName;
+
+    /**
+     * 平台
+     */
+    private String projectPlatform;
+
+    /**
+     * 项目备份 json
+     */
+    private String projectBackupConfig;
+
+    /**
+     * 项目模块 字符串数组
+     */
+    private String projectModuleConfig;
+
+    private LocalDateTime lastUploadTime;
+
+
+}

+ 41 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/CustomHomeService.kt

@@ -0,0 +1,41 @@
+package com.cretin.webdb.mapper
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper
+import org.apache.ibatis.annotations.Param
+import org.apache.ibatis.annotations.Select
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: CustomUserService
+ * Author: cretin
+ * Date: 2021/3/17 6:23 下午
+ * Description: 自定义的用户service
+ */
+interface CustomHomeService : BaseMapper<Any> {
+    /**
+     * 获取翻译的module
+     */
+    @Select("select ios_package,android_package from tb_str_translate_item where app_id = #{appId} group by ios_package,android_package")
+    fun getTranslateModules(@Param("appId") appId: String): MutableList<MutableMap<String, Any?>>?
+
+    /**
+     * 获取已经有翻译的key
+     */
+//    @Select("select count(item_key),item_key from tb_v2_str_translate_item where item_channel in (#{channels}) and app_id = #{appId} group by item_key")
+    @Select(
+        "<script> select count(item_key) as cnum,item_key from tb_v2_str_translate_item where item_channel in " +
+                "<foreach collection='channels' item='channel' open='(' separator=',' close=')'>" +
+                "#{channel}" +
+                "</foreach>" +
+                " and app_id = #{appId} group by item_key" +
+                "</script> "
+    )
+    fun getTranslatedKey(@Param("appId") appId:Int,@Param("channels") channels: MutableList<String>): MutableList<MutableMap<String, Any?>>?
+
+    /**
+     * 获取指定项目的所有key
+     */
+    @Select("select item_key from tb_v2_str_translate_item where app_id = #{appId} group by item_key")
+    fun getAllKeys(@Param("appId") appId:Int): MutableList<MutableMap<String, Any?>>?
+
+}

+ 15 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/CustomRecommendService.kt

@@ -0,0 +1,15 @@
+package com.cretin.webdb.mapper
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: CustomUserService
+ * Author: cretin
+ * Date: 2021/3/17 6:23 下午
+ * Description: 自定义的推荐service
+ */
+interface CustomRecommendService : BaseMapper<Any> {
+
+
+}

+ 14 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/CustomUserService.kt

@@ -0,0 +1,14 @@
+package com.cretin.webdb.mapper
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper
+
+/**
+ * Copyright (C), 2015-2021, 电点科技有限公司
+ * FileName: CustomUserService
+ * Author: cretin
+ * Date: 2021/3/17 6:23 下午
+ * Description: 自定义的用户service
+ */
+interface CustomUserService : BaseMapper<Any> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/TbGlobalConfigMapper.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.mapper;
+
+import com.cretin.webdb.entity.TbGlobalConfig;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 全局配置表 Mapper 接口
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+public interface TbGlobalConfigMapper extends BaseMapper<TbGlobalConfig> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/TbStrTranslateItemMapper.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.mapper;
+
+import com.cretin.webdb.entity.TbStrTranslateItem;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * APP国际化翻译表 Mapper 接口
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+public interface TbStrTranslateItemMapper extends BaseMapper<TbStrTranslateItem> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/TbStrTranslateLogMapper.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.mapper;
+
+import com.cretin.webdb.entity.TbStrTranslateLog;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * APP国际化翻译表 操作日志 Mapper 接口
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+public interface TbStrTranslateLogMapper extends BaseMapper<TbStrTranslateLog> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/TbStrTranslateModuleMapper.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.mapper;
+
+import com.cretin.webdb.entity.TbStrTranslateModule;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * APP国际化翻译 模块对应表 Mapper 接口
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-10-15
+ */
+public interface TbStrTranslateModuleMapper extends BaseMapper<TbStrTranslateModule> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/TbStrTranslateUserMapper.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.mapper;
+
+import com.cretin.webdb.entity.TbStrTranslateUser;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * APP国际化翻译用户表 Mapper 接口
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+public interface TbStrTranslateUserMapper extends BaseMapper<TbStrTranslateUser> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/TbV2HandleLogMapper.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.mapper;
+
+import com.cretin.webdb.entity.TbV2HandleLog;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 用户操作日志 Mapper 接口
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-06
+ */
+public interface TbV2HandleLogMapper extends BaseMapper<TbV2HandleLog> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/TbV2StrTranslateItemMapper.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.mapper;
+
+import com.cretin.webdb.entity.TbV2StrTranslateItem;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * APP国际化翻译表 Mapper 接口
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-09
+ */
+public interface TbV2StrTranslateItemMapper extends BaseMapper<TbV2StrTranslateItem> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/mapper/TbV2StrTranslateProjectMapper.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.mapper;
+
+import com.cretin.webdb.entity.TbV2StrTranslateProject;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * APP国际化翻译项目配置表 Mapper 接口
+ * </p>
+ *
+ * @author Cretin
+ * @since 2024-01-19
+ */
+public interface TbV2StrTranslateProjectMapper extends BaseMapper<TbV2StrTranslateProject> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/service/ITbGlobalConfigService.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.service;
+
+import com.cretin.webdb.entity.TbGlobalConfig;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 全局配置表 服务类
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+public interface ITbGlobalConfigService extends IService<TbGlobalConfig> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/service/ITbStrTranslateItemService.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.service;
+
+import com.cretin.webdb.entity.TbStrTranslateItem;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * APP国际化翻译表 服务类
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+public interface ITbStrTranslateItemService extends IService<TbStrTranslateItem> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/service/ITbStrTranslateLogService.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.service;
+
+import com.cretin.webdb.entity.TbStrTranslateLog;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * APP国际化翻译表 操作日志 服务类
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-02-09
+ */
+public interface ITbStrTranslateLogService extends IService<TbStrTranslateLog> {
+
+}

+ 16 - 0
web-db/src/main/kotlin/com/cretin/webdb/service/ITbStrTranslateModuleService.java

@@ -0,0 +1,16 @@
+package com.cretin.webdb.service;
+
+import com.cretin.webdb.entity.TbStrTranslateModule;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * APP国际化翻译 模块对应表 服务类
+ * </p>
+ *
+ * @author Cretin
+ * @since 2022-10-15
+ */
+public interface ITbStrTranslateModuleService extends IService<TbStrTranslateModule> {
+
+}

+ 0 - 0
web-db/src/main/kotlin/com/cretin/webdb/service/ITbStrTranslateUserService.java


Неке датотеке нису приказане због велике количине промена