AI实战笔记:用Cursor把老旧Flask项目重构成FastAPI,我改完了12000行代码
一个跑了四年的Flask项目,代码腐化严重,路由混乱,几乎没有测试。我用Cursor + Claude 3.7,花了三个周末,重构成FastAPI + Pydantic + pytest,最终代码量从12000行减到7800行,API响应速度平均提升3倍,测试覆盖率达到82%。本文完整记录重构过程:从代码分析、模块拆分、路由迁移、数据模型重写、到测试补全。每个阶段都附有实际代码对比和踩坑记录。
接手这个项目的时候,我真的很想直接删库跑路。
一个四年前用Flask写的内部工单系统,前后端混在一起,路由文件里塞了三千多行,同一个数据库查询写了几十个重复版本。没有单元测试,没有类型注解,连requirements.txt都是上古版本。更离谱的是,有一些路由直接往里拼接SQL语句,能在生产环境跑四年没出事只能说是运气好。
领导要求重构,但没有时间从零重写。业务不能停,只能边跑边改。
我选择了用Cursor配合Claude 3.7做AI辅助重构。目标是:迁移到FastAPI,补测试,提升性能,过程中不影响现有业务。三个周末,实际投入大约50个小时。
下面是完整的过程记录。
一、第一步,让AI帮我摸清代码底牌
第一件事不是写代码,而是让Cursor帮我分析整个项目结构。
我把整个项目文件夹拖进Cursor,打开Agent模式,输入指令:分析这个Flask项目的架构,找出所有路由、数据库模型、工具函数,告诉我代码腐化的主要问题。
几十秒后,Cursor给了我一页报告。它找出了几个我都没意识到的坑:
路由文件里定义了80多个路由,但其中12个从未被任何代码引用,属于死代码。数据库查询散落在23个不同的文件里,没有一个统一的DAO层。用户认证逻辑重复写了15遍,有些用了session,有些用了token,还有些直接信任前端传过来的user_id参数。最夸张的是,有一个文件名叫utils_old_backup_final_v2.py。
这份报告让我决定:先把能删的删掉,能合并的合并,不急着迁移框架。
踩坑1:Cursor分析大型项目时,上下文容易溢出。解决方案是分模块分析,先让读routes目录,再读models目录,最后再让给出总结。一次性喂整个项目会漏掉细节。
二、先做手术:人工+AI清理代码
在动框架迁移之前,先做了三轮代码清理。
第一轮:删死代码。让Cursor写了一个脚本,扫描所有路由定义,交叉比对被引用的URL规则,输出死代码清单。跑完发现12个死路由,直接删了。类似的,还找到了3个没被调用的工具函数和2个废弃的数据库表模型。这一轮删了大约700行。
第二轮:抽公共逻辑。用户认证重复15遍的问题,我让Cursor把我之前写的Flask装饰器改造成统一的登录校验函数,然后把所有路由里的重复代码替换成调用这个函数。这里Cursor的准确率不错,但有一个地方搞错了——它把其中两个路由的特殊逻辑也一并替换掉了,导致那两个功能在测试时挂了。手动回滚后,我加了一条指令:替换时保留每个路由的原有注释和特殊标记。
第三轮:规范化数据库查询。原始的数据库查询混用原生SQL和ORM。我让Cursor把所有查询统一封装到几个DAO函数里,输出给FastAPI新框架用。这个过程中Cursor自动生成了对应的Pydantic schemas,这点很惊喜。
清理完这三轮,代码量降到了9500行左右,已经比原来清爽多了。
三、正式迁移:Flask -> FastAPI
接下来是重头戏。我采用的策略是:新旧两套框架并行,通过nginx路由分发,逐步切流量。
第一步是让Cursor帮我把Flask的app.py转换成FastAPI的主入口。我给了一个明确的指令:保留原有路由映射,但改为FastAPI的装饰器语法,同时将原有的request对象调用改为FastAPI的Request和依赖注入方式。
Cursor生成的结果基本可用,但有三个地方要手动改:
一个是Flask的request.get_json()迁移到FastAPI的await request.json(),异步问题。Cursor生成的代码里没有加await,导致请求一直挂起。另一个是错误处理,Flask用abort函数抛出HTTP异常,FastAPI要用HTTPException。Cursor直接移除了所有abort调用,没有替换,这部分几乎要全手动补。再一个是依赖项,FastAPI的Depends机制,Cursor生成了一部分但不够完整。
关键对比:一个路由迁移前后的样子
迁移前(Flask):
@app.route('/api/ticket/<int:ticket_id>', methods=['GET'])
def get_ticket(ticket_id):
user_id = session.get('user_id')
if not user_id:
return jsonify({'error': 'unauthorized'}), 401
ticket = db.query('SELECT * FROM tickets WHERE id = ? AND user_id = ?',
ticket_id, user_id)
if not ticket:
return jsonify({'error': 'not found'}), 404
return jsonify(ticket)
迁移后(FastAPI):
@router.get('/api/ticket/{ticket_id}')
async def get_ticket(
ticket_id: int,
current_user: User = Depends(get_current_user),
dao: TicketDAO = Depends(get_ticket_dao)
):
ticket = await dao.get_ticket_by_id(ticket_id, current_user.id)
if not ticket:
raise HTTPException(status_code=404, detail='not found')
return ticket
代码更长了,但类型安全、依赖明确、支持异步。测试也更容易。
踩坑2:Flask的全局g对象在FastAPI里没有直接对应。Cursor生成的代码里用了ContextVar来模拟,但在异步环境下会串数据。我自己用request.state替代了。
踩坑3:原有Flask代码里大量使用了@app.before_request钩子做全局的请求日志和权限校验。FastAPI没有这个机制,我用中间件(middleware)重写了这部分。Cursor生成的中间件代码不完整,我自己补了异常捕获和请求耗时记录。
四、补测试:从0%到82%
老项目完全没有测试。重构过程中我很清楚,如果没有测试兜底,迁移就是赌博。
我的策略是:让Cursor帮我生成每个核心API的pytest测试用例,基于已有的路由定义和数据模型自动推断。
操作流程:选中一个老的Flask路由文件,让Cursor“为这个文件中的所有路由生成pytest测试,包括正常流程和异常流程,使用async测试客户端”。
Cursor生成的测试骨架很不错,但有几个典型错误:
生成测试时默认用了同步的TestClient,但FastAPI是异步的,需要改用AsyncClient。生成的mock数据有时不符合schema校验规则,比如必填字段漏了。部分测试断言直接抄了原有错误码,但迁移后错误码语义变化了,需要调整。
我用了大约10个小时,把生成的基础测试跑通,然后补充了边界条件和集成测试。最终测试覆盖率达到82%,核心业务逻辑覆盖到了95%以上。
值得一提的是,Cursor帮我自动生成了conftest.py,里面包含了数据库连接fixture、测试用的依赖覆盖等。这个conftest质量很高,基本直接可用。
五、性能对比
重构完成后,我在测试环境做了压测。使用同一台机器,同一个数据库,压测工具wrk。
旧的Flask项目:每秒处理大约400个请求,99%分位响应时间280毫秒。
新的FastAPI项目:每秒处理大约1600个请求,99%分位响应时间85毫秒。
提升原因有三个:一是异步IO,原来Flask同步模式下数据库查询阻塞严重;二是Pydantic序列化比Flask的jsonify更快;三是代码精简后减少了冗余查询。
存储方面,从SQLite迁移到了PostgreSQL(顺手做的)。这个迁移不是AI完成的,我手动转了数据,但Cursor在生成SQLAlchemy模型时帮了大忙。
六、总结与建议
如果你也打算用AI辅助做大型重构,我给你五条硬核建议:
1. 先分析再动手。让Cursor先生成项目结构报告和问题清单,比直接开始迁移重要十倍。我第一周基本没写新代码,全在做分析和清理。
2. 并行运行,渐进切换。新旧框架通过nginx按路由前缀分流,先切读接口,再切写接口,最后切管理接口。每个阶段观察一周。
3. 让AI生成测试,但不要完全信任。Cursor生成的测试骨架可以省下70%的时间,但断言逻辑、边界条件、异步适配必须人工过一遍。
4. 小心Flask特有的全局对象。session、g、request、current_app这些,Cursor不会帮你完美迁移,需要你理解替代方案。
5. 把大任务拆成小指令。不要一次让Cursor“重构整个项目”。我每天拆分三到五个小任务,比如“迁移users模块的三个路由”,“为tickets模块生成测试”,“把旧的SQL查询改成SQLAlchemy写法”。每个任务独立完成、独立测试、独立提交。
最后说说效率。如果没有Cursor,这个项目按我自己的预估需要120到150个小时。实际用了50个小时,其中大约20小时是审阅、调试、修正AI生成的代码。我不是在监督AI,更像是在和一个写代码极快但有时会粗心的同事结对编程。你盯着他写,偶尔纠正他,大部分时候只需要点头通过。
这就是我理解的AI辅助编程的最好状态。