b76e5bd515
按 docs/数据库迁移.md 第 3 节扫雷清单落地代码层改动: - pyproject.toml: 新增 psycopg[binary]>=3.1 依赖 (psycopg3, SQLAlchemy 2.0 时代默认, 不要再装 psycopg2) - app/db/session.py: 非 SQLite 自动加连接池参数 - pool_size=10, max_overflow=20, pool_recycle=3600 - SQLite 单文件不池化, _is_sqlite 判断分支保留以便本地 dev 临时回退 - app/models/savings.py: dishes 列从 JSON 改为 JSONB - PG 上能用 GIN 索引和 jsonb 操作符 - SQLite 上 JSON 实际是 TEXT, 切到 PG 后用 JSON 类型存的是 json 不是 jsonb - alembic/versions/ef96beb47b1e_*.py: 新增迁移把 savings_record.dishes 从 json 转 jsonb - 用 op.get_bind().dialect.name 判断 PG 才执行 ALTER COLUMN - SQLite 上是 no-op (SQLite 没有 jsonb 类型) - 旧的 savings_shop_dishes.py 迁移保持 sa.JSON() 不动 (按文档"不改老迁移"原则) 本地切 PG 验证: 10 张表全部建出, dishes 字段为 jsonb 类型, 登录/me/美团 feed 三个真接口全 200, user 数据正确写入 PG. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
43 lines
2.0 KiB
Python
43 lines
2.0 KiB
Python
"""省钱记录表。
|
|
|
|
是 profile「累计帮你省了」和「省钱战绩」的唯一数据源:每完成一次比价/下单,
|
|
省了多少记一行。本期比价还没接后端,数据由 demo seeder 幂等灌入(source='demo'),
|
|
聚合接口照常做真实 SUM/分组;等比价真接入后,改成上报写入、停掉 seeder 即可。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, ForeignKey, Integer, String, func
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.db.base import Base
|
|
|
|
|
|
class SavingsRecord(Base):
|
|
__tablename__ = "savings_record"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
user_id: Mapped[int] = mapped_column(
|
|
Integer, ForeignKey("user.id"), index=True, nullable=False
|
|
)
|
|
# 订单金额 / 省下金额,单位:分
|
|
order_amount_cents: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
saved_amount_cents: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
platform: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
title: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
# 店铺名(订单明细卡标题,如「窑鸡王(王府井店)」)
|
|
shop_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
# 菜品名列表(JSONB),PG 上可建 GIN 索引,前 2 道直接展示,其余收进「还有 N 道菜」展开。
|
|
dishes: Mapped[list[str]] = mapped_column(JSONB, nullable=False, default=list)
|
|
# 来源:demo(演示) / compare(真实比价上报)
|
|
source: Mapped[str] = mapped_column(String(16), nullable=False, default="compare")
|
|
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), index=True, nullable=False
|
|
)
|
|
|
|
def __repr__(self) -> str: # pragma: no cover
|
|
return f"<SavingsRecord id={self.id} user_id={self.user_id} saved={self.saved_amount_cents}>"
|