Files
server-core/scripts/db/seed.py
T
Pine c2f4c13ab3 feat(auth): 账号唯一性+清理多余账号 —— 迁移0011加phone/微信openid非空部分唯一索引
- users.phone/wx_openid/wx_mini_openid/wx_unionid 各自唯一(非空部分唯一索引)
- 创建账号必生成ID(create用new_id);find_by_phone/find_by_wx 唯一校验
- cleanup_accounts.py 清理旧accounts已被users接管的重复账号;seed不再重复造账号、迁移后删旧行
2026-08-25 22:47:27 +08:00

117 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""数据库种子脚本(非运行态)—— 在迁移后、启动应用前执行。
用法:uv run python scripts/db/seed.py
职责:灌入平台/园区/培训的基础种子数据(角色、权限、用户、默认园区、培训事件课程等)。
全部幂等(按标记行判断,重跑安全)。禁止在应用启动时调用本逻辑。
"""
from __future__ import annotations
import asyncio
import logging
import sys
import os
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
from app.infrastructure.repositories import Database
from app.infrastructure.seed import seed_data as platform_seed
from app import config
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
log = logging.getLogger("db.seed")
def _seed_training() -> None:
"""培训种子(唯一总库 app.db):排期 + 在线课程(幂等)。
不再向旧 ``accounts`` 表种子账号 —— 平台规范账号已由 platform_seed 写入
``users``(如 u_demo_01/pine),旧 accounts 为「多余账号仓」已被清理
(见 scripts/db/cleanup_accounts.py),此处不再重复造账号。
"""
import sqlite3
from app.training import db as tdb
conn = sqlite3.connect(tdb.DB_PATH)
conn.row_factory = sqlite3.Row
try:
if conn.execute("SELECT COUNT(*) AS c FROM events").fetchone()["c"] == 0:
for e in tdb.SEED_EVENTS:
conn.execute("INSERT INTO events (id,type,mode,title,subtitle,desc,location,host,image,link,start_at,duration_min,capacity,status) VALUES (:id,:type,:mode,:title,:subtitle,:desc,:location,:host,:image,:link,:start_at,:duration_min,:capacity,:status)", e)
if conn.execute("SELECT COUNT(*) AS c FROM courses").fetchone()["c"] == 0:
for c in tdb.SEED_COURSES:
conn.execute("INSERT INTO courses (id,category,level,title,subtitle,desc,price,status,start_at,end_at,venue,quota,image,host,created_at) VALUES (:id,:category,:level,:title,:subtitle,:desc,:price,:status,:start_at,:end_at,:venue,:quota,:image,:host,:created_at)", c)
conn.commit()
log.info("培训种子完成(账号/排期/课程)")
finally:
conn.close()
async def _migrate_accounts_to_users(db) -> None:
"""把培训端 accounts 存量并入平台 users(幂等:已存在账号跳过并保证身份)。
迁移旧小程序账号 → userswx_mini_openid/phone 关联),使微信/手机登录仍命中,
且并入全局唯一账号体系。迁移/种子由用户执行(铁律),禁止运行态调用。
"""
import secrets
import sqlite3
from app.training import db as tdb
from app.api.routers.auth import _ensure_opc_identity
conn = sqlite3.connect(tdb.DB_PATH)
conn.row_factory = sqlite3.Row
migrated = created = 0
try:
for row in conn.execute("SELECT * FROM accounts"):
username = (row["username"] or "").strip()
wxid = (row["wxid"] or "").strip()
phone = (row["phone"] or "").strip()
if not username and not wxid and not phone:
continue
user = (await db.users.get_by_username(username)) if username else None
if user is None and phone:
user = await db.users.get_by_username(phone)
if user is None and wxid:
user = (await db.users.get_by_wx_mini_openid(wxid)
or await db.users.get_by_wx_openid(wxid))
if user is None:
uname = (username or phone or f"wx_{wxid[:24]}")
user = await db.users.create(
username=uname, password=secrets.token_hex(16),
nickname=(row["name"] or uname), avatar=(row["avatar"] or ""),
phone=phone, wx_mini_openid=wxid or "",
wx_openid=(f"wx_{wxid[:24]}" if wxid else ""),
source="mini_program",
auth_type=("wx_openid" if wxid else ("phone" if phone else "unknown")),
role="opc_member",
)
created += 1
await _ensure_opc_identity(db, user["id"])
# 迁移即接管:删掉旧 accounts 行,避免同一逻辑账号落在两套表(多余账号)。
conn.execute("DELETE FROM accounts WHERE id=?", (row["id"],))
migrated += 1
conn.commit()
log.info("accounts→users 迁移完成:共 %s 条,新建 %s 条(旧 accounts 行已清理)", migrated, created)
finally:
conn.close()
async def run() -> None:
db = Database()
try:
await platform_seed(db.session)
await db.session.commit()
log.info("平台种子完成:%s", config.DATABASE_URL)
_seed_training()
await _migrate_accounts_to_users(db)
except Exception as e: # noqa: BLE001
await db.session.rollback()
log.error("种子失败:%s", e)
raise
finally:
await db.close()
if __name__ == "__main__":
asyncio.run(run())