feat: membership benefits refinement - permissions and quotas linked to plans

This commit is contained in:
qyyfw committed 2026-07-07 15:39:01 +08:00
1 parent 0886c26157
commit 839be8f97f
4 files changed
+1188 -426

No files matched your search

+572 -39
View File
@@ -1,18 +1,55 @@
""" """
会员订阅体系管理 API(管理员) 会员订阅体系管理 API(管理员)— 增强版:权益细化与权限挂钩
""" """
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import text
from ...database import get_db, SessionLocal from ...database import get_db, SessionLocal
from ...models import MembershipPlan, MembershipSubscription, User, RiskRule from ...models import MembershipPlan, MembershipSubscription, User, RiskRule
from ...auth import get_current_admin from ...auth import get_current_admin
from ...middleware.log_service import LogService from ...middleware.log_service import LogService
import uuid import uuid
from datetime import datetime import json
from datetime import datetime, date
router = APIRouter() router = APIRouter()
def _format_plan(p):
"""格式化会员计划数据"""
return {
"id": p.id,
"name": p.name,
"level": p.level,
"duration_type": p.duration_type,
"price": float(p.price) if p.price else 0,
"daily_free_tokens": p.daily_free_tokens or 0,
"features": p.features or [],
"free_app_ids": p.free_app_ids or [],
"free_model_ids": p.free_model_ids or [],
"is_active": p.is_active,
# 配额字段
"max_agents": getattr(p, 'max_agents', 5) or 5,
"max_workflows": getattr(p, 'max_workflows', 3) or 3,
"max_knowledge_bases": getattr(p, 'max_knowledge_bases', 2) or 2,
"max_storage_mb": getattr(p, 'max_storage_mb', 1024) or 1024,
"api_calls_per_day": getattr(p, 'api_calls_per_day', 100) or 100,
"api_calls_per_min": getattr(p, 'api_calls_per_min', 10) or 10,
# 权限字段
"can_publish_apps": bool(getattr(p, 'can_publish_apps', 0)),
"can_create_api_keys": bool(getattr(p, 'can_create_api_keys', 0)),
"can_use_workflow": bool(getattr(p, 'can_use_workflow', 0)),
"can_use_mcp": bool(getattr(p, 'can_use_mcp', 0)),
"can_use_code_exec": bool(getattr(p, 'can_use_code_exec', 0)),
"can_use_privacy_mode": bool(getattr(p, 'can_use_privacy_mode', 0)),
"can_use_dag": bool(getattr(p, 'can_use_dag', 0)),
"allowed_model_tiers": getattr(p, 'allowed_model_tiers', None) or ["free"],
"permissions": getattr(p, 'permissions', None) or {},
"created_at": p.created_at.isoformat() if p.created_at else None,
"updated_at": p.updated_at.isoformat() if p.updated_at else None,
}
# ============================================================ # ============================================================
# 会员计划 CRUD # 会员计划 CRUD
# ============================================================ # ============================================================
@@ -22,25 +59,9 @@ async def get_plans(
admin_user=Depends(get_current_admin), admin_user=Depends(get_current_admin),
db: Session = Depends(get_db), db: Session = Depends(get_db),
): ):
"""获取会员计划列表""" """获取会员计划列表(含权益配置)"""
plans = db.query(MembershipPlan).order_by(MembershipPlan.level).all() plans = db.query(MembershipPlan).order_by(MembershipPlan.level).all()
return [ return [_format_plan(p) for p in plans]
{
"id": p.id,
"name": p.name,
"level": p.level,
"duration_type": p.duration_type,
"price": float(p.price) if p.price else 0,
"daily_free_tokens": p.daily_free_tokens,
"features": p.features,
"free_app_ids": p.free_app_ids,
"free_model_ids": p.free_model_ids,
"is_active": p.is_active,
"created_at": p.created_at.isoformat() if p.created_at else None,
"updated_at": p.updated_at.isoformat() if p.updated_at else None,
}
for p in plans
]
@router.post("/plans") @router.post("/plans")
@@ -53,11 +74,28 @@ async def create_plan(
features: list = None, features: list = None,
free_app_ids: list = None, free_app_ids: list = None,
free_model_ids: list = None, free_model_ids: list = None,
# 配额参数
max_agents: int = 5,
max_workflows: int = 3,
max_knowledge_bases: int = 2,
max_storage_mb: int = 1024,
api_calls_per_day: int = 100,
api_calls_per_min: int = 10,
# 权限参数
can_publish_apps: bool = False,
can_create_api_keys: bool = False,
can_use_workflow: bool = False,
can_use_mcp: bool = False,
can_use_code_exec: bool = False,
can_use_privacy_mode: bool = False,
can_use_dag: bool = False,
allowed_model_tiers: list = None,
permissions: dict = None,
is_active: bool = True, is_active: bool = True,
admin_user=Depends(get_current_admin), admin_user=Depends(get_current_admin),
db: Session = Depends(get_db), db: Session = Depends(get_db),
): ):
"""创建会员计划""" """创建会员计划(含权益配置)"""
plan = MembershipPlan( plan = MembershipPlan(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
name=name, name=name,
@@ -70,6 +108,24 @@ async def create_plan(
free_model_ids=free_model_ids, free_model_ids=free_model_ids,
is_active=is_active, is_active=is_active,
) )
# 设置配额字段
plan.max_agents = max_agents
plan.max_workflows = max_workflows
plan.max_knowledge_bases = max_knowledge_bases
plan.max_storage_mb = max_storage_mb
plan.api_calls_per_day = api_calls_per_day
plan.api_calls_per_min = api_calls_per_min
# 设置权限字段
plan.can_publish_apps = can_publish_apps
plan.can_create_api_keys = can_create_api_keys
plan.can_use_workflow = can_use_workflow
plan.can_use_mcp = can_use_mcp
plan.can_use_code_exec = can_use_code_exec
plan.can_use_privacy_mode = can_use_privacy_mode
plan.can_use_dag = can_use_dag
plan.allowed_model_tiers = allowed_model_tiers or ["free"]
plan.permissions = permissions or {}
db.add(plan) db.add(plan)
db.commit() db.commit()
@@ -79,7 +135,7 @@ async def create_plan(
action="create_membership_plan", action="create_membership_plan",
target_type="membership_plan", target_type="membership_plan",
target_id=plan.id, target_id=plan.id,
detail=f"创建会员计划 {name}", detail=f"创建会员计划 {name} (level={level})",
) )
return {"message": "会员计划已创建", "id": plan.id} return {"message": "会员计划已创建", "id": plan.id}
@@ -96,15 +152,33 @@ async def update_plan(
features: list = None, features: list = None,
free_app_ids: list = None, free_app_ids: list = None,
free_model_ids: list = None, free_model_ids: list = None,
# 配额参数
max_agents: int = None,
max_workflows: int = None,
max_knowledge_bases: int = None,
max_storage_mb: int = None,
api_calls_per_day: int = None,
api_calls_per_min: int = None,
# 权限参数
can_publish_apps: bool = None,
can_create_api_keys: bool = None,
can_use_workflow: bool = None,
can_use_mcp: bool = None,
can_use_code_exec: bool = None,
can_use_privacy_mode: bool = None,
can_use_dag: bool = None,
allowed_model_tiers: list = None,
permissions: dict = None,
is_active: bool = None, is_active: bool = None,
admin_user=Depends(get_current_admin), admin_user=Depends(get_current_admin),
db: Session = Depends(get_db), db: Session = Depends(get_db),
): ):
"""更新会员计划""" """更新会员计划(含权益配置)"""
plan = db.query(MembershipPlan).filter(MembershipPlan.id == id).first() plan = db.query(MembershipPlan).filter(MembershipPlan.id == id).first()
if not plan: if not plan:
raise HTTPException(status_code=404, detail="会员计划不存在") raise HTTPException(status_code=404, detail="会员计划不存在")
# 基础字段
if name is not None: if name is not None:
plan.name = name plan.name = name
if level is not None: if level is not None:
@@ -123,6 +197,41 @@ async def update_plan(
plan.free_model_ids = free_model_ids plan.free_model_ids = free_model_ids
if is_active is not None: if is_active is not None:
plan.is_active = is_active plan.is_active = is_active
# 配额字段
if max_agents is not None:
plan.max_agents = max_agents
if max_workflows is not None:
plan.max_workflows = max_workflows
if max_knowledge_bases is not None:
plan.max_knowledge_bases = max_knowledge_bases
if max_storage_mb is not None:
plan.max_storage_mb = max_storage_mb
if api_calls_per_day is not None:
plan.api_calls_per_day = api_calls_per_day
if api_calls_per_min is not None:
plan.api_calls_per_min = api_calls_per_min
# 权限字段
if can_publish_apps is not None:
plan.can_publish_apps = can_publish_apps
if can_create_api_keys is not None:
plan.can_create_api_keys = can_create_api_keys
if can_use_workflow is not None:
plan.can_use_workflow = can_use_workflow
if can_use_mcp is not None:
plan.can_use_mcp = can_use_mcp
if can_use_code_exec is not None:
plan.can_use_code_exec = can_use_code_exec
if can_use_privacy_mode is not None:
plan.can_use_privacy_mode = can_use_privacy_mode
if can_use_dag is not None:
plan.can_use_dag = can_use_dag
if allowed_model_tiers is not None:
plan.allowed_model_tiers = allowed_model_tiers
if permissions is not None:
plan.permissions = permissions
plan.updated_at = datetime.now() plan.updated_at = datetime.now()
db.commit() db.commit()
@@ -132,7 +241,7 @@ async def update_plan(
action="update_membership_plan", action="update_membership_plan",
target_type="membership_plan", target_type="membership_plan",
target_id=id, target_id=id,
detail=f"更新会员计划 {plan.name}", detail=f"更新会员计划 {plan.name} (含权益配置)",
) )
return {"message": "会员计划已更新"} return {"message": "会员计划已更新"}
@@ -149,6 +258,11 @@ async def delete_plan(
if not plan: if not plan:
raise HTTPException(status_code=404, detail="会员计划不存在") raise HTTPException(status_code=404, detail="会员计划不存在")
# 检查是否有用户正在使用此计划
user_count = db.query(User).filter(User.membership_plan_id == id).count()
if user_count > 0:
raise HTTPException(status_code=400, detail=f"有 {user_count} 个用户正在使用此计划,无法删除")
db.delete(plan) db.delete(plan)
db.commit() db.commit()
@@ -164,6 +278,352 @@ async def delete_plan(
return {"message": "会员计划已删除"} return {"message": "会员计划已删除"}
# ============================================================
# 会员权益对比(前端对比表用)
# ============================================================
@router.get("/benefits-comparison")
async def get_benefits_comparison(
admin_user=Depends(get_current_admin),
db: Session = Depends(get_db),
):
"""获取会员权益对比表数据"""
plans = db.query(MembershipPlan).filter(
MembershipPlan.is_active == True
).order_by(MembershipPlan.level).all()
# 定义对比维度
dimensions = [
{"key": "daily_free_tokens", "label": "每日免费Token", "type": "number"},
{"key": "max_agents", "label": "最大Agent数量", "type": "number"},
{"key": "max_workflows", "label": "最大工作流数量", "type": "number"},
{"key": "max_knowledge_bases", "label": "最大知识库数量", "type": "number"},
{"key": "max_storage_mb", "label": "存储配额(MB)", "type": "number"},
{"key": "api_calls_per_day", "label": "每日API调用限额", "type": "number"},
{"key": "api_calls_per_min", "label": "每分钟API调用限额", "type": "number"},
{"key": "can_publish_apps", "label": "发布应用到市场", "type": "boolean"},
{"key": "can_create_api_keys", "label": "创建开发者API密钥", "type": "boolean"},
{"key": "can_use_workflow", "label": "使用工作流编排", "type": "boolean"},
{"key": "can_use_mcp", "label": "使用MCP工具", "type": "boolean"},
{"key": "can_use_code_exec", "label": "使用代码执行", "type": "boolean"},
{"key": "can_use_privacy_mode", "label": "使用隐私模式", "type": "boolean"},
{"key": "can_use_dag", "label": "使用DAG多Agent", "type": "boolean"},
{"key": "allowed_model_tiers", "label": "可用模型层级", "type": "list"},
]
return {
"dimensions": dimensions,
"plans": [_format_plan(p) for p in plans],
}
# ============================================================
# 全局会员配置管理
# ============================================================
@router.get("/global-configs")
async def get_global_configs(
admin_user=Depends(get_current_admin),
db: Session = Depends(get_db),
):
"""获取全局会员配置"""
result = db.execute(text("SELECT id, config_key, config_value, config_type, description FROM membership_global_configs ORDER BY config_key"))
configs = []
for row in result:
val = row[2]
if row[3] == 'boolean':
val = val == 'true'
elif row[3] == 'number':
try:
val = int(val)
except:
val = 0
elif row[3] == 'json':
try:
val = json.loads(val)
except:
pass
configs.append({
"id": row[0],
"key": row[1],
"value": val,
"type": row[3],
"description": row[4] or "",
})
return configs
@router.put("/global-configs/{key}")
async def update_global_config(
key: str,
value: str = None,
admin_user=Depends(get_current_admin),
db: Session = Depends(get_db),
):
"""更新全局会员配置"""
result = db.execute(text("SELECT id, config_type FROM membership_global_configs WHERE config_key = :key"), {"key": key})
row = result.fetchone()
if not row:
raise HTTPException(status_code=404, detail="配置项不存在")
config_id, config_type = row[0], row[1]
store_value = value
if config_type == 'boolean':
store_value = 'true' if value in ('true', '1', 'True') else 'false'
db.execute(text("UPDATE membership_global_configs SET config_value = :val WHERE id = :id"), {"val": store_value, "id": config_id})
db.commit()
LogService.log_audit(
admin_id=admin_user.id,
admin_name=admin_user.username,
action="update_membership_config",
target_type="membership_config",
target_id=config_id,
detail=f"更新会员配置 {key}={store_value}",
)
return {"message": "配置已更新"}
# ============================================================
# 用户会员管理
# ============================================================
@router.get("/user-memberships")
async def get_user_memberships(
user_id: str = Query(None),
plan_id: str = Query(None),
status: str = Query(None, description="会员状态: active/expired"),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
admin_user=Depends(get_current_admin),
db: Session = Depends(get_db),
):
"""获取用户会员信息列表"""
query = db.query(User).filter(User.role == 'user')
if user_id:
query = query.filter(User.id == user_id)
users_q = query
if plan_id:
users_q = users_q.filter(User.membership_plan_id == plan_id)
total = users_q.count()
users = users_q.order_by(User.created_at.desc()).offset((page - 1) * page_size).limit(page_size).all()
items = []
for u in users:
plan = db.query(MembershipPlan).filter(MembershipPlan.id == u.membership_plan_id).first() if u.membership_plan_id else None
# 判断状态
now = datetime.now()
if u.membership_expire_at and u.membership_expire_at < now:
member_status = "expired"
elif u.membership_level > 0:
member_status = "active"
else:
member_status = "free"
if status and member_status != status:
continue
items.append({
"user_id": u.id,
"username": u.username,
"email": u.email,
"membership_plan_id": u.membership_plan_id,
"membership_plan_name": plan.name if plan else "免费版",
"membership_level": u.membership_level or 0,
"membership_expire_at": u.membership_expire_at.isoformat() if u.membership_expire_at else None,
"auto_renew": bool(u.auto_renew),
"member_status": member_status,
"created_at": u.created_at.isoformat() if u.created_at else None,
})
return {"total": total, "page": page, "page_size": page_size, "items": items}
@router.put("/users/{user_id}/assign-plan")
async def assign_plan_to_user(
user_id: str,
plan_id: str,
duration_days: int = Query(30, description="订阅时长(天)"),
admin_user=Depends(get_current_admin),
db: Session = Depends(get_db),
):
"""为用户分配会员计划(管理员手动分配)"""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
plan = db.query(MembershipPlan).filter(MembershipPlan.id == plan_id).first()
if not plan:
raise HTTPException(status_code=404, detail="会员计划不存在")
from datetime import timedelta
now = datetime.now()
# 如果用户当前会员未到期,从到期时间续期
start_at = now
if user.membership_expire_at and user.membership_expire_at > now:
start_at = user.membership_expire_at
end_at = start_at + timedelta(days=duration_days)
user.membership_plan_id = plan_id
user.membership_level = plan.level
user.membership_expire_at = end_at
# 创建订阅记录
sub = MembershipSubscription(
id=str(uuid.uuid4()),
user_id=user_id,
plan_id=plan_id,
start_at=start_at,
end_at=end_at,
status="active",
)
db.add(sub)
db.commit()
LogService.log_audit(
admin_id=admin_user.id,
admin_name=admin_user.username,
action="assign_membership_plan",
target_type="user",
target_id=user_id,
detail=f"为用户 {user.username} 分配计划 {plan.name} ({duration_days}天)",
)
return {"message": f"已为用户 {user.username} 分配 {plan.name} 计划", "expire_at": end_at.isoformat()}
@router.put("/users/{user_id}/revoke")
async def revoke_membership(
user_id: str,
admin_user=Depends(get_current_admin),
db: Session = Depends(get_db),
):
"""撤销用户会员(降级为免费版)"""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
old_plan = user.membership_plan_id
user.membership_plan_id = None
user.membership_level = 0
user.membership_expire_at = None
user.auto_renew = False
db.commit()
LogService.log_audit(
admin_id=admin_user.id,
admin_name=admin_user.username,
action="revoke_membership",
target_type="user",
target_id=user_id,
detail=f"撤销用户 {user.username} 的会员 (原计划ID: {old_plan})",
)
return {"message": f"已撤销用户 {user.username} 的会员"}
# ============================================================
# 用户配额使用情况
# ============================================================
@router.get("/users/{user_id}/usage")
async def get_user_usage(
user_id: str,
admin_user=Depends(get_current_admin),
db: Session = Depends(get_db),
):
"""获取用户资源使用情况"""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 获取用户计划
plan = None
if user.membership_plan_id:
plan = db.query(MembershipPlan).filter(MembershipPlan.id == user.membership_plan_id).first()
# 统计用户实际资源使用
from ...models import Agent, KnowledgeBase
agents_count = db.query(Agent).filter(Agent.owner_id == user_id).count() if hasattr(Agent, 'owner_id') else 0
# 使用原生SQL统计
try:
kb_result = db.execute(text("SELECT COUNT(*) FROM knowledge_bases WHERE owner_id = :uid"), {"uid": user_id})
kb_count = kb_result.fetchone()[0]
except:
kb_count = 0
try:
agent_result = db.execute(text("SELECT COUNT(*) FROM agents WHERE owner_id = :uid"), {"uid": user_id})
agent_count = agent_result.fetchone()[0]
except:
agent_count = 0
try:
wf_result = db.execute(text("SELECT COUNT(*) FROM workflows WHERE owner_id = :uid"), {"uid": user_id})
wf_count = wf_result.fetchone()[0]
except:
wf_count = 0
# 获取今日API使用量
today = date.today()
try:
usage_result = db.execute(text("SELECT api_calls_total, tokens_used FROM user_api_usage WHERE user_id = :uid AND usage_date = :d"), {"uid": user_id, "d": today})
usage_row = usage_result.fetchone()
api_calls_today = usage_row[0] if usage_row else 0
tokens_today = usage_row[1] if usage_row else 0
except:
api_calls_today = 0
tokens_today = 0
# 获取存储使用量
try:
storage_result = db.execute(text("SELECT storage_used_mb FROM user_resource_usage WHERE user_id = :uid"), {"uid": user_id})
storage_row = storage_result.fetchone()
storage_used = float(storage_row[0]) if storage_row else 0.0
except:
storage_used = 0.0
plan_limits = {}
if plan:
plan_limits = {
"max_agents": getattr(plan, 'max_agents', 5) or 5,
"max_workflows": getattr(plan, 'max_workflows', 3) or 3,
"max_knowledge_bases": getattr(plan, 'max_knowledge_bases', 2) or 2,
"max_storage_mb": getattr(plan, 'max_storage_mb', 1024) or 1024,
"api_calls_per_day": getattr(plan, 'api_calls_per_day', 100) or 100,
"daily_free_tokens": plan.daily_free_tokens or 0,
}
return {
"user_id": user_id,
"username": user.username,
"plan_name": plan.name if plan else "免费版",
"plan_level": user.membership_level or 0,
"usage": {
"agents_count": agent_count,
"workflows_count": wf_count,
"knowledge_bases_count": kb_count,
"storage_used_mb": storage_used,
"api_calls_today": api_calls_today,
"tokens_today": tokens_today,
},
"limits": plan_limits,
"over_limit": {
"agents": agent_count > plan_limits.get("max_agents", 999) if plan_limits else False,
"workflows": wf_count > plan_limits.get("max_workflows", 999) if plan_limits else False,
"knowledge_bases": kb_count > plan_limits.get("max_knowledge_bases", 999) if plan_limits else False,
"storage": storage_used > plan_limits.get("max_storage_mb", 999999) if plan_limits else False,
"api_calls": api_calls_today > plan_limits.get("api_calls_per_day", 999999) if plan_limits else False,
},
}
# ============================================================ # ============================================================
# 用户订阅管理 # 用户订阅管理
# ============================================================ # ============================================================
@@ -190,24 +650,98 @@ async def get_subscriptions(
MembershipSubscription.created_at.desc() MembershipSubscription.created_at.desc()
).offset((page - 1) * page_size).limit(page_size).all() ).offset((page - 1) * page_size).limit(page_size).all()
# 关联用户名和计划名
items = []
for s in subscriptions:
u = db.query(User).filter(User.id == s.user_id).first()
p = db.query(MembershipPlan).filter(MembershipPlan.id == s.plan_id).first()
items.append({
"id": s.id,
"user_id": s.user_id,
"username": u.username if u else "",
"plan_id": s.plan_id,
"plan_name": p.name if p else "",
"order_id": s.order_id,
"start_at": s.start_at.isoformat() if s.start_at else None,
"end_at": s.end_at.isoformat() if s.end_at else None,
"auto_renew": s.auto_renew,
"status": s.status,
"created_at": s.created_at.isoformat() if s.created_at else None,
})
return { return {
"total": total, "total": total,
"page": page, "page": page,
"page_size": page_size, "page_size": page_size,
"items": [ "items": items,
{ }
"id": s.id,
"user_id": s.user_id,
"plan_id": s.plan_id, # ============================================================
"order_id": s.order_id, # 会员统计概览
"start_at": s.start_at.isoformat() if s.start_at else None, # ============================================================
"end_at": s.end_at.isoformat() if s.end_at else None,
"auto_renew": s.auto_renew, @router.get("/stats")
"status": s.status, async def get_membership_stats(
"created_at": s.created_at.isoformat() if s.created_at else None, admin_user=Depends(get_current_admin),
} db: Session = Depends(get_db),
for s in subscriptions ):
], """获取会员统计概览"""
total_users = db.query(User).filter(User.role == 'user').count()
# 各等级用户数
level_stats = {}
for level in range(4):
count = db.query(User).filter(User.membership_level == level).count()
level_stats[f"level_{level}"] = count
# 活跃会员(未到期)
now = datetime.now()
active_members = db.query(User).filter(
User.membership_level > 0,
User.membership_expire_at > now
).count()
# 已过期会员
expired_members = db.query(User).filter(
User.membership_level > 0,
User.membership_expire_at <= now
).count()
# 自动续费用户数
auto_renew_count = db.query(User).filter(
User.auto_renew == True,
User.membership_level > 0
).count()
# 各计划订阅数
plan_stats = []
plans = db.query(MembershipPlan).order_by(MembershipPlan.level).all()
for plan in plans:
count = db.query(User).filter(User.membership_plan_id == plan.id).count()
plan_stats.append({
"plan_id": plan.id,
"plan_name": plan.name,
"level": plan.level,
"user_count": count,
"is_active": plan.is_active,
})
# 今日新增订阅
today_start = datetime(now.year, now.month, now.day)
today_new_subs = db.query(MembershipSubscription).filter(
MembershipSubscription.created_at >= today_start
).count()
return {
"total_users": total_users,
"active_members": active_members,
"expired_members": expired_members,
"free_users": level_stats.get("level_0", 0),
"auto_renew_count": auto_renew_count,
"level_stats": level_stats,
"plan_stats": plan_stats,
"today_new_subs": today_new_subs,
} }
@@ -224,7 +758,6 @@ async def get_automation_rules(
rules = db.query(RiskRule).order_by(RiskRule.created_at.desc()).all() rules = db.query(RiskRule).order_by(RiskRule.created_at.desc()).all()
if not rules: if not rules:
# 返回默认规则
return { return {
"rules": [], "rules": [],
"defaults": { "defaults": {
+17
View File
@@ -347,6 +347,23 @@ class MembershipPlan(Base):
features = Column(JSON, default=None) features = Column(JSON, default=None)
free_app_ids = Column(JSON, default=None) free_app_ids = Column(JSON, default=None)
free_model_ids = Column(JSON, default=None) free_model_ids = Column(JSON, default=None)
# 配额字段
max_agents = Column(Integer, default=5)
max_workflows = Column(Integer, default=3)
max_knowledge_bases = Column(Integer, default=2)
max_storage_mb = Column(Integer, default=1024)
api_calls_per_day = Column(Integer, default=100)
api_calls_per_min = Column(Integer, default=10)
# 权限字段
can_publish_apps = Column(Boolean, default=False)
can_create_api_keys = Column(Boolean, default=False)
can_use_workflow = Column(Boolean, default=False)
can_use_mcp = Column(Boolean, default=False)
can_use_code_exec = Column(Boolean, default=False)
can_use_privacy_mode = Column(Boolean, default=False)
can_use_dag = Column(Boolean, default=False)
allowed_model_tiers = Column(JSON, default=None)
permissions = Column(JSON, default=None)
is_active = Column(Boolean, default=True) is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.now) created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
+19 -4
View File
@@ -360,13 +360,28 @@ export const adminAPI = {
updateCustomRequest: (id: string, data: any) => put<any>(`/admin/services/custom-requests/${id}`, data), updateCustomRequest: (id: string, data: any) => put<any>(`/admin/services/custom-requests/${id}`, data),
getAvailableAgents: () => get<any[]>('/admin/services/agents'), getAvailableAgents: () => get<any[]>('/admin/services/agents'),
assignAgentToService: (id: string, agentId: string) => put<any>(`/admin/services/${id}/agent?agent_id=${agentId}`), assignAgentToService: (id: string, agentId: string) => put<any>(`/admin/services/${id}/agent?agent_id=${agentId}`),
// 会员管理 // 会员管理(增强版)
getPlans: () => get<any[]>('/admin/membership/plans'), getPlans: () => get<any[]>('/admin/membership/plans'),
addPlan: (data: any) => post<any>('/admin/membership/plans', data), addPlan: (params: string) => post<any>(`/admin/membership/plans${params}`),
updatePlan: (id: string, data: any) => put<any>(`/admin/membership/plans/${id}`, data), updatePlan: (id: string, params: string) => put<any>(`/admin/membership/plans/${id}${params}`),
deletePlan: (id: string) => del<any>(`/admin/membership/plans/${id}`), deletePlan: (id: string) => del<any>(`/admin/membership/plans/${id}`),
getSubscriptions: () => get<any[]>('/admin/membership/subscriptions'), getSubscriptions: (page?: number) => get<any>(`/admin/membership/subscriptions${page ? `?page=${page}` : ''}`),
getAutomationRules: () => get<any>('/admin/membership/automation-rules'), getAutomationRules: () => get<any>('/admin/membership/automation-rules'),
getBenefitsComparison: () => get<any>('/admin/membership/benefits-comparison'),
getMembershipConfigs: () => get<any[]>('/admin/membership/global-configs'),
updateMembershipConfig: (key: string, params: string) => put<any>(`/admin/membership/global-configs/${key}${params}`),
getUserMemberships: (page?: number, planId?: string, status?: string) => {
const p = new URLSearchParams();
if (page) p.append('page', String(page));
if (planId) p.append('plan_id', planId);
if (status) p.append('status', status);
const qs = p.toString();
return get<any>(`/admin/membership/user-memberships${qs ? `?${qs}` : ''}`);
},
assignPlan: (userId: string, planId: string, days: number) => put<any>(`/admin/membership/users/${userId}/assign-plan?plan_id=${planId}&duration_days=${days}`),
revokeMembership: (userId: string) => put<any>(`/admin/membership/users/${userId}/revoke`),
getUserUsage: (userId: string) => get<any>(`/admin/membership/users/${userId}/usage`),
getMembershipStats: () => get<any>('/admin/membership/stats'),
// 广告管理 // 广告管理
getAds: () => get<any[]>('/admin/ads/'), getAds: () => get<any[]>('/admin/ads/'),
addAd: (data: any) => post<any>('/admin/ads/', data), addAd: (data: any) => post<any>('/admin/ads/', data),
File diff suppressed because it is too large. Load diff