目录
- 1. 初始化项目
- 2. mongodb初始化
- 3. 实现业务需求:实现user和book数据库的增删改查操作
- user的业务:注册、登录和退出登录
- 1.1 注册页面 views/register.ejs
- 1.1 注册页面渲染注册路由 routers/index
- 1.1 修改注册页面 views/register.ejs
- 1.1 添加注册提交信息的路由接口 routers/user
- 1.1 修改注册提交信息的路由接口 routers/user
- 1.2 添加登录提交信息的路由接口 routers/index
- 1.2 添加渲染页面 login.ejs
- 1.2 添加用户登录接口 routers/users
- 1.2 用户登录成功之后需要在浏览器缓存用户信息,指定时间里用户不需要再次登录express-session
- 1.2 修改user/login接口
- 1.2 app.js中进行登录拦截
- 1.2 修改首页的头部 views/index.ejs,将头部单独放在一个headerbar.ejs
- 1.2 添加头部headerbar.ejs
- 1.3 添加退出路由 router/user.js
- 1.3 在首页路由中获得session存储的username routers/index.js
- 1.3 修改首页的头部 views/index.ejs,将用户名加入进去
- 1.3 头部拿到username并显示 headerbar.ejs
- book的业务:增删改查
- 2.1 重新添加一个article的路由,在app.js中配置,routers中新建article.js文件
- 2.1 在index.js中增加路由write
- 2.1 views下新建write.ejs模板
- 2.1 textarea使用xhediter编辑
- 2.1 在article.js中添加写完文章提交的接口 /article/add
- 2.2 在首页index.ejs中显示所有书籍,使用传参循环遍历显示书籍
- 2.2 添加/路由 访问数据库中的数据 ,修改原index.js中的渲染首页
- 2.2 实现首页的分页展示,首页路由index.js
- 2.2 实现首页的分页展示,首页index.ejs
- 2.3 删除功能,定义删除接口/article/del,并将当前页码与书籍信息传递过去
- 2.4 改:修改书籍信息,跳转write页面并显示书籍详情,重新插入书籍数据
- 2.5 文件上传
该项目是书籍管理系统:用户注册登录后访问首页,可以看到分页展示的书籍,可对书籍进行添加,修改等操作
数据库内容主要分为两类:用户类和书籍类
1. 初始化项目
express:node内置http模块封装
express -e express_mongo_lagou npm i -D npm i nodemon -g //实现热刷新
package.json文件需要修改
// 原来
"start": "node ./bin/www"
// 修改后
"start": "nodemon ./bin/www"
启动项目
npm start
2. mongodb初始化
- 在项目中新开终端启动mongodb的服务器(管理员)
使用shell或是图形界面robo 3T创建所需的数据库// 自己本地的MongoDB的目录 mongod --dbpath D:\data\db --logpath D:\data\log\mongodb2.log --auth
- 通过nodejs的mongdb链接数据库。在model文件夹中封装
// 认证登录
db.createUser({
"user": "u1",
"pwd": "u1",
"roles": [{
role: "readWrite",
db: "bookSystem"
}]
})
- 首先搭建项目架构:实现数据库连接的封装
model文件夹下的index.js封装连接数据库
// 数据库连接公共方法
// 1 mongo客户端对象
var MongoClient = require('mongodb').MongoClient;
// 2 url
var url = 'mongodb://u1:u1@localhost:27017/bookSystem';
// 3 数据库名称
const dbName = 'bookSystem';
// 4 封装数据库连接的方法
function connect(callback) {
MongoClient.connect(url, function (err, client) {
// 回调函数传参是:错误对象,客户端连接成功的对象
// 有错误先打印错误
if (err) {
console.log("数据库连接错误", err);
} else {
// 数据库连接成功的对象
var db = client.db();
// 如果callback存在的话
callback & callback(db);
// 关闭数据库
client.close();
}
});
}
module.exports = {
connect: connect,
}
在路由router/index.js中测试
router.get('/', function (req, res, next) {
model.connect(function (db) {
db.collection('users').find().toArray(function (err, docs) {
console.log("用户列表", docs);
res.render('index', { title: 'Express' });
})
})
});
测试数据库连接成功
3. 实现业务需求:实现user和book数据库的增删改查操作
user的业务:注册、登录和退出登录
分析:
- 注册需要页面register.ejs渲染,在index.js中增加/register路由。注册页面提交数据的路由是/users/register,注册页面可能跳转的路由是/login。提交数据主要是实现数据库users的插入,如果成功,跳转路由/login,如果不成功,刷新回本页面/register
- 登录需要页面登录页面login.ejs渲染,在index.js中增加/login路由。登录页面提交数据的路由是/users/login,登录页面可能跳转的路由是/register。提交数据主要是实现数据库users的查询,如果成功,跳转路由/(首页),如果不成功,刷新回本页面/login.用户登录成功之后需要在浏览器缓存用户信息,指定时间里用户不需要再次登录.跳转到首页后,首页从session中获取登录的用户名,将其传给首页模板index.ejs并渲染在页面。
- 退出登录在index.ejs中实现,路由是/user/logout,主要删除session的内容,并重定向到登录页面/login
公共头部 views/head.ejs
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie-edge">
<link rel='stylesheet' href='/stylesheets/style.css' />
1.1 注册页面 views/register.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<%- include head %>
<title>注册页</title>
</head>
<body>
<h1>注册</h1>
</body>
</html>
1.1 注册页面渲染注册路由 routers/index
// /register路由使用 注册页渲染
router.get('/register', function (req, res, next) {
res.render('register', {});
});
测试成功
1.1 修改注册页面 views/register.ejs
<body>
// 这里使用form-box样式自定义
<div class="form-box">
<form action="/users/register" method="post">
<input type="text" name="username" value="" placeholder="请输入用户名">
<input type="password" name="password" value="" placeholder="请输入密码">
<input type="password" name="password2" value="" placeholder="请再次输入密码">
<input type="submit" value="注册">
</form>
<div>已有账号,<a href="/login">立即登录</a>!</div>
</div>
</body>
1.1 添加注册提交信息的路由接口 routers/user
// 用户注册路由接口测试
router.post('/register', function (req, res, next) {
var data = {
username: req.body.username,
password: req.body.password,
password2: req.body.password2,
}
res.send(data);
});
测试成功
1.1 修改注册提交信息的路由接口 routers/user
// 用户注册接口测试
router.post('/register', function (req, res, next) {
var data = {
username: req.body.username,
password: req.body.password,
password2: req.body.password2,
}
/* 验证数据暂时忽略 */
/* 插入数据 */
model.connect(function (db) {
db.collection('users').insertOne(data, function (err, ret) {
if (err) {
console.log("注册用户失败");
return res.redirect('/register')
}
else {
console.log("成功");
return res.redirect('/login')
}
})
})
});
1.2 添加登录提交信息的路由接口 routers/index
// /login路由使用 登录页面login.ejs渲染
router.get('/login', function (req, res, next) {
res.render('login', {});
});
1.2 添加渲染页面 login.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<%- include head %>
<title>注册页</title>
</head>
<body>
<div class="form-box">
<form action="/users/login" method="post">
<input type="text" name="username" value="" placeholder="请输入用户名">
<input type="password" name="password" value="" placeholder="请输入密码">
<input type="submit" value="登录">
</form>
<div>没有账号,<a href="/register">立即注册</a>!</div>
</div>
</body>
</html>
- [ × ] 、、、還在上傳中注册成功后可跳转到登录頁面的視頻
1.2 添加用户登录接口 routers/users
// 用户登录接口
router.post('/login', function (req, res, next) {
var data = {
username: req.body.username,
password: req.body.password
}
// res.send(data)
/* 验证数据暂时忽略 */
/* 数据库查询数据 */
model.connect(function (db) {
db.collection('users').find(data, function (err, docs) {
if (err) {
// console.log("用户登录失败,请重新登录");
res.redirect('/login')
}
else {
if (docs.length > 0) {
console.log("用户登录成功");
res.redirect('/')
}
else {
res.redirect('/login')
}
}
})
})
});
/…。。。。。。。。用户登录成功跳转首页的视频还在上传中
1.2 用户登录成功之后需要在浏览器缓存用户信息,指定时间里用户不需要再次登录express-session
app.js添加express-session
var session = require('express-session');
// 配置session
app.use(session({
secret: 'mongodb_express_api',
resave: false,
saveUninitialized: true,
// 指定会话的有效时长,5分钟
cookie: { maxAge: 1000 * 60 * 5 }
}))
1.2 修改user/login接口
if (docs.length > 0) {
// console.log("用户登录成功,显示首页");
// 用户登录成功之后需要在浏览器缓存用户信息,指定时间里用户不需要再次登录express-session
// 如果session中存储用户名,说明5分钟之内登陆过系统,不需要再次登录,需要一个登陆拦截
req.session.username = data.username
res.redirect('/')
}
1.2 app.js中进行登录拦截
// 需要登陆拦截,在其他页面中如果session中存储了username,说明之前登陆过,如果没有session只能访问登录或注册页面
app.get('*', function (req, res, next) {
var username = req.session.username
var path = req.path
console.log('session', username)
if (path != '/login' && path != '/register') {
if (!username) {
res.redirect('/login')
}
}
next()
})
1.2 修改首页的头部 views/index.ejs,将头部单独放在一个headerbar.ejs
<%- include('headerbar',{}) %>
1.2 添加头部headerbar.ejs
<div class="bar">
<!-- 用户名,文章,退出 -->
<span>用户名</span>
<a href="/write">写文章</a>
<a href="/user/logout">退出</a><!-- 回到首页 -->
<a href="/" class="home"><img src="/images/home.png" alt="首页"></a>
</div>
1.3 添加退出路由 router/user.js
// 退出登录路由
router.get('/logout', function (req, res, next) {
// 把seesion的用户信息去掉,改为null
req.session.username = null;
res.redirect('/login')
})
1.3 在首页路由中获得session存储的username routers/index.js
router.get('/', function (req, res, next) {
var username = req.session.username;
});
1.3 修改首页的头部 views/index.ejs,将用户名加入进去
<%- include('headerbar',{username:username}) %>
1.3 头部拿到username并显示 headerbar.ejs
<%- include('headerbar',{username:username}) %>
目前完成的需求:注册,登录,退出登录。
book的业务:增删改查
加粗样式
- 增:首页点击“写文章”进入write路由,使用write.ejs模板渲染。
- 查:首页会自动分页显示所有的书籍信息。后端先获取数据库一共有多少书籍,根据每页展示条数,可以得到一共需要展示的页数total_num,发给前端;前段根据total_num循环打印出每页的a标签,也就是页码;前端将每次点击a标签的内容传送给后端,后端获取页码数,判断需要展示的数据列表,返回前端用以展示。
- 删:删除一篇文章
- 改:编辑文章。
2.1 重新添加一个article的路由,在app.js中配置,routers中新建article.js文件
var articleRouter = require('./routes/article');
app.use('/article', articleRouter);
2.1 在index.js中增加路由write
// /write路由使用 写文章页面write.ejs渲染
router.get('/write', function (req, res, next) {
var username = req.session.username;
res.render('write', { username: username });
});
2.1 views下新建write.ejs模板
<!DOCTYPE html>
<html>
<head>
<title>写文章</title>
<%- include head %>
</head>
<body>
<%- include('headerbar',{username:username}) %>
<div class="article">
<form action="/article/add" method="post">
<input type="text" name="title" value="" placeholder="请输入文章标题">
<textarea name="content" id=""></textarea>
<input type="submit" value="发布">
</form>
</div>
</body>
</html>
2.1 textarea使用xhediter编辑
<script src="/public/xheditor/jquery/jquery-1.4.4.min.js" type="text/javascript"></script>
<script type="text/javascript" src="/public/xheditor/xheditor-1.2.2.min.js"></script>
<script type="text/javascript" src="/public/xheditor/xheditor_lang/zh-cn.js"></script>
<script>
$('#elm1').xheditor({ tools: 'full', skin: 'default' });
</script>
2.1 在article.js中添加写完文章提交的接口 /article/add
var express = require('express');
var router = express.Router();
var model = require('../model');
const { route } = require('.');
// 用户注册接口
router.post('/add', function (req, res, next) {
var data = {
title: req.body.title,
content: req.body.content,
// 使用时间戳作为插入书籍的时间
id: Date.now(),
username: req.session.username
}
// res.send(data)
/* 验证数据暂时忽略 */
/* 数据库插入数据 */
model.connect(function (db) {
db.collection('articles').insertOne(data, function (err, ret) {
if (err) {
console.log("插入书籍失败");
return res.redirect('/write')
}
else {
console.log("插入书籍成功");
return res.redirect('/')
}
})
})
});
module.exports = router;
2.2 在首页index.ejs中显示所有书籍,使用传参循环遍历显示书籍
<div class="list">
<% list.map(function(item,index){ %>
<div class="row">
<!-- 序号 -->
<span><%- index+1 %></span>
<!-- <span>作者</span> -->
<span><%- item.username %></span>
<!-- 书名 -->
<span><%- item.title %></span>
<!-- 插入时间 -->
<span><%- item.time %></span>
<span><a href="">编辑</a><a href="">删除</a></span>
</div>
<% }) %>
</div>
2.2 添加/路由 访问数据库中的数据 ,修改原index.js中的渲染首页
文章事件的格式化显示使用到的插件是moment npm i moment -g
/* GET home page. */
/* 数据库访问测试 */
/* router.get('/', function (req, res, next) {
var username = req.session.username;
// console.log(username)
model.connect(function (db) {
db.collection('users').find().toArray(function (err, docs) {
if (err) {
console.log("数据库连接错误", err);
}
// console.log("用户列表", docs);
res.render('index', { username: username });
})
})
}); */
/* GET book listing. */
router.get('/', function (req, res, next) {
var username = req.session.username || ""
model.connect(function (db) {
db.collection('articles').find().toArray(function (err, docs) {
var list = docs
list.map(function (ele, index) {
ele.time = moment(ele.id).format('YYYY-MM-DD HH:mm:ss')
})
res.render('index', { username: username, list: list })
})
})
});
2.2 实现首页的分页展示,首页路由index.js
router.get('/', function (req, res, next) {
var username = req.session.username || ""
// 首页传来的数据,需要显式第几页,默认为1
var page = req.query.page || 1
// console.log("当前是第", page)
var data = {
// 总共有多少页
total_num: 0,
curPage: page, //当前页
list: [] //当前页的文章列表
}
var pageSize = 2 //默认每页显示两条数据
model.connect(function (db) {
// 1 查询所有文章
db.collection('articles').find().toArray(function (err, docs) {
// 文章数目
data.total_num = Math.ceil(docs.length / pageSize)
// 2 查询当前页的文章列表
model.connect(function (db) {
// 倒序查询,每次查两条,当前游标需要跳过多少数据
db.collection('articles').find().sort({ _id: -1 }).skip((data.curPage - 1) * pageSize).limit(pageSize).toArray(function (err, docs2) {
// 所有的文章列表修改时间样式
docs2.map(function (ele, index) {
ele.time = moment(ele.id).format('YYYY-MM-DD HH:mm:ss')
})
data.list = docs2
res.render('index', { username: username, data: data })
})
})
})
})
});
2.2 实现首页的分页展示,首页index.ejs
// 修改1:遍历后端传来的书籍list
<% data.list.map(function(item,index){ %>
// 修改2:页码
<div class="pages">
<% for(let i=1;i<=data.total_num;i++){ %>
<a href="/?page=<%= i %> "><%= i %></a>
<% } %>
</div>
// 修改3:详情
<a href="/detail?id=<%= item.id %>"><%- item.title %></a>
index.js中添加详情路由/detail以及detail.ejs页面
// /detail路由使用 详情页面detail.ejs渲染
router.get('/detail', function (req, res, next) {
var username = req.session.username;
var id = parseInt(req.query.id)
console.log(id)
model.connect(function (db) {
db.collection('articles').findOne({ id: id }, function (err, docs) {
if (err) {
console.log("查询失败")
}
else {
docs["time"] = moment(docs[id]).format('YYYY-MM-DD HH:mm:ss')
res.render('detail', { username: username, item: docs });
}
})
})
});
detail.ejs
<head>
<title>详情页</title>
<%- include head %>
</head>
<body>
<%- include('headerbar',{username:username}) %>
<div class="detail">
<div class="title"><%- item.title %></div>
<div class="desc">
<!-- <span>作者</span> -->
<span>作者:<%- item.username %></span>
<!-- 插入时间 -->
<span>发布时间<%- item.time %></span>
</div>
<div class="content"><%- item.content %></div>
</div>
</body>
</html>
2.3 删除功能,定义删除接口/article/del,并将当前页码与书籍信息传递过去
修改首页index.ejs 删除a链接的路由接口,并传送数据
<a href="/article/del?id=<%= item._id %>&page=<%= data.curPage %>">删除</a></span>
添加article.js路由的删除接口
// 删除书籍接口
router.get('/del', function (req, res, next) {
var id = parseInt(req.query.id)
var page = req.query.page
/* 验证数据暂时忽略 */
/* 数据库删除数据 */
model.connect(function (db) {
db.collection('articles').deleteOne({ id: id }, function (err, ret) {
if (err) {
console.log("删除书籍失败");
}
else {
console.log("删除书籍成功");
}
res.redirect('/?page=' + page)
})
})
});
这里需要重新修改index.js中显示分页数据,可能删除完当前页的list为空,需要跳到上一页或是第一页
if (docs2.length == 0) {
res.redirect('/?page=' + ((data.curPage - 1) || 1))
}
2.4 改:修改书籍信息,跳转write页面并显示书籍详情,重新插入书籍数据
修改首页index.ejs 编辑a链接的路由接口,并传送数据
<a href="/write?id=<%= item._id %>&page=<%= data.curPage %>">删除</a></span>
修改index.js路由的编辑接口 ,复用写文章的路由write
router.get('/write', function (req, res, next) {
var username = req.session.username;
var id = parseInt(req.query.id)
var page = req.query.page
var item = {
title: "",
content: ""
}
if (id) {
model.connect(function (db) {
db.collection('articles').findOne({ id: id }, function (err, docs) {
if (err) {
console.log("查询失败")
}
else {
item = docs
item["page"] = page
res.render('write', { username: username, item: item });
}
})
})
}
else { //新增
item["page"] = 0
res.render('write', { username: username, item: item });
}
});
在write.ejs页面中显示是新增书籍还是编辑书籍,使用两个隐藏域传参
<div class="article">
<form action="/article/add" method="post">
<input type="hidden" value="<%- item.id %>" name="id">
<input type="hidden" value="<%- item.page %>" name="page">
<input type="text" name="title" value="<%=item.title%>" placeholder="请输入文章标题">
<textarea name="content" id="" class="xheditor"><%- item.content %></textarea>
<% if(item.id){ %>
<input type="submit" value="修改">
<% }else{ %>
<input type="submit" value="发布">
<% } %>
</form>
</div>
修改add接口用以判断当前传参是新增还是修改,
// 插入书籍接口或修改书籍
router.post('/add', function (req, res, next) {
var id = parseInt(req.body.id)
if (id) {
// 修改
var page = req.body.page
console.log("page", page)
var title = req.body.title
var content = req.body.content
model.connect(function (db) {
db.collection('articles').updateOne({ id: id }, {
$set: {
title: title,
content: content
}
}, function (err, ret) {
if (err) {
console.log("修改书籍失败");
res.redirect('/write')
}
else {
console.log("修改书籍成功");
res.redirect('/?page=' + page)
}
})
})
}
else {
// 新增
var data = {
title: req.body.title,
content: req.body.content,
// 使用时间戳作为插入书籍的时间
id: Date.now(),
username: req.session.username
}
// res.send(data)
/* 验证数据暂时忽略 */
/* 数据库插入数据 */
model.connect(function (db) {
db.collection('articles').insertOne(data, function (err, ret) {
if (err) {
console.log("插入书籍失败");
return res.redirect('/write')
}
else {
console.log("插入书籍成功");
return res.redirect('/')
}
})
})
}
});
2.5 文件上传
xhediter配置,修改write.ejs,文件上传的接口为/article/ipload,一次仅上传一张
$('.xheditor').xheditor({
tools: 'full',
skin: 'default',
upImgUrl: '/article/upload',
html5Upload: false,
upMultiple: 1
});
文件上传处理插件multiparty npm install multiparty -g
nodejs模块ffs进行文件的读写
// 文件上传接口
router.post('/upload', function (req, res, next) {
var form = new multiparty.Form()
form.parse(req, function (err, fields, files) {
if (err) {
console.log("文件上传失败", err)
}
else {
console.log("文件列表", files)
var file = files.filedata[0]
var rs = fs.createReadStream(file.path)
var newPath = '/uploads/' + file.originalFilename
var ws = fs.createWriteStream('./public' + newPath)
rs.pipe(ws)
ws.on('close', function () {
console.log("文件上传成功")
// 给xhediter的反馈
res.send({ err: "", msg: newPath })
})
}
})
});