登录后页面如图:

点击“删除”按钮:

点击“确定”,跳转到一个新页面(不知道为什么不能正常删除):

返回之前的留言板页面,留言没被删除:

点击修改按钮:

直接从地址栏输入'/update':

代码:
controllers层:
const router = require("../routes/guestBookRoutes");
const guestbookDAO = require("../models/guestbookModel");
const userModel = require("./user"); // 引入user.js
const dbDAO = new guestbookDAO("guestbook.db");
//调用model层的constructor方法(构造函数)
// 创建了一个实例对象dbDAO,用于操作嵌入式数据库。
// const dbDAO = new guestbookDAO();
// 创建了一个实例对象dbDAO,用于操作嵌入式数据库。
exports.landing_page = function (req, res) {
res.send("Hello! Welcome to my application!");
// dbDAO.init();
//初始化数据库,即在内存中创建一个空的数组。
};
exports.entries_list = function (req, res) {
dbDAO
.getAllEntries()
.then((entries) => {
res.render("entries", {
title: "Guestbook",
entries: entries.map((entry) => ({
...entry,
isCurrentUser: req.user && req.user.user === entry.author, // 判断是否是当前用户
})),
user: req.user, // 当前登录用户
});
})
.catch((err) => {
console.error("获取留言失败:", err);
res.status(500).send("服务器错误");
});
};
exports.peter_entries = function (req, res) {
res.send("<h1>Processing Peter's Entries, see terminal</h1>");
dbDAO
.getPetersEntries()
.then((list1) => {
//then拿到promise对象,list是resolve的值,也就是数据库中的所有记录。
res.render("entries_p", {
//渲染名为entries的视图
title: "Peter's Guest Book",
//要渲染的内容,key:value形式,
//这里的key是mustache文件的title,value是每个循环中的内容,
// 也就是想显示的内容。
entries_p: list1,
//这里的entries要和mustache文件中的{{#entries}}对应起来。
//这里的list是数据库中的所有记录,要和mustache文件中的{{entries}}对应起来。
});
})
.catch((err) => {
console.log("err:", err);
});
};
exports.show_new_entry = function (req, res) {
res.render("newEntry", { title: "Post a new entry" });
};
exports.post_new_entry = function (req, res) {
const { subject, contents } = req.body;
if (!req.user) {
return res.status(403).send("请先登录后再添加留言");
}
const newEntry = {
subject,
contents,
published: new Date().toISOString().split("T")[0],
author: req.user.user,
};
dbDAO
.addEntry(newEntry)
.then(() => {
res.redirect("/guestbook");
})
.catch((err) => {
console.error("添加留言失败:", err);
res.status(500).send("服务器错误");
});
};
exports.show_login_page = function (req, res) {
res.render("login", {
error: req.flash("error"),
});
};
exports.show_user_entries = function (req, res) {
let user = req.params.author; // URL:后的内容,是个形参
dbDAO
.getUserEntries(user)
.then((list) => {
//then拿到promise对象,list是resolve的值,也就是数据库中的所有记录。
res.render("entries", {
//渲染名为entries的视图
title: user + " Guestbook",
//要渲染的内容,key:value形式,
//这里的key是mustache文件的title,value是每个循环中的内容,
// 也就是想显示的内容。
entries: list,
//这里的entries要和mustache文件中的{{#entries}}对应起来。
//这里的list是数据库中的所有记录,要和mustache文件中的{{entries}}对应起来。
});
})
.catch((err) => {
console.log("err:", err);
}); //catch在then后面,捕获then中的错误。
};
exports.show_update_entry = function (req, res) {
const id = req.params.id;
dbDAO
.getEntryById(id)
.then((entry) => {
if (!entry) {
return res.status(404).send("留言不存在");
}
res.render("updateEntry", { title: "Update Entry", entry });
})
.catch((err) => {
console.error("获取留言失败:", err);
res.status(500).send("服务器错误");
});
};
exports.update_entry = function (req, res) {
const id = req.params.id;
const { subject, contents } = req.body;
dbDAO
.updateEntry(id, req.user.user, subject, contents)
.then(() => {
res.redirect("/guestbook");
})
.catch((err) => {
console.error("更新留言失败:", err);
res.status(500).send("服务器错误");
});
};
exports.delete_entry = function (req, res) {
const id = req.params.id;
if (!req.user) {
return res.status(403).send("请先登录后再删除留言");
}
dbDAO
.getEntryById(id)
.then((entry) => {
if (!entry) {
// 留言不存在
return Promise.reject({ message: "留言不存在", status: 404 });
}
if (entry.author !== req.user.user) {
// 用户无权限
return Promise.reject({ message: "您无权删除此留言", status: 403 });
}
// 删除留言
return dbDAO.removeEntry(id);
})
.then((numRemoved) => {
if (numRemoved > 0) {
// 删除成功
res.redirect("/guestbook");
} else {
// 删除失败
return Promise.reject({ message: "删除失败,请稍后再试", status: 500 });
}
})
.catch((err) => {
if (err.status) {
res.status(err.status).send(err.message);
} else {
console.error("删除留言失败:", err);
res.status(500).send("服务器错误");
}
});
};
// exports.new_entry = function (req, res) {
// res.send("<h1>Not yet implemented:show a new entry page.</h1>");
// dbDAO.init();
// };
// exports.show_user_entries;
exports.about_page = function (req, res) {
// 返回位于public文件夹下的about.html文件
res.redirect("about.html");
//直接重定向,app.use(express.static(public));的作用
dbDAO.init();
};
exports.image = function (req, res) {
// 返回位于public/img/logo.jpg的图片文件
res.redirect("logo.jpg");
dbDAO.init();
};
exports.status_404 = function (req, res) {
res.status(404); // 设置HTTP状态码为404
res.send("Oops! Page not found."); // 返回错误提示
};
exports.status_500 = function (err, req, res, next) {
console.log("error:", err);
res.status(500);
res.type("text/plain");
res.send("Internal Server Error");
};
exports.show_register_page = function (req, res) {
res.render("register", {
error: req.flash("error"),
});
};
exports.register_user = function (req, res) {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).send("用户名和密码为必填项");
}
const userExists = userModel.authenticate(username);
if (userExists) {
return res.status(400).send("用户名已存在");
}
userModel.addUser(username, password);
res.redirect("/login");
};
model层:
const nedb = require("@seald-io/nedb"); //nedb是一个变量
class GuestBook {
constructor(dbFilePath) {
if (dbFilePath) {
this.db = new nedb({ filename: dbFilePath, autoload: true });
//db是该对象的一个属性,用来存储该数据库的引用信息。
console.log("DB created:" + dbFilePath);
} else {
this.db = new nedb();
}
}
init() {
this.db.insert(
{
subject: "I liked the exhibition",
contents: "nice",
published: "2020-02-16",
author: "Peter",
},
function (err, newDoc) {
if (err) {
console.log("Error:" + err);
} else {
console.log("Peter inserted!");
}
}
);
this.db.insert(
{
subject: "Didn't like it",
contents: "A really terrible style!",
published: "2020-02-18",
author: "Ann",
},
function (err, newDoc) {
if (err) {
console.log("Error:" + err);
} else {
console.log("Ann inserted!");
}
}
);
}
addEntry(author_value, subject_value, contents_value) {
this.db.insert(
{
subject: subject_value,
contents: contents_value,
published: new Date().toISOString().split("T")[0],
author: author_value,
},
function (err, newDoc) {
if (err) {
console.log("Error:" + err);
} else {
console.log("日期为:" + new Date().toISOString());
console.log("addEntry inserted!");
}
}
);
}
getAllEntries() {
//return a Promise object, which can be resolved or rejected
return new Promise((resolve, reject) => {
//Promise对象可以记录异步操作是成功还是失败
//resolve记录成功时的状态,reject记录失败时的报错信息,
// 同时变化了Promise的状态。
//避免回调地狱,用Promise代替回调函数。
this.db.find({}, function (err, entries) {
if (err) {
reject(err);
console.log("getAllEntries Promise rejected:" + err);
} else {
resolve(entries);
console.log("getAllEntries Promise resolved: ", entries);
}
});
});
}
getPetersEntries() {
return new Promise((resolve, reject) => {
this.db.find({ author: "Peter" }, function (err, entries_p) {
if (err) {
reject(err);
console.log("getPetersEntries Promise rejected:" + err);
} else {
resolve(entries_p);
console.log("getPetersEntries Promise resolved:", entries_p);
}
});
});
}
getUserEntries(userName) {
return new Promise((resolve, reject) => {
this.db.find({ author: userName }, function (err, entries_u) {
if (err) {
reject(err);
console.log("getUserEntries Promise rejected:" + err);
} else {
resolve(entries_u);
console.log("getUserEntries Promise resolved:", entries_u);
}
});
});
}
updateEntry(id, author, subject, contents) {
return new Promise((resolve, reject) => {
this.db.update(
{ _id: id },
{ $set: { author, subject, contents } },
{},
function (err, numUpdated) {
if (err) {
reject(err);
} else {
resolve(numUpdated);
}
}
);
});
}
getEntryById(id) {
return new Promise((resolve, reject) => {
this.db.findOne({ _id: id }, (err, entry) => {
if (err) {
reject(err);
} else {
resolve(entry); // 返回找到的留言条目或 null
}
});
});
}
removeEntry(id) {
return new Promise((resolve, reject) => {
this.db.remove({ _id: id }, {}, (err, numRemoved) => {
if (err) {
reject(err);
} else {
resolve(numRemoved); // 返回删除的条目数量
}
});
});
}
}
module.exports = GuestBook;
router层:
const express = require("express");
const passport = require("passport"); // 导入 Passport
const controllers = require("../controllers/guestbookControllers");
const auth = require("../controllers/auth");
const router = express.Router();
router.get("/", controllers.landing_page);
router.get("/guestbook", controllers.entries_list);
router.get("/peter", controllers.peter_entries);
router.get("/new", controllers.show_new_entry); // 显示新留言表单
router.post("/new", controllers.post_new_entry); // 处理新留言提交
router.get("/login", controllers.show_login_page);
router.post(
"/login",
passport.authenticate("local", {
successRedirect: "/guestbook",
failureRedirect: "/login",
failureFlash: true, // 启用 flash 消消息
})
);
router.get("/posts/:author", controllers.show_user_entries);
router.get("/update/:id", controllers.show_update_entry); // 显示修改表单
router.post("/update/:id", controllers.update_entry); // 处理修改提交
router.post("/delete/:id", controllers.delete_entry); // 处理删除操作
router.get("/register", controllers.show_register_page);
router.post("/register", controllers.register_user);
module.exports = router; // 导出路由
views层:entries.mustache:
<html lang="en">
<head>
{{>header}}
</head>
<body class="container">
<h1>{{title}}</h1>
{{#user}}
<p>Welcome, {{user.user}}!</p>
<div class="text-right">
<a href="/new" class="btn btn-primary">添加新留言</a>
</div>
{{/user}}
{{^user}}
<div class="text-right">
<a href="/login" class="btn btn-primary">登录</a>
<a href="/register" class="btn btn-secondary">注册</a>
</div>
{{/user}}
{{#entries}}
<div class="card">
<div class="card-header">
{{subject}}
</div>
<div class="card-body">
<div class="card-text">
{{contents}}
</div>
<div class="card-text">
Written by <a href="/posts/{{author}}">{{author}}</a>, on {{published}}
{{#user}}
{{#isCurrentUser}}
<br>
<div class="btn-group" role="group"> <!-- 使用btn-group实现按钮水平排列 -->
<a href="/update/{{_id}}" class="btn btn-primary" style="height:80%">修改</a>
<form action="/delete/{{_id}}" method="post" style="display: inline;" onsubmit="return confirm('确定要删除这条留言吗?');">
<button type="submit" class="btn btn-danger">删除</button>
</form>
</div>
{{/isCurrentUser}}
{{/user}}
</div>
</div>
</div>
{{/entries}}
{{^entries}}
<p>No entries yet!</p>
{{/entries}}
</body>
</html>