Răsfoiți Sursa

first commit

zyx 2 ani în urmă
comite
f321a009df
45 a modificat fișierele cu 1802 adăugiri și 0 ștergeri
  1. 37 0
      .gitignore
  2. 10 0
      Dockerfile
  3. 56 0
      build.gradle
  4. BIN
      gradle/wrapper/gradle-wrapper.jar
  5. 5 0
      gradle/wrapper/gradle-wrapper.properties
  6. 240 0
      gradlew
  7. 91 0
      gradlew.bat
  8. 4 0
      push.sh
  9. 1 0
      settings.gradle
  10. 15 0
      src/main/java/com/sxtvs/open/OpenApplication.java
  11. 9 0
      src/main/java/com/sxtvs/open/api/sms/dao/VerifyCodeMapper.java
  12. 13 0
      src/main/java/com/sxtvs/open/api/sms/entity/VerifyCode.java
  13. 108 0
      src/main/java/com/sxtvs/open/api/sms/service/SmsService.java
  14. 33 0
      src/main/java/com/sxtvs/open/api/user/controller/UserController.java
  15. 9 0
      src/main/java/com/sxtvs/open/api/user/dto/CodeVerifyDto.java
  16. 14 0
      src/main/java/com/sxtvs/open/api/user/dto/LoginDto.java
  17. 8 0
      src/main/java/com/sxtvs/open/api/user/dto/TokenDto.java
  18. 37 0
      src/main/java/com/sxtvs/open/api/user/entity/User.java
  19. 16 0
      src/main/java/com/sxtvs/open/api/user/mapper/UserMapper.java
  20. 16 0
      src/main/java/com/sxtvs/open/api/user/service/IUserService.java
  21. 55 0
      src/main/java/com/sxtvs/open/api/user/service/impl/UserServiceImpl.java
  22. 32 0
      src/main/java/com/sxtvs/open/api/youmei/controller/YoumeiController.java
  23. 9 0
      src/main/java/com/sxtvs/open/api/youmei/dto/CheckWordParam.java
  24. 104 0
      src/main/java/com/sxtvs/open/api/youmei/dto/CheckWordResponse.java
  25. 15 0
      src/main/java/com/sxtvs/open/api/youmei/dto/SearchLogDto.java
  26. 29 0
      src/main/java/com/sxtvs/open/api/youmei/entity/YoumeiAccount.java
  27. 18 0
      src/main/java/com/sxtvs/open/api/youmei/mappser/YoumeiAccountMapper.java
  28. 8 0
      src/main/java/com/sxtvs/open/api/youmei/service/IYoumeiAccountService.java
  29. 167 0
      src/main/java/com/sxtvs/open/api/youmei/service/YoumeiAccountServiceImpl.java
  30. 13 0
      src/main/java/com/sxtvs/open/core/advice/APINoDataResponse.java
  31. 13 0
      src/main/java/com/sxtvs/open/core/advice/APIResponse.java
  32. 138 0
      src/main/java/com/sxtvs/open/core/advice/APIResponseAdvice.java
  33. 31 0
      src/main/java/com/sxtvs/open/core/advice/BizException.java
  34. 11 0
      src/main/java/com/sxtvs/open/core/advice/NoAPIResponse.java
  35. 13 0
      src/main/java/com/sxtvs/open/core/advice/ResponseMsg.java
  36. 80 0
      src/main/java/com/sxtvs/open/core/auth/AESUtil.java
  37. 37 0
      src/main/java/com/sxtvs/open/core/auth/LoginInterceptor.java
  38. 11 0
      src/main/java/com/sxtvs/open/core/auth/LoginRequired.java
  39. 21 0
      src/main/java/com/sxtvs/open/core/conf/Constant.java
  40. 31 0
      src/main/java/com/sxtvs/open/core/conf/WebConfig.java
  41. 117 0
      src/main/java/com/sxtvs/open/core/sls/AliyunLogger.java
  42. 45 0
      src/main/resources/application.yml
  43. 5 0
      src/main/resources/com/sxtvs/UserMapper.xml
  44. 58 0
      src/test/java/com/sxtvs/open/GenCode.java
  45. 19 0
      src/test/java/com/sxtvs/open/OpenApplicationTests.java

+ 37 - 0
.gitignore

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

+ 10 - 0
Dockerfile

