当前位置:   article > 正文

10分钟学习如何使用 Express+Mysql开发图书管理系统_数据库设置一个图书管理系统

数据库设置一个图书管理系统

阅读前必读

1分钟学会使用Express+MySQL实现注册功能

在1分钟内,使用 Express 快速编写一个简单的登录功能。

Express EJS渲染技术详解

在开始之前,请确保已在您的电脑上安装了 Node.js 和 npm和MySQL,并且它们的版本分别为:

  • node v16.14.2
  • npm 8.5.0
  • mysql 5.7

开发工具:Visual Studio Code(不唯一,也可以是其他开发工具)。


前方高能,可能阅读需要10分钟

摘要

        本文将介绍如何使用 Express 框架和 MySQL 数据库来搭建一个简单而实用的图书管理系统。通过这个系统,用户可以进行图书的增删改查操作,并能够实现基本的用户权限管理。本文将详细介绍系统的需求分析、数据库设计、后端接口的开发与测试等方面,并附上相关的代码示例。

一、需求分析

        在开始系统的开发之前,我们首先需要进行需求分析,明确系统的功能和特性。本文将提出以下几个基本需求:

管理员登录:系统应该支持管理员登录。

用户的增删改查:系统应该提供用户的增加、删除、修改和查询功能,以方便管理员对用户信息的管理。

图书的增删改查:系统应该提供图书的增加、删除、修改和查询功能,以方便管理员对图书信息的管理。

借阅和归还管理:系统应该支持用户借阅图书和归还图书的管理,包括借阅记录的保存和查询。

系统功能结构图 

二、数据库设计

我将介绍如何设计一个简单而有效的关系型数据库模型,以满足管理员登录,以及图书的增删改查,借阅和归还管理等功能的需求。

2.1 数据表设计

首先,我们需要设计几个核心的数据表来存储系统的数据:

用户表(users):用于存储用户的登录信息,包括用户ID、用户名和密码等字段。

图书表(books):用于存储图书的相关信息,包括图书ID、名称、作者和出版社等字段。

借阅记录表(borrow_records):用于记录用户借阅图书的情况,包括记录ID、用户ID、图书ID、借阅日期和归还日期等字段。

2.2 数据表创建

首先我们在MySQL中创建一个名为 “book_library”的数据库。

CREATE DATABASE book_library;

再打开“book_library” 数据库

USE book_library;

接下来,我们在“book_library”数据库中创建这些数据表。

创建用户表(users):

  1. CREATE TABLE users (
  2. id INT AUTO_INCREMENT PRIMARY KEY,
  3. username VARCHAR(50) NOT NULL,
  4. password VARCHAR(50) NOT NULL
  5. );

创建图书表(books):

  1. CREATE TABLE books (
  2. id INT AUTO_INCREMENT PRIMARY KEY,
  3. title VARCHAR(100) NOT NULL,
  4. author VARCHAR(50),
  5. publisher VARCHAR(50)
  6. );

创建管理员表(admin):

  1. CREATE TABLE admin (
  2. id INT AUTO_INCREMENT PRIMARY KEY,
  3. username VARCHAR(50) NOT NULL,
  4. password VARCHAR(50) NOT NULL,
  5. nick_name VARCHAR(50) NOT NULL
  6. );

插入管理员数据:

INSERT INTO admin (username, password, nick_name) VALUES ('root', 'root', '管理员');

创建借阅记录表(borrow_records):

  1. CREATE TABLE borrow_records (
  2. id INT AUTO_INCREMENT PRIMARY KEY,
  3. user_id INT NOT NULL,
  4. book_id INT NOT NULL,
  5. borrow_date DATE NOT NULL,
  6. return_date DATE
  7. );

三、后端接口的开发

3.1 项目准备工作

3.1.1 创建项目目录

首先我们创建一个名为“book-server”的项目目录。

3.1.2 打开项目目录

将创建好的 “book-server”文件夹拖动到vscode图标进行打开(也可以通过其他方式打开)。

3.1.2 初始化项目

我们在终端输入 "npm init -y" 命令对项目进行初始化。

npm init -y

我们成功初始化项目后,项目目录将会生成一个重要的文件“package.json”。 

3.1.3 安装框架包

 在项目终端使用以下下命令可以快速安装框架包:

  1. Express:Node.js Web应用程序框架
npm install express

     2.Express-session:处理会话(Session)的中间件

npm install express-session

     3.ejs:模版引擎

npm install ejs

     4.mysql: MySQL数据库进行交互的软件包

npm install mysql

     5.nodemon:热部署(无需手动重启服务器)

npm install nodemon

下载成功后,你会发现在项目目录下出现了一个名为 node_modules 的文件夹和一个名为 package-lock.json 的文件。

 3.1.3 创建项目结构目录

在项目的根目录下创建以下目录文件夹:

  1. public 文件夹: 用于存放静态资源文件,例如 CSS、JavaScript 和图像文件。

     2.routes 文件夹: 用于存放路由文件。在 Express 中,路由用于定义不同 URL 路径的处理逻辑。

  3.utils 文件夹:用于存放实用工具函数或模块。

    4.views 文件夹:用于存放视图文件,通常是使用模板引擎生成动态 HTML 页面的模板文件。

 

3.2 编写后端代码

3.2.1 创建入口文件 app.js

在您的项目的根目录下,创建一个名为app.js的文件。

3.2.1.1 引入模块
  1. const express = require("express");
  2. const bodyParser = require("body-parser");
  3. const session = require("express-session");
  4. const path = require("path");

3.2.1.2 创建express应用程序

express() 函数是 Express 框架的顶级函数,调用它会返回一个 Express 应用程序实例。

const app = express();

3.2.1.3 注册解析请求体

body-parser 中间件来解析 POST 请求的请求体,并将解析后的数据添加到 req.body 对象中。

app.use(bodyParser.urlencoded({ extended: false }));

3.2.1.4 注册会话

session 方法接受一个对象作为参数,其中 secret 是用于对会话数据进行加密的密钥,resave 和 saveUninitialized 分别指定是否在每次请求中重新保存会话和是否自动初始化未经过初始化的会话。

app.use(session({ secret: "secret", resave: false, saveUninitialized: true }));

3.2.1.5 注册静态资源目录

所有放置在名为 "public" 的文件夹中的静态文件(例如 CSS、JavaScript 和图像)可以通过相对路径直接从客户端浏览器访问。

app.use(express.static("public"));

3.2.1.5 设置ejs模版引擎

当我们在路由中使用 res.render 方法来渲染页面时,Express 将使用 EJS 引擎来编译和渲染模板文件。

app.set("view engine", "ejs");

3.2.1.6 指定视图文件夹路径

将模板文件夹设置为应用程序所在的根目录下的 views 文件夹。

app.set("views", path.join(__dirname, "views"));

 3.2.1.7 配置项目监听端口

app.listen() 用于启动 HTTP 服务器的方法。

  1. app.listen(8080, () => {
  2. console.log("http://127.0.0.1:8080");
  3. });

3.2.1.8 简单测试

在终端中输入以下命令来启动项目

nodemon app.js

3.3 创建数据库工具

为了与数据库进行交互,我们需要在应用程序中使用相应的数据库驱动程序,并编写代码来处理与数据库的连接等操作。

在utils目录文件夹下创建 “db.js”文件,并编写以下内容:

  1. const mysql = require("mysql");
  2. // 创建数据库连接
  3. const connection = mysql.createConnection({
  4. host: "localhost",
  5. user: "你的数据库用户名",
  6. password: "你的数据库密码",
  7. database: "book_library",
  8. });
  9. // 连接数据库
  10. connection.connect();
  11. module.exports = connection;

我们将要将数据库的user和password替换成你的数据库用户名与密码。

user: "你的数据库用户名",
password: "你的数据库密码",

在utils目录文件夹下创建 “formattedDate.js”文件,主要用于生成格式化日期,并编写一下内容:

  1. const currentDate = new Date();
  2. const year = currentDate.getFullYear();
  3. const month = String(currentDate.getMonth() + 1).padStart(2, '0');
  4. const day = String(currentDate.getDate()).padStart(2, '0');
  5. const formattedDate = `${year}-${month}-${day}`;
  6. module.exports = formattedDate;

 

3.4 创建路由

3.4.1 创建页面路由文件

本路由模块用于定义不同路径的处理函数。

在routes目录文件夹下创建一个名为 “pageRoutes.js” 文件。

路由模块使用了 express.Router() 方法实例化一个路由器对象

  1. const express = require("express");
  2. const router = express.Router();

 因为接口中有数据库查询引入了 db.js工具

const db = require('../utils/db');

当客户端访问根路径时,res.redirect()函数是 重定向到登录页面。

  1. router.get("/", (req, res) => {
  2. res.redirect("/login");
  3. });

当客户端访问登录页面时,渲染包含登录表单的视图, login是我们的login.ejs页面,在后面将会编写。。

  1. router.get("/login", (req, res) => {
  2. res.render("login");
  3. });

当客户端访问添加用户界面时,渲染包含用户信息表单的视图。

  1. router.get("/page_user", (req, res) => {
  2. res.render("add_user");
  3. });

当客户端访问添加图书界面时,渲染包含图书信息表单的视图。

  1. router.get("/page_book", (req, res) => {
  2. res.render("add_book");
  3. });

当客户端访问借阅图书界面时,从数据库中获取所有用户和图书的信息,并将其传递到包含借阅记录表单的视图中进行渲染。

这里用到了两个sql查询语句:

查询全部用户的id和username

SELECT id, username FROM users

 查询全部图书的id和title

SELECT id, title FROM books

 usersResult 是从数据库中查询到的用户信息数组,每个用户信息对象包含了 idusername 两个属性。然后,通过 map() 方法对 usersResult 数组进行遍历,返回一个新的对象数组。、

usersResult.map(user => ({ id: user.id, name: user.username }));

这段代码的作用是将一个名为 add_record 的模板文件渲染成 HTML,并将数据 usersbooks 注入到模板中。

res.render('add_record', { users, books });
  1. router.get('/page_record', (req, res) => {
  2. const sqlUsers = 'SELECT id, username FROM users';
  3. const sqlBooks = 'SELECT id, title FROM books';
  4. db.query(sqlUsers, (err, usersResult) => {
  5. if (err) throw err;
  6. const users = usersResult.map(user => ({ id: user.id, name: user.username }));
  7. db.query(sqlBooks, (err, booksResult) => {
  8. if (err) throw err;
  9. const books = booksResult.map(book => ({ id: book.id, title: book.title }));
  10. res.render('add_record', { users, books });
  11. });
  12. });
  13. });
module.exports = router;
pageRoutes.js 文件整体代码:
  1. const express = require("express");
  2. const router = express.Router();
  3. const db = require('../utils/db');
  4. router.get("/", (req, res) => {
  5. res.redirect("/login");
  6. });
  7. router.get("/login", (req, res) => {
  8. res.render("login");
  9. });
  10. /**
  11. * 添加用户界面
  12. */
  13. router.get("/page_user", (req, res) => {
  14. res.render("add_user");
  15. });
  16. /**
  17. * 添加图书界面
  18. */
  19. router.get("/page_book", (req, res) => {
  20. res.render("add_book");
  21. });
  22. /**
  23. * 借阅图书界面
  24. */
  25. router.get('/page_record', (req, res) => {
  26. const sqlUsers = 'SELECT id, username FROM users';
  27. const sqlBooks = 'SELECT id, title FROM books';
  28. db.query(sqlUsers, (err, usersResult) => {
  29. if (err) throw err;
  30. const users = usersResult.map(user => ({ id: user.id, name: user.username }));
  31. db.query(sqlBooks, (err, booksResult) => {
  32. if (err) throw err;
  33. const books = booksResult.map(book => ({ id: book.id, title: book.title }));
  34. res.render('add_record', { users, books });
  35. });
  36. });
  37. });
  38. module.exports = router;

