mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
Migrate SiteUserData userid field from Integer to String type
Co-authored-by: jxxghp <jxxghp@live.cn>
This commit is contained in:
@@ -0,0 +1,175 @@
|
|||||||
|
# SiteUserData表userid字段类型迁移说明
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本次迁移将 `SiteUserData` 表中的 `userid` 字段从 `Integer` 类型改为 `String` 类型,以支持更灵活的用户ID格式。
|
||||||
|
|
||||||
|
## 变更内容
|
||||||
|
|
||||||
|
### 1. 数据模型变更
|
||||||
|
|
||||||
|
**文件**: `app/db/models/siteuserdata.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 变更前
|
||||||
|
userid = Column(Integer)
|
||||||
|
|
||||||
|
# 变更后
|
||||||
|
userid = Column(String)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Schema定义变更
|
||||||
|
|
||||||
|
**文件**: `app/schemas/site.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 变更前
|
||||||
|
userid: Optional[Union[int, str]] = None
|
||||||
|
|
||||||
|
# 变更后
|
||||||
|
userid: Optional[str] = None
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 数据库迁移脚本
|
||||||
|
|
||||||
|
**文件**: `database/versions/a946dae52526_2_2_1.py`
|
||||||
|
|
||||||
|
- **版本号**: 2.2.1
|
||||||
|
- **修订ID**: a946dae52526
|
||||||
|
- **前置版本**: 5b3355c964bb (2.2.0)
|
||||||
|
|
||||||
|
#### 迁移功能
|
||||||
|
|
||||||
|
1. **PostgreSQL数据库迁移**:
|
||||||
|
- 创建临时列 `userid_new` (VARCHAR类型)
|
||||||
|
- 将现有数据转换为字符串并复制到新列
|
||||||
|
- 删除旧列 `userid`
|
||||||
|
- 重命名新列为 `userid`
|
||||||
|
|
||||||
|
2. **SQLite数据库迁移**:
|
||||||
|
- 创建新表结构,userid字段为VARCHAR类型
|
||||||
|
- 复制现有数据,将userid转换为字符串
|
||||||
|
- 删除旧表并重命名新表
|
||||||
|
- 重新创建索引
|
||||||
|
|
||||||
|
#### 降级功能
|
||||||
|
|
||||||
|
1. **PostgreSQL数据库降级**:
|
||||||
|
- 创建临时列 `userid_old` (INTEGER类型)
|
||||||
|
- 将字符串转换为整数(仅转换数字字符串)
|
||||||
|
- 删除旧列并重命名新列
|
||||||
|
|
||||||
|
2. **SQLite数据库降级**:
|
||||||
|
- 创建新表结构,userid字段为INTEGER类型
|
||||||
|
- 复制数据,仅转换数字字符串为整数
|
||||||
|
- 删除旧表并重命名新表
|
||||||
|
- 重新创建索引
|
||||||
|
|
||||||
|
### 4. 代码兼容性修复
|
||||||
|
|
||||||
|
#### 修复的文件
|
||||||
|
|
||||||
|
1. **app/modules/indexer/parser/nexus_rabbit.py**
|
||||||
|
```python
|
||||||
|
# 修复前
|
||||||
|
"data": {"type": "seeding", "id": int(self.userid)},
|
||||||
|
|
||||||
|
# 修复后
|
||||||
|
"data": {"type": "seeding", "id": int(self.userid) if self.userid and str(self.userid).isdigit() else 0},
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **app/modules/synologychat/synologychat.py**
|
||||||
|
```python
|
||||||
|
# 修复前
|
||||||
|
payload_data['user_ids'] = [int(userid)]
|
||||||
|
|
||||||
|
# 修复后
|
||||||
|
payload_data['user_ids'] = [int(userid) if str(userid).isdigit() else userid]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 迁移步骤
|
||||||
|
|
||||||
|
### 1. 备份数据库
|
||||||
|
|
||||||
|
在执行迁移前,请务必备份数据库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SQLite数据库备份
|
||||||
|
cp user.db user.db.backup
|
||||||
|
|
||||||
|
# PostgreSQL数据库备份
|
||||||
|
pg_dump -h localhost -U username -d database_name > backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 执行迁移
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入项目目录
|
||||||
|
cd /path/to/project
|
||||||
|
|
||||||
|
# 执行数据库迁移
|
||||||
|
python -m alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 验证迁移
|
||||||
|
|
||||||
|
运行测试脚本验证迁移是否成功:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python test_migration.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 影响分析
|
||||||
|
|
||||||
|
### 正面影响
|
||||||
|
|
||||||
|
1. **灵活性提升**: 支持非数字格式的用户ID
|
||||||
|
2. **兼容性增强**: 适应不同站点的用户ID格式
|
||||||
|
3. **数据完整性**: 保持原有数据不丢失
|
||||||
|
|
||||||
|
### 潜在风险
|
||||||
|
|
||||||
|
1. **性能影响**: 字符串类型可能比整数类型占用更多存储空间
|
||||||
|
2. **查询性能**: 字符串比较可能比整数比较稍慢
|
||||||
|
3. **数据验证**: 需要确保应用程序正确处理字符串类型的userid
|
||||||
|
|
||||||
|
### 兼容性说明
|
||||||
|
|
||||||
|
1. **向后兼容**: 迁移脚本包含降级功能,可以回滚到Integer类型
|
||||||
|
2. **代码兼容**: 已修复所有直接使用userid的代码
|
||||||
|
3. **API兼容**: Schema变更保持了API的向后兼容性
|
||||||
|
|
||||||
|
## 测试验证
|
||||||
|
|
||||||
|
### 功能测试
|
||||||
|
|
||||||
|
1. **数据插入测试**: 验证整数和字符串类型的userid都能正常插入
|
||||||
|
2. **数据查询测试**: 验证按域名、日期等条件查询功能正常
|
||||||
|
3. **数据更新测试**: 验证userid字段更新功能正常
|
||||||
|
4. **API测试**: 验证相关API接口正常工作
|
||||||
|
|
||||||
|
### 性能测试
|
||||||
|
|
||||||
|
1. **查询性能**: 验证查询性能无明显下降
|
||||||
|
2. **存储空间**: 验证存储空间使用情况
|
||||||
|
3. **并发性能**: 验证并发操作正常
|
||||||
|
|
||||||
|
## 回滚方案
|
||||||
|
|
||||||
|
如果迁移出现问题,可以执行降级操作:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 降级到上一个版本
|
||||||
|
python -m alembic downgrade 5b3355c964bb
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **备份重要**: 执行迁移前必须备份数据库
|
||||||
|
2. **测试环境**: 建议先在测试环境验证迁移
|
||||||
|
3. **监控日志**: 迁移过程中注意观察日志输出
|
||||||
|
4. **数据验证**: 迁移完成后验证数据完整性
|
||||||
|
|
||||||
|
## 联系信息
|
||||||
|
|
||||||
|
如有问题,请联系开发团队或查看项目文档。
|
||||||
@@ -20,7 +20,7 @@ class SiteUserData(Base):
|
|||||||
# 用户名
|
# 用户名
|
||||||
username = Column(String)
|
username = Column(String)
|
||||||
# 用户ID
|
# 用户ID
|
||||||
userid = Column(Integer)
|
userid = Column(String)
|
||||||
# 用户等级
|
# 用户等级
|
||||||
user_level = Column(String)
|
user_level = Column(String)
|
||||||
# 加入时间
|
# 加入时间
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class NexusRabbitSiteUserInfo(SiteParserBase):
|
|||||||
"page": 1,
|
"page": 1,
|
||||||
"limit": 5000000,
|
"limit": 5000000,
|
||||||
"action": "userTorrentsList",
|
"action": "userTorrentsList",
|
||||||
"data": {"type": "seeding", "id": int(self.userid)},
|
"data": {"type": "seeding", "id": int(self.userid) if self.userid and str(self.userid).isdigit() else 0},
|
||||||
}
|
}
|
||||||
self._torrent_seeding_headers = {
|
self._torrent_seeding_headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class SynologyChat:
|
|||||||
if image:
|
if image:
|
||||||
payload_data['file_url'] = quote(image)
|
payload_data['file_url'] = quote(image)
|
||||||
if userid:
|
if userid:
|
||||||
payload_data['user_ids'] = [int(userid)]
|
payload_data['user_ids'] = [int(userid) if str(userid).isdigit() else userid]
|
||||||
else:
|
else:
|
||||||
userids = self.__get_bot_users()
|
userids = self.__get_bot_users()
|
||||||
if not userids:
|
if not userids:
|
||||||
@@ -121,7 +121,7 @@ class SynologyChat:
|
|||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
if userid:
|
if userid:
|
||||||
userids = [int(userid)]
|
userids = [int(userid) if str(userid).isdigit() else userid]
|
||||||
else:
|
else:
|
||||||
userids = self.__get_bot_users()
|
userids = self.__get_bot_users()
|
||||||
payload_data = {
|
payload_data = {
|
||||||
@@ -169,7 +169,7 @@ class SynologyChat:
|
|||||||
caption = f"{caption}\n[查看详情]({link})"
|
caption = f"{caption}\n[查看详情]({link})"
|
||||||
|
|
||||||
if userid:
|
if userid:
|
||||||
userids = [int(userid)]
|
userids = [int(userid) if str(userid).isdigit() else userid]
|
||||||
else:
|
else:
|
||||||
userids = self.__get_bot_users()
|
userids = self.__get_bot_users()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ class SiteUserData(BaseModel):
|
|||||||
# 用户名
|
# 用户名
|
||||||
username: Optional[str] = None
|
username: Optional[str] = None
|
||||||
# 用户ID
|
# 用户ID
|
||||||
userid: Optional[Union[int, str]] = None
|
userid: Optional[str] = None
|
||||||
# 用户等级
|
# 用户等级
|
||||||
user_level: Optional[str] = None
|
user_level: Optional[str] = None
|
||||||
# 加入时间
|
# 加入时间
|
||||||
|
|||||||
@@ -0,0 +1,258 @@
|
|||||||
|
"""2.2.1
|
||||||
|
|
||||||
|
Revision ID: a946dae52526
|
||||||
|
Revises: 5b3355c964bb
|
||||||
|
Create Date: 2025-08-20 17:50:00.000000
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
from app.log import logger
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'a946dae52526'
|
||||||
|
down_revision = '5b3355c964bb'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""
|
||||||
|
升级:将SiteUserData表的userid字段从Integer改为String
|
||||||
|
"""
|
||||||
|
connection = op.get_bind()
|
||||||
|
|
||||||
|
if settings.DB_TYPE.lower() == "postgresql":
|
||||||
|
# PostgreSQL数据库迁移
|
||||||
|
migrate_postgresql_userid(connection)
|
||||||
|
else:
|
||||||
|
# SQLite数据库迁移
|
||||||
|
migrate_sqlite_userid(connection)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""
|
||||||
|
降级:将SiteUserData表的userid字段从String改回Integer
|
||||||
|
"""
|
||||||
|
connection = op.get_bind()
|
||||||
|
|
||||||
|
if settings.DB_TYPE.lower() == "postgresql":
|
||||||
|
# PostgreSQL数据库降级
|
||||||
|
downgrade_postgresql_userid(connection)
|
||||||
|
else:
|
||||||
|
# SQLite数据库降级
|
||||||
|
downgrade_sqlite_userid(connection)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_postgresql_userid(connection):
|
||||||
|
"""
|
||||||
|
PostgreSQL数据库userid字段迁移
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info("开始PostgreSQL数据库userid字段迁移...")
|
||||||
|
|
||||||
|
# 1. 创建临时列
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
ALTER TABLE siteuserdata
|
||||||
|
ADD COLUMN userid_new VARCHAR
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 2. 将现有数据转换为字符串并复制到新列
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
UPDATE siteuserdata
|
||||||
|
SET userid_new = CAST(userid AS VARCHAR)
|
||||||
|
WHERE userid IS NOT NULL
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 3. 删除旧列
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
ALTER TABLE siteuserdata
|
||||||
|
DROP COLUMN userid
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 4. 重命名新列
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
ALTER TABLE siteuserdata
|
||||||
|
RENAME COLUMN userid_new TO userid
|
||||||
|
"""))
|
||||||
|
|
||||||
|
logger.info("PostgreSQL数据库userid字段迁移完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"PostgreSQL数据库userid字段迁移失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_sqlite_userid(connection):
|
||||||
|
"""
|
||||||
|
SQLite数据库userid字段迁移
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info("开始SQLite数据库userid字段迁移...")
|
||||||
|
|
||||||
|
# SQLite不支持直接修改列类型,需要重建表
|
||||||
|
# 1. 创建新表结构
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
CREATE TABLE siteuserdata_new (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
domain VARCHAR,
|
||||||
|
name VARCHAR,
|
||||||
|
username VARCHAR,
|
||||||
|
userid VARCHAR,
|
||||||
|
user_level VARCHAR,
|
||||||
|
join_at VARCHAR,
|
||||||
|
bonus FLOAT DEFAULT 0,
|
||||||
|
upload FLOAT DEFAULT 0,
|
||||||
|
download FLOAT DEFAULT 0,
|
||||||
|
ratio FLOAT DEFAULT 0,
|
||||||
|
seeding FLOAT DEFAULT 0,
|
||||||
|
leeching FLOAT DEFAULT 0,
|
||||||
|
seeding_size FLOAT DEFAULT 0,
|
||||||
|
leeching_size FLOAT DEFAULT 0,
|
||||||
|
seeding_info JSON DEFAULT '{}',
|
||||||
|
message_unread INTEGER DEFAULT 0,
|
||||||
|
message_unread_contents JSON DEFAULT '[]',
|
||||||
|
err_msg VARCHAR,
|
||||||
|
updated_day VARCHAR,
|
||||||
|
updated_time VARCHAR
|
||||||
|
)
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 2. 复制数据,将userid转换为字符串
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
INSERT INTO siteuserdata_new
|
||||||
|
SELECT
|
||||||
|
id, domain, name, username,
|
||||||
|
CAST(userid AS VARCHAR) as userid,
|
||||||
|
user_level, join_at, bonus, upload, download, ratio,
|
||||||
|
seeding, leeching, seeding_size, leeching_size,
|
||||||
|
seeding_info, message_unread, message_unread_contents,
|
||||||
|
err_msg, updated_day, updated_time
|
||||||
|
FROM siteuserdata
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 3. 删除旧表
|
||||||
|
connection.execute(sa.text("DROP TABLE siteuserdata"))
|
||||||
|
|
||||||
|
# 4. 重命名新表
|
||||||
|
connection.execute(sa.text("ALTER TABLE siteuserdata_new RENAME TO siteuserdata"))
|
||||||
|
|
||||||
|
# 5. 重新创建索引
|
||||||
|
connection.execute(sa.text("CREATE INDEX ix_siteuserdata_domain ON siteuserdata (domain)"))
|
||||||
|
connection.execute(sa.text("CREATE INDEX ix_siteuserdata_updated_day ON siteuserdata (updated_day)"))
|
||||||
|
|
||||||
|
logger.info("SQLite数据库userid字段迁移完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"SQLite数据库userid字段迁移失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade_postgresql_userid(connection):
|
||||||
|
"""
|
||||||
|
PostgreSQL数据库userid字段降级
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info("开始PostgreSQL数据库userid字段降级...")
|
||||||
|
|
||||||
|
# 1. 创建临时列
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
ALTER TABLE siteuserdata
|
||||||
|
ADD COLUMN userid_old INTEGER
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 2. 将字符串转换为整数并复制到新列
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
UPDATE siteuserdata
|
||||||
|
SET userid_old = CAST(userid AS INTEGER)
|
||||||
|
WHERE userid IS NOT NULL AND userid ~ '^[0-9]+$'
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 3. 删除旧列
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
ALTER TABLE siteuserdata
|
||||||
|
DROP COLUMN userid
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 4. 重命名新列
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
ALTER TABLE siteuserdata
|
||||||
|
RENAME COLUMN userid_old TO userid
|
||||||
|
"""))
|
||||||
|
|
||||||
|
logger.info("PostgreSQL数据库userid字段降级完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"PostgreSQL数据库userid字段降级失败: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade_sqlite_userid(connection):
|
||||||
|
"""
|
||||||
|
SQLite数据库userid字段降级
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info("开始SQLite数据库userid字段降级...")
|
||||||
|
|
||||||
|
# SQLite不支持直接修改列类型,需要重建表
|
||||||
|
# 1. 创建新表结构
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
CREATE TABLE siteuserdata_old (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
domain VARCHAR,
|
||||||
|
name VARCHAR,
|
||||||
|
username VARCHAR,
|
||||||
|
userid INTEGER,
|
||||||
|
user_level VARCHAR,
|
||||||
|
join_at VARCHAR,
|
||||||
|
bonus FLOAT DEFAULT 0,
|
||||||
|
upload FLOAT DEFAULT 0,
|
||||||
|
download FLOAT DEFAULT 0,
|
||||||
|
ratio FLOAT DEFAULT 0,
|
||||||
|
seeding FLOAT DEFAULT 0,
|
||||||
|
leeching FLOAT DEFAULT 0,
|
||||||
|
seeding_size FLOAT DEFAULT 0,
|
||||||
|
leeching_size FLOAT DEFAULT 0,
|
||||||
|
seeding_info JSON DEFAULT '{}',
|
||||||
|
message_unread INTEGER DEFAULT 0,
|
||||||
|
message_unread_contents JSON DEFAULT '[]',
|
||||||
|
err_msg VARCHAR,
|
||||||
|
updated_day VARCHAR,
|
||||||
|
updated_time VARCHAR
|
||||||
|
)
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 2. 复制数据,将字符串转换为整数(只转换数字字符串)
|
||||||
|
connection.execute(sa.text("""
|
||||||
|
INSERT INTO siteuserdata_old
|
||||||
|
SELECT
|
||||||
|
id, domain, name, username,
|
||||||
|
CASE
|
||||||
|
WHEN userid IS NULL THEN NULL
|
||||||
|
WHEN userid REGEXP '^[0-9]+$' THEN CAST(userid AS INTEGER)
|
||||||
|
ELSE NULL
|
||||||
|
END as userid,
|
||||||
|
user_level, join_at, bonus, upload, download, ratio,
|
||||||
|
seeding, leeching, seeding_size, leeching_size,
|
||||||
|
seeding_info, message_unread, message_unread_contents,
|
||||||
|
err_msg, updated_day, updated_time
|
||||||
|
FROM siteuserdata
|
||||||
|
"""))
|
||||||
|
|
||||||
|
# 3. 删除旧表
|
||||||
|
connection.execute(sa.text("DROP TABLE siteuserdata"))
|
||||||
|
|
||||||
|
# 4. 重命名新表
|
||||||
|
connection.execute(sa.text("ALTER TABLE siteuserdata_old RENAME TO siteuserdata"))
|
||||||
|
|
||||||
|
# 5. 重新创建索引
|
||||||
|
connection.execute(sa.text("CREATE INDEX ix_siteuserdata_domain ON siteuserdata (domain)"))
|
||||||
|
connection.execute(sa.text("CREATE INDEX ix_siteuserdata_updated_day ON siteuserdata (updated_day)"))
|
||||||
|
|
||||||
|
logger.info("SQLite数据库userid字段降级完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"SQLite数据库userid字段降级失败: {e}")
|
||||||
|
raise
|
||||||
Reference in New Issue
Block a user