IHateMoney 7.2.1 API/CLI 使用详解

IHateMoney 7.2.1 REST API 与管理 CLI 全端点实测详解:Basic/Bearer Token 双认证、项目/成员/账单/统计/货币五域逐条请求与真实回显、错误码语义(401 防枚举、405 禁列项目)、账单无分页与跨币种无汇率源等行为边界,附 routes 全路由表与 db/delete-project/get-project-count 等 9 条 CLI 命令实测。

IHateMoney财务记账自托管2026/10/23

IHateMoney 7.2.1 API/CLI 使用详解

目标:对 IHateMoney 7.2.1 的 REST API 全部端点与管理 CLI 全部子命令做逐条实测——认证方式、请求参数、真实响应回显、错误码语义与行为边界(成员更新全量替换、账单无分页、跨币种无汇率源、db 子命令工作目录依赖等)。已在 Ubuntu 22.04 + ihatemoney 7.2.1(pip 安装 + Gunicorn/systemd 形态,安装过程见《Ubuntu 22.04 安装 IHateMoney 7.2.1 指南》)实测通过,API 基地址 http://127.0.0.1:8000。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • IHateMoney:7.2.1(pip 安装,虚拟环境 /home/ihatemoney/ihatemoney)
  • 运行形态:Gunicorn 26.2.0(3 workers,监听 127.0.0.1:8000)+ systemd(ihatemoney.service)
  • API 客户端:curl(系统自带)
  • 数据库:SQLite /var/lib/ihatemoney/ihatemoney.sqlite(alembic 迁移头 c941aaca38c2)
  • 配置基线:ALLOW_PUBLIC_PROJECT_CREATION = True、ENABLE_CAPTCHA = False(与《IHateMoney 7.2.1 配置参数详解》第六章生产定稿一致;若您按该篇把创建收口为 False,本文所有「创建/更新项目」请求会返回 400,详见其 5.3 节实测)
  • 演示项目口令约定:测试项目 apitest 私有码 test1234、clitest 私有码 cli12345,均为演示值

二、前置条件(端点/认证/客户端工具)

确认服务健康,并拿到可用的 API 端点清单:

systemctl is-active ihatemoney.service
curl -s -w "\nHTTP %{http_code}\n" http://127.0.0.1:8000/healthcheck

实测回显:

active
OK
HTTP 200

/healthcheck 是应用自带健康检查端点(无认证),接监控探活直接用它。

认证模型一句话概括:除了创建项目,其余一切请求都要带「项目 ID + 私有码」的 Basic 认证,或用该项目签发的 Bearer Token。没有全局管理员 API 账号(管理口令只作用于 Web 端管理页,不作用于 API,实测见《IHateMoney 7.2.1 配置参数详解》5.3 节)。

准备一个干净测试项目(后文全部示例基于它):

curl -s -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:8000/api/projects \
  -d 'name=API 测试项目&id=apitest&password=test1234&contact_email=api@example.com'

实测回显:

"apitest"
HTTP 201

三、API 总览

  • 协议与风格:REST,主格式 JSON;POST/PUT 同时接受 application/x-www-form-urlencoded 与 JSON body(两种均有实测);无独立版本前缀,端点固定在 /api/ 下。
  • 认证方式:① 项目级 Basic(id:私有码);② Bearer Token(向项目签发,见 4.2);③ 创建项目(POST /api/projects)免认证。
  • 限流:7.2.1 无内置限流参数(配置模板与镜像默认 Env 均无 RATELIMIT 项),官方文档亦未声明频控;公网实例如需限流在 Nginx 层实现。

API 路由总表(取自 ihatemoney routes 实测回显的 api.* 行,完整路由表见 7.7):

EndpointMethodsRule
api.projectshandlerPOST/api/projects
api.projecthandlerDELETE, GET, PUT/api/projects/<project_id>
api.tokenhandlerGET/api/projects/<project_id>/token
api.membershandlerGET, POST/api/projects/<project_id>/members
api.memberhandlerDELETE, GET, PUT/api/projects/<project_id>/members/<member_id>
api.billshandlerGET, POST/api/projects/<project_id>/bills
api.billhandlerDELETE, GET, PUT/api/projects/<project_id>/bills/<bill_id>
api.projectstatshandlerGET/api/projects/<project_id>/statistics
api.currencieshandlerGET/api/currencies

四、认证与首次调用

4.1 Basic 认证

用项目 ID 与私有码做 HTTP Basic,查项目信息即「首次调用」:

curl -s -w "\nHTTP %{http_code}\n" --basic -u apitest:test1234 \
  http://127.0.0.1:8000/api/projects/apitest

实测回显:

{"id": "apitest", "name": "API \u6d4b\u8bd5\u9879\u76ee", "contact_email": "api@example.com", "logging_preference": 1, "members": [], "default_currency": "XXX"}
HTTP 200

(中文按 JSON 默认转为 \uXXXX 转义序列,属标准行为,客户端解码后即原文。)

4.2 Token 认证(签发与使用)

Token 端点是 GET(官方示例即 GET;实测误用 POST 会得到 405):

curl -s --basic -u apitest:test1234 http://127.0.0.1:8000/api/projects/apitest/token

实测回显:

{"token": "WyJhcGl0ZXN0Il0.ErD4nxzYFN9dT3htRM2YhG-cKBs"}

token 由「["apitest"] 的 Base64 + 签名」构成(WyJhcGl0ZXN0Il0 解码即 ["apitest"]),与项目绑定。后续请求用 Bearer 头替代 Basic:

T=$(curl -s --basic -u apitest:test1234 http://127.0.0.1:8000/api/projects/apitest/token | sed -E 's/.*"token": "([^"]+)".*/\1/')
curl -s -o /dev/null -w "Bearer查项目 HTTP %{http_code}\n" --oauth2-bearer "$T" http://127.0.0.1:8000/api/projects/apitest
curl -s -o /dev/null -w "Bearer查成员 HTTP %{http_code}\n" --oauth2-bearer "$T" http://127.0.0.1:8000/api/projects/apitest/members
curl -s -o /dev/null -w "无效token HTTP %{http_code}\n" --oauth2-bearer "invalid.token.value" http://127.0.0.1:8000/api/projects/apitest

实测回显:

Bearer查项目 HTTP 200
Bearer查成员 HTTP 200
无效token HTTP 401

token 还可直接用作 Web 加入链接(官方文档定位:授予该项目读写权限、但不能改项目设置):

curl -s -o /dev/null -w "GET /apitest/join/<token> HTTP %{http_code}\n" \
  "http://127.0.0.1:8000/apitest/join/$T"

实测回显:

GET /apitest/join/<token> HTTP 200

4.3 错误码语义(实测甄别)

场景实测状态码实测响应体
私有码错误401{"message": "The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required."}
项目不存在401与私有码错误完全相同(同一文案同一码)
未带认证401同上
对 /api/projects 发 GET(列项目)405{"message": "The method is not allowed for the requested URL."}
表单校验失败400{"<字段名>": ["<原因>"]}
项目内资源不存在(如账单 id)404"Not Found"

注意两点:其一,「密码错」与「项目不存在」返回完全相同的 401,这是防枚举的安全设计,客户端脚本不能靠 401/404 区分两者;其二,官方出于安全不提供项目列表接口,GET /api/projects 直接 405(路由表里该端点只有 POST),自动化场景需自行维护项目 ID 清单。

五、各 API 域详解

5.1 项目域

创建项目 POST /api/projects(免认证)——必填 name/id/password/contact_email,可选 default_currency(ISO 4217,默认 XXX 表示无货币)。id 会被归一化(大写转小写等,实测提交 id=TestProj 返回 "testproj"):

curl -s -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:8000/api/projects \
  -d 'name=API 测试项目&id=apitest&password=test1234&contact_email=api@example.com'

实测回显:

"apitest"
HTTP 201

重复 id 与缺必填字段的失败形态(实测回显):

{"id": ["A project with this identifier (\"apitest\") already exists. Please choose a new identifier"]}   ← 400
{"contact_email": ["This field is required."]}   ← 400

也支持 JSON body 建项目(实测 201,回显 "jsontest")。

更新项目 PUT /api/projects/<id>——必须带 current_password(当前私有码),这是防「仅持 token 即可改设置」的安全措施;id 永远不可改。实测改私有码:

curl -s -w "\nHTTP %{http_code}\n" --basic -u apitest:test1234 -X PUT \
  http://127.0.0.1:8000/api/projects/apitest \
  -d 'name=API 测试项目&id=apitest&current_password=test1234&password=newcode9&contact_email=api@example.com'

实测回显:

"UPDATED"
HTTP 200

随后旧私有码立即失效(401)、新私有码可查询(200);缺 current_password 的更新返回 {"current_password": ["This field is required."]} 400。default_currency 也在此端点修改(实测改 CNY 返回 "UPDATED",跨币种账单的连带影响见 5.5)。

删除项目 DELETE /api/projects/<id>(Basic 认证):

curl -s -w "\nHTTP %{http_code}\n" --basic -u apitest:test1234 -X DELETE \
  http://127.0.0.1:8000/api/projects/apitest

实测回显:

"DELETED"
HTTP 200

删除后再查同项目返回 401(与「不存在」同形态,见 4.3)。

5.2 成员域

添加成员 POST /api/projects/<id>/members,参数 name,可选 weight(权重,默认 1)。实测依次添加 alice、bob、carol(weight=2)、dave,响应为纯数字 id(依次 1/2/3/4)。

成员列表 GET /api/projects/<id>/members:

curl -s --basic -u apitest:test1234 http://127.0.0.1:8000/api/projects/apitest/members

实测回显:

[{"id": 1, "name": "alice", "weight": 1.0, "activated": true}, {"id": 2, "name": "bob", "weight": 1.0, "activated": true}, {"id": 3, "name": "carol", "weight": 2.0, "activated": true}, {"id": 4, "name": "dave", "weight": 1.0, "activated": true}]

更新成员 PUT /api/projects/<id>/members/<member_id>——实测发现 7.2.1 的一个重要行为边界:PUT 是全量替换,未传的字段直接回退默认值。只传 name 会把成员停用并把权重打回 1:

curl -s -w "\nHTTP %{http_code}\n" --basic -u apitest:test1234 -X PUT \
  http://127.0.0.1:8000/api/projects/apitest/members/3 -d 'name=Carol'

实测回显:

{"id": 7, "name": "Carol", "weight": 1.0, "activated": false}
HTTP 200

(weight 由 2.0 变 1.0、activated 由 true 变 false——与官方旧版文档「只传 name 也安全」的示例行为不一致,7.2.1 实测必须显式带上。)改回需传全字段,推荐 JSON body:

curl -s -w "\nHTTP %{http_code}\n" --basic -u apitest:test1234 -X PUT \
  http://127.0.0.1:8000/api/projects/apitest/members/3 \
  -H 'Content-Type: application/json' \
  -d '{"name": "Carol", "weight": 2, "activated": true}'

实测回显:

{"id": 7, "name": "Carol", "weight": 2.0, "activated": true}
HTTP 200

删除成员 DELETE /api/projects/<id>/members/<member_id>——实测发现软硬两种删除:无账单引用的成员被硬删(实测删除 dave(id=8) 后重新添加,新 id=9,id 8 消失且列表无痕);有账单引用的成员被软删(实测删除有账单的 carol,返回 "OK",列表中仍可见但 activated: false):

[{"id": 5, "name": "alice", "weight": 1.0, "activated": true}, {"id": 6, "name": "bob", "weight": 1.0, "activated": true}, {"id": 7, "name": "Carol", "weight": 2.0, "activated": false}, {"id": 9, "name": "dave", "weight": 1.0, "activated": true}]

连带影响:activated=false 的成员不能作为账单的付款人/分摊人,选它会报 {"payed_for": ["'3' is not a valid choice for this field."]} 400(实测)。

5.3 账单域

创建账单 POST /api/projects/<id>/bills——必填 what/payer(成员 id)/payed_for(成员 id,多人用重复参数或 JSON 数组)/amount;可选 date(缺省为当天,实测不传 date 自动记为 2026-10-23)、original_currency、external_link。响应为纯数字账单 id。表单形态实测:

curl -s --basic -u apitest:test1234 -X POST http://127.0.0.1:8000/api/projects/apitest/bills \
  -d 'what=晚餐&date=2026-10-20&payer=5&payed_for=6&payed_for=7&amount=150'

实测回显:

1

JSON 形态实测(多人分摊用数组):

curl -s --basic -u apitest:test1234 -X POST http://127.0.0.1:8000/api/projects/apitest/bills \
  -H 'Content-Type: application/json' \
  -d '{"what": "超市采购", "date": "2026-10-22", "payer": 6, "payed_for": [5, 7], "amount": 90}'

实测回显:

2

账单列表 GET /api/projects/<id>/bills(实测默认按日期倒序排列):

curl -s --basic -u apitest:test1234 http://127.0.0.1:8000/api/projects/apitest/bills

实测回显(节选首条,注意 7.2.1 新增 bill_type 字段):

[{"id": 3, "payer_id": 6, "owers": [{"id": 5, "name": "alice", "weight": 1.0, "activated": true}, {"id": 7, "name": "Carol", "weight": 2.0, "activated": true}], "amount": 60.0, "date": "2026-10-23", "creation_date": "2026-10-23", "what": "\u7535\u5f71\u7968", "bill_type": "Expense", "external_link": "", "original_currency": "XXX", "converted_amount": 60.0}, ...]

查单条 GET .../bills/<bill_id>——字段含 amount(原币金额)与 converted_amount(换算到项目默认货币后的金额,无货币项目两者相等)。

修改账单 PUT .../bills/<bill_id>(全字段重传,返回账单 id,实测 200 回显 3);删除账单 DELETE .../bills/<bill_id>(回显 "OK",实测 200)。查询不存在的账单返回 "Not Found" 404(实测 bill id 99999)。缺 payed_for 的创建返回 {"payed_for": ["This field is required."]} 400。

5.4 统计域

GET /api/projects/<id>/statistics——按成员返回 paid(垫付)、spent(消费,负值)、transferred/received(结算转账)、balance(净额)。基于上节三笔账单(晚餐 150=alice 垫付、超市 90 与电影 80=bob 垫付,carol 权重 2)实测:

curl -s --basic -u apitest:test1234 http://127.0.0.1:8000/api/projects/apitest/statistics

实测回显:

[{"member": {"id": 5, "name": "alice", "weight": 1.0, "activated": true}, "spent": -56.66666666666667, "paid": 150.0, "transferred": 0.0, "received": -0.0, "balance": 93.33333333333333}, {"member": {"id": 6, "name": "bob", "weight": 1.0, "activated": true}, "spent": -50.0, "paid": 170.0, "transferred": 0.0, "received": -0.0, "balance": 120.0}, {"member": {"id": 7, "name": "Carol", "weight": 2.0, "activated": true}, "spent": -213.33333333333334, "paid": 0.0, "transferred": 0.0, "received": -0.0, "balance": -213.33333333333334}]

数字可人工复核(权重分摊):晚餐 150 由 bob(1)+carol(2) 分摊,carol 承担 150×2/3=100;同理超市 90×2/3=60、电影 80×2/3≈53.33,合计 213.33,与回显一致。

5.5 货币域

GET /api/currencies 返回全部支持的 ISO 4217 货币码(实测首段 ["AED", "AFN", "ALL", ...],含无货币占位 XXX)。

重要边界:把项目 default_currency 改为 CNY 后,再创建 original_currency=USD 的账单被直接拒绝:

curl -s -w "\nHTTP %{http_code}\n" --basic -u apitest:test1234 -X POST \
  http://127.0.0.1:8000/api/projects/apitest/bills \
  -d 'what=跨境订阅&payer=5&payed_for=6&amount=100&original_currency=USD'

实测回显:

{"original_currency": ["Failed to convert from USD currency to CNY"]}
HTTP 400

7.2.1 没有内置汇率源,有默认货币的项目无法记其他币种的账单。建议:统一币种实例保持默认 XXX(各账单自记本币金额),或所有账单与项目默认货币一致。

5.6 错误码汇总

码触发场景(实测)应对
400校验失败(缺字段/重复 id/已停用成员入选/跨币种无汇率/ALLOW_PUBLIC_PROJECT_CREATION=False 拦停)看响应体字段级原因
401私有码错、项目不存在、未认证、无效 token、项目已删除同文案,无法区分,核对 id 与私有码
404项目内资源不存在(账单/成员 id 无效)核对资源 id
405对只读/不存在的方法调端点(GET /api/projects、POST /token)改用正确方法

六、分页/过滤/排序通用约定

7.2.1 API 没有分页、过滤、排序参数,实测证据:

curl -s --basic -u apitest:test1234 "http://127.0.0.1:8000/api/projects/apitest/bills?limit=1" | python3 -c 'import sys,json; print(len(json.load(sys.stdin)))'
curl -s --basic -u apitest:test1234 "http://127.0.0.1:8000/api/projects/apitest/bills?order=name" | head -c 40; echo

实测回显(limit=1 仍返回全部 3 条;order=name 输出与不带参数完全相同):

3
[{"id": 3, "payer_id": 6, "owers": [{"id": 5, "nam

行为约定:账单列表默认按日期倒序固定返回全量;成员列表全量返回;项目列表不存在(405)。账单量大的项目(数千笔以上)如需分页/检索,建议在客户端拉全量后自行处理,或直接读数据库(表 bill/person,只读)。

七、CLI 管理命令详解

7.1 命令清单与调用形态

ihatemoney --help 实测回显(命令全集):

Commands:
  db                      Perform database migrations.
  delete-project          Delete a project
  generate-config         Generate front-end server configuration
  generate_password_hash  Get password from user and hash it without...
  get-project-count       Count projets with at least x bills and at less...
  limiter                 Flask-Limiter maintenance & utility commands
  routes                  Show the routes for the app.
  run                     Run a development server.
  runserver               Deprecated, use the "run" command instead
  shell                   Run a shell in the app context.

调用形态(服务以专用用户运行时):

runuser -u ihatemoney -- bash -c 'source /home/ihatemoney/ihatemoney/bin/activate && ihatemoney <命令>'

注意:每个 ihatemoney 命令执行前都会加载应用并自动建库/跑迁移(输出形如 INFO [alembic.runtime.migration] Context impl SQLiteImpl. 属正常现象,非报错)。

7.2 generate-config——生成配置模板

ihatemoney generate-config --help

实测回显(支持 5 种模板):

Usage: ihatemoney generate-config [OPTIONS] {ihatemoney.cfg|apache-vhost.conf|
                                  gunicorn.conf.py|supervisord.conf|nginx.conf
                                  }

  Generate front-end server configuration

用法(重定向落盘)与逐模板用途见《Ubuntu 22.04 安装 IHateMoney 7.2.1 指南》方案 A 实测段。

7.3 generate_password_hash——生成管理口令哈希

交互读入口令并输出 pbkdf2 哈希。7.2.1 只认 pbkdf2: 前缀、方法串格式为 pbkdf2:摘要:轮数(OpenSSL 3 下必须显式 sha256),完整三坑实测与配置方法见《IHateMoney 7.2.1 配置参数详解》5.2 节,此处不重复。

7.4 db——数据库迁移

子命令含 current/heads/history/check/upgrade/downgrade 等(ihatemoney db --help 可见全集)。实测查询当前迁移状态:

工作目录硬约束(实测踩坑):db 子命令默认在 当前目录 找 migrations/,不在包目录下执行会静默失败(rc=1、无任何错误文本)。实测对比:

$ cd /home/ihatemoney && ihatemoney db current        # rc=1,无输出
$ cd /home/ihatemoney/ihatemoney/lib/python3.11/site-packages/ihatemoney && ihatemoney db current
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
c941aaca38c2 (head)                                    # rc=0

定稿用法(先 cd 到包目录):

PKG=/home/ihatemoney/ihatemoney/lib/python3.11/site-packages/ihatemoney
runuser -u ihatemoney -- bash -c "source /home/ihatemoney/ihatemoney/bin/activate && cd $PKG && ihatemoney db current && ihatemoney db heads"

实测回显:

c941aaca38c2 (head)
c941aaca38c2 (head)

db history 实测(前 3 行,最近迁移在顶部):

06884b17c50f -> c941aaca38c2 (head), remove ip recording
7a9b38559992 -> 06884b17c50f, fix password hash length
927ed575acbd -> 7a9b38559992, new bill type attribute added

db check 在 SQLite 实例上实测返回 1 并输出模型与库结构的类型比对噪声(如 Detected type change from TEXT() to Enum(...))——这是 alembic 对 SQLite 类型比对能力的限制,判断升级状态以 db current/db heads 一致为准,不必因 check 报告告警。

7.5 get-project-count——统计活跃项目

源码参数默认值:print_emails=False、bills=0、days=73000(约 200 年,即不限时间)。语义:统计「账单数严格大于 bills 且最近一笔账单在 days 天内」的项目数。实测三种调用(测试项目 clitest 有 2 笔当天账单):

runuser -u ihatemoney -- bash -c 'source /home/ihatemoney/ihatemoney/bin/activate && ihatemoney get-project-count'

实测回显:

Number of projects: 1

ihatemoney get-project-count "" 1 30(超过 1 笔且 30 天内活跃)→ Number of projects: 1;ihatemoney get-project-count y 1 30(附带联系邮箱)实测回显:

Number of projects: 1
Contact email: cli@example.com

适用场景:实例管理员巡检活跃度(配合 journalctl 与数据库备份节奏决策)。

7.6 delete-project——删除项目

无交互确认,直接删除(源码即 db.session.delete(project)),删错只能从备份恢复。实测删除不存在的项目:

Project "nosuchproj" not found

(红色提示,rc=0。)实测删除 clitest 后验证:API 查询 401、get-project-count 归 0(均实测通过)。

7.7 routes——全路由表

ihatemoney routes 实测回显(API 之外的全部 Web 路由,做反向代理路径规划与安全收敛时对照):

Endpoint                       Methods           Rule
-----------------------------  ----------------  ---------------------------------------------------------
main.add_bill                  GET, POST         /<project_id>/add
main.add_member                GET, POST         /<project_id>/members/add
main.add_settlement_bill       POST              /<project_id>/settle
main.admin                     GET, POST         /admin
main.authenticate              GET, POST         /authenticate
main.change_lang               GET               /lang/<lang>
main.create_project            GET, POST         /create
main.dashboard                 GET               /dashboard
main.dashboard_delete_project  POST              /dashboard/<project_id>/delete
main.delete_bill               POST              /<project_id>/delete/<int:bill_id>
main.delete_project            POST              /<project_id>/delete
main.demo                      GET               /demo
main.edit_bill                 GET, POST         /<project_id>/edit/<int:bill_id>
main.edit_member               GET, POST         /<project_id>/members/<member_id>/edit
main.edit_project              GET, POST         /<project_id>/edit
main.erase_history             POST              /<project_id>/erase_history
main.exit                      GET, POST         /exit
main.export_project            GET               /<project_id>/export/<string:file>.<string:format>
main.favicon                   GET               /favicon.ico
main.feed                      GET               /<project_id>/feed/<string:token>.xml
main.health                    GET               /healthcheck
main.history                   GET               /<project_id>/history
main.home                      GET               /
main.import_project            POST              /<project_id>/import
main.invite                    GET, POST         /<project_id>/invite
main.join_project              GET               /<project_id>/join/<string:token>
main.list_bills                GET               /<project_id>/
main.mobile                    GET               /mobile
main.password_reminder_sent    GET               /password-reminder-sent
main.reactivate                POST              /<project_id>/members/<member_id>/reactivate
main.remind_password           GET, POST         /password-reminder
main.remove_member             POST              /<project_id>/members/<member_id>/delete
main.reset_password            GET, POST         /reset-password
main.settle_bill               GET               /<project_id>/settle_bills
main.statistics                GET               /<project_id>/statistics
static                         GET               /static/<path:filename>

(api.* 9 条已在第三章列表,此处略。)值得注意:/<project_id>/export/<file>.<format> 支持导出——file 取 bills(账单明细)或 transactions(结算转账清单),format 取 csv 或 json(其余取值 404,分支逻辑实测源码确认);/dashboard 即管理仪表盘(受 ACTIVATE_ADMIN_DASHBOARD 与 ADMIN_PASSWORD 控制,见配置篇)。

7.8 run / runserver——开发服务器

ihatemoney run 拉起 Werkzeug 开发服务器(-p 端口、-h 地址),实测:

runuser -u ihatemoney -- bash -c 'source /home/ihatemoney/ihatemoney/bin/activate && ihatemoney run -p 8001 -h 127.0.0.1 &'
sleep 4
curl -s -o /dev/null -w "dev server HTTP %{http_code}\n" http://127.0.0.1:8001/

实测回显:

dev server HTTP 200
INFO  [werkzeug] WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.

开发服务器仅供调试(启动日志自带 Werkzeug 警告),生产一律 Gunicorn/systemd(见 B1)。runserver 为弃用别名,勿再使用。

7.9 shell / limiter

shell 在应用上下文内开 Python Shell(可直查 Project/Person/Bill 模型,排查数据问题用,交互式工具不做回显截图);limiter 为 Flask-Limiter 维护命令,实测其子命令清单:

Commands:
  clear   Clear limits for a specific key
  config  View the extension configuration
  limits  Enumerate details about all routes with rate limits

(默认配置未启用限流,命令日常用不上,列此处备查。)

八、常见问题(FAQ)

Q1:API 返回 401,到底是密码错了还是项目不存在? 无法区分——两者返回完全相同的 401 与文案(实测见 4.3),这是防枚举设计。核对项目 id 拼写与私有码即可;用 token 的场景先重新签发一次 token 排除签名密钥变更(SECRET_KEY 换过会导致旧 token 全部失效)。

Q2:PUT 改成员后,成员被停用、权重也丢了? 7.2.1 的成员 PUT 是全量替换:未传 activated 会被置 false、未传 weight 回退 1.0(实测见 5.2)。改成员必须带全字段,推荐 JSON body {"name": "Carol", "weight": 2, "activated": true}。停用的成员立即不能被账单选用('3' is not a valid choice)。

Q3:脚本想拉全部项目列表,怎么做? API 层无解:GET /api/projects 是 405(官方安全设计,见 4.3)。方案:调用方自行维护项目清单(建项目时登记);或管理员在服务器上只读查库(SQLite select id from project;)。

Q4:记外币账单报 Failed to convert from USD currency to CNY? 项目设了默认货币后,7.2.1 要求账单币种可换算到默认货币,而应用无内置汇率源(实测见 5.5)。保持项目为 XXX(无货币)或统一币种记账。

Q5:ihatemoney db current 没有任何输出、退出码 1? db 子命令默认在当前目录找 migrations/,不在包目录执行即静默失败(实测见 7.4)。先 cd 到 site-packages/ihatemoney 再执行。

Q6:token 是永久的吗?能干什么、不能干什么? token 与项目绑定(payload 即项目 id 数组 + 签名),无过期时间,但 SECRET_KEY 变更后全部失效。能通过 Bearer 调该项目全部 API;用作 Web 链接 /apitest/join/<token> 时授予读写访问但不能改项目设置(官方文档定位)。签发通道(GET /token)本身需要 Basic 认证,泄露面可控。

Q7:升级后 API 建项目突然全部 400? 检查 ALLOW_PUBLIC_PROJECT_CREATION 是否被改成 False——该参数连 REST API 的建项目与改项目一起拦停且无管理口令旁路(4 个实测回显见《IHateMoney 7.2.1 配置参数详解》5.3 节)。需要 API 自动化的实例保持 True。

九、参考链接