核心概念

电商网站的商品详情页展示了超媒体驱动架构的核心理念:同一份数据描述,多种消费方式。服务端返回包含丰富链接关系的超媒体文档,不同客户端根据自身能力和需求选择性地消费关联资源。

技术实现

基础商品页面结构

<!DOCTYPE html>
<html>
<head>
    <title>智能手表 SR-5000 - 示例商城</title>
    <!-- 核心样式 -->
    <link rel="stylesheet" href="/styles/core.css">
</head>
<body>
    <div class="product" data-product-id="123">
        
        <header>
            <h1>智能手表 SR-5000</h1>
            <img src="/img/watch-sr5000.jpg" alt="SR-5000 手表" />
        </header>
        
        <section class="description">
            <p>这是我们最新款的智能手表,配备 GPS 和心率监测...</p>
            <span class="price">¥1999.00</span>
        </section>
 
        <!-- 超媒体链接声明开始 -->
        <link rel="reviews" 
              href="/api/products/123/reviews" 
              type="application/json" 
              title="查看评价" />
        
        <link rel="related-products" 
              href="/api/products/123/related" 
              type="application/json" 
              title="相关商品" />
 
        <link rel="add-to-cart" 
              href="/api/cart/add?item_id=123" 
              method="POST"
              title="添加到购物车" />
 
        <link rel="inventory" 
              href="/api/products/123/inventory" 
              type="application/json" 
              title="库存信息" />
 
        <link rel="alternate" 
              type="model/gltf-binary" 
              href="/models/sr5000.glb" 
              title="3D/AR 模型" />
 
        <link rel="alternate" 
              type="application/pdf" 
              href="/docs/sr5000-specs.pdf" 
              title="下载完整规格书" />
        
        <link rel="price-alert" 
              href="/api/products/123/price-alerts" 
              method="POST"
              type="application/json"
              title="设置降价提醒" />
    </div>
</body>
</html>

客户端消费场景分析

场景一:普通网页浏览器

消费模式:基础内容 + 选择性增强

// 浏览器默认行为
class BrowserClient {
    consume(htmlDocument) {
        // 1. 渲染核心HTML内容
        this.renderCoreContent(htmlDocument);
        
        // 2. 选择性处理已知的link关系
        const links = htmlDocument.querySelectorAll('link[rel="stylesheet"]');
        links.forEach(link => this.loadCSS(link.href));
        
        // 3. 忽略不理解的link关系(reviews, add-to-cart等)
        // 这些关系需要额外JS来激活
    }
    
    enhanceWithJavaScript() {
        // 渐进增强:通过JS激活超媒体链接
        const reviewLink = document.querySelector('link[rel="reviews"]');
        if (reviewLink) {
            this.loadReviews(reviewLink.href);
        }
        
        const cartLink = document.querySelector('link[rel="add-to-cart"]');
        if (cartLink) {
            this.setupAddToCart(cartLink.href, cartLink.getAttribute('method'));
        }
    }
    
    async loadReviews(reviewsUrl) {
        try {
            const response = await fetch(reviewsUrl);
            const reviews = await response.json();
            this.renderReviews(reviews);
        } catch (error) {
            console.log('评价加载失败,但不影响核心功能');
        }
    }
}

场景二:移动端电商 App

消费模式:主动发现 + 深度集成

class MobileAppClient {
    async consumeProduct(productUrl) {
        // 1. 获取超媒体文档
        const response = await fetch(productUrl, {
            headers: { 'Accept': 'text/html' }
        });
        const html = await response.text();
        
        // 2. 解析超媒体链接
        const hypermediaLinks = this.parseHypermediaLinks(html);
        
        // 3. 并行加载相关资源
        await Promise.all([
            this.loadReviews(hypermediaLinks.reviews),
            this.loadInventory(hypermediaLinks.inventory),
            this.loadRelatedProducts(hypermediaLinks.relatedProducts)
        ]);
        
        // 4. 渲染集成界面
        this.renderIntegratedProductView();
    }
    
    parseHypermediaLinks(html) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        
        return {
            reviews: doc.querySelector('link[rel="reviews"]')?.href,
            inventory: doc.querySelector('link[rel="inventory"]')?.href,
            relatedProducts: doc.querySelector('link[rel="related-products"]')?.href,
            addToCart: {
                url: doc.querySelector('link[rel="add-to-cart"]')?.href,
                method: doc.querySelector('link[rel="add-to-cart"]')?.getAttribute('method')
            },
            arModel: doc.querySelector('link[rel="alternate"][type="model/gltf-binary"]')?.href
        };
    }
    
    async loadReviews(reviewsUrl) {
        if (!reviewsUrl) return;
        
        const response = await fetch(reviewsUrl);
        this.reviewsData = await response.json();
    }
    
    setupAddToCart() {
        // 使用发现的操作端点
        const cartAction = this.hypermediaLinks.addToCart;
        this.cartButton.onClick = () => {
            fetch(cartAction.url, {
                method: cartAction.method || 'POST',
                body: JSON.stringify({ productId: this.productId })
            });
        };
    }
}

