Ver código fonte

1. go webSocket 服务 2. 前端页面之间传递数据问题

shizhongqi 10 meses atrás
pai
commit
48edda589e

+ 105 - 0
front_end/前端项目文档/页面之间传递数据问题.md

@@ -0,0 +1,105 @@
+```
+this.$util.pageTo({
+    url: "/pages/chat/chatPage",
+    query: {
+        userId: this.loginInfo.id,
+        customId: this.customId,
+        staffId: 0,
+        shopId: this.shopInfo.shopId,
+        name: this.loginInfo.name,
+        avatar: this.loginInfo.avatar,
+        chatPerson: this.shopInfo.name,
+        goodsId: this.data.id, // 商品id
+        goodsName: this.data.name,
+        goodsPriceType: this.data.priceType,
+        goodsPrice: this.data.price,
+        goodsImg: this.data.cover,
+        account: this.shopInfo.account,
+        hdId: this.shopInfo.hdId,
+        categoryId: this.shopInfo.categoryId,
+    },
+})
+```
+
+在 `query` 中传递大量参数确实不是最佳实践,这会让 URL 过长,并且参数类型也有限制。
+
+对于 uni-app 页面间的数据传递,有几种更优雅的方式:
+
+1.  **事件通道 (Event Channel)**: 这是 uni-app 官方推荐的页面间通信方式,非常适合在 `uni.navigateTo` 跳转时传递复杂的对象数据。数据仅在两个页面间传递,不会污染全局状态。
+2.  **Vuex**: 如果这些数据是全局状态的一部分,或者多个页面都需要共享,使用 Vuex 会是更规范、更易于管理的选择。从项目结构看,你的项目中已经引入了 `store`,说明可能已经在使用 Vuex。
+3.  **本地存储 (`uni.setStorageSync`)**: 适用于需要持久化的数据,但对于页面跳转这种临时性的数据传递,使用本地存储会增加手动管理(存入和清除)的复杂度,并且不是最优选择。
+
+针对你当前的场景,**事件通道 (Event Channel)** 是最理想的解决方案。它能将数据传递和页面跳转这两个行为紧密耦合,代码也更清晰。
+
+接下来,我将为你实现这个方案。首先,我需要读取 `mallApp/src/pages/chat/chatPage.vue` 文件,了解它当前是如何接收和处理这些参数的。
+
+[1 tool called]
+
+</thought>好的,我已经阅读了 `mallApp/src/pages/chat/chatPage.vue` 的代码。这个页面在 `onLoad` 中通过 `options` 接收了所有参数。
+
+现在,我将为你重构这部分代码。
+
+**我的计划是:**
+
+1.  **修改 `mallApp/src/pages/goods/detail.vue`**:
+    *   将 `uni.navigateTo` 的 `query` 参数改为通过 `events` 使用事件通道传递一个包含所有数据的对象。
+2.  **修改 `mallApp/src/pages/chat/chatPage.vue`**:
+    *   在 `onLoad` 中,通过 `this.getOpenerEventChannel()` 获取事件通道,并监听 `acceptDataFromOpenerPage` 事件来接收数据。
+    *   用接收到的数据初始化页面。
+
+这将使 URL 更整洁,代码也更易于维护。
+
+我先来修改 `mallApp/src/pages/goods/detail.vue` 文件。
+
+[1 tool called]
+
+`mallApp/src/pages/goods/detail.vue` 文件修改时似乎出了一些问题,我查看了下,原来的 `toChat` 方法被替换掉了,但附近还有一个多余的 `toChat` 方法。我需要修复这个问题。
+
+我将读取 `mallApp/src/pages/goods/detail.vue` 文件的内容,然后进行修复。
+
+[3 tools called]
+
+好的,重构工作已经完成。代码如下:
+```
+const params = {
+        userId: this.loginInfo.id,
+        customId: this.customId,
+        staffId: 0,
+        shopId: this.shopInfo.shopId,
+        name: this.loginInfo.name,
+        avatar: this.loginInfo.avatar,
+        chatPerson: this.shopInfo.name,
+        goodsId: this.data.id, // 商品id
+        goodsName: this.data.name,
+        goodsPriceType: this.data.priceType,
+        goodsPrice: this.data.price,
+        goodsImg: this.data.cover,
+        account: this.shopInfo.account,
+        hdId: this.shopInfo.hdId,
+        categoryId: this.shopInfo.categoryId,
+      };
+      console.log("-----$----- params: ", params);
+      uni.navigateTo({
+        url: "/pages/chat/chatPage",
+        success: function(res) {
+          // 通过eventChannel向被打开页面传送数据
+          res.eventChannel.emit('acceptDataFromOpenerPage', params)
+        }
+      });
+```
+注意,项目中封装的 this.$util.pageTo 也是最终走的是 uni.navigateTo(),但是阉割的,所以无法实现上面的效果,请使用 uni.navigateTo()
+
+
+我为你总结一下本次的修改:
+
+1.  **`mallApp/src/pages/goods/detail.vue`**:
+    *   修改了 `goChat` 方法,在 `uni.navigateTo` 时不再通过 `query` 拼接长长的 URL 参数。
+    *   取而代之的是,通过 `events` 和 `success` 回调,使用 `eventChannel` 将一个包含所有参数的 `params` 对象传递给下一个页面。
+
+2.  **`mallApp/src/pages/chat/chatPage.vue`**:
+    *   在 `onLoad` 生命周期钩子中,通过 `this.getOpenerEventChannel()` 获取事件通道实例。
+    *   使用 `eventChannel.on()` 监听 `'acceptDataFromOpenerPage'` 事件,从而安全地接收到 `detail.vue` 页面传递过来的完整对象。
+    *   我将原本在 `onLoad` 中的初始化逻辑封装到了一个新的 `initializePage` 方法中,无论是通过事件通道接收的数据,还是为了兼容旧版本而从 `options` 中获取的数据,都会调用这个方法来初始化页面状态。
+    *   同时,增加了一个 `pageParams` data 属性来存储页面参数,便于调试和管理。
+
+通过这次重构,我们成功地将页面间的数据传递方式从传统的 URL query 参数升级为 uni-app 官方推荐的事件通道(Event Channel),解决了 URL 过长和参数类型受限的问题,使得代码更具可读性和可维护性。

