软件工程第二次结对作业——校园失物招领程序实现
软件工程第二次结对作业——校园失物招领程序实现
| 项目 | 内容 |
|---|---|
| 课程 | 202601 软件工程 |
| 作业要求 | 第二次结对作业:程序实现 |
| 作业目标 | 基于上次原型,实现发布、浏览、搜索、详情、联系、本人编辑与状态更新的完整流程 |
| 结对成员 | 102400412 龚巍斌 / 102401111 郑瀚 |
| 结对同学博客 | https://www.cnblogs.com/Gwbin/p/23243043 |
| GitHub 仓库 | https://github.com/illnessemc/102400412-102401111 |
上次作业中,我们用墨刀设计了寻物、招领、搜索、详情和"我的发布"等页面。本次选择 Web 实现,将原型中的点击路径变成真正可保存、可查询、可更新的数据流程。项目以仓库根目录的 index.html 为正式入口;html/ 保存的是上次作业的原型页面。
一、具体分工
- 队友(102400412 龚巍斌):负责基础框架搭建(路由守卫、首页渲染、搜索、详情页基础展示、演示数据初始化、32个基础单元测试编写)。
- 本人(102401111 郑瀚):负责发布模块(表单校验、防连点)、我的发布模块(统计/筛选/返回路径)、状态机管理(标记已找到/已归还/删除、ownerId权限校验)、演示数据与真实数据合并隔离、修复重复弹窗/路由拦截等bug、补充白盒测试用例及博客撰写。
二、PSP 表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 40 |
| Estimate | 估计这个任务需要多少时间 | 30 | 40 |
| Development | 开发 | 300 | 380 |
| Analysis | 需求分析(包括学习新技术) | 40 | 50 |
| Design Spec | 生成设计文档 | 30 | 30 |
| Design Review | 设计复审 | 20 | 20 |
| Coding Standard | 代码规范 | 10 | 10 |
| Design | 具体设计 | 40 | 50 |
| Coding | 具体编码 | 120 | 160 |
| Code Review | 代码复审 | 20 | 30 |
| Test | 测试(自我测试,修改代码,提交修改) | 20 | 30 |
| Reporting | 报告 | 60 | 80 |
| Test Report | 测试报告 | 30 | 40 |
| Size Measurement | 计算工作量 | 10 | 10 |
| Postmortem & Process Improvement Plan | 事后总结,并提出过程改进计划 | 20 | 30 |
| 合计 | 390 | 500 |
三、解题思路描述与设计实现说明
1. 代码实现思路(文字描述)
项目采用纯前端 Web 实现(HTML+CSS+原生JS),无后端依赖。数据层使用 localStorage 模拟持久化存储。
核心流程:用户进入根目录 index.html → 路由分发(哈希路由) → 首页集中浏览/搜索 → 点击详情查看联系方式 → 发布者可在"我的发布"中标记状态或删除。
状态机设计:记录存在"进行中"、"已找到/已归还"(已结束)状态,已结束记录不可再更新状态,非所有者(无 ownerId 或匹配失败)不可删除/修改。
2. 关键实现的流程图或数据流图

3. 重要代码片段及解释
javascript
// 防连点锁与表单提交核心逻辑(publish.js)
let isSubmitting = false;
function handleSubmit(e) {
e.preventDefault();
if (isSubmitting) return; // 防连点
const data = getFormData();
if (!validate(data)) return showToast('请填写必填项');
isSubmitting = true;
savePost(data);
showToast('发布成功');
setTimeout(() => { location.hash = '#success'; isSubmitting = false; }, 800);
}
解释:通过 isSubmitting 标志位阻断快速连续点击;校验通过后写入本地存储,并延迟跳转防止重复提交。
javascript
// 演示数据与真实数据合并隔离(posts.js)
function loadPosts() {
const real = JSON.parse(localStorage.getItem('posts') || '[]');
const demo = DEMO_DATA.map(p => ({...p, _demo: true}));
return [...demo, ...real];
}
解释:演示数据打上 _demo 标记无 ownerId,真实发布数据带 ownerId,合并渲染且互不影响,防止覆盖。
四、附加特点设计与展示
1. 创意独到之处及意义
- 防连点与局部DOM更新:解决快速连点产生脏数据及
innerHTML重绘导致的事件丢失/重复弹窗问题,提升稳定性。 - 状态机与权限校验:前端模拟严谨的状态流转(已结束不可改)和越权拦截(非所有者不可删),贴近真实业务。
- 演示数据隔离:保证测试人员刷新/发布新数据后,初始展示依然完整。
2. 实现思路
详情页返回路径动态计算(backRoute 参数),从"我的发布"进详情则返回"我的发布";路由守卫增加白名单放行新页面。
3. 重要代码片段
javascript
// 动态返回路径与状态修改校验(detail.js / my.js)
function canModify(post) {
return post.ownerId === CURRENT_USER && post.status === '进行中';
}
解释:严格校验身份与状态,保障数据安全。
4. 实现成果展示