场景三:AR 试戴应用

消费模式:目标驱动 + 最小化消费

class ARTryOnApp {
    async findAndLoad3DModel(productUrl) {
        // 1. 只获取HTML头部,寻找3D模型链接
        const headResponse = await fetch(productUrl, {
            headers: { 
                'Accept': 'text/html',
                'Range': 'bytes=0-2048' // 只获取前2KB,通常包含所有link
            }
        });
        
        const headHtml = await headResponse.text();
        const modelUrl = this.extract3DModelUrl(headHtml);
        
        if (modelUrl) {
            // 2. 直接加载3D模型,忽略其他所有内容
            await this.loadGLBModel(modelUrl);
            this.setupARExperience();
        } else {
            throw new Error('此商品不支持AR试戴');
        }
    }
    
    extract3DModelUrl(html) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        
        const modelLink = doc.querySelector(
            'link[rel="alternate"][type="model/gltf-binary"]'
        );
        
        return modelLink?.href;
    }
}

场景四:价格监控爬虫

消费模式:特定关系挖掘

class PriceMonitorBot {
    async monitorProducts(productUrls) {
        for (const url of productUrls) {
            const html = await this.fetchProductPage(url);
            const productInfo = this.extractProductInfo(html);
            
            // 发现价格提醒端点
            const priceAlertLink = this.findPriceAlertLink(html);
            if (priceAlertLink) {
                await this.subscribeToPriceAlert(priceAlertLink, productInfo);
            }
        }
    }
    
    findPriceAlertLink(html) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        
        const priceAlertLink = doc.querySelector('link[rel="price-alert"]');
        if (priceAlertLink) {
            return {
                url: priceAlertLink.href,
                method: priceAlertLink.getAttribute('method') || 'POST'
            };
        }
        return null;
    }
}

服务端实现

超媒体驱动的 API 设计

// Express.js 示例
app.get('/products/:id', (req, res) => {
    const product = getProductById(req.params.id);
    
    // 基础产品信息
    const productHtml = `
        <div class="product" data-product-id="${product.id}">
            <h1>${product.name}</h1>
            <p>${product.description}</p>
            <span class="price">${product.price}</span>
            
            ${this.renderHypermediaLinks(product)}
        </div>
    `;
    
    res.send(productHtml);
});
 
// 生成超媒体链接
function renderHypermediaLinks(product) {
    const baseUrl = '/api';
    const productId = product.id;
    
    return `
        <link rel="reviews" href="${baseUrl}/products/${productId}/reviews" type="application/json" />
        <link rel="related-products" href="${baseUrl}/products/${productId}/related" type="application/json" />
        <link rel="add-to-cart" href="${baseUrl}/cart/add" method="POST" />
        <link rel="inventory" href="${baseUrl}/products/${productId}/inventory" type="application/json" />
        ${product.has3DModel ? 
            `<link rel="alternate" type="model/gltf-binary" href="/models/${product.modelFile}" />` : ''}
        <link rel="price-alert" href="${baseUrl}/products/${productId}/price-alerts" method="POST" />
    `;
}

架构优势

1. 关注点分离

  • 服务端:专注于声明可用的服务和关系
  • 客户端:根据自身需求消费相关服务

2. 向后兼容

// 新客户端可以发现新功能
const newFeatureLink = document.querySelector('link[rel="new-feature"]');
if (newFeatureLink) {
    // 使用新功能
} else {
    // 降级到基础功能
}

3. 动态能力发现

class AdaptiveClient {
    async discoverCapabilities(productUrl) {
        const links = await this.fetchHypermediaLinks(productUrl);
        
        this.capabilities = {
            hasReviews: !!links.reviews,
            hasAR: !!links.arModel,
            hasPriceAlerts: !!links.priceAlert,
            canAddToCart: !!links.addToCart
        };
        
        this.setupUIBasedOnCapabilities();
    }
}

实际业务扩展

个性化链接

<!-- 根据用户权限动态生成链接 -->
<link rel="admin-actions" 
      href="/api/admin/products/123" 
      type="application/json"
      role="administrator" />
 
<!-- 根据库存状态 -->
<link rel="pre-order" 
      href="/api/products/123/pre-order" 
      method="POST"
      available="${product.inStock ? 'false' : 'true'}" />

这种超媒体驱动的架构使电商系统能够优雅地演进,新功能可以通过添加新的链接关系来引入,而不会破坏现有客户端的正常工作。