美文网首页
登录验证从前端到后台

登录验证从前端到后台

作者: Veycn | 来源:发表于2019-01-29 18:03 被阅读0次

    数据库

    建立一张 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 的返回结果, 判断是否成功登录.

    相关文章

      网友评论

          本文标题:登录验证从前端到后台

          本文链接:https://www.haomeiwen.com/subject/kxtfsqtx.html