共用方式為


在 ACS UI 連結庫中收集用戶意見反應

簡介

本完整指南旨在協助開發人員使用 Azure 服務進行後端處理,將增強的支援整合到 ACS UI 連結庫中。 本指南分成用戶端和伺服器端步驟,以便清楚且輕鬆地實作。

必要條件

  • Azure 訂用帳戶: 您需要有效的 Azure 訂用帳戶。 如果您沒有帳戶,您可以在 Azure 免費帳戶建立 免費帳戶
  • Azure 通訊服務 資源:ACS 資源必須使用通話和聊天功能。 您可以在 Azure 入口網站中建立一個。
  • 開發環境設定: 確定您的開發環境已針對一或多個目標平台設定 – Android、iOS 或 Web。
  • Azure 儲存體 帳戶:若要安全地儲存使用者意見反應和相關數據,則需要 Azure 儲存體 帳戶。
  • Node.js和Express.js: Node.js和Express.js的基本知識有助於設定伺服器端應用程式以接收和處理支援要求。
  • RESTful API 的知識: 瞭解如何建立及取用 RESTful API 有助於部署和設定客戶端和伺服器。
  • 客戶端開發技能: 開發 Android 或 iOS 應用程式的熟練程度。

備妥這些必要條件,可確保使用 Azure 通訊服務 和其他 Azure 資源整合完整的使用者意見反應系統,一個順暢的開始。

您學到什麼

在本指南中,您會取得整合 Azure 通訊服務 (ACS) 應用程式內使用者意見反應機制的完整見解。 重點是使用 Azure 後端服務來處理,透過 ACS UI 連結庫來增強客戶支援。 遵循本指南,開發人員會瞭解如何:

  • 實作客戶端意見反應擷取: 瞭解如何使用ACS UI連結庫直接從Android和iOS應用程式擷取使用者意見反應、錯誤記錄和支援要求。
  • 設定伺服器端應用程式:使用Express.js設定Node.js應用程式的逐步指示,以接收、處理及儲存 Azure Blob 儲存體 中的支援要求。 此伺服器包含處理檔案上傳的多部分/表單數據,以及安全地管理用戶數據。
  • 建立支援票證: 瞭解如何產生唯一的支援票證號碼,以及相關的應用程式數據來儲存使用者意見反應。
  • 利用 Azure Blob 儲存體:深入瞭解如何使用 Azure Blob 儲存體 來儲存意見反應和支援要求數據,確保支援有效率的擷取和分析的安全和結構化數據管理。
  • 增強應用程式可靠性和用戶滿意度: 開發人員可以藉由實作本指南中所述的策略,快速解決和解決用戶問題。

伺服器端設定

設定Node.js應用程式以處理支援要求

區段目標: 目標是使用Express.js建立Node.js應用程式,做為後端,以接收用戶的支援要求。 這些要求可能包含文字意見反應、錯誤記錄、螢幕快照和其他相關信息,有助於診斷和解決用戶問題。 應用程式會將此資料儲存在 Azure Blob 儲存體 中,以供組織且安全存取。

架構和工具

  • Express.js: 建置 Web 應用程式和 API 的Node.js架構。 它可作為伺服器設定和要求處理的基礎。
  • 可形成: 剖析表單數據的連結庫,特別針對處理多部分/表單數據而設計,通常用於檔案上傳。
  • Azure Blob 儲存體:用於儲存大量非結構化數據的 Microsoft Azure 服務。

步驟 1:環境設定

開始之前,請確定您的開發環境已準備就緒,並已安裝Node.js。 您也需要存取 Azure 儲存體 帳戶來儲存送出的數據。

  1. 安裝Node.js: 確定系統上已安裝Node.js。 您可以從Node.js下載。

  2. 建立 Azure Blob 儲存體 帳戶:如果您尚未這麼做,請透過 Azure 入口網站 建立 Azure 儲存體 帳戶。 此帳戶用來儲存支援要求數據。

  3. 收集必要的認證:請確定您有 Azure Blob 儲存體 帳戶的 連接字串。

