你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程:在 Node.js 应用程序中使用变体功能标志

在本教程中,你将使用变体功能标志来管理示例应用程序中不同用户段的体验,今日名言。 利用在使用变体功能标志创建的变体功能标志。 在继续操作之前,请确保在应用程序配置存储中创建名为 Greeting 的变体功能标志。

先决条件

创建 Node.js 应用程序

  1. 创建一个名为quote-of-the-day的文件夹,并初始化项目。

    mkdir quote-of-the-day
    cd quote-of-the-day
    npm init -y
    
  2. 安装以下包。

    npm install @azure/app-configuration-provider
    npm install @microsoft/feature-management
    npm install express
    
  3. 创建名为 server.js 的新文件,并添加以下代码。

    const express = require("express");
    const server = express();
    
    const appConfigEndpoint = process.env.AZURE_APPCONFIG_ENDPOINT;
    const { DefaultAzureCredential } = require("@azure/identity");
    const { load } = require("@azure/app-configuration-provider");
    const { FeatureManager, ConfigurationMapFeatureFlagProvider } = require("@microsoft/feature-management");
    
    let appConfig;
    let featureManager;
    async function initializeConfig() {
        appConfig = await load(appConfigEndpoint, new DefaultAzureCredential(), {
            featureFlagOptions: {
                enabled: true,
                refresh: {
                    enabled: true
                }
            }
        });
    
        const featureFlagProvider = new ConfigurationMapFeatureFlagProvider(appConfig);
        featureManager = new FeatureManager(featureFlagProvider);
    }
    
    function startServer() {
        // Use a middleware to refresh the configuration before each request
        server.use((req, res, next) => {
            appConfig.refresh();
            next();
        });
        server.use(express.json());
        // Serve static index.html from the current folder
        server.use(express.static("."));
    
        // This API returns the different greeting messages based on the segment the user belongs to.
        // It evaluates a variant feature flag based on user context. The greeting message is retrieved from the variant configuration.
        server.get("/api/getGreetingMessage", async (req, res) => {
            const { userId, groups } = req.query;
            const variant = await featureManager.getVariant("Greeting", { userId: userId, groups: groups ? groups.split(",") : [] });
            res.status(200).send({ message: variant?.configuration });
        });
    
        server.post("/api/like", (req, res) => {
            const { UserId } = req.body;
            if (UserId === undefined) {
                return res.status(400).send({ error: "UserId is required" });
            }
            // Here you would typically emit a 'like' event to compare variants.
            res.status(200).send();
        });
    
        const port = "8080";
        server.listen(port, () => {
            console.log(`Server is running at http://localhost:${port}`);
        });
    }
    
    // Initialize the configuration and start the server
    initializeConfig()
        .then(() => {
            startServer();
        })
        .catch((error) => {
            console.error("Failed to load configuration:", error);
            process.exit(1);
        });
    
  4. 创建名为 index.html 的新文件并添加以下代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Quote of the Day</title>
    <style>
        .heart-button {
        background-color: transparent;
        border: none;
        cursor: pointer;
        font-size: 24px;
        }
    
        .heart-button:hover {
        background-color: #F0F0F0;
        }
    </style>
    </head>
    <body>
    <div style="display: flex; flex-direction: column; min-height: 100vh; background-color: #f4f4f4;">
        <header style="background-color: white; border-bottom: 1px solid #eaeaea; display: flex; justify-content: space-between; align-items: center; font-family: 'Arial', sans-serif; font-size: 16px;">
        <div style="font-size: 1.25em; color: black;">QuoteOfTheDay</div>
        </header>
    
        <main style="display: flex; justify-content: center; align-items: center; flex-grow: 1;">
        <div style="background-color: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); max-width: 700px; position: relative; text-align: left;">
            <div id="quote-content">
            <h2 id="greeting">Quote of the Day</h2>
            <blockquote style="font-size: 2em; font-style: italic; color: #4EC2F7; margin: 0 0 20px 0; line-height: 1.4;">
                <p>"You cannot change what you are, only what you do."</p>
                <footer style="font-size: 0.55em; color: black; font-family: 'Arial', sans-serif; font-style: normal; font-weight: bold;">— Philip Pullman</footer>
            </blockquote>
            <div style="position: absolute; top: 10px; right: 10px; display: flex;">
                <button class="heart-button" id="like-button">
                <span id="heart-icon" style="color: #ccc">♥</span>
                </button>
            </div>
            </div>
            <div id="loading" style="display: none;">
            <p>Loading</p>
            </div>
        </div>
        </main>
    </div>
    
    <script>
        // extract URL parameters to simulate user login
        document.addEventListener('DOMContentLoaded', function() {
        const urlParams = new URLSearchParams(window.location.search);
        const currentUser = urlParams.get('userId') || '';
        let liked = false;
    
        const greetingElement = document.getElementById('greeting');
        const heartIcon = document.getElementById('heart-icon');
        const likeButton = document.getElementById('like-button');
        const quoteContent = document.getElementById('quote-content');
        const loadingElement = document.getElementById('loading');
    
        async function init() {
            quoteContent.style.display = 'none';
            loadingElement.style.display = 'block';
    
            const response = await fetch(`/api/getGreetingMessage?userId=${currentUser}`, { 
            method: "GET"
            });
            const result = await response.json();
            greetingElement.textContent = result.message || "";
            quoteContent.style.display = 'block';
            loadingElement.style.display = 'none';
        }
    
        likeButton.addEventListener('click', async function() {
            if (!liked) {
            const response = await fetch("/api/like", { 
                method: "POST", 
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ UserId: currentUser }),
            });
            }
            liked = !liked;
            heartIcon.style.color = liked ? 'red' : '#ccc';
        });
    
        init();
        });
    </script>
    </body>
    </html>
    

    为简单起见,该示例从 URL 查询参数(例如)userId中提取?userId=UserA,以模拟不同的用户标识。

运行应用程序

  1. 将名为 AZURE_APPCONFIG_ENDPOINT 的环境变量设置为 Azure 门户中应用商店的“概述”下找到的应用程序配置存储区的终结点

    如果使用 Windows 命令提示符,则请运行以下命令并重启命令提示符,这样更改才会生效:

    setx AZURE_APPCONFIG_ENDPOINT "<endpoint-of-your-app-configuration-store>"
    

    如果使用 PowerShell,请运行以下命令:

    $Env:AZURE_APPCONFIG_ENDPOINT = "<endpoint-of-your-app-configuration-store>"
    

    如果使用 macOS 或 Linux,则请运行以下命令:

    export AZURE_APPCONFIG_ENDPOINT='<endpoint-of-your-app-configuration-store>'
    
  2. 运行该应用程序。

    node server.js
    
  3. 打开浏览器并导航到 localhost:8080。 应会看到没有任何问候消息的应用的默认视图。

    “每日名言”应用程序的截图,显示用户没有收到问候消息。

  4. 可以使用 userId URL 中的查询参数来指定用户 ID。 访问 localhost:8080/?userId=UserA 并看到一条长问候语。

    每日名言应用程序的屏幕截图,显示用户的长问候信息。

  5. 请尝试不同的用户 ID,了解变体功能标志如何更改不同用户段的问候消息。 访问 localhost:8080/?userId=UserB 并看到较短的问候消息。

    “每日名言”应用程序的屏幕截图,其中显示了简单的用户问候消息。

后续步骤

有关 JavaScript 功能管理库完整功能概述,请参阅以下文档。