核心概念
电商网站的商品详情页展示了超媒体驱动架构的核心理念:同一份数据描述,多种消费方式。服务端返回包含丰富链接关系的超媒体文档,不同客户端根据自身能力和需求选择性地消费关联资源。
技术实现
基础商品页面结构
<!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'}" />这种超媒体驱动的架构使电商系统能够优雅地演进,新功能可以通过添加新的链接关系来引入,而不会破坏现有客户端的正常工作。