FastAPI与微信小程序开发个人记账系统全栈实践 1. 项目概述记账是个人财务管理的基础但市面上大多数记账工具要么功能臃肿要么无法满足个性化需求。作为一名开发者我决定从零开始构建一个轻量级的个人记账系统包含网页版和小程序两个版本。这个项目不仅解决了我的实际需求也让我完整实践了前后端开发的整个流程。这个系统采用前后端分离架构后端使用Python的FastAPI框架提供RESTful API前端则分别开发了网页版和微信小程序版本。从最初的内存存储到SQLite数据库再到完整的CRUD功能实现整个开发过程循序渐进非常适合想要学习全栈开发的朋友参考。2. 开发环境准备2.1 Python环境配置我选择使用conda来管理Python环境这样可以避免不同项目间的依赖冲突。以下是具体步骤conda create -n bill_env python3.10 conda activate bill_env pip install fastapi[standard]提示如果系统询问Proceed ([y]/n)?直接输入y回车即可。这个环境将包含我们需要的所有Python依赖。2.2 开发工具选择对于后端开发我推荐使用VS Code作为代码编辑器它轻量且插件丰富。前端开发则需要网页版Chrome浏览器 开发者工具小程序微信开发者工具官方IDE3. 后端开发3.1 快速启动FastAPI服务我们先创建一个最简单的FastAPI应用来验证环境是否正常工作。创建main.py文件from fastapi import FastAPI app FastAPI() app.get(/) def read_root(): return {message: hello,记账本}启动服务有两种方式使用FastAPI CLIfastapi dev main.py直接运行Python文件适合在VS Code中调试import uvicorn if __name__ __main__: uvicorn.run(app, host127.0.0.1, port8000)启动后访问http://127.0.0.1:8000应该能看到返回的JSON消息。3.2 数据模型设计记账应用的核心是账单数据我们使用Pydantic来定义数据模型from pydantic import BaseModel, Field import datetime class BillItem(BaseModel): amount: float # 金额必须是浮点数 category: str # 分类字符串类型 record_date: datetime.date Field(default_factorydatetime.date.today) note: str # 备注默认为空这个模型会自动处理数据验证如果前端传来的金额不是数字FastAPI会自动返回错误不需要我们手动写验证逻辑。3.3 内存存储实现在开发初期我们先用Python列表作为临时数据库# 模拟数据库 db [] app.post(/add-bill) def create_bill(bill: BillItem): db.append(bill) return { status: 添加成功, added_data: bill, current_total: len(db) } app.get(/bills) def get_bills(): return db这种方式的优点是简单快捷适合快速验证业务逻辑。缺点是程序重启后数据会丢失所以我们后续会升级到SQLite。3.4 跨域问题解决当前后端分离开发时会遇到跨域问题。FastAPI通过CORSMiddleware轻松解决from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[*], allow_credentialsTrue, allow_methods[*], allow_headers[*], )注意生产环境应该限制allow_origins为具体的域名而不是通配符*。4. 数据库升级4.1 SQLite配置内存存储不适合实际使用我们升级到SQLite数据库。首先安装依赖pip install sqlalchemy databases[aiosqlite]然后配置数据库连接和模型from sqlalchemy import create_engine, Column, Integer, String, Float, Date from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, Session SQLALCHEMY_DATABASE_URL sqlite:///./bills.db engine create_engine(SQLALCHEMY_DATABASE_URL, connect_args{check_same_thread: False}) SessionLocal sessionmaker(autocommitFalse, autoflushFalse, bindengine) Base declarative_base() class BillModel(Base): __tablename__ bills id Column(Integer, primary_keyTrue, indexTrue) amount Column(Float) category Column(String) record_date Column(Date) note Column(String) Base.metadata.create_all(bindengine)4.2 CRUD接口实现基于SQLAlchemy实现完整的增删改查功能# 增 app.post(/add-bill) def create_bill(bill: BillSchema, db: Session Depends(get_db)): db_bill BillModel( amountbill.amount, categorybill.category, record_datebill.record_date, notebill.note ) db.add(db_bill) db.commit() db.refresh(db_bill) return {status: success, id: db_bill.id} # 删 app.delete(/delete-bill/{bill_id}) def delete_bill(bill_id: int, db: Session Depends(get_db)): bill db.query(BillModel).filter(BillModel.id bill_id).first() if bill is None: raise HTTPException(status_code404, detail找不到这条账单) db.delete(bill) db.commit() return {status: success} # 改 app.put(/update-bill/{bill_id}) def update_bill(bill_id: int, bill_update: BillSchema, db: Session Depends(get_db)): db_bill db.query(BillModel).filter(BillModel.id bill_id).first() if not db_bill: raise HTTPException(status_code404, detail找不到这条账单) db_bill.amount bill_update.amount db_bill.category bill_update.category db_bill.note bill_update.note db.commit() db.refresh(db_bill) return {status: success, message: 修改成功} # 查 app.get(/bills) def get_bills(q: str None, db: Session Depends(get_db)): query db.query(BillModel) if q: query query.filter( (BillModel.category.contains(q)) | (BillModel.note.contains(q)) ) return query.all()5. 网页前端开发5.1 HTML结构设计我们创建一个简单的HTML页面包含记账表单和账单列表!DOCTYPE html html langzh-CN head meta charsetUTF-8 title我的个人记账本/title style body { font-family: Arial, sans-serif; max-width: 600px; margin: 20px auto; padding: 20px; } .form-group { margin-bottom: 10px; } label { display: inline-block; width: 80px; } button { padding: 5px 15px; background-color: #28a745; color: white; border: none; cursor: pointer; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } /style /head body h2 我的记账本/h2 !-- 输入区域 -- div stylebackground: #f9f9f9; padding: 15px; border-radius: 5px; div classform-group label金额:/label input typenumber idamount placeholder例如: 12.5 /div div classform-group label分类:/label input typetext idcategory placeholder例如: 吃饭 /div div classform-group label备注:/label input typetext idnote placeholder例如: 黄焖鸡米饭 /div button onclickaddBill()记一笔/button /div !-- 展示区域 -- h3账单列表/h3 table idbillTable thead tr th日期/th th分类/th th备注/th th金额/th /tr /thead tbody !-- 动态填充 -- /tbody /table script // JavaScript代码将在下一节介绍 /script /body /html5.2 JavaScript交互实现使用Fetch API与后端交互const API_URL http://127.0.0.1:8000; // 1. 记一笔 async function addBill() { const amount parseFloat(document.getElementById(amount).value); const category document.getElementById(category).value; const note document.getElementById(note).value; if (!amount || !category) { alert(请输入金额和分类); return; } const data { amount: amount, category: category, note: note }; try { const response await fetch(${API_URL}/add-bill, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(data) }); if (response.ok) { alert(记账成功); loadBills(); document.getElementById(amount).value ; document.getElementById(note).value ; } } catch (error) { console.error(请求失败:, error); alert(无法连接到服务器请检查后端是否启动); } } // 2. 加载账单 async function loadBills() { try { const response await fetch(${API_URL}/bills); const bills await response.json(); const tbody document.querySelector(#billTable tbody); tbody.innerHTML ; bills.forEach(bill { const row tr td${bill.record_date}/td td${bill.category}/td td${bill.note}/td td stylecolor: red; font-weight: bold;¥${bill.amount}/td /tr ; tbody.innerHTML row; }); } catch (error) { console.error(加载失败:, error); } } // 页面加载时自动获取账单 window.onload loadBills;6. 微信小程序开发6.1 开发环境准备下载并安装微信开发者工具创建新项目选择不使用云服务在项目设置中勾选不校验合法域名仅限开发环境6.2 页面结构设计小程序使用WXML语言定义界面结构view classcontainer view classheader 我的记账本 Pro/view !-- 输入区域 -- view classinput-card view classform-group text classlabel金额 (¥)/text input classinput typedigit placeholder0.00 model:value{{amount}} / /view view classform-group text classlabel分类/text input classinput typetext placeholder餐饮/交通 model:value{{category}} / /view view classform-group text classlabel备注/text input classinput typetext placeholder详细说明... model:value{{note}} / /view view classbtn-group button class{{currentEditingId ? btn-update : btn-add}} bindtapsubmitData {{currentEditingId ? 保存修改 : 记一笔}} /button /view /view !-- 账单列表 -- view classbill-list view classbill-item wx:for{{bills}} wx:keyid view classbill-info text classcol date{{item.record_date}}/text text classcol{{item.category}}/text text classcol amount¥{{item.amount}}/text view classcol ops text classedit-text bindtapstartEdit>const API_URL http://127.0.0.1:8000; Page({ data: { bills: [], amount: , category: , note: , currentEditingId: null }, onLoad() { this.loadBills(); }, loadBills() { wx.request({ url: ${API_URL}/bills, method: GET, success: (res) { this.setData({ bills: res.data }); } }); }, submitData() { const { amount, category, note, currentEditingId } this.data; if (!amount || !category) { wx.showToast({ title: 金额和分类必填, icon: none }); return; } const isEdit !!currentEditingId; const url isEdit ? ${API_URL}/update-bill/${currentEditingId} : ${API_URL}/add-bill; const method isEdit ? PUT : POST; wx.request({ url: url, method: method, data: { amount: parseFloat(amount), category, note }, success: (res) { if (res.statusCode 200) { wx.showToast({ title: isEdit ? 修改成功 : 添加成功 }); this.resetForm(); this.loadBills(); } } }); }, deleteBill(e) { const id e.currentTarget.dataset.id; wx.showModal({ title: 确认, content: 确定要删除这条记录吗, success: (res) { if (res.confirm) { wx.request({ url: ${API_URL}/delete-bill/${id}, method: DELETE, success: () { this.loadBills(); } }); } } }); }, startEdit(e) { const item e.currentTarget.dataset.item; this.setData({ amount: item.amount, category: item.category, note: item.note, currentEditingId: item.id }); }, resetForm() { this.setData({ amount: , category: , note: , currentEditingId: null }); } });6.4 样式设计小程序使用WXSS类似CSS的子集定义样式.container { padding: 20rpx; background-color: #f8f8f8; min-height: 100vh; } .header { font-size: 36rpx; font-weight: bold; margin-bottom: 30rpx; text-align: center; } .input-card { background: #fff; padding: 30rpx; border-radius: 16rpx; margin-bottom: 30rpx; box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.05); } .form-group { margin-bottom: 20rpx; } .label { font-size: 24rpx; color: #666; margin-bottom: 10rpx; display: block; } .input { border: 1rpx solid #ddd; padding: 15rpx; border-radius: 8rpx; font-size: 28rpx; } .btn-group { display: flex; gap: 20rpx; margin-top: 20rpx; } .btn-add { background: #28a745; color: #fff; flex: 1; } .btn-update { background: #ffc107; color: #000; flex: 1; } .bill-list { margin-top: 30rpx; background: #fff; border-radius: 16rpx; overflow: hidden; } .bill-item { padding: 20rpx; border-bottom: 1rpx solid #eee; } .bill-info { display: flex; align-items: center; justify-content: space-between; } .col { flex: 1; font-size: 26rpx; } .amount { color: #d9534f; font-weight: bold; } .ops { display: flex; gap: 20rpx; } .edit-text { color: #17a2b8; } .delete-text { color: #dc3545; } .note-text { font-size: 22rpx; color: #999; margin-top: 10rpx; }7. 部署方案7.1 微信云开发方案这是最简单快捷的部署方式在微信开发者工具中开通云开发将数据库操作改为云数据库API将业务逻辑改为云函数优点无需服务器、自动HTTPS、免费额度足够个人使用7.2 云服务器部署适合需要保留现有FastAPI后端的场景购买云服务器推荐腾讯云或阿里云轻量应用服务器注册域名并备案必须步骤配置Nginx反向代理和HTTPS使用PM2管理Node.js进程如果用uvicorn需要配置守护进程优点完全控制、性能更好7.3 内网穿透方案仅适用于临时演示下载内网穿透工具如cpolar运行命令cpolar http 8000将小程序中的API_URL改为cpolar提供的地址缺点不稳定电脑关机服务就不可用8. 开发经验总结在实际开发过程中我积累了一些有价值的经验接口设计RESTful API的设计要遵循规范GET用于查询POST用于创建PUT用于更新DELETE用于删除。这样代码更易读易维护。错误处理后端要对所有可能的错误情况进行处理并返回有意义的错误信息。比如当用户尝试删除不存在的账单时应该返回404状态码和明确的错误信息。数据验证虽然Pydantic提供了基本的数据验证但对于业务逻辑的验证如金额不能为负数还需要在接口中额外处理。小程序开发真机调试时要注意必须使用HTTPS开发环境可以临时关闭校验域名必须备案开发环境可以用IP请求域名需要在微信小程序后台配置性能优化对于账单列表可以考虑分页加载减少一次性返回的数据量按时间范围查询避免加载全部历史数据前端缓存减少重复请求这个项目从零开始完整实现了一个前后端分离的记账应用涵盖了环境搭建、API设计、数据库操作、网页前端和小程序开发全流程。对于想要学习全栈开发的朋友这是一个非常好的练手项目。你可以基于这个基础继续扩展功能比如用户系统多设备同步数据统计和可视化账单分类管理导出Excel报表等整个开发过程中我最大的体会是不要一开始就追求完美先实现核心功能再逐步迭代优化。这个项目就是从最简单的内存存储开始一步步升级到数据库再到完整的小程序实现。这种渐进式的开发方式能让学习曲线更加平缓也更容易获得成就感。