diff --git a/Dockerfile b/Dockerfile index 8cb50cdc5..f4d1f31af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,14 +2,15 @@ FROM openjdk:8-jre-alpine VOLUME /tmp -ARG JAR_FILE=build/libs/halo.jar +ARG JAR_FILE=build/libs/halo-1.1.0-beta.2.jar ARG PORT=8090 ARG TIME_ZONE=Asia/Shanghai ENV TZ=${TIME_ZONE} +ENV JAVA_OPTS="-Xms256m -Xmx256m" COPY ${JAR_FILE} halo.jar EXPOSE ${PORT} -ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","halo.jar"] \ No newline at end of file +ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -server -jar halo.jar \ No newline at end of file diff --git a/README.md b/README.md index 0ebeb83a8..0ecbfeaef 100755 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@

Halo

-> Halo 是一款现代化的个人独立博客系统,给习惯写博客的同学一个更好的选择。 +> Halo 是一款现代化的个人独立博客系统,给习惯写博客的同学多一个选择。 + +

JDK GitHub release GitHub All Releases -GitHub commit activity +Docker pulls GitHub last commit Travis CI

@@ -19,26 +21,26 @@ 轻快,简洁,功能强大,使用 Java 开发的博客系统。 -> [官网论坛](https://bbs.halo.run) | [QQ 交流群](https://jq.qq.com/?_wv=1027&k=5tnr930) | [Telegram 交流群](https://t.me/HaloBlog) | [Telegram 频道](https://t.me/halo_dev) | [WeHalo 小程序](https://github.com/aquanlerou/WeHalo)。 +> [官方社区](https://bbs.halo.run) | [QQ 交流群](https://jq.qq.com/?_wv=1027&k=5tnr930) | [Telegram 交流群](https://t.me/HaloBlog) | [Telegram 频道](https://t.me/halo_dev) | [WeHalo 小程序](https://github.com/aquanlerou/WeHalo)。 ## 快速开始 ### 下载最新的 Halo 安装包 ```bash -curl -L https://github.com/halo-dev/halo/releases/download/v1.0.3/halo-1.0.3.jar --output halo-latest.jar +curl -L https://github.com/halo-dev/halo/releases/download/v1.1.0/halo-1.1.0.jar --output halo-latest.jar ``` 或者 ```bash -wget https://github.com/halo-dev/halo/releases/download/v1.0.3/halo-1.0.3.jar -O halo-latest.jar +wget https://github.com/halo-dev/halo/releases/download/v1.1.0/halo-1.1.0.jar -O halo-latest.jar ``` ### 启动 Halo ```bash -nohup java -jar halo-latest.jar >/dev/null 2>&1& +java -jar halo-latest.jar ``` 详细文档请移步: @@ -70,32 +72,32 @@ nohup java -jar halo-latest.jar >/dev/null 2>&1& ## 预览图 -![anatole.png](https://i.loli.net/2019/05/08/5cd2fc3e478dc.png) +![theme-anatole.png](https://i.loli.net/2019/09/11/OQtKEWcCe8xYBph.png) -![casper.png](https://i.loli.net/2019/05/08/5cd2fc3e4e587.png) +![theme-walker.png](https://i.loli.net/2019/09/11/F6CjZKJX1N2x5dU.png) -![material.png](https://i.loli.net/2019/05/08/5cd2fc3e78012.png) +![theme-icarus.png](https://i.loli.net/2019/09/11/4lO2wNCLiqyIJmR.png) -![admin-install.png](https://i.loli.net/2019/05/08/5cd2fcae89cae.png) +![theme-destiny.png](https://i.loli.net/2019/09/11/q4t86cCPUEwlGMn.png) -![admin-login.png](https://i.loli.net/2019/05/08/5cd2fcae6a3fa.png) +![install.png](https://i.loli.net/2019/09/11/Iu1eMzZDg6frw97.png) -![admin-dashboard.png](https://i.loli.net/2019/05/08/5cd2fcaf1b2d2.png) +![admin-login.png](https://i.loli.net/2019/09/11/3CahVJAvXngwiQu.png) -![admin-posts.png](https://i.loli.net/2019/05/08/5cd2fcaf18c37.png) +![admin-dashboard.png](https://i.loli.net/2019/09/11/G5R6bOweYoiZKEv.png) -![admin-write.png](https://i.loli.net/2019/05/08/5cd2fcae9bea2.png) +![admin-posts.png](https://i.loli.net/2019/09/11/uMHSeIkmC4iPzfJ.png) -![admin-write1.png](https://i.loli.net/2019/05/08/5cd2fcaf6a1ac.png) +![admin-post-edit.png](https://i.loli.net/2019/09/11/ucDh2tOZLJGTyHX.png) -![admin-write2.png](https://i.loli.net/2019/05/08/5cd2fcb08ea2d.png) +![admin-post-publish.png](https://i.loli.net/2019/09/11/UwC3ecsRpAMONgq.png) -![admin-attachment.png](https://i.loli.net/2019/05/08/5cd2fcb1c752e.png) +![admin-post-settings.png](https://i.loli.net/2019/09/11/bxmJ7OTirtvV4Gs.png) -![admin-themes.png](https://i.loli.net/2019/05/08/5cd2fcb21fc76.png) +![admin-attachment.png](https://i.loli.net/2019/09/11/B5UlX6vAgZ3bzaJ.png) -![admin-theme-settings.png](https://i.loli.net/2019/05/08/5cd2fcb04f9c3.png) +![admin-profile.png](https://i.loli.net/2019/09/11/mMageUXv5EDpfJQ.png) -![admin-profile.png](https://i.loli.net/2019/05/08/5cd2fccee89f5.png) +![admin-themes.png](https://i.loli.net/2019/09/11/FVA953Ljswd4c8G.png) -![admin-options.png](https://i.loli.net/2019/05/08/5cd2fccedc309.png) +![admin-options.png](https://i.loli.net/2019/09/11/TK6SeMdVj1xcrDw.png) \ No newline at end of file diff --git a/build.gradle b/build.gradle index 149d7876c..dd059e8f9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.springframework.boot' version '2.1.3.RELEASE' + id 'org.springframework.boot' version '2.1.7.RELEASE' id "io.freefair.lombok" version "3.6.6" // id 'war' id 'java' @@ -9,7 +9,7 @@ apply plugin: 'io.spring.dependency-management' group = 'run.halo.app' archivesBaseName = 'halo' -version = '1.0.3' +version = '1.1.0' sourceCompatibility = '1.8' description = 'Halo, personal blog system developed in Java.' @@ -24,6 +24,7 @@ repositories { configurations { implementation { exclude module: 'spring-boot-starter-tomcat' + exclude module: 'slf4j-log4j12' } developmentOnly @@ -40,6 +41,23 @@ bootJar { } } +ext { + ohMyEmailVersion = '0.0.4' + hutoolVersion = '4.6.3' + upyunSdkVersion = '4.0.1' + qiniuSdkVersion = '7.2.18' + aliyunSdkVersion = '3.4.2' + baiduSdkVersion = '0.10.36' + qcloudSdkVersion = '5.5.7' + swaggerVersion = '2.9.2' + commonsLangVersion = '3.8.1' + httpclientVersion = '4.5.7' + dataformatYamlVersion = '2.9.2' + jgitVersion = '5.3.0.201903130848-r' + flexmarkVersion = '0.42.12' + thumbnailatorVersion = '0.4.8' +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' @@ -47,34 +65,37 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-undertow' implementation 'org.springframework.boot:spring-boot-starter-freemarker' - implementation 'io.github.biezhi:oh-my-email:0.0.4' - implementation 'cn.hutool:hutool-core:4.5.0' - implementation 'cn.hutool:hutool-crypto:4.5.0' - implementation 'cn.hutool:hutool-extra:4.5.0' - implementation 'com.upyun:java-sdk:4.0.1' - implementation 'com.qiniu:qiniu-java-sdk:7.2.18' - implementation 'com.aliyun.oss:aliyun-sdk-oss:3.4.2' - implementation 'net.coobird:thumbnailator:0.4.8' - implementation 'io.springfox:springfox-swagger2:2.9.2' - implementation 'io.springfox:springfox-swagger-ui:2.9.2' - implementation 'org.apache.commons:commons-lang3:3.8.1' - implementation 'org.apache.httpcomponents:httpclient:4.5.7' - implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.2' - implementation 'org.eclipse.jgit:org.eclipse.jgit:5.3.0.201903130848-r' + implementation "io.github.biezhi:oh-my-email:$ohMyEmailVersion" + implementation "cn.hutool:hutool-core:$hutoolVersion" + implementation "cn.hutool:hutool-crypto:$hutoolVersion" + implementation "cn.hutool:hutool-extra:$hutoolVersion" + implementation "com.upyun:java-sdk:$upyunSdkVersion" + implementation "com.qiniu:qiniu-java-sdk:$qiniuSdkVersion" + implementation "com.aliyun.oss:aliyun-sdk-oss:$aliyunSdkVersion" + implementation "com.baidubce:bce-java-sdk:$baiduSdkVersion" + implementation "com.qcloud:cos_api:$qcloudSdkVersion" + implementation "io.springfox:springfox-swagger2:$swaggerVersion" + implementation "io.springfox:springfox-swagger-ui:$swaggerVersion" + implementation "org.apache.commons:commons-lang3:$commonsLangVersion" + implementation "org.apache.httpcomponents:httpclient:$httpclientVersion" + implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$dataformatYamlVersion" + implementation "org.eclipse.jgit:org.eclipse.jgit:$jgitVersion" - implementation 'com.vladsch.flexmark:flexmark:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-attributes:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-autolink:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-emoji:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-escaped-character:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-gfm-tasklist:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-ins:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-media-tags:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-tables:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-toc:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-ext-yaml-front-matter:0.42.12' - implementation 'com.vladsch.flexmark:flexmark-html-parser:0.42.12' + implementation "com.vladsch.flexmark:flexmark:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-attributes:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-autolink:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-emoji:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-escaped-character:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-gfm-tasklist:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-ins:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-media-tags:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-tables:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-toc:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-ext-yaml-front-matter:$flexmarkVersion" + implementation "com.vladsch.flexmark:flexmark-html-parser:$flexmarkVersion" + + implementation "net.coobird:thumbnailator:$thumbnailatorVersion" runtimeOnly 'com.h2database:h2' runtimeOnly 'mysql:mysql-connector-java' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 135367700..87b738cbd 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 56c603f58..ba9e3b178 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Apr 23 17:12:17 CST 2019 +#Mon Sep 09 12:27:59 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/gradlew b/gradlew index cccdd3d51..af6708ff2 100755 --- a/gradlew +++ b/gradlew @@ -28,7 +28,7 @@ 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="" +DEFAULT_JVM_OPTS='"-Xmx64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/gradlew.bat b/gradlew.bat index e95643d6a..0f8d5937c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ 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= +set DEFAULT_JVM_OPTS="-Xmx64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/src/main/java/run/halo/app/cache/InMemoryCacheStore.java b/src/main/java/run/halo/app/cache/InMemoryCacheStore.java index 4ef7789a1..b492c2215 100644 --- a/src/main/java/run/halo/app/cache/InMemoryCacheStore.java +++ b/src/main/java/run/halo/app/cache/InMemoryCacheStore.java @@ -27,7 +27,7 @@ public class InMemoryCacheStore extends StringCacheStore { /** * Cache container. */ - private final static ConcurrentHashMap> cacheContainer = new ConcurrentHashMap<>(); + private final static ConcurrentHashMap> CACHE_CONTAINER = new ConcurrentHashMap<>(); private final Timer timer; @@ -46,7 +46,7 @@ public class InMemoryCacheStore extends StringCacheStore { Optional> getInternal(String key) { Assert.hasText(key, "Cache key must not be blank"); - return Optional.ofNullable(cacheContainer.get(key)); + return Optional.ofNullable(CACHE_CONTAINER.get(key)); } @Override @@ -55,7 +55,7 @@ public class InMemoryCacheStore extends StringCacheStore { Assert.notNull(cacheWrapper, "Cache wrapper must not be null"); // Put the cache wrapper - CacheWrapper putCacheWrapper = cacheContainer.put(key, cacheWrapper); + CacheWrapper putCacheWrapper = CACHE_CONTAINER.put(key, cacheWrapper); log.debug("Put [{}] cache result: [{}], original cache wrapper: [{}]", key, putCacheWrapper, cacheWrapper); } @@ -90,7 +90,7 @@ public class InMemoryCacheStore extends StringCacheStore { public void delete(String key) { Assert.hasText(key, "Cache key must not be blank"); - cacheContainer.remove(key); + CACHE_CONTAINER.remove(key); log.debug("Removed key: [{}]", key); } @@ -110,7 +110,7 @@ public class InMemoryCacheStore extends StringCacheStore { @Override public void run() { - cacheContainer.keySet().forEach(key -> { + CACHE_CONTAINER.keySet().forEach(key -> { if (!InMemoryCacheStore.this.get(key).isPresent()) { log.debug("Deleted the cache: [{}] for expiration", key); } diff --git a/src/main/java/run/halo/app/config/HaloConfiguration.java b/src/main/java/run/halo/app/config/HaloConfiguration.java index 0b3d8f627..9b5be4d5b 100644 --- a/src/main/java/run/halo/app/config/HaloConfiguration.java +++ b/src/main/java/run/halo/app/config/HaloConfiguration.java @@ -1,6 +1,7 @@ package run.halo.app.config; import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; @@ -36,6 +37,7 @@ import java.security.NoSuchAlgorithmException; */ @Configuration @EnableConfigurationProperties(HaloProperties.class) +@Slf4j public class HaloConfiguration { private final static int TIMEOUT = 5000; @@ -80,7 +82,6 @@ public class HaloConfiguration { * * @return Log filter registration bean */ - @Bean public FilterRegistrationBean logFilter() { FilterRegistrationBean logFilter = new FilterRegistrationBean<>(); @@ -149,7 +150,10 @@ public class HaloConfiguration { "/api/admin/login", "/api/admin/refresh/*", "/api/admin/installations", - "/api/admin/recoveries/migrations/*" + "/api/admin/recoveries/migrations/*", + "/api/admin/is_installed", + "/api/admin/password/code", + "/api/admin/password/reset" ); adminAuthenticationFilter.setFailureHandler( failureHandler); diff --git a/src/main/java/run/halo/app/config/WebMvcAutoConfiguration.java b/src/main/java/run/halo/app/config/WebMvcAutoConfiguration.java index 3207be57b..e3f48fe4b 100644 --- a/src/main/java/run/halo/app/config/WebMvcAutoConfiguration.java +++ b/src/main/java/run/halo/app/config/WebMvcAutoConfiguration.java @@ -22,7 +22,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver; import run.halo.app.config.properties.HaloProperties; -import run.halo.app.controller.support.PageJacksonSerializer; +import run.halo.app.core.PageJacksonSerializer; import run.halo.app.factory.StringToEnumConverterFactory; import run.halo.app.model.support.HaloConst; import run.halo.app.security.resolver.AuthenticationArgumentResolver; diff --git a/src/main/java/run/halo/app/controller/admin/api/AdminController.java b/src/main/java/run/halo/app/controller/admin/api/AdminController.java index 0586dcfc9..0c5f0b332 100644 --- a/src/main/java/run/halo/app/controller/admin/api/AdminController.java +++ b/src/main/java/run/halo/app/controller/admin/api/AdminController.java @@ -8,9 +8,12 @@ import run.halo.app.cache.lock.CacheLock; import run.halo.app.model.dto.EnvironmentDTO; import run.halo.app.model.dto.StatisticDTO; import run.halo.app.model.params.LoginParam; +import run.halo.app.model.params.ResetPasswordParam; +import run.halo.app.model.properties.PrimaryProperties; import run.halo.app.model.support.BaseResponse; import run.halo.app.security.token.AuthToken; import run.halo.app.service.AdminService; +import run.halo.app.service.OptionService; import javax.validation.Valid; @@ -28,8 +31,50 @@ public class AdminController { private final AdminService adminService; - public AdminController(AdminService adminService) { + private final OptionService optionService; + + public AdminController(AdminService adminService, OptionService optionService) { this.adminService = adminService; + this.optionService = optionService; + } + + @GetMapping(value = "/is_installed") + @ApiOperation("Check install status") + public boolean isInstall() { + return optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false); + } + + @PostMapping("login") + @ApiOperation("Login") + @CacheLock(autoDelete = false) + public AuthToken auth(@RequestBody @Valid LoginParam loginParam) { + return adminService.authenticate(loginParam); + } + + @PostMapping("logout") + @ApiOperation("Logs out (Clear session)") + @CacheLock(autoDelete = false) + public void logout() { + adminService.clearToken(); + } + + @PostMapping("password/code") + @ApiOperation("Send reset password verify code.") + public void sendResetCode(@RequestBody @Valid ResetPasswordParam param) { + adminService.sendResetPasswordCode(param); + } + + @PutMapping("password/reset") + @ApiOperation("Reset password by verify code.") + public void resetPassword(@RequestBody @Valid ResetPasswordParam param) { + adminService.resetPasswordByCode(param); + } + + @PostMapping("refresh/{refreshToken}") + @ApiOperation("Refreshes token") + @CacheLock(autoDelete = false) + public AuthToken refresh(@PathVariable("refreshToken") String refreshToken) { + return adminService.refreshToken(refreshToken); } /** @@ -49,27 +94,6 @@ public class AdminController { return adminService.getEnvironments(); } - @PostMapping("login") - @ApiOperation("Login") - @CacheLock(autoDelete = false) - public AuthToken auth(@RequestBody @Valid LoginParam loginParam) { - return adminService.authenticate(loginParam); - } - - @PostMapping("logout") - @ApiOperation("Logs out (Clear session)") - @CacheLock(autoDelete = false) - public void logout() { - adminService.clearToken(); - } - - @PostMapping("refresh/{refreshToken}") - @ApiOperation("Refreshes token") - @CacheLock(autoDelete = false) - public AuthToken refresh(@PathVariable("refreshToken") String refreshToken) { - return adminService.refreshToken(refreshToken); - } - @PutMapping("halo-admin") @ApiOperation("Updates halo-admin manually") public void updateAdmin() { @@ -79,6 +103,6 @@ public class AdminController { @GetMapping("spring/logs") @ApiOperation("Get application logs") public BaseResponse getSpringLogs() { - return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(),adminService.getSpringLogs()); + return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), adminService.getSpringLogs()); } } diff --git a/src/main/java/run/halo/app/controller/admin/api/AttachmentController.java b/src/main/java/run/halo/app/controller/admin/api/AttachmentController.java index 17efea70f..cbf4e5199 100644 --- a/src/main/java/run/halo/app/controller/admin/api/AttachmentController.java +++ b/src/main/java/run/halo/app/controller/admin/api/AttachmentController.java @@ -23,7 +23,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC; * Attachment controller. * * @author johnniang - * @date 3/21/19 + * @date 2019-03-21 */ @RestController @RequestMapping("/api/admin/attachments") diff --git a/src/main/java/run/halo/app/controller/admin/api/BackupController.java b/src/main/java/run/halo/app/controller/admin/api/BackupController.java index 8f7dca762..6f860121e 100644 --- a/src/main/java/run/halo/app/controller/admin/api/BackupController.java +++ b/src/main/java/run/halo/app/controller/admin/api/BackupController.java @@ -29,14 +29,9 @@ public class BackupController { this.backupService = backupService; } - @PostMapping("import/markdowns") - @ApiOperation("Import markdowns") - public List backupMarkdowns(@RequestPart("files") MultipartFile[] files) throws IOException { - List result = new LinkedList<>(); - for (MultipartFile file : files) { - BasePostDetailDTO post = backupService.importMarkdowns(file); - result.add(post); - } - return result; + @PostMapping("import/markdown") + @ApiOperation("Import markdown") + public BasePostDetailDTO backupMarkdowns(@RequestPart("file") MultipartFile file) throws IOException { + return backupService.importMarkdown(file); } } diff --git a/src/main/java/run/halo/app/controller/admin/api/InstallController.java b/src/main/java/run/halo/app/controller/admin/api/InstallController.java index 5465b0585..2ad1661ba 100644 --- a/src/main/java/run/halo/app/controller/admin/api/InstallController.java +++ b/src/main/java/run/halo/app/controller/admin/api/InstallController.java @@ -1,5 +1,7 @@ package run.halo.app.controller.admin.api; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.crypto.SecureUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationEventPublisher; @@ -144,9 +146,9 @@ public class InstallController { PostCommentParam commentParam = new PostCommentParam(); commentParam.setAuthor("Halo Bot"); - commentParam.setAuthorUrl("https://github.com/halo-dev/halo"); + commentParam.setAuthorUrl("https://halo.run"); commentParam.setContent("欢迎使用 Halo,这是你的第一条评论。"); - commentParam.setEmail("i@ryanc.cc"); + commentParam.setEmail("halo@halo.run"); commentParam.setPostId(post.getId()); return postCommentService.create(commentParam.convertTo()); } @@ -196,11 +198,15 @@ public class InstallController { installParam.update(user); // Set password manually userService.setPassword(user, installParam.getPassword()); - // Set default avatar - userService.setDefaultAvatar(user); // Update user return userService.update(user); - }).orElseGet(() -> userService.createBy(installParam)); + }).orElseGet(() -> { + StrBuilder gravatar = new StrBuilder("//cn.gravatar.com/avatar/"); + gravatar.append(SecureUtil.md5(installParam.getEmail())); + gravatar.append("?s=256&d=mm"); + installParam.setAvatar(gravatar.toString()); + return userService.createBy(installParam); + }); } private void initSettings(InstallParam installParam) { diff --git a/src/main/java/run/halo/app/controller/admin/api/JournalController.java b/src/main/java/run/halo/app/controller/admin/api/JournalController.java index 708518113..d4fe65f50 100644 --- a/src/main/java/run/halo/app/controller/admin/api/JournalController.java +++ b/src/main/java/run/halo/app/controller/admin/api/JournalController.java @@ -51,7 +51,7 @@ public class JournalController { @GetMapping @ApiOperation("Lists journals") - public Page pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable, + public Page pageBy(@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable, JournalQuery journalQuery) { Page journalPage = journalService.pageBy(journalQuery, pageable); return journalService.convertToCmtCountDto(journalPage); diff --git a/src/main/java/run/halo/app/controller/admin/api/MenuController.java b/src/main/java/run/halo/app/controller/admin/api/MenuController.java index 9d25b72e8..d85e9d7c8 100644 --- a/src/main/java/run/halo/app/controller/admin/api/MenuController.java +++ b/src/main/java/run/halo/app/controller/admin/api/MenuController.java @@ -40,7 +40,7 @@ public class MenuController { @GetMapping("tree_view") @ApiOperation("List as category tree") - public List listAsTree(@SortDefault(sort = "name", direction = ASC) Sort sort) { + public List listAsTree(@SortDefault(sort = "priority", direction = ASC) Sort sort) { return menuService.listAsTree(sort); } @@ -79,6 +79,13 @@ public class MenuController { @DeleteMapping("{menuId:\\d+}") @ApiOperation("Deletes a menu") public MenuDTO deleteBy(@PathVariable("menuId") Integer menuId) { + List menus = menuService.listByParentId(menuId); + if (null != menus && menus.size() > 0) { + menus.forEach(menu -> { + menu.setParentId(0); + menuService.update(menu); + }); + } return new MenuDTO().convertFrom(menuService.removeById(menuId)); } } diff --git a/src/main/java/run/halo/app/controller/admin/api/OptionController.java b/src/main/java/run/halo/app/controller/admin/api/OptionController.java index cc75de97c..ee572e29e 100644 --- a/src/main/java/run/halo/app/controller/admin/api/OptionController.java +++ b/src/main/java/run/halo/app/controller/admin/api/OptionController.java @@ -49,7 +49,7 @@ public class OptionController { @PostMapping("map_view/saving") @ApiOperation("Saves options by option map") - public void saveOptionsWithMapView(@RequestBody Map optionMap) { + public void saveOptionsWithMapView(@RequestBody Map optionMap) { optionService.save(optionMap); } diff --git a/src/main/java/run/halo/app/controller/admin/api/PhotoController.java b/src/main/java/run/halo/app/controller/admin/api/PhotoController.java index 3b8f5018e..98f5dc2ed 100644 --- a/src/main/java/run/halo/app/controller/admin/api/PhotoController.java +++ b/src/main/java/run/halo/app/controller/admin/api/PhotoController.java @@ -22,7 +22,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC; * Photo controller * * @author ryanwang - * @date : 2019/3/21 + * @date : 2019-3-21 */ @RestController @RequestMapping("/api/admin/photos") @@ -92,4 +92,10 @@ public class PhotoController { // Update menu in database return new PhotoDTO().convertFrom(photoService.update(photo)); } + + @GetMapping("teams") + @ApiOperation("Lists all of photo teams") + public List listTeams() { + return photoService.listAllTeams(); + } } diff --git a/src/main/java/run/halo/app/controller/admin/api/PostCommentController.java b/src/main/java/run/halo/app/controller/admin/api/PostCommentController.java index 0758a070a..6c7a16e3d 100644 --- a/src/main/java/run/halo/app/controller/admin/api/PostCommentController.java +++ b/src/main/java/run/halo/app/controller/admin/api/PostCommentController.java @@ -13,6 +13,7 @@ import run.halo.app.model.params.PostCommentParam; import run.halo.app.model.vo.PostCommentWithPostVO; import run.halo.app.service.PostCommentService; +import javax.validation.Valid; import java.util.List; import static org.springframework.data.domain.Sort.Direction.DESC; @@ -21,6 +22,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC; * Post comment controller. * * @author johnniang + * @author ryanwang * @date 3/19/19 */ @RestController @@ -74,4 +76,21 @@ public class PostCommentController { PostComment deletedPostComment = postCommentService.removeById(commentId); return postCommentService.convertTo(deletedPostComment); } + + @GetMapping("{commentId:\\d+}") + @ApiOperation("Gets a post comment by comment id") + public PostCommentWithPostVO getBy(@PathVariable("commentId") Long commentId) { + PostComment comment = postCommentService.getById(commentId); + return postCommentService.convertToWithPostVo(comment); + } + + @PutMapping("{commentId:\\d+}") + public BaseCommentDTO updateBy(@Valid @RequestBody PostCommentParam commentParam, + @PathVariable("commentId") Long commentId) { + PostComment commentToUpdate = postCommentService.getById(commentId); + + commentParam.update(commentToUpdate); + + return postCommentService.convertTo(postCommentService.update(commentToUpdate)); + } } diff --git a/src/main/java/run/halo/app/controller/admin/api/PostController.java b/src/main/java/run/halo/app/controller/admin/api/PostController.java index d69f1ff25..539a2a54a 100644 --- a/src/main/java/run/halo/app/controller/admin/api/PostController.java +++ b/src/main/java/run/halo/app/controller/admin/api/PostController.java @@ -1,10 +1,15 @@ package run.halo.app.controller.admin.api; +import cn.hutool.core.util.IdUtil; import io.swagger.annotations.ApiOperation; 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.data.web.PageableDefault; +import org.springframework.data.web.SortDefault; import org.springframework.web.bind.annotation.*; +import run.halo.app.cache.StringCacheStore; import run.halo.app.model.dto.post.BasePostMinimalDTO; import run.halo.app.model.dto.post.BasePostSimpleDTO; import run.halo.app.model.entity.Post; @@ -13,10 +18,12 @@ import run.halo.app.model.params.PostParam; import run.halo.app.model.params.PostQuery; import run.halo.app.model.vo.PostDetailVO; import run.halo.app.model.vo.PostListVO; +import run.halo.app.service.OptionService; import run.halo.app.service.PostService; import javax.validation.Valid; import java.util.List; +import java.util.concurrent.TimeUnit; import static org.springframework.data.domain.Sort.Direction.DESC; @@ -24,6 +31,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC; * Post controller. * * @author johnniang + * @author ryanwang * @date 3/19/19 */ @RestController @@ -32,14 +40,27 @@ public class PostController { private final PostService postService; - public PostController(PostService postService) { + private final StringCacheStore cacheStore; + + private final OptionService optionService; + + public PostController(PostService postService, + StringCacheStore cacheStore, + OptionService optionService) { this.postService = postService; + this.cacheStore = cacheStore; + this.optionService = optionService; } @GetMapping @ApiOperation("Lists posts") - public Page pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable, + public Page pageBy(Integer page, Integer size, + @SortDefault.SortDefaults({ + @SortDefault(sort = "topPriority", direction = DESC), + @SortDefault(sort = "createTime", direction = DESC) + }) Sort sort, PostQuery postQuery) { + Pageable pageable = PageRequest.of(page, size, sort); Page postPage = postService.pageBy(postQuery, pageable); return postService.convertToListVo(postPage); } @@ -54,7 +75,7 @@ public class PostController { @ApiOperation("Gets a page of post by post status") public Page pageByStatus(@PathVariable(name = "status") PostStatus status, @RequestParam(value = "more", required = false, defaultValue = "false") Boolean more, - @PageableDefault(sort = "editTime", direction = DESC) Pageable pageable) { + @PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) { Page posts = postService.pageBy(status, pageable); if (more) { @@ -116,4 +137,16 @@ public class PostController { postService.removeById(postId); } + @GetMapping("preview/{postId:\\d+}") + public String preview(@PathVariable("postId") Integer postId) { + Post post = postService.getById(postId); + + String token = IdUtil.simpleUUID(); + + // cache preview token + cacheStore.putAny("preview-post-token-" + postId, token, 10, TimeUnit.MINUTES); + + // build preview post url and return + return String.format("%s/archives/%s?preview=true&token=%s", optionService.getBlogBaseUrl(), post.getUrl(), token); + } } diff --git a/src/main/java/run/halo/app/controller/admin/api/SheetCommentController.java b/src/main/java/run/halo/app/controller/admin/api/SheetCommentController.java index 4b648c8a1..ce8445249 100644 --- a/src/main/java/run/halo/app/controller/admin/api/SheetCommentController.java +++ b/src/main/java/run/halo/app/controller/admin/api/SheetCommentController.java @@ -13,6 +13,7 @@ import run.halo.app.model.params.SheetCommentParam; import run.halo.app.model.vo.SheetCommentWithSheetVO; import run.halo.app.service.SheetCommentService; +import javax.validation.Valid; import java.util.List; import static org.springframework.data.domain.Sort.Direction.DESC; @@ -21,6 +22,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC; * Sheet comment controller. * * @author johnniang + * @author ryanwang * @date 19-4-25 */ @RestController @@ -37,14 +39,14 @@ public class SheetCommentController { public Page pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable, CommentQuery commentQuery) { Page sheetCommentPage = sheetCommentService.pageBy(commentQuery, pageable); - return sheetCommentService.convertToWithPostVo(sheetCommentPage); + return sheetCommentService.convertToWithSheetVo(sheetCommentPage); } @GetMapping("latest") public List listLatest(@RequestParam(name = "top", defaultValue = "10") int top, @RequestParam(name = "status", required = false) CommentStatus status) { Page sheetCommentPage = sheetCommentService.pageLatest(top, status); - return sheetCommentService.convertToWithPostVo(sheetCommentPage.getContent()); + return sheetCommentService.convertToWithSheetVo(sheetCommentPage.getContent()); } @PostMapping @@ -69,4 +71,21 @@ public class SheetCommentController { SheetComment deletedSheetComment = sheetCommentService.removeById(commentId); return sheetCommentService.convertTo(deletedSheetComment); } + + @GetMapping("{commentId:\\d+}") + @ApiOperation("Gets a post comment by comment id") + public SheetCommentWithSheetVO getBy(@PathVariable("commentId") Long commentId) { + SheetComment comment = sheetCommentService.getById(commentId); + return sheetCommentService.convertToWithSheetVo(comment); + } + + @PutMapping("{commentId:\\d+}") + public BaseCommentDTO updateBy(@Valid @RequestBody SheetCommentParam commentParam, + @PathVariable("commentId") Long commentId) { + SheetComment commentToUpdate = sheetCommentService.getById(commentId); + + commentParam.update(commentToUpdate); + + return sheetCommentService.convertTo(sheetCommentService.update(commentToUpdate)); + } } diff --git a/src/main/java/run/halo/app/controller/admin/api/SheetController.java b/src/main/java/run/halo/app/controller/admin/api/SheetController.java index 36c2333f2..653774037 100644 --- a/src/main/java/run/halo/app/controller/admin/api/SheetController.java +++ b/src/main/java/run/halo/app/controller/admin/api/SheetController.java @@ -1,20 +1,24 @@ package run.halo.app.controller.admin.api; +import cn.hutool.core.util.IdUtil; import io.swagger.annotations.ApiOperation; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.*; +import run.halo.app.cache.StringCacheStore; import run.halo.app.model.dto.InternalSheetDTO; import run.halo.app.model.dto.post.BasePostDetailDTO; import run.halo.app.model.entity.Sheet; import run.halo.app.model.enums.PostStatus; import run.halo.app.model.params.SheetParam; import run.halo.app.model.vo.SheetListVO; +import run.halo.app.service.OptionService; import run.halo.app.service.SheetService; import javax.validation.Valid; import java.util.List; +import java.util.concurrent.TimeUnit; import static org.springframework.data.domain.Sort.Direction.DESC; @@ -31,8 +35,16 @@ public class SheetController { private final SheetService sheetService; - public SheetController(SheetService sheetService) { + private final StringCacheStore cacheStore; + + private final OptionService optionService; + + public SheetController(SheetService sheetService, + StringCacheStore cacheStore, + OptionService optionService) { this.sheetService = sheetService; + this.cacheStore = cacheStore; + this.optionService = optionService; } @GetMapping("{sheetId:\\d+}") @@ -44,7 +56,7 @@ public class SheetController { @GetMapping @ApiOperation("Gets a page of sheet") - public Page pageBy(@PageableDefault(sort = "editTime", direction = DESC) Pageable pageable) { + public Page pageBy(@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) { Page sheetPage = sheetService.pageBy(pageable); return sheetService.convertToListVo(sheetPage); } @@ -97,4 +109,17 @@ public class SheetController { Sheet sheet = sheetService.removeById(sheetId); return sheetService.convertToDetail(sheet); } + + @GetMapping("preview/{sheetId:\\d+}") + public String preview(@PathVariable("sheetId") Integer sheetId) { + Sheet sheet = sheetService.getById(sheetId); + + String token = IdUtil.simpleUUID(); + + // cache preview token + cacheStore.putAny("preview-sheet-token-" + sheetId, token, 10, TimeUnit.MINUTES); + + // build preview post url and return + return String.format("%s/s/%s?preview=true&token=%s", optionService.getBlogBaseUrl(), sheet.getUrl(), token); + } } diff --git a/src/main/java/run/halo/app/controller/admin/api/ThemeController.java b/src/main/java/run/halo/app/controller/admin/api/ThemeController.java index 5955ee053..361cb7883 100644 --- a/src/main/java/run/halo/app/controller/admin/api/ThemeController.java +++ b/src/main/java/run/halo/app/controller/admin/api/ThemeController.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import run.halo.app.handler.theme.config.support.Group; import run.halo.app.handler.theme.config.support.ThemeProperty; +import run.halo.app.model.params.ThemeContentParam; import run.halo.app.model.support.BaseResponse; import run.halo.app.model.support.ThemeFile; import run.halo.app.service.ThemeService; @@ -47,20 +48,36 @@ public class ThemeController { return themeService.getThemes(); } - @GetMapping("files") + @GetMapping("activation/files") public List listFiles() { return themeService.listThemeFolderBy(themeService.getActivatedThemeId()); } + @GetMapping("{themeId}/files") + public List listFiles(@PathVariable("themeId") String themeId) { + return themeService.listThemeFolderBy(themeId); + } + @GetMapping("files/content") public BaseResponse getContentBy(@RequestParam(name = "path") String path) { return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), themeService.getTemplateContent(path)); } + @GetMapping("{themeId}/files/content") + public BaseResponse getContentBy(@PathVariable("themeId") String themeId, + @RequestParam(name = "path") String path) { + return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), themeService.getTemplateContent(themeId, path)); + } + @PutMapping("files/content") - public void updateContentBy(@RequestParam(name = "path") String path, - @RequestBody String content) { - themeService.saveTemplateContent(path, content); + public void updateContentBy(@RequestBody ThemeContentParam param) { + themeService.saveTemplateContent(param.getPath(), param.getContent()); + } + + @PutMapping("{themeId}/files/content") + public void updateContentBy(@PathVariable("themeId") String themeId, + @RequestBody ThemeContentParam param) { + themeService.saveTemplateContent(themeId, param.getPath(), param.getContent()); } @GetMapping("files/custom") @@ -117,13 +134,6 @@ public class ThemeController { themeSettingService.save(settings, themeId); } - @PutMapping("{themeId}") - public ThemeProperty updateTheme(@PathVariable("themeId") String themeId, - @RequestPart(name = "file", required = false) MultipartFile file) { - - return themeService.update(themeId); - } - @DeleteMapping("{themeId}") @ApiOperation("Deletes a theme") public void deleteBy(@PathVariable("themeId") String themeId) { @@ -136,12 +146,25 @@ public class ThemeController { return themeService.upload(file); } + @PutMapping("upload/{themeId}") + public ThemeProperty updateThemeByUpload(@PathVariable("themeId") String themeId, + @RequestPart("file") MultipartFile file) { + return themeService.update(themeId, file); + } + @PostMapping("fetching") @ApiOperation("Fetches a new theme") public ThemeProperty fetchTheme(@RequestParam("uri") String uri) { return themeService.fetch(uri); } + @PutMapping("fetching/{themeId}") + public ThemeProperty updateThemeByFetching(@PathVariable("themeId") String themeId, + @RequestPart(name = "file", required = false) MultipartFile file) { + + return themeService.update(themeId); + } + @PostMapping("reload") @ApiOperation("Reloads themes") public void reload() { diff --git a/src/main/java/run/halo/app/controller/content/ContentArchiveController.java b/src/main/java/run/halo/app/controller/content/ContentArchiveController.java index acb363232..030de33c1 100644 --- a/src/main/java/run/halo/app/controller/content/ContentArchiveController.java +++ b/src/main/java/run/halo/app/controller/content/ContentArchiveController.java @@ -1,5 +1,6 @@ package run.halo.app.controller.content; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.PageUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -9,10 +10,10 @@ import org.springframework.data.domain.Sort; import org.springframework.data.web.SortDefault; 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 org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; +import run.halo.app.cache.StringCacheStore; +import run.halo.app.cache.lock.CacheLock; +import run.halo.app.exception.ForbiddenException; import run.halo.app.model.entity.Category; import run.halo.app.model.entity.Post; import run.halo.app.model.entity.Tag; @@ -20,8 +21,10 @@ import run.halo.app.model.enums.PostStatus; import run.halo.app.model.vo.BaseCommentVO; import run.halo.app.model.vo.PostListVO; import run.halo.app.service.*; +import run.halo.app.utils.MarkdownUtils; import java.util.List; +import java.util.concurrent.TimeUnit; import static org.springframework.data.domain.Sort.Direction.DESC; @@ -38,28 +41,32 @@ public class ContentArchiveController { private final PostService postService; - private final PostCommentService postCommentService; - private final ThemeService themeService; private final PostCategoryService postCategoryService; private final PostTagService postTagService; + private final PostCommentService postCommentService; + private final OptionService optionService; + private final StringCacheStore cacheStore; + public ContentArchiveController(PostService postService, - PostCommentService postCommentService, ThemeService themeService, PostCategoryService postCategoryService, PostTagService postTagService, - OptionService optionService) { + PostCommentService postCommentService, + OptionService optionService, + StringCacheStore cacheStore) { this.postService = postService; - this.postCommentService = postCommentService; this.themeService = themeService; this.postCategoryService = postCategoryService; this.postTagService = postTagService; + this.postCommentService = postCommentService; this.optionService = optionService; + this.cacheStore = cacheStore; } /** @@ -99,35 +106,103 @@ public class ContentArchiveController { /** * Render post page. * - * @param url post slug url. - * @param cp comment page number - * @param model model + * @param url post slug url. + * @param preview preview + * @param token preview token + * @param model model * @return template path: themes/{theme}/post.ftl */ @GetMapping("{url}") public String post(@PathVariable("url") String url, + @RequestParam(value = "preview", required = false, defaultValue = "false") boolean preview, + @RequestParam(value = "intimate", required = false, defaultValue = "false") boolean intimate, + @RequestParam(value = "token", required = false) String token, @RequestParam(value = "cp", defaultValue = "1") Integer cp, @SortDefault(sort = "createTime", direction = DESC) Sort sort, Model model) { - Post post = postService.getBy(PostStatus.PUBLISHED, url); + Post post; + if (preview) { + post = postService.getBy(PostStatus.DRAFT, url); + } else if (intimate) { + post = postService.getBy(PostStatus.INTIMATE, url); + } else { + post = postService.getBy(PostStatus.PUBLISHED, url); + } + + // if this is a preview url. + if (preview) { + // render markdown to html when preview post + post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent())); + + // verify token + String cachedToken = cacheStore.getAny("preview-post-token-" + post.getId(), String.class).orElseThrow(() -> new ForbiddenException("该文章的预览链接不存在或已过期")); + + if (!cachedToken.equals(token)) { + throw new ForbiddenException("该文章的预览链接不存在或已过期"); + } + } + + // if this is a intimate url. + if (intimate) { + // verify token + String cachedToken = cacheStore.getAny(token, String.class).orElseThrow(() -> new ForbiddenException("您没有该文章的访问权限")); + if (!cachedToken.equals(token)) { + throw new ForbiddenException("您没有该文章的访问权限"); + } + } postService.getNextPost(post.getCreateTime()).ifPresent(nextPost -> model.addAttribute("nextPost", nextPost)); postService.getPrePost(post.getCreateTime()).ifPresent(prePost -> model.addAttribute("prePost", prePost)); - - List categories = postCategoryService.listCategoryBy(post.getId()); + List categories = postCategoryService.listCategoriesBy(post.getId()); List tags = postTagService.listTagsBy(post.getId()); Page comments = postCommentService.pageVosBy(post.getId(), PageRequest.of(cp, optionService.getCommentPageSize(), sort)); - final int[] pageRainbow = PageUtil.rainbow(cp, comments.getTotalPages(), 3); model.addAttribute("is_post", true); - model.addAttribute("post", post); + model.addAttribute("post", postService.convertToDetailVo(post)); model.addAttribute("categories", categories); model.addAttribute("tags", tags); model.addAttribute("comments", comments); - model.addAttribute("pageRainbow", pageRainbow); + + if (preview) { + // refresh timeUnit + cacheStore.putAny("preview-post-token-" + post.getId(), token, 10, TimeUnit.MINUTES); + } return themeService.render("post"); } + + @GetMapping(value = "{url}/password") + public String password(@PathVariable("url") String url, + Model model) { + Post post = postService.getBy(PostStatus.INTIMATE, url); + if (null == post) { + throw new ForbiddenException("没有查询到该文章信息"); + } + + model.addAttribute("url", url); + return "common/template/post_password"; + } + + @PostMapping(value = "{url}/password") + @CacheLock + public String password(@PathVariable("url") String url, + @RequestParam(value = "password") String password) { + Post post = postService.getBy(PostStatus.INTIMATE, url); + if (null == post) { + throw new ForbiddenException("没有查询到该文章信息"); + } + + if (password.equals(post.getPassword())) { + String token = IdUtil.simpleUUID(); + cacheStore.putAny(token, token, 10, TimeUnit.SECONDS); + + String redirect = String.format("%s/archives/%s?intimate=true&token=%s", optionService.getBlogBaseUrl(), post.getUrl(), token); + return "redirect:" + redirect; + } else { + String redirect = String.format("%s/archives/%s/password", optionService.getBlogBaseUrl(), post.getUrl()); + return "redirect:" + redirect; + } + } } diff --git a/src/main/java/run/halo/app/controller/content/ContentFeedController.java b/src/main/java/run/halo/app/controller/content/ContentFeedController.java index 94879bea9..13b790130 100644 --- a/src/main/java/run/halo/app/controller/content/ContentFeedController.java +++ b/src/main/java/run/halo/app/controller/content/ContentFeedController.java @@ -2,6 +2,7 @@ package run.halo.app.controller.content; import freemarker.template.Template; import freemarker.template.TemplateException; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -22,6 +23,9 @@ import run.halo.app.service.OptionService; import run.halo.app.service.PostService; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.List; import static org.springframework.data.domain.Sort.Direction.DESC; @@ -30,6 +34,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC; * @author ryanwang * @date : 2019-03-21 */ +@Slf4j @Controller public class ContentFeedController { @@ -144,6 +149,14 @@ public class ContentFeedController { private List buildPosts(@NonNull Pageable pageable) { Page postPage = postService.pageBy(PostStatus.PUBLISHED, pageable); Page posts = postService.convertToListVo(postPage); + posts.getContent().forEach(postListVO -> { + try { + // Encode post url + postListVO.setUrl(URLEncoder.encode(postListVO.getUrl(), StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException e) { + log.warn("Failed to encode url: " + postListVO.getUrl(), e); + } + }); return posts.getContent(); } } diff --git a/src/main/java/run/halo/app/controller/content/ContentIndexController.java b/src/main/java/run/halo/app/controller/content/ContentIndexController.java index c3ce7449a..51e886373 100644 --- a/src/main/java/run/halo/app/controller/content/ContentIndexController.java +++ b/src/main/java/run/halo/app/controller/content/ContentIndexController.java @@ -6,7 +6,6 @@ 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.data.web.SortDefault; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -14,6 +13,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import run.halo.app.model.entity.Post; import run.halo.app.model.enums.PostStatus; +import run.halo.app.model.properties.PostProperties; import run.halo.app.model.vo.PostListVO; import run.halo.app.service.OptionService; import run.halo.app.service.PostService; @@ -55,7 +55,7 @@ public class ContentIndexController { */ @GetMapping public String index(Model model) { - return this.index(model, 1, Sort.by(DESC, "topPriority").and(Sort.by(DESC, "createTime"))); + return this.index(model, 1); } /** @@ -67,14 +67,10 @@ public class ContentIndexController { */ @GetMapping(value = "page/{page}") public String index(Model model, - @PathVariable(value = "page") Integer page, - @SortDefault.SortDefaults({ - @SortDefault(sort = "topPriority", direction = DESC), - @SortDefault(sort = "createTime", direction = DESC) - }) Sort sort) { - log.debug("Requested index page, sort info: [{}]", sort); + @PathVariable(value = "page") Integer page) { + String indexSort = optionService.getByPropertyOfNonNull(PostProperties.INDEX_SORT).toString(); int pageSize = optionService.getPostPageSize(); - Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, pageSize, sort); + Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(DESC, "topPriority").and(Sort.by(DESC, indexSort))); Page postPage = postService.pageBy(PostStatus.PUBLISHED, pageable); Page posts = postService.convertToListVo(postPage); diff --git a/src/main/java/run/halo/app/controller/content/ContentJournalController.java b/src/main/java/run/halo/app/controller/content/ContentJournalController.java index 925542cfc..8308da3f6 100644 --- a/src/main/java/run/halo/app/controller/content/ContentJournalController.java +++ b/src/main/java/run/halo/app/controller/content/ContentJournalController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import run.halo.app.model.entity.Journal; +import run.halo.app.model.enums.JournalType; import run.halo.app.service.JournalCommentService; import run.halo.app.service.JournalService; import run.halo.app.service.OptionService; @@ -70,15 +71,15 @@ public class ContentJournalController { */ @GetMapping(value = "page/{page}") public String journals(Model model, - @PathVariable(value = "page") Integer page, - @SortDefault(sort = "createTime", direction = DESC) Sort sort) { + @PathVariable(value = "page") Integer page, + @SortDefault(sort = "createTime", direction = DESC) Sort sort) { log.debug("Requested journal page, sort info: [{}]", sort); int pageSize = optionService.getPostPageSize(); Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, pageSize, sort); - Page journals = journalService.listAll(pageable); + Page journals = journalService.pageBy(JournalType.PUBLIC, pageable); int[] rainbow = PageUtil.rainbow(page, journals.getTotalPages(), 3); diff --git a/src/main/java/run/halo/app/controller/content/ContentSheetController.java b/src/main/java/run/halo/app/controller/content/ContentSheetController.java index 2b1ae4c61..e60e53f2a 100644 --- a/src/main/java/run/halo/app/controller/content/ContentSheetController.java +++ b/src/main/java/run/halo/app/controller/content/ContentSheetController.java @@ -1,14 +1,29 @@ package run.halo.app.controller.content; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.SortDefault; 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.RequestParam; +import run.halo.app.cache.StringCacheStore; +import run.halo.app.exception.ForbiddenException; import run.halo.app.model.entity.Sheet; import run.halo.app.model.enums.PostStatus; import run.halo.app.model.support.HaloConst; +import run.halo.app.model.vo.BaseCommentVO; +import run.halo.app.service.OptionService; +import run.halo.app.service.SheetCommentService; import run.halo.app.service.SheetService; import run.halo.app.service.ThemeService; +import run.halo.app.utils.MarkdownUtils; + +import java.util.concurrent.TimeUnit; + +import static org.springframework.data.domain.Sort.Direction.DESC; /** * Content sheet controller. @@ -24,10 +39,22 @@ public class ContentSheetController { private final ThemeService themeService; + private final SheetCommentService sheetCommentService; + + private final OptionService optionService; + + private final StringCacheStore cacheStore; + public ContentSheetController(SheetService sheetService, - ThemeService themeService) { + ThemeService themeService, + SheetCommentService sheetCommentService, + OptionService optionService, + StringCacheStore cacheStore) { this.sheetService = sheetService; this.themeService = themeService; + this.sheetCommentService = sheetCommentService; + this.optionService = optionService; + this.cacheStore = cacheStore; } /** @@ -53,19 +80,46 @@ public class ContentSheetController { /** * Render custom sheet * - * @param url sheet url - * @param model model + * @param url sheet url + * @param preview preview + * @param token token + * @param model model * @return template path: themes/{theme}/sheet.ftl */ @GetMapping(value = "/s/{url}") public String sheet(@PathVariable(value = "url") String url, + @RequestParam(value = "preview", required = false, defaultValue = "false") boolean preview, + @RequestParam(value = "token", required = false) String token, + @RequestParam(value = "cp", defaultValue = "1") Integer cp, + @SortDefault(sort = "createTime", direction = DESC) Sort sort, Model model) { - Sheet sheet = sheetService.getBy(PostStatus.PUBLISHED, url); + Sheet sheet = sheetService.getBy(preview ? PostStatus.DRAFT : PostStatus.PUBLISHED, url); + + if (preview) { + // render markdown to html when preview post + sheet.setFormatContent(MarkdownUtils.renderHtml(sheet.getOriginalContent())); + + // verify token + String cachedToken = cacheStore.getAny("preview-sheet-token-" + sheet.getId(), String.class).orElseThrow(() -> new ForbiddenException("该页面的预览链接不存在或已过期")); + + if (!cachedToken.equals(token)) { + throw new ForbiddenException("该页面的预览链接不存在或已过期"); + } + } + + Page comments = sheetCommentService.pageVosBy(sheet.getId(), PageRequest.of(cp, optionService.getCommentPageSize(), sort)); + // sheet and post all can use model.addAttribute("sheet", sheetService.convertToDetail(sheet)); model.addAttribute("post", sheetService.convertToDetail(sheet)); model.addAttribute("is_sheet", true); + model.addAttribute("comments", comments); + + if (preview) { + // refresh timeUnit + cacheStore.putAny("preview-sheet-token-" + sheet.getId(), token, 10, TimeUnit.MINUTES); + } if (themeService.templateExists(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate() + HaloConst.SUFFIX_FTL)) { return themeService.render(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate()); diff --git a/src/main/java/run/halo/app/controller/content/MainController.java b/src/main/java/run/halo/app/controller/content/MainController.java index fd01587ac..c912ae058 100644 --- a/src/main/java/run/halo/app/controller/content/MainController.java +++ b/src/main/java/run/halo/app/controller/content/MainController.java @@ -50,7 +50,7 @@ public class MainController { @GetMapping("/avatar") public void avatar(HttpServletResponse response) throws IOException { - User user = userService.getCurrentUser().orElseThrow(() -> new ServiceException("找不到博主信息")); + User user = userService.getCurrentUser().orElseThrow(() -> new ServiceException("未查询到博主信息")); if (StringUtils.isNotEmpty(user.getAvatar())) { response.sendRedirect(user.getAvatar()); } diff --git a/src/main/java/run/halo/app/controller/content/api/PostController.java b/src/main/java/run/halo/app/controller/content/api/PostController.java index 6ec1514c5..eceaf2085 100644 --- a/src/main/java/run/halo/app/controller/content/api/PostController.java +++ b/src/main/java/run/halo/app/controller/content/api/PostController.java @@ -20,6 +20,7 @@ import run.halo.app.model.params.PostCommentParam; import run.halo.app.model.vo.BaseCommentVO; import run.halo.app.model.vo.BaseCommentWithParentVO; import run.halo.app.model.vo.CommentWithHasChildrenVO; +import run.halo.app.model.vo.PostDetailVO; import run.halo.app.service.OptionService; import run.halo.app.service.PostCommentService; import run.halo.app.service.PostService; @@ -69,22 +70,22 @@ public class PostController { @GetMapping("{postId:\\d+}") @ApiOperation("Gets a post") - public BasePostDetailDTO getBy(@PathVariable("postId") Integer postId, + public PostDetailVO getBy(@PathVariable("postId") Integer postId, @RequestParam(value = "formatDisabled", required = false, defaultValue = "true") Boolean formatDisabled, @RequestParam(value = "sourceDisabled", required = false, defaultValue = "false") Boolean sourceDisabled) { - BasePostDetailDTO detailDTO = postService.convertToDetail(postService.getById(postId)); + PostDetailVO postDetailVO = postService.convertToDetailVo(postService.getById(postId)); if (formatDisabled) { // Clear the format content - detailDTO.setFormatContent(null); + postDetailVO.setFormatContent(null); } if (sourceDisabled) { // Clear the original content - detailDTO.setOriginalContent(null); + postDetailVO.setOriginalContent(null); } - return detailDTO; + return postDetailVO; } @GetMapping("{postId:\\d+}/comments/top_view") diff --git a/src/main/java/run/halo/app/controller/base/CommonResultControllerAdvice.java b/src/main/java/run/halo/app/core/CommonResultControllerAdvice.java similarity index 98% rename from src/main/java/run/halo/app/controller/base/CommonResultControllerAdvice.java rename to src/main/java/run/halo/app/core/CommonResultControllerAdvice.java index 3a251ae21..53a731c57 100644 --- a/src/main/java/run/halo/app/controller/base/CommonResultControllerAdvice.java +++ b/src/main/java/run/halo/app/core/CommonResultControllerAdvice.java @@ -1,4 +1,4 @@ -package run.halo.app.controller.base; +package run.halo.app.core; import org.springframework.core.MethodParameter; import org.springframework.http.HttpStatus; diff --git a/src/main/java/run/halo/app/controller/base/ControllerExceptionHandler.java b/src/main/java/run/halo/app/core/ControllerExceptionHandler.java similarity index 94% rename from src/main/java/run/halo/app/controller/base/ControllerExceptionHandler.java rename to src/main/java/run/halo/app/core/ControllerExceptionHandler.java index 5bd8474cd..2bc317394 100644 --- a/src/main/java/run/halo/app/controller/base/ControllerExceptionHandler.java +++ b/src/main/java/run/halo/app/core/ControllerExceptionHandler.java @@ -1,4 +1,4 @@ -package run.halo.app.controller.base; +package run.halo.app.core; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DataIntegrityViolationException; @@ -38,7 +38,7 @@ public class ControllerExceptionHandler { if (e.getCause() instanceof org.hibernate.exception.ConstraintViolationException) { baseResponse = handleBaseException(e.getCause()); } - baseResponse.setMessage("Failed to validate request parameter"); + baseResponse.setMessage("字段验证错误,请完善后重试!"); return baseResponse; } @@ -46,7 +46,7 @@ public class ControllerExceptionHandler { @ResponseStatus(HttpStatus.BAD_REQUEST) public BaseResponse handleMissingServletRequestParameterException(MissingServletRequestParameterException e) { BaseResponse baseResponse = handleBaseException(e); - baseResponse.setMessage(String.format("Missing request parameter, required %s type %s parameter", e.getParameterType(), e.getParameterName())); + baseResponse.setMessage(String.format("请求字段缺失, 类型为 %s,名称为 %s", e.getParameterType(), e.getParameterName())); return baseResponse; } @@ -92,7 +92,7 @@ public class ControllerExceptionHandler { public BaseResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { BaseResponse baseResponse = handleBaseException(e); baseResponse.setStatus(HttpStatus.BAD_REQUEST.value()); - baseResponse.setMessage("Required request body is missing"); + baseResponse.setMessage("缺失请求主体"); return baseResponse; } diff --git a/src/main/java/run/halo/app/core/ControllerLogAop.java b/src/main/java/run/halo/app/core/ControllerLogAop.java new file mode 100644 index 000000000..a2a7c1ad1 --- /dev/null +++ b/src/main/java/run/halo/app/core/ControllerLogAop.java @@ -0,0 +1,90 @@ +package run.halo.app.core; + +import cn.hutool.extra.servlet.ServletUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; +import run.halo.app.utils.JsonUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Objects; + +@Aspect +@Component +@Slf4j +public class ControllerLogAop { + + @Pointcut("execution(* *..*.*.controller..*.*(..))") + public void controller() { + } + + @Around("controller()") + public Object controller(ProceedingJoinPoint joinPoint) throws Throwable { + String className = joinPoint.getTarget().getClass().getSimpleName(); + String methodName = joinPoint.getSignature().getName(); + Object[] args = joinPoint.getArgs(); + + // Get request attribute + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = Objects.requireNonNull(requestAttributes).getRequest(); + + printRequestLog(request, className, methodName, args); + long start = System.currentTimeMillis(); + Object returnObj = joinPoint.proceed(); + printResponseLog(request, className, methodName, returnObj, System.currentTimeMillis() - start); + return returnObj; + } + + + private void printRequestLog(HttpServletRequest request, String clazzName, String methodName, Object[] args) throws JsonProcessingException { + log.debug("Request URL: [{}], URI: [{}], Request Method: [{}], IP: [{}]", + request.getRequestURL(), + request.getRequestURI(), + request.getMethod(), + ServletUtil.getClientIP(request)); + + if (args == null || !log.isDebugEnabled()) { + return; + } + + boolean shouldNotLog = false; + for (Object arg : args) { + if (arg == null || + arg instanceof HttpServletRequest || + arg instanceof HttpServletResponse || + arg instanceof MultipartFile || + arg.getClass().isAssignableFrom(MultipartFile[].class)) { + shouldNotLog = true; + break; + } + } + + if (!shouldNotLog) { + String requestBody = JsonUtils.objectToJson(args); + log.debug("{}.{} Parameters: [{}]", clazzName, methodName, requestBody); + } + } + + private void printResponseLog(HttpServletRequest request, String className, String methodName, Object returnObj, long usage) throws JsonProcessingException { + if (log.isDebugEnabled()) { + String returningData = null; + if (returnObj != null) { + if (returnObj.getClass().isAssignableFrom(byte[].class)) { + returningData = "Binary data"; + } else { + returningData = JsonUtils.objectToJson(returnObj); + } + } + log.debug("{}.{} Response: [{}], usage: [{}]ms", className, methodName, returningData, usage); + } + } +} + diff --git a/src/main/java/run/halo/app/controller/support/PageJacksonSerializer.java b/src/main/java/run/halo/app/core/PageJacksonSerializer.java similarity index 97% rename from src/main/java/run/halo/app/controller/support/PageJacksonSerializer.java rename to src/main/java/run/halo/app/core/PageJacksonSerializer.java index 408dafd2f..de694297d 100644 --- a/src/main/java/run/halo/app/controller/support/PageJacksonSerializer.java +++ b/src/main/java/run/halo/app/core/PageJacksonSerializer.java @@ -1,4 +1,4 @@ -package run.halo.app.controller.support; +package run.halo.app.core; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; diff --git a/src/main/java/run/halo/app/event/comment/CommentEventListener.java b/src/main/java/run/halo/app/event/comment/CommentEventListener.java index 213f4c5ee..fd0e73b9e 100644 --- a/src/main/java/run/halo/app/event/comment/CommentEventListener.java +++ b/src/main/java/run/halo/app/event/comment/CommentEventListener.java @@ -65,7 +65,7 @@ public class CommentEventListener { return; } - User user = userService.getCurrentUser().orElseThrow(() -> new ServiceException("找不到博主信息")); + User user = userService.getCurrentUser().orElseThrow(() -> new ServiceException("未查询到博主信息")); Map data = new HashMap<>(); diff --git a/src/main/java/run/halo/app/event/freemarker/FreemarkerConfigAwareListener.java b/src/main/java/run/halo/app/event/freemarker/FreemarkerConfigAwareListener.java index 003ddf25c..a1bd97a9e 100644 --- a/src/main/java/run/halo/app/event/freemarker/FreemarkerConfigAwareListener.java +++ b/src/main/java/run/halo/app/event/freemarker/FreemarkerConfigAwareListener.java @@ -12,6 +12,7 @@ import run.halo.app.event.options.OptionUpdatedEvent; import run.halo.app.event.theme.ThemeActivatedEvent; import run.halo.app.event.user.UserUpdatedEvent; import run.halo.app.handler.theme.config.support.ThemeProperty; +import run.halo.app.model.properties.OtherProperties; import run.halo.app.model.support.HaloConst; import run.halo.app.service.OptionService; import run.halo.app.service.ThemeService; @@ -98,7 +99,8 @@ public class FreemarkerConfigAwareListener { private void loadThemeConfig() throws TemplateModelException { ThemeProperty activatedTheme = themeService.getActivatedTheme(); configuration.setSharedVariable("theme", activatedTheme); - configuration.setSharedVariable("static", optionService.getBlogBaseUrl() + "/" + activatedTheme.getFolderName()); + String baseUrl = optionService.getByPropertyOrDefault(OtherProperties.CDN_DOMAIN, String.class, optionService.getBlogBaseUrl()); + configuration.setSharedVariable("static", baseUrl + "/" + activatedTheme.getFolderName()); configuration.setSharedVariable("settings", themeSettingService.listAsMapBy(themeService.getActivatedThemeId())); log.debug("Loaded theme and settings"); } diff --git a/src/main/java/run/halo/app/handler/file/AliYunFileHandler.java b/src/main/java/run/halo/app/handler/file/AliYunFileHandler.java index 493a77958..d13a1289c 100644 --- a/src/main/java/run/halo/app/handler/file/AliYunFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/AliYunFileHandler.java @@ -25,6 +25,7 @@ import java.util.Objects; * AliYun file handler. * * @author MyFaith + * @author ryanwang * @date 2019-04-04 00:06:13 */ @Slf4j @@ -42,12 +43,14 @@ public class AliYunFileHandler implements FileHandler { Assert.notNull(file, "Multipart file must not be null"); // Get config + String ossDomain = optionService.getByPropertyOrDefault(AliYunProperties.OSS_DOMAIN, String.class, ""); String ossEndPoint = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_ENDPOINT).toString(); String ossAccessKey = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_ACCESS_KEY).toString(); String ossAccessSecret = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_ACCESS_SECRET).toString(); String ossBucketName = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_BUCKET_NAME).toString(); - String ossStyleRule = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_STYLE_RULE).toString(); String ossSource = StringUtils.join("https://", ossBucketName, "." + ossEndPoint); + String ossStyleRule = optionService.getByPropertyOrDefault(AliYunProperties.OSS_STYLE_RULE, String.class, ""); + String ossThumbnailStyleRule = optionService.getByPropertyOrDefault(AliYunProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, ""); // Init OSS client OSS ossClient = new OSSClientBuilder().build(ossEndPoint, ossAccessKey, ossAccessSecret); @@ -57,7 +60,7 @@ public class AliYunFileHandler implements FileHandler { String extension = FilenameUtils.getExtension(file.getOriginalFilename()); String timestamp = String.valueOf(System.currentTimeMillis()); String upFilePath = StringUtils.join(basename, "_", timestamp, ".", extension); - String filePath = StringUtils.join(StringUtils.appendIfMissing(ossSource, "/"), upFilePath); + String filePath = StringUtils.join(StringUtils.appendIfMissing(StringUtils.isNotBlank(ossDomain) ? ossDomain : ossSource, "/"), upFilePath); // Upload PutObjectResult putObjectResult = ossClient.putObject(ossBucketName, upFilePath, file.getInputStream()); @@ -68,7 +71,7 @@ public class AliYunFileHandler implements FileHandler { // Response result UploadResult uploadResult = new UploadResult(); uploadResult.setFilename(basename); - uploadResult.setFilePath(filePath); + uploadResult.setFilePath(StringUtils.isBlank(ossStyleRule) ? filePath : filePath + ossStyleRule); uploadResult.setKey(upFilePath); uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); uploadResult.setSuffix(extension); @@ -79,7 +82,7 @@ public class AliYunFileHandler implements FileHandler { BufferedImage image = ImageIO.read(file.getInputStream()); uploadResult.setWidth(image.getWidth()); uploadResult.setHeight(image.getHeight()); - uploadResult.setThumbPath(StringUtils.isBlank(ossStyleRule) ? filePath : filePath + ossStyleRule); + uploadResult.setThumbPath(StringUtils.isBlank(ossThumbnailStyleRule) ? filePath : filePath + ossThumbnailStyleRule); } return uploadResult; @@ -106,7 +109,6 @@ public class AliYunFileHandler implements FileHandler { String ossAccessKey = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_ACCESS_KEY).toString(); String ossAccessSecret = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_ACCESS_SECRET).toString(); String ossBucketName = optionService.getByPropertyOfNonNull(AliYunProperties.OSS_BUCKET_NAME).toString(); - String ossSource = StringUtils.join("https://", ossBucketName, "." + ossEndPoint); // Init OSS client OSS ossClient = new OSSClientBuilder().build(ossEndPoint, ossAccessKey, ossAccessSecret); diff --git a/src/main/java/run/halo/app/handler/file/BaiDuYunFileHandler.java b/src/main/java/run/halo/app/handler/file/BaiDuYunFileHandler.java new file mode 100644 index 000000000..e01873641 --- /dev/null +++ b/src/main/java/run/halo/app/handler/file/BaiDuYunFileHandler.java @@ -0,0 +1,130 @@ +package run.halo.app.handler.file; + + +import com.baidubce.auth.DefaultBceCredentials; +import com.baidubce.services.bos.BosClient; +import com.baidubce.services.bos.BosClientConfiguration; +import com.baidubce.services.bos.model.PutObjectResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.web.multipart.MultipartFile; +import run.halo.app.exception.FileOperationException; +import run.halo.app.model.enums.AttachmentType; +import run.halo.app.model.properties.BaiDuYunProperties; +import run.halo.app.model.support.UploadResult; +import run.halo.app.service.OptionService; +import run.halo.app.utils.FilenameUtils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.util.Objects; + +/** + * BaiDuYun file handler. + * + * @author wangya + * @date 2019-07-20 + */ +@Slf4j +@Component +public class BaiDuYunFileHandler implements FileHandler { + + private final OptionService optionService; + + public BaiDuYunFileHandler(OptionService optionService) { + this.optionService = optionService; + } + + @Override + public UploadResult upload(MultipartFile file) { + Assert.notNull(file, "Multipart file must not be null"); + + // Get config + String bosDomain = optionService.getByPropertyOrDefault(BaiDuYunProperties.BOS_DOMAIN, String.class, ""); + String bosEndPoint = optionService.getByPropertyOfNonNull(BaiDuYunProperties.BOS_ENDPOINT).toString(); + String bosAccessKey = optionService.getByPropertyOfNonNull(BaiDuYunProperties.BOS_ACCESS_KEY).toString(); + String bosSecretKey = optionService.getByPropertyOfNonNull(BaiDuYunProperties.BOS_SECRET_KEY).toString(); + String bosBucketName = optionService.getByPropertyOfNonNull(BaiDuYunProperties.BOS_BUCKET_NAME).toString(); + String bosStyleRule = optionService.getByPropertyOrDefault(BaiDuYunProperties.BOS_STYLE_RULE, String.class, ""); + String bosThumbnailStyleRule = optionService.getByPropertyOrDefault(BaiDuYunProperties.BOS_THUMBNAIL_STYLE_RULE, String.class, ""); + String bosSource = StringUtils.join("https://", bosBucketName, "." + bosEndPoint); + + BosClientConfiguration config = new BosClientConfiguration(); + config.setCredentials(new DefaultBceCredentials(bosAccessKey, bosSecretKey)); + config.setEndpoint(bosEndPoint); + + // Init OSS client + BosClient client = new BosClient(config); + + try { + String basename = FilenameUtils.getBasename(file.getOriginalFilename()); + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + String timestamp = String.valueOf(System.currentTimeMillis()); + String upFilePath = StringUtils.join(basename, "_", timestamp, ".", extension); + String filePath = StringUtils.join(StringUtils.appendIfMissing(StringUtils.isNotBlank(bosDomain) ? bosDomain : bosSource, "/"), upFilePath); + + // Upload + PutObjectResponse putObjectResponseFromInputStream = client.putObject(bosBucketName, upFilePath, file.getInputStream()); + if (putObjectResponseFromInputStream == null) { + throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到百度云失败 "); + } + + // Response result + UploadResult uploadResult = new UploadResult(); + uploadResult.setFilename(basename); + uploadResult.setFilePath(StringUtils.isBlank(bosStyleRule) ? filePath : filePath + bosStyleRule); + uploadResult.setKey(upFilePath); + uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); + uploadResult.setSuffix(extension); + uploadResult.setSize(file.getSize()); + + // Handle thumbnail + if (FileHandler.isImageType(uploadResult.getMediaType())) { + BufferedImage image = ImageIO.read(file.getInputStream()); + uploadResult.setWidth(image.getWidth()); + uploadResult.setHeight(image.getHeight()); + uploadResult.setThumbPath(StringUtils.isBlank(bosThumbnailStyleRule) ? filePath : filePath + bosThumbnailStyleRule); + } + + return uploadResult; + } catch (Exception e) { + throw new FileOperationException("附件 " + file.getOriginalFilename() + " 上传失败(百度云)", e); + } finally { + client.shutdown(); + } + } + + @Override + public void delete(String key) { + Assert.notNull(key, "File key must not be blank"); + + // Get config + String bosEndPoint = optionService.getByPropertyOfNonNull(BaiDuYunProperties.BOS_ENDPOINT).toString(); + String bosAccessKey = optionService.getByPropertyOfNonNull(BaiDuYunProperties.BOS_ACCESS_KEY).toString(); + String bosSecretKey = optionService.getByPropertyOfNonNull(BaiDuYunProperties.BOS_SECRET_KEY).toString(); + String bosBucketName = optionService.getByPropertyOfNonNull(BaiDuYunProperties.BOS_BUCKET_NAME).toString(); + + BosClientConfiguration config = new BosClientConfiguration(); + config.setCredentials(new DefaultBceCredentials(bosAccessKey, bosSecretKey)); + config.setEndpoint(bosEndPoint); + + // Init OSS client + BosClient client = new BosClient(config); + + try { + client.deleteObject(bosBucketName, key); + } catch (Exception e) { + throw new FileOperationException("附件 " + key + " 从百度云删除失败", e); + } finally { + client.shutdown(); + } + } + + @Override + public boolean supportType(AttachmentType type) { + return AttachmentType.BAIDUYUN.equals(type); + } +} diff --git a/src/main/java/run/halo/app/handler/file/LocalFileHandler.java b/src/main/java/run/halo/app/handler/file/LocalFileHandler.java index 6180e8967..531543d17 100644 --- a/src/main/java/run/halo/app/handler/file/LocalFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/LocalFileHandler.java @@ -3,7 +3,6 @@ package run.halo.app.handler.file; import lombok.extern.slf4j.Slf4j; import net.coobird.thumbnailator.Thumbnails; import org.springframework.http.MediaType; -import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.multipart.MultipartFile; @@ -24,6 +23,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Calendar; import java.util.Objects; +import java.util.concurrent.locks.ReentrantLock; import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR; @@ -31,7 +31,8 @@ import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR; * Local file handler. * * @author johnniang - * @date 3/27/19 + * @author ryanwang + * @date 2019-03-27 */ @Slf4j @Component @@ -54,6 +55,8 @@ public class LocalFileHandler implements FileHandler { */ private final static int THUMB_HEIGHT = 256; + ReentrantLock lock = new ReentrantLock(); + private final OptionService optionService; private final String workDir; @@ -135,28 +138,38 @@ public class LocalFileHandler implements FileHandler { uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); uploadResult.setSize(file.getSize()); + // TODO refactor this: if image is svg ext. extension + boolean isSvg = "svg".equals(extension); + // Check file type - if (FileHandler.isImageType(uploadResult.getMediaType())) { - // Upload a thumbnail - String thumbnailBasename = basename + THUMBNAIL_SUFFIX; - String thumbnailSubFilePath = subDir + thumbnailBasename + '.' + extension; - Path thumbnailPath = Paths.get(workDir + thumbnailSubFilePath); + if (FileHandler.isImageType(uploadResult.getMediaType()) && !isSvg) { + lock.lock(); + try { + // Upload a thumbnail + String thumbnailBasename = basename + THUMBNAIL_SUFFIX; + String thumbnailSubFilePath = subDir + thumbnailBasename + '.' + extension; + Path thumbnailPath = Paths.get(workDir + thumbnailSubFilePath); - // Create the thumbnail - Files.createFile(thumbnailPath); + // Read as image + BufferedImage originalImage = ImageIO.read(uploadPath.toFile()); + // Set width and height + uploadResult.setWidth(originalImage.getWidth()); + uploadResult.setHeight(originalImage.getHeight()); - // Generate thumbnail - generateThumbnail(uploadPath, thumbnailPath); - - // Read as image - BufferedImage image = ImageIO.read(Files.newInputStream(uploadPath)); - - // Set width and height - uploadResult.setWidth(image.getWidth()); - uploadResult.setHeight(image.getHeight()); - - // Set thumb path - uploadResult.setThumbPath(thumbnailSubFilePath); + // Generate thumbnail + boolean result = generateThumbnail(originalImage, thumbnailPath, extension); + if (result) { + // Set thumb path + uploadResult.setThumbPath(thumbnailSubFilePath); + } else { + // If generate error + uploadResult.setThumbPath(subFilePath); + } + } finally { + lock.unlock(); + } + } else { + uploadResult.setThumbPath(subFilePath); } return uploadResult; @@ -194,7 +207,7 @@ public class LocalFileHandler implements FileHandler { try { boolean deleteResult = Files.deleteIfExists(thumbnailPath); if (!deleteResult) { - log.warn("Thumbnail: [{}] way not exist", thumbnailPath.toString()); + log.warn("Thumbnail: [{}] may not exist", thumbnailPath.toString()); } } catch (IOException e) { throw new FileOperationException("附件缩略图 " + thumbnailName + " 删除失败", e); @@ -206,21 +219,24 @@ public class LocalFileHandler implements FileHandler { return AttachmentType.LOCAL.equals(type); } - /** - * Generates thumbnail image. - * - * @param imagePath image path must not be null - * @param thumbPath thumbnail path must not be null - * @throws IOException throws if image provided is not valid - */ - private void generateThumbnail(@NonNull Path imagePath, @NonNull Path thumbPath) throws IOException { - Assert.notNull(imagePath, "Image path must not be null"); + private boolean generateThumbnail(BufferedImage originalImage, Path thumbPath, String extension) { + Assert.notNull(originalImage, "Image must not be null"); Assert.notNull(thumbPath, "Thumb path must not be null"); - log.info("Generating thumbnail: [{}] for image: [{}]", thumbPath.getFileName(), imagePath.getFileName()); - // Convert to thumbnail and copy the thumbnail - Thumbnails.of(imagePath.toFile()).size(THUMB_WIDTH, THUMB_HEIGHT).keepAspectRatio(true).toFile(thumbPath.toFile()); + boolean result = false; + // Create the thumbnail + try { + Files.createFile(thumbPath); + // Convert to thumbnail and copy the thumbnail + log.debug("Trying to generate thumbnail: [{}]", thumbPath.toString()); + Thumbnails.of(originalImage).size(THUMB_WIDTH, THUMB_HEIGHT).keepAspectRatio(true).toFile(thumbPath.toFile()); + log.debug("Generated thumbnail image, and wrote the thumbnail to [{}]", thumbPath.toString()); + result = true; + } catch (Throwable t) { + log.warn("Failed to generate thumbnail: [{}]", thumbPath); + } + return result; } } diff --git a/src/main/java/run/halo/app/handler/file/QnYunFileHandler.java b/src/main/java/run/halo/app/handler/file/QnYunFileHandler.java index 0d0d406fa..c9907e32a 100644 --- a/src/main/java/run/halo/app/handler/file/QnYunFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/QnYunFileHandler.java @@ -35,6 +35,7 @@ import static run.halo.app.handler.file.FileHandler.isImageType; * Qi niu yun file handler. * * @author johnniang + * @author ryanwang * @date 3/27/19 */ @Slf4j @@ -58,6 +59,7 @@ public class QnYunFileHandler implements FileHandler { String bucket = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_BUCKET).toString(); String domain = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_DOMAIN).toString(); String styleRule = optionService.getByPropertyOrDefault(QnYunProperties.OSS_STYLE_RULE, String.class, ""); + String thumbnailStyleRule = optionService.getByPropertyOrDefault(QnYunProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, ""); // Create configuration Configuration configuration = new Configuration(zone); @@ -77,7 +79,6 @@ public class QnYunFileHandler implements FileHandler { // Create temp path Path tmpPath = Paths.get(System.getProperty("java.io.tmpdir"), bucket); - try { String basename = FilenameUtils.getBasename(file.getOriginalFilename()); String extension = FilenameUtils.getExtension(file.getOriginalFilename()); @@ -103,7 +104,7 @@ public class QnYunFileHandler implements FileHandler { // Build upload result UploadResult result = new UploadResult(); result.setFilename(basename); - result.setFilePath(filePath); + result.setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule); result.setKey(putSet.getKey()); result.setSuffix(extension); result.setWidth(putSet.getWidth()); @@ -112,7 +113,7 @@ public class QnYunFileHandler implements FileHandler { result.setSize(file.getSize()); if (isImageType(result.getMediaType())) { - result.setThumbPath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule); + result.setThumbPath(StringUtils.isBlank(thumbnailStyleRule) ? filePath : filePath + thumbnailStyleRule); } return result; @@ -147,7 +148,7 @@ public class QnYunFileHandler implements FileHandler { bucketManager.delete(bucket, key); } catch (QiniuException e) { log.error("QnYun error response: [{}]", e.response); - throw new FileOperationException("Failed to delete file with " + key + " key", e); + throw new FileOperationException("附件 " + key + " 从七牛云删除失败", e); } } diff --git a/src/main/java/run/halo/app/handler/file/SmmsFileHandler.java b/src/main/java/run/halo/app/handler/file/SmmsFileHandler.java index 51e895cf9..3acc7c0ee 100644 --- a/src/main/java/run/halo/app/handler/file/SmmsFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/SmmsFileHandler.java @@ -9,11 +9,15 @@ import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import run.halo.app.exception.FileOperationException; +import run.halo.app.exception.ServiceException; import run.halo.app.model.enums.AttachmentType; +import run.halo.app.model.properties.SmmsProperties; import run.halo.app.model.support.UploadResult; +import run.halo.app.service.OptionService; import run.halo.app.utils.FilenameUtils; import run.halo.app.utils.HttpClientUtils; @@ -30,24 +34,40 @@ import java.util.Objects; @Component public class SmmsFileHandler implements FileHandler { + @Deprecated private final static String UPLOAD_API = "https://sm.ms/api/upload"; + private final static String UPLOAD_API_V2 = "https://sm.ms/api/v2/upload"; + + @Deprecated private final static String DELETE_API = "https://sm.ms/api/delete/%s"; + private final static String DELETE_API_V2 = "https://sm.ms/api/v2/delete/%s"; + private final static String SUCCESS_CODE = "success"; private final static String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"; private final RestTemplate httpsRestTemplate; - public SmmsFileHandler(RestTemplate httpsRestTemplate) { + private final OptionService optionService; + + public SmmsFileHandler(RestTemplate httpsRestTemplate, + OptionService optionService) { this.httpsRestTemplate = httpsRestTemplate; + this.optionService = optionService; } @Override public UploadResult upload(MultipartFile file) { Assert.notNull(file, "Multipart file must not be null"); + String apiSecretToken = optionService.getByPropertyOfNonNull(SmmsProperties.SMMS_API_SECRET_TOKEN).toString(); + + if (StringUtils.isEmpty(apiSecretToken)) { + throw new ServiceException("请先设置 SM.MS 的 Secret Token"); + } + if (!FileHandler.isImageType(file.getContentType())) { log.error("Invalid extension: [{}]", file.getContentType()); throw new FileOperationException("不支持的文件类型,仅支持 \"jpeg, jpg, png, gif, bmp\" 格式的图片"); @@ -58,6 +78,7 @@ public class SmmsFileHandler implements FileHandler { headers.setContentType(MediaType.MULTIPART_FORM_DATA); // Set user agent manually headers.set(HttpHeaders.USER_AGENT, DEFAULT_USER_AGENT); + headers.set(HttpHeaders.AUTHORIZATION, apiSecretToken); LinkedMultiValueMap body = new LinkedMultiValueMap<>(); @@ -74,7 +95,7 @@ public class SmmsFileHandler implements FileHandler { HttpEntity> httpEntity = new HttpEntity<>(body, headers); // Upload file - ResponseEntity mapResponseEntity = httpsRestTemplate.postForEntity(UPLOAD_API, httpEntity, SmmsResponse.class); + ResponseEntity mapResponseEntity = httpsRestTemplate.postForEntity(UPLOAD_API_V2, httpEntity, SmmsResponse.class); // Check status if (mapResponseEntity.getStatusCode().isError()) { @@ -117,7 +138,7 @@ public class SmmsFileHandler implements FileHandler { Assert.hasText(key, "Deleting key must not be blank"); // Build delete url - String url = String.format(DELETE_API, key); + String url = String.format(DELETE_API_V2, key); // Set user agent manually HttpHeaders headers = new HttpHeaders(); diff --git a/src/main/java/run/halo/app/handler/file/TencentYunFileHandler.java b/src/main/java/run/halo/app/handler/file/TencentYunFileHandler.java new file mode 100644 index 000000000..1abdb97c5 --- /dev/null +++ b/src/main/java/run/halo/app/handler/file/TencentYunFileHandler.java @@ -0,0 +1,143 @@ +package run.halo.app.handler.file; + + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.auth.BasicCOSCredentials; +import com.qcloud.cos.auth.COSCredentials; +import com.qcloud.cos.model.ObjectMetadata; +import com.qcloud.cos.model.PutObjectResult; +import com.qcloud.cos.region.Region; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.web.multipart.MultipartFile; +import run.halo.app.exception.FileOperationException; +import run.halo.app.model.enums.AttachmentType; +import run.halo.app.model.properties.TencentYunProperties; +import run.halo.app.model.support.UploadResult; +import run.halo.app.service.OptionService; +import run.halo.app.utils.FilenameUtils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.util.Objects; + +/** + * TencentYun file handler. + * + * @author wangya + * @author ryanwang + * @date 2019-07-25 + */ +@Slf4j +@Component +public class TencentYunFileHandler implements FileHandler { + + private final OptionService optionService; + + public TencentYunFileHandler(OptionService optionService) { + this.optionService = optionService; + } + + @Override + public UploadResult upload(MultipartFile file) { + Assert.notNull(file, "Multipart file must not be null"); + + // Get config + String cosDomain = optionService.getByPropertyOrDefault(TencentYunProperties.COS_DOMAIN, String.class, ""); + String cosRegion = optionService.getByPropertyOfNonNull(TencentYunProperties.COS_REGION).toString(); + String cosSecretId = optionService.getByPropertyOfNonNull(TencentYunProperties.COS_SECRET_ID).toString(); + String cosSecretKey = optionService.getByPropertyOfNonNull(TencentYunProperties.COS_SECRET_KEY).toString(); + String cosBucketName = optionService.getByPropertyOfNonNull(TencentYunProperties.COS_BUCKET_NAME).toString(); + String cosSource = StringUtils.join("https://", cosBucketName, ".cos." + cosRegion + ".myqcloud.com"); + + //get file attribute + long size = file.getSize(); + String contentType = file.getContentType(); + + COSCredentials cred = new BasicCOSCredentials(cosSecretId, cosSecretKey); + Region region = new Region(cosRegion); + ClientConfig clientConfig = new ClientConfig(region); + + + // Init OSS client + COSClient cosClient = new COSClient(cred, clientConfig); + + + try { + String basename = FilenameUtils.getBasename(file.getOriginalFilename()); + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + String timestamp = String.valueOf(System.currentTimeMillis()); + String upFilePath = StringUtils.join(basename, "_", timestamp, ".", extension); + String filePath = StringUtils.join(StringUtils.appendIfMissing(StringUtils.isNotBlank(cosDomain) ? cosDomain : cosSource, "/"), upFilePath); + + // Upload + ObjectMetadata objectMetadata = new ObjectMetadata(); + //提前告知输入流的长度, 否则可能导致 oom + objectMetadata.setContentLength(size); + // 设置 Content type, 默认是 application/octet-stream + objectMetadata.setContentType(contentType); + PutObjectResult putObjectResponseFromInputStream = cosClient.putObject(cosBucketName, upFilePath, file.getInputStream(), objectMetadata); + if (putObjectResponseFromInputStream == null) { + throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到腾讯云失败 "); + } + + // Response result + UploadResult uploadResult = new UploadResult(); + uploadResult.setFilename(basename); + uploadResult.setFilePath(filePath); + uploadResult.setKey(upFilePath); + uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); + uploadResult.setSuffix(extension); + uploadResult.setSize(file.getSize()); + + // Handle thumbnail + if (FileHandler.isImageType(uploadResult.getMediaType())) { + BufferedImage image = ImageIO.read(file.getInputStream()); + uploadResult.setWidth(image.getWidth()); + uploadResult.setHeight(image.getHeight()); + uploadResult.setThumbPath(filePath); + } + + return uploadResult; + } catch (Exception e) { + throw new FileOperationException("附件 " + file.getOriginalFilename() + " 上传失败(腾讯云)", e); + } finally { + cosClient.shutdown(); + } + } + + @Override + public void delete(String key) { + Assert.notNull(key, "File key must not be blank"); + + // Get config + String cosRegion = optionService.getByPropertyOfNonNull(TencentYunProperties.COS_REGION).toString(); + String cosSecretId = optionService.getByPropertyOfNonNull(TencentYunProperties.COS_SECRET_ID).toString(); + String cosSecretKey = optionService.getByPropertyOfNonNull(TencentYunProperties.COS_SECRET_KEY).toString(); + String cosBucketName = optionService.getByPropertyOfNonNull(TencentYunProperties.COS_BUCKET_NAME).toString(); + + COSCredentials cred = new BasicCOSCredentials(cosSecretId, cosSecretKey); + Region region = new Region(cosRegion); + ClientConfig clientConfig = new ClientConfig(region); + + // Init OSS client + COSClient cosClient = new COSClient(cred, clientConfig); + + try { + cosClient.deleteObject(cosBucketName, key); + } catch (Exception e) { + throw new FileOperationException("附件 " + key + " 从腾讯云删除失败", e); + } finally { + cosClient.shutdown(); + } + } + + @Override + public boolean supportType(AttachmentType type) { + return AttachmentType.TENCENTYUN.equals(type); + } +} diff --git a/src/main/java/run/halo/app/handler/file/UpYunFileHandler.java b/src/main/java/run/halo/app/handler/file/UpYunFileHandler.java index 9445d497d..0942c84f4 100644 --- a/src/main/java/run/halo/app/handler/file/UpYunFileHandler.java +++ b/src/main/java/run/halo/app/handler/file/UpYunFileHandler.java @@ -23,6 +23,7 @@ import java.util.Objects; * Up Yun file handler. * * @author johnniang + * @author ryanwang * @date 3/27/19 */ @Slf4j @@ -46,6 +47,7 @@ public class UpYunFileHandler implements FileHandler { String ossOperator = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_OPERATOR).toString(); // style rule can be null String ossStyleRule = optionService.getByPropertyOrDefault(UpYunProperties.OSS_STYLE_RULE, String.class, ""); + String ossThumbnailStyleRule = optionService.getByPropertyOrDefault(UpYunProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, ""); // Create up yun UpYun upYun = new UpYun(ossBucket, ossOperator, ossPassword); @@ -75,7 +77,7 @@ public class UpYunFileHandler implements FileHandler { // Build upload result UploadResult uploadResult = new UploadResult(); uploadResult.setFilename(basename); - uploadResult.setFilePath(filePath); + uploadResult.setFilePath(StringUtils.isBlank(ossStyleRule) ? filePath : filePath + ossStyleRule); uploadResult.setKey(upFilePath); uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType()))); uploadResult.setSuffix(extension); @@ -86,7 +88,7 @@ public class UpYunFileHandler implements FileHandler { BufferedImage image = ImageIO.read(file.getInputStream()); uploadResult.setWidth(image.getWidth()); uploadResult.setHeight(image.getHeight()); - uploadResult.setThumbPath(StringUtils.isBlank(ossStyleRule) ? filePath : filePath + ossStyleRule); + uploadResult.setThumbPath(StringUtils.isBlank(ossThumbnailStyleRule) ? filePath : filePath + ossThumbnailStyleRule); } return uploadResult; @@ -100,7 +102,6 @@ public class UpYunFileHandler implements FileHandler { Assert.notNull(key, "File key must not be blank"); // Get config - String ossSource = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_SOURCE).toString(); String ossPassword = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_PASSWORD).toString(); String ossBucket = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_BUCKET).toString(); String ossOperator = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_OPERATOR).toString(); @@ -111,14 +112,13 @@ public class UpYunFileHandler implements FileHandler { upYun.setApiDomain(UpYun.ED_AUTO); try { - String filePath = ossSource + key; // Delete the file - boolean deleteResult = upYun.deleteFile(filePath); + boolean deleteResult = upYun.deleteFile(key); if (!deleteResult) { - log.warn("Failed to delete file " + filePath + " from UpYun"); + log.warn("Failed to delete file " + key + " from UpYun"); } } catch (Exception e) { - throw new FileOperationException("附件从又拍云删除失败", e); + throw new FileOperationException("附件 " + key + " 从又拍云删除失败", e); } } diff --git a/src/main/java/run/halo/app/handler/theme/config/support/Item.java b/src/main/java/run/halo/app/handler/theme/config/support/Item.java index fa83d74d6..01e3675af 100644 --- a/src/main/java/run/halo/app/handler/theme/config/support/Item.java +++ b/src/main/java/run/halo/app/handler/theme/config/support/Item.java @@ -53,8 +53,12 @@ public class Item { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Item item = (Item) o; return name.equals(item.name); } diff --git a/src/main/java/run/halo/app/handler/theme/config/support/ThemeProperty.java b/src/main/java/run/halo/app/handler/theme/config/support/ThemeProperty.java index dcae2ae0c..90fcd5016 100644 --- a/src/main/java/run/halo/app/handler/theme/config/support/ThemeProperty.java +++ b/src/main/java/run/halo/app/handler/theme/config/support/ThemeProperty.java @@ -85,8 +85,12 @@ public class ThemeProperty { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } ThemeProperty that = (ThemeProperty) o; return id.equals(that.id); } diff --git a/src/main/java/run/halo/app/listener/StartedListener.java b/src/main/java/run/halo/app/listener/StartedListener.java index 1b81d501b..79f75a8f5 100644 --- a/src/main/java/run/halo/app/listener/StartedListener.java +++ b/src/main/java/run/halo/app/listener/StartedListener.java @@ -66,10 +66,10 @@ public class StartedListener implements ApplicationListener { private Long likes; private Date createTime; + + private JournalType type; } diff --git a/src/main/java/run/halo/app/model/dto/MenuDTO.java b/src/main/java/run/halo/app/model/dto/MenuDTO.java index 8c8e4c2ab..cf18f90a7 100644 --- a/src/main/java/run/halo/app/model/dto/MenuDTO.java +++ b/src/main/java/run/halo/app/model/dto/MenuDTO.java @@ -10,6 +10,7 @@ import run.halo.app.model.entity.Menu; * Menu output dto. * * @author johnniang + * @author ryanwang * @date 4/3/19 */ @Data @@ -30,4 +31,6 @@ public class MenuDTO implements OutputConverter { private String icon; private Integer parentId; + + private String team; } diff --git a/src/main/java/run/halo/app/model/dto/post/BasePostMinimalDTO.java b/src/main/java/run/halo/app/model/dto/post/BasePostMinimalDTO.java index ca1be35aa..16b60b08d 100644 --- a/src/main/java/run/halo/app/model/dto/post/BasePostMinimalDTO.java +++ b/src/main/java/run/halo/app/model/dto/post/BasePostMinimalDTO.java @@ -28,8 +28,6 @@ public class BasePostMinimalDTO implements OutputConverter { /** * 阿里云 */ - ALIYUN(4); + ALIYUN(4), + + /** + * 百度云 + */ + BAIDUYUN(5), + + /** + * 腾讯云 + */ + TENCENTYUN(6); private Integer value; diff --git a/src/main/java/run/halo/app/model/enums/InputType.java b/src/main/java/run/halo/app/model/enums/InputType.java index 4dcbb1fd3..392fc2331 100644 --- a/src/main/java/run/halo/app/model/enums/InputType.java +++ b/src/main/java/run/halo/app/model/enums/InputType.java @@ -6,6 +6,7 @@ import org.springframework.lang.Nullable; * Input type enum. * * @author johnniang + * @author ryanwang * @date 4/10/19 */ public enum InputType { @@ -18,7 +19,11 @@ public enum InputType { SELECT, - TEXTAREA; + TEXTAREA, + + COLOR, + + ATTACHMENT; /** * Convert type to input type. diff --git a/src/main/java/run/halo/app/model/enums/JournalType.java b/src/main/java/run/halo/app/model/enums/JournalType.java new file mode 100644 index 000000000..97386238b --- /dev/null +++ b/src/main/java/run/halo/app/model/enums/JournalType.java @@ -0,0 +1,30 @@ +package run.halo.app.model.enums; + +/** + * Journal type. + * + * @author ryanwnag + */ +public enum JournalType implements ValueEnum { + + /** + * Public type. + */ + PUBLIC(1), + + /** + * Intimate type. + */ + INTIMATE(0); + + private final int value; + + JournalType(int value) { + this.value = value; + } + + @Override + public Integer getValue() { + return value; + } +} diff --git a/src/main/java/run/halo/app/model/enums/Mode.java b/src/main/java/run/halo/app/model/enums/Mode.java index 1dd4a7374..fb1c457ca 100644 --- a/src/main/java/run/halo/app/model/enums/Mode.java +++ b/src/main/java/run/halo/app/model/enums/Mode.java @@ -30,15 +30,15 @@ public enum Mode { @Nullable @JsonCreator public static Mode valueFrom(@Nullable String value) { - if (StringUtils.isBlank(value) || value.equalsIgnoreCase("prod")) { + if (StringUtils.isBlank(value) || "prod".equalsIgnoreCase(value)) { return Mode.PRODUCTION; } - if (value.equalsIgnoreCase("dev")) { + if ("dev".equalsIgnoreCase(value)) { return Mode.DEVELOPMENT; } - if (value.equalsIgnoreCase("test")) { + if ("test".equalsIgnoreCase(value)) { return Mode.TEST; } diff --git a/src/main/java/run/halo/app/model/enums/PostStatus.java b/src/main/java/run/halo/app/model/enums/PostStatus.java index fedd6ecff..9e5b4e3f6 100644 --- a/src/main/java/run/halo/app/model/enums/PostStatus.java +++ b/src/main/java/run/halo/app/model/enums/PostStatus.java @@ -20,7 +20,12 @@ public enum PostStatus implements ValueEnum { /** * Recycle status. */ - RECYCLE(2); + RECYCLE(2), + + /** + * Intimate status + */ + INTIMATE(3); private final int value; diff --git a/src/main/java/run/halo/app/model/enums/PostType.java b/src/main/java/run/halo/app/model/enums/PostType.java index 555bafa71..79dadd87b 100644 --- a/src/main/java/run/halo/app/model/enums/PostType.java +++ b/src/main/java/run/halo/app/model/enums/PostType.java @@ -5,6 +5,7 @@ package run.halo.app.model.enums; * * @author johnniang */ +@Deprecated public enum PostType implements ValueEnum { /** diff --git a/src/main/java/run/halo/app/model/enums/converter/PostTypeConverter.java b/src/main/java/run/halo/app/model/enums/converter/PostTypeConverter.java index 70aa84667..600fdac4d 100644 --- a/src/main/java/run/halo/app/model/enums/converter/PostTypeConverter.java +++ b/src/main/java/run/halo/app/model/enums/converter/PostTypeConverter.java @@ -11,6 +11,7 @@ import javax.persistence.Converter; * @date 3/27/19 */ @Converter(autoApply = true) +@Deprecated public class PostTypeConverter extends AbstractConverter { public PostTypeConverter() { diff --git a/src/main/java/run/halo/app/model/freemarker/tag/CategoryTagDirective.java b/src/main/java/run/halo/app/model/freemarker/tag/CategoryTagDirective.java index 44bd9d2de..54056d30d 100644 --- a/src/main/java/run/halo/app/model/freemarker/tag/CategoryTagDirective.java +++ b/src/main/java/run/halo/app/model/freemarker/tag/CategoryTagDirective.java @@ -44,7 +44,7 @@ public class CategoryTagDirective implements TemplateDirectiveModel { break; case "listByPostId": Integer postId = Integer.parseInt(params.get("postId").toString()); - env.setVariable("categories", builder.build().wrap(postCategoryService.listCategoryBy(postId))); + env.setVariable("categories", builder.build().wrap(postCategoryService.listCategoriesBy(postId))); break; case "count": env.setVariable("count", builder.build().wrap(categoryService.count())); diff --git a/src/main/java/run/halo/app/model/freemarker/tag/CommentTagDirective.java b/src/main/java/run/halo/app/model/freemarker/tag/CommentTagDirective.java index 9b01c571b..62e8df8ac 100644 --- a/src/main/java/run/halo/app/model/freemarker/tag/CommentTagDirective.java +++ b/src/main/java/run/halo/app/model/freemarker/tag/CommentTagDirective.java @@ -2,7 +2,10 @@ package run.halo.app.model.freemarker.tag; import freemarker.core.Environment; import freemarker.template.*; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Component; +import run.halo.app.model.entity.PostComment; +import run.halo.app.model.enums.CommentStatus; import run.halo.app.model.support.HaloConst; import run.halo.app.service.PostCommentService; @@ -34,7 +37,8 @@ public class CommentTagDirective implements TemplateDirectiveModel { switch (method) { case "latest": int top = Integer.parseInt(params.get("top").toString()); - env.setVariable("comments", builder.build().wrap(postCommentService.pageLatest(top))); + Page postComments = postCommentService.pageLatest(top, CommentStatus.PUBLISHED); + env.setVariable("comments", builder.build().wrap(postCommentService.convertToWithPostVo(postComments))); break; case "count": env.setVariable("count", builder.build().wrap(postCommentService.count())); diff --git a/src/main/java/run/halo/app/model/freemarker/tag/MenuTagDirective.java b/src/main/java/run/halo/app/model/freemarker/tag/MenuTagDirective.java index ee13324e2..cb39621bd 100644 --- a/src/main/java/run/halo/app/model/freemarker/tag/MenuTagDirective.java +++ b/src/main/java/run/halo/app/model/freemarker/tag/MenuTagDirective.java @@ -43,6 +43,13 @@ public class MenuTagDirective implements TemplateDirectiveModel { case "tree": env.setVariable("menus", builder.build().wrap(menuService.listAsTree(Sort.by(DESC, "priority")))); break; + case "listTeams": + env.setVariable("teams", builder.build().wrap(menuService.listTeamVos(Sort.by(DESC, "priority")))); + break; + case "listByTeam": + String team = params.get("team").toString(); + env.setVariable("menus", builder.build().wrap(menuService.listByTeam(team, Sort.by(DESC, "priority")))); + break; case "count": env.setVariable("count", builder.build().wrap(menuService.count())); break; diff --git a/src/main/java/run/halo/app/model/params/BaseMetaParam.java b/src/main/java/run/halo/app/model/params/BaseMetaParam.java new file mode 100644 index 000000000..e0ffb6401 --- /dev/null +++ b/src/main/java/run/halo/app/model/params/BaseMetaParam.java @@ -0,0 +1,36 @@ +package run.halo.app.model.params; + +import lombok.Data; +import run.halo.app.model.dto.base.InputConverter; +import run.halo.app.utils.ReflectionUtils; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.lang.reflect.ParameterizedType; + +/** + * Base meta param. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +@Data +public abstract class BaseMetaParam implements InputConverter { + + @NotBlank(message = "文章id不能为空") + private Integer postId; + + @NotBlank(message = "Meta key 不能为空") + @Size(max = 1023, message = "Meta key 的字符长度不能超过 {max}") + private String key; + + @NotBlank(message = "Meta value 不能为空") + @Size(max = 1023, message = "Meta value 的字符长度不能超过 {max}") + private String value; + + @Override + public ParameterizedType parameterizedType() { + return ReflectionUtils.getParameterizedTypeBySuperClass(BaseMetaParam.class, this.getClass()); + } +} diff --git a/src/main/java/run/halo/app/model/params/JournalParam.java b/src/main/java/run/halo/app/model/params/JournalParam.java index 6ed22ef4d..d951d08e4 100644 --- a/src/main/java/run/halo/app/model/params/JournalParam.java +++ b/src/main/java/run/halo/app/model/params/JournalParam.java @@ -3,6 +3,7 @@ package run.halo.app.model.params; import lombok.Data; import run.halo.app.model.dto.base.InputConverter; import run.halo.app.model.entity.Journal; +import run.halo.app.model.enums.JournalType; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; @@ -11,6 +12,7 @@ import javax.validation.constraints.Size; * Journal param. * * @author johnniang + * @author ryanwang * @date 19-4-25 */ @Data @@ -19,4 +21,6 @@ public class JournalParam implements InputConverter { @NotBlank(message = "内容不能为空") @Size(max = 511, message = "内容的字符长度不能超过 {max}") private String content; + + private JournalType type = JournalType.PUBLIC; } diff --git a/src/main/java/run/halo/app/model/params/JournalQuery.java b/src/main/java/run/halo/app/model/params/JournalQuery.java index 06cb654eb..0f2dc6ca7 100644 --- a/src/main/java/run/halo/app/model/params/JournalQuery.java +++ b/src/main/java/run/halo/app/model/params/JournalQuery.java @@ -1,6 +1,7 @@ package run.halo.app.model.params; import lombok.Data; +import run.halo.app.model.enums.JournalType; /** * Journal query params. @@ -15,4 +16,6 @@ public class JournalQuery { * Keyword. */ private String keyword; + + private JournalType type; } diff --git a/src/main/java/run/halo/app/model/params/MenuParam.java b/src/main/java/run/halo/app/model/params/MenuParam.java index 4788cad20..5334e2227 100644 --- a/src/main/java/run/halo/app/model/params/MenuParam.java +++ b/src/main/java/run/halo/app/model/params/MenuParam.java @@ -13,6 +13,7 @@ import javax.validation.constraints.Size; * Menu param. * * @author johnniang + * @author ryanwang * @date 4/3/19 */ @Data @@ -37,4 +38,7 @@ public class MenuParam implements InputConverter { private String icon; private Integer parentId; + + @Size(max = 255, message = "菜单分组的字符长度不能超过 {max}") + private String team; } diff --git a/src/main/java/run/halo/app/model/params/OptionParam.java b/src/main/java/run/halo/app/model/params/OptionParam.java index 6c888f52a..53e85c151 100644 --- a/src/main/java/run/halo/app/model/params/OptionParam.java +++ b/src/main/java/run/halo/app/model/params/OptionParam.java @@ -8,7 +8,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; /** - * Optiona param. + * Optional param. * * @author johnniang * @date 3/20/19 diff --git a/src/main/java/run/halo/app/model/params/PostMetaParam.java b/src/main/java/run/halo/app/model/params/PostMetaParam.java new file mode 100644 index 000000000..49d3d9c2b --- /dev/null +++ b/src/main/java/run/halo/app/model/params/PostMetaParam.java @@ -0,0 +1,19 @@ +package run.halo.app.model.params; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import run.halo.app.model.entity.PostMeta; + +/** + * Post meta param. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class PostMetaParam extends BaseMetaParam { +} diff --git a/src/main/java/run/halo/app/model/params/PostParam.java b/src/main/java/run/halo/app/model/params/PostParam.java index b955d052c..43f3d08b5 100644 --- a/src/main/java/run/halo/app/model/params/PostParam.java +++ b/src/main/java/run/halo/app/model/params/PostParam.java @@ -7,7 +7,6 @@ import run.halo.app.model.dto.base.InputConverter; import run.halo.app.model.entity.Post; import run.halo.app.model.enums.PostCreateFrom; import run.halo.app.model.enums.PostStatus; -import run.halo.app.utils.HaloUtils; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; @@ -19,6 +18,7 @@ import java.util.Set; * Post param. * * @author johnniang + * @author ryanwang * @date 3/21/19 */ @Data @@ -62,29 +62,24 @@ public class PostParam implements InputConverter { @Override public Post convertTo() { if (StringUtils.isBlank(url)) { - url = title; + url = title.replace(".",""); + } + if (null == thumbnail) { + thumbnail = ""; } - Post post = InputConverter.super.convertTo(); - // Crypt password - if (StringUtils.isNotBlank(password)) { - post.setPassword(BCrypt.hashpw(password, BCrypt.gensalt())); - } - - return post; + return InputConverter.super.convertTo(); } @Override public void update(Post post) { if (StringUtils.isBlank(url)) { - url = title; + url = title.replace(".",""); + } + if (null == thumbnail) { + thumbnail = ""; } InputConverter.super.update(post); - - // Crypt password - if (StringUtils.isNotBlank(password)) { - post.setPassword(BCrypt.hashpw(password, BCrypt.gensalt())); - } } } diff --git a/src/main/java/run/halo/app/model/params/ResetPasswordParam.java b/src/main/java/run/halo/app/model/params/ResetPasswordParam.java new file mode 100644 index 000000000..998e5db0e --- /dev/null +++ b/src/main/java/run/halo/app/model/params/ResetPasswordParam.java @@ -0,0 +1,25 @@ +package run.halo.app.model.params; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * Reset password params. + * + * @author ryanwang + * @date 2019-09-05 + */ +@Data +public class ResetPasswordParam { + + @NotBlank(message = "用户名不能为空") + private String username; + + @NotBlank(message = "邮箱不能为空") + private String email; + + private String code; + + private String password; +} diff --git a/src/main/java/run/halo/app/model/params/SheetMetaParam.java b/src/main/java/run/halo/app/model/params/SheetMetaParam.java new file mode 100644 index 000000000..b32b1879b --- /dev/null +++ b/src/main/java/run/halo/app/model/params/SheetMetaParam.java @@ -0,0 +1,19 @@ +package run.halo.app.model.params; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import run.halo.app.model.entity.SheetMeta; + +/** + * Sheet meta param. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class SheetMetaParam extends BaseMetaParam { +} diff --git a/src/main/java/run/halo/app/model/params/SheetParam.java b/src/main/java/run/halo/app/model/params/SheetParam.java index a5570eccb..1e45eb24a 100644 --- a/src/main/java/run/halo/app/model/params/SheetParam.java +++ b/src/main/java/run/halo/app/model/params/SheetParam.java @@ -11,9 +11,13 @@ import run.halo.app.utils.HaloUtils; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; +import java.util.Date; /** + * Sheet param. + * * @author johnniang + * @author ryanwang * @date 19-4-24 */ @Data @@ -35,6 +39,8 @@ public class SheetParam implements InputConverter { private Boolean disallowComment = false; + private Date createTime; + @Size(max = 255, message = "Length of password must not be more than {max}") private String password; @@ -47,37 +53,26 @@ public class SheetParam implements InputConverter { @Override public Sheet convertTo() { if (StringUtils.isBlank(url)) { - url = HaloUtils.normalizeUrl(title); - } else { - url = HaloUtils.normalizeUrl(url); + url = title.replace(".",""); } - url = HaloUtils.initializeUrlIfBlank(url); - - Sheet sheet = InputConverter.super.convertTo(); - // Crypt password - if (StringUtils.isNotBlank(password)) { - sheet.setPassword(BCrypt.hashpw(password, BCrypt.gensalt())); + if (null == thumbnail) { + thumbnail = ""; } - return sheet; + return InputConverter.super.convertTo(); } @Override public void update(Sheet sheet) { if (StringUtils.isBlank(url)) { - url = HaloUtils.normalizeUrl(title); - } else { - url = HaloUtils.normalizeUrl(url); + url = title.replace(".",""); } - url = HaloUtils.initializeUrlIfBlank(url); + if (null == thumbnail) { + thumbnail = ""; + } InputConverter.super.update(sheet); - - // Crypt password - if (StringUtils.isNotBlank(password)) { - sheet.setPassword(BCrypt.hashpw(password, BCrypt.gensalt())); - } } } diff --git a/src/main/java/run/halo/app/model/params/ThemeContentParam.java b/src/main/java/run/halo/app/model/params/ThemeContentParam.java new file mode 100644 index 000000000..f8b2ede1a --- /dev/null +++ b/src/main/java/run/halo/app/model/params/ThemeContentParam.java @@ -0,0 +1,15 @@ +package run.halo.app.model.params; + +import lombok.Data; + +/** + * Theme content param. + * + * @author ryanwang + * @date 2019-09-01 + */ +@Data +public class ThemeContentParam { + private String path; + private String content; +} diff --git a/src/main/java/run/halo/app/model/properties/AliYunProperties.java b/src/main/java/run/halo/app/model/properties/AliYunProperties.java index 7e4b076cf..c7ae609e2 100644 --- a/src/main/java/run/halo/app/model/properties/AliYunProperties.java +++ b/src/main/java/run/halo/app/model/properties/AliYunProperties.java @@ -4,10 +4,16 @@ package run.halo.app.model.properties; * AliYun properties. * * @author MyFaith + * @author ryanwang * @date 2019-04-04 00:00:56 */ public enum AliYunProperties implements PropertyEnum { + /** + * Aliyun oss domain + */ + OSS_DOMAIN("oss_aliyun_domain",String.class,""), + /** * Aliyun oss endpoint. */ @@ -31,7 +37,12 @@ public enum AliYunProperties implements PropertyEnum { /** * Aliyun oss style rule */ - OSS_STYLE_RULE("oss_aliyun_style_rule", String.class, ""); + OSS_STYLE_RULE("oss_aliyun_style_rule", String.class, ""), + + /** + * Aliyun oss thumbnail style rule + */ + OSS_THUMBNAIL_STYLE_RULE("oss_aliyun_thumbnail_style_rule", String.class, ""); private final String value; diff --git a/src/main/java/run/halo/app/model/properties/ApiProperties.java b/src/main/java/run/halo/app/model/properties/ApiProperties.java index 17d67006c..f4d42c995 100644 --- a/src/main/java/run/halo/app/model/properties/ApiProperties.java +++ b/src/main/java/run/halo/app/model/properties/ApiProperties.java @@ -8,8 +8,14 @@ package run.halo.app.model.properties; */ public enum ApiProperties implements PropertyEnum { + /** + * api_enabled + */ API_ENABLED("api_enabled", Boolean.class, "false"), + /** + * api_access_key + */ API_ACCESS_KEY("api_access_key", String.class, ""); private final String value; diff --git a/src/main/java/run/halo/app/model/properties/AttachmentProperties.java b/src/main/java/run/halo/app/model/properties/AttachmentProperties.java index 8ff4d9aaa..c33d7c04f 100644 --- a/src/main/java/run/halo/app/model/properties/AttachmentProperties.java +++ b/src/main/java/run/halo/app/model/properties/AttachmentProperties.java @@ -10,6 +10,24 @@ import run.halo.app.model.enums.AttachmentType; */ public enum AttachmentProperties implements PropertyEnum { + /** + * Upload image preview enable + */ + UPLOAD_IMAGE_PREVIEW_ENABLE("attachment_upload_image_preview_enable", Boolean.class, "true"), + + /** + * Upload max parallel uploads + */ + UPLOAD_MAX_PARALLEL_UPLOADS("attachment_upload_max_parallel_uploads", Integer.class, "3"), + + /** + * Upload max files + */ + UPLOAD_MAX_FILES("attachment_upload_max_files", Integer.class, "50"), + + /** + * attachment_type + */ ATTACHMENT_TYPE("attachment_type", AttachmentType.class, AttachmentType.LOCAL.name()); private final String value; diff --git a/src/main/java/run/halo/app/model/properties/BaiDuYunProperties.java b/src/main/java/run/halo/app/model/properties/BaiDuYunProperties.java new file mode 100644 index 000000000..48655fe59 --- /dev/null +++ b/src/main/java/run/halo/app/model/properties/BaiDuYunProperties.java @@ -0,0 +1,77 @@ +package run.halo.app.model.properties; + +/** + * BaiDuYun properties. + * + * @author wangya + * @author ryanwang + * @date 2019-07-19 + */ +public enum BaiDuYunProperties implements PropertyEnum { + + /** + * Baidu yun bos domain. + */ + BOS_DOMAIN("bos_baiduyun_domain",String.class,""), + + /** + * Baidu yun bos endpoint. + */ + BOS_ENDPOINT("bos_baiduyun_endpoint", String.class, ""), + + /** + * Baidu yun bos bucket name. + */ + BOS_BUCKET_NAME("bos_baiduyun_bucket_name", String.class, ""), + + /** + * Baidu yun bos access key. + */ + BOS_ACCESS_KEY("bos_baiduyun_access_key", String.class, ""), + + /** + * Baidu yun bos secret key. + */ + BOS_SECRET_KEY("bos_baiduyun_secret_key", String.class, ""), + + /** + * Baidu yun bos style rule. + */ + BOS_STYLE_RULE("bos_baiduyun_style_rule", String.class, ""), + + /** + * Baidu yun bos thumbnail style rule. + */ + BOS_THUMBNAIL_STYLE_RULE("bos_baiduyun_thumbnail_style_rule", String.class, ""); + + private final String value; + + private final Class type; + + private final String defaultValue; + + BaiDuYunProperties(String value, Class type, String defaultValue) { + this.defaultValue = defaultValue; + if (!PropertyEnum.isSupportedType(type)) { + throw new IllegalArgumentException("Unsupported blog property type: " + type); + } + + this.value = value; + this.type = type; + } + + @Override + public Class getType() { + return type; + } + + @Override + public String defaultValue() { + return defaultValue; + } + + @Override + public String getValue() { + return value; + } +} diff --git a/src/main/java/run/halo/app/model/properties/CommentProperties.java b/src/main/java/run/halo/app/model/properties/CommentProperties.java index d1fa61405..1d622b4e9 100644 --- a/src/main/java/run/halo/app/model/properties/CommentProperties.java +++ b/src/main/java/run/halo/app/model/properties/CommentProperties.java @@ -23,7 +23,9 @@ public enum CommentProperties implements PropertyEnum { PAGE_SIZE("comment_page_size", Integer.class, "10"), - CONTENT_PLACEHOLDER("comment_content_placeholder", String.class, ""); + CONTENT_PLACEHOLDER("comment_content_placeholder", String.class, ""), + + INTERNAL_PLUGIN_JS("comment_internal_plugin_js", String.class, "//cdn.jsdelivr.net/gh/halo-dev/halo-comment@latest/dist/halo-comment.min.js"); private final String value; diff --git a/src/main/java/run/halo/app/model/properties/OtherProperties.java b/src/main/java/run/halo/app/model/properties/OtherProperties.java index dc8c295b9..29c382534 100644 --- a/src/main/java/run/halo/app/model/properties/OtherProperties.java +++ b/src/main/java/run/halo/app/model/properties/OtherProperties.java @@ -9,7 +9,9 @@ package run.halo.app.model.properties; */ public enum OtherProperties implements PropertyEnum { - CUSTOM_HEAD("blog_custom_head",String.class,""), + CDN_DOMAIN("blog_cdn_domain", String.class, ""), + + CUSTOM_HEAD("blog_custom_head", String.class, ""), STATISTICS_CODE("blog_statistics_code", String.class, ""); diff --git a/src/main/java/run/halo/app/model/properties/PostProperties.java b/src/main/java/run/halo/app/model/properties/PostProperties.java index 54c86c1d8..c6ac52403 100644 --- a/src/main/java/run/halo/app/model/properties/PostProperties.java +++ b/src/main/java/run/halo/app/model/properties/PostProperties.java @@ -12,7 +12,9 @@ public enum PostProperties implements PropertyEnum { RSS_PAGE_SIZE("rss_page_size", Integer.class, "20"), - INDEX_PAGE_SIZE("post_index_page_size", Integer.class, "10"); + INDEX_PAGE_SIZE("post_index_page_size", Integer.class, "10"), + + INDEX_SORT("post_index_sort",String.class,"createTime"); private final String value; diff --git a/src/main/java/run/halo/app/model/properties/PropertyEnum.java b/src/main/java/run/halo/app/model/properties/PropertyEnum.java index 7190c7a5a..5c7f6ac26 100644 --- a/src/main/java/run/halo/app/model/properties/PropertyEnum.java +++ b/src/main/java/run/halo/app/model/properties/PropertyEnum.java @@ -29,7 +29,7 @@ public interface PropertyEnum extends ValueEnum { */ @SuppressWarnings("unchecked") static T convertTo(@NonNull String value, @NonNull Class type) { - Assert.hasText(value, "Value must not be null"); + Assert.notNull(value, "Value must not be null"); Assert.notNull(type, "Type must not be null"); if (type.isAssignableFrom(String.class)) { @@ -57,7 +57,7 @@ public interface PropertyEnum extends ValueEnum { } if (type.isAssignableFrom(Double.class)) { - return (T) Byte.valueOf(value); + return (T) Double.valueOf(value); } if (type.isAssignableFrom(Float.class)) { diff --git a/src/main/java/run/halo/app/model/properties/QnYunProperties.java b/src/main/java/run/halo/app/model/properties/QnYunProperties.java index 01d76ba76..a12caf9be 100644 --- a/src/main/java/run/halo/app/model/properties/QnYunProperties.java +++ b/src/main/java/run/halo/app/model/properties/QnYunProperties.java @@ -4,6 +4,7 @@ package run.halo.app.model.properties; * Qi niu yun properties. * * @author johnniang + * @author ryanwang * @date 3/26/19 */ public enum QnYunProperties implements PropertyEnum { @@ -18,7 +19,9 @@ public enum QnYunProperties implements PropertyEnum { OSS_BUCKET("oss_qiniu_bucket", String.class, ""), - OSS_STYLE_RULE("oss_qiniu_style_rule", String.class, ""); + OSS_STYLE_RULE("oss_qiniu_style_rule", String.class, ""), + + OSS_THUMBNAIL_STYLE_RULE("oss_qiniu_thumbnail_style_rule", String.class, ""); private final String value; diff --git a/src/main/java/run/halo/app/model/properties/SmmsProperties.java b/src/main/java/run/halo/app/model/properties/SmmsProperties.java new file mode 100644 index 000000000..a12af7b8c --- /dev/null +++ b/src/main/java/run/halo/app/model/properties/SmmsProperties.java @@ -0,0 +1,47 @@ +package run.halo.app.model.properties; + +/** + * Base meta entity. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +public enum SmmsProperties implements PropertyEnum { + + /** + * SM.MS personal api secret token + */ + SMMS_API_SECRET_TOKEN("smms_api_secret_token", String.class, ""); + + private final String value; + + private final Class type; + + private final String defaultValue; + + SmmsProperties(String value, Class type, String defaultValue) { + this.defaultValue = defaultValue; + if (!PropertyEnum.isSupportedType(type)) { + throw new IllegalArgumentException("Unsupported blog property type: " + type); + } + + this.value = value; + this.type = type; + } + + @Override + public Class getType() { + return type; + } + + @Override + public String defaultValue() { + return defaultValue; + } + + @Override + public String getValue() { + return value; + } +} diff --git a/src/main/java/run/halo/app/model/properties/TencentYunProperties.java b/src/main/java/run/halo/app/model/properties/TencentYunProperties.java new file mode 100644 index 000000000..cead0171b --- /dev/null +++ b/src/main/java/run/halo/app/model/properties/TencentYunProperties.java @@ -0,0 +1,67 @@ +package run.halo.app.model.properties; + +/** + * TencentYun properties. + * + * @author wangya + * @author ryanwang + * @date 2019-07-25 + */ +public enum TencentYunProperties implements PropertyEnum { + + /** + * Tencentyun cos domain. + */ + COS_DOMAIN("cos_tencentyun_domain",String.class,""), + + /** + * Tencentyun cos endpoint. + */ + COS_REGION("cos_tencentyun_region", String.class, ""), + + /** + * Tencentyun cos bucket name. + */ + COS_BUCKET_NAME("cos_tencentyun_bucket_name", String.class, ""), + + /** + * Tencentyun cos secret id. + */ + COS_SECRET_ID("cos_tencentyun_secret_id", String.class, ""), + + /** + * Tencentyun cos secret key. + */ + COS_SECRET_KEY("cos_tencentyun_secret_key", String.class, ""); + + private final String value; + + private final Class type; + + private final String defaultValue; + + TencentYunProperties(String value, Class type, String defaultValue) { + this.defaultValue = defaultValue; + if (!PropertyEnum.isSupportedType(type)) { + throw new IllegalArgumentException("Unsupported blog property type: " + type); + } + + this.value = value; + this.type = type; + } + + @Override + public Class getType() { + return type; + } + + @Override + public String defaultValue() { + return defaultValue; + } + + @Override + public String getValue() { + return value; + } +} diff --git a/src/main/java/run/halo/app/model/properties/UpYunProperties.java b/src/main/java/run/halo/app/model/properties/UpYunProperties.java index 11ffc4f60..8073cf587 100644 --- a/src/main/java/run/halo/app/model/properties/UpYunProperties.java +++ b/src/main/java/run/halo/app/model/properties/UpYunProperties.java @@ -4,21 +4,45 @@ package run.halo.app.model.properties; * You pai yun properties. * * @author johnniang + * @author ryanwang * @date 3/27/19 */ public enum UpYunProperties implements PropertyEnum { + /** + * upyun oss source + */ OSS_SOURCE("oss_upyun_source", String.class, ""), + /** + * upyun oss password + */ OSS_PASSWORD("oss_upyun_password", String.class, ""), + /** + * upyun oss bucket + */ OSS_BUCKET("oss_upyun_bucket", String.class, ""), + /** + * upyun oss domain + */ OSS_DOMAIN("oss_upyun_domain", String.class, ""), + /** + * upyun oss operator + */ OSS_OPERATOR("oss_upyun_operator", String.class, ""), - OSS_STYLE_RULE("oss_upyun_style_rule", String.class, ""); + /** + * upyun oss style rule + */ + OSS_STYLE_RULE("oss_upyun_style_rule", String.class, ""), + + /** + * upyun oss thumbnail style rule + */ + OSS_THUMBNAIL_STYLE_RULE("oss_upyun_thumbnail_style_rule",String.class,""); private final String defaultValue; private String value; diff --git a/src/main/java/run/halo/app/model/vo/BaseCommentWithParentVO.java b/src/main/java/run/halo/app/model/vo/BaseCommentWithParentVO.java index 71bc08bd1..b7b9e5eb1 100644 --- a/src/main/java/run/halo/app/model/vo/BaseCommentWithParentVO.java +++ b/src/main/java/run/halo/app/model/vo/BaseCommentWithParentVO.java @@ -23,6 +23,7 @@ public class BaseCommentWithParentVO extends BaseCommentDTO implements Cloneable */ private BaseCommentWithParentVO parent; + @Override public BaseCommentWithParentVO clone() { try { return (BaseCommentWithParentVO) super.clone(); diff --git a/src/main/java/run/halo/app/model/vo/MenuTeamVO.java b/src/main/java/run/halo/app/model/vo/MenuTeamVO.java new file mode 100644 index 000000000..166518a76 --- /dev/null +++ b/src/main/java/run/halo/app/model/vo/MenuTeamVO.java @@ -0,0 +1,22 @@ +package run.halo.app.model.vo; + +import lombok.Data; +import lombok.ToString; +import run.halo.app.model.dto.MenuDTO; + +import java.util.List; + +/** + * Menu team vo. + * + * @author ryanwang + * @date : 2019/8/28 + */ +@Data +@ToString +public class MenuTeamVO { + + private String team; + + private List menus; +} diff --git a/src/main/java/run/halo/app/model/vo/PostDetailVO.java b/src/main/java/run/halo/app/model/vo/PostDetailVO.java index f87927319..410361881 100644 --- a/src/main/java/run/halo/app/model/vo/PostDetailVO.java +++ b/src/main/java/run/halo/app/model/vo/PostDetailVO.java @@ -2,8 +2,14 @@ package run.halo.app.model.vo; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.ToString; +import run.halo.app.model.dto.CategoryDTO; +import run.halo.app.model.dto.TagDTO; import run.halo.app.model.dto.post.BasePostDetailDTO; +import run.halo.app.model.entity.Category; +import run.halo.app.model.entity.Tag; +import java.util.List; import java.util.Set; /** @@ -12,11 +18,17 @@ import java.util.Set; * @author johnniang * @date 3/21/19 */ -@EqualsAndHashCode(callSuper = true) @Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) public class PostDetailVO extends BasePostDetailDTO { private Set tagIds; + private List tags; + private Set categoryIds; + + private List categories; } + diff --git a/src/main/java/run/halo/app/repository/AttachmentRepository.java b/src/main/java/run/halo/app/repository/AttachmentRepository.java index ecd9118e4..0adaef8f2 100644 --- a/src/main/java/run/halo/app/repository/AttachmentRepository.java +++ b/src/main/java/run/halo/app/repository/AttachmentRepository.java @@ -12,6 +12,7 @@ import java.util.List; * Attachment repository * * @author johnniang + * @date 2019-04-03 */ public interface AttachmentRepository extends BaseRepository, JpaSpecificationExecutor { diff --git a/src/main/java/run/halo/app/repository/CategoryRepository.java b/src/main/java/run/halo/app/repository/CategoryRepository.java index 078e87a35..f01e079ee 100644 --- a/src/main/java/run/halo/app/repository/CategoryRepository.java +++ b/src/main/java/run/halo/app/repository/CategoryRepository.java @@ -4,6 +4,7 @@ import org.springframework.lang.NonNull; import run.halo.app.model.entity.Category; import run.halo.app.repository.base.BaseRepository; +import java.util.List; import java.util.Optional; /** @@ -44,4 +45,11 @@ public interface CategoryRepository extends BaseRepository { * @return Optional of Category */ Optional getByName(@NonNull String name); + + /** + * List categories by parent id. + * @param id parent id. + * @return list of category + */ + List findByParentId(@NonNull Integer id); } diff --git a/src/main/java/run/halo/app/repository/JournalRepository.java b/src/main/java/run/halo/app/repository/JournalRepository.java index b7e28a2c5..38b73770e 100644 --- a/src/main/java/run/halo/app/repository/JournalRepository.java +++ b/src/main/java/run/halo/app/repository/JournalRepository.java @@ -1,15 +1,29 @@ package run.halo.app.repository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.lang.NonNull; import run.halo.app.model.entity.Journal; +import run.halo.app.model.enums.JournalType; import run.halo.app.repository.base.BaseRepository; /** * Journal repository. * * @author johnniang - * @date 3/22/19 + * @author ryanwang + * @date 2019-03-22 */ public interface JournalRepository extends BaseRepository, JpaSpecificationExecutor { + /** + * Finds journals by type and pageable. + * + * @param type journal type must not be null + * @param pageable page info must not be null + * @return a page of journal + */ + @NonNull + Page findAllByType(@NonNull JournalType type, @NonNull Pageable pageable); } diff --git a/src/main/java/run/halo/app/repository/MenuRepository.java b/src/main/java/run/halo/app/repository/MenuRepository.java index e916875ae..16d092da9 100644 --- a/src/main/java/run/halo/app/repository/MenuRepository.java +++ b/src/main/java/run/halo/app/repository/MenuRepository.java @@ -1,17 +1,26 @@ package run.halo.app.repository; +import org.springframework.data.domain.Sort; import org.springframework.lang.NonNull; import run.halo.app.model.entity.Menu; import run.halo.app.repository.base.BaseRepository; +import java.util.List; + /** * Menu repository. * * @author johnniang + * @author ryanwang + * @date 2019-8-28 */ public interface MenuRepository extends BaseRepository { boolean existsByName(@NonNull String name); boolean existsByIdNotAndName(@NonNull Integer id, @NonNull String name); + + List findByParentId(@NonNull Integer id); + + List findByTeam(String team, Sort sort); } diff --git a/src/main/java/run/halo/app/repository/PhotoRepository.java b/src/main/java/run/halo/app/repository/PhotoRepository.java index c1d989ae4..f87f43d85 100644 --- a/src/main/java/run/halo/app/repository/PhotoRepository.java +++ b/src/main/java/run/halo/app/repository/PhotoRepository.java @@ -2,6 +2,7 @@ package run.halo.app.repository; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; import run.halo.app.model.entity.Photo; import run.halo.app.repository.base.BaseRepository; @@ -11,6 +12,8 @@ import java.util.List; * Photo repository. * * @author johnniang + * @author ryanwang + * @date 2019-04-03 */ public interface PhotoRepository extends BaseRepository, JpaSpecificationExecutor { @@ -22,4 +25,12 @@ public interface PhotoRepository extends BaseRepository, JpaSpec * @return list of photo */ List findByTeam(String team, Sort sort); + + /** + * Find all photo teams. + * + * @return list of teams. + */ + @Query(value = "select distinct p.team from Photo p") + List findAllTeams(); } diff --git a/src/main/java/run/halo/app/repository/PostCommentRepository.java b/src/main/java/run/halo/app/repository/PostCommentRepository.java index 974178d46..22c2500af 100644 --- a/src/main/java/run/halo/app/repository/PostCommentRepository.java +++ b/src/main/java/run/halo/app/repository/PostCommentRepository.java @@ -29,5 +29,6 @@ public interface PostCommentRepository extends BaseCommentRepository findDirectChildrenCount(@NonNull Iterable commentIds); } diff --git a/src/main/java/run/halo/app/repository/PostMetaRepository.java b/src/main/java/run/halo/app/repository/PostMetaRepository.java new file mode 100644 index 000000000..e2bc2acbe --- /dev/null +++ b/src/main/java/run/halo/app/repository/PostMetaRepository.java @@ -0,0 +1,14 @@ +package run.halo.app.repository; + +import run.halo.app.model.entity.PostMeta; +import run.halo.app.repository.base.BaseMetaRepository; + +/** + * PostMeta repository. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +public interface PostMetaRepository extends BaseMetaRepository { +} diff --git a/src/main/java/run/halo/app/repository/SheetCommentRepository.java b/src/main/java/run/halo/app/repository/SheetCommentRepository.java index e121952b5..4a858c768 100644 --- a/src/main/java/run/halo/app/repository/SheetCommentRepository.java +++ b/src/main/java/run/halo/app/repository/SheetCommentRepository.java @@ -29,5 +29,6 @@ public interface SheetCommentRepository extends BaseCommentRepository findDirectChildrenCount(@NonNull Iterable commentIds); } diff --git a/src/main/java/run/halo/app/repository/SheetMetaRepository.java b/src/main/java/run/halo/app/repository/SheetMetaRepository.java new file mode 100644 index 000000000..4a6b7d98b --- /dev/null +++ b/src/main/java/run/halo/app/repository/SheetMetaRepository.java @@ -0,0 +1,14 @@ +package run.halo.app.repository; + +import run.halo.app.model.entity.SheetMeta; +import run.halo.app.repository.base.BaseMetaRepository; + +/** + * SheetMeta repository. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +public interface SheetMetaRepository extends BaseMetaRepository { +} diff --git a/src/main/java/run/halo/app/repository/SheetRepository.java b/src/main/java/run/halo/app/repository/SheetRepository.java index 5846ef803..557300ffc 100644 --- a/src/main/java/run/halo/app/repository/SheetRepository.java +++ b/src/main/java/run/halo/app/repository/SheetRepository.java @@ -25,5 +25,6 @@ public interface SheetRepository extends BasePostRepository { Long countLike(); @NonNull + @Override Optional getByUrlAndStatus(@NonNull String url, @NonNull PostStatus status); } diff --git a/src/main/java/run/halo/app/repository/base/BaseMetaRepository.java b/src/main/java/run/halo/app/repository/base/BaseMetaRepository.java new file mode 100644 index 000000000..297ad38f7 --- /dev/null +++ b/src/main/java/run/halo/app/repository/base/BaseMetaRepository.java @@ -0,0 +1,28 @@ +package run.halo.app.repository.base; + +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.lang.NonNull; +import run.halo.app.model.entity.BaseMeta; + +import java.util.List; + +/** + * Base meta repository. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +@NoRepositoryBean +public interface BaseMetaRepository extends BaseRepository, JpaSpecificationExecutor { + + /** + * Finds all metas by post id. + * + * @param postId post id must not be null + * @return a list of meta + */ + @NonNull + List findAllByPostId(@NonNull Integer postId); +} diff --git a/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java b/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java index 736ab2f53..5070617de 100644 --- a/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java +++ b/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java @@ -90,7 +90,7 @@ public class AdminAuthenticationFilter extends AbstractAuthenticationFilter { String token = getTokenFromRequest(request); if (StringUtils.isBlank(token)) { - getFailureHandler().onFailure(request, response, new AuthenticationException("You have to login before accessing admin api")); + getFailureHandler().onFailure(request, response, new AuthenticationException("未登录,请登陆后访问")); return; } diff --git a/src/main/java/run/halo/app/service/AdminService.java b/src/main/java/run/halo/app/service/AdminService.java index d8f1b054f..0287c8bda 100644 --- a/src/main/java/run/halo/app/service/AdminService.java +++ b/src/main/java/run/halo/app/service/AdminService.java @@ -4,6 +4,7 @@ import org.springframework.lang.NonNull; import run.halo.app.model.dto.EnvironmentDTO; import run.halo.app.model.dto.StatisticDTO; import run.halo.app.model.params.LoginParam; +import run.halo.app.model.params.ResetPasswordParam; import run.halo.app.security.token.AuthToken; /** @@ -42,6 +43,20 @@ public interface AdminService { */ void clearToken(); + /** + * Send reset password code to administrator's email. + * + * @param param param must not be null + */ + void sendResetPasswordCode(@NonNull ResetPasswordParam param); + + /** + * Reset password by code. + * + * @param param param must not be null + */ + void resetPasswordByCode(@NonNull ResetPasswordParam param); + /** * Get system counts. * @@ -74,6 +89,7 @@ public interface AdminService { /** * Get spring logs. + * * @return recently logs. */ String getSpringLogs(); diff --git a/src/main/java/run/halo/app/service/BackupService.java b/src/main/java/run/halo/app/service/BackupService.java index eefc8dccc..48b6973bd 100644 --- a/src/main/java/run/halo/app/service/BackupService.java +++ b/src/main/java/run/halo/app/service/BackupService.java @@ -19,5 +19,5 @@ public interface BackupService { * @param file file * @return post info */ - BasePostDetailDTO importMarkdowns(MultipartFile file) throws IOException; + BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException; } diff --git a/src/main/java/run/halo/app/service/CategoryService.java b/src/main/java/run/halo/app/service/CategoryService.java index d0a76e021..fee7021cb 100755 --- a/src/main/java/run/halo/app/service/CategoryService.java +++ b/src/main/java/run/halo/app/service/CategoryService.java @@ -65,6 +65,12 @@ public interface CategoryService extends CrudService { @Transactional void removeCategoryAndPostCategoryBy(Integer categoryId); + /** + * List categories by parent id. + * @param id parent id. + * @return list of category. + */ + List listByParentId(@NonNull Integer id); /** * Converts to category dto. diff --git a/src/main/java/run/halo/app/service/JournalService.java b/src/main/java/run/halo/app/service/JournalService.java index 080ab6068..afe469576 100644 --- a/src/main/java/run/halo/app/service/JournalService.java +++ b/src/main/java/run/halo/app/service/JournalService.java @@ -7,6 +7,7 @@ import org.springframework.lang.Nullable; import run.halo.app.model.dto.JournalDTO; import run.halo.app.model.dto.JournalWithCmtCountDTO; import run.halo.app.model.entity.Journal; +import run.halo.app.model.enums.JournalType; import run.halo.app.model.params.JournalParam; import run.halo.app.model.params.JournalQuery; import run.halo.app.service.base.CrudService; @@ -49,6 +50,16 @@ public interface JournalService extends CrudService { @NonNull Page pageBy(@NonNull JournalQuery journalQuery, @NonNull Pageable pageable); + /** + * Lists by type. + * + * @param type journal type must not be null + * @param pageable page info must not be null + * @return a page of journal + */ + @NonNull + Page pageBy(@NonNull JournalType type, @NonNull Pageable pageable); + /** * Converts to journal dto. * diff --git a/src/main/java/run/halo/app/service/MenuService.java b/src/main/java/run/halo/app/service/MenuService.java index 72135eef3..defb6c36b 100644 --- a/src/main/java/run/halo/app/service/MenuService.java +++ b/src/main/java/run/halo/app/service/MenuService.java @@ -5,6 +5,7 @@ import org.springframework.lang.NonNull; import run.halo.app.model.dto.MenuDTO; import run.halo.app.model.entity.Menu; import run.halo.app.model.params.MenuParam; +import run.halo.app.model.vo.MenuTeamVO; import run.halo.app.model.vo.MenuVO; import run.halo.app.service.base.CrudService; @@ -28,6 +29,24 @@ public interface MenuService extends CrudService { @NonNull List listDtos(@NonNull Sort sort); + /** + * Lists menu team vos. + * + * @param sort must not be null + * @return a list of menu team vo + */ + @NonNull + List listTeamVos(@NonNull Sort sort); + + /** + * List menus by team. + * + * @param team team + * @param sort sort + * @return list of menus + */ + List listByTeam(@NonNull String team, Sort sort); + /** * Creates a menu. * @@ -44,4 +63,12 @@ public interface MenuService extends CrudService { * @return a menu tree */ List listAsTree(Sort sort); + + /** + * Lists menu by parent id. + * + * @param id id + * @return a list of menu + */ + List listByParentId(@NonNull Integer id); } diff --git a/src/main/java/run/halo/app/service/OptionService.java b/src/main/java/run/halo/app/service/OptionService.java index 7f7c49389..c93282a6f 100755 --- a/src/main/java/run/halo/app/service/OptionService.java +++ b/src/main/java/run/halo/app/service/OptionService.java @@ -39,7 +39,7 @@ public interface OptionService extends CrudService { * @param options options */ @Transactional - void save(@Nullable Map options); + void save(@Nullable Map options); /** * SAve multiple options diff --git a/src/main/java/run/halo/app/service/PhotoService.java b/src/main/java/run/halo/app/service/PhotoService.java index fb3759fdd..dec0645d0 100644 --- a/src/main/java/run/halo/app/service/PhotoService.java +++ b/src/main/java/run/halo/app/service/PhotoService.java @@ -64,4 +64,11 @@ public interface PhotoService extends CrudService { */ @NonNull Photo createBy(@NonNull PhotoParam photoParam); + + /** + * List all teams. + * + * @return list of teams + */ + List listAllTeams(); } diff --git a/src/main/java/run/halo/app/service/PostCategoryService.java b/src/main/java/run/halo/app/service/PostCategoryService.java index bca6cb80c..383666108 100644 --- a/src/main/java/run/halo/app/service/PostCategoryService.java +++ b/src/main/java/run/halo/app/service/PostCategoryService.java @@ -34,7 +34,7 @@ public interface PostCategoryService extends CrudService * @return a list of category */ @NonNull - List listCategoryBy(@NonNull Integer postId); + List listCategoriesBy(@NonNull Integer postId); /** * List category list map by post id collection. diff --git a/src/main/java/run/halo/app/service/PostCommentService.java b/src/main/java/run/halo/app/service/PostCommentService.java index a55c09a03..ca5ac5db0 100644 --- a/src/main/java/run/halo/app/service/PostCommentService.java +++ b/src/main/java/run/halo/app/service/PostCommentService.java @@ -15,6 +15,7 @@ import java.util.List; * Post comment service interface. * * @author johnniang + * @author ryanwang * @date 2019-03-14 */ public interface PostCommentService extends BaseCommentService { @@ -28,6 +29,15 @@ public interface PostCommentService extends BaseCommentService { @NonNull Page convertToWithPostVo(@NonNull Page commentPage); + /** + * Converts to with post vo + * + * @param comment comment + * @return a comment with post vo + */ + @NonNull + PostCommentWithPostVO convertToWithPostVo(@NonNull PostComment comment); + /** * Converts to with post vo * diff --git a/src/main/java/run/halo/app/service/PostMetaService.java b/src/main/java/run/halo/app/service/PostMetaService.java new file mode 100644 index 000000000..5f1930c01 --- /dev/null +++ b/src/main/java/run/halo/app/service/PostMetaService.java @@ -0,0 +1,14 @@ +package run.halo.app.service; + +import run.halo.app.model.entity.PostMeta; +import run.halo.app.service.base.BaseMetaService; + +/** + * Post meta service interface. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +public interface PostMetaService extends BaseMetaService { +} diff --git a/src/main/java/run/halo/app/service/PostService.java b/src/main/java/run/halo/app/service/PostService.java index bedf523c3..71cd51f65 100755 --- a/src/main/java/run/halo/app/service/PostService.java +++ b/src/main/java/run/halo/app/service/PostService.java @@ -3,7 +3,6 @@ package run.halo.app.service; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.lang.NonNull; -import org.springframework.transaction.annotation.Transactional; import run.halo.app.model.entity.Post; import run.halo.app.model.enums.PostStatus; import run.halo.app.model.params.PostQuery; @@ -55,7 +54,6 @@ public interface PostService extends BasePostService { * @return post created */ @NonNull - @Transactional PostDetailVO createBy(@NonNull Post post, Set tagIds, Set categoryIds, boolean autoSave); /** @@ -68,7 +66,6 @@ public interface PostService extends BasePostService { * @return updated post */ @NonNull - @Transactional PostDetailVO updateBy(@NonNull Post postToUpdate, Set tagIds, Set categoryIds, boolean autoSave); /** diff --git a/src/main/java/run/halo/app/service/SheetCommentService.java b/src/main/java/run/halo/app/service/SheetCommentService.java index dea4a1931..614a3a401 100644 --- a/src/main/java/run/halo/app/service/SheetCommentService.java +++ b/src/main/java/run/halo/app/service/SheetCommentService.java @@ -13,13 +13,35 @@ import java.util.List; * Sheet comment service interface. * * @author johnniang + * @author ryanwang * @date 2019-04-24 */ public interface SheetCommentService extends BaseCommentService { + /** + * Converts to with sheet vo + * + * @param comment comment + * @return a comment with sheet vo + */ @NonNull - List convertToWithPostVo(@Nullable List sheetComments); + SheetCommentWithSheetVO convertToWithSheetVo(@NonNull SheetComment comment); + /** + * Converts to with sheet vo + * + * @param sheetComments sheet comments + * @return a sheet comments with sheet vo + */ @NonNull - Page convertToWithPostVo(@NonNull Page sheetCommentPage); + List convertToWithSheetVo(@Nullable List sheetComments); + + /** + * Converts to with sheet vo + * + * @param sheetCommentPage sheet comments + * @return a page of sheet comments with sheet vo + */ + @NonNull + Page convertToWithSheetVo(@NonNull Page sheetCommentPage); } diff --git a/src/main/java/run/halo/app/service/SheetMetaService.java b/src/main/java/run/halo/app/service/SheetMetaService.java new file mode 100644 index 000000000..e78ca6f94 --- /dev/null +++ b/src/main/java/run/halo/app/service/SheetMetaService.java @@ -0,0 +1,14 @@ +package run.halo.app.service; + +import run.halo.app.model.entity.SheetMeta; +import run.halo.app.service.base.BaseMetaService; + +/** + * Sheet meta service interface. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +public interface SheetMetaService extends BaseMetaService { +} diff --git a/src/main/java/run/halo/app/service/ThemeService.java b/src/main/java/run/halo/app/service/ThemeService.java index 6ad65d68d..1af4c5d30 100644 --- a/src/main/java/run/halo/app/service/ThemeService.java +++ b/src/main/java/run/halo/app/service/ThemeService.java @@ -24,6 +24,7 @@ public interface ThemeService { /** * Theme property file name. */ + @Deprecated String THEME_PROPERTY_FILE_NAME = "theme.yaml"; /** @@ -164,6 +165,15 @@ public interface ThemeService { */ String getTemplateContent(@NonNull String absolutePath); + /** + * Gets template content by template absolute path and themeId. + * + * @param themeId themeId + * @param absolutePath absolute path + * @return template content + */ + String getTemplateContent(@NonNull String themeId, @NonNull String absolutePath); + /** * Saves template content by template absolute path. * @@ -172,6 +182,15 @@ public interface ThemeService { */ void saveTemplateContent(@NonNull String absolutePath, @NonNull String content); + /** + * Saves template content by template absolute path and themeId. + * + * @param themeId themeId + * @param absolutePath absolute path + * @param content new content + */ + void saveTemplateContent(@NonNull String themeId, @NonNull String absolutePath, @NonNull String content); + /** * Deletes a theme by key. * @@ -236,6 +255,7 @@ public interface ThemeService { * * @param themeTmpPath theme temporary path must not be null * @return theme property + * @throws IOException IOException */ @NonNull ThemeProperty add(@NonNull Path themeTmpPath) throws IOException; @@ -262,4 +282,13 @@ public interface ThemeService { */ @NonNull ThemeProperty update(@NonNull String themeId); + + /** + * Updates theme by theme id. + * + * @param themeId theme id must not be blank + * @param file multipart file must not be null + * @return theme info + */ + ThemeProperty update(@NonNull String themeId, @NonNull MultipartFile file); } diff --git a/src/main/java/run/halo/app/service/UserService.java b/src/main/java/run/halo/app/service/UserService.java index 3f2563fd4..c306c5c28 100755 --- a/src/main/java/run/halo/app/service/UserService.java +++ b/src/main/java/run/halo/app/service/UserService.java @@ -14,6 +14,7 @@ import java.util.Optional; * User service interface. * * @author johnniang + * @author ryanwang * @date 2019-03-14 */ public interface UserService extends CrudService { @@ -125,9 +126,11 @@ public interface UserService extends CrudService { void setPassword(@NonNull User user, @NonNull String plainPassword); /** - * Set user default avatar,use Gravatar(http://cn.gravatar.com) + * verify user's email and username * - * @param user user must not be null + * @param username username must not be null + * @param password password must not be null + * @return boolean */ - void setDefaultAvatar(@NonNull User user); + boolean verifyUser(@NonNull String username, @NonNull String password); } diff --git a/src/main/java/run/halo/app/service/base/BaseMetaService.java b/src/main/java/run/halo/app/service/base/BaseMetaService.java new file mode 100644 index 000000000..a368f2b5a --- /dev/null +++ b/src/main/java/run/halo/app/service/base/BaseMetaService.java @@ -0,0 +1,62 @@ +package run.halo.app.service.base; + +import org.springframework.lang.NonNull; +import run.halo.app.model.entity.BaseMeta; +import run.halo.app.model.params.BaseMetaParam; + +import java.util.List; +import java.util.Map; + +/** + * Base meta service interface. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +public interface BaseMetaService extends CrudService { + + /** + * Lists metas by post id. + * + * @param postId post id must not be null + * @return a list of meta + */ + @NonNull + List listBy(@NonNull Integer postId); + + + /** + * Creates a meta by meta. + * + * @param meta meta must not be null + * @return created meta + */ + @NonNull + @Override + META create(@NonNull META meta); + + /** + * Creates a meta by meta param. + * + * @param metaParam meta param must not be null + * @return created meta + */ + @NonNull + META createBy(@NonNull BaseMetaParam metaParam); + + /** + * Target validation. + * + * @param targetId target id must not be null (post id, sheet id) + */ + void validateTarget(@NonNull Integer targetId); + + /** + * Convert to map. + * + * @param metas a list of metas + * @return a map of metas + */ + Map convertToMap(List metas); +} diff --git a/src/main/java/run/halo/app/service/base/BasePostService.java b/src/main/java/run/halo/app/service/base/BasePostService.java index e6f62a96d..520e045a5 100644 --- a/src/main/java/run/halo/app/service/base/BasePostService.java +++ b/src/main/java/run/halo/app/service/base/BasePostService.java @@ -4,7 +4,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; -import org.springframework.transaction.annotation.Transactional; import run.halo.app.model.dto.post.BasePostDetailDTO; import run.halo.app.model.dto.post.BasePostMinimalDTO; import run.halo.app.model.dto.post.BasePostSimpleDTO; @@ -154,7 +153,6 @@ public interface BasePostService extends CrudService extends CrudService extends CrudService extends CrudService { + throw new ServiceException("已经获取过验证码,不能重复获取"); + }); + + Boolean emailEnabled = optionService.getByPropertyOrDefault(EmailProperties.ENABLED, Boolean.class, false); + + if (!emailEnabled) { + throw new ServiceException("未启用 SMTP 服务"); + } + + if (!userService.verifyUser(param.getUsername(), param.getEmail())) { + throw new ServiceException("用户名或者邮箱验证错误"); + } + + // Gets random code. + String code = RandomUtil.randomNumbers(6); + + log.info("Get reset password code:{}", code); + + // Send email to administrator. + String content = "您正在进行密码重置操作,如不是本人操作,请尽快做好相应措施。密码重置验证码如下(五分钟有效):\n" + code; + mailService.sendMail(param.getEmail(), "找回密码验证码", content); + + // Cache code. + cacheStore.putAny("code", code, 5, TimeUnit.MINUTES); + } + + @Override + public void resetPasswordByCode(ResetPasswordParam param) { + if (StringUtils.isEmpty(param.getCode())) { + throw new ServiceException("验证码不能为空"); + } + + if (StringUtils.isEmpty(param.getPassword())) { + throw new ServiceException("密码不能为空"); + } + + if (!userService.verifyUser(param.getUsername(), param.getEmail())) { + throw new ServiceException("用户名或者邮箱验证错误"); + } + + // verify code + String code = cacheStore.getAny("code", String.class).orElseThrow(() -> new ServiceException("未获取过验证码")); + if (!code.equals(param.getCode())) { + throw new ServiceException("验证码不正确"); + } + + User user = userService.getCurrentUser().orElseThrow(() -> new ServiceException("未查询到博主信息")); + + // reset password + userService.setPassword(user, param.getPassword()); + + // Update this user + userService.update(user); + + // clear code cache + cacheStore.delete("code"); + } + @Override public StatisticDTO getCount() { StatisticDTO statisticDTO = new StatisticDTO(); - statisticDTO.setPostCount(postService.countByStatus(PostStatus.PUBLISHED)); + statisticDTO.setPostCount(postService.countByStatus(PostStatus.PUBLISHED) + sheetService.countByStatus(PostStatus.PUBLISHED)); statisticDTO.setAttachmentCount(attachmentService.count()); // Handle comment count @@ -221,8 +289,7 @@ public class AdminServiceImpl implements AdminService { EnvironmentDTO environmentDTO = new EnvironmentDTO(); // Get application start time. - RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); - environmentDTO.setStartTime(runtimeMXBean.getStartTime()); + environmentDTO.setStartTime(ManagementFactory.getRuntimeMXBean().getStartTime()); environmentDTO.setDatabase("org.h2.Driver".equals(driverClassName) ? "H2" : "MySQL"); @@ -323,7 +390,7 @@ public class AdminServiceImpl implements AdminService { String contentType = aAssetMap.getOrDefault("content_type", "").toString(); Object name = aAssetMap.getOrDefault("name", ""); - return name.toString().matches(HALO_ADMIN_VERSION_REGEX) && contentType.equalsIgnoreCase("application/zip"); + return name.toString().matches(HALO_ADMIN_VERSION_REGEX) && "application/zip".equalsIgnoreCase(contentType); }; } diff --git a/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java b/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java index ed9133bf7..5360e2720 100644 --- a/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java @@ -26,7 +26,7 @@ public class BackupServiceImpl implements BackupService { } @Override - public BasePostDetailDTO importMarkdowns(MultipartFile file) throws IOException { + public BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException { // Read markdown content. String markdown = IoUtil.read(file.getInputStream(), StandardCharsets.UTF_8); diff --git a/src/main/java/run/halo/app/service/impl/BaseCommentServiceImpl.java b/src/main/java/run/halo/app/service/impl/BaseCommentServiceImpl.java index 14c030324..5852b7ee5 100644 --- a/src/main/java/run/halo/app/service/impl/BaseCommentServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/BaseCommentServiceImpl.java @@ -390,7 +390,7 @@ public abstract class BaseCommentServiceImpl extend Assert.notNull(toCompareComment, "Comment to compare must not be null"); // Get sort order - Sort.Order order = sort.filter(anOrder -> anOrder.getProperty().equals("id")) + Sort.Order order = sort.filter(anOrder -> "id".equals(anOrder.getProperty())) .get() .findFirst() .orElseGet(() -> Sort.Order.desc("id")); @@ -404,6 +404,7 @@ public abstract class BaseCommentServiceImpl extend } @NonNull + @Override public List convertToVo(@Nullable List comments, @Nullable Comparator comparator) { if (CollectionUtils.isEmpty(comments)) { return Collections.emptyList(); diff --git a/src/main/java/run/halo/app/service/impl/BaseMetaServiceImpl.java b/src/main/java/run/halo/app/service/impl/BaseMetaServiceImpl.java new file mode 100644 index 000000000..9ea1c9b7e --- /dev/null +++ b/src/main/java/run/halo/app/service/impl/BaseMetaServiceImpl.java @@ -0,0 +1,72 @@ +package run.halo.app.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import run.halo.app.model.entity.BaseMeta; +import run.halo.app.model.params.BaseMetaParam; +import run.halo.app.repository.base.BaseMetaRepository; +import run.halo.app.service.base.AbstractCrudService; +import run.halo.app.service.base.BaseMetaService; +import run.halo.app.utils.ServiceUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Base meta service implementation. + * + * @author ryanwang + * @author ikaisec + * @date 2019-08-04 + */ +@Slf4j +public abstract class BaseMetaServiceImpl extends AbstractCrudService implements BaseMetaService { + + private final BaseMetaRepository baseMetaRepository; + + + public BaseMetaServiceImpl(BaseMetaRepository baseMetaRepository) { + super(baseMetaRepository); + this.baseMetaRepository = baseMetaRepository; + } + + @Override + public List listBy(Integer postId) { + Assert.notNull(postId, "Post id must not be null"); + return baseMetaRepository.findAllByPostId(postId); + } + + @Override + public META create(META meta) { + Assert.notNull(meta, "Domain must not be null"); + + // Check post id + if (!ServiceUtils.isEmptyId(meta.getPostId())) { + validateTarget(meta.getPostId()); + } + + // Create meta + return super.create(meta); + } + + @Override + public META createBy(BaseMetaParam metaParam) { + Assert.notNull(metaParam, "Meta param must not be null"); + return create(metaParam.convertTo()); + } + + @Override + public Map convertToMap(List metas) { + if (CollectionUtils.isEmpty(metas)) { + return Collections.emptyMap(); + } + + Map result = new HashMap<>(); + metas.forEach(meta -> result.put(meta.getKey(), meta.getValue())); + + return result; + } +} diff --git a/src/main/java/run/halo/app/service/impl/BasePostServiceImpl.java b/src/main/java/run/halo/app/service/impl/BasePostServiceImpl.java index ee36ee138..d81f180c5 100644 --- a/src/main/java/run/halo/app/service/impl/BasePostServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/BasePostServiceImpl.java @@ -7,6 +7,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.lang.NonNull; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import run.halo.app.exception.AlreadyExistsException; @@ -78,7 +79,7 @@ public abstract class BasePostServiceImpl extends Abstrac public POST getByUrl(String url) { Assert.hasText(url, "Url must not be blank"); - return basePostRepository.getByUrl(url).orElseThrow(() -> new NotFoundException("该文章不存在").setErrorData(url)); + return basePostRepository.getByUrl(url).orElseThrow(() -> new NotFoundException("查询不到该文章的信息").setErrorData(url)); } @Override @@ -88,7 +89,7 @@ public abstract class BasePostServiceImpl extends Abstrac Optional postOptional = basePostRepository.getByUrlAndStatus(url, status); - return postOptional.orElseThrow(() -> new NotFoundException("The post with status " + status + " and url " + url + " was not existed").setErrorData(url)); + return postOptional.orElseThrow(() -> new NotFoundException("查询不到该文章的信息").setErrorData(url)); } @Override @@ -137,7 +138,7 @@ public abstract class BasePostServiceImpl extends Abstrac public Page pageLatest(int top) { Assert.isTrue(top > 0, "Top number must not be less than 0"); - PageRequest latestPageable = PageRequest.of(0, top, Sort.by(DESC, "editTime")); + PageRequest latestPageable = PageRequest.of(0, top, Sort.by(DESC, "createTime")); return listAll(latestPageable); } @@ -152,7 +153,7 @@ public abstract class BasePostServiceImpl extends Abstrac public List listLatest(int top) { Assert.isTrue(top > 0, "Top number must not be less than 0"); - PageRequest latestPageable = PageRequest.of(0, top, Sort.by(DESC, "editTime")); + PageRequest latestPageable = PageRequest.of(0, top, Sort.by(DESC, "createTime")); return basePostRepository.findAllByStatus(PostStatus.PUBLISHED, latestPageable).getContent(); } @@ -173,9 +174,10 @@ public abstract class BasePostServiceImpl extends Abstrac } @Override + @Transactional public void increaseVisit(long visits, Integer postId) { Assert.isTrue(visits > 0, "Visits to increase must not be less than 1"); - Assert.notNull(postId, "Goods id must not be null"); + Assert.notNull(postId, "Post id must not be null"); long affectedRows = basePostRepository.updateVisit(visits, postId); @@ -186,6 +188,7 @@ public abstract class BasePostServiceImpl extends Abstrac } @Override + @Transactional public void increaseLike(long likes, Integer postId) { Assert.isTrue(likes > 0, "Likes to increase must not be less than 1"); Assert.notNull(postId, "Goods id must not be null"); @@ -199,21 +202,31 @@ public abstract class BasePostServiceImpl extends Abstrac } @Override + @Transactional public void increaseVisit(Integer postId) { increaseVisit(1L, postId); } @Override + @Transactional public void increaseLike(Integer postId) { increaseLike(1L, postId); } @Override + @Transactional public POST createOrUpdateBy(POST post) { Assert.notNull(post, "Post must not be null"); // Render content - post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent())); + if (post.getStatus() == PostStatus.PUBLISHED) { + post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent())); + } + + // if password is not empty,change status to intimate + if (StringUtils.isNotEmpty(post.getPassword()) && post.getStatus() != PostStatus.DRAFT) { + post.setStatus(PostStatus.INTIMATE); + } // Create or update post if (ServiceUtils.isEmptyId(post.getId())) { diff --git a/src/main/java/run/halo/app/service/impl/CategoryServiceImpl.java b/src/main/java/run/halo/app/service/impl/CategoryServiceImpl.java index e7da2c132..9f44e4f70 100644 --- a/src/main/java/run/halo/app/service/impl/CategoryServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/CategoryServiceImpl.java @@ -154,7 +154,7 @@ public class CategoryServiceImpl extends AbstractCrudService @Override public Category getBySlugNameOfNonNull(String slugName) { - return categoryRepository.getBySlugName(slugName).orElseThrow(() -> new NotFoundException("该分类不存在").setErrorData(slugName)); + return categoryRepository.getBySlugName(slugName).orElseThrow(() -> new NotFoundException("查询不到该分类的信息").setErrorData(slugName)); } @Override @@ -165,12 +165,25 @@ public class CategoryServiceImpl extends AbstractCrudService @Override @Transactional public void removeCategoryAndPostCategoryBy(Integer categoryId) { + List categories = listByParentId(categoryId); + if (null != categories && categories.size() > 0) { + categories.forEach(category -> { + category.setParentId(0); + update(category); + }); + } // Remove category removeById(categoryId); // Remove post categories postCategoryService.removeByCategoryId(categoryId); } + @Override + public List listByParentId(Integer id) { + Assert.notNull(id, "Parent id must not be null"); + return categoryRepository.findByParentId(id); + } + @Override public CategoryDTO convertTo(Category category) { Assert.notNull(category, "Category must not be null"); diff --git a/src/main/java/run/halo/app/service/impl/JournalCommentServiceImpl.java b/src/main/java/run/halo/app/service/impl/JournalCommentServiceImpl.java index 25928445a..c3ba32348 100644 --- a/src/main/java/run/halo/app/service/impl/JournalCommentServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/JournalCommentServiceImpl.java @@ -49,7 +49,7 @@ public class JournalCommentServiceImpl extends BaseCommentServiceImpl im return journalRepository.findAll(buildSpecByQuery(journalQuery), pageable); } + @Override + public Page pageBy(JournalType type, Pageable pageable) { + Assert.notNull(type, "Journal type must not be null"); + Assert.notNull(pageable, "Page info must not be null"); + return journalRepository.findAllByType(type, pageable); + } + @Override public JournalDTO convertTo(Journal journal) { Assert.notNull(journal, "Journal must not be null"); @@ -118,6 +126,10 @@ public class JournalServiceImpl extends AbstractCrudService im return (Specification) (root, query, criteriaBuilder) -> { List predicates = new LinkedList<>(); + if (journalQuery.getType() != null) { + predicates.add(criteriaBuilder.equal(root.get("type"), journalQuery.getType())); + } + if (journalQuery.getKeyword() != null) { // Format like condition String likeCondition = String.format("%%%s%%", StringUtils.strip(journalQuery.getKeyword())); diff --git a/src/main/java/run/halo/app/service/impl/MailServiceImpl.java b/src/main/java/run/halo/app/service/impl/MailServiceImpl.java index a2d58ca79..9556619c4 100644 --- a/src/main/java/run/halo/app/service/impl/MailServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/MailServiceImpl.java @@ -52,7 +52,7 @@ public class MailServiceImpl implements MailService { } catch (Exception e) { log.debug("Email properties: to username: [{}], from username: [{}], subject: [{}], content: [{}]", to, fromUsername, subject, content); - throw new EmailException("Failed to send email to " + to, e); + throw new EmailException("发送邮件到 " + to + " 失败,请检查 SMTP 服务配置是否正确", e); } } @@ -74,7 +74,7 @@ public class MailServiceImpl implements MailService { } catch (Exception e) { log.debug("Email properties: to username: [{}], from username: [{}], subject: [{}], template name: [{}], content: [{}]", to, fromUsername, subject, templateName, content); - throw new EmailException("Failed to send template email to " + to, e).setErrorData(templateName); + throw new EmailException("发送模板邮件到 " + to + " 失败,请检查 SMTP 服务配置是否正确", e); } } @@ -97,7 +97,7 @@ public class MailServiceImpl implements MailService { } catch (Exception e) { log.debug("Email properties: to username: [{}], from username: [{}], subject: [{}], template name: [{}], attachment: [{}], content: [{}]", to, fromUsername, subject, templateName, attachFilename, content); - throw new EmailException("Failed to send attachment email to " + to, e); + throw new EmailException("发送附件邮件到 " + to + " 失败,请检查 SMTP 服务配置是否正确", e); } } diff --git a/src/main/java/run/halo/app/service/impl/MenuServiceImpl.java b/src/main/java/run/halo/app/service/impl/MenuServiceImpl.java index 29aa71f65..d1981e6cd 100644 --- a/src/main/java/run/halo/app/service/impl/MenuServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/MenuServiceImpl.java @@ -9,15 +9,14 @@ import run.halo.app.exception.AlreadyExistsException; import run.halo.app.model.dto.MenuDTO; import run.halo.app.model.entity.Menu; import run.halo.app.model.params.MenuParam; +import run.halo.app.model.vo.MenuTeamVO; import run.halo.app.model.vo.MenuVO; import run.halo.app.repository.MenuRepository; import run.halo.app.service.MenuService; import run.halo.app.service.base.AbstractCrudService; import run.halo.app.utils.ServiceUtils; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; /** @@ -43,6 +42,41 @@ public class MenuServiceImpl extends AbstractCrudService implemen return convertTo(listAll(sort)); } + @Override + public List listTeamVos(Sort sort) { + Assert.notNull(sort, "Sort info must not be null"); + + // List all menus + List menus = listDtos(sort); + + // Get teams + Set teams = ServiceUtils.fetchProperty(menus, MenuDTO::getTeam); + + // Convert to team menu list map (Key: team, value: menu list) + Map> teamMenuListMap = ServiceUtils.convertToListMap(teams, menus, MenuDTO::getTeam); + + List result = new LinkedList<>(); + + // Wrap menu team vo list + teamMenuListMap.forEach((team, menuList) -> { + // Build menu team vo + MenuTeamVO menuTeamVO = new MenuTeamVO(); + menuTeamVO.setTeam(team); + menuTeamVO.setMenus(menuList); + + // Add it to result + result.add(menuTeamVO); + }); + + return result; + } + + @Override + public List listByTeam(String team, Sort sort) { + List menus = menuRepository.findByTeam(team, sort); + return menus.stream().map(menu -> (MenuDTO) new MenuDTO().convertFrom(menu)).collect(Collectors.toList()); + } + @Override public Menu createBy(MenuParam menuParam) { Assert.notNull(menuParam, "Menu param must not be null"); @@ -71,6 +105,13 @@ public class MenuServiceImpl extends AbstractCrudService implemen return topLevelMenu.getChildren(); } + @Override + public List listByParentId(Integer id) { + Assert.notNull(id, "Menu parent id must not be null"); + + return menuRepository.findByParentId(id); + } + @Override public Menu create(Menu menu) { nameMustNotExist(menu); diff --git a/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java b/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java index cb0966039..e4f5ef163 100644 --- a/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java +++ b/src/main/java/run/halo/app/service/impl/OptionServiceImpl.java @@ -6,7 +6,10 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import run.halo.app.cache.StringCacheStore; @@ -23,6 +26,7 @@ import run.halo.app.service.base.AbstractCrudService; import run.halo.app.utils.DateUtils; import run.halo.app.utils.HaloUtils; import run.halo.app.utils.ServiceUtils; +import run.halo.app.utils.ValidationUtils; import java.util.*; @@ -59,46 +63,55 @@ public class OptionServiceImpl extends AbstractCrudService impl propertyEnumMap = Collections.unmodifiableMap(PropertyEnum.getValuePropertyEnumMap()); } - private void save(String key, String value) { + @Deprecated + @Transactional + private void save(@NonNull String key, @Nullable String value) { Assert.hasText(key, "Option key must not be blank"); - - if (StringUtils.isBlank(value)) { - // If the value is blank, remove the key - optionRepository.deleteByKey(key); - log.debug("Removed option key: [{}]", key); - return; - } - - Option option = optionRepository.findByKey(key) - .map(anOption -> { - log.debug("Updating option key: [{}], value: from [{}] to [{}]", key, anOption.getValue(), value); - // Exist - anOption.setValue(value); - return anOption; - }).orElseGet(() -> { - log.debug("Creating option key: [{}], value: [{}]", key, value); - // Not exist - Option anOption = new Option(); - anOption.setKey(key); - anOption.setValue(value); - return anOption; - }); - - // Save or update the options - Option savedOption = optionRepository.save(option); - - log.debug("Saved option: [{}]", savedOption); + save(Collections.singletonMap(key, value)); } @Override - public void save(Map options) { - if (CollectionUtils.isEmpty(options)) { + @Transactional + public void save(Map optionMap) { + if (CollectionUtils.isEmpty(optionMap)) { return; } - options.forEach(this::save); + Map optionKeyMap = ServiceUtils.convertToMap(listAll(), Option::getKey); + + List