软件工程结对作业:项目实现
软件工程实验:校园失物招领系统开发博客
一、相关链接
- 结对同学博客:https://www.cnblogs.com/QGNB/p/23242158
- 本次作业博客链接:https://www.cnblogs.com/dingweitong/p/23227071
- GitHub项目仓库地址:https://github.com/dingweitong2367/102402123-052404147
二、具体分工
| 成员学号 | 负责工作内容 |
|---|---|
| 102402123 | 后端逻辑开发、数据库设计与实现、接口编写、GitHub仓库维护、单元测试编写 |
| 052404147 | 前端页面开发、CSS样式设计、页面交互逻辑、实验文档编写、测试与体验优化 |
| 共同完成 | 需求分析、功能架构设计、前后端代码联调、问题排查与文档完善 |
三、PSP表格
| PSP阶段 | 工作内容 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划阶段 | 30 | 25 |
| · Estimate | 预估任务规模与时间分配 | 15 | 10 |
| Development | 开发阶段 | 360 | 420 |
| · Analysis | 需求分析与核心功能梳理 | 40 | 35 |
| · Design | 系统架构设计与数据库表设计 | 50 | 60 |
| · Coding | 前后端代码编写 | 200 | 240 |
| · Code Review | 代码审查与逻辑优化 | 30 | 25 |
| · Test | 功能测试与问题调试 | 40 | 60 |
| Release | 发布与文档阶段 | 60 | 50 |
| · Blog | 撰写实验博客 | 40 | 35 |
| · Commit | 代码提交与仓库整理 | 20 | 15 |
| 合计 | 450 | 495 |
四、解题思路描述与设计实现说明
1. 代码实现思路
本项目采用 B/S 架构,基于 Python Flask 轻量 Web 框架开发,整体遵循 MVC 设计模式:
- Model 层:使用 SQLite 文件型数据库,设计用户表、物品表、通知表、搜索历史表四张核心业务表,封装数据库连接与增删改查通用操作。
- View 层:使用 Jinja2 模板引擎,通过
base.html作为公共基础模板统一导航栏与全局样式,各业务页面继承模板填充内容,配合 CSS 实现移动端优先的响应式布局。 - Controller 层:通过 Flask 路由函数处理前端请求,完成物品发布、条件查询、状态修改、通知生成等业务逻辑,返回渲染后的页面或执行重定向。
整体开发流程:梳理核心功能点 → 设计数据库表结构 → 搭建后端基础框架与路由 → 逐个开发前端页面 → 联调前后端数据交互 → 优化交互细节与异常处理。
2. 关键实现的数据流图
核心业务数据流转逻辑:
- 用户访问首页 → 后端查询物品表最新数据 → 渲染首页列表返回浏览器
- 用户提交发布表单 → 后端接收并校验参数 → 写入物品表 → 生成发布成功通知 → 跳转成功页面
- 用户查看物品详情 → 后端查询对应物品信息 → 对联系方式做脱敏处理 → 渲染详情页
- 用户修改物品状态 → 后端更新物品表状态字段 → 生成状态更新通知 → 返回成功提示
3. 重要代码片段与解释
(1)数据库连接管理
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
解释:借助 Flask 的 g 对象管理数据库连接,每个请求复用同一个连接,请求结束自动关闭连接,避免频繁创建销毁连接的开销,同时保证线程安全。
(2)物品发布路由逻辑
@app.route('/publish', methods=['GET', 'POST'])
def publish():
if request.method == 'POST':
title = request.form.get('title')
item_type = request.form.get('item_type')
category = request.form.get('category')
location = request.form.get('location')
time = request.form.get('time')
description = request.form.get('description')
phone = request.form.get('phone')
wechat = request.form.get('wechat')
qq = request.form.get('qq')
publish_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
db = get_db()
db.execute('''INSERT INTO items
(title, item_type, category, location, time, description,
status, user_id, publish_time, phone, wechat, qq)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)''',
(title, item_type, category, location, time, description,
'进行中', CURRENT_USER['id'], publish_time, phone, wechat, qq))
db.commit()
return redirect(url_for('publish_success'))
return render_template('publish.html')
解释:同时支持 GET 和 POST 请求,GET 时渲染发布页面,POST 时接收表单数据写入数据库,之后重定向到成功页,遵循 Post/Redirect/Get 模式,避免表单重复提交。
(3)联系方式脱敏处理
def mask_phone(phone):
if not phone or len(phone) < 7:
return phone
return phone[:3] + '****' + phone[-4:]
解释:对手机号做中间四位脱敏处理,在列表和详情页展示时保护用户隐私,仅在联系认领页面展示完整号码,兼顾信息公开与个人隐私。
五、附加特点设计与展示
1. 设计的创意独到之处与意义
- 移动端优先的响应式设计:校园场景下用户大多通过手机访问失物招领信息,移动端优先的布局保证了手机端的浏览体验,卡片式 UI 信息清晰,触控操作便捷。
- 分级联系方式隐私保护:列表和详情页对联系方式脱敏,仅有意认领的用户进入联系页才可查看完整信息,平衡了信息公开与个人隐私保护。
- 完整的物品状态流转机制:支持进行中、已归还、暂停展示三种状态,符合真实失物招领的业务流程,信息状态清晰,避免无效联系。
2. 实现思路
- 响应式布局:通过 CSS 媒体查询,针对不同屏幕宽度调整卡片尺寸、导航布局,小屏设备自动适配单列布局。
- 隐私分级:后端模板渲染时,详情页的手机号调用脱敏函数,联系页直接返回完整数据,通过不同路由控制展示级别。
- 状态管理:数据库中设置 status 字段,修改状态时更新字段值,所有列表查询同步返回状态标签,全局状态统一。
3. 重要代码片段与解释
@media (max-width: 768px) {
.item-card {
width: 100%;
margin: 10px 0;
}
.nav-bottom {
position: fixed;
bottom: 0;
width: 100%;
}
}
解释:通过媒体查询实现移动端适配,屏幕宽度小于 768px 时,卡片占满屏幕宽度,底部导航固定在屏幕底部,适配手机触控操作习惯。
4. 实现成果展示
- 首页:
![首页]()
- 发布页:
![发布页]()
- 我的发布页:
![我的发布页]()
- 消息页:
![消息页]()
-详细消息页:
![详细消息页]()
-搜索页:
![搜索页]()
-特殊类别页:



六、目录说明和使用说明
1. 目录组织说明
项目采用标准 Flask 项目结构,按功能职责划分目录层级,清晰易维护:
campus_lost_found/
├── app.py # 后端核心程序:路由逻辑、数据库操作、业务处理
├── lost_found.db # 数据库文件:首次运行自动生成,存储所有业务数据
├── static/ # 静态资源目录
│ └── css/
│ └── style.css # 全局样式文件:页面布局、配色、组件样式
└── templates/ # 页面模板目录
├── base.html # 公共基础模板:统一顶部、底部导航
├── index.html # 首页:分类入口、最新发布列表
├── search.html # 搜索页:关键词搜索、热门标签、历史记录
├── category.html # 分类浏览页:按品类筛选信息
├── detail.html # 物品详情页:完整信息、脱敏联系方式
├── contact.html # 联系发布者页:完整联系方式、防骗提示
├── notifications.html # 消息通知页:四类系统通知
├── all_items.html # 全部信息页:三类筛选的完整列表
├── publish.html # 发布信息页:表单填写与提交
├── publish_success.html # 发布成功页:跳转引导
├── my_posts.html # 我的发布页:个人发布统计与管理
├── edit_status.html # 修改状态页:状态切换
└── edit_success.html # 修改成功页:结果提示
根目录存放核心后端代码与数据库文件,static 目录存放静态资源,templates 目录按页面拆分所有前端模板,每个页面对应独立 HTML 文件,便于分工开发与后续维护。
2. 运行说明
环境准备
- 操作系统:Windows / macOS / Linux 均可
- 软件要求:Python 3.7 及以上版本
- 第三方依赖:Flask Web 框架
运行步骤
- 将项目文件夹下载到本地任意位置
- 打开命令行,进入项目根目录
- 执行命令安装依赖:
pip install flask - 执行命令启动程序:
python app.py
5.当命令行出现 Running on http://127.0.0.1:5000 时,打开浏览器访问以下任意地址:http://localhost:5000 http://127.0.0.1:5000
七、单元测试
1. 测试工具与简易教程
选用工具
选用 Python 标准库自带的 unittest 单元测试框架,无需额外安装,语法简洁清晰,适合小型项目的单元测试。
简易教程
- 新建测试文件
test_app.py,导入 unittest 和待测试的函数 - 创建测试类,继承
unittest.TestCase - 编写测试方法,方法名必须以
test_开头 - 使用
assertEqual、assertTrue等断言方法验证运行结果 - 运行测试:命令行执行
python -m unittest test_app.py
2. 单元测试代码示例
import unittest
from app import mask_phone
class TestApp(unittest.TestCase):
def test_mask_phone_normal(self):
"""测试标准11位手机号脱敏"""
self.assertEqual(mask_phone("13812345678"), "138****5678")
def test_mask_phone_short(self):
"""测试过短手机号不做处理"""
self.assertEqual(mask_phone("12345"), "12345")
def test_mask_phone_empty(self):
"""测试空手机号输入"""
self.assertEqual(mask_phone(""), "")
if __name__ == '__main__':
unittest.main()
测试函数说明:
- `test_mask_phone_normal`:验证常规 11 位手机号的脱敏结果是否正确
- `test_mask_phone_short`:验证长度不足的手机号不会被错误截断
- `test_mask_phone_empty`:验证空输入的边界场景
3. 测试数据设计思路
- 正常场景:覆盖最常用的功能场景,保证主流程逻辑正确
- 边界场景:测试空输入、超长输入、特殊字符等边界情况,保证程序健壮性
- 异常场景:考虑参数缺失、数据异常等情况,保证程序不会崩溃
- 易用性考虑:每个测试用例只验证一个功能点,命名清晰直观,后续测试人员直接执行命令即可查看结果,无需复杂配置。
八、GitHub 的代码签入记录截图


九、遇到的代码模块异常或结对困难及解决方法
问题1:手机号脱敏函数特殊字符输入异常
问题描述:当手机号输入包含字母、特殊符号或长度异常时,脱敏函数直接执行字符串切片会触发索引越界报错,导致页面返回500错误。
做过哪些尝试:仅增加了长度判断,但未考虑非数字字符的异常输入,直接切片仍会出现边界错误。
是否解决:已解决。
解决方法:函数入口先做参数合法性校验,仅当输入为纯数字且长度≥7位时才执行中间脱敏切片,其余场景直接返回原输入字符串。
有何收获:理解了函数健壮性的重要性,核心逻辑执行前必须先做参数校验,覆盖正常、边界、异常三类输入场景。
问题2:SQLite查询结果在模板中无法按字段名访问
问题描述:从数据库查询出的物品列表数据,传到HTML模板中按字段名遍历渲染时,模板引擎提示变量不存在。
做过哪些尝试:反复核对数据表字段名、打印查询结果确认数据存在,修改模板变量写法,均未解决。
是否解决:已解决。
解决方法:SQLite 默认查询返回的是元组格式,不支持键名访问。在建立数据库连接时配置 conn.row_factory = sqlite3.Row,将查询结果转为支持字段名访问的行对象。
有何收获:掌握了 Python SQLite 行工厂的配置方法,理清了数据库查询结果不同格式的区别和适用场景。
问题3:表单提交后首页数据不实时刷新
问题描述:提交失物招领发布表单成功后,跳转回首页时新发布的内容没有立刻显示,需要手动刷新浏览器才能看到。
做过哪些尝试:调整代码执行顺序、添加响应头禁用缓存、测试不同浏览器,问题均未解决。
是否解决:已解决。
解决方法:遵循 Post/Redirect/Get 开发模式,POST请求处理完业务逻辑后,使用 redirect() 重定向到首页路由,而非直接渲染首页模板,避免浏览器缓存旧的页面数据。
有何收获:理解了Web开发中重定向与直接渲染的本质区别,掌握了表单提交后的标准处理流程。
十、评价我的队友
值得学习的地方
- 前端开发细致,对样式和交互细节把控很好,实现的页面美观且操作流畅,非常注重用户体验。
- 文档编写能力强,能把功能说明和操作步骤写得清晰易懂,梳理文档的逻辑很清晰。
- 沟通效率高,遇到问题会及时同步,一起讨论解决方案,结对协作过程很顺畅。
需要改进的地方
- 代码编写时可以适当补充注释,方便后续维护和他人阅读理解。
- 可以多了解一些后端和数据库相关知识,前后端都熟悉的话联调效率会更高。






浙公网安备 33010602011771号