From 5545af003f6578559bc181207db77d73be2764a8 Mon Sep 17 00:00:00 2001 From: wwwwwwwww <526125649@qq.com> Date: Wed, 8 Jul 2026 11:30:31 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=90=8E=E5=8F=B0=E6=89=80=E6=9C=89?= =?UTF-8?q?=E7=9A=84=E4=BF=AE=E6=94=B9=E6=B7=BB=E5=8A=A0=E7=9A=84=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E9=83=BD=E8=A6=81=E5=AF=B9=E5=BA=94=E4=BA=BA=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=88=B0=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97=E9=87=8C?= =?UTF-8?q?=20=E2=80=8B=EF=BC=8C=E9=99=A4=E4=BA=86GET=E4=B9=8B=E5=A4=96?= =?UTF-8?q?=E9=83=BD=E8=A6=81=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/admin/video_prompt_schema_config.py | 51 ++- video-gen-api/app/api/v1/admin.py | 379 ++++++++++++++++++ 2 files changed, 424 insertions(+), 6 deletions(-) diff --git a/video-gen-api/app/api/admin/video_prompt_schema_config.py b/video-gen-api/app/api/admin/video_prompt_schema_config.py index dcce0901..57006823 100644 --- a/video-gen-api/app/api/admin/video_prompt_schema_config.py +++ b/video-gen-api/app/api/admin/video_prompt_schema_config.py @@ -1,5 +1,7 @@ from __future__ import annotations +import json + from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession @@ -21,6 +23,7 @@ from app.services.video_prompt_schema_config_service import ( reset_video_prompt_schema_config, save_video_prompt_schema_config, ) +from app.services.operation_log import log_operation router = APIRouter(prefix="/admin/video-prompt-schema-config", tags=["admin-video-prompt-schema-config"]) @@ -40,8 +43,22 @@ async def save_config( admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): - _ = admin - return await save_video_prompt_schema_config(db, data=req.data, is_enabled=req.is_enabled) + result = await save_video_prompt_schema_config(db, data=req.data, is_enabled=req.is_enabled) + await log_operation( + db, + admin.id, + admin.username, + "保存视频提词 Schema 配置", + "PUT", + "/admin/video-prompt-schema-config", + detail=json.dumps( + { + "is_enabled": req.is_enabled, + }, + ensure_ascii=False, + ), + ) + return result @router.post("/reset-default", response_model=VideoPromptSchemaConfigOut, summary="恢复默认视频提词 Schema 配置") @@ -49,8 +66,16 @@ async def reset_default( admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): - _ = admin - return await reset_video_prompt_schema_config(db) + result = await reset_video_prompt_schema_config(db) + await log_operation( + db, + admin.id, + admin.username, + "恢复默认视频提词 Schema 配置", + "POST", + "/admin/video-prompt-schema-config/reset-default", + ) + return result @router.get("/export", response_model=VideoPromptSchemaExportOut, summary="导出视频提词 Schema 配置") @@ -68,8 +93,22 @@ async def import_config( admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): - _ = admin - return await import_video_prompt_schema_config(db, data=req.data, is_enabled=req.is_enabled) + result = await import_video_prompt_schema_config(db, data=req.data, is_enabled=req.is_enabled) + await log_operation( + db, + admin.id, + admin.username, + "导入视频提词 Schema 配置", + "POST", + "/admin/video-prompt-schema-config/import", + detail=json.dumps( + { + "is_enabled": req.is_enabled, + }, + ensure_ascii=False, + ), + ) + return result @router.post("/preview", response_model=VideoPromptSchemaPreviewOut, summary="预览视频提词运行时 Schema") diff --git a/video-gen-api/app/api/v1/admin.py b/video-gen-api/app/api/v1/admin.py index 62181177..fb11b4b1 100644 --- a/video-gen-api/app/api/v1/admin.py +++ b/video-gen-api/app/api/v1/admin.py @@ -493,6 +493,22 @@ async def create_admin_notification( db, None, title, content, notif_type, push_ws=True ) + await log_operation( + db, + admin.id, + admin.username, + f"{'发送通知给指定用户' if target_user_id else '广播通知'}: {title}", + "POST", + "/admin/notifications", + detail=json.dumps( + { + "title": title, + "target_user_id": target_user_id, + "type": notif_type, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -513,6 +529,21 @@ async def delete_admin_notification( ) await db.delete(notif) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"删除通知: {notif.title}", + "DELETE", + f"/admin/notifications/{notification_id}", + detail=json.dumps( + { + "notification_id": notification_id, + "title": notif.title, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -577,6 +608,15 @@ async def batch_update_payment_configs( value=value, )) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + "批量更新支付配置", + "PUT", + "/admin/payment-configs/batch", + detail=json.dumps(list(req.keys()), ensure_ascii=False), + ) return {"ok": True} @@ -767,6 +807,22 @@ async def update_payment_config( raise HTTPException(status_code=404, detail="支付配置不存在") config.value = req.value await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"更新支付配置: {config.key}", + "PUT", + f"/admin/payment-configs/{config_id}", + detail=json.dumps( + { + "config_id": config_id, + "key": config.key, + "value": req.value, + }, + ensure_ascii=False, + ), + ) return { "id": config.id, "key": config.key, @@ -785,6 +841,20 @@ async def refund_payment_order( result = await process_refund(db, order_no) if not result.get("success"): raise HTTPException(status_code=400, detail=result.get("message", "退款失败")) + await log_operation( + db, + admin.id, + admin.username, + f"订单退款: {order_no}", + "POST", + f"/admin/payment-orders/{order_no}/refund", + detail=json.dumps( + { + "order_no": order_no, + }, + ensure_ascii=False, + ), + ) return result @@ -842,6 +912,22 @@ async def create_industry_config( ) db.add(config) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"创建行业配置: {req.label}", + "POST", + "/admin/industry-configs", + detail=json.dumps( + { + "config_id": config.id, + "key": req.key, + "label": req.label, + }, + ensure_ascii=False, + ), + ) return _serialize_industry(config) @@ -866,6 +952,22 @@ async def update_industry_config( config.is_active = req.is_active config.sort_order = req.sort_order await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"更新行业配置: {req.label}", + "PUT", + f"/admin/industry-configs/{config_id}", + detail=json.dumps( + { + "config_id": config_id, + "key": req.key, + "label": req.label, + }, + ensure_ascii=False, + ), + ) return _serialize_industry(config) @@ -883,6 +985,22 @@ async def delete_industry_config( raise HTTPException(status_code=404, detail="行业配置不存在") await db.delete(config) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"删除行业配置: {config.label}", + "DELETE", + f"/admin/industry-configs/{config_id}", + detail=json.dumps( + { + "config_id": config_id, + "key": config.key, + "label": config.label, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -908,6 +1026,21 @@ async def create_video_engine( engine = VideoEngine(id=generate_id(), **req.model_dump()) db.add(engine) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"创建视频引擎: {req.name}", + "POST", + "/admin/video-engines", + detail=json.dumps( + { + "engine_id": engine.id, + "name": req.name, + }, + ensure_ascii=False, + ), + ) return engine @@ -927,6 +1060,21 @@ async def update_video_engine( for k, v in req.model_dump().items(): setattr(engine, k, v) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"更新视频引擎: {engine.name}", + "PUT", + f"/admin/video-engines/{engine_id}", + detail=json.dumps( + { + "engine_id": engine_id, + "name": engine.name, + }, + ensure_ascii=False, + ), + ) return engine @@ -944,6 +1092,21 @@ async def delete_video_engine( raise HTTPException(status_code=404, detail="视频引擎不存在") await db.delete(engine) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"删除视频引擎: {engine.name}", + "DELETE", + f"/admin/video-engines/{engine_id}", + detail=json.dumps( + { + "engine_id": engine_id, + "name": engine.name, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -969,6 +1132,21 @@ async def create_image_engine( engine = ImageEngine(id=generate_id(), **req.model_dump()) db.add(engine) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"创建图片引擎: {req.name}", + "POST", + "/admin/image-engines", + detail=json.dumps( + { + "engine_id": engine.id, + "name": req.name, + }, + ensure_ascii=False, + ), + ) return engine @@ -988,6 +1166,21 @@ async def update_image_engine( for k, v in req.model_dump().items(): setattr(engine, k, v) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"更新图片引擎: {engine.name}", + "PUT", + f"/admin/image-engines/{engine_id}", + detail=json.dumps( + { + "engine_id": engine_id, + "name": engine.name, + }, + ensure_ascii=False, + ), + ) return engine @@ -1005,6 +1198,21 @@ async def delete_image_engine( raise HTTPException(status_code=404, detail="图片引擎不存在") await db.delete(engine) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"删除图片引擎: {engine.name}", + "DELETE", + f"/admin/image-engines/{engine_id}", + detail=json.dumps( + { + "engine_id": engine_id, + "name": engine.name, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -1056,6 +1264,22 @@ async def create_credit_ratio( ratio = CreditRatio(id=generate_id(), **data) db.add(ratio) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"创建积分规则: {data.get('gen_type', '')} - {data.get('model_config_id', '')}", + "POST", + "/admin/credit-ratios", + detail=json.dumps( + { + "ratio_id": ratio.id, + "gen_type": data.get("gen_type"), + "model_config_id": data.get("model_config_id"), + }, + ensure_ascii=False, + ), + ) return ratio @@ -1079,6 +1303,22 @@ async def update_credit_ratio( for k, v in data.items(): setattr(ratio, k, v) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"更新积分规则: {data.get('gen_type', '')} - {data.get('model_config_id', '')}", + "PUT", + f"/admin/credit-ratios/{ratio_id}", + detail=json.dumps( + { + "ratio_id": ratio_id, + "gen_type": data.get("gen_type"), + "model_config_id": data.get("model_config_id"), + }, + ensure_ascii=False, + ), + ) return ratio @@ -1096,6 +1336,22 @@ async def delete_credit_ratio( raise HTTPException(status_code=404, detail="积分比例不存在") await db.delete(ratio) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"删除积分规则: {ratio.gen_type} - {ratio.model_config_id}", + "DELETE", + f"/admin/credit-ratios/{ratio_id}", + detail=json.dumps( + { + "ratio_id": ratio_id, + "gen_type": ratio.gen_type, + "model_config_id": ratio.model_config_id, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -1136,6 +1392,21 @@ async def create_model_config( config = ModelConfig(id=generate_id(), **req.model_dump()) db.add(config) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"创建模型配置: {req.name}", + "POST", + "/admin/model-configs", + detail=json.dumps( + { + "config_id": config.id, + "name": req.name, + }, + ensure_ascii=False, + ), + ) return config @@ -1153,6 +1424,21 @@ async def update_model_config( for k, v in req.model_dump().items(): setattr(config, k, v) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"更新模型配置: {config.name}", + "PUT", + f"/admin/model-configs/{config_id}", + detail=json.dumps( + { + "config_id": config_id, + "name": config.name, + }, + ensure_ascii=False, + ), + ) return config @@ -1168,6 +1454,21 @@ async def delete_model_config( raise HTTPException(status_code=404, detail="配置不存在") await db.delete(config) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"删除模型配置: {config.name}", + "DELETE", + f"/admin/model-configs/{config_id}", + detail=json.dumps( + { + "config_id": config_id, + "name": config.name, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -1195,6 +1496,22 @@ async def update_system_config( raise HTTPException(status_code=404, detail="配置不存在") config.value = str(req.value) await db.commit() + await log_operation( + db, + admin.id, + admin.username, + f"更新系统配置: {config.key}", + "PUT", + f"/admin/system-configs/{config_id}", + detail=json.dumps( + { + "config_id": config_id, + "key": config.key, + "value": req.value, + }, + ensure_ascii=False, + ), + ) return config @@ -1557,6 +1874,21 @@ async def admin_update_generation_status( if new_status == "completed": record.generated_at = datetime.now() await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"更新生成记录状态: {new_status}", + "PUT", + f"/admin/generation-records/{record_id}/status", + detail=json.dumps( + { + "record_id": record_id, + "new_status": new_status, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -1688,6 +2020,22 @@ async def admin_generate_video( ) await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"管理员触发生成{type_str}: {record_id}", + "POST", + f"/admin/generation-records/{record_id}/generate", + detail=json.dumps( + { + "record_id": record_id, + "gen_type": record.gen_type, + "project_name": project_name, + }, + ensure_ascii=False, + ), + ) return {"message": "ok", "record_id": record_id} @@ -1739,6 +2087,22 @@ async def upload_pdf( value=url, )) await db.commit() + await log_operation( + db, + admin.id, + admin.username, + f"上传PDF文件: {file.filename}", + "POST", + "/admin/upload-pdf", + detail=json.dumps( + { + "filename": file.filename, + "config_key": config_key, + "url": url, + }, + ensure_ascii=False, + ), + ) return {"url": url} @@ -1785,6 +2149,21 @@ async def upload_logo( description="网站Logo图片", )) await db.commit() + await log_operation( + db, + admin.id, + admin.username, + f"上传Logo图片: {file.filename}", + "POST", + "/admin/upload-logo", + detail=json.dumps( + { + "filename": file.filename, + "url": url, + }, + ensure_ascii=False, + ), + ) return {"url": url} From 2882d371124d9a63b4b89c6978fa0c956f6b68a0 Mon Sep 17 00:00:00 2001 From: wwwwwwwww <526125649@qq.com> Date: Wed, 8 Jul 2026 14:24:50 +0800 Subject: [PATCH 2/2] 1 --- video-gen-api/app/api/admin/__init__.py | 4 + video-gen-api/app/api/admin/menu_config.py | 124 +++++++++++++++ .../app/api/admin/recharge_package.py | 145 ++++++++++++++++++ .../api/admin/video_prompt_schema_config.py | 6 + video-gen-api/app/api/v1/admin.py | 113 +++++++++++++- video-gen-api/app/api/v1/menu_configs.py | 59 +------ video-gen-api/app/api/v1/recharge_packages.py | 70 +-------- 7 files changed, 388 insertions(+), 133 deletions(-) create mode 100644 video-gen-api/app/api/admin/menu_config.py create mode 100644 video-gen-api/app/api/admin/recharge_package.py diff --git a/video-gen-api/app/api/admin/__init__.py b/video-gen-api/app/api/admin/__init__.py index 16818bc5..f7d1db0e 100644 --- a/video-gen-api/app/api/admin/__init__.py +++ b/video-gen-api/app/api/admin/__init__.py @@ -5,6 +5,8 @@ from app.api.admin.resource_capacity import router as resource_capacity_router from app.api.admin.team import router as team_router from app.api.admin.home_material import router as home_material_router from app.api.admin.private_portrait import router as private_portrait_router +from app.api.admin.recharge_package import router as recharge_package_router +from app.api.admin.menu_config import router as menu_config_router router = APIRouter() router.include_router(video_prompt_schema_config_router) @@ -12,3 +14,5 @@ router.include_router(resource_capacity_router) router.include_router(team_router) router.include_router(home_material_router) router.include_router(private_portrait_router) +router.include_router(recharge_package_router) +router.include_router(menu_config_router) diff --git a/video-gen-api/app/api/admin/menu_config.py b/video-gen-api/app/api/admin/menu_config.py new file mode 100644 index 00000000..d1f5ffc1 --- /dev/null +++ b/video-gen-api/app/api/admin/menu_config.py @@ -0,0 +1,124 @@ +import json + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.dependencies import get_db, get_admin_user, get_backend_user +from app.models.user import User +from app.models.menu_config import MenuConfig +from app.schemas.menu import MenuConfigCreate, MenuConfigOut +from app.services.operation_log import log_operation +from app.utils.id_gen import generate_id + +router = APIRouter(prefix="/admin/menu-configs", tags=["admin-menu-configs"]) + + +@router.get("", response_model=list[MenuConfigOut]) +async def admin_list_menu_configs( + admin: User = Depends(get_backend_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute(select(MenuConfig).order_by(MenuConfig.sort_order)) + return result.scalars().all() + + +@router.post("", response_model=MenuConfigOut) +async def create_menu_config( + req: MenuConfigCreate, + admin: User = Depends(get_admin_user), + db: AsyncSession = Depends(get_db), +): + menu = MenuConfig(id=generate_id(), **req.model_dump()) + db.add(menu) + await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"创建菜单 {menu.label}", + "POST", + "/admin/menu-configs", + detail=json.dumps( + { + "id": menu.id, + "label": menu.label, + "path": menu.path, + "menu_target": menu.menu_target, + "menu_type": menu.menu_type, + }, + ensure_ascii=False, + ), + ) + return menu + + +@router.put("/{menu_id}", response_model=MenuConfigOut) +async def update_menu_config( + menu_id: str, + req: MenuConfigCreate, + admin: User = Depends(get_admin_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute(select(MenuConfig).where(MenuConfig.id == menu_id).limit(1)) + menu = result.scalar_one_or_none() + if not menu: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="菜单不存在") + before_label = menu.label + for k, v in req.model_dump().items(): + setattr(menu, k, v) + await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"更新菜单 {before_label}", + "PUT", + f"/admin/menu-configs/{menu_id}", + detail=json.dumps( + { + "id": menu_id, + "before": {"label": before_label}, + "after": { + "label": menu.label, + "path": menu.path, + "is_active": menu.is_active, + "sort_order": menu.sort_order, + }, + }, + ensure_ascii=False, + ), + ) + return menu + + +@router.delete("/{menu_id}") +async def delete_menu_config( + menu_id: str, + admin: User = Depends(get_admin_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute(select(MenuConfig).where(MenuConfig.id == menu_id).limit(1)) + menu = result.scalar_one_or_none() + if not menu: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="菜单不存在") + menu_label = menu.label + await db.delete(menu) + await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"删除菜单 {menu_label}", + "DELETE", + f"/admin/menu-configs/{menu_id}", + detail=json.dumps( + { + "id": menu_id, + "label": menu_label, + "path": menu.path, + }, + ensure_ascii=False, + ), + ) + return {"message": "ok"} diff --git a/video-gen-api/app/api/admin/recharge_package.py b/video-gen-api/app/api/admin/recharge_package.py new file mode 100644 index 00000000..4875c96c --- /dev/null +++ b/video-gen-api/app/api/admin/recharge_package.py @@ -0,0 +1,145 @@ +import json + +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.dependencies import get_db, get_admin_user +from app.models.user import User +from app.models.recharge_package import RechargePackage +from app.schemas.recharge_package import ( + RechargePackageCreate, + RechargePackageUpdate, + RechargePackageOut, +) +from app.services.operation_log import log_operation +from app.utils.id_gen import generate_id + +router = APIRouter(prefix="/admin/recharge-packages", tags=["admin-recharge-packages"]) + + +def _to_out(pkg: RechargePackage) -> dict: + return { + "id": pkg.id, + "name": pkg.name, + "credits": round(pkg.credits, 2), + "price": round(pkg.price, 2), + "bonus_credits": round(pkg.bonus_credits, 2), + "total_credits": round(pkg.credits + pkg.bonus_credits, 2), + "description": pkg.description, + "package_type": pkg.package_type, + "is_gift": pkg.is_gift, + "is_active": pkg.is_active, + "sort_order": pkg.sort_order, + } + + +@router.get("", response_model=list[RechargePackageOut]) +async def admin_list_packages( + admin: User = Depends(get_admin_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(RechargePackage).order_by(RechargePackage.sort_order) + ) + return [_to_out(p) for p in result.scalars().all()] + + +@router.post("", response_model=RechargePackageOut) +async def create_package( + data: RechargePackageCreate, + admin: User = Depends(get_admin_user), + db: AsyncSession = Depends(get_db), +): + pkg = RechargePackage(id=generate_id(), **data.model_dump()) + db.add(pkg) + await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"创建充值套餐 {pkg.name}", + "POST", + "/admin/recharge-packages", + detail=json.dumps( + { + "id": pkg.id, + "name": pkg.name, + "price": round(pkg.price, 2), + "credits": round(pkg.credits, 2), + "bonus_credits": round(pkg.bonus_credits, 2), + }, + ensure_ascii=False, + ), + ) + return _to_out(pkg) + + +@router.put("/{pkg_id}", response_model=RechargePackageOut) +async def update_package( + pkg_id: str, + data: RechargePackageUpdate, + admin: User = Depends(get_admin_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(RechargePackage).where(RechargePackage.id == pkg_id).limit(1) + ) + pkg = result.scalar_one_or_none() + if not pkg: + raise HTTPException(status_code=404, detail="套餐不存在") + before = _to_out(pkg) + for k, v in data.model_dump(exclude_unset=True).items(): + setattr(pkg, k, v) + await db.flush() + after = _to_out(pkg) + await log_operation( + db, + admin.id, + admin.username, + f"更新充值套餐 {pkg.name}", + "PUT", + f"/admin/recharge-packages/{pkg_id}", + detail=json.dumps( + { + "id": pkg_id, + "before": before, + "after": after, + }, + ensure_ascii=False, + ), + ) + return _to_out(pkg) + + +@router.delete("/{pkg_id}") +async def delete_package( + pkg_id: str, + admin: User = Depends(get_admin_user), + db: AsyncSession = Depends(get_db), +): + result = await db.execute( + select(RechargePackage).where(RechargePackage.id == pkg_id).limit(1) + ) + pkg = result.scalar_one_or_none() + if not pkg: + raise HTTPException(status_code=404, detail="套餐不存在") + pkg_name = pkg.name + await db.delete(pkg) + await db.flush() + await log_operation( + db, + admin.id, + admin.username, + f"删除充值套餐 {pkg_name}", + "DELETE", + f"/admin/recharge-packages/{pkg_id}", + detail=json.dumps( + { + "id": pkg_id, + "name": pkg_name, + }, + ensure_ascii=False, + ), + ) + return {"ok": True} diff --git a/video-gen-api/app/api/admin/video_prompt_schema_config.py b/video-gen-api/app/api/admin/video_prompt_schema_config.py index 57006823..69ee31cb 100644 --- a/video-gen-api/app/api/admin/video_prompt_schema_config.py +++ b/video-gen-api/app/api/admin/video_prompt_schema_config.py @@ -74,6 +74,12 @@ async def reset_default( "恢复默认视频提词 Schema 配置", "POST", "/admin/video-prompt-schema-config/reset-default", + detail=json.dumps( + { + "reset_to_default": True, + }, + ensure_ascii=False, + ), ) return result diff --git a/video-gen-api/app/api/v1/admin.py b/video-gen-api/app/api/v1/admin.py index fb11b4b1..c80d854a 100644 --- a/video-gen-api/app/api/v1/admin.py +++ b/video-gen-api/app/api/v1/admin.py @@ -218,7 +218,21 @@ async def update_user_menus( raise HTTPException(status_code=404, detail="用户不存在") user.allowed_menus = req.allowed_menus await db.flush() - await log_operation(db, admin.id, admin.username, f"更新菜单权限", "PUT", f"/admin/users/{user_id}/menus") + await log_operation( + db, + admin.id, + admin.username, + f"更新菜单权限", + "PUT", + f"/admin/users/{user_id}/menus", + detail=json.dumps( + { + "user_id": user_id, + "allowed_menus": req.allowed_menus, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -259,7 +273,22 @@ async def adjust_credits( f"您的积分已{'增加' if req.amount > 0 else '扣除'}{abs(req.amount)}积分。原因:{req.description}", "credit", ) - await log_operation(db, admin.id, admin.username, f"调整积分 {'+' if req.amount > 0 else ''}{req.amount}", "POST", f"/admin/users/{user_id}/credits") + await log_operation( + db, + admin.id, + admin.username, + f"调整积分 {'+' if req.amount > 0 else ''}{req.amount}", + "POST", + f"/admin/users/{user_id}/credits", + detail=json.dumps( + { + "user_id": user_id, + "amount": req.amount, + "description": req.description, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -275,7 +304,21 @@ async def update_user_status( update(User).where(User.id == user_id).values(is_active=is_active) ) await db.flush() - await log_operation(db, admin.id, admin.username, f"{'启用' if is_active else '禁用'}用户", "PUT", f"/admin/users/{user_id}/status") + await log_operation( + db, + admin.id, + admin.username, + f"{'启用' if is_active else '禁用'}用户", + "PUT", + f"/admin/users/{user_id}/status", + detail=json.dumps( + { + "user_id": user_id, + "is_active": is_active, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -295,7 +338,21 @@ async def update_user_admin_status( raise HTTPException(status_code=400, detail="仅后台用户支持设置超级管理员状态") user.is_admin = is_admin await db.flush() - await log_operation(db, admin.id, admin.username, f"{is_admin and '设为' or '取消'}超级管理员", "PUT", f"/admin/users/{user_id}/admin-status") + await log_operation( + db, + admin.id, + admin.username, + f"{is_admin and '设为' or '取消'}超级管理员", + "PUT", + f"/admin/users/{user_id}/admin-status", + detail=json.dumps( + { + "user_id": user_id, + "is_admin": is_admin, + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -314,7 +371,21 @@ async def update_user_frontend_kind( raise HTTPException(status_code=400, detail="仅前台用户支持设置内部/外部归类") user.frontend_user_kind = req.frontend_user_kind or FrontendUserKind.EXTERNAL.value await db.flush() - await log_operation(db, admin.id, admin.username, f"设置前台用户归类为 {user.frontend_user_kind}", "PUT", f"/admin/users/{user_id}/frontend-kind") + await log_operation( + db, + admin.id, + admin.username, + f"设置前台用户归类为 {user.frontend_user_kind}", + "PUT", + f"/admin/users/{user_id}/frontend-kind", + detail=json.dumps( + { + "user_id": user_id, + "frontend_user_kind": user.frontend_user_kind, + }, + ensure_ascii=False, + ), + ) return user @@ -354,7 +425,21 @@ async def reset_user_password( raise HTTPException(status_code=404, detail="用户不存在") user.hashed_password = hash_password(req.new_password) await db.flush() - await log_operation(db, admin.id, admin.username, f"重置密码", "PUT", f"/admin/users/{user_id}/reset-password") + await log_operation( + db, + admin.id, + admin.username, + f"重置密码", + "PUT", + f"/admin/users/{user_id}/reset-password", + detail=json.dumps( + { + "user_id": user_id, + "new_password": "***", + }, + ensure_ascii=False, + ), + ) return {"message": "ok"} @@ -372,7 +457,21 @@ async def admin_change_password( raise HTTPException(status_code=400, detail="密码至少6位") admin.hashed_password = hash_password(new_password) await db.flush() - await log_operation(db, admin.id, admin.username, "修改密码", "POST", "/admin/change-password") + await log_operation( + db, + admin.id, + admin.username, + "修改密码", + "POST", + "/admin/change-password", + detail=json.dumps( + { + "old_password": "***", + "new_password": "***", + }, + ensure_ascii=False, + ), + ) return {"message": "密码修改成功"} diff --git a/video-gen-api/app/api/v1/menu_configs.py b/video-gen-api/app/api/v1/menu_configs.py index 0880989a..d2e13ff6 100644 --- a/video-gen-api/app/api/v1/menu_configs.py +++ b/video-gen-api/app/api/v1/menu_configs.py @@ -2,11 +2,9 @@ from fastapi import APIRouter, Depends from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from app.dependencies import get_db, get_admin_user, get_backend_user, get_current_user +from app.dependencies import get_db, get_current_user from app.models.user import User from app.models.menu_config import MenuConfig -from app.schemas.menu import MenuConfigCreate, MenuConfigOut -from app.utils.id_gen import generate_id router = APIRouter(tags=["menu-configs"]) @@ -57,58 +55,3 @@ async def public_list_menu_configs( } for m in menus ] - - -@router.get("/admin/menu-configs", response_model=list[MenuConfigOut]) -async def admin_list_menu_configs( - admin: User = Depends(get_backend_user), - db: AsyncSession = Depends(get_db), -): - result = await db.execute(select(MenuConfig).order_by(MenuConfig.sort_order)) - return result.scalars().all() - - -@router.post("/admin/menu-configs", response_model=MenuConfigOut) -async def create_menu_config( - req: MenuConfigCreate, - admin: User = Depends(get_admin_user), - db: AsyncSession = Depends(get_db), -): - menu = MenuConfig(id=generate_id(), **req.model_dump()) - db.add(menu) - await db.flush() - return menu - - -@router.put("/admin/menu-configs/{menu_id}", response_model=MenuConfigOut) -async def update_menu_config( - menu_id: str, - req: MenuConfigCreate, - admin: User = Depends(get_admin_user), - db: AsyncSession = Depends(get_db), -): - result = await db.execute(select(MenuConfig).where(MenuConfig.id == menu_id).limit(1)) - menu = result.scalar_one_or_none() - if not menu: - from fastapi import HTTPException, status - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="菜单不存在") - for k, v in req.model_dump().items(): - setattr(menu, k, v) - await db.flush() - return menu - - -@router.delete("/admin/menu-configs/{menu_id}") -async def delete_menu_config( - menu_id: str, - admin: User = Depends(get_admin_user), - db: AsyncSession = Depends(get_db), -): - result = await db.execute(select(MenuConfig).where(MenuConfig.id == menu_id).limit(1)) - menu = result.scalar_one_or_none() - if not menu: - from fastapi import HTTPException, status - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="菜单不存在") - await db.delete(menu) - await db.flush() - return {"message": "ok"} diff --git a/video-gen-api/app/api/v1/recharge_packages.py b/video-gen-api/app/api/v1/recharge_packages.py index acc82560..a6d95b14 100644 --- a/video-gen-api/app/api/v1/recharge_packages.py +++ b/video-gen-api/app/api/v1/recharge_packages.py @@ -1,16 +1,10 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from app.dependencies import get_db, get_admin_user, get_current_user +from app.dependencies import get_db, get_current_user from app.models.user import User from app.models.recharge_package import RechargePackage -from app.schemas.recharge_package import ( - RechargePackageCreate, - RechargePackageUpdate, - RechargePackageOut, -) -from app.utils.id_gen import generate_id router = APIRouter(tags=["recharge-packages"]) @@ -43,63 +37,3 @@ async def list_active_packages( .order_by(RechargePackage.sort_order) ) return [_to_out(p) for p in result.scalars().all()] - - -@router.get("/admin/recharge-packages") -async def admin_list_packages( - _admin=Depends(get_admin_user), - db: AsyncSession = Depends(get_db), -): - """Admin: list all packages.""" - result = await db.execute( - select(RechargePackage).order_by(RechargePackage.sort_order) - ) - return [_to_out(p) for p in result.scalars().all()] - - -@router.post("/admin/recharge-packages") -async def create_package( - data: RechargePackageCreate, - _admin=Depends(get_admin_user), - db: AsyncSession = Depends(get_db), -): - pkg = RechargePackage(id=generate_id(), **data.model_dump()) - db.add(pkg) - await db.flush() - return _to_out(pkg) - - -@router.put("/admin/recharge-packages/{pkg_id}") -async def update_package( - pkg_id: str, - data: RechargePackageUpdate, - _admin=Depends(get_admin_user), - db: AsyncSession = Depends(get_db), -): - result = await db.execute( - select(RechargePackage).where(RechargePackage.id == pkg_id).limit(1) - ) - pkg = result.scalar_one_or_none() - if not pkg: - raise HTTPException(status_code=404, detail="套餐不存在") - for k, v in data.model_dump(exclude_unset=True).items(): - setattr(pkg, k, v) - await db.flush() - return _to_out(pkg) - - -@router.delete("/admin/recharge-packages/{pkg_id}") -async def delete_package( - pkg_id: str, - _admin=Depends(get_admin_user), - db: AsyncSession = Depends(get_db), -): - result = await db.execute( - select(RechargePackage).where(RechargePackage.id == pkg_id).limit(1) - ) - pkg = result.scalar_one_or_none() - if not pkg: - raise HTTPException(status_code=404, detail="套餐不存在") - await db.delete(pkg) - await db.flush() - return {"ok": True}