实验要求
实验成果展示
准备工作
安装express包,
在控制台输入
npm install -g express
实验过程
创建数据库
想要实现用户注册、登录及管理,需要创建两个数据库分别记录用户数据和操作日志。
用户数据表:
用户操作日志
创建路由
首先引入express
包,通过server.get
和server.post
创建路由。
server.get('/', function (req, res){
//创建主站
}
server.get('/login', function (req, res) {
//创建登录界面
}
server.post('/login', function (req, res) {
//处理用户登录请求
}
server.get("/logout", function (req, res) {
//处理用户登出请求
}
server.get("/sign", function (req, res) {
//创建注册界面
}
server.post("/sign", function (req, res) {
//处理用户注册请求
}
server.get('/root', function(req, res) {
//创建管理界面
}
1.主站
基本沿用上次作业的代码,但是对前端进行了部分改动,这部分会在后面提及。
2.登录界面
首先在后端设置默认访客,即没有登录的用户:
var currentUser = { id: -1, name: "", password: "" };
这部分访客无法查看主站内容,只能查看默认的登录界面。
接下来便是完善登录操作:
用username
和password
储存用户输入的数据,通过对比数据库中的username
和password
来判断该用户是否输入正确的信息。若不正确或用户被停用,则提示且返回登录页面。若登陆成功,则在数据库中记录该操作。
server.post('/login', function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
let username = req.body.username;
let password = req.body.password;
database.query('SELECT * FROM users WHERE name = ? AND password = ?;', [username, password], function(qerr, vals, fields) {
if (qerr) {
console.error(qerr);
return;
}
if (vals.length == 0) {
res.end(`<script>alert("用户名或密码不正确!");location.replace("/");</script>`);
return;
}
if (vals[0].state == 0) {
res.end(`<script>alert("该用户已被停用!");location.replace("/");</script>`);
return;
}
database.query('INSERT INTO userlog (userID, method, content, datetime) VALUES(?, ?, ?, ?);',
[vals[0].id, 'LOGIN', req.connection.remoteAddress, new Date()],
function(qerr1, vals1, fields1) {
if (qerr1) {
console.error(qerr1);
return;
}
currentUser = vals[0];
res.end(`<script>location.replace("/");</script>`);
});
});
});
3.用户登出
用户执行登出操作时,在数据库中记录该操作,并将当前访客信息重置为默认访客。之后跳转到登录界面。
server.get("/logout", function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
database.query('INSERT INTO userlog (userID, method, content, datetime) VALUES(?, ?, ?, ?);',
[currentUser.id, 'LOGOUT', req.connection.remoteAddress, new Date()],
function (qerr, vals, fields) {
if (qerr) {
console.error(qerr);
return;
}
currentUser = { id: -1, name: "", password: "" };
res.end(`<script>location.replace("/");</script>`);
});
});
4.用户注册
注册时让用户输入用户名、密码以及二次输入密码,将用户两次输入的密码进行对比,若不一致,则提示并返回注册界面。
let password = req.body.password;
let password2 = req.body.password2;
if (password != password2) {
res.end(`<script>alert("两次密码不一致!");location.replace("/sign");</script>`);
return;
}
将用户输入的用户名与数据库中的进行对比,若发现用户名重复,则返回注册页面。
database.query('SELECT * FROM users WHERE name = ?;', [username], function (qerr, vals, fields) {
if (qerr) {
console.error(qerr);
return;
}
if (vals.length != 0) {
res.end(`<script>alert("该用户名已被注册!");location.replace("/sign");</script>`);
return;
});
若注册成功,则在操作日志中记录该操作。
database.query('INSERT INTO users (name, password) VALUES (?, ?);', [username, password], function (qerr1, vals1, fields1) {
if (qerr1) {
console.error(qerr1);
return;
}
database.query('INSERT INTO userlog (userID, method, content, datetime) VALUES(?, ?, ?, ?);',
[vals[0].id, 'SIGN', req.connection.remoteAddress, new Date()],function (qerr2, vals2, fields2) {
if (qerr2) {
console.error(qerr2);
return;
}
res.end(`<script>alert("注册成功!");location.replace("/login");</script>`);
});
});
完整代码如下:
server.get("/sign", function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html' });
let $ = cheerio.load(fs.readFileSync("sign.html")); //TODO
res.end($.html());
});
server.post("/sign", function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
let username = req.body.username;
let password = req.body.password;
let password2 = req.body.password2;
if (password != password2) {
res.end(`<script>alert("两次密码不一致!");location.replace("/sign");</script>`);
return;
}
database.query('SELECT * FROM users WHERE name = ?;', [username], function (qerr, vals, fields) {
if (qerr) {
console.error(qerr);
return;
}
if (vals.length != 0) {
res.end(`<script>alert("该用户名已被注册!");location.replace("/sign");</script>`);
return;
}
database.query('INSERT INTO users (name, password) VALUES (?, ?);', [username, password], function (qerr1, vals1, fields1) {
if (qerr1) {
console.error(qerr1);
return;
}
database.query('INSERT INTO userlog (userID, method, content, datetime) VALUES(?, ?, ?, ?);',
[vals[0].id, 'SIGN', req.connection.remoteAddress, new Date()],
function (qerr2, vals2, fields2) {
if (qerr2) {
console.error(qerr2);
return;
}
res.end(`<script>alert("注册成功!");location.replace("/login");</script>`);
});
});
});
});
5.建立管理端
作业要求建立一个管理界面,可以查看用户操作日志以及管理用户帐号状态。想要实现这个功能首先判断当前用户是否具有操作权限:
if (currentUser.id != 0) {
res.end('<script>alert("没有权限!");location.replace("/");</script>');
return;
}
将数据库中的数据导入到前端:
database.query_noparam('SELECT * FROM users;', function (qerr, vals, fields) {
if (qerr) {
console.error(qerr);
return;
}
for (let i in vals) {
$('table#usertable').append(`<tr class="item-usertable" dbid=${i}>
<td>${vals[i].id}</td>
<td>${vals[i].name}</td>
<td><a href="/changeState?id=${vals[i].id}&state=${1 - vals[i].state}"><button>${vals[i].state == 0 ? "启用" : "停用"}</button></a></td>
</tr>`);
}
database.query_noparam('SELECT * FROM userlog;', function (qerr1, vals1, fields1){
if (qerr1) {
console.error(qerr1);
return;
}
for (let i in vals1) {
$('table#userlog').append(`<tr class="item-userlog" dbid=${i}>
<td>${vals1[i].id}</td>
<td>${vals1[i].userID}</td>
<td>${vals1[i].method}</td>
<td>${vals1[i].content}</td>
</tr>`);
}
res.end($.html());
});
});
完整代码如下:
server.get('/root', function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
let $ = cheerio.load(fs.readFileSync("root.html"));
if (currentUser.id != 0) {
res.end('<script>alert("没有权限!");location.replace("/");</script>');
return;
}
database.query_noparam('SELECT * FROM users;', function (qerr, vals, fields) {
if (qerr) {
console.error(qerr);
return;
}
for (let i in vals) {
$('table#usertable').append(`<tr class="item-usertable" dbid=${i}>
<td>${vals[i].id}</td>
<td>${vals[i].name}</td>
<td><a href="/changeState?id=${vals[i].id}&state=${1 - vals[i].state}"><button>${vals[i].state == 0 ? "启用" : "停用"}</button></a></td>
</tr>`);
}
database.query_noparam('SELECT * FROM userlog;', function (qerr1, vals1, fields1){
if (qerr1) {
console.error(qerr1);
return;
}
for (let i in vals1) {
$('table#userlog').append(`<tr class="item-userlog" dbid=${i}>
<td>${vals1[i].id}</td>
<td>${vals1[i].userID}</td>
<td>${vals1[i].method}</td>
<td>${vals1[i].content}</td>
</tr>`);
}
res.end($.html());
});
});
});
更改用户状态:即在数据库中设置状态,0表示停用,1表示启用。当点击“停用”或“启用”按钮时,更新其在数据库中的状态。
server.get('/changeState', function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
let userID = parseInt(req.query.id);
let nextState = parseInt(req.query.state);
if (currentUser.id != 0) {
res.end('<script>alert("没有权限!");location.replace("/");</script>');
return;
}
database.query('UPDATE users SET state = ? WHERE id = ?;', [nextState, userID], function(qerr, vals, fields) {
if (qerr) {
console.error(qerr);
return;
}
res.end('<script>alert("改变用户状态成功!");location.replace("/root");</script>');
});
});
后端到这里基本已经写好。
前端
翻页操作的实现
<script type="text/javascript">
const countPerPage = 4;
function dividePage(tableID) {
let count = document.getElementsByClassName("item-" + tableID).length;
let ret = Math.ceil(count / countPerPage - 1e-06);
document.getElementById("pageSpan-" + tableID).innerText = ` 1/${ret} `;
return ret;
}
function changePage(tableID, page, maxp) {
let items = document.getElementsByClassName("item-" + tableID);
document.getElementById("pageSpan-" + tableID).innerText = ` ${page}/${maxp} `;
for (let item of items) {
if ((page - 1) * countPerPage <= parseInt(item.getAttribute("dbid")) &&
parseInt(item.getAttribute("dbid")) < page * countPerPage) {
item.removeAttribute("hidden");
} else {
item.setAttribute("hidden", "true");
}
}
}
</script>
1.登录界面
设置一个表单,以便于用户输入。为了将表单居中,将表单放在一个<div>
中。但因为没办法做到垂直居中,便在上面加入一张图片,使其视觉上垂直居中,并将注册链接到注册路由中。
代码如下:
<div style="height:100%;width:100%;text-align:center">
<img src="logo.png" height="300px" />
<form method="post" action="/login" style="vertical-align:middle">
<p style="font-size: 24px">用户名:<input type="text" name="username" style="font-size: 24px;width: 500px" /></p>
<p style="font-size: 24px">密 码:<input type="password" name="password" style="font-size: 24px; width: 500px" /></p>
<input type="submit" value="登录" />
<p style="font-size: 16px">还没有帐号?<a href="/sign">立即注册</a></p>
</form>
</div>
2.注册界面
操作与登录界面一致。
<div style="height:100%;width:100%;text-align:center">
<img src="logo2.png" height="300px" />
<form method="post" action="/sign" style="vertical-align:middle">
<p style="font-size: 24px"> 用户名:<input type="text" name="username" style="font-size: 24px;width: 500px" /></p>
<p style="font-size: 24px">密 码:<input type="password" name="password" style="font-size: 24px; width: 500px" /></p>
<p style="font-size: 24px">再次输入:<input type="password" name="password2" style="font-size: 24px; width: 500px" /></p>
<input type="submit" value="提交" />
</form>
</div>
3.管理界面
建立两个表格,分别存放用户信息和用户操作日志。
<div style="margin: 0 auto;">
<table border="1" id="usertable">
<caption style="font-size: 24px;">用户表</caption>
<tr>
<th>用户ID</th>
<th>用户名</th>
<th>操作</th>
</tr>
</table>
<h1><br /></h1>
<table border="1" id="userlog">
<caption style="font-size: 24px">操作日志</caption>
<tr>
<th>日志ID</th>
<th>用户名</th>
<th>用户操作</th>
<th>搜索记录</th>
</tr>
</table>
</div>
用Echarts生成图表
由于我爬取的静态网站,不能很好的契合柱状图、条形图等,于是做了词云,人物关系表以及人物的生日在日历中的显示。
用Echarts制作关系图:
function addGraphChart(nodes, links, categories, chartId, Title) {
//指定图表的配置和数据
var option = {
tooltip: {
trigger: 'item',
formatter: function(params) {
console.log(params);
if(params.data.relation) {
return params.data.source + "是" + params.data.target + "的" + params.data.relation;
}
return params.data.name;
}
},
title:{
text: Title
},
legend:{
data: categories.map(function (a) {
return a.name;
})
},
series:[{
type: 'graph',
name: Title,
layout: 'force',
force: {
repulsion: 1000
},
draggable: true,
data: nodes,
links: links,
categories: categories,
roam: true,
label: {
position: 'right',
formatter: '{b}'
},
lineStyle: {
normal: {
color: '#000000',
curveness: 0.3,
opacity: 0.5,
width: 2
}
},
emphasis: {
focus: 'adjacency'
}
}]
};
//初始化echarts
var myChart = echarts.init(document.getElementById(chartId));
//应用配置和数据
myChart.setOption(option);
}
在后端设置路由,访问数据库:
server.get('/relation-chart', function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html' });
let $ = cheerio.load(fs.readFileSync("jojo.html"));
let nodes_str = "";
let links_str = "";
let categories_str = '{name:"JOJO"}, {name:"Friends"}, {name:"Enemies"}';
database.query_noparam("SELECT a.Name AS subject_name, b.Name AS object_name, relation, a.Category AS subject_cat, b.Category AS object_cat, a.node_size AS subject_size, b.node_size AS object_size FROM jojo a, jojo_relationship, jojo b WHERE a.id = `subject` AND object = b.id;", function (qerr, vals, fields) {
if (qerr) {
console.error(qerr);
return;
}
let nodes = [];
for (let i in vals) {
nodes.push({ name: vals[i].subject_name, symbolSize: 36 - 12 * vals[i].subject_size, category: vals[i].subject_cat });
nodes.push({ name: vals[i].object_name, symbolSize: 36 - 12 * vals[i].object_size, category: vals[i].object_cat });
}
let tmpObj = {};
nodes = nodes.reduce(function (preValue, item) {
if (!tmpObj[item.name]) {
tmpObj[item.name] = true;
preValue.push(item);
}
return preValue;
}, []);
for (let i in nodes) {
nodes_str = nodes_str + `{ name: "${nodes[i].name}", symbolSize: ${nodes[i].symbolSize}, category: ${nodes[i].category} },`;
}
for (let i in vals) {
links_str = links_str + `{source: "${vals[i].subject_name}", target: "${vals[i].object_name}", relation: "${vals[i].relation}"},`;
}
$('body').append(`<script>addGraphChart([${nodes_str}], [${links_str}], [${categories_str}], "relation-chart", "关系");</script>`)
res.end($.html());
});
});
存放图表的页面:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script src="https://cdn.bootcss.com/echarts/3.6.2/echarts.min.js"></script>
<script src="./js/addChart.js"></script>
</head>
<body background="jojo.png"
style="background-repeat:no-repeat;
background-size:100% 100%;
background-attachment: fixed;">
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">Home</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">图片<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">关系图</a></li>
<li><a href="/calendar-chart">日历图</a></li>
<li><a href="/Klee_table.png">词云</a></li>
</ul>
</li>
<li>
<a href="#" class="dropdown-toggle" data-toggle="dropdown">账号管理<span class="caret"></span></a>
<ul class="dropdown-menu">
<li class="dropdown-header">账号</li>
<li><a href="/logout">退出登录</a></li>
<li><a href="/root">管理用户</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<h1><br /></h1>
<div id="relation-chart" style="height: 1000px; width:1000px">
</div>
</body>
</html>
效果如下:
日历表操作大体相同,不做赘述。
心得体会
磕磕绊绊做了很久的作业,到现在终于差不多快要完成了。刚看到要求时感觉十分困难,有些泄气。但写着写着就发现,其实并不难,翻来覆去还是那些知识:创建路由,访问数据库,Echarts实现数据可视化,等等。到现在这门课也算是结束了,不敢说学会了多少,最起码明白了“实践出真知”这样的道理。