做了几年全栈开发,接过的管理系统项目少说也有七八个,但说实话,像"企业级热门网游推荐网站管理系统"这样,把游戏资讯展示、推荐策略、后台管理和数据报表全揉在一个项目里的需求,反而是最考验架构功力的。因为这类系统看着是"网站",骨子里却是一套内容运营+用户管理+数据推荐的组合拳。最近刚好基于SpringBoot+Vue+MyBatis+MySQL这套经典Java全栈组合,从零到上线完整落地了一版热门网游推荐网站管理系统,前后端源码完整,含数据库脚本、推荐策略、权限控制、运营后台等一整套东西。这篇文章我就把整个项目从设计拆解到实操落地的全过程写出来,包括我在实际开发里踩过的坑、排查过的Bug、压测过的数据量,给想拿这类项目做毕设、做私活、或者入门企业级全栈开发的朋友当一份可复现的参考。
提示:这篇文章不是教程式的"跟着点下一步",而是站在一个真实的开发者视角,讲清楚模块为什么要这么拆、表为什么要这么建、推荐策略为什么要这么写,以及哪些地方最容易翻车。看完你可以直接对着源码复现。
1. 整体设计:为什么推荐网站最好做成"前台展示+后台运营"双端形态
1.1 一套系统里的两个核心角色,缺一个都不行
先想清楚一个问题:热门网游推荐网站,到底给谁用?
表面上是给最终用户看"今天有什么好游戏、什么游戏火、什么游戏值得玩"。但撑起这个网站内容的是运营人员——他们要录入游戏、维护热度、配置推荐规则、发布文章、审核评论。所以这套系统天然分成两个端:
- 前台门户端:面向普通玩家,展示游戏列表、热度排行、推荐专题、游戏详情、资讯文章。
- 后台管理端:面向运营和管理员,负责游戏入库、推荐配置、用户管理、角色权限、数据统计。
我见过不少初学者拿到类似需求时,第一反应是"不就是个列表页加详情页吗",于是只做了一个用户端,后台全靠数据库里手工改数。短期看不出来问题,一旦游戏数量过百、运营要调整推荐顺序、老板要看每日访问数据,手工改数据的方案直接废掉。所以管理后台不是附属品,而是这个系统的核心生产力工具。
在技术选型上,这次我选了 Java 生态里最稳的组合:SpringBoot 做后端接口和业务逻辑,Vue + Element UI 做后台管理界面,MyBatis 做数据库持久层,MySQL 8.0 做数据存储。整套技术栈的好处是:SpringBoot 足够轻量,内置容器,一个 jar 就能部署;MyBatis 对复杂 SQL 和动态查询的支持非常灵活,游戏推荐这种需要多条件组合筛选的业务场景很适合;Vue 组件化开发让前后台共用一套代码里也能快速迭代页面。
另外我特意把前后端完全分离,前端走 Nginx 静态资源服务,后端独立进程跑 API。这样做不是为了炫技,而是因为游戏推荐网站通常要接推广入口、活动页、小程序端,前后端分离后,前端可以独立发布,后端接口也能被多个客户端复用。
1.2 推荐系统在"企业级"项目里的真实定位
标题里有个关键词是"热门网游推荐",很多第一次接触的人会把"推荐"想得很高级,以为要用协同过滤、深度学习模型。实际上,在企业级内容站的落地场景里,推荐系统的核心是规则可配、数据可查、效果可复盘。
我这次实现的推荐模块分了三层:
- 基础筛选层:按游戏类型、平台、状态(在售/预约/停运)、价格区间过滤。
- 热度加权层:综合考虑搜索热度、资讯指数、用户点击、运营人工加权重,生成综合热度分。
- 运营干预层:运营手动配置推荐位、置顶游戏、专题活动,推荐结果里内置人工规则,避免纯算法导致热门内容固定不变。
这三层下来,既保住了"热门榜"的时效性,也给了运营足够的调控空间。系统上线后,运营可以在后台调整某款游戏的权重系数,第二天前台榜单就跟着变,不需要开发介入,这个能力对真实业务很有价值。
1.3 项目模块划分:前后端到底拆成多少块
实际落地时,我把工程拆成了四个部分:
| 模块 | 技术载体 | 核心职责 |
|---|---|---|
| 前台门户 | Vue 2.x + Vue Router + Axios | 游戏列表、详情、攻略资讯、榜单排行 |
| 后台管理 | Vue 2.x + Element UI | 游戏管理、推荐配置、用户管理、数据统计 |
| 后端服务 | SpringBoot 2.7 + MyBatis | 接口鉴权、业务逻辑、推荐计算、数据访问 |
| 数据层 | MySQL 8.0 + Redis | 结构化存储、热点缓存、会话管理 |
Redis 在这里不是必须的,但我在实际项目里还是加上了。因为游戏列表页和榜单页的查询频率非常高,如果不加缓存,高峰期数据库压力会很大。用 Redis 做热点数据的缓存层,设置的过期时间一般是 5 分钟,运营改了权重后主动缓存刷新,保证前台数据不会滞后太久。
注意:如果只是做课程设计或演示项目,没有 Redis 也能跑通,直接查 MySQL 就行。但如果项目声明"企业级",建议还是把缓存和日志这套基础设施加上,代码层面做一层防腐,后面扩展行为化推荐、用户画像时会轻松很多。
2. 数据库设计:游戏推荐系统最关键的不是表数量,而是那几个索引
2.1 核心表结构拆解
我在设计表结构时,没有按教科书那样把每个字段都写得很保守,而是以业务为驱动。一共建了 8 张核心业务表:
- user(用户表):后台管理员和前台注册用户共用一张表,通过 role 字段区分。字段上留了 status 做禁用标记,password_hash 存加盐后的哈希值,绝对不存明文密码。
- game(游戏主表):核心表,包含游戏名称、类型、开发商、发行商、平台、价格、评分、热度值、推荐等级、封面图、简介、状态等字段。
- game_detail(游戏详情扩展表):游戏大图、系统配置要求、下载链接、标签、详细介绍。因为详情内容体积大且查询频率低于列表页,所以单独拆表,避免列表查询拖慢速度。
- recommend_strategy(推荐策略表):存推荐规则配置,比如按哪个热度公式、各个权重系数、策略优先级。
- recommend_log(推荐日志表):记录每次推荐结果和用户点击行为,为后续调优和报表提供数据支撑。
- article(资讯文章表):游戏资讯、攻略文章,支撑前台的门户内容。
- operation_log(操作日志表):后台每次增删改关键操作都落日志,方便问题追溯。
- category(分类表):游戏类型分类,虽然字段简单,但能避免硬编码。
举两个典型表的建表思路:
CREATE TABLE `game` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键', `game_name` varchar(128) NOT NULL COMMENT '游戏名称', `game_type` varchar(32) DEFAULT NULL COMMENT '游戏类型: RPG, ACT, SLG...', `developer` varchar(128) DEFAULT NULL COMMENT '开发商', `publisher` varchar(128) DEFAULT NULL COMMENT '发行商', `platform` varchar(64) DEFAULT NULL COMMENT '平台: PC, PS5, Switch...', `price` decimal(6,2) DEFAULT NULL COMMENT '价格', `score` decimal(3,1) DEFAULT NULL COMMENT '评分', `heat_value` int DEFAULT '0' COMMENT '热度值', `recommend_level` tinyint DEFAULT '0' COMMENT '推荐等级: 0普通 1重点 2爆款', `status` tinyint DEFAULT '1' COMMENT '状态: 0下架 1在售 2预约', `cover_url` varchar(255) DEFAULT NULL COMMENT '封面图', `description` text COMMENT '游戏简介', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_game_type` (`game_type`), KEY `idx_heat_value` (`heat_value`), KEY `idx_status_recommend` (`status`, `recommend_level`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='游戏主表';这个表的关键不在字段数量,而在三个点:
第一,状态和推荐等级建了联合索引。因为后台列表页最常用的筛选条件是 status + recommend_level,联合索引能直接覆盖这个查询路径,避免全表扫描。
第二,热度值单独建索引。前台榜单的查询几乎都是ORDER BY heat_value DESC,单列索引对排序优化非常明显。
第三,大字段和主查询字段做了拆分。游戏的详细介绍、截图这种体积大的内容放扩展表,列表页只查主表,行体积小,查询速度自然快。
2.2 推荐日志表为什么会成为整个系统的"隐形功臣"
很多人设计推荐系统时容易忽略日志表,但我强烈建议保留。recommend_log 表记录四个核心信息:用户ID、推荐的游戏ID、命中的策略ID、展示场景(榜单/详情页/专题页)。
这张表的作用有两个:
- 运营复盘:某款游戏上榜后实际点击率怎么样,哪个推荐位转化最好,一查日志就清楚了。
- 后续算法升级:如果未来要上协同过滤、用户画像,这批点击日志就是最宝贵的训练数据。
我甚至建议在后端接口里对日志写入做成异步的,不要让推荐日志读写影响主流程响应时间。用 Spring 的@Async注解或者消息队列都行,小项目用@Async就够了,日志丢了也比主流程崩溃强。
2.3 索引和 SQL 层面的几个实战经验
在实际写 MyBatis 的 XML 时,有几个坑是新手特别容易踩的:
动态排序字段不要直接拼接。很多推荐场景需要"按热度、按评分、按上架时间"动态切换排序,新手容易写出${orderColumn}直接拼接,这会导致 SQL 注入风险。正确做法是在代码层做白名单校验,比如把可排序字段放进枚举,只允许传约定好的几个值,杜绝任意拼接。
分页查询一定要用 PageHelper 或 Limit 参数化。我这次用的是 MyBatis 通用分页插件 PageHelper,设置pageNum和pageSize后插件自动生成 count 查询和 limit。要注意的是 PageHelper 只对紧跟其后的第一条查询生效,所以多表关联或嵌套查询时一定要确认分页逻辑是否落在正确语句上,否则容易出现"count 正确但列表数据错位"的怪问题。
热门榜的 ORDER BY 字段上必须有索引,否则数据量过 10 万后排序会成为性能瓶颈。我在测试库塞了 20 万条模拟游戏数据,无索引时order by heat_value desc limit 10执行时间接近 800ms,加上索引后立刻降到 20ms 以内。就这一个小改动,带来的体感提升非常明显。
MySQL 8.0 里建议所有表都使用 utf8mb4 字符集。因为游戏简介、资讯文章里会包含 emoji 符号,utf8mb3 存不进这些特殊字符,接口返回时可能会乱码或者直接报错。这个细节在游戏推荐这种内容型网站里非常常见,提前在数据源连接串里加上characterEncoding=utf8JVM 参数,能省去后面一堆乱码排查。
提示:数据库脚本文件是完整版源码里最值得先看的东西。建议拿到项目后不要急着跑业务代码,而是先把建库、建表、初始化数据脚本完整执行一遍,再顺着游戏表和推荐策略表的数据关系读代码,效率会高很多。
3. 后端实现:SpringBoot 接口拆解与推荐策略的落地方案
3.1 后端工程结构怎么组织才不拧巴
后端我采用了标准的 Controller-Service-Mapper 三层结构,但在包组织上做了一点优化,按业务域分包,而不是按技术层分包。
com.demo.game ├── controller // 接口入口 │ ├── admin // 后台管理接口 │ └── portal // 前台门户接口 ├── service // 业务逻辑层 │ ├── game │ ├── recommend │ ├── user │ └── article ├── mapper // MyBatis 持久层 ├── entity // 数据库实体 ├── dto // 传输对象,避免直接暴露实体 ├── config // 配置类,包括安全拦截、跨域、缓存 ├── common // 统一返回结果、异常处理、工具类 └── task // 定时任务,比如刷新热度值很多教程喜欢按 controller、service、mapper 这种技术层分包,小项目没问题,但业务变多之后,一个 controller 包里堆五六十个类,找起来非常头大。按业务域分包后,改游戏模块只需要钻进 game 文件夹,上下游代码一眼就能扫完。
3.2 统一返回结构和全局异常处理
前后端联调时最怕什么?最怕每个接口返回的数据格式都不一样。今天这个接口返回{code:0,data:{}},明天那个接口返回{success:true,result:[]},前端封装 Axios 时就得写一堆特殊判断。
所以后端我第一件事就是定统一返回体:
public class ApiResult<T> { private int code; // 0 成功,非0 失败 private String msg; // 提示信息 private T data; // 业务数据 // 静态方法 success(...), error(...) }配合全局异常处理器,业务里抛出BusinessException("游戏不存在")时,会被统一捕获并返回{code:400, msg:"游戏不存在", data:null},不会出现堆栈信息直接抛给前端的裸奔情况。这个规范要定了,前后端联调就容易很多——前端只需要在 Axios 拦截器里判断code === 0进入正常流程,其他一律弹错误提示。
3.3 推荐算法的具体实现:权重公式和动态策略怎么落代码
这是整个后端最核心的部分。我设计了一套可配置的热度综合分计算方法,公式是这样的:
heatScore = searchHeat * 0.5 + newsHeat * 0.3 + socialHeat * 0.2 + manualWeight- searchHeat:搜索指数,来自游戏关键词的站内搜索次数。
- newsHeat:资讯指数,来自游戏相关资讯文章的阅读、分享量。
- socialHeat:社交指数,模拟玩家讨论热度,比如攻略评论、收藏数。
- manualWeight:运营人工加权,后台里可以手工加 0~100 的修正值。
为什么要这样设计?因为单一看搜索量或者单一看运营推荐都太片面。搜索量高只能说明玩家在找这个游戏,不代表游戏口碑好;运营推荐高可能只是商业合作;但综合三个维度再叠加运营修正,就能满足"热门网游推荐"的核心诉求。
后端实现上,把公式抽成一个可配置的策略对象,存在 recommend_strategy 表里。策略类下面的字段包括权重参数、生效状态、优先级。运营在后台改完权重后,前端榜单实时刷新就是读取策略表里的最新参数,然后用 MyBatis 动态 SQL 批量更新游戏表的 heat_value。
核心查询的 XML 大概是这个思路:
<select id="selectRecommendGames" resultType="com.demo.game.entity.Game"> SELECT id, game_name, cover_url, score, heat_value FROM game <where> status = 1 <if test="gameType != null and gameType != ''"> AND game_type = #{gameType} </if> <if test="platform != null and platform != ''"> AND platform = #{platform} </if> <if test="minPrice != null"> AND price >= #{minPrice} </if> </where> ORDER BY heat_value DESC LIMIT #{limit} </select>这里有几个细节值得说明:
<where>标签会自动去掉前导 AND,避免多条件拼接出错。- 价格比较里用
>=而不是>=,因为 XML 文件里>有时会破坏节点解析,虽然 MyBatis 现在能自动容错一部分,但养成写转义符的习惯更安全。 - 分页和排序逻辑我放在 Service 层控制,Mapper 只负责取数,不让 SQL 承担过多业务判断,后期好维护。
3.4 权限控制:后台接口不能裸奔
企业级系统最基础的要求是后台接口不能被直接裸调。我用 SpringBoot 拦截器 + JWT 做了接口认证。
流程很简单:
- 用户登录后,后端校验用户名密码,成功则签发 JWT token,过期时间设为 2 小时。
- 前端把 token 存到 localStorage,每次请求在 Header 里带上
Authorization: Bearer xxx。 - 后端拦截器对
/api/admin/**路径做统一校验,token 不合法直接返回 401。
除了登录态,我还做了角色区分:ROLE_ADMIN可以执行全部后台操作;ROLE_OPERATOR只能维护游戏内容和推荐策略,不能操作用户和系统配置。这个用拦截器里的权限判定方法只需要一个注解就能搞定,我在项目里定义了一个@RequireRole("ROLE_ADMIN")注解,写起来非常省事。
需要注意的点:密码存储使用 BCrypt 加密,不要用 MD5。MD5 现在用彩虹表破解成本太低,BCrypt 每次加密自动加盐,同样的密码入库后的哈希值都不同,安全性完全不是一个量级。
4. 前端实现:Vue+Vue Router+Axios 双端页面的开发细节
4.1 前台门户页面:列表、详情、榜单三大核心
前台页面我重点做了三个业务场景:
游戏列表页:支持按类型、平台、价格区间筛选,排序方式支持热度/评分/最新。这个页面看起来简单,但实际开发时要注意筛选条件和 URL 参数同步。比如玩家把筛选条件翻到第二页,刷新浏览器后条件不能丢。我是用 Vue Router 的 query 参数在维护筛选状态,每次筛选变化时写入router.replace({query}),页面加载时先从route.query恢复条件再调接口。
游戏详情页:除了基本信息,还展示了系统配置要求、游戏截图、相关文章推荐。详情页的数据来源有两个接口:一个是游戏详情接口,一个是相关文章列表接口。开发时我用了 Promise.all 并发请求,避免两个接口串行等待,首屏速度大概能提升 30%。
热门榜单页:按综合热度分倒序排列,展示 Top50。榜单页的所有数据从后端一次接口拿回,页面端只做展示,不在前端做二次排序。原因是榜单排序逻辑完全由后端推荐策略控制,前端再做排序会导致策略失效。
4.2 后台管理页面:表格+弹窗+表单的组合拳
后台管理端我用的 Element UI,页面风格统一,开箱即用。核心的几个页面是大件:
- 游戏管理页:表格展示所有游戏,支持搜索、分页、编辑、上下架、批量操作。这里最值得说的细节是表单校验规则一定要覆盖完整。很多项目表单校验只写了必填,结果运营提交时类型没选、评分超范围、价格负数都能提交成功,数据库里垃圾数据越积越多。我在前端用 async-validator 做了层校验,价格范围 0~999、评分 0~10、图片链接必须是 http 开头,不合格直接前端拦截。
- 推荐策略配置页:权重系数、策略开关、优先级都做成了可视化表单。运营改完保存,后端立即刷新缓存。
- 用户管理页:管理员列表、角色分配、账号禁用。
- 数据统计页:用简单统计图展示每日访问量、热门游戏 Top10、推荐位点击率。图表用的是 ECharts,只接后端统计接口,不涉及复杂的实时推送。
4.3 Axios 封装和路由守卫是前端工程质量的分水岭
市面上很多教学项目把 Axios 请求直接写在组件里,一个页面里一堆this.$http.get,项目大了之后维护成本极高。我在项目里统一封装了个request.js:
- 请求拦截器:自动附带 token,统一设置 Content-Type。
- 响应拦截器:统一解析
ApiResult,code !== 0时弹出错误信息,401 时自动跳转登录页。 - 统一错误处理:网络超时、服务器 500、接口报错都能落到同一个处理函数,不会让每次请求都写一遍
catch。
路由守卫的核心逻辑也很简单:所有需要登录的页面在路由 meta 里标记requiresAuth: true,全局前置守卫里检查 token,没有就跳登录页。后台管理页还要检查角色,不匹配的直接重定向 403 页面。
4.4 懒加载路由提升首屏速度
游戏推荐网站的门户页面图片多、组件多,如果首屏加载时把所有页面的 JS 一次性打包,体积会非常大。我用了 Vue Router 的懒加载:
const GameDetail = () => import('@/views/portal/GameDetail.vue')这样每个路由组件独立分包,访问到哪个页面才加载对应的 JS。实测首页首屏从原来的 1.8s 降到 0.9s 左右,体感提升非常明显。
注意:懒加载虽然很好,但有一个坑——如果部署环境没配置好
publicPath,打包后的 JS 文件路径可能对不上,刷新页面时会出现白屏。解决办法是在vue.config.js里显式设置publicPath: './',或者根据实际部署路径配置绝对路径。
5. 核心流程实操:从登录鉴权到榜单发布的一条完整链路
5.1 搭建开发环境
开始动手前,先把环境准备好:JDK 1.8 或 11、Maven 3.6+、Node.js 14+、MySQL 8.0、Redis 5+。
我推荐用 Docker 启动 MySQL 和 Redis,省去本地安装的麻烦:
docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root123 mysql:8.0 docker run -d --name redis -p 6379:6379 redis:6数据库初始化时,把完整版自带的game_recommend.sql导入即可。这里要提醒一点:MySQL 8.0 的默认认证插件是 caching_sha2_password,项目里用的数据库驱动要匹配 MySQL 8 的驱动版本,如果用老版本驱动连接,很容易报Public Key Retrieval is not allowed这个经典错误。解决方案是在 JDBC 连接串上加allowPublicKeyRetrieval=true,或者用 mysql-connector-java 8.x 以上的版本。
5.2 后端启动和接口自测
后端工程是标准的 Maven 项目,启动步骤:
- 修改
application.yml里的数据库连接、Redis 连接。 - 执行
mvn clean package -DskipTests打包。 - 执行
java -jar target/game-recommend.jar启动服务。 - 启动后用 Swagger 或 Postman 快速验证几个核心接口。
这里我实际在验证接口时最爱用 Postman 的集合管理功能,一个登录接口拿 token,然后在游戏列表、推荐榜单、详情这些接口的 Header 里统一加 token。用环境变量存 token,可以避免每个请求手动改。
5.3 前端启动和前后端联调
前端部分:
npm install npm run serve默认开发服务器端口是 8080,但后端接口也是 8080,所以我在vue.config.js里配置了 devServer 代理,把所有/api请求代理到http://localhost:8080。这样前端的调用地址就是相对路径,省去手写完整 URL 的麻烦,也解决了跨域问题。
前后端联调最容易出的问题就是接口路径匹配不上。比如后端定义的是GET /api/portal/game/list,前端请求写成了/api/game/list,页面报 404。我建议前端和后端共同维护一份接口文档,每次修改接口后同步更新,能省下一大批联调时间。
5.4 榜单发布全流程
验证整个系统是否正常,最直接的办法是跑通一条完整链路:
- 管理员登录后台,在"游戏管理"里新增一款游戏,填写所有必填字段,保存。
- 游戏出现在前台列表页,状态为"在售"。
- 运营在"推荐策略"里给该游戏加 20 分人工权重,保存。
- 回到前台榜单页,刷新后该游戏的排序立即上升。
- 玩家点击该游戏,进入详情页,后端记录一条推荐日志。
- 在后台"数据统计"里查看该游戏的点击量,数据同步更新。
这条链路只要通了,就说明系统主业务流程没问题。我测试时最喜欢用这条链路做回归,每次改动后跑一遍,能快速发现前后端或者数据库层面的基础故障。
6. 常见问题排查与避坑实录
6.1 前端页面白屏
现象:打包部署后,首页能打开,但刷新页面变白屏。
排查过程:F12 查看 Network,发现刷新后请求的 JS 文件路径不对,返回 404。这是因为 Vue Router 默认使用 history 模式,刷新时浏览器直接访问了/game/xxx,而 Nginx 没有做 try_files 回退。
解决办法:在 Nginx 配置中加:
location / { try_files $uri $uri/ /index.html; }这样刷新任意路由都会回到 index.html,由前端路由接管。
6.2 后端接口报 401
现象:Postman 调试接口时,明明登录成功了,但请求业务接口还是 401。
排查过程:检查后发现 token 没有放到 Header 里,或者 Header 名和后端拦截器解析的不一致。后端要求Authorization头的格式是Bearer xxxxx,我前端封装时写成了直接传xxxxx,少了 Bearer 前缀。
解决办法:统一在 Axios 请求拦截器里处理:
config.headers.Authorization = 'Bearer ' + getToken()6.3 MySQL 8 时区问题导致时间错乱
现象:插入数据时,数据库里的 created_at 比本地时间早了 8 小时。
排查过程:MySQL 8 连接串里没加 serverTimezone 参数,JDBC 默认用了 UTC 时区。
解决办法:连接串改成:
jdbc:mysql://localhost:3306/game_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai6.4 推荐榜单数据不变化
现象:运营在后台改了游戏权重,前台榜单仍然保持原顺序。
排查过程:发现查询走的是 Redis 缓存,权重更新后没有清缓存。
解决办法:在推荐策略保存接口里,主动删除相关缓存 key,或者在策略表里加一个 version 字段,查询时校验版本号,版本不一致就重新查库写缓存。
6.5 热门榜数据量大后变慢
现象:塞入 20 万条测试数据后,榜单接口响应时间接近 1 秒。
排查过程:执行计划发现排序字段没有走索引,全表排序。
解决办法:给 heat_value 字段建索引,并把榜单接口的查询改成只查必要字段,不SELECT *。
6.6 常见问题速查表
| 问题表现 | 可能原因 | 解决方式 |
|---|---|---|
| 登录后跳回登录页 | token 过期或未校验 | 检查路由守卫逻辑和 token 过期时间 |
| 图片不显示 | 图片链接是相对路径 | 统一使用完整 URL,Nginx 配置静态资源 |
| 接口返回乱码 | 数据库或连接串字符集不对 | 全链路使用 utf8mb4 |
| 分页数据重复 | PageHelper 使用位置错误 | 确认分页紧跟第一条查询 |
| 前端报跨域 | 后端未配置 CORS | 开发环境用代理,生产环境 Nginx 配反向代理 |
| 定时刷新热度不生效 | 定时任务没开启 | 检查 @Scheduled 注解和 spring.task.scheduling 配置 |
7. 部署上线:从本地到服务器的一次完整过程
7.1 前端打包和 Nginx 配置
前端发布时执行:
npm run build打包产物在dist目录。把dist目录传到服务器,用 Nginx 托管:
server { listen 80; server_name your-domain.com; root /var/www/game/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里我实际部署时踩过一个坑:如果前端只配置了location /而没有单独配置/api代理,所有请求都会尝试去dist目录找静态文件,结果就是接口全部 404。所以记得location /api/一定要加上。
7.2 后端上线运行
后端是打好的 jar 包,上传到服务器后用 nohup 后台运行:
nohup java -jar game-recommend.jar --server.port=8080 > app.log 2>&1 &日志输出到 app.log,方便排查问题。上线初期我建议每天都翻一遍日志,重点看有没有 SQL 报错、接口超时、异常堆栈。后期稳定后可以接入更完善的日志采集,但小项目前期用文件日志完全够用。
7.3 上线后的基础监控
企业级系统上线后,最基础的监控至少要有三块:
- 接口可用性:核心接口定时探测,挂了能第一时间发现。
- 数据库慢查询:开启 MySQL 慢查询日志,定期分析。
- 服务器资源:CPU、内存、磁盘使用率要有预警,尤其是游戏封面图多的系统,磁盘很快就会被图片占满。
我用了个非常轻量但好用的方案:写一个定时任务脚本,每分钟用 curl 探测首页和榜单接口的 HTTP 状态码,如果连续三次非 200 就发告警。虽然没有商业化监控平台那么细致,但作为企业级项目的兜底,已经足够敏捷。
8. 复盘与扩展:这套系统接下来还能怎么长
8.1 运营两周后的真实体验复盘
系统上线运营了两周后,我自己盘点了一下各模块的实际使用频率和问题:
最常用的功能是游戏管理和推荐策略配置,运营几乎每天都要上新游戏、调整热门游戏的权重。这说明刚开始的判断是对的——管理后台才是系统的生产力中心。
最容易被忽视的是推荐日志模块。前期运营觉得无所谓,等要看活动专题的转化率时,才发现日志表有多重要。所以如果源码里没有这个模块,我建议一定要补上,不然后续想优化推荐效果会缺少数据支持。
最需要优化的点是图片处理。运营传游戏封面图时,经常有原图三四 MB 的情况,导致页面加载很慢。我在后期加了图片压缩和 WebP 格式转换。如果重新做这个系统,我会在最初就把图片处理模块设计好:上传原图后自动生成缩略图、列表图、详情大图三套尺寸,前台的加载速度会好很多。
8.2 后续升级的三个方向
第一,推荐策略引入时间衰减。目前的综合热度分不区分新老游戏,导致上架很久的老游戏如果一直在做推广,热度值会霸榜。加入时间衰减系数后,新游戏更容易获得曝光,榜单的活力会更强。
第二,用户行为采集和分析。给前台的浏览、点击、收藏、评分行为埋点,然后给用户打标签,做个性化推荐。这是推荐系统的进阶玩法,但前提是第 2 章里的 recommend_log 数据积累足够,不然模型没有数据喂。
第三,多端适配。游戏信息天然适合在手机上浏览。前端代码如果在 Vue 基础上用 uni-app 重构成小程序和移动端的版本,一套业务接口可以同时支撑三端,后端的服务能力会进一步放大。
8.3 最后分享一个我个人的小技巧
开发这种前后端分离的管理系统,最耗时间的往往不是功能逻辑,而是联调时接口细节来回改。我后来养成一个习惯:后端每个接口写完,先用 Postman 把参数、响应、异常情况全部测一遍,再通知前端联调。后端把异常剧本都铺排好,前端拿到的是一个标准化的接口,联调效率能提升一倍。
另外,数据库脚本和接口文档一定要跟着代码一起提交版本管理。我见过太多项目,代码是新的,数据库脚本还是老的,导致新人拉代码起服务时各种报错。这个项目里,我把初始化 SQL、测试数据、接口 Markdown 文档全部放进了源码库的 docs 目录,每次有同学接手都能顺畅跑通。
如果你正准备做同类游戏推荐或内容管理系统,拿这套源码做骨架,理解清楚推荐策略、权限模型和前后端交互的底层逻辑,再加上自己的业务定制,会比自己从零开始写得要稳得多。项目做完后再回头看,技术难点其实不难,难的是把业务规则和工程实现缝合成一个可维护的完整系统。