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>  <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>  <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>  <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对应的手机号时才可以继续渲染该页面。
至此,增加功能(一):用户功能就已经全部实现了。
本文详细介绍如何使用Node.js实现用户注册、登录、修改密码等功能,包括数据库设计、前端页面路由设置及用户管理界面搭建。
1509

被折叠的 条评论
为什么被折叠?



