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:
Neo
2026-03-20 09:02:10 +08:00
parent 3d2e5f8165
commit beb88d5bea
388 changed files with 6436 additions and 25458 deletions

View File

@@ -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 []