bce8cd9923
一、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>
71 lines
2.6 KiB
Python
71 lines
2.6 KiB
Python
"""comparison_record 加 product_names 列(下单商品名派生串)+ 回填历史行
|
|
|
|
admin 比价记录页要把原「店/商品」一列拆成「店」+「商品」两列、并支持按商品名搜索。
|
|
items 是 JSON(SQLite 下 json.dumps ensure_ascii 会把中文转义存成 \\uXXXX,无法直接 CAST+LIKE
|
|
命中中文),故把商品名派生成普通文本列,跨库 LIKE 一致、可索引。
|
|
|
|
写路径(upsert_record / harvest_done)已同步派生;本迁移建列并从已存 items 回填历史行。
|
|
全新环境顺序应用即得空列 + 回填(表本为空,回填 no-op)。
|
|
|
|
Revision ID: comparison_product_names
|
|
Revises: comparison_record_trace_unique
|
|
Create Date: 2026-07-04 00:00:00.000000
|
|
"""
|
|
|
|
import json
|
|
from collections.abc import Sequence
|
|
|
|
import sqlalchemy as sa
|
|
|
|
from alembic import op
|
|
|
|
revision: str = "comparison_product_names"
|
|
down_revision: str | Sequence[str] | None = "comparison_record_trace_unique"
|
|
branch_labels: str | Sequence[str] | None = None
|
|
depends_on: str | Sequence[str] | None = None
|
|
|
|
|
|
def _product_names(items) -> str | None:
|
|
"""items([{name,...}]) → 顿号分隔的商品名串(去重保序)。与 repositories.comparison
|
|
._product_names_from_items 同口径,迁移内联一份避免耦合 app 代码。"""
|
|
if isinstance(items, str): # SQLite: items 以 JSON 文本存,取回是 str
|
|
try:
|
|
items = json.loads(items)
|
|
except (ValueError, TypeError):
|
|
return None
|
|
if not isinstance(items, list) or not items:
|
|
return None
|
|
names: list[str] = []
|
|
for it in items:
|
|
name = it.get("name") if isinstance(it, dict) else None
|
|
if not name:
|
|
continue
|
|
s = str(name).strip()
|
|
if s and s not in names:
|
|
names.append(s)
|
|
joined = "、".join(names)
|
|
return joined[:500] or None
|
|
|
|
|
|
def upgrade() -> None:
|
|
with op.batch_alter_table("comparison_record", schema=None) as batch_op:
|
|
batch_op.add_column(sa.Column("product_names", sa.String(length=512), nullable=True))
|
|
|
|
# 回填:从已存 items 派生商品名。items 小(菜名列表),一次取回即可;只更新有商品名的行。
|
|
bind = op.get_bind()
|
|
rows = bind.execute(
|
|
sa.text("SELECT id, items FROM comparison_record")
|
|
).fetchall()
|
|
for rid, items in rows:
|
|
pn = _product_names(items)
|
|
if pn:
|
|
bind.execute(
|
|
sa.text("UPDATE comparison_record SET product_names = :pn WHERE id = :id"),
|
|
{"pn": pn, "id": rid},
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
with op.batch_alter_table("comparison_record", schema=None) as batch_op:
|
|
batch_op.drop_column("product_names")
|