web期末作业

web期末项目

一、项目要求

•基于第一个项目爬虫爬取的数据,完成数据展示网站。

•基本要求:

•1、用户可注册登录网站,非注册用户不可登录查看数据

•2、用户注册、登录、查询等操作记入数据库中的日志

•3、爬虫数据查询结果列表支持分页和排序

•4、用Echarts或者D3实现3个以上的数据分析图表展示在网站中

•5、实现一个管理端界面,可以查看(查看用户的操作记录)和管理(停用启用)注册用户。

•扩展要求(非必须):

•1、实现对爬虫数据中文分词的查询

•2、实现查询结果按照主题词打分的排序

•3、用Elastic Search+Kibana展示爬虫的数据结果

二、项目介绍

1.组织结构

├── app.js – 应用配置
├── bin
│ └── www – 项目运行脚本
├── conf
│ └── mysqlConf.js – mysql配置文件
├── dao
│ ├── actionDAO.js/logDAO.js/manageDAO.js/newsDAO/userDAO.js – 封装和数据库的交互
│ └── userSqlMap.js – SQL语句封装
├── node_modules
│ └── result.js – 返回结果对象封装
├── package.json – 依赖模块
├── public – 前端静态页面目录(.html文件等)
├── views
└── routes
└── users.js – 用户操作路由及业务逻辑

2.模块依赖

(以 users.js 为例)

www -> app.js -> routes/users.js -> userDAO.js -> mysqlConf.js 、 userSqlMap.js

3.使用技术

  • 后端:

    ​ node.js

    ​ express

  • 前端:

    ​ angular.js

3.1 node.js

​ Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台,],是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎(V8引擎执行Javascript的速度快,性能好)。

Node.js 应用组成:

  1. **引入 required 模块:**使用 require 指令来载入模块。
    在这里插入图片描述

  2. **创建服务器:**服务器监听客户端的请求。
    在这里插入图片描述

  3. 接收请求与响应请求: 创建服务器,客户端可以使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据。

3.2 express

Express 是一个简洁而灵活的 node.js Web应用框架, 提供了丰富的 HTTP 工具,使用 Express 可以快 速地搭建一个完整功能的网站。

**Express 框架核心特性: **

​ – 可以设置中间件来响应 HTTP 请求。

​ – 定义了路由表用于执行不同的 HTTP 请求动作。

​ – 可以通过向模板传递参数来动态渲染 HTML 页面。

重要目录和文件:

​ – bin/www:应用的启动文件。

​ – public/** : 设置静态文件。

​ – xxx.html:前端,搜索网页,用html form表单构建查询请求。

​ – routes/**:决定了由谁(指定脚本)去响应客户端请求。

​ – xxx.js:后端,响应客户端的查询请求。

3.3 Angular.js

Angular是一个前端框架,可以把静态页面与动态数据绑定起来。AngularJS有具有许多特性,主要有MVC、模块化、自动化双向数据绑定、依赖注入等。

AngularJS 应用组成

  • View(视图), 即 HTML。
  • Model(模型), 当前视图中可用的数据。
  • Controller(控制器), 即 JavaScript 函数,可以添加或修改属性。

项目中用到的一些指令

  • ng-app : 指令初始化一个 AngularJS 应用程序。指令告诉 AngularJS,<div> 元素是 AngularJS 应用程序 的"所有者"。

  • ng-init : 指令初始化应用程序数据。

  • ng-model : 指令把元素值(比如输入域的值)绑定到应用程序。双向绑定,即在修改输入域的值时, AngularJS 属性的值也将修改.

  • ng-controller : 用于应用添加控制器。在控制器中,你可以编写函数和变量等,通过 scope 对象进行访问。

  • ng-bind :把应用程序数据绑定到 HTML 视图。

  • Scope : 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带,是一个 JavaScript 对象($Scope),带有属性和方法,这些属性和方法可以在视图和控制器中使用。

模块化设计

1.官方提供的模块:ng、ngRoute、ngAnimate

2.用户自定义的模块:angular.module(‘xxx’,[ ])

4.项目运行

项目根目录终端执行: npm start

三、mysql配置与数据库设计

1. 安装mysql

下载MySQL Community Server 8.0.19的免安装版

网址:https://dev.mysql.com/downloads/mysql/

因之前已安装过mysql,具体安装步骤省略。

2. 数据库

2.1 数据库设计

database:crawl

tables: fetches、user、user_action

  • fetches : 存爬取的新闻数据。

  • user : 存用户信息,记录了id、用户名、密码、注册时间和是否可用(管理员可以通过canuse进行用户禁用和启用)。

在这里插入图片描述

  • user_action : 存用户操作信息(id、用户名、请求时间、方法、url、状态码、地址)。

在这里插入图片描述

2.2 数据库连接

在conf/mysqlConf.js中:

module.exports = {
    mysql: {
        host: 'localhost',
        user: 'root',
        password: 'root',
        database:'crawl',
        // 最大连接数,默认为10
        connectionLimit: 10
    }
};

四、登陆、注册、限制查看、退出

登陆与注册

动图界面:

在这里插入图片描述

1.后端:

www -> app.js -> routes/users.js -> userDAO.js -> mysqlConf.js -> userSqlMap.js

  • routes/users.js:路由,注册、登陆、登出。调用userDAO中的函数在后端对数据库数据进行操作。

以登陆为例:调用了userDAO的getByUsername函数,得到数据库中username的密码,进而判断密码是否输入正确。

var express = require('express');
var router = express.Router();   //路由
var userDAO = require('../dao/userDAO');    //调用userDAO中编写的函数

router.post('/login', function(req, res) {
  var username = req.body.username;
  var password = req.body.password;

  userDAO.getByUsername(username, function (user) {
    if(user.length==0){
      res.json({msg:'用户不存在或被禁用!请检查后输入'});

    }else {
      if(password===user[0].password){
        req.session['username'] = username;
        res.cookie('username', username);
        res.json({msg: 'ok'});
        // res.json({msg:'ok'});
      }else{
        res.json({msg:'用户名或密码错误!请检查后输入'});
      }
    }
  });
});

  • /dao/userDAO.js: 编写具体的函数和sql语句。

    • 编写了add函数和getByUsername函数,作用分别为注册时插入数据和登陆时通过username得到对应的正确密码。

    • 这里使用了userSqlMap文件,中编写了具体的sql语句。

    • mysql.createPool:使用了连接池,重复使用数据库连接,而不必每执行一次CRUD操作就获取、释放一次数据库连接,从而提高了对数据库操作的性能。

var mysql = require('mysql');
var mysqlConf = require('../conf/mysqlConf');
var userSqlMap = require('./userSqlMap');
var pool = mysql.createPool(mysqlConf.mysql);//连接池

module.exports = {
    add: function (user, callback) {
        pool.query(userSqlMap.add, [user.username, user.password], function (error, result) {
            if (error) throw error;
            callback(result.affectedRows > 0);
        });
    },
    getByUsername: function (username, callback) {
        pool.query(userSqlMap.getByUsername, [username], function (error, result) {
            if (error) throw error;
            callback(result);
        });
    },

};
  • /dao/userSqlMap.js:userDAO中函数用到的具体语句:

    注意这里canuse = 1才能被返回(canuse = 0 时用户被禁用)

    var userSqlMap = {
        add: 'insert into user(username, password) values(?, ?)',//注册时用
        getByUsername: 'select username, password from canuse = 1 and username = ?'//登陆时用
    };
    
    module.exports = userSqlMap;
    

    这里module.exports提供了暴露接口的方法, module.exports对象由模块系统创建。在自己写模块时,需在模块最后写好模块接口,声明这个模块对外暴露了什么内容。

2.前端:

使用AngularJS框架

public/index.html:

  • 定义模块名称login:var app = angular.module(‘login’, []);

    网页中设置:<html ng-app=“login”>

  • 应用添加控制器loginCtrl:app.controller(‘loginCtrl’, function ($scope, $http, $timeout) {……}

    网页中设置:<div class=“container” ng-controller=“loginCtrl”>

  • 在控制器中可以编写函数和变量等,通过 Scope 对象进行访问。

    • index.html中,创建应用程序login,为其添加控制器loginCtrl,并编写函数check_pwd检查密码是否正确、doAdd增加注册用户(此函数代码省略)。
        <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;
                        });
    
                };
                
        <\script>
    

3.限制查看:非注册用户不可查看:

在响应文件时设置判断:如果用户未登陆,返回到登陆界面index.html

4.退出:

在这里插入图片描述

html文件中:ng-click=“logout()”

public/javascripts/news.js 中进行请求:

在这里插入图片描述

routes/news.js:响应请求,跳回到index界面

在这里插入图片描述

五、用户操作记录计入数据库

  1. 在dao/logDAO.js中:编写sql语句insert到数据库
module.exports = {
    userlog :function (useraction, callback) {
        pool.query('insert into user_action(username,request_time,request_method,request_url,status,remote_addr) values(?, ?,?,?,?,?)',
            useraction, function (error, result) {
            if (error) throw error;
            callback(result.affectedRows > 0);
        });
    },
};
  1. 在app.js中:使用logDAO.js的userlog函数直接将用户操作记入mysql中

    var logDAO = require('./dao/logDAO.js');
    
    if(username!='notlogin'){
        logDAO.userlog([username,request_time,request_method,request_url,status,remote_addr], function (success) {
          console.log('成功保存!');
        })
      }
    

六、查询列表支持排序与分页

在这里插入图片描述

1.排序

从前端到后端:

(1)search.html:

由上图可看出,左侧有两个按钮进行时间的升序和降序排序。在代码中,通过ng-click指令分别执行searchsortASC()和searchsortDESC()函数。

<div class="pull-left" style="margin-top: 12px;">
    <button type="submit" class="btn btn-primary" ng-click="searchsortASC()" >发布时间升序</button>
    <button type="submit" class="btn btn-primary" ng-click="searchsortDESC()">发布时间降序</button>
</div>
(2)public/javascripst/news.js

在public/javascripst/news.js中定义了searchsortASC()和searchsortDESC()的具体内容,分别将sorttime变量设置为1和2,调用search函数。

search()函数:

​ 获取到输入的文本信息,并构建请求路径url,使用$http.get()发送请求并获取返回的数据。(返回的对象有success和error两个回调方法,两个方法都有四个参数:data: 返回的数据(或错误),status: 响应的状态码,headers: 一个函数,congfig: 请求的配置对象)

​ 成功时调用$scope.initPageSort()进行分页显示。

2.分页

(1)search.html:

在页面右下角显示,点击页码page调用selectPage(page),点击“上一页”、“下一页”分别调用函数Previsous(),Next()(在news.js中本质是调用selectPage(page-1)和selectPage(page+1));

使用ng-class指令动态绑定类。isActivePage用于设置当前选中页样式。

<div class="pull-right">
    <nav>
        <ul class="pagination">
            <li>
                <a ng-click="Previous()" role="button"><span role="button">上一页</span></a>
            </li>
            <li ng-repeat="page in pageList" ng-class="{active:isActivePage(page)}" role="button">
                <a ng-click="selectPage(page)" >{{ page }}</a>
            </li>
            <li>
                <a ng-click="Next()" role="button"><span role="button">下一页</span></a>
            </li>
        </ul>
    </nav>
</div>
(2)public/javascripst/news.js

起主要作用的是selectPage函数,先进行分页转换(需注意第一页和最后一页分别无前一页和后一页),用slice(start,end)截取筛选出表格当前显示数据。

    $scope.selectPage = function (page) {
        //不能小于1大于最大(第一页不会有前一页,最后一页不会有后一页)
        if (page < 1 || page > $scope.pages) return;
        //最多显示分页数5,开始分页转换
        var pageList = [];
        if(page>2){
            for (var i = page-2; i <= $scope.pages && i < page+3; i++) {
                pageList.push(i);
            }
        }else {
            for (var i = page; i <= $scope.pages && i < page+5; i++) {
                pageList.push(i);
            }
        }

        $scope.index =(page-1)*$scope.pageSize+1;
        $scope.pageList = pageList;
        $scope.selPage = page;
        $scope.items = $scope.data.slice(($scope.pageSize * (page - 1)), (page * $scope.pageSize));//通过当前页数筛选出表格当前显示数据
        console.log("选择的页:" + page);
    };

七、数据可视化展示:Echarts

使用Echarts进行绘图

  • news.html:

设置一个下拉菜单,ng-click指令分别调用对应函数。

  • public/javascripts/news.js:

    对5个图像的操作。具体见下方。

1.柱状图-新闻发布数与时间的变化

在这里插入图片描述

  • public/javascripts/news.js中:$scope.histogram = function () :柱状图的具体操作

    主要思路:

    • 获取响应的数据后,使用正则表达式对时间数据进行处理
    • 使用echarts.init创建实例(发到news.html中id= 'main1’的位置)
    • 设置配置项和数据:title、legend、xAxis、yAxis
    • 使用刚指定的配置项和数据显示图表 (myChart.setOption(option);)

在这里插入图片描述

  • routes/news.js :router.get(’/histogram’, function(request, response) 定义了收到请求后的响应操作,从数据库中抽取响应信息并响应。

在这里插入图片描述

2.饼状图:作者发布新闻数量

注:只显示发布过5条以上新闻数量的作者。

在这里插入图片描述

public/javascripts/news.js中:$scope.pie = function () :饼状图的具体操作

思路同前面柱状图,不再赘述,主要差异在于动态效果:setInterval()中设置了间隔和相关高亮显示。

routes/news.js router.get(’/pie’, function(request, response):定义了收到请求后的响应操作,从数据库中抽取响应信息并响应。

3.折线图:”疫情“在新闻中出现的次数时变图

在这里插入图片描述

public/javascripts/news.js中:$scope.line = function () :饼状图的具体操作

思路同前面柱状图,不再赘述。

routes/news.js router.get(/line’, function(request, response):定义了收到请求后的响应操作,从数据库中抽取响应信息并响应。

4.词云:将新闻类容进行jieba分词的展示

在这里插入图片描述

public/javascripts/news.js中:$scope.wordloud = function () :饼状图的具体操作

思路同前面柱状图,不再赘述。主要差异在于使用了蒙版:用于改变词云形状(实际为遮挡了一部分)。

在这里插入图片描述

routes/news.js router.get(/wordloud’, function(request, response):定义了收到请求后的响应操作,从数据库中抽取响应信息并响应。

5.柱状对比图:播放不同时间,三个热点词的出现次数统计。

分别对应三个时间:[‘2021-04-26’, ‘2021-04-27’, ‘2021-04-28’]

具体文件设置思路和前面四个图类似,不再赘述。

在这里插入图片描述

八、管理端界面

实现一个管理端界面,可以查看(查看用户的操作记录)和管理(停用启用)注册用户。

注:只有成功登陆管理员账号guanliyuan才能成功查找,否则将跳回登陆界面。

在菜单栏增加一个下拉菜单,两个页面分别进行用户操作记录和管理注册用户,分别调用showAction()和showManage()。

在这里插入图片描述

1.查看(查看用户的操作记录)

输入栏为空时,查询所有用户的操作,否则查询某个用户的操作记录

在这里插入图片描述

(1)public/javascripts/news.js
  • $scope.showAction():控制是否显示界面

在这里插入图片描述

之所以能够实现隐藏和展示的原因在于html文件中,body部分初始化时将所有控制界面显示的变量设为了false,然后在showXxx中设置它的值为true或false,ng-show指令设置其为true时才显现出来。

在这里插入图片描述

ng-hide:表达式为 true 时隐藏 HTML 元素。

ng-show:表达式为 true 时显示指定的 HTML 元素。

在这里插入图片描述

注:除action外(查看用户操作),search(查看新闻数据)、manage (查看并管理注册用户)情况相同,不再一一赘述。

  • $scope.action():获取到输入的文本信息(需要查找的用户名),并构建请求路径url,使用$http.get()发送请求并获取返回的数据。获取到响应数据后设置isisshoaction为true,以表格的形式展示出来。
    在这里插入图片描述
(2)routes/news.js

调用actionDAO的函数action(),获取返回的用户操作的数据。

注:

1.响应设置为:只有为‘guanliyuan’才能返回对应数据,其他用户将跳回登陆页面

2.注意在文件开头:var actionDAO = require(’…/dao/actionDAO’); (实验时因为忘记此步骤而卡了很久。)

在这里插入图片描述

(3)dao/actionDAO

编写函数action():编写sql语句,根据传入的用户名从数据库中获取得到用户操作信息。

在这里插入图片描述

2.管理(停用启用)注册用户

  • 输入为空时点击查找,可看到所有注册用户的信息:

在这里插入图片描述

  • 输入为某用户名,点击查找,看到某用户的而信息。

    在这里插入图片描述

  • 输入为某用户名,点击启用或禁用,达到对某用户启用或禁用的效果。

    如:这里输入hhh后点击启用,再查看用户信息:

在这里插入图片描述

canuse从0变成了1,启用成功。

实现思路:在建表时创建了属性canuse,默认值为1,当管理员进行禁用时,值改为0;在登陆时根据canuse的值判断是否能成功登陆。(1:可以登陆;0:不可登陆)

在登陆时获取密码时,需确保canuse为1,否则返回的值的长度为0,返回警告信息且无法登陆页面。

getByUsername: 'select username, password from user where canuse = 1 and username = ?'

在这里插入图片描述

启用某用户时,设置canuse为1,则用户可以正常登陆。

(1)public/javascripts/news.js

$scope.showManage():控制是否显示界面

获取到输入的文本信息(需要查找的用户名),并构建请求路径url,使用$http.get()发送请求并获取返回的数据:

  • $scope.manage():查找用户信息功能。

​ user2传入的想要查询的用户的username。
在这里插入图片描述

  • $scope.able():启用用户功能。

​ user2传入想要启用的用户username,method传入进行的操作(字符串):“able”

在这里插入图片描述

  • $scope.disable():禁用用户功能。

    user2传入想要禁用的用户username,method传入进行的操作(字符串):“disable”

    代码与上面scope.able()类似,method=${“able”}处改为method=${“disable”}即可。

(2)routes/news.js

对请求进行响应,调用响应的函数。注:响应设置为:只有为‘guanliyuan’才能返回对应数据,其他用户将跳回登陆页面。

router.get(’/manage’, function(request, response):

  • 当传入的method为"able":调用manageDAO.able函数。
  • 当传入的method为"disable":调用manageDAO.disable函数。
  • 当未传入的method(undefined):调用manageDAO.manage函数。

在这里插入图片描述

(3)dao/manageDAO.js
  • 函数manage():select语句选出用户信息。

在这里插入图片描述

  • 函数able()和disable():通过update语句将用户的canuse设置为1或0,以实现用户的启用和禁用。(用户在登陆时若canuse=0,则无法登陆,达到禁用效果)。

    以able()为例:

在这里插入图片描述

九、扩展要求思路

  • 按主题词打分

思路:在返回查询结果后新增一个函数,使用TF-IDF方法对各查询结果的主题词进行打分,每篇文章的得分取得分最高的前5个主题词的打分平均值,然后参照“按时间进行排序”,对返回的数据结果按主题词score值进行展现。

若爬取的数据不多,也可考虑将fetches表中新增一个float类型的属性score。存数据时对主题词使用TF-IDF公式进行打分,然后取最高的5个(或小于3个)的平均值作为score值。然后参照“按时间进行排序”,对返回的数据结果按主题词score值进行展现。

在这里插入图片描述

  • 对爬虫数据中文分词的查询

思路:使用jieba进行分词,新增一个表fetches_wordcut用于对应文章的id和分词结果,查询时在fetches_wordcut表中查询即可。

十、易错点

  1. 注意单双引号,这里字符串是必须要用双引号的,使用单引号导致获取到的json文件不能通过JSON.parse()进行转码。

在这里插入图片描述

  1. 要区分undefined和字符串为“”的情况,当输入框未被点击,提交的是undefined;当输入框已被点击,但未输入东西时,提交的是“”,因此在sql语句查找时应考虑完整。
    在这里插入图片描述

  2. 注意引入要使用的文件,例如在routes/news.js开头:var actionDAO = require(’…/dao/actionDAO’);

  3. 文本框无法输入的问题:class 引起导致冲突。前两个文本框无法输入内容,只有第三个文本框可以输入。

    (1)action.html中:删掉 class=“form-control”

    (2)search.html中:

在这里插入图片描述

​ 发现上面多一个<div class = “col-lg-3”>,删掉class = “col-lg-3”,发现可以输入了。

​ 再自行设置样式即可。

  1. app.js:是一个自加载函数,包含路由信息和angular程序启动时最先加载的代码(app.run())。当有新的路由文件,要记得放在app.js中。
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值