From 98925f4cce7ea1fc50adc5032a4971555f5f5103 Mon Sep 17 00:00:00 2001 From: xiaji Date: Thu, 11 Dec 2025 14:33:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86admin=E7=9A=84?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 13 - .trae/documents/任务中心管理系统实现计划.md | 233 +++++++++++------- db.sqlite3 | Bin 155648 -> 155648 bytes .../__pycache__/settings.cpython-311.pyc | Bin 2813 -> 3315 bytes task_center/__pycache__/urls.cpython-311.pyc | Bin 1542 -> 1543 bytes tasks/__pycache__/admin.cpython-311.pyc | Bin 220 -> 2291 bytes tasks/admin.py | 57 ++++- tasks/templates/tasks/index.html | 6 +- 8 files changed, 207 insertions(+), 102 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index a6574a5..0000000 --- a/.env.example +++ /dev/null @@ -1,13 +0,0 @@ -# Django settings -SECRET_KEY=your-secret-key -DEBUG=True -ALLOWED_HOSTS=127.0.0.1,localhost - -# Database settings -# DATABASE_URL=sqlite:///db.sqlite3 -# For PostgreSQL: DATABASE_URL=postgres://user:password@localhost:5432/dbname -# For MySQL: DATABASE_URL=mysql://user:password@localhost:3306/dbname - -# Static and media files -STATIC_ROOT=static -MEDIA_ROOT=media \ No newline at end of file diff --git a/.trae/documents/任务中心管理系统实现计划.md b/.trae/documents/任务中心管理系统实现计划.md index 6178948..d7e892a 100644 --- a/.trae/documents/任务中心管理系统实现计划.md +++ b/.trae/documents/任务中心管理系统实现计划.md @@ -1,6 +1,7 @@ # 任务中心管理系统最终实现计划 ## 项目结构设计 + 1. 创建Django项目:`task_center` 2. 创建任务管理应用:`tasks` 3. 配置数据库为SQLite @@ -10,41 +11,45 @@ ## 数据库模型设计 ### Client模型 -| 字段名 | 类型 | 描述 | -|-------|------|------| -| id | AutoField | 客户端ID | -| name | CharField(unique=True) | 客户端标识 | -| token | CharField(max_length=128) | API Token | -| last_seen | DateTimeField | 最后活跃时间 | -| created_at | DateTimeField | 创建时间 | + +| 字段名 | 类型 | 描述 | +| ----------- | -------------------------- | --------- | +| id | AutoField | 客户端ID | +| name | CharField(unique=True) | 客户端标识 | +| token | CharField(max\_length=128) | API Token | +| last\_seen | DateTimeField | 最后活跃时间 | +| created\_at | DateTimeField | 创建时间 | ### Task模型 -| 字段名 | 类型 | 描述 | -|-------|------|------| -| id | AutoField | 任务ID | -| name | CharField | 任务名称 | -| client_name | CharField(null=True, blank=True) | 指定执行客户端 | -| script | TextField(null=True, blank=True) | 执行脚本 | -| status | CharField(choices=STATUS_CHOICES, default='pending') | 任务状态 | -| timeout_seconds | IntegerField(default=259200) | 超时时间(默认3天=259200秒) | -| created_at | DateTimeField | 创建时间 | -| updated_at | DateTimeField | 更新时间 | -| assigned_to | CharField(null=True, blank=True) | 实际执行客户端 | -| started_at | DateTimeField(null=True, blank=True) | 开始执行时间 | -| completed_at | DateTimeField(null=True, blank=True) | 完成时间 | + +| 字段名 | 类型 | 描述 | +| ---------------- | ----------------------------------------------------- | ------------------ | +| id | AutoField | 任务ID | +| name | CharField | 任务名称 | +| client\_name | CharField(null=True, blank=True) | 指定执行客户端 | +| script | TextField(null=True, blank=True) | 执行脚本 | +| status | CharField(choices=STATUS\_CHOICES, default='pending') | 任务状态 | +| timeout\_seconds | IntegerField(default=259200) | 超时时间(默认3天=259200秒) | +| created\_at | DateTimeField | 创建时间 | +| updated\_at | DateTimeField | 更新时间 | +| assigned\_to | CharField(null=True, blank=True) | 实际执行客户端 | +| started\_at | DateTimeField(null=True, blank=True) | 开始执行时间 | +| completed\_at | DateTimeField(null=True, blank=True) | 完成时间 | ### TaskResult模型 -| 字段名 | 类型 | 描述 | -|-------|------|------| -| id | AutoField | 结果ID | -| task | ForeignKey(Task) | 关联任务 | -| client | ForeignKey(Client) | 执行客户端 | -| result_file | FileField(upload_to='task_results/') | 结果文件 | -| status | CharField(choices=STATUS_CHOICES) | 执行状态 | -| message | TextField(null=True, blank=True) | 执行消息 | -| created_at | DateTimeField | 创建时间 | + +| 字段名 | 类型 | 描述 | +| ------------ | -------------------------------------- | ----- | +| id | AutoField | 结果ID | +| task | ForeignKey(Task) | 关联任务 | +| client | ForeignKey(Client) | 执行客户端 | +| result\_file | FileField(upload\_to='task\_results/') | 结果文件 | +| status | CharField(choices=STATUS\_CHOICES) | 执行状态 | +| message | TextField(null=True, blank=True) | 执行消息 | +| created\_at | DateTimeField | 创建时间 | ## 状态定义 + ```python STATUS_CHOICES = [ ('pending', '待分配'), @@ -60,35 +65,53 @@ STATUS_CHOICES = [ ## API接口设计 ### 认证机制 -- **所有API端点均需token认证** -- 客户端通过HTTP头`Authorization: Token `进行身份验证 -- 使用Django REST Framework的TokenAuthentication -- 未提供有效token的请求将返回401 Unauthorized + +* **所有API端点均需token认证** + +* 客户端通过HTTP头`Authorization: Token `进行身份验证 + +* 使用Django REST Framework的TokenAuthentication + +* 未提供有效token的请求将返回401 Unauthorized ### 任务管理API -- `GET /api/tasks/` - 获取任务列表(需认证) -- `GET /api/tasks//` - 获取任务详情(需认证) -- `POST /api/tasks/` - 创建任务(需认证) -- `PUT /api/tasks//` - 更新任务(需认证) -- `DELETE /api/tasks//` - 删除任务(需认证) + +* `GET /api/tasks/` - 获取任务列表(需认证) + +* `GET /api/tasks//` - 获取任务详情(需认证) + +* `POST /api/tasks/` - 创建任务(需认证) + +* `PUT /api/tasks//` - 更新任务(需认证) + +* `DELETE /api/tasks//` - 删除任务(需认证) ### 客户端API -- `POST /api/tasks/claim/` - 客户端原子认领任务(需认证) -- `POST /api/tasks//start/` - 客户端开始执行任务(需认证) -- `POST /api/tasks//complete/` - 客户端完成任务(需认证) -- `POST /api/task_results/` - 上传任务结果(需认证,支持文件上传) + +* `POST /api/tasks/claim/` - 客户端原子认领任务(需认证) + +* `POST /api/tasks//start/` - 客户端开始执行任务(需认证) + +* `POST /api/tasks//complete/` - 客户端完成任务(需认证) + +* `POST /api/task_results/` - 上传任务结果(需认证,支持文件上传) ### 客户端管理API -- `GET /api/clients/` - 获取客户端列表(需认证) -- `POST /api/clients/` - 创建客户端(需认证) -- `GET /api/clients//` - 获取客户端详情(需认证) + +* `GET /api/clients/` - 获取客户端列表(需认证) + +* `POST /api/clients/` - 创建客户端(需认证) + +* `GET /api/clients//` - 获取客户端详情(需认证) ### 文件下载API -- `GET /api/task_results//download/` - 下载任务结果文件(需认证) + +* `GET /api/task_results//download/` - 下载任务结果文件(需认证) ## 文件上传实现细节 ### 1. Django媒体文件配置 + ```python # settings.py MEDIA_URL = '/media/' @@ -96,28 +119,43 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media') ``` ### 2. 文件上传API实现 -- 使用Django REST Framework的`MultiPartParser`和`FormParser` -- API端点:`POST /api/task_results/` -- 支持`multipart/form-data`格式上传文件 -- 上传字段:`task_id`、`status`、`message`、`result_file` + +* 使用Django REST Framework的`MultiPartParser`和`FormParser` + +* API端点:`POST /api/task_results/` + +* 支持`multipart/form-data`格式上传文件 + +* 上传字段:`task_id`、`status`、`message`、`result_file` ### 3. 前端文件上传实现 -- 使用HTML5的`input type="file"`元素 -- 表单设置`enctype="multipart/form-data"` -- 使用Bootstrap样式美化文件上传控件 -- 支持进度条显示(可选) + +* 使用HTML5的`input type="file"`元素 + +* 表单设置`enctype="multipart/form-data"` + +* 使用Bootstrap样式美化文件上传控件 + +* 支持进度条显示(可选) ### 4. 文件存储策略 -- 本地文件系统存储:`media/task_results/`目录 -- 文件名自动生成,避免冲突 -- 支持大文件上传(通过Django默认配置) + +* 本地文件系统存储:`media/task_results/`目录 + +* 文件名自动生成,避免冲突 + +* 支持大文件上传(通过Django默认配置) ### 5. 文件下载实现 -- API端点:`GET /api/task_results//download/` -- 返回`Content-Disposition: attachment`头,触发浏览器下载 -- 支持断点续传(通过Django默认配置) + +* API端点:`GET /api/task_results//download/` + +* 返回`Content-Disposition: attachment`头,触发浏览器下载 + +* 支持断点续传(通过Django默认配置) ## 前端页面设计 + 1. 任务列表页:展示所有任务,支持筛选和搜索 2. 任务创建页:表单创建新任务 3. 任务详情页:查看任务详情和执行结果历史 @@ -128,26 +166,37 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media') ## 核心功能实现 ### 1. 全API Token认证 -- 所有API视图均使用`TokenAuthentication` -- 配置`DEFAULT_AUTHENTICATION_CLASSES`和`DEFAULT_PERMISSION_CLASSES` + +* 所有API视图均使用`TokenAuthentication` + +* 配置`DEFAULT_AUTHENTICATION_CLASSES`和`DEFAULT_PERMISSION_CLASSES` ### 2. 原子任务认领机制 -- 使用数据库事务确保任务认领的原子性 -- 客户端调用`/api/tasks/claim/`时,系统自动查找并分配可用任务 + +* 使用数据库事务确保任务认领的原子性 + +* 客户端调用`/api/tasks/claim/`时,系统自动查找并分配可用任务 ### 3. 任务超时自动回收 -- 使用Django管理命令定期检查超时任务 -- 超过`timeout_seconds`的任务自动设置为`timeout`状态 -- 管理命令:`python manage.py check_task_timeouts` + +* 使用Django管理命令定期检查超时任务 + +* 超过`timeout_seconds`的任务自动设置为`timeout`状态 + +* 管理命令:`python manage.py check_task_timeouts` ### 4. 任务结果版本管理 -- TaskResult模型记录每次执行结果 -- 支持查看任务的完整执行历史 -- 支持下载不同版本的结果文件 + +* TaskResult模型记录每次执行结果 + +* 支持查看任务的完整执行历史 + +* 支持下载不同版本的结果文件 ## 测试用例设计 ### 目录结构 + ``` task_center/ ├── tasks/ @@ -164,14 +213,16 @@ task_center/ ``` ### 测试类型 + 1. **模型测试**:测试模型字段、方法和关系 2. **API测试**:测试所有API端点的功能和认证机制 3. **文件上传测试**:测试文件上传和下载功能 4. **视图测试**:测试前端视图渲染 5. **集成测试**:测试完整的任务流程 -6. **工厂测试**:使用factory_boy创建测试数据 +6. **工厂测试**:使用factory\_boy创建测试数据 ## 实现步骤 + 1. 创建Django项目和应用 2. 配置项目设置(数据库、REST Framework、认证、媒体文件等) 3. 实现数据库模型 @@ -183,18 +234,32 @@ task_center/ 9. 测试API接口和功能 ## 技术栈 -- 后端:Django 5.0.6 + Django REST Framework -- 前端:HTML + Bootstrap 5 -- 数据库:SQLite -- 文件存储:本地文件系统 -- 测试:pytest + factory_boy + +* 后端:Django 5.0.6 + Django REST Framework + +* 前端:HTML + Bootstrap 5 + +* 数据库:SQLite + +* 文件存储:本地文件系统 + +* 测试:pytest + factory\_boy ## 预期效果 -- 所有API端点均需要token认证 -- 支持结果文件的上传和下载功能 -- 后台可通过admin或API创建和管理任务 -- 客户端通过API进行身份验证,原子认领任务 -- 支持任务超时自动回收 -- 完整的任务执行历史记录 -- 简单大方的Bootstrap前端界面 -- 全面的测试用例覆盖 \ No newline at end of file + +* 所有API端点均需要token认证 + +* 支持结果文件的上传和下载功能 + +* 后台可通过admin或API创建和管理任务 + +* 客户端通过API进行身份验证,原子认领任务 + +* 支持任务超时自动回收 + +* 完整的任务执行历史记录 + +* 简单大方的Bootstrap前端界面 + +* 全面的测试用例覆盖 + diff --git a/db.sqlite3 b/db.sqlite3 index 3b9a0a5b311d2fe634655d9dd91123037a0a66fd..c2bf6a11d29344558e1eff89bad3c1549cafe643 100644 GIT binary patch delta 507 zcmajbPjAv-0LSq*LnB+H^E!3bV0P=$KhHy35)WO00(LOg4uMP8K~vj4Yhjeqav8Wc zfbkvdpl8P$*h}DO77ugp!MnW}PsZeT{pOc^=C|4TZFcz_-~04J#`nIRelaozMi{Ht zoX?$U6byZo5P zA01wuNRCtBXCCJw-g2P>Cl?Tj&>Pzh%Udku(7siV+iq#xoVrr$z%m>K`BYu4G_;;D zd#^^UY6JTijC9$raAn7`v0R9}4uWQU$aaVT4;Cbp_jiu#vAGV8Pl z^`Ta+d4>r~AABohnj%HnEji@%ZbMN*c^cAaSabDq>B5_Vqi}pAPxR(=M4QYx3JyAF zfqLwkF$+L7(ELOwTe=O3Or)CvC@KRu=tdL9KxWLvn$VXuo8TczV3l`+_b2PFr@-j5qGE7d-PuTo0Us+)@ u%L8@+My}0*4e$9U2CyzVAkcJR+W|%vgT*WxfRY>j$ZtBpA^=qNM;-t`>>wNf diff --git a/task_center/__pycache__/settings.cpython-311.pyc b/task_center/__pycache__/settings.cpython-311.pyc index e45fd21be70fb212ce341d873af9c7b124425c49..f1b19f3602886247fc995493508b8078f3100c69 100644 GIT binary patch delta 1039 zcmZWnOG_J36ux(onaoTwaeODfpVfdhSj75Rr6#nc)|6!0>ZK5(CykTF2a~iEDL6tG zE?g)n)P;*6#TMKM`4KI2<48c6m8-4;f{Q|-cM>d`o_o*Zp8K6M-(l{b#!IjJt0akt z%GvmY`m<)&?NP2ULZ1;Jf&c@P#spJuOEb$hV#8L>p0+QugvE$K3HFfI9$5{_QqI{D z+#&68M71J$$8Mx|gTFqe{zXKfEpVz#z`6w%xI>1PQmqJaRv{d~-$nrcri>=8Dx?77 zP2>hbdFi_CO%Kl7Y>7LGz;%F04fPWBGPp@CSSJ~3sjWM}Du()fdlHj6Xdv~_2u%qA zn%A9l1kXOBV$uL!XrU-Cw3cm~x?vsKRh+bwM(7|-&`Fv>CLXF@=prpu?7L~h9;@Na z84x0^&`a8&5Bigo1Zjsmq@${GfL0FvU&)hBV4}$9D-7XD+&AmPru|twyF|S+y&8uY ztY#A{?@TconvR5&*ptwFZiNYUn`|)j;Q2$-u4r!)rWBZ&3BL>lV~@g7C2FGIc&Jcd zUNBfG2y6u7hQceZ7LPgBm+mgbS2F8TAfv|pQ5q+g;_5o@&t&6SbVmb-k>}+0K1UrVw|9v{*rhu+rH_&>Y%DvusV4SAv{ zPvm|HKSo+~>B7~j3m48Boxczyoh!MJ+oOx;^ivDl>YkK;e>`XmOd9T~qI*hr=Dv&l bolO-M`MG0<(SP5N#*5OpE?P?)rNjRP2C(!d delta 537 zcmYjNyK59-5dUVMci%p4_a3?3XbcHh97%{+iP*&4`oQSP-X`MLEN5of+mNW_&cZbA zKOhpe(BcXSqJo9Rfvv5jHWpi5W4~Z6_-#aS9={ppH~a?Xr~9jAzB3F;xLG?FllSJa z>8uhU%0aimB>wL?`#wj*W zv6Vn9U1Ao_F&o>g&JQ+tHql`%blDuvFYuIo9y?R9T%gnq9VWE1LNSmQq}Up*RfEJOe(1PW3?N^l6cUy1X7WY@H1+a#tB zDJ>LIHBxzI)w5697uQLRnQo`mnN~|HGtMZ5PRw>nvyb|DyY))_W-O+(^urZ?@i*U= BfY1N{ diff --git a/task_center/__pycache__/urls.cpython-311.pyc b/task_center/__pycache__/urls.cpython-311.pyc index 0f1ee799515793c921c7e0ea78737c102cf964f5..ef4074c0ba046d9eca7c60f846f660a19696f7de 100644 GIT binary patch delta 53 zcmZqUY3Jcx&dbZi00gUotuk#k@}6gAXV1tlElJGW{D^rMBd_EH*C}38{4cVoTwzg} IyplB*0Fp!yqyPW_ delta 52 zcmZqYY2)Et&dbZi00fl@Mw!+ddCxPmu_dPDX69{v%)E<{S7L(c6ssxr7g?0AuqaPn H#Tp9$bh!`1 diff --git a/tasks/__pycache__/admin.cpython-311.pyc b/tasks/__pycache__/admin.cpython-311.pyc index b9941d0ce9c07e2a2517ddced49bcb856a39e535..20ecb781aabb0e9f11575e1af568ed1011727164 100644 GIT binary patch literal 2291 zcmb_dOKTff7`=C1l17%_mYvv%-4L5jNem5zf?KzgESl2B=_9l-7>23t*qvnb=$%o) zJC&Ng=)zElDTGpY;b2S>3Qf}DMgD>eT8O&})RyC&jFU}Q{k|E=sx60R(HYIreSG)a z$2s%OtyD@UaNYZ4%Kacl$X{4#URtBDKSBvvCX6uZkt{VSB}`(nCuL<*&MKyoRZW!= zi8%CHa}06hRT#AnPjpG-ZNijA!c>TYnWAe|h8TDnHTYX^R)`rt!l)d>r~zZ_xfv5j z7}aAKo%MP;HiVbGx`}Ll)Xa&vkNvP_Ekkb)h4E==eo1$Q*1E z3e6+hKLeFz;t-QEVoHpfGLuY&$)?H_Q)8+bV;W2r6Um78Eu5>^3!}F0{960=!Or^K zojWT#OJDE&wqYnj$=O*)s6qa^lM@Ng_5;gzpr~iKV+Rhi?7)zC3WoCz0E2S0f+6!H zHXXFj7;zD|EF`lmp<7lq&x#(FJ1pz-qU|+$VwT178Ep1kn2Nc6!Lt`cGOW$HUf^(% z@*SIJKC|Xr$78+=_aeMpWSF-^es6QJX`D9^%Cve>eDm?y+0HuTbkup+NMyh>pmfoxOo-e&y z?LSqzTuo1vuHelFn?nyN(=_w`CFuSyWmt^GT{Zi=G)4eD@v!H@F+DvUJzD7U2#JXAQ2j>uky zQs|kTa2d55-)wE%hkTmkVZMa?;5qy(hEE{iyUh)RBf6f$HUx}q({;40j8^-mQP)>c z*J;#syrt`Oo30bbs2AxveoW6%U2!2ZMpAS}$!ikc*nqpAa{S;44f3et`5coK$4tZV zt0>~!nTPxd$x^?J0ijM&Y70#p?(p!g|Bk;N<>WW`t3ZL@6Tb(rE>lXYWUPGnS0(4l z&;Eg-rS8(e;^`%AlMYtsU|mw_Yjx5NOXHQ^Gke(a1Yu7m($GPCcaufgqyrT?P?vhC sRwwPSq*l7e_ORm#0av7r-85vTTf*CZK zUxE~9GTvfMOv%m6^V4Ly#g~$mn3tZfmz=%bkZhlH>PO4oI a2T+U=h>K+zCf{b4Kp_CV)gVj& diff --git a/tasks/admin.py b/tasks/admin.py index ea5d68b..a264d99 100644 --- a/tasks/admin.py +++ b/tasks/admin.py @@ -1,3 +1,56 @@ -from django.contrib import admin +from django.contrib import admin +from .models import Client, Task, TaskResult + + +@admin.register(Client) +class ClientAdmin(admin.ModelAdmin): + """客户端管理类""" + list_display = ('name', 'token', 'last_seen', 'created_at') + list_filter = ('created_at', 'last_seen') + search_fields = ('name',) + readonly_fields = ('token', 'created_at', 'last_seen') + + +@admin.register(Task) +class TaskAdmin(admin.ModelAdmin): + """任务管理类""" + list_display = ('name', 'client_name', 'status', 'timeout_seconds', 'created_at', 'updated_at', 'assigned_to') + list_filter = ('status', 'created_at', 'updated_at', 'started_at', 'completed_at') + search_fields = ('name', 'client_name', 'assigned_to') + readonly_fields = ('created_at', 'updated_at', 'started_at', 'completed_at') + fieldsets = ( + (None, { + 'fields': ('name', 'client_name', 'script', 'status', 'timeout_seconds') + }), + ('执行信息', { + 'fields': ('assigned_to', 'started_at', 'completed_at'), + 'classes': ('collapse',) + }), + ('时间信息', { + 'fields': ('created_at', 'updated_at'), + 'classes': ('collapse',) + }), + ) + + +@admin.register(TaskResult) +class TaskResultAdmin(admin.ModelAdmin): + """任务结果管理类""" + list_display = ('task', 'client', 'status', 'created_at') + list_filter = ('status', 'created_at', 'client') + search_fields = ('task__name', 'client__name', 'message') + readonly_fields = ('created_at',) + fieldsets = ( + (None, { + 'fields': ('task', 'client', 'status', 'message') + }), + ('结果文件', { + 'fields': ('result_file',), + 'classes': ('collapse',) + }), + ('时间信息', { + 'fields': ('created_at',), + 'classes': ('collapse',) + }), + ) -# Register your models here. diff --git a/tasks/templates/tasks/index.html b/tasks/templates/tasks/index.html index dba7006..8b38124 100644 --- a/tasks/templates/tasks/index.html +++ b/tasks/templates/tasks/index.html @@ -28,7 +28,7 @@ 客户端管理 @@ -50,7 +50,7 @@ @@ -78,7 +78,7 @@
后台管理

使用Django Admin进行系统管理,包括用户、任务和客户端。

- 进入 + 进入