从零搭建全栈博客系统:Go + Vue 3 + Docker 全流程实战

从零搭建全栈博客系统:Go + Vue 3 + Docker 全流程实战

前言

一直想拥有一个属于自己的博客系统,不想用现成的 WordPress 或 Hexo,而是想从零开始,亲手搭建一个前后端分离的全栈应用。经过一段时间的开发,最终完成了 JunBlog ------ 一个基于 Go + Vue 3 的全栈个人博客系统。

本文将分享整个项目的技术选型、架构设计、核心功能实现以及 Docker 部署方案,希望能给同样想造轮子的朋友一些参考。

🔗 项目地址:https://github.com/super164/junblog


技术栈总览

层级 技术选型
后端 Go 1.25 + Gin + GORM
前端 Vue 3 + Vite + Vue Router 4
数据库 MySQL 8.0 + Redis 7
认证 JWT + GitHub OAuth
部署 Docker Compose + Nginx + Jenkins CI/CD

选择 Go 做后端是因为它的高性能、简洁语法和出色的并发能力;Vue 3 则是因为上手快、生态完善,配合 Vite 开发体验极佳。


项目架构设计

后端:分层架构

后端采用经典的 分层架构,参考了 Go 社区推荐的标准项目布局:

复制代码
blog_backend/
├── cmd/server/main.go        # 程序入口
├── configs/config.yaml       # 配置文件
├── internal/
│   ├── app/app.go            # 应用初始化(依赖注入)
│   ├── api/
│   │   ├── router.go         # 路由注册
│   │   └── v1/               # API 版本控制
│   │       ├── auth/         # 认证模块
│   │       ├── article/      # 文章模块
│   │       ├── category/     # 分类模块
│   │       ├── tag/          # 标签模块
│   │       ├── comment/      # 评论模块
│   │       ├── interaction/  # 互动模块(点赞/收藏)
│   │       ├── user/         # 用户模块
│   │       └── setting/      # 站点设置
│   ├── middleware/            # 中间件(JWT、CORS、日志)
│   ├── model/
│   │   ├── entity/           # 数据库实体
│   │   └── dto/              # 数据传输对象
│   ├── repository/           # 数据访问层
│   └── service/              # 业务逻辑层
├── pkg/                      # 公共包
│   ├── config/               # 配置加载
│   ├── database/             # 数据库连接
│   ├── jwt/                  # JWT 工具
│   ├── logger/               # 日志(Zap)
│   ├── response/             # 统一响应
│   └── errors/               # 错误处理
└── Makefile                  # 构建脚本

核心设计原则:

  • 关注点分离:每层只关心自己的职责,Repository 层只做数据操作,Service 层只做业务逻辑
  • 依赖注入 :通过 app.go 统一初始化和注入依赖,避免循环依赖
  • 接口驱动:Repository 和 Service 都定义了接口,方便测试和替换实现

前端:模块化组织

复制代码
bolg_forntend/src/
├── App.vue                   # 根组件
├── main.js                   # 入口
├── router/index.js           # 路由配置
├── services/api.js           # Axios API 封装
├── pages/                    # 页面组件
│   ├── HomePage.vue
│   ├── ArticlePage.vue
│   ├── LoginPage.vue
│   └── ...
├── components/               # 通用组件
├── composables/              # 组合式函数
├── modules/admin/            # 后台管理模块
├── data/                     # 静态数据
└── style.css                 # 全局样式

前端保持轻量,没有引入 Vuex/Pinia 等状态管理库,而是通过 组合式函数(Composables) 管理共享状态,对于个人博客来说完全够用。


核心功能实现

1. JWT 认证体系

JWT 认证是整个系统的安全基石。实现思路:

Token 双令牌机制:

  • Access Token:有效期 15 分钟,用于接口鉴权
  • Refresh Token:有效期 7 天,用于无感刷新

认证中间件核心逻辑:

go 复制代码
// middleware/auth.go
func Auth() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 1. 从 Header 获取 Authorization
        authHeader := c.GetHeader("Authorization")

        // 2. 解析 Bearer Token
        parts := strings.SplitN(authHeader, " ", 2)
        if len(parts) != 2 || parts[0] != "Bearer" {
            response.Unauthorized(c, "令牌格式错误")
            c.Abort()
            return
        }

        // 3. 验证 Token 有效性
        claims, err := jwt.ParseToken(parts[1])
        if err != nil {
            response.Unauthorized(c, err.Error())
            c.Abort()
            return
        }

        // 4. 检查用户状态(是否被封禁)
        userRepo := repository.NewUserRepository(database.GetMySQL())
        user, err := userRepo.FindByID(claims.UserID)
        if !user.Status {
            response.Forbidden(c, "账号已被封禁")
            c.Abort()
            return
        }

        // 5. 将用户信息存入 Context,供后续 handler 使用
        c.Set("user_id", claims.UserID)
        c.Set("role", claims.Role)
        c.Next()
    }
}

