├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.MD ├── application.yml.sample ├── docker-compose.yml ├── dockerFiles ├── java │ └── Dockerfile └── mysql │ ├── Dockerfile │ ├── charset.cnf │ └── database.sql ├── mvnw ├── mvnw.cmd ├── mysql.env ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── hpm │ │ └── blog │ │ ├── SpringBootBlogApplication.java │ │ ├── annotation │ │ ├── CurrentUser.java │ │ └── LoginRequired.java │ │ ├── api │ │ ├── AuthenticationApi.java │ │ ├── PostApi.java │ │ └── UserApi.java │ │ ├── config │ │ ├── AuthenticationInterceptor.java │ │ ├── CurrentUserMethodArgumentResolver.java │ │ ├── FastJsonHttpMessageConverterEx.java │ │ ├── GlobalExceptionHandler.java │ │ └── WebMvcConfigurer.java │ │ ├── mapper │ │ ├── PostMapper.java │ │ └── UserMapper.java │ │ ├── model │ │ ├── Post.java │ │ └── User.java │ │ └── service │ │ ├── AuthenticationService.java │ │ ├── PostService.java │ │ └── UserService.java └── resources │ ├── application.yml │ ├── com │ └── hpm │ │ └── blog │ │ └── mapper │ │ ├── PostMapper.xml │ │ └── UserMapper.xml │ ├── logback-spring.xml │ ├── mybatis.xml │ └── static │ ├── css │ ├── bulma.css │ ├── common.css │ └── font-awesome.min.css │ ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 │ ├── index.html │ └── js │ ├── axios.min.js │ ├── index.js │ ├── marked.min.js │ └── vue.js └── test └── java └── com └── hpm └── blog └── SpringBootBlogApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | log/ 4 | # 根目录配置文件 5 | /application.yml 6 | 7 | ### docker ### 8 | # mysql 最终配置文件 9 | mysql.env.local 10 | /maven 11 | /mysql 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iml -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyrijk/spring-boot-blog/34160e7cd92c7171d7fe3da2748e8933e47222f4/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip 2 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | Spring Boot step by step 2 | 3 | 1. [使用 idea 新建 Spring Boot 项目](http://www.jianshu.com/p/45ba074dbc81) 4 | 2. [使用 Spring Maven 仓库加速 Maven jar 包下载](http://www.jianshu.com/p/87100dd1ec52) 5 | 3. [实现用户注册接口](http://www.jianshu.com/p/07184349738a) 6 | 4. [实现用户登录接口](http://www.jianshu.com/p/d99e4de60e5d) 7 | 5. [配置 logback 和全局异常处理](http://www.jianshu.com/p/e0b6f29f9676) 8 | 6. [使用 spring 拦截器和自定义注解进行登录拦截](http://www.jianshu.com/p/97362fdf039e) 9 | 7. [通过自定义 @CurrentUser 获取当前登录用户](http://www.jianshu.com/p/01a6a61d9e02) 10 | 8. [实现文章添加和查询接口](http://www.jianshu.com/p/e54077b0bf37) 11 | 9. [使用 fastjson 解析数据](http://www.jianshu.com/p/45682cd30ca2) 12 | 13 | # 部署说明 14 | 15 | --- 16 | 17 | ## docker 18 | 默认 18080 端口 19 | 20 | 1. [安装 docker](https://get.daocloud.io/#install-docker) 21 | 2. [安装 docker-compose](https://get.daocloud.io/#install-compose) 22 | 3. 修改 mysql 用户名和密码 23 | ``` 24 | $ cp application.yml.sample application.yml 25 | $ vi application.yml 26 | ``` 27 | 28 | ``` 29 | $ cp mysql.env mysql.env.local 30 | $ vi mysql.env.local 31 | ``` 32 | 4. package 33 | ``` 34 | $ docker-compose up maven 35 | ``` 36 | 5. run 37 | - 直接运行 38 | ``` 39 | $ docker-compose up app 40 | ``` 41 | - 后台运行 42 | ``` 43 | $ docker-compose start app 44 | ``` 45 | 46 | --- 47 | 48 | ## 非docker 49 | 默认 8080 端口 50 | 51 | 1. package 52 | ``` 53 | $ mvn clean package 54 | ``` 55 | 2. run 56 | ``` 57 | $ nohub java -jar target/spring-boot-blog-0.0.1-SNAPSHOT.jar & 58 | ``` 59 | -------------------------------------------------------------------------------- /application.yml.sample: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:mysql://mysql:3306/spring-blog?useUnicode=true&characterEncoding=utf8&autoReconnect=true 4 | username: username 5 | password: password -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | maven: 2 | image: maven:3.3-jdk-8 3 | command: mvn clean package -Dmaven.test.skip=true 4 | volumes: 5 | - .:/code 6 | - ./maven:/root/.m2 7 | working_dir: /code 8 | 9 | mysql: 10 | build: ./dockerFiles/mysql 11 | volumes: 12 | - ./mysql:/var/lib/mysql 13 | working_dir: /var/lib/mysql 14 | ports: 15 | - "13306:3306" 16 | env_file: 17 | - ./mysql.env 18 | - ./mysql.env.local 19 | 20 | app: 21 | build: ./dockerFiles/java 22 | command: java -jar target/spring-boot-blog-0.0.1-SNAPSHOT.jar 23 | volumes: 24 | - .:/code 25 | working_dir: /code 26 | ports: 27 | - "18080:8080" 28 | links: 29 | - mysql:mysql -------------------------------------------------------------------------------- /dockerFiles/java/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8-jdk 2 | RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 3 | RUN echo "Asia/Shanghai" > /etc/timezone 4 | RUN dpkg-reconfigure -f noninteractive tzdata -------------------------------------------------------------------------------- /dockerFiles/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:5.6 2 | ADD charset.cnf /etc/mysql/conf.d/charset.cnf 3 | 4 | ADD database.sql /docker-entrypoint-initdb.d 5 | 6 | RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 7 | RUN echo "Asia/Shanghai" > /etc/timezone 8 | RUN dpkg-reconfigure -f noninteractive tzdata -------------------------------------------------------------------------------- /dockerFiles/mysql/charset.cnf: -------------------------------------------------------------------------------- 1 | # https://segmentfault.com/a/1190000000616820 emoji存储 2 | [client] 3 | default-character-set=utf8mb4 4 | 5 | [mysql] 6 | default-character-set=utf8mb4 7 | 8 | [mysqld] 9 | skip-character-set-client-handshake 10 | character-set-server=utf8mb4 11 | collation-server=utf8mb4_general_ci 12 | init-connect=SET NAMES utf8mb4 13 | -------------------------------------------------------------------------------- /dockerFiles/mysql/database.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : localhost-root 5 | Source Server Type : MySQL 6 | Source Server Version : 50716 7 | Source Host : localhost 8 | Source Database : spring-blog 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50716 12 | File Encoding : utf-8 13 | 14 | Date: 02/07/2017 19:55:25 PM 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for `post` 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `post`; 24 | CREATE TABLE `post` ( 25 | `id` int(11) NOT NULL AUTO_INCREMENT, 26 | `author_id` int(11) NOT NULL, 27 | `title` varchar(100) NOT NULL, 28 | `content` text NOT NULL, 29 | `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 30 | `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 31 | PRIMARY KEY (`id`) 32 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; 33 | 34 | -- ---------------------------- 35 | -- Table structure for `user` 36 | -- ---------------------------- 37 | DROP TABLE IF EXISTS `user`; 38 | CREATE TABLE `user` ( 39 | `id` int(11) NOT NULL AUTO_INCREMENT, 40 | `name` varchar(20) NOT NULL, 41 | `password` varchar(255) NOT NULL, 42 | PRIMARY KEY (`id`), 43 | UNIQUE KEY `user_name_uindex` (`name`) 44 | ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4; 45 | 46 | SET FOREIGN_KEY_CHECKS = 1; 47 | -------------------------------------------------------------------------------- /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 | # http://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 | # Maven2 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 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | if [ -d "$wdir"/.mvn ] ; then 204 | basedir=$wdir 205 | break 206 | fi 207 | wdir=$(cd "$wdir/.."; pwd) 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 228 | 229 | exec "$JAVACMD" \ 230 | $MAVEN_OPTS \ 231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 233 | ${WRAPPER_LAUNCHER} "$@" 234 | -------------------------------------------------------------------------------- /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 http://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 Maven2 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 key stroke 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 enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 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 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% -------------------------------------------------------------------------------- /mysql.env: -------------------------------------------------------------------------------- 1 | # MySQL docker 默认配置 2 | MYSQL_ROOT_PASSWORD=root 3 | MYSQL_DATABASE=spring-blog 4 | MYSQL_USER=username 5 | MYSQL_PASSWORD=password 6 | MYSQL_ALLOW_EMPTY_PASSWORD=no -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.hpm.blog 7 | spring-boot-blog 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | spring-boot-blog 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.4.3.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.mybatis.spring.boot 30 | mybatis-spring-boot-starter 31 | 1.1.1 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | com.alibaba 39 | fastjson 40 | 1.2.83 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-devtools 46 | 47 | 48 | 49 | mysql 50 | mysql-connector-java 51 | runtime 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-test 56 | test 57 | 58 | 59 | 60 | com.auth0 61 | java-jwt 62 | 3.0.2 63 | 64 | 65 | 66 | commons-lang 67 | commons-lang 68 | 2.6 69 | 70 | 71 | 72 | com.google.guava 73 | guava 74 | 21.0 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-maven-plugin 84 | 85 | 86 | 87 | 88 | 89 | 90 | spring-releases 91 | https://repo.spring.io/libs-release 92 | 93 | 94 | 95 | 96 | spring-releases 97 | https://repo.spring.io/libs-release 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/SpringBootBlogApplication.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog; 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.hpm.blog.mapper") 9 | public class SpringBootBlogApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(SpringBootBlogApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/annotation/CurrentUser.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.annotation; 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 | /** 9 | * 在Controller的方法参数中使用此注解,该方法在映射时会注入当前登录的User对象 10 | */ 11 | @Target(ElementType.PARAMETER) // 可用在方法的参数上 12 | @Retention(RetentionPolicy.RUNTIME) // 运行时有效 13 | public @interface CurrentUser { 14 | } -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/annotation/LoginRequired.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.annotation; 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 | /** 9 | * 在需要登录验证的Controller的方法上使用此注解 10 | */ 11 | @Target({ElementType.METHOD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface LoginRequired { 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/api/AuthenticationApi.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.api; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.hpm.blog.model.User; 5 | import com.hpm.blog.service.AuthenticationService; 6 | import com.hpm.blog.service.UserService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RestController 14 | @RequestMapping("/api/authentication") 15 | public class AuthenticationApi { 16 | private AuthenticationService authenticationService; 17 | private UserService userService; 18 | 19 | @Autowired 20 | public AuthenticationApi(AuthenticationService authenticationService, UserService userService) { 21 | this.authenticationService = authenticationService; 22 | this.userService = userService; 23 | } 24 | 25 | @PostMapping("") 26 | public Object login(@RequestBody User user) { 27 | User userInDataBase = userService.findByName(user.getName()); 28 | JSONObject jsonObject = new JSONObject(); 29 | if (userInDataBase == null) { 30 | jsonObject.put("error", "用户不存在"); 31 | } else if (!userService.comparePassword(user, userInDataBase)) { 32 | jsonObject.put("error", "密码不正确"); 33 | } else { 34 | String token = authenticationService.getToken(userInDataBase); 35 | jsonObject.put("token", token); 36 | jsonObject.put("user", userInDataBase); 37 | } 38 | return jsonObject; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/api/PostApi.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.api; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.hpm.blog.annotation.CurrentUser; 5 | import com.hpm.blog.annotation.LoginRequired; 6 | import com.hpm.blog.model.Post; 7 | import com.hpm.blog.model.User; 8 | import com.hpm.blog.service.PostService; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * 文章接口 16 | */ 17 | @RestController 18 | @RequestMapping("/api/post") 19 | public class PostApi { 20 | private PostService postService; 21 | 22 | @Autowired 23 | public PostApi(PostService postService) { 24 | this.postService = postService; 25 | } 26 | 27 | @PostMapping("") 28 | @LoginRequired 29 | public Post add(@RequestBody Post post, @CurrentUser User user) { 30 | post.setAuthorId(user.getId()); // 添加作者信息 31 | post = postService.add(post); 32 | return post; 33 | } 34 | 35 | @GetMapping("/{id}") 36 | public Object findById(@PathVariable int id) { 37 | Post post = postService.findById(id); 38 | return postService.findById(id); 39 | } 40 | 41 | @GetMapping("") 42 | public List all() { 43 | return postService.all(); 44 | } 45 | 46 | /** 47 | * 更新文章,需要登录 48 | * @param post 需要修改的内容 49 | * @param id 文章 id 50 | * @param currentUser 当前用户 51 | * @return 更新之后的文章 52 | */ 53 | @LoginRequired 54 | @PutMapping("/{id}") 55 | public Post update(@RequestBody Post post, @PathVariable int id, @CurrentUser User currentUser) { 56 | post.setId(id); 57 | return postService.update(post, currentUser); 58 | } 59 | 60 | /** 61 | * 删除文章,需要登录 62 | * @param id 文章 id 63 | * @param currentUser 当前登录用户 64 | * @return 提示信息 65 | */ 66 | @LoginRequired 67 | @DeleteMapping("/{id}") 68 | public Object delete(@PathVariable int id, @CurrentUser User currentUser) { 69 | postService.delete(id, currentUser); 70 | JSONObject jsonObject = new JSONObject(); 71 | jsonObject.put("message", "删除成功"); 72 | return jsonObject; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/api/UserApi.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.api; 2 | 3 | 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.hpm.blog.model.User; 6 | import com.hpm.blog.service.UserService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | @RestController 11 | @RequestMapping("/api/user") 12 | public class UserApi { 13 | private UserService userService; 14 | 15 | @Autowired 16 | public UserApi(UserService userService) { 17 | this.userService = userService; 18 | } 19 | 20 | @PostMapping("") 21 | public Object add(@RequestBody User user) { 22 | if (userService.findByName(user.getName()) != null) { 23 | JSONObject jsonObject = new JSONObject(); 24 | jsonObject.put("error","用户名已被使用"); 25 | return jsonObject; 26 | } 27 | return userService.add(user); 28 | } 29 | 30 | @GetMapping("{id}") 31 | public Object findById(@PathVariable int id) { 32 | return userService.findById(id); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/config/AuthenticationInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.config; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.JWTVerifier; 5 | import com.auth0.jwt.algorithms.Algorithm; 6 | import com.auth0.jwt.exceptions.JWTDecodeException; 7 | import com.auth0.jwt.exceptions.JWTVerificationException; 8 | import com.hpm.blog.annotation.LoginRequired; 9 | import com.hpm.blog.model.User; 10 | import com.hpm.blog.service.UserService; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.web.method.HandlerMethod; 13 | import org.springframework.web.servlet.HandlerInterceptor; 14 | import org.springframework.web.servlet.ModelAndView; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpServletResponse; 18 | import java.io.UnsupportedEncodingException; 19 | import java.lang.reflect.Method; 20 | 21 | 22 | public class AuthenticationInterceptor implements HandlerInterceptor { 23 | @Autowired 24 | private UserService userService; 25 | 26 | public boolean preHandle(HttpServletRequest request, 27 | HttpServletResponse response, Object handler) throws Exception { 28 | // 如果不是映射到方法直接通过 29 | if (!(handler instanceof HandlerMethod)) { 30 | return true; 31 | } 32 | HandlerMethod handlerMethod = (HandlerMethod) handler; 33 | Method method = handlerMethod.getMethod(); 34 | 35 | // 判断接口是否需要登录 36 | LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class); 37 | // 有 @LoginRequired 注解,需要认证 38 | if (methodAnnotation != null) { 39 | // 执行认证 40 | String token = request.getHeader("token"); // 从 http 请求头中取出 token 41 | if (token == null) { 42 | throw new RuntimeException("无token,请重新登录"); 43 | } 44 | int userId; 45 | try { 46 | userId = Integer.parseInt(JWT.decode(token).getAudience().get(0)); // 获取 token 中的 user id 47 | } catch (JWTDecodeException e) { 48 | throw new RuntimeException("token无效,请重新登录"); 49 | } 50 | User user = userService.findById(userId); 51 | if (user == null) { 52 | throw new RuntimeException("用户不存在,请重新登录"); 53 | } 54 | // 验证 token 55 | try { 56 | JWTVerifier verifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build(); 57 | try { 58 | verifier.verify(token); 59 | } catch (JWTVerificationException e) { 60 | throw new RuntimeException("token无效,请重新登录"); 61 | } 62 | } catch (UnsupportedEncodingException ignore) {} 63 | request.setAttribute("currentUser", user); 64 | return true; 65 | } 66 | return true; 67 | } 68 | 69 | public void postHandle(HttpServletRequest request, 70 | HttpServletResponse response, Object handler, 71 | ModelAndView modelAndView) throws Exception { 72 | } 73 | 74 | public void afterCompletion(HttpServletRequest request, 75 | HttpServletResponse response, Object handler, Exception ex) 76 | throws Exception { 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/config/CurrentUserMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.config; 2 | 3 | import com.hpm.blog.annotation.CurrentUser; 4 | import com.hpm.blog.model.User; 5 | import org.springframework.core.MethodParameter; 6 | import org.springframework.web.bind.support.WebDataBinderFactory; 7 | import org.springframework.web.context.request.NativeWebRequest; 8 | import org.springframework.web.context.request.RequestAttributes; 9 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 10 | import org.springframework.web.method.support.ModelAndViewContainer; 11 | import org.springframework.web.multipart.support.MissingServletRequestPartException; 12 | 13 | /** 14 | * 增加方法注入,将含有 @CurrentUser 注解的方法参数注入当前登录用户 15 | */ 16 | public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver { 17 | @Override 18 | public boolean supportsParameter(MethodParameter parameter) { 19 | return parameter.getParameterType().isAssignableFrom(User.class) 20 | && parameter.hasParameterAnnotation(CurrentUser.class); 21 | } 22 | 23 | @Override 24 | public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { 25 | User user = (User) webRequest.getAttribute("currentUser", RequestAttributes.SCOPE_REQUEST); 26 | if (user != null) { 27 | return user; 28 | } 29 | throw new MissingServletRequestPartException("currentUser"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/config/FastJsonHttpMessageConverterEx.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.config; 2 | 3 | import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; 4 | 5 | public class FastJsonHttpMessageConverterEx extends FastJsonHttpMessageConverter { 6 | public FastJsonHttpMessageConverterEx() { 7 | } 8 | 9 | @Override 10 | protected boolean supports(Class clazz) { 11 | return super.supports(clazz); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/config/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.config; 2 | 3 | 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.apache.commons.lang.exception.ExceptionUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.bind.annotation.ControllerAdvice; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | import org.springframework.web.bind.annotation.ResponseBody; 11 | 12 | import javax.servlet.ServletRequest; 13 | 14 | 15 | @ControllerAdvice 16 | public class GlobalExceptionHandler { 17 | private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); 18 | @ResponseBody 19 | @ExceptionHandler(Exception.class) 20 | public Object handleException(Exception e) { 21 | logger.error(ExceptionUtils.getFullStackTrace(e)); // 记录错误信息 22 | String msg = e.getMessage(); 23 | if (msg == null || msg.equals("")) { 24 | msg = "服务器出错"; 25 | } 26 | JSONObject jsonObject = new JSONObject(); 27 | jsonObject.put("error", msg); 28 | return jsonObject; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/config/WebMvcConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.converter.HttpMessageConverter; 6 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 9 | 10 | import java.util.List; 11 | 12 | @Configuration 13 | public class WebMvcConfigurer extends WebMvcConfigurerAdapter { 14 | @Override 15 | public void addInterceptors(InterceptorRegistry registry) { 16 | registry.addInterceptor(authenticationInterceptor()) 17 | .addPathPatterns("/**"); // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录 18 | super.addInterceptors(registry); 19 | } 20 | 21 | @Override 22 | public void addArgumentResolvers(List argumentResolvers) { 23 | argumentResolvers.add(currentUserMethodArgumentResolver()); 24 | super.addArgumentResolvers(argumentResolvers); 25 | } 26 | 27 | @Override 28 | public void configureMessageConverters(List> converters) { 29 | converters.add(fastJsonHttpMessageConverterEx()); 30 | super.configureMessageConverters(converters); 31 | } 32 | 33 | @Bean 34 | public FastJsonHttpMessageConverterEx fastJsonHttpMessageConverterEx() { 35 | return new FastJsonHttpMessageConverterEx(); 36 | } 37 | 38 | @Bean 39 | public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() { 40 | return new CurrentUserMethodArgumentResolver(); 41 | } 42 | 43 | @Bean 44 | public AuthenticationInterceptor authenticationInterceptor() { 45 | return new AuthenticationInterceptor(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/mapper/PostMapper.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.mapper; 2 | 3 | import com.hpm.blog.model.Post; 4 | 5 | import java.util.List; 6 | 7 | public interface PostMapper { 8 | int add(Post post); 9 | 10 | Post findOne(Post param); 11 | 12 | List all(); 13 | 14 | void update(Post post); 15 | 16 | void delete(int id); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.mapper; 2 | 3 | import com.hpm.blog.model.User; 4 | 5 | public interface UserMapper { 6 | int add(User user); 7 | 8 | User findOne(User user); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/model/Post.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.model; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * 文章类 7 | */ 8 | public class Post { 9 | private Integer id; 10 | private User author; 11 | private Integer authorId; // 作者的 id 12 | private String title; // 文章标题 13 | private String content; // 文章内容 14 | private Date createTime; 15 | 16 | public Integer getId() { 17 | return id; 18 | } 19 | 20 | public void setId(Integer id) { 21 | this.id = id; 22 | } 23 | 24 | public User getAuthor() { 25 | return author; 26 | } 27 | 28 | public void setAuthor(User author) { 29 | this.author = author; 30 | } 31 | 32 | public Integer getAuthorId() { 33 | return authorId; 34 | } 35 | 36 | public void setAuthorId(Integer authorId) { 37 | this.authorId = authorId; 38 | } 39 | 40 | public String getTitle() { 41 | return title; 42 | } 43 | 44 | public void setTitle(String title) { 45 | this.title = title; 46 | } 47 | 48 | public String getContent() { 49 | return content; 50 | } 51 | 52 | public void setContent(String content) { 53 | this.content = content; 54 | } 55 | 56 | public Date getCreateTime() { 57 | return createTime; 58 | } 59 | 60 | public void setCreateTime(Date createTime) { 61 | this.createTime = createTime; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/model/User.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.model; 2 | 3 | public class User { 4 | private Integer id; // 这里不用 int, 应为 int 自动初始化为0,mybatis mapper 文件 就不能使用 了 5 | private String name; 6 | private String password; 7 | 8 | public Integer getId() { 9 | return id; 10 | } 11 | 12 | public void setId(Integer id) { 13 | this.id = id; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | public String getPassword() { 25 | return password; 26 | } 27 | 28 | public void setPassword(String password) { 29 | this.password = password; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/service/AuthenticationService.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.service; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import com.hpm.blog.model.User; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.io.UnsupportedEncodingException; 9 | 10 | @Service 11 | public class AuthenticationService { 12 | public String getToken(User user) { 13 | String token = ""; 14 | try { 15 | token = JWT.create() 16 | .withAudience(user.getId().toString()) // 将 user id 保存到 token 里面 17 | .sign(Algorithm.HMAC256(user.getPassword())); // 以 password 作为 token 的密钥 18 | } catch (UnsupportedEncodingException ignore) { 19 | } 20 | return token; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/service/PostService.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.service; 2 | 3 | import com.hpm.blog.mapper.PostMapper; 4 | import com.hpm.blog.model.Post; 5 | import com.hpm.blog.model.User; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import java.util.List; 11 | 12 | import static com.google.common.base.Preconditions.checkNotNull; 13 | 14 | @Service 15 | public class PostService { 16 | private PostMapper postMapper; 17 | 18 | @Autowired 19 | public PostService(PostMapper postMapper) { 20 | this.postMapper = postMapper; 21 | } 22 | 23 | @Transactional 24 | public Post add(Post post) { 25 | postMapper.add(post); 26 | return findById(post.getId()); 27 | } 28 | 29 | public Post findById(Integer id) { 30 | Post param = new Post(); 31 | param.setId(id); 32 | Post post = postMapper.findOne(param); 33 | checkNotNull(post, "文章不存在"); 34 | return post; 35 | } 36 | 37 | public List all() { 38 | return postMapper.all(); 39 | } 40 | 41 | public Post update(Post post, User currentUser) { 42 | checkNotNull(post.getId(), "id不能为空"); 43 | checkOwner(post.getId(), currentUser); 44 | postMapper.update(post); 45 | return findById(post.getId()); 46 | } 47 | 48 | private void checkOwner(Integer id, User currentUser) { 49 | Post post = findById(id); 50 | if (!post.getAuthorId().equals(currentUser.getId())) { 51 | throw new RuntimeException("不能删除或修改别人的文章"); 52 | } 53 | } 54 | 55 | public void delete(int id, User currentUser) { 56 | checkOwner(id, currentUser); 57 | postMapper.delete(id); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/hpm/blog/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog.service; 2 | 3 | 4 | import com.hpm.blog.mapper.UserMapper; 5 | import com.hpm.blog.model.User; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.security.MessageDigest; 10 | import java.security.NoSuchAlgorithmException; 11 | 12 | @Service 13 | public class UserService { 14 | private UserMapper userMapper; 15 | 16 | @Autowired 17 | public UserService(UserMapper userMapper) { 18 | this.userMapper = userMapper; 19 | } 20 | 21 | public User add(User user) { 22 | String passwordHash = passwordToHash(user.getPassword()); 23 | user.setPassword(passwordHash); 24 | userMapper.add(user); 25 | return findById(user.getId()); 26 | } 27 | 28 | private String passwordToHash(String password) { 29 | try { 30 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 31 | digest.update(password.getBytes()); 32 | byte[] src = digest.digest(); 33 | StringBuilder stringBuilder = new StringBuilder(); 34 | // 字节数组转16进制字符串 35 | // https://my.oschina.net/u/347386/blog/182717 36 | for (byte aSrc : src) { 37 | String s = Integer.toHexString(aSrc & 0xFF); 38 | if (s.length() < 2) { 39 | stringBuilder.append('0'); 40 | } 41 | stringBuilder.append(s); 42 | } 43 | return stringBuilder.toString(); 44 | } catch (NoSuchAlgorithmException ignore) { 45 | } 46 | return null; 47 | } 48 | 49 | public User findById(int id) { 50 | User user = new User(); 51 | user.setId(id); 52 | return userMapper.findOne(user); 53 | } 54 | 55 | public User findByName(String name) { 56 | User param = new User(); 57 | param.setName(name); 58 | return userMapper.findOne(param); 59 | } 60 | 61 | public boolean comparePassword(User user, User userInDataBase) { 62 | return passwordToHash(user.getPassword()) // 将用户提交的密码转换为 hash 63 | .equals(userInDataBase.getPassword()); // 数据库中的 password 已经是 hash,不用转换 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:mysql://localhost:3306/spring-blog?useUnicode=true&characterEncoding=utf8&autoReconnect=true 4 | username: root 5 | password: root 6 | debug: true 7 | mybatis: 8 | type-aliases-package: com.hpm.blog.model 9 | config-location: classpath:mybatis.xml 10 | -------------------------------------------------------------------------------- /src/main/resources/com/hpm/blog/mapper/PostMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | INSERT INTO post (author_id, title, content) VALUES (#{authorId}, #{title}, #{content}); 15 | 16 | 17 | 18 | UPDATE post 19 | 20 | 21 | title=#{title}, 22 | 23 | 24 | content=#{content}, 25 | 26 | 27 | where post.id=#{id} 28 | 29 | 30 | 31 | DELETE FROM post WHERE post.id=#{id} 32 | 33 | 34 | 42 | 43 | 46 | 47 | 48 | SELECT 49 | post.id, 50 | post.author_id , 51 | post.title , 52 | post.content , 53 | post.create_time , 54 | post.update_time, 55 | 56 | `user`.id as author__id, 57 | `user`.`name` as author__name 58 | FROM post 59 | LEFT JOIN `user` ON `user`.id=post.author_id 60 | 61 | -------------------------------------------------------------------------------- /src/main/resources/com/hpm/blog/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | insert into user(name, password) values (#{name},#{password}) 6 | 7 | 8 | 20 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | [%-5level] %d{HH:mm:ss} %logger{36} %line: %msg %n 9 | UTF-8 10 | 11 | 12 | 13 | 14 | 15 | log/spring-boot-blog.log 16 | 17 | log/spring-boot-blog.%d{yyyy-MM-dd}.log 18 | 30 19 | 20 | 21 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{35} : %n %msg %n 22 | UTF-8 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/mybatis.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/static/css/common.css: -------------------------------------------------------------------------------- 1 | .content { 2 | word-break: break-word; 3 | } 4 | 5 | .main { 6 | max-width: 800px; 7 | margin: 0 auto; 8 | } 9 | 10 | .post img { 11 | margin: 0 auto; 12 | display: block; 13 | } 14 | 15 | .fade-enter-active, .fade-leave-active { 16 | transition: all 1s 17 | } 18 | .fade-enter, .fade-leave-active { 19 | opacity: 0 20 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.2.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"} -------------------------------------------------------------------------------- /src/main/resources/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyrijk/spring-boot-blog/34160e7cd92c7171d7fe3da2748e8933e47222f4/src/main/resources/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyrijk/spring-boot-blog/34160e7cd92c7171d7fe3da2748e8933e47222f4/src/main/resources/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyrijk/spring-boot-blog/34160e7cd92c7171d7fe3da2748e8933e47222f4/src/main/resources/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyrijk/spring-boot-blog/34160e7cd92c7171d7fe3da2748e8933e47222f4/src/main/resources/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyrijk/spring-boot-blog/34160e7cd92c7171d7fe3da2748e8933e47222f4/src/main/resources/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spring Boot blog demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 |

