Files
iot-device-management-service/app/routers/yudao_auth.py

358 lines
10 KiB
Python
Raw Normal View History

"""
芋道认证兼容路由
提供与芋道主平台一致的认证接口便于前端统一对接
测试阶段 (dev_mode=True)
- 本地验证 admin/admin
- 返回模拟的 Token 和用户信息
生产阶段 (dev_mode=False)
- 转发到芋道主平台验证
- 返回真实的 Token 和用户信息
API 路径规范
- /admin-api/system/auth/login - 登录
- /admin-api/system/auth/logout - 登出
- /admin-api/system/auth/refresh-token - 刷新令牌
- /admin-api/system/auth/get-permission-info - 获取权限信息
"""
from fastapi import APIRouter, HTTPException, Header, Query
from pydantic import BaseModel
from typing import Optional
import time
import secrets
from app.config import settings
from app.yudao_compat import YudaoResponse
router = APIRouter(prefix="/admin-api/system", tags=["系统认证"])
class LoginRequest(BaseModel):
"""登录请求"""
username: str
password: str
captchaVerification: Optional[str] = None
# 社交登录参数(预留)
socialType: Optional[int] = None
socialCode: Optional[str] = None
socialState: Optional[str] = None
@router.post("/auth/login")
async def login(req: LoginRequest):
"""
登录接口
测试阶段验证 admin/admin返回模拟 Token
生产阶段转发到芋道主平台验证
Returns:
{
"code": 0,
"data": {
"userId": 1,
"accessToken": "xxx",
"refreshToken": "xxx",
"expiresTime": 1234567890000
},
"msg": "success"
}
"""
if settings.app.dev_mode:
# 测试阶段:简单验证 admin/admin
if req.username == "admin" and req.password == "admin":
# 生成模拟 Token
access_token = secrets.token_urlsafe(32)
refresh_token = secrets.token_urlsafe(32)
return YudaoResponse.success({
"userId": 1,
"accessToken": access_token,
"refreshToken": refresh_token,
"expiresTime": int(time.time() * 1000) + 24 * 60 * 60 * 1000 # 24小时后过期
})
raise HTTPException(status_code=401, detail="用户名或密码错误")
# TODO: 生产阶段转发到芋道主平台
# response = requests.post(
# f"{settings.yudao.base_url}/system/auth/login",
# json=req.dict()
# )
# return response.json()
raise HTTPException(status_code=501, detail="生产模式认证未实现,请配置芋道主平台地址")
@router.get("/auth/get-permission-info")
async def get_permission_info(
authorization: Optional[str] = Header(None, alias="Authorization")
):
"""
获取当前用户权限信息
测试阶段返回超级管理员权限
生产阶段验证 Token 并获取真实权限
Returns:
{
"code": 0,
"data": {
"user": { "id": 1, "nickname": "超级管理员", ... },
"roles": ["super_admin"],
"permissions": ["*:*:*"]
},
"msg": "success"
}
"""
if settings.app.dev_mode:
return YudaoResponse.success({
"user": {
"id": 1,
"nickname": "超级管理员",
"avatar": "",
"deptId": 1,
"homePath": "/aiot/alarm/list"
},
"roles": ["super_admin"],
"permissions": ["*:*:*"], # 超级权限,拥有所有操作权限
"menus": _build_aiot_menus()
})
# 生产阶段:验证 Token
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="请先登录")
# TODO: 生产阶段调用芋道主平台验证
# token = authorization.replace("Bearer ", "")
# response = requests.get(
# f"{settings.yudao.base_url}/system/auth/get-permission-info",
# headers={"Authorization": f"Bearer {token}"}
# )
# return response.json()
raise HTTPException(status_code=501, detail="生产模式认证未实现")
@router.post("/auth/logout")
async def logout(
authorization: Optional[str] = Header(None, alias="Authorization")
):
"""
登出接口
清除用户登录状态
"""
# 测试阶段和生产阶段都直接返回成功
# 实际的 Token 失效由前端清除 localStorage 完成
return YudaoResponse.success(True)
@router.post("/auth/refresh-token")
async def refresh_token(
refreshToken: str = Query(..., description="刷新令牌")
):
"""
刷新访问令牌
使用 refreshToken 获取新的 accessToken
Returns:
{
"code": 0,
"data": {
"accessToken": "xxx",
"refreshToken": "xxx",
"expiresTime": 1234567890000
},
"msg": "success"
}
"""
if settings.app.dev_mode:
# 测试阶段:直接返回新 Token
new_access_token = secrets.token_urlsafe(32)
new_refresh_token = secrets.token_urlsafe(32)
return YudaoResponse.success({
"accessToken": new_access_token,
"refreshToken": new_refresh_token,
"expiresTime": int(time.time() * 1000) + 24 * 60 * 60 * 1000
})
# TODO: 生产阶段调用芋道主平台刷新
raise HTTPException(status_code=501, detail="生产模式认证未实现")
# ==================== 租户相关接口(预留) ====================
@router.get("/tenant/simple-list")
async def get_tenant_list():
"""
获取租户列表
测试阶段返回空列表前端 VITE_APP_TENANT_ENABLE=false 不会调用
"""
return YudaoResponse.success([])
@router.get("/tenant/get-by-website")
async def get_tenant_by_website(website: str = Query(...)):
"""
根据域名获取租户
测试阶段返回默认租户
"""
return YudaoResponse.success({
"id": 1,
"name": "默认租户"
})
# ==================== 验证码相关接口(预留) ====================
@router.post("/captcha/get")
async def get_captcha(data: dict):
"""
获取验证码
测试阶段前端 VITE_APP_CAPTCHA_ENABLE=false 不会调用
"""
return YudaoResponse.success({
"enable": False
})
@router.post("/captcha/check")
async def check_captcha(data: dict):
"""
校验验证码
测试阶段直接返回成功
"""
return YudaoResponse.success(True)
# ==================== 字典数据接口(预留) ====================
@router.get("/dict-data/simple-list")
async def get_dict_data_simple_list():
"""
获取字典数据列表
测试阶段返回空列表
"""
return YudaoResponse.success([])
@router.get("/notify-message/get-unread-count")
async def get_unread_message_count():
"""
获取未读消息数量
测试阶段返回 0
"""
return YudaoResponse.success(0)
# ==================== 辅助函数 ====================
def _build_aiot_menus():
"""
构建 AIoT 菜单树
芋道前端 (accessMode='backend') 期望的菜单格式
- id, parentId, name, path, component, icon, sort, visible, keepAlive
- 顶层 parentId=0, component='Layout'
- 叶子节点 component views/ 下的路径
"""
return [
{
"id": 2000,
"parentId": 0,
"name": "AIoT 智能平台",
"path": "aiot",
"component": "Layout",
"icon": "ep:monitor",
"sort": 10,
"visible": True,
"keepAlive": True,
"children": [
{
"id": 2010,
"parentId": 2000,
"name": "告警列表",
"path": "alarm/list",
"component": "aiot/alarm/list/index",
"componentName": "AiotAlarmList",
"icon": "ep:warning",
"sort": 1,
"visible": True,
"keepAlive": True,
},
{
"id": 2011,
"parentId": 2000,
"name": "摄像头告警汇总",
"path": "alarm/summary",
"component": "aiot/alarm/summary/index",
"componentName": "AiotAlarmSummary",
"icon": "ep:data-analysis",
"sort": 2,
"visible": True,
"keepAlive": True,
},
{
"id": 2020,
"parentId": 2000,
"name": "摄像头管理",
"path": "device/camera",
"component": "aiot/device/camera/index",
"componentName": "AiotDeviceCamera",
"icon": "ep:camera",
"sort": 3,
"visible": True,
"keepAlive": True,
},
{
"id": 2021,
"parentId": 2000,
"name": "ROI 区域配置",
"path": "device/roi",
"component": "aiot/device/roi/index",
"componentName": "AiotDeviceRoi",
"icon": "ep:aim",
"sort": 4,
"visible": True,
"keepAlive": True,
},
{
"id": 2030,
"parentId": 2000,
"name": "实时视频",
"path": "video/live",
"component": "aiot/video/live/index",
"componentName": "AiotVideoLive",
"icon": "ep:video-camera",
"sort": 5,
"visible": True,
"keepAlive": True,
},
{
"id": 2040,
"parentId": 2000,
"name": "边缘节点管理",
"path": "edge/node",
"component": "aiot/edge/node/index",
"componentName": "AiotEdgeNode",
"icon": "ep:cpu",
"sort": 6,
"visible": True,
"keepAlive": True,
},
],
}
]