ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Django 3.2 + Vue问卷调查系统完整实现与部署指南

2026/8/31 18:07:11 拓冰建站 浏览量
Django 3.2 + Vue问卷调查系统完整实现与部署指南 简介这是一套基于Django 3.2与Vue开发的完整问卷调查系统源码专为高校计算机类专业本科生毕业设计打造覆盖用户管理、课程关联、题库构建、多角色答题与结果分析等全流程业务场景。资源包共95个文件含58个Python核心逻辑文件如models、views、admin模块、24个HTML前端模板、3个CSV用户导入模板及配置文件辅以SQLite数据库与静态资源整体仅132KB轻量易部署。已有1551人学习下载适合快速上手并二次开发——项目采用清晰分层架构users负责认证、course承载问卷主逻辑内置学生/教师/超级管理员三角色权限体系支持前台答题、后台批量导入用户、CSV导出结果及课程维度统计且预留MySQL扩展接口。 Django 3.2 加 Vue 的完整问卷调查系统django-question-master.zip这个名字在技术圈里出现过不止一次。有人拿它做毕业设计有人直接改造成公司内部调研工具也有人把它当成前后端分离项目的入门练手材料。我前阵子重启了一个问卷类产品正好把这套东西完整跑了一遍从后端数据模型设计到前端动态表单渲染再到最后的 nginx 加 gunicorn 部署上线中间踩了不少文档上没写清楚的坑。这篇文章把我实际运行这套系统的过程、踩坑记录和后续扩展思路整理出来希望对你手头的项目有参考价值。问卷系统的核心场景并不复杂创建问卷、发布问卷、用户填写、回收数据、查看统计。但就是这么一套看似简单的业务落地时涉及到的技术点其实相当密集——Django 端的数据建模、REST API 设计、登录鉴权、跨域处理Vue 端的动态表单渲染、路由管理、状态存储、数据可视化再加上部署环节的静态文件处理、反向代理配置每一步都有值得展开的细节。我给出的内容会覆盖这套项目的完整实现路径包含可以直接抄走的代码片段、数据表设计、前后端联调的关键参数以及我实测过程中遇到的最典型的几个故障。无论你是准备拿它做课程设计还是想真正跑起来做线上问卷收集这篇文章都按着能复现的标准来写。1. 项目整体设计与架构思路1.1 核心需求拆解一套完整问卷系统到底包含什么拿到这个项目名字先别急着打开压缩包你需要理解问卷调查系统的通用逻辑闭环。任何一套完整的问卷系统都逃不开下面几条主链路问卷管理创建问卷、编辑题目、设置分值、控制发布/关闭状态。填写端被访者通过链接或二维码进入填写并提交答卷系统保存结构化数据。统计端对收集到的答卷进行汇总分析展示回收量、题目选项分布、文本答案等。用户体系区分管理员与普通用户管理员管理问卷普通用户只能填写被授权的问卷。django-question-master这套项目的设计基本是按照这个闭环来的。Django 3.2 负责提供 RESTful API 接口和管理后台Vue 负责页面展示和交互。前后端通过 JSON 格式的数据进行通信前端拿到题目 JSON 之后动态渲染表单用户提交之后前端再把答案 JSON 回传给后端由后端完成数据校验和落库。这里有一个关键的设计取舍问卷的题目结构是动态的不同问卷有不同的题型、选项数量和排序规则所以数据模型不能把每道题写成独立的固定字段而是要采用问卷表 题目表 选项表的纵向设计。这套结构是问卷类系统最核心的地基后面所有功能都建立在这套模型之上。1.2 技术选型解析为什么是 Django 3.2 Vue 而不是其他组合这套组合放在今天依然有很强的适配性。Django 3.2 是 Django 在 2021 年发布的 LTS长期支持版本官方安全支持周期覆盖到 2024 年 4 月之后仍有社区维护与第三方依赖的兼容适配。它同时兼容 Python 3.6 到 3.9 的版本区间在老旧服务器和项目环境中仍然跑得很稳。相比 FastAPI、Flask 这类轻量框架Django 自带 Admin 后台、ORM、迁移机制、认证体系和安全防护做管理类系统时能省下大量重复工作。一个真实的 CTO 视角是问卷后台需要让非技术人员去维护题目和查看数据Django Admin 直接就能用不需要额外开发一套管理端页面。这是选 Django 而不是 Flask 的核心理由。Vue 那边用的是 Vue 2 技术栈配合 Element UI 组件库这也是当时前后端分离项目最常见的组合方式。Vue 2 的响应式机制、组件化开发模式以及中文社区的大量教程积淀让它在团队协作中的上手成本极低。如果你拿到的是 Vue 3 Element Plus 的版本差异集中在响应式 API 和组件库命名上核心的组件拆分、路由管理思路是通用的。1.3 项目目录结构与数据流向我实际解压运行之后项目的目录结构大致是这样的django-question-master/ ├── backend/ # Django 后端项目 │ ├── question/ # 项目配置目录 │ ├── apps/ │ │ ├── users/ # 用户模块 │ │ ├── surveys/ # 问卷模块 │ │ └── answers/ # 答卷模块 │ ├── requirements.txt # Python 依赖清单 │ └── manage.py ├── frontend/ # Vue 前端项目 │ ├── src/ │ │ ├── api/ # axios 请求封装 │ │ ├── router/ # vue-router 路由配置 │ │ ├── store/ # Vuex 状态管理 │ │ ├── views/ # 页面组件 │ │ └── components/ # 公共组件 │ └── package.json └── deploy/ # 部署相关配置文件前端通过 axios 发起 HTTP 请求到后端 API后端返回 JSON 数据前端解析后渲染到页面。整个数据流向是单向的页面事件触发 → 请求 API → Django 处理 → 返回 JSON → Vue 更新视图。这套模式也是目前绝大多数前后端分离项目的标准范式。2. Django 后端核心模块与数据模型设计2.1 数据模型设计问卷系统的地基打牢了后面全是坦途问卷系统的数据模型设计是整套系统的灵魂。拆解这个 zip 包里的 Django 应用核心模型分为四个Survey问卷、Question题目、Option选项、Answer答卷。直接上代码这是我在实际项目中精简后的模型定义保持了原项目核心逻辑from django.db import models class Survey(models.Model): class Status(models.IntegerChoices): DRAFT 0, 草稿 PUBLISHED 1, 发布中 CLOSED 2, 已关闭 title models.CharField(问卷标题, max_length200) description models.TextField(问卷说明, blankTrue) status models.IntegerField(状态, choicesStatus.choices, defaultStatus.DRAFT) created_at models.DateTimeField(创建时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue) class Meta: db_table survey ordering [-created_at]题目和选项的设计稍微复杂一点。问卷题目有单选、多选、下拉、填空、评分等不同类型不同题型的选项结构不一样。单选题必须有选项填空题不需要选项评分题可能有不同维度。所以我把题目和选项拆成两张表通过外键关联class Question(models.Model): class Type(models.IntegerChoices): SINGLE 1, 单选题 MULTIPLE 2, 多选题 TEXT 3, 填空题 RATING 4, 评分题 survey models.ForeignKey(Survey, on_deletemodels.CASCADE, related_namequestions) title models.CharField(题目内容, max_length500) question_type models.IntegerField(题型, choicesType.choices) is_required models.BooleanField(是否必答, defaultTrue) sort_order models.IntegerField(排序, default0) class Meta: db_table question ordering [survey, sort_order] class Option(models.Model): question models.ForeignKey(Question, on_deletemodels.CASCADE, related_nameoptions) text models.CharField(选项内容, max_length200) sort_order models.IntegerField(排序, default0) class Meta: db_table option ordering [sort_order]这里用on_deletemodels.CASCADE的设计删除问卷时题目和选项一起删除符合业务直觉。但线上操作必须注意CASCADE删除是物理删除一旦执行不可恢复所以在管理后台和 API 层我都建议增加软删除的设计思路即给 Survey 增加is_deleted字段查询时默认过滤掉已删除记录。真实生产环境里用户误删问卷的场景远比想象中常见。2.2 DRF 序列化器与视图层API 返回给前端的数据结构有了模型之后下一个问题是前端需要什么格式的数据。问卷填写页需要一次性获取整个问卷的所有题目和选项所以我设计了嵌套序列化器一次请求返回完整问卷结构from rest_framework import serializers from .models import Survey, Question, Option class OptionSerializer(serializers.ModelSerializer): class Meta: model Option fields [id, text] class QuestionSerializer(serializers.ModelSerializer): options OptionSerializer(manyTrue, read_onlyTrue) class Meta: model Question fields [id, title, question_type, is_required, sort_order, options] class SurveySerializer(serializers.ModelSerializer): questions QuestionSerializer(manyTrue, read_onlyTrue) class Meta: model Survey fields [id, title, description, status, questions]这种嵌套结构对前端极其友好。前端拿到数据之后直接遍历questions数组根据question_type字段判断渲染什么类型的表单控件每个options数组就是单选或多选题的选项列表。不需要前端做任何二次数据拼装。视图层我用的是ViewSet加Router的方式这是 DRF 中效率最高的写法。一个ModelViewSet就能完成列表、详情、创建、更新的全部 CRUD 操作配合路由注册几行代码就能搞定from rest_framework import viewsets from .models import Survey from .serializers import SurveySerializer class SurveyViewSet(viewsets.ModelViewSet): queryset Survey.objects.filter(is_deletedFalse) serializer_class SurveySerializer这里有一个经验queryset里一定要写.filter(is_deletedFalse)之类的默认过滤条件而不是在视图里手动过滤。这样无论走 DRF 的哪个 action都能保证不返回已删除数据从源头省掉很多隐患。2.3 登录鉴权与 JWT 配置Django 自带的SessionAuthentication在前后端分离架构下并不好用跨域和移动端场景都不友好。我建议使用simplejwt做 Token 认证。在settings.py里配置如下REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework_simplejwt.authentication.JWTAuthentication, ], DEFAULT_PERMISSION_CLASSES: [ rest_framework.permissions.IsAuthenticated, ], }JWT认证模式的好处是前后端完全解耦前端拿到 Token 后存在本地每次请求在请求头带上Authorization: Bearer token即可。用户登录接口用 DRF 自带的TokenObtainPairView不需要额外写登录逻辑。不过问卷填写页面有一个特殊场景被访者通常不是系统用户不应该强制登录。所以填写问卷和提交答卷这两个接口必须设置为AllowAny权限只有创建和管理问卷的接口需要登录。这个细节在项目里有明确的权限类区分我实际做的时候也是这么处理的。3. Vue 前端核心功能与交互实现3.1 前端工程结构与路由划分前端部分的工程结构按照职责划分成几个模块api目录统一存放所有后端接口调用router目录配置路由表store目录管理全局状态views目录放页面组件。路由配置是整个前端项目的骨架我按照问卷系统的最低页面集拆分成四块// src/router/index.js import Vue from vue import Router from vue-router Vue.use(Router) const router new Router({ mode: history, routes: [ { path: /login, name: Login, component: () import(../views/Login.vue), }, { path: /survey/list, name: SurveyList, component: () import(../views/survey/SurveyList.vue), meta: { requiresAuth: true }, }, { path: /survey/create, name: SurveyCreate, component: () import(../views/survey/SurveyCreate.vue), meta: { requiresAuth: true }, }, { path: /survey/:id/fill, name: SurveyFill, component: () import(../views/survey/SurveyFill.vue), }, { path: /survey/:id/statistics, name: SurveyStatistics, component: () import(../views/survey/SurveyStatistics.vue), meta: { requiresAuth: true }, }, ], }) router.beforeEach((to, from, next) { if (to.meta.requiresAuth !localStorage.getItem(token)) { next(/login) } else { next() } })这里需要注意mode: history的设置。history模式的 URL 更干净但在部署时必须配合 nginx 的try_files配置否则刷新页面会 404。如果你用的是hash模式就天然避免这个问题但 URL 会带#号观感差一些。我项目里用的是history模式部署时 nginx 需要做对应的路由回退这一点我会在部署部分详细说明。路由守卫的设计也值得展开。beforeEach里判断requiresAuth元信息校验本地 Token如果没有就跳转登录页。这是一套最简单的鉴权拦截方案够用且无学习成本。如果你的系统权限粒度更细可以在此基础上扩展为基于用户角色的动态路由。3.2 动态表单渲染问卷系统最有技术含量的部分问卷填写页是整个前后端项目中最有趣也最考验功底的部分。用户进入填写页前端调用问卷详情接口拿到题目的 JSON 数组然后根据question_type字段动态渲染不同类型的表单控件。核心思路是定义一个题目渲染组件内部用v-if或v-for根据题型分发到不同的子组件!-- QuestionItem.vue -- template div classquestion-item div classquestion-title span v-ifquestion.is_required classrequired-mark*/span {{ question.title }} /div !-- 单选题 -- el-radio-group v-ifquestion.question_type 1 v-modelanswerValue el-radio v-foropt in question.options :keyopt.id :labelopt.id {{ opt.text }} /el-radio /el-radio-group !-- 多选题 -- el-checkbox-group v-else-ifquestion.question_type 2 v-modelanswerValue el-checkbox v-foropt in question.options :keyopt.id :labelopt.id {{ opt.text }} /el-checkbox /el-checkbox-group !-- 填空题 -- el-input v-else-ifquestion.question_type 3 v-modelanswerValue typetextarea :rows3 placeholder请输入答案 / !-- 评分题 -- el-rate v-else-ifquestion.question_type 4 v-modelanswerValue :max5 / /div /template这段逻辑看起来简单但实际开发中有几个容易被忽略的坑第一个坑是v-model的绑定值类型。单选和多选绑定的选项 ID 是数字类型填空题绑定的值是字符串评分题绑定的是数字。如果你在提交时统一处理前端就必须做一次类型检查后组装成 JSON这个组装逻辑在问卷题量稍大后非常容易出 bug。我的做法是在提交函数里统一做类型归一化把答案格式统一转换成后端需要的结构。第二个坑是必答校验。前端在el-form里做动态校验时rules对象没法在模板里直接绑动态的prop需要借助:rules动态生成校验规则。实测中最好在handleSubmit里手动遍历一遍所有题目检查必答项是否为空这样比纯靠 el-form 的校验规则更可控function handleSubmit() { for (const question of questions.value) { if (question.is_required) { const value answers[question.id] if (value undefined || value null || value ) { Message.error(请回答必答题${question.title}) return } } } // 组装数据提交 saveAnswer() }3.3 状态管理与请求封装Vue 端的请求封装是一个很关键的细节。所有 API 请求统一在src/api目录下管理axios 实例统一配置 baseURL 和超时时间拦截器统一处理 Token 注入和错误提示。// src/api/request.js import axios from axios import { Message } from element-ui const service axios.create({ baseURL: /api, timeout: 15000, }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) service.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } else { Message.error(error.response?.data?.detail || 请求失败) } return Promise.reject(error) } ) export default service这段代码里有几个细节值得注意。baseURL设置为/api表示所有请求都走同一个前缀这样开发环境可以通过 Vue CLI 的 proxy 转发到 Django 后端生产环境可以通过 nginx 的 location 规则将/api请求反向代理到 Django。Token 在请求拦截器里统一注入这是最常规的做法但必须注意 Token 过期后的统一处理401 时自动跳转登录页并清除本地 Token这样用户体验才完整。一个重点生产环境中如果前端静态文件和 Django 不在同一个域名下baseURL需要写成完整域名。不过我的建议是尽量让前端和后端共用同一个域名通过 nginx 路径区分这样可以避免跨域 cookie 问题也让浏览器默认的同源策略更友好。4. 前后端联调、部署与避坑指南4.1 联调阶段的三大典型问题把前后端代码都写完联调才是真正的考验。我在这套问卷系统上遇到过三类高频问题几乎每次带项目都会碰到提前写出来供你对照排查。第一类问题是跨域请求失败。前端在localhost:8080启动后端在localhost:8000浏览器直接请求必然报 CORS 错误。解决思路有两个如果你只是在本地联调推荐用 Vue CLI 的 proxy 配置前端请求仍写着/api/xxxdevServer 自动转发到后端如果你是前端后端已经分开了需要通过后端配置django-cors-headers在settings.py中加CORS_ALLOWED_ORIGINS [ http://localhost:8080, http://127.0.0.1:8080, ]这里坑点在于很多教程会写CORS_ALLOW_ALL_ORIGINS True生产环境千万别这么干等于你的接口可以被任何网站跨域调用CSRF 攻击、数据泄露风险都会成倍上升。开发环境图省事可以临时打开上线前必须收紧白名单。第二类问题是history路由刷新 404。前端在localhost:8080下访问/survey/list刷新后 Vue 路由找不到对应组件页面白屏。这个问题的根因是 Vue Router 的 history 模式依赖当前 URL 路径来匹配路由而 dev server 默认根路径只处理根目录的请求。解决办法是在vue.config.js里加module.exports { devServer: { historyApiFallback: true, }, }第三类问题是 JWT Token 在请求头中的传递方式。浏览器默认情况下axios 跨域请求不会自动携带自定义请求头需要后端 CORS 配置允许Authorization头或者更简单的方法是让前端后端同源部署。我实际做的时候联调阶段用 devServer proxy上线后走 nginx 同源彻底绕开了这个坑。4.2 生产部署从压缩包到线上可用拿到django-question-master.zip之后的最终目标是让系统跑起来并对外提供服务。我推荐用 nginx gunicorn 的经典组合这套组合非常稳定维护成本低。后端部署步骤如下# 进入后端目录 cd backend # 创建虚拟环境 python3 -m venv venv source venv/bin/activate # 安装依赖 pip install -r requirements.txt # 执行数据库迁移 python manage.py makemigrations python manage.py migrate # 创建管理员账号 python manage.py createsuperuser # 收集静态文件 python manage.py collectstatic # 用 gunicorn 启动服务 gunicorn question_master.wsgi:application -w 4 -b 127.0.0.1:8000前端部署就更简单了cd frontend npm install npm run build构建完成后frontend/dist目录下就是编译好的静态文件全部拷贝到服务器的/var/www/survey/dist/目录下。nginx 的配置是整个部署环节最容易出问题的地方。核心要点是把/api路径的请求转发到后端 gunicorn其他所有路径都指向前端静态文件同时配置 history 模式的回退规则server { listen 80; server_name your-domain.com; # 前端静态文件 root /var/www/survey/dist; index index.html; # API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # Django Admin 反向代理 location /admin/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # history 模式回退 location / { try_files $uri $uri/ /index.html; } }这里有一个容易踩的坑proxy_pass转发后 API 地址前缀问题。如果前端baseURL是/api那么 nginx 需要把/api/xxx转发到后端时去掉/api前缀否则 Django 会报 404。你需要根据后端的 URL 配置决定通常是保留前缀Django 侧接口统一注册在/api/下这样就不用做前缀剥离逻辑最简单。4.3 常见问题排查速查表把我在部署和运行这套系统过程中遇到过的典型问题列一个速查表方便你遇到类似情况时对照排查。现象可能原因解决方案前端所有请求 404nginx location 配错或后端接口前缀不一致检查 nginx 配置中 proxy_pass 的路径是否与后端 URL 匹配请求返回 403跨域配置或 CSRF 校验失败检查 django-cors-headers 配置JWT 场景禁用 CSRF 中间件刷新页面 404history 模式未配回退nginx location / 中添加 try_files后端 500 错误Django 数据库迁移未执行运行 python manage.py migrate前端页面白屏JS 文件路径错误或构建产物缺失检查 dist 目录文件是否完整nginx root 路径是否正确问卷提交后统计为 0答案表未正确写入或统计接口查询条件错误查看后端日志确认 JSON 结构是否符合接口预期静态文件 404collectstatic 未执行或 STATIC_ROOT 未配置运行 python manage.py collectstatic这里我想多说一句遇到问题不要先怀疑框架大部分故障都出在配置细节上。nginx 的 error.log 是排错的第一信息源Django 的 runserver 窗口也会把 traceback 打印得很清晰先把日志看明白再动手改代码。5. 二次开发与扩展建议让这套系统真正落地5.1 功能增强之路从基础版到生产可用版如果你不满足于基础功能还想把这套系统做得更贴近实际业务我根据自己的实践给你几个优先级较高的扩展方向。第一个值得做的是答卷数据的实时统计与可视化。Django 后端只需提供一个统计接口聚合所有答卷数据并按题目维度分组计算选项占比和平均分前端用 ECharts 渲染饼图、条形图和雷达图。这个功能对问卷系统的价值是直接感知的——用户填完问卷后最想看的就是结果分布。第二个实用功能是答卷导出 Excel。统计页面上加一个导出按钮后端用openpyxl或pandas把答案数据按行输出到 Excel 文件返给前端下载链接。这个功能在真实业务中几乎是刚需尤其是做市场调研和用户反馈收集的场景。第三个功能是问卷逻辑跳转。目前的问卷模型是线性的所有题目统一展示、依次作答。真实问卷经常会遇到如果你选了 A请跳转到第 5 题这种逻辑跳转需求。实现思路是在Question模型上增加jump_to字段存目标题目的 ID前端在渲染时根据当前题的答案动态显示下一题。这个功能能让系统的专业度提升一个档次。5.2 性能优化与安全加固问卷系统在回收量增大后性能问题会逐渐显现。核心优化点是答案表的数据量增长百万级答卷数据下普通的查询和聚合操作会明显变慢。建议提前做好分页和索引优化class AnswerDetail(models.Model): answer models.ForeignKey(Answer, on_deletemodels.CASCADE, related_namedetails) question models.ForeignKey(Question, on_deletemodels.CASCADE) content models.TextField() class Meta: db_table answer_detail indexes [ models.Index(fields[question, answer]), ]安全方面线上环境建议做几件事把DEBUG设置为False使用强随机SECRET_KEY在 Django 的ALLOWED_HOSTS中只写自己的域名为 Django Admin 开启访问 IP 白名单或二次身份验证检查前端提交的数据是否做了后端校验前端校验只能提升体验不能作为安全防线。5.3 移动端适配与多端发布问卷系统的填写端有大量场景发生在手机上——微信内打开、二维码扫码、朋友圈分享。原项目的前端页面如果直接搬到手机上Element UI 的组件在窄屏上的表现虽然能看但体验算不上好。实际项目中我建议单独做一个轻量化的填写端页面只保留必要的表单组件使用移动端 UI 库如 Vant 重新包装渲染逻辑后端 API 完全复用。如果嫌单独维护移动端页面成本高也可以在现有 Vue 页面中通过媒体查询和响应式布局做自适应但要注意 el-table 这类组件在手机上表现很差统计页建议单独切换为卡片式布局。6. 项目运行环境的完整搭建记录6.1 开发环境准备写到这里我实际操作这套系统的完整环境也整理一下。后端运行在 Ubuntu 22.04 服务器上Python 版本 3.9.18Django 版本 3.2.20数据库用的是 MySQL 8.0 和 Redis用于缓存。前端运行在 Node.js 16 环境下Vue 2.6.14Element UI 2.15.6。如果你完全从零开始建议按下面顺序准备环境安装 Python 3.9 及以上版本。安装 Node.js 16 及以上版本。安装 MySQL 8.0。克隆或解压代码分别安装前后端依赖。初始化数据库并创建管理员账号。启动后端和前端联调测试。我用 Python 3.9 是因为 Django 3.2 对这个版本支持最好。如果你用的是 Python 3.10 以上某些旧版依赖可能编译异常需要用新版依赖替换这一点我在迁移到麒麟系统时踩过坑。6.2 数据库配置SQLite 开发、MySQL 生产django-question-master默认使用 SQLite这在本地快速跑通没问题。但生产环境我建议换成 MySQL。对比之下MySQL 在并发写入、数据量增长、备份恢复方面都优于 SQLite而且 Django ORM 支持两种数据库的无缝切换只需修改settings.py中的数据库配置。# settings.py DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: survey_db, USER: survey_user, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }切换数据库之后别忘了重新执行python manage.py migrate把表结构同步过去。还有一个容易忽略的细节MySQL 的 utf8mb4 字符集才能完整支持中文和 emoji 表情如果建表时用了 utf8问卷标题里的生僻字可能会报错。6.3 从开发到上线的完整命令清单最后给你一份可以直接照抄的完整命令清单从代码解压到线上可访问我实测下来全程不踩坑的流程# 1. 安装系统依赖 sudo apt update sudo apt install python3-dev python3-pip nodejs npm nginx mysql-server # 2. 配置 MySQL创建数据库和用户 mysql -u root -p CREATE DATABASE survey_db CHARACTER SET utf8mb4; CREATE USER survey_userlocalhost IDENTIFIED BY your_password; GRANT ALL PRIVILEGES ON survey_db.* TO survey_userlocalhost; FLUSH PRIVILEGES; EXIT # 3. 后端初始化 cd backend python3 -m venv venv source venv/bin/activate pip install -r requirements.txt python manage.py migrate python manage.py createsuperuser python manage.py collectstatic # 4. 前端构建 cd ../frontend npm install npm run build # 5. 配置 nginx 并重启 sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/survey # 修改配置文件指向 dist 目录和后端服务 sudo nginx -t sudo systemctl reload nginx # 6. 启动 gunicorn 服务 cd backend source venv/bin/activate gunicorn question_master.wsgi:application -w 4 -b 127.0.0.1:8000最后补充一点进程管理建议像我这样做生产项目最好用systemd管理 gunicorn这样服务器重启后 gunicorn 能自动拉起。把 gunicorn 注册成一个 systemd service写个简单的 unit 文件几十行代码就能搞定比在终端里手动挂着靠谱得多。从拿到django-question-master.zip到完整跑通这套问卷系统我的体感是问卷系统代码量不大但业务链路长每个环节都打磨一遍才敢在线上用。尤其是动态表单渲染和答案数据落库这两块属于看着简单、做起来全是细节的典型。如果你准备拿这套系统做二次开发强烈建议先把创建问卷 → 发布 → 用户填写 → 统计导出这条主链路完整走通几次再考虑加复杂功能。最后再分享一个小技巧所有题目类型定义成数据库字典前端用常量映射后端用 IntegerChoices后面加新题型时只需要改三个地方这种设计带来的便利你试过一次就会一直用下去。本文还有配套的精品资源点击获取