65.9K
CodeProject 正在变化。 阅读更多。
Home

wexCommerce - 极简强大的电子商务平台

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (10投票s)

2022年11月11日

MIT

19分钟阅读

viewsIcon

27410

downloadIcon

591

极简强大的电子商务平台。

目录

  1. 引言
  2. 特点
  3. 实时演示
  4. 必备组件
  5. 快速概览
    1. 前端
    2. 后端
  6. 背景
  7. 安装(自托管)
  8. 安装(Docker)
    1. Docker 镜像
    2. 演示数据库
    3. SSL
  9. 设置 Stripe
  10. 更改语言和货币
  11. 添加新语言
  12. 演示数据库
    1. Windows, Linux, macOS
    2. Docker
  13. 从源码运行
  14. 日志
  15. Using the Code
    1. API
    2. 前端
    3. 后端
  16. 历史

引言

wexCommerce 是一个简约而强大的电子商务平台,使用 React(带 SSR)、MongoDB 和 Stripe 进行支付。

使用此解决方案,您可以在至少1GB内存的 Docker droplet 上托管,以极低的成本构建一个完全可定制、针对 SEO 优化并具有可操作的 Stripe 支付网关的电子商务网站。

wexCommerce 由前端和管理仪表板组成。在前端,客户可以搜索他们想要的产品,将其添加到购物车并结账。客户可以使用 Google、Facebook、Apple 或电子邮件注册,并使用信用卡、货到付款、银行转账、PayPal、Google Pay、Apple Pay、Link 或其他 Stripe 支付方式付款。登录后,他们可以访问他们的购买历史并跟踪他们的订单。在管理仪表板中,管理员可以管理订单、支付、产品、类别、客户和通用设置,例如默认语言、货币、配送、运输和接受的支付方式。

出于其诸多优点,我们做出了一个关键的设计决策,即使用 TypeScript 而非 JavaScript。TypeScript 提供了强类型、工具和集成,从而生成高质量、可扩展、更易读和可维护的代码,且易于调试和测试。

wexCommerce 可以在 Docker 容器中运行。请按照此分步指南,了解如何构建 wexCommerce Docker 镜像并在 Docker 容器中运行它。

在本文中,您将学习 wexCommerce 的制作过程,包括源代码主要部分和软件架构的描述、如何部署它以及如何运行源代码。但在深入探讨之前,我们将从平台的快速概述开始。

特点

  • 库存管理
  • 订单管理
  • 支付管理
  • 客户管理
  • 多种支付选项(信用卡、货到付款、银行转账、PayPal、Google Pay、Apple Pay、Link)
  • 运营中的 Stripe 支付网关
  • 多种配送选项(送货上门、门店自提)
  • 多语言支持(英语、法语)
  • 多种登录选项(Google、Facebook、Apple、电子邮件)
  • 响应式后端和前端

实时演示

必备组件

  • Node.js
  • Express
  • MongoDB
  • Next.js
  • SSR
  • React
  • MUI
  • TypeScript
  • MVC
  • JWT
  • Docker
  • NGINX
  • Git

快速概览

在本节中,您将快速了解后端和前端的主要页面。

前端

在前端,用户可以搜索可用产品,将产品添加到购物车并结账。

以下是前端的登录页面

以下是前端的搜索页面

以下是一个示例产品页面

以下是产品图片的全屏视图

以下是购物车页面

以下是结账页面

以下是登录页面

以下是注册页面

以下是用户可以查看其订单的页面

就是这样!这些是前端的主要页面。

后端

在后端,管理员可以管理类别、产品、用户和订单。

管理员还可以管理以下设置

  • 区域设置:平台语言(英语或法语)和货币
  • 配送设置:启用的配送方式及其成本
  • 支付设置:启用的支付方式(信用卡、货到付款或银行转账)
  • 银行设置:银行转账的银行信息(IBAN 和其他信息)

以下是后端登录页面

以下是后端仪表板页面,管理员可以在其中查看和管理订单

以下是管理员管理类别的页面

以下是管理员可以查看和管理产品的页面

以下是管理员编辑产品的页面

以下是产品图片的全屏视图

以下是后端设置页面

就是这样。这些是后端的主要页面。

背景

wexCommerce 的基本思想非常简单

  • 后端:管理员在其中创建和管理类别和产品。一个仪表板,管理员可以在其中查看新订单并通过电子邮件收到新订单通知。
  • 前端:用户可以在其中搜索他们想要的产品,将其添加到购物车,然后使用多种支付方式和多种配送方式结账。

后端和前端都依赖于 wexCommerce API,这是一个 RESTful API,公开了访问 wexCommerce 数据库的功能。

安装(自托管)

wexCommerce 是跨平台的,可以在 Windows、Linux 和 macOS 上运行和安装。

以下是在 Ubuntu Linux 上的安装说明。

必备组件

  1. 安装 gitNode.jsNGINXIISMongoDBmongosh。如果您想使用 MongoDB Atlas,可以跳过安装和配置 MongoDB。

  2. 配置 MongoDB
    mongosh

    创建管理员用户

    db = db.getSiblingDB('admin')
    db.createUser({ user: "admin" , pwd: "PASSWORD",
    roles: ["userAdminAnyDatabase", "dbAdminAnyDatabase", "readWriteAnyDatabase"]})

    PASSWORD 替换为强密码。

    安全 MongoDB

    sudo nano /etc/mongod.conf

    按如下方式更改配置

    net:
      port: 27017
      bindIp: 0.0.0.0
    
    security:
      authorization: enabled

    重启 MongoDB 服务

    sudo systemctl restart mongod.service
    sudo systemctl status mongod.service

