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 开发技能。

相关推荐
Libby博仙33 分钟前
Spring Boot 条件化注解深度解析
java·spring boot·后端
源代码•宸1 小时前
Golang原理剖析(Map 源码梳理)
经验分享·后端·算法·leetcode·golang·map
小周在成长1 小时前
动态SQL与MyBatis动态SQL最佳实践
后端
瓦尔登湖懒羊羊1 小时前
TCP的自我介绍
后端
小周在成长1 小时前
MyBatis 动态SQL学习
后端
子非鱼9211 小时前
SpringBoot快速上手
java·spring boot·后端
我爱娃哈哈1 小时前
SpringBoot + XXL-JOB + Quartz:任务调度双引擎选型与高可用调度平台搭建
java·spring boot·后端
JavaGuide1 小时前
Maven 4 终于快来了,新特性很香!
后端·maven
开心就好20252 小时前
全面解析iOS应用代码混淆和加密加固方法与实践注意事项
后端
Thomas游戏开发2 小时前
分享一个好玩的:一次提示词让AI同时开发双引擎框架
前端·javascript·后端