• 文章管理系统 -- 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
        }
      ]
    }
    */
    
  • 相关阅读:
    DP 水题 最长不下降子序列
    数的划分
    水题------纪念品分组
    NY95 众数问题
    NY86 找球号(一)
    C3-Zexal的矩阵链乘
    C3-Zexal的多路流水线调度
    C4-Zexal的食物链
    C4-排列
    C3-炮弹杀伤力
  • 原文地址:https://www.cnblogs.com/aeipyuan/p/12783489.html
Copyright © 2020-2023  润新知