文章管理系統 – 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.文章編輯
頁面結構
編輯與添加文章結構相同,區別在於數據id
和pageIndex
記錄,修改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
頁時沒有id
和pageIndex
傳入,修改文章時可以通過id
查詢到文章title
和content
,通過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
}
]
}
*/