@@ -0,0 +1,10 @@
+FROM amazoncorretto:17-alpine-jdk
+
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
+RUN apk update && apk add tzdata
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+RUN echo "Asia/Shanghai" > /etc/timezone
+RUN mkdir /conf
+COPY conf/* /conf/
+COPY build/libs/app.jar /app.jar
+

+ 56 - 0
build.gradle

@@ -0,0 +1,56 @@
+plugins {
+    id 'java'
+    id 'org.springframework.boot' version '3.0.2'
+    id 'io.spring.dependency-management' version '1.1.0'
+}
+
+group = 'com.sxtvs'
+version = '0.0.1-SNAPSHOT'
+sourceCompatibility = '17'
+archivesBaseName = 'app'
+
+configurations {
+    compileOnly {
+        extendsFrom annotationProcessor
+    }
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+
+
+    implementation 'cn.hutool:hutool-core:5.8.12'
+    implementation 'cn.hutool:hutool-crypto:5.8.12'
+    implementation 'org.springframework.boot:spring-boot-starter-validation'
+
+    implementation 'com.github.ben-manes.caffeine:caffeine:2.9.2'
+    implementation 'mysql:mysql-connector-java:8.0.28'
+
+    implementation 'org.apache.commons:commons-lang3:3.12.0'
+    implementation 'com.aliyun:dysmsapi20170525:2.0.23'
+
+    implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.3'
+    implementation 'com.baomidou:dynamic-datasource-spring-boot-starter:3.6.1'
+    testImplementation 'com.baomidou:mybatis-plus-generator:3.5.3'
+    implementation 'org.freemarker:freemarker:2.3.31'
+
+    implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
+    implementation 'org.apache.httpcomponents.client5:httpclient5-fluent:5.2.1'
+
+    implementation 'mysql:mysql-connector-java:8.0.28'
+
+    implementation 'commons-io:commons-io:2.11.0'
+    implementation 'com.aliyun.openservices:aliyun-log-producer:0.3.11'
+    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
+    implementation 'org.springframework.boot:spring-boot-starter-web'
+    compileOnly 'org.projectlombok:lombok'
+    annotationProcessor 'org.projectlombok:lombok'
+    testImplementation 'org.springframework.boot:spring-boot-starter-test'
+}
+
+tasks.named('test') {
+    useJUnitPlatform()
+}

BIN
gradle/wrapper/gradle-wrapper.jar


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

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

+ 240 - 0
gradlew

@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"

+ 91 - 0
gradlew.bat

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

+ 4 - 0
push.sh

@@ -0,0 +1,4 @@
+git pull
+git add .
+git commit -m 'commit'
+git push

+ 1 - 0
settings.gradle

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

+ 15 - 0
src/main/java/com/sxtvs/open/OpenApplication.java

@@ -0,0 +1,15 @@
+package com.sxtvs.open;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@MapperScan("com.sxtvs")
+public class OpenApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(OpenApplication.class, args);
+    }
+
+}

+ 9 - 0
src/main/java/com/sxtvs/open/api/sms/dao/VerifyCodeMapper.java

@@ -0,0 +1,9 @@
+package com.sxtvs.open.api.sms.dao;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sxtvs.open.api.sms.entity.VerifyCode;
+
+public interface VerifyCodeMapper extends BaseMapper<VerifyCode> {
+
+}

+ 13 - 0
src/main/java/com/sxtvs/open/api/sms/entity/VerifyCode.java

@@ -0,0 +1,13 @@
+package com.sxtvs.open.api.sms.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Data;
+
+@Data
+public class VerifyCode {
+    @TableId(type = IdType.INPUT)
+    private String phone;
+    private String code;
+    private Long expireTime;
+}

+ 108 - 0
src/main/java/com/sxtvs/open/api/sms/service/SmsService.java

@@ -0,0 +1,108 @@
+package com.sxtvs.open.api.sms.service;
+
+
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.aliyun.teaopenapi.models.Config;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sxtvs.open.api.sms.dao.VerifyCodeMapper;
+import com.sxtvs.open.api.sms.entity.VerifyCode;
+import com.sxtvs.open.core.advice.BizException;
+import jakarta.annotation.Resource;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Component
+public class SmsService {
+
+    @Resource
+    private VerifyCodeMapper verifyCodeMapper;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+    private com.aliyun.dysmsapi20170525.Client client;
+
+    {
+        try {
+            client = new com.aliyun.dysmsapi20170525.Client(new Config()
+                    // 您的AccessKey ID
+                    .setAccessKeyId("LTAI4GEBqfF1GX4VwsYU2Wpg")
+                    // 您的AccessKey Secret
+                    .setAccessKeySecret("rVIv0E1lRfXOCrAmkFTZnfgWiuv4ea")
+                    .setEndpoint("dysmsapi.aliyuncs.com"));
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            System.exit(1);
+        }
+    }
+
+    public void verifyCode(String phone, String code) {
+        VerifyCode verifyCode = verifyCodeMapper.selectOne(new LambdaQueryWrapper<VerifyCode>()
+                .eq(VerifyCode::getPhone, phone)
+                .eq(VerifyCode::getCode, code)
+        );
+        if (verifyCode == null) {
+            throw new BizException("验证码错误");
+        }
+        if (System.currentTimeMillis() > verifyCode.getExpireTime()) {
+            throw new BizException("验证码已超时,请重新发送");
+        }
+    }
+
+
+    public void sendVerifyCode(String phone) {
+        String pattern = "^1\\d{10}$";
+        Pattern p = Pattern.compile(pattern);
+        Matcher match = p.matcher(phone);
+        if (!match.matches()) {
+            throw new BizException(40000, "请输入正确的手机号码");
+        }
+
+        VerifyCode verifyCode = verifyCodeMapper.selectOne(new LambdaQueryWrapper<VerifyCode>()
+                .eq(VerifyCode::getPhone, phone)
+        );
+        if (verifyCode != null) {
+            if (System.currentTimeMillis() - (verifyCode.getExpireTime() - 60 * 5 * 1000) < 60 * 1000) {
+                throw new BizException("验证码已发送,请等待60秒后重新发送");
+            }
+            verifyCodeMapper.deleteById(phone);
+        }
+
+
+        String code = RandomStringUtils.randomNumeric(4);
+
+
+        SendSmsRequest sendSmsRequest = new SendSmsRequest()
+                .setPhoneNumbers(phone)
+                .setSignName("陕西广电融媒体集团")
+                .setTemplateCode("SMS_219739949")
+                .setTemplateParam("{\"code\":\"" + code + "\"}");
+
+        // 复制代码运行请自行打印 API 的返回值
+        try {
+            SendSmsResponse sendSmsResponse = client.sendSms(sendSmsRequest);
+            log.info("sendSmsResponse {}", objectMapper.writeValueAsString(sendSmsResponse.getBody()));
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new BizException("发送短信异常");
+        }
+
+        verifyCode = new VerifyCode();
+        verifyCode.setPhone(phone);
+        verifyCode.setCode(code);
+        // 5分钟的过期时间
+        verifyCode.setExpireTime(System.currentTimeMillis() + 60 * 5 * 1000);
+        verifyCodeMapper.insert(verifyCode);
+
+    }
+}

+ 33 - 0
src/main/java/com/sxtvs/open/api/user/controller/UserController.java

@@ -0,0 +1,33 @@
+package com.sxtvs.open.api.user.controller;
+
+import com.sxtvs.open.api.user.dto.LoginDto;
+import com.sxtvs.open.api.user.dto.TokenDto;
+import com.sxtvs.open.api.user.service.impl.UserServiceImpl;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 前端控制器
+ * </p>
+ *
+ * @author zyx
+ * @since 2023-02-15
+ */
+@RestController
+@RequestMapping("/user")
+public class UserController {
+
+    @Autowired
+    private UserServiceImpl userService;
+
+    @RequestMapping("/login")
+    public TokenDto login(@RequestBody @Valid LoginDto dto) {
+        return userService.login(dto);
+    }
+
+
+}

