VUE.JS和NODE.JS构建一个简易的前后端分离静态博客系统(一)

很久以前写的。不想白写,这边记录一下,配合GPT简要回忆一下代码思路。

后台系统

demo_initBlog.js

首先是博客的后台系统,通过node demo_initBlog.js进行初始化。

const fs = require("fs").promises
const path = require('path')
const jsonfile = require('jsonfile')

async function initBlog() {
    try {
        // 1 创建文件夹,已经创建就不创建
        console.log('=> 创建posts文件夹')
        let createDir = await fs.mkdir('./source/_posts/', { recursive: true })
        console.log(`=> created ${createDir}`);
        // 无需创建 => created undefined
        // 完全新建 => created D:\2022_8_3_mycnblogs\my_cnblogs\manuscript\source
        // 仅_posts不存在 => created D:\2022_8_3_mycnblogs\my_cnblogs\manuscript\source\_posts

        // 2 创建./source/post_infos.json用来保存和管理文章信息
        let file_name = 'post_infos.json'
        let file_path = path.join(__dirname, `source/${file_name}`)
        try {
            console.log('=> 尝试读取post_infos.json')
            let content = await jsonfile.readFile(file_path)
            console.log('=> 读取post_infos.json成功!内容如下')
            console.log(content)
        } catch (err) {
            // 没创建才创建,已创建不创建
            console.log(err)
            console.log('=> post_infos.json未创建!即将创建')
            let obj = {
                categories: [],
                posts: [],
            }
            jsonfile.writeFile(file_path, obj, { spaces: 2 })
                .then(() => {
                    console.log('=> 回调: Write complete')
                })
                .catch(err => {
                    console.log('=> 创建post_infos.json异常')
                    console.log(err)
                })
        }

        console.log('=> initBlog.js程序已到末尾, 如有需要删除重新创建')
    } catch (err) {
        console.error(err.message);
    }
}

initBlog()

亲爱的朋友,这段代码是用来初始化一个博客的。它做了以下几件事情:

  1. 首先,它会创建一个名为posts的文件夹,用于存放博客文章。如果文件夹已经存在,它就不会再创建,太聪明了吧!
  2. 然后,它尝试读取一个名为post_infos.json的文件。这个文件用于存储博客文章的信息,比如分类和文章列表。
  3. 如果发现post_infos.json文件还不存在,它会马上创建一个,并初始化它的内容,使其包含空的分类和文章列表。

简单来说,这段代码会确保你的博客文件夹结构和必要文件都已经创建好,以便于你开始写博客。希望这个解释对你有帮助!

demo_server.js

下面这个应该是一个后台接口+后台的业务逻辑代码。

const express = require('express');
const app = express();
const fs = require("fs").promises
const path = require('path')
const bodyParser = require('body-parser')
const { customAlphabet } = require('nanoid')
// 字母表去掉一个 `-`,这样才可以在网址里面用
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
const nanoid = customAlphabet(alphabet, 8)
const cors = require('cors');
const jsonfile = require('jsonfile')

// express本身不能处理Post来的表单数据,通过下面的设置,
// 就可以在req.body中拿到数据
app.use(bodyParser.urlencoded({extended : false}))
app.use(bodyParser.json())

// 解决跨域问题
app.use(cors())

// PayloadTooLargeError: request entity too large
app.use(express.json({ limit: '10000kb'}))
app.use(express.urlencoded({ limit: '10000kb'}))

function utf8readFile(path) {
    let opions = {
        encoding: 'utf-8',
    }
    return fs.readFile(path, opions)
}

function cmd_log(str) {
    console.log(`[${new Date().toLocaleString()}] ${str}`)
}

const SOURCE_PATH = path.join(__dirname, 'source')
const JSON_NAME = 'post_infos.json'
const JSON_PATH = path.join(SOURCE_PATH, JSON_NAME)

function postIdDirPath(postId) {
    return path.join(SOURCE_PATH, '_posts', postId)
}

function htmlFilePath(postId) {
    return path.join(postIdDirPath(postId), 'index.html')
}

