diff --git a/docs/websocket/README.md b/docs/websocket/README.md new file mode 100644 index 000000000..724df9f50 --- /dev/null +++ b/docs/websocket/README.md @@ -0,0 +1,355 @@ +# WebSocket Server 说明文档 + +## 1. 概述 + +WebSocket Server 是基于 Ratchet 库实现的纯 PHP WebSocket 服务器,主要用于为智能客服系统提供实时通信支持,特别是为不支持 EventSource 的微信小程序提供流式请求处理能力。 + +## 2. 安装与依赖 + +### 2.1 依赖库 + +- **Ratchet**: 纯 PHP WebSocket 实现库 + ```bash + composer require cboden/ratchet + ``` + +### 2.2 环境要求 + +- PHP 7.4+ +- Composer +- ThinkPHP 6.x + +## 3. 服务器文件结构 + +### 3.1 核心文件 + +- **启动脚本**: `src/ws_server.php` - WebSocket 服务器主启动文件 +- **智能客服控制器**: `src/addon/aikefu/api/controller/WebSocket.php` - aikefu 插件的 WebSocket 控制器实现 + +### 3.2 目录结构 + +``` +├── backend/ +│ ├── docs/ +│ │ └── websocket/ +│ │ └── README.md # 本文档 +│ └── src/ +│ ├── ws_server.php # WebSocket 服务器启动脚本 +│ └── addon/ +│ └── aikefu/ +│ └── api/ +│ └── controller/ +│ └── WebSocket.php # aikefu 插件 WebSocket 控制器 +``` + +## 4. 服务器启动与配置 + +### 4.1 启动命令 + +在 `src` 目录下执行: + +```bash +php ws_server.php +``` + +### 4.2 默认配置 + +- **监听地址**: `0.0.0.0` (所有网络接口) +- **端口**: `8080` +- **WebSocket 地址**: `ws://localhost:8080` + +### 4.3 自定义配置 + +可以在 `ws_server.php` 中修改以下配置: + +```php +// 配置WebSocket服务器 +$httpHost = 'localhost'; // 客户端连接时使用的主机名 +$port = 8080; // WebSocket服务器端口 +$address = '0.0.0.0'; // 监听所有网络接口 +``` + +## 5. WebSocket 路径 + +### 5.1 默认测试路径 + +- **路径**: `/ws` +- **功能**: 用于测试 WebSocket 连接 +- **示例**: `ws://localhost:8080/ws` + +### 5.2 智能客服路径 + +- **路径**: `/ws/aikefu` +- **功能**: 智能客服聊天功能接口 +- **示例**: `ws://localhost:8080/ws/aikefu` + +## 6. 消息格式 + +### 6.1 客户端发送消息格式 + +```json +{ + "message": "用户输入的消息", + "token": "用户认证令牌", + "session_id": "会话ID", + "action": "chat" // 动作类型 +} +``` + +### 6.2 服务器响应消息格式 + +#### 聊天响应 + +```json +{ + "type": "chat", + "message": "客服回复的消息内容", + "session_id": "会话ID", + "complete": false // 是否完成响应 +} +``` + +#### 错误响应 + +```json +{ + "type": "error", + "message": "错误信息描述", + "code": "错误代码" +} +``` + +#### 系统消息 + +```json +{ + "type": "system", + "message": "系统消息内容" +} +``` + +## 7. 客户端使用示例 + +### 7.1 JavaScript 示例 + +```javascript +// 创建WebSocket连接 +const ws = new WebSocket('ws://localhost:8080/ws/aikefu'); + +// 连接打开时 +ws.onopen = function(event) { + console.log('WebSocket连接已打开'); + + // 发送聊天消息 + ws.send(JSON.stringify({ + message: '你好', + token: 'user_token_here', + session_id: 'session_123', + action: 'chat' + })); +}; + +// 接收消息 +ws.onmessage = function(event) { + const data = JSON.parse(event.data); + console.log('收到消息:', data); + + if (data.type === 'chat') { + // 处理聊天消息 + console.log('客服回复:', data.message); + + if (data.complete) { + console.log('对话完成'); + } + } else if (data.type === 'error') { + // 处理错误 + console.error('错误:', data.message); + } +}; + +// 连接关闭时 +ws.onclose = function(event) { + console.log('WebSocket连接已关闭'); +}; + +// 连接错误时 +ws.onerror = function(error) { + console.error('WebSocket错误:', error); +}; +``` + +### 7.2 微信小程序示例 + +```javascript +// 创建WebSocket连接 +const ws = wx.connectSocket({ + url: 'ws://localhost:8080/ws/aikefu', + header: { + 'content-type': 'application/json' + } +}); + +// 连接打开时 +wx.onSocketOpen(function(res) { + console.log('WebSocket连接已打开', res); + + // 发送聊天消息 + wx.sendSocketMessage({ + data: JSON.stringify({ + message: '你好', + token: 'user_token_here', + session_id: 'session_123', + action: 'chat' + }) + }); +}); + +// 接收消息 +wx.onSocketMessage(function(res) { + const data = JSON.parse(res.data); + console.log('收到消息:', data); + + if (data.type === 'chat') { + // 处理聊天消息 + console.log('客服回复:', data.message); + + if (data.complete) { + console.log('对话完成'); + } + } else if (data.type === 'error') { + // 处理错误 + console.error('错误:', data.message); + } +}); + +// 连接关闭时 +wx.onSocketClose(function(res) { + console.log('WebSocket连接已关闭', res); +}); + +// 连接错误时 +wx.onSocketError(function(res) { + console.error('WebSocket错误:', res); +}); +``` + +## 8. 功能特性 + +### 8.1 实时通信 + +- 支持双向实时通信 +- 消息即时推送 +- 支持流式响应 + +### 8.2 智能客服集成 + +- 与现有智能客服系统无缝集成 +- 支持上下文会话管理 +- 支持多用户并发访问 + +### 8.3 插件化设计 + +- 支持为不同插件注册独立的 WebSocket 控制器 +- 路径格式:`/ws/{addon_name}` +- 便于扩展其他插件的 WebSocket 支持 + +## 9. 故障排除 + +### 9.1 常见问题 + +#### 问题:服务器无法启动 + +**可能原因**: +- 端口被占用 +- 依赖库未安装 +- PHP 版本不兼容 + +**解决方案**: +- 检查端口占用情况:`netstat -an | findstr 8080` +- 重新安装依赖:`composer install` +- 确保 PHP 版本 >= 7.4 + +#### 问题:客户端无法连接 + +**可能原因**: +- 服务器未启动 +- 网络防火墙限制 +- WebSocket 地址错误 + +**解决方案**: +- 确认服务器已启动 +- 检查防火墙设置,确保端口 8080 开放 +- 验证 WebSocket 地址格式 + +#### 问题:消息发送失败 + +**可能原因**: +- 消息格式错误 +- 认证失败 +- 服务器内部错误 + +**解决方案**: +- 检查消息格式是否符合要求 +- 验证用户认证信息 +- 查看服务器日志获取详细错误信息 + +### 9.2 日志查看 + +服务器启动时会输出详细日志,包括: +- 已注册的 WebSocket 控制器 +- 连接信息 +- 错误信息 + +## 10. 扩展开发 + +### 10.1 为其他插件添加 WebSocket 支持 + +1. 在插件目录下创建 WebSocket 控制器: + ``` + addon/{addon_name}/api/controller/WebSocket.php + ``` + +2. 实现 MessageComponentInterface 接口: + ```php + clients = new \SplObjectStorage; + } + + public function onOpen(ConnectionInterface $conn) { + // 处理连接打开 + } + + public function onMessage(ConnectionInterface $conn, $msg) { + // 处理收到的消息 + } + + public function onClose(ConnectionInterface $conn) { + // 处理连接关闭 + } + + public function onError(ConnectionInterface $conn, \Exception $e) { + // 处理错误 + } + } + ``` + +3. 重启 WebSocket 服务器,新插件的 WebSocket 控制器将自动注册到 `/ws/{addon_name}` 路径 + +## 11. 版本历史 + +| 版本 | 日期 | 说明 | +|------|------|------| +| v1.0 | 2025-12-19 | 初始版本,支持智能客服系统的 WebSocket 通信 | + +## 12. 联系方式 + +如有问题或建议,请联系技术支持团队。 diff --git a/docs/websocket/test_readme.md b/docs/websocket/test_readme.md new file mode 100644 index 000000000..8d15cc8e1 --- /dev/null +++ b/docs/websocket/test_readme.md @@ -0,0 +1,231 @@ +# WebSocket 测试文件说明 + +本目录包含用于测试 WebSocket 服务器的各种测试文件,支持不同环境和场景的测试需求。 + +## 测试文件列表 + +1. **`test_websocket.php`** - PHP 客户端测试脚本 +2. **`test_websocket.html`** - 浏览器端 JavaScript 测试页面 +3. **`test_wechat_miniprogram.js`** - 微信小程序客户端测试代码 +4. **`test_readme.md`** - 本测试文件说明文档 + +## 测试前准备 + +在开始测试之前,请确保: + +1. **启动 WebSocket 服务器**: + ```bash + cd d:/projects/shop-projects/backend/src + php ws_server.php + ``` + +2. **确保服务器运行正常**: + 服务器启动后,您应该看到类似以下输出: + ``` + WebSocket服务器已启动,监听地址: ws://localhost:8080 + + 已注册WebSocket控制器的addon路径: + - ws://localhost:8080/ws/aikefu (已注册) + + 默认测试路径: + - ws://localhost:8080/ws (默认路径,用于连接测试) + 按 Ctrl+C 停止服务器 + ``` + +## 1. PHP 客户端测试 (`test_websocket.php`) + +### 功能说明 +用于在 PHP 环境下测试 WebSocket 服务器的连接和基本功能。 + +### 使用方法 + +1. **直接运行测试脚本**: + ```bash + cd d:/projects/shop-projects/backend/docs/websocket + php test_websocket.php + ``` + +2. **测试不同路径**: + - 默认测试路径:`ws://localhost:8080/ws` + - aikefu 插件路径:`ws://localhost:8080/ws/aikefu` + + 可以通过修改脚本中的 `$wsUrl` 变量来切换测试路径。 + +### 预期输出 + +成功连接并发送消息后,您应该看到类似以下输出: + +``` +正在连接到 WebSocket 服务器: ws://localhost:8080/ws +✅ 成功连接到 WebSocket 服务器 +📤 发送 ping 消息 +📤 发送测试消息 +📥 收到消息: {"type":"welcome","message":"欢迎连接到默认WebSocket测试路径","info":"此路径仅用于测试,不提供实际功能。请使用/ws/{addonName}连接到具体的addon服务。"} +📥 收到消息: {"type":"pong"} +📥 收到消息: {"type":"info","message":"收到消息,但默认路径不提供实际功能","received":{"message":"Hello WebSocket!","action":"test"}} +``` + +## 2. 浏览器端 JavaScript 测试 (`test_websocket.html`) + +### 功能说明 +用于在浏览器环境下测试 WebSocket 服务器的连接和交互功能,提供直观的用户界面。 + +### 使用方法 + +1. **打开测试页面**: + - 直接用浏览器打开 `test_websocket.html` 文件 + - 或者通过 Web 服务器访问:`http://your-domain/docs/websocket/test_websocket.html` + +2. **配置测试参数**: + - 在 "设置" 区域输入 WebSocket 服务器地址 + - 默认地址:`ws://localhost:8080/ws` + - aikefu 插件地址:`ws://localhost:8080/ws/aikefu` + +3. **连接和测试**: + - 点击 "连接" 按钮建立 WebSocket 连接 + - 使用 "发送 Ping"、"发送测试消息" 或 "发送自定义消息" 按钮进行测试 + - 在消息区域查看发送和接收的消息 + +### 主要功能 + +- **连接管理**:连接、断开连接、重连 +- **消息发送**: + - Ping 消息:测试服务器响应 + - 测试消息:发送预设的测试数据 + - 自定义消息:支持发送任意 JSON 格式消息 +- **消息显示**: + - 美化显示 JSON 格式消息 + - 区分发送和接收的消息 + - 显示消息时间和发送者 + +## 3. 微信小程序客户端测试 (`test_wechat_miniprogram.js`) + +### 功能说明 +用于在微信小程序环境下测试 WebSocket 服务器的连接和通信功能,模拟实际的微信小程序客户端环境。 + +### 使用方法 + +1. **集成测试代码**: + - 将 `test_wechat_miniprogram.js` 文件中的代码复制到微信小程序的页面 JavaScript 文件中 + - 将注释中的 WXML 和 WXSS 代码分别复制到对应的 `.wxml` 和 `.wxss` 文件中 + +2. **配置服务器地址**: + - 修改 `wsUrl` 变量为您的 WebSocket 服务器地址 + - 注意:微信小程序要求使用 HTTPS/WSS 协议,需要配置 SSL 证书 + +3. **运行测试**: + - 在微信开发者工具中打开小程序项目 + - 进入包含测试代码的页面 + - 点击 "连接" 按钮建立 WebSocket 连接 + - 使用测试按钮发送消息并查看接收结果 + +### 注意事项 + +- **微信小程序 WebSocket 限制**: + - 只支持 HTTPS/WSS 协议 + - 需要在小程序管理后台配置合法域名 + - 最多同时存在 5 个 WebSocket 连接 + +- **调试建议**: + - 使用微信开发者工具的调试模式查看日志 + - 先在浏览器端测试确保服务器正常 + - 注意网络环境和防火墙设置 + +## 测试场景和用例 + +### 基本连接测试 + +| 测试场景 | 预期结果 | 测试文件 | +|---------|---------|---------| +| 连接默认测试路径 | 成功连接,收到欢迎消息 | 所有测试文件 | +| 连接 aikefu 插件路径 | 成功连接 | 所有测试文件 | +| 断开连接 | 连接关闭,收到关闭通知 | 所有测试文件 | +| 重连 | 成功重新建立连接 | 浏览器和小程序测试文件 | + +### 消息功能测试 + +| 测试场景 | 预期结果 | 测试文件 | +|---------|---------|---------| +| 发送 Ping 消息 | 收到 Pong 响应 | 所有测试文件 | +| 发送测试消息 | 收到服务器处理结果 | 所有测试文件 | +| 发送 JSON 消息 | 正确解析和处理 | 所有测试文件 | +| 发送错误格式消息 | 收到错误提示 | 所有测试文件 | + +## 常见问题和解决方案 + +### 1. 连接失败 + +**可能原因**: +- WebSocket 服务器未启动 +- 服务器地址或端口错误 +- 防火墙或网络设置阻止连接 +- 微信小程序域名未配置 + +**解决方案**: +- 确认服务器已启动并运行正常 +- 检查服务器地址和端口配置 +- 关闭防火墙或添加例外规则 +- 在微信小程序管理后台配置合法域名 + +### 2. 消息发送失败 + +**可能原因**: +- WebSocket 连接已关闭 +- 消息格式不正确 +- 服务器处理错误 + +**解决方案**: +- 检查连接状态,必要时重新连接 +- 确保消息格式符合 JSON 规范 +- 查看服务器日志排查错误 + +### 3. 接收消息异常 + +**可能原因**: +- 服务器返回格式错误 +- 网络传输问题 +- 客户端解析错误 + +**解决方案**: +- 检查服务器代码确保返回正确格式 +- 检查网络连接稳定性 +- 查看客户端日志排查解析错误 + +## 测试完成后的清理 + +测试完成后,您可以: + +1. **停止 WebSocket 服务器**: + 在运行服务器的终端中按 `Ctrl+C` 停止服务器。 + +2. **关闭测试客户端**: + - 浏览器测试:关闭浏览器标签页 + - PHP 测试:测试完成后自动退出 + - 微信小程序测试:退出小程序页面 + +## 扩展测试建议 + +1. **性能测试**: + - 测试同时连接多个客户端 + - 测试大消息传输 + - 测试长时间连接稳定性 + +2. **异常场景测试**: + - 服务器意外关闭 + - 网络中断恢复 + - 消息丢失场景 + +3. **功能完整性测试**: + - 测试所有消息类型 + - 测试错误处理机制 + - 测试安全验证功能 + +## 联系和支持 + +如果在测试过程中遇到问题,请联系开发人员或查看服务器日志获取更多信息。 + +--- + +**更新时间**:2025-12-19 +**版本**:1.0.0 +**维护人员**:WebSocket 开发团队 diff --git a/docs/websocket/test_uniapp_wechat.vue b/docs/websocket/test_uniapp_wechat.vue new file mode 100644 index 000000000..c999a5414 --- /dev/null +++ b/docs/websocket/test_uniapp_wechat.vue @@ -0,0 +1,754 @@ + + + + + + + + + + + diff --git a/docs/websocket/test_websocket.html b/docs/websocket/test_websocket.html new file mode 100644 index 000000000..e0a65a6d9 --- /dev/null +++ b/docs/websocket/test_websocket.html @@ -0,0 +1,283 @@ + + + + + + WebSocket 测试客户端 + + + +
+
+

WebSocket 服务器测试客户端

+

用于测试 WebSocket 服务器的连接和基本功能

+
+ +
+

设置

+ + +
+ + +
+ +
+ 连接状态: + 未连接 +
+ +
+ +
+ + + +
+ +
+ +
+
+ + + + diff --git a/docs/websocket/test_websocket.php b/docs/websocket/test_websocket.php new file mode 100644 index 000000000..2606cd7a6 --- /dev/null +++ b/docs/websocket/test_websocket.php @@ -0,0 +1,72 @@ +then(function ($conn) use ($loop) { + echo "✅ 成功连接到 WebSocket 服务器\n"; + + // 创建 WebSocket 客户端 + $ws = new WebSocket($conn); + + // 收到消息时的处理 + $ws->on('message', function (MessageInterface $msg) use ($ws) { + echo "📥 收到消息: {$msg}\n"; + }); + + // 连接关闭时的处理 + $ws->on('close', function ($code = null, $reason = null) { + echo "❌ 连接已关闭: {$code} - {$reason}\n"; + }); + + // 发送 ping 消息测试 + echo "📤 发送 ping 消息\n"; + $ws->send(json_encode(['action' => 'ping'])); + + // 发送测试消息 + echo "📤 发送测试消息\n"; + $ws->send(json_encode(['message' => 'Hello WebSocket!', 'action' => 'test'])); + + // 3秒后关闭连接 + $loop->addTimer(3, function () use ($ws) { + echo "⏰ 关闭连接\n"; + $ws->close(); + }); + + // 如果是 aikefu 插件路径,可以发送聊天消息测试 + // $ws->send(json_encode(['message' => '你好,客服', 'token' => 'your_token', 'action' => 'chat'])); + + }, function ($e) use ($loop) { + echo "❌ 连接失败: {$e->getMessage()}\n"; + $loop->stop(); + }); + +// 运行事件循环 +$loop->run(); + +echo "测试完成\n"; diff --git a/docs/websocket/test_wechat_miniprogram.js b/docs/websocket/test_wechat_miniprogram.js new file mode 100644 index 000000000..006c15307 --- /dev/null +++ b/docs/websocket/test_wechat_miniprogram.js @@ -0,0 +1,429 @@ +/** + * 微信小程序 WebSocket 客户端测试代码 + * 用于测试微信小程序环境下与 WebSocket 服务器的连接和通信 + * + * 使用方法: + * 1. 将此代码复制到微信小程序的页面 JavaScript 文件中 + * 2. 在对应的 WXML 文件中添加测试按钮和消息显示区域 + * 3. 在微信开发者工具中运行测试 + */ + +// WebSocket 连接实例 +let ws = null; + +// 页面数据 +Page({ + data: { + wsUrl: 'wss://your-domain.com/ws/aikefu', // 替换为你的 WebSocket 服务器地址 + connectionStatus: '未连接', + messages: [], + inputMessage: '' + }, + + // 生命周期函数--监听页面加载 + onLoad: function(options) { + this.connectWebSocket(); + }, + + // 生命周期函数--监听页面卸载 + onUnload: function() { + this.closeWebSocket(); + }, + + // 生命周期函数--监听页面隐藏 + onHide: function() { + this.closeWebSocket(); + }, + + // 连接 WebSocket 服务器 + connectWebSocket: function() { + const that = this; + const wsUrl = this.data.wsUrl; + + // 创建 WebSocket 连接 + ws = wx.connectSocket({ + url: wsUrl, + header: { + 'content-type': 'application/json' + }, + method: 'GET', + success: function(res) { + console.log('WebSocket 连接请求发送成功', res); + that.updateStatus('连接中...'); + }, + fail: function(err) { + console.error('WebSocket 连接请求发送失败', err); + that.updateStatus('连接失败'); + that.addMessage('系统', 'WebSocket 连接请求发送失败: ' + JSON.stringify(err), 'error'); + } + }); + + // 监听 WebSocket 连接打开 + ws.onOpen(function(res) { + console.log('WebSocket 连接已打开', res); + that.updateStatus('已连接'); + that.addMessage('系统', 'WebSocket 连接已打开', 'system'); + }); + + // 监听 WebSocket 接收到服务器的消息 + ws.onMessage(function(res) { + console.log('收到服务器消息', res.data); + that.addMessage('服务器', res.data, 'received'); + }); + + // 监听 WebSocket 连接关闭 + ws.onClose(function(res) { + console.log('WebSocket 连接已关闭', res); + that.updateStatus('已断开'); + that.addMessage('系统', 'WebSocket 连接已关闭', 'system'); + }); + + // 监听 WebSocket 错误 + ws.onError(function(res) { + console.error('WebSocket 连接发生错误', res); + that.updateStatus('连接错误'); + that.addMessage('系统', 'WebSocket 连接发生错误: ' + JSON.stringify(res), 'error'); + }); + }, + + // 关闭 WebSocket 连接 + closeWebSocket: function() { + if (ws) { + wx.closeSocket(); + ws = null; + } + }, + + // 发送 Ping 消息 + sendPing: function() { + this.sendMessage(JSON.stringify({ action: 'ping' })); + }, + + // 发送测试消息 + sendTestMessage: function() { + this.sendMessage(JSON.stringify({ + message: '你好,这是微信小程序的测试消息!', + action: 'test' + })); + }, + + // 发送自定义消息 + sendCustomMessage: function() { + const message = this.data.inputMessage.trim(); + if (!message) { + wx.showToast({ + title: '请输入消息内容', + icon: 'none' + }); + return; + } + + try { + // 尝试解析为 JSON,如果不是 JSON 则包装为 JSON + JSON.parse(message); + this.sendMessage(message); + } catch (error) { + // 不是 JSON,作为普通文本发送 + this.sendMessage(JSON.stringify({ message: message })); + } + + // 清空输入框 + this.setData({ + inputMessage: '' + }); + }, + + // 发送消息 + sendMessage: function(message) { + if (!ws) { + wx.showToast({ + title: 'WebSocket 未连接', + icon: 'none' + }); + return; + } + + // 检查 WebSocket 连接状态 + wx.getSocketTask().then(task => { + if (task.readyState === 1) { // 连接已打开 + wx.sendSocketMessage({ + data: message, + success: () => { + this.addMessage('我', message, 'sent'); + }, + fail: (err) => { + console.error('发送消息失败', err); + this.addMessage('系统', '发送消息失败: ' + JSON.stringify(err), 'error'); + } + }); + } else { + wx.showToast({ + title: 'WebSocket 连接未打开', + icon: 'none' + }); + this.updateStatus('连接已断开'); + } + }).catch(err => { + console.error('获取 SocketTask 失败', err); + this.updateStatus('连接已断开'); + }); + }, + + // 更新输入框内容 + onInputMessage: function(e) { + this.setData({ + inputMessage: e.detail.value + }); + }, + + // 更新连接状态 + updateStatus: function(status) { + this.setData({ + connectionStatus: status + }); + }, + + // 添加消息到消息列表 + addMessage: function(sender, content, type) { + const messages = this.data.messages; + const now = new Date(); + const time = now.toLocaleTimeString(); + + // 尝试美化 JSON 格式 + let formattedContent = content; + try { + const parsed = JSON.parse(content); + formattedContent = JSON.stringify(parsed, null, 2); + } catch (error) { + // 不是 JSON,保持原样 + } + + messages.push({ + id: Date.now(), + sender: sender, + content: formattedContent, + type: type, + time: time + }); + + this.setData({ + messages: messages + }); + + // 滚动到最新消息 + this.scrollToBottom(); + }, + + // 滚动到消息底部 + scrollToBottom: function() { + wx.createSelectorQuery().select('#message-list').boundingClientRect(function(rect) { + if (rect) { + wx.pageScrollTo({ + scrollTop: rect.height, + duration: 300 + }); + } + }).exec(); + }, + + // 重连 WebSocket + reconnectWebSocket: function() { + this.closeWebSocket(); + setTimeout(() => { + this.connectWebSocket(); + }, 1000); + } +}); + +// 对应的 WXML 文件示例 +/* + + + WebSocket 测试 + + {{connectionStatus}} + + + + + + + + + + + + + + + + {{item.sender}} + {{item.time}} + + {{item.content}} + + + + + + +