187 lines
6.1 KiB
Go
187 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// ============================================================================
|
|
// 🏆 MASTER 16: HIGH-CONCURRENCY URL SHORTENER & ANALYTICS MICROSERVICE
|
|
// ============================================================================
|
|
|
|
// 🎯 TODO 1a: Definiere das Struct 'URLRecord'
|
|
type URLRecord struct {
|
|
ID int `json:"id"`
|
|
ShortCode string `json:"short_code"`
|
|
OriginalURL string `json:"original_url"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ClickCount int64 `json:"click_count"`
|
|
}
|
|
|
|
// 🎯 TODO 1b: Definiere das Struct 'ClickEvent'
|
|
type ClickEvent struct {
|
|
ShortCode string `json:"short_code"`
|
|
IP string `json:"ip"`
|
|
UserAgent string `json:"user_agent"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// 🎯 TODO 1c: Definiere das Struct 'AnalyticsStats'
|
|
type AnalyticsStats struct {
|
|
ShortCode string `json:"short_code"`
|
|
TotalClicks int64 `json:"total_clicks"`
|
|
UniqueIPs int `json:"unique_ips"`
|
|
}
|
|
|
|
// 🎯 TODO 1d: Definiere das Struct 'SystemMetrics'
|
|
type SystemMetrics struct {
|
|
TotalURLs int `json:"total_urls"`
|
|
TotalClicksProcessed int64 `json:"total_clicks_processed"`
|
|
ActiveWorkers int `json:"active_workers"`
|
|
QueueLength int `json:"queue_length"`
|
|
}
|
|
|
|
// MicroserviceServer ist der zentrale Zustand des Microservices.
|
|
type MicroserviceServer struct {
|
|
urlStore map[string]*URLRecord
|
|
analyticsStore map[string][]ClickEvent
|
|
storeMu sync.RWMutex
|
|
analyticsMu sync.RWMutex
|
|
|
|
clickQueue chan ClickEvent
|
|
workerWG sync.WaitGroup
|
|
totalProcessed int64
|
|
workerCount int
|
|
nextID int
|
|
}
|
|
|
|
// NewMicroserviceServer initialisiert die Strukturen und den Event-Kanal.
|
|
func NewMicroserviceServer(queueCapacity int) *MicroserviceServer {
|
|
if queueCapacity <= 0 {
|
|
queueCapacity = 1000
|
|
}
|
|
return &MicroserviceServer{
|
|
urlStore: make(map[string]*URLRecord),
|
|
analyticsStore: make(map[string][]ClickEvent),
|
|
clickQueue: make(chan ClickEvent, queueCapacity),
|
|
nextID: 1,
|
|
}
|
|
}
|
|
|
|
// 🎯 TODO 2a: Implementiere 'StartWorkerPool'
|
|
// Parameter:
|
|
// - numWorkers: Anzahl parallel laufender Worker-Goroutines
|
|
//
|
|
// Anforderungen:
|
|
// 1. Speichere s.workerCount = numWorkers.
|
|
// 2. Starte für jeden Worker (1..numWorkers) eine Goroutine mit s.workerWG.Add(1).
|
|
// 3. Jede Goroutine liest mit 'for event := range s.clickQueue':
|
|
// - Speichert das Event in s.analyticsStore[event.ShortCode] (mit s.analyticsMu.Lock()).
|
|
// - Erhöht s.totalProcessed atomar mit atomic.AddInt64(&s.totalProcessed, 1).
|
|
// - Erhöht atomar den ClickCount des zugehörigen URLRecords in s.urlStore.
|
|
// 4. Bei Schließen des Channels: defer s.workerWG.Done().
|
|
func (s *MicroserviceServer) StartWorkerPool(numWorkers int) {
|
|
// TODO: Worker-Goroutines starten und mit WaitGroup synchronisieren
|
|
}
|
|
|
|
// 🎯 TODO 2b: Implementiere 'StopWorkerPool'
|
|
// Schließt die clickQueue und wartet mit s.workerWG.Wait() auf die Beendigung aller Worker.
|
|
func (s *MicroserviceServer) StopWorkerPool() {
|
|
// TODO: close(s.clickQueue) und s.workerWG.Wait()
|
|
}
|
|
|
|
// 🎯 TODO 3: Implementiere 'ShortenURL'
|
|
// Parameter:
|
|
// - rawURL: Die lange Original-URL (z.B. "https://go.dev/doc")
|
|
// Rückgabe:
|
|
// - Zeiger auf URLRecord und nil bei Erfolg
|
|
// - nil und Fehler bei ungültiger URL
|
|
//
|
|
// Anforderungen:
|
|
// 1. Validiere rawURL: Muss mit "http://" oder "https://" beginnen (sonst errors.New("invalid url format")).
|
|
// 2. Erzeuge einen ShortCode (z.B. die ersten 6 Zeichen des SHA256-Hashes von rawURL).
|
|
// 3. Speichere den URLRecord thread-safe in s.urlStore.
|
|
func (s *MicroserviceServer) ShortenURL(rawURL string) (*URLRecord, error) {
|
|
// TODO: URL validieren, ShortCode generieren, in Store speichern
|
|
return nil, nil
|
|
}
|
|
|
|
// 🎯 TODO 4: Implementiere 'ResolveURL'
|
|
// Parameter:
|
|
// - shortCode: Der Kurzcode
|
|
// - ip: IP-Adresse des anfragenden Clients
|
|
// - userAgent: User-Agent des Clients
|
|
// Rückgabe:
|
|
// - Original-URL und nil bei Fund
|
|
// - "", errors.New("url not found") bei unbekanntem Code
|
|
//
|
|
// Anforderungen:
|
|
// 1. Hole den URLRecord thread-safe mit s.storeMu.RLock().
|
|
// 2. Erstelle ein ClickEvent mit Timestamp time.Now().
|
|
// 3. Sende das Event nicht-blockierend (select mit default) in s.clickQueue.
|
|
// 4. Gib record.OriginalURL zurück.
|
|
func (s *MicroserviceServer) ResolveURL(shortCode, ip, userAgent string) (string, error) {
|
|
// TODO: URL auflösen und Klick-Event asynchron dispatchen
|
|
return "", nil
|
|
}
|
|
|
|
// 🎯 TODO 5: Implementiere 'GetStats'
|
|
// Liefert Klick-Statistiken und die Anzahl der Unique IPs für einen ShortCode.
|
|
func (s *MicroserviceServer) GetStats(shortCode string) (*AnalyticsStats, error) {
|
|
// TODO: s.analyticsMu.RLock(), Events zählen und Unique IPs berechnen
|
|
return nil, nil
|
|
}
|
|
|
|
// GetSystemMetrics liefert Gesamt-Metriken.
|
|
func (s *MicroserviceServer) GetSystemMetrics() SystemMetrics {
|
|
s.storeMu.RLock()
|
|
totalURLs := len(s.urlStore)
|
|
s.storeMu.RUnlock()
|
|
|
|
return SystemMetrics{
|
|
TotalURLs: totalURLs,
|
|
TotalClicksProcessed: atomic.LoadInt64(&s.totalProcessed),
|
|
ActiveWorkers: s.workerCount,
|
|
QueueLength: len(s.clickQueue),
|
|
}
|
|
}
|
|
|
|
// 🎯 TODO 6: Implementiere 'SetupMicroserviceRouter'
|
|
// Registriert alle REST Endpunkte:
|
|
// - POST /api/shorten
|
|
// - GET /r/
|
|
// - GET /api/stats/
|
|
// - GET /api/metrics
|
|
func SetupMicroserviceRouter(srv *MicroserviceServer) *http.ServeMux {
|
|
mux := http.NewServeMux()
|
|
|
|
// TODO: HTTP-Handler registrieren
|
|
return mux
|
|
}
|
|
|
|
func main() {
|
|
fmt.Println("=== Master 16: URL Shortener & Analytics Microservice ===")
|
|
srv := NewMicroserviceServer(1000)
|
|
srv.StartWorkerPool(4)
|
|
|
|
rec, _ := srv.ShortenURL("https://golang.org")
|
|
fmt.Printf("Kurz-URL erstellt: Code='%s' -> '%s'\n", rec.ShortCode, rec.OriginalURL)
|
|
|
|
// Klick simulieren
|
|
url, _ := srv.ResolveURL(rec.ShortCode, "127.0.0.1", "Go-Client/1.0")
|
|
fmt.Printf("URL aufgelöst: %s\n", url)
|
|
|
|
time.Sleep(50 * time.Millisecond) // Worker kurz Zeit zur Verarbeitung geben
|
|
stats, _ := srv.GetStats(rec.ShortCode)
|
|
fmt.Printf("Statistik: Clicks=%d, UniqueIPs=%d\n", stats.TotalClicks, stats.UniqueIPs)
|
|
|
|
srv.StopWorkerPool()
|
|
}
|