Dela via


Självstudie: Använda variantfunktionsflaggor i ett Go Gin-webbprogram

I den här självstudien använder du en variantfunktionsflagga för att hantera upplevelser för olika användarsegment i ett exempelprogram, Quote of the Day. Du använder den variantfunktionsflagga som skapats i Använd variantfunktionsflaggor. Innan du fortsätter måste du skapa variantfunktionsflaggan med namnet Greeting (Hälsning ) i appkonfigurationsarkivet.

Förutsättningar

Konfigurera ett Go Gin-webbprogram

  1. Skapa en ny katalog för Ditt Go-projekt och navigera till det:

    mkdir quote-of-the-day
    cd quote-of-the-day
    
  2. Skapa en ny Go-modul:

    go mod init quote-of-the-day
    
  3. Installera nödvändiga Go-paket:

    go get github.com/gin-gonic/gin
    go get github.com/gin-contrib/sessions
    go get github.com/gin-contrib/sessions/cookie
    go get github.com/microsoft/Featuremanagement-Go/featuremanagement
    go get github.com/microsoft/Featuremanagement-Go/featuremanagement/providers/azappconfig
    
  4. Skapa en mallkatalog för dina HTML-mallar och lägg till nödvändiga HTML-filer:

    mkdir templates
    

    Lägg till följande HTML-mallfiler från GitHub-lagringsplatsen och placera dem i templates katalogen:

  5. Skapa en fil med namnet appconfig.go med följande innehåll. Du kan ansluta till App Configuration-butik genom Microsoft Entra-ID, vilket rekommenderas, eller genom en anslutningssträng.

    package main
    
    import (
        "context"
        "log"
        "os"
    
        "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration"
        "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
    )
    
    func loadAzureAppConfiguration(ctx context.Context) (*azureappconfiguration.AzureAppConfiguration, error) {
        // Get the endpoint from environment variable
        endpoint := os.Getenv("AZURE_APPCONFIG_ENDPOINT")
        if endpoint == "" {
            log.Fatal("AZURE_APPCONFIG_ENDPOINT environment variable is not set")
        }
    
        // Create a credential using DefaultAzureCredential
        credential, err := azidentity.NewDefaultAzureCredential(nil)
        if err != nil {
            log.Fatalf("Failed to create credential: %v", err)
        }
    
        // Set up authentication options with endpoint and credential
        authOptions := azureappconfiguration.AuthenticationOptions{
            Endpoint:   endpoint,
            Credential: credential,
        }
    
        // Set up options to enable feature flags
        options := &azureappconfiguration.Options{
            FeatureFlagOptions: azureappconfiguration.FeatureFlagOptions{
                Enabled: true,
                RefreshOptions: azureappconfiguration.RefreshOptions{
                    Enabled: true,
                },
            },
        }
    
        // Load configuration from Azure App Configuration
        appConfig, err := azureappconfiguration.Load(ctx, authOptions, options)
        if err != nil {
            log.Fatalf("Failed to load configuration: %v", err)
        }
    
        return appConfig, nil
    }
    