步驟 2:應用程式設定

  1. 初始化新的Node.js專案:

    • 為您的專案建立新的目錄,並使用 初始化它 npm init 以建立 package.json 檔案。

    • 使用 npm 安裝Express.js、Formidable、Azure 儲存體 Blob SDK 和其他必要連結庫。

      npm install express formidable @azure/storage-blob uuid
      
  2. 伺服器實作:

    • 使用Express.js來設定在特定端點上接聽 POST 要求的基本 Web 伺服器。
    • 使用 Formidable 來剖析傳入表單資料、處理多部分 /表單數據內容。
    • 為每個支援要求產生唯一的票證號碼,可用來組織 Azure Blob 儲存體 中的數據,併為使用者提供參考。
    • 將結構化數據,例如使用者訊息和記錄檔元數據儲存在 Blob 儲存體 內的 JSON 檔案中。 將實際記錄檔和任何螢幕快照或附件儲存在相同票證目錄內的個別 Blob 中。
    • 提供端點來擷取支持詳細數據,其中包含從 Azure Blob 儲存體 擷取和顯示數據。
  3. 安全性考慮:

    • 請確定您的應用程式會驗證傳入數據,以防止惡意承載。
    • 使用環境變數安全地儲存敏感性資訊,例如您的 Azure 儲存體 連接字串。

步驟 3:執行和測試應用程式

  1. 環境變數:

    • 設定 Azure Blob 儲存體 連接字串 和其他任何敏感性資訊的環境變數。 例如,您可以使用 .env 檔案 (和 dotenv npm 套件來載入這些變數)。
  2. 執行伺服器:

    • 執行 node <filename>.js來啟動您的Node.js應用程式,其中 <filename> 是主伺服器檔案的名稱。
    • 使用適合 Web 開發的工具來驗證伺服器。

伺服器程式代碼:

這裡提供一個可開始的工作實作。 此程式代碼是專為示範從 ACS UI 範例應用程式建立票證而量身打造的基本實作。

const express = require('express');
const formidable = require('formidable');
const fs = require('fs').promises
const { BlobServiceClient } = require('@azure/storage-blob');
const { v4: uuidv4 } = require('uuid');
const app = express();
const connectionString = process.env.SupportTicketStorageConnectionString
const port = process.env.PORT || 3000;
const portPostfix = (!process.env.PORT || port === 3000 || port === 80 || port === 443) ? '' : `:${port}`;

app.use(express.json());

app.all('/receiveEvent', async (req, res) => {
    try {
        const form = new formidable.IncomingForm();
        form.parse(req, async (err, fields, files) => {
            if (err) {
                return res.status(500).send("Error processing request: " + err.message);
            }
            // Generate a unique ticket number
            const ticketNumber = uuidv4();
            const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
            const containerClient = blobServiceClient.getContainerClient('supporttickets');
            await containerClient.createIfNotExists();

            // Prepare and upload support data
            const supportData = {
                userMessage: fields.user_message,
                uiVersion: fields.ui_version,
                sdkVersion: fields.sdk_version,
                callHistory: fields.call_history
            };
            const supportDataBlobClient = containerClient.getBlockBlobClient(`${ticketNumber}/supportdata.json`);
            await supportDataBlobClient.upload(JSON.stringify(supportData), Buffer.byteLength(JSON.stringify(supportData)));

            // Upload log files
            Object.values(files).forEach(async (fileOrFiles) => {
                // Check if the fileOrFiles is an array (multiple files) or a single file object
                const fileList = Array.isArray(fileOrFiles) ? fileOrFiles : [fileOrFiles];
            
                for (let file of fileList) {
                    const blobClient = containerClient.getBlockBlobClient(`${ticketNumber}/logs/${file.originalFilename}`);
                    
                    // Read the file content into a buffer
                    const fileContent = await fs.readFile(file.filepath);
                    
                    // Now upload the buffer
                    await blobClient.uploadData(fileContent); // Upload the buffer instead of the file path
                }
            });
            // Return the ticket URL
            const endpointUrl = `${req.protocol}://${req.headers.host}${portPostfix}/ticketDetails?id=${ticketNumber}`;
            res.send(endpointUrl);
        });
    } catch (err) {
        res.status(500).send("Error processing request: " + err.message);
    }
});

// ticketDetails endpoint to serve details page
app.get('/ticketDetails', async (req, res) => {
    const ticketNumber = req.query.id;
    if (!ticketNumber) {
        return res.status(400).send("Ticket number is required");
    }

    // Fetch the support data JSON blob to display its contents
    try {
        const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
        const containerClient = blobServiceClient.getContainerClient('supporttickets');
        const blobClient = containerClient.getBlobClient(`${ticketNumber}/supportdata.json`);
        const downloadBlockBlobResponse = await blobClient.download(0);
        const downloadedContent = (await streamToBuffer(downloadBlockBlobResponse.readableStreamBody)).toString();
        const supportData = JSON.parse(downloadedContent);

        // Generate links for log files
        let logFileLinks = `<h3>Log Files:</h3>`;
        const listBlobs = containerClient.listBlobsFlat({ prefix: `${ticketNumber}/logs/` });
        for await (const blob of listBlobs) {
            logFileLinks += `<a href="/getLogFile?id=${ticketNumber}&file=${encodeURIComponent(blob.name.split('/')[2])}">${blob.name.split('/')[2]}</a><br>`;
        }

        // Send a simple HTML page with support data and links to log files
        res.send(`
            <h1>Ticket Details</h1>
            <p><strong>User Message:</strong> ${supportData.userMessage}</p>
            <p><strong>UI Version:</strong> ${supportData.uiVersion}</p>
            <p><strong>SDK Version:</strong> ${supportData.sdkVersion}</p>
            <p><strong>Call History:</strong> </p> <pre>${supportData.callHistory}</pre>
            ${logFileLinks}
        `);
    } catch (err) {
        res.status(500).send("Error fetching ticket details: " + err.message);
    }
});

// getLogFile endpoint to allow downloading of log files
app.get('/getLogFile', async (req, res) => {
    const { id: ticketNumber, file } = req.query;
    if (!ticketNumber || !file) {
        return res.status(400).send("Ticket number and file name are required");
    }

    try {
        const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
        const containerClient = blobServiceClient.getContainerClient('supporttickets');
        const blobClient = containerClient.getBlobClient(`${ticketNumber}/logs/${file}`);

        // Stream the blob to the response
        const downloadBlockBlobResponse = await blobClient.download(0);
        res.setHeader('Content-Type', 'application/octet-stream');
        res.setHeader('Content-Disposition', `attachment; filename=${file}`);
        downloadBlockBlobResponse.readableStreamBody.pipe(res);
    } catch (err) {
        res.status(500).send("Error downloading file: " + err.message);
    }
});

// Helper function to stream blob content to a buffer
async function streamToBuffer(stream) {
    const chunks = [];
    return new Promise((resolve, reject) => {
        stream.on('data', (chunk) => chunks.push(chunk));
        stream.on('end', () => resolve(Buffer.concat(chunks)));
        stream.on('error', reject);
    });
}


app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

客戶端設定

本節涵蓋客戶端設定,以及如何達成下列目標:

  1. 註冊用戶回報的問題。
  2. 串行化數據。
  3. 將它轉送至伺服器。
  4. 接收回應。
  5. 向用戶呈現回應。

在 Azure 通訊服務 (ACS) UI 連結庫中啟用使用者意見反應需要開發人員的動作。 藉由利用 onUserReportedIssueEventHandler 連結庫整合中的 ,開發人員可以啟用內建支援窗體,讓使用者可以直接回報問題。 本節會引導您設定客戶端意見反應表單。

在 Android 中實作客戶端意見反應擷取

啟用支援表單

  1. 事件處理程式註冊:

    • 若要在 Android 應用程式中啟用支援表單,請在應用程式生命週期的適當時間點註冊 onUserReportedIssueEventHandler 。 此註冊不僅可啟用表單,還能確保表單可供使用者看見且可存取。
  2. 表單可見性和輔助功能:

    • 已註冊 onUserReportedIssueEventHandler 的存在直接影響支援窗體的可見度。 如果沒有這個處理程式,窗體會保持隱藏在使用者介面中,轉譯它無法存取的問題報告。

