diff --git a/.bowerrc b/.bowerrc index 4ba2034..950ad51 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,3 +1,4 @@ { - "directory": "src/main/resources/resources/bower_components" + "allow_root": true, + "directory": "src/main/resources/static/bower_components" } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7d53d77..bdd8046 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,7 @@ bin scripts classes out -production_app.properties *.iml *.ipr *.iws -src/main/resources/resources/bower_components +src/main/resources/static/bower_components diff --git a/README.md b/README.md index 852a10e..d521a7c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ SpringBlog ===== +中文开发和部署文档请查看:http://raysmond.com/posts/springblog-guide + SpringBlog is a very simple and clean-design blog system implemented with Spring Boot. -It's one of my learning projects to explore awesome features in Spring Boot web programming. I had put it on production -for my personal blog site [https://raysmond.com](https://raysmond.com) for about one year. Now, I turn to Hexo for my blog site. +It's one of my learning projects to explore awesome features in Spring Boot web programming. You can check my blog +site for demo [https://raysmond.com](http://raysmond.com). There's no demo online. Here's the screenshot of my previous blog homepage. -![](http://7b1fa0.com1.z0.glb.clouddn.com/screencapture-blog-raysmond-9000-1480663084590.png) +![](http://7b1fa0.com1.z0.glb.clouddn.com/screencapture-blog-raysmond-8080-1480663084590.png) SpringBlog is powered by many powerful frameworks and third-party projects: @@ -16,18 +18,15 @@ SpringBlog is powered by many powerful frameworks and third-party projects: - [Bootstrap](https://getbootstrap.com) - A very popular and responsive front-end framework - [Pegdown](https://github.com/sirthias/pegdown) - A pure-java markdown processor - [ACE Editor](http://ace.c9.io/) - A high performance code editor which I use to write posts and code. -- [Pygments](http://pygments.org/) - A python library for highlighting code syntax -- [Jade4j](https://github.com/neuland/jade4j) - [Jade](http://jade-lang.com/) is an elegant template language. -- [Webjars](http://www.webjars.org/) - A client-side web libraries packaged into JAR files. A easy way to manage JavaScript and CSS vendors in Gradle. - [Redis](http://redis.io/) - A very powerful in-memory data cache server. +- EhCache +- Thymeleaf (Spring MVC) ## Development Before development, please install the following service software: - [MySQL](https://www.mysql.com) -- [Redis](http://redis.io) -- [Pygments](http://pygments.org) Edit the spring config profile `src/main/resources/application.yml` according to your settings. @@ -41,18 +40,6 @@ apt-get install mysql-server service mysql start mysql -u root -p >> create database spring_blog; - - -# Install Python pygments -apt-get install python-pip -pip install pygments -``` - -``` -# If you want to enable redis cache -# Install redis server first, you can find instructions -# from https://www.digitalocean.com/community/tutorials/how-to-install-and-use-redis -service redis_6379 start ``` This is a Gradle project. Make sure Gradle is installed in your machine. @@ -68,32 +55,43 @@ I recommend you import the source code into Intellij IDE to edit the code. **How to import the project into Intellij IDEA and run from the IDE?** +``` +git clone https://github.com/Raysmond/SpringBlog.git +cd SpringBlog + +bower install +``` 1. Clone the project -`git clone https://github.com/Raysmond/SpringBlog.git ` 2. Download all dependencies -`cd SpringBlog ` -`./gradlew idea ` 3. Open the project in Intellij IDEA. -4. Run `Application.java` as Java application. -5. Preview: http://localhost:9000 - Admin: http://localhost:900/admin , the default admin account is: admin@admin.com, password: admin +4. Run `SpringBlogApplication.java` as Java application. +5. Preview: http://localhost:8080 + Admin: http://localhost:8080/admin , the default admin account is: `admin`, password: `admin` > Lombok is required to run the project. You can install the plugin in Intellij IDEA. > Reference: https://github.com/mplushnikov/lombok-intellij-plugin -## Deployment - - Build application jar `./gradlew build`, then upload the distribution jar (e.g. `build/libs/SpringBlog-0.1.jar`) to your remote server. - Upload `application-production.yml` to your server and change it according to your server settings. - Run it (Java8 is a must) ``` - # assuming you have the jar and yml files under current dir + java -jar SpringBlog-0.1.jar --spring.profiles.active=prod + # OR with external spring profile file java -jar SpringBlog-0.1.jar --spring.config.location=application-production.yml ``` +## TODO + +- [x] Upgrade frontend framework to Bootstrap4 +- [ ] Replace Jade with Thymeleaf(HTML) +- [ ] Frontend building tools, e.g. webpack +- [x] Use hibernate 2nd level cache (EHCache?) +- [ ] Markdown preview while editing +- [ ] Html editor + ## License -Modified BSD license. Copyright (c) 2015, Jiankun LEI (Raysmond). +Modified BSD license. Copyright (c) 2015 - 2018, Jiankun LEI (Raysmond). diff --git a/bower.json b/bower.json index 41e896c..2b4f287 100644 --- a/bower.json +++ b/bower.json @@ -1,17 +1,18 @@ { "name": "springblog", "version": "0.0.0", - "appPath": "src/main/resources", + "appPath": "src/main/resources/static", "testPath": "src/test/javascript/spec", "dependencies": { - "jquery": "2.1.4", + "jquery": "3.2.1", + "bootstrap": "4.0.0", + "fontawesome": "4.7.0", "marked": "0.3.5", - "simple-module": "2.0.6", - "simple-hotkeys": "1.0.3", - "simditor": "2.3.5", - "simditor-markdown": "1.1.2", "to-markdown": "1.3.0", - "adminlte": "2.3.6" + "highlightjs": "9.12.0", + "ace-builds": "1.2.9", + "contents": "4.0.2", + "sticky-kit": "1.1.2" }, "devDependencies": {} } diff --git a/build.gradle b/build.gradle index 9afef37..e9012dc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,86 +1,71 @@ apply plugin: 'java' apply plugin: 'idea' apply plugin: 'eclipse' -apply plugin: 'spring-boot' +apply plugin: 'org.springframework.boot' +group = 'com.raysmond.blog' +version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 -targetCompatibility = 1.8 - -version = '0.1' repositories { mavenLocal() jcenter() mavenCentral() - + maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } maven { url 'http://repo.spring.io/libs-release' } - maven { url "http://repo.springsource.org/repo" } + maven { url 'http://repo.springsource.org/repo' } } bootRun { systemProperties = System.properties } - -configurations { - compile.exclude module: "spring-boot-starter-tomcat" - all*.exclude module: 'spring-boot-starter-logging' +buildscript { + ext { + springBootVersion = '1.5.10.RELEASE' + } + repositories { + mavenLocal() + jcenter() + mavenCentral() + maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } + } + dependencies { + classpath('org.springframework.boot:spring-boot-gradle-plugin:1.5.10.RELEASE') + } } dependencies { // spring boot - compile "org.springframework.boot:spring-boot-starter-web:1.4.0.RELEASE" - compile "org.springframework.boot:spring-boot-starter-jetty:1.4.0.RELEASE" - compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.4.0.RELEASE" - compile "com.domingosuarez.boot:spring-boot-starter-jade4j:0.3.1" - compile "org.springframework.boot:spring-boot-starter-redis:1.4.0.RELEASE" - compile(group: 'org.springframework.boot', name: 'spring-boot-starter', version:'1.4.0.RELEASE') { - exclude(module: 'spring-boot-starter-logging') - } - compile 'org.springframework.boot:spring-boot-starter-log4j:1.3.7.RELEASE' - compile 'org.springframework.boot:spring-boot-starter-data-jpa:1.4.0.RELEASE' - compile 'org.springframework.boot:spring-boot-starter-security:1.4.0.RELEASE' - - // spring framework - compile 'org.springframework:spring-context:4.2.7.RELEASE' - compile 'org.springframework:spring-webmvc:4.2.7.RELEASE' - compile 'org.springframework.security:spring-security-config:3.2.7.RELEASE' - compile 'org.springframework.security:spring-security-web:3.2.7.RELEASE' - - //persistence - compile 'com.zaxxer:HikariCP:+' - compile 'org.springframework:spring-orm:4.2.7.RELEASE' - compile 'org.hibernate:hibernate-entitymanager:4.3.11.Final' - compile 'javax.el:javax.el-api:+' - compile 'org.hsqldb:hsqldb:+' + compile 'org.springframework.boot:spring-boot-starter-web' + compile 'org.springframework.boot:spring-boot-starter-thymeleaf' + compile 'com.domingosuarez.boot:spring-boot-starter-jade4j:0.3.1' + compile 'org.springframework.boot:spring-boot-starter-redis:1.4.7.RELEASE' + compile 'org.springframework.boot:spring-boot-starter-data-jpa' + compile 'org.springframework.boot:spring-boot-starter-security' + compile 'org.springframework.boot:spring-boot-starter-logging' + compile 'org.springframework.boot:spring-boot-devtools' + compile 'org.springframework.boot:spring-boot-starter-actuator' + compile 'org.springframework.boot:spring-boot-starter-cache' // view + compile 'net.sourceforge.nekohtml:nekohtml:1.9.22' + compile 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2+' compile 'de.neuland-bfi:spring-jade4j:0.4.2' compile 'org.pegdown:pegdown:1.6.0' + compile 'com.vladsch.flexmark:flexmark-all:0.28.38' // python compile 'org.python:jython-standalone:2.7.0' compile 'org.pygments:pygments:2.0.2' - // spring data - compile 'org.springframework.data:spring-data-jpa:1.10.0.RELEASE' - - // cache - compile 'org.springframework.data:spring-data-redis:1.7.0.RELEASE' - compile 'redis.clients:jedis:2.7.3' - - // MySQL + // mysql/hibernate compile 'mysql:mysql-connector-java:+' - - // Validation - compile 'org.hibernate:hibernate-validator:4.3.2.Final' - - // Logging - compile 'ch.qos.logback:logback-classic:1.1.3' - compile 'org.slf4j:slf4j-api:+' - compile 'org.apache.commons:commons-lang3:+' - - // @Inject - compile 'javax.inject:javax.inject:+' + compile 'org.hibernate:hibernate-core:5.2.12.Final' + compile 'org.hibernate:hibernate-entitymanager:5.2.12.Final' + compile 'org.hibernate:hibernate-validator' + compile 'org.hibernate:hibernate-ehcache:5.2.12.Final' + compile 'com.zaxxer:HikariCP:2.7.6' + compile 'net.sf.ehcache:ehcache' // JSON compile 'com.fasterxml.jackson.core:jackson-databind:2.4.1.3' @@ -88,45 +73,13 @@ dependencies { // Utilities compile 'com.google.guava:guava:+' - compile 'org.modelmapper:modelmapper:0.7.5' - compile 'org.projectlombok:lombok:1.16.6' - //compile 'com.qiniu:qiniu-java-sdk:7.0.+' - - // static resources, ref. http://www.webjars.org/ - compile 'org.webjars:jquery:2.1.4' - compile 'org.webjars:bootstrap:3.3.5' - compile 'org.webjars:font-awesome:4.3.0-3' - compile 'org.webjars:ace:1.2.0' + compile 'org.modelmapper:modelmapper:+' + compile 'org.projectlombok:lombok:+' + compile 'org.apache.commons:commons-lang3:+' // test - testCompile("org.springframework.boot:spring-boot-starter-test:1.4.0.RELEASE") testCompile 'junit:junit:+' - testCompile 'org.mockito:mockito-core:+' - testCompile 'org.assertj:assertj-core:+' - testCompile 'org.hamcrest:hamcrest-core:+' - testCompile 'org.hamcrest:hamcrest-library:+' - testCompile 'org.objenesis:objenesis:+' - testCompile 'org.springframework:spring-test:+' - + testCompile('org.springframework.boot:spring-boot-starter-test') + testCompile('org.springframework.security:spring-security-test') } - -buildscript { - ext { - springBootVersion = '1.2.6.RELEASE' - } - - repositories { - mavenLocal() - jcenter() - mavenCentral() - - maven { url "http://repo.spring.io/release" } - maven { url "http://repo.spring.io/milestone" } - // maven { url "http://repo.spring.io/snapshot" } - } - - dependencies { - classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" - } -} diff --git a/docker-compose.yml b/docker-compose.yml index 0e1d837..3ba529a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,22 +1,23 @@ -version: '2' +version: '3' services: mysql: image: mysql - hostname: mysql environment: - MYSQL_USER=root - - MYSQL_DATABASE=spring_blog + - MYSQL_DATABASE=spring_blog_prod - MYSQL_ROOT_PASSWORD=123456 webapp: image: java:8 - links: - - mysql:mysql - # - redis:redis + depends_on: + - mysql + - redis volumes: - - ./build/libs/SpringBlog-0.1.jar:/app/app.jar + - ./application-prod.yml:/app/application-prod.yml + - ./build/libs/SpringBlog-0.0.1-SNAPSHOT.jar:/app/app.jar ports: - - "9000:9000" - command: "java -jar /app/app.jar --spring.profiles.active=dev" + - "9000:8080" +# command: "java -jar /app/app.jar --spring.profiles.active=prod --spring.config.location=/app/application-prod.yml" + command: "java -jar /app/app.jar --spring.profiles.active=prod" -# redis: -# image: redis + redis: + image: redis diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index fd7e590..1a958be 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 95d77ba..56155af 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Sep 27 13:55:48 CST 2015 +#Thu Jan 18 22:04:34 CST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-all.zip diff --git a/gradlew b/gradlew index 91a7e26..4453cce 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec9973..e95643d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 160ae68..0000000 --- a/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'SpringBlog' \ No newline at end of file diff --git a/src/main/java/com/raysmond/blog/CacheConfiguration.java b/src/main/java/com/raysmond/blog/CacheConfiguration.java new file mode 100644 index 0000000..7c07443 --- /dev/null +++ b/src/main/java/com/raysmond/blog/CacheConfiguration.java @@ -0,0 +1,28 @@ +package com.raysmond.blog; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.ehcache.EhCacheCacheManager; +import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +@Configuration +@EnableCaching +public class CacheConfiguration { + @Bean + public EhCacheManagerFactoryBean ehCacheManagerFactory() { + EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean(); + cacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml")); + cacheManagerFactoryBean.setShared(true); + return cacheManagerFactoryBean; + } + + @Bean + public EhCacheCacheManager ehCacheCacheManager() { + EhCacheCacheManager cacheManager = new EhCacheCacheManager(); + cacheManager.setCacheManager(ehCacheManagerFactory().getObject()); + cacheManager.setTransactionAware(true); + return cacheManager; + } +} \ No newline at end of file diff --git a/src/main/java/com/raysmond/blog/Constants.java b/src/main/java/com/raysmond/blog/Constants.java index 3120247..8436e0f 100644 --- a/src/main/java/com/raysmond/blog/Constants.java +++ b/src/main/java/com/raysmond/blog/Constants.java @@ -1,15 +1,15 @@ package com.raysmond.blog; /** - * @author: Raysmond + * @author: Raysmond */ public final class Constants { - public static final String ENV_PRODUCTION = "production"; + public static final String ENV_PRODUCTION = "prod"; - public static final String ENV_DEVELOPMENT = "development"; + public static final String ENV_DEVELOPMENT = "dev"; - public static final String DEFAULT_ADMIN_EMAIL = "admin@admin.com"; + public static final String DEFAULT_ADMIN_EMAIL = "admin"; public static final String DEFAULT_ADMIN_PASSWORD = "admin"; diff --git a/src/main/java/com/raysmond/blog/JpaConfig.java b/src/main/java/com/raysmond/blog/JpaConfig.java deleted file mode 100644 index 820c918..0000000 --- a/src/main/java/com/raysmond/blog/JpaConfig.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.raysmond.blog; - -import java.util.Properties; - -import javax.sql.DataSource; - -import com.raysmond.blog.Application; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.annotation.EnableTransactionManagement; -import org.springframework.transaction.annotation.TransactionManagementConfigurer; - -@Configuration -@EnableTransactionManagement -@EnableJpaRepositories(basePackageClasses = Application.class) -class JpaConfig implements TransactionManagementConfigurer { - - @Value("${spring.dataSource.driverClassName}") - private String driver; - @Value("${spring.dataSource.url}") - private String url; - @Value("${spring.dataSource.username}") - private String username; - @Value("${spring.dataSource.password}") - private String password; - @Value("${spring.hibernate.dialect}") - private String dialect; - @Value("${spring.hibernate.hbm2ddl.auto}") - private String hbm2ddlAuto; - @Value("${spring.hibernate.show_sql}") - private Boolean showSql; - - @Bean - public DataSource configureDataSource() { - HikariConfig config = new HikariConfig(); - config.setDriverClassName(driver); - config.setJdbcUrl(url); - config.setUsername(username); - config.setPassword(password); - - config.addDataSourceProperty("useUnicode", "true"); - config.addDataSourceProperty("characterEncoding", "utf8"); - config.addDataSourceProperty("cachePrepStmts", "true"); - config.addDataSourceProperty("prepStmtCacheSize", "250"); - config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); - config.addDataSourceProperty("useServerPrepStmts", "true"); - - return new HikariDataSource(config); - } - - @Bean - public LocalContainerEntityManagerFactoryBean entityManagerFactory() { - LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); - entityManagerFactoryBean.setDataSource(configureDataSource()); - entityManagerFactoryBean.setPackagesToScan("com.raysmond.blog"); - entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); - - Properties jpaProperties = new Properties(); - jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, dialect); - jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, hbm2ddlAuto); - jpaProperties.put(org.hibernate.cfg.Environment.SHOW_SQL, showSql); - entityManagerFactoryBean.setJpaProperties(jpaProperties); - - return entityManagerFactoryBean; - } - - @Bean(name = "transactionManager") - public PlatformTransactionManager annotationDrivenTransactionManager() { - return new JpaTransactionManager(); - } -} diff --git a/src/main/java/com/raysmond/blog/SecurityConfig.java b/src/main/java/com/raysmond/blog/SecurityConfig.java index c2bcceb..95ee67e 100644 --- a/src/main/java/com/raysmond/blog/SecurityConfig.java +++ b/src/main/java/com/raysmond/blog/SecurityConfig.java @@ -1,7 +1,9 @@ package com.raysmond.blog; import com.raysmond.blog.services.UserService; -import org.springframework.context.annotation.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @@ -11,49 +13,46 @@ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { - - @Bean - public UserService userService() { - return new UserService(); - } + @Autowired + private UserService userService; @Bean public TokenBasedRememberMeServices rememberMeServices() { - return new TokenBasedRememberMeServices("remember-me-key", userService()); + return new TokenBasedRememberMeServices("remember-me-key", userService); } @Bean public PasswordEncoder passwordEncoder() { return new StandardPasswordEncoder(); - } + } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth - .eraseCredentials(true) - .userDetailsService(userService()) - .passwordEncoder(passwordEncoder()); + .eraseCredentials(true) + .userDetailsService(userService) + .passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http - .authorizeRequests() + .authorizeRequests() .antMatchers("/admin/**").authenticated() .anyRequest().permitAll() .and() - .formLogin() - .loginPage("/signin") + .formLogin() + .loginPage("/login") .permitAll() - .failureUrl("/signin?error=1") + .failureUrl("/login?error=1") .loginProcessingUrl("/authenticate") .and() - .logout() + .logout() .logoutUrl("/logout") .permitAll() - .logoutSuccessUrl("/signin?logout") + .logoutSuccessUrl("/login?logout") .and() - .rememberMe() + .rememberMe() .rememberMeServices(rememberMeServices()) .key("remember-me-key"); } diff --git a/src/main/java/com/raysmond/blog/Application.java b/src/main/java/com/raysmond/blog/SpringBlogApplication.java similarity index 50% rename from src/main/java/com/raysmond/blog/Application.java rename to src/main/java/com/raysmond/blog/SpringBlogApplication.java index e6cb3fc..a6a3892 100644 --- a/src/main/java/com/raysmond/blog/Application.java +++ b/src/main/java/com/raysmond/blog/SpringBlogApplication.java @@ -1,18 +1,18 @@ package com.raysmond.blog; +import com.google.common.collect.ImmutableMap; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; -/** - * @author Raysmond - */ + @SpringBootApplication -// 开启缓存请把下行取消注释 //@EnableCaching -public class Application { +public class SpringBlogApplication { public static void main(String[] args) { - SpringApplication.run(Application.class, args); + SpringApplication app = new SpringApplication(SpringBlogApplication.class); + app.setDefaultProperties(ImmutableMap.of("spring.profiles.default", Constants.ENV_DEVELOPMENT)); + app.run(args); } } diff --git a/src/main/java/com/raysmond/blog/WebConfig.java b/src/main/java/com/raysmond/blog/WebConfig.java index e56e183..ee41c1a 100644 --- a/src/main/java/com/raysmond/blog/WebConfig.java +++ b/src/main/java/com/raysmond/blog/WebConfig.java @@ -1,6 +1,7 @@ package com.raysmond.blog; import com.raysmond.blog.support.web.ViewHelper; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -8,6 +9,7 @@ import org.springframework.security.web.csrf.CsrfToken; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; @@ -16,12 +18,16 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import static com.raysmond.blog.Constants.*; +import static com.raysmond.blog.Constants.ENV_DEVELOPMENT; +import static com.raysmond.blog.Constants.ENV_PRODUCTION; + +import lombok.extern.slf4j.Slf4j; /** - * @author Raysmond. + * @author Raysmond . */ @Configuration +@Slf4j public class WebConfig extends WebMvcConfigurerAdapter { @Autowired private ViewHelper viewHelper; @@ -35,8 +41,21 @@ public void addInterceptors(InterceptorRegistry registry) { super.addInterceptors(registry); } + @Override + public void addCorsMappings(CorsRegistry registry) { + if (env.acceptsProfiles(ENV_DEVELOPMENT)) { + log.debug("Register CORS configuration"); + registry.addMapping("/api/**") + .allowedOrigins("http://localhost:8080") + .allowedMethods("*") + .allowedHeaders("*") + .allowCredentials(true) + .maxAge(3600); + } + } + @PostConstruct - public void registerJadeViewHelpers(){ + public void registerJadeViewHelpers() { viewHelper.setApplicationEnv(this.getApplicationEnv()); } @@ -44,14 +63,16 @@ public void registerJadeViewHelpers(){ public HandlerInterceptor viewObjectAddingInterceptor() { return new HandlerInterceptorAdapter() { @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { viewHelper.setStartTime(System.currentTimeMillis()); return true; } @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView view) { + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, + ModelAndView view) { CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); if (token != null) { view.addObject(token.getParameterName(), token); @@ -60,7 +81,7 @@ public void postHandle(HttpServletRequest request, HttpServletResponse response, }; } - public String getApplicationEnv(){ + public String getApplicationEnv() { return this.env.acceptsProfiles(ENV_PRODUCTION) ? ENV_PRODUCTION : ENV_DEVELOPMENT; } } diff --git a/src/main/java/com/raysmond/blog/admin/controllers/AdminController.java b/src/main/java/com/raysmond/blog/admin/controllers/AdminController.java index b1d2037..2f8f072 100644 --- a/src/main/java/com/raysmond/blog/admin/controllers/AdminController.java +++ b/src/main/java/com/raysmond/blog/admin/controllers/AdminController.java @@ -4,18 +4,20 @@ import com.raysmond.blog.services.AppSetting; import com.raysmond.blog.support.web.MessageHelper; import com.raysmond.blog.utils.DTOUtil; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.validation.Valid; /** - * @author Raysmond + * @author Raysmond */ @Controller @RequestMapping("admin") @@ -24,26 +26,26 @@ public class AdminController { private AppSetting appSetting; @Autowired - public AdminController( AppSetting appSetting){ + public AdminController(AppSetting appSetting) { this.appSetting = appSetting; } - @RequestMapping("") - public String index(){ + @GetMapping("") + public String index() { return "admin/home/index"; } - @RequestMapping(value = "settings") - public String settings(Model model){ + @GetMapping(value = "settings") + public String settings(Model model) { SettingsForm settingsForm = DTOUtil.map(appSetting, SettingsForm.class); model.addAttribute("settings", settingsForm); return "admin/home/settings"; } - @RequestMapping(value = "settings", method = RequestMethod.POST) - public String updateSettings(@Valid SettingsForm settingsForm, Errors errors, Model model, RedirectAttributes ra){ - if (errors.hasErrors()){ + @PostMapping(value = "settings") + public String updateSettings(@Valid SettingsForm settingsForm, Errors errors, RedirectAttributes ra) { + if (errors.hasErrors()) { return "admin/settings"; } else { appSetting.setSiteName(settingsForm.getSiteName()); diff --git a/src/main/java/com/raysmond/blog/admin/controllers/PostController.java b/src/main/java/com/raysmond/blog/admin/controllers/PostController.java index 8ad6aa7..ee3482d 100644 --- a/src/main/java/com/raysmond/blog/admin/controllers/PostController.java +++ b/src/main/java/com/raysmond/blog/admin/controllers/PostController.java @@ -2,16 +2,13 @@ import com.raysmond.blog.forms.PostForm; import com.raysmond.blog.models.Post; -import com.raysmond.blog.models.Tag; -import com.raysmond.blog.models.User; import com.raysmond.blog.models.support.PostFormat; import com.raysmond.blog.models.support.PostStatus; +import com.raysmond.blog.models.support.PostType; import com.raysmond.blog.repositories.PostRepository; import com.raysmond.blog.repositories.UserRepository; import com.raysmond.blog.services.PostService; -import com.raysmond.blog.services.TagService; import com.raysmond.blog.utils.DTOUtil; -import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -19,38 +16,33 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; + import javax.validation.Valid; import java.security.Principal; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; import static org.springframework.web.bind.annotation.RequestMethod.*; /** - * @author Raysmond + * @author Raysmond */ @Controller("adminPostController") @RequestMapping("admin/posts") public class PostController { + private static final int PAGE_SIZE = 20; @Autowired private PostRepository postRepository; - @Autowired private PostService postService; - @Autowired private UserRepository userRepository; - private static final int PAGE_SIZE = 20; - - @RequestMapping(value = "") - public String index(@RequestParam(defaultValue = "0") int page, Model model){ + @GetMapping("") + public String index(@RequestParam(defaultValue = "0") int page, Model model) { Page posts = postRepository.findAll(new PageRequest(page, PAGE_SIZE, Sort.Direction.DESC, "id")); model.addAttribute("totalPages", posts.getTotalPages()); @@ -60,20 +52,21 @@ public String index(@RequestParam(defaultValue = "0") int page, Model model){ return "admin/posts/index"; } - @RequestMapping(value = "new") - public String newPost(Model model){ + @GetMapping("new") + public String newPost(Model model) { PostForm postForm = DTOUtil.map(new Post(), PostForm.class); postForm.setPostTags(""); model.addAttribute("postForm", postForm); model.addAttribute("postFormats", PostFormat.values()); + model.addAttribute("postTypes", PostType.values()); model.addAttribute("postStatus", PostStatus.values()); return "admin/posts/new"; } @RequestMapping(value = "{postId:[0-9]+}/edit") - public String editPost(@PathVariable Long postId, Model model){ + public String editPost(@PathVariable Long postId, Model model) { Post post = postRepository.findOne(postId); PostForm postForm = DTOUtil.map(post, PostForm.class); @@ -82,19 +75,20 @@ public String editPost(@PathVariable Long postId, Model model){ model.addAttribute("post", post); model.addAttribute("postForm", postForm); model.addAttribute("postFormats", PostFormat.values()); + model.addAttribute("postTypes", PostType.values()); model.addAttribute("postStatus", PostStatus.values()); return "admin/posts/edit"; } @RequestMapping(value = "{postId:[0-9]+}/delete", method = {DELETE, POST}) - public String deletePost(@PathVariable Long postId){ + public String deletePost(@PathVariable Long postId) { postService.deletePost(postRepository.findOne(postId)); return "redirect:/admin/posts"; } @RequestMapping(value = "", method = POST) - public String create(Principal principal, @Valid PostForm postForm, Errors errors, Model model){ + public String create(Principal principal, @Valid PostForm postForm, Errors errors, Model model) { if (errors.hasErrors()) { model.addAttribute("postFormats", PostFormat.values()); model.addAttribute("postStatus", PostStatus.values()); @@ -112,8 +106,8 @@ public String create(Principal principal, @Valid PostForm postForm, Errors error } @RequestMapping(value = "{postId:[0-9]+}", method = {PUT, POST}) - public String update(@PathVariable Long postId, @Valid PostForm postForm, Errors errors, Model model){ - if (errors.hasErrors()){ + public String update(@PathVariable Long postId, @Valid PostForm postForm, Errors errors, Model model) { + if (errors.hasErrors()) { model.addAttribute("postFormats", PostFormat.values()); model.addAttribute("postStatus", PostStatus.values()); diff --git a/src/main/java/com/raysmond/blog/admin/controllers/UserController.java b/src/main/java/com/raysmond/blog/admin/controllers/UserController.java index 172ad6e..c7b6c48 100644 --- a/src/main/java/com/raysmond/blog/admin/controllers/UserController.java +++ b/src/main/java/com/raysmond/blog/admin/controllers/UserController.java @@ -1,11 +1,11 @@ package com.raysmond.blog.admin.controllers; +import com.raysmond.blog.error.NotFoundException; import com.raysmond.blog.forms.UserForm; import com.raysmond.blog.models.User; import com.raysmond.blog.repositories.UserRepository; import com.raysmond.blog.services.UserService; import com.raysmond.blog.support.web.MessageHelper; -import com.raysmond.blog.utils.DTOUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -17,10 +17,10 @@ import javax.validation.Valid; -import static org.springframework.web.bind.annotation.RequestMethod.*; +import static org.springframework.web.bind.annotation.RequestMethod.POST; /** - * @author Raysmond. + * @author Raysmond */ @Controller("adminUserController") @RequestMapping("admin/users") @@ -30,30 +30,33 @@ public class UserController { private UserRepository userRepository; @Autowired - public UserController(UserService userService, UserRepository userRepository){ + public UserController(UserService userService, UserRepository userRepository) { this.userService = userService; this.userRepository = userRepository; } @RequestMapping("profile") - public String profile(Model model){ + public String profile(Model model) { model.addAttribute("user", userService.currentUser()); return "admin/users/profile"; } @RequestMapping(value = "{userId:[0-9]+}", method = POST) - public String update(@PathVariable Long userId, @Valid UserForm userForm, Errors errors, RedirectAttributes ra){ + public String update(@PathVariable Long userId, @Valid UserForm userForm, Errors errors, RedirectAttributes ra) { User user = userRepository.findOne(userId); - Assert.notNull(user); - if (errors.hasErrors()){ + if (null == user) { + throw new NotFoundException("User " + userId + " is not found."); + } + + if (errors.hasErrors()) { // do something return "admin/users/profile"; } - if (!userForm.getNewPassword().isEmpty()){ + if (!userForm.getNewPassword().isEmpty()) { if (!userService.changePassword(user, userForm.getPassword(), userForm.getNewPassword())) MessageHelper.addErrorAttribute(ra, "Change password failed."); diff --git a/src/main/java/com/raysmond/blog/controllers/HomeController.java b/src/main/java/com/raysmond/blog/controllers/HomeController.java index 5278d37..eacc8a2 100644 --- a/src/main/java/com/raysmond/blog/controllers/HomeController.java +++ b/src/main/java/com/raysmond/blog/controllers/HomeController.java @@ -1,6 +1,5 @@ package com.raysmond.blog.controllers; -import com.raysmond.blog.Constants; import com.raysmond.blog.models.Post; import com.raysmond.blog.services.AppSetting; import com.raysmond.blog.services.PostService; @@ -8,9 +7,8 @@ import org.springframework.data.domain.Page; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.*; - -import static org.springframework.web.bind.annotation.RequestMethod.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; @Controller public class HomeController { @@ -21,7 +19,7 @@ public class HomeController { @Autowired private AppSetting appSetting; - @RequestMapping(value = "", method = GET) + @GetMapping(value = "") public String index(@RequestParam(defaultValue = "1") int page, Model model) { page = page < 1 ? 0 : page - 1; Page posts = postService.getAllPublishedPostsByPage(page, appSetting.getPageSize()); @@ -29,20 +27,6 @@ public String index(@RequestParam(defaultValue = "1") int page, Model model) { model.addAttribute("totalPages", posts.getTotalPages()); model.addAttribute("posts", posts); model.addAttribute("page", page + 1); - - return "home/index"; + return "home/home"; } - - @RequestMapping(value = "about", method = GET) - public String about(Model model) { - Post post = postService.getPublishedPostByPermalink(Constants.ABOUT_PAGE_PERMALINK); - - if (post == null) { - post = postService.createAboutPage(); - } - - model.addAttribute("about", post); - return "home/about"; - } - } diff --git a/src/main/java/com/raysmond/blog/controllers/PostController.java b/src/main/java/com/raysmond/blog/controllers/PostController.java index 6929788..97d31c6 100644 --- a/src/main/java/com/raysmond/blog/controllers/PostController.java +++ b/src/main/java/com/raysmond/blog/controllers/PostController.java @@ -1,55 +1,78 @@ package com.raysmond.blog.controllers; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.raysmond.blog.error.NotFoundException; import com.raysmond.blog.models.Post; +import com.raysmond.blog.models.support.PostType; import com.raysmond.blog.services.PostService; -import com.raysmond.blog.services.TagService; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import java.util.List; -import java.util.Set; - -import static org.springframework.web.bind.annotation.RequestMethod.*; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import java.util.List; +import java.util.Map; /** - * @author Raysmond + * @author Raysmond */ @Controller -@RequestMapping("posts") public class PostController { @Autowired private PostService postService; - @RequestMapping(value = "archive", method = GET) - public String archive(Model model){ - model.addAttribute("posts", postService.getArchivePosts()); - + @GetMapping(value = "posts/archive") + public String archive(Model model) { + Map> posts = Maps.newHashMap(); + postService.getArchivePosts().forEach(post -> { + if (!posts.containsKey(post.getCreatedAt().getYear())) { + posts.put(post.getCreatedAt().getYear(), Lists.newArrayList()); + } + posts.get(post.getCreatedAt().getYear()).add(post); + }); + model.addAttribute("posts", posts); return "posts/archive"; } - @RequestMapping(value = "{permalink}", method = GET) - public String show(@PathVariable String permalink, Model model){ - Post post = null; + @GetMapping(value = "posts/{permalink}") + public String show(@PathVariable String permalink, Model model) { + return showPost(permalink, model, PostType.POST); + } + + @GetMapping(value = "{permalink}") + public String page(@PathVariable String permalink, Model model) { + return showPost(permalink, model, PostType.PAGE); + } + + private String showPost(String permalink, Model model, PostType postType) { + Post post; - try{ + try { post = postService.getPublishedPostByPermalink(permalink); - } catch (NotFoundException ex){ - if (permalink.matches("\\d+")) + } catch (NotFoundException ex) { + if (permalink.matches("\\d+") && postType.equals(PostType.POST)) { post = postService.getPost(Long.valueOf(permalink)); + } else { + throw new NotFoundException(); + } } - if (post == null) - throw new NotFoundException("Post with permalink " + permalink + " is not found"); + if (!post.getPostType().equals(postType)) { + throw new NotFoundException(); + } + + postService.incrementViews(post.getId()); + model.addAttribute("postType", postType.name()); model.addAttribute("post", post); model.addAttribute("tags", postService.getPostTags(post)); - - return "posts/show"; + return "posts/post"; } } diff --git a/src/main/java/com/raysmond/blog/controllers/TagController.java b/src/main/java/com/raysmond/blog/controllers/TagController.java index a9bbe7f..eb13eda 100644 --- a/src/main/java/com/raysmond/blog/controllers/TagController.java +++ b/src/main/java/com/raysmond/blog/controllers/TagController.java @@ -3,8 +3,6 @@ import com.raysmond.blog.error.NotFoundException; import com.raysmond.blog.models.Post; import com.raysmond.blog.models.Tag; -import com.raysmond.blog.repositories.PostRepository; -import com.raysmond.blog.repositories.TagRepository; import com.raysmond.blog.services.AppSetting; import com.raysmond.blog.services.PostService; import com.raysmond.blog.services.TagService; @@ -14,14 +12,12 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import static org.springframework.web.bind.annotation.RequestMethod.*; import org.springframework.web.bind.annotation.RequestParam; -import java.util.List; -import java.util.Map; +import static org.springframework.web.bind.annotation.RequestMethod.GET; /** - * @author Raysmond. + * @author Raysmond */ @Controller @RequestMapping("tags") @@ -36,7 +32,7 @@ public class TagController { private AppSetting appSetting; @RequestMapping(value = "", method = GET) - public String index(Model model){ + public String index(Model model) { model.addAttribute("tags", postService.countPostsByTags()); return "tags/index"; } diff --git a/src/main/java/com/raysmond/blog/controllers/UserController.java b/src/main/java/com/raysmond/blog/controllers/UserController.java index ba4f27a..4675a45 100644 --- a/src/main/java/com/raysmond/blog/controllers/UserController.java +++ b/src/main/java/com/raysmond/blog/controllers/UserController.java @@ -1,6 +1,5 @@ package com.raysmond.blog.controllers; -import com.raysmond.blog.support.web.MessageHelper; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.mvc.support.RedirectAttributes; @@ -8,13 +7,15 @@ import java.security.Principal; /** - * @author Raysmond + * User pages + * + * @author Raysmond */ @Controller public class UserController { - @RequestMapping("signin") + @RequestMapping("login") public String signin(Principal principal, RedirectAttributes ra) { - return principal == null ? "users/signin" : "redirect:/"; + return principal == null ? "users/login" : "redirect:/"; } } \ No newline at end of file diff --git a/src/main/java/com/raysmond/blog/error/ExceptionHandlerController.java b/src/main/java/com/raysmond/blog/error/ExceptionHandlerController.java index 1ee42c7..da06617 100644 --- a/src/main/java/com/raysmond/blog/error/ExceptionHandlerController.java +++ b/src/main/java/com/raysmond/blog/error/ExceptionHandlerController.java @@ -1,16 +1,11 @@ package com.raysmond.blog.error; -import com.raysmond.blog.services.AppSetting; -import com.raysmond.blog.support.web.ViewHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; +import com.google.common.base.Throwables; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; - -import com.google.common.base.Throwables; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; @@ -19,39 +14,39 @@ * General error handler for the application. */ @ControllerAdvice +@Slf4j class ExceptionHandlerController { - private static final Logger logger = LoggerFactory.getLogger(ExceptionHandlerController.class); - - @ResponseStatus(HttpStatus.NOT_FOUND) - @ExceptionHandler(NotFoundException.class) - public ModelAndView notFound(HttpServletRequest request, NotFoundException exception){ - String uri = request.getRequestURI(); - logger.error("Request page: " + uri + " raised NotFoundException : " + exception); - - ModelAndView model = new ModelAndView("error/general"); - model.addObject("status", HttpStatus.NOT_FOUND.value()); - model.addObject("error", HttpStatus.NOT_FOUND.getReasonPhrase()); - model.addObject("path", uri); - model.addObject("customMessage", exception.getMessage()); - - return model; - } - - /** - * Handle all exceptions - */ + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(NotFoundException.class) + public ModelAndView notFound(HttpServletRequest request, NotFoundException exception) { + String uri = request.getRequestURI(); + + log.error("Request page: {} raised NotFoundException {}", uri, exception); + + ModelAndView model = new ModelAndView("error/general"); + model.addObject("status", HttpStatus.NOT_FOUND.value()); + model.addObject("error", HttpStatus.NOT_FOUND.getReasonPhrase()); + model.addObject("path", uri); + model.addObject("customMessage", exception.getMessage()); + + return model; + } + + /** + * Handle all exceptions + */ // @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) - @ExceptionHandler(Exception.class) - public ModelAndView exception(HttpServletRequest request, Exception exception) { - String uri = request.getRequestURI(); - logger.error("Request page: " + uri + " raised exception : " + exception); - - ModelAndView model = new ModelAndView("error/general"); - model.addObject("error", Throwables.getRootCause(exception).getMessage()); - model.addObject("status", Throwables.getRootCause(exception).getCause()); - model.addObject("path", uri); - model.addObject("customMessage", exception.getMessage()); - - return model; - } + @ExceptionHandler(Exception.class) + public ModelAndView exception(HttpServletRequest request, Exception exception) { + String uri = request.getRequestURI(); + log.error("Request page: {} raised exception {}", uri, exception); + + ModelAndView model = new ModelAndView("error/general"); + model.addObject("error", Throwables.getRootCause(exception).getMessage()); + model.addObject("status", Throwables.getRootCause(exception).getCause()); + model.addObject("path", uri); + model.addObject("customMessage", exception.getMessage()); + + return model; + } } \ No newline at end of file diff --git a/src/main/java/com/raysmond/blog/error/NotFoundException.java b/src/main/java/com/raysmond/blog/error/NotFoundException.java index cd34454..1531242 100644 --- a/src/main/java/com/raysmond/blog/error/NotFoundException.java +++ b/src/main/java/com/raysmond/blog/error/NotFoundException.java @@ -4,22 +4,22 @@ import org.springframework.web.bind.annotation.ResponseStatus; /** - * @author Raysmond + * @author Raysmond */ @ResponseStatus(value = HttpStatus.NOT_FOUND) public final class NotFoundException extends RuntimeException { private String message; - public NotFoundException(){ + public NotFoundException() { } - public NotFoundException(String message){ + public NotFoundException(String message) { this.message = message; } @Override - public String getMessage(){ + public String getMessage() { return message; } } \ No newline at end of file diff --git a/src/main/java/com/raysmond/blog/forms/PostForm.java b/src/main/java/com/raysmond/blog/forms/PostForm.java index 56b1b9f..fca6ed0 100644 --- a/src/main/java/com/raysmond/blog/forms/PostForm.java +++ b/src/main/java/com/raysmond/blog/forms/PostForm.java @@ -1,16 +1,17 @@ package com.raysmond.blog.forms; -import com.raysmond.blog.models.Tag; import com.raysmond.blog.models.support.PostFormat; import com.raysmond.blog.models.support.PostStatus; +import com.raysmond.blog.models.support.PostType; + import lombok.Data; + import org.hibernate.validator.constraints.NotEmpty; import javax.validation.constraints.NotNull; -import java.util.Set; /** - * @author Raysmond + * @author Raysmond */ @Data public class PostForm { @@ -20,6 +21,8 @@ public class PostForm { @NotEmpty private String content; + private String summary; + @NotNull private PostFormat postFormat; @@ -32,4 +35,7 @@ public class PostForm { @NotNull private String postTags; + @NotNull + private PostType postType; + } diff --git a/src/main/java/com/raysmond/blog/forms/SettingsForm.java b/src/main/java/com/raysmond/blog/forms/SettingsForm.java index 5c5a868..67ce9f3 100644 --- a/src/main/java/com/raysmond/blog/forms/SettingsForm.java +++ b/src/main/java/com/raysmond/blog/forms/SettingsForm.java @@ -1,12 +1,13 @@ package com.raysmond.blog.forms; import lombok.Data; + import org.hibernate.validator.constraints.NotEmpty; import javax.validation.constraints.NotNull; /** - * @author Raysmond + * @author Raysmond */ @Data public class SettingsForm { @@ -20,4 +21,8 @@ public class SettingsForm { @NotNull private Integer pageSize; + private String intro; + + private String pictureUrl; + } diff --git a/src/main/java/com/raysmond/blog/forms/UserForm.java b/src/main/java/com/raysmond/blog/forms/UserForm.java index f746fff..fe4ebc2 100644 --- a/src/main/java/com/raysmond/blog/forms/UserForm.java +++ b/src/main/java/com/raysmond/blog/forms/UserForm.java @@ -5,7 +5,7 @@ import javax.validation.constraints.NotNull; /** - * @author Raysmond. + * @author Raysmond */ @Data public class UserForm { diff --git a/src/main/java/com/raysmond/blog/models/BaseModel.java b/src/main/java/com/raysmond/blog/models/BaseModel.java index 564e9da..8f262d0 100644 --- a/src/main/java/com/raysmond/blog/models/BaseModel.java +++ b/src/main/java/com/raysmond/blog/models/BaseModel.java @@ -1,14 +1,17 @@ package com.raysmond.blog.models; import org.apache.commons.lang3.builder.HashCodeBuilder; + import javax.persistence.*; + import java.io.Serializable; +import java.time.ZonedDateTime; import java.util.Date; /** * An abstract base model class for entities * - * @author Raysmond + * @author Raysmond */ @MappedSuperclass public abstract class BaseModel implements Comparable, Serializable { @@ -19,19 +22,19 @@ public abstract class BaseModel implements Comparable, Serializable { private Long id; @Column(nullable = false) - private Date createdAt; + private ZonedDateTime createdAt; @Column(nullable = false) - private Date updatedAt; + private ZonedDateTime updatedAt; @PrePersist - public void prePersist(){ - createdAt = updatedAt = new Date(); + public void prePersist() { + createdAt = updatedAt = ZonedDateTime.now(); } @PreUpdate - public void preUpdate(){ - updatedAt = new Date(); + public void preUpdate() { + updatedAt = ZonedDateTime.now(); } @Override @@ -40,18 +43,17 @@ public int compareTo(BaseModel o) { } public boolean equals(Object other) { - if (other == null || other.getClass() != this.getClass()) + if (other == null || other.getClass() != this.getClass()) { return false; + } return this.getId().equals(((BaseModel) other).getId()); } - public int hashCode() { return new HashCodeBuilder().append(getId()).toHashCode(); } - public Long getId() { return id; } @@ -60,20 +62,19 @@ public void setId(Long _id) { id = _id; } - public Date getCreatedAt() { + public ZonedDateTime getCreatedAt() { return createdAt; } - public void setCreatedAt(Date createdAt) { + public void setCreatedAt(ZonedDateTime createdAt) { this.createdAt = createdAt; } - public Date getUpdatedAt() { + public ZonedDateTime getUpdatedAt() { return updatedAt; } - public void setUpdatedAt(Date updatedAt) { + public void setUpdatedAt(ZonedDateTime updatedAt) { this.updatedAt = updatedAt; } - } \ No newline at end of file diff --git a/src/main/java/com/raysmond/blog/models/Post.java b/src/main/java/com/raysmond/blog/models/Post.java index bbec0a2..a3485d2 100644 --- a/src/main/java/com/raysmond/blog/models/Post.java +++ b/src/main/java/com/raysmond/blog/models/Post.java @@ -3,26 +3,29 @@ import com.raysmond.blog.models.support.PostFormat; import com.raysmond.blog.models.support.PostStatus; import com.raysmond.blog.models.support.PostType; -import lombok.Builder; + import lombok.Getter; import lombok.Setter; + +import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.Type; import org.springframework.util.StringUtils; import javax.persistence.*; + import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collection; import java.util.HashSet; import java.util.Set; /** - * @author Raysmond + * @author Raysmond */ @Entity @Table(name = "posts") -@Getter @Setter -public class Post extends BaseModel{ +@Getter +@Setter +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "postCache") +public class Post extends BaseModel { private static final SimpleDateFormat SLUG_DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd"); @ManyToOne @@ -31,12 +34,18 @@ public class Post extends BaseModel{ @Column(nullable = false) private String title; - @Type(type="text") + @Type(type = "text") private String content; @Type(type = "text") private String renderedContent; + @Type(type = "text") + private String summary; + + @Type(type = "text") + private String renderedSummary; + @Column(nullable = false) @Enumerated(EnumType.STRING) private PostStatus postStatus = PostStatus.PUBLISHED; @@ -54,20 +63,27 @@ public class Post extends BaseModel{ joinColumns = {@JoinColumn(name = "post_id", nullable = false, updatable = false)}, inverseJoinColumns = {@JoinColumn(name = "tag_id", nullable = false, updatable = false)} ) + @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "tagCache") private Set tags = new HashSet<>(); private String permalink; + private Integer views = 0; + + public Integer getViews() { + return views == null ? 0 : views; + } + public String getRenderedContent() { - if (this.postFormat == PostFormat.MARKDOWN) + if (this.postFormat == PostFormat.MARKDOWN) { return renderedContent; + } return getContent(); } - public void setPermalink(String permalink){ + public void setPermalink(String permalink) { String token = permalink.toLowerCase().replace("\n", " ").replaceAll("[^a-z\\d\\s]", " "); this.permalink = StringUtils.arrayToDelimitedString(StringUtils.tokenizeToStringArray(token, " "), "-"); } - } diff --git a/src/main/java/com/raysmond/blog/models/Setting.java b/src/main/java/com/raysmond/blog/models/Setting.java index 17ffa78..4704a57 100644 --- a/src/main/java/com/raysmond/blog/models/Setting.java +++ b/src/main/java/com/raysmond/blog/models/Setting.java @@ -1,22 +1,27 @@ package com.raysmond.blog.models; -import lombok.Builder; import lombok.Getter; import lombok.Setter; -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.persistence.Table; import java.io.Serializable; -import java.util.Date; + +import org.hibernate.annotations.CacheConcurrencyStrategy; /** * A generic setting model * - * @author Raysmond + * @author Raysmond */ @Entity @Table(name = "settings") -@Getter @Setter -public class Setting extends BaseModel{ +@Getter +@Setter +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "settingCache") +public class Setting extends BaseModel { @Column(name = "_key", unique = true, nullable = false) private String key; diff --git a/src/main/java/com/raysmond/blog/models/Tag.java b/src/main/java/com/raysmond/blog/models/Tag.java index a424505..67c2733 100644 --- a/src/main/java/com/raysmond/blog/models/Tag.java +++ b/src/main/java/com/raysmond/blog/models/Tag.java @@ -5,15 +5,18 @@ import javax.persistence.*; import java.util.ArrayList; -import java.util.Collection; import java.util.List; +import org.hibernate.annotations.CacheConcurrencyStrategy; + /** - * @author Raysmond. + * @author Raysmond */ @Entity @Table(name = "tags") -@Getter @Setter +@Getter +@Setter +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "tagCache") public class Tag extends BaseModel { @Column(nullable = false, unique = true) @@ -22,11 +25,11 @@ public class Tag extends BaseModel { @ManyToMany(fetch = FetchType.LAZY, mappedBy = "tags") private List posts = new ArrayList<>(); - public Tag(){ + public Tag() { } - public Tag(String name){ + public Tag(String name) { this.setName(name); } } diff --git a/src/main/java/com/raysmond/blog/models/User.java b/src/main/java/com/raysmond/blog/models/User.java index 3b2591e..cf75b07 100644 --- a/src/main/java/com/raysmond/blog/models/User.java +++ b/src/main/java/com/raysmond/blog/models/User.java @@ -1,23 +1,28 @@ package com.raysmond.blog.models; -import javax.persistence.*; - import com.fasterxml.jackson.annotation.JsonIgnore; + import lombok.Getter; import lombok.Setter; +import javax.persistence.*; + import java.util.ArrayList; import java.util.Collection; +import org.hibernate.annotations.CacheConcurrencyStrategy; + /** - * @author Raysmond + * @author Raysmond */ @Entity @Table(name = "users") -@Getter @Setter -public class User extends BaseModel{ +@Getter +@Setter +@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "userCache") +public class User extends BaseModel { public static final String ROLE_ADMIN = "ROLE_ADMIN"; - public static final String ROLE_USER = "ROLE_USER"; + public static final String ROLE_USER = "ROLE_USER"; @Column(unique = true) private String email; diff --git a/src/main/java/com/raysmond/blog/models/support/PostFormat.java b/src/main/java/com/raysmond/blog/models/support/PostFormat.java index e1adb0b..6b72ec9 100644 --- a/src/main/java/com/raysmond/blog/models/support/PostFormat.java +++ b/src/main/java/com/raysmond/blog/models/support/PostFormat.java @@ -1,7 +1,7 @@ package com.raysmond.blog.models.support; /** - * @author Raysmond + * @author Raysmond */ public enum PostFormat { HTML("Html"), @@ -9,11 +9,11 @@ public enum PostFormat { private String displayName; - PostFormat(String displayName){ + PostFormat(String displayName) { this.displayName = displayName; } - public String getDisplayName(){ + public String getDisplayName() { return displayName; } diff --git a/src/main/java/com/raysmond/blog/models/support/PostStatus.java b/src/main/java/com/raysmond/blog/models/support/PostStatus.java index 2a0dd4b..bd78ecd 100644 --- a/src/main/java/com/raysmond/blog/models/support/PostStatus.java +++ b/src/main/java/com/raysmond/blog/models/support/PostStatus.java @@ -1,7 +1,7 @@ package com.raysmond.blog.models.support; /** - * @author Raysmond + * @author Raysmond */ public enum PostStatus { DRAFT("Draft"), @@ -9,7 +9,7 @@ public enum PostStatus { private String name; - PostStatus(String name){ + PostStatus(String name) { this.name = name; } @@ -21,7 +21,7 @@ public void setName(String name) { this.name = name; } - public String getId(){ + public String getId() { return name(); } diff --git a/src/main/java/com/raysmond/blog/models/support/PostType.java b/src/main/java/com/raysmond/blog/models/support/PostType.java index ed9fbb8..83f9055 100644 --- a/src/main/java/com/raysmond/blog/models/support/PostType.java +++ b/src/main/java/com/raysmond/blog/models/support/PostType.java @@ -1,7 +1,7 @@ package com.raysmond.blog.models.support; /** - * @author Raysmond + * @author Raysmond */ public enum PostType { PAGE("Page"), @@ -9,7 +9,7 @@ public enum PostType { private String name; - PostType(String name){ + PostType(String name) { this.name = name; } @@ -21,7 +21,7 @@ public void setName(String name) { this.name = name; } - public String getId(){ + public String getId() { return name(); } diff --git a/src/main/java/com/raysmond/blog/repositories/PostRepository.java b/src/main/java/com/raysmond/blog/repositories/PostRepository.java index 9acb1a5..5970541 100644 --- a/src/main/java/com/raysmond/blog/repositories/PostRepository.java +++ b/src/main/java/com/raysmond/blog/repositories/PostRepository.java @@ -10,15 +10,13 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import java.util.Map; import java.util.List; /** - * @author Raysmond + * @author Raysmond */ @Repository -@Transactional public interface PostRepository extends JpaRepository { Post findByPermalinkAndPostStatus(String permalink, PostStatus postStatus); diff --git a/src/main/java/com/raysmond/blog/repositories/SettingRepository.java b/src/main/java/com/raysmond/blog/repositories/SettingRepository.java index d696976..5a48fba 100644 --- a/src/main/java/com/raysmond/blog/repositories/SettingRepository.java +++ b/src/main/java/com/raysmond/blog/repositories/SettingRepository.java @@ -2,15 +2,13 @@ import com.raysmond.blog.models.Setting; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; /** - * @author Raysmond + * @author Raysmond */ @Repository -@Transactional public interface SettingRepository extends JpaRepository { Setting findByKey(String key); } diff --git a/src/main/java/com/raysmond/blog/repositories/TagRepository.java b/src/main/java/com/raysmond/blog/repositories/TagRepository.java index 780271b..663869d 100644 --- a/src/main/java/com/raysmond/blog/repositories/TagRepository.java +++ b/src/main/java/com/raysmond/blog/repositories/TagRepository.java @@ -4,8 +4,8 @@ import org.springframework.data.jpa.repository.JpaRepository; /** - * @author Raysmond. + * @author Raysmond */ -public interface TagRepository extends JpaRepository{ +public interface TagRepository extends JpaRepository { Tag findByName(String name); } diff --git a/src/main/java/com/raysmond/blog/repositories/UserRepository.java b/src/main/java/com/raysmond/blog/repositories/UserRepository.java index 9a2a905..8382b60 100644 --- a/src/main/java/com/raysmond/blog/repositories/UserRepository.java +++ b/src/main/java/com/raysmond/blog/repositories/UserRepository.java @@ -2,15 +2,12 @@ import com.raysmond.blog.models.User; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; /** - * @author Raysmond + * @author Raysmond */ @Repository -@Transactional public interface UserRepository extends JpaRepository { User findByEmail(String email); } diff --git a/src/main/java/com/raysmond/blog/services/AppSetting.java b/src/main/java/com/raysmond/blog/services/AppSetting.java index 2bbac1e..1aaa8bc 100644 --- a/src/main/java/com/raysmond/blog/services/AppSetting.java +++ b/src/main/java/com/raysmond/blog/services/AppSetting.java @@ -1,33 +1,28 @@ package com.raysmond.blog.services; -import com.raysmond.blog.services.SettingService; +import com.domingosuarez.boot.autoconfigure.jade4j.JadeHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import com.domingosuarez.boot.autoconfigure.jade4j.JadeHelper; /** - * @author Raysmond + * @author Raysmond */ @JadeHelper("App") @Service public class AppSetting { - private SettingService settingService; - - private String siteName = "SpringBlog"; - private String siteSlogan = "An interesting place to discover"; - private Integer pageSize = 5; - public static final String SITE_NAME = "site_name"; public static final String SITE_SLOGAN = "site_slogan"; public static final String PAGE_SIZE = "page_size"; @Autowired - public AppSetting(SettingService settingService){ - this.settingService = settingService; - } + private SettingService settingService; + + private String siteName = "SpringBlog"; + private String siteSlogan = "An interesting place to discover"; + private Integer pageSize = 5; - public String getSiteName(){ + public String getSiteName() { return (String) settingService.get(SITE_NAME, siteName); } diff --git a/src/main/java/com/raysmond/blog/services/CacheSettingService.java b/src/main/java/com/raysmond/blog/services/CacheSettingService.java index cde66d3..cb2ec8b 100644 --- a/src/main/java/com/raysmond/blog/services/CacheSettingService.java +++ b/src/main/java/com/raysmond/blog/services/CacheSettingService.java @@ -2,58 +2,58 @@ import com.raysmond.blog.models.Setting; import com.raysmond.blog.repositories.SettingRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import lombok.extern.slf4j.Slf4j; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.io.Serializable; /** - * @author Raysmond + * @author Raysmond */ @Service +@Slf4j +@Transactional public class CacheSettingService implements SettingService { - private SettingRepository settingRepository; - private static final String CACHE_NAME = "cache.settings"; - @Autowired public CacheSettingService(SettingRepository settingRepository) { this.settingRepository = settingRepository; } - private static final Logger logger = LoggerFactory.getLogger(SettingService.class); - @Override + @Cacheable(value = "settingCache", key = "#key") public Serializable get(String key) { Setting setting = settingRepository.findByKey(key); Serializable value = null; try { value = setting == null ? null : setting.getValue(); } catch (Exception ex) { - logger.info("Cannot deserialize setting value with key = " + key); + log.info("Cannot deserialize setting value with key = " + key); } - logger.info("Get setting " + key + " from database. Value = " + value); + log.info("Get setting " + key + " from database. Value = " + value); return value; } @Override - @Cacheable(value = CACHE_NAME, key = "#key") + @Cacheable(value = "settingCache", key = "#key") public Serializable get(String key, Serializable defaultValue) { Serializable value = get(key); return value == null ? defaultValue : value; } @Override - @CacheEvict(value = CACHE_NAME, key = "#key") + @CacheEvict(value = "settingCache", key = "#key") public void put(String key, Serializable value) { - logger.info("Update setting " + key + " to database. Value = " + value); + log.info("Update setting " + key + " to database. Value = " + value); Setting setting = settingRepository.findByKey(key); if (setting == null) { @@ -64,8 +64,8 @@ public void put(String key, Serializable value) { setting.setValue(value); settingRepository.save(setting); } catch (Exception ex) { - - logger.info("Cannot save setting value with type: " + value.getClass() + ". key = " + key); + + log.info("Cannot save setting value with type: " + value.getClass() + ". key = " + key); } } } diff --git a/src/main/java/com/raysmond/blog/services/PostService.java b/src/main/java/com/raysmond/blog/services/PostService.java index 0f496b5..550f5d5 100644 --- a/src/main/java/com/raysmond/blog/services/PostService.java +++ b/src/main/java/com/raysmond/blog/services/PostService.java @@ -8,25 +8,34 @@ import com.raysmond.blog.models.support.PostStatus; import com.raysmond.blog.models.support.PostType; import com.raysmond.blog.repositories.PostRepository; -import com.raysmond.blog.utils.Markdown; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.raysmond.blog.support.web.MarkdownService; + +import lombok.extern.slf4j.Slf4j; + import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.Caching; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; /** - * @author Raysmond. + * @author Raysmond */ @Service +@Slf4j +@Transactional public class PostService { + @Autowired private PostRepository postRepository; @@ -36,17 +45,12 @@ public class PostService { @Autowired private UserService userService; - public static final String CACHE_NAME = "cache.post"; - public static final String CACHE_NAME_ARCHIVE = CACHE_NAME + ".archive"; - public static final String CACHE_NAME_PAGE = CACHE_NAME + ".page"; - public static final String CACHE_NAME_TAGS = CACHE_NAME + ".tag"; - public static final String CACHE_NAME_COUNTS = CACHE_NAME + ".counts_tags"; - - private static final Logger logger = LoggerFactory.getLogger(PostService.class); + @Autowired + @Qualifier("flexmark") + private MarkdownService markdownService; - @Cacheable(CACHE_NAME) public Post getPost(Long postId) { - logger.debug("Get post " + postId); + log.debug("Get post " + postId); Post post = postRepository.findOne(postId); @@ -57,12 +61,19 @@ public Post getPost(Long postId) { return post; } - @Cacheable(CACHE_NAME) public Post getPublishedPostByPermalink(String permalink) { - logger.debug("Get post with permalink " + permalink); + log.debug("Get post with permalink " + permalink); Post post = postRepository.findByPermalinkAndPostStatus(permalink, PostStatus.PUBLISHED); + if (post == null) { + try { + post = postRepository.findOne(Long.valueOf(permalink)); + } catch (NumberFormatException e) { + post = null; + } + } + if (post == null) { throw new NotFoundException("Post with permalink '" + permalink + "' is not found."); } @@ -70,65 +81,41 @@ public Post getPublishedPostByPermalink(String permalink) { return post; } - @Caching(evict = { - @CacheEvict(value = CACHE_NAME_ARCHIVE, allEntries = true), - @CacheEvict(value = CACHE_NAME_PAGE, allEntries = true), - @CacheEvict(value = CACHE_NAME_COUNTS, allEntries = true) - }) public Post createPost(Post post) { if (post.getPostFormat() == PostFormat.MARKDOWN) { - post.setRenderedContent(Markdown.markdownToHtml(post.getContent())); + post.setRenderedContent(markdownService.renderToHtml(post.getContent())); + post.setRenderedSummary(markdownService.renderToHtml(post.getSummary())); } return postRepository.save(post); } - @Caching(evict = { - @CacheEvict(value = CACHE_NAME, key = "#post.id"), - @CacheEvict(value = CACHE_NAME, key = "#post.permalink", condition = "#post.permalink != null"), - @CacheEvict(value = CACHE_NAME_TAGS, key = "#post.id.toString().concat('-tags')"), - @CacheEvict(value = CACHE_NAME_ARCHIVE, allEntries = true), - @CacheEvict(value = CACHE_NAME_PAGE, allEntries = true), - @CacheEvict(value = CACHE_NAME_COUNTS, allEntries = true) - }) public Post updatePost(Post post) { if (post.getPostFormat() == PostFormat.MARKDOWN) { - post.setRenderedContent(Markdown.markdownToHtml(post.getContent())); + post.setRenderedContent(markdownService.renderToHtml(post.getContent())); + post.setRenderedSummary(markdownService.renderToHtml(post.getSummary())); } return postRepository.save(post); } - @Caching(evict = { - @CacheEvict(value = CACHE_NAME, key = "#post.id"), - @CacheEvict(value = CACHE_NAME, key = "#post.permalink", condition = "#post.permalink != null"), - @CacheEvict(value = CACHE_NAME_TAGS, key = "#post.id.toString().concat('-tags')"), - @CacheEvict(value = CACHE_NAME_ARCHIVE, allEntries = true), - @CacheEvict(value = CACHE_NAME_PAGE, allEntries = true), - @CacheEvict(value = CACHE_NAME_COUNTS, allEntries = true) - }) public void deletePost(Post post) { postRepository.delete(post); } - @Cacheable(value = CACHE_NAME_ARCHIVE, key = "#root.method.name") public List getArchivePosts() { - logger.debug("Get all archive posts from database."); - - Iterable posts = postRepository.findAllByPostTypeAndPostStatus( - PostType.POST, - PostStatus.PUBLISHED, - new PageRequest(0, Integer.MAX_VALUE, Sort.Direction.DESC, "createdAt")); - - List cachedPosts = new ArrayList<>(); - posts.forEach(post -> cachedPosts.add(extractPostMeta(post))); - - return cachedPosts; + log.debug("Get all archive posts from database."); + + Pageable page = new PageRequest(0, Integer.MAX_VALUE, Sort.Direction.DESC, "createdAt"); + return postRepository.findAllByPostTypeAndPostStatus(PostType.POST, PostStatus.PUBLISHED, page) + .getContent() + .stream() + .map(this::extractPostMeta) + .collect(Collectors.toList()); } - @Cacheable(value = CACHE_NAME_TAGS, key = "#post.id.toString().concat('-tags')") public List getPostTags(Post post) { - logger.debug("Get tags of post " + post.getId()); + log.debug("Get tags of post {}", post.getId()); List tags = new ArrayList<>(); @@ -139,7 +126,6 @@ public List getPostTags(Post post) { return tags; } - private Post extractPostMeta(Post post) { Post archivePost = new Post(); archivePost.setId(post.getId()); @@ -150,9 +136,8 @@ private Post extractPostMeta(Post post) { return archivePost; } - @Cacheable(value = CACHE_NAME_PAGE, key = "T(java.lang.String).valueOf(#page).concat('-').concat(#pageSize)") public Page getAllPublishedPostsByPage(int page, int pageSize) { - logger.debug("Get posts by page " + page); + log.debug("Get posts by page " + page); return postRepository.findAllByPostTypeAndPostStatus( PostType.POST, @@ -161,7 +146,7 @@ public Page getAllPublishedPostsByPage(int page, int pageSize) { } public Post createAboutPage() { - logger.debug("Create default about page"); + log.debug("Create default about page"); Post post = new Post(); post.setTitle(Constants.ABOUT_PAGE_PERMALINK); @@ -188,8 +173,9 @@ public Set parseTagNames(String tagNames) { } public String getTagNames(Set tags) { - if (tags == null || tags.isEmpty()) + if (tags == null || tags.isEmpty()) { return ""; + } StringBuilder names = new StringBuilder(); tags.forEach(tag -> names.append(tag.getName()).append(",")); @@ -203,10 +189,18 @@ public Page findPostsByTag(String tagName, int page, int pageSize) { return postRepository.findByTag(tagName, new PageRequest(page, pageSize, Sort.Direction.DESC, "createdAt")); } - @Cacheable(value = CACHE_NAME_COUNTS, key = "#root.method.name") public List countPostsByTags() { - logger.debug("Count posts group by tags."); + log.debug("Count posts group by tags."); return postRepository.countPostsByTags(PostStatus.PUBLISHED); } + + @Async + public void incrementViews(Long postId) { + synchronized(this) { + Post post = postRepository.findOne(postId); + post.setViews(post.getViews() + 1); + postRepository.save(post); + } + } } diff --git a/src/main/java/com/raysmond/blog/services/SettingService.java b/src/main/java/com/raysmond/blog/services/SettingService.java index be81430..3d0e988 100644 --- a/src/main/java/com/raysmond/blog/services/SettingService.java +++ b/src/main/java/com/raysmond/blog/services/SettingService.java @@ -3,10 +3,12 @@ import java.io.Serializable; /** - * @author Raysmond + * @author Raysmond */ public interface SettingService { Serializable get(String key); + Serializable get(String key, Serializable defaultValue); + void put(String key, Serializable value); } diff --git a/src/main/java/com/raysmond/blog/services/TagService.java b/src/main/java/com/raysmond/blog/services/TagService.java index 398ace3..da1d7ca 100644 --- a/src/main/java/com/raysmond/blog/services/TagService.java +++ b/src/main/java/com/raysmond/blog/services/TagService.java @@ -2,61 +2,41 @@ import com.raysmond.blog.models.Tag; import com.raysmond.blog.repositories.TagRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Service; import java.util.List; - /** - * @author Raysmond. + * @author Raysmond */ @Service public class TagService { private TagRepository tagRepository; - private static final Logger logger = LoggerFactory.getLogger(PostService.class); - - public static final String CACHE_NAME = "cache.tag"; - public static final String CACHE_NAME_TAGS = "cache.tag.all"; - - public static final String CACHE_TYPE = "'_Tag_'"; - public static final String CACHE_KEY = CACHE_TYPE + " + #tagName"; - public static final String CACHE_TAG_KEY = CACHE_TYPE + " + #tag.name"; - @Autowired - public TagService(TagRepository tagRepository){ + public TagService(TagRepository tagRepository) { this.tagRepository = tagRepository; } - public Tag findOrCreateByName(String name){ + public Tag findOrCreateByName(String name) { Tag tag = tagRepository.findByName(name); - if (tag == null){ + if (tag == null) { tag = tagRepository.save(new Tag(name)); } return tag; } - @Cacheable(value = CACHE_NAME, key = CACHE_KEY) public Tag getTag(String tagName) { return tagRepository.findByName(tagName); } - @Caching(evict = { - @CacheEvict(value = CACHE_NAME, key = CACHE_TAG_KEY), - @CacheEvict(value = CACHE_NAME_TAGS, allEntries = true) - }) - public void deleteTag(Tag tag){ + public void deleteTag(Tag tag) { tagRepository.delete(tag); } - @Cacheable(value = CACHE_NAME_TAGS, key = "#root.method.name") - public List getAllTags(){ + public List getAllTags() { return tagRepository.findAll(); } diff --git a/src/main/java/com/raysmond/blog/services/UserService.java b/src/main/java/com/raysmond/blog/services/UserService.java index 0fff9f0..744b5f6 100644 --- a/src/main/java/com/raysmond/blog/services/UserService.java +++ b/src/main/java/com/raysmond/blog/services/UserService.java @@ -1,48 +1,51 @@ package com.raysmond.blog.services; -import java.util.Collections; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; - import com.raysmond.blog.Constants; import com.raysmond.blog.models.User; import com.raysmond.blog.repositories.UserRepository; +import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.*; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -public class UserService implements UserDetailsService { +import javax.annotation.PostConstruct; +import java.util.Collections; +@Transactional +@Service +@Slf4j +public class UserService implements UserDetailsService { @Autowired private UserRepository userRepository; - - @Inject + @Autowired private PasswordEncoder passwordEncoder; - private static final Logger logger = LoggerFactory.getLogger(UserService.class); - @PostConstruct protected void initialize() { getSuperUser(); } - public User createUser(User user){ + public User createUser(User user) { user.setPassword(passwordEncoder.encode(user.getPassword())); return userRepository.save(user); } - public User getSuperUser(){ + public User getSuperUser() { User user = userRepository.findByEmail(Constants.DEFAULT_ADMIN_EMAIL); - if ( user == null) { + if (user == null) { user = createUser(new User(Constants.DEFAULT_ADMIN_EMAIL, Constants.DEFAULT_ADMIN_PASSWORD, User.ROLE_ADMIN)); } @@ -58,9 +61,9 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx return createSpringUser(user); } - public User currentUser(){ + public User currentUser() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if(auth == null || auth instanceof AnonymousAuthenticationToken){ + if (auth == null || auth instanceof AnonymousAuthenticationToken) { return null; } @@ -69,11 +72,10 @@ public User currentUser(){ return userRepository.findByEmail(email); } - public boolean changePassword(User user, String password, String newPassword){ + public boolean changePassword(User user, String password, String newPassword) { if (password == null || newPassword == null || password.isEmpty() || newPassword.isEmpty()) return false; - logger.info("" + passwordEncoder.matches(password, user.getPassword())); boolean match = passwordEncoder.matches(password, user.getPassword()); if (!match) return false; @@ -81,7 +83,7 @@ public boolean changePassword(User user, String password, String newPassword){ user.setPassword(passwordEncoder.encode(newPassword)); userRepository.save(user); - logger.info("User @"+user.getEmail() + " changed password."); + log.info("User @{} changed password.", user.getEmail()); return true; } diff --git a/src/main/java/com/raysmond/blog/support/web/MarkdownService.java b/src/main/java/com/raysmond/blog/support/web/MarkdownService.java index 184296b..755e97b 100644 --- a/src/main/java/com/raysmond/blog/support/web/MarkdownService.java +++ b/src/main/java/com/raysmond/blog/support/web/MarkdownService.java @@ -1,8 +1,8 @@ package com.raysmond.blog.support.web; /** - * @author Raysmond + * @author Raysmond */ public interface MarkdownService { - public String renderToHtml(String content); + String renderToHtml(String content); } diff --git a/src/main/java/com/raysmond/blog/support/web/Message.java b/src/main/java/com/raysmond/blog/support/web/Message.java index e05d046..ca9645f 100644 --- a/src/main/java/com/raysmond/blog/support/web/Message.java +++ b/src/main/java/com/raysmond/blog/support/web/Message.java @@ -7,40 +7,38 @@ public class Message implements java.io.Serializable { /** * Name of the flash attribute. */ - public static final String MESSAGE_ATTRIBUTE = "message"; + public static final String MESSAGE_ATTRIBUTE = "message"; + private final String message; + private final Type type; + private final Object[] args; + public Message(String message, Type type) { + this.message = message; + this.type = type; + this.args = null; + } - /** - * The type of the message to be displayed. The type is used to show message in a different style. - */ - public static enum Type { - DANGER, WARNING, INFO, SUCCESS; - } - - private final String message; - private final Type type; - private final Object[] args; + public Message(String message, Type type, Object... args) { + this.message = message; + this.type = type; + this.args = args; + } - public Message(String message, Type type) { - this.message = message; - this.type = type; - this.args = null; - } - - public Message(String message, Type type, Object... args) { - this.message = message; - this.type = type; - this.args = args; - } + public String getMessage() { + return message; + } - public String getMessage() { - return message; - } + public Type getType() { + return type; + } - public Type getType() { - return type; - } + public Object[] getArgs() { + return args; + } - public Object[] getArgs() { - return args; - } + /** + * The type of the message to be displayed. The type is used to show message in a different style. + */ + public static enum Type { + DANGER, WARNING, INFO, SUCCESS; + } } diff --git a/src/main/java/com/raysmond/blog/support/web/MessageHelper.java b/src/main/java/com/raysmond/blog/support/web/MessageHelper.java index f85bcda..02b7557 100644 --- a/src/main/java/com/raysmond/blog/support/web/MessageHelper.java +++ b/src/main/java/com/raysmond/blog/support/web/MessageHelper.java @@ -1,10 +1,10 @@ package com.raysmond.blog.support.web; -import static com.raysmond.blog.support.web.Message.MESSAGE_ATTRIBUTE; - import org.springframework.ui.Model; import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import static com.raysmond.blog.support.web.Message.MESSAGE_ATTRIBUTE; + public final class MessageHelper { private MessageHelper() { diff --git a/src/main/java/com/raysmond/blog/support/web/PygmentsService.java b/src/main/java/com/raysmond/blog/support/web/PygmentsService.java index 811e31c..0f47c3a 100644 --- a/src/main/java/com/raysmond/blog/support/web/PygmentsService.java +++ b/src/main/java/com/raysmond/blog/support/web/PygmentsService.java @@ -1,10 +1,10 @@ package com.raysmond.blog.support.web; -import org.springframework.stereotype.Service; import org.python.util.PythonInterpreter; +import org.springframework.stereotype.Service; /** - * @author Raysmond + * @author Raysmond */ @Service public class PygmentsService implements SyntaxHighlightService { @@ -18,9 +18,9 @@ public String highlight(String content) { // Simple use Pygments as you would in Python interpreter.exec("from pygments import highlight\n" - + "from pygments.lexers import PythonLexer\n" - + "from pygments.formatters import HtmlFormatter\n" - + "\nresult = highlight(code, PythonLexer(), HtmlFormatter())"); + + "from pygments.lexers import PythonLexer\n" + + "from pygments.formatters import HtmlFormatter\n" + + "\nresult = highlight(code, PythonLexer(), HtmlFormatter())"); return interpreter.get("result", String.class); } diff --git a/src/main/java/com/raysmond/blog/support/web/PygmentsVerbatimSerializer.java b/src/main/java/com/raysmond/blog/support/web/PygmentsVerbatimSerializer.java index d673890..332b703 100644 --- a/src/main/java/com/raysmond/blog/support/web/PygmentsVerbatimSerializer.java +++ b/src/main/java/com/raysmond/blog/support/web/PygmentsVerbatimSerializer.java @@ -3,10 +3,9 @@ import org.pegdown.Printer; import org.pegdown.VerbatimSerializer; import org.pegdown.ast.VerbatimNode; -import org.springframework.beans.factory.annotation.Autowired; /** - * @author Raysmond + * @author Raysmond */ public class PygmentsVerbatimSerializer implements VerbatimSerializer { public static final PygmentsVerbatimSerializer INSTANCE = new PygmentsVerbatimSerializer(); diff --git a/src/main/java/com/raysmond/blog/support/web/SyntaxHighlightService.java b/src/main/java/com/raysmond/blog/support/web/SyntaxHighlightService.java index aadad28..cb4c4b3 100644 --- a/src/main/java/com/raysmond/blog/support/web/SyntaxHighlightService.java +++ b/src/main/java/com/raysmond/blog/support/web/SyntaxHighlightService.java @@ -1,8 +1,8 @@ package com.raysmond.blog.support.web; /** - * @author Raysmond + * @author Raysmond */ public interface SyntaxHighlightService { - public String highlight(String content); + String highlight(String content); } diff --git a/src/main/java/com/raysmond/blog/support/web/ViewHelper.java b/src/main/java/com/raysmond/blog/support/web/ViewHelper.java index 801da18..e2df79f 100644 --- a/src/main/java/com/raysmond/blog/support/web/ViewHelper.java +++ b/src/main/java/com/raysmond/blog/support/web/ViewHelper.java @@ -1,15 +1,21 @@ package com.raysmond.blog.support.web; - +import com.domingosuarez.boot.autoconfigure.jade4j.JadeHelper; import com.raysmond.blog.services.AppSetting; -import org.springframework.stereotype.Service; + import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Date; -import com.domingosuarez.boot.autoconfigure.jade4j.JadeHelper; + /** - * @author Raysmond + * @author Raysmond */ @Service @JadeHelper("viewHelper") @@ -21,15 +27,26 @@ public class ViewHelper { private AppSetting appSetting; private String applicationEnv; + private long startTime; + + /** + * Check if current user is authenticated + * + * @return true/false + */ + public boolean isLogin() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return null != authentication + && authentication.isAuthenticated() + && !(authentication instanceof AnonymousAuthenticationToken); + } @Autowired - public ViewHelper(AppSetting appSetting){ + public ViewHelper(AppSetting appSetting) { this.appSetting = appSetting; } - private long startTime; - - public long getResponseTime(){ + public long getResponseTime() { return System.currentTimeMillis() - startTime; } @@ -41,15 +58,19 @@ public void setStartTime(long startTime) { this.startTime = startTime; } - public String getFormattedDate(Date date){ + public String getFormattedDate(Date date) { return date == null ? "" : DATE_FORMAT.format(date); } - public String getMonthAndDay(Date date){ + public String getFormattedDate(ZonedDateTime date) { + return date == null ? "" : date.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public String getMonthAndDay(Date date) { return date == null ? "" : DATE_FORMAT_MONTH_DAY.format(date); } - public String metaTitle(String title){ + public String metaTitle(String title) { return title + " · " + appSetting.getSiteName(); } diff --git a/src/main/java/com/raysmond/blog/support/web/impl/FlexmarkMarkdownService.java b/src/main/java/com/raysmond/blog/support/web/impl/FlexmarkMarkdownService.java new file mode 100644 index 0000000..db19899 --- /dev/null +++ b/src/main/java/com/raysmond/blog/support/web/impl/FlexmarkMarkdownService.java @@ -0,0 +1,46 @@ +package com.raysmond.blog.support.web.impl; + +import java.util.Arrays; + +import org.springframework.stereotype.Service; + +import com.raysmond.blog.support.web.MarkdownService; +import com.vladsch.flexmark.ast.Node; +import com.vladsch.flexmark.ext.autolink.AutolinkExtension; +import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension; +import com.vladsch.flexmark.ext.tables.TablesExtension; +import com.vladsch.flexmark.html.HtmlRenderer; +import com.vladsch.flexmark.parser.Parser; +import com.vladsch.flexmark.util.options.MutableDataSet; + +import lombok.extern.slf4j.Slf4j; + +/** + * 使用flexmark-java解析markdown + * 参考:https://github.com/vsch/flexmark-java + * + * @author Raysmond + */ +@Service("flexmark") +@Slf4j +public class FlexmarkMarkdownService implements MarkdownService { + @Override + public String renderToHtml(String content) { + MutableDataSet options = new MutableDataSet(); + + options.set(Parser.EXTENSIONS, + Arrays.asList(TablesExtension.create(), + AutolinkExtension.create(), + StrikethroughExtension.create())); + + // uncomment to convert soft-breaks to hard breaks + //options.set(HtmlRenderer.SOFT_BREAK, "
\n"); + + Parser parser = Parser.builder(options).build(); + HtmlRenderer renderer = HtmlRenderer.builder(options).build(); + + // You can re-use parser and renderer instances + Node document = parser.parse(content); + return renderer.render(document); + } +} diff --git a/src/main/java/com/raysmond/blog/support/web/PegDownMarkdownService.java b/src/main/java/com/raysmond/blog/support/web/impl/PegDownMarkdownService.java similarity index 61% rename from src/main/java/com/raysmond/blog/support/web/PegDownMarkdownService.java rename to src/main/java/com/raysmond/blog/support/web/impl/PegDownMarkdownService.java index 27a5a0a..313d298 100644 --- a/src/main/java/com/raysmond/blog/support/web/PegDownMarkdownService.java +++ b/src/main/java/com/raysmond/blog/support/web/impl/PegDownMarkdownService.java @@ -1,14 +1,17 @@ -package com.raysmond.blog.support.web; +package com.raysmond.blog.support.web.impl; import org.pegdown.*; import org.pegdown.ast.RootNode; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.util.Collections; +import com.raysmond.blog.support.web.MarkdownService; + /** - * @author Raysmond + * @author Raysmond */ @Service @Qualifier("pegdown") @@ -17,16 +20,19 @@ public class PegDownMarkdownService implements MarkdownService { private final PegDownProcessor pegdown; public PegDownMarkdownService() { - pegdown = new PegDownProcessor(Extensions.ALL); + pegdown = new PegDownProcessor(Extensions.ALL ^ Extensions.EXTANCHORLINKS); } @Override public String renderToHtml(String markdownSource) { + if (StringUtils.isEmpty(markdownSource)) { + return null; + } // synchronizing on pegdown instance since neither the processor nor the underlying parser is thread-safe. - synchronized (pegdown) { + synchronized(pegdown) { RootNode astRoot = pegdown.parseMarkdown(markdownSource.toCharArray()); - ToHtmlSerializer serializer = new ToHtmlSerializer(new LinkRenderer(), - Collections.singletonMap(VerbatimSerializer.DEFAULT, PygmentsVerbatimSerializer.INSTANCE)); + ToHtmlSerializer serializer = new ToHtmlSerializer(new LinkRenderer()); + // Collections.singletonMap(VerbatimSerializer.DEFAULT, PygmentsVerbatimSerializer.INSTANCE)); return serializer.toHtml(astRoot); } } diff --git a/src/main/java/com/raysmond/blog/utils/DTOUtil.java b/src/main/java/com/raysmond/blog/utils/DTOUtil.java index dd5431f..9ccb014 100644 --- a/src/main/java/com/raysmond/blog/utils/DTOUtil.java +++ b/src/main/java/com/raysmond/blog/utils/DTOUtil.java @@ -7,14 +7,14 @@ import java.util.List; /** - * @author Raysmond + * @author Raysmond */ public class DTOUtil { private static ModelMapper MAPPER = null; - private static ModelMapper getMapper(){ - if(MAPPER == null){ + private static ModelMapper getMapper() { + if (MAPPER == null) { MAPPER = new ModelMapper(); MAPPER.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); } diff --git a/src/main/java/com/raysmond/blog/utils/Markdown.java b/src/main/java/com/raysmond/blog/utils/Markdown.java index 0f7de93..9adc711 100644 --- a/src/main/java/com/raysmond/blog/utils/Markdown.java +++ b/src/main/java/com/raysmond/blog/utils/Markdown.java @@ -1,18 +1,18 @@ package com.raysmond.blog.utils; import com.raysmond.blog.support.web.MarkdownService; -import com.raysmond.blog.support.web.PegDownMarkdownService; +import com.raysmond.blog.support.web.impl.PegDownMarkdownService; /** * A Markdown processing util class * - * @author Raysmond. + * @author Raysmond */ public class Markdown { private static final MarkdownService MARKDOWN_SERVICE = new PegDownMarkdownService(); - public static String markdownToHtml(String content){ + public static String markdownToHtml(String content) { return MARKDOWN_SERVICE.renderToHtml(content); } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..5e9705d --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,39 @@ +spring: + profiles: + active: dev + + thymeleaf: + cache: false + + devtools: + restart: + enabled: true + exclude: static/**,templates/** + + jade4j: + caching: false + + jpa: + show-sql: true + + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mysql://127.0.0.1/spring_blog_dev?useUnicode=true&characterEncoding=utf8&useSSL=false + username: root + password: + + hikari: + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + redis: + host: localhost + port: 6379 + default_expire_time: 86400 + +logging: + level: + org.springframework.web: ERROR + com.raysmond.blog: DEBUG diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..646bb66 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,38 @@ +spring: + profiles: + active: prod + devtools: + restart: + enabled: false + + thymeleaf: + cache: true + + jade4j: + caching: true + + jpa: + show-sql: true + + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mysql://127.0.0.1/spring_blog_prod?useUnicode=true&characterEncoding=utf8&useSSL=false + username: root + password: + + hikari: + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + + redis: + host: redis + port: 6379 + default_expire_time: 86400 + +logging: + level: + org.springframework.web: ERROR + com.raysmond.blog: DEBUG \ No newline at end of file diff --git a/src/main/resources/application-production.yml b/src/main/resources/application-production.yml deleted file mode 100644 index a333170..0000000 --- a/src/main/resources/application-production.yml +++ /dev/null @@ -1,30 +0,0 @@ -server: - port: 9000 - contextPath: - -spring: - profiles: - active: production - - thymeleaf: - cache: true - - jade4j: - caching: true - - dataSource: - driverClassName: com.mysql.jdbc.Driver - url: jdbc:mysql://127.0.0.1/spring_blog?serverTimezone=UTC - username: root - password: - - hibernate: - dialect: org.hibernate.dialect.MySQLDialect - hbm2ddl.auto: update - show_sql: false - - redis: - host: localhost - port: 6379 - default_expire_time: 86400 - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 80d7039..27b8350 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,30 +1,40 @@ server: - port: 9000 - contextPath: + port: 8080 spring: - profiles: - active: dev + jpa: + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + database: MYSQL + show-sql: true + generate-ddl: true + hibernate: + ddl-auto: update + dialect: org.hibernate.dialect.MySQL5Dialect + properties: + hibernate: + generate_statistics: true + cache: + use_second_level_cache: true + use_query_cache: true + region: + factory_class: org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory thymeleaf: - cache: false + mode: LEGACYHTML5 - jade4j: - caching: false + cache: + ehcache: + config: classpath:ehcache.xml - dataSource: - driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://mysql/spring_blog?serverTimezone=UTC - username: root - password: 123456 +logging: + level: + org.springframework.web: ERROR + com.raysmond.blog: DEBUG - hibernate: - dialect: org.hibernate.dialect.MySQLDialect - hbm2ddl.auto: update - show_sql: false - - redis: - host: localhost - port: 6379 - default_expire_time: 86400 +# Redisson configuration file +redisson-config: conf/redisson.yaml +management: + security: + enabled: true + roles: ROLE_ADMIN \ No newline at end of file diff --git a/src/main/resources/ehcache.xml b/src/main/resources/ehcache.xml new file mode 100644 index 0000000..e21c829 --- /dev/null +++ b/src/main/resources/ehcache.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml deleted file mode 100644 index 41b38cb..0000000 --- a/src/main/resources/logback.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - SpringBlog - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{32} - %msg%n - - - - - - - - - - - diff --git a/src/main/resources/resources/css/admin/admin.css b/src/main/resources/resources/css/admin/admin.css deleted file mode 100644 index da8a9e5..0000000 --- a/src/main/resources/resources/css/admin/admin.css +++ /dev/null @@ -1,45 +0,0 @@ -body { - -webkit-font-smoothing: antialiased; - color: #434343; -} - -.main-container{ - padding-top: 60px; -} - -form .item-row{ - margin-top: 10px; -} - -td.operations, th.operations{ - text-align: center; - width: 120px; -} -td.operations a{ - margin-left: 5px; -} - -.post-form textarea{ - height: 350px; -} - - -.post-form #content-editor{ - height: 430px; - width: 100%; -} - - -.settings-form tbody td.setting-label{ - width: 250px; - text-align: right; - padding-right: 20px; - vertical-align: middle; - font-weight: bold; -} - -.profile .row .col-sm-2{ - font-weight: bold; - text-align: right; - padding-right: 30px; -} diff --git a/src/main/resources/static/css/admin/admin.css b/src/main/resources/static/css/admin/admin.css new file mode 100644 index 0000000..43134c3 --- /dev/null +++ b/src/main/resources/static/css/admin/admin.css @@ -0,0 +1,142 @@ +body { + color: #434343; + font-size: 14px; +} + +.main-container { + padding-top: 60px; +} + +form .item-row { + margin-top: 10px; +} + +td.operations, th.operations { + text-align: center; + width: 120px; +} + +td.operations a { + margin-left: 5px; +} + +.post-form textarea { + height: 350px; +} + +.post-form #content-editor { + height: 430px; + width: 100%; +} +.post-form #summary-editor { + height: 230px; + width: 100%; +} + +.settings-form tbody td.setting-label { + width: 250px; + text-align: right; + padding-right: 20px; + vertical-align: middle; + font-weight: bold; +} + +.profile .row .col-sm-2 { + font-weight: bold; + text-align: right; + padding-right: 30px; +} + +.feather { + width: 16px; + height: 16px; + vertical-align: text-bottom; +} + +/* + * Sidebar + */ + +.sidebar { + position: fixed; + top: 0; + bottom: 0; + left: 0; + z-index: 100; /* Behind the navbar */ + padding: 0; + box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1); +} + +.sidebar-sticky { + position: -webkit-sticky; + position: sticky; + top: 48px; /* Height of navbar */ + height: calc(100vh - 48px); + padding-top: .5rem; + overflow-x: hidden; + overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ +} + +.sidebar .nav-link { + font-weight: 500; + color: #333; +} + +.sidebar .nav-link .feather { + margin-right: 4px; + color: #999; +} + +.sidebar .nav-link.active { + color: #007bff; +} + +.sidebar .nav-link:hover .feather, +.sidebar .nav-link.active .feather { + color: inherit; +} + +.sidebar-heading { + font-size: .75rem; + text-transform: uppercase; +} + +/* + * Navbar + */ +.navbar-brand { + padding-top: .75rem; + padding-bottom: .75rem; + font-size: 1rem; + background-color: rgba(0, 0, 0, .25); + box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25); +} + +.navbar .form-control { + padding: .75rem 1rem; + border-width: 0; + border-radius: 0; +} + +.form-control-dark { + color: #fff; + background-color: rgba(255, 255, 255, .1); + border-color: rgba(255, 255, 255, .1); +} + +.form-control-dark:focus { + border-color: transparent; + box-shadow: 0 0 0 3px rgba(255, 255, 255, .25); +} + +/* + * Utilities + */ + +.border-top { + border-top: 1px solid #e5e5e5; +} + +.border-bottom { + border-bottom: 1px solid #e5e5e5; +} \ No newline at end of file diff --git a/src/main/resources/static/css/bootstrap4-theme.css b/src/main/resources/static/css/bootstrap4-theme.css new file mode 100644 index 0000000..22b5c83 --- /dev/null +++ b/src/main/resources/static/css/bootstrap4-theme.css @@ -0,0 +1,124 @@ +html { + font-size: 14px; + color: #444; +} + +body { + color: #444; +} + +@media (min-width: 768px) { + html { + font-size: 14px; + } +} + +.container { + max-width: 900px; +} + +.main { + background: #FBFBFB; +} + +.logo a { + color: #444; +} + +.pricing-header { + max-width: 700px; +} + +.card-deck .card { + min-width: 220px; +} + +.border-top { + border-top: 1px solid #e5e5e5; +} + +.border-bottom { + border-bottom: 1px solid #e5e5e5; +} + +.box-shadow { + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); +} + +a { + color: #409eff; +} + +a:hover { + text-decoration: none; +} + +/* topbar */ +nav.topbar > a { + color: #444; +} + +nav.topbar > a:focus, nav.topbar > a:hover, nav.topbar > a:active { + color: #409eff; +} + +/* post */ +.blog-post a.post-title > h2 { + color: #444; +} + +h1, h2, h3, h4, h5, h6 { + font-weight: 300; +} + +blockquote { + padding: 20px; + margin: 20px 0; + border: 1px solid #afafaf; + border-left-width: 5px; + border-radius: 3px; +} + +h1.blog-post-title { + font-weight: 500; +} + +.post-content h1 > a, +.post-content h2 > a, +.post-content h3 > a, +.post-content h4 > a, +.post-content h5 > a, +.post-content h6 > a { + color: #444; +} + +.post-content h2 { + border-bottom: 1px #e5e5e5 solid; + padding-bottom: 10px; + margin-bottom: 15px; +} + +.post-content { + line-height: 1.7; +} + +.post-content img { + max-width: 100%; +} + +code { + color: #292e31; + border-radius: 3px; + border: 1px solid #e5e5e5; + background: #fafafa; + padding: 2px 3px; +} + +#post-contents ol { + padding-left: 20px; +} + +#post-contents ol li a { + color: #444; + font-size: 13px; +} \ No newline at end of file diff --git a/src/main/resources/resources/css/theme-light.css b/src/main/resources/static/css/theme-light.css similarity index 71% rename from src/main/resources/resources/css/theme-light.css rename to src/main/resources/static/css/theme-light.css index 12ddd5b..6cb93c2 100644 --- a/src/main/resources/resources/css/theme-light.css +++ b/src/main/resources/static/css/theme-light.css @@ -1,15 +1,16 @@ -html{ +html { font-size: 100%; } body { background: #fff; - font: 300 1em/1.8 "Helvetica Neue",Helvetica, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans; + /*font: 300 1em/1.8 "Helvetica Neue", Helvetica, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans;*/ + font: 300 1em/1.8 -apple-system,SF UI Text,Arial,PingFang SC,Hiragino Sans GB,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif;; -webkit-font-smoothing: antialiased; color: #515862; } -a{ +a { -webkit-transition-property: opacity; -moz-transition-property: opacity; -o-transition-property: opacity; @@ -21,24 +22,26 @@ a{ } /* override typo css */ -h1, h2, h3, h4, h5, h6{ +h1, h2, h3, h4, h5, h6 { color: #47596D; -webkit-font-smoothing: normal; } -.typo a{ + +.typo a { color: #337ab7; } -.typo a:hover{ + +.typo a:hover { color: #666; } -hr{ +hr { border-color: #eee; } /* end */ -pre{ +pre { background: #F9F9F9; border-radius: 3px; border-color: #eee; @@ -49,66 +52,66 @@ pre{ word-wrap: normal; } -a.btn-default{ +a.btn-default { color: #888; } -a.btn-default:hover{ +a.btn-default:hover { background: #fff; color: #333; border-color: #333; } -.navbar{ +.navbar { z-index: 100; } -.navbar a.navbar-brand{ +.navbar a.navbar-brand { color: #9caab0; font-weight: bold; } -.navbar .container{ +.navbar .container { border-bottom: 1px #eee solid; padding-top: 10px; } -.navbar li a{ +.navbar li a { color: #9caab0; text-transform: uppercase; font-weight: bold; font-size: 0.9em; } -.nav>li>a:focus, .nav>li>a:hover{ +.nav > li > a:focus, .nav > li > a:hover { background: none; color: #888; } @media screen and (max-width: 480px) { - #navbar{ + #navbar { text-align: center; } - .nav>li{ + + .nav > li { display: inline-block; } } -.container{ +.container { padding-top: 20px; max-width: 840px; } -.main-container{ +.main-container { padding-top: 0; } -form input, form button{ +form input, form button { margin-top: 10px; } - -.header-title{ +.header-title { padding: 0 100px; width: 100%; position: relative; @@ -118,16 +121,17 @@ form input, form button{ margin-top: 20px; } -.header-title p{ +.header-title p { font-size: 0.9em; } -.header-title p.meta{ + +.header-title p.meta { color: #999; font-size: 0.9em; } -.header-title .avatar img{ +.header-title .avatar img { margin-top: 20px; border-radius: 50%; box-shadow: 0 0 0 3px #fff, 0 0 0 4px #ccc; @@ -135,7 +139,7 @@ form input, form button{ height: 80px; } -.header-title-wrapper{ +.header-title-wrapper { display: table-cell; vertical-align: middle; margin: 0 auto; @@ -144,119 +148,117 @@ form input, form button{ display: block; } - -.post{ +.post { padding: 50px 80px; margin-bottom: 30px; } @media screen and (max-width: 480px) { - .post{ + .post { padding: 30px 10px; } - .post h2.title, .post .meta{ + + .post h2.title, .post .meta { text-align: center; } } - -.post .meta, .typo .post .meta{ - color: rgba(187,187,187,0.8); +.post .meta, .typo .post .meta { + color: rgba(187, 187, 187, 0.8); font-size: 0.9em; } -.post h1 > a, .post h2 > a, .post h3 > a, .post h4 > a{ +.post h1 > a, .post h2 > a, .post h3 > a, .post h4 > a { color: #75889E; text-decoration: none; } -.post h2.title{ +.post h2.title { margin-bottom: 20px; margin-top: 15px; } - -.post h2.title a:hover, .post h2.title a:focus{ +.post h2.title a:hover, .post h2.title a:focus { color: #888; } -.post .info{ +.post .info { margin-top: 30px; text-align: center; color: #888; font-size: 0.9em; } -.post .comments{ +.post .comments { margin-top: 30px; padding-top: 30px; border-top: 1px #eee solid; } -.post ul.tags{ +.post ul.tags { margin-left: 0; padding-left: 0; } -.post ul.tags li{ +.post ul.tags li { display: inline-block; list-style: none; margin-left: 10px; } -.latest-posts{ +.latest-posts { width: 80%; margin: 0 auto; margin-top: 30px; margin-bottom: 50px; } -.loadmore{ +.loadmore { text-align: center; margin-top: 40px; } -.post-item{ +.post-item { text-align: center; width: 80%; margin: 10px auto; padding: 10px 20px; } -.post-item:hover{ +.post-item:hover { background: #F2F8FB; } -.post-item a > h2{ +.post-item a > h2 { font-size: 1.1em; margin-top: 0; color: #515862; font-weight: 400; } -.post-item .meta{ +.post-item .meta { text-align: center; color: #888; font-size: 0.9em; } -ul.archive-list li span.month{ +ul.archive-list li span.month { width: 60px; display: inline-block; } -ul.tags li{ +ul.tags li { margin: 5px 10px; } -.footer{ +.footer { text-align: center; margin-bottom: 50px; font-size: 0.9em; color: #888; } -.footer a{ +.footer a { color: #888; } @@ -264,12 +266,12 @@ ul.tags li{ margin-bottom: 0.4em; } -.footer-wrapper{ +.footer-wrapper { display: block; max-width: 500px; margin: 0 auto; } -.footer-wrapper hr{ +.footer-wrapper hr { border-color: #ddd; } diff --git a/src/main/resources/resources/css/theme.css b/src/main/resources/static/css/theme.css similarity index 96% rename from src/main/resources/resources/css/theme.css rename to src/main/resources/static/css/theme.css index 3c960cf..2a59b18 100644 --- a/src/main/resources/resources/css/theme.css +++ b/src/main/resources/static/css/theme.css @@ -5,10 +5,7 @@ html{ body { background: #efefef; font: 300 1em/1.8 "Helvetica Neue",Helvetica, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans; - /*-webkit-font-smoothing: antialiased; - /*font-size: 1em; - line-height: 1.8; - font-weight: 300;*/ + /*-webkit-font-smoothing: antialiased; */ } /* override typo css */ diff --git a/src/main/resources/resources/images/user.jpg b/src/main/resources/static/images/user.jpg similarity index 100% rename from src/main/resources/resources/images/user.jpg rename to src/main/resources/static/images/user.jpg diff --git a/src/main/resources/resources/vendors/bootstrap-paginator-1.0.2/LICENSE b/src/main/resources/static/vendors/bootstrap-paginator-1.0.2/LICENSE similarity index 100% rename from src/main/resources/resources/vendors/bootstrap-paginator-1.0.2/LICENSE rename to src/main/resources/static/vendors/bootstrap-paginator-1.0.2/LICENSE diff --git a/src/main/resources/resources/vendors/bootstrap-paginator-1.0.2/README.md b/src/main/resources/static/vendors/bootstrap-paginator-1.0.2/README.md similarity index 100% rename from src/main/resources/resources/vendors/bootstrap-paginator-1.0.2/README.md rename to src/main/resources/static/vendors/bootstrap-paginator-1.0.2/README.md diff --git a/src/main/resources/resources/vendors/bootstrap-paginator-1.0.2/js/bootstrap-paginator.min.js b/src/main/resources/static/vendors/bootstrap-paginator-1.0.2/js/bootstrap-paginator.min.js similarity index 100% rename from src/main/resources/resources/vendors/bootstrap-paginator-1.0.2/js/bootstrap-paginator.min.js rename to src/main/resources/static/vendors/bootstrap-paginator-1.0.2/js/bootstrap-paginator.min.js diff --git a/src/main/resources/resources/vendors/pygments/github.css b/src/main/resources/static/vendors/pygments/github.css similarity index 100% rename from src/main/resources/resources/vendors/pygments/github.css rename to src/main/resources/static/vendors/pygments/github.css diff --git a/src/main/resources/resources/vendors/pygments/monokai.css b/src/main/resources/static/vendors/pygments/monokai.css similarity index 100% rename from src/main/resources/resources/vendors/pygments/monokai.css rename to src/main/resources/static/vendors/pygments/monokai.css diff --git a/src/main/resources/resources/vendors/typo.css-2.1.2/.gitignore b/src/main/resources/static/vendors/typo.css-2.1.2/.gitignore similarity index 100% rename from src/main/resources/resources/vendors/typo.css-2.1.2/.gitignore rename to src/main/resources/static/vendors/typo.css-2.1.2/.gitignore diff --git a/src/main/resources/resources/vendors/typo.css-2.1.2/README.md b/src/main/resources/static/vendors/typo.css-2.1.2/README.md similarity index 100% rename from src/main/resources/resources/vendors/typo.css-2.1.2/README.md rename to src/main/resources/static/vendors/typo.css-2.1.2/README.md diff --git a/src/main/resources/resources/vendors/typo.css-2.1.2/license.txt b/src/main/resources/static/vendors/typo.css-2.1.2/license.txt similarity index 100% rename from src/main/resources/resources/vendors/typo.css-2.1.2/license.txt rename to src/main/resources/static/vendors/typo.css-2.1.2/license.txt diff --git a/src/main/resources/resources/vendors/typo.css-2.1.2/typo.css b/src/main/resources/static/vendors/typo.css-2.1.2/typo.css similarity index 100% rename from src/main/resources/resources/vendors/typo.css-2.1.2/typo.css rename to src/main/resources/static/vendors/typo.css-2.1.2/typo.css diff --git a/src/main/resources/resources/vendors/typo.css-2.1.2/typo.html b/src/main/resources/static/vendors/typo.css-2.1.2/typo.html similarity index 100% rename from src/main/resources/resources/vendors/typo.css-2.1.2/typo.html rename to src/main/resources/static/vendors/typo.css-2.1.2/typo.html diff --git a/src/main/resources/resources/vendors/typo.css-2.1.2/typo.min.css b/src/main/resources/static/vendors/typo.css-2.1.2/typo.min.css similarity index 100% rename from src/main/resources/resources/vendors/typo.css-2.1.2/typo.min.css rename to src/main/resources/static/vendors/typo.css-2.1.2/typo.min.css diff --git a/src/main/resources/templates/admin/home/settings.jade b/src/main/resources/templates/admin/home/settings.jade index 302e91e..5dabed7 100644 --- a/src/main/resources/templates/admin/home/settings.jade +++ b/src/main/resources/templates/admin/home/settings.jade @@ -21,7 +21,17 @@ block content td.setting-label Page Size td.input input.form-control(type="text", name="pageSize", value="#{settings.getPageSize()}") - + + tr + td.setting-label Intro + td.input + input.form-control(type="text", name="intro", value="#{settings.getIntro()}") + + tr + td.setting-label Picture Url + td.input + input.form-control(type="text", name="pictureUrl", value="#{settings.getPictureUrl()}") + tr td.setting-label td.input diff --git a/src/main/resources/templates/admin/layout/admin.jade b/src/main/resources/templates/admin/layout/admin.jade index 235e408..35eceb4 100644 --- a/src/main/resources/templates/admin/layout/admin.jade +++ b/src/main/resources/templates/admin/layout/admin.jade @@ -1,11 +1,23 @@ !!! 5 html(lang="en") - head - include head - block head - - body - include navbar - .container.main-container - include message - block content + head + include head + block head + + body + include navbar + .container-fluid + .row + nav.col-md-2.d-none.d-md-block.bg-light.sidebar + .sidebar-sticky + ul.nav.flex-column + li.nav-item + a.nav-link.active(href="/admin/posts") Posts + li.nav-item + a.nav-link(href="/admin/settings") Settings + li.nav-item + a.nav-link(href="/admin/users/profile") Profile + main.col-md-9.ml-sm-auto.col-lg-10.pt-3.px-4(role="main") + include message + block content + diff --git a/src/main/resources/templates/admin/layout/head.jade b/src/main/resources/templates/admin/layout/head.jade index 7e2cc84..04a4543 100644 --- a/src/main/resources/templates/admin/layout/head.jade +++ b/src/main/resources/templates/admin/layout/head.jade @@ -6,18 +6,13 @@ meta(name="description", content="A simple blog demo with Spring MVC.") meta(name="author", content="Raysmond") title #{App.getSiteName()} -link(rel='stylesheet', type='text/css', href='/webjars/bootstrap/3.3.5/css/bootstrap.min.css') -link(rel='stylesheet', type='text/css', href='/webjars/font-awesome/4.3.0-3/css/font-awesome.min.css') +link(rel='stylesheet', type='text/css', href="/bower_components/bootstrap/dist/css/bootstrap.min.css") +link(rel='stylesheet', type='text/css', href="/bower_components/fontawesome/css/font-awesome.min.css") link(rel="stylesheet", type='text/css', href="/css/admin/admin.css") -link(rel="stylesheet", href="/bower_components/simditor/styles/simditor.css") -link(rel="stylesheet", href="/bower_components/simditor-markdown/styles/simditor-markdown.css") -script(src="/webjars/jquery/2.1.4/jquery.min.js") -script(src="/webjars/bootstrap/3.3.5/js/bootstrap.min.js") -script(src="/vendors/bootstrap-paginator-1.0.2/js/bootstrap-paginator.min.js") +script(src="/bower_components/jquery/dist/jquery.min.js") +script(src="/bower_components/bootstrap/dist/js/bootstrap.min.js") script(src="/bower_components/marked/marked.min.js") -script(src="/bower_components/simple-module/lib/module.js") -script(src="/bower_components/simple-hotkeys/lib/hotkeys.js") script(src="/bower_components/to-markdown/dist/to-markdown.js") -script(src="/bower_components/simditor/lib/simditor.js") -script(src="/bower_components/simditor-markdown/lib/simditor-markdown.js") +script(src="/vendors/bootstrap-paginator-1.0.2/js/bootstrap-paginator.min.js") + diff --git a/src/main/resources/templates/admin/layout/navbar.jade b/src/main/resources/templates/admin/layout/navbar.jade index 64317d5..2f9582f 100644 --- a/src/main/resources/templates/admin/layout/navbar.jade +++ b/src/main/resources/templates/admin/layout/navbar.jade @@ -1,24 +1,8 @@ -nav.navbar.navbar-fixed-top.navbar-default - .container - .navbar-header - button.navbar-toggle.collapsed(type="button", data-toggle="collapse", data-target="#navbar", aria-expanded="false", aria-controls="navbar") - span.sr-only Toggle navigation - span.icon-bar - span.icon-bar - span.icon-bar - a.navbar-brand(href="/admin") - span #{App.getSiteName()} - .collapse.navbar-collapse#navbar - ul.nav.navbar-nav - li: a(href="/admin/posts") Posts - li: a(href="/admin/settings") Settings - li: a(href="/admin/users/profile") Profile - li: a(href="/") Site Home - - ul.nav.navbar-nav.navbar-right.navbar-user - li - a(href="javascript:$('#form').submit();") Logout - - form#form(style="visibility: hidden", method="post", action="/logout") - input(type="hidden", name='_csrf', value='#{_csrf.token}') - +nav.navbar.navbar-dark.sticky-top.bg-dark.flex-md-nowrap.p-0 + a.navbar-brand.col-sm-3.col-md-2.mr-0(href="/") SpringBlog + input.form-control.form-control-dark.w-100(placeholder="Search") + ul.navbar-nav.px-3 + li.nav-item.text-nowrap + a.nav-link(href="javascript:$('#form').submit();") Logout + form#form(style="visibility: hidden", method="post", action="/logout") + input(type="hidden", name='_csrf', value='#{_csrf.token}') diff --git a/src/main/resources/templates/admin/posts/edit.jade b/src/main/resources/templates/admin/posts/edit.jade index abd293c..de5376a 100644 --- a/src/main/resources/templates/admin/posts/edit.jade +++ b/src/main/resources/templates/admin/posts/edit.jade @@ -1,73 +1,96 @@ extends ../layout/admin block head - script(src="/webjars/ace/1.2.0/src-min/ace.js") - script(src="/webjars/ace/1.2.0/src-min/theme-github.js") - script(src="/webjars/ace/1.2.0/src-min/mode-markdown.js") - + script(src="/bower_components/ace-builds/src-min/ace.js") + script(src="/bower_components/ace-builds/src-min/theme-github.js") + script(src="/bower_components/ace-builds/src-min/mode-markdown.js") + block content - h1 Edit Post - hr + h1 Edit Post + hr - form.post-form(method="post",action="/admin/posts/#{post.getId()}") - .item-row - input(type="hidden", name='_csrf', value='#{_csrf.token}') - .item-row - input.form-control(type="text", name="title", value='#{postForm.getTitle()}') - .item-row - textarea.form-control#content(name="content", style="display:none;") - = postForm.getContent() - div#content-editor - #{postForm.getContent()} - .item-row - hr - .row - .col-sm-3 - span Format - select.form-control(name="postFormat") - for format in postFormats - if format != postForm.getPostFormat() - option(value="#{format.getId()}") #{format.getDisplayName()} - else - option(value="#{format.getId()}", selected="selected") #{format.getDisplayName()} - .col-sm-3 - span Status - select.form-control(name="postStatus") - for status in postStatus - if status != postForm.getPostStatus() - option(value="#{status.getId()}") #{status.getName()} - else - option(value="#{status.getId()}", selected="selected") #{status.getName()} - .col-sm-3 - span Permalink - input.form-control(name="permalink", value="#{postForm.getPermalink()}") - - .col-sm-3 - span Tags - input.form-control(name="postTags", value="#{postForm.getPostTags()}") - .item-row - hr - button.btn.btn-primary.btn-block(type="submit") Save + form.post-form(method="post",action="/admin/posts/#{post.getId()}") + .item-row + input(type="hidden", name='_csrf', value='#{_csrf.token}') + .form-group + label 标题 + input.form-control(type="text", name="title", value='#{postForm.getTitle()}') - script - var editor = ace.edit("content-editor"); - editor.setTheme("ace/theme/github"); + .form-group + label 正文 + textarea.form-control#content(name="content", style="display:none;") + = postForm.getContent() + div#content-editor + #{postForm.getContent()} + + .form-group + label 摘要 + textarea.form-control#summary(name="summary", style="display:none;") + = postForm.getSummary() + div#summary-editor + #{postForm.getSummary()} + + .item-row + hr + .row + .col-sm-2 + .form-group + label Type + select.form-control(name="postType") + for item in postTypes + if item != postForm.getPostType() + option(value="#{item.getId()}") #{item.getName()} + else + option(value="#{item.getId()}", selected="selected") #{item.getName()} + .col-sm-2 + .form-group + label Format + select.form-control(name="postFormat") + for format in postFormats + if format != postForm.getPostFormat() + option(value="#{format.getId()}") #{format.getDisplayName()} + else + option(value="#{format.getId()}", selected="selected") #{format.getDisplayName()} + .col-sm-2 + .form-group + label Status + select.form-control(name="postStatus") + for status in postStatus + if status != postForm.getPostStatus() + option(value="#{status.getId()}") #{status.getName()} + else + option(value="#{status.getId()}", selected="selected") #{status.getName()} - var MarkdownMode = ace.require("ace/mode/markdown").Mode; - editor.getSession().setMode(new MarkdownMode()); + .col-sm-2 + .form-group + label Permalink + input.form-control(name="permalink", value="#{postForm.getPermalink()}") + + .col-sm-3 + .form-group + label Tags + input.form-control(name="postTags", value="#{postForm.getPostTags()}") + .item-row + hr + button.btn.btn-primary.btn-lg(type="submit") Save + hr + + script + var editor = ace.edit("content-editor"); + var summaryEditor = ace.edit("summary-editor"); - editor.getSession().setUseWrapMode(true); + editor.setTheme("ace/theme/github"); + summaryEditor.setTheme("ace/theme/github"); - $("form").submit(function(){ - $("#content").val(editor.getValue()); - return true; - }); + var MarkdownMode = ace.require("ace/mode/markdown").Mode; + editor.getSession().setMode(new MarkdownMode()); + editor.getSession().setUseWrapMode(true); - //$(function() { - // var editor = new Simditor({ - // textarea: $('#content'), - // markdown: true, - // toolbar: ['title', 'bold', 'italic', 'underline', 'strikethrough', 'color', '|', 'ol', 'ul', 'blockquote', 'code', 'table', '|', 'link', 'image', 'hr', '|', 'indent', 'outdent', 'alignment', '|', 'markdown'] - // }); - //}); + summaryEditor.getSession().setMode(new MarkdownMode()); + summaryEditor.getSession().setUseWrapMode(true); + $("form").submit(function(){ + $("#content").val(editor.getValue()); + $("#summary").val(summaryEditor.getValue()); + return true; + }); diff --git a/src/main/resources/templates/admin/posts/index.jade b/src/main/resources/templates/admin/posts/index.jade index fa9f861..82571f8 100644 --- a/src/main/resources/templates/admin/posts/index.jade +++ b/src/main/resources/templates/admin/posts/index.jade @@ -8,17 +8,17 @@ block content .row(style="margin-top: 10px;") .col-sm-12 - table.table.table-bordered.table-hover.table-stripped + table.table.table-striped thead tr - th ID + th # th Title th Format th Status th Permalink th Created At th Updated At - th.operations Operations + th Operations tbody for post in posts @@ -28,14 +28,14 @@ block content td #{post.getPostFormat()} td #{post.getPostStatus()} td #{post.getPermalink()} - td #{post.getCreatedAt()} - td #{post.getUpdatedAt()} - td.operations - a.btn.btn-xs.btn-info(href="/posts/#{post.getId()}", target:'_blank') + td #{viewHelper.getFormattedDate(post.getCreatedAt())} + td #{viewHelper.getFormattedDate(post.getUpdatedAt())} + td + a.btn.btn-sm.btn-info.mr-1(href="/posts/#{post.getId()}", target:'_blank') i.fa.fa-eye - a.btn.btn-xs.btn-primary(href="/admin/posts/#{post.getId()}/edit") + a.btn.btn-sm.btn-primary.mr-1(href="/admin/posts/#{post.getId()}/edit") i.fa.fa-edit - a.btn.btn-xs.btn-danger.btn-delete(href="javascript:deletePost(#{post.id})", postId="#{post.id}") + a.btn.btn-sm.btn-danger.btn-delete(href="javascript:deletePost(#{post.id})", postId="#{post.id}") i.fa.fa-trash-o form(id="form-#{post.getId()}",style="visibility: hidden", method="post", action="/admin/posts/#{post.getId()}/delete") diff --git a/src/main/resources/templates/admin/posts/new.jade b/src/main/resources/templates/admin/posts/new.jade index ba02010..a99c2d8 100644 --- a/src/main/resources/templates/admin/posts/new.jade +++ b/src/main/resources/templates/admin/posts/new.jade @@ -1,65 +1,95 @@ extends ../layout/admin block head - script(src="/webjars/ace/1.2.0/src-min/ace.js") - script(src="/webjars/ace/1.2.0/src-min/theme-github.js") - script(src="/webjars/ace/1.2.0/src-min/mode-markdown.js") + script(src="/bower_components/ace-builds/src-min/ace.js") + script(src="/bower_components/ace-builds/src-min/theme-github.js") + script(src="/bower_components/ace-builds/src-min/mode-markdown.js") block content - h1 New Post - hr + h1 New Post + hr - form.post-form(method="post",action="/admin/posts") - .item-row - input(type="hidden", name='_csrf', value='#{_csrf.token}') - .item-row - input.form-control(type="text", name="title", value='#{postForm.getTitle()}') - .item-row - textarea.form-control#content(name="content", style="display:none;") - = postForm.getContent() - div#content-editor - #{postForm.getContent()} - .item-row - hr - .row - .col-sm-3 - span Format - select.form-control(name="postFormat") - for format in postFormats - if format != postForm.getPostFormat() - option(value="#{format.getId()}") #{format.getDisplayName()} - else - option(value="#{format.getId()}", selected="selected") #{format.getDisplayName()} - .col-sm-3 - span Status - select.form-control(name="postStatus") - for status in postStatus - if status != postForm.getPostStatus() - option(value="#{status.getId()}") #{status.getName()} - else - option(value="#{status.getId()}", selected="selected") #{status.getName()} - - .col-sm-3 - span Permalink - input.form-control(name="permalink", value="#{postForm.getPermalink()}") - - .col-sm-3 - span Tags - input.form-control(name="postTags", value="#{postForm.getPostTags()}") - .item-row - hr - button.btn.btn-primary.btn-block(type="submit") Create new post + form.post-form(method="post",action="/admin/posts") + .item-row + input(type="hidden", name='_csrf', value='#{_csrf.token}') + .form-group + label 标题 + input.form-control(type="text", name="title", value='#{postForm.getTitle()}') + + .form-group + label 正文 + textarea.form-control#content(name="content", style="display:none;") + = postForm.getContent() + div#content-editor + #{postForm.getContent()} + + .form-group + label 摘要 + textarea.form-control#summary(name="summary", style="display:none;") + = postForm.getSummary() + div#summary-editor + #{postForm.getSummary()} + .item-row + hr + .row + .col-sm-2 + .form-group + label Type + select.form-control(name="postType") + for item in postTypes + if item != postForm.getPostType() + option(value="#{item.getId()}") #{item.getName()} + else + option(value="#{item.getId()}", selected="selected") #{item.getName()} + .col-sm-2 + .form-group + label Format + select.form-control(name="postFormat") + for format in postFormats + if format != postForm.getPostFormat() + option(value="#{format.getId()}") #{format.getDisplayName()} + else + option(value="#{format.getId()}", selected="selected") #{format.getDisplayName()} + .col-sm-2 + .form-group + label Status + select.form-control(name="postStatus") + for status in postStatus + if status != postForm.getPostStatus() + option(value="#{status.getId()}") #{status.getName()} + else + option(value="#{status.getId()}", selected="selected") #{status.getName()} + + .col-sm-2 + .form-group + label Permalink + input.form-control(name="permalink", value="#{postForm.getPermalink()}") + + .col-sm-3 + .form-group + label Tags + input.form-control(name="postTags", value="#{postForm.getPostTags()}") + .item-row + hr + button.btn.btn-primary.btn-block(type="submit") Create new post + + script + var editor = ace.edit("content-editor"); + var summaryEditor = ace.edit("summary-editor"); + + editor.setTheme("ace/theme/github"); + summaryEditor.setTheme("ace/theme/github"); + + var MarkdownMode = ace.require("ace/mode/markdown").Mode; + editor.getSession().setMode(new MarkdownMode()); + editor.getSession().setUseWrapMode(true); + + summaryEditor.getSession().setMode(new MarkdownMode()); + summaryEditor.getSession().setUseWrapMode(true); + + $("form").submit(function(){ + $("#content").val(editor.getValue()); + $("#summary").val(summaryEditor.getValue()); + return true; + }); - script - var editor = ace.edit("content-editor"); - editor.setTheme("ace/theme/github"); - - var MarkdownMode = ace.require("ace/mode/markdown").Mode; - editor.getSession().setMode(new MarkdownMode()); - - editor.getSession().setUseWrapMode(true); - - $("form").submit(function(){ - $("#content").val(editor.getValue()); - return true; - }); diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html new file mode 100644 index 0000000..fc8b92a --- /dev/null +++ b/src/main/resources/templates/error.html @@ -0,0 +1,23 @@ + + + + + +
+