+ 9 - 0
src/main/java/com/sxtvs/open/api/user/dto/CodeVerifyDto.java

@@ -0,0 +1,9 @@
+package com.sxtvs.open.api.user.dto;
+
+import lombok.Data;
+
+@Data
+public class CodeVerifyDto {
+    private String phone;
+    private String code;
+}

+ 14 - 0
src/main/java/com/sxtvs/open/api/user/dto/LoginDto.java

@@ -0,0 +1,14 @@
+package com.sxtvs.open.api.user.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+public class LoginDto {
+
+    @NotBlank(message = "用户名不能为空")
+    private String loginName;
+    @NotBlank(message = "密码不能为空")
+    private String loginPassword;
+
+}

+ 8 - 0
src/main/java/com/sxtvs/open/api/user/dto/TokenDto.java

@@ -0,0 +1,8 @@
+package com.sxtvs.open.api.user.dto;
+
+import lombok.Data;
+
+@Data
+public class TokenDto {
+    private String token;
+}

+ 37 - 0
src/main/java/com/sxtvs/open/api/user/entity/User.java

@@ -0,0 +1,37 @@
+package com.sxtvs.open.api.user.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ *
+ * </p>
+ *
+ * @author zyx
+ * @since 2023-02-15
+ */
+@Getter
+@Setter
+public class User implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    private String loginName;
+
+    private String loginPassword;
+    private String encodePassword;
+
+    private LocalDateTime createTime;
+
+    private LocalDateTime updateTime;
+}

+ 16 - 0
src/main/java/com/sxtvs/open/api/user/mapper/UserMapper.java

@@ -0,0 +1,16 @@
+package com.sxtvs.open.api.user.mapper;
+
+import com.sxtvs.open.api.user.entity.User;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author zyx
+ * @since 2023-02-15
+ */
+public interface UserMapper extends BaseMapper<User> {
+
+}

+ 16 - 0
src/main/java/com/sxtvs/open/api/user/service/IUserService.java

@@ -0,0 +1,16 @@
+package com.sxtvs.open.api.user.service;
+
+import com.sxtvs.open.api.user.entity.User;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author zyx
+ * @since 2023-02-15
+ */
+public interface IUserService extends IService<User> {
+
+}

+ 55 - 0
src/main/java/com/sxtvs/open/api/user/service/impl/UserServiceImpl.java

@@ -0,0 +1,55 @@
+package com.sxtvs.open.api.user.service.impl;
+
+import cn.hutool.crypto.SecureUtil;
+import com.sxtvs.open.api.user.dto.LoginDto;
+import com.sxtvs.open.api.user.dto.TokenDto;
+import com.sxtvs.open.api.user.entity.User;
+import com.sxtvs.open.api.user.mapper.UserMapper;
+import com.sxtvs.open.api.user.service.IUserService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sxtvs.open.core.advice.BizException;
+import com.sxtvs.open.core.auth.AESUtil;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+/**
+ * <p>
+ * 服务实现类
+ * </p>
+ *
+ * @author zyx
+ * @since 2023-02-15
+ */
+@Service
+public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
+
+
+    public TokenDto login(LoginDto dto) {
+
+        var pwd = SecureUtil.sha256(dto.getLoginPassword());
+        var optionalUser = lambdaQuery().eq(User::getLoginName, dto.getLoginName())
+                .eq(User::getLoginPassword, pwd)
+                .oneOpt();
+
+        if (optionalUser.isEmpty()) {
+            throw new BizException("用户不存在");
+        }
+
+        var id = optionalUser.get().getId();
+
+        var token = AESUtil.encryptHex(id.toString());
+        var tokenDto = new TokenDto();
+        tokenDto.setToken(token);
+        return tokenDto;
+
+    }
+
+    public void createUser(String userName, String pwd) {
+        var user = new User();
+        user.setLoginName(userName);
+        user.setLoginPassword(SecureUtil.sha256(pwd));
+        user.setEncodePassword(AESUtil.encryptHex(pwd));
+        save(user);
+    }
+}

+ 32 - 0
src/main/java/com/sxtvs/open/api/youmei/controller/YoumeiController.java

@@ -0,0 +1,32 @@
+package com.sxtvs.open.api.youmei.controller;
+
+import com.sxtvs.open.api.youmei.dto.CheckWordParam;
+import com.sxtvs.open.api.youmei.service.YoumeiAccountServiceImpl;
+import com.sxtvs.open.core.auth.LoginRequired;
+import com.sxtvs.open.core.sls.AliyunLogger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("api")
+public class YoumeiController {
+
+    @Autowired
+    private YoumeiAccountServiceImpl youmeiAccountService;
+
+
+    @Autowired
+    private AliyunLogger logger;
+
+
+    @RequestMapping("check")
+    @LoginRequired
+    public Object checkWord(@RequestBody CheckWordParam checkWordParam) {
+        logger.info("api", "check", "data", checkWordParam);
+        return youmeiAccountService.wordCheckCache(checkWordParam.getText());
+    }
+
+
+}

+ 9 - 0
src/main/java/com/sxtvs/open/api/youmei/dto/CheckWordParam.java

@@ -0,0 +1,9 @@
+package com.sxtvs.open.api.youmei.dto;
+
+import lombok.Data;
+
+@Data
+public class CheckWordParam {
+    private String text;
+    private String raw;
+}

+ 104 - 0
src/main/java/com/sxtvs/open/api/youmei/dto/CheckWordResponse.java

