6 Commits 27323dafe7 ... ed8e86f284

Autore SHA1 Messaggio Data
  Lan ed8e86f284 初始化 2 mesi fa
  lan 27323dafe7 上传文件至 '' 6 mesi fa
  lan f90867236b 上传文件至 '' 6 mesi fa
  lan c374a77ff3 上传文件至 '' 7 mesi fa
  lan 4085787fda 上传文件至 '' 7 mesi fa
  Lan 7e6c2bd9fe initial 7 mesi fa
100 ha cambiato i file con 2052 aggiunte e 20 eliminazioni
  1. 31 0
      .gitignore
  2. 0 20
      .vscode/settings.json
  3. 0 0
      aes.js
  4. 0 0
      backend/api/__init__.py
  5. BIN
      backend/api/__pycache__/__init__.cpython-310.pyc
  6. BIN
      backend/api/__pycache__/admin.cpython-310.pyc
  7. BIN
      backend/api/__pycache__/api_calculate.cpython-310.pyc
  8. BIN
      backend/api/__pycache__/api_login.cpython-310.pyc
  9. BIN
      backend/api/__pycache__/api_prepare.cpython-310.pyc
  10. BIN
      backend/api/__pycache__/api_rawDataTrans.cpython-310.pyc
  11. BIN
      backend/api/__pycache__/api_user.cpython-310.pyc
  12. BIN
      backend/api/__pycache__/apps.cpython-310.pyc
  13. BIN
      backend/api/__pycache__/models.cpython-310.pyc
  14. BIN
      backend/api/__pycache__/serializers.cpython-310.pyc
  15. BIN
      backend/api/__pycache__/tokenAuthentication.cpython-310.pyc
  16. BIN
      backend/api/__pycache__/urls.cpython-310.pyc
  17. BIN
      backend/api/__pycache__/utils.cpython-310.pyc
  18. BIN
      backend/api/__pycache__/views.cpython-310.pyc
  19. 3 0
      backend/api/admin.py
  20. 79 0
      backend/api/api_calculate.py
  21. 22 0
      backend/api/api_dashboard.py
  22. 185 0
      backend/api/api_prepare.py
  23. 68 0
      backend/api/api_rawDataTrans.py
  24. 82 0
      backend/api/api_user.py
  25. 6 0
      backend/api/apps.py
  26. 28 0
      backend/api/migrations/0001_initial.py
  27. 22 0
      backend/api/migrations/0002_alter_user_options_user_last_login.py
  28. 38 0
      backend/api/migrations/0003_view_file.py
  29. 27 0
      backend/api/migrations/0004_rename_display_name_user_displayname_file_usage.py
  30. 34 0
      backend/api/migrations/0005_file_associate_file_content.py
  31. 24 0
      backend/api/migrations/0006_alter_file_associate.py
  32. 41 0
      backend/api/migrations/0007_fileinfo.py
  33. 89 0
      backend/api/migrations/0008_mission_result.py
  34. 28 0
      backend/api/migrations/0009_alter_fileinfo_file_alter_mission_name.py
  35. 84 0
      backend/api/migrations/0010_algorithm_plan.py
  36. 39 0
      backend/api/migrations/0011_result_plan_result_state.py
  37. 45 0
      backend/api/migrations/0012_result_edgefile_result_nodefile_alter_result_plan.py
  38. 30 0
      backend/api/migrations/0013_remove_result_state_alter_file_usage.py
  39. 0 0
      backend/api/migrations/__init__.py
  40. BIN
      backend/api/migrations/__pycache__/0001_initial.cpython-310.pyc
  41. BIN
      backend/api/migrations/__pycache__/0002_alter_user_options_user_last_login.cpython-310.pyc
  42. BIN
      backend/api/migrations/__pycache__/0003_view_file.cpython-310.pyc
  43. BIN
      backend/api/migrations/__pycache__/0004_rename_display_name_user_displayname_file_usage.cpython-310.pyc
  44. BIN
      backend/api/migrations/__pycache__/0005_file_associate_file_content.cpython-310.pyc
  45. BIN
      backend/api/migrations/__pycache__/0006_alter_file_associate.cpython-310.pyc
  46. BIN
      backend/api/migrations/__pycache__/0007_fileinfo.cpython-310.pyc
  47. BIN
      backend/api/migrations/__pycache__/0008_mission_result.cpython-310.pyc
  48. BIN
      backend/api/migrations/__pycache__/0009_alter_fileinfo_file_alter_mission_name.cpython-310.pyc
  49. BIN
      backend/api/migrations/__pycache__/0010_algorithm_plan.cpython-310.pyc
  50. BIN
      backend/api/migrations/__pycache__/0011_result_plan_result_state.cpython-310.pyc
  51. BIN
      backend/api/migrations/__pycache__/0012_result_edgefile_result_nodefile_alter_result_plan.cpython-310.pyc
  52. BIN
      backend/api/migrations/__pycache__/0013_remove_result_state_alter_file_usage.cpython-310.pyc
  53. BIN
      backend/api/migrations/__pycache__/__init__.cpython-310.pyc
  54. 7 0
      backend/api/models/__init__.py
  55. BIN
      backend/api/models/__pycache__/__init__.cpython-310.pyc
  56. BIN
      backend/api/models/__pycache__/algorithm.cpython-310.pyc
  57. BIN
      backend/api/models/__pycache__/file.cpython-310.pyc
  58. BIN
      backend/api/models/__pycache__/mission.cpython-310.pyc
  59. BIN
      backend/api/models/__pycache__/plan.cpython-310.pyc
  60. BIN
      backend/api/models/__pycache__/result.cpython-310.pyc
  61. BIN
      backend/api/models/__pycache__/user.cpython-310.pyc
  62. BIN
      backend/api/models/__pycache__/view.cpython-310.pyc
  63. 21 0
      backend/api/models/algorithm.py
  64. 284 0
      backend/api/models/file.py
  65. 24 0
      backend/api/models/mission.py
  66. 25 0
      backend/api/models/plan.py
  67. 31 0
      backend/api/models/result.py
  68. 55 0
      backend/api/models/user.py
  69. 12 0
      backend/api/models/view.py
  70. 32 0
      backend/api/serializers.py
  71. 3 0
      backend/api/tests.py
  72. 20 0
      backend/api/tokenAuthentication.py
  73. 16 0
      backend/api/urls.py
  74. 41 0
      backend/api/utils.py
  75. 3 0
      backend/api/views.py
  76. 0 0
      backend/backend/__init__.py
  77. BIN
      backend/backend/__pycache__/__init__.cpython-310.pyc
  78. BIN
      backend/backend/__pycache__/settings.cpython-310.pyc
  79. BIN
      backend/backend/__pycache__/urls.cpython-310.pyc
  80. BIN
      backend/backend/__pycache__/wsgi.cpython-310.pyc
  81. 16 0
      backend/backend/asgi.py
  82. 142 0
      backend/backend/settings.py
  83. 23 0
      backend/backend/urls.py
  84. 16 0
      backend/backend/wsgi.py
  85. BIN
      backend/db.sqlite3
  86. 22 0
      backend/manage.py
  87. 0 0
      backend/process/__init__.py
  88. 3 0
      backend/process/admin.py
  89. 6 0
      backend/process/apps.py
  90. 0 0
      backend/process/migrations/__init__.py
  91. 3 0
      backend/process/models.py
  92. 3 0
      backend/process/tests.py
  93. 3 0
      backend/process/views.py
  94. 0 0
      scheduler/__init__.py
  95. BIN
      scheduler/__pycache__/processManager.cpython-310.pyc
  96. BIN
      scheduler/__pycache__/utils.cpython-310.pyc
  97. 38 0
      scheduler/algo1Folder/controller.py
  98. 1 0
      scheduler/algorithms.config
  99. 197 0
      scheduler/processManager.py
  100. 0 0
      scheduler/process_logs/proc_20250314-122720_21672.stderr

+ 31 - 0
.gitignore

@@ -0,0 +1,31 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+uploads
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo

+ 0 - 20
.vscode/settings.json

@@ -1,20 +0,0 @@
-{
-  "MicroPython.executeButton": [
-    {
-      "text": "▶",
-      "tooltip": "运行",
-      "alignment": "left",
-      "command": "extension.executeFile",
-      "priority": 3.5
-    }
-  ],
-  "MicroPython.syncButton": [
-    {
-      "text": "$(sync)",
-      "tooltip": "同步",
-      "alignment": "left",
-      "command": "extension.execute",
-      "priority": 4
-    }
-  ]
-}

File diff suppressed because it is too large
+ 0 - 0
aes.js


+ 0 - 0
backend/api/__init__.py


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


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


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


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


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


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


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


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


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


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


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


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


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


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


+ 3 - 0
backend/api/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 79 - 0
backend/api/api_calculate.py

@@ -0,0 +1,79 @@
+from django.contrib import auth
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework.authtoken.models import Token
+from rest_framework.authentication import BasicAuthentication, TokenAuthentication
+from .serializers import UserRegisterSerializer
+
+from django.middleware.csrf import get_token
+from django.contrib.auth import login
+
+from api.utils import *
+from api.models import File, Mission
+import requests
+
+import json
+
+class CalculateAPI(APIView):
+  def post(self, request):
+    user = request.user
+    try:
+      mission = Mission.objects.get(id=request.data.get('mission'))
+    except Mission.DoesNotExist:
+      return failed(message="处理任务控制失败,未找到处理任务")
+    except Exception as error:
+      print("处理任务控制失败", error)
+      return failed(message="处理任务控制失败,未找到处理任务")
+    
+    command = request.data.get('command')
+    try:
+      assert command in ['start', 'pause', 'stop']
+    except Exception as error:
+      print("处理任务控制代码错误")
+      return failed(message="处理任务控制失败,控制代码错误")
+
+    # 向调度程序提交计算任务
+    # mission = request.json['mission']
+    # plans = request.json['plans']
+    calculateData = {
+      'mission': {
+        'id': mission.id,
+      },
+      'plans': []
+    }
+    rootPlan = mission.own_plans.get(parent=None)
+    calculateData['plans'].append({
+      'id': rootPlan.id,
+      'nodes': mission.nodeFile.toJson(),
+      'edges': mission.edgeFile.toJson(),
+      'children': list(mission.own_plans.filter(parent=rootPlan).values_list('id', flat=True)),
+    })
+
+    rootPlans = [ child for child in mission.own_plans.filter(parent=rootPlan)]
+    while rootPlans:
+      tempPlans = rootPlans.copy()
+      rootPlans = []
+      for p in tempPlans:
+        children = [ child for child in mission.own_plans.filter(parent=p)]
+        # 判断是否父节点存在计算结果,有则作为子节点输入
+        if hasattr(p.parent, 'own_result'):
+          calculateData['plans'].append({
+            'id': p.id,
+            'algorithm': p.algorithm.name,
+            'nodes': p.parent.own_result.nodeFile.toJson(),
+            'edges': p.parent.own_result.edgeFile.toJson(),
+            'children': [child.id for child in children],
+          })
+        else:
+          calculateData['plans'].append({
+            'id': p.id,
+            'algorithm': p.algorithm.name,
+            'nodes': None,
+            'edges': None,
+            'children': [child.id for child in children],
+          })
+        rootPlans.extend(children)
+    requests.post(SCHEDULER_BASE_URL + '/addMission', json=calculateData)
+    
+    return success(message="成功启动计算")

+ 22 - 0
backend/api/api_dashboard.py

@@ -0,0 +1,22 @@
+from django.contrib import auth
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework.authtoken.models import Token
+from rest_framework.authentication import BasicAuthentication, TokenAuthentication
+from .serializers import UserRegisterSerializer
+
+from django.middleware.csrf import get_token
+from django.contrib.auth import login
+
+from api.utils import *
+from api.models import File
+    
+class dashboardAPI(APIView):
+  def get(self, request):
+    user = request.user
+    views = user.own_views.all()
+    return Response()
+  
+  def post(self, request):
+    return Response()

+ 185 - 0
backend/api/api_prepare.py

@@ -0,0 +1,185 @@
+from django.contrib import auth
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework.authtoken.models import Token
+from rest_framework.authentication import BasicAuthentication, TokenAuthentication
+from .serializers import UserRegisterSerializer
+
+from django.middleware.csrf import get_token
+from django.contrib.auth import login
+
+from api.utils import *
+from api.models import File, Mission, Plan, Algorithm
+
+import json
+    
+class UploadFileAPI(APIView):
+    def get(self, request):
+        user = request.user
+        history = File.objects.getHistory(user=user)
+        if history == FAILED:
+            return failed(message="获取文件上传历史失败")
+        else:
+            return success(data=history)
+        
+    def post(self, request):
+        user = request.user
+        # 获取上传的文件对象
+        nodeFileName = request.data.get('nodeFileName')
+        edgeFileName = request.data.get('edgeFileName')
+        nodeFile = request.data.get('nodes')
+        edgeFile = request.data.get('edges')
+
+        # 检查文件用途并进行响应处理
+        file_usage = request.data.get('usage')
+        if file_usage == 'input':
+            pass
+        else:
+            return failed(message=filename + "上传失败 暂时只支持上传图处理原料文件")
+        
+        # 检查文件类型并进行相应处理
+        file_type = request.data.get('type')
+        for filename in [nodeFileName, edgeFileName]:
+            
+            if file_type == 'csv':
+                if filename.split('.')[-1] != 'csv':
+                    return failed(message=filename + "上传失败 文件类型和文件后缀名不匹配")
+            else:
+                return failed(message=filename + "上传失败 暂不支持CSV之外文件")
+
+        successUploadedFiles = []
+        pre_file = None
+        for filename, fileData in [[edgeFileName, edgeFile], [nodeFileName, nodeFile]]:
+            # 处理数据库中记录
+            file = File()
+            file.name = filename
+            file.user = request.user
+            file.type = file_type
+            file.usage = file_usage
+            # 一定先edge再node
+            if pre_file == None:
+                file.content = 'edge'
+            else:
+                file.content = 'node'
+            file.save()
+            # 尝试保存文件
+            result = file.storage(fileData)
+            if result != OK:
+                file.delete()
+                # 第二个文件错误,同样删除第一个文件
+                if pre_file:
+                    pre_file.delete()
+                if result == FILE_ALREADY_EXIST:
+                    return failed(message=filename + "上传失败 已存在同名文件", data="已存在同名文件", code=400)
+                elif result == FILE_FAILED_CREATE_DIR:
+                    return failed(message=filename + "上传失败 文件目录创建失败", data="文件目录创建失败", code=400)
+                else:
+                    return failed(message=filename + "上传失败 因未知原因文件上传失败", data="因未知原因文件上传失败", code=400)
+            # 第二个文件也成功时,将两个文件互相关联
+            if pre_file:
+                file.associate = pre_file
+                pre_file.associate = file
+                file.saveWithInfo()
+                pre_file.saveWithInfo()
+            else:
+                pre_file = file
+            # 两个文件均上传成功后,检测文件内容是否符合规范
+        if file.checkIllegal() and pre_file.checkIllegal():
+            # 确保正确后创建mission,并将信息返回
+            mission = Mission()
+            mission.nodeFile = file
+            mission.edgeFile = pre_file
+            mission.user = user
+            mission.save()
+            successUploadedFiles.append({
+                "id": mission.id,
+                "name": mission.name,
+                "content": "mission",
+            })
+            
+            # 初步对文件内容进行分析,获取节点和边的基本数据
+            for file in [file, pre_file]:
+                successUploadedFiles.append({
+                    "id": file.id,
+                    "name": file.name,
+                    "content": file.content,
+                    "nodes": file.own_file_info.nodes,
+                    "sNodes": file.own_file_info.sNodes,
+                    "dNodes": file.own_file_info.dNodes,
+                    "iNodes": file.own_file_info.iNodes,
+                    "edges": file.own_file_info.edges,
+                })
+            return success(message="文件上传成功", data=successUploadedFiles, code=200)
+        else:
+            return failed(message="文件合法性检查失败")
+    
+    def delete(self, request):
+        user = request.user
+        file = File.objects.get(id=request.data.get('id'))
+
+        result = file.deleteStorage()
+        if result == OK:
+            return success(message="删除文件成功")
+        elif result == False:
+            return failed(messgae="删除文件失败")
+
+
+class PlanAPI(APIView):
+    def post(self, request):
+        user = request.user
+        plans = request.data.get('plans')
+        overWritePlans = False
+        try:
+            mission = Mission.objects.get(id=request.data.get('mission'))
+        except Mission.DoesNotExist:
+            print("处理规划所属任务不存在")
+            return failed(message="未找到规划任务所属任务")
+        # 重复提交同一Mission的规划将覆盖
+        if mission.own_plans.exists():
+            overWritePlans = True
+            for plan in mission.own_plans.all():
+                plan.delete()
+        # 全部放入矩阵
+        planMat = [[{}, {}, {}] for i in range(10)]
+        # 找出所有并行的源头
+        origins = []
+        for plan in plans:
+            if plan['row'] == 0:
+                origins.append(plan)
+            planMat[plan['row']][plan['col']] = plan
+        rootPlan = Plan(mission=mission, parent=None, algorithm=None, user=user)
+        rootPlan.save()
+
+        originPlans = []
+        # 添加第一代规划
+        for pJson in origins:
+            # p.algorithm应该用来新建plan,先占位
+            # childPlan = Plan(mission=mission, parent=rootPlan, algorithm=p['algorithm'], user=user)
+            try:
+                algorithm = Algorithm.objects.get(name=pJson['algorithm'])
+            except Algorithm.DoesNotExist:
+                print("Not Exist Algorithm")
+                return failed(message=str(pJson['id']) + "规划选定算法不存在")
+            pModel = Plan(mission=mission, parent=rootPlan, algorithm=algorithm, user=user)
+            pModel.save()
+            # p是json数据,headPlan是保存的PlanModel
+            originPlans.append([pJson, pModel])
+
+        # 添加后续规划
+        while originPlans:
+            pJson, pModel = originPlans.pop()
+            for childPos in pJson['children']:
+                childJson = planMat[childPos['row']][childPos['col']]
+                try:
+                    algorithm = Algorithm.objects.get(name=pJson['algorithm'])
+                except Algorithm.DoesNotExist:
+                    print("Not Exist Algorithm")
+                    return failed(message=str(pJson['id']) + "规划选定算法不存在")
+                childModel = Plan(mission=mission, parent=pModel, algorithm=algorithm, user=user)
+                childModel.save()
+                originPlans.append([childJson, childModel])
+        if overWritePlans:
+            return success(message="规划已覆盖")
+        else:
+            return success(message="规划已提交")

+ 68 - 0
backend/api/api_rawDataTrans.py

@@ -0,0 +1,68 @@
+from django.contrib import auth
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework.authtoken.models import Token
+from rest_framework.authentication import BasicAuthentication, TokenAuthentication
+from .serializers import UserRegisterSerializer
+
+from django.middleware.csrf import get_token
+from django.contrib.auth import login
+
+from api.utils import *
+from api.models import File, Mission, Plan, Result
+import requests
+
+import json, csv
+
+class RawDataTrans(APIView):
+    authentication_classes = []
+    permission_classes = []
+
+    def get(self, request):
+        if request.user:
+            # 用户从前端发来请求
+            return success("测试返回图数据")
+        else:
+            # 进程管理器发来请求
+            # mission = Mission.objects.get(id=int(request.data.get('missionId')))
+            plan = Plan.objects.get(id=int(request.data.get('planId')))
+            return success("测试返回图数据")
+    def post(self, request):
+        mission = Mission.objects.get(id=int(request.data.get('missionId')))
+        plan = Plan.objects.get(id=int(request.data.get('planId')))
+        nodes = request.data.get('nodes')
+        edges = request.data.get('edges')
+        for param in [mission, plan, nodes, edges]:
+            if not param:
+                print("结果传递参数不足")
+                return failed(message="缺少结果参数")
+        try:
+            result = plan.own_result
+            print("PLAN结果数据已存在,是否重复提交?")
+            return failed(message="重复提交结果")
+        except Result.DoesNotExist:
+            # 读取nodes和edges,生成结果文件
+            nodeFile = File(type='csv', usage='result', content='node', user=plan.user)
+            nodeFile.save()
+            if not nodeFile.generate(nodes) == OK:
+                return failed(message="保存结果文件失败")
+            edgeFile = File(type='csv', usage='result', content='edge', user=plan.user)
+            edgeFile.save()
+            if not edgeFile.generate(edges) == OK:
+                return failed(message="保存结果文件失败")
+            nodeFile.associate = edgeFile
+            edgeFile.associate = nodeFile
+            nodeFile.save()
+            edgeFile.save()
+
+            result = Result()
+            result.plan = plan
+            result.mission = mission
+            result.user = plan.user
+            result.nodeFile = nodeFile
+            result.edgeFile = edgeFile
+            result.save()
+
+            return success(message="保存结果文件成功")
+            

+ 82 - 0
backend/api/api_user.py

@@ -0,0 +1,82 @@
+from django.contrib import auth
+from rest_framework.views import APIView
+from rest_framework.authtoken.models import Token
+from rest_framework.authentication import BasicAuthentication, TokenAuthentication
+from .serializers import UserRegisterSerializer
+
+from django.middleware.csrf import get_token
+from django.contrib.auth import login
+
+from api.utils import *
+
+class UserRegisterAPI(APIView):
+    authentication_classes = []
+    permission_classes = []
+
+    def get(self, request):
+        csrf_token = get_token(request)
+        response = Response({'csrftoken': csrf_token})
+        # response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+    def post(self, request):
+        serializer = UserRegisterSerializer(data=request.data)
+        if serializer.is_valid():
+            newuser = serializer.save()
+            token = Token.objects.create(user=newuser)
+            # 注册完,同时将用户登录
+            auth.login(request, newuser)
+            return success(message="用户注册成功", data={
+                "username": newuser.username,
+                "displayname": newuser.displayname,
+                "token": token.key,
+            },code=201)
+        
+        # 处理错误信息
+        errors = {}
+        for field, field_errors in serializer.errors.items():
+            if isinstance(field_errors, list):
+                errors[field] = field_errors[0]
+                print(field_errors)
+                if field_errors[0].code == 'unique':
+                    errors[field] = str("用户名已存在")
+            else:
+                errors[field] = str(field_errors)
+        
+        return failed(message="注册失败", data=errors, code=400)
+
+class UserLoginAPI(APIView):
+    authentication_classes = []
+    permission_classes = []
+    
+    def get(self, request):
+        csrf_token = get_token(request)
+        response = Response({'csrftoken': csrf_token})
+        # response["Access-Control-Allow-Origin"] = "*"
+        return response
+    
+    def post(self, request):
+        username = request.data.get('username')
+        password = request.data.get('password')
+        user = auth.authenticate(request, username=username, password=password)
+        if user:
+            auth.login(request, user)
+            if Token.objects.filter(user=user).exists():
+                token = Token.objects.get(user=user).key
+            else:
+                token = Token.objects.create(user=user).key
+            return success(message="登录成功", data={
+                'username': user.username,
+                'displayName': user.displayname,
+                'token': token,
+            }, code=201)
+        else:
+            return failed(message="登录失败", data="用户名不存在,或密码错误", code=401)
+
+class getDashboard(APIView):
+    
+    def post(self, request):
+        return Response({
+            'data': 'yes'
+        })
+        

+ 6 - 0
backend/api/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ApiConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'api'

+ 28 - 0
backend/api/migrations/0001_initial.py

@@ -0,0 +1,28 @@
+# Generated by Django 4.2 on 2025-02-05 12:31
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='User',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('username', models.CharField(max_length=50, unique=True)),
+                ('password', models.CharField(max_length=100)),
+                ('display_name', models.CharField(max_length=32)),
+                ('create_time', models.DateField(auto_now_add=True)),
+                ('identity', models.CharField(choices=[('user', 'user'), ('admin', 'administrator')], default='user', max_length=16)),
+            ],
+            options={
+                'ordering': ['create_time'],
+            },
+        ),
+    ]

+ 22 - 0
backend/api/migrations/0002_alter_user_options_user_last_login.py

@@ -0,0 +1,22 @@
+# Generated by Django 4.2 on 2025-02-06 06:38
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('api', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='user',
+            options={},
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='last_login',
+            field=models.DateTimeField(blank=True, null=True, verbose_name='last login'),
+        ),
+    ]

+ 38 - 0
backend/api/migrations/0003_view_file.py

@@ -0,0 +1,38 @@
+# Generated by Django 4.2 on 2025-02-07 10:07
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('api', '0002_alter_user_options_user_last_login'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='View',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=64)),
+                ('create_time', models.DateTimeField(auto_now_add=True)),
+                ('update_time', models.DateTimeField(auto_now=True)),
+                ('description', models.CharField(max_length=256)),
+                ('state', models.CharField(choices=[('create', '创建'), ('work', '运行'), ('done', '完成')], max_length=10)),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='own_views', to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='File',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(default='untitled', max_length=64)),
+                ('type', models.CharField(choices=[('csv', 'csv')], max_length=5)),
+                ('create_time', models.DateTimeField(auto_now_add=True)),
+                ('update_time', models.DateTimeField(auto_now=True)),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='own_files', to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+    ]

+ 27 - 0
backend/api/migrations/0004_rename_display_name_user_displayname_file_usage.py

@@ -0,0 +1,27 @@
+# Generated by Django 4.2 on 2025-02-08 05:27
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0003_view_file"),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name="user",
+            old_name="display_name",
+            new_name="displayname",
+        ),
+        migrations.AddField(
+            model_name="file",
+            name="usage",
+            field=models.CharField(
+                choices=[("input", "input"), ("show", "show"), ("output", "output")],
+                default="input",
+                max_length=20,
+            ),
+            preserve_default=False,
+        ),
+    ]

+ 34 - 0
backend/api/migrations/0005_file_associate_file_content.py

@@ -0,0 +1,34 @@
+# Generated by Django 4.2 on 2025-03-06 15:22
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0004_rename_display_name_user_displayname_file_usage"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="file",
+            name="associate",
+            field=models.ForeignKey(
+                default="4",
+                on_delete=django.db.models.deletion.CASCADE,
+                to="api.file",
+            ),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name="file",
+            name="content",
+            field=models.CharField(
+                choices=[("node", "node"), ("edge", "edge")],
+                default="node",
+                max_length=10,
+            ),
+            preserve_default=False,
+        ),
+    ]

+ 24 - 0
backend/api/migrations/0006_alter_file_associate.py

@@ -0,0 +1,24 @@
+# Generated by Django 4.2 on 2025-03-06 15:26
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0005_file_associate_file_content"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="file",
+            name="associate",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                to="api.file",
+            ),
+        ),
+    ]

+ 41 - 0
backend/api/migrations/0007_fileinfo.py

@@ -0,0 +1,41 @@
+# Generated by Django 4.2 on 2025-03-12 11:37
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0006_alter_file_associate"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="FileInfo",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("nodes", models.IntegerField(default=0)),
+                ("sNodes", models.IntegerField(default=0)),
+                ("dNodes", models.IntegerField(default=0)),
+                ("iNodes", models.IntegerField(default=0)),
+                ("edges", models.IntegerField(default=0)),
+                (
+                    "file",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="own_file_info",
+                        to="api.file",
+                    ),
+                ),
+            ],
+        ),
+    ]

+ 89 - 0
backend/api/migrations/0008_mission_result.py

@@ -0,0 +1,89 @@
+# Generated by Django 4.2 on 2025-03-12 11:50
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0007_fileinfo"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Mission",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(default="untitled", max_length=64)),
+                ("create_time", models.DateTimeField(auto_now_add=True)),
+                ("update_time", models.DateTimeField(auto_now=True)),
+                (
+                    "edgeFile",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="own_missions_edge",
+                        to="api.file",
+                    ),
+                ),
+                (
+                    "nodeFile",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="own_missions_node",
+                        to="api.file",
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="own_missions",
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Result",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(default="untitled", max_length=64)),
+                ("create_time", models.DateTimeField(auto_now_add=True)),
+                ("update_time", models.DateTimeField(auto_now=True)),
+                (
+                    "mission",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="own_results",
+                        to="api.mission",
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="own_results",
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+        ),
+    ]

+ 28 - 0
backend/api/migrations/0009_alter_fileinfo_file_alter_mission_name.py

@@ -0,0 +1,28 @@
+# Generated by Django 4.2 on 2025-03-12 13:20
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0008_mission_result"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="fileinfo",
+            name="file",
+            field=models.OneToOneField(
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="own_file_info",
+                to="api.file",
+            ),
+        ),
+        migrations.AlterField(
+            model_name="mission",
+            name="name",
+            field=models.CharField(default="未命名任务", max_length=64),
+        ),
+    ]

+ 84 - 0
backend/api/migrations/0010_algorithm_plan.py

@@ -0,0 +1,84 @@
+# Generated by Django 4.2 on 2025-03-13 02:28
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0009_alter_fileinfo_file_alter_mission_name"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Algorithm",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(default="", max_length=32)),
+                ("create_time", models.DateTimeField(auto_now_add=True)),
+                ("update_time", models.DateTimeField(auto_now=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Plan",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("create_time", models.DateTimeField(auto_now_add=True)),
+                ("update_time", models.DateTimeField(auto_now=True)),
+                (
+                    "algorithm",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.DO_NOTHING,
+                        related_name="own_plans",
+                        to="api.algorithm",
+                    ),
+                ),
+                (
+                    "mission",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="own_plans",
+                        to="api.mission",
+                    ),
+                ),
+                (
+                    "parent",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="own_child_plans",
+                        to="api.plan",
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="own_plans",
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+        ),
+    ]

+ 39 - 0
backend/api/migrations/0011_result_plan_result_state.py

@@ -0,0 +1,39 @@
+# Generated by Django 4.2 on 2025-03-13 18:18
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0010_algorithm_plan"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="result",
+            name="plan",
+            field=models.ForeignKey(
+                default=1,
+                on_delete=django.db.models.deletion.DO_NOTHING,
+                related_name="own_results",
+                to="api.plan",
+            ),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name="result",
+            name="state",
+            field=models.CharField(
+                choices=[
+                    ("init", "init"),
+                    ("calculating", "calculating"),
+                    ("done", "done"),
+                ],
+                default="init",
+                max_length=20,
+            ),
+            preserve_default=False,
+        ),
+    ]

+ 45 - 0
backend/api/migrations/0012_result_edgefile_result_nodefile_alter_result_plan.py

@@ -0,0 +1,45 @@
+# Generated by Django 4.2 on 2025-03-14 09:07
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0011_result_plan_result_state"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="result",
+            name="edgeFile",
+            field=models.ForeignKey(
+                default=1,
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="own_results_edge",
+                to="api.file",
+            ),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name="result",
+            name="nodeFile",
+            field=models.ForeignKey(
+                default=1,
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="own_results_node",
+                to="api.file",
+            ),
+            preserve_default=False,
+        ),
+        migrations.AlterField(
+            model_name="result",
+            name="plan",
+            field=models.OneToOneField(
+                on_delete=django.db.models.deletion.DO_NOTHING,
+                related_name="own_result",
+                to="api.plan",
+            ),
+        ),
+    ]

+ 30 - 0
backend/api/migrations/0013_remove_result_state_alter_file_usage.py

@@ -0,0 +1,30 @@
+# Generated by Django 4.2 on 2025-03-14 20:25
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("api", "0012_result_edgefile_result_nodefile_alter_result_plan"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="result",
+            name="state",
+        ),
+        migrations.AlterField(
+            model_name="file",
+            name="usage",
+            field=models.CharField(
+                choices=[
+                    ("input", "input"),
+                    ("show", "show"),
+                    ("result", "result"),
+                    ("output", "output"),
+                ],
+                max_length=20,
+            ),
+        ),
+    ]

+ 0 - 0
backend/api/migrations/__init__.py


BIN
backend/api/migrations/__pycache__/0001_initial.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0002_alter_user_options_user_last_login.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0003_view_file.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0004_rename_display_name_user_displayname_file_usage.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0005_file_associate_file_content.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0006_alter_file_associate.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0007_fileinfo.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0008_mission_result.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0009_alter_fileinfo_file_alter_mission_name.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0010_algorithm_plan.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0011_result_plan_result_state.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0012_result_edgefile_result_nodefile_alter_result_plan.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/0013_remove_result_state_alter_file_usage.cpython-310.pyc


BIN
backend/api/migrations/__pycache__/__init__.cpython-310.pyc


+ 7 - 0
backend/api/models/__init__.py

@@ -0,0 +1,7 @@
+from .user import User
+from .file import File, FileInfo
+from .view import View
+from .mission import Mission
+from .result import Result
+from .plan import Plan
+from .algorithm import Algorithm

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


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


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


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


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


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


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


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


+ 21 - 0
backend/api/models/algorithm.py

@@ -0,0 +1,21 @@
+from django.db import models
+import os, errno
+
+from api.utils import *
+
+class AlgorithmManager(models.Manager):
+    def statistic(self, user):
+      results = user.own_plans.all()
+      return {
+          'amount': len(results),
+      }
+
+class Algorithm(models.Model):
+    name = models.CharField(default="", max_length=32)
+    create_time = models.DateTimeField(auto_now_add=True)
+    update_time = models.DateTimeField(auto_now=True)
+    
+    objects = AlgorithmManager()
+
+    class Meta:
+        app_label = 'api'

+ 284 - 0
backend/api/models/file.py

@@ -0,0 +1,284 @@
+from django.db import models
+import os, errno
+import csv
+from api.utils import *
+
+types = [
+    ('csv', 'csv'),
+]
+
+usages = [
+    ('input', 'input'),
+    ('show', 'show'),
+    ('result', 'result'),
+    ('output', 'output'),
+]
+
+contents = [
+    ('node', 'node'),
+    ('edge', 'edge'),
+]
+
+BASE_FILE_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'uploads')
+
+
+class FileManager(models.Manager):
+    def getHistory(self, user):
+        # try:
+        files = user.own_files.all()
+        history = []
+        for file in files:
+            fileId = file.id
+            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("未找到对应文件,现将记录删除1", fileId, file.name)
+                self.get(id=fileId).delete()
+                continue
+            except Exception as error:
+                print("读取历史记录时出现未知错误")
+                return FAILED
+                
+            if size >= 1024 * 1024:
+                size = size / (1024 * 1024)
+                size = f"{size:.2f} MB"
+            else:
+                size = size / 1024
+                size = f"{size:.2f} KB"
+            history.append({
+                'id': file.id,
+                'name': file.name,
+                'uploadTime': file.update_time,
+                'size': size,
+                'content': file.content,
+            })
+        return history
+        # except Exception as error:
+        #     print("Failed to get upload history", error)
+        #     return FAILED
+
+
+# Create your models here.
+class File(models.Model):
+    
+    name = models.CharField(default="untitled", max_length=64)
+    type = models.CharField(choices=types, max_length=5)
+    usage = models.CharField(choices=usages, max_length=20)
+    create_time = models.DateTimeField(auto_now_add=True)
+    update_time = models.DateTimeField(auto_now=True)
+    content = models.CharField(choices=contents, max_length=10)
+    associate = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True)
+    
+    user = models.ForeignKey(to="api.User", on_delete=models.CASCADE, related_name='own_files')
+    
+    objects = FileManager()
+
+    def saveWithInfo(self):
+        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':
+            sCount = dCount = iCount = 0
+            nodeFile = csv.reader(open(path, 'r'))
+            for line in nodeFile:
+                if line[1] == 'S':
+                    sCount += 1
+                if line[1] == 'D':
+                    dCount += 1
+                if line[1] == 'I':
+                    iCount += 1
+            fileInfo = FileInfo()
+            fileInfo.file = self
+            fileInfo.nodes = sCount + dCount + iCount
+            fileInfo.sNodes = sCount
+            fileInfo.dNodes = dCount
+            fileInfo.iNodes = iCount
+            fileInfo.save()
+        if self.content == 'edge':
+            edges = 0
+            edgeFile = csv.reader(open(path2, 'r'))
+            for line in edgeFile:
+                edges += 1
+            fileInfo = FileInfo()
+            fileInfo.file = self
+            fileInfo.edges = edges
+            fileInfo.save()
+        self.save()
+
+    def generate(self, data):
+        path = os.path.join(BASE_FILE_PATH, str(self.user.id))
+        if os.path.exists(os.path.join(path, str(self.id))):
+            self.delete()
+            return FILE_ALREADY_EXIST
+        else:
+            if self.content == 'node':
+                nodes = []
+                file = open(os.path.join(path, str(self.id)), 'w', newline='')
+                csvFile = csv.writer(file)
+                for line in data:
+                    if not len(line) == 2:
+                        print("check file illegal failed", "node", "len = 2")
+                        return FAILED
+                    if not str(line[0]).isdigit():
+                        print("check file illegal failed", "node", "id wrong")
+                        return FAILED
+                    if not line[1] in ['S', 'D', 'I']:
+                        print("check file illegal failed", "node", "type wrong")
+                        return FAILED
+                    if line[0] not in nodes:
+                        nodes.append(line[0])
+                    else:
+                        print("check file illegal failed", "node", "dudplicate id")
+                        return FAILED
+                    csvFile.writerow(line)
+                file.close()
+                return OK
+            if self.content == 'edge':
+                edges = []
+                file = open(os.path.join(path, str(self.id)), 'w', newline='')
+                csvFile = csv.writer(file)
+                for line in data:
+                    if not len(line) == 2:
+                        print("check file illegal failed", "edge", "len =2")
+                        return FAILED
+                    if [line[0], line[1]] not in edges and [line[1], line[0]] not in edges:
+                        edges.append([line[0], line[1]])
+                    csvFile.writerow(line)
+                file.close()
+                return OK
+            return UNKNOWN_CONTENT
+
+    def storage(self, file):
+        try:
+            path = os.path.join(BASE_FILE_PATH, str(self.user.id))
+            if os.path.exists(os.path.join(path, str(self.id))):
+                self.delete()
+                return FILE_ALREADY_EXIST
+            else:
+                try:
+                    os.mkdir(path)
+                except Exception as error:
+                    if not error.args[0] == 17:
+                        print(error)
+                        return FILE_FAILED_CREATE_DIR
+                file_path = os.path.join(path, str(self.id))
+                f = open(file_path, 'wb')
+                for bite in file:
+                    f.write(bite)
+                f.close()
+                return OK
+        except Exception as error:
+            print(error)
+            return FAILED
+    
+    # 检查文件是否合法
+    def checkIllegal(self):
+        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':
+            file = csv.reader(open(path, 'r'))
+            # 针对csv文件的检测
+            if self.type == 'csv':
+                nodes = []
+                for line in file:
+                    if not len(line) == 2:
+                        print("check file illegal failed", "node", "len = 2")
+                        return False
+                    if not line[0].isdigit():
+                        print("check file illegal failed", "node", "id wrong")
+                        return False
+                    if not line[1] in ['S', 'D', 'I']:
+                        print("check file illegal failed", "node", "type wrong")
+                        return False
+                    if line[0] not in nodes:
+                        nodes.append(line[0])
+                    else:
+                        print("check file illegal failed", "node", "dudplicate id")
+                        return False
+                return True
+        if self.content == 'edge':
+            edgeFile = csv.reader(open(path, 'r'))
+            nodeFile = csv.reader(open(path2, 'r'))
+            # 针对csv文件的检测
+            if self.type == 'csv':
+                nodes = []
+                edges = []
+                for line in nodeFile:
+                    if not len(line) == 2:
+                        print("check file illegal failed", "node", "len = 2")
+                        return False
+                    if not line[0].isdigit():
+                        print("check file illegal failed", "node", "len = 2")
+                        return False
+                    nodes.append(line[0])
+                for line in edgeFile:
+                    if not len(line) == 2:
+                        print("check file illegal failed", "edge", "len =2")
+                        return False
+                    if line[0] not in nodes or line[1] not in nodes:
+                        print("check file illegal failed", "edge", "id not exist")
+                        return False
+                    if [line[0], line[1]] not in edges and [line[1], line[0]] not in edges:
+                        edges.append([line[0], line[1]])
+                    else:
+                        # 将图视为无向图,同一条边的正反算作重复
+                        print("check file illegal failed", "edge", "duplicate edge")
+                        return False
+                return True
+    
+    def toJson(self):
+        path = os.path.join(os.path.join(BASE_FILE_PATH, str(self.user.id)), str(self.id))
+        file = csv.reader(open(path, 'r'))
+        if self.content == 'node':
+            if self.type == 'csv':
+                nodes = []
+                for line in file:
+                    nodes.append({'id': line[0], 'type': line[1]})
+                return nodes
+        if self.content == 'edge':
+            if self.type == 'csv':
+                edges = []
+                for line in file:
+                    edges.append([line[0], line[1]])
+                return edges
+
+    def deleteStorage(self):
+        path = os.path.join(os.path.join(BASE_FILE_PATH, str(self.user.id)), str(self.id))
+        if self.associate:
+            path2 = os.path.join(os.path.join(BASE_FILE_PATH, str(self.user.id)), str(self.associate.id))
+        else:
+            path2 = ""
+        failedFlag = False
+        for p in [path, path2]:
+            if os.path.exists(p):
+                try:
+                    os.remove(p)
+                except Exception as error:
+                    # 可能出现失败的原因是文件被占用
+                    print("删除文件" + self.id + self.name + "失败", error)
+                    failedFlag = True
+        # 无论文件删除是否成功,都要把记录删除,多余的文件可以再后续清理时删除
+        if self.associate:
+            self.associate.delete()
+        if self:
+            self.delete()
+        if failedFlag:
+            return FAILED
+        return OK
+
+    class Meta:
+        app_label = 'api'
+
+
+class FileInfo(models.Model):
+    file = models.OneToOneField(File, on_delete=models.CASCADE, related_name='own_file_info')
+    nodes = models.IntegerField(default=0)
+    sNodes = models.IntegerField(default=0)
+    dNodes = models.IntegerField(default=0)
+    iNodes = models.IntegerField(default=0)
+    edges = models.IntegerField(default=0)
+
+    # 待添加集中度等边的信息
+    class Meta:
+        app_label = 'api'

+ 24 - 0
backend/api/models/mission.py

@@ -0,0 +1,24 @@
+from django.db import models
+import os, errno
+
+from api.utils import *
+
+class MissionManager(models.Manager):
+    def statistic(self, user):
+      missions = user.own_missions.all()
+      return {
+          'amount': len(missions),
+      }
+
+class Mission(models.Model):
+    name = models.CharField(default="未命名任务", max_length=64)
+    create_time = models.DateTimeField(auto_now_add=True)
+    update_time = models.DateTimeField(auto_now=True)
+    nodeFile = models.ForeignKey(to="api.File", on_delete=models.CASCADE, related_name="own_missions_node")
+    edgeFile = models.ForeignKey(to="api.File", on_delete=models.CASCADE, related_name="own_missions_edge")
+    user = models.ForeignKey(to="api.User", on_delete=models.CASCADE, related_name="own_missions")
+    
+    objects = MissionManager()
+
+    class Meta:
+        app_label = 'api'

+ 25 - 0
backend/api/models/plan.py

@@ -0,0 +1,25 @@
+from django.db import models
+import os, errno
+
+from api.utils import *
+
+class PlanManager(models.Manager):
+    def statistic(self, user):
+        results = user.own_plans.all()
+        return {
+            'amount': len(results),
+        }
+
+class Plan(models.Model):
+    create_time = models.DateTimeField(auto_now_add=True)
+    update_time = models.DateTimeField(auto_now=True)
+    parent = models.ForeignKey('self', on_delete=models.CASCADE, related_name='own_child_plans', blank=True, null=True)
+    mission = models.ForeignKey(to="api.Mission", on_delete=models.CASCADE, related_name="own_plans")
+    algorithm = models.ForeignKey(to="api.Algorithm", on_delete=models.DO_NOTHING, related_name="own_plans", blank=True, null=True)
+
+    user = models.ForeignKey(to="api.User", on_delete=models.CASCADE, related_name='own_plans')
+
+    objects = PlanManager()
+
+    class Meta:
+        app_label = 'api'

+ 31 - 0
backend/api/models/result.py

@@ -0,0 +1,31 @@
+from django.db import models
+import os, errno
+
+from api.utils import *
+
+
+class ResultManager(models.Manager):
+    def statistic(self, user):
+      results = user.own_results.all()
+      return {
+          'amount': len(results),
+      }
+
+class Result(models.Model):
+    name = models.CharField(default="untitled", max_length=64)
+    create_time = models.DateTimeField(auto_now_add=True)
+    update_time = models.DateTimeField(auto_now=True)
+
+    nodeFile = models.ForeignKey(to="api.File", on_delete=models.CASCADE, related_name="own_results_node")
+    edgeFile = models.ForeignKey(to="api.File", on_delete=models.CASCADE, related_name="own_results_edge")
+    
+    # 注意plan和result的一对一,反向名为单数形式
+    plan = models.OneToOneField(to="api.plan", on_delete=models.DO_NOTHING, related_name="own_result")
+
+    mission = models.ForeignKey(to="api.Mission", on_delete=models.CASCADE, related_name="own_results")
+    user = models.ForeignKey(to="api.User", on_delete=models.CASCADE, related_name='own_results')
+    
+    objects = ResultManager()
+
+    class Meta:
+        app_label = 'api'

+ 55 - 0
backend/api/models/user.py

@@ -0,0 +1,55 @@
+from django.db import models
+from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
+
+identities = [
+    ('user', 'user'),
+    ('admin', 'administrator'),
+]
+
+
+class UserManager(BaseUserManager):
+    def create_user(self, username, password):
+        if password == None or username == None:
+            raise ValueError("用户名或密码为空")
+        user = self.model(
+            username = username,
+            displayname = username,
+            identity = 'user',
+        )
+        user.set_password(password)
+        user.save()
+        return user
+    
+    def create_superuser(self, username, password):
+        if password == None or username == None:
+            raise ValueError("用户名或密码为空")
+        user = self.model(
+            username = username,
+            displayname = username,
+            identity = 'admin',
+        )
+        user.set_password(password)
+        user.save()
+        return user
+
+# Create your models here.
+class User(AbstractBaseUser):
+    
+    username = models.CharField(max_length=50, unique=True)
+    password = models.CharField(max_length=100)
+    displayname = models.CharField(max_length=32)
+    create_time = models.DateField(auto_now_add=True)
+    identity = models.CharField(default='user', max_length=16, choices=identities)
+    
+    objects = UserManager()
+    USERNAME_FIELD = 'username'
+    REQUIRED_FIELDS = []
+    
+    def is_admin(self):
+        if self.identity == 'admin':
+            return True
+        else:
+            return False
+        
+    class Meta:
+        app_label = 'api'

+ 12 - 0
backend/api/models/view.py

@@ -0,0 +1,12 @@
+from django.db import models
+
+class View(models.Model):
+    name = models.CharField(unique=False, max_length=64)
+    create_time = models.DateTimeField(auto_now_add=True)
+    update_time = models.DateTimeField(auto_now=True)
+    description = models.CharField(max_length=256)
+    state = models.CharField(choices=(('create', '创建'), ('work', '运行'), ('done', '完成')), max_length=10)
+    user = models.ForeignKey(to="api.User", on_delete=models.CASCADE, related_name='own_views')
+  
+    class Meta:
+        app_label = 'api'

