Accomplish backup listing and deletion

pull/389/head
johnniang 2019-11-18 02:37:36 +08:00
parent a041409ff7
commit c7d88b1d1f
12 changed files with 243 additions and 30 deletions

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.jackson.JsonComponentModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
@ -80,16 +81,17 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String workDir = FILE_PROTOCOL + haloProperties.getWorkDir();
String workDir = FILE_PROTOCOL + StringUtils.appendIfMissing(haloProperties.getWorkDir(), "/");
String backupDir = FILE_PROTOCOL + StringUtils.appendIfMissing(haloProperties.getBackupDir(), "/");
registry.addResourceHandler("/**")
.addResourceLocations(workDir + "templates/themes/")
.addResourceLocations(workDir + "templates/admin/")
.addResourceLocations("classpath:/admin/")
.addResourceLocations(workDir + "static/");
registry.addResourceHandler("/upload/**")
registry.addResourceHandler(haloProperties.getUploadUrlPrefix() + "/**")
.addResourceLocations(workDir + "upload/");
registry.addResourceHandler("/backup/**")
.addResourceLocations(workDir + "backup/");
registry.addResourceHandler(haloProperties.getBackupUrlPrefix() + "/**")
.addResourceLocations(workDir + "backup/", backupDir);
registry.addResourceHandler(haloProperties.getAdminPath() + "/**")
.addResourceLocations(workDir + HALO_ADMIN_RELATIVE_PATH)
.addResourceLocations("classpath:/admin/");

View File

@ -4,10 +4,12 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import run.halo.app.model.support.HaloConst;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* Halo configuration properties.
*
@ -37,13 +39,29 @@ public class HaloProperties {
*/
private String adminPath = "/admin";
/**
* Halo backup directory.(Not recommended to modify this config);
*/
private String backupDir = HaloConst.TEMP_DIR + "/halo-backup/";
/**
* Work directory.
*/
private String workDir = HaloConst.USER_HOME + "/.halo/";
/**
* Upload prefix.
*/
private String uploadUrlPrefix = "/upload";
/**
* backup prefix.
*/
private String backupUrlPrefix = "/backup";
public HaloProperties() throws IOException {
// Create work directory if not exist
Files.createDirectories(Paths.get(workDir));
Files.createDirectories(Paths.get(backupDir));
}
}

View File

@ -6,13 +6,10 @@ 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
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;
@ -40,6 +37,24 @@ public class BackupController {
this.backupService = backupService;
}
@PostMapping("halo")
@ApiOperation("Backup halo")
public BackupDTO backupHalo() {
return backupService.zipWorkDirectory();
}
@GetMapping("halo")
@ApiOperation("Get all backups")
public List<BackupDTO> listBackups() {
return backupService.listHaloBackups();
}
@DeleteMapping("halo")
@ApiOperation("Delete 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 {

View File

@ -37,10 +37,10 @@ public class MainController {
this.haloProperties = haloProperties;
}
@GetMapping("/{permlink}")
public String admin(@PathVariable(name = "permlink") String permlink) {
return "redirect:/" + permlink + "/index.html";
}
// @GetMapping("/{permlink}")
// public String admin(@PathVariable(name = "permlink") String permlink) {
// return "redirect:/" + permlink + "/index.html";
// }
@GetMapping("/install")
public String installation() {

View File

@ -11,13 +11,7 @@ import java.util.Date;
@Data
public class BackupDTO {
private String fileName;
private String downloadUrl;
private Date createTime;
private String fileSize;
private String fileType;
private String type;
private String filename;
}

View File

@ -13,6 +13,7 @@ import java.util.Date;
* @date : 2018/6/4
*/
@Data
@Deprecated
public class BackupDto {
/**

View File

@ -17,6 +17,11 @@ public class HaloConst {
*/
public final static String USER_HOME = System.getProperties().getProperty("user.home");
/**
* Temporary directory.
*/
public final static String TEMP_DIR = System.getProperties().getProperty("java.io.tmpdir");
/**
* Default theme name.
*/

View File

@ -1,7 +1,9 @@
package run.halo.app.service;
import org.json.JSONObject;
import org.springframework.lang.NonNull;
import org.springframework.web.multipart.MultipartFile;
import run.halo.app.model.dto.BackupDTO;
import run.halo.app.model.dto.post.BasePostDetailDTO;
import java.io.IOException;
@ -27,7 +29,7 @@ public interface BackupService {
/**
* export posts by hexo formatter
*
* @return
* @return json object
*/
JSONObject exportHexoMDs();
@ -38,4 +40,28 @@ public interface BackupService {
* @param path
*/
void exportHexoMd(List<JSONObject> posts, String path);
/**
* Zips work directory.
*
* @return backup dto.
*/
@NonNull
BackupDTO zipWorkDirectory();
/**
* Lists all backups.
*
* @return backup list
*/
@NonNull
List<BackupDTO> listHaloBackups();
/**
* Deletes backup.
*
* @param filename filename must not be blank
*/
void deleteHaloBackup(@NonNull String filename);
}

View File

@ -1,26 +1,39 @@
package run.halo.app.service.impl;
import cn.hutool.core.io.IoUtil;
import lombok.RequiredArgsConstructor;
import cn.hutool.core.util.IdUtil;
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.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.exception.NotFoundException;
import run.halo.app.exception.ServiceException;
import run.halo.app.model.dto.AttachmentDTO;
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.service.BackupService;
import run.halo.app.service.OptionService;
import run.halo.app.service.PostService;
import run.halo.app.service.PostTagService;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@ -35,14 +48,27 @@ import java.util.stream.Collectors;
*/
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class BackupServiceImpl implements BackupService {
private final PostService postService;
private final PostTagService postTagService;
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
private final OptionService optionService;
private final HaloProperties halo;
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
public BackupServiceImpl(PostService postService,
PostTagService postTagService,
OptionService optionService,
HaloProperties halo) {
this.postService = postService;
this.postTagService = postTagService;
this.optionService = optionService;
this.halo = halo;
}
@Override
public BasePostDetailDTO importMarkdown(MultipartFile file) throws IOException {
@ -117,6 +143,74 @@ public class BackupServiceImpl implements BackupService {
});
}
@Override
public BackupDTO zipWorkDirectory() {
// Zip work directory to temporary file
try {
// Create zip path for halo zip
String haloZipFileName = new StringBuilder().append("Halo-backup-")
.append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")))
.append(IdUtil.simpleUUID())
.append(".zip").toString();
// Create halo zip file
Path haloZipPath = Files.createFile(Paths.get(halo.getBackupDir(), haloZipFileName));
// Zip halo
run.halo.app.utils.FileUtils.zip(Paths.get(this.halo.getWorkDir()), haloZipPath);
// Build download url
String downloadUrl = buildDownloadUrl(haloZipFileName);
// Build attachment dto
BackupDTO backup = new BackupDTO();
backup.setDownloadUrl(downloadUrl);
backup.setFilename(haloZipFileName);
return backup;
} catch (IOException e) {
throw new ServiceException("Failed to backup halo", e);
}
}
@Override
public List<BackupDTO> listHaloBackups() {
try {
return Files.list(Paths.get(halo.getBackupDir())).map(backupPath -> {
// Get filename
String filename = backupPath.getFileName().toString();
// Build download url
String downloadUrl = buildDownloadUrl(filename);
// Build backup dto
BackupDTO backup = new BackupDTO();
backup.setDownloadUrl(downloadUrl);
backup.setFilename(filename);
return backup;
}).collect(Collectors.toList());
} catch (IOException e) {
throw new ServiceException("Failed to fetch backups", e);
}
}
@Override
public void deleteHaloBackup(String filename) {
Assert.hasText(filename, "File name must not be blank");
// Get backup path
Path backupPath = Paths.get(halo.getBackupDir(), filename);
try {
// Delete backup file
Files.delete(backupPath);
} catch (NoSuchFileException e) {
throw new NotFoundException("The file " + filename + " was not found", e);
} catch (IOException e) {
throw new ServiceException("Failed to delete backup", e);
}
}
/**
* Sanitizes the specified file name.
*
@ -129,4 +223,19 @@ public class BackupServiceImpl implements BackupService {
replaceAll("[\\?\\\\/:|<>\\*\\[\\]\\(\\)\\$%\\{\\}@~]", "").
replaceAll("\\s", "");
}
/**
* Builds download url.
*
* @param filename filename must not be blank
* @return download url
*/
private String buildDownloadUrl(@NonNull String filename) {
Assert.hasText(filename, "File name must not be blank");
return StringUtils.joinWith("/",
optionService.getBlogBaseUrl(),
StringUtils.removeEnd(StringUtils.removeStart(halo.getBackupUrlPrefix(), "/"), "/"),
filename);
}
}

View File

@ -124,13 +124,26 @@ public class FileUtils {
/**
* Zip folder or file.
*
* @param fileToZip file path to zip must not be null
* @param pathToZip file path to zip must not be null
* @param pathOfArchive zip file path to archive must not be null
* @throws IOException
*/
public static void zip(@NonNull Path pathToZip, @NonNull Path pathOfArchive) throws IOException {
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(pathOfArchive))) {
zip(pathToZip, zipOut);
}
}
/**
* Zip folder or file.
*
* @param pathToZip file path to zip must not be null
* @param zipOut zip output stream must not be null
* @throws IOException
*/
public static void zip(@NonNull Path fileToZip, @NonNull ZipOutputStream zipOut) throws IOException {
public static void zip(@NonNull Path pathToZip, @NonNull ZipOutputStream zipOut) throws IOException {
// Zip file
zip(fileToZip, fileToZip.getFileName().toString(), zipOut);
zip(pathToZip, pathToZip.getFileName().toString(), zipOut);
}
/**

View File

@ -3,6 +3,7 @@ package run.halo.app.utils;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import run.halo.app.model.support.HaloConst;
import java.io.File;
import java.io.IOException;
@ -82,10 +83,13 @@ public class FileUtilsTest {
FileUtils.zip(rootFolder, zipOut);
}
// Clear the test folder created before
FileUtils.deleteFolder(rootFolder);
Files.delete(zipToStore);
}
@Test
public void tempFolderTest() {
log.debug(HaloConst.TEMP_DIR);
}
}

View File

@ -0,0 +1,26 @@
package run.halo.app.utils;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* Local date time test.
*
* @author johnniang
*/
@Slf4j
public class LocalDateTimeTest {
@Test
public void dateTimeToStringTest() {
LocalDateTime dateTime = LocalDateTime.now();
log.debug(dateTime.toString());
log.debug(dateTime.toLocalDate().toString());
String DATE_FORMATTER = "yyyy-MM-dd-HH-mm-ss-";
log.debug(dateTime.format(DateTimeFormatter.ofPattern(DATE_FORMATTER)));
}
}