文章管理系统 -- Express学习

文章管理系统 – Express学习

仓库:https://gitee.com/aeipyuan/articles_manage

1.项目搭建

生成express项目

express -e article_managemet   创建项目 -e 表示使用ejs模板引擎

mongodb创建数据库

mongo                               - 开启mongodb
use articles_db                     - 创建/使用数据库
db.createCollection('users')        - 创建users集合
db.createCollection('articles')     - 创建article集合
show collections                    - 查询是否创建成功

测试:
db.users.insertOne({username:'admin',passsword:'000'})
db.users.find()

创建连接mongodb连接的模块

文件位置:model > index.js
    
const MongoClient = require('mongodb').MongoClient;
 /* 连接数据库的url,在mongo命令下可以找到 */
 let url = 'mongodb://localhost:27017';
 /* 连接的数据库名字 */
 let dbName = 'articles_db';
 /* 封装数据库连接方法 */
 function connect(callback) {
     MongoClient.connect(url, (err, client) => {
         if (err) {
             console.log('数据库连接错误!', err);
         } else {
             /* 根据数据库名获取数据库返回给callback处理 */
             let db = client.db(dbName);
             callback && callback(db);
             client.close();
         }
     })
 }
 module.exports = { connect };

 /* 测试 */
 connect(db => {
     db.collection('users').findOne({ username: 'admin' }, (err, docs) => {
         if (err)
             console.log(err)
         else
             console.log(docs); 
     });
 });//{ _id: 5ea442dbbc0dfbff14afd728, username: 'admin', passsword: '000'

2.注册页

在这里插入图片描述

注册路由

//位置:routes > index.js
router.get('/regist', (req, res, next) => {
	res.render('regist');
})

页面结构

<div class='form-box'>
    <form action="/users/regist" method="post">
        <input type="text" name="username" placeholder="请输入用户名">
        <input type="password" name="password" placeholder="请输入密码">
        <input type="password" name="password2" placeholder="请确认密码">
        <input type="submit" value="注册">
    </form>
    <div>已有帐户,<a href="/login">立即登录</a></div>
</div>

提交处理

//位置 routes > user.js   
router.post('/regist', (req, res, next) => {
	/* 取出提交数据 */
	let { username, password, password2 } = req.body;
	let data = { username, password, password2 };
	/* 校验数据 */
	if (password !== password2) {
		console.log("两次输入的密码不一致");
		res.redirect('/regist');
	} else {
		model.connect(db => {/* 写入数据库 */
			db.collection('users').insertOne(data, (err, docs) => {
				if (err) {
					console.log('注册失败');
					res.redirect('/regist');//返回注册页
				} else {
					console.log('注册成功');
					res.redirect('/login');//返回登陆页
				}
			})
		})
	}
})

3.登录页

在这里插入图片描述

页面结构

<div class="form-box">
    <form action="/users/login" method="post">
        <input type="text" name="username" placeholder="请输入用户名">
        <input type="password" name="password" placeholder="请输入密码">
        <input type="submit" value="登录">
    </form>
    <div>没有帐号,<a href="/regist">立即注册</a></div>
</div>

提交处理
(1)安装express-session拦截登录状态

安装
npm i express-session -S 

/* -------------app.js配置------------- */
/* 配置session */
app.use(session({
	secret: 'qf project',
	resave: false,
	saveUninitialized: true,
	cookie: { maxAge: 1000 * 60 * 10 }   // 指定登录会话的有效时长
}))
/* 拦截登陆状态 */
app.get('*', (req, res, next) => {
	let username = req.session.username;
	let path = req.path;
    console.log("session-" + username);
	if (path != '/login' && path != '/regist') {
		if (!username)
			res.redirect('/login');
	}
	next();
})

(2)从数据库查询信息并存入session

/* 登录提交 */
router.post('/login', (req, res, next) => {
	let data = {
		username: req.body.username,
		password: req.body.password
	}
	/* 连接数据库 */
	model.connect(db => {
		db.collection('users').findOne(data, (err, docs) => {
			if (err) {
				console.log(err);
				res.redirect('/login');
			} else {
				req.session.username = data.username;
				res.redirect('/');
			}
		})
	})
})

4. 通用顶部组件

在这里插入图片描述
结构

<!-- bar.ejs -->
<div class='bar'>
    <span><%- username  %></span>
    <a href='/write'>写文章</a>
    <a href='/users/logout'>退出</a>
    <a href='/' class='home'>
        <img src="/images/home.png" alt="首页">
    </a>
</div>

处理退出请求

router.get('/logout', (req, res, next) => {
	req.session.username = null;
	res.redirect('/login');
})

5. 写文章

在这里插入图片描述

页面结构

<%- include('bar',{username:username}) %>
<form class="article" action="/article/add" method="post">
    <input type="text" name="title" placeholder="请输入文章标题">
    <textarea name="content" class="xheditor" cols="30" rows="10"></textarea>
    <input type="submit" value="发布">
</form>
<!-- xheditor富文本编辑器 -->
<script type="text/javascript" src="/xheditor/jquery/jquery-1.4.4.min.js"></script>
<script type="text/javascript" src="/xheditor/xheditor-1.2.2.min.js"></script>
<script type="text/javascript" src="/xheditor/xheditor_lang/zh-cn.js"></script>
<script>
    $('.xheditor').xheditor({
        tools: 'full',
        skin: 'default',
        upImgUrl: '/article/upload',
        html5Upload: false,
        upMultiple: 1
    });
</script>

数据提交
(1)创建article路由处理文件

/* app.js配置 */
var articleRouter=require('./routes/article');
app.use('/article', articleRouter);

(2)处理提交请求

/* routes > article.js */
router.post('/add', (req, res, next) => {
    /* 获取数据 */
    let username = req.session.username;
    let data = {
        title: req.body.title,
        content: req.body.content,
        id: Date.now(),
        username: username
    }
    /* 写入数据库 */
    model.connect(db => {
        db.collection('articles').insertOne(data, (err, docs) => {
            if (err) {
                console.log('提交失败', err);
                res.redirect('/write');
            } else {
                console.log('提交成功');
                res.redirect('/');
            }
        })
    })
})

6. 首页

在这里插入图片描述

页面结构

<%- include('bar',{username}) %>
<div class='list'>
	<% data.list.map((item,index)=>{ %>
	<div class='row'>
		<span><%- index+1 %></span><!-- 序号 -->
		<span><%- item.username %></span><!-- 作者 -->
		<span>
			<a href="/detail?id=<%- item.id %>&pageIndex=<%- data.pageIndex %> "> <%- item.title %></a><!-- 标题 -->
		</span>
		<span><%- item.time %></span><!-- 时间 -->
		<div>
			<a href=" /write?id=<%- item.id %>&pageIndex=<%- data.pageIndex %>">编辑</a>
			<a href="/article/delete?id=<%- item.id %>&pageIndex=<%- data.pageIndex %>">删除</a>
		</div>
	</div>
	<% }) %>
	<!-- 显示页码 -->
	<div class="pages">
		<% for(let i=1;i<=data.total;i++){ %>
		<a href="/?pageIndex=<%- i %>"><%- i %></a><!-- 根据页码重新请求数据 -->
		<% } %>
	</div>
</div>
<!--
data数据结构:
{
	total: 1,
	pageIndex: 1,
	list: [{
		_id: 5ea5538ad8c2e22d908155eb,
		title: '1dxsxx',
		content: 'dccfwvfr',
		id: 1587893130840,
		username: 'a',
		time: '2020-04-26 05:25:30'
	}]
} -->

数据请求实现分页
通过url将当前页码pageIndex传入,然后第一次查询总的数量处理pageSize获得总页数total,第二次查询添加限制条件获取页面需要显示的数据列表,针对不是第一页时页面数据为空的问题,将pageIndex-1重新加载


