- 新增 platform_tenant_wechat_apps 表(租户企微应用配置) - platform_apps 增加 require_jssdk 字段 - platform_tenant_apps 增加 wechat_app_id 关联字段 - 新增企微应用管理 API 和页面 - 应用管理页面增加 JS-SDK 开关 - 应用配置页面增加企微应用选择
This commit is contained in:
@@ -30,6 +30,7 @@ class AppCreate(BaseModel):
|
||||
base_url: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
tools: Optional[List[ToolItem]] = None
|
||||
require_jssdk: bool = False
|
||||
|
||||
|
||||
class AppUpdate(BaseModel):
|
||||
@@ -38,6 +39,7 @@ class AppUpdate(BaseModel):
|
||||
base_url: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
tools: Optional[List[ToolItem]] = None
|
||||
require_jssdk: Optional[bool] = None
|
||||
status: Optional[int] = None
|
||||
|
||||
|
||||
@@ -117,6 +119,7 @@ async def create_app(
|
||||
base_url=data.base_url,
|
||||
description=data.description,
|
||||
tools=json.dumps([t.model_dump() for t in data.tools], ensure_ascii=False) if data.tools else None,
|
||||
require_jssdk=1 if data.require_jssdk else 0,
|
||||
status=1
|
||||
)
|
||||
db.add(app)
|
||||
@@ -147,6 +150,10 @@ async def update_app(
|
||||
else:
|
||||
update_data['tools'] = None
|
||||
|
||||
# 处理 require_jssdk
|
||||
if 'require_jssdk' in update_data:
|
||||
update_data['require_jssdk'] = 1 if update_data['require_jssdk'] else 0
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(app, key, value)
|
||||
|
||||
@@ -261,6 +268,7 @@ def format_app(app: App) -> dict:
|
||||
"base_url": app.base_url,
|
||||
"description": app.description,
|
||||
"tools": json.loads(app.tools) if app.tools else [],
|
||||
"require_jssdk": bool(app.require_jssdk),
|
||||
"status": app.status,
|
||||
"created_at": app.created_at,
|
||||
"updated_at": app.updated_at
|
||||
|
||||
@@ -10,7 +10,6 @@ from ..database import get_db
|
||||
from ..models.tenant_app import TenantApp
|
||||
from .auth import get_current_user, require_operator
|
||||
from ..models.user import User
|
||||
from ..services.crypto import encrypt_config, decrypt_config
|
||||
|
||||
router = APIRouter(prefix="/tenant-apps", tags=["应用配置"])
|
||||
|
||||
@@ -21,9 +20,7 @@ class TenantAppCreate(BaseModel):
|
||||
tenant_id: str
|
||||
app_code: str = "tools"
|
||||
app_name: Optional[str] = None
|
||||
wechat_corp_id: Optional[str] = None
|
||||
wechat_agent_id: Optional[str] = None
|
||||
wechat_secret: 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
|
||||
@@ -31,9 +28,7 @@ class TenantAppCreate(BaseModel):
|
||||
|
||||
class TenantAppUpdate(BaseModel):
|
||||
app_name: Optional[str] = None
|
||||
wechat_corp_id: Optional[str] = None
|
||||
wechat_agent_id: Optional[str] = None
|
||||
wechat_secret: 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
|
||||
@@ -66,7 +61,7 @@ async def list_tenant_apps(
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"items": [format_tenant_app(app, mask_secret=True) for app in apps]
|
||||
"items": [format_tenant_app(app, mask_secret=True, db=db) for app in apps]
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +76,7 @@ async def get_tenant_app(
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用配置不存在")
|
||||
|
||||
return format_tenant_app(app, mask_secret=True)
|
||||
return format_tenant_app(app, mask_secret=True, db=db)
|
||||
|
||||
|
||||
@router.post("")
|
||||
@@ -102,18 +97,11 @@ async def create_tenant_app(
|
||||
# 自动生成 access_token
|
||||
access_token = data.access_token or secrets.token_hex(32)
|
||||
|
||||
# 加密 wechat_secret
|
||||
wechat_secret_encrypted = None
|
||||
if data.wechat_secret:
|
||||
wechat_secret_encrypted = encrypt_config(data.wechat_secret)
|
||||
|
||||
app = TenantApp(
|
||||
tenant_id=data.tenant_id,
|
||||
app_code=data.app_code,
|
||||
app_name=data.app_name,
|
||||
wechat_corp_id=data.wechat_corp_id,
|
||||
wechat_agent_id=data.wechat_agent_id,
|
||||
wechat_secret_encrypted=wechat_secret_encrypted,
|
||||
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,
|
||||
@@ -140,12 +128,6 @@ async def update_tenant_app(
|
||||
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
|
||||
# 处理 wechat_secret
|
||||
if 'wechat_secret' in update_data:
|
||||
if update_data['wechat_secret']:
|
||||
app.wechat_secret_encrypted = encrypt_config(update_data['wechat_secret'])
|
||||
del update_data['wechat_secret']
|
||||
|
||||
# 处理 JSON 字段
|
||||
if 'allowed_origins' in update_data:
|
||||
update_data['allowed_origins'] = json.dumps(update_data['allowed_origins']) if update_data['allowed_origins'] else None
|
||||
@@ -194,34 +176,28 @@ async def regenerate_token(
|
||||
return {"success": True, "access_token": new_token}
|
||||
|
||||
|
||||
@router.get("/{app_id}/wechat-secret")
|
||||
async def get_wechat_secret(
|
||||
app_id: int,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取解密的 wechat_secret(仅操作员以上)"""
|
||||
app = db.query(TenantApp).filter(TenantApp.id == app_id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="应用配置不存在")
|
||||
|
||||
secret = None
|
||||
if app.wechat_secret_encrypted:
|
||||
secret = decrypt_config(app.wechat_secret_encrypted)
|
||||
|
||||
return {"wechat_secret": secret}
|
||||
|
||||
|
||||
def format_tenant_app(app: TenantApp, mask_secret: bool = True) -> dict:
|
||||
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_corp_id": app.wechat_corp_id,
|
||||
"wechat_agent_id": app.wechat_agent_id,
|
||||
"has_wechat_secret": bool(app.wechat_secret_encrypted),
|
||||
"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 [],
|
||||
|
||||
198
backend/app/routers/tenant_wechat_apps.py
Normal file
198
backend/app/routers/tenant_wechat_apps.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""租户企业微信应用配置路由"""
|
||||
import json
|
||||
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_wechat_app import TenantWechatApp
|
||||
from .auth import get_current_user, require_operator
|
||||
from ..models.user import User
|
||||
from ..services.crypto import encrypt_config, decrypt_config
|
||||
|
||||
router = APIRouter(prefix="/tenant-wechat-apps", tags=["租户企微应用"])
|
||||
|
||||
|
||||
# Schemas
|
||||
|
||||
class TenantWechatAppCreate(BaseModel):
|
||||
tenant_id: str
|
||||
name: str
|
||||
corp_id: str
|
||||
agent_id: str
|
||||
secret: Optional[str] = None # 明文,存储时加密
|
||||
|
||||
|
||||
class TenantWechatAppUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
corp_id: Optional[str] = None
|
||||
agent_id: Optional[str] = None
|
||||
secret: Optional[str] = None
|
||||
status: Optional[int] = None
|
||||
|
||||
|
||||
# API Endpoints
|
||||
|
||||
@router.get("")
|
||||
async def list_tenant_wechat_apps(
|
||||
page: int = Query(1, ge=1),
|
||||
size: int = Query(20, ge=1, le=100),
|
||||
tenant_id: Optional[str] = None,
|
||||
user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取租户企微应用列表"""
|
||||
query = db.query(TenantWechatApp)
|
||||
|
||||
if tenant_id:
|
||||
query = query.filter(TenantWechatApp.tenant_id == tenant_id)
|
||||
|
||||
total = query.count()
|
||||
apps = query.order_by(TenantWechatApp.id.desc()).offset((page - 1) * size).limit(size).all()
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"items": [format_wechat_app(app) for app in apps]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/by-tenant/{tenant_id}")
|
||||
async def list_by_tenant(
|
||||
tenant_id: str,
|
||||
user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取指定租户的所有企微应用(用于下拉选择)"""
|
||||
apps = db.query(TenantWechatApp).filter(
|
||||
TenantWechatApp.tenant_id == tenant_id,
|
||||
TenantWechatApp.status == 1
|
||||
).order_by(TenantWechatApp.id.asc()).all()
|
||||
|
||||
return [{"id": app.id, "name": app.name, "corp_id": app.corp_id, "agent_id": app.agent_id} for app in apps]
|
||||
|
||||
|
||||
@router.get("/{app_id}")
|
||||
async def get_tenant_wechat_app(
|
||||
app_id: int,
|
||||
user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取企微应用详情"""
|
||||
app = db.query(TenantWechatApp).filter(TenantWechatApp.id == app_id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="企微应用不存在")
|
||||
|
||||
return format_wechat_app(app)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_tenant_wechat_app(
|
||||
data: TenantWechatAppCreate,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建企微应用"""
|
||||
# 加密 secret
|
||||
secret_encrypted = None
|
||||
if data.secret:
|
||||
secret_encrypted = encrypt_config(data.secret)
|
||||
|
||||
app = TenantWechatApp(
|
||||
tenant_id=data.tenant_id,
|
||||
name=data.name,
|
||||
corp_id=data.corp_id,
|
||||
agent_id=data.agent_id,
|
||||
secret_encrypted=secret_encrypted,
|
||||
status=1
|
||||
)
|
||||
db.add(app)
|
||||
db.commit()
|
||||
db.refresh(app)
|
||||
|
||||
return {"success": True, "id": app.id}
|
||||
|
||||
|
||||
@router.put("/{app_id}")
|
||||
async def update_tenant_wechat_app(
|
||||
app_id: int,
|
||||
data: TenantWechatAppUpdate,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新企微应用"""
|
||||
app = db.query(TenantWechatApp).filter(TenantWechatApp.id == app_id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="企微应用不存在")
|
||||
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
|
||||
# 处理 secret 加密
|
||||
if 'secret' in update_data:
|
||||
if update_data['secret']:
|
||||
app.secret_encrypted = encrypt_config(update_data['secret'])
|
||||
del update_data['secret']
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(app, key, value)
|
||||
|
||||
db.commit()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.delete("/{app_id}")
|
||||
async def delete_tenant_wechat_app(
|
||||
app_id: int,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除企微应用"""
|
||||
app = db.query(TenantWechatApp).filter(TenantWechatApp.id == app_id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="企微应用不存在")
|
||||
|
||||
# 检查是否有租户应用在使用
|
||||
from ..models.tenant_app import TenantApp
|
||||
usage_count = db.query(TenantApp).filter(TenantApp.wechat_app_id == app_id).count()
|
||||
if usage_count > 0:
|
||||
raise HTTPException(status_code=400, detail=f"有 {usage_count} 个应用配置正在使用此企微应用,无法删除")
|
||||
|
||||
db.delete(app)
|
||||
db.commit()
|
||||
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.get("/{app_id}/secret")
|
||||
async def get_wechat_secret(
|
||||
app_id: int,
|
||||
user: User = Depends(require_operator),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取解密的 secret(仅操作员以上)"""
|
||||
app = db.query(TenantWechatApp).filter(TenantWechatApp.id == app_id).first()
|
||||
if not app:
|
||||
raise HTTPException(status_code=404, detail="企微应用不存在")
|
||||
|
||||
secret = None
|
||||
if app.secret_encrypted:
|
||||
secret = decrypt_config(app.secret_encrypted)
|
||||
|
||||
return {"secret": secret}
|
||||
|
||||
|
||||
def format_wechat_app(app: TenantWechatApp) -> dict:
|
||||
"""格式化企微应用数据"""
|
||||
return {
|
||||
"id": app.id,
|
||||
"tenant_id": app.tenant_id,
|
||||
"name": app.name,
|
||||
"corp_id": app.corp_id,
|
||||
"agent_id": app.agent_id,
|
||||
"has_secret": bool(app.secret_encrypted),
|
||||
"status": app.status,
|
||||
"created_at": app.created_at,
|
||||
"updated_at": app.updated_at
|
||||
}
|
||||
Reference in New Issue
Block a user