+ + +

+ +
+ Path: +
+ +
+
+ +
+ + diff --git a/src/main/resources/templates/error.jade b/src/main/resources/templates/error.jade deleted file mode 100644 index 13bbe5e..0000000 --- a/src/main/resources/templates/error.jade +++ /dev/null @@ -1 +0,0 @@ -include error/general \ No newline at end of file diff --git a/src/main/resources/templates/error/general.html b/src/main/resources/templates/error/general.html new file mode 100644 index 0000000..fc8b92a --- /dev/null +++ b/src/main/resources/templates/error/general.html @@ -0,0 +1,23 @@ + + + + + +
+

+ + +

+ +
+ Path: +
+ +
+
+ +
+ + diff --git a/src/main/resources/templates/error/general.jade b/src/main/resources/templates/error/general.jade deleted file mode 100644 index cf93c0e..0000000 --- a/src/main/resources/templates/error/general.jade +++ /dev/null @@ -1,12 +0,0 @@ -extends ../layout/app - -block title - #{status} #{error} - -block page_title - h1 #{status} #{error} - - p Request page: #{path} - - p #{customMessage} - diff --git a/src/main/resources/templates/fragments/disqus.html b/src/main/resources/templates/fragments/disqus.html new file mode 100644 index 0000000..134ce45 --- /dev/null +++ b/src/main/resources/templates/fragments/disqus.html @@ -0,0 +1,30 @@ + + + + + Title + + +
+
+ + + +
+ + diff --git a/src/main/resources/templates/fragments/disqus.jade b/src/main/resources/templates/fragments/disqus.jade deleted file mode 100644 index bfd563c..0000000 --- a/src/main/resources/templates/fragments/disqus.jade +++ /dev/null @@ -1,16 +0,0 @@ -div#disqus_thread - -script - /* * * CONFIGURATION VARIABLES * * */ - var disqus_shortname = 'raysmond'; - - /* * * DON'T EDIT BELOW THIS LINE * * */ - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - -noscript - |Please enable JavaScript to view the - a(href="https://disqus.com/?ref_noscript", rel="nofollow") comments powered by Disqus diff --git a/src/main/resources/templates/fragments/ga.jade b/src/main/resources/templates/fragments/ga.jade deleted file mode 100644 index e5a5f8e..0000000 --- a/src/main/resources/templates/fragments/ga.jade +++ /dev/null @@ -1,8 +0,0 @@ -script - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-34658010-1', 'auto'); - ga('send', 'pageview'); diff --git a/src/main/resources/templates/home/about.jade b/src/main/resources/templates/home/about.jade deleted file mode 100644 index 061d8ad..0000000 --- a/src/main/resources/templates/home/about.jade +++ /dev/null @@ -1,16 +0,0 @@ -extends ../layout/app - -block page_title - h1 #{App.getSiteName()} - p Something about me. - -block title - = viewHelper.metaTitle("About " + App.getSiteName()) - -block content - .post - .content - !{about.getRenderedContent()} - - .comments - include ../fragments/disqus diff --git a/src/main/resources/templates/home/home.html b/src/main/resources/templates/home/home.html new file mode 100644 index 0000000..bb7efd4 --- /dev/null +++ b/src/main/resources/templates/home/home.html @@ -0,0 +1,51 @@ + + + + + +
+ +
+ +

