多人博客项目express框架

目录

1. 项目环境搭建

1.1 项目介绍

1.2 案例初始化 

2. 项目功能实现

2.1 登录

2.2 新增用户

2.3 数据分页

2.4 用户信息修改

 2.5 用户信息删除

2.6 开发环境与生产环境

2.7 第三方模块config 

2.8 文章评论

2.9 首页文章列表

 

3. 项目包含的知识点

3.1 密码加密 bcrypt

3.2 cookie与session

 3.3 Joi 

3.4 formidable 

3.5 文件读取 FileReader

3.6 数据分页 mongoose-sex-page

3.7 mongoDB数据库添加账号 

4. 踩坑

1. 联合查询

2. mongoose 设置默认时间

3. dateformat第三方模块不支持require()方法

4. 模板语法书写错误

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. 创建网站服务器
    // 引用express框架
    const express = require('express');
    // 创建网站服务器
    const app = express();
    
    // 监听端口
    app.listen(80);
    console.log('网站服务器启动成功,请访问localhost');
    
    // 在命令行工具中使用nodemon app.js运行
  5.  构建模块化路由
    // home.js文件
    // 引入express框架
    const express = require('express');
    // 创建博客展示页面路由
    const home = express.Router();
    
    home.get('/', (req, res) => {
        res.send('欢迎来到博客首页');
    });
    
    // 将路由对象作为模块成员进行导出
    module.exports = home;
    // admin.js文件
    // 引入express框架
    const express = require('express');
    // 创建博客管理页面路由
    const admin = express.Router();
    
    admin.get('/', (req, res) => {
        res.send('欢迎来到博客管理页面');
    });
    
    // 将路由对象作为模块成员进行导出
    module.exports = admin;
    // app.js文件
    // 引入路由模块
    const home = require('./route/home');
    const admin = require('./route/admin');
    
    // 为路由匹配请求路径
    app.use('/home', home);
    app.use('/admin', admin);
  6.  构建博客管理页面模板

        在public文件夹中引入静态页面admin和home文件夹 不包含HTML文件

        在views文件夹中引入html文件,并将后缀名改为.art

// app.js文件
// 告诉express框架模板所在位置
app.set('views', path.join(__dirname, 'views'));
// 告诉express框架模板默认后缀
app.set('view engine', 'art');
// 配置 当渲染后缀为art的模板时 所使用的模板引擎
app.engine('art', require('express-art-template'));

// 开放静态资源文件
app.use(express.static(path.join(__dirname, 'public')));
// admin.js文件
admin.get('/login', (req, res) => {
    res.render('admin/login');
});

        模板中外链资源的相对路径是相对于地址栏中的请求路径的

        需将模板中外链资源的相对路径改为绝对路径 在路径前添加上/admin/

        优化模板-》将公共部分提取出来

        头部->header.art        侧边栏-> aside.art        骨架->layout.art

2. 项目功能实现