+ 32 - 0
backend/api/serializers.py

@@ -0,0 +1,32 @@
+from rest_framework import serializers
+from api.models import User
+from django.contrib.auth.hashers import make_password
+
+class UserRegisterSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = User
+        fields = ('username', 'password')
+        extra_kwargs = {
+            'password': {'write_only': True}
+        }
+
+    def validate_username(self, value):
+        if len(value) < 5 or len(value) > 12:
+            raise serializers.ValidationError("用户名长度需在5-12位之间")
+        if User.objects.filter(username=value).exists():
+            raise serializers.ValidationError("用户名已存在")
+        return value
+
+    def validate_password(self, value):
+        if len(value) < 6 or len(value) > 20:
+            raise serializers.ValidationError("密码长度需在6-20位之间")
+        return value
+
+    def create(self, validated_data):
+        # 使用固定盐值加密
+        validated_data['password'] = make_password(
+            validated_data['password'],
+            salt='vrviewer',
+            hasher='pbkdf2_sha256'
+        )
+        return User.objects.create(**validated_data)

+ 3 - 0
backend/api/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 20 - 0
backend/api/tokenAuthentication.py

@@ -0,0 +1,20 @@
+from rest_framework.authentication import BaseAuthentication
+from rest_framework import exceptions
+from rest_framework.authtoken.models import Token
+import time
+from api.models import User
+
+class TokenAuthentication(BaseAuthentication):
+    def authenticate(self, request):
+        try:
+            token = request.headers['Authorization'].replace("Token ", "")
+            if Token.objects.filter(key=token).exists():
+                user_id = Token.objects.filter(key=token).first().user_id
+                user= User.objects.get(id=user_id)
+                return (user, token)
+            return None
+            
+            
+        except Exception as error:
+            print(error)
+            raise exceptions.AuthenticationFailed("Failed pass authenticate")

+ 16 - 0
backend/api/urls.py

@@ -0,0 +1,16 @@
+from django.urls import path
+from .api_user import UserRegisterAPI, UserLoginAPI
+from .api_user import getDashboard
+from .api_prepare import UploadFileAPI, PlanAPI
+from .api_calculate import CalculateAPI
+from .api_rawDataTrans import RawDataTrans
+
+urlpatterns = [
+    path('register/', UserRegisterAPI.as_view(), name='user_register_api'),
+    path('login/', UserLoginAPI.as_view(), name='user_login_api'),
+    path('uploadfile/', UploadFileAPI.as_view(), name='upload_file_api'),
+    path('plan/', PlanAPI.as_view(), name='plan_api'),
+    path('getDashboard/', getDashboard.as_view()),
+    path('calculate/', CalculateAPI.as_view(), name='calculate_api'),
+    path('rawDataTrans/', RawDataTrans.as_view(), name='rawDataTrans_api'),
+]

+ 41 - 0
backend/api/utils.py

@@ -0,0 +1,41 @@
+from rest_framework.response import Response
+from rest_framework import status
+
+SCHEDULER_BASE_URL = "http://localhost:5000"
+
+OK = 0
+FAILED = 1
+FILE_ALREADY_EXIST = 101
+FILE_FAILED_CREATE_DIR = 102
+UNKNOWN_CONTENT = 998
+UNKNOWN_ERROR = 999
+
+def failed(message="访问失败", data=None, code=400):
+    if code == 400:
+        mStatus = status.HTTP_400_BAD_REQUEST
+    elif code == 401:
+        mStatus = status.HTTP_401_UNAUTHORIZED
+    elif code == 403:
+        mStatus = status.HTTP_403_FORBIDDEN
+    else:
+        raise ValueError("不支持的HTTP状态")
+    return Response({
+        'status': 'failed',
+        'message': message,
+        'data': data,    
+    }, status=mStatus)
+
+def success(message="访问成功", data=None, code=200):
+    if code == 200:
+        mStatus = status.HTTP_200_OK
+    elif code == 201:
+        mStatus = status.HTTP_201_CREATED
+    elif code == 202:
+        mStatus = status.HTTP_202_ACCEPTED
+    else:
+        raise ValueError("不支持的HTTP状态")
+    return Response({
+        'status': 'success',
+        'message': message,
+        'data': data,
+    }, status=mStatus)

+ 3 - 0
backend/api/views.py

@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.

+ 0 - 0
backend/backend/__init__.py


BIN
backend/backend/__pycache__/__init__.cpython-310.pyc


BIN
backend/backend/__pycache__/settings.cpython-310.pyc


BIN
backend/backend/__pycache__/urls.cpython-310.pyc


BIN
backend/backend/__pycache__/wsgi.cpython-310.pyc


+ 16 - 0
backend/backend/asgi.py

@@ -0,0 +1,16 @@
+"""
+ASGI config for backend project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
+
+application = get_asgi_application()

+ 142 - 0
backend/backend/settings.py

@@ -0,0 +1,142 @@
+"""
+Django settings for backend project.
+
+Generated by 'django-admin startproject' using Django 4.2.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/4.2/ref/settings/
+"""
+
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-1wvwk*vqn21we=)usoe)+w13!+4-sq3_g@0-&j-wa0#lq6yf1='
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    'corsheaders',
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'rest_framework',
+    'rest_framework.authtoken',
+    'api',
+]
+
+REST_FRAMEWORK = {
+    'DEFAULT_AUTHENTICATION_CLASSES': ['api.tokenAuthentication.TokenAuthentication'],
+    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
+    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
+    'NON_FIELD_ERRORS_KEY': 'message',
+}
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'corsheaders.middleware.CorsMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+CORS_ORIGIN_ALLOW_ALL = True
+CORS_ALLOW_METHODS=("*")
+CORS_ALLOW_CREDENTIALS = True
+
+AUTH_USER_MODEL = "api.User"
+
+
+ROOT_URLCONF = 'backend.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'backend.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': BASE_DIR / 'db.sqlite3',
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/4.2/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'Asia/Shanghai'
+
+USE_I18N = True
+
+USE_TZ = False
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/4.2/howto/static-files/
+
+STATIC_URL = 'static/'
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

+ 23 - 0
backend/backend/urls.py

@@ -0,0 +1,23 @@
+"""
+URL configuration for backend project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/4.2/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path, include
+
+urlpatterns = [
+    path('admin/', admin.site.urls),
+    path('api/', include("api.urls"))
+]

+ 16 - 0
backend/backend/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for backend project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
+
+application = get_wsgi_application()

BIN
backend/db.sqlite3


+ 22 - 0
backend/manage.py

@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    """Run administrative tasks."""
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()

+ 0 - 0
backend/process/__init__.py


+ 3 - 0
backend/process/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 6 - 0
backend/process/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ProcessConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'process'

+ 0 - 0
backend/process/migrations/__init__.py


+ 3 - 0
backend/process/models.py

@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.

+ 3 - 0
backend/process/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 3 - 0
backend/process/views.py

@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.

+ 0 - 0
scheduler/__init__.py


BIN
scheduler/__pycache__/processManager.cpython-310.pyc


BIN
scheduler/__pycache__/utils.cpython-310.pyc


+ 38 - 0
scheduler/algo1Folder/controller.py

