软件工程现场编程
福大智联 · 简易记账本:13 人协作下的 FastAPI + SQLite3 前后端分离实践
团队:福大智联 · 第七组
仓库:https://github.com/H3HN/expense-book
作业信息
| 项目 | 内容 |
|---|---|
| 作业所属课程 | H202601 软件工程与软件工程实践 |
| 作业要求 | 现场编程 |
| 作业的目标 | 用 FastAPI + SQLite3 + 原生 ES Modules 完成一个可记账、可查询、可统计、可可视化的个人记账本,并在13人分工中实践接口契约、Git 分支与 PR 协作 |
| 团队名称 | 福大智联 · 第七组 |
| 团队成员名字-学号 | 黄俊翔-102402125、洪豊幀-102402124、邓杰-102402122、越长智-102402143、谭段昊-102402133、王世豪-102402134、王伟浩-102402135、陈封-102402119、邱福铭-102402132、陈韦融-102402121、王泽炜-102302113、黄炜杰-102402126、金若曦-102402127 |
一、项目简介与需求对照
本次任务旨在开发一个个人记账本,让用户能对平时的收入和支出进行简单的记录、查看和统计。我们在满足基础需求的前提下,把本地存储从单一 JSON 文件升级为 SQLite3 主存储 + JSON 快照备份,并补充了 CSV 导入导出、一句话记账和 ECharts 图表。
| 需求 | 我们的实现 | 主要入口 |
|---|---|---|
| 基础记账:日期、金额、类别、备注 | 五要素录入,type 区分收入/支出;SQLite 落库,写操作后同步 JSON 快照 |
记账页、POST /api/records |
| 查看数据:最近几笔记录 | 明细页支持类型、类别、日期区间、关键字、条数 5 类筛选 | 明细页、GET /api/records |
| 收支统计:按日期、类别、金额 | 总收入/总支出/结余/笔数,按日趋势、类别排行与占比 | 统计页、GET /api/stats |
| 用户友好 | 单页 5 个页签、Toast 提示、删除二次确认、编辑弹窗、响应式布局 | web/index.html |
| 附加功能:简单图表 | ECharts 收支趋势图 + 类别占比环形图,支持支出/收入切换 | 图表页、web/js/charts.js |
数据存储上,data/records.db 是主存储(表 records,date / type 建索引),data/records.json 是每次写操作后的快照备份;首次启动时若数据库为空,会自动把 JSON 里的演示数据迁移进去。
二、程序运行环境
| 项目 | 版本 / 说明 |
|---|---|
| 操作系统 | Windows 10/11(开发机为 Windows 11) |
| Python | 3.10 及以上(实测 3.11.1) |
| 后端框架 | FastAPI 0.141.1、uvicorn 0.52.4、python-multipart |
| 数据库 | SQLite3(Python 标准库自带) |
| 前端 | 原生 HTML/CSS/ES Modules + ECharts 5(本地 vendor 文件) |
| 测试 | pytest、httpx |
| 浏览器 | Chrome / Edge |
启动命令:
cd expense-book
python -m pip install -r requirements.txt
python -m uvicorn app:app --reload --port 8000
打开 http://127.0.0.1:8000,接口文档在 http://127.0.0.1:8000/docs,健康检查在 http://127.0.0.1:8000/api/health。
【首页与顶部 4 张指标卡】

【明细页筛选、编辑与删除二次确认】



【统计页与 ECharts 双图】


三、系统架构
web/index.html + web/js/*.js(5 个 ES Module)
│ fetch JSON / REST
▼
FastAPI app.py(9 组业务接口 + 静态托管)
│
├─ src/validator.py 字段校验
├─ src/storage.py SQLite3 主存储 + JSON 快照
├─ src/stats.py 区间统计
├─ src/stats_by_category.py 类别排行与占比
├─ src/nl_parser.py 一句话记账解析
├─ src/import_export.py CSV 导入导出
└─ web/js/vendor/echarts.min.js 本地图表库
接口契约(冻结)如下:
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/config |
类别、类型、货币、存储引擎信息 |
| GET | /api/records |
按类型/类别/日期/关键字/条数筛选 |
| POST | /api/records |
新增记录,校验失败返回 400 |
| PUT | /api/records/{id} |
修改记录 |
| DELETE | /api/records/{id} |
删除记录 |
| POST | /api/nl-parse |
一句话记账,支持预览与直接保存 |
| GET | /api/stats |
区间汇总、每日趋势、类别占比 |
| GET | /api/export/csv |
导出 UTF-8 BOM 的 CSV |
| POST | /api/import/csv |
上传 CSV,按主键合并入库 |
存储层的关键设计是“一个写入口,两份数据”:所有增删改都走 src/storage.py,在同一个事务里写 SQLite,随后用临时文件 replace 原子刷新 JSON 快照;查询和统计全部读 SQLite,JSON 只作为可读备份与初始种子。
四、组员职责分工
| 姓名 | 学号 | 负责模块 | 文件 | 验收标准 |
|---|---|---|---|---|
| 黄俊翔 | 102402125 | FastAPI 路由装配、统筹、演示 | app.py |
9 组接口可用,/api/config 返回存储引擎 |
| 洪豊幀 | 102402124 | 数据模型与配置 | src/models.py、src/config.py |
Record 签名不变,含 DB_FILE |
| 邓杰 | 102402122 | 数据合法性校验器 | src/validator.py |
脏金额、坏日期、越界类别被拦截 |
| 越长智 | 102402143 | SQLite 存储层 + JSON 双写 | src/storage.py |
建表建索引、迁移、增删改查、快照同步 |
| 谭段昊 | 102402133 | 类别排行与占比统计 | src/stats_by_category.py |
占比合计 100%,金额降序 |
| 王世豪 | 102402134 | 收支区间统计引擎 | src/stats.py |
filter_by_range 口径正确 |
| 王伟浩 | 102402135 | 自然语言快速记账 | src/nl_parser.py |
2月15日午饭 25 解析为 25 |
| 陈封 | 102402119 | CSV 导入导出解析 | src/import_export.py |
脏行过滤,合法行往返不丢字段 |
| 邱福铭 | 102402132 | 单元测试与端到端验收 | tests/ |
pytest 全绿 |
| 陈韦融 | 102402121 | 前端外壳 + ECharts 图表 | web/index.html、web/js/charts.js |
5 个页签、双图、Tooltip、收支切换 |
| 王泽炜 | 102302113 | API 封装 + 数据管理页 | web/js/api.js、web/js/data_page.js |
写操作后全局刷新,CSV 拖拽导入 |
| 黄炜杰 | 102402126 | 支出 / 收入录入组件 | web/js/add_expense.js、web/js/add_income.js |
前端校验、提交成功 Toast |
| 金若曦 | 102402127 | 明细表格与多维筛选、编辑删除 | web/js/records_view.js |
5 类筛选 + 编辑弹窗 + 二次确认 |
五、关键代码与说明
5.1 CSV 导入:先归一化,再校验,脏行整行丢弃
CSV 是“人手工做出来的文件”,表头、编码、日期格式都不可控。导入端先把每个单元格归一化,再交给校验器;只要有一处不合法,整行丢弃并记录原因,绝不写入半条数据。
def _normalize_header(value):
"""表头归一化:全角转半角、小写、去空格、去掉“(元)”“:”这类装饰。"""
text = unicodedata.normalize("NFKC", _clean_cell(value)).lower()
text = re.sub(r"\s+", "", text)
previous = None
while previous != text: # 循环剥离,处理“金额(元):”这种组合
previous = text
text = text.strip("::*")
text = re.sub(r"\([^)]*\)$", "", text)
return text
for offset, row in enumerate(data_rows):
row_number = first_row_number + offset
if not any(_clean_cell(cell) for cell in row):
continue
candidate = _normalize_candidate(_cells_to_candidate(row, columns))
errors = validate_record(candidate)
if errors: # 脏行整行丢弃,并记录行号与原因
skipped.append({"row": row_number, "errors": errors})
continue
if not candidate.get("id"): # 没有 id 的行生成稳定指纹,重复导入不会重复记账
base = _fingerprint_id(candidate)
occurrences[base] = occurrences.get(base, 0) + 1
suffix = occurrences[base]
candidate["id"] = base if suffix == 1 else f"{base}-{suffix}"
candidates.append(Record.from_dict(candidate).to_dict())
说明:
NFKC先把全角数字、全角括号、全角冒号转成半角,再统一小写、去空格;- 表头支持中英文别名(
日期/date、金额(元)/amount)、列顺序打乱和多余列; - 日期兼容
2026/11/07、2026年11月7日、20261107、Excel 序列号;金额兼容¥1,234.50、25元整、全角数字; parse_csv()额外返回skipped(行号 + 具体错误)和skipped_count,便于接口层展示“过滤了几行”。
5.2 SQLite 建表、种子迁移与 JSON 双写
SCHEMA = """
CREATE TABLE IF NOT EXISTS records (
id TEXT PRIMARY KEY, date TEXT, type TEXT,
amount REAL, category TEXT, note TEXT, created_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_records_date ON records(date);
CREATE INDEX IF NOT EXISTS idx_records_type ON records(type);
"""
def _bootstrap(conn):
count = conn.execute("SELECT COUNT(*) FROM records").fetchone()[0]
if count: # 只在空表时迁移,避免重复灌入种子
return
seeds = _read_json()
if seeds:
_insert_many(conn, seeds)
@contextmanager
def _connect():
...
conn = sqlite3.connect(str(_db_path()), timeout=15)
conn.row_factory = sqlite3.Row
try:
conn.executescript(SCHEMA) # 幂等建表 + 建索引
_bootstrap(conn) # 空库首次启动时迁移 JSON 种子
yield conn
conn.commit() # 正常结束统一提交
except Exception:
conn.rollback() # 任何一步失败整体回滚
raise
finally:
conn.close()
def upsert_records(records):
payload = [Record.from_dict(item).to_dict() for item in records]
if not payload:
return 0
with _LOCK, _connect() as conn:
_insert_many(conn, payload) # INSERT OR REPLACE:按主键合并
_write_json(_fetch_all(conn)) # 同步 JSON 快照
return len(payload)
说明:
SCHEMA里的建表和两个索引都带IF NOT EXISTS,每次连接执行也安全;_db_path()根据DATA_FILE推导,测试把DATA_FILE指到临时目录后,数据库也跟着落在临时目录;_connect()用上下文管理器统一 commit / rollback / close,写操作不会留下半条数据;_write_json()先写.tmp再replace,避免快照写一半;upsert_records用INSERT OR REPLACE,同一份 CSV 重复导入按主键合并。
注:当前版本在模块导入时会调用
init_db()同步一次 JSON 快照,因此启动服务或导入storage时data/records.json可能被重写;演示前如果要保留手工改过的 JSON,记得先备份。
5.3 写操作后的全局刷新
所有写操作(记一笔、一句话记账、编辑、删除、CSV 导入)成功后只做一件事:调用 refreshApp()。
export async function refreshApp() {
try {
const [config, data] = await Promise.all([api.getConfig(), api.getRecords()]);
state.config = config;
state.totals = data.totals;
updateMetricCards(); // 顶部 4 张指标卡
emit("app:changed", { totals: state.totals, records: data.records });
return state.totals; // 激活视图收到事件后重新取数
} catch (error) {
toast(error.message, "error");
throw error;
}
}
说明:
- 指标卡、明细、图表、统计页不互相调用,只订阅
app:changed,避免循环刷新; - 写操作与刷新解耦,新增一个写入口时不需要改四个视图;
- 失败时统一
toast,用户能立刻看到“为什么没保存”。
5.4 一句话记账:金额候选与数量词消歧
for match in _AMOUNT_PATTERN.finditer(text):
raw_number = match.group("number")
after = text[match.end():]
has_currency = bool(
_CURRENCY_PREFIX_PATTERN.search(match.group())
or _CURRENCY_SUFFIX_PATTERN.search(match.group())
)
# “2杯奶茶”里的 2 是数量,不是金额
if not has_currency and re.match(r"\s*(?:" + QUANTIFIER_SUFFIX + r")", after):
continue
# 负号表示非法金额,跳过
if match.group("sign") == "-":
continue
...
return candidates[-1] if candidates else (None, None)
说明:
- 先遮掉日期和“数量+量词”,再找金额,避免把“2月15日”里的 2 当成钱;
- 带
元/¥/人民币的数字优先,纯数字取最后一个候选; - 用
Decimal+ROUND_HALF_UP保留两位小数,避免浮点误差。
六、三个亮点
亮点一:CSV 的“脏数据免疫”
普通的 CSV 导入通常只认标准表头,用户用 Excel/WPS 另存一次就会失败。我们把兼容性做在了导入端:
- 编码:UTF-8 BOM、UTF-16、GB18030 依次尝试,Excel 在中文 Windows 下存的 CSV 不再乱码;
- 表头:中英文别名、大小写、空格、
金额(元)、金额(元):、列顺序打乱、多余列都能识别; - 值:斜杠日期、中文日期、8 位日期、Excel 序列号、货币符号、千分位、
元整、全角数字统一归一化; - 脏行:金额非数字、小于等于 0、日期非法、类别越界、备注超长整行丢弃,并返回
skipped报告; - 幂等:导出的 CSV 带
id,再导入按主键合并;没有id的行按内容指纹生成稳定 ID,同一份文件重复导入不会重复记账。
结果是:演示时把导出的 CSV 用 WPS 打开、改两笔、另存再拖回来,仍然能正确合并,坏行不会污染账本。
亮点二:SQLite + JSON 的“双保险”
- SQLite 负责查询、筛选、统计和并发写入,WAL 模式降低读写互相阻塞;
- JSON 快照每次写操作后原子刷新,出问题可以直接用文本查看、手工修复,也能作为新环境的种子;
- 首次启动自动建表、建索引、迁移种子;数据库路径由
DATA_FILE推导,测试切到临时目录后读写都落在临时目录;
亮点三:零构建前端 + 事件总线式状态同步
- 前端没有 npm/webpack/vite,5 个业务模块都是原生 ES Module,浏览器直接加载;
- ECharts 放在
web/js/vendor/,断网也能出图; - 所有写操作统一走
refreshApp(),通过app:changed广播,指标卡、明细、图表、统计页各自重新取数; - 删除有二次确认、保存有 Toast、空状态有占位。
七、耗时较长 / 争论 / 复审的事件
事件一:从 JSON 全量读写迁移到 SQLite
- 背景:最初每次读写都把整个 JSON 读进内存再整体写回,数据一多就慢,并发写还会互相覆盖。
- 争论:要不要保留 JSON?测试里
monkeypatch了DATA_FILE,数据库应该落在哪?种子数据会不会每次启动都重复灌入? - 解决:保留 JSON 作为快照而不是主存储;
_db_file()根据DATA_FILE的目录推导records.db,测试自动隔离;_bootstrap只在空表时迁移;写入用事务 + 临时文件替换。PR #3 合并后曾出现 app 与测试失效,随后用59d9de0恢复旧接口与种子迁移。 - 收获:存储层最重要的是“接口稳定”,换实现不能影响上层;测试隔离要在设计时就考虑,而不是事后补。
事件二:CSV 兼容边界与“金额(元):”复审
- 背景:Excel/WPS 另存 CSV 后,表头会变成
金额(元),日期会变成2026/11/7,编码可能是 GB18030;如果只按标准格式解析,用户会以为“导入坏了”。 - 争论:兼容要做到什么程度?脏行是报错还是静默丢弃?重复导入要不要去重?
- 解决:导入端做归一化,脏行整行丢弃但通过
parse_csv()返回原因;导出的 CSV 带id,重复导入按主键合并;没有id的行用内容指纹。复审时发现金额(元):因为冒号在括号后面,_normalize_header先剥括号再剥冒号会漏掉,导致金额列整列丢失、所有行被判脏——改成循环剥离后才通过。 - 收获:“能导入”不等于“导入对”。兼容性必须拿真实脏文件测,而不是只测自己导出的标准文件。
事件三:前端写操作后的状态一致性
- 背景:5 个页签各自维护状态,记账成功后如果只刷新当前页,顶部指标卡、图表、明细就会各说各话。
- 争论:让每个模块自己刷新,还是统一一个刷新入口?模块之间要不要互相调用?
- 解决:统一
refreshApp(),写操作成功后调用;通过app:changed广播,激活的视图自己重新取数;Toast 和二次确认也收口到公共层。 - 收获:前端同样需要“单一数据源”。事件总线比模块之间互相调用更容易扩展,新增一个视图只要订阅事件即可。
事件四:13 人 Git 协作与合并顺序
- 背景:13 个人、13 个分支,公共文件(
app.py、models.py、config.py、index.html、api.js)由固定负责人维护,其他人只能改自己的文件。 - 争论:先合谁?出现越界提交怎么办?冲突谁来解决?
- 解决:按依赖顺序合并:模型/配置 → 存储 → 统计/NLP/CSV → 前端外壳 → 表单 → 明细 → 图表 → 数据页 → 测试;每次合并后跑
pytest;冲突用git pull --rebase origin main,只解决自己文件内的冲突;提交时只git add自己的文件。 - 收获:流程本身就是代码质量的一部分。作者身份、提交粒度、合并顺序都会影响最后能不能讲清楚“这行是谁写的、为什么改”。
八、测试与验收
当前仓库的测试全部通过:
$ python -m pytest -q
69 passed
覆盖范围包括:
tests/test_storage.py:建表、索引、种子迁移、增删改查、JSON 快照同步、SQL 参数化;tests/test_stats.py:区间筛选、每日汇总、类别占比、金额精度;tests/test_acceptance.py:9 组接口、CSV 导入导出与幂等、NL 解析、编辑/删除、指标 totals。
| 验收项 | 结果 |
|---|---|
9 组接口可用,/api/config 返回存储引擎 |
通过 |
| SQLite 建表、索引、种子迁移、JSON 双写 | 通过 |
| 脏金额、坏日期、越界类别被拦截 | 通过 |
| CSV 脏行过滤、导出再导入不丢字段 | 通过 |
| 类别占比合计 100%、金额降序 | 通过 |
| 5 个页签、双图、筛选、编辑删除、Toast | 通过 |

九、每个组员的团队编程体验
黄俊翔(102402125 · 路由装配与统筹)
这次我主要负责 app.py 的接口装配、PR 合并和演示节奏。最大的收获是先把接口契约冻结,再让 13 个人并行开发;合并时只看两件事:有没有改别人的文件、测试是不是全绿。演示前我们把 5 个页签的流程完整走了几遍,把时间控制在 15 分钟以内。
洪豊幀(102402124 · 数据模型与配置)
我负责 Record 数据类和全局配置。看似只是几个字段,但字段顺序、默认值、DB_FILE 放在哪里,都会影响存储层和测试。把配置集中到一个文件后,别人改路径不用满仓库搜索,这是我感受最深的一点。
邓杰(102402122 · 校验器)
校验器是“脏数据”进入系统的最后一道门。我学会了把错误信息写清楚:不能只说“金额不合法”,要区分空值、负数、小于 0.01;前端还补了两位小数的拦截。和 CSV 导入联调时,我们也确认了“导入端先清洗、校验器只做最终判断”的边界。
越长智(102402143 · SQLite 存储层)
从 JSON 全量读写迁到 SQLite 是我这次投入最多的部分。建表、索引、WAL、种子迁移、JSON 快照,每一块都踩过坑。最深的体会是:迁移不是把 json.load 换成 SQL 就行,还要保证旧接口、测试隔离和演示数据都不被破坏。
谭段昊(102402133 · 类别排行与占比)
类别占比最麻烦的是“合计必须正好 100%”。直接用浮点除法会出现 99.99% 或 100.01%,最后用“整数分 + 最大余数法”分配比例,才让每一类的占比加起来刚好是 1.0000。这个细节让我理解了统计里“精度”不是小事。
王世豪(102402134 · 区间统计)
我负责区间筛选和汇总。日期格式稍微变一下(2026/11/7、带时间、空值)就会让统计结果不对,所以先做了日期容错和金额安全转换。后来补了日均、极值和月度趋势,才真正体会到“统计算法要先用脏数据喂一遍”。
王伟浩(102402135 · 自然语言记账)
一句话记账看起来简单,实际最难的是消歧:2月15日午饭 25 不能把 2 当成金额,买2杯奶茶 36元 要取 36。我们用“先遮日期和数量词、再找金额候选、取最后一个”的策略,配合大量测试用例,才把误判压下来。
陈封(102402119 · CSV 导入导出)
我负责 CSV 模块。最开始只按标准表头解析,结果用 WPS 另存一次就整列丢失;后来补了编码识别、表头别名、日期/金额归一化和脏行报告。复审时发现 金额(元): 因为冒号位置导致识别失败,改成循环剥离后才修好。最大的收获是:导入功能的验收标准不是“能跑”,而是“拿真实脏文件也能跑对”。
邱福铭(102402132 · 单元测试与验收)
我负责测试和端到端验收。13 个人并行开发时,测试是唯一能阻止“合并后突然坏了”的东西。我们把存储、统计、接口、CSV 都覆盖到 69 项,合并前跑一遍 pytest -q,比口头保证有用得多。
陈韦融(102402121 · 前端外壳与图表)
我负责单页外壳、5 个页签和 ECharts。前端没有构建工具,模块拆分和加载顺序都要自己控制;图表还要处理空数据、切换收支、Tooltip 和断网。把 ECharts 放到本地 vendor 后,演示现场没有网络也不慌。
王泽炜(102302113 · API 封装与数据页)
我负责 api.js 和 data 页。统一 fetch 封装、错误提示、超时、拖拽导入、导入大小限制,都是为了让每个业务模块不用重复写网络代码。最麻烦的是“写操作后全局刷新”,最后用 refreshApp() + app:changed 把状态收口。
黄炜杰(102402126 · 支出/收入录入组件)
我负责两个录入表单。日期默认今天、金额前端先校验、提交成功 Toast、提交按钮 loading 防重复点击,这些细节决定了演示时顺不顺手。和 api.js 联调时也学会了:前端校验只是体验,后端校验才是底线。
金若曦(102402127 · 明细与编辑删除)
我负责明细表格、5 类筛选、编辑弹窗和删除二次确认。表格看起来简单,但空状态、长备注、筛选组合、编辑后同步指标卡都要考虑。删除做成“点两次确认”后,演示时再也没有误删过数据。
十、GitHub 仓库与 commit 记录
仓库地址:https://github.com/H3HN/expense-book
截至 2026-11-07,main 分支 HEAD 为 8e36cb7,共 39 次提交、35 个受版本控制的文件。主要提交记录如下(部分早期提交使用团队共用账号或 GitHub 昵称,后期已切换为本人身份):
| commit | 作者(Git 显示) | 说明 |
|---|---|---|
07bced8 |
黄俊翔 | 后端重构为 FastAPI,新增 update_record、区间筛选、安全导入 |
a5f31ba |
黄俊翔 | 前端单页外壳、API 封装与 5 个业务模块落地,ECharts 本地化 |
a0ca9f7 |
福大智联 | SQLite3 主存储 + JSON 双写迁移,补齐索引与测试 |
992e43b |
jied4227-max | 新增数据合法性校验器 |
38e4b13 |
王世豪 | 增强日期解析容错与金额安全转换 |
c89ec06 |
王世豪 | 支持多维关键词检索与区间日均/极值统计 |
4f82254 |
王世豪 | 新增月度收支趋势聚合函数 |
a4e59f0 |
Shiyue999 | 改进自然语言记账解析 |
f2a44b7 |
Shiyue999 | 完善类别排行与占比精度 |
b0d8503 |
金若曦 | 完善明细筛选、编辑与删除交互 |
3636fcf |
黄炜杰 | 优化支出/收入录入组件 |
c191075 |
Rong-hub1 | 精简页面技术栈说明,快捷记账区自适应 |
334f84c |
shiluooo | 请求超时、导出失败提示、Toast 点击关闭、导入大小限制 |
dbf424e |
陈封 | 增强 CSV 导入导出,兼容中文表头/GB18030/脏格式并返回过滤报告 |
59d9de0 |
黄俊翔 | 恢复旧接口与种子迁移,修复 PR#3 合并后 app 与测试失效 |
f7d1e84 |
HEHN | Merge pull request #4(CSV 模块) |
说明:为便于核对,表中“作者”列保留 Git 提交时的原始显示名;部分同学使用 GitHub 昵称,昵称与姓名的对应关系以仓库作者页和分工表为准。
完整的提交历史、分支和 PR 记录见仓库的 Commits / Pull requests 页面。
十一、结语
这个项目让我们第一次完整体验了“13 个人同时改一个仓库”的过程:接口契约先行、每人只改自己的文件、按依赖顺序合并、每次合并跑测试。技术上的收获是 FastAPI、SQLite、ES Modules 和 ECharts 的落地;协作上的收获是明白了 提交记录、测试结果和演示流程 本身就是软件工程的一部分。
浙公网安备 33010602011771号