2.1 登录

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

  1. 连接数据库
    // model/connect.js 需将这一文件在app.js中引入
    // 连接数据库
    // 引入mongoose第三方模块
    const mongoose = require('mongoose');
    // 连接数据库
    mongoose.connect('mongodb://localhost/blog')
        .then(() => console.log('数据库连接成功'))
        .catch(() => console.log('数据库连接失败'));

  2. 创建用户集合
    // model/user.js文件
    // 创建用户集合
    // 引入mongoose第三方模块
    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.create({
    //     username: 'Emi',
    //     email: '123456@163.cn',
    //     password: '123456',
    //     role: 'admin',
    //     state: 0
    // }).then(() => {
    //     console.log('用户创建成功');
    // }).catch(() => {
    //     console.log('用户创建失败');
    // });
    // 将用户集合作为模块成员进行导出
    module.exports = {
        // 等价于User:User
        User
    }

  3. 初始化用户

2. 为登录表单项设置请求地址、请求方式以及表单项name属性

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

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

function serializeToJson(form) {
    var result = {};
    // [{name: 'email', value: ''}, {name: 'password', value: ''}]
    // JQuery中提供的方法
    var f = form.serializeArray();
    f.forEach(function(item) {
        result[item.name] = item.value;
    });
    return result;
}       

//为表单添加提交事件
        $('#loginForm').on('submit', function(){
            // 获取表单内容 {email: '', password: ''}
           var result = serializeToJson($(this));
            // 如果用户没有输入邮件地址
            if(result.email.trim().length == 0){
                alert('请输入邮件地址');
                // 阻止程序向下执行
                return false;
            }
            // 如果用户没有输入密码
            if(result.password.trim().length == 0){
                alert('请输入密码');
                //阻止程序向下执行
                return false;
            }
        })

5. 服务器端接收请求参数,验证用户是否填写了登录表单

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

// 实现登录功能
admin.post('/login', (req, res) => {
    // 接收请求参数
    const { email, password } = req.body;
    // 如果用户没有输入邮件地址 客户端请求地址格式错误 400
    if (email.trim().length == 0 || password.trim().length == 0) {
        return res.status(400).render('admin/error', { msg: '邮件地址或密码错误' });
    }
});

7. 根据邮箱地址查询用户信息

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

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

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

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

// 实现登录功能
admin.post('/login', async(req, res) => {
    // 接收请求参数
    const { email, password } = req.body;
    // 如果用户没有输入邮件地址 客户端请求地址格式错误 400
    if (email.trim().length == 0 || password.trim().length == 0) {
        return res.status(400).render('admin/error', { msg: '邮件地址或密码错误' });
    }
    // 根据邮箱地址查询用户信息
    // ES6中规定 属性与属性值同名 可以只写一个
    // 如果查询到了用户 user变量的值为对象类型
    // 如果没有查询到用户 user变量的值为空
    let user = await User.findOne({ email });

    if (user) {
        // 将客户端传递的密码与用户信息中的密码进行比对
        let isValid = await bcrypt.compare(password, user.password);
        if (isValid) {
            //登录成功
            req.session.username = user.username;
            //res.send('登录成功');
            // 重定向到用户列表页面
            // 将user存储到app.locals 可以在模板中拿到该值
            req.app.locals.userInfo = user;
            res.redirect('/admin/user');
        } else {
            res.status(400).render('admin/error', { msg: '邮件地址或密码错误' });
        }

    } else {
        // 没有查询到用户
        res.status(400).render('admin/error', { msg: '邮件地址或密码错误' });
    }
});

12. 保存登录状态 实现登录拦截 即未登录不允许访问其他页面 登陆后服务器存储登录信息

const guard = (req, res, next) => {
    // 判断用户访问的是否是登录页面
    // 判断用户的登录状态
    /// 如果是登录的 将请求放行
    // 如果不是登陆的 将请求重定向至登陆页面
    if (req.url != '/login' && !req.session.username) {
        res.redirect('/admin/login');
    } else {
        // 用户是登录状态 将请求放行
        next();
    }
}

module.exports = guard;

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

13. 密码加密处理

2.2 新增用户

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

2. 添加一个连接对应的路由,在路由处理函数中渲染新增用户模板

3 .为新增用户表单指定请求地址、请求方式、为表单项添加name属性

4. 增加实现添加用户的功能路由

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

6. 对请求参数的格式进行验证

7. 验证当前要注册的邮箱地址是否已经注册过

8. 对密码进行加密处理

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

10. 重定向页面到用户列表页面

module.exports = async(req, res) => {
    // 定义对象的验证规则
    const schema = Joi.object({
        username: Joi.string().min(2).max(20).required().error(new Error('用户名不符合规则')),
        email: Joi.string().email().required().error(new Error('邮箱格式不符合要求')),
        password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/).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 schema.validateAsync(req.body);
    } catch (error) {
        // 验证没有通过
        // 重定向至用户添加页面
        // ES6模板字符串方式
        return res.redirect(`/admin/user-edit?message=${error.message}`);
    }

    // 根据邮箱地址查询用户是否存在
    let user = await User.findOne({ email: req.body.email });
    // 用户存在 则邮箱地址已被占用
    if (user) {
        // 重定向至用户添加页面
        return res.redirect('/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');
};

2.3 数据分页

当数据库中的数据非常多时,数据需要分批次显示,这时就需要用到数据分页功能。

分页功能核心要素:

1. 当前页,用户通过点击上一页或者下一页或者页码产生,客户端通过get参数方式传递到服务器端

2. 总页数,根据总页数判断当前页是否为最后一页,根据判断结果做相应操作 

总页数:Math.ceil(总数据条数 / 每页显示数据条数) 

limit(2) // limit 限制查询数量  传入每页显示的数据数量
skip(1) // skip 跳过多少条数据  传入显示数据的开始位置

数据开始查询位置=(当前页-1)* 每页显示的数据条数 

    // 接收客户端传递的当前页参数
    let page = req.query.page || 1;
    // 每一页显示的数据条数
    let pagesize = 10;
    // 查询用户数据总数
    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
    });
            <!-- 分页 -->
            <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}}">
			        <span>&raquo;</span>
			      </a>
                </li>
            </ul>
            <!-- /分页 -->