@@ -0,0 +1,104 @@
+package com.sxtvs.open.api.youmei.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@NoArgsConstructor
+@Data
+public class CheckWordResponse {
+
+    @JsonProperty("msg")
+    private String msg;
+    @JsonProperty("umeiTransactionId")
+    private String umeiTransactionId;
+    @JsonProperty("code")
+    private Integer code;
+    @JsonProperty("data")
+    private DataDTO data;
+    @JsonProperty("transactionId")
+    private String transactionId;
+
+    @NoArgsConstructor
+    @Data
+    public static class DataDTO {
+        @JsonProperty("checklist")
+        private List<ChecklistDTO> checklist;
+
+        private String rawText;
+        private String replaceText;
+
+        @NoArgsConstructor
+        @Data
+        public static class ChecklistDTO {
+            @JsonProperty("um_error_level")
+            private Integer umErrorLevel;
+            @JsonProperty("length")
+            private Integer length;
+            @JsonProperty("suggest")
+            private List<String> suggest;
+            @JsonProperty("source")
+            private Integer source;
+            @JsonProperty("explanation")
+            private String explanation;
+            @JsonProperty("type")
+            private TypeDTO type;
+            @JsonProperty("checkModule")
+            private CheckModuleDTO checkModule;
+            @JsonProperty("htmlWords")
+            private List<HtmlWordsDTO> htmlWords;
+            @JsonProperty("context")
+            private String context;
+            @JsonProperty("action")
+            private ActionDTO action;
+            @JsonProperty("position")
+            private Integer position;
+            @JsonProperty("word")
+            private String word;
+            @JsonProperty("wordHtml")
+            private String wordHtml;
+
+            private int colorLength;
+            private int colorPosition;
+            private String youmeiWordName;
+
+            @NoArgsConstructor
+            @Data
+            public static class TypeDTO {
+                @JsonProperty("name")
+                private String name;
+                @JsonProperty("belongId")
+                private Integer belongId;
+                @JsonProperty("id")
+                private Integer id;
+                @JsonProperty("desc")
+                private String desc;
+            }
+
+            @NoArgsConstructor
+            @Data
+            public static class CheckModuleDTO {
+                @JsonProperty("id")
+                private Integer id;
+            }
+
+            @NoArgsConstructor
+            @Data
+            public static class ActionDTO {
+                @JsonProperty("id")
+                private Integer id;
+            }
+
+            @NoArgsConstructor
+            @Data
+            public static class HtmlWordsDTO {
+                @JsonProperty("position")
+                private Integer position;
+                @JsonProperty("word")
+                private String word;
+            }
+        }
+    }
+}

+ 15 - 0
src/main/java/com/sxtvs/open/api/youmei/dto/SearchLogDto.java

@@ -0,0 +1,15 @@
+package com.sxtvs.open.api.youmei.dto;
+
+import lombok.Data;
+
+@Data
+public class SearchLogDto {
+    private String startTime;
+    private String endTime;
+
+    private String keyword;
+
+    private Integer page = 1;
+
+    private Integer size = 20;
+}

+ 29 - 0
src/main/java/com/sxtvs/open/api/youmei/entity/YoumeiAccount.java

@@ -0,0 +1,29 @@
+package com.sxtvs.open.api.youmei.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * <p>
+ *
+ * </p>
+ *
+ * @author zyx
+ * @since 2021-12-17
+ */
+@Getter
+@Setter
+public class YoumeiAccount implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "open_id", type = IdType.INPUT)
+    private String openId;
+
+    private String accessToken;
+
+}

+ 18 - 0
src/main/java/com/sxtvs/open/api/youmei/mappser/YoumeiAccountMapper.java

@@ -0,0 +1,18 @@
+package com.sxtvs.open.api.youmei.mappser;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sxtvs.open.api.youmei.entity.YoumeiAccount;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author zyx
+ * @since 2021-12-17
+ */
+@DS("collect")
+public interface YoumeiAccountMapper extends BaseMapper<YoumeiAccount> {
+
+}

+ 8 - 0
src/main/java/com/sxtvs/open/api/youmei/service/IYoumeiAccountService.java

@@ -0,0 +1,8 @@
+package com.sxtvs.open.api.youmei.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.sxtvs.open.api.youmei.entity.YoumeiAccount;
+
+public interface IYoumeiAccountService extends IService<YoumeiAccount> {
+
+}

+ 167 - 0
src/main/java/com/sxtvs/open/api/youmei/service/YoumeiAccountServiceImpl.java

