基于nodejs的问卷调查系统

2016-12-28 04:23:09

花了4天时间撸了一个问卷调查系统,算是入门nodejs后端开发。

github传送门:https://github.com/flute/survey

技术

  • 后端:nodejs+express+mysql
  • 前端:material design

install

  1. npm install
  2. 配置数据库 conf/db.js ,导入sql文件 mysql.sql
  3. node app.js

功能

  1. 登陆验证
  2. 问题类型包括 单选、多选及问答三类
  3. 查看问卷列表、删除
  4. 填写提交问卷
  5. 问卷结果列表及结果详情

注:

在系统实现过程中,数据表的设计及数据操作有些麻烦,感兴趣的往下看:

为了问卷结果的可读性及统计方便,数据表设计时将 问卷、问题、选项 分为三个表,在插入、读取问卷时,需要顺序、批量进行数据库操作。

如:新增1个问卷,该问卷有10个问题,单选及多选问题包含2个以上的选项。

那么数据操作顺序是:

  1. 先插入问卷,并得到返回的问卷ID。
  2. 得到问卷ID,按顺序插入10个问题,每个问题在插入成功后,返回问题ID。
  3. 每个问题插入成功时得到问题ID,按顺序插入该问题的多个选项,只有当该问题及问题选项全部插入成功时,开始执行下个问题。

可以看到3是2中的内部循环,2本身是个循环,1和2是顺序执行关系。对于这样的数据插入,如果用常规的嵌套回调,光一个问题就是三层以上的嵌套,几十个问题的话就没法玩了。而且所有的操作只需要一个返回结果,及最终成功与否,只要当其中任何一次插入失败则失败。

最终实现方法是使用async.eachSeries,以下代码实现上述问题:

insertSurvey: function (data,callback) {  
    //从mysql连接池获取连接
    pool.getConnection(function(err,connection){
        if( err ){
            callback(err);
            return;
        }
        connection.query( "insert into t_survey(... , ... ,...) values(..., ...)", function(err,res){
            if(res){
                //问卷插入成功
                var surveyId = res.insertId;
                //异步批量按序插入问题
                //拼接sql数组
                {...}
                /**
                 * insertQuestionSql 是插入问题的语句数组
                 * 
                 * insertQuestionSql = [
                 *      insert into t_survey_question(createAt,type,.....) values(... ,.. , ...),
                 *      insert into t_survey_question(createAt,type,.....) values(... ,.. , ...),
                 *      .....
                 * ];
                 */
                async.eachSeries( insertQuestionSql, function(item,questioncb){
                    connection.query( item.survey, function(err, results) {
                        if(err) {
                            questioncb(err,result);
                        }else{
                            //问题插入成功,继续插入选项
                            var questionId = results.insertId;
                            //如果是单选或多选,存在多个选项,异步批量按序插入选项
                            //拼接sql数组
                            {...}
                            /**
                             * insertOptionSql 是需要插入的选项数组
                             * 
                             * insertOptionSql = [
                             *      insert into t_survey_option(createAt,type,.....) values(... ,.. , ...),
                             *      insert into t_survey_option(createAt,type,.....) values(... ,.. , ...),
                             *      .....
                             * ];
                             */
                            if( item.data.type == 'radio' || item.data.type == 'checkbox' ){
                                async.eachSeries( insertOptionSql, function( optionitem, optioncb ){
                                    connection.query( optionitem, function( operror,opresult){
                                        if(operror){
                                            optioncb(operror,opresult);
                                        }else{
                                            //选项插入成功
                                            optioncb( opresult.insertId );
                                        }
                                    });
                                },function(err){
                                    //当前问题的所有选项插入结束
                                    if(err){
                                        callback(err);
                                    }else{
                                        callback();
                                    }
                                });
                            }else{
                                //问答无选项,直接回调
                                questioncb();
                            }
                        }
                    });
                },function(err) {
                    //所有问题插入结束
                    callback(err);
                });
            }else if(err){
                callback(err)
            }
            //回调结果
            callback(result);
            //释放连接 
            connection.release();
        })
    })
},

最终的解决方案也是三层嵌套回调,也对应了对三个表的操作,暂时算是最优解决方案了。

async.eachSeries 保证了SQL的执行顺序,而且当其中一条执行异常,就不会继续执行下一条,简单示例:

var sqls = [  
  "INSERT INTO log SET data='data1'",
  "INSERT INTO log SET data='data2'",
  "INSERT INTO log SET data='data3'"
];

async.eachSeries(sqls, function(item, callback) {  
  // 遍历每条SQL并执行
  connection.query(item, function(err, results) {
    if(err) {
      // 异常后调用callback并传入err
      callback(err);
    } else {
      console.log(item + "执行成功");
      // 执行完成后也要调用callback,不需要参数
      callback();
    }
  });
}, function(err) {
  // 所有SQL执行完成后回调
  if(err) {
    console.log(err);
  } else {
    console.log("SQL全部执行成功");
  }
});
Echarts3 中国地图下钻至县级

Echarts3 地图下钻至县级 看了会D3.js,鉴于学习曲线较高,且要实现的效果不复杂,遂使用Echarts完成——中国地图下钻至县级。 HTML <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>echarts3中国地图下钻至县级</title> <link rel="stylesheet" type="text/css" href="static/css/main.css"> <!-- Echarts3 --> <

2016总结

2016 过去的这一年,算是一个转折点吧,学生时代的终结,开始工作了。毕业后的几个月,也会经常想起上学时的很多美好,日常用语也多了句“我在学校的时候...”,我想所有刚离校的同学有共感,毕竟伴随了16年的读书模式要转换了,虽然在学校时也会出去做外包,去实习,但当全职工作后还是很回念学生时代。 说是16年的年度总结,其实多半是下半年的一些变化。以前我觉得总结什么的,都是给别人看的,形式主义。但工作的半年我转变了自己的看法,我尝试在工作的每周、每月梳理下,在每个时间段内,自己学到的新东西以及欠缺的东西,这样就有一种看着自己不断成长的感觉。不然你可能只是觉得自己好像增长了,好像有些问题被人诟病,但有不明确的知道哪些点增长了,哪些还在拖后腿。 既然是贴着程序猿的标签,那年度总结就分为生活和技术两部分吧: 生活 虽然学的是自动化专业,但是转战互联网其实从大二就开始了,虽然家里人都希望去稳定的国企,但他们也尊重我的选择,所以毕业后之久投身IT行业有惊无险。前半年的毕业找工作期也是经历了很多波折,换了好几个offer。。虽然一波三折,但是吃一堑长一智,记得任何时候都有必要签书面合同!!!不要因为对方是熟人而口头承诺!!!这是对自己的基本保障。 get 自己理发技能,从6月份开始远离理发店,到目前已经半年时间自行理发了,虽然插曲不断,总归做了件自己一直想做的事~