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

相关推荐
华洛2 分钟前
我用AI做了一个48秒的真人精品漫剧,不难也不贵
前端·javascript·后端
WZTTMoon7 分钟前
Spring Boot 中Servlet、Filter、Listener 四种注册方式全解析
spring boot·后端·servlet
standovon32 分钟前
Spring Boot整合Redisson的两种方式
java·spring boot·后端
Cosolar1 小时前
LlamaIndex RAG 本地部署+API服务,快速搭建一个知识库检索助手
后端·openai·ai编程
MX_93591 小时前
SpringMVC请求参数
java·后端·spring·servlet·apache
忆想不到的晖2 小时前
Codex 探索:别急着调 Prompt,先把工作流收住
后端·agent·ai编程
weixin_408099672 小时前
【实战对比】在线 OCR 识别 vs OCR API 接口:从个人工具到系统集成该怎么选?
图像处理·人工智能·后端·ocr·api·图片文字识别·文字识别ocr
Victor3564 小时前
MongoDB(73)如何设置用户权限?
后端
Victor3564 小时前
MongoDB(74)什么是数据库级别和集合级别的访问控制?
后端
计算机学姐4 小时前
基于SpringBoot的咖啡店管理系统【个性化推荐+数据可视化统计+配送信息】
java·vue.js·spring boot·后端·mysql·信息可视化·tomcat