@@ -0,0 +1,167 @@
+package com.sxtvs.open.api.youmei.service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.sxtvs.open.api.youmei.dto.CheckWordResponse;
+import com.sxtvs.open.api.youmei.entity.YoumeiAccount;
+import com.sxtvs.open.api.youmei.mappser.YoumeiAccountMapper;
+import lombok.Cleanup;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
+import org.apache.hc.client5.http.fluent.ContentResponseHandler;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.apache.http.Consts;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Service
+@Slf4j
+public class YoumeiAccountServiceImpl extends ServiceImpl<YoumeiAccountMapper, YoumeiAccount> implements IYoumeiAccountService {
+
+    private final ObjectMapper objectMapper;
+
+    public YoumeiAccountServiceImpl(ObjectMapper objectMapper) {
+        this.objectMapper = objectMapper;
+    }
+
+
+    private final HashMap<Integer, String> belongMap = new HashMap<>() {{
+        put(9, "错别字、词");
+        put(31, "多字错误");
+        put(32, "少字错误");
+        put(35, "语义重复");
+        put(34, "语序错误");
+        put(39, "量和单位差错");
+        put(36, "数字差错");
+        put(20, "句式杂糅");
+        put(21, "标点符号差错");
+        put(24, "句子查重");
+        put(23, "英文校对");
+        put(119, "重要讲话引用");
+        put(101, "姓名和职务信息");
+        put(123, "地理名词");
+        put(19, "机构名称");
+        put(124, "专有名词及术语");
+        put(8, "时政重点词");
+        put(122, "媒体报道禁用词和慎用词");
+        put(240, "法律法规名称");
+        put(6, "常识差错");
+        put(105, "涉国家统一、主权和领土完整");
+        put(109, "涉民族宗教");
+        put(112, "涉黄、暴、恐、赌、毒");
+        put(111, "涉低俗辱骂");
+        put(108, "涉违法违规");
+        put(118, "其他敏感内容");
+        put(2001, "自定义禁用词");
+        put(2002, "自定义敏感词");
+        put(2003, "自定义正词");
+        put(2004, "自定义错词");
+        put(2005, "自定义重点词");
+        put(2006, "自定义领导人职务信息");
+        put(2007, "自定义领导人排序");
+    }};
+
+    private final Cache<String, Object> cache = Caffeine.newBuilder()
+            .expireAfterWrite(10, TimeUnit.MINUTES)
+            .maximumSize(10000)
+            .build();
+
+
+    private final Cache<String, String> tokenCache = Caffeine.newBuilder()
+            .expireAfterWrite(1, TimeUnit.MINUTES)
+            .build();
+
+
+
+    public String getWbjcTokenCache() {
+        return tokenCache.get("sxgdwbjc", this::getWbjcToken);
+    }
+
+    public String getWbjcToken(String accountName) {
+        return this.lambdaQuery()
+                .eq(YoumeiAccount::getOpenId, accountName)
+                .one().getAccessToken();
+    }
+
+    private final CloseableHttpClient client = HttpClients.createDefault();
+
+    @SneakyThrows
+    public Object wordCheck(String text) {
+        List<NameValuePair> formparams = new ArrayList<>();
+        formparams.add(new BasicNameValuePair("accessToken", getWbjcTokenCache()));
+        formparams.add(new BasicNameValuePair("text", text));
+        @Cleanup
+        UrlEncodedFormEntity params = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
+
+        var httpPost = new HttpPost("https://api-open-wx-www.yqt365.com/dataapp/api/umei/fw/open/wbjc/article_correct_external");
+        httpPost.setEntity(params);
+
+        var body = client.execute(httpPost, new ContentResponseHandler());
+
+        CheckWordResponse checkWordDto = objectMapper.readValue(body.asString(StandardCharsets.UTF_8), CheckWordResponse.class);
+        CheckWordResponse.DataDTO data = checkWordDto.getData();
+
+        if (data.getChecklist().size() == 0) {
+            data.setRawText(text);
+            data.setReplaceText(text);
+            return checkWordDto.getData();
+        }
+
+        replaceText(text, data);
+
+        return checkWordDto.getData();
+    }
+
+    private void replaceText(String text, CheckWordResponse.DataDTO data) {
+        StringBuilder replaceBuilder = new StringBuilder();
+        StringBuilder rawBuilder = new StringBuilder();
+        int index = 0;
+        for (CheckWordResponse.DataDTO.ChecklistDTO checklistDTO : data.getChecklist()) {
+            Integer position = checklistDTO.getPosition();
+            int length = checklistDTO.getWordHtml().length();
+
+            CheckWordResponse.DataDTO.ChecklistDTO.TypeDTO typeDTO = checklistDTO.getType();
+            Integer belongId = typeDTO.getBelongId();
+            checklistDTO.setYoumeiWordName(belongMap.getOrDefault(belongId, typeDTO.getName()));
+
+
+            replaceBuilder.append(text, index, position);
+            rawBuilder.append(text, index, position);
+            index += (position - index + length);
+
+            String suggest = checklistDTO.getSuggest().stream().findFirst().orElse("");
+
+            replaceBuilder.append(suggest);
+            String wrapRed = wrapRed(text.substring(position, position + length), rawBuilder.length());
+            checklistDTO.setColorPosition(rawBuilder.length());
+            rawBuilder.append(wrapRed);
+            checklistDTO.setColorLength(wrapRed.length());
+
+        }
+        rawBuilder.append(text, index, text.length());
+        replaceBuilder.append(text, index, text.length());
+        data.setRawText(rawBuilder.toString());
+        data.setReplaceText(replaceBuilder.toString());
+    }
+
+    private String wrapRed(String text, int position) {
+        return "<span  class=\"selectSpan\" data-umpos=\"" + position + "\">" + text + "</span>";
+    }
+
+    public Object wordCheckCache(String text) {
+        return cache.get(text, this::wordCheck);
+    }
+
+}

+ 13 - 0
src/main/java/com/sxtvs/open/core/advice/APINoDataResponse.java

@@ -0,0 +1,13 @@
+package com.sxtvs.open.core.advice;
+
+import lombok.Data;
+
+@Data
+public class APINoDataResponse  {
+
+    private String message;
+
+    private Integer code;
+
+
+}

+ 13 - 0
src/main/java/com/sxtvs/open/core/advice/APIResponse.java

@@ -0,0 +1,13 @@
+package com.sxtvs.open.core.advice;
+
+import lombok.Data;
+
+@Data
+public class APIResponse {
+
+    private String message;
+
+    private Integer code;
+
+    private Object data;
+}

+ 138 - 0
src/main/java/com/sxtvs/open/core/advice/APIResponseAdvice.java