+ Welcome to this site. My name is Raysmond and I'm a backend development engineer in + Shanghai, China. Here, I want to share some topics around development, architecture, DevOps and more.

+
+

最新文章

+
+
+ +
+
+ +

+
+ +
+
+ +
+
+ + +
+ +
+ + diff --git a/src/main/resources/templates/home/index.jade b/src/main/resources/templates/home/index.jade deleted file mode 100644 index 0346867..0000000 --- a/src/main/resources/templates/home/index.jade +++ /dev/null @@ -1,28 +0,0 @@ -extends ../layout/app - -block page_title - .avatar - img(src="/images/user.jpg") - h1 #{App.getSiteName()} - p.meta #{App.getSiteSlogan()} - - p Hi, there. I'm Raysmond, a postgraduate of Computer Science in Fudan University from Shanghai, China. I love developing software and web applications and I blog about them. - -block title - = App.getSiteName() - -block content - // include ../posts/index - - .latest-posts - hr - h2.text-center Latest Posts - for post in posts - .post-item - a(href="/posts/#{post.getPermalink().length() > 0 ? post.getPermalink() : post.getId()}") - h2 #{post.getTitle()} - .meta - span #{viewHelper.getFormattedDate(post.getCreatedAt())} - - .loadmore - a.btn.btn-default(href="/posts/archive") More diff --git a/src/main/resources/templates/layout/app.jade b/src/main/resources/templates/layout/app.jade deleted file mode 100644 index e0d319f..0000000 --- a/src/main/resources/templates/layout/app.jade +++ /dev/null @@ -1,18 +0,0 @@ -!!! 5 -html(lang="en") - head - include head - block head - - body.typo - include navbar - - include top - - .container.main-container - block content - - include footer - - if viewHelper.getApplicationEnv() == "production" - include ../fragments/ga diff --git a/src/main/resources/templates/layout/bootstrap4.html b/src/main/resources/templates/layout/bootstrap4.html new file mode 100644 index 0000000..2115263 --- /dev/null +++ b/src/main/resources/templates/layout/bootstrap4.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + +
+ +
+
+
+
+
+ +
+
+

