|
|
@@ -0,0 +1,150 @@
|
|
|
+import cron from 'node-cron';
|
|
|
+import fs from 'fs/promises'; // 使用 Promise 版本的 fs API
|
|
|
+import path from 'path';
|
|
|
+
|
|
|
+import { Files, Goods } from '../model/index.js';
|
|
|
+import { clearLoginCode } from '../controller/userController.js';
|
|
|
+import { isEnv, upStaticFileUrl, writeLogUrl } from '../config/config.default.js';
|
|
|
+import { AddTxtFileFu } from './index.js';
|
|
|
+
|
|
|
+/**
|
|
|
+ * 增强版清理函数:删除孤立Files记录及对应的静态文件
|
|
|
+ */
|
|
|
+export const clearFileFu = async (Model: any) => {
|
|
|
+ try {
|
|
|
+ console.log('开始清理孤立的files数据和静态文件...');
|
|
|
+
|
|
|
+ // 1. 聚合管道:找出所有videos文档中正在使用的fileId
|
|
|
+ const usedFileIds = await Model.aggregate([
|
|
|
+ { $unwind: '$fileIds' },
|
|
|
+ { $group: { _id: null, ids: { $addToSet: '$fileIds' } } },
|
|
|
+ ]);
|
|
|
+ const usedIdsArray = usedFileIds.length > 0 ? usedFileIds[0].ids : [];
|
|
|
+
|
|
|
+ // 2. 查找需要删除的孤立文件记录
|
|
|
+ const filesToDelete = await Files.find({
|
|
|
+ _id: { $nin: usedIdsArray },
|
|
|
+ });
|
|
|
+
|
|
|
+ if (filesToDelete.length === 0) {
|
|
|
+ console.log('没有需要清理的孤立文件记录。');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`找到 ${filesToDelete.length} 条待删除的文件记录,开始清理静态文件...`);
|
|
|
+
|
|
|
+ // 3. 并行删除静态文件,并处理结果
|
|
|
+ const fileDeletionResults = await Promise.allSettled(
|
|
|
+ filesToDelete.map(async (fileDoc) => {
|
|
|
+ const deletionPromises = [];
|
|
|
+
|
|
|
+ // 处理 originalUrl
|
|
|
+ if (fileDoc.originalUrl) {
|
|
|
+ // 关键:将URL路径转换为服务器上的绝对路径
|
|
|
+ const fullOriginalPath = path.join(
|
|
|
+ isEnv ? process.cwd() : upStaticFileUrl,
|
|
|
+ '',
|
|
|
+ fileDoc.originalUrl
|
|
|
+ );
|
|
|
+ deletionPromises.push(deleteFileSafely(fullOriginalPath));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理 compressedUrl
|
|
|
+ if (fileDoc.compressedUrl) {
|
|
|
+ const fullCompressedPath = path.join(
|
|
|
+ isEnv ? process.cwd() : upStaticFileUrl,
|
|
|
+ '',
|
|
|
+ fileDoc.compressedUrl
|
|
|
+ );
|
|
|
+ deletionPromises.push(deleteFileSafely(fullCompressedPath));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待当前文件的所有路径删除完成
|
|
|
+ await Promise.allSettled(deletionPromises);
|
|
|
+ return fileDoc._id; // 返回成功处理了文件的文档ID
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ // 4. 分析文件删除结果
|
|
|
+ const successfulFileDeletions: any[] = [];
|
|
|
+ const failedFileDeletions: any[] = [];
|
|
|
+
|
|
|
+ fileDeletionResults.forEach((result, index) => {
|
|
|
+ if (result.status === 'fulfilled') {
|
|
|
+ successfulFileDeletions.push(result.value);
|
|
|
+ } else {
|
|
|
+ console.error(`删除第${index}条记录的静态文件时失败:`, result.reason);
|
|
|
+ failedFileDeletions.push(filesToDelete[index]._id);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 5. 删除数据库记录(仅删除静态文件已清理或本来就不存在的记录)
|
|
|
+ // 注意:即使静态文件删除失败,也可能选择不删除数据库记录,以便手动处理
|
|
|
+ const recordsToDeleteFromDB = successfulFileDeletions; // 这里可以根据策略调整
|
|
|
+ const deleteResult = await Files.deleteMany({
|
|
|
+ _id: { $in: recordsToDeleteFromDB },
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(`清理完成!`);
|
|
|
+ if (!isEnv)
|
|
|
+ AddTxtFileFu(
|
|
|
+ writeLogUrl,
|
|
|
+ 'config/access.log',
|
|
|
+ `- 成功删除静态文件并移除数据库记录:${deleteResult.deletedCount} 条`
|
|
|
+ );
|
|
|
+ if (!isEnv)
|
|
|
+ AddTxtFileFu(
|
|
|
+ writeLogUrl,
|
|
|
+ 'config/access.log',
|
|
|
+ `- 静态文件删除失败(数据库记录保留): ${failedFileDeletions.length} 条`
|
|
|
+ );
|
|
|
+
|
|
|
+ if (failedFileDeletions.length > 0) {
|
|
|
+ if (!isEnv)
|
|
|
+ AddTxtFileFu(
|
|
|
+ writeLogUrl,
|
|
|
+ 'config/access.log',
|
|
|
+ `以下记录的静态文件可能需要手动处理:${failedFileDeletions}`
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ if (!isEnv) AddTxtFileFu(writeLogUrl, 'config/access.log', `清理过程中发生未知错误${error}`);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 安全删除文件的辅助函数
|
|
|
+ * @param {string} filePath 文件的绝对路径
|
|
|
+ */
|
|
|
+async function deleteFileSafely(filePath: string) {
|
|
|
+ try {
|
|
|
+ await fs.access(filePath); // 检查文件是否存在
|
|
|
+ await fs.unlink(filePath); // 删除文件
|
|
|
+ console.log(`成功删除静态文件: ${path.basename(filePath)}`);
|
|
|
+ } catch (err: any) {
|
|
|
+ if (err.code === 'ENOENT') {
|
|
|
+ // 文件不存在,可记录日志或忽略
|
|
|
+ console.warn(`文件不存在,无需删除: ${filePath}`);
|
|
|
+ } else {
|
|
|
+ // 其他错误(如权限不足),重新抛出供上层处理
|
|
|
+ throw new Error(`删除文件 ${filePath} 时出错: ${err.message}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export const clearAllFu = () => {
|
|
|
+ // 每晚12点清理文件
|
|
|
+ cron.schedule(
|
|
|
+ '0 0 * * *',
|
|
|
+ () => {
|
|
|
+ console.log('执行定时任务...');
|
|
|
+ // 清理 展品展示中 没有使用的file文件
|
|
|
+ clearFileFu(Goods);
|
|
|
+ // 清理用户登录的验证码对象
|
|
|
+ clearLoginCode();
|
|
|
+ },
|
|
|
+ {
|
|
|
+ timezone: 'Asia/Shanghai', // 根据你的服务器时区设置
|
|
|
+ }
|
|
|
+ );
|
|
|
+};
|