前言

“小王,明天公司在***举办一个xxx产品发布会,你今天准备2000份问卷调查。还有,我们这次还做一个抽奖活动,也记得弄一个抽奖箱和一些抽奖球哦。”

……

活动结束了,小王想起早上捧着这2000张问卷和抽奖箱的情景,生平第一次对弘二头肌起了念想。回过神来看着桌子上回收回来的问卷,整整齐齐的像座小山一样好看,但领导依然不太满意,因为只回收了1000来张。可是1000多张的样本已经足够了呀,统计也很花时间的呀。小王本想反驳,但他什么也没说,只是下意识地摸了摸自己的背包,包里装着那丢失的900多张问卷。

以上剧情根据真实故事改编,如有雷同,算你倒霉。

数字化大背景

现在还有不少活动是用纸质问卷来做调查的,几千张纸是小钱,但后期统计这一堆数据可是费神费力的苦力活。以前设备落后,手机上做问卷体验太差。但现在是80岁大爷都会玩智能手机的年代,一个二维码也解决了入口问题,在线调查问卷的体验也就上来了。再加上现在办个活动什么的都是用微信宣传微信组织,配合一点抽奖活动,观众们还是愿意去回答的。既然已经具备了在线问卷的大环境,下面就让小茄带大家来做一个在线问卷调查吧。

需求

先来分析一下需求。

1、在线问卷调查的使用者都是市场运营的工作人员,他们对编程的了解很少,所以后台操作必须简单明了。

2、输入为问题信息,输出为回答统计信息,输出需要使用可视化图表呈现,必要时也提供元数据。

3、最好能带一点圈粉属性,扫一扫关注公众号然后才开始答题。硬生生让人关注公众号,许多人可能无动于衷,但增加了一个问卷和抽奖的梗,关注公众号就显得非常合理自然。

4、最好能带一点统计功能,统计一下到底多少人打开了页面,从而为后续改进提供数据分析支撑。

其中1、2是刚需,3、4是软需。

后端

简单分析可以发现,开发这个小应用最主要的工作是在后端开发部分,而且这个主要是以数据处理为主,显然采用面向数据库编程的方式来开发更为合适。

面向数据库开发第一步,先来定义数据库吧。先使用excel做出相应的表格,大概是这样的:

20160718141649

然后就是分表写数据库,将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 }

输出部分:问卷数据采集完之后,可以使用echart来展现统计数据。具体教程可以看看官方文档:http://docs.wex5.com/integrate-echarts/ ,但是不赞同使用单文件的形式,建议采用模块按需载入的方式。这里用到的无外乎是柱状或者饼状图,所以只加载基类和这两类js文件即可。

如果妹子要元数据怎么办?一行代码搞定:select * from answer into outfile  ‘d:/answer.xls’;  建议一定要拉着妹子的手,手把手地把这个好用的技能教给她。

更进一步

通过上面几步,一个简单好用的在线问卷就已经实现了。细心的你估计发现了,3、4点需求还没实现呢。好吧,下面看看这两点怎么实现,没兴趣的同学可以直接到文章末尾点赞了,谢谢配合。

首先是增加圈粉属性。

这个前提就是你要把应用部署在你的公众号服务器上。还没有服务器?Cloud X5 搞起吧,简明教程:http://docs.wex5.com/about-cloudx5/

圈粉主要是要把你的应用入口改成微信网页授权页面,也就是这个地址:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect,记得里面的大写字母参数要改成你自己的参数。回调 uri 记得要做URI转码。一般来说我们还要获取用户信息的,所以这里的SCOPE填入snsapi_userinfo。其他参数请参考微信开发者文档自行补充,这里就不赘述了:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842&token=&lang=zh_CN。如果对这一部分不太熟悉的话,可以看看小茄上一篇【30分钟做一个二维码名片应用】http://www.wex5.com/openway_qrcode/,里面有详细介绍如何使用WeX5进行微信公众号开发。

再来看看统计功能:在2016年7月4号之前,你都只能在网页中引用站长工具啦、百度统计啦、谷歌统计来进行数据统计。而现在你也可以使用微信自家的统计功能了,这个是专门统计微信客户端的访问量的。传送门:https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&key=1467639271&version=1&lang=zh_CN,直接在后台就能看。由于它统计的是使用了JSSDK的页面,所以这个页面也需要配置jssdk_config。既然上面都说要圈粉了,那就增加一个分享接口就好了,后面判断这个分享接口被调用的次数就能间接得到某个时间段的访问量了。对了,每个接口还按照页面区分好了,所以你不用担心其他页面数据的干扰。

20160718161908

 

然后,然后小王终于可以忘了曾一度被问卷调查所支配的恐怖和被囚禁于数据统计中的那份屈辱了。

全文完,点赞不谢!