2.4 用户信息修改

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

2. 建立用户信息修改功能对应的路由

3. 接收客户端表单传递过来的请求参数

4. 根据id查询用户信息,并将客户端传递过来的密码和数据库中的密码进行比对

5. 如果比对失败,对客户端做出响应

6. 如果密码对比成功,将用户信息更新到数据库中

module.exports = async(req, res, next) => {
    // 接收客户端传递的请求参数
    const { username, email, password, role, state } = req.body;
    const id = req.query.id;
    console.log(req.body);

    // 查询将要修改的用户
    let user = await User.findOne({ _id: id });

    // 密码比对
    const isValid = await bcrypt.compare(password, user.password);
    if (isValid) { // 密码正确
        // 将用户信息更新到数据库中
        await User.updateOne({ _id: id }, {
            username: username,
            email: email,
            role: role,
            state: state
        });
        // 重定向到用户列表页面
        res.redirect('/admin/user');
    } else { // 密码错误
        let obj = { path: '/admin/user-edit', message: '密码错误,不能修改用户信息', id: id };
        return next(JSON.stringify(obj));
    }
};
// 错误处理中间件
app.use((err, req, res, next) => {
    // JSON.parse() 将字符串转换为对象
    const result = JSON.parse(err);
    let params = [];
    for (let attr in result) {
        if (attr != 'path') {
            params.push(attr + '=' + result[attr]);
        }
    }
    res.redirect(`${result.path}?${params.join('&')}`);
})

 2.5 用户信息删除

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

<div class="modal-body">
    <p>您确定要删除这个用户吗?</p>
    <input type="hidden" name="id">
</div>

2. 为删除按钮添自定义属性用以存储要删除用户的ID值

<i class="glyphicon glyphicon-remove delete" data-toggle="modal" data-target=".confirm-modal" data-id="{{@$value._id}}"></i>

3. 为删除按钮添加点击事件,在点击事件处理函数中获取自定义属性中存储的ID值并将ID值存储在表单的隐藏域中

{{block 'script'}}
    <script>
        $('.delete').on('click', function(){
            // 获取用户id
            var id = $(this).attr('data-id');
            // 将要删除的用户id存储在隐藏域中
            $('#deleteUserId').val(id);
        });
    </script>
{{/block}}

4. 为删除表单添加提交地址以及提交方式

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

6. 接收客户端传递过来的id参数

7. 根据id删除用户

2.6 开发环境与生产环境

什么是开发环境与生产环境:环境,就是指项目运行的地方,当项目处于开发阶段,项目运行在开发人员的电脑上,项目所处的环境就是开发环境。当项目开发完成以后,要将项目放到真实的网站服务器电脑中运行,项目所处的环境就是生产环境。

为什么要区分开发环境与生产环境:因为在不同的环境中,项目的配置是不一样的,需要在项目代码中判断当前项目运行的环境,根据不同的环境应用不同的项目配置。

如何区分开发环境与生产环境:通过电脑操作系统中的系统环境变量区分当前是开发环境还是生产环境。

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

2.7 第三方模块config 

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

使用步骤:

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

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

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

4. 在项目中通过require方法,将模块进行导入

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

将敏感配置信息存储在环境变量中:

1. 在config文件夹中建立custom-environment-variables.json文件

2. 配置项属性的值填写系统环境变量的名字

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

