Metadata-Version: 2.4
Name: uc-integration
Version: 0.1.30
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
Requires-Dist: pyyaml>=6.0
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 → 按 username 同步本地 admin → JWT |
| `POST /{subsystem}/uc_integration/auth/sso/admin/refresh` | 管理端续期 |
| `POST /{subsystem}/uc_integration/internal/uc-sync` | UC 主数据推送（仅本机）**（已停用）**：UC 不再推送，端点仍挂载但不会被调用，子系统无需实现/验证 |
| `GET  /{subsystem}/uc_integration/internal/uc-sync` | 接收端存活探测（不查本地表）**（已停用）**：UC 不再调用，子系统无需联调 |

### 服务层（无子系统 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.UcPermissionService import UcPermissionService
from uc_integration.service.UcStorePickerService import UcStorePickerService
from uc_integration.service.UserCenterClient import UserCenterClient
from uc_integration.service.OmniChatService import OmniChatService

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 失败 → []
# 业务功能权限（角色功能权限 ∪ 用户额外功能权限）；subsystem 省略则用 UC_SUBSYSTEM_CODE
perms = UcPermissionService().permissions_for_user(user_id)              # 本子系统
portal_perms = UcPermissionService().permissions_for_user(user_id, 'portal')
ok = UcPermissionService().has_permission(user_id, 'todo.task.assign')
catalog = UcStorePickerService().list_catalog(user_id)       # 选店列表 + 按门店裁剪的品牌/区域/省市
# catalog['stores'] / catalog['filters']
# 不要用 list_brand_departments / list_region_departments 做选店筛选（全量部门，仅管理端）

cfg = UserCenterClient().get_sys_config('OMNI_CHAT_URL')     # {name,value,desc} 或 None
vals = UserCenterClient().get_sys_configs(['OMNI_CHAT_URL', 'OMNI_CHAT_TOKEN', 'OMNI_CHAT_MODEL'])
text = OmniChatService().complete([{'role': 'user', 'content': 'hi'}])  # 凭证取自 UC sys_config

# 其它子系统 HTTP origin（= UC「子系统管理 → 访问地址」routePath；换域名只改 UC 登记）
finance_origin = UserCenterClient().get_subsystem_base_url('finance')  # 或 get_subsystem_catalog('finance')
# 禁止写死 http://127.0.0.1:5007

# AI 预览 / 加入模块：幂等登记为「开发中」（服务端 → UC internal，无 CORS）
draft = UserCenterClient().ensure_subsystem_draft(
    'my-mod', name='我的模块', route_path='https://host/my-mod/',
)
```

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**。

> **说明：UC 已停用「数据同步」**——UC 不再向子系统主动推送用户/组织/门店主数据。`internal/uc-sync` 端点与 `UcDataSyncService` 仍随包挂载（代码保留），但 UC 不会调用它们，子系统无需实现、验证或联调；用户/门店/组织一律走 `UcUserQueryService` / `UcStoreQueryService` / `UcStoreScopeService` / `UcStorePickerService` 在线 HTTP 查询。本地 `uc_*` 镜像表不再由 UC 更新，请勿依赖其新鲜度。

本地库仅用于：

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

`GET .../internal/uc-sync` 为接收端存活探测（`status=ok, mode=receiver`），不 COUNT 本地表（**已停用**：UC 不再调用，子系统无需验证）。

## 宿主前置条件

- 配置 `USER_CENTER_BASE_URL`、`USER_CENTER_INTERNAL_SECRET`、`UC_SUBSYSTEM_CODE`
- 本地 `uc_*` 镜像表原仅服务 push 接收（**已停用**：UC 不再推送，镜像表不再由 UC 更新，勿依赖其新鲜度）；只读查询与 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.30`：`UcPermissionService.permissions_for_user` / `has_permission` 支持可选 `subsystem_code`；权限码走 Redis 共享缓存（`uc:perm:v1:{user_id}:{subsystem}`，TTL 3600s，与 UC 同契约；Redis 异常降级 OpenAPI）。用于门户等场景只认 UC 角色∪用户功能权限。
