Files
000-platform/backend/app/routers/tenant_apps.py
Admin c1ba17f809
All checks were successful
continuous-integration/drone/push Build is passing
fix: 恢复租户应用订阅的自定义配置功能
定时任务提交(104487f)误删了 custom_configs 相关代码,现恢复:
- backend/app/models/tenant_app.py: 恢复 custom_configs 数据库字段
- backend/app/routers/tenant_apps.py: 恢复 CustomConfigItem、custom_configs 逻辑、get_token API
2026-01-29 17:47:57 +08:00

255 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""租户应用配置路由"""
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