Metadata-Version: 2.4
Name: uc-integration
Version: 0.1.18
Summary: GSS 用户中心 SSO / 主数据同步 Fred 模块（子系统可 pip 安装）
Author: GSS
License: Proprietary
Project-URL: Homepage, https://github.com/renzhong/gss
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: flask>=2.0
Requires-Dist: flask-smorest>=0.40
Requires-Dist: flask-jwt-extended>=4.0
Requires-Dist: flask-babelplus>=2.0
Requires-Dist: marshmallow>=3.0
Requires-Dist: sqlalchemy>=1.4
Requires-Dist: pip==26.1.2
Provides-Extra: fred
Requires-Dist: fred-admin; extra == "fred"
Dynamic: requires-python

# GSS UC Integration — 用户中心 SSO / 主数据同步（Fred 子系统共享包）

将各子系统里同构的 `modules/uc_integration` 抽成可安装包，避免复制粘贴分叉。

## 能力

| 接口 | 说明 |
|------|------|
| `GET  /{subsystem}/uc_integration/auth/sso/config` | 登录模式 |
| `POST /{subsystem}/uc_integration/auth/sso/exchange` | UC 一次性 code → 本地 JWT（不接受 accessToken） |
| `POST /{subsystem}/uc_integration/auth/sso/refresh` | UC refreshToken 续期 |
| `POST /{subsystem}/uc_integration/auth/sso/admin/exchange` | 管理端：code → **UC 用户** JWT |
| `POST /{subsystem}/uc_integration/auth/sso/admin/refresh` | 管理端续期 |
| `POST /{subsystem}/uc_integration/internal/uc-sync` | UC 主数据推送（仅本机） |
| `GET  /{subsystem}/uc_integration/internal/uc-sync` | 接收端存活探测（不查本地表） |

### 服务层（无子系统 HTTP）：按用户 id 查组织 + 门店

只读走 UC Open API `/open_api/internal/*`，依赖 `USER_CENTER_BASE_URL` / `USER_CENTER_INTERNAL_SECRET`。

```python
from uc_integration.service.UcStoreScopeService import (
    UcStoreScopeService,
    UcScopeQueryError,
)

try:
    scope = UcStoreScopeService().get_org_and_stores_by_user_id(user_id)
except UcScopeQueryError as e:
    # UC HTTP 失败 / 内部密钥未配置（含明确中文说明）
    ...
# 用户不存在 → None
# scope['departments']  — 所属组织
# scope['stores'] / scope['store_ids']  — 可访问门店
```

```python
from uc_integration.service.UcUserQueryService import UcUserQueryService
from uc_integration.service.UcDeptQueryService import UcDeptQueryService
from uc_integration.service.UcStoreQueryService import UcStoreQueryService
from uc_integration.service.UcDeptUserQueryService import UcDeptUserQueryService
from uc_integration.service.UcBusinessRoleService import UcBusinessRoleService
from uc_integration.service.UcStorePickerService import UcStorePickerService

user = UcUserQueryService().get_user_by_id(user_id)          # 无则 None
depts = UcDeptQueryService().list_departments(dept_type=1)   # 门店型部门
tree = UcDeptQueryService().build_dept_tree()                # parent_id = 远程 dept_id
store = UcStoreQueryService().get_store_by_id(store_id)
dept_ids = UcDeptUserQueryService().list_dept_ids_by_user_id(user_id)
roles = UcBusinessRoleService().list_roles_for_user(user_id) # HTTP 失败 → []
catalog = UcStorePickerService().list_catalog(user_id)       # 选店列表 + 按门店裁剪的品牌/区域/省市
# catalog['stores'] / catalog['filters']
# 不要用 list_brand_departments / list_region_departments 做选店筛选（全量部门，仅管理端）
```

User / Dept / Store / DeptUser / Scope 只读调 UC，返回 dict（不返回 ORM）。空 IN → `[]` / `{}`，不发 HTTP。

本地 JWT 的 `sub` 含：

- 本地身份：`id` / `username` / `name` / `store_id`（当前选中，可空）/ `role`
- **`uc` 精简**：`code` / `name` / `store` / `is_store_manager`，以及 `departments` / `roles` / `roleCodes`（payload 有则带）
- **不含**全量 `store_ids` / `stores`（避免 Authorization 过大 → 431）

