feat: migrate ORM base to sqlmodel-ext, add file viewers and WOPI integration
All checks were successful
Test / test (push) Successful in 1m45s

- Migrate SQLModel base classes, mixins, and database management to
  external sqlmodel-ext package; remove sqlmodels/base/, sqlmodels/mixin/,
  and sqlmodels/database.py
- Add file viewer/editor system with WOPI protocol support for
  collaborative editing (OnlyOffice, Collabora)
- Add enterprise edition license verification module (ee/)
- Add Dockerfile multi-stage build with Cython compilation support
- Add new dependencies: sqlmodel-ext, cryptography, whatthepatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 14:23:17 +08:00
parent 53b757de7a
commit ccadfe57cd
81 changed files with 5106 additions and 4837 deletions

View File

@@ -25,11 +25,11 @@ async def load_secret_key() -> None:
从数据库读取 JWT 的密钥。
"""
# 延迟导入以避免循环依赖
from sqlmodels.database import get_session
from sqlmodels.database_connection import DatabaseManager
from sqlmodels.setting import Setting
global SECRET_KEY
async for session in get_session():
async for session in DatabaseManager.get_session():
setting: Setting = await Setting.get(
session,
(Setting.type == "auth") & (Setting.name == "secret_key")

67
utils/JWT/wopi_token.py Normal file
View File

@@ -0,0 +1,67 @@
"""
WOPI 访问令牌生成与验证。
使用 JWT 签名payload 包含 file_id, user_id, can_write, exp。
TTL 默认 10 小时WOPI 规范推荐长 TTL
"""
from datetime import datetime, timedelta, timezone
from uuid import UUID, uuid4
import jwt
from sqlmodels.wopi import WopiAccessTokenPayload
WOPI_TOKEN_TTL = timedelta(hours=10)
"""WOPI 令牌有效期"""
def create_wopi_token(
file_id: UUID,
user_id: UUID,
can_write: bool = False,
) -> tuple[str, int]:
"""
创建 WOPI 访问令牌。
:param file_id: 文件UUID
:param user_id: 用户UUID
:param can_write: 是否可写
:return: (token_string, access_token_ttl_ms)
"""
from utils.JWT import SECRET_KEY
expire = datetime.now(timezone.utc) + WOPI_TOKEN_TTL
payload = {
"jti": str(uuid4()),
"file_id": str(file_id),
"user_id": str(user_id),
"can_write": can_write,
"exp": expire,
"type": "wopi",
}
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
# WOPI 规范要求 access_token_ttl 是毫秒级的 UNIX 时间戳
access_token_ttl = int(expire.timestamp() * 1000)
return token, access_token_ttl
def verify_wopi_token(token: str) -> WopiAccessTokenPayload | None:
"""
验证 WOPI 访问令牌并返回 payload。
:param token: JWT 令牌字符串
:return: WopiAccessTokenPayload 或 None验证失败
"""
from utils.JWT import SECRET_KEY
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
if payload.get("type") != "wopi":
return None
return WopiAccessTokenPayload(
file_id=UUID(payload["file_id"]),
user_id=UUID(payload["user_id"]),
can_write=payload.get("can_write", False),
)
except (jwt.InvalidTokenError, KeyError, ValueError):
return None