2.8 文章评论

 创建评论集合

// 1. 引入mongoose模块
const mongoose = require('mongoose');

// 2. 创建评论集合规则
const commentSchema = new mongoose.Schema({
    // 文章id
    aid: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Article'
    },
    // 用户id
    uid: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    },
    // 评论时间
    time: {
        type: Date
    },
    // 评论内容
    content: {
        type: String
    }
});

// 3. 创建评论集合
const Comment = mongoose.model('Comment', commentSchema);

// 4. 将评论集合构造函数导出
module.exports = {
    Comment
}

判断用户是否登录,如果用户登录,再允许用户提交评论表单

                    <div class="article-comment">
					{{if userInfo}}
					<h4>评论</h4>
					
					<form class="comment-form">
						<textarea class="comment"></textarea>
						<div class="items">
							<input type="submit" value="提交">
						</div>
					</form>
					{{else}}
						<div><h2>请先登录,再进行评论</h2></div>
					{{/if}}

在服务器端创建文章评论功能对应的路由

在路由请求处理函数中接收客户端传递过来的评论信息

将评论信息存储在评论集合中

将页面重定向回文章详情页面

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);

}

在文章详情页面路由中获取文章评论信息并展示在页面中 

                    <div class="comment-list">
						{{each comments}}
						<div class="mb10">
							<div class="article-info">
								<span class="author">{{$value.uid.username}}</span>
								<span>{{moment($value.time).format('YYYY-MM-DD')}}</span>
								<span>{{$value.uid.email}}</span>
							</div>
							<div class="comment-content">
								{{$value.content}}
							</div>
						</div>
						{{/each}}
					</div>

2.9 首页文章列表

    <!-- 文章列表开始 -->
	<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>{{moment($value.publishDate).format('YYYY-MM-DD')}}</span>
				</div>
				<!--
					将html标签替换为空字符串 并截取前150个字符 并输出原文
				-->
				<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>
	<!-- 分页结束 -->

3. 项目包含的知识点

3.1 密码加密 bcrypt

哈希加密是单程加密方式:1234 => abcd

在加密的密码中加入随机字符串可以增加密码被破解的难度。

// 导入bcrypt模块
const bcrypt = require('bcrypt');
// 生成随机字符串 gen => generate 生成 salt 盐
let salt = await bcrypt.genSalt(10);
// 使用随机字符串对密码进行加密
let pass = await bcrypt.hash('明文密码', salt);

// 密码比对
let isEqual = await bcrypt.compare('明文密码', '加密密码');

 bcrypt依赖的其他环境

1. python 2.x

2. node-gyp     npm install -g node-gyp

3. windows-build-tools      npm install --global --production windows-build-tools

3.2 cookie与session

cookie:浏览器在电脑硬盘中开辟的一块空间,主要供服务器端存储数据。

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

cookie中的数据是有过期时间的,超过时间数据会被浏览器自动删除。若不设置,则关闭浏览器时会删除cookie数据

cookie中的数据会随着请求被自动发送到服务器端。

session:实际上就是一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个sessionid做为唯一标识。当网站服务器重启时,服务器端的session对象失效。

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

const session = require('express-session');
app.use(session({ secret: 'secret key' }));

 3.3 Joi 

JavaScript对象的规则描述语言和验证器。

// 老版本 已弃用
const Joi = require('joi');
const schema = {
    username: Joi.string().alphanum().min(3).max(30).required().error(new Error(‘错误信息’)),
    password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
    access_token: [Joi.string(), Joi.number()],
    birthyear: Joi.number().integer().min(1900).max(2013),
    email: Joi.string().email()
};
Joi.validate({ username: 'abc', birthyear: 1994 }, schema);

Joi.validate is a not function解决办法及最新joi验证方法
使用Joi.validate()方法报错:Joi.validate is not a function的原因是因为这个方法已经被joi弃用了,有两种解决方法

把最新版本的joi卸载,下以前的joi版本
npm uninstall joi
npm install joi@14.3.1
使用最新方法

// 引入joi模块
const Joi = require('joi');