@@ -0,0 +1,138 @@
+package com.sxtvs.open.core.advice;
+
+import com.sxtvs.open.core.sls.AliyunLogger;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+@RestControllerAdvice
+@Slf4j
+public class APIResponseAdvice implements ResponseBodyAdvice<Object> {
+
+    @Autowired
+    private AliyunLogger logger;
+
+    //自动处理APINoDataResponse,包装为APINoDataResponse
+    @ExceptionHandler(BizException.class)
+    public APINoDataResponse handleBizException(HttpServletRequest request, BizException ex, HttpServletResponse response) {
+        logger.warn("error", ex);
+        APINoDataResponse apiNoDataResponse = new APINoDataResponse();
+        apiNoDataResponse.setCode(ex.getErrorCode());
+        apiNoDataResponse.setMessage(ex.getErrorMessage());
+        return apiNoDataResponse;
+    }
+
+    @ExceptionHandler(MaxUploadSizeExceededException.class)
+    public APINoDataResponse handleException(HttpServletRequest request, MaxUploadSizeExceededException ex, HttpServletResponse response) {
+        logger.warn("error", ex);
+        APINoDataResponse apiResponse = new APINoDataResponse();
+        apiResponse.setCode(-1);
+        apiResponse.setMessage("文件长度过长");
+        return apiResponse;
+    }
+
+    /**
+     * 拦截未知的运行时异常
+     *
+     * @author fengshuonan
+     * @date 2020/12/16 15:12
+     */
+    @ExceptionHandler(Throwable.class)
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    public APINoDataResponse serverError(Throwable ex) {
+        logger.error("error", ex);
+        APINoDataResponse apiResponse = new APINoDataResponse();
+        apiResponse.setCode(-1);
+        apiResponse.setMessage("服务器运行异常");
+        return apiResponse;
+    }
+
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public APINoDataResponse validationBodyException(MethodArgumentNotValidException exception) {
+        StringBuilder sb = new StringBuilder();
+        BindingResult result = exception.getBindingResult();
+        if (result.hasErrors()) {
+            List<ObjectError> errors = result.getAllErrors();
+
+            errors.forEach(p -> {
+                FieldError fieldError = (FieldError) p;
+                sb.append("Data check failure : object{").append(fieldError.getObjectName()).append("},field{")
+                        .append(fieldError.getField()).append("},errorMessage{")
+                        .append(fieldError.getDefaultMessage()).append("}");
+            });
+        }
+        logger.warn("error", sb.toString());
+        APINoDataResponse apiResponse = new APINoDataResponse();
+        apiResponse.setCode(-1);
+        apiResponse.setMessage("请填写正确信息");
+        return apiResponse;
+    }
+
+
+    //仅当方法或类没有标记@NoAPIResponse才自动包装
+    @Override
+    public boolean supports(MethodParameter returnType, Class converterType) {
+        for (Annotation methodAnnotation : returnType.getMethodAnnotations()) {
+            if (methodAnnotation.annotationType() == NoAPIResponse.class) {
+                return false;
+            }
+        }
+        return returnType.getParameterType() != APIResponse.class && returnType.getParameterType() != APINoDataResponse.class
+                && AnnotationUtils.findAnnotation(returnType.getDeclaringClass(), NoAPIResponse.class) == null;
+    }
+
+    //自动包装外层APIResposne响应
+    @Override
+    public Object beforeBodyWrite(Object body,
+                                  MethodParameter returnType,
+                                  MediaType selectedContentType,
+                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
+                                  ServerHttpRequest request, ServerHttpResponse response) {
+
+
+        ResponseMsg responseMsg = returnType.getMethodAnnotation(ResponseMsg.class);
+        String msg = "OK";
+
+        if (null != responseMsg) {
+            msg = responseMsg.value();
+        }
+
+        if (null == body) {
+            APINoDataResponse apiResponse = new APINoDataResponse();
+
+            apiResponse.setCode(0);
+            apiResponse.setMessage(msg);
+            return apiResponse;
+        } else {
+            APIResponse apiResponse = new APIResponse();
+
+            apiResponse.setCode(0);
+            apiResponse.setMessage(msg);
+            apiResponse.setData(body);
+            return apiResponse;
+        }
+
+    }
+
+}

+ 31 - 0
src/main/java/com/sxtvs/open/core/advice/BizException.java

@@ -0,0 +1,31 @@
+package com.sxtvs.open.core.advice;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class BizException extends RuntimeException {
+
+    private int errorCode;
+
+    private String errorMessage;
+
+    public BizException(String errorMessage) {
+        super(errorMessage);
+        this.errorCode = -1;
+        this.errorMessage = errorMessage;
+    }
+
+    public BizException(int errorCode, String errorMessage) {
+        super(errorMessage);
+        this.errorCode = errorCode;
+        this.errorMessage = errorMessage;
+    }
+
+    public BizException(Throwable cause, int errorCode, String errorMessage) {
+        super(errorMessage, cause);
+        this.errorCode = errorCode;
+        this.errorMessage = errorMessage;
+    }
+}

+ 11 - 0
src/main/java/com/sxtvs/open/core/advice/NoAPIResponse.java

@@ -0,0 +1,11 @@
+package com.sxtvs.open.core.advice;
+
+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 NoAPIResponse {
+}

+ 13 - 0
src/main/java/com/sxtvs/open/core/advice/ResponseMsg.java

@@ -0,0 +1,13 @@
+package com.sxtvs.open.core.advice;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ResponseMsg {
+    String value() default "OK";
+}

+ 80 - 0
src/main/java/com/sxtvs/open/core/auth/AESUtil.java

@@ -0,0 +1,80 @@
+package com.sxtvs.open.core.auth;
+
+
+
+
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.symmetric.AES;
+import com.sxtvs.open.core.advice.BizException;
+import com.sxtvs.open.core.conf.Constant;
+
+import java.nio.charset.StandardCharsets;
+
+public class AESUtil {
+    private static final AES aes = SecureUtil.aes(Constant.AES_KEY.getBytes(StandardCharsets.UTF_8));
+
+    public static String encryptHex(String data, long addSecond) {
+        //加入了随机旋转和时间戳 避免加密后得到的是同样的数据 得到的token比传统的jwt要小很多
+        int randomInt = RandomUtil.randomInt(data.length());
+        data = shift(data, randomInt);
+        return aes.encryptHex(randomInt + "," + data + "," + (getCurrentTime() + addSecond));
+    }
+
+    public static String encryptHex(String data){
+        return encryptHex(data,Constant.ACCESS_TOKEN_EXPIRE_TIME);
+    }
+
+    public static String decryptStr(String token) {
+        String[] dataArray;
+        long time;
+        try {
+            token = aes.decryptStr(token, CharsetUtil.CHARSET_UTF_8);
+            dataArray = token.split(",");
+            time = Long.parseLong(dataArray[2]);
+        } catch (Exception e) {
+            throw new BizException(Constant.TOKEN_PARSE_ERROR, "token 异常");
+        }
+        if (time < getCurrentTime()) {
+            throw new BizException(Constant.TOKEN_EXPIRE_ERROR, "token 已过期");
+        }
+        int i = Integer.parseInt(dataArray[0]);
+        return shift(dataArray[1], i * -1);
+    }
+
+    public static boolean isOk(String token){
+        try {
+            decryptStr(token);
+        } catch (Exception e) {
+            return false;
+        }
+        return true;
+    }
+
+
+
+    private static long getCurrentTime() {
+        return System.currentTimeMillis() / 1000 - Constant.SUB_TIME;
+    }
+
+    private static String shift(String str, int n) {
+        if (n == 0) {
+            return str;
+        }
+        int len = str.length();
+        char[] oldStr = str.toCharArray();
+        char[] newStr = new char[len];
+        n = n % len;
+        if (n < 0) {
+            n = len + n;
+        }
+        for (int i = 0; i < len; i++) {
+            newStr[(i + n) % len] = oldStr[i];
+        }
+        return new String(newStr);
+    }
+
+
+
+}

