(十二)项目功能的实现之文章评论和退出功能

  1. 创建评论集合
  2. 判断用户是否登录,如果用户登录,再允许用户提交评论表单
  3. 在服务器端创建文章评论功能对应的路由
  4. 在路由请求处理函数中接收客户端传递过来的评论信息
  5. 将评论信息存储在评论集合中
  6. 将页面重定向回文章详情页面
  7. 在文章详情页面路由中获取文章评论信息并展示在页面中

    BLOG -- 源码目录
     └──  config -- 配置文件
     └──   middleware
          └── loginGuard.js
     └── model -- 数据库操作
          └── comment.js
     ├──  public -- 静态资源
     └──  route -- 路由
          └── admin
                  ├── login.js 登录
                  └── logout.js 退出
          └── home
                  ├── article.js 博客文章
                  └──comment.js 博客评论
          └── home.js 博客
     └── views -- 模板
          └── home
                  └── common
                          └──header.art 公共头部模板
          └── article.art 文章内容模板
     └── app.js -- 创建网站服务

    comment.js

  8. 引入mongoose模块,创建集合规则
  9. 添加存储每篇文章的aid,关联文章集合
  10. 存储uid评论的用户名,关联用户集合
  11. 存储评论时间,内容time content
  12. 创建评论结合并作为模块成员进行导出
// 将评论集合构造函数进行导入
const { Comment } = require('../../model/comment'); 

module.exports = async (req, res) => {
    // 接收客户端传递过来的请求参数
    const { content, uid, aid } = req.body;

    // 将评论信息存储到评论集合中
    await Comment.create({
        content: content,
        uid: uid,
        aid: aid,
        time: new Date()
    });

    // 将页面重定向回文章详情页面
    res.redirect('/home/article?id='+ aid);
}

login.js

  • 对用户进行权限管理,管理可以去后台,普通的跳转到博客首页
  • userinfo表示用户是否登录
// 导入用户集合构造函数
const { User } = require('../../model/user');
const bcrypt = require('bcrypt');

module.exports = async (req, res) => {
    // 接收请求参数
    const {email, password} = req.body;
    // 如果用户没有输入邮件地址
    // if (email.trim().length == 0 || password.trim().length == 0) return res.status(400).send('<h4>邮件地址或者密码错误</h4>');
    if (email.trim().length == 0 || password.trim().length == 0) return res.status(400).render('admin/error', {msg: '邮件地址或者密码错误'});
    // 根据邮箱地址查询用户信息
    // 如果查询到了用户 user变量的值是对象类型 对象中存储的是用户信息
    // 如果没有查询到用户 user变量为空
    let user = await User.findOne({email});
    // 查询到了用户
    if (user) {
        // 将客户端传递过来的密码和用户信息中的密码进行比对
        // true 比对成功
        // false 对比失败
        let isValid = await bcrypt.compare(password, user.password);
        // 如果密码比对成功
        if ( isValid ) {
            // 登录成功
            // 将用户名存储在请求对象中
            req.session.username = user.username;
            // 将用户角色存储在session对象中
            req.session.role = user.role;
            // res.send('登录成功');
            req.app.locals.userInfo = user;
            // 对用户的角色进行判断
            if (user.role == 'admin') {
                // 重定向到用户列表页面
                res.redirect('/admin/user');
            } else {
                // 重定向到博客首页
                res.redirect('/home/');
            }
            
        } else {
            // 没有查询到用户
            res.status(400).render('admin/error', {msg: '邮箱地址或者密码错误'})
        }
    } else {
        // 没有查询到用户
        res.status(400).render('admin/error', {msg: '邮箱地址或者密码错误'})
    }
}

loginGuard.js

  • 把用户角色也存储在session中,如果用户是登录状态就让它跳转到博客首页,这样直接在url进行修改也不可以
const guard = (req, res, next) => {
    // 判断用户访问的是否是登录页面
    // 判断用户的登录状态
    // 如果用户是登录的 将请求放行
    // 如果用户不是登录的 将请求重定向到登录页面
    if (req.url != '/login' && !req.session.username) {
        res.redirect('/admin/login');
    } else {
        // 如果用户是登录状态 并且是一个普通用户
        if (req.session.role == 'normal') {
            // 让它跳转到博客首页 阻止程序向下执行
            return res.redirect('/home/')
        }
        // 用户是登录状态 将请求放行
        next();
    }
}

