实现效果:用强类型 Service / *Maint 对象替代手工拼 XML 模板,经反射映射写入 InSite XML Document,再由 InSiteXMLClient 提交到 Camstar App Server。目标框架 net10.0。需按 Camstar 版本替换项目内 dll/InSiteXMLClient.dll 等原生依赖(来源一般为 Camstar 安装目录下的 InSite XML Client)。

一、项目结构

1
2
3
4
5
6
7
8
9
10
11
12
Opcenter.Execution.Services/
├── Client/ # IOpcenterExServiceClient、OpcenterExServiceClient、模型与结果
├── Config/ # OpcenterExConfiguration
├── Extensions/ # AddOpcenterExService
├── Reflection/ # CDO 反射映射
├── ServiceContent/ # 强类型 Service / Ref / Changes(含 ExecuteAny)
├── Exceptions/
├── ExecuteAnyCLF/
│ ├── ScriptLoader.cs # 按名加载嵌入式 .clf
│ ├── Metadata/ClfFunctions.md # CLF 函数与 ExecuteAny 字段速查
│ └── Scripts/*.clf # 嵌入资源(csproj EmbeddedResource)
└── dll/ # Camstar 原生 DLL

二、注册(DI)

1
2
3
4
5
6
7
8
9
using Opcenter.Execution.Services.Extensions;

services.AddOpcenterExService(options =>
{
options.Host = "localhost";
options.Port = 443;
options.DefaultUser = "CamstarAdmin";
options.DefaultPassword = "******";
});

注册后注入 IOpcenterExServiceClient(与 OpcenterExServiceClient 共享同一 Scoped 实例)。调用事务前须 Login(或绑定已有 sessionName);同 sessionName 串行,不同会话可并发。

三、使用示例

1. 快捷封装:Start(StartInfo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using Opcenter.Execution.Services.Client;

var result = client.Start(new StartInfo
{
ContainerName = "20231201_P_02",
Level = "Lot",
Owner = "Owner_A",
StartReason = "StartReason_A",
WorkflowName = "Workflow_A",
WorkflowRevison = "1",
ProductName = "Product_A",
Qty = 10,
});

if (!result.IsSuccess)
{
// result.Message 为失败原因
}

2. 通用提交:组装 ServiceSubmit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using Opcenter.Execution.Services.Client;
using Opcenter.Execution.Services.ServiceContent;

var start = new Start
{
Details = new StartDetails
{
ContainerName = "20231201_P_02",
StartReason = new StartReasonRef("StartReason_A"),
Product = new ProductRef("Product_A", null, true),
Qty = 10,
Level = new ContainerLevelRef("Lot"),
Owner = new OwnerRef("Owner_A"),
},
CurrentStatusDetails = new CurrentStatusStartDetails
{
Workflow = new WorkflowRef("Workflow_A", "1", false),
},
};

var result = client.Submit(start);

主数据维护同理:构造 ProductMaint / MfgOrderMaint 等后 Submit

3. 清空字段:ClearDataFields

映射约定:属性为 null 表示不修改该字段。若要在 Camstar 侧把某个 dataField 置空,须在对应 BaseObject(含 *Changes / Subentity)上填写 ClearDataFields;提交时客户端对每个名称调用 csiDataField.setEmptyValue()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Opcenter.Execution.Services.Client;
using Opcenter.Execution.Services.ServiceContent;

// 示例:清空工单计划开工 / 计划完工(移回待排池)
var changes = new MfgOrderChanges
{
ClearDataFields =
[
nameof(MfgOrderChanges.PlannedStartDate),
nameof(MfgOrderChanges.PlannedCompletionDate),
],
};

var result = client.Submit(new MfgOrderMaint
{
SyncName = "MO_001",
ObjectToChange = new MfgOrderRef("MO_001"),
ObjectChanges = changes,
});

注意:

  • ClearDataFields 仅作用于 dataField(字符串、数值、日期等);引用型字段(namedObjectField / revisionedObjectField / 集合等)不能靠本列表清空。
  • 字段名须与 CDO / *Changes 属性名一致;空白项会被忽略。
  • 可与普通赋值混用:未列入清空列表、且非 null 的属性仍按「写入」映射。

4. ExecuteAny / 嵌入式 CLF(ExecuteAnyCLF/

适用于:多步事务(如 Start→MoveStd)、JSON 入参/出参、或执行已部署的 BusinessRule。单服务仍优先强类型 Submit

服务内容类型为 ServiceContent.ExecuteAny(继承 Update),主要字段:

字段说明
InputData入参字符串(脚本侧通常按 JSON 解析)
OutputData出参;Submit 时客户端额外 requestField("OutputData"),成功写入 result.Data
ScriptCLF 脚本文本
ServiceName服务名
ExecuteBusinessRule已部署 BusinessRule 的引用(BusinessRuleRef

方式 A:快捷封装 ExecuteAny(script, ...)

签名:ExecuteAny(string script, string? inputData = null, string? serviceName = null, string? businessRule = null)

快捷方法会组装并 SubmitInputData 默认 "{}"ServiceName 默认 "ExecuteAny"ExecuteBusinessRule 默认名为 "ExecuteAny" 的规则;script 必填(推荐用 ScriptLoader 加载)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using Opcenter.Execution.Services.Client;
using Opcenter.Execution.Services.ExecuteAnyCLF;

// 嵌入式脚本:ExecuteAnyCLF/Scripts/StartAndMove.clf
var inputData = """
{
"container":"TST_LOT_01", "qty":10,
"level":"Lot", "owner":"Owner_A", "startReason":"StartReason_A",
"product":"Product_A", "productRev":"1",
"mfgOrder":"MO_001", "uom":"EA",
"workflow":"Workflow_A", "workflowRev":"1",
"factory":"Factory_A",
"resource":"RESOURCE_A",
"path":"Path_A"
}
""";

// 建议用具名参数:首参是 script,不是 inputData
var result = client.ExecuteAny(
script: ScriptLoader.Load("StartAndMove"),
inputData: inputData);

if (result.IsSuccess)
{
// result.Data 为 OutputData(脚本写回的字符串,常为 JSON)
}
参数含义
script必填 CLF 文本;推荐 ScriptLoader.Load("Name")
inputData入参;null 时提交 "{}"
serviceName可选;默认 "ExecuteAny"
businessRule可选;默认规则名 "ExecuteAny"(映射为 ExecuteBusinessRule

方式 B:组装 ExecuteAnySubmit

适合只跑已部署 BusinessRule、或需要精确控制字段(不经过快捷方法的默认值):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using Opcenter.Execution.Services.Client;
using Opcenter.Execution.Services.ExecuteAnyCLF;
using Opcenter.Execution.Services.ServiceContent;

// 嵌入式脚本
var result = client.Submit(new ExecuteAny
{
InputData = """{ "container":"TST_LOT_01", "qty":10 }""",
Script = ScriptLoader.Load("JsonEcho"),
});

// 仅跑已部署规则(无 Script)
var ruleResult = client.Submit(new ExecuteAny
{
ExecuteBusinessRule = new BusinessRuleRef("YourBusinessRuleName"),
InputData = "{}",
});

ScriptLoader(命名空间 Opcenter.Execution.Services.ExecuteAnyCLF

  • ScriptLoader.Load("StartAndMove"):按不含扩展名的脚本名加载嵌入资源,首次读取后缓存。
  • ScriptLoader.ListNames():列出已嵌入脚本名(不含 .clf)。
  • 脚本目录:ExecuteAnyCLF/Scripts/*.clf(csproj:EmbeddedResource Include="ExecuteAnyCLF\Scripts\**\*.clf");新增 .clf 放入该目录即可。
  • CLF 写法、Json UserFunctions、内置 Service 字段(Start / MoveStd 等)速查:ExecuteAnyCLF/Metadata/ClfFunctions.md

仓内脚本

名称用途
JsonEchoInputDatacontainer/qty,写回 OutputData(联调/探活)
StartAndMove单容器:Start →(可选 Resource)MoveStd;出参含 qty 等
StartAndMoveLoop多容器:containers 数组循环 Start + MoveStd

StartAndMove / StartAndMoveLoop 常用 JSON 字段:container(或 containers)、qtylevelownerstartReasonproductproductRevmfgOrderuomworkflowworkflowRevfactory,以及可选 resourcepath

注意:

  • 元数据长度上限约:InputData / OutputData ≈ 1000、Script ≈ 4000;超长需拆脚本或改走已部署 BusinessRule。
  • 脚本内用 CreateCDO + ExecuteService 调用内置服务字段;赋值约定为 setfieldex(源, 目标);Json 函数 ClassName 用空占位(如 JsonGetString(, InputData, "container"))。
  • WebApi 参考:CamstarService.StartAndMove(序列化命令 → ScriptLoader.Load("StartAndMove")ExecuteAny)。

5. 手工构造(无 DI)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using Opcenter.Execution.Services.Client;
using Opcenter.Execution.Services.Config;

var client = new OpcenterExServiceClient(new OpcenterExConfiguration
{
Host = "localhost",
Port = 443,
DefaultUser = "CamstarAdmin",
DefaultPassword = "******",
});

client.Login();
try
{
var result = client.MoveStd("CONTAINER_01", resource: "RESOURCE_A");
}
finally
{
client.Logout();
}

结果请判断 OpcenterExResult.IsSuccess

四、版本信息

当前Opcenter Execution版本为2504 Core

项目地址:https://github.com/LicoCode/Opcenter.Execution.Services