web编程第二次大作业
1. 作业要求
- 基于第一个项目爬虫爬取的数据,完成数据展示网站。
- 基本要求
- 1、用户可注册登录网站,非注册用户不可登录查看数据
- 2、用户注册、登录、查询等操作记入数据库中的日志
- 3、爬虫数据查询结果列表支持分页和排序
- 4、用Echarts或者D3实现3个以上的数据分析图表展示在网站中
- 5、实现一个管理端界面,可以查看(查看用户的操作记录)和管理(停用启用)注册用户。
2. 技术栈
理解该项目需要熟悉一下技术
-
后端: nodejs, express框架
-
前端: angular, bootstrap, jQuery
-
语言: html, CSS, JavaScript
-
数据可视化: eCharts
3. 实现步骤
3.1 express框架建立
在命令行中生成web网站脚手架
# 命令1
npm install -g express-generator
# 命令2
express eps_1
- package.json应用的配置文件,文件内包含程序的基础信 息、启动脚本和依赖包等。
- app.js应用的初始化文件,包括引入应用程序的基础依赖 项、设置视图即view的引擎目录以及模板、设置静态资 源路径、配置通用的中间件、引入路由和一些错误处理中间件等。
- bin/www应用的启动文件,文件内包含引用要启动的应用、设置应用监听的端口和启动http服务等。
- public/**应用的静态资源文件目录,该目录下的 文件资源不需要经过文件映射就可以直接访问。
- routes/**应用的路由文件,这些路由文件中设置的接口 最终会以指定的HTTP请求方式暴露给用户,并在用户请 求之后将结果返回。
- views应用的视图文件,在app.js中设置好视图引擎和模板 之后,该目录即为应用视图的根目录,然后路由文件就 会根据app.js中的设置加载并渲染该目录下的视图文件。
3.2 安装库
本项目的packages.json文件
{
"name": "final-project",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"angular": "^1.7.9",
"body-parser": "^1.19.0",
"bootstrap": "^4.5.0",
"cookie-parser": "^1.4.5",
"debug": "~2.6.9",
"express": "~4.16.1",
"express-session": "^1.17.1",
"http-errors": "~1.6.3",
"morgan": "~1.9.1",
"mysql": "^2.18.1",
"nodejieba": "^2.4.1",
"pug": "2.0.0-beta11"
}
}
3.3 配置mysql数据库
本案例使用的数据库为mysql, 需要前面爬虫的crawl表.
使用命令建立user, user_action两张表, 分别存储用户信息和用户操作记录
user表格字段分别为id, username, password, registertime, valid
useraction表格分别为id, username, request_time, request_url, status
--之前的新闻数据表
CREATE TABLE `fetches` (
`id_fetches` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(200) DEFAULT NULL,
`source_name` varchar(200) DEFAULT NULL,
`source_encoding` varchar(45) DEFAULT NULL,
`title` varchar(200) DEFAULT NULL,
`keywords` varchar(200) DEFAULT NULL,
`author` varchar(200) DEFAULT NULL,
`publish_date` date DEFAULT NULL,
`crawltime` datetime DEFAULT NULL,
`content` longtext,
`createtime` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id_fetches`),
UNIQUE KEY `id_fetches_UNIQUE` (`id_fetches`),
UNIQUE KEY `url_UNIQUE` (`url`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--创建用户信息数据表
CREATE TABLE `crawl`.`user` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(45) NOT NULL,
`password` VARCHAR(45) NOT NULL,
`registertime` datetime DEFAULT CURRENT_TIMESTAMP,
`valid` INT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
UNIQUE KEY `username_UNIQUE` (`username`))
ENGINE=InnoDB DEFAULT CHARSET=utf8;
--记录用户的登陆,查询(具体查询语句)操作
CREATE TABLE `crawl`.`user_action` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(45) NOT NULL,
`request_time` VARCHAR(45) NOT NULL,
`request_method` VARCHAR(20) NOT NULL,
`request_url` VARCHAR(300) NOT NULL,
`status` int(4),
`remote_addr` VARCHAR(100) NOT NULL,
PRIMARY KEY (`id`))
ENGINE=InnoDB DEFAULT CHARSET=utf8;
之后需要使用nodejs中的mysql库去连接本机上的mysql数据库, 这里我们需要连接crawl数据库
// 1. 导入mysql 模块
const mysql = require('mysql');
// 2. 建立与mysql数据库的连接
const db = mysql.createPool({
host: 'localhost', // 数据库ip地址
user: 'root', // 登陆数据库的账号
password: 'root', // 登陆数据库的密码
database: 'crawl' // 指定要操作哪个数据库
});
3.4 登陆页面
这里我们需要实现一个登陆页面和登陆功能.
3.4.1 登陆页面前端
登陆页面前端存放在./public/index.html文件中
前端页面借助了jQuery, angular, bootstap等前端库, 我们通过script标签引入.
我们自己编写的样式存放在./public/stylesheets/index.css
<!DOCTYPE html>
<html ng-app="login">
<head>
<meta charset="utf-8" />
<title>Login</title>
<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/popper.js/1.12.5/umd/popper.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/js/bootstrap.min.js"></script>
<!-- <script src="../node_modules/angular/angular.min.js"></script>-->
<script src="/angular/angular.min.js"></script>
<!-- 引入自己的样式与js-->
<link rel="stylesheet" type="text/css" href="stylesheets/index.css">
</head>
<body>
<div class="container" ng-controller="loginCtrl">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-login">
<div class="panel-heading">
<div class="row">
<div class="col-xs-6">
<a href="#" class="active" id="login-form-link">Login</a>
</div>
<div class="col-xs-6">
<a href="#" id="register-form-link">Register</a>
</div>
</div>
<hr>
</div>
<div class="panel-body">
<div class="row">
<div class="col-lg-12">
<form id="login-form" method="post" role="form" style="display: block;">
<!-- 登陆部分-->
<div class="form-group">
<input ng-model="username" tabindex="1" class="form-control" placeholder="Username" value=""/>
</div>
<div class="form-group">
<input type="password" ng-model="password" tabindex="2" class="form-control" placeholder="Password">
</div>
<!-- <div class="form-group text-center">-->
<!-- <input type="checkbox" tabindex="3" class="" name="remember" id="remember">-->
<!-- <label for="remember"> Remember Me</label>-->
<!-- </div>-->
<div class="form-group">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<button id="login-submit" tabindex="4" class="form-control btn btn-login" ng-click="check_pwd()">LOG IN</button>
</div>
</div>
</div>
</form>
<form id="register-form" method="post" role="form" style="display: none;">
<div class="form-group">
<input ng-model="add_username" tabindex="1" class="form-control" placeholder="Username" value=""/>
</div>
<div class="form-group">
<input type="password" ng-model="add_password" tabindex="2" class="form-control" placeholder="Password">
</div>
<div class="form-group">
<input type="password" ng-model="confirm_password" tabindex="2" class="form-control" placeholder="Confirm Password">
</div>
<div class="form-group">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<button tabindex="4" class="form-control btn btn-register" ng-click="doAdd()">Register Now</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- <div class="alert alert-warning alert-dismissible fade show">-->
<!-- <button type="button" class="close" data-dismiss="alert">×</button>-->
<!-- <strong>警告!</strong>{
{msg}}-->
<!-- </div>-->
</div>
<div class="alert alert-warning" ng-if="msg && msg!='ok'">
<a href="#" class="close" data-dismiss="alert">×</a>
<strong>警告!</strong>{
{msg}}
</div>
</div>
</div>
</div>
</body>
接下来, 我们需要实现路由功能, 确保在进入登陆页面后, 本地服务端会处理登陆请求事件.
3.4.2 angular登陆界面逻辑实现
在html中内置script标签, 使用angular框架实现登陆逻辑
<script>
var app = angular.module('login', []);
app.controller('loginCtrl', function ($scope, $http, $timeout) {
// 登录时,检查用户输入的账户密码是否与数据库中的一致
$scope.check_pwd = function () {
var data = JSON.stringify({
username: $scope.username,
password: $scope.password
});
$http.post("/users/login", data)
.then(
function (res) {
if(res.data.msg=='ok') {
window.location.href='/news.html';
}else{
$scope.msg=res.data.msg;
}
},
function (err) {
$scope.msg = err.data;
});
};
//增加注册用户
$scope.doAdd = function () {
// 检查用户注册时,输入的两次密码是否一致
if($scope.add_password!==$scope.confirm_password){
// $timeout(function () {
// $scope.msg = '两次密码不一致!';
// },100);
$scope.msg = '两次密码不一致!';
}
else {
var data = JSON.stringify({
username: $scope.add_username,
password: $scope.add_password
});
$http.post("/users/register", data)
.then(function (res) {
if(res.data.msg=='成功注册!请登录') {
$scope.msg=res.data.msg;
$timeout(function () {
window.location.href='index.html';
},2000);
} else {
$scope.msg = res.data.msg;
}
}, function (err) {
$scope.msg = err.data;
});
}
};
});
</script>
引入index.js文件为登陆界面添加点击时间, 并引入到index.html中
$(function() {
$('#login-form-link').click(function(e) {
$("#login-form").delay(100).fadeIn(100);
$("#register-form").fadeOut(100);
$('#register-form-link').removeClass('active');
$(this).addClass('active');
e.preventDefault();
});
$('#register-form-link').click(function(e) {
$("#register-form").delay(100).fadeIn(100);
$("#login-form").fadeOut(100);
$('#login-form-link').removeClass('active');
$(this).addClass('active');
e.preventDefault();
});
});
3.4.3 为登陆功能实现路由
为登陆, 注册页面添加路由, 路由代码存放在./router/user.js中
var express = require('express');
var router = express.Router();
var userDAO = require('../dao/userDAO');
router.post('/login', function(req, res) {
var username = req.body.username;
var password = req.body.password;
// var sess = req.session;
userDAO.getByUsername(username, function (user) {
// console.log(user);
// console.log(user[0].valid);
// console.log("success!");
if(user.length==0){
res.json({
msg:'用户不存在!请检查后输入'});
}else {
if(password===user[0].password && user[0].valid == 1){
req.session['username'] = username;
res.cookie('username', username);
res.json({
msg: 'ok'});
// res.json({msg:'ok'});
} else if(user[0].valid != 1) {
res.json({
msg:'你输入的注册用户无效!'});
} else {
res.json({
msg:'用户名或密码错误!请检查后输入'});
}
}
});
});
/* add users */
router.post('/register', function (req, res) {
var add_user = req.body;
// 先检查用户是否存在
userDAO.getByUsername(add_user.username, function (user) {
if (user.length != 0) {
// res.render('index', {msg:'用户不存在!'});
res.json({
msg: '用户已存在!'});
}else {
userDAO.add(add_user, function (success) {
res.json({
msg: '成功注册!请登录'});
})
}
});
});
// 退出登录
router.get('/logout', function(req, res, next){
// 备注:这里用的 session-file-store 在destroy 方法里,并没有销毁cookie
// 所以客户端的 cookie 还是存在,导致的问题 --> 退出登陆后,服务端检测到cookie
// 然后去查找对应的 session 文件,报错
// session-file-store 本身的bug
req.session.destroy(function(err) {
if(err){
res.json('退出登录失败');
return;
}
// req.session.loginUser = null;
res.clearCookie('username');
res.json({
result:'/index.html'});
});
});
module.exports = router;
3.4.4 数据库业务逻辑实现
我们需要在用musql库操作数据库, 对登陆界面发来的请求进行接受并处理.
- 接受登陆请求并在数据库中搜索
- 接受注册请求, 并将注册信息填入数据库
var mysql = require('mysql');
var mysqlConf = require('../conf/mysqlConf');
var userSqlMap = require('./userSqlMap');
var pool = mysql.createPool(mysqlConf.mysql);
// 使用了连接池,重复使用数据库连接,而不必每执行一次CRUD操作就获取、释放一次数据库连接,从而提高了对数据库操作的性能。
module.exports = {
add: function (user, callback) {
pool.query(userSqlMap.add, [user.username, user.password