ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

微信小程序投票系统毕业设计实战:云开发+实名制+实时统计

微信小程序投票系统毕业设计实战:云开发+实名制+实时统计 简介这是一套面向计算机专业本科生的微信小程序毕业设计实战项目完整实现了一个B/S架构的投票评选系统适用于课程设计、毕设选题与全栈开发能力训练。资源包含可直接运行的前后端源码、详细开发说明文档、MySQL数据库脚本及配套演示视频覆盖从用户端在线投票支持图文投票、单主题限投一次到后台管理含管理员/用户/公告/主题/结果全维度管控的完整业务闭环。压缩包共884个文件以148个JS逻辑文件、71个Java服务端代码、58个CSS样式文件、91个XML配置及33个GZ压缩资源为主辅以PNG/JPG图片、WXML/WXSS小程序页面组件和SQL数据库脚本总大小15.56MB结构规范、模块清晰便于分层学习与二次开发。已有1322人下载学习特别适合希望掌握微信小程序Java后端MySQL协同开发流程的学生能快速上手部署、理解权限控制与数据统计逻辑。1. 微信小程序毕业设计里的投票评选系统不是套模板而是跑通「用户发起→实名投票→实时统计→结果导出」闭环很多计算机专业学生拿到「微信小程序毕业设计」选题时第一反应是找现成源码改个名字交差。但真正能过答辩、被老师追问细节的项目核心不在界面有多炫而在业务链路是否真实可验证——比如这个「投票评选系统」它必须能支撑班级评优、社团竞选、课程作品展评等典型场景管理员创建带截止时间的投票项参与者用学号姓名实名提交单选/多选后台实时刷新柱状图与排名导出 Excel 时保留原始投票记录与时间戳。它不依赖第三方服务数据库表结构清晰可解释vote_item,vote_option,vote_record,user_info四张主表所有交互逻辑在小程序端与云函数间合理分层。适合大三下学期动手能力中等以上的学生用 23 周完成部署、调试和演示视频录制重点考察你对微信小程序生命周期、云开发权限控制、数据库索引优化及前端防重复提交的理解而不是堆砌组件或调用黑盒 API。2. 用云开发快速搭建投票评选系统的最小可行架构从数据库建模到云函数封装2.1 数据库设计必须满足「可追溯、可审计、可扩展」三原则微信小程序云开发的数据库是 JSON 文档型但投票系统本质是强关系型业务。不能直接把所有字段塞进一个 collection否则后期查「某用户投了哪些票」「某选项被多少人选择」会极其低效。我们按业务域拆分为 4 个集合collection每个集合的_id使用自定义字符串 ID如item_20240520_001便于人工排查和导出对齐集合名核心字段示例设计要点vote_itemstitle: 2024届优秀毕业生评选,start_time: 2024-05-20 08:00,end_time: 2024-05-25 17:00,status: activestatus字段用于控制前端展示逻辑未开始/进行中/已结束避免仅靠时间判断导致时区误差vote_optionsitem_id: item_20240520_001,option_text: 张三,sort_order: 1item_id建立与vote_items的逻辑关联sort_order控制选项显示顺序比数组下标更稳定vote_recordsitem_id: item_20240520_001,user_id: stu_2021001,option_ids: [opt_001, opt_003],submit_time: 2024-05-21T14:22:33Zuser_id为学号非微信 openid因需实名option_ids存数组支持多选submit_time用 ISO 格式方便云函数聚合统计user_infouser_id: stu_2021001,name: 李四,class: 计科2101班,avatar_url: https://...jpg单独建表避免每次投票都重复录入姓名班级也便于后期做「同班同学互评」类筛选提示所有集合均需在云开发控制台手动创建并为vote_records.item_id和vote_records.user_id添加复合索引item_iduser_id否则查询「某用户是否已投某投票」会触发全表扫描小程序端等待超时。2.2 云函数封装核心业务逻辑避免前端直连数据库带来的安全风险小程序端不能直接调用db.collection(vote_records).add()否则恶意用户可伪造请求刷票。必须通过云函数做校验层。以「提交投票」为例submitVote云函数代码如下// cloudfunctions/submitVote/index.js const cloud require(wx-server-sdk) cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main async (event, context) { const { item_id, user_id, option_ids } event const db cloud.database() const $ db.command.aggregate try { // 1. 检查投票项是否存在且处于进行中状态 const item await db.collection(vote_items).doc(item_id).get() if (!item.data || item.data.status ! active) { return { success: false, msg: 投票未开启或已结束 } } // 2. 检查用户是否已提交防止重复投票 const existing await db.collection(vote_records) .where({ item_id, user_id }) .limit(1) .get() if (existing.data.length 0) { return { success: false, msg: 您已投过此票 } } // 3. 检查选项ID是否全部属于该投票项 const options await db.collection(vote_options) .where({ item_id, _id: db.command.in(option_ids) }) .field({ _id: true }) .get() if (options.data.length ! option_ids.length) { return { success: false, msg: 存在非法选项ID } } // 4. 写入投票记录 await db.collection(vote_records).add({ data: { item_id, user_id, option_ids, submit_time: new Date().toISOString() } }) return { success: true, msg: 投票成功 } } catch (err) { console.error(submitVote error:, err) return { success: false, msg: 系统繁忙请稍后重试 } } }这段代码的关键在于第 1 步用db.collection(vote_items).doc(item_id).get()直接查文档比where更快且能精确获取status字段第 2 步用where({ item_id, user_id })加limit(1)避免查出全部记录浪费资源第 3 步用db.command.in(option_ids)批量校验选项合法性比循环查单个更高效第 4 步写入时只传必要字段不带_id由云开发自动生成submit_time用new Date().toISOString()确保时区统一UTC。部署该云函数后在小程序端调用方式为wx.cloud.callFunction({ name: submitVote, data: { item_id: item_20240520_001, user_id: stu_2021001, option_ids: [opt_001, opt_002] }, success: res { if (res.result.success) { wx.showToast({ title: 投票成功 }) } else { wx.showToast({ title: res.result.msg, icon: none }) } } })2.3 小程序端关键交互实现加载页、顶部导航、单选框状态同步毕业设计演示视频里老师最常问「刚打开小程序时那个旋转图标怎么换」——这其实是app.js中onLaunch生命周期里控制的。修改方法不是改project.config.json而是调整app.json的splashScreen配置微信基础库 2.27.0 支持{ splashScreen: { alwaysShowBeforeRender: true, backgroundColor: #f8f8f8, image: cloud://your-env-id.7961-your-env-id/launch_logo.png } }其中image必须是云存储中的图片路径且尺寸建议 640×1136iPhone X 竖屏比例否则拉伸变形。上传后需在云开发控制台设置该文件为「公有读」。顶部导航栏高度问题如标题被状态栏遮挡的根源是app.json中navigationStyle设置为custom。此时需手动计算安全区域// pages/index/index.js Page({ onReady() { const systemInfo wx.getSystemInfoSync() this.setData({ statusBarHeight: systemInfo.statusBarHeight }) } })WXML 中用view styleheight: {{statusBarHeight}}px/view占位再放自定义导航栏。单选框radio的选中状态必须与data绑定且change事件要更新data而非直接操作 DOMradio-group bindchangeonRadioChange label wx:for{{options}} wx:keyid radio value{{item._id}} checked{{item._id selectedOptionId}}/ text{{item.option_text}}/text /label /radio-groupJS 中onRadioChange(e) { this.setData({ selectedOptionId: e.detail.value }) }若不绑定checked切换时 UI 不同步若不setData后续提交时拿不到最新值。3. 实战部署从本地调试到生成可演示的正式版小程序3.1 本地真机调试必须绕开的三个坑微信开发者工具的「真机调试」功能虽方便但在投票系统中极易踩坑坑一云函数本地调试返回空数据原因云函数本地运行时wx.cloud.database()默认连接的是「测试环境」而你在控制台创建的集合在「正式环境」。解决方法在云函数代码开头加cloud.init({ env: your-prod-env-id })将your-prod-env-id替换为你云开发环境的实际 ID形如xxx-yyy。坑二真机扫码后页面白屏表象是app.js报错Cannot read property then of undefined本质是wx.getSystemInfoSync()在部分安卓机型上返回null。修复方式加空值判断const systemInfo wx.getSystemInfoSync() const statusBarHeight systemInfo?.statusBarHeight || 44坑三投票提交后云函数日志无记录这通常是因为云函数未发布。开发者工具中右键云函数文件夹 →「上传并部署云端安装依赖」必须勾选「发布版本」否则只部署到「测试版」小程序体验版无法调用。3.2 数据库初始化脚本一键导入示例数据避免手动填表毕业设计答辩前你需要快速还原一套完整数据用于演示。不要在控制台一条条添加而是写一个initDB云函数执行一次即可// cloudfunctions/initDB/index.js const cloud require(wx-server-sdk) cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) exports.main async (event, context) { const db cloud.database() // 插入投票项 await db.collection(vote_items).add({ data: { _id: item_20240520_001, title: 2024届优秀毕业生评选, start_time: 2024-05-20 08:00, end_time: 2024-05-25 17:00, status: active } }) // 插入选项3个候选人 const options [ { _id: opt_001, item_id: item_20240520_001, option_text: 张三, sort_order: 1 }, { _id: opt_002, item_id: item_20240520_001, option_text: 李四, sort_order: 2 }, { _id: opt_003, item_id: item_20240520_001, option_text: 王五, sort_order: 3 } ] await db.collection(vote_options).add({ data: options }) // 插入模拟用户5人 const users [ { user_id: stu_2021001, name: 张三, class: 计科2101班 }, { user_id: stu_2021002, name: 李四, class: 计科2101班 }, { user_id: stu_2021003, name: 王五, class: 计科2101班 }, { user_id: stu_2021004, name: 赵六, class: 计科2101班 }, { user_id: stu_2021005, name: 钱七, class: 计科2101班 } ] await db.collection(user_info).add({ data: users }) return { msg: 初始化完成共插入135条记录 } }部署后在开发者工具控制台调用wx.cloud.callFunction({ name: initDB })5 秒内即可生成一套可演示的完整数据比手动操作快 10 倍且保证各集合间 ID 关联正确。3.3 演示视频录制要点聚焦「可验证动作」避开敏感操作答辩用的演示视频不是功能秀而是证明你理解系统边界。录制时严格按以下顺序操作总时长控制在 3 分钟内打开小程序首页→ 展示顶部导航栏含「我的投票」「全部投票」Tab点击「全部投票」→ 列表显示「2024届优秀毕业生评选」右上角显示「距结束 4天12小时」用wx.getStorageSync(countdown)缓存倒计时避免频繁请求进入该投票页→ 滚动查看候选人头像与简介从user_info表查出点击「我要投票」填写学号stu_2021001→ 输入姓名「张三」→ 选择「张三」→ 点击「提交」→ 弹出「投票成功」toast立即返回列表页→ 下拉刷新 → 「已投票」标签变蓝右侧显示「已投」切换到「我的投票」Tab→ 列表显示「2024届优秀毕业生评选」→ 点击进入 → 显示「您投给了张三」及提交时间管理员视角用另一个微信账号登录→ 进入「管理后台」→ 查看实时统计图表柱状图高度随新投票动态变化→ 点击「导出 Excel」→ 下载文件名为vote_result_20240520.xlsx打开后含学号,姓名,班级,投票选项,提交时间五列。注意视频中绝不出现「数据库控制台」界面或「云函数日志」截图这些属于后台运维细节答辩时不考察也不演示「删除投票」或「修改截止时间」等管理操作避免被追问权限模型设计。4. 进阶技巧让投票结果页支持「按班级筛选」与「导出时自动脱敏」4.1 按班级筛选的聚合查询用 aggregate 替代多次 where管理后台需要查看「计科2101班」的投票分布如果用where({ class: 计科2101班 })先查用户再查记录效率极低。正确做法是在vote_records集合上做聚合一次查出结果// 云函数 getVoteByClass const pipeline [ { $lookup: { from: user_info, localField: user_id, foreignField: user_id, as: user } }, { $unwind: $user }, { $match: { user.class: 计科2101班 } }, { $group: { _id: $option_ids.0, // 假设单选取数组第一个 count: { $sum: 1 } } } ] const result await db.collection(vote_records).aggregate().pipeline(pipeline).end()$lookup实现左连接$unwind展开user数组因user_info是一对一$match过滤班级$group按选项ID分组计数。返回结果形如[ { _id: opt_001, count: 12 }, { _id: opt_002, count: 8 }, { _id: opt_003, count: 5 } ]前端用wx.setStorageSync(classStats, result)缓存避免重复请求。4.2 导出 Excel 时自动脱敏隐藏学号后四位保留可追溯性毕业设计要求「保护隐私」但又需老师能核对真实性。导出时对user_id做掩码处理非加密规则为stu_2021001→stu_2021****。云函数exportExcel中处理const records await db.collection(vote_records).aggregate() .lookup({ from: user_info, localField: user_id, foreignField: user_id, as: user }) .unwind($user) .project({ _id: 0, 学号: { $concat: [ { $substrCP: [$user.user_id, 0, 7] }, **** ] }, 姓名: $user.name, 班级: $user.class, 投票选项: { $arrayElemAt: [$option_ids, 0] }, // 单选取第一个 提交时间: $submit_time }) .end()$substrCP按 Unicode 字符截取比$substr更可靠$concat拼接字符串。这样导出的 Excel 既满足「匿名统计」要求又保留前缀stu_2021供人工核对年级专业。4.3 防刷票的最后防线同一 IP 10 分钟内最多提交 3 次即使有user_id校验仍需防批量脚本刷票。在submitVote云函数开头加入 IP 限流const ip context.CLIENTIP || unknown const ipKey ip_limit_${ip}_${item_id} const count await cloud.redis.get(ipKey) || 0 if (parseInt(count) 3) { return { success: false, msg: 操作过于频繁请10分钟后重试 } } await cloud.redis.set(ipKey, parseInt(count) 1, { EX: 600 }) // 10分钟过期需在云开发控制台开通「云 redis」服务免费额度足够毕业设计使用EX: 600表示 600 秒后自动失效。此方案不依赖前端传参从源头拦截恶意请求。微信小程序毕业设计中的投票评选系统其价值不在于代码行数而在于你能说清为什么用云开发而非自己搭 Node.js 服务为什么vote_records不存user_name而要关联查询为什么导出 Excel 用$concat而不用前端 JS 处理把这些逻辑钉死在代码注释与答辩话术里比堆砌 10 个花哨动画更有说服力。本文还有配套的精品资源点击获取
返回列表