分毫不差——项目系统设计与数据库设计
分毫不差——项目系统设计与数据库设计

| 这个作业属于哪个课程 | 2601_CS_SE_FZU |
|---|---|
| 这个作业要求在哪里 | 团队第三次作业——概要设计与数据库设计 |
| 这个作业的目标 | 完成同账的系统设计与数据库设计:体系结构和功能模块层次、E-R 和表、设计类图、安全与权限、改进分析和三个 LLM 设计示例、本次绩效,以及排到 11 月下旬的开发计划。同时交两份说明书、答辩 PPT 和评审表 |
| 其他参考文献 | GB/T 8567—2006《计算机软件文档编制规范》;邹欣《构建之法》第 17 章;UML 类图;数据库表结构设计的几条准则;用户密码加密 |
华为云团队仓库: 分毫不差
- 分毫不差_系统设计说明书.pdf:分毫不差_系统设计说明书
- 分毫不差_数据库设计说明书.pdf:分毫不差_数据库设计说明书
- 分毫不差_系统设计和数据库设计答辩PPT.pdf:分毫不差_系统设计和数据库设计答辩PPT
- 分毫不差_系统设计和数据库设计答辩评审表:分毫不差_系统设计和数据库设计答辩评审表
目录
- 一、体系结构与数据库设计
- 1.1 三层
- 1.2 功能模块层次
- 1.3 账本状态
- 1.4 E-R
- 1.5 十三张表
- 1.6 设计取舍
- 二、类图与安全权限
- 2.1 设计类图
- 2.2 登录凭证和密码
- 2.3 创建者、普通成员、尚未加入
- 2.4 为什么划成这样
- 三、改进分析
- 3.1 成员上的角色列
- 3.2 结算批次自己的状态
- 3.3 收据排序号,以及跨到另一本账
- 3.4 登录会话还没有落点
- 3.5 LLM 辅助设计示例 1:正式结算单放哪
- 第 1 轮:只要三张表的关系
- 第 2 轮:按拒绝的地方重画
- 第 3 轮:主键、外键、不许级联删
- 3.6 LLM 辅助设计示例 2:四种身份放哪一列
- 第 1 轮:身份分开,不要角色列
- 第 2 轮:换成脚本里的列名
- 第 3 轮:只补两格
- 3.7 LLM 辅助设计示例 3:删消费时图片和账怎么办
- 第 1 轮:封面、收据、删除、冻结
- 第 2 轮:按表名把五件事收紧
- 第 3 轮:只改类型、约束和清理
- 四、组员分工与工作量比例
- 4.1 本轮的 8 条任务
- 4.2 五个维度
- 4.2.1 记 60 的理由
- 4.2.2 记 100 的项
- 4.2.3 解开阻塞,各加 2 分
- 4.3 个人分
- 4.4 贡献系数
- 4.5 本次贡献度
- 五、开发计划安排
- 5.1 时间以哪一天为准
- 5.2 按周的里程碑和产出
- 5.3 设计收口与落库:10 月 20 日–10 月 26 日
- 5.4 消费与结算接口:10 月 27 日–11 月 2 日
- 5.5 Alpha:11 月 3 日–11 月 9 日
- 5.6 冲刺第一周:11 月 10 日–11 月 16 日
- 5.7 Beta:11 月 17 日–11 月 23 日
- 5.8 冲刺收尾:11 月 24 日–11 月 30 日
- 5.9 一个里程碑怎样算过去
- 5.10 组队后到冲刺的安排
- 5.11 分工怎么划
- 5.12 每人的角色和详细开发部分
- 5.12.1 风车
- 5.12.2 饶恩哲
- 5.12.3 whisper
- 5.12.4 circlexx
- 5.12.5 叶银珍
- 5.12.6 陈林祺
- 5.12.7 李润君
- 5.12.8 G_bamboopole
- 5.13 各周交到谁手里
- 5.14 里程碑
一、体系结构与数据库设计
同账是一本多人账:谁垫了钱、谁该摊、结束时最少转几笔。这一节写四件事:系统分成哪三层、功能按哪七块切开、十三张表在 E-R 上怎么连、以及这些切法要挡住什么。金额在库里和接口里都是整数分。账本状态只有进行中、结算中、已结清。
鼓山一日用来核对整条路径。四人、三笔消费,合计 30000 分。正式转账 2 笔、共 9000 分。四人净额相加为 0。
1.1 三层
手机负责把表单填对、把结果画出来。允不允许写、金额算成多少,由服务端在锁住这本账之后重算。PostgreSQL 只按参数落行,并守住单行就能看住的约束。
三层是模块化单体,同一次课程项目里只有一个 API 进程。手机拿不到数据库地址。表示层收操作、发 HTTPS、把错误留在当前页。业务层认会话、认成员、认状态,事务从这里开。数据层执行参数化查询和行锁,查询结果回到业务层之后才决定页面上说什么。

图2-1 分层架构。自上而下是页面、服务、库。contracts 两边共用字段形状,domain 两边共用整数分、余分和最少转账,这两包不引用 Node、React Native 或库表实体。手机上先看到的预览只供当场看,正式结果以服务端重算为准。
| 层 | 技术 | 这一层做的事 | 这一层不碰的事 |
|---|---|---|---|
| 表示层 | React Native 0.86.3、Expo SDK 57、React 19.2.3 | 页面、面板、本地校验、把错误留在当前页 | 最终权限,数据库模型 |
| 业务层 | Fastify 5.12.7、TypeScript 6.0.3、Zod 4.6.7 | 会话、权限、状态、事务、重新分摊 | 决定页面跳到哪一屏 |
| 数据层 | Drizzle 0.45.4、pg 8.23.1、PostgreSQL 17 | 参数化查询、约束、行锁、bytea 图片 | 业务上允不允许这笔账 |
| 共享 | contracts、domain | 跨端字段和纯函数 | Node、React Native、库表实体 |
一次写入按这个顺序走:
- 手机把表单交给
contracts里的字段形状,金额先在本地看成整数分。 - HTTPS 把请求送到 Fastify。Zod 先看格式,会话再看是谁。
- 业务层锁住这本账,重查成员和状态,用
domain再算一遍分摊或结算。正式方案取这次重算的结果。 - 同一事务里写消费、分摊、图片关联、版本和幂等结果。失败则整笔回滚,原表单还在。
外面进来的是账号、邀请码、消费表单和已还声明,出去的是账本、余额、结算方案和错误码。顶层数据流只画这些来源和去向,不画页面跳转。

图2-5 顶层数据流。成员交账号、邀请码、消费和已还声明,创建者另交邀请管理、正式结算和结清确认,手机系统交选中的图片和扫码内容。系统交回账本、流水、余额、方案、状态和可复制的文字。
进了系统之后,同一批表上有五块加工:
- 账号与资料读写
users、auth_sessions,并记一条操作摘要。 - 账本与成员读写
ledgers、ledger_members、invitations。结清和本人归档也落回这里。 - 消费与分摊读写
expenses、expense_splits,图片内容和收据引用在image_assets、expense_images。 - 余额统计只读有效消费、分摊和已登记转账,自己不插行。
- 结算与归档读写
settlement_batches、settlement_transfers,并把账本状态写回去。
记账、加入、正式结算、登记已还都带幂等键,成功结果和业务写在同一个事务里,落在 idempotency_keys。关键操作另写 operation_logs。账号级日志可以不挂账本。
正式环境是一台演示机。手机只拿公开的 API 地址。Caddy 收 443 并持有证书,Fastify 听内部 3300,PostgreSQL 在私有网络里,数据库端口不发布到公网。数据库口令只在服务端环境里。图片二进制放在库里的 bytea,不另开对象存储。开发机走局域网,库在本机 35432,这一段可以明文。

图5-3 部署结构。开发机走局域网和本机 35432,正式库不发布公网端口。
1.2 功能模块层次
七个一级模块,叶子对着 FR01 到 FR30。划分按稳定的业务规则:账号、账本、邀请、消费、余额、结算、个人资料,各有自己的写入对象。页面草稿会改,同学分工也会改,这两样不拿来切模块。图片、审计、幂等给各模块共用,不单列成用户能点开的一级模块。

图2-10 功能模块层次。根是同账,下面七块,每块再挂到具体的 FR。余额统计只有查询,没有自己的账本写入。
| 模块 | 需求 | 服务端目录 | 手机目录 | 读写 |
|---|---|---|---|---|
| 账号 | FR01–FR03 注册、登录、退出 | api/modules/auth | mobile/features/auth | users、auth_sessions |
| 账本 | FR04–FR06 创建、列表、详情 | api/modules/ledgers | mobile/features/ledgers | ledgers、ledger_members |
| 成员邀请 | FR07–FR11 名单、分享、重置、加入 | api/modules/invitations | 账本模块里的邀请页 | invitations、ledger_members |
| 消费 | FR12–FR18 记账、分摊、流水、收据 | api/modules/expenses、images | mobile/features/expenses | expenses、expense_splits、expense_images、image_assets |
| 余额统计 | FR19–FR20 余额、支出统计 | api/modules/insights | mobile/features/insights | 只读有效消费、分摊和已登记转账 |
| 结算 | FR21–FR27 预览、正式结算、登记、结清、归档 | api/modules/settlements | mobile/features/settlements | settlement_batches、settlement_transfers;归档写 ledger_members |
| 个人中心 | FR28–FR30 资料、通知、协议 | api/modules/users | mobile/features/profile | 只改本人那一行 users |
余额统计自己不写账。协议页不产生业务写接口。支撑能力 audit、shared、images 跟在同一次 service 事务里,不单独给用户一条菜单。
一次请求先落到上表的那个目录,再由该目录里的 service 决定写哪几张表。页面只跟自己的 feature 说话,service 再进 repository。例如新增消费走 expenses,同一事务里写消费、分摊,有收据再写图片关联,并给账本 version 加 1。
鼓山这条路径把七个模块串成一次可检查的运行:
- 账号:四人登录。
- 账本:创建者创建「鼓山一日」,创建者同时成为第一名成员,状态为进行中。
- 邀请:另外三人凭有效码加入。已是成员则返回原来那一行,不追摊加入前的消费。人数含创建者,到 12 为止。
- 消费:三笔。小安付食堂 12000 分,四人均分;小白付门票 12000 分,四人均分;小陈付车费 6000 分,只在小安和小陈之间均分。
- 余额:总支出 30000 分。小安净额 +3000,小白 +6000,小陈 −3000,小丁 −6000,相加为 0。
- 结算:成员可以预览和复制。预览不插结算单。创建者确认后写入正式单,两笔转账共 9000 分,账本进入结算中。
- 登记与结清:两笔都登记之后,创建者结清。各人再把自己的账本收进历史,只改本人的
archived_at。
查询每一步都先确认是本账成员。冻结之后再记账,服务端拒绝,原表单留着。普通成员可以预览,正式结算和整本结清只交给创建者。

图5-1 运行时模块怎么接上。上表的目录仍是七个,这条链说明鼓山一次活动按什么次序调用它们。查询都先做成员校验。
三条主路径各有一张活动图,看步骤和分支。手机、服务端、数据库各做哪一段,写在对应的图下面。
记账与分摊。 成员在手机上填金额、付款人、参与人和分摊方式。手机先看格式,不合法就停在原字段。提交时带上幂等键。服务端认人之后锁账本,重查权限、状态和版本,再用整数分重算分摊。合计对得上,才在同一事务里写消费、分摊、图片关联、版本、审计和幂等结果。任一步失败就回滚,原表单还在。成功则回到来源流水,并刷新摘要。

图2-2 记账与分摊。本地先看格式,服务端锁账本、重算分摊,失败就回滚。
结算。 成员打开预览时,服务端在同一次只读快照里读取账本版本、有效消费和分摊,算出最少转账,把版本和方案返回去,不冻结、不插正式单。创建者确认后另开写事务:锁账本,核对仍是进行中、请求里的版本仍等于 ledgers.version,然后重新汇总、重新求解。有人净额不为 0,就保存转账明细并进入结算中。每人净额都是 0,就保存 0 笔正式单并直接已结清,不生成金额为 0 的转账。核对失败则回滚,页面提示重新打开预览。

图2-3 结算。预览不写表。创建者确认后,有非零净额进入结算中,全员净额为 0 则直接已结清。
还款、结清与归档。 钱在系统外转。付款人或创建者在结算中登记一整笔已还,事务里写登记人、时间和说明,剩余净额重算,消费总额不动。全部已登记且逐人剩余净额为 0 之后,创建者再确认结清,账本写成已结清并记下 settled_at。成员本人随后归档,只更新自己那一行的 archived_at。条件不够就停在原页。

图2-4 还款、结清与归档。登记已还不改消费总额。归档只写本人那一行。
1.3 账本状态
库存的三个值是 ACTIVE、SETTLING、SETTLED。页面上写成进行中、结算中、已结清。三个值都是字符串,由检查约束卡住,不另建枚举类型。已结清之后没有回到进行中的操作。归档只改成员自己的 archived_at,不推动账本状态。正式结算单自己不存状态,这张单走到哪一步,看所属账本。
从进行中出去有两条路。创建者确认零笔方案,且逐人净额为 0,直接到已结清。创建者确认正式结算,且存在非零净额,进入结算中;付款人或创建者可以反复登记整笔已还,状态仍是结算中。创建者确认结清时,每一笔都已登记,且逐人剩余净额为 0,才到已结清。进行中留在本状态的,是成员记账和创建者管理邀请。已结清留在本状态的,是本人归档和查询历史。

图5-2 账本状态。
| 当前 | 谁、做什么 | 变成 | 拒绝时 |
|---|---|---|---|
| 进行中 | 成员记账,或创建者管邀请 | 仍是进行中 | FORBIDDEN、VERSION_CONFLICT |
| 进行中 | 成员预览 | 仍是进行中,不写结算单 | FORBIDDEN |
| 进行中 | 创建者正式结算,有人净额不为 0 | 结算中 | FORBIDDEN、VERSION_CONFLICT |
| 进行中 | 创建者确认,每人净额都是 0 | 已结清,0 笔转账 | 同上 |
| 结算中 | 付款人或创建者登记一整笔已还 | 仍是结算中 | FORBIDDEN |
| 结算中 | 创建者结清,每笔已登记且剩余净额为 0 | 已结清 | OUTSTANDING_TRANSFERS |
| 结算中或已结清 | 再记账、加人、重置邀请 | 状态不动 | LEDGER_FROZEN |
| 已结清 | 本人归档 | 状态仍是已结清 | 未结清则 LEDGER_STATE_INVALID |
鼓山走右边这条:进行中记账,创建者确认后进入结算中,两笔转账登记完,创建者结清。四人净额在确认前已经相加为 0,但逐人并不都是 0,所以不会走左边的零笔分支。
1.4 E-R
实体按稳定的业务对象来划。用户是账号,成员是用户和某一本账的关系。记录人、垫付人、分摊人都引用成员,不引用昵称。消费和成员通过分摊明细形成多对多。图片的二进制和「这一笔用了哪张图」分成两个对象。正式结算单和转账分开。还款是转账上的状态和登记属性,不另建还款实体。会话、幂等、审计是工程上要落下的行。
下面这一张是总体 E-R。账号、成员、消费、结算四块都在上面,基数按鸦爪读。

总体 E-R。付款人、收款人、记录人各走自己的外键。临时图片的 ledger_id 可以为空,所以「图片属于账本」是 0..1 对 0..;绑定之后,封面和收据的复合外键才能引用它。
四块在总图上的读法:
- 账号与账本。 一个用户创建 0 到多本账,并持有 0 到多条会话。成员把用户和账本连起来。退出会话只作废那一行,不删用户。
- 成员与邀请。 一本账有 1 到 12 名成员,含创建者。邀请记住是哪一名成员发出的。重置后旧行还在,只是写上
revoked_at。有效与否由撤销时间、过期时间和账本状态一起算。 - 消费、分摊与收据。 一本账有多笔消费。每笔分给 1 到 12 名本账成员。二进制在
image_assets,收据关联只保存这一笔用了哪一张,一笔最多一张。封面可以是预设关键字,也可以是上传图,两列恰好有一个。 - 结算与转账。 一本账最多一张正式结算单。一张单有 0 到 11 笔转账:12 人时最多 11 笔,全员净额为 0 则 0 笔。登记人可以空着,等到这笔转账记成已还再填。
| 从 | 到 | 基数 | 含义 |
|---|---|---|---|
| 用户 | 账本 | 1 对 0.. | 一个人可以创建多本账 |
| 账本 | 成员 | 1 对 1..12 | 含创建者,最多 12 人 |
| 账本 | 邀请 | 1 对 0.. | 重置后旧码还在,只是写上撤销时间 |
| 账本 | 消费 | 1 对 0.. | 一本账有多笔消费 |
| 消费 | 分摊明细 | 1 对 1..12 | 每笔至少分给一个人 |
| 消费 | 收据关联 | 1 对 0..1 | 最多一张收据 |
| 图片 | 收据关联 | 1 对 0..1 | 一张图只挂一笔消费 |
| 图片 | 账本封面 | 0..1 对 0..1 | 也可以只用预设封面,不上传 |
| 账本 | 正式结算单 | 1 对 0..1 | 一本账最多一张正式单 |
| 结算单 | 转账 | 1 对 0..11 | 12 人时最多 11 笔;全零则 0 笔 |
| 用户 | 会话 | 1 对 0.. | 每次登录一行,退出不删行 |
1.5 十三张表
库名 tongzhang,PostgreSQL 17,UTF-8。十三张表里,业务和关联 10 张,会话、幂等、审计 3 张。字段用 snake_case,金额以 _cents 结尾,时间以 _at 结尾,消费日期用 _on。类上的 camelCase 由 mapper 对照过来。主键、外键、唯一约束和检查约束都有名字。字段全文在 同账-建表设计.sql。
表和表之间的外键如下。箭头从引用表指向被引用表,删除和更新都是 RESTRICT。删掉一本账或一名成员,不会把历史消费和转账一起带走。

表间关联。财务记录限制删除。能由查询算出来的余额不在这张图里占一张表。
(1) 账号
users 保存登录身份。邮箱去首尾空格后按小写存放,用户名 2 到 30 字。password_hash 只存 Argon2id 编码串。auth_sessions 每次登录一行:token_hash 唯一,expires_at 为创建后 30 天,退出写 revoked_at。原文令牌不在这张表里。
| 表 | 关键列 | 约束 |
|---|---|---|
users | password_hash、邮箱、用户名 | 邮箱按小写保存;密码只存 Argon2id 编码串 |
auth_sessions | token_hash、expires_at、revoked_at | 摘要唯一;到期和撤销都留在这一行 |
(2) 账本
ledgers.status 只取三个字符串。version 从 1 起,消费或成员一变就加 1,正式结算拿它和预览时的版本对。封面是 cover_key 与 cover_image_id 恰好一个有值。已结清当且仅当 settled_at 有值。ledger_members 没有角色列,(ledger_id, user_id) 唯一。invitations.code 是六位无歧义字符,全局唯一,有效期晚于创建时间,重置只填撤销时间。
| 表 | 关键列 | 约束 |
|---|---|---|
ledgers | created_by_user_id、status、version、cover_key、cover_image_id、settled_at | 状态三值;version 从 1 起;封面两列恰好有一个;已结清当且仅当 settled_at 有值 |
ledger_members | ledger_id、user_id、joined_at、archived_at | (ledger_id, user_id) 唯一;没有角色列 |
invitations | code、expires_at、revoked_at | 六位无歧义字符,全局唯一;有效期晚于创建时间 |
(3) 消费
expenses.amount_cents 是 1 到 999999999 的整数分。记录人创建后不改。垫付人可以不出现在分摊明细里。split_mode 取人均、按份、按金额。deleted_at 有值的行退出汇总,行本身还在。expense_splits 的主键是消费加成员,金额允许 0;按份时 shares 为正整数,其余方式为空。外键都带 ledger_id,账本 A 的消费分不到账本 B 的人。
图片单独放。image_assets.kind 是封面或收据,content 为 JPEG 或 PNG 的原始二进制,1 到 1048576 字节,并等于 octet_length(content)。宽、高各 1 到 4096,像素个数用 width::bigint * height 与 16000000 比较。未绑定的图 ledger_id 为空,只清理超过 24 小时仍未绑定的那些。expense_images 以消费为主键,一笔一行,一张图只出现一次。
| 表 | 关键列 | 约束 |
|---|---|---|
expenses | recorder_member_id、payer_member_id、amount_cents、version、deleted_at | 金额 1 到 999999999 分;记录人创建后不改;软删除不删行 |
expense_splits | expense_id、member_id、ledger_id、shares、amount_cents | 主键是消费加成员;外键都带 ledger_id |
image_assets | ledger_id 可空、kind、content | COVER 或 RECEIPT;字节数等于 octet_length(content) |
expense_images | 主键 expense_id、image_id | 一笔一行;一张图只出现一次 |
(4) 结算和工程
settlement_batches.ledger_id 唯一,所以一本账只有一张正式单。确认时写入消费总额、转账总额、笔数、来源版本,以及当时每人净额的 jsonb 数组。之后新的消费不再改写这份快照。0 笔时转账总额为 0。settlement_transfers 只存 PENDING 或 PAID。待登记时,登记人、时间、说明都是空;记成已还后三者一起有值。说明最长 200 字。
idempotency_keys 把用户、操作、请求摘要和成功结果放在同一行,和业务写同一个事务。operation_logs 记操作者和可选的账本。这两张表不存密码,也不存令牌原文。
| 表 | 关键列 | 约束 |
|---|---|---|
settlement_batches | ledger_id、source_version、两个总额、transfer_count、net_snapshot | ledger_id 唯一,所以一本账一张正式单;快照是 jsonb 数组;0 笔则转账总额为 0 |
settlement_transfers | payer_member_id、payee_member_id、status、登记三列 | 只存 PENDING / PAID;待登记时登记人、时间、说明都空 |
idempotency_keys | 用户、操作、请求摘要、成功结果 | 同一次重试和业务写在同一个事务里 |
operation_logs | 操作者、可选的 ledger_id | 账号级日志可以不挂账本 |
派生数不进表:总支出、已付、应摊、消费净额、剩余净额、类别占比、预览方案、登记进度。它们在查询时从有效消费和已登记转账算出来。正式结算才把当时的净额写进 net_snapshot。
鼓山落表之后,应当能查出下面这组数。消费三行,金额 12000、12000、6000,分摊合计 30000。正式单一行:消费总额 30000,转账 2 笔、9000 分。转账两行:小陈付给小安 3000,小丁付给小白 6000。
| 成员 | 已付 | 应摊 | 消费净额 |
|---|---|---|---|
| 小安 | 12000 | 9000 | +3000 |
| 小白 | 12000 | 6000 | +6000 |
| 小陈 | 6000 | 9000 | −3000 |
| 小丁 | 0 | 6000 | −6000 |
| 合计 | 30000 | 30000 | 0 |
两笔都登记之后,逐人剩余净额为 0,总消费仍是 30000 分。对不上这组数,这版表不能当作鼓山验收通过。
1.6 设计取舍
能由消费和已登记转账算出来的数,不另建一张会过期的表。确认过的方案要留下,所以快照是例外。
余额不建表。 总支出、应摊、净额会随每一笔有效消费变化。若再存一份余额,改一笔消费就要改两处。查询时用 paid − shared 得到消费净额,再用已登记转出、转入得到剩余净额。全员合计应为 0;合计为 0 并不表示逐人都是 0。统计消费总额时排除已软删除的行,也排除还款。正式结算把当时的净额写进 net_snapshot,此后这份数组不再跟着新消费改。
标识用 uuid。 昵称会改。历史消费、分摊和转账认的是成员 id 和用户 id。改显示名不影响「这笔是谁记的」。
同账本用复合外键。 分摊、收据、转账都带 ledger_id,外键同时指向本账的消费和本账的成员。只改其中一端会被拒绝。跨账引用在数据库里就过不去,不必等服务端想起来再查一次。
金额用 bigint 分。 接口传整数字符串。12000 表示 120 元。按份或人均时,先做整数除法,余数按余数从大到小逐分送给成员;余数相同,按成员 UUID 字典序升序。10000 分三人均分,结果是 3334、3333、3333,多分的那 1 分给稳定 id 最小的人。相同的人、相同的金额、相同的 id,每次得到相同的结果。按金额不自动调平,调用方给出的非负金额合计必须等于总额。
消费软删除。 只写 expenses.deleted_at。汇总排除它,分摊行和收据关联还在。这样一条删除不会把当时的分摊依据一起清掉。
财务外键 RESTRICT。 删父行会被数据库拒绝。改历史要走软删除、撤销邀请或登记已还,而不是删掉账本或成员。
幂等键与业务同事务。 超时之后用同一个键重试,提交过的那一次把原来的成功结果交回去,不会写出第二笔同样的消费,也不会把登记时间改掉。
| 取舍 | 落到库里 | 挡住什么 |
|---|---|---|
| 第三范式,快照单独标明 | 余额不建表;net_snapshot 只在正式结算时写 | 改一笔消费后,余额和快照各写各的 |
| 标识用 uuid | 不拿昵称当外键 | 改显示名把历史账连丢 |
| 同账本用复合外键 | (ledger_id, 成员或消费 id) | 账本 A 的消费分给账本 B 的人 |
金额用 bigint 分 | 接口传整数字符串 | 浮点元在分摊余数上对不齐 |
| 消费软删除 | 只写 deleted_at,汇总排除它 | 删行把分摊和收据一起级联掉 |
财务外键 RESTRICT | 删父行会被数据库拒绝 | 用删除账本或成员来改历史 |
| 幂等键与业务同事务 | 成功结果和账一起提交 | 超时重试写出第二笔同样的消费 |
跨行才能知道的规则放在服务端,不写成一条跨表的检查约束。例如分摊合计必须等于消费金额、净额之和必须为 0、结清前每笔都已登记。这些在锁住账本之后、提交之前重算。单行就能看住的,交给检查约束:金额范围、状态取值、封面两列恰好一个、待登记时不能已经写着登记人。
二、类图与安全权限
类名和表名是同一套东西的两种写法。属性用 camelCase,字段用 snake_case,由 mapper 对照。事务放在 service,不放在页面,也不放在 repository。这一节写设计类图怎么对应到表,登录凭证和密码放在哪,三种身份各自能做什么,以及为什么身份要拆开认。
2.1 设计类图
核心类和十三张表一一对应。登记已还不单独建类,记在 SettlementTransfer 上,方法是把这一笔从待登记写成已登记。成员余额不建类,打开余额页时现算。IdempotencyRecord 和 OperationLog 是每次写入都要带上的工程对象,用户能点到的仍是账本、消费和结算。

图3-1 核心设计类图。Ledger 到 SettlementBatch 是 1 对 0..1。Expense 上的记录人和垫付人是两条指向 LedgerMember 的线。LedgerMember 没有角色属性。
类上的方法和表上的约束是同一件事的两面。Ledger 是否还能记账,看的是 status。Expense 改完要带上自己的 version,对不上就拒绝。SettlementBatch 没有自己的状态字段,这张单处于什么阶段,看所属账本。创建者必须同时有成员行,由创建账本的那一个事务保证。人数上限和分摊人数在锁内再数一遍。
| 类 | 对上的表 | 这一版卡住的关系 |
|---|---|---|
User | users | 创建账本、持有会话、上传图片 |
AuthSession | auth_sessions | 一个用户多条会话;退出只作废这一条 |
Ledger | ledgers | 1 到 12 名成员;正式结算单 0 或 1 张 |
LedgerMember | ledger_members | 无角色列;archivedAt 只属于这一行 |
Invitation | invitations | 账本发出去的每一次邀请都留一行 |
Expense | expenses | recorderMemberId 与 payerMemberId 是两列 |
ExpenseSplit | expense_splits | 一笔消费 1 到 12 条 |
ImageAsset | image_assets | 封面和收据都从这里取二进制 |
ExpenseImage | expense_images | 一笔消费最多一张收据 |
SettlementBatch | settlement_batches | 写入后不再按新消费改快照 |
SettlementTransfer | settlement_transfers | 待登记、已登记,以及登记人、时间、说明 |

图4-1 模块调用。页面进到对应的 service,service 再进表。手机不绕过服务端去写库。一个 feature 只调用自己的模块;结算要读消费,也是结算 service 在服务端读,而不是手机把算好的转账列表送进库。
2.2 登录凭证和密码
密码和会话分开存。原密码、令牌原文不进业务表、日志和幂等响应。库被拷走时,拿到的是不可逆的密码编码和令牌摘要,换不回下一次登录用的原文。
密码。 users.password_hash 只存 Argon2id 编码串。内存 19456 KiB,时间代价 2,并行度 1,16 字节随机盐,输出 32 字节。盐和参数写在编码串里,没有另一把可逆的密码密钥。上线前会量登录耗时,以后只把参数往更慢的方向调。
会话。 登录成功时生成 256 位随机令牌,原文只这一次返回手机,放在 SecureStore。库里的 auth_sessions.token_hash 是 SHA-256 摘要。expires_at 为创建后 30 天,固定到期,不用 JWT 刷新链。退出成功则写 revoked_at,这一行留着,旧令牌不能再换一条新会话。服务端没确认撤销时,手机提示未退出,并清掉本地凭证。
| 出现的地方 | 令牌 | 密码 |
|---|---|---|
| 登录或注册的那一次响应 | 原文,仅这一次 | 不回传 |
| 手机 SecureStore | 原文 | 不保存 |
users.password_hash | — | Argon2id 编码串 |
auth_sessions.token_hash | SHA-256 摘要 | — |
| 日志、幂等结果、图片缓存 | 不写 | 不写 |
邀请码是六位可读字符串,存在 invitations.code,给创建者复制和分享。它能加入一本账,不能代替登录,也不能当成支付凭证。猜码按每人每分钟 30 次封顶,登录按 IP 和规范化账号 15 分钟 10 次封顶。首版限流记在单进程里,进程重启会清零;多实例之前要另做。
传输上,正式环境走 HTTPS,证书在 Caddy。开发时局域网可以明文,数据库口令只放在服务端环境里。业务角色没有建库权限,迁移用单独的角色。图片读取要先过登录和归属,响应带 Cache-Control: no-store,手机不把图片持久缓存。SQL 一律参数绑定,错误响应不带 SQL 和堆栈。
2.3 创建者、普通成员、尚未加入
三种身份都从数据里认,不在成员表上再存一列角色,也不建全局角色表。创建者看 ledgers.created_by_user_id。普通成员看本账有没有自己的成员行。尚未加入就是本账没有这一行。记录人、垫付人、分摊人写在具体一笔消费上,同一个人可以同时占这三种。