+ 37 - 0
src/main/java/com/sxtvs/open/core/auth/LoginInterceptor.java

@@ -0,0 +1,37 @@
+package com.sxtvs.open.core.auth;
+
+
+import com.sxtvs.open.core.advice.BizException;
+import com.sxtvs.open.core.conf.Constant;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.AsyncHandlerInterceptor;
+
+import java.lang.reflect.Method;
+
+public class LoginInterceptor implements AsyncHandlerInterceptor {
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        if (!(handler instanceof HandlerMethod)) {
+            return true;
+        }
+
+        HandlerMethod handlerMethod = (HandlerMethod) handler;
+        Method method = handlerMethod.getMethod();
+
+        LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class);
+        if (methodAnnotation == null) {
+            return true;
+        }
+
+        // 这写你拦截需要干的事儿,比如取缓存,SESSION,权限判断等
+        String token = request.getHeader(Constant.TOKEN_HEADER_NAME);
+
+        if (!AESUtil.isOk(token)) {
+            throw new BizException("没有登录");
+        }
+
+        return true;
+    }
+}

+ 11 - 0
src/main/java/com/sxtvs/open/core/auth/LoginRequired.java

@@ -0,0 +1,11 @@
+package com.sxtvs.open.core.auth;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LoginRequired {
+}

+ 21 - 0
src/main/java/com/sxtvs/open/core/conf/Constant.java

@@ -0,0 +1,21 @@
+package com.sxtvs.open.core.conf;
+
+public class Constant {
+    public static final String AES_KEY = "2329065635414178";
+
+    public static final int TOKEN_PARSE_ERROR = 400;
+
+    public static final int TOKEN_EXPIRE_ERROR = 401;
+
+    //access_token 过期时间为7天 单位是秒
+    public static final long ACCESS_TOKEN_EXPIRE_TIME = 730 * 24 * 60 * 60 ;
+
+    //refresh_token 过期时间为30天 单位是秒
+    public static final long REFRESH_TOKEN_EXPIRE_TIME = 30 * 24 * 60 * 60 ;
+
+    //生成的时间戳减去这个时间 不容易被破解
+    public static final long SUB_TIME = 1600000000;
+
+    public static final String TOKEN_HEADER_NAME = "Authorization";
+
+}

+ 31 - 0
src/main/java/com/sxtvs/open/core/conf/WebConfig.java

@@ -0,0 +1,31 @@
+package com.sxtvs.open.core.conf;
+
+import com.sxtvs.open.core.auth.LoginInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedMethods("*")
+                .allowedOrigins("*")
+                .allowedHeaders("*");
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
+        registry.addInterceptor(LoginInterceptor()).addPathPatterns("/**");
+    }
+
+    @Bean
+    public LoginInterceptor LoginInterceptor() {
+        return new LoginInterceptor();
+    }
+
+}

+ 117 - 0
src/main/java/com/sxtvs/open/core/sls/AliyunLogger.java

@@ -0,0 +1,117 @@
+package com.sxtvs.open.core.sls;
+
+import com.aliyun.openservices.aliyun.log.producer.LogProducer;
+import com.aliyun.openservices.aliyun.log.producer.Producer;
+import com.aliyun.openservices.aliyun.log.producer.ProducerConfig;
+import com.aliyun.openservices.aliyun.log.producer.ProjectConfig;
+import com.aliyun.openservices.aliyun.log.producer.errors.ProducerException;
+import com.aliyun.openservices.log.common.LogItem;
+import jakarta.annotation.PostConstruct;
+import org.springframework.stereotype.Component;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+@Component
+public class AliyunLogger {
+    private Producer producer;
+    private final String logStore = "java-log";
+    private final String project = "k8s-log-c4d23a31b8c6c4ed49e7fc1473dfd0c57";
+
+    private final String app = "open";
+
+    @PostConstruct
+    public void init() {
+
+        ProducerConfig producerConfig = new ProducerConfig();
+        producerConfig.setBatchSizeThresholdInBytes(3 * 1024 * 1024);
+        producerConfig.setBatchCountThreshold(40960);
+
+        producer = new LogProducer(producerConfig);
+        producer.putProjectConfig(new ProjectConfig(project,
+                "cn-chengdu.log.aliyuncs.com",
+                "LTAI5tCb5r8efPipWaKSeCkE", "BsKtHRePUFBrfF9aA8b8sDTrox23cU"));
+
+        Runtime.getRuntime().addShutdownHook(new Thread(this::close));
+    }
+
+    public void info(Object... kv) {
+        try {
+            LogItem item = log(kv);
+            item.PushBack("level", "info");
+            producer.send(project, logStore, item);
+        } catch (InterruptedException | ProducerException ignored) {
+        }
+    }
+
+    public void error(Object... kv) {
+        try {
+            LogItem item = log(kv);
+            item.PushBack("level", "error");
+            producer.send(project, logStore, item);
+        } catch (InterruptedException | ProducerException ignored) {
+        }
+    }
+
+    public void warn(Object... kv) {
+        try {
+            LogItem item = log(kv);
+            item.PushBack("level", "warn");
+            producer.send(project, logStore, item);
+        } catch (InterruptedException | ProducerException ignored) {
+        }
+    }
+
+    private LogItem log(Object... kv) {
+        LogItem item = new LogItem();
+        item.PushBack("app", app);
+        var stackTrace = Thread.currentThread().getStackTrace();
+        if (stackTrace.length >= 4) {
+            var traceElement = stackTrace[3];
+            item.PushBack("at", traceElement.getClassName() + "." + traceElement.getMethodName()
+                    + "(" + traceElement.getFileName() + ":" + traceElement.getLineNumber() + ")");
+        }
+        int length = kv.length;
+        if (length % 2 != 0) {
+            return item;
+        }
+        for (int i = 0; i < kv.length; i += 2) {
+            if (kv[i] == null) {
+                continue;
+            }
+            item.PushBack(kv[i].toString(), format(kv[i + 1]));
+        }
+        return item;
+    }
+
+    private String format(Object obj) {
+        if (obj == null) {
+            return "";
+        }
+        if (obj instanceof Throwable err) {
+            StringWriter sw = new StringWriter();
+            err.printStackTrace(new PrintWriter(sw, true));
+            return sw.getBuffer().toString();
+        }
+        return obj.toString();
+    }
+
+
+    private void close() {
+        try {
+            producer.close();
+        } catch (InterruptedException | ProducerException ignored) {
+        }
+    }
+
+    public static void main(String[] args) {
+        AliyunLogger aliyunLogger = new AliyunLogger();
+        aliyunLogger.init();
+
+        aliyunLogger.info("msg", "123");
+        aliyunLogger.info("msg", 1);
+        aliyunLogger.info("msg", new RuntimeException("what"));
+
+        aliyunLogger.close();
+    }
+}

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