3.4.2 创建登录和退出路由文件

本路由模块用于实现用户登录和退出登录的处理函数。

在routes目录文件夹下创建一个名为 “loginRoutes.js” 文件。

引入模块

  1. const express = require("express");
  2. const router = express.Router();
  3. const connection = require("../utils/db");

用户登录函数:当用户提交登录表单时,Express 会将表单数据封装在 req.body 中,其中包括用户名和密码。

const { username, password } = req.body;

查询用户名和密码是否相同的用户

SELECT * FROM admin WHERE username = '' AND password = ''

使用了 connection.query 方法来执行 SQL 查询。如果查询成功,则将用户信息保存在 req.session.user 中,然后重定向到 /users 路由;

  1. router.post("/", (req, res) => {
  2. const { username, password } = req.body;
  3. connection.query(
  4. "SELECT * FROM admin WHERE username = ? AND password = ?",
  5. [username, password],
  6. (error, results) => {
  7. if (error) throw error;
  8. if (results.length > 0) {
  9. req.session.user = results;
  10. res.redirect("/users");
  11. } else {
  12. res.status(401).json({ message: "账户或密码错误" });
  13. }
  14. }
  15. );
  16. });

用户退出函数: 当用户访问 /logout 路由时,会清除 req.session 中保存的用户信息,并重定向到 /login 路由。

  1. /**
  2. * 退出登录接口
  3. * @route GET /api/logout
  4. * @returns {object} 退出登录结果
  5. */
  6. router.get("/logout", (req, res) => {
  7. // 清除session
  8. req.session.destroy();
  9. res.redirect("/login");
  10. });
loginRoutes.js 整体代码
  1. const express = require("express");
  2. const router = express.Router();
  3. const connection = require("../utils/db");
  4. // 用户登录
  5. router.post("/", (req, res) => {
  6. const { username, password } = req.body;
  7. connection.query(
  8. "SELECT * FROM admin WHERE username = ? AND password = ?",
  9. [username, password],
  10. (error, results) => {
  11. if (error) throw error;
  12. if (results.length > 0) {
  13. req.session.user = results;
  14. res.redirect("/users");
  15. } else {
  16. res.status(401).json({ message: "账户或密码错误" });
  17. }
  18. }
  19. );
  20. });
  21. /**
  22. * 退出登录接口
  23. * @route GET /api/logout
  24. * @returns {object} 退出登录结果
  25. */
  26. router.get("/logout", (req, res) => {
  27. // 清除session
  28. req.session.destroy();
  29. res.redirect("/login");
  30. });
  31. module.exports = router;

3.4.3 创建用户路由文件

在routes下创建“userRoutes.js”文件, 这个文件是用来实现用户管理的增、删、改、查函数的。

查询用户

当访问 "/users" 路径时,会执行查询所有用户的 SQL 语句,并将查询结果传递给名为 "users" 的模板进行渲染,并在浏览器中显示。

  1. router.get("/", (req, res) => {
  2. connection.query("SELECT * FROM users", (error, results) => {
  3. if (error) throw error;
  4. res.render("users", { users: results }); // 渲染users.ejs模板,并传入查询结果
  5. });
  6. });

添加用户

当通过 POST 请求访问 "/users/add" 路径时,会从请求正文中获取用户名和密码,并将它们插入到数据库中。然后重定向到用户列表页面 "/users"。

  1. router.post("/add", (req, res) => {
  2. const { username, password } = req.body;
  3. console.log(username, password);
  4. connection.query(
  5. "INSERT INTO users (username, password) VALUES (?, ?)",
  6. [username, password],
  7. (error, result) => {
  8. if (error) throw error;
  9. res.redirect("/users");
  10. }
  11. );
  12. });

通过用户id查询用户信息

当访问 "/users/:id" 路径时,会根据路径参数中提供的用户 id,执行查询特定用户的 SQL 语句,并将查询结果传递给一个名为 "update_user" 的模板进行渲染,并在浏览器中显示。

  1. router.get("/:id", (req, res) => {
  2. const userId = req.params.id;
  3. connection.query(
  4. "select * from users WHERE id = ?",
  5. userId,
  6. (error, result) => {
  7. if (error) throw error;
  8. console.log(result);
  9. res.render("update_user", { user: result });
  10. }
  11. );
  12. });

修改用户

当通过 POST 请求访问 "/users/update" 路径时,会从请求正文中获取用户名、密码和用户ID,并执行更新特定用户的 SQL 语句。之后重定向到用户列表页面 "/users"。

  1. router.post("/update", (req, res) => {
  2. const { id, username, password } = req.body;
  3. connection.query(
  4. "UPDATE users SET username = ?, password = ? WHERE id = ?",
  5. [username, password, id],
  6. (error, result) => {
  7. if (error) throw error;
  8. res.redirect("/users");
  9. }
  10. );
  11. });

删除用户

当通过 GET 请求访问 "/users/del/:id" 路径时,会根据路径参数中提供的用户 id,执行删除特定用户的 SQL 语句。然后重定向到用户列表页面 "/users"。

  1. router.get("/del/:id", (req, res) => {
  2. const userId = req.params.id;
  3. connection.query(
  4. "DELETE FROM users WHERE id = ?",
  5. [userId],
  6. (error, result) => {
  7. if (error) throw error;
  8. res.redirect("/users");
  9. }
  10. );
  11. });
