uc-integration 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uc_integration-0.1.0/PKG-INFO +112 -0
- uc_integration-0.1.0/README.md +92 -0
- uc_integration-0.1.0/pyproject.toml +33 -0
- uc_integration-0.1.0/setup.cfg +4 -0
- uc_integration-0.1.0/setup.py +18 -0
- uc_integration-0.1.0/uc_integration/__init__.py +64 -0
- uc_integration-0.1.0/uc_integration/backend/__init__.py +42 -0
- uc_integration-0.1.0/uc_integration/backend/controller/SsoAuthController.py +47 -0
- uc_integration-0.1.0/uc_integration/backend/controller/UcDataSyncController.py +62 -0
- uc_integration-0.1.0/uc_integration/backend/controller/__init__.py +49 -0
- uc_integration-0.1.0/uc_integration/backend/deps.py +27 -0
- uc_integration-0.1.0/uc_integration/backend/schema/SsoAuthSchema.py +32 -0
- uc_integration-0.1.0/uc_integration/backend/schema/__init__.py +0 -0
- uc_integration-0.1.0/uc_integration/backend/service/SsoAuthService.py +301 -0
- uc_integration-0.1.0/uc_integration/backend/service/UcDataSyncService.py +260 -0
- uc_integration-0.1.0/uc_integration/backend/service/UcStoreScopeService.py +245 -0
- uc_integration-0.1.0/uc_integration/backend/service/UserCenterClient.py +92 -0
- uc_integration-0.1.0/uc_integration/backend/service/__init__.py +0 -0
- uc_integration-0.1.0/uc_integration.egg-info/PKG-INFO +112 -0
- uc_integration-0.1.0/uc_integration.egg-info/SOURCES.txt +21 -0
- uc_integration-0.1.0/uc_integration.egg-info/dependency_links.txt +1 -0
- uc_integration-0.1.0/uc_integration.egg-info/requires.txt +10 -0
- uc_integration-0.1.0/uc_integration.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: uc-integration
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GSS 用户中心 SSO / 主数据同步 Fred 模块(子系统可 pip 安装)
|
|
5
|
+
Author: GSS
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Homepage, https://github.com/renzhong/gss
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: flask>=2.0
|
|
11
|
+
Requires-Dist: flask-smorest>=0.40
|
|
12
|
+
Requires-Dist: flask-jwt-extended>=4.0
|
|
13
|
+
Requires-Dist: flask-babelplus>=2.0
|
|
14
|
+
Requires-Dist: marshmallow>=3.0
|
|
15
|
+
Requires-Dist: sqlalchemy>=1.4
|
|
16
|
+
Requires-Dist: pip==26.1.2
|
|
17
|
+
Provides-Extra: fred
|
|
18
|
+
Requires-Dist: fred-admin; extra == "fred"
|
|
19
|
+
Dynamic: requires-python
|
|
20
|
+
|
|
21
|
+
# GSS UC Integration — 用户中心 SSO / 主数据同步(Fred 子系统共享包)
|
|
22
|
+
|
|
23
|
+
将各子系统里同构的 `modules/uc_integration` 抽成可安装包,避免复制粘贴分叉。
|
|
24
|
+
|
|
25
|
+
## 能力
|
|
26
|
+
|
|
27
|
+
| 接口 | 说明 |
|
|
28
|
+
|------|------|
|
|
29
|
+
| `GET /{subsystem}/uc_integration/auth/sso/config` | 登录模式 |
|
|
30
|
+
| `POST /{subsystem}/uc_integration/auth/sso/exchange` | UC code/accessToken → 本地 JWT |
|
|
31
|
+
| `POST /{subsystem}/uc_integration/auth/sso/refresh` | UC refreshToken 续期 |
|
|
32
|
+
| `POST /{subsystem}/uc_integration/internal/uc-sync` | UC 主数据推送(仅本机) |
|
|
33
|
+
| `GET /{subsystem}/uc_integration/internal/uc-sync` | 同步健康检查 |
|
|
34
|
+
|
|
35
|
+
本地 JWT 的 `sub` 含:
|
|
36
|
+
|
|
37
|
+
- 本地身份:`id` / `username` / `name` / `store_id` / `store_ids` / `role`
|
|
38
|
+
- **`uc`:UC validate/exchange 原样透传**(不做字段白名单)
|
|
39
|
+
|
|
40
|
+
## 安装
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 开发:可编辑安装
|
|
44
|
+
pip install -e /path/to/uc-integration
|
|
45
|
+
|
|
46
|
+
# 或从 git
|
|
47
|
+
pip install "gss-uc-integration @ git+ssh://git@github.com/org/uc-integration.git"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Fred 只从项目 `modules/` 扫描蓝图,安装后必须链接:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 在子系统根目录(含 modules/)
|
|
54
|
+
python /path/to/uc-integration/scripts/link_to_project.py .
|
|
55
|
+
# 或覆盖已有 symlink
|
|
56
|
+
python /path/to/uc-integration/scripts/link_to_project.py . --force
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
等价手动操作:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
ln -sfn "$(python -c 'import uc_integration,pathlib; print(pathlib.Path(uc_integration.__file__).parent)')" \
|
|
63
|
+
modules/uc_integration
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 宿主配置
|
|
67
|
+
|
|
68
|
+
`common/config/Config.py`(或等价配置):
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
LOAD_CUSTOM_MODULES = [..., "uc_integration"]
|
|
72
|
+
|
|
73
|
+
SSO_LOGIN_MODE = 'uc' # 或 'local'
|
|
74
|
+
UC_SUBSYSTEM_CODE = 'ai_check' # 路由前缀:/{code}/uc_integration
|
|
75
|
+
USER_CENTER_BASE_URL = 'http://127.0.0.1:5001'
|
|
76
|
+
USER_CENTER_INTERNAL_SECRET = '...' # 与 UC OPEN_API_INTERNAL_SECRET 一致
|
|
77
|
+
|
|
78
|
+
# 可选
|
|
79
|
+
UC_INCLUDE_STORE_SCOPE = True # JWT 写入 store_ids / role(默认 True)
|
|
80
|
+
UC_STORE_FALLBACK_FIRST = True # 无匹配门店时回退第一个(默认 True)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 宿主前置条件
|
|
84
|
+
|
|
85
|
+
- 本地库有 `uc_user` / `uc_store` 等镜像表(可由 sync DDL 自动建)
|
|
86
|
+
- ORM:`model.model` 中存在 `UcUser`(以及门店 scope 需要的 `UcDept` 等)
|
|
87
|
+
- 依赖:`fred_admin`、Flask、SQLAlchemy(由子系统环境提供)
|
|
88
|
+
|
|
89
|
+
## 目录结构(Fred 兼容)
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
uc_integration/
|
|
93
|
+
__init__.py # 导出蓝图
|
|
94
|
+
backend/
|
|
95
|
+
__init__.py # Blueprint + url_prefix
|
|
96
|
+
deps.py # 惰性解析 model.model
|
|
97
|
+
controller/
|
|
98
|
+
service/
|
|
99
|
+
schema/
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 从旧模块迁移
|
|
103
|
+
|
|
104
|
+
1. `pip install -e ../uc-integration`
|
|
105
|
+
2. 备份并删除子系统 `modules/uc_integration`
|
|
106
|
+
3. `python ../uc-integration/scripts/link_to_project.py .`
|
|
107
|
+
4. 确认 `UC_SUBSYSTEM_CODE` 与门户子系统编码一致
|
|
108
|
+
5. 验证 `GET /{code}/uc_integration/auth/sso/config`
|
|
109
|
+
|
|
110
|
+
## 版本
|
|
111
|
+
|
|
112
|
+
当前 `0.1.0`,以 ai_check(UC 原样透传)+ todo(门店 scope / 动态前缀)合并为基准。
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# GSS UC Integration — 用户中心 SSO / 主数据同步(Fred 子系统共享包)
|
|
2
|
+
|
|
3
|
+
将各子系统里同构的 `modules/uc_integration` 抽成可安装包,避免复制粘贴分叉。
|
|
4
|
+
|
|
5
|
+
## 能力
|
|
6
|
+
|
|
7
|
+
| 接口 | 说明 |
|
|
8
|
+
|------|------|
|
|
9
|
+
| `GET /{subsystem}/uc_integration/auth/sso/config` | 登录模式 |
|
|
10
|
+
| `POST /{subsystem}/uc_integration/auth/sso/exchange` | UC code/accessToken → 本地 JWT |
|
|
11
|
+
| `POST /{subsystem}/uc_integration/auth/sso/refresh` | UC refreshToken 续期 |
|
|
12
|
+
| `POST /{subsystem}/uc_integration/internal/uc-sync` | UC 主数据推送(仅本机) |
|
|
13
|
+
| `GET /{subsystem}/uc_integration/internal/uc-sync` | 同步健康检查 |
|
|
14
|
+
|
|
15
|
+
本地 JWT 的 `sub` 含:
|
|
16
|
+
|
|
17
|
+
- 本地身份:`id` / `username` / `name` / `store_id` / `store_ids` / `role`
|
|
18
|
+
- **`uc`:UC validate/exchange 原样透传**(不做字段白名单)
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 开发:可编辑安装
|
|
24
|
+
pip install -e /path/to/uc-integration
|
|
25
|
+
|
|
26
|
+
# 或从 git
|
|
27
|
+
pip install "gss-uc-integration @ git+ssh://git@github.com/org/uc-integration.git"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Fred 只从项目 `modules/` 扫描蓝图,安装后必须链接:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 在子系统根目录(含 modules/)
|
|
34
|
+
python /path/to/uc-integration/scripts/link_to_project.py .
|
|
35
|
+
# 或覆盖已有 symlink
|
|
36
|
+
python /path/to/uc-integration/scripts/link_to_project.py . --force
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
等价手动操作:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
ln -sfn "$(python -c 'import uc_integration,pathlib; print(pathlib.Path(uc_integration.__file__).parent)')" \
|
|
43
|
+
modules/uc_integration
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 宿主配置
|
|
47
|
+
|
|
48
|
+
`common/config/Config.py`(或等价配置):
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
LOAD_CUSTOM_MODULES = [..., "uc_integration"]
|
|
52
|
+
|
|
53
|
+
SSO_LOGIN_MODE = 'uc' # 或 'local'
|
|
54
|
+
UC_SUBSYSTEM_CODE = 'ai_check' # 路由前缀:/{code}/uc_integration
|
|
55
|
+
USER_CENTER_BASE_URL = 'http://127.0.0.1:5001'
|
|
56
|
+
USER_CENTER_INTERNAL_SECRET = '...' # 与 UC OPEN_API_INTERNAL_SECRET 一致
|
|
57
|
+
|
|
58
|
+
# 可选
|
|
59
|
+
UC_INCLUDE_STORE_SCOPE = True # JWT 写入 store_ids / role(默认 True)
|
|
60
|
+
UC_STORE_FALLBACK_FIRST = True # 无匹配门店时回退第一个(默认 True)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 宿主前置条件
|
|
64
|
+
|
|
65
|
+
- 本地库有 `uc_user` / `uc_store` 等镜像表(可由 sync DDL 自动建)
|
|
66
|
+
- ORM:`model.model` 中存在 `UcUser`(以及门店 scope 需要的 `UcDept` 等)
|
|
67
|
+
- 依赖:`fred_admin`、Flask、SQLAlchemy(由子系统环境提供)
|
|
68
|
+
|
|
69
|
+
## 目录结构(Fred 兼容)
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
uc_integration/
|
|
73
|
+
__init__.py # 导出蓝图
|
|
74
|
+
backend/
|
|
75
|
+
__init__.py # Blueprint + url_prefix
|
|
76
|
+
deps.py # 惰性解析 model.model
|
|
77
|
+
controller/
|
|
78
|
+
service/
|
|
79
|
+
schema/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 从旧模块迁移
|
|
83
|
+
|
|
84
|
+
1. `pip install -e ../uc-integration`
|
|
85
|
+
2. 备份并删除子系统 `modules/uc_integration`
|
|
86
|
+
3. `python ../uc-integration/scripts/link_to_project.py .`
|
|
87
|
+
4. 确认 `UC_SUBSYSTEM_CODE` 与门户子系统编码一致
|
|
88
|
+
5. 验证 `GET /{code}/uc_integration/auth/sso/config`
|
|
89
|
+
|
|
90
|
+
## 版本
|
|
91
|
+
|
|
92
|
+
当前 `0.1.0`,以 ai_check(UC 原样透传)+ todo(门店 scope / 动态前缀)合并为基准。
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "uc-integration"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "GSS 用户中心 SSO / 主数据同步 Fred 模块(子系统可 pip 安装)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Proprietary" }
|
|
12
|
+
authors = [{ name = "GSS" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"flask>=2.0",
|
|
15
|
+
"flask-smorest>=0.40",
|
|
16
|
+
"flask-jwt-extended>=4.0",
|
|
17
|
+
"flask-babelplus>=2.0",
|
|
18
|
+
"marshmallow>=3.0",
|
|
19
|
+
"sqlalchemy>=1.4",
|
|
20
|
+
"pip==26.1.2",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
fred = ["fred-admin"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/renzhong/gss"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
include = ["uc_integration*"]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.package-data]
|
|
33
|
+
uc_integration = ["py.typed"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name='uc-integration',
|
|
5
|
+
version='0.1.0',
|
|
6
|
+
description='GSS 用户中心 SSO / 主数据同步 Fred 模块',
|
|
7
|
+
packages=find_packages(include=['uc_integration', 'uc_integration.*']),
|
|
8
|
+
python_requires='>=3.10',
|
|
9
|
+
install_requires=[
|
|
10
|
+
'flask>=2.0',
|
|
11
|
+
'flask-smorest>=0.40',
|
|
12
|
+
'flask-jwt-extended>=4.0',
|
|
13
|
+
'flask-babelplus>=2.0',
|
|
14
|
+
'marshmallow>=3.0',
|
|
15
|
+
'sqlalchemy>=1.4',
|
|
16
|
+
],
|
|
17
|
+
include_package_data=True,
|
|
18
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# uc_integration — 用户中心 SSO / 主数据同步(Fred 子系统可安装模块)
|
|
2
|
+
"""安装后可直接 import;在 Fred 下通过 modules/ symlink 自动注册蓝图。"""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import sys
|
|
7
|
+
import types
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
__version__ = '0.1.0'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _ensure_fred_style_aliases() -> None:
|
|
14
|
+
"""将 backend/{service,schema,...} 注册为 uc_integration.*,并预加载 .py 文件。
|
|
15
|
+
|
|
16
|
+
对齐 Fred `_register_backend_submodules`:仅建空包会导致
|
|
17
|
+
`from uc_integration.schema.X import Y` 在部分加载顺序下失败。
|
|
18
|
+
"""
|
|
19
|
+
backend = Path(__file__).resolve().parent / 'backend'
|
|
20
|
+
if not backend.is_dir():
|
|
21
|
+
return
|
|
22
|
+
for subdir in backend.iterdir():
|
|
23
|
+
if not subdir.is_dir() or subdir.name.startswith('_') or subdir.name == 'controller':
|
|
24
|
+
continue
|
|
25
|
+
pkg_name = f'uc_integration.{subdir.name}'
|
|
26
|
+
if pkg_name not in sys.modules:
|
|
27
|
+
mod = types.ModuleType(pkg_name)
|
|
28
|
+
mod.__path__ = [str(subdir)]
|
|
29
|
+
mod.__file__ = str(subdir / '__init__.py')
|
|
30
|
+
mod.__package__ = pkg_name
|
|
31
|
+
sys.modules[pkg_name] = mod
|
|
32
|
+
setattr(sys.modules[__name__], subdir.name, mod)
|
|
33
|
+
else:
|
|
34
|
+
mod = sys.modules[pkg_name]
|
|
35
|
+
if not getattr(mod, '__path__', None):
|
|
36
|
+
mod.__path__ = [str(subdir)]
|
|
37
|
+
|
|
38
|
+
init_file = subdir / '__init__.py'
|
|
39
|
+
if init_file.exists() and not getattr(mod, '__file__', None):
|
|
40
|
+
mod.__file__ = str(init_file)
|
|
41
|
+
|
|
42
|
+
for py_file in subdir.glob('*.py'):
|
|
43
|
+
if py_file.name.startswith('__'):
|
|
44
|
+
continue
|
|
45
|
+
file_mod_name = f'{pkg_name}.{py_file.stem}'
|
|
46
|
+
if file_mod_name in sys.modules:
|
|
47
|
+
continue
|
|
48
|
+
try:
|
|
49
|
+
spec = importlib.util.spec_from_file_location(file_mod_name, py_file)
|
|
50
|
+
if not spec or not spec.loader:
|
|
51
|
+
continue
|
|
52
|
+
file_mod = importlib.util.module_from_spec(spec)
|
|
53
|
+
sys.modules[file_mod_name] = file_mod
|
|
54
|
+
spec.loader.exec_module(file_mod)
|
|
55
|
+
setattr(mod, py_file.stem, file_mod)
|
|
56
|
+
except Exception:
|
|
57
|
+
sys.modules.pop(file_mod_name, None)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
_ensure_fred_style_aliases()
|
|
61
|
+
|
|
62
|
+
from .backend import uc_integration, get_admin_role, get_subsystem_code # noqa: E402
|
|
63
|
+
|
|
64
|
+
__all__ = ['uc_integration', 'get_admin_role', 'get_subsystem_code', '__version__']
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# -*- coding: utf-8
|
|
2
|
+
"""蓝图入口:url_prefix 由 UC_SUBSYSTEM_CODE 动态推导。"""
|
|
3
|
+
from flask_smorest import Blueprint
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_subsystem_code() -> str:
|
|
7
|
+
"""子系统编码:优先 Config.UC_SUBSYSTEM_CODE,否则 DEFAULT_MODULE。"""
|
|
8
|
+
try:
|
|
9
|
+
from flask import current_app, has_app_context
|
|
10
|
+
if has_app_context():
|
|
11
|
+
code = (current_app.config.get('UC_SUBSYSTEM_CODE') or '').strip()
|
|
12
|
+
if code:
|
|
13
|
+
return code
|
|
14
|
+
return (current_app.config.get('DEFAULT_MODULE') or 'app').strip()
|
|
15
|
+
except Exception:
|
|
16
|
+
pass
|
|
17
|
+
try:
|
|
18
|
+
from common.config.Config import Config
|
|
19
|
+
code = (getattr(Config, 'UC_SUBSYSTEM_CODE', None) or '').strip()
|
|
20
|
+
if code:
|
|
21
|
+
return code
|
|
22
|
+
return (getattr(Config, 'DEFAULT_MODULE', None) or 'app').strip()
|
|
23
|
+
except Exception:
|
|
24
|
+
return 'app'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_admin_role() -> str:
|
|
28
|
+
"""从 DEFAULT_MODULE 推导管理员角色:{subsystem}_admin。"""
|
|
29
|
+
from flask import current_app
|
|
30
|
+
subsystem = (current_app.config.get('DEFAULT_MODULE') or get_subsystem_code()).strip()
|
|
31
|
+
return f'{subsystem}_admin'
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
uc_integration = Blueprint(
|
|
35
|
+
'uc_integration',
|
|
36
|
+
__name__,
|
|
37
|
+
url_prefix=f'/{get_subsystem_code()}/uc_integration',
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# SSO:/{UC_SUBSYSTEM_CODE}/uc_integration/auth/sso/*
|
|
41
|
+
# 同步:/{UC_SUBSYSTEM_CODE}/uc_integration/internal/uc-sync
|
|
42
|
+
# controller 由 Fred 扫描 modules/uc_integration/backend/controller/
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# -*- coding: utf-8
|
|
2
|
+
"""SSO 换票接口:UC code / accessToken → 本地 JWT。"""
|
|
3
|
+
from flask import abort, current_app
|
|
4
|
+
from flask.views import MethodView
|
|
5
|
+
from flask_babelplus import gettext
|
|
6
|
+
|
|
7
|
+
from uc_integration import uc_integration
|
|
8
|
+
from uc_integration.schema.SsoAuthSchema import (
|
|
9
|
+
SsoExchangeBodySchema,
|
|
10
|
+
SsoRefreshBodySchema,
|
|
11
|
+
SsoExchangeResponseSchema,
|
|
12
|
+
)
|
|
13
|
+
from uc_integration.service.SsoAuthService import SsoAuthService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@uc_integration.route('/auth/sso/config')
|
|
17
|
+
class SsoConfigController(MethodView):
|
|
18
|
+
def get(self):
|
|
19
|
+
"""子系统前端读取登录方式(无需登录)。"""
|
|
20
|
+
mode = (current_app.config.get('SSO_LOGIN_MODE') or 'local').strip().lower()
|
|
21
|
+
if mode not in ('uc', 'local'):
|
|
22
|
+
mode = 'local'
|
|
23
|
+
base_url = (current_app.config.get('USER_CENTER_BASE_URL') or '').strip().rstrip('/')
|
|
24
|
+
if not base_url:
|
|
25
|
+
abort(500, description=gettext('用户中心地址未配置(USER_CENTER_BASE_URL)'))
|
|
26
|
+
return {
|
|
27
|
+
'loginMode': mode,
|
|
28
|
+
'userCenterBaseUrl': base_url,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@uc_integration.route('/auth/sso/exchange')
|
|
33
|
+
class SsoExchangeController(MethodView):
|
|
34
|
+
@uc_integration.arguments(SsoExchangeBodySchema)
|
|
35
|
+
@uc_integration.response(200, SsoExchangeResponseSchema)
|
|
36
|
+
def post(self, args):
|
|
37
|
+
"""用 UC 授权码或 accessToken 兑换本子系统 JWT(服务端调 UC)。"""
|
|
38
|
+
return SsoAuthService().sso_exchange(args)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@uc_integration.route('/auth/sso/refresh')
|
|
42
|
+
class SsoRefreshController(MethodView):
|
|
43
|
+
@uc_integration.arguments(SsoRefreshBodySchema)
|
|
44
|
+
@uc_integration.response(200, SsoExchangeResponseSchema)
|
|
45
|
+
def post(self, args):
|
|
46
|
+
"""UC refreshToken 续期并重新签发本地 JWT。"""
|
|
47
|
+
return SsoAuthService().sso_refresh(args)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""
|
|
3
|
+
UC 主数据同步接口
|
|
4
|
+
|
|
5
|
+
接收 user-center 推送的用户、组织架构、门店等主数据,保存到本地数据库。
|
|
6
|
+
- POST /{subsystem}/uc_integration/internal/uc-sync 接收推送
|
|
7
|
+
- GET /{subsystem}/uc_integration/internal/uc-sync 健康检查
|
|
8
|
+
|
|
9
|
+
安全策略:
|
|
10
|
+
- 仅允许本机(127.0.0.1 / ::1)访问
|
|
11
|
+
- 校验请求头 X-UC-Internal-Secret 与本地 USER_CENTER_INTERNAL_SECRET 一致
|
|
12
|
+
"""
|
|
13
|
+
from flask import abort, current_app, request
|
|
14
|
+
from flask.views import MethodView
|
|
15
|
+
from flask_babelplus import gettext
|
|
16
|
+
from functools import wraps
|
|
17
|
+
|
|
18
|
+
from uc_integration import uc_integration
|
|
19
|
+
from uc_integration.service.UcDataSyncService import UcDataSyncService
|
|
20
|
+
|
|
21
|
+
_LOCALHOST_ADDRS = frozenset({'127.0.0.1', '::1'})
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _verify_local_internal_access() -> None:
|
|
25
|
+
"""校验请求来自本机且携带正确的内部密钥。"""
|
|
26
|
+
remote = (request.remote_addr or '').strip()
|
|
27
|
+
if remote not in _LOCALHOST_ADDRS:
|
|
28
|
+
abort(403, description=gettext('仅允许本机访问'))
|
|
29
|
+
|
|
30
|
+
expected = (current_app.config.get('USER_CENTER_INTERNAL_SECRET') or '').strip()
|
|
31
|
+
if not expected:
|
|
32
|
+
abort(501, description=gettext('内部同步密钥未配置(USER_CENTER_INTERNAL_SECRET)'))
|
|
33
|
+
|
|
34
|
+
provided = (request.headers.get('X-UC-Internal-Secret') or '').strip()
|
|
35
|
+
if provided != expected:
|
|
36
|
+
abort(403, description=gettext('无权访问同步接口'))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def uc_sync_local_only(f):
|
|
40
|
+
"""装饰器:限制仅本机 + 正确密钥可访问。"""
|
|
41
|
+
@wraps(f)
|
|
42
|
+
def wrapper(*args, **kwargs):
|
|
43
|
+
_verify_local_internal_access()
|
|
44
|
+
return f(*args, **kwargs)
|
|
45
|
+
return wrapper
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@uc_integration.route('/internal/uc-sync')
|
|
49
|
+
class UcDataSyncController(MethodView):
|
|
50
|
+
|
|
51
|
+
@uc_sync_local_only
|
|
52
|
+
@uc_integration.response(200)
|
|
53
|
+
def post(self):
|
|
54
|
+
"""接收 UC 推送的用户、组织、门店主数据(仅本机)。"""
|
|
55
|
+
payload = request.get_json(silent=True) or {}
|
|
56
|
+
return UcDataSyncService().sync(payload)
|
|
57
|
+
|
|
58
|
+
@uc_sync_local_only
|
|
59
|
+
@uc_integration.response(200)
|
|
60
|
+
def get(self):
|
|
61
|
+
"""同步接口健康检查(仅本机),返回各镜像表有效记录数。"""
|
|
62
|
+
return UcDataSyncService.health_check()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""业务 API 认证装饰器:仅认本地 JWT。"""
|
|
3
|
+
from functools import wraps
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from flask import abort, g, request
|
|
7
|
+
from flask_babelplus import gettext
|
|
8
|
+
from flask_jwt_extended import decode_token
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _extract_bearer_token() -> str:
|
|
12
|
+
auth = request.headers.get('Authorization') or ''
|
|
13
|
+
if auth.lower().startswith('bearer '):
|
|
14
|
+
return auth[7:].strip()
|
|
15
|
+
return ''
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _try_admin_jwt(token: str) -> bool:
|
|
19
|
+
from uc_integration.backend import get_admin_role
|
|
20
|
+
try:
|
|
21
|
+
decoded = decode_token(token)
|
|
22
|
+
except Exception:
|
|
23
|
+
return False
|
|
24
|
+
roles = decoded.get('role')
|
|
25
|
+
admin_role = get_admin_role()
|
|
26
|
+
if isinstance(roles, list):
|
|
27
|
+
if 'uc_integration' not in roles and admin_role not in roles:
|
|
28
|
+
return False
|
|
29
|
+
elif roles not in ('uc_integration', admin_role):
|
|
30
|
+
return False
|
|
31
|
+
identity = json.loads(decoded['sub'])
|
|
32
|
+
g.uc_user_id = identity.get('username') or str(identity.get('id'))
|
|
33
|
+
g.uc_identity = identity
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def public_api(f):
|
|
38
|
+
"""业务 API 仅认本地 JWT(uc_integration 或子系统管理员角色)。"""
|
|
39
|
+
|
|
40
|
+
@wraps(f)
|
|
41
|
+
def wrapper(*args, **kwargs):
|
|
42
|
+
token = _extract_bearer_token()
|
|
43
|
+
if not token:
|
|
44
|
+
abort(401, description=gettext('请先登录'))
|
|
45
|
+
if not _try_admin_jwt(token):
|
|
46
|
+
abort(401, description=gettext('请先登录'))
|
|
47
|
+
return f(*args, **kwargs)
|
|
48
|
+
|
|
49
|
+
return wrapper
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# -*- coding: utf-8
|
|
2
|
+
"""宿主依赖惰性解析:避免包安装时强绑 model.model。"""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_db():
|
|
7
|
+
"""返回宿主 Flask-SQLAlchemy db 实例。"""
|
|
8
|
+
from model.model import db
|
|
9
|
+
return db
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_uc_user_model():
|
|
13
|
+
"""返回宿主 UcUser ORM 类。"""
|
|
14
|
+
from model.model import UcUser
|
|
15
|
+
return UcUser
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_uc_models():
|
|
19
|
+
"""返回门店 scope 所需的 UC 镜像表模型。"""
|
|
20
|
+
from model.model import UcDept, UcDeptUserRelation, UcStore, UcUser, db
|
|
21
|
+
return {
|
|
22
|
+
'UcDept': UcDept,
|
|
23
|
+
'UcDeptUserRelation': UcDeptUserRelation,
|
|
24
|
+
'UcStore': UcStore,
|
|
25
|
+
'UcUser': UcUser,
|
|
26
|
+
'db': db,
|
|
27
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# -*- coding: utf-8
|
|
2
|
+
"""SSO 换票接口 Schema 定义。"""
|
|
3
|
+
from marshmallow import Schema, fields
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SsoExchangeBodySchema(Schema):
|
|
7
|
+
code = fields.Str(metadata={'description': 'UC 跨域回跳一次性授权码'})
|
|
8
|
+
accessToken = fields.Str(metadata={'description': 'UC accessToken(同域兜底)'})
|
|
9
|
+
refreshToken = fields.Str(metadata={'description': 'UC refreshToken(可选)'})
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SsoRefreshBodySchema(Schema):
|
|
13
|
+
refreshToken = fields.Str(required=True, metadata={'description': 'UC refreshToken'})
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SsoExchangeResponseSchema(Schema):
|
|
17
|
+
id = fields.Int()
|
|
18
|
+
username = fields.Str()
|
|
19
|
+
name = fields.Str()
|
|
20
|
+
role = fields.Str(metadata={'description': '业务角色 manager/employee'})
|
|
21
|
+
access_token = fields.Str()
|
|
22
|
+
refresh_token = fields.Str()
|
|
23
|
+
uc_access_token = fields.Str()
|
|
24
|
+
uc_refresh_token = fields.Str()
|
|
25
|
+
store_id = fields.Int(metadata={'description': '当前门店 ID'})
|
|
26
|
+
storeId = fields.Int(metadata={'description': '当前门店 ID(驼峰)'})
|
|
27
|
+
store_ids = fields.List(fields.Int(), metadata={'description': '可访问门店 ID 列表'})
|
|
28
|
+
storeIds = fields.List(fields.Int(), metadata={'description': '可访问门店 ID 列表(驼峰)'})
|
|
29
|
+
store_name = fields.Str(metadata={'description': '门店名称(来自 uc_store)'})
|
|
30
|
+
uc = fields.Dict(
|
|
31
|
+
metadata={'description': 'UC 换票/校验原样返回(完整透传,不做字段裁剪)'},
|
|
32
|
+
)
|
|
File without changes
|