导读:本期聚焦于小伙伴创作的《Go语言实现简易在线投票评分系统:从零搭建Web互动应用》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《Go语言实现简易在线投票评分系统:从零搭建Web互动应用》有用,将其分享出去将是对创作者最好的鼓励。

HTML模板

模板存储在 templates/ 目录下。注意在代码示例中,HTML标签已全部转义,以便在文章中正确显示。

index.html – 主题列表页:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>投票系统</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 1rem; }
        .topic-card { border: 1px solid #ccc; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; }
    </style>
</head>
<body>
    <h1>可参与的投票与评分</h1>
    {{range .}}
    <div class="topic-card">
        <h2><a href="/topic/{{.ID}}">{{.Title}}</a></h2>
        <p>{{.Description}}</p>
        <a href="/results/{{.ID}}">查看结果</a>
    </div>
    {{end}}
</body>
</html>

topic.html – 投票表单页:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>{{.Title}} – 投票</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 1rem; }
        .option { margin-bottom: 0.5rem; }
        button { margin-top: 1rem; padding: 0.5rem 1.5rem; }
    </style>
</head>
<body>
    <h1>{{.Title}}</h1>
    <p>{{.Description}}</p>
    <form method="POST" action="/vote">
        <input type="hidden" name="topic" value="{{.ID}}">
        {{range .Options}}
        <div class="option">
            <input type="radio" id="opt{{.ID}}" name="option" value="{{.ID}}">
            <label for="opt{{.ID}}">{{.Name}}</label> (评分 {{.Score}})
        </div>
        {{end}}
        <button type="submit">提交投票</button>
    </form>
    <p><a href="/results/{{.ID}}">查看当前结果</a></p>
    <p><a href="/">返回首页</a></p>
</body>
</html>

results.html – 结果显示页:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>{{.Topic.Title}} – 结果</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 1rem; }
        .bar-wrapper { background: #eee; height: 24px; border-radius: 4px; margin: 0.5rem 0; }
        .bar { background: #4CAF50; height: 100%; border-radius: 4px; }
        .result-item { margin-bottom: 1rem; }
        .votes { color: #666; }
    </style>
</head>
<body>
    <h1>{{.Topic.Title}} – 投票结果</h1>
    <p>总票数:{{.Total}}</p>
    {{range .Results}}
    <div class="result-item">
        <strong>{{.Name}}</strong> <span class="votes">({{.Votes}} 票)</span>
        <div class="bar-wrapper">
            <div class="bar" style="width:{{printf "%.1f" .Percentage}}%"></div>
        </div>
        <span>{{printf "%.1f" .Percentage}}%</span>
    </div>
    {{end}}
    <p><a href="/topic/{{.Topic.ID}}">继续投票</a> | <a href="/">返回首页</a></p>
</body>
</html>

注意模板中的 {{printf "%.1f" .Percentage}} 用于格式化小数。Go模板默认不支持printf,需要注册自定义函数或使用其他方式。为简单起见,我们可以在传递数据前将百分比格式化为字符串,或者注册template.FuncMap。下面给出修改建议。

模板函数的改进

renderTemplate中注册printf函数:

func renderTemplate(w http.ResponseWriter, name string, data interface{}) {
	funcMap := template.FuncMap{
		"printf": fmt.Sprintf,
	}
	tmpl, err := template.New(name).Funcs(funcMap).ParseFiles("templates/" + name)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	err = tmpl.Execute(w, data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

完整的 main.go

整合所有代码,并添加必要的导入包:

package main

import (
	"fmt"
	"html/template"
	"net/http"
	"strconv"
	"sync"
	"time"
)

type Option struct {
	ID    int
	Name  string
	Score int
}

type Topic struct {
	ID          int
	Title       string
	Description string
	Options     []Option
	Votes       map[int]int
	CreatedAt   time.Time
}

type Store struct {
	mu     sync.RWMutex
	topics map[int]*Topic
	nextID int
}

func NewStore() *Store {
	return &Store{
		topics: make(map[int]*Topic),
		nextID: 1,
	}
}

func (s *Store) AddTopic(title, desc string, options []Option) int {
	s.mu.Lock()
	defer s.mu.Unlock()
	id := s.nextID
	s.nextID++
	t := &Topic{
		ID:          id,
		Title:       title,
		Description: desc,
		Options:     options,
		Votes:       make(map[int]int),
		CreatedAt:   time.Now(),
	}
	s.topics[id] = t
	return id
}

func (s *Store) GetTopic(id int) *Topic {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.topics[id]
}

func (s *Store) Vote(topicID, optionID int) bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	t, ok := s.topics[topicID]
	if !ok {
		return false
	}
	found := false
	for _, opt := range t.Options {
		if opt.ID == optionID {
			found = true
			break
		}
	}
	if !found {
		return false
	}
	t.Votes[optionID]++
	return true
}

func main() {
	store := NewStore()
	store.AddTopic("你最喜欢的编程语言?", "选择一项", []Option{
		{ID: 1, Name: "Go", Score: 5},
		{ID: 2, Name: "Python", Score: 4},
		{ID: 3, Name: "JavaScript", Score: 3},
	})
	store.AddTopic("给这部电影打分", "满分10分", []Option{
		{ID: 1, Name: "1-3分", Score: 2},
		{ID: 2, Name: "4-6分", Score: 6},
		{ID: 3, Name: "7-8分", Score: 8},
		{ID: 4, Name: "9-10分", Score: 10},
	})

	mux := http.NewServeMux()
	mux.HandleFunc("/", indexHandler(store))
	mux.HandleFunc("/topic/", topicHandler(store))
	mux.HandleFunc("/vote", voteHandler(store))
	mux.HandleFunc("/results/", resultsHandler(store))

	http.ListenAndServe(":8080", mux)
}

func indexHandler(s *Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		s.mu.RLock()
		topics := make([]*Topic, 0, len(s.topics))
		for _, t := range s.topics {
			topics = append(topics, t)
		}
		s.mu.RUnlock()
		renderTemplate(w, "index.html", topics)
	}
}

func topicHandler(s *Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		idStr := r.URL.Path[len("/topic/"):]
		id, err := strconv.Atoi(idStr)
		if err != nil {
			http.NotFound(w, r)
			return
		}
		topic := s.GetTopic(id)
		if topic == nil {
			http.NotFound(w, r)
			return
		}
		renderTemplate(w, "topic.html", topic)
	}
}

func voteHandler(s *Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}
		topicID, _ := strconv.Atoi(r.FormValue("topic"))
		optionID, _ := strconv.Atoi(r.FormValue("option"))
		if s.Vote(topicID, optionID) {
			http.Redirect(w, r, fmt.Sprintf("/results/%d", topicID), http.StatusSeeOther)
		} else {
			http.Error(w, "无效的投票", http.StatusBadRequest)
		}
	}
}

