运营后台后端: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,7 @@ from app.models.ad_pangle_revenue import AdPangleDailyRevenue # noqa: F401
|
||||
from app.models.ad_reward import AdRewardRecord # noqa: F401
|
||||
from app.models.ad_watch_log import AdWatchLog # noqa: F401
|
||||
from app.models.admin import AdminAuditLog, AdminUser # noqa: F401
|
||||
from app.models.admin_role import AdminRole # noqa: F401
|
||||
from app.models.analytics_event import AnalyticsEvent # noqa: F401
|
||||
from app.models.app_config import AppConfig # noqa: F401
|
||||
from app.models.comparison import ComparisonRecord # noqa: F401
|
||||
|
||||
@@ -25,6 +25,9 @@ class AdminUser(Base):
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
username: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
|
||||
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
# 明文登录密码:仅「后台 UI 创建/重置」的管理员留存,供超管在权限管理页复看转交。
|
||||
# 脚本/起后台时建的超管账号不写(为 None → 前端「不显示密码」)。⚠️ 内部工具便利取舍,见 create/list。
|
||||
plain_password: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
||||
# super_admin(全权+管账号)/ finance(钱:提现+金币)/ operator(用户+反馈+大盘)
|
||||
role: Mapped[str] = mapped_column(String(20), nullable=False, default="operator")
|
||||
# active / disabled
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
"""admin 角色 → 可见页面(权限)映射表。
|
||||
|
||||
RBAC 的「角色」侧:每个角色持有一组「页面 key」(= 左侧导航项),决定该角色登录后台后左边能看到
|
||||
哪些页。super_admin 是内建全权角色(is_builtin=True),恒可见全部页(effective_pages 里特判,
|
||||
不依赖本表存的 pages)、不可编辑/删除。其余角色(含 operator/finance)可由 super_admin 增删改。
|
||||
|
||||
admin_user.role 存的是本表的 name(字符串弱引用,不建外键——与既有 require_role 字符串口径一致;
|
||||
删除在用角色由业务层拦截,见 routers/roles.py)。页面 key 清单见 app/admin/permissions.py。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import JSON, Boolean, DateTime, Integer, String, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
_JSON = JSON().with_variant(JSONB(), "postgresql")
|
||||
|
||||
|
||||
class AdminRole(Base):
|
||||
__tablename__ = "admin_role"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
# 角色标识 key(不可变,承重):admin_user.role 引用它、require_role 按它鉴权。
|
||||
# 内建 = super_admin/operator/finance/tech(英文,勿改);自定义 = 创建时的名称。
|
||||
name: Mapped[str] = mapped_column(String(32), unique=True, index=True, nullable=False)
|
||||
# 展示名(可改):内建 = 管理员/运营/财务/技术;自定义默认 = name。UI 一律展示 label。
|
||||
label: Mapped[str] = mapped_column(String(32), nullable=False, default="")
|
||||
# 该角色可见页面 key 列表(= 左侧导航项),见 app/admin/permissions.py 的 PERMISSION_CATALOG
|
||||
pages: Mapped[list] = mapped_column(_JSON, nullable=False, default=list)
|
||||
# 内建角色(当前仅 super_admin):不可编辑/删除,恒全权
|
||||
is_builtin: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), nullable=False
|
||||
)
|
||||
|
||||
def __repr__(self) -> str: # pragma: no cover
|
||||
return f"<AdminRole name={self.name} pages={len(self.pages)} builtin={self.is_builtin}>"
|
||||
@@ -86,6 +86,10 @@ class ComparisonRecord(Base):
|
||||
|
||||
# ===== 订单概要 =====
|
||||
store_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
||||
# 下单商品名拼接串(顿号分隔,从 items[].name 去重派生),供 admin 列表「商品」列展示 + 商品名搜索。
|
||||
# items 是 JSON(SQLite 下 json.dumps ensure_ascii 把中文转义,无法直接 CAST+LIKE),故另派生成普通
|
||||
# 文本列——跨库 LIKE 一致、可加索引。写路径(upsert_record / harvest_done)落库时同步派生。
|
||||
product_names: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
total_dish_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
skipped_dish_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user