// 定义对象的验证规则
const schema = Joi.object({
    username: Joi.string().min(2).max(5).required().error(new Error('username属性没有通过验证')),
    birth: Joi.number().min(1900).max(2020).error(new Error('birth属性没有通过验证'))
});

async function run() {
    try {
        // 实施验证
        await schema.validateAsync({ username: 'ab', birth: 1800 });
    } catch (error) {
        console.log(error.message);
        return;
    }
    console.log('验证通过');
}

run();


里面的验证规则和Joi.validate()一样

3.4 formidable 

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

// 引入formidable第三方模块
const formidable = require('formidable');
const path = require('path');
const { Article } = require('../../model/article');

module.exports = (req, res) => {
    // 创建表单解析对象
    const form = formidable({
        // 配置上传文件的存放位置
        uploadDir: path.join(__dirname, '../', '../', 'public', 'uploads'),
        // 保留上传文件后缀
        keepExtensions: true
    });

    // 解析表单
    form.parse(req, async(err, fields, files) => {
        // err 错误对象 如果表单解析失败 err存储错误信息 如果表单解析成功 err存储null
        // fields 对象 保存普通表单数据
        // files 对象 保存上传文件相关数据
        await Article.create({
            title: fields.title,
            author: fields.author,
            publishDate: fields.publishDate,
            cover: files.cover.filepath.split('public')[1],
            content: fields.content
        });
        // 重定向到文章列表页面
        res.redirect('/admin/article');
    });

}

3.5 文件读取 FileReader

        var file = document.querySelector('#file');
        var preview = document.querySelector('#preview');
        // 当用户选择完文件
        file.onchange = fumction(){
            // 创建文件读取对象
            var reader = new FileReader();
            // 用户选择的文件列表 this.files[0]
            // 读取文件
            reader.readAsDataURL(this.files[0]);
            // 监听onload事件
            reader.onload = function(){
                // 将选中的图片显示在页面中
                preview.src = reader.result;
            }
        }

3.6 数据分页 mongoose-sex-page

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';

    // 查询所有文章数据 联合查询 author变成user对象
    // page 当前页 size 每页显示的数据条数 display 客户端要显示的页码数量 exec向数据库中发送查询请求
    let articles = await pagination(Article).find().page(page).size(2).display(3).populate('author').exec();
    let str = JSON.stringify(articles);
    let json = JSON.parse(str);
    // res.send(articles)
    // 渲染文章列表页面模板
    res.render('admin/article', {
        articles: json
    });
}

            <!-- 分页 -->
            <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.7 mongoDB数据库添加账号 

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

2. 连接数据库 mongo

3. 查看数据库 show dbs

4. 切换到admin数据库 use admin

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

6. 切换到blog数据库 use blog

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

                 退出 exit

8. 卸载mongodb服务        

         1. 停止服务 net stop mongodb

         2. 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. 启动mongodb服务 net start mongodb

11. 在项目中使用账号连接数据库                   mongoose.connect('mongodb://user:pass@localhost:port/database')

4. 踩坑

1. 联合查询

报错信息


报错代码

    // 查询所有文件数据  (多集合联合查询)
let articles = await Article.find().populate('author');
    // 渲染文章列表页面模板
    res.render('admin/article', {
        articles: articles
    });


当集合联合查询和渲染页面模板同时进行时会导致两者冲突,从而导致无法渲染页面。所以报错
解决方法:
利用 lean() 方法将多级联合的结果转化为普通对象 ,缓解两者的冲突。

 let articles = await Article.find().populate('author').lean();


另外一个报错代码

let articles = await pagination(Article).find().page(1).size(1).display(3).populate('author').exec();
res.render('admin/article', {
        articles: articles
    });


报错原因:
当集合联合查询和渲染页面模板同时进行时会导致两者冲突,从而导致无法渲染页面。所以报错

解决后的代码:

    let articles = await pagination(Article).find().page(1).size(1).display(3).populate('author').exec();
    let str = JSON.stringify(articles);
    let json = JSON.parse(str);
    // res.send(articles)
    // 渲染文章列表页面模板
    res.render('admin/article', {
        articles: json
    });
}

2. mongoose 设置默认时间

问题:在集合规则中设置default:Date.now 时 ,数据库中显示null