@@ -0,0 +1,45 @@
+
+
+mybatis-plus:
+  mapper-locations: classpath*:com/sxtvs/**/*.xml
+  configuration:
+    map-underscore-to-camel-case: true
+    cache-enabled: true
+    lazy-loading-enabled: true
+    multiple-result-sets-enabled: true
+  global-config:
+    banner: false
+    enable-sql-runner: true
+    db-config:
+      id-type: assign_id
+      table-underline: true
+
+
+spring:
+  application:
+    name: open
+  servlet:
+    multipart:
+      max-request-size: 500MB
+      max-file-size: 500MB
+  datasource:
+    dynamic:
+      hikari:
+        driver-class-name: com.mysql.cj.jdbc.Driver
+        max-lifetime: 180000
+        idle-timeout: 30000
+        max-pool-size: 100
+        min-idle: 5
+      primary: open
+      strict: false
+      datasource:
+        open:
+          url: jdbc:mysql://rm-2vc3039h858t9ab7sfo.mysql.cn-chengdu.rds.aliyuncs.com:3306/open?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
+          username: cxzx
+          password: sxtvs53$68HD
+        collect:
+          url: jdbc:mysql://rm-2vc3039h858t9ab7sfo.mysql.cn-chengdu.rds.aliyuncs.com:3306/collect?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&nullCatalogMeansCurrent=true
+          username: cxzx
+          password: sxtvs53$68HD
+server:
+  port: 80

+ 5 - 0
src/main/resources/com/sxtvs/UserMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.sxtvs.open.api.user.mapper.UserMapper">
+
+</mapper>

+ 58 - 0
src/test/java/com/sxtvs/open/GenCode.java

@@ -0,0 +1,58 @@
+package com.sxtvs.open;
+
+import com.baomidou.mybatisplus.generator.FastAutoGenerator;
+import com.baomidou.mybatisplus.generator.config.OutputFile;
+import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+
+public class GenCode {
+    @Test
+    public void genCodeZyxPc() {
+        FastAutoGenerator.create("jdbc:mysql://rm-2vc3039h858t9ab7sfo.mysql.cn-chengdu.rds.aliyuncs.com:3306/open",
+                        "cxzx", "sxtvs53$68HD")
+                .globalConfig(builder -> {
+                    builder.author("zyx") // 设置作者
+//                            .fileOverride() // 覆盖已生成文件
+                            .outputDir("D:\\code\\java\\open\\open\\src\\main\\java"); // 指定输出目录
+                })
+                .packageConfig(builder -> {
+                    builder.parent("com.sxtvs.open.api") // 设置父包名
+                            .moduleName("user") // 设置父包模块名
+                            .pathInfo(Collections.singletonMap(OutputFile.xml,
+                                    "D:\\code\\java\\open\\open\\src\\main\\resources\\com\\sxtvs")); // 设置mapperXml生成路径
+                })
+                .strategyConfig(builder -> {
+                    builder.addInclude("user"); // 设置需要生成的表名
+                    builder.controllerBuilder().enableRestStyle();
+                    builder.entityBuilder().enableLombok();
+                })
+                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
+                .execute();
+    }
+
+//    @Test
+//    public void genCodeMac(){
+//        FastAutoGenerator.create("jdbc:mysql://rm-2vc3039h858t9ab7sfo.mysql.cn-chengdu.rds.aliyuncs.com:3306/collect",
+//                        "cxzx", "sxtvs53$68HD")
+//                .globalConfig(builder -> {
+//                    builder.author("zyx") // 设置作者
+//                            .fileOverride() // 覆盖已生成文件
+//                            .outputDir("/Users/zhangyunxiang/IdeaProjects/topic-java/src/main/java"); // 指定输出目录
+//                })
+//                .packageConfig(builder -> {
+//                    builder.parent("com.smcic.api") // 设置父包名
+//                            .moduleName("topic") // 设置父包模块名
+//                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml,
+//                                    "/Users/zhangyunxiang/IdeaProjects/topic-java/src/main/resources/com/smcic")); // 设置mapperXml生成路径
+//                })
+//                .strategyConfig(builder -> {
+//                    builder.addInclude("chunwan_detail"); // 设置需要生成的表名
+//                    builder.controllerBuilder().enableRestStyle();
+//                    builder.entityBuilder().enableLombok();
+//                })
+//                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
+//                .execute();
+//    }
+}

+ 19 - 0
src/test/java/com/sxtvs/open/OpenApplicationTests.java

@@ -0,0 +1,19 @@
+package com.sxtvs.open;
+
+import com.sxtvs.open.api.user.service.impl.UserServiceImpl;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class OpenApplicationTests {
+
+    @Autowired
+    private UserServiceImpl userService;
+
+    @Test
+    void createUser() {
+        userService.createUser("zhangyunxiang", "123456");
+    }
+
+}