func resultsHandler(s *Store) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		idStr := r.URL.Path[len("/results/"):]
		id, _ := strconv.Atoi(idStr)
		topic := s.GetTopic(id)
		if topic == nil {
			http.NotFound(w, r)
			return
		}
		s.mu.RLock()
		total := 0
		for _, v := range topic.Votes {
			total += v
		}
		type OptionResult struct {
			Option
			Votes      int
			Percentage float64
		}
		var results []OptionResult
		for _, opt := range topic.Options {
			votes := topic.Votes[opt.ID]
			pct := 0.0
			if total > 0 {
				pct = float64(votes) / float64(total) * 100
			}
			results = append(results, OptionResult{
				Option:     opt,
				Votes:      votes,
				Percentage: pct,
			})
		}
		s.mu.RUnlock()
		data := struct {
			Topic   *Topic
			Results []OptionResult
			Total   int
		}{
			Topic:   topic,
			Results: results,
			Total:   total,
		}
		renderTemplate(w, "results.html", data)
	}
}

func renderTemplate(w http.ResponseWriter, name string, data interface{}) {
	funcMap := template.FuncMap{
		"printf": fmt.Sprintf,
	}
	tmpl, err := template.New(name).Funcs(funcMap).ParseFiles("templates/" + name)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	err = tmpl.Execute(w, data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

运行与测试

将上述代码保存为 main.go,并确保 templates/ 目录下有三个HTML模板文件。然后在终端运行:

go run main.go

打开浏览器访问 http://127.0.0.1:8080,即可看到投票主题列表。进入一个主题,选择选项后提交,系统会跳转到结果页面并用进度条展示百分比。同一主题可以多次投票(当前未做重复限制),方便观察数据变化。

扩展与改进

这个小系统可以继续增强:

  • 防止重复投票:结合 context 传递客户端标识,或使用Cookie/Session。

  • 持久化存储:将数据写入JSON文件或SQLite,保证重启后数据不丢失。

  • 评分计算:根据选项分数和票数加权平均,展示综合评分。

  • RESTful API:提供JSON接口供前端框架调用,前后端分离。

实现这些扩展时,核心的并发安全存储和模板渲染逻辑可以复用。

总结

本文展示了使用Go标准库构建在线投票与评分系统的完整过程。从数据结构设计、处理器编写到HTML模板渲染,所有组件都清晰可控。Go的高并发特性和简洁的语法使得这类Web小工具的开发效率非常高。你可以以此为基础,快速定制出符合实际需求的企业投票或在线评分应用。

Golang 投票系统 评分系统 Web应用开发 并发安全存储

免责声明:已尽一切努力确保本网站所含信息的准确性。网站部分内容来源于网络或由用户自行发表,内容观点不代表本站立场。本站是个人网站免费分享,内容仅供个人学习、研究或参考使用,如内容中引用了第三方作品,其版权归原作者所有。若内容触犯了您的权益,请联系我们进行处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。前端、网络、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握网站开发与运维所需的核心技术栈。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端逻辑,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。