📢 新文章推送 · 每周更新优质内容 · 订阅更新 →
向下滚动
游戏娱乐

游戏后台管理实现 - Go API + Vue SPA 全栈方案

AI 智能总结

概述

为配合前台游戏展示页面,在管理后台系统中新增了 游戏管理 功能模块,支持游戏的增删改查、封面图片上传、数据持久化到 TOML 文件。本文详解后端 Go API 和前端 Vue SPA 的完整实现方案。

架构概览

 1┌─ 前端 (Vue 3 + Element Plus) ─────────────────┐
 2│  admin/frontend/src/views/Game/Index.vue       │
 3│  admin/frontend/src/api/index.js               │
 4│  admin/frontend/src/router/index.js            │
 5│  admin/frontend/src/layouts/MainLayout.vue     │
 6└────────────────────┬───────────────────────────┘
 7                     │ HTTP (Bearer Token 鉴权)
 8 9┌─ 后端 (Go + Gin) ─────────────────────────────┐
10│  admin/backend/internal/handler/game.go        │
11│  admin/backend/internal/handler/router.go      │
12└────────────────────┬───────────────────────────┘
13                     │ TOML 序列化 / 反序列化
1415              data/games.toml

API 端点注册在 Gin 路由组 /api/games 下,由 JWT 认证中间件保护。

Go 后端实现

数据结构

 1type Game struct {
 2    Title       string     `json:"title" toml:"title"`
 3    Cover       string     `json:"cover,omitempty" toml:"cover,omitempty"`
 4    Status      string     `json:"status" toml:"status"`
 5    Rating      float64    `json:"rating,omitempty" toml:"rating,omitempty"`
 6    Platform    string     `json:"platform,omitempty" toml:"platform,omitempty"`
 7    Developer   string     `json:"developer,omitempty" toml:"developer,omitempty"`
 8    Publisher   string     `json:"publisher,omitempty" toml:"publisher,omitempty"`
 9    ReleaseYear int        `json:"releaseYear,omitempty" toml:"releaseYear,omitempty"`
10    Genre       []string   `json:"genre,omitempty" toml:"genre,omitempty"`
11    Desc        string     `json:"desc,omitempty" toml:"desc,omitempty"`
12    Links       []GameLink `json:"links,omitempty" toml:"links"`
13}

每个字段同时标记 jsontoml tag,实现 JSON ↔ 结构体 ↔ TOML 的双向转换。omitempty 确保未填的可选字段不会写入文件。

API 端点

方法路径功能
GET/api/games读取 data/games.toml,返回游戏数组
PUT/api/games全量替换游戏列表,写入 TOML 文件

采用 全量读写 策略(而非 PATCH 单条更新):前端维护完整的游戏数组,每次修改后发送完整数据覆盖文件。这种方式简化了并发控制逻辑,适合数据量较小的场景。

TOML 序列化

writeGameToml 函数不依赖第三方 TOML 库的 Marshal 功能,而是手动拼接 TOML 格式字符串。这样做的优势:

  • 可读性:生成的 TOML 文件带注释和缩进,可直接手动编辑
  • 字段控制:精确控制每个字段的写入顺序和格式
  • 可选字段omitempty 字段不写入,保持文件简洁
1sb.WriteString("[[games]]\n")
2sb.WriteString(fmt.Sprintf("  title = %s\n", strconvQuote(g.Title)))
3sb.WriteString(fmt.Sprintf("  status = %s\n", strconvQuote(g.Status)))
4if g.Rating > 0 {
5    sb.WriteString(fmt.Sprintf("  rating = %.1f\n", g.Rating))
6}

路由注册

router.go 中将游戏路由插入到图书和音乐之间,保持管理功能按逻辑分组:

1gamesGroup := api.Group("/games")
2{
3    gamesGroup.GET("", getGames(cfg))
4    gamesGroup.PUT("", updateGames(cfg))
5}

登录路由修复

在实现过程中发现原有 loginhealth 路由被错误地放在 RequireAuth() 中间件组内,导致"需要登录才能登录"的死循环。将其移出认证组:

1r.POST("/api/auth/login", middleware.LoginRateLimit(), login(cfg))
2r.GET("/api/health", healthCheck)
3
4api := r.Group("/api")
5api.Use(authMW.RequireAuth())
6{
7    // 受保护的 API 路由
8}

Vue 前端实现

组件结构

views/Game/Index.vue 是一个自包含的单文件组件(SFC),包含模板、逻辑和样式三个部分,约 520 行代码。

核心状态管理

