工作流、任务与签核人
工作流定制同时涉及配置、Identity、运行中的 Workflow Process 和业务 Item。先用 Workflow Map、Activity、Assignment、Lifecycle 与 Notification 配置表达流程;代码只补足动态决策。
适用版本: 工作流数据模型以 R31 配置手册为主;内置 Method、启动 API 与 System Event helper 必须按目标 Release 核验。
执行层: 服务端 Method / Workflow Event;客户端只能发起请求和展示结果。
主要依据: R31 原厂手册 Unit 14、Unit 15、Unit 18,11.0 SP9 旧手册 Unit 8、Unit 9、Appendix E,以及本项目提供的签核、任务和流程代码素材。
不要直接改工作流表
WORKFLOW、ACTIVITY 和 ACTIVITY_ASSIGNMENT 的 SQL 表不是业务 API。直接更新创建人、状态或签核人会绕过权限、缓存、事件和审计,也可能破坏正在运行的流程。
先区分四个对象
| 对象 | 含义 | 常见用途 |
|---|---|---|
| Workflow Map | 流程模板 | 设计 Activity、Path、Assignment 和变量 |
| Workflow Process | 一次运行实例 | 查询当前流程、状态和受控 Item |
| Activity | 运行中的节点 | 完成、委派、动态分配 |
| Activity Assignment | Activity 与签核 Identity 的关系 | 指定谁可以处理当前节点 |
User 与 Identity 也不能混用。Assignment 的 related_id 通常指 Identity,而不是 User。
获取 Controlled Item
标注:Classic client compatibility
旧版本经常通过内置 Method Get My Controlled Item,从 Activity 或 Workflow Process 找到业务 Item。不要复制 2008 年的 VB.NET 实现;先确认目标数据库仍有该 Method,并从当前工作流上下文调用它。
Innovator inn = this.getInnovator();
Item controlled = this.apply("Get My Controlled Item");
if (controlled.isError())
{
return inn.newError("无法取得工作流受控 Item,请联系管理员并提供操作时间。");
}
if (controlled.getItemCount() != 1)
{
return inn.newError("工作流没有返回唯一的受控 Item。");
}
return controlled;调用前要确认当前 Method 的 this 确实是 Activity 或 Workflow Process。若目标 Release 已提供更明确的服务或 API,应替换上面的兼容调用。
动态增加签核人
标注:Stable(IOM 数据操作骨架) + 版本化工作流规则
动态 Assignment 必须满足五个条件:
- Activity 与 Identity 来自当前流程或受信服务端规则,而不是硬编码。
- 当前用户有权改变该 Activity 的签核人。
- 同一
(activity, identity)不会重复加入。 - 只修改目标 Activity,不用宽泛
where批量删除。 voting_weight、is_required以及组成员展开等路由属性由当前 Workflow Map 的业务规则显式决定并验证,不能依赖未知默认值。
下面只演示“定位 Activity/Identity、授权、查重、创建关系”的骨架,不是一份可直接投产的完整签核算法:
Innovator inn = this.getInnovator();
string activityId = this.getProperty("activity_id", "");
string identityId = this.getProperty("identity_id", "");
bool activityIdIsValid = System.Text.RegularExpressions.Regex.IsMatch(
activityId ?? "",
"^[0-9A-Fa-f]{32}$");
bool identityIdIsValid = System.Text.RegularExpressions.Regex.IsMatch(
identityId ?? "",
"^[0-9A-Fa-f]{32}$");
if (!activityIdIsValid || !identityIdIsValid)
{
return inn.newError("Activity 或 Identity ID 格式无效。");
}
// 这里应调用项目自己的服务端授权规则。
if (!CanManageAssignments(activityId))
{
return inn.newError("当前用户不能修改此 Activity 的签核人。");
}
Item existing = inn.newItem("Activity Assignment", "get");
existing.setAttribute("select", "id");
existing.setProperty("source_id", activityId);
existing.setProperty("related_id", identityId);
existing = existing.apply();
if (existing.isError())
{
return inn.newError("签核人查重失败,请联系管理员并提供操作时间。");
}
if (existing.getItemCount() > 0)
{
return inn.newResult("该签核人已存在,无需重复增加。");
}
Item assignment = inn.newItem("Activity Assignment", "add");
assignment.setProperty("source_id", activityId);
assignment.setProperty("related_id", identityId);
// 由项目规则显式设置并验证,例如:
// assignment.setProperty("voting_weight", calculatedVotingWeight);
// assignment.setProperty("is_required", isRequired ? "1" : "0");
assignment = assignment.apply();
if (assignment.isError())
{
return inn.newError("增加签核人失败,请联系管理员并提供操作时间。");
}
return assignment;CanManageAssignments 是项目必须实现的服务端函数。它至少应确认 Activity 属于预期流程、仍处于可修改状态,并检查调用者 Identity。示例中的“先查再加”也不是原子幂等:并发请求仍可能同时通过查询。生产实现必须使用目标 Release 支持的唯一性/串行机制,或在创建时捕获并规范化重复冲突,再用并发测试证明不会产生两条 Assignment。
删除签核人
删除必须先唯一查询 (activity_id, identity_id),再删除查询得到的那个 Assignment ID。不要发布或执行“只按 source_id 删除全部 Assignment”的代码。批量清空签核人可能让流程失去合法处理者。
按条件选择流程
标注:Stable(决策原则) + Classic client compatibility(启动 API)
如果不同业务类型只是流程路径不同,优先在一个 Workflow Map 中使用 Path、变量、自动 Activity 或 Subflow。只有确实需要不同模板时,才由服务端白名单选择 Map。
string ResolveWorkflowMap(string requestKind)
{
switch (requestKind)
{
case "standard":
return "Example Standard Workflow";
case "expedited":
return "Example Expedited Workflow";
default:
throw new InvalidOperationException("不支持的流程类型。");
}
}启动前可以先用 IOM 做一次非原子前置检查,但查询条件必须包含目标 Map 与“运行中”语义。下面用占位符强调条件结构;具体关系名、状态字段和值由目标 Release 的 Workflow 模型确认:
Item existing = inn.newItem("Workflow", "get");
existing.setAttribute("select", "id");
existing.setProperty("source_id", this.getID());
existing.setProperty("workflow_map_id", resolvedMapId);
existing.setProperty("status", activeStatusValue); // 目标版本验证后的运行中值
existing = existing.apply();
if (existing.isError())
{
return inn.newError("工作流查重失败,请联系管理员并提供操作时间。");
}
if (existing.getItemCount() > 0)
{
return inn.newResult("该 Item 已有同一 Map 的运行中工作流。");
}常见判断错误
getItemCount() < 0 不是“没有查到数据”。正常的空集合是 0;错误应先用 isError() 判断。这个错误会让启动流程的分支永远不执行。
历史或已完成 Process 不应阻止一个合法的新实例,除非业务契约明确规定“一生只能启动一次”。上面的查询也不能阻止两个并发请求同时通过。实际实例化和启动 API 在不同 Release 中可能变化,应封装为 workflowAdapter.start(item, mapId, idempotencyKey),使用目标版本支持的原子唯一性/串行机制,并做并发双提交测试。不要用 SQL 改 created_by_id;如果业务需要记录发起人,应使用受支持的属性或审计 Item。
查询当前用户任务
标注:Stable(按登录用户权限查询)
查询“我的任务”时不应临时授予高权限 Identity,否则 my_assignment=1 的语义会变得模糊,并可能返回用户本不该看到的任务。
Innovator inn = this.getInnovator();
Item query = inn.newItem("InBasket Task", "get");
query.setAttribute("select", "id,item,status");
query.setProperty("status", "Active");
query.setProperty("my_assignment", "1");
Item tasks = query.apply();
if (tasks.isError())
{
return inn.newError("任务查询失败,请联系管理员并提供操作时间。");
}
return tasks;如果还要筛选业务 Item 类型或日期:
- 用 ItemType 查询得到目标 ID,不把数据库 GUID 写死在代码里。
- 使用 Aras 约定的中性时间和项目时区策略。
- .NET 格式化 24 小时制应使用
HH,不是hh。 - 给查询加
select、分页或最大记录数。
登录、失败与退出事件
标注:Classic client compatibility / Private API
Successful Login、Failed Login 和 Logout 是三个不同的 System Event handler。每个 Method 只处理一种事件,不能把三段带 return 的代码拼在一起。
// 伪代码:由目标 Release 的 System Event API adapter 实现。
return systemEventLogAdapter.Write(new SystemEventRecord
{
EventType = "SuccessfulLogin",
Actor = eventData.LoginName,
Message = "Login succeeded"
});记录登录名属于个人数据处理。文档必须同时定义访问权限、保留期限、失败重试、脱敏和审计用途。旧资料里的 CCO.SystemEventLogger 应先在目标 Release 验证,再由 adapter 包装。
OOTB Method 名称不是公共契约
标注:Private API
某些应用包会提供内部 Method 来完成“加入变更”“选择对象”等动作。只记录一个 Method 名称不足以构成教程,因为它可能随 Product Engineering 或其他应用版本改变。
使用前应记录:
- 所属应用包及版本;
- 输入 AML 契约与返回结构;
- 调用权限;
- 是否允许客户 Method 直接调用;
- 升级时的替代方案。
为什么不要这样做
| 原始模式 | 标注 | 风险 |
|---|---|---|
| 硬编码 Activity 与 Identity GUID | Reject | 环境不可迁移,容易改错运行实例 |
| 只按 Activity 删除 Assignment | Reject | 可能清空所有签核人 |
| Grant 高权限后查询“我的任务” | Reject | 扩大数据范围,破坏当前用户语义 |
| SQL 更新 Workflow 创建人或状态 | Reject | 绕过工作流引擎与审计 |
复制旧版 Get My Controlled Item 实现 | Reject | 依赖旧 VB.NET、DOM 和数据模型细节 |
| 多个 System Event handler 拼在一个 Method | Reject | 第一处 return 后其他分支不可达 |
验证清单
- [ ] 动态签核人操作是幂等的,并且仅作用于一个 Activity。
- [ ] User 与 Identity 转换经过唯一性校验。
- [ ] 不提升权限也能正确返回“我的任务”。
- [ ] 工作流查重先判断
isError(),再判断getItemCount()。 - [ ] 用户重复提交或并发提交不会启动两个流程。
- [ ] System Event 日志有隐私、权限和保留策略。
- [ ] 目标 Release 的内置 Method 和 workflow adapter 有集成测试。