/* http://localhost:8888/?pageIndex=1  */
router.get('/', async (req, res, next) => {
	/* 页面数据 */
	let pageIndex = req.query.pageIndex || 1;
	let pageInfo = {
		total: 0,/* 总共页数 */
		pageIndex,/* 当前页 */
		list: []/* 页面数据 */
	}
	let pageSize = 3;
	model.connect(db => {/* 查询所有数据 */
		db.collection('articles').find().toArray((err, docs) => {
			if (err) {
				console.log('首页数据查询错误');
				res.render('index', { username, pageInfo });
			} else {
				pageInfo.total = Math.ceil(docs.length / pageSize);//获取总的页面数
				model.connect(db => {/* 限制条件查询 */
					db.collection('articles').find()/* 查找所有 */
						.sort({ id: -1 })/* 按时间倒序 */
						.limit(pageSize)/* 限制数量 */
						.skip((pageIndex - 1) * pageSize)/* 跳过数量 */
						.toArray((err2, list) => {
							if (err2) {
								console.log('首页数据查询错误', err2);
							} else {/* 除第一页若页面数据条数为0则请求前一页 */
								if (pageIndex != 1 && !list.length) {
									res.redirect('/?pageIndex=' + (pageIndex - 1));
								} else {
									/* 格式化时间 */
									list.forEach(v => v.time = moment(v.id).format('YYYY-MM-DD hh:mm:ss'))
									pageInfo.list = list;
								}
								/* 将数据传给页面 */
								res.render('index', {
									username: req.session.username,
									data: pageInfo
								});
							}
						})
				})
			}
		})
	})
})

7.文章详情

在这里插入图片描述

页面结构

<!-- index.ejs入口 -->
<a href="/detail?id=<%- item.id %>&pageIndex=<%- data.pageIndex %> ">
 	<%- item.title %>
</a><!-- 标题 -->

<!-- detail.ejs -->
<%- include('bar',{username}) %>
<div class='detail'>
	<div class='title'><%- data.title %></div>
	<div class="desc"><span>作者:<%- data.title %></span> <span> 发布时间:<%- data.time %></span></div>
	<div class="content"><%- data.content %></div>
</div>
<!-- 
data格式:
{
  _id: 5ea5538ad8c2e22d908155eb,
  title: '1dxsxx',
  content: 'dccfwvfr',
  id: 1587893130840,
  username: 'a',
  time: '2020-04-26 05:25:30'
}
 -->

数据处理

/* 文章详情 http://localhost:8888/detail?id=1587900351782&pageIndex=1 */
router.get('/detail', (req, res, next) => {
	let id = parseInt(req.query.id);
	let pageIndex = req.query.pageIndex;
	model.connect(db => {
		db.collection('articles').findOne({ id }, (err, docs) => {
			if (err) {
				console.log('详情获取失败' + err);
				res.redirect('/?=' + pageIndex);//返回主页
			} else {
				/* 时间格式化 */
				docs.time = moment(docs.id).format('YYYY-MM-DD hh:mm:ss');
				console.log(docs)
				res.render('detail', {
					username: req.session.username,
					data: docs
				})
			}
		})
	})
})

8.文章编辑

在这里插入图片描述

页面结构
编辑与添加文章结构相同,区别在于数据idpageIndex记录,修改write页结构即可

<!-- index.ejs的入口 -->
<a href=" /write?id=<%- item.id %>&pageIndex=<%- data.pageIndex %>">编辑</a>
<!-- write.ejs -->
<form class="article" action="/article/add" method="post">
	<!-- 传递参数使用hidden -->
	<input type="hidden" name="id" value="<%- data.id %>">
	<input type="hidden" name="pageIndex" value="<%- data.pageIndex %>">
	<input type="text" name="title" placeholder="请输入文章标题" value="<%- data.title %>">
	<textarea class="xheditor" name="content" cols="30" rows="10"><%- data.content %></textarea>
	<input type="submit" value="<%- data.id?'修改':'发布' %>">
</form>
<!-- data数据结构
	{
		id: 1587893123696,
		title: 'qqqqq',
		content: 'qcdxcdxcxzczx',
		pageIndex: '1'
	}
-->

数据处理
1.进入write页面数据获取
写文章进入write页时没有idpageIndex传入,修改文章时可以通过id查询到文章titlecontent,通过pageIndex可以标识修改完成后要返回的页面

/* 文章 http://localhost:8888/write?id=1587900351782&pageIndex=1*/
router.get('/write', (req, res, next) => {
	/* 获取数据 */
	let id = parseInt(req.query.id) || null;
	let pageIndex = req.query.pageIndex || 1;
	let data = {
		id,
		title: '',
		content: '',
		pageIndex
	}
	/* 查询数据 */
	model.connect(db => {
		db.collection('articles').findOne({ id: id }, (err, docs) => {
			if (err) {
				console.log('获取文章数据失败', err);
				res.redirect('/?pageIndex=' + pageIndex);
			} else {
				if (docs) {/* 查询结果为空是新增文章 */
					data.title = docs.title;
					data.content = docs.content;
				}
				res.render('write', {
					username: req.session.username,
					data
				})
			}
		})
	})
})

2.点击修改按钮更新数据
通过是否含有id来判断是更新操作还是插入操作,操作失败返回当前页,成功则返回主页

/* 文章发布/修改 req.body => id pageIndex title content*/
router.post('/add', (req, res, next) => {
    /* 获取数据 */
    let username = req.session.username;
    let id = parseInt(req.body.id);
    let pageIndex = req.body.pageIndex;
    let data = {
        title: req.body.title,
        content: req.body.content,
        id: id || Date.now(),/* 修改是id,添加是Date.now() */
        username: username,
    }
    model.connect(db => {
        if (id) {/* 修改 */
            db.collection('articles').updateOne({ id }, { $set: data }, (err, docs) => {
                if (err) {
                    console.log('修改失败', err);
                    res.redirect(`/write?id=${id}&pageIndex=${pageIndex}`);/* 重新操作 */
                } else {
                    res.redirect(`/?pageIndex=${pageIndex}`);//回主页
                }
            })
        } else {/* 添加 */
            db.collection('articles').insertOne(data, (err, docs) => {
                if (err) {
                    console.log('发布失败', err);
                    res.redirect('/write');/* 重新操作 */
                } else {
                    console.log('发布成功');
                    res.redirect('/');/* 回主页 */
                }
            })
        }
    })
})

9.删除文章

<a href="/article/delete?id=<%- item.id %>&pageIndex=<%- data.pageIndex %>">删除</a>

在主页加入删除文章选项,id用于找到指定文章,pageIndex确定删除后返回的页面

/* 文章删除  http://localhost:8888/?id=1587874300950&pageIndex=1 */
router.get('/delete', (req, res, next) => {
    /* 获取id和当前页码 */
    let id = parseInt(req.query.id);
    let pageIndex = req.query.pageIndex;
    /* 删除数据 */
    model.connect(db => {
        db.collection('articles').deleteOne({ id }, (err, ret) => {
            if (err) {
                console.log('删除失败', err);
                res.redirect(`/?pageIndex=${pageIndex}`);
            } else {
                console.log('删除成功', ret);
                res.redirect(`/?pageIndex=${pageIndex}`);
            }
        })
    })
})

10.实现图片上传

安装multiparty插件解析请求

npm i multiparty -S

配置xheditor富文本编辑器

$('.xheditor').xheditor({
	tools: 'full',
	skin: 'default',
	upImgUrl: '/article/upload',/* 上传路由 */
	html5Upload: false,
	upMultiple: 1
});

配置上传路由

/* 图片上传 */
router.post('/upload', (req, res, next) => {
    var form = new multiparty.Form();
    form.parse(req, (err, fields, files) => {
        if (err) {
            console.log('上传失败', err);
        } else {
            let file = files.filedata[0];
            /* 创建读写流 */
            let rs = fs.createReadStream(file.path);
            /* 存图片的路径为public下的/uploads/ */
            let newPath = '/uploads/' + file.originalFilename;
            let ws = fs.createWriteStream('./public' + newPath);
            /* 边读边写 */
            rs.pipe(ws);
            ws.on('close', () => {
                console.log('文件上传成功');
                res.send({ err: '', msg: newPath });
            })
        }
    })
})
/* 
files {
  filedata: [
    {
      fieldName: 'filedata',
      originalFilename: 'Snipaste_2019-09-23_00-10-40.png',
      path: 'C:\Users\14329\AppData\Local\Temp\qWOzXsQRXhVyqiO57W3qjMgC.png',
      headers: [Object],
      size: 146188
    }
  ]
}
*/
原文地址:https://www.cnblogs.com/aeipyuan/p/12783489.html