第一次看到这个题目,我的第一反应是这学生选题眼光不错。Django + LLM大模型把后端开发和最前沿的智能生成能力结合起来,再加上数据分析、个性化推荐,正好覆盖了毕业设计里最容易出彩的三个方向:系统完整、算法有深度、展示有亮点。这不是那种只做登录注册加增删改查的“管理系统”,而是一个能从数据采集一路做到智能推荐、最后用可视化大屏讲出完整故事的旅游路线推荐系统。这篇文章,我会从实际动手做过类似系统的角度,把需求拆解、架构设计、算法实现、答辩准备的思路完整讲一遍。这个选题适合谁参考?如果你正打算拿它作为毕业设计题目,或者想在这个基础上改成其他领域的路线规划(比如餐饮打卡路线、学习路径规划),下面的拆解思路都通用。我会尽量把每一步的“为什么”讲清楚,关键代码片段给到,容易踩的坑也提前标出来。1. 选题价值与需求拆解,为什么这套架构能撑起一篇毕设1.1 从标题拆解出的真实功能边界一个标题里通常藏着三个技术关键词:Django + LLM 智能路线规划、数据分析、个性化推荐。落到实际系统,至少要拆出四条主线,缺一条都撑不起“系统”这两个字。用户端。注册登录、个人中心、景点库浏览、按关键词和分类筛选、景点详情、收藏和评分。别小看这些基础功能,它们是后续推荐算法的数据来源。评分记录和收藏记录就是协同过滤的输入,没有这些行为数据,算法再漂亮也是空转。智能推荐与规划端。这是整篇论文的核心。推荐端负责从用户历史行为中推断偏好,产出候选景点;规划端负责把这些候选景点按照地理位置、游玩时长、开放时间组装成一条合理路线。LLM在这里不是主角,而是增强模块——它用来生成路线的解说文案、推荐理由、天气穿衣建议等自然语言内容,提升系统的“智能感”。这里的分工要想清楚:算法负责“算得准”,大模型负责“说得好”。数据分析端。对景点热度、用户画像、访问趋势做统计,再以可视化图表展示。这部分答辩评委特别爱看,因为能直观体现数据处理能力,也容易做出视觉效果。管理后台端。用Django自带的Admin可以快速搭建,用来管理景点、用户、收藏、路线数据。演示时也可以从这里快速造数据,方便展示不同场景。功能边界上,我的建议是不要贪多。很多同学看到这个题目就想着把所有功能都做一遍,结果每个模块都是半成品。更好的策略是:把用户端、推荐端、规划端三个核心闭环做扎实,管理后台用Admin自带能力加少量自定义页面,数据分析做一张大屏。这样工作量可控,论文也有清晰的主次关系。1.2 技术栈对比:Django为什么比Flask和Spring Boot更适合这个题目用Django几乎是最优解,我从三个维度说理由。第一,Django自带Admin后台和用户认证。毕业设计最耗时间的从来不是算法,而是各种管理页面。Django的Admin只需要注册模型,就能获得一个可操作的、界面干净的后台。论文里截图、演示时展示数据,都足够用。如果换成Flask,这些都要自己写,多出来的时间比算法本身还多。第二,Django的ORM对数据分析友好。后面要做景点热度排行、用户年龄分布这类统计,直接用ORM的aggregate、annotate就能完成,不需要再额外写一堆原生SQL。如果用Pandas做更复杂的分析,Django也提供了把查询集数据转DataFrame的成熟路径。第三,Django的MTV分层结构特别适合画架构图。答辩评委看论文,第一眼就是技术架构图。MTV分层思路和最终论文的章节逻辑基本一一对应,画图省事,讲起来也顺。我也做过Flask项目,轻量是真轻量,但项目一旦涉及多模块、后台管理、权限控制,需要手动接的东西太多。Spring Boot能力最强,但学习成本高,一套Java写的毕设如果时间分配不好,很可能卡在环境配置上。所以毕设场景下,Django是开发效率、学习曲线、答辩表现三者最均衡的选择。方案后台与认证算法落地速度可视化配合答辩友好度Django + LLM API自带Admin,最快高ECharts直接接API高Flask + 规则引擎需手写,较慢中可行低,缺乏智能性Spring Boot配置复杂中可行中,但时间风险大1.3 哪些模块最容易被评委追问,提前想好答案做过答辩的人都知道,评委喜欢盯着标了“智能”“推荐”的模块反复问。这个系统里有三个问题,务必提前准备。推荐算法为什么选协同过滤而不是深度学习?因为毕设数据量不大,行为数据稀疏,深度学习容易过拟合,而且很难解释。协同过滤简单、可解释性强,配合标签相似度做混合推荐,讲起来更有底气。路线规划算法是精确解还是近似解?景点数量增加后,精确组合是NP难问题,所以一般用最近邻贪心加局部搜索(比如2-opt)求近似解。要能说出复杂度:贪心阶段O(n²),2-opt每轮O(n²)。这个回答在答辩时很加分。LLM在系统里是必需还是可选?诚实的说法是:增强体验、生成自然语言,核心路线规划不依赖它。这样解释既展示工程判断,又避免被追问“如果大模型挂了系统还能不能用”时陷入被动。2. 系统架构、数据建模与工程初始化2.1 整体架构与代码目录怎么组织先讲整体数据流。用户打开浏览器进入路线推荐页,填写偏好或者直接点击“为我规划”,前端把请求发给Django视图;视图层不直接写业务逻辑,而是调用Service层;Service层里,推荐服务取用户历史评分计算相似用户,规划服务调用推荐结果和景点库生成路线,大模型服务负责生成描述文本;最后视图组装JSON返回前端。数据存储全部由Django ORM操作。目录上我建议不要把所有代码堆在一个app里,模块拆开便于论文分章节叙述。举例:travel_recommend/ ├── manage.py ├── config/ # 项目配置 ├── apps/ │ ├── users/ # 注册登录、用户画像 │ ├── spots/ # 景点管理、搜索 │ ├── recommend/ # 推荐算法与路线规划 │ ├── analysis/ # 数据分析接口 │ └── llm/ # 大模型封装 └── templates/static/这样每个app的名字本身就是论文里的一章标题,写作时思路不会乱。前端部分,不用上太重的框架。Bootstrap加JQuery,或者原生HTML加ECharts,就能完成演示效果。如果想提升一点层次,可以用Vue的CDN模式,但没必要引入构建工具链,否则工程复杂度反而变成负担。毕设的核心是系统逻辑完整,不是前端工程化。2.2 核心数据表:用户、景点、评分、路线数据库设计决定了后面算法实现顺不顺畅,按核心程度排序如下。用户表扩展。Django自带User模型,但推荐系统需要用户画像,所以通常新建Profile表和User一对一关联,字段包括年龄段、职业、偏好标签。偏好标签用多对多关联到景点类型表,这样新用户冷启动时可以直接按标签匹配。景点表。字段尽量完整,除了名称、简介、图片,至少要有经度、纬度、门票价格、开放时间、建议游玩时长、评分、热度、景点类型。经纬度用DecimalField存,精度设为小数点后6位,方便计算地理距离。开放时间建议用字符串存“08:00-17:30”,解析时拆开。评分表。一张表同时记录评分和评论,外键关联用户和景点,字段包括评分、评论内容、创建时间。要建联合索引(user_id, spot_id),后面算相似矩阵时查询快。这张表是推荐算法的燃料,纯手工录入会很痛苦,我用管理后台模拟脚本批量生成了一组行为数据,保证每个用户至少有十几次评分记录。收藏表可以单独建,也可以和评分合并,我建议单独建。因为“收藏”这个动作比“评分”轻量,是另一个维度的正反馈,数据分析时也可以作为热度权重。路线表。存储规划结果:用户、路线名称、路线内容(JSON格式,包含每天的景点顺序和时间安排)、创建时间。用Django的JSONField存,读取方便,论文里展示时也好解释。2.3 景点数据从哪里来,三条路径怎么搭配景点数据是系统的基础,但也是最容易被忽略的坑。很多同学上来就想写爬虫去抓旅游网站,结果遇到反爬限制,又怕惹麻烦。我的建议是三路并行。第一路是公开数据集。一些开源社区有现成的旅游景点数据,虽然可能老一点,但做毕设完全够用,可以在基础上补充字段。第二路是手动采集。挑40到60个周边及知名景点,手工录入基本信息。别嫌慢,数据质量和数量直接决定推荐效果能不能“看得出来”。第三路是爬虫补充。如果确实要爬,务必控制频率、只抓公开基础信息、不涉及用户隐私数据,本地只做学习和研究用途。一个我踩过的坑是数据字段不统一。比如有的景点开放时间写“全天”,有的写“夏季08:00”,有的写“05:00开门”。如果直接用字符串解析,程序里全是if-else。所以录入数据前就要定好规范:统一为“HH:MM-HH:MM”格式,全天就写“00:00-24:00”。这个细节写进论文的数据预处理章节,反而是亮点。2.4 初始化流程与依赖管理工程初始化这部分,直接给你一套可以执行的命令。先建虚拟环境,安装依赖:python -m venv venv source venv/bin/activate pip install django djangorestframework pandas requests pip freeze > requirements.txt然后创建项目和应用:django-admin startproject config . python manage.py startapp users python manage.py startapp spots python manage.py startapp recommend python manage.py startapp analysis python manage.py startapp llm这里有个小技巧:给app取名偏向功能名而不是通用名,比如用recommend而不是tools。论文写“推荐模块设计与实现”,代码目录正好对应,导师看到的截图也更规范。在settings.py里注册app、配置数据库连接、配置静态文件目录。然后依次执行:python manage.py makemigrations python manage.py migrate python manage.py createsuperuser依赖管理多说一句:不要用全局Python环境装包,很容易出现版本冲突。requirements.txt要在项目中期就固定,不要拖到答辩前再生成,否则可能因为依赖版本变化导致跑不起来。3. 核心算法:个性化推荐、路线规划与LLM落地3.1 协同过滤从零实现与冷启动破解毕设里推荐算法直接调surprise库还是自己手写?都有道理。surprise库是现成的,效果规范,但答辩时评委追问“相似度公式怎么写的”,你只答“调库”,印象分会打折扣。我建议自己在系统里写一个简化版协同过滤,既展示算法理解,代码量也不大。核心分两步:第一步算用户相似度,第二步加权评分预测。用户相似度常用皮尔逊相关系数:import math def pearson(user1_ratings, user2_ratings, common_items): n = len(common_items) if n == 0: return 0.0 sum1 = sum(user1_ratings[i] for i in common_items) sum2 = sum(user2_ratings[i] for i in common_items) sum1_sq = sum(user1_ratings[i] ** 2 for i in common_items) sum2_sq = sum(user2_ratings[i] ** 2 for i in common_items) p_sum = sum(user1_ratings[i] * user2_ratings[i] for i in common_items) num = p_sum - (sum1 * sum2 / n) den = math.sqrt((sum1_sq - sum1 ** 2 / n) * (sum2_sq - sum2 ** 2 / n)) if den == 0: return 0.0 return num / den注意这里要先取共同评分项再计算,否则分母可能出现负数无意义。皮尔逊相似度的取值范围在[-1, 1],使用时一般只保留正相关的邻居。预测用户对未评分景点的分数,用加权平均:# neighbors是[(用户ID, 相似度), ...],ratings_map是邻居对该景点的评分 def predict_rating(user_id, spot_id, neighbors, user_ratings, sim_matrix): total_sim = 0.0 total_score = 0.0 for neighbor_id, sim in neighbors: r = user_ratings.get(neighbor_id, {}).get(spot_id) if r is None or sim <= 0: continue total_sim += sim total_score += sim * r return total_score / total_sim if total_sim > 0 else 0.0冷启动几乎必被问。新注册用户一条行为数据都没有,协同过滤直接失效。解决思路是模块化兜底:新用户注册时做一份“偏好问卷”,让用户选择感兴趣的景点类型,按照标签匹配和热门度生成初始推荐;用户积累了几条行为后再切换到协同过滤。这样既保证用户体验,又能在答辩中展示你考虑了实际场景。3.2 路线规划:最近邻贪心加2-opt局部优化路线规划的目标,是把推荐出来的候选景点排列成时间上合理的线路。如果只有三五个景点,全排列也能算;但有几十个候选时,就必须用近似算法。我的方案是:最近邻贪心生成初始解,再用2-opt做局部优化。先定义约束条件:景区开放时间、每个景点的建议游玩时长、景点之间的交通时间、每天游玩总时长上限、是否需要午餐时间。这些约束在生成时要逐项检查。贪心阶段伪代码:def greedy_route(candidate_spots, current_location, open_time, day_hours, travel_time_fn): route = [] time_left = day_hours current = current_location unvisited = candidate_spots[:] while unvisited and time_left > 0: # 选最近且能放进时间段的未访问景点 best = min(unvisited, key=lambda s: travel_time_fn(current, s) + s.duration) if feasible(best, current, time_left): route.append(best) current = best time_left -= (travel_time_fn(current, best) + best.duration) unvisited.remove(best) return route贪心解容易绕路,所以接一个2-opt优化。2-opt核心思想是:如果路径中两条边交叉,交换节点顺序能缩短总长度,就交换。简单实现:def two_opt(route, travel_time_fn): improved = True while improved: improved = False for i in range(1, len(route) - 1): for k in range(i + 1, len(route)): new_route = route[:i] + route[i:k+1][::-1] + route[k+1:] if total_time(new_route) < total_time(route): route = new_route improved = True return route2-opt的复杂度是O(n²)每轮,对毕设数据量完全够用。论文里可以写:本系统采用贪心构造初始解再用2-opt优化的策略,相较于动态规划,在大规模数据下有效控制了计算时间。这里有个容易踩坑的点:距离计算用的经纬度要转成实际距离,不能直接用经纬度差值。用Haversine公式:import math def haversine_km(lng1, lat1, lng2, lat2): r = 6371.0 p1, p2 = math.radians(lat1), math.radians(lat2) dp = math.radians(lat2 - lat1) dl = math.radians(lng2 - lng1) a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2 return 2 * r * math.asin(math.sqrt(a))如果你演示时直接输入坐标差,算出来的交通时间会明显偏短,评委一眼就能看出不对。这个细节在论文测试章节写出来,反而是加分项。3.3 LLM接入:提示词设计、结构化输出与降级方案LLM模块是题目的最大亮点,要让它真正发挥作用而不是摆设。我做的方案分三步。第一步选接入方式。可以是商用大模型API,也可以本地部署开源模型。毕设更推荐商用API,因为不需要本地显卡、集成速度快、效果稳定。但如果答辩现场没有网络或者网络不稳,就必须有降级方案。这是整个模块最关键的工程决策。第二步设计提示词。大模型的主要任务是给路线生成“人话”,不是做路线计算。典型提示词结构包含角色设定、任务描述、输入数据、输出格式、约束条件。例子:你是资深旅游规划专家。下面给出一个用户画像和一份候选景点列表。 请根据用户偏好,生成一份三日行程说明。 要求: 1. 每天选取2-4个景点; 2. 按游玩顺序列出,标注推荐时间段; 3. 每个景点用50字左右说明推荐理由; 4. 输出JSON数组,字段包括day、spot_name、time_range、reason。 景点数据:{json.dumps(spots, ensure_ascii=False)}关键是把输出格式限定为JSON,方便程序解析。调用时设置timeout,做好异常捕获。第三步写降级逻辑。模型服务不可用时,系统自动退回到规则引擎:用评分热门度排序加贪心算法生成路线,描述文本用模板字符串拼接。这样系统断网也能完整运行。演示时万一断网,你还可以主动演示“降级模式下的运行效果”,反而变成加分项。解析模型输出时也要容错。模型偶尔会输出多余文字,或者把JSON包裹在markdown代码块里。我用正则先提取JSON块再json.loads:import re, json def parse_llm_json(text): pattern = r"```json\s*(.*?)\s*```" match = re.search(pattern, text, re.S) if match: text = match.group(1) return json.loads(text)3.4 推荐、规划、LLM三者怎么串联这三个模块不能各自独立,而是要串成一条完整的用户故事。完整流程如下:用户在路线推荐页点击“智能规划”,前端带着userId和可选的自然语言需求请求后端。后端先判断用户行为数据量:行为丰富就走协同过滤推荐候选景点;新用户就走偏好标签加热门度兜底。路线规划服务拿到候选景点后,用贪心和2-opt生成初步路线。最后LLM服务把路线和用户画像一起塞进提示词,生成带推荐理由的行程说明。整条链路返回一个JSON:day、spots、route、reasoning。接口设计上,我建议用Django REST framework写三个视图:推荐接口、规划接口、生成行程接口。其中生成行程接口内部依赖前两个服务。代码这样组织,论文可以分开讲,代码里又能看到服务间的协作关系。答辩时,评委最喜欢问的“各模块之间怎么衔接”也能借助这条链路讲清楚。4. 数据分析与可视化大屏的设计4.1 数据分析维度怎么定数据分析模块的价值不是“有图表”,而是“能回答问题”。评委看大屏时,脑子里会有几个问题:这个系统积累了哪些数据?这些数据能反映什么规律?我设计了四个回答维度。景点热度排行。用评分人数、平均评分、收藏次数三个因子加权,算出一个热度分。例如热度分 = 0.4 * 平均评分 + 0.3 * 评分人数归一化 + 0.3 * 收藏次数归一化。这个排行可以直接指导新用户的冷启动推荐。用户画像分布。统计用户注册时的年龄段、职业、偏好标签,画饼图和柱状图。这个维度直接回应“个性化推荐是否有基础”。访问与收藏趋势。按时间维度统计每天的新增用户、收藏量、路线规划次数,画折线图。趋势图能体现系统活跃度,也是数据分析里最直观的展示。景点分类占比。不同类型景点的数量、平均评分、平均门票价格对比。这个维度可以进一步挖掘“哪些类型更受欢迎”“门票价格是否影响评分”。每个维度在后端写一个统计接口,返回聚合后的JSON,前端用ECharts渲染。统计口径要在接口注释里写明,答辩被追问时可以快速说清楚。4.2 后端统计接口与ECharts大屏的配合统计接口用Django ORM实现比较方便。比如热度排行:from django.db.models import Avg, Count def spot_hot_rank(request): qs = (Spot.objects .annotate(avg_rating=Avg('rating__score'), rating_count=Count('rating'), collect_count=Count('collect')) .order_by('-avg_rating')[:10]) data = [{ 'name': s.name, 'avg_rating': round(s.avg_rating or 0, 2), 'rating_count': s.rating_count, 'collect_count': s.collect_count, 'hot_score': round((s.avg_rating or 0) * 0.4 + min(s.rating_count / 50, 1) * 30 + min(s.collect_count / 20, 1) * 30, 2) } for s in qs] return JsonResponse({'data': data})前端的做法是页面加载时fetch接口,拿到的JSON直接塞进ECharts。比如折线图配置大致是这样:fetch('/api/analysis/trend/') .then(res => res.json()) .then(res => { chart.setOption({ xAxis: { data: res.data.map(d => d.date) }, yAxis: {}, series: [{ type: 'line', data: res.data.map(d => d.count) }] }); });整个大屏用flex布局把多个图表卡片排开,背景用深色,更有“数据大屏”的感觉。实测下来,ECharts官方示例的深色主题稍微改改就够用,不需要自己造轮子。4.3 性能优化与数据量级处理毕设数据量一般不大,但接口响应也可能因N+1查询变慢。典型问题是循环里查数据库。比如热度排行里,如果对每个景点再查一次评分详情,几十个景点就要几十次查询。解决方法是像上面代码一样,用annotate把聚合查询一次性算完。场景更复杂时,再用select_related和prefetch_related减少查询次数。数据分析如果用Pandas处理,要注意DataFrame转JSON时的中文编码问题。我用to_json(orient='records', force_ascii=False)就能避开中文变成\uXXXX的情况。缓存也是提升接口响应速度的简单手段。统计结果一小时内的变化不大,给接口加Django cache_page装饰器,设置timeout为3600秒,压力测试数据能明显好看一些。答辩展示时,页面秒开比一堆loading强太多。5. 毕设文档、答辩演示与避坑记录5.1 论文结构怎么组织,哪些图表能加分论文结构建议按标准软件工程流程写:绪论、相关技术、需求分析、系统设计、系统实现、系统测试、总结与展望。这部分容易流水账,想拿高分要注意三个地方。相关技术章节不要大段抄百科。你用了协同过滤、最近邻、2-opt、大模型提示词工程,每一项都要结合本项目说明“为什么用它”。比如写2-opt时,不只写算法来源,还要写“由于景点数量增加后全排列不可行,采用贪心加局部搜索策略在可接受时间内获得近似最优路线”。系统设计章节把架构图、数据库ER图、接口设计表、算法流程图都画出来。论文里的图要跟实际代码完全对应,别画一套做一套。我见过太多论文架构图上写了Redis,代码里根本没有,答辩时底气立刻没了。系统测试章节要有真实测试过程。除了单元测试,建议记录算法对比数据。比如协同过滤与热门榜推荐在同一测试集上的准确率差异、2-opt优化前后路线总时长对比。这些数字是论文里最有说服力的部分,也是评委最喜欢的实验数据。写论文时间至少留出两周。我的个人建议是:开发到中期就开始写需求分析和设计章节,边写边对照代码,等代码稳定后测试数据也积累好了,再写实现和测试,效率最高。5.2 答辩演示脚本怎么准备答辩中最怕的不是功能少,而是演示时手忙脚乱。我建议准备一条三分钟的主演示流程和两条备选路线。主演示流程:登录系统,进入一个提前准备的行为数据充足的账号。先打开数据分析大屏,用30秒展示热度排行和用户画像;再进入推荐页面,点击“智能路线规划”,展示系统返回的路线JSON和LLM生成的行程说明;最后展示管理后台,添加一个新景点,并刷新前台景点列表。每一步都对应论文的一章,讲起来逻辑清楚。备选路线一:断网降级演示。如果答辩环境网络不稳定,提前把降级模式触发方式准备好,展示规则引擎生成的路线,并强调“系统设计时考虑了大模型服务不可用的降级策略,这正是生产环境的工程思维”。备选路线二:冷启动演示。用新注册账号登录,展示偏好问卷入口,选完标签后推荐结果立即出现。这个场景能直接回应用户没有行为数据时系统如何工作的问题。演示前至少完整走三遍流程,把容易卡壳的地方提前规避。特别提醒:模型API请求一定要在答辩现场先测一次,很多商用API有每分钟调用次数限制,如果前面有人用光额度,你这边就会超时。5.3 常见问题与排查速查表我把实际操作里容易踩的坑整理成一张表,遇到问题先查一遍。现象可能原因处理方案推荐结果与热门榜几乎一样行为数据太少,协同过滤失效增加模拟评分数据;调低热门兜底权重路线景点重复出现未维护已选状态集合规划前拷贝候选列表,访问后移除交通时间明显异常短直接用经纬度差值当距离改用Haversine公式换算公里大模型返回内容无法解析输出带markdown或多余文字正则提取JSON后解析;检查提示词格式中文在返回JSON里变成\uXXXX响应未指定字符集设置JsonResponse的json_dumps_params为ensure_ascii=False大屏图表不显示前端字段名与后端不一致打印接口返回逐字段核对管理后台无法上传图片未配置MEDIA_URL和MEDIA_ROOT在settings.py配置并迁移静态文件还有一个特别值得注意的问题,就是数据库清空重导。开发过程中频繁改模型字段,会导致迁移文件混乱。我的办法是中期用Django自带的dumpdata备份,改完模型重建库再loaddata,数据恢复快,不会因为迁移报错卡住半天。最后说点个人体会。这个题目最出彩的地方不在于LLM技术有多前沿,而在于几个模块能紧密协作,形成一条完整的用户故事。写代码时我把大部分时间花在数据清洗和接口调试上,算法反而比想象中顺利。所以如果你想做类似项目,数据基本功一定要重视。另一个经验是,大模型模块一定要留降级开关,不要让它成为系统的单点。答辩时如果能在评委开口前主动讲出这个设计考量,效果会非常好。希望这些拆解能帮准备做这个方向的同学,把毕业设计做得既有章法又有亮点。
第一次看到这个题目,我的第一反应是这学生选题眼光不错。Django + LLM大模型把后端开发和最前沿的智能生成能力结合起来,再加上数据分析、个性化推荐,正好覆盖了毕业设计里最容易出彩的三个方向:系统完整、算法有深度、展示有亮点。这不是那种只做登录注册加增删改查的“管理系统”,而是一个能从数据采集一路做到智能推荐、最后用可视化大屏讲出完整故事的旅游路线推荐系统。这篇文章,我会从实际动手做过类似系统的角度,把需求拆解、架构设计、算法实现、答辩准备的思路完整讲一遍。这个选题适合谁参考?如果你正打算拿它作为毕业设计题目,或者想在这个基础上改成其他领域的路线规划(比如餐饮打卡路线、学习路径规划),下面的拆解思路都通用。我会尽量把每一步的“为什么”讲清楚,关键代码片段给到,容易踩的坑也提前标出来。1. 选题价值与需求拆解,为什么这套架构能撑起一篇毕设1.1 从标题拆解出的真实功能边界一个标题里通常藏着三个技术关键词:Django + LLM 智能路线规划、数据分析、个性化推荐。落到实际系统,至少要拆出四条主线,缺一条都撑不起“系统”这两个字。用户端。注册登录、个人中心、景点库浏览、按关键词和分类筛选、景点详情、收藏和评分。别小看这些基础功能,它们是后续推荐算法的数据来源。评分记录和收藏记录就是协同过滤的输入,没有这些行为数据,算法再漂亮也是空转。智能推荐与规划端。这是整篇论文的核心。推荐端负责从用户历史行为中推断偏好,产出候选景点;规划端负责把这些候选景点按照地理位置、游玩时长、开放时间组装成一条合理路线。LLM在这里不是主角,而是增强模块——它用来生成路线的解说文案、推荐理由、天气穿衣建议等自然语言内容,提升系统的“智能感”。这里的分工要想清楚:算法负责“算得准”,大模型负责“说得好”。数据分析端。对景点热度、用户画像、访问趋势做统计,再以可视化图表展示。这部分答辩评委特别爱看,因为能直观体现数据处理能力,也容易做出视觉效果。管理后台端。用Django自带的Admin可以快速搭建,用来管理景点、用户、收藏、路线数据。演示时也可以从这里快速造数据,方便展示不同场景。功能边界上,我的建议是不要贪多。很多同学看到这个题目就想着把所有功能都做一遍,结果每个模块都是半成品。更好的策略是:把用户端、推荐端、规划端三个核心闭环做扎实,管理后台用Admin自带能力加少量自定义页面,数据分析做一张大屏。这样工作量可控,论文也有清晰的主次关系。1.2 技术栈对比:Django为什么比Flask和Spring Boot更适合这个题目用Django几乎是最优解,我从三个维度说理由。第一,Django自带Admin后台和用户认证。毕业设计最耗时间的从来不是算法,而是各种管理页面。Django的Admin只需要注册模型,就能获得一个可操作的、界面干净的后台。论文里截图、演示时展示数据,都足够用。如果换成Flask,这些都要自己写,多出来的时间比算法本身还多。第二,Django的ORM对数据分析友好。后面要做景点热度排行、用户年龄分布这类统计,直接用ORM的aggregate、annotate就能完成,不需要再额外写一堆原生SQL。如果用Pandas做更复杂的分析,Django也提供了把查询集数据转DataFrame的成熟路径。第三,Django的MTV分层结构特别适合画架构图。答辩评委看论文,第一眼就是技术架构图。MTV分层思路和最终论文的章节逻辑基本一一对应,画图省事,讲起来也顺。我也做过Flask项目,轻量是真轻量,但项目一旦涉及多模块、后台管理、权限控制,需要手动接的东西太多。Spring Boot能力最强,但学习成本高,一套Java写的毕设如果时间分配不好,很可能卡在环境配置上。所以毕设场景下,Django是开发效率、学习曲线、答辩表现三者最均衡的选择。方案后台与认证算法落地速度可视化配合答辩友好度Django + LLM API自带Admin,最快高ECharts直接接API高Flask + 规则引擎需手写,较慢中可行低,缺乏智能性Spring Boot配置复杂中可行中,但时间风险大1.3 哪些模块最容易被评委追问,提前想好答案做过答辩的人都知道,评委喜欢盯着标了“智能”“推荐”的模块反复问。这个系统里有三个问题,务必提前准备。推荐算法为什么选协同过滤而不是深度学习?因为毕设数据量不大,行为数据稀疏,深度学习容易过拟合,而且很难解释。协同过滤简单、可解释性强,配合标签相似度做混合推荐,讲起来更有底气。路线规划算法是精确解还是近似解?景点数量增加后,精确组合是NP难问题,所以一般用最近邻贪心加局部搜索(比如2-opt)求近似解。要能说出复杂度:贪心阶段O(n²),2-opt每轮O(n²)。这个回答在答辩时很加分。LLM在系统里是必需还是可选?诚实的说法是:增强体验、生成自然语言,核心路线规划不依赖它。这样解释既展示工程判断,又避免被追问“如果大模型挂了系统还能不能用”时陷入被动。2. 系统架构、数据建模与工程初始化2.1 整体架构与代码目录怎么组织先讲整体数据流。用户打开浏览器进入路线推荐页,填写偏好或者直接点击“为我规划”,前端把请求发给Django视图;视图层不直接写业务逻辑,而是调用Service层;Service层里,推荐服务取用户历史评分计算相似用户,规划服务调用推荐结果和景点库生成路线,大模型服务负责生成描述文本;最后视图组装JSON返回前端。数据存储全部由Django ORM操作。目录上我建议不要把所有代码堆在一个app里,模块拆开便于论文分章节叙述。举例:travel_recommend/ ├── manage.py ├── config/ # 项目配置 ├── apps/ │ ├── users/ # 注册登录、用户画像 │ ├── spots/ # 景点管理、搜索 │ ├── recommend/ # 推荐算法与路线规划 │ ├── analysis/ # 数据分析接口 │ └── llm/ # 大模型封装 └── templates/static/这样每个app的名字本身就是论文里的一章标题,写作时思路不会乱。前端部分,不用上太重的框架。Bootstrap加JQuery,或者原生HTML加ECharts,就能完成演示效果。如果想提升一点层次,可以用Vue的CDN模式,但没必要引入构建工具链,否则工程复杂度反而变成负担。毕设的核心是系统逻辑完整,不是前端工程化。2.2 核心数据表:用户、景点、评分、路线数据库设计决定了后面算法实现顺不顺畅,按核心程度排序如下。用户表扩展。Django自带User模型,但推荐系统需要用户画像,所以通常新建Profile表和User一对一关联,字段包括年龄段、职业、偏好标签。偏好标签用多对多关联到景点类型表,这样新用户冷启动时可以直接按标签匹配。景点表。字段尽量完整,除了名称、简介、图片,至少要有经度、纬度、门票价格、开放时间、建议游玩时长、评分、热度、景点类型。经纬度用DecimalField存,精度设为小数点后6位,方便计算地理距离。开放时间建议用字符串存“08:00-17:30”,解析时拆开。评分表。一张表同时记录评分和评论,外键关联用户和景点,字段包括评分、评论内容、创建时间。要建联合索引(user_id, spot_id),后面算相似矩阵时查询快。这张表是推荐算法的燃料,纯手工录入会很痛苦,我用管理后台模拟脚本批量生成了一组行为数据,保证每个用户至少有十几次评分记录。收藏表可以单独建,也可以和评分合并,我建议单独建。因为“收藏”这个动作比“评分”轻量,是另一个维度的正反馈,数据分析时也可以作为热度权重。路线表。存储规划结果:用户、路线名称、路线内容(JSON格式,包含每天的景点顺序和时间安排)、创建时间。用Django的JSONField存,读取方便,论文里展示时也好解释。2.3 景点数据从哪里来,三条路径怎么搭配景点数据是系统的基础,但也是最容易被忽略的坑。很多同学上来就想写爬虫去抓旅游网站,结果遇到反爬限制,又怕惹麻烦。我的建议是三路并行。第一路是公开数据集。一些开源社区有现成的旅游景点数据,虽然可能老一点,但做毕设完全够用,可以在基础上补充字段。第二路是手动采集。挑40到60个周边及知名景点,手工录入基本信息。别嫌慢,数据质量和数量直接决定推荐效果能不能“看得出来”。第三路是爬虫补充。如果确实要爬,务必控制频率、只抓公开基础信息、不涉及用户隐私数据,本地只做学习和研究用途。一个我踩过的坑是数据字段不统一。比如有的景点开放时间写“全天”,有的写“夏季08:00”,有的写“05:00开门”。如果直接用字符串解析,程序里全是if-else。所以录入数据前就要定好规范:统一为“HH:MM-HH:MM”格式,全天就写“00:00-24:00”。这个细节写进论文的数据预处理章节,反而是亮点。2.4 初始化流程与依赖管理工程初始化这部分,直接给你一套可以执行的命令。先建虚拟环境,安装依赖:python -m venv venv source venv/bin/activate pip install django djangorestframework pandas requests pip freeze > requirements.txt然后创建项目和应用:django-admin startproject config . python manage.py startapp users python manage.py startapp spots python manage.py startapp recommend python manage.py startapp analysis python manage.py startapp llm这里有个小技巧:给app取名偏向功能名而不是通用名,比如用recommend而不是tools。论文写“推荐模块设计与实现”,代码目录正好对应,导师看到的截图也更规范。在settings.py里注册app、配置数据库连接、配置静态文件目录。然后依次执行:python manage.py makemigrations python manage.py migrate python manage.py createsuperuser依赖管理多说一句:不要用全局Python环境装包,很容易出现版本冲突。requirements.txt要在项目中期就固定,不要拖到答辩前再生成,否则可能因为依赖版本变化导致跑不起来。3. 核心算法:个性化推荐、路线规划与LLM落地3.1 协同过滤从零实现与冷启动破解毕设里推荐算法直接调surprise库还是自己手写?都有道理。surprise库是现成的,效果规范,但答辩时评委追问“相似度公式怎么写的”,你只答“调库”,印象分会打折扣。我建议自己在系统里写一个简化版协同过滤,既展示算法理解,代码量也不大。核心分两步:第一步算用户相似度,第二步加权评分预测。用户相似度常用皮尔逊相关系数:import math def pearson(user1_ratings, user2_ratings, common_items): n = len(common_items) if n == 0: return 0.0 sum1 = sum(user1_ratings[i] for i in common_items) sum2 = sum(user2_ratings[i] for i in common_items) sum1_sq = sum(user1_ratings[i] ** 2 for i in common_items) sum2_sq = sum(user2_ratings[i] ** 2 for i in common_items) p_sum = sum(user1_ratings[i] * user2_ratings[i] for i in common_items) num = p_sum - (sum1 * sum2 / n) den = math.sqrt((sum1_sq - sum1 ** 2 / n) * (sum2_sq - sum2 ** 2 / n)) if den == 0: return 0.0 return num / den注意这里要先取共同评分项再计算,否则分母可能出现负数无意义。皮尔逊相似度的取值范围在[-1, 1],使用时一般只保留正相关的邻居。预测用户对未评分景点的分数,用加权平均:# neighbors是[(用户ID, 相似度), ...],ratings_map是邻居对该景点的评分 def predict_rating(user_id, spot_id, neighbors, user_ratings, sim_matrix): total_sim = 0.0 total_score = 0.0 for neighbor_id, sim in neighbors: r = user_ratings.get(neighbor_id, {}).get(spot_id) if r is None or sim <= 0: continue total_sim += sim total_score += sim * r return total_score / total_sim if total_sim > 0 else 0.0冷启动几乎必被问。新注册用户一条行为数据都没有,协同过滤直接失效。解决思路是模块化兜底:新用户注册时做一份“偏好问卷”,让用户选择感兴趣的景点类型,按照标签匹配和热门度生成初始推荐;用户积累了几条行为后再切换到协同过滤。这样既保证用户体验,又能在答辩中展示你考虑了实际场景。3.2 路线规划:最近邻贪心加2-opt局部优化路线规划的目标,是把推荐出来的候选景点排列成时间上合理的线路。如果只有三五个景点,全排列也能算;但有几十个候选时,就必须用近似算法。我的方案是:最近邻贪心生成初始解,再用2-opt做局部优化。先定义约束条件:景区开放时间、每个景点的建议游玩时长、景点之间的交通时间、每天游玩总时长上限、是否需要午餐时间。这些约束在生成时要逐项检查。贪心阶段伪代码:def greedy_route(candidate_spots, current_location, open_time, day_hours, travel_time_fn): route = [] time_left = day_hours current = current_location unvisited = candidate_spots[:] while unvisited and time_left > 0: # 选最近且能放进时间段的未访问景点 best = min(unvisited, key=lambda s: travel_time_fn(current, s) + s.duration) if feasible(best, current, time_left): route.append(best) current = best time_left -= (travel_time_fn(current, best) + best.duration) unvisited.remove(best) return route贪心解容易绕路,所以接一个2-opt优化。2-opt核心思想是:如果路径中两条边交叉,交换节点顺序能缩短总长度,就交换。简单实现:def two_opt(route, travel_time_fn): improved = True while improved: improved = False for i in range(1, len(route) - 1): for k in range(i + 1, len(route)): new_route = route[:i] + route[i:k+1][::-1] + route[k+1:] if total_time(new_route) < total_time(route): route = new_route improved = True return route2-opt的复杂度是O(n²)每轮,对毕设数据量完全够用。论文里可以写:本系统采用贪心构造初始解再用2-opt优化的策略,相较于动态规划,在大规模数据下有效控制了计算时间。这里有个容易踩坑的点:距离计算用的经纬度要转成实际距离,不能直接用经纬度差值。用Haversine公式:import math def haversine_km(lng1, lat1, lng2, lat2): r = 6371.0 p1, p2 = math.radians(lat1), math.radians(lat2) dp = math.radians(lat2 - lat1) dl = math.radians(lng2 - lng1) a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2 return 2 * r * math.asin(math.sqrt(a))如果你演示时直接输入坐标差,算出来的交通时间会明显偏短,评委一眼就能看出不对。这个细节在论文测试章节写出来,反而是加分项。3.3 LLM接入:提示词设计、结构化输出与降级方案LLM模块是题目的最大亮点,要让它真正发挥作用而不是摆设。我做的方案分三步。第一步选接入方式。可以是商用大模型API,也可以本地部署开源模型。毕设更推荐商用API,因为不需要本地显卡、集成速度快、效果稳定。但如果答辩现场没有网络或者网络不稳,就必须有降级方案。这是整个模块最关键的工程决策。第二步设计提示词。大模型的主要任务是给路线生成“人话”,不是做路线计算。典型提示词结构包含角色设定、任务描述、输入数据、输出格式、约束条件。例子:你是资深旅游规划专家。下面给出一个用户画像和一份候选景点列表。 请根据用户偏好,生成一份三日行程说明。 要求: 1. 每天选取2-4个景点; 2. 按游玩顺序列出,标注推荐时间段; 3. 每个景点用50字左右说明推荐理由; 4. 输出JSON数组,字段包括day、spot_name、time_range、reason。 景点数据:{json.dumps(spots, ensure_ascii=False)}关键是把输出格式限定为JSON,方便程序解析。调用时设置timeout,做好异常捕获。第三步写降级逻辑。模型服务不可用时,系统自动退回到规则引擎:用评分热门度排序加贪心算法生成路线,描述文本用模板字符串拼接。这样系统断网也能完整运行。演示时万一断网,你还可以主动演示“降级模式下的运行效果”,反而变成加分项。解析模型输出时也要容错。模型偶尔会输出多余文字,或者把JSON包裹在markdown代码块里。我用正则先提取JSON块再json.loads:import re, json def parse_llm_json(text): pattern = r"```json\s*(.*?)\s*```" match = re.search(pattern, text, re.S) if match: text = match.group(1) return json.loads(text)3.4 推荐、规划、LLM三者怎么串联这三个模块不能各自独立,而是要串成一条完整的用户故事。完整流程如下:用户在路线推荐页点击“智能规划”,前端带着userId和可选的自然语言需求请求后端。后端先判断用户行为数据量:行为丰富就走协同过滤推荐候选景点;新用户就走偏好标签加热门度兜底。路线规划服务拿到候选景点后,用贪心和2-opt生成初步路线。最后LLM服务把路线和用户画像一起塞进提示词,生成带推荐理由的行程说明。整条链路返回一个JSON:day、spots、route、reasoning。接口设计上,我建议用Django REST framework写三个视图:推荐接口、规划接口、生成行程接口。其中生成行程接口内部依赖前两个服务。代码这样组织,论文可以分开讲,代码里又能看到服务间的协作关系。答辩时,评委最喜欢问的“各模块之间怎么衔接”也能借助这条链路讲清楚。4. 数据分析与可视化大屏的设计4.1 数据分析维度怎么定数据分析模块的价值不是“有图表”,而是“能回答问题”。评委看大屏时,脑子里会有几个问题:这个系统积累了哪些数据?这些数据能反映什么规律?我设计了四个回答维度。景点热度排行。用评分人数、平均评分、收藏次数三个因子加权,算出一个热度分。例如热度分 = 0.4 * 平均评分 + 0.3 * 评分人数归一化 + 0.3 * 收藏次数归一化。这个排行可以直接指导新用户的冷启动推荐。用户画像分布。统计用户注册时的年龄段、职业、偏好标签,画饼图和柱状图。这个维度直接回应“个性化推荐是否有基础”。访问与收藏趋势。按时间维度统计每天的新增用户、收藏量、路线规划次数,画折线图。趋势图能体现系统活跃度,也是数据分析里最直观的展示。景点分类占比。不同类型景点的数量、平均评分、平均门票价格对比。这个维度可以进一步挖掘“哪些类型更受欢迎”“门票价格是否影响评分”。每个维度在后端写一个统计接口,返回聚合后的JSON,前端用ECharts渲染。统计口径要在接口注释里写明,答辩被追问时可以快速说清楚。4.2 后端统计接口与ECharts大屏的配合统计接口用Django ORM实现比较方便。比如热度排行:from django.db.models import Avg, Count def spot_hot_rank(request): qs = (Spot.objects .annotate(avg_rating=Avg('rating__score'), rating_count=Count('rating'), collect_count=Count('collect')) .order_by('-avg_rating')[:10]) data = [{ 'name': s.name, 'avg_rating': round(s.avg_rating or 0, 2), 'rating_count': s.rating_count, 'collect_count': s.collect_count, 'hot_score': round((s.avg_rating or 0) * 0.4 + min(s.rating_count / 50, 1) * 30 + min(s.collect_count / 20, 1) * 30, 2) } for s in qs] return JsonResponse({'data': data})前端的做法是页面加载时fetch接口,拿到的JSON直接塞进ECharts。比如折线图配置大致是这样:fetch('/api/analysis/trend/') .then(res => res.json()) .then(res => { chart.setOption({ xAxis: { data: res.data.map(d => d.date) }, yAxis: {}, series: [{ type: 'line', data: res.data.map(d => d.count) }] }); });整个大屏用flex布局把多个图表卡片排开,背景用深色,更有“数据大屏”的感觉。实测下来,ECharts官方示例的深色主题稍微改改就够用,不需要自己造轮子。4.3 性能优化与数据量级处理毕设数据量一般不大,但接口响应也可能因N+1查询变慢。典型问题是循环里查数据库。比如热度排行里,如果对每个景点再查一次评分详情,几十个景点就要几十次查询。解决方法是像上面代码一样,用annotate把聚合查询一次性算完。场景更复杂时,再用select_related和prefetch_related减少查询次数。数据分析如果用Pandas处理,要注意DataFrame转JSON时的中文编码问题。我用to_json(orient='records', force_ascii=False)就能避开中文变成\uXXXX的情况。缓存也是提升接口响应速度的简单手段。统计结果一小时内的变化不大,给接口加Django cache_page装饰器,设置timeout为3600秒,压力测试数据能明显好看一些。答辩展示时,页面秒开比一堆loading强太多。5. 毕设文档、答辩演示与避坑记录5.1 论文结构怎么组织,哪些图表能加分论文结构建议按标准软件工程流程写:绪论、相关技术、需求分析、系统设计、系统实现、系统测试、总结与展望。这部分容易流水账,想拿高分要注意三个地方。相关技术章节不要大段抄百科。你用了协同过滤、最近邻、2-opt、大模型提示词工程,每一项都要结合本项目说明“为什么用它”。比如写2-opt时,不只写算法来源,还要写“由于景点数量增加后全排列不可行,采用贪心加局部搜索策略在可接受时间内获得近似最优路线”。系统设计章节把架构图、数据库ER图、接口设计表、算法流程图都画出来。论文里的图要跟实际代码完全对应,别画一套做一套。我见过太多论文架构图上写了Redis,代码里根本没有,答辩时底气立刻没了。系统测试章节要有真实测试过程。除了单元测试,建议记录算法对比数据。比如协同过滤与热门榜推荐在同一测试集上的准确率差异、2-opt优化前后路线总时长对比。这些数字是论文里最有说服力的部分,也是评委最喜欢的实验数据。写论文时间至少留出两周。我的个人建议是:开发到中期就开始写需求分析和设计章节,边写边对照代码,等代码稳定后测试数据也积累好了,再写实现和测试,效率最高。5.2 答辩演示脚本怎么准备答辩中最怕的不是功能少,而是演示时手忙脚乱。我建议准备一条三分钟的主演示流程和两条备选路线。主演示流程:登录系统,进入一个提前准备的行为数据充足的账号。先打开数据分析大屏,用30秒展示热度排行和用户画像;再进入推荐页面,点击“智能路线规划”,展示系统返回的路线JSON和LLM生成的行程说明;最后展示管理后台,添加一个新景点,并刷新前台景点列表。每一步都对应论文的一章,讲起来逻辑清楚。备选路线一:断网降级演示。如果答辩环境网络不稳定,提前把降级模式触发方式准备好,展示规则引擎生成的路线,并强调“系统设计时考虑了大模型服务不可用的降级策略,这正是生产环境的工程思维”。备选路线二:冷启动演示。用新注册账号登录,展示偏好问卷入口,选完标签后推荐结果立即出现。这个场景能直接回应用户没有行为数据时系统如何工作的问题。演示前至少完整走三遍流程,把容易卡壳的地方提前规避。特别提醒:模型API请求一定要在答辩现场先测一次,很多商用API有每分钟调用次数限制,如果前面有人用光额度,你这边就会超时。5.3 常见问题与排查速查表我把实际操作里容易踩的坑整理成一张表,遇到问题先查一遍。现象可能原因处理方案推荐结果与热门榜几乎一样行为数据太少,协同过滤失效增加模拟评分数据;调低热门兜底权重路线景点重复出现未维护已选状态集合规划前拷贝候选列表,访问后移除交通时间明显异常短直接用经纬度差值当距离改用Haversine公式换算公里大模型返回内容无法解析输出带markdown或多余文字正则提取JSON后解析;检查提示词格式中文在返回JSON里变成\uXXXX响应未指定字符集设置JsonResponse的json_dumps_params为ensure_ascii=False大屏图表不显示前端字段名与后端不一致打印接口返回逐字段核对管理后台无法上传图片未配置MEDIA_URL和MEDIA_ROOT在settings.py配置并迁移静态文件还有一个特别值得注意的问题,就是数据库清空重导。开发过程中频繁改模型字段,会导致迁移文件混乱。我的办法是中期用Django自带的dumpdata备份,改完模型重建库再loaddata,数据恢复快,不会因为迁移报错卡住半天。最后说点个人体会。这个题目最出彩的地方不在于LLM技术有多前沿,而在于几个模块能紧密协作,形成一条完整的用户故事。写代码时我把大部分时间花在数据清洗和接口调试上,算法反而比想象中顺利。所以如果你想做类似项目,数据基本功一定要重视。另一个经验是,大模型模块一定要留降级开关,不要让它成为系统的单点。答辩时如果能在评委开口前主动讲出这个设计考量,效果会非常好。希望这些拆解能帮准备做这个方向的同学,把毕业设计做得既有章法又有亮点。