# ============================================================================= # 企微IT智能服务台 — 开发模式 Mock 登录 # ============================================================================= # ⚠️ 警告:此模块只在 DEV_MODE=true 时可用 # - 仅供本地开发 / 集成测试使用 # - 生产环境(DEV_MODE 未设置或 false)会直接 403 # - 部署前必须确认 .env / .env.production 没有 DEV_MODE=true # 用法: # GET /api/dev/login?userid=dev-user-001&name=测试&role=user # GET /api/dev/users # 列出所有预设 dev 用户 # ============================================================================= import logging import os from datetime import datetime from typing import Optional import redis.asyncio as aioredis from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.database import get_db from app.dependencies import get_redis from app.models.employee import Employee from app.services.token_service import TokenService from app.utils.response import success_response logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/dev", tags=["dev-mock"]) def _dev_mode_enabled() -> bool: """检查是否启用了开发模式。 优先级 (P0-NEW12 修复后,2026-08-12,与 main.py:_is_dev_mode 保持一致): 1. app_env 优先: production 永远 = 非 dev (即使 DEV_MODE=true 也无效) 背景: 预生产服历史配置 APP_ENV=production + DEV_MODE=true 不一致, 导致容器间网络可达 backend:8000 时仍能拿到 /api/dev/* 端点。 2. 非 production 环境(development / staging)下: - DEV_MODE=true 环境变量 → 启用 - settings.dev_mode 字段 → 启用 3. 其余 → 非 dev """ # 优先级 1: app_env 决定总体框架(production 永远不是 dev) app_env = getattr(settings, "app_env", "production").lower() if app_env == "production": return False # 优先级 2: 非生产环境才看显式开关 env_val = os.getenv("DEV_MODE", "false").lower() == "true" if env_val: return True # 兜底:从 settings 读 if hasattr(settings, "dev_mode") and getattr(settings, "dev_mode", False): return True return False # ----------------------------------------------------------------------------- # 预设 dev 用户(便于测试不同角色) # ----------------------------------------------------------------------------- PRESET_DEV_USERS = [ {"userid": "dev-user-001", "name": "张三(普通员工)", "role": "user", "department": "财务部"}, {"userid": "dev-agent-001", "name": "李四(IT 坐席)", "role": "agent", "department": "信息技术部"}, {"userid": "dev-supervisor-001", "name": "王五(部门主管)", "role": "supervisor", "department": "信息技术部"}, {"userid": "dev-security-001", "name": "赵六(安全团队)", "role": "security", "department": "信息安全部"}, {"userid": "dev-admin-001", "name": "钱七(系统管理员)", "role": "admin", "department": "信息技术部"}, {"userid": "dev-multi-001", "name": "周八(多角色测试)", "role": "user,agent,supervisor", "department": "测试部"}, ] # ----------------------------------------------------------------------------- # GET /api/dev/login — Mock 登录(返回 token) # ----------------------------------------------------------------------------- @router.get("/login") async def dev_login( userid: str = Query("dev-user-001", description="用户 ID(模拟企微 userid)"), name: str = Query("开发测试用户", description="用户姓名"), role: str = Query("user", description="角色:user/agent/admin/supervisor/security,多个用逗号分隔"), department: str = Query("信息技术部", description="部门"), avatar: Optional[str] = Query(None, description="头像 URL(可选)"), redis: aioredis.Redis = Depends(get_redis), db: AsyncSession = Depends(get_db), ): """开发模式 Mock 登录。 用法: GET /api/dev/login?userid=dev-agent-001&name=李四&role=agent 返回: { "code": 0, "data": { "token": "abc123...", "user": { "userid": "...", "name": "...", "roles": [...] } } } """ if not _dev_mode_enabled(): logger.warning("🚨 /api/dev/login 被调用但 DEV_MODE 未启用,返回 403") raise HTTPException( status_code=403, detail="DEV_MODE not enabled. Set DEV_MODE=true in .env.dev to use this endpoint." ) # 解析多角色 roles = [r.strip() for r in role.split(",") if r.strip()] if not roles: roles = ["user"] # 调 TokenService 创建 token(走完全真实的 token 流程) token_service = TokenService(redis) token = await token_service.create_token( employee_id=userid, name=name, roles=roles, department=department, avatar=avatar or "", login_source="dev", ) # Mock 登录时同步写入 employees 表(便于坐席端显示员工姓名) from sqlalchemy import select stmt = select(Employee).where(Employee.employee_id == userid) result = await db.execute(stmt) employee = result.scalars().first() if employee: # 更新已有记录 employee.name = name employee.department = department employee.avatar = avatar or "" employee.avatar_updated_at = datetime.utcnow() else: # 创建新记录 employee = Employee( corp_id=settings.wecom_corp_id, employee_id=userid, name=name, department=department, position="", avatar=avatar or "", avatar_updated_at=datetime.utcnow(), ) db.add(employee) # 清 Redis 头像缓存,确保下次读取数据库最新头像(要求 C:清缓存 + 更新时间一致) if avatar: try: await redis.delete(f"employee:avatar:{userid}") except Exception as e: logger.warning(f"删除头像Redis缓存失败: userid={userid}, error={e}") await db.commit() logger.info(f"🧪 [DEV] 同步员工信息到 employees 表: userid={userid}, name={name}") logger.info(f"🧪 [DEV] Mock 登录成功: userid={userid}, roles={roles}") return success_response(data={ "token": token, "user": { "userid": userid, "name": name, "department": department, "avatar": avatar or "", "roles": roles, "login_source": "dev", }, }) # ----------------------------------------------------------------------------- # GET /api/dev/users — 列出所有预设 dev 用户 # ----------------------------------------------------------------------------- @router.get("/users") async def dev_list_users(): """列出所有预设 dev 用户(便于前端测试用)。""" if not _dev_mode_enabled(): raise HTTPException(status_code=403, detail="DEV_MODE not enabled") return success_response(data=PRESET_DEV_USERS) # ----------------------------------------------------------------------------- # GET /api/dev/health — 检查 dev 模式状态 # ----------------------------------------------------------------------------- @router.get("/health") async def dev_health(): """检查 dev 模式是否启用 + 关键依赖。""" if not _dev_mode_enabled(): raise HTTPException(status_code=403, detail="DEV_MODE not enabled") return success_response(data={ "dev_mode": True, "env": os.getenv("APP_ENV", "unknown"), "database_url": os.getenv("DATABASE_URL", "not set")[:50] + "...", "redis_url": os.getenv("REDIS_URL", "not set"), "preset_users": len(PRESET_DEV_USERS), })