mirror of https://github.com/halo-dev/halo
Batch export markdown backup in hexo format
parent
54e17acfc0
commit
eaee041644
|
@ -1,16 +1,26 @@
|
||||||
package run.halo.app.controller.admin.api;
|
package run.halo.app.controller.admin.api;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ZipUtil;
|
||||||
import io.swagger.annotations.ApiOperation;
|
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.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import run.halo.app.exception.FileOperationException;
|
||||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||||
import run.halo.app.service.BackupService;
|
import run.halo.app.service.BackupService;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.LinkedList;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +31,7 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/admin/backups")
|
@RequestMapping("/api/admin/backups")
|
||||||
|
@Slf4j
|
||||||
public class BackupController {
|
public class BackupController {
|
||||||
|
|
||||||
private final BackupService backupService;
|
private final BackupService backupService;
|
||||||
|
@ -34,4 +45,49 @@ public class BackupController {
|
||||||
public BasePostDetailDTO backupMarkdowns(@RequestPart("file") MultipartFile file) throws IOException {
|
public BasePostDetailDTO backupMarkdowns(@RequestPart("file") MultipartFile file) throws IOException {
|
||||||
return backupService.importMarkdown(file);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package run.halo.app.service;
|
package run.halo.app.service;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backup service interface.
|
* Backup service interface.
|
||||||
|
@ -20,4 +22,20 @@ public interface BackupService {
|
||||||
* @return post info
|
* @return post info
|
||||||
*/
|
*/
|
||||||
BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException;
|
BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* export posts by hexo formatter
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
JSONObject exportHexoMDs();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports the specified articles to the specified dir path.
|
||||||
|
*
|
||||||
|
* @param posts
|
||||||
|
* @param path
|
||||||
|
*/
|
||||||
|
void exportHexoMd(List<JSONObject> posts, String path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,31 @@
|
||||||
package run.halo.app.service.impl;
|
package run.halo.app.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
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.service.BackupService;
|
import run.halo.app.service.BackupService;
|
||||||
import run.halo.app.service.PostService;
|
import run.halo.app.service.PostService;
|
||||||
|
import run.halo.app.service.PostTagService;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backup service implementation.
|
* Backup service implementation.
|
||||||
|
@ -17,13 +34,15 @@ import java.nio.charset.StandardCharsets;
|
||||||
* @date 2019-04-26
|
* @date 2019-04-26
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
|
||||||
public class BackupServiceImpl implements BackupService {
|
public class BackupServiceImpl implements BackupService {
|
||||||
|
|
||||||
private final PostService postService;
|
private final PostService postService;
|
||||||
|
|
||||||
public BackupServiceImpl(PostService postService) {
|
private final PostTagService postTagService;
|
||||||
this.postService = postService;
|
|
||||||
}
|
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException {
|
public BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException {
|
||||||
|
@ -35,4 +54,79 @@ public class BackupServiceImpl implements BackupService {
|
||||||
|
|
||||||
return postService.importMarkdown(markdown, file.getOriginalFilename());
|
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);
|
||||||
|
continue;
|
||||||
|
} else if (post.getDeleted()) {
|
||||||
|
drafts.add(one);
|
||||||
|
continue;
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes the specified file name.
|
||||||
|
*
|
||||||
|
* @param unsanitized the specified file name
|
||||||
|
* @return sanitized file name
|
||||||
|
*/
|
||||||
|
public static String sanitizeFilename(final String unsanitized) {
|
||||||
|
return unsanitized.
|
||||||
|
replaceAll("[^(a-zA-Z0-9\\u4e00-\\u9fa5\\.)]", "").
|
||||||
|
replaceAll("[\\?\\\\/:|<>\\*\\[\\]\\(\\)\\$%\\{\\}@~]", "").
|
||||||
|
replaceAll("\\s", "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue