添加服务端

This commit is contained in:
code@server 2025-12-20 18:04:10 +08:00
parent 55bee29a4d
commit 43bbd626c5
14 changed files with 815 additions and 4 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ node-modules
/node_modules/ /node_modules/
**/dist/ **/dist/
**/node-modules/ **/node-modules/
server/admin-dist/*

View File

@ -20,8 +20,8 @@ JWT_EXPIRES_IN=7d
JWT_REFRESH_EXPIRES_IN=30d JWT_REFRESH_EXPIRES_IN=30d
# WeChat Configuration # WeChat Configuration
WECHAT_APP_ID=your-wechat-app-id WECHAT_APP_ID=wx5ab1c98df1ec13f3
WECHAT_APP_SECRET=your-wechat-app-secret WECHAT_APP_SECRET=091adc2c6dc3c7fddf198d782b6ac240
# File Upload Configuration # File Upload Configuration
UPLOAD_PATH=./uploads UPLOAD_PATH=./uploads

View File

@ -20,8 +20,8 @@ JWT_EXPIRES_IN=7d
JWT_REFRESH_EXPIRES_IN=30d JWT_REFRESH_EXPIRES_IN=30d
# WeChat Configuration # WeChat Configuration
WECHAT_APP_ID=your-wechat-app-id WECHAT_APP_ID=wx5ab1c98df1ec13f3
WECHAT_APP_SECRET=your-wechat-app-secret WECHAT_APP_SECRET=091adc2c6dc3c7fddf198d782b6ac240
# File Upload Configuration # File Upload Configuration
UPLOAD_PATH=./uploads UPLOAD_PATH=./uploads

53
server/.env Normal file
View File

@ -0,0 +1,53 @@
# ===========================================
# 生产环境配置模板
# 复制此文件为 .env 并填写实际配置
# ===========================================
# 应用端口(唯一对外暴露的端口)
APP_PORT=2701
# ===========================================
# 数据库配置 (外部 MySQL)
# ===========================================
DB_HOST=192.168.195.15
DB_PORT=3306
DB_NAME=overseas_appointment
DB_USER=root
DB_PASSWORD=root123456
# ===========================================
# Redis 配置 (外部 Redis)
# ===========================================
REDIS_HOST=192.168.195.15
REDIS_PORT=6379
REDIS_PASSWORD=
# ===========================================
# JWT 认证配置
# ===========================================
JWT_SECRET=your-super-secret-key-change-in-production
JWT_EXPIRES_IN=7d
JWT_REFRESH_EXPIRES_IN=30d
# ===========================================
# 微信小程序配置
# ===========================================
WECHAT_APP_ID=wx5ab1c98df1ec13f3
WECHAT_APP_SECRET=091adc2c6dc3c7fddf198d782b6ac240
# ===========================================
# 文件上传配置
# ===========================================
UPLOAD_PATH=./uploads
MAX_FILE_SIZE=5242880
# ===========================================
# 限流配置
# ===========================================
RATE_LIMIT_WINDOW=60000
RATE_LIMIT_MAX=100
# ===========================================
# 日志配置
# ===========================================
LOG_LEVEL=info

52
server/.env.example Normal file
View File

@ -0,0 +1,52 @@
# ===========================================
# 生产环境配置模板
# 复制此文件为 .env 并填写实际配置
# ===========================================
# 应用端口(唯一对外暴露的端口)
APP_PORT=2701
# ===========================================
# 数据库配置 (外部 MySQL)
# ===========================================
DB_HOST=192.168.195.15
DB_PORT=3306
DB_NAME=overseas_appointment
DB_USER=root
DB_PASSWORD=your_password_here
# ===========================================
# Redis 配置 (外部 Redis)
# ===========================================
REDIS_HOST=192.168.195.15
REDIS_PORT=6379
REDIS_PASSWORD=
# ===========================================
# JWT 认证配置
# ===========================================
JWT_SECRET=your-super-secret-key-change-in-production
JWT_EXPIRES_IN=7d
JWT_REFRESH_EXPIRES_IN=30d
# ===========================================
# 微信小程序配置
# ===========================================
WECHAT_APP_ID=your-wechat-app-id
WECHAT_APP_SECRET=your-wechat-app-secret
# ===========================================
# 文件上传配置
# ===========================================
MAX_FILE_SIZE=5242880
# ===========================================
# 限流配置
# ===========================================
RATE_LIMIT_WINDOW=60000
RATE_LIMIT_MAX=100
# ===========================================
# 日志配置
# ===========================================
LOG_LEVEL=info

112
server/Makefile Normal file
View File

@ -0,0 +1,112 @@
# ============================================
# 海外预约系统 - Makefile
# ============================================
.PHONY: help deploy build start stop restart logs status clean
# 默认目标
.DEFAULT_GOAL := help
# 颜色定义
GREEN := \033[0;32m
YELLOW := \033[1;33m
BLUE := \033[0;34m
NC := \033[0m
# 检测 docker compose 命令
DOCKER_COMPOSE := $(shell if docker compose version > /dev/null 2>&1; then echo "docker compose"; else echo "docker-compose"; fi)
# 帮助信息
help:
@echo ""
@echo "$(BLUE)============================================$(NC)"
@echo "$(BLUE) 海外预约系统 - 部署命令$(NC)"
@echo "$(BLUE)============================================$(NC)"
@echo ""
@echo "$(GREEN)部署命令:$(NC)"
@echo " make deploy - 完整部署(构建 Admin + 启动服务)"
@echo " make build - 仅构建 Admin 前端"
@echo " make start - 启动服务(跳过构建)"
@echo ""
@echo "$(GREEN)管理命令:$(NC)"
@echo " make stop - 停止服务"
@echo " make restart - 重启服务"
@echo " make status - 查看服务状态"
@echo " make logs - 查看实时日志"
@echo " make logs-api - 查看 API 日志"
@echo " make logs-nginx - 查看 Nginx 日志"
@echo ""
@echo "$(GREEN)其他命令:$(NC)"
@echo " make clean - 清理构建产物"
@echo " make shell-api - 进入 API 容器"
@echo " make shell-nginx - 进入 Nginx 容器"
@echo " make init - 初始化环境配置"
@echo ""
# 完整部署
deploy:
@./deploy.sh
# 仅构建 Admin
build:
@echo "$(YELLOW)构建 Admin 前端...$(NC)"
@cd ../admin && npm install && npm run build
@rm -rf ./admin-dist
@cp -r ../admin/dist ./admin-dist
@echo "$(GREEN)✓ Admin 构建完成$(NC)"
# 启动服务(跳过构建)
start:
@./deploy.sh --skip-build
# 停止服务
stop:
@./deploy.sh --stop
# 重启服务
restart:
@./deploy.sh --restart
# 查看服务状态
status:
@$(DOCKER_COMPOSE) ps
# 查看所有日志
logs:
@$(DOCKER_COMPOSE) logs -f
# 查看 API 日志
logs-api:
@$(DOCKER_COMPOSE) logs -f api
# 查看 Nginx 日志
logs-nginx:
@$(DOCKER_COMPOSE) logs -f nginx
# 进入 API 容器
shell-api:
@docker exec -it overseas-appointment-api sh
# 进入 Nginx 容器
shell-nginx:
@docker exec -it overseas-appointment-nginx sh
# 初始化环境配置
init:
@if [ ! -f .env ]; then \
cp .env.example .env; \
echo "$(GREEN)✓ 已创建 .env 文件,请编辑配置$(NC)"; \
else \
echo "$(YELLOW).env 文件已存在$(NC)"; \
fi
# 清理构建产物
clean:
@echo "$(YELLOW)清理构建产物...$(NC)"
@rm -rf ./admin-dist
@$(DOCKER_COMPOSE) down --rmi local 2>/dev/null || true
@echo "$(GREEN)✓ 清理完成$(NC)"
# 查看健康状态
health:
@curl -s http://localhost:2701/health | python3 -m json.tool 2>/dev/null || curl -s http://localhost:2701/health

193
server/README.md Normal file
View File

@ -0,0 +1,193 @@
# 服务器部署配置
此目录包含生产环境的 Docker 部署配置,使用**外部 MySQL 和 Redis** 服务。
## 目录结构
```
server/
├── docker-compose.yml # Docker Compose 配置
├── deploy.sh # 自动部署脚本
├── .env.example # 环境变量模板
├── .env # 实际环境变量(需手动创建)
├── nginx/ # Nginx 配置
│ ├── nginx.conf
│ └── conf.d/
│ └── default.conf
├── data/ # 数据目录(本地挂载,方便迁移)
│ ├── uploads/ # 用户上传的文件
│ └── logs/
│ ├── api/ # API 服务日志
│ └── nginx/ # Nginx 访问日志
├── admin-dist/ # Admin 打包后的静态文件(需手动放置)
└── README.md
```
## 架构说明
```
┌─────────────────────────────────────┐
│ 外部代理 (SSL) │
└──────────────┬──────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ Docker Compose (单一端口 2701) │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Nginx │ │
│ │ /admin/* → 静态文件 (Admin 后台) │ │
│ │ /api/* → API 服务 │ │
│ │ /uploads → API 服务 │ │
│ └────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ API 服务 (:3000) │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ MySQL │ │ Redis │
│ (外部服务) │ │ (外部服务) │
└─────────────┘ └─────────────┘
```
## 快速开始
### 1. 配置环境变量
```bash
cd server
cp .env.example .env
vim .env # 编辑配置
```
### 2. 构建 Admin 前端
```bash
cd ../admin
npm install
npm run build
# 将打包产物复制到 server 目录
cp -r dist ../server/admin-dist
```
### 3. 启动服务
```bash
cd server
docker-compose up -d --build
```
### 4. 验证服务
```bash
# 检查服务状态
docker-compose ps
# 访问 Admin 后台
curl http://localhost:2701/admin
# 检查 API
curl http://localhost:2701/health
curl http://localhost:2701/api/v1/services
```
## 访问地址
| 路径 | 说明 |
|------|------|
| `/admin` | Admin 管理后台 |
| `/api/*` | 后端 API 接口 |
| `/api-docs` | API 文档 |
| `/health` | 健康检查 |
| `/uploads/*` | 上传文件 |
## 常用命令
```bash
# 启动
docker-compose up -d
# 停止
docker-compose down
# 重新构建并启动
docker-compose up -d --build
# 查看日志
docker-compose logs -f
docker-compose logs -f api
docker-compose logs -f nginx
# 重启服务
docker-compose restart api
# 进入容器
docker exec -it overseas-appointment-api sh
docker exec -it overseas-appointment-nginx sh
```
## 更新 Admin 前端
```bash
# 1. 重新构建 admin
cd ../admin
npm run build
# 2. 更新静态文件
rm -rf ../server/admin-dist
cp -r dist ../server/admin-dist
# 3. 重启 nginx
cd ../server
docker-compose restart nginx
```
## 外部代理配置示例
### Nginx (外部)
```nginx
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
location / {
proxy_pass http://127.0.0.1:2701;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
## 故障排查
### Admin 页面 404
确认 `admin-dist` 目录存在且包含 `index.html`
```bash
ls -la server/admin-dist/
```
### API 连接数据库失败
```bash
# 检查数据库连通性
docker exec -it overseas-appointment-api sh
node -e "require('./src/models').sequelize.authenticate().then(() => console.log('OK')).catch(console.error)"
```
### 查看实时日志
```bash
docker-compose logs -f --tail=100
```

View File

View File

View File

189
server/deploy.sh Executable file
View File

@ -0,0 +1,189 @@
#!/bin/bash
# ============================================
# 海外预约系统 - 自动部署脚本
# ============================================
set -e # 遇到错误立即退出
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 项目根目录
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# 检测 docker compose 命令
if docker compose version > /dev/null 2>&1; then
DOCKER_COMPOSE="docker compose"
elif docker-compose version > /dev/null 2>&1; then
DOCKER_COMPOSE="docker-compose"
else
echo -e "${RED}错误: 未找到 docker compose 命令${NC}"
exit 1
fi
echo -e "${BLUE}============================================${NC}"
echo -e "${BLUE} 海外预约系统 - 自动部署脚本${NC}"
echo -e "${BLUE}============================================${NC}"
echo ""
# 检查 Docker 是否运行
check_docker() {
echo -e "${YELLOW}[1/5] 检查 Docker 环境...${NC}"
if ! docker info > /dev/null 2>&1; then
echo -e "${RED}错误: Docker 未运行,请先启动 Docker${NC}"
exit 1
fi
echo -e "${GREEN}✓ Docker 运行正常 (使用 $DOCKER_COMPOSE)${NC}"
}
# 检查环境变量文件
check_env() {
echo -e "${YELLOW}[2/5] 检查环境配置...${NC}"
if [ ! -f "$SCRIPT_DIR/.env" ]; then
echo -e "${YELLOW} .env 文件不存在,从模板创建...${NC}"
cp "$SCRIPT_DIR/.env.example" "$SCRIPT_DIR/.env"
echo -e "${YELLOW} 请编辑 $SCRIPT_DIR/.env 配置数据库等信息${NC}"
echo -e "${RED} 首次部署请先配置 .env 文件后重新运行脚本${NC}"
exit 1
fi
echo -e "${GREEN}✓ 环境配置已就绪${NC}"
}
# 构建 Admin 前端
build_admin() {
echo -e "${YELLOW}[3/5] 构建 Admin 前端...${NC}"
cd "$PROJECT_ROOT/admin"
# 检查 node_modules
if [ ! -d "node_modules" ]; then
echo -e "${YELLOW} 安装依赖...${NC}"
npm install
fi
echo -e "${YELLOW} 执行构建...${NC}"
npm run build
# 复制构建产物
echo -e "${YELLOW} 复制构建产物到 server/admin-dist...${NC}"
rm -rf "$SCRIPT_DIR/admin-dist"
cp -r dist "$SCRIPT_DIR/admin-dist"
echo -e "${GREEN}✓ Admin 构建完成${NC}"
}
# 构建并启动 Docker 服务
deploy_docker() {
echo -e "${YELLOW}[4/5] 构建并启动 Docker 服务...${NC}"
cd "$SCRIPT_DIR"
# 停止旧容器(如果存在)
echo -e "${YELLOW} 停止旧容器...${NC}"
$DOCKER_COMPOSE down 2>/dev/null || true
# 构建并启动
echo -e "${YELLOW} 构建镜像并启动容器...${NC}"
$DOCKER_COMPOSE up -d --build
echo -e "${GREEN}✓ Docker 服务已启动${NC}"
}
# 检查服务状态
check_status() {
echo -e "${YELLOW}[5/5] 检查服务状态...${NC}"
sleep 3
# 检查容器状态
echo ""
$DOCKER_COMPOSE ps
echo ""
# 等待服务就绪
echo -e "${YELLOW} 等待服务就绪...${NC}"
for i in {1..30}; do
if curl -s http://localhost:2701/health > /dev/null 2>&1; then
echo -e "${GREEN}✓ 服务已就绪${NC}"
break
fi
if [ $i -eq 30 ]; then
echo -e "${YELLOW} 服务启动中,请稍后检查...${NC}"
fi
sleep 1
done
}
# 显示访问信息
show_info() {
echo ""
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} 部署完成!${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo -e " 访问地址:"
echo -e " Admin 后台: ${BLUE}http://localhost:2701/admin${NC}"
echo -e " API 接口: ${BLUE}http://localhost:2701/api/v1${NC}"
echo -e " 健康检查: ${BLUE}http://localhost:2701/health${NC}"
echo ""
echo -e " 常用命令:"
echo -e " 查看日志: ${YELLOW}cd server && $DOCKER_COMPOSE logs -f${NC}"
echo -e " 停止服务: ${YELLOW}cd server && $DOCKER_COMPOSE down${NC}"
echo -e " 重启服务: ${YELLOW}cd server && $DOCKER_COMPOSE restart${NC}"
echo ""
}
# 主流程
main() {
check_docker
check_env
build_admin
deploy_docker
check_status
show_info
}
# 处理命令行参数
case "${1:-}" in
--skip-build)
echo -e "${YELLOW}跳过 Admin 构建${NC}"
check_docker
check_env
deploy_docker
check_status
show_info
;;
--restart)
echo -e "${YELLOW}重启服务${NC}"
cd "$SCRIPT_DIR"
$DOCKER_COMPOSE restart
check_status
;;
--stop)
echo -e "${YELLOW}停止服务${NC}"
cd "$SCRIPT_DIR"
$DOCKER_COMPOSE down
echo -e "${GREEN}✓ 服务已停止${NC}"
;;
--logs)
cd "$SCRIPT_DIR"
$DOCKER_COMPOSE logs -f
;;
--help)
echo "用法: ./deploy.sh [选项]"
echo ""
echo "选项:"
echo " (无参数) 完整部署(构建 Admin + 启动服务)"
echo " --skip-build 跳过 Admin 构建,直接部署"
echo " --restart 重启服务"
echo " --stop 停止服务"
echo " --logs 查看日志"
echo " --help 显示帮助"
;;
*)
main
;;
esac

67
server/docker-compose.yml Normal file
View File

@ -0,0 +1,67 @@
# 生产环境 Docker Compose 配置
# 使用外部 MySQL 和 Redis 服务
# 只暴露一个端口SSL 由外部代理处理
services:
# Backend API Service
api:
build:
context: ../backend
dockerfile: Dockerfile
container_name: overseas-appointment-api
restart: unless-stopped
expose:
- "3000"
environment:
- NODE_ENV=production
- PORT=3000
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT:-3306}
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT:-6379}
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
- JWT_SECRET=${JWT_SECRET}
- JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-7d}
- JWT_REFRESH_EXPIRES_IN=${JWT_REFRESH_EXPIRES_IN:-30d}
- WECHAT_APP_ID=${WECHAT_APP_ID:-}
- WECHAT_APP_SECRET=${WECHAT_APP_SECRET:-}
- UPLOAD_PATH=/app/uploads
- MAX_FILE_SIZE=${MAX_FILE_SIZE:-5242880}
- RATE_LIMIT_WINDOW=${RATE_LIMIT_WINDOW:-60000}
- RATE_LIMIT_MAX=${RATE_LIMIT_MAX:-100}
- LOG_LEVEL=${LOG_LEVEL:-info}
volumes:
- ./data/uploads:/app/uploads
- ./data/logs/api:/app/logs
networks:
- app-network
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Nginx - 托管 Admin 静态文件 + 代理 API
nginx:
image: nginx:alpine
container_name: overseas-appointment-nginx
restart: unless-stopped
ports:
- "${APP_PORT:-2701}:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./admin-dist:/usr/share/nginx/html/admin:ro
- ./data/logs/nginx:/var/log/nginx
depends_on:
- api
networks:
- app-network
networks:
app-network:
driver: bridge

View File

@ -0,0 +1,93 @@
server {
listen 80;
server_name localhost;
# Client body size limit
client_max_body_size 10M;
# Admin 后台静态文件
location /admin {
alias /usr/share/nginx/html/admin;
index index.html;
try_files $uri $uri/ /admin/index.html;
# 缓存静态资源
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# API 接口代理
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 10;
proxy_pass http://api_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Health check
location /health {
proxy_pass http://api_servers;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# API 文档
location /api-docs {
proxy_pass http://api_servers;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 上传文件访问
location /uploads/ {
proxy_pass http://api_servers;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_valid 200 1d;
expires 1d;
add_header Cache-Control "public, immutable";
}
# 根路径重定向到 admin
location = / {
return 302 /admin;
}
# 禁止访问隐藏文件
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
internal;
}
}

51
server/nginx/nginx.conf Normal file
View File

@ -0,0 +1,51 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging format
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time';
access_log /var/log/nginx/access.log main;
# Performance optimizations
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript
application/xml application/xml+rss text/javascript;
# Rate limiting zones
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# Upstream API server
upstream api_servers {
least_conn;
server api:3000 weight=1 max_fails=3 fail_timeout=30s;
keepalive 32;
}
include /etc/nginx/conf.d/*.conf;
}