+ Back to top +

+

All rights reserved © Raysmond, 2015-2018.

+

Lovingly made by SpringBlog, powered by Spring Boot. +

+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/layout/footer.jade b/src/main/resources/templates/layout/footer.jade deleted file mode 100644 index 1ba02e3..0000000 --- a/src/main/resources/templates/layout/footer.jade +++ /dev/null @@ -1,12 +0,0 @@ -.container - .footer - .footer-wrapper - hr - p - | © · 2011-2015   - a(href="http://raysmond.com") Raysmond - | · - - if viewHelper != null - span #{viewHelper.getResponseTime()}ms - diff --git a/src/main/resources/templates/layout/head.jade b/src/main/resources/templates/layout/head.jade deleted file mode 100644 index a93e7f4..0000000 --- a/src/main/resources/templates/layout/head.jade +++ /dev/null @@ -1,19 +0,0 @@ -meta(charset="utf-8") -meta(http-equiv="X-UA-Compatible", content="IE=edge") -meta(name="viewport", content="width=device-width, initial-scale=1") - -meta(name="description", content="#{App.getSiteSlogan()}") - -meta(name="author", content="Raysmond") - -title - block title - = App.getSiteName() - -link(rel='stylesheet', type='text/css', href='/webjars/bootstrap/3.3.5/css/bootstrap.min.css') -link(rel="stylesheet", type='text/css', href="/vendors/pygments/github.css") -link(rel="stylesheet", type='text/css', href="/vendors/typo.css-2.1.2/typo.min.css") -link(rel="stylesheet", type='text/css', href="/css/theme-light.css") - - -script(src="/webjars/jquery/2.1.4/jquery.min.js") diff --git a/src/main/resources/templates/layout/header.html b/src/main/resources/templates/layout/header.html new file mode 100644 index 0000000..11e4b2f --- /dev/null +++ b/src/main/resources/templates/layout/header.html @@ -0,0 +1,29 @@ + + + + + Title + + +
+
+
+
+ + + 后台管理 +
+
+
+
+ + diff --git a/src/main/resources/templates/layout/navbar.jade b/src/main/resources/templates/layout/navbar.jade deleted file mode 100644 index cb6f7da..0000000 --- a/src/main/resources/templates/layout/navbar.jade +++ /dev/null @@ -1,11 +0,0 @@ -nav.navbar.navbar-bright - .container - .navbar-header - a.navbar-brand(href="/") Raysmond - div#navbar - ul.nav.navbar-nav.navbar-right - li: a(href="/posts/archive") Blog - li: a(href="/tags") Tags - li: a(href="/posts/projects") Projects - li: a(href="/about") About - diff --git a/src/main/resources/templates/layout/top.jade b/src/main/resources/templates/layout/top.jade deleted file mode 100644 index 855203e..0000000 --- a/src/main/resources/templates/layout/top.jade +++ /dev/null @@ -1,4 +0,0 @@ -.container - .header-title - .header-title-wrapper - block page_title diff --git a/src/main/resources/templates/posts/archive.html b/src/main/resources/templates/posts/archive.html new file mode 100644 index 0000000..610649a --- /dev/null +++ b/src/main/resources/templates/posts/archive.html @@ -0,0 +1,24 @@ + + + + + + +
+

