毕业设计这潭水,真的是每年都有人趟出不一样的坑。你手上要是正好拿到“基于django的Bilibili青少年模式使用情况的数据分析系统”这个题目,第一眼估计和我当年一样:标题开头写的是“Java计算机毕设”,结果后面技术栈却写着Django,这到底是让人用Java还是用Python?别慌,这种“看起来矛盾”的题目在高校毕设里反而是最常见的一种,因为题目库是很多年前统一定的,写“Java”只是沿用老的命名习惯,真正落地的时候你用什么B/S技术栈,答辩老师通常更看重“系统能不能跑、逻辑清不清楚、论文顶不顶得住”。
这个题目的完整形态其实很清晰:一个带用户端和管理后台的Web系统,外加一套围绕“青少年模式”的行为数据采集、指标分析和可视化看板,最后再配一本看起来像那么回事的说明文档和论文。适合谁做?正在选题或者已经拿到类似题目的本科生,以及想快速搞懂Django+Bootstrap+ECharts怎么串成一套完整毕设的人。下面我把整个系统的设计思路、核心代码、常见坑和答辩要点,按我自己实际做项目的路子完整过一遍。
1. 需求拆解与方案选型
1.1 标题里的“Java和Django混搭”怎么理解
先说最劝退人的一点:题目是“Java计算机毕设”,实现却是Django。我接触过的很多毕设题目其实都是这么个情况,学校下发题目的时候,要么是抄的前几年题目模板,要么是“Java”已经变成了“Web项目”的统称。真正做的时候,你完全可以用Python的Django来完成这个系统,因为题目核心研究对象是“青少年模式使用情况的数据分析”,这句话的落点明显偏数据分析和可视化,而Python生态在这个赛道上比Java顺手太多。
你要是答辩时被老师逮住问“题目不是Java吗,你怎么用Python”,答案也很现成:Django是基于Python的Web框架,本质上和Spring Boot一样是解决B/S架构问题的,系统层面对外呈现的是Web页面和接口,用户并不感知底层是什么语言;而且分析模块用Python的pandas、numpy做数据处理,比在Java里逐个字段写统计逻辑要快得多。说白了,毕设考的是工程思维和分析逻辑,不是编程语言信仰。
拒绝Java的另一个现实原因是时间。毕设周期一般也就一个学期,还要扣除实习、考研、写论文和改格式的时间,真正能稳定写代码的时间不到四周。Java那套SSM或Spring Boot光配置环境、写XML、打包部署就能吃掉一个周末,Django则是“pip安装+startapp”三分钟起项目,自带Admin后台还解决了一大半后台管理需求,这对毕设来说就是救命稻草。
1.2 功能模块划分与业务闭环
把题目拆开,你会发现这个系统要盘的不是“B站”本身,而是“青少年模式”。B站的青少年模式是产品的一个功能开关,开启后只展示适合未成年人的内容、限制使用时长,那么“使用情况的数据分析”自然就落在了这几个维度上:谁开了青少年模式、什么时候开什么时候关、开了之后在看什么东西、每天/每周累计用多长时间、系统拦截了多少不适合的内容请求。
由此,系统业务自然就分成四个模块:
- 用户与权限模块:注册登录、角色区分(普通用户、管理员)、青少年模式开关状态维护。
- 内容管理模块:模拟B站视频的标题、分区、标签,以及对每个视频做“是否适合青少年观看”的审核标记,这是青少年模式过滤的核心依据。
- 行为采集模块:记录用户每次播放、暂停、切换、被拦截的行为日志,以及青少年模式的每一次开启和关闭记录,这些日志就是分析的原料。
- 数据分析与可视化模块:围绕行为日志计算开启率、时段热力、内容偏好、平均使用时长、内容拦截率等指标,并用ECharts渲染成图表展示到前台。
这四个模块串起来就是一个完整的闭环:用户在页面上触发行为,后端记录日志,管理端维护内容分级,统计分析模块从日志里汇总指标,最后把结果以图表形式反馈出来。整个系统的灵魂在第四块,前面三块本质上是为它服务的“数据生产车间”。
1.3 Django与Java B/S方案的可比性参考
既然题目标题里有Java,那干脆把两边的方案放在一起做个对照,方便你决定怎么落笔写开题报告里的“技术选型”章节。
| 对比维度 | Spring Boot / SSM | Django |
|---|---|---|
| 环境搭建 | Maven/Gradle依赖管理,起步配置文件多 | pip安装,startproject一条命令,内置开发服务器 |
| 后台管理 | 需要自己写增删改查页面 | Admin站点自动生成,几分钟出一个管理后台 |
| ORM与数据库迁移 | MyBatis写SQL或JPA写实体类 | Django ORM + migrate迁移,改模型跑一次命令就同步 |
| 数据分析生态 | 手动遍历List写统计,Excel导出要引POI | ORM聚合一行代码出结果,pandas/Numpy随时接上 |
| 部署复杂度 | 打包war/jar,配Tomcat | python manage.py runserver就能演示,上线用gunicorn也简单 |
| 毕设工作量 | 中等偏重,很多时间花在配置和冗余代码上 | 轻很多,能把省下的时间砸在分析逻辑和论文上 |
这张表不是要踩Java抬Python,而是说在这个具体题目里,Django能把“数据采集-落库-聚合分析-可视化”的链路明显拉短,而这恰恰是评审最关注的地方。如果你不想用Python,坚持用Spring Boot做完全能行,但你要做好用Java重写聚合统计、还要自己撸一个后台UI的心理准备,时间上会紧很多。
2. 数据模型与核心功能实现
2.1 五张核心表的设计与字段落地
数据分析系统最怕的就是建模草率,后面写统计SQL时各种别扭。我这个项目里一共设计了五张核心表,基本都是围绕“用户-内容-行为”三件事转的。
| 表名 | 核心字段 | 作用 |
|---|---|---|
| UserAccount | username, password_hash, role, is_teen_mode, created_at | 用户身份和青少年模式状态 |
| VideoInfo | title, category, tags, is_teen_safe, play_count, video_url, cover_url | 模拟B站视频库,is_teen_safe是内容分级标记 |
| UserBehaviorLog | user_id, video_id, behavior_type, start_time, end_time, duration_seconds, is_teen_mode_on | 播放、暂停、切换、被拦截等行为流水 |
| TeenModeRecord | user_id, action, action_time, duration_minutes | 每次开启/关闭青少年模式的记录 |
| ContentAuditLog | video_id, audit_result, audit_reason, auditor_id, audit_time | 管理员对视频做审核标记的留痕 |
UserName表里的is_teen_mode字段是冗余状态,真正的分析依据是TeenModeRecord里的流水,这样好处是:展示当前状态时直接读字段,算“开启时长”“退出频率”时去流水表里聚合,互不干扰。VideoInfo表里的is_teen_safe是青少年模式过滤的第一层依据,ContentAuditLog记录的是审核动作本身,方便后面算“审核覆盖率”和“拦截率”。
有一个我实际踩过的细节:UserBehaviorLog的behavior_type不要用字符串随意填,最好用常量枚举,play、pause、switch、blocked四种。尤其是“blocked”这一条,代表青少年模式下用户试图访问不适合的内容且被系统拦截,后面算“拦截率”全靠它,你要是漏记这个行为,整个分析模块就少了一个关键指标。
2.2 Django工程搭建与基础配置要点
这个环节本来很机械,但总有人卡住,我把脚手架步骤列一下。首先创建项目和应用:
pip install django djangorestframework django-cors-headers channels django-admin startproject teen_project cd teen_project python manage.py startapp user_app python manage.py startapp video_app python manage.py startapp behavior_app python manage.py startapp analysis_app四个app分别对应四个模块,比全部写在一个app里强百倍。然后到settings.py里重点处理三件事。第一,INSTALLED_APPS里把rest_framework、corsheaders和四个app注册进去;第二,时区一定要这样配:
LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_TZ = True这个时区配置坑了我一下午,后面调试部分我会专门讲。第三,做前后端分离时,把跨域的配置加上:
INSTALLED_APPS += ['corsheaders'] MIDDLEWARE.insert(0, 'corsheaders.middleware.CorsMiddleware') CORS_ALLOW_CREDENTIALS = True CORS_ALLOWED_ORIGINS = ['http://localhost:5173', 'http://127.0.0.1:8000']初始化数据库就靠migrate一条命令,Django的数据表变化全部走makemigrations,这一步跑顺了后面改字段就不用慌。
2.3 青少年模式的核心逻辑落库
青少年模式不是简单一个布尔开关,它要能回答“用户什么时候处于受限状态”。我这里的做法是:把“开启/关闭”动作本身写入TeenModeRecord,同时维护UserAccount.is_teen_mode作为当前状态的快速读取。开启和关闭动作写一个视图函数就能处理:
from django.db import transaction from django.utils import timezone from teen_project.utils import get_now @transaction.atomic def set_teen_mode(request, action): user = request.user now = timezone.now() if action == 'enable' and not user.is_teen_mode: user.is_teen_mode = True user.save() TeenModeRecord.objects.create(user=user, action='enable', action_time=now) elif action == 'disable' and user.is_teen_mode: # 把本次开启的连续时长算出来再写入 last_enable = TeenModeRecord.objects.filter(user=user, action='enable').last() if last_enable: duration_minutes = round((now - last_enable.action_time).total_seconds() / 60, 2) TeenModeRecord.objects.filter(id=last_enable.id).update(duration_minutes=duration_minutes) user.is_teen_mode = False user.save() TeenModeRecord.objects.create(user=user, action='disable', action_time=now) return JsonResponse({'status': 'ok'})关键在disable分支里,关闭时要回填上一次开启的持续时长,这样后面的“平均单次开启时长”指标才有数据来源。用transaction.atomic包住是为了避免“状态改了但流水没记”或反过来,保证数据一致性。
内容拦截逻辑也很直白,用户请求播放视频时先判断是否处于青少年模式,是的话再查VideoInfo.is_teen_safe字段,为False就拒绝播放并写一条behavior_type='blocked'的日志。数据一致性问答里如果被问到“高并发下你怎么保证日志不丢”,我的回答就是“事务包裹+一次写入一条完整流水”,老师基本认可。
2.4 登录态与Token设置,Cookie怎么放
毕设系统一般不存在高并发,但不代表登录态可以随便写。前后端分离时比较省心的方案是DRF + SimpleJWT,接口返回access_token和refresh_token。但要说到Cookie设置,有几点特别容易翻车。
第一,token放哪儿。直接用localStorage存是最省事的,但一旦站点被人注了XSS脚本,token就直接被读走。稳妥做法是放到HttpOnly的Cookie里,这样JS读不到,靠浏览器自动附带。设置Cookie时的参数要带上:
response = JsonResponse({'status': 'ok'}) response.set_cookie( 'access_token', token, max_age=24 * 3600, httponly=True, samesite='Lax', )第二,samesite属性要注意。前后端如果不在同一个域,浏览器可能默认拦掉跨站的Cookie携带,你需要显式设Lax或者None并配合安全条件。调试时如果发现前端每次刷新后登录态就丢了,八成就是samesite或domain配的不对。
第三,Django自带的session认证在纯前后端分离场景下不太好用,因为DRF的token认证默认不读Cookie,需要自己扩展认证类。我实际是写了一个Authentication类,从request.COOKIES里拿access_token,然后去SimpleJWT校验,二十几行代码搞定,比套复杂的OAuth流程舒服得多。
3. 数据分析链路与可视化
3.1 行为数据从哪里来,合规路径要先想清楚
做数据分析最尴尬的一步就是“没数据”。不少人一上来就想着爬B站真实数据,结果反爬、风控、登录限制折腾了两周,数据没爬到几条,账号倒先受限了。对于一个毕设来说,最稳的路径是自产数据:管理端能在后台添加模拟视频并标注is_teen_safe,用户端就能在浏览器里正常播放、切换、触发拦截,这种行为流水每点一下都会真实落入UserBehaviorLog。拿着这套自产数据,指标链路可以完整跑通。
如果你想让数据规模显得更大,可以再加一个“批量导入”功能,管理端支持上传CSV或者JSON格式的模拟行为记录,在论文测试部分交代一句“为了验证统计模块在数据量较大时的表现,导入了一万条测试数据”,这比真实爬取安全且有说服力。答辩被问到“为什么不用真实B站数据”时,标准回应是:B站没有开放官方数据接口,自行抓取获取平台内容存在合规风险,因此本项目采用用户操作采集和模拟数据结合的方式验证分析指标的准确性,如需真实数据,需通过官方合作或授权途径获取。这个回答既专业又滴水不漏。
3.2 定义“使用情况”的五个核心指标口径
数据分析系统的价值全在指标定义上,指标口径不写清楚,后面图表画得再炫都没有意义。我这套系统里坚持只做五个核心指标,不多不少:
- 青少年模式开启率:开启过青少年模式的用户数 / 全部注册用户数,反映功能触达比例。
- 平均单次开启时长:TeenModeRecord里所有enable记录匹配到的duration_minutes之和 / enable记录条数,反映一次开启后能坚持多久。
- 日/周活跃使用时长:UserBehaviorLog里behavior_type为play的duration_seconds按天或按周汇总后除以对应人数,反映整体使用强度。
- 时段使用热力:把play行为的开始时间按小时分组统计次数,主要看下午放学后和晚上两个高峰。
- 内容拦截率:blocked行为次数 /(play行为次数 + blocked行为次数),反映内容分级策略的拦截强度。
这五个指标恰好对应论文里的“需求指标”和“性能指标”两块,答辩时你把这套口径讲清楚,老师会认为你是真的理解了这个数据分析系统要做什么,而不是只会把图表无脑堆到一起。
3.3 用Django ORM一条条把指标算出来
指标明确了,后端实现就变成了一道道SQL/ORM题。先说时段热力,Django一个TruncHour就能搞定:
from django.db.models.functions import TruncHour from django.db.models import Count hourly_data = ( UserBehaviorLog.objects .filter(behavior_type='play') .annotate(hour=TruncHour('start_time')) .values('hour') .annotate(total=Count('id')) .order_by('hour') )返回结果里hour字段可能是带时区的datetime对象,转成“14:00”这种字符串需要再做一步格式化。这里有一个我踩过的坑:如果你的start_time是DateTimeField且开启了USE_TZ,TruncHour切出来的hour会基于UTC而不是本地时间,很可能你下午两点的热度被算到了早上六点。解决办法有两种,要么查询之前用timezone.localtime把每个时间字段转好再计算,要么干脆让start_time改用DateField存日期、另外加一个hour字段存小时数,学乖之后我就用第二种,绕开时区烦恼。
接下来是“内容偏好TOP榜”,也就是青少年模式下大家最喜欢把时间花在哪个分区:
from django.db.models import Sum, F pref_result = ( UserBehaviorLog.objects .filter(is_teen_mode_on=True) .values('video__category') .annotate(total_seconds=Sum('duration_seconds')) .order_by('-total_seconds')[:10] )这里用到了video__category跨表查询,前提是UserBehaviorLog里的video字段是外键。要是你发现total_seconds数值翻倍或者统计不准,多半是join后数据重复了,可以用.annotate(Sum('duration_seconds', distinct=True))处理,或者干脆把video的category冗余一个字段到行为表里,分析起来更快。我在项目里选择了冗余方案,冗余字段让统计分析少了很多join,性能也更好。
3.4 ECharts渲染看板与WebSocket推送
可视化层我选ECharts而不是Chart.js,主要是ECharts对折线图、饼图、热力图的支持成熟,而且中文文档多,答辩演示时就算现场改配置也不慌。后端把ORM结果序列化成JSON后,前端通过fetch拉取接口数据再渲染。一个示例接口长这样:
def analyze_overview(request): if request.method != 'GET': return JsonResponse({'error': 'method not allowed'}, status=405) enable_count = TeenModeRecord.objects.filter(action='enable').values('user').distinct().count() total_users = UserAccount.objects.exclude(role='admin').count() play_seconds = UserBehaviorLog.objects.filter(behavior_type='play').aggregate(total=Sum('duration_seconds')) return JsonResponse({ 'open_rate': round(enable_count / total_users * 100, 2) if total_users else 0, 'total_play_seconds': play_seconds['total'] or 0, })前端ECharts初始化时先是空数据,拿到接口结果后再setOption,这个模式网上到处都是,不再赘述。
至于WebSocket,毕设里这属于加分项,一般不会强制要求。但你要是想让页面实时刷新“最新一条行为日志”或者“拦截数实时变化”,可以用Django Channels。先安装channels和channels-redis,项目结构里加一个asgi.py:
import os from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter from behavior_app.consumers import BehaviorConsumer os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'teen_project.settings') application = ProtocolTypeRouter({ 'http': get_asgi_application(), 'websocket': URLRouter([ path('ws/behavior/', BehaviorConsumer.as_asgi()), ]), })consumer里逻辑很简单,收到前端发来的任意消息就把数据库里最新一条行为记录推给前端。前端那边用原生WebSocket对象连接,onmessage里做图表增量更新。这里务必注意:Django Channels要求你部署时用支持ASGI的服务器,不能再开python manage.py runserver就当完了,如果你不想折腾,完全可以用setInterval每三秒轮询一次接口,毕设演示效果差别不大。
4. 论文组织、调试排查与答辩准备
4.1 论文结构怎么和代码一一对应
论文不是代码的说明书,而是“设计论证”。我的建议是直接按这套结构来:
- 绪论:写背景和意义,落点放在“未成年人网络保护的现实需要”和“内容推荐与内容分级的关系”上,顺带提一下国内外短视频平台青少年模式的发展。
- 相关技术介绍:Python/Django、MySQL、Bootstrap、ECharts,每个技术写清楚“为什么在本项目中用它”。
- 需求分析:把系统拆成用户端、管理端、数据分析端三个角色,每个角色列出用例,画用例图。
- 系统设计:总体架构图、数据库E-R图加上上面的五张表字段说明、接口设计列表。
- 系统实现:按我前面讲的四层结构,逐个模块贴关键代码并配运行截图。
- 系统测试:功能测试与性能测试,功能测试要包含正常、异常和边界三种用例。
关键是系统实现那一章,代码不要整段贴,只截最核心的三四段,比如青少年模式开关逻辑、ORM聚合统计、ECharts渲染,其他功能用文字描述即可。老师和查重系统都不会喜欢满屏代码的论文。
4.2 调试现场常见问题速查表
这部分是从真实开发里踩坑踩出来的,每一行都对应一个深夜。直接给你做成速查表。
| 问题现象 | 根本原因 | 解决办法 |
|---|---|---|
| 数据库存的时间比本地多了8小时 | USE_TZ=True,TIME_ZONE设置缺失或不对 | settings里TIME_ZONE='Asia/Shanghai',写完重启服务 |
| 后台列表里中文全是乱码 | MySQL默认字符集不是utf8mb4 | 建库时指定utf8mb4,或統一用SQLite避免这个麻烦 |
| 用户登录后刷新页面就掉线 | Cookie的samesite属性限制了跨站携带 | 前后端同域部署,或samesite=None加Secure |
| 统计播放时长时数字翻倍 | join后行为记录重复 | 用distinct=True,或把category冗余到行为表 |
| 删除主表记录时子表残留 | 外键没有设置on_delete级联 | models.ForeignKey(..., on_delete=models.CASCADE) |
| 前端从接口拿到数据后图表空白 | 后端返回的字段名和前端对不上 | 先看Network面板里实际JSON结构,逐字段核对 |
| 调用.objects.filter().delete()后信号没触发 | queryset.delete()是批量删除不走save/delete方法 | 需要信号就逐条delete,或程序里手动补逻辑 |
这里面最隐蔽的是第1条,时区问题不解决,你的时段热力分析基本是废的,凌晨三点的高峰能给你画到下午去,直接毁掉整个分析结果的说服力。我当时排查出来后,在论文测试章节还专门补了一段“时区正确性验证”,反而变成了一个亮点。
4.3 答辩前必须准备好的五个问题
答辩其实是一场预判。老师翻来覆去问的就那么几个问题,你提前组织好语言,现场就不会慌。
第一个问题:题目写Java,你为什么用Python/Django?标准回答:系统本质是B/S架构,Django作为Web框架完成数据采集、业务逻辑和接口发布,与题目要求的“Java计算机毕设”在工程目标上一致,而且Python的数据处理生态更适合本系统的数据分析模块;如果必须使用Java,可以用Spring Boot复刻全部功能,但开发周期会明显拉长。
第二个问题:这些数据是真的B站数据吗?标准回答:真实B站未开放官方数据接口,本系统通过用户操作采集和模拟数据导入两种方式产生行为数据,重点验证的是数据分析方法和指标模型,并预留了对接真实数据的接口,后续可结合授权合作方式扩展。
第三个问题:青少年模式怎么判定一个内容适不适合青少年?标准回答:系统对每个视频维护is_teen_safe审核标记,管理员通过后台审核并写入ContentAuditLog,用户在青少年模式下发出播放请求时,系统根据该标记做实时拦截并记录blocked日志,同时统计审核覆盖率,形成“标记-拦截-复盘”的闭环。
第四个问题:统计模块如果数据量很大,性能怎么办?标准回答:当前采用Django ORM聚合加索引优化的方式,行为日志表对user_id和start_time建立复合索引;数据规模达到百万级时可引入离线分析和缓存机制,比如用pandas做批处理、把指标结果缓存到Redis,保证页面秒开。
第五个问题:你有没有考虑用户隐私?标准回答:系统对行为记录采用匿名化处理,UserAccount只保留必要的登录字段和状态标记,不采集真实姓名、家庭位置等敏感信息,行为数据仅用于模式统计分析。这个回答既能体现工程素养,又能让答辩老师觉得你考虑问题全面。
写在后面的一些实在话
做一个毕设,和开发一个真实商业项目最大的区别,在于毕设的重点是“完整闭环”而商业项目更重“极端稳定”。所以我不建议你在毕设里堆微服务、上K8s、搞一大堆炫技却跟题目无关的技术,因为答辩老师看的是你能不能把一条业务链路讲通,再顺手讲清楚这个系统解决了什么问题。
我个人的体会是:拿到这种带“数据分析”字眼的题目后,最值得投入精力的地方永远是那五个核心指标的口径定义和可视化呈现,而不是把时间耗在界面美化或者纠结某个动画效果上。把这个主分析页面做成整个系统的门面,让老师打开首页就能看到图表和关键数字,你的答辩就已经赢了一半。顺带再留个后手,就是WebSocket那条“实时推送最新拦截记录”的效果,每次演示时我都能看到评委多看了两眼屏幕,这个细节投入小、收益高,强烈建议你也保留。