feat: 租户级企微配置改造
All checks were successful
continuous-integration/drone/push Build is passing

- 新增 platform_tenant_wechat_apps 表(租户企微应用配置)
- platform_apps 增加 require_jssdk 字段
- platform_tenant_apps 增加 wechat_app_id 关联字段
- 新增企微应用管理 API 和页面
- 应用管理页面增加 JS-SDK 开关
- 应用配置页面增加企微应用选择
This commit is contained in:
111
2026-01-23 19:05:00 +08:00
parent f815b29c51
commit c4bd7c8251
13 changed files with 1198 additions and 580 deletions

View File

@@ -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 [],