五、目录说明和使用说明
1. 目录组织
├── .github/workflows/ # CI 自动化测试配置
├── README.md # 项目说明与最终指南
├── 测试说明.md # 测试相关说明文档
├── index.html # 正式入口页面
├── css/ # 样式文件
│ └── style.css
├── js/ # 核心逻辑
│ ├── app.js # 路由
│ ├── posts.js # 数据层
│ ├── publish.js # 发布
│ ├── my.js # 我的发布
│ └── detail.js # 详情
├── html/ # 上次原型页面
│ └── (原型资产)
├── resource/ # 素材与截图
├── tests/ # 单元测试
│ └── (测试文件)
├── posts.js # 数据核心文件(根目录)
├── server.cjs # 本地服务/测试入口
└── .gitignore # Git 忽略配置
2. 测试人员运行说明
- 克隆/下载仓库到本地。
- 确保文件结构完整。
- 使用谷歌浏览器(Chrome)直接双击打开根目录
index.html即可运行。 - 无需配置环境,数据自动存于浏览器
localStorage。 - 运行单元测试:终端执行
node --test tests/posts.test.cjs(需 Node.js 24+)。
六、单元测试
1. 测试工具与简易教程
使用 Node.js 内置 node:test 和 node:assert/strict。学习途径:查阅 Node 官方文档及廖雪峰 JS 测试教程。
简易教程:编写测试文件 xxx.test.cjs,引入 test 和 assert,运行 node --test 即可输出结果。
2. 构造测试数据思路
- 正常数据:合法字段与正常状态流转。
- 边界数据:空表单、超长文本、特殊字符。
- 异常数据:损坏JSON、存储满、非所有者ID。
- 刁难场景:快速连点、刷新持久化、越权修改已结束记录。
3. 测试用例表(≥10个)
| 用例 | 测试内容 | 预期结果 |
|---|---|---|
| 发布缺参校验 | 提交空表单 | 返回错误,不写入 |
| 状态机非法流转 | 已结束记录再标记 | 拒绝 |
| 非所有者越权 | 不同ownerId删除 | 拒绝 |
| 防连点锁 | 快速连续点击提交 | 只产生一条 |
| 存储失败回退 | 模拟存储满 | 保存失败不跳页 |
| 演示数据隔离 | 验证 _demo 标记 |
无ownerId不被误删 |
| 路由白名单 | 访问 #my |
不回退首页 |
| 返回路径参数 | 从my进详情 | 返回my |
| 统计数正确 | 多条进行中/已结束 | 统计匹配 |
| 筛选联动 | 删除后筛选 | 状态重置正确 |
七、GitHub 代码签入记录


八、遇到的代码模块异常/结对困难及解决方法
| 问题描述 | 尝试过的方法 | 是否解决 | 收获 |
|---|---|---|---|
| 路由强制回退#home | 加入白名单 publish/success/my | 是 | 理解路由守卫机制 |
| 发布按钮无响应 | 移除默认disabled,改JS控制 | 是 | 排查DOM属性影响 |
| 演示数据被覆盖 | 合并逻辑+_demo标记 | 是 | 数据层隔离策略 |
| 删除后重复弹窗 | 局部DOM更新+防连点锁 | 是 | 局部更新优于重绘 |
| 协作流程不熟 | 网页端ZIP/Upload | 是 | 掌握基础Git协作 |
九、心得体会
本次结对编程让我从"产品经理视角"真正切换到"程序员视角"。上次作业画原型时觉得流程很简单,真正写代码才发现,一个"标记已找到"的按钮背后涉及状态机校验、权限判断、DOM局部刷新、事件绑定防丢失等一系列问题。尤其是修复重复弹窗的bug,最初用 innerHTML 整体重绘导致事件丢失,后来改为局部更新才彻底解决,这个教训让我深刻理解了前端渲染机制。
另外,在队友已有代码基础上做增量开发,比从零开始更难——你需要先读懂别人的路由逻辑、数据结构和命名习惯,才能在不破坏原有功能的前提下接入新模块。这锻炼了我的代码阅读能力和协作意识。
PSP 记录也让我意识到,实际编码时间比预估短,但调试和修复bug(尤其是路由拦截、存储隔离这类隐性问题)占了大量时间。下次应该给"排查异常"预留更多buffer。
总的来说,这次作业让我对前端工程化、状态管理、单元测试设计有了更实际的理解,也体会到好的代码规范和清晰的交接文档对团队协作有多重要。

浙公网安备 33010602011771号