2026秋软件工程结对作业(第二次之程序实现)
2026秋软件工程第二次结对作业:拾光校园失物招领 App 的实现与测试
| 项目 | 内容 |
|---|---|
| 所属课程 | 软件工程与软件工程实践 |
| 作业要求 | 第二次结对作业之程序实现 |
| 本文作者 | 曾炜毅(102401314) |
| 结对成员 | 曾炜毅(102401314)、陈勇昊(102401323) |
| 本人博客主页 | 曾炜毅 |
| 队友博客主页 | 陈勇昊 |
| 本次队友作业博客 | 博客链接 |
| 项目仓库 | 102401314-102401323 |
| APK 下载 | 拾光共享版 1.1.0 |
| 上次原型 | 拾光原型 |
本次把上一份拾光原型实现为可安装的 Android App,并增加共享服务,让不同客户端集中查看寻物和招领信息。代码和测试采用 Codex 辅助实施,本文的程序结果依据真实源码、日志和模拟器截图。分工、个人总结和队友评价已审核确认,个人实际耗时采用回顾估计。
一、结对分工
项目成员为曾炜毅(102401314)和陈勇昊(102401323)。程序与技术材料采用 Codex 辅助开发,按照“App 交互与安卓交付”和“共享服务与测试复核”划分主要任务。以下分工已经审核确认。
| 成员 | 分工安排 | 对应模块与材料 |
|---|---|---|
| 曾炜毅(102401314) | 侧重需求梳理和 App 端:核对发布、搜索、详情、联系、我的发布等页面流程;理解并复审页面交互、草稿及缓存逻辑;负责安卓入口、网络连接和 APK 构建的交付复核,以及主仓库和使用说明整理。 | web/app.js、web/styles.css、web/network.js、android/、scripts/build_apk.py、README.md |
| 陈勇昊(102401323) | 侧重共享服务与质量验证:理解并复审接口、SQLite 存储和发布者权限;按白盒分支和边界值设计、补充测试;核对跨客户端同步、断网与重启行为;整理测试报告、成果截图和博客技术材料。 | server/server.cjs、web/domain.js、tests/、docs/testing.md、docs/images/、博客稿 |
双方共同负责需求确认、接口与状态约定、App 和服务联调、关键代码复审及总结。曾炜毅侧重从使用者流程检查测试是否覆盖实际操作,陈勇昊侧重从异常和权限条件检查 App 行为是否正确;发现问题后共同复核修改结果。
协作安排:曾炜毅维护主仓库,陈勇昊使用 Cgg0024 账号创建的 作业 fork 已核对来源。该账号已在 PR #1 中提交 README 运行记录贡献,维护者随后修正文档的 Windows 环境名称,PR 已合并到主仓库 main,具体记录及复审结果见结对协作记录.md。个人耗时、总结和队友评价已审核整理,两人分别发布、提交作业博客。
已有证据对应的实际协作
| 成员 | 已能核对的参与 | 记录依据 |
|---|---|---|
| 曾炜毅 | 提出 App 交付要求,推动离线共享范围复审、联网补充、模拟器运行路径和材料整理;要求复审修正并合并队友 PR。主仓库维护操作由辅助工具执行。 | 本次需求与复审记录;主仓库 k0n0y;修正 b667b8d 与合并 3f85f54 |
| 陈勇昊 | 使用 Cgg0024 账号 fork 作业仓库,通过 PR 提交 README 运行环境、测试输出及服务健康检查说明。 | 提交 e2f0ac2 和 PR #1;贡献文件为 README.md |
模块职责已经审核确认;上表列出可通过本次交流和仓库记录核对的贡献。源程序与自动验证采用 Codex 辅助实施,仓库提交用于追踪修改,个人学习与复审投入按 PSP 的回顾估计记录。
二、PSP 记录
下面保留初始阶段编码前记录的 325 分钟规划,并列出审核确认的个人实际投入,单位为分钟。个人实际采用回顾估计,原预估是助手实施规划,统计口径不同,不直接计算个人超时比例。父级为小计,合计只累加明细。
| PSP2.1 | Personal Software Process Stages | 初始实施预估 | 曾炜毅实际(回顾估计) | 陈勇昊实际(回顾估计) |
|---|---|---|---|---|
| Planning | 计划(小计) | 15 | 25 | 25 |
| Estimate | 估计任务所需时间 | 15 | 25 | 25 |
| Development | 开发(小计) | 250 | 790 | 820 |
| Analysis | 需求分析及学习新技术 | 30 | 75 | 80 |
| Design Spec | 设计文档 | 20 | 45 | 55 |
| Design Review | 设计复审 | 15 | 30 | 35 |
| Coding Standard | 制定代码规范 | 10 | 20 | 20 |
| Design | 具体设计 | 25 | 75 | 75 |
| Coding | 编码与代码理解、修改 | 90 | 300 | 285 |
| Code Review | 代码复审 | 20 | 65 | 80 |
| Test | 测试与修复 | 40 | 180 | 190 |
| Reporting | 报告(小计) | 60 | 195 | 205 |
| Test Report | 测试报告 | 20 | 90 | 95 |
| Size Measurement | 计算工作量 | 10 | 25 | 30 |
| Postmortem & Process Improvement Plan | 总结与改进计划 | 30 | 80 | 80 |
| 合计 | 只累加明细,避免重复计小计 | 325 | 1010 | 1050 |
曾炜毅合计为 16 小时 50 分钟,陈勇昊为 17 小时 30 分钟。个人工时合计为 2060 分钟(34 小时 20 分钟),不代表项目历时。父级行只作小计,不能再次累加。
统计方式:个人实际栏为事后回顾估计,已经审核确认,不属于连续计时记录。个人学习、修改、复审和材料整理计入对应阶段;辅助工具自行执行和无人值守等待不作为个人投入。
复审共享需求后,编码前另记录了补充阶段规划:需求复审 15 分钟,接口与身份设计 20 分钟,实现 80 分钟,服务与独立客户端验证 35 分钟,APK 构建和安装验证 20 分钟,材料修订 25 分钟,合计 195 分钟。这部分与初始规划分开记录,原始记录见仓库 docs/PSP.md。
曾炜毅实际投入合计 1010 分钟,陈勇昊合计 1050 分钟,采用审核确认的回顾估计。具体工作内容及时间分析如下。
以下为本人审核确认的个人实际投入,采用事后回顾估计,单位为分钟。曾炜毅总量为 1010 分钟(16 小时 50 分钟),陈勇昊为 1050 分钟(17 小时 30 分钟);差异主要来自模块分工。投入包括学习、理解和复审辅助生成的代码、联调及材料整理。
数值已同步到 PSP.md 的实际投入列。统计采用回顾估计,不属于连续计时;辅助工具自行执行和无人值守等待不计入个人投入。共同讨论分别计入各自个人工时,两个个人工时相加不代表项目历时。原始 325 分钟与补充 195 分钟是编码前实施规划,分别保留。
曾炜毅:App 交互与安卓交付方向
| PSP2.1 | 阶段 | 实际投入(分钟,回顾估计) | 对应工作内容 |
|---|---|---|---|
| Planning | 计划(小计) | 25 | 仅汇总 Estimate |
| Estimate | 估计任务所需时间 | 25 | 拆分发布、搜索、详情、联系和状态更新任务,并安排实施顺序 |
| Development | 开发(小计) | 790 | 汇总下列开发明细 |
| Analysis | 需求分析及学习新技术 | 75 | 理解作业评分要求、上一版原型与共享范围;学习 WebView、请求与页面状态管理 |
| Design Spec | 生成设计文档 | 45 | 整理页面入口、字段规则、主流程和失败提示,核对流程图与页面行为 |
| Design Review | 设计复审 | 30 | 与队友核对发布成功条件、本人状态权限、离线行为和多客户端同步 |
| Coding Standard | 制定代码规范 | 20 | 确认命名、页面与业务模块职责、表单校验和统一提示规则 |
| Design | 具体设计 | 75 | 细化导航、卡片、发布表单、详情弹窗、我的发布和状态确认交互 |
| Coding | 具体编码 | 300 | 阅读和复审页面、样式与网络代码;整理修改、草稿恢复、原生入口及安卓打包相关操作 |
| Code Review | 代码复审 | 65 | 检查事件绑定、返回行为、输入校验、缓存同步和凭证保存;对照接口约定核对异常提示 |
| Test | 测试与修复 | 180 | 核对模拟器安装与核心流程、断网和重开验证,参与交互问题定位与修正复核 |
| Reporting | 报告(小计) | 195 | 汇总下列报告明细 |
| Test Report | 测试报告 | 90 | 整理验证记录、截图说明、安装与运行步骤,核对日志的验证范围 |
| Size Measurement | 计算工作量 | 25 | 核对实际参与文件、修改点和对应提交,整理个人工作范围 |
| Postmortem & Process Improvement Plan | 事后总结与改进计划 | 80 | 整理个人收获、交互与运行环境问题、队友评价,并提出下次的安排 |
| 合计 | 只累加明细,不重复累加小计 | 1010 | 16 小时 50 分钟 |
陈勇昊:共享服务与测试方向
| PSP2.1 | 阶段 | 实际投入(分钟,回顾估计) | 对应工作内容 |
|---|---|---|---|
| Planning | 计划(小计) | 25 | 仅汇总 Estimate |
| Estimate | 估计任务所需时间 | 25 | 拆分接口、权限、持久化、测试和报告任务,并安排优先级 |
| Development | 开发(小计) | 820 | 汇总下列开发明细 |
| Analysis | 需求分析及学习新技术 | 80 | 理解跨客户端共享需求;学习 Node 内置测试、SQLite、HTTP 请求和凭证校验 |
| Design Spec | 生成设计文档 | 55 | 整理数据字段、接口输入输出、发布者权限、状态流转和错误返回 |
| Design Review | 设计复审 | 35 | 与队友核对时间格式、重复请求、缓存边界、授权与客户端提示 |
| Coding Standard | 制定代码规范 | 20 | 确认业务函数职责、错误码、测试名称、断言和临时数据库的使用规则 |
| Design | 具体设计 | 75 | 细化接口与数据库约束;围绕输入、权限、并发、重启和重试设计白盒用例 |
| Coding | 具体编码 | 285 | 理解和复审服务、业务及测试代码,参与接口修订、测试补充和辅助脚本修改复核 |
| Code Review | 代码复审 | 80 | 检查参数校验、SQL 参数绑定、凭证归属、请求幂等性及测试是否断言真实状态 |
| Test | 测试与修复 | 190 | 核对测试输出与覆盖率;复核独立客户端读取、越权拒绝、服务重启与断网恢复,整理问题与处理结果 |
| Reporting | 报告(小计) | 205 | 汇总下列报告明细 |
| Test Report | 测试报告 | 95 | 整理测试方法、典型用例、实测日志与截图来源,区分自动验证和个人操作 |
| Size Measurement | 计算工作量 | 30 | 统计实际参与的模块、用例和修改记录,核对个人 commit 与 PR 贡献 |
| Postmortem & Process Improvement Plan | 事后总结与改进计划 | 80 | 整理测试学习、协作问题、队友评价与下一次的流程改进建议 |
| 合计 | 只累加明细,不重复累加小计 | 1050 | 17 小时 30 分钟 |
统计方式与时间分析
本次按工作阶段回顾投入并审核数值。每个工作段归入一个明细阶段,等待、离开和休息不计入个人投入;由于没有连续计时,明确采用“回顾估计”的统计方式。
编码部分应区分本人学习与修改、助手执行和无人值守等待。两人共同讨论可以分别记录个人投入,但同一个人不能同时把同一段时间重复计入编码、复审和测试。材料整理计入报告,软件学习计入分析,遇到问题后的定位与修复可以计入测试阶段,分类保持一致即可。
App 方向主要投入在页面与网络逻辑理解、连接配置和打包复核;服务测试方向主要投入在权限边界、时间格式、重复请求和独立客户端联调复核。陈勇昊在代码复审、测试和报告方面的投入略高,曾炜毅在 App 编码与交互理解方面的投入略高。原来的 325 + 195 分钟是助手实施规划,与两人的个人投入统计口径不同,不直接计算个人超时百分比。
需求变化与下次改进
初始实施规划为 325 分钟,补充共享服务阶段另规划 195 分钟。需求范围的变化包括共享存储、独立凭证、服务接口与跨客户端验证,因此补充工作涉及新的实现和验证任务。两人的 1010/1050 分钟还包含学习、复审、联调和材料整理。
项目的问题集中在共享范围、跨端时间、模拟器连接和中文路径等方面,表明实施安排应为环境联调、异常验证和文档复核留出时间。
下一次改进是开始前分别估计任务,按阶段记录开始与结束时间并扣除休息,结束时及时归类;学习计入分析,问题定位与修复计入测试,材料整理计入报告。辅助工具自行执行而本人没有参与的工作不计入个人投入,以便建立口径一致的预估与实际对比。
三、解题思路与设计实现
3.1 从原型落实到完整流程
校园寻物和招领信息常散落在群聊中。使用者既需要找到相关物品,也需要知道旧信息是否仍然有效。因此,程序围绕“发布—浏览或搜索—查看详情—联系发布者—更新状态”设计,保留上次拾光原型的首页卡片、薄荷绿配色和底部导航。
本次选择 Android App 形式,交付可安装 APK。首页集中呈现线索,搜索页按关键词和条件筛选,详情展示物品特征与联系方式,发布页完成字段校验,“我的发布”提供原发布者的状态操作。寻物完成显示“已找到”,招领完成显示“已归还”。联系通过发布者填写的方式在应用外进行,没有加入实名认证、地图、即时聊天或复杂管理后台。
3.2 共享数据与发布者身份
只把列表保存在手机里,其他安装实例无法读取。共享版增加一个轻量 Node 服务,正式记录统一保存在 SQLite;不同客户端连接同一服务后读取相同的公开列表。手机内只保存自己的发布者凭证、最近缓存和草稿。
每个安装实例自动获取随机发布者编号和凭证。服务存储凭证的 SHA-256 摘要,用凭证识别发布者,再检查记录归属。隐藏他人记录的按钮只是交互提示,真正的修改权限由服务核验。即使使用者直接请求接口、提交别人的 ownerId,也不能获得对方的更新权限。
3.3 模块划分
| 模块 | 作用 |
|---|---|
web/app.js |
页面、路由、表单、确认弹窗、同步与缓存 |
web/domain.js |
输入校验、关键词和组合搜索、状态文案,兼容旧数据的函数 |
web/network.js |
请求封装、服务地址校验、原生请求回调 |
MainActivity.java |
内置 WebView、后台网络线程、私有存储、剪贴板和返回键 |
server/server.cjs |
共享接口、身份核验、重复提交处理和 SQLite 持久化 |
页面和图标随 APK 打包,网络请求由 Java 后台线程执行,避免把等待服务的操作放在界面线程上。程序使用 Android 系统 WebView,采用自有页面嵌入方式,参考 Android 官方说明。
四、关键流程、数据流与代码
4.1 发布和状态更新流程
发布先在页面校验必填项、长度、分类、类型和时间,再把本地时间转为带时区的 ISO 时间,携带凭证提交。服务再次校验并写入数据库,返回记录后才进入成功页。填写错误或服务不可达时保留输入并提示原因。
联系后,原发布者在“我的发布”或本人详情中确认状态变更。取消确认不会发送更新请求;确认后服务核验归属并更新单条记录。其他客户端可手动刷新,前台约每 5 秒自动同步。

4.2 数据流
客户端的表单和条件进入共享服务,服务读取或修改 SQLite,返回公开信息或错误。凭证只保存在各自设备,公开列表不会带出凭证;断网时设备读取最近缓存,写入操作不会假报成功。

4.3 关键代码:服务端核验发布者
以下代码直接来自 server/server.cjs。没有合法凭证时返回 401;匹配后得到的发布者编号来自数据库,而不是客户端指定的身份。
function auth(req) {
const token = /^Bearer ([a-f0-9]{64})$/.exec(req.headers.authorization || '')?.[1];
const user = token && db.prepare('SELECT id FROM publishers WHERE token_hash=?').get(hash(token));
if (!user) fail(401, '发布者凭证无效,请重新连接服务');
return user.id;
}
状态更新还检查记录归属:
if (auth(req) !== row.owner_id) fail(403, '只有发布者能更新状态');
4.4 重试与并发处理
发布请求带有 requestKey,数据库设置 (owner_id, request_key) 唯一约束。相同请求号和相同内容的重试返回原记录;同编号对应不同内容时返回冲突,避免覆盖。每条记录独立写入,两个客户端的并发发布不会用整份旧列表互相覆盖。
const existing = db.prepare(
'SELECT payload,input_hash FROM items WHERE owner_id=? AND request_key=?'
).get(ownerId, input.requestKey);
if (existing) {
if (existing.input_hash !== inputHash)
fail(409, '该发布请求已保存其他内容,请重新填写');
return json(200, {item: JSON.parse(existing.payload), replayed: true});
}
五、附加特点及成果展示
5.1 组合筛选
只有物品名称,往往难以区分相似线索。程序支持关键词、类型、类别、地点和处理状态同时筛选。关键词经过 Unicode 和大小写归一化,多词需要全部命中;服务和页面复用同一个搜索函数。
function search(items, { q = '', kind = 'all', category = 'all', place = '', status = 'all' } = {}) {
const words = normalize(q).split(/\s+/).filter(Boolean);
return items.filter(item => {
const text = normalize(`${item.title} ${item.category} ${item.place} ${item.description}`);
return words.every(word => text.includes(word)) &&
(kind === 'all' || item.kind === kind) &&
(category === 'all' || item.category === category) &&
(!normalize(place) || normalize(item.place).includes(normalize(place))) &&
(status === 'all' || item.status === status);
}).slice().sort((a, b) => Date.parse(b.postedAt) - Date.parse(a.postedAt) || a.id.localeCompare(b.id));
}

5.2 一键复制联系方式
详情里的联系弹窗展示发布者提供的字段。安卓版本调用系统剪贴板,减少在两个应用之间手工抄写联系方式的步骤;使用者仍需要核对物品特征后自行联系。
ClipboardManager clipboard =
(ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(ClipData.newPlainText("拾光联系方式", value));

5.3 草稿恢复、断网缓存与错误提示
发布页保存草稿,切换页面后能继续填写。服务不可达时仍可查看缓存,但发布失败会留在表单,状态更新失败保持原状态。网络恢复后可重试提交。

5.4 确认更新与恢复进行中
更新前用弹窗确认,防止误触;原发布者可以把已完成记录恢复为进行中。最新状态会出现在其他客户端,减少已经找到或归还之后的重复询问。



5.5 实际 APK 发布界面


以上安装版图片来自 Android 12 模拟器的实际 APK。标注“独立客户端乙”的图片来自另一套浏览器配置,不将它们描述成第二台实体手机。
六、目录组织和使用说明
6.1 目录结构
102401314-102401323/
├─ apk/shiguang-1.1.0.apk 可安装的签名 App
├─ 启动共享服务.cmd Windows 服务入口
├─ server/server.cjs HTTP 接口与 SQLite
├─ android/app/src/main/ Activity、网络线程、存储、图标
├─ web/ 内置页面、样式、业务和网络模块
├─ tests/ 业务与服务测试
├─ scripts/ 构建、验证、图示及材料生成
├─ docs/ 博客、PSP、说明和图片
├─ evidence/ 实际日志和验证结果
└─ data/ 运行时数据库,不上传仓库
页面、共享业务、原生入口和服务分开组织。测试直接引用实际业务和服务,证据单独保留,便于复现。完整说明见 README。
6.2 运行服务和 APK
- 电脑安装 Node.js 24,本项目实测 24.18.0。下载整个仓库,双击“启动共享服务.cmd”,或在项目目录运行
npm start。没有第三方 npm 依赖,无需npm install。 - 保持服务窗口运行。普通手机与服务电脑连接互通的局域网,在 App 连接设置填写窗口显示的电脑地址,例如
http://192.168.1.5:8787。例子中的 IP 需要替换,手机不能直接填 localhost。 - 把
apk/shiguang-1.1.0.apk传到 Android 8.0 以上设备或模拟器,按系统提示允许安装。最低版本来自 manifest 声明,实际安装验证环境是 Android 12 模拟器。 - 本机 MuMu 模拟器可使用下面的 ADB 转发,再在 App 填写
http://127.0.0.1:8787:
adb -s 127.0.0.1:16384 reverse tcp:8787 tcp:8787
其中 16384 是本机 MuMu 的 ADB 端口;其他模拟器应使用自己的设备序列号。ADB 端口转发使模拟器访问电脑服务,不依赖校园 Wi-Fi 能否直接访问电脑。
- 用一个安卓模拟器和独立浏览器配置,或两个独立模拟器,验证“甲发布—乙搜索与详情—甲更新—乙同步”。浏览器可打开
http://localhost:8787,但正式交付形式仍是 APK。
6.3 数据和使用范围
共享服务默认保存到 data/shiguang.sqlite,停止服务后再备份整个 data 目录。清除 App 数据会失去该设备的修改凭证,公共记录仍保留在服务中;当前没有账号跨设备登录和凭证恢复。同服务用稳定 serverId 识别,地址变化后重新连接可以保留身份。
当前交付支持局域网或本机模拟器转发。代码可以部署到有持久磁盘的服务器并配置 HTTPS,但本次没有公网上线地址,也没有把云端部署写成已验证结果。App 只接受局域网 HTTP 地址,公网服务地址需要 HTTPS。
七、单元测试学习、白盒案例与结果
7.1 工具和简易教程
选用 Node 内置 node:test 和 node:assert/strict。纯业务模块不依赖页面,服务模块能启动真实 HTTP 和临时 SQLite,因此测试不需要另做一套模拟业务实现。
学习顺序是:先确定输入和预期结果,再写断言,随后根据源代码条件补充反例,最后用覆盖率检查遗漏。在项目目录运行:
npm test
npm run test:coverage
正常结束会显示 tests、pass 和 fail;失败时进程返回非零退出码。修改测试后可以直接重跑。覆盖率反映被执行的代码范围,不能代替对所有设备和场景的验证。
7.2 部分测试代码及被测行为
下面的代码来自服务测试。session、publish、call 是 tests/server.test.cjs 中的工厂,实际调用当前服务。它既检查他人不能修改,也检查拒绝后原状态没有变化。
test('乙即使知道甲的信息编号也不能更新状态', async () => {
const a = await session(), b = await session();
const {body} = await publish(a);
const result = await call('/api/items/' + body.item.id + '/status',
'PATCH', {status: 'resolved', ownerId: a.ownerId}, b.token);
assert.equal(result.status, 403);
assert.equal((await call('/api/items/' + body.item.id)).body.item.status, 'open');
});
这覆盖了服务的 auth(req) !== row.owner_id 分支。直接调用接口可以检验真正的授权边界,避免只测试“页面有没有按钮”。
7.3 白盒与边界设计
| 典型情况 | 设计依据 | 预期 |
|---|---|---|
| 完整寻物与招领 | 类型分支 | 初始 open,对应寻找中/待认领 |
| 空文本和纯空格 | 必填与 trim 分支 | 拒绝发布并指出字段 |
| 文本 max 与 max+1 | 长度边界 | 上限允许,超限拒绝 |
| 非法/未来/当前时间 | 时间比较 | 非法和未来拒绝,业务边界可验证 |
| 分类和类型不在枚举内 | 合法值分支 | 拒绝无效枚举 |
| 多词、全角、大小写、无结果 | 归一化及筛选条件 | 多词同时命中,无结果返回空列表 |
| 多个筛选条件冲突 | 条件组合 | 所有条件同时满足才匹配 |
| 无凭证/伪造凭证/本人凭证 | 授权分支 | 401或本人成功 |
| 他人凭证且伪造 ownerId | 记录归属分支 | 403,原状态不变 |
| 重复请求和内容冲突 | 唯一约束及摘要 | 返回原记录或409,不覆盖 |
| 并发发布、重启服务 | 独立写入与持久化 | 两条保留,重启仍能读取和授权 |
| 时区不同、UTF-8 分段 | 实际网络输入边界 | 同一时刻正确校验,中文不乱码 |
普通服务用例使用独立内存数据库;重启用例使用专门临时文件,避免污染正式记录。设计同时考虑使用者常见习惯,例如多词搜索、误触取消、反复提交及填写后切换页面。
7.4 实测结果与范围
| 验证内容 | 结果 |
|---|---|
| 业务、兼容与服务测试 | 55/55 通过 |
| 两套独立浏览器配置的界面检查 | 24 项通过 |
| 安装 APK 与独立客户端检查 | 21 项通过 |
| domain.js 行覆盖 | 98.86% |
| domain.js 分支覆盖 | 92.39% |
| domain.js 函数覆盖 | 100% |
55 个测试中,31 个是原业务与旧数据兼容回归,24 个针对共享接口及集成行为。旧版身份切换的兼容测试不作为联网授权证据;当前服务的权限由上述实际 HTTP 测试覆盖。domain.js 的覆盖率也不代表 Java 网络桥或整个 App 的覆盖率。
界面检查包括发布、搜索、详情和联系、越权拒绝、自动同步、取消更新、断网缓存、发布及更新失败、恢复重试、服务重启和 App 强停重开。APK 检查还验证了原生剪贴板及返回键。
日志在 evidence/unit-tests.txt、browser-network-ui.json 和 android-network-ui.json。本次使用 Android 12 模拟器与独立浏览器配置;没有两位成员实体手机试玩的确认,模拟器验收应如实标注。作业截图没有强制实体手机,这些本地结果也不等同于助教官方评分。
八、GitHub 签入记录
仓库按两人的学号命名,保留需求和预估、功能、修复、测试、材料等真实提交。最近记录如下,完整记录见 GitHub commits。
7f260dc docs: fill PSP reference allocations with per-stage totals
8ff1e7c docs: complete paired blog drafts and verify public image links
70a9203 docs: audit homework requirements and refresh verification evidence
a743e37 docs: synchronize merged partner PR across delivery materials
3f85f54 Merge pull request #1 from Cgg0024/docs-add-run-result
146ee2e docs: record maintainer correction of partner PR environment
b667b8d docs: correct Windows product name for build 26200
66ddd3d docs: record partner PR contribution and review findings
e2f0ac2 docs: add verified run results
3878fc2 docs: record verified partner fork and pending pull request

截图反映采集时的真实页面,后续提交以仓库记录为准。
| 结对协作记录 | 链接 |
|---|---|
| 主仓库 | 102401314-102401323 |
| 另一成员真实 fork | Cgg0024/102401314-102401323 |
| 有实际贡献的 Pull Request | PR #1:补充运行结果 |
Fork 与 Pull Request
陈勇昊使用的 GitHub 账号为 Cgg0024,其 fork 仓库 来源已经核对为作业主仓库。随后该账号从 docs-add-run-result 分支向主仓库 main 提交了 PR #1:docs: add verified run results。
PR 对应 提交 e2f0ac2,GitHub 记录的提交账号为 Cgg0024。核对时仅修改 README.md,新增 126 行,内容包括运行环境、无 npm 时的 Node 测试命令、55 项测试的输出,以及共享服务启动和健康接口检查步骤。这是本次已经能通过仓库核对的文档贡献,不能据此推断其完成了全部服务代码或 App 人工验收。
README 记录报告 Node.js v24.19.0、55/55 测试通过、健康接口 HTTP 200;这些是队友提交的运行结果,与仓库中原有的自动验证记录分别说明。目前没有在队友机器上独立复现该环境,也没有把服务健康检查当成安卓交互验收。
PR 状态与复审
PR 已由维护者 k0n0y 合并至主仓库 main,GitHub 状态为 Merged(已合并),对应 合并提交 3f85f54。主仓库已包含队友提交的 README 运行记录和维护者的环境名称修正;队友原提交保留在合并历史中。源码、APK 和现有自动测试未由这个 PR 修改。
复审发现原环境名称与版本、构建号不一致:README 原写“Windows 10 Home China 25H2(build 26200)”,而 微软发布信息 把 25H2、build 26200 列在 Windows 11 下。根据用户要求,维护者 k0n0y 已在同一个 PR 中提交 修正 b667b8d,改为“Windows 11 25H2(build 26200;系统名称按版本与构建号校正)”。远端 README 已核对。修正只涉及这一行,队友原提交、测试输出和健康接口记录保持原样;未将此修正描述为对队友机器重新测量。
PR 和提交的核验摘要见 evidence/partner-pr-verification.json。后续修正、复审和合并状态以 GitHub 当前记录为准。
九、遇到的问题、尝试与解决
9.1 共享范围不足
初始离线数据只能在一个安装实例中浏览,不能满足不同客户端集中查看线索的使用流程。复审后增加共享服务、SQLite 和设备凭证,保留缓存与草稿。APK与独立浏览器验证了甲发布乙读取、甲更新乙同步及他人修改被拒绝。这里的收获是先确认数据的共享范围,再判断页面上的“公共列表”是否真正实现了需求。
9.2 手机时间被服务误判为未来
首次 APK 联网验证时,模拟器与电脑时区不同,表单里的本地日期时间被服务按自己的时区解析,导致发布失败。客户端改为提交带时区 ISO 时间,服务拒绝没有时区的 API 输入;新增回归测试后安装检查通过。跨端传输时间不能只传看起来完整的日期字符串,还要明确同一时刻。
9.3 实体手机无法访问当前电脑服务
联调时,电脑服务已启动,用户反馈手机无法打开健康接口。检查确认电脑使用有线网卡,其他列出的地址属于虚拟网络。此次没有确认网络不可达的具体根因,也未把实体手机写成验收通过。随后使用 MuMu 与 ADB reverse 连接电脑 8787 服务,已确认模拟器健康接口返回 ok:true。共享功能的自动验证仍依据安装版与独立浏览器的实际记录。
9.4 Windows 中文目录与网络中文数据
Android 工具曾无法识别中文绝对资源路径,构建脚本改为相对参数后正常打包,并通过签名和安装检查。网络读取则把分段字节汇总后统一按 UTF-8 解码,新增中文跨段测试。问题已经修复;也说明文件编码、路径和传输边界要分别检验。
9.5 协作仓库与运行记录复审
协作材料整理时,先收到的 fork 链接指向个人主页仓库,不是本次按学号命名的作业仓库。随后改为核对 Cgg0024/102401314-102401323 的来源,再使用实际 PR 和提交记录确认协作,避免把无关仓库写成作业贡献。正确 fork 与 PR #1 已核验,链接见第八节。
队友通过 fork 提交 README 运行结果后,复审发现系统名称和构建号不一致。维护者在同一个 PR 中提交一行修正,再合并到主仓库,保留队友原来的提交与运行输出。问题已经解决;具体分工和尝试可由 e2f0ac2、b667b8d、3f85f54 三条记录追踪。
这项协作的改进经验是:先确认仓库和分支,再核对运行环境、命令与结果,最后合并。文档也需要复审;某项检查通过应对应它实际验证的范围。上述说明依据收到的链接和真实提交,未补写没有记录的口头讨论或成员亲自操作经历。
十、队友评价与个人总结
10.1 对陈勇昊的评价
从本次已经保留的协作记录来看,陈勇昊值得学习的地方是把运行方法和运行结果一起整理进交付文档。其 Cgg0024 账号通过 fork 提交了 README 运行记录,内容包括环境、测试命令、55 项测试输出和服务健康检查。这使其他人能够沿着明确的步骤尝试运行项目,也让测试结论有对应的记录可以查阅。对课程项目而言,完成程序之后继续补充可复现的说明,是很有价值的工作习惯。
建议改进的是环境信息的复核方式。原 PR 中的 Windows 名称与版本、构建号不一致,后来由维护者修正。下次可以在提交前逐项核对系统版本、运行命令和截图来源;健康接口检查与 App 完整流程也应分别记录。这样可以减少复审返工,让说明中的每个结论都与证据对应。
10.2 本人总结
这次项目最值得总结的是对“实现完成”的理解。原型中能够看到公共列表,并不意味着不同安装实例真的共享数据;只有甲发布的信息能被乙读取,甲更新状态后乙也能看到结果,才形成了作业要求的使用闭环。从离线版补充为共享版的过程说明,复审需求时需要同时检查页面、数据来源和状态传播。
用户侧的运行过程也是产品的一部分。服务已经启动,手机仍可能无法连接;使用模拟器时又需要明确服务地址与 ADB 转发。README 如果只写“安装后使用”,其他人就很难复现。今后整理交付材料时,应把安装、启动、连接、发布、搜索和更新状态组织成一条可以照着操作的路径,并说明常见失败时应该检查什么。
仓库维护方面,本次保留了队友的原始提交、维护者的修正和合并记录。文档问题可以通过一个清楚的修正提交解决,保留原作者的贡献,也便于以后追踪改动原因。对辅助生成的代码,仍需要能够解释功能、权限和异常处理;程序通过自动检查不能替代本人理解和复核。
当前过程记录的不足是个人投入没有形成连续计时记录,本次 PSP 采用审核确认的回顾估计。下一次应从开始就分别记录 PSP,完成一个模块后及时写明修改范围、测试结果和问题,而不是到报告阶段再回忆。验收时也应把设备环境、操作步骤与结果一起保存,使报告内容便于核对。
浙公网安备 33010602011771号