博客搭建案例详细思路:express+node.js

##项目:博客搭建

1项目环境搭建

1.1项目介绍

1.博客内容展示
2.博客管理功能

1.2案例初始化

  1. 建立项目所需文件夹
    public 静态资源
    model 数据库操作
    route 路由
    views 模板

  2. 初始化项目描述文件:npm init -y

  3. 下载所需第三方模块:npm install express mongoose art-template express-art-template

  4. 创建网站服务器

    const express = require('express');
    const app = express();
    app.listen(80);
    console.log('网站服务器启动成功,请访问localhost');
    
  5. 构建模块化路由
    app.js

    const home = require('./route/home');
    const admin = require('./route/admin');
    //拦截客户端请求,如果输入的是/home,去找home这个路由,/home是自定义的
    app.use('/home',home);
    app.use('/admin',admin);
    
  6. 构建博客管理页面模板

    const path = require('path');
    // 开放静态资源
    app.use(express.static(path.join(__dirname,'public')));
    //把配置模板
    app.set('views',path.join(__dirname,'views'));
    app.set('view engine','art');
    app.engine('art',require('express-art-template'));
    
    
  7. 地址栏输入:http://localhost/admin/login看效果。首先我们设置的是app.use(’/admin’,admin);,所以路由第一步去找admin.art

    //**admin.art**
    const express = require('express');
    //路由对象
    const admin  = express.Router();
    admin.get('/login',(req,res)=>{
        // res.send('欢迎来到博客登录页');
        res.render('admin/login');
    });
    module.exports = admin;
    
    

    第二步内部会渲染 模板文件login.art:res.render(‘admin/login’);
    在这里插入图片描述

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

  1. 模板文件的外链资源,它的的相对路径是相对于浏览器中的地址栏中的请求路径,
    • 比如如果修改引导路由:app.use(’/abc’,admin);
    • 地址栏相应的访问login方式:http://localhost/abc/login
    • 此时login的外链css会报错,因为abc下面没有base.css
      在这里插入图片描述

总结: app.use(’/xxx’,admin)是动态的,导致外链资源css也是动态的,即http://localhost/xxx/css/base,所以在模板文件中要以绝对路径代替相对路径,前面加/代表绝对路径。xxx代表引导路由。外链正确格式写法: <linkrel=“stylesheet” href="/xxx/css/base.css">,比如 <linkrel=“stylesheet” href="/abc/css/base.css">

  1. 将模板公共部分抽离,方便维护。静态资源css等是由浏览器解析的,所以它的相对路径是相对于浏览器请求地址的,得用绝对路径代替。而模板文件中引入子模板是由模板引擎来解析的,所以写相对路径.
  • 新建的common文件夹,抽离出公共模板aside.art和header.art作为子模板 、layout.art作为html骨架
    在这里插入图片描述

抽离后的模板文件之一:user.art,以此类推
在这里插入图片描述

2 项目功能实现

2.1登陆

1.创建用户集合,初始化用户

  • 链接数据库
//connect.js
// 引入mongoose第三方模块
const mongoose = require('mongoose');
// 连接数据库
mongoose.connect('mongodb://localhost/blog', {useNewUrlParser: true })
	.then(() => console.log('数据库连接成功'))
	.catch(() => console.log('数据库连接失败'))
  • 创建用户集合
//user.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
	username: {
		type: String,
		required: true,
		minlength: 2,
		maxlength: 20
	},
	email: {
		type: String,
		// 保证邮箱地址在插入数据库时不重复
		unique: true,
		required: true
	},
	password: {
		type: String,
		required: true
	},
	// admin 超级管理员
	// normal 普通用户
	role: {
		type: String,
		required: true
	},
	// 0 启用状态
	// 1 禁用状态
	state: {
		type: Number,
		default: 0
	}
});
// 创建集合
const User = mongoose.model('User', userSchema);
  • 初始化用户
//user.js
const user = await User.create({
		username: 'iteheima',
		email: 'itheima@itcast.cn',
		password: pass,
		role: 'admin',
		state: 0
	});
// 将用户集合做为模块成员进行导出
module.exports = {
	User
}
//app.js
//数据库连接
...
require('./model/connect');
require('./model/user');
...
  1. 为登陆表单设置请求地址,请求方式和表单项name属性
    在这里插入图片描述

  2. 当用户点击登录按钮时,客户端验证用户是否填写了登录表单

  3. 如果其中一项没有输入,阻止表单提交

    //新建common.js
    function serializeToJson(form){
    	var result ={};
    	var f = form.serializeArray();
    	f.forEach(function(item){
            result[item.name]= item.value;
        });
     return result;}
    
    
    //login.art
    ...
    //注意提交地址写法/admin/login
     <form action="/admin/login" method="post">
        <div class="form-group">
           <label>邮件</label>
            <input name="email" type="email" class="form-control" placeholder="请输入邮件地址">
        </div>
        <div class="form-group">
          <label>密码</label>
          <input name="password" type="password" class="form-control" placeholder="请输入密码">
       </div>
       <button type="submit" class="btn btn-primary">登录</button>
     </form>
     ...
     <script src="/admin/js/common.js"></script>  
     <script>
         $('#loginForm').on('submit',function(){
            var  result = serializeToJson($(this))
            /* console.log(result);
            {email: "1234353@admin.com", password: "yqs2891682"}
            */
            if(result.email.trim().length==0){
                alert('请输入邮件地址');
                //阻止程序向下执行
                return false;
            }else if(result.email.trim().length==0){
                 alert('请输入密码')
                //阻止程序向下执行
                return false;
            }          
        });
    </script>
    
  4. 服务端接收请求参数,再次验证用户是否填写了登录表单,因为客户端可以禁止js验证

  5. 如果其中一项没有输入,客户端做出响应,阻止程序向下执行

    //admin.js添加登陆功能路由
    const express = require('express');
    
    const admin  = express.Router();
    admin.get('/login',(req,res)=>{
        res.render('admin/login');
    });
    //实现登陆功能
    admin.post('/login',(req,res)=>{
     //{"email":"123456@qq.com","password":"yqs2891682"}
    	const {email,password}=req.body;
       if(email.trim().length==0||password.trim().length==0){
        //res.end状态码默认200,客户端请求格式错误状态码一般设置成400
        return  res.status(400).send('<h4>邮件地址或者密码错误</h4>')     
       }
    })
    
    admin.get('/user',(req,res)=>{
        res.render('admin/user');
    })
    module.exports = admin;
    
    
    
    //app.js
    ...
    const bodyPaser= require('body-parser');
    app.use(bodyPaser.urlencoded({extended:false}));
    ...
    
  6. 根据邮箱地址查询用户数据库信息

  7. 如果用户不存在,为客户端做出响应,阻止程序向下执行

  8. 如果用户存在,将用户名和密码进行比对

  9. 比对成功,用户登录成功

  10. 比对失败,用户登录失败

    //app.js
    //导入用户集合构造函数
    ...
    const { User }=require('../model/user');
    admin.post('/login',async(req,res)=>{
    ....
    let user = await User.findOne({email});
      	if(user){
       		if(password==user.password){
        	 res.send('登陆成功');
         	}else{
        	 return  res.status(400).send('<h4>邮件地址或者密码错误</h4>')     
        }else{
        return  res.status(400).send('<h4>邮件地址或者密码错误</h4>')    
        }})
    
    ...
    

3 项目包含知识点

3.1密码加密bcrypt

  1. 哈希加密是单层加密方式;1234 =>abcd
  2. 在加密的密码中加入随机字符串可以增加密码被破解的难度
  3. bcrypt依赖的环境:
    • python 2.x ;(装完路径需加入环境变量)
    • node install -g node-gyp;
    • windows-build-tools(npm install --global --production windows-build-tools)
//导入bcrypt模块
const bcypt = require('bcrypt');
//生成随机字符串gen=>generate生成salt盐,数字越大越复杂,默认值就好
let salt = await bcrypt.genSalt(10);
//使用随机字符串对密码进行加密
let pass = await bcrypt.hash('明文密码',salt);
//密码比对
let isEqual=await bcrypt.compare('明文密码','加密密码');

//改动一下user.js

const User = mongoose.model('User',userSchema);
async function  createUser(){
     const salt = await bcrypt.genSalt(10);
     const password = await bcrypt.hash('123456',salt);
     const user1 = await User.create({
        username:'yan',
        email:'123456@qq.com',
        password:password,
        role:'admin',
        state:0
    });
};
createUser();

//改动一下admin.js

 let user = await User.findOne({email});
  if(user){
        let isValid = await bcrypt.compare(password,user.password);
        if(isValid){
            res.send('登陆成功');
        }else{
            res.status(400).render('admin/error',{message:'邮件地址或者密码错误'});  
        }
  }

3.2cookie和session

cookie中的数据是以域名的形式进行区分的;

  • cookie:浏览器在电脑硬盘中开辟的一块空间,主要供服务器端存储数据。
  • cookie中的数据是有过期时间的,超过时间数据会被浏览器自动删除。
  • cookie中的数据会随着请求被自动发送到服务器端。
  • 当客户端第一次向服务器发送请求的时候,服务器在响应的同时可以为客户端的cookie存储数据,下次客户端会自动携带cookie发送。
  • session:实际上就是一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个sessionId做为唯一标识。将sessionId存储在客户端的cookie中,在下一次客户端发送请求时,cookie会自动发送给服务器端,然后在session对象中查找sessionId,验明正身

在node.js中需要借助express-session实现session功能。

//app.js
const session = require('express-session');
app.use(session({secret:'secret key'}));//secret固定的,secret key是自定义的
//admin.js
if(isValid){
          //第一个username是req的自定义属性,值是查询出的用户名
            req.session.username= user.username;
           //重定向用户列表,用express自带的方法redirect
            res.redirect('/admin/user');
        }
//创建用户列表路由
admin.get('/user',(req,res)=>{
    res.render('admin/user',{
      msg:req.session.username
    });
})
//user.art
...
<div class="title">
    <h4>用户{{msg?msg:'用户名不存在'}}</h4>
    <span>找到1个用户</span>
    <a href="user-edit.html" class="btn btn-primary new">新增用户</a>
 </div>
 ...

问题1:头部模块是公共区域,跳转到其他页面,每个路由跳转都要重复一遍render代码
解决:把一些公共数据暴露到模板中。在app对象下面有个locals对象,把数据放到这个对象里面,在模板中就可以直接拿到,不需要render了

//...
if(isValid){
          //第一个username是req的自定义属性,值是查询出的用户名
            req.session.username= user.username;
            //app对象无需引入,在req对象下面有个app属性,实际就是app.js的app对象
            req.app.locals.userInfo123=user;
            //重定向用户列表,用express自带的方法redirect
            res.redirect('/admin/user');
        }
// ...
//创建用户列表路由
admin.get('/user',(req,res)=>{
    res.render('admin/user');
})
//user.art
...
<div class="title">
    <h4>用户{{userInfo123&&userInfo123.username}}</h4>
    <span>找到1个用户</span>
    <a href="user-edit.html" class="btn btn-primary new">新增用户</a>
 </div>
 ...

问题2:没有登录依然可以输入localhost/admin/user,却能跳过登录验证直接访问到用户界面
解决:在跳转路由前拦截请求,判断用户登录状态,也就是请求地址必须是’/dmin/login’,并且session保存有用户名

//写在前面,第一个参数也可是表示以/admin开头的请求
app.use('/admin',(req,res,next)=>{
    if (req.url !='/login' && !req.session.username) {
        res.redirect('/admin/login');
    } else {
        next();
    }
});
//拦截客户端请求,如果是home,去找home这个路由
app.use('/home',home);
app.use('/admin',admin);

问题3:优化代码,功能代码放到另外一个文件
在这里插入图片描述
根目录下新建文件夹middleware,再新建loginGuard.js文件

//app.js
app.use('/admin',require('./middleware/loginGuard'));
//loginGuard.js
const guard = (req,res,next)=>{
    //如果用户访问的是除登录页之外的页面并且
    if (req.url !='/login' && !req.session.username) {
        res.redirect('/admin/login');
    } else {
        next();
    }
}
module.exports = guard;

route下新建文件夹admin,把admin.js的功能代码抽离到这里
在这里插入图片描述

//admin.js
//渲染登录页面
admin.get('/login',require('./admin/loginPage'));
//实现登陆功能
admin.post('/login',require('./admin/login'));
//创建用户列表路由
admin.get('/user',require('./admin/userPage'));
//实现退出功能
admin.get('/logout',require('./admin/logout'));

3.3新增用户,验证器joi

  1. 为用户列表页面的新增用户按钮添加链接

    <!-- 分类标题 -->
    <div class="title">
       <a href="/admin/user-edit" class="btn btn-primary new">新增用户</a>
    </div>
    <!-- /分类标题 -->           
    
  2. 添加一个连接对应的路由,在路由处理函数中渲染新增用户模板

    //admin.js
    //创建用户编辑页面路由,渲染出编辑页面功能
    admin.get('/user-edit',require('./admin/user-edit'));
    

    在这里插入图片描述
    新建user-edit.js

    module.exports=(req,res)=>{
        res.render('admin/user-edit');
    }
    
  3. 为新增用户表单指定请求地址、请求方式、为表单项添加name属性

     <form class="form-container" action="/admin/user-edit" method="post">
                    <div class="form-group">
                        <label>用户名</label>
                        <input name="username" type="text" class="form-control" placeholder="请输入用户名">
                    </div>
                    <div class="form-group">
                        <label>邮箱</label>
                        <input name="email" type="email" class="form-control" placeholder="请输入邮箱地址">
                    </div>
                    <div class="form-group">
                        <label>密码</label>
                        <input name="password" type="password" class="form-control" placeholder="请输入密码">
                    </div>
                    <div class="form-group">
                        <label>角色</label>
                        <select class="form-control" name="role">
                            <option value="normal">普通用户</option>
                            <option value="admin">超级管理员</option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label>状态</label>
                        <select name="state" class="form-control">
                            <option value="0">启用</option>
                            <option value="1">禁用</option>
                        </select>
                    </div>
                    <div class="buttons">
                        <input type="submit" class="btn btn-primary">
                    </div>
                </form>
    
  4. 增加实现添加用户的功能路由
    admin.js

    //创建实现用户添加功能路由,对页面进行验证功能
    ...
    admin.post('/user-edit',require('./admin/user-edit-fn'));
    ...
    

    新建user-edit-fn.js
    在这里插入图片描述

  5. 接收到客户端传递过来的请求参数:req.body

  6. 对请求参数的格式进行验证:Joi.validate(req.body,schema);
    user-edit-fn.js

    	//引入joi模块
    	const Joi = require('joi');
    	module.exports= async (req,res)=>{
    	    //定义对象的验证规则
    	    const schema = {
    	        username:Joi.string().min(2).max(12).required().error(new Error('用户名不符合')),
    	        password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/).required().error(new Error('密码格式不符合')),
    	        email: Joi.string().email().required().error(new Error('邮箱格式不符合')),
    	        role:Joi.string().valid('normal','admin').required().error(new Error('角色值非法')),
    	        state:Joi.number().valid(0,1).required().error(new Error('状态值非法'))
    	
    	    };
    	    try {
    	        await Joi.validate(req.body,schema);
    	    } catch (e) {
    	        res.redirect(`/admin/user-edit?message=${e.message}`)
    	    }
    	    
    	    res.send(req.body);
    	}
    
  7. 验证当前要注册的邮箱地址是否已经注册过

    let user = await User.findOne({email:req.body.email});
        if (user) {
            //1重定向响应后还做了res.end()这个事情;2重定向后代码就应该return终止了,不然后面继续res.end()会冲突报错
           return res.redirect(`/admin/user-edit?message=邮箱地址被占用`)
        }
    res.send(user);    
    
  8. 对密码进行加密处理

  9. 将用户信息添加到数据库中

  10. 重定向页面到用户列表页面
    在这里插入图片描述

优化代码:
1.验证用户处理抽离到user.js,
2.格式验证错误和邮箱账号重复错误处理方式抽离到app.js

//app.js

//错误处理中间件
app.use((err,req,res,next)=>{
    //将字符串类型转换为对象啊
    const result = JSON.parse(err);
    res.redirect(`${result.path}?message=${result.message}`);
})

//user.js

//....
//验证用户信息
const Joi = require('joi');
const validateUser=(user)=>{
    //定义对象的验证规则
    const schema = {
        username:Joi.string().min(2).max(12).required().error(new Error('用户名不符合')),
        password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/).required().error(new Error('密码格式不符合')),
        email: Joi.string().email().required().error(new Error('邮箱格式不符合')),
        role:Joi.string().valid('normal','admin').required().error(new Error('角色值非法')),
        state:Joi.number().valid(0,1).required().error(new Error('状态值非法'))

    };
    //实施验证
    return  Joi.validate(user,schema);
}

//user-edit-fn.js

const { User,validateUser } = require('../../model/user');
//引入加密模块
const bcrypt = require('bcrypt');
module.exports= async (req,res,next)=>{
    
    try {
        await validateUser(req.body);
    } catch (e) {
      //return  res.redirect(`/admin/user-edit?message=${e.message}`);
      //next()只接受字符串;加return停止向下运行;next调用后触发错误处理中间件
      return next(JSON.stringify({path:'/admin/user-edit',message:e.message}));
    }
    let user = await User.findOne({email:req.body.email});
    if (user) {
        //1重定向响应后还做了res.end()这个事情;2重定向后代码就应该return终止了,不然后面继续res.end()会冲突报错
       //return res.redirect(`/admin/user-edit?message=邮箱地址被占用`);
       return next(JSON.stringify({path:'/admin/user-edit',message:'邮箱地址被占用'}))
    }
    //对密码加密处理
    const salt = await bcrypt.genSalt(10);
    const password = await bcrypt.hash(req.body.password,salt);
    //替换
    req.body.password = password;
    //添加数据库
    await User.create(req.body);
    res.redirect('/admin/user');
}

3.4展示用户信息

当访问用户列表页面时,现在用户列表对应路由处理函数中,将所有用户信息从数据库中查询出来,完成后用res.render()方法渲染用户模板,并且将查询出的用户数据传递到模板中展示出来。

  • route
    • admin
      • userPage.js
//userPage.js
	//1.引入用户集合函数
const {User} = require('../../model/user');
module.exports=async(req,res)=>{
	//将全部用户信息从数据库查询出来
   let users = await User.find({});
   //渲染模板
    res.render('admin/user',{
        users:users
    });
}

模板展示

  • views
    • admin
      • user.art
 <!-- 内容列表 --> 
<table>
 <tbody>
   {{each users}}
    <tr>
       <td>{{@$value._id}}</td>
       <td>{{$value.username}}</td>
       <td>{{$value.email}}</td>
       <td>{{$value.role=='admin'?'超级管理员':'普通用户'}}</td>
       <td>{{$value.state==0?'启用':'禁用'}}</td>
       <td>
          <a href="user-edit.html" class="glyphicon glyphicon-edit"></a>
             <i class="glyphicon glyphicon-remove" data-toggle="modal" data-target=".confirm-modal"></i>
       </td>
    </tr>
  {{/each}}   
</tbody>
</table>
<!-- /内容列表 -->

