前言

“小王,明天公司在***舉辦一個xxx產品發布會,你今天準備2000份問卷調查?;褂?,我們這次還做一個抽獎活動,也記得弄一個抽獎箱和一些抽獎球哦?!?/p>

……

活動結束了,小王想起早上捧著這2000張問卷和抽獎箱的情景,生平第一次對弘二頭肌起了念想?;毓窶純醋拋雷由匣厥棧乩吹奈示?,整整齊齊的像座小山一樣好看,但領導依然不太滿意,因為只回收了1000來張??墑?000多張的樣本已經足夠了呀,統計也很花時間的呀。小王本想反駁,但他什么也沒說,只是下意識地摸了摸自己的背包,包里裝著那丟失的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來展現統計數據。具體教程可以看看官方文檔://docs.wex5.com/integrate-echarts/ ,但是不贊同使用單文件的形式,建議采用??榘蔥柙厝氳姆絞?。這里用到的無外乎是柱狀或者餅狀圖,所以只加載基類和這兩類js文件即可。

如果妹子要元數據怎么辦?一行代碼搞定:select * from answer into outfile  ‘d:/answer.xls’;  建議一定要拉著妹子的手,手把手地把這個好用的技能教給她。

更進一步

通過上面幾步,一個簡單好用的在線問卷就已經實現了。細心的你估計發現了,3、4點需求還沒實現呢。好吧,下面看看這兩點怎么實現,沒興趣的同學可以直接到文章末尾點贊了,謝謝配合。

首先是增加圈粉屬性。

這個前提就是你要把應用部署在你的公眾號服務器上。還沒有服務器?Cloud X5 搞起吧,簡明教程://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分鐘做一個二維碼名片應用】//www.usdzf.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

 

然后,然后小王終于可以忘了曾一度被問卷調查所支配的恐怖和被囚禁于數據統計中的那份屈辱了。

全文完,點贊不謝!