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的重構建議

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