feat: add data export and import api. (#672)

* feat: add data export and import api.

* refactor: data export and import api.
pull/687/head
Ryan Wang 2020-03-17 13:14:15 +08:00 committed by GitHub
parent d6b3d6cb5d
commit 4bc8ad44da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 451 additions and 223 deletions

View File

@ -62,13 +62,16 @@ public class AdminController {
@PostMapping("password/code")
@ApiOperation("Sends reset password verify code")
@CacheLock(autoDelete = false)
@DisableOnCondition
public void sendResetCode(@RequestBody @Valid ResetPasswordParam param) {
adminService.sendResetPasswordCode(param);
}
@PutMapping("password/reset")
@DisableOnCondition
@ApiOperation("Resets password by verify code")
@CacheLock(autoDelete = false)
@DisableOnCondition
public void resetPassword(@RequestBody @Valid ResetPasswordParam param) {
adminService.resetPasswordByCode(param);
}
@ -95,32 +98,35 @@ public class AdminController {
@PutMapping("halo-admin")
@ApiOperation("Updates halo-admin manually")
@Deprecated
public void updateAdmin() {
adminService.updateAdminAssets();
}
@GetMapping("spring/application.yaml")
@ApiOperation("Gets application config content")
@DisableOnCondition
public BaseResponse<String> getSpringApplicationConfig() {
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), adminService.getApplicationConfig());
}
@PutMapping("spring/application.yaml")
@DisableOnCondition
@ApiOperation("Updates application config content")
@DisableOnCondition
public void updateSpringApplicationConfig(@RequestParam(name = "content") String content) {
adminService.updateApplicationConfig(content);
}
@PostMapping(value = {"halo/restart", "spring/restart"})
@DisableOnCondition
@ApiOperation("Restarts halo server")
@DisableOnCondition
public void restartApplication() {
Application.restart();
}
@GetMapping(value = "halo/logfile")
@ApiOperation("Gets halo log file content")
@DisableOnCondition
public BaseResponse<String> getLogFiles(@RequestParam("lines") Long lines) {
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), adminService.getLogFiles(lines));
}

View File

@ -1,26 +1,20 @@
package run.halo.app.controller.admin.api;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.core.date.DateUtil;
import io.swagger.annotations.ApiOperation;
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.annotation.DisableOnCondition;
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.Date;
import java.util.List;
@ -44,6 +38,7 @@ public class BackupController {
@PostMapping("halo")
@ApiOperation("Backups halo")
@DisableOnCondition
public BackupDTO backupHalo() {
return backupService.zipWorkDirectory();
}
@ -56,6 +51,7 @@ public class BackupController {
@GetMapping("halo/{fileName:.+}")
@ApiOperation("Downloads backup file")
@DisableOnCondition
public ResponseEntity<Resource> downloadBackup(@PathVariable("fileName") String fileName, HttpServletRequest request) {
log.info("Try to download backup file: [{}]", fileName);
@ -79,6 +75,7 @@ public class BackupController {
@DeleteMapping("halo")
@ApiOperation("Deletes a backup")
@DisableOnCondition
public void deleteBackup(@RequestParam("filename") String filename) {
backupService.deleteHaloBackup(filename);
}
@ -89,48 +86,17 @@ public class BackupController {
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());
@GetMapping("export/data")
@DisableOnCondition
public ResponseEntity<String> exportData() {
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 + "\"");
}
String contentType = "application/octet-stream;charset=UTF-8";
response.getOutputStream().write(zipData);
} catch (final Exception e) {
log.error("Export failed", e);
throw new FileOperationException("文章导出失败", e);
}
String filename = "halo-data-" + DateUtil.format(new Date(), "yyyy-MM-dd-HH-mm-ss.json");
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.body(backupService.exportData().toJSONString());
}
}

View File