擷取和處理支援事件

  1. 問題報告時的事件排放:

    • 當使用者透過啟用的支援窗體回報問題時,會 onUserReportedIssueEventHandler 擷取發出的事件。 這些事件會封裝與使用者回報問題相關的所有必要詳細數據,例如描述、錯誤記錄,以及可能的螢幕快照。
  2. 提交的數據準備:

    • 一旦使用者回報問題,下一個步驟會牽涉到準備伺服器提交的回報問題數據。 此準備包括將擷取的信息結構化成適合 HTTP 傳輸的格式,並符合伺服器的期望。

將問題數據提交至伺服器

  1. 異步資料傳輸:

    • 利用異步機制,將備妥的數據傳輸到指定的伺服器端點。 此方法可確保應用程式在背景傳送數據時保持回應,提供順暢的用戶體驗。
  2. 伺服器回應處理:

    • 提交數據時,處理伺服器回應十分重要。 此處理可能牽涉到剖析伺服器意見反應,以確認數據傳輸成功,並可能擷取提交問題的參考(例如票證號碼或 URL),以傳回給使用者。

提供使用者意見反應和通知

  1. 立即使用者意見反應:

    • 透過應用程式的使用者介面,立即通知使用者其問題報告提交的狀態。 針對成功的提交,請考慮提供提交問題的參考,讓用戶能夠追蹤其報告的進度。
  2. Android O 和更新版本通知策略:

    • 對於執行 Android O (API 層級 26) 和更新版本的裝置,請確定報告提交的特定通知通道實作。 此設定對於有效傳遞通知至關重要,而且是這些 Android 版本的需求。

藉由遵循這些步驟,開發人員可以使用 將強大的使用者意見反應機制整合到其 Android 應用程式中,以便 onUserReportedIssueEventHandler 有效率的問題報告和追蹤。 此程式不僅有助於及時解決用戶問題,而且可大幅提升應用程式的整體用戶體驗和滿意度。

Android 程式代碼範例

Kotlin 代碼段示範如何使用 Azure 通訊服務,整合系統來處理 Android 應用程式內使用者回報的問題。 此整合旨在啟用使用者與支援小組之間的直接通訊,以簡化支援程式。 以下是相關步驟的概觀:

  1. 事件擷取:系統會透過ACS UI連結庫接聽使用者回報的問題。 它會利用 onUserReportedIssueEventHandler 從應用程式的 UI 擷取意見反應,包括錯誤和用戶疑慮。

  2. 數據傳輸至伺服器:回報問題時,系統會封裝相關的數據,包括使用者訊息、錯誤記錄檔、版本和診斷資訊。 接著,此數據會使用異步 POST 要求傳送至伺服器端點,確保程式不會妨礙應用程式的效能。

  3. 使用者意見反應和通知:在提交之後,用戶會透過應用程式內通知立即通知其報告的狀態。 對於成功的提交,通知會包含提交票證的鏈接或參考,讓用戶能夠追蹤解決進度。

此設定不僅有助於迅速解決用戶問題,而且透過提供明確的支持和意見反應管道,大幅提升用戶滿意度和應用程式可靠性。

package com.azure.android.communication.ui.callingcompositedemoapp

import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.azure.android.communication.ui.calling.CallCompositeEventHandler
import com.azure.android.communication.ui.calling.models.CallCompositeCallHistoryRecord
import com.azure.android.communication.ui.calling.models.CallCompositeUserReportedIssueEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
import org.threeten.bp.format.DateTimeFormatter
import java.io.File
import java.io.IOException

/**
 * This class is responsible for handling user-reported issues within the Azure Communication Services Calling UI composite.
 * It implements the CallCompositeEventHandler interface to listen for CallCompositeUserReportedIssueEvents.
 * The class demonstrates how to send diagnostic information to a server endpoint for support purposes and
 * how to provide user feedback through notifications.
 */
class UserReportedIssueHandler : CallCompositeEventHandler<CallCompositeUserReportedIssueEvent> {
    // Flow to observe user reported issues.
    val userIssuesFlow = MutableStateFlow<CallCompositeUserReportedIssueEvent?>(null)

    // Reference to the application context, used to display notifications.
    lateinit var context: Application

    // Lazy initialization of the NotificationManagerCompat for managing notifications.
    private val notificationManager by lazy { NotificationManagerCompat.from(context) }

    /**
     * Handles the event when a user reports an issue.
     * - Creates a notification channel for Android O and above.
     * - Updates the userIssuesFlow with the new event data.
     * - Sends the event data including user message, app and SDK versions, call history, and log files to a server.
     */
    override fun handle(eventData: CallCompositeUserReportedIssueEvent?) {
        createNotificationChannel()
        userIssuesFlow.value = eventData
        eventData?.apply {
            sendToServer(
                userMessage,
                debugInfo.versions.azureCallingUILibrary,
                debugInfo.versions.azureCallingLibrary,
                debugInfo.callHistoryRecords,
                debugInfo.logFiles
            )
        }
    }

    /**
     * Prepares and sends a POST request to a server with the user-reported issue data.
     * Constructs a multipart request body containing the user message, app versions, call history, and log files.
     */
    private fun sendToServer(
        userMessage: String?,
        callingUIVersion: String?,
        callingSDKVersion: String?,
        callHistoryRecords: List<CallCompositeCallHistoryRecord>,
        logFiles: List<File>
    ) {
        if (SERVER_URL.isBlank()) { // Check if the server URL is configured.
            return
        }
        showProgressNotification()
        CoroutineScope(Dispatchers.IO).launch {
            val client = OkHttpClient()
            val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM).apply {
                userMessage?.let { addFormDataPart("user_message", it) }
                callingUIVersion?.let { addFormDataPart("ui_version", it) }
                callingSDKVersion?.let { addFormDataPart("sdk_version", it) }
                addFormDataPart(
                    "call_history",
                    callHistoryRecords.map { "\n\n${it.callStartedOn.format(DateTimeFormatter.BASIC_ISO_DATE)}\n${it.callIds.joinToString("\n")}" }
                        .joinToString("\n"))
                logFiles.filter { it.length() > 0 }.forEach { file ->
                    val mediaType = "application/octet-stream".toMediaTypeOrNull()
                    addFormDataPart("log_files", file.name, file.asRequestBody(mediaType))
                }
            }.build()

            val request = Request.Builder()
                .url("$SERVER_URL/receiveEvent")
                .post(requestBody)
                .build()

            client.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    CoroutineScope(Dispatchers.Main).launch {
                        onTicketFailed(e.message ?: "Unknown error")
                    }
                }

                override fun onResponse(call: Call, response: Response) {
                    CoroutineScope(Dispatchers.Main).launch {
                        if (response.isSuccessful) {
                            onTicketCreated(response.body?.string() ?: "No URL provided")
                        } else {
                            onTicketFailed("Server error: ${response.message}")
                        }
                    }
                }
            })
        }
    }

    /**
     * Displays a notification indicating that the issue ticket has been created successfully.
     * The notification includes a URL to view the ticket status, provided by the server response.
     */
    private fun onTicketCreated(url: String) {
        showCompletionNotification(url)
    }

    /**
     * Displays a notification indicating that the submission of the issue ticket failed.
     * The notification includes the error reason.
     */
    private fun onTicketFailed(error: String) {
        showErrorNotification(error)
    }

    companion object {
        // The server URL to which the user-reported issues will be sent. Must be configured.
        private const val SERVER_URL = "${INSERT_YOUR_SERVER_ENDPOINT_HERE}"
    }

    /**
     * Creates a notification channel for Android O and above.
     * This is necessary to display notifications on these versions of Android.
     */
    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name = "Report Submission"
            val descriptionText = "Notifications for report submission status"
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel = NotificationChannel("report_submission_channel", name, importance).apply {
                description = descriptionText
            }
            notificationManager.createNotificationChannel(channel)
        }
    }

    /**
     * Shows a notification indicating that the report submission is in progress.
     * This uses an indeterminate progress indicator to signify ongoing activity.
     */
    private fun showProgressNotification() {
        val notification = NotificationCompat.Builder(context, "report_submission_channel")
            .setContentTitle("Submitting Report")
            .setContentText("Your report is being submitted...")
            .setSmallIcon(R.drawable.image_monkey) // Replace with an appropriate icon for your app
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setProgress(0, 0, true) // Indeterminate progress
            .build()

        notificationManager.notify(1, notification)
    }

    /**
     * Shows a notification indicating that the report has been successfully submitted.
     * The notification includes an action to view the report status via a provided URL.
     */
    private fun showCompletionNotification(url: String) {
        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
        val pendingIntent = PendingIntent.getActivity(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val notification = NotificationCompat.Builder(context, "report_submission_channel")
            .setContentTitle("Report Submitted")
            .setContentText("Tap to view")
            .setSmallIcon(R.drawable.image_monkey) // Replace with an appropriate icon for your app
            .setContentIntent(pendingIntent)
            .setAutoCancel(true) // Removes notification after tap
            .build()

        notificationManager.notify(1, notification)
    }

    /**
     * Shows a notification indicating an error in submitting the report.
     * The notification includes the reason for the submission failure.
     */
    private fun showErrorNotification(error: String) {
        val notification = NotificationCompat.Builder(context, "report_submission_channel")
            .setContentTitle("Submission Error")
            .setContentText("Error submitting report\nReason: $error")
            .setSmallIcon(R.drawable.image_monkey) // Replace with an appropriate icon for your app
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .build()

        notificationManager.notify(1, notification)
    }
}

一旦您有事件的 Handler,您可以在建立呼叫復合時註冊它。

            callComposite.addOnUserReportedEventHandler(userReportedIssueEventHandler)

iOS 支援概觀

若要使用 Azure 通訊服務 (ACS) UI 連結庫在 iOS 應用程式中整合使用者意見反應集合,開發人員必須遵循結構化的方法。 此程式牽涉到擷取使用者意見反應,包括錯誤記錄檔和用戶資訊。 完成後,此資訊會提交至伺服器進行處理。 在本節中,我們會詳細說明完成這項工作所需的步驟。

在此範例中 ,我們使用 Alamofire 連結庫來處理將多部分表單,包括記錄檔傳送至伺服器。

實作支持表單

  1. 事件處理程式註冊:從註冊接聽用戶回報問題的事件處理程序開始。 此處理程式對於使用 ACS UI 連結庫的功能,直接從 iOS 應用程式的介面擷取意見反應非常重要。

  2. 表單的可見度和輔助功能:確定支援表單可供應用程式內的用戶輕鬆存取及看見。 表單的啟用會直接連結至事件處理程序的實作,而事件處理程式會在UI中觸發其外觀,讓使用者報告問題。

擷取和處理支援要求

  1. 用戶動作上的事件排放:當使用者透過支援表單回報問題時,事件處理程式會擷取此動作。 使用者對問題的描述、錯誤記錄檔和任何呼叫標識碼等資訊應該準備好傳送至伺服器。

  2. 提交的數據結構:將擷取的信息組織成適合傳輸的結構化格式。 以符合接收及處理支援要求之伺服器端點預期格式的方式準備數據。

將數據提交至伺服器

  1. 異步提交:利用異步網路呼叫將結構化數據傳送至伺服器。 此方法可確保應用程式保持回應,在背景傳輸數據時為使用者提供順暢的體驗。

  2. 處理伺服器回應:提交時,有效率地處理伺服器回應。 接收並剖析回應,以確認成功接收數據。 從剖析的回應中擷取支援票證連結,以便與用戶進行後續操作。

對使用者的意見反應和通知

  1. 立即認可:立即確認在應用程式中提交支援要求,為使用者提供已收到其報告的確認。

  2. 通知策略:實作將通知傳遞給使用者的策略,特別是在執行支援特定通知架構之 iOS 版本的裝置上。 您可以使用本機通知來通知使用者報告的狀態,或在問題解決時提供更新。

iOS 程式代碼範例

此 Swift 程式代碼範例概述擷取使用者回報問題的基本實作,並將其提交至伺服器進行處理。 此範例示範如何建構支援事件處理程式,包括使用者意見反應和應用程式診斷資訊,以及傳遞至伺服器。 此程式代碼也包含錯誤處理和使用者通知策略,以確保使用者體驗順暢。

下列範例設計成要安裝在事件處理程式內的勾點。

安裝

let onUserReportedIssueHandler: (CallCompositeUserReportedIssue) -> Void = { issue in
    // Add a hook to this method, and provide it the Server endpoint + a result callback
    sendSupportEventToServer(server: self.issueUrl, event: issue) { success, result in
        if success {
            // Success: Convey the result link back to the user
        } else {
            // Error: Let the user know something has happened
        }
    }
}

網路勾點

import Foundation
import UIKit
import Combine
import AzureCommunicationUICalling
import Alamofire

/// Sends a support event to a server with details from a `CallCompositeUserReportedIssue`.
/// - Parameters:
///   - server: The URL of the server where the event will be sent.
///   - event: The `CallCompositeUserReportedIssue` containing details about the issue reported by the user.
///   - callback: A closure that is called when the operation is complete.
///               It provides a `Bool` indicating success or failure, and a `String`
///               containing the server's response or an error message.
func sendSupportEventToServer(server: String,
                              event: CallCompositeUserReportedIssue,
                              callback: @escaping (Bool, String) -> Void) {
    // Construct the URL for the endpoint.
    let url = "\(server)/receiveEvent" // Ensure this is replaced with the actual server URL.

    // Extract debugging information from the event.
    let debugInfo = event.debugInfo

    // Prepare the data to be sent as key-value pairs.
    let parameters: [String: String] = [
        "user_message": event.userMessage, // User's message about the issue.
        "ui_version": debugInfo.versions.callingUIVersion, // Version of the calling UI.
        "call_history": debugInfo.callHistoryRecords
            .map { $0.callIds.joined(separator: ",") }
            .joined(separator: "\n") // Call history, formatted.
    ]

    // Define the headers for the HTTP request.
    let headers: HTTPHeaders = [
        .contentType("multipart/form-data")
    ]

    // Perform the multipart/form-data upload.
    AF.upload(multipartFormData: { multipartFormData in
        // Append each parameter as a part of the form data.
        for (key, value) in parameters {
            if let data = value.data(using: .utf8) {
                multipartFormData.append(data, withName: key)
            }
        }

        // Append log files.
        debugInfo.logFiles.forEach { fileURL in
            do {
                let fileData = try Data(contentsOf: fileURL)
                multipartFormData.append(fileData,
                                         withName: "log_files",
                                         fileName: fileURL.lastPathComponent,
                                         mimeType: "application/octet-stream")
            } catch {
                print("Error reading file data: \(error)")
            }
        }
    }, to: url, method: .post, headers: headers).response { response in
        // Handle the response from the server.
        switch response.result {
        case .success(let responseData):
            // Attempt to decode the response.
            if let data = responseData, let responseString = String(data: data, encoding: .utf8) {
                callback(true, responseString) // Success case.
            } else {
                callback(false, "Failed to decode response.") // Failed to decode.
            }
        case .failure(let error):
            // Handle any errors that occurred during the request.
            print("Error sending support event: \(error)")
            callback(false, "Error sending support event: \(error.localizedDescription)")
        }
    }
}

此 Swift 程式代碼示範如何使用 Azure 通訊服務,從 iOS 應用程式提交使用者回報的問題。 它會處理使用者意見反應的集合、診斷資訊的封裝,以及異步提交至伺服器端點。 此外,它提供實作意見反應機制的基礎,確保使用者知道其報告的狀態,並增強整體應用程式可靠性和用戶滿意度。

推論

使用 Azure 通訊服務 (ACS) 將使用者意見反應機制整合到應用程式,對於開發回應式和使用者導向的應用程式至關重要。 本指南提供清楚的路徑,可讓您使用 Android 和 iOS 應用程式的Node.js和客戶端意見反應擷取來設定伺服器端處理。 透過這類整合,開發人員可以增強應用程式可靠性和用戶滿意度,同時利用 Azure 的雲端服務進行有效率的數據管理。

本指南概述直接從應用程式擷取使用者意見反應、錯誤記錄和支援要求的實際步驟。 整合支援事件可確保處理意見反應的安全且有條理的方式,讓開發人員能夠快速解決和解決用戶問題,進而改善整體用戶體驗。

依照本指南中詳述的指示,開發人員可以改善其應用程式的回應性,並更符合使用者需求。 這些整合不僅有助於更有效地瞭解使用者意見反應,還利用雲端服務來確保順暢且有效的意見反應收集和處理機制。 歸根結底,整合使用者意見反應機制對於建立吸引人且可靠的應用程式至關重要,可優先處理用戶滿意度。