关于 b2c-custom-job-steps
为 Salesforce B2C Commerce 开发者提供创建自定义批处理作业步骤的指导,涵盖任务型与分块型执行模式、步骤定义和作业流程配置。适用于数据导入导出、同步及维护任务。
主要功能
- 01任务型与分块型步骤设计
- 02配置 steptypes.json 与 jobs.xml
- 03定义和校验步骤参数
- 04将自定义步骤与标准步骤串联
- 05指导 IMPEX 文件交接
适用场景
- 01创建商品或订单批量导入作业
- 02实现数据导出或系统间同步
- 03编写清理和维护任务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ salesforcecommercecloud/ b2c-developer-tooling --skill b2c-custom-job-steps安装后在 Claude Code 中输入/b2c-custom-job-steps确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/trigger-evals.jsonreferences/CHUNK-ORIENTED.mdJOBS-XML.mdSTEPTYPES-JSON.mdTASK-ORIENTED.md详细介绍
自定义作业步骤技能
本技能将指导你如何为 Salesforce B2C Commerce 批处理创建新的自定义作业步骤。
要运行现有作业? 如果你需要通过 CLI 执行作业或导入站点归档,请改用
b2c-cli:b2c-job技能。
适用场景
- 为批处理创建新的计划作业
- 构建数据导入作业(客户、产品、订单)
- 构建数据导出作业(报告、数据流、同步)
- 实现系统间的数据同步
- 创建清理或维护任务
概述
自定义作业步骤允许你在 B2C Commerce 作业中执行自定义业务逻辑。有两种执行模式:
| 模式 | 使用场景 | 进度跟踪 |
|---|---|---|
| 任务型 | 单次操作(FTP、导入/导出) | 有限 |
| 分块型 | 批量数据处理 | 精细 |
文件结构
my_cartridge/
├── cartridge/
│ ├── scripts/
│ │ └── steps/
│ │ ├── myTaskStep.js # Task-oriented script
│ │ └── myChunkStep.js # Chunk-oriented script
│ └── my_cartridge.properties
└── steptypes.json # Step type definitions (at cartridge ROOT)
重要: steptypes.json 文件必须放在 cartridge 的根目录中,而不是 cartridge/ 目录内。每个 cartridge 只能有一个 steptypes.json 文件。
步骤类型定义(steptypes.json)
{
"step-types": {
"script-module-step": [
{
"@type-id": "custom.MyTaskStep",
"@supports-parallel-execution": "false",
"@supports-site-context": "true",
"@supports-organization-context": "false",
"description": "My custom task step",
"module": "my_cartridge/cartridge/scripts/steps/myTaskStep.js",
"function": "execute",
"timeout-in-seconds": 900,
"parameters": {
"parameter": [
{
"@name": "InputFile",
"@type": "string",
"@required": "true",
"description": "Path to input file"
},
{
"@name": "Enabled",
"@type": "boolean",
"@required": "false",
"default-value": "true",
"description": "Enable processing"
}
]
},
"status-codes": {
"status": [
{
"@code": "OK",
"description": "Step completed successfully"
},
{
"@code": "ERROR",
"description": "Step failed"
},
{
"@code": "NO_DATA",
"description": "No data to process"
}
]
}
}
],
"chunk-script-module-step": [
{
"@type-id": "custom.MyChunkStep",
"@supports-parallel-execution": "true",
"@supports-site-context": "true",
"@supports-organization-context": "false",
"description": "Bulk data processing step",
"module": "my_cartridge/cartridge/scripts/steps/myChunkStep.js",
"before-step-function": "beforeStep",
"read-function": "read",
"process-function": "process",
"write-function": "write",
"after-step-function": "afterStep",
"total-count-function": "getTotalCount",
"chunk-size": 100,
"transactional": "false",
"timeout-in-seconds": 1800,
"parameters": {
"parameter": [
{
"@name": "CategoryId",
"@type": "string",
"@required": "true"
}
]
}
}
]
}
}
从步骤类型到可运行的作业(jobs.xml)
steptypes.json 仅用于声明步骤类型,不会创建作业。要创建可由 b2c job run 执行并可在 Business Manager 中安排计划的作业,请编写引用该步骤类型的作业定义(jobs.xml),然后导入:
b2c job import ./my-job-archive # jobs.xml at the archive root
一个最简有效定义会将一个步骤接入流程,并包含必需的 <triggers> 元素:
<?xml version="1.0" encoding="UTF-8"?>
<jobs xmlns="http://www.demandware.com/xml/impex/jobs/2015-07-01">
<job job-id="MyNightlyExport">
<flow>
<context site-id="RefArch"/>
<step step-id="ExportStep" type="custom.ProductExport">
<parameters>
<parameter name="OutputFile">/export/products.csv</parameter>
</parameters>
</step>
</flow>
<triggers>
<run-once enabled="false">
<date>2025-01-01</date>
<time>00:00:00.000Z</time>
</run-once>
</triggers>
</job>
</jobs>
关键规则(详情请参阅 jobs.xml 参考):
- 架构要求必须包含
<triggers>——没有该元素的jobs.xml会在导入验证时失败。对于按需/手动运行的作业,请使用<run-once enabled="false">;对于计划作业,请使用<run-recurring>。 <step>的type属性引用步骤类型;其值必须与在steptypes.json中声明的@type-id一致(不要混淆两者——steptypes.json使用@type-id,jobs.xml使用type)。<job>子元素必须按以下顺序排列:description → parameters → flow/split → rules → triggers。- 要让作业解析步骤类型,包含步骤
steptypes.json和模块的 cartridge 必须先部署,并位于 cartridge 路径中。
导入后,使用 b2c job run MyNightlyExport --wait 运行(请参阅 b2c-cli:b2c-job 技能)。
任务型步骤
适用于 FTP 传输、文件生成或导入/导出等单次操作。
脚本(scripts/steps/myTaskStep.js)
'use strict';
var Status = require('dw/system/Status');
var Logger = require('dw/system/Logger');
/**
* Execute the task step
* @param {Object} parameters - Job step parameters
* @param {dw.job.JobStepExecution} stepExecution - Step execution context
* @returns {dw.system.Status} Execution status
*/
exports.execute = function (parameters, stepExecution) {
var log = Logger.getLogger('job', 'MyTaskStep');
try {
var inputFile = parameters.InputFile;
var enabled = parameters.Enabled;
if (!enabled) {
log.info('Step disabled, skipping');
return new Status(Status.OK, 'SKIP', 'Step disabled');
}
// Your business logic here
log.info('Processing file: ' + inputFile);
// Return success
return new Status(Status.OK);
} catch (e) {
log.error('Step failed: ' + e.message);
return new Status(Status.ERROR, 'ERROR', e.message);
}
};
状态码
// Success
return new Status(Status.OK);
return new Status(Status.OK, 'CUSTOM_CODE', 'Custom message');
// Error
return new Status(Status.ERROR);
return new Status(Status.ERROR, null, 'Error message');
重要: 自定义状态码仅适用于 OK 状态。如果你在 ERROR 状态下使用自定义状态码,该状态码会被替换为 ERROR。自定义状态码不能包含逗号、通配符、首尾空白,也不能超过 100 个字符。
分块型步骤
适用于产品、订单、客户等可计数数据的批量处理。
重要: 不能为分块型步骤定义自定义退出状态。分块模块始终以 OK 或 ERROR 结束。
必需函数
| 函数 | 用途 | 返回值 |
|---|---|---|
read() | 获取下一项 | 项目或无 |
process(item) | 转换项目 | 已处理的项目或无(用于筛选) |
write(items) | 保存数据块中的项目 | 无 |
可选函数
| 函数 | 用途 | 返回值 |
|---|---|---|
beforeStep() | 初始化(打开文件、查询) | 无 |
afterStep(success) | 清理(关闭文件) | 无 |
getTotalCount() | 返回用于显示进度的项目总数 | 数字 |
beforeChunk() | 每个数据块开始前调用 | 无 |
afterChunk() | 每个数据块结束后调用 | 无 |
脚本(scripts/steps/myChunkStep.js)
'use strict';
var ProductMgr = require('dw/catalog/ProductMgr');
var Transaction = require('dw/system/Transaction');
var Logger = require('dw/system/Logger');
var File = require('dw/io/File');
var FileWriter = require('dw/io/FileWriter');
var log = Logger.getLogger('job', 'MyChunkStep');
var products;
var fileWriter;
/**
* Initialize before processing
*/
exports.beforeStep = function (parameters, stepExecution) {
log.info('Starting chunk processing');
// Open resources
var outputFile = new File(File.IMPEX + '/export/products.csv');
fileWriter = new FileWriter(outputFile);
fileWriter.writeLine('ID,Name,Price');
// Query products
products = ProductMgr.queryAllSiteProducts();
};
/**
* Get total count for progress tracking
*/
exports.getTotalCount = function (parameters, stepExecution) {
return products.count;
};
/**
* Read next item
* Return nothing to signal end of data
*/
exports.read = function (parameters, stepExecution) {
if (products.hasNext()) {
return products.next();
}
// Return nothing = end of data
};
/**
* Process single item
* Return nothing to filter out item
*/
exports.process = function (product, parameters, stepExecution) {
// Filter: skip offline products
if (!product.online) {
return; // Filtered out
}
// Transform
return {
id: product.ID,
name: product.name,
price: product.priceModel.price.value
};
};
/**
* Write chunk of processed items
*/
exports.write = function (items, parameters, stepExecution) {
for (var i = 0; i < items.size(); i++) {
var item = items.get(i);
fileWriter.writeLine(item.id + ',' + item.name + ',' + item.price);
}
};
/**
* Cleanup after all chunks
*/
exports.afterStep = function (success, parameters, stepExecution) {
// Close resources
if (fileWriter) {
fileWriter.close();
}
if (products) {
products.close();
}
if (success) {
log.info('Chunk processing completed successfully');
} else {
log.error('Chunk processing failed');
}
};
参数类型
| 类型 | 描述 | 示例值 |
|---|---|---|
string | 文本值 | "my-value" |
boolean | true/false | true |
long | 整数 | 12345 |
double | 小数 | 123.45 |
datetime-string | ISO 日期时间 | "2024-01-15T10:30:00Z" |
date-string | ISO 日期 | "2024-01-15" |
time-string | ISO 时间 | "10:30:00" |
参数验证属性
| 属性 | 适用范围 | 描述 |
|---|---|---|
@trim | 全部 | 验证前去除空白(默认:true) |
@required | 全部 | 标记为必填(默认:true) |
@target-type | datetime-string, date-string, time-string | 转换为 long 或 date(默认:date) |
pattern | string | 用于验证的正则表达式模式 |
min-length | string | 最小字符串长度(必须 ≥1) |
max-length | string | 最大字符串长度(总计最多 1000 个字符) |
min-value | long, double, datetime-string, time-string | 最小数值 |
max-value | long, double, datetime-string, time-string | 最大数值 |
enum-values | 全部 | 限定为允许的值(BM 中显示为下拉菜单) |
配置选项
steptypes.json 属性
| 属性 | 必填 | 描述 |
|---|---|---|
@type-id | 是 | 唯一 ID(必须以 custom. 开头,最多 100 个字符) |
@supports-parallel-execution | 否 | 允许并行执行(默认:true) |
@supports-site-context | 否 | 可用于站点范围的作业(默认:true) |
@supports-organization-context | 否 | 可用于组织范围的作业(默认:true) |
module | 是 | 脚本模块的路径 |
function | 是 | 要执行的函数名称(面向任务) |
timeout-in-seconds | 否 | 步骤超时(建议设置) |
transactional | 否 | 在单个事务中执行(默认:false) |
chunk-size | 是* | 每个分块的项目数(*分块步骤必填) |
上下文限制: @supports-site-context 和 @supports-organization-context 不能同时为 true 或同时为 false;其中一个必须为 true,另一个必须为 false。
标准(系统)作业步骤
自定义步骤只是作业流程的一部分。B2C Commerce 还提供标准(系统)作业步骤——内置的步骤类型 ID,可在 Business Manager → Administration → Operations → Jobs 中添加到流程,也可在站点导入归档中的 jobs.xml 流程里通过类型 ID 引用。与自定义步骤不同,这些步骤无需在 steptypes.json 中声明;它们始终可用。可使用它们执行目录、库存、价格、内容、优惠券、自定义对象的导入和导出、订单导出以及类似的平台操作,并将它们与自定义步骤串联。
不要猜测标准步骤的类型 ID 或参数。 完整目录——包括每个步骤的用途及其配置参数(必填项、默认值、允许值)——已随 CLI 一同提供,并可通过 b2c-cli:b2c-docs 技能搜索:
# Browse the catalog of standard step type IDs
b2c docs read job-steps
# Look up a specific step's purpose + configuration parameters
b2c docs read ImportCatalog
b2c docs read ExportInventoryLists
# Fuzzy-search for a step
b2c docs search "import price"
常用标准步骤
捆绑的目录涵盖 Business Manager 作业步骤选择器中显示的所有标准步骤类型 ID(导入、导出和处理步骤)。以下是部分代表性步骤——运行 b2c docs read job-steps 查看完整列表,并运行 b2c docs read <TypeID> 查看任意步骤的完整参数和默认值。范围是执行范围(Organization、Site 或两者)。
| Type ID | 范围 | 关键必需参数 |
|---|---|---|
ImportCatalog | Organization | NoFilesFoundHandling、ImportMode、ImportFailedHandling |
ImportInventoryLists | Organization | NoFilesFoundHandling、ImportMode、ImportFailedHandling |
ImportPriceBook | Organization | NoFilesFoundHandling、ImportMode、ImportFailedHandling |
ImportContent | Site | NoFilesFoundHandling、ImportMode、ImportFailedHandling |
ImportCustomObjects | Organization & Sites | NoFilesFoundHandling、ImportFailedHandling |
ExportCatalog | Organization | CatalogID |
ExportInventoryLists | Site | (无) |
ExportPriceBook | Organization | PriceBookID |
ExportContent | Organization & Sites | LibraryID |
ExportOrders | Site | Confirmation Status、Shipment Status、Payment Status |
ExecutePreconfiguredDataReplicationProcess | Organization | ReplicationProcessID |
SearchReindex | Site | Indexer Type |
ExecuteScriptModule | Organization & Sites | ExecuteScriptModule.Module |
导入步骤共用一组文件处理参数(WorkingFolder、FileNamePattern、ImportMode、NoFilesFoundHandling、ImportFailedHandling、AfterImportFileHandling、ArchiveFolder);导出步骤共用 ExportFile / FileNamePrefix / OverwriteExportFile。处理步骤(复制、重新建立索引、缓存失效、ExecutePipeline/ExecuteScriptModule/IncludeStepsFromJob)各自有自己的参数。请查阅相应步骤的文档,了解确切的参数列表。
引用前一步暂存于 IMPEX 的文件
标准导入步骤从实例的 IMPEX 区域读取文件。WorkingFolder 参数是相对于 IMPEX/src/ 解析的(默认值为 IMPEX/src/);FileNamePattern 是一个正则表达式,用于筛选该文件夹中要导入的文件。其交接约定如下:在 IMPEX/src/... 下写入文件的步骤之后,可以接一个读取该文件的标准导入步骤——无需往返下载和上传。
在自定义步骤中,使用 IMPEX 常量通过 dw.io.File 将文件写入该位置:
var File = require('dw/io/File');
// Custom step writes a catalog import file into IMPEX/src/jobdata/
exports.beforeStep = function () {
var dir = new File(File.IMPEX + '/src/jobdata');
dir.mkdirs();
outputFile = new File(dir, 'catalog-' + Date.now() + '.xml');
fileWriter = new dw.io.FileWriter(outputFile);
// ... write valid catalog XML (validate against the `catalog` XSD:
// b2c docs schema catalog) ...
};
然后,在流程下一阶段的标准 ImportCatalog 步骤中,将 WorkingFolder 指向 src/jobdata(相对于 IMPEX/src/,因此使用 jobdata),并将 FileNamePattern 设为 catalog-.*\.xml,即可读取该文件。
在一个流程中串联自定义步骤和标准步骤
流程可以交错使用自定义步骤和标准步骤。例如:自定义步骤从外部系统拉取数据,并在 IMPEX 中生成目录 XML;接着,标准 ImportCatalog 步骤应用该文件;最后,标准复制步骤将变更发布到生产环境。
<job job-id="NightlyCatalogSync">
<flow>
<step step-id="generateCatalog" type="custom.GenerateCatalogExport">
<parameters>
<parameter name="OutputFolder">jobdata</parameter>
</parameters>
</step>
<step step-id="importCatalog" type="ImportCatalog">
<parameters>
<parameter name="WorkingFolder">jobdata</parameter>
<parameter name="FileNamePattern">catalog-.*\.xml</parameter>
<parameter name="ImportMode">Merge</parameter>
<parameter name="NoFilesFoundHandling">ERROR</parameter>
<parameter name="ImportFailedHandling">ERROR</parameter>
<parameter name="AfterImportFileHandling">Archive</parameter>
</parameters>
</step>
<step step-id="publishCatalog" type="ExecutePreconfiguredDataReplicationProcess">
<parameters>
<parameter name="ReplicationProcessID">nightly-catalog-publish</parameter>
</parameters>
</step>
</flow>
</job>
自定义步骤的 OutputFolder 与标准步骤的 WorkingFolder 都设为 jobdata(即 IMPEX/src/jobdata/),因此步骤 1 写入的文件正是步骤 2 导入的文件;步骤 3 随后复制结果。(在 Business Manager 中,你可以通过可视化方式构建相同的流程:添加自定义步骤,然后添加标准 ImportCatalog 步骤,再添加复制步骤,并在各步骤的表单中设置参数。)
流程内标准步骤与 CLI 等效命令
某些标准步骤与 b2c CLI 命令有重叠(CLI 的 b2c job import/b2c job export 本身就是 sfcc-site-archive-import/-export 系统作业)。请根据数据所在位置进行选择:
- 当文件在实例上生成或已暂存时,使用流程内标准步骤——尤其是当同一流程中的前置步骤生成了该文件(无需往返传输)、需要按 Business Manager 计划运行,或必须在服务器端经过自定义处理时。例如:标准
ImportCatalog步骤使用前置自定义步骤写入 IMPEX 的目录 XML。 - 当需要在本机与实例之间传输数据时,使用 CLI(
b2c job import、b2c job export)——例如上传本地归档、下载导出文件,或通过 CI 编写一次性脚本。
经验法则:数据已在实例上(或在实例上生成)→ 使用流程内标准步骤;数据需要跨越本机与实例之间的边界 → 使用 CLI。CLI 相关内容请参阅 b2c-cli:b2c-job 技能。
最佳实践
- 批量数据使用面向分块的方式——进度跟踪和恢复能力更好
- 在
afterStep()中关闭资源——查询、文件、连接 - 设置明确的超时——默认超时可能过短
- 记录进度——有助于调试
- 妥善处理错误——返回适当的 Status 对象
- 不要依赖
transactional=true——使用Transaction.wrap()进行控制
相关技能
b2c-cli:b2c-job- 使用 CLI 运行现有作业并导入站点归档b2c-cli:b2c-docs- 查询标准作业步骤类型 ID 及其参数(b2c docs read job-steps、b2c docs read <TypeID>)b2c:b2c-webservices- 当作业步骤需要调用外部 HTTP 服务或 API 时,使用 webservices 技能配置服务并了解 HTTP 客户端模式
详细参考
- 面向任务的步骤 - 完整的任务步骤模式
- 面向分块的步骤 - 完整的分块步骤模式
- steptypes.json 参考 - 完整的架构
- jobs.xml 参考 - 编写和导入作业定义(流程、步骤、必需的触发器)