1. 项目背景与需求分析
在移动互联网时代,个人财务管理已经成为现代生活的刚需。作为一名长期使用Python进行开发的程序员,我发现市面上大多数记账应用要么功能过于复杂,要么存在隐私担忧。于是萌生了开发一个轻量级、完全本地运行的记账备忘录小程序的想法。
这个项目的核心价值在于:
- 完全离线运行,数据存储在本地,保护用户隐私
- 结合备忘录功能,可以记录消费场景和特殊备注
- 基于Python开发,便于功能扩展和二次开发
- 适配Android平台,满足移动端使用需求
从技术角度看,这个项目需要解决几个关键问题:
- 如何在Android环境下运行Python程序
- 如何设计简洁高效的数据存储方案
- 如何实现友好的用户交互界面
- 如何保证数据的安全性和可靠性
2. 技术选型与环境搭建
2.1 Python在Android上的运行方案
要让Python程序在Android上运行,主要有以下几种方案:
-
Kivy框架:
- 优点:原生支持移动端开发,跨平台
- 缺点:需要打包成APK,开发环境较复杂
-
Termux+Python:
- 优点:直接在Android上运行Python解释器
- 缺点:需要用户安装Termux
-
BeeWare的Briefcase:
- 优点:可以将Python应用打包为原生应用
- 缺点:仍在发展中,社区支持有限
经过对比,我选择了Termux+Python的方案,因为:
- 不需要复杂的打包过程
- 可以直接使用Python标准库和第三方库
- 调试和开发更加方便
提示:Termux是一个强大的Android终端模拟器,可以在不root的情况下提供完整的Linux环境。
2.2 开发环境配置
具体安装步骤如下:
- 在Android设备上安装Termux(可从F-Droid或Google Play获取)
- 打开Termux,执行以下命令:
bash复制
pkg update pkg upgrade pkg install python pip install --upgrade pip - 安装必要的Python库:
bash复制
pip install pandas numpy matplotlib
对于习惯使用PC开发的程序员,可以通过以下方式提高开发效率:
- 在PC上使用VS Code开发,通过SSH连接到Termux
- 安装必要的VS Code扩展:
- Python
- Remote - SSH
- Pylance
2.3 数据库选择
考虑到移动端的特点,我们需要一个轻量级的数据库方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| SQLite | 轻量,无需服务器 | 需要SQL知识 |
| JSON文件 | 简单易用 | 大数据量性能差 |
| Pickle | Python原生支持 | 安全性问题 |
最终选择了SQLite,因为:
- Python内置sqlite3模块
- 适合中小规模数据
- 支持事务和复杂查询
3. 核心功能实现
3.1 数据模型设计
记账应用的核心是收支记录,我们设计以下数据表:
-
transactions表:
- id: 主键
- amount: 金额
- type: 收入/支出
- category: 分类
- date: 日期
- note: 备注
-
categories表:
- id: 主键
- name: 分类名称
- type: 收入/支出
-
budgets表:
- id: 主键
- category_id: 外键
- amount: 预算金额
- period: 周期(月/周)
创建表的SQL语句:
python复制import sqlite3
def init_db():
conn = sqlite3.connect('finance.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS transactions
(id INTEGER PRIMARY KEY AUTOINCREMENT,
amount REAL NOT NULL,
type TEXT CHECK(type IN ('income', 'expense')) NOT NULL,
category_id INTEGER,
date TEXT NOT NULL,
note TEXT,
FOREIGN KEY(category_id) REFERENCES categories(id))''')
c.execute('''CREATE TABLE IF NOT EXISTS categories
(id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT CHECK(type IN ('income', 'expense')) NOT NULL)''')
c.execute('''CREATE TABLE IF NOT EXISTS budgets
(id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER,
amount REAL NOT NULL,
period TEXT CHECK(period IN ('month', 'week')) NOT NULL,
FOREIGN KEY(category_id) REFERENCES categories(id))''')
conn.commit()
conn.close()
3.2 用户界面设计
在Termux环境下,我们可以使用以下方式实现用户界面:
-
基于文本的UI:
- 使用curses库创建文本界面
- 优点:轻量,无需额外依赖
- 缺点:交互体验有限
-
Web界面:
- 使用Flask等框架创建本地Web服务
- 优点:界面美观
- 缺点:需要浏览器访问
-
图形界面:
- 使用Kivy或PyQt
- 优点:原生体验
- 缺点:打包复杂
考虑到Termux的使用场景,我选择了文本界面方案,使用curses库实现:
python复制import curses
def main(stdscr):
# 初始化curses
curses.curs_set(0)
stdscr.clear()
# 创建主菜单
menu_items = ["1. 添加记录", "2. 查看记录", "3. 统计分析", "4. 退出"]
current_row = 0
while True:
stdscr.clear()
h, w = stdscr.getmaxyx()
# 显示标题
title = "个人记账系统"
stdscr.addstr(0, w//2 - len(title)//2, title)
# 显示菜单
for idx, item in enumerate(menu_items):
x = w//2 - len(item)//2
y = h//2 - len(menu_items)//2 + idx
if idx == current_row:
stdscr.addstr(y, x, item, curses.A_REVERSE)
else:
stdscr.addstr(y, x, item)
# 获取用户输入
key = stdscr.getch()
if key == curses.KEY_UP and current_row > 0:
current_row -= 1
elif key == curses.KEY_DOWN and current_row < len(menu_items)-1:
current_row += 1
elif key == curses.KEY_ENTER or key in [10, 13]:
if current_row == len(menu_items)-1: # 退出
break
else:
# 处理菜单选择
handle_menu_selection(stdscr, current_row)
stdscr.clear()
stdscr.refresh()
def handle_menu_selection(stdscr, selection):
if selection == 0: # 添加记录
add_transaction(stdscr)
elif selection == 1: # 查看记录
view_transactions(stdscr)
elif selection == 2: # 统计分析
show_statistics(stdscr)
3.3 核心功能实现
3.3.1 添加收支记录
python复制def add_transaction(stdscr):
stdscr.clear()
stdscr.addstr(0, 0, "添加新记录")
# 获取交易类型
stdscr.addstr(2, 0, "类型 (1.收入 2.支出): ")
curses.echo()
tran_type = stdscr.getstr(2, 20).decode('utf-8')
tran_type = 'income' if tran_type == '1' else 'expense'
curses.noecho()
# 获取金额
stdscr.addstr(3, 0, "金额: ")
curses.echo()
amount = float(stdscr.getstr(3, 10).decode('utf-8'))
curses.noecho()
# 获取分类
categories = get_categories(tran_type)
stdscr.addstr(4, 0, "分类: ")
for i, cat in enumerate(categories):
stdscr.addstr(5+i, 0, f"{i+1}. {cat[1]}")
stdscr.addstr(5+len(categories), 0, "选择分类编号: ")
curses.echo()
cat_idx = int(stdscr.getstr(5+len(categories), 20).decode('utf-8')) - 1
curses.noecho()
category_id = categories[cat_idx][0]
# 获取日期
stdscr.addstr(6+len(categories), 0, "日期 (YYYY-MM-DD, 留空为今天): ")
curses.echo()
date_str = stdscr.getstr(6+len(categories), 40).decode('utf-8')
curses.noecho()
date = date_str if date_str else datetime.now().strftime('%Y-%m-%d')
# 获取备注
stdscr.addstr(7+len(categories), 0, "备注: ")
curses.echo()
note = stdscr.getstr(7+len(categories), 10).decode('utf-8')
curses.noecho()
# 保存到数据库
conn = sqlite3.connect('finance.db')
c = conn.cursor()
c.execute("INSERT INTO transactions (amount, type, category_id, date, note) VALUES (?, ?, ?, ?, ?)",
(amount, tran_type, category_id, date, note))
conn.commit()
conn.close()
stdscr.addstr(9+len(categories), 0, "记录已保存! 按任意键返回...")
stdscr.getch()
3.3.2 查看记录
python复制def view_transactions(stdscr):
stdscr.clear()
stdscr.addstr(0, 0, "收支记录")
conn = sqlite3.connect('finance.db')
c = conn.cursor()
# 获取所有记录
c.execute('''SELECT t.id, t.amount, t.type, c.name, t.date, t.note
FROM transactions t JOIN categories c ON t.category_id = c.id
ORDER BY t.date DESC''')
transactions = c.fetchall()
# 显示记录
if not transactions:
stdscr.addstr(2, 0, "暂无记录")
else:
stdscr.addstr(2, 0, "ID 日期 类型 金额 分类 备注")
for i, tran in enumerate(transactions):
tran_id, amount, tran_type, cat_name, date, note = tran
row = f"{tran_id:<4} {date} {'收入' if tran_type == 'income' else '支出'} {amount:<8.2f} {cat_name:<10} {note}"
stdscr.addstr(3+i, 0, row)
conn.close()
stdscr.addstr(5+len(transactions), 0, "按任意键返回...")
stdscr.getch()
3.3.3 统计分析
python复制def show_statistics(stdscr):
stdscr.clear()
stdscr.addstr(0, 0, "统计分析")
conn = sqlite3.connect('finance.db')
c = conn.cursor()
# 本月收支统计
current_month = datetime.now().strftime('%Y-%m')
c.execute("SELECT SUM(amount) FROM transactions WHERE type='income' AND strftime('%Y-%m', date) = ?", (current_month,))
total_income = c.fetchone()[0] or 0
c.execute("SELECT SUM(amount) FROM transactions WHERE type='expense' AND strftime('%Y-%m', date) = ?", (current_month,))
total_expense = c.fetchone()[0] or 0
stdscr.addstr(2, 0, f"本月收入: {total_income:.2f}")
stdscr.addstr(3, 0, f"本月支出: {total_expense:.2f}")
stdscr.addstr(4, 0, f"本月结余: {total_income - total_expense:.2f}")
# 支出分类统计
stdscr.addstr(6, 0, "支出分类统计:")
c.execute('''SELECT c.name, SUM(t.amount)
FROM transactions t JOIN categories c ON t.category_id = c.id
WHERE t.type='expense' AND strftime('%Y-%m', t.date) = ?
GROUP BY c.name''', (current_month,))
expenses_by_cat = c.fetchall()
for i, (cat_name, amount) in enumerate(expenses_by_cat):
stdscr.addstr(7+i, 0, f"{cat_name}: {amount:.2f}")
conn.close()
stdscr.addstr(9+len(expenses_by_cat), 0, "按任意键返回...")
stdscr.getch()
4. 高级功能与优化
4.1 数据备份与恢复
考虑到数据安全,我们需要实现备份功能:
python复制def backup_data(stdscr):
stdscr.clear()
stdscr.addstr(0, 0, "数据备份")
backup_dir = "/sdcard/FinanceBackup"
if not os.path.exists(backup_dir):
os.makedirs(backup_dir)
backup_file = os.path.join(backup_dir, f"finance_backup_{datetime.now().strftime('%Y%m%d')}.db")
# 复制数据库文件
shutil.copy2('finance.db', backup_file)
stdscr.addstr(2, 0, f"备份已保存到: {backup_file}")
stdscr.addstr(3, 0, "按任意键返回...")
stdscr.getch()
def restore_data(stdscr):
stdscr.clear()
stdscr.addstr(0, 0, "数据恢复")
backup_dir = "/sdcard/FinanceBackup"
if not os.path.exists(backup_dir):
stdscr.addstr(2, 0, "未找到备份目录")
stdscr.addstr(3, 0, "按任意键返回...")
stdscr.getch()
return
backups = [f for f in os.listdir(backup_dir) if f.endswith('.db')]
if not backups:
stdscr.addstr(2, 0, "未找到备份文件")
stdscr.addstr(3, 0, "按任意键返回...")
stdscr.getch()
return
stdscr.addstr(2, 0, "选择要恢复的备份:")
for i, backup in enumerate(backups):
stdscr.addstr(3+i, 0, f"{i+1}. {backup}")
stdscr.addstr(4+len(backups), 0, "输入备份编号: ")
curses.echo()
choice = int(stdscr.getstr(4+len(backups), 20).decode('utf-8')) - 1
curses.noecho()
# 恢复备份
backup_file = os.path.join(backup_dir, backups[choice])
shutil.copy2(backup_file, 'finance.db')
stdscr.addstr(6+len(backups), 0, "数据恢复完成! 按任意键返回...")
stdscr.getch()
4.2 预算管理
python复制def manage_budgets(stdscr):
stdscr.clear()
stdscr.addstr(0, 0, "预算管理")
conn = sqlite3.connect('finance.db')
c = conn.cursor()
# 获取所有支出分类
c.execute("SELECT id, name FROM categories WHERE type='expense'")
categories = c.fetchall()
# 获取现有预算
c.execute('''SELECT c.id, c.name, b.amount, b.period
FROM categories c LEFT JOIN budgets b ON c.id = b.category_id
WHERE c.type='expense' ''')
budgets = c.fetchall()
# 显示预算
stdscr.addstr(2, 0, "分类 预算金额 周期")
for i, (cat_id, cat_name, amount, period) in enumerate(budgets):
amount_str = f"{amount:.2f}" if amount else "未设置"
period_str = period if period else ""
stdscr.addstr(3+i, 0, f"{cat_name:<10} {amount_str:<10} {period_str}")
# 预算设置菜单
stdscr.addstr(5+len(budgets), 0, "1. 设置预算 2. 返回")
stdscr.addstr(6+len(budgets), 0, "选择操作: ")
curses.echo()
choice = stdscr.getstr(6+len(budgets), 20).decode('utf-8')
curses.noecho()
if choice == '1':
set_budget(stdscr, categories)
conn.close()
def set_budget(stdscr, categories):
stdscr.clear()
stdscr.addstr(0, 0, "设置预算")
# 选择分类
stdscr.addstr(2, 0, "选择分类:")
for i, (cat_id, cat_name) in enumerate(categories):
stdscr.addstr(3+i, 0, f"{i+1}. {cat_name}")
stdscr.addstr(4+len(categories), 0, "输入分类编号: ")
curses.echo()
cat_idx = int(stdscr.getstr(4+len(categories), 20).decode('utf-8')) - 1
curses.noecho()
category_id = categories[cat_idx][0]
# 输入预算金额
stdscr.addstr(6+len(categories), 0, "预算金额: ")
curses.echo()
amount = float(stdscr.getstr(6+len(categories), 20).decode('utf-8'))
curses.noecho()
# 选择周期
stdscr.addstr(8+len(categories), 0, "周期 (1.月 2.周): ")
curses.echo()
period_choice = stdscr.getstr(8+len(categories), 20).decode('utf-8')
curses.noecho()
period = 'month' if period_choice == '1' else 'week'
# 保存预算
conn = sqlite3.connect('finance.db')
c = conn.cursor()
# 检查是否已有预算
c.execute("SELECT id FROM budgets WHERE category_id=?", (category_id,))
existing = c.fetchone()
if existing:
c.execute("UPDATE budgets SET amount=?, period=? WHERE category_id=?",
(amount, period, category_id))
else:
c.execute("INSERT INTO budgets (category_id, amount, period) VALUES (?, ?, ?)",
(category_id, amount, period))
conn.commit()
conn.close()
stdscr.addstr(10+len(categories), 0, "预算设置成功! 按任意键返回...")
stdscr.getch()
4.3 预算提醒功能
python复制def check_budget_alerts():
conn = sqlite3.connect('finance.db')
c = conn.cursor()
# 获取当前月份
current_month = datetime.now().strftime('%Y-%m')
# 获取所有预算
c.execute('''SELECT c.name, b.amount, b.period
FROM budgets b JOIN categories c ON b.category_id = c.id''')
budgets = c.fetchall()
alerts = []
for cat_name, amount, period in budgets:
if period == 'month':
# 计算本月已支出
c.execute('''SELECT SUM(t.amount)
FROM transactions t JOIN categories c ON t.category_id = c.id
WHERE c.name=? AND t.type='expense' AND strftime('%Y-%m', t.date) = ?''',
(cat_name, current_month))
spent = c.fetchone()[0] or 0
# 计算百分比
percent = (spent / amount) * 100
if percent > 80:
alerts.append(f"{cat_name}预算已使用{percent:.0f}%")
conn.close()
return alerts
5. 部署与使用指南
5.1 安装与配置
- 在Android设备上安装Termux
- 打开Termux,执行以下命令:
bash复制
pkg update pkg upgrade pkg install python git pip install --upgrade pip - 克隆项目代码:
bash复制git clone https://github.com/yourusername/finance-app.git cd finance-app - 安装依赖:
bash复制
pip install -r requirements.txt - 初始化数据库:
bash复制
python init_db.py
5.2 添加初始分类
在首次使用前,需要添加一些基本的收支分类:
python复制def init_categories():
conn = sqlite3.connect('finance.db')
c = conn.cursor()
# 收入分类
income_categories = [
('工资', 'income'),
('奖金', 'income'),
('投资', 'income'),
('其他收入', 'income')
]
# 支出分类
expense_categories = [
('餐饮', 'expense'),
('交通', 'expense'),
('购物', 'expense'),
('娱乐', 'expense'),
('住房', 'expense'),
('医疗', 'expense'),
('教育', 'expense'),
('其他支出', 'expense')
]
c.executemany("INSERT INTO categories (name, type) VALUES (?, ?)", income_categories)
c.executemany("INSERT INTO categories (name, type) VALUES (?, ?)", expense_categories)
conn.commit()
conn.close()
5.3 日常使用
- 启动应用:
bash复制
python main.py - 主菜单选项:
- 添加记录:记录日常收支
- 查看记录:浏览历史记录
- 统计分析:查看收支报表
- 预算管理:设置和查看预算
- 数据备份:备份和恢复数据
5.4 定期维护
- 定期备份数据(建议每周一次)
- 检查预算使用情况
- 根据需要调整分类
6. 开发经验与优化建议
在实际开发过程中,我积累了一些有价值的经验:
-
Termux环境下的Python开发:
- 使用SSH连接Termux可以大大提高开发效率
- 安装termux-api可以访问更多Android系统功能
- 注意Termux的存储权限问题,需要运行
termux-setup-storage命令
-
性能优化:
- 对于大量数据,考虑添加索引提高查询速度
- 定期执行
VACUUM命令优化数据库文件 - 使用连接池管理数据库连接
-
用户体验改进:
- 添加快捷键支持,提高操作效率
- 实现搜索和筛选功能,方便查找特定记录
- 添加导出功能,支持导出为CSV或Excel格式
-
安全性考虑:
- 对敏感操作添加确认提示
- 考虑添加密码保护功能
- 备份文件可以加密存储
-
扩展功能思路:
- 添加多账户支持
- 实现定期收支(如工资、房租等)自动记录
- 添加图表可视化功能
- 支持自定义报表
这个项目展示了Python在移动端的应用潜力,虽然Termux环境下的文本界面不如原生应用美观,但它提供了快速开发和高度定制化的优势。对于有一定技术基础的用户来说,这是一个既实用又有趣的个人财务管理解决方案。
