```
feat: 添加飞书用户模块和订阅功能支持 - 新增feishu_users模块用于处理飞书用户身份验证和权限管理 - 新增subscriptions模块用于处理订阅相关功能 - 新增personalization模块用于个性化服务 - 在alembic迁移配置中注册新的模型模块 - 在API路由器中添加feishu_users和subscriptions路由 - 实现事件调度服务的改进,包括错误处理和状态更新优化 - 添加飞书命令处理的权限检查机制 - 实现飞书应用票据事件处理 - 改进审计日志记录功能 ```
This commit is contained in:
@@ -47,6 +47,16 @@ class WorkflowAction(Base):
|
||||
ForeignKey("workflow_instances.code", ondelete="RESTRICT"),
|
||||
index=True,
|
||||
)
|
||||
source_event_id: Mapped[str | None] = mapped_column(
|
||||
ForeignKey(
|
||||
"domain_events.event_id",
|
||||
name="fk_workflow_actions_source_event_id",
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
nullable=True,
|
||||
unique=True,
|
||||
index=True,
|
||||
)
|
||||
action: Mapped[str] = mapped_column(String(128), index=True)
|
||||
actor: Mapped[str] = mapped_column(String(128), default=ActorValue.SYSTEM, index=True)
|
||||
from_status: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
||||
|
||||
@@ -3,6 +3,7 @@ from uuid import uuid4
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.constants import ActorValue
|
||||
@@ -34,20 +35,30 @@ class WorkflowService:
|
||||
actor: str = ActorValue.SYSTEM,
|
||||
payload: dict[str, Any] | None = None,
|
||||
commit: bool = True,
|
||||
source_event_id: str | None = None,
|
||||
) -> WorkflowInstance:
|
||||
existing_workflow = self._workflow_for_source_event(source_event_id)
|
||||
if existing_workflow is not None:
|
||||
return existing_workflow
|
||||
|
||||
aggregate_id_text = str(aggregate_id) if aggregate_id is not None else None
|
||||
record = self.db.execute(
|
||||
workflow_query = (
|
||||
select(WorkflowInstance).where(
|
||||
WorkflowInstance.workflow_type == workflow_type,
|
||||
WorkflowInstance.aggregate_type == aggregate_type,
|
||||
WorkflowInstance.aggregate_id == aggregate_id_text,
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
.with_for_update()
|
||||
)
|
||||
record = self.db.execute(workflow_query).scalar_one_or_none()
|
||||
previous_status = None
|
||||
now = utc_now()
|
||||
if record is None:
|
||||
record = WorkflowInstance(
|
||||
code=f"{WORKFLOW_CODE_PREFIX}-{now:%Y%m%d%H%M%S%f}",
|
||||
candidate = WorkflowInstance(
|
||||
code=(
|
||||
f"{WORKFLOW_CODE_PREFIX}-{now:%Y%m%d%H%M%S%f}-"
|
||||
f"{uuid4().hex[:8]}"
|
||||
),
|
||||
workflow_type=workflow_type,
|
||||
aggregate_type=aggregate_type,
|
||||
aggregate_id=aggregate_id_text,
|
||||
@@ -56,10 +67,26 @@ class WorkflowService:
|
||||
current_step=action,
|
||||
payload=payload or {},
|
||||
)
|
||||
self.db.add(record)
|
||||
self.db.flush()
|
||||
try:
|
||||
with self.db.begin_nested():
|
||||
self.db.add(candidate)
|
||||
self.db.flush()
|
||||
record = candidate
|
||||
except IntegrityError:
|
||||
record = self.db.execute(workflow_query).scalar_one()
|
||||
previous_status = record.status
|
||||
else:
|
||||
previous_status = record.status
|
||||
|
||||
existing_workflow = self._workflow_for_source_event(source_event_id)
|
||||
if existing_workflow is not None:
|
||||
if existing_workflow.code != record.code:
|
||||
raise ValueError(
|
||||
"Source event is already attached to a different workflow"
|
||||
)
|
||||
return existing_workflow
|
||||
|
||||
if previous_status is not None:
|
||||
record.status = status_value
|
||||
record.actor = actor
|
||||
record.current_step = action
|
||||
@@ -86,6 +113,7 @@ class WorkflowService:
|
||||
f"{uuid4().hex[:8]}"
|
||||
),
|
||||
workflow_code=record.code,
|
||||
source_event_id=source_event_id,
|
||||
action=action,
|
||||
actor=actor,
|
||||
from_status=previous_status,
|
||||
@@ -100,6 +128,23 @@ class WorkflowService:
|
||||
self.db.flush()
|
||||
return record
|
||||
|
||||
def _workflow_for_source_event(
|
||||
self,
|
||||
source_event_id: str | None,
|
||||
) -> WorkflowInstance | None:
|
||||
if not source_event_id:
|
||||
return None
|
||||
action = self.db.execute(
|
||||
select(WorkflowAction).where(
|
||||
WorkflowAction.source_event_id == source_event_id
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if action is None:
|
||||
return None
|
||||
return self.db.execute(
|
||||
select(WorkflowInstance).where(WorkflowInstance.code == action.workflow_code)
|
||||
).scalar_one()
|
||||
|
||||
def list_workflows(
|
||||
self,
|
||||
status_filter: str | None = None,
|
||||
|
||||
Reference in New Issue
Block a user