mirror of https://github.com/halo-dev/halo
Accomplish backup listing and deletion
parent
a041409ff7
commit
c7d88b1d1f
|
@ -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/");
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Date;
|
|||
* @date : 2018/6/4
|
||||
*/
|
||||
@Data
|
||||
@Deprecated
|
||||
public class BackupDto {
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue