feat: chat integration, tenant admin spec, backend chat service, miniprogram updates, DEMO moved to tmp, XCX-TEST removed, migrations & docs
This commit is contained in:
@@ -1,20 +1,24 @@
|
||||
# AI_CHANGELOG
|
||||
# - 2026-03-20 | Prompt: H2 FDW→直连ETL统一改造 | FDW 外部表(fdw_etl.*)改为直连 ETL 库
|
||||
# 查询 app.v_* RLS 视图。原因:postgres_fdw 不传递 GUC 参数,RLS 门店隔离失效。
|
||||
# 使用 fdw_queries._fdw_context() 上下文管理器统一管理 ETL 连接。
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
人员匹配服务 —— 根据申请信息在 FDW 外部表中查找候选匹配。
|
||||
人员匹配服务 —— 根据申请信息在 ETL 库 RLS 视图中查找候选匹配。
|
||||
|
||||
职责:
|
||||
- find_candidates():根据 site_id + phone(+ employee_number)在助教表和员工表中查找匹配
|
||||
|
||||
查询通过业务库的 fdw_etl Schema 访问 ETL 库的 RLS 视图。
|
||||
查询前需 SET LOCAL app.current_site_id 以启用门店隔离。
|
||||
FDW 外部表可能不存在(测试库等场景),需优雅降级返回空列表。
|
||||
直连 ETL 库查询 app.v_* RLS 视图,通过 _fdw_context 设置 site_id 实现门店隔离。
|
||||
ETL 库连接失败时优雅降级返回空列表。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from app.database import get_connection
|
||||
from app.services.fdw_queries import _fdw_context
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -29,9 +33,9 @@ async def find_candidates(
|
||||
|
||||
查询逻辑:
|
||||
1. 若 site_id 为 None,跳过匹配,返回空列表
|
||||
2. 设置 app.current_site_id 进行 RLS 隔离
|
||||
3. fdw_etl.v_dim_assistant: WHERE mobile = phone
|
||||
4. fdw_etl.v_dim_staff JOIN fdw_etl.v_dim_staff_ex: WHERE mobile = phone OR job_num = employee_number
|
||||
2. 设置 app.current_site_id 进行 RLS 隔离(直连 ETL 库)
|
||||
3. app.v_dim_assistant: WHERE mobile = phone
|
||||
4. app.v_dim_staff JOIN app.v_dim_staff_ex: WHERE mobile = phone OR job_num = employee_number
|
||||
5. 合并结果返回统一候选列表
|
||||
|
||||
参数:
|
||||
@@ -49,56 +53,46 @@ async def find_candidates(
|
||||
|
||||
candidates: list[dict] = []
|
||||
|
||||
conn = get_connection()
|
||||
# CHANGE 2026-03-20 | H2 FDW→直连ETL | 从业务库 fdw_etl.* 改为直连 ETL 库 app.v_*
|
||||
# intent: 修复 RLS 门店隔离失效(postgres_fdw 不传递 GUC 参数)
|
||||
# assumptions: _fdw_context 内部管理 ETL 连接生命周期,无需外部 conn
|
||||
try:
|
||||
conn.autocommit = False
|
||||
with conn.cursor() as cur:
|
||||
# 设置 RLS 隔离:FDW 会透传 session 变量到远端 ETL 库
|
||||
cur.execute(
|
||||
"SET LOCAL app.current_site_id = %s", (str(site_id),)
|
||||
)
|
||||
|
||||
with _fdw_context(None, site_id) as cur:
|
||||
# 1. 查询助教匹配
|
||||
candidates.extend(_query_assistants(cur, phone))
|
||||
|
||||
# 2. 查询员工匹配
|
||||
candidates.extend(_query_staff(cur, phone, employee_number))
|
||||
|
||||
conn.commit()
|
||||
except Exception:
|
||||
logger.warning(
|
||||
"FDW 人员匹配查询失败 (site_id=%s, phone=%s),返回空列表",
|
||||
"ETL 人员匹配查询失败 (site_id=%s, phone=%s),返回空列表",
|
||||
site_id,
|
||||
phone,
|
||||
exc_info=True,
|
||||
)
|
||||
try:
|
||||
conn.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return candidates
|
||||
|
||||
|
||||
def _query_assistants(cur, phone: str) -> list[dict]:
|
||||
"""查询 fdw_etl.v_dim_assistant 中按 mobile 匹配的助教记录。"""
|
||||
"""查询 app.v_dim_assistant 中按 mobile 匹配的助教记录(直连 ETL 库)。"""
|
||||
try:
|
||||
# CHANGE 2026-03-20 | H2 | fdw_etl.v_dim_assistant → app.v_dim_assistant
|
||||
# 列名映射: scd2_is_current 是 integer 类型(1=当前),不是 boolean
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT assistant_id, real_name, mobile
|
||||
FROM fdw_etl.v_dim_assistant
|
||||
FROM app.v_dim_assistant
|
||||
WHERE mobile = %s
|
||||
AND scd2_is_current = TRUE
|
||||
AND scd2_is_current = 1
|
||||
""",
|
||||
(phone,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
except Exception:
|
||||
logger.warning(
|
||||
"查询 fdw_etl.v_dim_assistant 失败,跳过助教匹配",
|
||||
"查询 app.v_dim_assistant 失败,跳过助教匹配",
|
||||
exc_info=True,
|
||||
)
|
||||
return []
|
||||
@@ -119,20 +113,21 @@ def _query_staff(
|
||||
cur, phone: str, employee_number: str | None
|
||||
) -> list[dict]:
|
||||
"""
|
||||
查询 fdw_etl.v_dim_staff JOIN fdw_etl.v_dim_staff_ex
|
||||
查询 app.v_dim_staff JOIN app.v_dim_staff_ex(直连 ETL 库)
|
||||
按 mobile 或 job_num 匹配的员工记录。
|
||||
"""
|
||||
try:
|
||||
# 构建 WHERE 条件:mobile = phone,或 job_num = employee_number
|
||||
# CHANGE 2026-03-20 | H2 | fdw_etl.v_dim_staff/v_dim_staff_ex → app.v_dim_staff/v_dim_staff_ex
|
||||
# 列名映射: scd2_is_current 是 integer 类型(1=当前),不是 boolean
|
||||
if employee_number:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT s.staff_id, s.staff_name, s.mobile, ex.job_num
|
||||
FROM fdw_etl.v_dim_staff s
|
||||
LEFT JOIN fdw_etl.v_dim_staff_ex ex
|
||||
FROM app.v_dim_staff s
|
||||
LEFT JOIN app.v_dim_staff_ex ex
|
||||
ON s.staff_id = ex.staff_id
|
||||
AND ex.scd2_is_current = TRUE
|
||||
WHERE s.scd2_is_current = TRUE
|
||||
AND ex.scd2_is_current = 1
|
||||
WHERE s.scd2_is_current = 1
|
||||
AND (s.mobile = %s OR ex.job_num = %s)
|
||||
""",
|
||||
(phone, employee_number),
|
||||
@@ -141,11 +136,11 @@ def _query_staff(
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT s.staff_id, s.staff_name, s.mobile, ex.job_num
|
||||
FROM fdw_etl.v_dim_staff s
|
||||
LEFT JOIN fdw_etl.v_dim_staff_ex ex
|
||||
FROM app.v_dim_staff s
|
||||
LEFT JOIN app.v_dim_staff_ex ex
|
||||
ON s.staff_id = ex.staff_id
|
||||
AND ex.scd2_is_current = TRUE
|
||||
WHERE s.scd2_is_current = TRUE
|
||||
AND ex.scd2_is_current = 1
|
||||
WHERE s.scd2_is_current = 1
|
||||
AND s.mobile = %s
|
||||
""",
|
||||
(phone,),
|
||||
@@ -153,7 +148,7 @@ def _query_staff(
|
||||
rows = cur.fetchall()
|
||||
except Exception:
|
||||
logger.warning(
|
||||
"查询 fdw_etl.v_dim_staff 失败,跳过员工匹配",
|
||||
"查询 app.v_dim_staff 失败,跳过员工匹配",
|
||||
exc_info=True,
|
||||
)
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user