1. 实验内容
基于第一个项目爬虫爬取的数据,完成数据展示网站。
基本要求:
- 用户可注册登录网站,非注册用户不可登录查看数据
- 用户注册、登录、查询等操作记入数据库中的日志
- 爬虫数据查询结果列表支持分页和排序
- 用Echarts或者D3实现3个以上的数据分析图表展示在网站中
- 实现一个管理端界面,可以查看(查看用户的操作记录)和管理(停用启用)注册用户。
附加要求:
- 分词查询
- 主题词排序
- ElasticSearch
2. 实验任务
-
注册与登录的实现
- 建立用户数据库并实现注册接口;
- 实现登录接口(验证是否登录成功);
- 登陆成功后将登录信息保存到session;
- 在主页添加session验证;
- 登录界面前端。
-
日志
- 建立数据库。
- 添加写入日志的操作。
-
管理实现
- 实现对数据库的查询与操作;
- 路由实现;
- 管理界面前端实现。
-
搜索分页和排序
- 添加分页查询返回;
- 添加翻页的实现;
- 修改前端。
-
可视化
- 用户访问数据可视化;
- 爬取新闻数量可视化;
- 新闻热词可视化。
-
分词查询
- 对原查询进行修改。
3. 实验过程
1. 注册与登陆的实现
-
建立用户信息数据库
在数据库crawl
中建立一个新的表usr
存放用户注册信息,包括id(自动生成)、用户名、密码、注册时间、用户权限和是否活跃五个字段。USE `crawl`; CREATE TABLE `usr` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(20) NOT NULL, `password` varchar(50) NOT NULL, `regtime` datetime DEFAULT CURRENT_TIMESTAMP, `is_admin` tinyint(1) DEFAULT 0, `active` tinyint(1) DEFAULT 1, PRIMARY KEY (`id`), UNIQUE KEY `user_id` (`id`), UNIQUE KEY `name` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
实现注册接口
注册需要不重复的用户名,因此首先要检查输入的用户名是否有重复。在确认没有重复之后,再将要使用的账号和密码插入数据库中。
var register = function(username, password, callback) { var checksql = 'select username from usr where username=?'; var checkparam = [username]; db.query(checksql, checkparam, function(qerr, vals, fields) { if (qerr) { console.log(qerr); callback(qerr, null); } else if (vals.length !== 0){ callback(qerr, "Duplicate username"); } else { var sql = 'INSERT INTO usr(username, password) VALUES(?,?)'; sqlparam = [username, password]; db.query(sql, sqlparam, function(qerr, vals, fields) { if (qerr) { console.log(qerr); callback(qerr, null); }else{ callback(qerr, "Success"); } }); } }); };
-
实现登录接口
登录逻辑:检查数据库中是否有相应账号密码的用户。有的话登陆成功,没有的话失败。要返回是否成功以及用户权限。
var login = function(username, password, callback) { var checksql = 'select is_admin from usr where username=? and password=?'; var checkparam = [username, password]; db.query(checksql, checkparam, function(qerr, vals, fields) { if (qerr) { console.log(qerr); callback(qerr,null, -1, -1); } else if(vals.length != 1) { callback(qerr, "Denied", -1); } else { var is_admin = JSON.parse(JSON.stringify(vals))[0].is_admin; var active = JSON.parse(JSON.stringify(vals))[0].active; callback(qerr, "Success", is_admin, active); } }); };
-
添加session保存登录信息
首先在app.js里,引入session,并设置session信息。
var session = require('express-session'); app.use(session({ secret: 'sessiontest',//与cookieParser中的一致 resave: true, saveUninitialized: false, // 是否保存未初始化的会话 cookie : { maxAge : 1000 * 60 * 60, // 设置 session 的有效时间,单位毫秒 }, }));
在路由端创建登录、注册的接口,解析前端传来的数据,根据它们调用数据库接口,然后判断是否成功登录/注册,成功则写入session,并且跳转到跳转去管理页面或者新闻查看界面的url(因为后续部分功能实现的原因,重新跳转进入新闻查看界面时要要先清除部分session,所以专门编写了一个跳转用的路由接口,后面讲到跳转实现的时候会具体介绍)。
router.post('/register', function(req, res, next) { var username = req.body.username; var password = req.body.password; var al = { msg:null}; //res.render('users'); usr.register(username, password, function(qerr, msg){ if(qerr) { al["msg"] = "sql error"; res.render('users', al); } else { if (msg==="Success") { req.session['username'] = username; res.cookie('username', username); res.redirect('../gotosearch'); // 输出消息:注册成功,自动登录 // 添加session和cookie // 跳转到index } else if (msg==="Duplicate username"){ al["msg"] = msg; res.render('users', al); } } }); }; router.post('/login', function(req, res, next) { var username = req.body.username; var password = req.body.password; al={ msg:null}; //res.render('users'); usr.login(username, password, function(qerr, msg, is_admin, active){ if(qerr) { al["msg"] = "sql error"; res.render('users', al); } else { if (msg==="Success") { if(active===0){ al["msg"]="你被封号了"; console.log(al); res.render('users', al); } else{ req.session['username'] = username; res.cookie('username', username); // 输出消息:登录成功 // 添加session和cookie if(is_admin) { req.session['is_admin'] = 1; res.cookie('is_admin', 1); res.redirect("../admin"); } else { req.session['is_admin'] = 0; res.cookie('is_admin',0); res.redirect("../gotosearch"); } } } else if (msg==="Denied"){ al["msg"] = msg; res.render('users', al); // 输出消息:账号或密码错误 } } }); //res.send('respond with a resource'); });
对于退出登录,销毁session即可。在页面上,也只需要加一个表单,只要一个提交按钮,用post方式提交到/users/logout。
router.post('/logout', function(req, res, next) { //console.log(1); req.session.destroy(function(err) { if(err){ res.json('退出登录失败'); return; } // req.session.loginUser = null; res.clearCookie('username'); res.clearCookie('is_admin'); var al = { msg:null}; res.render('users',al); }); });
而对于需要登录/是管理员才能访问的页面,就在路由端添加一段判断,如果session内的信息不符合的话就返回登录界面。
以搜索新闻页面为例:
if(req.session['username']===undefined) { res.redirect('/users'); }else{ ... }
同时,利用session还可以添加一些别的东西,比如读取用户名后,在页面最上方显示“欢迎某某某”,如果用户是管理员就多显示一个按钮(是一个表单)可以跳转到管理页面等等。
-
登录界面前端实现
用户界面前端:有一个表单,可以输入用户名/密码,然后有两个按钮:一个提交到注册,一个提交到登录。其中,注册时要判断是否合法。
提交按钮用onclick进行处理,设置不同的action,然后submit。其中,注册函数会调用判断合法的函数,如果不合法的话,就会用alert进行提示。onclick()调用函数如下:
<script> function login(){ // 跳转形式: // document.("表单的name值").action = “” // document.("表单的name值").submit = “” document.user_form.action="/users/login"; document.user_form.submit(); } function register() { var check = check_username(document.user_form.username.value); if (check!=="ok"){ alert(check); }else{ var check = check_passwd(document.user_form.password.value); if (check!=="ok"){ alert(check); } else { document.user_form.action = "/users/register"; document.user_form.submit(); } } } function check_username(str) { var str2 = "ok"; if ("" == str) { str2 = "用户名为空"; return str2; } else if ((str.length < 5) || (str.length > 20)) { str2 = "用户名必须为5 ~ 20位"; return str2; } else if (check_other_char(str)) { str2 = "不能含有特殊字符"; return str2; } return str2; } function check_passwd(str) { var str2 = "ok"; if ("" == str) { str2 = "密码为空"; return str2; } else if ((str.length < 5) || (str.length > 50)) { str2 = "密码必须为5 ~ 50位"; return str2; } else if (check_other_char(str)) { str2 = "不能含有特殊字符"; return str2; } return str2; } function check_other_char(str) { var arr = ["&", "\\", "/", "*", ">", "<", "@", "!"]; for (var i = 0; i < arr.length; i++) { for (var j = 0; j < str.length; j++) { if (arr[i] == str.charAt(j)) { return true; } } } return false; } </script>
而数据库判断的登录/注册失败的提示消息会传到前端来,要用ejs的函数进行处理。首先判断是否有提示消息,如果有,就输出出来。
<%if(msg !== null && msg != undefined){ %> Error: <%-msg%> <%}%>
登录界面的url是/users,所以需要路由先渲染这个页面。虽然浏览器发送的是post,但是在有的浏览器(比如safari)里,好像确认alert之后会post,所以用了all方法。