2026秋软件工程第二次结对作业——校园失物招领程序实现
2026秋软件工程第二次结对作业——校园失物招领程序实现
- 本文作者:072308214 周兴宇
- 结对成员:102302152 戴锴斌、072308214 周兴宇
- 本人博客:主页
- 队友博客:主页
- 本次作业博客:校园失物招领程序实现(本文)
- GitHub项目:102302152-072308214
- 课程作业要求
- 上次原型作业
一、作业回顾与从原型到程序
回顾
上次作业完成了“寻回”校园失物招领的 Figma 原型,这次把首页、发布、搜索、详情和“我的发布”变成能实际使用的网页。范围仍然是发布寻物与招领、按名称查找、查看联系方式,以及交接后的状态更新。没有增加地图、实名认证和站内聊天。
1.容易忽略的问题:
有“已找回”按钮,不代表结束流程已经完整。因此本次明确了操作入口和结果:发布者从“我的发布”进入详情,确认结束;取消不写入数据;确认后详情显示“已找回”或“已归还”,默认首页隐藏该记录,“我的发布”仍然保留。其他使用共享服务的浏览器刷新后也能看到新状态。
2.方法模式
我们本次实现采用 HTML、CSS、原生 JavaScript。助教完整下载、解压后,用 Chrome 打开 index.html 即可使用。考虑到浏览器本地数据不能在同学之间共享,还提供了 Python 标准库和 SQLite 实现的共享服务,两种运行方式使用相同的页面。
| 环节 | 具体处理 |
|---|---|
| 输入 | 名称、地点、联系方式去掉首尾空白;检查必填、长度和发生时间 |
| 查找 | 按物品名称包含匹配;可叠加类型、类别、地点筛选 |
| 保存 | 本地版写入 localStorage,共享版由服务端写入 SQLite |
| 权限 | 浏览器持有随机管理凭证;共享服务核对凭证哈希后才修改 |
| 展示 | 用户填写的内容用 textContent 显示,避免被当成 HTML 执行 |
| 异常 | 保存失败保留输入并显示错误,不跳转到成功页 |
二、分工与协作记录
职责分配:
戴锴斌负责主仓库、实现材料和交付整理,程序开发与测试使用工具辅助。
周兴宇通过自己的 GitHub 账户 fork 仓库并提交测试计划,在后续提交中补充了两处验收条件
三、PSP 与过程记录
| PSP2.1 | 阶段 | 预估(分钟) | 实际(分钟) |
|---|---|---|---|
| Planning | 计划 | 10 | 8 |
| Estimate | 估计任务时间 | 10 | 8 |
| Development | 开发 | 195 | 220 |
| Analysis | 需求分析与技术选择 | 15 | 20 |
| Design Spec | 设计文档 | 15 | 10 |
| Design Review | 设计复审 | 5 | 5 |
| Coding Standard | 代码规范 | 5 | 5 |
| Design | 具体设计 | 10 | 10 |
| Coding | 编码 | 90 | 80 |
| Code Review | 代码复审 | 15 | 15 |
| Test | 测试、修复与回归 | 40 | 30 |
| Reporting | 报告 | 30 | 30 |
| Test Report | 测试报告 | 15 | 20 |
| Size Measurement | 工作量统计 | 5 | 5 |
| Postmortem & Process Improvement Plan | 总结与改进 | 10 | 5 |
| 合计 | 汇总行不重复累加 | 235 | 411 |
四、关键流程与代码
1. 发布信息

2. 查找、联系与结束

function close(item, ownerId) {
if (!ownerId || item.ownerId !== ownerId)
throw new Error('只有发布者可以修改状态');
if (item.status !== 'active')
throw new Error('这条信息已经结束');
return {...item, status: 'closed'};
}
3. 数据流

另外:
页面通过统一的 list/create/close 方法访问数据。直接打开 HTML 时选择本地存储,通过项目服务访问时选择 HTTP 接口。接口失败不会悄悄切回本地存储,否则用户可能以为发布成功,实际上其他人看不到。
管理权限绑定浏览器,并不等于实名认证。换浏览器、清除数据或丢失管理凭证后,无法再管理原记录,本版没有找回凭证功能。本地模式只适合单机演示;跨浏览器查看同一批数据需要运行共享服务。
五、两个附加特点
1. 类别与地点组合筛选
只搜索“校园卡”可能得到很多相似结果,因此增加类别和地点筛选。比如选择“校园卡”,再输入“图书馆”,列表只保留同时符合条件的记录。筛选条件之间使用 AND,避免选得越多结果反而越多。下面是 query 中的条件片段:
(!filters.category || p.category === filters.category) &&
(!filters.place || p.place.includes(filters.place.trim())) &&
(!keyword || p.name.toLocaleLowerCase().includes(keyword))
其中 keyword 业已去除首尾空白并转为小写;显式搜索时全空白会提示输入关键词。默认不展示已结束信息,也可以勾选“包含已结束”查看历史结果。

2. 复制联系方式
小注释:详情页增加复制按钮,减少在页面与微信等应用之间切换时重新输入的操作。不能一点击就提示成功,而要等剪贴板写入完成:
await navigator.clipboard.writeText(p.contact);
toast('联系方式已复制');

六、目录与运行说明
index.html 页面入口
assets/
app.js 路由、表单、列表及详情交互
core.js 输入校验、查询、状态规则
store.js 本地存储与 HTTP 适配器
styles.css 响应式样式
favicon.svg 图标
server.py 可选共享服务与 SQLite 存储
package.json 前端测试命令
tests/
core.test.js 业务规则测试
store.test.js 存储与异常处理测试
ui-state.test.js 异步请求顺序回归测试
test_server.py HTTP 与数据库测试
docs/ 实现、测试、过程记录及截图
测试计划.txt 通过 PR 修改的验收计划
data/ 运行后自动生成,不提交仓库
验收方法与安装环境
最方便的验收方法:在仓库选择 Code → Download ZIP,完整解压,用普通 Chrome 窗口打开 index.html。
共享模式:安装 Python 3.9 或以上,在项目目录执行:
python3 server.py
然后用 Chrome 访问 http://127.0.0.1:8000。Windows 可用 py -3 server.py。首次列表为空,发布后存入 data/campus.sqlite,重启服务仍保留。普通窗口发布、无痕窗口查看,可验证非发布者没有结束权限。端口被占用时用 python3 server.py --port 8765。请使用项目自带服务,普通静态网页服务器没有所需接口。

七、单元测试与复现方法
1. 工具及简易教程
前端使用 Node 自带的 node:test 和 node:assert/strict,服务端使用 Python 标准库 unittest。业务规则从界面中分离后,不必每次点页面就能验证输入、筛选和状态变化。测试方法从一个有效输入开始,先断言正常结果,再每次只改变一个条件,检查拒绝分支。
安装 Node.js 18 或以上,在项目目录运行:
npm test
python3 -m unittest discover -s tests -v
前者出现 fail 0,后者末尾为 OK,代表这次运行通过。若要添加测试,在相应测试文件中新增 test('描述预期行为', () => {...}),准备数据,调用函数,再用断言检查结果。失败后先看是哪一项预期不符,修改后重跑全部测试,防止修好一处又破坏另一处。运行网页本身不需要 Node。
2. 测试代码示例
下面摘自对 close 函数的测试:
const p = {...posts[0], ownerId: 'owner'};
assert.throws(() => C.close(p, 'other'));
const out = C.close(p, 'owner');
assert.equal(out.status, 'closed');
assert.equal(p.status, 'active');
assert.throws(() => C.close(out, 'owner'));
这段同时检查拒绝其他人修改、本人可以修改、原对象不被改变和不能重复结束。仅测纯函数还不够,Python 测试会对实际启动的临时 HTTP 服务发请求,检查非发布者直接请求接口也会得到403。
3. 数据设计与结果
按照白盒思路检查条件分支,再使用等价类、边界值和异常输入补充。首版实际运行通过 27项前端测试、13项服务端测试,共40项。原始输出在仓库 docs/evidence,不是两位同学独立试用的记录。
| 检查方向 | 测试数据及预期 |
|---|---|
| 必填分支 | 名称、地点、联系方式为全空白,拒绝发布 |
| 长度边界 | 名称40字通过、41字拒绝,另检查 emoji 计数 |
| 时间校验 | 未来时间、2月30日、异常时区和24点规范化输入被拒绝 |
| 输入类型 | 未知类别、非法类型和无效 Unicode 字符被拒绝 |
| 搜索分支 | 前后空白、全空白、大小写、部分匹配及无结果 |
| 组合筛选 | 类别、地点、类型同时约束,默认排除已结束 |
| 状态权限 | 本人、其他人、不存在的记录、已结束记录分别处理 |
| 持久化 | 新建存储对象重新读取,服务重启后数据仍在 |
| 存储异常 | JSON损坏、结构错误、配额不足,不能误报保存成功 |
| 接口异常 | 网络失败、请求格式错误、超大请求、跨站修改请求 |
| 并发顺序 | 旧列表请求后返回时,不覆盖新数据 |
| 公开内容 | 响应不包含管理凭证,静态服务不暴露数据库 |
测试环境是 macOS 26.6.2、Chrome 155.0.8059.40、Node.js 25.1.0、Python 3.9.6。Chrome 中还实际检查了发布招领、空白搜索、零结果、取消结束后刷新不变、确认归还后刷新保持,以及另一浏览器能查看共享记录但不能结束它。390px 窄屏补查没有横向溢出。
这些测试覆盖了核心流程和已发现的异常,但没有验证长期运行、大量并发或真实校园使用效果,因此不能据此声称寻物时间缩短了多少。
八、提交记录与实际问题

提交按设计与估算、业务规则、本地存储、共享服务、页面交互、修复和文档逐步进行,截图之后的兼容性修复可在完整提交记录中查看。
| 问题 | 定位与处理 | 结果及收获 |
|---|---|---|
| 较早发出的请求较晚返回,覆盖新列表数据 | 用两个可控制完成顺序的 Promise 复现;将请求序号检查移到写入状态之前 | 已修复并加入回归测试,不能只检查当次画面是否正确 |
| Python 接受异常时区偏移,前后端判断不一致 | 加入 +00:99 输入检查,显式限制偏移范围 |
接口返回400,两端不能只依赖各自日期库的默认行为 |
| 孤立 Unicode 代理字符写入数据库时报错 | 在前后端增加无效字符检查,保留异常样本 | 已修复,能通过JSON解析不代表可以正常保存 |
| Node 18 的全局 Web Crypto 可用性与开发环境不同 | Node 端显式使用内置 node:crypto.webcrypto,在没有全局 crypto 的隔离上下文验证 |
已修复;未声称在未安装的版本上实际完整跑过程序 |
协作上的一个具体不足是第一轮 PR 以测试计划为主,尚未形成队友独立运行测试代码后的结果。因此后续操作包已安排补充“我的发布”筛选和时间边界两个用例,并要求提交实际运行输出,便于下一轮共同验收。
九、个人总结与队友评价
本次实现需要特别关注权限校验与操作可追溯性。日志落库之前,不能仅依赖前端提示成功;删除操作也应该和新增操作一样完整记录。对我来说,下一步需要能独立说清楚软删除和物理删除的区别,以及为什么客户端已经拦截了非法请求,服务端仍然必须再校验一次。李婉婷在测试方案里补充了并发提交和重复点击的场景验证,这种把边界条件提前列清楚的做法值得借鉴。协作还可以优化的地方是尽早同步真实接口联调结果与异常日志,避免只停留在方案讨论、缺少统一验收依据的情况。

浙公网安备 33010602011771号