平台服务

Odoo IAP 为什么不只是“买点积分”:account token 生命周期、中和库禁用与 credit 边界讲透

很多人把 IAP 理解成 Odoo 的在线充值功能,但 iap 源码真正处理的是 account token 的创建与缓存、NoCredit 回滚时如何保住账号、credit URL 只传哈希、中和数据库自动禁用 token,以及多公司下优先选哪一个 IAP account。本文把这套边界讲清楚。

框架
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

IAP 在 Odoo 里看起来像“在线服务余额”或“买积分”,但从 /home/ubuntu/odoo-temp/addons/iap/models/iap_account.py 看,官方真正维护的是一条服务账号关系链,而不是一个简单余额字段。

这条链至少包括:

  1. 每个 service 对应的 iap.account 如何找到或创建。
  2. token 如何生成、何时不能明文外传。
  3. 如果调用 IAP 服务会触发回滚,账号创建怎样不被一起回滚掉。
  4. 中和数据库为什么要自动把 token 变成 +disabled
  5. 多公司环境里,默认该拿哪一个 account。

所以最短结论是:

Odoo IAP 管的不是“余额”,而是“本库如何以受控身份去消费 Odoo 在线服务”。


iap.account 不是钱包,而是“服务身份”

iap.account 上最关键的字段不是 balance,而是:

  • service_id
  • account_token
  • company_ids
  • state

这说明一条 IAP account 记录的本质不是资金账户,而是:

  • 这个数据库 / 公司,
  • 对某个 IAP service,
  • 拿什么身份去调用。

所以 balance 只是外部服务回传的动态信息,真正稳定存在的是服务身份映射

这和传统“充值中心”思路不同。IAP 在 Odoo 里更像:

一个受服务名驱动的远程能力凭证容器。


为什么 token 默认生成,但又被限制在系统组可见

account_token 默认来自 uuid.uuid4().hex,而且字段权限是:

  • groups="base.group_system"

这两个细节放在一起,很能说明官方的安全思路:

  • token 需要一开始就有,因为很多服务调用都依赖它;
  • 但 token 绝不能被普通用户当作一般字段看到。

因为一旦 token 泄露,本质上就等于:

  • 别人可以冒这套库 / 这家公司去消费对应在线服务;
  • 甚至可能查看、购买、消耗与你业务相关的服务能力。

所以把 token 视作“认证密钥”而不是“配置字段”,这个理解非常重要。


为什么 get() 要用额外 cursor 创建账号

get(service_name) 是整篇源码里最值得看的方法之一。

如果当前数据库里还没有该 service 对应的 account,Odoo 不会简单在当前事务里 create() 完事,而是用:

  • 新 cursor;
  • flush_all()
  • 再在独立上下文中创建 account;
  • account_token 手动塞回当前 env cache。

注释写得很直白:

  • 因为后续 IAP 调用可能抛 NoCreditError
  • 这类错误会回滚当前事务;
  • 如果 account 也是在同一事务里创建的,就会被一起抹掉。

这非常有代表性。

Odoo 在这里防的不是“创建失败”,而是:

第一次接触某个 IAP service 时,不能因为后续业务异常,导致身份记录永远建不起来。

这是一种典型的“把基础设施对象从业务事务里解耦”的写法。


为什么还要清理没有 token 的 account

get() 里还有一段很务实:

  • 先找到所有符合 domain 的 accounts;
  • account_token 为空的记录筛出来;
  • 用 sudo 和独立 cursor 删除它们。

这说明官方承认现实里可能出现“半拉子 IAP account”:

  • 记录建了;
  • token 没建成;
  • 或某次异常留下了残缺状态。

如果不清,后面 get() 很容易拿到一个名义上存在、实际上没法认证的脏 account。

所以这里不是在做“洁癖清理”,而是在保证:

  • 服务账号要么可用,要么不存在;
  • 不要让半残记录卡住后续自动修复。

为什么中和数据库会把 token 改成 +disabled

create() 里有个非常关键的分支:

  • 如果 database.is_neutralized 为真;
  • 新建 account 后就把 token 改成 原 token + "+disabled"

这一步特别有现实价值。

很多团队会把生产库复制到:

  • 测试;
  • 培训;
  • 演示;
  • UAT。

如果复制后的数据库还能沿用生产 IAP token,风险会非常大:

  • 测试环境可能继续消耗真实积分;
  • 演示数据可能误打远程服务;
  • 非生产库行为会污染真实计费和调用轨迹。

Odoo 的做法非常克制:

  • 不粗暴删除 token;
  • 而是用 +disabled 标记让它在逻辑上失效。

_hash_iap_token() 又会先把 + 后缀剥掉再哈希,这说明官方有意识地区分:

  • 底层 token 主体;
  • 本地环境附加的状态后缀。

这是很成熟的环境隔离设计。


为什么买积分 URL 只带哈希 token

get_credits_url() 不会把明文 account_token 拼进购买链接,而是:

  • _hash_iap_token(account_token)
  • 然后参数里带 hashed=1

这一步很重要。

如果购买入口带的是明文 token,那么:

  • 浏览器历史;
  • 代理日志;
  • 引荐头;
  • 截图;
  • 外部排障记录;

都有机会把它泄露出去。

而哈希后的 token 至少把风险降低到了:

  • URL 可识别 account;
  • 但不直接暴露原始认证秘密。

这说明 Odoo 即使在“把用户送去充值页面”这种看上去很普通的动作里,也没有放弃对 credential 暴露面的控制。


多公司环境为什么优先选带 company_ids 的 account

get() 末尾有个不显眼但很重要的选择顺序:

  • 先找符合当前 companies 的记录;
  • 如果有带 company_ids 的 account,优先返回它;
  • 否则才退到 company 为空的全局 account。

这意味着 Odoo 认为 IAP account 既可以:

  • 是全局共享;
  • 也可以公司专属。

而默认策略是:

有更具体的公司级身份,就不要回退到全局身份。

这和权限系统、财务归属、服务配额都密切相关。尤其当不同公司:

  • 消耗不同服务;
  • 单独付费;
  • 需要独立控制 alert recipient;

公司级 account 就比全局 account 更稳。


warning alert 为什么要同步到 IAP 远端

write() 里只要改了:

  • warning_threshold
  • warning_user_ids

Odoo 就会去调用 /iap/1/update-warning-email-alerts

也就是说,这些并不是纯本地 UI 配置,而是远程服务也需要知道的账号告警规则。

这说明 IAP account 并不是“本地缓存一个余额后就算完”,而是:

  • 本地和远端共同维护一套账号元数据。

它让“积分不足提醒发给谁、低于多少提醒”这种设置,真正成为服务账户的一部分,而不是某台数据库里的一段孤立设置。


一句话理解 Odoo IAP 的真正边界

如果要用一句话概括这套设计,我会说:

IAP 不是支付中心,而是 Odoo 为各种在线能力维护的一层服务身份与额度边界。

所以理解 IAP 最该抓住的不是“怎么买积分”,而是下面这些边界:

  • token 是认证秘密,不是普通字段;
  • 新 account 要能在回滚环境里存活;
  • 中和库必须自动失效;
  • 外跳链接尽量不泄露明文 token;
  • 多公司优先具体身份而不是全局身份。

这也是为什么 IAP 虽然表面很轻,但平台味道非常重。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。