第一章:Odoo多租户隔离失效风险全景剖析
Odoo原生支持多租户(Multi-tenancy)架构,但其隔离能力高度依赖配置策略与扩展实践。当数据库级、模型级或会话级隔离机制被绕过或误配时,租户间数据泄露、权限越界与业务逻辑污染等高危风险将直接暴露。
核心隔离层与常见失效点
Odoo的租户隔离并非默认“开箱即用”的强隔离,而是分层构建:
- 数据库级:每个租户独占数据库——最安全,但运维成本高;
- schema级(通过
--db-filter+共享数据库):依赖严格dbfilter正则匹配,若配置为.*或未启用--db-filter参数,所有租户可枚举并访问任意数据库; - 记录规则(Record Rules):需为每张敏感表(如
res.partner,account.move)显式定义['|', ('company_id', '=', company_id), ('company_id', '=', False)]类规则,缺失即导致跨公司数据可见; - 环境上下文(env.context)滥用:自定义方法中若硬编码
env.cr.execute("SELECT * FROM res_partner")跳过ORM安全检查,将彻底绕过租户过滤。
典型配置漏洞验证步骤
执行以下命令快速检测dbfilter是否生效:
# 启动Odoo服务时检查实际生效的dbfilter
odoo-bin --db-filter='^%d$' --log-level=debug 2>&1 | grep "dbfilter"
# 若输出为空或显示 "dbfilter: .*",说明隔离策略未受控
高风险开发模式示例
以下代码片段在共享数据库部署中将导致租户隔离崩溃:
# ❌ 危险:直连游标绕过ORM租户上下文
self.env.cr.execute("SELECT id, name FROM res_partner WHERE active = true")
# ✅ 正确:始终通过ORM接口,自动注入company_id/allowed_company_ids过滤
partners = self.env['res.partner'].search([('active', '=', True)])
| 风险类型 | 触发条件 | 检测方式 |
|---|---|---|
| 数据库枚举 | --db-filter未启用或正则过宽 |
访问/web/database/manager |
| 跨公司记录可见 | 缺失res.company相关记录规则 |
以租户A用户查询租户B的发票 |
| 会话上下文污染 | env.su=True + 未重置company_id |
在sudo操作后未显式指定公司域 |
租户隔离失效不是理论威胁,而是已在多个生产环境中引发客户数据混同事故的现实隐患。
第二章:Golang中间件架构设计与核心路由机制
2.1 多租户上下文建模:从HTTP请求到TenantID提取的全链路实践
多租户上下文需在请求入口处完成无感注入,避免业务代码侵入。
核心提取策略对比
| 方式 | 位置 | 安全性 | 灵活性 | 示例 |
|---|---|---|---|---|
请求头(X-Tenant-ID) |
HTTP Header | 高(可校验签名) | 高 | X-Tenant-ID: acme-corp |
| 子域名 | Host | 中(依赖DNS配置) | 低 | acme.example.com |
| 路径前缀 | URI Path | 低(易被篡改) | 中 | /t/acme-corp/api/users |
典型Spring Boot拦截器实现
@Component
public class TenantContextFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String tenantId = resolveTenantId(request); // 见下文逻辑分析
TenantContextHolder.set(tenantId); // 绑定至ThreadLocal
try {
chain.doFilter(req, res);
} finally {
TenantContextHolder.reset(); // 防止线程复用污染
}
}
private String resolveTenantId(HttpServletRequest req) {
return Optional.ofNullable(req.getHeader("X-Tenant-ID"))
.filter(StringUtils::isNotBlank)
.orElseThrow(() -> new TenantResolutionException("Missing X-Tenant-ID"));
}
}
逻辑分析:
resolveTenantId()优先从安全可信的X-Tenant-ID头中提取,拒绝空值/空白值;TenantContextHolder.set()使用InheritableThreadLocal支持异步线程继承;finally块确保上下文重置,规避连接池线程复用导致的租户泄漏。
上下文传播流程
graph TD
A[HTTP Request] --> B{Header contains X-Tenant-ID?}
B -->|Yes| C[Parse & Validate TenantID]
B -->|No| D[Reject with 400 Bad Request]
C --> E[Store in TenantContextHolder]
E --> F[DAO Layer自动路由数据源]
2.2 Schema级动态路由引擎:基于PostgreSQL search_path的实时切换原理与实现
PostgreSQL 的 search_path 是实现多租户 Schema 隔离的核心机制,支持运行时毫秒级切换默认命名空间。
核心原理
search_path 决定对象(如表、函数)解析顺序,形如 'tenant_abc, public'。会话级设置即刻生效,无需重启连接。
动态路由实现
-- 在应用层按租户ID动态设置
SET search_path TO 'tenant_123', 'public';
-- 或使用参数化方式(避免SQL注入)
SELECT set_config('search_path', format('tenant_%s, public', $1), false);
set_config(..., false)表示仅对当前会话生效;$1为经校验的租户标识,确保 schema 名合规(仅含字母数字下划线)。
关键约束对照
| 约束类型 | 是否强制 | 说明 |
|---|---|---|
| Schema存在性 | 是 | tenant_123 必须已创建 |
| 用户权限 | 是 | 当前角色需有USAGE权限 |
| search_path长度 | 否 | 最长1024字符,建议≤5项 |
graph TD
A[HTTP请求含X-Tenant-ID] --> B{租户校验}
B -->|有效| C[生成search_path值]
B -->|无效| D[返回400]
C --> E[执行SET search_path]
E --> F[后续所有SQL自动路由]
2.3 中间件生命周期管理:Gin/Echo框架中租户感知中间件的注册与优先级控制
租户感知中间件需在请求进入路由前完成上下文注入,并确保执行顺序严格可控。
注册时机与作用域隔离
- Gin:通过
engine.Use()全局注册,或group.Use()按路径分组; - Echo:使用
e.Use()(全局)或group.Use()(子路由),支持链式注册。
优先级控制机制
| 框架 | 控制方式 | 租户ID注入时机 |
|---|---|---|
| Gin | 中间件函数入参顺序 | c.Set("tenant_id", tid) |
| Echo | echo.MiddlewareFunc 返回顺序 |
c.Set("tenant_id", tid) |
// Gin 中租户解析中间件(带动态优先级)
func TenantMiddleware(tenantResolver func(c *gin.Context) string) gin.HandlerFunc {
return func(c *gin.Context) {
tid := tenantResolver(c) // 从 header/x-tenant-id 或 subdomain 解析
c.Set("tenant_id", tid)
c.Next() // 确保后续中间件可访问 tenant_id
}
}
该中间件必须在认证、日志等依赖租户上下文的中间件之前注册。tenantResolver 函数解耦租户识别逻辑,支持多源适配(如 JWT claim、Host 头、路径前缀)。c.Next() 显式控制调用链延续,是 Gin 生命周期调度的核心契约。
2.4 连接池租户绑定:pgx连接池按Schema分片与连接上下文透传实战
在多租户SaaS系统中,基于PostgreSQL Schema隔离是常见策略。pgx连接池本身不支持租户上下文感知,需手动实现“连接→租户→schema”的绑定闭环。
Schema分片连接池构建
// 按租户ID初始化独立连接池(避免交叉污染)
pools := make(map[string]*pgxpool.Pool)
for _, tenant := range tenants {
cfg, _ := pgxpool.ParseConfig("postgres://...")
cfg.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
_, err := conn.Exec(ctx, fmt.Sprintf("SET search_path TO %s", tenant.Schema))
return err // 确保每次获取连接时自动切换schema
}
pools[tenant.ID] = pgxpool.NewWithConfig(ctx, cfg)
}
逻辑分析:AfterConnect钩子在连接首次建立/复用前执行SET search_path,将连接上下文锁定至租户专属schema;参数tenant.Schema需严格校验合法性(防SQL注入),且不可动态变更。
上下文透传关键约束
- 租户标识必须在HTTP请求头(如
X-Tenant-ID)或gRPC metadata中携带 - 连接获取必须通过租户ID路由到对应池,禁止跨池复用
- 所有SQL语句不得硬编码schema前缀,依赖search_path生效
| 方案 | 隔离性 | 连接复用率 | 实现复杂度 |
|---|---|---|---|
| 每租户单池 | ★★★★★ | 中 | 低 |
| 连接级SET | ★★★★☆ | 高 | 中 |
| SQL重写 | ★★☆☆☆ | 高 | 高 |
2.5 性能压测对比:原生Odoo多数据库模式 vs Golang Schema路由模式QPS/延迟分析
为验证架构演进实效,我们在同等硬件(16C32G,SSD RAID0)下对两种模式执行 5 分钟恒定并发压测(wrk -t4 -c200 -d300s)。
压测结果概览
| 指标 | Odoo 多库模式 | Golang Schema 路由 |
|---|---|---|
| 平均 QPS | 187 | 943 |
| P95 延迟 | 1240 ms | 186 ms |
| 连接池峰值 | 212 | 47 |
核心差异解析
Golang 层通过 schema-aware 连接复用避免了 Odoo 每请求重建 DB 连接与 ORM 初始化开销:
// schema_router.go:基于租户标识动态绑定 PostgreSQL 连接池
func (r *Router) GetConn(tenantID string) (*sql.DB, error) {
schema := tenantToSchema[tenantID] // 映射如 "acme" → "tenant_acme"
return r.pools[schema].Get(), nil // 复用预热连接池,非新建
}
逻辑说明:
tenantToSchema为内存映射表,r.pools是按 schema 预初始化的独立*sql.DB实例池。参数schema决定物理隔离度,Get()触发连接复用而非sql.Open(),规避了 Odoo 中pg_connect + init_env + load_models的链式耗时。
数据同步机制
- Odoo 模式:各库完全独立,跨租户数据需 ETL 或外部消息队列
- Go 路由模式:共享元数据服务 + schema 级 CDC(Debezium + Kafka),变更实时广播
graph TD
A[HTTP Request] --> B{Tenant ID}
B --> C[Schema Router]
C --> D[tenant_acme pool]
C --> E[tenant_biz pool]
D --> F[PostgreSQL]
E --> F
第三章:RBAC穿透校验的深度集成方案
3.1 Odoo权限模型逆向解析:ir.model.access、res.groups与ACL规则的Golang映射策略
Odoo 的权限体系由三重机制协同构成:ir.model.access(模型级粗粒度控制)、res.groups(用户组抽象)与 ACL(记录级细粒度规则)。在 Golang 服务对接 Odoo 时,需将其映射为类型安全、可组合的权限结构。
核心映射结构
type OdooAccessRule struct {
ModelName string `json:"model"` // 如 "sale.order"
GroupIDs []int64 `json:"group_ids"` // 对应 res.groups.id
PermRead bool `json:"perm_read"`
PermWrite bool `json:"perm_write"`
PermCreate bool `json:"perm_create"`
PermUnlink bool `json:"perm_unlink"`
}
该结构直接对应 ir.model.access 数据表字段,GroupIDs 关联用户所属组 ID,用于运行时权限裁决。
权限决策流程
graph TD
A[请求: sale.order/123] --> B{查用户所属 res.groups}
B --> C[匹配所有 ir.model.access 规则]
C --> D[叠加 ACL WHERE 条件]
D --> E[返回布尔结果]
映射关键约束
res.groups必须预加载并缓存,避免每次鉴权查库- ACL 规则需编译为 Go 表达式树(如
user.id == record.user_id || user.has_group('sales_team'))
3.2 请求级RBAC校验中间件:基于Odoo服务端元数据API的实时权限决策树构建
该中间件在HTTP请求进入业务逻辑前拦截,动态调用 ir.model.access 和 res.groups 元数据接口,构建以模型-操作-组为节点的三元决策树。
核心校验流程
def check_request_access(env, model_name, operation, uid):
# operation ∈ {'read','write','create','unlink'}
groups = env['res.users'].browse(uid).groups_id.ids
access_rule = env['ir.model.access'].search([
('model_id.model', '=', model_name),
('group_id', 'in', groups),
('perm_' + operation, '=', True)
], limit=1)
return bool(access_rule)
→ 调用轻量级元数据查询,避免加载完整ACL规则表;perm_* 字段映射Odoo原生权限位,limit=1保障O(1)最坏时间复杂度。
决策树结构示意
| 节点类型 | 字段来源 | 动态性 |
|---|---|---|
| 模型节点 | ir.model.name |
静态 |
| 操作边 | perm_read等布尔 |
运行时计算 |
| 组节点 | res.groups.id |
用户会话绑定 |
graph TD
A[HTTP Request] --> B{Model: sale.order<br>Op: write}
B --> C[Fetch user groups]
C --> D[Query ir.model.access]
D --> E[Allow/Deny]
3.3 权限缓存一致性保障:Redis分布式缓存+PostgreSQL LISTEN/NOTIFY事件驱动刷新机制
数据同步机制
传统轮询拉取权限变更易导致延迟与资源浪费。本方案采用 PostgreSQL 的 LISTEN/NOTIFY 实时事件推送,结合 Redis 缓存主动失效,实现毫秒级最终一致性。
核心实现流程
-- 在 PostgreSQL 中创建权限变更通知触发器
CREATE OR REPLACE FUNCTION notify_permission_change()
RETURNS TRIGGER AS $$
BEGIN
PERFORM pg_notify('permission_update',
json_build_object(
'resource_id', NEW.resource_id,
'role_id', NEW.role_id,
'action', TG_OP
)::text
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 绑定到权限表(如 role_permissions)
CREATE TRIGGER trigger_permission_change
AFTER INSERT OR UPDATE OR DELETE ON role_permissions
FOR EACH ROW EXECUTE FUNCTION notify_permission_change();
逻辑分析:触发器在权限变更时构造 JSON 消息并发布至
permission_update通道;TG_OP区分INSERT/UPDATE/DELETE类型,便于下游精准处理。pg_notify零延迟、轻量,且不阻塞事务。
应用层监听与刷新
# Python(使用 asyncpg + redis-py)
async def listen_and_invalidate():
conn = await asyncpg.connect(DSN)
await conn.execute("LISTEN permission_update")
async for notify in conn.notifies():
payload = json.loads(notify.payload)
key_pattern = f"perm:{payload['resource_id']}:{payload['role_id']}"
await redis.delete(key_pattern) # 精准失效,非全量清空
优势对比
| 方案 | 延迟 | 一致性模型 | 数据库负载 |
|---|---|---|---|
| 定时轮询 | 秒级~分钟级 | 弱一致 | 高(频繁 SELECT) |
| 双写直写 | 毫秒级 | 强一致(但易失败) | 中(额外写开销) |
| LISTEN/NOTIFY + Redis | 最终一致(可靠投递) | 极低(仅通知) |
graph TD
A[PostgreSQL] -->|NOTIFY permission_update| B{Event Bus}
B --> C[Worker-1: Redis DEL]
B --> D[Worker-2: Redis DEL]
C --> E[Client 读缓存 → 自动重建]
D --> E
第四章:生产级安全加固与可观测性落地
4.1 租户越界访问防御:SQL注入防护、schema前缀白名单与动态查询重写拦截器
多租户系统中,租户ID若被恶意拼入SQL语句,将导致跨租户数据泄露。防御需三层协同:
SQL注入防护(预编译优先)
// 使用PreparedStatement绑定参数,杜绝字符串拼接
String sql = "SELECT * FROM orders WHERE tenant_id = ? AND status = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, currentTenantId); // 安全注入点
ps.setString(2, "PAID");
✅ 强制参数化:? 占位符交由JDBC驱动转义;❌ 禁止 + tenantId 字符串拼接。
schema前缀白名单校验
| 允许schema | 说明 | 是否启用租户隔离 |
|---|---|---|
t_001 |
金融租户 | ✅ |
t_007 |
医疗租户 | ✅ |
public |
共享元数据 | ❌(禁止用于业务表) |
动态查询重写拦截器(核心防线)
graph TD
A[原始SQL] --> B{含FROM/JOIN子句?}
B -->|是| C[提取表名]
C --> D[检查schema前缀是否在白名单]
D -->|否| E[抛出TenantAccessViolationException]
D -->|是| F[重写为 t_001.orders]
三者叠加,实现从语法层到语义层的纵深防御。
4.2 全链路租户追踪:OpenTelemetry在Golang中间件与Odoo日志中的TraceID透传实践
为实现跨技术栈的租户级可观测性,需在 Golang HTTP 中间件与 Odoo(Python)服务间透传 trace_id 和 tenant_id。
数据同步机制
采用 W3C Trace Context 标准,在请求头中注入:
traceparent: 标准格式00-<trace-id>-<span-id>-01x-tenant-id: 自定义租户标识头
// Golang 中间件注入 trace & tenant context
func TraceTenantMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// 从 header 提取或生成新 trace
spanCtx, _ := otelpropagators.TraceContext{}.Extract(ctx, propagation.HeaderCarrier(r.Header))
tracer := otel.Tracer("golang-middleware")
_, span := tracer.Start(
trace.ContextWithRemoteSpanContext(ctx, spanCtx),
"http-request",
trace.WithAttributes(attribute.String("tenant.id", r.Header.Get("x-tenant-id"))),
)
defer span.End()
// 注入 tenant_id 到上下文供后续使用
ctx = context.WithValue(ctx, "tenant_id", r.Header.Get("x-tenant-id"))
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
逻辑分析:该中间件利用 OpenTelemetry SDK 提取上游
traceparent,创建带租户属性的新 Span;x-tenant-id被显式注入 Span Attributes 并存入context,确保下游业务逻辑可安全读取。trace.WithAttributes保证tenant.id出现在所有导出的 trace 数据中,为多维下钻分析提供关键维度。
Odoo 日志增强策略
Odoo 通过自定义 logging filter 注入 trace_id 与 tenant_id:
| 字段 | 来源 | 示例值 |
|---|---|---|
trace_id |
traceparent 解析 |
4bf92f3577b34da6a3ce929d0e0e4736 |
tenant_id |
x-tenant-id Header |
acme-corp |
service.name |
静态配置 | odoo-web |
# odoo/addons/otel_tracing/models/otel_logger.py
class TenantTraceFilter(logging.Filter):
def filter(self, record):
ctx = get_current_span().get_span_context()
record.trace_id = format_trace_id(ctx.trace_id) if ctx else "-"
record.tenant_id = request.httprequest.headers.get('x-tenant-id', 'unknown')
return True
参数说明:
format_trace_id()将 OpenTelemetry 的 128-bit trace ID(uint128)转为 32 字符十六进制字符串;request.httprequest是 Odoo WSGI 请求对象,确保 header 可达;该 filter 绑定至 root logger 后,所有日志行自动携带租户与追踪上下文。
跨语言链路串联示意
graph TD
A[Frontend] -->|traceparent, x-tenant-id| B[Golang API Gateway]
B -->|propagated headers| C[Odoo WSGI]
C --> D[(Jaeger/Tempo)]
D --> E{TraceID + tenant.id}
E --> F[按 tenant.id 聚合慢查询]
4.3 审计日志增强:租户ID、Schema名、操作模型、字段级变更的结构化审计事件生成
为支撑多租户数据治理与合规追溯,审计日志需从“粗粒度操作记录”升级为“细粒度语义事件”。
字段级变更捕获机制
通过数据库CDC(如Debezium)或ORM拦截器提取before/after字段快照,结合JSON Patch规范生成差异描述:
{
"tenant_id": "t-789",
"schema": "finance_v2",
"operation": "UPDATE",
"table": "accounts",
"primary_key": {"id": 42},
"changes": [
{"field": "balance", "old": 1500.0, "new": 1420.5, "type": "DECIMAL"},
{"field": "status", "old": "active", "new": "frozen", "type": "ENUM"}
]
}
逻辑分析:
tenant_id确保跨租户隔离;schema标识逻辑数据域;changes数组按字段粒度结构化记录变更,支持下游做精准影响分析与回滚决策。
审计事件元数据模型
| 字段 | 类型 | 说明 |
|---|---|---|
event_id |
UUID | 全局唯一事件标识 |
timestamp |
ISO8601 | 精确到毫秒的操作时间 |
actor |
string | 执行用户或服务主体 |
数据流拓扑
graph TD
A[DB Binlog / ORM Hook] --> B[Change Extractor]
B --> C{Field-level Diff Engine}
C --> D[Structured Audit Event]
D --> E[Async Kafka Topic]
4.4 故障熔断与降级:租户级限流(基于令牌桶)、Schema不可用时的优雅降级响应策略
租户级令牌桶限流实现
每个租户独享令牌桶,避免跨租户干扰:
RateLimiter tenantLimiter = RateLimiter.create(100.0, 1, TimeUnit.SECONDS); // 每秒100个令牌
if (!tenantLimiter.tryAcquire()) {
throw new TenantRateLimitException("Tenant " + tenantId + " exceeded QPS");
}
create(100.0, 1, SECONDS) 表示稳态速率100 QPS;tryAcquire() 非阻塞校验,保障低延迟。
Schema不可用时的降级策略
当元数据服务异常,自动切换至缓存Schema并返回轻量响应:
| 降级层级 | 响应内容 | 可用性保障 |
|---|---|---|
| L1(缓存) | 本地LRU缓存的Schema副本 | 99.95%可用 |
| L2(兜底) | 预置通用schema(如{id: string}) |
100%可达 |
熔断-降级协同流程
graph TD
A[请求到达] --> B{Schema服务健康?}
B -- 是 --> C[正常解析执行]
B -- 否 --> D[启用缓存Schema]
D --> E{缓存命中?}
E -- 是 --> F[构造轻量JSON响应]
E -- 否 --> G[返回预置兜底结构]
第五章:架构演进路径与未来挑战
从单体到服务网格的渐进式重构
某头部电商平台在2018年启动架构升级,初始单体Java应用承载全部业务,部署耗时超45分钟,故障平均恢复时间(MTTR)达112分钟。团队采用“绞杀者模式”逐步剥离订单、库存、支付模块,2020年完成12个核心微服务拆分,并引入Istio 1.6构建服务网格。关键落地动作包括:在Kubernetes集群中为每个服务注入Envoy Sidecar;通过VirtualService实现灰度流量切分(3%→15%→100%三阶段);利用Prometheus+Grafana建立服务间SLA看板(P99延迟
多云异构环境下的数据一致性挑战
金融级风控系统需同时对接阿里云ACK、AWS EKS及本地OpenShift集群,跨云事务成为瓶颈。团队放弃强一致性方案,采用Saga模式实现分布式事务:以“授信申请-额度冻结-征信查询-结果回写”为四步链路,每步均配置补偿接口(如额度解冻API)。实际运行中发现AWS区域DNS解析超时导致补偿失败,最终通过在Envoy Filter层嵌入自定义重试策略(指数退避+最大3次)解决。下表为不同云厂商网络延迟实测对比(单位:ms):
| 云厂商 | 跨AZ延迟 | 跨Region延迟 | DNS解析P95 |
|---|---|---|---|
| 阿里云华东1 | 0.8 | 32.4 | 18.7 |
| AWS us-east-1 | 1.2 | 41.9 | 42.3 |
| 自建OpenShift | 0.5 | 28.6 | 12.1 |
边缘计算场景的架构适配
智能物流调度平台需在2000+边缘节点(NVIDIA Jetson设备)运行轻量推理服务。传统K8s Operator无法满足资源约束(内存
- 使用CRD定义
EdgeModel资源,声明模型版本、输入尺寸、GPU显存需求 - 开发轻量Agent替代kubelet,支持离线模型热加载(md5校验+增量更新)
- 在K3s主控节点部署模型编排器,根据节点负载自动触发模型降级(FP32→INT8)
# EdgeModel示例定义
apiVersion: edge.ai/v1
kind: EdgeModel
metadata:
name: route-optimizer-v3
spec:
modelUrl: "https://oss-cn-hangzhou.aliyuncs.com/models/route-opt-v3.tflite"
memoryLimitMB: 850
inputShape: [1, 512, 512, 3]
quantization: int8
AI原生架构的工程化落地
新一代客服对话系统将大语言模型能力深度融入架构:
- 模型服务层采用vLLM框架,通过PagedAttention优化显存,单A10卡并发处理128路会话
- 构建RAG流水线:用户问题经Embedding模型向量化后,通过FAISS索引实时检索知识库(日均更新2000+文档)
- 设计缓存穿透防护:对高频模糊查询(如“忘记密码”)预生成响应模板,命中率提升至73%
graph LR
A[用户请求] --> B{意图识别}
B -->|FAQ类| C[知识库检索]
B -->|复杂咨询| D[LLM生成]
C --> E[模板填充]
D --> F[安全过滤]
E --> G[响应返回]
F --> G
安全合规驱动的架构改造
因GDPR审计要求,用户数据必须实现物理隔离。团队在现有微服务架构上叠加数据主权层:
- 在API网关增加GeoIP路由规则,欧盟用户请求强制转发至法兰克福集群
- 所有数据库连接串动态注入region标签,MySQL Proxy拦截非授权跨区访问
- 敏感字段(身份证号、银行卡)在应用层使用AES-GCM加密,密钥轮换周期设为7天
该架构已支撑欧盟区230万用户连续18个月零数据违规事件。
