运营后台后端:RBAC 权限管理 + 系统配置下发修复 + 比价记录店/商品搜索
一、RBAC 权限管理(角色 → 可见页面) - 新增 admin_role 表 + 权限目录 permissions.py:角色持有一组页面 key,登录后左侧只展示这些页 - 内建角色 管理员(super_admin 全权锁定)/运营/财务/技术,页集对齐原型;key 承重(require_role 用),另加中文 label 展示 - 角色 CRUD 端点(仅 super_admin)+ 目录端点;登录/me 下发当前角色有效可见页 - admins 加删除、角色存在性校验;可复看已确定登录密码:UI 建的账号留存明文 plain_password,脚本建的超管不留存 二、系统配置下发修复 - 首页数据「保存即生效」:显式保存(apply_now)对 real/manual 直接落配置目标值、绕过只增不减护栏(修「改了 app 端不变」),护栏仍管自动 tick - 首页轮播新增数据源三选一:mixed(真实优先+种子)/real(只真实)/seed(只种子),get_feed 分支 + /marquee-seeds/mode 端点 - 福利页 Tab 隐藏 任务/里程碑,及看广告的单次金币/每轮次数/信息流开关:CONFIG_DEFS 加 hidden + list_config 过滤,业务读取默认值不受影响 三、比价记录店/商品搜索 - comparison_record 加 product_names 派生列(从下单 items 拼商品名),迁移建列并回填历史行 - admin 比价记录列表店/商品分列可搜:走 product_names 普通列 LIKE,规避 SQLite JSON 中文 ensure_ascii 转义搜不到的坑 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@ from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
|
||||
from app.admin.audit import write_audit
|
||||
from app.admin.deps import AdminDb, CurrentAdmin, get_client_ip, require_role
|
||||
from app.admin.permissions import SUPER_ADMIN_ROLE
|
||||
from app.admin.repositories import admin_role as role_repo
|
||||
from app.admin.repositories import admin_user as admin_repo
|
||||
from app.admin.schemas.admin import AdminCreateRequest, AdminUpdateRequest
|
||||
from app.admin.schemas.auth import AdminOut
|
||||
@@ -23,9 +25,23 @@ def _active_super_count(db: AdminDb) -> int:
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_model=list[AdminOut], summary="管理员列表")
|
||||
def _validate_role(db: AdminDb, role: str) -> None:
|
||||
"""角色必须是 super_admin 或 admin_role 表里已存在的角色,否则 400。"""
|
||||
if role == SUPER_ADMIN_ROLE:
|
||||
return
|
||||
role_repo.ensure_builtin_roles(db) # 空表(测试/全新库)兜底播种,再校验
|
||||
if role_repo.get_role(db, role) is None:
|
||||
raise HTTPException(status_code=400, detail=f"角色不存在: {role}")
|
||||
|
||||
|
||||
@router.get("", response_model=list[AdminOut], summary="管理员列表(含明文密码,super_admin 专属)")
|
||||
def list_admins(db: AdminDb) -> list[AdminOut]:
|
||||
return [AdminOut.model_validate(a) for a in admin_repo.list_admins(db)]
|
||||
out: list[AdminOut] = []
|
||||
for a in admin_repo.list_admins(db):
|
||||
item = AdminOut.model_validate(a)
|
||||
item.password = a.plain_password # 明文:仅本 super_admin 专属路由下发,供权限管理页复看
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
|
||||
@router.post("", response_model=AdminOut, summary="创建管理员")
|
||||
@@ -34,8 +50,10 @@ def create_admin(
|
||||
) -> AdminOut:
|
||||
if admin_repo.get_by_username(db, body.username) is not None:
|
||||
raise HTTPException(status_code=409, detail="用户名已存在")
|
||||
_validate_role(db, body.role)
|
||||
new = admin_repo.create_admin(
|
||||
db, username=body.username, password=body.password, role=body.role
|
||||
db, username=body.username, password=body.password, role=body.role,
|
||||
plain_password=body.password, # UI 建的账号留存明文,供权限管理页复看
|
||||
)
|
||||
write_audit(
|
||||
db, admin, action="admin.create", target_type="admin", target_id=new.id,
|
||||
@@ -67,6 +85,9 @@ def update_admin(
|
||||
if demotes_super and _active_super_count(db) <= 1:
|
||||
raise HTTPException(status_code=400, detail="不能降级/禁用最后一个超级管理员")
|
||||
|
||||
if body.role is not None:
|
||||
_validate_role(db, body.role)
|
||||
|
||||
changes: dict = {}
|
||||
if body.role is not None and body.role != target.role:
|
||||
changes["role"] = {"before": target.role, "after": body.role}
|
||||
@@ -77,6 +98,7 @@ def update_admin(
|
||||
if body.password is not None:
|
||||
changes["password"] = "reset"
|
||||
target.password_hash = hash_password(body.password)
|
||||
target.plain_password = body.password # 同步留存明文,权限管理页复看保持一致
|
||||
if not changes:
|
||||
raise HTTPException(status_code=400, detail="无任何变更字段")
|
||||
db.commit()
|
||||
@@ -86,3 +108,27 @@ def update_admin(
|
||||
detail=changes, ip=get_client_ip(request), commit=True,
|
||||
)
|
||||
return AdminOut.model_validate(target)
|
||||
|
||||
|
||||
@router.delete("/{admin_id}", summary="删除管理员(带审计)")
|
||||
def delete_admin(admin_id: int, request: Request, admin: CurrentAdmin, db: AdminDb) -> dict:
|
||||
target = admin_repo.get_by_id(db, admin_id)
|
||||
if target is None:
|
||||
raise HTTPException(status_code=404, detail="管理员不存在")
|
||||
if admin_id == admin.id:
|
||||
raise HTTPException(status_code=400, detail="不能删除自己")
|
||||
# 防自锁:删掉某个 active super_admin 前,确认后仍至少剩 1 个,否则进「零可用超管」死局。
|
||||
if (
|
||||
target.role == "super_admin"
|
||||
and target.status == "active"
|
||||
and _active_super_count(db) <= 1
|
||||
):
|
||||
raise HTTPException(status_code=400, detail="不能删除最后一个超级管理员")
|
||||
username = target.username
|
||||
db.delete(target)
|
||||
db.commit()
|
||||
write_audit(
|
||||
db, admin, action="admin.delete", target_type="admin", target_id=admin_id,
|
||||
detail={"username": username, "role": target.role}, ip=get_client_ip(request), commit=True,
|
||||
)
|
||||
return {"deleted": True}
|
||||
|
||||
Reference in New Issue
Block a user