Go语言实战案例:构建简单留言板(使用文件存储)

在 Web 应用开发中,留言板是非常经典的入门案例。它涵盖了用户提交数据、后台存储和页面渲染等核心功能,能够帮助我们熟悉 HTTP 请求处理、模板渲染以及数据持久化等知识点。

本文将以 Go 语言为例,演示如何用最简单的文件存储方式,实现一个基本的留言板系统,满足用户留言的新增和查看功能。


一、项目需求分析

  • • 用户可以通过网页填写姓名和留言内容,提交后留言保存到服务器。
  • • 服务器端将留言存储到本地 JSON 文件,实现数据的持久化。
  • • 首页展示所有留言,支持留言列表的浏览。
  • • 使用 Go 标准库,尽量减少外部依赖。
  • • 保证并发写文件的安全性。

二、技术点梳理

  • • HTTP 请求处理 (net/http)
  • • 表单数据接收与解析
  • • 文件读写(JSON 格式)
  • • 模板渲染 (html/template)
  • • 并发同步 (sync.Mutex)

三、核心代码解析

1. 留言结构体定义

go 复制代码
type Message struct {
    Name    string
    Content string
    Time    string
}

每条留言包含用户名、留言内容和留言时间。

2. 读取和保存留言

go 复制代码
func loadMessages() ([]Message, error) {
    file, err := os.Open(dataFile)
    if os.IsNotExist(err) {
        return []Message{}, nil
    }
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var msgs []Message
    err = json.NewDecoder(file).Decode(&msgs)
    if err != nil {
        return []Message{}, nil
    }
    return msgs, nil
}

func saveMessages(msgs []Message) error {
    file, err := os.Create(dataFile)
    if err != nil {
        return err
    }
    defer file.Close()

    return json.NewEncoder(file).Encode(msgs)
}

这两个函数负责从文件加载留言列表以及将留言列表写回文件。

3. 并发写保护

使用 sync.Mutex 避免多请求同时写文件导致的数据混乱:

dart 复制代码
var mutex sync.Mutex

所有写操作用 mutex.Lock()mutex.Unlock() 包裹。

4. 首页展示留言和留言表单

go 复制代码
func indexHandler(w http.ResponseWriter, r *http.Request) {
    msgs, _ := loadMessages()
    // 使用 html/template 渲染留言页面
    tmpl := ` ... `
    t := template.Must(template.New("index").Parse(tmpl))
    t.Execute(w, msgs)
}

5. 处理留言提交

scss 复制代码
func addHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }

    name := r.FormValue("name")
    content := r.FormValue("content")
    if name == "" || content == "" {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }

    mutex.Lock()
    defer mutex.Unlock()

    msgs, _ := loadMessages()
    msgs = append(msgs, Message{
        Name: name,
        Content: content,
        Time: time.Now().Format("2006-01-02 15:04:05"),
    })
    saveMessages(msgs)

    http.Redirect(w, r, "/", http.StatusSeeOther)
}

四、完整代码

go 复制代码
package main

import (
    "encoding/json"
    "html/template"
    "net/http"
    "os"
    "sync"
    "time"
)

type Message struct {
    Name    string
    Content string
    Time    string
}

var (
    dataFile = "messages.json"
    mutex    sync.Mutex
)

func loadMessages() ([]Message, error) {
    file, err := os.Open(dataFile)
    if os.IsNotExist(err) {
        return []Message{}, nil
    }
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var msgs []Message
    err = json.NewDecoder(file).Decode(&msgs)
    if err != nil {
        return []Message{}, nil
    }
    return msgs, nil
}

func saveMessages(msgs []Message) error {
    file, err := os.Create(dataFile)
    if err != nil {
        return err
    }
    defer file.Close()

    return json.NewEncoder(file).Encode(msgs)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    msgs, _ := loadMessages()

    tmpl := `
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>简易留言板</title>
    </head>
    <body>
        <h1>简易留言板</h1>
        <form method="POST" action="/add">
            姓名: <input name="name" required><br>
            留言: <textarea name="content" required></textarea><br>
            <button type="submit">提交</button>
        </form>
        <hr>
        <h2>留言列表</h2>
        {{range .}}
            <p><b>{{.Name}}</b> ({{.Time}}):<br>{{.Content}}</p>
            <hr>
        {{else}}
            <p>暂无留言</p>
        {{end}}
    </body>
    </html>
    `
    t := template.Must(template.New("index").Parse(tmpl))
    t.Execute(w, msgs)
}

func addHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }

    name := r.FormValue("name")
    content := r.FormValue("content")
    if name == "" || content == "" {
        http.Redirect(w, r, "/", http.StatusSeeOther)
        return
    }

    mutex.Lock()
    defer mutex.Unlock()

    msgs, _ := loadMessages()
    msgs = append(msgs, Message{
        Name:    name,
        Content: content,
        Time:    time.Now().Format("2006-01-02 15:04:05"),
    })
    saveMessages(msgs)

    http.Redirect(w, r, "/", http.StatusSeeOther)
}

func main() {
    http.HandleFunc("/", indexHandler)
    http.HandleFunc("/add", addHandler)

    println("服务器启动:http://localhost:8080")
    http.ListenAndServe(":8080", nil)
}

五、运行测试

    1. 在项目目录下执行:
go 复制代码
go run main.go
    1. 打开浏览器访问:
arduino 复制代码
http://localhost:8080
    1. 输入姓名和留言内容,点击提交,留言即被保存并显示。
    1. 服务器目录下生成 messages.json 文件,实现留言持久化。

六、注意事项与扩展建议

  • 数据存储:文件存储适合小型简单应用,生产环境建议使用数据库(如 MySQL、SQLite、MongoDB)存储留言。
  • 并发安全 :此示例使用 sync.Mutex 简单保证写文件安全,实际复杂系统建议设计更严谨的并发策略。
  • 输入校验:本示例未对输入内容做更多安全处理,生产环境需防止 XSS 注入、恶意内容。
  • 分页显示:当留言数量多时,建议分页加载,提升页面性能。
  • 样式美化:可增加 CSS 使界面更加美观友好。

七、总结

本文示范了如何用 Go 语言快速搭建一个简易留言板,涵盖 HTTP 请求处理、表单解析、模板渲染和文件存储等基础知识,帮助初学者掌握实用的 Web 开发技能。

相关推荐
Mike_小新13 分钟前
【Mike随想】未来更看重架构能力和业务经验,而非单纯编码能力
后端·程序员
Abadbeginning16 分钟前
FastSoyAdmin导出excel报错‘latin-1‘ codec can‘t encode characters in position 41-54
前端·javascript·后端
很小心的小新21 分钟前
五、SpringBoot工程打包与运行
java·spring boot·后端
ACGkaka_23 分钟前
SpringBoot 集成 MapStruct
java·spring boot·后端
anthem3724 分钟前
12、Python项目实战
后端
anthem3724 分钟前
7、Python高级特性 - 提升代码质量与效率
后端
anthem3724 分钟前
6、Python文件操作与异常处理
后端
anthem3728 分钟前
3、Python控制流与函数 - 从Java到Python的转变
后端
pe7er1 小时前
Mac 上使用 Homebrew 安装 MySQL 8.4 和 MySQL 5.7 共存
前端·后端
coding随想1 小时前
数据库里的“锁”事:共享锁、排他锁与三级封锁协议
后端