xianyu-auto-reply/fix_complete_isolation.py
2025-07-25 17:24:29 +08:00

318 lines
11 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
"""
完整的多用户数据隔离修复脚本
"""
import sqlite3
import json
import time
from loguru import logger
def backup_database():
"""备份数据库"""
try:
import shutil
timestamp = time.strftime("%Y%m%d_%H%M%S")
backup_file = f"xianyu_data_backup_{timestamp}.db"
shutil.copy2("xianyu_data.db", backup_file)
logger.info(f"数据库备份完成: {backup_file}")
return backup_file
except Exception as e:
logger.error(f"数据库备份失败: {e}")
return None
def add_user_id_columns():
"""为相关表添加user_id字段"""
conn = sqlite3.connect('xianyu_data.db')
cursor = conn.cursor()
try:
# 检查并添加user_id字段到cards表
cursor.execute("PRAGMA table_info(cards)")
columns = [column[1] for column in cursor.fetchall()]
if 'user_id' not in columns:
logger.info("为cards表添加user_id字段...")
cursor.execute('ALTER TABLE cards ADD COLUMN user_id INTEGER REFERENCES users(id)')
logger.info("✅ cards表user_id字段添加成功")
else:
logger.info("cards表已有user_id字段")
# 检查并添加user_id字段到delivery_rules表
cursor.execute("PRAGMA table_info(delivery_rules)")
columns = [column[1] for column in cursor.fetchall()]
if 'user_id' not in columns:
logger.info("为delivery_rules表添加user_id字段...")
cursor.execute('ALTER TABLE delivery_rules ADD COLUMN user_id INTEGER REFERENCES users(id)')
logger.info("✅ delivery_rules表user_id字段添加成功")
else:
logger.info("delivery_rules表已有user_id字段")
# 检查并添加user_id字段到notification_channels表
cursor.execute("PRAGMA table_info(notification_channels)")
columns = [column[1] for column in cursor.fetchall()]
if 'user_id' not in columns:
logger.info("为notification_channels表添加user_id字段...")
cursor.execute('ALTER TABLE notification_channels ADD COLUMN user_id INTEGER REFERENCES users(id)')
logger.info("✅ notification_channels表user_id字段添加成功")
else:
logger.info("notification_channels表已有user_id字段")
conn.commit()
return True
except Exception as e:
logger.error(f"添加user_id字段失败: {e}")
conn.rollback()
return False
finally:
conn.close()
def create_user_settings_table():
"""创建用户设置表"""
conn = sqlite3.connect('xianyu_data.db')
cursor = conn.cursor()
try:
# 检查表是否已存在
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='user_settings'")
if cursor.fetchone():
logger.info("user_settings表已存在")
return True
logger.info("创建user_settings表...")
cursor.execute('''
CREATE TABLE user_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
key TEXT NOT NULL,
value TEXT,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user_id, key)
)
''')
conn.commit()
logger.info("✅ user_settings表创建成功")
return True
except Exception as e:
logger.error(f"创建user_settings表失败: {e}")
conn.rollback()
return False
finally:
conn.close()
def migrate_existing_data():
"""迁移现有数据到admin用户"""
conn = sqlite3.connect('xianyu_data.db')
cursor = conn.cursor()
try:
# 获取admin用户ID
cursor.execute("SELECT id FROM users WHERE username = 'admin'")
admin_result = cursor.fetchone()
if not admin_result:
logger.error("未找到admin用户请先创建admin用户")
return False
admin_id = admin_result[0]
logger.info(f"找到admin用户ID: {admin_id}")
# 迁移cards表数据
cursor.execute("SELECT COUNT(*) FROM cards WHERE user_id IS NULL")
unbound_cards = cursor.fetchone()[0]
if unbound_cards > 0:
logger.info(f"迁移 {unbound_cards} 个未绑定的卡券到admin用户...")
cursor.execute("UPDATE cards SET user_id = ? WHERE user_id IS NULL", (admin_id,))
logger.info("✅ 卡券数据迁移完成")
# 迁移delivery_rules表数据
cursor.execute("SELECT COUNT(*) FROM delivery_rules WHERE user_id IS NULL")
unbound_rules = cursor.fetchone()[0]
if unbound_rules > 0:
logger.info(f"迁移 {unbound_rules} 个未绑定的发货规则到admin用户...")
cursor.execute("UPDATE delivery_rules SET user_id = ? WHERE user_id IS NULL", (admin_id,))
logger.info("✅ 发货规则数据迁移完成")
# 迁移notification_channels表数据
cursor.execute("SELECT COUNT(*) FROM notification_channels WHERE user_id IS NULL")
unbound_channels = cursor.fetchone()[0]
if unbound_channels > 0:
logger.info(f"迁移 {unbound_channels} 个未绑定的通知渠道到admin用户...")
cursor.execute("UPDATE notification_channels SET user_id = ? WHERE user_id IS NULL", (admin_id,))
logger.info("✅ 通知渠道数据迁移完成")
conn.commit()
return True
except Exception as e:
logger.error(f"数据迁移失败: {e}")
conn.rollback()
return False
finally:
conn.close()
def verify_isolation():
"""验证数据隔离效果"""
conn = sqlite3.connect('xianyu_data.db')
cursor = conn.cursor()
try:
logger.info("验证数据隔离效果...")
# 检查各表的用户分布
tables = ['cards', 'delivery_rules', 'notification_channels']
for table in tables:
cursor.execute(f'''
SELECT u.username, COUNT(*) as count
FROM {table} t
JOIN users u ON t.user_id = u.id
GROUP BY u.id, u.username
ORDER BY count DESC
''')
results = cursor.fetchall()
logger.info(f"📊 {table} 表用户分布:")
for username, count in results:
logger.info(f"{username}: {count} 条记录")
# 检查是否有未绑定的数据
cursor.execute(f"SELECT COUNT(*) FROM {table} WHERE user_id IS NULL")
unbound_count = cursor.fetchone()[0]
if unbound_count > 0:
logger.warning(f"⚠️ {table} 表还有 {unbound_count} 条未绑定用户的记录")
else:
logger.info(f"{table} 表所有记录都已正确绑定用户")
return True
except Exception as e:
logger.error(f"验证失败: {e}")
return False
finally:
conn.close()
def create_default_user_settings():
"""为现有用户创建默认设置"""
conn = sqlite3.connect('xianyu_data.db')
cursor = conn.cursor()
try:
logger.info("为现有用户创建默认设置...")
# 获取所有用户
cursor.execute("SELECT id, username FROM users")
users = cursor.fetchall()
default_settings = [
('theme_color', '#1890ff', '主题颜色'),
('language', 'zh-CN', '界面语言'),
('notification_enabled', 'true', '通知开关'),
('auto_refresh', 'true', '自动刷新'),
]
for user_id, username in users:
logger.info(f"为用户 {username} 创建默认设置...")
for key, value, description in default_settings:
# 检查设置是否已存在
cursor.execute(
"SELECT id FROM user_settings WHERE user_id = ? AND key = ?",
(user_id, key)
)
if not cursor.fetchone():
cursor.execute('''
INSERT INTO user_settings (user_id, key, value, description)
VALUES (?, ?, ?, ?)
''', (user_id, key, value, description))
conn.commit()
logger.info("✅ 默认用户设置创建完成")
return True
except Exception as e:
logger.error(f"创建默认用户设置失败: {e}")
conn.rollback()
return False
finally:
conn.close()
def main():
"""主函数"""
print("🚀 完整的多用户数据隔离修复")
print("=" * 60)
# 1. 备份数据库
print("\n📦 1. 备份数据库")
backup_file = backup_database()
if not backup_file:
print("❌ 数据库备份失败,停止修复")
return False
# 2. 添加user_id字段
print("\n🔧 2. 添加用户隔离字段")
if not add_user_id_columns():
print("❌ 添加user_id字段失败")
return False
# 3. 创建用户设置表
print("\n📋 3. 创建用户设置表")
if not create_user_settings_table():
print("❌ 创建用户设置表失败")
return False
# 4. 迁移现有数据
print("\n📦 4. 迁移现有数据")
if not migrate_existing_data():
print("❌ 数据迁移失败")
return False
# 5. 创建默认用户设置
print("\n⚙️ 5. 创建默认用户设置")
if not create_default_user_settings():
print("❌ 创建默认用户设置失败")
return False
# 6. 验证隔离效果
print("\n🔍 6. 验证数据隔离")
if not verify_isolation():
print("❌ 验证失败")
return False
print("\n" + "=" * 60)
print("🎉 多用户数据隔离修复完成!")
print("\n📋 修复内容:")
print("✅ 1. 为cards表添加用户隔离")
print("✅ 2. 为delivery_rules表添加用户隔离")
print("✅ 3. 为notification_channels表添加用户隔离")
print("✅ 4. 创建用户设置表")
print("✅ 5. 迁移现有数据到admin用户")
print("✅ 6. 创建默认用户设置")
print("\n⚠️ 下一步:")
print("1. 重启服务以应用数据库更改")
print("2. 运行API接口修复脚本")
print("3. 测试多用户数据隔离")
print("4. 更新前端代码")
print(f"\n💾 数据库备份文件: {backup_file}")
print("如有问题,可以使用备份文件恢复数据")
return True
if __name__ == "__main__":
main()