Skip to content

20:横切能力:租户、数据权限、远程服务、流程和数据库总表

1. 什么是横切能力

它们不属于“客户”或“合同”单一领域,却会影响所有业务:

text
租户隔离
当前用户和部门
数据权限
操作日志
附件服务
BPM 流程
远程成员、字典、企业工商信息

在正式项目中,业务代码能否正确运行,经常取决于这些上下文是否存在。

2. 租户和用户上下文

常见调用:

java
TenantContextHolder.getTenantId();
UserContext.getOpenId();
saasDeptMemberServer.getPrimaryDeptId(openId);

用途:

text
tenantId                 数据属于哪个租户
openId                   当前操作用户
creatorDepartmentId      创建人主部门
followerOpenId           产品跟进负责人
followerDepartmentId     产品跟进部门

定时任务或异步线程必须显式设置并最终清理租户上下文。PublicPoolJob 的多租户循环就是示例。

3. 数据权限

客户分页和项目评估/合同分页会调用数据权限组件:

text
DataPermissionServer.applyDataPermissionFilter(resourceCode, QueryModel, userFields, departmentFields)

它不是在 Java List 过滤数据,而是向 QueryModel 注入条件,让 Provider/Mapper 的 SQL 在数据库层过滤。

典型效果:

text
普通商务:只能看自己跟进的客户产品
部门负责人:可以看部门成员数据
管理员:可以看全部租户数据

因此排查“接口有数据但某用户看不到”时,先检查数据权限、租户、逻辑删除,而不是立刻怀疑 SQL。

4. 远程服务组件

当前 Service 中会通过 component 或 Feign Client 调用:

外部能力典型用途
认证/成员中心用户姓名、部门、领导关系
租户中心当前租户组织与成员归属
流程中心发起审批、接收状态回调
系统/字典中心字典标签、配置
集成中心企业工商信息查询
文件/OSS 中心上传文件预览与存储

写代码时必须处理远程返回为空、调用失败和重复调用。不能默认“远程服务永远成功”。

5. BPM 流程的统一模式

项目评估、合同、试单的流程形态相同:

text
业务主表保存
  -> flowStatus = PENDING
  -> SaasBpmModelServer.startFlow(form)
  -> 回写 flowId、IN_PROGRESS、flowStartTime
  -> 流程回调 updateFlowProgress(flowId, status)
  -> 更新业务状态并应用领域联动规则

需要注意:外部 BPM 与本地数据库不是一个普通本地事务。若本地保存成功但发起流程失败,要有明确的异常与补偿策略;若流程回调重复到达,更新逻辑要幂等。

6. 数据库总表清单

以下表来自 pginit.sql,按领域分组,覆盖当前仓库的核心业务数据:

text
客户主资料
customer_info
customer_region
customer_business_info
customer_contact
customer_invoice
customer_bank_info
customer_industry
customer_info_change_log

品牌、产品与基础资料
customer_brand
customer_brand_relation
brand_owner_relation
product
industry_dict
region
position
position_change_log
settlement_rule

产品跟进与公海
customer_product_follow
customer_product_follow_contact
customer_product_follow_contact_relation
customer_follow_record
customer_follow_record_comment
customer_follow_deadline_log
customer_product_claim_log
customer_transfer_log
customer_product_public_pool
customer_protect_configuration
customer_protect_special_config

项目评估
project_evaluation
project_evaluation_insurance_instruction
project_evaluation_risk_content
project_evaluation_service_requirement
project_evaluation_settlement_rule
project_evaluation_personnel_type
project_evaluation_advance_payment_purpose

合同
contract
contract_contact
contract_customer_relation
contract_settlement_rule
contract_advance_payment_purpose
contract_business_model
contract_number_sequence

试单与通用能力
trial_order_application
trial_order_product
sys_file
import_record

表之间的外键关系很多是在业务代码中按 id 维护,未必全由数据库物理外键强制。因此 Service 的存在非常关键:它负责校验关联记录是否存在,并保证主子表事务一致。

7. 进入正式环境前必须问团队的问题

text
1. 当前环境使用 MySQL 还是 PostgreSQL?对应 DDL 版本是哪份?
2. 数据库迁移脚本如何执行、谁负责审核?
3. Maven 私服和配置中心如何认证?
4. 本地/测试环境如何设置 tenantId 与当前用户?
5. 哪些 resourceCode 对应哪些数据权限?
6. BPM 回调由哪个服务转发,如何测试?
7. 附件上传、预览和 OSS 的测试地址是什么?
8. 公海定时任务在测试环境是否允许手工触发?

这些问题没有答案时,不要为了“让项目跑起来”而伪造正式租户和权限逻辑。

8. Demo 到正式项目的映射

Demo 做法正式 CRM 对应
X-Demo-User-Id 请求头UserContext.getOpenId()
单数据库 tenant_id 字段TenantContextHolder + 租户插件
简单 ownerId 过滤DataPermissionServer 注入 QueryModel
内部 /approve 接口SaasBpmModelServer + 流程回调
demo_filesys_file + OSS/文件服务
本地定时器PublicPoolJob + 分布式锁 + 多租户循环

9. 最终学习检查

当你能把下面的问题答清楚,就已经具备阅读和实现当前 CRM 小需求的能力:

  • 一个客户为什么不能在有产品跟进时删除?
  • 为什么产品跟进到期不一定会进入公海?
  • 为什么项目评估/试单/合同都要先校验产品跟进状态?
  • 为什么合同审批通过会更新客户成交日期?
  • 为什么列表查询先分页主表再批量查关联表?
  • 为什么 Form、DTO、Entity、VO 要拆开?
  • 为什么定时任务必须清理租户上下文?
  • 为什么流程回调和远程调用要考虑幂等和失败补偿?

Lucking