重要说明
此项 Azure 通信服务功能目前以预览版提供。 预览版中的功能已公开发布,可供所有新客户和现有Microsoft客户使用。
提供的预览版 API 和 SDK 不附带服务级别协议。 建议不要将它们用于生产工作负荷。 某些功能可能不受支持,或者功能可能受到限制。
有关详细信息,请参阅 Azure 预览版Microsoft补充使用条款。
在 Azure 通信服务聊天中,我们可以在通信用户之间启用文件共享。 Azure 通信服务聊天不同于 Teams 互操作性聊天(互操作聊天)。 如果要在互作聊天中启用文件共享,请参阅 在 Teams 互作性聊天中使用 UI 库添加文件共享。
本文介绍如何配置 Azure 通信服务 UI 库聊天复合以启用文件共享。 UI 库聊天组件提供了一组丰富的组件和 UI 控件,可用于实现文件共享功能。 我们将使用 Azure Blob 存储来启用通过聊天线程共享的文件的存储。
重要说明
Azure 通信服务不提供文件存储服务。 你需要使用自己的文件存储服务来共享文件。 本教程介绍如何使用 Azure Blob 存储。
下载代码
在 UI 库示例 - 使用 UI 聊天复合进行文件共享时访问本教程的完整代码。 如果要使用 UI 组件进行文件共享,请参阅 UI 库示例 - 使用 UI 组件进行文件共享。
先决条件
- 具有活动订阅的 Azure 帐户。 有关详细信息,请参阅 免费创建帐户。
- 在其中一个支持的平台上运行Visual Studio Code。
- Node.js、Active LTS 和 Maintenance LTS 版本(建议使用 10.14.1)。 可以使用
node --version
命令检查你的版本。 - 活动的通信服务资源和连接字符串。 创建通信服务资源。
若要使用这篇文章,需要了解如何设置和运行Chat Composite。 有关如何设置和运行聊天复合的详细信息,请参阅 ChatComposite 教程。
概述
UI 库聊天复合组件使开发人员能够将 URL 传递给通过 Azure 通信服务聊天服务发送的托管文件,并凭此支持文件共享。 UI 库呈现附加的文件,并支持通过多个扩展来配置发送的文件的外观。 更具体地说,它支持以下功能:
- “附加文件”按钮,用于通过 OS 文件选取器选取文件。
- 配置允许的文件扩展名。
- 启用/禁用多文件上传。
- 各种文件类型的文件图标。
- 带进度指示器的文件上传/下载卡片。
- 能够动态验证每次文件上传的结果并在 UI 中显示错误。
- 在发送之前,能够取消上传并删除上传的文件。
- 在 MessageThread 中查看上传的文件以及下载这些文件。 允许异步下载。
下图显示了上传和下载的文件共享方案的典型流。 标记为 Client Managed
的部分显示了开发人员需要在其中实现它们的构建基块。
使用 Azure Blob 设置文件存储
可以按照教程: 使用 Azure 函数将文件上传到 Azure Blob 存储 ,以编写文件共享所需的后端代码。
实现后,可以在 handleAttachmentSelection
函数中调用此 Azure 函数,以将文件上传到 Azure Blob 存储。 在本教程的剩余部分,我们假设你使用前面链接的 Azure Blob 存储教程生成了函数。
保护 Azure Blob 存储容器
本文假设 Azure Blob 存储容器允许公开访问上传的文件。 不建议将 Azure 存储容器公开用于实际生产应用程序。
若要下载文件,请上传到 Azure Blob 存储。 然后,可以使用共享访问签名(SAS)。 共享访问签名 (SAS) 提供对存储帐户中资源的安全委托访问。 使用 SAS 可以精细控制客户端访问数据的方式。
可下载的 GitHub 示例 展示了如何使用 SAS 创建 Azure 存储内容的 SAS URL。 此外,还可以 阅读有关 SAS 的详细信息。
UI 库要求设置 React 环境。 我们接下来就完成该操作。 如果你已有 React 应用,则可跳过此部分。
设置 React 应用
我们对本快速入门使用“create-react-app”模板。 有关详细信息,请参阅: React 入门
npx create-react-app ui-library-quickstart-composites --template typescript
cd ui-library-quickstart-composites
此过程结束时,你在 ui-library-quickstart-composites
文件夹中应该会有一个完整的应用程序。
在本快速入门中,我们将修改 src
文件夹中的文件。
安装包
使用 npm install
命令安装适用于 JavaScript 的 beta 版本 Azure 通信服务 UI 库。
npm install @azure/communication-react@1.16.0-beta.1
@azure/communication-react
将核心 Azure 通信服务指定为 peerDependencies
,使你能够在应用程序中以最为一致的方式使用核心库中的 API。 还需要安装以下库:
npm install @azure/communication-calling@1.24.1-beta.2
npm install @azure/communication-chat@1.6.0-beta.1
创建 React 应用
运行以下内容来测试“创建 React 应用”安装项:
npm run start
配置聊天复合组件以启用文件共享
我们需要替换初始化聊天复合组件所需的两个通用变量的变量值。
App.tsx
import { initializeFileTypeIcons } from '@fluentui/react-file-type-icons';
import {
ChatComposite,
AttachmentUploadTask,
AttachmentUploadOptions,
AttachmentSelectionHandler,
fromFlatCommunicationIdentifier,
useAzureCommunicationChatAdapter
} from '@azure/communication-react';
import React, { useMemo } from 'react';
initializeFileTypeIcons();
function App(): JSX.Element {
// Common variables
const endpointUrl = 'INSERT_ENDPOINT_URL';
const userId = ' INSERT_USER_ID';
const displayName = 'INSERT_DISPLAY_NAME';
const token = 'INSERT_ACCESS_TOKEN';
const threadId = 'INSERT_THREAD_ID';
// We can't even initialize the Chat and Call adapters without a well-formed token.
const credential = useMemo(() => {
try {
return new AzureCommunicationTokenCredential(token);
} catch {
console.error('Failed to construct token credential');
return undefined;
}
}, [token]);
// Memoize arguments to `useAzureCommunicationChatAdapter` so that
// a new adapter is only created when an argument changes.
const chatAdapterArgs = useMemo(
() => ({
endpoint: endpointUrl,
userId: fromFlatCommunicationIdentifier(userId) as CommunicationUserIdentifier,
displayName,
credential,
threadId
}),
[userId, displayName, credential, threadId]
);
const chatAdapter = useAzureCommunicationChatAdapter(chatAdapterArgs);
if (!!chatAdapter) {
return (
<>
<div style={containerStyle}>
<ChatComposite
adapter={chatAdapter}
options={{
attachmentOptions: {
uploadOptions: uploadOptions,
downloadOptions: downloadOptions,
}
}} />
</div>
</>
);
}
if (credential === undefined) {
return <h3>Failed to construct credential. Provided token is malformed.</h3>;
}
return <h3>Initializing...</h3>;
}
const uploadOptions: AttachmentUploadOptions = {
// default is false
disableMultipleUploads: false,
// define mime types
supportedMediaTypes: ["image/jpg", "image/jpeg"]
handleAttachmentSelection: attachmentSelectionHandler,
}
const attachmentSelectionHandler: AttachmentSelectionHandler = async (uploadTasks) => {
for (const task of uploadTasks) {
try {
const uniqueFileName = `${v4()}-${task.file?.name}`;
const url = await uploadFileToAzureBlob(task);
task.notifyUploadCompleted(uniqueFileName, url);
} catch (error) {
if (error instanceof Error) {
task.notifyUploadFailed(error.message);
}
}
}
}
const uploadFileToAzureBlob = async (uploadTask: AttachmentUploadTask) => {
// You need to handle the file upload here and upload it to Azure Blob Storage.
// This is how you can configure the upload
// Optionally, you can also update the file upload progress.
uploadTask.notifyUploadProgressChanged(0.2);
return {
url: 'https://sample.com/sample.jpg', // Download URL of the file.
};
配置上传方法以使用 Azure Blob 存储
为了启用 Azure Blob 存储上传,我们将使用以下代码修改之前声明的 uploadFileToAzureBlob
方法。 我们需要替换 Azure 函数信息以上传文件。
App.tsx
const uploadFileToAzureBlob = async (uploadTask: AttachmentUploadTask) => {
const file = uploadTask.file;
if (!file) {
throw new Error("uploadTask.file is undefined");
}
const filename = file.name;
const fileExtension = file.name.split(".").pop();
// Following is an example of calling an Azure Function to handle file upload
// The https://learn.microsoft.com/azure/developer/javascript/how-to/with-web-app/azure-function-file-upload
// tutorial uses 'username' parameter to specify the storage container name.
// the container in the tutorial is private by default. To get default downloads working in
// this sample, you need to change the container's access level to Public via Azure Portal.
const username = "ui-library";
// You can get function url from the Azure Portal:
const azFunctionBaseUri = "<YOUR_AZURE_FUNCTION_URL>";
const uri = `${azFunctionBaseUri}&username=${username}&filename=${filename}`;
const formData = new FormData();
formData.append(file.name, file);
const response = await axios.request({
method: "post",
url: uri,
data: formData,
onUploadProgress: (p) => {
// Optionally, you can update the file upload progress.
uploadTask.notifyUploadProgressChanged(p.loaded / p.total);
},
});
const storageBaseUrl = "https://<YOUR_STORAGE_ACCOUNT>.blob.core.windows.net";
return {
url: `${storageBaseUrl}/${username}/${filename}`,
};
};
错误处理
上传失败时,UI 库聊天复合组件将显示错误消息。
下面是演示如何由于大小验证错误而导致上传失败的示例代码:
App.tsx
import { AttachmentSelectionHandler } from from '@azure/communication-react';
const attachmentSelectionHandler: AttachmentSelectionHandler = async (uploadTasks) => {
for (const task of uploadTasks) {
if (task.file && task.file.size > 99 * 1024 * 1024) {
// Notify ChatComposite about upload failure.
// Allows you to provide a custom error message.
task.notifyUploadFailed('File too big. Select a file under 99 MB.');
}
}
}
export const attachmentUploadOptions: AttachmentUploadOptions = {
handleAttachmentSelection: attachmentSelectionHandler
};
文件下载 - 高级用法
默认情况下,UI 库会打开一个新的选项卡,指向你notifyUploadCompleted
时设置的 URL。 或者,你可以通过 actionsForAttachment
来使用自定义逻辑处理附件下载。 让我们看看一个示例。
App.tsx
import { AttachmentDownloadOptions } from "communication-react";
const downloadOptions: AttachmentDownloadOptions = {
actionsForAttachment: handler
}
const handler = async (attachment: AttachmentMetadata, message?: ChatMessage) => {
// here we are returning a static action for all attachments and all messages
// alternately, you can provide custom menu actions based on properties in `attachment` or `message`
return [defaultAttachmentMenuAction];
};
const customHandler = = async (attachment: AttachmentMetadata, message?: ChatMessage) => {
if (attachment.extension === "pdf") {
return [
{
title: "Custom button",
icon: (<i className="custom-icon"></i>),
onClick: () => {
return new Promise((resolve, reject) => {
// custom logic here
window.alert("custom button clicked");
resolve();
// or to reject("xxxxx") with a custom message
})
}
},
defaultAttachmentMenuAction
];
} else if (message?.senderId === "user1") {
return [
{
title: "Custom button 2",
icon: (<i className="custom-icon-2"></i>),
onClick: () => {
return new Promise((resolve, reject) => {
window.alert("custom button 2 clicked");
resolve();
})
}
},
// you can also override the default action partially
{
...defaultAttachmentMenuAction,
onClick: () => {
return new Promise((resolve, reject) => {
window.alert("default button clicked");
resolve();
})
}
}
];
}
}
如果在下载过程中遇到任何问题,并且需要通知用户,可以在 onClick
函数中 throw
错误并显示一条消息。 然后消息会显示在聊天组件顶部的错误栏中。
清理资源
如果想要清理并删除通信服务订阅,可以删除资源或资源组。 删除资源组同时也会删除与之相关联的任何其他资源。 可以详细了解如何清理 Azure 通信服务资源和清理 Azure 函数资源。