- 后端
- 前端
- 协同办公
- 即时通讯
- 企业应用
【免费下载链接】dootask
DooTask是一款开源在线项目任务管理工具,提供各类文档协作工具、在线思维导图、在线流程图、项目管理、任务分发、即时IM,文件管理等工具;同时消息功能使用非对称加密技术让你的沟通更安全。
本文基于 DooTask 知识库文档《子任务是什么》及其配套的限制说明展开,讲清子任务(subtask)的定义、关键属性、创建方式与全部硬性限制,并结合 ProjectTask 模型、ProjectController 接口 与数据库迁移文件,把「50 个子任务上限」「只允许 1 层嵌套」「父删子级联删」等规则逐条落到源码证据上。读完本文,你将能准确判断子任务与关联任务的适用边界,并能通过 API 或界面正确地拆分、统计和管理任务。
一、子任务是什么
子任务是挂在某个父任务下的下级任务,用于把一个较大的工作拆分成多个可独立分配、独立完成的小步骤。父子关系通过project_tasks表的parent_id字段维护:parent_id = 0表示主任务(根任务),parent_id > 0表示它是某个父任务的子任务。
这一点在数据库迁移中可以直接确认,建表脚本 中定义了该字段:
$table->bigInteger('parent_id')->nullable()->default(0)->comment('父级任务ID');同时 2023 年的索引迁移 为parent_id补充了复合索引(['parent_id', 'deleted_at']),说明「按父任务查子任务」是高频查询路径。从源码结构看,DooTask 没有为子任务单独建表——每个子任务都是一条完整的project_tasks记录,是一个独立的任务实例,可以有自己的负责人、截止时间、状态。
二、关键属性与源码印证
知识库文档给出的四个关键属性,都能在源码中找到对应实现:
2.1 从属关系:父任务删除,子任务级联删除
子任务必须有父任务;删除父任务时所有子任务一起删除(软删),还原父任务时子任务一并还原。ProjectTask::deleteTask() 的实现非常直白:
public function deleteTask($pushMsg = true) { AbstractModel::transaction(function () { if ($this->dialog_id) { $dialog = WebSocketDialog::find($this->dialog_id); $dialog?->deleteDialog(); } self::whereParentId($this->id)->remove(); // 级联软删全部子任务 $this->deleted_userid = User::userid(); $this->save(); $this->addLog("删除{任务}"); $this->delete(); }); ... }restoreTask() 对称地执行self::whereParentId($this->id)->withTrashed()->restore(),保证父任务还原后子任务不丢失。
2.2 独立性:子任务有自己的状态、负责人与时间
子任务写入ProjectTaskUser表时维护task_pid字段:若任务本身是主任务则等于自身 id,若是子任务则等于父任务 id。这一设计让「查某主任务的全部相关人员」可以一次性带出子任务成员,见 relationUserids()。日志体系也利用了这种独立性:addLog() 中子任务的日志文案会显示为「子任务」,且日志的task_id记录在父任务名下('task_id' => $this->parent_id ?: $this->id),并附加record.subtask记录子任务自身信息,因此任务动态里能看到「某某子任务被完成」这类细粒度操作。
2.3 可见性:继承父任务,不可单独配置
修改主任务可见性时,ProjectTaskObserver::visibilityUpdate() 会执行:
ProjectTask::whereParentId($projectTask->id)->change(['visibility' => $projectTask->visibility]);即父任务可见性一变,其全部子任务的visibility被同步改写。创建子任务时同样如此:task__addsub() 直接将父任务的visibility传给addTask()。
2.4 完成度:父任务显示「子任务 X/Y」
模型内的 generateSubTaskData() 负责计算这三个对外属性:
sub_num:parent_id等于本任务 id 的子任务总数;sub_complete:其中complete_at非空(已完成)的数量;percent:主任务已完成则强制 100,否则取sub_complete / sub_num * 100。
注意该函数开头有一段判断:若parent_id > 0(自身是子任务),sub_num与sub_complete直接置 0——这与「子任务不能再挂子任务」的限制相互印证。
前端刷新进度不必拉全量任务,task__subdata()(api/project/task/subdata)就是专门为此设计的轻量接口,只返回:
{ "id": 123, "sub_num": 5, "sub_complete": 2, "percent": 40 }三、使用场景
- 拆分大功能为多个开发步骤(前端 / 后端 / 测试 / 上线);
- 将复杂请求分配给团队不同成员(每个子任务有独立负责人);
- 在一个任务卡片里管理多个相关动作,父任务进度条自动反映整体完成度。
四、创建方式与默认值
4.1 界面操作
进入任务详情页 → 「子任务」区 → 「+ 新增子任务」→ 输入名称 → 回车。名称必填且不超过 255 字符(addTask() 中校验:mb_strlen($name) > 255时抛出「子任务描述最多只能设置255个字」)。
4.2 API 创建:task/addsub
task__addsub() 对应api/project/task/addsub,需要 token 身份,且要求调用者具备TASK_ADD项目权限。参数只有两个:task_id(父任务 id)与name(子任务名称)。默认值规则从源码可完整读出:
| 字段 | 默认值 | 源码依据 |
|---|---|---|
| 负责人 | 当前登录用户 | 'owner' => [User::userid()] |
parent_id | 父任务 id | 直接取$task->id |
project_id/column_id | 继承父任务 | 直接取$task->project_id、$task->column_id |
| 计划时间 | 继承父任务时间范围 | 'times' => [$task->start_at, $task->end_at] |
| 可见性 | 继承父任务 | 'visibility' => $task->visibility |
创建成功后,会向「子任务相关人员 + 项目主/副负责人」逐一推送add消息。另一个前置校验值得注意:
if ($task->complete_at) { return Base::retError('主任务已完成无法添加子任务'); }即已完成的主任务不能追加子任务。
4.3 建主任务时批量携带子任务
api/project/task/add支持subtasks数组参数(格式[{name, owner, times}]),可在创建主任务的同时落库多个子任务。addTask() 中对每个子任务执行两个约束:
- 子任务若自带时间,开始时间不能早于主任务开始时间、结束时间不能晚于主任务结束时间,否则分别抛出「子任务开始时间不能小于主任务开始时间」「子任务结束时间不能大于主任务结束时间」;
- 未指定时间时继承主任务时间。
五、硬性限制:只允许 1 层、最多 50 个、不能跨项目
DooTask 对子任务模型刻意做了简化,以下三条限制由配套文档《子任务的限制(嵌套 / 数量 / 跨项目)》明确说明,并均有代码兜底:
5.1 嵌套层级:最多 1 层
子任务的parent_id必须指向主任务(parent_id = 0的任务)。批量创建分支只存在于if ($task->parent_id == 0 && $subtasks && is_array($subtasks))中(见 addTask()),api/project/task/addsub也不做「父任务是子任务」时的拒绝提示之外的特殊分支——从整体数据约束看,parent_id链被设计为深度 1。想表达更深层级,官方建议改用关联任务,或按业务拆成多个独立任务。
5.2 数量上限:单个父任务最多 50 个子任务
addTask() 在落库前做硬校验:
if ($parent_id > 0 && ProjectTask::whereParentId($parent_id) ->whereNull('project_tasks.complete_at') ->whereNull('project_tasks.archived_at') ->count() > 50) { throw new ApiException('每个任务的子任务最多不能超过50个'); }注意这里统计的是未完成且未归档的子任务——这意味着「归档已完成的子任务腾位置」是官方认可的扩容手段(归档文档见知识库 howto 中任务归档条目)。同一方法里还有两道相邻的容量红线,供容量规划参考:单个项目未完成任务不超过 2000 个、单个列表未完成任务不超过 500 个。
5.3 跨项目:禁止
子任务自动继承父任务的project_id、column_id、可见性与对话归属,不能把子任务移到别的项目。历史上存在子任务与父任务项目不一致的脏数据时,由迁移脚本修复,例如 2024_12_16 的修复迁移 会按父任务回写子任务的project_id/column_id,2024_12_21 的迁移 则统一了子任务可见性——可见「与父任务同项目」是系统长期维护的不变式。若父任务被整体移动到其他项目,子任务随之一起走。跨项目协作应使用关联任务。
六、子任务不支持的功能清单
以下能力在子任务上被显式屏蔽,接口文档(task__update() 的参数注释)逐条标注了「子任务不支持」:
| 不支持项 | 说明 / 源码依据 |
|---|---|
独立任务聊天室dialog_id | 子任务无独立对话;syncDialogUser() 中若parent_id > 0,直接把同步请求转发给父任务处理 |
直接上传附件file_num | getFileNumAttribute() 中parent_id > 0时file_num恒为 0,附件必须挂在父任务 |
重复周期loop | 更新接口参数loop标注「子任务不支持」,传入会返回「子任务不支持此功能」(错误分支、另一处校验) |
任务详情content、背景色color | 同上,参数标注「子任务不支持」 |
修改协助人assist、任务标签task_tag | 同上 |
优先级p_level/p_name/p_color | 主任务缺省时按系统优先级配置补齐(addTask() 中if ($parent_id == 0)分支才处理优先级);子任务在升级为主任务时会从父任务继承优先级 |
| 单独配置 visibility | 创建时继承父任务,父任务变更时被 Observer 强制同步(见 2.3) |
此外还有一条时间约束:addsub创建时子任务时间窗口被固定为父任务的时间范围;批量创建时子任务时间必须落在主任务时间范围内(见 4.3)。
七、父子联动行为汇总
结合文档与 ProjectTask 模型,父子联动可归纳为一张表:
| 父任务操作 | 子任务行为 |
|---|---|
| 删除 | 级联软删(deleteTask()中whereParentId($this->id)->remove()) |
| 还原 | 级联还原(restoreTask()中withTrashed()->restore()) |
| 归档 / 取消归档 | 子任务跟随归档,archived_at、archived_userid、archived_follow三个字段被整体同步(归档逻辑) |
| 跨项目移动 | 子任务整体随迁 |
| 完成 | 不会自动完成子任务,需要手动逐个完成;此时父任务percent直接按 100% 展示 |
| 修改可见性 | 子任务 visibility 被批量改写 |
其中archived_follow字段(建表注释:跟随项目归档)在 模型属性注释 中也有说明,用于区分「跟随归档」与「独立归档」两种状态。
八、与「关联任务」的区别
这是最容易混淆的一对概念,两者底层实现完全不同:
| 维度 | 子任务(parent_id) | 关联任务(ProjectTaskRelation) |
|---|---|---|
| 层级 | 父子,从属关系 | 平级引用,互不从属 |
| 跨项目 | 禁止,强制同project_id | 允许 |
| 删除联动 | 父删则子级联软删 | 两边互不影响(只是关系记录消失) |
| 完成度统计 | 父任务显示「子任务 X/Y」并计算 percent | 不参与统计 |
| 数量限制 | 单个父任务最多 50 个子任务 | 无硬上限 |
| 附加能力 | — | 关联双向可见:A 关联 B 后,B 侧也能看到 A(api/project/task/related返回,task__related()) |
从源码结构看,关联任务由ProjectTaskRelation表承载(字段含task_id、related_task_id、direction、userid),方向分为消息提及(mention / mentioned_by,绑定dialog_id+msg_id)与任务直接关联两类。选型经验法则:有归属、要统计进度、要一起生灭用子任务;只是引用、要跨项目、不希望删除联动用关联。
一个有趣的衔接点:「子任务升级为主任务」成功后,系统会自动在两者之间写入一条 mention 方向的关联记录(task__upgrade()),让升级前的父子痕迹仍可通过关联任务找回。
九、进阶:把子任务升级为主任务
DooTask 支持将子任务反转为独立主任务,对应api/project/task/upgrade(task__upgrade()),参数为子任务task_id。执行过程在一个事务中完成:
- 校验:
parent_id == 0直接返回「当前任务已是主任务」;需具备TASK_MOVE权限; - 置
parent_id = 0,并从父任务继承p_level/p_name/p_color(补齐子任务缺失的优先级字段); - 将该任务在
ProjectTaskUser中的task_pid改写为自身 id(脱离父任务的人员聚合); - 若可见性是「指定人可见」,将原可见人列表重建到本任务名下(父任务可见人记录不再自动跟随时需自持一份);
- 双端写入日志「子任务升级为主任务」「升级为主任务」,并向双方推送
update,再补写双向关联(见上一节)。
十、查询子任务的接口约定
任务列表接口支持用parent_id直接查询子任务集合(task 查询接口 的参数约定):
parent_id > 0:只查该主任务下的子任务(此时归档过滤强制为 all,忽略 project_id / scope);parent_id = -1:只查主任务(where('parent_id', 0))。
列表数据中还带聚合出的sub_num/sub_complete/ 进度百分比(SQL 聚合:count(*)与sum(CASE WHEN complete_at IS NOT NULL ...)按parent_id分组),保证看板卡片不点开详情也能显示「子任务 X/Y」。
十一、小结
DooTask 的子任务是一个刻意保持简单的两层模型:parent_id一个字段承载父子关系,子任务复用完整任务表从而天然具备负责人、状态与时间等独立属性,同时通过可见性继承、附件/对话/标签禁用、50 个上限与禁止跨项目等约束,把复杂度控制在「一个父任务、一层子任务」之内。理解上述规则后,你可以:
- 用
task/addsub或建任务时携带subtasks参数完成拆分; - 用
sub_num/sub_complete/percent三件套跟踪整体进度; - 在触及 50 上限时用「归档已完成子任务」腾出空间;
- 需要更深层级或跨项目引用时,果断切换到关联任务(
ProjectTaskRelation)。
以上行为均基于当前仓库代码与知识库文档(文档标注last_verified: v1.7.90)核对;若你的版本较旧或较新,建议以 ProjectController.php 与 ProjectTask.php 中的实际实现为准。
- 后端
- 前端
- 协同办公
- 即时通讯
- 企业应用
【免费下载链接】dootask
DooTask是一款开源在线项目任务管理工具,提供各类文档协作工具、在线思维导图、在线流程图、项目管理、任务分发、即时IM,文件管理等工具;同时消息功能使用非对称加密技术让你的沟通更安全。
相关推荐
DooTask 子任务的硬限制:嵌套层级、数量上限与跨项目禁令的源码解析
DooTask 子任务的硬限制:嵌套层级、数量上限与跨项目禁令的源码解析 DooTask 的子任务(subtask)通过 parent_id 字段把大任务拆分为
后端前端协同办公即时通讯企业应用CacheCloud Redis 任务流管理实战:以 RedisClusterAppDeployTask 为例解读父子任务、步骤执行与重试机制
CacheCloud Redis 任务流管理实战:以 RedisClusterAppDeployTask 为例解读父子任务、步骤执行与重试机制 CacheClo
后端运维Resque任务调度系统中的钩子机制详解
Resque任务调度系统中的钩子机制详解 概述 Resque是一个基于Redis的Ruby后台任务处理系统,它提供了一套完善的钩子 Hook 机制,允许开发者在
任务调度消息队列后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考