
做了一段时间Django相关的Web和小程序项目后我最大的感受是Django这种自带Admin、ORM、迁移体系的全栈框架用来做“数据展示类”的毕业设计或者课设真的能省掉很多重复造轮子的时间。今天要拆解的这个项目——基于Python的Django后端 学生移动端小程序 数据分析就是一个典型的“数据采集、统计、可视化、移动端展示”闭环。它的目标用户很明确学生会用小程序查看自己的成绩趋势和排名区间老师可以查看班级维度的成绩分析报表。适合谁看准备做同类毕设的学生想快速搭建小程序数据分析后端的开发者以及想了解Django和小程序如何串联的初学者。全文会用“需求→设计→实现→踩坑”的顺序来讲尽量把每个关键决策背后的原因说透。1. 项目骨架与技术选型思路1.1 先把需求拆清楚这个“数据分析”到底想分析什么很多同学拿到“学生移动端数据分析小程序”这种题目第一反应是打开编辑器直接写代码这其实是大忌。你连分析的对象、分析的维度、给谁看都没定义清楚写出来的东西大概率是一个四处拼凑的“假项目”。我习惯先拆需求。题目里的三个关键词各有侧重Django负责“后端服务”小程序负责“移动端载体”数据分析才是这个项目的灵魂。换句话说如果只是把数据库里的学生信息增删改查搬到小程序端那不叫数据分析小程序那叫“学生信息管理系统换了个壳”。要让答辩老师或者评审认可你必须有“分析”的动作从一堆原始数据里算指标、看分布、找趋势。具体到这个项目我建议把分析场景定为“学生成绩分析”这是最稳妥也最好讲的场景。原始数据是学生的成绩明细分析对象可以拆成三个层面个体层面单个学生的总分、平均分、排名区间、成绩趋势班级层面班级平均分、及格率、优秀率、分数段分布班级与班级之间的横向对比课程层面各门课程的平均分、难度差异、选课人数分布。这三个层面正好对应小程序的三个核心页面也对应后端Django要提供的三类接口。这种拆分方式的好处是每个人都有数据可看老师关心班级整体情况学生关心自己的位置功能职责非常清晰答辩时也很好解释。1.2 为什么是Django 小程序而不是别的组合选型环节可能是被低估最多的一步很多同学直接沿用别人的方案但心里并不清楚为什么。我在这类项目里推荐的组合是 Django 微信小程序 Python数据分析三件套理由有三个。第一个理由是 Django 非常适合“数据后端”。它有自带 Django Admin 后台数据录进去之后可以直接在后台管理不用额外写一个管理前端。对于毕设来说这能节省至少两天的前端开发工作量。另外 Django 的 ORM 写统计类查询时非常顺畅比如按班级分组算平均分用 annotate 加一个 Avg 就能搞定代码量小、逻辑直观。第二个理由是 Python 数据分析生态。pandas 负责清洗和聚合数据matplotlib 负责生成统计图表Django 只是把分析结果以接口形式暴露出去。这样你就可以把“数据分析”这个亮点真正落地而不是只放一个表格。第三个理由是微信小程序作为移动端的载体对校园场景很合适。学生不用装独立App扫码就能用前端页面也轻量。加上小程序本身的 API如 Canvas 绘图、图表组件足够支撑数据展示需求。当然这并不意味着这套方案没有缺点后面我会把跨域、域名校验、图表适配这些坑也一并讲清楚。整体来说它的收益远大于成本特别适合一个人在一到两个月内完成从零到上线。1.3 整体架构设计一条数据是怎么跑通全链路的架构层面我把它分成四层数据源 → 后端分析 → API接口 → 小程序渲染。数据源就是数据库里的原始表最核心的是一张成绩表关联学生表和课程表。Django 的 models 定义好之后可以用自带的 migration 机制快速建表也可以用脚本把 CSV 格式的模拟数据批量导入。后端分析这层要写一个专门的服务模块比如分析工具类它负责从数据库里取原始数据用 pandas 做分组统计算出平均分、及格率、分数段人数这类指标再用 matplotlib 生成图表。图表有两种交付方式一种是生成 PNG 图片通过 URL 或 base64 返回前端另一种是返回纯数值前端用 ECharts 渲染。我后面会对比这两种方式的优缺点。API 接口层用 Django REST Framework也就是常说的 DRF把分析结果序列化成 JSON。这里要注意设计好返回结构让前端拿到数据后可以直接绑定不需要再做二次加工。小程序渲染层就是用户最终看到的界面通过 wx.request 请求后端接口拿到 JSON 后用数据绑定渲染页面图表部分用 canvas 或者渲染图片的方式呈现。四层链路跑通之后整个项目的边界就非常清晰了。我见过不少同学把分析逻辑写在小程序端后端只存数据这是不合理的。数据分析的计算压力应该放在服务端小程序只负责展示否则真机上卡的还是用户的手机。2. 后端Django的精髓建模、统计接口与图表生成2.1 先跑通工程骨架和基础配置拿到项目建议先执行两条命令django-admin startproject student_data创建工程python manage.py startapp analysis创建分析应用。项目名用下划线不要用中划线否则后面 import 的时候会直接报错。然后记得在settings.py的INSTALLED_APPS里注册analysis和rest_framework不注册后面迁移和接口路由全都不生效。如果用的是 Django 5.x 配合 Python 3.11 以上版本需要留意pymysql和mysqlclient的兼容性。我的建议是开发阶段直接用默认的 SQLite足够支撑几千条以内的模拟数据等要部署上线再切换 MySQL 也来得及。切换时只要改DATABASES配置模型层代码不用动这是 Django ORM 带来的最大优势。2.2 数据模型怎么设计最省心Django 的模型设计决定了后面所有分析逻辑的写法先想清楚关联关系再动手可以避免后期反复改表。我给出的最简设计是三个模型Student学生、Course课程、Score成绩。Student 包含学号、姓名、性别、班级、入学年份字段Course 包含课程编号、课程名称、学分字段Score 是关联表包含学生外键、课程外键、成绩数值、学期、考试类型比如期中/期末。代码大概是这样的from django.db import models class Student(models.Model): student_no models.CharField(max_length20, uniqueTrue, verbose_name学号) name models.CharField(max_length50, verbose_name姓名) gender models.CharField(max_length10, choices[(M, 男), (F, 女)], verbose_name性别) class_name models.CharField(max_length50, verbose_name班级) enroll_year models.IntegerField(verbose_name入学年份) class Meta: verbose_name 学生 verbose_name_plural verbose_name def __str__(self): return f{self.student_no} - {self.name} class Course(models.Model): code models.CharField(max_length20, uniqueTrue, verbose_name课程编号) name models.CharField(max_length50, verbose_name课程名称) credit models.FloatField(verbose_name学分) def __str__(self): return self.name class Score(models.Model): student models.ForeignKey(Student, on_deletemodels.CASCADE, related_namescores, verbose_name学生) course models.ForeignKey(Course, on_deletemodels.CASCADE, related_namescores, verbose_name课程) score models.FloatField(verbose_name成绩) semester models.CharField(max_length20, verbose_name学期) exam_type models.CharField(max_length20, default期末, verbose_name考试类型) class Meta: unique_together (student, course, semester, exam_type) def __str__(self): return f{self.student.name} {self.course.name} {self.score}三个细节值得注意。第一Score 表一定要设置 unique_together 联合唯一约束防止同一个学生在同一学期同一门课存在重复成绩这个是数据质量的底线。第二外键要用 related_name这样反向查询会很顺手比如拿到一个学生对象后直接 student.scores.all() 就能取出他所有成绩。第三字段类型上成绩用 FloatField 而不是 IntegerField因为后面可能算加权平均分小数精度会很有用。建好模型之后执行 python manage.py makemigrations 和 python manage.py migrate 就能生成数据库表。默认的 SQLite 对毕设来说完全够用没必要上 MySQL除非你的导师明确要求。2.3 模拟数据怎么造直接写脚本或命令模型建完表是空的分析无从谈起。我建议写一个 Django management command 来批量生成模拟数据这样生成完随时可以清空重建比手动在后台录数据高效一百倍。操作方法是在 app 的 management/commands 目录下新建一个 fill_data.py 文件然后定义一个 Command 类里面的 handle 方法负责生成数据。生成逻辑很简单先造 30 名学生分布在3个班级再造 5 门课程然后对每个学生、每门课程随机生成一个 40 到 100 之间的成绩按两个学期分别生成。随机可以用 Python 的 random.uniform生成后再 round 到一位小数。这里我补充一个实操经验模拟数据一定要“有规律、有差异”而不是纯随机。比如让 A 班整体平均分偏高一些B 班偏中C 班偏低一些这样后面做班级对比时图表效果才明显答辩演示的时候也更好讲。纯随机的数据画出来所有柱子都差不多高观感很差也体现不出“分析”的价值。想让某班成绩偏高造数时给这个班的随机分数加一个偏移量即可。数据生成之后还可以用 Django shell 里验证一下总记录数。实测填充 30 人 × 5 门课 × 2 学期 300 条成绩记录已经足够支撑后续所有统计分析了。2.4 数据分析逻辑从“算数”到“找出结论”数据分析模块是整个后端的核心亮点。我第一次做的时候发现很多同学只知道把数据列出来然后画个图但图为什么要这么画、从图里能得出什么结论完全说不出来。这里我给出一个非常实用的思路所有分析指标都围绕“比较”和“分布”两个关键词。“比较”指的是不同维度之间的对比比如各班平均分对比、各课程平均分对比、同班两个学期的成绩变化。“分布”指的是成绩落在各个分数段的人数情况比如不及格、60-69、70-79、80-89、90-100 的人数占比。围绕这两个关键词后端需要输出以下几类核心指标总分、平均分、最高分、最低分、及格率、优秀率优秀率通常按 90 分及以上计算班级维度各科平均分的横向对比单科成绩分数段分布某个学生两个学期的成绩趋势对比。用 Django ORM 写这些统计非常方便。比如统计各班级的平均分核心代码是from django.db.models import Avg from analysis.models import Student class_scores ( Student.objects.values(class_name) .annotate(avg_scoreAvg(scores__score)) .order_by(class_name) )这里的关键是 values(class_name).annotate() 实现的分组聚合scores 是 Student 上通过 related_name 建立的反向关系。如果希望统计各班级的人数还可以加一个 Count(scores__id) 来统计记录数。分数段分布可以用 Case-When 或者 filter 参数来实现from django.db.models import Count, Q fail_count Score.objects.filter(score__lt60).count() good_count Score.objects.filter(score__gte90).count()不过如果分析逻辑变得更复杂比如要做加权平均、百分位排名、多表宽表汇总纯 ORM 写起来会非常绕。这种情况我建议把数据 load 到 pandas DataFrame 里统一处理代码可读性好得多。2.5 pandas 和 matplotlib 的引入时机就这个项目来说pandas 的主要作用是生成宽表和做复杂计算。比如要算每个学生在班级里的排名可以先按班级分组再用 rank 方法生成排名列。ORM 做这类操作会比较别扭pandas 一行就解决了。matplotlib 的作用是生成图表。这里有一个非常重要的方案选择后端生成 PNG 图片返回前端还是前端用 ECharts 自己画我两种都试过总结一下利弊后端先生成图片优点是代码简单、前端渲染压力小、学生对图片没法乱改缺点是图片无法交互也没有 Tooltip且需要处理中文字体路径、高清图 DPI 等问题。前端用 ECharts 渲染优点是图表可以交互、缩放、提示视觉效果好答辩时观感更高级缺点是需要引入组件库、协调接口返回的数据格式。我最终的推荐是如果时间紧后端生成图片足够如果想把项目做成精品建议用前端 ECharts 方案。这个项目标题强调“数据分析”答辩时图表的交互性是一个加分项所以我更倾向于前后端配合后端提供聚合后的数值接口前端负责图表绘制。2.6 DRF接口封装让小程序拿到的数据直接用接口层用 Django REST Framework。安装依赖后在 settings 里注册 rest_framework然后设计几个视图集。我建议按照小程序的页面需求来规划接口/api/overview/ 返回整体概览学生总数、课程数、平均分、及格率/api/class-stats/ 返回各班级的平均分、人数、及格率用于柱状图/api/score-distribution/ 返回分数段分布用于饼图或环图/api/course-stats/ 返回各课程平均分/api/student/ / 返回单个学生的成绩明细和趋势。写视图时最简单的方式是用 api_view 装饰器加上函数视图统计逻辑直接调用前面写的分析服务类返回 Response(JSON)。也可以使用 DRF 的 ViewSet配 ModelSerializer 做标准接口。考虑到这是一个面向小程序的接口服务我建议统一使用 JSONRenderer并在响应的最外层包一个统一的格式比如{ code: 0, message: success, data: {...} }前端拿到这个结构后判断 code 是否为 0然后取 data 渲染页面错误处理会非常统一。这个习惯看起来很基础但对于小程序这种多端环境非常关键因为前端不可能像网页那样直接在浏览器控制台看到完整的异常输出。写视图的时候还要注意不要在每个接口里都粘贴一大段统计数据计算逻辑。正确做法是抽出一个 analysis_service.py 模块把“统计图表生成”的复杂函数都放进去视图层只负责调用和返回。这样代码结构清晰答辩被问“你的分析逻辑在哪一层”时也回答得上来。3. 微信小程序端从列表到图表的呈现3.1 页面规划与目录结构小程序端我规划了三个 Tab 页面首页数据概览、分析数据图表、我的个人信息或说明页加一个学生列表详情页作为子页面。页面目录大概是这样的pages/ index/ // 首页展示整体指标卡片 analysis/ // 分析页班级对比柱状图、分数段环形图 students/ // 学生列表可点击进入个人详情 student-detail/ // 学生详情个人成绩趋势pages 配置在 app.json 里完成。这里有个容易踩的坑小程序的 tabBar 页面必须是 app.json 中 pages 列表的前几个且 tabBar 的图标是必需的。如果不想准备图标也可以不设 tabBar改用自定义首页的轮播或导航按钮来跳转。对毕设来说三个 Tab 的方式最直观评审打开小程序就知道项目有哪些功能。我用过 uniapp 也用过原生小程序开发。如果只是做这个项目原生小程序足够如果以后想多端复用可以用 uniapp 编译。但要注意uniapp 里引用 canvas 图表库时canvasId 的管理和原生开发不太一样同一页面多个 canvas 容易冲突建议一个页面只放一个图表需要多个图表时用多个页面承载。3.2 网络请求封装与数据绑定小程序的 wx.request 是基础能力但直接在每个页面里写 wx.request 会非常痛苦因为回调嵌套一多代码就特别乱。我的习惯是在 utils/request.js 里封装一个 promise 风格的请求工具const BASE_URL http://127.0.0.1:8000/api; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${path}, method, data, header: { Content-Type: application/json }, success: (res) { if (res.data res.data.code 0) { resolve(res.data.data); } else { reject(res.data); } }, fail: reject, }); }); } module.exports { request, BASE_URL };页面里调用就非常清爽了const { request } require(../../utils/request); Page({ data: { overview: {}, }, onLoad() { request(/overview/).then((data) { this.setData({ overview: data }); }); }, });这里需要强调一个基础但致命的细节小程序的 wx.request 在开发阶段必须在小程序开发者工具中勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”否则请求会直接报错。如果使用手机真机预览那就必须在公众平台的“开发设置”里配置服务器域名而且只支持 HTTPS。对本地调试来说勾选“不校验”就可以正常运行。3.3 图表方案wx-charts 还是 ECharts小程序内画图表目前主流方案有两个wx-charts 和 echarts-for-weixin。wx-charts 是一个轻量图表库上手快内置柱状图、折线图、饼图API 风格和文档都比较简单适合学习项目。缺点是交互能力弱很多自定义配置支持不好。echarts-for-weixin 是 ECharts 官方为小程序提供的适配方案基于 Canvas 2D 渲染可以支持绝大多数 Web 端 ECharts 配置。引入方式稍复杂一些需要把 ec-canvas 组件放进项目并在 Page 里初始化图表。不过演示效果好了不止一个档次答辩时手指划过图表会有 Tooltip 提示这视觉冲击力很强。我个人建议用 echarts-for-weixin 作为主力图表方案。下面是一个典型的柱状图初始化代码import * as echarts from ../../ec-canvas/echarts; function initChart(canvas, width, height, dpr) { const chart echarts.init(canvas, null, { width, height, devicePixelRatio: dpr }); canvas.setChart(chart); chart.setOption({ xAxis: { type: category, data: [A班, B班, C班] }, yAxis: { type: value, min: 0 }, series: [ { type: bar, data: [82.5, 75.2, 68.9], itemStyle: { color: #4f8ff7 }, }, ], }); return chart; }这里的 dpr 参数很关键真机上如果不设置 devicePixelRatio图表会显示模糊。很多同学遇到“图表发虚”的问题就是这个原因。实际调用时页面里需要一个 canvas 标签并绑定 ec 组件事件。3.4 学生个人详情页趋势线与数据洞察个人详情页是这个小程序里比较能体现“数据分析”精髓的地方。不只是展示这个学生考了多少分而是要展示连续两个学期各科成绩的对比趋势以及他的各科成绩在班级中的相对位置。比如显示一条折线图横轴是课程纵轴是分数分别画大二上学期和大二下学期的线两条线一对比立马上一个档次。后端接口需要返回学生各学期的成绩字典。前端拿到后把两个学期的数据分别转成数组传给 ECharts 的 series 即可。这里要注意空数据显示的处理比如某个学生某门课没有成绩接口返回 None前端要过滤掉否则图表会断线。可以在后端就统一处理成 0 或者 null建议处理成 null这样 ECharts 会自动跳过空值点连接前后数据。4. 联调实战与高频问题排查4.1 前后端联调的基本流程前后端开发完成后联调是绕不开的一步。完整的流程是先启动 Django 开发服务器确认接口用浏览器访问能看到 JSON再打开微信开发者工具在项目配置里修改 BASE_URL 指向你的开发机 IP 或 localhost然后运行小程序逐个页面测试接口是否正常返回如果报错就打开 Console 面板看具体错误信息。我强烈建议在联调阶段就用 Wi-Fi 下局域网 IP比如 http://192.168.1.101:8000而不是 localhost。因为微信开发者工具模拟器虽然可以访问本地 localhost但如果你用手机真机预览localhost 指向的是手机自己根本连不上你的电脑。真机调试时使用电脑的局域网 IP 才能通。4.2 跨域问题Django 端需要配置什么这里要分清楚一个关键点小程序的 wx.request 不受浏览器同源策略限制所以小程序开发时一般不需要处理 CORS。但你如果用浏览器直接访问 Django 接口或者网页端也接同一个后端那 CORS 就躲不掉了。处理方式是在 Django 里安装 django-cors-headers然后把需要的域名加到配置里INSTALLED_APPS [ ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, ... ] CORS_ORIGIN_ALLOW_ALL True # 开发阶段可以放开上线再收紧如果接口是给微信小程序专用这一步可以省掉但为了保险我还是会加上。因为评审老师可能自己在浏览器里打开接口地址验证如果弹出 CORS 报错体验会很差。4.3 matplotlib 中文乱码与图片清晰度后端生成图片方案里最大的坑是 matplotlib 中文字体。默认字体不支持中文你在柱状图上写“班级平均分”会看到一堆方框。解决方案是显式指定支持中文的字体。Windows 下常见的是 SimHei 或 Microsoft YaHei也可以把字体文件放到项目目录里统一管理import matplotlib matplotlib.use(Agg) import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False第二行关闭 Unicode 负号显示是为了避免负号显示成方块。这个设置必须放在所有绘图代码之前而且要多次确认生效。另外保存图片时建议设置 dpi150 以上同时用 bbox_inchestight 裁剪空白否则图表四周会有大片留白在小程序里的显示效果很懒散。4.4 小程序真机预览的几类怪问题真机预览出现的问题通常比模拟器多我遇到过的典型场景有三个。第一个是 network request failed多半是服务器没启动、IP 不对或者域名没备案。排查顺序先确认 Django 服务进程活着再确认手机和电脑在同一局域网再确认防火墙允许 8000 端口。Windows 防火墙经常拦截开发服务器的端口测试时可以在防火墙规则里临时放行 python 进程。第二个是图表空白或加载不出来先看 Console 是否有 ec-canvas 相关报错。常见原因是未在 Page 的数据里配置 canvasId或者 Canvas 高度为 0导致图表渲染区域不可见。需要给 canvas 标签显式设置宽度和高度。第三个是 setData 渲染过慢导致卡顿。因为小程序的 setData 每次都是全量更新视图层如果你把一个很大的列表一次性绑到界面上真机就会卡顿。解决办法是分页加载或使用 observer 组件按需渲染。4.5 常见问题速查我把高频问题整理成一个速查表方便对照排查。症状可能原因解决方法小程序请求报域名错误未勾选“不校验合法域名”或域名未配置开发阶段勾选不校验上线配置HTTPS域名wx.request 返回 invalid urlBASE_URL 写错或包含空格检查 url 是否完整可访问Django 接口 500 错误视图代码异常或数据库迁移未执行先执行 migrate查看 Django 终端输出图表中文字体显示方块matplotlib 未设置中文字体设置 font.sans-serif 并清理字体缓存图表模糊未设置 devicePixelRatioecharts.init 时传入 dpr真机连不上接口电脑防火墙拦截或 IP 写错换局域网IP确认防火墙放行数据统计结果不对造数据时分数区间太低或关联错误检查 Score 的 student 外键数据和 unique_together 约束删除学生后成绩还在外键删除策略设置错误Score 表 on_delete 设为 CASCADE学生删除时级联删除成绩5. 从毕设到实用扩展与经验复盘5.1 三个值得做的扩展方向这个项目做完后如果还有精力或者想让答辩更有亮点我建议往三个方向扩展。第一个方向是增加预测能力比如基于历史成绩用 scikit-learn 做一个简单的线性回归预测学生下次考试的成绩区间。这个扩展可以直接把项目从“统计展示”提升到“分析与预测”在数据分析类毕设中属于典型加分项。后端新增一个接口前端在首页加一个“成绩预测”入口即可。第二个方向是图表维度的丰富加入雷达图展示学生多维能力各科成绩对比加入热力图展示各班级各科的表现强弱这些视觉形式比普通柱状图、饼图更容易让人记住。第三个方向是引入用户角色权限。目前系统是所有人看所有数据不太符合真实场景。可以给 Django 引入一个简单的登录机制老师账号看班级汇总学生账号看个人详情。移动端可以结合微信登录手机号授权流程小程序端通过 wx.login 获取用户身份服务端签发 token之后每个请求带上 token 标识角色。这一步能显著增加项目完整度。5.2 时间规划建议我在接手这种项目时会给学生一个课时建议需求拆解与实际调研 5 到 7 天Django 建模和接口开发 10 天小程序页面与图表开发 7 天联调与修复 bug 5 天写论文和准备答辩 5 天。总计大概 30 个工作日。如果时间紧张可以先用后端生成图片的方案顶上后续再替换成 ECharts功能迭代的优先级不能乱。5.3 踩坑总结哪些教训最值钱最后把我在实际开发和辅导项目过程中踩过最深的几个坑拎出来说一遍。第一不要高估 Django Admin 的作用。毕设演示时很多同学喜欢打开 Admin 后台给老师看数据录入功能但 Admin 的默认界面非常简陋如果不做样式优化观感不会太好。我通常建议 Admin 只当作内部管理工具对外展示重心放在小程序端。第二一定要重视数据造数的合理性。成绩全是 80 分以上图表会丧失分析感成绩全部集中在 60-70又显得没有区分度。造数据时先用 Excel 或脚本预览一眼统计指标确认分布合理再写代码。第三Django 的 DEBUGTrue 在开发阶段很好用能看到详细异常但联调真机时如果把服务端日志打满就很难定位问题。我一般会在项目的日志配置里单独分文件记录 Django 请求日志避免和浏览器控制台信息混在一起。这个项目真正值得花心思的地方不在“会不会 Django”或者“会不会小程序”而是当你拿到一个真实场景时能不能把数据链路完整跑通并用可视化的方式把结论讲清楚。只要统计口径定义到位、数据结构设计合理后面的展示层工作都是在做填空题。答辩那天把班级对比、分数段分布、个人趋势这几张图放出来说清楚每个指标怎么算的、为什么这样算就已经赢过大多数做成“学生信息管理”的同类项目了。