userRoutes.js 整体代码
  1. const express = require("express");
  2. const router = express.Router();
  3. const connection = require("../utils/db");
  4. // 查询用户
  5. router.get("/", (req, res) => {
  6. connection.query("SELECT * FROM users", (error, results) => {
  7. if (error) throw error;
  8. res.render("users", { users: results }); // 渲染users.ejs模板,并传入查询结果
  9. });
  10. });
  11. // 添加用户
  12. router.post("/add", (req, res) => {
  13. const { username, password } = req.body;
  14. console.log(username, password);
  15. connection.query(
  16. "INSERT INTO users (username, password) VALUES (?, ?)",
  17. [username, password],
  18. (error, result) => {
  19. if (error) throw error;
  20. res.redirect("/users");
  21. }
  22. );
  23. });
  24. // 通过用户id查询用户信息
  25. router.get("/:id", (req, res) => {
  26. const userId = req.params.id;
  27. connection.query(
  28. "select * from users WHERE id = ?",
  29. userId,
  30. (error, result) => {
  31. if (error) throw error;
  32. console.log(result);
  33. res.render("update_user", { user: result });
  34. }
  35. );
  36. });
  37. // 修改用户
  38. router.post("/update", (req, res) => {
  39. const { id, username, password } = req.body;
  40. connection.query(
  41. "UPDATE users SET username = ?, password = ? WHERE id = ?",
  42. [username, password, id],
  43. (error, result) => {
  44. if (error) throw error;
  45. res.redirect("/users");
  46. }
  47. );
  48. });
  49. // 删除用户
  50. router.get("/del/:id", (req, res) => {
  51. const userId = req.params.id;
  52. connection.query(
  53. "DELETE FROM users WHERE id = ?",
  54. [userId],
  55. (error, result) => {
  56. if (error) throw error;
  57. res.redirect("/users");
  58. }
  59. );
  60. });
  61. module.exports = router;

3.4.5 创建图书路由文件

在routes目录文件夹下创建 “bookRoutes.js” 文件,这个文件是用来实现图书的增、删、改、查函数的。

查询图书

当访问 "/books" 路径时,会执行查询所有图书的 SQL 语句,并将查询结果传递给名为 "books" 的模板进行渲染,并在浏览器中显示。

  1. router.get("/", (req, res) => {
  2. connection.query("SELECT * FROM books", (error, results) => {
  3. if (error) throw error;
  4. res.render("books", { books: results }); // 渲染books.ejs模板,并传入查询结果
  5. });
  6. });

添加图书

当通过 POST 请求访问 "/books/add" 路径时,会从请求正文中获取图书的标题、作者和出版商,并将它们插入到数据库中。然后重定向到图书列表页面 "/books"。

  1. router.post("/add", (req, res) => {
  2. const { title, author, publisher } = req.body;
  3. connection.query(
  4. "INSERT INTO books (title, author, publisher) VALUES (?, ?, ?)",
  5. [title, author, publisher],
  6. (error, result) => {
  7. if (error) throw error;
  8. res.redirect("/books");
  9. }
  10. );
  11. });

修改图书

当通过 POST 请求访问 "/books/update" 路径时,会从请求正文中获取图书的 ID、标题、作者和出版商,并执行更新特定图书的 SQL 语句。之后重定向到图书列表页面 "/books"。

  1. router.post("/update", (req, res) => {
  2. const { bookId, title, author, publisher } = req.body;
  3. connection.query(
  4. "UPDATE books SET title = ?, author = ?, publisher = ? WHERE id = ?",
  5. [title, author, publisher, bookId],
  6. (error, result) => {
  7. if (error) throw error;
  8. res.redirect("/books")
  9. }
  10. );
  11. });

通过图书id查询图书信息

当访问 "/books/:id" 路径时,会根据路径参数中提供的图书 id,执行查询特定图书的 SQL 语句,并将查询结果传递给一个名为 "update_book" 的模板进行渲染,并在浏览器中显示。

  1. router.get("/:id", (req, res) => {
  2. const userId = req.params.id;
  3. connection.query(
  4. "select * from books WHERE id = ?",
  5. userId,
  6. (error, result) => {
  7. if (error) throw error;
  8. res.render("update_book", { book: result });
  9. }
  10. );
  11. });

删除图书

当通过 GET 请求访问 "/books/del/:id" 路径时,会根据路径参数中提供的图书 id,执行删除特定图书的 SQL 语句。然后重定向到图书列表页面 "/books"。

  1. router.get("/del/:id", (req, res) => {
  2. const bookId = req.params.id;
  3. connection.query(
  4. "DELETE FROM books WHERE id = ?",
  5. [bookId],
  6. (error, result) => {
  7. if (error) throw error;
  8. res.redirect("/books");
  9. }
  10. );
  11. });
bookRoutes.js 整体代码
  1. const express = require("express");
  2. const router = express.Router();
  3. const connection = require("../utils/db");
  4. // 查询图书
  5. router.get("/", (req, res) => {
  6. connection.query("SELECT * FROM books", (error, results) => {
  7. if (error) throw error;
  8. res.render("books", { books: results }); // 渲染books.ejs模板,并传入查询结果
  9. });
  10. });
  11. // 添加图书
  12. router.post("/add", (req, res) => {
  13. const { title, author, publisher } = req.body;
  14. connection.query(
  15. "INSERT INTO books (title, author, publisher) VALUES (?, ?, ?)",
  16. [title, author, publisher],
  17. (error, result) => {
  18. if (error) throw error;
  19. res.redirect("/books");
  20. }
  21. );
  22. });
  23. // 修改图书
  24. router.post("/update", (req, res) => {
  25. const { bookId, title, author, publisher } = req.body;
  26. connection.query(
  27. "UPDATE books SET title = ?, author = ?, publisher = ? WHERE id = ?",
  28. [title, author, publisher, bookId],
  29. (error, result) => {
  30. if (error) throw error;
  31. res.redirect("/books")
  32. }
  33. );
  34. });
  35. // 通过图书id查询图书信息
  36. router.get("/:id", (req, res) => {
  37. const userId = req.params.id;
  38. connection.query(
  39. "select * from books WHERE id = ?",
  40. userId,
  41. (error, result) => {
  42. if (error) throw error;
  43. res.render("update_book", { book: result });
  44. }
  45. );
  46. });
  47. // 删除图书
  48. router.get("/del/:id", (req, res) => {
  49. const bookId = req.params.id;
  50. connection.query(
  51. "DELETE FROM books WHERE id = ?",
  52. [bookId],
  53. (error, result) => {
  54. if (error) throw error;
  55. res.redirect("/books");
  56. }
  57. );
  58. });
  59. module.exports = router;