数据分页
分页功能核心要素:

  1. 当前页,用户通过点击上一页或者下一页或者页码产生,客户端通过get参数方式传递到服务器端
  2. 总页数,根据总页数判断当前页是否为最后一页,根据判断结果做响应操作
  3. 总页数:Math.ceil(总数据条数 / 每页显示数据条数)
  4. 数据库集合方法:limit(2) 限制查询数量 传入每页显示的数据数量
  5. 数据库集合方法:skip(1) 跳过多少条数据 传入显示数据的开始位置
  6. 数据开始查询位置=(当前页-1)* 每页显示的数据条数

userPage.js

const {User} = require('../../model/user');
module.exports=async(req,res)=>{
    //接受客户端传递过来的当前页参数
    let page = req.query.page||1;
    //每一页显示的数据条数
    let pagesize =3;
    //查询用户数据的总数的集合方法
    let count =await User.countDocuments({});
    //总页数
    let total  = Math.ceil(count/pagesize);
    //页码对应的数据查询开始位置
    let start = (page-1)*pagesize;
   
	//将用户信息从数据库查询出来
   	let users = await User.find({}).limit(pagesize).skip(start);   
    res.render('admin/user',{
        users:users,
        page:page,
        total:total
    });
}

user.art

 <!-- 分页 -->
<ul class="pagination">
    <li style="display:<%= page-1<1?'none':'inline'%>">
        <a href="/admin/user?page=<%=page-1 %>">
		<span>&laquo;</span>
		</a>
    </li>
    <% for(var i =1;i<=total;i++){%>
    	 <li><a href="/admin/user?page=<%=i %>">{{i}}</a></li>
                <% } %>
         <li style="display:<%= page-0+1>total?'none':'inline'%>">
               <a href="/admin/user?page=<%=page-0+1 %>">//减0是为了隐式转换
			        <span>&raquo;</span>
			   </a>
         </li>
</ul>
<!-- /分页 -->

3.5编辑用户信息

对列表用户进行编辑,页面是跟添加用户一样,那怎么区分呢?
1.如果是添加操作,直接跳转;跳转地址:http://localhost/admin/user-edit
2.如果是修改操作,跳转时候将当前用户的id通过get方式传递,这样在跳转到用户编辑页面的时候,可以根据get参数中是否有id参数来区分当前是添加还是编辑。修改地址:http://localhost/admin/user-edit?id=5ffec57c28
user.art
一.用户列表页面user.art中,编辑按钮的请求地址后加上id,跳转:user-edit.art

  • views
    • admin
      • user.art
 <!-- 内容列表 --> 
<table>
 <tbody>
   {{each users}}
    <tr>
       
       <td>
         <a href="/admin/user-edit?id={{@$value._id}}" class="glyphicon glyphicon-edit"></a>
         <i class="glyphicon glyphicon-remove" data-toggle="modal" data-target=".confirm-modal"></i>
       </td>
    </tr>
  {{/each}}   
</tbody>
</table>
<!-- /内容列表 -->
<!-- 分页 -->
<!-- /分页 -->

二.根据是否有带id判断进入的是不是用户编辑页面:user-edit.art

  • route
    • admin
      • user-edit.js
//引入User
const {User} = require('../../model/user');
module.exports=async (req,res)=>{
    //获取到地址栏中的id参数
    const {message,id}=req.query;
    //如果当前传递了id参数就是修改操作
    if (id) {
     //修改操作
        let user = await  User.findOne({_id:id});   
        //渲染用户编辑页面(修改)
        res.render('admin/user-edit',{
            message:message,
            user:user,
            link:'/admin/user-add',
            button:'修改'
        });
    } else {
    //增加操作
        res.render('admin/user-edit',{
            message:message,
            link:'/admin/user-edit',
            button:'添加'
        });
    }
   
}

三.给模板添加参数,注意因为是增加和修改都是这个页面,而增加操作未带user参数,所以先判断是否存在,即user&&user.username

  • views
    • admin
      • user-edit.art
<form class="form-container" action="{{link}}" method="post">
   <div class="form-group">
      <label>用户名</label>
      <input name="username" type="text" class="form-control" placeholder="请输入用户名" value="{{user&&user.username}}">
   </div>
    <div class="form-group">
       <label>邮箱</label>
          <input name="email" type="email" class="form-control" placeholder="请输入邮箱地址"  value="{{user&&user.email}}">
    </div>
    <div class="form-group">
       <label>密码</label>
          <input name="password" type="password" class="form-control" placeholder="请输入密码">
    </div>
    <div class="form-group">
         <label>角色</label>
         <select class="form-control" name="role">
          <option value="normal" {{user&&user.role=='normal'?'selected':''}}>普通用户</option>
           <option value="admin"  {{user&&user.role=='admin'?'selected':''}}>超级管理员</option>
          </select>
    </div>
    <div class="form-group">
       <label>状态</label>
           <select name="state" class="form-control">
               <option value="0" {{user&&user.state=='0'?'selected':''}}>启用</option>
                <option value="1" {{user&&user.state=='1'?'selected':''}}>禁用</option>
           </select>
    </div>
    <div class="buttons">
         <input type="submit" class="btn btn-primary" value='{{button}}'>
    </div>
</form>

user-edit.art

3.6用户信息修改

  1. 将要修改的用户ID传递到服务器端

    //user-edit.js
     if (id) {
         //修改操作
            let user = await  User.findOne({_id:id});   
            //渲染用户编辑页面(修改)
            res.render('admin/user-edit',{
                message:message,
                user:user,
                link:'/admin/user-modify?id='+id,
                button:'修改'
            });
        }
        }
    
  2. 建立用户信息修改功能对应的路由

    //admin.js
    admin.post('/user-modify',require('./admin/user-modify'));
    
  3. 接收客户端表单传递过来的请求参数

    //user-modify.js
    const body =req.body;
    //即将要修改的id,id是get方式传过来的
    const id =req.query.id;
    
  4. 根据id查询用户信息,并将客户端传递过来的密码和数据库中的密码进行比对

    //user-modify.js
    const {User} = require('../../model/user');
    const bcrypt = require('bcrypt');
    module.exports=async(req,res,next)=>{
    let user = await User.findOne({_id:id});
    	const isValid = await bcrypt.compare(req.body.password,user.password);
    	if(isValid){
            res.send('密码比对成功')
        }else{
            let obj={path:'/admin/user-edit',message:'密码比对失败了',id:'id'}
            next(JSON.stringify(obj));
    	}
    }
    
  5. 如果比对失败,对客户端做出响应,触发错误处理中间件

    //app.js
    //改写错误处理中间件
    app.use((err,req,res,next)=>{
        //将字符串类型转换为对象啊
        const result = JSON.parse(err);
       //result{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}?message=${result.message}`);
        res.redirect(`${result.path}?${params.join('&')}`);
    })
    
  6. 如果密码对比成功,将用户信息更新到数据库中

    if(isValid){
            //res.send('密码比对成功');
            //将用户信息更新到数据库,密码不能在这里更新
            await User.updateOne({_id:id},{
                username:username,
                email:email,
                role:role,
                state:state
            });
            //重定向到用户列表页
            res.redirect('/admin/user');
        }else{
        //注意id值不要加冒号
        let obj={path:'/admin/user-edit',message:'密码比对失败了',id:id}
        next(JSON.stringify(obj));
    }
    

