|
本帖最后由 小太阳 于 2016-7-27 16:54 编辑
前言
“小王,明天公司在***举办一个xxx产品发布会,你今天准备2000份问卷调查。还有,我们这次还做一个抽奖活动,也记得弄一个抽奖箱和一些抽奖球哦。”
……
活动结束了,小王想起早上捧着这2000张问卷和抽奖箱的情景,生平第一次对弘二头肌起了念想。回过神来看着桌子上回收回来的问卷,整整齐齐的像座小山一样好看,但领导依然不太满意,因为只回收了1000来张。可是1000多张的样本已经足够了呀,统计也很花时间的呀。小王本想反驳,但他什么也没说,只是下意识地摸了摸自己的背包,包里装着那丢失的900多张问卷。
以上剧情根据真实故事改编,如有雷同,算你倒霉。
数字化大背景
现在还有不少活动是用纸质问卷来做调查的,几千张纸是小钱,但后期统计这一堆数据可是费神费力的苦力活。以前设备落后,手机上做问卷体验太差。但现在是80岁大爷都会玩智能手机的年代,一个二维码也解决了入口问题,在线调查问卷的体验也就上来了。再加上现在办个活动什么的都是用微信宣传微信组织,配合一点抽奖活动,观众们还是愿意去回答的。既然已经具备了在线问卷的大环境,下面就让小茄带大家来做一个在线问卷调查吧。
需求
先来分析一下需求。
1、在线问卷调查的使用者都是市场运营的工作人员,他们对编程的了解很少,所以后台操作必须简单明了。
2、输入为问题信息,输出为回答统计信息,输出需要使用可视化图表呈现,必要时也提供元数据。
3、最好能带一点圈粉属性,扫一扫关注公众号然后才开始答题。硬生生让人关注公众号,许多人可能无动于衷,但增加了一个问卷和抽奖的梗,关注公众号就显得非常合理自然。
4、最好能带一点统计功能,统计一下到底多少人打开了页面,从而为后续改进提供数据分析支撑。
其中1、2是刚需,3、4是软需。
后端
简单分析可以发现,开发这个小应用最主要的工作是在后端开发部分,而且这个主要是以数据处理为主,显然采用面向数据库编程的方式来开发更为合适。
面向数据库开发第一步,先来定义数据库吧。先使用excel做出相应的表格,大概是这样的:
然后就是分表写数据库,将question、options、answer分成3个表,以questionID做索引关联3个表,另外用户信息和奖品信息也要用一个数据表来保存。本来这里想用MySQL for Excel来实现,这样市场的妹子们也能简单上手。不过想想还是导出一个sql脚本更好,毕竟这样就可以手把手教妹子怎么把问卷数据写到sql文件里面了。(/▽╲)
问卷数据的读写都可以用WeX5通用的查询接口来实现数据的读写,这里不再赘述。
这里要自己写的是抽奖算法的实现,要点是保证中奖几率的均一性。但是,算法也不能太死板,主要看脸,哦不,主要看奖品大小。
如果有大奖,那么大奖单独出来所有人抽一次会比较好,这样能有效活跃起现场气氛。这种情况下可以设置一个抽奖期间,后台统计这个期间内的人数,然后在这个人数里面随机选中一个即可。如果都是些小奖品,那么肯定就是先答题后抽奖,抽奖结果要马上呈现。也就是每个观众抽奖的时刻是不同的,而且抽奖的人数也是未知的,这种情况下要保证前后抽奖的人都有相同的中奖几率,而且要把奖品发完的话,好像很难的样子。但是,既然是小奖品,按照先来先得发不完也没事的原则,每次都查询当前奖品池的奖品,如果还有奖品则用随机数判断是否中奖,否则就不中奖就完事了,简单粗暴。
所以说,一切以实际出发,把重心放到重要的事上,把吃奶的力用到吃奶上,才是王道。
贴个抽奖算法的简单实现:
1 public static JSONObject drawPrize(JSONObject params, ActionContext context) throws SQLException, NamingException {
2 // 获取参数
3 String batch = params.getString("batch");
4 int index = params.getInteger("index");
5 String weixinID = params.getString("weixinID");
6 JSONObject result = new JSONObject();
7 Connection conn = context.getConnection(DATASOURCE);
8
9 try {
10 conn.setAutoCommit(false);
11 try {
12 // 获取user
13 Statement stat = conn.createStatement();
14 try {
15 ResultSet rsUser = stat.executeQuery("SELECT * FROM user WHERE fBatch = '" + batch + "' AND fWeixinID = '" + weixinID + "'");
16 if (!rsUser.next()) {
17 // 未登记
18 result.put("code", -2);
19 } else if (!Utils.isEmptyString(rsUser.getString("fPrize" + index))) {
20 // 已中奖
21 result.put("code", -1);
22 result.put("prize", rsUser.getString("fPrize" + index));
23 } else {
24 // 读取奖池
25 List<String> prizes = new ArrayList<String>();
26 ResultSet rsPrize = stat.executeQuery("SELECT * FROM prize WHERE (fTotal - COALESCE(fCount, 0)) > 0 AND fBatch = '" + batch + "' AND fIndex = " + index);
27 while (rsPrize.next()) {
28 prizes.add(rsPrize.getString("fName"));
29 }
30 if (prizes.size() == 0) {
31 // 奖池空了
32 result.put("code", -3);
33 } else {
34 Random r = new Random();
35 // 看运气
36 int luck = r.nextInt(10);
37 if (luck > 0) {
38 // 未中奖
39 result.put("code", 0);
40 } else {
41 // 抽奖
42 luck = r.nextInt(prizes.size());
43 String prize = prizes.get(luck);
44
45 int k = stat.executeUpdate("UPDATE prize SET fCOUNT = COALESCE(fCount, 0) + 1 WHERE (fTotal - COALESCE(fCount, 0)) > 0 AND fBatch = '" + batch + "' AND fIndex = "
46 + index + " AND fName = '" + prize + "'");
47 if (k == 0) {
48 // 未中奖
49 result.put("code", 0);
50 } else {
51 // 记录数据
52 stat.executeUpdate("UPDATE user SET fPrize" + index + " = '" + prize + "' WHERE fBatch = '" + batch + "' AND fWeixinID = '" + weixinID + "'");
53 result.put("code", 1);
54 result.put("prize", prize);
55 }
56 }
57 }
58 }
59 } finally {
60 stat.close();
61 }
62 conn.commit();
63 } catch (SQLException e) {
64 conn.rollback();
65 throw e;
66 }
67 } finally {
68 conn.close();
69 }
70
71 return result;
72 }
前端
问卷部分:前端当然是一个单页应用了。因为问题形式差不多,所以可以做一个问题模板,将从后端获取到的依次问题数据渲染到页面。这里可以用WeX5的数据组件和模板绑定来实现。另外要考虑到的一个问题是问卷的原子性,就是说要么不回答,要么就要回答所有题目。所以问卷的提交是一次性的,不能做成每道题都提交的形式。因为数据量不大,所以可以一次请求把所有question、option都取回来,减少请求数。
抽奖部分:这里使用了摇一摇的形式来进行抽奖。原理很简单,就是判断加速度计在一个时间区间内的变化率大小,当变化率超过一定阈值时就说明当前手机受力突增,也就是正在“摇一摇”的状态。具体实现是监听’devicemotion’事件,代码如下:
1 // 摇一摇事件
2 if (window.DeviceMotionEvent) {
3 window.addEventListener('devicemotion', deviceMotionHandler, false);
4 } else {
5 alert('本设备不支持摇一摇');
6 }
7 function deviceMotionHandler(eventData) {
8 var acceleration = eventData.accelerationIncludingGravity;
9 var curTime = new Date().getTime();
10 if ((curTime - last_update) > 100) {
11 var diffTime = curTime - last_update;
12 last_update = curTime;
13 x = acceleration.x;
14 y = acceleration.y;
15 z = acceleration.z;
16 var speed = Math.abs(x + y + z - last_x - last_y - last_z) / diffTime * 10000;
17
18 if (speed > SHAKE_THRESHOLD) {
19 self.imgRockClick();
20 }
21 last_x = x;
22 last_y = y;
23 last_z = z;
24 }
25 }
|
评分
-
查看全部评分
|