第四十七章 常见应用场景

篇别:第十五篇 场景与实践

本章学习目标

  • 在多公司部署中落实 ACL + 记录规则 + 代码路径隔离闭环,并识别 性能热点(规则复杂度、索引、sudo)。
  • 能为 company_id 高频过滤 等查询设计 合理索引,理解 多公司部分索引 的取舍。
  • 说清 库存批次/序列号 在 Odoo 中的 产品配置要点业务流位置(不替代库存用户手册)。
  • 正确使用 context 的传递、合并与危险键active_testsudo、语言、默认值)。
  • 掌握 批量写入ORM 批量 API、分批提交、避免 N+1 等习惯。
  • 能对照本章 扩展场景上线前检查清单

导读:场景章与全书的关系

本章是 跨章节综合:多公司与安全见 第四十五章、第四十六章、第三十章;ORM 与 context第四章;性能见 第十四章;索引与 SQL 见 ORM 文档与 PostgreSQL 实践。实施时以 官方 Multi-company GuidelinesPerformance 为权威补充。


47.1 多公司下数据安全隔离与性能

47.1.1 知识要点

安全隔离(三层缺一不可)

  1. 访问控制(ACL)ir.model.access 决定 能否 CRUD;无权限则 记录规则不执行到直接拒访
  2. 记录规则(ir.rule:用 domain 限制 可见行;多公司典型模式:

    ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]

    错误:漏写 company_id 条件、或 sudo() 后未再约束公司串数据
  3. 业务代码create/write显式或默认 写入 company_id;跨公司报表用 with_company() 而非 随意 sudo

性能要点

  • 规则越多、domain 越复杂search 生成的 WHERE 越长;合并同类规则、避免 重复子查询式 domain(能用单表条件就不用嵌套)。
  • company_ids 在规则中求值 是常规路径;确保 res.users公司关联 无异常膨胀(测试用户挂十几家公司会放大 IN 列表)。
  • 缓存 在循环里 sudo().env['res.company'].browse(...) 做可缓存的读取;公司相关计算 注意 @api.depends 是否包含 company_id
  • 测试:同一用户 切换当前公司同一菜单search_count断言差集符合预期(与 46.1 一致)。

47.1.2 案例(检查清单)

检查项 说明
自定义模型有 company_id 布尔/多公司字段与规则一致
规则 domain 与 ACL 同测 Portal / 内部用户各测
无「全局 sudo + 忘记公司」 审计 grep -R "sudo()" custom_addons/
共享主数据策略明确 产品/伙伴 company_id 为空时的规则文档化

47.1.3 本节练习

  1. 简答:仅依赖 界面切换公司 而不写 记录规则,为何仍可能 不安全
  2. 判断sudo()env.company 仍为用户当前公司,故数据一定隔离。( )

参考答案提示:1. API/RPC、定时任务、服务器动作 可能 不经过同一套 UI 假设。2. 错;sudo 绕过权限,必须 代码或规则层 再约束。


47.2 多公司环境下的数据库索引与查询性能

47.2.1 知识要点

为何多公司更「吃索引」

  • 列表、看板、报表常在 domain 中固定 ('company_id', '=', user.company_id.id)规则隐式过滤company_id 与状态、日期 组合出现频率高。
  • PostgreSQLWHERE company_id = ? AND state = ? 类查询,适合 复合索引 (company_id, state)(company_id, date DESC)(按实际 SQL 为准,用 EXPLAIN ANALYZE 验证)。

实践建议

  • 不要盲目给每个字段加索引:写放大、规划器选择变差;以慢查询日志 / ORM 日志为准
  • 外键与 company_id:业务表 company_id 索引 常与 parent_idpartner_id组合 更有效。
  • 部分索引(partial index):若 绝大多数查询 只关心 state != 'cancel' 等,可与 DBA 评估 WHERE state <> 'cancel' 部分索引(迁移脚本 维护,见 第四十四章)。
  • Odoo 侧index=True字段定义 上声明;复合索引 往往需 init 钩子或 post_init_hook 执行 CREATE INDEX CONCURRENTLY注意升级窗口)。

47.2.2 案例(示意)

sale.order 列表极慢,EXPLAIN 显示 Seq Scan。在确认 domain 主要为 本公司 + 未取消 后,评估:

  • 字段级 index=Truecompany_id;或
  • 复合索引 (company_id, state)(与团队 DBA 评审)。

47.2.3 截图占位

图 47-1 pgAdmin 或 psql 中对业务表 EXPLAIN ANALYZE 的示意

47.2.4 本节练习

  1. 简答单字段索引 company_id复合 (company_id, state) 各适合什么 WHERE 形态?
  2. 实操:在 测试库 对一条慢 search_read 打开 SQL 日志,记录 是否用到索引

参考答案提示:1. 仅按公司过滤 → 单字段可能够;同时按状态/日期排序过滤 → 复合更常见。


47.3 产品批次管理(简述)

47.3.1 知识要点

Odoo 库存应用 中,追溯 依赖 跟踪方式

  • 按批次(Lots):适合 食品、化工同 SKU 多批次、需 保质期/召回
  • 按序列号(Serial Numbers)一物一码(设备、高价值单品)。
  • 不跟踪不启用 lot/serial,库存只到 数量 + 库位

配置链(概念)

  1. 产品类别或产品 上设置 追踪none / lot / serial(具体字段名以版本为准,见 库存模块)。
  2. 入库/出库/调拨 行上指定 批次或序列号制造委外 可在 完工生成或消耗 批次。
  3. 报表库存估值、追溯从单据反查到批次,再反查 供应商批次(若维护)。

开发扩展时注意

  • 业务代码常通过 stock.move.linelot_id / lot_name 交互;不要 绕过库存移动 直接改 quant(破坏一致性)。
  • 多公司:批次主数据 是否共享产品与公司策略;与 47.1 规则一致。
  • 性能大批量扫码 场景关注 移动行数量条码模块;用户手册见 Inventory 用户文档

47.3.2 本节练习

  1. 简答序列号批次数据结构 上的典型差异是什么(粒度)?
  2. 判断:启用批次后,销售订单 可不指定批次直接发货。( )

参考答案提示:1. 序列号 通常 一条移动行对应唯一 SN批次 多数量共享同一 lot。2. 依 流程配置;常 在拣货/发货环节 指定,不一定在订单行——以你们实施为准。


47.4 context 在 Odoo 中的使用方式

47.4.1 知识要点

context 是什么

  • self.env.context:当前环境的 字典,影响 ORM 行为、默认值、部分方法分支、翻译语言 等。
  • 不可变假设:应使用 with_context(...) 生成 新环境,避免 原地修改 引发 诡异副作用

常见键(示例)

作用 注意
lang 翻译与 用户可见字符串 RPC 默认常带用户语言
tz 时区相关 日期边界 报表与日切
allowed_company_ids 多公司 可见范围 当前公司 配合
active_id / active_ids 向导、按钮 默认关联 服务端 勿盲信,需 校验权限
active_test active=False 记录是否参与 search 默认 True;归档排查时改为 False 要谨慎
tracking_disable / mail_create_nosubscribe mail 行为 批量导入时常用,见官方 mail 文档
prefetch_fields / bin_size 读取优化 按版本文档使用

危险习惯

  • sudo 不是 context 键,是 env 替换with_user / with_company 同理。
  • context 里塞「权限」不可靠权限必须是 ACL + 规则
  • 前端 context 与后端合并action、view、field 可传 context服务端敏感操作 必须 再次校验

47.4.2 案例

# 推荐:链式合并,不修改原 env
self.env['my.model'].with_context(my_flag=True).do_something()

# 仅本次调用需要英文标签(示例)
labels = self.with_context(lang='en_US').mapped('name')

47.4.3 本节练习

  1. 简答:为何 active_test=False慎用
  2. 实操:在 某 onchange 中打印 self.env.context.keys(),区分 来自 action 与来自默认值 的键。

参考答案提示:1. 易暴露已归档/历史数据,且 若配合 sudo 风险更大。


47.5 高性能批量写入数据

47.5.1 知识要点

ORM 层

  • create([{...}, {...}]):单次调用 多行 优于 循环 create
  • write 合并同一值 更新 多条记录一次 recordset.write({...})
  • create_multi:在 自定义模型 上若 有复杂默认值,可重写 create 仍调 super实现官方推荐的批量入口(以 ORM 文档 为准)。
  • 跳过重计算/消息:批量导入时 context关闭 mail tracking、自动订阅 等(务必在事务结束后 恢复一致的业务通知策略)。

分批与事务

  • 上万行:按 批次 commitShell/脚本 中)或 cr.commit()慎用,破坏 请求内原子性优先用队列 job)。
  • Odoo 多 worker:长事务 锁行批量导入专用 cron / queue_job 更稳。

SQL 直连(最后手段)

  • env.cr.execute + INSERT ... SELECT绕过 ORM,需 自己处理 约束、翻译、bus、mail升级脚本业务代码 更合适。
  • 官方 Performance — Good practices批量 ORM、减少循环中的 search

47.5.2 反模式清单

反模式 替代
for r in recs: r.write({...}) recs.write({...})
循环内 search 同条件 一次 search字典映射
每条 message_post 批量后 一条汇总 或 关闭 tracking
巨型单事务 create 十万行 分批 + 监控锁

47.5.3 本节练习

  1. 实操:将 1000 条 测试数据 分两路 创建:循环 create vs 单次 create(list),对比 日志 SQL 条数或耗时
  2. 简答:为何 业务接口随意 cr.commit() 往往 不推荐

参考答案提示:2. 破坏请求事务边界与 HTTP 重试/并发 难协调,调试困难


47.6 扩展场景一:大列表、导出与「不要一次读爆内存」

要点search_read 指定 fields导出 十万级用 分批 search + 流式标准导出限制read_group 做汇总而非 拉全表到 Python

关联第十四章 性能、报表 章节。


47.7 扩展场景二:Webhook / 外部回调的幂等与并发

要点HTTP 控制器 快速 200重活丢队列;用 外部唯一键(如电商订单号)做 sql_constraint 或唯一索引 防重复建单。

关联第三十六章第三十八章


47.8 扩展场景三:附件与二进制字段

要点:大文件走 filestore不要在 fields.Binary 上建业务索引;列表 勿默认加载 大 binary,用 bin_size 等策略。

关联存储与备份(安装与运维章)。


47.9 扩展场景四:定时任务(Cron)与长任务拆分

要点:单次 cron 处理有限条数游标或状态字段 记录进度;避免 与业务高峰抢锁ir.cron 超时worker 配置 协调。

关联第九章 Actions、第四十三章


47.10 扩展场景五:Portal 仅看「自己的」数据

要点:Portal 用户使用 专用组 + 记录规则partner_id = user.partner_id 等);控制器 request.env.user 勿混用 内部用户 逻辑。

关联第三十章第三十六章


本章综合练习

  1. 画出 多公司请求HTTP → env.company → ir.rule → SQL简图5 个框以内)。
  2. 一批量导入接口3 条 性能与安全 约束(context、分批、权限 各至少一点)。
  3. 结合 47.3,用 一句话仓库主管 说明 批次序列号 选型差异。

本章场景可与 官方文档对照与延伸阅读 中 Multi-company、Performance、Controllers 等条目交叉阅读。内置 AI 模块 的工程细节见 第四十八章本机三系统装 Odoo第四十九章