Använd variantfunktionsflaggan

  1. Skapa en fil med namnet main.go med följande innehåll:

    package main
    
    import (
        "context"
        "fmt"
        "log"
        "net/http"
        "strings"
    
        "github.com/Azure/AppConfiguration-GoProvider/azureappconfiguration"
        "github.com/gin-contrib/sessions"
        "github.com/gin-contrib/sessions/cookie"
        "github.com/gin-gonic/gin"
        "github.com/microsoft/Featuremanagement-Go/featuremanagement"
        "github.com/microsoft/Featuremanagement-Go/featuremanagement/providers/azappconfig"
    )
    
    type Quote struct {
        Message string `json:"message"`
        Author  string `json:"author"`
    }
    
    type WebApp struct {
        featureManager *featuremanagement.FeatureManager
        appConfig      *azureappconfiguration.AzureAppConfiguration
        quotes         []Quote
    }
    
    func main() {
        // Load Azure App Configuration
        appConfig, err := loadAzureAppConfiguration(context.Background())
        if err != nil {
            log.Fatalf("Error loading Azure App Configuration: %v", err)
        }
    
        // Create feature flag provider
        featureFlagProvider, err := azappconfig.NewFeatureFlagProvider(appConfig)
        if err != nil {
            log.Fatalf("Error creating feature flag provider: %v", err)
        }
    
        // Create feature manager
        featureManager, err := featuremanagement.NewFeatureManager(featureFlagProvider, nil)
        if err != nil {
            log.Fatalf("Error creating feature manager: %v", err)
        }
    
        // Initialize quotes
        quotes := []Quote{
            {
                Message: "You cannot change what you are, only what you do.",
                Author:  "Philip Pullman",
            },
        }
    
        // Create web app
        app := &WebApp{
            featureManager: featureManager,
            appConfig:      appConfig,
            quotes:         quotes,
        }
    
        // Setup Gin with default middleware (Logger and Recovery)
        r := gin.Default()
    
        // Start server
        if err := r.Run(":8080"); err != nil {
            log.Fatalf("Failed to start server: %v", err)
        }
    
        fmt.Println("Starting Quote of the Day server on http://localhost:8080")
        fmt.Println("Open http://localhost:8080 in your browser")
        fmt.Println()
    }
    
  2. Aktivera konfigurations- och funktionsflaggauppdatering från Azure App Configuration med mellanprogrammet.

    // Existing code
    // ... ...
    
    func (app *WebApp) refreshMiddleware() gin.HandlerFunc {
        return func(c *gin.Context) {
            go func() {
                if err := app.appConfig.Refresh(context.Background()); err != nil {
                    log.Printf("Error refreshing configuration: %v", err)
                }
            }()
            c.Next()
        }
    }
    // The rest of existing code
    //... ...
    
  3. Konfigurera vägarna med följande innehåll:

    // Existing code
    // ... ...
    
    func (app *WebApp) setupRoutes(r *gin.Engine) {
        // Setup sessions
        store := cookie.NewStore([]byte("secret-key-change-in-production"))
        store.Options(sessions.Options{
            MaxAge:   3600, // 1 hour
            HttpOnly: true,
            Secure:   false, // Set to true in production with HTTPS
        })
        r.Use(sessions.Sessions("session", store))
    
        r.Use(app.refreshMiddleware())
    
        // Load HTML templates
        r.LoadHTMLGlob("templates/*.html")
        // Routes
        r.GET("/", app.homeHandler)
        r.GET("/login", app.loginPageHandler)
        r.POST("/login", app.loginHandler)
        r.GET("/logout", app.logoutHandler)
    }
    
    // Home page handler
    func (app *WebApp) homeHandler(c *gin.Context) {
        session := sessions.Default(c)
        username := session.Get("username")
        quote := app.quotes[0]
    
        var greetingMessage string
        var targetingContext featuremanagement.TargetingContext
        if username != nil {
            // Create targeting context for the user
            targetingContext = createTargetingContext(username.(string))
    
            // Get the Greeting variant for the current user
            if variant, err := app.featureManager.GetVariant("Greeting", targetingContext); err != nil {
                log.Printf("Error getting Greeting variant: %v", err)
            } else if variant != nil && variant.ConfigurationValue != nil {
                // Extract the greeting message from the variant configuration
                if configValue, ok := variant.ConfigurationValue.(string); ok {
                    greetingMessage = configValue
                }
            }
        }
    
        c.HTML(http.StatusOK, "index.html", gin.H{
            "title":           "Quote of the Day",
            "user":            username,
            "greetingMessage": greetingMessage,
            "quote":           quote,
        })
    }
    
    func (app *WebApp) loginPageHandler(c *gin.Context) {
        c.HTML(http.StatusOK, "login.html", gin.H{
            "title": "Login - Quote of the Day",
        })
    }
    
    func (app *WebApp) loginHandler(c *gin.Context) {
        email := strings.TrimSpace(c.PostForm("email"))
    
        // Basic validation
        if email == "" {
            c.HTML(http.StatusOK, "login.html", gin.H{
                "title": "Login - Quote of the Day",
                "error": "Email cannot be empty",
            })
            return
        }
    
        if !strings.Contains(email, "@") {
            c.HTML(http.StatusOK, "login.html", gin.H{
                "title": "Login - Quote of the Day",
                "error": "Please enter a valid email address",
            })
            return
        }
    
        // Store email in session
        session := sessions.Default(c)
        session.Set("username", email)
        if err := session.Save(); err != nil {
            log.Printf("Error saving session: %v", err)
        }
    
        c.Redirect(http.StatusFound, "/")
    }
    
    func (app *WebApp) logoutHandler(c *gin.Context) {
        session := sessions.Default(c)
        session.Clear()
        if err := session.Save(); err != nil {
            log.Printf("Error saving session: %v", err)
        }
        c.Redirect(http.StatusFound, "/")
    }
    
    // Helper function to create TargetingContext
    func createTargetingContext(userID string) featuremanagement.TargetingContext {
        targetingContext := featuremanagement.TargetingContext{
            UserID: userID,
            Groups: []string{},
        }
    
        if strings.Contains(userID, "@") {
            parts := strings.Split(userID, "@")
            if len(parts) == 2 {
                domain := parts[1]
                targetingContext.Groups = append(targetingContext.Groups, domain) // Add domain as group
            }
        }
    
        return targetingContext
    }
    // The rest of existing code
    //... ...
    
  4. main.go Uppdatera med följande innehåll:

    // Existing code
    // ... ...
     r := gin.Default()
    
     // Setup routes
     app.setupRoutes(r)
    
     // Start server
     if err := r.Run(":8080"); err != nil {
        log.Fatalf("Failed to start server: %v", err)
    }
    // The rest of existing code
    // ... ...
    

Skapa och köra appen

  1. Ange miljövariabeln för autentisering och kör programmet:

    go mod tidy
    go run .
    
  2. Öppna din webbläsare och gå till http://localhost:8080. Välj Logga in längst upp till höger för att logga in som usera@contoso.com.

    Skärmbild av Gin-webbappen innan användaren loggar in.

  3. När du har loggat in visas ett långt hälsningsmeddelande för usera@contoso.com.

    Skärmbild av Gin-webbappen som visar ett långt meddelande för användaren.

  4. Klicka på Logga ut och logga in som userb@contoso.com. Du ser det enkla hälsningsmeddelandet.

    Skärmbild av Gin-webbappen som visar ett enkelt meddelande för användaren.

    Anmärkning

    Det är viktigt för den här självstudien att använda dessa namn exakt. Så länge funktionen har konfigurerats som förväntat bör de två användarna se olika varianter.

Nästa steg

Om du vill veta mer om funktionshantering i Go fortsätter du till följande dokument: