BookCars - 汽车租赁平台及移动应用






4.98/5 (81投票s)
带移动应用的汽车租赁平台
目录
- 引言
- 特点
- 实时演示
- 必备组件
- 快速概览
- 背景
- 安装(自托管)
- 安装 (VPS)
- 安装(Docker)
- 设置 Stripe
- 更改货币
- 添加新语言
- 演示数据库
- 构建移动应用
- 从源码运行
- 运行移动应用
- 单元测试和覆盖率
- 日志
- Using the Code
- 关注点
- 历史
引言
BookCars 是一个面向供应商的汽车租赁平台,提供后端用于管理汽车车队和预订,以及用于租赁汽车的前端和移动应用。
通过以下解决方案,您可以以极低的成本构建一个完全可定制的、针对多个供应商优化的汽车租赁网站,并且拥有一个运行中的 Stripe 支付网关,只需将其托管在至少 1GB RAM 的 Docker 虚拟机上。
BookCars 设计用于与多个供应商协作。每个供应商都可以从后端管理其汽车车队和预订。BookCars 也可以只与一个供应商合作,并用作汽车租赁聚合器。
通过后端,管理员可以创建和管理供应商、汽车、地点、客户和预订。
当创建新供应商时,他们会收到一封电子邮件,提示他们创建账户,以便访问后端并管理其汽车车队和预订。
客户可以从前端或移动应用注册,根据取车和还车地点及时间搜索可用车辆,选择车辆并完成结账流程。
一项关键的设计决策是使用 TypeScript 而不是 JavaScript,因为其具有诸多优势。TypeScript 提供了强类型、出色的工具支持和集成,从而产生了高质量、可扩展、更具可读性和可维护性的代码,易于调试和测试。
BookCars 可以在 Docker 容器中运行。在本文中,您可以找到如何构建 BookCars Docker 镜像并在 Docker 容器中运行它。
在本文中,您将学习 BookCars 是如何构建的,包括源代码的主要部分和软件架构的描述,如何部署它,以及如何运行源代码。但在深入研究之前,我们将从对平台的快速概述开始。
特点
- 供应商管理
- 支持单个或多个供应商
- 车队管理
- 地点、国家、停车位和地图功能
- 预订管理
- 支付管理
- 客户管理
- 多种支付方式(信用卡、PayPal、Google Pay、Apple Pay、Link、Pay Later)
- 运营中的 Stripe 支付网关
- 多语言支持(英语、法语)
- 多种分页选项(经典的带有下一页和上一页按钮的分页,无限滚动)
- 响应式后端和前端
- 原生 Android 和 iOS 移动应用,代码库单一
- 推送通知
- 防 XSS、XST、CSRF 和 MITM 攻击
- 支持的平台:iOS、Android、Web、Docker
实时演示
前端
- URL:https://bookcars.dynv6.net:3002/
- 登录:jdoe@bookcars.ma
- 密码:B00kC4r5
后端
- URL:https://bookcars.dynv6.net:3001/
- 登录:admin@bookcars.ma
- 密码:B00kC4r5
移动应用
您可以在任何 Android 设备上安装该 Android 应用。
用设备扫描此代码
打开相机应用,将其对准此代码。然后点击出现的通知。
如何在 Android 上安装移动应用
-
在运行 Android 8.0 (API 级别 26) 及更高版本的设备上,您必须导航到“安装未知应用”系统设置屏幕,以允许从特定来源(即您正在下载应用的浏览器)安装应用。
-
在运行 Android 7.1.1 (API 级别 25) 及更低版本的设备上,您应该在设备上启用“设置”>“安全”中的“未知来源”系统设置。
备用方法
您也可以通过直接下载 APK 并将其安装在任何 Android 设备上来安装 Android 应用。
- 下载 APK
- 登录:jdoe@bookcars.ma
- 密码:B00kC4r5
必备组件
- TypeScript
- Node.js
- Express
- MongoDB
- React
- Vite
- MUI
- React Native
- Expo
- JWT
- MVC
- Docker
- NGINX/Apache
- Git
快速概览
在本节中,您将看到前端、后端和移动应用主要页面的快速概述。
前端
通过前端,客户可以搜索可用车辆,选择车辆并结账。
下图是前端的主页面,客户可以在其中选择取车和还车地点及时间,然后搜索可用车辆。
下图是主页面的搜索结果,客户可以在其中选择要租赁的车辆。
下图是结账页面,客户可以在其中设置租赁选项并结账。如果客户未注册,他可以同时结账并注册。他将收到一封确认和激活电子邮件,提示他设置密码(如果尚未注册)。
下图是登录页面。在生产环境中,身份验证 cookie 是 httpOnly、已签名、安全且严格的 sameSite。这些选项可防止 XSS、CSRF 和 MITM 攻击。通过自定义中间件,身份验证 cookie 也可免受 XST 攻击。
下图是注册页面。
下图是客户查看和管理其预订的页面。
下图是客户查看预订详情的页面。
下图是联系页面。
下图是汽车租赁地点页面。
下图是客户查看和管理其通知的页面。
还有其他页面,但这些是前端的主要页面。
移动应用
通过移动应用,客户可以搜索可用车辆,选择车辆并结账。
如果客户的预订状态从后端更新,客户还可以接收推送通知。
下图是移动应用的主要页面,客户可以在其中选择取车和还车地点及时间,然后搜索可用车辆。
下图是主页面的搜索结果,客户可以在其中选择要租赁的车辆并结账。
下图是登录和注册页面。
下图是客户查看和管理其预订的页面。
下图是客户更新其个人信息、更改密码和查看其通知的页面。
以上是移动应用的主要页面。
后端
BookCars 是面向供应商的。这意味着有三种类型的用户
- 管理员:他拥有对后端的完全访问权限。他可以做任何事情。
- 供应商:他拥有对后端的有限访问权限。他只能管理其汽车车队和预订。
- 客户:他只能访问前端和移动应用。他无法访问后端。
BookCars 设计用于与多个供应商协作。每个供应商都可以从后端管理其汽车车队和预订。BookCars 也可以只与一个供应商合作。
通过后端,管理员可以创建和管理供应商、汽车、地点、客户和预订。
当创建新供应商时,他们会收到一封电子邮件,提示他们创建账户,以便访问后端并管理其汽车车队和预订。
下图是后端的登录页面。
下图是后端的仪表板页面,管理员和供应商可以在其中查看和管理预订。
下图是显示汽车车队并可进行管理的页面。
下图是管理员和供应商通过提供图像和汽车信息来创建新汽车的页面。如果汽车选项是免费包含的,则将相应汽车选项设置为 0。否则,设置选项的价格或留空如果您不想包含它。
下图是管理员和供应商编辑汽车的页面。
下图是管理员管理用户的页面。
下图是编辑预订的页面。
还有其他页面,但这些是后端的主要页面。
背景
BookCars 的基本理念非常简单
- 后端:管理员通过后端创建新供应商。每个供应商都会收到一封自动邮件以激活其账户并获得对后端的访问权限,以便管理其汽车车队、预订和客户。
- 前端和移动应用:客户通过前端和移动应用,可以根据取车和还车地点及时间查看可用车辆。然后,他们可以进行结账以预订车辆。
安装(自托管)
BookCars 是跨平台的,可以在 Windows、Linux 和 macOS 上运行和安装。
以下是 Linux 上的安装说明。
必备组件
-
安装 git、Node.js、NGINX、MongoDB 和 mongosh。如果您想使用 MongoDB Atlas,可以跳过安装和配置 MongoDB。
-
配置 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
说明
- 克隆 BookCars 仓库
cd /opt sudo git clone https://github.com/aelassas/bookcars.git
- 添加权限
sudo chown -R $USER:$USER /opt/bookcars sudo chmod -R +x /opt/bookcars/__scripts
- 创建部署快捷方式
sudo ln -s /opt/bookcars/__scripts/bc-deploy.sh /usr/local/bin/bc-deploy
- 创建 BookCars 服务
sudo cp /opt/bookcars/__services/bookcars.service /etc/systemd/system sudo systemctl enable bookcars.service
- 创建 /opt/bookcars/api/.env 文件
NODE_ENV=production BC_PORT=4002 BC_HTTPS=false BC_PRIVATE_KEY=/etc/ssl/bookcars.ma.key BC_CERTIFICATE=/etc/ssl/bookcars.ma.crt BC_DB_URI=mongodb://admin:PASSWORD@127.0.0.1:27017/bookcars?authSource=admin&appName=bookcars BC_DB_SSL=false BC_DB_SSL_CERT=/etc/ssl/bookcars.ma.crt BC_DB_SSL_CA=/etc/ssl/bookcars.ma.ca.pem BC_DB_DEBUG=false BC_COOKIE_SECRET=COOKIE_SECRET BC_AUTH_COOKIE_DOMAIN=localhost BC_JWT_SECRET=JWT_SECRET BC_JWT_EXPIRE_AT=86400 BC_TOKEN_EXPIRE_AT=86400 BC_SMTP_HOST=smtp.sendgrid.net BC_SMTP_PORT=587 BC_SMTP_USER=apikey BC_SMTP_PASS=PASSWORD BC_SMTP_FROM=admin@bookcars.ma BC_CDN_USERS=/var/www/cdn/bookcars/users BC_CDN_TEMP_USERS=/var/www/cdn/bookcars/temp/users BC_CDN_CARS=/var/www/cdn/bookcars/cars BC_CDN_TEMP_CARS=/var/www/cdn/temp/bookcars/cars BC_CDN_LOCATIONS=/var/www/cdn/bookcars/locations BC_CDN_TEMP_LOCATIONS=/var/www/cdn/bookcars/temp/locations BC_DEFAULT_LANGUAGE=en BC_BACKEND_HOST=https://:3001/ BC_FRONTEND_HOST=https:/// BC_MINIMUM_AGE=21 BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN BC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY BC_ADMIN_EMAIL=admin@bookcars.ma BC_RECAPTCHA_SECRET=RECAPTCHA_SECRET
-
您需要配置以下选项
BC_DB_URI=mongodb://admin:PASSWORD@127.0.0.1:27017/bookcars?authSource=admin&appName=bookcars BC_COOKIE_SECRET=COOKIE_SECRET BC_AUTH_COOKIE_DOMAIN=localhost BC_JWT_SECRET=JWT_SECRET BC_SMTP_HOST=smtp.sendgrid.net BC_SMTP_PORT=587 BC_SMTP_USER=apikey BC_SMTP_PASS=PASSWORD BC_SMTP_FROM=admin@bookcars.ma BC_BACKEND_HOST=https://:3001/ BC_FRONTEND_HOST=https:/// BC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY BC_RECAPTCHA_SECRET=RECAPTCHA_SECRET
如果您想使用 MongoDB Atlas,请将您的 MongoDB Atlas URI 放入
BC_DB_URI
,否则,请将PASSWORD
替换为您在BC_DB_URI
中的 MongoDB 密码。将JWT_SECRET
替换为一个秘密令牌。最后,设置 SMTP 选项。SMTP 选项对于注册是必需的。您可以使用 sendgrid 或任何其他事务性电子邮件提供商。如果您选择 sendgrid,请在 sendgrid.com 上创建一个账户,登录并转到仪表板。在左侧面板上,点击 **Email API**,然后点击 **Integration Guide**。接着,选择 **SMTP Relay** 并按照步骤操作。您将需要创建一个 API 密钥。一旦您创建了 API 密钥并验证了 smtp 转发,请将 API 密钥复制到 ./api/.env 中的
BC_SMTP_PASS
。Sendgrid 的免费套餐允许每天发送多达 100 封电子邮件。如果您需要每天发送超过 100 封电子邮件,请切换到付费套餐或选择其他事务性电子邮件提供商。COOKIE_SECRET
和JWT_SECRET
至少应为 32 个字符长,但越长越好。您可以使用在线密码生成器并将密码长度设置为 32 个或更长。以下设置非常重要,如果设置不正确,身份验证将无法工作
BC_AUTH_COOKIE_DOMAIN=localhost BC_BACKEND_HOST=https://:3001/ BC_FRONTEND_HOST=https:///
将
localhost
替换为 IP 或 FQDN。也就是说,如果您从 http://<FQDN>:3001/ 访问后端。BC_BACKEND_HOST
应该是 http://<FQDN>:3001/。BC_FRONTEND_HOST
也是如此。而BC_AUTH_COOKIE_DOMAIN
应该是 FQDN。如果您想在移动应用中启用推送通知,请遵循这些 说明 并设置以下选项
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
如果您想启用 Stripe 支付网关,请 注册一个 Stripe 账户,填写表格并保存 Stripe 仪表板中的发布密钥和密钥。然后,在 api/.env 的以下选项中设置密钥
BC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
不要在网站上公开 Stripe 密钥或将其嵌入移动应用程序中。它必须保密并安全地存储在服务器端。
在 Stripe 中,所有账户默认总共有四个 API 密钥 - 两个用于测试模式,两个用于实时模式。
- 测试模式密钥:默认情况下,您可以使用此密钥在测试模式下对服务器上的请求进行身份验证。默认情况下,您可以使用此密钥不受限制地执行任何 API 请求。
- 测试模式发布密钥:在 Web 或移动应用程序的客户端代码中,可以使用此密钥进行测试。
- 实时模式密钥:在实时模式下,您可以使用此密钥在服务器上对请求进行身份验证。默认情况下,您可以使用此密钥不受限制地执行任何 API 请求。
- 实时模式发布密钥:当您准备好启动应用程序时,可以使用此密钥在 Web 或移动应用程序的客户端代码中。
仅使用您的测试 API 密钥进行测试。这样可以确保您不会意外修改您的实时客户或交易。
如果您想启用 HTTPS,则需要设置以下选项
BC_HTTPS = true BC_PRIVATE_KEY=/etc/ssl/bookcars.ma.key BC_CERTIFICATE=/etc/ssl/bookcars.ma.crt
如果您想在前端使用 Google reCAPTCHA,则需要设置
BC_RECAPTCHA_SECRET=RECAPTCHA_SECRET
- 创建 /opt/bookcars/backend/.env 文件
VITE_PORT=3001 VITE_NODE_ENV=production VITE_BC_API_HOST=https://:4002 VITE_BC_DEFAULT_LANGUAGE=en VITE_BC_PAGE_SIZE=30 VITE_BC_CARS_PAGE_SIZE=15 VITE_BC_BOOKINGS_PAGE_SIZE=20 VITE_BC_BOOKINGS_MOBILE_PAGE_SIZE=10 VITE_BC_CDN_USERS=https:///cdn/bookcars/users VITE_BC_CDN_TEMP_USERS=https:///cdn/bookcars/temp/users VITE_BC_CDN_CARS=https:///cdn/bookcars/cars VITE_BC_CDN_TEMP_CARS=https:///cdn/bookcars/temp/cars VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations VITE_BC_CDN_TEMP_LOCATIONS=https:///cdn/bookcars/temp/locations VITE_BC_SUPPLIER_IMAGE_WIDTH=60 VITE_BC_SUPPLIER_IMAGE_HEIGHT=30 VITE_BC_CAR_IMAGE_WIDTH=300 VITE_BC_CAR_IMAGE_HEIGHT=200 VITE_BC_MINIMUM_AGE=21 VITE_BC_PAGINATION_MODE=classic VITE_BC_CURRENCY=$ VITE_BC_DEPOSIT_FILTER_VALUE_1=250 VITE_BC_DEPOSIT_FILTER_VALUE_2=500 VITE_BC_DEPOSIT_FILTER_VALUE_3=750
您需要配置以下选项
VITE_BC_API_HOST=https://:4002 VITE_BC_CDN_USERS=https:///cdn/bookcars/users VITE_BC_CDN_TEMP_USERS=https:///cdn/bookcars/temp/users VITE_BC_CDN_CARS=https:///cdn/bookcars/cars VITE_BC_CDN_TEMP_CARS=https:///cdn/bookcars/temp/cars VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations VITE_BC_CDN_TEMP_LOCATIONS=https:///cdn/bookcars/temp/locations
如果您想本地测试,请将
localhost
保留;否则,请将其替换为 IP、主机名或 FQDN。
VITE_BC_PAGINATION_MODE
:您可以选择classic
或infinite_scroll
。此选项默认为classic
。如果您选择classic
,则在桌面上会看到经典的下一页和上一页按钮分页,在移动设备上会看到无限滚动。如果您选择infinite_scroll
,则在桌面和移动设备上都会看到无限滚动。 - 创建 /opt/bookcars/frontend/.env 文件
VITE_NODE_ENV=production VITE_BC_API_HOST=https://:4002 VITE_BC_RECAPTCHA_ENABLED=false VITE_BC_RECAPTCHA_SITE_KEY=GOOGLE_RECAPTCHA_SITE_KEY VITE_BC_DEFAULT_LANGUAGE=en VITE_BC_PAGE_SIZE=30 VITE_BC_CARS_PAGE_SIZE=15 VITE_BC_BOOKINGS_PAGE_SIZE=20 VITE_BC_BOOKINGS_MOBILE_PAGE_SIZE=10 VITE_BC_CDN_USERS=https:///cdn/bookcars/users VITE_BC_CDN_CARS=https:///cdn/bookcars/cars VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations VITE_BC_SUPPLIER_IMAGE_WIDTH=60 VITE_BC_SUPPLIER_IMAGE_HEIGHT=30 VITE_BC_CAR_IMAGE_WIDTH=300 VITE_BC_CAR_IMAGE_HEIGHT=200 VITE_BC_MINIMUM_AGE=21 VITE_BC_PAGINATION_MODE=classic VITE_BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY VITE_BC_STRIPE_CURRENCY_CODE=USD VITE_BC_SET_LANGUAGE_FROM_IP=false VITE_BC_GOOGLE_ANALYTICS_ENABLED=false VITE_BC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX VITE_BC_CONTACT_EMAIL=info@bookcars.ma VITE_BC_DEPOSIT_FILTER_VALUE_1=250 VITE_BC_DEPOSIT_FILTER_VALUE_2=500 VITE_BC_DEPOSIT_FILTER_VALUE_3=750
您需要配置以下选项
VITE_BC_API_HOST=https://:4002 VITE_BC_CDN_USERS=https:///cdn/bookcars/users VITE_BC_CDN_CARS=https:///cdn/bookcars/cars VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations VITE_BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY VITE_BC_STRIPE_CURRENCY_CODE=USD VITE_BC_CONTACT_EMAIL=info@bookcars.ma
如果您想本地测试,请将
localhost
保留;否则,请将其替换为 IP、主机名或 FQDN。如果您想启用 Stripe 支付网关,请将 Stripe 发布密钥设置在
VITE_BC_STRIPE_PUBLISHABLE_KEY
中。您可以从 Stripe 仪表板中检索它。
VITE_BC_STRIPE_CURRENCY_CODE
是三位字母的 ISO 4217 货币代码,例如“USD”或“EUR”。Stripe 支付必需。必须是支持的货币:https://docs.stripe.com/currenciesreCAPTCHA 默认禁用。如果您想启用它,需要将
VITE_BC_RECAPTCHA_ENABLED
设置为true
,并将VITE_BC_RECAPTCHA_SITE_KEY
设置为 Google reCAPTCHA 站点密钥。
通过联系表单发送的电子邮件将发送到VITE_BC_CONTACT_EMAIL=info@bookcars.ma
如果您想启用 Google Analytics,请设置以下选项
VITE_BC_GOOGLE_ANALYTICS_ENABLED=true VITE_BC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX
- 如果您想运行或构建移动应用,您需要创建 mobile/.env
BC_API_HOST=https://bookcars.ma:4002 BC_DEFAULT_LANGUAGE=en BC_PAGE_SIZE=20 BC_CARS_PAGE_SIZE=8 BC_BOOKINGS_PAGE_SIZE=8 BC_CDN_USERS=https://bookcars.ma/cdn/bookcars/users BC_CDN_CARS=https://bookcars.ma/cdn/bookcars/cars BC_SUPPLIER_IMAGE_WIDTH=60 BC_SUPPLIER_IMAGE_HEIGHT=30 BC_CAR_IMAGE_WIDTH=300 BC_CAR_IMAGE_HEIGHT=200 BC_MINIMUM_AGE=21 BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY BC_STRIPE_MERCHANT_IDENTIFIER=MERCHANT_IDENTIFIER BC_STRIPE_COUNTRY_CODE=US BC_STRIPE_CURRENCY_CODE=USD BC_CURRENCY=$
您需要配置以下选项
BC_API_HOST=https://bookcars.ma:4002 BC_CDN_USERS=https://bookcars.ma/cdn/bookcars/users BC_CDN_CARS=https://bookcars.ma/cdn/bookcars/cars BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY BC_STRIPE_MERCHANT_IDENTIFIER=MERCHANT_IDENTIFIER BC_STRIPE_COUNTRY_CODE=US BC_STRIPE_CURRENCY_CODE=USD BC_CURRENCY=$
将
localhost
替换为 IP、主机名或 FQDN。如果您想启用 Stripe 支付网关,请将 Stripe 发布密钥设置在
BC_STRIPE_PUBLISHABLE_KEY
中。您可以从 Stripe 仪表板中检索它。BC_STRIPE_MERCHANT_IDENTIFIER
是您在 Apple 注册的用于 Apple Pay 的商户标识符。BC_STRIPE_COUNTRY_CODE
是您公司所在国家/地区的两位字母 ISO 3166 代码,例如“US”。Stripe 支付必需。BC_STRIPE_CURRENCY_CODE
是三位字母的 ISO 4217 货币代码,例如“USD”或“EUR”。Stripe 支付必需。必须是支持的货币:https://docs.stripe.com/currencies - 配置 NGINX
sudo nano /etc/nginx/sites-available/default
按以下方式更改前端的配置
server { root /var/www/bookcars/frontend; #listen 443 http2 ssl default_server; listen 80 default_server; server_name _; #ssl_certificate_key /etc/ssl/bookcars.ma.key; #ssl_certificate /etc/ssl/bookcars.ma.pem; access_log /var/log/nginx/bookcars.frontend.access.log; error_log /var/log/nginx/bookcars.frontend.error.log; index index.html; location / { # First attempt to serve request as file, then as directory, # then as index.html, then fall back to displaying a 404. try_files $uri $uri/ /index.html =404; } location /cdn { alias /var/www/cdn; } }
如果您想启用 SSL,请取消注释并设置这些行
#listen 443 http2 ssl default_server #ssl_certificate_key /etc/ssl/bookcars.ma.key #ssl_certificate /etc/ssl/bookcars.ma.pem;
为后端添加以下配置
server { root /var/www/bookcars/backend; #listen 3001 http2 ssl default_server; listen 3001 default_server; server_name _; #ssl_certificate_key /etc/ssl/bookcars.ma.key; #ssl_certificate /etc/ssl/bookcars.ma.pem; #error_page 497 301 =307 https://$host:$server_port$request_uri; access_log /var/log/nginx/bookcars.backend.access.log; error_log /var/log/nginx/bookcars.backend.error.log; index index.html; location / { # First attempt to serve request as file, then as directory, # then as index.html, then fall back to displaying a 404. try_files $uri $uri/ /index.html =404; } }
创建
/var/www/cdn/bookcars
文件夹,并为运行 bookcars 服务的用户在/var/www/cdn/bookcars
上授予完全访问权限。
如果您想启用 SSL,请取消注释并设置这些行#listen 3001 http2 ssl default_server #ssl_certificate_key /etc/ssl/bookcars.ma.key #ssl_certificate /etc/ssl/bookcars.ma.pem #error_page 497 301 =307 https://$host:$server_port$request_uri;
然后,检查 NGINX 配置并重新启动 NGINX 服务
sudo nginx -t sudo systemctl restart nginx.service sudo systemctl status nginx.service
- 启用防火墙并打开 BookCars 端口
sudo ufw enable sudo ufw allow 4002/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw allow 3001/tcp sudo ufw allow 27017/tcp
- 启动 bookcars 服务
cd /opt/bookcars/api npm install --omit=dev sudo systemctl start bookcars.service
使用以下命令确保 BookCars 服务正在运行
sudo systemctl status bookcars.service
通过检查日志确保数据库连接已建立
tail -f /var/log/bookcars.log
或通过打开此文件
tail -f /opt/bookcars/api/logs/all.log
错误日志写入于
tail -f /opt/bookcars/api/logs/error.log
- 部署 BookCars
bc-deploy all
BookCars 后端可通过端口 3001 访问,前端可通过端口 80 访问。
如果您想在 RAM 较少的 VPS 上安装 BookCars,在运行
bc-deploy all
时可能会遇到内存问题。在这种情况下,您需要按以下方式操作- 运行
bc-deploy api
来安装和运行 API。 - 在您的桌面 PC 上,按前面所述设置 frontend/.env,然后从 frontend 文件夹运行以下命令
npm install npm run build
- 将 frontend/build 的内容从您的桌面 PC 复制到 VPS 上的 /var/www/bookcars/frontend。
- 在您的桌面 PC 上,按前面所述设置 backend/.env,然后从 backend 文件夹运行以下命令
npm install npm run build
- 将 backend/build 的内容从您的桌面 PC 复制到 VPS 上的 /var/www/bookcars/backend。
- 重启 NGINX
sudo rm -rf /var/cache/nginx sudo systemctl restart nginx sudo systemctl status nginx
- 运行
-
如果您不想使用演示数据库,请导航到 hostname:3001/sign-up 来创建一个管理员。
-
打开 backend/src/App.tsx 并注释掉这些行以保护后端
const SignUp = lazy(() => import('./pages/SignUp')) <Route exact path='/sign-up' element={<SignUp />} />
然后再次运行后端部署
bc-deploy backend
如果您只想部署前端,请运行以下命令
bc-deploy frontend
如果您只想部署 API,请运行以下命令
bc-deploy api
如果您想部署 API、后端和前端,请运行以下命令
bc-deploy all
要更改货币,请遵循这些 说明。
安装 (VPS)
这个 指南 展示了如何在运行 Ubuntu 22.04 的 VPS 上安装 BookCars,该 VPS 至少需要 512MB RAM 和一个 CPU。
安装(Docker)
BookCars 可以在 Linux 上的 Docker 容器以及 Windows 或 Mac 的 Docker Desktop 中运行。
Docker 镜像
本节介绍如何构建 BookCars Docker 镜像并在 Docker 容器中运行它。
- 克隆 BookCars 仓库
git clone https://github.com/aelassas/bookcars.git
- 创建 ./api/.env.docker 文件,内容如下
NODE_ENV=production BC_PORT=4002 BC_HTTPS=false BC_PRIVATE_KEY=/etc/ssl/bookcars.key BC_CERTIFICATE=/etc/ssl/bookcars.crt BC_DB_URI=mongodb://admin:PASSWORD@mongo:27017/bookcars?authSource=admin&appName=bookcars BC_DB_SSL=false BC_DB_SSL_CERT=/etc/ssl/bookcars.crt BC_DB_SSL_CA=/etc/ssl/bookcars.ca.pem BC_DB_DEBUG=false BC_COOKIE_SECRET=COOKIE_SECRET BC_AUTH_COOKIE_DOMAIN=localhost BC_JWT_SECRET=JWT_SECRET BC_JWT_EXPIRE_AT=86400 BC_TOKEN_EXPIRE_AT=86400 BC_SMTP_HOST=smtp.sendgrid.net BC_SMTP_PORT=587 BC_SMTP_USER=apikey BC_SMTP_PASS=PASSWORD BC_SMTP_FROM=admin@bookcars.ma BC_CDN_USERS=/var/www/cdn/bookcars/users BC_CDN_TEMP_USERS=/var/www/cdn/bookcars/temp/users BC_CDN_CARS=/var/www/cdn/bookcars/cars BC_CDN_TEMP_CARS=/var/www/cdn/bookcars/temp/cars BC_CDN_LOCATIONS=/var/www/cdn/bookcars/locations BC_CDN_TEMP_LOCATIONS=/var/www/cdn/bookcars/temp/locations BC_DEFAULT_LANGUAGE=en BC_BACKEND_HOST=https://:3001/ BC_FRONTEND_HOST=https:/// BC_MINIMUM_AGE=21 BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN BC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY BC_ADMIN_EMAIL=admin@bookcars.ma BC_RECAPTCHA_SECRET=RECAPTCHA_SECRET
设置以下选项
BC_DB_URI=mongodb://admin:PASSWORD@mongo:27017/bookcars?authSource=admin&appName=bookcars BC_COOKIE_SECRET=COOKIE_SECRET BC_AUTH_COOKIE_DOMAIN=localhost BC_JWT_SECRET=JWT_SECRET BC_SMTP_HOST=smtp.sendgrid.net BC_SMTP_PORT=587 BC_SMTP_USER=apikey BC_SMTP_PASS=PASSWORD BC_SMTP_FROM=admin@bookcars.ma BC_BACKEND_HOST=https://:3001/ BC_FRONTEND_HOST=https:/// BC_RECAPTCHA_SECRET=RECAPTCHA_SECRET
如果您想使用 MongoDB Atlas,请将您的 MongoDB Atlas URI 放入
BC_DB_URI
,否则,请将PASSWORD
替换为您在BC_DB_URI
中的 MongoDB 密码。将JWT_SECRET
替换为一个秘密令牌。最后,设置 SMTP 选项。SMTP 选项对于注册是必需的。您可以使用 sendgrid 或任何其他事务性电子邮件提供商。如果您选择 sendgrid,请在 sendgrid.com 上创建一个账户,登录并转到仪表板。在左侧面板上,点击 **Email API**,然后点击 **Integration Guide**。接着,选择 **SMTP Relay** 并按照步骤操作。您将需要创建一个 API 密钥。一旦您创建了 API 密钥并验证了 smtp 转发,请将 API 密钥复制到 ./api/.env 中的
BC_SMTP_PASS
。Sendgrid 的免费套餐允许每天发送多达 100 封电子邮件。如果您需要每天发送超过 100 封电子邮件,请切换到付费套餐或选择其他事务性电子邮件提供商。COOKIE_SECRET
和JWT_SECRET
至少应为 32 个字符长,但越长越好。您可以使用在线密码生成器并将密码长度设置为 32 个或更长。以下设置非常重要,如果设置不正确,身份验证将无法工作
BC_AUTH_COOKIE_DOMAIN=localhost BC_BACKEND_HOST=https://:3001/ BC_FRONTEND_HOST=https:///
将
localhost
替换为 IP 或 FQDN。也就是说,如果您从 http://<FQDN>:3001/ 访问后端。BC_BACKEND_HOST
应该是 http://<FQDN>:3001/。BC_FRONTEND_HOST
也是如此。而BC_AUTH_COOKIE_DOMAIN
应该是 FQDN。如果您想本地测试,请将
localhost
保留。如果您想在移动应用中启用推送通知,请遵循这些 说明 并设置以下选项
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
如果您想启用 Stripe 支付网关,请 注册一个 Stripe 账户,填写表格并保存 Stripe 仪表板中的发布密钥和密钥。然后,在 api/.env 的以下选项中设置密钥
BC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
不要在网站上公开 Stripe 密钥或将其嵌入移动应用程序中。它必须保密并安全地存储在服务器端。
在 Stripe 中,所有账户默认总共有四个 API 密钥 - 两个用于测试模式,两个用于实时模式。
- 测试模式密钥:默认情况下,您可以使用此密钥在测试模式下对服务器上的请求进行身份验证。默认情况下,您可以使用此密钥不受限制地执行任何 API 请求。
- 测试模式发布密钥:在 Web 或移动应用程序的客户端代码中,可以使用此密钥进行测试。
- 实时模式密钥:在实时模式下,您可以使用此密钥在服务器上对请求进行身份验证。默认情况下,您可以使用此密钥不受限制地执行任何 API 请求。
- 实时模式发布密钥:当您准备好启动应用程序时,可以使用此密钥在 Web 或移动应用程序的客户端代码中。
仅使用您的测试 API 密钥进行测试。这样可以确保您不会意外修改您的实时客户或交易。
如果您想在前端使用 Google reCAPTCHA,则需要设置
BC_RECAPTCHA_SECRET=RECAPTCHA_SECRET
- 创建 ./backend/.env.docker 文件,内容如下
VITE_NODE_ENV=production VITE_BC_API_HOST=https://:4002 VITE_BC_DEFAULT_LANGUAGE=en VITE_BC_PAGE_SIZE=30 VITE_BC_CARS_PAGE_SIZE=15 VITE_BC_BOOKINGS_PAGE_SIZE=20 VITE_BC_BOOKINGS_MOBILE_PAGE_SIZE=10 VITE_BC_CDN_USERS=https:///cdn/bookcars/users VITE_BC_CDN_TEMP_USERS=https:///cdn/bookcars/temp/users VITE_BC_CDN_CARS=https:///cdn/bookcars/cars VITE_BC_CDN_TEMP_CARS=https:///cdn/bookcars/temp/cars VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations VITE_BC_CDN_TEMP_LOCATIONS=https:///cdn/bookcars/temp/locations VITE_BC_SUPPLIER_IMAGE_WIDTH=60 VITE_BC_SUPPLIER_IMAGE_HEIGHT=30 VITE_BC_CAR_IMAGE_WIDTH=300 VITE_BC_CAR_IMAGE_HEIGHT=200 VITE_BC_MINIMUM_AGE=21 VITE_BC_PAGINATION_MODE=classic VITE_BC_CURRENCY=$ VITE_BC_DEPOSIT_FILTER_VALUE_1=250 VITE_BC_DEPOSIT_FILTER_VALUE_2=500 VITE_BC_DEPOSIT_FILTER_VALUE_3=750
设置以下选项
VITE_BC_API_HOST=https://:4002 VITE_BC_CDN_USERS=https:///cdn/bookcars/users VITE_BC_CDN_TEMP_USERS=https:///cdn/bookcars/temp/users VITE_BC_CDN_CARS=https:///cdn/bookcars/cars VITE_BC_CDN_TEMP_CARS=https:///cdn/bookcars/temp/cars VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations VITE_BC_CDN_TEMP_LOCATIONS=https:///cdn/bookcars/temp/locations VITE_BC_CURRENCY=$
如果您想本地测试,请将
localhost
保留;否则,请将其替换为 IP、主机名或 FQDN。如果您想更改分页模式,请更改
VITE_BC_PAGINATION_MODE
选项。您可以选择classic
或infinite_scroll
。此选项默认为classic
。如果您选择classic
,则在桌面上会看到经典的下一页和上一页按钮分页,在移动设备上会看到无限滚动。如果您选择infinite_scroll
,则在桌面和移动设备上都会看到无限滚动。 - 创建 ./frontend/.env.docker 文件,内容如下
VITE_NODE_ENV=production VITE_BC_API_HOST=https://:4002 VITE_BC_RECAPTCHA_ENABLED=false VITE_BC_DEFAULT_LANGUAGE=en VITE_BC_PAGE_SIZE=30 VITE_BC_CARS_PAGE_SIZE=15 VITE_BC_BOOKINGS_PAGE_SIZE=20 VITE_BC_BOOKINGS_MOBILE_PAGE_SIZE=10 VITE_BC_CDN_USERS=https:///cdn/bookcars/users VITE_BC_CDN_CARS=https:///cdn/bookcars/cars VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations VITE_BC_SUPPLIER_IMAGE_WIDTH=60 VITE_BC_SUPPLIER_IMAGE_HEIGHT=30 VITE_BC_CAR_IMAGE_WIDTH=300 VITE_BC_CAR_IMAGE_HEIGHT=200 VITE_BC_MINIMUM_AGE=21 VITE_BC_PAGINATION_MODE=classic VITE_BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY VITE_BC_STRIPE_CURRENCY_CODE=USD VITE_BC_SET_LANGUAGE_FROM_IP=false VITE_BC_GOOGLE_ANALYTICS_ENABLED=false VITE_BC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX VITE_BC_CONTACT_EMAIL=info@bookcars.ma VITE_BC_DEPOSIT_FILTER_VALUE_1=250 VITE_BC_DEPOSIT_FILTER_VALUE_2=500 VITE_BC_DEPOSIT_FILTER_VALUE_3=750
设置以下选项
VITE_BC_API_HOST=https://:4002 VITE_BC_CDN_USERS=https:///cdn/bookcars/users VITE_BC_CDN_CARS=https:///cdn/bookcars/cars VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations VITE_BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY VITE_BC_STRIPE_CURRENCY_CODE=USD VITE_BC_SET_LANGUAGE_FROM_IP=false VITE_BC_GOOGLE_ANALYTICS_ENABLED=false VITE_BC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX VITE_BC_CONTACT_EMAIL=info@bookcars.ma
如果您想本地测试,请将
localhost
保留;否则,请将其替换为 IP、主机名或 FQDN。如果您想启用 Stripe 支付网关,请将 Stripe 发布密钥设置在
VITE_BC_STRIPE_PUBLISHABLE_KEY
中。您可以从 Stripe 仪表板中检索它。
VITE_BC_STRIPE_CURRENCY_CODE
是三位字母的 ISO 4217 货币代码,例如“USD”或“EUR”。Stripe 支付必需。必须是支持的货币:https://docs.stripe.com/currencies如果您想更改分页模式,请更改
VITE_BC_PAGINATION_MODE
选项。reCAPTCHA 默认在前端禁用。如果您想启用它,需要将
VITE_BC_RECAPTCHA_ENABLED
设置为true
,并将VITE_BC_RECAPTCHA_SITE_KEY
设置为 Google reCAPTCHA 站点密钥。通过联系表单发送的电子邮件将发送到
VITE_BC_CONTACT_EMAIL=info@bookcars.ma
如果您想启用 Google Analytics,请设置以下选项
VITE_BC_GOOGLE_ANALYTICS_ENABLED=true VITE_BC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX
- 打开 ./docker-compose.yml 并设置 MongoDB 密码
version: "3.8" services: api: build: context: . dockerfile: ./api/Dockerfile env_file: ./api/.env.docker restart: always ports: - 4002:4002 depends_on: - mongo volumes: - cdn:/var/www/cdn/bookcars 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: - 3001:3001 frontend: build: context: . dockerfile: ./frontend/Dockerfile depends_on: - api ports: - 80:80 volumes: - cdn:/var/www/cdn/bookcars volumes: cdn:
如果您想使用 MongoDB Atlas,请移除
mongo
容器。否则,请将PASSWORD
替换为您在 ./api/.env.docker 的BC_DB_URI
中设置的密码。 - 构建并运行 Docker 镜像
sudo docker compose up
要在后台运行 compose,请在命令中添加
-d
选项sudo docker compose up -d
如果您想重新构建,请使用以下命令
docker compose up --build --force-recreate --no-deps api backend frontend
如果您想检查容器的日志以进行故障排除,请使用以下命令
sudo docker compose logs
如果您想在不缓存的情况下重新构建,请使用以下命令
docker compose build --no-cache api backend frontend docker compose up
就是这样!BookCars 后端可通过 http://<hostname>:3001 访问,BookCars 前端可通过 http://<hostname> 访问。
如果您是第一次运行 BookCars,您将从一个空数据库开始。因此,您需要通过 http://<hostname>:3001/sign-up 填写表单来从后端创建管理员。需要配置 SMTP 设置才能处理注册。然后,通过打开 backend/src/App.tsx
并注释掉以下行来保护后端
const SignUp = lazy(() => import('./pages/SignUp'))
<Route exact path='/sign-up' element={<Signup />} />
您需要重新构建并运行 Docker 镜像
sudo docker compose build --no-cache
sudo docker compose up
创建管理员用户后,请执行以下操作
- 转到供应商页面并创建一名或多名供应商
- 转到地点页面并创建一名或多个地点
- 转到汽车页面并创建一辆或多辆汽车
- 转到前端,注册,选择一辆车并结账。
最后,您将在后端仪表板中看到预订列表。
如果您愿意,可以使用 演示数据库。
以下是 Docker 配置文件
- API:Dockerfile
- 后端:Dockerfile
- 前端:Dockerfile
- BookCars:docker-compose.yml
就是这样。您可以探索后端和前端中的其他页面。
SSL
本节将指导您如何在 Docker 容器中的 API、后端和前端启用 SSL。
将您的私钥 bookcars.key 和证书 bookcars.crt 复制到 ./。
bookcars.key 将作为 /etc/ssl/bookcars.key 加载,bookcars.crt 将作为 /etc/ssl/bookcars.crt 加载到 ./docker-compose.yml 中。
API
对于 API,请按以下方式更新 ./api/.env.docker 以启用 SSL
BC_HTTPS=true
BC_PRIVATE_KEY=/etc/ssl/bookcars.key
BC_CERTIFICATE=/etc/ssl/bookcars.crt
BC_BACKEND_HOST=https://:3001/
BC_FRONTEND_HOST=https:///
将 https://
替换为 https://<fqdn>
。
后端
对于后端,请在 ./backend/.env.docker 中更新以下选项
VITE_BC_API_HOST=https://:4002
VITE_BC_CDN_USERS=https:///cdn/bookcars/users
VITE_BC_CDN_TEMP_USERS=https:///cdn/bookcars/temp/users
VITE_BC_CDN_CARS=https:///cdn/bookcars/cars
VITE_BC_CDN_TEMP_CARS=https:///cdn/bookcars/temp/cars
VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations
VITE_BC_CDN_TEMP_LOCATIONS=https:///cdn/bookcars/temp/locations
将 https://
替换为 https://<fqdn>
。
然后,按以下方式更新 ./backend/nginx.conf 以启用 SSL
server {
listen 3001 ssl;
root /usr/share/nginx/html;
index index.html;
ssl_certificate_key /etc/ssl/bookcars.key;
ssl_certificate /etc/ssl/bookcars.crt;
error_page 497 301 =307 https://$host:$server_port$request_uri;
access_log /var/log/nginx/backend.access.log;
error_log /var/log/nginx/backend.error.log;
location / {
# First attempt to serve request as file, then as directory,
# then as index.html, then fall back to displaying a 404.
try_files $uri $uri/ /index.html =404;
}
}
前端
对于前端,请在 ./frontend/.env.docker 中更新以下选项
VITE_BC_API_HOST=https://:4002
VITE_BC_CDN_USERS=https:///cdn/bookcars/users
VITE_BC_CDN_CARS=https:///cdn/bookcars/cars
VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations
将 https://
替换为 https://<fqdn>
。
将端口 443 添加到 ./frontend/Dokerfile,如下所示
# syntax=docker/dockerfile:1
FROM node:lts-alpine as build
WORKDIR /frontend
COPY . .
COPY ./.env.docker ./.env
RUN npm install
RUN npm run build
FROM nginx:stable-alpine
COPY --from=build /frontend/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]
EXPOSE 80
EXPOSE 443
然后,按以下方式更新 ./frontend/nginx.conf 以启用 SSL
server {
listen 80;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
root /usr/share/nginx/html;
index index.html;
ssl_certificate_key /etc/ssl/bookcars.key;
ssl_certificate /etc/ssl/bookcars.crt;
access_log /var/log/nginx/frontend.access.log;
error_log /var/log/nginx/frontend.error.log;
location / {
# First attempt to serve request as file, then as directory,
# then as index.html, then fall back to displaying a 404.
try_files $uri $uri/ /index.html =404;
}
location /cdn {
alias /var/www/cdn;
}
}
docker-compose.yml
更新 ./docker-compose.yml 以加载您的私钥 bookcars.key 和证书 bookcars.crt,并将端口 443 添加到前端,如下所示
version: "3.8"
services:
api:
build:
context: .
dockerfile: ./api/Dockerfile
env_file: ./api/.env.docker
restart: always
ports:
- 4002:4002
depends_on:
- mongo
volumes:
- cdn:/var/www/cdn/bookcars
- ./bookcars.key:/etc/ssl/bookcars.key
- ./bookcars.crt:/etc/ssl/bookcars.crt
mongo:
image: mongo:latest
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:
- 3001:3001
volumes:
- ./bookcars.key:/etc/ssl/bookcars.key
- ./bookcars.crt:/etc/ssl/bookcars.crt
frontend:
build:
context: .
dockerfile: ./frontend/Dockerfile
depends_on:
- api
ports:
- 80:80
- 443:443
volumes:
- cdn:/var/www/cdn/bookcars
- ./bookcars.key:/etc/ssl/bookcars.key
- ./bookcars.crt:/etc/ssl/bookcars.crt
volumes:
cdn:
重新构建并运行 Docker 镜像
sudo docker compose build --no-cache
sudo docker compose up
设置 Stripe
如果您想启用 Stripe 支付网关,请 注册一个 Stripe 账户,填写表格并保存 Stripe 开发者仪表板中的发布密钥和密钥。
不要在网站上公开 Stripe 密钥或将其嵌入移动应用程序中。它必须保密并安全地存储在服务器端。
在 Stripe 中,所有账户默认共有四个 API 密钥 - 两个用于测试模式,两个用于实时模式
- 测试模式密钥:默认情况下,您可以使用此密钥在测试模式下对服务器上的请求进行身份验证。默认情况下,您可以使用此密钥不受限制地执行任何 API 请求。
- 测试模式发布密钥:在 Web 或移动应用程序的客户端代码中,可以使用此密钥进行测试。
- 实时模式密钥:在实时模式下,您可以使用此密钥在服务器上对请求进行身份验证。默认情况下,您可以使用此密钥不受限制地执行任何 API 请求。
- 实时模式发布密钥:当您准备好启动应用程序时,可以使用此密钥在 Web 或移动应用程序的客户端代码中。
您可以在 Stripe 开发者仪表板的 API 密钥页面找到您的密钥和发布密钥。
仅使用您的测试 API 密钥进行测试和开发。这样可以确保您不会意外修改您的实时客户或交易。
在生产环境中,请在 API、后端、前端和移动应用中使用 HTTPS 以便使用 Stripe 支付网关。
API
在 api/.env 的以下选项中设置 Stripe 密钥
BC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
前端
在 frontend/.env 的以下选项中设置 Stripe 发布密钥和货币
VITE_BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY
VITE_BC_STRIPE_CURRENCY_CODE=USD
移动应用
在 mobile/.env 中设置 Stripe 发布密钥和其他 Stripe 设置
BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY
BC_STRIPE_MERCHANT_IDENTIFIER=MERCHANT_IDENTIFIER
BC_STRIPE_COUNTRY_CODE=US
BC_STRIPE_CURRENCY_CODE=USD
BC_STRIPE_MERCHANT_IDENTIFIER
是您在 Apple 注册的用于 Apple Pay 的商户标识符。
BC_STRIPE_COUNTRY_CODE
是您公司所在国家/地区的两位字母 ISO 3166 代码,例如“US”。Stripe 支付必需。
BC_STRIPE_CURRENCY_CODE
是三位字母的 ISO 4217 货币代码,例如“USD”或“EUR”。Stripe 支付必需。必须是支持的货币:https://docs.stripe.com/currencies
如果您想启用 Apple Pay,还需要在 mobile/app.json 的 plugins
部分设置 merchantIdentifier
。
Google Pay
Google Pay 不支持 Expo Go。要使用 Google Pay,您必须创建 开发构建。这可以通过 EAS Build 完成,或者通过运行 npx expo run:android
在本地完成。
Apple Pay
Apple Pay 不支持 Expo Go。要使用 Apple Pay,您必须创建 开发构建。这可以通过 EAS Build 完成,或者通过运行 npx expo run:ios
在本地完成。
更改货币
要更改货币,请遵循这些说明
前端
打开 frontend/.env 并更改 VITE_BC_CURRENCY
和 VITE_BC_STRIPE_CURRENCY_CODE
设置。
默认情况下,它设置为
VITE_BC_CURRENCY=$ VITE_BC_STRIPE_CURRENCY_CODE=USD
例如,如果您想更改为欧元
VITE_BC_CURRENCY=€ VITE_BC_STRIPE_CURRENCY_CODE=EUR
在生产环境中,您需要重新构建前端以应用更改。
后端
打开 backend/.env 并更改 VITE_BC_CURRENCY
设置。
默认情况下,它设置为
VITE_BC_CURRENCY=$
在生产环境中,您需要重新构建后端以应用更改。
移动应用
打开 mobile/.env 并更改 BC_CURRENCY
和 BC_STRIPE_CURRENCY_CODE
设置。
默认情况下,它设置为
BC_CURRENCY=$ BC_STRIPE_CURRENCY_CODE=USD
例如,如果您想更改为欧元
BC_CURRENCY=€ BC_STRIPE_CURRENCY_CODE=EUR
在生产环境中,您需要重新构建移动应用以应用更改。
添加新语言
要添加新语言,请按以下步骤操作
API
- 将新的语言 ISO 639-1 代码 添加到 api/src/config/env.config.ts 中的
LANGUAGES
设置。 - 在 src/lang 文件夹中创建一个新文件 <ISO 639-1 code>.ts 并在此文件中添加翻译。
- 将您的翻译添加到 src/lang/i18n.ts
后端和前端
- 将新的语言 ISO 639-1 代码 及其标签添加到 src/config/env.config.ts 的
LANGUAGES
常量中。 - 将翻译添加到 src/lang/*.ts。
移动应用
- 在
LANGUAGES
常量中,将新的语言 ISO 639-1 代码及其标签添加到 config/env.config.ts。 - 在 lang 文件夹中创建一个新文件 <ISO 639-1 代码>.ts 并在此文件中添加翻译。
- 将您的翻译添加到 lang/i18n.ts
演示数据库
Windows、Linux 和 macOS
- 下载并安装 MongoDB 命令行数据库工具。
- 在 Windows 上,将 MongoDB 命令行数据库工具文件夹添加到
Path
环境变量。 - 将 bookcars-db.zip 下载到您的机器,解压它,然后从终端进入解压后的文件夹。
- 使用以下命令恢复 BookCars 演示数据库
mongorestore --verbose --drop --gzip --host=127.0.0.1 --port=27017 --username=admin --password=$PASSWORD --authenticationDatabase=admin --nsInclude="bookcars.*" --archive=bookcars.gz
将 $PASSWORD 替换为您的 MongoDB 密码。
如果您正在使用 MongoDB Atlas,请将您的 MongoDB Atlas URI 放在 --uri= 命令行参数中
mongorestore --verbose --drop --gzip --uri="mongodb://admin:$PASSWORD@127.0.0.1:27017/bookcars?authSource=admin&appName=bookcars" --nsInclude="bookcars.*" --nsFrom="bookcars.*" --nsTo="bookcars.*" --archive=bookcars.gz
将 cdn 文件夹的内容复制到您的 Web 服务器上,以便可以通过 https:///cdn/bookcars/ 访问这些文件
cdn 文件夹包含以下文件夹
- users:此文件夹包含用户的头像和供应商的图像。
- cars:此文件夹包含汽车的图像。
- temp:此文件夹包含临时文件。
如果您想从源代码运行 BookCars 或在 Windows 或 Linux 上安装它而无需使用 Docker,请按以下步骤操作
- 在 Windows 上,安装 IIS 并将 cdn 文件夹的内容复制到 C:\inetpub\wwwroot\cdn\bookcars。最后,为运行 BookCars API 的用户在 C:\inetpub\wwwroot\cdn\bookcars 上添加完全访问权限。
- 在 Linux 上,安装 NGINX 并将 cdn 文件夹的内容复制到 /var/www/cdn/bookcars。然后,按以下方式更新 /etc/nginx/sites-enabled/default
server { listen 80 default_server; server_name _; ... location /cdn { alias /var/www/cdn; } }
最后,为运行 BookCars API 的用户在 /var/www/cdn/bookcars 上添加完全访问权限。
后端凭证
- 用户名: admin@bookcars.ma
- 密码: B00kC4r5
前端和移动应用凭证
- 用户名: jdoe@bookcars.ma
- 密码: B00kC4r5
Docker
要在 Docker 容器中恢复 BookCars 演示数据库,请按以下步骤操作
- 确保端口 80、3001、4002 和 27017 未被任何应用程序使用。
- 在您的本地计算机上下载并安装 MongoDB 命令行数据库工具。
- 将 MongoDB 命令行数据库工具文件夹添加到您本地计算机的
Path
环境变量中。 - 将 bookcars-db.zip 下载到您的本地计算机并解压。
- 运行 compose
docker compose up
- 进入 bookcars-db 文件夹,使用以下命令恢复演示数据库
mongorestore --verbose --drop --gzip --host=127.0.0.1 --port=27017 --username=admin --password=$PASSWORD --authenticationDatabase=admin --nsInclude="bookcars.*" --archive=bookcars.gz
将$PASSWORD
替换为您在 docker-compose.yml 中设置的 MongoDB 密码。 - 使用以下命令获取 API Docker 容器名称
docker container ls
名称应类似:src-api-1
- 进入 bookcars-db/cdn 文件夹,使用以下命令将文件夹内容复制到 API 容器中
docker cp ./cdn/users src-api-1:/var/www/cdn/bookcars docker cp ./cdn/cars src-api-1:/var/www/cdn/bookcars
将src-api-1
替换为您的 API 容器名称。 - 进入后端 https://:3001 并使用以下凭证登录
用户名: admin@bookcars.ma
密码: B00kC4r5 - 进入前端 https:// 并使用以下凭证登录
用户名: jdoe@bookcars.ma
密码: B00kC4r5
构建移动应用
必备组件
要构建 BookCars 移动应用,您需要在您的机器上安装以下工具
使用以下命令安装 eas-cli
npm i -g eas-cli
配置
-
您需要下载 google-services.json 文件并将其放在 ./mobile 的根目录中以启用推送通知。否则,移动应用将无法构建。不要忘记按照文档中的说明,在 expo.dev > **Credentials** > **Service Credentials** > **Google Cloud Messaging Token** 中设置 Firebase 服务器密钥。
-
如果您没有 Expo 账户,则需要创建一个账户才能构建 BookCars 移动应用。
-
转到 expo.dev,点击 **Projects** 然后点击 **Create a Project**。将 **BookCars** 设置为项目名称,然后点击 **Create**。
-
转到 **BookCars** 项目并复制 **project ID**。打开 ./mobile/app.json 并将 **project ID** 粘贴到
extra.eas.projectId
中。 -
从 expo.dev(**Account Settings** > **Access Tokens**)创建 Expo 访问令牌,并将 api/.env 中的
BC_EXPO_ACCESS_TOKEN
设置。BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
- 创建 mobile/.env 文件,内容如下
BC_API_HOST=https://bookcars.ma:4002 BC_DEFAULT_LANGUAGE=en BC_PAGE_SIZE=20 BC_CARS_PAGE_SIZE=8 BC_BOOKINGS_PAGE_SIZE=8 BC_CDN_USERS=https://bookcars.ma/cdn/bookcars/users BC_CDN_CARS=https://bookcars.ma/cdn/bookcars/cars BC_SUPPLIER_IMAGE_WIDTH=60 BC_SUPPLIER_IMAGE_HEIGHT=30 BC_CAR_IMAGE_WIDTH=300 BC_CAR_IMAGE_HEIGHT=200 BC_MINIMUM_AGE=21 BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY BC_STRIPE_MERCHANT_IDENTIFIER=MERCHANT_IDENTIFIER BC_STRIPE_COUNTRY_CODE=US BC_STRIPE_CURRENCY_CODE=USD BC_CURRENCY=$
您需要配置以下选项
BC_API_HOST=https://bookcars.ma:4002 BC_CDN_USERS=https://bookcars.ma/cdn/bookcars/users BC_CDN_CARS=https://bookcars.ma/cdn/bookcars/cars BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY BC_STRIPE_MERCHANT_IDENTIFIER=MERCHANT_IDENTIFIER BC_STRIPE_COUNTRY_CODE=US BC_STRIPE_CURRENCY_CODE=USD BC_CURRENCY=$
将
https://bookcars.ma
替换为 IP 或 FQDN。如果您想启用 Stripe 支付网关,请将 Stripe 发布密钥设置在
BC_STRIPE_PUBLISHABLE_KEY
中。您可以从 Stripe 仪表板中检索它。BC_STRIPE_MERCHANT_IDENTIFIER
是您在 Apple 注册的用于 Apple Pay 的商户标识符。BC_STRIPE_COUNTRY_CODE
是您公司所在国家/地区的两位字母 ISO 3166 代码,例如“US”。Stripe 支付必需。BC_STRIPE_CURRENCY_CODE
是三位字母的 ISO 4217 货币代码,例如“USD”或“EUR”。Stripe 支付必需。必须是支持的货币:https://docs.stripe.com/currencies
生产构建
如果您想在生产环境中使用 BookCars 移动应用,您应该在 BookCars API 中使用 HTTPS,并在 ./mobile/app.json 中禁用 usesCleartextTraffic
expo 插件,方法是在 plugins
部分删除 "./plugins/usesCleartextTraffic"
这一行。
说明
- 将源代码克隆到您的计算机
git clone https://github.com/aelassas/bookcars.git
- 进入 mobile 文件夹
cd ./mobile
- 运行以下命令
npm install
Android
EAS Build
要使用 EAS Build 托管服务构建 BookCars Android 应用,请运行以下命令
npm run build:android
本地构建
本地构建需要 macOS 或 Linux。对于 Windows,您应该使用 EAS Builds。
您需要安装 Android Studio、openjdk-17,并设置 ANDROID_HOME
和 JAVA_HOME
环境变量。然后,运行以下命令
npm run build:android:local
在 macOS 上,如果遇到本地构建问题,请尝试在 ./mobile/eas.json 中设置 ANDROID_HOME
和 JAVA_HOME
环境变量,如下所示
{
"cli": {
"version": ">= 0.53.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"android": {
"image": "latest",
"gradleCommand": ":app:assembleDebug"
},
"ios": {
"image": "latest",
"buildConfiguration": "Debug"
}
},
"preview": {
"distribution": "internal"
},
"production": {
"env": {
"ANDROID_HOME": "/path/to/android/sdk",
"JAVA_HOME": "/path/to/java/home"
},
"android": {
"image": "latest",
"buildType": "apk"
},
"ios": {
"image": "latest"
}
}
},
"submit": {
"production": {}
}
}
iOS
EAS Build
要构建 BookCars iOS 应用,请运行以下命令
npm run build:ios
本地构建
您需要安装 fastlane 和 CocoaPods 在 macOS 上。
要本地构建 BookCars iOS 应用,请运行以下命令
npm run build:ios:local
从源码运行
以下是运行 BookCars 的源代码的说明。
必备组件
-
安装 git、Node.js、Windows 上的 NGINX 或 IIS、MongoDB 和 mongosh。如果您想使用 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 服务。
说明
- 克隆 BookCars 仓库
git clone https://github.com/aelassas/bookcars.git
- 创建 api/.env 文件,内容如下
NODE_ENV=development BC_PORT=4002 BC_HTTPS=false BC_PRIVATE_KEY=/etc/ssl/bookcars.ma.key BC_CERTIFICATE=/etc/ssl/bookcars.ma.crt BC_DB_URI=mongodb://admin:PASSWORD@127.0.0.1:27017/bookcars?authSource=admin&appName=bookcars BC_DB_SSL=false BC_DB_SSL_CERT=/etc/ssl/bookcars.ma.crt BC_DB_SSL_CA=/etc/ssl/bookcars.ma.ca.pem BC_DB_DEBUG=false BC_COOKIE_SECRET=COOKIE_SECRET BC_AUTH_COOKIE_DOMAIN=localhost BC_JWT_SECRET=JWT_SECRET BC_JWT_EXPIRE_AT=86400 BC_TOKEN_EXPIRE_AT=86400 BC_SMTP_HOST=smtp.sendgrid.net BC_SMTP_PORT=587 BC_SMTP_USER=apikey BC_SMTP_PASS=PASSWORD BC_SMTP_FROM=admin@bookcars.ma BC_CDN_USERS=/var/www/cdn/bookcars/users BC_CDN_TEMP_USERS=/var/www/cdn/bookcars/temp/users BC_CDN_CARS=/var/www/cdn/bookcars/cars BC_CDN_TEMP_CARS=/var/www/cdn/bookcars/temp/cars BC_CDN_LOCATIONS=/var/www/cdn/bookcars/locations BC_CDN_TEMP_LOCATIONS=/var/www/cdn/bookcars/temp/locations BC_DEFAULT_LANGUAGE=en BC_BACKEND_HOST=https://:3001/ BC_FRONTEND_HOST=https://:3002/ BC_MINIMUM_AGE=21 BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN BC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY BC_ADMIN_EMAIL=admin@bookcars.ma BC_RECAPTCHA_SECRET=RECAPTCHA_SECRET
在 Windows 上,安装 IIS 并使用以下值更新以下设置
BC_CDN_USERS=C:\inetpub\wwwroot\cdn\bookcars\users BC_CDN_TEMP_USERS=C:\inetpub\wwwroot\cdn\bookcars\temp\users BC_CDN_CARS=C:\inetpub\wwwroot\cdn\bookcars\cars BC_CDN_TEMP_CARS=C:\inetpub\wwwroot\cdn\bookcars\temp\cars BC_CDN_LOCATIONS=C:\inetpub\wwwroot\cdn\bookcars\locations BC_CDN_TEMP_LOCATIONS=C:\inetpub\wwwroot\cdn\bookcars\temp\locations
为运行 BookCars API 的用户在 C:\inetpub\wwwroot\cdn\bookcars 上添加完全访问权限。
您需要配置以下选项
BC_DB_URI=mongodb://admin:PASSWORD@127.0.0.1:27017/bookcars?authSource=admin&appName=bookcars BC_COOKIE_SECRET=COOKIE_SECRET BC_JWT_SECRET=JWT_SECRET BC_SMTP_HOST=smtp.sendgrid.net BC_SMTP_PORT=587 BC_SMTP_USER=apikey BC_SMTP_PASS=PASSWORD BC_SMTP_FROM=admin@bookcars.ma
如果您想使用 MongoDB Atlas,请将您的 MongoDB Atlas URI 放入
BC_DB_URI
,否则,请将PASSWORD
替换为您在BC_DB_URI
中的 MongoDB 密码。将JWT_SECRET
替换为一个秘密令牌。最后,设置 SMTP 选项。SMTP 选项对于注册是必需的。您可以使用 sendgrid 或任何其他事务性电子邮件提供商。如果您选择 sendgrid,请在 sendgrid.com 上创建一个账户,登录并转到仪表板。在左侧面板上,点击 **Email API**,然后点击 **Integration Guide**。接着,选择 **SMTP Relay** 并按照步骤操作。您将需要创建一个 API 密钥。一旦您创建了 API 密钥并验证了 smtp 转发,请将 API 密钥复制到 ./api/.env 中的
BC_SMTP_PASS
。Sendgrid 的免费套餐允许每天发送多达 100 封电子邮件。如果您需要每天发送超过 100 封电子邮件,请切换到付费套餐或选择其他事务性电子邮件提供商。COOKIE_SECRET
和JWT_SECRET
至少应为 32 个字符长,但越长越好。您可以使用在线密码生成器并将密码长度设置为 32 个或更长。如果您想在移动应用中启用推送通知,请遵循这些 说明 并设置以下选项
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
如果您想启用 Stripe 支付网关,请 注册一个 Stripe 账户,填写表格并保存 Stripe 仪表板中的发布密钥和密钥。然后,在 api/.env 的以下选项中设置密钥
BC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
不要在网站上公开 Stripe 密钥或将其嵌入移动应用程序中。它必须保密并安全地存储在服务器端。使用 Stripe 的测试模式。
仅使用您的测试 API 密钥进行测试。这样可以确保您不会意外修改您的实时客户或交易。
如果您想在前端使用 Google reCAPTCHA,则需要设置
BC_RECAPTCHA_SECRET=RECAPTCHA_SECRET
要运行 API,请使用以下命令
cd ./api npm install npm run dev
- 添加 backend/.env 文件
VITE_PORT=3001 VITE_NODE_ENV=development VITE_BC_API_HOST=https://:4002 VITE_BC_DEFAULT_LANGUAGE=en VITE_BC_PAGE_SIZE=30 VITE_BC_CARS_PAGE_SIZE=15 VITE_BC_BOOKINGS_PAGE_SIZE=20 VITE_BC_BOOKINGS_MOBILE_PAGE_SIZE=10 VITE_BC_CDN_USERS=https:///cdn/bookcars/users VITE_BC_CDN_TEMP_USERS=https:///cdn/bookcars/temp/users VITE_BC_CDN_CARS=https:///cdn/bookcars/cars VITE_BC_CDN_TEMP_CARS=https:///cdn/bookcars/temp/cars VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations VITE_BC_CDN_TEMP_LOCATIONS=https:///cdn/bookcars/temp/locations VITE_BC_SUPPLIER_IMAGE_WIDTH=60 VITE_BC_SUPPLIER_IMAGE_HEIGHT=30 VITE_BC_CAR_IMAGE_WIDTH=300 VITE_BC_CAR_IMAGE_HEIGHT=200 VITE_BC_MINIMUM_AGE=21 VITE_BC_PAGINATION_MODE=classic VITE_BC_CURRENCY=$ VITE_BC_DEPOSIT_FILTER_VALUE_1=250 VITE_BC_DEPOSIT_FILTER_VALUE_2=500 VITE_BC_DEPOSIT_FILTER_VALUE_3=750
要运行后端,请使用以下命令
cd ./backend npm install npm start
- 添加 frontend/.env 文件
VITE_PORT=3002 VITE_NODE_ENV=development VITE_BC_API_HOST=https://:4002 VITE_BC_RECAPTCHA_ENABLED=false VITE_BC_RECAPTCHA_SITE_KEY=GOOGLE_RECAPTCHA_SITE_KEY VITE_BC_DEFAULT_LANGUAGE=en VITE_BC_PAGE_SIZE=30 VITE_BC_CARS_PAGE_SIZE=15 VITE_BC_BOOKINGS_PAGE_SIZE=20 VITE_BC_BOOKINGS_MOBILE_PAGE_SIZE=10 VITE_BC_CDN_USERS=https:///cdn/bookcars/users VITE_BC_CDN_CARS=https:///cdn/bookcars/cars VITE_BC_CDN_LOCATIONS=https:///cdn/bookcars/locations VITE_BC_SUPPLIER_IMAGE_WIDTH=60 VITE_BC_SUPPLIER_IMAGE_HEIGHT=30 VITE_BC_CAR_IMAGE_WIDTH=300 VITE_BC_CAR_IMAGE_HEIGHT=200 VITE_BC_MINIMUM_AGE=21 VITE_BC_PAGINATION_MODE=classic VITE_BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY VITE_BC_STRIPE_CURRENCY_CODE=USD VITE_BC_SET_LANGUAGE_FROM_IP=false VITE_BC_GOOGLE_ANALYTICS_ENABLED=false VITE_BC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX VITE_BC_CONTACT_EMAIL=info@bookcars.ma VITE_BC_DEPOSIT_FILTER_VALUE_1=250 VITE_BC_DEPOSIT_FILTER_VALUE_2=500 VITE_BC_DEPOSIT_FILTER_VALUE_3=750
如果您想启用 Stripe 支付网关,请将 Stripe 发布密钥设置在
VITE_BC_STRIPE_PUBLISHABLE_KEY
中。您可以从 Stripe 仪表板中检索它。
VITE_BC_STRIPE_CURRENCY_CODE
是三位字母的 ISO 4217 货币代码,例如“USD”或“EUR”。Stripe 支付必需。必须是支持的货币:https://docs.stripe.com/currenciesreCAPTCHA 默认禁用。如果您想启用它,需要将
VITE_BC_RECAPTCHA_ENABLED
设置为true
,并将VITE_BC_RECAPTCHA_SITE_KEY
设置为 Google reCAPTCHA 站点密钥。要运行前端,请使用以下命令
cd ./frontend npm install npm start
- 如果您想运行移动应用,您需要添加 mobile/.env
BC_API_HOST=https://bookcars.ma:4002 BC_DEFAULT_LANGUAGE=en BC_PAGE_SIZE=20 BC_CARS_PAGE_SIZE=8 BC_BOOKINGS_PAGE_SIZE=8 BC_CDN_USERS=https://bookcars.ma/cdn/bookcars/users BC_CDN_CARS=https://bookcars.ma/cdn/bookcars/cars BC_SUPPLIER_IMAGE_WIDTH=60 BC_SUPPLIER_IMAGE_HEIGHT=30 BC_CAR_IMAGE_WIDTH=300 BC_CAR_IMAGE_HEIGHT=200 BC_MINIMUM_AGE=21 BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY BC_STRIPE_MERCHANT_IDENTIFIER=MERCHANT_IDENTIFIER BC_STRIPE_COUNTRY_CODE=US BC_STRIPE_CURRENCY_CODE=USD BC_CURRENCY=$
您需要配置以下选项
BC_API_HOST=https://bookcars.ma:4002 BC_CDN_USERS=https://bookcars.ma/cdn/bookcars/users BC_CDN_CARS=https://bookcars.ma/cdn/bookcars/cars BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY BC_STRIPE_MERCHANT_IDENTIFIER=MERCHANT_IDENTIFIER BC_STRIPE_COUNTRY_CODE=US BC_STRIPE_CURRENCY_CODE=USD BC_CURRENCY=$
您需要将
https://bookcars.ma
替换为 IP 或主机名。 - 配置 https:///cdn
- 在 Windows 上,安装 IIS,创建 C:\inetpub\wwwroot\cdn\bookcars 文件夹,并为运行 BookCars API 的用户在 bookcarsC:\inetpub\wwwroot\cdn\bookcars 文件夹上添加完全访问权限。
- 在 Linux 上,安装 NGINX,创建 /var/www/cdn/bookcars 文件夹,为运行 BookCars API 的用户在 /var/www/cdn/bookcars 文件夹上添加完全访问权限,并通过更改 /etc/nginx/sites-available/default 将 cdn 文件夹添加到 NGINX,如下所示
server { listen 80 default_server; server_name _; ... location /cdn { alias /var/www/cdn; } }
- 从 https://:3001/sign-up 创建管理员用户
- 要运行移动应用,只需在您的设备上下载 Expo 应用,并在 ./mobile 文件夹中运行以下命令
npm install npm start
您需要下载 google-services.json 文件并将其放在 ./mobile 的根目录中以启用推送通知。
您可以在 这里 找到运行移动应用的详细说明。
要更改货币,请遵循这些 说明。
运行移动应用
要运行移动应用,请创建 ./mobile/.env 文件,内容如下
BC_API_HOST=https://bookcars.ma:4002
BC_DEFAULT_LANGUAGE=en
BC_PAGE_SIZE=20
BC_CARS_PAGE_SIZE=8
BC_BOOKINGS_PAGE_SIZE=8
BC_CDN_USERS=https://bookcars.ma/cdn/bookcars/users
BC_CDN_CARS=https://bookcars.ma/cdn/bookcars/cars
BC_SUPPLIER_IMAGE_WIDTH=60
BC_SUPPLIER_IMAGE_HEIGHT=30
BC_CAR_IMAGE_WIDTH=300
BC_CAR_IMAGE_HEIGHT=200
BC_MINIMUM_AGE=21
BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY
BC_STRIPE_MERCHANT_IDENTIFIER=MERCHANT_IDENTIFIER
BC_STRIPE_COUNTRY_CODE=US
BC_STRIPE_CURRENCY_CODE=USD
BC_CURRENCY=$
您需要配置以下选项
BC_API_HOST=https://bookcars.ma:4002
BC_CDN_USERS=https://bookcars.ma/cdn/bookcars/users
BC_CDN_CARS=https://bookcars.ma/cdn/bookcars/cars
BC_STRIPE_PUBLISHABLE_KEY=STRIPE_PUBLISHABLE_KEY
BC_STRIPE_MERCHANT_IDENTIFIER=MERCHANT_IDENTIFIER
BC_STRIPE_COUNTRY_CODE=US
BC_STRIPE_CURRENCY_CODE=USD
BC_CURRENCY=$
将 https://bookcars.ma
替换为 IP、主机名或 FQDN。
如果您想启用 Stripe 支付网关,请将 Stripe 发布密钥设置在 BC_STRIPE_PUBLISHABLE_KEY
中。您可以从 Stripe 仪表板中检索它。使用 Stripe 的测试模式。
BC_STRIPE_MERCHANT_IDENTIFIER
是您在 Apple 注册的用于 Apple Pay 的商户标识符。
BC_STRIPE_COUNTRY_CODE
是您公司所在国家/地区的两位字母 ISO 3166 代码,例如“US”。Stripe 支付必需。
BC_STRIPE_CURRENCY_CODE
是三位字母的 ISO 4217 货币代码,例如“USD”或“EUR”。Stripe 支付必需。必须是支持的货币:https://docs.stripe.com/currencies
通过遵循这些 说明 来安装演示数据库。
配置 https:///cdn
在 Windows 上,安装 IIS 并为运行 BookCars API 的用户在 C:\inetpub\wwwroot\cdn\bookcars 上添加完全访问权限。
在 Linux 上,安装 NGINX 并按以下方式更新 /etc/nginx/sites-enabled/default
server {
listen 80 default_server;
server_name _;
...
location /cdn {
alias /var/www/cdn;
}
}
最后,为运行 BookCars API 的用户在 /var/www/cdn/bookcars 上添加完全访问权限。
通过遵循这些 说明 来配置 ./api。
使用以下命令运行 ./api
cd ./api
npm run dev
通过在您的设备上下载 Expo 应用并从 ./mobile 文件夹中运行以下命令来运行移动应用
cd ./mobile
npm install
npm start
在您的设备上打开 Expo 应用并扫描 QR 码以运行 BookCars 移动应用。
推送通知
如果您想启用 BookCars 推送通知,请下载 google-services.json 文件并将其放在 ./mobile 的根目录中以启用推送通知。其路径可以在 ./mobile/app.json 配置文件中的 googleServicesFile
设置选项中配置。不要忘记在 Firebase Console 中生成新的 私钥,并将 JSON 文件上传到 Expo 仪表板。
单元测试和覆盖率
以下是运行单元测试和构建覆盖率报告的说明。
必备组件
-
配置 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 服务。
说明
- 克隆 Movin' In 仓库
git clone https://github.com/aelassas/bookcars.git
- 创建 api/.env 文件,内容如下
NODE_ENV=development BC_PORT=4002 BC_HTTPS=false BC_PRIVATE_KEY=/etc/ssl/bookcars.ma.key BC_CERTIFICATE=/etc/ssl/bookcars.ma.crt BC_DB_URI=mongodb://admin:PASSWORD@127.0.0.1:27017/bookcars?authSource=admin&appName=bookcars BC_DB_SSL=false BC_DB_SSL_CERT=/etc/ssl/bookcars.ma.crt BC_DB_SSL_CA=/etc/ssl/bookcars.ma.ca.pem BC_DB_DEBUG=false BC_COOKIE_SECRET=COOKIE_SECRET BC_AUTH_COOKIE_DOMAIN=localhost BC_JWT_SECRET=JWT_SECRET BC_JWT_EXPIRE_AT=86400 BC_TOKEN_EXPIRE_AT=86400 BC_SMTP_HOST=in-v3.mailjet.com BC_SMTP_PORT=587 BC_SMTP_USER=USER BC_SMTP_PASS=PASSWORD BC_SMTP_FROM=admin@bookcars.ma BC_CDN_USERS=/var/www/cdn/bookcars/users BC_CDN_TEMP_USERS=/var/www/cdn/bookcars/temp/users BC_CDN_CARS=/var/www/cdn/bookcars/cars BC_CDN_TEMP_CARS=/var/www/cdn/bookcars/temp/cars BC_DEFAULT_LANGUAGE=en BC_BACKEND_HOST=https://:3001/ BC_FRONTEND_HOST=https://:3002/ BC_MINIMUM_AGE=21 BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN BC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY BC_STRIPE_SESSION_EXPIRE_AT=82800 BC_ADMIN_EMAIL=admin@bookcars.ma BC_RECAPTCHA_SECRET=RECAPTCHA_SECRET
在 Windows 上,安装 IIS 并使用以下值更新以下设置
BC_CDN_USERS=C:\inetpub\wwwroot\cdn\bookcars\users
BC_CDN_TEMP_USERS=C:\inetpub\wwwroot\cdn\bookcars\temp\users
BC_CDN_CARS=C:\inetpub\wwwroot\cdn\bookcars\cars
BC_CDN_TEMP_CARS=C:\inetpub\wwwroot\cdn\bookcars\temp\cars
为运行 Movin' In API 的用户在 C:\inetpub\wwwroot\cdn\bookcars
上添加完全访问权限。
然后,设置以下选项
BC_DB_URI=mongodb://admin:PASSWORD@127.0.0.1:27017/bookcars?authSource=admin&appName=bookcars
BC_COOKIE_SECRET=COOKIE_SECRET
BC_JWT_SECRET=JWT_SECRET
BC_SMTP_HOST=in-v3.mailjet.com
BC_SMTP_PORT=587
BC_SMTP_USER=USER
BC_SMTP_PASS=PASSWORD
BC_SMTP_FROM=admin@bookcars.ma
BC_STRIPE_SECRET_KEY=STRIPE_SECRET_KEY
将 PASSWORD
替换为 BC_DB_URI
中的 MongoDB 密码,将 JWT_SECRET
替换为一个秘密令牌。最后,设置 SMTP 选项。SMTP 选项对于注册是必需的。您可以使用 mailjet、brevo、sendgrid 或任何其他事务性电子邮件提供商。
将 STRIPE_SECRET_KEY
替换为您的 Stripe 测试模式密钥。您可以在 Stripe 开发者仪表板中找到它。
COOKIE_SECRET
和 JWT_SECRET
至少应为 32 个字符长,但越长越好。您可以使用在线密码生成器并将密码长度设置为 32 个或更长。
如果您想在移动应用中启用推送通知,请遵循这些 说明 并设置以下选项
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
运行 Movin' In API 单元测试
cd ./api
npm install
npm test
覆盖率
运行单元测试后,会自动构建覆盖率报告
./api/coverage
您也可以在 codecov 上查看覆盖率报告。
日志
所有 API 日志都写入 ./api/logs/all.log。
API 错误日志也写入 ./api/logs/error.log。
Using the Code
本节描述了 BookCars 的软件架构,包括 API、前端、移动应用和后端。
BookCars API 是一个 Node.js 服务器应用程序,使用 Express 公开 RESTful API,从而访问 BookCars MongoDB 数据库。
BookCars 前端是一个 React Web 应用程序,是用于预订汽车的主要 Web 界面。
Bookcars 后端是一个 React Web 应用程序,允许管理员和供应商管理汽车车队、预订和客户。
BookCars 移动应用是一个 React Native 应用程序,是用于预订汽车的主要移动应用。
一项关键的设计决策是使用 TypeScript 而不是 JavaScript,因为其具有诸多优势。TypeScript 提供了强类型、出色的工具支持和集成,从而产生了高质量、可扩展、更具可读性和可维护性的代码,易于调试和测试。
API
BookCars API 公开后端、前端和移动应用所需的所有 BookCars 功能。API 遵循 MVC 设计模式。JWT 用于身份验证。有一些功能需要身份验证,例如与管理汽车、预订和客户相关的功能,而其他功能(如检索未认证用户的地点和可用车辆)则不需要身份验证。
- ./api/src/models/ 文件夹包含 MongoDB 模型。
- ./api/src/routes/ 文件夹包含 Express 路由。
- ./api/src/controllers/ 文件夹包含控制器。
- ./api/src/middlewares/ 文件夹包含中间件。
- ./api/src/config/env.config.ts 包含配置和 TypeScript 类型定义。
- ./api/src/lang/ 文件夹包含本地化。
- ./api/src/app.ts 是加载路由的主服务器。
- ./api/index.ts 是 BookCars API 的主入口点。
index.ts
index.ts 是 BookCars API 的主入口点
import 'dotenv/config'
import process from 'node:process'
import fs from 'node:fs/promises'
import http from 'node:http'
import https from 'node:https'
import * as env from './config/env.config'
import * as DatabaseHelper from './common/DatabaseHelper'
import app from './app'
if (await DatabaseHelper.Connect(env.DB_DEBUG)) {
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 = { key: privateKey, cert: certificate }
server = https.createServer(credentials, app)
server.listen(env.PORT, () => {
console.log('HTTPS server is running on Port', env.PORT)
})
} else {
server = app.listen(env.PORT, () => {
console.log('HTTP server is running on Port', env.PORT)
})
}
const close = () => {
console.log('\nGracefully stopping...')
server.close(async () => {
console.log(`HTTP${env.HTTPS ? 'S' : ''} server closed`)
await DatabaseHelper.Close(true)
console.log('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。然后,它检查 HTTPS
环境变量是否设置为 true
,如果是,则使用 https 模块和提供的私钥及证书创建一个 HTTPS 服务器。否则,它使用 http 模块创建一个 HTTP 服务器。服务器监听 PORT
环境变量中指定的端口。
定义了 close
函数,用于在接收到终止信号时优雅地停止服务器。它关闭服务器和 MongoDB 连接,然后以状态码 0
退出进程。最后,它注册 close 函数,以便在进程接收到 SIGINT
、SIGTERM
或 SIGQUIT
信号时调用。
app.ts
app.ts 位于主服务器中
import express, { Express } from 'express'
import compression from 'compression'
import helmet from 'helmet'
import nocache from 'nocache'
import cookieParser from 'cookie-parser'
import strings from './config/app.config'
import * as env from './config/env.config'
import cors from './middlewares/cors'
import allowedMethods from './middlewares/allowedMethods'
import supplierRoutes from './routes/supplierRoutes'
import bookingRoutes from './routes/bookingRoutes'
import locationRoutes from './routes/locationRoutes'
import notificationRoutes from './routes/notificationRoutes'
import carRoutes from './routes/carRoutes'
import userRoutes from './routes/userRoutes'
import * as Helper from './common/Helper'
const app: Express = 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('/', supplierRoutes)
app.use('/', bookingRoutes)
app.use('/', locationRoutes)
app.use('/', notificationRoutes)
app.use('/', carRoutes)
app.use('/', userRoutes)
strings.setLanguage(env.DEFAULT_LANGUAGE)
Helper.mkdir(env.CDN_USERS)
Helper.mkdir(env.CDN_TEMP_USERS)
Helper.mkdir(env.CDN_CARS)
Helper.mkdir(env.CDN_TEMP_CARS)
export default app
首先,我们检索 MongoDB 连接字符串,然后与 BookCars MongoDB 数据库建立连接。然后,我们创建一个 Express app
并加载中间件,如 cors、compression、helmet 和 nocache。我们使用 helmet 中间件库设置了各种安全措施。我们还导入了应用程序不同部分的各种路由文件,例如 supplierRoutes
、bookingRoutes
、locationRoutes
、notificationRoutes
、carRoutes
和 userRoutes
。最后,我们加载 Express 路由并导出 app
。
路由
BookCars API 中有六个路由。每个路由都有自己的控制器,遵循 MVC 设计模式和 SOLID 原则。以下是主要路由
userRoutes
:提供与用户相关的 REST 函数supplierRoutes
:提供与供应商相关的 REST 函数locationRoutes
:提供与地点相关的 REST 函数carRoutes
:提供与汽车相关的 REST 函数bookingRoutes
:提供与预订相关的 REST 函数notificationRoutes
:提供与通知相关的 REST 函数
我们不打算逐一解释每个路由。我们将以 locationRoutes
为例,看看它是如何实现的
import express from 'express'
import routeNames from '../config/locationRoutes.config'
import authJwt from '../middlewares/authJwt'
import * as locationController from '../controllers/locationController'
const routes = express.Router()
routes
.route(routeNames.validate)
.post(authJwt.verifyToken, locationController.validate)
routes
.route(routeNames.create)
.post(authJwt.verifyToken, locationController.create)
routes
.route(routeNames.update)
.put(authJwt.verifyToken, locationController.update)
routes
.route(routeNames.delete)
.delete(authJwt.verifyToken, locationController.deleteLocation)
routes
.route(routeNames.getLocation)
.get(locationController.getLocation)
routes
.route(routeNames.getLocations)
.get(locationController.getLocations)
routes
.route(routeNames.checkLocation)
.get(authJwt.verifyToken, locationController.checkLocation)
export default routes
首先,我们创建一个 Express Router
。然后,我们使用路由名称、方法、中间件和控制器来创建路由。
routeNames
包含 locationRoutes
路由名称
export default {
validate: '/api/validate-location',
create: '/api/create-location',
update: '/api/update-location/:id',
delete: '/api/delete-location/:id',
getLocation: '/api/location/:id/:language',
getLocations: '/api/locations/:page/:size/:language',
checkLocation: '/api/check-location/:id',
}
locationController
包含有关地点的核心业务逻辑。由于控制器代码量很大,我们不打算展示所有源代码,但将以 create
和 getLocations
控制器函数为例。
以下是 Location
模型
import { Schema, model } from 'mongoose'
import * as env from '../config/env.config'
const locationSchema = new Schema<env.Location>(
{
values: {
type: [Schema.Types.ObjectId],
ref: 'LocationValue',
validate: (value: any): boolean => Array.isArray(value) && value.length > 1,
},
},
{
timestamps: true,
strict: true,
collection: 'Location',
},
)
const locationModel = model<env.Location>('Location', locationSchema)
locationModel.on('index', (err) => {
if (err) {
console.error('Location index error: %s', err)
} else {
console.info('Location indexing complete')
}
})
export default locationModel
以下是 env.Location
TypeScript 类型
export interface Location extends Document {
values: Types.ObjectId[]
name?: string
}
一个 Location
有多个值。每个语言一个。默认支持英语和法语。
以下是 LocationValue
模型
import { Schema, model } from 'mongoose'
import * as env from '../config/env.config'
const locationValueSchema = new Schema<env.LocationValue>(
{
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: 'LocationValue',
},
)
const locationValueModel = model<env.LocationValue>('LocationValue', locationValueSchema)
locationValueModel.on('index', (err) => {
if (err) {
console.error('LocationValue index error: %s', err)
} else {
console.info('LocationValue indexing complete')
}
})
export default locationValueModel
以下是 env.LocationValue
TypeScript 类型
export interface LocationValue extends Document {
language: string
value: string
}
一个 LocationValue
包含一个 language
代码(ISO 639-1)和一个字符串 value
。
以下是 create
控制器函数
export const create = async (req: Request, res: Response) => {
const body: bookcarsTypes.LocationName[] = req.body
const names = body
try {
const values = []
for (let i = 0; i < names.length; i++) {
const name = names[i]
const locationValue = new LocationValue({
language: name.language,
value: name.name,
})
await locationValue.save()
values.push(locationValue._id)
}
const location = new Location({ values })
await location.save()
return res.sendStatus(200)
} catch (err) {
console.error(`[location.create] ${strings.DB_ERROR} ${req.body}`, err)
return res.status(400).send(strings.DB_ERROR + err)
}
}
在此函数中,我们检索请求体,迭代请求体中提供的值(每个语言一个值),然后创建一个 LocationValue
。最后,我们根据创建的地点值来创建地点。
以下是 getLocations
控制器函数
export const getLocations = async (req: Request, res: Response) => {
try {
const page = Number.parseInt(req.params.page)
const size = Number.parseInt(req.params.size)
const language = req.params.language
const keyword = escapeStringRegexp(String(req.query.s || ''))
const options = 'i'
const locations = await Location.aggregate(
[
{
$lookup: {
from: 'LocationValue',
let: { values: '$values' },
pipeline: [
{
$match: {
$and: [
{ $expr: { $in: ['$_id', '$$values'] } },
{ $expr: { $eq: ['$language', language] } },
{
$expr: {
$regexMatch: {
input: '$value',
regex: keyword,
options,
},
},
},
],
},
},
],
as: 'value',
},
},
{ $unwind: { path: '$value', preserveNullAndEmptyArrays: false } },
{ $addFields: { name: '$value.value' } },
{
$facet: {
resultData: [{ $sort: { name: 1 } }, { $skip: (page - 1) * size }, { $limit: size }],
pageInfo: [
{
$count: 'totalRecords',
},
],
},
},
],
{ collation: { locale: env.DEFAULT_LANGUAGE, strength: 2 } },
)
return res.json(locations)
} catch (err) {
console.error(`[location.getLocations] ${strings.DB_ERROR} ${req.query.s}`, err)
return res.status(400).send(strings.DB_ERROR + err)
}
}
在此控制器函数中,我们使用 aggregate
MongoDB 函数和 facet
从数据库检索地点以实现分页。
以下是另一个简单的路由,notificationRoutes
import express from 'express'
import routeNames from '../config/notificationRoutes.config'
import authJwt from '../middlewares/authJwt'
import * as notificationController from '../controllers/notificationController'
const routes = express.Router()
routes
.route(routeNames.notificationCounter)
.get(authJwt.verifyToken, notificationController.notificationCounter)
routes
.route(routeNames.notify)
.post(authJwt.verifyToken, notificationController.notify)
routes
.route(routeNames.getNotifications)
.get(authJwt.verifyToken, notificationController.getNotifications)
routes
.route(routeNames.markAsRead)
.post(authJwt.verifyToken, notificationController.markAsRead)
routes
.route(routeNames.markAsUnRead)
.post(authJwt.verifyToken, notificationController.markAsUnRead)
routes
.route(routeNames.delete)
.post(authJwt.verifyToken, notificationController.deleteNotifications)
export default routes
以下是 Notification
模型
import { Schema, model } from 'mongoose'
import * as env from '../config/env.config'
const notificationSchema = new Schema<env.Notification>(
{
user: {
type: Schema.Types.ObjectId,
required: [true, "can't be blank"],
ref: 'User',
index: true,
},
message: {
type: String,
required: [true, "can't be blank"],
},
booking: {
type: Schema.Types.ObjectId,
ref: 'Booking',
},
isRead: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
strict: true,
collection: 'Notification',
},
)
const notificationModel = model<env.Notification>('Notification', notificationSchema)
notificationModel.on('index', (err) => {
if (err) {
console.error('Notification index error: %s', err)
} else {
console.info('Notification indexing complete')
}
})
export default notificationModel
以下是 env.Notification
TypeScript 类型
export interface Notification extends Document {
user: Types.ObjectId
message: string
booking: Types.ObjectId
isRead?: boolean
}
一个 Notification
由对 user
的引用、一个 message
、对 booking
的引用和一个 isRead
标志组成。
以下是 getNotifications
控制器函数
export const getNotifications = async (req: Request, res: Response) => {
const { userId: _userId, page: _page, size: _size } = req.params
try {
const userId = new mongoose.Types.ObjectId(_userId)
const page = Number.parseInt(_page)
const size = Number.parseInt(_size)
const notifications = await Notification.aggregate([
{ $match: { user: userId } },
{
$facet: {
resultData: [{ $sort: { createdAt: -1 } }, { $skip: (page - 1) * size }, { $limit: size }],
pageInfo: [
{
$count: 'totalRecords',
},
],
},
},
])
return res.json(notifications)
} catch (err) {
console.error(`[notification.getNotifications] ${strings.DB_ERROR} ${_userId}`, err)
return res.status(400).send(strings.DB_ERROR + err)
}
}
在这个简单的控制器函数中,我们使用 MongoDB aggregate
函数以及 page
和 size
参数来检索通知。
以下是 markAsRead
控制器函数
export const markAsRead = async (req: Request, res: Response) => {
try {
const body: { ids: string[] } = req.body
const { ids: _ids } = body
const ids = _ids.map((id) => new mongoose.Types.ObjectId(id))
const { userId: _userId } = req.params
const userId = new mongoose.Types.ObjectId(_userId)
const bulk = Notification.collection.initializeOrderedBulkOp()
const notifications = await Notification.find({
_id: { $in: ids },
isRead: false,
})
const length = notifications.length
bulk.find({ _id: { $in: ids }, isRead: false }).update({ $set: { isRead: true } })
const result = await bulk.execute()
if (result.modifiedCount !== length) {
console.error(`[notification.markAsRead] ${strings.DB_ERROR}`)
return res.status(400).send(strings.DB_ERROR)
}
const counter = await NotificationCounter.findOne({ user: userId })
if (!counter || typeof counter.count === 'undefined') {
return res.sendStatus(204)
}
counter.count -= length
await counter.save()
return res.sendStatus(200)
} catch (err) {
console.error(`[notification.markAsRead] ${strings.DB_ERROR}`, err)
return res.status(400).send(strings.DB_ERROR + err)
}
}
在此控制器函数中,我们批量更新通知并将其标记为已读。
前端
前端是一个使用 Node.js、React、MUI 和 TypeScript 构建的 Web 应用程序。通过前端,客户可以根据取车和还车地点及时间搜索可用车辆,选择车辆并进行结账。
- ./frontend/src/assets/ 文件夹包含 CSS 和图像。
- ./frontend/src/pages/ 文件夹包含 React 页面。
- ./frontend/src/components/ 文件夹包含 React 组件。
- ./frontend/src/services/ 包含 BookCars API 客户端服务。
- ./frontend/src/App.tsx 是包含路由的主 React 应用。
- ./frontend/src/index.tsx 是前端的主入口点。
TypeScript 类型定义在包 ./packages/bookcars-types 中定义。
App.tsx 是主 React 应用
import React, { lazy, Suspense } from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
const SignIn = lazy(() => import('./pages/SignIn'))
const SignUp = lazy(() => import('./pages/SignUp'))
const Activate = lazy(() => import('./pages/Activate'))
const ForgotPassword = lazy(() => import('./pages/ForgotPassword'))
const ResetPassword = lazy(() => import('./pages/ResetPassword'))
const Home = lazy(() => import('./pages/Home'))
const Cars = lazy(() => import('./pages/Cars'))
const Checkout = lazy(() => import('./pages/Checkout'))
const Bookings = lazy(() => import('./pages/Bookings'))
const Booking = lazy(() => import('./pages/Booking'))
const Settings = lazy(() => import('./pages/Settings'))
const Notifications = lazy(() => import('./pages/Notifications'))
const ToS = lazy(() => import('./pages/ToS'))
const About = lazy(() => import('./pages/About'))
const ChangePassword = lazy(() => import('./pages/ChangePassword'))
const Contact = lazy(() => import('./pages/Contact'))
const NoMatch = lazy(() => import('./pages/NoMatch'))
const App = () => (
<Router>
<div className="App">
<Suspense fallback={<></>}>
<Routes>
<Route path="/sign-in" element={<SignIn />} />
<Route path="/sign-up" element={<SignUp />} />
<Route path="/activate" element={<Activate />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/" element={<Home />} />
<Route path="/cars" element={<Cars />} />
<Route path="/checkout" element={<Checkout />} />
<Route path="/bookings" element={<Bookings />} />
<Route path="/booking" element={<Booking />} />
<Route path="/settings" element={<Settings />} />
<Route path="/notifications" element={<Notifications />} />
<Route path="/change-password" element={<ChangePassword />} />
<Route path="/about" element={<About />} />
<Route path="/tos" element={<ToS />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NoMatch />} />
</Routes>
</Suspense>
</div>
</Router>
)
export default App
我们使用 React 懒加载来加载每个路由。
我们不会介绍前端的每个页面,但您可以打开源代码查看每个页面。
移动应用
BookCars 提供原生 Android 和 iOS 移动应用。该移动应用使用 React Native、Expo 和 TypeScript 构建。与前端一样,移动应用允许客户根据取车和还车地点及时间搜索可用车辆,选择车辆并进行结账。
如果客户的预订从后端更新,客户会收到推送通知。推送通知使用 Node.js、Expo Server SDK 和 Firebase 构建。
- ./mobile/assets/ 文件夹包含图像。
- ./mobile/screens/ 文件夹包含主要的 React Native 屏幕。
- ./mobile/components/ 文件夹包含 React Native 组件。
- ./mobile/services/ 包含 BookCars API 客户端服务。
- ./mobile/App.tsx 是主 React Native 应用。
TypeScript 类型定义在
- ./mobile/types/index.d.ts
- ./mobile/types/env.d.ts
- ./mobile/miscellaneous/bookcarsTypes.ts
./mobile/types/ 在 ./mobile/tsconfig.json 中按以下方式加载
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"typeRoots": [
"./types"
]
}
}
App.tsx 是主 React Native 应用
import 'react-native-gesture-handler'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { RootSiblingParent } from 'react-native-root-siblings'
import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native'
import { StatusBar as ExpoStatusBar } from 'expo-status-bar'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { Provider } from 'react-native-paper'
import * as SplashScreen from 'expo-splash-screen'
import * as Notifications from 'expo-notifications'
import DrawerNavigator from './components/DrawerNavigator'
import * as Helper from './common/Helper'
import * as NotificationService from './services/NotificationService'
import * as UserService from './services/UserService'
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
})
// Prevent native splash screen from autohiding before App component declaration
SplashScreen.preventAutoHideAsync()
.then((result) => console.log(`SplashScreen.preventAutoHideAsync() succeeded: ${result}`))
.catch(console.warn) // it's good to explicitly catch and inspect any error
const App = () => {
const [appIsReady, setAppIsReady] = useState(false)
const responseListener = useRef<Notifications.Subscription>()
const navigationRef = useRef<NavigationContainerRef<StackParams>>(null)
useEffect(() => {
const register = async () => {
const loggedIn = await UserService.loggedIn()
if (loggedIn) {
const currentUser = await UserService.getCurrentUser()
if (currentUser?._id) {
await Helper.registerPushToken(currentUser._id)
} else {
Helper.error()
}
}
}
// Register push notifiations token
register()
// This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed)
responseListener.current = Notifications.addNotificationResponseReceivedListener(async (response) => {
try {
if (navigationRef.current) {
const { data } = response.notification.request.content
if (data.booking) {
if (data.user && data.notification) {
await NotificationService.markAsRead(data.user, [data.notification])
}
navigationRef.current.navigate('Booking', { id: data.booking })
} else {
navigationRef.current.navigate('Notifications', {})
}
}
} catch (err) {
Helper.error(err, false)
}
})
return () => {
if (responseListener.current) {
Notifications.removeNotificationSubscription(responseListener.current)
}
}
}, [])
setTimeout(() => {
setAppIsReady(true)
}, 500)
const onReady = useCallback(async () => {
if (appIsReady) {
// This tells the splash screen to hide immediately! If we call this after
// `setAppIsReady`, then we may see a blank screen while the app is
// loading its initial state and rendering its first pixels. So instead,
// we hide the splash screen once we know the root view has already
// performed layout.
await SplashScreen.hideAsync()
}
}, [appIsReady])
if (!appIsReady) {
return null
}
return (
<SafeAreaProvider>
<Provider>
<RootSiblingParent>
<NavigationContainer ref={navigationRef} onReady={onReady}>
<ExpoStatusBar style="light" backgroundColor="rgba(0, 0, 0, .9)" />
<DrawerNavigator />
</NavigationContainer>
</RootSiblingParent>
</Provider>
</SafeAreaProvider>
)
}
export default App
我们不会介绍移动应用的每个屏幕,但您可以打开源代码查看每个屏幕。
后端
后端是一个使用 Node.js、React、MUI 和 TypeScript 构建的 Web 应用程序。通过后端,管理员可以创建和管理供应商、汽车、地点、客户和预订。当从后端创建新供应商时,他们将收到一封电子邮件,提示他们创建账户,以便访问后端并管理其汽车车队和预订。
- ./backend/assets/ 文件夹包含 CSS 和图像。
- ./backend/pages/ 文件夹包含 React 页面。
- ./backend/components/ 文件夹包含 React 组件。
- ./backend/services/ 包含 BookCars API 客户端服务。
- ./backend/App.tsx 是包含路由的主 React 应用。
- ./backend/index.tsx 是后端的入口点。
TypeScript 类型定义在包 ./packages/bookcars-types 中定义。
后端的 App.tsx 的逻辑与前端的 App.tsx 类似。
我们不会介绍后端的每个页面,但您可以打开源代码查看每个页面。
关注点
使用 React Native 和 Expo 构建移动应用非常简单。Expo 使 React Native 的移动开发变得非常简单。
在后端、前端和移动开发中使用相同的语言(TypeScript)非常方便。
TypeScript 是一种非常有趣的语言,具有许多优点。通过为 JavaScript 添加静态类型,我们可以避免许多错误,并生成高质量、可扩展、更具可读性和可维护性的代码,易于调试和测试。
就是这样!希望您喜欢阅读这篇文章。