2026秋软件工程结对作业(第二次之程序实现)

第二次结对作业:校园失物信息共享平台

一、作业信息

这个作业属于哪个课程 202601 软件工程
这个作业要求在哪里 2026秋软件工程结对作业(第二次之程序实现)
这个作业的目标 1. 完成校园失物信息共享平台的前端程序实现
2. 实现失物与招领信息的发布、多校区筛选、精准检索与详情交互
3. 完善数据白盒校验、状态分层管理、日夜双主题与 6 条/页数字分页器
4. 搭建零依赖自动化单元测试体系,通过 GitHub PR 规范协作保障工程质量
学号 102401609
结对成员 成员一:102401609 王智钦;成员二:102401608 魏之皓
主 GitHub 仓库 King-Zhi/102401609-102401608
队友 Fork 仓库 wizneko/102401609-102401608
项目名称 福州大学失物信息共享平台
项目形式 PC 端 Web 页面,纯前端、绿色免部署运行
结对同学博客链接 魏之皓:wizneko 的博客园主页 / 第二次结对作业随笔
本作业博客链接 王智钦:zhiking 的博客园主页 / 第二次结对作业随笔

二、结对分工

为保障结对编程的高效协同与敏捷交付,团队按照“分工明确、交叉复审、协同验证、联合攻关”的原则进行职责划分,具体分工如下:

成员一:王智钦(102401609)

  • 系统架构搭建与数据持久化:负责系统整体前端响应式架构搭建与基础数据流设计,实现基于 LocalStorage 的数据持久化层(js/data.js),定制全套本地离线高保真摄影图片素材,并设计预设示例图保护与升级清洗机制。
  • 智能模糊搜索算法与状态分层:设计校园专属常用简称词典(如“高数”联想《高等数学》,“饭卡”联想《学生卡》)与字符子序列模糊匹配算法,实现多级加权相关度评分引擎(Relevance Ranking)与正文命中上下文摘要截取(Snippet);引入活跃事务置顶、已结贴事务沉底的两级分层排序机制。
  • 多校区支持与地标胶囊:实现福州大学全域 7 大校区(旗山、铜盘、怡山、泉港、晋江、集美、鼓浪屿)专属筛选与发布绑定,将校区选择移至顶部地标定位栏作为全局切换胶囊,简化搜索栏。
  • 最新线索置顶与 6 条/页数字分页器:排查并彻底解决发布后最新线索因校区过滤死板与浏览器脚本缓存导致的消失问题,实现免刷新动态置顶渲染;重构实现每页固定 6 条(2行 × 3列)经典数字分页器、平滑视口滚动对齐与条目区间提示。
  • 版本合流与代码审查:主导主仓库(King-Zhi)的版本维护,审查并合入队友提交的 PR #1、PR #3、PR #4、PR #5、PR #7,解决分支合并冲突,把控整体项目质量。

成员二:魏之皓(102401608)

  • 通用工具库与白盒防御校验:负责核心通用纯函数工具库(js/utils.js)的开发与数学算法推导,实现严格的表单校验(近 90 天日历防未来越界、微信号空格与格式校验、中国大陆手机号规范)。
  • 搜索高亮切片与日历时区校验:重构搜索关键词高亮切片算法,先按原始文本定位区间切片再转义包裹 <mark>,彻底根治 HTML 实体转义冲突缺陷;重构日历校验,限定本地 YYYY-MM-DD 格式、拒绝跨时区日期偏移。
  • 图片上传安全与并发防抖锁:实现图片格式、大小(2MB)、非空及异步解码有效性校验,增加表单并发提交防抖锁与同毫秒 ID 递增避让。
  • 日间/夜间双主题系统重构:全面重构主页视觉与交互架构,新增日间清新与夜间深色双主题无缝切换,实现 LocalStorage 记忆自适应,全面优化 WCAG AA 级 4.5:1 对比度标准与移动端多断点布局。
  • 分页算法推导与测试套件:推导实现数字分页切片算法(Utils.paginate)与智能省略号页码生成算法(Utils.generatePageNumbers),编写全套自动化测试用例,搭建零依赖可视化测试运行器,提交 PR 并复盘缺陷。

三、PSP 表格

PSP2.1 Personal Software Process Stages 预计耗时(分钟) 实际耗时(分钟)
Planning 计划 20 25
Estimate 估计这个任务需要多少时间 20 25
Development 开发 40 45
Analysis 需求分析(包括学习新技术) 40 50
Design Spec 生成设计文档 30 40
Design Review 设计复审 20 25
Coding Standard 代码规范(为目前的开发制定合适的规范) 10 15
Design 具体设计 50 60
Coding 具体编码 180 235
Code Review 代码复审 30 35
Test 测试(自我测试,修改代码,提交修改) 60 75
Reporting 报告 40 50
Test Report 测试报告 20 30
Size Measurement 计算工作量 10 10
Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 20 30
合计 580 750

说明:实际开发耗时略多于预计时间(总偏差约 24%),主要超时集中在具体编码与测试阶段,原因是福州大学 7 大校区专属筛选、双主题色彩对比度适配、6 条/页数字分页器算法推导以及浏览器静态脚本缓存击穿等边界细节的打磨。


四、项目简介与需求分析

校园失物信息通常散落在班级群、年级大群、表白墙和各类社交平台中,消息极易被刷屏覆盖,既不便于按物品类别、校区地点检索,失物找回后也很难及时结贴,导致失主与捡拾者陷入“寻找难、联系难、重复询问多”的困境。

本项目面向福州大学全域校园生活场景,提供一个开箱即用、免安装免配置的纯前端失物信息共享平台:

  1. 多校区精准分流:全面覆盖旗山校区、铜盘校区、怡山校区、泉港校区、晋江校区、集美校区、鼓浪屿校区等 7 大校区;
  2. 闭环业务流转:支持信息发布、多维度复合检索、详情查看与一键联系,物品物归原主后可由发布者一键标记为“已解决”,自动沉底归档;
  3. 绿色免部署高可用:采用浏览器 localStorage 进行本地持久化闭环,纯静态页面无任何服务器依赖,双击 index.html 即可在现代浏览器中秒级呈现。

五、设计与实现思路

5.1 页面系统架构与组件划分

页面采用模块化响应式单页架构,整体划分为五大核心区域:

  1. 顶部导航区:展示平台标识、日间/夜间主题切换按钮、“浏览信息”、“我的发布”和“+ 发布信息”快捷操作;
  2. 校区地标胶囊栏:置顶展示福州大学 7 大校区快捷切换按钮,支持“全部校区”与单一校区的一键沉浸式切换,具备状态记忆;
  3. 最新线索置顶专区:动态自适应展示当前校区与类型下最新的 1~3 条进行中失物/招领,发布新贴后免刷新瞬时呈现在第一位;
  4. 多维搜索与过滤区:包含关键词搜索框、寻物/招领 Tab、分类下拉、功能大区下拉及状态筛选;
  5. 规整卡片网格与数字分页器:采用 2 行 × 3 列的规整响应式卡片流;底部配备经典数字分页器,无论数据量如何膨胀,始终保持每页 6 条,翻页时平滑对齐视口起始位置。

5.2 系统核心数据流图

用户交互 (校区切换 / 搜索输入 / 分页点击 / 发布提交)
   │
   ▼
index.html (DOM 事件总线入口)
   │
   ▼
js/home.js & js/app.js (组织页面全局状态树、弹窗流转、分页状态机与视图重绘)
   │
   ├──► js/utils.js (纯函数工具库:白盒校验、7大校区多维过滤、加权排序、XSS切片高亮、数字分页切片与智能省略号算法)
   │
   └──► js/data.js (LocalStorage 持久化层:读写缓存、版本迁移清洗、数据隔离、增删改查闭环)
   │
   ▼
DOM 视图更新 (2x3 卡片流、最新线索免刷新回显、数字分页栏、Toast 提示、日夜双主题样式)

5.3 关键代码片段解析

片段 1:福州大学 7 大校区专属隔离与多条件复合过滤 (js/utils.js)

在实际校园使用中,不同校区学生需要跨越“校区 + 类型 + 分类 + 地点大区”进行交集检索:

filterItems(items, query = {}) {
  if (!Array.isArray(items)) return [];

  const {
    keyword = '',
    type = 'all',       // lost / found / all
    category = 'all',   // 物品分类
    campus = 'all',     // 福州大学 7 大校区
    location = 'all',   // 功能大区(食堂/图书馆/教学区等)
    status = 'all'      // open / solved / all
  } = query;

  const kw = keyword.trim().toLowerCase();

  return items.filter(item => {
    // 1. 类型、分类与状态过滤
    if (type !== 'all' && item.type !== type) return false;
    if (category !== 'all' && item.category !== category) return false;
    if (status !== 'all' && item.status !== status) return false;

    // 2. 福州大学 7 大校区排他隔离(兼容旧数据从地点中识别校区)
    if (campus && campus !== 'all') {
      const itemCampus = item.campus || this.detectCampus(item);
      if (itemCampus !== campus && !String(item.location || '').includes(campus)) {
        return false;
      }
    }

    // 3. 功能大区包含判断
    if (location !== 'all' && !item.location.includes(location)) return false;

    // 4. 多字段智能模糊检索(支持精准、简称联想与子序列匹配)
    if (kw) {
      const fields = [item.title, item.desc, item.location, item.category, item.campus || ''];
      if (!fields.some(field => this.matchKeyword(field, kw))) return false;
    }

    return true;
  });
}

代码价值与设计思想:
采用短路判定逻辑,将校区物理维度与业务分类深度融合,内置 detectCampus 兼容历史缓存数据,保障检索兼具精准性与鲁棒性。

片段 2:状态两级优先级分层与相关度评分排序 (js/utils.js)

解决“已解决”的旧记录与“寻找中”的新记录随意交错穿插、干扰师生视线的问题:

// 在过滤出的结果集合上执行两级复合排序
return filtered.slice().sort((a, b) => {
  // 第一优先级:状态分层(进行中 open 优先置顶,已解决 solved 沉底归档)
  const solvedA = a.status === 'solved' ? 1 : 0;
  const solvedB = b.status === 'solved' ? 1 : 0;
  if (solvedA !== solvedB) return solvedA - solvedB;

  // 第二优先级:在相同状态分组内,根据搜索加权得分降序排列
  if (kw) {
    const scoreA = this.calculateRelevance(a, kw);
    const scoreB = this.calculateRelevance(b, kw);
    if (scoreA !== scoreB) return scoreB - scoreA;
  }

  // 默认兜底按录入时间倒序排列(最新的在最前)
  const timeA = Number(a.timestamp) || new Date(a.date).getTime() || 0;
  const timeB = Number(b.timestamp) || new Date(b.date).getTime() || 0;
  return timeB - timeA;
});

代码价值与设计思想:
既确保了活跃事务始终占据黄金视觉焦点,又保留了历史结贴记录供师生查阅,且在搜索模式下完美兼顾商业级搜索引擎的权重排序。

片段 3:每页固定 6 条数字分页切片与智能省略号算法 (js/utils.js)

针对物品不断增多引发单页无限纵向拉长、破坏浏览节奏的问题,抽离纯数学分页函数:

/**
 * 物品列表数字分页切片(固定每页 6 条,2行 x 3列)
 */
paginate(items, page = 1, pageSize = 6) {
  if (!Array.isArray(items)) return [];
  const size = Math.max(1, pageSize);
  const totalPages = Math.max(1, Math.ceil(items.length / size));
  // 越界保护:输入负数截断为 1,超大页码截断为最后一页
  const validPage = Math.max(1, Math.min(page, totalPages));
  const startIndex = (validPage - 1) * size;
  return items.slice(startIndex, startIndex + size);
},

/**
 * 生成带智能省略号的页码序列(<=7页平铺,>7页首尾固定+动态滑动窗口)
 */
generatePageNumbers(currentPage, totalPages) {
  const total = Math.max(1, totalPages);
  const cur = Math.max(1, Math.min(currentPage, total));
  const pages = [];

  if (total <= 7) {
    for (let i = 1; i <= total; i++) pages.push(i);
  } else {
    pages.push(1);
    if (cur > 3) pages.push('...');
    const start = Math.max(2, cur - 1);
    const end = Math.min(total - 1, cur + 1);
    for (let i = start; i <= end; i++) {
      if (!pages.includes(i)) pages.push(i);
    }
    if (cur < total - 2) pages.push('...');
    if (!pages.includes(total)) pages.push(total);
  }
  return pages;
}

代码价值与设计思想:
纯函数设计,不依赖任何 DOM 环境。算法与商业级组件库(如 Element Plus / Ant Design)完全对齐,并与视图层 scrollIntoView({ behavior: 'smooth' }) 联动,实现丝滑翻页对齐。


六、项目特色设计(附加特点)

作业评分标准明确规定:“附加特点【10分】,有两个或以上较好特点的,可给满分”。我们深入挖掘福州大学多校区办学特点及用户真实体验,打造了 8 项高价值附加特色:

1. 福州大学全域 7 大校区专属切换与“地标胶囊”联动

  • 创意独到之处与意义:福州大学办学地域广阔,跨越福州、泉州、厦门三地,包含 7 大校区。铜盘校区的学生不希望在满屏旗山校区的帖子中费力寻物。
  • 实现思路:在顶部设计“校区地标胶囊”,支持全部校区与单一校区一键切换,具备 LocalStorage 记忆;发布与编辑时严格校验并绑定校区。
  • 成果展示:点击“铜盘校区”,页面瞬间过滤出该校区物品,标题自动自适应为“最新线索 · 铜盘校区”。

2. 每页固定 6 条(2行 × 3列)经典数字分页器与平滑视口对齐

  • 创意独到之处与意义:传统单页无限加载(Infinite Scroll)会导致 DOM 节点无限暴增、页面卡顿,且用户无法定位历史页码。
  • 实现思路:固定每页 6 条卡片,形成工整的 2x3 网格;动态计算总页数并生成智能省略号页码;点击翻页时自动平滑滚动至列表顶端;少于 6 条时分页栏平滑隐蔽。
  • 成果展示:翻页过程井然有序,页面标题旁实时呈现“正在展示第 1-6 条(共 9 条相关失物招领)”。

3. 日间 / 夜间双主题沉浸自适应与 WCAG AA 级对比度优化

  • 创意独到之处与意义:满足学生在白天户外阳光直射与夜间熄灯躺在床上刷失物招领的不同光线环境需求。
  • 实现思路:采用原生 CSS 变量系统与主题类名,LocalStorage 记忆选择;遵循 WCAG 2.1 AA 级标准,关键文本对比度全部达到或超过 4.5:1。
  • 成果展示:点击右上角太阳/月亮图标,全站瞬时无缝切换日夜主题,表单与过滤状态完整保留。

4. 最新线索免刷新动态置顶渲染与脚本防缓存控制

  • 创意独到之处与意义:失物寻回极重时效。用户发布新信息后,最希望立刻在首页最显眼的位置看到它。
  • 实现思路:打通事件总线,发布成功后清除可能冲突的关键词筛选并触发 app.refresh();在黄金首屏自适应渲染前 1~3 条进行中记录;静态资源引入版本号(?v=20261009_v4)彻底击穿浏览器磁盘旧缓存。
  • 成果展示:点击发布成功后,新发布的失物无需按 F5 刷新网页,瞬间置顶在“最新线索”首位呈现。

5. 校园常用简称 / 拼音模糊检索与命中归因摘要(Snippet)

  • 创意独到之处与意义:大学生习惯搜简称(“高数”搜课本、“饭卡”搜学生卡)。同时,正文命中必须在卡片上有上下文展示,否则失主看不到为何匹配。

  • 核心代码:

    const CAMPUS_ACRONYMS = { '高数': ['高等数学', '数学'], '饭卡': ['校园卡', '学生卡'], '线代': ['线性代数'] };
    // 描述命中智能上下文摘录 (Snippet)
    extractSnippet(text, keyword, maxLen = 38) {
      const idx = text.toLowerCase().indexOf(keyword.toLowerCase());
      if (idx === -1) return this.escapeHtml(text.slice(0, maxLen)) + '...';
      const start = Math.max(0, idx - 10);
      const end = Math.min(text.length, idx + keyword.length + 20);
      const snippet = (start > 0 ? '...' : '') + text.slice(start, end) + (end < text.length ? '...' : '');
      return this.highlightKeyword(snippet, keyword);
    }
    
  • 成果展示:输入“高数”秒级匹配高数课本;输入“蓝色”,卡片下方呈现 💬 描述匹配:...手柄系有<mark>蓝色</mark>小熊挂绳...。

6. 检索关键词实时安全高亮提示(<mark> 防 XSS 注入高亮引擎)

  • 创意独到之处与意义:高亮不能简单粗暴地字符串替换,否则会破坏 &amp; 实体或引发 XSS 脚本执行。
  • 核心代码:先在纯文本定位所有命中的索引切片,普通片段与命中片段分别调用 escapeHtml 独立转义,仅在命中片段包裹 <mark>,彻底杜绝破坏实体。
  • 成果展示:搜索特殊符号 & 或包含 <script> 的文字,高亮渲染精准,绝对安全无乱码。

7. 联系方式“一键复制”与 file:/// 本地跨域安全降级

  • 创意独到之处与意义:避免手动划词复制微信号时多选空格导致搜不到人。
  • 实现思路:优先调用异步 navigator.clipboard.writeText,若在本地 file:/// 协议被浏览器安全拦截,则自动降级为创建隐形 <textarea> 执行 document.execCommand('copy') 并销毁节点,伴随 Toast 提示。

8. 本地离线高清摄影素材与规范化缺省占位设计

  • 创意独到之处与意义:摆脱外网图库网络波动;未传实物图时严格保持空值,展示微质感“暂无实物照片”标准占位,绝不乱套示例图误导失主。

七、目录结构与运行说明

1. 规范的工程目录组织

campus-lost-found/
├── index.html              # 网页唯一入口(Google Chrome 双击即可启动)
├── README.md               # 项目使用说明、目录结构与技术文档
├── css/
│   ├── style.css           # 通用工具样式、弹窗与动画
│   └── home.css            # 首页核心布局、日间/夜间双主题变量、分页器样式
├── js/
│   ├── app.js              # 核心控制器(发布、编辑回显、状态流转、详情弹窗)
│   ├── home.js             # 首页交互驱动(最新线索、校区胶囊、6条/页数字分页渲染)
│   ├── data.js             # LocalStorage 数据层与福大 7 大校区高保真初始数据集
│   └── utils.js            # 纯函数工具库(白盒校验、多维过滤、防XSS高亮、数字分页算法)
├── assets/
│   ├── favicon.svg         # 网站专属矢量 Favicon 图标
│   ├── images/             # 全套本地离线高保真摄影图片素材
│   ├── web_preview.png     # 夜间深色主题运行截图
│   └── web_preview_day.png # 日间清新主题运行截图
├── docs/
│   ├── 开发记录.md          # 详细的技术演进与缺陷排查全景记录
│   └── images/             # 开发验证过程截图
└── test/
    ├── test_runner.html    # 浏览器端自动化单元测试可视化控制台
    ├── unit_tests.js       # 57 项核心单元测试用例
    ├── app_tests.js        # 15 项页面逻辑测试用例
    ├── test_helpers.js     # 纯内存隔离沙箱,彻底避免测试污染真实浏览器数据
    └── run_tests.js        # Node.js 跨平台命令行自动化测试脚本

2. 测试人员运行指南

  1. 获取代码:下载 ZIP 压缩包或在终端执行 git clone https://github.com/King-Zhi/102401609-102401608.git;
  2. 启动程序:无需配置 Node.js、Python 或任何数据库服务,直接双击项目根目录下的 index.html,系统将在 Google Chrome 浏览器中瞬间渲染启动;
  3. 功能验证步骤:
    • 校区切换:点击顶部“铜盘校区”胶囊,观察页面瞬间精准过滤出铜盘校区失物,标题自动更新;
    • 日夜双主题:点击右上角太阳/月亮图标,验证日间浅灰/青绿与夜间深色的平滑切换,刷新后仍记忆主题;
    • 智能搜索与高亮:输入“高数”或“饭卡”,验证常用简称联想与精准高亮;输入“蓝色”,观察卡片下方提取的上下文命中摘要(Snippet);
    • 分页器体验:观察下方 2x3 网格卡片流,点击分页器页码与“下一页”,体验平滑滚动对齐视口及条目区间提示;
    • 发布与置顶:点击右上角“+ 发布信息”,提交一条新数据,验证未传图时的优雅占位,以及提交后“最新线索”免刷新瞬时置顶回显;
    • 标记结贴:点击“我的发布”,点击“标记已解决”,验证状态徽标更新且卡片在列表中自动沉底后移。

八、单元测试

作业要求:“使用单元测试对项目进行测试,并写出至少 5 个测试用例确保你的程序能够正确处理各种情况”。我们系统性构建了 57 项核心单元测试 + 15 项页面逻辑检查(共计 72 项自动化检查),在 Google Chrome 可视化测试运行器与 Node.js 命令行下均实现 100% 全绿通过!

8.1 测试工具与自学单元测试简易教程【4分】

  • 测试工具选型:
    参考 Mocha / Chai 核心思想,自主实现了一个零依赖的微型自动化测试运行器(test/test_runner.html)。它提供 assert.strictEqual、assert.isTrue、assert.isFalse 断言体系,并通过 test/test_helpers.js 注入纯内存隔离沙箱,保证测试绝不污染浏览器中真实的师生失物数据。

  • 简易学习教程(3 步上手 JavaScript 单元测试):

    1. 第一步(设计无副作用纯函数):将核心计算与 DOM 彻底解耦,例如 Utils.paginate(items, page, pageSize) 或 Utils.validateItem(item);

    2. 第二步(构建断言与边界用例):给定一组极限输入参数,使用 assert 比对预期输出与实际输出:

      // 例子:验证超大页码是否被安全截断回退至最后一页
      const result = Utils.paginate(mockList, 999, 6);
      assert.strictEqual(result.length, 3, '超大页码应安全回退至最后一页的实际剩余条数');
      
    3. 第三步(执行引擎与可视化报告):运行测试引擎,若发生抛错则捕获并标红记录为 FAILED,若平稳结束则标绿记录为 PASSED,输出通过率与微秒级耗时。


8.2 部分核心单元测试代码展示【3分】

// 用例 45:子序列模糊匹配精准字符高亮与防 XSS 渲染
{
  name: '测试用例 45: 子序列模糊匹配精准字符高亮与防 XSS 渲染',
  category: '安全防护与高亮测试 (highlightKeyword)',
  testFn(assert) {
    const text = '高等数学 & <上册>';
    const highlighted = Utils.highlightKeyword(text, '高数');
    assert.isTrue(highlighted.includes('<mark class="bg-amber-200 text-amber-900 rounded px-1 font-semibold">高</mark>'), '高字应高亮');
    assert.isTrue(highlighted.includes('<mark class="bg-amber-200 text-amber-900 rounded px-1 font-semibold">数</mark>'), '数字应高亮');
    assert.isTrue(highlighted.includes('&amp;'), '& 符号必须安全转义');
    assert.isTrue(highlighted.includes('&lt;上册&gt;'), '尖括号必须转义');
  }
}

// 用例 51:福州大学 7 大校区专属筛选(精准隔离与过滤测试)
{
  name: '测试用例 51: 福州大学 7 大校区专属筛选(精准隔离与过滤测试)',
  category: '多校区维度测试 (campus filter)',
  testFn(assert) {
    const mockList = [
      { id: 1, title: '旗山饭卡', campus: '旗山校区', status: 'open' },
      { id: 2, title: '铜盘钥匙', campus: '铜盘校区', status: 'open' },
      { id: 3, title: '怡山画板', campus: '怡山校区', status: 'open' }
    ];
    const tongpanResult = Utils.filterItems(mockList, { campus: '铜盘校区' });
    assert.strictEqual(tongpanResult.length, 1, '铜盘校区应精准过滤出 1 条数据');
    assert.strictEqual(tongpanResult[0].id, 2);

    const allResult = Utils.filterItems(mockList, { campus: 'all' });
    assert.strictEqual(allResult.length, 3, '全部校区应返回全量 3 条数据');
  }
}

// 用例 55:列表数字分页切片算法验证(每页6条,多页智能切分)
{
  name: '测试用例 55: 列表数字分页切片算法验证(每页6条,多页智能切分)',
  category: '数字分页器测试 (paginate)',
  testFn(assert) {
    const mockList = Array.from({ length: 15 }, (_, i) => ({ id: i + 1, title: `物品${i + 1}` }));
    // 第 1 页应取前 6 条
    const page1 = Utils.paginate(mockList, 1, 6);
    assert.strictEqual(page1.length, 6, '第1页应包含6条数据');
    assert.strictEqual(page1[0].id, 1);
    // 第 3 页应取剩余 3 条 (第13~15条)
    const page3 = Utils.paginate(mockList, 3, 6);
    assert.strictEqual(page3.length, 3, '第3页应包含最后3条数据');
    assert.strictEqual(page3[0].id, 13);
  }
}

// 用例 57:智能省略号页码生成逻辑(<=7页平铺,>7页动态窗口)
{
  name: '测试用例 57: 省略号智能页码生成逻辑',
  category: '数字分页器测试 (generatePageNumbers)',
  testFn(assert) {
    const pages5 = Utils.generatePageNumbers(1, 5);
    assert.strictEqual(JSON.stringify(pages5), JSON.stringify([1, 2, 3, 4, 5]), '5页时应完整平铺');

    const pages10Mid = Utils.generatePageNumbers(5, 10);
    assert.strictEqual(JSON.stringify(pages10Mid), JSON.stringify([1, '...', 4, 5, 6, '...', 10]), '居中时两侧均应有省略号');
  }
}

8.3 测试数据设计思路与“如何考虑测试人员的刁难”【3分】

为从容应对测试人员可能的极限“刁难”,我们在设计测试数据集时覆盖了五大极端维度:

  1. 边界值与非法字符刁难:标题长度下限(1字拦截、2字通过)、上限(40字通过、41字拦截);微信号带空格拦截(如 a b c);发生日期不存在拦截(平年 2026-02-29 拦截)、未来日期拦截、近 90 天外陈旧日期拦截。
  2. 恶意脚本 XSS 注入刁难:故意输入 <script>alert('xss')</script>、<img src=x onerror=alert(1)>,高亮算法在拆解切片前后强制 escapeHtml,绝不输出可执行标签。
  3. 极端存储与并发提交刁难:在 QuotaExceededError 存储已满下发布,系统优雅报错并保留表单输入,绝不卡死;同一毫秒内双击提交,并发锁阻断重入,ID 递增避让。
  4. 多校区与分页越界刁难:输入非法校区名称安全拦截;分页传入负数页码或超大页码,受控截断至第一页或末页;空数组切片安全输出,不抛出异常。
  5. 脏数据容错与状态机保护刁难:非本人记录禁止编辑、结贴;重复点击结贴保持首次完成时间戳;破坏性 JSON 数据具备容错自愈能力。

8.4 自动化测试实测结果

在项目根目录下执行命令行测试:

node test/run_tests.js

测试执行结果:

57/57 PASSED, 0 FAILED
15/15 application checks passed

在 Google Chrome 打开 test/test_runner.html,全套 57 项核心单元测试全部通过(57/57 PASS),执行仅耗时 14.8ms!


九、主要功能展示截图与测试材料

1. 夜间深色主题首页(总览、地标胶囊、最新线索与卡片流)

image

首页展示平台品牌标识、日间/夜间模式切换胶囊、福州大学 7 大校区地标胶囊、最新线索置顶专区、多维组合检索以及规整的失物卡片流。

2. 正常文字搜索与关键词安全高亮

image

输入关键词“耳机”后,系统秒级筛选出对应的 AirPods 招领记录,并在卡片标题中以黄色高亮背景醒目提示匹配字符。

3. 物品详情弹窗与一键复制联系方式

image

点击卡片弹出高保真详情模态框,完整展示实物高清摄影、拾获校区、具体地点、发生日期、生命周期状态、详细描述及联系人信息,点击“一键复制”可直接将微信号/手机号安全写入剪贴板。

4. 规范化信息发布表单与白盒校验

image

发布表单支持选择 7 大校区、输入物品属性、近 90 天日历选择、上传不超过 2MB 的实物照片并支持实时解码预览,提交前执行全字段白盒校验。


十、GitHub 代码签入记录与协作说明

团队严格遵循规范的 Git 工作流(Fork + Pull Request)与 Conventional Commits 规范进行协作:

1. 团队 Pull Request 协作与合流记录

魏之皓在 Fork 仓库开发并通过 PR 提交,王智钦在主仓库进行严格 Code Review 并裁决合并:

PR 编号 分支来源 目标分支 对应合并 Commit 审查重点与核心工作
PR #1 wizneko/main main 8fd31c2 项目文档与规范初始化走查,统一双方协作规范
PR #3 wizneko/posts-save-fix main 8fca877 走查“我的发布”编辑回显、修改保存、存储失败保留输入与空列表防误恢复;在合流时针对双方同时修改 js/data.js 产生的冲突,通过 b265936 完成冲突裁决并引入内存测试沙箱
PR #4 wizneko/search-date-fix main ac55aa2 走查搜索高亮切片算法(彻底解决 & 与实体破坏问题)及本地 YYYY-MM-DD 严格日历校验(解决跨时区偏移与未来日期)
PR #5 wizneko/upload-submit-fix main a0e1d6b 走查实物图片格式/大小(2MB)校验、异步解码尺寸有效性及表单并发提交防抖锁(同一毫秒 ID 递增避让)
PR #7 wizneko/homepage-theme-update main a82884f 走查日间清新与夜间深色双主题切换系统、LocalStorage 记忆、WCAG AA 级 4.5:1 对比度标准及移动端布局适配

image

图:GitHub Pull Request 列表,清晰记录了各功能分支的代码走查与合流痕迹。


2. GitHub 提交历史截图与最新 Commit 记录

主仓库(King-Zhi)完整记录了功能演进全过程:

2544fab feat: implement 6-items-per-page pagination with smooth scrolling and responsive controls (57/57 tests pass)
9433a1f fix(home): ensure latest clues section dynamically displays without page reload and bust script cache
490a43f fix(home): retain latest clues after publishing and support type switching
9f266c5 fix(home): allow latest clues to display per-campus without disappearing
c3517c5 docs: correct member name to 王智钦 across README and development log
84d88a5 docs: clean up defensive notes and update development log for blog synthesis
23f152d docs: update README, development log and add .gitignore for multi-campus support and test suite expansion
b6dc50d feat: 将校区选择移至顶部地标定位栏作为全局切换胶囊,简化搜索栏
4805f9e feat: 支持福州大学全7大校区精细化筛选与发布绑定 (测试用例扩充至54项全绿)
61aaddb style: remove redundant footer action buttons and center brand note

GitHub 实际提交历史截图:

image

图:2026 年 10 月 9 日提交记录:涵盖 7 大校区地标胶囊、最新线索免刷新置顶渲染、脚本防缓存与 6 条/页数字分页器。

image

图:2026 年 10 月 8 日提交记录:涵盖日间/夜间双主题重构、搜索高亮 Snippet 摘要、状态分层排序及 PR #7 合并。

image

图:2026 年 10 月 7 日提交记录:涵盖数据修复、图片处理安全、测试隔离沙箱及主分支冲突裁决。


十一、遇到的代码模块异常或结对困难及解决方法

在整个结对攻关过程中,我们记录并攻克了 6 大典型技术难题:

1. 搜索高亮与 HTML 实体转义冲突缺陷

  • 问题表现:初版实现采用了“先将文本做 HTML 实体转义(如把 & 转为 &amp;),再用正则替换高亮”的逻辑。当搜索 & 时,高亮把 &amp; 里的 & 替换成了 <mark>&</mark>amp;,直接破坏了实体结构;搜索 amp 时反而错误高亮。
  • 做过的尝试:试图在正则替换时排除 &[a-z]+; 实体,但逻辑臃肿且易漏特例。
  • 解决方案:在分支 search-date-fix(PR #4,提交 797318f)中重构高亮算法机制:先在原始纯文本中定位所有命中的索引区间,将文本切分为“普通片段”与“命中片段”,随后分别独立转义,最后仅在命中片段包裹 <mark>。
  • 收获:彻底杜绝了 HTML 实体被破坏和恶意脚本执行的双重隐患。

2. 未上传实物照片时的缺省展示规范与预置数据隔离

  • 问题表现:实物照片为选填项。早期逻辑在表单处理时对无图记录采用了分类默认图回退,导致未选图的“手机”意外显示成“AirPods”;且数据升级清洗时缺乏隔离,导致预设示例物品的图片被误清空。
  • 解决方案:明确实物图片为选填项,未选图时数据层严格保持 img = '';统一在卡片和详情弹窗设计“暂无实物照片”标准空状态;通过预设 ID 范围与编辑状态双重判定,既保护预设示例图,又规范自建记录(提交 6d8128a, 08d7a18, 68580ac)。
  • 收获:深刻理解了演示数据与用户自建数据分层隔离的重要性,确立了空状态(Empty State)的规范设计。

3. 本地文件协议 (file:///) 下剪贴板写入权限受限与优雅降级

  • 问题表现:在 Google Chrome 本地以 file:/// 协议双击打开网页时,浏览器会对异步 navigator.clipboard.writeText 抛出 NotAllowedError 权限拒绝异常。
  • 解决方案:构建双重保障机制:当捕获到剪贴板 API 拒绝时,平滑降级为在 DOM 中动态创建隐藏 <textarea>,写入内容后触发 document.execCommand('copy') 并销毁节点(提交 9046a81)。
  • 收获:掌握了跨浏览器、跨安全上下文的前端优雅降级模式。

4. 结对多分支合流时的冲突消除与测试沙箱隔离

  • 问题表现:王智钦在主分支重构本地离线高清摄影素材与版本升级,魏之皓在 PR #3 分支增加“我的发布”编辑与保存校验,双方同时修改 js/data.js 导致严重合并冲突;且本地测试频繁污染浏览器实际数据。
  • 解决方案:两人通过语音会议进行 Code Review,逐行比对冲突,确立“保留离线素材 + 融合编辑功能与存储校验”的合流策略(提交 b265936);并在 test/test_helpers.js 中构建纯内存沙箱(提交 73fde62),隔离测试数据。
  • 收获:深刻体会了结对编程中沟通与代码审查的关键价值,掌握了 Git 冲突裁决最佳实践。

5. 最新线索发布后因校区过滤死板与浏览器脚本缓存导致意外消失

  • 问题表现:发布新信息后,顶部“最新线索”专区突然消失,必须按 F5 刷新才出现;有时刚发布的物品无法即刻回显。
  • 根因剖析:早期最新线索提取逻辑写死了 category: 'all' 和 campus: 'all',在当前页面存在校区选择时发生过滤冲突;且发布成功后未重新触发首页渲染总线,加上浏览器磁盘强缓存旧脚本。
  • 解决方案:重构最新线索提取算法,动态感知当前进行中的活跃记录(提交 9f266c5);打通事件总线,发布成功后重置冲突筛选并调用 app.refresh()(提交 490a43f);在 HTML 中引入静态资源防缓存版本号(?v=20261009_v4,提交 9433a1f)。
  • 收获:掌握了单页面应用中局部状态同步与静态资源缓存控制的工程技巧。

6. 物品增多无限向下排布引发的渲染卡顿与视觉疲劳(分页器重构)

  • 问题表现:随着测试数据和发布物品持续累积,单页列表无限纵向拉长,失物翻到下方后想回到顶部筛选非常吃力,破坏了浏览节奏。
  • 解决方案:重构引入经典的每页固定 6 条(2行 × 3列)数字分页器(提交 2544fab)。纯函数实现数组切片(Utils.paginate)与智能省略号算法(Utils.generatePageNumbers);少于 6 条时分页栏平滑隐蔽;翻页时配合平滑滚动,并联动显示条目范围提示。
  • 收获:认识到界面布局与浏览节奏(Rhythm)对用户体验的决定性作用,确立了规整分页的标准交互规范。

十二、队友评价

魏之皓同学治学态度极其严谨,工程逻辑非常扎实!在通用纯函数工具库的白盒校验算法(如近 90 天跨时区日历边界、微信号空格与格式防注入)与分页数学算法推导上非常细致;在自动化单元测试的设计上敏锐度极高,自主搭建了零依赖的可视化测试运行器并贡献了大量的边界测试用例;在 GitHub PR 代码审查中认真负责,提交的 PR 质量极高;由他主导重构的日间/夜间双主题系统视觉品质优异、色彩对比度达标,大幅提升了系统的体验档次。


十三、个人总结

这次结对作业对我而言是一次非常完整且深刻的软件工程全生命周期实战体验。从需求分析、架构设计,到模块解耦、冲突裁决,再到自动化测试与防刁难设计,我深刻体会到了“让一个页面跑起来很容易,但要交付一个鲁棒、优雅、工业级可用且经得起极限考验的系统却需要极高的专业素养”。

在这次实战中,有三点体会尤为深刻:

  1. 纯函数的威力与可测试性:我们将多校区复合检索、加权排序、输入校验、分页切片与智能省略号算法全部抽离为无 DOM 副作用的纯函数。正是因为工具层的高度解耦,使得我们能够迅速编写并维护覆盖 57 项核心单元测试的自动化测试套件,在双端(浏览器仪表盘 / Node.js CLI)均实现秒级 100% 全绿通过。
  2. 细节决定体验的上限:无论是为未传图设计的微质感“暂无实物照片”占位,还是解决物品无限增多后的 6 条/页数字分页器,亦或是击穿浏览器缓存保证最新线索免刷新置顶渲染,这些真正贴近用户直觉的细节,才是一个好产品与粗糙原型之间的本质区别。
  3. 结对协同与代码审查的价值:两个人结对编程不是简单的代码分摊,而是思维的相互纠偏与互补。魏之皓同学严谨的边界意识帮我们拦截了大量潜在的格式漏洞与时区偏移缺陷,而我在架构推进与痛点攻坚上的果断决策保障了项目的敏捷交付。这次结对编程不仅收获了一份高分作业,更收获了珍贵的工程协作经验与团队信任!
posted @ 2026-10-10 22:41  zhiking  阅读(5)  评论(0)    收藏  举报