課程管理系統
使用node實現簡單的增刪改查
一.handlebars模板引擎的使用
handlebars的安裝
npm i express
npm i express-hndlebars
handlebars的使用
模板引擎的目錄結構,必須如下圖所示:
資料夾名稱必須是views,views目錄下必須有一個layouts資料夾,layouts資料夾下有一個handlebars檔案,作為模板渲染的主檔案,所有的其他handlebars都會渲染到這個檔案中。
|---app.js
|---views
|---layouts
|---main.handlebars
|---index.handlebars
app.js中設定模板引擎:
const express = require('express');
const exphbs = require('express-handlebars');
const app = express();
//設定模板引擎
app.engine('handlebars', exphbs({defaultLayout: 'main'}));
app.set('view engine', 'handlebars');
app.get('/',(req,res) => {
res.render('index');
});
main.handlebars:所有的handlebars都會被渲染到這個檔案中
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no>
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
{{{body}}}
</body>
</html>
index.handlebars:是你路由指定的渲染模板
<h1>這裡是課程專案</h1>
二.新增課程
//新增課程
app.get('/ideas/add',(req,res) => {
res.render('ideas/add')
});
使用body-parser解析請求體
新增課程時,我們需要使用post請求,post請求包含請求體,在Node原生的http模組中,請求體需要使用流來進行接收和解析。body-parser是express使用的HTTP請求體解析的中介軟體,可以解析JSON、Raw、文字、URL-encoded格式的請求體。
body-parser的使用
通過使用body-parser.json()方法可以解析application/json格式的檔案
通過使用body-parser.urlencoded()方法可以解析application/x-www-form-urlencoded表單格式的資料
//使用body-parser中介軟體解析請求體
const bodyParser = require('body-parser');
// 解析 application/json
app.use(bodyParser.urlencoded());
const jsonParser = bodyParser.json();
// 解析 application/x-www-form-urlencoded
const urlencodedParser = bodyParser.urlencoded({ extended: false })
//解析後的資料在req.body中
app.post('/ideas',urlencodedParser,(req,res) => {
res.render('ideas/index',{
title:req.body.title,
details:req.body.details
})
});
後臺錯誤驗證
表單提交時,需要進行錯誤驗證。表單提交的資訊是否正確(是否全部填寫,填寫部分是否有要求等),在這裡我們需要提交兩個資料。
如下面程式碼所示:建立一個error陣列,如果req.body.title不存在表示沒有輸入這個內容,將提示文字新增到error陣列中。根據陣列的長度來驗證是否有錯誤,如果有錯誤,需要錯誤提示。error.handlebars用來描述錯誤提示。
app.post('/ideas',urlencodedParser,(req,res) => {
//後臺錯誤驗證
const error = [];
if(!req.body.title){
error.push({text:'請輸入標題'});
}
if(!req.body.details){
error.push({text:'請輸入詳情'});
}
if(error.length > 0){
res.render('ideas/add',{
//實現自動填寫
title:req.body.title,
details:req.body.details,
//實現錯誤提示
errors:error
})
}else{
res.render('ideas/index',{
title:req.body.title,
details:req.body.details
})
}
});
error.handlebars
{{#each errors}}
<div class="alert alert-danger">{{text}}</div>
{{/each}}
如果errors陣列不存在,那麼就沒有渲染的內容。
main.handlebars
<div class="container">
{{> error}}
{{{body}}}
</div>
三.增
新增課程以後,我們需要將新增的課程存入資料庫中。在需要展示時載從資料庫中調取。
mongoose的使用
1.連線資料庫 :node-course是我們自己定義的資料庫名稱
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/node-course');
2.建立集合
資料庫集合通常建立在models檔案中,每一個檔案代表一個集合。比如Idea.js表示的是集合Idea
|---app.js
|---models
|---Idea.js
Idea.js:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//建立一個Schema
const IdeaSchema = new Schema({
title:{
type:String,
required:true
},
details:{
type:String,
require:true
}
});
//建立集合idea
const Idea = mongoose.model('ideas',IdeaSchema);
//匯出集合物件
module.exports = Idea;
3.儲存資料
const Idea = require('./models/Idea');
//新增到資料庫
const newCourse = {
title:req.body.title,
details:req.body.details
};
//集合建立的示例物件就是要儲存的文件(資料)
new Idea(newCourse)
.save()
.then(()=>{
res.redirect('/ideas');
})
四.查
通過呼叫資料庫檢視資料
app.get('/ideas',(req,res) => {
Idea.find({})
.then((idea)=>{
res.render('ideas/index',{
ideas:idea
})
})
});
檢視資料可以直接使用Idea.find({})靜態方法,獲取到的是一個陣列,我們需要將這個陣列渲染到ideas/index.handlebars.
ideas/index.handlebars
{{#each ideas}}
<div class="card card-body">
<h3>{{title}}</h3>
<h3>{{details}}</h3>
</div>
{{/each}}
五.改
我們有時候需要對課程資訊進行編輯
跳轉到編輯頁面:
{{#each ideas}}
<div class="card card-body">
<h3>{{title}}</h3>
<h3>{{details}}</h3>
<a href="/ideas/edit/{{id}}" class="btn btn-dark btn-block">編輯</a>
</div>
{{/each}}
注意:上面的編輯按鈕中的{{id}}來自資料庫,每一個文件都有一個特定的id。我們在這裡可以直接獲取到。
跳轉到編輯頁面
app.get('/ideas/edit/:id',(req,res) => {
Idea.findOne({_id:req.params.id})
.then((idea) => {
res.render('ideas/edit',{
title:idea.title,
details:idea.details
})
})
});
進行編輯(改)
修改通常是使用put方法,但是表單一般只支援get和post方法,想要讓form支援put或者delete等方法,需要使用中介軟體method-override
method-override中介軟體的使用
1.使用method-override
//使用method-override支援put和delete等http請求方法
const methodOverride = require('method-override');
app.use(methodOverride('_method'))
2.修改form表單內容
修改action和新增一個隱藏的input框
<form action="/ideas/{{id}}?_method=PUT" method = "post">
//需要在這裡新增一個隱藏的input
<input type="hidden" name="_method" value="PUT">
<button type="submit" class="btn btn-primary">提交</button>
</form>
使用put進行修改:
資料庫的修改時先查詢到資料,然後跟操作物件一樣將資料修改完,然後記得儲存。
app.put('/ideas/:id',urlencodedParser,(req,res) => {
const course = {
title:req.body.title,
details:req.body.details
}
Idea.findOne({_id:req.params.id})
.then((idea) => {
idea.title = req.body.title;
idea.details = req.body.details;
idea.save()
.then(()=>{
res.redirect('/ideas')
})
})
});
六.刪
如果我們想要刪除課程,那麼不需要跳轉到新的頁面,直接在當前頁面刪除就行。由於刪除使用delete方法,因此我們同樣需要使用method-override,因此將請求放到form表單中。
<form action="/ideas/{{id}}?_method=DELETE" method = 'POST'>
<input type="hidden" name="method" value = 'DELETE'>
<input type="submit" class="btn btn-danger btn-block" value="刪除">
</form>
刪除操作:
//刪除
app.delete('/ideas/:id',urlencodedParser,(req,res) => {
Idea.remove({_id:req.params.id})
.then(()=>{
res.redirect('/ideas');
})
})
七.對使用者的操作進行提醒
我們在進行增刪改查的時候,需要對使用者的操作進行提醒,比如修改成功後,提示修改成功,刪除成功後,提示刪除成功,以及出現錯誤時,提示錯誤。connect-flash是nodejs中的一個模組,flash是一個暫存器,而且暫存器裡面的值使用過一次便被清空,適合用來做網站的提示資訊。flash 是 session 中一個用於儲存資訊的特殊區域。訊息寫入到 flash 中,在跳轉目標頁中顯示該訊息。flash 是配置 redirect 一同使用的,以確保訊息在目標頁面中可用
安裝
npm i express-session connect-flash
使用
在app.js中引入
const session = require('express-session');
const flash = require('connect-flash');
在app中使用flash中介軟體
app.use(session({
secret: 'secret',
resave: true,
saveUninitialized: true
}));
app.use(flash());
使用完flash中介軟體以後,所有的req中都存在一個flash方法,可以儲存內容。req.flash('success')。將flash中存入的變數存入res.locals全域性變數中,假如我要在網站中使用flash中存的error和success變數,加可以把它們傳入locals變數中,這樣所有的模板都可以拿到這個變數。注意flash儲存的變數都是隻能使用一次,使用完畢就會被移除
定義falsh變數:req.flash(success_msg)表示定義一個success_msg變數
//將flash中存入的變數存入res.locals物件中
app.use(function(req,res,next){
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
next();
});
給flash變數賦值,一般是在res.redirect()前面進行賦值req.flash('success_msg',"刪除成功")
app.delete('/ideas/:id',urlencodedParser,(req,res) => {
Idea.remove({_id:req.params.id})
.then(()=>{
req.flash('success_msg',"資料刪除成功");
res.redirect('/ideas');
})
})
req.flash賦值以後,res.locals中的變數就能夠獲取到這個值,那麼在任何模板中,都可以使用這個值。
_msg.handlebars
{{#if success_msg}}
<div class="alert alert-success">{{success_msg}}</div>
{{/if}}
{{#if error_msg}}
<div class="alert alert-danger">{{error_msg}}</div>
{{/if}}
七.路由管理
目前,我們所有的中介軟體的使用和路由的設定都在app.js中,這樣的話就導致整個app.js檔案顯得臃腫,而且之後可能還有新的路由設定,因此我們需要對路由進行管理。頂級express物件具有建立新的router物件的功能,這個新的router物件可以用來幫助我們實現路由管理。
app.js
const app = express();
const idea = require('./routes/idea');
//使用idea routes
//這裡的/表示根目錄,之後的router.get(/idea)都是在這個根目錄下進行組合的
app.use('/',idea);
app.use('/',idea)表示所有的/下面的路由都在idea中進行管理(idea是一個迷你路由router)
idea.js
const express = require('express');
const router = express.Router();
//新增課程
router.get('/ideas/add',(req,res) => {
res.render('ideas/add')
});
module.exports = router;
通過express.Router()建立一個新的router物件,用來代替app管理指定路徑下(/)的所有路由
八.註冊頁面的實現##
|---routes
|---user.js
//註冊頁面
router.get('/users/register',(req,res) => {
res.render('users/register.handlebars')
});
//註冊
router.post('/users/register',urlencodedParser,(req,res) => {
console.log(res.body);
res.send('註冊成功')
});
register.handlebars
<form action="/users/register" method="POST">
<div class="form-group">
<label for="name">使用者名稱</label>
<input type="text" class="form-control" name="name" required>
</div>
<div class="form-group">
<label for="email">郵箱</label>
<input type="email" name="email" class="form-control" required>
</div>
<div class="form-group">
<label for="password">密碼</label>
<input type="password" name="password" class="form-control" required>
</div>
<div class="form-group">
<label for="password2">確認密碼</label>
<input type="password" name="password2" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">註冊</button>
</form>
1.登錄檔單錯誤資訊後臺處理
只要是設計到表單的提交,通常都需要進行錯誤處理,比如密碼驗證,密碼長度處理等。
router.post('/users/register',urlencodedParser,(req,res) => {
const errors = [];
if(req.body.password !== req.body.password2){
errors.push({text:'兩次輸入的密碼不一致'})
}
if(req.body.password.length < 4){
errors.push({text:'密碼長度不能小於4位'})
}
if(errors.length > 0){
res.render('users/register',{
name:req.body.name,
email:req.body.email,
password:req.body.password,
password2:req.body.password2,
errors:errors
})
}
});
2.如果沒有錯誤,儲存到資料庫中
if(errors.length > 0){
res.render('users/register',{
name:req.body.name,
email:req.body.email,
password:req.body.password,
password2:req.body.password2,
errors:errors
})
}else{
// 如果沒有錯就儲存到資料庫中
const newUser = {
name:req.body.name,
email:req.body.email,
password:req.body.password
}
new User(newUser)
.save()
.then(() => {
res.redirect('/ideas');
})
}
3.儲存到資料庫之前,同樣需要驗證使用者名稱,郵箱等是否已經註冊過了
//驗證郵箱是否存在
User.find({email:req.body.email})
.then((user) =>{
if(user.length > 0){
req.flash('error_msg',"使用的郵箱已註冊,請使用新的郵箱")
res.redirect('/users/register');
}else{
//驗證使用者名稱是否存在
User.find({name:req.body.name})
.then((user) =>{
if(user.length > 0){
req.flash('error_msg',"使用者名稱已存在,請使用其他的使用者名稱")
res.redirect('/users/register');
}else{
const newUser = {
name:req.body.name,
email:req.body.email,
password:req.body.password
}
new User(newUser)
.save()
.then(() => {
req.flash('success_msg','註冊成功');
res.redirect('/ideas');
})
}
})
}
})
4.加密操作
使用者註冊時,密碼儲存到資料庫一定是明文的,而需要進行一定的加密。這裡使用bcrypt進行加密。
安裝:
npm i bcrypt
使用
const newUser =new User({
name:req.body.name,
email:req.body.email,
password:req.body.password
});
const saltRounds = 10;//加密強度
const myPlaintextPassword = req.body.password;//加密物件
bcrypt.genSalt(saltRounds, function(err, salt) {
bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
newUser.password = hash;
newUser.save()
.then(() => {
req.flash('success_msg','註冊成功');
res.redirect('/ideas');
})
});
});
加密後的密碼為:
"password" : "$2b$10$CRirGkbEvmwNbfBpx21Uyesju3MWyb9oU432dNFPAwvW5C9H8KzqW }
九.登陸
登陸時,首先通過使用者名稱或者郵箱從資料庫中查詢使用者,如果使用者存在則進行密碼驗證。
router.post('/users/login',urlencodedParser,(req,res) => {
// 從資料庫中通過使用者名稱或者郵箱進行查詢,如果有這個使用者且密碼正確則進行登陸
User.findOne({email:req.body.email})
.then((user) => {
if(user){
// 驗證密碼
bcrypt.compare(req.body.password, user.password, function(err, isMatch) {
// res == true
if(isMatch){
res.redirect('/ideas');
}else{
req.flash('error_msg',"您輸入的密碼不正確");
res.redirect('/users/login')
}
});
}
})
});
使用者登陸狀態的持久化
使用者登陸成功以後,在退出之前應該都是登陸狀態,這需要passport模組來幫助我們實現。同時,登陸,註冊等提示應該消失。而且是在所有的頁面消失,因此我們需要一個全域性的變數來控制它。這就是app.locals.user。app.locals在整個應用生命週期內都是有效的,但是這裡的app必須是app.js中唯一的那一個,不能是在一個檔案內建立的新的app。
關於passport的使用可以檢視passport
1.使用passport進行登陸驗證和持久話
app.js中
const app = express();
const passport = require('passport');
app.use(passport.initialize());
app.use(passport.session());
//passport持久資料時涉及到session,需要對session進行序列化和反序列化。(同時需要安裝session等npm)
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
//定義驗證的策列
passport.use(new LocalStrategy(
{usernameField:"email"}, //驗證物件改為email
function(email, password, done) {
User.findOne({ email: email })
.then((user) => {
if(!user){
return done(null,false,{message:'沒有該使用者'});
}else{
//使用者存在密碼驗證
bcrypt.compare(password,user.password, (err, isMatch) => {
if(err){
throw err;
}else{
if(isMatch){
app.locals.user = true;
return done(null,user)
}else{
return done(null,false,{message:'密碼錯誤'});
}
}
});
}
})
}
));
在登陸時進行驗證
router.post('/users/login',urlencodedParser,(req,res,next) => {
// passport進行登陸驗證
passport.authenticate('local', {
successRedirect:'/ideas',
failureRedirect: '/users/login',
failureFlash: true //是否使用flash進行提示,如果使用需要定義res.locals.error
})(req, res, next);
});
2.使用app.locals.user進行狀態的控制
從app.js中引入app
const app = require('../app.js').app;
驗證密碼通過以後,通過app.locals.user = true;來持久登陸狀態
router.post('/users/login',urlencodedParser,(req,res) => {
// 從資料庫中通過使用者名稱或者郵箱進行查詢,如果有這個使用者且密碼正確則進行登陸
User.findOne({email:req.body.email})
.then((user) => {
if(user){
// 驗證密碼
bcrypt.compare(req.body.password, user.password, function(err, isMatch) {
// res == true
if(isMatch){
app.locals.user = true;
req.flash('success_msg','登陸成功');
res.redirect('/ideas');
}else{
req.flash('error_msg',"您輸入的密碼不正確");
res.redirect('/users/login')
}
});
}
})
});
handlebars檔案中通過這個變數控制登陸和註冊的顯示:
<ul class="navbar-nav ml-auto">
{{#if user}}
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" id="navbarDropdownMenuLink">想學的課程</a>
<div class="dropdown-menu">
<a href="/ideas" class="dropdown-item">Idea</a>
<a href="/ideas/add" class="dropdown-item">新增</a>
</div>
</li>
<li class="nav-item">
<a href="/users/logout" class="nav-link">退出</a>
</li>
{{else}}
<li class="nav-item">
<a href="/users/login" class="nav-link">登入</a>
</li>
<li class="nav-item">
<a href="/users/register" class="nav-link">註冊</a>
</li>
{{/if}}
</ul>
十.登出登陸
登出時,需要清理許多使用者資訊,這裡使用passport來幫助我們進行登出。
安裝
npm i pssport
使用:通過req.logout來實現登出,登出時需要將持久的變數設定為false。變成未登陸狀態。
router.get('/users/logout',(req,res) => {
req.logout();
app.locals.user = false;
req.flash('success_msg',"退出登陸成功");
res.redirect('/users/login')
});
十一.導航守衛
在沒有進行登陸時,使用者應該不能訪問任何頁面。也就是說永不能通過輸入網址進行頁面訪問。也就是說我們需要對所有的get請求進行守衛。這裡同樣需要用到passport模組通過自定義中介軟體來實現。
|---helpers
|---auth.js
module.exports = {
ensureAuthenticated:(req,res,next) => {
if(req.isAuthenticated()){
return next();
}else{
req.flash('error_msg',"請先登陸");
res.redirect('/users/login');
}
}
}
上面使用的req.isAuthenticated必須先安裝passport模組才能夠使用。
|---router
|---idea.js
//導航守衛
const {ensureAuthenticated} = require('../helpers/auth');
//新增課程
router.get('/ideas/add',ensureAuthenticated,(req,res) => {
res.render('ideas/add')
});
//查
router.get('/ideas',ensureAuthenticated,(req,res) => {
Idea.find({})
.then((idea)=>{
res.render('ideas/index',{
ideas:idea
})
})
});
在路由時,第二個引數時是導航守衛的中介軟體。
十二.資料的管理
每一個使用者對應有自己的課程,也只能對自己的課程進行編輯和刪除。因此需要對使用者的資料進行管理。否則的話,無論什麼人進行什麼操作都會影響到其他的人的課程。
解決辦法:在每次新增課程時,把使用者的資訊新增進去。
1.新增使用者欄位
model/Idea.js
const IdeaSchema = new Schema({
title:{
type:String,
required:true
},
details:{
type:String,
required:true
},
//把使用者的資訊新增進去
user:{
type:String,
required:true
}
});
router/idea.js:將user:req.user.id新增到資料庫中。
//新增到資料庫
const newCourse = {
title:req.body.title,
details:req.body.details,
user:req.user.id
};
new Idea(newCourse)
.save()
.then(()=>{
res.redirect('/ideas');
})
觀察資料庫中的結果:多了一個user欄位
{ "_id" : ObjectId("5bf90f0ae5436e489cb3e29c"), "details" : "html" }
{ "_id" : ObjectId("5bf9139ec99ee92c70e3dc4f"), "details" : "test", "user" : "5bf904946607d339d8b8a30b" }
2.每次檢視時,都通過這個user欄位來進行篩選,只能檢視具有這個欄位的使用者資訊
//增加了篩選條件{user:req.user.id}
router.get('/ideas',ensureAuthenticated,(req,res) => {
Idea.find({user:req.user.id})
.then((idea)=>{
res.render('ideas/index',{
ideas:idea
})
})
});
3.編輯
如果我們每次進入一個賬號先獲取到他的url,然後再使用另外一個賬號進行登陸,這樣的話還是能夠進行操作的。因此這裡也需要進行設定。必須驗證他的user和req.user.id是否相同。
//跳轉到編輯頁面
router.get('/ideas/edit/:id',ensureAuthenticated,(req,res) => {
Idea.findOne({_id:req.params.id})
.then((idea) => {
// 增加user和請求的id的驗證
if(idea.user !== req.user.id ){
req.flash('error_msg','非法操作');
res.redirect('/ideas')
}else{
res.render('ideas/edit',{
title:idea.title,
details:idea.details,
id:idea._id
})
}
})
});
相關文章
- 學生資訊管理系統課程設計
- 資料庫課程設計-宿舍管理系統資料庫
- 學生成績管理系統——課程設計報告
- javaweb課程設計之XXX管理系統JavaWeb
- 【C++課程設計】通訊錄管理系統C++
- 資料結構課程設計-宿舍管理系統資料結構
- C++課程設計:學生資訊管理系統C++
- 【管理系統課程設計】美少女手把手教你後臺管理
- 資料結構課程設計——學生資訊管理系統資料結構
- 【C語言課程設計】學生學籍管理系統C語言
- 做好用的課程管理報名系統
- Java圖書管理系統,課程設計必用(原始碼+文件)Java原始碼
- 作業系統課程設計感受作業系統
- 課程排課系統:智慧排課+線上約課+直播上課+作業打卡!
- 資料結構 課程設計 員工管理系統(C語言)資料結構C語言
- 《資料庫系統原理》課程筆記資料庫筆記
- 作業系統課程實踐報告作業系統
- 課堂管理系統;線上教輔平臺;java課設Java
- MIT 6.824 分散式系統課程第四課:主備複製MIT分散式
- 北航OS課程筆記--二、系統引導筆記
- 北航OS課程筆記--七、檔案系統筆記
- Java作業系統課設之模擬程式管理系統Java作業系統
- OCP課程23:管理Ⅰ之資料庫體系結構資料庫
- 資料庫課程設計—超市零售資訊管理系統(Python實現)資料庫Python
- RHCE課程-系統管理部分|5、linux的遠端登陸,telnet.sshLinux
- OCP課程27:管理Ⅰ之管理ASM例項ASM
- 雲端計算髮展前景,linux系統課程Linux
- C語言課程訓練系統題-字串cquptC語言字串
- 為什麼要學習作業系統課程?作業系統
- 資料庫課設(校友錄資訊管理系統)資料庫
- OCP課程54:管理II之管理記憶體記憶體
- 實驗課程名稱:資料庫系統概論資料庫
- 5分鐘課程:物聯網的系統設計
- 為什麼要學習嵌入式系統課程?
- Java專案實戰(企業人事管理系統)-李興華-專題視訊課程Java
- MIT 6.824 分散式系統課程第二課:RPC 和多執行緒MIT分散式RPC執行緒
- 北航OS課程筆記--六、磁碟管理筆記
- 順通高校學生網上選課管理系統