mirror of https://github.com/halo-dev/halo
parent
03a43fb245
commit
a77ebed299
|
@ -14,6 +14,8 @@ import static run.halo.app.utils.HaloUtils.ensureSuffix;
|
||||||
* Halo configuration properties.
|
* Halo configuration properties.
|
||||||
*
|
*
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
|
* @author ryanwang
|
||||||
|
* @date 2019-03-15
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@ConfigurationProperties("halo")
|
@ConfigurationProperties("halo")
|
||||||
|
@ -54,6 +56,11 @@ public class HaloProperties {
|
||||||
*/
|
*/
|
||||||
private String backupDir = ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-backup" + FILE_SEPARATOR;
|
private String backupDir = ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-backup" + FILE_SEPARATOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Halo data export directory.
|
||||||
|
*/
|
||||||
|
private String dataExportDir = ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-data-export" + FILE_SEPARATOR;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload prefix.
|
* Upload prefix.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package run.halo.app.controller.admin.api;
|
package run.halo.app.controller.admin.api;
|
||||||
|
|
||||||
import cn.hutool.core.date.DateUtil;
|
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
@ -9,6 +8,7 @@ import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import run.halo.app.config.properties.HaloProperties;
|
||||||
import run.halo.app.model.annotation.DisableOnCondition;
|
import run.halo.app.model.annotation.DisableOnCondition;
|
||||||
import run.halo.app.model.dto.BackupDTO;
|
import run.halo.app.model.dto.BackupDTO;
|
||||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||||
|
@ -16,13 +16,13 @@ import run.halo.app.service.BackupService;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backup controller
|
* Backup controller
|
||||||
*
|
*
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
|
* @author ryanwang
|
||||||
* @date 2019-04-26
|
* @date 2019-04-26
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
|
@ -32,31 +32,35 @@ public class BackupController {
|
||||||
|
|
||||||
private final BackupService backupService;
|
private final BackupService backupService;
|
||||||
|
|
||||||
public BackupController(BackupService backupService) {
|
private final HaloProperties haloProperties;
|
||||||
|
|
||||||
|
public BackupController(BackupService backupService,
|
||||||
|
HaloProperties haloProperties) {
|
||||||
this.backupService = backupService;
|
this.backupService = backupService;
|
||||||
|
this.haloProperties = haloProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("halo")
|
@PostMapping("work-dir")
|
||||||
@ApiOperation("Backups halo")
|
@ApiOperation("Backups work directory")
|
||||||
@DisableOnCondition
|
@DisableOnCondition
|
||||||
public BackupDTO backupHalo() {
|
public BackupDTO backupHalo() {
|
||||||
return backupService.zipWorkDirectory();
|
return backupService.backupWorkDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("halo")
|
@GetMapping("work-dir")
|
||||||
@ApiOperation("Gets all backups")
|
@ApiOperation("Gets all work directory backups")
|
||||||
public List<BackupDTO> listBackups() {
|
public List<BackupDTO> listBackups() {
|
||||||
return backupService.listHaloBackups();
|
return backupService.listWorkDirBackups();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("halo/{fileName:.+}")
|
@GetMapping("work-dir/{fileName:.+}")
|
||||||
@ApiOperation("Downloads backup file")
|
@ApiOperation("Downloads a work directory backup file")
|
||||||
@DisableOnCondition
|
@DisableOnCondition
|
||||||
public ResponseEntity<Resource> downloadBackup(@PathVariable("fileName") String fileName, HttpServletRequest request) {
|
public ResponseEntity<Resource> downloadBackup(@PathVariable("fileName") String fileName, HttpServletRequest request) {
|
||||||
log.info("Try to download backup file: [{}]", fileName);
|
log.info("Try to download backup file: [{}]", fileName);
|
||||||
|
|
||||||
// Load file as resource
|
// Load file as resource
|
||||||
Resource backupResource = backupService.loadFileAsResource(fileName);
|
Resource backupResource = backupService.loadFileAsResource(haloProperties.getBackupDir(), fileName);
|
||||||
|
|
||||||
String contentType = "application/octet-stream";
|
String contentType = "application/octet-stream";
|
||||||
// Try to determine file's content type
|
// Try to determine file's content type
|
||||||
|
@ -73,30 +77,59 @@ public class BackupController {
|
||||||
.body(backupResource);
|
.body(backupResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("halo")
|
@DeleteMapping("work-dir")
|
||||||
@ApiOperation("Deletes a backup")
|
@ApiOperation("Deletes a work directory backup")
|
||||||
@DisableOnCondition
|
@DisableOnCondition
|
||||||
public void deleteBackup(@RequestParam("filename") String filename) {
|
public void deleteBackup(@RequestParam("filename") String filename) {
|
||||||
backupService.deleteHaloBackup(filename);
|
backupService.deleteWorkDirBackup(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("import/markdown")
|
@PostMapping("markdown")
|
||||||
@ApiOperation("Import markdown")
|
@ApiOperation("Imports markdown")
|
||||||
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/data")
|
@PostMapping("data")
|
||||||
|
@ApiOperation("Exports all data")
|
||||||
@DisableOnCondition
|
@DisableOnCondition
|
||||||
public ResponseEntity<String> exportData() {
|
public BackupDTO exportData() {
|
||||||
|
return backupService.exportData();
|
||||||
|
}
|
||||||
|
|
||||||
String contentType = "application/octet-stream;charset=UTF-8";
|
@GetMapping("data")
|
||||||
|
@ApiOperation("Lists all exported data")
|
||||||
|
public List<BackupDTO> listExportedData() {
|
||||||
|
return backupService.listExportedData();
|
||||||
|
}
|
||||||
|
|
||||||
String filename = "halo-data-" + DateUtil.format(new Date(), "yyyy-MM-dd-HH-mm-ss.json");
|
@DeleteMapping("data")
|
||||||
|
@ApiOperation("Deletes a exported data")
|
||||||
|
@DisableOnCondition
|
||||||
|
public void deleteExportedData(@RequestParam("filename") String filename) {
|
||||||
|
backupService.deleteExportedData(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("data/{fileName:.+}")
|
||||||
|
@ApiOperation("Downloads a exported data")
|
||||||
|
@DisableOnCondition
|
||||||
|
public ResponseEntity<Resource> downloadExportedData(@PathVariable("fileName") String fileName, HttpServletRequest request) {
|
||||||
|
log.info("Try to download exported data file: [{}]", fileName);
|
||||||
|
|
||||||
|
// Load exported data as resource
|
||||||
|
Resource exportDataResource = backupService.loadFileAsResource(haloProperties.getDataExportDir(), fileName);
|
||||||
|
|
||||||
|
String contentType = "application/octet-stream";
|
||||||
|
// Try to determine file's content type
|
||||||
|
try {
|
||||||
|
contentType = request.getServletContext().getMimeType(exportDataResource.getFile().getAbsolutePath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("Could not determine file type", e);
|
||||||
|
}
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.contentType(MediaType.parseMediaType(contentType))
|
.contentType(MediaType.parseMediaType(contentType))
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + exportDataResource.getFilename() + "\"")
|
||||||
.body(backupService.exportData().toJSONString());
|
.body(exportDataResource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package run.halo.app.controller.admin.api;
|
package run.halo.app.controller.admin.api;
|
||||||
|
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import run.halo.app.service.DataProcessService;
|
import run.halo.app.service.DataProcessService;
|
||||||
import run.halo.app.service.ThemeSettingService;
|
import run.halo.app.service.ThemeSettingService;
|
||||||
|
|
||||||
|
|
|
@ -148,6 +148,7 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
|
||||||
private void initDirectory() {
|
private void initDirectory() {
|
||||||
Path workPath = Paths.get(haloProperties.getWorkDir());
|
Path workPath = Paths.get(haloProperties.getWorkDir());
|
||||||
Path backupPath = Paths.get(haloProperties.getBackupDir());
|
Path backupPath = Paths.get(haloProperties.getBackupDir());
|
||||||
|
Path dataExportPath = Paths.get(haloProperties.getDataExportDir());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Files.notExists(workPath)) {
|
if (Files.notExists(workPath)) {
|
||||||
|
@ -160,6 +161,11 @@ public class StartedListener implements ApplicationListener<ApplicationStartedEv
|
||||||
log.info("Created backup directory: [{}]", backupPath);
|
log.info("Created backup directory: [{}]", backupPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Files.notExists(dataExportPath)) {
|
||||||
|
Files.createDirectories(dataExportPath);
|
||||||
|
log.info("Created data export directory: [{}]", dataExportPath);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (IOException ie) {
|
} catch (IOException ie) {
|
||||||
throw new RuntimeException("Failed to initialize directories", ie);
|
throw new RuntimeException("Failed to initialize directories", ie);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,6 @@ import lombok.Data;
|
||||||
@Data
|
@Data
|
||||||
public class BackupDTO {
|
public class BackupDTO {
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
private String downloadUrl;
|
|
||||||
|
|
||||||
private String downloadLink;
|
private String downloadLink;
|
||||||
|
|
||||||
private String filename;
|
private String filename;
|
||||||
|
|
|
@ -15,7 +15,7 @@ import java.util.Objects;
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "post_categories",
|
@Table(name = "post_categories",
|
||||||
indexes = {@Index(name = "post_categories_post_id", columnList = "post_id"),
|
indexes = {@Index(name = "post_categories_post_id", columnList = "post_id"),
|
||||||
@Index(name = "post_categories_category_id", columnList = "category_id")})
|
@Index(name = "post_categories_category_id", columnList = "category_id")})
|
||||||
@Data
|
@Data
|
||||||
@ToString(callSuper = true)
|
@ToString(callSuper = true)
|
||||||
public class PostCategory extends BaseEntity {
|
public class PostCategory extends BaseEntity {
|
||||||
|
|
|
@ -29,6 +29,11 @@ public class HaloConst {
|
||||||
*/
|
*/
|
||||||
public final static String HALO_BACKUP_PREFIX = "halo-backup-";
|
public final static String HALO_BACKUP_PREFIX = "halo-backup-";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Halo data export prefix.
|
||||||
|
*/
|
||||||
|
public final static String HALO_DATA_EXPORT_PREFIX = "halo-data-export-";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static pages pack prefix.
|
* Static pages pack prefix.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package run.halo.app.service;
|
package run.halo.app.service;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
@ -14,6 +13,7 @@ import java.util.List;
|
||||||
* Backup service interface.
|
* Backup service interface.
|
||||||
*
|
*
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
|
* @author ryanwang
|
||||||
* @date 2019-04-26
|
* @date 2019-04-26
|
||||||
*/
|
*/
|
||||||
public interface BackupService {
|
public interface BackupService {
|
||||||
|
@ -33,7 +33,7 @@ public interface BackupService {
|
||||||
* @return backup dto.
|
* @return backup dto.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
BackupDTO zipWorkDirectory();
|
BackupDTO backupWorkDirectory();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,23 +42,24 @@ public interface BackupService {
|
||||||
* @return backup list
|
* @return backup list
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
List<BackupDTO> listHaloBackups();
|
List<BackupDTO> listWorkDirBackups();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes backup.
|
* Deletes backup.
|
||||||
*
|
*
|
||||||
* @param fileName filename must not be blank
|
* @param fileName filename must not be blank
|
||||||
*/
|
*/
|
||||||
void deleteHaloBackup(@NonNull String fileName);
|
void deleteWorkDirBackup(@NonNull String fileName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads file as resource.
|
* Loads file as resource.
|
||||||
*
|
*
|
||||||
* @param fileName backup file name must not be blank.
|
* @param fileName backup file name must not be blank.
|
||||||
|
* @param basePath base path
|
||||||
* @return resource of the given file
|
* @return resource of the given file
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
Resource loadFileAsResource(@NonNull String fileName);
|
Resource loadFileAsResource(@NonNull String basePath, @NonNull String fileName);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,7 +68,21 @@ public interface BackupService {
|
||||||
* @return data
|
* @return data
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
JSONObject exportData();
|
BackupDTO exportData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all exported data.
|
||||||
|
*
|
||||||
|
* @return list of backup dto
|
||||||
|
*/
|
||||||
|
List<BackupDTO> listExportedData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes exported data.
|
||||||
|
*
|
||||||
|
* @param fileName fileName
|
||||||
|
*/
|
||||||
|
void deleteExportedData(@NonNull String fileName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import data
|
* Import data
|
||||||
|
|
|
@ -2,6 +2,8 @@ package run.halo.app.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.io.file.FileWriter;
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -51,7 +53,9 @@ import java.util.stream.Stream;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BackupServiceImpl implements BackupService {
|
public class BackupServiceImpl implements BackupService {
|
||||||
|
|
||||||
private static final String BACKUP_RESOURCE_BASE_URI = "/api/admin/backups/halo";
|
private static final String BACKUP_RESOURCE_BASE_URI = "/api/admin/backups/work-dir";
|
||||||
|
|
||||||
|
private static final String DATA_EXPORT_BASE_URI = "/api/admin/backups/data";
|
||||||
|
|
||||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||||
|
|
||||||
|
@ -155,7 +159,7 @@ public class BackupServiceImpl implements BackupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BackupDTO zipWorkDirectory() {
|
public BackupDTO backupWorkDirectory() {
|
||||||
// Zip work directory to temporary file
|
// Zip work directory to temporary file
|
||||||
try {
|
try {
|
||||||
// Create zip path for halo zip
|
// Create zip path for halo zip
|
||||||
|
@ -169,14 +173,14 @@ public class BackupServiceImpl implements BackupService {
|
||||||
run.halo.app.utils.FileUtils.zip(Paths.get(this.haloProperties.getWorkDir()), haloZipPath);
|
run.halo.app.utils.FileUtils.zip(Paths.get(this.haloProperties.getWorkDir()), haloZipPath);
|
||||||
|
|
||||||
// Build backup dto
|
// Build backup dto
|
||||||
return buildBackupDto(haloZipPath);
|
return buildBackupDto(BACKUP_RESOURCE_BASE_URI, haloZipPath);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ServiceException("Failed to backup halo", e);
|
throw new ServiceException("Failed to backup halo", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<BackupDTO> listHaloBackups() {
|
public List<BackupDTO> listWorkDirBackups() {
|
||||||
// Ensure the parent folder exist
|
// Ensure the parent folder exist
|
||||||
Path backupParentPath = Paths.get(haloProperties.getBackupDir());
|
Path backupParentPath = Paths.get(haloProperties.getBackupDir());
|
||||||
if (Files.notExists(backupParentPath)) {
|
if (Files.notExists(backupParentPath)) {
|
||||||
|
@ -187,7 +191,7 @@ public class BackupServiceImpl implements BackupService {
|
||||||
try (Stream<Path> subPathStream = Files.list(backupParentPath)) {
|
try (Stream<Path> subPathStream = Files.list(backupParentPath)) {
|
||||||
return subPathStream
|
return subPathStream
|
||||||
.filter(backupPath -> StringUtils.startsWithIgnoreCase(backupPath.getFileName().toString(), HaloConst.HALO_BACKUP_PREFIX))
|
.filter(backupPath -> StringUtils.startsWithIgnoreCase(backupPath.getFileName().toString(), HaloConst.HALO_BACKUP_PREFIX))
|
||||||
.map(this::buildBackupDto)
|
.map(backupPath -> buildBackupDto(BACKUP_RESOURCE_BASE_URI, backupPath))
|
||||||
.sorted((leftBackup, rightBackup) -> {
|
.sorted((leftBackup, rightBackup) -> {
|
||||||
// Sort the result
|
// Sort the result
|
||||||
if (leftBackup.getUpdateTime() < rightBackup.getUpdateTime()) {
|
if (leftBackup.getUpdateTime() < rightBackup.getUpdateTime()) {
|
||||||
|
@ -203,7 +207,7 @@ public class BackupServiceImpl implements BackupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteHaloBackup(String fileName) {
|
public void deleteWorkDirBackup(String fileName) {
|
||||||
Assert.hasText(fileName, "File name must not be blank");
|
Assert.hasText(fileName, "File name must not be blank");
|
||||||
|
|
||||||
Path backupRootPath = Paths.get(haloProperties.getBackupDir());
|
Path backupRootPath = Paths.get(haloProperties.getBackupDir());
|
||||||
|
@ -225,10 +229,11 @@ public class BackupServiceImpl implements BackupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Resource loadFileAsResource(String fileName) {
|
public Resource loadFileAsResource(String basePath, String fileName) {
|
||||||
|
Assert.hasText(basePath, "Base path must not be blank");
|
||||||
Assert.hasText(fileName, "Backup file name must not be blank");
|
Assert.hasText(fileName, "Backup file name must not be blank");
|
||||||
|
|
||||||
Path backupParentPath = Paths.get(haloProperties.getBackupDir());
|
Path backupParentPath = Paths.get(basePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Files.notExists(backupParentPath)) {
|
if (Files.notExists(backupParentPath)) {
|
||||||
|
@ -237,7 +242,7 @@ public class BackupServiceImpl implements BackupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get backup file path
|
// Get backup file path
|
||||||
Path backupFilePath = Paths.get(haloProperties.getBackupDir(), fileName).normalize();
|
Path backupFilePath = Paths.get(basePath, fileName).normalize();
|
||||||
|
|
||||||
// Check directory traversal
|
// Check directory traversal
|
||||||
run.halo.app.utils.FileUtils.checkDirectoryTraversal(backupParentPath, backupFilePath);
|
run.halo.app.utils.FileUtils.checkDirectoryTraversal(backupParentPath, backupFilePath);
|
||||||
|
@ -258,7 +263,7 @@ public class BackupServiceImpl implements BackupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JSONObject exportData() {
|
public BackupDTO exportData() {
|
||||||
JSONObject data = new JSONObject();
|
JSONObject data = new JSONObject();
|
||||||
data.put("version", HaloConst.HALO_VERSION);
|
data.put("version", HaloConst.HALO_VERSION);
|
||||||
data.put("export_date", DateUtil.now());
|
data.put("export_date", DateUtil.now());
|
||||||
|
@ -283,7 +288,67 @@ public class BackupServiceImpl implements BackupService {
|
||||||
data.put("tags", tagService.listAll());
|
data.put("tags", tagService.listAll());
|
||||||
data.put("theme_settings", themeSettingService.listAll());
|
data.put("theme_settings", themeSettingService.listAll());
|
||||||
data.put("user", userService.listAll());
|
data.put("user", userService.listAll());
|
||||||
return data;
|
|
||||||
|
try {
|
||||||
|
String haloDataFileName = HaloConst.HALO_DATA_EXPORT_PREFIX +
|
||||||
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss-")) +
|
||||||
|
IdUtil.simpleUUID().hashCode() + ".json";
|
||||||
|
|
||||||
|
Path haloDataPath = Files.createFile(Paths.get(haloProperties.getDataExportDir(), haloDataFileName));
|
||||||
|
|
||||||
|
FileWriter fileWriter = new FileWriter(haloDataPath.toFile(), CharsetUtil.UTF_8);
|
||||||
|
fileWriter.write(data.toJSONString());
|
||||||
|
|
||||||
|
return buildBackupDto(DATA_EXPORT_BASE_URI, haloDataPath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ServiceException("导出数据失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BackupDTO> listExportedData() {
|
||||||
|
|
||||||
|
Path exportedDataParentPath = Paths.get(haloProperties.getDataExportDir());
|
||||||
|
if (Files.notExists(exportedDataParentPath)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Stream<Path> subPathStream = Files.list(exportedDataParentPath)) {
|
||||||
|
return subPathStream
|
||||||
|
.filter(backupPath -> StringUtils.startsWithIgnoreCase(backupPath.getFileName().toString(), HaloConst.HALO_DATA_EXPORT_PREFIX))
|
||||||
|
.map(backupPath -> buildBackupDto(DATA_EXPORT_BASE_URI, backupPath))
|
||||||
|
.sorted((leftBackup, rightBackup) -> {
|
||||||
|
// Sort the result
|
||||||
|
if (leftBackup.getUpdateTime() < rightBackup.getUpdateTime()) {
|
||||||
|
return 1;
|
||||||
|
} else if (leftBackup.getUpdateTime() > rightBackup.getUpdateTime()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ServiceException("Failed to fetch exported data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteExportedData(String fileName) {
|
||||||
|
Assert.hasText(fileName, "File name must not be blank");
|
||||||
|
|
||||||
|
Path dataExportRootPath = Paths.get(haloProperties.getDataExportDir());
|
||||||
|
|
||||||
|
Path backupPath = dataExportRootPath.resolve(fileName);
|
||||||
|
|
||||||
|
run.halo.app.utils.FileUtils.checkDirectoryTraversal(dataExportRootPath, backupPath);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -363,14 +428,14 @@ public class BackupServiceImpl implements BackupService {
|
||||||
* @param backupPath backup path must not be null
|
* @param backupPath backup path must not be null
|
||||||
* @return backup dto
|
* @return backup dto
|
||||||
*/
|
*/
|
||||||
private BackupDTO buildBackupDto(@NonNull Path backupPath) {
|
private BackupDTO buildBackupDto(@NonNull String basePath, @NonNull Path backupPath) {
|
||||||
|
Assert.notNull(basePath, "Base path must not be null");
|
||||||
Assert.notNull(backupPath, "Backup path must not be null");
|
Assert.notNull(backupPath, "Backup path must not be null");
|
||||||
|
|
||||||
String backupFileName = backupPath.getFileName().toString();
|
String backupFileName = backupPath.getFileName().toString();
|
||||||
BackupDTO backup = new BackupDTO();
|
BackupDTO backup = new BackupDTO();
|
||||||
try {
|
try {
|
||||||
backup.setDownloadUrl(buildDownloadUrl(backupFileName));
|
backup.setDownloadLink(buildDownloadUrl(basePath, backupFileName));
|
||||||
backup.setDownloadLink(backup.getDownloadUrl());
|
|
||||||
backup.setFilename(backupFileName);
|
backup.setFilename(backupFileName);
|
||||||
backup.setUpdateTime(Files.getLastModifiedTime(backupPath).toMillis());
|
backup.setUpdateTime(Files.getLastModifiedTime(backupPath).toMillis());
|
||||||
backup.setFileSize(Files.size(backupPath));
|
backup.setFileSize(Files.size(backupPath));
|
||||||
|
@ -388,11 +453,12 @@ public class BackupServiceImpl implements BackupService {
|
||||||
* @return download url
|
* @return download url
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
private String buildDownloadUrl(@NonNull String filename) {
|
private String buildDownloadUrl(@NonNull String basePath, @NonNull String filename) {
|
||||||
|
Assert.notNull(basePath, "Base path must not be null");
|
||||||
Assert.hasText(filename, "File name must not be blank");
|
Assert.hasText(filename, "File name must not be blank");
|
||||||
|
|
||||||
// Composite http url
|
// Composite http url
|
||||||
String backupUri = BACKUP_RESOURCE_BASE_URI + HaloUtils.URL_SEPARATOR + filename;
|
String backupUri = basePath + HaloUtils.URL_SEPARATOR + filename;
|
||||||
|
|
||||||
// Get a one-time token
|
// Get a one-time token
|
||||||
String oneTimeToken = oneTimeTokenService.create(backupUri);
|
String oneTimeToken = oneTimeTokenService.create(backupUri);
|
||||||
|
|
Loading…
Reference in New Issue