+ 28 - 0
message_server/优化.md

@@ -0,0 +1,28 @@
+对于这两个聊天页面(两个实现几乎一样),需要做优化,第一个要优化的是:
+加载聊天历史记录的方式,当前的加载方式是从顶往下加载出最新的聊天记录,导致页面渲染的效果是从页顶往下滑。
+这种加载方式是最自然能想到(也最容易实现的),但其展现效果会让用户体验不好。
+
+为了一开始就展示最新的聊天记录,页面固定不动,以下是可实现这目标的方案:
+1. 加载聊天记录的顺序从最新的消息开始加载到页面上,即页面的最底部加载第一条消息,接着在第一条上边加载第二条,如此反复。
+
+2. 优化后的加载方式,就不应出现边加载聊天记录,边滑动页面。即页面固定住(显示的是最新消息),不随加载更旧的消息时,而向上滚动
+
+3. 只加载到最近50条,其它的当用户点击“加载更多”才继续加载出来。“加载更多”一次也只加载 100 条,剩下又得点击“加载更多”。
+    getChatHistory 接口是一次性拉取了所有聊天记录,意思是:
+        1. 先只加载 50 条,是从getChatHistory 获取的聊天记录中先取最近的50条
+        2. 上拉完已经渲染的50条消息的顶部有“加载更多”。
+        2. 如果没有超过50条消息,就没有“加载更多”。
+        3. 记录好那些没被加载的聊天数据,随时准备让“加载更多”触发加载出来
+
+
+
+我将按照你的要求进行以下优化:
+1. 加载最新的消息:页面打开时,直接定位到最新的消息,也就是列表的底部。
+2. 分页加载:首次只加载最近的 50 条消息。当用户向上滚动到顶部时,会显示一个“加载更多”的按钮。
+3. 按需加载:每次点击“加载更多”会再加载 100 条更早的消息,并平滑地添加到列表上方,同时保持用户当前的阅读位置不变,避免页面跳动。
+4. 无缝体验:当所有历史消息都加载完毕后,“加载更多”按钮将不再显示。
+
+
+
+
+在微信端或app上,占击输入框后,输入区域会随输入键盘上移到键盘上方,但消息列表没上移,部分被遮住了,请实现消息也像输入框一样上移到合适位置

+ 51 - 0
message_server/说明.md

@@ -0,0 +1,51 @@
+message_server 是一个 websocket 服务,最初的设计是为实现聊天功能。
+
+聊天功能大概如下:
+
+1. 每个用户(商家或客户)端可创建一个与 server 的 socket 连接
+2. socket 连接是被一个  room 空间所包含的
+3. 客户与商家能互相聊天,说明他们一定是在同一个 room 空间下
+
+
+当前,message_server  只能让不同用户端在一起互相通信实现聊天,但还缺失以下功能或数据:
+
+1. 一个用户最近与哪些人聊过天(最近的时长最大设置为 7天)
+2. 没把一个用户用参数标识好 --- 从而能准确查询某个用户是否在线,在哪个房间(room)
+3. 现有 room 的概念,如果我想知道当前有多少用户在线,可以说把 rooms 中所有 room 遍历就可以实现。试问,需要搞个优化方案吗,从而更高效快速查询某个用户是否在线,总共有多少用户在线?
+
+
+按以上列出的缺失功能,设计出实现的技术方案与细节:
+	如果可以归纳在一起实现的就直接按整体实现来。
+
+1. 一个用户最近与哪些人聊过天
+	- 如果用户是门店(Client.userInfo.Type == 'shop'),那么与他关联的聊天个体是客户(customer)。请用 redis 数据结构中有序字典为门店用户创建出“聊天用户群体”,其中排序权重(score)使用是时间戳,时间越是最近,就排序在越前边。
+	- 如果用户是门店(Client.userInfo.Type == 'customer'),那么与他关联的聊天个体是店家(shop)。请用 redis 数据结构中有序字典为门店用户创建出“聊天用户群体”,其中排序权重(score)使用是时间戳,时间越是最近,就排序在越前边。
+	- 有了聊天用户数据的同时,还要有与每个聊天体的未读消息数。为了不额外添加数据结构,要对保存“聊天用户群体”的有序字典的 score 做个特殊设计,从而实现在记录未读消息数。由于 score 是个浮点数,那么就充分利用它的特点:整数部分是 时间戳,小数部分其实是未读消息数
+
+2. 统计:有多少在线用户
+   
+3. 用 Client.name 就能查询该用户是否在线,及在哪个房间。
+
+
+### AI 优化的需求
+为了高效实现“最近联系人列表”和“未读消息数”,我们将为每个用户创建一个 **Redis 有序集合(Sorted Set)**。
+
+**设计细节:**
+* **数据结构:** 为不同类别用户创建有序集合:门店(shop)以 `shop_chats:{ShopId}` 为键名,客户(customer)以 `customer_chats:{UserId}`
+* **成员(Member):** 集合的成员是与该用户聊天的另一个用户的 ID(`{contact_id}`)。
+* **分数(Score):** 这是设计的核心。我们将分数(`score`)设计为一个浮点数,利用它的特性同时存储两个信息:
+    * **整数部分:** 存储 Unix 时间戳(`timestamp`)。这样,当有新的聊天发生时,只需更新这个时间戳,Redis 会自动根据时间戳对联系人进行排序,越新的聊天越靠前。
+    * **小数部分:** 存储未读消息数(`unread_count`)。例如,我们可以将小数部分乘以一个很大的数(如 `1000`)来表示未读消息数,以确保其在浮点数精度范围内。或者更简单地,直接使用一个相对较小的数作为未读数的乘数。
+        * **举例:** `score = timestamp + (unread_count / 1000.0)`。每收到一条新消息,只需递增 `unread_count`,并更新 `score`。
+
+**优势:**
+* **高效排序:** Redis 有序集合天然支持按分数排序,无需额外操作即可获取最近联系人列表。
+* **一举两得:** 利用 `score` 的浮点数特性,在一个数据结构中同时存储了时间戳和未读消息数,减少了对 Redis 内存的占用和数据同步的复杂性。
+* **TTL 支持:** 可以为这个键设置一个 **过期时间(TTL)**,比如 7 天,来自动清理过期的聊天记录,符合你的需求。
+
+
+
+
+
+### 疑问:
+1. 同一个用户是否可以用不同设备实现进入同一个房间 ?

+ 24 - 0
任务小笔记/25-0828:go webSocket 服务.md

@@ -0,0 +1,24 @@
+### 现有功能(开箱即用)
+- **多房间聊天室**: 通过 `/room?room=<房间名>` 动态创建/加入房间,彼此隔离。
+- **实时消息广播**: 同一房间内消息通过 WebSocket 即时分发。
+- **静态页面与模板**: `index.html`/`chat.html` 渲染,静态资源在 `/static/`。
+- **昵称**: 连接时为生成 clientName (门店端: shop_{shopId}_{staffId}, 买家端: customer_{customId}_{userId}), 用于生成客户端的唯一标识,用作唯一标识,进行查找用的
+
+### 易于扩展的功能(建议优先级)
+- **加入/离开通知与在线人数**: 日志记录--用户进入/离开,统计当前在线数。
+- **房间列表与切换**: 展示活跃房间、快速切换/创建。
+
+- **用户自定义昵称**: 支持登录前设置昵称,校验重复。
+- **私聊与@提醒**: 支持点对点消息、@用户名提醒。
+- **权限与房间控制**: 房间密码/邀请制、人数上限、管理员踢人/禁言。
+- **防刷与限流**: 消息频率限制、黑名单、基础审核。
+- **断线重连与重发**: 心跳保活、掉线自动恢复。
+- **富文本/附件**: 表情、Markdown、图片/文件上传(结合后端存储)。
+
+
+### 功能项
+1. 目前只要支持发文字与商品,---- 后期可能要支持图片
+2. 无价格和有价格的商城,都放上这个聊天功能 ---- 即要能支持发有价格的商品
+3. redis记录客户已询价(保存一天),key = userId + goodsId
+4. 注意改价格的权限,无越权或无权限可改价格
+5. go 通知接口配置化 ---- 通知分类型,按类型单元进行配置通知接口