Node.js实现网络新闻爬虫及搜索增加功能(一):用户功能

本文详细介绍如何使用Node.js实现用户注册、登录、修改密码等功能,包括数据库设计、前端页面路由设置及用户管理界面搭建。

Node.js实现网络新闻爬虫及搜索增加功能(一):用户功能

系列文章查看不到可能是优快云审核原因,可以在我的知乎专栏看到所有文章:https://www.zhihu.com/column/c_1370026160999415808

项目要求

基于第一个项目爬虫爬取的数据,完成数据展示网站。

基本要求:
        1、用户可注册登录网站,非注册用户不可登录查看数据
        2、用户注册、登录、查询等操作记入数据库中的日志
        3、爬虫数据查询结果列表支持分页和排序
        4、用Echarts或者D3实现3个以上的数据分析图表展示在网站中
        5、实现一个管理端界面,可以查看(查看用户的操作记录)和管理(停用启用)注册用户。

扩展要求(非必须):
        1、实现对爬虫数据中文分词的查询
        2、实现查询结果按照主题词打分的排序
        3、用Elastic Search+Kibana展示爬虫的数据结果

本文是该项目增加功能第一部分:用户功能。包括实现用户注册、登录、修改密码,设置Cookie拒绝非注册用户登录查看数据、记录用户操作、用户管理界面、查看用户操作日志等功能。

一、用户注册、登录、修改密码及用户管理、查看用户操作日志

1. 数据库

数据库连接已在Node.js实现网络新闻爬虫及搜索功能(一)中实现,本地配置mysql和建表步骤参考Node.js实现网络新闻爬虫及搜索功能(一)

        之前数据库中只有存储新闻信息一张表,因为现在要实现用户功能,所以要增加用户信息表和用户操作表。
        设计用户信息表userinfo拥有五个字段,分别是用户ID、用户名、手机号、密码和用户状态。用户ID为主键,手机号为唯一键。使用如下SQL指令创建:

