{"id":28240740,"url":"https://github.com/flute/survey","last_synced_at":"2025-07-15T11:40:49.117Z","repository":{"id":77565455,"uuid":"77514857","full_name":"flute/survey","owner":"flute","description":"a simple survey system made by nodejs+express+mysql+material design","archived":false,"fork":false,"pushed_at":"2020-06-24T03:13:29.000Z","size":2109,"stargazers_count":4,"open_issues_count":3,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-10T23:38:13.187Z","etag":null,"topics":["express","nodejs","survey"],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flute.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-12-28T08:05:43.000Z","updated_at":"2020-08-30T17:20:28.000Z","dependencies_parsed_at":"2023-03-01T20:15:58.929Z","dependency_job_id":null,"html_url":"https://github.com/flute/survey","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/flute/survey","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flute%2Fsurvey","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flute%2Fsurvey/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flute%2Fsurvey/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flute%2Fsurvey/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flute","download_url":"https://codeload.github.com/flute/survey/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flute%2Fsurvey/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265432803,"owners_count":23764165,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["express","nodejs","survey"],"created_at":"2025-05-19T04:11:55.655Z","updated_at":"2025-07-15T11:40:49.104Z","avatar_url":"https://github.com/flute.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# survey\na simple survey system made by nodejs+express+mysql+material design\n\n## install\n1. `npm install`\n2. 配置数据库 `conf/db.js` ，导入sql文件 `conf/mysql.sql`\n3. `node app.js`\n\n## 功能\n1. 登陆验证\n1. 问题类型包括 单选、多选及问答三类\n2. 查看问卷列表、删除\n3. 填写提交问卷\n4. 问卷结果列表及结果详情\n\n### 注：\n在系统实现过程中，数据表的设计及数据操作有些麻烦，感兴趣的往下看：\n\n为了问卷结果的可读性及统计方便，数据表设计时将 问卷、问题、选项 分为三个表，在插入、读取问卷时，需要顺序、批量进行数据库操作。\n\n如：新增1个问卷，该问卷有10个问题，单选及多选问题包含2个以上的选项。\n\n那么数据操作顺序是：\n\n1. 先插入问卷，并得到返回的问卷ID。\n2. 得到问卷ID，按顺序插入10个问题，每个问题在插入成功后，返回问题ID。\n3. 每个问题插入成功时得到问题ID，按顺序插入该问题的多个选项，只有当该问题及问题选项全部插入成功时，开始执行下个问题。\n\n可以看到3是2中的内部循环，2本身是个循环，1和2是顺序执行关系。对于这样的数据插入，如果用常规的嵌套回调，光一个问题就是三层以上的嵌套，几十个问题的话就没法玩了😂。而且所有的操作只需要一个返回结果，及最终成功与否，只要当其中任何一次插入失败则失败。\n\n最终实现方法是使用`async.eachSeries`，以下代码实现上述问题：\n\n```js\ninsertSurvey: function (data,callback) {\n\t//从mysql连接池获取连接\n\tpool.getConnection(function(err,connection){\n\t\tif( err ){\n\t\t\tcallback(err);\n\t\t\treturn;\n\t\t}\n\t\tconnection.query( \"insert into t_survey(... , ... ,...) values(..., ...)\", function(err,res){\n\t\t\tif(res){\n\t\t\t\t//问卷插入成功\n\t\t\t\tvar surveyId = res.insertId;\n\t\t\t\t//异步批量按序插入问题\n\t\t\t\t//拼接sql数组\n\t\t\t\t{...}\n\t\t\t\t/**\n\t\t\t\t * insertQuestionSql 是插入问题的语句数组\n\t\t\t\t * \n\t\t\t\t * insertQuestionSql = [\n\t\t\t\t * \t\tinsert into t_survey_question(createAt,type,.....) values(... ,.. , ...),\n\t\t\t\t * \t\tinsert into t_survey_question(createAt,type,.....) values(... ,.. , ...),\n\t\t\t\t * \t\t.....\n\t\t\t\t * ];\n\t\t\t\t */\n\t\t\t\tasync.eachSeries( insertQuestionSql, function(item,questioncb){\n\t\t\t\t\tconnection.query( item.survey, function(err, results) {\n\t\t\t\t\t\tif(err) {\n\t\t\t\t\t\t\tquestioncb(err,result);\n\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t//问题插入成功，继续插入选项\n\t\t\t\t\t\t\tvar questionId = results.insertId;\n\t\t\t\t\t\t\t//如果是单选或多选，存在多个选项，异步批量按序插入选项\n\t\t\t\t\t\t\t//拼接sql数组\n\t\t\t\t\t\t\t{...}\n\t\t\t\t\t\t\t/**\n\t\t\t\t\t\t\t * insertOptionSql 是需要插入的选项数组\n\t\t\t\t\t\t\t * \n\t\t\t\t\t\t\t * insertOptionSql = [\n\t\t\t\t\t\t\t * \t\tinsert into t_survey_option(createAt,type,.....) values(... ,.. , ...),\n\t\t\t\t\t\t\t * \t\tinsert into t_survey_option(createAt,type,.....) values(... ,.. , ...),\n\t\t\t\t\t\t\t * \t\t.....\n\t\t\t\t\t\t\t * ];\n\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\tif( item.data.type == 'radio' || item.data.type == 'checkbox' ){\n\t\t\t\t\t\t\t\tasync.eachSeries( insertOptionSql, function( optionitem, optioncb ){\n\t\t\t\t\t\t\t\t\tconnection.query( optionitem, function( operror,opresult){\n\t\t\t\t\t\t\t\t\t\tif(operror){\n\t\t\t\t\t\t\t\t\t\t\toptioncb(operror,opresult);\n\t\t\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\t\t\t//选项插入成功\n\t\t\t\t\t\t\t\t\t\t\toptioncb( opresult.insertId );\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t},function(err){\n\t\t\t\t\t\t\t\t\t//当前问题的所有选项插入结束\n\t\t\t\t\t\t\t\t\tif(err){\n\t\t\t\t\t\t\t\t\t\tcallback(err);\n\t\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\t\tcallback();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t//问答无选项，直接回调\n\t\t\t\t\t\t\t\tquestioncb();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},function(err) {\n\t\t\t\t\t//所有问题插入结束\n\t\t\t\t\tcallback(err);\n\t\t\t\t});\n\t\t\t}else if(err){\n\t\t\t\tcallback(err)\n\t\t\t}\n\t\t\t//回调结果\n\t\t\tcallback(result);\n\t\t\t//释放连接 \n            connection.release();\n\t\t})\n\t})\n},\n```\n最终的解决方案也是三层嵌套回调，也对应了对三个表的操作，暂时算是最优解决方案了。\n\n`async.eachSeries` 保证了SQL的执行顺序，而且当其中一条执行异常，就不会继续执行下一条，简单示例：\n\n```js\nvar sqls = [\n  \"INSERT INTO log SET data='data1'\",\n  \"INSERT INTO log SET data='data2'\",\n  \"INSERT INTO log SET data='data3'\"\n];\n\nasync.eachSeries(sqls, function(item, callback) {\n  // 遍历每条SQL并执行\n  connection.query(item, function(err, results) {\n    if(err) {\n      // 异常后调用callback并传入err\n      callback(err);\n    } else {\n      console.log(item + \"执行成功\");\n      // 执行完成后也要调用callback，不需要参数\n      callback();\n    }\n  });\n}, function(err) {\n  // 所有SQL执行完成后回调\n  if(err) {\n    console.log(err);\n  } else {\n    console.log(\"SQL全部执行成功\");\n  }\n});\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflute%2Fsurvey","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflute%2Fsurvey","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflute%2Fsurvey/lists"}