数据库
建立一张 student 表
字段:
id 学生 id, 使用无实际意义的字段, 作为主键
stu_num 学生学号, 可以唯一标识一位学生
name
age
class 班级号
psw 密码
注意: 只是一个 demo, 密码也是明文, 没有MD5加密, ajax请求, 不管是get还是post, 都是基于http协议, 都是明文传输, 都不安全.
登录验证验证的就是根据学生学号和密码进行验证.
然后随意给这张表添加几条记录
DAO层, 连接数据库
DBUtil.js
// 数据库工具定义的 js 文件
const mysql = require('mysql');
function createConnection() {
return mysql.createConnection({
host: '127.0.0.1',
port: '3306',
user: 'root',
password: 'vey0831',
database: 'school'
});
}
module.exports.createConnection = createConnection;
/*
这个文件用来导出一个创建数据库连接的方法, 里面包含了连接数据库需要所有信息.
每调用一次这个函数, 都会创建一个与 mysql 的连接
*/
studentDao.js
// 导入上面的 utils
const dbUtil = require('./dbutils');
// 此方法用来查询 student 表中所有的学生信息
function queryAllStudent(success) {
let querySQL = "select * from student;";
let connection = dbUtil.createConnection();
connection.connect();
connection.query(querySQL, function (error, data) {
if(error){
console.log(error);
} else {
// console.log(data);
success(data);
}
});
connection.end();
}
// 通过 stu_num 查询某个学生的信息
function queryStudentByStuNum(stuNum, success) {
let querySQL = "select * from student where stu_num = ?;";
let connection = dbUtil.createConnection();
connection.connect();
connection.query(querySQL, stuNum, function (error, data) {
if(error){
console.log(error);
} else {
success(data);
}
});
connection.end();
}
// 通过某个班级号, 查询这个班级内所有的学生信息
function queryStudentByClass(classNum, age) {
let querySQL = "select * from student where class = ? and age > ?;";
// 传入多个参数, 用数组装起来传递给 query 函数
let queryParams = [classNum, age];
connection.connect();
connection.query(querySQL, queryParams, function (error, data) {
if(error){
console.log(error);
} else {
console.log(data);
}
});
connection.end();
}
// 将以上三个方法导出以备服务层使用
module.exports = {
"queryAllStudent": queryAllStudent,
"queryStudentByClass": queryStudentByClass,
"queryStudentByStuNum": queryStudentByStuNum
};
这里有两个需要注意的点:
其一, 如果在 dbUtil 里面 直接导出一个创建好的 connection 对象, 那么就只可以查询一次数据库, 第二次查询将会报错, 因为每创建一次连接, 当查询完成之后, 是一定要将数据库连接关闭的, 否则当一千万个用户请求都需要查询的时候, 数据库的压力可想而知.
所以在这里导出了一个函数, 当需要查询, 创建一个连接的时候, 再去实例化一个 connection (即函数执行, 返回一个 connection 对象)执行查询任务, 查询完了之后, 依然将它关闭.
let connection = dbUtil.createConnection();
// 每创建一次连接任务实例化一次 connection
另外一个点是, 如果数据库查询, 需要传递参数, 不要拼串! 不要拼串! 不要拼串! 在数据库查询中, 拼串是一种很 low, 且很危险的行为, 主要是 SQL 注入.
服务层(service)
studentService.js
// 引入 DAO 层的方法
let studentDao = require('../dao/studentDao');
function queryAllStu(success) {
studentDao.queryAllStudent(success);
}
function queryStuNum(stuNum, suc) {
studentDao.queryStudentByStuNum(stuNum, suc);
}
module.exports = {
queryAllStu: queryAllStu,
queryStuNum: queryStuNum
};
// 这里是简单的查询任务, 在服务层的业务逻辑不算复杂, 所以只需要将上一层的方法在这里导出去就好了
// 但是一旦服务层的逻辑复杂了, 这里就需要写业务逻辑代码了
web层
loginController.js
const studentService = require('../service/studentService');
const url = require('url');
// 创建一个 map, 保存请求路径
// 例如, 用户需要登录, 那么请求就是这个样子的:
// xhr.open('GET', '/login');
// 其中, '/login' 就是 map 的 key, login() 方法就是对应的处理函数
// 然后将其导出, 给服务器(server.js)使用
let path = new Map();
// 查询所有学生的方法
function getData(req, res) {
studentService.queryAllStu(function (data) {
let userArr = [];
for (let i = 0; i < data.length; i ++){
console.log(data[i].name);
userArr.push(data[i].name);
}
res.writeHead(200);
res.write(userArr.toString());
res.end();
});
}
// 通过 GET 方法发送数据给后台.
function login(req, res) {
let query = url.parse(req.url, true).query;
studentService.queryStuNum(query.username, function (result) {
if(result == null || result.length === 0){
} else {
if(result[0].psw === query.password){
res.writeHead(200);
res.write("OK");
res.end();
} else {
res.writeHead(200);
res.write("Fail");
res.end();
}
}
})
}
// 通过 POST 请求发送数据给后台
function login(request, res) {
request.on('data', function (data) {
console.log(data.toString());
let stuNum = data.toString().split('&')[0].split('=')[1];
let password = data.toString().split('&')[1].split('=')[1];
studentService.queryStuNum(stuNum, function (data) {
if (data && data.length > 0) {
if(password === data[0].psw){
res.writeHead(200);
res.write('OK');
res.end();
} else {
res.writeHead(200);
res.write('Fail');
res.end();
}
}
})
})
}
path.set('/getData', getData);
path.set('/login', login);
module.exports.path = path;
服务器
server.js
const http = require('http'); // 创建 http 服务的模块
const url = require('url'); // 解析请求 url 的模块
const fs = require('fs'); // 读取文件模块
const config = require('./config'); // 全局的配置文件, 读取 server.conf 导出
const loader = require('./loader'); // 加载文件的模块, 后面的 loader.js
const log = require('./log'); // 打日志
http.createServer((request, response) => {
let pathName = url.parse(request.url).pathname;
let params = url.parse(request.url, true).query;
let isStatic = isStaticRequest(pathName);
log(pathName);
if(isStatic){ // 请求的是静态文件
console.log(config["page_path"] + pathName);
// 防止路径不对导致服务器停掉
try {
// 读取请求的静态文件, 将文件作为数据写回
let data = fs.readFileSync(config["page_path"] + pathName);
response.writeHead(200);
response.write(data);
response.end();
}catch (e) {
// 出错, 文件没有找到或者其他问题出现, 返回一个 404 页面
response.writeHead(404);
response.write("<html><body><h1>404 Not Found</h1></body></html>");
response.end();
}
} else { // 请求动态数据
if(loader.get(pathName) !== null){
try {
loader.get(pathName)(request, response);
} catch (e) {
response.writeHead(500);
response.write("<html><body><h1>500 Bad Server</h1></body></html>");
response.end();
}
} else {
response.writeHead(404);
response.write("<html><body><h1>404 Not Found</h1></body></html>");
response.end();
}
}
}).listen(config["port"]);
log('服务已启动');
// 判断是否是静态文件的方法
// pathName.length - temp.length
// 例如: index.html?a=1&b=2
// pathName: /index.html
// pathName.length: 11
// temp.length: 5
// pathName.indexOf('.html'): 6
// pathName.length - temp.length = 11 - 5 = 6
isStaticRequest = pathName => {
for (let i = 0; i < config['static_file_type'].length; i ++){
let temp = config['static_file_type'][i];
if(pathName.indexOf(temp) === pathName.length - temp.length){
return true;
}
}
return false;
};
config.js
/*
此文件的作用是对整个服务进行配置
导出的 config 就是配置对象
通过读取 config 里面的值
在需要它的地方使用
当配置改变, 只需要修改 server.conf 文件即可
*/
const fs = require('fs');
// 解析 server.conf 之后需要导出的配置对象
let config = {};
// 读取配置文件
let conf = fs.readFileSync('server.conf');
let confArr = conf.toString().split('\r\n');
for (let i = 0; i < confArr.length; i ++){
let temp = confArr[i].split('=');
if(temp.length === 2){
config[temp[0]] = temp[1];
}
}
// 将静态文件拆分成单个存在数组中
if (config["static_file_type"]){
config["static_file_type"] = config["static_file_type"].split('|');
}else {
// 如果没有静态文件, 那么肯定是有问题的
throw new Error("配置文件异常, 缺少静态文件类型");
}
module.exports = config;
server.conf
port=8080
page_path=page
static_file_type=.html|.css|.js|.png|.jpg|.gif|.ico
web_path=web
log_path=log/
log_name=server.log
loader.js
const fs = require("fs");
const config = require('./config');
// readdir 读出的是某个目录下所有的文件
let files = fs.readdirSync(config['web_path']);
// web 层不可能只有一个 loginController, 还有其他的 controller
// 所以下面这是一个 controller 的集合, 在此 demo 中没有什么用, 但是业务复杂了, 就有用了
let controllerSet = [];
let pathMap = new Map();
for (let i = 0; i < files.length; i++){
// 引入 web_path 路径下的所有文件
let temp = require('./' + config['web_path'] + '/' + files[i]);
// 如果有 path 这个属性,
if(temp.path){
for (let [key, value] of temp.path){
// 如果 pathMap 中没有, 就设置进去
// 要放进 if 判断的原因是防止重复, 因为没有一个请求对应两个处理函数
if(pathMap.get(key) == null){
pathMap.set(key, value);
} else {
throw new Error('url path 异常, url:' + key);
}
controllerSet.push(temp);
}
}
}
module.exports = pathMap;
展示层
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
<title>Title</title>
</head>
<body>
<input type="text" id="username" placeholder="用户名">
<input type="password" id="password" placeholder="密码">
<input type="submit" value="登录" onclick="login()">
<!--<script type="text/javascript" src="./login.js"></script>-->
<script type="text/javascript">
function login() {
let user = document.getElementById('username').value;
let pasd = document.getElementById('password').value;
let params = "?username=" + user + "&password=" + pasd;
let xhr = new XMLHttpRequest();
xhr.open('POST', '/login', true);
xhr.send(params);
xhr.onreadystatechange = function () {
if(xhr.readyState === 4 && xhr.status === 200){
suc(xhr.responseText);
}
}
}
function suc(data) {
if(data === 'OK'){
alert("登录成功");
} else {
alert("登录失败, 用户名或密码错误!");
}
}
</script>
</body>
</html>
总结:
业务流程:
展示层:
输入stu_num, 密码, 点击提交, 触发login()方法, 发送 ajax 请求;
服务端:
这个请求被 server 监听到, 进行处理(解析url), 判断出这是请求动态数据, 需要查询数据库;
web层:
根据解析好的pathName, 去pathMap中调用对应的方法执行, 这里是{"/login" => login()}, 所以就触发了loginController.js中的 login 方法, login方法解析了POST传递过去的参数, 解析出stuNum以及password, 根据stuNum, 调用服务层的方法studentService.queryStuNum 去查询数据库, 并且需要在此传递过去回调函数;
服务层:
这里的服务层没有什么业务逻辑, 只是将DAO层的的方法进行了一次转发, 这个层的作用在这个demo里面不明显.
DAO层:
查询数据库, 将查询结果传递给web层的回调函数, 这里就回到了web层;
web层:
在回调函数中判断, 用户输入的stuNum对应密码和数据库中的密码是否一致, 一直则res.write('OK'), 否则res.write('Fail'), 在这之后, 返回到 ajax 处, 此时已经能够拿到 xhr.response 了. 所以回到展示层.
展示层:
根据 ajax 的返回结果, 判断是否成功登录.
网友评论