说明

  1. 克隆 wexCommerce 仓库
    cd /opt
    sudo git clone https://github.com/aelassas/wexcommerce.git
  2. 添加权限
    sudo chown -R $USER:$USER /opt/wexcommerce
    sudo chmod -R +x /opt/wexcommerce/__scripts
  3. 创建部署快捷方式
    sudo ln -s /opt/wexcommerce/__scripts/wc-deploy.sh /usr/local/bin/wc-deploy
  4. 创建 wexCommerce 服务
    sudo cp /opt/wexcommerce/__services/wexcommerce.service /etc/systemd/system
    sudo systemctl enable wexcommerce.service
    
    sudo cp /opt/wexcommerce/__services/wexcommerce-backend.service /etc/systemd/system
    sudo systemctl enable wexcommerce-backend.service
    
    sudo cp /opt/wexcommerce/__services/wexcommerce-frontend.service /etc/systemd/system
    sudo systemctl enable wexcommerce-frontend.service
  • 添加 /opt/wexcommerce/api/.env 文件
    NODE_ENV=production
    WC_PORT=4005
    WC_HTTPS=false
    WC_PRIVATE_KEY=/etc/ssl/wexcommerce.key
    WC_CERTIFICATE=/etc/ssl/wexcommerce.crt
    WC_DB_URI=mongodb://127.0.0.1:27017/wexcommerce?authSource=admin&appName=wexcommerce
    WC_DB_SSL=false
    WC_DB_SSL_KEY=/etc/ssl/wexcommerce.key
    WC_DB_SSL_CERT=/etc/ssl/wexcommerce.crt
    WC_DB_SSL_CA=/etc/ssl/wexcommerce.ca.pem
    WC_DB_DEBUG=false
    WC_COOKIE_SECRET=COOKIE_SECRET
    WC_AUTH_COOKIE_DOMAIN=localhost
    WC_JWT_SECRET=JWT_SECRET
    WC_JWT_EXPIRE_AT=86400
    WC_TOKEN_EXPIRE_AT=86400
    WC_SMTP_HOST=in-v3iljet.com
    WC_SMTP_PORT=587
    WC_SMTP_USER=USER
    WC_SMTP_PASS=PASSWORD
    WC_SMTP_FROM=admin@wexcommerce.com
    WC_CDN_USERS=/var/www/cdn/wexcommerce/users
    WC_CDN_TEMP_USERS=/var/www/cdn/wexcommerce/temp/users
    WC_CDN_CATEGORIES=/var/www/cdn/wexcommerce/categories
    WC_CDN_TEMP_CATEGORIES=/var/www/cdn/wexcommerce/temp/categories
    WC_CDN_PRODUCTS=/var/www/cdn/wexcommerce/products
    WC_CDN_TEMP_PRODUCTS=/var/www/cdn/wexcommerce/temp/products
    WC_BACKEND_HOST=https://:8005/
    WC_FRONTEND_HOST=https:///
    WC_DEFAULT_LANGUAGE=en
    WC_DEFAULT_CURRENCY=$
    WC_DEFAULT_STRIPE_CURRENCY=USD
    WC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
    WC_STRIPE_SESSION_EXPIRE_AT=82800
    WC_ADMIN_EMAIL=admin@wexcommerce.com
    WC_RECAPTCHA_SECRET=RECAPTCHA_SECRET

    您必须配置以下选项

    WC_DB_URI=mongodb://127.0.0.1:27017/wexcommerce?authSource=admin&appName=wexcommerce
    WC_SMTP_HOST=in-v3iljet.com
    WC_SMTP_PORT=587
    WC_SMTP_USER=USER
    WC_SMTP_PASS=PASSWORD
    WC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
    WC_BACKEND_HOST=https://:8001/
    WC_FRONTEND_HOST=https:///

    如果您想启用 SSL,您必须配置以下选项

    WC_HTTPS=true
    WC_PRIVATE_KEY=/etc/ssl/wexcommerce.key
    WC_CERTIFICATE=/etc/ssl/wexcommerce.crt

    如果您想使用 MongoDB Atlas,请将您的 MongoDB Atlas URI 放入 WC_DB_URI,否则将 WC_DB_URI 中的 PASSWORD 替换为您的 MongoDB 密码。将 JWT_SECRET 替换为一个秘密令牌。最后,设置 SMTP 选项。SMTP 选项对于注册是必需的。您可以使用 sendgrid 或任何其他事务性电子邮件提供商。

    如果您选择 sendgrid,请在 sendgrid.com 上创建一个帐户,登录并转到仪表板。在左侧面板上,点击电子邮件 API,然后点击集成指南。然后,选择SMTP 中继并按照步骤操作。系统将提示您创建一个 API 密钥。创建 API 密钥并验证 smtp 中继后,将 API 密钥复制到 ./api/.env 中的 WC_SMTP_PASS 中。Sendgrid 的免费计划允许每天发送最多 100 封电子邮件。如果您需要每天发送超过 100 封电子邮件,请切换到付费计划或选择其他事务性电子邮件提供商。

    COOKIE_SECRETJWT_SECRET 至少应为 32 个字符长,但越长越好。您可以使用在线密码生成器并将密码长度设置为 32 个或更长。

    以下设置非常重要,如果设置不正确,身份验证将无法工作

    WC_AUTH_COOKIE_DOMAIN=localhost
    WC_BACKEND_HOST=https://:8001/
    WC_FRONTEND_HOST=https:///

    要启用 Stripe 支付网关,请注册一个 Stripe 账户,填写表格并从 Stripe 仪表板保存可发布密钥和秘密密钥。然后,在 api/.env 中的以下选项中设置秘密密钥

    WC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
    

    不要在网站上公开 Stripe 密钥或将其嵌入移动应用程序中。它必须保密并安全地存储在服务器端。

    在 Stripe 中,所有账户默认共有四个 API 密钥 - 两个用于测试模式,两个用于实时模式

    测试模式密钥:默认情况下,您可以使用此密钥在测试模式下对服务器上的请求进行身份验证。默认情况下,您可以使用此密钥不受限制地执行任何 API 请求。

    测试模式发布密钥:在 Web 或移动应用程序的客户端代码中,可以使用此密钥进行测试。

    实时模式密钥:在实时模式下,您可以使用此密钥在服务器上对请求进行身份验证。默认情况下,您可以使用此密钥不受限制地执行任何 API 请求。

    实时模式发布密钥:当您准备好启动应用程序时,可以使用此密钥在 Web 或移动应用程序的客户端代码中。

    仅使用您的测试 API 密钥进行测试。这样可以确保您不会意外修改您的实时客户或交易。

    如果您要启用 HTTPS,则必须设置以下选项

    WC_HTTPS=true
    WC_PRIVATE_KEY=/etc/ssl/wexcommerce.com.key
    WC_CERTIFICATE=/etc/ssl/wexcommerce.com.crt
    

    localhost 替换为 IP 或 FQDN。也就是说,如果您从 https://<FQDN>:8001/ 访问后端。WC_BACKEND_HOST 应该是 https://<FQDN>:3001/。WC_FRONTEND_HOST 也是如此。WC_AUTH_COOKIE_DOMAIN 应该是 FQDN。

  1. 添加 /opt/wexcommerce/backend/.env 文件并设置以下选项
    NEXT_PUBLIC_WC_SERVER_API_HOST=https://:4005
    NEXT_PUBLIC_WC_CLIENT_API_HOST=https://:4005
    NEXT_PUBLIC_WC_CDN_USERS=https:///cdn/wexcommerce/users
    NEXT_PUBLIC_WC_CDN_TEMP_USERS=https:///cdn/wexcommerce/temp/users
    NEXT_PUBLIC_WC_CDN_CATEGORIES=https:///cdn/wexcommerce/categories
    NEXT_PUBLIC_WC_CDN_TEMP_CATEGORIES=https:///cdn/wexcommerce/temp/categories
    NEXT_PUBLIC_WC_CDN_PRODUCTS=https:///cdn/wexcommerce/products
    NEXT_PUBLIC_WC_CDN_TEMP_PRODUCTS=https:///cdn/wexcommerce/temp/products

    localhost 替换为 IP、主机名或 FQDN。

  2. 添加 /opt/wexcommerce/frontend/.env 文件并设置以下选项
    NEXT_PUBLIC_WC_SERVER_API_HOST=https://:4005
    NEXT_PUBLIC_WC_CLIENT_API_HOST=https://:4005
    NEXT_PUBLIC_WC_PAGE_SIZE=30
    NEXT_PUBLIC_WC_CDN_USERS=https:///cdn/wexcommerce/users
    NEXT_PUBLIC_WC_CDN_CATEGORIES=https:///cdn/wexcommerce/categories
    NEXT_PUBLIC_WC_CDN_PRODUCTS=https:///cdn/wexcommerce/products
    NEXT_PUBLIC_WC_FB_APP_ID=XXXXXXXXXX
    NEXT_PUBLIC_WC_APPLE_ID=XXXXXXXXXX
    NEXT_PUBLIC_WC_GG_APP_ID=XXXXXXXXXX
    NEXT_PUBLIC_WC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY
    NEXT_PUBLIC_WC_GOOGLE_ANALYTICS_ENABLED=false
    NEXT_PUBLIC_WC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX

    localhost 替换为 IP、主机名或 FQDN。

  3. 将您的域名添加到 backend/next.config.mjs

    /** @type {import('next').NextConfig} */
    const nextConfig = {
      reactStrictMode: false,
      poweredByHeader: false,
      images: {
        //
        // Add your backend domain here
        //
        remotePatterns: [
          {
            protocol: 'http',
            hostname: 'localhost',
            pathname: '**',
          },
          {
            protocol: 'https',
            hostname: 'wexcommerce.com',
            pathname: '**',
          },
        ],
        unoptimized: true,
      },
      //
      // Nginx will do gzip compression. We disable
      // compression here so we can prevent buffering
      // streaming responses
      //
      compress: false,
      //
      // Add your backend domain here
      //
      experimental: {
        serverActions: {
          allowedOrigins: ['localhost:8001', 'wexcommerce.com:8001'],
        },
      },
    }
    
    export default nextConfig

    wexcommerce.com 替换为您的域名。

  4. 将您的域名添加到 frontend/next.config.mjs

    /** @type {import('next').NextConfig} */
    const nextConfig = {
      reactStrictMode: false,
      poweredByHeader: false,
      images: {
        //
        // Add your frontend domain here
        //
        remotePatterns: [
          {
            protocol: 'http',
            hostname: 'localhost',
            pathname: '**',
          },
          {
            protocol: 'https',
            hostname: 'wexcommerce.com',
            pathname: '**',
          },
        ],
        unoptimized: true,
      },
      //
      // Nginx will do gzip compression. We disable
      // compression here so we can prevent buffering
      // streaming responses
      //
      compress: false,
      //
      // Add your frontend domain here
      //
      experimental: {
        serverActions: {
          allowedOrigins: ['localhost', 'wexcommerce.com'],
        },
      },
    }
    
    export default nextConfig

    wexcommerce.com 替换为您的域名。

  5. 配置 NGINX
    sudo nano /etc/nginx/sites-available/default

    按如下方式更改配置 (NGINX 反向代理)

    #
    # redirect http to https
    #
    server
    {
        listen 80 default_server;
        server_name _;
        return 301 https://$host$request_uri;
    }
    
    #
    # frontend
    #
    server
    {
        listen 443 ssl;
        server_name _;
    
        ssl_certificate_key /etc/letsencrypt/live/wexdev.dynv6.net/privkey.pem;
        ssl_certificate /etc/letsencrypt/live/wexdev.dynv6.net/fullchain.pem;
    
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    
        error_page 497 301 =307 https://$host:$server_port$request_uri;
    
        access_log /var/log/nginx/wexcommerce.frontend.access.log;
        error_log /var/log/nginx/wexcommerce.frontend.error.log;
    
        location /cdn
        {
            alias /var/www/cdn;
        }
    
        location /
        {
            proxy_pass http://127.0.0.1:8006;
    
            proxy_http_version 1.1;
            proxy_read_timeout 900;
    
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Real-IP $remote_addr;
    
            proxy_cache_bypass $http_upgrade;
        }
    }
    
    #
    # backend
    #
    server
    {
        listen 8001 ssl;
        server_name _;
    
        error_page 497 301 =307 https://$host:$server_port$request_uri;
    
        ssl_certificate_key /etc/letsencrypt/live/wexdev.dynv6.net/privkey.pem;
        ssl_certificate /etc/letsencrypt/live/wexdev.dynv6.net/fullchain.pem;
    
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    
        access_log /var/log/nginx/wexcommerce.backend.access.log;
        error_log /var/log/nginx/wexcommerce.backend.error.log;
    
        location /
        {
            proxy_pass http://127.0.0.1:8005;
    
            proxy_http_version 1.1;
            proxy_read_timeout 900;
    
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Real-IP $remote_addr;
    
            proxy_cache_bypass $http_upgrade;
        }
    }
    

    然后,检查 nginx 配置并启动 nginx 服务

    sudo nginx -t
    sudo systemctl restart nginx.service
    sudo systemctl status nginx.service
    
  6. 启用防火墙并打开 wexCommerce 端口
    sudo ufw enable
    sudo ufw allow 4005/tcp
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw allow 8001/tcp
  7. 部署 wexCommerce
    wc-deploy all

    wexCommerce 后端可在端口 3000 访问,如果启用 SSL,前端可在端口 80 或 443 访问。

  8. 您可以从后端设置页面更改语言和货币。

安装(Docker)

wexCommerce 可以在 Linux 上的 Docker 容器中运行,也可以在 Windows 或 Mac 的 Docker Desktop 中运行。

Docker 镜像

本节描述了如何构建 wexCommerce Docker 镜像并在 Docker 容器中运行它。

  1. 确保主机上没有其他应用程序使用端口 80、443、8001、4005 和 27017。
  2. 克隆 wexCommerce 仓库
git clone https://github.com/aelassas/wexcommerce.git
  1. 在 ./docker-compose.yml 中设置您的 MongoDB 密码
version: "3.8"
services:
  api:
    build: 
      context: .
      dockerfile: ./api/Dockerfile
    restart: always
    ports:
      - 4005:4005
    depends_on:
      - mongo
    volumes:
      - cdn:/var/www/cdn/wexcommerce

  mongo:
    image: mongo:latest
    command: mongod --quiet --logpath /dev/null
    restart: always
    environment:
      # Provide your credentials here
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: PASSWORD
    ports:
      - 27017:27017

  backend:
    build: 
      context: .
      dockerfile: ./backend/Dockerfile
    depends_on:
      - api
    ports:
      - 8005:8005
    restart: always

  nginx-backend:
    build: 
      context: .
      dockerfile: ./backend/nginx/Dockerfile
    depends_on:
      - backend
    ports:
      - 8001:8001
    restart: always

  frontend:
    build: 
      context: .
      dockerfile: ./frontend/Dockerfile
    depends_on:
      - api
    ports:
      - 8006:8006
    volumes:
      - cdn:/var/www/cdn/wexcommerce
    restart: always

  nginx-frontend:
    build: 
      context: .
      dockerfile: ./frontend/nginx/Dockerfile
    depends_on:
      - frontend
    ports:
      - 80:80
      - 443:443
    volumes:
      - cdn:/var/www/cdn/wexcommerce
    restart: always

volumes:
  cdn:
  1. 创建 ./api/.env.docker
NODE_ENV=development
WC_PORT=4005
WC_HTTPS=false
WC_PRIVATE_KEY=/etc/ssl/wexcommerce.key
WC_CERTIFICATE=/etc/ssl/wexcommerce.crt
WC_DB_URI=mongodb://admin:PASSWORD@mongo:27017/wexcommerce?authSource=admin&appName=wexcommerce
WC_DB_SSL=false
WC_DB_SSL_KEY=/etc/ssl/wexcommerce.key
WC_DB_SSL_CERT=/etc/ssl/wexcommerce.crt
WC_DB_SSL_CA=/etc/ssl/wexcommerce.ca.pem
WC_DB_DEBUG=false
WC_COOKIE_SECRET=COOKIE_SECRET
WC_AUTH_COOKIE_DOMAIN=localhost
WC_JWT_SECRET=JWT_SECRET
WC_JWT_EXPIRE_AT=86400
WC_TOKEN_EXPIRE_AT=86400
WC_SMTP_HOST=in-v3iljet.com
WC_SMTP_PORT=587
WC_SMTP_USER=USER
WC_SMTP_PASS=PASSWORD
WC_SMTP_FROM=admin@wexcommerce.com
WC_CDN_USERS=/var/www/cdn/wexcommerce/users
WC_CDN_TEMP_USERS=/var/www/cdn/wexcommerce/temp/users
WC_CDN_CATEGORIES=/var/www/cdn/wexcommerce/categories
WC_CDN_TEMP_CATEGORIES=/var/www/cdn/wexcommerce/temp/categories
WC_CDN_PRODUCTS=/var/www/cdn/wexcommerce/products
WC_CDN_TEMP_PRODUCTS=/var/www/cdn/wexcommerce/temp/products
WC_BACKEND_HOST=https://:8001/
WC_FRONTEND_HOST=https:///
WC_DEFAULT_LANGUAGE=en
WC_DEFAULT_CURRENCY=$
WC_DEFAULT_STRIPE_CURRENCY=USD
WC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
WC_STRIPE_SESSION_EXPIRE_AT=82800
WC_ADMIN_EMAIL=admin@wexcommerce.com
WC_RECAPTCHA_SECRET=RECAPTCHA_SECRET

设置以下设置

WC_DB_URI=mongodb://admin:PASSWORD@mongo:27017/wexcommerce?authSource=admin&appName=wexcommerce
WC_SMTP_HOST=in-v3iljet.com
WC_SMTP_PORT=587
WC_SMTP_USER=USER
WC_SMTP_PASS=PASSWORD
WC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
  1. 创建 ./backend/.env.docker
