如何对每个用户的聊天消息进行分组?

How to group chat messages per user?

我使用 Vue.js 构建了群聊消息。我目前正在获取 returns 这样的数组的消息:

"data":[
    {
        "id":1,
        "message":"<p>yo<\/p>",
        "removed":"false",
        "user":{
            "uid":2,
            "metadata":{
                "username":"Testing"
            }
        },
        "post_date":"2018-02-24 14:30"
    },
    {
        "id":2,
        "message":"<p>test<\/p>",
        "removed":"false",
        "user":{
            "uid":1,
            "metadata":{
                "username":"Admin"
            }
        },
        "post_date":"2018-02-24 22:31"
    },
    {
        "id":3,
        "message":"<p>Wassup?<\/p>",
        "removed":"false",
        "user":{
            "uid":1,
            "metadata":{
                "username":"Admin"
            }
        },
        "post_date":"2018-02-24 22:40"
    },
    {
        "id":12,
        "message":"again for testing post date",
        "removed":"false",
        "user":{
            "uid":1,
            "metadata":{
                "username":"Admin"
            }
        },
        "post_date":"2018-03-04 00:59"
    },
    {
        "id":13,
        "message":"Hello!",
        "removed":"false",
        "user":{
            "uid":2,
            "metadata":{
                "username":"Testing"
            }
        },
        "post_date":"2018-03-04 11:13"
    },
    {
        "id":13,
        "message":"<p>Hi!</p>",
        "removed":"false",
        "user":{
            "uid":2,
            "metadata":{
                "username":"Testing"
            }
        },
        "post_date":"2018-03-04 11:13"
    },
],

目前我只是循环遍历数据并将每条消息输出到单独的 div。当同一用户连续多次发布时,我更喜欢对消息进行分组。

我该怎么做?它应该是一个选项服务器端(可能是一个可选的 group 参数?)还是以某种方式完成客户端?

编辑 这是聊天当前的样子:

这是你想要的样子:

如果我按 UID/Username 对它们进行分组,问题是消息需要按顺序输出。因此,如果用户 1 发送三条消息,然后用户 2 发送两条,然后用户 1 发送另一条消息,则用户 1 的所有消息将被组合在一起。相反,用户 1 的三条消息应该分组,然后是用户 2,然后它应该显示用户 1 的最后一条消息。

您可以在 server-side 上按用户对您的消息进行分组。

迭代数据,将每个对象推入数组对象,其中键是 uid,值是相应用户的对象数组。

let dataByUser = {};

//Iterate over your data
for (let i = 0; i < data.length; i++) {
    //If the object doesn't have a key equal to the current objects username, 
    //add it as a new key and set the values to a new array containing this datum.
    if(!dataByUser.hasOwnProperty(data[i].user.uid) {
        dataByUser[data[i].user.uid] = new Array(data[i]);
    } else {
        //If it does exist, push to the array for the existing key.
        dataByUser[data[i].user.uid].push(data[i]);
    }
}

应该 最终得到一个对象,在你的数据数组中每个用户有一个 K:V 对,它们的值是对象数组,其中包含所有相应的消息.

然后您必须编写一些客户端代码来迭代这个新的数据结构并根据需要对其进行格式化。

我的方法是保留对包含最后一个用户语音的 div 的引用,以及一个表示最后一个用户发言的 uid 的变量。当收到与最后一个 uid 匹配的消息时,您只需将其添加到您正在跟踪的 div;如果新的 uid 匹配前一个,您只需创建一个新的 div,将其添加到 DOM,然后更新 uid 变量.

// initialize variables
let lastUid,curDiv;

getData().forEach(msg => {
  if (msg.user.uid !== lastUid) {
    // new user is speaking!
    curDiv = document.createElement("div");
    document.body.appendChild(curDiv);
    lastUid = msg.user.uid;
  }
  // add current message to the current div
  curDiv.innerHTML += msg.message;
});

function getData() {
  // really only put this in a function so that I could hoist it so that my logic can go above.
  return [
      {
          "id":1,
          "message":"<p>yo<\/p>",
          "removed":"false",
          "user":{
              "uid":2,
              "metadata":{
                  "username":"Testing"
              }
          },
          "post_date":"2018-02-24 14:30"
      },
      {
          "id":2,
          "message":"<p>test<\/p>",
          "removed":"false",
          "user":{
              "uid":1,
              "metadata":{
                  "username":"Admin"
              }
          },
          "post_date":"2018-02-24 22:31"
      },
      {
          "id":3,
          "message":"<p>Wassup?<\/p>",
          "removed":"false",
          "user":{
              "uid":1,
              "metadata":{
                  "username":"Admin"
              }
          },
          "post_date":"2018-02-24 22:40"
      },
      {
          "id":12,
          "message":"again for testing post date",
          "removed":"false",
          "user":{
              "uid":1,
              "metadata":{
                  "username":"Admin"
              }
          },
          "post_date":"2018-03-04 00:59"
      },
      {
          "id":13,
          "message":"Hello!",
          "removed":"false",
          "user":{
              "uid":2,
              "metadata":{
                  "username":"Testing"
              }
          },
          "post_date":"2018-03-04 11:13"
      },
      {
          "id":13,
          "message":"<p>Hi!</p>",
          "removed":"false",
          "user":{
              "uid":2,
              "metadata":{
                  "username":"Testing"
              }
          },
          "post_date":"2018-03-04 11:13"
      },
  ]
}
div {
  border: 1px solid black;
  margin-bottom: 5px;
}

似乎是计算属性的理想用例。先将数据按post_date

排序
computed: {
    sortedPosts() {
        return this.posts.sort((a, b) => {
            if (a.post_date < b.post_date) return -1;
            else if (a.post_date > b.post_date) return +1;
            return 0;
        });
    },

然后按user.uid

分组
    groupedPosts() {
        const posts = this.sortedPosts.reduce((post, grouped) => {
            if (grouped.length === 0) 
                grouped.unshift([post]);
            else if (grouped[0][0].user.uid === post.user.uid)
                grouped[0].push(post);
            else
                grouped.unshift([post]);
            return grouped;
        }, []);
        return posts.reverse();
    }

然后在模板中使用计算的属性

<div v-for="group in groupedPosts">
    <div v-for="post in group">

(我没有测试过上面的代码,所以要注意错别字等)

我最近自己解决了这个问题。 这是 full example.

在上面的例子中,消息分组的核心业务逻辑可以在addMessage函数中的src/store.js下找到。

除非您的服务器存储所有消息并且您有某种机制来确保所有客户端之间的因果顺序,否则我建议您运行每个客户。这样,您就可以确保客户端在任何情况下都不会看到跳来跳去的消息。在最坏的情况下,消息会显示为未分组。

当收到新消息时 运行 确定这一点的算法,因此它在每个客户端上 运行s,但您也可以调整它以适用于您的用例!它如下所示(我可能使用了错误的流程图元素..抱歉)。

addMessage({ state }, { msg, selfHash }) {
  let addAsNewMsg = true;
  const lastMessage = state.messages.slice(-1)[0];
  if (lastMessage && lastMessage.userHash === msg.userHash) {
    // The last message was also sent by the same user
    // Check if it arrived within the grouping threshold duration (60s)
    const lastMessageTime = moment(lastMessage.time);
    const msgTime = moment(msg.time);
    const diffSeconds = msgTime.diff(lastMessageTime, "seconds");
    if (diffSeconds <= 60) {
      addAsNewMsg = false; // We're going to be appending.. so
      lastMessage.message.push(msg.message);
      // We're going to now update the timestamp and (any) other fields.
      delete msg.message; // Since, we've already added this above
      Object.assign(lastMessage, msg); // Update with any remaining properties
    }
  }
  if (addAsNewMsg) {
    state.messages.push(msg);
  }
}