解决方法:应该是代码逻辑问题

await Article.create({
            title: fields.title,
            author: fields.author,
            publishDate: fields.publishDate == null ? Date.now : files.publishDate,
            cover: files.cover.filepath.split('public')[1],
            content: fields.content
        });

3. dateformat第三方模块不支持require()方法

报错信息:

解决方法 :改用moment第三方模块

const moment = require('moment');
var time = moment(Date.now()).format('YYYY-MM-DD HH:mm:ss');

4. 模板语法书写错误

报错信息:

大部分是模板语法书写错误 

双大括号是否书写完整 或 结束位置是否添加 / 

免费个博客系统(兼用户博客系统)是支持一个空间2个网站的全能型网站管理系统,本免费个博客系统通用和拓展性强,博客、文章系统、商城、企业网站、个性化论坛等类型网站都可以使用,将来网站无论如何转型或拓展,只需要修改模板就可以实现,无需重建网站。本系统不同于以往任何逻辑架构的网站程序。本软件开发者希望通过注重商业化开发,助力用户通过网络创业和赚钱,当然您也可以通过这个软件在互联网高效地展示自己。 详细说明: 1.本个博客系统可以用于商业用途,本软件官方、开发者不收取任何授权费用; 2.本个博客系统是支持一个空间2个网站的全能型博客系统; 3.本个博客系统通用和拓展性强,博客、文章系统、商城、企业网站、个性化论坛等类型网站都可以使用; 4.本个博客系统功能强大,代码少,运行效率更高,程序运行速度是其它主流同类软件的3~4倍,内存占用不到其它主流同类软件的五分之一; 5.开启和关闭会员注册,开启和关闭普通会员投稿功能; 6.会员功能拓展到了兼职专题功能,SEO设置和开放特约编辑的用户不同权限管理功能等; 7.超级管理员可无密码一键登录任意会员后台,管理员用受限登录会员身份后台发布信息,也可让网站攻击者无法猜解密码; 8.自动生成手机版网站,系统默认带www的域名为PC模板站,不带www的顶级域名为手机站,不增加维护难度,就可以同时拥有2个网站; 9.博客程序还包含订单、秒杀、限时抢购和数量虚拟功能,助力用户互联网创业和商业化运营,就看脑洞大开的你怎么使用了; 10.本个博客系统能够适应各种界面浏览器,后台可手机随时随地访问、管理和更新网站; 11.可一键切换成.shtml、.html、.htm、.asp、.aspx、.cgi、.php、.jsp、.cgi、/ 等网页后缀,模拟不同语言编程的网站程序; 12.前端页面精简,前端编码不用div标签,不用id、class规则的CSS样式,最大限度精简前端代码,鼓励用户抛弃div+CSS前端代码编写模式,我们这样做不是为了迎合HTML5,只是为了更合理的应用HTML标签; 13.安装程序自动识别和设置伪静态; 14.全站无死角SEO设置; 15.强大的内链逻辑,特别适应大数据类型网站使用; 16.强大的广告和精准广告设置; 17.数据缓存模式,不依赖外部服务器组件和其它插件,不额外占用服务器系统内存资源; 18.删除局部缓存和一键清除全部缓存; 19.可设置邮件实时通知新订单和访客留言; 20.可设置管理员回复留言可同时邮件通知留言者; 21.可查看和删除无用上传文件,为将来数据备份节省时间和空间; 22.特色的tag标签功能; 23.分类、tag标签、url表单填写自动补缺; 24.url表单可自动生成拼音,也可以用汉字,自动转码,有利于SEO搜索引擎排名; 25.开放式PHP原生态模板,用户任意修改、穿插内容或广告,无需花时间研究额外规则,模板修改成本更低; 26.可对模板备份,使用备份模板,并可对模板恢复系统初始状态; 27.模板修改全站页面秒更新; 28.可自定义SQL语句的图片展示页面; 29.后台可控制各个模块是否开启验证码、设置验证码长度,以及设置验证码破解难度; 30.访客留言关键词过滤; 31.可自定义导航; 32.可在线编辑js和CSS文件; 33.本免费个博客系统(兼用户博客系统)无后门。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值