在线问卷系统是常见的业务场景,使用Golang实现这类系统可以充分发挥其高并发、性能稳定的优势,完整流程包含问卷定义、答卷收集、结果统计三个核心环节。

核心需求与表结构设计
首先需要明确系统的基础功能:支持创建问卷、用户提交答卷、后台查看统计结果。对应的数据库表可以设计如下:
问卷表 survey
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int | 主键自增 |
| title | varchar(200) | 问卷标题 |
| question_list | text | 问题列表JSON,包含问题类型、选项等内容 |
| create_time | datetime | 创建时间 |
答卷表 answer
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int | 主键自增 |
| survey_id | int | 关联问卷ID |
| answer_content | text | 答卷内容JSON,对应问题ID和选择结果 |
| submit_time | datetime | 提交时间 |
答卷收集接口实现
使用Golang的net/http标准库可以快速开发答卷提交接口,接收前端传递的答卷数据并存储到数据库。以下是核心实现代码:
package main
import (
"database/sql"
"encoding/json"
"log"
"net/http"
"time"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
// 初始化数据库连接
func initDB() {
var err error
db, err = sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/survey_db")
if err != nil {
log.Fatal("数据库连接失败:", err)
}
// 设置连接池参数
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
}
// 答卷提交请求结构体
type SubmitAnswerRequest struct {
SurveyID int `json:"survey_id"`
AnswerContent string `json:"answer_content"`
}
// 答卷提交接口处理函数
func submitAnswerHandler(w http.ResponseWriter, r *http.Request) {
// 仅允许POST请求
if r.Method != http.MethodPost {
http.Error(w, "仅支持POST请求", http.StatusMethodNotAllowed)
return
}
// 解析请求参数
var req SubmitAnswerRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
http.Error(w, "参数解析失败", http.StatusBadRequest)
return
}
// 插入答卷数据
insertSQL := "INSERT INTO answer (survey_id, answer_content, submit_time) VALUES (?, ?, ?)"
_, err = db.Exec(insertSQL, req.SurveyID, req.AnswerContent, time.Now())
if err != nil {
log.Println("答卷插入失败:", err)
http.Error(w, "提交失败", http.StatusInternalServerError)
return
}
// 返回成功响应
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"code": 0,
"msg": "提交成功",
})
}
func main() {
initDB()
defer db.Close()
http.HandleFunc("/api/submit_answer", submitAnswerHandler)
log.Println("服务启动在 :8080 端口")
http.ListenAndServe(":8080", nil)
}
答卷统计逻辑实现
统计功能需要根据问卷的问题类型,对答卷内容做对应处理,比如单选题统计各选项选择人数,多选题统计每个选项的选择次数。以下是统计逻辑的示例代码:
// 统计结果结构体
type StatResult struct {
QuestionID int `json:"question_id"`
QuestionTitle string `json:"question_title"`
StatDetail map[string]int `json:"stat_detail"` // 选项ID:选择人数
}
// 统计答卷数据
func statSurveyAnswers(surveyID int) ([]StatResult, error) {
// 查询问卷问题列表
var questionListStr string
err := db.QueryRow("SELECT question_list FROM survey WHERE id = ?", surveyID).Scan(&questionListStr)
if err != nil {
return nil, err
}
var questionList []map[string]interface{}
json.Unmarshal([]byte(questionListStr), &questionList)
// 查询所有答卷
rows, err := db.Query("SELECT answer_content FROM answer WHERE survey_id = ?", surveyID)
if err != nil {
return nil, err
}
defer rows.Close()
// 初始化统计容器,key为问题ID,value为选项统计map
statMap := make(map[int]map[string]int)
for _, q := range questionList {
qID := int(q["id"].(float64))
statMap[qID] = make(map[string]int)
// 初始化所有选项为0
options := q["options"].([]interface{})
for _, opt := range options {
optInfo := opt.(map[string]interface{})
optID := optInfo["id"].(string)
statMap[qID][optID] = 0
}
}
// 遍历答卷统计
for rows.Next() {
var answerContentStr string
rows.Scan(&answerContentStr)
var answerContent map[string]interface{}
json.Unmarshal([]byte(answerContentStr), &answerContent)
// 遍历答卷中的每个问题答案
for qIDStr, ans := range answerContent {
// 转换问题ID为int
var qID int
fmt.Sscanf(qIDStr, "%d", &qID)
if stat, ok := statMap[qID]; ok {
// 处理单选和多选
switch ansVal := ans.(type) {
case string:
// 单选题,答案是选项ID字符串
stat[ansVal]++
case []interface{}:
// 多选题,答案是选项ID数组
for _, optID := range ansVal {
stat[optID.(string)]++
}
}
}
}
}
// 组装返回结果
var result []StatResult
for _, q := range questionList {
qID := int(q["id"].(float64))
qTitle := q["title"].(string)
result = append(result, StatResult{
QuestionID: qID,
QuestionTitle: qTitle,
StatDetail: statMap[qID],
})
}
return result, nil
}
注意事项
- 实际开发中需要对请求参数做合法性校验,比如校验问卷ID是否存在、答卷内容是否符合问题要求
- 如果问卷访问量较大,可以对统计结果做缓存,避免每次查询都遍历所有答卷
- 存储答卷内容时建议使用JSON格式,方便扩展不同的问题类型,不需要频繁修改表结构