关于 magento-graphql
这项开发技能介绍如何在无头店铺和应用中使用 Magento 2 与 Adobe Commerce GraphQL,涵盖商品查询、购物车、客户操作及自定义解析器。适合基于 Magento 构建项目的团队。
主要功能
- 01配置 GraphQL 客户端
- 02查询商品目录
- 03管理访客购物车
- 04客户身份验证
- 05开发自定义解析器
适用场景
- 01构建无头 Magento 店铺
- 02在移动应用中使用 Magento 数据
- 03管理购物车与结账流程
- 04公开自定义模块数据
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill magento-graphql安装后在 Claude Code 中输入/magento-graphql确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
tile.jsonevals/cart-lifecycle-and-customer-authenticati/criteria.jsontask.mdevals/graphql-client-setup-and-auth-security/criteria.jsontask.mdevals/product-catalog-queries-with-polymorphic/criteria.jsontask.md详细介绍
Magento GraphQL API
概述
Magento 2(以及 Adobe Commerce)提供全面的 GraphQL API,用于店面操作,包括产品目录、搜索、购物车、结账、客户身份验证和订单历史记录。端点为 /graphql,查询公开目录数据无需管理员令牌。购物车和客户操作需要访客购物车 ID(UUID)或客户登录后获取的 Bearer 令牌。PWA Studio(Venia)完全基于此 API 构建,是 Magento 无头架构的参考实现。
何时使用此技能
- 在 Magento 2 上构建 React、Vue 或 Next.js 无头店面时
- 创建需要 Magento 产品和结账数据的原生移动应用时
- 实现通过 GraphQL 解析器获取自定义数据的 PWA Studio 扩展时
- 在现有无头项目中用更高效的 GraphQL 查询替换 REST API 调用时
- 构建自定义 GraphQL 解析器,以公开第三方或自定义模块数据时
- 将无头 CMS 与 Magento 产品目录集成时
核心说明
-
设置 GraphQL 客户端
Magento GraphQL 使用标准 HTTP POST 请求访问
/graphql。请使用 Apollo Client 或轻量级 fetch 封装:// lib/magento.ts const MAGENTO_URL = process.env.NEXT_PUBLIC_MAGENTO_URL; // e.g. https://magento.example.com export async function magentoQuery<T>( query: string, variables: Record<string, unknown> = {}, token?: string ): Promise<T> { const headers: Record<string, string> = { "Content-Type": "application/json", Store: process.env.NEXT_PUBLIC_MAGENTO_STORE_CODE ?? "default", }; if (token) { headers["Authorization"] = `Bearer ${token}`; } const response = await fetch(`${MAGENTO_URL}/graphql`, { method: "POST", headers, body: JSON.stringify({ query, variables }), next: { revalidate: 300 }, // Next.js ISR caching }); if (!response.ok) { throw new Error(`Magento GraphQL HTTP error: ${response.status}`); } const { data, errors } = await response.json(); if (errors?.length) { throw new Error(errors[0].message); } return data as T; } -
查询产品目录
// Fetch products by category UID or search export async function getProducts(params: { categoryUid?: string; search?: string; pageSize?: number; currentPage?: number; }) { return magentoQuery<{ products: MagentoProductList }>(` query GetProducts( $search: String $filter: ProductAttributeFilterInput $pageSize: Int $currentPage: Int ) { products( search: $search filter: $filter pageSize: $pageSize currentPage: $currentPage sort: { position: ASC } ) { total_count page_info { current_page page_size total_pages } aggregations { attribute_code label count options { label value count } } items { uid sku name url_key ... on SimpleProduct { price_range { minimum_price { regular_price { value currency } final_price { value currency } discount { percent_off amount_off } } } } ... on ConfigurableProduct { configurable_options { attribute_code label values { uid label swatch_data { value } } } variants { attributes { uid label code value_index } product { sku stock_status price_range { minimum_price { final_price { value } } } } } } small_image { url label } thumbnail { url label } rating_summary review_count stock_status } } } `, { search: params.search, filter: params.categoryUid ? { category_uid: { eq: params.categoryUid } } : undefined, pageSize: params.pageSize ?? 20, currentPage: params.currentPage ?? 1, }); } -
创建访客购物车并添加商品
// Create a guest cart and get the cart ID export async function createGuestCart(): Promise<string> { const data = await magentoQuery<{ createEmptyCart: string }>(` mutation { createEmptyCart } `); return data.createEmptyCart; // Returns a UUID cart ID } // Add a simple product to cart export async function addSimpleProductToCart(cartId: string, sku: string, qty: number) { return magentoQuery(` mutation AddSimpleProduct($cartId: String!, $sku: String!, $qty: Float!) { addSimpleProductsToCart( input: { cart_id: $cartId cart_items: [{ data: { sku: $sku, quantity: $qty } }] } ) { cart { items { uid quantity product { name sku } prices { price { value currency } } } prices { subtotal_excluding_tax { value currency } grand_total { value currency } } } } } `, { cartId, sku, qty }); } // Add a configurable product with selected variant export async function addConfigurableProductToCart( cartId: string, parentSku: string, variantSku: string, qty: number ) { return magentoQuery(` mutation AddConfigurable($cartId: String!, $parentSku: String!, $variantSku: String!, $qty: Float!) { addConfigurableProductsToCart( input: { cart_id: $cartId cart_items: [{ parent_sku: $parentSku data: { sku: $variantSku, quantity: $qty } }] } ) { cart { items { uid quantity product { name } } } } } `, { cartId, parentSku, variantSku, qty }); } -
验证客户身份并管理账户
// Customer login — returns a bearer token export async function loginCustomer(email: string, password: string): Promise<string> { const data = await magentoQuery<{ generateCustomerToken: { token: string } }>(` mutation Login($email: String!, $password: String!) { generateCustomerToken(email: $email, password: $password) { token } } `, { email, password }); return data.generateCustomerToken.token; } // Get authenticated customer's cart export async function getCustomerCart(token: string) { return magentoQuery(` query { customerCart { id items { uid quantity product { name sku small_image { url } } prices { price { value currency } } } prices { grand_total { value currency } } } } `, {}, token); } // Get order history export async function getCustomerOrders(token: string, pageSize = 10) { return magentoQuery(` query GetOrders($pageSize: Int) { customer { orders(pageSize: $pageSize, sort: { sort_field: CREATED_AT, sort_direction: DESC }) { total_count items { id number status order_date total { grand_total { value currency } } items { product_name product_sku quantity_ordered } shipping_address { firstname lastname city region { code } country_code } } } } } `, { pageSize }, token); } -
在 Magento 2 模块中创建自定义 GraphQL 解析器
<?php // app/code/MyVendor/CustomGraphQL/etc/schema.graphqls type Query { myCustomProducts( brand: String @doc(description: "Filter by brand") ): MyCustomProductOutput @resolver(class: "MyVendor\\CustomGraphQL\\Model\\Resolver\\CustomProducts") @doc(description: "Get custom product list") } type MyCustomProductOutput { items: [CustomProductItem] total_count: Int } type CustomProductItem { sku: String name: String brand: String custom_attribute: String }<?php // app/code/MyVendor/CustomGraphQL/Model/Resolver/CustomProducts.php namespace MyVendor\CustomGraphQL\Model\Resolver; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; class CustomProducts implements ResolverInterface { public function __construct( private readonly CollectionFactory $collectionFactory ) {} public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { $collection = $this->collectionFactory->create(); $collection->addAttributeToSelect(['sku', 'name', 'brand', 'custom_attribute']); $collection->addAttributeToFilter('status', 1); $collection->addAttributeToFilter('visibility', ['neq' => 1]); if (!empty($args['brand'])) { $collection->addAttributeToFilter('brand', ['like' => '%' . $args['brand'] . '%']); } $items = []; foreach ($collection as $product) { $items[] = [ 'sku' => $product->getSku(), 'name' => $product->getName(), 'brand' => $product->getData('brand'), 'custom_attribute' => $product->getData('custom_attribute'), ]; } return ['items' => $items, 'total_count' => $collection->getSize()]; } }
示例
产品详情页 — 带色板的可配置产品
export async function getProductByUrlKey(urlKey: string) {
return magentoQuery<{ products: { items: MagentoProduct[] } }>(`
query GetProduct($urlKey: String!) {
products(filter: { url_key: { eq: $urlKey } }) {
items {
uid sku name url_key
meta_title meta_description
description { html }
short_description { html }
... on ConfigurableProduct {
configurable_options {
id uid label attribute_code position
values {
uid label
swatch_data {
... on ColorSwatchData { value }
... on ImageSwatchData { thumbnail value }
... on TextSwatchData { value }
}
}
}
variants {
attributes { uid label code value_index }
product {
uid sku stock_status
media_gallery { url label disabled }
price_range {
minimum_price {
regular_price { value currency }
final_price { value currency }
}
}
}
}
}
media_gallery { url label disabled position }
reviews(pageSize: 5) {
items {
summary text created_at
ratings_breakdown { name value }
nickname
}
}
}
}
}
`, { urlKey });
}
结账流程 — 设置配送地址并下单
export async function setShippingAddress(cartId: string, address: AddressInput) {
return magentoQuery(`
mutation SetShipping($cartId: String!, $address: CartAddressInput!) {
setShippingAddressesOnCart(input: {
cart_id: $cartId
shipping_addresses: [{ address: $address }]
}) {
cart {
shipping_addresses {
available_shipping_methods {
carrier_code method_code carrier_title method_title
amount { value currency }
}
}
}
}
}
`, { cartId, address });
}
export async function placeOrder(cartId: string) {
return magentoQuery<{ placeOrder: { order: { order_number: string } } }>(`
mutation PlaceOrder($cartId: String!) {
placeOrder(input: { cart_id: $cartId }) {
order { order_number }
errors { message code }
}
}
`, { cartId });
}
最佳实践
- 使用
StoreHTTP 标头指定特定店铺视图——如果不指定,Magento 会默认使用默认店铺;在多店铺配置中,这会导致定价或货币不正确 - 通过 Varnish 或内置缓存启用 GraphQL 查询缓存,用于公开目录查询——缓存产品列表查询数分钟即可减少 PHP 执行
- 对多态产品类型使用内联片段——在适用时,始终包含
... on SimpleProduct、... on ConfigurableProduct、... on BundleProduct - 将购物车 ID 保存在 Cookie 中——访客购物车 ID(UUID)在 24 小时无活动后过期;登录后使用
mergeCartsmutation 与客户购物车合并 - 批量处理相关查询——对于同时需要产品、类别和 CMS 区块数据的页面,Apollo Client 批处理或手动合并查询可减少往返时间
- 为
errors数组实现适当的错误处理——即使发生错误,GraphQL 响应也会返回 200;务必检查响应正文中的errors属性 - 安全地缓存客户令牌——将 bearer 令牌存储在
httpOnlyCookie 中,而不是localStorage,以防止 XSS 令牌窃取
常见问题
| 问题 | 解决方案 |
|---|---|
| GraphQL 中缺少产品,但在 Admin 中可见 | 检查产品的 visibility 属性——设为“Not Visible Individually”的产品不会出现在目录查询中 |
可配置产品变体返回空的 stock_status | 查询 variants.product 中的 product 字段——库存按简单产品(变体)级别跟踪,而不是按父级跟踪 |
| 无法识别自定义解析器 | 添加新的 GraphQL 架构文件后运行 bin/magento setup:upgrade;同时仔细检查 schema.graphqls 语法 |
| 客户登录后购物车合并失败 | 调用 mergeCarts(source_cart_id: $guestCartId, destination_cart_id: $customerCartId)——然后丢弃访客购物车 ID Cookie |
| GraphQL 响应缓慢 | 启用 Magento 内置的 GraphQL 缓存:bin/magento config:set system/full_page_cache/caching_application 1;对公开查询使用 Varnish |
| 会话期间 Bearer 令牌过期 | 实现令牌刷新:捕获 The current customer isn't authorized 错误并重定向至登录页面,或在可用时使用刷新令牌 |
Related Skills
- @magento-module-development
- @magento-indexing-caching
- @magento-multi-store
- @headless-commerce-architecture
- @graphql-api-design
评论
支持 Markdown