Dela via


Distribuera funktioner till målgrupper i ett Go Gin-webbprogram

I den här guiden använder du målfiltret för att distribuera en funktion till målgrupper för ditt Go Gin-webbprogram. Mer information om målfiltret finns i Distribuera funktioner till målgrupper.

Prerequisites

Skapa ett webbprogram med en funktionsflagga

I det här avsnittet skapar du ett webbprogram som gör att användare kan logga in och använda den betafunktionsflagga som du skapade tidigare.

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

    mkdir gin-targeting-quickstart
    cd gin-targeting-quickstart
    
  2. Skapa en ny Go-modul:

    go mod init gin-targeting-quickstart
    
  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 appkonfigurationsarkivet med hjälp av Microsoft Entra-ID (rekommenderas) eller 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
    }
    

Målinriktning med funktionsflaggor

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

    package main
    
    import (
        "context"
        "fmt"
        "log"
        "net/http"
        "strings"
    
        "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 WebApp struct {
        featureManager *featuremanagement.FeatureManager
        appConfig      *azureappconfiguration.AzureAppConfiguration
    }
    
    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)
        }
    
        // Create web app
        app := &WebApp{
            featureManager: featureManager,
            appConfig:      appConfig,
        }
    
        // 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 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()
        }
    }
    
    func (app *WebApp) featureMiddleware() gin.HandlerFunc {
        return func(c *gin.Context) {
            // Get current user from session
            session := sessions.Default(c)
            username := session.Get("username")
    
            var betaEnabled bool
            var targetingContext featuremanagement.TargetingContext
            if username != nil {
                // Evaluate Beta feature with targeting context
                var err error
                targetingContext = createTargetingContext(username.(string))
                betaEnabled, err = app.featureManager.IsEnabledWithAppContext("Beta", targetingContext)
                if err != nil {
                    log.Printf("Error checking Beta feature with targeting: %v", err)
                }
            }
    
            c.Set("betaEnabled", betaEnabled)
            c.Set("user", username)
            c.Set("targetingContext", targetingContext)
            c.Next()
        }
    }
    
    // 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 {
                targetingContext.Groups = append(targetingContext.Groups, parts[1]) // Add domain as group
            }
        }
    
        return targetingContext
    }
    
    // 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())
        r.Use(app.featureMiddleware())
    
        // Load HTML templates
        r.LoadHTMLGlob("templates/*.html")
    
        // Routes
        r.GET("/", app.homeHandler)
        r.GET("/beta", app.betaHandler)
        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) {
        betaEnabled := c.GetBool("betaEnabled")
        user := c.GetString("user")
    
        c.HTML(http.StatusOK, "index.html", gin.H{
            "title":       "TestFeatureFlags",
            "betaEnabled": betaEnabled,
            "user":        user,
        })
    }
    
    // Beta page handler
    func (app *WebApp) betaHandler(c *gin.Context) {
        betaEnabled := c.GetBool("betaEnabled")
        if !betaEnabled {
            return
        }
    
        c.HTML(http.StatusOK, "beta.html", gin.H{
            "title": "Beta Page",
        })
    }
    
    func (app *WebApp) loginPageHandler(c *gin.Context) {
        c.HTML(http.StatusOK, "login.html", gin.H{
            "title": "Login",
        })
    }
    
    func (app *WebApp) loginHandler(c *gin.Context) {
        username := c.PostForm("username")
    
        // Basic validation - ensure username is not empty
        if strings.TrimSpace(username) == "" {
            c.HTML(http.StatusOK, "login.html", gin.H{
                "title": "Login",
                "error": "Username cannot be empty",
            })
            return
        }
    
        // Store username in session - any valid username is accepted
        session := sessions.Default(c)
        session.Set("username", username)
        session.Save()
        c.Redirect(http.StatusFound, "/")
    }
    
    func (app *WebApp) logoutHandler(c *gin.Context) {
        session := sessions.Default(c)
        session.Clear()
        session.Save()
        c.Redirect(http.StatusFound, "/")
    }
    
    // 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
    // ... ...
    
  5. När du har slutfört föregående steg main.go bör filen nu innehålla den fullständiga implementeringen enligt nedan:

    package main
    
    import (
        "context"
        "fmt"
        "log"
        "net/http"
        "strings"
    
        "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 WebApp struct {
        featureManager *featuremanagement.FeatureManager
        appConfig      *azureappconfiguration.AzureAppConfiguration
    }
    
    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()
        }
    }
    
    func (app *WebApp) featureMiddleware() gin.HandlerFunc {
        return func(c *gin.Context) {
            // Get current user from session
            session := sessions.Default(c)
            username := session.Get("username")
    
            var betaEnabled bool
            var targetingContext featuremanagement.TargetingContext
            if username != nil {
                // Evaluate Beta feature with targeting context
                var err error
                targetingContext = createTargetingContext(username.(string))
                betaEnabled, err = app.featureManager.IsEnabledWithAppContext("Beta", targetingContext)
                if err != nil {
                    log.Printf("Error checking Beta feature with targeting: %v", err)
                }
            }
    
            c.Set("betaEnabled", betaEnabled)
            c.Set("user", username)
            c.Set("targetingContext", targetingContext)
            c.Next()
        }
    }
    
    // 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 {
                targetingContext.Groups = append(targetingContext.Groups, parts[1]) // Add domain as group
            }
        }
    
        return targetingContext
    }
    
    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())
        r.Use(app.featureMiddleware())
    
        // Load HTML templates
        r.LoadHTMLGlob("templates/*.html")
    
        // Routes
        r.GET("/", app.homeHandler)
        r.GET("/beta", app.betaHandler)
        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) {
        betaEnabled := c.GetBool("betaEnabled")
        user := c.GetString("user")
    
        c.HTML(http.StatusOK, "index.html", gin.H{
            "title":       "TestFeatureFlags",
            "betaEnabled": betaEnabled,
            "user":        user,
        })
    }
    
    // Beta page handler
    func (app *WebApp) betaHandler(c *gin.Context) {
        betaEnabled := c.GetBool("betaEnabled")
        if !betaEnabled {
            return
        }
    
        c.HTML(http.StatusOK, "beta.html", gin.H{
            "title": "Beta Page",
        })
    }
    
    func (app *WebApp) loginPageHandler(c *gin.Context) {
        c.HTML(http.StatusOK, "login.html", gin.H{
            "title": "Login",
        })
    }
    
    func (app *WebApp) loginHandler(c *gin.Context) {
        username := c.PostForm("username")
    
        // Basic validation - ensure username is not empty
        if strings.TrimSpace(username) == "" {
            c.HTML(http.StatusOK, "login.html", gin.H{
                "title": "Login",
                "error": "Username cannot be empty",
            })
            return
        }
    
        // Store username in session - any valid username is accepted
        session := sessions.Default(c)
        session.Set("username", username)
        session.Save()
        c.Redirect(http.StatusFound, "/")
    }
    
    func (app *WebApp) logoutHandler(c *gin.Context) {
        session := sessions.Default(c)
        session.Clear()
        session.Save()
        c.Redirect(http.StatusFound, "/")
    }
    
    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)
        }
    
        // Create web app
        app := &WebApp{
            featureManager: featureManager,
            appConfig:      appConfig,
        }
    
        // Setup Gin with default middleware (Logger and Recovery)
        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)
        }
    
        fmt.Println("Starting server on http://localhost:8080")
        fmt.Println("Open http://localhost:8080 in your browser")
        fmt.Println()
    }
    

Målfilter i praktiken

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

    go mod tidy
    go run .
    
  2. Öppna ett webbläsarfönster och gå till http://localhost:8080. Till en början visas inte betaobjektet i verktygsfältet eftersom alternativet Standardprocent är inställt på 0.

    Skärmbild av Gin-webbappen före användarinloggning som inte visar någon betaåtkomst.

  3. Klicka på länken Inloggning i det övre högra hörnet. Prova att logga in med test@contoso.com.

  4. När du har loggat in som test@contoso.comvisas betaobjektet nu i verktygsfältet, eftersom test@contoso.com det har angetts som en målanvändare.

    Skärmbild av Gin-webbappen efter riktad användarinloggning som visar betaåtkomst.

  5. Logga nu ut och logga in som testuser@contoso.com. Betaobjektet visas inte i verktygsfältet eftersom testuser@contoso.com det anges som en exkluderad användare.

Nästa steg

Om du vill veta mer om funktionsfiltren fortsätter du till följande dokument.

Om du vill ha mer information om Go Feature Management-biblioteket fortsätter du till följande dokument: