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

项目内容 内容
这个作业属于哪个课程 H202601 软件工程与软件工程实践
这个作业要求在哪里 2026秋软件工程结对作业(第二次之程序实现)
这个作业的目标 完成校园失物招领小程序的开发
学号 102401509-102401519
GitHub 仓库 zhewanyiershinicheng/102401509-102401519

校园失物招领小程序

分工

模块 主要负责人 协作内容
需求分析、原型整理、博客与 PSP 本人 协作者复核功能流程
Python API、SQLite 数据迁移、权限与安全 本人 协作者联调请求和数据
页面布局、交互、图片画廊、搜索筛选 协作者 本人检查移动端显示和异常输入
代码审查、测试用例设计、README 两人共同 按功能提交并互相审查

PSP

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 25
Estimate 估计这个任务需要多少时间 20 20
Development 开发 20 25
Analysis 需求分析(包括学习新技术) 60 75
Design Spec 生成设计文档 60 80
Design Review 设计复审 30 35
Coding Standard 代码规范 30 35
Design 具体设计 90 110
Coding 具体编码 280 360
Code Review 代码复审 60 80
Test 测试、修改代码和提交 130 170
Reporting 报告 120 150
Test Report 测试报告 50 65
Size Measurement 计算工作量 20 25
Postmortem & Process Improvement Plan 事后总结和过程改进 45 55
合计 1035 1310

实际耗时高于预估,主要来自多图压缩、数据库结构迁移、移动端显示回归和独立状态语义调整。过程改进项是:先冻结 JSON 数据契约,再并行开发前后端;数据库迁移先在副本上验证。

1. 问题分析与设计实现

1.1 需求拆解

核心闭环是:

发布信息 -> 浏览或搜索 -> 查看详情 -> 联系发布者 -> 更新状态

其中“状态”不能和“关闭”耦合。寻物信息完成后显示“已找到”,招领信息完成后显示“已归还”;关闭只是停止公开浏览,不应阻止发布者继续切换完成状态。

1.2 总体数据流

浏览器页面
  │  fetch(JSON) + X-Owner-Token
  ▼
REST API(server.py)
  │  字段校验 / 权限校验 / 时间范围校验
  ▼
SQLite(items 表)
  │  row_to_item()
  ▼
内存缓存(store.js)
  │
  ├── 首页卡片列表
  ├── 搜索结果列表
  ├── 详情页图片画廊
  └── 我的发布管理

1.3 数据模型

items 表保存物品类型、名称、类别、发生时间、地点、描述、联系方式、发布者昵称、images JSON 数组、found 和 closed 两个独立布尔字段,以及发布者令牌摘要。

图片在前端压缩为 JPEG data URL,服务端最多接收 6 张,并接受 PNG、JPEG、WebP 的 Base64 data URL。旧版本的单图字段会自动迁移为单元素 images 数组。

1.4 代码实现思路

  1. 用 server.py 提供静态页面和同源 REST API,避免前端直接访问数据库。
  2. 创建、修改数据前统一经过 _validate_item(),把非法类型、超长文本、非法时间和非法图片挡在数据库之外。
  3. store.js 负责请求、缓存和发布者令牌,各页面只处理展示和交互。
  4. utils.js、ui.js 放置跨页面复用的图片压缩、状态文案、卡片和大图组件。
  5. 视图使用 Hash 路由切换,发布和编辑复用同一个表单,降低重复代码。
  6. SQLite 启动迁移保证旧单图、旧状态结构可以平滑升级到当前 images、found、closed 结构。

1.5 关键代码

发布者令牌摘要

服务端不保存原始令牌,只保存 SHA-256 摘要,避免数据库泄露后直接获得管理权限:

def _owner_hash(token):
    return hashlib.sha256(token.encode('utf-8')).hexdigest()

图片数组校验

MAX_IMAGES = 6

def _validate_images(value):
    if not isinstance(value, list):
        raise RequestError(400, 'images 必须是数组')
    if len(value) > MAX_IMAGES:
        raise RequestError(400, 'images 最多包含 6 张图片')
    images = []
    for index, image in enumerate(value, 1):
        if not isinstance(image, str) or not IMAGE_RE.fullmatch(image):
            raise RequestError(400, 'images 第 %d 项必须是受支持的图片 data URL' % index)
        images.append(image)
    return json.dumps(images, ensure_ascii=False, separators=(',', ':'))

组合搜索与时间范围

GET /api/items 同时支持关键词、类型、类别、时间范围和本人信息筛选。时间边界使用 julianday(time) 比较,避免不同 ISO 时间格式的字符串比较产生边界误差:

if time_from:
    where.append('julianday(time) >= julianday(?)')
    params.append(time_from.isoformat().replace('+00:00', 'Z'))
if time_to:
    where.append('julianday(time) <= julianday(?)')
    params.append(time_to.isoformat().replace('+00:00', 'Z'))

状态文案与图片放大

function resolutionLabel(item) {
  return item && item.type === 'found' ? '已归还' : '已找到';
}

详情页将每张图片渲染成横向滚动画廊;点击图片调用 LF.openLightbox(),支持关闭按钮、背景点击、方向键切换和 Esc 关闭。

2. 附加特点设计与展示

2.1 多图、滚动和放大

发布页最多选择 6 张图片,图片会自动压缩,缩略图横向滚动,可单张删除;详情页显示完整图片画廊和当前页码,点击后进入大图模式。它解决了单一图片无法描述外观细节的问题,也适合展示校园卡正反面、钥匙串、设备细节等。

2.2 独立的“找到/归还”和“关闭”

状态拆为 found、closed 两个字段。发布者可以先关闭已经结束的信息,之后仍能切换处理状态;招领信息使用“已归还”,寻物信息使用“已找到”,减少语义歧义和重复询问。

2.3 时间范围筛选

搜索页提供开始日期、结束日期和清除按钮,日期范围可与关键词、类型、类别组合。API 也提供 from、to 参数,方便后续接入更多客户端。

2.4 联系方式快捷操作

详情页支持一键复制联系方式;识别为手机号时显示拨号按钮,减少用户手动输入和误联系。

2.5 数据与安全

  • .env 是唯一配置来源,不依赖进程环境变量。
  • 静态资源使用白名单,数据库和源代码不能通过 Web 下载。
  • 响应启用 CSP、禁止 iframe、禁用 MIME 嗅探。
  • 创建、修改、删除都必须携带发布者令牌,服务端再次校验摘要。

2.6 附加特性代码

状态展示和大图预览都采用轻量、无依赖的原生实现:

function stateBadgesHTML(item) {
  return (item.found === true
      ? badgeHTML('resolved', LF.resolutionLabel(item)) : '') +
    (item.closed === true ? badgeHTML('closed', '已关闭') : '');
}

button.addEventListener('click', function () {
  LF.openLightbox(galleryImages,
    Number(button.getAttribute('data-photo-index')));
});

stateBadgesHTML() 根据信息类型显示“已找到”或“已归还”;大图组件复用在发布缩略图和详情画廊中,支持键盘操作。

2.7 实现成果展示

image

首页集中显示最新信息,搜索页支持关键词、类型、类别和时间范围组合筛选;发布页支持多图;详情页提供图片画廊、联系方式操作和发布者状态管理。

3. 目录说明

校园失物招领小程序/
├── server.py              # Python 标准库 REST API、SQLite 和静态资源服务
├── index.html             # 单页应用入口
├── css/
│   ├── base.css           # 颜色、字体、布局基础
│   ├── components.css     # 按钮、卡片、表单、上传等组件
│   └── pages.css          # 首页、搜索、发布、详情和我的页面
├── js/
│   ├── app.js             # Hash 路由和应用初始化
│   ├── store.js           # REST 请求和内存缓存
│   ├── utils.js           # 图标、格式化、图片压缩等工具
│   ├── ui.js              # 卡片、弹窗、Toast、大图组件
│   └── views/             # home/search/publish/detail/my 页面视图
├── images/                # Logo、空状态和分类占位图
├── .env                   # host、port、database、max_request_bytes
└── README.md              # 项目使用说明和 API 文档

根目录保留 README.md、界面预览 image.png 和本博客文档,不依赖第三方前端框架或构建工具。

4. 测试人员运行说明

  1. 安装 Python 3.10 或更高版本,推荐使用 Google Chrome。

  2. 在命令行进入项目目录:

    cd 校园失物招领小程序
    python server.py
    
  3. 打开 Chrome 访问 http://127.0.0.1:8765/。

  4. 首次启动会自动创建 lostfound.db。配置文件位于 .env,修改后重启服务。

  5. 测试基本流程:发布一条寻物或招领信息 -> 首页/搜索页查找 -> 进入详情 -> 复制或拨打联系方式 -> 在“我的发布”标记完成或关闭。

  6. 测试多图时选择 1 到 6 张 JPG/PNG/WebP 图片,确认缩略图可滚动、删除和点击放大。

  7. 测试时间筛选时分别设置开始日期、结束日期,并验证关键词、类型和类别可以叠加。

5. 单元测试

5.1 测试工具与学习过程

后端使用 Python 标准库 unittest,前端交互使用浏览器手工回归;测试思路参考 Python 官方 unittest 文档和“边界值、等价类、白盒分支覆盖”方法。每个测试准备最小的合法请求,再逐项改变一个条件,观察返回状态、错误信息和数据库结果。当前仓库按项目交付约定不保留 tests/ 文件,以下代码片段用于博客记录测试设计,测试人员可以按片段在临时目录运行。

简易学习教程:

  1. 用 import unittest 引入 Python 自带测试框架,无需安装第三方包。
  2. 继承 unittest.TestCase,每个方法只验证一个明确行为。
  3. 合法结果用 assertEqual、assertTrue,非法输入用 assertRaises。
  4. 在 setUp() 中准备最小测试数据,在 tearDown() 中删除临时数据库。
  5. 使用 python -m unittest <模块名> -v 运行,并阅读每个用例的通过或失败信息。

5.2 测试用例(12 个)

编号 用例 输入/操作 预期结果
T01 创建合法寻物 完整字段、images: [] 返回 201,字段入库
T02 缺少名称 name 为空 返回 400,不写入数据库
T03 类型非法 type: exchange 返回 400
T04 时间非法 time: not-a-date 返回 400
T05 图片数量越界 images 为 7 个合法 data URL 返回 400
T06 图片格式不支持 data:image/gif;base64,... 返回 400
T07 两个状态独立 found: true, closed: false 两个字段分别保存
T08 关闭后仍可切换完成 先关闭,再更新 found 更新成功,公开列表不显示
T09 公开列表过滤关闭项 查询 GET /api/items 不返回 closed: true
T10 无权限修改 使用另一令牌 PUT 返回 403
T11 关键词和类别组合 kw=校园卡&category=校园卡 只返回匹配项
T12 时间范围边界 from=2026-10-09&to=2026-10-09 包含当天开始和结束时间

5.3 测试代码示例

import unittest

class ItemValidationTests(unittest.TestCase):
    def test_valid_item(self):
        item = VALID_ITEM.copy()
        item['images'] = []
        self.assertEqual(_validate_item(item)['name'], '蓝色校园卡')

    def test_reject_missing_name(self):
        item = VALID_ITEM.copy()
        item['name'] = ''
        with self.assertRaises(RequestError):
            _validate_item(item)

    def test_reject_too_many_images(self):
        item = VALID_ITEM.copy()
        item['images'] = [VALID_IMAGE] * 7
        with self.assertRaises(RequestError):
            _validate_item(item)

    def test_found_and_closed_are_independent(self):
        item = _validate_item({**VALID_ITEM, 'found': True, 'closed': True})
        self.assertTrue(item['found'])
        self.assertTrue(item['closed'])

if __name__ == '__main__':
    unittest.main()

前端的关键回归点通过 Chrome DevTools 或手工脚本检查:输入框必填校验、最多 6 张图片、横向滚动、大图关闭方式、时间范围清除、复制联系方式和状态按钮文案。

5.4 测试数据构造

合法数据覆盖寻物/招领两种类型和所有类别;边界数据覆盖空文本、最长文本、过去/未来时间、0/1/6/7 张图片、非 Base64 data URL;权限数据使用两个随机发布者令牌;状态数据分别组合 found、closed 的四种情况。针对测试人员的“刁难”,还考虑了重复点击按钮、连续上传失败、浏览器隐私模式无法写入 localStorage、中文和表情输入、超长请求、非法查询参数、旧单图数据库迁移、关闭后修改状态以及并发修改同一信息等情况。

6. 代码签入记录

协作时由一名同学维护主仓库,另一名同学 fork;每完成一个功能就创建功能分支并发起 Pull Request,另一人完成代码审查后再合并。
提交记录:
image

7. 开发中遇到的问题与解决方法

7.1 数据库状态迁移

问题:早期原型把处理状态保存在单个 status 字段,后续需要独立切换“找到/归还”和“关闭”。
尝试:直接添加字段会破坏旧数据,先用 SQLite 表重建迁移,再把 status 映射到 found、closed。
结果:旧记录、发布者摘要和创建时间均保留,迁移后 API 结构统一。
收获:迁移脚本应先在临时数据库验证,再对正式数据库执行,并在启动时保持幂等。

7.2 多图体积和展示

问题:6 张原图可能超过请求上限,单图布局也无法横向浏览。
尝试:前端使用 Canvas 压缩到 900 像素、JPEG 质量 0.72;后端限制 6 张并校验 data URL;画廊使用 CSS scroll-snap 和独立大图组件。
结果:移动端可流畅滚动,图片可删除和放大。
收获:上传控件、服务端校验和展示组件必须使用同一份数据契约。

7.3 状态语义

问题:“已找到”对招领信息不准确,且关闭状态曾影响完成状态。
尝试:把文案按 type 动态切换,并将 found、closed 作为独立字段。
结果:招领显示“已归还”,寻物显示“已找到”,两个操作互不阻塞。
收获:业务状态不应为了页面筛选而合并,数据库字段应表达真实业务含义。

8. 队友评价

值得学习的地方

  • 能把原型中的视觉流程拆成可测试的 API 和页面状态。
  • 对中文显示、移动端宽度和异常输入比较敏感,能及时提出修改意见。

需要改进的地方

  • 部分功能可以更早拆成小提交,方便通过 commit history 回溯设计决策。
  • 代码审查时关注权限、数据迁移和长期维护,不只满足于页面能打开。

9. 总结

本项目完成了“发布信息—浏览或搜索—查看详情—联系发布者—更新状态”的完整闭环,并增加了多图上传、滚动图片画廊、大图预览、时间范围筛选、复制/拨号联系方式、独立处理状态、SQLite 持久化和基本安全防护。

posted @ 2026-10-09 20:40  丛雨绫刀  阅读(5)  评论(0)    收藏  举报