基于Python与React构建自托管收入追踪器:技术栈与自动化实践

张开发
2026/5/14 11:36:21 15 分钟阅读

分享文章

基于Python与React构建自托管收入追踪器:技术栈与自动化实践
1. 项目概述一个为自由职业者量身定制的收入追踪器如果你是一名自由职业者、独立开发者或者正在经营自己的小生意那么“收入管理”这件事大概率是你的痛点之一。项目“Indomi/earnings-tracker”直译过来就是“收入追踪器”这个名字听起来简单但它背后瞄准的正是我们这些“一人公司”或小团队在财务管理上的核心困境。我干了十多年自由职业从最初的接单写代码到后来运营自己的技术博客和咨询业务收入来源从单一的客户项目款逐渐扩展到广告联盟、付费订阅、课程销售、技术咨询等多个渠道。每个月末面对来自PayPal、Stripe、银行转账、微信/支付宝、甚至还有客户直接给的现金要把这些零散的收入记录清楚计算净利再为报税做准备这个过程曾经让我无比头疼。用过Excel但表格越做越复杂公式容易出错试过一些现成的记账软件但它们要么太“个人化”只适合记录日常开销要么太“企业化”功能臃肿价格昂贵总感觉不那么趁手。“Indomi/earnings-tracker”这个项目在我看来就是为了解决这个精准痛点而生的。它不是一个泛泛的记账工具而是一个专门为“赚取收入”这个场景设计的追踪系统。它的核心价值在于帮助像你我这样的个体经营者清晰、自动、可定制地掌握自己的现金流全貌。你不再需要手动汇总不同平台的账单而是建立一个中心化的仪表盘所有收入数据一目了然。这对于评估业务健康度、预测未来收入、以及最重要的——让每年的报税季不再成为一场噩梦——有着至关重要的意义。简单来说这个项目适合所有有收入来源需要管理的个人或微型团队。无论你是程序员、设计师、作家、顾问还是网店店主只要你有多元化的收入流并且厌倦了手工对账的繁琐那么这个收入追踪器的理念和实现方案就值得你深入了解甚至亲手搭建一个属于自己的版本。2. 核心需求与设计思路拆解在动手构建任何工具之前明确它要解决的具体问题至关重要。对于收入追踪器我们不能停留在“记账”这个笼统的概念上必须深入拆解自由职业者财务管理的独特需求。2.1 自由职业者收入管理的四大核心痛点第一收入来源碎片化。这是最显著的特征。你的收入可能来自A客户的固定月费、B项目的里程碑付款、C平台的广告点击分成、D产品的单次销售、E咨询服务的时薪结算。每种收入可能通过不同的支付网关如Stripe、PayPal、支付宝、银行转账进入不同的账户。手动从各个平台导出CSV再合并整理耗时耗力且易出错。第二收入与成本关联复杂。自由职业的收入往往不是纯利润。接一个项目可能涉及服务器费用、软件订阅费如Figma、JetBrains全家桶、云服务开销AWS/Azure额度甚至给外包伙伴的分成。传统的个人记账软件很难清晰地将某项成本与特定项目的收入挂钩导致算不清单个项目的真实利润率。第三税务筹备前置化需求。在很多地区自由职业者需要按季度预缴税款或每年进行复杂的税务申报。这意味着你需要实时或定期知道自己的应税收入总收入减去可抵扣的业务成本。临时抱佛脚在报税截止日前翻遍一年的账单绝对是痛苦的体验。第四数据洞察与预测需求。你希望知道哪个月份收入最高哪种收入类型项目制 vs 产品制利润更高哪个客户贡献了最大价值未来的收入管道是否健康这些问题的答案都藏在数据里但分散的数据无法提供洞察。2.2 “Indomi/earnings-tracker”的设计哲学基于以上痛点一个理想的收入追踪器应该遵循几个核心设计原则中心化聚合自动化录入设计核心是建立一个“数据枢纽”能够通过API、邮件解析、或文件导入等方式自动或半自动地从各个收入源头抓取交易数据。减少手动输入是保证数据准确性和节省时间的第一步。结构化数据模型每一笔收入记录都不应只是一个数字和备注。它需要包含丰富的元数据Metadata例如收入来源客户名称、平台名称。收入类型项目报酬、产品销售、订阅收入、广告分成等。支付方式Stripe、PayPal、银行转账等。关联项目/产品这笔收入属于哪个具体的工作项。状态已付款、待付款、已开票。时间戳交易发生日期、实际到账日期。成本追踪与关联允许记录业务成本并能够将其与特定的项目或收入源进行关联。这是计算净利润和进行项目核算的基础。报表与可视化驱动系统不是数据的坟墓而是信息的金矿。必须提供强大的报表功能月度/年度收入图表、收入来源分布饼图、项目利润分析表、应税收入计算表等。一个清晰的仪表盘Dashboard是系统的“门面”。隐私与自托管优先财务数据极其敏感。因此许多资深从业者倾向于采用自托管Self-hosted方案将数据完全掌握在自己手中的服务器或本地电脑上。“Indomi/earnings-tracker”很可能采用了这样的架构这也是其吸引技术型用户的关键点。注意在设计之初就要考虑数据的扩展性。比如未来可能需要支持加密货币收入、多币种处理等。良好的数据模型设计可以避免后期推翻重来。3. 技术栈选型与核心模块解析要实现上述设计我们需要选择合适的技术工具。虽然我们不知道“Indomi/earnings-tracker”项目的具体实现但我们可以基于通用最佳实践推导出一个稳健、可扩展的技术栈方案。这本身也是项目拆解的重要部分。3.1 后端技术栈稳健与效率的平衡后端负责数据处理、业务逻辑和API提供。考虑到自由职业者项目可能是一个人开发维护技术栈应在功能强大和易于维护之间取得平衡。编程语言Python或Node.js是绝佳选择。Python拥有Pandas、NumPy等强大的数据分析库非常适合处理财务数据并且开发效率高。Node.js则擅长处理I/O密集型任务如调用多个支付网关API事件驱动模型很适合构建实时性要求不高的数据聚合服务。Web框架如果选PythonFastAPI或Django是主流。FastAPI轻量、高性能自动生成API文档非常适合构建纯后端API。Django则更“全家桶”自带Admin后台、ORM和用户认证能快速搭建出功能完善的原型。如果选Node.jsExpress.js或NestJS是常见选择NestJS的架构更清晰适合中大型项目。数据库这是核心。财务数据要求ACID原子性、一致性、隔离性、持久性特性因此关系型数据库是首选。PostgreSQL强烈推荐。它功能强大支持JSON字段可以灵活存储一些非结构化的交易详情拥有丰富的扩展如用于地理位置的PostGIS社区活跃。对于收入追踪这种数据结构相对固定但查询需求复杂的应用PostgreSQL游刃有余。SQLite如果追求极简部署或者希望应用是单机桌面版SQLite是完美的选择。它将整个数据库存储在一个文件中无需单独运行数据库服务。许多成功的开源个人管理工具如Joplin都使用SQLite。对于自用或小范围使用的收入追踪器SQLite完全够用且部署简单到只需拷贝一个文件。ORM/ODM使用ORM对象关系映射可以让我们用编程语言的对象方式来操作数据库提升开发效率和代码可读性。Python有SQLAlchemy功能强大或Django ORM与Django绑定Node.js有Prisma或TypeORM。3.2 前端技术栈交互与体验的关键前端是用户直接交互的界面需要清晰、直观、响应迅速。现代前端框架React、Vue.js或Svelte。三者皆可选择往往取决于开发者的熟悉程度。React生态庞大组件丰富Vue.js易于上手文档友好Svelte编译时框架能生成高效代码。对于以数据展示和操作为主的后台管理系统三者都能胜任。UI组件库为了快速构建美观且一致的界面使用现成的UI库是明智之举。例如Ant Design、Element PlusVue、MUIReact等都提供了丰富的表格、表单、图表组件能极大节省开发时间。图表库数据可视化是核心卖点。ECharts或Chart.js是功能强大且开源的选择。它们支持折线图、柱状图、饼图、散点图等几乎所有需要的图表类型并且交互性良好。ApexCharts也是一个颜值和功能都在线的优秀库。状态管理对于数据流不太复杂的应用React的Context APIuseReducer或Vue的Pinia/Vuex足以应对。如果应用变得非常复杂可以考虑Redux或MobX但对于收入追踪器前期可能不需要。3.3 核心功能模块设计一个完整的收入追踪器可以拆解为以下几个核心模块数据源连接器模块这是自动化的入口。该模块包含一系列适配器Adapter用于连接不同的数据源。API连接器通过OAuth或API Key连接Stripe、PayPal、Patreon、GitHub Sponsors等平台定期拉取交易记录。邮件解析器很多银行或支付平台会发送交易通知邮件。可以设置一个专用邮箱通过IMAP协议读取邮件使用正则表达式或自然语言处理NLP库解析邮件内容提取金额、日期、对手方等信息。这是一个“曲线救国”但非常实用的自动化手段。文件导入器支持上传CSV、Excel或OFX开放金融交换格式文件并定义映射规则将文件中的列对应到系统的数据模型字段。数据存储与处理核心模块实体定义主要的数据表包括User用户、Income收入记录、Expense支出记录、Project项目、Client客户、PaymentMethod支付方式、Category收入/支出类别。关联关系Income和Expense都归属于一个User可以关联到一个Project和一个Client并有一个Category。Project归属于一个Client。这种设计支持多维度的查询和分析。报表与计算引擎模块聚合计算根据时间范围本日、本周、本月、本季度、本年、自定义、项目、客户、类别等维度对收入和支出进行求和、平均等计算。利润计算净利润 总收入 - 总支出。项目利润 项目相关收入 - 项目相关支出。可视化数据准备将聚合计算的结果格式化成图表库如ECharts所需的dataset格式。用户界面模块仪表盘首页展示关键指标KPI卡片如“本月收入”、“本年利润”、“待收款金额”以及核心图表月度收入趋势图、收入来源分布图。交易记录列表页以表格形式展示所有收入和支出支持筛选、排序、搜索和批量操作。详情与编辑页查看或编辑单笔交易的详细信息。报表页提供更高级、可定制的报表生成功能允许用户选择维度、指标和时间范围生成特定报表。设置页管理数据源连接、分类标签、客户/项目信息等。实操心得在开发初期不要追求连接所有支付平台。优先实现手动录入和文件导入功能确保核心数据模型和报表跑通。然后再选择1-2个你最常用的平台如Stripe和PayPal实现API连接验证自动化流程。这种“由内而外”的迭代方式更稳妥。4. 系统搭建与核心功能实现详解现在让我们进入实战环节以一个基于 Python (FastAPI) PostgreSQL React 的技术栈为例勾勒出搭建这个收入追踪器的关键步骤和代码要点。请注意以下是一个高度概括的实现指南旨在展示核心逻辑而非完整的、可直接运行的代码。4.1 环境准备与项目初始化首先确保你的开发环境就绪。# 创建项目目录 mkdir earnings-tracker cd earnings-tracker # 后端目录 mkdir backend cd backend python -m venv venv # 创建虚拟环境 # Windows: venv\Scripts\activate # Mac/Linux: source venv/bin/activate pip install fastapi uvicorn sqlalchemy psycopg2-binary pydantic python-dotenv pandas # FastAPI: Web框架 # uvicorn: ASGI服务器 # sqlalchemy: ORM # psycopg2-binary: PostgreSQL驱动 # pydantic: 数据验证 # pandas: 数据处理用于报表计算 # python-dotenv: 环境变量管理 # 初始化项目结构 touch main.py models.py schemas.py crud.py database.py dependencies.py在backend目录下创建.env文件存放数据库连接等敏感信息DATABASE_URLpostgresql://username:passwordlocalhost:5432/earnings_db4.2 数据模型定义与数据库连接在models.py中我们使用SQLAlchemy定义核心的数据表结构。from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Enum, Text from sqlalchemy.orm import relationship from sqlalchemy.sql import func from database import Base # database.py中会创建Base import enum class IncomeType(str, enum.Enum): PROJECT project PRODUCT_SALE product_sale SUBSCRIPTION subscription AFFILIATE affiliate OTHER other class ExpenseType(str, enum.Enum): SOFTWARE software HARDWARE hardware SERVICE service MARKETING marketing TRAVEL travel OTHER other class TransactionStatus(str, enum.Enum): PENDING pending PAID paid FAILED failed class User(Base): __tablename__ users id Column(Integer, primary_keyTrue, indexTrue) email Column(String, uniqueTrue, indexTrue) hashed_password Column(String) # 关系 incomes relationship(Income, back_populatesowner) expenses relationship(Expense, back_populatesowner) clients relationship(Client, back_populatesowner) projects relationship(Project, back_populatesowner) class Client(Base): __tablename__ clients id Column(Integer, primary_keyTrue, indexTrue) name Column(String, indexTrue) contact_email Column(String, nullableTrue) notes Column(Text, nullableTrue) owner_id Column(Integer, ForeignKey(users.id)) # 关系 owner relationship(User, back_populatesclients) projects relationship(Project, back_populatesclient) incomes relationship(Income, back_populatesclient) class Project(Base): __tablename__ projects id Column(Integer, primary_keyTrue, indexTrue) name Column(String, indexTrue) description Column(Text, nullableTrue) client_id Column(Integer, ForeignKey(clients.id)) owner_id Column(Integer, ForeignKey(users.id)) # 关系 client relationship(Client, back_populatesprojects) owner relationship(User, back_populatesprojects) incomes relationship(Income, back_populatesproject) expenses relationship(Expense, back_populatesproject) class Income(Base): __tablename__ incomes id Column(Integer, primary_keyTrue, indexTrue) amount Column(Float, nullableFalse) # 收入金额 currency Column(String, defaultUSD) # 货币 type Column(Enum(IncomeType), defaultIncomeType.PROJECT) description Column(Text) status Column(Enum(TransactionStatus), defaultTransactionStatus.PAID) transaction_date Column(DateTime, nullableFalse) # 交易日期 received_date Column(DateTime, nullableFalse, server_defaultfunc.now()) # 到账日期 source Column(String) # 来源如 Stripe, PayPal, Bank Transfer external_id Column(String, uniqueTrue, nullableTrue) # 外部系统ID用于去重 # 外键关联 owner_id Column(Integer, ForeignKey(users.id)) client_id Column(Integer, ForeignKey(clients.id), nullableTrue) project_id Column(Integer, ForeignKey(projects.id), nullableTrue) # 关系 owner relationship(User, back_populatesincomes) client relationship(Client, back_populatesincomes) project relationship(Project, back_populatesincomes) class Expense(Base): __tablename__ expenses id Column(Integer, primary_keyTrue, indexTrue) amount Column(Float, nullableFalse) currency Column(String, defaultUSD) type Column(Enum(ExpenseType), defaultExpenseType.OTHER) description Column(Text) vendor Column(String) # 供应商 invoice_url Column(String, nullableTrue) # 发票链接 incurred_date Column(DateTime, nullableFalse) # 发生日期 paid_date Column(DateTime, nullableTrue) # 外键关联 owner_id Column(Integer, ForeignKey(users.id)) project_id Column(Integer, ForeignKey(projects.id), nullableTrue) # 可关联到特定项目 # 关系 owner relationship(User, back_populatesexpenses) project relationship(Project, back_populatesexpenses)在database.py中我们建立数据库连接和会话管理。from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker import os from dotenv import load_dotenv load_dotenv() SQLALCHEMY_DATABASE_URL os.getenv(DATABASE_URL) engine create_engine(SQLALCHEMY_DATABASE_URL) SessionLocal sessionmaker(autocommitFalse, autoflushFalse, bindengine) Base declarative_base() # 依赖项用于在请求中获取数据库会话 def get_db(): db SessionLocal() try: yield db finally: db.close()4.3 API接口设计与实现以收入记录为例我们使用Pydantic定义数据验证模式Schemas在schemas.py中。from pydantic import BaseModel from datetime import datetime from typing import Optional from enum import Enum class IncomeType(str, Enum): PROJECT project PRODUCT_SALE product_sale # ... 同上 class IncomeBase(BaseModel): amount: float currency: str USD type: IncomeType description: Optional[str] None status: str paid transaction_date: datetime source: str external_id: Optional[str] None client_id: Optional[int] None project_id: Optional[int] None class IncomeCreate(IncomeBase): pass class Income(IncomeBase): id: int owner_id: int received_date: datetime class Config: orm_mode True # 允许从ORM对象读取数据在crud.py中我们创建增删改查的通用函数。from sqlalchemy.orm import Session from models import Income from schemas import IncomeCreate from datetime import datetime def get_incomes(db: Session, skip: int 0, limit: int 100, owner_id: int None): query db.query(Income) if owner_id: query query.filter(Income.owner_id owner_id) return query.offset(skip).limit(limit).all() def get_income_by_external_id(db: Session, external_id: str): return db.query(Income).filter(Income.external_id external_id).first() def create_income(db: Session, income: IncomeCreate, owner_id: int): # 防止重复导入 if income.external_id: db_income get_income_by_external_id(db, income.external_id) if db_income: return db_income # 已存在返回现有记录 db_income Income(**income.dict(), owner_idowner_id, received_datedatetime.utcnow()) db.add(db_income) db.commit() db.refresh(db_income) return db_income最后在main.py中我们创建FastAPI应用和路由。from fastapi import FastAPI, Depends, HTTPException from sqlalchemy.orm import Session from typing import List import crud, schemas, models from database import engine, get_db from dependencies import get_current_user # 假设有用户认证依赖 models.Base.metadata.create_all(bindengine) # 创建数据表 app FastAPI(titleEarnings Tracker API) app.post(/incomes/, response_modelschemas.Income) def create_income( income: schemas.IncomeCreate, db: Session Depends(get_db), current_user: models.User Depends(get_current_user) ): 创建一条收入记录 return crud.create_income(dbdb, incomeincome, owner_idcurrent_user.id) app.get(/incomes/, response_modelList[schemas.Income]) def read_incomes( skip: int 0, limit: int 100, db: Session Depends(get_db), current_user: models.User Depends(get_current_user) ): 获取当前用户的收入记录列表 incomes crud.get_incomes(db, skipskip, limitlimit, owner_idcurrent_user.id) return incomes app.get(/dashboard/summary) def get_dashboard_summary( start_date: str, # 格式: YYYY-MM-DD end_date: str, db: Session Depends(get_db), current_user: models.User Depends(get_current_user) ): 获取仪表盘摘要数据总收入、总支出、净利润、按类型/来源分布 # 这里需要编写复杂的查询或使用Pandas进行聚合计算 # 示例伪代码 # total_income db.query(func.sum(Income.amount)).filter(...).scalar() or 0 # total_expense db.query(func.sum(Expense.amount)).filter(...).scalar() or 0 # income_by_type db.query(Income.type, func.sum(Income.amount)).group_by(Income.type).all() # 将结果组装成JSON返回 return {total_income: 10000, total_expense: 2000, net_profit: 8000}4.4 前端界面与数据可视化集成前端部分我们以React为例展示如何调用API并渲染图表。使用axios进行HTTP请求react-query管理服务器状态recharts或victory渲染图表。首先创建一个展示月度收入趋势的组件MonthlyIncomeChart.jsximport React, { useEffect, useState } from react; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from recharts; import { fetchDashboardData } from ../api; // 封装好的API调用函数 const MonthlyIncomeChart ({ startDate, endDate }) { const [chartData, setChartData] useState([]); const [loading, setLoading] useState(false); useEffect(() { const loadData async () { setLoading(true); try { // 假设API返回格式 { monthly_data: [ {month: 2024-01, income: 5000, expense: 1000}, ...] } const data await fetchDashboardData(startDate, endDate, monthly); setChartData(data.monthly_data || []); } catch (error) { console.error(Failed to fetch chart data:, error); } finally { setLoading(false); } }; loadData(); }, [startDate, endDate]); if (loading) return divLoading chart.../div; return ( ResponsiveContainer width100% height{300} LineChart data{chartData} margin{{ top: 5, right: 30, left: 20, bottom: 5 }} CartesianGrid strokeDasharray3 3 / XAxis dataKeymonth / YAxis / Tooltip formatter{(value) $${value.toFixed(2)}} / Legend / Line typemonotone dataKeyincome stroke#8884d8 activeDot{{ r: 8 }} name收入 / Line typemonotone dataKeyexpense stroke#82ca9d name支出 / /LineChart /ResponsiveContainer ); }; export default MonthlyIncomeChart;然后在主仪表盘页面中集成这个组件和其他KPI卡片。注意事项在前端处理货币金额时务必注意精度问题。JavaScript的浮点数计算存在精度丢失风险。对于财务计算最佳实践是将所有金额以“分”或“最小货币单位”为整数存储在数据库中例如存储1000代表10.00美元。在前端显示时再根据货币的小数位数进行格式化。或者使用decimal.js这类库进行精确计算。5. 数据自动化采集与处理实战手动录入是基础但自动化才是解放生产力的关键。这里我们深入探讨两种最实用的自动化方案API集成和邮件解析。5.1 通过API同步支付平台数据以Stripe为例它提供了功能完善的API。我们可以创建一个后台任务例如使用Celery或APScheduler定期调用Stripe API获取新的支付记录。首先安装Stripe Python库pip install stripe。然后创建一个服务类stripe_sync.pyimport stripe from sqlalchemy.orm import Session from crud import create_income, get_income_by_external_id from schemas import IncomeCreate from datetime import datetime import os from dotenv import load_dotenv load_dotenv() stripe.api_key os.getenv(STRIPE_SECRET_KEY) def sync_stripe_charges(db: Session, owner_id: int, days: int 30): 同步最近N天的Stripe支付记录 # 计算时间范围 latest datetime.utcnow() earliest latest - timedelta(daysdays) # 将时间转换为Unix时间戳Stripe API参数 created_gt int(earliest.timestamp()) charges stripe.Charge.list( created{gt: created_gt}, limit100 # 一次最多取100条实际应用中可能需要分页 ) incomes_created 0 for charge in charges.auto_paging_iter(): # 检查是否已存在通过external_id if get_income_by_external_id(db, charge.id): continue # 已存在跳过 # 构建IncomeCreate对象 # 注意Stripe金额是以“分”为单位的需要除以100 amount charge.amount / 100.0 # 尝试从metadata或description中获取更多信息 description charge.description or fStripe Charge: {charge.id} # 客户邮箱可能在charge.billing_details.email或charge.receipt_email customer_email charge.billing_details.get(email) if charge.billing_details else None income_data IncomeCreate( amountamount, currencycharge.currency, typeproduct_sale, # 这里需要根据业务逻辑判断类型 descriptiondescription, statuspaid if charge.paid else failed, transaction_datedatetime.fromtimestamp(charge.created), sourceStripe, external_idcharge.id, # client_id 需要根据customer_email映射到系统中的Client这里简化处理 ) create_income(dbdb, incomeincome_data, owner_idowner_id) incomes_created 1 return incomes_created这个函数可以配置为每天凌晨运行的定时任务。对于其他平台如PayPal逻辑类似只是API调用方式和数据格式不同。5.2 通过邮件解析实现“伪自动化”不是所有平台都提供友好的API。对于银行转账或某些国内平台交易通知邮件是普遍存在的信息源。我们可以搭建一个简单的邮件解析服务。核心思路使用imaplib库连接邮箱。搜索特定发件人如noreplyyourbank.com或包含特定关键词如“入账通知”、“收入”的邮件。使用email库解析邮件内容。使用正则表达式或更高级的NLP库如spaCy从邮件正文中提取金额、日期、交易方等信息。将提取的信息转换为系统内部的IncomeCreate或ExpenseCreate对象并保存。这是一个更具挑战性但也更通用的方法因为几乎所有的在线交易都会产生邮件通知。实操心得邮件解析的准确率是关键。建议先从规则明确的邮件开始例如某银行的入账通知邮件格式非常固定编写针对性的正则表达式。对于格式多变的邮件可以引入一个“待审核”状态将解析出的数据先存入临时表通过一个简单的管理界面让用户确认或修正后再正式入库实现“半自动化”。6. 部署、安全与维护指南一个工具只有跑起来并且安全可靠才能产生价值。对于自托管应用部署和维护是必须掌握的技能。6.1 部署方案选择传统VPS/云服务器在DigitalOcean、Linode、AWS EC2或腾讯云CVM上部署。你需要自己安装Python/Node.js环境、PostgreSQL数据库、Nginx反向代理并配置SSL证书。这种方式控制力最强但维护成本也最高。容器化部署使用Docker和Docker Compose。你可以将后端、前端、数据库分别容器化通过一个docker-compose.yml文件一键启动整个应用。这极大地简化了环境配置和依赖管理是当前的主流实践。# docker-compose.yml 示例 version: 3.8 services: db: image: postgres:15 volumes: - postgres_data:/var/lib/postgresql/data environment: POSTGRES_DB: earnings_db POSTGRES_USER: user POSTGRES_PASSWORD: strongpassword backend: build: ./backend command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload volumes: - ./backend:/app ports: - 8000:8000 depends_on: - db environment: DATABASE_URL: postgresql://user:strongpassworddb:5432/earnings_db frontend: build: ./frontend ports: - 3000:3000 depends_on: - backend volumes: postgres_data:Serverless/平台即服务如果你不想管理服务器可以考虑Vercel部署前端、Railway或Fly.io部署全栈应用。这些平台抽象了基础设施你只需要提交代码。但对于有数据库和定时任务的后端应用配置可能稍复杂且长期运行成本可能高于VPS。6.2 安全加固要点财务数据无小事安全必须放在首位。认证与授权必须实现用户登录系统。使用安全的密码哈希算法如bcrypt并实现基于JWTJSON Web Token或Session的认证。确保每个用户只能访问自己的数据在所有的数据库查询中强制加入owner_id过滤条件。HTTPS无论何种部署方式都必须启用HTTPS。可以使用Let‘s Encrypt免费获取SSL证书。Nginx或Caddy可以很方便地配置HTTPS和反向代理。数据库安全不要使用默认端口5432。使用强密码并定期更换。将数据库服务绑定到127.0.0.1本地回环仅允许后端应用访问不要暴露在公网。定期备份数据库。API安全对输入数据进行严格的验证和清理防止SQL注入和XSS攻击FastAPI的Pydantic在这方面做得很好。限制API的请求频率Rate Limiting防止暴力破解。敏感操作如删除、大额修改应记录审计日志。环境变量所有敏感信息数据库密码、API密钥、JWT密钥必须通过环境变量传入绝不要硬编码在代码中。6.3 日常维护与数据备份日志应用应输出结构化日志如JSON格式记录错误、警告和信息性事件。使用logging模块并配置好日志轮转Log Rotation。监控简单的监控可以通过cron定时任务调用一个健康检查端点如/health来实现。更完善的方案可以集成Prometheus和Grafana。备份策略这是生命线。至少执行以下备份数据库自动备份使用pg_dump命令创建每日备份并同步到另一个存储位置如AWS S3、Backblaze B2或另一台服务器。# 示例cron任务每天凌晨2点备份 0 2 * * * pg_dump -U username earnings_db /backup/earnings_db_$(date \%Y\%m\%d).sql应用代码与配置备份使用Git进行版本控制并将仓库推送到远程GitHub、GitLab或Gitea。定期恢复演练定期如每季度测试备份文件是否能够成功恢复确保备份有效。构建一个属于自己的“Indomi/earnings-tracker”绝非一蹴而就但从一个简单的、只能手动录入的MVP最小可行产品开始逐步添加自动化、报表和高级功能这个迭代过程本身就能带来巨大的学习价值和成就感。最重要的是你最终获得了一个完全贴合自己工作流、数据自主可控的财务管理中枢。当你能在几秒钟内回答“我这个季度从产品销售中净赚了多少”这类问题时你就会觉得所有投入都是值得的。

更多文章