文章归档

+ +
+
+
+
    +
  • + +
  • +
+
+
+
+ + diff --git a/src/main/resources/templates/posts/archive.jade b/src/main/resources/templates/posts/archive.jade deleted file mode 100644 index 1a5b34c..0000000 --- a/src/main/resources/templates/posts/archive.jade +++ /dev/null @@ -1,24 +0,0 @@ -extends ../layout/app - -block page_title - h1 Posts Archive - p Look back and find something interesting. - -block title - = viewHelper.metaTitle("Posts Archive") - -block content - .post - .content - .year - h2 2015 - ul.archive-list - for post in posts - if post.getCreatedAt().getYear() == (2015 - 1900) - li - span.month - = viewHelper.getMonthAndDay(post.getCreatedAt()) - span   ·  - a(href="/posts/#{post.getPermalink() == null ? post.getId() : post.getPermalink()}") - = post.getTitle() - diff --git a/src/main/resources/templates/posts/index.jade b/src/main/resources/templates/posts/index.jade deleted file mode 100644 index 7bdcc52..0000000 --- a/src/main/resources/templates/posts/index.jade +++ /dev/null @@ -1,22 +0,0 @@ -.posts - for post in posts - .post-item - a(href="/posts/#{post.getPermalink()}") - h2 #{post.getTitle()} - .meta - span #{viewHelper.getFormattedDate(post.getCreatedAt())} - -.load-more - nav - ul.pager - if totalPages > page - li.previous - a.btn(href="?page=#{page+1}") - span(aria-hidden="true) ← - | Older posts - - if page > 1 - li.next - a.btn(href="?page=#{page-1}") - |Newer posts - span(aria-hidden="true) → \ No newline at end of file diff --git a/src/main/resources/templates/posts/post.html b/src/main/resources/templates/posts/post.html new file mode 100644 index 0000000..50f9991 --- /dev/null +++ b/src/main/resources/templates/posts/post.html @@ -0,0 +1,52 @@ + + + + + + +
+
+
+
+

