包含多个会话的累积代码变更: - backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔 - admin-web: ETL 状态页、任务管理、调度配置、登录优化 - miniprogram: 看板页面、聊天集成、UI 组件、导航更新 - etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强 - tenant-admin: 项目初始化 - db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8) - packages/shared: 枚举和工具函数更新 - tools: 数据库工具、报表生成、健康检查 - docs: PRD/架构/部署/合约文档更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
137 lines
4.2 KiB
Python
137 lines
4.2 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
角色权限服务 —— 查询用户在指定店铺下的角色和权限。
|
||
|
||
职责:
|
||
- get_user_permissions():获取用户在指定 site_id 下的权限 code 列表
|
||
- get_user_sites():获取用户关联的所有店铺及对应角色
|
||
- check_user_has_site_role():检查用户在指定 site_id 下是否有任何角色绑定
|
||
|
||
所有数据库操作使用 psycopg2 原生 SQL,不引入 ORM。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
|
||
from app.database import get_connection
|
||
from app.trace.decorators import trace_service
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
@trace_service(description_zh="获取用户权限列表", description_en="Get user permissions")
|
||
async def get_user_permissions(user_id: int, site_id: int) -> list[str]:
|
||
"""
|
||
获取用户在指定 site_id 下的权限 code 列表。
|
||
|
||
通过 user_site_roles → role_permissions → permissions 三表联查,
|
||
返回去重后的权限 code 列表。
|
||
|
||
参数:
|
||
user_id: 用户 ID
|
||
site_id: 门店 ID
|
||
|
||
返回:
|
||
权限 code 字符串列表,如 ["view_tasks", "view_board"]
|
||
"""
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor() as cur:
|
||
cur.execute(
|
||
"""
|
||
SELECT DISTINCT p.code
|
||
FROM auth.user_site_roles usr
|
||
JOIN auth.role_permissions rp ON usr.role_id = rp.role_id
|
||
JOIN auth.permissions p ON rp.permission_id = p.id
|
||
WHERE usr.user_id = %s AND usr.site_id = %s
|
||
AND usr.is_removed = false
|
||
""",
|
||
(user_id, site_id),
|
||
)
|
||
rows = cur.fetchall()
|
||
finally:
|
||
conn.close()
|
||
|
||
return [row[0] for row in rows]
|
||
|
||
|
||
@trace_service(description_zh="获取用户门店列表", description_en="Get user sites")
|
||
async def get_user_sites(user_id: int) -> list[dict]:
|
||
"""
|
||
获取用户关联的所有店铺及对应角色。
|
||
|
||
查询 user_site_roles JOIN roles,LEFT JOIN biz.sites 获取店铺名称,
|
||
按 site_id 分组聚合角色列表。
|
||
|
||
参数:
|
||
user_id: 用户 ID
|
||
|
||
返回:
|
||
[{"site_id": int, "site_name": str, "roles": [{"code": str, "name": str}]}]
|
||
"""
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor() as cur:
|
||
cur.execute(
|
||
"""
|
||
SELECT usr.site_id,
|
||
COALESCE(scm.site_name, '') AS site_name,
|
||
r.code,
|
||
r.name
|
||
FROM auth.user_site_roles usr
|
||
JOIN auth.roles r ON usr.role_id = r.id
|
||
LEFT JOIN biz.sites scm ON scm.site_id = usr.site_id
|
||
WHERE usr.user_id = %s
|
||
AND usr.is_removed = false
|
||
ORDER BY usr.site_id, r.code
|
||
""",
|
||
(user_id,),
|
||
)
|
||
rows = cur.fetchall()
|
||
finally:
|
||
conn.close()
|
||
|
||
# 按 site_id 分组聚合
|
||
sites_map: dict[int, dict] = {}
|
||
for site_id, site_name, role_code, role_name in rows:
|
||
if site_id not in sites_map:
|
||
sites_map[site_id] = {
|
||
"site_id": site_id,
|
||
"site_name": site_name,
|
||
"roles": [],
|
||
}
|
||
sites_map[site_id]["roles"].append({"code": role_code, "name": role_name})
|
||
|
||
return list(sites_map.values())
|
||
|
||
|
||
@trace_service(description_zh="检查用户门店角色", description_en="Check user site role")
|
||
async def check_user_has_site_role(user_id: int, site_id: int) -> bool:
|
||
"""
|
||
检查用户在指定 site_id 下是否有任何角色绑定。
|
||
|
||
参数:
|
||
user_id: 用户 ID
|
||
site_id: 门店 ID
|
||
|
||
返回:
|
||
True 表示有角色绑定,False 表示无
|
||
"""
|
||
conn = get_connection()
|
||
try:
|
||
with conn.cursor() as cur:
|
||
cur.execute(
|
||
"""
|
||
SELECT 1
|
||
FROM auth.user_site_roles
|
||
WHERE user_id = %s AND site_id = %s
|
||
AND is_removed = false
|
||
LIMIT 1
|
||
""",
|
||
(user_id, site_id),
|
||
)
|
||
return cur.fetchone() is not None
|
||
finally:
|
||
conn.close()
|