图2-11 用况关系。未加入者只能注册、登录和凭码加入。创建者的箭头连到邀请和正式结算,同时也连到记账和本人归档。普通成员能记账、预览、登记自己应付的转账,不能重置邀请,也不能整本结清。
- 创建者。
ledgers.created_by_user_id等于当前用户,并且这个人在ledger_members里有一行。建账的那个事务里同时写入这第一名成员。能管邀请、改删进行中的任意消费、正式结算和整本结清,也能做普通成员能做的记账、预览、登记自己应付的转账和本人归档。创建者读不到其他账本,也读不到别人的会话。 - 普通成员。 本账有成员行,且用户 id 不等于创建者。能记账、查看、预览和复制已经生成的方案。编辑或删除只限于
expenses.recorder_member_id是自己的那些笔,还要带上这笔消费的version。登记已还只限于settlement_transfers.payer_member_id是自己的那一笔。 - 尚未加入。 本账没有成员行。可以注册、登录、改自己的资料,凭有效邀请码看最小预览:账本名、创建者昵称、当前人数、是否已是成员、能否加入。看不到流水、余额、完整名单、收据和结算。未登录者只能碰到注册、登录和公开的健康检查。
记录人、垫付人、分摊人写在具体一笔消费上。
- 记录人是
expenses.recorder_member_id,创建后不再改。谁能改、谁能删,认的是这一列,再加上是不是创建者。 - 垫付人是
expenses.payer_member_id,可以不出现在分摊明细里,也不因此获得改删权。 - 分摊人是
expense_splits里的一行。同一人可以既记账、又垫钱、又被分到。
「允许」还要满足最后一列。页面上的按钮只决定发不发请求。这张表也不表示手机可以连上数据库。
| 操作 | 创建者 | 普通成员 | 尚未加入 | 还要满足 |
|---|---|---|---|---|
| 查看本账、消费、图片、余额 | 允许 | 允许 | 不允许 | 仅所属账本;图片再查归属 |
| 新增消费 | 允许 | 允许 | 不允许 | 进行中 |
| 编辑或删除消费 | 允许 | 允许 | 不允许 | 进行中;普通成员仅本人记录,并核对 expenses.version |
| 查看、复制、分享邀请 | 允许 | 不允许 | 不允许 | 进行中;未加入者只能凭码看最小预览 |
| 重置邀请 | 允许 | 不允许 | 不允许 | 进行中;旧码写入 revoked_at,新码 7 天 |
| 预览结算 | 允许 | 允许 | 不允许 | 进行中;不插入结算单 |
| 正式结算 | 允许 | 不允许 | 不允许 | 进行中,且请求里的版本等于 ledgers.version |
| 复制已经生成的方案 | 允许 | 允许 | 不允许 | 进行中复制刚预览的文字;结算中和已结清复制已落库的那一份 |
| 登记已还 | 允许 | 允许 | 不允许 | 结算中;普通成员须为该笔付款人;重复登记返回原记录 |
| 整本结清 | 允许 | 不允许 | 不允许 | 每笔已登记且剩余净额为 0;全零在正式结算时直接结清 |
| 本人归档 | 允许 | 允许 | 不允许 | 已结清;只改本人 archived_at |
| 修改本人资料 | 允许 | 允许 | 允许 | 已登录,只改自己,与在哪本账无关 |
| 凭邀请码加入 | 允许 | 允许 | 允许 | 进行中、未满 12 人;已是成员则返回原行,不追摊旧消费 |
服务端拒绝时用固定错误码,不把 SQL 返回给手机。
- 非成员,或普通成员改别人记下的消费:
FORBIDDEN - 冻结后还记账、加人、重置邀请:
LEDGER_FROZEN - 预览之后账被改过,版本对不上:
VERSION_CONFLICT - 分摊合计不等于消费金额:
SPLIT_SUM_MISMATCH - 未结清就归档:
LEDGER_STATE_INVALID - 转账没登记完就结清:
OUTSTANDING_TRANSFERS - 第二张收据,或图片不属于本账:
RECEIPT_LIMIT、IMAGE_INVALID - 金额不是合法的整数分:
VALIDATION_ERROR - 猜码或登录过频:
RATE_LIMITED
2.4 为什么划成这样
写入顺序固定,后一步看到的是前一步锁住之后的行:
- 格式。金额必须是合法的整数分字符串。
- 会话。认出用户,并确认这条会话没过期、没撤销。
- 幂等。同一个键若已经成功,直接交回原来的结果。
- 锁账本。锁住之后再数人数,两个人同时加入也过不了 12 人。
- 重新查成员和创建者。按钮当时亮着,锁内再读一次。
- 状态、版本、金额与关联。消费或成员一变,
ledgers.version就加 1。预览时看到的版本和确认时对不上,正式结算就拒绝。 - 提交。失败则整笔回滚。
| 这样划 | 原因 |
|---|---|
创建者只认 created_by_user_id | 邀请、正式结算、结清都要先认出同一个人。成员行负责「在不在这本账」,创建人标识负责「是不是这本账的创建者」 |
| 创建者同时是第一名成员 | 创建者也要记账、归档。建账的那个事务里同时写入这第一行 |
| 记录人、垫付人、分摊人分三处 | 一笔账里同一个人可以既记、又垫、又被分到。三列各认各的事 |
| 尚未加入等于没有成员行 | 最小预览只交账本名、创建者昵称、人数、是否已是成员、能否加入。有了成员行,才进入上面的矩阵 |
| 登记已还认转账付款人,不认消费垫付人 | 垫的是当时那一笔消费,还的是结算单上的一笔转账。鼓山里小陈垫的是车费,结算单上小陈要付给小安的是净额 3000 分 |
| 按钮和手机预览不算权限 | 锁账本之后再读状态。检查时还在进行中、写入时已经结算中,这一笔就回滚 |
健壮性上,这些输入在锁内就被挡住,原表单留着:
- 消费金额从 1 分到 999999999 分。分摊金额可以为 0,合计必须等于这笔消费。
- 分摊人数 1 到 12,成员不重复,并且都在本账。按份的份数是正整数。
- 重复加入、重复登记已还,都返回原来的那一行,不改时间、不插第二行。
- 0 消费或每人净额都是 0 时,预览给出空方案;创建者确认后保存 0 笔正式单并直接结清,不生成 0 元转账。
- 一次要读好几项合计时,用同一次 SQL 快照。不把这一秒的消费总额和下一秒的净额拼在一起。
- 已知的业务冲突映射成上面的错误码。未知的约束错误只进内部日志。
三、改进分析
10 月 16 日的需求分析答辩上,老师、助教和其他组没有向同账提问。
现场没有留下问题。组里后来把课程社区里其他组的第二次作业打开看过,看完回到同账自己的稿上:需求规格说明书里的类属性,和这次要写进表的字段,有没有两句不能一起成立。能一起成立的不动。成员余额本来就是查询时算出来的,这一版仍然不建余额表。
下图分四条,每条从左看到右。左边是讨论前写在类上的,右边是收进表里的。

3.1 成员上的角色列
问题。
需求规格说明书 3.1.5 里,
LedgerMember带role,取值是创建者或普通成员。账本上同时有创建人。创建者、记下这笔账的人、垫钱的人、分摊到的人,是四种身份,类图却只给成员准备了一列角色。
改了什么。
ledger_members去掉角色列,只留账本、用户、加入时间、本人归档时间。创建者改为读取ledgers.created_by_user_id,并且创建账本时这个人必须同时成为第一条成员。记录人、付款人写在消费上,分摊人写在分摊明细上。权限矩阵仍分创建者、普通成员、尚未加入,服务端查的是创建人标识和有没有成员关系,不读角色列。数据库设计说明书 3.1.1 账本成员,系统设计说明书 8.3、8.4。
为什么改。
重置邀请、正式结算、整本结清都要先认出创建者。角色列和创建人各写一次,两次写入可以不一致,权限矩阵就没有唯一依据。记录人、付款人、分摊人如果也塞进这一列,一笔账里同一个人只能占一种身份,和“谁记的、谁垫的、谁摊的可以不是同一个人”对不上。
3.2 结算批次自己的状态
问题。
需求规格说明书 3.1.4 写账本与批次是 1 对 0..,同一段又写同一本账同时最多一个有效批次。3.1.5 的批次属性里还有
status。账本自己的状态已经是进行中、结算中、已结清。
改了什么。
批次不再存状态。类图里账本到正式结算单改为 0..1,一本账最多一批。账本状态仍只取进行中、结算中、已结清。确认时在同一事务里写入消费总额、转账总额、笔数、每人净额快照和来源版本,之后不按新的消费重算这张方案。每一笔转账自己保留待登记、已登记。预览只返回方案,不插入结算单,也不冻结。全员净额为 0 时,确认后直接已结清,不生成金额为 0 的转账。数据库设计说明书 3.1.1 正式结算单,系统设计说明书图 3-1 与 2.3.2。
为什么改。
预览、冻结、结清如果各写一处状态,批次可以是“结算中”,账本仍是“进行中”,后面的人还能继续记账,正式方案就不是确认时的那一张。0.. 也允许同一本账再插第二张正式批次,收款对象会被重算。待登记和已登记是每一笔转账的事,不是整本账的事,放在批次上会把“这本账冻住了”和“这一笔还了没有”混在一起。
3.3 收据排序号,以及跨到另一本账
问题。
需求规格说明书 3.1.3 已写首版每笔最多一张收据,3.1.5 的
ExpenseImage仍留着sortOrder。分摊明细只有消费、成员、份数和金额,没有账本。同一本账的约定写的是“服务端在事务里检查”。
改了什么。
expense_images去掉sortOrder,用消费做主键,一笔一行。图片二进制放在单独的image_assets,消费只存引用。封面允许在账本创建前先上传,创建成功后再绑定。分摊明细、收据关联、结算转账增加ledger_id,外键同时指向本账的消费和本账的成员,只改其中一端会被拒绝。财务记录使用限制删除。冻结之后消费只读。数据库设计说明书 2.4.1、3.1.1,系统设计说明书 8.5、8.6。
为什么改。
排序号是给多张图排队用的。它还在,实现时就会再加第二张收据。同账只放在程序里,仍可能把账本 A 的消费分给账本 B 的人。封面和收据若塞进消费那一行,删消费时会把图片二进制一起抹掉。
3.4 登录会话还没有落点
问题。
需求规格说明书 2.6 把登录会话的载体留到工程准备再定。类图里没有会话表。业务规则已经要求登录后持有会话,失效后重新登录,退出只清除这一次的本机凭证。密码只要求保存哈希,没有写算法,也没有写令牌原文能不能进库。
改了什么。
新增
auth_sessions。主键id,user_id指向用户。手机拿到 256 位随机令牌,库里的token_hash只存 SHA-256 摘要。expires_at为创建后 30 天。退出写revoked_at,这一行保留。密码在用户表改为 Argon2id。系统设计说明书 8.1、8.2,表在数据库设计说明书 3.1.1。
为什么改。
载体不定,退出就没有一行可以作废。令牌原文若覆写在用户身上,旧凭证清不干净,库备份里还会留下能直接登录的原文。密码若可逆或只做无盐摘要,泄露之后可以还原或直接比对。
3.5 LLM 辅助设计示例 1:正式结算单放哪
模型是腾讯混元 Hy4 preview。只谈 ledgers、settlement_batches、settlement_transfers,不谈最少转账怎么算。落点是 同账-建表设计.sql。截图贴在对应一轮旁边。
第 1 轮:只要三张表的关系
我的提示词(大意,原文见截图)
账本只有进行中、结算中、已结清。预览不能冻账。确认后留下当时每人该收该付。净额全是 0 就直接已结清,不写 0 元转账。转账自己有待登记、已登记。
只回答四件:结算单要不要状态列,一本账几张正式单,冻结哪些字段,预览写不写表。

AI 回了什么(摘要)
| 问到的 | 它的回答 |
|---|---|
| 要不要状态列 | 要。open / closed,表示这张单登记完了没有 |
| 几张正式单 | 同时 open 最多一张;历史上可以有 N 张,用 settled_through_at 切下一轮 |
| 冻结什么 | DECIMAL,另加币种、名册、凭证 |
| 预览写不写 | 不写 |
账本三个状态改成从结算单推出来。结清后再记消费可以回到进行中。结算中的新消费归到下一轮。

我采纳
- 预览不写正式结算单,也不冻结
- 确认后的付款人、收款人、金额不改
- 不插入 0 元转账
我拒绝
- 结算单自己的状态列
- 一轮一轮的历史结算单
- 小数金额
- 用结算单反推账本状态
- 结算中继续记账
状态只在账本上。一本账最多一张正式单。金额是整数分。确认后不能再记消费。已结清不回到进行中。
第 2 轮:按拒绝的地方重画
我的提示词(大意,原文见截图)
- 表名
ledgers、settlement_batches、settlement_transfers,金额用整数分 - 结算单不要状态;
ledger_id唯一,一本账一张 - 确认时留下
source_version、两个总额、笔数、net_snapshot - 转账只有
PENDING/PAID - 净额全 0:仍写这一张单,笔数为 0,账本直接
SETTLED

AI 回了什么(摘要)
(1) 到 (5) 的关系它跟上了:1 对 0..1,结算单无状态,预览不落库,整数分,PENDING / PAID。
实现另写了一套:
| 它写成的 | 差在哪 |
|---|---|
| 主键 BIGINT | 后面要改成 uuid |
账本上的列叫 source_version | 乐观锁列不叫这个名字 |
ON DELETE CASCADE | 财务行不能级联删 |
| 成员外键只指成员表主键 | 没有绑到本账的成员 |
把 UNIQUE(ledger_id) 叫幂等键 | 这条只表示一本账一张正式单 |
| 可以删转账,也可以删空单 | 正常业务不删这些行 |
它另外问了三句:第二次确认记在哪,禁止新消费要不要触发器,全部 PAID 之后净额是不是必然为 0。

我采纳
一本账一张正式单。结算单不设状态。整数分。转账只有待登记和已登记。预览不写表。
我拒绝
BIGINT 主键,级联删除,账本列叫
source_version,唯一约束当成幂等,业务里删除转账行。第二次确认只改ledgers.status和settled_at。不加结算单列,不加触发器。
第 3 轮:主键、外键、不许级联删
我的提示词(大意,原文见截图)
- 主键改 uuid
- 账本列名
version;source_version只保存确认当时的ledgers.version - 外键
ON DELETE RESTRICT - 创建人、付款人、收款人、登记人用
(ledger_id, 成员 id)指向ledger_members - 转账补
created_at;笔数 0 到 11,顺序号 1 到 11 - 幂等不放在这三张表上

AI 回了什么(摘要)
点过名的结构改过来了:UNIQUE(ledger_id)、复合外键、RESTRICT、settled_at 在账本上、结算单写入后不再 UPDATE。唯一约束也不再被叫成幂等键。
约束还松着。它问回五件事,这一轮就停在这里。
| 稿子里仍这样 | 它问的 |
|---|---|
net_snapshot 是对象文本 | |
PENDING 仍可带说明,说明 varchar(500) | |
version 为 BIGINT,默认 0;结清时间只写在说明里 | 成员变动要不要让 version 加 1;11 笔是不是把人数封顶;结清要不要记人 |
| 仍允许删转账和空单 | 确认后再插一行,数据库拦不拦;sequence 要不要连续 |
唯一约束写成 (id, ledger_id) |

我采纳
uuid。version 与 source_version 分开。复合外键指向本账成员。外键不级联。第二次确认只写 ledgers.status 与 ledgers.settled_at。UNIQUE(ledger_id) 不当作幂等键。
我拒绝 / 我补充
最后一稿留下。下面五处收进 同账-建表设计.sql。
| 它最后一稿 | 脚本里 | 为什么动 | |
|---|---|---|---|
| 1 | net_snapshot 对象文本 | jsonb 数组;0 笔则转账总额为 0;algorithm_version 为 zero-sum-dp-v1 | 对象过不了数组检查;0 笔和总额会各写各的 |
| 2 | PENDING 可以带着登记人;说明 500 | PENDING 的登记人、时间、说明都空;说明 varchar(200),不是到账证据 | 还没登记,库里却像记过 |
| 3 | version 从 0 起;结清时间只在说明里 | version 从 1 起,消费或成员一变就加 1;已结清当且仅当 settled_at 有值。结清人就是创建者,不加 settled_by。笔数上限 11,因为一本账最多 12 人 | 快照要和账本版本同一起点;状态和时间不能拆开 |
| 4 | 可删转账,空单也可删 | 业务不删这些行。外键 ON DELETE RESTRICT。确认后不再插入、不再删除转账。不加触发器。sequence 由服务端写成 1 到笔数 | 删掉一笔,冻结的笔数和总额对不上 |
| 5 | UNIQUE (id, ledger_id),转账用 (batch_id, ledger_id) 去引用 | UNIQUE (ledger_id, id),转账按 (ledger_id, batch_id) 引用。成员仍是 (ledger_id, 成员 id) 指向 ledger_members | 两边列序不一致时,PostgreSQL 建不起这条外键 |
快照和 0 笔绑在 settlement_batches:
net_snapshot jsonb NOT NULL,
CONSTRAINT ck_settlement_batches_snapshot CHECK (jsonb_typeof(net_snapshot) = 'array'),
CONSTRAINT ck_settlement_batches_zero CHECK ((transfer_count = 0) = (transfer_total_cents = 0))
登记绑在 settlement_transfers。PAID 必须有登记人和时间:
CONSTRAINT ck_settlement_transfers_registration CHECK (
(status = 'PENDING'
AND registered_by_member_id IS NULL
AND registered_at IS NULL
AND registration_note IS NULL)
OR (status = 'PAID'
AND registered_by_member_id IS NOT NULL
AND registered_at IS NOT NULL))
版本和结清绑在 ledgers:version integer NOT NULL DEFAULT 1,并且 (status = 'SETTLED') = (settled_at IS NOT NULL)。
外键按脚本的列序:
CONSTRAINT uq_settlement_batches_ledger_id_id UNIQUE (ledger_id, id),
FOREIGN KEY (ledger_id, batch_id) REFERENCES settlement_batches (ledger_id, id)
ON DELETE RESTRICT ON UPDATE RESTRICT
3.6 LLM 辅助设计示例 2:四种身份放哪一列
模型仍是腾讯混元 Hy4 preview,新开一个对话。只问创建者、记录人、垫付人、分摊人落在哪一列,以及权限矩阵。不建角色表,成员表不放角色列。不重做结算单,不写接口代码。落点是系统设计说明书 8.3、8.4,表在 同账-建表设计.sql。截图贴在对应一轮旁边。
第 1 轮:身份分开,不要角色列
我的提示词(大意,原文见截图)
四种身份不要并成一个角色。创建者才能重置邀请、正式结算、整本结清,进行中可以改删任意消费,结算中可以登记任意一笔已还。普通成员只能改删自己记下的消费,只能登记自己应付的转账。垫付人可以不参与分摊。尚未加入的人只能凭有效邀请码看最小预览。归档只影响自己,且必须已结清。
不要全局角色表,成员表不要角色列。回答两件事:五种身份各在哪一列;一张权限矩阵,列是创建者、普通成员、尚未加入。

AI 回了什么(摘要)
没有角色表,也没有成员角色列。创建者认账本上的人,同时有一行成员。记录人、垫付人、分摊人分三处,互不排斥。垫付不给改删权。尚未加入就是没有成员行。归档只改自己那一行。这些留下。
列名和四格矩阵没对上:
| 它写成的 | 差在哪 |
|---|---|
creator_id、is_archived | 创建者列是 created_by_user_id;归档只有 archived_at |
recorded_by_member_id、settled_by_member_id | 记录人列是 recorder_member_id;登记人是 registered_by_member_id,状态只存 PENDING / PAID |
成员外键只指 member_id | 要带 ledger_id,避免指到另一本账 |
| 普通成员可以查看邀请;预览三种状态都可以 | 邀请只给创建者,且只在进行中;预览只在进行中 |
| 正式结算一定进入结算中 | 全员净额为 0 时,这次确认直接已结清 |
编辑删除不核对 expenses.version | 要核对这笔消费自己的版本 |

我采纳
- 不建角色表,成员表不放角色列
- 创建者只认账本上的创建人,并且必须有成员行
- 记录人、垫付人、分摊人分开;垫付人可以不在分摊明细里
- 查看本账在三种状态下都允许,仅限本账成员
我拒绝
错的列名,成员外键不带账本,邀请和预览放得太宽,全员净额为 0 仍要再结清一次,编辑消费不核对版本。
第 2 轮:换成脚本里的列名
我的提示词(大意,原文见截图)
- 列名改为
ledgers.created_by_user_id、ledger_members.archived_at、expenses.recorder_member_id、expenses.payer_member_id expense_splits主键(expense_id, member_id),外键都带ledger_id- 登记已还写
registered_by_member_id、registered_at、registration_note,状态只用PENDING/PAID - 邀请只给创建者,只在进行中;预览只在进行中;正式结算要核对
ledgers.version;全员净额为 0 直接已结清

AI 回了什么(摘要)
列名改过来了。邀请、预览、正式结算、整本结清四格也按上一问收紧了。expenses.version 和 ledgers.version 分开,不共用。
矩阵里还没有两格:结算中和已结清能不能复制已经生成的方案;凭邀请码加入算不算允许。

我采纳
列名、复合外键、邀请只在进行中、预览只在进行中、正式结算核对账本版本、全零直接结清。
我拒绝
先不补别的表。下一问只加「复制已经生成的方案」和「凭邀请码加入」。
第 3 轮:只补两格
我的提示词(大意,原文见截图)
- 复制已经生成的方案:创建者和普通成员在三种状态下都可以。进行中复制刚预览的文字;结算中、已结清复制已经落库的那一份。不重算,不插新预览,不改账本状态。尚未加入不允许。
- 凭邀请码加入:只在进行中,并且不满 12 人。已经是成员的不另插一行。结算中、已结清不能加入。

AI 回了什么(摘要)
上一稿的列名和其余格子没动。两格都加上了。
| 补上的 | 它的写法 |
|---|---|
| 复制已经生成的方案 | 创建者、普通成员三种状态都允许;尚未加入不允许。不重算,不插新预览,不改 ledgers.version |
| 凭邀请码加入 | 尚未加入只在进行中、人数小于 12 时允许。创建者和普通成员两格写成不允许 |
正文里又写:已经有成员行时不插新行、不报错。加入成功只加一行 ledger_members,不补旧流水,并且「不参与结算计算」。最小预览写成账本名、创建者昵称、人数/上限、邀请码是否有效。

我采纳
复制方案与预览分成两格。预览只在进行中,而且会算;复制三种状态都可以,只取已经有的结果。加入不追摊旧消费,结算中和已结清不能加入。
我拒绝 / 我补充
最后一稿留下。交进 8.3、8.4 的是下面两处。
| 它最后一稿 | 说明书里 | 为什么动 | |
|---|---|---|---|
| 1 | 创建者、普通成员「凭码加入」写成不允许 | 已是本账成员时仍允许调用。按 (ledger_id, user_id) 返回原来那一行,不插新行,也不当错误 | 矩阵上的不允许,和它自己写的「不报错」是两套结果 |
| 2 | 加入后不参与结算计算;预览里带人数上限和邀请码是否有效 | 不追摊加入前的消费。之后被写进新消费的分摊,才进入结算。预览成功体是账本名、创建者昵称、当前人数、是否已是成员、能否加入。无效、过期、已撤销不放进成功体 | 新成员若被记进后来的消费,净额里应该有他。邀请码无效是查询失败,不是预览里的一个字段 |
3.7 LLM 辅助设计示例 3:删消费时图片和账怎么办
模型仍是腾讯混元 Hy4 preview,再新开一个对话。只问图片放哪、收据怎么挂、删除怎么做、冻结后还能改哪几列。不写上传接口,不重做结算单,也不重做权限矩阵。落点是 image_assets、expense_images、expenses.deleted_at,以及系统设计说明书 8.5、8.6。截图贴在对应一轮旁边。
第 1 轮:封面、收据、删除、冻结
我的提示词(大意,原文见截图)
封面要么用预设,要么用自己上传的一张,两套只能取一种。封面可以在账本创建前先上传。一笔消费最多一张收据。二进制不要放在消费行上。删消费不能把金额和结算记录一起抹掉。结算中之后,消费、分摊、成员和已确认的方案都不再改。
回答五件事:图片放哪、收据怎么关联、进行中怎么删消费、结算中和已结清还能改什么、外键怎样拒绝物理删除。

AI 回了什么(摘要)
三件事对上了方向。
- 二进制不放在消费行上
- 收据单独一张关联表,主键就是消费 id,一张图也不能挂到两笔上
- 不要排序号;财务外键用
RESTRICT,不用级联
其余和已经定下的表不一致:
| 它写成的 | 差在哪 |
|---|---|
图片在对象存储,主键 BIGINT,另有状态机和 upload_token | 二进制在库里的 bytea;主键是 uuid;未绑定只是 ledger_id 为空 |
| 预设封面是图片表里的共享行,封面还可以是 none | 预设是账本上的 cover_key,和上传封面恰好有一个 |
| 分摊行打上 voided,旧图 7 到 30 天后物理删除,结清后用红冲 | 消费只写 deleted_at;分摊行不动;没有红冲表 |
| 结算中还能改封面、账本名、备注,还能补收据 | 结算中只改转账登记 |
| 用触发器看账本状态来拦删除 | 状态由服务端拒绝,数据库只拦 DELETE 父行 |

我采纳
图片独立成表。收据以消费为主键,不要排序号。外键不级联。
我拒绝
对象存储、状态机、预设封面共享行、分摊作废标记、红冲、触发器,以及冻结后还改封面和收据。
第 2 轮:按表名把五件事收紧
我的提示词(大意,原文见截图)
- 表用
image_assets、expense_images。ledger_id可空,kind取COVER/RECEIPT,二进制放content cover_key与cover_image_id恰好一个有值。收据表主键是expense_id,image_id唯一- 删除只写
expenses.deleted_at。结算中只改转账的登记四列。已结清只写本人archived_at - 外键
ON DELETE RESTRICT。清理只删未绑定且超过 24 小时的图。已经绑上的不要改回空

AI 回了什么(摘要)
ledger_id 可空、kind 在图片上、封面两列互斥、收据主键、软删除、冻结后的可写列、RESTRICT,这些都按上一问改了。
还差五处:
| 它仍这样 | 要改成 |
|---|---|
CREATE TYPE,状态叫 in_progress | varchar 检查约束;状态仍是 ACTIVE / SETTLING / SETTLED |
| 图片没有宽高、类型、摘要 | 补上 mime_type、byte_size、width、height、sha256 |
收据上再加一列 kind,外键带上它 | 不加这列。用途由服务端在绑定和更换时检查 |
转账列叫 from_member_id / to_member_id | payer_member_id / payee_member_id |
旧图为了回收把 ledger_id 改回空 | 绑过账本的图留在原账本上 |

我采纳
未绑定才能被封面或收据的复合外键用上。更换收据只改 image_id,不删行。不是进行中就拒绝再写 deleted_at,错误码用 LEDGER_FROZEN。
我拒绝
枚举类型、多余的 kind 列、转账列名,以及把已绑定的图改回未绑定再删。
第 3 轮:只改类型、约束和清理
我的提示词(大意,原文见截图)
结构不动。去掉
CREATE TYPE。补上图片的类型、大小、宽高和摘要。收据外键只引用(ledger_id, id)。转账列改回付款人、收款人。清理语句只删ledger_id为空且超过 24 小时的行。

AI 回了什么(摘要)
五处都改了。kind、mime_type、大小、宽高、sha256 都有检查约束。封面用 num_nonnulls(cover_key, cover_image_id) = 1。收据表有 created_at,没有排序号,也没有第二列 kind。删除、冻结后可写的列、外键动作和 24 小时清理,与上一问一致。
它另外写了两句脚本里没有的做法:宽高相乘没有先转成 bigint;建议再建视图 v_active_expenses 来排除已删消费。

我采纳
图片、收据、软删除、冻结列和 RESTRICT 按这一稿。谁能删消费仍按示例 2:进行中,记录人或创建者,并核对 expenses.version。全员净额为 0 时直接结清,仍按示例 1,这一稿没有改那条路径。
我拒绝 / 我补充
最后一稿留下。交进 同账-建表设计.sql 的是下面两处。
| 它最后一稿 | 脚本里 | 为什么动 | |
|---|---|---|---|
| 1 | width * height <= 16000000 | width::bigint * height <= 16000000,宽和高仍各在 1 到 4096 | 两个 integer 直接相乘,边界上的像素积会溢出 |
| 2 | 建议视图 v_active_expenses | 不建这张视图。列表索引带 WHERE deleted_at IS NULL,汇总自己排除已删消费 | 十三张表不再为一条过滤条件加视图 |
清理任务只保留这一句:
DELETE FROM image_assets
WHERE ledger_id IS NULL
AND created_at < now() - interval '24 hours';
四、组员分工与工作量比例
这次是第三次作业,按选题博客里的考核方案算贡献系数。
(1) 不数代码行,不数提交次数,也不看谁先交。
(2) 代码是否完成,以组长风车在群里确认为准。
(3) 文稿和 PPT 以风车确认可以发出为准。
这次要交的是系统设计说明书、数据库设计说明书、答辩 PPT、评审表,以及这篇博客。答辩是 10 月 23 日。
顺序固定。跳过前面的人直接改后面的稿,先在群里说。
- 饶恩哲交体系结构、功能模块层次、设计类图,以及 13 张表的字段。类名和表名用同一套。
- whisper 把记账、分摊、结算、登记已还写成模块接口和数据流,对上类图和表。
- 叶银珍把类图、E-R 图、层次图、泳道图收成同一套符号。两份说明书和 PPT 里的图沿用这一套。
- 风车写系统安全和权限:登录凭证、密码不明文、创建者与普通成员的权限矩阵,服务端再校验一遍。然后核对两份说明书是同一套名称。
- 陈林祺写引言和设计基线,并把改进分析写明改了哪一节。10 月 16 日需求分析答辩没有提问,改动按需求规格说明书和这一版设计的差别写。
- 李润君用鼓山手算核库。13 张表上消费与分摊都是 30000 分,结算 2 笔、共 9000 分。对不上就写成不通过。
- circlexx 做答辩 PPT。评审表上的项都要有一页:PPT 质量、功能模块层次、接口、安全与健壮性、E-R 和表结构、存在的问题。8 分钟能讲完。
- G_bamboopole 把《系统设计说明书》《数据库设计说明书》《系统设计和数据库设计答辩 PPT》的 PDF 传到华为云约定位置。
- 风车把华为云仓库链接和三份 PDF 链接写进博客开头,确认可以发出后再交。
每人在华为云仓库里有一次对得上自己任务编号的提交。
这次作业的交接顺序。前面的人交完,下一个人再改后面的稿。

4.1 本轮的 8 条任务
一条任务一个负责人。点数只用 1、2、3、5、8。
难度和时效各两档,相乘,不组成四种情况。本轮全部按时,时效都是 1.00。普通难度 1.00。认领时写明要先验证技术方案的,难度 1.25。
任务得分 = 点数 × 难度 × 时效。
| 编号 | 学号 | 昵称 | 任务 | 做完的标准 | 点数 | 难度 | 时效 | 任务得分 |
|---|---|---|---|---|---|---|---|---|
| T1 | 072403123 | 饶恩哲 | 体系结构、类图、E-R 和表 | 层次图、设计类图、13 张表的字段能对上鼓山手算 | 8 | 1.25 | 1.00 | 10.00 |
| T2 | 102400109 | circlexx | 答辩 PPT | 评审表上的项都有一页,8 分钟能讲完 | 8 | 1.00 | 1.00 | 8.00 |
| T3 | 102400307 | 风车 | 两份说明书收口和权限 | 模块、类、接口、13 张表、权限是同一套名称 | 3 | 1.00 | 1.00 | 3.00 |
| T4 | 102400201 | 陈林祺 | 引言、设计基线和改进 | 不看代码能知道系统怎么设计;改进写明改了哪一节。答辩没有提问 | 3 | 1.00 | 1.00 | 3.00 |
| T5 | 102400103 | 李润君 | 数据库验收和鼓山手算 | 30000 分、结算 2 笔、9000 分,对不上就判不通过 | 3 | 1.00 | 1.00 | 3.00 |
| T6 | 072403324 | whisper | 模块接口和数据流 | 记账、分摊、结算、登记已还的接口和 13 张表对得上 | 3 | 1.00 | 1.00 | 3.00 |
| T7 | 102400202 | G_bamboopole | 华为云仓库和 PDF | 两份说明书和答辩 PPT 放在约定位置并能打开 | 3 | 1.00 | 1.00 | 3.00 |
| T8 | 052406103 | 叶银珍 | 图的符号统一 | 类图、E-R、层次图、泳道图是同一套符号 | 3 | 1.00 | 1.00 | 3.00 |
全组任务得分 = 10 + 8 + 3 + 3 + 3 + 3 + 3 + 3 = 36.00。
本期人均 = 36.00 / 8 = 4.50。一半是 2.25。
规模只看任务得分够不够这条线,不看单笔高低。
- 达到人均,记 100
- 达到一半、还没到人均,记 60
- 不到一半,记 0
| 昵称 | 任务得分 | 和 4.50 比 | 规模分 |
|---|---|---|---|
| 饶恩哲 | 10.00 | 达到人均 | 100 |
| circlexx | 8.00 | 达到人均 | 100 |
| 风车 | 3.00 | 过半,未到人均 | 60 |
| 陈林祺 | 3.00 | 过半,未到人均 | 60 |
| 李润君 | 3.00 | 过半,未到人均 | 60 |
| whisper | 3.00 | 过半,未到人均 | 60 |
| G_bamboopole | 3.00 | 过半,未到人均 | 60 |
| 叶银珍 | 3.00 | 过半,未到人均 | 60 |
T1 的难度是 1.25,因为认领时写了要先把类图、13 张表和鼓山手算对上。对上之后规模分到顶仍是 100,不会因为单笔 10 分再往上加。T1、T2 记 8 点,因为本阶段看设计和答辩能不能检查,这两条是关键路径。T3 到 T8 各记 3 点,都过了人均的一半,规模没有人记 0。
4.2 五个维度
交付、协作、规范、文档也只打三档:达标 100,能用但有缺陷 60,打不开或对不上任务 0。60 分写明理由。
| 学号 | 昵称 | 交付 | 规模 | 协作 | 规范 | 文档 |
|---|---|---|---|---|---|---|
| 072403123 | 饶恩哲 | 100 | 100 | 100 | 100 | 60 |
| 102400109 | circlexx | 60 | 100 | 60 | 100 | 60 |
| 102400307 | 风车 | 100 | 60 | 100 | 100 | 100 |
| 102400201 | 陈林祺 | 60 | 60 | 60 | 100 | 60 |
| 102400103 | 李润君 | 60 | 60 | 60 | 100 | 60 |
| 072403324 | whisper | 60 | 60 | 60 | 100 | 60 |
| 102400202 | G_bamboopole | 60 | 60 | 60 | 60 | 60 |
| 052406103 | 叶银珍 | 60 | 60 | 60 | 60 | 60 |
4.2.1 记 60 的理由
一项一行。分数仍是 60,只把原来挤在一段里的话拆开。
| 昵称 | 哪一项 | 理由 |
|---|---|---|
| 饶恩哲 | 文档 | 类图和 13 张表能核对,整份答辩还不能按评审表讲完 |
| circlexx | 交付 | 答辩页按评审表列了,组长还没确认可以发出 |
| circlexx | 协作 | PPT 在,没有留下一条指出哪一页该怎么改的记录 |
| circlexx | 文档 | 页能看,整份演示还不能按清单讲完 |
| 风车 | 规模 | 任务得分 3.00,过了 2.25,没有到 4.50 |
| 陈林祺 | 交付 | 引言、设计基线和改进能读,组长还没确认可以发出 |
| 陈林祺 | 协作 | 文稿在,没有留下一条指出具体下一句该怎么改的记录 |
| 陈林祺 | 文档 | 上次的问题写了,整份答辩还不能按清单讲完 |
| 陈林祺 | 规模 | 任务得分 3.00,过了 2.25,没有到 4.50 |
| 李润君 | 交付 | 鼓山手算能复算,验收表还没到可以发出的版本 |
| 李润君 | 协作 | 例子在,没有留下一条指出哪一张表的哪一列要改的记录 |
| 李润君 | 文档 | 30000 分、2 笔、9000 分可以复算,整份演示还不能按清单讲完 |
| 李润君 | 规模 | 任务得分 3.00,过了 2.25,没有到 4.50 |
| whisper | 交付 | 记账、分摊、结算、登记已还的接口写了,还有字段没有和表对死 |
| whisper | 协作 | 接口写了,没有留下一条指出类图哪一个字段要改的记录 |
| whisper | 文档 | 数据流可以照着看,整份演示还不能按清单讲完 |
| whisper | 规模 | 任务得分 3.00,过了 2.25,没有到 4.50 |
| G_bamboopole | 交付 | 目录写了,华为云上的 PDF 还不是可以发出的终稿 |
| G_bamboopole | 协作 | 目录在,没有留下一条指出哪个文件该放到哪的记录 |
| G_bamboopole | 规范 | 还没有对上任务编号的提交 |
| G_bamboopole | 文档 | 链接位置能看,演示还不能按清单讲完 |
| G_bamboopole | 规模 | 任务得分 3.00,过了 2.25,没有到 4.50 |
| 叶银珍 | 交付 | 类图、E-R、层次图、泳道图已经是一套符号,还有一张图没有套完 |
| 叶银珍 | 协作 | 符号在,没有留下一条指出哪一张图偏离了这套符号的记录 |
| 叶银珍 | 规范 | 图的符号还没有对上任务编号的仓库提交 |
| 叶银珍 | 文档 | 主图能看,整份演示还不能按清单讲完 |
| 叶银珍 | 规模 | 任务得分 3.00,过了 2.25,没有到 4.50 |
4.2.2 记 100 的项
| 谁 | 哪些是 100 | 因为 |
|---|---|---|
| 饶恩哲 | 交付、规模、协作、规范 | 层次图、设计类图、13 张表能和鼓山手算对上,任务编号也对得上 |
| circlexx | 规模、规范 | 任务得分 8.00,到了 4.50;PPT 的页和评审表条目、任务编号对得上 |
| 陈林祺、李润君、whisper | 规范 | 各自那一块的章节、手算例子、接口和任务编号对得上 |
| 风车 | 交付、协作、规范、文档 | 两份说明书收成同一套名称,八条任务也在这张表里 |
4.2.3 解开阻塞,各加 2 分
| 昵称 | 加分 | 做了什么 |
|---|---|---|
| 陈林祺 | +2 | 把上次需求分析答辩的问题写成可以落到具体章节的条目,解开了改进分析没法落笔的阻塞 |
| 李润君 | +2 | 鼓山手算核到 13 张表,是当时别人接不了的验证 |
| whisper | +2 | 模块接口把类图和表的字段对齐,解开了对不上的阻塞 |
4.3 个人分
R = 0.35 × 交付 + 0.25 × 规模 + 0.20 × 协作 + 0.12 × 规范 + 0.08 × 文档。
| 昵称 | 计算 | 五项结果 | 加 2 分后 | 过程分 |
|---|---|---|---|---|
| 饶恩哲 | 0.35×100 + 0.25×100 + 0.20×100 + 0.12×100 + 0.08×60 = 35 + 25 + 20 + 12 + 4.8 | 96.8 | 不加 | 96 |
| circlexx | 0.35×60 + 0.25×100 + 0.20×60 + 0.12×100 + 0.08×60 = 21 + 25 + 12 + 12 + 4.8 | 74.8 | 不加 | 74 |
| 风车 | 0.35×100 + 0.25×60 + 0.20×100 + 0.12×100 + 0.08×100 = 35 + 15 + 20 + 12 + 8 | 90 | 不加 | 90 |
| 陈林祺 | 0.35×60 + 0.25×60 + 0.20×60 + 0.12×100 + 0.08×60 = 21 + 15 + 12 + 12 + 4.8 | 64.8 | 66.8 | 66 |
| 李润君 | 0.35×60 + 0.25×60 + 0.20×60 + 0.12×100 + 0.08×60 = 21 + 15 + 12 + 12 + 4.8 | 64.8 | 66.8 | 66 |
| whisper | 0.35×60 + 0.25×60 + 0.20×60 + 0.12×100 + 0.08×60 = 21 + 15 + 12 + 12 + 4.8 | 64.8 | 66.8 | 66 |
| G_bamboopole | 0.35×60 + 0.25×60 + 0.20×60 + 0.12×60 + 0.08×60 = 21 + 15 + 12 + 7.2 + 4.8 | 60 | 不加 | 60 |
| 叶银珍 | 0.35×60 + 0.25×60 + 0.20×60 + 0.12×60 + 0.08×60 = 21 + 15 + 12 + 7.2 + 4.8 | 60 | 不加 | 60 |
饶恩哲的五项结果是 96.8,circlexx 是 74.8,都比过程分多 0.8。陈林祺、李润君、whisper 加完 2 分是 66.8,也比 66 多 0.8。这 0.8 不进入过程分。风车的 90、G_bamboopole 的 60、叶银珍的 60 没有这 0.8。
过程分之和 = 96 + 74 + 90 + 66 + 66 + 66 + 60 + 60 = 578。这组数留作本轮过程记录。折小组分不用这 578。
本轮计入 W 的份额按角色定。组长风车 17,主程序饶恩哲 16,答辩汇报 circlexx 13,陈林祺 12,G_bamboopole 11.2,文档李润君 10,功能 whisper 10,功能叶银珍 10。
17 + 16 + 13 + 12 + 11.2 + 10 + 10 + 10 = 99.2。
4.4 贡献系数
W = 这个人的份额 / 99.2。
- 先得到百分数,再保留两位。
- 两位之后加起来要是 100%。
- 小数部分从大到小,每个加 0.01,直到补齐。
- 小数相同,按封面学号从小到大。
| 学号 | 计算 | 未补余数 | 贡献度 |
|---|---|---|---|
| 102400307 | 17 / 99.2 = 17.137097% | 17.14% | 17.14% |
| 072403123 | 16 / 99.2 = 16.129032% | 16.13% | 16.13% |
| 102400109 | 13 / 99.2 = 13.104839% | 13.10% | 13.10% |
| 102400201 | 12 / 99.2 = 12.096774% | 12.10% | 12.10% |
| 102400202 | 11.2 / 99.2 = 11.290323% | 11.29% | 11.29% |
| 102400103 | 10 / 99.2 = 10.080645% | 10.08% | 10.08% |
| 072403324 | 10 / 99.2 = 10.080645% | 10.08% | 10.08% |
| 052406103 | 10 / 99.2 = 10.080645% | 10.08% | 10.08% |
保留两位之后是 17.14 + 16.13 + 13.10 + 12.10 + 11.29 + 10.08 + 10.08 + 10.08 = 100.00%。三个 10.08% 的小数部分相同,这次没有余数要补,不用再按学号加 0.01。
4.5 本次贡献度
| 学号 | 工作内容 | 贡献度 |
|---|---|---|
| 102400307 | 组长。两份说明书收成同一套:模块、类、接口、13 张表、权限用同一批名称。系统安全和权限矩阵。排期、风险和这次贡献度 | 17.14% |
| 072403123 | 主程序员。体系结构和功能模块层次图;设计类图;E-R 与 13 张表的字段;接口和表对上 | 16.13% |
| 102400109 | 答辩汇报。系统设计和数据库设计答辩 PPT,按评审表讲架构、类图、E-R、接口、权限和开发计划,8 分钟讲完 | 13.10% |
| 102400201 | 引言、设计基线、上次需求分析答辩的改进:评审问题对应到改了哪一节 | 12.10% |
| 102400202 | 华为云仓库。系统设计说明书、数据库设计说明书、答辩 PPT 的 PDF 放在约定位置并能打开 | 11.29% |
| 102400103 | 文档。数据库验收和鼓山手算:消费与分摊 30000 分,结算 2 笔、9000 分,对不上写成不通过 | 10.08% |
| 072403324 | 功能。记账、分摊、结算、登记已还的模块接口和数据流,和类图、表对上 | 10.08% |
| 052406103 | 功能。类图、E-R 图、层次图、泳道图用同一套符号,两份说明书和 PPT 的图样式一致 | 10.08% |
17.14 + 16.13 + 13.10 + 12.10 + 11.29 + 10.08 + 10.08 + 10.08 = 100.00%。
五、开发计划安排
组队之后到学期结束,同账按周往前做。每一周写明时间、里程碑、这一周交出来的东西、产品本身要做到哪一步。11 月中下旬是项目冲刺,计划从 11 月 10 日排到 11 月 30 日,盖住中旬的收口和下旬的 Beta、收尾。
八个人,队名分毫不差。产品是同账:一起吃饭、一起出门,记下谁先垫钱、谁该摊,结束时算出最少要转几笔。
选题时技术栈已经定下,后面每一周都沿用这一套。
- 手机端:React Native、Expo
- 服务端:Fastify
- 数据库:PostgreSQL
- 语言:前后端都写 TypeScript
手机端发出请求,Fastify 处理,PostgreSQL 存账。前后端都写 TypeScript。表示层目录在 mobile/features,业务层目录在 api/modules,金额计算放在共享的 domain,手机上的预览不能代替服务端重算。
选题、原型与需求两周已经过去。这份计划从 10 月 20 日这一周写起,只排同账自己的模块。首版模块仍是账号与登录、账本管理、成员与邀请、消费记录、分摊设置、成员余额、结算与还款、结清与归档,对应 FR01–FR30。排除真实支付、到账校验、离线同步、Web 端、OCR、即时通讯、多币种换算,以及预算、导出、成员移除和取消结算。
编码完成度卡在这些区间:落库这一周 40%–60%,接口这一周 70%–90%。Alpha 之后不再用百分比往上加,改看主路径能不能走通。每一周都用鼓山同一笔账来对。
鼓山一日四名成员,三笔有效消费,库内整数分如下。
| 成员 | 已付 | 应摊 | 消费净额 |
|---|---|---|---|
| 小安 | 12000 | 9000 | +3000 |
| 小白 | 12000 | 6000 | +6000 |
| 小陈 | 6000 | 9000 | −3000 |
| 小丁 | 0 | 6000 | −6000 |
| 合计 | 30000 | 30000 | 0 |
三笔消费是:小安付食堂 12000 分,四人均分;小白付门票 12000 分,四人均分;小陈付车费 6000 分,只由小安与小陈均分。最少转账两笔,合计 9000 分:小陈付给小安 3000 分,小丁付给小白 6000 分。全员净额加总为 0。对不上这张表,当周里程碑记不通过。
5.1 时间以哪一天为准
10 月 23 日的答辩已经写在这一次作业里。23 日之后按大约七天一周来估。六周的起止都写到日。
助教把截止日期写上作业页面之后,以页面上的时间为准。每一周要做的模块、要交的产出保持不动。
同账从这一周到冲刺结束。10 月 20 日这一周是现在所在的一周,在图的第一格。11 月 10 日起的三周是冲刺,排在第二行。

5.2 按周的里程碑和产出
对照表先列结果。里程碑是这一周产品要到达的位置,产出是这一周要交出来、别人打得开的东西。
| 周 | 里程碑 | 产出 | 同账要做到 |
|---|---|---|---|
| 10 月 20 日–10 月 26 日 | 设计收口与落库。答辩 10 月 23 日 | 系统设计说明书、数据库设计说明书、答辩 PPT、评审表、这篇博客。测试库迁移能建出 13 张表。鼓山手算表 | 类名与表名一致,金额以分入库。编码 40%–60%。可运行的记账留到下一周 |
| 10 月 27 日–11 月 2 日 | 消费与结算接口可用,服务端第一次可访问 | 可访问地址、启动步骤、鼓山账的接口记录(请求、响应、库内行) | 登录能拿到会话。消费与分摊同一事务写入。结算预览给出两笔转账;正式结算后写入批次和转账,账本进入结算中。编码 70%–90% |
| 11 月 3 日–11 月 9 日 | Alpha,约 11 月 6 日。自己的手机走通主路径 | 代码规范、测试记录、阶段总结、资源汇总、答辩 PPT、评审表。手机演示录屏或当场操作记录 | 接通账号、建账、六位码邀请、记账、分摊、余额、结算预览与正式结算、还款登记、结清与归档。只演示已经通过的模块 |
| 11 月 10 日–11 月 16 日 | 冲刺第一周:主路径收口 | 交接清单、缺陷清单、第二台手机装得上的内部安装包、鼓山回归记录 | 接通扫码加入、按份分摊、按金额分摊、收据、流水筛选。阻断主路径的缺陷清掉。功能集停在 FR01–FR30 |
| 11 月 17 日–11 月 23 日 | Beta 冲刺,约 11 月 20 日前后。地点听老师安排 | 可安装包、可访问接口、用户使用调查、三步验收记录、阶段总结、答辩 PPT、评审表 | 外部用户在自己的设备上独立完成建账、记账、结算预览,得到付款人、收款人与转账金额 |
| 11 月 24 日–11 月 30 日 | 冲刺收尾 | 使用调查缺陷清单、修完后的安装包、仍可打开的地址、鼓山收尾回归记录 | 只修挡住上述三步的缺陷。功能集停在 Beta 已验收的范围 |
11 月中下旬单独列出来。这三周是冲刺,前面三周的接口和 Alpha 是为了让冲刺只收口、只交付。
| 冲刺周 | 时间 | 里程碑 | 产出 | 验收 |
|---|---|---|---|---|
| 第一周 | 11 月 10 日–11 月 16 日,中旬 | 主路径收口,规格内剩余页面接通 | 交接清单、缺陷清单、内部安装包、回归记录 | 第二台手机能登录、建账、记一笔人均消费、打开结算预览。扫码、按份、按金额、收据、筛选有入口 |
| 第二周 | 11 月 17 日–11 月 23 日,中下旬之交,约 11 月 20 日答辩 | Beta 交付 | 外部可安装包、接口地址、用户使用调查、三步验收记录、总结、PPT | 外部用户独立完成三步,中间没有我们代点。预览里的付款人、收款人、金额与接口一致 |
| 第三周 | 11 月 24 日–11 月 30 日,下旬 | 按调查收尾 | 缺陷清单、修完后的包和地址、收尾回归记录 | 鼓山账仍是 30000 分、2 笔、9000 分。挡住三步的项清掉。没有新模块进仓库 |
下面按周写全。每一节写这一周完成哪些模块、交哪些产出、谁在这一周交什么。
5.3 设计收口与落库:10 月 20 日–10 月 26 日
这是现在所在的一周。答辩在 10 月 23 日。同账这一周把类落到库里。可运行的记账留到 10 月 27 日那一周。
13 张表在测试库建成,类名与表名一致。
| 表 | 放什么 |
|---|---|
| users | 账号。密码只存哈希 |
| auth_sessions | 登录会话 |
| ledgers | 账本。状态 ACTIVE、SETTLING、SETTLED |
| ledger_members | 用户与账本的成员关系 |
| invitations | 六位邀请码、失效时间 |
| expenses | 消费。金额是整数分 |
| expense_splits | 逐人应摊。与消费同一事务 |
| image_assets | 封面和收据的二进制 |
| expense_images | 消费与收据的引用。每笔至多一张 |
| settlement_batches | 正式结算批次。预览不写这张表 |
| settlement_transfers | 正式转账。鼓山账是 2 行、共 9000 分 |
| idempotency_keys | 同一次提交不写两遍 |
| operation_logs | 谁在什么时候改了账 |
金额以整数分持久化。账本状态在界面上是进行中、结算中、已结清。样本消费从这一周起以数据库记录为准。
这一周的产出:
| 产出 | 放到哪 | 谁交 | 怎样算交了 |
|---|---|---|---|
| 系统设计说明书 | 华为云,博客给链接 | G_bamboopole 上传,风车确认能发出 | PDF 打得开,模块名和表名与数据库说明书一致 |
| 数据库设计说明书 | 同上 | 同上 | 13 张表的字段能对上鼓山手算 |
| 答辩 PPT、评审表 | 华为云 / 腾讯文档 | circlexx 做 PPT,G_bamboopole 传 PDF | 评审表上的项都有一页,8 分钟能讲完 |
| 这篇博客 | 作业页面 | 风车确认后交 | 开头有目录,链接能打开 |
| 测试库迁移 | 仓库 | 饶恩哲 | 空库执行后得到上述 13 张表 |
表结构以 10 月 23 日这版数据库设计说明书为准。后面要加字段,先改说明书再改代码。编码按我们自己的区间,这一周走到 40%–60%。
本周仓库里的分工:饶恩哲交迁移;风车核对状态和权限字段;whisper 核对消费、分摊、批次、转账;陈林祺核对图片两张表;李润君用鼓山四个人的已付、应摊、净额对表,30000 分、2 笔、9000 分对不上就写成不通过。
5.4 消费与结算接口:10 月 27 日–11 月 2 日
设计答辩结束之后的一周。这一周写同账的写入接口,并把服务端部署到一个别人打得开的地址。成员邀请页、还款登记页、归档页可以尚未接通。账要能从接口记进去。
这一周完成的模块:
- 注册和登录能拿到会话。后面的写接口都带这个会话。退出后原会话失效。
- 创建账本的人同时成为第一名成员,账本状态为进行中。
- 消费与分摊在同一事务中提交。分摊有一行没写入,整笔回滚,库里不留半笔消费。
- 人均、按份由服务端用最大余数法重算。10000 分三人均分得到 3334、3333、3333,余分给成员标识字典序最小的人。按金额不自动抹平,合计不等于总额就拒绝。
- 结算预览在只读事务里读当前净额,返回最少转账。预览不写 settlement_batches,不把账本改成结算中。全员净额为 0 时返回空方案。
- 正式结算由创建者发起。同一事务里锁账本、核对版本、重算、写入批次和转账、把状态改为结算中。客户端传来的方案不作数。鼓山账写入两行:小陈付给小安 3000 分,小丁付给小白 6000 分。
- 正式结算之后,消费的新增、编辑、删除和成员变更变为只读。
- 封面与收据能入库。收据每笔至多一张。消费只存引用。
- 服务端完成一次可访问部署。地址写进启动步骤,换一台机器按步骤能打开。
第一次部署放在这一周。地址打不开、步骤缺环境变量,都在这一周解决,不堆到 11 月 20 日前后。页面少几张也可以。编码按我们自己的区间,这一周走到 70%–90%。
这一周的产出:
| 产出 | 放到哪 | 谁交 | 怎样算交了 |
|---|---|---|---|
| 可访问地址 | 群里,并写入环境说明 | 饶恩哲给出,G_bamboopole 从另一台机器打开 | 健康检查有响应,未登录写消费被拒绝 |
| 启动步骤 | 仓库里的运行说明 | G_bamboopole | 按步骤能连上 PostgreSQL 并启动 Fastify |
| 鼓山接口记录 | 验收记录 | 李润君 | 三笔消费合计 30000 分;预览为上述两笔;正式结算后库里 2 行、共 9000 分 |
| 说明书修订 | 两份说明书里偏离的那一节 | 陈林祺 | 代码里的字段名和 10 月 23 日的表一致,有偏离就先改说明书 |
本周接口落到人:饶恩哲接登录并部署;风车接建账和结算后的只读;whisper 接消费事务、余额和最少转账;陈林祺接收据上传。circlexx、叶银珍这一周把对应页面的请求改成打这些接口,页面没接完不影响这一周的里程碑。
5.5 Alpha:11 月 3 日–11 月 9 日
约 11 月 6 日。演示在自己的手机上完成,请求打到 10 月 27 日那一周已经部署的服务端。只演示已经通过的模块。
手机上要接通的路径,按操作顺序:
- 注册、登录、退出后再登录,会话仍能恢复。
- 创建账本,创建者出现在成员名单里。
- 另一名成员用六位码加入。人数含创建者不超过 12。后加入的人不追摊加入前的消费。
- 记入鼓山三笔消费。人均由服务端重算。输错金额留在当前页。
- 成员余额显示小安 +3000、小白 +6000、小陈 −3000、小丁 −6000,合计 0。
- 结算预览给出两笔转账。创建者确认后账本变为结算中,消费页变为只读。
- 付款人或创建者登记这两笔已还。登记只改转账状态,不连接真实支付。
- 两笔都登记并且逐人剩余净额为 0 之后,创建者执行整本结清。
- 成员把自己的账本归档。归档只影响本人列表,不删除账目。
权限由服务端校验。普通成员修改他人消费、重置邀请、执行整本结清,接口返回拒绝。手机上把按钮藏起来,不能代替这一层。收据每笔至多一张。预算、导出、成员移除、取消结算不纳入本阶段。
这一周的产出:
| 产出 | 谁交 | 怎样算交了 |
|---|---|---|
| 代码规范 | 饶恩哲起草,风车确认 | 目录、分支、金额用整数分、错误留在当前页,写进仓库 |
| 测试记录 | 李润君 | 上面 9 步每步有过或不过。不过的写明停在哪一步 |
| 阶段总结、资源汇总 | 风车收口 | 写明已通过的模块和尚未接通的页面 |
| 答辩 PPT、评审表 | circlexx | 只讲测试记录里通过的模块 |
| 手机演示 | 叶银珍、circlexx 各自那几页,饶恩哲负责装到自己的手机 | 请求打到已部署的服务端,刷新后鼓山账还在 |
冲刺还没开始。这一周每个工作日用鼓山账走一遍当天已经接通的步骤,不过的记在当天记录里,第二天先改这一条。
本周页面落到人:circlexx 接通记账、分摊、消费详情、结算预览、登记已还;叶银珍接通建账、成员与六位码邀请、余额展示、结清与归档、个人中心;饶恩哲审查后合并到主分支。扫码、按份、按金额、收据选择、流水筛选若这一周没接通,写进阶段总结,下一周补,不在 Alpha 答辩里演示。
5.6 冲刺第一周:11 月 10 日–11 月 16 日
11 月中旬,冲刺从这里开始。Alpha 已经接通的模块保持能用。这一周做两件事:清掉阻断主路径的缺陷,把规格里还没接通的五张页面补上。
五张页面都在 FR 里:扫码加入是 FR10,按份和按金额是 FR13,收据是 FR18,流水筛选是 FR14。补上它们是完成已经写进说明书的功能。预算、导出、成员移除、取消结算、真实支付、离线同步仍然留在排除项里。
主路径是:登录、建账、邀请加入、记一笔人均消费、看到余额、打开结算预览。预览要给出付款人、收款人和转账金额。这六步有一步在第二台手机上走不通,这一周不算过去。
工作日怎么排:
| 日期 | 当天完成 | 当天产出 | 负责人 |
|---|---|---|---|
| 11 月 10 日 | 交接清单:已实现模块、13 张表、尚未接通的页面 | 清单放进仓库 | 风车写,李润君按 Alpha 测试记录核对 |
| 11 月 11 日 | 扫码加入能打开加入确认页。二维码载荷按说明书的邀请格式解析 | 第二台手机扫到六位码 | 叶银珍页面,风车接口 |
| 11 月 12 日 | 按份、按金额在分摊面板里可选。按份走最大余数法,按金额合计不等就停在当前页 | 鼓山车费那一笔用按金额复算,小安与小陈各 3000 分 | circlexx 页面,whisper 重算 |
| 11 月 13 日 | 记账页能选一张收据。第二张被拒绝。流水能按日期、付款人、类别取交集 | 一笔带收据的消费,刷新后图还在 | circlexx 选图和筛选,陈林祺图片接口 |
| 11 月 14 日 | 内部安装包装到第二台手机,请求打到已部署的服务端 | 安装包放上华为云 | 饶恩哲打包,G_bamboopole 核对下载 |
| 11 月 15 日 | 鼓山账从头走到结算预览 | 回归记录:入口、操作、刷新、净额 | 李润君 |
| 11 月 16 日 | 四条有不过的,退回模块负责人改完再记一次 | 缺陷清单关闭或写明仍开着 | 风车确认 |
这一周的产出就是上表最后一列交齐的那些:交接清单、缺陷清单、内部安装包、回归记录。
5.7 Beta:11 月 17 日–11 月 23 日
约 11 月 20 日前后,地点听老师安排。这是冲刺的交付周。
交付可安装的移动端和可访问的接口。外部用户在自己的设备上独立完成三步,中间不需要我们代点。
| 步骤 | 外部用户自己做 | 屏幕上要出现 | 库里要有 |
|---|---|---|---|
| 建账 | 登录后创建一个账本 | 账本进入进行中列表,创建者在成员里 | ledgers 一行,ledger_members 一行,状态 ACTIVE |
| 记账 | 记入一笔消费,选付款人和参与人 | 流水里看得到这笔,输错留在当前页 | expenses 与 expense_splits 同时在,合计等于总额的分 |
| 结算预览 | 打开预览 | 付款人、收款人、转账金额 | 预览不写 settlement_batches。金额与 whisper 的接口一致 |
演示范围限于已通过验收的模块。正式结算、还款登记、结清与归档若已在 Alpha 通过,可以接着演示;测试记录里没通过的,不放进这一场。真实支付、离线同步、预算、导出、取消结算不进入本阶段。
工作日怎么排:
| 日期 | 当天完成 | 当天产出 | 负责人 |
|---|---|---|---|
| 11 月 17 日 | 打出给外部用户安装的包,地址仍是已部署的服务端 | 包和地址放到华为云 | 饶恩哲打包,G_bamboopole 从外部网络下载并打开 |
| 11 月 18 日 | 找一名组外用户只做建账。我们不碰他的屏幕 | 三步验收记录的第 1 步 | 李润君在场记录,叶银珍改挡住建账的界面 |
| 11 月 19 日 | 同一名用户自己记一笔消费 | 三步验收记录的第 2 步 | 李润君记录,circlexx 改挡住记账的界面 |
| 11 月 20 日 | 同一名用户打开结算预览,读出付款人、收款人、金额。答辩按老师安排的时间 | 三步验收记录的第 3 步,答辩 PPT | 李润君记录,whisper 核对金额,circlexx 讲已通过的页 |
| 11 月 21 日 | 把这名用户和其他试用的人的意见收成使用调查 | 用户使用调查 | 风车收口 |
| 11 月 22 日 | 调查里挡住三步的项,退回模块负责人 | 当天缺陷清单 | 各模块负责人改自己的目录 |
| 11 月 23 日 | 三步再走一遍。鼓山账仍用来核对 30000 分、2 笔、9000 分 | 阶段总结、评审表 | 李润君回归,风车确认可以演示的模块 |
这一周交出来的是安装包、接口地址、用户使用调查、三步验收记录、阶段总结、答辩 PPT 和评审表。
5.8 冲刺收尾:11 月 24 日–11 月 30 日
Beta 演示之后的一周,仍算冲刺。按用户使用调查里挡住三步的记录改。没挡住建账、记账、结算预览的意见记在调查里,这一周不排进仓库。
| 日期 | 当天完成 | 当天产出 | 负责人 |
|---|---|---|---|
| 11 月 24 日 | 把调查拆成「挡住三步」和「其余意见」两列 | 收尾缺陷清单 | 李润君拆,风车确认哪几条要改 |
| 11 月 25 日 | 建账、邀请页上的阻断项 | 叶银珍的提交 | 叶银珍 |
| 11 月 26 日 | 记账、结算预览上的阻断项 | circlexx、whisper 各自的提交 | 页面归 circlexx,金额归 whisper |
| 11 月 27 日 | 鼓山账再走一次:30000 分、2 笔、9000 分,净额加总为 0 | 收尾回归记录 | 李润君 |
| 11 月 28 日–11 月 29 日 | 安装包和地址保持可下载、可打开 | G_bamboopole 各打开一次 | G_bamboopole,打不开退回饶恩哲 |
| 11 月 30 日 | 功能集与 Beta 已验收的范围一致,清单里挡住三步的项已关闭 | 修完后的包、仍可访问的地址 | 风车确认 |
功能集停在 Beta 已验收的范围。这一周没有新的需求编号进入实现。
5.9 一个里程碑怎样算过去
每个里程碑按该周的模块验收。金额以整数分守恒,成员净额加总为 0。
入口可进入,操作完成,刷新后数据仍在,净额仍可查询。四条都对上,才进入下一周。
四条在不同的周,检查的入口不一样。
| 周 | 入口 | 操作 | 刷新后仍在 | 净额 |
|---|---|---|---|---|
| 10 月 20 日–10 月 26 日 | 迁移能在空库建出 13 张表 | 鼓山数额能写入 | 重新查询记录还在 | 按消费和转账算出的净额与手算一致 |
| 10 月 27 日–11 月 2 日 | 地址可访问,登录能拿到会话 | 三笔消费和一次预览、一次正式结算调用成功 | 重启服务后再查,行还在 | 预览两笔共 9000 分,全员净额 0 |
| 11 月 3 日–11 月 16 日 | 自己的手机或第二台手机进得了页面 | 主路径六步做完 | 杀掉应用再打开,账还在 | 四个人的净额与鼓山表一致 |
| 11 月 17 日–11 月 30 日 | 外部用户自己的设备进得了应用 | 建账、记账、结算预览三步独立完成 | 换一次网络再打开,账还在 | 预览金额与接口一致,鼓山回归仍通过 |
李润君填写这四条。有一条空白或不过,风车不确认进入下一周。
5.10 组队后到冲刺的安排
十月初组队定题之后,同账沿用 React Native、Expo、Fastify、PostgreSQL。10 月 20 日这一周把用户到转账落成 13 张表。10 月 27 日那一周接通消费与结算接口,并完成一次可访问部署。11 月 6 日前后在自己的手机上接通账号到归档。11 月 10 日进入冲刺:中旬收口主路径并接通规格内剩余页面,约 11 月 20 日交出可安装客户端,11 月 24 日那一周按使用调查修挡住三步的缺陷。23 日之后的具体日期以作业页面为准,每周要完成的模块按上面各节执行。
5.11 分工怎么划
一条开发部分只有一个负责人。登录、底部导航、建表、审查和部署归主程序员。页面清单、账本状态和权限归组长。消费金额、分摊和最少转账归服务端账务。手机上的记账和结算页面归移动端账务。账本、邀请、统计、结清页面和界面变量归移动端账本。封面与收据的存取归图片模块。鼓山手算和每周回归归验收。地址、安装包和华为云目录归发布。
合并进主分支之前,饶恩哲审查,风车确认可以合入。李润君对不上鼓山数额,就记不通过,退回该模块的负责人。同一张表的写入不由两个人各写一套。
下图分三块,每块从左看到右。第 1 块是记账时手机把操作交给服务端。第 2 块是代码进主分支:各人提交,饶恩哲审查,风车确认。第 3 块是合入之后:李润君用鼓山账核对,对不上退回第 1 块的负责人;饶恩哲打出地址和安装包,G_bamboopole 从外面打开。


| 模块 | 需求 | 服务端目录与负责人 | 手机端目录与负责人 |
|---|---|---|---|
| 账号 | FR01–FR03 | api/modules/auth,饶恩哲 | mobile/features/auth,饶恩哲 |
| 账本 | FR04–FR06 | api/modules/ledgers,风车 | mobile/features/ledgers,叶银珍 |
| 成员与邀请 | FR07–FR11 | api/modules/invitations,风车 | 邀请页在叶银珍的账本功能里 |
| 消费与分摊 | FR12–FR17 | api/modules/expenses,whisper | mobile/features/expenses,circlexx |
| 封面与收据 | FR18 | api/modules/images,陈林祺 | 封面在叶银珍的建账页;收据在 circlexx 的记账页。两边只调用图片接口 |
| 余额与支出统计 | FR19–FR20 | api/modules/insights,whisper | mobile/features/insights,叶银珍 |
| 结算方案、批次与转账 | FR21–FR25 | api/modules/settlements,whisper | mobile/features/settlements,circlexx |
| 冻结、结清、归档 | FR22、FR26、FR27 的状态 | 风车写在 ledgers | 结清与归档页面归叶银珍 |
| 个人中心 | FR28–FR30 | api/modules/users,饶恩哲 | mobile/features/profile,叶银珍 |
| 迁移、审查、部署 | — | 饶恩哲 | 安装包由饶恩哲打出 |
| 鼓山手算与每周回归 | — | 李润君 | 手机上的四条也由李润君记 |
| 地址、安装包、华为云 | — | G_bamboopole 归档与核对 | — |
消费与分摊由 whisper 在同一事务里提交。收据文件先经陈林祺的图片接口入库,消费只存引用。结算方案由 whisper 计算并写入批次和转账。账本改为结算中、已结清,以及个人归档时间,由风车写入。预览不写正式表。复制结算方案到剪贴板是 circlexx 的页面动作,不新增服务端写入。
5.12 每人的角色和详细开发部分
下表是分工总表。每一人一行,角色和开发部分都写到目录、需求编号和做完的标准。后面 5.12.1 起按人写每一周交什么。
| 学号 | 昵称 | 角色 | 负责的详细开发部分 | 做完的标准 |
|---|---|---|---|---|
| 102400307 | 风车 | 组长,服务端账本与权限 | 页面清单、排期、合并确认。api/modules/ledgers 与 api/modules/invitations:创建账本、账本列表与详情、成员名单、查看和复制邀请、重置邀请、手输或扫码后的加入确认。账本状态 ACTIVE、SETTLING、SETTLED。正式结算后消费与成员只读。整本结清和本人归档时间写在账本与成员上。权限在服务端再校验:普通成员不能改他人消费,不能重置邀请,不能整本结清。未加入者读不到账目 | 普通成员调用上述接口被拒绝。创建者才能正式结算和整本结清。状态名与 10 月 23 日说明书一致。后加入的成员不追摊旧消费。账本人数含创建者最多 12 |
| 072403123 | 饶恩哲 | 主程序员 | 仓库、分支、审查合并。13 张表的迁移。api/modules/auth 与 mobile/features/auth:注册、登录、退出、会话恢复,登录页和底部导航。api/modules/users 的本人资料。idempotency_keys、operation_logs 随业务事务写入。10 月 27 日这周把 Fastify 部署成可访问地址。11 月 14 日打出内部安装包,11 月 17 日打出外部可安装包 | 空库迁移得到 13 张表。登录后请求带会话,退出后原会话失效。密码不明文。第二台手机和外部用户的设备能装上包并打开已部署的服务端。金额算法不写在 auth 和 users 里 |
| 072403324 | whisper | 服务端账务 | api/modules/expenses:消费新增、编辑、删除、详情和流水。分摊与消费同一事务,失败整笔回滚。人均与按份用最大余数法,按金额不自动抹平。api/modules/insights:已付、应摊、净额、剩余净额由有效消费和已登记转账派生,不另存一份余额。api/modules/settlements:预览生成最少转账,全员净额为 0 时返回空方案;正式结算把方案写入批次和转账。登记已还只把 settlement_transfers 的状态改为已还 | 鼓山账有效消费 30000 分。预览和正式转账都是小陈→小安 3000 分、小丁→小白 6000 分,共 2 笔、9000 分。刷新后净额仍可查询,全员加总为 0。预览不插入正式批次 |
| 102400109 | circlexx | 移动端账务 | mobile/features/expenses 与 mobile/features/settlements:新增和编辑消费、分摊面板、流水和筛选、消费详情、结算预览、结算进度、复制方案、登记已还。金额、参与人、分摊方式提交给 whisper 的接口,页面展示返回值。输错留在当前页。正式结算后这些页按风车返回的状态变为只读 | 预览上的付款人、收款人、金额与接口一致。按份、按金额、筛选在 11 月 13 日前可点。页面不直接写 PostgreSQL,不自己改最终应摊 |
| 052406103 | 叶银珍 | 移动端账本与界面 | 颜色、字号、按钮、卡片、底部栏写成同一套变量,用到账本、记账、结算和结清各页。mobile/features/ledgers、insights、profile:账本列表、创建账本、账本详情、成员与邀请、扫码加入、支出统计、结清与个人归档、个人中心。支出统计的数来自 insights,还款不计入支出 | 建账、邀请、看统计、归档都能从底部导航进到对应接口。11 月 11 日前扫码能打开加入确认。界面变量和说明书里的图用同一套名称。不改接口字段,不改金额算法 |
| 102400201 | 陈林祺 | 图片与规格对照 | api/modules/images:封面与收据写入 image_assets,收据经 expense_images 绑定消费。每笔消费至多一张收据,读取要登录并且属于该账本。实现若偏离 10 月 23 日的类图或表,先改说明书里对应的那一节,再允许代码合并 | 第二张收据被拒绝。未加入者取不到图片二进制。说明书、表、接口字段是同一套名称。11 月 13 日前记账页能通过这个接口看到收据 |
| 102400103 | 李润君 | 验收 | 鼓山一日按 13 张表和接口复算,每个里程碑填写四条:入口、操作、刷新后仍在、净额可查询。维护同一份种子数额:三笔消费、两笔转账。Beta 在场记录外部用户的三步,用户的手不由我们代替。对不上就写成不通过,退回该模块负责人 | 30000 分、2 笔、9000 分可以复算。净额加总不为 0,或刷新后记录消失,记不通过。六周各有一份验收记录 |
| 102400202 | G_bamboopole | 发布 | 华为云目录。系统设计说明书、数据库设计说明书、答辩 PPT、安装包、环境说明、每周验收记录放在约定位置。10 月 27 日起核对地址,11 月 14 日起核对安装包能否下载。运行说明写清 Node、PostgreSQL、Expo 的启动顺序。不改业务计算,不改表结构 | 约定位置上的文件能打开。地址打不开或包下不下来,记不通过,退回饶恩哲。Beta 那一周从外部网络下载成功 |
5.12.1 风车
组长。服务端账本、邀请、状态和权限归这一人。排期以这一节的六周为准,作业页面改了截止日期,风车改日期、不改模块。
| 周 | 这一周写的部分 | 交出的结果 |
|---|---|---|
| 10 月 20 日–10 月 26 日 | 权限矩阵和 ACTIVE、SETTLING、SETTLED 与两份说明书对齐 | 说明书里创建者、普通成员、未加入者各能做的操作是同一张表 |
| 10 月 27 日–11 月 2 日 | 创建账本、成员查询。正式结算成功后把消费和成员改为只读 | 普通成员在结算中调用新增消费被拒绝 |
| 11 月 3 日–11 月 9 日 | 六位码加入、重置邀请、整本结清、本人归档 | Alpha 路径第 3、8、9 步的接口通过 |
| 11 月 10 日–11 月 16 日 | 扫码查询接口;缺陷按模块退回 | 交接清单、11 月 16 日的确认 |
| 11 月 17 日–11 月 23 日 | 收使用调查,圈出挡住三步的项 | 调查定稿,阶段总结里的可演示模块 |
| 11 月 24 日–11 月 30 日 | 确认没有新需求编号进入仓库 | 收尾清单上挡住三步的项已关闭 |
5.12.2 饶恩哲
主程序员。账号、迁移、底部导航、审查和部署归这一人。业务金额的计算留在 whisper 的目录。
| 周 | 这一周写的部分 | 交出的结果 |
|---|---|---|
| 10 月 20 日–10 月 26 日 | 13 张表迁移,空库能建出来 | 迁移文件进入仓库 |
| 10 月 27 日–11 月 2 日 | 注册、登录、退出、会话。服务端部署到可访问地址 | 地址和一份能登录的账号 |
| 11 月 3 日–11 月 9 日 | 登录页、底部导航、审查合并。代码规范 | 自己的手机能登录并切到账本、记账、结算 |
| 11 月 10 日–11 月 16 日 | 内部安装包 | 11 月 14 日第二台手机装上 |
| 11 月 17 日–11 月 23 日 | 外部可安装包 | 11 月 17 日包可下载 |
| 11 月 24 日–11 月 30 日 | 按收尾清单改 auth 和安装配置里挡住三步的项 | 11 月 30 日的包仍可安装 |
5.12.3 whisper
服务端账务。消费、分摊、余额、最少转账和登记已还的金额状态归这一人。账本改成结算中由风车在同一事务的后半段写,两人按 10 月 23 日的正式结算顺序接,不各写一套状态。
| 周 | 这一周写的部分 | 交出的结果 |
|---|---|---|
| 10 月 20 日–10 月 26 日 | 消费、分摊、批次、转账的字段与鼓山列对齐 | 字段对照,30000 分和 9000 分对得上 |
| 10 月 27 日–11 月 2 日 | 消费事务、预览、正式批次与转账、余额查询 | 鼓山接口记录里的两笔转账 |
| 11 月 3 日–11 月 9 日 | 编辑、删除、登记已还。删除用软删除,汇总排除已删消费 | Alpha 第 4、5、6、7 步的金额通过 |
| 11 月 10 日–11 月 16 日 | 按份、按金额的服务端重算。流水筛选条件 | 车费 6000 分按金额各 3000 分 |
| 11 月 17 日–11 月 23 日 | 预览接口保持和页面同一套付款人、收款人、金额 | 外部用户读出的数与接口一致 |
| 11 月 24 日–11 月 30 日 | 只改挡住预览或记账的计算缺陷 | 11 月 27 日鼓山回归通过 |
5.12.4 circlexx
移动端账务。记账、分摊、流水、结算预览和登记已还的页面归这一人。最终应摊用接口返回值。
| 周 | 这一周写的部分 | 交出的结果 |
|---|---|---|
| 10 月 20 日–10 月 26 日 | 答辩 PPT 按评审表讲到开发计划这一节 | 8 分钟能讲完的 PPT |
| 10 月 27 日–11 月 2 日 | 记账页改成提交到消费接口,失败留在当前页 | 一笔人均消费能从页面写入 |
| 11 月 3 日–11 月 9 日 | 分摊、详情、预览、登记已还接通 | Alpha 手机路径里记账到登记已还可点 |
| 11 月 10 日–11 月 16 日 | 按份、按金额、流水筛选、收据选择 | 11 月 12 日、13 日的页面记录 |
| 11 月 17 日–11 月 23 日 | 预览页让外部用户能读出三列:付款人、收款人、金额 | 三步验收的第 2、3 步 |
| 11 月 24 日–11 月 30 日 | 调查里记账和预览页面上的阻断项 | 11 月 26 日的提交 |
5.12.5 叶银珍
移动端账本和界面。建账、邀请、统计、结清、个人中心和全组界面变量归这一人。
| 周 | 这一周写的部分 | 交出的结果 |
|---|---|---|
| 10 月 20 日–10 月 26 日 | 说明书和 PPT 的图保持同一套符号,并列出页面将用的颜色、字号、按钮变量 | 变量名和层次图、泳道图一致 |
| 10 月 27 日–11 月 2 日 | 创建账本页打到风车的接口 | 创建一个进行中的账本 |
| 11 月 3 日–11 月 9 日 | 列表、详情、六位码邀请、余额与统计展示、结清和归档 | Alpha 路径里建账、邀请、余额、归档可点 |
| 11 月 10 日–11 月 16 日 | 扫码加入 | 11 月 11 日第二台手机扫到加入确认 |
| 11 月 17 日–11 月 23 日 | 外部用户建账时卡住的界面 | 三步验收的第 1 步 |
| 11 月 24 日–11 月 30 日 | 建账和邀请上的阻断项 | 11 月 25 日的提交 |
5.12.6 陈林祺
图片和规格对照。封面、收据的存取,以及说明书和代码的字段一致,归这一人。
| 周 | 这一周写的部分 | 交出的结果 |
|---|---|---|
| 10 月 20 日–10 月 26 日 | 引言、设计基线、image_assets 与 expense_images 的字段 | 两张表和说明书是同一套列名 |
| 10 月 27 日–11 月 2 日 | 上传和鉴权读取。收据第二张拒绝 | 未加入者拿不到图 |
| 11 月 3 日–11 月 9 日 | 封面能挂在新建账本上 | 账本详情看得到封面或预设封面 |
| 11 月 10 日–11 月 16 日 | 和 circlexx 接上记账页的收据选择 | 11 月 13 日刷新后收据还在 |
| 11 月 17 日–11 月 23 日 | 代码若因修缺陷偏离表,先改说明书对应节 | 合并前字段名一致 |
| 11 月 24 日–11 月 30 日 | 收据挡住记账时才改图片接口 | 没有新的图片种类 |
5.12.7 李润君
验收。六周用同一笔鼓山账,归这一人记过或不过。
| 周 | 检查什么 | 交出的结果 |
|---|---|---|
| 10 月 20 日–10 月 26 日 | 13 张表对上已付、应摊、净额 | 手算表。对不上写成不通过 |
| 10 月 27 日–11 月 2 日 | 接口记录:30000 分、预览 2 笔、正式 9000 分 | 接口验收记录 |
| 11 月 3 日–11 月 9 日 | 手机上 9 步 | Alpha 测试记录 |
| 11 月 10 日–11 月 16 日 | 第二台手机主路径四条 | 11 月 15 日回归记录 |
| 11 月 17 日–11 月 23 日 | 外部用户三步,记录时不代点 | 三步验收记录、用户使用调查里的事实部分 |
| 11 月 24 日–11 月 30 日 | 收尾回归,并把调查拆成挡住三步和其余意见 | 11 月 24 日清单、11 月 27 日回归 |
5.12.8 G_bamboopole
发布。华为云上的文件、地址和安装包能不能打开,归这一人核对。
| 周 | 这一周放上去的 | 怎样算对 |
|---|---|---|
| 10 月 20 日–10 月 26 日 | 两份说明书、答辩 PPT | 博客里的链接能打开 PDF |
| 10 月 27 日–11 月 2 日 | 地址、启动步骤 | 另一台机器按步骤能打开服务端 |
| 11 月 3 日–11 月 9 日 | 代码规范、测试记录、阶段总结、资源汇总 | 目录约定位置上找得到 |
| 11 月 10 日–11 月 16 日 | 内部安装包、交接清单 | 11 月 14 日包能下载 |
| 11 月 17 日–11 月 23 日 | 外部安装包、使用调查、三步记录、Beta 的 PPT | 外部网络能下载,链接写进博客或作业页 |
| 11 月 24 日–11 月 30 日 | 修完后的包、收尾清单 | 11 月 28 日、29 日各打开一次地址和包 |
5.13 各周交到谁手里
| 周 | 主责 | 这一周写进仓库的部分 | 同时要交的记录 |
|---|---|---|---|
| 10 月 20 日–10 月 26 日 | 饶恩哲 | 13 张表的迁移。风车核对状态与权限,whisper 核对消费、分摊、批次、转账,陈林祺核对图片 | 李润君的鼓山手算。两份说明书和答辩 PPT 由 G_bamboopole 放到华为云 |
| 10 月 27 日–11 月 2 日 | whisper | 消费与分摊事务、余额、最少转账预览、正式批次与转账。风车接上冻结。陈林祺接上收据。饶恩哲部署并接上登录 | 可访问地址、启动步骤、鼓山接口记录 |
| 11 月 3 日–11 月 9 日 | circlexx、叶银珍 | circlexx 接通记账到登记已还。叶银珍接通建账、邀请、余额、结清与归档。饶恩哲审查后合并 | 手机主路径记录、代码规范、测试记录、阶段总结 |
| 11 月 10 日–11 月 16 日 | 风车 | 叶银珍补扫码。circlexx 补按份、按金额、筛选。陈林祺补收据。饶恩哲打内部包。缺陷退回各模块 | 交接清单、缺陷清单、回归记录 |
| 11 月 17 日–11 月 23 日 | 饶恩哲、李润君 | 外部安装包。各人只改自己模块上挡住三步的缺陷。G_bamboopole 从外部网络核对 | 用户使用调查、三步验收记录、阶段总结、答辩 PPT |
| 11 月 24 日–11 月 30 日 | 李润君 | 挡住三步的项退回对应负责人。没有新模块进入仓库 | 收尾缺陷清单、仍可安装的包、仍可打开的地址 |
5.14 里程碑
每一周写同账这一阶段完成的模块。要交的材料见 5.2 的对照表。
| 里程碑 | 时间 | 完成的模块 |
|---|---|---|
| 设计收口与落库 | 10 月 20 日–10 月 26 日,答辩 10 月 23 日 | 13 张表落成,类名与表名一致。金额以整数分持久化。账本状态为进行中、结算中、已结清。鼓山手算能对上表:30000 分、2 笔、9000 分。本周交付说明书、PPT 和迁移 |
| 消费与结算接口 | 10 月 27 日–11 月 2 日 | 登录可用。消费与分摊同一事务写入,分摊失败则整笔回滚。结算预览给出小陈→小安 3000 分、小丁→小白 6000 分;全员净额为 0 时返回空方案。正式结算后账本进入结算中,消费与成员只读。服务端有一个可访问地址 |
| Alpha | 11 月 3 日–11 月 9 日,约 11 月 6 日 | 自己的手机接通账号与登录、账本管理、六位码邀请、消费记录、分摊设置、成员余额、结算预览与正式结算、还款登记、整本结清、个人归档。权限由服务端校验。只演示已经通过的模块 |
| 冲刺收口 | 11 月 10 日–11 月 16 日 | 交接已实现模块和 13 张表。接通扫码加入、按份分摊、按金额分摊、收据和流水筛选。阻断主路径的缺陷修完。第二台手机能装上内部包 |
| Beta | 11 月 17 日–11 月 23 日,约 11 月 20 日前后 | 可安装的移动端与可访问的接口。外部用户独立完成账本创建、消费录入和结算预览,得到付款人、收款人与转账金额。演示范围限于已通过验收的模块 |
| 冲刺收尾 | 11 月 24 日–11 月 30 日 | 使用调查里挡住上述三步的缺陷修完。功能集保持在 Beta 已验收的范围。安装包和地址仍然可打开 |
相关推荐
哈工大理论力学 ① 第七版-绝对正版答案
习题分毫不差
PJ85718DM+MKV46F256VLH16构建HVAC高稳温度监测系统
温度传感器是工业物联网数据采集的起点,其精度、抗干扰性与通信可靠性直接决定边缘感知质量。基于I²C/SPI接口的高精度数字温度传感器(如PJ85718DM)配合具备FlexIO协议引擎的ARM Cortex-M4微控制器(如MKV46F256VLH16),可实现从物理信号采集、边缘预处理到多协议分发的全链路闭环。该组合突破传统单总线或SMBus方案在HVAC强电磁环境下的稳定性瓶颈,支持Modbus RTU、BACnet MS/TP等工业协议硬件卸载,并通过GND分割、硬件时间戳、专用内存池等工程实践保障本
银行级机器学习系统落地:从模型正确性到系统韧性的工程实践
机器学习模型部署不是算法问题,而是系统工程问题——当模型走出Jupyter Notebook,它必须在真实业务环境中应对数据漂移、依赖失效、流量突增与监管审计等多重挑战。其核心原理在于将‘模型输出正确’升级为‘系统行为可预期、可追溯、可降级’,技术价值体现在保障SLA、满足金融级合规(如可审计性、确定性、人工兜底)并支撑高可用决策服务。典型应用场景包括反欺诈、信贷审批、反洗钱等强耦合、高风险业务系统。本文聚焦银行级ML生产落地中的韧性设计、特征供给分层、司法级日志、漂移检测与模型验证四大实操支柱。
CC2430无线SoC架构解析:从8051内核到Zigbee协议栈的嵌入式设计
在嵌入式系统与物联网领域,片上系统(SoC)通过将微控制器、射频前端及专用外设集成于单一芯片,实现了硬件的高度协同与功耗优化。其核心原理在于通过统一内存架构、直接内存访问(DMA)及硬件加速模块(如AES协处理器),在资源受限环境下平衡性能与功耗。这种设计为低功耗无线通信(如Zigbee、6LoWPAN)提供了关键技术支撑,广泛应用于智能家居、工业传感等场景。CC2430作为经典范例,其增强型8051内核与射频子系统的深度整合,尤其体现了软硬件协同在实现高效无线节点中的核心价值。
MP3按时间分割 破解版 MP3Splitter
MP3分割软件,这款软件是按您需要的时间来分割。每分每秒,分毫不差。都可以分割出来。。呵,这是破解版。。


