feat(admin): app_config 运营配置后台(rewards 等常量可后台配置) (#14)
- 新增 app_config 表 + model/repository/config_schema + admin config 路由与 schema + 测试 - rewards.py 及 ad_reward/signin/task/wallet/comparison_milestone repositories 接入可配置项;ad.py / admin/main.py 配套 - alembic merge(ebb6af5c0b56)合并本地 app_config(cfg1a2b3c4d5)与 incoming price_report/best_deeplink(b7e2c1a9f4d3)两 head Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Reviewed-on: #14
This commit was merged in pull request #14.
This commit is contained in:
@@ -16,6 +16,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.admin.routers.admins import router as admins_router
|
||||
from app.admin.routers.audit import router as audit_router
|
||||
from app.admin.routers.auth import router as auth_router
|
||||
from app.admin.routers.config import router as config_router
|
||||
from app.admin.routers.dashboard import router as dashboard_router
|
||||
from app.admin.routers.feedback import router as feedback_router
|
||||
from app.admin.routers.users import router as users_router
|
||||
@@ -78,3 +79,4 @@ admin_app.include_router(withdraw_router)
|
||||
admin_app.include_router(feedback_router)
|
||||
admin_app.include_router(admins_router)
|
||||
admin_app.include_router(audit_router)
|
||||
admin_app.include_router(config_router)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""admin 运营配置:列出所有可配项 + 改某项(带审计 + 改值校验)。
|
||||
|
||||
配置项定义见 app.core.config_schema.CONFIG_DEFS;业务读配置 fallback 默认(见 app_config repo)。
|
||||
权限:operator / finance 都可改(运营调奖励、财务调额度),super 恒可。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated, Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
|
||||
from app.admin.audit import write_audit
|
||||
from app.admin.deps import AdminDb, get_client_ip, get_current_admin, require_role
|
||||
from app.admin.schemas.config import ConfigItemOut, ConfigUpdateRequest
|
||||
from app.core.config_schema import CONFIG_DEFS
|
||||
from app.models.admin import AdminUser
|
||||
from app.repositories import app_config
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/admin/api/config",
|
||||
tags=["admin-config"],
|
||||
dependencies=[Depends(get_current_admin)],
|
||||
)
|
||||
|
||||
|
||||
def _validate(key: str, value: Any) -> None:
|
||||
"""按配置项 type 校验新值,不合法抛 ValueError(router 转 400)。"""
|
||||
t = CONFIG_DEFS[key]["type"]
|
||||
if t == "int":
|
||||
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
||||
raise ValueError("需为非负整数")
|
||||
elif t == "int_list":
|
||||
if not isinstance(value, list) or not value:
|
||||
raise ValueError("需为非空整数列表")
|
||||
if not all(isinstance(x, int) and not isinstance(x, bool) and x >= 0 for x in value):
|
||||
raise ValueError("列表元素需为非负整数")
|
||||
if key == "signin_rewards" and len(value) != 7:
|
||||
raise ValueError("签到档位必须正好 7 个(对应 7 天循环)")
|
||||
elif t == "dict_str_int":
|
||||
if not isinstance(value, dict) or not all(
|
||||
isinstance(k, str) and isinstance(v, int) and not isinstance(v, bool)
|
||||
for k, v in value.items()
|
||||
):
|
||||
raise ValueError("需为 {字符串: 整数} 映射")
|
||||
|
||||
|
||||
def _item(db, key: str) -> ConfigItemOut:
|
||||
for item in app_config.list_all(db):
|
||||
if item["key"] == key:
|
||||
return ConfigItemOut(**item)
|
||||
raise HTTPException(status_code=404, detail="未知配置项")
|
||||
|
||||
|
||||
@router.get("", response_model=list[ConfigItemOut], summary="所有可配项 + 当前值")
|
||||
def list_config(db: AdminDb) -> list[ConfigItemOut]:
|
||||
return [ConfigItemOut(**item) for item in app_config.list_all(db)]
|
||||
|
||||
|
||||
@router.patch("/{key}", response_model=ConfigItemOut, summary="改某项配置(带审计)")
|
||||
def update_config(
|
||||
key: str,
|
||||
body: ConfigUpdateRequest,
|
||||
request: Request,
|
||||
admin: Annotated[AdminUser, Depends(require_role("operator", "finance"))],
|
||||
db: AdminDb,
|
||||
) -> ConfigItemOut:
|
||||
if key not in CONFIG_DEFS:
|
||||
raise HTTPException(status_code=404, detail="未知配置项")
|
||||
try:
|
||||
_validate(key, body.value)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
before = app_config.get_value(db, key)
|
||||
app_config.set_value(db, key, body.value, admin_id=admin.id, commit=False)
|
||||
write_audit(
|
||||
db, admin, action="config.set", target_type="config", target_id=key,
|
||||
detail={"before": before, "after": body.value}, ip=get_client_ip(request), commit=False,
|
||||
)
|
||||
db.commit()
|
||||
return _item(db, key)
|
||||
@@ -0,0 +1,22 @@
|
||||
"""admin 运营配置 schemas。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ConfigItemOut(BaseModel):
|
||||
key: str
|
||||
label: str
|
||||
group: str
|
||||
type: str # int / int_list / dict_str_int
|
||||
help: str | None = None
|
||||
default: Any
|
||||
value: Any
|
||||
overridden: bool # 是否被运营覆盖过(false=用默认)
|
||||
updated_at: str | None = None
|
||||
|
||||
|
||||
class ConfigUpdateRequest(BaseModel):
|
||||
value: Any
|
||||
Reference in New Issue
Block a user