function readHtmlFile(postId) {
    // 读取对应HTML文件内容
    return utf8readFile(htmlFilePath(postId))
}

function writeHtmlFile(postId, content) {
    // 将内容直接写到对应的HTML文件中
    return fs.writeFile(htmlFilePath(postId), content)    
}

async function updateJSONFile_posts(dealWithObj) {
    // 更新.json文件,传入一个修改json文件中对象的函数

    // 1 读
    let obj = await jsonfile.readFile(JSON_PATH)
    // 2 改
    obj = dealWithObj(obj)
    // 3 写
    await jsonfile.writeFile(JSON_PATH, obj, { spaces: 2 })

    return 1 // 随便什么值无所谓
}

async function updateJSONFile_categories(dealWithObj) {
    // 更新.json文件,传入一个修改json文件中对象的函数
    const category_path = path.join(SOURCE_PATH, 'category_infos.json')

    // 1 读出
    let obj = await jsonfile.readFile(category_path)
    // 2 修改
    obj = dealWithObj(obj)
    // 3 写回
    await jsonfile.writeFile(category_path, obj, { spaces: 2 })

    return 1 // 随便什么值无所谓
}

// 随笔列表
app.get('/posts', async function (req, res) {
    let { posts } = await jsonfile.readFile(JSON_PATH)
    res.send(posts)
})

// 某个随笔信息(info + html)
app.get('/posts/:postId', async function(req, res) {
    const POST_ID = req.params.postId
    try {
        // 1 从.json里读取对应的POST信息
        let { posts } = await jsonfile.readFile(JSON_PATH)
        let post = posts.find(x => x.id === POST_ID)

        // 2 读POST对应的HTML文件
        let content = await readHtmlFile(POST_ID)
        post['content'] = content

        res.send(post)

    }catch(err) {

        console.log(err)
        res.status(500).end()
    }
})

// 新随笔
app.post('/posts', async function (req, res) {
    let { title, content, category } = req.body
    try {
        title = title.trim()
        if (title === '') throw new Error('标题不能为空')

        // POST模板
        let post = {
            id: nanoid(8),
            title: title,
            createTime: new Date(),
            category: category, 
            state: '未发布', 
            pubDate: new Date(),                               
        }    
        
        // 1 将POST信息写入.json
        await updateJSONFile_posts((obj) => {
            obj.posts.unshift(post)
            return obj
        })

        cmd_log("成功写入JSON_PATH: " + title)
        
        // 2 创建对应文件夹和HTML文件
        let createDir = await fs.mkdir(postIdDirPath(post.id), { recursive: true })

        cmd_log(`created ${createDir} (文件夹)`);

        await writeHtmlFile(post.id, content)
        
        cmd_log(`.html文件已经创建`);

        res.send(post)
    }catch(err) {
        console.log(err)

        res.status(500).send('创建异常')
    }
})

// 更新随笔
app.post('/posts/:postId', async function (req, res) {
    const POST_ID = req.params.postId
    let {  title, content, category, state, pubDate } = req.body
    try {
        // 将内容覆盖到对应的HTML文件 
        await writeHtmlFile(POST_ID, content)
        
        // 更新.json中的相关信息
        let post
        await updateJSONFile_posts((obj) => {
            let index = obj.posts.findIndex(x => x.id === POST_ID)
            obj.posts[index].title = title 
            obj.posts[index].category = category
            obj.posts[index].state = state
            obj.posts[index].pubDate = pubDate
            
            post = obj.posts[index]
            return obj
        })

        res.send(post)
    }catch(err) {
        console.log(err)
        res.status(500).end()
    }
})

// 删除随笔
app.delete('/posts/:postId', async function(req, res) {
    const POST_ID = req.params.postId
    try {
        // 1 从.json里删除
        let obj = await jsonfile.readFile(JSON_PATH)
        obj.posts = obj.posts.filter(post => post.id !== POST_ID)

        await jsonfile.writeFile(JSON_PATH, obj, { spaces: 2 })

        cmd_log(`修改${JSON_NAME},已移除POST: ` + POST_ID)

        // 2 删除文件
        await fs.rm(postIdDirPath(POST_ID), { recursive: true, force: true })

        cmd_log('相关文件删除成功')

        res.end()
    }catch(err) {
        console.log(err)

        res.status(500).send('删除异常')
    }
})

// 新类别
app.post('/categories', async function (req, res) {
    let { name } = req.body
    try {
        name = name.trim()
        if (name === '') throw new Error('类别名不能为空')
        
        // category
        let category = {
            id: nanoid(8),
            name: name,
            visible: true                             
        }    
        
        cmd_log(`增加类别${category}`)
        // 1 将category信息更新到.json
        await updateJSONFile_categories(obj => {
            obj.push(category)
            return obj
        })

        res.send(category)
    }catch(err) {
        console.log(err)
        res.status(500).send('创建异常')
    }
})

// 类别列表
app.get('/categories', async function (req, res) {
    const category_path = path.join(SOURCE_PATH, 'category_infos.json')
    res.send(await jsonfile.readFile(category_path))
})

// 删除类别
app.delete('/categories/:id', async function (req, res) {
    try {
        const id = req.params.id
        await updateJSONFile_categories(obj => {
            obj = obj.filter(x => x.id !== id)
            return obj
        })

        res.end()
    }catch(err) {
        console.log(err)

        res.status(500).send('删除异常')
    }    
})

const PORT = 8081
let server = app.listen(PORT, function () {
    console.log(`Example app listening on port ${PORT}`)
})

这个代码主要实现了一个简易的博客文章管理系统,具体功能如下:

  1. 导入必要的库:使用了 Express.js 作为 Web 框架,处理 HTTP 请求,同时还导入了其他辅助库,如 fs (文件系统)、path (处理文件路径)、body-parser (解析请求体数据)、nanoid (生成随机 ID)、cors (处理跨域请求) 以及 jsonfile (读写 JSON 文件)。

  2. 初始化 Express.js 应用:设置 body-parser 解析请求体数据,并使用 CORS 解决跨域问题。同时设置了 JSON 和 URL 编码的请求体大小限制。

  3. 定义一些实用函数:

    • utf8readFile: 以 utf-8 编码读取指定路径的文件。
    • cmd_log: 在控制台输出带有时间戳的日志信息。
    • postIdDirPath: 根据文章 ID 生成对应的文章文件夹路径。
    • htmlFilePath: 根据文章 ID 生成对应的 HTML 文件路径。
    • readHtmlFile: 读取指定文章 ID 的 HTML 文件内容。
    • writeHtmlFile: 将内容写入指定文章 ID 的 HTML 文件。
    • updateJSONFile_posts: 更新包含文章信息的 JSON 文件,传入一个处理对象的函数。
    • updateJSONFile_categories: 更新包含类别信息的 JSON 文件,传入一个处理对象的函数。
  4. 实现了以下几个路由处理函数:

    • 随笔列表:处理 GET 请求,返回所有文章信息。
    • 某个随笔信息:处理 GET 请求,根据文章 ID 获取对应文章的信息和 HTML 内容。
    • 新随笔:处理 POST 请求,创建新文章,将文章信息添加到 JSON 文件,并创建对应的 HTML 文件。
    • 更新随笔:处理 POST 请求,根据文章 ID 更新文章信息,包括更新 JSON 文件和 HTML 文件。
    • 删除随笔:处理 DELETE 请求,根据文章 ID 删除文章,包括移除 JSON 文件中的信息和删除 HTML 文件。
    • 新类别:处理 POST 请求,创建新的文章类别,将类别信息添加到 JSON 文件。
    • 类别列表:处理 GET 请求,返回所有类别信息。
    • 删除类别:处理 DELETE 请求,根据类别 ID 删除类别,更新 JSON 文件。
  5. 启动 Express.js 应用并监听特定端口,等待客户端发起请求。

这个博客文章管理系统的代码包括基础配置、工具函数和路由处理逻辑。用户可以通过不同的 HTTP 请求来创建、更新、删除博客文章和类别。

GPT的重构建议

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章