From 6208da97251e77d780ea3e7cec1e0e3579d139df Mon Sep 17 00:00:00 2001 From: query-database Date: Fri, 27 Mar 2026 15:52:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.cjs | 25 +++++++ Dockerfile | 18 +++++ README.md | 19 ++--- api/Dockerfile | 4 +- deploy/AUTODEPLOY-UBUNTU-TENCENT.md | 69 +++++++++++++++++++ deploy/webhook/hooks.json | 26 +++++++ deploy/webhook/nginx-webhook.conf | 14 ++++ deploy/webhook/query-database-webhook.service | 16 +++++ docker-compose.prod.yml | 10 ++- nginx/default.conf | 30 ++++++++ scripts/deploy.sh | 13 ++++ 11 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 Dockerfile create mode 100644 deploy/AUTODEPLOY-UBUNTU-TENCENT.md create mode 100644 deploy/webhook/hooks.json create mode 100644 deploy/webhook/nginx-webhook.conf create mode 100644 deploy/webhook/query-database-webhook.service create mode 100644 nginx/default.conf create mode 100644 scripts/deploy.sh diff --git a/.eslintrc.cjs b/.eslintrc.cjs index e69de29..bf070b7 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -0,0 +1,25 @@ +module.exports = { + root: true, + env: { + browser: true, + es2020: true, + node: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:vue/vue3-recommended', + 'plugin:@typescript-eslint/recommended', + ], + parser: 'vue-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaVersion: 2020, + sourceType: 'module', + }, + rules: { + 'vue/multi-word-component-names': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + }, +} + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ef0d45d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM docker.m.daocloud.io/library/node:20-alpine AS build + +WORKDIR /app + +RUN corepack enable + +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +COPY . . +RUN pnpm run build + + +FROM docker.m.daocloud.io/library/nginx:1.27-alpine + +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx/default.conf /etc/nginx/conf.d/default.conf + diff --git a/README.md b/README.md index 39af6f5..f479e9e 100644 --- a/README.md +++ b/README.md @@ -47,21 +47,24 @@ pnpm run dev ## 部署说明(生产) 当前仓库支持: -- 前端部署到 Vercel(静态站点) -- 后端+MySQL 用 Docker Compose 部署到一台服务器(或任意支持 Docker 的环境) +- 前端+后端+MySQL 用 Docker Compose 部署到一台服务器(或任意支持 Docker 的环境) -### 1) 后端+MySQL(Docker Compose) +### 1) 前端+后端+MySQL(Docker Compose) 在服务器上: ```bash docker compose -f docker-compose.prod.yml up -d ``` -默认会把后端暴露到 `http://<你的服务器>:8080`。 +默认会把前端暴露到 `http://<你的服务器>/`。 -### 2) 前端(Vercel) -- 通过 Vercel 部署本仓库根目录(Vite 构建输出为 `dist`,配置见 `vercel.json`) -- 在 Vercel 项目环境变量中设置: - - `VITE_API_BASE_URL` = `http://<你的服务器>:8080` +前端容器内置 Nginx: +- 静态资源由 Nginx 提供 +- `/api/*` 会反向代理到后端容器 `api:8080`,因此生产环境无需单独配置 `VITE_API_BASE_URL` + +安全默认:生产 compose 默认不对外暴露后端 `8080` 端口(只允许通过前端 Nginx 访问)。你可以用 `http://<你的服务器>/health` 检查后端健康。 + +### 2) 可选:前端单独部署 +如果你希望把前端放到 CDN/其他静态站点,也可以通过环境变量 `VITE_API_BASE_URL` 指向后端。 ## 常用命令 - `pnpm run check`:TypeScript 类型检查 diff --git a/api/Dockerfile b/api/Dockerfile index 6516eab..3ea1cb0 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22-alpine AS build +FROM docker.m.daocloud.io/library/golang:1.23-alpine AS build WORKDIR /src COPY go.mod go.sum ./ @@ -8,7 +8,7 @@ COPY . . RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/api . -FROM alpine:3.20 +FROM docker.m.daocloud.io/library/alpine:3.20 WORKDIR /app RUN adduser -D -u 10001 app diff --git a/deploy/AUTODEPLOY-UBUNTU-TENCENT.md b/deploy/AUTODEPLOY-UBUNTU-TENCENT.md new file mode 100644 index 0000000..130a145 --- /dev/null +++ b/deploy/AUTODEPLOY-UBUNTU-TENCENT.md @@ -0,0 +1,69 @@ +# Ubuntu + Docker Compose(腾讯云)自动化发布方案 + +本项目已经支持“前端+后端+MySQL”同机 Docker Compose 部署:使用 `docker-compose.prod.yml`,对外只暴露 `80` 端口(由 Nginx 托管前端并反向代理 `/api`)。 + +如果你有多个服务,不想每次 SSH 登录手动执行 `git pull` / `docker compose up`,建议用下面两种自动化方案之一。 + +## 方案 A(推荐):Portainer Stack(GitOps)自动更新 + +特点:不需要额外写 CI;适合多服务统一管理;支持“轮询仓库”或“Webhook 触发更新”。 + +### 1) 服务器安装 Portainer(一次性) +```bash +docker volume create portainer_data +docker run -d \ + --name portainer \ + --restart=always \ + -p 9443:9443 \ + -p 9000:9000 \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v portainer_data:/data \ + portainer/portainer-ce:latest +``` + +访问:`https://<服务器IP>:9443`,首次进入创建管理员账号。 + +### 2) 在 Portainer 创建 Stack(从 Git 拉取) +- Stacks → Add stack +- Build method 选 Git repository +- Repository URL:`http://47.95.203.241:3000/ReeseLin/query-database.git` +- Compose path:`docker-compose.prod.yml` +- 选择分支:`main` + +### 3) 开启自动更新 +二选一: +- 轮询更新:开启 “Auto update”,设置间隔(例如 1-5 分钟) +- Webhook 更新:开启 Webhook,复制 URL。以后每次 push 后调用这个 URL,即可自动拉取并重启 + +### 4) 建议的安全组 +- 对外开放:`80`、`9443`(Portainer 管理台) +- 如果只在内网管理 Portainer:建议把 `9443` 限制为你自己的 IP 白名单 + + +## 方案 B(更工程化):腾讯云 TCR + CI 构建镜像 + 服务器自动 pull + +特点:构建在 CI 完成,服务器只负责 pull/重启;可回滚;更适合团队/正式环境。 + +高层流程: +1. push 到 `main` +2. CI 构建镜像:`web`、`api` +3. push 到腾讯云 TCR +4. 服务器 `docker compose pull && docker compose up -d` + +建议: +- `web`/`api` 镜像 tag 用 commit SHA +- 生产 `docker-compose` 改为引用镜像(不再在服务器 build) + +如果你要走这条路线,我可以把: +- 生产 compose 的“镜像版”文件 +- CI 配置(含 TCR 登录/构建/推送/远程重启) +直接补进仓库。 + + +## 方案 C(最轻量):服务器定时拉取(systemd timer) + +特点:无入站端口,无 CI;缺点是“定时检查”不是严格实时。 + +思路:每 1 分钟执行一次: +`git fetch` → 检测远端是否变化 → 变化则 `docker compose up -d --build` + diff --git a/deploy/webhook/hooks.json b/deploy/webhook/hooks.json new file mode 100644 index 0000000..c0f2238 --- /dev/null +++ b/deploy/webhook/hooks.json @@ -0,0 +1,26 @@ +[ + { + "id": "query-database", + "execute-command": "/opt/query-database/scripts/deploy.sh", + "command-working-directory": "/opt/query-database", + "trigger-rule": { + "and": [ + { + "match": { + "type": "value", + "value": "refs/heads/main", + "parameter": { "source": "payload", "name": "ref" } + } + }, + { + "match": { + "type": "value", + "value": "change-me", + "parameter": { "source": "url", "name": "token" } + } + } + ] + } + } +] + diff --git a/deploy/webhook/nginx-webhook.conf b/deploy/webhook/nginx-webhook.conf new file mode 100644 index 0000000..ed934b4 --- /dev/null +++ b/deploy/webhook/nginx-webhook.conf @@ -0,0 +1,14 @@ +server { + listen 80; + server_name _; + + location /hooks/ { + 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_pass http://127.0.0.1:9000; + } +} + diff --git a/deploy/webhook/query-database-webhook.service b/deploy/webhook/query-database-webhook.service new file mode 100644 index 0000000..4c54d7a --- /dev/null +++ b/deploy/webhook/query-database-webhook.service @@ -0,0 +1,16 @@ +[Unit] +Description=query-database deploy webhook +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=deploy +WorkingDirectory=/opt/query-database +ExecStart=/usr/local/bin/webhook -hooks /opt/query-database/deploy/webhook/hooks.json -port 9000 -ip 127.0.0.1 -hotreload +Restart=always +RestartSec=2 + +[Install] +WantedBy=multi-user.target + diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e868ec5..beb15f3 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -24,11 +24,17 @@ services: CORS_ALLOW_ALL: "1" depends_on: - mysql - ports: - - "8080:8080" volumes: - api_data:/app/data + web: + build: + context: . + depends_on: + - api + ports: + - "3999:80" + volumes: mysql_data: api_data: diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..c3f213f --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,30 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location /api/ { + 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_pass http://api:8080; + } + + location = /health { + 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_pass http://api:8080/health; + } + + location / { + try_files $uri $uri/ /index.html; + } +} + diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..58929d4 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR=${REPO_DIR:-/opt/query-database} +BRANCH=${BRANCH:-main} + +cd "$REPO_DIR" + +git fetch --all --prune +git reset --hard "origin/$BRANCH" + +docker compose -f docker-compose.prod.yml up -d --build +