NEXT_PUBLIC_WC_SERVER_API_HOST=http://api:4005
NEXT_PUBLIC_WC_CLIENT_API_HOST=https://:4005
NEXT_PUBLIC_WC_PAGE_SIZE=30
NEXT_PUBLIC_WC_CDN_USERS=https:///cdn/wexcommerce/users
NEXT_PUBLIC_WC_CDN_CATEGORIES=https:///cdn/wexcommerce/categories
NEXT_PUBLIC_WC_CDN_TEMP_CATEGORIES=https:///cdn/wexcommerce/temp/categories
NEXT_PUBLIC_WC_CDN_PRODUCTS=https:///cdn/wexcommerce/products
NEXT_PUBLIC_WC_CDN_TEMP_PRODUCTS=https:///cdn/wexcommerce/temp/products
  1. 创建 ./frontend/.env.docker
NEXT_PUBLIC_WC_SERVER_API_HOST=http://api:4005
NEXT_PUBLIC_WC_CLIENT_API_HOST=https://:4005
NEXT_PUBLIC_WC_PAGE_SIZE=30
NEXT_PUBLIC_WC_CDN_USERS=https:///cdn/wexcommerce/users
NEXT_PUBLIC_WC_CDN_CATEGORIES=https:///cdn/wexcommerce/categories
NEXT_PUBLIC_WC_CDN_PRODUCTS=https:///cdn/wexcommerce/products
NEXT_PUBLIC_WC_FB_APP_ID=XXXXXXXXXX
NEXT_PUBLIC_WC_APPLE_ID=XXXXXXXXXX
NEXT_PUBLIC_WC_GG_APP_ID=XXXXXXXXXX
NEXT_PUBLIC_WC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY
NEXT_PUBLIC_WC_GOOGLE_ANALYTICS_ENABLED=false
NEXT_PUBLIC_WC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX
  1. 运行 compose
docker compose up

如果您想重建并运行镜像,请运行以下命令

docker compose up --build --force-recreate --no-deps api backend frontend

如果您想在没有缓存的情况下重建并运行镜像,请运行以下命令

docker compose build --no-cache api backend frontend
docker compose up --force-recreate --no-deps api backend frontend

演示数据库

要恢复演示数据库,请按照这些说明操作。

SSL

本节将引导您了解如何为 API、后端和前端启用 SSL。

将您的私钥 wexcommerce.key 和证书 wexcommerce.crt 复制到 ./ 中。

wexcommerce.key 将作为 /etc/ssl/wexcommerce.key 加载,wexcommerce.crt 将作为 /etc/ssl/wexcommerce.crt 加载到 ./docker-compose.yml 中。

API

对于 API,按如下方式更新 ./api/.env.docker 以启用 SSL

WC_HTTPS=true
WC_PRIVATE_KEY=/etc/ssl/wexcommerce.key
WC_CERTIFICATE=/etc/ssl/wexcommerce.crt
WC_BACKEND_HOST=https://domain.com:8001/
WC_FRONTEND_HOST=https://domain.com/

后端

对于后端,更新 ./backend/.env.docker 中的以下选项

NEXT_PUBLIC_WC_CLIENT_API_HOST=https://domain.com:4005
NEXT_PUBLIC_WC_PAGE_SIZE=30
NEXT_PUBLIC_WC_CDN_USERS=https://domain.com/cdn/wexcommerce/users
NEXT_PUBLIC_WC_CDN_CATEGORIES=https://domain.com/cdn/wexcommerce/categories
NEXT_PUBLIC_WC_CDN_TEMP_CATEGORIES=https://domain.com/cdn/wexcommerce/temp/categories
NEXT_PUBLIC_WC_CDN_PRODUCTS=https://domain.com/cdn/wexcommerce/products
NEXT_PUBLIC_WC_CDN_TEMP_PRODUCTS=https://domain.com/cdn/wexcommerce/temp/products

然后,按如下方式更新 ./backend/nginx/nginx.conf 以启用 SSL

server
{
    listen 8001 ssl;

    ssl_certificate_key /etc/ssl/wexcommerce.key;
    ssl_certificate /etc/ssl/wexcommerce.crt;

    error_page 497 301 =307 https://$host:$server_port$request_uri;

	location /
	{
		proxy_pass http://backend:8005;

		proxy_http_version 1.1;
		proxy_read_timeout 900;

		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection 'upgrade';
		proxy_set_header Host $host;
		proxy_set_header X-Forwarded-Host $host;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Real-IP $remote_addr;

		proxy_cache_bypass $http_upgrade;
	}
}

前端

对于前端,更新 ./frontend/.env.docker 中的以下选项

NEXT_PUBLIC_WC_CLIENT_API_HOST=https://domain.com:4005
NEXT_PUBLIC_WC_PAGE_SIZE=30
NEXT_PUBLIC_WC_CDN_USERS=https://domain.com/cdn/wexcommerce/users
NEXT_PUBLIC_WC_CDN_CATEGORIES=https://domain.com/cdn/wexcommerce/categories
NEXT_PUBLIC_WC_CDN_PRODUCTS=https://domain.com/cdn/wexcommerce/products
NEXT_PUBLIC_WC_FB_APP_ID=XXXXXXXXXX
NEXT_PUBLIC_WC_APPLE_ID=XXXXXXXXXX
NEXT_PUBLIC_WC_GG_APP_ID=XXXXXXXXXX
NEXT_PUBLIC_WC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY
NEXT_PUBLIC_WC_GOOGLE_ANALYTICS_ENABLED=false
NEXT_PUBLIC_WC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX

然后,按如下方式更新 ./frontend/nginx.conf 以启用 SSL

server {
    listen 80;
    return 301 https://$host$request_uri;
}
server
{
    listen 443 ssl;

    ssl_certificate_key /etc/ssl/wexcommerce.key;
    ssl_certificate /etc/ssl/wexcommerce.crt;

	location /
	{
		proxy_pass http://frontend:8006;

		proxy_http_version 1.1;
		proxy_read_timeout 900;

		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection 'upgrade';
		proxy_set_header Host $host;
		proxy_set_header X-Forwarded-Host $host;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Real-IP $remote_addr;

		proxy_cache_bypass $http_upgrade;
	}

	location /cdn
	{
		alias /var/www/cdn;
	}
}

docker-compose.yml

更新 ./docker-compose.yml 以加载您的私钥 wexcommerce.key 和证书 wexcommerce.crt,并按如下方式将端口 443 添加到前端

version: "3.8"
services:
  api:
    build: 
      context: .
      dockerfile: ./api/Dockerfile
    restart: always
    ports:
      - 4005:4005
    depends_on:
      - mongo
    volumes:
      - cdn:/var/www/cdn/wexcommerce
      - ./wexcommerce.key:/etc/ssl/wexcommerce.key
      - ./wexcommerce.crt:/etc/ssl/wexcommerce.crt

  mongo:
    image: mongo:latest
    command: mongod --quiet --logpath /dev/null
    restart: always
    environment:
      # Provide your credentials here
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: PASSWORD
    ports:
      - 27017:27017

  backend:
    build: 
      context: .
      dockerfile: ./backend/Dockerfile
    depends_on:
      - api
    ports:
      - 8005:8005
    restart: always

  nginx-backend:
    build: 
      context: .
      dockerfile: ./backend/nginx/Dockerfile
    depends_on:
      - backend
    ports:
      - 8001:8001
    restart: always
    volumes:
      - ./wexcommerce.key:/etc/ssl/wexcommerce.key
      - ./wexcommerce.crt:/etc/ssl/wexcommerce.crt

  frontend:
    build: 
      context: .
      dockerfile: ./frontend/Dockerfile
    depends_on:
      - api
    ports:
      - 8006:8006
    volumes:
      - cdn:/var/www/cdn/wexcommerce
    restart: always

  nginx-frontend:
    build: 
      context: .
      dockerfile: ./frontend/nginx/Dockerfile
    depends_on:
      - frontend
    ports:
      - 80:80
      - 443:443
    volumes:
      - cdn:/var/www/cdn/wexcommerce
      - ./wexcommerce.key:/etc/ssl/wexcommerce.key
      - ./wexcommerce.crt:/etc/ssl/wexcommerce.crt
    restart: always

volumes:
  cdn:

重建并运行 Docker 镜像

docker compose up --build --force-recreate --no-deps api nginx-backend nginx-frontend

设置 Stripe

如果您想启用 Stripe 支付网关,请注册一个 Stripe 账户,填写表格并从 Stripe 开发者仪表板保存可发布密钥和秘密密钥。

不要在网站上公开秘密密钥或将其嵌入移动应用程序中。它必须是秘密的,并安全地存储在服务器端。

在 Stripe 中,所有账户默认共有四个 API 密钥——两个用于测试模式,两个用于实时模式

  • 测试模式密钥:默认情况下,您可以使用此密钥在测试模式下对服务器上的请求进行身份验证。默认情况下,您可以使用此密钥不受限制地执行任何 API 请求。
  • 测试模式发布密钥:在 Web 或移动应用程序的客户端代码中,可以使用此密钥进行测试。
  • 实时模式密钥:在实时模式下,您可以使用此密钥在服务器上对请求进行身份验证。默认情况下,您可以使用此密钥不受限制地执行任何 API 请求。
  • 实时模式发布密钥:当您准备好启动应用程序时,可以使用此密钥在 Web 或移动应用程序的客户端代码中。

您可以在 Stripe 开发者仪表板的 API 密钥页面找到您的密钥和发布密钥。

仅使用您的测试 API 密钥进行测试和开发。这样可以确保您不会意外修改您的实时客户或交易。

在生产环境中,API、后端和前端都使用 HTTPS,以便能够使用 Stripe 支付网关。

API

api/.env 的以下选项中设置 Stripe 秘密密钥

WC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY

前端

frontend/.env 的以下选项中设置 Stripe 可发布密钥和货币

NEXT_PUBLIC_WC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY

更改语言和货币

要更改语言和货币,请从管理员仪表板的设置页面,在“区域设置”部分更改语言(英语或法语)、货币和 Stripe 货币。

对于 Stripe 货币,它应该是以下支持的货币之一。

添加新语言

要添加新语言,请按以下步骤操作

API

  1. 将新的语言 ISO 639-1 代码 添加到 api/src/config/env.config.ts 中的 LANGUAGES 设置。
  2. src/lang 文件夹中创建一个新文件 <ISO 639-1 code>.ts 并在此文件中添加翻译。
  3. 将您的翻译添加到 src/lang/i18n.ts

