你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
在本指南中,你将使用目标筛选器向 Go Gin Web 应用程序的目标受众推出功能。 有关目标筛选器的详细信息,请参阅《向目标受众推出功能》。
Prerequisites
- 拥有有效订阅的 Azure 帐户。 免费创建一个。
- 应用配置存储区,如 创建存储区的教程所示。
- 具有目标筛选器的功能标志。 创建功能标志。
- 转到 1.21 或更高版本。 有关安装 Go 的信息,请参阅 Go 下载页。
- Azure 应用程序配置 Go 提供程序 v1.1.0 或更高版本。
使用功能标志创建 Web 应用程序
在本部分,你将创建一个 Web 应用程序,它允许用户登录并使用之前创建的 Beta 功能标志。
为 Go 项目创建新目录并导航到它:
mkdir gin-targeting-quickstart cd gin-targeting-quickstart初始化新的 Go 模块:
go mod init gin-targeting-quickstart安装所需的 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为 HTML 模板创建模板目录,并添加所需的 HTML 文件:
mkdir templates从 GitHub 存储库添加以下 HTML 模板文件,并将其放在
templates目录中:-
index.html- 主页模板 -
beta.html- beta 页面模板 -
login.html- 登录页模板
-
创建包含以下内容的名为
appconfig.go的文件。 可以使用 Microsoft Entra ID (建议) 或连接字符串连接到应用程序配置存储区。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 }
使用目标定位和特性标志
创建包含以下内容的名为
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() }使用中间件从 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 //... ...使用以下内容设置路由:
// 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 //... ...将
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 // ... ...完成上述步骤后,文件
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() }
实际操作中的目标筛选器
设置用于身份验证的环境变量 并运行应用程序:
go mod tidy go run .打开浏览器窗口,然后转到
http://localhost:8080。 一开始,“默认百分比”选项设为 0,因此 Beta 项不在工具栏上显示。
单击右上角的 “登录” 链接。 请稍后使用
test@contoso.com尝试登录。登录
test@contoso.com后, Beta 项现在会显示在工具栏上,因为test@contoso.com指定为目标用户。
现在登出并以
testuser@contoso.com登录。 Beta 版项目不会显示在工具栏上,因为testuser@contoso.com被指定为排除的用户。
后续步骤
若要了解有关功能筛选器的详细信息,请继续学习以下文档。
有关 Go 功能管理库的详细信息,请继续阅读以下文档: