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

Golang构建小型在线投票与评分系统

投票与评分是Web应用中常见的互动功能。无论是简单的每日话题投票,还是带有星级评分的产品评价,都可以用Go语言快速实现。本文将带你从零开始,使用Go标准库构建一个小型在线投票与评分系统,支持创建主题、提交投票以及查看结果。

项目概述

我们将实现一个单文件Web应用,包含以下功能:

  • 展示投票主题列表

  • 进入单个主题并提交投票

  • 实时查看投票结果(支持百分比和票数)

  • 简单的评分机制(选项附带分数)

  • 所有数据存储在内存中,无需数据库

技术栈仅使用Go net/httphtml/template 以及标准库的JSON序列化,便于理解核心原理。

准备工作

确保本地已安装Go(1.16+)。创建项目文件夹 voting-system,并初始化模块:

mkdir voting-system
cd voting-system
go mod init voting-system

项目目录结构如下:

voting-system/
├── main.go
└── templates/
    ├── index.html
    ├── topic.html
    └── results.html

数据模型与存储

首先定义投票涉及的核心结构体。一个投票主题(Topic)包含标题、描述、选项列表以及创建时间。每个选项(Option)拥有唯一ID、名称和分数(用于评分场景)。

package main

import (
	"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 // optionID -> count
	CreatedAt   time.Time
}

type VoteRecord struct {
	TopicID  int
	OptionID int
}

为简化并发访问,将数据存储在一个线程安全的结构体中:

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

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

添加几个辅助方法:AddTopicGetTopicVote。为了防止重复投票,这里通过客户端IP简单限制,实际场景可结合Cookie或用户登录。

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
}

路由与处理器

使用标准库的多路复用器 http.NewServeMux 注册路由。处理器函数直接读取模板并操作Store。

主入口:

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)
	}
}

投票处理器 处理POST请求,记录投票并重定向到结果页:

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)
	}
}

所有处理器都用到一个renderTemplate辅助函数,负责解析和执行模板:

func renderTemplate(w http.ResponseWriter, name string, data interface{}) {
	tmpl, err := template.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)
	}
}

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

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