- 最簡單的數據庫連接操作
- 封裝數據庫連接模塊
- 從頁面到數據庫一個完整的數據請求過程
一、用於測試的數據庫(用於第二三部分測試)
數據庫: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