3.7用户信息删除

在这里插入图片描述

  1. 在确认删除框中添加隐藏域用以存储要删除用户的ID值

     <form class="modal-content" action="/admin/delete" method="get">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
                    <h4 class="modal-title">请确认</h4>
                </div>
                <div class="modal-body">
                    <p>您确定要删除这个用户吗?</p>
                    <input type="hidden" name="id" id="deleteId"><!-- 隐藏域 -->
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                    <input type="submit" class="btn btn-primary">
                </div>
            </form>
    
  2. 为操作选项下的删除按钮添自定义属性用以存储要删除用户的ID值

    <i class=" delete" data-toggle="modal" data-target=".confirm-modal" data-id="{{@$value._id}}"></i>
    
  3. 为删除按钮添加点击事件,在点击事件处理函数中获取自定义属性中存储的ID值并将ID值存储在表单的隐藏域中

    {{block 'script'}}
        <script>
            $(".delete").on('click',function(){
             let id = $(this).attr('data-id');
             //将要删除的用户id存储在隐藏域
             $('#deleteId').val(id); 
            })
        </script>
     {{/block}}  
    
  4. 为删除表单添加提交地址以及提交方式

  5. 在服务器端建立删除功能路由

    //admin.js
    admin.get('/delete',require('./admin/user-delete'));
    
  6. 接收客户端传递过来的id参数

  7. 根据id删除用户

    //user-delete.js
    const {User} =require("../../model/user");
    module.exports= async(req,res)=>{
     	await User.findByIdAndDelete({_id:req.query.id});
        res.redirect('/admin/user');
    }
    

3.8文章管理

添加访问路由:文章列表页面和文章编辑页面

//admin.js
//文章列表路由
admin.get('/article',require('./admin/article'));
//文章编辑路由
admin.get('/article-edit',require('./admin/article-edit'));
  • route
    • admin
      • article.js
      • article-edit.js
      • userPage.js
//article.js
module.exports= (req,res)=>{
   res.render('admin/article.art');
 }
//article-edit.js
module.exports= (req,res)=>{
   res.render('admin/article-edit.art');
 }

1、实现点击切换文章管理页面和用户管理页面
在这里插入图片描述

用户列表userPage.js添加标识
用户编辑user-edit.js添加标识

//标识当前访问的是用户管理页面
    req.app.locals.currentLink="user";

用户列表userPage.js添加标识
用户编辑user-edit.js添加标识

//标识当前访问的是文章管理页面
    req.app.locals.currentLink="article";

三元表达式判断当前选中状态

//aside.art
<li>
   <a class="item {{currentLink=='user'?'active':''}}" href="/admin/user">
	<span class="glyphicon glyphicon-user"></span>
		用户管理
	</a>
/li>
<li>
    <a class="item {{currentLink=='article'?'active':''}}" href="/admin/article">
	<span class="glyphicon glyphicon-th-list"></span>
	文章管理
	</a>
</li>

1、创建文章集合

  • model
    • article.js

语法:

  • require:[true,‘错误提示’]
  • 作者就是User集合的用户,所以要将文章集合的作者和User进行关联:ref:“User”
  • ObjectId是数据库独有的数据类型
//引入mongoose模块
const mongoose = require('mongoose');
//创建文章集合规则
const articleSchema = new mongoose.Schema({
    title:{
        type:String,
        maxlength:20,
        minlength:4,
        require:[true,'请填写文章标题']
    },
    author:{
        type:mongoose.Schema.Types.ObjectId,
        ref:'User',
        required:[true,'请传递作者姓名']
    },
    publishDate:{
        type:Date,
        default:Date.now
    },
    cover:{
        type:String,
        default:null
    },
    content:{
        type:String
    }
})
//根据规则创建集合
const Article = mongoose.model('Article',articleSchema);
//将集合规则作为模块成员进行导出
module.exports={
    Article
}

2、点击发布按钮,跳转至文章编辑页面article-edit.art

//article.art
<a href="/admin/article-edit" class="btn btn-primary new">发布新文章</a>

3、为表单添加请求方式和地址,由于往数据库添加数据,所以为post。添加的name属性最好与数据库保持一致

// enctype指定表单数据的编码类型,默认用
            //application/x-wwww-form-urlencoded的类型:name=zhangsan&age=20
            //multipart/form-data将表单数据编码成二进制类型,用于涉及文件上传
            <form class="form-container" action="/admin/article-add" method="post" enctype="multipart/form-data">
                <div class="form-group">
                    <label>标题</label>
                    <input type="text" class="form-control" placeholder="请输入文章标题" name="title">
                </div>
                <div class="form-group">
                    <label>作者</label>
                    <input name="author" type="text" class="form-control" readonly>
                </div>
                <div class="form-group">
                    <label>发布时间</label>
                    <input name="publishDate" type="date" class="form-control">
                </div>
                
                <div class="form-group">
                   <label for="exampleInputFile">文章封面</label>
                   <input type="file" name="cover">
                   <div class="thumbnail-waper">
                       <img class="img-thumbnail" src="">
                   </div>
                </div>
                <div class="form-group">
                    <label>内容</label>
                    <textarea name="content" class="form-control" id="editor"></textarea>
                </div>
                <div class="buttons">
                    <input type="submit" class="btn btn-primary">
                </div>
            </form>

3、在服务器端建立点击提交按钮后,文章添加功能的路由

  • route
    • admin
      • article-add.js
    • admin.js
//admin.js
admin.post('/article-add',require('./admin/article-add'));

bodyPaser不能处理客户端发送的二级制数据

formidable替代:解析表单,支持get请求参数,post请求参数、文件上传。

//article-add.js
const path =require('path');
// 引入formidable模块
const formidable = require('formidable');
module.exports= (req,res)=>{
    // 创建表单解析对象
    const form = new formidable.IncomingForm();
    // 设置文件上传路径
    form.uploadDir = path.join(__dirname,'../','../','/public','uploads')
    // 是否保留表单上传文件的扩展名
    form.keepExtensions = true;
      // 对表单进行解析
    form.parse(req, (err, fields, files) => {
        //err错误对象,如果解析成功则为null
        // fields对象 存储普通表单数据
        // files对象 存储上传文件的信息
        res.send(files);
    });
 }

作者author就是集合User的_id值,之前login.js里登录成功后已经把用户信息挂载到公用属性了 :req.app.locals.userInfo123=user;

//article-edit.art
<label>作者</label>
<input name="author" type="text" class="form-control" readonly value="{{@userInfo123._id}}">

4、封面图片显示在预览中

//article-edit.art
<label for="exampleInputFile">文章封面</label>
<input type="file" name="cover" id="file" >
<div class="thumbnail-waper">
    <img class="img-thumbnail" src="" id="preview">
</div>

<script>
var file =document.querySelector("#file");
        var preview = document.querySelector("#preview");
        //onchange触发说明选择完文件
        file.onchange=function(){
            //1创建文件读取对象
            var reader = new FileReader();
            //2读取文件
            reader.readAsDataURL(this.files[0]);
            //3readAsDataURL是异步方法不能通过返回值的方式来获取结果,所以监听文件读取对象的onload事件
            reader.onload=function(){
                //reader.result文件读取的结果
                preview.src= reader.result
            }
        }
</script>

5、将客户端传递到服务器的文件传递到数据库

//article-add.js
const path =require('path');
const formidable = require('formidable');
//引入文章规则
const {Article}=require('../../model/article.js');
module.exports= (req,res)=>{
// 创建表单解析对象
    const form = new formidable.IncomingForm();
    // 设置文件上传路径
    form.uploadDir = path.join(__dirname,'../','../','/public','uploads')
    // 是否保留表单上传文件的扩展名
    form.keepExtensions = true;
      // 对表单进行解析,
    form.parse(req,async (err, fields, files) => {
        await Article.create({
            title:fields.title,    
            author:fields.author,
            publishDate:fields.publishDate,
            //files对象 存储上传文件的信息中路径path保存的是硬盘绝对路径,需要以public为分割字符去截取uploads后的路径
            cover:files.cover.path.split('public')[1],
            content:fields.content
        });
        res.redirect('/admin/article')
    });
 }

6、实现文章列表页面的数据展示功能

  • route
    • admin
      • article.js

跟用户列表的展示功能步骤是一样的,从数据库中查询数据再渲染模板

1.多集合联合查询:populate(‘要查询的字段名称’);
2.告诉mongoose返回普通对象,不返回mongoose文档对象,防止报错要加上:.lean()

//将文章结合的构造函数导入到当前文件中
const {Article}=require('../../model/article');
module.exports= async(req,res)=>{
    //标识当前访问的是文章管理页面
    req.app.locals.currentLink="article";
    //查询所有文章数据
    let articles = await Article.find().populate('author').lean(); 
   // res.send(articles);
   res.render('admin/article.art',{
    articles:articles
   });
 }
//article.art
 {{each articles}}
      <tr>
          <td>{{@$value._id}}</td>
          <td>{{$value.title}}</td>
          <td>{{$value.publishDate}}</td>
          <td>{{$value.author.username}}</td>
          <td>
              <a href="article-edit.html" class="glyphicon glyphicon-edit"></a>
              <i class="glyphicon glyphicon-remove" data-toggle="modal" data-target=".confirm-modal"></i>
          </td>
    </tr>
{{/each }}                  

日期格式的处理npm i dateformat
dateformat是处理js的方法,要想在模板文件art中用这个方法要先配置

//app.js
//导入art-tempate模板引擎
const template = require('art-template');
//导入dateformat第三方模块格式日期
const dateFormat = require('dateformat');
//向模板内部导入dateFormate变量
template.defaults.imports.dateFormat = dateFormat;
<td>{{dateFormat($value.publishDate,'yyyy-mm-dd')}}</td>

数据分页 npm i mongoose-sex-page

const pagination = require('mongoose-sex-page');
pagination(集合构造函数).page(1) .size(20) .display(8) .exec();

7、实现分页展示
article.js

//将文章结合的构造函数导入到当前文件中
const {Article}=require('../../model/article');
//导入mongoose-sex-page模块
const pagination = require('mongoose-sex-page');

module.exports= async(req,res)=>{
    //接受客户端传递过来的页码
    const page = req.query.page;
    //标识当前访问的是文章管理页面
    req.app.locals.currentLink="article";
  
    //page 指定当前页
    //size 指定每页显示的数据条数
    //display 指定客户端要显示的页码数量
    //exec 向数据库中发送查询请求
    //返回的是一个对象,数据在records里

    // 查询所有文章数据
    let articles = await pagination(Article).find().page(page).size(2).display(3).populate('author').exec(); 
   //res.send(articles);

   /*articles{
      "page": 1,
      "size": 2,
      "total": 4,
      "records": [{},{}],
      "pages": 2,
      "display": [1,2]
      }
    */
    //使用了第三方模块mongoose-sex-page控制查询数据,则不能使用lean(),换下面一种方式
    articles = JSON.stringify(articles);
    articles = JSON.parse(articles); 
    res.render('admin/article.art',{
      articles:articles
    });
 } 

article.art

 <!-- 分页 -->
            <ul class="pagination">
                {{if articles.page>1}}
                    <li>
                        <a href="/admin/article?page={{articles.page-1}}">
                        <span>&laquo;</span>
                    </a>
                    </li>
                {{/if}}
                                
                {{each articles.display}}
                <li><a href="/admin/article?page={{$value}}">{{$value}}</a></li>
                {{/each}}
                
                {{if articles.page<articles.pages}}
                <li>
                    <a href="/admin/article?page={{articles.page-0+1}}">
			        <span>&raquo;</span>
			      </a>
                </li>
                {{/if}}
            </ul>
<!-- /分页 -->

3.9数据库添加管理账号

给数据库添加超级管理员账号,只有登录账号才能操作数据库;普通账号只能操作单独的数据库,比如当前这个blogyqs数据库

  1. 以系统管理员的方式运行powershell

  2. 连接数据库: mongo

  3. 查看数据库: show dbs

  4. 切换到admin数据库 use admin

  5. 创建超级管理员账户(键都是固定的) db.createUser({user:'root',pwd:'root',roles:['root']})

  6. 切换到blog数据 use blogyqs

  7. 创建普通账号 db.createUser({user:'yqs4739',pwd:'yqs4739',roles:['readWrite']})

  8. 卸载原有的mongodb服务:

    • net stop mongodb
    • mongod --remove
  9. 创建mongodb服务
    mongod --logpath="C:\Program Files\MongoDB\Server\4.1\log\mongod.log" --dbpath="C:\Program Files\MongoDB\Server\4.1\data" --install –-auth

  10. 在项目中使用账号连接数据库

    //app.js
    mongoose.connect('mongodb://yqs4739:yqs4739@localhost:27017/blogyqs',
    {useNewUrlParser:true,useUnifiedTopology: true})
    
    

