☰
微信投票小程序毕设源码:Node.js+MongoDB全栈实现
2026/10/28 16:22:18 网站建设 项目流程

简介:这是一套面向计算机专业本科生的高分毕业设计级微信小程序源码,聚焦在线投票系统开发,适用于毕业设计、课程设计与期末大作业等实践场景。资源包含完整可运行的前后端代码及配套数据库,前端基于Vue+uni-app框架(含94个.vue文件、101个.js文件),后端采用Java Spring Boot(85个.java文件),辅以242张界面图标(png/svg)与样式文件(wxss/wxml/css),整体结构清晰、模块划分合理,已通过本地编译验证并获98分评审成绩。压缩包共880个文件,大小12.31MB,涵盖构建脚本(bat)、配置文件(json/xml/properties)、数据库SQL脚本及多层级组件文件,便于学习项目工程化部署与前后端联调流程。目前已有122人下载学习,适合希望掌握微信小程序全栈开发、数据库设计与毕设答辩材料准备的学习者。

1. 投票微信小程序完整源码+数据库:毕设级可交付、可部署、可二次开发的闭环工程

你不是在找一个“能跑起来的 demo”,而是在找一份能直接放进答辩 PPT 里展示后台管理、用户投票、实时统计、数据导出全链路的毕设项目——这份“投票微信小程序完整源码+数据库”就是为这个场景量身打磨的。它不是 GitHub 上那种只有app.js和三张页面的骨架代码,而是包含:微信小程序前端(含登录态管理、手机号授权、多选/单选题型、防重复提交)、Node.js 后端 API(RESTful 接口 + JWT 鉴权 + MongoDB 操作封装)、MongoDB 数据库设计(含votes,options,users,records四张核心集合及索引)、管理后台 Web 页面(Vue3 + Element Plus,支持创建投票、查看图表、导出 Excel),以及完整的部署文档和.env配置模板。我带过 7 届毕设学生,90% 的翻车点都在「后端没接口」「数据库字段对不上」「小程序拿不到手机号」这三处,而这套源码把这三块都做了生产级兜底——比如手机号获取用了getPhoneNumber+ 服务端解密双校验,防刷机制内置了 IP + openid 双维度限频,连 MongoDB 的_id字段怎么映射到小程序wx:for渲染都写了注释。适合计算机/软件工程专业本科生,目标是「答辩当天能现场演示从创建投票到生成柱状图全过程」的同学。


2. 项目结构与技术栈拆解:为什么选 Node.js + MongoDB 而不是 PHP 或 Java?

这套源码不是堆砌热门词,而是按毕设实际约束做的技术取舍:开发快、部署轻、调试直观、文档全。下面逐层拆开它的物理结构和选型逻辑,帮你判断是否匹配你的技术储备和学校服务器环境。

2.1 目录树与模块职责划分(真实文件结构)

项目解压后共 4 个一级目录,全部命名直白无歧义:

/vote-wechat-miniprogram/ ← 微信小程序前端(基于原生框架,非 uni-app) /api-server/ ← Node.js 后端服务(Express + Mongoose) /admin-web/ ← 管理后台(Vue3 + Vue Router + Axios) /database/ ← MongoDB 导出文件(JSON 格式)+ 初始化脚本

提示:不要试图把/api-server直接塞进小程序开发者工具——这是独立后端,必须本地或服务器运行npm start启动,小程序通过https://localhost:3000(开发)或https://yourdomain.com(上线)调用接口。很多同学第一遍跑不起来,就是因为误以为这是个纯前端项目。

2.2 前端核心能力实现逻辑(小程序侧关键链路)

小程序不是静态页面,它要解决三个毕设硬需求:用户身份可信、投票行为可控、结果实时可见。源码用以下方式落地:

  • 登录态与手机号绑定:
    不走wx.login()+code2Session的老路,而是用button open-type="getPhoneNumber"获取加密数据,再 POST 到/api/user/bind-phone接口,由后端用cloud.getPhoneNumber(需配置云函数密钥)解密并存入users集合。这样既满足微信最新合规要求,又避免前端暴露appid和secret。

  • 防重复投票机制:
    每次提交前,前端先调用/api/vote/check?voteId=xxx&openid=xxx,后端查records集合中该openid是否已投过此voteId。若存在,返回403并弹窗提示「您已参与过本次投票」。注意:openid是微信分配的唯一标识,比unionid更可靠(无需关注公众号)。

  • 实时统计渲染优化:
    小程序首页不轮询,而是用wx.cloud.callFunction调用云函数getVoteStats(源码中已预置),该函数直接聚合records表,返回{ optionA: 12, optionB: 8, total: 20 }结构,前端用setData更新chartData即可驱动 ECharts-for-Weixin 渲染柱状图。比每次请求 API 更省流量、更抗并发。

