建站日记四 第一版博客上线

82 阅读4分钟

整体介绍

博客的整体分为以下三部分:

  • 博客后台,使用express + mysql,提供接口
  • 博客管理端,使用vue3 + ts + vite,提供博客和分类增删改查的功能
  • 博客前端,使用vue3 + ts + vite,负责展示博客内容

博客地址:影月的个人博客
页面展示如下: image.png

博客后台

集成数据库

1. 本地安装数据库

# 安装
brew install mysql
# 启动数据库
brew services start mysql
# 登陆数据库
mysql -u root -p

2. 建表
目前博客仅有三张表,用户表、分类表、文章表

-- 创建用户表
CREATE TABLE users (
    user_id INT AUTO_INCREMENT PRIMARY KEY,
    -- 用户ID,主键,自增  
    user_name VARCHAR(50) NOT NULL UNIQUE,
    -- 用户名,非空且唯一  
    user_password VARCHAR(255) NOT NULL,
    -- 密码,非空  
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
desc articles;
-- 创建分类表
CREATE TABLE category (
    id INT AUTO_INCREMENT PRIMARY KEY,
    category_name VARCHAR(50) NOT NULL UNIQUE,
    category_desc VARCHAR(100) NOT NULL,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建文章表
CREATE TABLE articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    -- 唯一标识,每个博客文章的自增 ID
    title VARCHAR(255) NOT NULL,
    -- 文章标题,最大长度为 255 字符
    content MEDIUMTEXT NOT NULL,
    -- 文章内容,存储中大型文本
    article_desc VARCHAR(100),
    author VARCHAR(100),
    -- 作者名称,最大长度为 100 字符
    category_id INT,
    created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    -- 文章创建时间,默认值为当前时间
    updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    -- 文章更新时间
    view_count INT DEFAULT 0 -- 浏览次数,初始为 0
);

3. express连接数据库
express中使用mysql2连接数据库

pnpm install mysql2

添加数据库配置

const mysql = require("mysql2/promise");

// 需要把这里换成真实的服务器信息
// 设置MySQL连接池
const pool = mysql.createPool({
    host: "searverIp",
    port: 3306,
    user: "root",
    password: "xxxxxx",
    database: "xxxxx",
    waitForConnections: true,
    connectionLimit: 10, // 连接池中允许的最大连接数
    queueLimit: 0, // 队列中允许的最大请求数,0表示无限制
});

module.exports = pool;

在接口中访问数据库

var express = require("express");
const pool = require("../config/dbConfig");
var router = express.Router();

router.get("/list", async (req, res) => {
    let connection;
    try {
        // 从连接池中获取一个连接
        connection = await pool.getConnection();
        const sqlQuery = `SELECT * FROM category`;
        const [rows, fields] = await connection.execute(sqlQuery);
        res.status(200).json({
            code: 200,
            msg: "查询成功",
            data: transformArray(rows),
        });
    } catch (err) {
        // 处理错误
        console.error("Error executing query:", err);
        res.status(500).json({ error: "Internal Server Error" });
    } finally {
        // 确保连接被释放回连接池(如果它仍然存在)
        if (connection) {
            connection.release();
        }
    }
});

express中的路由

目前博客分为用户/分类/文章三块路由,在router目录下实现用户/文章/分类的增删改查

const express = require("express");
const cors = require("cors");
const cookieParser = require("cookie-parser");

const categoryRouter = require("./router/category");
const articlesRouter = require("./router/articles");
const userRouter = require("./router/user");

const app = express();
const port = 4000;

// 使用 cors 中间件
app.use(cors());

// 中间件,解析 JSON 请求体
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 解析cookie
app.use(cookieParser());

app.use("/category", categoryRouter);
app.use("/article", articlesRouter);
app.use("/user", userRouter);

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
});

登陆校验

  1. 用户登陆,后台使用jsonwebtoken生成token,把token返回在cookie中
  2. 在需要鉴权的接口中校验token,校验成功进入正常逻辑,校验失败返回code=700
  3. 客户端判断code=700,表示token失效,跳转到700

1. 使用jsonwebtoken生成token
utils/index.js

const jwt = require("jsonwebtoken");

const jwtSecret = "blog_admin";

function generateToken(userId) {
    const token = jwt.sign({ userId }, jwtSecret, { expiresIn: "1d" });
    return token;
}

2. 登陆接口,在cookie中添加token
router/user.js

const [rows] = await connection.execute(
    `SELECT * FROM users WHERE user_name = '${username}'`
);
const user = rows[0];

const token = generateToken(user["user_id"]);
res.cookie("token", token, { httpOnly: true });

3. 增加token校验中间件
middleware/jwt.js

const jwt = require("jsonwebtoken");

const jwtSecret = "blog_admin";

function verifyToken(req, res, next) {
    const token = req.cookies.token;

    if (!token) {
        return res.json({
            code: 700,
            msg: "无效的token",
        });
    }

    jwt.verify(token, jwtSecret, (err, userId) => {
        if (err) {
            return res.json({
                code: 700,
                msg: "无效的token",
            });
        }
        req.userId = userId;
        next(); // 调用next()来继续处理请求
    });
}

4. 在路由中使用校验中间件
例如只有具有token的用户,才能进行删除操作

router.post("/delete", verifyToken, async (req, res) => {
// ...
}

服务器上连接数据库

在服务器上安装mysql,参考在Linux实例中安装MySQL数据库,登陆、建表过程如上节,不再赘述。

express应用部署在容器中,mysql部署在宿主机上,可能会导致连接不上,需要开放mysql被连接的权限

  1. mysql默认只能被自身ip连接,需要开放被外部ip连接,在/etc/my.cnf文件上修改 [mysqld] bind-address = 0.0.0.0

  2. 授予特定用户从指定ip访问数据库的权限 # 如果没有用户,需要先创建用户 CREATE USER 'app_user'@'your_container_ip' IDENTIFIED BY '@Hmh6861860@'; # 授予这个用户对数据任意操作的权限,登陆mysql后操作,在>mysql>下进行 GRANT ALL PRIVILEGES ON your_database.* TO 'app_user'@'your_container_ip'; FLUSH PRIVILEGES;

  3. 开放安全组 允许容器ip访问3306端口

博客前端

博客管理端

  • 包含登录、文章管理、分类管理功能,进行修改/删除等操作前需要校验token,接口返回700跳转登陆页;
  • 安全组仅对自身ip开放即可。

博客前端

  • 使用md-editor-v3完成文章的预览

其他内容在之前的文章中都有记录,在此就不作赘述。