3.4.6 创建借阅路由文件

在routes目录文件夹下创建“recordRoutes.js”文件,这个文件主要是对借阅图书管理的增、删、改、查的函数。

查询借阅记录

当用户访问 /records 路由时,会执行 SQL 查询语句,并将查询结果渲染到名为 "records" 的 EJS 模板中。查询结果包括借阅记录的 ID、用户名、图书标题、借阅日期和归还日期。

  1. router.get("/", (req, res) => {
  2. const sql = `
  3. SELECT borrow_records.id, users.username, books.title, DATE_FORMAT(borrow_records.borrow_date, '%Y-%m-%d') AS borrow_date, DATE_FORMAT(borrow_records.return_date, '%Y-%m-%d') AS return_date
  4. FROM borrow_records
  5. JOIN users ON borrow_records.user_id = users.id
  6. JOIN books ON borrow_records.book_id = books.id`;
  7. connection.query(sql, (error, results) => {
  8. if (error) throw error;
  9. res.render("records", { records: results }); // 渲染borrow-records.ejs模板,并传入查询结果
  10. });
  11. });

 借阅图书

 当用户提交借阅图书的表单时,Express 会将表单数据封装在 req.body 中,包括用户 ID 和图书 ID。然后,使用 connection.query 方法向数据库中的 borrow_records 表插入一条新的借阅记录,并将当前日期作为借阅日期。插入成功后,重定向到 /records 路由。

  1. router.post("/add", (req, res) => {
  2. const { userId, bookId } = req.body;
  3. connection.query(
  4. "INSERT INTO borrow_records (user_id, book_id, borrow_date) VALUES (?, ?, ?)",
  5. [userId, bookId, formattedDate],
  6. (error, result) => {
  7. if (error) throw error;
  8. res.redirect("/records");
  9. }
  10. );
  11. });

 归还图书

当用户访问 /records/update/:id 路由时,会将 URL 中的 id 参数提取出来,并将当前日期作为归还日期。然后,使用 connection.query 方法更新数据库中的 borrow_records 表中对应记录的归还日期。更新成功后,重定向到 /records 路由。

  1. router.get("/update/:id", (req, res) => {
  2. const recordId = req.params.id;
  3. const return_date = new Date().toISOString().slice(0, 10); // 当前日期作为归还日期
  4. connection.query(
  5. "UPDATE borrow_records SET return_date = ? WHERE id = ?",
  6. [return_date, recordId],
  7. (error, result) => {
  8. if (error) throw error;
  9. res.redirect("/records");
  10. }
  11. );
  12. });
 recordRoutes.js 整体代码
  1. const express = require("express");
  2. const router = express.Router();
  3. const connection = require("../utils/db");
  4. const formattedDate = require("../utils/formattedDate");
  5. // 查询借阅记录
  6. router.get("/", (req, res) => {
  7. const sql = `SELECT borrow_records.id, users.username, books.title, DATE_FORMAT(borrow_records.borrow_date, '%Y-%m-%d') AS borrow_date, DATE_FORMAT(borrow_records.return_date, '%Y-%m-%d') AS return_date
  8. FROM borrow_records
  9. JOIN users ON borrow_records.user_id = users.id
  10. JOIN books ON borrow_records.book_id = books.id`;
  11. connection.query(sql, (error, results) => {
  12. if (error) throw error;
  13. res.render("records", { records: results }); // 渲染borrow-records.ejs模板,并传入查询结果
  14. });
  15. });
  16. // 借阅图书
  17. router.post("/add", (req, res) => {
  18. const { userId, bookId } = req.body;
  19. connection.query(
  20. "INSERT INTO borrow_records (user_id, book_id, borrow_date) VALUES (?, ?, ?)",
  21. [userId, bookId, formattedDate],
  22. (error, result) => {
  23. if (error) throw error;
  24. res.redirect("/records");
  25. }
  26. );
  27. });
  28. // 归还图书
  29. router.get("/update/:id", (req, res) => {
  30. const recordId = req.params.id;
  31. const return_date = new Date().toISOString().slice(0, 10); // 当前日期作为归还日期
  32. connection.query(
  33. "UPDATE borrow_records SET return_date = ? WHERE id = ?",
  34. [return_date, recordId],
  35. (error, result) => {
  36. if (error) throw error;
  37. res.redirect("/records");
  38. }
  39. );
  40. });
  41. module.exports = router;

3.5 注册路由

在根目录下的app.js文件中对路由进行注册

 引入路由

  1. const pageRoutes = require("./routes/pageRoutes");
  2. const userRoutes = require("./routes/userRoutes");
  3. const bookRoutes = require("./routes/bookRoutes");
  4. const recordRoutes = require("./routes/recordRoutes");
  5. const loginRoutes = require("./routes/loginRoutes");

 注册路由

  1. app.use("/", pageRoutes);
  2. app.use("/users", userRoutes);
  3. app.use("/books", bookRoutes);
  4. app.use("/records", recordRoutes);
  5. app.use("/login", loginRoutes);