路由分层设计很清晰:

go 复制代码
// 公开路由 - 无需认证
r.articleCtrl.RegisterPublicRoutes(v1)

// 需认证路由 - 必须登录
authorized := v1.Group("/")
authorized.Use(middleware.Auth())
r.userCtrl.RegisterRoutes(authorized)

// 管理路由 - 必须 admin 角色
admin := v1.Group("/admin")
admin.Use(middleware.Auth())
admin.Use(middleware.RequireRole("admin"))
r.articleCtrl.RegisterAdminRoutes(admin)

2. GitHub OAuth 第三方登录

这是项目中比较有意思的功能。整体流程如下:

复制代码
用户点击「GitHub 登录」
    ↓
前端跳转 GitHub 授权页面
    ↓
用户授权后,GitHub 回调带 code
    ↓
前端将 code 发送给后端
    ↓
后端用 code 换取 access_token
    ↓
后端用 access_token 获取用户信息
    ↓
查找/创建用户 → 生成 JWT → 返回前端

关键实现 - OAuth 回调处理:

go 复制代码
// service/auth_service.go
func (s *authService) GitHubLogin(code string) (*AuthResponse, error) {
    // 1. 用 code 换取 access_token
    token, err := s.getGitHubToken(code)
    if err != nil {
        return nil, err
    }

    // 2. 获取 GitHub 用户信息
    githubUser, err := s.getGitHubUser(token)
    if err != nil {
        return nil, err
    }

    // 3. 查找或创建用户
    user, err := s.userRepo.FindByGitHubID(githubUser.ID)
    if err != nil {
        // 首次登录,创建用户
        user = &entity.User{
            GitHubID:    githubUser.ID,
            GitHubLogin: githubUser.Login,
            Username:    githubUser.Login,
            Avatar:      githubUser.AvatarURL,
            Role:        "user",
        }
        s.userRepo.Create(user)
    }

    // 4. 生成 JWT
    return s.generateToken(user)
}

前端回调页面处理:

javascript 复制代码
// pages/GithubCallbackPage.vue
onMounted(async () => {
    const urlParams = new URLSearchParams(window.location.search)
    const code = urlParams.get('code')

    if (code) {
        try {
            const res = await api.githubLogin(code)
            // 保存 token 并跳转首页
            localStorage.setItem('token', res.data.token)
            router.push('/')
        } catch (error) {
            console.error('GitHub 登录失败:', error)
        }
    }
})

3. 文章管理功能

博客的核心功能,支持:

  • Markdown 编辑器(md-editor-v3)
  • 图片上传(限制 10MB,支持 jpg/png/gif/webp/svg)
  • 分类和标签管理
  • 文章状态管理(草稿/已发布)
  • 按热度、时间排序
  • 关键词搜索

Docker 部署方案

采用 Docker Compose 一键部署,包含 4 个服务:

yaml 复制代码
# docker-compose.yml
services:
  mysql:
    image: mysql:8.0
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    # 只绑定 127.0.0.1,外部无法访问
    ports:
      - "127.0.0.1:3306:3306"

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    # 不暴露端口到宿主机
    command: redis-server --appendonly yes --requirepass junblog_redis_2026

  backend:
    build:
      context: ../blog_backend
      dockerfile: Dockerfile
    volumes:
      - ./uploads:/app/uploads
      - ./logs:/app/logs
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy

  frontend:
    build:
      context: ../bolg_forntend
      dockerfile: Dockerfile
    ports:
      - "80:80"
    depends_on:
      - backend

安全设计亮点:

  • MySQL 只绑定 127.0.0.1,外部无法直接访问数据库
  • Redis 不暴露端口到宿主机,仅 Docker 内部网络可访问
  • 数据库密码通过 .env 文件管理,不硬编码在 docker-compose.yml 中
  • 后端也不直接暴露端口,通过 Nginx 反向代理访问

Nginx 配置:

nginx 复制代码
# 前端静态资源
location / {
    root /usr/share/nginx/html;
    try_files $uri $uri/ /index.html;  # SPA 路由支持
}

