我的第二次Web编程作业

本文档展示了如何使用Express框架构建一个用户管理系统,包括用户注册、登录、登出和管理功能。通过创建数据库表、定义路由和处理HTTP请求,实现了用户数据验证、操作日志记录以及权限管理。同时,前端页面包括登录、注册和管理界面,利用Echarts展示数据关系图。整个过程中,实践了前后端交互和数据库操作,体现了Web开发的基本流程。
摘要由CSDN通过智能技术生成

实验要求

在这里插入图片描述

实验成果展示

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

准备工作

安装express包,
在控制台输入
npm install -g express

实验过程

创建数据库

想要实现用户注册、登录及管理,需要创建两个数据库分别记录用户数据和操作日志。
用户数据表:用户数据
用户操作日志
在这里插入图片描述

创建路由

首先引入express包,通过server.getserver.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: "" };

这部分访客无法查看主站内容,只能查看默认的登录界面。
接下来便是完善登录操作:
usernamepassword储存用户输入的数据,通过对比数据库中的usernamepassword来判断该用户是否输入正确的信息。若不正确或用户被停用,则提示且返回登录页面。若登陆成功,则在数据库中记录该操作。

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实现数据可视化,等等。到现在这门课也算是结束了,不敢说学会了多少,最起码明白了“实践出真知”这样的道理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值