release 1.1.0 (#296)

release 1.1.0

Co-authored-by: Darkcloth <darkcloth@126.com>
Co-authored-by: null <jinqilin721@163.com>
Co-authored-by: John Niang <johnniang@foxmail.com>
Co-authored-by: WangYa <757914144@qq.com>
Co-authored-by: ikaisec <ikaisec@gmail.com>
pull/344/head^2 v1.1.0
Ryan Wang 2019-09-11 21:47:00 +08:00 committed by GitHub
commit b788dbe67e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
245 changed files with 2906 additions and 658 deletions

View File

@ -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"]
ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -server -jar halo.jar

View File

@ -1,12 +1,14 @@
<h1 align="center"><a href="https://github.com/halo-dev" target="_blank">Halo</a></h1>
> Halo 是一款现代化的个人独立博客系统,给习惯写博客的同学一个更好的选择。
> Halo 是一款现代化的个人独立博客系统,给习惯写博客的同学多一个选择。
<p align="center">
<a href="#"><img alt="JDK" src="https://img.shields.io/badge/JDK-1.8-yellow.svg?style=flat-square"/></a>
<a href="https://github.com/halo-dev/halo/releases"><img alt="GitHub release" src="https://img.shields.io/github/release/halo-dev/halo.svg?style=flat-square"/></a>
<a href="https://github.com/halo-dev/halo/releases"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/halo-dev/halo/total.svg?style=flat-square"></a>
<a href="https://github.com/halo-dev/halo/commits"><img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/w/halo-dev/halo.svg?style=flat-square"></a>
<a href="https://hub.docker.com/r/ruibaby/halo"><img alt="Docker pulls" src="https://img.shields.io/docker/pulls/ruibaby/halo?style=flat-square"></a>
<a href="https://github.com/halo-dev/halo/commits"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/halo-dev/halo.svg?style=flat-square"></a>
<a href="https://travis-ci.org/halo-dev/halo"><img alt="Travis CI" src="https://img.shields.io/travis/halo-dev/halo.svg?style=flat-square"/></a>
</p>
@ -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
```
详细文档请移步:<https://halo.run/guide>
@ -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)

View File

@ -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'

Binary file not shown.

View File

@ -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

2
gradlew vendored
View File

@ -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"

2
gradlew.bat vendored
View File

@ -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

View File

@ -27,7 +27,7 @@ public class InMemoryCacheStore extends StringCacheStore {
/**
* Cache container.
*/
private final static ConcurrentHashMap<String, CacheWrapper<String>> cacheContainer = new ConcurrentHashMap<>();
private final static ConcurrentHashMap<String, CacheWrapper<String>> CACHE_CONTAINER = new ConcurrentHashMap<>();
private final Timer timer;
@ -46,7 +46,7 @@ public class InMemoryCacheStore extends StringCacheStore {
Optional<CacheWrapper<String>> 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<String> putCacheWrapper = cacheContainer.put(key, cacheWrapper);
CacheWrapper<String> 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);
}

View File

@ -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> logFilter() {
FilterRegistrationBean<LogFilter> 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);

View File

@ -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;

View File

@ -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() {

View File

@ -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")

View File

@ -29,14 +29,9 @@ public class BackupController {
this.backupService = backupService;
}
@PostMapping("import/markdowns")
@ApiOperation("Import markdowns")
public List<BasePostDetailDTO> backupMarkdowns(@RequestPart("files") MultipartFile[] files) throws IOException {
List<BasePostDetailDTO> 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);
}
}

View File

@ -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) {

View File

@ -51,7 +51,7 @@ public class JournalController {
@GetMapping
@ApiOperation("Lists journals")
public Page<JournalWithCmtCountDTO> pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
public Page<JournalWithCmtCountDTO> pageBy(@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
JournalQuery journalQuery) {
Page<Journal> journalPage = journalService.pageBy(journalQuery, pageable);
return journalService.convertToCmtCountDto(journalPage);

View File

@ -40,7 +40,7 @@ public class MenuController {
@GetMapping("tree_view")
@ApiOperation("List as category tree")
public List<MenuVO> listAsTree(@SortDefault(sort = "name", direction = ASC) Sort sort) {
public List<MenuVO> 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<Menu> 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));
}
}

View File

@ -49,7 +49,7 @@ public class OptionController {
@PostMapping("map_view/saving")
@ApiOperation("Saves options by option map")
public void saveOptionsWithMapView(@RequestBody Map<String, String> optionMap) {
public void saveOptionsWithMapView(@RequestBody Map<String, Object> optionMap) {
optionService.save(optionMap);
}

View File

@ -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<String> listTeams() {
return photoService.listAllTeams();
}
}

View File

@ -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));
}
}

View File

@ -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<PostListVO> pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
public Page<PostListVO> 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<Post> 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<? extends BasePostSimpleDTO> 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<Post> 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);
}
}

View File

@ -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<SheetCommentWithSheetVO> pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
CommentQuery commentQuery) {
Page<SheetComment> sheetCommentPage = sheetCommentService.pageBy(commentQuery, pageable);
return sheetCommentService.convertToWithPostVo(sheetCommentPage);
return sheetCommentService.convertToWithSheetVo(sheetCommentPage);
}
@GetMapping("latest")
public List<SheetCommentWithSheetVO> listLatest(@RequestParam(name = "top", defaultValue = "10") int top,
@RequestParam(name = "status", required = false) CommentStatus status) {
Page<SheetComment> 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));
}
}

View File

@ -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<SheetListVO> pageBy(@PageableDefault(sort = "editTime", direction = DESC) Pageable pageable) {
public Page<SheetListVO> pageBy(@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
Page<Sheet> 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);
}
}

View File

@ -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<ThemeFile> listFiles() {
return themeService.listThemeFolderBy(themeService.getActivatedThemeId());
}
@GetMapping("{themeId}/files")
public List<ThemeFile> listFiles(@PathVariable("themeId") String themeId) {
return themeService.listThemeFolderBy(themeId);
}
@GetMapping("files/content")
public BaseResponse<String> getContentBy(@RequestParam(name = "path") String path) {
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), themeService.getTemplateContent(path));
}
@GetMapping("{themeId}/files/content")
public BaseResponse<String> 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() {

View File

@ -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;
}
/**
@ -100,34 +107,102 @@ public class ContentArchiveController {
* Render post page.
*
* @param url post slug url.
* @param cp comment page number
* @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<Category> categories = postCategoryService.listCategoryBy(post.getId());
List<Category> categories = postCategoryService.listCategoriesBy(post.getId());
List<Tag> tags = postTagService.listTagsBy(post.getId());
Page<BaseCommentVO> 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;
}
}
}

View File

@ -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<PostListVO> buildPosts(@NonNull Pageable pageable) {
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostListVO> 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();
}
}

View File

@ -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<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostListVO> posts = postService.convertToListVo(postPage);

View File

@ -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;
@ -78,7 +79,7 @@ public class ContentJournalController {
Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, pageSize, sort);
Page<Journal> journals = journalService.listAll(pageable);
Page<Journal> journals = journalService.pageBy(JournalType.PUBLIC, pageable);
int[] rainbow = PageUtil.rainbow(page, journals.getTotalPages(), 3);

View File

@ -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;
}
/**
@ -54,18 +81,45 @@ public class ContentSheetController {
* Render custom sheet
*
* @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<BaseCommentVO> 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());

View File

@ -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());
}

View File

@ -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")

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -65,7 +65,7 @@ public class CommentEventListener {
return;
}
User user = userService.getCurrentUser().orElseThrow(() -> new ServiceException("找不到博主信息"));
User user = userService.getCurrentUser().orElseThrow(() -> new ServiceException("未查询到博主信息"));
Map<String, Object> data = new HashMap<>();

View File

@ -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");
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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())) {
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());
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());
boolean result = false;
// Create the thumbnail
try {
Files.createFile(thumbPath);
// Convert to thumbnail and copy the thumbnail
Thumbnails.of(imagePath.toFile()).size(THUMB_WIDTH, THUMB_HEIGHT).keepAspectRatio(true).toFile(thumbPath.toFile());
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;
}
}

View File

@ -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);
}
}

View File

@ -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<String, Object> body = new LinkedMultiValueMap<>();
@ -74,7 +95,7 @@ public class SmmsFileHandler implements FileHandler {
HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(body, headers);
// Upload file
ResponseEntity<SmmsResponse> mapResponseEntity = httpsRestTemplate.postForEntity(UPLOAD_API, httpEntity, SmmsResponse.class);
ResponseEntity<SmmsResponse> 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();

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -66,10 +66,10 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
// Whether the blog has initialized
Boolean isInstalled = optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false);
if (haloProperties.isProductionEnv() && isInstalled) {
/*if (haloProperties.isProductionEnv() && isInstalled) {
// Skip
return;
}
}*/
try {
String themeClassPath = ResourceUtils.CLASSPATH_URL_PREFIX + ThemeService.THEME_FOLDER;
@ -80,7 +80,7 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
Path source;
if (themeUri.getScheme().equalsIgnoreCase("jar")) {
if ("jar".equalsIgnoreCase(themeUri.getScheme())) {
// Create new file system for jar
FileSystem fileSystem = FileSystems.newFileSystem(themeUri, Collections.emptyMap());
source = fileSystem.getPath("/BOOT-INF/classes/" + ThemeService.THEME_FOLDER);
@ -91,7 +91,8 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
// Create theme folder
Path themePath = themeService.getBasePath();
if (!haloProperties.isProductionEnv() || Files.notExists(themePath)) {
// Fix the problem that the project cannot start after moving to a new server
if (!haloProperties.isProductionEnv() || Files.notExists(themePath) || !isInstalled) {
FileUtils.copyFolder(source, themePath);
log.info("Copied theme folder from [{}] to [{}]", source, themePath);
} else {

View File

@ -3,6 +3,7 @@ package run.halo.app.model.dto;
import lombok.Data;
import run.halo.app.model.dto.base.OutputConverter;
import run.halo.app.model.entity.Journal;
import run.halo.app.model.enums.JournalType;
import java.util.Date;
@ -22,4 +23,6 @@ public class JournalDTO implements OutputConverter<JournalDTO, Journal> {
private Long likes;
private Date createTime;
private JournalType type;
}

View File

@ -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<MenuDTO, Menu> {
private String icon;
private Integer parentId;
private String team;
}

View File

@ -28,8 +28,6 @@ public class BasePostMinimalDTO implements OutputConverter<BasePostMinimalDTO, B
private String url;
private PostType type;
private Date updateTime;
private Date createTime;

View File

@ -4,7 +4,6 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import run.halo.app.model.enums.PostCreateFrom;
import run.halo.app.model.enums.PostType;
/**
* Base page simple output dto.
@ -16,8 +15,6 @@ import run.halo.app.model.enums.PostType;
@EqualsAndHashCode(callSuper = true)
public class BasePostSimpleDTO extends BasePostMinimalDTO {
private PostType type;
private String summary;
private String thumbnail;
@ -26,6 +23,8 @@ public class BasePostSimpleDTO extends BasePostMinimalDTO {
private Boolean disallowComment;
private String password;
private String template;
private Integer topPriority = 0;

View File

@ -14,9 +14,9 @@ import java.util.Date;
* @author johnniang
* @date 3/20/19
*/
@MappedSuperclass
@Data
@ToString
@MappedSuperclass
@EqualsAndHashCode
public class BaseEntity {

View File

@ -0,0 +1,46 @@
package run.halo.app.model.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.persistence.*;
/**
* Base meta entity.
*
* @author ryanwang
* @author ikaisec
* @date 2019-08-04
*/
@Data
@Entity(name = "BaseMeta")
@Table(name = "metas")
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.INTEGER, columnDefinition = "int default 0")
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class BaseMeta extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* Post id.
*/
@Column(name = "post_id", columnDefinition = "int not null")
private Integer postId;
/**
* meta key
*/
@Column(name = "meta_key", columnDefinition = "varchar(100) not null")
private String key;
/**
* meta value
*/
@Column(name = "meta_value", columnDefinition = "varchar(1023) not null")
private String value;
}

View File

@ -10,9 +10,10 @@ import javax.persistence.*;
import java.util.Date;
/**
* Post entity.
* Post base entity.
*
* @author johnniang
* @author ryanwang
*/
@Data
@Entity(name = "BasePost")
@ -41,7 +42,7 @@ public class BasePost extends BaseEntity {
/**
* Post url.
*/
@Column(name = "url", columnDefinition = "varchar(255) not null")
@Column(name = "url", columnDefinition = "varchar(255) not null", unique = true)
private String url;
/**
@ -168,6 +169,10 @@ public class BasePost extends BaseEntity {
if (likes == null || likes < 0) {
likes = 0L;
}
if (formatContent == null) {
formatContent = "";
}
}
}

View File

@ -31,7 +31,7 @@ public class Category extends BaseEntity {
/**
* Category slug name.
*/
@Column(name = "slug_name", columnDefinition = "varchar(50) not null")
@Column(name = "slug_name", columnDefinition = "varchar(50) not null", unique = true)
private String slugName;
/**

View File

@ -3,6 +3,7 @@ package run.halo.app.model.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import run.halo.app.model.enums.JournalType;
import javax.persistence.*;
@ -10,6 +11,7 @@ import javax.persistence.*;
* Journal entity
*
* @author johnniang
* @author ryanwang
* @date 3/22/19
*/
@Data
@ -29,6 +31,9 @@ public class Journal extends BaseEntity {
@Column(name = "likes", columnDefinition = "bigint default 0")
private Long likes;
@Column(name = "type", columnDefinition = "int default 1")
private JournalType type;
@Override
public void prePersist() {
super.prePersist();
@ -38,5 +43,10 @@ public class Journal extends BaseEntity {
if (likes == null || likes < 0) {
likes = 0L;
}
if (type == null) {
type = JournalType.PUBLIC;
}
}
}

View File

@ -60,6 +60,12 @@ public class Menu extends BaseEntity {
@Column(name = "parent_id", columnDefinition = "int default 0")
private Integer parentId;
/**
* Menu team name.
*/
@Column(name = "team", columnDefinition = "varchar(255) default ''")
private String team;
@Override
public void prePersist() {
super.prePersist();
@ -81,5 +87,9 @@ public class Menu extends BaseEntity {
if (parentId == null) {
parentId = 0;
}
if(team == null){
team = "";
}
}
}

View File

@ -1,7 +1,6 @@
package run.halo.app.model.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.persistence.*;
@ -43,8 +42,12 @@ public class PostCategory extends BaseEntity {
@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;
}
PostCategory that = (PostCategory) o;
return categoryId.equals(that.categoryId) &&
postId.equals(that.postId);

View File

@ -0,0 +1,16 @@
package run.halo.app.model.entity;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
/**
* PostMeta entity.
*
* @author ryanwang
* @author ikaisec
* @date 2019-08-04
*/
@Entity(name = "PostMeta")
@DiscriminatorValue("0")
public class PostMeta extends BaseMeta {
}

View File

@ -44,8 +44,12 @@ public class PostTag extends BaseEntity {
@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;
}
PostTag postTag = (PostTag) o;
return Objects.equals(postId, postTag.postId) &&
Objects.equals(tagId, postTag.tagId);

View File

@ -0,0 +1,16 @@
package run.halo.app.model.entity;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
/**
* SheetMeta entity.
*
* @author ryanwang
* @author ikaisec
* @date 2019-08-04
*/
@Entity(name = "SheetMeta")
@DiscriminatorValue("1")
public class SheetMeta extends BaseMeta {
}

View File

@ -33,7 +33,7 @@ public class Tag extends BaseEntity {
/**
* Tag slug name.
*/
@Column(name = "slug_name", columnDefinition = "varchar(255) not null")
@Column(name = "slug_name", columnDefinition = "varchar(255) not null", unique = true)
private String slugName;
@Override

View File

@ -31,7 +31,17 @@ public enum AttachmentType implements ValueEnum<Integer> {
/**
*
*/
ALIYUN(4);
ALIYUN(4),
/**
*
*/
BAIDUYUN(5),
/**
*
*/
TENCENTYUN(6);
private Integer value;

View File

@ -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.

View File

@ -0,0 +1,30 @@
package run.halo.app.model.enums;
/**
* Journal type.
*
* @author ryanwnag
*/
public enum JournalType implements ValueEnum<Integer> {
/**
* Public type.
*/
PUBLIC(1),
/**
* Intimate type.
*/
INTIMATE(0);
private final int value;
JournalType(int value) {
this.value = value;
}
@Override
public Integer getValue() {
return value;
}
}

View File

@ -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;
}

View File

@ -20,7 +20,12 @@ public enum PostStatus implements ValueEnum<Integer> {
/**
* Recycle status.
*/
RECYCLE(2);
RECYCLE(2),
/**
* Intimate status
*/
INTIMATE(3);
private final int value;

View File

@ -5,6 +5,7 @@ package run.halo.app.model.enums;
*
* @author johnniang
*/
@Deprecated
public enum PostType implements ValueEnum<Integer> {
/**

View File

@ -11,6 +11,7 @@ import javax.persistence.Converter;
* @date 3/27/19
*/
@Converter(autoApply = true)
@Deprecated
public class PostTypeConverter extends AbstractConverter<PostType, Integer> {
public PostTypeConverter() {

View File

@ -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()));

View File

@ -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<PostComment> 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()));

View File

@ -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;

View File

@ -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<META> implements InputConverter<META> {
@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());
}
}

View File

@ -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<Journal> {
@NotBlank(message = "内容不能为空")
@Size(max = 511, message = "内容的字符长度不能超过 {max}")
private String content;
private JournalType type = JournalType.PUBLIC;
}

View File

@ -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;
}

View File

@ -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<Menu> {
private String icon;
private Integer parentId;
@Size(max = 255, message = "菜单分组的字符长度不能超过 {max}")
private String team;
}

View File

@ -8,7 +8,7 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
/**
* Optiona param.
* Optional param.
*
* @author johnniang
* @date 3/20/19

View File

@ -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<PostMeta> {
}

View File

@ -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<Post> {
@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()));
}
}
}

View File

@ -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;
}

View File

@ -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<SheetMeta> {
}

View File

@ -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<Sheet> {
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<Sheet> {
@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()));
}
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -9,6 +9,8 @@ package run.halo.app.model.properties;
*/
public enum OtherProperties implements PropertyEnum {
CDN_DOMAIN("blog_cdn_domain", String.class, ""),
CUSTOM_HEAD("blog_custom_head", String.class, ""),
STATISTICS_CODE("blog_statistics_code", String.class, "");

View File

@ -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;

View File

@ -29,7 +29,7 @@ public interface PropertyEnum extends ValueEnum<String> {
*/
@SuppressWarnings("unchecked")
static <T> T convertTo(@NonNull String value, @NonNull Class<T> 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<String> {
}
if (type.isAssignableFrom(Double.class)) {
return (T) Byte.valueOf(value);
return (T) Double.valueOf(value);
}
if (type.isAssignableFrom(Float.class)) {

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -23,6 +23,7 @@ public class BaseCommentWithParentVO extends BaseCommentDTO implements Cloneable
*/
private BaseCommentWithParentVO parent;
@Override
public BaseCommentWithParentVO clone() {
try {
return (BaseCommentWithParentVO) super.clone();

View File

@ -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<MenuDTO> menus;
}

View File

@ -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<Integer> tagIds;
private List<TagDTO> tags;
private Set<Integer> categoryIds;
private List<CategoryDTO> categories;
}

View File

@ -12,6 +12,7 @@ import java.util.List;
* Attachment repository
*
* @author johnniang
* @date 2019-04-03
*/
public interface AttachmentRepository extends BaseRepository<Attachment, Integer>, JpaSpecificationExecutor<Attachment> {

View File

@ -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<Category, Integer> {
* @return Optional of Category
*/
Optional<Category> getByName(@NonNull String name);
/**
* List categories by parent id.
* @param id parent id.
* @return list of category
*/
List<Category> findByParentId(@NonNull Integer id);
}

View File

@ -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<Journal, Integer>, JpaSpecificationExecutor<Journal> {
/**
* 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<Journal> findAllByType(@NonNull JournalType type, @NonNull Pageable pageable);
}

Some files were not shown because too many files have changed in this diff Show More