release: 1.2.0. (#467)

release: 1.2.0.

Co-authored-by: Jiahuan Shen <shenjiahuan@sjtu.edu.cn>
Co-authored-by: John Niang <johnniang@foxmail.com>
Co-authored-by: Ryan Wang <i@ryanc.cc>
Co-authored-by: null <weiwensangsang@users.noreply.github.com>
Co-authored-by: bobo <yabo_han@163.com>
Co-authored-by: Lei XinXin <nglsl666@aliyun.com>
Co-authored-by: 寒山 <ms915818993@163.com>
Co-authored-by: IJKZEN <ijkzen@outlook.com>
pull/645/head v1.2.0
Ryan Wang 2020-01-05 22:55:31 +08:00 committed by GitHub
commit 2203889fe7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
372 changed files with 7888 additions and 2056 deletions

View File

@ -1,19 +0,0 @@
<!--
如果你不认真勾选下面的内容,我可能会直接关闭你的 Issue。
提问之前,建议先阅读 https://github.com/ruby-china/How-To-Ask-Questions-The-Smart-Way
-->
**我确定我已经查看了** (标注`[ ]`为`[x]`)
- [ ] [Halo 使用文档](https://halo.run/docs)
- [ ] [Halo 论坛](https://bbs.halo.run)
- [ ] [Github Wiki 常见问题](https://github.com/halo-dev/halo/wiki/4.-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
- [ ] [其他 Issues](https://github.com/halo-dev/halo/issues)
----
**我要申请** (标注`[ ]`为`[x]`)
- [ ] BUG 反馈
- [ ] 添加新的特性或者功能
- [ ] 请求技术支持

54
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,54 @@
---
name: Bug Report
about: 事情不像预期的那样工作吗?
title: ''
labels: 'bug'
assignees: ''
---
<!--
你好!感谢你正在考虑为 Halo 提交一个 bug。请花一点点时间尽量详细地回答以下基础问题。
谢谢!
-->
<!--
请确认你已经做了下面这些事情,若 bug 还是未解决,那么请尽可详细地描述你的问题。
- 我已经安装了最新版的 Halo
- 我已经搜索了已有的 Issues 列表中有关的信息
- 我已经搜索了论坛中有关的信息https://bbs.halo.run
- 我已经阅读了 Halo 的 FAQhttps://halo.run/guide/faq.html
-->
## 我的环境
<!--
请登录到博客后台,进入菜单 `系统->关于` 点击 `环境信息` 旁边的按钮即可复制环境信息。
-->
---
## 错误日志
<!--
请登录到博客后台,点击菜单栏中的 `Halo Dashboard` 10 次即可开启 `开发者选项`。进入开发者选项中的 `实时日志`
你可以选择查找具体的错误日志,或者直接下载日志文件复制到下面即可。(注意:日志堆栈信息请用 Markdown 的代码块语法,以方便查看。)
-->
---
## 期望行为
<!--
你期望会发生什么?
-->
## 当前行为
<!--
描述 bug 细节,确认出现此问题的复现步骤,例如点击了哪里,发生了什么情况?
你可以粘贴截图或附件。
-->

View File

@ -0,0 +1,34 @@
---
name: Feature Request
about: 想让我们为 Halo 增加什么功能吗?
title: 'feat: '
labels: 'Feature Request'
assignees: ''
---
<!--
你好!感谢你愿意考虑希望 Halo 增加某个新功能。请花一点点时间尽量详细地回答以下基础问题。
谢谢!
-->
## 概述
<!--
对这个新功能的一段描述
-->
## 动机
<!--
为什么你希望在 Halo 中使用这个功能?
-->
## 详细解释
<!--
详细描述这个新功能。
如果这是一个小功能,你可以忽略这部分。
-->

14
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@ -0,0 +1,14 @@
---
name: Question
about: 对 Halo 有任何问题吗?
title: ''
labels: 'question'
assignees: ''
---
<!--
如果你有任何问题也可以通过此渠道来向我们反馈。
谢谢!
-->

17
.github/workflows/gradle.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Java CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: ./gradlew build

View File

@ -1,5 +1,4 @@
FROM openjdk:8-jre-alpine
FROM adoptopenjdk/openjdk8-openj9
VOLUME /tmp
ARG JAR_FILE=build/libs/halo.jar
@ -13,4 +12,4 @@ COPY ${JAR_FILE} halo.jar
EXPOSE ${PORT}
ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -server -jar halo.jar
ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -server -jar halo.jar

View File

@ -2,8 +2,6 @@
> 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>
@ -21,20 +19,24 @@
轻快,简洁,功能强大,使用 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://halo.run) | [社区](https://bbs.halo.run) | [QQ 交流群](https://jq.qq.com/?_wv=1027&k=5tnr930) | [Telegram 交流群](https://t.me/HaloBlog) | [Telegram 频道](https://t.me/halo_dev)
## 声明
> 本项目旨在创造一个好的产品以供人们使用虽然现在并不是太好并非一个所谓提供学习的项目。所以不提供任何学习代码的帮助。所以请不要在讨论群issues论坛发起任何有关代码学习的问题。当然如果你是要参与代码贡献我们非常欢迎。
## 快速开始
### 下载最新的 Halo 安装包
```bash
curl -L https://github.com/halo-dev/halo/releases/download/v1.1.1/halo-1.1.1.jar --output halo-latest.jar
curl -L https://github.com/halo-dev/halo/releases/download/v1.2.0/halo-1.2.0.jar --output halo-latest.jar
```
或者
```bash
wget https://github.com/halo-dev/halo/releases/download/v1.1.1/halo-1.1.1.jar -O halo-latest.jar
wget https://github.com/halo-dev/halo/releases/download/v1.2.0/halo-1.2.0.jar -O halo-latest.jar
```
### 启动 Halo
@ -55,12 +57,13 @@ java -jar halo-latest.jar
- 独立评论模块halo-comment<https://github.com/halo-dev/halo-comment>
- 管理 APPhalo-app<https://github.com/halo-dev/halo-app>
- 主题仓库:<https://halo.run/theme>
- WeHalo 小程序:<https://github.com/aquanlerou/WeHalo>
## 许可证
[![license](https://img.shields.io/github/license/halo-dev/halo.svg?style=flat-square)](https://github.com/halo-dev/halo/blob/master/LICENSE)
> Halo 使用 GPL-v3.0 协议开源,请尽量遵守开源协议,即便是在中国
> Halo 使用 GPL-v3.0 协议开源,请尽量遵守开源协议。
## 贡献
参考 [CONTRIBUTING](./CONTRIBUTING.md)。
@ -81,8 +84,6 @@ java -jar halo-latest.jar
![theme-icarus.png](https://i.loli.net/2019/09/11/4lO2wNCLiqyIJmR.png)
![theme-destiny.png](https://i.loli.net/2019/09/11/q4t86cCPUEwlGMn.png)
![install.png](https://i.loli.net/2019/09/11/Iu1eMzZDg6frw97.png)
![admin-login.png](https://i.loli.net/2019/09/11/3CahVJAvXngwiQu.png)

View File

@ -1,5 +1,5 @@
plugins {
id 'org.springframework.boot' version '2.1.7.RELEASE'
id 'org.springframework.boot' version '2.2.1.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.1.1'
version = '1.2.0'
sourceCompatibility = '1.8'
description = 'Halo, personal blog system developed in Java.'
@ -43,7 +43,7 @@ bootJar {
ext {
ohMyEmailVersion = '0.0.4'
hutoolVersion = '4.6.3'
hutoolVersion = '5.0.3'
upyunSdkVersion = '4.0.1'
qiniuSdkVersion = '7.2.18'
aliyunSdkVersion = '3.4.2'
@ -56,6 +56,9 @@ ext {
jgitVersion = '5.3.0.201903130848-r'
flexmarkVersion = '0.42.12'
thumbnailatorVersion = '0.4.8'
image4jVersion = '0.7zensight1'
flywayVersion = '6.1.0'
h2Version = '1.4.196'
}
dependencies {
@ -93,17 +96,21 @@ dependencies {
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-ext-gitlab:$flexmarkVersion"
implementation "com.vladsch.flexmark:flexmark-html-parser:$flexmarkVersion"
implementation "net.coobird:thumbnailator:$thumbnailatorVersion"
implementation "net.sf.image4j:image4j:$image4jVersion"
implementation "org.flywaydb:flyway-core:$flywayVersion"
runtimeOnly 'com.h2database:h2'
runtimeOnly "com.h2database:h2:$h2Version"
runtimeOnly 'mysql:mysql-connector-java'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
}
}

View File

@ -1,9 +1,11 @@
package run.halo.app;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableAsync;
@ -11,12 +13,10 @@ import org.springframework.scheduling.annotation.EnableScheduling;
import run.halo.app.repository.base.BaseRepositoryImpl;
/**
* <pre>
* Halo run!
* </pre>
* Halo main class.
*
* @author ryanwang
* @date : 2017/11/14
* @date 2017-11-14
*/
@SpringBootApplication
@EnableJpaAuditing
@ -25,12 +25,29 @@ import run.halo.app.repository.base.BaseRepositoryImpl;
@EnableJpaRepositories(basePackages = "run.halo.app.repository", repositoryBaseClass = BaseRepositoryImpl.class)
public class Application extends SpringBootServletInitializer {
private static ConfigurableApplicationContext context;
public static void main(String[] args) {
// Customize the spring config location
System.setProperty("spring.config.additional-location", "file:${user.home}/.halo/,file:${user.home}/halo-dev/");
// Run application
SpringApplication.run(Application.class, args);
context = SpringApplication.run(Application.class, args);
}
/**
* Restart Application.
*/
public static void restart() {
ApplicationArguments args = context.getBean(ApplicationArguments.class);
Thread thread = new Thread(() -> {
context.close();
context = SpringApplication.run(Application.class, args.getSourceArgs());
});
thread.setDaemon(false);
thread.start();
}
@Override

View File

@ -67,8 +67,8 @@ public class InMemoryCacheStore extends StringCacheStore {
log.debug("Preparing to put key: [{}], value: [{}]", key, cacheWrapper);
lock.lock();
try {
lock.lock();
// Get the value before
Optional<String> valueOptional = get(key);
@ -98,6 +98,11 @@ public class InMemoryCacheStore extends StringCacheStore {
public void preDestroy() {
log.debug("Cancelling all timer tasks");
timer.cancel();
clear();
}
private void clear() {
CACHE_CONTAINER.clear();
}
/**

View File

@ -2,6 +2,7 @@ package run.halo.app.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
@ -24,6 +25,7 @@ import run.halo.app.security.handler.ContentAuthenticationFailureHandler;
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
import run.halo.app.service.OptionService;
import run.halo.app.service.UserService;
import run.halo.app.utils.HaloUtils;
import run.halo.app.utils.HttpClientUtils;
import java.security.KeyManagementException;
@ -40,7 +42,8 @@ import java.security.NoSuchAlgorithmException;
@Slf4j
public class HaloConfiguration {
private final static int TIMEOUT = 5000;
@Autowired
HaloProperties haloProperties;
@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
@ -49,9 +52,11 @@ public class HaloConfiguration {
}
@Bean
public RestTemplate httpsRestTemplate(RestTemplateBuilder builder) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
public RestTemplate httpsRestTemplate(RestTemplateBuilder builder)
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
RestTemplate httpsRestTemplate = builder.build();
httpsRestTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(HttpClientUtils.createHttpsClient(TIMEOUT)));
httpsRestTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(HttpClientUtils.createHttpsClient(
(int) haloProperties.getDownloadTimeout().toMillis())));
return httpsRestTemplate;
}
@ -94,10 +99,20 @@ public class HaloConfiguration {
@Bean
public FilterRegistrationBean<ContentFilter> contentFilter(HaloProperties haloProperties,
OptionService optionService) {
ContentFilter contentFilter = new ContentFilter(haloProperties, optionService);
OptionService optionService,
StringCacheStore cacheStore) {
ContentFilter contentFilter = new ContentFilter(haloProperties, optionService, cacheStore);
contentFilter.setFailureHandler(new ContentAuthenticationFailureHandler());
contentFilter.addExcludeUrlPatterns("/api/**", "/install", "/version", "/admin/**", "/js/**", "/css/**");
String adminPattern = HaloUtils.ensureBoth(haloProperties.getAdminPath(), "/") + "**";
contentFilter.addExcludeUrlPatterns(
adminPattern,
"/api/**",
"/install",
"/version",
"/js/**",
"/css/**");
FilterRegistrationBean<ContentFilter> contentFrb = new FilterRegistrationBean<>();
contentFrb.addUrlPatterns("/*");
@ -110,8 +125,9 @@ public class HaloConfiguration {
@Bean
public FilterRegistrationBean<ApiAuthenticationFilter> apiAuthenticationFilter(HaloProperties haloProperties,
ObjectMapper objectMapper,
OptionService optionService) {
ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties, optionService);
OptionService optionService,
StringCacheStore cacheStore) {
ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties, optionService, cacheStore);
apiFilter.addExcludeUrlPatterns(
"/api/content/*/comments",
"/api/content/**/comments/**",
@ -151,6 +167,7 @@ public class HaloConfiguration {
"/api/admin/refresh/*",
"/api/admin/installations",
"/api/admin/recoveries/migrations/*",
"/api/admin/migrations/*",
"/api/admin/is_installed",
"/api/admin/password/code",
"/api/admin/password/reset"

View File

@ -13,8 +13,6 @@ import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMethod;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.model.entity.User;
import run.halo.app.security.filter.AdminAuthenticationFilter;
import run.halo.app.security.filter.ApiAuthenticationFilter;
import run.halo.app.security.support.UserDetail;
import springfox.documentation.builders.*;
import springfox.documentation.schema.AlternateTypeRule;
@ -33,7 +31,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static run.halo.app.model.support.HaloConst.HALO_VERSION;
import static run.halo.app.model.support.HaloConst.*;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
/**
@ -80,7 +78,7 @@ public class SwaggerConfiguration {
log.debug("Doc has been disabled");
}
return buildApiDocket("run.halo.app.admin",
return buildApiDocket("run.halo.app.admin.api",
"run.halo.app.controller.admin",
"/api/admin/**")
.securitySchemes(adminApiKeys())
@ -123,8 +121,8 @@ public class SwaggerConfiguration {
private List<ApiKey> adminApiKeys() {
return Arrays.asList(
new ApiKey("Token from header", AdminAuthenticationFilter.ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()),
new ApiKey("Token from query", AdminAuthenticationFilter.ADMIN_TOKEN_QUERY_NAME, In.QUERY.name())
new ApiKey("Token from header", ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()),
new ApiKey("Token from query", ADMIN_TOKEN_QUERY_NAME, In.QUERY.name())
);
}
@ -139,8 +137,8 @@ public class SwaggerConfiguration {
private List<ApiKey> contentApiKeys() {
return Arrays.asList(
new ApiKey("Access key from header", ApiAuthenticationFilter.API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()),
new ApiKey("Access key from query", ApiAuthenticationFilter.API_ACCESS_KEY_QUERY_NAME, In.QUERY.name())
new ApiKey("Access key from header", API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()),
new ApiKey("Access key from query", API_ACCESS_KEY_QUERY_NAME, In.QUERY.name())
);
}
@ -171,7 +169,9 @@ public class SwaggerConfiguration {
.description("Documentation for Halo API")
.version(HALO_VERSION)
.termsOfServiceUrl("https://github.com/halo-dev")
.contact(new Contact("RYAN0UP", "https://ryanc.cc/", "i#ryanc.cc"))
.contact(new Contact("halo-dev", "https://github.com/halo-dev/halo/issues", "i#ryanc.cc"))
.license("GNU General Public License v3.0")
.licenseUrl("https://github.com/halo-dev/halo/blob/master/LICENSE")
.build();
}

View File

@ -1,6 +1,7 @@
package run.halo.app.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import freemarker.core.TemplateClassResolver;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import lombok.extern.slf4j.Slf4j;
@ -31,13 +32,15 @@ import java.io.IOException;
import java.util.List;
import java.util.Properties;
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
import static run.halo.app.model.support.HaloConst.HALO_ADMIN_RELATIVE_PATH;
import static run.halo.app.utils.HaloUtils.*;
/**
* Mvc configuration.
*
* @author ryanwang
* @date : 2018/1/2
* @date 2018-01-02
*/
@Slf4j
@Configuration
@ -80,17 +83,20 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String workDir = FILE_PROTOCOL + haloProperties.getWorkDir();
String workDir = FILE_PROTOCOL + ensureSuffix(haloProperties.getWorkDir(), FILE_SEPARATOR);
String backupDir = FILE_PROTOCOL + ensureSuffix(haloProperties.getBackupDir(), FILE_SEPARATOR);
registry.addResourceHandler("/**")
.addResourceLocations(workDir + "templates/themes/")
.addResourceLocations(workDir + "templates/admin/")
.addResourceLocations("classpath:/admin/")
.addResourceLocations(workDir + "static/");
registry.addResourceHandler("/upload/**")
String uploadUrlPattern = ensureBoth(haloProperties.getUploadUrlPrefix(), URL_SEPARATOR) + "**";
String adminPathPattern = ensureSuffix(haloProperties.getAdminPath(), URL_SEPARATOR) + "**";
registry.addResourceHandler(uploadUrlPattern)
.addResourceLocations(workDir + "upload/");
registry.addResourceHandler("/backup/**")
.addResourceLocations(workDir + "backup/");
registry.addResourceHandler("/admin/**")
registry.addResourceHandler(adminPathPattern)
.addResourceLocations(workDir + HALO_ADMIN_RELATIVE_PATH)
.addResourceLocations("classpath:/admin/");
@ -126,6 +132,9 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
// Predefine configuration
freemarker.template.Configuration configuration = configurer.createConfiguration();
configuration.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
if (haloProperties.isProductionEnv()) {
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
}

View File

@ -2,11 +2,15 @@ package run.halo.app.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import run.halo.app.model.support.HaloConst;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import static run.halo.app.model.support.HaloConst.*;
import static run.halo.app.utils.HaloUtils.ensureSuffix;
/**
* Halo configuration properties.
@ -32,13 +36,34 @@ public class HaloProperties {
*/
private boolean authEnabled = true;
/**
* Admin path.
*/
private String adminPath = "admin";
/**
* Work directory.
*/
private String workDir = HaloConst.USER_HOME + "/.halo/";
private String workDir = ensureSuffix(USER_HOME, FILE_SEPARATOR) + ".halo" + FILE_SEPARATOR;
/**
* Halo backup directory.(Not recommended to modify this config);
*/
private String backupDir = ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-backup" + FILE_SEPARATOR;
/**
* Upload prefix.
*/
private String uploadUrlPrefix = "upload";
/**
* Download Timeout.
*/
private Duration downloadTimeout = Duration.ofSeconds(30);
public HaloProperties() throws IOException {
// Create work directory if not exist
Files.createDirectories(Paths.get(workDir));
Files.createDirectories(Paths.get(backupDir));
}
}

View File

@ -4,6 +4,7 @@ import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import run.halo.app.Application;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.model.dto.EnvironmentDTO;
import run.halo.app.model.dto.StatisticDTO;
@ -15,6 +16,7 @@ import run.halo.app.security.token.AuthToken;
import run.halo.app.service.AdminService;
import run.halo.app.service.OptionService;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
/**
@ -22,7 +24,7 @@ import javax.validation.Valid;
*
* @author johnniang
* @author ryanwang
* @date 3/19/19
* @date 2019-03-19
*/
@Slf4j
@RestController
@ -39,7 +41,7 @@ public class AdminController {
}
@GetMapping(value = "/is_installed")
@ApiOperation("Check install status")
@ApiOperation("Checks Installation status")
public boolean isInstall() {
return optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false);
}
@ -59,13 +61,13 @@ public class AdminController {
}
@PostMapping("password/code")
@ApiOperation("Send reset password verify code.")
@ApiOperation("Sends reset password verify code")
public void sendResetCode(@RequestBody @Valid ResetPasswordParam param) {
adminService.sendResetPasswordCode(param);
}
@PutMapping("password/reset")
@ApiOperation("Reset password by verify code.")
@ApiOperation("Resets password by verify code")
public void resetPassword(@RequestBody @Valid ResetPasswordParam param) {
adminService.resetPasswordByCode(param);
}
@ -77,13 +79,9 @@ public class AdminController {
return adminService.refreshToken(refreshToken);
}
/**
* Get some statistics about the count of posts, the count of comments, etc.
*
* @return counts
*/
@GetMapping("counts")
@ApiOperation("Gets count info")
@Deprecated
public StatisticDTO getCount() {
return adminService.getCount();
}
@ -100,9 +98,27 @@ public class AdminController {
adminService.updateAdminAssets();
}
@GetMapping("spring/logs")
@ApiOperation("Get application logs")
public BaseResponse<String> getSpringLogs() {
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), adminService.getSpringLogs());
@GetMapping("spring/application.yaml")
@ApiOperation("Gets application config content")
public BaseResponse<String> getSpringApplicationConfig() {
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), adminService.getApplicationConfig());
}
@PutMapping("spring/application.yaml")
@ApiOperation("Updates application config content")
public void updateSpringApplicationConfig(@RequestParam(name = "content") String content) {
adminService.updateApplicationConfig(content);
}
@PostMapping(value = {"halo/restart", "spring/restart"})
@ApiOperation("Restarts halo server")
public void restartApplication() {
Application.restart();
}
@GetMapping(value = "halo/logfile")
@ApiOperation("Gets halo log file content")
public BaseResponse<String> getLogFiles(@RequestParam("lines") Long lines) {
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), adminService.getLogFiles(lines));
}
}

View File

@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.model.dto.AttachmentDTO;
import run.halo.app.model.entity.Attachment;
import run.halo.app.model.enums.AttachmentType;
import run.halo.app.model.params.AttachmentParam;
import run.halo.app.model.params.AttachmentQuery;
import run.halo.app.service.AttachmentService;
@ -35,26 +36,14 @@ public class AttachmentController {
this.attachmentService = attachmentService;
}
/**
* List of attachment.
*
* @param pageable pageable
* @return Page<AttachmentDTO>
*/
@GetMapping
public Page<AttachmentDTO> pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
AttachmentQuery attachmentQuery) {
return attachmentService.pageDtosBy(pageable, attachmentQuery);
}
/**
* Get attachment by id.
*
* @param id attachment id
* @return AttachmentDTO
*/
@GetMapping("{id:\\d+}")
@ApiOperation("Get attachment detail by id")
@ApiOperation("Gets attachment detail by id")
public AttachmentDTO getBy(@PathVariable("id") Integer id) {
Attachment attachment = attachmentService.getById(id);
return attachmentService.convertToDto(attachment);
@ -69,17 +58,18 @@ public class AttachmentController {
return new AttachmentDTO().convertFrom(attachmentService.update(attachment));
}
/**
* Delete attachment by id
*
* @param id id
*/
@DeleteMapping("{id:\\d+}")
@ApiOperation("Delete attachment by id")
@ApiOperation("Deletes attachment permanently by id")
public AttachmentDTO deletePermanently(@PathVariable("id") Integer id) {
return attachmentService.convertToDto(attachmentService.removePermanently(id));
}
@DeleteMapping
@ApiOperation("Deletes attachments permanently in batch by id array")
public List<Attachment> deletePermanentlyInBatch(@RequestBody List<Integer> ids) {
return attachmentService.removePermanently(ids);
}
@PostMapping("upload")
@ApiOperation("Uploads single file")
public AttachmentDTO uploadAttachment(@RequestPart("file") MultipartFile file) {
@ -106,4 +96,10 @@ public class AttachmentController {
public List<String> listMediaTypes() {
return attachmentService.listAllMediaType();
}
@GetMapping("types")
@ApiOperation("Lists all of types.")
public List<AttachmentType> listTypes() {
return attachmentService.listAllType();
}
}

View File

@ -1,26 +1,39 @@
package run.halo.app.controller.admin.api;
import cn.hutool.core.util.ZipUtil;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.json.JSONObject;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.exception.FileOperationException;
import run.halo.app.model.dto.BackupDTO;
import run.halo.app.model.dto.post.BasePostDetailDTO;
import run.halo.app.service.BackupService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Date;
import java.util.List;
/**
* Backup controller
*
* @author johnniang
* @date 19-4-26
* @date 2019-04-26
*/
@RestController
@RequestMapping("/api/admin/backups")
@Slf4j
public class BackupController {
private final BackupService backupService;
@ -29,9 +42,94 @@ public class BackupController {
this.backupService = backupService;
}
@PostMapping("halo")
@ApiOperation("Backups halo")
public BackupDTO backupHalo() {
return backupService.zipWorkDirectory();
}
@GetMapping("halo")
@ApiOperation("Gets all backups")
public List<BackupDTO> listBackups() {
return backupService.listHaloBackups();
}
@GetMapping("halo/{fileName:.+}")
@ApiOperation("Downloads backup file")
public ResponseEntity<Resource> downloadBackup(@PathVariable("fileName") String fileName, HttpServletRequest request) {
log.info("Try to download backup file: [{}]", fileName);
// Load file as resource
Resource backupResource = backupService.loadFileAsResource(fileName);
String contentType = "application/octet-stream";
// Try to determine file's content type
try {
contentType = request.getServletContext().getMimeType(backupResource.getFile().getAbsolutePath());
} catch (IOException e) {
log.warn("Could not determine file type", e);
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + backupResource.getFilename() + "\"")
.body(backupResource);
}
@DeleteMapping("halo")
@ApiOperation("Deletes a backup")
public void deleteBackup(@RequestParam("filename") String filename) {
backupService.deleteHaloBackup(filename);
}
@PostMapping("import/markdown")
@ApiOperation("Import markdown")
public BasePostDetailDTO backupMarkdowns(@RequestPart("file") MultipartFile file) throws IOException {
return backupService.importMarkdown(file);
}
@GetMapping("export/hexo")
@ApiOperation("export hexo markdown")
public void exportMarkdowns(HttpServletResponse response) {
final String tmpDir = System.getProperty("java.io.tmpdir");
final String date = DateFormatUtils.format(new Date(), "yyyyMMddHHmmss");
String localFilePath = tmpDir + File.separator + "halo-markdown-" + date;
log.trace(localFilePath);
final File localFile = new File(localFilePath);
final File postDir = new File(localFilePath + File.separator + "posts");
final File passwordDir = new File(localFilePath + File.separator + "passwords");
final File draftDir = new File(localFilePath + File.separator + "drafts");
try {
if (!postDir.mkdirs()) {
throw new Exception("Create dir [" + postDir.getPath() + "] failed");
}
if (!passwordDir.mkdirs()) {
throw new Exception("Create dir [" + passwordDir.getPath() + "] failed");
}
if (!draftDir.mkdirs()) {
throw new Exception("Create dir [" + draftDir.getPath() + "] failed");
}
final JSONObject result = backupService.exportHexoMDs();
final List<JSONObject> posts = (List<JSONObject>) result.opt("posts");
backupService.exportHexoMd(posts, postDir.getPath());
final List<JSONObject> passwords = (List<JSONObject>) result.opt("passwords");
backupService.exportHexoMd(passwords, passwordDir.getPath());
final List<JSONObject> drafts = (List<JSONObject>) result.opt("drafts");
backupService.exportHexoMd(drafts, draftDir.getPath());
final File zipFile = ZipUtil.zip(localFile);
byte[] zipData;
try (final FileInputStream inputStream = new FileInputStream(zipFile)) {
zipData = IOUtils.toByteArray(inputStream);
response.setContentType("application/zip");
final String fileName = "halo-markdown-" + date + ".zip";
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
}
response.getOutputStream().write(zipData);
} catch (final Exception e) {
log.error("Export failed", e);
throw new FileOperationException("文章导出失败", e);
}
}
}

View File

@ -21,7 +21,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Category controller.
*
* @author johnniang
* @date 3/21/19
* @date 2019-03-21
*/
@RestController
@RequestMapping("/api/admin/categories")

View File

@ -2,6 +2,7 @@ package run.halo.app.controller.admin.api;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.crypto.SecureUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
@ -38,7 +39,7 @@ import java.util.Set;
* Installation controller.
*
* @author ryanwang
* @date : 2019-03-17
* @date 2019-03-17
*/
@Slf4j
@Controller
@ -78,6 +79,7 @@ public class InstallController {
@PostMapping
@ResponseBody
@CacheLock
@ApiOperation("Initializes the blog")
public BaseResponse<String> installBlog(@RequestBody InstallParam installParam) {
// Validate manually
ValidationUtils.validate(installParam, CreateCheck.class);
@ -179,7 +181,7 @@ public class InstallController {
@Nullable
private Category createDefaultCategoryIfAbsent() {
long categoryCount = categoryService.count();
if (categoryCount == 0) {
if (categoryCount > 0) {
return null;
}

View File

@ -2,16 +2,22 @@ package run.halo.app.controller.admin.api;
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.model.dto.BaseCommentDTO;
import run.halo.app.model.entity.JournalComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.params.CommentQuery;
import run.halo.app.model.params.JournalCommentParam;
import run.halo.app.model.vo.BaseCommentVO;
import run.halo.app.model.vo.BaseCommentWithParentVO;
import run.halo.app.model.vo.JournalCommentWithJournalVO;
import run.halo.app.service.JournalCommentService;
import run.halo.app.service.OptionService;
import java.util.List;
@ -21,7 +27,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Journal comment controller.
*
* @author johnniang
* @date 19-4-25
* @date 2019-04-25
*/
@RestController
@RequestMapping("/api/admin/journals/comments")
@ -29,8 +35,12 @@ public class JournalCommentController {
private final JournalCommentService journalCommentService;
public JournalCommentController(JournalCommentService journalCommentService) {
private final OptionService optionService;
public JournalCommentController(JournalCommentService journalCommentService,
OptionService optionService) {
this.journalCommentService = journalCommentService;
this.optionService = optionService;
}
@GetMapping
@ -43,12 +53,29 @@ public class JournalCommentController {
}
@GetMapping("latest")
@ApiOperation("Lists latest journal comments")
public List<JournalCommentWithJournalVO> listLatest(@RequestParam(name = "top", defaultValue = "10") int top,
@RequestParam(name = "status", required = false) CommentStatus status) {
List<JournalComment> latestComments = journalCommentService.pageLatest(top, status).getContent();
return journalCommentService.convertToWithJournalVo(latestComments);
}
@GetMapping("{journalId:\\d+}/tree_view")
@ApiOperation("Lists comments with tree view")
public Page<BaseCommentVO> listCommentTree(@PathVariable("journalId") Integer journalId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return journalCommentService.pageVosAllBy(journalId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{journalId:\\d+}/list_view")
@ApiOperation("Lists comment with list view")
public Page<BaseCommentWithParentVO> listComments(@PathVariable("journalId") Integer journalId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return journalCommentService.pageWithParentVoBy(journalId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@PostMapping
@ApiOperation("Creates a journal comment")
public BaseCommentDTO createCommentBy(@RequestBody JournalCommentParam journalCommentParam) {

View File

@ -2,22 +2,15 @@ package run.halo.app.controller.admin.api;
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.model.dto.JournalDTO;
import run.halo.app.model.dto.JournalWithCmtCountDTO;
import run.halo.app.model.entity.Journal;
import run.halo.app.model.params.JournalParam;
import run.halo.app.model.params.JournalQuery;
import run.halo.app.model.vo.BaseCommentVO;
import run.halo.app.model.vo.BaseCommentWithParentVO;
import run.halo.app.service.JournalCommentService;
import run.halo.app.service.JournalService;
import run.halo.app.service.OptionService;
import javax.validation.Valid;
import java.util.List;
@ -29,7 +22,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
*
* @author johnniang
* @author ryanwang
* @date 19-4-25
* @date 2019-04-25
*/
@RestController
@RequestMapping("/api/admin/journals")
@ -37,16 +30,8 @@ public class JournalController {
private final JournalService journalService;
private final JournalCommentService journalCommentService;
private final OptionService optionService;
public JournalController(JournalService journalService,
JournalCommentService journalCommentService,
OptionService optionService) {
public JournalController(JournalService journalService) {
this.journalService = journalService;
this.journalCommentService = journalCommentService;
this.optionService = optionService;
}
@GetMapping
@ -77,7 +62,8 @@ public class JournalController {
@RequestBody @Valid JournalParam journalParam) {
Journal journal = journalService.getById(id);
journalParam.update(journal);
return new JournalDTO().convertFrom(journalService.update(journal));
Journal updatedJournal = journalService.updateBy(journal);
return journalService.convertTo(updatedJournal);
}
@DeleteMapping("{journalId:\\d+}")
@ -86,20 +72,4 @@ public class JournalController {
Journal deletedJournal = journalService.removeById(journalId);
return journalService.convertTo(deletedJournal);
}
@GetMapping("{journalId:\\d+}/comments/tree_view")
@ApiOperation("Lists comments with tree view")
public Page<BaseCommentVO> listCommentTree(@PathVariable("journalId") Integer journalId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return journalCommentService.pageVosBy(journalId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{journalId:\\d+}/comments/list_view")
@ApiOperation("Lists comment with list view")
public Page<BaseCommentWithParentVO> listComments(@PathVariable("journalId") Integer journalId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return journalCommentService.pageWithParentVoBy(journalId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
}

View File

@ -12,11 +12,14 @@ import run.halo.app.service.LinkService;
import javax.validation.Valid;
import java.util.List;
import static org.springframework.data.domain.Sort.Direction.ASC;
import static org.springframework.data.domain.Sort.Direction.DESC;
/**
* Link Controller
*
* @author ryanwang
* @date : 2019/3/21
* @date 2019-03-21
*/
@RestController
@RequestMapping("/api/admin/links")
@ -28,30 +31,26 @@ public class LinkController {
this.linkService = linkService;
}
/**
* List all links
*
* @param sort sort
* @return List
*/
@GetMapping
public List<LinkDTO> listLinks(@SortDefault(sort = "priority", direction = Sort.Direction.ASC) Sort sort) {
return linkService.listDtos(sort);
@ApiOperation("Lists links")
public List<LinkDTO> listLinks(@SortDefault(sort = "team", direction = DESC) Sort sort) {
return linkService.listDtos(sort.and(Sort.by(ASC, "priority")));
}
/**
* Get link by id.
*
* @param id id
* @return LinkDTO
*/
@GetMapping("{id:\\d+}")
@ApiOperation("Get link detail by id")
@ApiOperation("Gets link detail by id")
public LinkDTO getBy(@PathVariable("id") Integer id) {
return new LinkDTO().convertFrom(linkService.getById(id));
}
@GetMapping("parse")
@ApiOperation("Gets link by parse url")
public LinkDTO getByParse(@RequestParam("url") String url) {
return linkService.getByParse(url);
}
@PostMapping
@ApiOperation("Creates a link")
public LinkDTO createBy(@RequestBody @Valid LinkParam linkParam) {
Link link = linkService.createBy(linkParam);
return new LinkDTO().convertFrom(link);
@ -66,14 +65,15 @@ public class LinkController {
return new LinkDTO().convertFrom(linkService.update(link));
}
/**
* Delete link by id.
*
* @param id id
*/
@DeleteMapping("{id:\\d+}")
@ApiOperation("Delete link by id")
public void deletePermanently(@PathVariable("id") Integer id) {
linkService.removeById(id);
}
@GetMapping("teams")
@ApiOperation(("Lists all link teams"))
public List<String> teams() {
return linkService.listAllTeams();
}
}

View File

@ -20,7 +20,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Log controller.
*
* @author johnniang
* @date 3/19/19
* @date 2019-03-19
*/
@RestController
@RequestMapping("/api/admin/logs")
@ -32,12 +32,6 @@ public class LogController {
this.logService = logService;
}
/**
* List latest logs.
*
* @param top top
* @return List of logs
*/
@GetMapping("latest")
@ApiOperation("Pages latest logs")
public List<LogDTO> pageLatest(@RequestParam(name = "top", defaultValue = "10") int top) {
@ -45,16 +39,14 @@ public class LogController {
}
@GetMapping
@ApiOperation("Lists logs")
public Page<LogDTO> pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable) {
Page<Log> logPage = logService.listAll(pageable);
return logPage.map(log -> new LogDTO().convertFrom(log));
}
/**
* Clear all logs.
*/
@GetMapping("clear")
@ApiOperation("Clear all logs")
@ApiOperation("Clears all logs")
public void clear() {
logService.removeAll();
}

View File

@ -1,5 +1,6 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@ -14,7 +15,7 @@ import javax.validation.Valid;
* Mail controller.
*
* @author johnniang
* @date 19-5-7
* @date 2019-05-07
*/
@RestController
@RequestMapping("/api/admin/mails")
@ -27,6 +28,7 @@ public class MailController {
}
@PostMapping("test")
@ApiOperation("Tests the SMTP service")
public BaseResponse testMail(@Valid @RequestBody MailParam mailParam) {
mailService.sendMail(mailParam.getTo(), mailParam.getSubject(), mailParam.getContent());
return BaseResponse.ok("发送成功");

View File

@ -20,7 +20,8 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Menu controller.
*
* @author johnniang
* @date 4/3/19
* @author ryanwang
* @date 2019-04-03
*/
@RestController
@RequestMapping("/api/admin/menus")
@ -34,24 +35,18 @@ public class MenuController {
@GetMapping
@ApiOperation("Lists all menus")
public List<MenuDTO> listAll(@SortDefault(sort = "priority", direction = DESC) Sort sort) {
return menuService.listDtos(sort);
public List<MenuDTO> listAll(@SortDefault(sort = "team", direction = DESC) Sort sort) {
return menuService.listDtos(sort.and(Sort.by(ASC, "priority")));
}
@GetMapping("tree_view")
@ApiOperation("List as category tree")
public List<MenuVO> listAsTree(@SortDefault(sort = "priority", direction = ASC) Sort sort) {
return menuService.listAsTree(sort);
@ApiOperation("Lists categories as tree")
public List<MenuVO> listAsTree(@SortDefault(sort = "team", direction = DESC) Sort sort) {
return menuService.listAsTree(sort.and(Sort.by(ASC, "priority")));
}
/**
* Get menu by menuId.
*
* @param menuId menuId
* @return MenuDTO
*/
@GetMapping("{menuId:\\d+}")
@ApiOperation("Get menu detail by id")
@ApiOperation("Gets menu detail by id")
public MenuDTO getBy(@PathVariable("menuId") Integer menuId) {
return new MenuDTO().convertFrom(menuService.getById(menuId));
}
@ -88,4 +83,10 @@ public class MenuController {
}
return new MenuDTO().convertFrom(menuService.removeById(menuId));
}
@GetMapping("teams")
@ApiOperation(("Lists all menu teams"))
public List<String> teams() {
return menuService.listAllTeams();
}
}

View File

@ -0,0 +1,45 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.model.enums.MigrateType;
import run.halo.app.service.MigrateService;
/**
* Migrate controller
*
* @author ryanwang
* @date 2019-10-29
*/
@RestController
@RequestMapping("/api/admin/migrations")
public class MigrateController {
private final MigrateService migrateService;
public MigrateController(MigrateService migrateService) {
this.migrateService = migrateService;
}
@PostMapping("halo_v0_4_4")
@ApiOperation("Migrate from Halo 0.4.4")
public void migrateHaloOldVersion(@RequestPart("file") MultipartFile file) {
migrateService.migrate(file, MigrateType.OLD_VERSION);
}
@PostMapping("wordpress")
@ApiOperation("Migrate from WordPress")
public void migrateWordPress(@RequestPart("file") MultipartFile file) {
migrateService.migrate(file, MigrateType.WORDPRESS);
}
@PostMapping("cnblogs")
@ApiOperation("Migrate from cnblogs")
public void migrateCnBlogs(@RequestPart("file") MultipartFile file) {
migrateService.migrate(file, MigrateType.CNBLOGS);
}
}

View File

@ -1,21 +1,30 @@
package run.halo.app.controller.admin.api;
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.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import run.halo.app.model.dto.OptionDTO;
import run.halo.app.model.dto.OptionSimpleDTO;
import run.halo.app.model.entity.Option;
import run.halo.app.model.params.OptionParam;
import run.halo.app.model.params.OptionQuery;
import run.halo.app.service.OptionService;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import static org.springframework.data.domain.Sort.Direction.DESC;
/**
* Option Controller.
*
* @author johnniang
* @date 3/20/19
* @author ryanwang
* @date 2019-03-20
*/
@RestController
@RequestMapping("/api/admin/options")
@ -28,11 +37,13 @@ public class OptionController {
}
@GetMapping
@ApiOperation("Lists options")
public List<OptionDTO> listAll() {
return optionService.listDtos();
}
@PostMapping("saving")
@ApiOperation("Saves options")
public void saveOptions(@Valid @RequestBody List<OptionParam> optionParams) {
optionService.save(optionParams);
}
@ -47,6 +58,39 @@ public class OptionController {
return optionService.listOptions(keys);
}
@GetMapping("list_view")
@ApiOperation("Lists all options with list view")
public Page<OptionSimpleDTO> listAllWithListView(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
OptionQuery optionQuery) {
return optionService.pageDtosBy(pageable, optionQuery);
}
@GetMapping("{id:\\d+}")
@ApiOperation("Gets option detail by id")
public OptionSimpleDTO getBy(@PathVariable("id") Integer id) {
Option option = optionService.getById(id);
return optionService.convertToDto(option);
}
@PostMapping
@ApiOperation("Creates option")
public void createBy(@RequestBody @Valid OptionParam optionParam) {
optionService.save(optionParam);
}
@PutMapping("{optionId:\\d+}")
@ApiOperation("Updates option")
public void updateBy(@PathVariable("optionId") Integer optionId,
@RequestBody @Valid OptionParam optionParam) {
optionService.update(optionId, optionParam);
}
@DeleteMapping("{optionId:\\d+}")
@ApiOperation("Deletes option")
public void deletePermanently(@PathVariable("optionId") Integer optionId) {
optionService.removePermanently(optionId);
}
@PostMapping("map_view/saving")
@ApiOperation("Saves options by option map")
public void saveOptionsWithMapView(@RequestBody Map<String, Object> 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-03-21
*/
@RestController
@RequestMapping("/api/admin/photos")
@ -34,47 +34,33 @@ public class PhotoController {
this.photoService = photoService;
}
/**
* List all photos
*
* @param sort sort
* @return all of photos
*/
@GetMapping(value = "latest")
@ApiOperation("Lists latest photos")
public List<PhotoDTO> listPhotos(@SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort) {
return photoService.listDtos(sort);
}
@GetMapping
@ApiOperation("Lists photos")
public Page<PhotoDTO> pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
PhotoQuery photoQuery) {
return photoService.pageDtosBy(pageable, photoQuery);
}
/**
* Get photo by id.
*
* @param photoId photo id
* @return PhotoDTO
*/
@GetMapping("{photoId:\\d+}")
@ApiOperation("Get photo detail by id")
@ApiOperation("Gets photo detail by id")
public PhotoDTO getBy(@PathVariable("photoId") Integer photoId) {
return new PhotoDTO().convertFrom(photoService.getById(photoId));
}
/**
* Delete photo by id.
*
* @param photoId photo id
*/
@DeleteMapping("{photoId:\\d+}")
@ApiOperation("Delete photo by id")
@ApiOperation("Deletes photo by id")
public void deletePermanently(@PathVariable("photoId") Integer photoId) {
photoService.removeById(photoId);
}
@PostMapping
@ApiOperation("Creates a photo")
public PhotoDTO createBy(@Valid @RequestBody PhotoParam photoParam) {
return new PhotoDTO().convertFrom(photoService.createBy(photoParam));
}

View File

@ -2,15 +2,21 @@ package run.halo.app.controller.admin.api;
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.model.dto.BaseCommentDTO;
import run.halo.app.model.entity.PostComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.params.CommentQuery;
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.PostCommentWithPostVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostCommentService;
import javax.validation.Valid;
@ -23,7 +29,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
*
* @author johnniang
* @author ryanwang
* @date 3/19/19
* @date 2019-03-29
*/
@RestController
@RequestMapping("/api/admin/posts/comments")
@ -31,8 +37,12 @@ public class PostCommentController {
private final PostCommentService postCommentService;
public PostCommentController(PostCommentService postCommentService) {
private final OptionService optionService;
public PostCommentController(PostCommentService postCommentService,
OptionService optionService) {
this.postCommentService = postCommentService;
this.optionService = optionService;
}
@GetMapping
@ -44,7 +54,7 @@ public class PostCommentController {
}
@GetMapping("latest")
@ApiOperation("Pages latest comments")
@ApiOperation("Pages post latest comments")
public List<PostCommentWithPostVO> listLatest(@RequestParam(name = "top", defaultValue = "10") int top,
@RequestParam(name = "status", required = false) CommentStatus status) {
// Get latest comment
@ -54,15 +64,31 @@ public class PostCommentController {
return postCommentService.convertToWithPostVo(content);
}
@GetMapping("{postId:\\d+}/tree_view")
@ApiOperation("Lists post comments with tree view")
public Page<BaseCommentVO> listCommentTree(@PathVariable("postId") Integer postId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return postCommentService.pageVosAllBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{postId:\\d+}/list_view")
@ApiOperation("Lists post comment with list view")
public Page<BaseCommentWithParentVO> listComments(@PathVariable("postId") Integer postId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return postCommentService.pageWithParentVoBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@PostMapping
@ApiOperation("Creates a comment (new or reply)")
@ApiOperation("Creates a post comment (new or reply)")
public BaseCommentDTO createBy(@RequestBody PostCommentParam postCommentParam) {
PostComment createdPostComment = postCommentService.createBy(postCommentParam);
return postCommentService.convertTo(createdPostComment);
}
@PutMapping("{commentId:\\d+}/status/{status}")
@ApiOperation("Updates comment status")
@ApiOperation("Updates post comment status")
public BaseCommentDTO updateStatusBy(@PathVariable("commentId") Long commentId,
@PathVariable("status") CommentStatus status) {
// Update comment status
@ -70,13 +96,27 @@ public class PostCommentController {
return postCommentService.convertTo(updatedPostComment);
}
@PutMapping("status/{status}")
@ApiOperation("Updates post comment status in batch")
public List<BaseCommentDTO> updateStatusInBatch(@PathVariable(name = "status") CommentStatus status,
@RequestBody List<Long> ids) {
List<PostComment> comments = postCommentService.updateStatusByIds(ids, status);
return postCommentService.convertTo(comments);
}
@DeleteMapping("{commentId:\\d+}")
@ApiOperation("Deletes comment permanently and recursively")
public BaseCommentDTO deleteBy(@PathVariable("commentId") Long commentId) {
@ApiOperation("Deletes post comment permanently and recursively")
public BaseCommentDTO deletePermanently(@PathVariable("commentId") Long commentId) {
PostComment deletedPostComment = postCommentService.removeById(commentId);
return postCommentService.convertTo(deletedPostComment);
}
@DeleteMapping
@ApiOperation("Delete post comments permanently in batch by id array")
public List<PostComment> deletePermanentlyInBatch(@RequestBody List<Long> ids) {
return postCommentService.removeByIds(ids);
}
@GetMapping("{commentId:\\d+}")
@ApiOperation("Gets a post comment by comment id")
public PostCommentWithPostVO getBy(@PathVariable("commentId") Long commentId) {
@ -85,6 +125,7 @@ public class PostCommentController {
}
@PutMapping("{commentId:\\d+}")
@ApiOperation("Updates a post comment")
public BaseCommentDTO updateBy(@Valid @RequestBody PostCommentParam commentParam,
@PathVariable("commentId") Long commentId) {
PostComment commentToUpdate = postCommentService.getById(commentId);

View File

@ -3,25 +3,26 @@ 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.BasePostDetailDTO;
import run.halo.app.model.dto.post.BasePostMinimalDTO;
import run.halo.app.model.dto.post.BasePostSimpleDTO;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.PostContentParam;
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.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -32,7 +33,8 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
*
* @author johnniang
* @author ryanwang
* @date 3/19/19
* @author guqing
* @date 2019-03-19
*/
@RestController
@RequestMapping("/api/admin/posts")
@ -54,15 +56,15 @@ public class PostController {
@GetMapping
@ApiOperation("Lists posts")
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);
public Page<? extends BasePostSimpleDTO> pageBy(@PageableDefault(sort = {"topPriority", "createTime"}, direction = DESC) Pageable pageable,
PostQuery postQuery,
@RequestParam(value = "more", defaultValue = "true") Boolean more) {
Page<Post> postPage = postService.pageBy(postQuery, pageable);
return postService.convertToListVo(postPage);
if (more) {
return postService.convertToListVo(postPage);
}
return postService.convertToSimple(postPage);
}
@GetMapping("latest")
@ -86,6 +88,7 @@ public class PostController {
}
@GetMapping("{postId:\\d+}")
@ApiOperation("Gets a post")
public PostDetailVO getBy(@PathVariable("postId") Integer postId) {
Post post = postService.getById(postId);
return postService.convertToDetailVo(post);
@ -98,15 +101,16 @@ public class PostController {
}
@PostMapping
@ApiOperation("Creates a post")
public PostDetailVO createBy(@Valid @RequestBody PostParam postParam,
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave) {
// Convert to
Post post = postParam.convertTo();
return postService.createBy(post, postParam.getTagIds(), postParam.getCategoryIds(), autoSave);
return postService.createBy(post, postParam.getTagIds(), postParam.getCategoryIds(), postParam.getPostMetas(), autoSave);
}
@PutMapping("{postId:\\d+}")
@ApiOperation("Updates a post")
public PostDetailVO updateBy(@Valid @RequestBody PostParam postParam,
@PathVariable("postId") Integer postId,
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave) {
@ -114,39 +118,60 @@ public class PostController {
Post postToUpdate = postService.getById(postId);
postParam.update(postToUpdate);
return postService.updateBy(postToUpdate, postParam.getTagIds(), postParam.getCategoryIds(), autoSave);
return postService.updateBy(postToUpdate, postParam.getTagIds(), postParam.getCategoryIds(), postParam.getPostMetas(), autoSave);
}
@PutMapping("{postId:\\d+}/status/{status}")
public void updateStatusBy(
@ApiOperation("Updates post status")
public BasePostMinimalDTO updateStatusBy(
@PathVariable("postId") Integer postId,
@PathVariable("status") PostStatus status) {
Post post = postService.getById(postId);
Post post = postService.updateStatus(status, postId);
// Set status
post.setStatus(status);
return new BasePostMinimalDTO().convertFrom(post);
}
// Update
postService.update(post);
@PutMapping("status/{status}")
@ApiOperation("Updates post status in batch")
public List<Post> updateStatusInBatch(@PathVariable(name = "status") PostStatus status,
@RequestBody List<Integer> ids) {
return postService.updateStatusByIds(ids, status);
}
@PutMapping("{postId:\\d+}/status/draft/content")
@ApiOperation("Updates draft")
public BasePostDetailDTO updateDraftBy(
@PathVariable("postId") Integer postId,
@RequestBody PostContentParam contentParam) {
// Update draft content
Post post = postService.updateDraftContent(contentParam.getContent(), postId);
return new BasePostDetailDTO().convertFrom(post);
}
@DeleteMapping("{postId:\\d+}")
@ApiOperation("Deletes a photo permanently")
public void deletePermanently(@PathVariable("postId") Integer postId) {
// Remove it
postService.removeById(postId);
}
@GetMapping("preview/{postId:\\d+}")
public String preview(@PathVariable("postId") Integer postId) {
@DeleteMapping
@ApiOperation("Deletes posts permanently in batch by id array")
public List<Post> deletePermanentlyInBatch(@RequestBody List<Integer> ids) {
return postService.removeByIds(ids);
}
@GetMapping(value = {"preview/{postId:\\d+}", "{postId:\\d+}/preview"})
@ApiOperation("Gets a post preview link")
public String preview(@PathVariable("postId") Integer postId) throws UnsupportedEncodingException {
Post post = postService.getById(postId);
String token = IdUtil.simpleUUID();
// cache preview token
cacheStore.putAny("preview-post-token-" + postId, token, 10, TimeUnit.MINUTES);
cacheStore.putAny(token, 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);
return String.format("%s/archives/%s?token=%s", optionService.getBlogBaseUrl(), URLEncoder.encode(post.getUrl(), StandardCharsets.UTF_8.name()), token);
}
}

View File

@ -19,6 +19,7 @@ import run.halo.app.service.RecoveryService;
* @author johnniang
* @date 19-4-26
*/
@Deprecated
@RestController
@RequestMapping("/api/admin/recoveries")
public class RecoveryController {

View File

@ -2,15 +2,21 @@ package run.halo.app.controller.admin.api;
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.model.dto.BaseCommentDTO;
import run.halo.app.model.entity.SheetComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.params.CommentQuery;
import run.halo.app.model.params.SheetCommentParam;
import run.halo.app.model.vo.BaseCommentVO;
import run.halo.app.model.vo.BaseCommentWithParentVO;
import run.halo.app.model.vo.SheetCommentWithSheetVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.SheetCommentService;
import javax.validation.Valid;
@ -23,7 +29,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
*
* @author johnniang
* @author ryanwang
* @date 19-4-25
* @date 2019-04-25
*/
@RestController
@RequestMapping("/api/admin/sheets/comments")
@ -31,11 +37,16 @@ public class SheetCommentController {
private final SheetCommentService sheetCommentService;
public SheetCommentController(SheetCommentService sheetCommentService) {
private final OptionService optionService;
public SheetCommentController(SheetCommentService sheetCommentService,
OptionService optionService) {
this.sheetCommentService = sheetCommentService;
this.optionService = optionService;
}
@GetMapping
@ApiOperation("Lists sheet comments")
public Page<SheetCommentWithSheetVO> pageBy(@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
CommentQuery commentQuery) {
Page<SheetComment> sheetCommentPage = sheetCommentService.pageBy(commentQuery, pageable);
@ -43,21 +54,38 @@ public class SheetCommentController {
}
@GetMapping("latest")
@ApiOperation("Lists latest sheet comments")
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.convertToWithSheetVo(sheetCommentPage.getContent());
}
@GetMapping("{sheetId:\\d+}/tree_view")
@ApiOperation("Lists sheet comments with tree view")
public Page<BaseCommentVO> listCommentTree(@PathVariable("sheetId") Integer sheetId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return sheetCommentService.pageVosAllBy(sheetId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@GetMapping("{sheetId:\\d+}/list_view")
@ApiOperation("Lists sheet comment with list view")
public Page<BaseCommentWithParentVO> listComments(@PathVariable("sheetId") Integer sheetId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
return sheetCommentService.pageWithParentVoBy(sheetId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
@PostMapping
@ApiOperation("Creates a comment (new or reply)")
@ApiOperation("Creates a sheet comment (new or reply)")
public BaseCommentDTO createBy(@RequestBody SheetCommentParam commentParam) {
SheetComment createdComment = sheetCommentService.createBy(commentParam);
return sheetCommentService.convertTo(createdComment);
}
@PutMapping("{commentId:\\d+}/status/{status}")
@ApiOperation("Updates comment status")
@ApiOperation("Updates sheet comment status")
public BaseCommentDTO updateStatusBy(@PathVariable("commentId") Long commentId,
@PathVariable("status") CommentStatus status) {
// Update comment status
@ -65,21 +93,37 @@ public class SheetCommentController {
return sheetCommentService.convertTo(updatedSheetComment);
}
@PutMapping("status/{status}")
@ApiOperation("Updates sheet comment status in batch")
public List<BaseCommentDTO> updateStatusInBatch(@PathVariable(name = "status") CommentStatus status,
@RequestBody List<Long> ids) {
List<SheetComment> comments = sheetCommentService.updateStatusByIds(ids, status);
return sheetCommentService.convertTo(comments);
}
@DeleteMapping("{commentId:\\d+}")
@ApiOperation("Deletes comment permanently and recursively")
public BaseCommentDTO deleteBy(@PathVariable("commentId") Long commentId) {
@ApiOperation("Deletes sheet comment permanently and recursively")
public BaseCommentDTO deletePermanently(@PathVariable("commentId") Long commentId) {
SheetComment deletedSheetComment = sheetCommentService.removeById(commentId);
return sheetCommentService.convertTo(deletedSheetComment);
}
@DeleteMapping
@ApiOperation("Deletes sheet comments permanently in batch by id array")
public List<SheetComment> deletePermanentlyInBatch(@RequestBody List<Long> ids) {
return sheetCommentService.removeByIds(ids);
}
@GetMapping("{commentId:\\d+}")
@ApiOperation("Gets a post comment by comment id")
@ApiOperation("Gets a sheet comment by comment id")
public SheetCommentWithSheetVO getBy(@PathVariable("commentId") Long commentId) {
SheetComment comment = sheetCommentService.getById(commentId);
return sheetCommentService.convertToWithSheetVo(comment);
}
@PutMapping("{commentId:\\d+}")
@ApiOperation("Updates a sheet comment")
public BaseCommentDTO updateBy(@Valid @RequestBody SheetCommentParam commentParam,
@PathVariable("commentId") Long commentId) {
SheetComment commentToUpdate = sheetCommentService.getById(commentId);

View File

@ -8,15 +8,18 @@ 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.SheetDetailVO;
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.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -49,9 +52,9 @@ public class SheetController {
@GetMapping("{sheetId:\\d+}")
@ApiOperation("Gets a sheet")
public BasePostDetailDTO getBy(@PathVariable("sheetId") Integer sheetId) {
public SheetDetailVO getBy(@PathVariable("sheetId") Integer sheetId) {
Sheet sheet = sheetService.getById(sheetId);
return sheetService.convertToDetail(sheet);
return sheetService.convertToDetailVo(sheet);
}
@GetMapping
@ -69,15 +72,15 @@ public class SheetController {
@PostMapping
@ApiOperation("Creates a sheet")
public BasePostDetailDTO createBy(@RequestBody @Valid SheetParam sheetParam,
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave) {
Sheet sheet = sheetService.createBy(sheetParam.convertTo(), autoSave);
return sheetService.convertToDetail(sheet);
public SheetDetailVO createBy(@RequestBody @Valid SheetParam sheetParam,
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave) {
Sheet sheet = sheetService.createBy(sheetParam.convertTo(), sheetParam.getSheetMetas(), autoSave);
return sheetService.convertToDetailVo(sheet);
}
@PutMapping("{sheetId:\\d+}")
@ApiOperation("Updates a sheet")
public BasePostDetailDTO updateBy(
public SheetDetailVO updateBy(
@PathVariable("sheetId") Integer sheetId,
@RequestBody @Valid SheetParam sheetParam,
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave) {
@ -85,12 +88,13 @@ public class SheetController {
sheetParam.update(sheetToUpdate);
Sheet sheet = sheetService.updateBy(sheetToUpdate, autoSave);
Sheet sheet = sheetService.updateBy(sheetToUpdate, sheetParam.getSheetMetas(), autoSave);
return sheetService.convertToDetail(sheet);
return sheetService.convertToDetailVo(sheet);
}
@PutMapping("{sheetId:\\d+}/{status}")
@ApiOperation("Updates a sheet")
public void updateStatusBy(
@PathVariable("sheetId") Integer sheetId,
@PathVariable("status") PostStatus status) {
@ -105,21 +109,22 @@ public class SheetController {
@DeleteMapping("{sheetId:\\d+}")
@ApiOperation("Deletes a sheet")
public BasePostDetailDTO deleteBy(@PathVariable("sheetId") Integer sheetId) {
public SheetDetailVO deleteBy(@PathVariable("sheetId") Integer sheetId) {
Sheet sheet = sheetService.removeById(sheetId);
return sheetService.convertToDetail(sheet);
return sheetService.convertToDetailVo(sheet);
}
@GetMapping("preview/{sheetId:\\d+}")
public String preview(@PathVariable("sheetId") Integer sheetId) {
@ApiOperation("Gets a sheet preview link")
public String preview(@PathVariable("sheetId") Integer sheetId) throws UnsupportedEncodingException {
Sheet sheet = sheetService.getById(sheetId);
String token = IdUtil.simpleUUID();
// cache preview token
cacheStore.putAny("preview-sheet-token-" + sheetId, token, 10, TimeUnit.MINUTES);
cacheStore.putAny(token, 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);
return String.format("%s/s/%s?token=%s", optionService.getBlogBaseUrl(), URLEncoder.encode(sheet.getUrl(), StandardCharsets.UTF_8.name()), token);
}
}

View File

@ -0,0 +1,52 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.model.support.StaticFile;
import run.halo.app.service.StaticStorageService;
import java.util.List;
/**
* Static storage controller.
*
* @author ryanwang
* @date 2019-12-06
*/
@RestController
@RequestMapping("/api/admin/statics")
public class StaticStorageController {
private final StaticStorageService staticStorageService;
public StaticStorageController(StaticStorageService staticStorageService) {
this.staticStorageService = staticStorageService;
}
@GetMapping
@ApiOperation("Lists static files")
public List<StaticFile> list() {
return staticStorageService.listStaticFolder();
}
@DeleteMapping
@ApiOperation("Deletes file by relative path")
public void deletePermanently(@RequestParam("path") String path) {
staticStorageService.delete(path);
}
@PostMapping
@ApiOperation("Creates a folder")
public void createFolder(String basePath,
@RequestParam("folderName") String folderName) {
staticStorageService.createFolder(basePath, folderName);
}
@PostMapping("upload")
@ApiOperation("Uploads static file")
public void upload(String basePath,
@RequestPart("file") MultipartFile file) {
staticStorageService.update(basePath, file);
}
}

View File

@ -0,0 +1,38 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.StatisticDTO;
import run.halo.app.model.dto.StatisticWithUserDTO;
import run.halo.app.service.StatisticService;
/**
* Statistic controller.
*
* @author ryanwang
* @date 2019-12-16
*/
@RestController
@RequestMapping("/api/admin/statistics")
public class StatisticController {
private final StatisticService statisticService;
public StatisticController(StatisticService statisticService) {
this.statisticService = statisticService;
}
@GetMapping
@ApiOperation("Gets blog statistics.")
public StatisticDTO statistics() {
return statisticService.getStatistic();
}
@GetMapping("user")
@ApiOperation("Gets blog statistics with user")
public StatisticWithUserDTO statisticsWithUser() {
return statisticService.getStatisticWithUser();
}
}

View File

@ -37,7 +37,7 @@ public class TagController {
}
@GetMapping
@ApiOperation("Lists tag")
@ApiOperation("Lists tags")
public List<? extends TagDTO> listTags(@SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort,
@ApiParam("Return more information(post count) if it is set")
@RequestParam(name = "more", required = false, defaultValue = "false") Boolean more) {
@ -48,7 +48,7 @@ public class TagController {
}
@PostMapping
@ApiOperation("Creates tag")
@ApiOperation("Creates a tag")
public TagDTO createTag(@Valid @RequestBody TagParam tagParam) {
// Convert to tag
Tag tag = tagParam.convertTo();
@ -59,20 +59,14 @@ public class TagController {
return tagService.convertTo(tagService.create(tag));
}
/**
* Get tag by id
*
* @param tagId tag id
* @return TagDTO
*/
@GetMapping("{tagId:\\d+}")
@ApiOperation("Get tag detail by id")
@ApiOperation("Gets tag detail by id")
public TagDTO getBy(@PathVariable("tagId") Integer tagId) {
return tagService.convertTo(tagService.getById(tagId));
}
@PutMapping("{tagId:\\d+}")
@ApiOperation("Updates tag")
@ApiOperation("Updates a tag")
public TagDTO updateBy(@PathVariable("tagId") Integer tagId,
@Valid @RequestBody TagParam tagParam) {
// Get old tag
@ -86,7 +80,7 @@ public class TagController {
}
@DeleteMapping("{tagId:\\d+}")
@ApiOperation("Deletes tag")
@ApiOperation("Deletes a tag")
public TagDTO deletePermanently(@PathVariable("tagId") Integer tagId) {
// Remove the tag
Tag deletedTag = tagService.removeById(tagId);

View File

@ -20,7 +20,7 @@ import java.util.Set;
* Theme controller.
*
* @author ryanwang
* @date : 2019/3/20
* @date 2019-03-20
*/
@RestController
@RequestMapping("/api/admin/themes")
@ -43,46 +43,59 @@ public class ThemeController {
}
@GetMapping
@ApiOperation("List all themes")
@ApiOperation("Lists all themes")
public Set<ThemeProperty> listAll() {
return themeService.getThemes();
}
@GetMapping("activation/files")
@ApiOperation("Lists all activate theme files")
public List<ThemeFile> listFiles() {
return themeService.listThemeFolderBy(themeService.getActivatedThemeId());
}
@GetMapping("{themeId}/files")
@ApiOperation("Lists theme files by theme id")
public List<ThemeFile> listFiles(@PathVariable("themeId") String themeId) {
return themeService.listThemeFolderBy(themeId);
}
@GetMapping("files/content")
@ApiOperation("Gets template content")
public BaseResponse<String> getContentBy(@RequestParam(name = "path") String path) {
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), themeService.getTemplateContent(path));
}
@GetMapping("{themeId}/files/content")
@ApiOperation("Gets template content by theme id")
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")
@ApiOperation("Updates template content")
public void updateContentBy(@RequestBody ThemeContentParam param) {
themeService.saveTemplateContent(param.getPath(), param.getContent());
}
@PutMapping("{themeId}/files/content")
@ApiOperation("Updates template content by theme id")
public void updateContentBy(@PathVariable("themeId") String themeId,
@RequestBody ThemeContentParam param) {
themeService.saveTemplateContent(themeId, param.getPath(), param.getContent());
}
@GetMapping("files/custom")
public Set<String> customTemplate() {
return themeService.listCustomTemplates(themeService.getActivatedThemeId());
@GetMapping("activation/template/custom/sheet")
@ApiOperation("Gets custom sheet templates")
public Set<String> customSheetTemplate() {
return themeService.listCustomTemplates(themeService.getActivatedThemeId(), ThemeService.CUSTOM_SHEET_PREFIX);
}
@GetMapping("activation/template/custom/post")
@ApiOperation("Gets custom post templates")
public Set<String> customPostTemplate() {
return themeService.listCustomTemplates(themeService.getActivatedThemeId(), ThemeService.CUSTOM_POST_PREFIX);
}
@PostMapping("{themeId}/activation")
@ -141,12 +154,13 @@ public class ThemeController {
}
@PostMapping("upload")
@ApiOperation("Upload theme")
@ApiOperation("Uploads a theme")
public ThemeProperty uploadTheme(@RequestPart("file") MultipartFile file) {
return themeService.upload(file);
}
@PutMapping("upload/{themeId}")
@ApiOperation("Upgrades theme by file")
public ThemeProperty updateThemeByUpload(@PathVariable("themeId") String themeId,
@RequestPart("file") MultipartFile file) {
return themeService.update(themeId, file);
@ -159,8 +173,8 @@ public class ThemeController {
}
@PutMapping("fetching/{themeId}")
public ThemeProperty updateThemeByFetching(@PathVariable("themeId") String themeId,
@RequestPart(name = "file", required = false) MultipartFile file) {
@ApiOperation("Upgrades theme by remote")
public ThemeProperty updateThemeByFetching(@PathVariable("themeId") String themeId) {
return themeService.update(themeId);
}
@ -172,8 +186,8 @@ public class ThemeController {
}
@GetMapping(value = "activation/template/exists")
@ApiOperation("Determines if template exists")
public BaseResponse exists(@RequestParam(value = "template") String template) {
return BaseResponse.ok(themeService.templateExists(template));
}
}

View File

@ -1,34 +1,34 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.service.TraceService;
import java.util.List;
/**
* Trace controller.
*
* @author johnniang
* @date 19-6-18
*/
@RestController
@RequestMapping("/api/admin/traces")
public class TraceController {
private final TraceService traceService;
public TraceController(TraceService traceService) {
this.traceService = traceService;
}
@GetMapping
@ApiOperation("Lists http traces")
public List<HttpTrace> listHttpTraces() {
return traceService.listHttpTraces();
}
}
//package run.halo.app.controller.admin.api;
//
//import io.swagger.annotations.ApiOperation;
//import org.springframework.boot.actuate.trace.http.HttpTrace;
//import org.springframework.web.bind.annotation.GetMapping;
//import org.springframework.web.bind.annotation.RequestMapping;
//import org.springframework.web.bind.annotation.RestController;
//import run.halo.app.service.TraceService;
//
//import java.util.List;
//
///**
// * Trace controller.
// *
// * @author johnniang
// * @date 19-6-18
// */
//@RestController
//@RequestMapping("/api/admin/traces")
//public class TraceController {
//
// private final TraceService traceService;
//
// public TraceController(TraceService traceService) {
// this.traceService = traceService;
// }
//
// @GetMapping
// @ApiOperation("Lists http traces")
// public List<HttpTrace> listHttpTraces() {
// return traceService.listHttpTraces();
// }
//
//}

View File

@ -1,5 +1,6 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import run.halo.app.model.dto.UserDTO;
import run.halo.app.model.entity.User;
@ -16,7 +17,7 @@ import javax.validation.Valid;
* User controller.
*
* @author johnniang
* @date 3/19/19
* @date 2019-03-19
*/
@RestController
@RequestMapping("/api/admin/users")
@ -29,11 +30,13 @@ public class UserController {
}
@GetMapping("profiles")
@ApiOperation("Gets user profile")
public UserDTO getProfile(User user) {
return new UserDTO().convertFrom(user);
}
@PutMapping("profiles")
@ApiOperation("Updates user profile")
public UserDTO updateProfile(@RequestBody UserParam userParam, User user) {
// Validate the user param
ValidationUtils.validate(userParam, UpdateCheck.class);
@ -46,6 +49,7 @@ public class UserController {
}
@PutMapping("profiles/password")
@ApiOperation("Updates user's password")
public BaseResponse updatePassword(@RequestBody @Valid PasswordParam passwordParam, User user) {
userService.updatePassword(passwordParam.getOldPassword(), passwordParam.getNewPassword(), user.getId());
return BaseResponse.ok("密码修改成功");

View File

@ -3,6 +3,7 @@ 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.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@ -16,14 +17,14 @@ 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.PostMeta;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.vo.BaseCommentVO;
import run.halo.app.model.support.HaloConst;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.service.*;
import run.halo.app.utils.MarkdownUtils;
import java.io.File;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -33,7 +34,9 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Blog archive page controller
*
* @author ryanwang
* @date : 2019-03-17
* @author guqing
* @author evanwang
* @date 2019-03-17
*/
@Slf4j
@Controller
@ -46,9 +49,9 @@ public class ContentArchiveController {
private final PostCategoryService postCategoryService;
private final PostTagService postTagService;
private final PostMetaService postMetaService;
private final PostCommentService postCommentService;
private final PostTagService postTagService;
private final OptionService optionService;
@ -57,15 +60,15 @@ public class ContentArchiveController {
public ContentArchiveController(PostService postService,
ThemeService themeService,
PostCategoryService postCategoryService,
PostMetaService postMetaService,
PostTagService postTagService,
PostCommentService postCommentService,
OptionService optionService,
StringCacheStore cacheStore) {
this.postService = postService;
this.themeService = themeService;
this.postCategoryService = postCategoryService;
this.postMetaService = postMetaService;
this.postTagService = postTagService;
this.postCommentService = postCommentService;
this.optionService = optionService;
this.cacheStore = cacheStore;
}
@ -107,68 +110,51 @@ public class ContentArchiveController {
/**
* Render post page.
*
* @param url post slug url.
* @param preview preview
* @param token preview token
* @param model model
* @param url post slug url.
* @param token view 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;
if (preview) {
post = postService.getBy(PostStatus.DRAFT, url);
} else if (intimate) {
post = postService.getBy(PostStatus.INTIMATE, url);
} else {
Post post = postService.getByUrl(url);
if (post.getStatus().equals(PostStatus.INTIMATE) && StringUtils.isEmpty(token)) {
String redirect = String.format("%s/archives/%s/password", optionService.getBlogBaseUrl(), post.getUrl());
return "redirect:" + redirect;
}
if (StringUtils.isEmpty(token)) {
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) {
} else {
// verify token
String cachedToken = cacheStore.getAny(token, String.class).orElseThrow(() -> new ForbiddenException("您没有该文章的访问权限"));
if (!cachedToken.equals(token)) {
throw new ForbiddenException("您没有该文章的访问权限");
}
post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent()));
}
postService.publishVisitEvent(post.getId());
postService.getNextPost(post.getCreateTime()).ifPresent(nextPost -> model.addAttribute("nextPost", nextPost));
postService.getPrePost(post.getCreateTime()).ifPresent(prePost -> model.addAttribute("prePost", prePost));
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));
List<PostMeta> metas = postMetaService.listBy(post.getId());
model.addAttribute("is_post", true);
model.addAttribute("post", postService.convertToDetailVo(post));
model.addAttribute("categories", categories);
model.addAttribute("tags", tags);
model.addAttribute("comments", comments);
model.addAttribute("metas", postMetaService.convertToMap(metas));
if (preview) {
// refresh timeUnit
cacheStore.putAny("preview-post-token-" + post.getId(), token, 10, TimeUnit.MINUTES);
// TODO,Will be deprecated
model.addAttribute("comments", Page.empty());
if (themeService.templateExists(ThemeService.CUSTOM_POST_PREFIX + post.getTemplate() + HaloConst.SUFFIX_FTL)) {
return themeService.render(ThemeService.CUSTOM_POST_PREFIX + post.getTemplate());
}
return themeService.render("post");
@ -177,29 +163,21 @@ public class ContentArchiveController {
@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
@CacheLock(traceRequest = true, expired = 2)
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);
String redirect = String.format("%s/archives/%s?token=%s", optionService.getBlogBaseUrl(), post.getUrl(), token);
return "redirect:" + redirect;
} else {
String redirect = String.format("%s/archives/%s/password", optionService.getBlogBaseUrl(), post.getUrl());

View File

@ -23,7 +23,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Category controller.
*
* @author ryanwang
* @date : 2019/3/20
* @date 2019-03-20
*/
@Controller
@RequestMapping(value = "/categories")

View File

@ -18,7 +18,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import run.halo.app.model.entity.Post;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.vo.PostListVO;
import run.halo.app.model.vo.PostDetailVO;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostService;
@ -32,7 +32,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
/**
* @author ryanwang
* @date : 2019-03-21
* @date 2019-03-21
*/
@Slf4j
@Controller
@ -79,7 +79,7 @@ public class ContentFeedController {
@GetMapping(value = {"atom", "atom.xml"}, produces = XML_MEDIA_TYPE)
@ResponseBody
public String atom(Model model) throws IOException, TemplateException {
model.addAttribute("posts", buildPosts(buildPostPageable(optionService.getPostPageSize())));
model.addAttribute("posts", buildPosts(buildPostPageable(optionService.getRssPageSize())));
Template template = freeMarker.getConfiguration().getTemplate("common/web/atom.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
}
@ -146,9 +146,9 @@ public class ContentFeedController {
* @param pageable pageable
* @return List<Post>
*/
private List<PostListVO> buildPosts(@NonNull Pageable pageable) {
private List<PostDetailVO> buildPosts(@NonNull Pageable pageable) {
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
Page<PostListVO> posts = postService.convertToListVo(postPage);
Page<PostDetailVO> posts = postService.convertToDetailVo(postPage);
posts.getContent().forEach(postListVO -> {
try {
// Encode post url

View File

@ -25,7 +25,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Blog index page controller
*
* @author ryanwang
* @date : 2019-03-17
* @date 2019-03-17
*/
@Slf4j
@Controller

View File

@ -14,7 +14,6 @@ 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;
import run.halo.app.service.ThemeService;
@ -25,7 +24,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Blog journal page controller
*
* @author ryanwang
* @date : 2019-05-04
* @date 2019-05-04
*/
@Slf4j
@Controller
@ -34,18 +33,14 @@ public class ContentJournalController {
private final JournalService journalService;
private final JournalCommentService journalCommentService;
private final OptionService optionService;
private final ThemeService themeService;
public ContentJournalController(JournalService journalService,
JournalCommentService journalCommentService,
OptionService optionService,
ThemeService themeService) {
this.journalService = journalService;
this.journalCommentService = journalCommentService;
this.optionService = optionService;
this.themeService = themeService;
}
@ -83,8 +78,8 @@ public class ContentJournalController {
int[] rainbow = PageUtil.rainbow(page, journals.getTotalPages(), 3);
model.addAttribute("is_journal", true);
model.addAttribute("journals", journals);
model.addAttribute("is_journals", true);
model.addAttribute("journals", journalService.convertToCmtCountDto(journals));
model.addAttribute("rainbow", rainbow);
return themeService.render("journals");
}

View File

@ -25,7 +25,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Search controller.
*
* @author ryanwang
* @date : 2019-04-21
* @date 2019-04-21
*/
@Controller
@RequestMapping(value = "/search")

View File

@ -1,9 +1,10 @@
package run.halo.app.controller.content;
import org.apache.commons.lang3.StringUtils;
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;
@ -11,25 +12,24 @@ 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.dto.PhotoDTO;
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.model.vo.SheetDetailVO;
import run.halo.app.service.PhotoService;
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.
*
* @author ryanwang
* @date : 2019-03-21
* @author evanwang
* @date 2019-03-21
*/
@Controller
public class ContentSheetController {
@ -39,21 +39,17 @@ public class ContentSheetController {
private final ThemeService themeService;
private final SheetCommentService sheetCommentService;
private final OptionService optionService;
private final PhotoService photoService;
private final StringCacheStore cacheStore;
public ContentSheetController(SheetService sheetService,
ThemeService themeService,
SheetCommentService sheetCommentService,
OptionService optionService,
PhotoService photoService,
StringCacheStore cacheStore) {
this.sheetService = sheetService;
this.themeService = themeService;
this.sheetCommentService = sheetCommentService;
this.optionService = optionService;
this.photoService = photoService;
this.cacheStore = cacheStore;
}
@ -63,7 +59,26 @@ public class ContentSheetController {
* @return template path: themes/{theme}/photos.ftl
*/
@GetMapping(value = "/photos")
public String photos() {
public String photos(Model model,
@RequestParam(value = "size", required = false, defaultValue = "10") Integer size) {
return photos(model, 1, size);
}
/**
* Render photo page
*
* @param model model
* @param page current page
* @param size current page size
* @return template path: themes/{theme}/photos.ftl
*/
@GetMapping(value = "/photos/page/{page}")
public String photos(Model model,
@PathVariable(value = "page") Integer page,
@RequestParam(value = "size", required = false, defaultValue = "10") Integer size) {
Pageable pageable = PageRequest.of(page >= 1 ? page - 1 : page, size, Sort.by(DESC, "createTime"));
Page<PhotoDTO> photos = photoService.pageBy(pageable);
model.addAttribute("photos", photos);
return themeService.render("photos");
}
@ -80,46 +95,42 @@ public class ContentSheetController {
/**
* Render custom sheet
*
* @param url sheet url
* @param preview preview
* @param token token
* @param model model
* @param url sheet url
* @param token view 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(preview ? PostStatus.DRAFT : PostStatus.PUBLISHED, url);
if (preview) {
// render markdown to html when preview post
Sheet sheet = sheetService.getByUrl(url);
if (StringUtils.isEmpty(token)) {
sheet = sheetService.getBy(PostStatus.PUBLISHED, url);
} else {
// render markdown to html when preview sheet
sheet.setFormatContent(MarkdownUtils.renderHtml(sheet.getOriginalContent()));
// verify token
String cachedToken = cacheStore.getAny("preview-sheet-token-" + sheet.getId(), String.class).orElseThrow(() -> new ForbiddenException("该页面的预览链接不存在或已过期"));
String cachedToken = cacheStore.getAny(token, String.class).orElseThrow(() -> new ForbiddenException("您没有该页面的访问权限"));
if (!cachedToken.equals(token)) {
throw new ForbiddenException("该页面的预览链接不存在或已过期");
throw new ForbiddenException("您没有该页面的访问权限");
}
}
sheetService.publishVisitEvent(sheet.getId());
Page<BaseCommentVO> comments = sheetCommentService.pageVosBy(sheet.getId(), PageRequest.of(cp, optionService.getCommentPageSize(), sort));
SheetDetailVO sheetDetailVO = sheetService.convertToDetailVo(sheet);
// sheet and post all can use
model.addAttribute("sheet", sheetService.convertToDetail(sheet));
model.addAttribute("post", sheetService.convertToDetail(sheet));
model.addAttribute("sheet", sheetDetailVO);
model.addAttribute("post", sheetDetailVO);
model.addAttribute("is_sheet", true);
model.addAttribute("comments", comments);
if (preview) {
// refresh timeUnit
cacheStore.putAny("preview-sheet-token-" + sheet.getId(), token, 10, TimeUnit.MINUTES);
}
// TODO,Will be deprecated
model.addAttribute("comments", Page.empty());
if (themeService.templateExists(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate() + HaloConst.SUFFIX_FTL)) {
return themeService.render(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate());

View File

@ -23,7 +23,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Tag controller.
*
* @author ryanwang
* @date : 2019-03-21
* @date 2019-03-21
*/
@Controller
@RequestMapping(value = "/tags")

View File

@ -4,6 +4,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.entity.User;
import run.halo.app.model.properties.BlogProperties;
@ -11,6 +12,7 @@ import run.halo.app.model.support.HaloConst;
import run.halo.app.service.OptionService;
import run.halo.app.service.UserService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@ -18,37 +20,52 @@ import java.io.IOException;
* Main controller.
*
* @author ryanwang
* @date : 2019-04-23
* @date 2019-04-23
*/
@Controller
public class MainController {
/**
* Index redirect uri.
*/
private final static String INDEX_REDIRECT_URI = "index.html";
/**
* Install redirect uri.
*/
private final static String INSTALL_REDIRECT_URI = INDEX_REDIRECT_URI + "#install";
private final UserService userService;
private final OptionService optionService;
public MainController(UserService userService, OptionService optionService) {
private final HaloProperties haloProperties;
public MainController(UserService userService, OptionService optionService, HaloProperties haloProperties) {
this.userService = userService;
this.optionService = optionService;
this.haloProperties = haloProperties;
}
@GetMapping("/admin")
public String admin() {
return "redirect:/admin/index.html";
@GetMapping("${halo.admin-path:admin}")
public void admin(HttpServletRequest request, HttpServletResponse response) throws IOException {
String adminIndexRedirectUri = StringUtils.appendIfMissing(this.haloProperties.getAdminPath(), "/") + INDEX_REDIRECT_URI;
response.sendRedirect(adminIndexRedirectUri);
}
@GetMapping("/install")
public String installation() {
return "redirect:/admin/index.html#install";
}
@GetMapping("/version")
@GetMapping("version")
@ResponseBody
public String version() {
return HaloConst.HALO_VERSION;
}
@GetMapping("/avatar")
@GetMapping("install")
public void installation(HttpServletResponse response) throws IOException {
String installRedirectUri = StringUtils.appendIfMissing(this.haloProperties.getAdminPath(), "/") + INSTALL_REDIRECT_URI;
response.sendRedirect(installRedirectUri);
}
@GetMapping("avatar")
public void avatar(HttpServletResponse response) throws IOException {
User user = userService.getCurrentUser().orElseThrow(() -> new ServiceException("未查询到博主信息"));
if (StringUtils.isNotEmpty(user.getAvatar())) {
@ -56,7 +73,7 @@ public class MainController {
}
}
@GetMapping("/logo")
@GetMapping("logo")
public void logo(HttpServletResponse response) throws IOException {
String blogLogo = optionService.getByProperty(BlogProperties.BLOG_LOGO).orElse("").toString();
if (StringUtils.isNotEmpty(blogLogo)) {
@ -64,7 +81,7 @@ public class MainController {
}
}
@GetMapping("/favicon.ico")
@GetMapping("favicon.ico")
public void favicon(HttpServletResponse response) throws IOException {
String favicon = optionService.getByProperty(BlogProperties.BLOG_FAVICON).orElse("").toString();
if (StringUtils.isNotEmpty(favicon)) {

View File

@ -35,7 +35,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* @author ryanwang
* @date 2019-04-26
*/
@RestController("PortalJournalController")
@RestController("ApiContentJournalController")
@RequestMapping("/api/content/journals")
public class JournalController {

View File

@ -16,10 +16,7 @@ import run.halo.app.model.entity.PostComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.enums.PostStatus;
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.model.vo.*;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostCommentService;
import run.halo.app.service.PostService;
@ -54,9 +51,9 @@ public class PostController {
@GetMapping
@ApiOperation("Lists posts")
public Page<BasePostSimpleDTO> pageBy(@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
public Page<PostListVO> pageBy(@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
return postService.convertToSimple(postPage);
return postService.convertToListVo(postPage);
}
@PostMapping(value = "search")

View File

@ -10,16 +10,12 @@ import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.*;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.model.dto.BaseCommentDTO;
import run.halo.app.model.dto.post.BasePostDetailDTO;
import run.halo.app.model.dto.post.BasePostSimpleDTO;
import run.halo.app.model.entity.Sheet;
import run.halo.app.model.entity.SheetComment;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.SheetCommentParam;
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.*;
import run.halo.app.service.OptionService;
import run.halo.app.service.SheetCommentService;
import run.halo.app.service.SheetService;
@ -35,7 +31,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* @author ryanwang
* @date 19-4-26
*/
@RestController("PortalSheetController")
@RestController("ApiContentSheetController")
@RequestMapping("/api/content/sheets")
public class SheetController {
@ -53,17 +49,17 @@ public class SheetController {
@GetMapping
@ApiOperation("Lists sheets")
public Page<BasePostSimpleDTO> pageBy(@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
public Page<SheetListVO> pageBy(@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
Page<Sheet> sheetPage = sheetService.pageBy(PostStatus.PUBLISHED, pageable);
return sheetService.convertToSimple(sheetPage);
return sheetService.convertToListVo(sheetPage);
}
@GetMapping("{sheetId:\\d+}")
@ApiOperation("Gets a sheet")
public BasePostDetailDTO getBy(@PathVariable("sheetId") Integer sheetId,
@RequestParam(value = "formatDisabled", required = false, defaultValue = "true") Boolean formatDisabled,
@RequestParam(value = "sourceDisabled", required = false, defaultValue = "false") Boolean sourceDisabled) {
BasePostDetailDTO sheetDetailVO = sheetService.convertToDetail(sheetService.getById(sheetId));
public SheetDetailVO getBy(@PathVariable("sheetId") Integer sheetId,
@RequestParam(value = "formatDisabled", required = false, defaultValue = "true") Boolean formatDisabled,
@RequestParam(value = "sourceDisabled", required = false, defaultValue = "false") Boolean sourceDisabled) {
SheetDetailVO sheetDetailVO = sheetService.convertToDetailVo(sheetService.getById(sheetId));
if (formatDisabled) {
// Clear the format content

View File

@ -0,0 +1,38 @@
package run.halo.app.controller.content.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.model.dto.StatisticDTO;
import run.halo.app.model.dto.StatisticWithUserDTO;
import run.halo.app.service.StatisticService;
/**
* Statistic controller.
*
* @author ryan0up
* @date 2019-12-16
*/
@RestController("ApiContentStatisticController")
@RequestMapping("/api/content/statistics")
public class StatisticController {
private final StatisticService statisticService;
public StatisticController(StatisticService statisticService) {
this.statisticService = statisticService;
}
@GetMapping
@ApiOperation("Gets blog statistics.")
public StatisticDTO statistics() {
return statisticService.getStatistic();
}
@GetMapping("user")
@ApiOperation("Gets blog statistics with user")
public StatisticWithUserDTO statisticsWithUser() {
return statisticService.getStatisticWithUser();
}
}

View File

@ -1,5 +1,6 @@
package run.halo.app.controller.core;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
@ -28,7 +29,7 @@ import java.util.Map;
* Error page Controller
*
* @author ryanwang
* @date : 2017/12/26
* @date 2017-12-26
*/
@Slf4j
@Controller
@ -43,6 +44,8 @@ public class CommonController extends AbstractErrorController {
private static final String DEFAULT_ERROR_PATH = "common/error/error";
private static final String COULD_NOT_RESOLVE_VIEW_WITH_NAME_PREFIX = "Could not resolve view with name '";
private final ThemeService themeService;
private final ErrorProperties errorProperties;
@ -66,9 +69,11 @@ public class CommonController extends AbstractErrorController {
*/
@GetMapping
public String handleError(HttpServletRequest request, HttpServletResponse response, Model model) {
HttpStatus status = getStatus(request);
log.error("Error path: [{}], status: [{}]", getErrorPath(), status);
log.error("Request URL: [{}], URI: [{}], Request Method: [{}], IP: [{}]",
request.getRequestURL(),
request.getRequestURI(),
request.getMethod(),
ServletUtil.getClientIP(request));
handleCustomException(request);
@ -77,6 +82,8 @@ public class CommonController extends AbstractErrorController {
log.debug("Error detail: [{}]", errorDetail);
HttpStatus status = getStatus(request);
if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
return contentInternalError();
} else if (status.equals(HttpStatus.NOT_FOUND)) {
@ -162,7 +169,7 @@ public class CommonController extends AbstractErrorController {
request.setAttribute("javax.servlet.error.exception", rootCause);
request.setAttribute("javax.servlet.error.message", haloException.getMessage());
}
} else if (StringUtils.startsWithIgnoreCase(throwable.getMessage(), "Could not resolve view with name '")) {
} else if (StringUtils.startsWithIgnoreCase(throwable.getMessage(), COULD_NOT_RESOLVE_VIEW_WITH_NAME_PREFIX)) {
request.setAttribute("javax.servlet.error.status_code", HttpStatus.NOT_FOUND.value());
NotFoundException viewNotFound = new NotFoundException("该路径没有对应的模板");
@ -188,7 +195,7 @@ public class CommonController extends AbstractErrorController {
* @param request the source request
* @return if the stacktrace attribute should be included
*/
protected boolean isIncludeStackTrace(HttpServletRequest request) {
private boolean isIncludeStackTrace(HttpServletRequest request) {
ErrorProperties.IncludeStacktrace include = errorProperties.getIncludeStacktrace();
if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
return true;

View File

@ -7,7 +7,11 @@ 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.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
@ -17,6 +21,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
/**
* @author johnniang
*/
@Aspect
@Component
@Slf4j
@ -75,16 +82,36 @@ public class ControllerLogAop {
private void printResponseLog(HttpServletRequest request, String className, String methodName, Object returnObj, long usage) throws JsonProcessingException {
if (log.isDebugEnabled()) {
String returningData = null;
String returnData = "";
if (returnObj != null) {
if (returnObj.getClass().isAssignableFrom(byte[].class)) {
returningData = "Binary data";
if (returnObj instanceof ResponseEntity) {
ResponseEntity responseEntity = (ResponseEntity) returnObj;
if (responseEntity.getBody() instanceof Resource) {
returnData = "[ BINARY DATA ]";
} else {
returnData = toString(responseEntity.getBody());
}
} else {
returningData = JsonUtils.objectToJson(returnObj);
returnData = toString(returnObj);
}
}
log.debug("{}.{} Response: [{}], usage: [{}]ms", className, methodName, returningData, usage);
log.debug("{}.{} Response: [{}], usage: [{}]ms", className, methodName, returnData, usage);
}
}
@NonNull
private String toString(@NonNull Object obj) throws JsonProcessingException {
Assert.notNull(obj, "Return object must not be null");
String toString = "";
if (obj.getClass().isAssignableFrom(byte[].class) && obj instanceof Resource) {
toString = "[ BINARY DATA ]";
} else {
toString = JsonUtils.objectToJson(obj);
}
return toString;
}
}

View File

@ -1,4 +1,4 @@
package run.halo.app.model.freemarker.method;
package run.halo.app.core.freemarker.method;
import cn.hutool.core.util.RandomUtil;
import freemarker.template.Configuration;
@ -10,12 +10,19 @@ import org.springframework.stereotype.Component;
import java.util.List;
/**
* Freemarker template random method.
*
* @author ryanwang
* @date : 2018/12/21
* @date 2018-12-21
*/
@Component
public class RandomMethod implements TemplateMethodModelEx {
/**
* Constructor.
*
* @param configuration injected by spring.
*/
public RandomMethod(Configuration configuration) {
configuration.setSharedVariable("randomMethod", this);
}
@ -29,6 +36,9 @@ public class RandomMethod implements TemplateMethodModelEx {
*/
@Override
public Object exec(List arguments) throws TemplateModelException {
if (arguments.size() != 2) {
throw new TemplateModelException("Wrong arguments! 2 arguments are needed");
}
SimpleNumber argOne = (SimpleNumber) arguments.get(0);
SimpleNumber argTwo = (SimpleNumber) arguments.get(1);
int start = argOne.getAsNumber().intValue();

View File

@ -1,4 +1,4 @@
package run.halo.app.model.freemarker.tag;
package run.halo.app.core.freemarker.tag;
import freemarker.core.Environment;
import freemarker.template.*;
@ -17,7 +17,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Freemarker custom tag of category.
*
* @author ryanwang
* @date : 2019/3/22
* @date 2019-03-22
*/
@Component
public class CategoryTagDirective implements TemplateDirectiveModel {

View File

@ -1,4 +1,4 @@
package run.halo.app.model.freemarker.tag;
package run.halo.app.core.freemarker.tag;
import freemarker.core.Environment;
import freemarker.template.*;
@ -16,7 +16,7 @@ import java.util.Map;
* Freemarker custom tag of comment.
*
* @author ryanwang
* @date : 2019/3/22
* @date 2019-03-22
*/
@Component
public class CommentTagDirective implements TemplateDirectiveModel {

View File

@ -1,4 +1,4 @@
package run.halo.app.model.freemarker.tag;
package run.halo.app.core.freemarker.tag;
import freemarker.core.Environment;
import freemarker.template.*;
@ -16,7 +16,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Freemarker custom tag of link.
*
* @author ryanwang
* @date : 2019/3/22
* @date 2019-03-22
*/
@Component
public class LinkTagDirective implements TemplateDirectiveModel {

View File

@ -1,4 +1,4 @@
package run.halo.app.model.freemarker.tag;
package run.halo.app.core.freemarker.tag;
import freemarker.core.Environment;
import freemarker.template.*;
@ -16,7 +16,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Freemarker custom tag of menu.
*
* @author ryanwang
* @date : 2019/3/22
* @date 2019-03-22
*/
@Component
public class MenuTagDirective implements TemplateDirectiveModel {
@ -50,6 +50,10 @@ public class MenuTagDirective implements TemplateDirectiveModel {
String team = params.get("team").toString();
env.setVariable("menus", builder.build().wrap(menuService.listByTeam(team, Sort.by(DESC, "priority"))));
break;
case "treeByTeam":
String treeTeam = params.get("team").toString();
env.setVariable("menus", builder.build().wrap(menuService.listByTeamAsTree(treeTeam, Sort.by(DESC, "priority"))));
break;
case "count":
env.setVariable("count", builder.build().wrap(menuService.count()));
break;

View File

@ -1,4 +1,4 @@
package run.halo.app.model.freemarker.tag;
package run.halo.app.core.freemarker.tag;
import freemarker.core.Environment;
import freemarker.template.*;
@ -16,7 +16,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Freemarker custom tag of photo.
*
* @author ryanwang
* @date : 2019/4/21
* @date 2019-04-21
*/
@Component
public class PhotoTagDirective implements TemplateDirectiveModel {

View File

@ -1,4 +1,4 @@
package run.halo.app.model.freemarker.tag;
package run.halo.app.core.freemarker.tag;
import freemarker.core.Environment;
import freemarker.template.*;
@ -16,7 +16,7 @@ import java.util.Map;
* Freemarker custom tag of post.
*
* @author ryanwang
* @date : 2018/4/26
* @date 2018-04-26
*/
@Component
public class PostTagDirective implements TemplateDirectiveModel {
@ -48,7 +48,7 @@ public class PostTagDirective implements TemplateDirectiveModel {
env.setVariable("posts", builder.build().wrap(postService.listLatest(top)));
break;
case "count":
env.setVariable("count", builder.build().wrap(postService.count()));
env.setVariable("count", builder.build().wrap(postService.countByStatus(PostStatus.PUBLISHED)));
break;
case "archiveYear":
env.setVariable("archives", builder.build().wrap(postService.listYearArchives()));
@ -56,14 +56,26 @@ public class PostTagDirective implements TemplateDirectiveModel {
case "archiveMonth":
env.setVariable("archives", builder.build().wrap(postService.listMonthArchives()));
break;
case "archive":
String type = params.get("type").toString();
env.setVariable("archives", builder.build().wrap("year".equals(type) ? postService.listYearArchives() : postService.listMonthArchives()));
break;
case "listByCategoryId":
Integer categoryId = Integer.parseInt(params.get("categoryId").toString());
env.setVariable("posts", builder.build().wrap(postCategoryService.listPostBy(categoryId, PostStatus.PUBLISHED)));
break;
case "listByCategorySlug":
String categorySlug = params.get("categorySlug").toString();
env.setVariable("posts", builder.build().wrap(postCategoryService.listPostBy(categorySlug, PostStatus.PUBLISHED)));
break;
case "listByTagId":
Integer tagId = Integer.parseInt(params.get("tagId").toString());
env.setVariable("posts", builder.build().wrap(postTagService.listPostsBy(tagId, PostStatus.PUBLISHED)));
break;
case "listByTagSlug":
String tagSlug = params.get("tagSlug").toString();
env.setVariable("posts", builder.build().wrap(postTagService.listPostsBy(tagSlug, PostStatus.PUBLISHED)));
break;
default:
break;
}

View File

@ -1,4 +1,4 @@
package run.halo.app.model.freemarker.tag;
package run.halo.app.core.freemarker.tag;
import freemarker.core.Environment;
import freemarker.template.*;
@ -17,7 +17,7 @@ import static org.springframework.data.domain.Sort.Direction.DESC;
* Freemarker custom tag of tag.
*
* @author ryanwang
* @date : 2019/3/22
* @date 2019-03-22
*/
@Component
public class TagTagDirective implements TemplateDirectiveModel {

View File

@ -1,22 +0,0 @@
package run.halo.app.event.comment;
import org.springframework.lang.NonNull;
/**
* PostComment pass event.
*
* @author johnniang
* @date 19-4-23
*/
public class CommentPassEvent extends CommentBaseEvent {
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
* @param commentId comment id
*/
public CommentPassEvent(Object source, @NonNull Long commentId) {
super(source, commentId);
}
}

View File

@ -3,6 +3,7 @@ package run.halo.app.event.logger;
import org.springframework.context.ApplicationEvent;
import run.halo.app.model.enums.LogType;
import run.halo.app.model.params.LogParam;
import run.halo.app.utils.ServletUtils;
import run.halo.app.utils.ValidationUtils;
/**
@ -25,6 +26,9 @@ public class LogEvent extends ApplicationEvent {
// Validate the log param
ValidationUtils.validate(logParam);
// Set ip address
logParam.setIpAddress(ServletUtils.getRequestIp());
this.logParam = logParam;
}

View File

@ -1,5 +1,9 @@
package run.halo.app.event.post;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import run.halo.app.utils.ServiceUtils;
/**
* Post visit event.
*
@ -14,7 +18,8 @@ public class PostVisitEvent extends AbstractVisitEvent {
* @param source the object on which the event initially occurred (never {@code null})
* @param postId post id must not be null
*/
public PostVisitEvent(Object source, Integer postId) {
public PostVisitEvent(Object source, @NonNull Integer postId) {
super(source, postId);
Assert.isTrue(!ServiceUtils.isEmptyId(postId), "Post id must not be empty");
}
}

View File

@ -6,7 +6,7 @@ import org.springframework.stereotype.Component;
/**
* @author ryanwang
* @date : 2019/3/14
* @date 2019-3-14
*/
@Component
public class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {

View File

@ -4,7 +4,6 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.filter.GenericFilterBean;
import run.halo.app.security.filter.AdminAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
@ -14,6 +13,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_HEADER_NAME;
import static run.halo.app.model.support.HaloConst.API_ACCESS_KEY_HEADER_NAME;
/**
* Filter for CORS.
*
@ -21,7 +23,7 @@ import java.io.IOException;
*/
public class CorsFilter extends GenericFilterBean {
private final static String ALLOW_HEADERS = StringUtils.joinWith(",", HttpHeaders.CONTENT_TYPE, AdminAuthenticationFilter.ADMIN_TOKEN_HEADER_NAME);
private final static String ALLOW_HEADERS = StringUtils.joinWith(",", HttpHeaders.CONTENT_TYPE, ADMIN_TOKEN_HEADER_NAME, API_ACCESS_KEY_HEADER_NAME);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

View File

@ -0,0 +1,160 @@
package run.halo.app.handler.file;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.DeleteObjectsRequest;
import com.aliyun.oss.model.PutObjectResult;
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.AliOssProperties;
import run.halo.app.model.support.UploadResult;
import run.halo.app.service.OptionService;
import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.ImageUtils;
import java.awt.image.BufferedImage;
import java.util.Objects;
/**
* Ali oss file handler.
*
* @author MyFaith
* @author ryanwang
* @date 2019-04-04
*/
@Slf4j
@Component
public class AliOssFileHandler implements FileHandler {
private final OptionService optionService;
public AliOssFileHandler(OptionService optionService) {
this.optionService = optionService;
}
@Override
public UploadResult upload(MultipartFile file) {
Assert.notNull(file, "Multipart file must not be null");
// Get config
String protocol = optionService.getByPropertyOfNonNull(AliOssProperties.OSS_PROTOCOL).toString();
String domain = optionService.getByPropertyOrDefault(AliOssProperties.OSS_DOMAIN, String.class, "");
String source = optionService.getByPropertyOrDefault(AliOssProperties.OSS_SOURCE, String.class, "");
String endPoint = optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ENDPOINT).toString();
String accessKey = optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ACCESS_KEY).toString();
String accessSecret = optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ACCESS_SECRET).toString();
String bucketName = optionService.getByPropertyOfNonNull(AliOssProperties.OSS_BUCKET_NAME).toString();
String styleRule = optionService.getByPropertyOrDefault(AliOssProperties.OSS_STYLE_RULE, String.class, "");
String thumbnailStyleRule = optionService.getByPropertyOrDefault(AliOssProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, "");
// Init OSS client
OSS ossClient = new OSSClientBuilder().build(endPoint, accessKey, accessSecret);
StringBuilder basePath = new StringBuilder(protocol);
if (StringUtils.isNotEmpty(domain)) {
basePath.append(domain)
.append("/");
} else {
basePath.append(bucketName)
.append(".")
.append(endPoint)
.append("/");
}
try {
String basename = FilenameUtils.getBasename(file.getOriginalFilename());
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
String timestamp = String.valueOf(System.currentTimeMillis());
StringBuilder upFilePath = new StringBuilder();
if (StringUtils.isNotEmpty(source)) {
upFilePath.append(source)
.append("/");
}
upFilePath.append(basename)
.append("_")
.append(timestamp)
.append(".")
.append(extension);
String filePath = StringUtils.join(basePath.toString(), upFilePath.toString());
log.info(basePath.toString());
// Upload
PutObjectResult putObjectResult = ossClient.putObject(bucketName, upFilePath.toString(), file.getInputStream());
if (putObjectResult == null) {
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到阿里云失败 ");
}
// Response result
UploadResult uploadResult = new UploadResult();
uploadResult.setFilename(basename);
uploadResult.setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule);
uploadResult.setKey(upFilePath.toString());
uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType())));
uploadResult.setSuffix(extension);
uploadResult.setSize(file.getSize());
// Handle thumbnail
if (FileHandler.isImageType(uploadResult.getMediaType())) {
BufferedImage image = ImageUtils.getImageFromFile(file.getInputStream(), extension);
uploadResult.setWidth(image.getWidth());
uploadResult.setHeight(image.getHeight());
if (ImageUtils.EXTENSION_ICO.equals(extension)) {
uploadResult.setThumbPath(filePath);
} else {
uploadResult.setThumbPath(StringUtils.isBlank(thumbnailStyleRule) ? filePath : filePath + thumbnailStyleRule);
}
}
return uploadResult;
} catch (Exception e) {
e.printStackTrace();
} finally {
ossClient.shutdown();
}
// Build result
UploadResult result = new UploadResult();
log.info("File: [{}] uploaded successfully", file.getOriginalFilename());
return result;
}
@Override
public void delete(String key) {
Assert.notNull(key, "File key must not be blank");
// Get config
String endPoint = optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ENDPOINT).toString();
String accessKey = optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ACCESS_KEY).toString();
String accessSecret = optionService.getByPropertyOfNonNull(AliOssProperties.OSS_ACCESS_SECRET).toString();
String bucketName = optionService.getByPropertyOfNonNull(AliOssProperties.OSS_BUCKET_NAME).toString();
// Init OSS client
OSS ossClient = new OSSClientBuilder().build(endPoint, accessKey, accessSecret);
try {
ossClient.deleteObject(new DeleteObjectsRequest(bucketName).withKey(key));
} catch (Exception e) {
throw new FileOperationException("附件 " + key + " 从阿里云删除失败", e);
} finally {
ossClient.shutdown();
}
}
@Override
public boolean supportType(AttachmentType type) {
return AttachmentType.ALIOSS.equals(type);
}
}

View File

@ -1,129 +0,0 @@
package run.halo.app.handler.file;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.DeleteObjectsRequest;
import com.aliyun.oss.model.PutObjectResult;
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.AliYunProperties;
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;
/**
* AliYun file handler.
*
* @author MyFaith
* @author ryanwang
* @date 2019-04-04 00:06:13
*/
@Slf4j
@Component
public class AliYunFileHandler implements FileHandler {
private final OptionService optionService;
public AliYunFileHandler(OptionService optionService) {
this.optionService = optionService;
}
@Override
public UploadResult upload(MultipartFile file) {
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 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);
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(ossDomain) ? ossDomain : ossSource, "/"), upFilePath);
// Upload
PutObjectResult putObjectResult = ossClient.putObject(ossBucketName, upFilePath, file.getInputStream());
if (putObjectResult == null) {
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到阿里云失败 ");
}
// Response result
UploadResult uploadResult = new UploadResult();
uploadResult.setFilename(basename);
uploadResult.setFilePath(StringUtils.isBlank(ossStyleRule) ? filePath : filePath + ossStyleRule);
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(ossThumbnailStyleRule) ? filePath : filePath + ossThumbnailStyleRule);
}
return uploadResult;
} catch (Exception e) {
e.printStackTrace();
} finally {
ossClient.shutdown();
}
// Build result
UploadResult result = new UploadResult();
log.info("File: [{}] uploaded successfully", file.getOriginalFilename());
return result;
}
@Override
public void delete(String key) {
Assert.notNull(key, "File key must not be blank");
// Get config
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();
// Init OSS client
OSS ossClient = new OSSClientBuilder().build(ossEndPoint, ossAccessKey, ossAccessSecret);
try {
ossClient.deleteObject(new DeleteObjectsRequest(ossBucketName).withKey(key));
} catch (Exception e) {
throw new FileOperationException("附件 " + key + " 从阿里云删除失败", e);
} finally {
ossClient.shutdown();
}
}
@Override
public boolean supportType(AttachmentType type) {
return AttachmentType.ALIYUN.equals(type);
}
}

View File

@ -13,28 +13,29 @@ 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.properties.BaiduBosProperties;
import run.halo.app.model.support.UploadResult;
import run.halo.app.service.OptionService;
import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.ImageUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.util.Objects;
/**
* BaiDuYun file handler.
* Baidu bos file handler.
*
* @author wangya
* @author ryanwang
* @date 2019-07-20
*/
@Slf4j
@Component
public class BaiDuYunFileHandler implements FileHandler {
public class BaiduBosFileHandler implements FileHandler {
private final OptionService optionService;
public BaiDuYunFileHandler(OptionService optionService) {
public BaiduBosFileHandler(OptionService optionService) {
this.optionService = optionService;
}
@ -43,31 +44,34 @@ public class BaiDuYunFileHandler implements FileHandler {
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);
Object protocol = optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_PROTOCOL);
String domain = optionService.getByPropertyOrDefault(BaiduBosProperties.BOS_DOMAIN, String.class, "");
String endPoint = optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_ENDPOINT).toString();
String accessKey = optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_ACCESS_KEY).toString();
String secretKey = optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_SECRET_KEY).toString();
String bucketName = optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_BUCKET_NAME).toString();
String styleRule = optionService.getByPropertyOrDefault(BaiduBosProperties.BOS_STYLE_RULE, String.class, "");
String thumbnailStyleRule = optionService.getByPropertyOrDefault(BaiduBosProperties.BOS_THUMBNAIL_STYLE_RULE, String.class, "");
String source = StringUtils.join(protocol, bucketName, "." + endPoint);
BosClientConfiguration config = new BosClientConfiguration();
config.setCredentials(new DefaultBceCredentials(bosAccessKey, bosSecretKey));
config.setEndpoint(bosEndPoint);
config.setCredentials(new DefaultBceCredentials(accessKey, secretKey));
config.setEndpoint(endPoint);
// Init OSS client
BosClient client = new BosClient(config);
domain = protocol + domain;
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);
String filePath = StringUtils.join(StringUtils.appendIfMissing(StringUtils.isNotBlank(domain) ? domain : source, "/"), upFilePath);
// Upload
PutObjectResponse putObjectResponseFromInputStream = client.putObject(bosBucketName, upFilePath, file.getInputStream());
PutObjectResponse putObjectResponseFromInputStream = client.putObject(bucketName, upFilePath, file.getInputStream());
if (putObjectResponseFromInputStream == null) {
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到百度云失败 ");
}
@ -75,7 +79,7 @@ public class BaiDuYunFileHandler implements FileHandler {
// Response result
UploadResult uploadResult = new UploadResult();
uploadResult.setFilename(basename);
uploadResult.setFilePath(StringUtils.isBlank(bosStyleRule) ? filePath : filePath + bosStyleRule);
uploadResult.setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule);
uploadResult.setKey(upFilePath);
uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType())));
uploadResult.setSuffix(extension);
@ -83,10 +87,14 @@ public class BaiDuYunFileHandler implements FileHandler {
// Handle thumbnail
if (FileHandler.isImageType(uploadResult.getMediaType())) {
BufferedImage image = ImageIO.read(file.getInputStream());
BufferedImage image = ImageUtils.getImageFromFile(file.getInputStream(), extension);
uploadResult.setWidth(image.getWidth());
uploadResult.setHeight(image.getHeight());
uploadResult.setThumbPath(StringUtils.isBlank(bosThumbnailStyleRule) ? filePath : filePath + bosThumbnailStyleRule);
if (ImageUtils.EXTENSION_ICO.equals(extension)) {
uploadResult.setThumbPath(filePath);
} else {
uploadResult.setThumbPath(StringUtils.isBlank(thumbnailStyleRule) ? filePath : filePath + thumbnailStyleRule);
}
}
return uploadResult;
@ -102,20 +110,20 @@ public class BaiDuYunFileHandler implements FileHandler {
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();
String endPoint = optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_ENDPOINT).toString();
String accessKey = optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_ACCESS_KEY).toString();
String secretKey = optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_SECRET_KEY).toString();
String bucketName = optionService.getByPropertyOfNonNull(BaiduBosProperties.BOS_BUCKET_NAME).toString();
BosClientConfiguration config = new BosClientConfiguration();
config.setCredentials(new DefaultBceCredentials(bosAccessKey, bosSecretKey));
config.setEndpoint(bosEndPoint);
config.setCredentials(new DefaultBceCredentials(accessKey, secretKey));
config.setEndpoint(endPoint);
// Init OSS client
BosClient client = new BosClient(config);
try {
client.deleteObject(bosBucketName, key);
client.deleteObject(bucketName, key);
} catch (Exception e) {
throw new FileOperationException("附件 " + key + " 从百度云删除失败", e);
} finally {
@ -125,6 +133,6 @@ public class BaiDuYunFileHandler implements FileHandler {
@Override
public boolean supportType(AttachmentType type) {
return AttachmentType.BAIDUYUN.equals(type);
return AttachmentType.BAIDUBOS.equals(type);
}
}

View File

@ -16,7 +16,7 @@ import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
* File handler interface.
*
* @author johnniang
* @date 3/27/19
* @date 2019-03-27
*/
public interface FileHandler {

View File

@ -20,7 +20,7 @@ import java.util.LinkedList;
* File handler manager.
*
* @author johnniang
* @date 3/27/19
* @date 2019-03-27
*/
@Slf4j
@Component

View File

@ -14,9 +14,10 @@ import run.halo.app.model.support.UploadResult;
import run.halo.app.service.OptionService;
import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.HaloUtils;
import run.halo.app.utils.ImageUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@ -54,12 +55,9 @@ public class LocalFileHandler implements FileHandler {
* Thumbnail height.
*/
private final static int THUMB_HEIGHT = 256;
ReentrantLock lock = new ReentrantLock();
private final OptionService optionService;
private final String workDir;
ReentrantLock lock = new ReentrantLock();
public LocalFileHandler(OptionService optionService,
HaloProperties haloProperties) {
@ -151,7 +149,7 @@ public class LocalFileHandler implements FileHandler {
Path thumbnailPath = Paths.get(workDir + thumbnailSubFilePath);
// Read as image
BufferedImage originalImage = ImageIO.read(uploadPath.toFile());
BufferedImage originalImage = ImageUtils.getImageFromFile(new FileInputStream(uploadPath.toFile()), extension);
// Set width and height
uploadResult.setWidth(originalImage.getWidth());
uploadResult.setHeight(originalImage.getHeight());
@ -238,5 +236,4 @@ public class LocalFileHandler implements FileHandler {
}
return result;
}
}

View File

@ -17,11 +17,12 @@ 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.QnYunProperties;
import run.halo.app.model.properties.QiniuOssProperties;
import run.halo.app.model.support.QiNiuPutSet;
import run.halo.app.model.support.UploadResult;
import run.halo.app.service.OptionService;
import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.ImageUtils;
import run.halo.app.utils.JsonUtils;
import java.io.IOException;
@ -32,19 +33,19 @@ import java.util.Objects;
import static run.halo.app.handler.file.FileHandler.isImageType;
/**
* Qi niu yun file handler.
* Qiniu oss file handler.
*
* @author johnniang
* @author ryanwang
* @date 3/27/19
* @date 2019-03-27
*/
@Slf4j
@Component
public class QnYunFileHandler implements FileHandler {
public class QiniuOssFileHandler implements FileHandler {
private final OptionService optionService;
public QnYunFileHandler(OptionService optionService) {
public QiniuOssFileHandler(OptionService optionService) {
this.optionService = optionService;
}
@ -54,12 +55,14 @@ public class QnYunFileHandler implements FileHandler {
// Get all config
Zone zone = optionService.getQnYunZone();
String accessKey = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_ACCESS_KEY).toString();
String secretKey = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_SECRET_KEY).toString();
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, "");
String accessKey = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_ACCESS_KEY).toString();
String secretKey = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_SECRET_KEY).toString();
String bucket = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_BUCKET).toString();
String protocol = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_PROTOCOL).toString();
String domain = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_DOMAIN).toString();
String source = optionService.getByPropertyOrDefault(QiniuOssProperties.OSS_SOURCE, String.class, "");
String styleRule = optionService.getByPropertyOrDefault(QiniuOssProperties.OSS_STYLE_RULE, String.class, "");
String thumbnailStyleRule = optionService.getByPropertyOrDefault(QiniuOssProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, "");
// Create configuration
Configuration configuration = new Configuration(zone);
@ -79,16 +82,31 @@ public class QnYunFileHandler implements FileHandler {
// Create temp path
Path tmpPath = Paths.get(System.getProperty("java.io.tmpdir"), bucket);
StringBuilder basePath = new StringBuilder(protocol)
.append(domain)
.append("/");
try {
String basename = FilenameUtils.getBasename(file.getOriginalFilename());
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
String timestamp = String.valueOf(System.currentTimeMillis());
StringBuilder upFilePath = new StringBuilder();
if (StringUtils.isNotEmpty(source)) {
upFilePath.append(source)
.append("/");
}
upFilePath.append(basename)
.append("_")
.append(timestamp)
.append(".")
.append(extension);
// Get file recorder for temp directory
FileRecorder fileRecorder = new FileRecorder(tmpPath.toFile());
// Get upload manager
UploadManager uploadManager = new UploadManager(configuration, fileRecorder);
// Put the file
Response response = uploadManager.put(file.getInputStream(), null, uploadToken, null, null);
Response response = uploadManager.put(file.getInputStream(), upFilePath.toString(), uploadToken, null, null);
log.debug("QnYun response: [{}]", response.toString());
log.debug("QnYun response body: [{}]", response.bodyString());
@ -99,13 +117,13 @@ public class QnYunFileHandler implements FileHandler {
QiNiuPutSet putSet = JsonUtils.jsonToObject(response.bodyString(), QiNiuPutSet.class);
// Get file full path
String filePath = StringUtils.appendIfMissing(domain, "/") + putSet.getHash();
String filePath = StringUtils.join(basePath.toString(), upFilePath.toString());
// Build upload result
UploadResult result = new UploadResult();
result.setFilename(basename);
result.setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule);
result.setKey(putSet.getKey());
result.setKey(upFilePath.toString());
result.setSuffix(extension);
result.setWidth(putSet.getWidth());
result.setHeight(putSet.getHeight());
@ -113,7 +131,11 @@ public class QnYunFileHandler implements FileHandler {
result.setSize(file.getSize());
if (isImageType(result.getMediaType())) {
result.setThumbPath(StringUtils.isBlank(thumbnailStyleRule) ? filePath : filePath + thumbnailStyleRule);
if (ImageUtils.EXTENSION_ICO.equals(extension)) {
result.setThumbPath(filePath);
} else {
result.setThumbPath(StringUtils.isBlank(thumbnailStyleRule) ? filePath : filePath + thumbnailStyleRule);
}
}
return result;
@ -132,9 +154,9 @@ public class QnYunFileHandler implements FileHandler {
// Get all config
Zone zone = optionService.getQnYunZone();
String accessKey = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_ACCESS_KEY).toString();
String secretKey = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_SECRET_KEY).toString();
String bucket = optionService.getByPropertyOfNonNull(QnYunProperties.OSS_BUCKET).toString();
String accessKey = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_ACCESS_KEY).toString();
String secretKey = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_SECRET_KEY).toString();
String bucket = optionService.getByPropertyOfNonNull(QiniuOssProperties.OSS_BUCKET).toString();
// Create configuration
Configuration configuration = new Configuration(zone);
@ -154,6 +176,6 @@ public class QnYunFileHandler implements FileHandler {
@Override
public boolean supportType(AttachmentType type) {
return AttachmentType.QNYUN.equals(type);
return AttachmentType.QINIUOSS.equals(type);
}
}

View File

@ -5,6 +5,7 @@ import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@ -22,13 +23,15 @@ import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.HttpClientUtils;
import java.io.IOException;
import java.util.Collections;
import java.util.Objects;
/**
* Sm.ms file handler.
*
* @author johnniang
* @date 3/29/19
* @author ryanwang
* @date 2019-03-29
*/
@Slf4j
@Component
@ -46,8 +49,6 @@ public class SmmsFileHandler implements FileHandler {
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;
private final OptionService optionService;
@ -56,6 +57,10 @@ public class SmmsFileHandler implements FileHandler {
OptionService optionService) {
this.httpsRestTemplate = httpsRestTemplate;
this.optionService = optionService;
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
this.httpsRestTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
}
@Override
@ -76,8 +81,6 @@ public class SmmsFileHandler implements FileHandler {
HttpHeaders headers = new HttpHeaders();
// Set content type
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<>();
@ -89,7 +92,6 @@ public class SmmsFileHandler implements FileHandler {
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到 SM.MS 失败", e);
}
body.add("ssl", false);
body.add("format", "json");
HttpEntity<LinkedMultiValueMap<String, Object>> httpEntity = new HttpEntity<>(body, headers);
@ -109,7 +111,11 @@ public class SmmsFileHandler implements FileHandler {
// Check error
if (!isResponseSuccessfully(smmsResponse)) {
log.error("Smms response detail: [{}]", smmsResponse);
throw new FileOperationException(smmsResponse == null ? "SM.MS 服务返回内容为空" : smmsResponse.getMsg()).setErrorData(smmsResponse);
throw new FileOperationException(smmsResponse == null ? "SM.MS 服务返回内容为空" : smmsResponse.getMessage()).setErrorData(smmsResponse);
}
if (smmsResponse.getSuccess()) {
throw new FileOperationException("上传请求失败:" + smmsResponse.getMessage()).setErrorData(smmsResponse);
}
// Get response data
@ -142,7 +148,6 @@ public class SmmsFileHandler implements FileHandler {
// Set user agent manually
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.USER_AGENT, DEFAULT_USER_AGENT);
// Delete the file
ResponseEntity<String> responseEntity = httpsRestTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, headers), String.class);
@ -178,12 +183,15 @@ public class SmmsFileHandler implements FileHandler {
@NoArgsConstructor
private static class SmmsResponse {
private Boolean success;
private String code;
private String msg;
private String message;
private SmmsResponseData data;
private String RequestId;
}
@Data
@ -191,23 +199,24 @@ public class SmmsFileHandler implements FileHandler {
@NoArgsConstructor
private static class SmmsResponseData {
private Integer width;
private Integer height;
private String filename;
private String storename;
private Integer size;
private Integer width;
private Integer height;
private String path;
private String hash;
private String delete;
private String url;
private String path;
private String delete;
private String page;
}
}

View File

@ -0,0 +1,169 @@
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.TencentCosProperties;
import run.halo.app.model.support.UploadResult;
import run.halo.app.service.OptionService;
import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.ImageUtils;
import java.awt.image.BufferedImage;
import java.util.Objects;
/**
* Tencent cos file handler.
*
* @author wangya
* @author ryanwang
* @date 2019-07-25
*/
@Slf4j
@Component
public class TencentCosFileHandler implements FileHandler {
private final OptionService optionService;
public TencentCosFileHandler(OptionService optionService) {
this.optionService = optionService;
}
@Override
public UploadResult upload(MultipartFile file) {
Assert.notNull(file, "Multipart file must not be null");
// Get config
String protocol = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_PROTOCOL).toString();
String domain = optionService.getByPropertyOrDefault(TencentCosProperties.COS_DOMAIN, String.class, "");
String region = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_REGION).toString();
String secretId = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_SECRET_ID).toString();
String secretKey = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_SECRET_KEY).toString();
String bucketName = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_BUCKET_NAME).toString();
String source = optionService.getByPropertyOrDefault(TencentCosProperties.COS_SOURCE, String.class, "");
String styleRule = optionService.getByPropertyOrDefault(TencentCosProperties.COS_STYLE_RULE, String.class, "");
String thumbnailStyleRule = optionService.getByPropertyOrDefault(TencentCosProperties.COS_THUMBNAIL_STYLE_RULE, String.class, "");
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
Region regionConfig = new Region(region);
ClientConfig clientConfig = new ClientConfig(regionConfig);
// Init OSS client
COSClient cosClient = new COSClient(cred, clientConfig);
StringBuilder basePath = new StringBuilder(protocol);
if (StringUtils.isNotEmpty(domain)) {
basePath.append(domain)
.append("/");
} else {
basePath.append(bucketName)
.append(".cos.")
.append(region)
.append(".myqcloud.com")
.append("/");
}
try {
String basename = FilenameUtils.getBasename(file.getOriginalFilename());
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
String timestamp = String.valueOf(System.currentTimeMillis());
StringBuilder upFilePath = new StringBuilder();
if (StringUtils.isNotEmpty(source)) {
upFilePath.append(source)
.append("/");
}
upFilePath.append(basename)
.append("_")
.append(timestamp)
.append(".")
.append(extension);
String filePath = StringUtils.join(basePath.toString(), upFilePath.toString());
// Upload
ObjectMetadata objectMetadata = new ObjectMetadata();
//提前告知输入流的长度, 否则可能导致 oom
objectMetadata.setContentLength(file.getSize());
// 设置 Content type, 默认是 application/octet-stream
objectMetadata.setContentType(file.getContentType());
PutObjectResult putObjectResponseFromInputStream = cosClient.putObject(bucketName, upFilePath.toString(), file.getInputStream(), objectMetadata);
if (putObjectResponseFromInputStream == null) {
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到腾讯云失败 ");
}
// Response result
UploadResult uploadResult = new UploadResult();
uploadResult.setFilename(basename);
uploadResult.setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule);
uploadResult.setKey(upFilePath.toString());
uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType())));
uploadResult.setSuffix(extension);
uploadResult.setSize(file.getSize());
// Handle thumbnail
if (FileHandler.isImageType(uploadResult.getMediaType())) {
BufferedImage image = ImageUtils.getImageFromFile(file.getInputStream(), extension);
uploadResult.setWidth(image.getWidth());
uploadResult.setHeight(image.getHeight());
if (ImageUtils.EXTENSION_ICO.equals(extension)) {
uploadResult.setThumbPath(filePath);
} else {
uploadResult.setThumbPath(StringUtils.isBlank(thumbnailStyleRule) ? filePath : filePath + thumbnailStyleRule);
}
}
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 region = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_REGION).toString();
String secretId = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_SECRET_ID).toString();
String secretKey = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_SECRET_KEY).toString();
String bucketName = optionService.getByPropertyOfNonNull(TencentCosProperties.COS_BUCKET_NAME).toString();
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
Region regionConfig = new Region(region);
ClientConfig clientConfig = new ClientConfig(regionConfig);
// Init OSS client
COSClient cosClient = new COSClient(cred, clientConfig);
try {
cosClient.deleteObject(bucketName, key);
} catch (Exception e) {
throw new FileOperationException("附件 " + key + " 从腾讯云删除失败", e);
} finally {
cosClient.shutdown();
}
}
@Override
public boolean supportType(AttachmentType type) {
return AttachmentType.TENCENTCOS.equals(type);
}
}

View File

@ -1,143 +0,0 @@
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

@ -10,29 +10,29 @@ import org.springframework.util.DigestUtils;
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.UpYunProperties;
import run.halo.app.model.properties.UpOssProperties;
import run.halo.app.model.support.UploadResult;
import run.halo.app.service.OptionService;
import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.ImageUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.util.Objects;
/**
* Up Yun file handler.
* Up oss file handler.
*
* @author johnniang
* @author ryanwang
* @date 3/27/19
* @date 2019-03-27
*/
@Slf4j
@Component
public class UpYunFileHandler implements FileHandler {
public class UpOssFileHandler implements FileHandler {
private final OptionService optionService;
public UpYunFileHandler(OptionService optionService) {
public UpOssFileHandler(OptionService optionService) {
this.optionService = optionService;
}
@ -40,17 +40,18 @@ public class UpYunFileHandler implements FileHandler {
public UploadResult upload(MultipartFile file) {
Assert.notNull(file, "Multipart file must not be null");
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 ossDomain = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_DOMAIN).toString();
String ossOperator = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_OPERATOR).toString();
String source = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_SOURCE).toString();
String password = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_PASSWORD).toString();
String bucket = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_BUCKET).toString();
String protocol = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_PROTOCOL).toString();
String domain = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_DOMAIN).toString();
String operator = optionService.getByPropertyOfNonNull(UpOssProperties.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, "");
String styleRule = optionService.getByPropertyOrDefault(UpOssProperties.OSS_STYLE_RULE, String.class, "");
String thumbnailStyleRule = optionService.getByPropertyOrDefault(UpOssProperties.OSS_THUMBNAIL_STYLE_RULE, String.class, "");
// Create up yun
UpYun upYun = new UpYun(ossBucket, ossOperator, ossPassword);
UpYun upYun = new UpYun(bucket, operator, password);
upYun.setDebug(log.isDebugEnabled());
upYun.setTimeout(60);
upYun.setApiDomain(UpYun.ED_AUTO);
@ -63,7 +64,7 @@ public class UpYunFileHandler implements FileHandler {
// Get md5 value of the file
String md5OfFile = DigestUtils.md5DigestAsHex(file.getInputStream());
// Build file path
String upFilePath = StringUtils.appendIfMissing(ossSource, "/") + md5OfFile + '.' + extension;
String upFilePath = StringUtils.appendIfMissing(source, "/") + md5OfFile + '.' + extension;
// Set md5Content
upYun.setContentMD5(md5OfFile);
// Write file
@ -72,12 +73,12 @@ public class UpYunFileHandler implements FileHandler {
throw new FileOperationException("上传附件 " + file.getOriginalFilename() + " 到又拍云失败" + upFilePath);
}
String filePath = StringUtils.removeEnd(ossDomain, "/") + upFilePath;
String filePath = protocol + StringUtils.removeEnd(domain, "/") + upFilePath;
// Build upload result
UploadResult uploadResult = new UploadResult();
uploadResult.setFilename(basename);
uploadResult.setFilePath(StringUtils.isBlank(ossStyleRule) ? filePath : filePath + ossStyleRule);
uploadResult.setFilePath(StringUtils.isBlank(styleRule) ? filePath : filePath + styleRule);
uploadResult.setKey(upFilePath);
uploadResult.setMediaType(MediaType.valueOf(Objects.requireNonNull(file.getContentType())));
uploadResult.setSuffix(extension);
@ -85,10 +86,14 @@ public class UpYunFileHandler implements FileHandler {
// Handle thumbnail
if (FileHandler.isImageType(uploadResult.getMediaType())) {
BufferedImage image = ImageIO.read(file.getInputStream());
BufferedImage image = ImageUtils.getImageFromFile(file.getInputStream(), extension);
uploadResult.setWidth(image.getWidth());
uploadResult.setHeight(image.getHeight());
uploadResult.setThumbPath(StringUtils.isBlank(ossThumbnailStyleRule) ? filePath : filePath + ossThumbnailStyleRule);
if (ImageUtils.EXTENSION_ICO.equals(extension)) {
uploadResult.setThumbPath(filePath);
} else {
uploadResult.setThumbPath(StringUtils.isBlank(thumbnailStyleRule) ? filePath : filePath + thumbnailStyleRule);
}
}
return uploadResult;
@ -102,12 +107,12 @@ public class UpYunFileHandler implements FileHandler {
Assert.notNull(key, "File key must not be blank");
// Get config
String ossPassword = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_PASSWORD).toString();
String ossBucket = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_BUCKET).toString();
String ossOperator = optionService.getByPropertyOfNonNull(UpYunProperties.OSS_OPERATOR).toString();
String password = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_PASSWORD).toString();
String bucket = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_BUCKET).toString();
String operator = optionService.getByPropertyOfNonNull(UpOssProperties.OSS_OPERATOR).toString();
// Create up yun
UpYun upYun = new UpYun(ossBucket, ossOperator, ossPassword);
UpYun upYun = new UpYun(bucket, operator, password);
// Set api domain with ED_AUTO
upYun.setApiDomain(UpYun.ED_AUTO);
@ -124,6 +129,6 @@ public class UpYunFileHandler implements FileHandler {
@Override
public boolean supportType(AttachmentType type) {
return AttachmentType.UPYUN.equals(type);
return AttachmentType.UPOSS.equals(type);
}
}

View File

@ -0,0 +1,23 @@
package run.halo.app.handler.migrate;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.model.enums.MigrateType;
/**
* Cnblogs(https://cnblogs.com) migrate handler.
*
* @author ryanwang
* @date 2019-10-30
*/
public class CnBlogsMigrateHandler implements MigrateHandler {
@Override
public void migrate(MultipartFile file) {
// TODO
}
@Override
public boolean supportType(MigrateType type) {
return MigrateType.CNBLOGS.equals(type);
}
}

View File

@ -0,0 +1,30 @@
package run.halo.app.handler.migrate;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.model.enums.MigrateType;
/**
* Migrate handler interface.
*
* @author ryanwang
* @date 2019-10-28
*/
public interface MigrateHandler {
/**
* Migrate
*
* @param file multipart file must not be null
*/
void migrate(@NonNull MultipartFile file);
/**
* Checks if the given type is supported.
*
* @param type migrate type
* @return true if supported; false or else
*/
boolean supportType(@Nullable MigrateType type);
}

View File

@ -0,0 +1,65 @@
package run.halo.app.handler.migrate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.exception.FileOperationException;
import run.halo.app.model.enums.MigrateType;
import java.util.Collection;
import java.util.LinkedList;
/**
* Migrate handler manager.
*
* @author ryanwang
* @date 2019-10-28
*/
@Slf4j
@Component
public class MigrateHandlers {
/**
* Migrate handler container.
*/
private final Collection<MigrateHandler> migrateHandlers = new LinkedList<>();
public MigrateHandlers(ApplicationContext applicationContext) {
// Add all migrate handler
addFileHandlers(applicationContext.getBeansOfType(MigrateHandler.class).values());
}
@NonNull
public void upload(@NonNull MultipartFile file, @NonNull MigrateType migrateType) {
Assert.notNull(file, "Multipart file must not be null");
Assert.notNull(migrateType, "Migrate type must not be null");
for (MigrateHandler migrateHandler : migrateHandlers) {
if (migrateHandler.supportType(migrateType)) {
migrateHandler.migrate(file);
return;
}
}
throw new FileOperationException("No available migrate handler to migrate the file").setErrorData(migrateType);
}
/**
* Adds migrate handlers.
*
* @param migrateHandlers migrate handler collection
* @return current migrate handlers
*/
@NonNull
private MigrateHandlers addFileHandlers(@Nullable Collection<MigrateHandler> migrateHandlers) {
if (!CollectionUtils.isEmpty(migrateHandlers)) {
this.migrateHandlers.addAll(migrateHandlers);
}
return this;
}
}

View File

@ -0,0 +1,693 @@
package run.halo.app.handler.migrate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.entity.*;
import run.halo.app.model.enums.AttachmentType;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.enums.MigrateType;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.service.*;
import run.halo.app.utils.BeanUtils;
import run.halo.app.utils.JsonUtils;
import run.halo.app.utils.ServiceUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* Old version(0.4.4) migrate handler
*
* @author ryanwang
* @author johnniang
* @date 2019-10-28
*/
@Slf4j
@Component
@SuppressWarnings("unchecked")
public class OldVersionMigrateHandler implements MigrateHandler {
private final AttachmentService attachmentService;
private final PostService postService;
private final LinkService linkService;
private final MenuService menuService;
private final CategoryService categoryService;
private final TagService tagService;
private final PostCommentService postCommentService;
private final SheetCommentService sheetCommentService;
private final SheetService sheetService;
private final PhotoService photoService;
private final PostCategoryService postCategoryService;
private final PostTagService postTagService;
public OldVersionMigrateHandler(AttachmentService attachmentService,
PostService postService,
LinkService linkService,
MenuService menuService,
CategoryService categoryService,
TagService tagService,
PostCommentService postCommentService,
SheetCommentService sheetCommentService,
SheetService sheetService,
PhotoService photoService,
PostCategoryService postCategoryService,
PostTagService postTagService) {
this.attachmentService = attachmentService;
this.postService = postService;
this.linkService = linkService;
this.menuService = menuService;
this.categoryService = categoryService;
this.tagService = tagService;
this.postCommentService = postCommentService;
this.sheetCommentService = sheetCommentService;
this.sheetService = sheetService;
this.photoService = photoService;
this.postCategoryService = postCategoryService;
this.postTagService = postTagService;
}
@Override
public void migrate(MultipartFile file) {
// Get migration content
try {
String migrationContent = FileCopyUtils.copyToString(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8));
Object migrationObject = JsonUtils.jsonToObject(migrationContent, Object.class);
if (migrationObject instanceof Map) {
Map<String, Object> migrationMap = (Map<String, Object>) migrationObject;
// Handle attachments
List<Attachment> attachments = handleAttachments(migrationMap.get("attachments"));
log.debug("Migrated attachments: [{}]", attachments);
// Handle links
List<Link> links = handleLinks(migrationMap.get("links"));
log.debug("Migrated links: [{}]", links);
// Handle galleries
List<Photo> photos = handleGalleries(migrationMap.get("galleries"));
log.debug("Migrated photos: [{}]", photos);
// Handle menus
List<Menu> menus = handleMenus(migrationMap.get("menus"));
log.debug("Migrated menus: [{}]", menus);
// Handle posts
List<BasePost> posts = handleBasePosts(migrationMap.get("posts"));
log.debug("Migrated posts: [{}]", posts);
}
} catch (IOException e) {
throw new ServiceException("备份文件 " + file.getOriginalFilename() + " 读取失败", e);
}
}
@NonNull
private List<BasePost> handleBasePosts(@Nullable Object postsObject) {
if (!(postsObject instanceof List)) {
return Collections.emptyList();
}
List<Object> postObjectList = (List<Object>) postsObject;
List<BasePost> result = new LinkedList<>();
postObjectList.forEach(postObject -> {
if (!(postObject instanceof Map)) {
return;
}
Map<String, Object> postMap = (Map<String, Object>) postObject;
BasePost post = new BasePost();
post.setTitle(postMap.getOrDefault("postTitle", "").toString());
post.setUrl(postMap.getOrDefault("postUrl", "").toString());
post.setOriginalContent(postMap.getOrDefault("postContentMd", "").toString());
post.setFormatContent(postMap.getOrDefault("postContent", "").toString());
post.setSummary(postMap.getOrDefault("postSummary", "").toString());
post.setThumbnail(postMap.getOrDefault("postThumbnail", "").toString());
post.setVisits(getLongOrDefault(postMap.getOrDefault("postViews", "").toString(), 0L));
post.setDisallowComment(false);
post.setTemplate(postMap.getOrDefault("customTpl", "").toString());
// Set disallow comment
Integer allowComment = getIntegerOrDefault(postMap.getOrDefault("allowComment", "1").toString(), 1);
if (allowComment != 1) {
post.setDisallowComment(true);
}
// Set create time
Long createTime = getLongOrDefault(postMap.getOrDefault("postDate", "").toString(), 0L);
if (createTime != 0L) {
post.setCreateTime(new Date(createTime));
}
// Set update time
Long updateTime = getLongOrDefault(postMap.getOrDefault("postUpdate", "").toString(), 0L);
if (updateTime != 0L) {
post.setUpdateTime(new Date(updateTime));
}
// Set status (default draft)
Integer postStatus = getIntegerOrDefault(postMap.getOrDefault("postStatus", "").toString(), 1);
if (postStatus == 0) {
post.setStatus(PostStatus.PUBLISHED);
} else if (postStatus == 1) {
post.setStatus(PostStatus.DRAFT);
} else {
post.setStatus(PostStatus.RECYCLE);
}
String postType = postMap.getOrDefault("postType", "post").toString();
try {
if ("post".equalsIgnoreCase(postType)) {
// Handle post
result.add(handlePost(post, postMap));
} else {
// Handle page
result.add(handleSheet(post, postMap));
}
} catch (Exception e) {
log.warn("Failed to migrate a post or sheet", e);
// Ignore this exception
}
});
return result;
}
@NonNull
private Post handlePost(@NonNull BasePost basePost, @NonNull Map<String, Object> postMap) {
Post post = BeanUtils.transformFrom(basePost, Post.class);
// Create it
Post createdPost = postService.createOrUpdateBy(post);
Object commentsObject = postMap.get("comments");
Object categoriesObject = postMap.get("categories");
Object tagsObject = postMap.get("tags");
// Handle comments
List<BaseComment> baseComments = handleComment(commentsObject, createdPost.getId());
// Handle categories
List<Category> categories = handleCategories(categoriesObject, createdPost.getId());
log.debug("Migrated categories of post [{}]: [{}]", categories, createdPost.getId());
// Handle tags
List<Tag> tags = handleTags(tagsObject, createdPost.getId());
log.debug("Migrated tags of post [{}]: [{}]", tags, createdPost.getId());
List<PostComment> postComments = baseComments.stream()
.map(baseComment -> BeanUtils.transformFrom(baseComment, PostComment.class))
.collect(Collectors.toList());
try {
// Build virtual comment
PostComment virtualPostComment = new PostComment();
virtualPostComment.setId(0L);
// Create comments
createPostCommentRecursively(virtualPostComment, postComments);
} catch (Exception e) {
log.warn("Failed to create post comments for post with id " + createdPost.getId(), e);
// Ignore this exception
}
return createdPost;
}
@NonNull
private Sheet handleSheet(@NonNull BasePost basePost, @NonNull Map<String, Object> postMap) {
Sheet sheet = BeanUtils.transformFrom(basePost, Sheet.class);
// Create it
Sheet createdSheet = sheetService.createOrUpdateBy(sheet);
Object commentsObject = postMap.get("comments");
// Handle comments
List<BaseComment> baseComments = handleComment(commentsObject, createdSheet.getId());
List<SheetComment> sheetComments = baseComments.stream()
.map(baseComment -> BeanUtils.transformFrom(baseComment, SheetComment.class))
.collect(Collectors.toList());
// Create comments
try {
// Build virtual comment
SheetComment virtualSheetComment = new SheetComment();
virtualSheetComment.setId(0L);
// Create comments
createSheetCommentRecursively(virtualSheetComment, sheetComments);
} catch (Exception e) {
log.warn("Failed to create sheet comments for sheet with id " + createdSheet.getId(), e);
// Ignore this exception
}
return createdSheet;
}
private void createPostCommentRecursively(@NonNull final PostComment parentComment, List<PostComment> postComments) {
Long oldParentId = parentComment.getId();
// Create parent
if (!ServiceUtils.isEmptyId(parentComment.getId())) {
PostComment createdComment = postCommentService.create(parentComment);
log.debug("Created post comment: [{}]", createdComment);
parentComment.setId(createdComment.getId());
}
if (CollectionUtils.isEmpty(postComments)) {
return;
}
// Get all children
List<PostComment> children = postComments.stream()
.filter(postComment -> Objects.equals(oldParentId, postComment.getParentId()))
.collect(Collectors.toList());
// Set parent id again
children.forEach(postComment -> postComment.setParentId(parentComment.getId()));
// Remove children
postComments.removeAll(children);
// Create children recursively
children.forEach(childComment -> createPostCommentRecursively(childComment, postComments));
}
private void createSheetCommentRecursively(@NonNull final SheetComment parentComment, List<SheetComment> sheetComments) {
Long oldParentId = parentComment.getId();
// Create parent
if (!ServiceUtils.isEmptyId(parentComment.getId())) {
SheetComment createComment = sheetCommentService.create(parentComment);
parentComment.setId(createComment.getId());
}
if (CollectionUtils.isEmpty(sheetComments)) {
return;
}
// Get all children
List<SheetComment> children = sheetComments.stream()
.filter(sheetComment -> Objects.equals(oldParentId, sheetComment.getParentId()))
.collect(Collectors.toList());
// Set parent id again
children.forEach(postComment -> postComment.setParentId(parentComment.getId()));
// Remove children
sheetComments.removeAll(children);
// Create children recursively
children.forEach(childComment -> createSheetCommentRecursively(childComment, sheetComments));
}
private List<BaseComment> handleComment(@Nullable Object commentsObject, @NonNull Integer postId) {
Assert.notNull(postId, "Post id must not be null");
if (!(commentsObject instanceof List)) {
return Collections.emptyList();
}
List<Object> commentObjectList = (List<Object>) commentsObject;
List<BaseComment> result = new LinkedList<>();
commentObjectList.forEach(commentObject -> {
if (!(commentObject instanceof Map)) {
return;
}
Map<String, Object> commentMap = (Map<String, Object>) commentObject;
BaseComment baseComment = new BaseComment();
baseComment.setId(getLongOrDefault(commentMap.getOrDefault("commentId", "").toString(), null));
baseComment.setAuthor(commentMap.getOrDefault("commentAuthor", "").toString());
baseComment.setEmail(commentMap.getOrDefault("commentAuthorEmail", "").toString());
baseComment.setIpAddress(commentMap.getOrDefault("commentAuthorIp", "").toString());
baseComment.setAuthorUrl(commentMap.getOrDefault("commentAuthorUrl", "").toString());
baseComment.setGravatarMd5(commentMap.getOrDefault("commentAuthorAvatarMd5", "").toString());
baseComment.setContent(commentMap.getOrDefault("commentContent", "").toString());
baseComment.setUserAgent(commentMap.getOrDefault("commentAgent", "").toString());
baseComment.setIsAdmin(getBooleanOrDefault(commentMap.getOrDefault("isAdmin", "").toString(), false));
baseComment.setPostId(postId);
baseComment.setParentId(getLongOrDefault(commentMap.getOrDefault("commentParent", "").toString(), 0L));
// Set create date
Long createTimestamp = getLongOrDefault(commentMap.getOrDefault("createDate", "").toString(), System.currentTimeMillis());
baseComment.setCreateTime(new Date(createTimestamp));
Integer commentStatus = getIntegerOrDefault(commentMap.getOrDefault("commentStatus", "").toString(), 1);
if (commentStatus == 0) {
baseComment.setStatus(CommentStatus.PUBLISHED);
} else if (commentStatus == 1) {
baseComment.setStatus(CommentStatus.AUDITING);
} else {
baseComment.setStatus(CommentStatus.RECYCLE);
}
result.add(baseComment);
});
return result;
}
@NonNull
private List<Category> handleCategories(@Nullable Object categoriesObject, @NonNull Integer postId) {
Assert.notNull(postId, "Post id must not be null");
if (!(categoriesObject instanceof List)) {
return Collections.emptyList();
}
List<Object> categoryObjectList = (List<Object>) categoriesObject;
List<Category> result = new LinkedList<>();
categoryObjectList.forEach(categoryObject -> {
if (!(categoryObject instanceof Map)) {
return;
}
Map<String, Object> categoryMap = (Map<String, Object>) categoryObject;
String slugName = categoryMap.getOrDefault("cateUrl", "").toString();
Category category = categoryService.getBySlugName(slugName);
if (null == category) {
category = new Category();
category.setName(categoryMap.getOrDefault("cateName", "").toString());
category.setSlugName(slugName);
category.setDescription(categoryMap.getOrDefault("cateDesc", "").toString());
category = categoryService.create(category);
}
PostCategory postCategory = new PostCategory();
postCategory.setCategoryId(category.getId());
postCategory.setPostId(postId);
postCategoryService.create(postCategory);
try {
result.add(category);
} catch (Exception e) {
log.warn("Failed to migrate a category", e);
}
});
return result;
}
@NonNull
private List<Tag> handleTags(@Nullable Object tagsObject, @NonNull Integer postId) {
Assert.notNull(postId, "Post id must not be null");
if (!(tagsObject instanceof List)) {
return Collections.emptyList();
}
List<Object> tagObjectList = (List<Object>) tagsObject;
List<Tag> result = new LinkedList<>();
tagObjectList.forEach(tagObject -> {
if (!(tagObject instanceof Map)) {
return;
}
Map<String, Object> tagMap = (Map<String, Object>) tagObject;
String slugName = tagMap.getOrDefault("tagUrl", "").toString();
Tag tag = tagService.getBySlugName(slugName);
if (null == tag) {
tag = new Tag();
tag.setName(tagMap.getOrDefault("tagName", "").toString());
tag.setSlugName(slugName);
tag = tagService.create(tag);
}
PostTag postTag = new PostTag();
postTag.setTagId(tag.getId());
postTag.setPostId(postId);
postTagService.create(postTag);
try {
result.add(tag);
} catch (Exception e) {
log.warn("Failed to migrate a tag", e);
}
});
return result;
}
@NonNull
private List<Menu> handleMenus(@Nullable Object menusObject) {
if (!(menusObject instanceof List)) {
return Collections.emptyList();
}
List<Object> menuObjectList = (List<Object>) menusObject;
List<Menu> result = new LinkedList<>();
menuObjectList.forEach(menuObject -> {
if (!(menuObject instanceof Map)) {
return;
}
Map<String, Object> menuMap = (Map<String, Object>) menuObject;
Menu menu = new Menu();
menu.setName(menuMap.getOrDefault("menuName", "").toString());
menu.setUrl(menuMap.getOrDefault("menuUrl", "").toString());
// Set priority
String sortString = menuMap.getOrDefault("menuSort", "0").toString();
menu.setPriority(getIntegerOrDefault(sortString, 0));
menu.setTarget(menuMap.getOrDefault("menuTarget", "_self").toString());
menu.setIcon(menuMap.getOrDefault("menuIcon", "").toString());
try {
// Create menu
result.add(menuService.create(menu));
} catch (Exception e) {
log.warn("Failed to migrate a menu", e);
}
});
return result;
}
@NonNull
private List<Photo> handleGalleries(@Nullable Object galleriesObject) {
if (!(galleriesObject instanceof List)) {
return Collections.emptyList();
}
List<Object> galleryObjectList = (List<Object>) galleriesObject;
List<Photo> result = new LinkedList<>();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
galleryObjectList.forEach(galleryObject -> {
if (!(galleriesObject instanceof Map)) {
return;
}
Map<String, Object> galleryMap = (Map<String, Object>) galleryObject;
Photo photo = new Photo();
photo.setName(galleryMap.getOrDefault("galleryName", "").toString());
photo.setDescription(galleryMap.getOrDefault("galleryDesc", "").toString());
photo.setLocation(galleryMap.getOrDefault("galleryLocation", "").toString());
photo.setThumbnail(galleryMap.getOrDefault("galleryThumbnailUrl", "").toString());
photo.setUrl(galleryMap.getOrDefault("galleryUrl", "").toString());
Object galleryDate = galleryMap.get("galleryDate");
try {
if (galleryDate != null) {
photo.setTakeTime(dateFormat.parse(galleryDate.toString()));
}
// Create it
result.add(photoService.create(photo));
} catch (Exception e) {
log.warn("Failed to create a photo", e);
// Ignore this exception
}
});
return result;
}
@NonNull
private List<Link> handleLinks(@Nullable Object linksObject) {
if (!(linksObject instanceof List)) {
return Collections.emptyList();
}
List<Object> linkObjectList = (List<Object>) linksObject;
List<Link> result = new LinkedList<>();
linkObjectList.forEach(linkObject -> {
if (!(linkObject instanceof Map)) {
return;
}
Map<String, Object> linkMap = (Map<String, Object>) linkObject;
Link link = new Link();
link.setName(linkMap.getOrDefault("linkName", "").toString());
link.setUrl(linkMap.getOrDefault("linkUrl", "").toString());
link.setLogo(linkMap.getOrDefault("linkPic", "").toString());
link.setDescription(linkMap.getOrDefault("linkDesc", "").toString());
try {
result.add(linkService.create(link));
} catch (Exception e) {
log.warn("Failed to migrate a link", e);
}
});
return result;
}
@NonNull
private List<Attachment> handleAttachments(@Nullable Object attachmentsObject) {
if (!(attachmentsObject instanceof List)) {
return Collections.emptyList();
}
List<Object> attachmentObjectList = (List<Object>) attachmentsObject;
List<Attachment> result = new LinkedList<>();
attachmentObjectList.forEach(attachmentObject -> {
if (!(attachmentObject instanceof Map)) {
return;
}
Map<String, Object> attachmentMap = (Map<String, Object>) attachmentObject;
// Convert to attachment param
Attachment attachment = new Attachment();
attachment.setName(attachmentMap.getOrDefault("attachName", "").toString());
attachment.setPath(StringUtils.removeStart(attachmentMap.getOrDefault("attachPath", "").toString(), "/"));
attachment.setThumbPath(attachmentMap.getOrDefault("attachSmallPath", "").toString());
attachment.setMediaType(attachmentMap.getOrDefault("attachType", "").toString());
attachment.setSuffix(StringUtils.removeStart(attachmentMap.getOrDefault("attachSuffix", "").toString(), "."));
attachment.setSize(0L);
if (StringUtils.startsWith(attachment.getPath(), "/upload")) {
// Set this key
attachment.setFileKey(attachment.getPath());
}
// Set location
String attachLocation = attachmentMap.getOrDefault("attachLocation", "").toString();
if (StringUtils.equalsIgnoreCase(attachLocation, "qiniu")) {
attachment.setType(AttachmentType.QINIUOSS);
} else if (StringUtils.equalsIgnoreCase(attachLocation, "upyun")) {
attachment.setType(AttachmentType.UPOSS);
} else {
attachment.setType(AttachmentType.LOCAL);
}
try {
// Save to db
Attachment createdAttachment = attachmentService.create(attachment);
result.add(createdAttachment);
} catch (Exception e) {
// Ignore this exception
log.warn("Failed to migrate an attachment " + attachment.getPath(), e);
}
});
return result;
}
@NonNull
private Integer getIntegerOrDefault(@Nullable String numberString, @Nullable Integer defaultNumber) {
try {
return Integer.valueOf(numberString);
} catch (Exception e) {
// Ignore this exception
return defaultNumber;
}
}
@NonNull
private Long getLongOrDefault(@Nullable String numberString, @Nullable Long defaultNumber) {
try {
return Long.valueOf(numberString);
} catch (Exception e) {
// Ignore this exception
return defaultNumber;
}
}
private Boolean getBooleanOrDefault(@Nullable String boolString, @Nullable Boolean defaultValue) {
if (StringUtils.equalsIgnoreCase(boolString, "0")) {
return false;
}
if (StringUtils.equalsIgnoreCase(boolString, "1")) {
return true;
}
if (StringUtils.equalsIgnoreCase(boolString, "true")) {
return true;
}
if (StringUtils.equalsIgnoreCase(boolString, "false")) {
return false;
}
return defaultValue;
}
@Override
public boolean supportType(MigrateType type) {
return MigrateType.OLD_VERSION.equals(type);
}
}

View File

@ -0,0 +1,229 @@
package run.halo.app.handler.migrate;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Element;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.entity.BasePost;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.enums.MigrateType;
import run.halo.app.service.*;
import run.halo.app.utils.MarkdownUtils;
import run.halo.app.utils.WordPressMigrateUtils;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* WordPress migrate handler
*
* @author ryanwang
* @date 2019-10-28
*/
@Slf4j
@Component
@SuppressWarnings("unchecked")
public class WordPressMigrateHandler implements MigrateHandler {
private final AttachmentService attachmentService;
private final PostService postService;
private final LinkService linkService;
private final MenuService menuService;
private final CategoryService categoryService;
private final TagService tagService;
private final PostCommentService postCommentService;
private final SheetCommentService sheetCommentService;
private final SheetService sheetService;
private final PhotoService photoService;
private final PostCategoryService postCategoryService;
private final PostTagService postTagService;
public WordPressMigrateHandler(AttachmentService attachmentService,
PostService postService,
LinkService linkService,
MenuService menuService,
CategoryService categoryService,
TagService tagService,
PostCommentService postCommentService,
SheetCommentService sheetCommentService,
SheetService sheetService,
PhotoService photoService,
PostCategoryService postCategoryService,
PostTagService postTagService) {
this.attachmentService = attachmentService;
this.postService = postService;
this.linkService = linkService;
this.menuService = menuService;
this.categoryService = categoryService;
this.tagService = tagService;
this.postCommentService = postCommentService;
this.sheetCommentService = sheetCommentService;
this.sheetService = sheetService;
this.photoService = photoService;
this.postCategoryService = postCategoryService;
this.postTagService = postTagService;
}
@Override
public void migrate(MultipartFile file) {
try {
String migrationContent = FileCopyUtils.copyToString(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8));
Element rootElement = WordPressMigrateUtils.getRootElement(new FileInputStream(migrationContent));
Map<String, Object> resultSetMapping = WordPressMigrateUtils.getResultSetMapping(rootElement);
// Handle categories
List<Category> categories = handleCategories(resultSetMapping.get("wp:category"));
// Handle tags
List<Tag> tags = handleTags(resultSetMapping.get("wp:tag"));
// Handle posts
List<BasePost> posts = handlePosts(resultSetMapping.get("item"));
log.debug("Migrated posts: [{}]", posts);
} catch (Exception e) {
throw new ServiceException("WordPress 导出文件 " + file.getOriginalFilename() + " 读取失败", e);
}
}
private List<Category> handleCategories(@Nullable Object categoriesObject) {
if (!(categoriesObject instanceof List)) {
return Collections.emptyList();
}
List<Object> categoryObjectList = (List<Object>) categoriesObject;
List<Category> result = new LinkedList<>();
categoryObjectList.forEach(categoryObject -> {
if (!(categoryObject instanceof Map)) {
return;
}
Map<String, Object> categoryMap = (Map<String, Object>) categoryObject;
String slugName = categoryMap.getOrDefault("wp:category_nicename", "").toString();
Category category = categoryService.getBySlugName(slugName);
if (null == category) {
category = new Category();
category.setName(categoryMap.getOrDefault("wp:cat_name", "").toString());
category.setSlugName(slugName);
category = categoryService.create(category);
}
try {
result.add(category);
} catch (Exception e) {
log.warn("Failed to migrate a category", e);
}
});
return result;
}
private List<Tag> handleTags(@Nullable Object tagsObject) {
if (!(tagsObject instanceof List)) {
return Collections.emptyList();
}
List<Object> tagObjectList = (List<Object>) tagsObject;
List<Tag> result = new LinkedList<>();
tagObjectList.forEach(tagObject -> {
if (!(tagObject instanceof Map)) {
return;
}
Map<String, Object> tagMap = (Map<String, Object>) tagObject;
String slugName = tagMap.getOrDefault("wp:tag_slug", "").toString();
Tag tag = tagService.getBySlugName(slugName);
if (null == tag) {
tag = new Tag();
tag.setName(tagMap.getOrDefault("wp:tag_name", "").toString());
tag.setSlugName(slugName);
tag = tagService.create(tag);
}
try {
result.add(tag);
} catch (Exception e) {
log.warn("Failed to migrate a tag", e);
}
});
return result;
}
@NonNull
private List<BasePost> handlePosts(@Nullable Object postsObject) {
if (!(postsObject instanceof List)) {
return Collections.emptyList();
}
List<Object> postObjectList = (List<Object>) postsObject;
List<BasePost> result = new LinkedList<>();
postObjectList.forEach(postObject -> {
if (!(postObject instanceof Map)) {
return;
}
Map<String, Object> postMap = (Map<String, Object>) postObject;
BasePost post = new BasePost();
post.setTitle(postMap.getOrDefault("title", "").toString());
post.setUrl(postMap.getOrDefault("wp:post_name", "").toString());
post.setOriginalContent(MarkdownUtils.renderMarkdown(postMap.getOrDefault("content:encoded", "").toString()));
post.setFormatContent(postMap.getOrDefault("content:encoded", "").toString());
post.setSummary(postMap.getOrDefault("excerpt:encoded", "").toString());
String url = postMap.getOrDefault("wp:post_name", "").toString();
Post temp = postService.getByUrl(url);
if (temp != null) {
post.setUrl(post.getUrl() + "_1");
}
});
return null;
}
@Override
public boolean supportType(MigrateType type) {
return MigrateType.WORDPRESS.equals(type);
}
}

View File

@ -10,7 +10,7 @@ import java.util.List;
* Theme config resolver interface.
*
* @author johnniang
* @date 4/10/19
* @date 2019-04-10
*/
public interface ThemeConfigResolver {

View File

@ -9,7 +9,7 @@ import java.io.IOException;
* Theme file resolver.
*
* @author johnniang
* @date 4/11/19
* @date 2019-04-11
*/
public interface ThemePropertyResolver {

View File

@ -22,7 +22,8 @@ import java.util.Map;
* Theme configuration resolver.
*
* @author johnniang
* @date 4/10/19
* @author ryanwang
* @date 2019-04-10
*/
@Slf4j
@Service
@ -116,6 +117,7 @@ public class YamlThemeConfigResolverImpl implements ThemeConfigResolver {
item.setType(InputType.typeOf(itemMap.get("type")));
item.setDefaultValue(itemMap.get("default"));
item.setPlaceholder(itemMap.getOrDefault("placeholder", "").toString());
item.setDescription(itemMap.getOrDefault("description", "").toString());
// Handle options
item.setOptions(handleOptions(itemMap.get("options")));
@ -142,6 +144,7 @@ public class YamlThemeConfigResolverImpl implements ThemeConfigResolver {
item.setType(InputType.typeOf(itemMap.get("type")));
item.setDefaultValue(itemMap.get("default"));
item.setPlaceholder(itemMap.getOrDefault("placeholder", "").toString());
item.setDescription(itemMap.getOrDefault("description", "").toString());
// Handle options
item.setOptions(handleOptions(itemMap.get("options")));

View File

@ -2,7 +2,6 @@ package run.halo.app.handler.theme.config.impl;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
@ -15,7 +14,7 @@ import java.io.IOException;
* Yaml theme file resolver.
*
* @author johnniang
* @date 4/11/19
* @date 2019-04-11
*/
@Service
public class YamlThemePropertyResolver implements ThemePropertyResolver {

View File

@ -11,7 +11,8 @@ import java.util.Objects;
* Theme configuration: item entity
*
* @author johnniang
* @date 4/10/19
* @author ryanwang
* @date 2019-04-10
*/
@Data
public class Item {
@ -46,6 +47,11 @@ public class Item {
*/
private String placeholder;
/**
* Text item description.
*/
private String description;
/**
* Item's options, default is empty list
*/

View File

@ -3,12 +3,13 @@ package run.halo.app.handler.theme.config.support;
import lombok.Data;
import java.util.Objects;
import java.util.Set;
/**
* Theme property.
*
* @author ryanwang
* @date : 2019-03-22
* @date 2019-03-22
*/
@Data
public class ThemeProperty {
@ -83,6 +84,16 @@ public class ThemeProperty {
*/
private String screenshots;
/**
* Post preset metas.
*/
private Set<String> postMetaField;
/**
* Sheet preset metas.
*/
private Set<String> sheetMetaField;
@Override
public boolean equals(Object o) {
if (this == o) {
@ -101,7 +112,7 @@ public class ThemeProperty {
}
@Data
public static class Author {
private static class Author {
/**
* Author name.

View File

@ -1,20 +1,24 @@
package run.halo.app.listener;
import lombok.extern.slf4j.Slf4j;
import org.flywaydb.core.Flyway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.service.OptionService;
import run.halo.app.service.ThemeService;
import run.halo.app.service.UserService;
import run.halo.app.utils.FileUtils;
import java.io.IOException;
import java.net.URI;
import java.nio.file.*;
import java.util.Collections;
@ -23,7 +27,8 @@ import java.util.Collections;
* The method executed after the application is started.
*
* @author ryanwang
* @date : 2018/12/5
* @author guqing
* @date 2018-12-05
*/
@Slf4j
@Configuration
@ -39,24 +44,47 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
@Autowired
private ThemeService themeService;
@Autowired
private UserService userService;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
// save halo version to database
this.printStartInfo();
this.migrate();
this.initThemes();
this.printStartInfo();
}
private void printStartInfo() {
String blogUrl = optionService.getBlogBaseUrl();
log.info("Halo started at {}", blogUrl);
log.info("Halo admin started at {}/admin", blogUrl);
log.info("Halo admin started at {}/{}", blogUrl, haloProperties.getAdminPath());
if (!haloProperties.isDocDisabled()) {
log.debug("Halo doc was enable at {}/swagger-ui.html", blogUrl);
log.debug("Halo api doc was enabled at {}/swagger-ui.html", blogUrl);
}
log.info("Halo has started successfully!");
}
/**
* Migrate database.
*/
private void migrate() {
log.info("Starting migrate database...");
Flyway flyway = Flyway
.configure()
.locations("classpath:/migration")
.baselineVersion("1")
.baselineOnMigrate(true)
.dataSource(url, username, password)
.load();
flyway.migrate();
log.info("Migrate database succeed.");
}
/**
@ -65,12 +93,6 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
private void initThemes() {
// Whether the blog has initialized
Boolean isInstalled = optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false);
/*if (haloProperties.isProductionEnv() && isInstalled) {
// Skip
return;
}*/
try {
String themeClassPath = ResourceUtils.CLASSPATH_URL_PREFIX + ThemeService.THEME_FOLDER;
@ -81,8 +103,9 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
Path source;
if ("jar".equalsIgnoreCase(themeUri.getScheme())) {
// Create new file system for jar
FileSystem fileSystem = FileSystems.newFileSystem(themeUri, Collections.emptyMap());
FileSystem fileSystem = getFileSystem(themeUri);
source = fileSystem.getPath("/BOOT-INF/classes/" + ThemeService.THEME_FOLDER);
} else {
source = Paths.get(themeUri);
@ -94,13 +117,27 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
// 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);
log.debug("Copied theme folder from [{}] to [{}]", source, themePath);
} else {
log.info("Skipped copying theme folder due to existence of theme folder");
log.debug("Skipped copying theme folder due to existence of theme folder");
}
} catch (Exception e) {
throw new RuntimeException("Initialize internal theme to user path error", e);
}
}
@NonNull
private FileSystem getFileSystem(@NonNull URI uri) throws IOException {
Assert.notNull(uri, "Uri must not be null");
FileSystem fileSystem;
try {
fileSystem = FileSystems.getFileSystem(uri);
} catch (FileSystemNotFoundException e) {
fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
}
return fileSystem;
}
}

View File

@ -1,4 +1,4 @@
package run.halo.app.event.comment;
package run.halo.app.listener.comment;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.text.StrBuilder;
@ -7,6 +7,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import run.halo.app.event.comment.CommentNewEvent;
import run.halo.app.event.comment.CommentReplyEvent;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.entity.*;
import run.halo.app.model.properties.CommentProperties;
@ -19,7 +21,8 @@ import java.util.Map;
* PostComment event listener.
*
* @author johnniang
* @date 19-4-23
* @author ryanwang
* @date 2019-04-23
*/
@Slf4j
@Component
@ -55,6 +58,11 @@ public class CommentEventListener {
this.userService = userService;
}
/**
* Received a new new comment event.
*
* @param newEvent new comment event.
*/
@Async
@EventListener
public void handleCommentNewEvent(CommentNewEvent newEvent) {
@ -117,12 +125,11 @@ public class CommentEventListener {
mailService.sendTemplateMail(user.getEmail(), "您的博客有新的评论", data, "common/mail_template/mail_notice.ftl");
}
@Async
@EventListener
public void handleCommentPassEvent(CommentPassEvent passEvent) {
}
/**
* Received a new reply comment event.
*
* @param newEvent reply comment event.
*/
@Async
@EventListener
public void handleCommentReplyEvent(CommentReplyEvent replyEvent) {
@ -151,6 +158,10 @@ public class CommentEventListener {
return;
}
if (!baseComment.getAllowNotification()) {
return;
}
baseAuthorEmail = baseComment.getEmail();
Post post = postService.getById(postComment.getPostId());
@ -175,6 +186,10 @@ public class CommentEventListener {
return;
}
if (!baseComment.getAllowNotification()) {
return;
}
baseAuthorEmail = baseComment.getEmail();
Sheet sheet = sheetService.getById(sheetComment.getPostId());
@ -198,6 +213,10 @@ public class CommentEventListener {
return;
}
if (!baseComment.getAllowNotification()) {
return;
}
baseAuthorEmail = baseComment.getEmail();
Journal journal = journalService.getById(journalComment.getPostId());

View File

@ -1,4 +1,4 @@
package run.halo.app.event.freemarker;
package run.halo.app.listener.freemarker;
import freemarker.template.Configuration;
import freemarker.template.TemplateModelException;
@ -12,7 +12,6 @@ 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;
@ -99,8 +98,7 @@ public class FreemarkerConfigAwareListener {
private void loadThemeConfig() throws TemplateModelException {
ThemeProperty activatedTheme = themeService.getActivatedTheme();
configuration.setSharedVariable("theme", activatedTheme);
String baseUrl = optionService.getByPropertyOrDefault(OtherProperties.CDN_DOMAIN, String.class, optionService.getBlogBaseUrl());
configuration.setSharedVariable("static", baseUrl + "/" + activatedTheme.getFolderName());
configuration.setSharedVariable("static", optionService.getBlogBaseUrl() + "/" + activatedTheme.getFolderName());
configuration.setSharedVariable("settings", themeSettingService.listAsMapBy(themeService.getActivatedThemeId()));
log.debug("Loaded theme and settings");
}

View File

@ -1,8 +1,9 @@
package run.halo.app.event.logger;
package run.halo.app.listener.logger;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import run.halo.app.event.logger.LogEvent;
import run.halo.app.model.entity.Log;
import run.halo.app.service.LogService;
@ -26,6 +27,7 @@ public class LogEventListener {
public void onApplicationEvent(LogEvent event) {
// Convert to log
Log logToCreate = event.getLogParam().convertTo();
// Create log
logService.create(logToCreate);
}

View File

@ -1,8 +1,9 @@
package run.halo.app.event.post;
package run.halo.app.listener.post;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import run.halo.app.event.post.AbstractVisitEvent;
import run.halo.app.service.base.BasePostService;
import java.util.Map;

View File

@ -1,8 +1,9 @@
package run.halo.app.event.post;
package run.halo.app.listener.post;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import run.halo.app.event.post.PostVisitEvent;
import run.halo.app.service.PostService;
/**

View File

@ -1,8 +1,9 @@
package run.halo.app.event.post;
package run.halo.app.listener.post;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import run.halo.app.event.post.SheetVisitEvent;
import run.halo.app.service.SheetService;
/**

View File

@ -1,9 +1,10 @@
package run.halo.app.event.theme;
package run.halo.app.listener.theme;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.event.options.OptionUpdatedEvent;
import run.halo.app.event.theme.ThemeUpdatedEvent;
import run.halo.app.service.ThemeService;
/**

View File

@ -2,8 +2,6 @@ package run.halo.app.model.dto;
import lombok.Data;
import java.util.Date;
/**
* @author ryanwang
* @date 2019-05-25
@ -11,13 +9,14 @@ import java.util.Date;
@Data
public class BackupDTO {
private String fileName;
@Deprecated
private String downloadUrl;
private Date createTime;
private String downloadLink;
private String fileSize;
private String filename;
private String fileType;
private Long updateTime;
private String type;
private Long fileSize;
}

View File

@ -13,6 +13,8 @@ import java.util.Date;
* Base comment output dto.
*
* @author johnniang
* @author ryanwang
* @date 2019-03-20
*/
@Data
@ToString
@ -41,6 +43,8 @@ public class BaseCommentDTO implements OutputConverter<BaseCommentDTO, BaseComme
private Boolean isAdmin;
private Boolean allowNotification;
private Date createTime;
}

View File

@ -0,0 +1,30 @@
package run.halo.app.model.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import run.halo.app.model.dto.base.OutputConverter;
import run.halo.app.model.entity.BaseMeta;
import java.util.Date;
/**
* Base meta Dto.
*
* @author ryanwang
* @date 2019-12-10
*/
@Data
@ToString
@EqualsAndHashCode
public class BaseMetaDTO implements OutputConverter<BaseMetaDTO, BaseMeta> {
private Long id;
private Integer postId;
private String key;
private String value;
private Date createTime;
}

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