mcpField
M
magento-graphql

magento-graphql

github.com/finsilabs开发6079 次安装5 天前更新
代码仓库

关于 magento-graphql

这项开发技能介绍如何在无头店铺和应用中使用 Magento 2 与 Adobe Commerce GraphQL,涵盖商品查询、购物车、客户操作及自定义解析器。适合基于 Magento 构建项目的团队。

主要功能

  1. 01配置 GraphQL 客户端
  2. 02查询商品目录
  3. 03管理访客购物车
  4. 04客户身份验证
  5. 05开发自定义解析器

适用场景

  1. 01构建无头 Magento 店铺
  2. 02在移动应用中使用 Magento 数据
  3. 03管理购物车与结账流程
  4. 04公开自定义模块数据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill magento-graphql

安装后在 Claude Code 中输入/magento-graphql确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/cart-lifecycle-and-customer-authenticati/
criteria.json
task.md
evals/graphql-client-setup-and-auth-security/
criteria.json
task.md
evals/product-catalog-queries-with-polymorphic/
criteria.json
task.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 产品目录集成时

核心说明

  1. 设置 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;
    }
    
  2. 查询产品目录

    // 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,
      });
    }
    
  3. 创建访客购物车并添加商品

    // 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 });
    }
    
  4. 验证客户身份并管理账户

    // 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);
    }
    
  5. 在 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 });
}

最佳实践

  • 使用 Store HTTP 标头指定特定店铺视图——如果不指定,Magento 会默认使用默认店铺;在多店铺配置中,这会导致定价或货币不正确
  • 通过 Varnish 或内置缓存启用 GraphQL 查询缓存,用于公开目录查询——缓存产品列表查询数分钟即可减少 PHP 执行
  • 对多态产品类型使用内联片段——在适用时,始终包含 ... on SimpleProduct、... on ConfigurableProduct、... on BundleProduct
  • 将购物车 ID 保存在 Cookie 中——访客购物车 ID(UUID)在 24 小时无活动后过期;登录后使用 mergeCarts mutation 与客户购物车合并
  • 批量处理相关查询——对于同时需要产品、类别和 CMS 区块数据的页面,Apollo Client 批处理或手动合并查询可减少往返时间
  • 为 errors 数组实现适当的错误处理——即使发生错误,GraphQL 响应也会返回 200;务必检查响应正文中的 errors 属性
  • 安全地缓存客户令牌——将 bearer 令牌存储在 httpOnly Cookie 中,而不是 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