@@ -0,0 +1,38 @@
+import requests
+import os
+import logging
+
+SCHEDULER_BASE_URL = os.getenv("SCHEDULER_BASE_URL")
+BACKEND_BASE_URL = os.getenv("BACKEND_BASE_URL")
+
+logger = logging.getLogger("algoA logger")
+missionId = os.getenv("missionId")
+planId = os.getenv("planId")
+
+
+for i in range(10000000):
+    print('.', end='')
+
+
+response = requests.post(SCHEDULER_BASE_URL + '/report', json={
+    'missionId': missionId,
+    'planId': planId,
+    'state': 'DONE',
+    'results': None,
+})
+
+if response:
+    logger.info("???")
+    if response.json()['code'] == 'OK':
+
+        response = requests.post(BACKEND_BASE_URL + "/rawDataTrans/", json={
+            'missionId': missionId,
+            'planId': planId,
+            'nodes': [[1, 'S'], [2, 'D'], [3, 'D'], [4, 'I']],
+            'edges': [[1, 2], [1, 4], [2, 4], [3, 4]],
+        })
+        logger.info(f"算法控制程序推送结果完毕 MissionId: {missionId} PlanId: {planId} Message: {response.json()}")
+    else:
+        logger.error(f"算法控制程序结果反馈未被识别 MissionId: {missionId} PlanId: {planId}")
+else:
+    logger.error(f"算法控制程序结果反馈失败 MissionId: {missionId} PlanId: {planId}")

+ 1 - 0
scheduler/algorithms.config

@@ -0,0 +1 @@
+algA,algo1Folder,python controller.py

+ 197 - 0
scheduler/processManager.py

@@ -0,0 +1,197 @@
+# process_manager.py
+import subprocess
+import psutil
+import logging
+import threading
+import time
+import os
+from typing import Dict, Optional, List, Union
+import requests
+
+from utils import SCHEDULER_BASE_URL, BACKEND_BASE_URL
+
+
+class ProcessManager:
+    def __init__(self, check_interval: int = 5, timeout: int = 300):
+        self.processes: Dict[int, dict] = {}  # {pid: {meta}}
+        self.lock = threading.Lock()
+        self.check_interval = check_interval  # 检查间隔(秒)
+        self.timeout = timeout                # 进程超时时间(秒)
+        self._monitor_thread: Optional[threading.Thread] = None
+        self._running = False
+
+        # 配置日志
+        self.log_dir: str = "process_logs"
+        # 确保日志目录存在
+        os.makedirs(self.log_dir, exist_ok=True)
+        logging.basicConfig(
+            level=logging.INFO,
+            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+        )
+        self.logger = logging.getLogger("ProcessManager")
+
+    def spawn(
+        self,
+        command: Union[str, List[str]],
+        cwd: str = None,
+        env: dict = None,
+        shell: bool = False,
+        **meta
+    ) -> Optional[int]:
+        """
+        创建并监控子进程
+        :param command: 命令字符串或参数列表
+        :param cwd: 工作目录
+        :param env: 环境变量(默认继承当前环境)
+        :param shell: 是否使用shell执行
+        :param meta: 附加元数据
+        :return: 成功返回PID,失败返回None
+        """
+        try:
+            # 准备日志文件
+            timestamp = time.strftime("%Y%m%d-%H%M%S")
+            log_prefix = os.path.join(
+                self.log_dir,
+                f"proc_{timestamp}_{os.getpid()}"
+            )
+            
+            # 打开日志文件
+            with open(f"{log_prefix}.stdout", "w") as stdout_f, \
+                 open(f"{log_prefix}.stderr", "w") as stderr_f:
+
+                # 创建子进程
+                env = {**os.environ, **(env or {})}
+                env.update({
+                    'SCHEDULER_BASE_URL': SCHEDULER_BASE_URL,
+                    'BACKEND_BASE_URL': BACKEND_BASE_URL,
+                    'missionId': str(meta['mission']['id']),
+                    'planId': str(meta['plan']['id']),
+                })
+                proc = subprocess.Popen(
+                    command,
+                    cwd=cwd,
+                    env=env,
+                    shell=shell,
+                    stdout=stdout_f,
+                    stderr=stderr_f,
+                    text=True,
+                )
+
+                # 注册进程信息
+                with self.lock:
+                    self.processes[proc.pid] = {
+                        "proc": proc,
+                        "command": command,
+                        "start_time": time.time(),
+                        "last_active": time.time(),
+                        "log_stdout": stdout_f.name,
+                        "log_stderr": stderr_f.name,
+                        "meta": meta
+                    }
+
+                self.logger.info(
+                    f"创建子进程 PID={proc.pid} | "
+                    f"命令: {command} | "
+                    f"日志: {log_prefix}.*"
+                )
+                return proc.pid
+
+        except Exception as e:
+            self.logger.error(f"创建进程失败: {str(e)}")
+            return None
+
+    def start_monitoring(self):
+        """启动后台监控线程"""
+        if self._running:
+            return
+
+        self._running = True
+        self._monitor_thread = threading.Thread(
+            target=self._monitor_loop,
+            daemon=True  # 随主进程退出
+        )
+        self._monitor_thread.start()
+        self.logger.info("进程监控线程已启动")
+
+    def stop_monitoring(self):
+        """停止监控"""
+        self._running = False
+        if self._monitor_thread:
+            self._monitor_thread.join()
+        self.logger.info("进程监控线程已停止")
+
+    def _monitor_loop(self):
+        """监控主循环"""
+        while self._running:
+            try:
+                self._check_processes()
+            except Exception as e:
+                self.logger.error(f"监控循环异常: {str(e)}", exc_info=True)
+            
+            time.sleep(self.check_interval)
+
+    def _check_processes(self):
+        """执行进程状态检查"""
+        current_time = time.time()
+        dead_pids = []
+
+        with self.lock:
+            for pid, info in self.processes.items():
+                try:
+                    proc = psutil.Process(pid)
+
+                    # 检测崩溃
+                    if proc.status() == psutil.STATUS_ZOMBIE:
+                        self.logger.warning(f"进程 {pid} 处于僵尸状态")
+                        dead_pids.append(pid)
+                        self._handle_crash(pid, info)
+                        continue
+
+                    # 检测超时(假死)
+                    if (current_time - info["last_active"]) > self.timeout:
+                        self.logger.warning(f"进程 {pid} 超时,即将终止")
+                        proc.terminate()
+                        dead_pids.append(pid)
+                        self._handle_timeout(pid, info)
+                        continue
+
+                    # 更新活跃时间(可根据业务逻辑调整)
+                    info["last_active"] = current_time
+
+                except psutil.NoSuchProcess:
+                    response = requests.post(SCHEDULER_BASE_URL + '/report', json={'missionId': info['meta']['mission']['id'], 'planId': info['meta']['plan']['id'], 'state': 'CRASH', 'results': None})
+                    code = response.json()['code']
+                    if code == 'OK':
+                        self.logger.warning(f"进程 {pid} 已正常退出")
+                        dead_pids.append(pid)
+                    if code == 'ERROR':
+                        dead_pids.append(pid)
+                        self._handle_crash(pid, info)
+
+            # 清理已终止进程
+            for pid in dead_pids:
+                del self.processes[pid]
+
+    def _handle_crash(self, pid: int, info: dict):
+        """进程崩溃处理逻辑"""
+        # 读取最后10行错误日志
+        try:
+            with open(info["log_stderr"]) as f:
+                last_lines = "".join(f.readlines()[-10:])
+        except Exception:
+            last_lines = "无法读取日志"
+
+        self.logger.error(
+            f"进程崩溃 PID={pid}\n"
+            f"命令: {info['command']}\n"
+            f"最后错误输出:\n{last_lines}"
+        )
+        # 向flask发送进程崩溃信息
+        response = requests.post(SCHEDULER_BASE_URL + '/report', json={'missionId': info['meta']['mission']['id'], 'planId': info['meta']['plan']['id'], 'state': 'DEAD', 'results': None})
+
+    def _handle_timeout(self, pid: int, info: dict):
+        """进程超时处理逻辑"""
+        # 记录诊断信息
+        self.logger.error(f"进程超时处理 PID={pid} | 运行时间: {time.time() - info['start_time']:.1f}s")
+        # 向flask发送进程超时信息
+        response = requests.post(SCHEDULER_BASE_URL + '/report', json={'missionId': info['meta']['mission']['id'], 'planId': info['meta']['plan']['id'], 'state': 'TIMEOUT', 'results': None})

+ 0 - 0
scheduler/process_logs/proc_20250314-122720_21672.stderr


Some files were not shown because too many files changed in this diff