Files
company-ai-platform/app/application/feishu/handlers/admin.py
JiuContinent d7db84571d ```
feat: 添加飞书用户模块和订阅功能支持

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

193 lines
5.6 KiB
Python

from typing import Any
from fastapi import HTTPException
from sqlalchemy.orm import Session
from app.application.feishu.delivery import send_text_if_configured
from app.application.feishu.results import command_result
from app.modules.audit.constants import AuditRiskLevel, AuditSource
from app.modules.audit.schemas import AuditLogCreate
from app.modules.audit.service import AuditService
from app.modules.feishu.constants import FeishuCommandName, FeishuReplyType
from app.modules.feishu.service import FeishuService
from app.modules.feishu_users.constants import (
FEISHU_USER_TARGET_TYPE,
FeishuUserAuditAction,
FeishuUserRole,
FeishuUserStatus,
)
from app.modules.feishu_users.principal import FeishuMention, FeishuPrincipal
from app.modules.feishu_users.services import (
FeishuIdentityService,
FeishuUserManagementService,
)
ADMIN_COMMAND_TITLE = "飞书用户管理"
ADMIN_COMMAND_HELP = (
"用户管理命令必须使用一个真实的飞书 @用户:\n"
"设为管理员 @用户\n"
"设为普通用户 @用户\n"
"停用用户 @用户\n"
"启用用户 @用户"
)
_ADMIN_COMMANDS: dict[str, tuple[FeishuCommandName, dict[str, str], str]] = {
"设为管理员": (
FeishuCommandName.USER_SET_ADMIN,
{"role": FeishuUserRole.ADMIN},
"已设为管理员",
),
"设为普通用户": (
FeishuCommandName.USER_SET_USER,
{"role": FeishuUserRole.USER},
"已设为普通用户",
),
"停用用户": (
FeishuCommandName.USER_DISABLE,
{"status": FeishuUserStatus.DISABLED},
"已停用",
),
"启用用户": (
FeishuCommandName.USER_ENABLE,
{"status": FeishuUserStatus.ACTIVE},
"已启用",
),
}
def is_admin_command(command_text: str) -> bool:
return any(command_text.startswith(prefix) for prefix in _ADMIN_COMMANDS)
def handle_admin_command(
db: Session,
feishu: FeishuService,
*,
raw_text: str,
command_text: str,
principal: FeishuPrincipal,
auto_reply: bool,
) -> dict[str, Any] | None:
"""Manage a user selected only from verified structured mention metadata."""
matched = next(
(
(prefix, definition)
for prefix, definition in _ADMIN_COMMANDS.items()
if command_text.startswith(prefix)
),
None,
)
if matched is None:
return None
command_prefix, definition = matched
command, changes, success_text = definition
target = _target_mention(raw_text, command_prefix, principal.mentions)
if target is None or not target.open_id:
_audit_denied(db, principal, "missing_or_ambiguous_structured_mention")
return _result(
feishu,
command,
ADMIN_COMMAND_HELP,
principal,
auto_reply,
)
target_tenant = target.tenant_key or principal.tenant_key
if target_tenant != principal.tenant_key:
_audit_denied(db, principal, "cross_tenant_target")
return _result(
feishu,
command,
"只能管理当前租户内通过飞书 @ 提及的用户。",
principal,
auto_reply,
)
target_principal = FeishuIdentityService(db).resolve_or_register(
tenant_key=target_tenant,
open_id=target.open_id,
union_id=target.union_id,
user_id=target.user_id,
actor=principal.user_code,
)
try:
updated = FeishuUserManagementService(db).update_user(
target_principal.user_code,
changes=changes,
actor=principal.user_code,
)
display_name = target.name or updated.code
content = f"{display_name} {success_text}"
except HTTPException as exc:
if exc.status_code == 409:
content = "操作已拒绝:不能停用或降级最后一个有效管理员。"
else:
content = "用户状态未修改,请确认目标用户后重试。"
return _result(feishu, command, content, principal, auto_reply)
def _target_mention(
raw_text: str,
command_text: str,
mentions: tuple[FeishuMention, ...],
) -> FeishuMention | None:
command_index = raw_text.find(command_text)
if command_index >= 0:
command_tail = raw_text[command_index + len(command_text) :]
candidates = [
mention
for mention in mentions
if mention.key and mention.key in command_tail
]
if len(candidates) == 1:
return candidates[0]
return None
if len(mentions) == 1:
return mentions[0]
return None
def _audit_denied(
db: Session,
principal: FeishuPrincipal,
reason: str,
) -> None:
AuditService(db).record(
AuditLogCreate(
actor=principal.user_code,
source=AuditSource.FEISHU,
action=FeishuUserAuditAction.UPDATE_DENIED,
target_type=FEISHU_USER_TARGET_TYPE,
risk_level=AuditRiskLevel.HIGH,
response_payload={"result": "denied", "reason": reason},
status="denied",
)
)
db.commit()
def _result(
feishu: FeishuService,
command: FeishuCommandName,
content: str,
principal: FeishuPrincipal,
auto_reply: bool,
) -> dict[str, Any]:
response = (
send_text_if_configured(
feishu,
principal.chat_id,
content,
principal.user_code,
)
if auto_reply
else None
)
return command_result(
command,
FeishuReplyType.TEXT,
ADMIN_COMMAND_TITLE,
content,
response,
)