Nodejs连接MySQL数据库
- 最简单的数据库连接操作
- 封装数据库连接模块
- 从页面到数据库一个完整的数据请求过程
一、用于测试的数据库(用于第二三部分测试)
数据库:school --管理员表:school_admini --老师表:teacher --班级表:class --学生表:student --成绩表:grade
以上的数据库表在这篇博客中不会全部应用到,提供这些表的目的是后面可能会继续使用这个示例,方便练习拓展其他内容。
创建数据库级数据库表:
1 create database school; 2 3 create table `school_admini`( 4 `school_admini_id` int(11) not null auto_increment comment '管理员编号', 5 `school_admini_name` varchar(24) not null comment '管理员昵称', 6 `school_admini_phone` varchar(11) not null comment '管理员的联系电话', 7 `school_admini_email` varchar(50) comment '管理员的邮箱地址', 8 `school_admini_password` varchar(32) not null comment '管理员账号登入密码', 9 `school_admini_grade` int(1) default 1 comment '默认值为1表示普通管理员,其他还有(2,3)分别表示高级管理员和超级管理员', 10 PRIMARY KEY (`school_admini_id`) 11 )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 12 13 create table `teacher`( 14 `teacher_id` int(11) not null auto_increment comment '教师编号', 15 `teacher_name` varchar(32) not null comment '教师名字', 16 `teacher_domain` varchar(20) not null comment '所属专业', 17 `teacher_sex` int(1) not null comment '性别:0表示男,1表示女', 18 PRIMARY KEY (`teacher_id`) 19 )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 20 21 create table `class`( 22 `class_id` int(11) not null auto_increment comment '班级编号', 23 `class_number` int(7) not null comment '班级号码:(例:2019001)', 24 `class_domain` varchar(20) not null comment '所属专业', 25 `class_teacher_id` int(11) not null comment '班主任编号', 26 PRIMARY KEY (`class_id`), 27 constraint `fk_class_teacher` foreign key (class_teacher_id) references teacher(teacher_id) 28 )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 29 30 create table `student`( 31 `student_id` int(11) not null auto_increment comment '学生编号', 32 `student_name` varchar(32) not null comment '学生名字', 33 `student_age` int(2) not null comment '学生年龄', 34 `student_sex` int(1) not null comment '性别:0表示男,1表示女', 35 `student_class_id` int(11) not null comment '学生所属的班级编号', 36 `student_contacts` varchar(32) not null comment '学生的联系人名字', 37 `student_contacts_phone` varchar(11) not null comment '学生联系人电话', 38 `student_contacts_relation` varchar(20) not null comment '学生联系人的关系', 39 PRIMARY KEY (`student_id`), 40 constraint `fk_student_class` foreign key (student_class_id) references class(class_id) 41 )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 42 43 create table `grade`( 44 `grade_id` int(11) not null comment '成绩记录编号', 45 `grade_number` int(3) not null comment '成绩', 46 `grade_course` varchar(20) not null comment '科目', 47 `adjudicator` int(11) not null comment '打分老师', 48 `keyboarder` int(11) not null comment '成绩录入老师', 49 `grade_student_id` int(11) not null comment '成绩所属学生编号', 50 PRIMARY KEY (`grade_id`), 51 constraint `fk_grade_teacher_id_adjudicator` foreign key (adjudicator) references teacher(teacher_id), 52 constraint `fk_grade_teacher_id_keyboarder` foreign key (keyboarder) references teacher(teacher_id) 53 )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
二、最简单的数据库连接操作
MySQL官方问文档连接:https://dev.mysql.com/doc/ndbapi/en/ndb-nodejs.html
GitHub的文档手册:https://github.com/mysqljs/mysql
中文手册(来源:菜鸟教程):https://www.runoob.com/nodejs/nodejs-mysql.html
一个比较好的nodejs连接MySQL博客:http://blog.fens.me/nodejs-mysql-intro/
使用nodejs连接MySQL数据库“school”,并查询数据:
npm init //初识化--生成package.json npm install mysql -save-dev //下载mysql的nodejs驱动模块
基于school数据库示例,使用nodejs连接并查询MySQL数据库的数据:
1 let mysql = require("mysql"); 2 3 //创建mysql连接(配置连接参数:服务器地址、端口、mysql用户名称、mysql用户密码、数据库名称) 4 let connection = mysql.createConnection({ 5 host:"127.0.0.1", 6 port:"3306", 7 user:"****", 8 password:"********", 9 database:"school" 10 }); 11 12 let querySql = "select * from school_admini"; 13 connection.connect(); //打开链接 14 //query:数据操作(传入操作数据的sql语句,回调函数会包含两个参数:异常信息对象,正常数据操作返回的结果) 15 connection.query(querySql,function (err,result) { 16 console.log(result); 17 }); 18 connection.end();//关闭连接
在示例中connection通常被称为连接池,这里使用了简单的配置参数,还有其他的配置参数可以参考前面的连接。connection也就是配置每次连接数据库的必须参数,并且还封装有打开MySQL数据库连接的connect()方法、访问数据的query()方法、关闭MySQL数据库连接的end()方法。
在实际开发中访问数据的query()方法不会直接传入SQL语句,这样容易被SQL注入攻击,而是将访问条件参数使用(?)替代,然后将(?)替代的参数按顺序作为一个数组,传递给query()方法作为数据访问条件参数值(具体见第三节示例)。
query(querySql,[field1,field2...,fieldn],callback) //querySql--SQL操作语句 //field--?替代的参数值 //callback--SQL操作结果的回调函数, //回调包含两个参数:error,result分别表示错误对象和操作结果对象,有结果就没有错误,有错误就没有结果
三、封装数据库连接模块
通常情况下实际开发会将连接数据库作为一个公共模块提取出来,毕竟不可能在数据访问层(DAO层)的每个模块中写一次数据库访问代码,增加冗余不说时间不能用在这种事情上呀。
公共数据库连接模块:dbutil.js
let mysql = require("mysql"); //创建mysql连接(配置连接参数:服务器地址、端口、mysql用户名称、mysql用户密码、数据库名称) let connection = mysql.createConnection({ host:"127.0.0.1", port:"3306", user:"****", password:"******", database:"school" }); module.exports = connection;
示例:管理员使用电话号码和密码登入(Dao层+Service层)
1 //--dao--schoolAdminiDao.js 2 let connection = require("./dbutil"); //导入MySQL连接数据库的模块 3 4 function adminiByPhonePassword(adminiPhone,adminiPassword){ 5 let querySQL = "select * from school_admini where school_admini_phone=? and school_admini_password=?"; 6 let queryParams = [adminiPhone,adminiPassword]; 7 connection.connect(); //打开数据库连接 8 connection.query(querySQL, queryParams,function(err,result){ 9 if(!err){ 10 console.log("true"); 11 console.log(result); 12 }else{ 13 console.log("false"); 14 console.log(err); 15 } 16 }); 17 connection.end(); //关闭数据库连接 18 } 19 20 module.exports={ 21 "adminiByPhonePassword":adminiByPhonePassword 22 } 23 24 //--service--schoolAdminiService.js 25 let schoolAdminiDao = require("../dao/schoolAdminiDao"); 26 27 schoolAdminiDao.adminiByPhonePassword("13100001111","123456789");
四、从页面到数据库一个完整的数据请求过程
这是一个完整的demo,为了简化不必要的内容数据库我从新设计了一个更简单的。业务功能包括了班级管理员登入、班级学员的信息分页加载渲染、在管理员未登入的情况下访问班级学员信息页面会被拦截重定向到登入页面;前端采用了全原生,其中包括了封装ajax方法和getElementsByclass方法,这样让web项目中最核心的基础内容,而不是把太多精力花在研究框架AIP上。
1、数据库与项目结构
1.1数据库结构(collations)
数据库:collations
----班级信息表:table_class
----学员信息表:table_student
1.2创建数据库及表的代码和测试数据
1 #创建数据库 2 create database collations; 3 4 #创建数据库表 5 #table_class表 6 CREATE TABLE `table_class` ( 7 `classId` int(11) NOT NULL AUTO_INCREMENT COMMENT '班级id,自增长,主键', 8 `classNumName` int(7) NOT NULL COMMENT '班级数字名称:例如"年份001"', 9 `className` varchar(30) NOT NULL COMMENT '班级名称:例如“计算机科学一班”', 10 `classAdministrator` char(11) NOT NULL COMMENT '班级管理员账号:用手机号注册', 11 `classAdministratorPassword` varchar(32) NOT NULL COMMENT '管理员密码:长度8~16字母数字字符', 12 PRIMARY KEY (`classId`) 13 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; 14 15 #table_student表 16 CREATE TABLE `table_student` ( 17 `studentId` int(11) NOT NULL AUTO_INCREMENT COMMENT '学生编号,自动递增,主键,非空', 18 `studentName` varchar(32) NOT NULL COMMENT '学生名字', 19 `studentClass` int(11) NOT NULL COMMENT '学生所属班级的编号,外键', 20 `studentAge` date NOT NULL, 21 `studentSex` int(11) NOT NULL, 22 PRIMARY KEY (`studentId`) 23 ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4; 24 25 #添加测试数据 26 insert into table_class(classNumName,className,classAdministrator,classAdministratorPassword) values (2020001,"计算机科学一班","13011112222","123456.."); 27 28 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("张三","1","2000-03-15",1); 29 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("李四","1","1999-09-12",1); 30 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("王五","1","1998-011-08",1); 31 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("小红","1","1999-04-20",0); 32 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("小明","1","2000-05-14",1); 33 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("涛涛","1","1999-06-06",1); 34 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("圆圆","1","2000-01-01",0); 35 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("六儿","1","1999-09-18",1); 36 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("三儿","1","1999-08-05",0); 37 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("菲菲","1","1999-12-11",0); 38 insert into table_student(studentName,studentClass,studentAge,studentSex) values ("丫丫","1","1998-03-15",0);
1.3项目结构
--工具区间
----node_modules(这个demo只需一个mysql模块)
----dao
------dbutil.js
------classDao.js
------studentDao.js
----filter
------loginFilter.js
----log
------server.log
----page
------css
--------studentInfor.css
------html
--------login.html
--------studentInfor.html
------js
--------login.js
--------studentInfor.js
----service
------classService.js
------studentService.js
----tool
------variableType.js
----web
------classWeb.js
------studentWeb.js
----cache.js
----config.js
----filterLoader.js
----index.js
----loader.js
----log.js
----package.json
----server.config
1 let mysql = require("mysql"); 2 3 //创建mysql链接(配置连接参数:服务器地址、端口、mysql用户名称、mysql用户密码、数据库名称) 4 5 function createConnection(){ 6 let connection = mysql.createConnection({ 7 host:"127.0.0.1", 8 port:"3306", 9 user:"****", 10 password:"******", 11 database:"nodethrough" 12 }); 13 return connection; 14 } 15 16 let pool = mysql.createPool({ 17 connectionLimit:10, 18 host:"127.0.0.1", 19 port:"3306", 20 user:"****", 21 password:"*******", 22 database:"school" 23 }); 24 25 module.exports ={ 26 "createConnection":createConnection, 27 "pool":pool 28 };
1 let dbutilModul = require("./dbutil.js"); 2 3 function classAdminiByPhonePassword(phone,password,responseCallback){ 4 let queryParams = [phone,password]; 5 let querySQL = "select * from table_class where classAdministrator=? and classAdministratorPassword=?"; 6 let connection = dbutilModul["createConnection"](); 7 connection.query(querySQL,queryParams,function(err,result){ 8 if(!err){ 9 responseCallback(result); 10 }else{ 11 responseCallback("error-dao"); 12 //日志记录错误 13 //... 14 } 15 }); 16 connection.end(); 17 } 18 19 module.exports = { 20 "classAdminiByPhonePassword":classAdminiByPhonePassword 21 }
1 let dbutilModule = require("./dbutil.js"); 2 3 //基于学生班级编号classId查询学生信息(分页查询) 4 //参数----classId:班级编号、offset:偏移量、limit:查询多少条数据 5 //将获取到指定班级的学生信息传递给消息响应回到函数resqonseCallback--web层传递过来的 6 function getStudentInforByClassId(classId,offset,limit,responseCallback){ 7 let queryParams = [classId,offset,limit]; 8 let querySQL = "select * from table_student where studentClass=? limit ?, ?"; 9 let connection = dbutilModule["createConnection"](); 10 connection.query(querySQL,queryParams,function(err,result){ 11 if(!err){ 12 responseCallback(result); 13 }else{ 14 responseCallback("error-dao"); 15 //日志记录错误 16 //... 17 } 18 }); 19 connection.end(); 20 } 21 //基于学生班级编号classId查询该班级的学生总人数 22 function getNumberStudentByClassId(classId,responseCallback){ 23 let queryParams = [classId]; 24 let querySQL = "select count(*) as NumberStudent from table_student where studentClass=?"; 25 let connection = dbutilModule["createConnection"](); 26 connection.query(querySQL,queryParams,function(err,result){ 27 if(!err){ 28 responseCallback(result); 29 }else{ 30 responseCallback("error-dao"); 31 } 32 }); 33 connection.end(); 34 } 35 36 module.exports = { 37 "getStudentInforByClassId":getStudentInforByClassId, 38 "getNumberStudentByClassId":getNumberStudentByClassId 39 }
1 let url = require("url"); 2 let serverConfig = require("../config.js"); //导入解析server.config配置文件模块 3 4 function loginFilter(request,response){ 5 let pathName = url.parse(request.url).path; 6 //当请求资源为登入页面:login.html、登入接口:/classAdminiLogin、以及静态文件时不拦截 7 if(pathName === "/html/login.html" || pathName === "/classAdminiLogin" || isStaticsRequest(pathName)){ 8 return true; 9 } 10 //当cookie中包含有classId的cookie名称时,表示当前用户已登入状态,不拦截 11 if(request.headers.cookie){ 12 let cookies = request.headers.cookie.split(";"); 13 for(let i = 0; i < cookies.length; i++){ 14 if(cookies[i].split("=")[0].trim() === "classid"){ 15 return true; 16 } 17 } 18 } 19 response.writeHead(302,{"location":"/html/login.html"}); 20 response.end(); 21 return false; 22 } 23 24 //判断是否请求静态资源的工具方法 25 function isStaticsRequest(pathName){ 26 for(let i = 0; i < serverConfig["staticFileType"].length; i++){ 27 let temp = serverConfig["staticFileType"][i]; 28 //注意html静态文件类型需要拦截,其他静态文件不拦截 29 if(temp == ".html"){ 30 continue; 31 } 32 if(pathName.indexOf(temp) === pathName.length - temp.length){ 33 return true; 34 } 35 return false; 36 } 37 } 38 39 module.exports = loginFilter;
1 *{ 2 margin: 0; 3 padding: 0; 4 } 5 li{ 6 list-style: none; 7 } 8 9 :root{ 10 width: 100%; 11 } 12 body{ 13 width: 100%; 14 position: absolute; 15 } 16 .content{ 17 position: relative; 18 width: 450px; 19 left: 50%; 20 transform: translate(-50%,10px); 21 } 22 .stuInfoHead{ 23 font-weight: 600; 24 } 25 .stuInfoList li{ 26 border-bottom: 1px solid #2b2b2b; 27 } 28 .stuInfoList span{ 29 display: inline-block; 30 width: 150px; 31 height: 25px; 32 text-align: center; 33 }
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>登入学生信息管理系统</title> 7 </head> 8 <body> 9 <form method="post" action="/classAdminiLogin"> 10 <div class="a"><span>账号:</span><input name="account" type="text"></div> 11 <div><span>密码:</span><input name="password" type="password"></div> 12 <div><input class="loginBut" type="submit" value="登入"></div> 13 </form> 14 <script src="../js/login.js"></script> 15 </body> 16 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Document</title> 7 <link href="../css/studentInfor.css" type="text/css" rel="stylesheet"></link> 8 </head> 9 <body> 10 <div class="content"> 11 <ul class="stuInfoList"> 12 <li class="stuInfoHead"><span>姓名</span><span>年龄</span><span>性别</span></li> 13 </ul> 14 <div> 15 <button class="lastBut">上一页</button> 16 <input class="stuPage" type="text" value="1"> 17 <button class="skipBut">跳转</button> 18 <button class="nextBut">下一页</button> 19 <span class="pageText"></span> 20 </div> 21 </div> 22 <script src="../js/studentInfor.js"></script> 23 </body> 24 </html>
1 let contentDom = getElementsByClass("content")[0]; 2 let loginButDom = getElementsByClass("loginBut",contentDom)[0]; 3 console.log(contentDom); 4 console.log(loginButDom); 5 // loginButDom.onclick = function(event){ 6 // console.log("发送请求"); 7 // ajaxFunc("post","/classAdminiLogin",{phone:"13011112222",password:"123456.."},function (responseText) { 8 // console.log(responseText); 9 // if(responseText === "ok"){ 10 // alert("成功!"); 11 // }else{ 12 // alert("失败"); 13 // } 14 // },true); 15 // } 16 17 18 //工具方法 19 //1:自定义兼容的获取DOM的方法 20 //2: 自定义获取当前DOM节点下的元素子节点集合 21 //3:自定义获取当前DOM节点下所有后代元素节点集合 22 //4:自定义兼容的ajax方法 23 //5: 自定义克隆元素节点的方法 --还未实现 24 25 //1 26 //参数:className--字符串:包含一个或多个class名称的字符串,使用空格间隔 27 //参数(可选):context--当在全局获取指定class属性的元素节点时可以省略或者写document; 28 // 当在局部获取指定class属性的元素节点时,写入自定范围的DOM节点 29 //--该方法需要工具方法3、4配合实现 30 //--以下两个测试-- 31 // let contentDom = getElementsByClass("content"); 32 // console.log(getElementsByClass("conversationBox",contentDom)); 33 function getElementsByClass(classNames, context) { 34 context = context || document; 35 let classNameAry = classNames.replace(/(^ +| +$)/g,"").split(/ +/g); 36 let ary = []; 37 let nodeList = []; 38 if(context === document){ 39 nodeList = context.getElementsByTagName("*"); 40 }else{ 41 retElementDescendant(context,nodeList); 42 } 43 for(let i = 0,len = nodeList.length; i < len; i++){ 44 let curNode = nodeList[i]; 45 let isOk = true; 46 for(let k = 0; k < classNameAry.length; k++){ 47 let curName = classNameAry[k]; 48 let reg = new RegExp("(^| +)" + curName + "( +|$)"); 49 if(!reg.test(curNode.className)){ 50 isOk = false; 51 break; 52 } 53 } 54 if(isOk){ 55 ary.push(curNode); 56 } 57 } 58 return ary; 59 } 60 61 //2: 62 //参数:node--元素节点:用于当前方法解析其子元素 63 function retElementChild(node){ 64 //如果原型上不包含length属性表示当前元素节点没有元素子节点,并返回null 65 if(node.hasOwnProperty("lenght")) return null; 66 let temp = [], 67 child = node.childNodes, 68 len = child.length; 69 for(let i = 0; i < len; i++){ 70 if(child[i].nodeType === 1){ 71 temp.push(child[i]); 72 } 73 } 74 return temp; 75 } 76 //3: 77 //参数originEle--元素节点、元素节点数组、元素节点类数组:用户当前方法解析其所有后代元素节点 78 //参数targetArr--数组类型:用户接收解析的元素节点容器 79 function retElementDescendant(originEle, targetArr){ 80 originEle = Array.prototype.isPrototypeOf(originEle) ? originEle : [originEle]; 81 let childEle = []; 82 let lap = []; 83 for(let j = 0; j < originEle.length; j++){ 84 childEle = retElementChild(originEle[j]); 85 if(!childEle) continue; 86 for(let ele in childEle){ 87 targetArr.push(childEle[ele]); 88 lap.push(childEle[ele]); 89 } 90 } 91 if(lap.length){ 92 retElementDescendant(lap, targetArr); 93 } 94 } 95 96 //4 97 function ajaxFunc(method,url,data,callback,flag){ 98 //参数:method(提交请求方法):get、post; 99 // url(提交地址):服务器地址; 100 // data(提交数据):采用JSON格式; 101 // callback(处理服务器响应数据的方法); 102 // flag(异步还是同步提交):true(异步)、false(同步); 103 104 //创建一个ajax对象 105 let xhr = null; 106 if(window.XMLHttpRequest){ 107 // 创建除IE以外的ajax对象 -- code f for IE7+, FireFox, Chrome, Opera, Safari 108 xhr = new XMLHttpRequest(); 109 }else{ 110 // 创建兼容IE的ajax对象 -- code for IE6, IE5 111 xhr = new ActiveXObject("Microsoft.XMLHttp"); 112 } 113 //将get、post转成大写 114 method = method.toUpperCase(); 115 //将dta的JSON格式数据转换拼接成可直接提交的字符串形式 116 data = (function(data){ 117 let httpText = ""; 118 if(!data){ 119 return null; 120 } 121 for(let temp in data){ 122 httpText = httpText + (temp + '=' + data[temp] + '&'); 123 } 124 return httpText; 125 })(data); 126 //发起ajax请求 127 if(method === 'GET'){ 128 //发起GET方法的请求 129 xhr.open(method, url + '?' + data, flag); 130 }else if(method === 'POST'){ 131 //发起POST方法的请求 132 xhr.open(method, url, flag); 133 xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded'); 134 xhr.send(data); 135 } 136 //监听物理信息 137 xhr.onreadystatechange = function(){ 138 // 监听到readystate=4时 139 // 解析服务器返回的responseText数据 140 if(xhr["readyState"] === 4){ 141 //判断响应状态是否为200--表示请求成功响应 142 if(xhr["status"] === 200){ 143 callback(xhr["responseText"]); 144 } 145 } 146 } 147 }
1 let stuInfoRenderParam = { 2 size:5, 3 page:1 4 } 5 window.onload = renderStuInfo; 6 let pageMax = 0; 7 function renderStuInfo(){ 8 stuInfoRenderParam.page = +getElementsByClass("stuPage")[0].value; 9 ajaxFunc("post","/getStuInfoAndSumByClassId",stuInfoRenderParam,function(data){ 10 pageMax = parseInt((JSON.parse(data)["sum"] + stuInfoRenderParam["size"]) / stuInfoRenderParam["size"]) 11 let stuInfoListDom = getElementsByClass("stuInfoList")[0]; 12 let pageTextDom = getElementsByClass("pageText")[0]; 13 pageTextDom.innerText = "共"+ pageMax + "页"; 14 let stuContent = '<li class="stuInfoHead"><span>姓名</span><span>年龄</span><span>性别</span></li>'; 15 let studentInfo = JSON.parse(data)["studentsInfor"]; 16 for(let i = 0; i < studentInfo.length; i++){ 17 let dateStr = studentInfo[i]["studentAge"].split("T")[0]; 18 let sex = studentInfo[i]["studentSex"] === 1 ? "男" : "女"; 19 stuContent += '<li><span>' + studentInfo[i]["studentName"] + '</span><span>' + ages(dateStr) +'</span><span>' + sex + '</span></li>'; 20 } 21 stuInfoListDom.innerHTML = stuContent; 22 },true); 23 } 24 25 let lastButDom = getElementsByClass("lastBut")[0];//上一页按钮 26 let skipButDom = getElementsByClass("skipBut")[0];//跳转按钮 27 let nextButDom = getElementsByClass("nextBut")[0];//下一页按钮 28 lastButDom.onclick = function(){ 29 let pageInputDom = getElementsByClass("stuPage")[0]; 30 let page = +pageInputDom.value; 31 let newPage = page <= 1 ? 1 : --page; 32 console.log(newPage,stuInfoRenderParam.page); 33 if(newPage !== stuInfoRenderParam.page){ 34 pageInputDom.value = newPage; 35 stuInfoRenderParam.page = newPage; 36 renderStuInfo(); 37 } 38 } 39 40 skipButDom.onclick = function(){ 41 let pageInputDom = getElementsByClass("stuPage")[0]; 42 let page = +pageInputDom.value; 43 if(page > 0 && page <= pageMax && page !== stuInfoRenderParam.page){ 44 stuInfoRenderParam.page = page; 45 renderStuInfo(); 46 } 47 } 48 49 nextButDom.onclick = function(){ 50 let pageInputDom = getElementsByClass("stuPage")[0]; 51 let page = +pageInputDom.value; 52 let newPage = page >= pageMax ? page : ++page; 53 if(newPage !== stuInfoRenderParam.page){ 54 pageInputDom.value = newPage; 55 stuInfoRenderParam.page = newPage; 56 renderStuInfo(); 57 } 58 } 59 60 61 62 //1 63 //参数:className--字符串:包含一个或多个class名称的字符串,使用空格间隔 64 //参数(可选):context--当在全局获取指定class属性的元素节点时可以省略或者写document; 65 // 当在局部获取指定class属性的元素节点时,写入自定范围的DOM节点 66 //--该方法需要工具方法3、4配合实现 67 //--以下两个测试-- 68 // let contentDom = getElementsByClass("content"); 69 // console.log(getElementsByClass("conversationBox",contentDom)); 70 function getElementsByClass(classNames, context) { 71 context = context || document; 72 let classNameAry = classNames.replace(/(^ +| +$)/g,"").split(/ +/g); 73 let ary = []; 74 let nodeList = []; 75 if(context === document){ 76 nodeList = context.getElementsByTagName("*"); 77 }else{ 78 retElementDescendant(context,nodeList); 79 } 80 for(let i = 0,len = nodeList.length; i < len; i++){ 81 let curNode = nodeList[i]; 82 let isOk = true; 83 for(let k = 0; k < classNameAry.length; k++){ 84 let curName = classNameAry[k]; 85 let reg = new RegExp("(^| +)" + curName + "( +|$)"); 86 if(!reg.test(curNode.className)){ 87 isOk = false; 88 break; 89 } 90 } 91 if(isOk){ 92 ary.push(curNode); 93 } 94 } 95 return ary; 96 } 97 98 //2: 99 //参数:node--元素节点:用于当前方法解析其子元素 100 function retElementChild(node){ 101 //如果原型上不包含length属性表示当前元素节点没有元素子节点,并返回null 102 if(node.hasOwnProperty("lenght")) return null; 103 let temp = [], 104 child = node.childNodes, 105 len = child.length; 106 for(let i = 0; i < len; i++){ 107 if(child[i].nodeType === 1){ 108 temp.push(child[i]); 109 } 110 } 111 return temp; 112 } 113 //3: 114 //参数originEle--元素节点、元素节点数组、元素节点类数组:用户当前方法解析其所有后代元素节点 115 //参数targetArr--数组类型:用户接收解析的元素节点容器 116 function retElementDescendant(originEle, targetArr){ 117 originEle = Array.prototype.isPrototypeOf(originEle) ? originEle : [originEle]; 118 let childEle = []; 119 let lap = []; 120 for(let j = 0; j < originEle.length; j++){ 121 childEle = retElementChild(originEle[j]); 122 if(!childEle) continue; 123 for(let ele in childEle){ 124 targetArr.push(childEle[ele]); 125 lap.push(childEle[ele]); 126 } 127 } 128 if(lap.length){ 129 retElementDescendant(lap, targetArr); 130 } 131 } 132 133 134 //4 135 function ajaxFunc(method,url,data,callback,flag){ 136 //参数:method(提交请求方法):get、post; 137 // url(提交地址):服务器地址; 138 // data(提交数据):采用Object数据类型,当get请求模式时转换为字符串拼接形式,当post请求模式时转换为JSON数据格式; 139 // callback(处理服务器响应数据的方法); 140 // flag(异步还是同步提交):true(异步)、false(同步); 141 142 //创建一个ajax对象 143 let xhr = null; 144 if(window.XMLHttpRequest){ 145 // 创建除IE以外的ajax对象 -- code f for IE7+, FireFox, Chrome, Opera, Safari 146 xhr = new XMLHttpRequest(); 147 }else{ 148 // 创建兼容IE的ajax对象 -- code for IE6, IE5 149 xhr = new ActiveXObject("Microsoft.XMLHttp"); 150 } 151 //将get、post转成大写 152 method = method.toUpperCase(); 153 //发起ajax请求 154 if(method === 'GET'){ 155 //将dta的JSON格式数据转换拼接成可直接提交的字符串形式 156 data = (function(data){ 157 let httpText = ""; 158 if(!data){ 159 return null; 160 } 161 for(let temp in data){ 162 httpText = httpText + (temp + '=' + data[temp] + '&'); 163 } 164 return httpText; 165 })(data); 166 //发起GET方法的请求 167 xhr.open(method, url + '?' + data, flag); 168 }else if(method === 'POST'){ 169 //发起POST方法的请求 170 xhr.open(method, url, flag); 171 xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded'); 172 xhr.send(JSON.stringify(data)); 173 } 174 //监听物理信息 175 xhr.onreadystatechange = function(){ 176 // 监听到readystate=4时 177 // 解析服务器返回的responseText数据 178 if(xhr["readyState"] === 4){ 179 //判断响应状态是否为200--表示请求成功响应 180 if(xhr["status"] === 200){ 181 callback(xhr["responseText"]); 182 } 183 } 184 } 185 } 186 187 //5通过出生年月计算年龄 188 function ages(str){ 189 let r = str.match(/^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2})$/); 190 if(r==null)return false; 191 let d=new Date(r[1],r[3]-1, r[4]); 192 if(d.getFullYear()==r[1]&&(d.getMonth()+1)==r[3]&&d.getDate()==r[4]){ 193 let Y = new Date().getFullYear(); 194 return Y-r[1]; 195 } 196 return false; 197 }
1 let classDao = require("../dao/classDao.js"); 2 3 //班级管理员登入业务层,负责验证参数并调用DAO层 4 function adminiLoginService(adminiLoginParameter,responseCallback){ 5 let phone = adminiLoginParameter["account"]; 6 let password = adminiLoginParameter["password"]; 7 let regPhone = /^[1][\d]{10}/g; 8 let regPassword = /^[\w\~\!\@\#\$\%\^\&\*\(\)\_\+\`\[\]\{\}\;\'\\\:\"\|\,\.\/\<\>\?]{8,16}$/g; 9 if( regPhone.test(phone) && regPassword.test(password)){ 10 classDao.classAdminiByPhonePassword(phone,password,responseCallback); 11 }else{ 12 responseCallback("error-service"); 13 } 14 } 15 16 module.exports = { 17 "adminiLoginService":adminiLoginService 18 }
1 let studentDao = require("../dao/studentDao.js"); 2 let variableType = require("../tool/variableType.js"); 3 4 //班级学生信息查询和班级学生总人数查询业务层(管理员登入后页需要的数据),负责通过web层传递过来的getStudentsInforParams数据对象计算出DAO查询需要的数据 5 //getStudentsInforParams包括以下参数: 6 //----classId:班级ID,如果没有数据直接响应客户端出错 7 //----size:一页需要的数据条数 8 //----page:查询第几页的数据 9 function getStudentInforByClassId(getStudentsInforParams,responseCallback){ 10 if(getStudentsInforParams["classId"]){ 11 let offset = (getStudentsInforParams["page"] -1) * getStudentsInforParams["size"] ; 12 let resultArr = [];//用于缓存从db获取的数据 13 let containerTime = 0;//用于标识db操作次数,给后面处理逻辑提供参照 14 let responseData = {};//用于缓存响应数据,基于db获取的数据处理后的数据 15 let controller = function(result){ 16 resultArr.push(result); 17 containerTime++; 18 if(containerTime === 2){ 19 for(let i = 0; i < resultArr.length; i++){ 20 if(variableType.typeOf(resultArr[i]) === "string"){ 21 responseData = resultArr[i]; 22 break; 23 }else{ 24 if( resultArr[i][0] && resultArr[i][0].hasOwnProperty("NumberStudent")){ 25 responseData["sum"] = resultArr[i][0]["NumberStudent"]; 26 }else{ 27 responseData["studentsInfor"] = resultArr[i]; 28 } 29 } 30 } 31 responseCallback(responseData); 32 } 33 } 34 studentDao.getStudentInforByClassId(getStudentsInforParams["classId"],offset,getStudentsInforParams["size"],controller); 35 studentDao.getNumberStudentByClassId(getStudentsInforParams["classId"],controller); 36 }else{ 37 responseCallback("error-service"); 38 } 39 } 40 module.exports = { 41 "getStudentInforByClassIdService":getStudentInforByClassId 42 }
1 function myTypeof(value){ 2 var result = NaN; 3 var valOf = typeof value; 4 var inOf = value instanceof String || value instanceof Number || value instanceof Boolean; 5 var typeObj = { 6 "[object Object]":"object", 7 "[object Array]":"array", 8 "[object Function]":"function", 9 "[object Date]":"date", 10 "[object Error]":"error", 11 "[object JSON]":"json", 12 "[object Math]":"math", 13 "[object RegExp]":"regExp", 14 "[object Boolean]":"boolean", 15 "[object String]":"string", 16 "[object Number]":"number", 17 "[object Undefined]":"undefined", 18 "[object Null]":"null" 19 } 20 21 var str = Object.prototype.toString.call(value); 22 for(var i in typeObj){ 23 if(i == str){ 24 result = typeObj[i]; 25 break; 26 } 27 } 28 if( result === "number" && isNaN(value)){ //此处可以考虑 value === "NaN"这种情况 29 return NaN; 30 } 31 if( inOf && valOf === "object"){ 32 return result + "-Object"; 33 } 34 return result; 35 } 36 37 module.exports = { 38 "typeOf":myTypeof 39 }
1 // index.js -- (调用web接口) -- request,response ==>-----------------------------------> 2 // | <-----通过request拿到客户端的请求参数 3 // 调用classService获取数据 ==> 4 // | 5 //使用response将classService处理好的数据响应给客户端<—— 6 7 let classService = require("../service/classService.js"); 8 let pathMap = new Map(); //定义一个路径-接口的容器,最终将这个容器包含的所有接口与对应路径的Map作为模块导出 9 10 //班级管理员登入接口 11 function adminiLoginFun(request,response){ 12 request.on("data",function(data){ 13 let adminiLoginParameter = (function(data){ 14 let param = {}; 15 data = data.toString(); 16 let eleArr = data.split("&"); 17 for(let i = 0, len = eleArr.length; i < len; i++){ 18 let ele = eleArr[i].split("="); 19 param[ele[0]] = ele[1]; 20 } 21 return param; 22 })(data); 23 classService.adminiLoginService(adminiLoginParameter,function(result){ 24 try{ 25 let cookieDate = new Date(Date.now() + 1000 * 60 * 20);//设定cookieDate缓存20分钟 26 if(result && result !== "error-service" && result !=="error-dao"){ 27 let classIdCookie = "classid=" + result[0]["classId"] + "; expires=" + cookieDate.toString(); 28 response.setHeader("Set-Cookie",[classIdCookie]); 29 //ajax事件请求响应 30 // response.writeHead(200); 31 // response.write("ok"); 32 //表单请求重定向响应 33 response.writeHead(302,{"location":"/html/studentInfor.html"}); 34 response.end(); 35 }else{ 36 response.writeHead(500); 37 response.write("<html><head> <meta charset='UTF-8'></head><h3>Error 500</h3><p>An unknown error occurred in the service:"+ result + "</p></html>"); 38 response.end(); 39 } 40 }catch(e){ 41 response.writeHead(500); 42 response.write("error-web"); 43 response.end(); 44 } 45 }); 46 }); 47 } 48 //将接口与对应路径装进容器 49 pathMap.set("/classAdminiLogin",adminiLoginFun); 50 51 //导出关于班级的所有网络接口 52 module.exports.pathMap= pathMap;
1 let studentService = require("../service/studentService.js"); 2 let pathMap = new Map(); 3 4 5 //班级管理员登入后学生信息页面数据接口 6 function getStuInfoAndSumByClassId(request,response){ 7 request.on("data",function(data){ 8 //data中应该包含数据:size(一页需要的数据条数),page(查询第几页数据) 9 let params = JSON.parse(data); 10 //从请求报文的cookie中解析classId 11 if(request.headers.cookie && !params.hasOwnProperty("classId")){ 12 let cookies = request.headers.cookie.split(";"); 13 for(let j = 0; j < cookies.length; j++){ 14 let cookieEle = cookies[j].split("="); 15 if(cookieEle[0].trim() === "classid"){ 16 params["classId"] = +cookieEle[1].trim(); 17 } 18 } 19 } 20 //调用业务层 21 studentService.getStudentInforByClassIdService(params,function(result){ 22 try{ 23 let cookieDate = new Date(Date.now() + 1000 * 60 * 20);//设定cookieDate缓存20分钟; 24 if(result !== "error-service" && result !== "error-dao"){ 25 let classIdCookie = "classid=" + params["classId"] + "; expires=" + cookieDate.toString(); 26 response.setHeader("Set-Cookie",[classIdCookie]); 27 response.writeHead(200); 28 response.write(JSON.stringify(result)); 29 response.end(); 30 }else{ 31 response.writeHead(500); 32 response.write(result); 33 response.end(); 34 } 35 }catch{ 36 response.writeHead(500); 37 response.write(result); 38 response.end(); 39 } 40 }); 41 }); 42 } 43 44 //将接口与对应路径装进容器 45 pathMap.set("/getStuInfoAndSumByClassId",getStuInfoAndSumByClassId); 46 47 module.exports.pathMap = pathMap;
1 let crypto = require("crypto"); 2 3 //禁止缓存 4 function noCacheFun(response){ 5 response.setHeader("Cache-Control","no-Store"); 6 response.setHeader("Expires","-1"); 7 return true; 8 } 9 10 //强缓存-- 11 //--通过response会话对象给相应报文添加强制缓存首部 12 //--缓存实际设置为一个月 13 //--scope可以用来指定缓存范围,默认取值public 14 //----取值public:客户端和代理服务器都可以缓存 15 //----取值private:只有客户端可以缓存 16 function compelCacheFun(response,scope){ 17 scope = scope | "public"; 18 cacheE(response); 19 response.setHeader("Cache-Control","max-age=2592000," + scope); 20 return true; 21 } 22 23 //协商缓存 24 //--参数:http请求对象request:用于获取请求报文中携带的if-none-match首部数据(该数据有服务器响应给客户端的ETag提供代理缓存) 25 //----http会话响应对象response:用于设置缓存首部属性及再验证响应 26 //----data:http客户端需要的数据资源,用户生成数据签名flag 27 //----scope:指定缓存范围(参考强缓存说明) 28 function consultCacheFun(request,response,data,scope){ 29 scope = scope | "public"; 30 let md5 = crypto.createHash("md5"); 31 let flag = md5.update(data).digest("hex"); 32 cacheE(response); 33 response.setHeader("ETag",flag); 34 response.setHeader("Cache-Control","no-cache,max-age=2592000," + scope); 35 if(request.headers["if-none-match"] === flag){ 36 response.writeHead(304); 37 response.end(); 38 return false; 39 } 40 return true; 41 } 42 43 44 function cacheE(response,scope){ 45 let shelfLife = new Date(Date.now()); 46 let shelfLifeMonth = shelfLife.getMonth(); 47 if(shelfLifeMonth < 11){ 48 shelfLife.setMonth(shelfLifeMonth + 1); 49 }else{ 50 shelfLife.setFullYear(shelfLife.getFullYear() + 1); 51 shelfLife.setMonth(0); 52 } 53 response.setHeader("Expires",shelfLife.toUTCString()); 54 } 55 56 module.exports = { 57 "no-cache":noCacheFun, 58 "compel-cache":compelCacheFun, 59 "consult-cache":consultCacheFun 60 }
1 const fs = require("fs"); 2 3 //解析服务配置文件server.config的配置内容 4 function analysisConfig(configFile){ 5 let obj = {}; 6 let arr = configFile.toString().split("\r\n"); 7 for(let i = 0; i < arr.length; i++){ 8 let item = arr[i].split("="); 9 if(item[0] === "static_file_type"){ 10 obj["staticFileType"] = item[1].split("|"); 11 }else{ 12 obj[item[0]] = item[1]; 13 } 14 } 15 return obj; 16 } 17 18 let configFile, configObj = {}; 19 20 //读取服务的配置文件server.config并调用解析方法analysisConfig解析生成配置模块对象 21 try{ 22 configFile = fs.readFileSync("./server.config"); 23 configObj = analysisConfig(configFile); 24 }catch(e){ 25 console.log("解析server.config配置文件出错:",e); 26 } 27 28 module.exports = configObj;
1 //解析filter路径下所有文件,然后导出拦截器集合 2 let fs = require("fs"); 3 let serverConig = require("./config.js"); 4 let filterSet=[]; 5 let files = fs.readdirSync(serverConig["filter_path"]); 6 for(let i = 0; i < files.length; i++){ 7 let temp = require("./" + serverConig["filter_path"] + files[i]); 8 filterSet.push(temp); 9 } 10 module.exports = filterSet;
1 let http = require("http"); 2 let fs = require("fs"); 3 let url = require("url"); 4 5 let serverConfig = require("./config.js"); //导入server.config系统配置文件的解析模块 6 let loader = require("./loader.js");//导入动态数据接口模块(接口-路由容器) 7 let cache = require("./cache.js");//导入HTTP请求缓存工具模块(禁止缓存、强缓存、协商缓存) 8 let log = require("./log.js");//导入日志工具模块(例如使用serverLogFun工具方法将请求路径记录到server.log日志中) 9 let filterSet = require("./filterLoader.js");//导入拦截器集合,用于检测是否有访问权 10 11 12 13 http.createServer(function(request,response){ 14 let pathName = url.parse(request.url).pathname; 15 //打日志--将被请求的静态文件路径和动态数据接口写入server.log 16 //拦截非登入请求:遍历拦截器,检测是否有访问权限 17 for(let i = 0; i < filterSet.length; i++){ 18 let flag = filterSet[i](request,response); 19 if(!flag){ 20 return; 21 } 22 } 23 let isStatic = isStaticsRequest(pathName);//判断是否请求静态资源 24 if(isStatic){ 25 //这里处理静态数据请求 26 try{ 27 let data = fs.readFileSync(serverConfig["page_path"] + pathName); //读取请求资源 28 if(cache["consult-cache"](request,response,data)){ //给静态资源设置协商缓存 29 response.writeHead(200); 30 response.write(data); 31 response.end(); 32 } 33 }catch(e){ 34 response.writeHead(404); 35 response.write("<html><h1>404 NotFound</h1><p>I didn't find "+pathName+"!</p></body></html>") 36 response.end(); 37 } 38 }else{ 39 //这里处理动态数据请求 40 if(loader.get(pathName) != null){ 41 loader.get(pathName)(request,response); 42 } 43 } 44 }).listen(serverConfig["port"]); 45 46 //判断是否请求静态资源的工具方法 47 function isStaticsRequest(pathName){ 48 for(let i = 0; i < serverConfig["staticFileType"].length; i++){ 49 let temp = serverConfig["staticFileType"][i]; 50 if(pathName.indexOf(temp) !== -1 && pathName.indexOf(temp) === pathName.length - temp["length"]){ 51 return true; 52 } 53 } 54 return false; 55 }
1 //解析web路径下所有文件,然后将所有动态数据接口-路由封装到一个容器内,作为当前模块导出 2 let fs = require("fs"); 3 let serverConfig = require("./config.js"); 4 5 let pathMap = new Map(); 6 let files = fs.readdirSync(serverConfig["web_path"]); 7 for(let i = 0; i < files.length; i++){ 8 let temp = require("./" + serverConfig["web_path"] + files[i]); 9 if(temp.pathMap){ 10 for(let [key,value] of temp.pathMap){ 11 //验证当前API是否重名,如果重名则抛出错误阻止启动服务 12 if(pathMap.get(key) == null){ 13 pathMap.set(key,value); 14 }else{ 15 throw new Error("url path异常,url:" + key); 16 } 17 } 18 } 19 } 20 21 module.exports = pathMap;
1 let fs = require("fs"); 2 let serverConfig = require("./config.js"); 3 4 //来自网络的静态文件和动态数据接口请求记录日志 5 let serverLog = function(fileAndInterfaces){ 6 let serverLogFile = serverConfig["log_path"] + serverConfig["serverLog_name"]; 7 let data = fileAndInterfaces + "----" + Date.now() + "\n"; 8 fs.appendFile(serverLogFile,data,function(){}); 9 } 10 11 module.exports = { 12 "serverLogFun" : serverLog 13 }
1 { 2 "name": "nodeThrough", 3 "version": "1.0.0", 4 "main": "index.js", 5 "scripts": { 6 "test": "echo \"Error: no test specified\" && exit 1" 7 }, 8 "keywords": [], 9 "author": "", 10 "license": "ISC", 11 "devDependencies": { 12 "mysql": "^2.18.1" 13 }, 14 "description": "" 15 }
1 port=12306 2 page_path=page 3 static_file_type=.html|.js|.css|.json|.png|.jpg|.gif|.ico 4 web_path=web/ 5 log_path=log/ 6 filter_path=filter/ 7 serverLog_name=server.log