feat(groups): chat_group 补 6 群主/建群列 + 复合PK迁移修复 + 存量回填
对齐云客员工聊天窗口·微信群 tab(2026-08-25 真机验证 raw_json 键): - ChatGroup 加列 owner_name/owner_wxid/owner_alias/owner_avatar/ friend_count/create_time(全 nullable) - scraper _normalize_chat_group 同名提取;createTime 兼容 ms-int 与 'YYYY-MM-DD HH:mm:ss'(smoke.db 实测 1105 行全为后者),复用 _parse_add_time 归一 ms-epoch - 迁移 099ac2097fa2:加 6 列 + 修复 Task1 评审遗留 drift——PK 由单列 room_id 变更为复合 (room_id, channel, owner_account_id) 且 owner_account_id NOT NULL(对齐模型 8a99940;smoke.db 1105 行无 NULL, 未兜底编造数据);SQLite 走 batch 重建(copy_from 显式旧结构), MySQL 渲染为直接 ALTER(离线 --sql 已验证) - 回填脚本 scripts/backfill_chat_group_fields.py(幂等,逐列只补空); smoke.db 实跑 backfilled 1105/1105,重跑 0/1105 - GroupOut 加 6 字段(model_validate 自动输出,路由无改) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+108
@@ -0,0 +1,108 @@
|
||||
"""chat_group 群主补充列 + 复合主键修复
|
||||
|
||||
Revision ID: 099ac2097fa2
|
||||
Revises: 524da8d2d597
|
||||
Create Date: 2026-08-25 14:26:06.532289
|
||||
|
||||
内容
|
||||
----
|
||||
1. 对齐云客 员工聊天窗口·微信群 tab 的 6 个补充列(2026-08-25 真机验证
|
||||
raw_json 键 ownerName/ownerWchatId/ownerAlias/ownerImage/friendCount/
|
||||
createTime,由 scraper 与 scripts/backfill_chat_group_fields.py 提取;
|
||||
createTime 实测为 'YYYY-MM-DD HH:mm:ss' 字符串,归一为 ms-epoch):
|
||||
owner_name / owner_wxid / owner_alias / owner_avatar / friend_count /
|
||||
create_time。
|
||||
2. 修复模型-迁移 drift(Task 1 评审遗留):模型 PK 自 2026-08-24(commit
|
||||
8a99940)起为复合键 (room_id, channel, owner_account_id),但迁移链
|
||||
initial_schema 仍建单列 PrimaryKeyConstraint('room_id') 且
|
||||
owner_account_id nullable。本迁移把 PK 变更为复合键并将
|
||||
owner_account_id 置 NOT NULL(smoke.db 实测 1105 行无 NULL,可安全
|
||||
变更;MySQL 存量若存在 NULL 行会在此报错——那是必须人工核对的数据
|
||||
问题,不用兜底值编造)。
|
||||
|
||||
SQLite 不支持直接改 PK,batch_alter_table 自动走表重建;copy_from 显式
|
||||
给出迁移前表结构(含命名 PK 与索引)以便 drop/recreate 定位约束。
|
||||
MySQL 无需重建,渲染为直接 ALTER(ADD COLUMN / MODIFY / DROP PRIMARY
|
||||
KEY / ADD PRIMARY KEY)。
|
||||
"""
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '099ac2097fa2'
|
||||
down_revision: str | Sequence[str] | None = '524da8d2d597'
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
# 迁移前的 chat_group 结构(initial_schema 原样;PK 命名仅为 batch
|
||||
# drop_constraint 定位用,实际库中为匿名约束)。
|
||||
_chat_group_before = sa.Table(
|
||||
"chat_group",
|
||||
sa.MetaData(),
|
||||
sa.Column("room_id", sa.String(128), nullable=False),
|
||||
sa.Column("channel", sa.String(16), nullable=False),
|
||||
sa.Column("room_name", sa.String(255), nullable=True),
|
||||
sa.Column("owner_account_id", sa.String(128), nullable=True),
|
||||
sa.Column("owner_account_nickname", sa.String(255), nullable=True),
|
||||
sa.Column("member_count", sa.Integer(), nullable=True),
|
||||
sa.Column("member_ids", sa.Text(), nullable=True),
|
||||
sa.Column("head_image_url", sa.String(1024), nullable=True),
|
||||
sa.Column("owner_user_id", sa.String(64), nullable=True),
|
||||
sa.Column("owner_user_phone", sa.String(64), nullable=True),
|
||||
sa.Column("message_count", sa.Integer(), nullable=True),
|
||||
sa.Column("own_message_count", sa.Integer(), nullable=True),
|
||||
sa.Column("loss_members", sa.Integer(), nullable=True),
|
||||
sa.Column("add_members", sa.Integer(), nullable=True),
|
||||
sa.Column("last_chat_time", sa.BigInteger(), nullable=True),
|
||||
sa.Column("update_time", sa.BigInteger(), nullable=True),
|
||||
sa.Column("version", sa.String(64), nullable=True),
|
||||
sa.Column("raw_json", sa.JSON(), nullable=True),
|
||||
sa.Column(
|
||||
"fetched_at", sa.DateTime(), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.PrimaryKeyConstraint("room_id", name="pk_chat_group"),
|
||||
sa.Index("ix_chat_group_owner", "channel", "owner_account_id", "room_id"),
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
with op.batch_alter_table(
|
||||
"chat_group", schema=None, copy_from=_chat_group_before
|
||||
) as batch_op:
|
||||
# 1) 群主/建群补充列(全部 nullable,存量默认 NULL,回填脚本再提取)
|
||||
batch_op.add_column(sa.Column("owner_name", sa.String(255), nullable=True))
|
||||
batch_op.add_column(sa.Column("owner_wxid", sa.String(128), nullable=True))
|
||||
batch_op.add_column(sa.Column("owner_alias", sa.String(255), nullable=True))
|
||||
batch_op.add_column(sa.Column("owner_avatar", sa.String(1024), nullable=True))
|
||||
batch_op.add_column(sa.Column("friend_count", sa.Integer(), nullable=True))
|
||||
batch_op.add_column(sa.Column("create_time", sa.BigInteger(), nullable=True))
|
||||
# 2) 复合 PK 对齐模型(2026-08-24 8a99940):单列 PK 会把多员工同群
|
||||
# 错误去重;owner_account_id 入键故需 NOT NULL。
|
||||
batch_op.alter_column(
|
||||
"owner_account_id", existing_type=sa.String(128), nullable=False
|
||||
)
|
||||
batch_op.drop_constraint("pk_chat_group", type_="primary")
|
||||
batch_op.create_primary_key(
|
||||
"pk_chat_group", ["room_id", "channel", "owner_account_id"]
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# 降级需先把可能存在的多 owner 同群行去重才能退回单列 PK——不做自动
|
||||
# 删行,保留数据由人工处理;仅删列。
|
||||
with op.batch_alter_table("chat_group", schema=None) as batch_op:
|
||||
batch_op.drop_column("owner_name")
|
||||
batch_op.drop_column("owner_wxid")
|
||||
batch_op.drop_column("owner_alias")
|
||||
batch_op.drop_column("owner_avatar")
|
||||
batch_op.drop_column("friend_count")
|
||||
batch_op.drop_column("create_time")
|
||||
Reference in New Issue
Block a user