CREATE TABLE `userinfo`  (
  `id_userinfo` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `phonenum` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `password` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `status` int(1) UNSIGNED ZEROFILL NOT NULL,
  PRIMARY KEY (`id_userinfo`) USING BTREE,
  UNIQUE INDEX `uk_phonenum`(`phonenum`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;

        设计用户操作表useriaction拥有四个字段,分别是操作ID、用户手机号、操作和操作时间。操作ID为主键。使用如下SQL指令创建:

CREATE TABLE `useraction`  (
  `id_useraction` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `phonenum` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `action` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id_useraction`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;

2. 添加前端页面路由

        为用户登录、注册、修改密码界面以及用户管理、用户操作日志界面添加路由。

app.get('/login.html', function(req, res) {
    res.sendFile(__dirname + "/templates/" + "login.html");
})

app.get('/register.html', function(req, res) {
    res.sendFile(__dirname + "/templates/" + "register.html");
})

app.get('/forget.html', function(req, res) {
    res.sendFile(__dirname + "/templates/" + "forget.html");
})

app.get('/users.html', function(req, res) {
    res.sendFile(__dirname + "/templates/" + "users.html");
})

app.get('/users_action.html', function(req, res) {
    res.sendFile(__dirname + "/templates/" + "users_action.html");
})

3. 登录功能实现

        登录功能的接口路由主要实现用户登录功能,需要读取数据库中的用户信息,判断输入手机号是否注册、与密码是否匹配,此外,还要判断用户状态是否被管理员停用,如果用户状态为1,则代表用户被停用,不能登录。
        同时注意将用户登录操作和结果记录到用户操作日志当中。

app.get('/login_user', function(req, res) {
    var phonenum = req.query.phonenum;
    var password = req.query.password;
    var userinfo_search_sql = "select username, password, status from userinfo where phonenum = " + phonenum;
    crawler_sql.query(userinfo_search_sql, function(err, result, fields)  {
        if (result.length > 0) {
            if (result[0].status == 1) {
                var useraction_add_sql = 'INSERT INTO useraction(phonenum, action, time) VALUES(?, ?, ?)';
                var time = new Date();
                var useraction_add = [phonenum, "login prohibited", time];
                crawler_sql.query(useraction_add_sql, useraction_add, function(qerr, vals, fields) {
                    if (qerr) {
                        console.log(qerr);
                    }
                });
                res.json({code: 0, msg: "账号已被停用,登录失败!"});
            } else if (result[0].password == password) {
                var useraction_add_sql = 'INSERT INTO useraction(phonenum, action, time) VALUES(?, ?, ?)';
                var time = new Date();
                var useraction_add = [phonenum, "login successfully", time];
                crawler_sql.query(useraction_add_sql, useraction_add, function(qerr, vals, fields) {
                    if (qerr) {
                        console.log(qerr);
                    }
                });
                // res.cookie("username", result[0].username, {maxAge:60000});
                res.cookie("phonenum", phonenum, {maxAge:60000});
                res.json({code: 1, msg: "登录成功!"});
            } else {
                res.json({code: 0, msg: "密码错误,登录失败!"});
            }
        } else {
            res.json({code: 0, msg: "手机号未注册,登录失败!"});
        }
    });    
})

        注意到其中设置了cookie。因为我们要实现非注册用户登录不能查看信息的功能,所以将唯一键手机号存入cookie,在访问相应的前端界面时,若cookie为空,则不允许其查看信息。
        这里设置cookie最大保持时长为60000。

res.cookie("phonenum", phonenum, {maxAge:60000});

        使用cookie需要添加cookie-parser模块,在代码开头加入如下代码引入该模块:

var cookieParser = require('cookie-parser');
app.use(cookieParser());

        登录功能的前端页面使用JQuery框架进行前后端交互,相应交互代码如下:

	<script text="text/javascript">
        function login(obj){
            var phonenum = $(obj).parent().prev().prev().children("#phonenum").val();
            var password = $(obj).parent().prev().children("#password").val();
            if (phonenum == "" || password == ""){
                alert("请输出完整登录信息!");
            } else {
                var url = "/login_user";
                $.ajax({
                    type: "GET",
                    url: url,
                    data: {
                        phonenum: phonenum,
                        password: password
                    },
                    success: function(data){
                        alert(data.msg);
                        if (data.code == 1) {
                            window.location = "/search.html";
                        } else {
                            window.location = "/login.html";
                        }
                    },
                    error: function(XMLHttpRequest, textStatus, errorThrown) {
                        alert(XMLHttpRequest.status);
                        alert(XMLHttpRequest.readyState);
                        alert(textStatus);
                        window.location = "/login.html";
                    }
                });
            }
        }
    </script>

        网页前端背景设置为一个视频,登录信息输入框设置为全透明的。详尽的前端代码请到本人Github中查看,这里只展示前端效果。
登录界面

4. 注册功能实现

        和登录功能类似,首先编写注册功能接口。注册功能需要判断手机号是否已经被注册,若还未注册,则可以写入数据库。

app.get('/register_user', function(req, res) {
    var phonenum = req.query.phonenum;
    var username = req.query.username;
    var password = req.query.password;
    var userinfo_search_sql = "select phonenum from userinfo where phonenum = " + phonenum;
    crawler_sql.query(userinfo_search_sql, function(err, result, fields)  {
        if (result.length == 0) {
            var userinfo_add_sql = 'INSERT INTO userinfo(phonenum, username, password, status) VALUES(?, ?, ?, ?)';
            var userinfo_add = [phonenum, username, password, 0];
            crawler_sql.query(userinfo_add_sql, userinfo_add, function(qerr, vals, fields) {
                if (qerr) {
                    console.log(qerr);
                    res.json({code: 0, msg: "系统出现问题,注册失败!"});
                }
            });
            var useraction_add_sql = 'INSERT INTO useraction(phonenum, action, time) VALUES(?, ?, ?)';
            var time = new Date();
            var useraction_add = [phonenum, "register successfully", time];
            crawler_sql.query(useraction_add_sql, useraction_add, function(qerr, vals, fields) {
                if (qerr) {
                    console.log(qerr);
                }
            });
            res.json({code: 1, msg: "注册成功!"});
        } else {
            res.json({code: 0, msg: "手机号已被注册,注册失败!"});
        }
    });    
})

        同样,注册功能的前端页面使用JQuery框架进行前后端交互,需要判断用户两次输入密码是否一致,相应交互代码如下:

<script text="text/javascript">
        function register(obj){
            var phonenum = $(obj).parent().prev().prev().prev().prev().children("#phonenum").val();
            var username = $(obj).parent().prev().prev().prev().children("#username").val();
            var password = $(obj).parent().prev().prev().children("#password").val();
            var password1 = $(obj).parent().prev().children("#password1").val();
            if (phonenum == "" || username == "" || password == ""){
                alert("请输出完整注册信息!");
            } else if(password != password1) {
                alert("两次密码输入不一致!");
            } else {
                var url = "/register_user";
                $.ajax({
                    type: "GET",
                    url: url,
                    data: {
                        phonenum: phonenum,
                        username: username,
                        password: password
                    },
                    success: function(data){
                        alert(data.msg);
                        if (data.code == 1) {
                            window.location = "/login.html";
                        } else {
                            window.location = "/register.html";
                        }
                    },
                    error: function(XMLHttpRequest, textStatus, errorThrown) {
                        alert(XMLHttpRequest.status);
                        alert(XMLHttpRequest.readyState);
                        alert(textStatus);
                        window.location = "/register.html";
                    }
                });
            }
        }
    </script>

        网页前端背景设置为一个视频,注册信息输入框设置为全透明的。详尽的前端代码请到本人Github中查看,这里只展示前端效果。
注册页面

5. 修改密码功能实现

        和登录功能类似,首先编写修改密码功能接口。修改密码功能需要判断手机号与用户名是否一致,以及手机号是否注册。

app.get('/change_password', function(req, res) {
    var phonenum = req.query.phonenum;
    var username = req.query.username;
    var password = req.query.password;
    var userinfo_search_sql = "select phonenum, username from userinfo where phonenum = " + phonenum;
    crawler_sql.query(userinfo_search_sql, function(err, result, fields) {
        if (result.length > 0) {
            if (result[0].username == username) {
                var userinfo_change_sql = 'UPDATE userinfo SET password = ? where phonenum = ?';
                var userinfo_change = [password, phonenum];
                crawler_sql.query(userinfo_change_sql, userinfo_change, function(qerr, vals, fields) {
                    if (qerr) {
                        console.log(qerr);
                        res.json({code: 0, msg: "系统出现问题,修改失败!"});
                    }
                });
                var useraction_add_sql = 'INSERT INTO useraction(phonenum, action, time) VALUES(?, ?, ?)';
                var time = new Date();
                var useraction_add = [phonenum, "change password successfully", time];
                crawler_sql.query(useraction_add_sql, useraction_add, function(qerr, vals, fields) {
                    if (qerr) {
                        console.log(qerr);
                    }
                });
                res.json({code: 1, msg: "修改成功!"});
            } else {
                var useraction_add_sql = 'INSERT INTO useraction(phonenum, action, time) VALUES(?, ?, ?)';
                var time = new Date();
                var useraction_add = [phonenum, "change password failed", time];
                crawler_sql.query(useraction_add_sql, useraction_add, function(qerr, vals, fields) {
                    if (qerr) {
                        console.log(qerr);
                    }
                });
                res.json({code: 0, msg: "用户名错误,修改失败!"});
            }
        } else {
            res.json({code: 0, msg: "手机号未注册,修改失败!"});
        }
    });    
})

        同样,修改密码功能的前端页面使用JQuery框架进行前后端交互,需要判断用户两次输入密码是否一致,相应交互代码如下:

<script text="text/javascript">
        function forget(obj){
            var phonenum = $(obj).parent().prev().prev().prev().prev().children("#phonenum").val();
            var username = $(obj).parent().prev().prev().prev().children("#username").val();
            var password = $(obj).parent().prev().prev().children("#password").val();
            var password1 = $(obj).parent().prev().children("#password1").val();
            if (phonenum == "" || username == "" || password == ""){
                alert("请输出完整账号信息!");
            } else if(password != password1) {
                alert("两次密码输入不一致!");
            } else {
                var url = "/change_password";
                $.ajax({
                    type: "GET",
                    url: url,
                    data: {
                        phonenum: phonenum,
                        username: username,
                        password: password
                    },
                    success: function(data){
                        alert(data.msg);
                        if (data.code == 1) {
                            window.location = "/login.html";
                        } else {
                            window.location = "/forget.html";
                        }
                    },
                    error: function(XMLHttpRequest, textStatus, errorThrown) {
                        alert(XMLHttpRequest.status);
                        alert(XMLHttpRequest.readyState);
                        alert(textStatus);
                        window.location = "/forget.html";
                    }
                });
            }
        }
    </script>

        网页前端背景设置为一个视频,修改密码信息输入框设置为全透明的。详尽的前端代码请到本人Github中查看,这里只展示前端效果。
修改密码页面
        在登录、注册、修改密码这三个页面的登录信息框中,将手机号输入框使用正则匹配规定,只允许输入数字。

<p style="padding-bottom:15px">
	<input id="phonenum" oninput="value=value.replace(/[^\d]/g,'')" autocomplete="off" placeholder="手机号码" name="phonenum">
</p>

6. 用户管理功能实现

        明确用户管理功能为查看用户数据库表中所有用户可见信息,以及对管理员提供停用或启用某用户的功能,并且可以查看该用户的操作日志。
        这里先展示前端界面,当管理员用户登录进入系统时,侧边栏会显示用户列表选项,点击进入用户列表界面。
用户列表界面
        点击启用或停用可以更改用户状态,被停用的用户无法再登录。点击详情页面进入查看该用户的操作日志,规定admin用户状态不允许更改、操作日志不允许查看。
操作日志界面
        操作日志界面包括了该用户所有操作的详细记录,包括登录、注册、修改密码、查询、查看新闻详情各种详细信息。
        下面介绍接口编写。首先,用户列表界面进入时需要渲染数据,编写渲染路由,即读取所有用户信息。

app.get('/show_users', function(req, res) {
    var userinfo_search_sql = "select phonenum, username, status, id_userinfo from userinfo";
    crawler_sql.query(userinfo_search_sql, function(err, result, fields) {
        res.json(result);
    });
})

        相应的前端交互代码如下:

$(document).ready(function() {
            if (document.cookie == "") {
                alert("请使用admin账号登录!");
                window.location = "/login.html";
            } else if (document.cookie.split('=')[1] != '000') {
                alert("请使用admin账号登录!");
                window.location = "/login.html";
            }
            var url = "/show_users";
            $.ajax({
                type: "GET",
                url: url,
                data: {
                },
                success: function(data){
                    for (var i = 0; i < data.length; i++) {
                        var begin_str = "<tr id='" + data[i].id_userinfo + "'>";
                        var phonenum_str = "<td class='text-left'>" + data[i].phonenum + "</td>";
                        var username_str = "<td class='text-left'>" + data[i].username + "</td>";
                        var status_str = "<td class='text-left'>使用中</td>";
                        var change_str = "<td class='text-right'><i class='fa fa-tags'></i>&nbsp&nbsp<input type='submit' value='停用' οnclick='change_status(" + data[i].phonenum + ", " + data[i].status + ")' /></td>";
                        var info_str = "<td class='text-right'><i class='fa fa-paper-plane'></i>&nbsp&nbsp<input type='submit' value='详情' οnclick='user_action(" + data[i].phonenum + ")' /></td>";
                        if (data[i].status == 1) {
                            status_str = "<td class='text-left'>停用</td>";
                            change_str = "<td class='text-right'><i class='fa fa-tags'></i>&nbsp&nbsp<input type='submit' value='启用' οnclick='change_status(" + data[i].phonenum + ", " + data[i].status + ")' /></td>";
                        }
                        if (data[i].phonenum == "000") {
                            change_str = "<td class='text-right'>不可更改</td>";
                            info_str = "<td class='text-right'>不可查看</td>";
                        }
                        $("#tbody-title").after($(begin_str + phonenum_str + username_str + status_str + change_str + info_str + "</tr>"));
                    }
                },
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    alert(XMLHttpRequest.status);
                    alert(XMLHttpRequest.readyState);
                    alert(textStatus);
                    window.location = "/users.html";
                }
            });
        });

        注意到该页面首先判断了cookie信息是否正确,只有当cookie中储存的手机号是admin对应的手机号时才可以继续渲染该页面。
        之后,编写更改用户状态路由,调用该路由时将用户状态取反。

app.get('/change_status', function(req, res) {
    var phonenum = req.query.phonenum;
    var status = req.query.status;
    if (status == "0") {
        status = 1;
    } else if (status == "1") {
        status = 0;
    }
    var userinfo_change_sql = 'UPDATE userinfo SET status = ? where phonenum = ?';
    var userinfo_change = [status, phonenum];
    crawler_sql.query(userinfo_change_sql, userinfo_change, function(qerr, vals, fields) {
        if (qerr) {
            console.log(qerr);
        }
    });
    res.json({code: 1});
})

        相应的前端交互代码如下:

function change_status(phonenum, status) {
            var url = "/change_status";
            $.ajax({
                type: "GET",
                url: url,
                data: {
                    phonenum: phonenum,
                    status: status
                },
                success: function(data){
                    window.location = "/users.html";
                },
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    alert(XMLHttpRequest.status);
                    alert(XMLHttpRequest.readyState);
                    alert(textStatus);
                    window.location = "/users.html";
                }
            });
        }

        同样,在点击详情键时,跳转到用户日志界面,这里需要进行页面间传值。在用户列表界面时,需要向后端发送选择的用户的信息,相应路由为:

var user_action = []
app.get('/user_action', function(req, res) {
    var phonenum = req.query.phonenum;
    var useraction_search_sql = "select phonenum, time, action, id_useraction from useraction where phonenum = " + phonenum;
    crawler_sql.query(useraction_search_sql, function(err, result, fields) {
        user_action = result;
        res.json({code: 1});
    });
})

        使用一个公共变量为用户操作日志界面渲染提供数据。相应前端交互代码为:

function user_action(phonenum) {
            var url = "/user_action";
            $.ajax({
                type: "GET",
                url: url,
                data: {
                    phonenum: phonenum
                },
                success: function(data){
                    window.location = "/users_action.html";
                },
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    alert(XMLHttpRequest.status);
                    alert(XMLHttpRequest.readyState);
                    alert(textStatus);
                    window.location = "/users.html";
                }
            });
        }

        用户操作日志渲染接口为:

app.get('/show_user_action', function(req, res) {
    res.json(user_action);
})

        相应的前端交互代码如下:

$(document).ready(function() {
            if (document.cookie == "") {
                alert("请使用admin账号登录!");
                window.location = "/login.html";
            } else if (document.cookie.split('=')[1] != '000') {
                alert("请使用admin账号登录!");
                window.location = "/login.html";
            }
            var url = "/show_user_action";
            $.ajax({
                type: "GET",
                url: url,
                data: {
                },
                success: function(data){
                    for (var i = 0; i < data.length; i++) {
                        var begin_str = "<tr id='" + data[i].id_useraction + "'>";
                        var phonenum_str = "<td class='text-left'>" + data[i].phonenum + "</td>";
                        var time_str = "<td class='text-left'>" + data[i].time.slice(0, 10) + "</td>";
                        var action_str = "<td class='text-left'>" + data[i].action + "</td>";
                        $("#tbody-title").after($(begin_str + phonenum_str + time_str + action_str + "</tr>"));
                    }
                },
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    alert(XMLHttpRequest.status);
                    alert(XMLHttpRequest.readyState);
                    alert(textStatus);
                    window.location = "/users.html";
                }
            });
        });

        注意到该页面首先判断了cookie信息是否正确,只有当cookie中储存的手机号是admin对应的手机号时才可以继续渲染该页面。

        至此,增加功能(一):用户功能就已经全部实现了。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值