后端

  1. 将新的语言 ISO 639-1 代码 及其标签添加到 src/config/env.config.tsLANGUAGES 常量中。
  2. 将翻译添加到 src/lang/*.ts

前端

将翻译添加到 src/lang/*.ts

演示数据库

Windows, Linux, macOS

mongorestore --verbose --drop --gzip --host=127.0.0.1 --port=27017 --username=admin --password=$PASSWORD --authenticationDatabase=admin --nsInclude="wexcommerce.*" --archive=wexcommerce.gz

不要忘记设置 $PASSWORD

如果您正在使用 MongoDB Atlas,请将您的 MongoDB Atlas URI 放入 --uri= 命令行参数中

mongorestore --verbose --drop --gzip --uri="mongodb://admin:$PASSWORD@127.0.0.1:27017/wexcommerce?authSource=admin&appName=wexcommerce" --nsInclude="wexcommerce.*" --nsFrom="wexcommerce.*" --nsTo="wexcommerce.*" --archive=wexcommerce.gz

将 cdn.zip 解压到您的 Web 服务器上,以便可以通过 https:///cdn/wexcommerce/ 访问文件

cdn/wexcommerce/ 包含以下文件夹

  • cdn/wexcommerce/users:此文件夹包含用户图片。
  • cdn/wexcommerce/categories:此文件夹包含类别图片。
  • cdn/wexcommerce/products:此文件夹包含产品图片。
  • cdn/wexcommerce/temp:此文件夹包含临时文件。

管理员用户admin@wexcommerce.com
密码:sh0ppingC4rt

前端用户jdoe@wexcommerce.com
密码:sh0ppingC4rt

Docker

要在 Docker 容器中恢复 wexCommerce 演示数据库,请按以下步骤操作

  1. 确保端口 80、8001、4005 和 27017 未被任何应用程序使用。

  2. 在主机上下载并安装 MongoDB 命令行数据库工具

  3. 将 MongoDB 命令行数据库工具文件夹添加到主机上的 Path 环境变量中。

  4. wexcommerce-db.zip 下载到您的主机并解压。

  5. 运行 compose

    docker compose up
  6. 进入 wexcommerce-db 文件夹并使用以下命令恢复演示数据库

    mongorestore --verbose --drop --gzip --host=127.0.0.1 --port=27017 --username=admin --password=$PASSWORD --authenticationDatabase=admin --nsInclude="wexcommerce.*" --archive=wexcommerce.gz
    

    将 $PASSWORD 替换为在 docker-compose.yml 中设置的 MongoDB 密码。

  7. 使用以下命令获取 API Docker 容器名称

    docker container ls
    名称应类似于:src-api-1
  8. 进入 wexcommerce-db/cdn 文件夹,并使用以下命令将该文件夹的内容复制到 API 容器中

docker cp ./cdn/categories src-api-1:/var/www/cdn/wexcommerce
docker cp ./cdn/products src-api-1:/var/www/cdn/wexcommerce

将 src-api-1 替换为您的 API 容器名称。

  1. 访问后端 https://:8001 并使用以下凭据登录
    管理员用户admin@wexcommerce.com
    密码:sh0ppingC4rt

  2. 访问前端 https:// 并使用以下凭据登录
    前端用户jdoe@wexcommerce.com
    密码:sh0ppingC4rt

从源码运行

以下是从代码运行 wexCommerce 的说明。

必备组件

安装 gitNode.jsNGINXIISMongoDBmongosh。如果您想使用 MongoDB Atlas,可以跳过安装和配置 MongoDB。

配置 MongoDB

mongosh

创建管理员用户

db = db.getSiblingDB('admin')
db.createUser({ user: "admin" , pwd: "PASSWORD",
roles: ["userAdminAnyDatabase", "dbAdminAnyDatabase", "readWriteAnyDatabase"]})

PASSWORD 替换为强密码。

通过更改 mongod.conf 以如下方式安全 MongoDB

net:
  port: 27017
  bindIp: 0.0.0.0

security:
  authorization: enabled

重启 MongoDB 服务。

说明

  1. 克隆 wexCommerce 仓库
    sudo git clone https://github.com/aelassas/wexcommerce.git
  2. 添加 api/.env 文件
    NODE_ENV=development
    WC_PORT=4005
    WC_HTTPS=false
    WC_PRIVATE_KEY=/etc/ssl/wexcommerce.key
    WC_CERTIFICATE=/etc/ssl/wexcommerce.crt
    WC_DB_URI=mongodb://127.0.0.1:27017/wexcommerce?authSource=admin&appName=wexcommerce
    WC_DB_SSL=false
    WC_DB_SSL_KEY=/etc/ssl/wexcommerce.key
    WC_DB_SSL_CERT=/etc/ssl/wexcommerce.crt
    WC_DB_SSL_CA=/etc/ssl/wexcommerce.ca.pem
    WC_DB_DEBUG=false
    WC_COOKIE_SECRET=COOKIE_SECRET
    WC_AUTH_COOKIE_DOMAIN=localhost
    WC_JWT_SECRET=JWT_SECRET
    WC_JWT_EXPIRE_AT=86400
    WC_TOKEN_EXPIRE_AT=86400
    WC_SMTP_HOST=in-v3iljet.com
    WC_SMTP_PORT=587
    WC_SMTP_USER=USER
    WC_SMTP_PASS=PASSWORD
    WC_SMTP_FROM=admin@wexcommerce.com
    WC_CDN_USERS=/var/www/cdn/wexcommerce/users
    WC_CDN_TEMP_USERS=/var/www/cdn/wexcommerce/temp/users
    WC_CDN_CATEGORIES=/var/www/cdn/wexcommerce/categories
    WC_CDN_TEMP_CATEGORIES=/var/www/cdn/wexcommerce/temp/categories
    WC_CDN_PRODUCTS=/var/www/cdn/wexcommerce/products
    WC_CDN_TEMP_PRODUCTS=/var/www/cdn/wexcommerce/temp/products
    WC_BACKEND_HOST=https://:8005/
    WC_FRONTEND_HOST=https://:8006/
    WC_DEFAULT_LANGUAGE=en
    WC_DEFAULT_CURRENCY=$
    WC_DEFAULT_STRIPE_CURRENCY=USD
    WC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
    WC_STRIPE_SESSION_EXPIRE_AT=82800
    WC_ADMIN_EMAIL=admin@wexcommerce.com
    WC_RECAPTCHA_SECRET=RECAPTCHA_SECRET

    您必须配置以下选项

    WC_DB_URI=mongodb://127.0.0.1:27017/wexcommerce?authSource=admin&appName=wexcommerce
    WC_SMTP_HOST=in-v3iljet.com
    WC_SMTP_PORT=587
    WC_SMTP_USER=USER
    WC_SMTP_PASS=PASSWORD
    WC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
    
    WC_CDN_USERS=/var/www/cdn/wexcommerce/users
    WC_CDN_TEMP_USERS=/var/www/cdn/wexcommerce/temp/users
    WC_CDN_CATEGORIES=/var/www/cdn/wexcommerce/categories
    WC_CDN_TEMP_CATEGORIES=/var/www/cdn/wexcommerce/temp/categories
    WC_CDN_PRODUCTS=/var/www/cdn/wexcommerce/products
    WC_CDN_TEMP_PRODUCTS=/var/www/cdn/wexcommerce/temp/products

    在 Windows 上,安装 IIS 并使用以下值更新以下设置

    WC_CDN_USERS=C:\inetpub\wwwroot\cdn\wexcommerce\users
    WC_CDN_TEMP_USERS=C:\inetpub\wwwroot\cdn\wexcommerce\temp\users
    WC_CDN_CATEGORIES=C:\inetpub\wwwroot\cdn\wexcommerce\categories
    WC_CDN_TEMP_CATEGORIES=C:\inetpub\wwwroot\cdn\wexcommerce\temp\categories
    WC_CDN_PRODUCTS=C:\inetpub\wwwroot\cdn\wexcommerce\products
    WC_CDN_TEMP_PRODUCTS=C:\inetpub\wwwroot\cdn\wexcommerce\temp\products
    

    授予运行 wexCommerce API 的用户对 C:\inetpub\wwwroot\cdn\wexcommerce 的完全访问权限。

    如果您想使用 MongoDB Atlas,请将您的 MongoDB Atlas URI 放入 WC_DB_URI,否则将 WC_DB_URI 中的 PASSWORD 替换为您的 MongoDB 密码。将 JWT_SECRET 替换为一个秘密令牌。最后,设置 SMTP 选项。SMTP 选项对于注册是必需的。您可以使用 sendgrid 或任何其他事务性电子邮件提供商。

    如果您选择 sendgrid,请在 sendgrid.com 上创建一个帐户,登录并转到仪表板。在左侧面板上,点击电子邮件 API,然后点击集成指南。然后,选择SMTP 中继并按照步骤操作。系统将提示您创建一个 API 密钥。创建 API 密钥并验证 smtp 中继后,将 API 密钥复制到 ./api/.env 中的 WC_SMTP_PASS 中。Sendgrid 的免费计划允许每天发送最多 100 封电子邮件。如果您需要每天发送超过 100 封电子邮件,请切换到付费计划或选择其他事务性电子邮件提供商。

    COOKIE_SECRETJWT_SECRET 至少应为 32 个字符长,但越长越好。您可以使用在线密码生成器并将密码长度设置为 32 个或更长。

    要启用 Stripe 支付网关,请注册一个 Stripe 账户,填写表格并从 Stripe 仪表板保存可发布密钥和秘密密钥。然后,在 api/.env 中的以下选项中设置秘密密钥

    WC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
    

    不要在网站上公开 Stripe 密钥或将其嵌入移动应用程序中。它必须保密并安全地存储在服务器端。使用 Stripe 的测试模式。

    仅使用您的测试 API 密钥进行测试。这样可以确保您不会意外修改您的实时客户或交易。

    运行 api

    cd ./api
    npm install
    npm run dev
    
  3. 添加 backend/.env 文件并设置以下选项
    NEXT_PUBLIC_WC_SERVER_API_HOST=https://:4005
    NEXT_PUBLIC_WC_CLIENT_API_HOST=https://:4005
    NEXT_PUBLIC_WC_PAGE_SIZE=30
    NEXT_PUBLIC_WC_CDN_USERS=https:///cdn/wexcommerce/users
    NEXT_PUBLIC_WC_CDN_TEMP_USERS=https:///cdn/wexcommerce/temp/users
    NEXT_PUBLIC_WC_CDN_CATEGORIES=https:///cdn/wexcommerce/categories
    NEXT_PUBLIC_WC_CDN_TEMP_CATEGORIES=https:///cdn/wexcommerce/temp/categories
    NEXT_PUBLIC_WC_CDN_PRODUCTS=https:///cdn/wexcommerce/products
    NEXT_PUBLIC_WC_CDN_TEMP_PRODUCTS=https:///cdn/wexcommerce/temp/products

    运行后端

    cd ./backend
    npm install
    npm run dev
  4. 添加 frontend/.env 文件
    NEXT_PUBLIC_WC_SERVER_API_HOST=https://:4005
    NEXT_PUBLIC_WC_CLIENT_API_HOST=https://:4005
    NEXT_PUBLIC_WC_PAGE_SIZE=30
    NEXT_PUBLIC_WC_CDN_USERS=https:///cdn/wexcommerce/users
    NEXT_PUBLIC_WC_CDN_CATEGORIES=https:///cdn/wexcommerce/categories
    NEXT_PUBLIC_WC_CDN_PRODUCTS=https:///cdn/wexcommerce/products
    NEXT_PUBLIC_WC_FB_APP_ID=XXXXXXXXXX
    NEXT_PUBLIC_WC_APPLE_ID=XXXXXXXXXX
    NEXT_PUBLIC_WC_GG_APP_ID=XXXXXXXXXX
    NEXT_PUBLIC_WC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY
    NEXT_PUBLIC_WC_GOOGLE_ANALYTICS_ENABLED=false
    NEXT_PUBLIC_WC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX
    

    您必须配置以下选项

    NEXT_PUBLIC_WC_CDN_USERS=https:///cdn/wexcommerce/users
    NEXT_PUBLIC_WC_CDN_CATEGORIES=https:///cdn/wexcommerce/categories
    NEXT_PUBLIC_WC_CDN_PRODUCTS=https:///cdn/wexcommerce/products
    NEXT_PUBLIC_WC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY
    

    要使用社交登录,请设置这些选项

    NEXT_PUBLIC_WC_FB_APP_ID=XXXXXXXXXX
    NEXT_PUBLIC_WC_APPLE_ID=XXXXXXXXXX
    NEXT_PUBLIC_WC_GG_APP_ID=XXXXXXXXXX
    

    要启用 Stripe 支付网关,请在 NEXT_PUBLIC_WC_STRIPE_PUBLISHABLE_KEY 中设置 Stripe 可发布密钥。您可以从 Stripe 仪表板中检索它。

    运行前端

    cd ./frontend
    npm install
    npm run dev
  5. 配置 https:///cdn
    • 在 Windows 上,安装 IIS 并创建 C:\inetpub\wwwroot\cdn 文件夹。
    • 在 Linux 上,安装 NGINX 并通过按如下方式更改 /etc/nginx/sites-available/default 来添加 cdn 文件夹
    server {
        listen 80 default_server;
        server_name _;
    
        ...
    
        location /cdn {
          alias /var/www/cdn;
        }
    }
  6. https://:8005/sign-up 创建一个管理员用户

您可以从后端设置页面更改语言和货币。

日志

所有 API 日志都写入 ./api/logs/all.log

API 错误日志也写入 ./api/logs/error.log

Using the Code

本节描述了 wexCommerce 的软件架构,包括 API、前端和后端。

wexCommerce API 是一个 Node.js 服务器应用程序,它使用 Express 公开一个 RESTful API,提供对 wexCommerce MongoDB 数据库的访问。

wexCommerce 前端是一个 Next.js Web 应用程序,它是搜索和购买产品的主要 Web 界面。

wexCommerce 后端是一个 Next.js Web 应用程序,允许管理员管理类别、产品、订单、客户、支付和设置。

API

wexCommerce API 公开了后端和前端所需的所有 wexCommerce 功能。API 遵循 MVC 设计模式。JWT 用于身份验证。有些功能需要身份验证,例如与管理产品和订单相关的功能,而另一些功能则不需要身份验证,例如为非身份验证用户检索类别和可用产品。

  • ./api/src/models/ 文件夹包含 MongoDB 模型。
  • ./api/src/routes/ 文件夹包含 Express 路由。
  • ./api/src/controllers/ 文件夹包含控制器。
  • ./api/src/middlewares/ 文件夹包含中间件。
  • ./api/src/app.ts 是加载路由的主服务器。
  • ./api/src/index.ts 是 wexCommerce API 的主要入口点。

index.ts

index.ts 在主服务器中

import 'dotenv/config'
import process from 'node:process'
import fs from 'node:fs/promises'
import http from 'node:http'
import https, { ServerOptions } from 'node:https'
import * as env from './config/env.config'
import * as databaseHelper from './common/databaseHelper'
import app from './app'
import * as logger from './common/logger'

if (
  await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize()
) {
  let server: http.Server | https.Server

  if (env.HTTPS) {
    https.globalAgent.maxSockets = Number.POSITIVE_INFINITY
    const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8')
    const certificate = await fs.readFile(env.CERTIFICATE, 'utf8')
    const credentials: ServerOptions = { key: privateKey, cert: certificate }
    server = https.createServer(credentials, app)

    server.listen(env.PORT, () => {
      logger.info('HTTPS server is running on Port', env.PORT)
    })
  } else {
    server = app.listen(env.PORT, () => {
      logger.info('HTTP server is running on Port', env.PORT)
    })
  }

  const close = () => {
    logger.info('Gracefully stopping...')
    server.close(async () => {
      logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`)
      await databaseHelper.close(true)
      logger.info('MongoDB connection closed')
      process.exit(0)
    })
  }

  ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close))
}

这是一个 TypeScript 文件,它使用 Node.js 和 Express 启动一个服务器。它导入了多个模块,包括 dotenv、process、fs、http、https、mongoose 和 app。然后它建立与 wexCommerce MongoDB 数据库的连接。然后它检查 HTTPS 环境变量是否设置为 true,如果是,则使用 https 模块和提供的私钥和证书创建一个 HTTPS 服务器。否则,它使用 http 模块创建一个 HTTP 服务器。服务器监听 PORT 环境变量中指定的端口。

定义了 close 函数,以便在收到终止信号时优雅地停止服务器。它关闭服务器和 MongoDB 连接,然后以状态码 0 退出进程。最后,它注册 close 函数,以便在进程收到 SIGINTSIGTERMSIGQUIT 信号时调用。

app.ts

app.ts 是 wexCommerce API 的主要入口点

import express from 'express'
import compression from 'compression'
import helmet from 'helmet'
import nocache from 'nocache'
import cookieParser from 'cookie-parser'
import i18n from './lang/i18n'
import * as env from './config/env.config'
import cors from './middlewares/cors'
import allowedMethods from './middlewares/allowedMethods'
import userRoutes from './routes/userRoutes'
import categoryRoutes from './routes/categoryRoutes'
import productRoutes from './routes/productRoutes'
import cartRoutes from './routes/cartRoutes'
import orderRoutes from './routes/orderRoutes'
import notificationRoutes from './routes/notificationRoutes'
import deliveryTypeRoutes from './routes/deliveryTypeRoutes'
import paymentTypeRoutes from './routes/paymentTypeRoutes'
import settingRoutes from './routes/settingRoutes'
import stripeRoutes from './routes/stripeRoutes'
import * as helper from './common/helper'

const app = express()

app.use(helmet.contentSecurityPolicy())
app.use(helmet.dnsPrefetchControl())
app.use(helmet.crossOriginEmbedderPolicy())
app.use(helmet.frameguard())
app.use(helmet.hidePoweredBy())
app.use(helmet.hsts())
app.use(helmet.ieNoOpen())
app.use(helmet.noSniff())
app.use(helmet.permittedCrossDomainPolicies())
app.use(helmet.referrerPolicy())
app.use(helmet.xssFilter())
app.use(helmet.originAgentCluster())
app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' }))
app.use(helmet.crossOriginOpenerPolicy())

app.use(nocache())
app.use(compression({ threshold: 0 }))
app.use(express.urlencoded({ limit: '50mb', extended: true }))
app.use(express.json({ limit: '50mb' }))

app.use(cors())
app.options('*', cors())
app.use(cookieParser(env.COOKIE_SECRET))
app.use(allowedMethods)

app.use('/', userRoutes)
app.use('/', categoryRoutes)
app.use('/', productRoutes)
app.use('/', cartRoutes)
app.use('/', orderRoutes)
app.use('/', notificationRoutes)
app.use('/', deliveryTypeRoutes)
app.use('/', paymentTypeRoutes)
app.use('/', settingRoutes)
app.use('/', stripeRoutes)

i18n.locale = env.DEFAULT_LANGUAGE

await helper.mkdir(env.CDN_USERS)
await helper.mkdir(env.CDN_TEMP_USERS)
await helper.mkdir(env.CDN_CATEGORIES)
await helper.mkdir(env.CDN_TEMP_CATEGORIES)
await helper.mkdir(env.CDN_PRODUCTS)
await helper.mkdir(env.CDN_TEMP_PRODUCTS)

export default app

首先,我们创建一个 Express app 并加载 cors、compression、helmet 和 nocache 等中间件。我们使用 helmet 中间件库设置了各种安全措施。我们还导入了应用程序不同部分的各种路由文件,例如 productRoutesorderRoutescategoryRoutesnotificationRoutesuserRoutes。最后,我们加载 Express 路由并导出 app

路由

wexCommerce API 中有 10 条路由。每条路由都有自己的控制器,遵循 MVC 设计模式和 SOLID 原则。以下是主要路由

  • userRoutes:提供与用户相关的 REST 函数
  • categoryRoutes:提供与类别相关的 REST 函数
  • productRoutes:提供与产品相关的 REST 函数
  • cartRoutes:提供与购物车相关的 REST 函数
  • deliveryTypeRoutes:提供与配送方式相关的 REST 函数
  • paymentTypeRoutes:提供与支付方式相关的 REST 函数
  • orderRoutes:提供与订单相关的 REST 函数
  • notificationRoutes:提供与通知相关的 REST 函数
  • settingRoutes:提供与设置相关的 REST 函数
  • stripeRoutes:提供与 Stripe 支付网关相关的 REST 函数

我们不会逐一解释每条路由。我们将以 categoryRoutes 为例,看看它是如何制作的

import express from 'express'
import multer from 'multer'
import routeNames from '../config/categoryRoutes.config'
import authJwt from '../middlewares/authJwt'
import * as categoryController from '../controllers/categoryController'

const routes = express.Router()

routes.route(routeNames.validate).post(authJwt.verifyToken, categoryController.validate)
routes.route(routeNames.checkCategory).get(authJwt.verifyToken, categoryController.checkCategory)
routes.route(routeNames.create).post(authJwt.verifyToken, categoryController.create)
routes.route(routeNames.update).put(authJwt.verifyToken, categoryController.update)
routes.route(routeNames.delete).delete(authJwt.verifyToken, categoryController.deleteCategory)
routes.route(routeNames.getCategory).get(authJwt.verifyToken, categoryController.getCategory)
routes.route(routeNames.getCategories).get(categoryController.getCategories)
routes.route(routeNames.getFeaturedCategories).get(categoryController.getFeaturedCategories)
routes.route(routeNames.searchCategories).get(authJwt.verifyToken, categoryController.searchCategories)
routes.route(routeNames.createImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], categoryController.createImage)
routes.route(routeNames.updateImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], categoryController.updateImage)
routes.route(routeNames.deleteImage).post(authJwt.verifyToken, categoryController.deleteImage)
routes.route(routeNames.deleteTempImage).post(authJwt.verifyToken, categoryController.deleteTempImage)

export default routes

首先,我们创建一个 Express Router。然后,我们使用其名称、方法、中间件和控制器创建路由。

routeNames 包含 categoryRoutes 路由名称

export default {
    validate: '/api/validate-category',
    checkCategory: '/api/check-category/:id',
    create: '/api/create-category',
    update: '/api/update-category/:id',
    delete: '/api/delete-category/:id',
    getCategory: '/api/category/:id/:language',
    getCategories: '/api/categories/:language/:imageRequired',
    getFeaturedCategories: '/api/featured-categories/:language/:size',
    searchCategories: '/api/search-categories/:language',
    createImage: '/api/create-category-image',
    updateImage: '/api/update-category-image/:id',
    deleteImage: '/api/delete-category-image/:id',
    deleteTempImage: '/api/delete-temp-category-image/:image',
}

categoryController 包含关于类别的主要业务逻辑。我们不会查看控制器的所有源代码,因为它相当大,但我们将以 creategetCategories 控制器函数为例。

以下是 Category 模型

import { Schema, model } from 'mongoose'
import * as env from '../config/env.config'

const categorySchema = new Schema<env.Category>({
  values: {
    type: [Schema.Types.ObjectId],
    ref: 'Value',
    validate: (value: any) => Array.isArray(value),
  },
  image: {
    type: String,
  },
  featured: {
    type: Boolean,
    default: false,
  },
}, {
  timestamps: true,
  strict: true,
  collection: 'Category',
})

const Category = model<env.Category>('Category', categorySchema)

export default Category

一个 Category 有多个值。每种语言一个值。默认支持英语和法语。

以下是 Value 模型

import { Schema, model } from 'mongoose'
import * as env from '../config/env.config'

const locationValueSchema = new Schema<env.Value>(
  {
    language: {
      type: String,
      required: [true, "can't be blank"],
      index: true,
      trim: true,
      lowercase: true,
      minLength: 2,
      maxLength: 2,
    },
    value: {
      type: String,
      required: [true, "can't be blank"],
      index: true,
      trim: true,
    },
  },
  {
    timestamps: true,
    strict: true,
    collection: 'Value',
  },
)

const Value = model<env.Value>('Value', locationValueSchema)

export default Value

一个 Value 包含一个 language 代码(ISO 639-1)和一个字符串 value

以下是 create 控制器函数

/**
 * Create a category.
 *
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const create = async (req: Request, res: Response) => {
  const { body }: { body: wexcommerceTypes.UpsertCategoryPayload } = req
  const { values, image, featured } = body

  try {
    if (image) {
      const _image = path.join(env.CDN_TEMP_CATEGORIES, image)

      if (!await helper.exists(_image)) {
        logger.error(i18n.t('CATEGORY_IMAGE_NOT_FOUND'), body)
        return res.status(400).send(i18n.t('CATEGORY_IMAGE_NOT_FOUND'))
      }
    }

    const _values = []
    for (const value of values) {
      const _value = new Value({
        language: value.language,
        value: value.value,
      })
      await _value.save()
      _values.push(_value._id)
    }

    const category = new Category({ values: _values, featured })
    await category.save()

    if (image) {
      const _image = path.join(env.CDN_TEMP_CATEGORIES, image)

      if (await helper.exists(_image)) {
        const filename = `${category._id}_${Date.now()}${path.extname(image)}`
        const newPath = path.join(env.CDN_CATEGORIES, filename)

        await fs.rename(_image, newPath)
        category.image = filename
        await category.save()
      }
    }

    return res.sendStatus(200)
  } catch (err) {
    logger.error(`[category.create] ${i18n.t('DB_ERROR')} ${req.body}`, err)
    return res.status(400).send(i18n.t('DB_ERROR') + err)
  }
}

在此函数中,我们检索请求的正文,我们遍历正文中提供的值(每种语言一个值),并创建一个 Value。最后,我们根据创建的值和图像文件创建类别。

以下是 getCategories 控制器函数

/**
 * Get categories.
 *
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {unknown}
 */
