diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..020c05c --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,120 @@ +# MiniApp API Test 自动化部署配置 + +## 文件说明 + +| 文件 | 说明 | +|------|------| +| `docker-compose.test.yml` | 测试环境 Docker Compose 配置 | +| `deploy/webhook-server.js` | Webhook 服务器,接收 Git 推送并自动部署 | +| `deploy/webhook.service` | systemd 服务配置,保持 webhook 常驻运行 | +| `deploy/setup.sh` | 一键初始化脚本 | + +## 云服务器部署步骤 + +### 1. 修改配置 + +编辑以下文件,替换为你的实际信息: + +- **`deploy/setup.sh`**: + ```bash + GIT_REPO="git@github.com:your-username/miniapp-api_test.git" + ``` + +- **`deploy/webhook.service`**: + ```ini + Environment="WEBHOOK_SECRET=你的webhook密钥" + ``` + +- **`deploy/webhook-server.js`** (可选): + ```javascript + const SECRET = process.env.WEBHOOK_SECRET || '你的webhook密钥'; + ``` + +### 2. 上传代码到服务器 + +```bash +# 方式1: 直接上传 +scp -r ./* root@your-server-ip:/opt/miniapp-api_test/ + +# 方式2: 先推送到 git,再在服务器克隆 +``` + +### 3. 运行初始化脚本 + +```bash +ssh root@your-server-ip +cd /opt/miniapp-api_test +chmod +x deploy/setup.sh +./deploy/setup.sh +``` + +### 4. 配置 Git Webhook + +在 Git 仓库设置中添加 Webhook: + +- **Payload URL**: `http://your-server-ip:9001/webhook` +- **Content type**: `application/json` +- **Secret**: 你设置的 `WEBHOOK_SECRET` +- **触发事件**: Push events (main 分支) + +### 5. 开放防火墙端口 + +```bash +# 开放 9001 端口(webhook)和 3001 端口(API) +ufw allow 9001/tcp +ufw allow 3001/tcp +``` + +### 6. 配置 Nginx 反向代理 (推荐) + +```nginx +server { + listen 80; + server_name miniapp-api-test.dxz99wyr.cn; + + location / { + proxy_pass http://localhost:3001; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + +## 常用命令 + +```bash +# 查看 webhook 日志 +journalctl -u miniapp-api_test-webhook -f + +# 查看容器状态 +docker-compose -f docker-compose.test.yml ps + +# 查看容器日志 +docker logs -f miniapp-api_test + +# 手动重启容器 +docker-compose -f docker-compose.test.yml restart + +# 手动触发部署 +cd /opt/miniapp-api_test && docker-compose -f docker-compose.test.yml up -d --build +``` + +## 升级正式版本 + +当测试版本稳定后,执行以下操作升级到正式版: + +```bash +# 1. 推送测试版本代码到正式仓库 +cd /opt/miniapp-api_test +git push 正式仓库地址 main + +# 2. 在正式环境重新构建 +# (正式环境的部署方式保持不变) +``` + +或者由开发者手动推送: +```bash +git remote add production <正式仓库地址> +git push production main +``` diff --git a/deploy/setup.sh b/deploy/setup.sh new file mode 100644 index 0000000..c74996e --- /dev/null +++ b/deploy/setup.sh @@ -0,0 +1,52 @@ +#!/bin/bash +set -e + +echo "==========================================" +echo " MiniApp API Test 自动化部署环境配置" +echo "==========================================" + +DEPLOY_DIR="/opt/miniapp-api_test" +GIT_REPO="ssh://git@8.136.137.59:2222/Superuser/miniapp-api_test.git" + +echo "" +echo "[1/6] 创建部署目录..." +mkdir -p "$DEPLOY_DIR" +cd "$DEPLOY_DIR" + +echo "" +echo "[2/6] 克隆 miniapp-api_test 仓库..." +if [ ! -d ".git" ]; then + git clone "$GIT_REPO" . +else + echo "仓库已存在, 跳过克隆" +fi + +echo "" +echo "[3/6] 创建必要的目录..." +mkdir -p public/uploads public/avatars public/admin + +echo "" +echo "[4/6] 配置 systemd 服务..." +cp deploy/webhook.service /etc/systemd/system/miniapp-api_test-webhook.service +systemctl daemon-reload +systemctl enable miniapp-api_test-webhook.service + +echo "" +echo "[5/6] 启动 webhook 服务..." +systemctl start miniapp-api_test-webhook.service + +echo "" +echo "[6/6] 启动 Docker 容器..." +docker-compose -f docker-compose.test.yml up -d --build + +echo "" +echo "==========================================" +echo " 配置完成!" +echo "==========================================" +echo "" +echo "Webhook 接收地址: http://$(curl -s ifconfig.me):9001/webhook" +echo "API 测试地址: https://miniapp-api-test.dxz99wyr.cn" +echo "" +echo "查看 webhook 日志: journalctl -u miniapp-api_test-webhook -f" +echo "查看容器状态: docker-compose -f docker-compose.test.yml ps" +echo "" diff --git a/deploy/webhook-server.js b/deploy/webhook-server.js new file mode 100644 index 0000000..43ae176 --- /dev/null +++ b/deploy/webhook-server.js @@ -0,0 +1,105 @@ +const http = require('http'); +const { exec } = require('child_process'); +const path = require('path'); +const crypto = require('crypto'); + +const PORT = 9001; +const DEPLOY_DIR = '/opt/miniapp-api_test'; +const COMPOSE_FILE = 'docker-compose.test.yml'; + +const SECRET = process.env.WEBHOOK_SECRET || 'miniapp-api-deploy-secret'; + +function verifySignature(payload, signature) { + const hmac = crypto.createHmac('sha256', SECRET); + const digest = 'sha256=' + hmac.update(payload).digest('hex'); + return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature)); +} + +function runCommand(command, cwd) { + return new Promise((resolve, reject) => { + console.log(`[${new Date().toISOString()}] 执行命令: ${command}`); + const child = exec(command, { cwd, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => { + if (error) { + console.error(`[${new Date().toISOString()}] 命令执行失败:`, error.message); + console.error('stderr:', stderr); + reject(error); + return; + } + console.log(`[${new Date().toISOString()}] 命令输出:\n${stdout}`); + if (stderr) console.error(`stderr: ${stderr}`); + resolve(stdout); + }); + }); +} + +async function deploy() { + const timestamp = new Date().toISOString(); + console.log(`\n========== 开始部署 miniapp-api_test [${timestamp}] ==========`); + + try { + await runCommand('git fetch origin', DEPLOY_DIR); + await runCommand('git reset --hard origin/main', DEPLOY_DIR); + + await runCommand(`docker-compose -f ${COMPOSE_FILE} build --no-cache`, DEPLOY_DIR); + await runCommand(`docker-compose -f ${COMPOSE_FILE} up -d`, DEPLOY_DIR); + + await runCommand('docker image prune -f', DEPLOY_DIR); + + console.log(`[${new Date().toISOString()}] 部署成功完成`); + return { success: true, message: '部署成功' }; + } catch (error) { + console.error(`[${new Date().toISOString()}] 部署失败:`, error.message); + return { success: false, message: error.message }; + } +} + +const server = http.createServer(async (req, res) => { + if (req.method !== 'POST' || req.url !== '/webhook') { + res.writeHead(404); + res.end('Not Found'); + return; + } + + let body = ''; + req.on('data', chunk => { body += chunk; }); + req.on('end', async () => { + const signature = req.headers['x-hub-signature-256'] || req.headers['x-gitlab-token']; + + if (SECRET !== 'your_webhook_secret_here' && signature) { + const isValid = verifySignature(body, signature); + if (!isValid) { + console.warn(`[${new Date().toISOString()}] Webhook 签名验证失败`); + res.writeHead(401); + res.end('Unauthorized'); + return; + } + } + + let payload; + try { + payload = JSON.parse(body); + } catch (e) { + payload = {}; + } + + const ref = payload.ref || payload.object_attributes?.ref || 'unknown'; + console.log(`[${new Date().toISOString()}] 收到 Webhook 请求, ref: ${ref}`); + + if (ref === 'refs/heads/main' || ref === 'main' || !ref.includes('refs/')) { + res.writeHead(202); + res.end(JSON.stringify({ status: 'accepted', message: '部署任务已启动' })); + + const result = await deploy(); + console.log(`[${new Date().toISOString()}] 部署结果:`, result); + } else { + res.writeHead(200); + res.end(JSON.stringify({ status: 'ignored', message: '非 main 分支推送, 忽略' })); + } + }); +}); + +server.listen(PORT, '0.0.0.0', () => { + console.log(`Webhook 服务器已启动, 监听端口 ${PORT}`); + console.log(`部署目录: ${DEPLOY_DIR}`); + console.log(`接收地址: http://your-server-ip:${PORT}/webhook`); +}); diff --git a/deploy/webhook.service b/deploy/webhook.service new file mode 100644 index 0000000..21fc25e --- /dev/null +++ b/deploy/webhook.service @@ -0,0 +1,18 @@ +[Unit] +Description=MiniApp API Test Webhook Auto Deploy Service +After=network.target docker.service +Requires=docker.service + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/miniapp-api_test/deploy +Environment="WEBHOOK_SECRET=miniapp-api-deploy-secret" +ExecStart=/usr/bin/node /opt/miniapp-api_test/deploy/webhook-server.js +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..0e112ec --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,56 @@ +services: + app-test: + build: + context: . + dockerfile: Dockerfile + container_name: miniapp-api_test + restart: unless-stopped + ports: + - "3001:3001" + environment: + - PORT=3001 + - NODE_ENV=production + - WECHAT_APPID=wxa83262674846ca1a + - WECHAT_APPSECRET=365653aa1214a5523a6a0e7d793eec6a + - MONGODB_URI=mongodb://mongo-test:27017/quanyixiaozhushou_test + - JWT_SECRET=your_jwt_secret_key_here_change_in_production + - JWT_EXPIRES_IN=7d + - LOG_LEVEL=info + - BAIDU_OCR_API_KEY=IfYLOLzL6X60h5UOdnkX6OmT + - BAIDU_OCR_SECRET_KEY=wGXbp6DwazDghJ1EXtjAT7XAFwJLqVD4 + - SERVER_URL=https://miniapp-api-test.dxz99wyr.cn + - EXPORT_ENCRYPT_KEY=QuanYiXiaoZhuShou_2026_Secret_Key + - ADMIN_KEY=quanyiAdmin2026 + volumes: + - uploads_data_test:/app/public/uploads + - ./public/avatars:/app/public/avatars + depends_on: + mongo-test: + condition: service_healthy + healthcheck: + test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + + mongo-test: + image: mongo:7.0 + container_name: miniapp-api_test-mongo + restart: unless-stopped + ports: + - "27019:27017" + volumes: + - mongo_data_test:/data/db + environment: + - MONGO_INITDB_DATABASE=quanyixiaozhushou_test + healthcheck: + test: echo 'db.runCommand("ping").ok' | mongosh --quiet + interval: 10s + timeout: 5s + retries: 5 + start_period: 20s + +volumes: + uploads_data_test: + mongo_data_test: