👤 用户认证兼容性详解
2.1 密码哈希 — 完全兼容 ✅
| 项目 | v0.17.2 | v0.25.6 | 兼容性 |
| 哈希函数 |
werkzeug.security.generate_password_hash() |
werkzeug.security.generate_password_hash() |
完全相同 |
| 哈希算法 |
pbkdf2:sha256(Werkzeug 默认) |
pbkdf2:sha256(Werkzeug 默认) |
完全相同 |
| 验证函数 |
werkzeug.security.check_password_hash() |
werkzeug.security.check_password_hash() |
完全相同 |
| 密码字段 |
CharField(max_length=255, null=True) |
CharField(max_length=255, null=True) |
完全相同 |
结论:v0.17.2 中已哈希的密码可以直接在 v0.25.6 中验证,无需任何迁移操作。用户用原密码即可登录。
2.2 User ID — 完全兼容 ✅
| 项目 | v0.17.2 | v0.25.6 | 兼容性 |
| ID 生成 |
uuid.uuid1().hex |
uuid.uuid1().hex |
完全相同 |
| ID 字段 |
CharField(max_length=32, primary_key=True) |
CharField(max_length=32, primary_key=True) |
完全相同 |
| access_token 格式 |
uuid.uuid1().hex(登录时重新生成) |
uuid.uuid1().hex(登录时重新生成) |
完全相同 |
| access_token 存储 |
CharField(max_length=255, null=True) |
CharField(max_length=255, null=True) |
完全相同 |
| email 约束 |
index=True |
unique=True |
⚠️ 需检查重复 email |
2.3 Email 唯一约束迁移 — 需确认 🟡
v0.25.6 将 user.email 从普通索引升级为 UNIQUE 约束。迁移函数 migrate_add_unique_email() 会自动处理:
- 检测所有重复 email
- 保留 superuser 或 最早创建 的账号
- 其余重复账号重命名为
{email}_DUPLICATE_{id[:8]}
- 添加 UNIQUE 约束
-- 迁移前
user1: admin@x.com (superuser, 最早) → 保留
user2: admin@x.com → admin@x.com_DUPLICATE_a1b2c3d4
-- 日志输出
WARNING: Renamed duplicate user a1b2c3d4 email to admin@x.com_DUPLICATE_a1b2c3d4 during migration
建议:迁移前先检查是否有重复 email:
SELECT email, COUNT(*) FROM user GROUP BY email HAVING COUNT(*) > 1;
2.4 登录认证框架变更 — 需重新登录 🔴
| 项目 | v0.17.2 | v0.25.6 | 影响 |
| 认证框架 |
Flask-Login (UserMixin) |
Quart-Auth (AuthUser) |
框架级变更 |
| SECRET_KEY 存储 |
配置文件 secret_key 项 |
Redis ragflow:system:secret_key |
密钥完全不同 |
| SECRET_KEY 默认值 |
str(date.today())(如 "2025-03-13") |
secrets.token_hex(32)(64 字符随机 hex) |
格式完全不同 |
| JWT 签名算法 |
URLSafeTimedSerializer(secret_key) |
URLSafeTimedSerializer(secret_key) |
算法相同 |
| get_id() 方法 |
jwt.dumps(str(self.access_token)) |
jwt.dumps(str(self.access_token)) |
逻辑相同 |
| 登出 token 处理 |
设置为空字符串 "" |
设置为 "INVALID_" + secrets.token_hex(16) |
安全改进,不影响迁移 |
结论:由于 SECRET_KEY 在升级后变化,所有在 v0.17.2 中登录的用户 session(JWT Authorization token)将在 v0.25.6 中失效。
用户必须重新登录。这不影响数据库中的用户数据,仅影响已登录的会话。
📚 知识库数据迁移详解
3.1 ID 和关键字段兼容性 ✅
| 检查项 | v0.17.2 | v0.25.6 | 兼容性 |
| KB ID 生成 | uuid.uuid1().hex | uuid.uuid1().hex | 相同 |
| Document ID 生成 | uuid.uuid1().hex | uuid.uuid1().hex | 相同 |
| File ID 生成 | uuid.uuid1().hex | uuid.uuid1().hex | 相同 |
| Task ID 生成 | uuid.uuid1().hex | uuid.uuid1().hex | 相同 |
| ParserType 枚举 | 14 个值 | 14 个值(完全一致) | 相同 |
| KB tenant_id | CharField(32) | CharField(32) | 相同 |
| Document kb_id | CharField(256) | CharField(256) | 相同 |
| File2Document 映射 | file_id + document_id | file_id + document_id | 相同 |
3.2 Knowledgebase 表新增字段
| 新增字段 | 类型 | 默认值 | 对旧数据影响 |
tenant_embd_id | IntegerField, nullable, index | NULL | 启动后 auto-fix 填充 |
pipeline_id | CharField(32), nullable, index | NULL | 可选字段,不影响 |
graphrag_task_id | CharField(32), nullable | NULL | 新功能,不影响 |
graphrag_task_finish_at | DateTimeField, nullable | NULL | 新功能,不影响 |
raptor_task_id | CharField(32), nullable | NULL | 新功能,不影响 |
raptor_task_finish_at | DateTimeField, nullable | NULL | 新功能,不影响 |
mindmap_task_id | CharField(32), nullable | NULL | 新功能,不影响 |
mindmap_task_finish_at | DateTimeField, nullable | NULL | 新功能,不影响 |
3.3 TenantLLM 主键重构及其影响
这是本次升级最核心的架构变更,影响所有引用 LLM 模型的表:
| 阶段 | 操作 | 数据安全 |
| 1. 添加临时列 |
ALTER TABLE tenant_llm ADD COLUMN temp_id INT NULL |
不丢失数据 |
| 2. 顺序编号 |
UPDATE tenant_llm SET temp_id = row_number ORDER BY tenant_id, llm_factory, llm_name |
确定性排序 |
| 3. 删除旧主键 |
ALTER TABLE tenant_llm DROP PRIMARY KEY |
列值保留 |
| 4. 设置新主键 |
temp_id → NOT NULL AUTO_INCREMENT PRIMARY KEY |
数据完整 |
| 5. 保留唯一性 |
ADD UNIQUE (tenant_id, llm_factory, llm_name) |
旧约束保留 |
| 6. 重命名列 |
RENAME COLUMN temp_id TO id |
完成 |
3.4 FK 自动填充机制
v0.25.6 启动时 fix_empty_tenant_model_id() 自动执行以下映射:
-- 伪代码逻辑
# Tenant: 填充 6 个 FK
UPDATE tenant t
SET t.tenant_llm_id = (SELECT tl.id FROM tenant_llm tl
WHERE tl.tenant_id = t.id AND tl.llm_name = t.llm_id)
WHERE t.tenant_llm_id IS NULL;
# Knowledgebase: 填充 tenant_embd_id
UPDATE knowledgebase kb
SET kb.tenant_embd_id = (SELECT tl.id FROM tenant_llm tl
WHERE tl.tenant_id = kb.tenant_id AND tl.llm_name = kb.embd_id)
WHERE kb.tenant_embd_id IS NULL;
# Dialog: 填充 tenant_llm_id 和 tenant_rerank_id
UPDATE dialog d
SET d.tenant_llm_id = (SELECT tl.id FROM tenant_llm tl
WHERE tl.tenant_id = d.tenant_id AND tl.llm_name = d.llm_id)
WHERE d.tenant_llm_id IS NULL;
# Memory: 填充 tenant_embd_id 和 tenant_llm_id
UPDATE memory m SET ... WHERE m.tenant_embd_id IS NULL;
注意:填充过程需要 v0.25.6 首次启动后自动运行。如果旧字符串引用(如 llm_id)对应的 TenantLLM 记录不存在,该 FK 将保持 NULL,系统会回退使用 tenant 级别的默认模型。
3.5 字段重命名 — 数据完整保留 ✅
| 表 | 旧字段名 | 新字段名 | 迁移方式 | 数据安全 |
document | process_duation | process_duration | ALTER TABLE RENAME COLUMN | 原子操作,数据保留 |
task | process_duation | process_duration | ALTER TABLE RENAME COLUMN | 原子操作,数据保留 |
3.6 字段类型变更 — 无截断风险 ✅
| 表 | 字段 | 旧类型 | 新类型 | 风险评估 |
document | size | IntegerField | BigIntegerField | 向上兼容,无截断 |
file | size | IntegerField | BigIntegerField | 向上兼容,无截断 |
tenant_llm | api_key | CharField(1024) | TextField | 向上兼容,无截断 |
canvas_template | title | CharField(255) | JSONField | 旧纯文本值变为 JSON 字符串 |
canvas_template | description | TextField | JSONField | 旧纯文本值变为 JSON 字符串 |
Canvas Template 注意:旧 title 如 "My Agent" 在 JSONField 中会作为 JSON 字符串 "My Agent" 存储。新代码期望 {"en": "...", "zh": "..."} 格式,需确认模板是否正确显示。不影响数据完整性,仅影响多语言功能。
🔑 API 接口兼容性详解
4.1 API Token 认证兼容性
| 场景 | 兼容级别 | 说明 |
| 已有 API Token 访问检索接口 |
✅ 直接可用 |
API Token 在 api_token 表中作为纯字符串存储,通过 APIToken.query(token=token) 精确匹配。v0.17.2 创建的 token(如 ragflow-eyJhbGciOi...)迁移后仍然可以匹配。 |
| 新建 API Token |
🟡 格式变化 |
新 token 格式从 JWT 变为纯随机字符串,但功能完全相同。
旧: ragflow-eyJzb21ld... (JWT, 依赖 tenant_id 作密钥)
新: ragflow-DkR8xZq3vN7... (43 字符 base64url 随机)
|
| API Token + 密码 Basic Auth |
✅ 兼容 |
密码哈希不变。API 接口支持 token 或 Basic Auth 两种方式。 |
| API Token 的 beta 字段 |
✅ 兼容 |
存储在 DB 中的 beta 值直接迁移。v0.25.6 中如 beta 为空,启动时会用新算法重新生成。 |
4.2 检索/搜索接口兼容性
| 接口 | 路径 | 认证方式 | v0.17.2 → v0.25.6 兼容性 |
| 知识库检索 |
/api/v1/retrieval |
Authorization: Bearer {api_token} |
✅ Token 字符串直接可用 |
| 对话 API |
/api/v1/new_token |
登录后获取 |
🟡 新 token 格式不同 |
| 文档上传 API |
/api/v1/document/upload |
Authorization: Bearer {api_token} |
✅ Token 字符串直接可用 |
| 文件管理 API |
/api/v1/dataset/* |
Authorization: Bearer {api_token} |
✅ Token 字符串直接可用 |
| SDK 连接 |
ragflow_client = RAGFlow(api_key, base_url) |
api_key (即 token) |
✅ 已有 api_key 直接可用 |
4.3 API Token 验证流程对比
v0.17.2 验证流程:
# api/utils/api_utils.py
def token_required(f):
token = request.headers.get('Authorization').split()[1]
obj = APIToken.query(token=token) # 直接查表
if not obj:
return get_error_data_result("Authentication failed")
v0.25.6 验证流程:
# api/utils/api_utils.py (line 288+)
def token_required(f):
token = request.headers.get('Authorization').split()[1]
obj = APIToken.query(token=token) # 先查表(与旧版相同)
if obj:
# token 有效
return f(...)
# 回退:尝试 JWT 解码(兼容旧 login token)
user = UserService.query(access_token=token)
...
关键发现:v0.25.6 的 token_required 新增了 JWT 回退逻辑。旧的 API Token(存储在 api_token 表中作为字符串)仍然通过第一优先级的 APIToken.query(token=token) 精确匹配验证。因此已有 API Token 在迁移后立即可用,无需重新创建。
4.4 Token 格式差异对比
| 属性 | v0.17.2 Token | v0.25.6 Token |
| 生成函数 | generate_confirmation_token(tenant_id) | generate_confirmation_token() |
| 生成算法 | "ragflow-" + URLSafeTimedSerializer(tenant_id).dumps(uuid)[2:34] | "ragflow-" + secrets.token_urlsafe(32) |
| 长度 | 约 40 字符 | 约 51 字符 |
| 示例 | ragflow-eyJzb21ldGhpbmdfbG9uZ19hbmRfYmFzZTY0 | ragflow-DkR8xZq3vN7pLmW2sA5bFyU1cQ6tG4hJ |
| 安全性 | 中等(基于 tenant_id UUID 作为密钥) | 高(基于操作系统 CSPRNG) |
| DB 存储 | 纯字符串 | 纯字符串 |
| DB 查询 | APIToken.query(token=token) | APIToken.query(token=token) |