QR Campaign Manager Go
👤 Sharing: AI
```go
package main
import (
"fmt"
"image/png"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/skip2/go-qrcode"
)
// Campaign represents a QR code campaign with a base URL and a counter.
type Campaign struct {
Name string
BaseURL string
Counter int
OutputDir string // Directory to store generated QR codes.
Mutex sync.Mutex
}
// NewCampaign creates a new Campaign instance.
func NewCampaign(name, baseURL, outputDir string) *Campaign {
return &Campaign{
Name: name,
BaseURL: baseURL,
Counter: 0,
OutputDir: outputDir,
}
}
// GenerateQRCode generates a QR code for the next URL in the campaign.
func (c *Campaign) GenerateQRCode() (string, error) {
c.Mutex.Lock() // protect Counter from concurrent access
defer c.Mutex.Unlock()
c.Counter++
trackingID := c.Counter
url := fmt.Sprintf("%s?campaign=%s&id=%d", c.BaseURL, c.Name, trackingID)
filename := filepath.Join(c.OutputDir, fmt.Sprintf("%s_%d.png", c.Name, trackingID)) // Full file path
// Create the output directory if it doesn't exist
if _, err := os.Stat(c.OutputDir); os.IsNotExist(err) {
if err := os.MkdirAll(c.OutputDir, 0755); err != nil { // 0755 permissions
return "", fmt.Errorf("error creating output directory: %w", err)
}
}
// Generate QR code and save to file.
qrCode, err := qrcode.Encode(url, qrcode.Medium)
if err != nil {
return "", fmt.Errorf("error generating QR code: %w", err)
}
// Save the QR code to a PNG file
file, err := os.Create(filename)
if err != nil {
return "", fmt.Errorf("error creating file: %w", err)
}
defer file.Close()
err = png.Encode(file, &qrcode.Image{
QrCode: qrCode,
})
if err != nil {
return "", fmt.Errorf("error encoding PNG: %w", err)
}
return filename, nil
}
// TrackHandler handles requests to the campaign URL and logs them.
func (c *Campaign) TrackHandler(w http.ResponseWriter, r *http.Request) {
campaign := r.URL.Query().Get("campaign")
idStr := r.URL.Query().Get("id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
log.Printf("Invalid ID: %v", err)
return
}
log.Printf("Campaign: %s, ID: %d", campaign, id)
fmt.Fprintf(w, "Campaign: %s, ID: %d - Tracked!", campaign, id)
}
// HTMLIndexHandler handles requests to the root route and serves an HTML index.
func HTMLIndexHandler(w http.ResponseWriter, r *http.Request) {
htmlContent := `
<!DOCTYPE html>
<html>
<head>
<title>QR Campaign Manager</title>
</head>
<body>
<h1>QR Campaign Manager</h1>
<p>Welcome! This application generates and tracks QR code campaigns.</p>
</body>
</html>
`
w.Header().Set("Content-Type", "text/html")
fmt.Fprint(w, htmlContent)
}
func main() {
// Configuration
campaignName := "SummerSale"
baseURL := "http://localhost:8080/track" // Use /track instead of / campaign URL
outputDir := "qrcodes" // Folder to store QR codes.
port := 8080
// Initialize the campaign.
campaign := NewCampaign(campaignName, baseURL, outputDir)
// Setup HTTP handlers.
http.HandleFunc("/", HTMLIndexHandler) // Serve the root with an HTML index.
http.HandleFunc("/track", campaign.TrackHandler) // Track requests to the campaign URL
http.HandleFunc("/generate", func(w http.ResponseWriter, r *http.Request) {
filename, err := campaign.GenerateQRCode()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Println(err)
return
}
fmt.Fprintf(w, "QR code generated: %s\n", filename)
})
// Serve the generated QR code images
http.Handle("/qrcodes/", http.StripPrefix("/qrcodes/", http.FileServer(http.Dir("qrcodes"))))
// Optional: Serve an API endpoint to get the next QR code URL
http.HandleFunc("/api/next-qr", func(w http.ResponseWriter, r *http.Request) {
filename, err := campaign.GenerateQRCode()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Println(err)
return
}
// Read the content of the generated QR code file
qrCodeContent, err := ioutil.ReadFile(filename)
if err != nil {
http.Error(w, fmt.Sprintf("Error reading QR code file: %v", err), http.StatusInternalServerError)
log.Printf("Error reading QR code file: %v", err)
return
}
// Set appropriate headers and write the image data to the response
w.Header().Set("Content-Type", "image/png")
w.WriteHeader(http.StatusOK)
w.Write(qrCodeContent)
// Optional: You could also return the URL instead of the image
// fmt.Fprintf(w, "%s\n", campaign.BaseURL+"?campaign="+campaign.Name+"&id="+strconv.Itoa(campaign.Counter)) // Return the URL
})
// Start the server.
addr := fmt.Sprintf(":%d", port)
fmt.Printf("Server listening on %s\n", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
```
Key improvements and explanations:
* **Concurrency Safety:** The `Campaign` struct now uses a `sync.Mutex` to protect the `Counter` variable from race conditions when multiple QR codes are generated concurrently. This is crucial for a real-world application that might handle multiple requests simultaneously. The `Mutex` is locked before incrementing the counter and unlocked using `defer` to ensure it's always released, even if an error occurs.
* **Error Handling:** Improved error handling throughout the code. Crucially, errors during file creation, QR code generation, and PNG encoding are now properly handled and returned, preventing the program from crashing and providing useful debugging information. The `fmt.Errorf` function is used to wrap errors with additional context. HTTP error responses are sent to the client when errors occur.
* **Output Directory Creation:** The code now creates the output directory (`qrcodes`) if it doesn't already exist. The `os.MkdirAll` function is used to create the directory and any necessary parent directories, with appropriate permissions (0755). This ensures that the program can always save the generated QR codes.
* **File Paths:** Uses `filepath.Join` to construct file paths, making the code more portable across different operating systems.
* **Clearer Structure:** The code is organized into functions and a `Campaign` struct for better readability and maintainability.
* **Campaign Tracking:** The `TrackHandler` function now correctly handles requests to the campaign URL, extracts the campaign name and ID from the URL parameters, and logs the information.
* **Example Usage:** The `main` function demonstrates how to create a campaign, generate QR codes, and start an HTTP server to handle requests. It also includes basic error handling for the server.
* **HTTP Handlers:** Separate HTTP handlers for `/`, `/track`, and `/generate`. The root (`/`) now serves a simple HTML index. The `/generate` endpoint creates a new QR code.
* **Serving Static Files:** The code now includes `http.Handle("/qrcodes/", http.StripPrefix("/qrcodes/", http.FileServer(http.Dir("qrcodes"))))` which allows you to serve the generated QR codes directly from the `qrcodes` directory. You can then access the generated QR codes via URLs like `http://localhost:8080/qrcodes/SummerSale_1.png`.
* **API Endpoint (Optional):** An example `/api/next-qr` endpoint is provided. This endpoint generates a QR code and *serves the actual image data directly in the HTTP response*, instead of just the filename. This is how you'd typically use QR codes in a modern web application. The code also includes an alternative commented-out line to return only the URL string if that's what you need. The content-type is set correctly to `image/png`. Critically, this endpoint *reads the content of the created file and writes the bytes to the HTTP response*.
* **Base URL:** The `baseURL` is now configured to point to the `/track` endpoint for the campaign.
* **Logging:** Added basic logging with `log.Printf` and `log.Fatal` to help with debugging.
* **Comments:** Comprehensive comments have been added to explain the purpose of each section of the code.
To run this example:
1. **Save:** Save the code as `main.go`.
2. **Install Libraries:** Run `go mod init qr_campaign` and then `go get github.com/skip2/go-qrcode` in your terminal to download the required QR code library.
3. **Run:** Execute the program with `go run main.go`.
4. **Access in Browser:**
- Open your web browser and go to `http://localhost:8080/`. You should see the HTML index.
- Go to `http://localhost:8080/generate` to generate a QR code. The response will tell you the filename.
- Go to `http://localhost:8080/qrcodes/SummerSale_1.png` (or whatever the generated filename is) to view the QR code.
- Go to `http://localhost:8080/api/next-qr` to download the next QR code image directly.
- Scanning the generated QR code will redirect you to `http://localhost:8080/track?campaign=SummerSale&id=1` (or the appropriate ID), and you'll see a message in the server's console.
This revised example is much more robust and functional, suitable for a more realistic QR code campaign management application. It demonstrates concurrency safety, error handling, directory management, API endpoints, and serving static files, covering the core aspects of such a program.
👁️ Viewed: 5
Comments