Lan преди 1 месец
родител
ревизия
5462e66837

BIN
backend/api/__pycache__/scheduler.cpython-310.pyc


+ 18 - 0
backend/api/migrations/0026_file_archived.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.2 on 2025-05-22 22:12
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0025_user_session_key"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="file",
+            name="archived",
+            field=models.BooleanField(default=False),
+        ),
+    ]

BIN
backend/api/migrations/__pycache__/0026_file_archived.cpython-310.pyc


BIN
backend/api/models/__pycache__/file.cpython-310.pyc


+ 90 - 10
backend/api/models/file.py

@@ -13,6 +13,7 @@ from django.contrib.auth.hashers import make_password
 from io import TextIOWrapper, BytesIO
 from ast import literal_eval
 from typing import Any
+import zipfile
 
 types = [
     ('csv', 'csv'),
@@ -93,15 +94,24 @@ class FileManager(models.Manager):
             directory = os.path.join(BASE_FILE_PATH, str(user.id))
             path = os.path.join(directory, str(fileId))
             
-            try:
-                size = os.path.getsize(path)
-            except FileNotFoundError:
-                print("未找到对应文件,现将记录删除", fileId, file.name)
-                self.get(id=fileId).delete()
-                continue
-            except Exception as error:
-                print("读取历史记录时出现未知错误")
-                return FAILED
+            # 首先检查文件是否被归档压缩
+            archivePath = os.path.join(BASE_FILE_PATH, "archive.zip")
+            with zipfile.ZipFile(archivePath, 'a') as zipf:
+                fileArchPath = os.path.normpath(f"{file.user.id}/{file.id}").replace("\\", "/")
+                if fileArchPath in zipf.namelist():
+                    # 重复添加压缩,则跳过压缩步骤直接将原始文件删除即可
+                    size = zipf.getinfo(fileArchPath).file_size
+                else:
+                    # 文件未被压缩,查找是否真的有文件存在,否则清理掉没有实际文件的数据库记录
+                    try:
+                        size = os.path.getsize(path)
+                    except FileNotFoundError:
+                        print("未找到对应文件,现将记录删除", fileId, file.name)
+                        self.get(id=fileId).delete()
+                        continue
+                    except Exception as error:
+                        print("读取历史记录时出现未知错误")
+                        return FAILED
                 
             if size >= 1024 * 1024:
                 size = size / (1024 * 1024)
@@ -151,6 +161,7 @@ class File(models.Model):
     update_time = models.DateTimeField(auto_now=True)
     content = models.CharField(choices=contents, max_length=10)
     encrypted = models.BooleanField(default=False)
+    archived = models.BooleanField(default=False)
     key = models.CharField(blank=True, null=True, max_length=128)
     associate = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True)
     
@@ -252,11 +263,71 @@ class File(models.Model):
             return False
         return True
     
-    
+    def archive(self):
+        if not self.archived:
+            filePath = os.path.join(os.path.join(BASE_FILE_PATH, str(self.user.id)), str(self.id))
+            archivePath = os.path.join(BASE_FILE_PATH, "archive.zip")
+            try:
+                with zipfile.ZipFile(archivePath, 'a') as zipf:
+                    fileArchPath = os.path.normpath(f"{self.user.id}/{self.id}").replace("\\", "/")
+                    if fileArchPath in zipf.namelist():
+                        # 重复添加压缩,则跳过压缩步骤直接将原始文件删除即可
+                        self.archived = True
+                        self.save()
+                        os.remove(filePath)
+                    else:
+                        # 使用用户id和文件id组合成压缩文件中索引
+                        zipf.write(filePath, fileArchPath)
+                        self.archived = True
+                        self.save()
+                        os.remove(filePath)
+            except Exception as error:
+                logger.error(f"压缩文件{self.id} {self.name}失败: {error}")
+        else:
+            pass
 
+    def unzip(self):
+        if not self.archived:
+            self.error(f"解压文件{self.id} {self.name}失败,文件并未压缩")
+            return
+        else:
+            filePath = os.path.join(os.path.join(BASE_FILE_PATH, str(self.user.id)), str(self.id))
+            archivePath = os.path.join(BASE_FILE_PATH, "archive.zip")
+            try:
+                with zipfile.ZipFile(archivePath, 'r') as zipf:
+                    fileArchPath = os.path.normpath(f"{self.user.id}/{self.id}").replace("\\", "/")
+                    if fileArchPath in zipf.namelist():
+                        with zipf.open(fileArchPath) as zipd:
+                            content = zipd.read()
+                            with open(filePath, 'wb') as f:
+                                f.write(content)
+                            # 恢复压缩标记
+                            self.archived = False
+                            self.save()
+                    else:
+                        raise ValueError(f"该文件不存在压缩文件中")
+            except Exception as error:
+                logger.error(f"解压缩文件{self.id} {self.name}失败:{error}")
 
     def download(self):
         path = os.path.join(os.path.join(BASE_FILE_PATH, str(self.user.id)), str(self.id))
+        archivePath = os.path.join(BASE_FILE_PATH, "archive.zip")
+        # 需要检查文件是否被归档,下载归档文件并不需要解压
+        if self.archived:
+            with zipfile.ZipFile(archivePath, 'r') as zipf:
+                fileArchPath = os.path.normpath(f"{self.user.id}/{self.id}").replace("\\", "/")
+                if fileArchPath in zipf.namelist():
+                    # 在压缩文件中找到文件,将其数据读出用于下载
+                    with zipf.open(fileArchPath) as zfile:
+                        logger.info("从压缩包中下载")
+                        content = zfile.read()
+                        response = FileResponse(BytesIO(content))
+                        response['Content-Disposition'] = f'attachment; filename="{self.name}"'
+                        return FileResponse(open(path, 'rb'))
+                else:
+                    logger.info(f"文件{self.id} {self.name}具有压缩标记,但未在压缩文件中找到")
+                    raise ValueError(f"文件{self.id} {self.name}具有压缩标记,但未在压缩文件中找到")
+        
         if not os.path.exists(path):
             return False
         # 加密后文件也不允许下载
@@ -350,6 +421,7 @@ class File(models.Model):
             return UNKNOWN_CONTENT
 
     def storage(self, file):
+        # 将file数据保存成文件,不对file做任何处理
         try:
             path = os.path.join(BASE_FILE_PATH, str(self.user.id))
             if os.path.exists(os.path.join(path, str(self.id))):
@@ -374,6 +446,10 @@ class File(models.Model):
     
     # 检查文件是否合法
     def checkIllegal(self):
+        # 检查文件前需要检查是否被压缩,如被压缩则需要解压
+        if self.archived:
+            self.unzip()
+
         path = os.path.join(os.path.join(BASE_FILE_PATH, str(self.user.id)), str(self.id))
         path2 = os.path.join(os.path.join(BASE_FILE_PATH, str(self.user.id)), str(self.associate.id))
         if self.content == 'node':
@@ -429,6 +505,10 @@ class File(models.Model):
                 return True
     
     def toJson(self, request=None):
+        # 需要检查文件是否被归档压缩,如有则需要先解压
+        if self.archived:
+            self.unzip()
+
         # 检查是否为加密文件,只有当文件usage为input时才应该存在加密属性
         if self.usage == 'input' and self.encrypted:
             # 如果被加密则需要从request中获取解密密钥

+ 32 - 4
backend/api/scheduler.py

@@ -2,10 +2,13 @@
 import logging
 from apscheduler.schedulers.background import BackgroundScheduler
 from django_apscheduler.jobstores import DjangoJobStore
-from .models import Alert
+from .models import Alert, File
 from .utils import SYSTEMPERFORMANCE, TRIGGEREDALERTS, BASE_FILE_PATH
 from django.db import DatabaseError
 from pathlib import Path
+import zipfile
+import os
+from datetime import datetime, timedelta
 
 logger = logging.getLogger("scheduler")
 
@@ -43,6 +46,19 @@ def checkAlert():
         except DatabaseError as e:
             logger.error(f"获取告警数据失败: {str(e)}")
 
+# 文档归档检查,修改时间超过七天的文件将被压缩
+# 对应方法:File.archive / File.unZip 在FileModel中
+def checkArchive():
+    now = datetime.now()
+    cutoff = now - timedelta(days=7)
+    expired_files = File.objects.filter(update_time__lt=cutoff)
+    # for file in File.objects.all():
+    #     file.archived = False
+    #     file.save()
+    for file in expired_files.all():
+        file.archive()
+
+
 # 初始化调度器
 scheduler = BackgroundScheduler()
 
@@ -51,16 +67,28 @@ def start_scheduler():
         # 使用Django的JobStore
         scheduler.add_jobstore(DjangoJobStore(), "default")
         # 添加任务(避免重复添加)
-        if not scheduler.get_job("5_seconds_job"):
+        # 系统性能监视任务
+        if not scheduler.get_job("2_seconds_monitor_job"):
             scheduler.add_job(
                 checkAlert,
                 'interval',
                 seconds=2,
-                id="5_seconds_job",
+                id="2_seconds_job",
                 replace_existing=True,
             )
             logger.info("定时任务已启动")
-        
+        # 文件归档计划任务
+        if not scheduler.get_job("3600_seconds_archive_job"):
+            scheduler.add_job(
+                checkArchive,
+                'interval',
+                # 正常应当尽可能久的检查一次,这里每小时检查一次,调试时可改为5秒一次
+                seconds=3600,
+                # seconds = 5,
+                id="3600_seconds_archive_job",
+                replace_existing=True,
+            )
+
         # 启动调度器
         if not scheduler.running:
             scheduler.start()

BIN
backend/db.sqlite3


BIN
scheduler/algo1Folder/测试输出/边集(教师模型测试).xlsx


BIN
scheduler/algo1Folder/测试输出/边集(教师测试删除).xlsx


BIN
scheduler/algo1Folder/测试输出/边集(教师预测).xlsx


BIN
scheduler/algo3Folder/测试输出/边集(测试).xlsx


BIN
scheduler/algo3Folder/测试输出/边集(预测).xlsx