├── .deepsource.toml ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── allen │ │ └── moments │ │ └── v2 │ │ ├── MomentsApplication.java │ │ ├── aop │ │ ├── ExceptionHandler.java │ │ └── LoggingHandler.java │ │ ├── api │ │ ├── AuthController.java │ │ ├── PostController.java │ │ └── UserController.java │ │ ├── dao │ │ ├── CommentDao.java │ │ ├── CommentSqlProvider.java │ │ ├── PostDao.java │ │ ├── PostSqlProvider.java │ │ ├── UserDao.java │ │ └── UserSqlProvider.java │ │ ├── interceptors │ │ ├── AuthInterceptor.java │ │ └── InterceptorConfig.java │ │ ├── model │ │ ├── Comment.java │ │ ├── DML.java │ │ ├── ErrorType.java │ │ ├── Like.java │ │ ├── Photo.java │ │ ├── Post.java │ │ └── User.java │ │ ├── redis │ │ ├── RedisConfig.java │ │ └── RedisUtil.java │ │ ├── service │ │ ├── AuthService.java │ │ ├── PostService.java │ │ ├── S3Service.java │ │ └── UserService.java │ │ └── utils │ │ ├── ApplicationException.java │ │ ├── JsonResult.java │ │ ├── JwtUtil.java │ │ ├── S3Client.java │ │ ├── ThreadPoolManager.java │ │ ├── annotations │ │ ├── PassToken.java │ │ └── RequireToken.java │ │ └── error_handler │ │ └── DBExceptionChecker.java └── resources │ ├── META-INF │ └── additional-spring-configuration-metadata.json │ ├── log4j2.xml │ └── mybatis-generator-config.xml └── test └── java └── com └── allen └── moments └── v2 └── MomentsApplicationTests.java /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "java" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | runtime_version = "8" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | /src/main/resources/application.properties 7 | /src/main/resources/mybatis-generator-config.xml 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | 18 | ### IntelliJ IDEA ### 19 | .idea 20 | *.iws 21 | *.iml 22 | *.ipr 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | /** 26 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 27 | */ 28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 30 | 31 | /** 32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 33 | * use instead of the default one. 34 | */ 35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 36 | ".mvn/wrapper/maven-wrapper.properties"; 37 | 38 | /** 39 | * Path where the maven-wrapper.jar will be saved to. 40 | */ 41 | private static final String MAVEN_WRAPPER_JAR_PATH = 42 | ".mvn/wrapper/maven-wrapper.jar"; 43 | 44 | /** 45 | * Name of the property which should be used to override the default download url for the wrapper. 46 | */ 47 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 48 | 49 | public static void main(String args[]) { 50 | System.out.println("- Downloader started"); 51 | File baseDirectory = new File(args[0]); 52 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 53 | 54 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 55 | // wrapperUrl parameter. 56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 57 | String url = DEFAULT_DOWNLOAD_URL; 58 | if (mavenWrapperPropertyFile.exists()) { 59 | FileInputStream mavenWrapperPropertyFileInputStream = null; 60 | try { 61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 62 | Properties mavenWrapperProperties = new Properties(); 63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 65 | } catch (IOException e) { 66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 67 | } finally { 68 | try { 69 | if (mavenWrapperPropertyFileInputStream != null) { 70 | mavenWrapperPropertyFileInputStream.close(); 71 | } 72 | } catch (IOException e) { 73 | // Ignore ... 74 | } 75 | } 76 | } 77 | System.out.println("- Downloading from: " + url); 78 | 79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 80 | if (!outputFile.getParentFile().exists()) { 81 | if (!outputFile.getParentFile().mkdirs()) { 82 | System.out.println( 83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 84 | } 85 | } 86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 87 | try { 88 | downloadFileFromURL(url, outputFile); 89 | System.out.println("Done"); 90 | System.exit(0); 91 | } catch (Throwable e) { 92 | System.out.println("- Error downloading"); 93 | e.printStackTrace(); 94 | System.exit(1); 95 | } 96 | } 97 | 98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 100 | String username = System.getenv("MVNW_USERNAME"); 101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 102 | Authenticator.setDefault(new Authenticator() { 103 | @Override 104 | protected PasswordAuthentication getPasswordAuthentication() { 105 | return new PasswordAuthentication(username, password); 106 | } 107 | }); 108 | } 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllenSun-HM/moments_v2_backend/6314021a343fbabfd880bf8a73916f014d8ee481/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # moments_v2_backend (Work In Progress) 2 | backend for a sharing app using SpringBoot, Redis, MySQL, and AWS S3. 3 | 4 | This is the second version of my project [ShareOurDays](https://github.com/AllenSun-HM/ShareOurDays)'s backend. I added more features like getting most-popular users or posts. 5 | I built this project while learning and using SpringBoot, Redis, and MySQL. 6 | 7 | ### Highlights 8 | 1. applied AOP in exception handling, logging, and access control, which substansively removed redundant code. 9 | 2. integrated multithreading and thread pool to take advantage of mutil-core CPU. 10 | 3. added cache layer using Redis to enhance performance 11 | 4. used MyBatis to achieve ORM 12 | ### How to Maintain the Consistency between MySQL and Redis? 13 | #### Query Data Process 14 | ![Query Data Process](https://general-pics-allen.s3.ap-northeast-2.amazonaws.com/github_moments_v2_backend/Query+Data+Process+(1).png) 15 | ### Delete Data Process 16 | ![Delete Data Process](https://general-pics-allen.s3.ap-northeast-2.amazonaws.com/github_moments_v2_backend/Delete+Data+Process+(1).png) 17 | ### Insert/Update Data Process 18 | ![Insert/Update Data Process](https://general-pics-allen.s3.ap-northeast-2.amazonaws.com/github_moments_v2_backend/Insert_Update+Data+Process+(3).png) 19 | 20 | ### TODOS 21 | 1. add ElasticSearch for user & post searching feature; 22 | 2. configure MySQL clusters to separate db-read and db-write to MySQL master node and slave node; 23 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.4 9 | 10 | 11 | com.allen 12 | MomentsV2 13 | 0.0.2-SNAPSHOT 14 | moments_app_v2 15 | a project that builds a website for daily sharing 16 | 17 | 1.8 18 | 1.3.2 19 | 20 | 21 | 22 | repository.spring.release 23 | Spring GA Repository 24 | https://repo.spring.io/plugins-release/ 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jdbc 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-logging 35 | 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-data-redis 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-logging 45 | 46 | 47 | 48 | 49 | org.apache.commons 50 | commons-pool2 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-jdbc 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-web 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-logging 63 | 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-devtools 69 | runtime 70 | true 71 | 72 | 73 | mysql 74 | mysql-connector-java 75 | runtime 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-starter-test 80 | test 81 | 82 | 83 | com.alibaba 84 | druid 85 | 1.0.5 86 | 87 | 88 | 89 | org.mybatis.spring.boot 90 | mybatis-spring-boot-starter 91 | 1.3.2 92 | 93 | 94 | org.mybatis.generator 95 | mybatis-generator-core 96 | ${mybatis.generator.version} 97 | 98 | 99 | com.auth0 100 | java-jwt 101 | 3.4.0 102 | 103 | 104 | com.alibaba 105 | fastjson 106 | 1.2.37 107 | 108 | 109 | junit 110 | junit 111 | 112 | 113 | 114 | com.amazonaws 115 | aws-java-sdk-s3 116 | 1.11.327 117 | 118 | 119 | com.amazonaws 120 | aws-java-sdk-dynamodb 121 | 1.11.327 122 | 123 | 124 | 125 | org.springframework.boot 126 | spring-boot-starter-aop 127 | 128 | 129 | 130 | 131 | org.springframework.boot 132 | spring-boot-starter-log4j2 133 | 134 | 135 | 136 | org.jetbrains 137 | annotations 138 | 13.0 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | org.springframework.boot 147 | spring-boot-maven-plugin 148 | 2.1.5.RELEASE 149 | 150 | 151 | org.mybatis.generator 152 | mybatis-generator-maven-plugin 153 | 1.3.6 154 | 155 | src/main/resources/mybatis-generator-config.xml 156 | true 157 | true 158 | true 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/MomentsApplication.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication() 8 | @MapperScan("com.allen.moments.v2.dao") 9 | public class MomentsApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(MomentsApplication.class, args); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/aop/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.aop; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.allen.moments.v2.utils.ApplicationException; 5 | import com.allen.moments.v2.utils.JsonResult; 6 | import org.aspectj.lang.JoinPoint; 7 | import org.aspectj.lang.annotation.AfterThrowing; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | public class ExceptionHandler { 12 | 13 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 14 | 15 | @AfterThrowing(pointcut = "execution(public * com.allen.moments.v2.api.*.*(..))", throwing="e") 16 | public JsonResult doAfterThrowing(JoinPoint joinPoint, Throwable e) { 17 | try { 18 | logger.error("------->Error Class:" + e.getClass().getName()); 19 | logger.error("------->Error msg:" + e.getMessage()); 20 | logger.error("------->Error method:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")); 21 | Object[] arguments = joinPoint.getArgs(); 22 | if (arguments != null && arguments.length > 0) { 23 | for ( int i = 0; i < arguments.length; i++) { 24 | logger.error("------->args[" + i + "]: " + JSONObject.toJSONString(arguments[i])); 25 | } 26 | } 27 | if (e.getClass() == ApplicationException.class) { // customized exception with err_no and message 28 | return JsonResult.failure(((ApplicationException) e).getErrNo(), e.getMessage()); 29 | } 30 | return JsonResult.unknownFailure(); 31 | } catch (Exception ex) { 32 | logger.error("------->exception occured!"); 33 | logger.error("------->exception message:{}", ex.getMessage()); 34 | return JsonResult.unknownFailure(); 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/aop/LoggingHandler.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.aop; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | import org.aspectj.lang.JoinPoint; 7 | import org.aspectj.lang.annotation.*; 8 | import org.springframework.stereotype.Component; 9 | 10 | 11 | @Aspect 12 | @Component 13 | public class LoggingHandler { 14 | 15 | private final Logger logger = LogManager.getLogger(this.getClass()); 16 | 17 | /** 18 | * 19 | */ 20 | @Before(value = "execution(public * com.allen.moments.v2.api.*.*(..))") 21 | public void before(JoinPoint joinPoint) { 22 | String className = joinPoint.getTarget().getClass().getName(); 23 | String methodName = joinPoint.getSignature().getName(); 24 | Object[] args = joinPoint.getArgs(); 25 | logger.info("class " + className + "'s " + methodName + " has parameter(s):" + JSONObject.toJSONString(args)); 26 | } 27 | 28 | /** 29 | * after method return, print returned value 30 | */ 31 | @AfterReturning(value = "execution(public * com.allen.moments.v2.api.*.*(..))", returning = "returnVal") 32 | public void afterReturning(JoinPoint joinPoint, Object returnVal) { 33 | String className = joinPoint.getTarget().getClass().getName(); 34 | String methodName = joinPoint.getSignature().getName(); 35 | logger.info("class " + className + "'s " + methodName + " returns: " + returnVal.toString()); 36 | } 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/api/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.api; 2 | 3 | import com.allen.moments.v2.model.User; 4 | import com.allen.moments.v2.service.AuthService; 5 | import com.allen.moments.v2.utils.JsonResult; 6 | import com.allen.moments.v2.utils.JwtUtil; 7 | import com.allen.moments.v2.utils.annotations.PassToken; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import java.util.HashMap; 16 | 17 | @RestController 18 | @RequestMapping("/api/v1/login") 19 | public class AuthController { 20 | 21 | private final AuthService authService; 22 | private final JwtUtil jwtUtil; 23 | 24 | @Autowired 25 | public AuthController(AuthService authService, JwtUtil jwtUtil) { 26 | this.authService = authService; 27 | this.jwtUtil = jwtUtil; 28 | } 29 | 30 | 31 | @PassToken 32 | @PostMapping("") 33 | public JsonResult login(HttpServletRequest request, @RequestBody String email, @RequestBody String password) { 34 | String token = request.getHeader("token"); 35 | if (token != null) { 36 | return JsonResult.failure(100002, "user already logged in"); 37 | } 38 | try { 39 | User user = authService.login(email, password); 40 | token = this.jwtUtil.getToken(user.getUid()); 41 | HashMap data = new HashMap<>(); 42 | data.put("token", token); 43 | data.put("user_info", user); 44 | return JsonResult.successWithData(data); 45 | } 46 | catch (RuntimeException exception) { 47 | exception.printStackTrace(); 48 | return JsonResult.failure(100003, exception.getMessage()); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/api/PostController.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.api; 2 | 3 | import com.allen.moments.v2.model.Post; 4 | import com.allen.moments.v2.service.PostService; 5 | import com.allen.moments.v2.service.S3Service; 6 | import com.allen.moments.v2.utils.JsonResult; 7 | import com.allen.moments.v2.utils.annotations.RequireToken; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.*; 10 | import org.springframework.web.multipart.MultipartFile; 11 | import org.springframework.dao.*; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | 17 | 18 | @RestController 19 | @RequestMapping("/api/v1/post") 20 | public class PostController { 21 | private final PostService postService; 22 | private final S3Service s3Service; 23 | 24 | @Autowired 25 | public PostController(PostService postService, S3Service s3Service) { 26 | this.postService = postService; 27 | this.s3Service = s3Service; 28 | } 29 | 30 | @PostMapping() 31 | @RequireToken 32 | public JsonResult addPost(HttpServletRequest request, @RequestBody String text, @RequestBody List photos) { 33 | int uid = (Integer) request.getAttribute("logged_uid"); 34 | try { 35 | int postId; 36 | if (photos != null && photos.size() > 0) { // photos are uploaded 37 | List photoUrls = s3Service.upload(photos); 38 | postId = postService.addPostWithPhotos(uid, text, photoUrls); 39 | } else { // no photos uploaded 40 | postId = postService.addPost(uid, text); 41 | } 42 | HashMap result = new HashMap<>(); 43 | result.put("post_id", postId); 44 | return JsonResult.successWithData(result); 45 | } 46 | catch (Exception exception) { 47 | return JsonResult.failure(200001, "post upload failed"); 48 | } 49 | } 50 | 51 | @PostMapping("/like/{postId}") 52 | @RequireToken 53 | public JsonResult likePost(HttpServletRequest request, HttpServletResponse response, @PathVariable("postId") Integer postId) { 54 | int uid = (Integer) request.getAttribute("logged_uid"); 55 | JsonResult result; 56 | try { 57 | if (postId == null) { 58 | throw new Exception("illegal query parameter"); 59 | } 60 | if (postService.likeOrUnlike(true, uid, postId)) { 61 | return JsonResult.success(); 62 | } 63 | else return JsonResult.unknownFailure(); 64 | } 65 | catch (Exception exception) { 66 | if (exception.getClass() == DuplicateKeyException.class) { 67 | return JsonResult.failure(200003, "aleady liked this post"); 68 | } 69 | return JsonResult.failure(200009, exception.getMessage()); 70 | } 71 | } 72 | 73 | @GetMapping("like/{#postId}") 74 | @RequireToken() 75 | public JsonResult getUidsWhoLikedThis(@PathVariable("postId") Integer postId, @RequestParam Integer start, @RequestParam Integer limit) { 76 | if (postId == null || start == null || limit == null) { 77 | return JsonResult.failure(200010, "illegal query parameter"); 78 | } 79 | if (limit > 200) { 80 | return JsonResult.failure(200011, "query range is too big"); 81 | } 82 | return JsonResult.successWithData(postService.getUsersWhoLikedPosts(postId, start, limit)); 83 | } 84 | 85 | @DeleteMapping("/like/{postId}") 86 | @RequireToken 87 | public JsonResult UnlikePost(HttpServletRequest request, HttpServletResponse response, @PathVariable("postId") Integer postId) { 88 | int uid = (Integer) request.getAttribute("logged_uid"); 89 | JsonResult result; 90 | try { 91 | if (postId == null) { 92 | throw new Exception("illegal query parameter"); 93 | } 94 | if (postService.likeOrUnlike(false, uid, postId)) { 95 | return JsonResult.success(); 96 | } 97 | else return JsonResult.unknownFailure(); 98 | } 99 | catch (Exception exception) { 100 | return JsonResult.failure(200003, exception.getMessage()); 101 | } 102 | } 103 | 104 | @PostMapping("/comment/{postId}") 105 | @RequireToken 106 | public JsonResult addComment(HttpServletRequest request, HttpServletResponse response, @PathVariable("postId") Integer postId, @RequestBody String comment) { 107 | int uid = (Integer) request.getAttribute("logged_uid"); 108 | JsonResult result; 109 | try { 110 | if (postId == null || comment == null) { 111 | throw new Exception("illegal query parameter"); 112 | } 113 | if (postService.addComment(uid, postId, comment)) { 114 | return JsonResult.success(); 115 | } 116 | else return JsonResult.unknownFailure(); 117 | } 118 | catch (Exception exception) { 119 | return JsonResult.failure(200003, exception.getMessage()); 120 | } 121 | } 122 | 123 | @DeleteMapping("/comment/{postId}/{commentId}") 124 | @RequireToken 125 | public JsonResult removeComment(HttpServletRequest request, HttpServletResponse response, @PathVariable("commentId") Integer commentId) { 126 | int uid = (Integer) request.getAttribute("logged_uid"); 127 | JsonResult result; 128 | try { 129 | if (commentId == null) { 130 | throw new Exception("illegal query parameter"); 131 | } 132 | if (postService.deleteComment(commentId, uid)) { 133 | return JsonResult.success(); 134 | } 135 | else return JsonResult.unknownFailure(); 136 | } 137 | catch (Exception exception) { 138 | return JsonResult.failure(200003, exception.getMessage()); 139 | } 140 | } 141 | 142 | @GetMapping("/{postId}") 143 | @RequireToken 144 | public JsonResult getPost(@PathVariable("postId") int postId) { 145 | Post post = postService.getPost(postId); 146 | JsonResult result; 147 | if (post != null) { 148 | post.setJsonPhotoUrls(null); 149 | return JsonResult.successWithData(post); 150 | } 151 | return JsonResult.failure(200002, "no post found"); 152 | } 153 | 154 | @GetMapping("/rank/like_count") 155 | @RequireToken 156 | public JsonResult getPostsWithHighestLikeCounts(@RequestParam Integer start, @RequestParam Integer limit) { 157 | if (start == null || limit == null) { 158 | return JsonResult.failure(200010, "illegal query parameter"); 159 | } 160 | List posts = postService.getPostsWithHighestLikeCounts(start, limit); 161 | if (posts == null) { 162 | return JsonResult.failure(400001, "no more posts"); 163 | } 164 | return JsonResult.successWithData(posts); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/api/UserController.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.api; 2 | 3 | import com.allen.moments.v2.model.User; 4 | import com.allen.moments.v2.service.S3Service; 5 | import com.allen.moments.v2.service.UserService; 6 | import com.allen.moments.v2.utils.JsonResult; 7 | import com.allen.moments.v2.utils.JwtUtil; 8 | import com.allen.moments.v2.utils.annotations.RequireToken; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.*; 11 | import org.springframework.web.multipart.MultipartFile; 12 | 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import java.util.List; 16 | 17 | /** 18 | * handle user info CRUD 19 | * 20 | */ 21 | @RestController 22 | @RequestMapping("/api/v1/user") 23 | @RequireToken 24 | public class UserController { 25 | private final UserService userService; 26 | private final S3Service s3Service; 27 | private final JwtUtil jwtUtil; 28 | 29 | @Autowired 30 | public UserController(UserService userService, S3Service s3Service, JwtUtil jwtUtil) { 31 | this.userService = userService; 32 | this.s3Service = s3Service; 33 | this.jwtUtil = jwtUtil; 34 | } 35 | 36 | private int getLoggedUid(HttpServletRequest request) { 37 | return (int) request.getAttribute("logged_uid"); 38 | } 39 | 40 | @PostMapping("/register") 41 | @RequireToken 42 | public JsonResult register(@RequestBody String name, @RequestBody String email, @RequestBody int sex, @RequestBody int age, @RequestBody String password) throws Exception { 43 | User addedUser = userService.addUser(email, name, sex, age, password); 44 | String token = jwtUtil.getToken(addedUser.getUid()); 45 | return JsonResult.successWithData(token); 46 | } 47 | 48 | 49 | @GetMapping("/{id}") 50 | @RequireToken 51 | public JsonResult getUserById(@PathVariable("id") int uid) { 52 | User user = userService.getUser(uid); 53 | if (user == null) { 54 | return JsonResult.failure(40001, "user does not exist"); 55 | } 56 | return JsonResult.successWithData(user); 57 | } 58 | 59 | @PostMapping("/password") 60 | @RequireToken 61 | public JsonResult setNewPassword(HttpServletRequest request, @RequestParam("old_passwd") String oldPassword, @RequestParam("new_passwd") String newPassword) throws Exception { 62 | int uid = (int) request.getAttribute("loggedUid"); 63 | userService.setNewPassword(uid, oldPassword, newPassword); 64 | return JsonResult.success(); 65 | } 66 | 67 | @GetMapping("/get_all") 68 | @RequireToken 69 | public List showAllUsers() { 70 | return userService.getAllUsers(); 71 | } 72 | 73 | @PostMapping("/follow") 74 | @RequireToken 75 | public JsonResult follow(HttpServletRequest request, @RequestParam("uid_to_follow") int uidToFollow) { 76 | int uidOfFollowed = this.getLoggedUid(request); 77 | userService.follow(uidOfFollowed, uidToFollow); 78 | return JsonResult.success(); 79 | } 80 | 81 | @GetMapping("/{id}/followers") 82 | @RequireToken 83 | public JsonResult getFollowers(@PathVariable("id") int uid) { 84 | return JsonResult.successWithData(userService.getFollowersId(uid)); 85 | } 86 | 87 | @GetMapping("/{id}/followings") 88 | @RequireToken 89 | public JsonResult getFollowings(@PathVariable("id") int uid) { 90 | return JsonResult.successWithData(userService.getFollowingsId(uid)); 91 | } 92 | 93 | @DeleteMapping("/unfollow/{id}") 94 | @RequireToken 95 | public JsonResult unfollow(HttpServletRequest request, @PathVariable("id") int followedId) throws Exception { 96 | int uidOfFollower = this.getLoggedUid(request); 97 | userService.unfollow(followedId, uidOfFollower); 98 | return JsonResult.success(); 99 | } 100 | 101 | @GetMapping("/rank/follower") 102 | @RequireToken 103 | public JsonResult getPopularUsers(HttpServletRequest request, @RequestParam int start, @RequestParam int limit) throws Exception { 104 | return JsonResult.successWithData(userService.selectUsersOrderByFollowerCounts(start, limit)); 105 | } 106 | 107 | @PostMapping("/avatar") 108 | @RequireToken 109 | public JsonResult setCustomizedAvatar(HttpServletRequest request, @RequestBody MultipartFile avatar) throws Exception { 110 | int uid = this.getLoggedUid(request); 111 | String avatarURI = s3Service.upload(avatar); 112 | userService.addCustomizedAvatar(uid, avatarURI); 113 | return JsonResult.successWithData(avatarURI); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/dao/CommentDao.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.dao; 2 | 3 | import com.allen.moments.v2.model.Comment; 4 | import org.apache.ibatis.annotations.Delete; 5 | import org.apache.ibatis.annotations.Insert; 6 | import org.apache.ibatis.annotations.InsertProvider; 7 | import org.apache.ibatis.annotations.Result; 8 | import org.apache.ibatis.annotations.Results; 9 | import org.apache.ibatis.annotations.Select; 10 | import org.apache.ibatis.annotations.Update; 11 | import org.apache.ibatis.annotations.UpdateProvider; 12 | import org.apache.ibatis.type.JdbcType; 13 | 14 | public interface CommentDao { 15 | @Delete({ 16 | "delete from post_comment", 17 | "where comment_id = #{commentId,jdbcType=INTEGER}" 18 | }) 19 | int deleteByPrimaryKey(Integer commentId); 20 | 21 | @Insert({ 22 | "insert into post_comment (comment_id, postid, ", 23 | "comment, commentedBy)", 24 | "values (#{commentId,jdbcType=INTEGER}, #{postid,jdbcType=INTEGER}, ", 25 | "#{comment,jdbcType=VARCHAR}, #{commentedby,jdbcType=INTEGER})" 26 | }) 27 | int insert(Comment record); 28 | 29 | @InsertProvider(type=CommentSqlProvider.class, method="insertSelective") 30 | int insertSelective(Comment record); 31 | 32 | @Select({ 33 | "select", 34 | "comment_id, postid, comment, commentedBy", 35 | "from post_comment", 36 | "where comment_id = #{commentId,jdbcType=INTEGER}" 37 | }) 38 | @Results({ 39 | @Result(column="comment_id", property="commentId", jdbcType=JdbcType.INTEGER, id=true), 40 | @Result(column="postid", property="postid", jdbcType=JdbcType.INTEGER), 41 | @Result(column="comment", property="comment", jdbcType=JdbcType.VARCHAR), 42 | @Result(column="commentedBy", property="commentedby", jdbcType=JdbcType.INTEGER) 43 | }) 44 | Comment selectByPrimaryKey(Integer commentId); 45 | 46 | @UpdateProvider(type=CommentSqlProvider.class, method="updateByPrimaryKeySelective") 47 | int updateByPrimaryKeySelective(Comment record); 48 | 49 | @Update({ 50 | "update post_comment", 51 | "set postid = #{postid,jdbcType=INTEGER},", 52 | "comment = #{comment,jdbcType=VARCHAR},", 53 | "commentedBy = #{commentedby,jdbcType=INTEGER}", 54 | "where comment_id = #{commentId,jdbcType=INTEGER}" 55 | }) 56 | int updateByPrimaryKey(Comment record); 57 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/dao/CommentSqlProvider.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.dao; 2 | 3 | import com.allen.moments.v2.model.Comment; 4 | import org.apache.ibatis.jdbc.SQL; 5 | 6 | public class CommentSqlProvider { 7 | 8 | public String insertSelective(Comment record) { 9 | SQL sql = new SQL(); 10 | sql.INSERT_INTO("post_comment"); 11 | 12 | if (record.getCommentId() != null) { 13 | sql.VALUES("comment_id", "#{commentId,jdbcType=INTEGER}"); 14 | } 15 | 16 | if (record.getPostid() != null) { 17 | sql.VALUES("postid", "#{postid,jdbcType=INTEGER}"); 18 | } 19 | 20 | if (record.getComment() != null) { 21 | sql.VALUES("comment", "#{comment,jdbcType=VARCHAR}"); 22 | } 23 | 24 | if (record.getCommentedby() != null) { 25 | sql.VALUES("commentedBy", "#{commentedby,jdbcType=INTEGER}"); 26 | } 27 | 28 | return sql.toString(); 29 | } 30 | 31 | public String updateByPrimaryKeySelective(Comment record) { 32 | SQL sql = new SQL(); 33 | sql.UPDATE("post_comment"); 34 | 35 | if (record.getPostid() != null) { 36 | sql.SET("postid = #{postid,jdbcType=INTEGER}"); 37 | } 38 | 39 | if (record.getComment() != null) { 40 | sql.SET("comment = #{comment,jdbcType=VARCHAR}"); 41 | } 42 | 43 | if (record.getCommentedby() != null) { 44 | sql.SET("commentedBy = #{commentedby,jdbcType=INTEGER}"); 45 | } 46 | 47 | sql.WHERE("comment_id = #{commentId,jdbcType=INTEGER}"); 48 | 49 | return sql.toString(); 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/dao/PostDao.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.dao; 2 | 3 | import com.allen.moments.v2.model.Post; 4 | import org.apache.ibatis.annotations.Delete; 5 | import org.apache.ibatis.annotations.Insert; 6 | import org.apache.ibatis.annotations.InsertProvider; 7 | import org.apache.ibatis.annotations.Result; 8 | import org.apache.ibatis.annotations.Results; 9 | import org.apache.ibatis.annotations.Select; 10 | import org.apache.ibatis.annotations.Update; 11 | import org.apache.ibatis.annotations.UpdateProvider; 12 | import org.apache.ibatis.type.JdbcType; 13 | import org.springframework.stereotype.Repository; 14 | 15 | import java.util.List; 16 | 17 | @Repository 18 | public interface PostDao { 19 | @Delete({ 20 | "delete from post", 21 | "where postid = #{postid,jdbcType=INTEGER}" 22 | }) 23 | int deleteByPrimaryKey(Integer postid); 24 | 25 | @Insert({ 26 | "insert into post (postid, text, ", 27 | "posted_by, time_created, ", 28 | "like_count, photo)", 29 | "values (#{postid,jdbcType=INTEGER}, #{text,jdbcType=VARCHAR}, ", 30 | "#{postedBy,jdbcType=INTEGER}, #{timeCreated,jdbcType=TIMESTAMP}, ", 31 | "#{likeCount,jdbcType=INTEGER}, #{jsonPhotoUrls,jdbcType=LONGVARCHAR})" 32 | }) 33 | int insert(Post record); 34 | 35 | @InsertProvider(type=PostSqlProvider.class, method="insertSelective") 36 | int insertSelective(Post record); 37 | 38 | @Select({ 39 | "select", 40 | "postid, text, posted_by, time_created, like_count, photo", 41 | "from post", 42 | "where postid = #{postid,jdbcType=INTEGER}" 43 | }) 44 | @Results({ 45 | @Result(column="postid", property="postid", jdbcType=JdbcType.INTEGER, id=true), 46 | @Result(column="text", property="text", jdbcType=JdbcType.VARCHAR), 47 | @Result(column="posted_by", property="postedBy", jdbcType=JdbcType.INTEGER), 48 | @Result(column="time_created", property="timeCreated", jdbcType=JdbcType.TIMESTAMP), 49 | @Result(column="like_count", property="likeCount", jdbcType=JdbcType.INTEGER), 50 | @Result(column="photo", property="jsonPhotoUrls", jdbcType=JdbcType.LONGVARCHAR) 51 | }) 52 | Post selectByPrimaryKey(Integer postid); 53 | 54 | @Select({ 55 | "SELECT", 56 | "postid, text, posted_by, time_created, like_count, photo", 57 | "FROM post", 58 | "ORDER BY like_count DESC", 59 | "LIMIT #{start, jdbcType =INTEGER}, #{limit, jdbcType=INTEGER}" 60 | }) 61 | @Results({ 62 | @Result(column="postid", property="postid", jdbcType=JdbcType.INTEGER, id=true), 63 | @Result(column="text", property="text", jdbcType=JdbcType.VARCHAR), 64 | @Result(column="posted_by", property="postedBy", jdbcType=JdbcType.INTEGER), 65 | @Result(column="time_created", property="timeCreated", jdbcType=JdbcType.TIMESTAMP), 66 | @Result(column="like_count", property="likeCount", jdbcType=JdbcType.INTEGER), 67 | @Result(column="photo", property="jsonPhotoUrls", jdbcType=JdbcType.LONGVARCHAR) 68 | }) 69 | List getPostsWithHighestLikeCounts(int start, int limit); 70 | 71 | 72 | @UpdateProvider(type=PostSqlProvider.class, method="updateByPrimaryKeySelective") 73 | int updateByPrimaryKeySelective(Post record); 74 | 75 | // @Update({ 76 | // "update post", 77 | // "set text = #{text,jdbcType=VARCHAR},", 78 | // "posted_by = #{postedBy,jdbcType=INTEGER},", 79 | // "time_created = #{timeCreated,jdbcType=TIMESTAMP},", 80 | // "like_count = #{likeCount,jdbcType=INTEGER},", 81 | // "photo = #{jsonPhotoUrls,jdbcType=LONGVARCHAR}", 82 | // "where postid = #{postid,jdbcType=INTEGER}" 83 | // }) 84 | // int updateByPrimaryKeyWithBLOBs(Post record); 85 | // 86 | // @Update({ 87 | // "update post", 88 | // "set text = #{text,jdbcType=VARCHAR},", 89 | // "posted_by = #{postedBy,jdbcType=INTEGER},", 90 | // "time_created = #{timeCreated,jdbcType=TIMESTAMP},", 91 | // "like_count = #{likeCount,jdbcType=INTEGER}", 92 | // "where postid = #{postid,jdbcType=INTEGER}" 93 | // }) 94 | // int updateByPrimaryKey(Post record); 95 | 96 | 97 | @Select({ 98 | "SELECT max(postid)", 99 | "FROM post" 100 | }) 101 | int selectMaxPostId(); 102 | 103 | @Insert({ 104 | "INSERT INTO post_likes", 105 | "(postid, like_by)", 106 | "VALUES (#{postId,jdbcType=INTEGER}, #{uid,jdbcType=INTEGER})", 107 | }) 108 | int insertLikeRecord(int uid, int postId); 109 | 110 | @Delete({ 111 | "DELETE FROM post_likes", 112 | "WHERE postid = #{postId,jdbcType=INTEGER} and like_by = #{uid,jdbcType=VARCHAR}", 113 | }) 114 | int removeLikeRecord(int uid, int postId); 115 | 116 | @Insert({ 117 | "INSERT INTO post_comment", 118 | "(postid, comment, commented_by)", 119 | "VALUES (#{postId,jdbcType=INTEGER},#{comment,jdbcType=LONGVARCHAR}, #{commentedBy,jdbcType=VARCHAR})", 120 | }) 121 | int insertCommentRecord(int postId, String comment, int commentedBy); 122 | 123 | @Delete({ 124 | "DELETE FROM post_comment", 125 | "WHERE comment_id = #{commentId,jdbcType=INTEGER} AND commented_by = #{uid,jdbcType=INTEGER}", 126 | }) 127 | int removeCommentRecord(int commentId, int uid); 128 | 129 | @Select({ 130 | "SELECT post.postid, post.text, post.posted_by, post.time_created, post.photo, post.like_count", 131 | "FROM MomentsDB.post", 132 | "ON post.postid = post_likes.postid", 133 | "GROUP BY post.postid" 134 | }) 135 | @Results({ 136 | @Result(column="postid", property="postid", jdbcType=JdbcType.INTEGER, id=true), 137 | @Result(column="text", property="text", jdbcType=JdbcType.VARCHAR), 138 | @Result(column="posted_by", property="postedBy", jdbcType=JdbcType.INTEGER), 139 | @Result(column="time_created", property="timeCreated", jdbcType=JdbcType.TIMESTAMP), 140 | @Result(column="photo", property="jsonPhotoUrls", jdbcType=JdbcType.LONGVARCHAR), 141 | @Result(column="like_count", property="likeCount", jdbcType=JdbcType.INTEGER) 142 | }) 143 | List selectAllPosts(); 144 | 145 | @Select({ 146 | "SELECT like_by", 147 | "FROM post_likes", 148 | "WHERE post_id = #{postId}, jdbcType=INTEGER}" 149 | }) 150 | List selectUidsThatLikedPost(int postId, int start, int limit); 151 | } 152 | 153 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/dao/PostSqlProvider.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.dao; 2 | 3 | import com.allen.moments.v2.model.Post; 4 | import org.apache.ibatis.jdbc.SQL; 5 | 6 | public class PostSqlProvider { 7 | 8 | public String insertSelective(Post record) { 9 | SQL sql = new SQL(); 10 | sql.INSERT_INTO("post"); 11 | 12 | if (record.getPostid() != null) { 13 | sql.VALUES("postid", "#{postid,jdbcType=INTEGER}"); 14 | } 15 | 16 | if (record.getText() != null) { 17 | sql.VALUES("text", "#{text,jdbcType=VARCHAR}"); 18 | } 19 | 20 | if (record.getPostedBy() != null) { 21 | sql.VALUES("posted_by", "#{postedBy,jdbcType=INTEGER}"); 22 | } 23 | 24 | if (record.getTimeCreated() != null) { 25 | sql.VALUES("time_created", "#{timeCreated,jdbcType=TIMESTAMP}"); 26 | } 27 | 28 | if (record.getLikeCount() != null) { 29 | sql.VALUES("like_count", "#{likeCount,jdbcType=INTEGER}"); 30 | } 31 | 32 | if (record.getJsonPhotoUrls() != null) { 33 | sql.VALUES("photo", "#{jsonPhotoUrls,jdbcType=LONGVARCHAR}"); 34 | } 35 | 36 | return sql.toString(); 37 | } 38 | 39 | public String updateByPrimaryKeySelective(Post record) { 40 | SQL sql = new SQL(); 41 | sql.UPDATE("post"); 42 | 43 | if (record.getText() != null) { 44 | sql.SET("text = #{text,jdbcType=VARCHAR}"); 45 | } 46 | 47 | if (record.getPostedBy() != null) { 48 | sql.SET("posted_by = #{postedBy,jdbcType=INTEGER}"); 49 | } 50 | 51 | if (record.getTimeCreated() != null) { 52 | sql.SET("time_created = #{timeCreated,jdbcType=TIMESTAMP}"); 53 | } 54 | 55 | if (record.getLikeCount() != null) { 56 | sql.SET("like_count = #{likeCount,jdbcType=INTEGER}"); 57 | } 58 | 59 | if (record.getJsonPhotoUrls() != null) { 60 | sql.SET("photo = #{jsonPhotoUrls,jdbcType=LONGVARCHAR}"); 61 | } 62 | 63 | sql.WHERE("postid = #{postid,jdbcType=INTEGER}"); 64 | 65 | return sql.toString(); 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/dao/UserDao.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.dao; 2 | 3 | import com.allen.moments.v2.model.User; 4 | import org.apache.ibatis.annotations.*; 5 | import org.apache.ibatis.type.JdbcType; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface UserDao { 11 | 12 | 13 | @Delete({ 14 | "delete from user", 15 | "where uid = #{uid,jdbcType=INTEGER}" 16 | }) 17 | int deleteByUid(Integer uid); 18 | 19 | 20 | @Insert({ 21 | "insert into user (uid, name, ", 22 | "age, sex, password, email)", 23 | "values (#{uid,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, ", 24 | "#{age,jdbcType=INTEGER}, #{sex,jdbcType=INTEGER}, #{password,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR})" 25 | }) 26 | int insert(User record); 27 | 28 | 29 | 30 | @InsertProvider(type=UserSqlProvider.class, method="insertSelective") 31 | int insertSelective(User record); 32 | 33 | 34 | @Select({ 35 | "select", 36 | "uid, name, age, sex, email, avatar_uri, follower_count", 37 | "from user", 38 | "where uid = #{uid,jdbcType=INTEGER}" 39 | }) 40 | @Results({ 41 | @Result(column="uid", property="uid", jdbcType=JdbcType.INTEGER, id=true), 42 | @Result(column="name", property="name", jdbcType=JdbcType.VARCHAR), 43 | @Result(column="age", property="age", jdbcType=JdbcType.INTEGER), 44 | @Result(column="sex", property="sex", jdbcType=JdbcType.INTEGER), 45 | @Result(column="email", property="email", jdbcType=JdbcType.VARCHAR), 46 | @Result(column="follower_count", property="followerCount", jdbcType=JdbcType.INTEGER), 47 | @Result(column="avatar_uri", property="avatarURI", jdbcType=JdbcType.VARCHAR) 48 | }) 49 | User selectByUid(Integer uid); 50 | 51 | @Select({ 52 | "SELECT", 53 | "uid, name, age, sex, email, follower_count, avatar_uri, password", 54 | "FROM user", 55 | "WHERE email = #{email, jdbcType=VARCHAR}" 56 | }) 57 | @Results({ 58 | @Result(column="uid", property="uid", jdbcType=JdbcType.INTEGER, id=true), 59 | @Result(column="name", property="name", jdbcType=JdbcType.VARCHAR), 60 | @Result(column="age", property="age", jdbcType=JdbcType.INTEGER), 61 | @Result(column="sex", property="sex", jdbcType=JdbcType.INTEGER), 62 | @Result(column="email", property="email", jdbcType=JdbcType.VARCHAR), 63 | @Result(column="follower_count", property="followerCount", jdbcType=JdbcType.INTEGER), 64 | @Result(column="avatar_uri", property="avatarURI", jdbcType=JdbcType.VARCHAR), 65 | @Result(column = "password", property = "password", jdbcType = JdbcType.VARCHAR) 66 | }) 67 | User selectByEmail(String email); 68 | 69 | @Select({ 70 | "select", 71 | "uid, name, age, sex. email", 72 | "from user", 73 | }) 74 | @Results({ 75 | @Result(column="uid", property="uid", jdbcType=JdbcType.VARCHAR, id=true), 76 | @Result(column="name", property="name", jdbcType=JdbcType.VARCHAR), 77 | @Result(column="age", property="age", jdbcType=JdbcType.INTEGER), 78 | @Result(column="sex", property="sex", jdbcType=JdbcType.INTEGER), 79 | @Result(column="email", property="email", jdbcType=JdbcType.VARCHAR) 80 | }) 81 | List selectAll(); 82 | 83 | 84 | @Select({ 85 | "SELECT", 86 | "uid, name, age, sex, email, follower_count, avatar_uri", 87 | "FROM user", 88 | "ORDER BY follower_count DESC", 89 | "LIMIT #{start,jdbcType=INTEGER}, #{limit,jdbcType=INTEGER}" 90 | }) 91 | @Results({ 92 | @Result(column="uid", property="uid", jdbcType=JdbcType.INTEGER, id=true), 93 | @Result(column="name", property="name", jdbcType=JdbcType.VARCHAR), 94 | @Result(column="age", property="age", jdbcType=JdbcType.INTEGER), 95 | @Result(column="sex", property="sex", jdbcType=JdbcType.INTEGER), 96 | @Result(column="email", property="email", jdbcType=JdbcType.VARCHAR), 97 | @Result(column="follower_count", property="followerCount", jdbcType=JdbcType.INTEGER), 98 | @Result(column="avatar_uri", property="avatarURI", jdbcType=JdbcType.VARCHAR) 99 | }) 100 | List selectUsersOrderByFollowerCounts(int start, int limit); 101 | 102 | @UpdateProvider(type=UserSqlProvider.class, method="updateByPrimaryKeySelective") 103 | int updateByPrimaryKeySelective(User record); 104 | 105 | @Update({ 106 | "update user", 107 | "set name = #{name,jdbcType=VARCHAR},", 108 | "age = #{age,jdbcType=INTEGER},", 109 | "sex = #{sex,jdbcType=INTEGER},", 110 | "password = #{password,jdbcType=VARCHAR}", 111 | "where uid = #{uid,jdbcType=INTEGER}" 112 | }) 113 | int updateByUid(User record); 114 | 115 | 116 | @Update( 117 | { 118 | "update user", 119 | "set password = #{newPassword,jdbcType=VARCHAR},", 120 | "where uid = #{uid, jdbcType=INTEGER} AND password = #{oldPassword, jdbcType=VARCHAR}", 121 | } 122 | ) 123 | int updatePassword(int uid, String oldPassword, String newPassword); 124 | 125 | 126 | @Select( 127 | { 128 | "select", 129 | "max(uid)", 130 | "from user" 131 | } 132 | ) 133 | int getMaxUid(); 134 | 135 | 136 | @Insert({ 137 | "INSERT INTO follow_relations", 138 | "(followed_id, follower_id)", 139 | "VALUES (#{followedId, jdbcType=INTEGER}, #{followerId, jdbcType=INTEGER})" 140 | }) 141 | int addFollower(int followedId, int followerId); 142 | 143 | @Select({ 144 | "SELECT", 145 | "follower_id", 146 | "FROM follow_relations", 147 | "WHERE followed_id = #{uid, jdbcType=INTEGER}" 148 | }) 149 | List selectFollowersById(int uid); 150 | 151 | @Select({ 152 | "SELECT", 153 | "followed_id", 154 | "FROM follow_relations", 155 | "WHERE follower_id = #{uid, jdbcType=INTEGER}" 156 | }) 157 | List selectFollowingsById(int uid); 158 | 159 | @Delete({ 160 | "DELETE FROM follow_relations", 161 | "WHERE", 162 | "followed_id = #{followedId, jdbcType=INTEGER}", 163 | "AND", 164 | "follower_id = #{followerId, jdbcType=INTEGER}" 165 | }) 166 | int removeFollowingRelation(int followedId, int followerId); 167 | 168 | @Update({ 169 | "UPDATE user", 170 | "SET avatar_uri = #{avatarURI, jdbcType=VARCHAR}", 171 | "WHERE uid = #{uid, jdbcType=INTEGER}" 172 | }) 173 | int addAvatarURI(int uid, String avatarURI); 174 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/dao/UserSqlProvider.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.dao; 2 | 3 | import com.allen.moments.v2.model.User; 4 | import org.apache.ibatis.jdbc.SQL; 5 | 6 | public class UserSqlProvider { 7 | 8 | public String insertSelective(User record) { 9 | SQL sql = new SQL(); 10 | sql.INSERT_INTO("user"); 11 | 12 | if (record.getUid() == 0) { 13 | sql.VALUES("uid", "#{uid,jdbcType=INTEGER}"); 14 | } 15 | 16 | if (record.getName() != null) { 17 | sql.VALUES("name", "#{name,jdbcType=VARCHAR}"); 18 | } 19 | 20 | if (record.getAge() != null) { 21 | sql.VALUES("age", "#{age,jdbcType=INTEGER}"); 22 | } 23 | 24 | if (record.getSex() != null) { 25 | sql.VALUES("sex", "#{sex,jdbcType=INTEGER}"); 26 | } 27 | 28 | if (record.getPassword() != null) { 29 | sql.VALUES("password", "#{password,jdbcType=VARCHAR}"); 30 | } 31 | 32 | return sql.toString(); 33 | } 34 | 35 | public String updateByPrimaryKeySelective(User record) { 36 | SQL sql = new SQL(); 37 | sql.UPDATE("user"); 38 | 39 | if (record.getName() != null) { 40 | sql.SET("name = #{name,jdbcType=VARCHAR}"); 41 | } 42 | 43 | if (record.getAge() != null) { 44 | sql.SET("age = #{age,jdbcType=INTEGER}"); 45 | } 46 | 47 | if (record.getSex() != null) { 48 | sql.SET("sex = #{sex,jdbcType=INTEGER}"); 49 | } 50 | 51 | if (record.getPassword() != null) { 52 | sql.SET("password = #{password,jdbcType=VARCHAR}"); 53 | } 54 | 55 | sql.WHERE("uid = #{uid,jdbcType=INTEGER}"); 56 | 57 | return sql.toString(); 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/interceptors/AuthInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.interceptors; 2 | 3 | import com.allen.moments.v2.redis.RedisUtil; 4 | import com.allen.moments.v2.utils.JsonResult; 5 | import com.allen.moments.v2.utils.annotations.PassToken; 6 | import com.allen.moments.v2.utils.annotations.RequireToken; 7 | import com.auth0.jwt.JWTVerifier; 8 | import com.auth0.jwt.algorithms.Algorithm; 9 | import com.auth0.jwt.interfaces.DecodedJWT; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.web.method.HandlerMethod; 14 | import org.springframework.web.servlet.HandlerInterceptor; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpServletResponse; 18 | import java.lang.reflect.Method; 19 | 20 | @Component 21 | public class AuthInterceptor implements HandlerInterceptor { 22 | @Autowired 23 | RedisUtil redisUtil; 24 | 25 | @Override 26 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception { 27 | if (!(object instanceof HandlerMethod)) { 28 | return true; 29 | } 30 | HandlerMethod handlerMethod = (HandlerMethod) object; 31 | Method method = handlerMethod.getMethod(); 32 | // allow access to methods annotated with passToken 33 | if (method.isAnnotationPresent(PassToken.class) ) { 34 | PassToken passToken = method.getAnnotation(PassToken.class); 35 | if (passToken == null) { 36 | passToken = method.getClass().getAnnotation(PassToken.class); 37 | } 38 | if (passToken.isTokenNeedless()) { 39 | return true; 40 | } 41 | } 42 | if (method.isAnnotationPresent(RequireToken.class) || method.getClass().isAnnotationPresent(RequireToken.class)) { 43 | RequireToken userLoginToken = method.getAnnotation(RequireToken.class); 44 | if (userLoginToken == null){ 45 | userLoginToken = method.getClass().getAnnotation(RequireToken.class); 46 | } 47 | if (userLoginToken.isTokenNeeded()) { 48 | try { // authenticate user 49 | String authorizationHeader = request.getHeader("Authorization"); 50 | if (authorizationHeader == null) { 51 | throw new RuntimeException("no token found, login is needed"); 52 | } 53 | String token = authorizationHeader.substring(7); 54 | // authenticate token 55 | JWTVerifier jwtVerifier = com.auth0.jwt.JWT.require(Algorithm.HMAC256("${application.jwt.secret_key}")).build(); 56 | DecodedJWT jwt = jwtVerifier.verify(token); 57 | int uid; 58 | uid = (Integer.parseInt(jwt.getAudience().get(0))); 59 | request.setAttribute("logged_uid", uid); 60 | // isLogged = redisUtil.getBit("loggedUsers", uid - 10000); // use redis as the session manager 61 | // if (!isLogged) { 62 | // throw new RuntimeException("user not found, please login again"); 63 | // } 64 | } 65 | catch (Exception exception) { 66 | ObjectMapper mapper = new ObjectMapper(); 67 | System.err.println(exception.getMessage()); 68 | JsonResult jsonResult = JsonResult.failure(10000, "user not logged or login expired");// customised pojo for error json message 69 | response.setContentType("application/json"); 70 | response.setCharacterEncoding("utf-8"); 71 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 72 | response.getWriter().write(mapper.writeValueAsString(jsonResult)); 73 | return false; 74 | } 75 | 76 | } 77 | } 78 | return true; 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/interceptors/InterceptorConfig.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.interceptors; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.HandlerInterceptor; 6 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | @Configuration 10 | public class InterceptorConfig implements WebMvcConfigurer { 11 | @Override 12 | public void addInterceptors(InterceptorRegistry registry) { 13 | registry.addInterceptor(authenticationInterceptor()) 14 | .addPathPatterns("/**"); // intercept all requests and use authentication interceptor to decide whether login is needed 15 | } 16 | @Bean 17 | public HandlerInterceptor authenticationInterceptor() { 18 | return new AuthInterceptor(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/model/Comment.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.model; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | public class Comment { 9 | 10 | private Integer commentId; 11 | 12 | private Integer postid; 13 | 14 | private String comment; 15 | 16 | private Integer commentedby; 17 | 18 | public Integer getCommentId() { 19 | return commentId; 20 | } 21 | 22 | public void setCommentId(Integer commentId) { 23 | this.commentId = commentId; 24 | } 25 | 26 | public Integer getPostid() { 27 | return postid; 28 | } 29 | 30 | public void setPostid(Integer postid) { 31 | this.postid = postid; 32 | } 33 | 34 | public String getComment() { 35 | return comment; 36 | } 37 | 38 | public void setComment(String comment) { 39 | this.comment = comment == null ? null : comment.trim(); 40 | } 41 | 42 | public Integer getCommentedby() { 43 | return commentedby; 44 | } 45 | 46 | public void setCommentedby(Integer commentedby) { 47 | this.commentedby = commentedby; 48 | } 49 | 50 | @Override 51 | public boolean equals(Object that) { 52 | if (this == that) { 53 | return true; 54 | } 55 | if (that == null) { 56 | return false; 57 | } 58 | if (getClass() != that.getClass()) { 59 | return false; 60 | } 61 | Comment other = (Comment) that; 62 | return (this.getCommentId() == null ? other.getCommentId() == null : this.getCommentId().equals(other.getCommentId())) 63 | && (this.getPostid() == null ? other.getPostid() == null : this.getPostid().equals(other.getPostid())) 64 | && (this.getComment() == null ? other.getComment() == null : this.getComment().equals(other.getComment())) 65 | && (this.getCommentedby() == null ? other.getCommentedby() == null : this.getCommentedby().equals(other.getCommentedby())); 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | final int prime = 31; 71 | int result = 1; 72 | result = prime * result + ((getCommentId() == null) ? 0 : getCommentId().hashCode()); 73 | result = prime * result + ((getPostid() == null) ? 0 : getPostid().hashCode()); 74 | result = prime * result + ((getComment() == null) ? 0 : getComment().hashCode()); 75 | result = prime * result + ((getCommentedby() == null) ? 0 : getCommentedby().hashCode()); 76 | return result; 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/model/DML.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.model; 2 | 3 | public enum DML { 4 | UPDATE("update"), 5 | INSERT("insertion"), 6 | DELETE("deletion"); 7 | 8 | public final String description; 9 | 10 | DML (String description) { 11 | this.description = description; 12 | } 13 | 14 | @Override 15 | public String toString() { 16 | return "DML{" + 17 | "description='" + description + '\'' + 18 | '}'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/model/ErrorType.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.model; 2 | 3 | import java.io.Serializable; 4 | 5 | public enum ErrorType implements Serializable { 6 | 7 | // user related 8 | UNKNOWN_ERROR(100000, "unknown error"), 9 | USER_ALREADY_REGISTERED(100001, "user already registered"), 10 | USER_ALREADY_FOLLOWED(100002, "user already followed"), 11 | PASSWORD_ACCOUNT_MISMATCH(100003, "Password incorrect"), 12 | NO_FOLLOWING_RELATION(200001, "no following relationship exists"), 13 | USER_UNIDENTIFIED(200002, "cannot find user"), 14 | 15 | DIRTY_DATA(900000, "dirty data exists"), 16 | DML_ERR(900001, "DML error"); 17 | 18 | public final int errNo; 19 | public final String message; 20 | 21 | ErrorType(int errNo, String message) { 22 | this.errNo = errNo; 23 | this.message = message; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "Error{" + 29 | "code=" + errNo + 30 | ", message='" + message + '\'' + 31 | '}'; 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/model/Like.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.model; 2 | 3 | public class Like { 4 | private Integer postid; 5 | 6 | private Integer likeby; 7 | 8 | public Integer getPostid() { 9 | return postid; 10 | } 11 | 12 | public void setPostid(Integer postid) { 13 | this.postid = postid; 14 | } 15 | 16 | public Integer getLikeby() { 17 | return likeby; 18 | } 19 | 20 | public void setLikeby(Integer likeby) { 21 | this.likeby = likeby; 22 | } 23 | 24 | @Override 25 | public boolean equals(Object that) { 26 | if (this == that) { 27 | return true; 28 | } 29 | if (that == null) { 30 | return false; 31 | } 32 | if (getClass() != that.getClass()) { 33 | return false; 34 | } 35 | Like other = (Like) that; 36 | return (this.getPostid() == null ? other.getPostid() == null : this.getPostid().equals(other.getPostid())) 37 | && (this.getLikeby() == null ? other.getLikeby() == null : this.getLikeby().equals(other.getLikeby())); 38 | } 39 | 40 | @Override 41 | public int hashCode() { 42 | final int prime = 31; 43 | int result = 1; 44 | result = prime * result + ((getPostid() == null) ? 0 : getPostid().hashCode()); 45 | result = prime * result + ((getLikeby() == null) ? 0 : getLikeby().hashCode()); 46 | return result; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/model/Photo.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.model; 2 | 3 | public class Photo { 4 | private Integer postid; 5 | 6 | private String url; 7 | 8 | public Integer getPostId() { 9 | return postid; 10 | } 11 | 12 | public void setPostId(Integer postid) { 13 | this.postid = postid; 14 | } 15 | 16 | public String getUrl() { 17 | return url; 18 | } 19 | 20 | public void setUrl(String url) { 21 | this.url = url == null ? null : url.trim(); 22 | } 23 | 24 | @Override 25 | public boolean equals(Object that) { 26 | if (this == that) { 27 | return true; 28 | } 29 | if (that == null) { 30 | return false; 31 | } 32 | if (getClass() != that.getClass()) { 33 | return false; 34 | } 35 | Photo other = (Photo) that; 36 | return (this.getPostId() == null ? other.getPostId() == null : this.getPostId().equals(other.getPostId())) 37 | && (this.getUrl() == null ? other.getUrl() == null : this.getUrl().equals(other.getUrl())); 38 | } 39 | 40 | @Override 41 | public int hashCode() { 42 | final int prime = 31; 43 | int result = 1; 44 | result = prime * result + ((getPostId() == null) ? 0 : getPostId().hashCode()); 45 | result = prime * result + ((getUrl() == null) ? 0 : getUrl().hashCode()); 46 | return result; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/model/Post.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.model; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | public class Post { 9 | private Integer postid; 10 | 11 | private String text; 12 | 13 | private Integer postedBy; 14 | 15 | private Date timeCreated; 16 | 17 | private Integer likeCount; 18 | 19 | private List photoUrls; 20 | 21 | private String jsonPhotoUrls; 22 | 23 | public Post() {} 24 | 25 | public Post(Integer postid, String text, Integer postedBy, List photoUrls) { 26 | this.postid = postid; 27 | this.text = text; 28 | this.postedBy = postedBy; 29 | this.photoUrls = photoUrls; 30 | this.jsonPhotoUrls = JSONObject.toJSONString(this.photoUrls); 31 | } 32 | 33 | public Post(Integer postid, String text, Integer postedBy) { 34 | this.postid = postid; 35 | this.text = text; 36 | this.postedBy = postedBy; 37 | } 38 | 39 | public Integer getPostid() { 40 | return postid; 41 | } 42 | 43 | public void setPostid(Integer postid) { 44 | this.postid = postid; 45 | } 46 | 47 | public String getText() { 48 | return text; 49 | } 50 | 51 | public void setText(String text) { 52 | this.text = text == null ? null : text.trim(); 53 | } 54 | 55 | public Integer getPostedBy() { 56 | return postedBy; 57 | } 58 | 59 | public void setPostedBy(Integer postedBy) { 60 | this.postedBy = postedBy; 61 | } 62 | 63 | public Date getTimeCreated() { 64 | return timeCreated; 65 | } 66 | 67 | public void setTimeCreated(Date timeCreated) { 68 | this.timeCreated = timeCreated; 69 | } 70 | 71 | public Integer getLikeCount() { 72 | return likeCount; 73 | } 74 | 75 | public void setLikeCount(Integer likeCount) { 76 | this.likeCount = likeCount; 77 | } 78 | 79 | public List getPhotoUrls() { 80 | return photoUrls; 81 | } 82 | 83 | public void setPhotoUrls(List photoUrls) { 84 | this.photoUrls = photoUrls; 85 | } 86 | 87 | public String getJsonPhotoUrls() { 88 | return jsonPhotoUrls; 89 | } 90 | 91 | public void setJsonPhotoUrls(String jsonPhotoUrls) { 92 | this.jsonPhotoUrls = null; 93 | this.photoUrls = JSONObject.parseArray(jsonPhotoUrls, String.class); 94 | } 95 | 96 | @Override 97 | public boolean equals(Object that) { 98 | if (this == that) { 99 | return true; 100 | } 101 | if (that == null) { 102 | return false; 103 | } 104 | if (getClass() != that.getClass()) { 105 | return false; 106 | } 107 | Post other = (Post) that; 108 | return (this.getPostid() == null ? other.getPostid() == null : this.getPostid().equals(other.getPostid())) 109 | && (this.getText() == null ? other.getText() == null : this.getText().equals(other.getText())) 110 | && (this.getPostedBy() == null ? other.getPostedBy() == null : this.getPostedBy().equals(other.getPostedBy())) 111 | && (this.getTimeCreated() == null ? other.getTimeCreated() == null : this.getTimeCreated().equals(other.getTimeCreated())) 112 | && (this.getLikeCount() == null ? other.getLikeCount() == null : this.getLikeCount().equals(other.getLikeCount())) 113 | && (this.getPhotoUrls() == null ? other.getPhotoUrls() == null : this.getPhotoUrls().equals(other.getPhotoUrls())); 114 | } 115 | 116 | @Override 117 | public int hashCode() { 118 | final int prime = 31; 119 | int result = 1; 120 | result = prime * result + ((getPostid() == null) ? 0 : getPostid().hashCode()); 121 | result = prime * result + ((getText() == null) ? 0 : getText().hashCode()); 122 | result = prime * result + ((getPostedBy() == null) ? 0 : getPostedBy().hashCode()); 123 | result = prime * result + ((getTimeCreated() == null) ? 0 : getTimeCreated().hashCode()); 124 | result = prime * result + ((getLikeCount() == null) ? 0 : getLikeCount().hashCode()); 125 | result = prime * result + ((getPhotoUrls() == null) ? 0 : getPhotoUrls().hashCode()); 126 | return result; 127 | } 128 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/model/User.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class User { 7 | private int uid; 8 | private String name; 9 | private Integer age; 10 | private transient String password; 11 | private String email; 12 | private Integer sex; 13 | private List followers; 14 | private List followings; 15 | private Integer followerCount; 16 | private String avatarURI; 17 | 18 | public User(String name, String email, Integer uid, Integer age, Integer sex, String password) { 19 | this.name = name; 20 | this.age = age; 21 | this.password = password; 22 | this.uid = uid; 23 | this.sex = sex; 24 | this.email = email; 25 | } 26 | 27 | public User(Integer uid, String name, Integer age, Integer sex, String email) { 28 | this.name = name; 29 | this.age = age; 30 | this.uid = uid; 31 | this.sex = sex; 32 | this.email = email; 33 | } 34 | 35 | public User(Integer uid, String name, Integer age, Integer sex, String email, int followerCount) { 36 | this.name = name; 37 | this.age = age; 38 | this.uid = uid; 39 | this.sex = sex; 40 | this.email = email; 41 | this.followerCount = followerCount; 42 | } 43 | 44 | public User(Integer uid, String name, Integer age, Integer sex, String email, int followerCount, String avatarURI) { 45 | this.name = name; 46 | this.age = age; 47 | this.uid = uid; 48 | this.sex = sex; 49 | this.email = email; 50 | this.followerCount = followerCount; 51 | this.avatarURI = avatarURI; 52 | } 53 | 54 | public User(Integer uid, String name, Integer age, Integer sex, String email, int followerCount, String avatarURI, String password) { 55 | this.name = name; 56 | this.age = age; 57 | this.uid = uid; 58 | this.sex = sex; 59 | this.email = email; 60 | this.followerCount = followerCount; 61 | this.avatarURI = avatarURI; 62 | this.password = password; 63 | } 64 | 65 | public int getUid() { 66 | return uid; 67 | } 68 | 69 | public void setUid(int uid) { 70 | this.uid = uid; 71 | } 72 | 73 | public String getName() { 74 | return name; 75 | } 76 | 77 | public void setName(String name) { 78 | this.name = name == null ? null : name.trim(); 79 | } 80 | 81 | public Integer getAge() { 82 | return age; 83 | } 84 | 85 | public void setAge(Integer age) { 86 | this.age = age; 87 | } 88 | 89 | public Integer getSex() { 90 | return sex; 91 | } 92 | 93 | public void setSex(Integer sex) { 94 | this.sex = sex; 95 | } 96 | 97 | public String getEmail() { 98 | return email; 99 | } 100 | 101 | public void setEmail(String email) { 102 | this.email = email; 103 | } 104 | 105 | public List getFollowers() { 106 | return followers; 107 | } 108 | 109 | 110 | public List getFollowings() { 111 | return followings; 112 | } 113 | 114 | public void addFollowing(Integer uidToFollow) { 115 | this.followings.add(uidToFollow); 116 | } 117 | 118 | public Integer getFollowerCount() { 119 | return followerCount; 120 | } 121 | 122 | public void setFollowerCount(Integer followerCount) { 123 | this.followerCount = followerCount; 124 | } 125 | 126 | public String getAvatarURI() { 127 | return this.avatarURI; 128 | } 129 | 130 | public void setAvatarURI(String avatarURI) { 131 | this.avatarURI = avatarURI; 132 | } 133 | 134 | @Override 135 | public boolean equals(Object that) { 136 | if (this == that) { 137 | return true; 138 | } 139 | if (that == null) { 140 | return false; 141 | } 142 | if (getClass() != that.getClass()) { 143 | return false; 144 | } 145 | User other = (User) that; 146 | return (this.getUid() == 0 ? other.getUid() == 0 : this.getUid() == other.getUid()) 147 | && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName())) 148 | && (this.getAge() == null ? other.getAge() == null : this.getAge().equals(other.getAge())) 149 | && (this.getSex() == null ? other.getSex() == null : this.getSex().equals(other.getSex())); 150 | } 151 | 152 | @Override 153 | public int hashCode() { 154 | final int prime = 31; 155 | int result = 1; 156 | result = prime * result + ((getUid() == 0) ? 0 : getUid()); 157 | result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); 158 | result = prime * result + ((getAge() == null) ? 0 : getAge().hashCode()); 159 | result = prime * result + ((getSex() == null) ? 0 : getSex().hashCode()); 160 | return result; 161 | } 162 | 163 | public String getPassword() { 164 | return this.password; 165 | } 166 | public void setPassword(String password) { 167 | this.password = password; 168 | } 169 | 170 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/redis/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.redis; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 10 | import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 11 | import org.springframework.boot.autoconfigure.data.redis.RedisProperties; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.data.redis.connection.RedisClusterConfiguration; 15 | import org.springframework.data.redis.connection.RedisNode; 16 | import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; 17 | import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; 18 | import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration; 19 | import org.springframework.data.redis.core.RedisTemplate; 20 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 21 | import org.springframework.data.redis.serializer.StringRedisSerializer; 22 | 23 | import java.util.ArrayList; 24 | 25 | 26 | @Configuration 27 | @AutoConfigureAfter(RedisAutoConfiguration.class) 28 | public class RedisConfig { 29 | @Autowired 30 | RedisProperties redisProperties; 31 | 32 | @Bean 33 | public GenericObjectPoolConfig poolConfig() { 34 | GenericObjectPoolConfig config = new GenericObjectPoolConfig(); 35 | config.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle()); 36 | config.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle()); 37 | config.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive()); 38 | config.setMaxWaitMillis(redisProperties.getLettuce().getPool().getMaxWait().toMillis()); 39 | return config; 40 | } 41 | 42 | /** 43 | * @Description: add sentinel config 44 | */ 45 | @Bean 46 | public RedisClusterConfiguration configuration() { 47 | RedisClusterConfiguration redisConfig = new RedisClusterConfiguration(); 48 | // redisConfig.setPassword(RedisPassword.of(redisConfigThree.getPassword())); 49 | if(redisProperties.getSentinel().getNodes()!=null) { 50 | ArrayList nodes =new ArrayList(); 51 | for(String sen : redisProperties.getCluster().getNodes()) { 52 | String[] hostWithPort = sen.split(":"); 53 | nodes.add(new RedisNode(hostWithPort[0], Integer.parseInt(hostWithPort[1]))); 54 | } 55 | redisConfig.setClusterNodes(nodes); 56 | } 57 | return redisConfig; 58 | } 59 | 60 | 61 | @Bean("RedisConnectionFactory") 62 | public LettuceConnectionFactory RedisConnectionFactory(@Qualifier("poolConfig") GenericObjectPoolConfig config, 63 | @Qualifier("configuration") RedisClusterConfiguration redisConfig) {//注意传入的对象名和类型RedisSentinelConfiguration 64 | LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(config).build(); 65 | return new LettuceConnectionFactory(redisConfig, clientConfiguration); 66 | } 67 | 68 | 69 | @Bean(name = "redisTemplate") 70 | public RedisTemplate redisTemplate(@Qualifier("RedisConnectionFactory") LettuceConnectionFactory factory) { 71 | RedisTemplate template = new RedisTemplate<>(); 72 | template.setConnectionFactory(factory); 73 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 74 | ObjectMapper om = new ObjectMapper(); 75 | om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 76 | om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 77 | jackson2JsonRedisSerializer.setObjectMapper(om); 78 | StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); 79 | template.setKeySerializer(stringRedisSerializer); 80 | template.setHashKeySerializer(stringRedisSerializer); 81 | template.setValueSerializer(jackson2JsonRedisSerializer); 82 | template.setHashValueSerializer(jackson2JsonRedisSerializer); 83 | template.afterPropertiesSet(); 84 | return template; 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/redis/RedisUtil.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.redis; 2 | import org.springframework.beans.factory.annotation.Autowired; 3 | import org.springframework.data.redis.core.RedisTemplate; 4 | import org.springframework.data.redis.core.ZSetOperations; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.util.CollectionUtils; 7 | 8 | import java.util.*; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | @Component 12 | public final class RedisUtil { 13 | 14 | @Autowired 15 | private RedisTemplate redisTemplate; 16 | 17 | // =============================Common============================ 18 | 19 | /** 20 | * set the expiration time of a key 21 | * 22 | * @param key key 23 | * @param time time in second 24 | */ 25 | public void expire(String key, long time) { 26 | try { 27 | if (time > 0) { 28 | redisTemplate.expire(key, time, TimeUnit.SECONDS); 29 | } 30 | } catch (Exception e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | 35 | /** 36 | * get expiration time of a key 37 | * 38 | * @param key not null 39 | * @return expiration time in seconds 40 | */ 41 | public long getExpire(String key) { 42 | return redisTemplate.getExpire(key, TimeUnit.SECONDS); 43 | } 44 | 45 | /** 46 | * check if a key exists 47 | * 48 | * @return true if exists, false otherwise; 49 | */ 50 | public Boolean hasKey(String key) { 51 | try { 52 | return redisTemplate.hasKey(key); 53 | } catch (Exception e) { 54 | e.printStackTrace(); 55 | return false; 56 | } 57 | } 58 | 59 | /** 60 | * delete key 61 | * 62 | * @param key could be one or multiple keys 63 | * whether operation is success or not 64 | */ 65 | @SuppressWarnings("unchecked") 66 | public void deleteKey(String... key) { 67 | if (key != null && key.length > 0) { 68 | if (key.length == 1) { 69 | redisTemplate.delete(key[0]); 70 | } else { 71 | redisTemplate.delete((Collection) CollectionUtils.arrayToList(key)); 72 | } 73 | } 74 | } 75 | 76 | // ============================String============================= 77 | 78 | /** 79 | * get value 80 | * 81 | * @return value 82 | */ 83 | public Object get(String key) { 84 | return key == null ? null : redisTemplate.opsForValue().get(key); 85 | } 86 | 87 | 88 | public boolean set(String key, Object value) { 89 | try { 90 | redisTemplate.opsForValue().set(key, value); 91 | return true; 92 | } catch (Exception e) { 93 | e.printStackTrace(); 94 | return false; 95 | } 96 | } 97 | 98 | /** 99 | * set key and value with expiration time 100 | * 101 | * @param time expiration time in seconds, -1 for no expiration 102 | * @return true if success false otherwise 103 | */ 104 | public boolean set(String key, Object value, long time) { 105 | try { 106 | if (time > 0) { 107 | redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); 108 | } else { 109 | set(key, value); 110 | } 111 | return true; 112 | } catch (Exception e) { 113 | e.printStackTrace(); 114 | return false; 115 | } 116 | } 117 | 118 | /** 119 | * increase a key's value by certain number 120 | * 121 | * @param delta the number to add to the key's value 122 | */ 123 | public Long incr(String key, long delta) { 124 | if (delta < 0) { 125 | throw new RuntimeException("delta must be greater or equal to than zero"); 126 | } 127 | return redisTemplate.opsForValue().increment(key, delta); 128 | } 129 | 130 | /** 131 | * decrease a key's value by certain number 132 | * 133 | * @param delta the number to minus from the key's value 134 | */ 135 | public long decr(String key, long delta) { 136 | if (delta < 0) { 137 | throw new RuntimeException("delta must be greater or equal to than zero"); 138 | } 139 | return redisTemplate.opsForValue().increment(key, -delta); 140 | } 141 | 142 | // ================================Map================================= 143 | 144 | /** 145 | * HashGet 146 | * 147 | * @param key not null 148 | * @param item not null 149 | */ 150 | public Object hashGet(String key, String item) { 151 | return redisTemplate.opsForHash().get(key, item); 152 | } 153 | 154 | public Map hashGetAll(String key) { 155 | return redisTemplate.opsForHash().entries(key); 156 | } 157 | 158 | 159 | public boolean hashMSet(String key, Map map) { 160 | try { 161 | redisTemplate.opsForHash().putAll(key, map); 162 | return true; 163 | } catch (Exception e) { 164 | e.printStackTrace(); 165 | return false; 166 | } 167 | } 168 | 169 | /** 170 | * @param time expiration time 171 | * @return true if success false otherwise 172 | */ 173 | public boolean hashMSetWithExpiration(String key, Map map, long time) { 174 | try { 175 | redisTemplate.opsForHash().putAll(key, map); 176 | if (time > 0) { 177 | expire(key, time); 178 | } 179 | return true; 180 | } catch (Exception e) { 181 | e.printStackTrace(); 182 | return false; 183 | } 184 | } 185 | 186 | 187 | public boolean hashSet(String key, String item, Object value) { 188 | try { 189 | redisTemplate.opsForHash().put(key, item, value); 190 | return true; 191 | } catch (Exception e) { 192 | e.printStackTrace(); 193 | return false; 194 | } 195 | } 196 | 197 | public boolean hashSetWithExpiration(String key, String item, Object value, long time) { 198 | try { 199 | redisTemplate.opsForHash().put(key, item, value); 200 | if (time > 0) { 201 | expire(key, time); 202 | } 203 | return true; 204 | } catch (Exception e) { 205 | e.printStackTrace(); 206 | return false; 207 | } 208 | } 209 | 210 | /** 211 | * delete item(s) from hashset 212 | * 213 | * @param item could be one or multiple items 214 | */ 215 | public void hashDelete(String key, Object... item) { 216 | redisTemplate.opsForHash().delete(key, item); 217 | } 218 | 219 | 220 | public boolean hashHasKey(String key, String item) { 221 | return redisTemplate.opsForHash().hasKey(key, item); 222 | } 223 | 224 | 225 | public double hashIncr(String key, String item, double delta) { 226 | return redisTemplate.opsForHash().increment(key, item, delta); 227 | } 228 | 229 | 230 | public double hashDecr(String key, String item, double delta) { 231 | return redisTemplate.opsForHash().increment(key, item, -delta); 232 | } 233 | 234 | // ============================set============================= 235 | 236 | 237 | public Set setGetAll(String key) { 238 | try { 239 | return redisTemplate.opsForSet().members(key); 240 | } catch (Exception e) { 241 | e.printStackTrace(); 242 | return null; 243 | } 244 | } 245 | 246 | 247 | public Boolean setHasKey(String key, Object value) { 248 | try { 249 | return redisTemplate.opsForSet().isMember(key, value); 250 | } catch (Exception e) { 251 | e.printStackTrace(); 252 | return false; 253 | } 254 | } 255 | 256 | 257 | public Long setSet(String key, Object... values) { 258 | try { 259 | return redisTemplate.opsForSet().add(key, values); 260 | } catch (Exception e) { 261 | e.printStackTrace(); 262 | return 0L; 263 | } 264 | } 265 | 266 | 267 | public Long setSetWithExpiration(String key, long time, Object... values) { 268 | try { 269 | Long count = redisTemplate.opsForSet().add(key, values); 270 | if (time > 0) { 271 | expire(key, time); 272 | } 273 | return count; 274 | } catch (Exception e) { 275 | e.printStackTrace(); 276 | return 0L; 277 | } 278 | } 279 | 280 | 281 | public Long setGetSize(String key) { 282 | try { 283 | return redisTemplate.opsForSet().size(key); 284 | } catch (Exception e) { 285 | e.printStackTrace(); 286 | return 0L; 287 | } 288 | } 289 | 290 | 291 | public void setRemove(String key, Object... values) { 292 | try { 293 | redisTemplate.opsForSet().remove(key, values); 294 | } catch (Exception e) { 295 | e.printStackTrace(); 296 | } 297 | } 298 | 299 | 300 | public Object sRandomMember(String key) { 301 | return redisTemplate.opsForSet().randomMember(key); 302 | } 303 | 304 | 305 | public List sRandomMembers(String key, long count) { 306 | return redisTemplate.opsForSet().randomMembers(key, count); 307 | } 308 | 309 | // ===============================list================================= 310 | 311 | 312 | public List listGet(String key, long start, long end) { 313 | try { 314 | return redisTemplate.opsForList().range(key, start, end); 315 | } catch (Exception e) { 316 | e.printStackTrace(); 317 | return null; 318 | } 319 | } 320 | 321 | 322 | public long listGetSize(String key) { 323 | try { 324 | return redisTemplate.opsForList().size(key); 325 | } catch (Exception e) { 326 | e.printStackTrace(); 327 | return 0; 328 | } 329 | } 330 | 331 | 332 | public Object listGetByIndex(String key, long index) { 333 | try { 334 | return redisTemplate.opsForList().index(key, index); 335 | } catch (Exception e) { 336 | e.printStackTrace(); 337 | return null; 338 | } 339 | } 340 | 341 | 342 | public boolean listSet(String key, Object value) { 343 | try { 344 | redisTemplate.opsForList().rightPush(key, value); 345 | return true; 346 | } catch (Exception e) { 347 | e.printStackTrace(); 348 | return false; 349 | } 350 | } 351 | 352 | 353 | public boolean listSetWithExpiration(String key, Object value, long ttl) { 354 | try { 355 | redisTemplate.opsForList().rightPush(key, value); 356 | if (ttl > 0) { 357 | expire(key, ttl); 358 | } 359 | return true; 360 | } catch (Exception e) { 361 | e.printStackTrace(); 362 | return false; 363 | } 364 | } 365 | 366 | 367 | public boolean listMSet(String key, List value) { 368 | try { 369 | redisTemplate.opsForList().rightPushAll(key, value); 370 | return true; 371 | } catch (Exception e) { 372 | e.printStackTrace(); 373 | return false; 374 | } 375 | } 376 | 377 | 378 | public boolean listMSetWithExpiration(String key, List value, long time) { 379 | try { 380 | redisTemplate.opsForList().rightPushAll(key, value); 381 | if (time > 0) { 382 | expire(key, time); 383 | } 384 | return true; 385 | } catch (Exception e) { 386 | e.printStackTrace(); 387 | return false; 388 | } 389 | } 390 | 391 | 392 | public boolean listUpdateByIndex(String key, long index, Object value) { 393 | try { 394 | redisTemplate.opsForList().set(key, index, value); 395 | return true; 396 | } catch (Exception e) { 397 | e.printStackTrace(); 398 | return false; 399 | } 400 | } 401 | 402 | 403 | public Long listRemove(String key, long count, Object value) { 404 | try { 405 | return redisTemplate.opsForList().remove(key, count, value); 406 | } catch (Exception e) { 407 | e.printStackTrace(); 408 | return 0L; 409 | } 410 | 411 | } 412 | 413 | // ======================================== bitmap ======================================== 414 | /** 415 | * @return whether operation is success or not 416 | */ 417 | public boolean bitGet(String key, int offset) { 418 | try { 419 | return redisTemplate.opsForValue().getBit(key, offset); 420 | } catch (Exception exception) { 421 | exception.printStackTrace(); 422 | return false; 423 | } 424 | } 425 | 426 | /** 427 | * @return whether operation is success or not 428 | */ 429 | public boolean bitSet(String key, int offset, boolean value) { 430 | try { 431 | redisTemplate.opsForValue().setBit(key, offset, value); 432 | return true; 433 | } catch (Exception exception) { 434 | exception.printStackTrace(); 435 | return false; 436 | } 437 | } 438 | 439 | 440 | 441 | // ================================== zSet ========================================== 442 | public Boolean zAdd(String key, String value, double score) { 443 | return redisTemplate.opsForZSet().add(key, value, score); 444 | } 445 | 446 | 447 | public Long zAdd(String key, Set> values) { 448 | return redisTemplate.opsForZSet().add(key, values); 449 | } 450 | 451 | 452 | public Long zRemove(String key, Object... values) { 453 | return redisTemplate.opsForZSet().remove(key, values); 454 | } 455 | 456 | 457 | public Double zIncrementScore(String key, String value, double delta) { 458 | return redisTemplate.opsForZSet().incrementScore(key, value, delta); 459 | } 460 | 461 | 462 | public Long zRank(String key, Object value) { 463 | return redisTemplate.opsForZSet().rank(key, value); 464 | } 465 | 466 | 467 | public Long zReverseRank(String key, Object value) { 468 | return redisTemplate.opsForZSet().reverseRank(key, value); 469 | } 470 | 471 | 472 | public Set zRange(String key, long start, long end) { 473 | return redisTemplate.opsForZSet().range(key, start, end); 474 | } 475 | 476 | 477 | public Set> zRangeWithScores(String key, long start, 478 | long end) { 479 | return redisTemplate.opsForZSet().rangeWithScores(key, start, end); 480 | } 481 | 482 | 483 | public Set zRangeByScore(String key, double min, double max) { 484 | return redisTemplate.opsForZSet().rangeByScore(key, min, max); 485 | } 486 | 487 | 488 | 489 | public Long zSize(String key) { 490 | return redisTemplate.opsForZSet().size(key); 491 | } 492 | 493 | 494 | 495 | public Double zScore(String key, Object value) { 496 | return redisTemplate.opsForZSet().score(key, value); 497 | } 498 | 499 | public Long zRemoveRange(String key, long start, long end) { 500 | return redisTemplate.opsForZSet().removeRange(key, start, end); 501 | } 502 | 503 | public Long zRemoveRangeByScore(String key, double min, double max) { 504 | return redisTemplate.opsForZSet().removeRangeByScore(key, min, max); 505 | } 506 | 507 | // ======================================== operations about multiple items ======================================== 508 | 509 | public Collection mGet(Collection keys) { 510 | return redisTemplate.opsForValue().multiGet(keys); 511 | } 512 | 513 | public void mSet(Map kvs) { 514 | redisTemplate.opsForValue().multiSet(kvs); 515 | } 516 | 517 | 518 | 519 | 520 | 521 | } 522 | 523 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/service/AuthService.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.service; 2 | 3 | import com.allen.moments.v2.dao.UserDao; 4 | import com.allen.moments.v2.model.ErrorType; 5 | import com.allen.moments.v2.model.User; 6 | import com.allen.moments.v2.utils.ApplicationException; 7 | import com.allen.moments.v2.utils.JwtUtil; 8 | import org.apache.logging.log4j.LogManager; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | 12 | 13 | @Service 14 | public class AuthService { 15 | private final UserDao userDao; 16 | // private final RedisUtil redisUtil; 17 | 18 | static { 19 | LogManager.getLogger(AuthService.class).info("Authentication service initialized"); 20 | } 21 | 22 | @Autowired 23 | public AuthService(UserDao userDao, JwtUtil jwtUtil) { 24 | this.userDao = userDao; 25 | } 26 | 27 | public User login(String email, String password) { 28 | User user = userDao.selectByEmail(email); 29 | if (user == null) { 30 | throw new ApplicationException(ErrorType.USER_UNIDENTIFIED.errNo, ErrorType.USER_UNIDENTIFIED.message); 31 | } 32 | if (!password.equals(user.getPassword())) { 33 | throw new ApplicationException(ErrorType.PASSWORD_ACCOUNT_MISMATCH.errNo, ErrorType.PASSWORD_ACCOUNT_MISMATCH.message); 34 | } 35 | user.setPassword(null); 36 | return user; 37 | // redisUtil.setBit("loggedUsers", user.getUid() - 10000, true); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/service/PostService.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.service; 2 | 3 | import com.allen.moments.v2.dao.PostDao; 4 | import com.allen.moments.v2.model.DML; 5 | import com.allen.moments.v2.model.Post; 6 | import com.allen.moments.v2.redis.RedisUtil; 7 | import com.allen.moments.v2.utils.ThreadPoolManager; 8 | import org.apache.logging.log4j.LogManager; 9 | import org.junit.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.*; 14 | 15 | import static com.allen.moments.v2.utils.error_handler.DBExceptionChecker.checkIfRowsAffectedIsOne; 16 | 17 | @Service 18 | public class PostService { 19 | private final PostDao postDao; 20 | private final RedisUtil redis; 21 | private static volatile Integer maxPostId; 22 | 23 | static { 24 | LogManager.getLogger(PostService.class).info("Post service initialized"); 25 | } 26 | 27 | @Autowired 28 | public PostService(PostDao postDao, RedisUtil redis) { 29 | this.postDao = postDao; 30 | this.redis = redis; 31 | maxPostId = this.postDao.selectMaxPostId(); 32 | } 33 | 34 | public int addPost(int uid, String text) throws Exception { 35 | int postId = getPostId(); 36 | Post post = new Post(postId, text, uid); 37 | int rowsAffected = postDao.insertSelective(post); 38 | redis.set("post:" + postId, post); 39 | checkIfRowsAffectedIsOne(rowsAffected, DML.INSERT); 40 | return postId; 41 | } 42 | 43 | 44 | /** 45 | * insert post with photo(s) 46 | * @return whether the operation success or not 47 | */ 48 | public int addPostWithPhotos(int uid, String text, List photoUrls) throws Exception { 49 | int postId = getPostId(); 50 | Post post = new Post(postId, text, uid, photoUrls); 51 | int rowsAffected = postDao.insertSelective(post); 52 | checkIfRowsAffectedIsOne(rowsAffected, DML.INSERT); 53 | ThreadPoolManager.getInstance().execute(new Runnable() { // user another thread to execute redis update in order to avoid waiting for redis execution in the main thread 54 | @Override 55 | public void run() { 56 | redis.set("post:" + postId, post); 57 | } 58 | }); 59 | return postId; 60 | } 61 | 62 | 63 | public Post getPost(int postId) { 64 | // redis.get("post:comment:" + postId); 65 | Post post = (Post) redis.get("post:" + (postId)); 66 | if (post != null) { 67 | return post; 68 | } 69 | return postDao.selectByPrimaryKey(postId); 70 | } 71 | 72 | 73 | public List getAllPosts() { 74 | return postDao.selectAllPosts(); 75 | } 76 | 77 | 78 | public List getPostsWithHighestLikeCounts(int start, int limit) { 79 | try { 80 | if (start < 100) { // to avoid having big key in redis, only writes the first 100 popular posts into redis 81 | List posts = (List) (Object) redis.listGet("postsWithHighestLikeCounts", start, limit); 82 | if (posts != null && posts.size() > 0) { 83 | return posts; 84 | } 85 | } 86 | List postsInDB = postDao.getPostsWithHighestLikeCounts(start, limit); 87 | redis.listMSetWithExpiration("postsWithHighestLikeCounts", (List) (Object) postsInDB.subList(0, Math.min(limit, 100)), 20); 88 | return postsInDB; 89 | } 90 | catch (ClassCastException castException) { 91 | return null; 92 | } 93 | } 94 | 95 | 96 | /** 97 | * like a post or unlike a post 98 | * @return returns whether the operation succeeded or not 99 | */ 100 | public boolean likeOrUnlike(boolean isLike, int uid, int postId) { 101 | if (isLike && postDao.insertLikeRecord(uid, postId) == 1) { 102 | ThreadPoolManager.getInstance().execute(new Runnable() { 103 | @Override 104 | public void run() { 105 | redis.setSet("post:likes:" + (postId), uid); 106 | } 107 | }); 108 | return true; 109 | } 110 | else if (!isLike && postDao.removeLikeRecord(uid, postId) == 1) { 111 | ThreadPoolManager.getInstance().execute(new Runnable() { 112 | @Override 113 | public void run() { 114 | redis.setRemove("post:likes:" + (postId), uid); 115 | } 116 | }); 117 | return true; 118 | } 119 | return false; 120 | } 121 | 122 | 123 | public List getUsersWhoLikedPosts(int postId, int start, int limit) { 124 | return postDao.selectUidsThatLikedPost(postId, start, limit); 125 | } 126 | 127 | 128 | public boolean addComment(int commentedBy, int postId, String comment) throws Exception { 129 | int rowsAffected = postDao.insertCommentRecord(postId, comment, commentedBy); 130 | checkIfRowsAffectedIsOne(rowsAffected, DML.INSERT); 131 | return true; 132 | } 133 | 134 | 135 | public boolean deleteComment(int commentId, int uid) throws Exception { 136 | int rowsAffected = postDao.removeCommentRecord(commentId, uid); 137 | checkIfRowsAffectedIsOne(rowsAffected, DML.DELETE); 138 | return true; 139 | } 140 | 141 | 142 | private synchronized int getPostId() { 143 | return maxPostId++; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/service/S3Service.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.service; 2 | 3 | import com.allen.moments.v2.utils.S3Client; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.multipart.MultipartFile; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Service 14 | public class S3Service { 15 | @Qualifier("client") 16 | private final S3Client s3Client; 17 | 18 | static { 19 | LogManager.getLogger(S3Service.class).info("S3 service initialized"); 20 | } 21 | 22 | @Autowired() 23 | public S3Service(S3Client s3Client) { 24 | this.s3Client = s3Client; 25 | } 26 | 27 | public String upload(MultipartFile file) throws Exception { 28 | return s3Client.uploadFile(file, file.getName()); 29 | } 30 | 31 | public List upload(List files) throws Exception { 32 | ArrayList fileUrls = new ArrayList(); 33 | for (MultipartFile file : files) { 34 | String url = s3Client.uploadFile(file, file.getName()); 35 | fileUrls.add(url); 36 | } 37 | return fileUrls; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.service; 2 | 3 | import com.allen.moments.v2.dao.UserDao; 4 | import com.allen.moments.v2.model.ErrorType; 5 | import com.allen.moments.v2.model.User; 6 | import com.allen.moments.v2.redis.RedisUtil; 7 | import com.allen.moments.v2.utils.ApplicationException; 8 | import com.allen.moments.v2.utils.ThreadPoolManager; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | import static com.allen.moments.v2.utils.error_handler.DBExceptionChecker.checkIfRowsAffectedIsOne; 19 | 20 | 21 | /** 22 | * @Description service layer for userinfo-related operations 23 | * basic logic: 24 | * query in redis(cache layer) first, 25 | * if found, return; 26 | * if not found. query in DB layer and cache the result in redis 27 | */ 28 | @Service 29 | public class UserService { 30 | private final UserDao userDao; 31 | private final RedisUtil redis; 32 | private static int maxUid; 33 | 34 | static { 35 | LogManager.getLogger(UserService.class).info("User service initialized"); 36 | } 37 | 38 | @Autowired 39 | public UserService(UserDao userDao, RedisUtil redis) { 40 | this.userDao = userDao; 41 | this.redis = redis; 42 | maxUid = this.userDao.getMaxUid(); 43 | } 44 | 45 | public User addUser(String email, String name, Integer sex, Integer age, String password) throws Exception { 46 | User newUser = new User(name, email, maxUid + 1, age, sex, password); 47 | try { 48 | int rowsAffected = userDao.insert(newUser); 49 | checkIfRowsAffectedIsOne(rowsAffected, ErrorType.UNKNOWN_ERROR); 50 | maxUid += 1; 51 | return newUser; 52 | } 53 | catch (RuntimeException exception) { 54 | if (exception.getClass() == org.springframework.dao.DuplicateKeyException.class) { 55 | throw new ApplicationException(ErrorType.USER_ALREADY_REGISTERED.errNo, ErrorType.USER_ALREADY_REGISTERED.message); 56 | } 57 | throw new ApplicationException(exception.getMessage()); 58 | } 59 | } 60 | 61 | public void setNewPassword(int uid, String oldPassword, String newPassword) throws Exception { 62 | int rowsAffected = userDao.updatePassword(uid, oldPassword, newPassword); 63 | checkIfRowsAffectedIsOne(rowsAffected, ErrorType.USER_UNIDENTIFIED); 64 | } 65 | 66 | public @Nullable 67 | User getUser(@NotNull int uid) { 68 | User cachedUser = (User) redis.hashGet("allUsers", String.valueOf(uid)); 69 | if (cachedUser != null) { 70 | return cachedUser; 71 | } 72 | User userInDB = userDao.selectByUid(uid); 73 | if (userInDB == null) { 74 | throw new RuntimeException("user not found"); 75 | } 76 | redis.hashSet("allUsers", String.valueOf(userInDB.getUid()), userInDB); 77 | return userInDB; 78 | } 79 | 80 | public List selectUsersOrderByFollowerCounts(int start, int limit) { 81 | try { 82 | if (start < 100) { // to avoid having big key in redis, only writes the first 100 popular posts into redis 83 | List usersCached = (List) (Object) redis.listGet("usersWithHighestFollowerCounts", start, limit); 84 | if (usersCached != null) { 85 | return usersCached; 86 | } 87 | } 88 | List usersInDB = userDao.selectUsersOrderByFollowerCounts(start, limit); 89 | redis.listMSetWithExpiration("postsWithHighestLikeCounts", (List) (Object) usersInDB.subList(0, Math.min(limit, 100)), 20); 90 | return usersInDB; 91 | } 92 | catch (ClassCastException castException) { 93 | return null; 94 | } 95 | } 96 | 97 | public Set getFollowersId(int uid) { 98 | String redisKey = "user:" + uid + ":follower"; 99 | Set followersCached = (Set) (Object) redis.setGetAll(redisKey); 100 | if (followersCached != null) { 101 | return followersCached; 102 | } 103 | Set followersInDB = (Set) userDao.selectFollowersById(uid); 104 | if (followersInDB != null) { 105 | ThreadPoolManager.getInstance().execute(new Runnable() { 106 | @Override 107 | public void run() { 108 | redis.setSet(redisKey, followersInDB); 109 | } 110 | }); 111 | return followersInDB; 112 | } 113 | return null; 114 | } 115 | 116 | 117 | public Set getFollowingsId(int uid) { 118 | String redisKey = "user:" + uid + ":following"; 119 | Set followingsCached = (Set) (Object) redis.setGetAll(redisKey); 120 | if (followingsCached != null && followingsCached.size() > 0) { 121 | return followingsCached; 122 | } 123 | Set followingsInDB = (Set) userDao.selectFollowingsById(uid); 124 | if (followingsInDB != null) { 125 | ThreadPoolManager.getInstance().execute(new Runnable() { 126 | @Override 127 | public void run() { 128 | redis.setSet(redisKey, followingsInDB); 129 | } 130 | }); 131 | return followingsInDB; 132 | } 133 | return null; 134 | } 135 | 136 | public void follow(int followedId, int followerId) { 137 | try { 138 | int rowsAffected = userDao.addFollower(followedId, followerId); 139 | ThreadPoolManager.getInstance().execute( 140 | new Runnable() { 141 | @Override 142 | public void run() { 143 | String followedRedisKey = "user:" + followedId + ":follower"; 144 | redis.setSet(followedRedisKey, followedId); 145 | 146 | String followerRedisKey = "user:" + followerId + ":following"; 147 | redis.setSet(followerRedisKey, followedId); 148 | } 149 | } 150 | ); 151 | } 152 | catch(Exception exception) { 153 | if (exception.getClass() == org.springframework.dao.DuplicateKeyException.class) { 154 | throw new ApplicationException(ErrorType.USER_ALREADY_FOLLOWED.errNo, ErrorType.USER_ALREADY_FOLLOWED.message); 155 | } 156 | throw new ApplicationException(exception.getMessage()); 157 | } 158 | } 159 | 160 | public void unfollow(int followedId, int followerId) throws Exception { 161 | int rowsAffected = userDao.removeFollowingRelation(followedId, followerId); 162 | ThreadPoolManager.getInstance().execute(new Runnable() { 163 | @Override 164 | public void run() { 165 | redis.setRemove("user:" + followedId + ":followers", followerId); 166 | } 167 | }); 168 | checkIfRowsAffectedIsOne(rowsAffected, ErrorType.NO_FOLLOWING_RELATION); 169 | 170 | } 171 | 172 | public void addCustomizedAvatar(int uid, String avatarURI) throws Exception { 173 | int rowsAffected = userDao.addAvatarURI(uid, avatarURI); 174 | checkIfRowsAffectedIsOne(rowsAffected, ErrorType.USER_UNIDENTIFIED); 175 | } 176 | 177 | public List getAllUsers() { 178 | // List allUsersCached = (List)(Object) redis.sGet("all_users_id"); 179 | // if (allUsersCached.size() != 0) { 180 | // return allUsersCached; 181 | // } 182 | return userDao.selectAll(); 183 | // if (allUsersDB != null) { 184 | // redis.sSet("all_users", allUsersDB); 185 | // } 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/utils/ApplicationException.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.utils; 2 | 3 | import com.allen.moments.v2.model.ErrorType; 4 | 5 | public class ApplicationException extends RuntimeException { 6 | private String message; 7 | private Object[] args; 8 | private int errNo = ErrorType.UNKNOWN_ERROR.errNo; // default err_no represents unknown error 9 | 10 | public ApplicationException(String message) 11 | { 12 | super(message); 13 | this.message = message; 14 | } 15 | 16 | public ApplicationException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | public ApplicationException(String message, Object ... args) 21 | { 22 | super(message); 23 | this.args = args; 24 | } 25 | 26 | public ApplicationException(int errNo, String message) { 27 | super(message); 28 | this.errNo = errNo; 29 | } 30 | 31 | public ApplicationException(String message, Throwable cause, Object ... args) 32 | { 33 | super(message, cause); 34 | this.args = args; 35 | } 36 | 37 | @Override 38 | public String getMessage() 39 | { 40 | return this.message; 41 | } 42 | 43 | public Object[] getArgs() 44 | { 45 | return this.args; 46 | } 47 | 48 | public int getErrNo() { 49 | return this.errNo; 50 | } 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/utils/JsonResult.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.utils; 2 | 3 | import com.allen.moments.v2.model.ErrorType; 4 | 5 | import java.io.Serializable; 6 | 7 | public class JsonResult implements Serializable { 8 | 9 | private T data; 10 | private int err_no; 11 | private int isSuccess; 12 | private String err_tips; 13 | private long ts; 14 | 15 | /** 16 | * constructs a JsonResult object representing successful operation 17 | * @param isSuccess 18 | */ 19 | private JsonResult(boolean isSuccess) { 20 | this.isSuccess = isSuccess ? 1 : 0; 21 | this.err_no = isSuccess ? 0 : ErrorType.UNKNOWN_ERROR.errNo; 22 | this.err_tips = isSuccess ? "" : ErrorType.UNKNOWN_ERROR.message; 23 | this.ts = new java.util.Date().getTime(); 24 | } 25 | 26 | /** 27 | * constructs a JsonResult object representing error 28 | * @param err_no 29 | * @param err_tips 30 | */ 31 | private JsonResult(int err_no, String err_tips) { 32 | this.err_no = err_no; 33 | this.err_tips = err_tips; 34 | this.ts = new java.util.Date().getTime(); 35 | } 36 | 37 | /** 38 | * use generic data to construct a JsonResult Object 39 | * @param data 40 | */ 41 | private JsonResult(T data) { 42 | this.data = data; 43 | this.err_no = 0; 44 | this.err_tips = ""; 45 | this.ts = new java.util.Date().getTime(); 46 | } 47 | 48 | public static JsonResult failure(int err_no, String err_tips) { 49 | return new JsonResult<>(err_no, err_tips); 50 | } 51 | 52 | public static JsonResult unknownFailure() { 53 | return new JsonResult<>(false); 54 | } 55 | public static JsonResult success() { 56 | return new JsonResult<>(true); 57 | } 58 | 59 | public static JsonResult successWithData(Object data) { 60 | return new JsonResult<>(data); 61 | } 62 | 63 | public T getData() { 64 | return data; 65 | } 66 | 67 | public void setData(T data) { 68 | this.data = data; 69 | } 70 | 71 | public int getErrNo() { 72 | return err_no; 73 | } 74 | 75 | public void setErrNo(int err_no) { 76 | this.err_no = err_no; 77 | } 78 | 79 | public int getIsSuccess() { 80 | return isSuccess; 81 | } 82 | 83 | public void setIsSuccess(int isSuccess) { 84 | this.isSuccess = isSuccess; 85 | } 86 | 87 | public String getErrTips() { 88 | return err_tips; 89 | } 90 | 91 | public void setErrTips(String err_tips) { 92 | this.err_tips = err_tips; 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/utils/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.utils; 2 | import com.allen.moments.v2.model.User; 3 | import com.auth0.jwt.algorithms.Algorithm; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.*; 9 | 10 | @Component 11 | public class JwtUtil { 12 | private static Logger log = LoggerFactory.getLogger(JwtUtil.class); 13 | private static final long aWeekInMs = 604800000; 14 | public String getToken(Integer uid) { 15 | String token = ""; 16 | token = com.auth0.jwt.JWT.create().withAudience(String.valueOf(uid)).withExpiresAt(new Date(System.currentTimeMillis() + aWeekInMs)) 17 | .sign(Algorithm.HMAC256("${application.jwt.secret_key}")); 18 | return token; 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/utils/S3Client.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.utils; 2 | 3 | import com.amazonaws.auth.AWSStaticCredentialsProvider; 4 | import com.amazonaws.auth.BasicAWSCredentials; 5 | import com.amazonaws.regions.Regions; 6 | import com.amazonaws.services.s3.AmazonS3; 7 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 8 | import com.amazonaws.services.s3.model.CannedAccessControlList; 9 | import com.amazonaws.services.s3.model.PutObjectRequest; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.web.multipart.MultipartFile; 14 | import java.io.File; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.util.Date; 18 | 19 | @Configuration 20 | public class S3Client { 21 | 22 | AmazonS3 client; 23 | @Value("${aws.access_key_id}") 24 | private String awsId; 25 | 26 | @Value("${aws.secret_access_key}") 27 | private String awsKey; 28 | 29 | @Value("${aws.s3.region}") 30 | private String region; 31 | 32 | @Bean(name = "client") 33 | public AmazonS3 s3Client() { 34 | BasicAWSCredentials awsCreds = new BasicAWSCredentials(awsId, awsKey); 35 | AmazonS3 s3Client = AmazonS3ClientBuilder.standard() 36 | .withRegion(Regions.fromName(region)) 37 | .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) 38 | .build(); 39 | this.client = s3Client; 40 | return s3Client; 41 | } 42 | 43 | 44 | @Value("${aws.s3.endpoint_url}") 45 | private String endpointUrl; 46 | 47 | @Value("${aws.s3.bucket_name}") 48 | private String bucketName; 49 | 50 | public String uploadFile(MultipartFile multipartFile, String fileName) 51 | throws Exception { 52 | String fileUrl = ""; 53 | File file = convertMultiPartToFile(multipartFile); 54 | fileUrl = endpointUrl + "/" + bucketName + "/" + fileName; 55 | uploadFileToS3Bucket(fileName, file); 56 | file.delete(); 57 | return fileUrl; 58 | } 59 | 60 | public Boolean deleteFileFromS3Bucket(String fileUrl) { 61 | try { 62 | String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1); 63 | client.deleteObject(bucketName, fileName); 64 | return true; 65 | } 66 | catch (Exception exception) { 67 | return false; 68 | } 69 | } 70 | 71 | private void uploadFileToS3Bucket(String fileName, File file) { 72 | client.putObject(new PutObjectRequest(bucketName, fileName, file).withCannedAcl(CannedAccessControlList.PublicRead)); 73 | } 74 | 75 | private File convertMultiPartToFile(MultipartFile file) 76 | throws IOException { 77 | File convFile = new File(file.getOriginalFilename()); 78 | FileOutputStream fos = new FileOutputStream(convFile); 79 | fos.write(file.getBytes()); 80 | fos.close(); 81 | return convFile; 82 | } 83 | 84 | private String generateFileName(MultipartFile multiPart) { 85 | return new Date().getTime() + "-" + multiPart.getOriginalFilename().replace(" ", "_"); 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/utils/ThreadPoolManager.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.utils; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.Callable; 5 | import java.util.concurrent.Executors; 6 | import java.util.concurrent.Future; 7 | import java.util.concurrent.ThreadPoolExecutor; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | 11 | public class ThreadPoolManager { 12 | 13 | 14 | private static final int CPU_CORE_COUNT = Runtime.getRuntime().availableProcessors(); 15 | 16 | private static final int CORE_POOL_SIZE = CPU_CORE_COUNT + 1; 17 | 18 | private static final int MAXIMUM_POOL_SIZE = CPU_CORE_COUNT * 2 + 1; 19 | 20 | private static final int KEEP_ALIVE_TIME = 1; 21 | 22 | private ThreadPoolExecutor executor; 23 | 24 | /** 25 | * use singleton with lazy initialization 26 | */ 27 | private ThreadPoolManager() { 28 | } 29 | 30 | private static ThreadPoolManager instance; 31 | 32 | public synchronized static ThreadPoolManager getInstance() { 33 | if (instance == null) { 34 | instance = new ThreadPoolManager<>(); 35 | } 36 | return instance; 37 | } 38 | 39 | /** 40 | * uses a thread to execute the task that has no value returned 41 | * @param runnable the task 42 | */ 43 | public void execute(Runnable runnable) { 44 | if (executor == null) { 45 | executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 46 | KEEP_ALIVE_TIME, TimeUnit.SECONDS, new ArrayBlockingQueue(20), 47 | Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); 48 | } 49 | executor.execute(runnable); 50 | } 51 | 52 | /** 53 | * uses a thread to execute the task that has value returned 54 | * @param runnable 55 | * @return 56 | */ 57 | public Future submit(Callable runnable) { 58 | if (executor == null) { 59 | executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 60 | KEEP_ALIVE_TIME, TimeUnit.SECONDS, new ArrayBlockingQueue(20), 61 | Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); 62 | } 63 | return executor.submit(runnable); 64 | } 65 | 66 | /** 67 | * remove task from queue 68 | * @param runnable 69 | */ 70 | public void cancel(Runnable runnable) { 71 | if (runnable != null) { 72 | executor.getQueue().remove(runnable); 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/utils/annotations/PassToken.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.utils.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD, ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface PassToken { 11 | boolean isTokenNeedless() default true; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/utils/annotations/RequireToken.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.utils.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD, ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface RequireToken { 11 | boolean isTokenNeeded() default true; 12 | } -------------------------------------------------------------------------------- /src/main/java/com/allen/moments/v2/utils/error_handler/DBExceptionChecker.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2.utils.error_handler; 2 | 3 | import com.allen.moments.v2.model.DML; 4 | import com.allen.moments.v2.model.ErrorType; 5 | import com.allen.moments.v2.utils.ApplicationException; 6 | 7 | public class DBExceptionChecker { 8 | public static void checkIfRowsAffectedIsOne(int rowsAffected, ErrorType zeroRowAffectedErrorType) throws Exception { 9 | if (rowsAffected == 0) { 10 | throw new ApplicationException(zeroRowAffectedErrorType.errNo, zeroRowAffectedErrorType.message); 11 | } 12 | if (rowsAffected > 1) { 13 | throw new ApplicationException(ErrorType.DIRTY_DATA.errNo, ErrorType.DIRTY_DATA.message); 14 | } 15 | } 16 | 17 | public static void checkIfRowsAffectedIsOne(int rowsAffected, DML dml) throws Exception { 18 | if (rowsAffected == 0) { 19 | throw new ApplicationException(ErrorType.DML_ERR.errNo, ErrorType.DML_ERR.message + ": " + dml.description); 20 | } 21 | if (rowsAffected > 1) { 22 | throw new ApplicationException(ErrorType.DIRTY_DATA.errNo, ErrorType.DIRTY_DATA.message); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/additional-spring-configuration-metadata.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllenSun-HM/moments_v2_backend/6314021a343fbabfd880bf8a73916f014d8ee481/src/main/resources/META-INF/additional-spring-configuration-metadata.json -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | moments-app 6 | logs 7 | 30 MB 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/main/resources/mybatis-generator-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /src/test/java/com/allen/moments/v2/MomentsApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.allen.moments.v2; 2 | 3 | import com.allen.moments.v2.dao.UserDao; 4 | import com.allen.moments.v2.model.User; 5 | import com.allen.moments.v2.redis.RedisUtil; 6 | import com.allen.moments.v2.utils.ThreadPoolManager; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | 11 | @SpringBootTest 12 | class MomentsApplicationTests { 13 | @Autowired 14 | RedisUtil redisUtil; 15 | @Autowired 16 | UserDao userDao; 17 | 18 | @Test 19 | void contextLoads() { 20 | } 21 | 22 | 23 | // @Test 24 | // void testRedisConnection() { 25 | // redisUtil.set("abc" , 123); 26 | // } 27 | // 28 | // @Test 29 | // void testThreadPool() { 30 | // for (int i = 0; i < 100; i++) { 31 | // int finalI = i; 32 | // Runnable r = new Runnable() { 33 | // @Override 34 | // public void run() { 35 | // System.out.println(finalI); 36 | // } 37 | // }; 38 | // ThreadPoolManager.getInstance().execute(r); 39 | // } 40 | // } 41 | // 42 | // @Test 43 | // void writeIntoDB() throws InterruptedException { 44 | // for (int i = 0; i < 500; i++) { 45 | // Thread.sleep(10); 46 | // User user = new User("test" + i, "test" + i + "@test.com", 100020 + i, (int) Math.floor(Math.random() * 20), Math.random() < 0.5 ? 1 : 2, "123456"); 47 | // userDao.insert(user); 48 | // } 49 | // } 50 | 51 | } 52 | --------------------------------------------------------------------------------