Sfoglia il codice sorgente

Add Docker deployment support and GitHub Actions

537yaha 6 mesi fa
parent
commit
09c67e8158

+ 26 - 0
.dockerignore

@@ -0,0 +1,26 @@
+# Git
+.git
+.gitignore
+
+# 环境变量文件
+.env
+.env.local
+.env.production
+
+# 文档
+doc/
+*.md
+!README.md
+
+# IDE
+.vscode
+.idea
+*.swp
+*.swo
+
+# 日志
+*.log
+
+# 系统文件
+.DS_Store
+Thumbs.db

+ 45 - 0
.env.example

@@ -0,0 +1,45 @@
+# ============================================
+# AI-CS 系统 Docker 部署配置模板
+# ============================================
+# 使用方法:
+# 1. 复制此文件为 .env:cp .env.example .env
+# 2. 编辑 .env 文件,修改以下配置
+# 3. .env 文件不要提交到 Git!
+
+# ============================================
+# MySQL 数据库配置
+# ============================================
+MYSQL_ROOT_PASSWORD=rootpassword
+MYSQL_DATABASE=ai_cs
+MYSQL_USER=ai_cs_user
+MYSQL_PASSWORD=ai_cs_password
+MYSQL_PORT=3306
+
+# ============================================
+# 后端服务配置
+# ============================================
+BACKEND_PORT=8080
+ADMIN_USERNAME=admin
+ADMIN_PASSWORD=admin123
+GIN_MODE=release
+
+# 加密密钥(用于加密 AI API Keys)
+# 生成方式:
+# - Linux/Mac: openssl rand -hex 32
+# - Windows PowerShell: -join ((48..57) + (97..102) | Get-Random -Count 64 | ForEach-Object {[char]$_})
+# - 在线工具: https://www.random.org/strings/?num=1&len=64&digits=on&upperalpha=off&loweralpha=on&unique=off&format=html&rnd=new
+ENCRYPTION_KEY=changeme_please_use_random_hex_32_bytes_here
+
+# ============================================
+# 前端服务配置
+# ============================================
+FRONTEND_PORT=3000
+# 前端访问后端的地址(Docker 内部使用容器名,外部访问使用 localhost)
+NEXT_PUBLIC_API_BASE_URL=http://localhost:8080
+
+# ============================================
+# 生产版镜像配置(仅用于 docker-compose.prod.yml)
+# ============================================
+# 如果使用预构建镜像,可以指定镜像名称(可选)
+# BACKEND_IMAGE=2930134478/ai-cs-backend:latest
+# FRONTEND_IMAGE=2930134478/ai-cs-frontend:latest

+ 16 - 0
.github/workflows/docker-build.yml

@@ -0,0 +1,16 @@
+name: Build and Push Docker Image
+
+on:
+  push:
+    branches: [ main ]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - name: Build and push
+        uses: docker/build-push-action@v4
+        with:
+          push: true
+          tags: 537yaha/ai-cs:latest

+ 166 - 7
README.md

@@ -24,20 +24,179 @@
 
 ## 🚀 快速开始
 
-### 环境要求
+### 方式一:预构建镜像一键部署(推荐,最简单)⭐
 
-- Go 1.21 或更高版本
+> **最简单快捷的方式**,直接使用预构建的 Docker 镜像,无需构建,一行命令启动。
+
+#### 前置要求
+
+- Docker Desktop(Windows/Mac)或 Docker + Docker Compose(Linux)
+
+#### 部署步骤
+
+1. **克隆项目并进入目录**
+
+```bash
+git clone https://github.com/2930134478/AI-CS.git
+cd AI-CS
+```
+
+2. **配置环境变量**
+
+```bash
+# 复制环境变量模板
+cp .env.example .env
+
+# 编辑 .env 文件,至少修改以下配置:
+# - MYSQL_ROOT_PASSWORD: MySQL root 密码
+# - ADMIN_PASSWORD: 管理员密码(首次登录使用)
+# - ENCRYPTION_KEY: 加密密钥(生成 64 位十六进制字符串)
+```
+
+生成加密密钥:
+
+```bash
+# Linux/Mac
+openssl rand -hex 32
+
+# Windows PowerShell
+-join ((48..57) + (97..102) | Get-Random -Count 64 | ForEach-Object {[char]$_})
+```
+
+3. **一键启动**
+
+```bash
+# 使用预构建镜像启动(自动从 Docker Hub 拉取镜像)
+docker-compose -f docker-compose.prod.yml up -d
+```
+
+就这么简单!🎉
+
+4. **访问应用**
+
+- **前端首页**: http://localhost:3000
+- **访客聊天**: http://localhost:3000/chat
+- **客服登录**: http://localhost:3000/agent/login
+  - 用户名:`admin`(或 `.env` 中配置的 `ADMIN_USERNAME`)
+  - 密码:`.env` 中配置的 `ADMIN_PASSWORD`
+
+#### 常用命令
+
+```bash
+# 查看日志
+docker-compose -f docker-compose.prod.yml logs -f
+
+# 查看服务状态
+docker-compose -f docker-compose.prod.yml ps
+
+# 停止服务
+docker-compose -f docker-compose.prod.yml stop
+
+# 停止并删除容器(保留数据)
+docker-compose -f docker-compose.prod.yml down
+
+# 完全重置(删除所有数据)
+docker-compose -f docker-compose.prod.yml down -v
+```
+
+---
+
+### 方式二:Docker 本地构建部署
+
+> 适合需要自定义构建或网络无法访问 Docker Hub 的情况。
+
+#### 前置要求
+
+- Docker Desktop(Windows/Mac)或 Docker + Docker Compose(Linux)
+- Git
+
+#### 部署步骤
+
+1. **克隆项目**
+
+```bash
+git clone https://github.com/2930134478/AI-CS.git
+cd AI-CS
+```
+
+2. **配置环境变量**
+
+```bash
+# 复制环境变量模板
+cp .env.example .env
+
+# 编辑 .env 文件,至少修改以下配置:
+# - MYSQL_ROOT_PASSWORD: MySQL root 密码
+# - ADMIN_PASSWORD: 管理员密码(首次登录使用)
+# - ENCRYPTION_KEY: 加密密钥(生成 64 位十六进制字符串)
+```
+
+生成加密密钥:
+
+```bash
+# Linux/Mac
+openssl rand -hex 32
+
+# Windows PowerShell
+-join ((48..57) + (97..102) | Get-Random -Count 64 | ForEach-Object {[char]$_})
+```
+
+3. **构建并启动服务**
+
+```bash
+# 构建并启动所有服务(首次构建需要一些时间)
+docker-compose up -d --build
+
+# 查看日志
+docker-compose logs -f
+
+# 查看服务状态
+docker-compose ps
+```
+
+4. **访问应用**
+
+- **前端首页**: http://localhost:3000
+- **访客聊天**: http://localhost:3000/chat
+- **客服登录**: http://localhost:3000/agent/login
+  - 用户名:`admin`(或 `.env` 中配置的 `ADMIN_USERNAME`)
+  - 密码:`.env` 中配置的 `ADMIN_PASSWORD`
+
+#### 常用命令
+
+```bash
+# 停止服务
+docker-compose stop
+
+# 停止并删除容器(保留数据)
+docker-compose down
+
+# 完全重置(删除所有数据)
+docker-compose down -v
+
+# 查看日志
+docker-compose logs -f backend
+docker-compose logs -f frontend
+```
+
+---
+
+### 方式三:传统部署(手动安装)
+
+#### 环境要求
+
+- Go 1.24 或更高版本
 - Node.js 18+ 和 npm/yarn
 - MySQL 8.0 或更高版本
 
-### 1. 克隆项目
+#### 1. 克隆项目
 
 ```bash
 git clone https://github.com/2930134478/AI-CS.git
 cd AI-CS
 ```
 
-### 2. 配置后端
+#### 2. 配置后端
 
 ```bash
 cd backend
@@ -73,7 +232,7 @@ go run main.go
 
 > ⚠️ **重要**:`ADMIN_PASSWORD` 是必填项,如果不设置,系统不会创建默认管理员账号。
 
-### 3. 配置前端
+#### 3. 配置前端
 
 ```bash
 cd frontend
@@ -85,7 +244,7 @@ npm install
 npm run dev
 ```
 
-### 4. 访问应用
+#### 4. 访问应用
 
 - **官网首页**: http://localhost:3000
 - **访客聊天**: 
@@ -93,7 +252,7 @@ npm run dev
   - 或点击首页右下角的客服插件按钮
 - **客服登录**: http://localhost:3000/agent/login
 
-### 5. 默认管理员账号
+#### 5. 默认管理员账号
 
 ⚠️ **重要说明**:
 

+ 35 - 0
backend/.dockerignore

@@ -0,0 +1,35 @@
+# Git
+.git
+.gitignore
+
+# 编译产物
+backend.exe
+backend
+*.exe
+*.dll
+*.so
+
+# 环境变量
+.env
+.env.local
+
+# 上传文件(保留目录结构但不包含文件)
+uploads/*
+!uploads/.gitkeep
+
+# 日志
+*.log
+
+# IDE
+.vscode
+.idea
+*.swp
+
+# 系统文件
+.DS_Store
+Thumbs.db
+
+# 其他
+node_modules
+package.json
+package-lock.json

+ 52 - 0
backend/Dockerfile

@@ -0,0 +1,52 @@
+# ============================================
+# 多阶段构建:构建阶段
+# ============================================
+FROM golang:1.24-alpine AS builder
+
+WORKDIR /app
+
+# 安装必要的构建工具
+RUN apk add --no-cache git ca-certificates tzdata
+
+# 复制 go mod 文件
+COPY go.mod go.sum ./
+
+# 下载依赖
+RUN go mod download
+
+# 复制源代码
+COPY . .
+
+# 构建应用
+# CGO_ENABLED=0: 禁用 CGO,生成纯 Go 二进制文件
+# GOOS=linux: 目标操作系统
+# GOARCH=amd64: 目标架构
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o backend main.go
+
+# ============================================
+# 运行阶段
+# ============================================
+FROM alpine:latest
+
+WORKDIR /app
+
+# 安装必要的运行时依赖
+RUN apk --no-cache add ca-certificates tzdata
+
+# 从构建阶段复制二进制文件
+COPY --from=builder /app/backend .
+
+# 从构建阶段复制时区数据(可选)
+COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
+
+# 设置时区(可选,默认 UTC)
+ENV TZ=Asia/Shanghai
+
+# 创建必要的目录
+RUN mkdir -p uploads/avatars uploads/messages
+
+# 暴露端口
+EXPOSE 8080
+
+# 启动应用
+CMD ["./backend"]

+ 75 - 0
docker-compose.prod.yml

@@ -0,0 +1,75 @@
+
+
+services:
+  # MySQL 数据库
+  mysql:
+    image: mysql:8.0
+    container_name: ai-cs-mysql
+    environment:
+      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
+      MYSQL_DATABASE: ${MYSQL_DATABASE:-ai_cs}
+      MYSQL_USER: ${MYSQL_USER:-ai_cs_user}
+      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-ai_cs_password}
+    ports:
+      - "${MYSQL_PORT:-3306}:3306"
+    volumes:
+      - mysql_data:/var/lib/mysql
+    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
+    healthcheck:
+      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-rootpassword}"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+      start_period: 30s
+    networks:
+      - ai-cs-network
+    restart: unless-stopped
+
+  # 后端服务(使用预构建镜像)
+  backend:
+    image: ${BACKEND_IMAGE:-537yaha/ai-cs-backend:latest}
+    container_name: ai-cs-backend
+    environment:
+      DB_HOST: mysql
+      DB_PORT: 3306
+      DB_USER: ${MYSQL_USER:-ai_cs_user}
+      DB_PASSWORD: ${MYSQL_PASSWORD:-ai_cs_password}
+      DB_NAME: ${MYSQL_DATABASE:-ai_cs}
+      ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
+      ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin123}
+      SERVER_HOST: 0.0.0.0
+      SERVER_PORT: 8080
+      GIN_MODE: ${GIN_MODE:-release}
+      ENCRYPTION_KEY: ${ENCRYPTION_KEY:-default-key}
+    ports:
+      - "${BACKEND_PORT:-8080}:8080"
+    volumes:
+      - ./backend/uploads:/app/uploads
+    depends_on:
+      mysql:
+        condition: service_healthy
+    networks:
+      - ai-cs-network
+    restart: unless-stopped
+
+  # 前端服务(使用预构建镜像)
+  frontend:
+    image: ${FRONTEND_IMAGE:-537yaha/ai-cs-frontend:latest}
+    container_name: ai-cs-frontend
+    environment:
+      NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-http://localhost:8080}
+    ports:
+      - "${FRONTEND_PORT:-3000}:3000"
+    depends_on:
+      - backend
+    networks:
+      - ai-cs-network
+    restart: unless-stopped
+
+volumes:
+  mysql_data:
+    driver: local
+
+networks:
+  ai-cs-network:
+    driver: bridge

+ 79 - 0
docker-compose.yml

@@ -0,0 +1,79 @@
+
+
+services:
+  # MySQL 数据库
+  mysql:
+    image: mysql:8.0
+    container_name: ai-cs-mysql
+    environment:
+      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
+      MYSQL_DATABASE: ${MYSQL_DATABASE:-ai_cs}
+      MYSQL_USER: ${MYSQL_USER:-ai_cs_user}
+      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-ai_cs_password}
+    ports:
+      - "${MYSQL_PORT:-3306}:3306"
+    volumes:
+      - mysql_data:/var/lib/mysql
+    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
+    healthcheck:
+      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-rootpassword}"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+      start_period: 30s
+    networks:
+      - ai-cs-network
+    restart: unless-stopped
+
+  # 后端服务(本地构建)
+  backend:
+    build:
+      context: ./backend
+      dockerfile: Dockerfile
+    container_name: ai-cs-backend
+    environment:
+      DB_HOST: mysql
+      DB_PORT: 3306
+      DB_USER: ${MYSQL_USER:-ai_cs_user}
+      DB_PASSWORD: ${MYSQL_PASSWORD:-ai_cs_password}
+      DB_NAME: ${MYSQL_DATABASE:-ai_cs}
+      ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
+      ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin123}
+      SERVER_HOST: 0.0.0.0
+      SERVER_PORT: 8080
+      GIN_MODE: ${GIN_MODE:-release}
+      ENCRYPTION_KEY: ${ENCRYPTION_KEY:-default-key}
+    ports:
+      - "${BACKEND_PORT:-8080}:8080"
+    volumes:
+      - ./backend/uploads:/app/uploads
+    depends_on:
+      mysql:
+        condition: service_healthy
+    networks:
+      - ai-cs-network
+    restart: unless-stopped
+
+  # 前端服务(本地构建)
+  frontend:
+    build:
+      context: ./frontend
+      dockerfile: Dockerfile
+      args:
+        NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-http://localhost:8080}
+    container_name: ai-cs-frontend
+    ports:
+      - "${FRONTEND_PORT:-3000}:3000"
+    depends_on:
+      - backend
+    networks:
+      - ai-cs-network
+    restart: unless-stopped
+
+volumes:
+  mysql_data:
+    driver: local
+
+networks:
+  ai-cs-network:
+    driver: bridge

+ 42 - 0
frontend/.dockerignore

@@ -0,0 +1,42 @@
+# Git
+.git
+.gitignore
+
+# 环境变量
+.env
+.env.local
+.env.production
+.env.development
+
+# 构建产物
+.next
+dist
+out
+build
+
+# 依赖
+node_modules
+
+# 日志
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# IDE
+.vscode
+.idea
+*.swp
+*.swo
+
+# 测试
+coverage
+.nyc_output
+
+# 系统文件
+.DS_Store
+Thumbs.db
+
+# 其他
+*.tsbuildinfo
+.eslintcache

+ 50 - 0
frontend/Dockerfile

@@ -0,0 +1,50 @@
+# ============================================
+# 多阶段构建:构建阶段
+# ============================================
+FROM node:18-alpine AS builder
+
+WORKDIR /app
+
+# 复制 package 文件
+COPY package*.json ./
+
+# 安装依赖(使用 npm ci 确保依赖版本一致)
+RUN npm ci
+
+# 复制源代码
+COPY . .
+
+# 构建参数(Next.js 环境变量必须在构建时传入)
+ARG NEXT_PUBLIC_API_BASE_URL
+ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
+
+# 构建 Next.js 应用
+RUN npm run build
+
+# ============================================
+# 运行阶段
+# ============================================
+FROM node:18-alpine
+
+WORKDIR /app
+
+# 设置生产环境
+ENV NODE_ENV=production
+
+# 复制 package 文件(用于安装生产依赖)
+COPY package*.json ./
+
+# 只安装生产依赖
+RUN npm ci --only=production && npm cache clean --force
+
+# 从构建阶段复制必要的文件
+COPY --from=builder /app/next.config.ts ./
+COPY --from=builder /app/tsconfig.json ./
+COPY --from=builder /app/public ./public
+COPY --from=builder /app/.next ./.next
+
+# 暴露端口
+EXPOSE 3000
+
+# 启动 Next.js 生产服务器
+CMD ["npm", "start"]

+ 0 - 65
frontend/README-配置说明.md

@@ -1,65 +0,0 @@
-# 前端配置说明
-
-## 真实设备测试配置
-
-### 步骤 1:创建 `.env.local` 文件
-
-在 `frontend` 目录下创建 `.env.local` 文件(如果不存在)。
-
-### 步骤 2:配置 IP 地址
-
-在 `.env.local` 文件中添加以下内容:
-
-```env
-# 将 192.168.124.9 替换为你的电脑 IP 地址
-# 获取 IP 地址:在 PowerShell 中运行 ipconfig
-NEXT_PUBLIC_API_BASE_URL=http://192.168.124.9:8080
-```
-
-**你的 IP 地址**:根据 `ipconfig` 结果,你的 IP 地址是 **192.168.124.9**
-
-### 步骤 3:重启前端服务
-
-```bash
-# 停止当前服务(Ctrl+C)
-# 重新启动
-npm run dev
-```
-
-### 步骤 4:在手机/平板上访问
-
-1. 确保手机/平板连接**同一 WiFi**
-2. 在手机浏览器输入:`http://192.168.124.9:3000`
-
----
-
-## 本地开发配置(默认)
-
-如果只想在本地开发,不需要配置 `.env.local` 文件,使用默认配置即可:
-
-- 前端:`http://localhost:3000`
-- 后端:`http://127.0.0.1:8080`
-
----
-
-## 注意事项
-
-1. ✅ **防火墙**:确保 Windows 防火墙允许端口 3000 和 8080
-2. ✅ **同一网络**:手机和电脑必须在同一 WiFi 网络
-3. ✅ **后端配置**:后端已配置为监听 `0.0.0.0:8080`,允许外部访问
-
----
-
-## 快速配置
-
-**Windows PowerShell**:
-
-```powershell
-# 在 frontend 目录下运行
-@"
-NEXT_PUBLIC_API_BASE_URL=http://192.168.124.9:8080
-"@ | Out-File -FilePath ".env.local" -Encoding utf8
-```
-
-(记得将 `192.168.124.9` 替换为你的实际 IP 地址)
-

+ 0 - 36
frontend/README.md

@@ -1,36 +0,0 @@
-This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
-
-## Getting Started
-
-First, run the development server:
-
-```bash
-npm run dev
-# or
-yarn dev
-# or
-pnpm dev
-# or
-bun dev
-```
-
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
-
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
-
-This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
-
-## Learn More
-
-To learn more about Next.js, take a look at the following resources:
-
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
-
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
-
-## Deploy on Vercel
-
-The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
-
-Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

+ 5 - 0
frontend/app/layout.tsx

@@ -1,6 +1,7 @@
 import type { Metadata } from "next";
 import { Geist, Geist_Mono } from "next/font/google";
 import "./globals.css";
+import MatomoTracker from "@/components/MatomoTracker";
 
 const geistSans = Geist({
   variable: "--font-geist-sans",
@@ -17,6 +18,9 @@ export const metadata: Metadata = {
   description: "融合 AI 技术与人工客服,为企业提供高效、智能的客户服务解决方案",
 };
 
+// Matomo 容器 URL(格式:container_*.js)
+const MATOMO_CONTAINER_URL = process.env.NEXT_PUBLIC_MATOMO_CONTAINER_URL || '';
+
 export default function RootLayout({
   children,
 }: Readonly<{
@@ -28,6 +32,7 @@ export default function RootLayout({
         className={`${geistSans.variable} ${geistMono.variable} antialiased`}
       >
         {children}
+        {MATOMO_CONTAINER_URL && <MatomoTracker containerUrl={MATOMO_CONTAINER_URL} />}
       </body>
     </html>
   );

+ 33 - 0
frontend/components/MatomoTracker.tsx

@@ -0,0 +1,33 @@
+'use client';
+
+import { useEffect } from 'react';
+
+interface MatomoTrackerProps {
+  containerUrl?: string;
+}
+
+export default function MatomoTracker({ containerUrl }: MatomoTrackerProps) {
+  useEffect(() => {
+    if (!containerUrl) {
+      console.warn('Matomo 容器 URL 未配置');
+      return;
+    }
+
+    // 初始化 Matomo Tag Manager
+    const _mtm = (window._mtm = window._mtm || []);
+    _mtm.push({ 'mtm.startTime': new Date().getTime(), event: 'mtm.Start' });
+    
+    const d = document;
+    const g = d.createElement('script');
+    const s = d.getElementsByTagName('script')[0];
+    
+    g.async = true;
+    g.src = containerUrl;
+    if (s.parentNode) {
+      s.parentNode.insertBefore(g, s);
+    }
+  }, [containerUrl]);
+
+  return null;
+}
+