@ -23,18 +23,18 @@ public class DataProcessController {
this.themeSettingService = themeSettingService;
}
@PutMapping("url/replace")
@ApiOperation("Replace url in all table.")
public void replaceUrl(@RequestParam("oldUrl") String oldUrl,
@RequestParam("newUrl") String newUrl) {
dataProcessService.replaceAllUrl(oldUrl, newUrl);
}
@DeleteMapping("themes/settings/inactivated")
@ApiOperation("Delete inactivated theme settings.")
public void deleteInactivatedThemeSettings() {
themeSettingService.deleteInactivated();
}
// @PutMapping("url/replace")
// @ApiOperation("Replace url in all table.")
// public void replaceUrl(@RequestParam("oldUrl") String oldUrl,
// @RequestParam("newUrl") String newUrl) {
// dataProcessService.replaceAllUrl(oldUrl, newUrl);
// }
//
// @DeleteMapping("themes/settings/inactivated")
// @ApiOperation("Delete inactivated theme settings.")
// public void deleteInactivatedThemeSettings() {
// themeSettingService.deleteInactivated();
// }
@DeleteMapping("tags/unused")
@ApiOperation("Delete unused tags")

View File

@ -164,6 +164,12 @@ public class InstallController {
return null;
}
long commentCount = postCommentService.count();
if (commentCount > 0) {
return null;
}
PostComment comment = new PostComment();
comment.setAuthor("Halo");
comment.setAuthorUrl("https://halo.run");
@ -175,7 +181,9 @@ public class InstallController {
@Nullable
private PostDetailVO createDefaultPostIfAbsent(@Nullable Category category) {
long publishedCount = postService.countByStatus(PostStatus.PUBLISHED);
if (publishedCount > 0) {
return null;
}
@ -210,6 +218,11 @@ public class InstallController {
@Nullable
private void createDefaultSheet() {
long publishedCount = sheetService.countByStatus(PostStatus.PUBLISHED);
if (publishedCount > 0) {
return;
}
SheetParam sheetParam = new SheetParam();
sheetParam.setSlug("about");
sheetParam.setTitle("关于页面");
@ -261,10 +274,19 @@ public class InstallController {
properties.put(PrimaryProperties.IS_INSTALLED, Boolean.TRUE.toString());
properties.put(BlogProperties.BLOG_LOCALE, installParam.getLocale());
properties.put(BlogProperties.BLOG_TITLE, installParam.getTitle());
properties.put(BlogProperties.BLOG_URL, StringUtils.isBlank(installParam.getUrl()) ?
optionService.getBlogBaseUrl() : installParam.getUrl());
properties.put(BlogProperties.BLOG_URL, StringUtils.isBlank(installParam.getUrl()) ? optionService.getBlogBaseUrl() : installParam.getUrl());
Long birthday = optionService.getByPropertyOrDefault(PrimaryProperties.BIRTHDAY, Long.class, 0L);
if (birthday.equals(0L)) {
properties.put(PrimaryProperties.BIRTHDAY, String.valueOf(System.currentTimeMillis()));
}
Boolean globalAbsolutePathEnabled = optionService.getByPropertyOrDefault(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.class, null);
if (globalAbsolutePathEnabled == null) {
properties.put(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.FALSE.toString());
}
// Create properties
optionService.saveProperties(properties);

View File

@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.mail.MailService;
import run.halo.app.model.annotation.DisableOnCondition;
import run.halo.app.model.params.MailParam;
import run.halo.app.model.support.BaseResponse;
@ -29,6 +30,7 @@ public class MailController {
@PostMapping("test")
@ApiOperation("Tests the SMTP service")
@DisableOnCondition
public BaseResponse<String> testMail(@Valid @RequestBody MailParam mailParam) {
mailService.sendTextMail(mailParam.getTo(), mailParam.getSubject(), mailParam.getContent());
return BaseResponse.ok("已发送,请查收。若确认没有收到邮件,请检查服务器日志");
@ -36,6 +38,7 @@ public class MailController {
@PostMapping("test/connection")
@ApiOperation("Test connection with email server")
@DisableOnCondition
public BaseResponse<String> testConnection() {
mailService.testConnection();
return BaseResponse.ok("您和邮箱服务器的连接通畅");

View File

@ -6,8 +6,11 @@ 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.exception.BadRequestException;
import run.halo.app.model.enums.MigrateType;
import run.halo.app.model.properties.PrimaryProperties;
import run.halo.app.service.MigrateService;
import run.halo.app.service.OptionService;
/**
* Migrate controller
@ -21,25 +24,32 @@ public class MigrateController {
private final MigrateService migrateService;
public MigrateController(MigrateService migrateService) {
private final OptionService optionService;
public MigrateController(MigrateService migrateService,
OptionService optionService) {
this.migrateService = migrateService;
this.optionService = optionService;
}
@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("halo")
@ApiOperation("Migrate from Halo")
public void migrateHalo(@RequestPart("file") MultipartFile file) {
if (optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false)) {
throw new BadRequestException("无法在博客初始化完成之后迁移数据");
}
migrateService.migrate(file, MigrateType.HALO);
}
@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);
}
// @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

@ -44,8 +44,8 @@ public class OptionController {
}
@PostMapping("saving")
@DisableOnCondition
@ApiOperation("Saves options")
@DisableOnCondition
public void saveOptions(@Valid @RequestBody List<OptionParam> optionParams) {
optionService.save(optionParams);
}
@ -78,30 +78,30 @@ public class OptionController {
}
@PostMapping
@DisableOnCondition
@ApiOperation("Creates option")
@DisableOnCondition
public void createBy(@RequestBody @Valid OptionParam optionParam) {
optionService.save(optionParam);
}
@PutMapping("{optionId:\\d+}")
@DisableOnCondition
@ApiOperation("Updates option")
@DisableOnCondition
public void updateBy(@PathVariable("optionId") Integer optionId,
@RequestBody @Valid OptionParam optionParam) {
optionService.update(optionId, optionParam);
}
@DeleteMapping("{optionId:\\d+}")
@DisableOnCondition
@ApiOperation("Deletes option")
@DisableOnCondition
public void deletePermanently(@PathVariable("optionId") Integer optionId) {
optionService.removePermanently(optionId);
}
@PostMapping("map_view/saving")
@DisableOnCondition
@ApiOperation("Saves options by option map")
@DisableOnCondition
public void saveOptionsWithMapView(@RequestBody Map<String, Object> optionMap) {
optionService.save(optionMap);
}

View File

@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.handler.theme.config.support.Group;
import run.halo.app.handler.theme.config.support.ThemeProperty;
import run.halo.app.model.annotation.DisableOnCondition;
import run.halo.app.model.params.ThemeContentParam;
import run.halo.app.model.support.BaseResponse;
import run.halo.app.model.support.ThemeFile;
@ -75,12 +76,14 @@ public class ThemeController {
@PutMapping("files/content")
@ApiOperation("Updates template content")
@DisableOnCondition
public void updateContentBy(@RequestBody ThemeContentParam param) {
themeService.saveTemplateContent(param.getPath(), param.getContent());
}
@PutMapping("{themeId}/files/content")
@ApiOperation("Updates template content by theme id")
@DisableOnCondition
public void updateContentBy(@PathVariable("themeId") String themeId,
@RequestBody ThemeContentParam param) {
themeService.saveTemplateContent(themeId, param.getPath(), param.getContent());
@ -149,6 +152,7 @@ public class ThemeController {
@DeleteMapping("{themeId}")
@ApiOperation("Deletes a theme")
@DisableOnCondition
public void deleteBy(@PathVariable("themeId") String themeId) {
themeService.deleteTheme(themeId);
}
@ -187,7 +191,7 @@ public class ThemeController {
@GetMapping(value = "activation/template/exists")
@ApiOperation("Determines if template exists")
public BaseResponse exists(@RequestParam(value = "template") String template) {
public BaseResponse<Boolean> exists(@RequestParam(value = "template") String template) {
return BaseResponse.ok(themeService.templateExists(template));
}
}

View File

@ -38,6 +38,7 @@ public class UserController {
@PutMapping("profiles")
@ApiOperation("Updates user profile")
@DisableOnCondition
public UserDTO updateProfile(@RequestBody UserParam userParam, User user) {
// Validate the user param
ValidationUtils.validate(userParam, UpdateCheck.class);
@ -50,9 +51,9 @@ public class UserController {
}
@PutMapping("profiles/password")
@DisableOnCondition
@ApiOperation("Updates user's password")
public BaseResponse updatePassword(@RequestBody @Valid PasswordParam passwordParam, User user) {
@DisableOnCondition
public BaseResponse<String> updatePassword(@RequestBody @Valid PasswordParam passwordParam, User user) {
userService.updatePassword(passwordParam.getOldPassword(), passwordParam.getNewPassword(), user.getId());
return BaseResponse.ok("密码修改成功");
}

View File

@ -2,6 +2,7 @@ package run.halo.app.exception;
/**
* repeat type exception
*
* @author bestsort
* @date 3/13/20 5:03 PM
*/
@ -9,6 +10,7 @@ public class RepeatTypeException extends ServiceException {
public RepeatTypeException(String message) {
super(message);
}
public RepeatTypeException(String message, Throwable cause) {
super(message, cause);
}

View File

@ -75,6 +75,7 @@ public interface FileHandler {
/**
* Get attachment type is supported.
*
* @return attachment type
*/
AttachmentType getAttachmentType();

View File

@ -0,0 +1,36 @@
package run.halo.app.handler.migrate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.model.enums.MigrateType;
import run.halo.app.service.BackupService;
import java.io.IOException;
/**
* @author ryanwang
* @date 2020-03-14
*/
@Component
public class HaloMigrateHandler implements MigrateHandler {
private final BackupService backupService;
public HaloMigrateHandler(BackupService backupService) {
this.backupService = backupService;
}
@Override
public void migrate(MultipartFile file) {
try {
backupService.importData(file);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public boolean supportType(MigrateType type) {
return MigrateType.HALO.equals(type);
}
}

View File

@ -688,6 +688,6 @@ public class OldVersionMigrateHandler implements MigrateHandler {
@Override
public boolean supportType(MigrateType type) {
return MigrateType.OLD_VERSION.equals(type);
return false;
}
}

View File

@ -55,6 +55,7 @@ public abstract class AbstractMailService implements MailService {
/**
* Test connection with email server.
*/
@Override
public void testConnection() {
JavaMailSender javaMailSender = getMailSender();
if (javaMailSender instanceof JavaMailSenderImpl) {

View File

@ -4,6 +4,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.GenericGenerator;
import run.halo.app.model.enums.AttachmentType;
import javax.persistence.*;
@ -22,8 +23,8 @@ import javax.persistence.*;
public class Attachment extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**

View File

@ -4,6 +4,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.GenericGenerator;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.utils.ServiceUtils;
@ -25,7 +26,8 @@ import javax.persistence.*;
public class BaseComment extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Long id;
/**

View File

@ -4,6 +4,7 @@ package run.halo.app.model.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@ -23,7 +24,8 @@ import javax.persistence.*;
public class BaseMeta extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Long id;
/**

View File

@ -4,6 +4,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.GenericGenerator;
import run.halo.app.model.enums.PostEditorType;
import run.halo.app.model.enums.PostStatus;
@ -25,7 +26,8 @@ import java.util.Date;
public class BasePost extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**

View File

@ -4,6 +4,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@ -24,7 +25,8 @@ import javax.persistence.*;
public class Category extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**

View File

@ -1,6 +1,7 @@
package run.halo.app.model.entity;
import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Date;
@ -20,7 +21,8 @@ import java.util.Date;
@NoArgsConstructor
public class CommentBlackList extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Long id;
@Column(name = "ip_address", length = 127, nullable = false)

View File

@ -4,6 +4,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.GenericGenerator;
import run.halo.app.model.enums.JournalType;
import javax.persistence.*;
@ -23,7 +24,8 @@ import javax.persistence.*;
public class Journal extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
@Column(name = "source_content", nullable = false)

View File

@ -4,6 +4,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@ -21,8 +22,8 @@ import javax.persistence.*;
public class Link extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**

View File

@ -4,6 +4,7 @@ package run.halo.app.model.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import run.halo.app.model.enums.LogType;
import javax.persistence.*;
@ -21,7 +22,8 @@ import javax.persistence.*;
public class Log extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Long id;
/**

View File

@ -4,6 +4,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@ -21,8 +22,8 @@ import javax.persistence.*;
public class Menu extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**

View File

@ -5,6 +5,7 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.GenericGenerator;
import run.halo.app.model.enums.OptionType;
import javax.persistence.*;
@ -25,7 +26,8 @@ import javax.persistence.*;
public class Option extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**
@ -44,7 +46,8 @@ public class Option extends BaseEntity {
/**
* option value
*/
@Column(name = "option_value", length = 1023, nullable = false)
@Column(name = "option_value", nullable = false)
@Lob
private String value;
public Option(String key, String value) {

View File

@ -3,6 +3,7 @@ package run.halo.app.model.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Date;
@ -21,8 +22,8 @@ import java.util.Date;
public class Photo extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**

View File

@ -2,6 +2,7 @@ package run.halo.app.model.entity;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Objects;
@ -20,7 +21,8 @@ import java.util.Objects;
public class PostCategory extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**

View File

@ -2,6 +2,7 @@ package run.halo.app.model.entity;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Objects;
@ -21,8 +22,8 @@ import java.util.Objects;
public class PostTag extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**

View File

@ -3,6 +3,7 @@ package run.halo.app.model.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@ -20,8 +21,8 @@ import javax.persistence.*;
public class Tag extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**

View File

@ -3,6 +3,7 @@ package run.halo.app.model.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@ -22,7 +23,8 @@ import javax.persistence.*;
public class ThemeSetting extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "custom-id")
@GenericGenerator(name = "custom-id", strategy = "run.halo.app.model.entity.support.CustomIdGenerator")
private Integer id;
/**
@ -35,6 +37,7 @@ public class ThemeSetting extends BaseEntity {
* Setting value
*/
@Column(name = "setting_value", nullable = false)
@Lob
private String value;
/**

View File

@ -0,0 +1,22 @@
package run.halo.app.model.entity.support;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentityGenerator;
import run.halo.app.utils.ReflectionUtils;
import java.io.Serializable;
/**
* @author ryanwang
* @date 2020-03-16
*/
public class CustomIdGenerator extends IdentityGenerator {
@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) {
Object id = ReflectionUtils.getFieldValue("id", object);
if (id != null) {
return (Serializable) id;
}
return super.generate(session, object);
}
}

View File

@ -9,9 +9,9 @@ package run.halo.app.model.enums;
public enum MigrateType implements ValueEnum<Integer> {
/**
* Halo version 0.4.4
* Halo
*/
OLD_VERSION(0),
HALO(0),
/**
* WordPress

View File

@ -60,7 +60,7 @@ public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
"/api/admin/login",
"/api/admin/refresh/*",
"/api/admin/installations",
"/api/admin/migrations/**",
"/api/admin/migrations/halo",
"/api/admin/is_installed",
"/api/admin/password/code",
"/api/admin/password/reset"

View File

@ -1,6 +1,6 @@
package run.halo.app.service;
import org.json.JSONObject;
import com.alibaba.fastjson.JSONObject;
import org.springframework.core.io.Resource;
import org.springframework.lang.NonNull;
import org.springframework.web.multipart.MultipartFile;
@ -19,29 +19,14 @@ import java.util.List;
public interface BackupService {
/**
* Backup posts and sheets
* Import markdown content.
*
* @param file file
* @return post info
* @return base post detail dto
* @throws IOException throws IOException
*/
BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException;
/**
* export posts by hexo formatter
*
* @return json object
*/
JSONObject exportHexoMDs();
/**
* Exports the specified articles to the specified dir path.
*
* @param posts
* @param path
*/
void exportHexoMd(List<JSONObject> posts, String path);
/**
* Zips work directory.
*
@ -74,4 +59,21 @@ public interface BackupService {
*/
@NonNull
Resource loadFileAsResource(@NonNull String fileName);
/**
* Export all database's data.
*
* @return data
*/
@NonNull
JSONObject exportData();
/**
* Import data
*
* @param file file
* @throws IOException throws IOException
*/
void importData(MultipartFile file) throws IOException;
}

View File

@ -1,6 +1,8 @@
package run.halo.app.service;
import run.halo.app.model.entity.CommentBlackList;
import run.halo.app.model.enums.CommentViolationTypeEnum;
import run.halo.app.service.base.CrudService;
/**
* Comment BlackList Service
@ -8,7 +10,7 @@ import run.halo.app.model.enums.CommentViolationTypeEnum;
* @author Lei XinXin
* @date 2020/1/3
*/
public interface CommentBlackListService {
public interface CommentBlackListService extends CrudService<CommentBlackList, Long> {
/**
*
*

View File

@ -4,6 +4,7 @@ import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.Transactional;
import run.halo.app.model.entity.ThemeSetting;
import run.halo.app.service.base.CrudService;
import java.util.List;
import java.util.Map;
@ -14,7 +15,7 @@ import java.util.Map;
* @author johnniang
* @date 2019-04-08
*/
public interface ThemeSettingService {
public interface ThemeSettingService extends CrudService<ThemeSetting, Integer> {
/**

View File

@ -1,36 +1,31 @@
package run.halo.app.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.json.JSONObject;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import org.yaml.snakeyaml.Yaml;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.event.options.OptionUpdatedEvent;
import run.halo.app.event.theme.ThemeUpdatedEvent;
import run.halo.app.exception.NotFoundException;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.dto.BackupDTO;
import run.halo.app.model.dto.post.BasePostDetailDTO;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.entity.*;
import run.halo.app.model.support.HaloConst;
import run.halo.app.security.service.OneTimeTokenService;
import run.halo.app.service.BackupService;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostService;
import run.halo.app.service.PostTagService;
import run.halo.app.service.*;
import run.halo.app.utils.HaloUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
@ -40,7 +35,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -59,26 +55,79 @@ public class BackupServiceImpl implements BackupService {
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private final AttachmentService attachmentService;
private final CategoryService categoryService;
private final CommentBlackListService commentBlackListService;
private final JournalService journalService;
private final JournalCommentService journalCommentService;
private final LinkService linkService;
private final LogService logService;
private final MenuService menuService;
private final OptionService optionService;
private final PhotoService photoService;
private final PostService postService;
private final PostCategoryService postCategoryService;
private final PostCommentService postCommentService;
private final PostMetaService postMetaService;
private final PostTagService postTagService;
private final OptionService optionService;
private final SheetService sheetService;
private final SheetCommentService sheetCommentService;
private final SheetMetaService sheetMetaService;
private final TagService tagService;
private final ThemeSettingService themeSettingService;
private final UserService userService;
private final OneTimeTokenService oneTimeTokenService;
private final HaloProperties haloProperties;
public BackupServiceImpl(PostService postService,
PostTagService postTagService,
OptionService optionService,
OneTimeTokenService oneTimeTokenService,
HaloProperties haloProperties) {
this.postService = postService;
this.postTagService = postTagService;
private final ApplicationEventPublisher eventPublisher;
public BackupServiceImpl(AttachmentService attachmentService, CategoryService categoryService, CommentBlackListService commentBlackListService, JournalService journalService, JournalCommentService journalCommentService, LinkService linkService, LogService logService, MenuService menuService, OptionService optionService, PhotoService photoService, PostService postService, PostCategoryService postCategoryService, PostCommentService postCommentService, PostMetaService postMetaService, PostTagService postTagService, SheetService sheetService, SheetCommentService sheetCommentService, SheetMetaService sheetMetaService, TagService tagService, ThemeSettingService themeSettingService, UserService userService, OneTimeTokenService oneTimeTokenService, HaloProperties haloProperties, ApplicationEventPublisher eventPublisher) {
this.attachmentService = attachmentService;
this.categoryService = categoryService;
this.commentBlackListService = commentBlackListService;
this.journalService = journalService;
this.journalCommentService = journalCommentService;
this.linkService = linkService;
this.logService = logService;
this.menuService = menuService;
this.optionService = optionService;
this.photoService = photoService;
this.postService = postService;
this.postCategoryService = postCategoryService;
this.postCommentService = postCommentService;
this.postMetaService = postMetaService;
this.postTagService = postTagService;
this.sheetService = sheetService;
this.sheetCommentService = sheetCommentService;
this.sheetMetaService = sheetMetaService;
this.tagService = tagService;
this.themeSettingService = themeSettingService;
this.userService = userService;
this.oneTimeTokenService = oneTimeTokenService;
this.haloProperties = haloProperties;
this.eventPublisher = eventPublisher;
}
/**
@ -105,66 +154,6 @@ public class BackupServiceImpl implements BackupService {
return postService.importMarkdown(markdown, file.getOriginalFilename());
}
@Override
public JSONObject exportHexoMDs() {
final JSONObject ret = new JSONObject();
final List<JSONObject> posts = new ArrayList<>();
ret.put("posts", (Object) posts);
final List<JSONObject> passwords = new ArrayList<>();
ret.put("passwords", (Object) passwords);
final List<JSONObject> drafts = new ArrayList<>();
ret.put("drafts", (Object) drafts);
List<Post> postList = postService.listAll();
Map<Integer, List<Tag>> talMap = postTagService.listTagListMapBy(postList.stream().map(Post::getId).collect(Collectors.toList()));
for (Post post : postList) {
final Map<String, Object> front = new LinkedHashMap<>();
final String title = post.getTitle();
front.put("title", title);
final String date = DateFormatUtils.format(post.getCreateTime(), "yyyy-MM-dd HH:mm:ss");
front.put("date", date);
front.put("updated", DateFormatUtils.format(post.getUpdateTime(), "yyyy-MM-dd HH:mm:ss"));
final List<String> tags = talMap.get(post.getId()).stream().map(Tag::getName).collect(Collectors.toList());
if (tags.isEmpty()) {
tags.add("halo");
}
front.put("tags", tags);
front.put("permalink", "");
final JSONObject one = new JSONObject();
one.put("front", new Yaml().dump(front));
one.put("title", title);
one.put("content", post.getOriginalContent());
one.put("created", post.getCreateTime().getTime());
if (StringUtils.isNotBlank(post.getPassword())) {
passwords.add(one);
} else if (post.getStatus() == PostStatus.DRAFT) {
drafts.add(one);
} else {
posts.add(one);
}
}
return ret;
}
@Override
public void exportHexoMd(List<JSONObject> posts, String dirPath) {
posts.forEach(post -> {
final String filename = sanitizeFilename(post.optString("title")) + ".md";
final String text = post.optString("front") + "---" + LINE_SEPARATOR + post.optString("content");
try {
final String date = DateFormatUtils.format(post.optLong("created"), "yyyyMM");
final String dir = dirPath + File.separator + date + File.separator;
new File(dir).mkdirs();
FileUtils.writeStringToFile(new File(dir + filename), text, "UTF-8");
} catch (final Exception e) {
log.error("Write markdown file failed", e);
}
});
}
@Override
public BackupDTO zipWorkDirectory() {
// Zip work directory to temporary file
@ -268,6 +257,106 @@ public class BackupServiceImpl implements BackupService {
}
}
@Override
public JSONObject exportData() {
JSONObject data = new JSONObject();
data.put("version", HaloConst.HALO_VERSION);
data.put("export_date", DateUtil.now());
data.put("attachments", attachmentService.listAll());
data.put("categories", categoryService.listAll());
data.put("comment_black_list", commentBlackListService.listAll());
data.put("journals", journalService.listAll());
data.put("journal_comments", journalCommentService.listAll());
data.put("links", linkService.listAll());
data.put("logs", logService.listAll());
data.put("menus", menuService.listAll());
data.put("options", optionService.listAll());
data.put("photos", photoService.listAll());
data.put("posts", postService.listAll());
data.put("post_categories", postCategoryService.listAll());
data.put("post_comments", postCommentService.listAll());
data.put("post_metas", postMetaService.listAll());
data.put("post_tags", postTagService.listAll());
data.put("sheets", sheetService.listAll());
data.put("sheet_comments", sheetCommentService.listAll());
data.put("sheet_metas", sheetMetaService.listAll());
data.put("tags", tagService.listAll());
data.put("theme_settings", themeSettingService.listAll());
data.put("user", userService.listAll());
return data;
}
@Override
public void importData(MultipartFile file) throws IOException {
String jsonContent = IoUtil.read(file.getInputStream(), StandardCharsets.UTF_8);
JSONObject data = JSONObject.parseObject(jsonContent);
List<Attachment> attachments = data.getJSONArray("attachments").toJavaList(Attachment.class);
attachmentService.createInBatch(attachments);
List<Category> categories = data.getJSONArray("categories").toJavaList(Category.class);
categoryService.createInBatch(categories);
List<Tag> tags = data.getJSONArray("tags").toJavaList(Tag.class);
tagService.createInBatch(tags);
List<CommentBlackList> commentBlackList = data.getJSONArray("comment_black_list").toJavaList(CommentBlackList.class);
commentBlackListService.createInBatch(commentBlackList);
List<Journal> journals = data.getJSONArray("journals").toJavaList(Journal.class);
journalService.createInBatch(journals);
List<JournalComment> journalComments = data.getJSONArray("journal_comments").toJavaList(JournalComment.class);
journalCommentService.createInBatch(journalComments);
List<Link> links = data.getJSONArray("links").toJavaList(Link.class);
linkService.createInBatch(links);
List<Log> logs = data.getJSONArray("logs").toJavaList(Log.class);
logService.createInBatch(logs);
List<Menu> menus = data.getJSONArray("menus").toJavaList(Menu.class);
menuService.createInBatch(menus);
List<Option> options = data.getJSONArray("options").toJavaList(Option.class);
optionService.createInBatch(options);
eventPublisher.publishEvent(new OptionUpdatedEvent(this));
List<Photo> photos = data.getJSONArray("photos").toJavaList(Photo.class);
photoService.createInBatch(photos);
List<Post> posts = data.getJSONArray("posts").toJavaList(Post.class);
postService.createInBatch(posts);
List<PostCategory> postCategories = data.getJSONArray("post_categories").toJavaList(PostCategory.class);
postCategoryService.createInBatch(postCategories);
List<PostComment> postComments = data.getJSONArray("post_comments").toJavaList(PostComment.class);
postCommentService.createInBatch(postComments);
List<PostMeta> postMetas = data.getJSONArray("post_metas").toJavaList(PostMeta.class);
postMetaService.createInBatch(postMetas);
List<PostTag> postTags = data.getJSONArray("post_tags").toJavaList(PostTag.class);
postTagService.createInBatch(postTags);
List<Sheet> sheets = data.getJSONArray("sheets").toJavaList(Sheet.class);
sheetService.createInBatch(sheets);
List<SheetComment> sheetComments = data.getJSONArray("sheet_comments").toJavaList(SheetComment.class);
sheetCommentService.createInBatch(sheetComments);
List<SheetMeta> sheetMetas = data.getJSONArray("sheet_metas").toJavaList(SheetMeta.class);
sheetMetaService.createInBatch(sheetMetas);
List<ThemeSetting> themeSettings = data.getJSONArray("theme_settings").toJavaList(ThemeSetting.class);
themeSettingService.createInBatch(themeSettings);
eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
}
/**
* Builds backup dto.
*

View File

@ -30,7 +30,7 @@ public class ImageUtils {
}
}
public static ImageReader getImageReaderFromFile(InputStream is,String formatName) {
public static ImageReader getImageReaderFromFile(InputStream is, String formatName) {
try {
Iterator<ImageReader> readerIterator = ImageIO.getImageReadersByFormatName(formatName);
ImageReader reader = readerIterator.next();

View File

@ -4,6 +4,7 @@ import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@ -11,6 +12,8 @@ import java.lang.reflect.Type;
* Reflection utilities.
*
* @author johnniang
* @author ryanwang
* @date 2019-03-15
*/
public class ReflectionUtils {
@ -89,4 +92,26 @@ public class ReflectionUtils {
return getParameterizedType(superClassType, extensionClass.getGenericSuperclass());
}
/**
* Gets field value from Object.
*
* @param fieldName fieldName must not be null
* @param object object must not be null.
* @return value
*/
public static Object getFieldValue(@NonNull String fieldName, @NonNull Object object) {
Assert.notNull(fieldName, "FieldName must not be null");
Assert.notNull(object, "Object type must not be null");
Object value = null;
try {
String firstLetter = fieldName.substring(0, 1).toUpperCase();
String getter = "get" + firstLetter + fieldName.substring(1);
Method method = object.getClass().getMethod(getter);
value = method.invoke(object);
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
}

View File

@ -0,0 +1,7 @@
-- Migrate 1.3.0-beta.2 to 1.3.0-beta.3
-- Migrate options Table
alter table options modify option_value longtext not null;
-- Migrate theme_settings Table
alter table theme_settings modify setting_value longtext not null;