1const games = ref([])           // 游戏数据数组
2const dialogVisible = ref(false) // 弹窗开关
3const editIndex = ref(-1)       // 编辑索引(-1 表示新增)
4const form = ref({
5    title: '', status: 'planned', cover: '', rating: 0,
6    platform: '', developer: '', publisher: '',
7    releaseYear: 0, genre: [], desc: '', links: []
8})

操作流程

  1. 加载onMountedgetGames() → 渲染卡片网格
  2. 新增:点击"添加游戏" → 弹出表单弹窗 → 填写后 games.pushsaveAll
  3. 编辑:点击卡片编辑图标 → 回填表单 → 修改后 games[index] = formsaveAll
  4. 删除:点击删除图标 → ElPopconfirm 确认 → games.splicesaveAll
  5. 保存saveAll()saveGames({ games }) → 后端 PUT /api/games

封面管理

提供四种封面来源,参考了电影管理模块的实现模式:

方式实现适用场景
上传图片el-upload + uploadImage()本地临时图片
本地路径弹窗输入 /images/games/xxx.webp已部署的静态资源
图床链接弹窗输入 URL外部 CDN 图片
上传到图床el-upload + uploadImageToHosting()上传并自动获取 CDN 链接
1<el-upload :http-request="handleHostingUpload" :before-upload="beforeCoverUpload">
2  <div class="card-inner">
3    <div class="card-icon hosting-icon-wrap"><el-icon><UploadFilled /></el-icon></div>
4    <div class="card-title">上传到图床</div>
5  </div>
6</el-upload>

平台与类型

提供预设选项的同时支持 filterable + allow-create,用户可以输入自定义值:

1<el-select v-model="form.platform" filterable allow-create default-first-option>
2  <el-option v-for="p in platforms" :key="p" :label="p" :value="p" />
3</el-select>

预设平台:PC, PS5, PS4, Xbox Series X/S, Xbox One, Switch, iOS, Android, Steam Deck

预设类型:动作RPG, 开放世界, 策略, CRPG, Roguelike, 类银河城, 独立游戏 等 20+ 种

API 接口层

1// admin/frontend/src/api/index.js
2export function getGames() {
3  return request.get('/games')
4}
5
6export function saveGames(data) {
7  return request.put('/games', data)
8}

请求自动附带 Authorization: Bearer <token> 头(由 axios 拦截器注入)。

路由与菜单

1// router/index.js
2{
3    path: 'games',
4    name: 'Games',
5    component: () => import('@/views/Game/Index.vue'),
6    meta: { title: '游戏管理' }
7}

侧边栏菜单使用 Monitor 图标(Element Plus 中与游戏最相近的可用图标),避免与前台 fa-gamepad 的直接复用。

暗黑模式样式

Vue 组件使用 [data-theme="dark"] 选择器覆盖所有关键元素的背景、文字和边框颜色:

 1[data-theme="dark"] {
 2  .game-card {
 3    background: rgba(255, 255, 255, 0.03);
 4    border-color: rgba(255, 255, 255, 0.06);
 5  }
 6  .cover-options .option-card {
 7    background: #1e1e32;
 8    border-color: rgba(255, 255, 255, 0.08);
 9  }
10}

涉及的所有文件

文件操作
后端admin/backend/internal/handler/game.go新建
后端admin/backend/internal/handler/router.go修改(添加路由 + 修复登录)
前端admin/frontend/src/views/Game/Index.vue新建
前端admin/frontend/src/api/index.js修改(添加 API 函数)
前端admin/frontend/src/router/index.js修改(添加路由)
前端admin/frontend/src/layouts/MainLayout.vue修改(添加菜单)
前端admin/frontend/src/views/Login.vue修改(修复 redirect)
前端admin/frontend/vite.config.js修改(修复代理端口)

技术要点总结

  1. 全量读写 适合小数据量的管理页面,实现简单、无需 ID 管理
  2. 手动 TOML 拼接 在可读性和格式控制上优于自动化序列化
  3. Vue 响应式数组 操作(push/splice/索引赋值)配合批量保存,用户体验流畅
  4. Element Plus 组件filterable + allow-create 模式兼顾了便捷输入和规范性
  5. 封面管理四合一 设计覆盖了从临时上传到 CDN 部署的完整场景
版权声明

本文作者 Lumin

本文链接 https://www.zhengquan.xyz/game/game-backend-implementation/

许可协议 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!

请作者喝杯咖啡 ☕

  • 微信打赏
    微信支付
  • 支付宝打赏
    支付宝
点击按钮查看打赏二维码
🎁 推荐工具
试试这些实用在线工具,提升工作效率
前往工具集 →

留言评论

期待你的想法

评论加载中