从 0 到 1 实现CloudBase云开发 + 低代码全栈开发活动管理小程序(09)

49 阅读3分钟

第 9 章:投票互动系统

“人类的本质是复读机,但人类的乐趣在于投票选出那个最好的复读机。”

投票是提升活动活跃度的神器。无论是“十佳歌手”评选,还是“最美摄影”大赛,一个实时、公正的投票系统都是必不可少的。

9.1 投票配置设计

投票的配置 (voteConfig) 相对独立,存储在 activities 表中。

数据结构

{
  "displayMode": "grid", // 展示模式:列表/网格
  "maxVotes": 3, // 每人最多投几票
  "allowRevote": false, // 是否允许改票
  "showVoteResult": true, // 投票后是否显示结果
  "options": [
    {
      "id": "opt_1",
      "name": "1号选手 张三",
      "imageUrl": "cloud://...",
      "description": "来自计算机系的灵魂歌手"
    },
    {
      "id": "opt_2",
      "name": "2号选手 李四"
      // ...
    }
  ]
}

9.2 投票逻辑实现

投票逻辑在 server/vote/index.js 中。

核心校验

submitVote 云函数中,我们必须做严格的校验:

  1. 活动状态: 活动是否已结束?是否是投票类型的活动?
  2. 选项有效性: 用户投的 optionId 是否在配置的 options 列表里?
  3. 票数限制: voteOptionIds.length 是否超过 maxVotes
  4. 重复投票: 查询 votes 表,检查该用户是否已经对该活动投过票。
    • 如果已投且 allowRevote=false: 报错“您已投票”。
    • 如果已投且 allowRevote=true: 执行 update 更新操作。
    • 如果未投: 执行 create 插入操作。

9.3 防刷机制

只要有投票,就有刷票。虽然我们无法做到银行级的风控,但可以增加刷票成本。

  1. 身份限制: 必须登录才能投票(OpenID 唯一)。
  2. 频率限制: (进阶) 云函数通过 Redis 限制同一 IP 或同一用户单位时间内的请求频率。
  3. 黑名单: 对于异常用户,管理员可以在后台将其禁用 (role='banned')。

9.4 实时统计与排名

用户投完票,最想看的就是当前排名。

统计逻辑 (getVoteStatistics)

为了保持云函数的轻量和可移植性,我们没有使用复杂的 SQL 聚合查询,而是采用了更直观的 “应用层聚合” 策略。

  1. 拉取数据: models.votes.list({ where: { activityId } }) 获取该活动的所有投票记录。
  2. 内存统计: 遍历记录,利用 JS 对象进行计数。
// server/vote/index.js
const optionVoteCount = {}
votes.records.forEach((vote) => {
  // 解析 voteOptionIds 字符串
  let voteOptionIds = []
  try {
    voteOptionIds = JSON.parse(vote.voteOptionIds)
  } catch (e) {
    /*...*/
  }

  // 累加票数
  voteOptionIds.forEach((optionId) => {
    optionVoteCount[optionId] = (optionVoteCount[optionId] || 0) + 1
  })
})
  1. 生成结果: 将统计出的 count 映射回 options 数组,并按票数降序排列。

性能优化方案

如果未来遇到全校级的大规模投票(如 10万+ 数据),上述内存统计可能会变慢。届时可以考虑:

  1. 预计算字段: 在 activities 表中维护一个 vote_counts 字段。每次用户投票时,直接更新该字段,读取时直接返回,无需实时计算。
  2. 缓存: 将统计结果缓存到 Redis 或 CDN 中,设置 1 分钟的过期时间。

本章小结: 我们完成了一个功能完备的投票系统,涵盖了配置、校验、防刷和统计。至此,核心业务功能(活动、报名、投票)已全部实现。

接下来,我们将进入本书最精彩、最硬核的部分——可视化编辑器。如果你想知道那些拖拖拽拽就能生成页面的功能是怎么写出来的,千万不要错过下一章!

下一章(10-组件化设计思想)