当想再多创建一个数据库账号时,必须先登录超级管理员账号


在开发环境将客户端向服务器端发送的请求信息打印到控制台
安装第三方模块

4开发环境与生产环境

如何区分开发环境与生产环境

1.通过电脑操作系统中的系统环境变量区分
电脑
2.配置后必须重启代码编辑器,再安装morgan

npm install morgan

3.获取当前系统下的环境变量方法:

console.log(process.env)

4.把客户端的请求信息打印到控制台:

//导入morgan第三方模块
const morgan = require('morgan');
if(process.env.NODE_ENV == 'development'){
    //当前是开发环境
    console.log('当前是开发环境');
    //在开发环境中将客户端向服务器端发送的请求信息打印到控制台
    //app.use(morgan('dev'))
}else{
    console.log('当前是生产环境');
};

5.第三方模块config实现上面的需求

作用:允许开发人员将不同运行环境下的应用配置信息如数据库连接地址、用户名、密码等,抽离到单独的文件中,模块内部自动判断当前应用的运行环境,并读取对应的配置信息,极大提供应用配置信息的维护成本,避免了当运行环境重复的多次切换时,手动到项目代码中修改配置信息。
使用步骤:

  1. 使用npm install config命令下载模块

  2. 在项目的根目录下新建config文件夹

  3. 在config文件夹下面新建default.json、development.json、production.json文件

    //development.json
    {
        "db":{
            "user":"yqs4739",
            "pwd":"yqs4739",
            "host":"localhost",
            "port":"27017",
            "name":"blogyqs"
        }
    }
    
  4. 在项目中通过require方法,将模块进行导入

  5. 使用模块内部提供的get方法获取配置信息

//connect.js
const mongoose = require('mongoose');
//导入config模块
const config=require('config');
mongoose.connect(`mongodb://${config.get('db.user')}:${config.get('db.pwd')}@${config.get('db.host')}:${config.get('db.port')}/${config.get('db.name')}`,{useNewUrlParser:true,useUnifiedTopology: true, useCreateIndex: true})
    .then(()=>console.log('数据库连接成功'))
    .catch(()=>console.log('数据库连接失败'))

6.将敏感信息存在环境变量中
1.在config文件夹中建立固定文件custom-environment-variables.json
2.先去电脑系统设置环境变量,再文件里填写系统环境变量的名字

//custom-environment-variables.json
{
    "db":{    
        "pwd":"yqs_PASSWORD"
    }
}
//development.json
{
    "db":{
        "user":"yqs4739",     
        "host":"localhost",
        "port":"27017",
        "name":"blogyqs"
    }
}

3.项目运行时config模块会查找系统环境变量,并读取其值作为当前配置项的值

5博客前台展示页面

5.1.1增加请求路由

在这里插入图片描述

//home.js
const express = require('express');
const home  = express.Router();
//博客前台首页的展示页面
home.get('/',require('./home/index'));
//博客前台文章的展示页面
home.get('/article',require('./home/article'))
module.exports = home;
module.exports = (req,res)=>{
    res.render('home/article.art');
}
module.exports = (req,res)=>{
	res.render('home/default.art');
}

5.1.2模板处理

外链资源相对路径不安全,需要修改成绝对路径
在这里插入图片描述

<link rel="stylesheet" href="/home/css/base.css">
<link rel="stylesheet" href="/home/css/article.css">

5.1.3抽离模板

1.抽离骨架layout.art

在这里插入图片描述
骨架模板layout.art
1.抽离HTML整体框架,不同的地方用{{block}}挖坑做标记

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>首页</title>
	<link rel="stylesheet" href="/home/css/base.css">
	{{block 'link'}}{{/block}}
</head>
<body>
	{{block 'main'}}{{/block}}
</body>
</html>

default.art示例:

  1. extend继承骨架模板
  2. {{block}}填坑
{{extend './common/layout.art'}}
{{block 'link'}}
	<link rel="stylesheet" href="/home/css/index.css">
{{/block}}
{{block 'main'}}
	<!-- 头部框架开始 -->
	<div class="header">这里是头部</div>
	<!-- 头部框架结束 -->
	<!-- 文章列表开始 -->
		//....
	<!-- 文章列表结束 -->
{{/block}}

2.抽离公共部分

公共模板header.art
1.抽离共有的头部代码

<div class="header">这里是头部</div>

default.art示例:

  1. include把头部再包含进来
