目录
介绍
BookCars是一个面向供应商的汽车租赁平台,其后端用于管理车队和预订,前端和移动应用程序用于租车。
BookCars是用户友好的,直接的,对XSS, XST, CSRF和MITM安全,并且精心制作。
BookCars旨在与多个供应商一起工作。每个供应商都可以在后端管理自己的车队和预订。BookCars也可以只与一个供应商合作。
从后台,管理员可以创建和管理供应商、汽车、地点、客户和预订。
当创建新的供应商时,他们将收到一封电子邮件,提示他们创建一个帐户,以便访问后端并管理他们的车队和预订。
客户可以通过前端或移动应用程序注册,根据接送点和时间搜索可用车辆,选择车辆并完成结账过程。
一个关键的设计决定是使用TypeScript而不是JavaScript,因为它有很多优点。TypeScript提供了强大的类型、工具和集成,从而产生高质量、可扩展、更易读和可维护的代码,并且易于调试和测试。
在本文中,您将了解如何制作BookCars,包括对源代码和软件体系结构的主要部分的描述、如何部署它以及如何运行源代码。但在我们深入研究之前,我们将从平台的快速概述开始。
特征
- 供应商管理
- 准备一个或多个供应商
- 车队管理
- 预订管理
- 客户端管理
- 多种支付方式(信用卡、延期付款)
- 多语言支持(英语、法语)
- 多个分页选项(带有“下一步”和“上一步”按钮的经典分页,无限滚动)
- 响应式后端和前端
- 适用于安卓和iOS的原生移动应用程序
- 推送通知
- 防止XSS,XST,CSRF和MITM
在本文中,您将了解 BookCars 是如何制作的,包括对源代码和软件架构的主要部分的描述、如何部署它以及如何运行源代码。但在深入研究之前,我们将从平台的快速概述开始。
先决条件
- TypeScript
- Node.js
- 表达
- MognoDB
- React
- Expo
- JWT
- MVC
- Docker
- Git
快速概览
在本节中,您将看到前端、后端和移动应用程序主页的快速概览。
前端
从前端,用户可以搜索可用的汽车,选择汽车并结帐。
下面是前端的主页,用户可以在其中选择接送地点、日期和时间预订以及搜索可用的汽车。
以下是主页的搜索结果,用户可以在其中选择租车。
下面是结帐页面,用户可以在其中设置租赁选项和结帐。如果用户未注册,他可以同时结帐和注册。如果他尚未注册,他将收到一封确认和激活电子邮件,以设置密码。
下面是登录页面。
下面是注册页面。
下面是客户可以查看和管理其预订的页面。
下面是客户可以看到预订详情的页面。
下面是客户可以查看和管理其通知的页面。
下面是客户可以管理其设置的页面。
下面是客户可以更改密码的页面。
就是这样!这是前端的主要页面。
移动应用
从移动应用程序中,用户可以搜索可用的汽车,选择汽车并结帐。
如果用户的预订状态已更新,用户还可以接收推送通知。
以下是移动应用程序的主页,用户可以在其中选择接送地点、日期和时间预订以及搜索可用汽车。
以下是主页的搜索结果,用户可以在其中选择租车和结账。
以下是登录和注册页面。
以下是用户可以查看和管理其预订的页面。
以下是户可以更新其个人资料信息,更改密码并查看其通知的页面。
移动应用程序的主页就是这样。
后端
BookCars以供应商为导向。这意味着有三种类型的用户:
- Admin:他具有对后端的完全访问权限。他无所不能。
- Supplier:他限制了对后端的访问。他只能管理他的汽车和预订。
- User:他只能访问前端和移动应用程序。他无法访问后端。
BookCars旨在与多个供应商合作。每个供应商都可以从后端管理他的车队和预订。BookCars也可以只与一个供应商合作。
在后端,管理员用户可以创建和管理供应商、汽车、位置、用户和预订。
当管理员用户创建新供应商时,供应商将收到一封自动电子邮件,用于创建其帐户以访问后端,以便他可以管理他的车队和预订。
下面是后端的登录页面。
下面是后端的仪表板页面,管理员和供应商可以在其中查看和管理预订。
下面是显示车队并可以管理的页面。
下面是管理员和供应商可以通过提供图像和汽车信息来创建新汽车的页面。
下面是管理员和供应商可以编辑汽车的页面。
下面是管理员可以管理平台用户的页面。
以下是编辑预订的页面。
就是这样!这是后端的主页。
背景
BookCars背后的基本思想非常简单:
- 后端:管理员从中创建新供应商。每个供应商都会收到一封自动电子邮件,以激活他的帐户并可以访问后端,以便他可以管理他的车队和预订。
- 前端和移动应用程序:用户可以根据某些参数(例如接送地点以及日期和时间预订)查看可用的汽车。然后,用户可以继续结帐以预订他们的汽车。
后端,前端和移动应用程序依赖于BookCars API,这是一个RESTful API,它公开了访问BookCars数据库的功能。
安装
BookCars是跨平台的,可以在Windows、Linux和macOS上运行和安装。
以下是Linux上的安装说明。
先决条件
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、克隆BookCars存储库:
cd /opt
sudo git clone https://github.com/aelassas/bookcars.git
2、添加权限:
sudo chown -R $USER:$USER /opt/bookcars
sudo chmod -R +x /opt/bookcars/__scripts
3、创建部署快捷方式:
sudo ln -s /opt/bookcars/__scripts/bc-deploy.sh /usr/local/bin/bc-deploy
4、创建BookCars服务:
sudo cp /opt/bookcars/__services/bookcars.service /etc/systemd/system
sudo systemctl enable bookcars.service
5、创建/opt/bookcars/api/.env 文件:
NODE_ENV=production
BC_PORT=4002
BC_HTTPS=false
BC_PRIVATE_KEY=/etc/ssl/bookcars.com.key
BC_CERTIFICATE=/etc/ssl/bookcars.com.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.com.crt
BC_DB_SSL_CA=/etc/ssl/bookcars.com.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.com
BC_ADMIN_EMAIL=admin@bookcars.com
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_DEFAULT_LANGUAGE=en
BC_BACKEND_HOST=http://localhost:3001/
BC_FRONTEND_HOST=http://localhost/
BC_MINIMUM_AGE=21
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
您需要配置以下选项:
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=in-v3.mailjet.com
BC_SMTP_PORT=587
BC_SMTP_USER=USER
BC_SMTP_PASS=PASSWORD
BC_SMTP_FROM=admin@bookcars.com
BC_ADMIN_EMAIL=admin@bookcars.com
BC_BACKEND_HOST=http://localhost:3001/
BC_FRONTEND_HOST=http://localhost/
将BC_DB_URI
中的PASSWORD
替换为MongoDB密码, JWT_SECRET
替换为secret token。最后,设置SMTP选项。SMTP选项是注册所必需的。您可以使用mailjet、brevo、sendgrid或任何其他事务性电子邮件提供商。
COOKIE_SECRET
和JWT_SECRET
至少应该有32个字符长,但越长越好。您可以使用在线密码生成器并将密码长度设置为32或更长。
如果要在本地进行测试或将其替换为IP、主机名或FQDN,请离开localhost
。
如果要在移动应用程序中启用推送通知,请按照以下说明操作并设置以下选项:
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
如果要启用HTTPS,需要设置以下选项:
BC_HTTPS = true
BC_PRIVATE_KEY=/etc/ssl/bookcars.com.key
BC_CERTIFICATE=/etc/ssl/bookcars.com.crt
6、创建/opt/bookcars/backend/.env 文件:
PORT=3001
REACT_APP_NODE_ENV=production
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_DEFAULT_LANGUAGE=en
REACT_APP_BC_PAGE_SIZE=30
REACT_APP_BC_CARS_PAGE_SIZE=15
REACT_APP_BC_BOOKINGS_PAGE_SIZE=20
REACT_APP_BC_BOOKINGS_MOBILE_PAGE_SIZE=10
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_TEMP_USERS=http://localhost/cdn/bookcars/temp/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
REACT_APP_BC_CDN_TEMP_CARS=http://localhost/cdn/bookcars/temp/cars
REACT_APP_BC_COMPANY_IMAGE_WIDTH=60
REACT_APP_BC_COMPANY_IMAGE_HEIGHT=30
REACT_APP_BC_CAR_IMAGE_WIDTH=300
REACT_APP_BC_CAR_IMAGE_HEIGHT=200
REACT_APP_BC_MINIMUM_AGE=21
REACT_APP_BC_PAGINATION_MODE=classic
您需要配置以下选项:
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_TEMP_USERS=http://localhost/cdn/bookcars/temp/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
REACT_APP_BC_CDN_TEMP_CARS=http://localhost/cdn/bookcars/temp/cars
如果要在本地进行测试或将其替换为IP、主机名或FQDN,请离开localhost
。
REACT_APP_BC_PAGINATION_MODE
:您可以在classic
或infinite_scroll
之间进行选择。此选项缺省为classic
。如果您选择classic
,您将获得一个经典的分页,在桌面上带有下一个和上一个按钮,在移动设备上无限滚动。如果您选择infinite_scroll
,您将在桌面和移动设备上获得无限滚动。
7、创建/opt/bookcars/frontend/.env 文件:
REACT_APP_NODE_ENV=production
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_RECAPTCHA_ENABLED=false
REACT_APP_BC_RECAPTCHA_SITE_KEY=GOOGLE_RECAPTCHA_SITE_KEY
REACT_APP_BC_DEFAULT_LANGUAGE=en
REACT_APP_BC_PAGE_SIZE=30
REACT_APP_BC_CARS_PAGE_SIZE=15
REACT_APP_BC_BOOKINGS_PAGE_SIZE=20
REACT_APP_BC_BOOKINGS_MOBILE_PAGE_SIZE=10
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
REACT_APP_BC_COMPANY_IMAGE_WIDTH=60
REACT_APP_BC_COMPANY_IMAGE_HEIGHT=30
REACT_APP_BC_CAR_IMAGE_WIDTH=300
REACT_APP_BC_CAR_IMAGE_HEIGHT=200
REACT_APP_BC_MINIMUM_AGE=21
REACT_APP_BC_PAGINATION_MODE=classic
您需要配置以下选项:
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
如果要在本地进行测试或将其替换为IP、主机名或FQDN,请离开localhost
。
默认情况下,reCAPTCHA处于禁用状态。如果要启用它,则需要设置REACT_APP_BC_RECAPTCHA_ENABLED
为true
和REACT_APP_BC_RECAPTCHA_SITE_KEY
为Google reCAPTCHA站点密钥。
8、如果要运行或构建移动应用程序,则需要创建 mobile/.env:
BC_API_HOST=https://bookcars.com:4002
BC_DEFAULT_LANGUAGE=en
BC_PAGE_SIZE=20
BC_CARS_PAGE_SIZE=8
BC_BOOKINGS_PAGE_SIZE=8
BC_CDN_USERS=https://bookcars.com/cdn/bookcars/users
BC_CDN_CARS=https://bookcars.com/cdn/bookcars/cars
BC_COMPANY_IMAGE_WIDTH=60
BC_COMPANY_IMAGE_HEIGHT=30
BC_CAR_IMAGE_WIDTH=300
BC_CAR_IMAGE_HEIGHT=200
BC_MINIMUM_AGE=21
您需要配置以下选项:
BC_API_HOST=https://bookcars.com:4002
BC_CDN_USERS=https://bookcars.com/cdn/bookcars/users
BC_CDN_CARS=https://bookcars.com/cdn/bookcars/cars
替换localhost
为IP、主机名或FQDN。
9、配置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.com.key;
#ssl_certificate /etc/ssl/bookcars.com.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.com.key
#ssl_certificate /etc/ssl/bookcars.com.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.com.key;
#ssl_certificate /etc/ssl/bookcars.com.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
文件夹,并向在/var/www/cdn/bookcars
上运行bookcars服务的用户添加完全访问权限。
如果要启用SSL,请取消注释并设置以下行:
#listen 3001 http2 ssl default_server
#ssl_certificate_key /etc/ssl/bookcars.com.key
#ssl_certificate /etc/ssl/bookcars.com.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
10、启用防火墙并打开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
11、启动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
12、部署BookCars:
bc-deploy all
BookCars后端可在端口3001上访问,前端可在端口80上访问。
13、如果您不想使用演示数据库,请导航到hostname:3001/sign-up来创建管理员
14、打开 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
如果您想更改货币,请按照以下说明操作。
Docker
BookCars可以在Linux上的Docker容器和适用于Windows或Mac的Docker Desktop中运行。
Docker镜像
本节介绍如何构建BookCars Docker镜像并在Docker容器中运行它。
1、克隆BookCars存储库:
git clone https://github.com/aelassas/bookcars.git
2、创建包含以下内容的./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=in-v3.mailjet.com
BC_SMTP_PORT=587
BC_SMTP_USER=USER
BC_SMTP_PASS=PASSWORD
BC_SMTP_FROM=admin@bookcars.ma
BC_ADMIN_EMAIL=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=http://localhost:3001/
BC_FRONTEND_HOST=http://localhost/
BC_MINIMUM_AGE=21
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
设置以下选项:
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=in-v3.mailjet.com
BC_SMTP_PORT=587
BC_SMTP_USER=USER
BC_SMTP_PASS=PASSWORD
BC_SMTP_FROM=admin@bookcars.com
BC_ADMIN_EMAIL=admin@bookcars.com
BC_BACKEND_HOST=http://localhost:3001/
BC_FRONTEND_HOST=http://localhost/
替换PASSWORDBC_DB_URI
为您选择的强密码和JWT_SECRET
为秘密令牌。最后,设置SMTP选项。SMTP选项是注册所必需的。您可以使用mailjet、brevo、sendgrid或任何其他事务性电子邮件提供商。
COOKIE_SECRET
和JWT_SECRET
至少应该有32个字符长,但越长越好。您可以使用在线密码生成器并将密码长度设置为32或更长。
如果要在本地进行测试,请离开localhost
,或将其替换为IP、主机名或FQDN。
如果要在移动应用程序中启用推送通知,请按照以下说明操作并设置以下选项:
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
3、创建包含以下内容的./backend/.env.docker 文件:
REACT_APP_NODE_ENV=production
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_DEFAULT_LANGUAGE=en
REACT_APP_BC_PAGE_SIZE=30
REACT_APP_BC_CARS_PAGE_SIZE=15
REACT_APP_BC_BOOKINGS_PAGE_SIZE=20
REACT_APP_BC_BOOKINGS_MOBILE_PAGE_SIZE=10
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_TEMP_USERS=http://localhost/cdn/bookcars/temp/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
REACT_APP_BC_CDN_TEMP_CARS=http://localhost/cdn/bookcars/temp/cars
REACT_APP_BC_COMPANY_IMAGE_WIDTH=60
REACT_APP_BC_COMPANY_IMAGE_HEIGHT=30
REACT_APP_BC_CAR_IMAGE_WIDTH=300
REACT_APP_BC_CAR_IMAGE_HEIGHT=200
REACT_APP_BC_MINIMUM_AGE=21
REACT_APP_BC_PAGINATION_MODE=classic
设置以下选项:
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_TEMP_USERS=http://localhost/cdn/bookcars/temp/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
REACT_APP_BC_CDN_TEMP_CARS=http://localhost/cdn/bookcars/temp/cars
如果要在本地进行测试,请离开localhost
,或将其替换为IP、主机名或FQDN。
如果要更改分页模式,请更改REACT_APP_BC_PAGINATION_MODE
选项。您可以在classic
或infinite_scroll
之间进行选择。此选项缺省为classic
。如果您选择classic
,您将获得一个经典的分页,在桌面上带有下一个和上一个按钮,在移动设备上无限滚动。如果您选择infinite_scroll
,您将在桌面和移动设备上获得无限滚动。
4、创建包含以下内容的./frontend/.env.docker 文件:
REACT_APP_NODE_ENV=production
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_RECAPTCHA_ENABLED=false
REACT_APP_BC_DEFAULT_LANGUAGE=en
REACT_APP_BC_PAGE_SIZE=30
REACT_APP_BC_CARS_PAGE_SIZE=15
REACT_APP_BC_BOOKINGS_PAGE_SIZE=20
REACT_APP_BC_BOOKINGS_MOBILE_PAGE_SIZE=10
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
REACT_APP_BC_COMPANY_IMAGE_WIDTH=60
REACT_APP_BC_COMPANY_IMAGE_HEIGHT=30
REACT_APP_BC_CAR_IMAGE_WIDTH=300
REACT_APP_BC_CAR_IMAGE_HEIGHT=200
REACT_APP_BC_MINIMUM_AGE=21
REACT_APP_BC_PAGINATION_MODE=classic
设置以下选项:
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
如果要在本地进行测试,请离开localhost
,或将其替换为IP、主机名或FQDN。
如果要更改分页模式,请更改REACT_APP_BC_PAGINATION_MODE
选项。
默认情况下,reCAPTCHA在前端处于禁用状态。如果要启用它,则必须设置REACT_APP_BC_RECAPTCHA_ENABLED
为true
和REACT_APP_BC_RECAPTCHA_SITE_KEY
为Google reCAPTCHA站点密钥。
5、打开./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:
替换PASSWORD
为您在./api/.env.docker 中的BC_DB_URI
中设置的密码。
6、构建并运行Docker镜像:
sudo docker compose up
要在后台运行撰写,请使用以下命令添加-d
选项:
sudo docker compose up -d
如果要重新生成,请使用以下命令:
sudo docker compose build --no-cache
sudo docker compose up
如果要检查容器的日志以进行故障排除,请使用以下命令:
sudo docker compose logs
如果要停止和删除由组合创建的容器、数据卷、网络和镜像,并从干净的镜像开始,请使用以下命令:
sudo docker compose down --volumes
sudo docker compose build --no-cache
sudo docker compose up
使用上述命令时要小心。MongoDB数据库和cdn内容将被删除。因此,请确保在继续之前备份数据库和cdn内容。
就是这样!BookCars后端可从http://<hostname>:3001访问,BookCars前端可从http://<hostname>访问。
如果您是第一次运行BookCars,您将从一个空数据库开始。因此,您必须通过填写from 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将在./docker-compose.yml 中加载为/etc/ssl/bookcars.crt。
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=http://localhost:3001/
BC_FRONTEND_HOST=http://localhost/
http://localhost
替换为https://<fqdn>
。
后端
对于后端,请在./backend/.env.docker 中更新以下选项:
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_TEMP_USERS=http://localhost/cdn/bookcars/temp/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
REACT_APP_BC_CDN_TEMP_CARS=http://localhost/cdn/bookcars/temp/cars
http://localhost
替换为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 中更新以下选项:
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
http://localhost
替换为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
更改货币
要更改货币,请按照以下说明操作:
前端
打开 frontend/src/lang/common.ts 并更改en.CURRENCY
和fr.CURRENCY
。
后端
打开 backend/src/lang/common.ts 并更改en.CURRENCY
和fr.CURRENCY
。
移动应用
打开 mobile/lang/en.ts 并更改en.CURRENCY
,en.CAR_CURRENCY
。
打开 mobile/lang/fr.ts 并更改fr.CURRENCY
,fr.CAR_CURRENCY
。
添加新语言
要添加新语言,请按以下步骤操作:
后端和前端
- 在
LANGUAGES
常量的src/config/env.config.ts中添加新语言 ISO 639-1代码及其标签。 - 在src/lang/*.ts中添加翻译。
移动应用
- 在
LANGUAGES
常量的config/env.config.ts中添加新语言 ISO 639-1代码及其标签。 - 在 lang 文件夹中创建一个新文件<ISO 639-1 code>.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密码。
将cdn文件夹的内容复制到Web服务器上,以便可以通过 http://localhost/cdn/bookcars/ 访问文件
CDN文件夹包含以下文件夹:
- users:此文件夹包含用户的头像和供应商的图片。
- cars:此文件夹包含汽车的图像。
- temp:此文件夹包含temporay文件。
如果您想从源代码运行BookCars或在不使用Docker的情况下将其安装在Windows或Linux上,请按以下步骤操作:
- 在Windows上,安装IIS并将cdn文件夹的内容复制到C:\inetpub\wwwroot\cdn\bookcars中。最后,向在C:\inetpub\wwwroot\cdn\bookcars上运行BookCars API的用户添加完全访问权限。
- 在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;
}
}
最后,向在/var/www/cdn/bookcars上运行BookCars API的用户添加完全访问权限。
后端凭据:
- 用户名:admin@bookcars.ma
- 密码:B00kC4r5
前端和移动应用凭据:
- 用户名: jdoe@bookcars.ma
- 密码:B00kC4r5
Docker
要在Docker容器中恢复BookCars演示数据库,请按以下步骤操作:
1、确保端口80、3001、4002和27017未被任何应用程序使用。
2、在本地计算机上下载并安装 MongoDB命令行数据库工具。
3、将MongoDB命令行数据库工具文件夹添加到本地计算机中的Path
环境变量。
4、将bookcars-db.zip下载到本地计算机并解压缩。
5、运行compose:
docker compose up
6、转到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密码
7、使用以下命令获取API Docker容器名称:
docker container ls
名称应如下所示:src-api-1
8、转到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容器名称。
9、转到后端 http://localhost:3001 并使用以下凭据登录:
用户名:admin@bookcars.ma
密码:B00kC4r5
10、转到前端 http://localhost 并使用以下凭据登录:
用户名:jdoe@bookcars.ma
密码:B00kC4r5
构建移动应用
先决条件
要构建BookCars移动应用程序,您需要在计算机上安装以下工具:
使用以下命令安装eas-cli:
npm i -g eas-cli
配置
- 您需要下载google-services.json文件并将其放在./mobile根目录下以发送推送通知。否则,移动应用将无法生成。不要忘记按照文档中所述 expo.dev>凭据>服务凭据> Google Cloud Messaging Token 中设置Firebase Server密钥。
- 如果您没有Expo 帐户,则需要创建一个帐户来构建BookCars移动应用程序。
- 转到 expo.dev,单击Pojects,然后单击Create a Project。将BookCars设置为项目名称,然后单击Create。
- 转到BookCars项目并复制项目ID。打开./mobile/app.json并将项目ID粘贴到
extra.eas.projectId
。 - 从expo.dev创建Expo访问令牌(帐户设置>访问令牌)并设置 api/.env
BC_EXPO_ACCESS_TOKEN
设置:
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
- 创建包含以下内容的mobile/.env文件:
BC_API_HOST=https://bookcars.com:4002
BC_DEFAULT_LANGUAGE=en
BC_PAGE_SIZE=20
BC_CARS_PAGE_SIZE=8
BC_BOOKINGS_PAGE_SIZE=8
BC_CDN_USERS=https://bookcars.com/cdn/bookcars/users
BC_CDN_CARS=https://bookcars.com/cdn/bookcars/cars
BC_COMPANY_IMAGE_WIDTH=60
BC_COMPANY_IMAGE_HEIGHT=30
BC_CAR_IMAGE_WIDTH=300
BC_CAR_IMAGE_HEIGHT=200
BC_MINIMUM_AGE=21
您需要配置以下选项:
BC_API_HOST=https://bookcars.com:4002
BC_CDN_USERS=https://bookcars.com/cdn/bookcars/users
BC_CDN_CARS=https://bookcars.com/cdn/bookcars/cars
替换https://bookcars.com
为IP、主机名或FQDN。
生产构建
如果您想在生产中使用BookCars移动应用程序,您应该在BookCars API中使用HTTPS,并通过删除plugins
部分中的"./plugins/usesCleartextTraffic"
行来禁用./mobile/app.json中的usesCleartextTraffic
expo插件。
说明
- 将源克隆到您的计算机:
git clone https://github.com/aelassas/bookcars.git
- 转到移动文件夹:
cd ./mobile
- 运行以下命令:
npm install
Android
EAS构建
若要使用EAS Build托管服务生成BookCars Android应用,请运行以下命令:
npm run build:android
本地构建
本地构建需要macOS或Linux。对于Windows,应使用EAS Builds。
您需要安装Android SDK、JDK 11以及设置ANDROID_HOME
和JAVA_HOME
环境变量。然后,运行以下命令:
npm run build:android:local
iOS
EAS构建
若要生成BookCars iOS应用程序,请运行以下命令:
npm run build:ios
本地构建
您需要在macOS上安装fastlane和CocoaPods。
若要在本地生成BookCars iOS应用,请运行以下命令:
npm run build:ios:local
从源代码运行
以下是从源代码运行BookCars的说明。
先决条件
- 在Windows,MongoDB和mongosh上安装git,Node.js,NGINX或IIS。
- 配置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、将BookCars 源代码下载到您的机器上。
2、添加 api/.env 文件:
NODE_ENV=development
BC_PORT=4002
BC_HTTPS=false
BC_PRIVATE_KEY=/etc/ssl/bookcars.com.key
BC_CERTIFICATE=/etc/ssl/bookcars.com.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.com.crt
BC_DB_SSL_CA=/etc/ssl/bookcars.com.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.com
BC_ADMIN_EMAIL=admin@bookcars.com
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=http://localhost:3001/
BC_FRONTEND_HOST=http://localhost:3002/
BC_MINIMUM_AGE=21
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
在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
为在C:\inetpub\wwwroot\cdn\bookcars上运行BookCars API的用户添加完全访问权限。
您需要配置以下选项:
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.com
BC_ADMIN_EMAIL=admin@bookcars.com
替换BC_DB_URI
中的PASSWORD
为您选择的强密码和JWT_SECRET
为秘密令牌。最后,设置SMTP选项。SMTP选项是注册所必需的。您可以使用mailjet、brevo、sendgrid或任何其他事务性电子邮件提供商。
COOKIE_SECRET
和JWT_SECRET
至少应该有32个字符长,但越长越好。您可以使用在线密码生成器并将密码长度设置为32或更长。
如果要在移动应用程序中启用推送通知,请按照以下说明操作并设置以下选项:
BC_EXPO_ACCESS_TOKEN=EXPO_ACCESS_TOKEN
要运行api,请使用以下命令:
cd ./api
npm install
npm run dev
3、添加 backend/.env 文件:
PORT=3001
REACT_APP_NODE_ENV=development
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_DEFAULT_LANGUAGE=en
REACT_APP_BC_PAGE_SIZE=30
REACT_APP_BC_CARS_PAGE_SIZE=15
REACT_APP_BC_BOOKINGS_PAGE_SIZE=20
REACT_APP_BC_BOOKINGS_MOBILE_PAGE_SIZE=10
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_TEMP_USERS=http://localhost/cdn/bookcars/temp/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
REACT_APP_BC_CDN_TEMP_CARS=http://localhost/cdn/bookcars/temp/cars
REACT_APP_BC_COMPANY_IMAGE_WIDTH=60
REACT_APP_BC_COMPANY_IMAGE_HEIGHT=30
REACT_APP_BC_CAR_IMAGE_WIDTH=300
REACT_APP_BC_CAR_IMAGE_HEIGHT=200
REACT_APP_BC_MINIMUM_AGE=21
REACT_APP_BC_PAGINATION_MODE=classic
若要运行后端,请使用以下命令:
cd ./backend
npm install
npm start
4、添加 frontend/.env 文件:
PORT=3002
REACT_APP_NODE_ENV=development
REACT_APP_BC_API_HOST=http://localhost:4002
REACT_APP_BC_RECAPTCHA_ENABLED=false
REACT_APP_BC_RECAPTCHA_SITE_KEY=GOOGLE_RECAPTCHA_SITE_KEY
REACT_APP_BC_DEFAULT_LANGUAGE=en
REACT_APP_BC_PAGE_SIZE=30
REACT_APP_BC_CARS_PAGE_SIZE=15
REACT_APP_BC_BOOKINGS_PAGE_SIZE=20
REACT_APP_BC_BOOKINGS_MOBILE_PAGE_SIZE=10
REACT_APP_BC_CDN_USERS=http://localhost/cdn/bookcars/users
REACT_APP_BC_CDN_CARS=http://localhost/cdn/bookcars/cars
REACT_APP_BC_COMPANY_IMAGE_WIDTH=60
REACT_APP_BC_COMPANY_IMAGE_HEIGHT=30
REACT_APP_BC_CAR_IMAGE_WIDTH=300
REACT_APP_BC_CAR_IMAGE_HEIGHT=200
REACT_APP_BC_MINIMUM_AGE=21
REACT_APP_BC_PAGINATION_MODE=classic
默认情况下,reCAPTCHA处于禁用状态。如果要启用它,则必须设置REACT_APP_BC_RECAPTCHA_ENABLED
为true
和REACT_APP_BC_RECAPTCHA_SITE_KEY
为Google reCAPTCHA 站点密钥。
要运行前端,请使用以下命令:
cd ./frontend
npm install
npm start
5、如果要运行移动应用程序,则需要添加mobile/.env:
BC_API_HOST=https://bookcars.com:4002
BC_DEFAULT_LANGUAGE=en
BC_PAGE_SIZE=20
BC_CARS_PAGE_SIZE=8
BC_BOOKINGS_PAGE_SIZE=8
BC_CDN_USERS=https://bookcars.com/cdn/bookcars/users
BC_CDN_CARS=https://bookcars.com/cdn/bookcars/cars
BC_COMPANY_IMAGE_WIDTH=60
BC_COMPANY_IMAGE_HEIGHT=30
BC_CAR_IMAGE_WIDTH=300
BC_CAR_IMAGE_HEIGHT=200
BC_MINIMUM_AGE=21
您需要配置以下选项:
BC_API_HOST=https://bookcars.com:4002
BC_CDN_USERS=https://bookcars.com/cdn/bookcars/users
BC_CDN_CARS=https://bookcars.com/cdn/bookcars/cars
您需要替换https://bookcars.com
为IP或主机名。
6、配置 http://localhost/cdn
- 在Windows上,安装IIS,创建 C:\inetpub\wwwroot\cdn\bookcars文件夹,并为在bookcars C:\inetpub\wwwroot\cdn\bookcars文件夹上运行BookCars API的用户添加完全访问权限。
- 在Linux上,安装NGINX,创建/var/www/cdn/bookcars文件夹,在/var/www/cdn/bookcars文件夹上为运行BookCars API的用户添加完全访问权限,并通过更改/etc/nginx/sites-available/default将cdn文件夹添加到NGINX,如下所示:
server {
listen 80 default_server;
server_name _;
...
location /cdn {
alias /var/www/cdn;
}
}
7、从 http://localhost:3001/sign-up 创建管理员用户
8、要运行移动应用程序,只需在您的设备上下载Expo应用程序,然后从./mobile文件夹运行以下命令:
npm install
npm start
您需要下载 google-services.json 文件并将其放在./mobile根目录下以发送推送通知。
您可以在此处找到有关运行移动应用程序的详细说明。
要更改货币,请按照以下说明操作。
运行移动应用
若要运行移动应用,请使用以下选项创建./mobile/.env 文件:
BC_API_HOST=https://bookcars.com:4002
BC_DEFAULT_LANGUAGE=en
BC_PAGE_SIZE=20
BC_CARS_PAGE_SIZE=8
BC_BOOKINGS_PAGE_SIZE=8
BC_CDN_USERS=https://bookcars.com/cdn/bookcars/users
BC_CDN_CARS=https://bookcars.com/cdn/bookcars/cars
BC_COMPANY_IMAGE_WIDTH=60
BC_COMPANY_IMAGE_HEIGHT=30
BC_CAR_IMAGE_WIDTH=300
BC_CAR_IMAGE_HEIGHT=200
BC_MINIMUM_AGE=21
您需要配置以下选项:
BC_API_HOST=https://bookcars.com:4002
BC_CDN_USERS=https://bookcars.com/cdn/bookcars/users
BC_CDN_CARS=https://bookcars.com/cdn/bookcars/cars
替换https://bookcars.com
为IP、主机名或FQDN。
按照以下说明安装演示数据库。
在Windows上,安装IIS并向在 C:\inetpub\wwwroot\cdn\bookcars上运行BookCars API的用户添加完全访问权限。
在Linux上,安装NGINX并更新/etc/nginx/sites-enabled/default,如下所示:
server {
listen 80 default_server;
server_name _;
...
location /cdn {
alias /var/www/cdn;
}
}
最后,向在/var/www/cdn/bookcars上运行BookCars API的用户添加完全访问权限。
按照以下说明配置./api。
使用以下命令运行./api:
cd ./api
npm run dev
只需在您的设备上下载世博会应用程序并从./mobile文件夹运行以下命令,即可运行移动应用程序:
cd ./mobile
npm install
npm start
在您的设备上打开Expo应用程序并扫描二维码以运行BookCars移动应用程序。
推送通知
如果要启用BookCars推送通知,请下载 google-services.json文件并将其放在./mobile根目录中。可以通过googleServicesFile设置选项从./mobile/app.json配置文件配置其路径。不要忘记按照文档中所述 expo.dev>凭据>服务凭据> Google Cloud Messaging Token 中设置Firebase Server密钥。
使用代码
本节介绍BookCars的软件架构,包括API、前端、移动端和后端。
BookCars API是一个Node.js服务器应用程序,它使用Express公开RESTful API,该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/server.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 mongoose from 'mongoose'
import app from './server'
import * as env from './config/env.config'
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 mongoose.connection.close(true)
console.log('MongoDB connection closed')
process.exit(0)
})
}
['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close))
这是一个使用Node.js和Express启动服务器的TypeScript文件。它导入了多个模块,包括dotenv、process、fs、http、https、mongoose和app。然后,它会检查HTTPS
环境变量是否设置为true
,如果是,则使用https模块以及提供的私钥和证书创建HTTPS服务器。否则,它将使用http模块创建HTTP服务器。服务器侦听PORT
环境变量中指定的端口。
该close
函数被定义为在接收到终止信号时正常停止服务器。它关闭服务器和MongoDB连接,然后退出进程,状态代码为0
。最后,它注册在进程接收到SIGINT
、SIGTERM
或SIGQUIT
信号时要调用的close函数。
server.ts
server.ts位于主服务器中:
import express, { Express } from 'express'
import cors from 'cors'
import mongoose, { ConnectOptions } from 'mongoose'
import compression from 'compression'
import helmet from 'helmet'
import nocache from 'nocache'
import strings from './config/app.config'
import * as env from './config/env.config'
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'
let options: ConnectOptions = {}
if (env.DB_SSL) {
options = {
tls: true,
tlsCertificateKeyFile: env.DB_SSL_CERT,
tlsCAFile: env.DB_SSL_CA,
}
}
mongoose.set('debug', env.DB_DEBUG)
mongoose.Promise = globalThis.Promise
try {
await mongoose.connect(env.DB_URI, options)
console.log('Database is connected')
} catch (err) {
console.error('Cannot connect to the database:', err)
}
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.use('/', supplierRoutes)
app.use('/', bookingRoutes)
app.use('/', locationRoutes)
app.use('/', notificationRoutes)
app.use('/', carRoutes)
app.use('/', userRoutes)
strings.setLanguage(env.DEFAULT_LANGUAGE)
export default app
首先,我们检索MongoDB连接字符串,然后与BookCars MongoDB数据库建立连接。然后我们创建一个 Express 并加载中间件,例如 cors、compression、helmet 和 nocache。我们使用 helmet 中间件库设置了各种安全措施。我们还为应用程序的不同部分导入各种路由文件,例如 、 、 、 和 。最后,我们加载 Express 路由并导出 。appsupplierRoutesbookingRouteslocationRoutesnotificationRoutescarRoutesuserRoutesapp
路由
BookCars API中有六条路由。每个路由都有自己的控制器,遵循MVC设计模式和SOLID原则。以下是主要路线:
userRoutes
:提供与用户相关的REST函数companyRoutes
:提供与供应商相关的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 async function create(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 async function getLocations(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 async function getNotifications(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 async function markAsRead(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/folder包含CSS和图像。
- ./frontend/src/pages/folder包含React页面。
- ./frontend/src/components/folder包含React组件。
- ./frontend/src/services/包含BookCars API客户端服务。
- ./frontend/src/App.tsx是包含路由的主要React应用程序。
- ./frontend/src/index.tsx是前端的主入口点。
TypeScript类型定义在包./packages/bookcars-types中定义。
App.tsx是主要的react App:
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>
)
我们使用React延迟加载来加载每个路由。
我们不会涵盖前端的每一页,但您可以打开源代码并根据需要查看每个页面。
移动应用
BookCars提供适用于Android和iOS的原生移动应用程序。该移动应用程序是使用React Native、Expo和TypeScript构建的。与前端一样,移动应用程序允许客户根据上车和下车地点和时间搜索可用的汽车,选择一辆汽车并继续结账。
如果客户从后端更新其预订,则会收到推送通知。推送通知是使用Node.js、Expo Server SDK和Firebase构建的。
- ./mobile/assets/folder包含图像。
- ./mobile/screens/folder包含主要的React Native屏幕。
- ./mobile/components/folder包含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 DrawerNavigator from './components/DrawerNavigator'
import { Provider } from 'react-native-paper'
import * as SplashScreen from 'expo-splash-screen'
import * as Notifications from 'expo-notifications'
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(() => {
async function register() {
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.data
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/folder包含CSS和图像。
- ./backend/pages/folder包含React页面。
- ./backend/components/folder包含React组件。
- ./backend/services/包含BookCars API客户端服务。
- ./backend/App.tsx是包含路由的主要React应用程序。
- ./backend/index.tsx是后端的主入口点。
TypeScript类型定义在包./packages/bookcars-types中定义。
后端的App.tsx遵循与前端的App.tsx类似的逻辑。
我们不会涵盖后端的每一页,但您可以打开源代码并根据需要查看每个页面。
兴趣点
我为一位经营汽车租赁公司的朋友开始了这个项目。他需要一个适用于Android和iOS的汽车租赁网站和移动应用程序,以及一个用于管理车辆、预订和客户的后端。我更进一步,通过附加功能增强项目,使其更加通用。最后,我根据MIT许可条款发布了它,并在CodeProject和 GitHub 上向所有人开放。
使用React Native和Expo构建移动应用程序非常容易。Expo使使用React Native进行移动开发变得非常简单。
使用相同的语言(TypeScript)进行后端和前端开发非常方便。
TypeScript是一种非常有趣的语言,有很多优点。通过在JavaScript中添加静态类型,我们可以避免许多错误,并生成易于调试和测试的高质量、可扩展、更具可读性和可维护性的代码。
https://www.codeproject.com/Articles/5346604/BookCars-Car-Rental-Platform-with-Mobile-App