+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + diff --git a/src/main/resources/templates/posts/show.jade b/src/main/resources/templates/posts/show.jade deleted file mode 100644 index 96d65df..0000000 --- a/src/main/resources/templates/posts/show.jade +++ /dev/null @@ -1,27 +0,0 @@ -extends ../layout/app - -block title - = viewHelper.metaTitle(post.getTitle()) - -block page_title - h1 #{post.getTitle()} - p #{viewHelper.getFormattedDate(post.getCreatedAt())} - -block content - .post - .content - !{post.getRenderedContent()} - - .info - if !tags.isEmpty() - ul.tags - li Tags: - for tag in tags - li: a.btn.btn-xs.btn-default(href="/tags/#{tag.getName()}") #{tag.getName()} - - .author - #{post.getCreatedAt() == post.getUpdatedAt() ? "Created" : "Updated"} - | on #{viewHelper.getFormattedDate(post.getCreatedAt())} - - .comments - include ../fragments/disqus diff --git a/src/main/resources/templates/tags/index.html b/src/main/resources/templates/tags/index.html new file mode 100644 index 0000000..43883a8 --- /dev/null +++ b/src/main/resources/templates/tags/index.html @@ -0,0 +1,20 @@ + + + + + + +
+

标签

+ +
+ +
+
+ + diff --git a/src/main/resources/templates/tags/index.jade b/src/main/resources/templates/tags/index.jade deleted file mode 100644 index bc6eb1d..0000000 --- a/src/main/resources/templates/tags/index.jade +++ /dev/null @@ -1,14 +0,0 @@ -extends ../layout/app - -block title - | Tags - -block page_title - h1 Tags - -block content - .post - .content - ul.tags - for tag in tags - li: a.btn.btn-default(href="/tags/#{tag[0]}") #{tag[0]}(#{tag[1]}) diff --git a/src/main/resources/templates/tags/show.jade b/src/main/resources/templates/tags/show.jade deleted file mode 100644 index 8d28f1c..0000000 --- a/src/main/resources/templates/tags/show.jade +++ /dev/null @@ -1,10 +0,0 @@ -extends ../layout/app - -block title - = viewHelper.metaTitle(tag.getName()) - -block page_title - h1 Tag #{tag.getName()} - -block content - include ../posts/index \ No newline at end of file diff --git a/src/main/resources/templates/users/login.html b/src/main/resources/templates/users/login.html new file mode 100644 index 0000000..76167cc --- /dev/null +++ b/src/main/resources/templates/users/login.html @@ -0,0 +1,30 @@ + + + + + +
+

登录

+ +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ + diff --git a/src/main/resources/templates/users/signin.jade b/src/main/resources/templates/users/signin.jade deleted file mode 100644 index 00f5211..0000000 --- a/src/main/resources/templates/users/signin.jade +++ /dev/null @@ -1,15 +0,0 @@ -extends ../layout/app - -block page_title - h1 Admin Signin - -block content - .row - .col-md-4.col-md-offset-4.login-container - .login-panel - form.signin-form(method="post",action="/authenticate") - input(type="hidden", name='_csrf', value='#{_csrf.token}') - input.form-control(type="text",name="username",placeholder="Email") - input.form-control(type="password",name="password",placeholder="Password") - button.btn.btn-primary.btn-block(type="submit") Login -