# 关于使用AI助手开发的彩票插件中的MessageBus实时更新求助

**URL:** <https://meta.discourse.org/t/need-help-with-messagebus-real-time-updates-in-lottery-plugin-developed-with-ai-assistance/385371>\
**Category:** Development\
**Created:** [2025年十月12日 15:39 UTC](https://meta.discourse.org/t/need-help-with-messagebus-real-time-updates-in-lottery-plugin-developed-with-ai-assistance/385371 "2025-10-12T15:39:14Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![joo](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/joo/32/334852_2.png) [@joo](https://meta.discourse.org/u/joo)\
**Post date:** [2025年十月12日 15:39 UTC](https://meta.discourse.org/t/need-help-with-messagebus-real-time-updates-in-lottery-plugin-developed-with-ai-assistance/385371/1 "2025-10-12T15:39:14Z")

</div>

大家好，

我正在为 Discourse 开发一个彩票插件，用户可以通过回复彩票主题来参与。 **坦白说：我不是程序员——我完全是在 AI 的帮助下（Claude Code）构建这个插件的，所以我可能缺少一些基本概念。** 基本功能已经可以工作了，但在使用 MessageBus 进行实时更新时遇到了一些问题。我希望能得到一些关于正确方法的指导。

## 我想实现的目标

- 使用 BBCode `[lottery]...[/lottery]` 创建一个彩票帖子。
- 在帖子中显示一个彩票卡片，显示参与者数量。
- 当用户回复参与时， **所有观看者都应该实时看到参与者数量更新** ，而无需刷新页面。
- 类似于 discourse-calendar 插件更新事件信息的方式。

## 当前实现

### 后端 (Ruby)

**Lottery 模型** (`app/models/lottery.rb`):

```ruby
def publish_update!
  channel = "/lottery/#{self.post.topic_id}"
  message = { id: self.id }

  MessageBus.publish(channel, message)
end

```

**LotteryParticipant 模型** (`app/models/lottery_participant.rb`):

```ruby
after_commit :publish_lottery_update, on: [:create, :destroy]

private

def publish_lottery_update
  self.lottery.publish_update!
end

```

后端日志确认 MessageBus.publish 已成功调用：

```plaintext
[Lottery] 📡 Publishing MessageBus update:
  Channel: /lottery/27
  Message: {:id=>52}
[Lottery] ✅ MessageBus.publish completed

```

### 前端 (JavaScript)

**Decorator** (`discourse-lottery-decorator.gjs`):

```javascript
api.decorateCookedElement((cooked, helper) => {
  const post = helper.getModel();

  if (!post?.lottery_data) return;

  // 检查是否已装饰（AI 建议使用 data 属性）
  if (cooked.dataset.lotteryDecorated === "true") return;

  const lotteryNode = cooked.querySelector(".discourse-lottery");
  if (!lotteryNode) return;

  const wrapper = document.createElement("div");
  lotteryNode.before(wrapper);

  const lottery = Lottery.create(post.lottery_data);

  helper.renderGlimmer(
    wrapper,
    `<template><DiscourseLottery @lotteryData={{lottery}} /></template>`
  );

  lotteryNode.remove();
  cooked.dataset.lotteryDecorated = "true";
}, { id: "discourse-lottery" });

```

**Component** (`discourse-lottery/index.gjs`):

```javascript
export default class DiscourseLottery extends Component {
  @service messageBus;
  @service lotteryApi;

  constructor() {
    super(...arguments);

    const { lotteryData } = this.args;
    if (lotteryData?.topicId) {
      this.lotteryPath = `/lottery/${lotteryData.topicId}`;
      this.lotteryData = lotteryData;

      // AI 建议手动绑定回调
      this._boundOnLotteryUpdate = this._onLotteryUpdate.bind(this);
      this.messageBus.subscribe(this.lotteryPath, this._boundOnLotteryUpdate);

      registerDestructor(this, () => {
        this.messageBus.unsubscribe(this.lotteryPath, this._boundOnLotteryUpdate);
      });
    }
  }

  async _onLotteryUpdate(msg) {
    console.log('[Lottery Component] 🔔 MessageBus message received!');

    const updatedData = await this.lotteryApi.lottery(this.lotteryData.topicId);
    this.lotteryData.updateFromLottery(updatedData);
  }
}

```

**Model** (`models/lottery.js`):

```javascript
export default class Lottery {
  @tracked _stats;

  set stats(stats) {
    this._stats = LotteryStats.create(stats || {});
  }

  updateFromLottery(lottery) {
    // 更新所有属性，包括 stats
    this.stats = lottery.stats || {};
    // ... 其他属性
  }
}

```

## 问题

**只有参与者的页面会实时更新，而其他观看者的页面在不刷新页面的情况下不会更新。**

有效的部分：

- ✅ 当用户回复参与时， **他们自己的页面** 会实时显示参与者数量更新。
- ✅ 后端成功发布 MessageBus 消息。
- ✅ 参与者的页面收到了 MessageBus 消息（日志显示“🔔 MessageBus message received!”）。
- ✅ 模型已更新为新的参与者数量（日志显示数量从 0 增加到 1）。

无效的部分：

- ❌ **其他观看彩票主题的用户看不到数量实时更新。**
- ❌ 彩票创建者的页面不会更新。
- ❌ 打开了彩票主题的任何其他用户的页面都不会更新。
- 他们都需要手动刷新页面才能看到更新的参与者数量。

此外，我在控制台中看到组件被多次销毁和重新创建：

```plaintext
[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27
[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27

```

这表明 `decorateCookedElement` 被触发了多次，导致组件被销毁和重新创建，这可能会破坏其他观看者的 MessageBus 订阅。

## 问题

1. **使用已装饰元素上的 `data-decorated` 属性是防止重复装饰的正确方法吗？** AI 建议了这种方法，但我找不到官方文档确认这是推荐的模式。

2. **为什么 UI 没有更新，即使跟踪的模型属性发生了变化？** `@tracked _stats` 在通过 `this.stats = lottery.stats` 更新时应该会触发重新渲染，但显示的参与者数量保持不变。我使用 `@tracked` 的方式有什么问题吗？

3. **我应该完全采用不同的方法吗？** 我让 AI 遵循 discourse-calendar 的模式，但也许我遗漏了 Discourse 处理实时更新的基本知识。

4. **移除原始的 `lotteryNode` 是否会导致问题？** AI 最初尝试不移除它，但这会导致彩票卡片被 CSS 隐藏（`.cooked > .discourse-lottery { display: none; }`）。有没有更好的模式？

由于我不是程序员，我可能会问一些基本问题——任何指向文档或示例的链接都会非常有帮助！谢谢！