```javascript
{{extend './common/layout.art'}}
{{block 'link'}}
	<link rel="stylesheet" href="/home/css/index.css">
{{/block}}
{{block 'main'}}
	<!-- 头部框架开始 -->
	{{include './common/header.art'}}
	<!-- 头部框架结束 -->
	<!-- 文章列表开始 -->
		//....
	<!-- 文章列表结束 -->
{{/block}}

5.1.4数据库查询文章并渲染

由于对数据库的操作属于异步操作,我们要不用异步函数接收异步方法的返回值,要不用promise的方式,最好的方法是用异步函数async await

//index.js
const {Article} = require('../../model/article');
//导入分页模块
const pagination = require('mongoose-sex-page');
module.exports = async(req,res)=>{
    //从数据库中查询数据
    let result = await pagination(Article).page(1).size(4).display(5).find().populate('author').exec();
    //res.send(result);
    result = JSON.stringify(result);
    result = JSON.parse(result); 
    //传递数据并渲染模板
    res.render('home/default.art',{
        result:result
    });
}
  • 日期格式化
  • 文章内容去掉html标签,超过150字符加标点符号代表隐藏
  • 分页跳转
	<!-- 文章列表开始 -->
	<ul class="list w1100">
	{{each result.records}}
		<li class="{{$index%2==0?'fl':'fr'}}">
			<a href="article.html" class="thumbnail">
				<img src="{{$value.cover}}">
			</a>
			<div class="content">
				<a class="article-title" href="article.html">{{$value.title}}</a>
				<div class="article-info">
					<span class="author">{{$value.author.username}}</span>
					<span>{{dateFormat($value.publishDate,'yyyy-mm-dd')}}</span>
				</div>
				<div class="brief">
					{{@$value.content.replace(/<[^>]+>/g,'').substr(0,150)+'...'}}
				</div>
			</div>
		</li>
	{{/each}}
	</ul>
	<!-- 文章列表结束 -->
	<!-- 分页开始 -->
	<div class="page w1100">
	{{if result.page>1}}
		<a href="/home/?page={{result.page-1}}">上一页</a>
	{{/if}}	
		{{each result.display}}
		<a href="/home/?page={{$value}}" class="{{$value==result.page?'active':''}}">{{$value}}</a>
		{{/each}}
	{{if result.page<result.pages}}	
		<a href="/home/?page={{result.page-0+1}}">下一页</a>
	{{/if}}	
	</div>
	<!-- 分页结束 -->

点击文章进入详情页
通过get方式将文章id传递给服务端

如果我们想传递HTML代码,那么模板引擎并不会将HTML代码中的特殊符号进行解码,还是以编码的形式显示。例如:< h1> 在模板中最后显示为 &#6 0; h1 &#6 2;想让模板中原文显示的标准语法:{{@ 数据 }}

//default.art
<a class="article-title" href="/home/article?id={{@$value._id}}">id记得要原文输出</a>
//article.js
const {Article} = require('../../model/article');
module.exports = async (req,res)=>{
    const id = req.query.id;
    let article = await Article.findOne({_id:id}).populate('author');
    // res.send(article);
    article = JSON.stringify(article);
    article = JSON.parse(article); 
    res.render('home/article.art',{
        article:article
    });
}

5.1.5文章评论

  1. 创建评论集合
    const mongoose = require('mongoose');
    //创建评论集合规则
    const commentSchema= new mongoose.Schema({
        //aid就是文章的id
        aid:{
            type:mongoose.Schema.Types.ObjectId,
            //关联写上文章集合的名字
            ref:'Article'
        },
        //用户id
        uid:{
            type:mongoose.Schema.Types.ObjectId,
            ref:'User'
        },
        //评论时间
        time:{
            type:Date
        },
        //评论内容
        content:{
            type:String
    
        }
    });
    //创建评论集合
    const Comment = mongoose.model('Comment',commentSchema);
    module.exports={
        Comment
    }
    
    接下来根据不同用户,跳转不同的路由
    //login.js
    let user = await User.findOne({email});
       if(user){
             let isValid = await bcrypt.compare(password,user.password);
             if(isValid){
               //登录成功
                 req.session.username= user.username;
              //同理将用户角色存储在session对象中
                 req.session.role= user.role;
                 req.app.locals.userInfo123=user;
                 //对用户角色进行判断
                 if(user.role=="admin"){
                     //重定向用户列表,用express自带的方法redirect
                   res.redirect('/admin/user');
                 }else{
                    //重定向博客列表
                  res.redirect('/home/');
                 }
                
             }else{
                 res.status(400).render('admin/error',{message:'邮件地址或者密码错误'});  
             }
       }else{
         res.status(400).render('admin/error',{message:'邮件地址或者密码错误'});  
       }	
    
    登陆compass,挑选几个不同的账号来测试跳转情况
    在这里插入图片描述
    通过判断用户角色来重定向,达到阻止普通用户跳转到管理页面目的
//loginGuard.js
const guard = (req,res,next)=>{
    //login.js已经提前设置了session,这里可以直接拿到里面的用户名username和角色role
    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;
  1. 判断用户是否登录,如果用户登录,再允许用户提交评论表单

在用户模板文件中如何判断用户是否登录了呢?有个对象叫req.app.locals在模板中是可以访问到的,可以通过判断这个对象下的自定义属性是否存在,代码就在上面第一步的login.js里,里面有这样一句:req.app.locals.userInfo123=user;
下面是在模板里做判断

//article.art
<h4>评论</h4>
{{if userInfo123}}
	<form class="comment-form">
	<textarea class="comment"></textarea>
	<div class="items">
		<input type="submit" value="提交">
	</div>
	</form>
{{else}}
	<h2>先进行登陆,再对文章进行评论</h2>
{{/if}}

小bug:退出账号后,评论区依然还显示 。
原因:删除了服务器端的session,并没有再去删除userInfo123

//logout.js
module.exports=(req,res)=>{
    //删除session
    req.session.destroy(function(){
        //删除cookie
        res.clearCookie('connect.sid');
        //重定向到用户登录页面
        res.redirect('/admin/login');
        //清除模板中的用户信息
        req.app.locals.userInfo123=null;
    })
}
  1. 在服务器端创建文章评论功能对应的路由

在这里插入图片描述
添加评论表单提交地址和方式,增加两个隐藏域存储文章id和用户id

//article.art
<form class="comment-form" action="/home/comment" method="post">
	<textarea class="comment" name="content"></textarea>//name跟数据库的评论对象名对应
	<input type="hidden" name="aid" value="{{@article._id}}">//article.js已经将文章数据article导入到模块当中,这里可以直接用
	<input type ="hidden" name="uid" value="{{@userInfo123._id}}">
	<div class="items">
			<input type="submit" value="提交">
	</div>
</form>
//home.js创建评论功能路由
home.post('/comment',require('./home/comment'));
  1. 在路由请求处理函数中接受客户端传递过来的评论信息
const {content,uid,aid}=req.body;
  1. 将评论信息存储在评论集合中
//导入评论集合构造函数   comment.js
const {Comment} =require('../../model/comment');
module.exports=async (req,res)=>{
    //res.send(req.body);
    // {
    //     "content": "",
    //     "aid": "600a834b0c38c715d0d1f3a7",
    //     "uid": "5ffec57c28bd41166c02db5b"
    //   }
    const {content,uid,aid}=req.body;
    await Comment.create({
        content:content,
        uid:uid,
        aid:aid,
        time:new Date()
    });
    //提交完成后将页面重定向回文章详情页面。还有地址栏带上文章id
    res.redirect("/home/article?id="+aid);
}
  1. 将页面重定向回文章详情页
  2. 在文章详情页面路由中获取文章评论信息并展示在页面中
//article.js
const {Article} = require('../../model/article');
//导入评论集合构造函数
const {Comment} = require('../../model/comment');
module.exports = async (req,res)=>{
    //接收客户端传递过来的文章id
    const id = req.query.id;
    let article = await Article.findOne({_id:id}).populate('author');
    //根据文章aid的值,来查询当前所对应的文章
    let comments =await Comment.find({aid:id}).populate('uid');
    
    article = JSON.stringify(article);
    article = JSON.parse(article); 
    comments = JSON.stringify(comments);
    comments = JSON.parse(comments); 
   
    res.render('home/article.art',{
        article,
        comments
    });
}
//article.art
{{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}}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值