换票响应 body 可另含 `storeIds`（当次组织现查结果，供前端首屏），**不进 JWT**。后续门店列表应走服务端按部门现查。

## 安装

```bash
# 开发：可编辑安装
pip install -e /path/to/uc-integration

# 或从 git
pip install "gss-uc-integration @ git+ssh://git@github.com/org/uc-integration.git"
```

Fred `>=1.0.17` 用 `EXTERNAL_MODULES` 从 **site-packages** 加载蓝图，**不要**再软链进 `modules/`：

```python
EXTERNAL_MODULES = ["uc_integration"]
```

旧脚本 `scripts/link_to_project.py` 仅兼容更早的 Fred，新项目不要跑。

## 宿主配置

`common/config/Config.py`（或等价配置）：

```python
EXTERNAL_MODULES = ["uc_integration"]   # 必填；不要把 uc_integration 放进 LOAD_CUSTOM_MODULES / modules/

SSO_LOGIN_MODE = 'uc'                    # 或 'local'
UC_SUBSYSTEM_CODE = 'ai_check'           # 路由前缀：/{code}/uc_integration；内部请求头 X-UC-Subsystem-Code
USER_CENTER_BASE_URL = 'http://127.0.0.1:5001'
USER_CENTER_INTERNAL_SECRET = '...'      # 与 UC OPEN_API_INTERNAL_SECRET 一致

# 可选
UC_INCLUDE_STORE_SCOPE = True            # 解析/返回门店 scope + 当前 store_id；不写全量 store_ids 进 JWT（默认 True）
UC_STORE_FALLBACK_FIRST = True           # 无匹配门店时回退第一个（默认 True；仅关闭 scope 时的选店回退）
```

**必填（UC 主数据只读 HTTP）**

| 配置 | 用途 |
|------|------|
| `USER_CENTER_BASE_URL` | UC Open API 基址 |
| `USER_CENTER_INTERNAL_SECRET` | 请求头 `X-UC-Internal-Secret`（与 UC `OPEN_API_INTERNAL_SECRET` 一致） |
| `UC_SUBSYSTEM_CODE` | 请求头 `X-UC-Subsystem-Code`；UC 侧若带此头则校验子系统已登记且 `status=1` |

UC 主数据 **reads 只走 HTTP**（`/open_api/internal/*`），**不对本地 `uc_*` 做 SELECT**。

本地库仅用于：

1. **接收 UC push upsert**（`UcDataSyncService` POST 写本地 `uc_*`）
2. **其它业务表**（与 UC 实体无关）；SSO / 组织 / 门店 / 角色 / scope **不再读本地 UC 镜像**

`GET .../internal/uc-sync` 为接收端存活探测（`status=ok, mode=receiver`），不 COUNT 本地表。

## 宿主前置条件

- 配置 `USER_CENTER_BASE_URL`、`USER_CENTER_INTERNAL_SECRET`、`UC_SUBSYSTEM_CODE`
- 本地 `uc_*` 镜像表仅服务 push 接收；只读查询与 SSO 换票不依赖它们
- 依赖：`fred_admin`、Flask、SQLAlchemy（由子系统环境提供；SQLAlchemy 仅 push 写入需要）

## 目录结构（Fred 兼容）

```
uc_integration/
  __init__.py              # 导出蓝图
  backend/
    __init__.py            # Blueprint + url_prefix
    deps.py                # 惰性解析 model.model
    controller/
    service/
    schema/
```

## 从旧模块迁移

1. `pip install "uc-integration>=0.1.13"`（或 `-e /path/to/uc-integration`）
2. 删除子系统里的 `modules/uc_integration`（拷贝或软链都删）
3. `Config.py` 设 `EXTERNAL_MODULES = ["uc_integration"]`，不要把它写进 `LOAD_CUSTOM_MODULES`
4. 确认 `UC_SUBSYSTEM_CODE` 与门户子系统编码一致
5. 重启进程，验证 `GET /{code}/uc_integration/auth/sso/config`

## 版本

当前 `0.1.17`：SSO exchange 优先从 UC 换票画像本地组装门店 scope，不足再 HTTP；scope 查询增加短 TTL 内存缓存。`list_catalog` 仍优先走 UC store-picker（含维度 `count`）。UC 主数据 reads 仅 HTTP；SSO / Admin SSO 不再写本地用户。