app.js 整体代码
  1. const express = require("express");
  2. const bodyParser = require("body-parser");
  3. const session = require("express-session");
  4. const path = require("path");
  5. const pageRoutes = require("./routes/pageRoutes");
  6. const userRoutes = require("./routes/userRoutes");
  7. const bookRoutes = require("./routes/bookRoutes");
  8. const recordRoutes = require("./routes/recordRoutes");
  9. const loginRoutes = require("./routes/loginRoutes");
  10. const app = express();
  11. app.use(bodyParser.urlencoded({ extended: false }));
  12. app.use(
  13. session({
  14. secret: "secret",
  15. resave: false,
  16. saveUninitialized: true,
  17. })
  18. );
  19. app.use(express.static("public"));
  20. // 设置ejs模板引擎
  21. app.set("view engine", "ejs");
  22. app.set("views", path.join(__dirname, "views")); // 指定视图文件夹路径
  23. app.use("/", pageRoutes);
  24. app.use("/users", userRoutes);
  25. app.use("/books", bookRoutes);
  26. app.use("/records", recordRoutes);
  27. app.use("/login", loginRoutes);
  28. app.listen(8080, () => {
  29. console.log("http://127.0.0.1:8080");
  30. });

四、前端页面的编写

4.1 创建资源文件

在public目录文件夹下创建两个目录 “css”用来存放css样式、“imgs”用来存放图片

css目录下创建两个文件

login.css

  1. body {
  2. font-family: Arial, sans-serif;
  3. background-image: url("../imgs/bg.jpg");
  4. background-size: cover;
  5. background-position: center;
  6. background-attachment: fixed;
  7. }
  8. .container {
  9. margin-top: 200px;
  10. max-width: 400px;
  11. padding: 20px;
  12. background-color: #fff;
  13. border: 1px solid #ccc;
  14. border-radius: 4px;
  15. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  16. margin: 200px auto;
  17. }
  18. h2 {
  19. color: #333;
  20. text-align: center;
  21. margin-bottom: 30px;
  22. }
  23. .form-group {
  24. width: 94%;
  25. margin-bottom: 20px;
  26. }
  27. label {
  28. font-weight: bold;
  29. }
  30. input[type="text"],
  31. input[type="password"] {
  32. width: 100%;
  33. padding: 10px;
  34. border: 1px solid #ccc;
  35. border-radius: 4px;
  36. }
  37. button[type="submit"] {
  38. width: 100%;
  39. padding: 10px;
  40. background-color: #007bff;
  41. border: none;
  42. color: #fff;
  43. border-radius: 4px;
  44. cursor: pointer;
  45. }
  46. button[type="submit"]:hover {
  47. background-color: #0056b3;
  48. }

main.css

  1. * {
  2. margin: 0;
  3. padding: 0;
  4. }
  5. body {
  6. background-color: #f8f8f8;
  7. font-family: Arial, sans-serif;
  8. }
  9. a {
  10. text-decoration: none;
  11. display: block;
  12. padding: 10px;
  13. color: #fff;
  14. font-weight: bold;
  15. transition: 0.3s;
  16. }
  17. nav {
  18. background-color: #2a384b;
  19. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  20. }
  21. ul {
  22. list-style: none;
  23. margin: 0;
  24. padding: 0;
  25. display: flex;
  26. justify-content: center;
  27. align-items: center;
  28. height: 50px;
  29. }
  30. li {
  31. margin-right: 20px;
  32. }
  33. a:hover {
  34. color: #007bff;
  35. border-radius: 4px;
  36. }
  37. .container {
  38. padding: 20px;
  39. margin-top: 50px;
  40. }
  41. h2 {
  42. color: #333;
  43. text-align: center;
  44. margin-bottom: 30px;
  45. }
  46. .table {
  47. border-collapse: collapse;
  48. width: 100%;
  49. background-color: #fff;
  50. border: 1px solid #fafafa;
  51. border-radius: 4px;
  52. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  53. justify-content: center;
  54. margin-top: 15px;
  55. }
  56. .table th,
  57. .table td {
  58. padding: 10px;
  59. text-align: center; /* 修改此处 */
  60. border-bottom: 1px solid #ccc;
  61. }
  62. .table th {
  63. background-color: #f7f7f8;
  64. font-weight: bold;
  65. color: #5d5062;
  66. }
  67. .table td {
  68. color: #656475;
  69. }
  70. .btns {
  71. display: flex;
  72. justify-content: center;
  73. }
  74. /* 这里是按钮代码块 */
  75. .btn {
  76. display: block;
  77. width: 80px;
  78. height: 20px;
  79. border: none;
  80. border-radius: 4px;
  81. color: #fff;
  82. font-size: 16px;
  83. cursor: pointer;
  84. text-align: center;
  85. margin-right: 10px;
  86. }
  87. .btn-primary {
  88. background-color: #007bff;
  89. }
  90. .btn-edit {
  91. background-color: cadetblue;
  92. }
  93. .btn-danger {
  94. background-color: crimson;
  95. }
  96. .btn-primary:hover {
  97. background-color: #0056b3;
  98. }
  99. .btn-edit:hover {
  100. background-color: rgb(98, 129, 130);
  101. }
  102. .btn-danger:hover {
  103. background-color: rgb(165, 28, 55);
  104. }
  105. /* 表单样式 */
  106. .form-group {
  107. margin-bottom: 15px;
  108. }
  109. label {
  110. font-weight: bold;
  111. }
  112. input[type="text"],
  113. select {
  114. width: 100%;
  115. height: 35px;
  116. padding: 5px 10px;
  117. border-radius: 3px;
  118. border: 1px solid #ccc;
  119. box-sizing: border-box;
  120. }
  121. input[type="submit"] {
  122. width: 100px;
  123. height: 40px;
  124. background-color: #007bff;
  125. }
  126. .btn-return {
  127. background-color: #888;
  128. }
  129. /* 提交按钮样式 */
  130. .btn-primary {
  131. background-color: #007bff;
  132. color: #fff;
  133. border: none;
  134. padding: 8px 16px;
  135. border-radius: 3px;
  136. cursor: pointer;
  137. }
  138. .btn-primary:hover {
  139. background-color: #0069d9;
  140. }

在imgs目录文件夹下存放以下两张图片 

 

 具体静态资源图片,可以在文章顶部进行下载。

4.2 创建导航栏页面

点击我了解什么是ejs模版

在views目录文件夹下创建 “navbar.ejs” 文件,编写以下内容:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>图书管理系统</title>
  5. <link rel="stylesheet" href="/css/main.css" />
  6. <link rel="icon" href="imgs/icon.png" type="image/png" />
  7. </head>
  8. <body>
  9. <nav>
  10. <ul>
  11. <li><a href="#">首页</a></li>
  12. <li><a href="/users">用户管理</a></li>
  13. <li><a href="/books">图书管理</a></li>
  14. <li><a href="/records">借阅图书管理</a></li>
  15. <li><a href="/login/logout">退出登录</a></li>
  16. </ul>
  17. </nav>
  18. </body>
  19. </html>

4.3 创建登录

在views目录文件夹下创建 “login.ejs” 文件,编写以下内容:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>图书管理系统</title>
  5. <link rel="stylesheet" href="css/login.css" />
  6. <link rel="icon" href="imgs/icon.png">
  7. </head>
  8. <body>
  9. <div class="container">
  10. <h2>后台登录</h2>
  11. <form method="post" action="/login">
  12. <div class="form-group">
  13. <label for="username">用户名:</label>
  14. <input
  15. type="text"
  16. class="form-control"
  17. id="username"
  18. name="username"
  19. placeholder="请输入用户名"
  20. />
  21. </div>
  22. <div class="form-group">
  23. <label for="password">密码:</label>
  24. <input
  25. type="password"
  26. class="form-control"
  27. id="password"
  28. name="password"
  29. placeholder="请输入密码"
  30. />
  31. </div>
  32. <button type="submit" class="btn btn-primary">登录</button>
  33. </form>
  34. </div>
  35. </body>
  36. </html>

4.4 创建用户管理页面

在views目录文件夹下创建 “users.ejs” 文件,编写以下内容:

  1. <%- include('navbar') %>
  2. <div class="container">
  3. <h2>用户管理</h2>
  4. <a class="btn btn-primary" href="/page_user">添加用户</a>
  5. <table class="table">
  6. <thead>
  7. <tr>
  8. <th>ID</th>
  9. <th>用户名</th>
  10. <th>密码</th>
  11. <th>操作</th>
  12. </tr>
  13. </thead>
  14. <tbody id="userTable">
  15. <% users.forEach(function(user) { %>
  16. <tr>
  17. <td><%= user.id %></td>
  18. <td><%= user.username %></td>
  19. <td><%= user.password %></td>
  20. <td>
  21. <div class="btns">
  22. <a class="btn btn-edit" href="/users/<%= user.id %>">修改</a>
  23. <a class="btn btn-danger" href="/users/del/<%= user.id %>">删除</a>
  24. </div>
  25. </td>
  26. </tr>
  27. <% }); %>
  28. </tbody>
  29. </table>
  30. </div>
  31. </body>
  32. </html>

4.5 创建添加用户界面

在views目录文件夹下创建 “add_user.ejs” 文件,编写以下内容:

  1. <%- include('navbar') %>
  2. <div class="container">
  3. <h2>用户添加</h2>
  4. <form id="editUserForm" action="/users/add" method="post">
  5. <div class="form-group">
  6. <label for="editUserName">用户名</label>
  7. <input
  8. type="text"
  9. class="form-control"
  10. id="editUserName"
  11. name="username"
  12. required
  13. />
  14. </div>
  15. <div class="form-group">
  16. <label for="editUserPhone">密码</label>
  17. <input
  18. type="text"
  19. class="form-control"
  20. id="editUserPhone"
  21. name="password"
  22. required
  23. />
  24. </div>
  25. <div class="btns">
  26. <input class="btn btn-add" type="submit" value="确定" />
  27. <a class="btn btn-return" href="/users">返回</a>
  28. </div>
  29. </form>
  30. </div>
  31. </body>
  32. </html>

4.6 创建修改用户界面

在views目录文件夹下创建 “update_user.ejs” 文件,编写以下内容:

  1. <%- include('navbar') %>
  2. <div class="container">
  3. <h2>用户修改</h2>
  4. <form id="editUserForm" action="/users/update" method="post">
  5. <input type="hidden" name="id" id="editUserId" value="<%= user[0].id %>"/>
  6. <div class="form-group">
  7. <label for="editUserName">用户名</label>
  8. <input
  9. type="text"
  10. class="form-control"
  11. id="editUserName"
  12. name="username"
  13. value="<%= user[0].username%>"
  14. required
  15. />
  16. </div>
  17. <div class="form-group">
  18. <label for="editUserPhone">密码</label>
  19. <input
  20. type="text"
  21. class="form-control"
  22. id="editUserPhone"
  23. name="password"
  24. value="<%= user[0].password%>"
  25. required
  26. />
  27. </div>
  28. <div class="btns">
  29. <input class="btn btn-add" type="submit" value="确定" />
  30. <a class="btn btn-return" href="/users">返回</a>
  31. </div>
  32. </div>
  33. </body>
  34. </html>

4.7 创建图书界面

在views目录文件夹下创建 “books.ejs” 文件,编写以下内容:

  1. <%- include('navbar') %>
  2. <div class="container">
  3. <h2>图书管理</h2>
  4. <a class="btn btn-primary" href="/page_book">添加图书</a>
  5. <table class="table">
  6. <thead>
  7. <tr>
  8. <th>ID</th>
  9. <th>图书名称</th>
  10. <th>作者名称</th>
  11. <th>出版社名称</th>
  12. <th>操作</th>
  13. </tr>
  14. </thead>
  15. <tbody id="userTable">
  16. <% books.forEach(function(book, index) { %>
  17. <tr>
  18. <td><%= index+1 %></td>
  19. <td><%= book.title %></td>
  20. <td><%= book.author %></td>
  21. <td><%= book.publisher %></td>
  22. <td>
  23. <div class="btns">
  24. <a class="btn btn-edit" href="/books/<%= book.id %>">修改</a>
  25. <a class="btn btn-danger" href="/books/del/<%= book.id %>">删除</a>
  26. </div>
  27. </td>
  28. </tr>
  29. <% }); %>
  30. </tbody>
  31. </table>
  32. </div>
  33. </body>
  34. </html>

4.8 创建添加图书界面

在views目录文件夹下创建 “add_book.ejs” 文件,编写以下内容: 

  1. <%- include('navbar') %>
  2. <div class="container">
  3. <h2>添加图书</h2>
  4. <form id="editUserForm" action="/books/add" method="post">
  5. <div class="form-group">
  6. <label for="editUserName">图书名称</label>
  7. <input
  8. type="text"
  9. class="form-control"
  10. id="editUserName"
  11. name="title"
  12. required
  13. />
  14. </div>
  15. <div class="form-group">
  16. <label for="editUserPhone">作者名</label>
  17. <input
  18. type="text"
  19. class="form-control"
  20. id="editUserPhone"
  21. name="author"
  22. required
  23. />
  24. </div>
  25. <div class="form-group">
  26. <label for="editUserPhone">出版社名称</label>
  27. <input
  28. type="text"
  29. class="form-control"
  30. id="editUserPhone"
  31. name="publisher"
  32. required
  33. />
  34. </div>
  35. <div class="btns">
  36. <input class="btn btn-add" type="submit" value="确定" />
  37. <a class="btn btn-return" href="/books">返回</a>
  38. </div>
  39. </form>
  40. </div>
  41. </body>
  42. </html>

 4.9 创建修改图书界面

在views目录文件夹下创建 “update_book.ejs” 文件,编写以下内容: 

  1. <%- include('navbar') %>
  2. <div class="container">
  3. <h2>图书修改</h2>
  4. <form id="editUserForm" action="/books/update" method="post">
  5. <input type="hidden" name="bookId" id="editUserId" value="<%= book[0].id %>"/>
  6. <div class="form-group">
  7. <label for="editUserName">图书名称</label>
  8. <input
  9. type="text"
  10. class="form-control"
  11. id="editUserName"
  12. name="title"
  13. value="<%= book[0].title %>"
  14. required
  15. />
  16. </div>
  17. <div class="form-group">
  18. <label for="editUserPhone">作者名</label>
  19. <input
  20. type="text"
  21. class="form-control"
  22. id="editUserPhone"
  23. name="author"
  24. value="<%= book[0].author %>"
  25. required
  26. />
  27. </div>
  28. <div class="form-group">
  29. <label for="editUserPhone">出版社名称</label>
  30. <input
  31. type="text"
  32. class="form-control"
  33. id="editUserPhone"
  34. name="publisher"
  35. value="<%= book[0].publisher %>"
  36. required
  37. />
  38. </div>
  39. <div class="btns">
  40. <input class="btn btn-add" type="submit" value="确定" />
  41. <a class="btn btn-return" href="/users">返回</a>
  42. </div>
  43. </div>
  44. </body>
  45. </html>

 4.10 创建借阅图书界面

在views目录文件夹下创建 “records.ejs” 文件,编写以下内容: 

  1. <%- include('navbar') %>
  2. <div class="container">
  3. <h2>借阅图书管理</h2>
  4. <a class="btn btn-primary" href="/page_record">添加借阅</a>
  5. <table class="table">
  6. <thead>
  7. <tr>
  8. <th>ID</th>
  9. <th>用户名称</th>
  10. <th>图书名称</th>
  11. <th>借阅时间</th>
  12. <th>归还时间</th>
  13. <th>操作</th>
  14. </tr>
  15. </thead>
  16. <tbody id="userTable">
  17. <% records.forEach(function(record, index) { %>
  18. <tr>
  19. <td><%= index+1 %></td>
  20. <td><%= record.username %></td>
  21. <td><%= record.title %></td>
  22. <td><%= record.borrow_date %></td>
  23. <td><%= record.return_date %></td>
  24. <td>
  25. <div class="btns">
  26. <% if (!record.return_date) { %>
  27. <a class="btn btn-edit" href="/records/update/<%= record.id %>">归还</a>
  28. <% } %>
  29. </div>
  30. </td>
  31. </tr>
  32. <% }); %>
  33. </tbody>
  34. </table>
  35. </div>
  36. </body>
  37. </html>

 4.11 创建添加借阅图书界面

在views目录文件夹下创建 “add_record.ejs” 文件,编写以下内容: 

  1. <%- include('navbar') %>
  2. <div class="container">
  3. <h2>添加借阅</h2>
  4. <form id="editUserForm" action="/records/add" method="post">
  5. <div class="form-group">
  6. <label for="editUserName">用户名称</label>
  7. <select name="userId">
  8. <% users.forEach(function(user) { %>
  9. <option value="<%= user.id %>"><%= user.name %></option>
  10. <% }); %>
  11. </select>
  12. </div>
  13. <div class="form-group">
  14. <label for="editUserPhone">图书名称</label>
  15. <select name="bookId">
  16. <% books.forEach(function(book) { %>
  17. <option value="<%= book.id %>"><%= book.title %></option>
  18. <% }); %>
  19. </select>
  20. </div>
  21. <div class="btns">
  22. <input class="btn btn-add" type="submit" value="借阅" />
  23. <a class="btn btn-return" href="/records">返回</a>
  24. </div>
  25. </form>
  26. </div>
  27. </body>
  28. </html>

五、运行测试项目

我们现在就可以去浏览器当中输入地址 http://127.0.0.1:8080 来访问我们的项目。

账号密码
rootroot

恭喜你,图书管理系统已经做好。 

其他文章推荐:

1分钟学会使用Express+MySQL实现注册功能

在1分钟内,使用 Express 快速编写一个简单的登录功能。

Express EJS渲染技术详解

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/808228
推荐阅读
相关标签
  

闽ICP备14008679号