export const getCategories = async (req: Request, res: Response) => {
  try {
    const { language, imageRequired } = req.params
    const _imageRequired = helper.StringToBoolean(imageRequired)

    let $match: mongoose.FilterQuery<env.Category> = {}
    if (_imageRequired) {
      $match = { image: { $ne: null } }
    }

    const categories = await Category.aggregate([
      {
        $match,
      },
      {
        $lookup: {
          from: 'Value',
          let: { values: '$values' },
          pipeline: [
            {
              $match: {
                $and: [
                  { $expr: { $in: ['$_id', '$$values'] } },
                  { $expr: { $eq: ['$language', language] } },
                ],
              },
            },
          ],
          as: 'value',
        },
      },
      { $unwind: { path: '$value', preserveNullAndEmptyArrays: false } },
      { $addFields: { name: '$value.value' } },
      { $project: { value: 0, values: 0 } },
      { $sort: { name: 1 } },
    ], { collation: { locale: env.DEFAULT_LANGUAGE, strength: 2 } })

    return res.json(categories)
  } catch (err) {
    logger.error(`[category.getCategories] ${i18n.t('DB_ERROR')}`, err)
    return res.status(400).send(i18n.t('DB_ERROR') + err)
  }
}

在此控制器函数中,我们根据提供的语言使用 aggregate MongoDB 函数从数据库中检索类别。

