Compare commits
16 Commits
b98a7dc924
...
3c6a92b1d4
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c6a92b1d4 | |||
| 3260ec0e99 | |||
| 179afea2e1 | |||
| ac76faacc4 | |||
| 7773b1dc88 | |||
| 874c06fa51 | |||
| 1c04ff0d7b | |||
| 4dc00ec35d | |||
| 772258adbb | |||
| 5fcb011bd1 | |||
| 7a57d43459 | |||
| 924b66f836 | |||
| 3c89e81de8 | |||
| e6b1d7400d | |||
| b2e831155c | |||
| 14dfa79477 |
103
.gitea/workflows/deploy-web.yaml
Normal file
103
.gitea/workflows/deploy-web.yaml
Normal file
@@ -0,0 +1,103 @@
|
||||
name: Web UI CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
# 仅当以下文件变动时触发构建,避免改个 README 也重新发版
|
||||
- 'apps/web-antd/**'
|
||||
- 'packages/**'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'Dockerfile'
|
||||
- '.gitea/workflows/deploy-web.yaml'
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: xw-runner
|
||||
# 使用包含 Docker 客户端的镜像,这样我们可以在容器内构建镜像 (Docker-in-Docker 模式)
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
# 这里的 options 很重要,有时候 Runner 需要特权才能操作宿主机的 Docker
|
||||
# 如果遇到 permission denied,尝试取消注释下一行:
|
||||
# options: --privileged --user root
|
||||
|
||||
steps:
|
||||
# 1. 检出代码
|
||||
- name: Checkout Code
|
||||
run: |
|
||||
# 清理当前目录,防止旧文件干扰
|
||||
ls -A1 | xargs rm -rf
|
||||
|
||||
# 拼接 Clone URL (支持 http 和 https)
|
||||
SERVER_DOMAIN=$(echo "${{ gitea.server_url }}" | sed -E 's|https?://||')
|
||||
PROTO="http"
|
||||
if [[ "${{ gitea.server_url }}" == https* ]]; then PROTO="https"; fi
|
||||
|
||||
# 使用 Gitea Token 进行认证
|
||||
GIT_URL="${PROTO}://${{ gitea.actor }}:${{ gitea.token }}@${SERVER_DOMAIN}/${{ gitea.repository }}.git"
|
||||
|
||||
echo "Cloning..."
|
||||
git clone --depth 1 "$GIT_URL" .
|
||||
git log -1 --format='%h - %s'
|
||||
|
||||
# 2. 准备 Docker 网络
|
||||
# 使用 1Panel 的默认网络,确保所有服务在同一个网络下,方便互相访问
|
||||
- name: Check Docker Network
|
||||
run: |
|
||||
# 检查 1panel-network 是否存在,如果不存在则创建
|
||||
docker network inspect 1panel-network > /dev/null 2>&1 || docker network create 1panel-network || true
|
||||
|
||||
# 3. 构建并部署
|
||||
- name: Build & Deploy Web UI
|
||||
env:
|
||||
# 镜像名称
|
||||
IMAGE_NAME: 'aiot-web-antd'
|
||||
# 容器名称
|
||||
CONTAINER_NAME: aiot-web-antd
|
||||
# 宿主机端口
|
||||
HOST_PORT: 9090
|
||||
run: |
|
||||
# --- 构建阶段 ---
|
||||
SHORT_SHA=$(git log -1 --format='%h')
|
||||
FULL_IMAGE_NAME="${IMAGE_NAME}:${SHORT_SHA}"
|
||||
|
||||
echo "Building Docker Image: $FULL_IMAGE_NAME..."
|
||||
|
||||
# 使用根目录的 Dockerfile 进行构建
|
||||
# Dockerfile 内部使用了多阶段构建,不需要 Runner 安装 Node
|
||||
# 启用 BuildKit 以支持缓存挂载(加速依赖安装)
|
||||
DOCKER_BUILDKIT=1 docker build -t "$FULL_IMAGE_NAME" -f Dockerfile .
|
||||
|
||||
# 打上 latest 标签
|
||||
docker tag "$FULL_IMAGE_NAME" "${IMAGE_NAME}:latest"
|
||||
|
||||
# --- 部署阶段 ---
|
||||
echo "Deploying Container: $CONTAINER_NAME..."
|
||||
|
||||
# 停止并删除旧容器
|
||||
docker stop $CONTAINER_NAME || true
|
||||
docker rm $CONTAINER_NAME || true
|
||||
|
||||
# 检查端口占用,如果有其他容器占用,先停止它
|
||||
PORT_CONTAINER=$(docker ps --filter "publish=${HOST_PORT}" --format "{{.Names}}" | head -1)
|
||||
if [ ! -z "$PORT_CONTAINER" ]; then
|
||||
echo "⚠️ Port ${HOST_PORT} is occupied by container: $PORT_CONTAINER"
|
||||
echo "Stopping conflicting container..."
|
||||
docker stop $PORT_CONTAINER || true
|
||||
fi
|
||||
|
||||
# 启动新容器
|
||||
# -d: 后台运行
|
||||
# --restart always: 开机自启
|
||||
# -p: 端口映射 宿主机:容器
|
||||
docker run -d \
|
||||
--name $CONTAINER_NAME \
|
||||
--network 1panel-network \
|
||||
--restart always \
|
||||
-p ${HOST_PORT}:80 \
|
||||
"${IMAGE_NAME}:latest"
|
||||
|
||||
echo "✅ Deployment Successful! Access at port ${HOST_PORT}"
|
||||
|
||||
61
Dockerfile
Normal file
61
Dockerfile
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
# 1. 构建阶段
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
# 设置 npm 和 pnpm 镜像源为淘宝源(加速下载)
|
||||
RUN npm config set registry https://registry.npmmirror.com && \
|
||||
echo "registry=https://registry.npmmirror.com" > ~/.npmrc
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 启用 pnpm (使用 corepack)
|
||||
# 设置环境变量让 Corepack 也从镜像源下载
|
||||
ENV COREPACK_NPM_REGISTRY=https://registry.npmmirror.com
|
||||
# 设置 pnpm store 路径,便于缓存
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable && corepack prepare pnpm@10.22.0 --activate
|
||||
|
||||
# 配置 pnpm 使用淘宝镜像源和自定义 store 路径(便于缓存)
|
||||
RUN pnpm config set registry https://registry.npmmirror.com && \
|
||||
pnpm config set store-dir /pnpm/store
|
||||
|
||||
# 单独复制依赖描述文件,利用 Docker 缓存层
|
||||
# 先只复制这些文件,如果它们没变,Docker 会复用缓存,跳过后续步骤
|
||||
# pnpm-workspace.yaml 包含 catalog 配置,pnpm 需要它来解析 catalog: 引用
|
||||
COPY package.json pnpm-lock.yaml turbo.json pnpm-workspace.yaml ./
|
||||
|
||||
# 复制所有 package.json(Monorepo 需要)
|
||||
# internal 目录包含构建工具包(@vben/tsconfig, @vben/vite-config 等),构建时需要
|
||||
COPY packages packages
|
||||
COPY apps apps
|
||||
COPY internal internal
|
||||
|
||||
# 安装依赖
|
||||
# 使用 BuildKit 缓存挂载加速依赖安装
|
||||
# 缓存 pnpm store,即使 package.json 变了,已下载的包也能复用
|
||||
# 使用明确的缓存路径和 ID,确保缓存持久化
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# 构建指定项目 (根据 package.json 里的 scripts)
|
||||
# 这里我们构建 antd 版本
|
||||
RUN pnpm build:antd
|
||||
|
||||
# 2. 运行阶段
|
||||
FROM nginx:alpine
|
||||
|
||||
# 移除默认配置
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
|
||||
# 复制自定义 Nginx 配置
|
||||
COPY apps/web-antd/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# 复制构建产物
|
||||
# 注意:Vben 5 的产物目录通常在 apps/web-antd/dist
|
||||
COPY --from=builder /app/apps/web-antd/dist /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
@@ -4,7 +4,7 @@ VITE_PORT=5666
|
||||
VITE_BASE=/
|
||||
|
||||
# 请求路径
|
||||
VITE_BASE_URL=http://127.0.0.1:48080
|
||||
VITE_BASE_URL=http://172.17.16.14:48080
|
||||
# 接口地址
|
||||
VITE_GLOB_API_URL=/admin-api
|
||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
VITE_BASE=/
|
||||
|
||||
# 请求路径
|
||||
VITE_BASE_URL=http://127.0.0.1:48080
|
||||
VITE_BASE_URL=/admin-api
|
||||
# 接口地址
|
||||
VITE_GLOB_API_URL=http://127.0.0.1:48080/admin-api
|
||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
|
||||
VITE_GLOB_API_URL=/admin-api
|
||||
# 文件上传类型:server - 后端上传<EFBFBD><EFBFBD>?client - 前端直连上传,仅支持S3服务
|
||||
VITE_UPLOAD_TYPE=server
|
||||
|
||||
# 是否开启压缩,可以设置为 none, brotli, gzip
|
||||
# 是否开启压缩,可以设置<EFBFBD><EFBFBD>?none, brotli, gzip
|
||||
VITE_COMPRESS=none
|
||||
|
||||
# 是否开启 PWA
|
||||
# 是否开<EFBFBD><EFBFBD>?PWA
|
||||
VITE_PWA=false
|
||||
|
||||
# vue-router 的模式
|
||||
VITE_ROUTER_HISTORY=hash
|
||||
# vue-router 的模<EFBFBD><EFBFBD>?VITE_ROUTER_HISTORY=hash
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
77
apps/web-antd/nginx.conf
Normal file
77
apps/web-antd/nginx.conf
Normal file
@@ -0,0 +1,77 @@
|
||||
# 配置 DNS 解析器(使用 Docker 内置 DNS)
|
||||
resolver 127.0.0.11 valid=30s;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost; # 通配符,匹配所有域名(适用于反向代理场景)
|
||||
|
||||
# 开启 gzip 压缩
|
||||
gzip on;
|
||||
gzip_min_length 1k;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# API 反向代理到后端服务
|
||||
# 后端服务在 Docker 容器中,使用容器名访问(都在 1panel-network 网络中)
|
||||
# 如果后端在宿主机上,改为:http://172.17.0.1:48080/admin-api
|
||||
location /admin-api {
|
||||
# 后端服务地址
|
||||
# 方案1:使用容器名(推荐,自动 DNS 解析)- 如果后端监听 0.0.0.0
|
||||
proxy_pass http://aiot-server:48080/admin-api;
|
||||
|
||||
# 方案2:直接使用后端容器 IP(当前后端 IP: 172.22.0.3)
|
||||
# 如果方案1不行,取消注释下面这行,注释掉上面那行
|
||||
# proxy_pass http://172.22.0.3:48080/admin-api;
|
||||
|
||||
# 方案3:通过宿主机网关访问(如果后端只监听 127.0.0.1)
|
||||
# proxy_pass http://172.22.0.1:48080/admin-api;
|
||||
|
||||
# 代理超时设置
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
|
||||
# 错误处理:如果后端不可用,返回友好的错误信息
|
||||
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
|
||||
proxy_next_upstream_tries 2;
|
||||
|
||||
# 代理请求头
|
||||
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;
|
||||
|
||||
# 解决跨域问题
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
|
||||
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
|
||||
|
||||
# 处理 OPTIONS 预检请求
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# 解决 SPA 路由刷新 404 问题
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# 静态资源缓存配置
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
|
||||
# 错误页面
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
|
||||
81
docs/DEPLOY.md
Normal file
81
docs/DEPLOY.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Gitea CI/CD 部署指南
|
||||
|
||||
本文档描述了 `aiot-platform-ui` (Web Antd 端) 的自动化构建与部署流程。
|
||||
|
||||
## 1. 架构说明
|
||||
|
||||
项目采用 **Gitea Actions + Docker** 进行部署。
|
||||
|
||||
- **构建方式**:Docker 多阶段构建 (Multi-stage Build)。
|
||||
- Stage 1: `node:20-alpine` -> 安装依赖 -> `pnpm build:web-antd` -> 生成 `dist`。
|
||||
- Stage 2: `nginx:alpine` -> 复制 `dist` -> 启动 Web 服务。
|
||||
- **运行环境**:目标服务器 (安装了 Gitea Runner 和 Docker)。
|
||||
- **网络模式**:容器加入 `aiot-net` 网络,端口映射到宿主机。
|
||||
|
||||
## 2. 前置准备
|
||||
|
||||
在使用 CI/CD 前,请确保目标服务器满足以下条件:
|
||||
|
||||
1. **安装 Docker**: `docker -v` 可用。
|
||||
2. **配置 Gitea Runner**:
|
||||
- Runner 已注册到 Gitea。
|
||||
- Runner 有权限访问 Docker Socket (`/var/run/docker.sock`)。
|
||||
3. **Docker 网络**:
|
||||
- 项目使用 1Panel 的默认网络 `1panel-network`
|
||||
- CI/CD 会自动检查网络是否存在,如果不存在则创建
|
||||
- 确保前端和后端容器都在同一个网络中
|
||||
|
||||
## 3. 端口规划
|
||||
|
||||
| 服务名称 | 容器端口 | 宿主机端口 (Host Port) | 说明 |
|
||||
| :-- | :-- | :-- | :-- |
|
||||
| **aiot-web-antd** | 80 | **9090** | 前端 Web 服务,请在 1Panel 中反代此端口 |
|
||||
| aiot-server | 48080 | 48080 | 后端 API 服务 (参考) |
|
||||
|
||||
> **注意**:如果 9090 端口已被占用,请修改 `.gitea/workflows/deploy-web.yaml` 中的 `HOST_PORT` 变量。
|
||||
|
||||
### 部署失败:端口已被占用 (`port is already allocated`)
|
||||
|
||||
- **原因**:宿主机端口被其他容器或服务占用。
|
||||
- **解法**:
|
||||
1. 修改 workflow 中的 `HOST_PORT` 为其他端口(如 8082、8083)。
|
||||
2. 或者手动停止占用端口的容器:`docker ps | grep <端口号>` 然后 `docker stop <容器名>`。
|
||||
|
||||
## 4. 1Panel 配置指南
|
||||
|
||||
由于容器运行在 `127.0.0.1:8080`,需要在 1Panel (或 Nginx) 中配置反向代理以通过域名访问。
|
||||
|
||||
1. **进入 1Panel** -> **网站** -> **创建网站**。
|
||||
2. **域名**: 填写你的域名 (e.g., `admin.example.com`)。
|
||||
3. **类型**: `反向代理`。
|
||||
4. **代理地址**: `http://127.0.0.1:9090`。
|
||||
5. **提交**。
|
||||
|
||||
## 5. 常见问题排查
|
||||
|
||||
### 构建失败:`pnpm install` 很慢或超时
|
||||
|
||||
- **原因**:网络问题。
|
||||
- **解法**:Dockerfile 中已配置 npm 淘宝源。如果依然慢,考虑在 Runner 机器上配置透明代理。
|
||||
|
||||
### 部署失败:`Permission denied` 访问 Docker
|
||||
|
||||
- **原因**:Gitea Runner 用户没有 Docker 组权限。
|
||||
- **解法**:
|
||||
1. 在服务器执行:`sudo usermod -aG docker <runner-user>`。
|
||||
2. 或者在 workflow yaml 中开启 `privileged: true` (不推荐)。
|
||||
|
||||
### 页面刷新 404
|
||||
|
||||
- **原因**:Nginx 未配置 SPA 重定向。
|
||||
- **解法**:检查 `apps/web-antd/nginx.conf` 是否包含 `try_files $uri $uri/ /index.html;`。
|
||||
|
||||
## 6. 如何触发部署
|
||||
|
||||
- 修改代码后,推送到 `master` 分支:
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: 更新功能"
|
||||
git push origin master
|
||||
```
|
||||
- 前往 Gitea 仓库页面 -> **Actions** 查看构建进度。
|
||||
Reference in New Issue
Block a user