مشاركة عبر


طرح الميزات للجماهير المستهدفة في تطبيق ويب Go Gin

في هذا الدليل، ستستخدم عامل تصفية الاستهداف لطرح ميزة للجماهير المستهدفة لتطبيق الويب Go Gene. لمزيد من المعلومات حول عامل تصفية الاستهداف، راجع طرح الميزات للجماهير المستهدفة.

Prerequisites

إنشاء تطبيق ويب مع علامة ميزة

في هذا القسم، يمكنك إنشاء تطبيق ويب يسمح للمستخدمين بتسجيل الدخول واستخدام علامة ميزة Beta التي قمت بإنشائها من قبل.

  1. أنشئ دليلا جديدا لمشروع Go وانتقل إليه:

    mkdir gin-targeting-quickstart
    cd gin-targeting-quickstart
    
  2. تهيئة وحدة نمطية Go جديدة:

    go mod init gin-targeting-quickstart
    
  3. قم بتثبيت باقات Go المطلوبة:

    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. قم بإنشاء دليل قوالب لقوالب HTML الخاصة بك وأضف ملفات HTML المطلوبة:

    mkdir templates
    

    أضف ملفات قوالب HTML التالية من مستودع GitHub وضعها في templates الدليل:

    • index.html - قالب الصفحة الرئيسية
    • beta.html - قالب الصفحة التجريبية
    • login.html - قالب صفحة تسجيل الدخول
  5. إنشاء ملف باسم appconfig.go بالمحتوى التالي. يمكنك الاتصال بمخزن App Configuration باستخدام معرف Microsoft Entra (مستحسن) أو سلسلة الاتصال.

    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
    }
    

استخدام الاستهداف مع علامات الميزات

  1. إنشاء ملف باسم main.go بالمحتوى التالي.

    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. قم بتمكين تحديث التكوين وعلامة الميزات من تكوين تطبيق Azure باستخدام البرامج الوسيطة.

    // 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. قم بإعداد المسارات بالمحتوى التالي:

    // 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 المحتوى التالي:

    // 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. بعد الانتهاء من الخطوات السابقة ، يجب أن يحتوي ملفك main.go الآن على التنفيذ الكامل كما هو موضح أدناه:

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

تصفية الاستهداف قيد التنفيذ

  1. قم بتعيين متغير البيئة للمصادقة وقم بتشغيل التطبيق:

    go mod tidy
    go run .
    
  2. افتح نافذة مستعرض، وانتقل إلى http://localhost:8080. مبدئيًا، لا يظهر عنصر إصدار بيتا على شريط الأدوات، لأنه تم تعيين خيار النسبة المئوية الافتراضية على 0.

    لقطة شاشة لتطبيق Gin على الويب قبل تسجيل دخول المستخدم لا تظهر أي وصول تجريبي.

  3. انقر فوق رابط تسجيل الدخول في الزاوية اليمنى العليا. حاول تسجيل الدخول باستخدام test@contoso.com.

  4. بعد تسجيل الدخول ك test@contoso.com، يظهر العنصر التجريبي الآن على شريط الأدوات، لأنه test@contoso.com تم تحديده كمستخدم مستهدف.

    لقطة شاشة لتطبيق الويب Gin بعد تسجيل دخول المستخدم المستهدف الذي يظهر الوصول التجريبي.

  5. الآن تسجيل الخروج وتسجيل الدخول ك testuser@contoso.com. لا يظهر عنصر Beta على شريط الأدوات، لأنه testuser@contoso.com محدد كمستخدم مستبعد.

الخطوات التالية

لمعرفة المزيد حول عوامل تصفية الميزات، تابع إلى المستندات التالية.

لمزيد من المعلومات حول مكتبة Go Feature Management، تابع إلى المستند التالي: