🚀 Nova的软件工程冒险:从原型到代码,让散落星辰真正回家
📚 第一关:任务加载,双人小队再次出发
结对成员:102402102 张佩琪、102401403 潘岳
| 项目 | 内容 |
|---|---|
| 课程 | 软件工程与软件工程实践 |
| 本次作业要求 | 校园失物招领核心功能实现 |
| 本篇作业博客 | 🚀 Nova的软件工程冒险:从原型到代码,让散落星辰真正回家 |
| 结对同学博客 | 软件工程第二次结对作业:从原型到代码,让散落星辰真正回家 |
| GitHub主仓库 | Starry-Nova/102402102-102401403 |
| 潘岳的Fork | pyyy123/102402102-102401403 |
| 上次原型设计 | 双人组队,让遗失的星星回家 |
| 呈现方式 | PC端Web,使用Google Chrome运行 |
🧑🚀 Nova × 小面包,再次组队!
上一次,我们画出了星星回家的地图;这一次,要让按钮真的有回应,让信息真的能更新。
Pick the lost stars, send roses in return.
拾起散落星辰,归还赠以玫瑰。
“拾星”关注的是校园失物信息分散、群聊消息容易被覆盖的问题。本次把原型中的设计落到代码:发布寻物与招领信息,浏览和搜索,查看详情与联系方式,最后由发布者确认找回或归还。
大家好,我是Nova。🌟
上一次,我和小面包在墨刀里给星星安排好了回家的路线。这一次,老师把任务从“画出页面”升级成了“写出能运行的程序”。于是,页面上的发布按钮不能只负责好看,玫瑰不能只是装饰,已找回也不能只是一句祝福——它们都得有数据、有判断、有后续。
本来以为给原型接上几段代码就可以通关,后来才发现,背包里还藏着状态管理、测试、Fork、PR和合并冲突。星星还没全部回家,Git先给我们出了几道题。🙂
我们没有把画出地图等同于实现真实定位,也不把模拟认证称为真实身份验证,课程要求的核心流程优先。
【首页全景】

🤝 第二关:分配装备,各自负责也互相检查
本次分工围绕视觉与浏览、业务与测试两条线展开。下表与提交方向对应,具体实现、学习和复核范围仍由两人按实际操作确认。
| 成员 | 主要负责范围 | 协作检查 |
|---|---|---|
| 张佩琪 / Nova | 浅色星空视觉、北斗导航、物品卡片、浏览搜索、详情展示、最终说明文档 | 检查潘岳提交的业务流程,记录问题并复查 |
| 潘岳 / 小面包 | 模拟账号、发布校验、本地存储、模拟认证、联系方式、交接状态、玫瑰与测试 | 通过Fork和PR提交,检查视觉及文档能否复现 |
实际仓库里也留下了互相检查的记录:导航阶段由小面包提出修改意见,我提交修复;业务阶段由小面包从Fork提交,我审阅;最终文档再交给小面包按说明检查。这不是两份代码最后放进同一个文件夹,而是交接时需要让对方看懂、用起来并提出问题。
⏱️ 第三关:PSP计时——星光不能代替时间记录
| PSP2.1 | Personal Software Process Stages | 预估(人分钟) | 实际(人分钟) |
|---|---|---|---|
| Planning | 计划(小计) | 30 | 40 |
| Estimate | 估计任务所需时间 | 30 | 40 |
| Development | 开发(小计) | 890 | 1100 |
| Analysis | 需求分析,包括学习新技术 | 90 | 100 |
| Design Spec | 生成设计文档 | 60 | 70 |
| Design Review | 设计复审 | 30 | 40 |
| Coding Standard | 制定代码规范 | 20 | 20 |
| Design | 具体设计 | 120 | 150 |
| Coding | 具体编码 | 360 | 420 |
| Code Review | 代码复审 | 90 | 120 |
| Test | 自测、修复与回归测试 | 120 | 180 |
| Reporting | 报告(小计) | 140 | 160 |
| Test Report | 测试报告 | 60 | 70 |
| Size Measurement | 计算工作量 | 20 | 20 |
| Postmortem & Process Improvement Plan | 总结及过程改进计划 | 60 | 70 |
| 合计 | 三个阶段小计相加,不重复计入子项 | 1060 | 1300 |
导航调整与合并冲突都提醒我们,计划不能只考虑编码,还应为互相检查、修复和复查留出时间。两人分别在各自电脑上操作时,也需要安排同步代码与确认版本的时间。
🧭 第四关:接通星路,让核心流程走到底
4.1 先守住任务范围
| 核心需求 | 当前设计对应 |
|---|---|
| 发布寻物、招领 | “我丢失了 / 我捡到了”及发布表单 |
| 浏览与搜索 | 首页便签、关键词与组合筛选 |
| 查看详情 | 物品描述、时间、地点、状态及发布者信息 |
| 联系发布者 | 查看联系方式及复制;不做站内聊天 |
| 更新状态 | 发布者确认交接,寻找中→已找回,待认领→已归还 |
项目使用HTML、CSS和原生JavaScript。index.html提供入口,styles.css管理样式,app.js负责页面渲染和交互。物品、账号及玫瑰等数据通过浏览器本地存储保存,无需部署服务器即可演示。
整体是单页式交互:go()切换页面,render()根据当前页面和选中物品生成内容;点击和表单事件更新状态,再重新渲染。浏览器通过普通脚本加载本地文件,避免把运行网页变成安装依赖的任务。
4.2 关键流程图
【核心流程图】

【数据流图】

4.3 关键代码:多个筛选条件如何一起生效?
以下取自本地交付版filtered(),仅调整格式;发布前核对与最终仓库版本一致。
function filtered() {
return items.filter(x =>
(!filter.keyword || `${x.title} ${x.description}`.toLowerCase()
.includes(filter.keyword.toLowerCase().trim())) &&
(!filter.category || x.category === filter.category) &&
(!filter.area || x.area === filter.area) &&
(!filter.date || x.date >= filter.date) &&
(!filter.status || x.status === filter.status)
);
}
每个条件都采用“没有选择就不限制,选择后必须匹配”的逻辑,条件之间通过&&连接。关键词匹配名称和描述;日期作为起始日期,在数据遵循YYYY-MM-DD格式时可以比较先后。这样“耳机+电子产品+图书馆”不是三个独立按钮,而是在同一批数据上共同缩小范围。
4.4 关键代码:不是谁都能替别人结束信息
交接确认处理中的核心片段如下,省略了弹窗、交接对象校验和保存后的渲染部分。
function isOwner(item) {
return item.publisher === activeAccount;
}
// confirm-finish 事件处理内
const x = current();
if (!isOwner(x) || !['seeking', 'claimed'].includes(x.status)) return;
// 校验并记录交接对象后更新状态
x.status = x.type === 'lost' ? 'found' : 'returned';
这里检查“当前账号是否为发布者”和“物品是否仍未完成”,既在页面入口做提示,也在事件处理中做检查。课程演示中的前端限制不能替代真实服务端授权,但可以把“谁修改、从哪里修改、修改后变什么”讲清楚。
【测试发布】

🌌 第五关:附加装备——星光负责指路,玫瑰负责感谢
5.1 多条件筛选与一键复制
意义:当物品变多时,用户不必逐条翻找;核对后可以复制联系方式,减少手动抄写。关键词搜索属于核心要求,多条件筛选和复制属于便捷性扩展。
实现:筛选复用filtered();复制调用浏览器剪贴板接口,失败时提供手动复制提示。这里的重点不只是“复制成功”,也要给权限被拒绝的用户一个出口。
【带筛选条件的结果页】

5.2 四色星星与北斗导航
蓝星寻找中、紫星待认领、黄星已找回、亮橙星已归还,颜色与文字同时出现,避免只靠颜色区分状态。完成状态便签保持静止,未完成便签保留轻微悬停效果。
校园图只作为地点示意与手动选点,不能称作真实地图定位功能。
【四色星星与校园星图】

页面底部的七星导航保留普通页面名,天枢也叫“星图首页”,天权也叫“登记星启”,避免让找物品变成星座考试。星点与连线共用坐标,星名和页面名组合展示。
【页面底部七星导航】

5.3 赠人玫瑰,把感谢送给正确的人
意义:交接后增加自愿致谢,而不是把送花变成完成流程的强制任务。可以跳过,每次交接最多一朵。
实现:交接时记录拾主和失主;赠送时保存颜色、时间、赠送者和感谢语;星册只展示当前账号收到的玫瑰。颜色从六种颜色中随机产生一次,刷新时读保存结果,不重新随机。
function canThank(x) {
return ['found', 'returned'].includes(x.status)
&& !x.rose
&& x.finder !== activeAccount
&& accountNames.includes(x.finder)
&& (recipientOf(x) === activeAccount
|| (isOwner(x) && x.type === 'lost'));
}
这个判断依次检查:已经完成交接、还没送过、不是自己收礼、拾主账号有效,以及当前账号确实是有资格致谢的失主。只画一朵玫瑰不难,难点是不能送错人,也不能重复送。
【赠送页面】

🗂️ 第六关:打开背包,助教怎样运行?
项目根目录/
├── index.html # Chrome打开的入口
├── styles.css # 主题、卡片和导航布局
├── app.js # 页面渲染、状态与业务事件
├── assets/ # 图片、头像、SVG装饰
├── tests/
│ └── pages.test.js # 当前网页的自动化测试
├── package.json # 测试命令配置
└── README.md # 目录和操作说明
- 在GitHub主仓库选择
main,点击Code → Download ZIP。 - 完整解压,用Google Chrome打开根目录
index.html。 - 无需安装Node.js即可运行网页;不要单独移动HTML而丢掉脚本、样式和图片。
- 顶部切换Nova、小面包、星星同学;绑定使用虚构手机号和演示验证码
123456。 - 认证使用虚构资料,结果页演示审核状态;测试时不要填写真实证件。
项目数据仅保存到当前浏览器,不跨设备同步;清除浏览器数据可能丢失记录。这里的认证、短信和定位均为演示,不提供真正的身份保障。
【最终仓库目录与README运行说明】

🧪 第七关:给星路设置检查点——单元测试
7.1 工具选择与学习路径
项目采用Node.js内置node:test及node:assert/strict,无需额外安装测试框架。测试通过node:vm加载真实app.js,用轻量DOM和本地存储替身模拟页面环境。
7.2 一份可以照着做的简易教程
- 安装Node.js 24.x LTS,重新打开终端,用
node -v确认可用。 - 在包含
package.json和tests的项目根目录打开终端。 - 理解测试的三步:准备输入→调用被测行为→用断言比较结果。
- 执行:
node --test
- 查看
tests / pass / fail。有失败时先读测试名称与报错位置,复现并修复,再执行全套测试。
7.3 项目测试代码
下面是现有测试中的组合筛选用例,依赖同文件的setup()环境,不能脱离辅助函数单独复制执行。
test('search combines keyword, area, category and date', () => {
const t = setup();
assert.equal(t.run(`
filter = {keyword:'耳机', area:'图书馆',
category:'电子产品', date:'2026-10-03'};
filtered().length
`), 1);
assert.equal(t.run(`filter.date='2026-10-04';filtered().length`), 0);
});
它测试filtered():第一组条件匹配示例耳机;把起始日期推进一天后,不再匹配。不是只检查函数能运行,而是比较返回数量是否符合预期。
玫瑰资格测试直接覆盖canThank()的正常与拒绝分支:
test('rose requires completed handover and recipient; duplicate is blocked', () => {
const t = setup();
assert.equal(t.run('canThank(items[0])'), false);
assert.equal(t.run('canThank(items[2])'), true);
assert.equal(t.run("items[2].rose={message:'谢谢'};canThank(items[2])"), false);
});
7.4 用例设计:不能只让程序走顺路
以下对应本地交付版中已有的测试,当前文件共23项,下面列出其中15项。
| 编号 | 被测行为 | 构造数据/情形 | 预期 |
|---|---|---|---|
| T01 | 页面渲染 | 依次进入12个路由 | 不抛异常,有内容 |
| T02 | esc及详情 | 标题包含HTML标签与事件文本 | 转义,不直接插入该标签 |
| T03 | 详情认证分支 | 未认证与approved分别渲染 | 未认证隐藏号码,通过后显示 |
| T04 | filtered | 关键词+类别+地点+日期 | 正确匹配,日期越界后为空 |
| T05 | canThank | 未完成交接 | 不允许赠送 |
| T06 | canThank | 完成交接且尚未送花 | 有资格账号可赠送 |
| T07 | canThank | 已有rose记录 | 拒绝重复赠送 |
| T08 | 发布处理 | 空表单 | 错误提示,物品数量不变 |
| T09 | 发布绑定分支 | 合法草稿、账号未绑定 | 跳转绑定,草稿标题保留 |
| T10 | 认证结果 | pending、approved、rejected | 显示对应状态 |
| T11 | makeRose | 固定随机源到六个区间 | 六种颜色可覆盖,时间可解析 |
| T12 | 玫瑰保存 | 保存后重渲染并读回 | 颜色、时间与感谢语一致 |
| T13 | 账号隔离 | 三账号轮换 | 绑定、认证与所属物品独立 |
| T14 | confirm-finish | 非发布者直接触发事件 | 状态不改变 |
| T15 | 核心事件流程 | 发布→搜索→详情→认证→联系→完成 | 新物品可找到,最后转为found |
白盒设计从条件分支出发:发布校验的失败与成功路径、认证状态分支、所有者与非所有者、完成与未完成、送过与未送过都要考虑。随机颜色用固定随机源分别命中六个分支,而不是靠反复随机碰运气。
7.5 实际结果
| 本次测试版本SHA(本人提供) | Node.js版本 | 总数 | 通过 | 失败 |
|---|---|---|---|---|
| 6f5a605 | v24.21.0 | 23 | 23 | 0 |
在Windows PowerShell中执行node --test,本次23项测试全部通过,无失败、取消或跳过的用例,运行耗时约129.85毫秒。
【已提供的23项测试通过截图和Node.js v24.21.0版本截图】

🌿 第八关:GitHub组队轨迹,不只是一键上传
我维护主仓库,小面包通过自己的Fork创建功能分支并提交PR,另一位审阅。解决问题后再合并,下一阶段从同步后的main开始。
截至本次核查,仓库PR列表显示9个PR已合并。项目按10个功能阶段整理,初始化直接提交。
| 阶段 | 功能范围 | 真实提交或PR |
|---|---|---|
| 01 | 项目骨架 | 张佩琪提交4b074c5 |
| 02 | 浅色星空布局与北斗导航 | 张佩琪PR #1;包含布局与导航修复两次提交 |
| 03 | 校园图与物品便签 | 张佩琪PR #2 |
| 04 | 搜索和筛选 | 张佩琪PR #3 |
| 05 | 详情展示 | 张佩琪PR #4 |
| 06 | 模拟账号与发布校验 | 潘岳从Fork提交PR #5 |
| 07 | 本地存储、绑定与模拟认证 | 潘岳从Fork提交PR #6 |
| 08 | 联系方式与交接状态 | 潘岳从Fork提交PR #7,处理冲突后合并 |
| 09 | 玫瑰感谢与测试 | 潘岳从Fork提交PR #8 |
| 10 | README交付说明 | 张佩琪PR #9,潘岳审阅 |
在提交历史中,能看到项目初始化、功能提交、修复和合并记录
【主仓库commits记录】

【潘岳Fork页面】

最能说明协作的一段是PR #1:小面包先指出星点与连线顶点偏移、标签分离;随后我提交d0e6c2d修复;她再检查七个入口并批准,最后合并。比“已阅,同意”更有价值的是,审阅确实推动了一次修改。
【PR #1】

最后的PR #9则把检查对象从代码变成了文档:我提交运行、测试和限制说明,小面包记录按README下载运行后的审阅意见。软件交付不仅要让作者会用,也要让第一次拿到文件的人知道从哪里开始。
提交说明应描述实际改动,例如fix: align dipper stars and group navigation labels,比“修改了一下”更容易回看。功能完成后检查并及时提交。
🛠️ 第九关:Boss战——星点要对齐,分支也要同步
9.1 星星没有站在连线交点上
- 问题: 北斗导航星点与连线错位,星名和页面名分离,部分文字遮挡。
- 尝试: 检查按钮尺寸和CSS偏移;将星点容器固定为44×44,以中心定位,连线与按钮共用坐标;把标签组合成一个整体。
- 处理: 天璇、天玑、开阳文字移到星点下方,其余标签保持在上方。修改已进入本地阶段包。
- 是否解决: PR #1中保留了修复提交与潘岳复查批准的记录,随后已合并;最终显示效果见导航截图。
- 收获: 图形和按钮如果各用一套位置规则,界面很容易“看起来差一点”;统一坐标比不断加偏移更容易维护。
9.2 PR已经批准,为什么仍然不能合并?
- 问题: 第08阶段PR出现README.md与app.js冲突;审阅批准并不会消除冲突。
- 原因: 这次小面包没有先在自己的Fork中同步主仓库已合并的进度,就继续了下一阶段。新功能与主仓库的修改在旧版本基础上分别推进,并改到了同一段内容,Git无法自动决定保留哪一边。并不是“落后几个提交”就一定冲突,而是这次确实存在重叠修改。
- 尝试: 区分Current为功能分支、Incoming为main,逐段比较。新增归还页面和联系事件需要保留;详情长行不能只看开头就全部接受。
- 处理原则: 避免直接Accept both导致旧详情覆盖新详情,或产生重复处理。冲突解决提交后还要重新运行。
- 是否解决: PR #7中出现
07dc52e合并提交,之后补充了“已解决合并冲突问题”的复查意见,最终通过5d79a1d合并到main。 - 收获: 下一阶段要在前一阶段合并后,同步Fork的main再建分支;同步main不会自动更新提前创建的旧分支。
【PR #7】

🌹 第十关:队友结算——一起把星星送回家
值得学习的地方:
小面包没有只看“页面能打开”就直接通过。在北斗导航的PR里,她指出了星点偏移和标签分离,修复后又检查入口。这件事让我看到,从使用者角度认真看一遍,确实能发现写代码时容易忽略的问题。最后文档阶段,她又按README检查运行步骤,让交付不只停留在“我电脑上可以”。
需要改进的地方:
我们都需要把分支同步与验证记录做得更及时。联系与交接阶段发生了冲突,PR #8的说明也曾保留待填写的测试结果。这提醒我们:不只是功能完成了就可以交接,分支起点、测试结果和文档都应一起核对。下一次可以约定“同步后建分支,测试后补记录,再请对方审阅”。我也不能只催着把功能合进去,而忽略最后这些看似琐碎的检查。
Nova的本次总结:
这次我们不在同一个地方,没办法随时面对面讨论,也不能直接凑到对方电脑前说“这里再点一下”。PR成了我们异步沟通和交接代码的地方:这次改了什么、哪里需要调整、修复后是否通过,都能留下记录,不必只靠聊天里一句“我改好了”。
我们可以分别完成各自负责的部分,再在各自电脑上下载、检查和迭代。发现问题就在PR里说明,修改后继续复查;即使没有同时在线,也能顺着记录了解对方的进度。对我来说,GitHub不再只是“上传作业的地方”,而是真正方便两个人一起推进项目的工具。
上一次,我更关心“页面能不能连起来”;这一次,我开始追问“数据有没有留下、状态由谁修改、异常时用户往哪里走”。星空配色和玫瑰让拾星有自己的样子,但真正支撑它的,是一条能够走到底、也能够接受检查的流程。
目前的拾星仍是本地课程演示,没有解决真实多用户同步,也不能拿模拟认证当安全承诺。后续若继续完善,应先增加真实试用反馈和更充分的异常测试,再考虑后台与多人数据共享。
⭐ 北斗负责指引方向,我们负责把每一步走清楚。
PickStar · Retrieve your lost star.
寻回属于你的星辰,也把这次学到的方法带往下一关。
EXP +1 |解锁技能:原型落地、状态管理、Fork / PR协作、自动化测试与交付说明。
🌟 本次冒险任务,等待最后的截图与验收存档。
愿你抬头有星光,低头有代码。下一张地图,加载中…… 🚀

浙公网安备 33010602011771号