b2c-controllers

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Controllers Skill

控制器技能

This skill guides you through creating storefront controllers for Salesforce B2C Commerce. Controllers handle HTTP requests and render responses for the storefront.
本技能将指导你为Salesforce B2C Commerce创建店面控制器。控制器负责处理HTTP请求并为店面渲染响应内容。

Overview

概述

Controllers are JavaScript modules that handle storefront requests. A controller URL has this structure:
https://{domain}/on/demandware.store/Sites-{SiteName}-Site/{locale}/{ControllerName}-{FunctionName}
Example:
https://example.com/on/demandware.store/Sites-RefArch-Site/en_US/Home-Show
控制器是用于处理店面请求的JavaScript模块。控制器URL的结构如下:
https://{domain}/on/demandware.store/Sites-{SiteName}-Site/{locale}/{ControllerName}-{FunctionName}
示例:
https://example.com/on/demandware.store/Sites-RefArch-Site/en_US/Home-Show

Two Controller Patterns

两种控制器模式

B2C Commerce supports two controller patterns:
PatternWhen to UseModule Style
SFRAStorefront Reference Architecture sites
server
module with middleware
ClassicNon-SFRA sites, simple APIsDirect exports with
.public = true
SFRA is recommended for most storefront development. Classic controllers are useful for simple endpoints or non-SFRA projects.
B2C Commerce支持两种控制器模式:
模式适用场景模块风格
SFRA店面参考架构站点
server
模块与中间件
经典非SFRA站点、简单API带有
.public = true
标记的直接导出
大多数店面开发推荐使用SFRA。经典控制器适用于简单端点或非SFRA项目。

File Location

文件位置

Controllers reside in the cartridge's
controllers
directory:
/my-cartridge
    /cartridge
        /controllers
            Home.js           # URL: Home-{function}
            Product.js        # URL: Product-{function}
            Cart.js           # URL: Cart-{function}
Naming: Controller filename becomes the URL prefix.
Home.js
handles
Home-*
requests.
控制器存放在cartridge的
controllers
目录下:
/my-cartridge
    /cartridge
        /controllers
            Home.js           # URL: Home-{function}
            Product.js        # URL: Product-{function}
            Cart.js           # URL: Cart-{function}
命名规则: 控制器文件名会成为URL前缀,
Home.js
会处理所有
Home-*
请求。

SFRA Controllers (Recommended)

SFRA控制器(推荐)

SFRA controllers use the
server
module for routing and middleware:
javascript
'use strict';

var server = require('server');

// Handle GET request
server.get('Show', function (req, res, next) {
    res.render('home/homepage');
    next();
});

// Handle POST request
server.post('Subscribe', function (req, res, next) {
    var email = req.form.email;
    // Process subscription...
    res.json({ success: true });
    next();
});

module.exports = server.exports();
SFRA控制器使用
server
模块实现路由和中间件功能:
javascript
'use strict';

var server = require('server');

// Handle GET request
server.get('Show', function (req, res, next) {
    res.render('home/homepage');
    next();
});

// Handle POST request
server.post('Subscribe', function (req, res, next) {
    var email = req.form.email;
    // Process subscription...
    res.json({ success: true });
    next();
});

module.exports = server.exports();

Request Object (req)

请求对象(req)

javascript
req.querystring          // Query parameters: ?q=shoes -> req.querystring.q
req.form                 // Form POST data: req.form.email
req.httpMethod           // HTTP method: 'GET', 'POST', etc.
req.httpHeaders          // Request headers
req.currentCustomer      // Current customer object
req.locale               // Current locale
req.session              // Session object
javascript
req.querystring          // Query parameters: ?q=shoes -> req.querystring.q
req.form                 // Form POST data: req.form.email
req.httpMethod           // HTTP method: 'GET', 'POST', etc.
req.httpHeaders          // Request headers
req.currentCustomer      // Current customer object
req.locale               // Current locale
req.session              // Session object

Response Object (res)

响应对象(res)

javascript
res.render('template', model)   // Render ISML template with data
res.json(object)                // Return JSON response
res.redirect(url)               // Redirect to URL
res.setViewData(data)           // Add data to view model
res.getViewData()               // Get current view model
res.setStatusCode(code)         // Set HTTP status code
javascript
res.render('template', model)   // Render ISML template with data
res.json(object)                // Return JSON response
res.redirect(url)               // Redirect to URL
res.setViewData(data)           // Add data to view model
res.getViewData()               // Get current view model
res.setStatusCode(code)         // Set HTTP status code

Middleware

中间件

Apply middleware to routes for cross-cutting concerns:
javascript
var server = require('server');
var cache = require('*/cartridge/scripts/middleware/cache');
var consentTracking = require('*/cartridge/scripts/middleware/consentTracking');
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');

// Apply caching
server.get('Show', cache.applyDefaultCache, function (req, res, next) {
    res.render('home/homepage');
    next();
});

// Require HTTPS
server.post('Login', server.middleware.https, function (req, res, next) {
    // Handle login...
    next();
});

// CSRF protection for forms
server.post('Submit', csrfProtection.validateAjaxRequest, function (req, res, next) {
    // Handle form submission...
    next();
});
为路由应用中间件来处理横切关注点:
javascript
var server = require('server');
var cache = require('*/cartridge/scripts/middleware/cache');
var consentTracking = require('*/cartridge/scripts/middleware/consentTracking');
var csrfProtection = require('*/cartridge/scripts/middleware/csrf');

// Apply caching
server.get('Show', cache.applyDefaultCache, function (req, res, next) {
    res.render('home/homepage');
    next();
});

// Require HTTPS
server.post('Login', server.middleware.https, function (req, res, next) {
    // Handle login...
    next();
});

// CSRF protection for forms
server.post('Submit', csrfProtection.validateAjaxRequest, function (req, res, next) {
    // Handle form submission...
    next();
});

Common Middleware

常用中间件

MiddlewarePurpose
server.middleware.https
Require HTTPS connection
cache.applyDefaultCache
Apply default page caching
csrfProtection.validateAjaxRequest
Validate CSRF token
consentTracking.consent
Check tracking consent
userLoggedIn.validateLoggedIn
Require authenticated user
中间件用途
server.middleware.https
要求HTTPS连接
cache.applyDefaultCache
应用默认页面缓存
csrfProtection.validateAjaxRequest
验证CSRF令牌
consentTracking.consent
检查追踪授权状态
userLoggedIn.validateLoggedIn
要求用户已完成身份验证

Route Events

路由事件

Execute code at specific points in the request lifecycle:
javascript
server.post('Submit', function (req, res, next) {
    var form = req.form;
    res.setViewData({ email: form.email });
    next();
}, function (req, res, next) {
    // Additional middleware
    next();
});

// Execute after all middleware, before render
this.on('route:BeforeComplete', function (req, res) {
    var viewData = res.getViewData();
    // Modify view data if needed
});
在请求生命周期的特定节点执行代码:
javascript
server.post('Submit', function (req, res, next) {
    var form = req.form;
    res.setViewData({ email: form.email });
    next();
}, function (req, res, next) {
    // Additional middleware
    next();
});

// Execute after all middleware, before render
this.on('route:BeforeComplete', function (req, res) {
    var viewData = res.getViewData();
    // Modify view data if needed
});

Extending Controllers

扩展控制器

Extend existing controllers to add or modify functionality:
javascript
'use strict';

var server = require('server');
var page = module.superModule;  // Get parent controller

server.extend(page);

// Add new route
server.get('NewRoute', function (req, res, next) {
    res.render('newtemplate');
    next();
});

// Override existing route
server.replace('Show', function (req, res, next) {
    // Custom implementation
    res.render('custom/homepage');
    next();
});

// Prepend to existing route
server.prepend('Show', function (req, res, next) {
    // Runs before original handler
    next();
});

// Append to existing route
server.append('Show', function (req, res, next) {
    // Runs after original handler
    var viewData = res.getViewData();
    viewData.customData = 'value';
    res.setViewData(viewData);
    next();
});

module.exports = server.exports();
扩展现有控制器以新增或修改功能:
javascript
'use strict';

var server = require('server');
var page = module.superModule;  // Get parent controller

server.extend(page);

// Add new route
server.get('NewRoute', function (req, res, next) {
    res.render('newtemplate');
    next();
});

// Override existing route
server.replace('Show', function (req, res, next) {
    // Custom implementation
    res.render('custom/homepage');
    next();
});

// Prepend to existing route
server.prepend('Show', function (req, res, next) {
    // Runs before original handler
    next();
});

// Append to existing route
server.append('Show', function (req, res, next) {
    // Runs after original handler
    var viewData = res.getViewData();
    viewData.customData = 'value';
    res.setViewData(viewData);
    next();
});

module.exports = server.exports();

Classic Controllers (Non-SFRA)

经典控制器(非SFRA)

For non-SFRA sites or simple endpoints, use direct exports:
javascript
'use strict';

var ISML = require('dw/template/ISML');

exports.Show = function () {
    var params = request.httpParameterMap;
    var productId = params.pid.stringValue;

    ISML.renderTemplate('product/detail', {
        productId: productId
    });
};
exports.Show.public = true;  // Required: marks function as accessible

exports.GetData = function () {
    var result = { status: 'ok', data: [] };

    response.setContentType('application/json');
    response.writer.print(JSON.stringify(result));
};
exports.GetData.public = true;
Key difference: Classic controllers use
exports.FunctionName.public = true
instead of the
server
module.
对于非SFRA站点或简单端点,可使用直接导出的方式:
javascript
'use strict';

var ISML = require('dw/template/ISML');

exports.Show = function () {
    var params = request.httpParameterMap;
    var productId = params.pid.stringValue;

    ISML.renderTemplate('product/detail', {
        productId: productId
    });
};
exports.Show.public = true;  // Required: marks function as accessible

exports.GetData = function () {
    var result = { status: 'ok', data: [] };

    response.setContentType('application/json');
    response.writer.print(JSON.stringify(result));
};
exports.GetData.public = true;
核心区别: 经典控制器使用
exports.FunctionName.public = true
标记可访问方法,而非使用
server
模块。

Module Imports

模块导入

Import B2C Commerce APIs using
require()
:
javascript
// B2C Commerce APIs
var ProductMgr = require('dw/catalog/ProductMgr');
var Transaction = require('dw/system/Transaction');
var Logger = require('dw/system/Logger');
var URLUtils = require('dw/web/URLUtils');
var Resource = require('dw/web/Resource');

// Cartridge modules (use */ for cartridge path resolution)
var collections = require('*/cartridge/scripts/util/collections');
var productHelper = require('*/cartridge/scripts/helpers/productHelpers');
Best Practice: Only require modules when needed, not all at the top of the file.
使用
require()
导入B2C Commerce API:
javascript
// B2C Commerce APIs
var ProductMgr = require('dw/catalog/ProductMgr');
var Transaction = require('dw/system/Transaction');
var Logger = require('dw/system/Logger');
var URLUtils = require('dw/web/URLUtils');
var Resource = require('dw/web/Resource');

// Cartridge modules (use */ for cartridge path resolution)
var collections = require('*/cartridge/scripts/util/collections');
var productHelper = require('*/cartridge/scripts/helpers/productHelpers');
最佳实践: 仅在需要时导入模块,不要在文件顶部一次性导入所有模块。

Error Handling

错误处理

Wrap operations in try-catch blocks:
javascript
server.get('Show', function (req, res, next) {
    try {
        var product = ProductMgr.getProduct(req.querystring.pid);
        if (!product) {
            res.setStatusCode(404);
            res.render('error/notfound');
            return next();
        }
        res.render('product/detail', { product: product });
    } catch (e) {
        Logger.error('Product error: ' + e.message);
        res.setStatusCode(500);
        res.render('error/general');
    }
    next();
});
使用try-catch块包裹操作逻辑:
javascript
server.get('Show', function (req, res, next) {
    try {
        var product = ProductMgr.getProduct(req.querystring.pid);
        if (!product) {
            res.setStatusCode(404);
            res.render('error/notfound');
            return next();
        }
        res.render('product/detail', { product: product });
    } catch (e) {
        Logger.error('Product error: ' + e.message);
        res.setStatusCode(500);
        res.render('error/general');
    }
    next();
});

Generating URLs

生成URL

Use URLUtils to generate locale-aware URLs:
javascript
var URLUtils = require('dw/web/URLUtils');

// Controller URL
var productUrl = URLUtils.url('Product-Show', 'pid', 'ABC123');
// Result: /on/demandware.store/Sites-RefArch-Site/en_US/Product-Show?pid=ABC123

// HTTPS URL
var loginUrl = URLUtils.https('Login-Show');

// Static resource URL
var imageUrl = URLUtils.staticURL('/images/logo.png');
使用URLUtils生成适配多语言的URL:
javascript
var URLUtils = require('dw/web/URLUtils');

// Controller URL
var productUrl = URLUtils.url('Product-Show', 'pid', 'ABC123');
// Result: /on/demandware.store/Sites-RefArch-Site/en_US/Product-Show?pid=ABC123

// HTTPS URL
var loginUrl = URLUtils.https('Login-Show');

// Static resource URL
var imageUrl = URLUtils.staticURL('/images/logo.png');

Best Practices

最佳实践

  1. Always call
    next()
    in SFRA middleware chain
  2. Use ViewModels to prepare data for templates
  3. Keep controllers thin - move business logic to scripts/helpers
  4. Use hooks for functionality that works for both storefront and OCAPI
  5. Handle errors gracefully - never expose stack traces
  6. Use
    */cartridge/...
    for portable module paths
  1. 在SFRA中间件链中始终调用
    next()
  2. 使用ViewModel为模板准备数据
  3. 保持控制器轻量化 - 将业务逻辑移至scripts/helpers目录
  4. 使用钩子实现同时适用于店面和OCAPI的功能
  5. 优雅处理错误 - 切勿暴露堆栈跟踪信息
  6. 使用
    */cartridge/...
    格式实现可移植的模块路径

Detailed Reference

详细参考

For comprehensive patterns and examples:
  • SFRA Patterns - Full SFRA patterns with middleware
  • Classic Patterns - Non-SFRA controller patterns
如需了解完整模式和示例:
  • SFRA模式 - 包含中间件的完整SFRA模式
  • 经典模式 - 非SFRA控制器模式