前阵子帮一个做非遗文创的朋友,搭了一套民间艺术知识答题小程序。玩法不复杂:用户进入小程序,每次从题库里随机抽 10 道题,答对一题得 10 分,累计到一定积分,可以在积分商城里兑换他们店里的文创产品优惠券。就是这个小程序,从 0 到上线大概用了一个多月,中间踩了不少坑,尤其是支付能力和微信审核这两个环节,差点卡住上线。如果你也在做类似的随机出题、积分、兑换优惠券类小程序,这篇文章应该能帮你少走很多弯路。
1. 项目概述:为什么做一套“答题+积分+兑换”的小程序
1.1 民间艺术答题小程序的真实需求
先说项目背景。朋友在做一个地方非遗文创品牌,产品线有剪纸、年画、皮影、手绣这些,线下门店在景区里,线上有个小程序商城。问题很典型:小程序商城流量低,用户下了单就走,没有粘性。他们想做一个能把用户留住的玩法,同时还能普及民间艺术知识,而不是干巴巴地上架商品。
答题小程序正好匹配这个需求。民间艺术本身知识点多、趣味性强,适合做成选择题。比如“剪纸中最常见的吉祥图案是什么”“皮影戏最早出现在哪个朝代”“杨柳青年画产自哪个省份”,这些题目既能让用户长知识,又能和文创产品形成情感连接。用户答完题,对某个非遗项目产生兴趣,再去兑换一张优惠券下单,转化路径非常自然。
这套模式适合谁?文化馆、博物馆、景区、非遗工作室、地方文旅项目,甚至想给电商小程序做用户运营的个人开发者都可以参考。核心产品逻辑就三个闭环:随机出题带来新鲜感,答对积累积分带来成就感,兑换文创产品优惠券带来实打实的利益刺激。三个环节串起来,用户才愿意持续回来。
1.2 技术方案选型:原生小程序加云开发
技术栈我选了微信小程序原生加微信云开发。理由很简单:这个项目不需要复杂的服务器架构,但又需要稳定的登录态、数据库和事务能力。
- 登录:云开发自带
openid识别,用户打开小程序就自动拿到唯一身份,不用做手机号授权、微信登录那一套。 - 数据库:云数据库支持基础查询、聚合、事务,存题库、积分流水、优惠券记录都够用。
- 部署:云函数按调用次数计费,前期流量不大,基本在免费额度内。
如果用传统前后端方案,还得买服务器、配域名、过备案、写接口文档,一个知识答题小程序被运维成本拖死,没必要。
数据模型我设计了 6 个核心集合,这里直接列出来给大家参考:
| 集合名 | 用途 | 关键字段 |
|---|---|---|
| questions | 题库 | title、options、answer、category、difficulty、enabled |
| quiz_sessions | 答题会话 | openid、status、questions、startTime |
| users | 用户扩展信息 | openid、score、nickname、avatar |
| score_logs | 积分流水 | openid、change、balance、type、source |
| coupon_templates | 优惠券模板 | title、cost、stock、validFrom、validTo、status |
| user_coupons | 用户已兑换优惠券 | openid、templateId、code、status、expireAt |
这套表结构从一开始就要定好,不然后面加字段、迁数据相当痛苦。score_logs 尤其重要,每一分钱的变动都要留痕,后面排查用户纠纷、刷分问题全靠它。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 随机出题模块:让每次答题都有新鲜感
2.1 题库结构设计,先给内容定好“书架”
民间艺术知识题目有很多分类,题库设计时第一件事是分类。我按非遗门类拆成了剪纸、年画、皮影、刺绣、陶瓷、戏曲、曲艺、传统工艺等大类,每道题可以打多个标签(tags),比如“剪纸-吉祥图案-北方风格”。
这种设计有什么好处?后期做专题挑战赛时,可以直接按 category 或 tags 筛选题目,不用再造一套题库。比如做“剪纸知识周”,前端传一个 category: 'paper-cutting',后端就把对应分类的题目抽出来。
题目字段里,answer 我存的是选项下标(0、1、2、3),不是答案文本。这样判分逻辑简单,而且前端不接收答案字段,用户从小程序端根本拿不到正确答案。其他字段像 explain,用来答完题显示解析,方便做知识科普。
题库内容来源要特别注意版权和准确性问题。朋友那边是和当地非遗传承人、文化馆合作整理的公开资料,再人工校对一遍,确保没有政治敏感、历史争议类内容。如果你是自己收集题库,千万别从网上随便扒,至少核对两个独立来源,并且避免涉及具体历史人物评价。
2.2 随机抽题怎么做才不卡
随机出题是用户感知最强的功能,也是新手最容易做错的地方。最直观的写法是“从题库里随机取 N 条记录”,听起来没问题,里面藏着性能坑。
在小程序端直接 db.collection('questions').where({ enabled: true }).skip(randomIndex).limit(10) 这种方式,一旦题库超过几千条,skip 的性能会明显下降,而且并发请求容易抽到重复题。云函数里如果用 orderBy(Math.random()) 这种方式,不同数据库实现差异很大,有的甚至不支持。
我在项目里用的是聚合管道里的 sample 操作,自带随机抽样,效率高。示例代码如下:
javascript复制const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command
exports.main = async (event) => {
const { OPENID } = cloud.getWXContext()
const { category, count = 10 } = event
const where = { enabled: true }
if (category) where.category = category
const res = await db.collection('questions')
.aggregate()
.match(where)
.sample({ size: Math.min(count, 20) })
.project({
title: true,
options: true,
category: true,
difficul
