1. 项目概述与需求拆解考研服务系统说白了就是给备考学生提供一个“信息工具交流”的一站式平台。这一点我需要先强调因为很多同学在做这类系统时容易跑偏——要么把它做成纯资讯网站要么做成纯笔记工具忽略了考研群体真正的使用场景。我说的这个大学生考研服务系统核心解决三个痛点院校信息零散难查、复习规划缺乏工具支撑、考研经验互相隔绝。围绕这三个痛点我把它拆成四个基础模块用户系统注册登录个人中心、院校专业库检索收藏对比、资料中心真题笔记上传下载、考研社区经验贴交流互动。在这四个模块之上再做后台管理用Django原生的Admin就能覆盖大部分管理需求。技术选型上我用的是Django做主体业务框架Flask单独拆出来跑轻量级的分支服务。为什么这么设计后面我会细说先放结论Django在ORM、Admin、认证授权、表单处理这些重量级能力上碾压Flask适合做业务密集型的主系统而某些边缘功能比如关键词过滤、数据清洗、简单统计用Flask写反而更轻快启动快、依赖少、不会污染主工程。这个项目适合谁来参考如果你是正在做Python Web方向课程设计或毕设的同学这篇文章可以直接作为你从零搭建同类系统的施工图如果你已经在写Django项目想了解“双框架协作”这个进阶玩法第四部分会对你有参考价值。基础要求是你至少懂Python Web的基本概念路由、视图、模型、模板这几个词不陌生。至于Flask部分不会也不影响看懂整体思路。2. 整体架构设计与技术选型2.1 为什么是DjangoFlask双框架组合我先解释这个组合背后的逻辑。很多人在做Web项目时会有个误区觉得一个项目必须、且只能用一套框架从头写到尾。实际上框架只是工具项目里的不同业务模块对工具的要求差异很大完全可以组合使用。Django的优势不用多讲自带Admin后台管理系统、内置User认证体系、ORM对象关系映射非常成熟、表单处理和安全防护CSRF、XSS、SQL注入都有现成机制非常适合做考研服务系统这种“数据管理业务逻辑页面展示”的项目。换句话说这个系统的核心矛盾是“信息录入、存储、查询、管理”而这些恰好是Django最擅长的事情。但纯Django也有别扭的地方某些高并发、轻计算、独立部署的边缘模块放在Django里每次改个代码都要走完整的Django加载流程加载配置、初始化ORM、注册APP开发和调试效率都低。比如我要给资料中心做“敏感词过滤”和“文件信息抽取”这两个功能它们逻辑独立、不依赖数据库、只需提供接口给主系统调用——这种情况下单独起一个Flask服务是更合理的。你可以这样理解Django是主力部队负责正面战场的阵地战业务建模、数据交互、页面管理Flask是特种小队负责侧翼的轻量任务独立计算、接口服务、数据预处理。两者通过HTTP调用方式协作互不干扰又各取所长。2.2 项目目录结构与模块划分实际动手时我的目录结构是这样组织的kaoyan_service/ ├── manage.py ├── config/ # Django 主配置 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ # 用户模块 │ ├── schools/ # 院校专业库 │ ├── materials/ # 资料中心 │ ├── community/ # 考研社区 │ └── dashboard/ # 个人中心/学习计划 ├── flask_service/ # Flask 独立服务目录 │ ├── keyword_filter.py # 敏感词过滤服务 │ ├── file_extractor.py # 文件信息抽取服务 │ └── stats_service.py # 轻量统计服务 ├── static/ ├── media/ # 用户上传文件 └── templates/这样划分的核心逻辑是“业务边界清晰”。apps下每个目录就是一个完整的业务模块包含自己的models、views、urls、admin模块之间通过明确的接口调用避免互相import混成一团。Flask服务独立在flask_service目录里跟Django主工程完全解耦它只暴露HTTP接口不关心谁在调用。2.3 数据库设计与关键模型数据库是这类系统的重头戏我直接给你看核心表的设计对照着建表就行。用户表在Django自带的User基础上扩展我用的是继承AbstractUser的方式加字段而不是用Profile一对一关联。前者在查询和Admin管理时更直观后者在第三方登录时会多一层嵌套没必要。class UserInfo(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) real_name models.CharField(max_length50, blankTrue) target_school models.ForeignKey(schools.School, nullTrue, blankTrue, on_deletemodels.SET_NULL) target_major models.CharField(max_length100, blankTrue) study_start_date models.DateField(nullTrue, blankTrue) daily_goal_hours models.IntegerField(default6) avatar models.ImageField(upload_toavatars/, blankTrue)院校专业库是整个系统的数据基石。考研学生第一需求就是查院校所以这张表的设计必须考虑检索的维度地区、院校层次985/211/双一流、专业名称、考试科目、往年分数线、招生人数。我把院校和分数线拆成两张表因为分数线是按年份累积的院校一条记录对应多条分数线记录。class School(models.Model): name models.CharField(max_length100, uniqueTrue) province models.CharField(max_length20) city models.CharField(max_length20) level models.CharField(max_length10, choices((985, 985), (211, 211), (double_first_class, 双一流), (normal, 普通院校))) is_graduate_school models.BooleanField(defaultTrue) description models.TextField(blankTrue) class SchoolScoreLine(models.Model): school models.ForeignKey(School, on_deletemodels.CASCADE, related_namescore_lines) year models.IntegerField() major models.CharField(max_length100) total_score models.IntegerField() politics_score models.IntegerField(nullTrue) english_score models.IntegerField(nullTrue) math_score models.IntegerField(nullTrue) professional_score models.IntegerField(nullTrue) enrollment_count models.IntegerField(nullTrue)资料表和社区表设计思路类似核心是外键关联到用户通过category字段做分类用status字段做审核状态标记。让我提醒一点资料表一定要加download_count计数因为“热门资料”排序靠的就是这个字段哪怕你现在不做排序功能后面想加了有字段在就不会被动。2.4 为什么不用微服务架构肯定有人会问既然都拆Frame了为什么不干脆上微服务我明确说现阶段完全没必要。微服务需要服务注册、服务发现、分布式事务、链路追踪一大堆配套设施对单体业务系统来说纯属增加维护成本。Flask服务之间存在的是调用关系不是强依赖关系Flask挂了可以用熔断逻辑降级不需要像微服务那样保障“整个链路”高可用。举个具体场景关键词过滤服务如果挂了最坏的结果是用户发布的内容跳过自动过滤等主服务恢复后再补审。这是可接受的降级方案为此引入一整套服务治理组件就是杀鸡用牛刀了。3. 核心功能模块实现细节3.1 用户认证与权限控制用户认证我直接用Django内置的authenticate和login注册时做邮箱和用户名唯一校验密码用Django默认的PBKDF2算法加密存储。这是安全底线不要自己去实现加密逻辑。注册流程我加了“邮箱验证码”环节。为什么不用手机号因为大学生邮箱基本是标配学校邮箱、QQ邮箱都行邮箱验证码的发送成本低、部署简单不需要接短信服务商的SDK。我用的方案是注册时前端先请求发送验证码接口后端生成6位随机码存Redis有效期5分钟发送到用户邮箱用户提交注册表单时带上验证码后端比对通过后才创建用户。权限控制分三级未登录游客只能浏览院校库和资讯普通登录用户可以发帖、上传资料、管理自己的学习计划管理员通过Django Admin标记is_staff可以审核内容、管理全部用户和数据。视图层用login_required装饰器控制页面访问用request.user.is_staff判断后台权限。不需要引入第三方权限库这个规模的系统Django原生的就够。要注意的是Django默认的User表中邮箱字段email不是唯一的如果你想用“邮箱密码”登录得在认证后端做定制。我实际写了EmailOrUsernameModelBackend逻辑是用户输入的名字先按用户名查查不到再按邮箱查都查不到返回None。代码不长但很实用from django.contrib.auth.backends import ModelBackend from django.contrib.auth import get_user_model UserModel get_user_model() class EmailOrUsernameModelBackend(ModelBackend): def authenticate(self, request, usernameNone, passwordNone, **kwargs): if username is None: username kwargs.get(UserModel.USERNAME_FIELD) if username is None or password is None: return None try: user UserModel.objects.get(usernameusername) except UserModel.DoesNotExist: try: user UserModel.objects.get(email__iexactusername) except UserModel.DoesNotExist: UserModel().set_password(password) return None if user.check_password(password) and self.user_can_authenticate(user): return user return None3.2 院校专业库的检索实现院校库页面是这个系统里最简单的页面之一但最容易做砸。因为数据是明细数据一个页面要承载“筛选条件列表分页”三件事处理不好就会让用户觉得卡、乱、烦。筛选条件我做了三个维度地区省份、院校层次、专业名称。实现上不用ORM的Q对象硬拼而是通过request.GET动态构造查询条件。关键代码逻辑是# 视图层 def school_list(request): schools School.objects.all() # 地区筛选 province request.GET.get(province, ) if province: schools schools.filter(provinceprovince) # 层次筛选 level request.GET.get(level, ) if level: schools schools.filter(levellevel) # 专业名称筛选 zymc request.GET.get(major, ) if zymc: schools schools.filter(majors__name__icontainszymc) # 分页 paginator Paginator(schools.distinct(), 20) page paginator.get_page(request.GET.get(page)) ...注意这里我用的是filter链式调用每个条件都是可选的不存在就跳过这样URL参数和ORM查询条件一一对应逻辑清晰。专业名称用icontains是因为专业全称太长用户往往只记得关键词比如输入“计算机”找“计算机科学与技术”icontains能模糊匹配到。另外我在School模型上建了一个majors的ManyToManyField关联专业表这样“一个学校对应多个招生专业”就直接用ORM表达。要多表查询时prefetch_related配合分页能防N1查询后期数据量大了这个优化很关键。3.3 学习计划与打卡功能考研是长跑学习规划和坚持打卡是刚需。我设计的方案是用户设定目标院校和目标专业后系统自动生成一个基础复习计划政治、英语、数学/专业课的基础轮、强化轮、冲刺轮三段式用户可手动调整每日任务每天勾选完成任务来打卡。自动生成计划的算法是我在这个项目中写过的比较有逻辑的代码之一。基础数据是几门公共课的平均复习周期用“倒排时间轴”方式计算假设考生当年12月下旬初试反推基础轮结束时间、强化轮结束时间、冲刺轮开始时间然后按各阶段天数分配任务# 简化版本的核心计算逻辑 from datetime import timedelta base_plan { 政治: {base_days: 30, strengthen_days: 30, sprint_days: 20}, 英语: {base_days: 60, strengthen_days: 45, sprint_days: 30}, 专业课: {base_days: 50, strengthen_days: 40, sprint_days: 25}, } def gen_plan(exam_date): for subject, stages in base_plan.items(): sprint_start exam_date - timedelta(daysstages[sprint_days]) strengthen_start sprint_start - timedelta(daysstages[strengthen_days]) base_start strengthen_start - timedelta(daysstages[base_days]) # 生成三段任务写入数据库...这个算法很简单但解决了一个实际问题用户不需要自己列计划表系统自动帮他分配好三轮复习的日历范围。打卡功能则是一张StudyLog表用户登录后点“今日打卡”后端检查当天是否已记录没有就新增一条带subject字段的记录前端显示一个绿色日历格子来激励用户坚持。3.4 资料中心与考研社区资料中心处理的是文件上传和下载。上传我用Django的FileField配media/目录存储前端限制文件类型和大小只允许PDF、Word、Excel单文件不超过50MB。下载走一个独立视图做计数download_count每次加1后返回FileResponse。社区模块则是标准的发帖、回帖、评论结构。发帖需要标题和正文标题用CharField(max_length200)正文用TextField()。我做了“经验贴”和“求助帖”两个分类经验贴可以关联院校和专业这样论坛内容也能反哺院校库的知识沉淀。回帖不做楼中楼嵌套因为考研社区的场景不需要那么复杂的树形结构平面列表加排序就够了。两个模块都接入了status字段做内容审核新发布的内容默认pending状态管理员在后台审核通过后才会出现在前台列表。审核机制一开始就容易被人忽略我后来吃了亏才补上的。凡是用户生成内容UGC的模块必须先过审核再接前台展示否则垃圾信息会直接淹没你的网站。4. Flask扩展服务模块的落地实践4.1 关键词过滤与内容安全帖子一旦放开给用户发第一个要处理的是敏感内容过滤。我把这个能力单独做成了Flask服务逻辑很简单内置一份敏感词库把文本与词库比对命中就把对应词替换成*号。接口用POST接收JSON返回过滤后的文本。为什么不在Django里直接写原因有两个。第一敏感词库会不停更新如果写死在Django的模型或视图里每次更新词库都要重新部署整个项目独立成服务后只需重启Flask这一个进程。第二这个过滤器可能会被多个模块使用帖子、评论、个人简介用独立服务可以避免每个模块都写一遍同样的逻辑。# flask_service/keyword_filter.py from flask import Flask, request, jsonify app Flask(__name__) SENSITIVE_WORDS [词库示例1, 词库示例2] # 实际项目从文件加载 app.route(/filter, methods[POST]) def filter_text(): data request.get_json() text data.get(text, ) for word in SENSITIVE_WORDS: text text.replace(word, * * len(word)) return jsonify({filtered: text}) if __name__ __main__: app.run(port5001)Django侧通过requests.post(http://127.0.0.1:5001/filter, json{text: content})调用。要点是Flask服务启动在5001端口与Django的8000端口错开避免端口冲突调用失败时用try/except捕获并降级为“原样返回”不能因为过滤服务挂了导致用户发不了帖子。4.2 文件信息抽取服务资料中心上传的文件我想自动提取PDF的标题、页数和创建时间来展示。这些信息用Django处理也不是不行但PDF解析库比如PyPDF2在Django进程里跑有一个现实风险解析大文件时会占用较多内存和CPU时间如果跑在Django请求线程里会拖慢整个网站响应。放到Flask独立进程后即使解析超时也不会影响主站。实现上Flask服务接收文件URL下载后解析然后把结果POST回主系统存库请求流程 1. Django收到用户上传先存到media目录 2. 把文件路径发给Flask /extract 接口 3. Flask用PDF解析器提取元信息 4. Flask返回JSON标题、页数、生成时间 5. Django把Meta信息更新到file记录这个模式叫“异步副作用分离”核心业务存储文件在主进程完成附加计算提取信息在边缘服务完成。对小项目来说这种松耦合比直接塞在一起好维护得多。4.3 Django与Flask的协作规范双框架协作最怕的是“两个框架都想管一切”最后代码互相纠缠部署时一团乱麻。我给自己定了三条规范你可以直接抄数据访问统一走Django的ORMFlask服务不直接操作数据库如果需要查数据Django通过API把数据POST给FlaskFlask服务只做“输入到输出的计算”无状态、无会话方便随时重启两个服务通过本机HTTP通信不直连数据库、不共享文件锁这三条规矩执行下来两个框架各司其职协作反而比单框架还清晰。遇到Flask服务挂了Django侧重试加上超时设置用户无感知。5. 部署上线与性能优化实战5.1 服务器环境与部署流程部署我选的是Linux的Ubuntu发行版Python环境用虚拟环境管理virtualenvWeb服务器用NginxGunicorn的组合。Django跑GunicornFlask服务单独跑一个Gunicorn实例Nginx做反向代理按URL前缀分发请求/api/开头转发给Django/flask/开头转发给Flask服务静态文件由Nginx直接serve。两个Gunicorn实例的启动配置我放在systemd服务里开机自启。核心配置就三行# Django gunicorn 配置 gunicorn config.wsgi:application --bind 127.0.0.1:8000 --workers 3 # Flask gunicorn 配置 gunicorn keyword_filter:app --bind 127.0.0.1:5001 --workers 2Nginx配置片段server { listen 80; server_name your-domain.com; # 静态文件 location /static/ { alias /path/kaoyan_service/static/; } location /media/ { alias /path/kaoyan_service/media/; } # Django location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # Flask location /flask/ { proxy_pass http://127.0.0.1:5001; proxy_set_header Host $host; } }部署过程中坑最多的是静态文件。Django开发模式会自动处理静态文件但一上生产就不再处理要先用collectstatic把散落在各APP的静态文件收拢到统一目录再交给Nginx。我连续两次忘了这一步导致样式全丢排查半天最后发现只是没跑collectstatic。数据库我用的是SQLite还是MySQL前期开发和测试用SQLite零配置、单文件、随项目走非常方便。但数据量上去后尤其是社区帖子、学习记录这类增长型数据SQLite并发写能力不够我切换到了MySQL。切换时要注意dumpdata导出JSON再loaddata导入时间字段格式需要检查一遍其它基本无缝。从SQLite切换到MySQL是绝大多数这类项目都会经历的路线不用一开始就上重量级数据库。5.2 缓存策略与访问优化考研服务系统的访问热点是院校库页面和热门帖子这类数据读多写少非常适合用缓存。我用了Django的缓存框架配置为Redis后台需要装redis-server并安装django-redis依赖。我的缓存策略是院校列表页的结果缓存5分钟cache_page装饰器直接加在视图上热门资料Top10缓存1小时凌晨定时任务更新一次用户首页的学习计划和打卡日历缓存5分钟用户每次打卡后主动清理该用户缓存一个细节缓存一定要设置过期时间防止数据永远不更新。我踩过一次坑就是忘记给院校列表设过期时间导致更新了一条分数线后前台三天都没变化用户以为系统坏了。Django的cache_page默认是180秒但我自定义时要记得timeout参数。分页和懒加载也是体验重点。帖子列表用Django的Paginator做分页每页15条图片和文件预览都是懒加载首屏只加载可见部分滚动到哪加载到哪。实测下来页面加载时间从最初的2.3秒降到1.2秒左右这部分优化虽然代码量不大但对SEO和用户体验的提升是很直观的。5.3 数据备份与安全加固数据备份我用的是最笨但最可靠的办法每天凌晨用cron执行一次mysqldump全库备份保留最近7天的备份文件同步到另一台机器的存储目录。备份这件事平时看起来没用等数据真丢了你就会感谢自己做了这个决定。安全方面做了三件事Django的DEBUGFalseALLOWED_HOSTS配置为域名生产环境全站走HTTPS证书用免费的就行。另外因为系统涉及用户上传文件我在Nginx层限制了上传大小client_max_body_size 50m;防止有人上传超大文件把磁盘塞满。Flask服务的接口加了白名单只允许来自本机Nginx的请求这样外网无法直接访问Flask端口降低被攻击的面。6. 常见问题与排查技巧实录6.1 用户登录报“用户名或密码错误”但数据没问题这个问题的根源大多是Django自带的认证后端只认username字段如果你做了邮箱登录必须像前面那样扩展认证后端。还有一个常见情况密码转义错误。前端传密码时表单提交方式是application/x-www-form-urlencoded后端接收没问题但如果你用AJAX传JSON要确保密码字段不是被前端加密过的形式。我第一次用JavaScript的encodeURIComponent处理密码导致后端存的是转义后的字符串永远匹配不上。6.2 上传文件失败提示连接重置排查方向依次是Nginx的client_max_body_size是否够大、Django的FILE_UPLOAD_MAX_MEMORY_SIZE默认2.5MB超过会写临时文件但不报错、服务器磁盘空间是否满了。我遇到的一次是磁盘满了导致上传写入失败Nginx直接返回502。排查命令很简单df -h看磁盘使用率tail -f /var/log/nginx/error.log看错误日志。6.3 院校库查询速度慢分页卡顿院校和专业数据量不大但如果没加索引全表扫描一样会慢。必加的索引是School.province、School.level和分词表的外键字段。另外前面提到的N1查询问题在列表页用select_related和prefetch_related能显著减少SQL条数。我在未优化之前一页院校列表产生了120条SQL优化后降到5条速度差一个数量级。判断N1问题的技巧是在Django的settings.py里临时打开LOGGING把SQL语句打印出来看到循环里执行N次相同结构SQL基本就是N1。6.4 时区导致的时间记录错误Django默认使用UTC时间如果你不设置TIME_ZONE打卡日历显示的时间会比北京时间早8小时。我第一版没注意这个问题用户晚上23点打卡记录的时间是下午15点日历格子显示错位。解决方式是在settings.py里设置LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_TZ True注意如果项目已经上线TIME_ZONE不要轻易改动因为数据库里已存的时间都是UTC改了会导致历史数据时间错乱。正确做法是在显示层做时区转换或者用USE_TZ False如果业务只在本地用的话。6.5 Flask接口调用超时主流程全部变慢我在调用Flask服务的代码里忘了加超时参数如果Flask服务卡死比如解析一个异常PDF文件Django的请求线程会一直挂着最终所有用户请求都会被阻塞。排查后我统一在requests.post加上了timeout3参数超时直接走异常分支并把Flask服务改成线程池模式保证单个慢任务不阻塞整个服务。另外还有个经验requests库的timeout分为连接超时和读取超时如果你只传一个数字它同时作用于两者。对不确定耗时的服务比如文件解析建议给更宽的读取超时、更严格的连接超时requests.post(url, jsondata, timeout(2, 5))这样连接阶段2秒没连上立刻放弃读取阶段5秒内没响应才算超时兼顾了快速失败和给服务留处理时间。7. 模拟环境搭建与项目复现如果你想在自己的机器上跑起来我给你一条完整的环境配置路径按顺序执行不会有问题。7.1 开发环境准备推荐Python 3.9及以上版本64位的。创建虚拟环境python3 -m venv venv source venv/bin/activate安装依赖我用的是一个requirements.txt内容大致是Django4.2,5.0 flask3.0 mysqlclient2.2 django-redis5.3 requests2.31 PyPDF23.0 Pillow10.0 uwsgi2.0注意mysqlclient在Windows上编译容易报错如果开发环境是Windows建议先在Windows上用SQLite跑通部署到Linux时再切换MySQL别给自己找麻烦。7.2 初始化数据库和超级账号激活虚拟环境后依次执行python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py collectstatic python manage.py runserver 0.0.0.0:8000浏览器访问http://127.0.0.1:8000能看到系统首页访问/admin使用刚才创建的超级账号登录就能进入后台管理。7.3 导入模拟数据开发阶段没有真实数据界面是空的没法调试。我建议写一个自定义管理命令python manage.py seed_data里面生成20所模拟院校、若干专业、测评分数的示例帖子。示例数据格式要与模型字段对应重点是多造几条不同地区、不同层次的院校数据方便测试筛选功能。数据导入还有一种更省事的办法直接从Django Admin里手工录入几条数据速度虽然慢但能更直观地理解每条数据字段的含义。我当年就是手工录了两条院校数据做冒烟测试然后才写的seed脚本。8. 在真题项目里打磨出来的一些实际经验这个系统的功能点并不复杂单看每个模块都挺常规用户注册登录、列表页筛选、富文本发布、文件上传下载、简单的日历打卡。但把这些模块组合成一个“能用、有人愿意用的系统”才能真正体现设计功力。我觉得做这类项目最关键能力有三个拆需求的能力、定边界的能力、排优先级的能力。先说拆需求。用户说“我要一个考研服务系统”这句话没有信息量。你得把它拆成“用户打开网站后首先要做什么第二步做什么最常做的三件事是什么”。在我的拆解里考研学生的高频动作就三件查院校、看经验、做计划。其它一切功能如果这三件做不好做得再多都是零。再说定边界。我知道很多同学会忍不住把功能越加越多加个模拟考试模块、加个在线答题、加个视频课程播放……停。项目周期是有限的你的精力是有限的每加一个功能就会稀释其它功能的质量。我给自己定的原则是核心功能用户、院校库、资料、计划打卡、社区必须做到用户能顺畅走完整个流程非核心功能一律砍掉或后置。这个系统里我砍掉过“考研日历订阅”“AI择校推荐”这些听着很酷但成本极高的功能换来的是核心流程的稳定可靠。最后说排优先级。我开发这个系统的顺序是先搭用户系统一切功能的前提然后是院校库数据密度最高的模块接着是资料中心能产生真实用户资产再是学习计划粘性最高的模块最后才是社区。如果时间不够社区甚至可以第一期不做。这个顺序的本质是“从刚需到痒需从基础设施到丰富体验”。Flask和Django的双框架协作看起来是技术选型问题本质上是工程管理问题知道什么功能适合用什么工具实现知道什么功能不值得为它增加架构复杂度。这个判断力比多会一个框架重要得多。如果你正在做类似的项目我给三条最实在的建议第一先搞定一个完整的“用户从注册到使用核心功能的闭环”不要贪多第二数据库字段冗余一点没关系但外键关系一定要想清楚后期改表结构是最痛苦的第三所有用户输入包括富文本内容一律过一遍清洗和审核这个安全底线不能妥协。最后再分享一个小技巧开发阶段把Django的DEBUGTrue打开会给你非常详细的报错页面但上线前记得关掉。我在实际测试中发现开着DEBUG时如果用户传了非法参数页面会直接把环境变量和部分配置路径暴露出来这对真实部署来说太危险了。在settings.py里根据环境变量区分配置开发环境用独立配置文件生产环境DEBUGFalse且把SECRET_KEY放到环境变量里读取这算是我踩过很多次坑才养成的好习惯。