Files
Django/migrate_data.py

464 lines
18 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.
#!/usr/bin/env python3
"""
migrate_data.py — 从 SQL 备份文件直接迁移数据到新的 gvsdsdk User + RBAC 体系
v3: 正确的字段映射 + 基于扩展表存在性分配角色 + bulk_create + 实时进度
"""
import os, sys, re, uuid, time, datetime, MySQLdb
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'a_long_dianjing.settings')
import django; django.setup()
from app_secrets import DATABASE_HOST, DATABASE_PORT, DATABASE_USER, DATABASE_PASSWORD, DATABASE_NAME
TENANT_UUID = uuid.UUID('a0000000-0000-0000-0000-000000000001').bytes
COMPANY_UUID = uuid.UUID('b0000000-0000-0000-0000-000000000001').bytes
BATCH_SIZE = 500
ROLE_MAP = {
'normal': '消费者', 'dashou': '打手', 'shop': '商家',
'guanshi': '负责人', 'admin': '管理员', 'kefu': '客服',
'zuzhang': '组长', 'shenheguan': '审核官',
}
# 角色优先级(高→低): 管理员 > 客服 > 负责人 > 组长 > 审核官 > 商家 > 打手 > 消费者
ROLE_PRIORITY = ['admin', 'kefu', 'guanshi', 'zuzhang', 'shenheguan', 'shop', 'dashou', 'normal']
# 扩展表 → 角色类型 映射
EXT_ROLE_MAP = {
'admin_profile': 'admin',
'user_kefu': 'kefu',
'user_guanshi': 'guanshi',
'user_zuzhang': 'zuzhang',
'user_shenheguan': 'shenheguan',
'user_shangjia': 'shop',
'user_dashou': 'dashou',
}
SQL_FILE = os.path.join(os.path.dirname(__file__), 'xaio_cheng_xu_backup.sql')
class Progress:
def __init__(self, total, width=35):
self.total = total; self.width = width; self.start = time.time(); self.done = 0
def update(self, n=1):
self.done += n; pct = self.done / self.total if self.total else 0
filled = int(self.width * pct); bar = '' * filled + '' * (self.width - filled)
elapsed = time.time() - self.start
eta = (elapsed / self.done * (self.total - self.done)) if self.done else 0
sys.stdout.write(f'\r [{bar}] {pct:5.1%} {self.done}/{self.total} {elapsed:.0f}s ETA{eta:.0f}s')
sys.stdout.flush()
def finish(self):
elapsed = time.time() - self.start
sys.stdout.write(f'\r [{""*self.width}] 100% {self.done}/{self.total} {elapsed:.0f}s\n')
sys.stdout.flush()
def parse_insert_values(table_name, sql_content):
pattern = re.compile(r"INSERT\s+INTO\s+`" + re.escape(table_name) + r"`\s+VALUES\s*\((.+?)\)\s*;", re.IGNORECASE | re.DOTALL)
return [m.group(1) for m in pattern.finditer(sql_content)]
def parse_sql_value(val_str):
val_str = val_str.strip()
if val_str.upper() == 'NULL': return None
if val_str.startswith("'") and val_str.endswith("'"):
return val_str[1:-1].replace("\\'", "'").replace("\\\\", "\\")
try: return int(val_str)
except ValueError:
try: return float(val_str)
except ValueError: return val_str
def split_sql_values(values_str):
values, current, in_quote, escape = [], [], False, False
for ch in values_str:
if escape: current.append(ch); escape = False; continue
if ch == '\\': current.append(ch); escape = True; continue
if ch == "'": in_quote = not in_quote; current.append(ch); continue
if ch == ',' and not in_quote: values.append(''.join(current).strip()); current = []; continue
current.append(ch)
if current: values.append(''.join(current).strip())
return [parse_sql_value(v) for v in values]
def parse_dt(v):
if isinstance(v, str) and v:
try: return datetime.datetime.fromisoformat(v.replace(' ', 'T'))
except: pass
return datetime.datetime.utcnow()
def get_raw_conn(db=None):
kwargs = dict(host=DATABASE_HOST, port=int(DATABASE_PORT), user=DATABASE_USER,
password=DATABASE_PASSWORD, charset='utf8mb4', connect_timeout=10,
read_timeout=60, write_timeout=60)
if db: kwargs['database'] = db
return MySQLdb.connect(**kwargs)
def run_migration():
from gvsdsdk.models import User, Role, UserRole
from users.models import (
UserBoss, UserDashou, UserShangjia, UserGuanshi,
UserZuzhang, UserShenheguan, AdminProfile, UserKefu,
UserSensitiveData
)
print("=" * 60)
print("开始从 SQL 备份迁移数据到 gvsdsdk.User + RBAC")
print("=" * 60)
# [0] 读取 SQL
print("\n[0/7] 读取 SQL 备份文件...", end=' ')
with open(SQL_FILE, 'r', encoding='utf-8') as f:
sql_content = f.read()
print(f"{len(sql_content)/1024/1024:.1f} MB")
# [1] 解析所有扩展表的 user_id → 建立角色映射
print("\n[1/7] 解析扩展表建立角色映射...")
# 正确的 user_id 索引位置
ext_user_id_idx = {
'user_dashou': 22,
'user_guanshi': 11,
'user_kefu': 9,
'user_shangjia': 14,
'user_boss': 6,
'admin_profile': 4,
'user_zuzhang': 17,
'user_shenheguan': 9,
}
# user_main.id → 角色集合
user_roles = {} # old_user_id -> set of role_types
for table_name, role_type in EXT_ROLE_MAP.items():
inserts = parse_insert_values(table_name, sql_content)
uid_idx = ext_user_id_idx[table_name]
count = 0
for values_str in inserts:
vals = split_sql_values(values_str)
if uid_idx < len(vals) and vals[uid_idx] is not None:
old_uid = vals[uid_idx]
if old_uid not in user_roles:
user_roles[old_uid] = set()
user_roles[old_uid].add(role_type)
count += 1
print(f" {table_name}: {count} 条有 user_id")
# [2] 解析 user_main
print("\n[2/7] 解析 user_main...")
user_main_inserts = parse_insert_values('user_main', sql_content)
total = len(user_main_inserts)
print(f"{total} 条记录")
uid_map = {} # yonghuid -> UserUUID (bytes)
user_id_map = {} # old id -> yonghuid
phone_to_yonghuid = {}
# 先解析所有 user_main 记录
user_data = [] # (old_id, yonghuid, fields_dict, role_type)
prog = Progress(total)
for i, values_str in enumerate(user_main_inserts):
vals = split_sql_values(values_str)
if len(vals) < 16:
prog.update(); continue
old_id = vals[1]
yonghuid = str(vals[2]) if vals[2] else None
if not yonghuid:
prog.update(); continue
# 确定角色:基于扩展表存在性 + user_type
roles = user_roles.get(old_id, set())
user_type = str(vals[11]) if vals[11] else 'normal'
# user_type 也可能指定角色
if user_type == 'admin':
roles.add('admin')
elif user_type == 'kefu':
roles.add('kefu')
# 如果没有任何角色,默认为 normal
if not roles:
roles.add('normal')
# 选择优先级最高的角色
primary_role = 'normal'
for r in ROLE_PRIORITY:
if r in roles:
primary_role = r
break
user_data.append((old_id, yonghuid, vals, primary_role, roles))
user_id_map[old_id] = yonghuid
phone = str(vals[5]) if vals[5] else ''
if phone: phone_to_yonghuid[phone] = yonghuid
prog.update()
prog.finish()
print(f" 解析完成: {len(user_data)} 条有效记录")
# [3] 创建角色
print("\n[3/7] 创建角色...")
created_roles = {}
for user_type, role_name in ROLE_MAP.items():
role, created = Role.objects.get_or_create(
RoleName=role_name, TenantUUID=TENANT_UUID,
defaults={'RoleUUID': uuid.uuid4().bytes, 'RoleDesc': f'{role_name}角色', 'RoleStatus': 1, 'AssignScope': 0})
created_roles[user_type] = role
print(f" {role_name}: {'NEW' if created else 'EXISTS'}")
# [4] 批量创建 User + UserRole + UserSensitiveData
print("\n[4/7] 批量创建 User + UserRole + UserSensitiveData...")
user_batch, ur_batch, usd_batch = [], [], []
errors = 0
prog = Progress(len(user_data))
for old_id, yonghuid, vals, primary_role, all_roles in user_data:
openid = str(vals[3]) if vals[3] else ''
avatar = str(vals[4]) if vals[4] else 'a_long/morentouxiang.jpg'
phone = str(vals[5]) if vals[5] else ''
password = str(vals[6]) if vals[6] else ''
zhifu = str(vals[7]) if vals[7] else ''
skzhanghao = str(vals[8]) if vals[8] else ''
ip = str(vals[9]) if vals[9] else ''
last_login_time = vals[10]
create_time = vals[12]
unionid = str(vals[14]) if vals[14] else ''
shoujihao_renzheng = bool(vals[15]) if vals[15] is not None else False
user_uuid = uuid.uuid4().bytes
ct, lt = parse_dt(create_time), parse_dt(last_login_time)
user_batch.append(User(
UserUUID=user_uuid, UserName=yonghuid, UserUID=yonghuid,
OpenID=openid, UnionID=unionid, Avatar=avatar, Phone=phone,
PhoneVerified=shoujihao_renzheng, IP=ip, UserAccountLicense=1,
UserPositionStatus=0, UserCreateTime=ct, UserLastLoginDate=lt,
IsStaff=('admin' in all_roles), IsSuperuser=('admin' in all_roles)))
# 为每个角色创建 UserRole多对多
for role_type in all_roles:
role = created_roles.get(role_type)
if role:
ur_batch.append(UserRole(
UserRoleUUID=uuid.uuid4().bytes, UserUUID=user_uuid,
RoleUUID=role.RoleUUID, CompanyUUID=COMPANY_UUID))
usd_batch.append(UserSensitiveData(
user_uuid_id=user_uuid, password=password, zhifu=zhifu,
skzhanghao=skzhanghao, shoujihao_renzheng=shoujihao_renzheng))
uid_map[yonghuid] = user_uuid
if len(user_batch) >= BATCH_SIZE:
try:
User.objects.bulk_create(user_batch, ignore_conflicts=True)
UserRole.objects.bulk_create(ur_batch, ignore_conflicts=True)
UserSensitiveData.objects.bulk_create(usd_batch, ignore_conflicts=True)
except Exception: errors += len(user_batch)
user_batch, ur_batch, usd_batch = [], [], []
prog.update()
if user_batch:
try:
User.objects.bulk_create(user_batch, ignore_conflicts=True)
UserRole.objects.bulk_create(ur_batch, ignore_conflicts=True)
UserSensitiveData.objects.bulk_create(usd_batch, ignore_conflicts=True)
except Exception: errors += len(user_batch)
prog.finish()
print(f" 成功: {len(uid_map)}, 错误: {errors}")
# [5] 迁移扩展表 — 用 ORM bulk_create正确字段映射
print("\n[5/7] 迁移扩展表...")
# 正确的字段映射: (sql_index, model_field_name)
# None = 跳过, '_user_id' = FK 到新 User
ext_configs = [
('user_dashou', UserDashou, [
(0, None), (1, 'nicheng'), (2, 'chenghao'), (3, 'zhuangtai'), (4, 'zaixianzhuangtai'),
(5, 'zhanghaozhuangtai'), (6, 'jieshao'), (7, 'jiedanzongliang'), (8, 'chengjiaozongliang'),
(9, 'tuikuanliang'), (10, 'yue'), (11, 'zonge'), (12, 'dianhua'), (13, 'wechat'),
(14, 'yaoqingren'), (15, 'jinrijiedan'), (16, 'jinrishouyi'), (17, 'jinyuejiedan'),
(18, 'jinyueshouyi'), (19, 'jifen'), (20, 'yajin'), (21, None), # create_time
(22, '_user_id'), # user_id FK
(23, 'ewai_xiane'), (24, 'jinritixian_jine'), (25, 'kaioi_ewai_xiane'),
(26, 'last_tixian_date'),
]),
('user_guanshi', UserGuanshi, [
(0, None), (1, 'yaoqingma'), (2, 'dianhua'), (3, 'wechat'),
(4, 'yaogingshuliang'), (5, 'zhuangtai'), (6, 'jinrichongzhi'),
(7, 'jinyuechongzhi'), (8, 'chongzhifenrun'), (9, 'yue'), (10, None), # create_time
(11, '_user_id'),
(12, 'fenghong_erci'), (13, 'fenghong_erci_enabled'), (14, 'fenhong_yici'),
(15, 'ewai_xiane'), (16, 'jinritixian_jine'), (17, 'kaioi_ewai_xiane'),
(18, 'last_tixian_date'), (19, 'yaoqingren'), (20, 'invite_qrcode_url'),
]),
('user_kefu', UserKefu, [
(0, None), (1, 'nicheng'), (2, 'erjimima'), (3, 'zhuangtai'),
(4, 'jinrichuli'), (5, 'jinyuechuli'), (6, 'zongchuli'),
(7, None), (8, None), (9, '_user_id'),
]),
('user_shangjia', UserShangjia, [
(0, None), (1, 'nicheng'), (2, 'zhuangtai'), (3, 'dianhua'), (4, 'wechat'),
(5, 'fabu'), (6, 'tuikuan'), (7, 'yue'), (8, 'chengjiao'),
(9, 'jinridingdan'), (10, 'jinriliushui'), (11, 'jinyuedingdan'),
(12, 'jinyueliushui'), (13, None), (14, '_user_id'),
]),
('user_boss', UserBoss, [
(0, None), (1, 'nickname'), (2, 'zonge'), (3, 'alldingdan'),
(4, 'alltui'), (5, None), (6, '_user_id'),
]),
('admin_profile', AdminProfile, [
(0, None), (1, 'password'), (2, 'wechat'), (3, None),
(4, '_user_id'), (5, 'yaoqingma'),
]),
('user_zuzhang', UserZuzhang, [
(0, None), (1, 'yaoqingma'), (2, 'fenyong_zonge'), (3, 'ketixian_jine'),
(4, 'yaoqing_zongshu'), (5, 'jinri_fenyong'), (6, 'jinyue_fenyong'),
(7, 'zhuangtai'), (8, 'haibao_url'), (9, 'jinri_tixian'), (10, 'last_tixian_time'),
(11, 'ewai_tixian_xiane'), (12, 'kaioi_ewai_tixian'), (13, 'kaioi_ewai_fenhong'),
(14, 'ewai_fenhong_jine'), (15, None), (16, None), (17, '_user_id'),
]),
('user_shenheguan', UserShenheguan, [
(0, None), (1, 'zhuangtai'), (2, 'shenhe_zhuangtai'),
(3, 'shenhe_zongshu'), (4, 'tongguo_zongshu'), (5, 'yue'), (6, 'zonge'),
(7, None), (8, None), (9, '_user_id'), (10, 'is_renzheng'),
]),
]
for table_name, model, field_map in ext_configs:
inserts = parse_insert_values(table_name, sql_content)
if not inserts:
print(f" {table_name}: 0 条"); continue
user_id_sql_idx = next((idx for idx, fn in field_map if fn == '_user_id'), None)
if user_id_sql_idx is None:
print(f" {table_name}: 无法定位 user_id"); continue
batch = []
success = 0
fail = 0
prog = Progress(len(inserts))
for values_str in inserts:
vals = split_sql_values(values_str)
if not vals or user_id_sql_idx >= len(vals) or vals[user_id_sql_idx] is None:
fail += 1; prog.update(); continue
old_user_id = vals[user_id_sql_idx]
yonghuid = user_id_map.get(old_user_id)
if not yonghuid:
fail += 1; prog.update(); continue
new_uuid = uid_map.get(yonghuid)
if not new_uuid:
fail += 1; prog.update(); continue
try:
obj = model(user_id=new_uuid)
for sql_idx, field_name in field_map:
if field_name is None or field_name == '_user_id':
continue
if sql_idx < len(vals) and vals[sql_idx] is not None:
try: setattr(obj, field_name, vals[sql_idx])
except: pass
batch.append(obj)
success += 1
except Exception:
fail += 1
if len(batch) >= BATCH_SIZE:
try: model.objects.bulk_create(batch, ignore_conflicts=True)
except: pass
batch = []
prog.update()
if batch:
try: model.objects.bulk_create(batch, ignore_conflicts=True)
except: pass
prog.finish()
print(f" 成功: {success}, 失败(无user_id): {fail}")
# [6] 迁移其他引用表
print("\n[6/7] 迁移其他引用 yonghuid 的表...")
other_tables = [
'tixianjilu', 'tixian_shenhe_jilu', 'tixian_auto_record',
'xiugaijilu', 'ranking_record',
]
conn = get_raw_conn(db=DATABASE_NAME)
cursor = conn.cursor()
cursor.execute('SET FOREIGN_KEY_CHECKS = 0')
for table_name in other_tables:
inserts = parse_insert_values(table_name, sql_content)
if not inserts:
print(f" {table_name}: 0 条"); continue
success = 0
prog = Progress(len(inserts))
for values_str in inserts:
sql = f"INSERT INTO `{table_name}` VALUES ({values_str})"
try:
cursor.execute(sql)
success += 1
except Exception:
pass
if success % 100 == 0 and success > 0:
try: conn.commit()
except: pass
prog.update()
try: conn.commit()
except MySQLdb.OperationalError:
conn = get_raw_conn(db=DATABASE_NAME)
cursor = conn.cursor()
cursor.execute('SET FOREIGN_KEY_CHECKS = 0')
prog.finish()
print(f" 成功: {success}")
try:
cursor.execute('SET FOREIGN_KEY_CHECKS = 1')
conn.commit(); conn.close()
except: pass
# [7] 验证
print("\n[7/7] 验证迁移结果...")
new_user_count = User.objects.count()
new_ur_count = UserRole.objects.count()
usd_count = UserSensitiveData.objects.count()
print(f" gvsdsdk.User: {new_user_count}")
print(f" UserRole: {new_ur_count}")
print(f" UserSensitiveData: {usd_count}")
print("\n 角色分布:")
for user_type, role in created_roles.items():
ur_count = UserRole.objects.filter(RoleUUID=role.RoleUUID).count()
print(f" {ROLE_MAP[user_type]}: {ur_count}")
print("\n 扩展表:")
for name, model in [('user_dashou', UserDashou), ('user_guanshi', UserGuanshi),
('user_kefu', UserKefu), ('user_shangjia', UserShangjia),
('user_boss', UserBoss), ('admin_profile', AdminProfile),
('user_zuzhang', UserZuzhang), ('user_shenheguan', UserShenheguan)]:
print(f" {name}: {model.objects.count()}")
print("\n" + "=" * 60)
print("迁移完成!")
print(f"TENANT_UUID = {uuid.UUID(bytes=TENANT_UUID)}")
print(f"COMPANY_UUID = {uuid.UUID(bytes=COMPANY_UUID)}")
print("=" * 60)
if __name__ == '__main__':
run_migration()