Build a Fast, Concurrent, Single‑File Lottery System with Go

This article shows how to quickly create a fair, concurrent, single‑binary lottery system for a company event using Go's standard library, sync.RWMutex for thread safety, and the embed package to bundle static assets without any external dependencies.

Code Wrench
Code Wrench
Code Wrench
Build a Fast, Concurrent, Single‑File Lottery System with Go

1. Core Design: Simplicity First

Because the deadline is tight, the solution avoids micro‑services, Redis, or MySQL and stores all data in memory using Go struct and slice types.

1.1 Data Model

Three core structures are defined:

type Employee struct {
    ID         int    `json:"id"`
    Name       string `json:"name"`
    Department string `json:"department"`
    JobNumber  string `json:"job_number"`
}

type Prize struct {
    ID      int    `json:"id"`
    Name    string `json:"name"`
    Count   int    `json:"count"` // total number of this prize
    Describe string `json:"describe"`
}

type Winner struct {
    Employee Employee `json:"employee"`
    Prize    Prize    `json:"prize"`
    Time     string   `json:"time"`
}

1.2 System State Management

The whole system state is managed by a LotterySystem struct protected by a sync.RWMutex to guarantee consistency when many users refresh the page or an admin clicks start/stop repeatedly.

type LotterySystem struct {
    mu           sync.RWMutex // read‑write lock protecting the fields below
    employees    []Employee    // all employees
    prizes       []Prize       // all prize types
    winners      []Winner      // recorded winners
    drawnIDs     map[int]bool  // set of employee IDs that have already won
    currentPrize int           // index of the prize currently being drawn
    isDrawing    bool          // true while the wheel is spinning
}

2. Core Logic Implementation

2.1 Lottery Logic: Prevent Over‑Allocation

The draw consists of two actions: start (begin rolling) and stop (select a winner). The critical part is the stopDrawing method, which runs under a write lock and performs the following steps:

Verify that a draw is in progress.

Collect employees that have not yet won by checking drawnIDs.

Randomly pick one employee from the remaining pool.

Record the winner, update drawnIDs, and clear the drawing flag.

func (ls *LotterySystem) stopDrawing() (*Winner, error) {
    ls.mu.Lock()
    defer ls.mu.Unlock()

    if !ls.isDrawing {
        return nil, fmt.Errorf("当前没有进行抽奖")
    }

    var available []Employee
    for _, emp := range ls.employees {
        if !ls.drawnIDs[emp.ID] {
            available = append(available, emp)
        }
    }
    if len(available) == 0 {
        ls.isDrawing = false
        return nil, fmt.Errorf("没有可抽奖的员工了")
    }

    winner := available[rand.Intn(len(available))]
    ls.drawnIDs[winner.ID] = true
    ls.isDrawing = false
    return &winnerRecord, nil
}

Note that the function directly builds the available slice instead of calling an external helper that would acquire another lock, avoiding a potential deadlock with sync.RWMutex.

2.2 Embedding Static Resources with embed

To let the organizer run the program with a single binary, all HTML templates are compiled into the executable using Go 1.16's embed package.

import "embed"

//go:embed templates/*
var templateFS embed.FS

Reading a template at runtime becomes a simple file read from the embedded filesystem:

func indexHandler(w http.ResponseWriter, r *http.Request) {
    data, err := templateFS.ReadFile("templates/index.html")
    if err != nil {
        // handle error
    }
    w.Write(data)
}

3. API and Interaction

A minimal RESTful API is exposed for the front‑end: GET /api/stats – total participants and number of winners. GET /api/current-prize – current prize and remaining count. POST /api/start – begin the rolling animation. POST /api/stop – stop the roll and return the winner.

A simple CORS middleware is added to ease front‑end testing:

func corsMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        // ... other headers if needed
        next(w, r)
    }
}

4. Running the Application

Compile and run the program:

go build -o lottery.exe main.go
./lottery.exe

Console output confirms the service is ready:

🎉 年会抽奖系统启动成功!
📍 访问地址: http://localhost:8080
🎲 准备好开始抽奖了吗?

Opening the URL in a browser (or projecting it) provides a fully functional, concurrency‑safe lottery system without any external database or configuration.

5. Summary

The project demonstrates several core Go capabilities:

Concurrency safety – correct use of sync.RWMutex to protect shared state.

Standard library power – net/http for the web server and encoding/json for data handling.

Engineering convenience – embed to bundle static assets, enabling single‑file distribution.

For ad‑hoc, time‑critical requirements like a company year‑end lottery, Go offers rapid development, easy deployment, and reliable concurrency handling.

Full source code is available at the following repositories:

GitHub: https://github.com/louis-xie-programmer/lottery-system

Gitee (China mirror): https://gitee.com/louis_xie/lottery-system

Lottery system screenshot
Lottery system screenshot
Lottery SystemConcurrencyGohttp serverembed
Code Wrench
Written by

Code Wrench

Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.