diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec376bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +target \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..bf82ff0 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..ca5ab4b --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/HELP.md b/HELP.md new file mode 100644 index 0000000..dc3c5c5 --- /dev/null +++ b/HELP.md @@ -0,0 +1,30 @@ +# Getting Started + +### User guide + +#### how to start the db? +`cd docker &&` +`docker-compose -f mysql.yml up` + +### USER AUTH + +the sequence of the user operation should be like + +register -> query -> update -> delete + +mapping to the API in our design should be: + +register: /user/add +query: /user/get or /user/get/name +update: /user/update +delete: /user/delete + +for all the APIs above, except for the register API without auth, the rest all have the auth verification +which means, only after login, the user can only operate with his/her own account + +## TODO +we will use more advanced auto method in the future like JWT token based SSO. + +the followers impl will do in the future too. + + diff --git a/README.md b/README.md index 447f14a..c1c556a 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,18 @@ Make sure you read the whole document carefully and follow the guidelines in it. ## Context -Build a RESTful API that can `get/create/update/delete` user data from a persistence database +Build a RESTful API that can `get/create/update/delete` userDo data from a persistence database ### User Model ``` { - "id": "xxx", // user ID - "name": "test", // user name + "id": "xxx", // userDo ID + "name": "test", // userDo name "dob": "", // date of birth - "address": "", // user address - "description": "", // user description - "createdAt": "" // user created date + "address": "", // userDo address + "description": "", // userDo description + "createdAt": "" // userDo created date } ``` @@ -44,11 +44,11 @@ Build a RESTful API that can `get/create/update/delete` user data from a persist *These are used for some further challenges. You can safely skip them if you are not asked to do any, but feel free to try out.* -- Provide a complete user auth (authentication/authorization/etc.) strategy, such as OAuth. This should provide a way to allow end users to securely login, autenticate requests and only access their own information. +- Provide a complete userDo auth (authentication/authorization/etc.) strategy, such as OAuth. This should provide a way to allow end userDos to securely login, autenticate requests and only access their own information. - Provide a complete logging (when/how/etc.) strategy. -- Imagine we have a new requirement right now that the user instances need to link to each other, i.e., a list of "followers/following" or "friends". Can you find out how you would design the model structure and what API you would build for querying or modifying it? -- Related to the requirement above, suppose the address of user now includes a geographic coordinate(i.e., latitude and longitude), can you build an API that, - - given a user name +- Imagine we have a new requirement right now that the userDo instances need to link to each other, i.e., a list of "followers/following" or "friends". Can you find out how you would design the model structure and what API you would build for querying or modifying it? +- Related to the requirement above, suppose the address of userDo now includes a geographic coordinate(i.e., latitude and longitude), can you build an API that, + - given a userDo name - return the nearby friends diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..8a8fb22 --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/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 /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + 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="`\\unset -f command; \\command -v 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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.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" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$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 \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..08b55d5 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,188 @@ +@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 userDo 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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq 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%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.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 "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\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% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2ad9b51 --- /dev/null +++ b/pom.xml @@ -0,0 +1,163 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.12 + + + com.test + test-backend-java + 0.0.1-SNAPSHOT + demo + Demo project for Spring Boot + + 1.8 + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.2.0 + + + + mysql + mysql-connector-java + 8.0.29 + + + + + com.auth0 + java-jwt + 3.0.0 + + + + com.alibaba + druid + 1.1.12 + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + net.sourceforge.nekohtml + nekohtml + 1.9.22 + + + + junit + junit + 4.11 + test + + + + javax.persistence + persistence-api + 1.0 + + + + log4j + log4j + 1.2.17 + + + com.suyeer + mybatis-plus + 3.2.0.6 + + + com.baomidou + mybatis-plus-annotation + 3.4.3.1 + + + + + io.springfox + springfox-swagger2 + 2.9.2 + + + + io.springfox + springfox-swagger-ui + 2.9.2 + + + + + + + org.asciidoctor + asciidoctor-maven-plugin + 2.2.1 + + + generate-docs + prepare-package + + process-asciidoc + + + html + book + + + + + + org.springframework.restdocs + spring-restdocs-asciidoctor + ${spring-restdocs.version} + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/com/test/demo/DemoApplication.java b/src/main/java/com/test/demo/DemoApplication.java new file mode 100644 index 0000000..0f59d92 --- /dev/null +++ b/src/main/java/com/test/demo/DemoApplication.java @@ -0,0 +1,18 @@ +package com.test.demo; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@SpringBootApplication +@EnableSwagger2 +@MapperScan("com.test.demo.mapper") +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + +} diff --git a/src/main/java/com/test/demo/common/UserRole.java b/src/main/java/com/test/demo/common/UserRole.java new file mode 100644 index 0000000..6562b56 --- /dev/null +++ b/src/main/java/com/test/demo/common/UserRole.java @@ -0,0 +1,10 @@ +package com.test.demo.common; + +/** + * @author zhangrucheng on 2023/5/22 + */ +public enum UserRole { + + ADMIN, + USER +} diff --git a/src/main/java/com/test/demo/config/DruidDataSourceFactory.java b/src/main/java/com/test/demo/config/DruidDataSourceFactory.java new file mode 100644 index 0000000..e18923f --- /dev/null +++ b/src/main/java/com/test/demo/config/DruidDataSourceFactory.java @@ -0,0 +1,28 @@ +package com.test.demo.config; + +import com.alibaba.druid.pool.DruidDataSource; +import com.zaxxer.hikari.util.DriverDataSource; +import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory; + +import javax.sql.DataSource; +import java.sql.SQLException; + +/** + * @author zhangrucheng on 2023/5/20 + */ +public class DruidDataSourceFactory extends UnpooledDataSourceFactory { + + @Override + public DataSource getDataSource() { + try { + ((DruidDataSource) this.dataSource).init(); + } catch (SQLException throwables) { + throw new RuntimeException(throwables); + } + return this.dataSource; + } + + public DruidDataSourceFactory() { + this.dataSource = new DruidDataSource(); + } +} diff --git a/src/main/java/com/test/demo/config/InterceptorConfig.java b/src/main/java/com/test/demo/config/InterceptorConfig.java new file mode 100644 index 0000000..0e90ac1 --- /dev/null +++ b/src/main/java/com/test/demo/config/InterceptorConfig.java @@ -0,0 +1,22 @@ +package com.test.demo.config; + +import com.test.demo.security.JWTInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * The type Interceptor config. + * TODO: add JWT token validation for SSO + */ +public class InterceptorConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new JWTInterceptor()) +// .addPathPatterns("/user/get") //need to interceptor all the requests +// .addPathPatterns("/user/delete") + .excludePathPatterns("/login"); + } +} diff --git a/src/main/java/com/test/demo/config/SecurityConfig.java b/src/main/java/com/test/demo/config/SecurityConfig.java new file mode 100644 index 0000000..6e42194 --- /dev/null +++ b/src/main/java/com/test/demo/config/SecurityConfig.java @@ -0,0 +1,75 @@ +package com.test.demo.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; + +import javax.sql.DataSource; + +/** + * @author zhangrucheng on 2023/5/22 + */ +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private DataSource dataSource; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public JdbcTokenRepositoryImpl jdbcTokenRepository() { + JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); + jdbcTokenRepository.setDataSource(dataSource); + return jdbcTokenRepository; + } + + @Override + public void configure(WebSecurity web) throws Exception { + //所需要用到的静态资源,允许访问 + web.ignoring().antMatchers( "/swagger-ui.html", + "/swagger-ui/*", + "/swagger-resources/**", + "/v2/api-docs", + "/v3/api-docs", + "/webjars/**"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/login", "/index", "/success", "/error", "/login.html", "/user/add").permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginProcessingUrl("/login") + .loginPage("/login") + .failureForwardUrl("/toError") + .successForwardUrl("/success") + .permitAll() + .and() + .logout() + .permitAll(); + http.csrf().disable(); + http.sessionManagement().invalidSessionUrl("/login"); + http.rememberMe().tokenValiditySeconds(1200).tokenRepository(jdbcTokenRepository()); + } +} diff --git a/src/main/java/com/test/demo/controller/LoginController.java b/src/main/java/com/test/demo/controller/LoginController.java new file mode 100644 index 0000000..dbdd57b --- /dev/null +++ b/src/main/java/com/test/demo/controller/LoginController.java @@ -0,0 +1,51 @@ +package com.test.demo.controller; + +import com.test.demo.entity.UserDo; +import com.test.demo.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.util.Optional; +import java.util.UUID; + +/** + * @author zhangrucheng on 2023/5/21 + */ +@Controller +@Slf4j +public class LoginController { + + @Autowired + private UserService userService; + + @RequestMapping("/login") + public String login() { + // 登录失败跳转回登录页面 + return "login"; + } + + @RequestMapping("/index") + public String index() { + // 登录失败跳转回登录页面 + return "redirect:/index.html"; + } + + + @RequestMapping("/success") + public String successLogin() { + return "main"; + } + + @RequestMapping("/toError") + public String toError() { + return "error"; + } +} diff --git a/src/main/java/com/test/demo/controller/UserController.java b/src/main/java/com/test/demo/controller/UserController.java new file mode 100644 index 0000000..55ca90d --- /dev/null +++ b/src/main/java/com/test/demo/controller/UserController.java @@ -0,0 +1,117 @@ +package com.test.demo.controller; + +import com.test.demo.entity.UserDo; +import com.test.demo.exception.UnAuthorizedException; +import com.test.demo.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; + +/** + * @author zhangrucheng on 2023/5/21 + */ +@RestController +@RequestMapping(path = "/user") +@Slf4j +public class UserController { + + @Autowired + private UserService userService; + + @Autowired + private PasswordEncoder passwordEncoder; + + /** + * getUser + * @param id userId + * @return User user信息 + * @description 根据id获取user + */ + @GetMapping("/get") + public ResponseEntity getUser(@RequestParam int id){ + log.info("begin to get user with id [{}]", id); + Optional user = userService.getUserById(id); + user.ifPresent(userDo -> validateAuth(userDo.getName(), userDo.getPassword())); + return user.map(value -> new ResponseEntity<>(value, HttpStatus.OK)) + .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND)); + } + + @GetMapping("/get/name") + public ResponseEntity getUserByName(@RequestParam String name){ + log.info("begin to get user with name [{}]", name); + UserDo user = userService.getUserByName(name); + if (user != null) { + validateAuth(user.getName(), user.getPassword()); + return new ResponseEntity<>(user, HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + /** + * addUser + * @param userDo user信息 + * @return user user信息 + * @description 添加user + */ + @PostMapping("/add") + public ResponseEntity addUser(@RequestBody UserDo userDo){ + userService.registerUser(userDo); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + /** + * updateUser + * @param userDo user信息 + * @return user user信息 + * @description 更新user + */ + @PutMapping("/update") + public ResponseEntity updateUser(@RequestBody UserDo userDo){ + Optional existingUser = userService.getUserById(userDo.getId()); + if (existingUser.isPresent()) { + existingUser.ifPresent(user -> validateAuth(user.getName(), user.getPassword())); + userService.updateUser(userDo); + return new ResponseEntity<>(HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + /** + * updateUser + * @param id userId + * @return void + * @description 根据userId 删除User + */ + @DeleteMapping("/delete") + public ResponseEntity delUser(@RequestParam int id){ + Optional existingUser = userService.getUserById(id); + if (existingUser.isPresent()) { + existingUser.ifPresent(user -> validateAuth(user.getName(), user.getPassword())); + userService.deleteUser(id); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } else { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + //TODO use AOP to do this + private void validateAuth(String name, String passwd) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Object principal = authentication.getPrincipal(); + UserDetails userDetails = (UserDetails) principal; + if (!name.equals(userDetails.getUsername()) && !passwd.equals(passwordEncoder.encode(userDetails.getPassword()))) { + throw new UnAuthorizedException("Unauthorized Operation !"); + } + } + +} diff --git a/src/main/java/com/test/demo/entity/UserDo.java b/src/main/java/com/test/demo/entity/UserDo.java new file mode 100644 index 0000000..d3ce87a --- /dev/null +++ b/src/main/java/com/test/demo/entity/UserDo.java @@ -0,0 +1,65 @@ +package com.test.demo.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +/** + * @author zhangrucheng on 2023/5/19 + */ + +@Entity +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@TableName("user") +public class UserDo implements Serializable { + + @Column(name = "id") + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(name = "name") + private String name; + + @Column(name = "password") + private String password; + + @Column(name = "dob") + private Date dob; + + @Column(name = "description") + private String description; + + @Column(name = "longitude") + private Double longitude; + + @Column(name = "latitude") + private Double latitude; + + @Column(name = "create_time") + private Date createTime; + + @Column(name = "update_time") + private Date updateTime; + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("User{"); + sb.append("id=").append(id); + sb.append(", name='").append(name).append('\''); + sb.append(", dob=").append(dob); + sb.append(", description='").append(description).append('\''); + sb.append(", longitude=").append(longitude); + sb.append(", latitude=").append(latitude); + sb.append(", createTime=").append(createTime); + sb.append(", updateTime=").append(updateTime); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/com/test/demo/exception/GlobalExceptionHandler.java b/src/main/java/com/test/demo/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..fd0bdef --- /dev/null +++ b/src/main/java/com/test/demo/exception/GlobalExceptionHandler.java @@ -0,0 +1,28 @@ +package com.test.demo.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.web.authentication.rememberme.CookieTheftException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * @author zhangrucheng on 2023/5/21 + */ +@ControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception ex) { + log.error(ex.getLocalizedMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal Server Error" + ex.getLocalizedMessage()); + } + + @ExceptionHandler({UnAuthorizedException.class, CookieTheftException.class, IllegalArgumentException.class}) + public String handleAuthException(Exception exception) { + log.error(exception.getMessage()); + return "login"; + } +} diff --git a/src/main/java/com/test/demo/exception/UnAuthorizedException.java b/src/main/java/com/test/demo/exception/UnAuthorizedException.java new file mode 100644 index 0000000..c49c72b --- /dev/null +++ b/src/main/java/com/test/demo/exception/UnAuthorizedException.java @@ -0,0 +1,11 @@ +package com.test.demo.exception; + +/** + * @author zhangrucheng on 2023/5/23 + */ +public class UnAuthorizedException extends RuntimeException { + + public UnAuthorizedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/test/demo/mapper/UserMapper.java b/src/main/java/com/test/demo/mapper/UserMapper.java new file mode 100644 index 0000000..981b83a --- /dev/null +++ b/src/main/java/com/test/demo/mapper/UserMapper.java @@ -0,0 +1,65 @@ +package com.test.demo.mapper; + +import com.test.demo.entity.UserDo; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Optional; + +/** + * The interface User mapper. + * + * @author zhangrucheng on 2023/5/19 + */ +@Mapper +public interface UserMapper { + + /** + * Add user int. + * + * @param userDo the user + * @return the int + */ + int addUser(UserDo userDo); + + /** + * Select user by name and password optional. + * + * @param name the name + * @return the optional + */ + List selectUserByName(String name); + + + /** + * Select user by id optional. + * + * @param id the id + * @return the optional + */ + Optional selectUserById(int id); + + + /** + * Select user by condition list. + * + * @param userDo the user + * @return the list + */ + List selectUserByCondition(UserDo userDo); + + /** + * Update user. + * + * @param userDo the user + */ + void updateUser(UserDo userDo); + + /** + * Delete user by id int. + * + * @param id the id + * @return the int + */ + int deleteUserById(Integer id); +} diff --git a/src/main/java/com/test/demo/security/JWTInterceptor.java b/src/main/java/com/test/demo/security/JWTInterceptor.java new file mode 100644 index 0000000..be73c15 --- /dev/null +++ b/src/main/java/com/test/demo/security/JWTInterceptor.java @@ -0,0 +1,49 @@ +package com.test.demo.security; + +import com.auth0.jwt.exceptions.AlgorithmMismatchException; +import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.test.demo.utils.JWTUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; + +/** + * The type Jwt interceptor, if the quest without token will reject directly + * then we will validate the token using JWT + * + * @author zhangrucheng on 2023/5/21 + */ +@Slf4j +public class JWTInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + String token = request.getParameter("token"); + + HashMap map = new HashMap<>(); + try { + DecodedJWT verify = JWTUtils.verify(token); + return true; + } catch (SignatureVerificationException e) { + log.error("无效签名" + e.toString()); + map.put("msg", "无效签名"); + } catch (AlgorithmMismatchException e) { + map.put("msg", "算法不一致"); + } catch (Exception e) { + log.error("token invalid"); + map.put("msg", "Token无效"); + } + map.put("state", false); + // 把Map转换为JSON响应 + String json = new ObjectMapper().writeValueAsString(map); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().println(json); + return false; + } +} diff --git a/src/main/java/com/test/demo/security/UserDetailsServiceImpl.java b/src/main/java/com/test/demo/security/UserDetailsServiceImpl.java new file mode 100644 index 0000000..2ff0a64 --- /dev/null +++ b/src/main/java/com/test/demo/security/UserDetailsServiceImpl.java @@ -0,0 +1,43 @@ +package com.test.demo.security; + +import com.test.demo.entity.UserDo; +import com.test.demo.mapper.UserMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @author zhangrucheng on 2023/5/22 + */ + +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private UserMapper userMapper; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + + UserDo userDo = new UserDo(); + userDo.setName(username); + List userDos = userMapper.selectUserByCondition(userDo); + + if (userDos == null || userDos.isEmpty()) { + throw new UsernameNotFoundException("User name doesn't exist!"); + } + userDo = userDos.get(0); + + return new User(username, userDo.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); + } +} diff --git a/src/main/java/com/test/demo/service/UserService.java b/src/main/java/com/test/demo/service/UserService.java new file mode 100644 index 0000000..bb57ee1 --- /dev/null +++ b/src/main/java/com/test/demo/service/UserService.java @@ -0,0 +1,61 @@ +package com.test.demo.service; + +import com.test.demo.entity.UserDo; +import org.apache.ibatis.javassist.NotFoundException; + +import java.util.List; +import java.util.Optional; + +/** + * The interface User service. + * + * @author zhangrucheng on 2023/5/21 + */ +public interface UserService { + + /** + * Register user int. + * + * @param userDo the user + * @return the int + */ + int registerUser(UserDo userDo); + + /** + * Update user. + * + * @param userDo the user + */ + void updateUser(UserDo userDo); + + /** + * Delete user. + * + * @param id the id + */ + void deleteUser(int id); + + /** + * Gets user by name and password. + * + * @param name the name + * @return the user by name + */ + UserDo getUserByName(String name); + + /** + * Gets user by id. + * + * @param id the id + * @return the user by id + */ + Optional getUserById(int id); + + /** + * Gets user by condition. + * + * @param userDo the user + * @return the user by condition + */ + List getUserByCondition(UserDo userDo); +} diff --git a/src/main/java/com/test/demo/service/impl/UserServiceImpl.java b/src/main/java/com/test/demo/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..df96631 --- /dev/null +++ b/src/main/java/com/test/demo/service/impl/UserServiceImpl.java @@ -0,0 +1,75 @@ +package com.test.demo.service.impl; + +import com.test.demo.entity.UserDo; +import com.test.demo.mapper.UserMapper; +import com.test.demo.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +/** + * The type User service. + * + * @author zhangrucheng on 2023/5/21 + */ +@Service +@Slf4j +public class UserServiceImpl implements UserService { + + @Autowired + private UserMapper userMapper; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + public int registerUser(UserDo userDo) { + log.info("register a new user [{}]", userDo); + userDo.setPassword(passwordEncoder.encode(userDo.getPassword())); + return userMapper.addUser(userDo); + } + + @Override + public void updateUser(UserDo userDo) { + log.info("update user with new info [{}]", userDo); + if (!userDo.getPassword().isEmpty()) { + userDo.setPassword(passwordEncoder.encode(userDo.getPassword())); + } + userMapper.updateUser(userDo); + } + + @Override + public void deleteUser(int id) { + log.info("delete user with id [{}]", id); + userMapper.deleteUserById(id); + } + + public UserDo getUserByName(String name) { + log.info("query user with name [{}]", name); + List userDoList = userMapper.selectUserByName(name); + if (userDoList != null && !userDoList.isEmpty()) { + return userDoList.get(0); + } else { + return null; + } + } + + @Override + public Optional getUserById(int id) { + log.info("query user with id [{}]", id); + return userMapper.selectUserById(id); + } + + @Override + public List getUserByCondition(UserDo userDo) { + log.info("query users with condition, where the condition is [{}]", userDo); + if (!userDo.getPassword().isEmpty()) { + userDo.setPassword(passwordEncoder.encode(userDo.getPassword())); + } + return userMapper.selectUserByCondition(userDo); + } +} diff --git a/src/main/java/com/test/demo/utils/JWTUtils.java b/src/main/java/com/test/demo/utils/JWTUtils.java new file mode 100644 index 0000000..2eeeb4e --- /dev/null +++ b/src/main/java/com/test/demo/utils/JWTUtils.java @@ -0,0 +1,56 @@ +package com.test.demo.utils; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTCreator; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; + +import java.io.UnsupportedEncodingException; +import java.util.Calendar; +import java.util.Map; + +/** + * The type Jwt utils. + * + * @author zhangrucheng on 2023/5/21 + */ +public class JWTUtils { + private static final String SING = "123test"; + + + /** + * Get token string. + * + * @param map the map + * @return the string + */ + public static String getToken(Map map) { + Calendar instance = Calendar.getInstance(); + instance.add(Calendar.DATE, 7); // 默认过期时间 7天 + JWTCreator.Builder builder = JWT.create(); + // payload 设置 + map.forEach(builder::withClaim); + // 生成Token 并返回 + try { + return builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(SING)); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + /** + * verify Token + * + * @param token the token + * @return DecodedJWT 可以用来获取用户信息 + */ + public static DecodedJWT verify(String token) throws JWTVerificationException { + // 如果不抛出异常说明验证通过,否则验证失败 + try { + return JWT.require(Algorithm.HMAC256(SING)).build().verify(token); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..19b4cec --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,2 @@ +server.port=8081 +spring.thymeleaf.mode=LEGACYHTML5 \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..87b3e53 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,9 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: ruczhang + password: password + driver-class-name: com.mysql.cj.jdbc.Driver + mvc: + pathmatch: + matching-strategy: ant_path_matcher \ No newline at end of file diff --git a/src/main/resources/com/test/demo/mapper/UserMapper.xml b/src/main/resources/com/test/demo/mapper/UserMapper.xml new file mode 100644 index 0000000..df54f2e --- /dev/null +++ b/src/main/resources/com/test/demo/mapper/UserMapper.xml @@ -0,0 +1,70 @@ + + + + + insert into user(name, password, dob, description, longitude, latitude) values (#{name}, #{password}, #{dob}, + #{description}, #{longitude}, #{latitude}) + + + update user + + + , name = #{name} + + + , password = #{password} + + + , dob = #{dob} + + + , longitude = #{longitude} + + + , latitude = #{latitude} + + + , create_time = #{createTime} + + + where id = #{id} + + + delete from user where id = #{id} + + + + + \ No newline at end of file diff --git a/src/main/resources/docker/db-scripts/init.sql b/src/main/resources/docker/db-scripts/init.sql new file mode 100644 index 0000000..fc0be5a --- /dev/null +++ b/src/main/resources/docker/db-scripts/init.sql @@ -0,0 +1,25 @@ +CREATE DATABASE IF NOT EXISTS test_db + DEFAULT CHARACTER SET utf8; + +CREATE TABLE IF NOT EXISTS `persistent_logins` ( +`username` VARCHAR (64) NOT NULL, +`series` VARCHAR (64) NOT NULL, +`token` VARCHAR (64) NOT NULL, +`last_used` TIMESTAMP NOT NULL, +PRIMARY KEY (`series`) +) ENGINE = INNODB DEFAULT CHARSET = utf8; + +CREATE TABLE IF NOT EXISTS `user` +( + `id` int(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, + `name` varchar(50) NOT NULL COMMENT 'user name', + `password` varchar(100) NOT NULL COMMENT 'user password', + `dob` DATE NULL COMMENT 'date of birth', + `address` varchar(50) NULL COMMENT 'user address', + `description` varchar(100) NULL COMMENT 'user description', + `longitude` decimal(18, 15) NULL COMMENT 'user location longitude', + `latitude` decimal(18, 15) NULL COMMENT 'user location latitude', + `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_user_name_password(`name`, `password`) +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; diff --git a/src/main/resources/docker/mysql.yml b/src/main/resources/docker/mysql.yml new file mode 100644 index 0000000..8d432b4 --- /dev/null +++ b/src/main/resources/docker/mysql.yml @@ -0,0 +1,15 @@ +version: '3.1' + +services: + db: + image: mysql:8.0.29 + restart: always + environment: + MYSQL_ROOT_PASSWORD: 123456 + MYSQL_DATABASE: test + MYSQL_USER: ruczhang + MYSQL_PASSWORD: password + ports: + - "3306:3306" + volumes: + - ./db-scripts/init.sql:/docker-entrypoint-initdb.d/init.sql \ No newline at end of file diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..bff8f7c --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,11 @@ +# Global logging configuration +log4j.rootLogger=DEBUG, stdout +# Console output... +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n + +log4j.appender.logfile=org.apache.log4j.FileAppender +log4j.appender.logfile.File=temp.log +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n diff --git a/src/main/resources/mybatisConfig.xml b/src/main/resources/mybatisConfig.xml new file mode 100644 index 0000000..78246df --- /dev/null +++ b/src/main/resources/mybatisConfig.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html new file mode 100644 index 0000000..eb83591 --- /dev/null +++ b/src/main/resources/templates/error.html @@ -0,0 +1,10 @@ + + + + + Title + + +登陆失败,点击此处重新登录 + + \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 0000000..37b2fcb --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,13 @@ + + + + Hello World! + + +

Hello thymeleaf!

+
+ +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..cffb64a --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,17 @@ + + + + + Title + + +

登录页面

+
+ 账号:
+ 密码:
+ + 记住我
+ +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/main.html b/src/main/resources/templates/main.html new file mode 100644 index 0000000..97f04ce --- /dev/null +++ b/src/main/resources/templates/main.html @@ -0,0 +1,11 @@ + + + + + + Title + + +登录成功 + + \ No newline at end of file diff --git a/src/test/java/com/test/demo/DemoApplicationTests.java b/src/test/java/com/test/demo/DemoApplicationTests.java new file mode 100644 index 0000000..f1d1196 --- /dev/null +++ b/src/test/java/com/test/demo/DemoApplicationTests.java @@ -0,0 +1,13 @@ +package com.test.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DemoApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/com/test/demo/mapper/UserDaoTest.java b/src/test/java/com/test/demo/mapper/UserDaoTest.java new file mode 100644 index 0000000..e13fae4 --- /dev/null +++ b/src/test/java/com/test/demo/mapper/UserDaoTest.java @@ -0,0 +1,124 @@ +package com.test.demo.mapper; + +import com.test.demo.entity.UserDo; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; +import java.util.Optional; + +/** + * @author zhangrucheng on 2023/5/20 + */ +@SpringBootTest +public class UserDaoTest { + + private SqlSession sqlSession; + + private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + @Before + public void init() { + SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder(); + InputStream resourceAsStream = null; + try { + resourceAsStream = Resources.getResourceAsStream("mybatisConfig.xml"); + } catch (IOException e) { + e.printStackTrace(); + } + + SqlSessionFactory factory = ssfb.build(resourceAsStream); + sqlSession = factory.openSession(true); + } + + @After + public void release() { + sqlSession.close(); + } + + @Test + public void testInsert() { + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + for (int i = 0; i < 10; i++) { + UserDo userDo = new UserDo(); + userDo.setName("aaa" + i); + Calendar calendar = Calendar.getInstance(); + calendar.set(1990, 6, 6); + userDo.setDob(calendar.getTime()); + userDo.setDescription("this is first guy " + i); + userDo.setPassword(passwordEncoder.encode("password" + i)); + userDo.setLatitude(new Double(123.12)); + userDo.setLongitude(new Double(445.23)); + userMapper.addUser(userDo); + System.out.println(userDo.getId()); + } + } + + @Test + public void testUpdateDOB() throws ParseException { + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + UserDo userDo = new UserDo(); + userDo.setId(6); + userDo.setDob(new SimpleDateFormat("yyyy-mm-dd").parse("1970-05-09")); + userMapper.updateUser(userDo); + System.out.println(userDo); + UserDo expected = new UserDo(); + expected.setId(6); + List userDos = userMapper.selectUserByCondition(expected); + for (UserDo test : userDos) { + System.out.println(test); + } + } + + @Test + public void testUpdate() throws ParseException { + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + UserDo userDo = new UserDo(); + userDo.setId(6); + userDo.setName("updated_name"); + userDo.setCreateTime(new SimpleDateFormat("yyyy-mm-dd").parse("1970-05-09")); + userMapper.updateUser(userDo); + UserDo expected = new UserDo(); + expected.setId(6); + List userDos = userMapper.selectUserByCondition(expected); + for (UserDo test : userDos) { + System.out.println(test); + } + } + + @Test + public void testQueryByCondition() { + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + UserDo userDo = new UserDo(); + userDo.setName("rucheng"); + List userDos = userMapper.selectUserByCondition(userDo); + for (UserDo test : userDos) { + System.out.println(test); + Assert.assertEquals("rucheng", test.getName()); + } + } + + @Test + public void testDeleteUser() { + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + userMapper.deleteUserById(9); + UserDo userDo = new UserDo(); + userDo.setId(9); + List userDos = userMapper.selectUserByCondition(userDo); + Assert.assertTrue(userDos.isEmpty()); + } +}