module.exports = guard;

article.art

  • 在文章评论处模板语法如果是登录状态则显示
  • 如果不是则隐藏评论表单
  • 为评论表单添加请求地址action和请求方式method和name属性隐藏域hidden,一个包含登录用户的id值uid,一个是文章的id值aid,进行原文输出
  • 对于评论人的信息进行循环动态输出,时间格式的处理dateFormate
{{extend './common/layout.art'}}

{{block 'link'}}
    <link rel="stylesheet" href="/home/css/article.css">
{{/block}}

{{block 'main'}}
    {{include './common/header.art'}}
    <!-- 文章框架开始 -->
    <div class="article">
        <div class="w1100">
            <div class="container">
                <div class="article-header">
                    <h3 class="article-title">{{article.title}}</h3>
                    <div class="article-info">
                        <span class="author">{{article.author.username}}</span>
                        <span>{{dateFormat(article.publishDate, 'yyyy-mm-dd')}}</span>
                    </div>
                </div>
                <div class="article-content">
                    {{@article.content}}
                </div>
                <div class="article-comment">
                    {{if userInfo}}
                    <h4>评论</h4>
                    <form class="comment-form" action="/home/comment" method="post">
                        <textarea class="comment" name="content"></textarea>
                        <input type="hidden" name="uid" value="{{@userInfo._id}}">
                        <input type="hidden" name="aid" value="{{@article._id}}">
                        <div class="items">
                            <input type="submit" value="提交">
                        </div>
                    </form>
                    {{else}}
                    <div><h2>先进行登录,再对文章进行评论</h2></div>
                    {{/if}}
                    <div class="comment-list">
                        {{each comments}}
                        <div class="mb10">
                            <div class="article-info">
                                <span class="author">{{$value.uid.username}}</span>
                                <span>{{dateFormat($value.time, 'yyyy-mm-dd')}}</span>
                                <span>{{$value.uid.email}}</span>
                            </div>
                            <div class="comment-content">
                                {{$value.content}}
                            </div>
                        </div>
                        {{/each}}
                    </div>
                </div>
            </div>
        </div>
    </div>
    <!-- 文章框架结束 -->
{{/block}}

