Deploy.php Webhook接口

PHP Webhook控制器,处理Gitee推送事件并触发部署脚本

<?php

namespace app\controller\common;

use app\BaseController;

class Deploy extends BaseController
{
    public function index()
    {
        // 密码验证
        $correct_password = '123456';
        $request_password = '';
        $dir = 'zdbs.localbird.cn';
        // 获取密码
        if (isset($_SERVER['HTTP_X_GITEE_TOKEN'])) {
            $request_password = $_SERVER['HTTP_X_GITEE_TOKEN'];
        }

        // 验证密码
        if ($request_password !== $correct_password) {
            return json(['code' => 401, 'msg' => '密码验证失败']);
        }

        // 检查是否是ping测试
        if (isset($_SERVER['HTTP_X_GITEE_PING']) && $_SERVER['HTTP_X_GITEE_PING'] === 'true') {
            return json([
                'code' => 200,
                'msg' => 'Ping测试成功',
                'data' => ['time' => date('Y-m-d H:i:s')]
            ]);
        }

        // 获取仓库信息
        $input = file_get_contents('php://input');
        $data = json_decode($input, true);
        $repo_name = 'webhook触发';
        if (!empty($data) && isset($data['repository']['name'])) {
            $repo_name = $data['repository']['name'];
        }
        // 执行部署脚本
        $script_path = '/www/wwwroot/' . $dir . '/scripts/build-temp.sh';
        $log_file = '/www/wwwroot/' . $dir . '/runtime/log/deploy_' . date('Ymd_His') . '.log';

        // 创建日志目录
        $log_dir = '/www/wwwroot/' . $dir . '/runtime/log';
        if (!is_dir($log_dir)) {
            mkdir($log_dir, 0755, true);
        }

        // 检查脚本是否存在
        if (!file_exists($script_path)) {
            return json([
                'code' => 500,
                'msg' => '部署脚本不存在',
                'data' => ['script_path' => $script_path]
            ]);
        }

        // 尝试多种命令执行方式
        try {
            $command = "cd /www/wwwroot/" . $dir . " && bash " . escapeshellarg($script_path) . " > " . escapeshellarg($log_file) . " 2>&1 &";

            // 使用shell_exec执行命令
            $result = shell_exec($command);

            return json([
                'code' => 200,
                'msg' => '部署任务已启动',
                'data' => [
                    'time' => date('Y-m-d H:i:s'),
                    'repository' => $repo_name,
                    'log_file' => basename($log_file),
                    'command' => $command,
                    'method' => 'shell_exec',
                    'result' => $result,
                    'status' => '部署脚本正在后台执行,请稍后查看日志文件'
                ]
            ]);
        } catch (\Exception $e) {
            return json([
                'code' => 500,
                'msg' => '执行部署失败: ' . $e->getMessage(),
                'data' => [
                    'command' => $command ?? 'undefined'
                ]
            ]);
        }
    }
}

build-temp.sh 部署脚本

Bash自动化部署脚本,适用于宝塔服务器环境

#!/bin/bash

# Vue项目自动部署脚本 - 适用于宝塔服务器
# 修改下面的git地址为你的仓库地址
GIT_URL="https://gitee.com/wuhuanmayun/automated-deployment-testing.git"

# 获取脚本所在目录的上级目录(网站根目录)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WEB_ROOT="$(dirname "$SCRIPT_DIR")"

echo "$(date '+%Y-%m-%d %H:%M:%S') - 开始自动部署..."

# 进入网站根目录
cd "$WEB_ROOT"

# 1. 如果buildTemp文件夹不存在就克隆,存在就更新
if [ ! -d "buildTemp" ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 克隆项目..."
    git clone "$GIT_URL" buildTemp
    if [ $? -ne 0 ]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - 克隆失败!"
        exit 1
    fi
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 更新代码..."
    cd buildTemp
    git reset --hard HEAD
    git clean -fd
    git fetch --all
    # 设置pull策略为merge,避免警告
    git config pull.rebase false
    git pull origin main || git pull origin master
    if [ $? -ne 0 ]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - 代码更新失败!"
        exit 1
    fi
    cd "$WEB_ROOT"
fi

# 2. 进入项目文件夹
cd buildTemp

# 3. 安装依赖
echo "$(date '+%Y-%m-%d %H:%M:%S') - 安装依赖..."
# 清理npm缓存并重新安装
npm cache clean --force
npm install
if [ $? -ne 0 ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 依赖安装失败!"
    exit 1
fi

# 检查vite是否正确安装
echo "$(date '+%Y-%m-%d %H:%M:%S') - 检查vite安装状态..."
if [ -f "node_modules/.bin/vite" ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - vite已安装在 node_modules/.bin/vite"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - vite未找到,尝试安装..."
    npm install vite --save-dev
fi

# 4. 打包项目
echo "$(date '+%Y-%m-%d %H:%M:%S') - 打包项目..."

# 方法1: 直接调用本地vite
if [ -f "node_modules/.bin/vite" ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 使用本地vite构建..."
    ./node_modules/.bin/vite build
    if [ $? -eq 0 ]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - 本地vite构建成功!"
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') - 本地vite构建失败!"
    fi
fi

# 方法2: 如果上面失败,尝试npx
if [ ! -d "dist" ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 尝试使用npx vite build..."
    npx vite build
    if [ $? -eq 0 ]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - npx构建成功!"
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') - npx构建失败!"
    fi
fi

# 方法3: 如果还是失败,尝试npm run build
if [ ! -d "dist" ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 尝试使用npm run build..."
    npm run build
    if [ $? -eq 0 ]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - npm run build成功!"
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') - npm run build失败!"
    fi
fi

# 检查是否成功生成dist目录
if [ ! -d "dist" ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 所有打包方式都失败!未生成dist目录"
    exit 1
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 打包成功,dist目录已生成"
fi

# 5. 复制文件到public/user目录
echo "$(date '+%Y-%m-%d %H:%M:%S') - 复制文件..."
cd "$WEB_ROOT"

# 确保public/user目录存在
mkdir -p public/user

# 清空目标目录但保留重要文件
cd public/user
find . -maxdepth 1 ! -name '.htaccess' ! -name '.user.ini' ! -name '.' -exec rm -rf {} + 2>/dev/null || true

# 复制新文件
cd "$WEB_ROOT"
if [ -d "buildTemp/dist" ] && [ "$(ls -A buildTemp/dist)" ]; then
    cp -r buildTemp/dist/* public/user/
    echo "$(date '+%Y-%m-%d %H:%M:%S') - 文件复制成功!"
else
    echo "$(date '+%Y-%m-%d %H:%M:%S') - dist目录为空或不存在!"
    exit 1
fi

# 6. 设置权限(宝塔环境)
chown -R www:www public/user
chmod -R 755 public/user

echo "$(date '+%Y-%m-%d %H:%M:%S') - 部署完成!"

Nginx配置文件

完整的Nginx配置示例:缓存、压缩、限流、负载均衡、反向代理

events {
    worker_connections 1024;
}

http {
    # 配置缓存区域
    proxy_cache_path /tmp/nginx_cache 
                     levels=1:2 
                     keys_zone=my_cache:10m 
                     max_size=100m 
                     inactive=10m;
    
    # 性能优化配置
    sendfile on;              # 启用高效文件传输
    tcp_nopush on;            # 优化 TCP 数据包发送
    tcp_nodelay on;            # 禁用 Nagle 算法,减少延迟
    keepalive_timeout 65;     # 保持连接超时时间
    types_hash_max_size 2048; # 类型哈希表大小

    # Gzip 压缩(减少响应客户端传输大小)
    gzip on;                  # 启用 Gzip 压缩
    gzip_vary on;             # 根据 Accept-Encoding 头决定是否压缩
    gzip_proxied any;         # 对所有代理请求启用压缩
    gzip_comp_level 6;        # 压缩级别(1-9,6 是平衡点)
    gzip_types                # 压缩的文件类型
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript
        application/xml+rss
        application/rss+xml
        image/svg+xml;
    gzip_min_length 1000;     # 只压缩大于 1KB 的文件
    gzip_disable "msie6";     # 禁用 IE6 的压缩(IE6 有 bug)

    # 文件上传大小限制
    client_max_body_size 100M;        # 最大请求体大小
    client_body_buffer_size 128k;    # 请求体缓冲区大小
    client_body_timeout 60s;         # 请求体超时时间

    # 限流配置(防止恶意请求)
    # 定义限流区域:zone=名称:大小 rate=速率
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;  # API 限流:每秒 10 个请求
    limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;   # 登录限流:每秒 1 个请求
    # 连接数限制
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
    
    # 负载均衡配置
    # 轮询策略(默认)
    upstream backend_round_robin {
        server 127.0.0.1:3000;
        server 127.0.0.1:3001;
        server 127.0.0.1:3002;
    }
    
    # 权重策略(weight 越大,分配请求越多)
    upstream backend_weighted {
        server 127.0.0.1:3000 weight=3;  # 权重 3
        server 127.0.0.1:3001 weight=2;  # 权重 2
        server 127.0.0.1:3002 weight=1;  # 权重 1
    }
    
    # 最少连接数策略
    upstream backend_least_conn {
        least_conn;
        server 127.0.0.1:3000;
        server 127.0.0.1:3001;
        server 127.0.0.1:3002;
    }
    
    # IP 哈希策略(会话保持,同一 IP 总是访问同一服务器)
    upstream backend_ip_hash {
        ip_hash;
        server 127.0.0.1:3000;
        server 127.0.0.1:3001;
        server 127.0.0.1:3002;
    }
    # 服务器配置一
    server {
        listen 8083;
        server_name localhost;
        
        location /login {
            # 应用限流
            limit_req zone=login_limit burst=2 nodelay;
            proxy_pass http://backend_ip_hash;
        }

        location /api/upload {
            # 文件上传接口,使用最少连接策略
            # 注意:这个 location 会优先匹配,不会走到 /api
            proxy_pass http://backend_least_conn;
            
            # 可以针对上传接口单独设置限流(可选)
            limit_req zone=api_limit burst=5 nodelay;
        }

        location /api {
            # 应用限流
            limit_req zone=api_limit burst=20 nodelay;
            
            # 启动缓存
            proxy_cache my_cache;
            # 缓存规则
            proxy_cache_valid 200 302 5m;
            proxy_cache_valid 404 1m;
            proxy_cache_valid any 0; 
            # 缓存键(使用原始 URI,包含 /api 前缀)
            # 注意:如果 rewrite 在 proxy_cache_key 之后,缓存键使用原始 URI
            # 如果 rewrite 在 proxy_cache_key 之前,缓存键使用重写后的 URI
            proxy_cache_key "$scheme$request_method$host$request_uri";
            # 缓存状态头
            add_header X-Cache-Status $upstream_cache_status;
            
            # 其他代理设置
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            
            # 去掉 /api 前缀,然后代理到后端服务器
            rewrite ^/api/(.*)$ /$1 break;
            # 反向代理
            proxy_pass http://backend_round_robin;
        }

        # 默认放在最后
        location / {
            root /Users/wuhuan/Desktop/前端资料/工作笔记/nginx_study;
            index test.html;
        }
    }
    # 服务器配置二
    server {
        listen 8084;
        server_name localhost;
        location / {
            root /Users/wuhuan/Desktop/前端资料/工作笔记/nginx_study;
            index index.html;
        }
    }
}