Compartir vía


Implementación de características para audiencias dirigidas en una aplicación web de Go Gin

En esta guía, usará el filtro de destino para implementar una característica en audiencias dirigidas para la aplicación web Go Gin. Para más información sobre el filtro de destino, consulte Implementación de características en audiencias de destino.

Prerequisites

Creación de una aplicación web con una marca de característica

En esta sección, crea una aplicación web que permite a los usuarios iniciar sesión y usar la marca de característica Beta que creó antes.

  1. Cree un directorio para el proyecto de Go y vaya a él:

    mkdir gin-targeting-quickstart
    cd gin-targeting-quickstart
    
  2. Inicialice un nuevo módulo de Go:

    go mod init gin-targeting-quickstart
    
  3. Instale los paquetes de Go necesarios:

    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. Cree un directorio de plantillas para las plantillas HTML y agregue los archivos HTML necesarios:

    mkdir templates
    

    Agregue los siguientes archivos de plantilla HTML desde el repositorio de GitHub y colóquelos en el templates directorio:

  5. Cree un archivo llamado appconfig.go con el siguiente contenido. Puede conectarse al almacén de App Configuration mediante Microsoft Entra ID (recomendado) o una cadena de conexión.

    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
    }
    

Uso de la selección de destino con marcas de características

  1. Cree un archivo llamado main.go con el siguiente contenido.

    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. Habilite la configuración y el refresco de banderas de características desde Azure App Configuration mediante el middleware.

    // 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. Configure las rutas con el siguiente contenido:

    // 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. Actualice el main.go con el siguiente contenido:

    // 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. Después de completar los pasos anteriores, el main.go archivo debe contener ahora la implementación completa, como se muestra a continuación:

    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()
    }
    

Filtro de selección de destino en acción

  1. Establezca la variable de entorno para la autenticación y ejecute la aplicación:

    go mod tidy
    go run .
    
  2. Abra una ventana del explorador y vaya a http://localhost:8080. Inicialmente, el elemento Beta no aparece en la barra de herramientas, porque la opción porcentaje predeterminado está establecida en 0.

    Captura de pantalla de la aplicación web Gin antes de que el usuario inicie sesión sin acceso beta.

  3. Haga clic en el vínculo Inicio de sesión en la esquina superior derecha. Pruebe a iniciar sesión con test@contoso.com.

  4. Después de iniciar sesión como test@contoso.com, el elemento Beta aparece ahora en la barra de herramientas, porque test@contoso.com se especifica como un usuario de destino.

    Captura de pantalla de la aplicación web Gin, mostrando el acceso beta tras el inicio de sesión del usuario objetivo.

  5. Ahora cierre sesión e inicie sesión como testuser@contoso.com. El elemento Beta no aparece en la barra de herramientas, ya que testuser@contoso.com se especifica como usuario excluido.

Pasos siguientes

Para obtener más información sobre los filtros de características, continúe con los siguientes documentos.

Para obtener más información sobre la biblioteca Go Feature Management, continúe con el siguiente documento: