尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
实验一考题二 FastAPI 记账接口实验报告
一、实验目的使用 FastAPI 实现简易记账 HTTP 服务掌握请求体、查询参数和 JSON 响应的处理方法。完成流水新增、条件筛选、分页查询和收支汇总理解 SQLite 持久化与事务处理。使用 pytest 验证正常功能、非法参数与并发写入并记录 AI 辅助编码的需求拆解、实现和验证过程。二、实验环境项目实验环境操作系统macOSPython3.12.14Web 框架FastAPI 0.146.0服务程序Uvicorn 0.54.0数据库SQLite 3.53.1测试工具pytest 9.1.1、HTTPX 0.28.1数据保存方式本地 SQLite 文件金额以整数“分”存储运行方式安装requirements.txt中的依赖后在工程目录执行以下命令。详细说明见同目录README.md。python-muvicorn main:app--host127.0.0.1--port8000python-mpytest-v三、实验内容1. 需求分析与验收约定本实验只完成考题二。输入为 HTTP 请求中的 JSON 数据与查询参数输出为 JSON 格式的流水、分页结果、收支汇总或错误信息。接口功能关键规则POST /records新增流水金额大于 0、最多两位小数类型为收入或支出分类非空日期为 YYYY-MM-DD成功返回 201GET /records查询流水可组合筛选类型、分类、起止日期支持 page 和 page_size成功返回 200GET /summary汇总收支按指定日期区间计算收入、支出和结余成功返回 200非法请求错误处理参数校验失败返回 422errors 数组给出字段、原因和错误类型日期区间包含开始和结束当天省略某一端表示不限制该端。开始日期晚于结束日期时拒绝请求。分页默认第 1 页每页 10 条最大每页 100 条超出数据范围返回空列表。查询结果按日期降序、编号降序排列。无数据时汇总返回 0.00。金额字段名为amount类型字段为type分类字段为category日期字段为date。为避免金额计算时的二进制浮点误差建议输入金额字符串服务用 Decimal 校验后转换为整数分保存返回时统一格式化为两位小数字符串。2. 实现过程与主要代码先定义输入模型和校验规则再创建数据库表随后实现新增、筛选分页和汇总最后运行测试与真实 HTTP 调用。下列代码为工程中完整的main.py代码中不含注释。importosimportsqlite3fromcontextlibimportclosingfromdatetimeimportdatefromdecimalimportDecimalfrompathlibimportPathfromtypingimportAnnotated,LiteralfromfastapiimportDepends,FastAPI,Queryfromfastapi.exceptionsimportRequestValidationErrorfromfastapi.responsesimportJSONResponsefrompydanticimportBaseModel,ConfigDict,Field,field_validator RecordTypeLiteral[收入,支出]classRecordInput(BaseModel):model_configConfigDict(extraforbid)amount:DecimalField(gt0,max_digits12,decimal_places2)type:RecordType category:strField(min_length1,max_length50)date:datefield_validator(date,modebefore)classmethoddefvalidate_date(cls,value):ifnotisinstance(value,str):raiseValueError(日期必须使用 YYYY-MM-DD 格式)parseddate.fromisoformat(value)ifparsed.isoformat()!value:raiseValueError(日期必须使用 YYYY-MM-DD 格式)returnvaluefield_validator(category)classmethoddefvalidate_category(cls,value):valuevalue.strip()ifnotvalue:raiseValueError(分类不能为空)returnvaluedefdate_range(start_date:date|NoneNone,end_date:date|NoneNone,):ifstart_dateandend_dateandstart_dateend_date:raiseRequestValidationError([{loc:[query,end_date],msg:结束日期不能早于开始日期,type:date_range,}])returnstart_date,end_datedefcreate_app(db_pathNone):db_pathstr(db_pathoros.getenv(ACCOUNT_DB,records.db))Path(db_path).parent.mkdir(parentsTrue,exist_okTrue)defconnect():connectionsqlite3.connect(db_path,timeout10)connection.row_factorysqlite3.Rowreturnconnectionwithclosing(connect())asconnection:connection.execute(PRAGMA journal_modeWAL)connection.execute( CREATE TABLE IF NOT EXISTS records ( id INTEGER PRIMARY KEY AUTOINCREMENT, amount_cents INTEGER NOT NULL CHECK(amount_cents 0), type TEXT NOT NULL CHECK(type IN (收入, 支出)), category TEXT NOT NULL, date TEXT NOT NULL ) )connection.commit()appFastAPI(title简易记账接口,version1.0.0)app.exception_handler(RequestValidationError)asyncdefvalidation_error(request,exception):errors[{field:..join(map(str,error[loc])),message:error[msg],type:error[type]}forerrorinexception.errors()]returnJSONResponse(status_code422,content{errors:errors})app.exception_handler(sqlite3.OperationalError)asyncdefdatabase_error(request,exception):returnJSONResponse(status_code503,content{errors:[{field:database,message:数据库暂时不可用请稍后重试,type:database_unavailable}]})deffilters(typeNone,categoryNone,dates(None,None)):clauses,values[],[]forcolumn,operator,valuein[(type,,type),(category,,category),(date,,dates[0]),(date,,dates[1])]:ifvalueisnotNone:clauses.append(f{column}{operator}?)values.append(value.isoformat()ifisinstance(value,date)elsevalue)return( WHERE AND .join(clauses)ifclauseselse),valuesdefmoney(cents):returnformat(Decimal(cents)/100,.2f)app.post(/records,status_code201)defadd_record(record:RecordInput):centsint(record.amount*100)withclosing(connect())asconnection,connection:cursorconnection.execute(INSERT INTO records(amount_cents, type, category, date) VALUES (?, ?, ?, ?),(cents,record.type,record.category,record.date.isoformat()))record_idcursor.lastrowidreturn{id:record_id,amount:money(cents),type:record.type,category:record.category,date:record.date.isoformat()}app.get(/records)deflist_records(type:RecordType|NoneNone,category:Annotated[str|None,Query(min_length1,max_length50)]None,page:Annotated[int,Query(ge1)]1,page_size:Annotated[int,Query(ge1,le100)]10,datesDepends(date_range),):where,valuesfilters(type,category,dates)withclosing(connect())asconnection:connection.execute(BEGIN)totalconnection.execute(SELECT COUNT(*) FROM recordswhere,values).fetchone()[0]rowsconnection.execute(SELECT * FROM recordswhere ORDER BY date DESC, id DESC LIMIT ? OFFSET ?,values[page_size,(page-1)*page_size]).fetchall()connection.commit()items[{id:row[id],amount:money(row[amount_cents]),type:row[type],category:row[category],date:row[date]}forrowinrows]return{total:total,page:page,page_size:page_size,items:items}app.get(/summary)defsummary(datesDepends(date_range)):where,valuesfilters(datesdates)withclosing(connect())asconnection:rowconnection.execute( SELECT COALESCE(SUM(CASE WHEN type收入 THEN amount_cents ELSE 0 END), 0), COALESCE(SUM(CASE WHEN type支出 THEN amount_cents ELSE 0 END), 0) FROM records where,values).fetchone()return{income:money(row[0]),expense:money(row[1]),balance:money(row[0]-row[1])}returnapp appcreate_app()新增流水使用参数化 SQL避免将用户输入直接拼接进 SQL。查询中的列名和运算符来自固定程序列表筛选值仍使用占位符传入。列表总数和当前页数据在同一读事务中获取避免并发写入时两次查询读到不同快照。每次请求建立独立 SQLite 连接写操作在事务中提交或回滚SQLite 对写事务进行串行协调主键由数据库分配。WAL 模式支持读写并行连接等待锁的超时为 10 秒。如果数据库忙碌超时等操作错误发生接口返回结构化的 503 提示。该机制适合本实验的小规模本地服务高写入负载仍需进一步压测。图 1 实际源代码片段及验证过程。图片由本次工程源码和执行记录排版生成并非终端原生截图。3. 功能调用与运行结果启动 Uvicorn 后使用 HTTP 客户端向本地服务依次新增以下三条流水再调用筛选和汇总接口。记录图片中的响应来自实际 HTTP 调用。日期类型分类金额元2026-10-01收入工资5000.002026-10-02支出餐饮35.502026-10-03支出交通12.80查询GET /records?type支出page1page_size1应得到符合条件的总记录数 2当前页仅返回日期较新的交通支出 12.80 元。查询GET /summary?start_date2026-10-01end_date2026-10-03实际返回{income:5000.00,expense:48.30,balance:4951.70}人工核对支出为 35.50 12.80 48.30 元结余为 5000.00 − 48.30 4951.70 元与接口结果一致。图 2 新增、筛选分页及区间汇总的实际 HTTP 响应展示。4. 测试用例与异常处理测试文件test_main.py使用临时数据库覆盖正常调用和多种边界情况。测试项目验证内容预期结果正常新增完整合法流水201返回编号及流水字段金额边界0、负数、超过两位小数、NaN、超过位数上限422不写入数据字段异常非法类型、空白分类、缺少金额、多余字段422结构化错误日期异常不存在的日期、非约定请求体日期格式422筛选分页类型、分类和日期组合筛选翻页及超出末页总数、排序和当前页结果正确汇总日期边界内的收入、支出、结余与预期金额相等区间外记录被排除空数据空列表及空区间汇总空列表与 0.00查询异常第 0 页、每页超过 100 条、反向日期区间、非法日期422并发写入10 个线程并发提交 100 笔 0.01 元收入全部 201100 个不同编号总收入 1.00 元持久化重建应用并读取同一数据库已写入数据仍存在实际执行结果为24 passed1 warning。警告来自测试客户端对 HTTPX 的兼容性弃用提示不影响本次测试通过。并发用例同时检查成功状态码、记录总数、编号唯一性和汇总金额。该结果验证了本次测试范围不能据此推断任意并发规模均无风险。另外向正在运行的 HTTP 服务提交金额为 0 的请求实际返回 422响应含errors数组与body.amount字段。图 3 pytest 实际输出与非法金额请求的实际响应展示。原始记录保存在pytest-result.txt和evidence.json中。5. AI 协作与代码审查记录本次协作中的用户要求是完成考题二、生成四部分 Markdown 报告、提供无注释主要代码及过程和结果图片。AI 将任务拆解为需求约定、接口实现、测试、真实 HTTP 验证和报告整理。阶段本次处理内容验证或保留记录需求澄清明确字段名、类型取值、金额精度、日期边界及分页约束本报告第 1 节编码新建 main.py 与 test_main.py完整源文件及 changes.diff验证安装依赖执行 pytest启动服务并调用接口pytest-result.txt、evidence.json、结果记录图片复核检查参数化 SQL、整数金额、事务、连接关闭及分页快照主要代码及对应测试changes.diff记录本次新增源码的差异不虚构多轮修改历史。完整对话保留在当前聊天中本节仅是关键过程摘要。风险审查发现当前接口未实现登录认证和账户隔离只适合本地实验数据库写入受 SQLite 单写者机制限制源码未记录完整请求体异常响应也不暴露数据库路径和 SQL 细节。运行前应根据使用场景确定数据库备份、访问控制与部署方式。代码由 AI 辅助生成提交前仍需由提交者阅读并确认。四、心得体会本实验表明在交给 AI 编写代码之前先明确金额精度、日期边界、分页规则和错误格式可以减少实现过程中反复调整。接口能够启动只是第一步还需要通过独立的预期值检查结果是否正确。金额处理和并发写入是本次实验的重点。使用整数分存储后汇总结果可以精确核对使用独立连接、事务与数据库主键后可以减少共享连接和手动分配编号引起的问题。测试既要检查正常返回也要检查非法数据没有进入数据库。AI 适合辅助搭建接口、补充测试和整理文档但业务规则与交付内容仍需要逐项核对。后续可以在现有功能上增加用户认证、账户隔离和数据库备份并在更高并发下继续验证服务行为。
RELATED

相关推荐

户外车载随身wifi怎么选?自驾露营实测分享

户外车载随身wifi怎么选?自驾露营实测分享

户外车载随身wifi怎么选?自驾露营实测分享上个月跟朋友去山里露营,晚上围炉聊天,有人刷视频有人发朋友圈,手机热点开了一个又一个,半小时不到全灭——电没了、信号也不稳。那趟回来我就认真研究了车载和露营场景的随身…

📅 2026/10/21 2:35:45
语音对讲功能架构分析

语音对讲功能架构分析

核心组件1. sidebar.vue - 主控制组件,负责语音采集、WebSocket通信、模式控制 2. voice.vue - 音频播放组件,使用 flv.js 播放实时音频流数据流向发送路径(麦克风 → 服务器) 麦克风设备↓ getUserMedia() 采集 (单声道, 8kHz, 带回声消除/降噪)↓ AudioContext 创建音频处理节…

📅 2026/10/21 2:35:45
Python 装饰器:@dataclass(一)

Python 装饰器:@dataclass(一)

一、dataclassdataclass 是 Python 3.7 引入的一个装饰器,用于自动生成类的样板代码。只需要声明字段和类型,它自动生成:自动生成说明__init__构造方法__repr__字符串表示__eq__相等比较from dataclasses import dataclassdataclass class Us…

📅 2026/10/21 2:35:45
MORE NEWS

更多资讯

📰

VS Code高效开发指南:工作区、多光标与调试技巧

1. 第一次使用就该搞懂的工作区概念:文件夹与.code-workspace很多人上手VS Code,习惯就是点击"文件-打开文件夹",把一个项目目录拖进窗口就开始写代码。这个流程对单仓库项目完全没问题,直到你开始同时维护多个仓库、前…

📰

统信UOS服务器部署OpenClaw AI Agent完整避坑手册

刚在一台统信 UOS V2500 服务器上把 OpenClaw AI Agent 完整跑通,前后踩了大大小小七八个坑,这里把整个安装部署过程整理成一份可照着抄的手册。UOS 服务器版和常见的 x86 发行版不太一样,尤其涉及 Python 版本、动态库、编译工具链这几块&am…

📰

Kubernetes生产实战:HPA自动扩缩容与Helm编排实践

1. 先搞清楚这两个东西到底在解决什么问题1.1 扩容这件事,为什么不能靠手点我见过太多团队在 Kubernetes 上跑业务,第一年风平浪静,第二年一到流量高峰就全员进入“备战状态”。运维盯着 Grafana 看流量,流量一涨立刻 kubectl sca…

📰

HPA与Helm实战:Kubernetes自动扩缩容与模板化部署

如果你负责维护一个 Kubernetes 集群,或者正打算把业务往容器平台上迁移,那么 HPA 和 Helm 这两样东西早晚会一起出现在你的工作清单里。HPA 干的事情比较纯粹:让应用的副本数跟着流量自动变化,流量大了自动扩容,闲下来…

📰

单周期RISC-V CPU设计:Vivado实战与FPGA落地指南

简介:本资源是一套基于Verilog实现的单周期CPU完整FPGA设计工程,面向数字电路、计算机组成原理课程学习者及FPGA初学者,旨在通过可综合、可仿真的真实项目,直观呈现取指、译码、执行、写回等CPU核心流程,解决教学中抽象…

📰

用GDAL实现遥感影像裁剪:从范围裁剪到批量处理实战

搞遥感影像处理的人,迟早都会碰到影像裁剪这个需求。无论是从一幅标准景影像里切出研究区,还是按行政区边界做镶嵌裁剪,抑或是给深度学习模型准备训练切片,这一步几乎绕不开。GDAL 是我这些年用得最顺手的工具,没有之一…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