# 后端 API 代理
location /api {
    proxy_pass http://junblog-backend:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

# 上传文件代理
location ^~ /uploads {
    proxy_pass http://junblog-backend:8080;
}

# 静态资源缓存(1年)
location ~* \.(js|css|png|jpg|ico|svg|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/javascript application/json;

CI/CD 流水线

项目配置了 Jenkins 流水线,实现自动化构建和部署:后续碍于我的服务器性能不够,我舍弃了Jenkins自动化部署

复制代码
Jenkins Pipeline 流程:
┌─────────┐    ┌──────────────┐    ┌─────────────┐    ┌─────────┐
│ Checkout │ →  │ Build Backend│ →  │Upload Files │ →  │ Deploy  │
│ 检出代码  │    │ 交叉编译 Go  │    │ SCP 上传     │    │ 重启服务 │
└─────────┘    └──────────────┘    └─────────────┘    └─────────┘

后端交叉编译:

bash 复制代码
# 在 Windows 上编译 Linux 二进制
set CGO_ENABLED=0
set GOOS=linux
set GOARCH=amd64
go build -o server.exe ./cmd/server

Go 的交叉编译能力非常方便,一条命令就能在 Windows 上编译出 Linux 的可执行文件。

Dockerfile 极简设计:

dockerfile 复制代码
# 后端 - 基于 Alpine,最终镜像仅约 15MB
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY server .
COPY configs ./configs
RUN mkdir -p uploads logs && chmod +x server
EXPOSE 8080
CMD ["./server"]

功能模块一览

模块 功能 说明
认证 注册/登录/JWT/GitHub OAuth 双令牌机制,支持第三方登录
文章 CRUD/Markdown/图片上传 支持分类、标签、状态管理
分类/标签 树形分类、标签关联 分类支持层级结构
评论 发表/审核/管理 支持评论审核机制
互动 点赞/收藏 Redis 缓存计数
用户 个人信息/角色/封禁 管理员可管理用户
站点设置 关于页面/系统配置 后台可配置站点信息
后台管理 全功能管理面板 文章/用户/评论/设置管理

踩坑与经验

1. GORM 的 N+1 查询问题

在查询文章列表时,如果每篇文章都单独查一次分类和标签,会产生 N+1 问题。解决方案是使用 GORM 的 Preload:

go 复制代码
// 一次性预加载关联数据
db.Preload("Category").Preload("Tags").Find(&articles)

2. Docker 网络安全

最初把 MySQL 端口直接映射到 0.0.0.0:3306,这意味着任何人只要知道服务器 IP 就能尝试连接数据库。后来改为 127.0.0.1:3306:3306,只允许本机访问。

3. 前端 SPA 路由刷新 404

Vue Router 使用 history 模式时,刷新页面会 404。解决方案是在 Nginx 配置中添加:

nginx 复制代码
try_files $uri $uri/ /index.html;

快速开始

Docker 一键部署:

bash 复制代码
git clone https://github.com/yourname/junblog.git
cd junblog/deploy

# 配置环境变量
cp .env.example .env
vim .env  # 设置数据库密码等

# 启动
docker-compose up -d

# 访问
# 前端:http://your-server-ip
# 后端 API:http://your-server-ip/api/v1/health

本地开发:

bash 复制代码
# 后端
cd blog_backend
go run ./cmd/server

# 前端
cd bolg_forntend
npm install
npm run dev  # 访问 http://localhost:5173

总结

这个项目虽然定位是个人博客,但在架构上并没有偷懒:

  • 后端:分层架构 + 接口驱动 + 中间件链,具备良好的可测试性和可扩展性
  • 前端:模块化组织 + 组合式函数,代码清晰易维护
  • 部署:Docker Compose + Nginx 反向代理 + Jenkins CI/CD,一键部署
  • 安全:JWT 双令牌 + RBAC 角色控制 + Docker 网络隔离

整个项目从零搭建到现在,收获了很多。如果你也想搭建自己的博客系统,欢迎参考这个项目。有问题欢迎在评论区交流!

相关推荐
我叫张小白。1 小时前
Docker镜像构建原理与Dockerfile工程化实践深度剖析
运维·docker·容器
EntyIU2 小时前
Vue History 模式配置文档
前端·javascript·vue.js
雨师@2 小时前
go语言项目--实例化(图书管理)--006
开发语言·后端·golang
梦想的颜色11 小时前
硬核实践:使用 Docker 部署生产级 Redis(持久化 + 安全配置 + 高可用)
redis·docker·redis持久化·docker compose·redis哨兵·rdb aof
weixin_4713830311 小时前
Docker - 05 - 构建流程
运维·docker·容器
格子软件14 小时前
2026年GEO优化系统源码的分布式状态机深度拆解
java·前端·vue.js·vue·geo
ejinxian14 小时前
微虚拟机 smolvm 与Docker 容器比较
运维·docker·容器·smolvm
爱码少年15 小时前
Docker如何一次查看多个容器日志
运维·docker·容器
格子软件15 小时前
2026年GEO优化系统源码解构:核心状态机与高并发流控深度剖析
java·vue.js·spring boot·vue·geo