软件工程现场编程
2026秋软件工程实践 蛋仔派队现场编程总结
| 项目 | 内容 |
|---|---|
| 作业所属课程 | H202601 软件工程与软件工程实践 |
| 作业要求 | 2026秋软工实践团队作业-现场编程 |
| 作业的目标 | 通过 12 人团队协作,在规定时间内完成一个能够实际运行的个人记账 Web 应用。要求团队合理分工、每人独立编写各自负责的功能模块,利用 GitHub 进行版本管理与代码审查,完成软件测试、现场演示及正式版本发布,并记录开发过程中的问题与收获。 |
| 团队名称 | 蛋仔派队 |
| 团队成员名字-学号 | 张诗培-102402145、张家祥-102402144、郑仕廷-102402146、郑宇嘉-102402147、叶腾骏-102402142、周凯-102402148、周山翔-102402149、周子钦-102402150、朱铭浩-102402151、韩尔洋-102402154、崔佳静-102402155、王昊-102402156 |
一、团队分工
1.1 项目介绍
本次现场编程,我们团队选择开发一款轻量级的个人记账应用——蛋仔记账本(Danzai Ledger)。
项目面向个人日常收支管理场景,用户可以记录每一笔收入与支出,按日期、类型、类别查询账单,并通过统计数据和类别图表了解自己的消费结构。
项目采用 Python + FastAPI + HTML/CSS/JavaScript 的技术方案,数据保存在本地 SQLite 文件(backend/data/account_book.db)中,首次启动自动建库建表,不需要单独安装数据库服务,也不依赖任何 npm 打包流程,把开发重点放在业务功能、团队协作与前后端集成上。
最终完成的主要功能包括:
- 新增记账记录:选择收入或支出,填写日期、金额、类别和备注,金额自动由「元」换算为整数「分」存储。
- 记录管理:后端实现记录的查询、新增、删除接口。
- 条件筛选:支持按日期范围、收支类型、类别组合筛选,多个条件之间为「与」关系。
- 收支统计:自动计算收入总额、支出总额、结余,空数据时返回 0 而不是
null。 - 分类统计与图表:按类别汇总金额并降序排行,前端用纯 CSS 条形图展示各类别占比。
- 数据持久化:记录写入本地 SQLite 文件,刷新页面或重启服务后仍可读取。
- Excel 导出:把当前筛选结果导出为 xlsx,含表头样式、冻结首行与金额两位小数格式。
- 异常输入处理:对金额为零、删除不存在的记录、日期区间倒置等情况分别返回 422 / 404 / 400,并给出可读的错误文案。
1.2 团队成员职责分工
为使 12 名成员能够并行开发,我们将项目划分为数据层、业务层、统计层、Web 接口层、前端层五个部分,进一步拆分为 12 个主要模块。其中组长张诗培除负责数据层外,还承担任务划分、接口协调、Pull Request 审查、代码合并与最终集成验收工作。
分工原则:
- 每名成员负责一个主要模块,明确职责边界与所改文件范围。
- 各模块依据统一接口契约开发:字段名、金额单位、错误码在动第一行代码之前先定完。
- 使用个人 GitHub 账号提交本人实际编写的代码,通过 Pull Request 提交功能,由组长检查后合并。
- 集成过程中出现跨模块问题,由相关成员协同排查,不跨人代改代码。
| # | 成员(姓名-学号) | 承担模块 | 主要交付文件 |
|---|---|---|---|
| 1 | 张诗培-102402145 | 项目集成、仓库管理、数据层 | 仓库与分支规范、PR #3/#8/#11/#12 的审查与合并、backend/database/init_db.py、backend/database/connection.py、前后端接口对齐 |
| 2 | 张家祥-102402144 | 项目骨架、前端页面结构、项目文档 | docs/PRD.md、docs/DEVELOPMENT_GUIDE.md、docs/TEAM_TASK_ASSIGNMENT.md、全部目录骨架与函数桩、backend/main.py、frontend/index.html 初版布局 |
| 3 | 周山翔-102402149 | 账单查询与条件筛选 | backend/repositories/query_repository.py、backend/services/query_service.py、backend/tests/test_query_module.py |
| 4 | 韩尔洋-102402154 | 收支记录新增 / 删除 | backend/services/record_service.py、backend/repositories/record_repository.py、backend/api/records.py |
| 5 | 周子钦-102402150 | 收支统计 | backend/services/statistics_service.py、backend/api/statistics.py、docs/MEMBER_E_STATISTICS.md |
| 6 | 朱铭浩-102402151 | 前端界面与交互主力(列表 / 分页 / 删除 / 导出 / 一键启动) | frontend/css/style.css、frontend/css/app.css、frontend/js/records.js、frontend/js/main.js、start.bat、backend/services/export_service.py |
| 7 | 周凯-102402148 | 仪表盘与统计可视化 | frontend/js/statistics.js(概览卡片、类别汇总、CSS 条形图、数据来源标识) |
| 8 | 郑宇嘉-102402147 | 新增记账表单 | frontend/js/records.js 中的表单交互与前端校验部分 |
| 9 | 郑仕廷-102402146 | 历史账单列表 | 账单列表的展示 / 筛选 / 删除原型实现 |
| 10 | 王昊-102402156 | 后端接口测试 | 接口用例清单与缺陷记录 |
| 11 | 崔佳静-102402155 | 前后端联调 | 验收清单、问题复测记录 |
| 12 | 叶腾骏-102402142 | 使用说明与测试 | readme撰写和测试 |
二、程序运行环境
2.1 开发环境
| 项目 | 具体环境 |
|---|---|
| 操作系统 | Windows 11(开发与现场演示环境),后端代码本身跨平台 |
| 编程语言 | Python 3.13(实测 3.13.14 / 3.13.12 均可运行) |
| 后端框架 | FastAPI、uvicorn、Pydantic v2 |
| 前端技术 | HTML5、CSS3、原生 JavaScript(ES2017),无 npm、无打包 |
| 数据存储 | SQLite(Python 标准库 sqlite3,数据文件 backend/data/account_book.db) |
| 导出依赖 | openpyxl(生成 xlsx) |
| 测试方式 | 仓库内自带 backend/tests/test_query_module.py,可用标准库 unittest 风格直接运行 |
| 版本控制 | Git、GitHub(分支 + Pull Request 协作) |
| 浏览器 | Edge / Chrome 最新版(演示使用 Edge) |
| 端口约定 | 后端 127.0.0.1:8000,前端静态服务 5500(CORS 白名单只放行这两个源) |
项目使用 backend/requirements.txt 管理 Python 依赖,仅包含 fastapi、uvicorn、openpyxl 三项。
2.2 项目运行步骤
第一步:克隆 GitHub 仓库。
git clone https://github.com/zsp-c/personal_expense_tracker.git
cd personal_expense_tracker
第二步:创建 Python 虚拟环境。
py -3 -m venv .venv
第三步:安装项目依赖。
.\.venv\Scripts\python.exe -m pip install -r backend\requirements.txt
第四步:启动后端服务。
cd backend
..\.venv\Scripts\python.exe -m uvicorn main:app --host 127.0.0.1 --port 8000
后端启动后会自动建库建表,接口文档地址:
第五步:另开一个终端,在仓库根目录启动前端静态服务。
python -m http.server 5500
注意:静态服务必须起在仓库根目录而不是
frontend/,否则页面里用../img/引用的校徽、背景图会全部 404。
第六步:浏览器访问。
http://localhost:5500/frontend/index.html
即可进入个人记账本界面。
也可以直接双击 start.bat 一键启动。 脚本会依次检查虚拟环境是否存在、fastapi/uvicorn/openpyxl 是否齐备、8000 与 5500 端口是否已被占用(占用则复用,不重复拉进程),然后轮询等待后端真正就绪再自动打开浏览器。
2.3 自动化测试环境
在 backend 目录下执行:
python tests/test_query_module.py
现场集成验收时,自动化测试结果为:
12 passed
测试覆盖查询与筛选模块的正常路径与异常输入,包括:日期范围筛选、类型筛选、类别筛选、组合条件、limit 截断、非法日期格式、日期区间倒置、非法类型、空类别、SQL 注入字符串等场景。
三、软件运行截图和视频
3.1 软件首页
首页集中展示三块内容:收支概览(总收入 / 总支出 / 结余)、新增记账表单、历史账单列表。顶部导航可切换到统计页。
概览卡片上的数字来自 GET /api/stats/summary,新增或删除记录后无需刷新页面,卡片会通过自定义事件自动重算。

3.2 新增收支记录
用户可选择收入或支出类型,并填写日期、金额、类别和备注。类型切换时下方的类别选项随之变化(收入侧为工资、奖金等,支出侧为饮食、交通、购物等)。
提交前前端先做一次校验:金额必须为正数且最多两位小数,日期默认今天且不允许晚于今天,类别必填。校验不通过时按字段标红对应输入框,不必等一次网络往返。

3.3 账单筛选与删除
系统支持以下筛选条件:
- 日期范围(开始日期、结束日期);
- 收入或支出类型;
- 收支类别。
多个条件之间采用「与」关系,只有全部满足的记录才会出现在列表中。列表支持分页加载,每条记录右侧提供删除按钮,删除前有二次确认,删除成功后概览与统计同步刷新。

3.4 收支统计与分类图表
统计页展示当前筛选范围内的收入、支出与结余,并按类别汇总金额、以降序排列,右侧用纯 CSS 条形图展示各类别占比,条形长度按该类别占最大类别的比例计算,不依赖任何外部图表库。
页面左上角有数据来源徽标:绿色「实时数据」表示来自后端接口,橙色「演示数据 · 已自动降级」表示接口不可用时改用本地流水重算。

3.5 异常输入处理
在集成验收时,我们逐条验证了异常输入的实际返回,结果如下:
| 场景 | 请求 | 实际返回 |
|---|---|---|
| 新增收入 5000 元 | POST /api/records/ |
{"id":1,"message":"添加成功"} |
| 新增支出 30 元 / 20 元 | POST /api/records/ |
{"id":2,...}、{"id":3,...} |
| 收支汇总 | GET /api/stats/summary |
{"income":500000,"expense":5000,"balance":495000} |
| 分类统计(支出) | GET /api/stats/category?type=expense |
[{"category":"饮食","total":3000},{"category":"交通","total":2000}] |
| 金额填 0 | POST /api/records/ |
422(Pydantic 在入口拦截) |
| 删除不存在的记录 | DELETE /api/records/999 |
404 {"detail":"记录 id=999 不存在"} |
| 开始日期晚于结束日期 | GET /api/records/?start_date=...&end_date=... |
400 {"detail":"start_date 不能晚于 end_date"} |
| 导出 Excel | GET /api/records/export |
5307 字节 xlsx,响应头 filename*=UTF-8''账单_20261107.xlsx |
3.6 接口文档(Swagger)
后端所有接口通过 FastAPI 自动生成文档,现场演示时可直接在 /docs 页面上发请求,便于助教核对每个接口的实际行为。

3.7 软件演示视频
视频地址:通过网盘分享的文件:20261107_个人记账.mp4
链接: https://pan.baidu.com/s/1Wn1mkg4tdmOiVM3GDkI96Q?pwd=dmck 提取码: dmck
四、软件实现的关键代码
本项目采用分层、模块化的设计方式:数据层负责建表与连接,模型层负责请求体校验,业务层(记录 / 查询 / 统计 / 导出)承载业务规则,路由层把功能暴露为 HTTP 接口,前端按职责拆成 api.js、records.js、statistics.js、main.js 四个文件。
以下截取四处最能体现设计取舍的代码,均摘自项目正式版本。
4.1 建表:约束写在表结构里(数据库层)
文件:backend/database/init_db.py
金额必须大于 0、类型只能是收入或支出——这类规则如果只在 Python 里判断,换个入口(导出、脚本、直接连库)就绕过去了。因此约束同时落在建表语句里,并用 IF NOT EXISTS 保证服务反复重启只建一次表。
def init_database() -> None:
"""创建数据表(幂等,可重复调用)。"""
conn = get_connection()
try:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS records ( -- 幂等:重复启动不会重复建表或报错
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL, -- YYYY-MM-DD,日期字符串可直接比较大小
type TEXT NOT NULL
CHECK(type IN ('income', 'expense')), -- 数据库层兜底,防止写入脏类型
amount INTEGER NOT NULL
CHECK(amount > 0), -- 金额是整数「分」,0 与负数写不进去
category TEXT NOT NULL,
remark TEXT DEFAULT '', -- 备注允许为空字符串,前端显示为「—」
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
"""
)
conn.commit()
finally:
conn.close() # 连接在本函数内关闭,不依赖连接池
实现说明:
建表语句本身承担了一部分数据校验职责。前端校验可能被绕过、脚本可能直接写库,但 CHECK 约束不会。
日期用 TEXT 存 YYYY-MM-DD,是因为该格式的字典序与时间序一致,WHERE date >= ? 直接用字符串比较即可,不需要引入日期类型转换。
4.2 收支汇总统计(统计层)
文件:backend/services/statistics_service.py
如果按「查两次、在 Python 里相减」的写法,空表时 SUM() 返回的是 NULL,前端拿到就是 NaN。我们用 CASE WHEN 把两类金额一次算出来,再用 COALESCE 把空结果压成 0。
def get_summary(start_date=None, end_date=None) -> dict:
"""返回 {"income": int, "expense": int, "balance": int}(单位:分)。"""
where, params = _build_where(start_date, end_date) # 只拼 " WHERE date >= ? AND date <= ?",不拼用户输入
sql = (
"SELECT "
# 一条 SQL 里按 type 分流求和,避免两次查询之间数据被改动导致收支对不上
"COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) AS income, "
"COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) AS expense "
f"FROM records{where}"
)
conn = get_connection()
try:
cur = conn.cursor()
cur.execute(sql, params) # 参数化查询:日期范围走 ? 占位符
row = cur.fetchone()
income, expense = int(row["income"]), int(row["expense"])
finally:
conn.close()
return {"income": income, "expense": expense, "balance": income - expense}
实现说明:
统计逻辑独立于路由,输入只有筛选条件,输出是纯字典,可以脱离 Web 层直接测试。分类统计复用同一个 _build_where(),只是把 GROUP BY category 换成按类别聚合,两个统计口径因此不会出现偏差。
4.3 查询与参数校验(查询层)
文件:backend/services/query_service.py;文末的全局异常处理器在 backend/main.py
查询接口是前端最容易传错参数的地方。我们的原则是:用户能看懂的输入错误 → 400 并说人话;机器传的数量越界 → 静默截断,不打断整页。
def list_records(start_date=None, end_date=None, type=None,
category=None, limit=None, offset=0) -> list[dict]:
if start_date: _validate_date(start_date, "start_date") # 格式不对 -> "应为 YYYY-MM-DD,当前值:xxx"
if end_date: _validate_date(end_date, "end_date")
if start_date and end_date and start_date > end_date:
raise ValueError("start_date 不能晚于 end_date") # 区间颠倒必须报错,否则用户以为「没数据」
if type and type not in VALID_TYPES:
raise ValueError("type 只能是 income 或 expense")
if category is not None and not category.strip():
raise ValueError("category 不能为空字符串")
# limit 越界不报错:前端误传 99999 时截到上限,页面仍能正常渲染
effective_limit = (query_repository.DEFAULT_LIMIT if limit is None
else max(1, min(int(limit), query_repository.MAX_LIMIT)))
return query_repository.query_records(start_date=start_date, end_date=end_date,
type=type, category=category,
limit=effective_limit, offset=offset)
Service 层抛出的 ValueError,由一个全局异常处理器统一翻译成 400,不用在 8 个接口里各写一遍 try/except:
@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
"""将 Service 层的 ValueError 统一转换为 400,错误文案直接透给前端。"""
return JSONResponse(status_code=400, content={"detail": str(exc)})
实现说明:
动态 WHERE 的拼装只拼列名和 ? 占位符,条件值一律通过参数传入,因此类别里写 '; DROP TABLE records; -- 也只会被当作普通字符串匹配,这一点在单元测试中有专门用例。
「什么该报错、什么该兜底」需要提前约定:日期颠倒、类型拼错是调用方的问题,必须明确告知;而 limit 只是数量参数,截断比报错对用户更友好。
4.4 金额换算:元与分只在这两个函数里互转(前端 API 层)
文件:frontend/js/api.js
浮点数做钱一定会出事,所以团队约定的第一条就是:数据库、接口、内部运算全部是整数「分」,只有「用户输入」和「屏幕显示」两个瞬间才碰「元」,而这两个瞬间收敛到 yuanToFen / fenToYuan 两个函数。
/** 元 -> 分:提交前调用。Math.round 兜住 19.99 * 100 = 1998.9999 这类二进制误差 */
function yuanToFen(value) {
return Math.round(Number(value) * 100);
}
/** 分 -> 元:展示前调用,固定两位小数 */
function fenToYuan(value) {
return (Number(value) / 100).toFixed(2);
}
实现说明:
在整个项目里搜 * 100 和 / 100,只能在这两个函数里搜到,其余业务代码一律用分运算。这条约定在写第一行代码之前就定下了,因此后端统计、前端展示、Excel 导出三处的金额口径天然一致,没有出现过「页面 4950、导出 49.5」这类问题。
五、软件的亮点
5.1 亮点一:把金额单位变成一条不可绕过的约定
「元 / 分」不一致是记账类程序最经典的 bug 来源:接口 A 收元、接口 B 收分,最后统计永远对不上。我们的做法是把它变成一条不可绕过的约定:数据库存的、接口传的、Python 算的,全是整数分;只有 yuanToFen() / fenToYuan() 两个函数允许出现「元」。
因此整个仓库里搜 * 100 和 / 100 只能搜到这两处,新人改代码时几乎没有犯错空间。实测新增 5000 元收入与 30 元、20 元支出后,summary 返回 {"income":500000,"expense":5000,"balance":495000},导出 Excel 中显示为 5000.00 / 30.00 / 20.00,三处口径完全一致。
5.2 亮点二:后端不可用时优雅降级,并诚实标注数据来源
现场演示最怕的是「后端没启动 / 端口被占 / 忘开服务」,页面一片空白。统计模块做了三级降级:后端可用 → 走 /api/stats;后端报错或 api.js 未加载 → 用内置的原始流水在本地重算(不是写死汇总数字,所以逻辑与真实路径完全一致)。
同时卡片左上角显示绿色「实时数据」或橙色「演示数据 · 已自动降级」,鼠标悬停可看到降级原因。接口一恢复,无需改代码就自动切回真实数据。 这条设计让我们在演示时敢于直接关掉后端,说明「这不是假的」。
// frontend/js/statistics.js
var message = isDemo
? "演示数据 · 后端统计接口未连接,已自动降级"
: "实时数据 · 来自后端 /api/stats";
5.3 亮点三:一键启动脚本把「环境问题」挡在演示之前
每人本地环境都不一样,最容易浪费时间的不是写代码而是「你那边能跑我这边跑不起来」。start.bat 把四件事做成了一条路径:检查 .venv 是否存在 → 检查 fastapi/uvicorn/openpyxl 是否齐备 → 检测 8000/5500 端口是否已被占用(占用就复用,不重复拉进程)→ 轮询等后端真正就绪再打开浏览器,而不是 timeout 3 赌它启得快。
还有一个反直觉的细节:静态服务必须起在仓库根目录而不是 frontend/,否则页面里的 ../img/校徽.png 全部 404——这个坑也写进了脚本注释里。
六、开发过程中收获较大的事件
6.1 事件一:三个人同时改 records.js,一次整份覆盖带走了别人的逻辑
问题背景:
records.js 是前端改动最频繁的文件,表单、列表、分页、删除都在里面。功能分支合并后,账单列表能显示,但「加载更多」失效了,而该分支自己单独测试是好的。
解决过程:
排查发现 records.js 被两个人各自整份重写后合并,后合并的一方以「我的版本更完整」为由整文件覆盖,把另一方新增的分页状态变量覆盖掉了。团队当场定下文件所有权规则(写在 docs/TEAM_TASK_ASSIGNMENT.md 第四节):一个文件只有一个主负责人,其他人要改必须走小 PR 且只改自己那段。records.js 以功能更完整的一版为基线,另一方保留的分页思路以 patch 形式补回。
收获:
「谁写的文件谁负责」不是官僚,是并行开发的必需品。冲突本身不可怕,可怕的是覆盖式合并——它不产生冲突标记,所以没人会去看 diff。此后我们的约定是:动公共文件之前先在群里说一句,合并前先看对方的 diff 有没有被自己抹掉。
6.2 事件二:start.bat 里的中文全是乱码,以及那个必须放对位置的静态服务
问题背景:
一键启动脚本在作者电脑上正常,同学拉下来双击后满屏 锟斤拷;另外页面打开后所有校徽、背景图 404。
解决过程:
.bat 默认按 GBK 解析,文件被存成 UTF-8 就必然乱码;而把静态服务起在 frontend/ 目录,会让页面里用 ../img/ 引的资源全部越界。解决方式是 .bat 统一用 GBK + CRLF 保存,并加 .gitattributes 强制该文件以 CRLF 检出(否则 Linux 同学一提交就变成 LF);静态服务改为在仓库根目录启动,并把这个前提写进启动说明。
收获:
跨平台的坑往往不在代码里,在文件编码和换行符里。.gitattributes 这种「一次配置、永久生效」的东西,越早知道越好。
七、每个组员的团队编程体验
七、每个组员的团队编程体验
本节由 12 名团队成员结合本人实际开发经历填写,重点说明所承担的任务、遇到的困难、解决方式以及本次团队编程的收获。以下按分工表顺序排列,按各人实际承担模块填写。
7.1 张诗培(组长:项目集成与数据层)
本次团队编程中,我的主要工作其实不是写功能,而是「让别人写的东西能装到一起」:定目录、定接口、定金额单位,负责 backend/database/ 下的建表与连接管理,然后不停地看 PR 的 diff,判断这次合并会不会踩到别人的文件。
最大的体会是约定要在写第一行代码之前定完——我们前期花时间讨论数据库字段和金额单位,后面几个小时几乎没有因为「字段对不上」返工。另外作为组长,最难的不是技术判断而是取舍:有人做了但没合并(PR #4),要在不打击积极性的前提下说清为什么不用,最后我们把分支和 PR 都保留下来,让过程可查。
7.2 张家祥(项目骨架与前端页面结构)
我搭的是空架子:目录、分层、每个模块留 TODO(成员 X) 的桩函数,三份文档(PRD、开发规范、任务分工),以及 frontend/index.html 的初版布局。当时担心「写文档浪费时间」,结果恰恰是这份分工文档在后期几个人抢同一个文件时救了场。
体会:骨架的价值不是代码,是让 12 个人对「东西该长在哪」有同一个答案。
7.3 周山翔(账单查询与条件筛选)
我负责查询模块,最花时间的不是写 SQL,而是想清楚「哪些参数该报错、哪些该兜底」,以及把动态 WHERE 拼装做成全程 ? 参数绑定。
另外补了 12 个单元测试,其中 SQL 注入那条最有用——它证明了即使 category 传 '; DROP TABLE records; --,也只会被当成普通字符串匹配。
7.4 韩尔洋(收支记录新增 / 删除)
我写增删改的 Repository 和 Service。踩得最实的坑是「删除不存在的记录」该返回什么:一开始 rowcount 没判断,接口永远返回成功,前端以为删掉了。
改成用 cursor.rowcount > 0 判断、Service 抛 ValueError、API 层转 404 之后才符合直觉。这件事让我意识到返回值的语义也是接口的一部分。
7.5 周子钦(收支统计)
统计模块看着最简单,其实边界最多:空表、只有收入没有支出、按日期筛选后无数据。SUM() 在没有记录时返回 NULL 而不是 0,前端拿到就是 NaN,所以全部用 COALESCE(...,0) 兜住。
体会:「没有数据」也是一种必须设计的状态,不能等它在前端渲染成 NaN 才想起来。
7.6 朱铭浩(前端界面与交互主力)
我改的文件最多,也最容易被覆盖,所以对「文件所有权」感触最深。
前端这边我做得最用力的是错误提示:金额格式、日期不能选未来、接口失败、删除二次确认,每一种都有对应文案,绝不静默失败。另外 records:changed 这个自定义事件是我比较满意的一个决定,它让列表和统计彻底解耦,统计页可以单独打开也不报错。
7.7 周凯(仪表盘与统计可视化)
我负责概览卡片、类别占比和收支对比条形图。为了让「后端没启动也能演示」,我做了本地降级加数据来源徽标,原则是可以降级,但不能骗人:降级时必须明确告诉用户这是演示数据。
图全部用 CSS 画,没有引外部库,断网也能跑。
7.8 郑宇嘉(新增记账表单)
我负责表单交互:类型切换、金额校验(正则限制两位小数)、日期默认今天且不能选未来、提交时把元转成分。
最纠结的是「保存成功后要不要清空表单」,最后决定只清空金额和备注、保留日期和类别,方便连续记账。这种小决定没人会写进需求文档,但直接影响用起来顺不顺手。
7.9 郑仕廷(历史账单列表原型)
我实现了一版账单列表(展示 / 筛选 / 删除),评审时发现与主实现的思路重叠。虽然 PR 最终关闭没有合并,但我的筛选控件与删除交互被主实现吸收了一部分。
体会:并行开发里「做了但没合并」也是有效工作,前提是把 PR 留下来,让过程可查。
7.10 王昊(后端接口测试)
我主要负责后端接口的测试与缺陷记录。在 /docs 页面上逐条核对了所有接口的正常与异常返回,把「金额为 0 返回 422」「删除不存在返回 404」「日期区间倒置返回 400」这几条固化成接口用例清单。在这个过程中,我深刻体会到了清晰的错误码和错误文案对前端联调有多重要,一个模糊的 500 错误能让前端排查半天。
7.11 崔佳静(前后端联调)
我负责前后端的联调与质量保障。工作重点在于确认端口与 CORS 白名单,并顺着「新增 → 列表刷新 → 概览数字变化 → 删除 → 统计同步」走完整业务链路,记录每一步的实际响应。
服务重启后重新打开页面确认数据还在,这一步让我第一次真切理解「持久化」的意义。联调不是简单的把前端地址指向后端,而是要确保两端对数据的语义认知是一致的。
7.12 叶腾骏(使用说明与测试)
我负责 README 的撰写以及配合进行测试工作。最大的收获是发现「自己看得懂」和「别人照着能跑起来」完全是两件事——把启动步骤写成命令后,我按新同学视角在干净目录重跑了一遍,才补上「静态服务必须起在仓库根目录」这个坑。同时,在整理测试用例时,也帮助我理清了项目的核心业务流程和边界情况。
八、GitHub仓库地址和Commit记录
8.1 GitHub仓库地址
项目仓库:
https://github.com/zsp-c/personal_expense_tracker
本次项目采用 GitHub 进行代码托管、功能分支管理、Pull Request 代码审查以及最终版本发布。
默认分支是 dev,它也是本次开发的主线:40 次提交(20 次功能提交、20 次合并提交,时间跨度 14:26–17:25),所有模块的开发与集成都发生在这条分支上。main 分支在 dev 基础上补充了 Excel 导出接口、一键启动脚本与页面排版修复,共 44 次提交(最后更新 19:06),作为最终交付版本。另有 9 个 feature/* 分支与 12 个 Pull Request(其中 11 个已合并)。
8.2 Commit记录




九、总结
通过本次现场编程,我们完成了从任务划分、独立开发、GitHub 协作,到模块整合、测试和版本发布的完整流程。
技术上,最大的收获是把「约定」当成了工程问题来对待:金额单位、日期格式、错误码、文件所有权,这些看起来是文档里的几句话,实际上决定了十几个人并行开发时会不会互相踩踏。
协作上,我们体会到完成自己的模块只是第一步。只有当各模块成功合并、通过整体测试,并且能在浏览器里走完一条完整的业务链路(记一笔 → 看到列表 → 概览变化 → 导出 Excel),才说明软件真正达到了可交付状态。
附录 A:贡献度评分
| # | 成员 | 建议分值 | 依据 |
|---|---|---|---|
| 1 | 张诗培(102402145) | 1.05 | 项目集成、仓库管理、数据层 |
| 2 | 朱铭浩(102402151) | 1.1 | 前端界面与交互主力 |
| 3 | 张家祥(102402144) | 1.1 | 项目骨架、前端页面结构、项目文档 |
| 4 | 周山翔(102402149) | 1 | 账单查询与条件筛选 |
| 5 | 周子钦(102402150) | 1 | 收支统计 |
| 6 | 韩尔洋(102402154) | 1 | 收支记录新增 / 删除 |
| 7 | 周凯(102402148) | 1 | 仪表盘与统计可视化 |
| 8 | 郑宇嘉(102402147) | 0.95 | 新增记账表单 |
| 9 | 郑仕廷(102402146) | 1 | 历史账单列表 |
| 10 | 王昊(102402156) | 0.95 | 后端接口测试 |
| 11 | 崔佳静(102402155) | 0.95 | 前后端联调 |
| 12 | 叶腾骏(102402142) | 0.9 | 使用说明与测试 |

浙公网安备 33010602011771号