feat: restore source parity and harden agent runtime

This commit is contained in:
2026-06-22 11:17:26 +08:00
parent e33f08277b
commit 0793eb82d6
596 changed files with 168879 additions and 290 deletions
@@ -0,0 +1,353 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
审批人解析器
负责根据节点配置解析实际的审批人列表
"""
import logging
from typing import List, Dict, Any
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
logger = logging.getLogger(__name__)
class AssigneeResolver:
"""
审批人解析器
支持的审批人类型:
- user: 指定用户
- role: 指定角色(获取角色下所有用户)
- department: 指定部门(获取部门下所有用户)
- superior: 上级主管(根据发起人的部门层级)
- manager: 直属经理(发起人的直属上级)
- initiator: 发起人自己
- form_field: 表单字段(从表单数据中获取用户)
"""
async def resolve(
self,
db: AsyncSession,
node_config: Dict,
instance: Any,
form_data: Dict,
) -> List[str]:
"""
解析审批人
Args:
db: 数据库会话
node_config: 节点配置(包含 assigneeType, assignees 等)
instance: 流程实例
form_data: 表单数据
Returns:
List[str]: 用户ID列表
"""
assignee_type = node_config.get('assigneeType', 'user')
resolvers = {
'user': self._resolve_users,
'role': self._resolve_by_role,
'department': self._resolve_by_department,
'superior': self._resolve_superior,
'manager': self._resolve_manager,
'initiator': self._resolve_initiator,
'form_field': self._resolve_from_form,
}
resolver = resolvers.get(assignee_type)
if not resolver:
logger.warning(f"未知的审批人类型: {assignee_type}")
return []
try:
return await resolver(db, node_config, instance, form_data)
except Exception as e:
logger.exception(f"解析审批人失败: {e}")
return []
async def _resolve_users(
self,
db: AsyncSession,
node_config: Dict,
instance: Any,
form_data: Dict,
) -> List[str]:
"""指定用户"""
assignees = node_config.get('assignees', [])
logger.info(f"[_resolve_users] assignees from config: {assignees}")
result = [str(uid) for uid in assignees] if assignees else []
logger.info(f"[_resolve_users] result: {result}")
return result
async def _resolve_by_role(
self,
db: AsyncSession,
node_config: Dict,
instance: Any,
form_data: Dict,
) -> List[str]:
"""根据角色获取用户(通过用户角色关联表)"""
from core.user.model import User
from core.user.user_role_model import UserRole
role_ids = node_config.get('assignees', [])
if not role_ids:
return []
# 通过 UserRole 关联表查询拥有指定角色的用户
stmt = select(User.id).join(
UserRole,
User.id == UserRole.user_id
).where(
UserRole.role_id.in_(role_ids),
User.is_deleted == False,
User.is_active == True,
).distinct()
result = await db.execute(stmt)
users = result.scalars().all()
logger.info(f"[_resolve_by_role] role_ids: {role_ids}, found users: {users}")
return [str(uid) for uid in users]
async def _resolve_by_department(
self,
db: AsyncSession,
node_config: Dict,
instance: Any,
form_data: Dict,
) -> List[str]:
"""根据部门获取用户"""
from core.user.model import User
dept_ids = node_config.get('assignees', [])
if not dept_ids:
return []
stmt = select(User.id).where(
User.dept_id.in_(dept_ids),
User.is_deleted == False,
User.is_active == True,
).distinct()
result = await db.execute(stmt)
users = result.scalars().all()
return [str(uid) for uid in users]
async def _resolve_superior(
self,
db: AsyncSession,
node_config: Dict,
instance: Any,
form_data: Dict,
) -> List[str]:
"""
获取上级主管
assigneeLevel 含义:
- 1 = 直接上级(当前部门的负责人)
- 2 = 上级的上级(父部门的负责人)
- 以此类推
"""
from core.user.model import User
from core.dept.model import Dept
level = node_config.get('assigneeLevel', 1)
initiator_id = instance.initiator_id
# 获取发起人信息
stmt = select(User).where(User.id == initiator_id)
result = await db.execute(stmt)
initiator = result.scalar_one_or_none()
if not initiator or not initiator.dept_id:
logger.warning(f"发起人 {initiator_id} 没有部门信息")
return []
# 获取部门信息
current_dept_id = initiator.dept_id
for _ in range(level - 1):
stmt = select(Dept).where(Dept.id == current_dept_id)
result = await db.execute(stmt)
dept = result.scalar_one_or_none()
if dept and dept.parent_id:
current_dept_id = dept.parent_id
else:
break
# 获取部门负责人
stmt = select(Dept).where(Dept.id == current_dept_id)
result = await db.execute(stmt)
current_dept = result.scalar_one_or_none()
if current_dept and current_dept.lead_id:
# 如果负责人是发起人自己,尝试向上找
if str(current_dept.lead_id) == str(initiator_id):
logger.info(f"部门负责人是发起人自己,尝试向上查找")
if current_dept.parent_id:
stmt = select(Dept).where(Dept.id == current_dept.parent_id)
result = await db.execute(stmt)
parent_dept = result.scalar_one_or_none()
if parent_dept and parent_dept.lead_id:
return [str(parent_dept.lead_id)]
return [str(current_dept.lead_id)]
logger.warning(f"部门没有负责人")
return []
async def _resolve_manager(
self,
db: AsyncSession,
node_config: Dict,
instance: Any,
form_data: Dict,
) -> List[str]:
"""
获取直属经理
assigneeLevel 含义:
- 1 = 直属经理(发起人的 manager)
- 2 = 经理的经理(发起人的 manager 的 manager
- 以此类推
"""
from core.user.model import User
level = node_config.get('assigneeLevel', 1)
initiator_id = instance.initiator_id
current_user_id = initiator_id
for _ in range(level):
stmt = select(User).where(User.id == current_user_id)
result = await db.execute(stmt)
user = result.scalar_one_or_none()
if user and user.manager_id:
current_user_id = user.manager_id
else:
logger.warning(f"用户没有设置直属经理")
return []
return [str(current_user_id)]
async def _resolve_initiator(
self,
db: AsyncSession,
node_config: Dict,
instance: Any,
form_data: Dict,
) -> List[str]:
"""发起人自己"""
if instance.initiator_id:
return [str(instance.initiator_id)]
return []
async def _resolve_from_form(
self,
db: AsyncSession,
node_config: Dict,
instance: Any,
form_data: Dict,
) -> List[str]:
"""从表单字段获取用户,支持多字段收集,支持 form-selector 间接查询"""
field_names = node_config.get('assigneeFields') or node_config.get('recipientFields')
if not field_names:
single = node_config.get('assigneeField') or node_config.get('recipientField', '')
field_names = [single] if single else []
if not field_names:
return []
# 构建 form-selector 字段映射表: fieldName -> {formCode, userField}
field_mappings = {}
for mapping in (node_config.get('assigneeFieldMappings') or node_config.get('recipientFieldMappings') or []):
field_mappings[mapping['fieldName']] = mapping
result = []
seen = set()
for field_name in field_names:
value = form_data.get(field_name)
if not value:
continue
mapping = field_mappings.get(field_name)
if mapping:
# form-selector 字段: value 是引用表单记录的 ID,需要间接查询用户字段
user_ids = await self._resolve_form_selector_field(
db, value, mapping.get('formCode', ''), mapping.get('userField', '')
)
for uid_str in user_ids:
if uid_str and uid_str not in seen:
seen.add(uid_str)
result.append(uid_str)
else:
# user-selector 字段: value 直接就是用户 ID
values = value if isinstance(value, list) else [value]
for uid in values:
uid_str = str(uid)
if uid_str and uid_str not in seen:
seen.add(uid_str)
result.append(uid_str)
return result
async def _resolve_form_selector_field(
self,
db: AsyncSession,
record_ids: Any,
form_code: str,
user_field: str,
) -> List[str]:
"""
从 form-selector 引用的表单记录中提取用户字段值
Args:
db: 数据库会话
record_ids: 引用的表单记录 ID(单个或列表)
form_code: 引用的表单编码
user_field: 引用表单中的用户字段名
"""
if not form_code or not user_field:
logger.warning(f"form-selector 映射配置不完整: form_code={form_code}, user_field={user_field}")
return []
ids = record_ids if isinstance(record_ids, list) else [record_ids]
if not ids:
return []
result = []
try:
from online_dev.form_data_manager.service import FormDataService
service = await FormDataService.create_service(db, form_code)
for record_id in ids:
try:
record = await service.get(db, str(record_id))
if not record:
logger.warning(f"form-selector 引用记录不存在: form_code={form_code}, id={record_id}")
continue
user_value = record.get(user_field)
if not user_value:
continue
user_values = user_value if isinstance(user_value, list) else [user_value]
for uid in user_values:
result.append(str(uid))
except Exception as e:
logger.warning(f"查询 form-selector 引用记录失败: form_code={form_code}, id={record_id}, error={e}")
continue
except Exception as e:
logger.exception(f"form-selector 字段解析失败: {e}")
return result
# 全局实例
assignee_resolver = AssigneeResolver()