feat: 添加飞书用户模块和订阅功能支持

- 新增feishu_users模块用于处理飞书用户身份验证和权限管理
- 新增subscriptions模块用于处理订阅相关功能
- 新增personalization模块用于个性化服务
- 在alembic迁移配置中注册新的模型模块
- 在API路由器中添加feishu_users和subscriptions路由
- 实现事件调度服务的改进,包括错误处理和状态更新优化
- 添加飞书命令处理的权限检查机制
- 实现飞书应用票据事件处理
- 改进审计日志记录功能
```
This commit is contained in:
2026-07-27 08:02:17 +08:00
parent db751f03b4
commit d7db84571d
148 changed files with 17110 additions and 765 deletions

View File

@@ -1,3 +1,4 @@
import re
from typing import Any
from fastapi import HTTPException, status
@@ -23,6 +24,13 @@ from app.modules.legacy_mysql.constants import (
from app.modules.legacy_mysql.services.common import FORBIDDEN_SQL_TOKENS, _normalize_sql, _query_name_text, _row_to_dict
_SQL_COMMENT_MARKERS = ("--", "#", "/*", "*/")
_SQL_QUOTED_CONTENT_PATTERN = re.compile(
r"""'(?:''|\\.|[^'])*'|"(?:""|\\.|[^"])*"|`(?:``|[^`])*`""",
flags=re.DOTALL,
)
_SQL_WORD_PATTERN = re.compile(r"[a-z_]+")
class LegacyQueryMixin:
@staticmethod
@@ -36,13 +44,27 @@ class LegacyQueryMixin:
@staticmethod
def _ensure_readonly(sql: str) -> None:
stripped = sql.strip().lower()
if not stripped.startswith(LEGACY_SELECT_PREFIX):
stripped = sql.strip()
scrubbed = _SQL_QUOTED_CONTENT_PATTERN.sub(" ", stripped)
if any(marker in scrubbed for marker in _SQL_COMMENT_MARKERS):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=LegacyQueryError.SQL_COMMENTS_NOT_ALLOWED,
)
statement = scrubbed.rstrip()
if statement.endswith(LEGACY_SQL_TRAILING_TERMINATOR):
statement = statement[:-1].rstrip()
if LEGACY_SQL_TRAILING_TERMINATOR in statement:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=LegacyQueryError.SINGLE_STATEMENT_REQUIRED,
)
if not re.match(rf"^{LEGACY_SELECT_PREFIX}\b", statement, flags=re.IGNORECASE):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=LegacyQueryError.ONLY_SELECT_ALLOWED,
)
tokens = {token.strip(" ;,\n\t") for token in stripped.replace("(", " ").split()}
tokens = set(_SQL_WORD_PATTERN.findall(statement.lower()))
if tokens & FORBIDDEN_SQL_TOKENS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -117,9 +139,10 @@ class LegacyQueryMixin:
engine = self._ensure_engine()
params = dict(params or {})
try:
params[LegacyResponseKey.LIMIT] = bounded_limit(
limit_value = bounded_limit(
params.get(LegacyResponseKey.LIMIT, limit)
)
params[LegacyResponseKey.LIMIT] = limit_value
except (TypeError, ValueError) as exc:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
@@ -130,7 +153,10 @@ class LegacyQueryMixin:
limited_sql = f"{sql.rstrip(LEGACY_SQL_TRAILING_TERMINATOR)}{LEGACY_LIMIT_CLAUSE}"
with engine.connect() as conn:
result = conn.execute(text(limited_sql), params)
rows = [_row_to_dict(row) for row in result.mappings().all()]
rows = [
_row_to_dict(row)
for row in result.mappings().fetchmany(limit_value)
]
columns = list(rows[0].keys()) if rows else []
return {
LegacyResponseKey.COLUMNS: columns,