diff --git a/ani-rss-application/src/main/java/ani/rss/controller/ConfigController.java b/ani-rss-application/src/main/java/ani/rss/controller/ConfigController.java index 2de8af00..4ef5fffc 100644 --- a/ani-rss-application/src/main/java/ani/rss/controller/ConfigController.java +++ b/ani-rss-application/src/main/java/ani/rss/controller/ConfigController.java @@ -20,6 +20,7 @@ import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.Assert; import cn.hutool.core.text.StrFormatter; import cn.hutool.core.util.*; import cn.hutool.http.Header; @@ -32,10 +33,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jsoup.Jsoup; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -277,4 +281,49 @@ public class ConfigController extends BaseController { OutputStream outputStream = response.getOutputStream(); IoUtil.writeUtf8(outputStream, true, customCss); } + + @Auth + @Operation(summary = "导出设置") + @GetMapping("/exportConfig") + public void backupConfig() throws IOException { + String version = MavenUtils.getVersion(); + String filename = StrUtil.format("ani-rss.backup.{}.zip", version); + + String contentType = getContentType(filename); + + HttpServletResponse response = Global.RESPONSE.get(); + + response.setContentType(contentType); + response.setHeader(Header.CONTENT_DISPOSITION.toString(), StrFormatter.format("inline; filename=\"{}\"", filename)); + + @Cleanup + OutputStream outputStream = response.getOutputStream(); + + ConfigUtil.backup(outputStream); + } + + @Auth + @Operation(summary = "导入设置") + @PostMapping("/importConfig") + public Result importConfig(@RequestParam("file") MultipartFile file) throws IOException { + String originalFilename = file.getOriginalFilename(); + String extName = FileUtil.extName(originalFilename); + Assert.isTrue("zip".equals(extName), "导入格式异常"); + + File configDir = ConfigUtil.getConfigDir(); + + // 删除旧的种子记录 + FileUtil.del(configDir + "/torrents"); + + @Cleanup + InputStream inputStream = file.getInputStream(); + + ZipUtil.unzip(inputStream, configDir, StandardCharsets.UTF_8); + + // 重新加载设置 + ConfigUtil.load(); + AniUtil.load(); + + return Result.success("导入成功"); + } } diff --git a/ani-rss-application/src/main/java/ani/rss/util/other/AniUtil.java b/ani-rss-application/src/main/java/ani/rss/util/other/AniUtil.java index 29240786..4caef3df 100644 --- a/ani-rss-application/src/main/java/ani/rss/util/other/AniUtil.java +++ b/ani-rss-application/src/main/java/ani/rss/util/other/AniUtil.java @@ -59,6 +59,7 @@ public class AniUtil { .setIgnoreNullValue(true) .setOverride(false); + ANI_LIST.clear(); for (Ani ani : anis) { Ani newAni = AniUtil.createAni(); BeanUtil.copyProperties(newAni, ani, copyOptions); diff --git a/ani-rss-application/src/main/java/ani/rss/util/other/ConfigUtil.java b/ani-rss-application/src/main/java/ani/rss/util/other/ConfigUtil.java index 12c99eae..699466bf 100644 --- a/ani-rss-application/src/main/java/ani/rss/util/other/ConfigUtil.java +++ b/ani-rss-application/src/main/java/ani/rss/util/other/ConfigUtil.java @@ -15,6 +15,7 @@ import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.func.Func1; import cn.hutool.core.lang.func.LambdaUtil; import cn.hutool.core.text.StrFormatter; @@ -25,9 +26,11 @@ import cn.hutool.core.util.ZipUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.system.OsInfo; import cn.hutool.system.SystemUtil; +import lombok.Cleanup; import lombok.extern.slf4j.Slf4j; import java.io.File; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Date; @@ -338,6 +341,19 @@ public class ConfigUtil { log.info("正在备份设置 {}", backupFile.getName()); + try { + @Cleanup + OutputStream outputStream = FileUtil.getOutputStream(backupFile); + backup(outputStream); + log.info("备份设置成功 {}", backupFile.getName()); + } catch (Exception e) { + log.error("备份失败 {}", backupFile.getName()); + log.error(e.getMessage(), e); + } + } + + public static synchronized void backup(OutputStream outputStream) { + File configDir = getConfigDir(); List backupFiles = Stream.of( "files", "torrents", "database.db", AniUtil.FILE_NAME, ConfigUtil.FILE_NAME @@ -348,19 +364,16 @@ public class ConfigUtil { .toList(); try { - ZipUtil.zip(backupFile, StandardCharsets.UTF_8, true, pathname -> { + ZipUtil.zip(outputStream, StandardCharsets.UTF_8, true, pathname -> { if (pathname.isFile()) { - return !List.of(".DS_Store", ".DS_Store@SynoResource") - .contains(pathname.getName()); + String name = pathname.getName(); + return !name.startsWith("."); } File[] files = FileUtils.listFiles(pathname); return !ArrayUtil.isEmpty(files); }, backupFiles.toArray(new File[0])); - - log.info("备份设置成功 {}", backupFile.getName()); - } catch (Exception e) { - log.error("备份失败 {}", backupFile.getName()); - log.error(e.getMessage(), e); + } finally { + IoUtil.close(outputStream); } } diff --git a/ani-rss-ui/src/config/Basic.vue b/ani-rss-ui/src/config/Basic.vue index e33ba6b3..8532ccbb 100644 --- a/ani-rss-ui/src/config/Basic.vue +++ b/ani-rss-ui/src/config/Basic.vue @@ -21,6 +21,9 @@ + + + @@ -33,6 +36,7 @@ import Rss from "@/config/basic/Rss.vue"; import Trackers from "@/config/basic/Trackers.vue"; import Other from "@/config/basic/Other.vue"; import Bangumi from "@/config/basic/Bangumi.vue"; +import Backup from "./basic/Backup.vue"; let activeName = ref('1') diff --git a/ani-rss-ui/src/config/basic/Backup.vue b/ani-rss-ui/src/config/basic/Backup.vue new file mode 100644 index 00000000..a1c24dc9 --- /dev/null +++ b/ani-rss-ui/src/config/basic/Backup.vue @@ -0,0 +1,71 @@ + + + \ No newline at end of file diff --git a/ani-rss-ui/src/js/api.js b/ani-rss-ui/src/js/api.js index a84861e4..c41392f0 100644 --- a/ani-rss-ui/src/js/api.js +++ b/ani-rss-ui/src/js/api.js @@ -26,9 +26,9 @@ let fetch_ = async (url, method, body) => { headers['Content-Type'] = 'application/json' } return await fetch(url, { - 'method': method, - 'body': body ? JSON.stringify(body) : null, - 'headers': headers + method: method, + body: body ? JSON.stringify(body) : null, + headers: headers }) .then(res => res.json()) .then(res => { diff --git a/ani-rss-ui/src/js/http.js b/ani-rss-ui/src/js/http.js index 777249b0..f122481b 100644 --- a/ani-rss-ui/src/js/http.js +++ b/ani-rss-ui/src/js/http.js @@ -1,5 +1,6 @@ import api from "@/js/api.js"; import CryptoJS from "crypto-js"; +import {authorization} from "./global.js"; /** * 获取设置 @@ -351,4 +352,16 @@ export let getAniBySubjectId = (id) => api.post(`api/getAniBySubjectId?id=${id}` * @param hash 种子hash * @returns {Promise} */ -export let deleteTorrent = (id, hash) => api.post(`api/deleteTorrent?id=${id}&hash=${hash}`) \ No newline at end of file +export let deleteTorrent = (id, hash) => api.post(`api/deleteTorrent?id=${id}&hash=${hash}`) + +export let importConfig = (file) => { + const formData = new FormData(); + formData.append("file", file); + return fetch('api/importConfig', { + method: 'POST', + body: formData, + headers: { + 'Authorization': authorization.value + } + }).then(res => res.json()) +} \ No newline at end of file