├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── new-star-blog.png └── src ├── main ├── java │ └── com │ │ └── waylau │ │ └── spring │ │ └── boot │ │ └── blog │ │ ├── Application.java │ │ ├── config │ │ └── SecurityConfig.java │ │ ├── controller │ │ ├── AdminController.java │ │ ├── BlogController.java │ │ ├── CatalogController.java │ │ ├── CommentController.java │ │ ├── HelloController.java │ │ ├── MainController.java │ │ ├── UserController.java │ │ ├── UserspaceController.java │ │ └── VoteController.java │ │ ├── domain │ │ ├── Authority.java │ │ ├── Blog.java │ │ ├── Catalog.java │ │ ├── Comment.java │ │ ├── User.java │ │ ├── Vote.java │ │ └── es │ │ │ └── EsBlog.java │ │ ├── repository │ │ ├── AuthorityRepository.java │ │ ├── BlogRepository.java │ │ ├── CatalogRepository.java │ │ ├── CommentRepository.java │ │ ├── UserRepository.java │ │ ├── VoteRepository.java │ │ └── es │ │ │ └── EsBlogRepository.java │ │ ├── service │ │ ├── AuthorityService.java │ │ ├── AuthorityServiceImpl.java │ │ ├── BlogService.java │ │ ├── BlogServiceImpl.java │ │ ├── CatalogService.java │ │ ├── CatalogServiceImpl.java │ │ ├── CommentService.java │ │ ├── CommentServiceImpl.java │ │ ├── EsBlogService.java │ │ ├── EsBlogServiceImpl.java │ │ ├── UserService.java │ │ ├── UserServiceImpl.java │ │ ├── VoteService.java │ │ └── VoteServiceImpl.java │ │ ├── util │ │ └── ConstraintViolationExceptionHandler.java │ │ └── vo │ │ ├── CatalogVO.java │ │ ├── Menu.java │ │ ├── Response.java │ │ └── TagVO.java └── resources │ ├── application.properties │ ├── import.sql │ ├── static │ ├── css │ │ ├── blog.css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap-table.css │ │ ├── bootstrap-table.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.css.map │ │ ├── component-chosen.css │ │ ├── component-chosen.min.css │ │ ├── cropbox.css │ │ ├── emoji │ │ │ ├── Sysmbols.css │ │ │ ├── nature.css │ │ │ ├── object.css │ │ │ ├── people.css │ │ │ ├── place.css │ │ │ └── twemoji.css │ │ ├── font-awesome.css │ │ ├── font-awesome.css.map │ │ ├── font-awesome.min.css │ │ ├── images │ │ │ └── emoji │ │ │ │ ├── Sysmbols.png │ │ │ │ ├── nature.png │ │ │ │ ├── object.png │ │ │ │ ├── people.png │ │ │ │ ├── place.png │ │ │ │ └── twemoji.png │ │ ├── jquery.tagsinput.min.css │ │ ├── nprogress.css │ │ ├── style.css │ │ ├── tether-theme-arrows-dark.css │ │ ├── tether-theme-arrows-dark.min.css │ │ ├── tether-theme-arrows.css │ │ ├── tether-theme-arrows.min.css │ │ ├── tether-theme-basic.css │ │ ├── tether-theme-basic.min.css │ │ ├── tether.css │ │ ├── tether.min.css │ │ ├── thinker-md.vendor.css │ │ ├── thymeleaf-bootstrap-paginator.css │ │ ├── toastr.css │ │ └── toastr.min.css │ ├── favicon.ico │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── gly-halflings-regular.eot │ │ ├── gly-halflings-regular.svg │ │ ├── gly-halflings-regular.ttf │ │ ├── gly-halflings-regular.woff │ │ └── gly-halflings-regular.woff2 │ ├── images │ │ ├── avatar-defualt.jpg │ │ └── delete.png │ └── js │ │ ├── admins │ │ └── main.js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── catalog-generator.js │ │ ├── chosen.jquery.js │ │ ├── cropbox.js │ │ ├── index.js │ │ ├── jquery-3.1.1.min.js │ │ ├── jquery.form.min.js │ │ ├── jquery.tag-editor.js │ │ ├── jquery.tag-editor.min.js │ │ ├── jquery.tagsinput.min.js │ │ ├── main.js │ │ ├── nprogress.js │ │ ├── tether.js │ │ ├── tether.min.js │ │ ├── thinker-md.vendor.js │ │ ├── thinker-md.vendor.min.js │ │ ├── thinker-md.vendor.min.map │ │ ├── thymeleaf-bootstrap-paginator.js │ │ ├── toastr.min.js │ │ ├── users │ │ └── main.js │ │ └── userspace │ │ ├── blog.js │ │ ├── blogedit.js │ │ ├── main.js │ │ └── u.js │ └── templates │ ├── admins │ └── index.html │ ├── fragments │ ├── footer.html │ ├── header.html │ └── page.html │ ├── index.html │ ├── login.html │ ├── register.html │ ├── users │ ├── add.html │ ├── edit.html │ └── list.html │ └── userspace │ ├── avatar.html │ ├── blog.html │ ├── blogedit.html │ ├── catalogedit.html │ ├── profile.html │ └── u.html └── test └── java └── com └── waylau └── spring └── boot └── blog ├── ApplicationTests.java └── controller └── HelloControllerTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | nbproject/private/ 21 | build/ 22 | nbbuild/ 23 | dist/ 24 | nbdist/ 25 | .nb-gradle/ 26 | /bin/ 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Way Lau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NewStarBlog 2 | 3 | NewStarBlog is an open source Java blog platform. NewStarBlog 是开源 Java 博客平台。 4 | 5 | ## Technologies 涉及技术 6 | 7 | NewStarBlog covers Spring Framework 5, Spring Boot 2, Thymeleaf, Elasticsearch, Spring Data, and so on. The following is a list of technologies inlcuded in NewStarBlog: 8 | 9 | * JDK 8 10 | * Gradle 4.5.1 11 | * Spring Boot 2.0.1 12 | * Spring 5.0.5 13 | * Thymeleaf 3.0.6.RELEASE 14 | * Thymeleaf Layout Dialect 2.2.2 15 | * MySQL Community Server 5.7.17 16 | * MySQL Workbench 6.3.9 17 | * Spring Data JPA 2.0.1 18 | * Hibernate 5.2.10.Final 19 | * MySQL Connector/J 6.0.5 20 | * H2 Database 1.4.196 21 | * Elasticsearch 5.5.0 22 | * Spring Data Elasticsearch 3.0.6 23 | * Tether 1.4.0 : 24 | * Bootstrap v4.0.0-alpha.6 : 25 | * jQuery 3.1.1 : 26 | * Font Awesome 4.7.0 : 27 | * NProgress 0.2.0 : 28 | * Thinker-md : 29 | * jQuery Tags Input 1.3.6 : 30 | * Bootstrap Chosen 1.0.3 : 31 | * toastr 2.1.1 : 32 | * Spring Security 5.0.4 33 | * Thymeleaf Spring Security 3.0.9 34 | * Apache Commons Lang 3.6 35 | * Markdown parser for the JVM 0.16 36 | * MongoDB 3.4.6 37 | * Spring Data Mongodb 2.0.0.M4 38 | * Embedded MongoDB 2.0.0 39 | * IK Analysis for Elasticsearch 5.5.0 40 | 41 | ## Features 特性 42 | 43 | * Multi-user blog platform. (多用户博客平台) 44 | * Markdown. (Markdown 编写) 45 | * Emoji. (表情,头像) 46 | * Aggregate classification/tag 47 | * Big Data search.(大数据分析) 48 | * Easy to use.(易于使用) 49 | * RESTful API. 50 | * Good look. (界面友好) 51 | * Chinese characters friendly.(中文友好) 52 | * ... 53 | 54 |  55 | 56 | ## How to 如何运行 57 | 58 | It's so easy to start up NewStarBlog with 2 steps. 59 | 60 | 只需要两步。 61 | 62 | ### 1. Get source(获取源码) 63 | 64 | ``` 65 | $ git clone https://github.com/waylau/new-star-blog.git 66 | ``` 67 | 68 | ### 2. Run(运行) 69 | 70 | ``` 71 | $ gradlew bootRun 72 | ``` 73 | 74 | 75 | then, you can visit the application at . 76 | 77 | More details can be found on my [blog](https://waylau.com). 78 | 79 | ## Who use 谁在使用这个博客 80 | 81 | If you have successfully deployed NewStarBlog online, just let me know. Here are the users: 82 | 83 | * 84 | * 85 | * 86 | 87 | ## Related projects 相关的项目 88 | 89 | * MongoDB File Server is a file server system based on MongoDB. 基于 MongoDB 的文件服务器。see 90 | 91 | ## Tutorial 手把手教你实现 92 | 93 | * Vedio: [基于Spring Boot的博客系统实战](https://coding.imooc.com/class/125.html) 94 | * Book: [《Spring Boot 企业级应用开发实战》](https://github.com/waylau/spring-boot-enterprise-application-development) 95 | 96 | ## Issue 意见、建议 97 | 98 | 如有勘误、意见或建议欢迎拍砖 99 | 100 | ## Host(托管) 101 | 102 | * GitHub:https://github.com/waylau/new-star-blog 103 | * 码云:https://gitee.com/waylau/new-star-blog 104 | 105 | ## Contact 联系作者: 106 | 107 | * Blog: [waylau.com](https://waylau.com) 108 | * Gmail: [waylau521(at)gmail.com](mailto:waylau521@gmail.com) 109 | * Weibo: [waylau521](http://weibo.com/waylau521) 110 | * Twitter: [waylau521](https://twitter.com/waylau521) 111 | * Github : [waylau](https://github.com/waylau) 112 | 113 | ## Donate 捐赠 114 | 115 | Support me! 116 | 117 | 感谢您对老卫开源工作的支持! 118 | 119 |  120 | 121 | 捐赠所得所有款项将用于开源事业! 122 | 123 | See [the list of Donors](https://waylau.com/donate). -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { // buildscript 代码块中脚本优先执行 2 | 3 | // ext 用于定义动态属性 4 | ext { 5 | springBootVersion = '2.0.1.RELEASE' 6 | } 7 | 8 | // 使用了 Maven 的中央仓库(也可以指定其他仓库) 9 | repositories { 10 | //mavenCentral() 11 | //maven { url "https://repo.spring.io/snapshot" } 12 | //maven { url "https://repo.spring.io/milestone" } 13 | maven { url "http://maven.aliyun.com/nexus/content/groups/public/" } 14 | } 15 | 16 | // 依赖关系 17 | dependencies { 18 | 19 | // classpath 声明说明了在执行其余的脚本时,ClassLoader 可以使用这些依赖项 20 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 21 | } 22 | } 23 | 24 | // 使用插件 25 | apply plugin: 'java' 26 | apply plugin: 'eclipse' 27 | apply plugin: 'org.springframework.boot' 28 | apply plugin: 'io.spring.dependency-management' 29 | 30 | // 指定了生成的编译文件的版本,默认是打成了 jar 包 31 | version = '1.0.0' 32 | 33 | // 指定编译 .java 文件的 JDK 版本 34 | sourceCompatibility = 1.8 35 | 36 | // 使用了 Maven 的中央仓库(也可以指定其他仓库) 37 | repositories { 38 | //mavenCentral() 39 | //maven { url "https://repo.spring.io/snapshot" } 40 | //maven { url "https://repo.spring.io/milestone" } 41 | maven { url "http://maven.aliyun.com/nexus/content/groups/public/" } 42 | } 43 | 44 | // 依赖关系 45 | dependencies { 46 | 47 | // 该依赖用于编译阶段 48 | compile('org.springframework.boot:spring-boot-starter-web') 49 | 50 | // 添加 Thymeleaf 的依赖 51 | compile('org.springframework.boot:spring-boot-starter-thymeleaf') 52 | 53 | // 添加 Spring Data JPA 的依赖 54 | compile('org.springframework.boot:spring-boot-starter-data-jpa') 55 | 56 | // 添加 MySQL 连接驱动的依赖 57 | compile('mysql:mysql-connector-java:6.0.5') 58 | 59 | // 添加 Apache Commons Lang 依赖 60 | compile('org.apache.commons:commons-lang3:3.6') 61 | 62 | // 添加 Spring Security 依赖 63 | compile('org.springframework.boot:spring-boot-starter-security') 64 | 65 | // 添加 Thymeleaf Spring Security 依赖 66 | compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE') 67 | 68 | // 添加 Markdown parser 依赖 69 | compile('es.nitaur.markdown:txtmark:0.16') 70 | 71 | // 添加 Spring Data Elasticsearch 的依赖 72 | compile('org.springframework.boot:spring-boot-starter-data-elasticsearch') 73 | 74 | // 添加 H2 的依赖 75 | runtime('com.h2database:h2:1.4.196') 76 | 77 | // 该依赖用于测试阶段 78 | testCompile('org.springframework.boot:spring-boot-starter-test') 79 | } 80 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 06 12:27:20 CET 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /new-star-blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/new-star-blog.png -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/Application.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.security.authentication.AuthenticationProvider; 6 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | 15 | /** 16 | * 安全配置类. 17 | * 18 | * @since 1.0.0 2017年5月30日 19 | * @author Way Lau 20 | */ 21 | @EnableWebSecurity 22 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 23 | private static final String KEY = "waylau.com"; 24 | 25 | @Autowired 26 | private UserDetailsService userDetailsService; 27 | 28 | @Autowired 29 | private PasswordEncoder passwordEncoder; 30 | 31 | @Bean 32 | public PasswordEncoder passwordEncoder() { 33 | return new BCryptPasswordEncoder(); // 使用 BCrypt 加密 34 | } 35 | 36 | @Bean 37 | public AuthenticationProvider authenticationProvider() { 38 | DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); 39 | authenticationProvider.setUserDetailsService(userDetailsService); 40 | authenticationProvider.setPasswordEncoder(passwordEncoder); // 设置密码加密方式 41 | return authenticationProvider; 42 | } 43 | 44 | /** 45 | * 自定义配置 46 | */ 47 | @Override 48 | protected void configure(HttpSecurity http) throws Exception { 49 | http.authorizeRequests().antMatchers("/css/**", "/js/**", "/fonts/**", "/index").permitAll() // 都可以访问 50 | .antMatchers("/h2-console/**").permitAll() // 都可以访问 51 | .antMatchers("/admins/**").hasRole("ADMIN") // 需要相应的角色才能访问 52 | .and() 53 | .formLogin() //基于 Form 表单登录验证 54 | .loginPage("/login").failureUrl("/login-error") // 自定义登录界面 55 | .and().rememberMe().key(KEY) // 启用 remember me 56 | .and().exceptionHandling().accessDeniedPage("/403"); // 处理异常,拒绝访问就重定向到 403 页面 57 | http.csrf().ignoringAntMatchers("/h2-console/**"); // 禁用 H2 控制台的 CSRF 防护 58 | http.headers().frameOptions().sameOrigin(); // 允许来自同一来源的H2 控制台的请求 59 | } 60 | 61 | /** 62 | * 认证信息管理 63 | * @param auth 64 | * @throws Exception 65 | */ 66 | @Autowired 67 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 68 | auth.userDetailsService(userDetailsService); 69 | auth.authenticationProvider(authenticationProvider()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/controller/AdminController.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.servlet.ModelAndView; 11 | 12 | import com.waylau.spring.boot.blog.vo.Menu; 13 | 14 | /** 15 | * 后台管理控制器. 16 | * 17 | * @since 1.0.0 2017年5月28日 18 | * @author Way Lau 19 | */ 20 | @Controller 21 | @RequestMapping("/admins") 22 | public class AdminController { 23 | 24 | /** 25 | * 获取后台管理主页面 26 | * @return 27 | */ 28 | @GetMapping 29 | public ModelAndView listUsers(Model model) { 30 | List list = new ArrayList<>(); 31 | list.add(new Menu("用户管理", "/users")); 32 | model.addAttribute("list", list); 33 | return new ModelAndView("/admins/index", "model", model); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/controller/BlogController.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.controller; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.PageRequest; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.data.domain.Sort; 10 | import org.springframework.data.domain.Sort.Direction; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.Model; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestParam; 16 | 17 | import com.waylau.spring.boot.blog.domain.User; 18 | import com.waylau.spring.boot.blog.domain.es.EsBlog; 19 | import com.waylau.spring.boot.blog.service.EsBlogService; 20 | import com.waylau.spring.boot.blog.vo.TagVO; 21 | 22 | /** 23 | * Blog 控制器. 24 | * 25 | * @since 1.0.0 2017年8月23日 26 | * @author Way Lau 27 | */ 28 | @Controller 29 | @RequestMapping("/blogs") 30 | public class BlogController { 31 | 32 | @Autowired 33 | private EsBlogService esBlogService; 34 | 35 | @GetMapping 36 | public String listEsBlogs( 37 | @RequestParam(value="order",required=false,defaultValue="new") String order, 38 | @RequestParam(value="keyword",required=false,defaultValue="" ) String keyword, 39 | @RequestParam(value="async",required=false) boolean async, 40 | @RequestParam(value="pageIndex",required=false,defaultValue="0") int pageIndex, 41 | @RequestParam(value="pageSize",required=false,defaultValue="10") int pageSize, 42 | Model model) { 43 | 44 | Page page = null; 45 | List list = null; 46 | boolean isEmpty = true; // 系统初始化时,没有博客数据 47 | try { 48 | if (order.equals("hot")) { // 最热查询 49 | Sort sort = new Sort(Direction.DESC,"readSize","commentSize","voteSize","createTime"); 50 | Pageable pageable = PageRequest.of(pageIndex, pageSize, sort); 51 | page = esBlogService.listHotestEsBlogs(keyword, pageable); 52 | } else if (order.equals("new")) { // 最新查询 53 | Sort sort = new Sort(Direction.DESC,"createTime"); 54 | Pageable pageable = PageRequest.of(pageIndex, pageSize, sort); 55 | page = esBlogService.listNewestEsBlogs(keyword, pageable); 56 | } 57 | 58 | isEmpty = false; 59 | } catch (Exception e) { 60 | Pageable pageable = PageRequest.of(pageIndex, pageSize); 61 | page = esBlogService.listEsBlogs(pageable); 62 | } 63 | 64 | list = page.getContent(); // 当前所在页面数据列表 65 | 66 | 67 | model.addAttribute("order", order); 68 | model.addAttribute("keyword", keyword); 69 | model.addAttribute("page", page); 70 | model.addAttribute("blogList", list); 71 | 72 | // 首次访问页面才加载 73 | if (!async && !isEmpty) { 74 | List newest = esBlogService.listTop5NewestEsBlogs(); 75 | model.addAttribute("newest", newest); 76 | List hotest = esBlogService.listTop5HotestEsBlogs(); 77 | model.addAttribute("hotest", hotest); 78 | List tags = esBlogService.listTop30Tags(); 79 | model.addAttribute("tags", tags); 80 | List users = esBlogService.listTop12Users(); 81 | model.addAttribute("users", users); 82 | } 83 | 84 | return (async==true?"/index :: #mainContainerRepleace":"/index"); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/controller/CatalogController.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.controller; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import javax.validation.ConstraintViolationException; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.security.access.prepost.PreAuthorize; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.security.core.userdetails.UserDetailsService; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.ui.Model; 15 | import org.springframework.web.bind.annotation.DeleteMapping; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RequestBody; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RequestParam; 22 | 23 | import com.waylau.spring.boot.blog.domain.Catalog; 24 | import com.waylau.spring.boot.blog.domain.User; 25 | import com.waylau.spring.boot.blog.service.CatalogService; 26 | import com.waylau.spring.boot.blog.util.ConstraintViolationExceptionHandler; 27 | import com.waylau.spring.boot.blog.vo.CatalogVO; 28 | import com.waylau.spring.boot.blog.vo.Response; 29 | 30 | 31 | /** 32 | * 分类 控制器. 33 | * 34 | * @since 1.0.0 2017年4月10日 35 | * @author Way Lau 36 | */ 37 | @Controller 38 | @RequestMapping("/catalogs") 39 | public class CatalogController { 40 | 41 | @Autowired 42 | private CatalogService catalogService; 43 | 44 | @Autowired 45 | private UserDetailsService userDetailsService; 46 | 47 | /** 48 | * 获取分类列表 49 | * @param username 50 | * @param model 51 | * @return 52 | */ 53 | @GetMapping 54 | public String listComments(@RequestParam(value="username",required=true) String username, Model model) { 55 | User user = (User)userDetailsService.loadUserByUsername(username); 56 | List catalogs = catalogService.listCatalogs(user); 57 | 58 | // 判断操作用户是否是分类的所有者 59 | boolean isOwner = false; 60 | 61 | if (SecurityContextHolder.getContext().getAuthentication() !=null 62 | && SecurityContextHolder.getContext().getAuthentication().isAuthenticated() 63 | && !SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString() 64 | .equals("anonymousUser")) { 65 | User principal = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 66 | if (principal !=null && user.getUsername().equals(principal.getUsername())) { 67 | isOwner = true; 68 | } 69 | } 70 | 71 | model.addAttribute("isCatalogsOwner", isOwner); 72 | model.addAttribute("catalogs", catalogs); 73 | return "/userspace/u :: #catalogRepleace"; 74 | } 75 | 76 | /** 77 | * 创建分类 78 | * @param catalogVO 79 | * @return 80 | */ 81 | @PostMapping 82 | @PreAuthorize("authentication.name.equals(#catalogVO.username)")// 指定用户才能操作方法 83 | public ResponseEntity create(@RequestBody CatalogVO catalogVO) { 84 | 85 | String username = catalogVO.getUsername(); 86 | Catalog catalog = catalogVO.getCatalog(); 87 | 88 | User user = (User)userDetailsService.loadUserByUsername(username); 89 | 90 | try { 91 | catalog.setUser(user); 92 | catalogService.saveCatalog(catalog); 93 | } catch (ConstraintViolationException e) { 94 | return ResponseEntity.ok().body(new Response(false, 95 | ConstraintViolationExceptionHandler.getMessage(e))); 96 | } catch (Exception e) { 97 | return ResponseEntity.ok().body(new Response(false, e.getMessage())); 98 | } 99 | 100 | return ResponseEntity.ok().body(new Response(true, "处理成功", null)); 101 | } 102 | 103 | /** 104 | * 删除分类 105 | * @param username 106 | * @param id 107 | * @return 108 | */ 109 | @DeleteMapping("/{id}") 110 | @PreAuthorize("authentication.name.equals(#username)") // 指定用户才能操作方法 111 | public ResponseEntity delete(String username, @PathVariable("id") Long id) { 112 | try { 113 | catalogService.removeCatalog(id); 114 | } catch (ConstraintViolationException e) { 115 | return ResponseEntity.ok().body(new Response(false, 116 | ConstraintViolationExceptionHandler.getMessage(e))); 117 | } catch (Exception e) { 118 | return ResponseEntity.ok().body(new Response(false, e.getMessage())); 119 | } 120 | 121 | return ResponseEntity.ok().body(new Response(true, "处理成功", null)); 122 | } 123 | 124 | /** 125 | * 获取分类编辑界面 126 | * @param model 127 | * @return 128 | */ 129 | @GetMapping("/edit") 130 | public String getCatalogEdit(Model model) { 131 | Catalog catalog = new Catalog(null, null); 132 | model.addAttribute("catalog",catalog); 133 | return "/userspace/catalogedit"; 134 | } 135 | 136 | /** 137 | * 根据 Id 获取编辑界面 138 | * @param id 139 | * @param model 140 | * @return 141 | */ 142 | @GetMapping("/edit/{id}") 143 | public String getCatalogById(@PathVariable("id") Long id, Model model) { 144 | Optional optionalCatalog = catalogService.getCatalogById(id); 145 | Catalog catalog = null; 146 | 147 | if (optionalCatalog.isPresent()) { 148 | catalog = optionalCatalog.get(); 149 | } 150 | 151 | model.addAttribute("catalog",catalog); 152 | return "/userspace/catalogedit"; 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/controller/CommentController.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.controller; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import javax.validation.ConstraintViolationException; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.security.access.prepost.PreAuthorize; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.ui.Model; 14 | import org.springframework.web.bind.annotation.DeleteMapping; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.PostMapping; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RequestParam; 20 | 21 | import com.waylau.spring.boot.blog.domain.Blog; 22 | import com.waylau.spring.boot.blog.domain.Comment; 23 | import com.waylau.spring.boot.blog.domain.User; 24 | import com.waylau.spring.boot.blog.service.BlogService; 25 | import com.waylau.spring.boot.blog.service.CommentService; 26 | import com.waylau.spring.boot.blog.util.ConstraintViolationExceptionHandler; 27 | import com.waylau.spring.boot.blog.vo.Response; 28 | 29 | /** 30 | * 评论 控制器. 31 | * 32 | * @since 1.0.0 2017年3月8日 33 | * @author Way Lau 34 | */ 35 | @Controller 36 | @RequestMapping("/comments") 37 | public class CommentController { 38 | 39 | @Autowired 40 | private BlogService blogService; 41 | 42 | @Autowired 43 | private CommentService commentService; 44 | 45 | /** 46 | * 获取评论列表 47 | * 48 | * @param blogId 49 | * @param model 50 | * @return 51 | */ 52 | @GetMapping 53 | public String listComments(@RequestParam(value = "blogId", required = true) Long blogId, Model model) { 54 | Optional optionalblog = blogService.getBlogById(blogId); 55 | List comments = null; 56 | 57 | if (optionalblog.isPresent()) { 58 | comments = optionalblog.get().getComments(); 59 | } 60 | 61 | // 判断操作用户是否是评论的所有者 62 | String commentOwner = ""; 63 | if (SecurityContextHolder.getContext().getAuthentication() != null 64 | && SecurityContextHolder.getContext().getAuthentication().isAuthenticated() 65 | && !SecurityContextHolder 66 | .getContext().getAuthentication().getPrincipal().toString().equals("anonymousUser")) { 67 | User principal = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 68 | if (principal != null) { 69 | commentOwner = principal.getUsername(); 70 | } 71 | } 72 | 73 | model.addAttribute("commentOwner", commentOwner); 74 | model.addAttribute("comments", comments); 75 | return "/userspace/blog :: #mainContainerRepleace"; 76 | } 77 | 78 | /** 79 | * 发表评论 80 | * 81 | * @param blogId 82 | * @param commentContent 83 | * @return 84 | */ 85 | @PostMapping 86 | @PreAuthorize("hasAnyAuthority('ROLE_ADMIN','ROLE_USER')") // 指定角色权限才能操作方法 87 | public ResponseEntity createComment(Long blogId, String commentContent) { 88 | 89 | try { 90 | blogService.createComment(blogId, commentContent); 91 | } catch (ConstraintViolationException e) { 92 | return ResponseEntity.ok().body(new Response(false, ConstraintViolationExceptionHandler.getMessage(e))); 93 | } catch (Exception e) { 94 | return ResponseEntity.ok().body(new Response(false, e.getMessage())); 95 | } 96 | 97 | return ResponseEntity.ok().body(new Response(true, "处理成功", null)); 98 | } 99 | 100 | /** 101 | * 删除评论 102 | * 103 | * @return 104 | */ 105 | @DeleteMapping("/{id}") 106 | @PreAuthorize("hasAnyAuthority('ROLE_ADMIN','ROLE_USER')") // 指定角色权限才能操作方法 107 | public ResponseEntity delete(@PathVariable("id") Long id, Long blogId) { 108 | 109 | boolean isOwner = false; 110 | Optional optionalComment = commentService.getCommentById(id); 111 | User user = null; 112 | 113 | if (optionalComment.isPresent()) { 114 | user = optionalComment.get().getUser(); 115 | } else { 116 | return ResponseEntity.ok().body(new Response(false, "不存在该评论!")); 117 | } 118 | 119 | // 判断操作用户是否是评论的所有者 120 | if (SecurityContextHolder.getContext().getAuthentication() != null 121 | && SecurityContextHolder.getContext().getAuthentication().isAuthenticated() 122 | && !SecurityContextHolder 123 | .getContext().getAuthentication().getPrincipal().toString().equals("anonymousUser")) { 124 | User principal = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 125 | if (principal != null && user.getUsername().equals(principal.getUsername())) { 126 | isOwner = true; 127 | } 128 | } 129 | 130 | if (!isOwner) { 131 | return ResponseEntity.ok().body(new Response(false, "没有操作权限")); 132 | } 133 | 134 | try { 135 | blogService.removeComment(blogId, id); 136 | commentService.removeComment(id); 137 | } catch (ConstraintViolationException e) { 138 | return ResponseEntity.ok().body(new Response(false, ConstraintViolationExceptionHandler.getMessage(e))); 139 | } catch (Exception e) { 140 | return ResponseEntity.ok().body(new Response(false, e.getMessage())); 141 | } 142 | 143 | return ResponseEntity.ok().body(new Response(true, "处理成功", null)); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/controller/HelloController.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.controller; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | /** 7 | * Hello 控制器. 8 | * 9 | * @since 1.0.0 2017年7月1日 10 | * @author Way Lau 11 | */ 12 | @RestController 13 | public class HelloController { 14 | 15 | @RequestMapping("/hello") 16 | public String hello() { 17 | return "Hello World! Welcome to visit waylau.com!"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/controller/MainController.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.Model; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | 12 | import com.waylau.spring.boot.blog.domain.Authority; 13 | import com.waylau.spring.boot.blog.domain.User; 14 | import com.waylau.spring.boot.blog.service.AuthorityService; 15 | import com.waylau.spring.boot.blog.service.UserService; 16 | 17 | /** 18 | * 主页控制器. 19 | * 20 | * @since 1.0.0 2017年7月27日 21 | * @author Way Lau 22 | */ 23 | @Controller 24 | public class MainController { 25 | 26 | private static final Long ROLE_USER_AUTHORITY_ID = 2L; // 用户权限(博主) 27 | 28 | @Autowired 29 | private UserService userService; 30 | 31 | @Autowired 32 | private AuthorityService authorityService; 33 | 34 | @GetMapping("/") 35 | public String root() { 36 | return "redirect:/index"; 37 | } 38 | 39 | @GetMapping("/index") 40 | public String index() { 41 | return "redirect:/blogs"; 42 | } 43 | 44 | @GetMapping("/login") 45 | public String login() { 46 | return "login"; 47 | } 48 | 49 | @GetMapping("/login-error") 50 | public String loginError(Model model) { 51 | model.addAttribute("loginError", true); 52 | model.addAttribute("errorMsg", "登录失败,用户名或者密码错误!"); 53 | return "login"; 54 | } 55 | 56 | @GetMapping("/register") 57 | public String register() { 58 | return "register"; 59 | } 60 | 61 | /** 62 | * 注册用户 63 | * @param user 64 | * @return 65 | */ 66 | @PostMapping("/register") 67 | public String registerUser(User user) { 68 | List authorities = new ArrayList<>(); 69 | authorities.add(authorityService.getAuthorityById(ROLE_USER_AUTHORITY_ID).get()); 70 | user.setAuthorities(authorities); 71 | userService.registerUser(user); 72 | return "redirect:/login"; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | import javax.validation.ConstraintViolationException; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.data.domain.Page; 11 | import org.springframework.data.domain.PageRequest; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.ui.Model; 15 | import org.springframework.web.bind.annotation.DeleteMapping; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RequestParam; 21 | import org.springframework.web.bind.annotation.RestController; 22 | import org.springframework.web.servlet.ModelAndView; 23 | 24 | import com.waylau.spring.boot.blog.domain.Authority; 25 | import com.waylau.spring.boot.blog.domain.User; 26 | import com.waylau.spring.boot.blog.service.AuthorityService; 27 | import com.waylau.spring.boot.blog.service.UserService; 28 | import com.waylau.spring.boot.blog.util.ConstraintViolationExceptionHandler; 29 | import com.waylau.spring.boot.blog.vo.Response; 30 | 31 | /** 32 | * User 控制器. 33 | * 34 | * @since 1.0.0 2017年7月9日 35 | * @author Way Lau 36 | */ 37 | @RestController 38 | @RequestMapping("/users") 39 | public class UserController { 40 | 41 | @Autowired 42 | private UserService userService; 43 | 44 | @Autowired 45 | private AuthorityService authorityService; 46 | 47 | /** 48 | * 查询所有用户 49 | * 50 | * @param async 51 | * @param pageIndex 52 | * @param pageSize 53 | * @param name 54 | * @param model 55 | * @return 56 | */ 57 | @GetMapping 58 | public ModelAndView list(@RequestParam(value = "async", required = false) boolean async, 59 | @RequestParam(value = "pageIndex", required = false, defaultValue = "0") int pageIndex, 60 | @RequestParam(value = "pageSize", required = false, defaultValue = "10") int pageSize, 61 | @RequestParam(value = "name", required = false, defaultValue = "") String name, Model model) { 62 | 63 | Pageable pageable = PageRequest.of(pageIndex, pageSize); 64 | Page page = userService.listUsersByNameLike(name, pageable); 65 | List list = page.getContent(); // 当前所在页面数据列表 66 | 67 | model.addAttribute("page", page); 68 | model.addAttribute("userList", list); 69 | return new ModelAndView(async == true ? "users/list :: #mainContainerRepleace" : "users/list", "userModel", 70 | model); 71 | } 72 | 73 | /** 74 | * 获取创建表单页面 75 | * 76 | * @param model 77 | * @return 78 | */ 79 | @GetMapping("/add") 80 | public ModelAndView createForm(Model model) { 81 | model.addAttribute("user", new User(null, null, null, null)); 82 | return new ModelAndView("users/add", "userModel", model); 83 | } 84 | 85 | /** 86 | * 保存或者修改用户 87 | * @param user 88 | * @param authorityId 89 | * @return 90 | */ 91 | @PostMapping 92 | public ResponseEntity saveOrUpateUser(User user, Long authorityId) { 93 | 94 | List authorities = new ArrayList<>(); 95 | authorities.add(authorityService.getAuthorityById(authorityId).get()); 96 | user.setAuthorities(authorities); 97 | 98 | try { 99 | userService.saveOrUpateUser(user); 100 | } catch (ConstraintViolationException e) { 101 | return ResponseEntity.ok().body(new Response(false, ConstraintViolationExceptionHandler.getMessage(e))); 102 | } 103 | 104 | return ResponseEntity.ok().body(new Response(true, "处理成功", user)); 105 | } 106 | 107 | /** 108 | * 删除用户 109 | * 110 | * @param id 111 | * @param model 112 | * @return 113 | */ 114 | @DeleteMapping(value = "/{id}") 115 | public ResponseEntity delete(@PathVariable("id") Long id, Model model) { 116 | try { 117 | userService.removeUser(id); 118 | } catch (Exception e) { 119 | return ResponseEntity.ok().body(new Response(false, e.getMessage())); 120 | } 121 | return ResponseEntity.ok().body(new Response(true, "处理成功")); 122 | } 123 | 124 | /** 125 | * 获取修改用户的界面 126 | * 127 | * @param id 128 | * @param model 129 | * @return 130 | */ 131 | @GetMapping(value = "edit/{id}") 132 | public ModelAndView modifyForm(@PathVariable("id") Long id, Model model) { 133 | Optional user = userService.getUserById(id); 134 | model.addAttribute("user", user.get()); 135 | return new ModelAndView("users/edit", "userModel", model); 136 | } 137 | 138 | } -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/controller/VoteController.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.controller; 2 | 3 | import java.util.Optional; 4 | 5 | import javax.validation.ConstraintViolationException; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.security.access.prepost.PreAuthorize; 10 | import org.springframework.security.core.context.SecurityContextHolder; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | 17 | import com.waylau.spring.boot.blog.domain.User; 18 | import com.waylau.spring.boot.blog.domain.Vote; 19 | import com.waylau.spring.boot.blog.service.BlogService; 20 | import com.waylau.spring.boot.blog.service.VoteService; 21 | import com.waylau.spring.boot.blog.util.ConstraintViolationExceptionHandler; 22 | import com.waylau.spring.boot.blog.vo.Response; 23 | 24 | 25 | /** 26 | * 点赞控制器. 27 | * 28 | * @since 1.0.0 2017年3月8日 29 | * @author Way Lau 30 | */ 31 | @Controller 32 | @RequestMapping("/votes") 33 | public class VoteController { 34 | 35 | @Autowired 36 | private BlogService blogService; 37 | 38 | @Autowired 39 | private VoteService voteService; 40 | 41 | /** 42 | * 发表点赞 43 | * @param blogId 44 | * @param VoteContent 45 | * @return 46 | */ 47 | @PostMapping 48 | @PreAuthorize("hasAnyAuthority('ROLE_ADMIN','ROLE_USER')") // 指定角色权限才能操作方法 49 | public ResponseEntity createVote(Long blogId) { 50 | 51 | try { 52 | blogService.createVote(blogId); 53 | } catch (ConstraintViolationException e) { 54 | return ResponseEntity.ok().body(new Response(false, 55 | ConstraintViolationExceptionHandler.getMessage(e))); 56 | } catch (Exception e) { 57 | return ResponseEntity.ok().body(new Response(false, e.getMessage())); 58 | } 59 | 60 | return ResponseEntity.ok().body(new Response(true, "点赞成功", null)); 61 | } 62 | 63 | /** 64 | * 删除点赞 65 | * @return 66 | */ 67 | @DeleteMapping("/{id}") 68 | @PreAuthorize("hasAnyAuthority('ROLE_ADMIN','ROLE_USER')") // 指定角色权限才能操作方法 69 | public ResponseEntity delete(@PathVariable("id") Long id, Long blogId) { 70 | 71 | boolean isOwner = false; 72 | Optional optionalVote = voteService.getVoteById(id); 73 | User user = null; 74 | if (optionalVote.isPresent()) { 75 | user = optionalVote.get().getUser(); 76 | } else { 77 | return ResponseEntity.ok().body(new Response(false, "不存在该点赞!")); 78 | } 79 | 80 | // 判断操作用户是否是点赞的所有者 81 | if (SecurityContextHolder.getContext().getAuthentication() !=null 82 | && SecurityContextHolder.getContext().getAuthentication().isAuthenticated() 83 | && !SecurityContextHolder.getContext().getAuthentication() 84 | .getPrincipal().toString().equals("anonymousUser")) { 85 | User principal = (User)SecurityContextHolder.getContext() 86 | .getAuthentication().getPrincipal(); 87 | if (principal !=null && user.getUsername().equals(principal.getUsername())) { 88 | isOwner = true; 89 | } 90 | } 91 | 92 | if (!isOwner) { 93 | return ResponseEntity.ok().body(new Response(false, "没有操作权限")); 94 | } 95 | 96 | try { 97 | blogService.removeVote(blogId, id); 98 | voteService.removeVote(id); 99 | } catch (ConstraintViolationException e) { 100 | return ResponseEntity.ok().body(new Response(false, 101 | ConstraintViolationExceptionHandler.getMessage(e))); 102 | } catch (Exception e) { 103 | return ResponseEntity.ok().body(new Response(false, e.getMessage())); 104 | } 105 | 106 | return ResponseEntity.ok().body(new Response(true, "取消点赞成功", null)); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/domain/Authority.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.domain; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | 9 | import org.springframework.security.core.GrantedAuthority; 10 | 11 | /** 12 | * 权限. 13 | * 14 | * @since 1.0.0 2017年5月30日 15 | * @author Way Lau 16 | */ 17 | @Entity 18 | public class Authority implements GrantedAuthority { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | @Id // 主键 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略 24 | private Long id; // 用户的唯一标识 25 | 26 | @Column(nullable = false) // 映射为字段,值不能为空 27 | private String name; 28 | 29 | public Long getId() { 30 | return id; 31 | } 32 | 33 | public void setId(Long id) { 34 | this.id = id; 35 | } 36 | 37 | @Override 38 | public String getAuthority() { 39 | return name; 40 | } 41 | 42 | public void setName(String name) { 43 | this.name = name; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/domain/Catalog.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.domain; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.persistence.CascadeType; 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.FetchType; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.GenerationType; 11 | import javax.persistence.Id; 12 | import javax.persistence.JoinColumn; 13 | import javax.persistence.OneToOne; 14 | import javax.validation.constraints.Size; 15 | 16 | import org.hibernate.validator.constraints.NotEmpty; 17 | 18 | /** 19 | * Catalog 实体 20 | * 21 | * @since 1.0.0 2017年4月10日 22 | * @author Way Lau 23 | */ 24 | @Entity // 实体 25 | public class Catalog implements Serializable { 26 | private static final long serialVersionUID = 1L; 27 | 28 | @Id // 主键 29 | @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略 30 | private Long id; // 用户的唯一标识 31 | 32 | @NotEmpty(message = "名称不能为空") 33 | @Size(min=2, max=30) 34 | @Column(nullable = false) // 映射为字段,值不能为空 35 | private String name; 36 | 37 | @OneToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY) 38 | @JoinColumn(name="user_id") 39 | private User user; 40 | 41 | protected Catalog() { 42 | } 43 | 44 | public Catalog(User user, String name) { 45 | this.name = name; 46 | this.user = user; 47 | } 48 | 49 | public Long getId() { 50 | return id; 51 | } 52 | 53 | public void setId(Long id) { 54 | this.id = id; 55 | } 56 | 57 | public User getUser() { 58 | return user; 59 | } 60 | public void setUser(User user) { 61 | this.user = user; 62 | } 63 | public String getName() { 64 | return name; 65 | } 66 | public void setName(String name) { 67 | this.name = name; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/domain/Comment.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.domain; 2 | 3 | import java.io.Serializable; 4 | import java.sql.Timestamp; 5 | 6 | import javax.persistence.CascadeType; 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.FetchType; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.GenerationType; 12 | import javax.persistence.Id; 13 | import javax.persistence.JoinColumn; 14 | import javax.persistence.OneToOne; 15 | import javax.validation.constraints.Size; 16 | 17 | import org.hibernate.validator.constraints.NotEmpty; 18 | 19 | /** 20 | * Comment 实体. 21 | * 22 | * @since 1.0.0 2017年4月9日 23 | * @author Way Lau 24 | */ 25 | @Entity // 实体 26 | public class Comment implements Serializable { 27 | private static final long serialVersionUID = 1L; 28 | 29 | @Id // 主键 30 | @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略 31 | private Long id; // 用户的唯一标识 32 | 33 | @NotEmpty(message = "评论内容不能为空") 34 | @Size(min=2, max=500) 35 | @Column(nullable = false) // 映射为字段,值不能为空 36 | private String content; 37 | 38 | @OneToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY) 39 | @JoinColumn(name="user_id") 40 | private User user; 41 | 42 | @Column(nullable = false) // 映射为字段,值不能为空 43 | @org.hibernate.annotations.CreationTimestamp // 由数据库自动创建时间 44 | private Timestamp createTime; 45 | 46 | protected Comment() { 47 | } 48 | 49 | public Comment(User user, String content) { 50 | this.content = content; 51 | this.user = user; 52 | } 53 | 54 | public Long getId() { 55 | return id; 56 | } 57 | 58 | public void setId(Long id) { 59 | this.id = id; 60 | } 61 | 62 | public String getContent() { 63 | return content; 64 | } 65 | 66 | public void setContent(String content) { 67 | this.content = content; 68 | } 69 | public User getUser() { 70 | return user; 71 | } 72 | public void setUser(User user) { 73 | this.user = user; 74 | } 75 | 76 | public Timestamp getCreateTime() { 77 | return createTime; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.domain; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | 7 | import javax.persistence.CascadeType; 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.FetchType; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.JoinColumn; 15 | import javax.persistence.JoinTable; 16 | import javax.persistence.ManyToMany; 17 | import javax.validation.constraints.Size; 18 | 19 | import org.hibernate.validator.constraints.Email; 20 | import org.hibernate.validator.constraints.NotEmpty; 21 | import org.springframework.security.core.GrantedAuthority; 22 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 23 | import org.springframework.security.core.userdetails.UserDetails; 24 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 25 | import org.springframework.security.crypto.password.PasswordEncoder; 26 | 27 | 28 | /** 29 | * User 实体. 30 | * 31 | * @since 1.0.0 2017年7月9日 32 | * @author Way Lau 33 | */ 34 | @Entity // 实体 35 | public class User implements UserDetails { 36 | 37 | private static final long serialVersionUID = 1L; 38 | 39 | @Id // 主键 40 | @GeneratedValue(strategy=GenerationType.IDENTITY) // 自增策略 41 | private Long id; // 实体一个唯一标识 42 | 43 | @NotEmpty(message = "姓名不能为空") 44 | @Size(min=2, max=20) 45 | @Column(nullable = false, length = 20) // 映射为字段,值不能为空 46 | private String name; 47 | 48 | @NotEmpty(message = "邮箱不能为空") 49 | @Size(max=50) 50 | @Email(message= "邮箱格式不对" ) 51 | @Column(nullable = false, length = 50, unique = true) 52 | private String email; 53 | 54 | @NotEmpty(message = "账号不能为空") 55 | @Size(min=3, max=20) 56 | @Column(nullable = false, length = 20, unique = true) 57 | private String username; // 用户账号,用户登录时的唯一标识 58 | 59 | @NotEmpty(message = "密码不能为空") 60 | @Size(max=100) 61 | @Column(length = 100) 62 | private String password; // 登录时密码 63 | 64 | @Column(length = 200) 65 | private String avatar; // 头像图片地址 66 | 67 | @ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.EAGER) 68 | @JoinTable(name = "user_authority", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), 69 | inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id")) 70 | private List authorities; 71 | 72 | protected User() { // 无参构造函数;设为 protected 防止直接使用 73 | } 74 | 75 | public User(String name, String email,String username,String password) { 76 | this.name = name; 77 | this.email = email; 78 | this.username = username; 79 | this.password = password; 80 | } 81 | 82 | public Long getId() { 83 | return id; 84 | } 85 | public void setId(Long id) { 86 | this.id = id; 87 | } 88 | public String getName() { 89 | return name; 90 | } 91 | public void setName(String name) { 92 | this.name = name; 93 | } 94 | public String getEmail() { 95 | return email; 96 | } 97 | public void setEmail(String email) { 98 | this.email = email; 99 | } 100 | 101 | public String getUsername() { 102 | return username; 103 | } 104 | 105 | public void setUsername(String username) { 106 | this.username = username; 107 | } 108 | 109 | public String getPassword() { 110 | return password; 111 | } 112 | 113 | public void setPassword(String password) { 114 | this.password = password; 115 | } 116 | 117 | public String getAvatar() { 118 | return avatar; 119 | } 120 | 121 | public void setAvatar(String avatar) { 122 | this.avatar = avatar; 123 | } 124 | 125 | @Override 126 | public Collection extends GrantedAuthority> getAuthorities() { 127 | // 需将 List 转成 List,否则前端拿不到角色列表名称 128 | List simpleAuthorities = new ArrayList<>(); 129 | for(GrantedAuthority authority : this.authorities){ 130 | simpleAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority())); 131 | } 132 | return simpleAuthorities; 133 | } 134 | 135 | public void setAuthorities(List authorities) { 136 | this.authorities = authorities; 137 | } 138 | 139 | @Override 140 | public boolean isAccountNonExpired() { 141 | return true; 142 | } 143 | 144 | @Override 145 | public boolean isAccountNonLocked() { 146 | return true; 147 | } 148 | 149 | @Override 150 | public boolean isCredentialsNonExpired() { 151 | return true; 152 | } 153 | 154 | @Override 155 | public boolean isEnabled() { 156 | return true; 157 | } 158 | 159 | @Override 160 | public String toString() { 161 | return String.format("User[id=%d,name='%s',username='%s',email='%s']", id, name, username, email); 162 | } 163 | 164 | /** 165 | * 加密密码 166 | * @param password 167 | */ 168 | public void setEncodePassword(String password) { 169 | PasswordEncoder encoder = new BCryptPasswordEncoder(); 170 | String encodePasswd = encoder.encode(password); 171 | this.password = encodePasswd; 172 | } 173 | } 174 | 175 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/domain/Vote.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.domain; 2 | 3 | import java.io.Serializable; 4 | import java.sql.Timestamp; 5 | 6 | import javax.persistence.CascadeType; 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.FetchType; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.GenerationType; 12 | import javax.persistence.Id; 13 | import javax.persistence.JoinColumn; 14 | import javax.persistence.OneToOne; 15 | 16 | /** 17 | * Vote 实体. 18 | * 19 | * @since 1.0.0 2017年4月9日 20 | * @author Way Lau 21 | */ 22 | @Entity // 实体 23 | public class Vote implements Serializable { 24 | private static final long serialVersionUID = 1L; 25 | 26 | @Id // 主键 27 | @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略 28 | private Long id; // 用户的唯一标识 29 | 30 | @OneToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY) 31 | @JoinColumn(name="user_id") 32 | private User user; 33 | 34 | @Column(nullable = false) // 映射为字段,值不能为空 35 | @org.hibernate.annotations.CreationTimestamp // 由数据库自动创建时间 36 | private Timestamp createTime; 37 | 38 | protected Vote() { 39 | } 40 | 41 | public Vote(User user) { 42 | this.user = user; 43 | } 44 | 45 | public Long getId() { 46 | return id; 47 | } 48 | 49 | public void setId(Long id) { 50 | this.id = id; 51 | } 52 | public User getUser() { 53 | return user; 54 | } 55 | public void setUser(User user) { 56 | this.user = user; 57 | } 58 | 59 | public Timestamp getCreateTime() { 60 | return createTime; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/domain/es/EsBlog.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.domain.es; 2 | 3 | import java.io.Serializable; 4 | import java.sql.Timestamp; 5 | 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.elasticsearch.annotations.Document; 8 | import org.springframework.data.elasticsearch.annotations.Field; 9 | import org.springframework.data.elasticsearch.annotations.FieldType; 10 | 11 | import com.waylau.spring.boot.blog.domain.Blog; 12 | 13 | /** 14 | * EsBlog 文档类. 15 | * 16 | * @since 1.0.0 2017年3月5日 17 | * @author Way Lau 18 | */ 19 | @Document(indexName = "blog", type = "blog") 20 | public class EsBlog implements Serializable { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | @Id // 主键 25 | private String id; 26 | @Field(type = FieldType.Long) 27 | private Long blogId; // Blog 实体的 id 28 | @Field(type = FieldType.text) 29 | private String title; 30 | @Field(type = FieldType.text) 31 | private String summary; 32 | @Field(type = FieldType.text) 33 | private String content; 34 | @Field(type = FieldType.keyword, index = false) // 不做全文检索字段 35 | private String username; 36 | @Field(type = FieldType.text,index = false) // 不做全文检索字段 37 | private String avatar; 38 | @Field(type = FieldType.Date,index = false) // 不做全文检索字段 39 | private Timestamp createTime; 40 | @Field(type = FieldType.Integer,index = false) // 不做全文检索字段 41 | private Integer readSize = 0; // 访问量、阅读量 42 | @Field(type = FieldType.Integer,index = false) // 不做全文检索字段 43 | private Integer commentSize = 0; // 评论量 44 | @Field(type = FieldType.Integer,index = false) // 不做全文检索字段 45 | private Integer voteSize = 0; // 点赞量 46 | @Field(type = FieldType.text,fielddata = true, searchAnalyzer = "ik_smart", analyzer = "ik_smart") 47 | private String tags; // 标签 48 | 49 | protected EsBlog() { // JPA 的规范要求无参构造函数;设为 protected 防止直接使用 50 | } 51 | 52 | public EsBlog(Long blogId, String title, String summary, String content, 53 | String username, String avatar,Timestamp createTime, 54 | Integer readSize,Integer commentSize, Integer voteSize , String tags) { 55 | this.blogId = blogId; 56 | this.title = title; 57 | this.summary = summary; 58 | this.content = content; 59 | this.username = username; 60 | this.avatar = avatar; 61 | this.createTime = createTime; 62 | this.readSize = readSize; 63 | this.commentSize = commentSize; 64 | this.voteSize = voteSize; 65 | this.tags = tags; 66 | } 67 | 68 | public EsBlog(Blog blog){ 69 | this.blogId = blog.getId(); 70 | this.title = blog.getTitle(); 71 | this.summary = blog.getSummary(); 72 | this.content = blog.getContent(); 73 | this.username = blog.getUser().getUsername(); 74 | this.avatar = blog.getUser().getAvatar(); 75 | this.createTime = blog.getCreateTime(); 76 | this.readSize = blog.getReadSize(); 77 | this.commentSize = blog.getCommentSize(); 78 | this.voteSize = blog.getVoteSize(); 79 | this.tags = blog.getTags(); 80 | } 81 | 82 | public void update(Blog blog){ 83 | this.blogId = blog.getId(); 84 | this.title = blog.getTitle(); 85 | this.summary = blog.getSummary(); 86 | this.content = blog.getContent(); 87 | this.username = blog.getUser().getUsername(); 88 | this.avatar = blog.getUser().getAvatar(); 89 | this.createTime = blog.getCreateTime(); 90 | this.readSize = blog.getReadSize(); 91 | this.commentSize = blog.getCommentSize(); 92 | this.voteSize = blog.getVoteSize(); 93 | this.tags = blog.getTags(); 94 | } 95 | 96 | 97 | public String getId() { 98 | return id; 99 | } 100 | 101 | public void setId(String id) { 102 | this.id = id; 103 | } 104 | public Long getBlogId() { 105 | return blogId; 106 | } 107 | 108 | public void setBlogId(Long blogId) { 109 | this.blogId = blogId; 110 | } 111 | 112 | public String getTitle() { 113 | return title; 114 | } 115 | 116 | public void setTitle(String title) { 117 | this.title = title; 118 | } 119 | 120 | public String getContent() { 121 | return content; 122 | } 123 | 124 | public void setContent(String content) { 125 | this.content = content; 126 | } 127 | 128 | public String getSummary() { 129 | return summary; 130 | } 131 | 132 | public void setSummary(String summary) { 133 | this.summary = summary; 134 | } 135 | 136 | public Timestamp getCreateTime() { 137 | return createTime; 138 | } 139 | 140 | public void setCreateTime(Timestamp createTime) { 141 | this.createTime = createTime; 142 | } 143 | 144 | public Integer getReadSize() { 145 | return readSize; 146 | } 147 | 148 | public void setReadSize(Integer readSize) { 149 | this.readSize = readSize; 150 | } 151 | 152 | public Integer getCommentSize() { 153 | return commentSize; 154 | } 155 | 156 | public void setCommentSize(Integer commentSize) { 157 | this.commentSize = commentSize; 158 | } 159 | 160 | public Integer getVoteSize() { 161 | return voteSize; 162 | } 163 | 164 | public void setVoteSize(Integer voteSize) { 165 | this.voteSize = voteSize; 166 | } 167 | 168 | public String getTags() { 169 | return tags; 170 | } 171 | 172 | public String getAvatar() { 173 | return avatar; 174 | } 175 | 176 | public void setAvatar(String avatar) { 177 | this.avatar = avatar; 178 | } 179 | 180 | public void setTags(String tags) { 181 | this.tags = tags; 182 | } 183 | 184 | public String getUsername() { 185 | return username; 186 | } 187 | 188 | public void setUsername(String username) { 189 | this.username = username; 190 | } 191 | 192 | @Override 193 | public String toString() { 194 | return String.format( 195 | "EsBlog[blogId=%d, title='%s', summary='%s']", 196 | blogId, title, summary); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/repository/AuthorityRepository.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.waylau.spring.boot.blog.domain.Authority; 6 | 7 | /** 8 | * Authority 仓库. 9 | * 10 | * @since 1.0.0 2017年5月30日 11 | * @author Way Lau 12 | */ 13 | public interface AuthorityRepository extends JpaRepository { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/repository/BlogRepository.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.repository; 2 | 3 | import org.springframework.data.domain.Page; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import com.waylau.spring.boot.blog.domain.Blog; 8 | import com.waylau.spring.boot.blog.domain.Catalog; 9 | import com.waylau.spring.boot.blog.domain.User; 10 | 11 | /** 12 | * Blog 仓库. 13 | * 14 | * @since 1.0.0 2017年6月4日 15 | * @author Way Lau 16 | */ 17 | public interface BlogRepository extends JpaRepository{ 18 | 19 | /** 20 | * 根据用户名、博客标题分页查询博客列表 21 | * @param user 22 | * @param title 23 | * @param pageable 24 | * @return 25 | */ 26 | Page findByUserAndTitleLike(User user, String title, Pageable pageable); 27 | 28 | /** 29 | * 根据用户名、博客查询博客列表(时间逆序) 30 | * @param title 31 | * @param user 32 | * @param tags 33 | * @param user2 34 | * @param pageable 35 | * @return 36 | */ 37 | Page findByTitleLikeAndUserOrTagsLikeAndUserOrderByCreateTimeDesc(String title, 38 | User user, String tags, User user2, Pageable pageable); 39 | 40 | /** 41 | * 根据分类查询博客列表 42 | * @param catalog 43 | * @param pageable 44 | * @return 45 | */ 46 | Page findByCatalog(Catalog catalog, Pageable pageable); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/repository/CatalogRepository.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import com.waylau.spring.boot.blog.domain.Catalog; 8 | import com.waylau.spring.boot.blog.domain.User; 9 | 10 | /** 11 | * Catalog Repository. 12 | * 13 | * @since 1.0.0 2017年6月7日 14 | * @author Way Lau 15 | */ 16 | public interface CatalogRepository extends JpaRepository { 17 | /** 18 | * 根据用户查询 19 | * @param user 20 | * @return 21 | */ 22 | List findByUser(User user); 23 | 24 | /** 25 | * 根据用户、分类名称查询 26 | * @param user 27 | * @param name 28 | * @return 29 | */ 30 | List findByUserAndName(User user,String name); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/repository/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.waylau.spring.boot.blog.domain.Comment; 6 | 7 | /** 8 | * Comment Repository 接口. 9 | * 10 | * @since 1.0.0 2017年6月6日 11 | * @author Way Lau 12 | */ 13 | public interface CommentRepository extends JpaRepository { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.repository; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | import org.springframework.data.jpa.repository.JpaRepository; 9 | 10 | import com.waylau.spring.boot.blog.domain.User; 11 | 12 | /** 13 | * User Repository 接口. 14 | * 15 | * @since 1.0.0 2017年7月16日 16 | * @author Way Lau 17 | */ 18 | public interface UserRepository extends JpaRepository{ 19 | 20 | /** 21 | * 根据用户姓名分页查询用户列表 22 | * @param name 23 | * @param pageable 24 | * @return 25 | */ 26 | Page findByNameLike(String name, Pageable pageable); 27 | 28 | /** 29 | * 根据用户账号查询用户 30 | * @param username 31 | * @return 32 | */ 33 | User findByUsername(String username); 34 | 35 | /** 36 | * 根据名称列表查询用户列表 37 | * @param usernames 38 | * @return 39 | */ 40 | List findByUsernameIn(Collection usernames); 41 | } -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/repository/VoteRepository.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.waylau.spring.boot.blog.domain.Vote; 6 | 7 | /** 8 | * Vote Repository接口. 9 | * @since 1.0.0 2017年6月6日 10 | * @author Way Lau 11 | */ 12 | public interface VoteRepository extends JpaRepository { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/repository/es/EsBlogRepository.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.repository.es; 2 | 3 | import org.springframework.data.domain.Page; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; 6 | 7 | import com.waylau.spring.boot.blog.domain.es.EsBlog; 8 | 9 | /** 10 | * EsBlog Repository接口. 11 | * 12 | * @since 1.0.0 2017年6月8日 13 | * @author Way Lau 14 | */ 15 | public interface EsBlogRepository extends ElasticsearchRepository { 16 | 17 | /** 18 | * 模糊查询(去重) 19 | * @param title 20 | * @param Summary 21 | * @param content 22 | * @param tags 23 | * @param pageable 24 | * @return 25 | */ 26 | Page findByTitleContainingOrSummaryContainingOrContentContainingOrTagsContaining( 27 | String title,String summary,String content,String tags,Pageable pageable); 28 | 29 | /** 30 | * 根据 Blog 的id 查询 EsBlog 31 | * @param blogId 32 | * @return 33 | */ 34 | EsBlog findByBlogId(Long blogId); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/AuthorityService.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.Optional; 4 | 5 | import com.waylau.spring.boot.blog.domain.Authority; 6 | 7 | /** 8 | * Authority 服务接口. 9 | * 10 | * @since 1.0.0 2017年5月30日 11 | * @author Way Lau 12 | */ 13 | public interface AuthorityService { 14 | /** 15 | * 根据ID查询 Authority 16 | * @param id 17 | * @return 18 | */ 19 | Optional getAuthorityById(Long id); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/AuthorityServiceImpl.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.waylau.spring.boot.blog.service; 5 | 6 | import java.util.Optional; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | import com.waylau.spring.boot.blog.domain.Authority; 12 | import com.waylau.spring.boot.blog.repository.AuthorityRepository; 13 | 14 | 15 | /** 16 | * Authority 服务接口的实现. 17 | * 18 | * @since 1.0.0 2017年5月30日 19 | * @author Way Lau 20 | */ 21 | @Service 22 | public class AuthorityServiceImpl implements AuthorityService { 23 | 24 | @Autowired 25 | private AuthorityRepository authorityRepository; 26 | 27 | @Override 28 | public Optional getAuthorityById(Long id) { 29 | return authorityRepository.findById(id); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/BlogService.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | 8 | import com.waylau.spring.boot.blog.domain.Blog; 9 | import com.waylau.spring.boot.blog.domain.Catalog; 10 | import com.waylau.spring.boot.blog.domain.User; 11 | 12 | /** 13 | * Blog 服务接口. 14 | * 15 | * @since 1.0.0 2017年4月7日 16 | * @author Way Lau 17 | */ 18 | public interface BlogService { 19 | /** 20 | * 保存Blog 21 | * @param EsBlog 22 | * @return 23 | */ 24 | Blog saveBlog(Blog blog); 25 | 26 | /** 27 | * 删除Blog 28 | * @param id 29 | * @return 30 | */ 31 | void removeBlog(Long id); 32 | 33 | /** 34 | * 根据id获取Blog 35 | * @param id 36 | * @return 37 | */ 38 | Optional getBlogById(Long id); 39 | 40 | /** 41 | * 根据用户进行博客名称分页模糊查询(最新) 42 | * @param user 43 | * @return 44 | */ 45 | Page listBlogsByTitleVote(User user, String title, Pageable pageable); 46 | 47 | /** 48 | * 根据用户进行博客名称分页模糊查询(最热) 49 | * @param user 50 | * @return 51 | */ 52 | Page listBlogsByTitleVoteAndSort(User user, String title, Pageable pageable); 53 | 54 | /** 55 | * 阅读量递增 56 | * @param id 57 | */ 58 | void readingIncrease(Long id); 59 | 60 | /** 61 | * 发表评论 62 | * @param blogId 63 | * @param commentContent 64 | * @return 65 | */ 66 | Blog createComment(Long blogId, String commentContent); 67 | 68 | /** 69 | * 删除评论 70 | * @param blogId 71 | * @param commentId 72 | * @return 73 | */ 74 | void removeComment(Long blogId, Long commentId); 75 | 76 | /** 77 | * 点赞 78 | * @param blogId 79 | * @return 80 | */ 81 | Blog createVote(Long blogId); 82 | 83 | /** 84 | * 取消点赞 85 | * @param blogId 86 | * @param voteId 87 | * @return 88 | */ 89 | void removeVote(Long blogId, Long voteId); 90 | 91 | /** 92 | * 根据分类进行查询 93 | * @param catalog 94 | * @param pageable 95 | * @return 96 | */ 97 | Page listBlogsByCatalog(Catalog catalog, Pageable pageable); 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/BlogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.Optional; 4 | 5 | import javax.transaction.Transactional; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.security.core.context.SecurityContextHolder; 11 | import org.springframework.stereotype.Service; 12 | 13 | import com.waylau.spring.boot.blog.domain.Blog; 14 | import com.waylau.spring.boot.blog.domain.Catalog; 15 | import com.waylau.spring.boot.blog.domain.Comment; 16 | import com.waylau.spring.boot.blog.domain.User; 17 | import com.waylau.spring.boot.blog.domain.Vote; 18 | import com.waylau.spring.boot.blog.domain.es.EsBlog; 19 | import com.waylau.spring.boot.blog.repository.BlogRepository; 20 | 21 | /** 22 | * Blog 服务. 23 | * 24 | * @since 1.0.0 2017年4月7日 25 | * @author Way Lau 26 | */ 27 | @Service 28 | public class BlogServiceImpl implements BlogService { 29 | 30 | @Autowired 31 | private BlogRepository blogRepository; 32 | 33 | @Autowired 34 | private EsBlogService esBlogService; 35 | 36 | @Transactional 37 | @Override 38 | public Blog saveBlog(Blog blog) { 39 | boolean isNew = (blog.getId() == null); 40 | EsBlog esBlog = null; 41 | 42 | Blog returnBlog = blogRepository.save(blog); 43 | 44 | if (isNew) { 45 | esBlog = new EsBlog(returnBlog); 46 | } else { 47 | esBlog = esBlogService.getEsBlogByBlogId(blog.getId()); 48 | esBlog.update(returnBlog); 49 | } 50 | 51 | esBlogService.updateEsBlog(esBlog); 52 | return returnBlog; 53 | } 54 | 55 | @Transactional 56 | @Override 57 | public void removeBlog(Long id) { 58 | blogRepository.deleteById(id); 59 | EsBlog esblog = esBlogService.getEsBlogByBlogId(id); 60 | esBlogService.removeEsBlog(esblog.getId()); 61 | } 62 | 63 | @Override 64 | public Optional getBlogById(Long id) { 65 | return blogRepository.findById(id); 66 | } 67 | 68 | @Override 69 | public Page listBlogsByTitleVote(User user, String title, Pageable pageable) { 70 | // 模糊查询 71 | title = "%" + title + "%"; 72 | String tags = title; 73 | Page blogs = blogRepository.findByTitleLikeAndUserOrTagsLikeAndUserOrderByCreateTimeDesc(title, 74 | user, tags, user, pageable); 75 | return blogs; 76 | } 77 | 78 | @Override 79 | public Page listBlogsByTitleVoteAndSort(User user, String title, Pageable pageable) { 80 | // 模糊查询 81 | title = "%" + title + "%"; 82 | Page blogs = blogRepository.findByUserAndTitleLike(user, title, pageable); 83 | return blogs; 84 | } 85 | 86 | @Override 87 | public void readingIncrease(Long id) { 88 | Optional blog = blogRepository.findById(id); 89 | Blog blogNew = null; 90 | 91 | if (blog.isPresent()) { 92 | blogNew = blog.get(); 93 | blogNew.setReadSize(blogNew.getReadSize() + 1); // 在原有的阅读量基础上递增1 94 | this.saveBlog(blogNew); 95 | } 96 | } 97 | 98 | @Override 99 | public Blog createComment(Long blogId, String commentContent) { 100 | Optional optionalBlog = blogRepository.findById(blogId); 101 | Blog originalBlog = null; 102 | if(optionalBlog.isPresent()) { 103 | originalBlog = optionalBlog.get(); 104 | User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 105 | Comment comment = new Comment(user, commentContent); 106 | originalBlog.addComment(comment); 107 | } 108 | 109 | return this.saveBlog(originalBlog); 110 | } 111 | 112 | @Override 113 | public void removeComment(Long blogId, Long commentId) { 114 | Optional optionalBlog = blogRepository.findById(blogId); 115 | if(optionalBlog.isPresent()) { 116 | Blog originalBlog = optionalBlog.get(); 117 | originalBlog.removeComment(commentId); 118 | this.saveBlog(originalBlog); 119 | } 120 | } 121 | 122 | @Override 123 | public Blog createVote(Long blogId) { 124 | Optional optionalBlog = blogRepository.findById(blogId); 125 | Blog originalBlog = null; 126 | 127 | if (optionalBlog.isPresent()) { 128 | originalBlog = optionalBlog.get(); 129 | 130 | User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 131 | Vote vote = new Vote(user); 132 | boolean isExist = originalBlog.addVote(vote); 133 | if (isExist) { 134 | throw new IllegalArgumentException("该用户已经点过赞了"); 135 | } 136 | } 137 | 138 | return this.saveBlog(originalBlog); 139 | } 140 | 141 | @Override 142 | public void removeVote(Long blogId, Long voteId) { 143 | Optional optionalBlog = blogRepository.findById(blogId); 144 | Blog originalBlog = null; 145 | 146 | if (optionalBlog.isPresent()) { 147 | originalBlog = optionalBlog.get(); 148 | originalBlog.removeVote(voteId); 149 | this.saveBlog(originalBlog); 150 | } 151 | } 152 | 153 | @Override 154 | public Page listBlogsByCatalog(Catalog catalog, Pageable pageable) { 155 | Page blogs = blogRepository.findByCatalog(catalog, pageable); 156 | return blogs; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/CatalogService.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import com.waylau.spring.boot.blog.domain.Catalog; 7 | import com.waylau.spring.boot.blog.domain.User; 8 | 9 | /** 10 | * Catalog 服务接口. 11 | * 12 | * @since 1.0.0 2017年4月10日 13 | * @author Way Lau 14 | */ 15 | public interface CatalogService { 16 | /** 17 | * 保存Catalog 18 | * @param catalog 19 | * @return 20 | */ 21 | Catalog saveCatalog(Catalog catalog); 22 | 23 | /** 24 | * 删除Catalog 25 | * @param id 26 | * @return 27 | */ 28 | void removeCatalog(Long id); 29 | 30 | /** 31 | * 根据id获取Catalog 32 | * @param id 33 | * @return 34 | */ 35 | Optional getCatalogById(Long id); 36 | 37 | /** 38 | * 获取Catalog列表 39 | * @return 40 | */ 41 | List listCatalogs(User user); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/CatalogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import com.waylau.spring.boot.blog.domain.Catalog; 10 | import com.waylau.spring.boot.blog.domain.User; 11 | import com.waylau.spring.boot.blog.repository.CatalogRepository; 12 | 13 | /** 14 | * Catalog 服务接口实现. 15 | * 16 | * @since 1.0.0 2017年6月7日 17 | * @author Way Lau 18 | */ 19 | @Service 20 | public class CatalogServiceImpl implements CatalogService { 21 | @Autowired 22 | private CatalogRepository catalogRepository; 23 | 24 | @Override 25 | public Catalog saveCatalog(Catalog catalog) { 26 | // 判断重复 27 | List list = catalogRepository.findByUserAndName(catalog.getUser(), 28 | catalog.getName()); 29 | if(list !=null && list.size() > 0) { 30 | throw new IllegalArgumentException("该分类已经存在了"); 31 | } 32 | return catalogRepository.save(catalog); 33 | } 34 | 35 | @Override 36 | public void removeCatalog(Long id) { 37 | catalogRepository.deleteById(id); 38 | } 39 | 40 | @Override 41 | public Optional getCatalogById(Long id) { 42 | return catalogRepository.findById(id); 43 | } 44 | 45 | @Override 46 | public List listCatalogs(User user) { 47 | return catalogRepository.findByUser(user); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/CommentService.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.Optional; 4 | 5 | import com.waylau.spring.boot.blog.domain.Comment; 6 | 7 | /** 8 | * Comment Service接口. 9 | * 10 | * @since 1.0.0 2017年6月6日 11 | * @author Way Lau 12 | */ 13 | public interface CommentService { 14 | 15 | /** 16 | * 根据id获取 Comment 17 | * @param id 18 | * @return 19 | */ 20 | Optional getCommentById(Long id); 21 | /** 22 | * 删除评论 23 | * @param id 24 | * @return 25 | */ 26 | void removeComment(Long id); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/CommentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import com.waylau.spring.boot.blog.domain.Comment; 9 | import com.waylau.spring.boot.blog.repository.CommentRepository; 10 | 11 | /** 12 | * Comment Service接口实现. 13 | * 14 | * @since 1.0.0 2017年6月6日 15 | * @author Way Lau 16 | */ 17 | @Service 18 | public class CommentServiceImpl implements CommentService { 19 | 20 | @Autowired 21 | private CommentRepository commentRepository; 22 | 23 | @Override 24 | public Optional getCommentById(Long id) { 25 | return commentRepository.findById(id); 26 | } 27 | 28 | @Override 29 | public void removeComment(Long id) { 30 | commentRepository.deleteById(id); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/EsBlogService.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | 4 | import java.util.List; 5 | 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | 9 | import com.waylau.spring.boot.blog.domain.User; 10 | import com.waylau.spring.boot.blog.domain.es.EsBlog; 11 | import com.waylau.spring.boot.blog.vo.TagVO; 12 | 13 | /** 14 | * EsBlog 服务接口. 15 | * 16 | * @since 1.0.0 2017年6月8日 17 | * @author Way Lau 18 | */ 19 | public interface EsBlogService { 20 | 21 | /** 22 | * 删除EsBlog 23 | * @param id 24 | * @return 25 | */ 26 | void removeEsBlog(String id); 27 | 28 | /** 29 | * 更新 EsBlog 30 | * @param EsBlog 31 | * @return 32 | */ 33 | EsBlog updateEsBlog(EsBlog esBlog); 34 | 35 | /** 36 | * 根据Blog的Id获取EsBlog 37 | * @param id 38 | * @return 39 | */ 40 | EsBlog getEsBlogByBlogId(Long blogId); 41 | 42 | /** 43 | * 最新博客列表,分页 44 | * @param keyword 45 | * @param pageable 46 | * @return 47 | */ 48 | Page listNewestEsBlogs(String keyword, Pageable pageable); 49 | 50 | /** 51 | * 最热博客列表,分页 52 | * @param keyword 53 | * @param pageable 54 | * @return 55 | */ 56 | Page listHotestEsBlogs(String keyword, Pageable pageable); 57 | 58 | /** 59 | * 博客列表,分页 60 | * @param pageable 61 | * @return 62 | */ 63 | Page listEsBlogs(Pageable pageable); 64 | /** 65 | * 最新前5 66 | * @param keyword 67 | * @return 68 | */ 69 | List listTop5NewestEsBlogs(); 70 | 71 | /** 72 | * 最热前5 73 | * @param keyword 74 | * @return 75 | */ 76 | List listTop5HotestEsBlogs(); 77 | 78 | /** 79 | * 最热前 30 标签 80 | * @return 81 | */ 82 | List listTop30Tags(); 83 | 84 | /** 85 | * 最热前12用户 86 | * @return 87 | */ 88 | List listTop12Users(); 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.data.domain.Pageable; 9 | 10 | import com.waylau.spring.boot.blog.domain.User; 11 | 12 | /** 13 | * 用户服务接口. 14 | * 15 | * @since 1.0.0 2017年7月27日 16 | * @author Way Lau 17 | */ 18 | public interface UserService { 19 | /** 20 | * 新增、编辑、保存用户 21 | * @param user 22 | * @return 23 | */ 24 | User saveOrUpateUser(User user); 25 | 26 | /** 27 | * 注册用户 28 | * @param user 29 | * @return 30 | */ 31 | User registerUser(User user); 32 | 33 | /** 34 | * 删除用户 35 | * @param id 36 | */ 37 | void removeUser(Long id); 38 | 39 | /** 40 | * 根据id获取用户 41 | * @param id 42 | * @return 43 | */ 44 | Optional getUserById(Long id); 45 | 46 | /** 47 | * 根据用户名进行分页模糊查询 48 | * @param name 49 | * @param pageable 50 | * @return 51 | */ 52 | Page listUsersByNameLike(String name, Pageable pageable); 53 | 54 | /** 55 | * 根据用户名集合,查询用户详细信息列表 56 | * @param usernames 57 | * @return 58 | */ 59 | List listUsersByUsernames(Collection usernames); 60 | } -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | import javax.transaction.Transactional; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.data.domain.Page; 11 | import org.springframework.data.domain.Pageable; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 15 | import org.springframework.stereotype.Service; 16 | 17 | import com.waylau.spring.boot.blog.domain.User; 18 | import com.waylau.spring.boot.blog.repository.UserRepository; 19 | 20 | /** 21 | * 用户服务接口实现. 22 | * 23 | * @since 1.0.0 2017年7月27日 24 | * @author Way Lau 25 | */ 26 | @Service 27 | public class UserServiceImpl implements UserService, UserDetailsService { 28 | 29 | @Autowired 30 | private UserRepository userRepository; 31 | 32 | @Transactional 33 | @Override 34 | public User saveOrUpateUser(User user) { 35 | return userRepository.save(user); 36 | } 37 | 38 | @Transactional 39 | @Override 40 | public User registerUser(User user) { 41 | // 加密密码 42 | user.setEncodePassword(user.getPassword()); 43 | return userRepository.save(user); 44 | } 45 | 46 | @Transactional 47 | @Override 48 | public void removeUser(Long id) { 49 | userRepository.deleteById(id); 50 | } 51 | 52 | @Override 53 | public Optional getUserById(Long id) { 54 | return userRepository.findById(id); 55 | } 56 | 57 | @Override 58 | public Page listUsersByNameLike(String name, Pageable pageable) { 59 | 60 | // 模糊查询 61 | name = "%" + name + "%"; 62 | Page users = userRepository.findByNameLike(name, pageable); 63 | return users; 64 | } 65 | 66 | @Override 67 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 68 | return userRepository.findByUsername(username); 69 | } 70 | 71 | @Override 72 | public List listUsersByUsernames(Collection usernames) { 73 | return userRepository.findByUsernameIn(usernames); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/VoteService.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.Optional; 4 | 5 | import com.waylau.spring.boot.blog.domain.Vote; 6 | 7 | /** 8 | * Vote 服务接口. 9 | * 10 | * @since 1.0.0 2017年4月9日 11 | * @author Way Lau 12 | */ 13 | public interface VoteService { 14 | /** 15 | * 根据id获取 Vote 16 | * @param id 17 | * @return 18 | */ 19 | Optional getVoteById(Long id); 20 | /** 21 | * 删除Vote 22 | * @param id 23 | * @return 24 | */ 25 | void removeVote(Long id); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/service/VoteServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.service; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import com.waylau.spring.boot.blog.domain.Vote; 9 | import com.waylau.spring.boot.blog.repository.VoteRepository; 10 | 11 | /** 12 | * Vote 服务实现. 13 | * @since 1.0.0 2017年6月6日 14 | * @author Way Lau 15 | */ 16 | @Service 17 | public class VoteServiceImpl implements VoteService { 18 | 19 | @Autowired 20 | private VoteRepository voteRepository; 21 | 22 | @Override 23 | public Optional getVoteById(Long id) { 24 | return voteRepository.findById(id); 25 | } 26 | 27 | @Override 28 | public void removeVote(Long id) { 29 | voteRepository.deleteById(id); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/util/ConstraintViolationExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import javax.validation.ConstraintViolation; 7 | import javax.validation.ConstraintViolationException; 8 | 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | /** 12 | * ConstraintViolationException 处理器. 13 | * 14 | * @since 1.0.0 2017年5月29日 15 | * @author Way Lau 16 | */ 17 | public class ConstraintViolationExceptionHandler { 18 | 19 | /** 20 | * 获取批量异常信息 21 | * @param e 22 | * @return 23 | */ 24 | public static String getMessage(ConstraintViolationException e) { 25 | List msgList = new ArrayList<>(); 26 | for (ConstraintViolation> constraintViolation : e.getConstraintViolations()) { 27 | msgList.add(constraintViolation.getMessage()); 28 | } 29 | String messages = StringUtils.join(msgList.toArray(), ";"); 30 | return messages; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/vo/CatalogVO.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.vo; 2 | 3 | 4 | import com.waylau.spring.boot.blog.domain.Catalog; 5 | 6 | /** 7 | * Catalog VO. 8 | * 9 | * @since 1.0.0 2017年8月1日 10 | * @author Way Lau 11 | */ 12 | public class CatalogVO { 13 | 14 | private String username; 15 | private Catalog catalog; 16 | 17 | public CatalogVO() { 18 | } 19 | 20 | public String getUsername() { 21 | return username; 22 | } 23 | 24 | public void setUsername(String username) { 25 | this.username = username; 26 | } 27 | 28 | public Catalog getCatalog() { 29 | return catalog; 30 | } 31 | 32 | public void setCatalog(Catalog catalog) { 33 | this.catalog = catalog; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/vo/Menu.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.vo; 2 | 3 | /** 4 | * 后台管理的菜单. 5 | * 6 | * @since 1.0.0 2017年5月29日 7 | * @author Way Lau 8 | */ 9 | public class Menu { 10 | 11 | private String name; // 菜单名称 12 | private String url; // 菜单 URL 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | public void setName(String name) { 18 | this.name = name; 19 | } 20 | public String getUrl() { 21 | return url; 22 | } 23 | public void setUrl(String url) { 24 | this.url = url; 25 | } 26 | 27 | public Menu(String name, String url) { 28 | this.name = name; 29 | this.url = url; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/vo/Response.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.vo; 2 | 3 | /** 4 | * 返回对象. 5 | * 6 | * @since 1.0.0 2017年5月29日 7 | * @author Way Lau 8 | */ 9 | public class Response { 10 | 11 | private boolean success; // 处理是否成功 12 | private String message; // 处理后消息提示 13 | private Object body; // 返回数据 14 | 15 | public boolean isSuccess() { 16 | return success; 17 | } 18 | public void setSuccess(boolean success) { 19 | this.success = success; 20 | } 21 | public String getMessage() { 22 | return message; 23 | } 24 | public void setMessage(String message) { 25 | this.message = message; 26 | } 27 | public Object getBody() { 28 | return body; 29 | } 30 | public void setBody(Object body) { 31 | this.body = body; 32 | } 33 | 34 | public Response(boolean success, String message) { 35 | this.success = success; 36 | this.message = message; 37 | } 38 | 39 | public Response(boolean success, String message, Object body) { 40 | this.success = success; 41 | this.message = message; 42 | this.body = body; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/waylau/spring/boot/blog/vo/TagVO.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.blog.vo; 2 | 3 | /** 4 | * Tag 值对象. 5 | * 6 | * @since 1.0.0 2017年4月13日 7 | * @author Way Lau 8 | */ 9 | public class TagVO { 10 | 11 | private String name; 12 | private Long count; 13 | 14 | public TagVO(String name, Long count) { 15 | this.name = name; 16 | this.count = count; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | public void setName(String name) { 24 | this.name = name; 25 | } 26 | 27 | public Long getCount() { 28 | return count; 29 | } 30 | 31 | public void setCount(Long count) { 32 | this.count = count; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Thymeleaf 编码 2 | spring.thymeleaf.encoding=UTF-8 3 | # 热部署静态文件 4 | spring.thymeleaf.cache=false 5 | # 使用HTML5标准 6 | spring.thymeleaf.mode=HTML5 7 | 8 | # 使用 H2 控制台 9 | #spring.h2.console.enabled=true 10 | 11 | # DataSource 12 | spring.datasource.url=jdbc:mysql://localhost/blog?useSSL=false&serverTimezone=UTC&characterEncoding=utf-8 13 | spring.datasource.username=root 14 | spring.datasource.password=123456 15 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 16 | 17 | # JPA 18 | spring.jpa.show-sql = true 19 | #values are none, validate, update, create, create-drop 20 | spring.jpa.hibernate.ddl-auto= update 21 | 22 | # 文件服务器的接口的位置 23 | file.server.url=http://localhost:8081/upload 24 | 25 | # Elasticsearch 服务地址 26 | spring.data.elasticsearch.cluster-nodes=localhost:9300 27 | # 设置连接超时时间 28 | spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s -------------------------------------------------------------------------------- /src/main/resources/import.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO user (id, username, password, name, email) VALUES (1, 'admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', '老卫', 'i@waylau.com'); 2 | INSERT INTO user (id, username, password, name, email) VALUES (2, 'waylau', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'Way Lau', 'waylau@waylau.com'); 3 | 4 | INSERT INTO authority (id, name) VALUES (1, 'ROLE_ADMIN'); 5 | INSERT INTO authority (id, name) VALUES (2, 'ROLE_USER'); 6 | 7 | INSERT INTO user_authority (user_id, authority_id) VALUES (1, 1); 8 | INSERT INTO user_authority (user_id, authority_id) VALUES (2, 2); -------------------------------------------------------------------------------- /src/main/resources/static/css/blog.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Blog main style. 3 | * 4 | * @since: 1.0.0 5 | * @author Way Lau 6 | */ 7 | /* Footer */ 8 | .blog-footer { 9 | bottom: 0; 10 | width: 100%; 11 | height: 60px; 12 | line-height: 60px; /* Vertically center the text there */ 13 | } 14 | 15 | /* Index content*/ 16 | .blog-content-container { 17 | margin-top: 2.0em; 18 | background-color: #fff; 19 | } 20 | 21 | /* Index sidebar */ 22 | .blog-avatar-50 { 23 | border-radius: 50%; 24 | height: 50px; 25 | width: 50px; 26 | } 27 | 28 | /* User space */ 29 | .blog-avatar-100 { 30 | border-radius: 50%; 31 | height: 100px; 32 | width: 100px; 33 | } 34 | 35 | .blog-avatar-230 { 36 | border-radius: 10%; 37 | height: 230px; 38 | width: 230px; 39 | } 40 | 41 | /* blog commit */ 42 | .blog-textarea { 43 | border-radius: 0; 44 | margin-bottom: 10px; 45 | width: 100%; 46 | height: 80px; 47 | padding: 5px 10px; 48 | border: 1px solid #ddd; 49 | border-radius: 3px; 50 | resize: none; 51 | } 52 | 53 | .blog-label-success { 54 | color: #5cb85c; 55 | } 56 | 57 | .blog-label-error { 58 | color: #d9534f; 59 | } 60 | 61 | /* blog avatar */ 62 | .blog-avatar-img { 63 | max-width: 100%; 64 | } 65 | 66 | /* 元素靠右 */ 67 | .blog-right { 68 | float: right; 69 | margin-right:0px; 70 | } 71 | 72 | .blog-list-group-item { 73 | padding: .75rem 1.25rem; 74 | border: 1px solid rgba(0,0,0,.125); 75 | } 76 | 77 | 78 | /* 避免图片超出 */ 79 | article img { 80 | max-width: 100%; 81 | } 82 | 83 | 84 | /* go to top */ 85 | #goToTop{ 86 | display:block; 87 | width:40px; 88 | height:40px; 89 | position:fixed; 90 | bottom:60px; 91 | right:10px; 92 | text-decoration:none; 93 | } 94 | 95 | #goToTop span{ 96 | display:block; 97 | color:#dddddd; 98 | } 99 | 100 | #goToTop span:hover{ 101 | color:#cccccc; 102 | } 103 | 104 | #md { 105 | height:400px; 106 | } 107 | -------------------------------------------------------------------------------- /src/main/resources/static/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}@-ms-viewport{width:device-width}html{-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}body{font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#292b2c;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{cursor:help}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}a{color:#0275d8;text-decoration:none}a:focus,a:hover{color:#014c8c;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle}[role=button]{cursor:pointer}[role=button],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse;background-color:transparent}caption{padding-top:.75rem;padding-bottom:.75rem;color:#636c72;text-align:left;caption-side:bottom}th{text-align:left}label{display:inline-block;margin-bottom:.5rem}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,select,textarea{line-height:inherit}input[type=checkbox]:disabled,input[type=radio]:disabled{cursor:not-allowed}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit}input[type=search]{-webkit-appearance:none}output{display:inline-block}[hidden]{display:none!important}/*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /src/main/resources/static/css/bootstrap-reboot.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../scss/_normalize.scss","bootstrap-reboot.css","../../scss/_reboot.scss","../../scss/_variables.scss","../../scss/mixins/_hover.scss"],"names":[],"mappings":"4EAYA,KACE,YAAA,WACA,YAAA,KACA,qBAAA,KACA,yBAAA,KAUF,KACE,OAAA,EAOF,QAAA,MAAA,OAAA,OAAA,IAAA,QAME,QAAA,MAQF,GACE,UAAA,IACA,OAAA,MAAA,EAWF,WAAA,OAAA,KAGE,QAAA,MAOF,OACE,OAAA,IAAA,KAQF,GACE,mBAAA,YAAA,WAAA,YACA,OAAA,EACA,SAAA,QAQF,IACE,YAAA,UAAA,UACA,UAAA,IAWF,EACE,iBAAA,YACA,6BAAA,QAQF,SAAA,QAEE,cAAA,EAQF,YACE,cAAA,KACA,gBAAA,UACA,gBAAA,UAAA,OAOF,EAAA,OAEE,YAAA,QAOF,EAAA,OAEE,YAAA,OAQF,KAAA,IAAA,KAGE,YAAA,UAAA,UACA,UAAA,IAOF,IACE,WAAA,OAOF,KACE,iBAAA,KACA,MAAA,KAOF,MACE,UAAA,IAQF,IAAA,IAEE,UAAA,IACA,YAAA,EACA,SAAA,SACA,eAAA,SAGF,IACE,OAAA,OAGF,IACE,IAAA,MAUF,MAAA,MAEE,QAAA,aAOF,sBACE,QAAA,KACA,OAAA,EAOF,IACE,aAAA,KAOF,eACE,SAAA,OAWF,OAAA,MAAA,SAAA,OAAA,SAKE,YAAA,WACA,UAAA,KACA,YAAA,KACA,OAAA,EAQF,OAAA,MAEE,SAAA,QAQF,OAAA,OAEE,eAAA,KASF,aAAA,cAAA,OAAA,mBAIE,mBAAA,OAOF,gCAAA,+BAAA,gCAAA,yBAIE,aAAA,KACA,QAAA,EAOF,6BAAA,4BAAA,6BAAA,sBAIE,QAAA,IAAA,OAAA,WAOF,SACE,OAAA,IAAA,MAAA,OACA,OAAA,EAAA,IACA,QAAA,MAAA,OAAA,MAUF,OACE,mBAAA,WAAA,WAAA,WACA,MAAA,QACA,QAAA,MACA,UAAA,KACA,QAAA,EACA,YAAA,OAQF,SACE,QAAA,aACA,eAAA,SAOF,SACE,SAAA,KCrKF,gBAAA,aD+KE,mBAAA,WAAA,WAAA,WACA,QAAA,EC1KF,yCAAA,yCDmLE,OAAA,KC9KF,cDuLE,mBAAA,UACA,eAAA,KCnLF,4CAAA,yCD4LE,mBAAA,KAQF,6BACE,mBAAA,OACA,KAAA,QAWF,QAAA,KAEE,QAAA,MAOF,QACE,QAAA,UAUF,OACE,QAAA,aAOF,SACE,QAAA,KCnNF,SD8NE,QAAA,KEtbF,KACE,mBAAA,WAAA,WAAA,WAGF,EAAA,QAAA,SAGE,mBAAA,QAAA,WAAA,QAoBA,cAAgB,MAAA,aAQlB,KAYE,mBAAA,UAGA,4BAAA,YAGF,KACE,YAAA,cAAA,UAAA,mBAAA,WAAA,OC2K4H,iBD3K5H,MAAA,WACA,UAAA,KACA,YAAA,IACA,YAAA,IAEA,MAAA,QAEA,iBAAA,KD2LF,sBClLE,QAAA,YAYF,GAAI,GAAI,GAAI,GAAI,GAAI,GAClB,WAAA,EACA,cAAA,MAOF,EACE,WAAA,EACA,cAAA,KAIF,0BAAA,YAGE,OAAA,KAGF,QACE,cAAA,KACA,WAAA,OACA,YAAA,QAGF,GAAA,GAAA,GAGE,WAAA,EACA,cAAA,KAGF,MAAA,MAAA,MAAA,MAIE,cAAA,EAGF,GACE,YAAA,IAGF,GACE,cAAA,MACA,YAAA,EAGF,WACE,OAAA,EAAA,EAAA,KAQF,EACE,MAAA,QACA,gBAAA,KEhJE,QAAA,QFmJA,MAAA,QACA,gBAAA,UAUJ,8BACE,MAAA,QACA,gBAAA,KEhKE,oCAAA,oCFmKA,MAAA,QACA,gBAAA,KANJ,oCAUI,QAAA,EASJ,IAEE,WAAA,EAEA,cAAA,KAEA,SAAA,KAQF,OAGE,OAAA,EAAA,EAAA,KAQF,IAGE,eAAA,ODsIF,cCzHE,OAAA,QAcF,cAAA,EAAA,KAAA,OAAA,MAAA,MAAA,OAAA,QAAA,SASE,iBAAA,aAAA,aAAA,aAQF,MAEE,gBAAA,SAEA,iBAAA,YAGF,QACE,YAAA,OACA,eAAA,OACA,MAAA,QACA,WAAA,KACA,aAAA,OAGF,GAEE,WAAA,KAQF,MAEE,QAAA,aACA,cAAA,MAOF,aACE,QAAA,IAAA,OACA,QAAA,IAAA,KAAA,yBAGF,OAAA,MAAA,OAAA,SAME,YAAA,QAGF,8BAAA,2BAMI,OAAA,YAKJ,iBAAA,iBAAA,2BAAA,kBASE,mBAAA,QAGF,SAEE,OAAA,SAGF,SAME,UAAA,EAEA,QAAA,EACA,OAAA,EACA,OAAA,EAGF,OAEE,QAAA,MACA,MAAA,KACA,QAAA,EACA,cAAA,MACA,UAAA,OACA,YAAA,QAGF,mBAKE,mBAAA,KAIF,OACE,QAAA,aDsEF,SC9DE,QAAA"} -------------------------------------------------------------------------------- /src/main/resources/static/css/bootstrap-table.min.css: -------------------------------------------------------------------------------- 1 | .fixed-table-container .bs-checkbox,.fixed-table-container .no-records-found{text-align:center}.fixed-table-body thead th .th-inner,.table td,.table th{box-sizing:border-box}.bootstrap-table .table{margin-bottom:0!important;border-bottom:1px solid #ddd;border-collapse:collapse!important;border-radius:1px}.bootstrap-table .table:not(.table-condensed),.bootstrap-table .table:not(.table-condensed)>tbody>tr>td,.bootstrap-table .table:not(.table-condensed)>tbody>tr>th,.bootstrap-table .table:not(.table-condensed)>tfoot>tr>td,.bootstrap-table .table:not(.table-condensed)>tfoot>tr>th,.bootstrap-table .table:not(.table-condensed)>thead>tr>td{padding:8px}.bootstrap-table .table.table-no-bordered>tbody>tr>td,.bootstrap-table .table.table-no-bordered>thead>tr>th{border-right:2px solid transparent}.bootstrap-table .table.table-no-bordered>tbody>tr>td:last-child{border-right:none}.fixed-table-container{position:relative;clear:both;border:1px solid #ddd;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px}.fixed-table-container.table-no-bordered{border:1px solid transparent}.fixed-table-footer,.fixed-table-header{overflow:hidden}.fixed-table-footer{border-top:1px solid #ddd}.fixed-table-body{overflow-x:auto;overflow-y:auto;height:100%}.fixed-table-container table{width:100%}.fixed-table-container thead th{height:0;padding:0;margin:0;border-left:1px solid #ddd}.fixed-table-container thead th:focus{outline:transparent solid 0}.fixed-table-container thead th:first-child{border-left:none;border-top-left-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px}.fixed-table-container tbody td .th-inner,.fixed-table-container thead th .th-inner{padding:8px;line-height:24px;vertical-align:top;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fixed-table-container thead th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px}.fixed-table-container thead th .both{background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC')}.fixed-table-container thead th .asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==)}.fixed-table-container thead th .desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII=)}.fixed-table-container th.detail{width:30px}.fixed-table-container tbody td{border-left:1px solid #ddd}.fixed-table-container tbody tr:first-child td{border-top:none}.fixed-table-container tbody td:first-child{border-left:none}.fixed-table-container tbody .selected td{background-color:#f5f5f5}.fixed-table-container .bs-checkbox .th-inner{padding:8px 0}.fixed-table-container input[type=radio],.fixed-table-container input[type=checkbox]{margin:0 auto!important}.fixed-table-pagination .pagination-detail,.fixed-table-pagination div.pagination{margin-top:10px;margin-bottom:10px}.fixed-table-pagination div.pagination .pagination{margin:0}.fixed-table-pagination .pagination a{padding:6px 12px;line-height:1.428571429}.fixed-table-pagination .pagination-info{line-height:34px;margin-right:5px}.fixed-table-pagination .btn-group{position:relative;display:inline-block;vertical-align:middle}.fixed-table-pagination .dropup .dropdown-menu{margin-bottom:0}.fixed-table-pagination .page-list{display:inline-block}.fixed-table-toolbar .columns-left{margin-right:5px}.fixed-table-toolbar .columns-right{margin-left:5px}.fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429}.fixed-table-toolbar .bs-bars,.fixed-table-toolbar .columns,.fixed-table-toolbar .search{position:relative;margin-top:10px;margin-bottom:10px;line-height:34px}.fixed-table-pagination li.disabled a{pointer-events:none;cursor:default}.fixed-table-loading{display:none;position:absolute;top:42px;right:0;bottom:0;left:0;z-index:99;background-color:#fff;text-align:center}.fixed-table-body .card-view .title{font-weight:700;display:inline-block;min-width:30%;text-align:left!important}.table td,.table th{vertical-align:middle}.fixed-table-toolbar .dropdown-menu{text-align:left;max-height:300px;overflow:auto}.fixed-table-toolbar .btn-group>.btn-group{display:inline-block;margin-left:-1px!important}.fixed-table-toolbar .btn-group>.btn-group>.btn{border-radius:0}.fixed-table-toolbar .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.fixed-table-toolbar .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .table>thead>tr>th{vertical-align:bottom;border-bottom:1px solid #ddd}.bootstrap-table .table thead>tr>th{padding:0;margin:0}.bootstrap-table .fixed-table-footer tbody>tr>td{padding:0!important}.bootstrap-table .fixed-table-footer .table{border-bottom:none;border-radius:0;padding:0!important}.bootstrap-table .pull-right .dropdown-menu{right:0;left:auto}p.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}.fixed-table-pagination:after,.fixed-table-toolbar:after{content:"";display:block;clear:both} -------------------------------------------------------------------------------- /src/main/resources/static/css/cropbox.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * 图片剪切 style. 3 | * 4 | * @since: 1.0.0 5 | * @author Way Lau 6 | */ 7 | .crop { 8 | display: none; 9 | } 10 | 11 | .crop-input { 12 | visibility: hidden; 13 | width: 0; 14 | height: 0; 15 | } 16 | 17 | .crop-mask { 18 | position: fixed; 19 | left: 0; 20 | top: 0; 21 | width: 100%; 22 | height: 100%; 23 | background-color: rgba(0, 0, 0, .4); 24 | z-index: 59; 25 | } 26 | 27 | .crop-wrap { 28 | position: fixed; 29 | left: 5%; 30 | top: 50%; 31 | width: 90%; 32 | background-color: #fff; 33 | -webkit-transform: translate3d(0, -50%, 0); 34 | -moz-transform: translate3d(0, -50%, 0); 35 | -ms-transform: translate3d(0, -50%, 0); 36 | -o-transform: translate3d(0, -50%, 0); 37 | transform: translate3d(0, -50%, 0); 38 | z-index: 99; 39 | } 40 | 41 | .crop-wrap-content { 42 | position: relative; 43 | width: 250px; 44 | height: 250px; 45 | line-height: 250px; 46 | margin: 10px auto; 47 | } 48 | 49 | .crop-wrap-thum { 50 | position: absolute; 51 | /* LEFT: 20%; */ 52 | top: 50%; 53 | width: 100%; 54 | height: 100%; 55 | z-index: 99; 56 | /*background: none repeat scroll 0 0 transparent;*/ 57 | background: transparent; 58 | border: 1px solid rgb(102, 102, 102); 59 | -webkit-transform: translate3d(0, -50%, 0); 60 | -moz-transform: translate3d(0, -50%, 0); 61 | -ms-transform: translate3d(0, -50%, 0); 62 | -o-transform: translate3d(0, -50%, 0); 63 | transform: translate3d(0, -50%, 0); 64 | -webkit-box-shadow: 0 0 0 51px rgba(0, 0, 0, 0.4); 65 | -moz-box-shadow: 0 0 0 51px rgba(0, 0, 0, 0.4); 66 | box-shadow: 0 0 0 51px rgba(0, 0, 0, 0.4); 67 | } 68 | 69 | .crop-wrap-spinner { 70 | position: absolute; 71 | left: 0; 72 | top: 0; 73 | width: 100%; 74 | height: 100%; 75 | background-color: rgba(0, 0, 0, .6); 76 | color: #fff; 77 | text-align: center; 78 | display: none; 79 | } 80 | 81 | .crop-wrap-group { 82 | width: 100%; 83 | height: 30px; 84 | line-height: 30px; 85 | text-align: center; 86 | margin: 10px 0; 87 | } 88 | 89 | .crop-wrap-group > a { 90 | width: 40%; 91 | height: 100%; 92 | display: inline-block; 93 | background-color: #ff0000; 94 | color: #fff; 95 | } 96 | -------------------------------------------------------------------------------- /src/main/resources/static/css/images/emoji/Sysmbols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/css/images/emoji/Sysmbols.png -------------------------------------------------------------------------------- /src/main/resources/static/css/images/emoji/nature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/css/images/emoji/nature.png -------------------------------------------------------------------------------- /src/main/resources/static/css/images/emoji/object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/css/images/emoji/object.png -------------------------------------------------------------------------------- /src/main/resources/static/css/images/emoji/people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/css/images/emoji/people.png -------------------------------------------------------------------------------- /src/main/resources/static/css/images/emoji/place.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/css/images/emoji/place.png -------------------------------------------------------------------------------- /src/main/resources/static/css/images/emoji/twemoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/css/images/emoji/twemoji.png -------------------------------------------------------------------------------- /src/main/resources/static/css/jquery.tagsinput.min.css: -------------------------------------------------------------------------------- 1 | div.tagsinput{border:1px solid #CCC;background:#FFF;padding:5px;width:300px;height:100px;overflow-y:auto}div.tagsinput span.tag{border:1px solid #a5d24a;-moz-border-radius:2px;-webkit-border-radius:2px;display:block;float:left;padding:5px;text-decoration:none;background:#cde69c;color:#638421;margin-right:5px;margin-bottom:5px;font-family:helvetica;font-size:13px}div.tagsinput span.tag a{font-weight:700;color:#82ad2b;text-decoration:none;font-size:11px}div.tagsinput input{width:80px;margin:0 5px 5px 0;font-family:helvetica;font-size:13px;border:1px solid transparent;padding:5px;background:0 0;color:#000;outline:0}div.tagsinput div{display:block;float:left}.tags_clear{clear:both;width:100%;height:0}.not_valid{background:#FBD8DB!important;color:#90111A!important} -------------------------------------------------------------------------------- /src/main/resources/static/css/nprogress.css: -------------------------------------------------------------------------------- 1 | /* Make clicks pass-through */ 2 | #nprogress { 3 | pointer-events: none; 4 | } 5 | 6 | #nprogress .bar { 7 | background: #29d; 8 | 9 | position: fixed; 10 | z-index: 1031; 11 | top: 0; 12 | left: 0; 13 | 14 | width: 100%; 15 | height: 2px; 16 | } 17 | 18 | /* Fancy blur effect */ 19 | #nprogress .peg { 20 | display: block; 21 | position: absolute; 22 | right: 0px; 23 | width: 100px; 24 | height: 100%; 25 | box-shadow: 0 0 10px #29d, 0 0 5px #29d; 26 | opacity: 1.0; 27 | 28 | -webkit-transform: rotate(3deg) translate(0px, -4px); 29 | -ms-transform: rotate(3deg) translate(0px, -4px); 30 | transform: rotate(3deg) translate(0px, -4px); 31 | } 32 | 33 | /* Remove these to get rid of the spinner */ 34 | #nprogress .spinner { 35 | display: block; 36 | position: fixed; 37 | z-index: 1031; 38 | top: 15px; 39 | right: 15px; 40 | } 41 | 42 | #nprogress .spinner-icon { 43 | width: 18px; 44 | height: 18px; 45 | box-sizing: border-box; 46 | 47 | border: solid 2px transparent; 48 | border-top-color: #29d; 49 | border-left-color: #29d; 50 | border-radius: 50%; 51 | 52 | -webkit-animation: nprogress-spinner 400ms linear infinite; 53 | animation: nprogress-spinner 400ms linear infinite; 54 | } 55 | 56 | .nprogress-custom-parent { 57 | overflow: hidden; 58 | position: relative; 59 | } 60 | 61 | .nprogress-custom-parent #nprogress .spinner, 62 | .nprogress-custom-parent #nprogress .bar { 63 | position: absolute; 64 | } 65 | 66 | @-webkit-keyframes nprogress-spinner { 67 | 0% { -webkit-transform: rotate(0deg); } 68 | 100% { -webkit-transform: rotate(360deg); } 69 | } 70 | @keyframes nprogress-spinner { 71 | 0% { transform: rotate(0deg); } 72 | 100% { transform: rotate(360deg); } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/main/resources/static/css/style.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Override Bootstrap style. 3 | * 4 | * @since: 1.0.0 5 | * @author Way Lau 6 | */ 7 | 8 | .badge { 9 | margin-left: auto; 10 | } 11 | 12 | .hidden { 13 | display: none !important; 14 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/tether-theme-arrows.min.css: -------------------------------------------------------------------------------- 1 | .tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-center .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-middle .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-top .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-middle .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-top .tether-content{margin-bottom:16px}.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-left .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-right.tether-element-attached-middle .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-left .tether-content{margin-right:16px}.tether-element,.tether-element *,.tether-element :after,.tether-element :before,.tether-element:after,.tether-element:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block}.tether-element.tether-theme-arrows{max-width:100%;max-height:100%}.tether-element.tether-theme-arrows .tether-content{border-radius:5px;position:relative;font-family:inherit;background:#fff;color:inherit;padding:1em;font-size:1.1em;line-height:1.5em;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-filter:drop-shadow(0 1px 4px rgba(0, 0, 0, .2));filter:drop-shadow(0 1px 4px rgba(0, 0, 0, .2))}.tether-element.tether-theme-arrows .tether-content:before{content:"";display:block;position:absolute;width:0;height:0;border-color:transparent;border-width:16px;border-style:solid}.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-center .tether-content:before{top:100%;left:50%;margin-left:-16px;border-top-color:#fff;border-bottom:0}.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-center .tether-content{margin-top:16px}.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-center .tether-content:before{bottom:100%;left:50%;margin-left:-16px;border-bottom-color:#fff;border-top:0}.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-right .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-left.tether-element-attached-middle .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-right .tether-content{margin-left:16px}.tether-element.tether-theme-arrows.tether-element-attached-right.tether-element-attached-middle .tether-content:before{left:100%;top:50%;margin-top:-16px;border-left-color:#fff;border-right:0}.tether-element.tether-theme-arrows.tether-element-attached-left.tether-element-attached-middle .tether-content:before{right:100%;top:50%;margin-top:-16px;border-right-color:#fff;border-left:0}.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-bottom .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-middle .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-bottom .tether-content,.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-middle .tether-content{margin-top:16px}.tether-element.tether-theme-arrows.tether-element-attached-left.tether-target-attached-center .tether-content{left:-32px}.tether-element.tether-theme-arrows.tether-element-attached-right.tether-target-attached-center .tether-content{left:32px}.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-middle .tether-content:before{bottom:100%;left:16px;border-bottom-color:#fff;border-top:0}.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-middle .tether-content:before{bottom:100%;right:16px;border-bottom-color:#fff;border-top:0}.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-middle .tether-content:before{top:100%;left:16px;border-top-color:#fff;border-bottom:0}.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-middle .tether-content:before{top:100%;right:16px;border-top-color:#fff;border-bottom:0}.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-bottom .tether-content:before{bottom:100%;left:16px;border-bottom-color:#fff;border-top:0}.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-bottom .tether-content:before{bottom:100%;right:16px;border-bottom-color:#fff;border-top:0}.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-top .tether-content:before{top:100%;left:16px;border-top-color:#fff;border-bottom:0}.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-top .tether-content:before{top:100%;right:16px;border-top-color:#fff;border-bottom:0}.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-right.tether-target-attached-left .tether-content:before{top:16px;left:100%;border-left-color:#fff;border-right:0}.tether-element.tether-theme-arrows.tether-element-attached-top.tether-element-attached-left.tether-target-attached-right .tether-content:before{top:16px;right:100%;border-right-color:#fff;border-left:0}.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-right.tether-target-attached-left .tether-content:before{bottom:16px;left:100%;border-left-color:#fff;border-right:0}.tether-element.tether-theme-arrows.tether-element-attached-bottom.tether-element-attached-left.tether-target-attached-right .tether-content:before{bottom:16px;right:100%;border-right-color:#fff;border-left:0} -------------------------------------------------------------------------------- /src/main/resources/static/css/tether-theme-basic.css: -------------------------------------------------------------------------------- 1 | .tether-element, .tether-element:after, .tether-element:before, .tether-element *, .tether-element *:after, .tether-element *:before { 2 | box-sizing: border-box; } 3 | 4 | .tether-element { 5 | position: absolute; 6 | display: none; } 7 | .tether-element.tether-open { 8 | display: block; } 9 | 10 | .tether-element.tether-theme-basic { 11 | max-width: 100%; 12 | max-height: 100%; } 13 | .tether-element.tether-theme-basic .tether-content { 14 | border-radius: 5px; 15 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); 16 | font-family: inherit; 17 | background: #fff; 18 | color: inherit; 19 | padding: 1em; 20 | font-size: 1.1em; 21 | line-height: 1.5em; } 22 | -------------------------------------------------------------------------------- /src/main/resources/static/css/tether-theme-basic.min.css: -------------------------------------------------------------------------------- 1 | .tether-element,.tether-element *,.tether-element :after,.tether-element :before,.tether-element:after,.tether-element:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block}.tether-element.tether-theme-basic{max-width:100%;max-height:100%}.tether-element.tether-theme-basic .tether-content{border-radius:5px;box-shadow:0 2px 8px rgba(0,0,0,.2);font-family:inherit;background:#fff;color:inherit;padding:1em;font-size:1.1em;line-height:1.5em} -------------------------------------------------------------------------------- /src/main/resources/static/css/tether.css: -------------------------------------------------------------------------------- 1 | .tether-element, .tether-element:after, .tether-element:before, .tether-element *, .tether-element *:after, .tether-element *:before { 2 | box-sizing: border-box; } 3 | 4 | .tether-element { 5 | position: absolute; 6 | display: none; } 7 | .tether-element.tether-open { 8 | display: block; } 9 | -------------------------------------------------------------------------------- /src/main/resources/static/css/tether.min.css: -------------------------------------------------------------------------------- 1 | .tether-element,.tether-element *,.tether-element :after,.tether-element :before,.tether-element:after,.tether-element:before{box-sizing:border-box}.tether-element{position:absolute;display:none}.tether-element.tether-open{display:block} -------------------------------------------------------------------------------- /src/main/resources/static/css/thymeleaf-bootstrap-paginator.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Thymeleaf Bootstrap Paginator style. 3 | * 4 | * @since: 1.0.0 5 | * @author Way Lau 6 | */ 7 | 8 | .tbpage-total-elements { 9 | position: relative; 10 | display: block; 11 | padding: .5rem .75rem; 12 | margin-left: -1px; 13 | line-height: 1.25; 14 | color: #464a4c; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/static/css/toastr.min.css: -------------------------------------------------------------------------------- 1 | .toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#FFF}.toast-message a:hover{color:#CCC;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#FFF;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);line-height:1}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}.rtl .toast-close-button{left:-.3em;float:left;right:.3em}button.toast-close-button{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999;pointer-events:none}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#FFF;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51A351}.toast-error{background-color:#BD362F}.toast-info{background-color:#2F96B4}.toast-warning{background-color:#F89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}} -------------------------------------------------------------------------------- /src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/fonts/gly-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/fonts/gly-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/resources/static/fonts/gly-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/fonts/gly-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/gly-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/fonts/gly-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/gly-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/fonts/gly-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/images/avatar-defualt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/images/avatar-defualt.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/waylau/new-star-blog/05c3e20a6594ebc13dfc83b375b0302e9915c281/src/main/resources/static/images/delete.png -------------------------------------------------------------------------------- /src/main/resources/static/js/admins/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bolg main JS. 3 | * Created by waylau.com on 2017/3/9. 4 | */ 5 | "use strict"; 6 | //# sourceURL=main.js 7 | 8 | // DOM 加载完再执行 9 | $(function() { 10 | 11 | // 菜单事件 12 | $(".blog-menu .list-group-item").click(function() { 13 | 14 | var url = $(this).attr("url"); 15 | 16 | // 先移除其他的点击样式,再添加当前的点击样式 17 | $(".blog-menu .list-group-item").removeClass("active"); 18 | $(this).addClass("active"); 19 | 20 | // 加载其他模块的页面到右侧工作区 21 | $.ajax({ 22 | url: url, 23 | success: function(data){ 24 | $("#rightContainer").html(data); 25 | }, 26 | error : function() { 27 | alert("error"); 28 | } 29 | }); 30 | }); 31 | 32 | // 选中菜单第一项 33 | $(".blog-menu .list-group-item:first").trigger("click"); 34 | }); -------------------------------------------------------------------------------- /src/main/resources/static/js/catalog-generator.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 页面目录生成器. 3 | * 4 | 比如,我们有如下代码: 5 | 6 | 7 | OAuth 1.0 的认证流程 8 | OAuth 2.0 的认证流程 9 | OAuth 3.0 的认证流程 10 | 11 | 12 | 13 | 要生成目录,按如下方式初始化: 14 | $.catalog("#catalog", ".abc"); 15 | 16 | * @since: 1.0.0 2017-03-26 17 | * @author Way Lau 18 | */ 19 | (function($) { 20 | 21 | "use strict"; 22 | 23 | $.catalog = function(selector, target) { 24 | 25 | $(target + " :header").each(function(i,item){ 26 | var tag = $(item).get(0).localName; 27 | $(item).attr("id","wow"+i); 28 | $(selector).append(''+$(this).text()+''); 29 | $(".newh1").css("margin-left",0); 30 | $(".newh2").css("margin-left",5); 31 | $(".newh3").css("margin-left",10); 32 | $(".newh4").css("margin-left",15); 33 | $(".newh5").css("margin-left",20); 34 | $(".newh6").css("margin-left",25); 35 | }); 36 | }; 37 | 38 | })(jQuery); -------------------------------------------------------------------------------- /src/main/resources/static/js/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * index main JS. 3 | * 4 | * @since: 1.0.0 2017/4/12 5 | * @author Way Lau 6 | */ 7 | "use strict"; 8 | //# sourceURL=index.js 9 | 10 | // DOM 加载完再执行 11 | $(function() { 12 | 13 | var _pageSize; // 存储用于搜索 14 | 15 | // 根据用户名、页面索引、页面大小获取用户列表 16 | function getBlogsByName(pageIndex, pageSize) { 17 | $.ajax({ 18 | url: "/blogs", 19 | contentType : 'application/json', 20 | data:{ 21 | "async":true, 22 | "pageIndex":pageIndex, 23 | "pageSize":pageSize, 24 | "keyword":$("#indexkeyword").val() 25 | }, 26 | success: function(data){ 27 | $("#mainContainer").html(data); 28 | 29 | var keyword = $("#indexkeyword").val(); 30 | 31 | // 如果是分类查询,则取消最新、最热选中样式 32 | if (keyword.length > 0) { 33 | $(".nav-item .nav-link").removeClass("active"); 34 | } 35 | }, 36 | error : function() { 37 | toastr.error("error!"); 38 | } 39 | }); 40 | } 41 | 42 | // 分页 43 | $.tbpage("#mainContainer", function (pageIndex, pageSize) { 44 | getBlogsByName(pageIndex, pageSize); 45 | _pageSize = pageSize; 46 | }); 47 | 48 | // 关键字搜索 49 | $("#indexsearch").click(function() { 50 | getBlogsByName(0, _pageSize); 51 | }); 52 | 53 | // 最新\最热切换事件 54 | $(".nav-item .nav-link").click(function() { 55 | 56 | var url = $(this).attr("url"); 57 | 58 | // 先移除其他的点击样式,再添加当前的点击样式 59 | $(".nav-item .nav-link").removeClass("active"); 60 | $(this).addClass("active"); 61 | 62 | // 加载其他模块的页面到右侧工作区 63 | $.ajax({ 64 | url: url+'&async=true', 65 | success: function(data){ 66 | $("#mainContainer").html(data); 67 | }, 68 | error : function() { 69 | toastr.error("error!"); 70 | } 71 | }) 72 | 73 | // 清空搜索框内容 74 | $("#indexkeyword").val(''); 75 | }); 76 | 77 | 78 | }); -------------------------------------------------------------------------------- /src/main/resources/static/js/jquery.tagsinput.min.js: -------------------------------------------------------------------------------- 1 | !function(a){var b=new Array,c=new Array;a.fn.doAutosize=function(b){var c=a(this).data("minwidth"),d=a(this).data("maxwidth"),e="",f=a(this),g=a("#"+a(this).data("tester_id"));if(e!==(e=f.val())){var h=e.replace(/&/g,"&").replace(/\s/g," ").replace(//g,">");g.html(h);var i=g.width(),j=i+b.comfortZone>=c?i+b.comfortZone:c,k=f.width(),l=k>j&&j>=c||j>c&&d>j;l&&f.width(j)}},a.fn.resetAutosize=function(b){var c=a(this).data("minwidth")||b.minInputWidth||a(this).width(),d=a(this).data("maxwidth")||b.maxInputWidth||a(this).closest(".tagsinput").width()-b.inputPadding,e=a(this),f=a("").css({position:"absolute",top:-9999,left:-9999,width:"auto",fontSize:e.css("fontSize"),fontFamily:e.css("fontFamily"),fontWeight:e.css("fontWeight"),letterSpacing:e.css("letterSpacing"),whiteSpace:"nowrap"}),g=a(this).attr("id")+"_autosize_tester";!a("#"+g).length>0&&(f.attr("id",g),f.appendTo("body")),e.data("minwidth",c),e.data("maxwidth",d),e.data("tester_id",g),e.css("width",c)},a.fn.addTag=function(d,e){return e=jQuery.extend({focus:!1,callback:!0},e),this.each(function(){var f=a(this).attr("id"),g=a(this).val().split(b[f]);if(""==g[0]&&(g=new Array),d=jQuery.trim(d),e.unique){var h=a(this).tagExist(d);1==h&&a("#"+f+"_tag").addClass("not_valid")}else var h=!1;if(""!=d&&1!=h){if(a("").addClass("tag").append(a("").text(d).append(" "),a("",{href:"#",title:"Removing tag",text:"x"}).click(function(){return a("#"+f).removeTag(escape(d))})).insertBefore("#"+f+"_addTag"),g.push(d),a("#"+f+"_tag").val(""),e.focus?a("#"+f+"_tag").focus():a("#"+f+"_tag").blur(),a.fn.tagsInput.updateTagsField(this,g),e.callback&&c[f]&&c[f].onAddTag){var i=c[f].onAddTag;i.call(this,d)}if(c[f]&&c[f].onChange){var j=g.length,i=c[f].onChange;i.call(this,a(this),g[j-1])}}}),!1},a.fn.removeTag=function(d){return d=unescape(d),this.each(function(){var e=a(this).attr("id"),f=a(this).val().split(b[e]);for(a("#"+e+"_tagsinput .tag").remove(),str="",i=0;i=0},a.fn.importTags=function(b){var c=a(this).attr("id");a("#"+c+"_tagsinput .tag").remove(),a.fn.tagsInput.importTags(this,b)},a.fn.tagsInput=function(e){var f=jQuery.extend({interactive:!0,defaultText:"add a tag",minChars:0,width:"300px",height:"100px",autocomplete:{selectFirst:!1},hide:!0,delimiter:",",unique:!0,removeWithBackspace:!0,placeholderColor:"#666666",autosize:!0,comfortZone:20,inputPadding:12},e),g=0;return this.each(function(){if("undefined"==typeof a(this).attr("data-tagsinput-init")){a(this).attr("data-tagsinput-init",!0),f.hide&&a(this).hide();var e=a(this).attr("id");(!e||b[a(this).attr("id")])&&(e=a(this).attr("id","tags"+(new Date).getTime()+g++).attr("id"));var h=jQuery.extend({pid:e,real_input:"#"+e,holder:"#"+e+"_tagsinput",input_wrapper:"#"+e+"_addTag",fake_input:"#"+e+"_tag"},f);b[e]=h.delimiter,(f.onAddTag||f.onRemoveTag||f.onChange)&&(c[e]=new Array,c[e].onAddTag=f.onAddTag,c[e].onRemoveTag=f.onRemoveTag,c[e].onChange=f.onChange);var i='';if(f.interactive&&(i=i+''),i+='',a(i).insertAfter(this),a(h.holder).css("width",f.width),a(h.holder).css("min-height",f.height),a(h.holder).css("height",f.height),""!=a(h.real_input).val()&&a.fn.tagsInput.importTags(a(h.real_input),a(h.real_input).val()),f.interactive){if(a(h.fake_input).val(a(h.fake_input).attr("data-default")),a(h.fake_input).css("color",f.placeholderColor),a(h.fake_input).resetAutosize(f),a(h.holder).bind("click",h,function(b){a(b.data.fake_input).focus()}),a(h.fake_input).bind("focus",h,function(b){a(b.data.fake_input).val()==a(b.data.fake_input).attr("data-default")&&a(b.data.fake_input).val(""),a(b.data.fake_input).css("color","#000000")}),void 0!=f.autocomplete_url){autocomplete_options={source:f.autocomplete_url};for(attrname in f.autocomplete)autocomplete_options[attrname]=f.autocomplete[attrname];void 0!==jQuery.Autocompleter?(a(h.fake_input).autocomplete(f.autocomplete_url,f.autocomplete),a(h.fake_input).bind("result",h,function(b,c,d){c&&a("#"+e).addTag(c[0]+"",{focus:!0,unique:f.unique})})):void 0!==jQuery.ui.autocomplete&&(a(h.fake_input).autocomplete(autocomplete_options),a(h.fake_input).bind("autocompleteselect",h,function(b,c){return a(b.data.real_input).addTag(c.item.value,{focus:!0,unique:f.unique}),!1}))}else a(h.fake_input).bind("blur",h,function(b){var c=a(this).attr("data-default");return""!=a(b.data.fake_input).val()&&a(b.data.fake_input).val()!=c?b.data.minChars<=a(b.data.fake_input).val().length&&(!b.data.maxChars||b.data.maxChars>=a(b.data.fake_input).val().length)&&a(b.data.real_input).addTag(a(b.data.fake_input).val(),{focus:!0,unique:f.unique}):(a(b.data.fake_input).val(a(b.data.fake_input).attr("data-default")),a(b.data.fake_input).css("color",f.placeholderColor)),!1});a(h.fake_input).bind("keypress",h,function(b){return d(b)?(b.preventDefault(),b.data.minChars<=a(b.data.fake_input).val().length&&(!b.data.maxChars||b.data.maxChars>=a(b.data.fake_input).val().length)&&a(b.data.real_input).addTag(a(b.data.fake_input).val(),{focus:!0,unique:f.unique}),a(b.data.fake_input).resetAutosize(f),!1):void(b.data.autosize&&a(b.data.fake_input).doAutosize(f))}),h.removeWithBackspace&&a(h.fake_input).bind("keydown",function(b){if(8==b.keyCode&&""==a(this).val()){b.preventDefault();var c=a(this).closest(".tagsinput").find(".tag:last").text(),d=a(this).attr("id").replace(/_tag$/,"");c=c.replace(/[\s]+x$/,""),a("#"+d).removeTag(escape(c)),a(this).trigger("focus")}}),a(h.fake_input).blur(),h.unique&&a(h.fake_input).keydown(function(b){(8==b.keyCode||String.fromCharCode(b.which).match(/\w+|[áéíóúÁÉÍÓÚñÑ,/]+/))&&a(this).removeClass("not_valid")})}}}),this},a.fn.tagsInput.updateTagsField=function(c,d){var e=a(c).attr("id");a(c).val(d.join(b[e]))},a.fn.tagsInput.importTags=function(d,e){a(d).val("");var f=a(d).attr("id"),g=e.split(b[f]);for(i=0;i200 ){ //判断滚动后高度超过200px,就显示 17 | $("#goToTop").fadeIn(400); //淡出 18 | }else{ 19 | $("#goToTop").stop().fadeOut(400); //如果返回或者没有超过,就淡入.必须加上stop()停止之前动画,否则会出现闪动 20 | } 21 | }); 22 | $("#goToTop").click(function(){ //当点击标签的时候,使用animate在200毫秒的时间内,滚到顶部 23 | $("html,body").animate({scrollTop:"0px"},200); 24 | }); 25 | NProgress.done(); 26 | }); -------------------------------------------------------------------------------- /src/main/resources/static/js/thymeleaf-bootstrap-paginator.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 分页处理. 3 | * 4 | * @since: 1.0.0 5 | * @author Way Lau 6 | */ 7 | (function($) { 8 | 9 | "use strict"; 10 | 11 | /** 12 | * handler:pageIndex 所选页面的索引,从0开始;pageSize 页面的大小,这里默认是10。 13 | */ 14 | $.tbpage = function(selector, handler) { 15 | 16 | $(selector).off("click", ".tbpage-item").on("click", ".tbpage-item", function() { 17 | 18 | var pageIndex = $(this).attr("pageIndex"); 19 | 20 | var pageSize = $('.tbpage-size option:selected').val(); 21 | // 判断所选元素是否为当前页面 22 | // 若不是当前页面才需要处理 23 | if($(this).parent().attr("class").indexOf("active")>0){ 24 | //console.log("为当前页面"); 25 | }else{ 26 | handler(pageIndex, pageSize); 27 | } 28 | 29 | }); 30 | 31 | 32 | $(selector).off("change", ".tbpage-size").on("change", ".tbpage-size", function() { 33 | 34 | var pageIndex = $(this).attr("pageIndex"); 35 | 36 | var pageSize = $('.tbpage-size option:selected').val(); 37 | 38 | handler(pageIndex, pageSize); 39 | }); 40 | }; 41 | 42 | })(jQuery); -------------------------------------------------------------------------------- /src/main/resources/static/js/toastr.min.js: -------------------------------------------------------------------------------- 1 | !function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'×',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e(""),M=e(""),B=e(""),q=e(""),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.3",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)}); 2 | //# sourceMappingURL=toastr.js.map 3 | -------------------------------------------------------------------------------- /src/main/resources/static/js/users/main.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bolg main JS. 3 | * 4 | * @since: 1.0.0 2017/3/9 5 | * @author Way Lau 6 | */ 7 | "use strict"; 8 | //# sourceURL=main.js 9 | 10 | //DOM 加载完再执行 11 | $(function() { 12 | 13 | var _pageSize; // 存储用于搜索 14 | 15 | // 根据用户名、页面索引、页面大小获取用户列表 16 | function getUersByName(pageIndex, pageSize) { 17 | $.ajax({ 18 | url: "/users", 19 | contentType : 'application/json', 20 | data:{ 21 | "async":true, 22 | "pageIndex":pageIndex, 23 | "pageSize":pageSize, 24 | "name":$("#searchName").val() 25 | }, 26 | success: function(data){ 27 | $("#mainContainer").html(data); 28 | }, 29 | error : function() { 30 | toastr.error("error!"); 31 | } 32 | }); 33 | } 34 | 35 | // 分页 36 | $.tbpage("#mainContainer", function (pageIndex, pageSize) { 37 | getUersByName(pageIndex, pageSize); 38 | _pageSize = pageSize; 39 | }); 40 | 41 | // 搜索 42 | $("#searchNameBtn").click(function() { 43 | getUersByName(0, _pageSize); 44 | }); 45 | 46 | // 获取添加用户的界面 47 | $("#addUser").click(function() { 48 | $.ajax({ 49 | url: "/users/add", 50 | success: function(data){ 51 | $("#userFormContainer").html(data); 52 | }, 53 | error : function(data) { 54 | toastr.error("error!"); 55 | } 56 | }); 57 | }); 58 | 59 | // 获取编辑用户的界面 60 | $("#rightContainer").on("click",".blog-edit-user", function () { 61 | $.ajax({ 62 | url: "/users/edit/" + $(this).attr("userId"), 63 | success: function(data){ 64 | $("#userFormContainer").html(data); 65 | }, 66 | error : function() { 67 | toastr.error("error!"); 68 | } 69 | }); 70 | }); 71 | 72 | // 提交变更后,清空表单 73 | $("#submitEdit").click(function() { 74 | $.ajax({ 75 | url: "/users", 76 | type: 'POST', 77 | data:$('#userForm').serialize(), 78 | success: function(data){ 79 | $('#userForm')[0].reset(); 80 | 81 | if (data.success) { 82 | // 从新刷新主界面 83 | getUersByName(0, _pageSize); 84 | } else { 85 | toastr.error(data.message); 86 | } 87 | 88 | }, 89 | error : function() { 90 | toastr.error("error!"); 91 | } 92 | }); 93 | }); 94 | 95 | // 删除用户 96 | $("#rightContainer").on("click",".blog-delete-user", function () { 97 | 98 | $.ajax({ 99 | url: "/users/" + $(this).attr("userId") , 100 | type: 'DELETE', 101 | success: function(data){ 102 | if (data.success) { 103 | // 从新刷新主界面 104 | getUersByName(0, _pageSize); 105 | } else { 106 | toastr.error(data.message); 107 | } 108 | }, 109 | error : function() { 110 | toastr.error("error!"); 111 | } 112 | }); 113 | }); 114 | }); -------------------------------------------------------------------------------- /src/main/resources/static/js/userspace/blog.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * blog.html 页面脚本. 3 | * 4 | * @since: 1.0.0 2017-03-26 5 | * @author Way Lau 6 | */ 7 | "use strict"; 8 | //# sourceURL=blog.js 9 | 10 | // DOM 加载完再执行 11 | $(function() { 12 | $.catalog("#catalog", ".post-content"); 13 | 14 | // 处理删除博客事件 15 | $(".blog-content-container").on("click",".blog-delete-blog", function () { 16 | // 获取 CSRF Token 17 | var csrfToken = $("meta[name='_csrf']").attr("content"); 18 | var csrfHeader = $("meta[name='_csrf_header']").attr("content"); 19 | 20 | 21 | $.ajax({ 22 | url: blogUrl, 23 | type: 'DELETE', 24 | beforeSend: function(request) { 25 | request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF Token 26 | }, 27 | success: function(data){ 28 | if (data.success) { 29 | // 成功后,重定向 30 | window.location = data.body; 31 | } else { 32 | toastr.error(data.message); 33 | } 34 | }, 35 | error : function() { 36 | toastr.error("error!"); 37 | } 38 | }); 39 | }); 40 | 41 | // 获取评论列表 42 | function getCommnet(blogId) { 43 | 44 | $.ajax({ 45 | url: '/comments', 46 | type: 'GET', 47 | data:{"blogId":blogId}, 48 | success: function(data){ 49 | $("#mainContainer").html(data); 50 | 51 | }, 52 | error : function() { 53 | toastr.error("error!"); 54 | } 55 | }); 56 | } 57 | 58 | // 提交评论 59 | $(".blog-content-container").on("click","#submitComment", function () { 60 | // 获取 CSRF Token 61 | var csrfToken = $("meta[name='_csrf']").attr("content"); 62 | var csrfHeader = $("meta[name='_csrf_header']").attr("content"); 63 | 64 | $.ajax({ 65 | url: '/comments', 66 | type: 'POST', 67 | data:{"blogId":blogId, "commentContent":$('#commentContent').val()}, 68 | beforeSend: function(request) { 69 | request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF Token 70 | }, 71 | success: function(data){ 72 | if (data.success) { 73 | // 清空评论框 74 | $('#commentContent').val(''); 75 | // 获取评论列表 76 | getCommnet(blogId); 77 | } else { 78 | toastr.error(data.message); 79 | } 80 | }, 81 | error : function() { 82 | toastr.error("error!"); 83 | } 84 | }); 85 | }); 86 | 87 | // 删除评论 88 | $(".blog-content-container").on("click",".blog-delete-comment", function () { 89 | // 获取 CSRF Token 90 | var csrfToken = $("meta[name='_csrf']").attr("content"); 91 | var csrfHeader = $("meta[name='_csrf_header']").attr("content"); 92 | 93 | $.ajax({ 94 | url: '/comments/'+$(this).attr("commentId")+'?blogId='+blogId, 95 | type: 'DELETE', 96 | beforeSend: function(request) { 97 | request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF Token 98 | }, 99 | success: function(data){ 100 | if (data.success) { 101 | // 获取评论列表 102 | getCommnet(blogId); 103 | } else { 104 | toastr.error(data.message); 105 | } 106 | }, 107 | error : function() { 108 | toastr.error("error!"); 109 | } 110 | }); 111 | }); 112 | 113 | // 初始化 博客评论 114 | getCommnet(blogId); 115 | 116 | // 提交点赞 117 | $(".blog-content-container").on("click","#submitVote", function () { 118 | // 获取 CSRF Token 119 | var csrfToken = $("meta[name='_csrf']").attr("content"); 120 | var csrfHeader = $("meta[name='_csrf_header']").attr("content"); 121 | 122 | $.ajax({ 123 | url: '/votes', 124 | type: 'POST', 125 | data:{"blogId":blogId}, 126 | beforeSend: function(request) { 127 | request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF Token 128 | }, 129 | success: function(data){ 130 | if (data.success) { 131 | toastr.info(data.message); 132 | // 成功后,重定向 133 | window.location = blogUrl; 134 | } else { 135 | toastr.error(data.message); 136 | } 137 | }, 138 | error : function() { 139 | toastr.error("error!"); 140 | } 141 | }); 142 | }); 143 | 144 | // 取消点赞 145 | $(".blog-content-container").on("click","#cancelVote", function () { 146 | // 获取 CSRF Token 147 | var csrfToken = $("meta[name='_csrf']").attr("content"); 148 | var csrfHeader = $("meta[name='_csrf_header']").attr("content"); 149 | 150 | $.ajax({ 151 | url: '/votes/'+$(this).attr('voteId')+'?blogId='+blogId, 152 | type: 'DELETE', 153 | beforeSend: function(request) { 154 | request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF Token 155 | }, 156 | success: function(data){ 157 | if (data.success) { 158 | toastr.info(data.message); 159 | // 成功后,重定向 160 | window.location = blogUrl; 161 | } else { 162 | toastr.error(data.message); 163 | } 164 | }, 165 | error : function() { 166 | toastr.error("error!"); 167 | } 168 | }); 169 | }); 170 | }); -------------------------------------------------------------------------------- /src/main/resources/static/js/userspace/blogedit.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * blogedit.html 页面脚本. 3 | * 4 | * @since: 1.0.0 2017-03-26 5 | * @author Way Lau 6 | */ 7 | "use strict"; 8 | //# sourceURL=blogedit.js 9 | 10 | // DOM 加载完再执行 11 | $(function() { 12 | 13 | // 初始化 md 编辑器 14 | $("#md").markdown({ 15 | language: 'zh', 16 | fullscreen: { 17 | enable: true 18 | }, 19 | resize:'vertical', 20 | localStorage:'md' 21 | }); 22 | 23 | $('.form-control-chosen').chosen(); 24 | 25 | 26 | $("#uploadImage").click(function() { 27 | $.ajax({ 28 | url: fileServerUrl, 29 | type: 'POST', 30 | cache: false, 31 | data: new FormData($('#uploadformid')[0]), 32 | processData: false, 33 | contentType: false, 34 | success: function(data){ 35 | var mdcontent=$("#md").val(); 36 | $("#md").val(mdcontent + "\n \n"); 37 | 38 | } 39 | }).done(function(res) { 40 | $('#file').val(''); 41 | }).fail(function(res) {}); 42 | }) 43 | 44 | // 发布博客 45 | $("#submitBlog").click(function() { 46 | 47 | // 获取 CSRF Token 48 | var csrfToken = $("meta[name='_csrf']").attr("content"); 49 | var csrfHeader = $("meta[name='_csrf_header']").attr("content"); 50 | 51 | $.ajax({ 52 | url: '/u/'+ $(this).attr("userName") + '/blogs/edit', 53 | type: 'POST', 54 | contentType: "application/json; charset=utf-8", 55 | data:JSON.stringify({"id":$('#blogId').val(), 56 | "title": $('#title').val(), 57 | "summary": $('#summary').val() , 58 | "content": $('#md').val(), 59 | "catalog":{"id":$('#catalogSelect').val()}, 60 | "tags":$('.form-control-tag').val() 61 | }), 62 | beforeSend: function(request) { 63 | request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF Token 64 | }, 65 | success: function(data){ 66 | if (data.success) { 67 | // 成功后,重定向 68 | window.location = data.body; 69 | } else { 70 | toastr.error("error!"+data.message); 71 | } 72 | 73 | }, 74 | error : function() { 75 | toastr.error("error!"); 76 | } 77 | }) 78 | }) 79 | 80 | // 初始化标签 81 | $('.form-control-tag').tagsInput({ 82 | 'defaultText':'输入标签' 83 | }); 84 | }); -------------------------------------------------------------------------------- /src/main/resources/static/js/userspace/main.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Avatar JS. 3 | * 4 | * @since: 1.0.0 2017/4/6 5 | * @author Way Lau 6 | */ 7 | "use strict"; 8 | //# sourceURL=main.js 9 | 10 | // DOM 加载完再执行 11 | $(function() { 12 | var avatarApi; 13 | 14 | // 获取编辑用户头像的界面 15 | $(".blog-content-container").on("click",".blog-edit-avatar", function () { 16 | avatarApi = "/u/"+$(this).attr("userName")+"/avatar"; 17 | $.ajax({ 18 | url: avatarApi, 19 | success: function(data){ 20 | $("#avatarFormContainer").html(data); 21 | }, 22 | error : function() { 23 | toastr.error("error!"); 24 | } 25 | }); 26 | }); 27 | 28 | /** 29 | * 将以base64的图片url数据转换为Blob 30 | * @param urlData 31 | * 用url方式表示的base64图片数据 32 | */ 33 | function convertBase64UrlToBlob(urlData){ 34 | 35 | var bytes=window.atob(urlData.split(',')[1]); //去掉url的头,并转换为byte 36 | 37 | //处理异常,将ascii码小于0的转换为大于0 38 | var ab = new ArrayBuffer(bytes.length); 39 | var ia = new Uint8Array(ab); 40 | for (var i = 0; i < bytes.length; i++) { 41 | ia[i] = bytes.charCodeAt(i); 42 | } 43 | 44 | return new Blob( [ab] , {type : 'image/png'}); 45 | } 46 | 47 | // 提交用户头像的图片数据 48 | $("#submitEditAvatar").on("click", function () { 49 | var form = $('#avatarformid')[0]; 50 | var formData = new FormData(form); 51 | var base64Codes = $(".cropImg > img").attr("src"); 52 | formData.append("file",convertBase64UrlToBlob(base64Codes)); 53 | 54 | $.ajax({ 55 | url: fileServerUrl, // 文件服务器地址 56 | type: 'POST', 57 | cache: false, 58 | data: formData, 59 | processData: false, 60 | contentType: false, 61 | success: function(data){ 62 | 63 | var avatarUrl = data; 64 | 65 | // 获取 CSRF Token 66 | var csrfToken = $("meta[name='_csrf']").attr("content"); 67 | var csrfHeader = $("meta[name='_csrf_header']").attr("content"); 68 | // 保存头像更改到数据库 69 | $.ajax({ 70 | url: avatarApi, 71 | type: 'POST', 72 | contentType: "application/json; charset=utf-8", 73 | data: JSON.stringify({"id":Number($("#userId").val()), 74 | "avatar":avatarUrl}), 75 | beforeSend: function(request) { 76 | request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF Token 77 | }, 78 | success: function(data){ 79 | if (data.success) { 80 | // 成功后,置换头像图片 81 | $(".blog-avatar").attr("src", data.avatarUrl); 82 | } else { 83 | toastr.error("error!"+data.message); 84 | } 85 | 86 | }, 87 | error : function() { 88 | toastr.error("error!"); 89 | } 90 | }); 91 | }, 92 | error : function() { 93 | toastr.error("error!"); 94 | } 95 | }) 96 | }); 97 | }); -------------------------------------------------------------------------------- /src/main/resources/static/js/userspace/u.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * u main JS. 3 | * 4 | * @since: 1.0.0 2017/3/9 5 | * @author Way Lau 6 | */ 7 | "use strict"; 8 | //# sourceURL=u.js 9 | 10 | // DOM 加载完再执行 11 | $(function() { 12 | 13 | var _pageSize; // 存储用于搜索 14 | 15 | // 根据用户名、页面索引、页面大小获取用户列表 16 | function getBlogsByName(pageIndex, pageSize) { 17 | $.ajax({ 18 | url: "/u/"+ username +"/blogs", 19 | contentType : 'application/json', 20 | data:{ 21 | "async":true, 22 | "pageIndex":pageIndex, 23 | "pageSize":pageSize, 24 | "catalog": catalogId, 25 | "keyword":$("#keyword").val() 26 | }, 27 | success: function(data){ 28 | $("#mainContainer").html(data); 29 | 30 | // 如果是分类查询,则取消最新、最热选中样式 31 | if (catalogId) { 32 | $(".nav-item .nav-link").removeClass("active"); 33 | } 34 | }, 35 | error : function() { 36 | toastr.error("error!"); 37 | } 38 | }); 39 | } 40 | 41 | // 分页 42 | $.tbpage("#mainContainer", function (pageIndex, pageSize) { 43 | getBlogsByName(pageIndex, pageSize); 44 | _pageSize = pageSize; 45 | }); 46 | 47 | // 关键字搜索 48 | $("#searchBlogs").click(function() { 49 | getBlogsByName(0, _pageSize); 50 | }); 51 | 52 | // 最新\最热切换事件 53 | $(".nav-item .nav-link").click(function() { 54 | 55 | var url = $(this).attr("url"); 56 | 57 | // 先移除其他的点击样式,再添加当前的点击样式 58 | $(".nav-item .nav-link").removeClass("active"); 59 | $(this).addClass("active"); 60 | 61 | // 加载其他模块的页面到右侧工作区 62 | $.ajax({ 63 | url: url+'&async=true', 64 | success: function(data){ 65 | $("#mainContainer").html(data); 66 | }, 67 | error : function() { 68 | toastr.error("error!"); 69 | } 70 | }) 71 | 72 | // 清空搜索框内容 73 | $("#keyword").val(''); 74 | }); 75 | 76 | var catalogId; 77 | 78 | // 获取分类列表 79 | function getCatalogs(username) { 80 | // 获取 CSRF Token 81 | 82 | $.ajax({ 83 | url: '/catalogs', 84 | type: 'GET', 85 | data:{"username":username}, 86 | success: function(data){ 87 | $("#catalogMain").html(data); 88 | }, 89 | error : function() { 90 | toastr.error("error!"); 91 | } 92 | }); 93 | } 94 | 95 | // 获取编辑分类的页面 96 | $(".blog-content-container").on("click",".blog-add-catalog", function () { 97 | $.ajax({ 98 | url: '/catalogs/edit', 99 | type: 'GET', 100 | success: function(data){ 101 | $("#catalogFormContainer").html(data); 102 | }, 103 | error : function() { 104 | toastr.error("error!"); 105 | } 106 | }); 107 | }); 108 | 109 | // 获取编辑某个分类的页面 110 | $(".blog-content-container").on("click",".blog-edit-catalog", function () { 111 | 112 | $.ajax({ 113 | url: '/catalogs/edit/'+$(this).attr('catalogId'), 114 | type: 'GET', 115 | success: function(data){ 116 | $("#catalogFormContainer").html(data); 117 | }, 118 | error : function() { 119 | toastr.error("error!"); 120 | } 121 | }); 122 | }); 123 | 124 | 125 | // 提交分类 126 | $("#submitEditCatalog").click(function() { 127 | // 获取 CSRF Token 128 | var csrfToken = $("meta[name='_csrf']").attr("content"); 129 | var csrfHeader = $("meta[name='_csrf_header']").attr("content"); 130 | 131 | $.ajax({ 132 | url : '/catalogs', 133 | type : 'POST', 134 | contentType : "application/json; charset=utf-8", 135 | data : JSON.stringify({ 136 | "username" : username, 137 | "catalog" : { 138 | "id" : $('#catalogId').val(), 139 | "name" : $('#catalogName').val() 140 | } 141 | }), 142 | beforeSend : function(request) { 143 | request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF 144 | // Token 145 | }, 146 | success : function(data) { 147 | if (data.success) { 148 | toastr.info(data.message); 149 | // 成功后,刷新列表 150 | getCatalogs(username); 151 | } else { 152 | toastr.error(data.message); 153 | } 154 | }, 155 | error : function() { 156 | toastr.error("error!"); 157 | } 158 | }); 159 | }); 160 | 161 | // 删除分类 162 | $(".blog-content-container").on("click",".blog-delete-catalog", function () { 163 | // 获取 CSRF Token 164 | var csrfToken = $("meta[name='_csrf']").attr("content"); 165 | var csrfHeader = $("meta[name='_csrf_header']").attr("content"); 166 | 167 | $.ajax({ 168 | url: '/catalogs/'+$(this).attr('catalogid')+'?username='+username, 169 | type: 'DELETE', 170 | beforeSend: function(request) { 171 | request.setRequestHeader(csrfHeader, csrfToken); // 添加 CSRF Token 172 | }, 173 | success: function(data){ 174 | if (data.success) { 175 | toastr.info(data.message); 176 | // 成功后,刷新列表 177 | getCatalogs(username); 178 | } else { 179 | toastr.error(data.message); 180 | } 181 | }, 182 | error : function() { 183 | toastr.error("error!"); 184 | } 185 | }); 186 | }); 187 | 188 | // 根据分类查询 189 | $(".blog-content-container").on("click",".blog-query-by-catalog", function () { 190 | catalogId = $(this).attr('catalogId'); 191 | getBlogsByName(0, _pageSize); 192 | }); 193 | 194 | getCatalogs(username); 195 | 196 | }); -------------------------------------------------------------------------------- /src/main/resources/templates/admins/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 菜单 21 | 22 | 23 | 24 | 用户管理 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ... 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 36 | 37 | 38 | 40 | 41 | 42 | 44 | 45 | 46 | 48 | 49 | 50 | 52 | 54 | 56 | 57 | 58 | 59 | 60 | 64 | 65 | 66 | NewStarBlog 67 | 68 | 69 | 70 | 71 | 最新 73 | (current) 74 | 75 | 最热 77 | 78 | 79 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 93 | 94 | 个人主页 个人设置 98 | 99 | 100 | 101 | 写博客 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 登录 注册 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 共[[${page.totalElements}]]条 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | « 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | » 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 共[[${page.totalElements}]]条 48 | 49 | 50 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | « 59 | 60 | 61 | 62 | 63 | 64 | 1 65 | 66 | 67 | 68 | 69 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ... 79 | 80 | 81 | 82 | 83 | 84 | 85 | ... 86 | 87 | 88 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | ... 100 | 101 | 102 | 103 | [[${page.number}]] 104 | 105 | 106 | [[${page.number + 1}]] 107 | 108 | 109 | [[${page.number + 2}]] 110 | 111 | 112 | 113 | 114 | ... 115 | 116 | 117 | 118 | 119 | 120 | [[${page.totalPages}]] 121 | 122 | 123 | 124 | 125 | 126 | » 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 29 | 30 | OAuth 33 | 2.0 认证的原理与实践 34 | 35 | 使用 OAuth 2.0 36 | 认证的的好处是显然易见的。你只需要用同一个账号密码,就能在各个网站进行访问, 37 | 而免去了在每个网站都进行注册的繁琐过程。 38 | 本文将介绍 OAuth 2.0 的原理,并基于 Spring Security 和 GitHub 账号, 39 | 来演示 OAuth2.0 的认证的过程。 40 | 41 | waylau 发表于 43 | [[${#dates.format(blog.createTime, 'yyyy-MM-dd HH:mm')}]] [[${blog.readSize}]] 45 | [[${blog.voteSize}]] 46 | [[${blog.commentSize}]] 47 | 48 | 49 | 50 | ... 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 热门标签 61 | 62 | 63 | 64 | 66 | Web Design 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 热门用户 81 | 82 | 83 | 84 | 85 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 热门文章 101 | 102 | 104 | [[*{title}]][[*{readSize}]] 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 最新发布 119 | 120 | 122 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | ... 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 请登录 12 | 13 | 14 | 账号 17 | 18 | 19 | 20 | 密码 23 | 24 | 25 | 记住我 26 | 27 | 28 | 登录 29 | 30 | 31 | 32 | 33 | 34 | 35 | ... 36 | 37 | -------------------------------------------------------------------------------- /src/main/resources/templates/register.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 注册成为博主 12 | 13 | 14 | 账号 17 | 18 | 19 | 20 | 邮箱 23 | 24 | 25 | 姓名 28 | 29 | 30 | 密码 33 | 34 | 35 | 提交 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ... 44 | 45 | -------------------------------------------------------------------------------- /src/main/resources/templates/users/add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 账号 12 | 13 | 14 | 15 | 邮箱 18 | 19 | 20 | 姓名 23 | 24 | 25 | 密码 29 | 30 | 31 | 角色 32 | 33 | 管理员 34 | 博主 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/resources/templates/users/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 账号 12 | 13 | 14 | 15 | 邮箱 18 | 19 | 20 | 姓名 23 | 24 | 25 | 密码 29 | 30 | 31 | 角色 32 | 33 | 管理员 34 | 博主 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/resources/templates/users/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ID 23 | 账号 24 | 姓名 25 | 邮箱 26 | 角色 27 | 操作 28 | 29 | 30 | 31 | 32 | 33 | 34 | 1 35 | 1 36 | waylau 37 | waylau 38 | waylau 39 | 40 | 41 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | ... 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 新增/编辑 68 | 70 | × 71 | 72 | 73 | 74 | 75 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/main/resources/templates/userspace/avatar.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 图片剪切 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Loading... 36 | 37 | 38 | 剪切 39 | 40 | 41 | 放大 42 | 缩小 44 | 45 | 46 | 47 | 48 | 49 | 50 | 66 | -------------------------------------------------------------------------------- /src/main/resources/templates/userspace/blog.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | OAuth 2.0 认证的原理与实践 33 | 34 | 35 | waylau 37 | 发表于 [[${#dates.format(blogModel.createTime, 'yyyy-MM-dd 38 | HH:mm')}]] [[${blogModel.readSize}]] 39 | [[${blogModel.voteSize}]] 40 | [[${blogModel.commentSize}]] 41 | 编辑 删除 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 分类: Spring Boot 61 | 62 | 63 | 64 | 标签: 67 | Web 68 | Design 69 | 70 | 71 | 72 | 73 | 74 | 评论: 75 | 76 | 77 | 79 | 80 | 81 | 发表评论 82 | 点赞 84 | 取消点赞 86 | 87 | 88 | 89 | 90 | 91 | 94 | 95 | 101 | 102 | 103 | 104 | 105 | waylau 107 | [[${commentStat.index} + 1]]楼 108 | [[${#dates.format(comment.createTime, 'yyyy-MM-dd HH:mm')}]] 113 | 不错哦,顶起! 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 文章目录 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | ... 147 | 148 | 149 | 150 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /src/main/resources/templates/userspace/blogedit.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 图片上传 52 | 53 | 54 | 55 | 56 | 57 | 58 | 60 | 插入 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 博客设置 72 | 73 | 74 | 75 | 76 | 77 | 标签: 79 | 80 | 81 | 82 | 83 | 84 | 85 | 分类: 88 | Java 92 | 93 | 94 | 95 | 96 | 97 | 发布 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | ... 116 | 117 | 118 | 121 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/main/resources/templates/userspace/catalogedit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 名称 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/templates/userspace/profile.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 个人设置 33 | 34 | 35 | 37 | 38 | 39 | 账号 42 | 43 | 44 | 45 | 邮箱 48 | 49 | 50 | 姓名 54 | 55 | 56 | 密码 60 | 61 | 62 | 保存 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 75 | 76 | 77 | 78 | 编辑头像 79 | 81 | × 82 | 83 | 84 | 85 | 86 | 92 | 93 | 94 | 95 | 96 | ... 97 | 98 | 99 | 102 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/test/java/com/waylau/spring/boot/blog/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.waylau.spring.boot.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 ApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/waylau/spring/boot/blog/controller/HelloControllerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.waylau.spring.boot.blog.controller; 5 | 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 15 | import static org.hamcrest.Matchers.equalTo; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 18 | /** 19 | * 20 | * @since 1.0.0 2017年7月1日 21 | * @author Way Lau 22 | */ 23 | @RunWith(SpringRunner.class) 24 | @SpringBootTest 25 | @AutoConfigureMockMvc 26 | public class HelloControllerTest { 27 | 28 | @Autowired 29 | private MockMvc mockMvc; 30 | 31 | @Test 32 | public void testHello() throws Exception { 33 | mockMvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON)) 34 | .andExpect(status().isOk()) 35 | .andExpect(content().string(equalTo("Hello World! Welcome to visit waylau.com!"))); 36 | } 37 | } --------------------------------------------------------------------------------
使用 OAuth 2.0 36 | 认证的的好处是显然易见的。你只需要用同一个账号密码,就能在各个网站进行访问, 37 | 而免去了在每个网站都进行注册的繁琐过程。 38 | 本文将介绍 OAuth 2.0 的原理,并基于 Spring Security 和 GitHub 账号, 39 | 来演示 OAuth2.0 的认证的过程。
不错哦,顶起!