mirror of
https://github.com/wushuo894/ani-rss.git
synced 2026-03-13 09:20:23 +00:00
feat: 新增设置导入与导出 close #623
This commit is contained in:
@@ -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<Void> 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("导入成功");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<File> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
<el-collapse-item name="7" title="其他">
|
||||
<other :config="props.config"/>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item name="8" title="备份">
|
||||
<backup :config="props.config"/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</template>
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
71
ani-rss-ui/src/config/basic/Backup.vue
Normal file
71
ani-rss-ui/src/config/basic/Backup.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<input id="backup-file" hidden="hidden" type="file" @change="changeFile">
|
||||
<div class="content flex">
|
||||
<el-button bg @click="exportConfig" icon="Upload">导出设置</el-button>
|
||||
<el-button bg @click="importConfig" icon="Download">导入设置</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {authorization} from "@/js/global.js";
|
||||
import * as http from "@/js/http.js"
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {markRaw} from "vue";
|
||||
import {WarnTriangleFilled} from "@element-plus/icons-vue";
|
||||
|
||||
let importConfig = () => {
|
||||
ElMessageBox.confirm(
|
||||
`<strong style="color: var(--el-color-danger);">
|
||||
将会覆盖掉现有的设置、订阅、下载记录, 是否执意继续?
|
||||
</strong>`,
|
||||
'警告',
|
||||
{
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: '继续',
|
||||
confirmButtonClass: 'is-text is-has-bg el-button--danger',
|
||||
cancelButtonText: '取消',
|
||||
cancelButtonClass: 'is-text is-has-bg',
|
||||
type: 'warning',
|
||||
icon: markRaw(WarnTriangleFilled),
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
let element = document.querySelector('#backup-file');
|
||||
element.click();
|
||||
})
|
||||
}
|
||||
|
||||
let changeFile = () => {
|
||||
let element = document.querySelector('#backup-file');
|
||||
http.importConfig(element.files[0])
|
||||
.then(res => {
|
||||
let {code, message} = res
|
||||
if (code !== 200) {
|
||||
ElMessage.error(message)
|
||||
return
|
||||
}
|
||||
ElMessage.success(message)
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
let exportConfig = () => {
|
||||
let element = document.createElement('a');
|
||||
element.href = `api/exportConfig?s=${authorization.value}`
|
||||
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
|
||||
let props = defineProps(['config'])
|
||||
</script>
|
||||
<style scoped>
|
||||
.content {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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<unknown>}
|
||||
*/
|
||||
export let deleteTorrent = (id, hash) => api.post(`api/deleteTorrent?id=${id}&hash=${hash}`)
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user