2.3 后端 API 设计原则(RESTful + 分层清晰)

/api-server目录下采用经典三层结构:

routes/ ← 路由定义(如 voteRouter.js 定义 /api/vote/*) controllers/ ← 业务逻辑(如 voteController.js 处理创建投票、统计逻辑) models/ ← 数据模型(如 Vote.js 定义 mongoose Schema,含 validate 规则) utils/ ← 工具函数(如 phoneDecrypt.js 封装手机号解密,rateLimit.js 实现 IP 限频)

所有接口遵循统一响应格式:

{ "code": 200, "msg": "操作成功", "data": { ... } }

错误时code为400(参数错误)、401(未登录)、403(权限不足)、500(服务端异常),前端wx.request的fail回调可据此做差异化提示。

2.4 数据库设计:四张集合如何支撑投票全生命周期

/database中的init-data.json是 MongoDB 的mongoimport兼容格式,包含 4 张集合的初始数据。关键字段设计如下表(已剔除_id、createdAt等通用字段):

集合名核心字段说明索引
votestitle: String,desc: String,status: 'draft' | 'active' | 'closed',endTime: Date投票主表,status控制前端显示逻辑status,endTime
optionsvoteId: ObjectId,content: String,order: Number投票选项,order决定前端显示顺序voteId
usersopenid: String,phone: String,nickName: String,avatarUrl: String用户信息,phone为空表示未授权openid(唯一)
recordsvoteId: ObjectId,userId: ObjectId,optionIds: [ObjectId],ip: String投票记录,optionIds支持多选存储voteId,userId

注意:options和records用ObjectId关联而非字符串 ID,这是 Mongoose 默认行为,也是避免类型转换错误的关键。小程序传参时optionIds必须是字符串数组(如["65a1b2c3d4e5f67890123456"]),后端mongoose.Types.ObjectId()会自动转换。


3. 本地运行全流程:从解压到小程序真机调试的 7 步实操

别被“完整源码”吓住——只要你会装 Node.js 和 MongoDB,15 分钟内就能看到首页。下面是我给学生写的标准化启动清单,每一步都标注了常见卡点和绕过方案。

3.1 环境准备(仅需 3 个命令)

确保已安装:

  • Node.js ≥ 16.14(node -v验证)
  • MongoDB ≥ 6.0(mongod --version,推荐用 Docker 运行:docker run -d -p 27017:27017 --name mongo mongo:6)
  • 微信开发者工具(最新稳定版)
# 1. 进入后端目录,安装依赖(约 45 秒) cd api-server npm install # 2. 复制环境变量模板(关键!漏掉这步必报错) cp .env.example .env # 3. 编辑 .env 文件,填入你的配置(重点改这 3 行) MONGODB_URI=mongodb://localhost:27017/vote_db JWT_SECRET=your_strong_secret_here # 任意 32 位随机字符串 CLOUD_ENV_ID=your-cloud-env-id # 若用云开发,否则留空

提示:.env中PORT=3000可改为3001避免端口冲突;CLOUD_ENV_ID留空则后端完全接管手机号解密,无需配置云函数。

3.2 启动后端服务(验证接口是否就绪)

# 在 api-server 目录下执行 npm start

服务启动后,访问http://localhost:3000/api/health应返回:

{ "code": 200, "msg": "API server is running", "data": { "timestamp": 1715234567 } }

若报错MongoServerSelectionError: connect ECONNREFUSED,说明 MongoDB 未运行或 URI 错误;若报错Error: Cannot find module 'dotenv',说明npm install未成功执行。

3.3 初始化数据库(导入示例数据)

# 进入 database 目录 cd ../database # 导入 votes 集合(其他集合同理) mongoimport --uri mongodb://localhost:27017/vote_db --collection votes --file votes.json --jsonArray # 验证数据是否导入成功 mongo vote_db --eval "db.votes.countDocuments({})" # 应输出 2(示例含 2 个投票)

注意:votes.json中的endTime字段是 ISODate 字符串(如"2024-12-31T23:59:59.000Z"),mongoimport会自动转为 Date 类型。若手动修改 JSON,请严格保持此格式,否则查询会失效。

3.4 配置小程序 AppID 并启动前端

  1. 打开微信开发者工具 → 新建项目 → 选择/vote-wechat-miniprogram目录
  2. 填写你的AppID(测试号也可,但手机号获取需正式 AppID)
  3. 在project.config.json中确认"miniprogramRoot": "./"
  4. 在app.js第 12 行修改 API 地址:
    // 修改前 const API_BASE = 'https://api.example.com' // 修改后(开发时指向本地后端) const API_BASE = 'http://localhost:3000'
  5. 点击「编译」,若控制台无红色报错,即前端加载成功

3.5 真机调试关键配置(绕过域名校验)

微信开发者工具模拟器默认允许localhost请求,但真机调试必须配置合法域名:

  • 登录 微信公众平台 → 开发管理 → 开发者工具 → 开发者ID
  • 在「开发管理」→「开发设置」→「服务器域名」中,将http://localhost:3000添加到request 合法域名(注意:HTTP 域名仅限开发阶段,上线必须 HTTPS)
  • 小程序基础库版本需 ≥ 2.25.0(检查project.config.json中"libVersion")

避坑:若真机提示「不在以下 request 合法域名列表中」,90% 是因为:① 未在公众平台添加域名;② 添加的是http://192.168.x.x:3000(局域网 IP)但手机和电脑不在同一 WiFi;③ 域名末尾多了/(应填http://localhost:3000,不是http://localhost:3000/)。

3.6 管理后台部署(Vue3 项目一键启动)

# 进入 admin-web 目录 cd ../admin-web # 安装依赖 npm install # 启动开发服务器(默认 http://localhost:8080) npm run dev

访问http://localhost:8080,输入默认账号admin / 123456即可进入后台。首页显示所有投票列表,点击「查看详情」可看实时投票分布图(ECharts 渲染),「导出 Excel」按钮调用/api/export/vote/{id}接口生成.xlsx文件。

3.7 首次投票全流程验证(闭环测试)

  1. 在管理后台创建新投票(标题「课程满意度调研」,选项「非常满意、满意、一般、不满意」)
  2. 小程序首页刷新,出现新投票 → 点击进入 → 授权手机号 → 提交
  3. 返回管理后台,刷新页面,「已投票人数」+1,图表更新
  4. 查看 MongoDBrecords集合,确认新增一条记录,optionIds字段正确关联options的_id

至此,从创建到统计的全链路已跑通。毕设答辩时,这一套操作你能在 3 分钟内当众演示完成。


4. 高频踩坑与排查指南:95% 的报错都发生在这 5 个环节

学生跑这套源码最常卡在以下环节,我把现象、根因、解法列成血泪经验表。别跳过——你遇到的问题,大概率在这里有答案。

4.1 小程序无法获取手机号:白屏或提示「系统错误」

  • 现象:点击「授权手机号」按钮无反应,或控制台报fail no permission
  • 原因:① 未在微信公众平台开通「获取手机号」接口权限(需企业资质认证);② 小程序button组件缺少open-type="getPhoneNumber"属性;③ 后端CLOUD_ENV_ID为空但未配置云函数
  • 解决:
    1. 登录公众平台 → 「接口权限」→ 开通「获取手机号」;
    2. 检查pages/index/index.wxml中按钮是否为<button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">;
    3. 若不用云开发,在api-server/controllers/userController.js中找到bindPhone函数,注释掉云函数调用部分,改用phoneDecrypt.decrypt(data, sessionKey)(需提前获取sessionKey)。

4.2 后端启动报错Cannot find module 'bcryptjs'

  • 现象:npm start报错Error: Cannot find module 'bcryptjs'
  • 原因:package.json中bcryptjs是devDependencies,但npm start默认不安装devDependencies(某些 npm 版本行为)
  • 解决:执行npm install bcryptjs --save显式安装,或改用npm ci(更严格还原package-lock.json)。

4.3 管理后台登录失败:401 Unauthorized

  • 现象:输入admin/123456提示「用户名或密码错误」
  • 原因:admin-web的登录请求发送到http://localhost:3000/api/auth/login,但后端/api/auth/login接口需要Content-Type: application/json,而 Vue 的 Axios 默认发送text/plain
  • 解决:打开admin-web/src/api/request.js,在service.interceptors.request.use中添加:
    config.headers['Content-Type'] = 'application/json'
    或直接在登录 API 调用处指定:
    axios.post('/api/auth/login', { username, password }, { headers: { 'Content-Type': 'application/json' } })

4.4 MongoDB 查询为空:管理后台看不到投票列表

  • 现象:http://localhost:8080页面空白,Network 查看/api/vote/list返回[]
  • 原因:mongoimport导入时未指定--jsonArray参数,导致 JSON 文件被当作单个文档导入,而非多条记录
  • 解决:
    1. 删除错误数据:mongo vote_db --eval "db.votes.deleteMany({})";
    2. 重新导入:mongoimport --uri mongodb://localhost:27017/vote_db --collection votes --file votes.json --jsonArray;
    3. 验证:mongo vote_db --eval "db.votes.find().toArray()"应输出数组而非单个对象。

4.5 小程序提交投票后提示「网络错误」

  • 现象:点击「提交」按钮,toast 提示「网络错误」,控制台 Network 显示net::ERR_CONNECTION_REFUSED
  • 原因:小程序前端API_BASE仍为https://api.example.com,未改为http://localhost:3000
  • 解决:
    1. 打开vote-wechat-miniprogram/app.js;
    2. 找到const API_BASE = ...行;
    3. 改为const API_BASE = 'http://localhost:3000';
    4. 在开发者工具中点击「重新编译」(不是刷新)。

注意:以上 5 条覆盖了 95% 的首次运行问题。如果仍有异常,打开api-server的console.log输出,重点看req.url和res.status,80% 的问题都能定位到具体哪一行代码。


5. 毕设定制化改造:3 个刚需功能的 5 行代码增补方案

毕设不是交源码,而是交「你改过的源码」。下面三个功能是答辩老师最爱问的,每个都只需改 5 行以内代码,且不影响原有逻辑。我教学生时,要求他们必须亲手改一遍——这比背 100 道面试题更有说服力。

5.1 增加「投票截止时间倒计时」组件(小程序端)

在pages/vote-detail/vote-detail.js的onLoad函数中插入倒计时逻辑:

// 在 data 中增加 data: { countdown: '' }, // 在 onLoad 中启动倒计时 onLoad(options) { const endTime = new Date(this.data.vote.endTime); // 从 data.votes 获取 this.countdownTimer = setInterval(() => { const now = new Date(); const diff = endTime - now; if (diff <= 0) { clearInterval(this.countdownTimer); this.setData({ countdown: '已截止' }); return; } const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); this.setData({ countdown: `${days}天${hours}小时` }); }, 1000); },

然后在vote-detail.wxml中添加:

<view class="countdown">剩余时间:{{countdown}}</view>

逻辑说明:endTime是 MongoDB 存储的Date类型,前端new Date()可直接解析;setInterval每秒更新一次,避免频繁请求后端。答辩时老师问「怎么知道投票是否结束」,你指这 5 行代码即可。

5.2 后端增加「按日期范围导出投票记录」(API 层)

在api-server/routes/voteRouter.js中新增路由:

// 在 router.get('/list') 下方添加 router.get('/export/by-date', async (req, res) => { const { voteId, startDate, endDate } = req.query; const records = await Record.find({ voteId: voteId, createdAt: { $gte: new Date(startDate), $lte: new Date(endDate) } }).populate('userId', 'nickName phone').populate('optionIds', 'content'); res.json({ code: 200, data: records }); });

参数说明:startDate和endDate传 ISO 格式字符串(如2024-05-01T00:00:00Z),MongoDB 的$gte/$lte可直接比较 Date 对象。管理后台调用时 URL 为/api/vote/export/by-date?voteId=xxx&startDate=...&endDate=...。

5.3 数据库增加「投票参与率统计」视图(MongoDB 聚合)

在database/init-data.json的votes集合中,为每条投票文档增加participationRate字段(计算公式:已投票人数 / 总用户数 × 100)。但更优雅的做法是用聚合管道动态计算:

// 在 api-server/controllers/voteController.js 中新增函数 exports.getParticipationRate = async (req, res) => { const { voteId } = req.params; const result = await Vote.aggregate([ { $match: { _id: new mongoose.Types.ObjectId(voteId) } }, { $lookup: { from: 'records', localField: '_id', foreignField: 'voteId', as: 'records' } }, { $addFields: { totalUsers: { $size: '$records' }, participationRate: { $multiply: [ { $divide: [{ $size: '$records' }, { $ifNull: ['$targetUserCount', 1000] }] }, 100 ] } } } ]).exec(); res.json({ code: 200, data: result[0] || {} }); };

边界处理:$ifNull防止targetUserCount为空时除零;$size计算数组长度即投票人数。答辩时可演示「这个投票面向 200 名学生,实际参与 187 人,参与率 93.5%」——老师立刻明白你懂业务指标。

从那以后我每次指导毕设,都强制学生在答辩前夜,把这三个小功能亲手敲一遍、跑一遍、截图存档。不是为了炫技,而是当老师问「这个系统你怎么保证数据准确」时,你能指着自己改的那几行代码说:「我加了倒计时防超时提交,加了日期范围导出防数据污染,加了参与率聚合防人工统计误差。」——这才是毕设该有的样子。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询