前端

前端是一个使用 Node.js、Next.js、React 和 MUI 构建的 Web 应用程序。在前端,用户可以搜索可用产品,将其添加到购物车并根据配送和支付方式进行结账。

  • ./frontend/public/ 文件夹包含公共资产。
  • ./frontend/src/styles/ 文件夹包含 CSS 样式。
  • ./frontend/src/components/ 文件夹包含 React 组件。
  • ./frontend/src/lang 包含语言环境文件。
  • ./frontend/src/app/ 文件夹包含 Next.js 页面。
  • ./frontend/src/lib/ 包含服务器操作。
  • ./frontend/next.config.mjs 是前端的主要配置文件。

前端是使用 create-next-app 创建的

npx create-next-app@latest

在 Next.js 中,页面是从 .js.jsx.ts.tsx 文件中导出的 React 组件,位于 pages 目录中。每个页面都根据其文件名与路由相关联。

默认情况下,Next.js 会预渲染每个页面。这意味着 Next.js 会提前为每个页面生成 HTML,而不是全部由客户端 JavaScript 完成。预渲染可以带来更好的性能和 SEO。

每个生成的 HTML 都与该页面所需的最小 JavaScript 代码相关联。当浏览器加载页面时,其 JavaScript 代码运行并使页面完全交互。(此过程称为hydration。)

wexCommerce 使用服务器端渲染进行 SEO 优化,以便搜索引擎可以索引产品。

后端

后端是一个使用 Node.js、Next.js、React 和 MUI 构建的 Web 应用程序。在后端,管理员可以管理类别、产品、订单和用户。当创建新订单时,管理员用户会在后端收到通知,并收到自动电子邮件。

  • ./backend/public/ 文件夹包含公共资产。
  • ./backend/src/styles/ 文件夹包含 CSS 样式。
  • ./backend/src/components/ 文件夹包含 React 组件。
  • ./backend/src/lang 包含语言环境文件。
  • ./backend/src/app/ 文件夹包含 Next.js 页面。
  • ./backend/src/lib/ 包含服务器操作。
  • ./backend/next.config.mjs后端的主要配置文件。

后端也是使用 create-next-app 创建的

npx create-next-app@latest

就是这样!希望您喜欢阅读这篇文章。

历史

© . All rights reserved.