前后端分离实践

27 |

Power by Spring Boot & Vue.js

28 |

29 |
30 |
31 |
32 | 33 | 34 | 62 | 63 | 64 | 75 | 76 | 77 | 86 | 87 | 88 | 103 | 104 | 105 | 124 | 125 | 126 | 135 | 136 | 137 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/main/resources/static/js/axios.min.js: -------------------------------------------------------------------------------- 1 | /* axios v0.15.3 | (c) 2016 by Matt Zabriskie */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new i(e),n=s(i.prototype.request,t);return o.extend(n,i.prototype,t),o.extend(n,t),n}var o=n(2),s=n(3),i=n(4),a=n(5),u=r(a);u.Axios=i,u.create=function(e){return r(o.merge(a,e))},u.Cancel=n(22),u.CancelToken=n(23),u.isCancel=n(19),u.all=function(e){return Promise.all(e)},u.spread=n(24),e.exports=u,e.exports.default=u},function(e,t,n){"use strict";function r(e){return"[object Array]"===C.call(e)}function o(e){return"[object ArrayBuffer]"===C.call(e)}function s(e){return"undefined"!=typeof FormData&&e instanceof FormData}function i(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function a(e){return"string"==typeof e}function u(e){return"number"==typeof e}function c(e){return"undefined"==typeof e}function f(e){return null!==e&&"object"==typeof e}function p(e){return"[object Date]"===C.call(e)}function d(e){return"[object File]"===C.call(e)}function l(e){return"[object Blob]"===C.call(e)}function h(e){return"[object Function]"===C.call(e)}function m(e){return f(e)&&h(e.pipe)}function y(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function w(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function g(){return"undefined"!=typeof window&&"undefined"!=typeof document&&"function"==typeof document.createElement}function v(e,t){if(null!==e&&"undefined"!=typeof e)if("object"==typeof e||r(e)||(e=[e]),r(e))for(var n=0,o=e.length;n=200&&e<300}};c.headers={common:{Accept:"application/json, text/plain, */*"}},s.forEach(["delete","get","head"],function(e){c.headers[e]={}}),s.forEach(["post","put","patch"],function(e){c.headers[e]=s.merge(u)}),e.exports=c},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(8),s=n(11),i=n(12),a=n(13),u=n(9),c="undefined"!=typeof window&&window.btoa&&window.btoa.bind(window)||n(14);e.exports=function(e){return new Promise(function(t,f){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest,h="onreadystatechange",m=!1;if("undefined"==typeof window||!window.XDomainRequest||"withCredentials"in l||a(e.url)||(l=new window.XDomainRequest,h="onload",m=!0,l.onprogress=function(){},l.ontimeout=function(){}),e.auth){var y=e.auth.username||"",w=e.auth.password||"";d.Authorization="Basic "+c(y+":"+w)}if(l.open(e.method.toUpperCase(),s(e.url,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l[h]=function(){if(l&&(4===l.readyState||m)&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in l?i(l.getAllResponseHeaders()):null,r=e.responseType&&"text"!==e.responseType?l.response:l.responseText,s={data:r,status:1223===l.status?204:l.status,statusText:1223===l.status?"No Content":l.statusText,headers:n,config:e,request:l};o(t,f,s),l=null}},l.onerror=function(){f(u("Network Error",e)),l=null},l.ontimeout=function(){f(u("timeout of "+e.timeout+"ms exceeded",e,"ECONNABORTED")),l=null},r.isStandardBrowserEnv()){var g=n(15),v=(e.withCredentials||a(e.url))&&e.xsrfCookieName?g.read(e.xsrfCookieName):void 0;v&&(d[e.xsrfHeaderName]=v)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),e.withCredentials&&(l.withCredentials=!0),e.responseType)try{l.responseType=e.responseType}catch(e){if("json"!==l.responseType)throw e}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),f(e),l=null)}),void 0===p&&(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(9);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n)):e(n)}},function(e,t,n){"use strict";var r=n(10);e.exports=function(e,t,n,o){var s=new Error(e);return r(s,t,n,o)}},function(e,t){"use strict";e.exports=function(e,t,n,r){return e.config=t,n&&(e.code=n),e.response=r,e}},function(e,t,n){"use strict";function r(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=n(2);e.exports=function(e,t,n){if(!t)return e;var s;if(n)s=n(t);else if(o.isURLSearchParams(t))s=t.toString();else{var i=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)&&(t+="[]"),o.isArray(e)||(e=[e]),o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),i.push(r(t)+"="+r(e))}))}),s=i.join("&")}return s&&(e+=(e.indexOf("?")===-1?"?":"&")+s),e}},function(e,t,n){"use strict";var r=n(2);e.exports=function(e){var t,n,o,s={};return e?(r.forEach(e.split("\n"),function(e){o=e.indexOf(":"),t=r.trim(e.substr(0,o)).toLowerCase(),n=r.trim(e.substr(o+1)),t&&(s[t]=s[t]?s[t]+", "+n:n)}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t){"use strict";function n(){this.message="String contains an invalid character"}function r(e){for(var t,r,s=String(e),i="",a=0,u=o;s.charAt(0|a)||(u="=",a%1);i+=u.charAt(63&t>>8-a%1*8)){if(r=s.charCodeAt(a+=.75),r>255)throw new n;t=t<<8|r}return i}var o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";n.prototype=new Error,n.prototype.code=5,n.prototype.name="InvalidCharacterError",e.exports=r},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,s,i){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(s)&&a.push("domain="+s),i===!0&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";function r(){this.handlers=[]}var o=n(2);r.prototype.use=function(e,t){return this.handlers.push({fulfilled:e,rejected:t}),this.handlers.length-1},r.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},r.prototype.forEach=function(e){o.forEach(this.handlers,function(t){null!==t&&e(t)})},e.exports=r},function(e,t,n){"use strict";function r(e){e.cancelToken&&e.cancelToken.throwIfRequested()}var o=n(2),s=n(18),i=n(19),a=n(5);e.exports=function(e){r(e),e.headers=e.headers||{},e.data=s(e.data,e.headers,e.transformRequest),e.headers=o.merge(e.headers.common||{},e.headers[e.method]||{},e.headers||{}),o.forEach(["delete","get","head","post","put","patch","common"],function(t){delete e.headers[t]});var t=e.adapter||a.adapter;return t(e).then(function(t){return r(e),t.data=s(t.data,t.headers,e.transformResponse),t},function(t){return i(t)||(r(e),t&&t.response&&(t.response.data=s(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)})}},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t,n){return r.forEach(n,function(n){e=n(e,t)}),e}},function(e,t){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,"")}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(22);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])}); 3 | //# sourceMappingURL=axios.min.map -------------------------------------------------------------------------------- /src/main/resources/static/js/index.js: -------------------------------------------------------------------------------- 1 | var bus = new Vue() 2 | 3 | function emitError(message) { 4 | bus.$emit('error', message) 5 | } 6 | 7 | function onError(fn) { 8 | bus.$on('error', fn) 9 | } 10 | 11 | function emitInfo(message) { 12 | bus.$emit('info', message) 13 | } 14 | 15 | function onInfo(fn) { 16 | bus.$on('info', fn) 17 | } 18 | 19 | function emitLogin() { 20 | bus.$emit('login') 21 | } 22 | 23 | function onLogin(fn) { 24 | bus.$on('login', fn) 25 | } 26 | 27 | function handleError(errorMessage) { 28 | emitError(errorMessage) 29 | } 30 | 31 | var Alert = { 32 | template: '#alert', 33 | data: function () { 34 | return { 35 | classObject: { 36 | 'is-danger': false, 37 | 'is-info': false 38 | }, 39 | message: '', 40 | visible: false 41 | } 42 | }, 43 | mounted: function () { 44 | onError(function (errorMessage) { 45 | this.classObject['is-danger'] = true; 46 | this.message = errorMessage 47 | this.visible = true 48 | setTimeout(this.reset.bind(this), 2000) 49 | }.bind(this)) 50 | 51 | onInfo(function (message) { 52 | this.classObject['is-info'] = true; 53 | this.message = message 54 | this.visible = true 55 | setTimeout(this.reset.bind(this), 2000) 56 | }.bind(this)) 57 | }, 58 | methods: { 59 | reset: function () { 60 | this.visible = false 61 | this.classObject = {} 62 | } 63 | } 64 | } 65 | 66 | var Header = { 67 | template: '#header', 68 | data: function () { 69 | return { 70 | currentUser: JSON.parse(sessionStorage.getItem('currentUser')) 71 | } 72 | }, 73 | mounted: function () { 74 | onLogin(function () { 75 | this.currentUser = JSON.parse(sessionStorage.getItem('currentUser')); 76 | }.bind(this)) 77 | }, 78 | methods: { 79 | logout: function () { 80 | this.currentUser = null 81 | sessionStorage.removeItem('token') 82 | sessionStorage.removeItem('currentUser') 83 | this.$router.push('/') 84 | } 85 | } 86 | } 87 | 88 | var PostList = { 89 | template: '#posts', 90 | data: function () { 91 | return { 92 | posts: [] 93 | } 94 | }, 95 | filters: { 96 | longToDate: function (long) { 97 | var date = new Date(long) 98 | return date.toLocaleString() 99 | } 100 | }, 101 | mounted: function () { 102 | axios.get('/api/post').then(function (res) { 103 | if (res.data.error) { 104 | handleError(res.data.error) 105 | } else { 106 | var posts = res.data 107 | this.posts = posts.sort(function (a, b) { 108 | return b.createTime - a.createTime 109 | }) 110 | } 111 | }.bind(this)) 112 | }, 113 | methods: { 114 | showDetail: {} 115 | } 116 | } 117 | 118 | var LoginForm = { 119 | template: '#login-form', 120 | data: function () { 121 | return { 122 | name: '', 123 | password: '' 124 | } 125 | }, 126 | methods: { 127 | handleSubmit: function (e) { 128 | e.preventDefault() 129 | if (this.name === '') { 130 | handleError('用户名不能为空') 131 | return false 132 | } 133 | if (this.password === '') { 134 | handleError('密码不能为空') 135 | return false 136 | } 137 | axios.post('/api/authentication', { 138 | name: this.name, 139 | password: this.password 140 | }).then(function (res) { 141 | if (res.data.error) { 142 | handleError(res.data.error) 143 | } else { 144 | sessionStorage.setItem('token', res.data.token) 145 | sessionStorage.setItem('currentUser', JSON.stringify(res.data.user)); 146 | emitLogin() 147 | this.$router.push('/') 148 | } 149 | }.bind(this)) 150 | } 151 | } 152 | } 153 | 154 | var SignupForm = { 155 | template: '#signup-form', 156 | data: function () { 157 | return { 158 | name: '', 159 | password: '', 160 | passwordAgain: '' 161 | } 162 | }, 163 | methods: { 164 | handleSubmit: function (e) { 165 | e.preventDefault() 166 | if (this.name === '') { 167 | handleError('用户名不能为空') 168 | return false 169 | } 170 | if (this.password === '') { 171 | handleError('密码不能为空') 172 | return false 173 | } 174 | if (this.password !== this.passwordAgain) { 175 | handleError('两次输入的密码不一致') 176 | return false 177 | } 178 | axios.post('/api/user', { 179 | name: this.name, 180 | password: this.password 181 | }).then(function (res) { 182 | if (res.data.error) { 183 | handleError(res.data.error) 184 | } else { 185 | emitInfo('注册成功,请登录') 186 | this.$router.push('/login') 187 | } 188 | }.bind(this)) 189 | } 190 | } 191 | } 192 | 193 | 194 | marked.setOptions({ 195 | renderer: new marked.Renderer(), 196 | gfm: true, 197 | tables: true, 198 | breaks: true, 199 | pedantic: false, 200 | sanitize: true, 201 | smartLists: true, 202 | smartypants: true 203 | }); 204 | 205 | var PostDetail = { 206 | template: '#post-detail', 207 | data: function () { 208 | return { 209 | post: { 210 | content: '', 211 | author: {} 212 | } 213 | } 214 | }, 215 | mounted: function () { 216 | axios.get('/api/post/' + this.$route.params.id).then(function (res) { 217 | if (res.data.error) { 218 | handleError(res.data.error) 219 | return 220 | } else { 221 | this.post = res.data 222 | } 223 | }.bind(this)) 224 | }, 225 | filters: { 226 | longToDate: PostList.filters.longToDate 227 | }, 228 | computed: { 229 | compiledMarkdown: function () { 230 | return marked(this.post.content) 231 | } 232 | } 233 | } 234 | 235 | var NewPost = { 236 | template: '#new-post', 237 | data: function () { 238 | return { 239 | title: null, 240 | content: null 241 | } 242 | 243 | }, 244 | beforeRouteEnter: function (to, from, next) { 245 | if (sessionStorage.getItem('token') === null) { 246 | next({path: 'login'}) 247 | emitInfo('请先登录') 248 | } else { 249 | next() 250 | } 251 | }, 252 | methods: { 253 | handleSubmit: function (e) { 254 | if (this.title === null || this.title === '') { 255 | handleError('标题不能为空') 256 | return false 257 | } 258 | if (this.content === null || this.content === '') { 259 | handleError('内容不能为空') 260 | return false 261 | } 262 | axios.post('/api/post', { 263 | title: this.title, 264 | content: this.content, 265 | }, { 266 | headers: {token: sessionStorage.getItem('token')} 267 | } 268 | ).then(function (res) { 269 | if (res.data.error) { 270 | handleError(res.data.error) 271 | return 272 | } 273 | this.$router.push('/posts/' + res.data.id) 274 | }.bind(this)) 275 | } 276 | } 277 | } 278 | 279 | var routes = [ 280 | {path: '/', component: PostList}, 281 | {path: '/posts', component: PostList}, 282 | {path: '/login', component: LoginForm}, 283 | {path: '/signup', component: SignupForm}, 284 | {path: '/posts/new', component: NewPost}, 285 | {path: '/posts/:id', component: PostDetail}, 286 | ] 287 | 288 | var router = new VueRouter({ 289 | routes: routes 290 | }) 291 | 292 | new Vue({ 293 | router: router, 294 | components: { 295 | 'x-header': Header, 296 | 'x-alert': Alert, 297 | } 298 | }).$mount('#app') -------------------------------------------------------------------------------- /src/main/resources/static/js/marked.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * marked - a markdown parser 3 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/chjj/marked 5 | */ 6 | (function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"
"+(escaped?code:escape(code,true))+"\n
"}return'
'+(escaped?code:escape(code,true))+"\n
\n"};Renderer.prototype.blockquote=function(quote){return"
\n"+quote+"
\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"'+text+"\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"\n"};Renderer.prototype.listitem=function(text){return"
  • "+text+"
  • \n"};Renderer.prototype.paragraph=function(text){return"

    "+text+"

    \n"};Renderer.prototype.table=function(header,body){return"\n"+"\n"+header+"\n"+"\n"+body+"\n"+"
    \n"};Renderer.prototype.tablerow=function(content){return"\n"+content+"\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"\n"};Renderer.prototype.strong=function(text){return""+text+""};Renderer.prototype.em=function(text){return""+text+""};Renderer.prototype.codespan=function(text){return""+text+""};Renderer.prototype.br=function(){return this.options.xhtml?"
    ":"
    "};Renderer.prototype.del=function(text){return""+text+""};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='
    ";return out};Renderer.prototype.image=function(href,title,text){var out=''+text+'":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i/g,">").replace(/"/g,""").replace(/'/g,"'")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;iAn error occured:

    "+escape(e.message+"",true)+"
    "}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); -------------------------------------------------------------------------------- /src/test/java/com/hpm/blog/SpringBootBlogApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.hpm.blog; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringBootBlogApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------