ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

游戏推荐系统毕业设计实战:Django+Vue+协同过滤全链路解析

游戏推荐系统毕业设计实战:Django+Vue+协同过滤全链路解析 每年毕业设计季都会有一批人纠结到底选什么题目。游戏推荐系统其实是特别适合拿来当毕设的赛道它既有后端逻辑、有前端交互又有算法味儿还自带大数据可视化这个加分项无论你将来简历往哪个方向投都能拿出对应的话术。这篇文章我想把这个项目的整体链路拆开讲从选题理由、技术选型到推荐算法怎么落地、可视化怎么做、前后端如何联动最后聊一聊答辩时那些容易被追问的细节。内容面向打算用DjangoVue.js做毕设的本科生也适合想把这个项目往上扩展成作品集的项目开发者。1. 为什么游戏推荐系统值得做成毕业设计1.1 选题的底层逻辑别选没有矛盾的题目毕设选题最忌讳的不是题目难而是没有矛盾。所谓矛盾就是系统内部必须存在一个需要你花力气解决的核心问题。很多学生选图书管理系统学生信息管理系统做到最后发现无非是增删改查数据库建五张表管理员登录进去点点点全程没有任何一个地方真正卡住你写完也就写完了答辩的时候老师问你觉得这个系统难点在哪里你自己都答不上来。游戏推荐系统的矛盾非常清晰数据太多用户不知道玩什么平台不知道给每个用户推什么。一个推荐行为背后牵扯到用户画像、物品特征、历史交互、相似度计算、效果评估这些环节随便挑一个都能做出深度。更重要的是推荐系统天然自带数据量大和需要可视化这两个属性正好把毕业设计里大数据和可视化两个热门要素都占上了。1.2 游戏领域的独特优势同样是推荐系统选电影还是选游戏效果差别很大。电影领域有现成的MovieLens数据集用的人太多了老师一眼就能看出来你是照搬公开数据集。游戏领域的数据集相对稀缺你往往需要自己设计爬虫或者手工构造数据这个过程本身就是工作量也是答辩时能拿出来讲的东西。第二个优势在于游戏数据维度丰富。游戏有类型、开发商、发行商、发行年份、平台、好评率、平均时长、价格、在线人数峰值、销量等几十个字段。这意味着你的可视化和推荐特征工程都有足够的素材而不是只有一张孤零零的评分表。再有一点游戏推荐的业务逻辑比电影推荐更有意思。游戏是一个高门槛娱乐产品——它需要用户投入时间甚至金钱所以用户决策更谨慎推荐系统发挥的空间更大。另外游戏之间天然存在品类迁移关系喜欢《文明》的人大概率也会喜欢《群星》喜欢《黑暗之魂》的人可能会接受《只狼》。这种隐性关联就是协同过滤能捕捉的东西。1.3 系统功能边界哪些功能必须做哪些是加分项我把这个项目的功能分成三个层级功能层级具体功能说明核心功能用户注册登录、游戏展示、基于用户的协同过滤推荐、基于物品的协同过滤推荐、游戏详情页这是系统的骨架没有这些题目就不成立提升功能热门榜单、个性化推荐页、游戏搜索、收藏/评分行为采集让系统用起来像个真正的产品展示功能后台数据可视化大屏、游戏分类分布、评分趋势、用户行为分析对应题目里的游戏可视化和大数据也是答辩PPT里的门面建议时间紧张的同学优先保证核心功能100%做完提升功能做两个就够可视化部分至少要有三个图其中一个必须是实时联动用户的——比如用户点击某个游戏分类后其他图表跟着变化。这种交互式可视化比静态图高一个档次答辩时很加分。2. 技术选型Django Vue.js这套组合到底好在哪2.1 后端选Django不是因为简单而是因为全套很多人选Django是因为简单、快、能跑这个认知需要修正。Django确实开发效率高但它的核心优势是自带全套基础设施这对毕设项目尤其重要。拿推荐系统来说一个标准的后端至少需要这些东西ORM操作、用户认证、Admin后台、路由分发、缓存接口。Django全部内置不需要你东拼西凑去集成第三方库。Django的ORM让你不需要手写SQL就能完成复杂查询——比如找出评分大于4且游戏类型包含策略的游戏直接用filter就能搞定这在答辩演示时非常直观。Django自带的Admin后台还能让你在管理系统里直接增删改查游戏数据不需要额外写界面省下来的时间全部可以用来打磨推荐算法和可视化。Django另一个关键特性是自带的认证系统。游戏推荐系统必须有用户体系Django的auth模块内置了User模型、Session管理、密码哈希你用几行代码就能搭起注册登录。如果临时想加个邮箱验证或者第三方登录Django社区到处都有现成方案。选Django本质上选的是一个不用太操心基础设施现成性的生态。2.2 前端选Vue.js前后分离不是赶时髦这里要诚实地说一句如果你的毕设只是做给老师看前后端不分离反而更快Django的Template就能搞定。但题目里写了Vue.js就意味着你要做成前后端分离架构这个架构本身在答辩时就是一个可以讲清楚的现代Web开发实践。Vue.js的选择理由主要有三个。第一上手曲线平缓比React更友好你不需要理解JSX也能写出组件。第二Vue的响应式机制特别适合数据驱动视图的场景——推荐结果一旦从后端返回页面自动渲染不需要手动操作DOM。第三Vue生态里有Vue Router和Vuex/Pinia做多页面应用非常方便。在游戏推荐系统里Vue的组件化优势体现得很明显。一个游戏卡片就是一个组件推荐列表、热门榜单、搜索结果显示都复用同一套卡片组件评分组件、筛选组件、图表组件各自独立页面之间互不干扰。这一点在后期维护和扩展时特别重要——如果你后面想加一个相似游戏推荐模块直接新增组件不用翻以前的代码。2.3 数据库与可视化选型数据库首选MySQL和Django的ORM配合最成熟。如果你本地不想装MySQLSQLite也能跑但答辩时最好还是用MySQL显得更企业级。这里有个小建议数据量不用太大5000条游戏数据、5万条评分记录就足够跑推荐和可视化了人工造数或者写爬虫都行。可视化部分后端提供数据接口前端用ECharts渲染这是最稳妥的组合。ECharts是百度开源的项目图表类型丰富文档齐全中文社区活跃遇到问题基本都能搜到答案。我后面会专门讲可视化怎么做这里先不展开。提示如果答辩老师问你为什么不用Spring Boot/Vue.js别慌。你可以说Django的ORM和Admin后台能快速构建数据密集型应用而Python生态在数据处理和算法验证上更方便你甚至可以直接用Python写推荐算法和Django无缝集成。这个回答比我不会Java要有说服力得多。3. 推荐算法核心实现从相似度到混合推荐3.1 推荐系统的基本套路先理解你喜欢的再找你没见过的很多同学对推荐算法有误解觉得一定要上深度学习才算高级。实际上经典协同过滤在中小规模数据上效果完全不差而且更容易解释。你完全可以在答辩时说我用的是基于用户的协同过滤因为它的核心思想非常直观——找到和你兴趣相似的人把他们喜欢但你没玩过的游戏推荐给你。这句话老师一听就懂。推荐系统的类型主要有三种基于内容的推荐根据游戏自身的属性类型、开发商、标签找相似游戏。不需要用户历史数据但结果比较死板。基于用户的协同过滤User-CF找相似用户推荐他们玩过的游戏。基于物品的协同过滤Item-CF找相似游戏推荐那些和你已经玩过/喜欢过的游戏相似的游戏。在游戏推荐系统里我推荐的做法是User-CF和Item-CF都实现然后用加权混合这个结构做出来会让整个项目的技术含金量上一个台阶。3.2 相似度计算的底层余弦相似度的原理与实现协同过滤的核心是找相似而衡量相似最常用的指标是余弦相似度。它的原理是把每个用户对游戏的评分看成一个向量向量在每个维度上的值就是该用户对某个游戏的评分没评过的记为0然后计算两个向量夹角的余弦值余弦值越接近1说明两个用户越相似。代码实现并不复杂。在Django里你可以这样组织# recommend/utils.py import math from collections import defaultdict def build_user_item_matrix(ratings): ratings: 评分记录列表每项是(用户id, 游戏id, 评分) 返回: {user_id: {game_id: rating}} matrix defaultdict(dict) for user_id, game_id, score in ratings: matrix[user_id][game_id] score return matrix def cosine_similarity(vec1, vec2): 计算两个评分向量的余弦相似度 common_keys set(vec1.keys()) set(vec2.keys()) if not common_keys: return 0.0 dot_product 0.0 norm1 0.0 norm2 0.0 for key in common_keys: dot_product vec1[key] * vec2[key] for value in vec1.values(): norm1 value ** 2 for value in vec2.values(): norm2 value ** 2 if norm1 0 or norm2 0: return 0.0 return dot_product / (math.sqrt(norm1) * math.sqrt(norm2))注意一个关键点两个用户共同评过分游戏越多相似度的可信度越高。所以在计算相似用户时最好只保留共同评分数量大于2或3的用户对否则两个用户只共同评了1个游戏相似度算出来1.0毫无意义。3.3 User-CF推荐流程候选集怎么找得分怎么算找到相似用户之后推荐分两步第一步确定候选游戏集合。对所有和目标用户相似的用户的行为做并集排除目标用户已经玩过的游戏剩下的就是候选集。第二步计算候选游戏的推荐得分。得分不是简单统计多少个相似用户玩过而是要加权——相似度越高的用户他的选择权重越大。我是这样写的def user_cf_recommend(user_id, top_n10): ratings Rating.objects.values_list(user_id, game_id, score) matrix build_user_item_matrix(ratings) target_vector matrix.get(user_id, {}) if not target_vector: return [] # 第一步找和目标用户最相似的K个用户 similarities [] for other_user, other_vector in matrix.items(): if other_user user_id: continue sim cosine_similarity(target_vector, other_vector) if sim 0.1: similarities.append((other_user, sim)) similarities.sort(keylambda x: x[1], reverseTrue) top_k_users similarities[:10] # 第二步聚合相似用户的喜好加权计算推荐得分 scores defaultdict(float) for other_user, sim in top_k_users: other_vector matrix[other_user] for game_id, score in other_vector.items(): if game_id in target_vector: continue # 用户已经玩过不再推荐 scores[game_id] sim * score ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return ranked[:top_n]这里有个细节值得注意——我用的评分是原始评分1到5分而没有做平均分去中心化。严格来说去中心化会更好因为它能消除用户的评分倾向有人习惯打4分有人习惯打5分。你可以做这样一个改进把用户评分减去该用户所有评分的平均值再用修正后的分数计算相似度和推荐得分。这个改进可以在论文里单独拿出来讲显得你考虑得很全面。3.4 Item-CF和混合推荐为什么要两个算法一起用User-CF有个天然缺陷——冷启动。新用户没有评分历史系统完全不知道他喜欢什么无法计算相似用户推荐直接失效。Item-CF的冷启动问题则相对好处理只要知道用户当前的偏好比如他刚点击了某个游戏就能推荐相似游戏。Item-CF的核心思想正好反过来——喜欢这个游戏的人往往也喜欢那个游戏。计算游戏和游戏之间的相似度用用户在游戏上的历史行为来加权推荐。混合推荐最简单也最稳妥的做法是加权融合def hybrid_recommend(user_id, alpha0.6, top_n10): user_cf_result dict(user_cf_recommend(user_id, top_n*2)) item_cf_result dict(item_cf_recommend(user_id, top_n*2)) merged {} for game_id in user_cf_result: merged[game_id] alpha * user_cf_result[game_id] for game_id in item_cf_result: merged[game_id] merged.get(game_id, 0) (1 - alpha) * item_cf_result[game_id] ranked sorted(merged.items(), keylambda x: x[1], reverseTrue) return [game_id for game_id, score in ranked[:top_n]]alpha是User-CF的权重0.6是我测试下来效果比较好的经验值。新用户阶段User-CF拿不到数据alpha可以动态调低。换句话说新用户主要靠Item-CF和热门游戏兜底老用户则两个算法一起发力。这个动态权重的设计在答辩时非常加分。3.5 冷启动问题的几个具体解法冷启动是推荐系统永恒的话题毕设里不需要做到完美但只要能给出合理方案就能体现水平。我实际用到的有三招新用户注册时强制勾选偏好标签你喜欢哪些游戏类型策略 / 角色扮演 / 射击 / 休闲 / 竞速…… 这些标签作为初始的UserProfile用来冷启动推荐。登录后先展示热门游戏榜用户产生点击行为后再逐步个性化推荐。用collectstatic把评分和收藏的行为埋点数据实时入库这样用户只需要操作几下系统就能逐步构建用户画像。代码上你可以在Django的Profile模型里加上偏好类型字段然后在User-CF算不出来结果时直接用filter(type__inuser_profile.favorite_types)来兜底。这虽然不是高级算法但它是真实产品里非常常见的技术方案说出来完全不被质疑。4. 游戏数据可视化用什么图数据从哪来怎么交互4.1 可视化的核心不是图好看是信息有层次很多同学把可视化做成装饰品放了一堆图表但每个图之间毫无关联数据也没有交互。答辩时老师问这个图想说明什么回答不出来。记住可视化在毕设里的价值是把推荐系统的效果和数据规律用直观的方式展示出来。图必须能讲出一个故事——比如游戏类型分布这个平台上有多少策略游戏、多少角色扮演游戏评分Top10哪些游戏最受用户认可用户评分活跃度哪个时间段的评分行为最多不同类型游戏的平均评分差异策略游戏是不是普遍比射击游戏评分高设计可视化时我建议先画一张故事线先展示整个游戏库的全貌类型分布、年份分布再聚焦到用户行为和游戏评分最后落到推荐结果的可视化比如当前登录用户推荐的10款游戏分布在哪几个类型。4.2 ECharts图表选型与Django后端数据接口前端可视化我用的是ECharts后端提供JSON接口。Django这边实现方式如下在views.py里写视图函数返回通过ORM聚合好的数据再用JsonResponse格式输出。举个例子统计游戏类型分布# game/views.py from django.http import JsonResponse from django.db.models import Count from game.models import Game def game_type_distribution(request): data (Game.objects .values(game_type) .annotate(countCount(id)) .order_by(-count)) result { categories: [item[game_type] for item in data], values: [item[count] for item in data] } return JsonResponse(result)前端在Vue里这么调用// 在Vue组件中 script import * as echarts from echarts; export default { name: TypeDistribution, mounted() { this.fetchData(); }, methods: { async fetchData() { const res await fetch(/api/game/type-distribution/); const data await res.json(); this.renderChart(data); }, renderChart(data) { const chart echarts.init(this.$refs.chartRef); chart.setOption({ title: { text: 游戏类型分布 }, tooltip: {}, xAxis: { data: data.categories }, yAxis: {}, series: [{ type: bar, data: data.values }] }); } } } /script这个模式非常简单——后端只管返回标准化的JSON前端负责把JSON变成图表。需要注意的是fetch到数据时后端返回的JSON字段名一定要和前端约定好比如统一使用categories和values不要一会用labels一会用data联调时会疯掉。4.3 交互式可视化的加分做法静态图表做出来了只会让老师觉得你会用ECharts但交互式可视化能让你显得更厉害。最简单的实现方式是给图表的click事件绑定一个回调点击某个分类后重新请求后端接口更新另一个图表的数据。现实操作中我做过一个点击类型柱状图更新评分Top10榜单的联动。前端绑定事件this.chart.on(click, (params) { const selectedType params.name; this.$emit(type-selected, selectedType); // 触发子组件的重新加载 this.$refs.topRanking.loadByType(selectedType); });这样用户在页面上点一下策略分类旁边的Top10就只显示策略游戏的评分榜。这种图表驱动图表的交互会让整个可视化变得有生命力——它不再是静态的图而是一个可以和用户对话的数据面板。5. 前后端联调DRF写接口Vue组件对接常见坑逐个拆5.1 DRF为什么比裸Django更好写API如果你准备用Django写JSON接口建议直接上Django REST Framework。DRF的价值在于你可以用ModelSerializer把Model自动序列化成JSON不需要手动拼字典它还自带了分页、过滤、认证等一大堆现成功能。举个例子游戏列表接口# game/serializers.py from rest_framework import serializers from game.models import Game class GameSerializer(serializers.ModelSerializer): class Meta: model Game fields [id, title, game_type, release_date, score, average_hours, cover_url] # game/views.py from rest_framework import viewsets from game.models import Game from game.serializers import GameSerializer class GameViewSet(viewsets.ModelViewSet): queryset Game.objects.all() serializer_class GameSerializer这样写完之后一个带增删改查全部接口的REST API就成型了。Django的URL配置指向这个ViewSetVue的axios.get(/api/games/)就直接拿到游戏列表。整个流程下来不超过50行代码这就是DRF的效率。5.2 JWT认证与Token存储别再用Session了前后端分离架构下再使用Django默认的Session认证会非常别扭因为前端不在服务端渲染页面上Session的Cookie交互逻辑不清楚。我建议用JWTJSON Web Token。用一个叫djangorestframework-simplejwt的库配置很简单# settings.py REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework_simplejwt.authentication.JWTAuthentication, ], }前端在登录接口拿到access和refresh两个token之后把accesstoken存在localStorage里每次请求在Authorization头带上// 封装的axios请求 axios.interceptors.request.use(config { const token localStorage.getItem(access_token); if (token) { config.headers.Authorization Bearer ${token}; } return config; });这是标准做法。但要注意JWT一旦签发过期之前无法在服务端强制吊销所以accesstoken有效期要设短一点比如30分钟过期后前端自动用refreshtoken换新token如果refresh也过期了就让用户重新登录。这个机制在真实项目中太常用了说出来非常加分。5.3 踩过的坑跨域问题、时间格式、图片路径这部分全部是实际开发时一定会碰到的问题我挨个说。跨域问题前后端分离意味着前端跑在localhost:5173Vite默认端口后端跑在localhost:8000浏览器默认不允许跨域请求。解决方案是Django装一个django-cors-headers然后在settings.py里配置INSTALLED_APPS [ # ... corsheaders, ] MIDDLEWARE [ # 注意CORS中间件要放在最前面 corsheaders.middleware.CorsMiddleware, # ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, http://127.0.0.1:5173, ]这个坑几乎所有人都会踩。别问为什么突然所有请求都报错大概率就是CORS没配好。时间格式问题DRF默认返回的日期格式是类似2024-05-01T00:00:00Z的ISO 8601格式前端直接显示会非常丑。你要么在后端settings.py里配置DATETIME_FORMAT要么在Serializer里指定格式class GameSerializer(serializers.ModelSerializer): release_date serializers.DateTimeField(format%Y-%m-%d) class Meta: model Game fields [id, title, release_date]图片路径问题游戏封面图片上传后Django默认只存MEDIA目录的相对路径前端要用绝对路径才能访问。在Serializer里处理一下def get_cover_url(self, obj): request self.context.get(request) if obj.cover and request: return request.build_absolute_uri(obj.cover.url) return None这一步不处理前端图片全部加载不出来而且这种情况只在部署后测试时才会暴露——开发时你可能根本没在乎过图片地址。5.4 Vue组件之间通信兄弟组件传值用事件总线还是Vuex游戏推荐页的结构通常是这样的左边是游戏列表和筛选器右边是推荐结果和个人信息。两个区域之间存在联动——用户在左边点击某款游戏右边要展示该游戏的相似推荐。这时候两个组件之间的通信就成问题。简单情况下用$emit向上传递事件父组件接收后再通过props传给另一个子组件能解决单层父子通信但组件多了会非常绕。更推荐用PiniaVue3的环境下做状态管理把当前点击的游戏当前筛选条件推荐结果全部放进Store里任何组件都可以直接读取和修改。# 简单的Store以Vue3Pinia为例 import { defineStore } from pinia export const useRecommendStore defineStore(recommend, { state: () ({ currentGame: null, filterType: , recommendList: [] }), actions: { setCurrentGame(game) { this.currentGame game } } })这个Store在任何组件里都能被访问不用一层层传参联调出问题的概率低很多。6. 推荐效果验证与系统冷启动别等答辩才发现推荐结果总是空6.1 评分评什么才给推荐系统留下痕迹前面说了算法实现但有一个容易被忽略的问题如果系统没有足够多的评分数据推荐出来就是空的。你在开发阶段肯定要往库里灌测试数据学生团队最容易犯的错是直接用SQL批量插入几万条随机评分结果算法跑出来一个乱码一样的推荐结果。我建议测试数据要有合理性。比如构造5个模拟用户每个用户的评分行为要符合某种画像——用户A只玩策略游戏评分普遍偏高用户B是射击游戏爱好者用户C什么类型都玩。这样推荐算法才会捕捉到真实的用户分群推荐结果才能讲得通。答辩时老师让你当场演示给新用户推荐你创建一个新账号勾选策略偏好如果系统推荐出《文明》《钢铁雄心》《城市天际线》这个演示就是成功的——比辩解算法跑出来可能不准强一百倍。6.2 推荐效果怎么评估离线指标和线上感受答辩时老师十有八九会问你的推荐效果怎么评估。哪怕是毕设也不能只说看起来还可以。最简单的离线评估方式是把评分数据按时间戳分成训练集前80%和测试集后20%在训练集上训练在测试集上验证计算准确率和召回率。def evaluate_recall(train_data, test_data, user_id): recommended user_cf_recommend(user_id, top_n20) recommended_set set(recommended) # 取测试集中该用户真正玩过的游戏 actual_set {game_id for game_id, score in test_data[user_id].items()} if not actual_set: return None hit_count len(recommended_set actual_set) recall hit_count / len(actual_set) precision hit_count / len(recommended_set) return precision, recall你不用做多复杂的实验只要在论文里给出3到5个测试用户的平均精确率和召回率然后简单说明推荐算法在测试集上达到了约X%的召回率和Y%的精确率说明在历史数据上具备一定的推荐能力就算给出了一份回答。当然如果你能力足够可以再加一个baseline对比——比如和热门推荐做对比证明协同过滤确实优于无脑推热门。这个对比在答辩时基本是必杀技。7. 答辩环节的核心拆解从做出来到讲明白7.1 演示顺序怎么安排答辩演示环节建议按照以下顺序来走每一步都有明确目的先展示可视化大屏2分钟。让老师对整个系统的数据量有个直观概念建立大数据的心理暗示。再展示用户注册与冷启动推荐1分钟。演示一个新用户怎么通过偏好标签获得初始推荐。接着演示老用户的行为反馈2分钟。给某个用户打几个评分刷新推荐页面展示推荐结果变化强调系统会根据用户行为动态调整推荐。最后切到后台管理界面1分钟。展示如何用Django Admin维护游戏数据并不经意地提到数据入库后推荐算法能实时感知变化。这个顺序的核心思想是最先展示最炫的东西再一步步展示系统逻辑的深度最后落到工程实现的细节。千万不要一开始就展示登录注册页面那是全项目最无聊的地方。7.2 答辩最容易被追问的四个问题问题一为什么推荐结果里有重评重复推荐这个不是错误而是两个推荐源User-CF和Item-CF的推荐列表有交集。你可以说系统是混合推荐两种算法的结果合并去重后按加权分数排序所以可能存在用户A因为和你相似所以推荐了同时也因为游戏X和你玩过的游戏Y相似所以推荐了的情况。这反而是系统考虑更全面的体现。问题二评分数据从哪来的这个问题要提前准备。如果你用的公开数据集直接说明出处和数据量如果是自己造的就说系统初期采用冷启动策略内置了一批匿名用户的行为数据作为种子。注意答辩老师不一定要求你用真实数据但你不能支支吾吾。问题三酒香也怕巷子深——推荐系统的探索与利用平衡。如果你有余力可以在论文里加一个小节说明你的系统暂时只做利用根据已有数据推荐最可能的还没有做探索随机推荐一些新游戏来获取反馈。然后主动说后续可以引入epsilon-greedy策略以一定概率随机推荐冷门游戏用来探索用户潜在兴趣。这会让老师觉得你不是只会调库而是对推荐系统的核心挑战有认知。问题四如果用户数增加到百万级这个算法还能跑吗答案要诚实协同过滤的时间复杂度是O(n^2)用户量百万级肯定跑不动生产环境会用矩阵分解、Embedding等更高效的方法。毕设的功能验证阶段这套实现足够。但你可以补一句如果要做生产级优化可以采用基于矩阵分解的协同过滤或者用计算物品相似度的离线预计算方案。这个问题答好了比你在答辩PPT上写一万字都管用。7.3 文档与PPT要匹配的演示逻辑毕业设计通常要求论文文档、PPT、演示三件套一致。很多人的PPT和演示脱节PPT在讲算法原理实机演示在展示页面流转老师看得云里雾里。我建议每一页PPT都对应一个演示环节。PPT讲完算法原理马上进行推荐效果演示PPT讲完可视化设计方案立刻展示可视化大屏。这样整个答辩节奏紧凑、逻辑连贯给人感觉是精心设计过的。文档里的系统架构图用文字来描述模块关系不要画得太复杂三到四个层级表现层Vue页面→ 应用层Django API→ 数据层MySQL再加一个推荐算法引擎作为独立模块放在中间表示它被API层调用但不属于CRUD逻辑。这张图不需要用复杂的绘图工具用Word自带的流程图或者draw.io就能画得很清晰。7.4 一个容易被忽视的加分细节用户行为埋点推荐系统越用越准这句话大家都爱听但如果你的系统没有行为记录只靠初始评分撑着这句话就是空谈。在完成核心功能之后建议加一个简单的埋点用户点击游戏详情、收藏、评分、搜索关键词全部记录到一张UserBehavior表里。class UserBehavior(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) game models.ForeignKey(Game, on_deletemodels.CASCADE) action models.CharField(max_length20) # click/favorite/rate/search rate_score models.IntegerField(nullTrue, blankTrue) created_at models.DateTimeField(auto_now_addTrue)有了这张表你既可以在可视化中展示用户行为时序图也可以在冷启动时用近期点击过的游戏来临时推荐更能在论文里写系统具有行为反馈闭环能力。一个表解决三个问题性价比极高。8. 开发过程中最值得复盘的经验从踩坑到提效8.1 数据库设计的教训不要反复改表游戏推荐系统的数据模型其实并不复杂用户表、游戏表、评分表、行为表再加上用户偏好表。但新手最容易犯的错是一开始就把表设计得很复杂字段加了一大堆最后发现前端根本用不上。我建议在开发第一个版本时保持数据模型的精简——游戏表字段先放10个以内评分表就放user_id、game_id、score、created_at四个字段。做完核心功能后再根据需求逐字段补充。Django的Migration在开发阶段很友好改字段不会丢数据但频繁改表会浪费大量时间而且容易在前后端联调时产生字段名对不上的问题。经验之谈表结构第一次设计至少要花半天时间把前端需要哪些字段、算法需要哪些字段、可视化需要哪些字段清单列出来。统一一次到位后面你就知道省了多少事。8.2 虚拟环境与依赖管理别在毕业设计上栽在环境上每年毕业设计季都有人因为环境问题翻车——在A电脑上跑了半天换到演示电脑直接起不来。最稳妥的做法是项目根目录下放一个requirements.txt并且把Django、Vue的版本全部固定住django5.0.3 djangorestframework3.14.0 djangorestframework-simplejwt5.3.1 django-cors-headers4.3.1 mysqlclient2.2.0前端的package.json同样要锁定版本尽量用package-lock.json提交到仓库。另外建议用Anaconda或者venv创建独立环境别把项目依赖装到全局Python里答辩机器上装一堆无关包老师一问这个依赖是干什么的你答不上来会非常尴尬。8.3 时间轴规划照着这个计划来不会慌如果你还有两个月时间可以按下面的节奏推进第1周搭建Django项目和Vue项目建好数据库表跑通注册登录。第2周游戏列表、详情页、搜索功能完成。第3周实现评分/收藏行为开始用测试数据验证User-CF推荐算法。第4周实现Item-CF和混合推荐做冷启动兜底逻辑。第5周可视化接口开发ECharts图表接入前端。第6周前后端联调修复跨域、时间格式、图片路径等细节。第7周写文档画架构图做PPT录演示视频。第8周预答辩模拟整理答辩问题优化演示流程。这个计划的核心是算法和可视化尽量不要留到最后因为它们是不可控的有可能调很久。留给后期打磨的时间越多项目完成度越高。9. 一些个人实操心得做完这个项目最大的感触是——毕业设计做得全不如做得透。游戏推荐系统这个题目你可以做到极简版也可以做到发表级别的完善程度关键不在于功能有多少个页面而在于核心链条数据→算法→推荐→评估→迭代是否闭环。我在项目里额外加了行为埋点和简单的离线评估模块这两块在答辩中是产生最多提问和最多认可的地方因为它们都体现了一个信号你不是在做一个摆设而是在试图真正解决推荐效果的问题。最后给一个非常实际的小技巧准备一个错误日志文档。开发过程中遇到的所有报错、解决方案、甚至你犯过的蠢错都记下来。答辩时如果老师问你遇到的最大困难是什么你直接翻这个文档讲一个具体的排错故事——比如CORS跨域问题排查了两天最后发现是中间件顺序不对。这种真实的故事比任何我克服了很多困难的套话都更有说服力。毕设的意义从来不只是交一份代码它是在训练你遇到问题、分析问题、解决问题这套完整的思维习惯而这个习惯才是你去下一个项目里真正带走的东西。
返回列表