简介:这份资源是北方民族大学软件工程专业《软件工程项目管理》课程设计的完整报告文档,面向软件工程、计算机相关专业学生以及需要撰写项目计划任务书的学习者,围绕区块链技术应用于教务管理系统的选题展开。内容按标准课程设计框架组织,涵盖引言与术语说明、项目开发背景与意义、项目初始范围(含系统业务价值、系统层次图、功能描述)、生存期模型选择与敏捷开发方法、区块链技术选型理由,以及用户需求概述、开发团队与开发环境、基于系统功能分解和基于项目开发过程的WBS方案等模块,后续还涉及进度计划、进度估算与软件估算等内容。资源包共1个doc文件,压缩后约1.47MB,篇幅结构完整,可直接作为课程设计报告的写作模板与章节参考。目前已有1669人浏览学习,适合需要参考WBS分解、项目进度安排与估算方法,或想了解区块链+教务管理系统方案设计思路的读者对照使用。
1. 一份「教务管理系统软件项目计划任务书.doc」为什么总在立项会上被打回
开学前两周,排课结果还在改,选课并发方案还没定,这时候一份《教务管理系统软件项目计划任务书.doc》被摊到评审桌上,这是很多学校信息化项目最真实的起点。它要解决的不是“把文档写漂亮”,而是让教务业务范围、工期、人力、验收口径在一份能被签字的文件里对齐。
被打回的原因通常只有两类。一类是范围没锁死——学籍、培养方案、开课、排课、选课、考务、成绩、毕业审核全写进去,却没说谁输入、谁加工、谁存储,评审一追问就露出空白。另一类是工期拍脑袋,“大概两个月”经不起一句“依据是什么”的反问,后面全是需求变更单和口头承诺。
下面这套内容面向要独立写出这份任务书的开发负责人、教务信息化专员和项目经理:从教务管理系统数据流图的画法,到 WBS 与三点估算的算法,再到 .doc 模板生成、无法预览 doc 的排查顺序。每一步都给出可复制的表和代码,拿去填自己学校的版本即可。
2. 教务管理系统范围界定与数据流图:任务书里最容易被追问的两块
范围和数据流是软件项目任务书里最容易被逐条追问的部分,也是后面所有工期估算的输入。一份任务书写到第三稿还在改范围,基本等于没写。先做两件事:用业务事件圈边界,用分层数据流图把边界画实。
2.1 用角色和业务事件圈定教务管理系统的功能边界
不要把“功能清单”当成范围。功能清单是静态的,业务事件是动态的,评审专家问的往往是“开学第一周会发生什么”,而不是“系统有几个菜单”。做法是按学期时间轴列业务事件,再给每个事件挂一个加工和一个角色。
| 角色 | 触发事件 | 对应加工(0 层) | 任务书中的边界说明 |
|---|---|---|---|
| 学生 | 每学期选课周 | P3 选课处理 | 只处理选课、退改选与结果查询,不含学费缴纳 |
| 教师 | 期末提交成绩 | P6 成绩录入与审核 | 只到成绩提交与提交后修改申请,不含学分置换 |
| 教务管理员 | 每学期排课 | P4 排课处理 | 输出教学任务与课表,不含教室资产台账 |
| 系主任 | 培养方案调整 | P2 培养方案维护 | 变更必须走审批流,不允许直接改库 |
| 系统管理员 | 账号与权限变更 | P7 权限与日志 | 任务书只写角色权限矩阵,不写具体实现 |
“不做什么”比“做什么”更值钱。表里最后一列就是留白清单,写进任务书后,后续任何越界需求都能对着它判定是变更而不是补充。
2.2 教务管理系统数据流图的三种画法与命名规范
数据流图分三层就够用:顶层图只放一个加工和所有外部实体;0 层图把系统拆成 6 到 10 个加工;1 层图只展开高风险加工,通常是选课、排课、成绩三条线。不要一次画到 3 层,任务书的读者是评审和甲方,不是设计文档的评审人。
命名规范要统一,否则图和文字对不上:
- 加工用“动词 + 名词”,如“P3 选课处理”,不要写“选课模块”。
- 数据存储用名词,如“D3 选课库”,不要写“选课表”。
- 数据流用名词短语,如“F5 选课申请单”,不要写“学生选课”。
- 每个加工必须有进有出。只有输入没有输出叫黑洞,只有输出没有输入叫奇迹,这两种图在评审现场一抓一个准。
数据流清单要和图同步维护,用编号对齐:
| 编号 | 数据流名称 | 来源 | 去向 | 主要组成 |
|---|---|---|---|---|
| F5 | 选课申请单 | 学生 | P3 选课处理 | 学号、课程号、教学班号、志愿序号 |
| F6 | 选课结果 | P3 选课处理 | 学生 / D3 选课库 | 学号、教学班号、结果状态、时间戳 |
| F7 | 学生课表 | P3 选课处理 | 学生 | 周次、节次、教室、任课教师 |
| F9 | 选课规则 | D5 规则库 | P3 选课处理 | 学分上限、先修课约束、冲突判定阈值 |
2.3 用脚本校验数据流图的父子平衡
0 层图和 1 层子图之间必须父子平衡:父加工的每一条输入流、输出流,都要在子图里有落点。手画到十几个加工时很容易漏,用一段脚本过一遍比人眼快。
# dfd_balance.py —— 校验 0 层加工与 1 层子图的父子平衡 from collections import namedtuple Node = namedtuple("Node", "inputs outputs") # 父加工:0 层图中 P3 的输入输出流 parent = Node( inputs={"F5 选课申请单", "F9 选课规则"}, outputs={"F6 选课结果", "F7 学生课表"}, ) # 子加工:1 层图中 P3 的分解 children = { "P3.1 校验选课资格": Node({"F5 选课申请单"}, {"F10 资格通过名单"}), "P3.2 名额分配与抽签": Node({"F10 资格通过名单", "F9 选课规则"}, {"F6 选课结果"}), "P3.3 生成学生课表": Node({"F6 选课结果"}, {"F7 学生课表"}), } def check(parent, children): child_in = set().union(*(c.inputs for c in children.values())) child_out = set().union(*(c.outputs for c in children.values())) problems = [] for f in parent.inputs - child_in: # 父图有、子图无 -> 该流悬空 problems.append(f"输入流 [{f}] 在子图中没有落点") for f in parent.outputs - child_out: # 父图有、子图无 -> 没有产出来源 problems.append(f"输出流 [{f}] 在子图中没有产出来源") for name, c in children.items(): if not c.inputs: problems.append(f"[{name}] 是奇迹加工:只有输出没有输入") if not c.outputs: problems.append(f"[{name}] 是黑洞加工:只有输入没有输出") return problems result = check(parent, children) print("父子平衡通过" if not result else "\n".join(result))这段逻辑的关键在集合差集:parent.inputs - child_in找出悬空流,set().union(*[...])把所有子加工的流合并成一个集合。注意子图内部的中间流(如 F10 资格通过名单)不会出现在父图里,这是允许的,脚本刻意不做反向校验,否则每个正常分解都会报“多余流”。
提示:把这段脚本的输出贴进任务书的附录,评审时能省掉一半“你这个图是不是画漏了”的争论。
3. 从任务书到可执行计划:教务管理系统的 WBS、三点估算与关键路径
范围定完,接下来把“大概两个月”变成可辩护的数字。软件项目任务书里的工期如果只是一个总数,就无法回答“为什么是两个月”,而三点估算加关键路径能给出一条可复算的推导链。
3.1 WBS 分解到一个人五天能做完的颗粒度
教务管理系统的 WBS 建议按交付物分四级:一级是阶段(需求、设计、开发、测试、上线、培训),二级是子系统,三级是模块,四级是任务。任务停在“一个人五天左右能做完”这一层,再往下拆就是排期工具的事,不该出现在任务书里。
编号规则直接决定后面能不能自动化核对,用阶段.模块.任务三段式,例如2.1.3表示第二阶段第一个模块的第三个任务。每一行任务必须带三样东西:可交付物、责任人、验收依据。缺了验收依据的任务行,验收阶段一定扯皮。
3.2 用三点估算把工期写成区间
对每个任务给出乐观值 O、最可能值 M、悲观值 P,期望工期 E = (O + 4M + P) / 6,标准差 σ = (P - O) / 6。E 用来排计划,σ 用来向领导报区间:E ± σ 大约覆盖 68% 的情况,E ± 2σ 覆盖约 95%。
教务类项目里,排课算法和选课并发的 P 值通常远大于 O,因为这两块受教室资源约束和真实并发规模影响,不确定性最大。把它们的三点差值拉开,估出来的数才诚实。
| 编号 | 任务名称 | O | M | P | 期望 E | σ | 前置任务 |
|---|---|---|---|---|---|---|---|
| 1.1 | 基础数据建模 | 4 | 6 | 10 | 6.33 | 1.00 | — |
| 1.2 | 培养方案与开课计划 | 5 | 8 | 14 | 8.50 | 1.50 | 1.1 |
| 2.1 | 排课算法实现 | 12 | 20 | 40 | 22.00 | 4.67 | 1.2 |
| 2.2 | 选课并发与抽签 | 8 | 14 | 28 | 15.33 | 3.33 | 2.1 |
| 3.1 | 考务与成绩录入 | 6 | 10 | 18 | 10.67 | 2.00 | 1.2 |
| 3.2 | 学籍异动与毕业审核 | 6 | 12 | 24 | 13.00 | 3.00 | 3.1 |
| 4.1 | 选课压测与调优 | 3 | 6 | 12 | 6.50 | 1.50 | 2.2 |
| 4.2 | 试运行与培训 | 4 | 7 | 12 | 7.33 | 1.33 | 3.2、4.1 |
| 5.1 | 验收与数据迁移核对 | 3 | 5 | 10 | 5.50 | 1.17 | 4.2 |
3.3 用 Python 算关键路径和总浮动
表格里的数字要能自动算,不然改一次估算就得全部重排。下面这段代码做三件事:算期望工期、拓扑排序后前推最早开始时间、从终点回推关键路径。
# pert_cpm.py —— 三点估算 + 最早开始时间 + 关键路径 import math # 编号: (乐观O, 最可能M, 悲观P, 前置任务列表),单位:人日 tasks = { "1.1 基础数据建模": (4, 6, 10, []), "1.2 培养方案与开课计划": (5, 8, 14, ["1.1 基础数据建模"]), "2.1 排课算法实现": (12, 20, 40, ["1.2 培养方案与开课计划"]), "2.2 选课并发与抽签": (8, 14, 28, ["2.1 排课算法实现"]), "3.1 考务与成绩录入": (6, 10, 18, ["1.2 培养方案与开课计划"]), "3.2 学籍异动与毕业审核": (6, 12, 24, ["3.1 考务与成绩录入"]), "4.1 选课压测与调优": (3, 6, 12, ["2.2 选课并发与抽签"]), "4.2 试运行与培训": (4, 7, 12, ["3.2 学籍异动与毕业审核", "4.1 选课压测与调优"]), "5.1 验收与数据迁移核对": (3, 5, 10, ["4.2 试运行与培训"]), } # 拓扑排序:保证算 ES 时前置任务都已处理 order, done = [], set() while len(order) < len(tasks): for k, v in tasks.items(): if k not in done and all(p in done for p in v[3]): order.append(k) done.add(k) E, SD, ES, EF = {}, {}, {}, {} for k in order: o, m, p, preds = tasks[k] E[k] = round((o + 4 * m + p) / 6, 2) # 期望工期 SD[k] = round((p - o) / 6, 2) # 标准差,用于报区间 ES[k] = max([EF[p] for p in preds] or [0]) EF[k] = round(ES[k] + E[k], 2) duration = max(EF.values()) # 从 EF 最大的任务回推:EF[前置] == ES[当前] 即为关键路径上的紧前任务 path, cur = [], [k for k in order if EF[k] == duration][0] while cur: path.append(cur) preds = [p for p in tasks[cur][3] if EF[p] == ES[cur]] cur = preds[0] if preds else None path.reverse() sigma = math.sqrt(sum(SD[k] ** 2 for k in path)) # 关键路径标准差按方差相加 print("关键路径:", " -> ".join(path)) print(f"期望总工期:{round(duration, 1)} 人日") print(f"±1σ 区间:{round(duration - sigma, 1)} ~ {round(duration + sigma, 1)} 人日") for k in order: print(f"{k:<22} E={E[k]:>6} σ={SD[k]:>5} ES={ES[k]:>6} EF={EF[k]:>6} 浮动={round(duration - EF[k], 1)}")跑出来的结果是关键路径1.1 → 1.2 → 2.1 → 2.2 → 4.1 → 4.2 → 5.1,期望总工期约 71.5 人日,±1σ 区间约 65.1 到 78 人日。另一条3.1 → 3.2的成绩与学籍线,总浮动有二十多个人日——这条结论很关键,它说明成绩模块可以晚两周开工而不影响验收时间,人力可以从这里临时抽去支援选课压测。
参数说明:tasks里的三个数字分别是乐观、最可能、悲观,单位人日;标准差按关键路径上的方差相加再开方,不能直接把各任务 σ 相加,那是常见误用,会把区间报得过宽。实际写任务书时,在总工期之外再加 10% 到 15% 的管理储备,并在“风险与应对”章节里说明储备的释放条件。
3.4 里程碑与验收口径的对应关系
工期算完要落成里程碑,每个里程碑必须挂可验证的证据,不写“完成开发”这种无法判定的话。
| 里程碑 | 时点 | 验收证据 |
|---|---|---|
| M1 需求基线 | 第 2 周末 | 数据流图 0 层与 1 层定稿、需求确认签字页 |
| M2 设计评审 | 第 4 周末 | 库表设计、接口清单、权限矩阵 |
| M3 排课试跑 | 第 9 周末 | 用一个学院真实数据跑通排课并人工抽查 |
| M4 选课压测 | 第 14 周末 | 目标并发下的响应时间与错误率报告 |
| M5 试运行 | 第 17 周末 | 一个学院整学期流程走通 |
| M6 验收 | 第 19 周末 | 数据迁移核对报告、培训签到表 |
4. 任务书文档工程化:doc 模板、自动生成与无法预览 doc 的排查
任务书写完只是开始,真正的麻烦在文档本身。教务单位的公文流转、签章系统、老打印机经常只认 .doc,而在网页端预览时又常常碰到无法预览 doc 的情况。这一章解决“格式合规 + 内容可复现”这两件事。
4.1 为什么教务项目还要交 .doc:兼容性约束与保存路径
.doc 是二进制复合文档格式,.docx 是 OOXML 压缩包。新版 Word 和 WPS 默认保存 .docx,所以“菜单新建里只有 docx 没有 doc”是正常现象,对应到常见的困扰就是菜单新建没有 WPS doc 选项。正确做法是先生成或编辑 .docx,再通过“文件 → 另存为 → Word 97-2003 文档 (*.doc)”导出。
注意:把 .docx 直接改后缀成 .doc 是无效的,文件头没有变,接收方打开时会报格式错误或直接乱码。别省这一步。
批量处理时,在拿到任务书电子版之前先确认文件真实格式:
# 1) 看文件头:D0CF11E0 是 OLE 复合文档(.doc),504B0304 是 ZIP(.docx) head -c 8 任务书.doc | xxd file 任务书.doc # 2) 批量核对后缀与真实格式是否一致,找出被改过后缀的文件 for f in *.doc; do printf "%s\t%s\n" "$f" "$(file -b "$f")"; done # 3) 服务端无 Word 环境时转换格式,供只支持 docx 的预览服务使用 soffice --headless --convert-to docx --outdir out 任务书.dochead -c 8取前 8 字节,xxd转十六进制,这一步能在几秒内定位格式问题。--headless表示不弹图形界面,适合放在服务器或 CI 里跑;--outdir指定输出目录,不加会写到当前目录,容易覆盖同名文件。
4.2 用 python-docx 从结构化数据生成任务书正文
任务书里的 WBS 表、里程碑表都来自前面算出来的结构化数据,手工复制粘贴既慢又容易和估算表对不上。用 python-docx 直接生成,改估算只要重跑一次。
# build_taskbook.py —— 从结构化数据生成任务书 docx,再另存为 doc from docx import Document from docx.shared import Pt, Cm from docx.oxml.ns import qn doc = Document() sec = doc.sections[0] sec.top_margin, sec.bottom_margin = Cm(2.54), Cm(2.54) # 公文常用页边距 doc.add_heading("教务管理系统软件项目计划任务书", level=0) doc.add_paragraph("项目编号:JW-2024-001").alignment = 2 # 2 = 右对齐 doc.add_heading("二、任务分解与工期", level=1) table = doc.add_table(rows=1, cols=4) table.style = "Table Grid" for i, t in enumerate(["编号", "任务名称", "期望工期(人日)", "前置任务"]): table.rows[0].cells[i].text = t for code, name, e, preds in wbs_rows: # wbs_rows 来自上一步的 E 与前置关系 cells = table.add_row().cells cells[0].text, cells[1].text = code, name cells[2].text = f"{e:.2f}" cells[3].text = "、".join(preds) or "—" for para in doc.paragraphs: # 统一字体,避免 WPS 打开时串体 for run in para.runs: run.font.name = "仿宋_GB2312" run.font.size = Pt(12) run._element.rPr.rFonts.set(qn("w:eastAsia"), "仿宋_GB2312") doc.core_properties.title = "教务管理系统软件项目计划任务书" doc.core_properties.version = "V1.3" doc.save("教务管理系统软件项目计划任务书.docx")两个容易踩的点:一是中文字体必须额外设置w:eastAsia,只设font.name在 WPS 里可能不生效;二是table.style = "Table Grid"依赖默认模板,如果生成时报样式不存在,改用doc.add_table(rows, cols)后手动加边框。python-docx 只能写 .docx,不读也不写 .doc,生成后按 4.1 的方式另存。
4.3 无法预览 doc 与「菜单新建没有 WPS doc」的排查顺序
在线预览服务大多只解析 OOXML 压缩包,遇到 OLE 复合结构的 .doc 就直接返回失败,这就是无法预览 doc 的常见成因。排查按下面顺序走,别一上来就换工具:
- 用
file确认真实格式,排除后缀被改过的文件。 - 打开文件本身能否正常?能打开说明文件没坏,问题在预览端。
- 用
soffice --headless --convert-to docx转一份再预览,能过就是预览端不支持 .doc。 - 预览端仍失败,再检查文件是否加密、是否带批注和修订。
至于菜单新建没有 WPS doc,本质是新版 WPS 默认模板只保留 docx,不是软件故障。需要 .doc 就从已有 docx 另存,或准备一份 .doc 模板放进模板目录。
| 转换方式 | 操作 | 保真度 | 适用场景 |
|---|---|---|---|
| WPS / Word 另存为 | 文件 → 另存为 → Word 97-2003 文档 | 高 | 定稿交付、签章 |
| LibreOffice 无头转换 | soffice --headless --convert-to docx | 中 | 服务端批量预览 |
| pandoc | pandoc a.doc -o a.docx | 低,表格边框与页眉易丢 | 只提取正文做文本比对 |
注意:任务书里含师生姓名、学号、成绩等个人信息,不要上传到来路不明的在线转换或文档下载站点处理,也不要用非官方渠道获取的模板文件,转换一律在本地或内网环境完成。
5. 任务书定稿后的三件事:评审清单、变更回写与交付一致性核对
定稿不等于结束。任务书是后续所有变更的基准,基准一旦漂移,工期和范围就同时失控。
评审清单按行打勾,别用“整体评估”这种无法判定的表述:
| 检查项 | 判定标准 |
|---|---|
| 范围边界 | 是否有明确的“不做”清单 |
| 数据流图 | 0 层与 1 层父子平衡,无黑洞无奇迹 |
| 工期 | 三点估算三项齐全,关键路径可复算 |
| 里程碑 | 每个里程碑有可验证的证据,不写“完成开发” |
| 交付物 | 清单里的每一项能在验收环节逐条核对 |
| 变更机制 | 写明变更谁提、谁批、多少工作日回写 |
变更回写靠版本记录表,字段固定为:版本号、变更日期、变更内容、影响工期、申请人、批准人。每次回写必须同步更新任务书正文里的工期数字,绝不能只改记录表不改正文——这是任务书失效最常见的原因。
最后一步是核对任务书与交付物是否一致,用脚本比手工快:
import csv from docx import Document doc = Document("教务管理系统软件项目计划任务书.docx") # 注意:python-docx 读不了 .doc wbs = {} for t in doc.tables: if t.rows[0].cells[0].text.strip() == "编号": for r in t.rows[1:]: wbs[r.cells[0].text.strip()] = r.cells[1].text.strip() with open("deliverables.csv", encoding="utf-8-sig") as f: delivered = {r["任务编号"] for r in csv.DictReader(f)} for code, name in wbs.items(): if code not in delivered: print(f"未交付:{code} {name}")这段脚本只做单向核对,任务书里有、交付清单里没有的会被打出来。encoding="utf-8-sig"用来吃掉 Excel 导出 CSV 的 BOM 头,不加第一列字段名会带一个不可见字符,导致匹配全部失败。如果交付清单里多出任务书没有的编号,那是范围蔓延的信号,先查变更记录表再动代码。
把版本号写进文档属性并留痕,是成本最低的追溯手段。定稿后执行doc.core_properties.version = "V1.3",再把文件哈希一并抄进邮件正文:
sha256sum 教务管理系统软件项目计划任务书.doc收件方对同一份文件重算哈希,值一致就说明传输过程没有损坏也没有被替换。绑定在文档属性里的版本号加哈希值,让“我们按哪一版任务书验收”这个问题有了唯一答案。
本文还有配套的精品资源,点击获取