logout.js

  • 对用户userinfo进行清除,否则退出之后只是删除了cookie和session,userinfo还是存在的
  • 实现退出功能,删除session 和cookie clearCookie(
  • 并且重定向到用户登录页面
module.exports = (req, res) => {
    // 删除session
    req.session.destroy(function () {
        // 删除cookie
        res.clearCookie('connect.sid');
        // 重定向到用户登录页面
        res.redirect('/admin/login');
        // 清除模板中的用户信息
        req.app.locals.userInfo = null;
    });
}

home.js

  • 添加评论功能路由
// 引用expess框架
const express = require('express');
// 创建博客展示页面路由
const home = express.Router();

// 博客前台首页的展示页面
home.get('/', require('./home/index'));

// 博客前台文章详情展示页面
home.get('/article', require('./home/article'));

// 创建评论功能路由
home.post('/comment', require('./home/comment'));

// 将路由对象做为模块成员进行导出
module.exports = home;

comment.js

  • 接受到客户端传过来的请求信息
  • 通过对象解构的形式获取
  • 将评论集合构造函数进行导入
  • 将评论信息存储到评论集合中,异步
  • 将页面重定向回文章详情页面
// 将评论集合构造函数进行导入
const { Comment } = require('../../model/comment'); 

module.exports = async (req, res) => {
    // 接收客户端传递过来的请求参数
    const { content, uid, aid } = req.body;

    // 将评论信息存储到评论集合中
    await Comment.create({
        content: content,
        uid: uid,
        aid: aid,
        time: new Date()
    });

    // 将页面重定向回文章详情页面
    res.redirect('/home/article?id='+ aid);
}

article.js

  • 导入评论集合构造函数
  • 查询当前文章所对应的评论信息,用户名要多集联合查询populate
  • 对文章和评论进行渲染
// 导入文章集合构造函数
const { Article } = require('../../model/article');
// 导入评论集合构造函数
const { Comment } = require('../../model/comment');

module.exports = async (req, res) => {
    // 接收客户端传递过来的文章id值
    const id = req.query.id;
    // 根据id查询文章详细信息
    let article = await Article.findOne({_id: id}).populate('author');
    // 查询当前文章所对应的评论信息
    let comments = await Comment.find({aid: id}).populate('uid')
    
    // res.send('欢迎来到博客文章详情页面')
    res.render('home/article.art', { article, comments });

header.art

  • 对于退出登录添加href
<!-- 头部 -->
<div class="header">
    <!-- 网站标志 -->
    <div class="logo fl">
      黑马程序员 <i>ITHEIMA</i>
    </div>
    <!-- /网站标志 -->
    <!-- 用户信息 -->
    <div class="info">
        <div class="profile dropdown fr">
            <span class="btn dropdown-toggle" data-toggle="dropdown">
                {{userInfo && userInfo.username}}
                <span class="caret"></span>
            </span>
            <ul class="dropdown-menu">
                <li><a href="user-edit.html">个人资料</a></li>
                <li><a href="/admin/logout">退出登录</a></li>
            </ul>
        </div>
    </div>
    <!-- /用户信息 -->
</div>
<!-- /头部 -->

app.js

  • 在用户未登录情况下,不会初始化一个cookie,saveUninitialized: false
  • 给cookie设置一个自动过期的时间
// 引用expess框架
const express = require('express');
// 处理路径
const path = require('path');
// 引入body-parser模块 用来处理post请求参数
const bodyPaser = require('body-parser');
// 导入express-session模块
const session = require('express-session');
// 导入art-tempate模板引擎
const template = require('art-template');
// 导入dateformat第三方模块
const dateFormat = require('dateformat');
// 导入morgan这个第三方模块
const morgan = require('morgan');
// 导入config模块
const config = require('config');
// 创建网站服务器
const app = express();
// 数据库连接
require('./model/connect');
// 处理post请求参数
app.use(bodyPaser.urlencoded({extended: false}));
// 配置session
app.use(session({
    secret: 'secret key',
    saveUninitialized: false,
    cookie: {
        maxAge: 24 * 60 * 60 * 1000
    }
}));

// 告诉express框架模板所在的位置
app.set('views', path.join(__dirname, 'views'));
// 告诉express框架模板的默认后缀是什么
app.set('view engine', 'art');
// 当渲染后缀为art的模板时 所使用的模板引擎是什么
app.engine('art', require('express-art-template'));
// 向模板内部导入dateFormate变量
template.defaults.imports.dateFormat = dateFormat;

// 开放静态资源文件
app.use(express.static(path.join(__dirname, 'public')));

console.log(config.get('title'))

// 获取系统环境变量 返回值是对象 
if (process.env.NODE_ENV == 'development') {
    // 当前是开发环境
    console.log('当前是开发环境')
    // 在开发环境中 将客户端发送到服务器端的请求信息打印到控制台中
    app.use(morgan('dev'))
} else {
    // 当前是生产环境
    console.log('当前是生产环境')
}

// 引入路由模块
const home = require('./route/home');
const admin = require('./route/admin');

// 拦截请求 判断用户登录状态
app.use('/admin', require('./middleware/loginGuard'));

// 为路由匹配请求路径
app.use('/home', home);
app.use('/admin', admin);

app.use((err, req, res, next) => {
    // 将字符串对象转换为对象类型
    // JSON.parse() 
    const result = JSON.parse(err);
    // {path: '/admin/user-edit', message: '密码比对失败,不能进行用户信息的修改', id: id}
    let params = [];
    for (let attr in result) {
        if (attr != 'path') {
            params.push(attr + '=' + result[attr]);
        }
    }
    res.redirect(`${result.path}?${params.join('&')}`);
})

// 监听端口
app.listen(80);
console.log('网站服务器启动成功, 请访问localhost')

日月彗心
4 声望0 粉丝

人生唯一确定的就是不确定的人生,人生唯一恐惧的就是恐惧本身