mcpField
B
b2c-custom-job-steps

b2c-custom-job-steps

github.com/salesforcecommercecloud开发5783 次安装今天更新
代码仓库

关于 b2c-custom-job-steps

为 Salesforce B2C Commerce 开发者提供创建自定义批处理作业步骤的指导,涵盖任务型与分块型执行模式、步骤定义和作业流程配置。适用于数据导入导出、同步及维护任务。

主要功能

  1. 01任务型与分块型步骤设计
  2. 02配置 steptypes.json 与 jobs.xml
  3. 03定义和校验步骤参数
  4. 04将自定义步骤与标准步骤串联
  5. 05指导 IMPEX 文件交接

适用场景

  1. 01创建商品或订单批量导入作业
  2. 02实现数据导出或系统间同步
  3. 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.json
references/
CHUNK-ORIENTED.md
JOBS-XML.md
STEPTYPES-JSON.md
TASK-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"
booleantrue/falsetrue
long整数12345
double小数123.45
datetime-stringISO 日期时间"2024-01-15T10:30:00Z"
date-stringISO 日期"2024-01-15"
time-stringISO 时间"10:30:00"
参数验证属性
属性适用范围描述
@trim全部验证前去除空白(默认:true)
@required全部标记为必填(默认:true)
@target-typedatetime-string, date-string, time-string转换为 long 或 date(默认:date)
patternstring用于验证的正则表达式模式
min-lengthstring最小字符串长度(必须 ≥1)
max-lengthstring最大字符串长度(总计最多 1000 个字符)
min-valuelong, double, datetime-string, time-string最小数值
max-valuelong, 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范围关键必需参数
ImportCatalogOrganizationNoFilesFoundHandling、ImportMode、ImportFailedHandling
ImportInventoryListsOrganizationNoFilesFoundHandling、ImportMode、ImportFailedHandling
ImportPriceBookOrganizationNoFilesFoundHandling、ImportMode、ImportFailedHandling
ImportContentSiteNoFilesFoundHandling、ImportMode、ImportFailedHandling
ImportCustomObjectsOrganization & SitesNoFilesFoundHandling、ImportFailedHandling
ExportCatalogOrganizationCatalogID
ExportInventoryListsSite(无)
ExportPriceBookOrganizationPriceBookID
ExportContentOrganization & SitesLibraryID
ExportOrdersSiteConfirmation Status、Shipment Status、Payment Status
ExecutePreconfiguredDataReplicationProcessOrganizationReplicationProcessID
SearchReindexSiteIndexer Type
ExecuteScriptModuleOrganization & SitesExecuteScriptModule.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 技能。

最佳实践

  1. 批量数据使用面向分块的方式——进度跟踪和恢复能力更好
  2. 在 afterStep() 中关闭资源——查询、文件、连接
  3. 设置明确的超时——默认超时可能过短
  4. 记录进度——有助于调试
  5. 妥善处理错误——返回适当的 Status 对象
  6. 不要依赖 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 客户端模式

详细参考

评论

支持 Markdown