"""租户应用配置路由""" import json import secrets from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from typing import Optional, List from sqlalchemy.orm import Session from ..database import get_db from ..models.tenant_app import TenantApp from ..models.app import App from .auth import get_current_user, require_operator from ..models.user import User router = APIRouter(prefix="/tenant-apps", tags=["应用配置"]) # Schemas class CustomConfigItem(BaseModel): """自定义配置项""" key: str # 配置键 value: str # 配置值 remark: Optional[str] = None # 备注说明 class TenantAppCreate(BaseModel): tenant_id: str app_code: str = "tools" app_name: Optional[str] = None wechat_app_id: Optional[int] = None # 关联的企微应用ID access_token: Optional[str] = None # 如果不传则自动生成 allowed_origins: Optional[List[str]] = None allowed_tools: Optional[List[str]] = None custom_configs: Optional[List[CustomConfigItem]] = None # 自定义配置 class TenantAppUpdate(BaseModel): app_name: Optional[str] = None wechat_app_id: Optional[int] = None # 关联的企微应用ID access_token: Optional[str] = None allowed_origins: Optional[List[str]] = None allowed_tools: Optional[List[str]] = None custom_configs: Optional[List[CustomConfigItem]] = None # 自定义配置 status: Optional[int] = None # API Endpoints @router.get("") async def list_tenant_apps( page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=1000), tenant_id: Optional[str] = None, app_code: Optional[str] = None, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取应用配置列表""" query = db.query(TenantApp) if tenant_id: query = query.filter(TenantApp.tenant_id == tenant_id) if app_code: query = query.filter(TenantApp.app_code == app_code) total = query.count() apps = query.order_by(TenantApp.id.desc()).offset((page - 1) * size).limit(size).all() return { "total": total, "page": page, "size": size, "items": [format_tenant_app(app, mask_secret=True, db=db) for app in apps] } @router.get("/{app_id}") async def get_tenant_app( app_id: int, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取应用配置详情""" app = db.query(TenantApp).filter(TenantApp.id == app_id).first() if not app: raise HTTPException(status_code=404, detail="应用配置不存在") return format_tenant_app(app, mask_secret=True, db=db) @router.post("") async def create_tenant_app( data: TenantAppCreate, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """创建应用配置""" # 验证 app_code 是否存在于应用管理中 app_exists = db.query(App).filter(App.app_code == data.app_code, App.status == 1).first() if not app_exists: raise HTTPException(status_code=400, detail=f"应用 '{data.app_code}' 不存在,请先在应用管理中创建") # 检查是否重复 exists = db.query(TenantApp).filter( TenantApp.tenant_id == data.tenant_id, TenantApp.app_code == data.app_code ).first() if exists: raise HTTPException(status_code=400, detail="该租户应用配置已存在") # 自动生成 access_token access_token = data.access_token or secrets.token_hex(32) app = TenantApp( tenant_id=data.tenant_id, app_code=data.app_code, app_name=data.app_name, wechat_app_id=data.wechat_app_id, access_token=access_token, allowed_origins=json.dumps(data.allowed_origins) if data.allowed_origins else None, allowed_tools=json.dumps(data.allowed_tools) if data.allowed_tools else None, custom_configs=json.dumps([c.model_dump() for c in data.custom_configs], ensure_ascii=False) if data.custom_configs else None, status=1 ) db.add(app) db.commit() db.refresh(app) return {"success": True, "id": app.id, "access_token": access_token} @router.put("/{app_id}") async def update_tenant_app( app_id: int, data: TenantAppUpdate, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """更新应用配置""" app = db.query(TenantApp).filter(TenantApp.id == app_id).first() if not app: raise HTTPException(status_code=404, detail="应用配置不存在") update_data = data.model_dump(exclude_unset=True) # 处理 JSON 字段 if 'allowed_origins' in update_data: update_data['allowed_origins'] = json.dumps(update_data['allowed_origins']) if update_data['allowed_origins'] else None if 'allowed_tools' in update_data: update_data['allowed_tools'] = json.dumps(update_data['allowed_tools']) if update_data['allowed_tools'] else None if 'custom_configs' in update_data: if update_data['custom_configs']: update_data['custom_configs'] = json.dumps( [c.model_dump() if hasattr(c, 'model_dump') else c for c in update_data['custom_configs']], ensure_ascii=False ) else: update_data['custom_configs'] = None for key, value in update_data.items(): setattr(app, key, value) db.commit() return {"success": True} @router.delete("/{app_id}") async def delete_tenant_app( app_id: int, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """删除应用配置""" app = db.query(TenantApp).filter(TenantApp.id == app_id).first() if not app: raise HTTPException(status_code=404, detail="应用配置不存在") db.delete(app) db.commit() return {"success": True} @router.get("/{app_id}/token") async def get_token( app_id: int, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """获取真实的 access_token(仅管理员可用)""" app = db.query(TenantApp).filter(TenantApp.id == app_id).first() if not app: raise HTTPException(status_code=404, detail="应用配置不存在") # 获取应用的 base_url app_info = db.query(App).filter(App.app_code == app.app_code).first() base_url = app_info.base_url if app_info else "" return { "access_token": app.access_token, "base_url": base_url } @router.post("/{app_id}/regenerate-token") async def regenerate_token( app_id: int, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """重新生成 access_token""" app = db.query(TenantApp).filter(TenantApp.id == app_id).first() if not app: raise HTTPException(status_code=404, detail="应用配置不存在") new_token = secrets.token_hex(32) app.access_token = new_token db.commit() return {"success": True, "access_token": new_token} def format_tenant_app(app: TenantApp, mask_secret: bool = True, db: Session = None) -> dict: """格式化应用配置""" # 获取关联的企微应用信息 wechat_app_info = None if app.wechat_app_id and db: from ..models.tenant_wechat_app import TenantWechatApp wechat_app = db.query(TenantWechatApp).filter(TenantWechatApp.id == app.wechat_app_id).first() if wechat_app: wechat_app_info = { "id": wechat_app.id, "name": wechat_app.name, "corp_id": wechat_app.corp_id, "agent_id": wechat_app.agent_id } result = { "id": app.id, "tenant_id": app.tenant_id, "app_code": app.app_code, "app_name": app.app_name, "wechat_app_id": app.wechat_app_id, "wechat_app": wechat_app_info, "access_token": "******" if mask_secret and app.access_token else app.access_token, "allowed_origins": json.loads(app.allowed_origins) if app.allowed_origins else [], "allowed_tools": json.loads(app.allowed_tools) if app.allowed_tools else [], "custom_configs": json.loads(app.custom_configs) if app.custom_configs else [], "status": app.status, "created_at": app.created_at, "updated_at": app.updated_at } return result