访问 CosmosDB 的机器人导致 "PreconditionFailed" 错误

Bots accessing CosmosDB cause "PreconditionFailed" error

我有几个机器人在访问 CosmosDB。他们通过来自客户端的 URL 参数获取 userId。

我的问题是在不同的实例中同时使用同一个机器人。

假设我开始与用户 ID“1”对话,输入一些数据,如姓名、年龄、性别。在谈话的过程中,我打开了另一个用户 ID 为“2”的网站。用户 2 输入名称。用户 1 完成配置文件创建。提示用户 2 输入年龄。突然,用户 2 的用户 ID 变为“1”,配置文件数据与用户 1 的用户数据相同。

我有时会收到以下错误:

{ code: 412,
body: ‘{“code”:“PreconditionFailed”,“message”:“Operation cannot be performed because one of the specified precondition is not met., \r\nRequestStartTime: 2019-03-07T20:57:19.6998897Z, RequestEndTime: 2019-03-07T20:57:19.7098745Z, Number of regions attempted: 1\r\nResponseTime: 2019-03-07T20:57:19.7098745Z, StoreResult: StorePhysicalAddress: rntbd://cdb-ms-prod-westeurope1-fd21.documents.azure.com:16817/apps/9bc5d0cc-9b7c-4b1d-9be2-0fa2654271c4/services/3507a423-5215-48ca-995a-8763ec527db8/partitions/940cd512-aa34-4c93-9596-743de41037da/replicas/131964420031154286p/, LSN: 172, GlobalCommittedLsn: 171, PartitionKeyRangeId: 0, IsValid: True, StatusCode: 412, SubStatusCode: 0, RequestCharge: 1.24, ItemLSN: -1, SessionToken: 172, UsingLocalLSN: False, TransportException: null, ResourceType: Document, OperationType: Replace\r\n, Microsoft.Azure.Documents.Common/2.2.0.0”}’,
activityId: ‘fbdaeedb-a73f-4052-bd55-b1417b10583f’ }

所以,机器人似乎以某种方式混淆了用户 ID。

我将 UserState 和 ConversationState 保存在本地存储中,因为我不需要将它们保存在我的数据库中:

// Local browser storage
const memoryStorageLocal = new MemoryStorage();

// ConversationState and UserState
const conversationState = new ConversationState(memoryStorageLocal);
const userState = new UserState(memoryStorageLocal);

我获取用户ID如下,等于URL参数

for (var idx in turnContext.activity.membersAdded) {
    if (turnContext.activity.membersAdded[idx].id !== turnContext.activity.recipient.id) {
        console.log("User added");
        this.userID = turnContext.activity.membersAdded[idx].id;
    }
}

我对数据库的读写如下:

// Read userData object
try {
    user = await this.memoryStorage.read([this.userID]);
    console.log("User Object read from DB: " + util.inspect(user, false, null, true /* enable colors */));
}
catch(e) {
    console.log("Reading user data failed");
    console.log(e);
}

// If user is new, create UserData object and save it to DB and read it for further use
if(isEmpty(user)) {
    await step.context.sendActivity("New User Detected");
    this.changes[this.userID] = this.userData;
    await this.memoryStorage.write(this.changes);
    user = await this.memoryStorage.read([this.userID]);
}

如果用户是新用户,这是我保存到数据库的 "empty" userData 对象:

this.userData = {

    name: "",
    age: "",
    gender: "",
    education: "",
    major: "",

    riskData: {
        roundCounter: "",
        riskAssessmentComplete: "",
        riskDescription: "",
        repeat: "",
        choices: "",
    },

    investData: {
        repeat: "",
        order: "",
        choice: "",
        follow: "",
        win1: "",
        win2: "",
        loss1: "",
        loss2: "",
    },

    endRepeat: "",
    eTag: '*',
} 

对我来说,实际问题似乎不是我如何读取和写入 CosmosDB,而是没有不同的机器人实例,因为用户 ID 和用户数据在同时使用时会混淆.

我怎样才能完成这项工作?你可以在这里找到我的机器人代码:

https://github.com/FRANZKAFKA13/roboadvisoryBot

在此先感谢您的帮助!

这是因为您将所有内容都存储在构造函数中,机器人只调用一次。两个用户同时使用 this.changesthis.userId 的相同实例。当一个用户更改它时,它也会为另一个用户更改。它基本上使它们成为任何用户都可以更改的全局变量。

相反,您可以使用 step.options 将您需要的变量传递给对话框。

例如,

调用对话框:

await dc.beginDialog('welcome', userId)

在对话框中使用:

this.userId = step.options // It may also be step.options.userId, if you pass it in as part of a larger object, like .beginDialog('welcome', { userId: '123', userName: 'xyz' })

备注

关于您最初尝试的方式,通过在构造函数中实例化 this.userId

您实际上可以在 C# 中解决这个问题,因为在 C# SDK 中,构造函数会在每条消息上调用(JS/TS 它仅在机器人首次启动时调用)。 BotFramework 的 JS 和 C# SDK 之间存在这个主要区别,因为 C# 是多线程的,而 JS 是单线程的。通过在 C# 中对每条消息调用构造函数,您可以跨线程分散负载。这在 JS/TS.

中是不可能的

话虽这么说,但我仍然会避免这种情况,因为最好在代码实践中尽可能不使用 "global" 变量。