使用 Instant 与 SvelteKit / Svelte 5 构建实时应用:从零搭建、响应式查询到多人协作
后端数据库【免费下载链接】instantInstant is the best backend for AI-coded apps. You get auth, permissions, storage, presence, and streams — everything you need to ship apps your users will love.项目地址https://gitcode.com/gh_mirrors/inst/instant点击查看免费下载本篇指南围绕 Instant 官方 Svelte 接入文档start-svelte 文档展开讲解如何在 SvelteKit 与 Svelte 5 项目中接入 Instant 后端提供 auth、permissions、storage、presence、streams 等能力覆盖脚手架初始化、手动配置、schema 推送、SSR 禁用、响应式查询、事务写入、登录鉴权与 Cursors 多人光标组件。读完你可以独立完成一个实时同步的 SvelteKit Todo 应用并理解 Instant Svelte SDK 在源码层面的实现原理。一、两种接入方式自动脚手架与手动配置1.1 自动搭建create-instant-app最快的方式是使用 create-instant-app 脚手架一条命令即可生成已经接好 Instant 的 SvelteKit 项目npx create-instant-app --sv--sv是 SvelteSvelteKit模板的标识。脚手架会生成包含instant.schema.ts、instant.perms.ts与客户端初始化代码的项目骨架省去手工配置步骤。1.2 手动搭建从空白 SvelteKit 项目开始如果你想完全掌控每一步可以手动配置。首先创建一个空白 SvelteKit 应用npx sv create my-app然后安装 Instant 的 Svelte 客户端库npm i instantdb/svelte该包要求svelte ^5.0.0作为 peerDependency见 client/packages/svelte/package.json因此请确保项目运行在 Svelte 5 之上——这正是本指南针对 Svelte 5 响应式运行时的前提。接着使用 instant-cli 初始化一个 Instant 项目npx instant-cli initinit命令会引导你完成登录如尚未登录随后自动完成三件事创建 schema 文件、创建 permissions 文件、把appId等环境变量写入你的.env文件。由此你的项目便拿到了连接 Instant 云端的凭据。二、创建数据库客户端并理解初始化配置在src/lib/db.ts中创建全局共享的数据库客户端import { init } from instantdb/svelte; import schema from ../instant.schema; export const db init({ appId: import.meta.env.VITE_INSTANT_APP_ID!, schema, useDateObjects: true, });关键配置项说明appId来自VITE_INSTANT_APP_ID环境变量即 instant-cli 写入.env的应用 IDschema类型安全的 schema 定义为查询与事务提供完整的 TypeScript 推导与编辑器自动补全useDateObjects: true让i.date()类型的字段在客户端以 JavaScriptDate对象呈现而非时间戳数字。从源码看init内部先调用 core 的core_init创建InstantCoreDatabase再包装为InstantSvelteDatabase见 client/packages/svelte/src/lib/InstantSvelteDatabase.svelte.ts。这个包装类把 core 的auth、storage、streams直接暴露出来并在此基础上叠加 Svelte 5 的响应式 hooks。完整导出面init、id、tx、lookup、i、SignedIn、SignedOut、Cursors以及大量类型集中在 client/packages/svelte/src/lib/index.ts。完成这一步后你就可以对数据库执行查询与事务了。三、实战创建一个实时同步的 Todo 应用3.1 在 schema 中定义 todo 实体编辑src/instant.schema.ts加入todos实体import { i } from instantdb/svelte; const _schema i.schema({ entities: { $files: i.entity({ path: i.string().unique().indexed(), url: i.string(), }), $users: i.entity({ email: i.string().unique().indexed().optional(), imageURL: i.string().optional(), type: i.string().optional(), }), todos: i.entity({ text: i.string(), done: i.boolean(), createdAt: i.date(), }), }, links: { $usersLinkedPrimaryUser: { forward: { on: $users, has: one, label: linkedPrimaryUser, onDelete: cascade, }, reverse: { on: $users, has: many, label: linkedGuestUsers, }, }, }, rooms: {}, }); //...要点解析$files、$users是 Instant 内建的系统实体通常保留在 schema 中$users上的email声明了unique().indexed().optional()type字段用于区分 guest 与正式用户todos使用i.date()声明createdAt与客户端useDateObjects: true配合links段声明了$users与自身的一对多链接linkedPrimaryUser / linkedGuestUsers用于用户关联模型rooms: {}声明该应用使用 Instant 的实时房间presence/topics能力。3.2 推送 schema将 schema 同步到 Instant 云端npx instant-cli pushpush会把本地 schema 与权限规则推送到服务器端。3.3 关键前提为使用 Instant 的路由关闭 SSRInstant 目前尚未支持 SvelteKit 的 SSR服务端渲染因此凡是使用 Instant 的路由都需要显式关闭 SSR。在src/routes/page.ts中export const ssr false;这会告知 SvelteKit 该路由仅在客户端渲染避免服务端无法访问浏览器专属的 websocket 与本地存储而报错。3.4 编写 Todo 页面替换src/routes/page.svelte的内容script langts import { id, type InstaQLEntity } from instantdb/svelte; import { db } from $lib/db; import type { AppSchema } from ../instant.schema; type Todo InstaQLEntityAppSchema, todos; const query db.useQuery({ todos: {} }); let text $state(); function addTodo(todoText: string) { db.transact( db.tx.todos[id()].update({ text: todoText, done: false, createdAt: Date.now(), }), ); } function deleteTodo(todo: Todo) { db.transact(db.tx.todos[todo.id].delete()); } function toggleDone(todo: Todo) { db.transact(db.tx.todos[todo.id].update({ done: !todo.done })); } function deleteCompleted(todos: Todo[]) { const completed todos.filter((todo) todo.done); if (completed.length 0) return; const txs completed.map((todo) db.tx.todos[todo.id].delete()); db.transact(txs); } /script {#if query.isLoading} divLoading.../div {:else if query.error} divError: {query.error.message}/div {:else} {const todos query.data?.todos ?? []} div h2Todos/h2 div form onsubmit{(e) { e.preventDefault(); if (!text) return; addTodo(text); text ; }} input bind:value{text} autofocus placeholderWhat needs to be done? typetext / /form {#each todos as todo (todo.id)} div input typecheckbox checked{todo.done} onchange{() toggleDone(todo)} / span class:line-through{todo.done}{todo.text}/span button onclick{() deleteTodo(todo)}X/button /div {/each} div Remaining todos: {todos.filter((t) !t.done).length} button onclick{() deleteCompleted(todos)} Delete Completed /button /div /div /div {/if}代码中的关键机制db.useQuery({ todos: {} })订阅查询返回{ isLoading, data, error }状态对象任何其他客户端的数据变更都会实时推送并触发重新渲染id()生成一个新的本地实体 IDdb.tx.todos[id].update(...)与db.tx.todos[id].delete()构造事务块deleteCompleted演示了如何把多条事务一次性提交db.transact(txs)transact同时接受单个 chunk 或 chunk 数组见 InstantSvelteDatabase.svelte.tsInstaQLEntityAppSchema, todos提供了实体级类型提示。启动开发服务器后访问localhost:5173你就拥有一个功能完整的实时 Todo 应用了。仓库的 client/sandbox/sveltekit 目录提供了同主题的完整可运行沙盒包含 todos、auth、cursors、streams、infinite-scroll 等路由其中 src/lib/db.svelte.ts 展示了另一种初始化方式通过$state管理 db 实例的异步初始化状态。四、Svelte 5 响应式集成原理4.1 查询状态本身就是$state代理在 Svelte 5 中Instant 的 hooks 返回$state代理。你可以直接读取返回对象上的属性Svelte 会自动追踪这些属性并触发重渲染script langts const query db.useQuery({ todos: {} }); // query.isLoading, query.data, query.error are all reactive /script {#if !query.isLoading} p{query.data?.todos.length} todos/p {/if}从源码可以看到useQuery内部用$state({...})创建初始状态再通过$effect订阅 core 的查询流函数形式查询会被立即求值若返回null则重置为 loading否则调用coerceQuery规范化查询后先用getPreviousResult快速回填缓存结果再通过subscribeQuery持续更新result.data/result.error见 InstantSvelteDatabase.svelte.ts。$effect的清理函数负责退订查询条件变化时旧订阅被自动销毁。4.2 原始值用{ current: value }包装对于连接状态、本地 ID 这类原始值Svelte 5 的$state无法代理string/boolean/number因此 hooks 返回{ current: value }包装对象script langts const status db.useConnectionStatus(); // status.current is reactive /script pConnection: {status.current}/p实现上useConnectionStatus以$state({ current: ... })初始化并通过subscribeConnectionStatus更新result.current见 InstantSvelteDatabase.svelte.ts。同类设计还包括useLocalId(name)其current初始为null随后异步加载本地持久化的 ID见 InstantSvelteDatabase.svelte.ts。4.3 响应式与条件查询useQuery接受一个函数作为参数函数内部读取的$state变量一旦变化查询就会自动重新求值返回null则跳过查询script langts import { db } from $lib/db; const auth db.useAuth(); // Only query when we have a logged-in user const query db.useQuery(() auth.user ? { todos: {} } : null ); /script {#if auth.isLoading} pLoading.../p {:else if !auth.user} pPlease log in./p {:else if query.isLoading} pLoading todos.../p {:else} {const todos query.data?.todos ?? []} p{todos.length} todos/p {/if}同样的机制也支持响应式参数——函数体内读取任意$state变量都会在变化时触发新查询script langts import { db } from $lib/db; let filter $stateall | active | done(all); const query db.useQuery(() { if (filter all) return { todos: {} }; return { todos: { $: { where: { done: filter done } } } }; }); /script button onclick{() filter all}All/button button onclick{() filter active}Active/button button onclick{() filter done}Done/button这里{ $: { where: ... } }是 InstaQL 的查询子句语法用于过滤条件详见 InstaQL 查询文档。由于filter是$state变量点击按钮切换过滤条件时$effect会重新执行函数、重建查询订阅模板中的todos列表随之更新。4.4 写入数据Svelte 中的事务与 React 完全一致均通过db.transact完成script langts import { id } from instantdb/svelte; import { db } from $lib/db; function addTodo(text: string) { db.transact(db.tx.todos[id()].update({ text, done: false, createdAt: Date.now() })); } function toggleDone(todo: { id: string; done: boolean }) { db.transact(db.tx.todos[todo.id].update({ done: !todo.done })); } function deleteTodo(todoId: string) { db.transact(db.tx.todos[todoId].delete()); } /script事务语法创建/更新/删除/关联对象的完整说明见 InstaML 事务文档。db.transact内部直接透传给 core 的transact因此多客户端之间保持强一致的实时同步。五、认证useAuth 与守卫组件Svelte SDK 支持 Instant 的全部认证方式magic codes 邮箱验证码、guest auth 访客登录、Google OAuth 等完整列表见 认证总览。5.1 使用 useAuth 掌控完整状态db.useAuth()返回{ isLoading, user, error }让你精细处理加载、错误与用户状态script langts import { db } from $lib/db; const auth db.useAuth(); /script {#if auth.isLoading} divLoading.../div {:else if auth.error} divError: {auth.error.message}/div {:else if auth.user} div pHello, {auth.user.isGuest ? Guest : auth.user.email}!/p button onclick{() db.auth.signOut()}Sign out/button /div {:else} div pPlease log in./p button onclick{() db.auth.signInAsGuest()} Try as guest /button /div {/if}从源码看useAuth先以缓存用户_currentUserCached初始化$state随后通过subscribeAuth持续更新见 InstantSvelteDatabase.svelte.ts。此外还有useUser()在未登录时会抛出错误适合用于受保护路由getAuth()则是一次性获取当前登录用户。认证子模块db.auth与db.storage、db.streams一样直接来自 core 实例。5.2 更简单的 SignedIn / SignedOut 守卫组件如果只是按登录状态显示/隐藏内容可以直接使用SignedIn与SignedOut组件script langts import { SignedIn, SignedOut } from instantdb/svelte; import { db } from $lib/db; /script SignedIn {db} pYou are logged in!/p button onclick{() db.auth.signOut()}Sign out/button /SignedIn SignedOut {db} pPlease log in./p /SignedOut两个组件的实现非常轻量内部都调用db.useAuth()SignedIn仅在!isLoading !error user时渲染默认插槽SignedOut则在无用户时渲染见 SignedIn.svelte 与 SignedOut.svelte。选择建议需要访问isLoading、error或user.isGuest时用useAuth仅需按登录态显隐内容时用守卫组件更简洁。六、组件多人实时光标CursorsCursors是一个基于 presence 的多人光标组件用来展示房间内其他用户的实时鼠标位置。将任意区域用其包裹即可script langts import { Cursors } from instantdb/svelte; import { db } from $lib/db; const room db.room(main, my-room-id); /script Cursors {room} userCursorColortomato divMove your mouse around!/div /Cursors组件支持的 props与 Cursors.svelte 源码一一对应room由db.room(type, id)得到的房间句柄必填userCursorColor本机光标的颜色renderCursor自定义光标渲染的 Svelte 5 snippet接收{ color, presence }不传时使用内置 SVG 光标as包裹元素的标签名默认divclassName/style透传给包裹元素的样式spaceId同一房间内多个光标区域的命名空间默认按 room 自动生成propagate是否阻止鼠标事件冒泡默认阻止zIndex光标浮层层级默认99999。实现要点组件内部通过usePresence(room, { keys: [spaceId] })订阅房间 presence在mousemove/touchmove时把光标坐标换算为相对于包裹区域的百分比xPercent/yPercent并publishPresence鼠标/触摸离开时清除 presence其他 peer 的 presence 变化会实时驱动光标位置并带有transition: transform 100ms的平滑移动效果。db.room(type, id)返回InstantSvelteRoom实例见 InstantSvelteRoom.svelte.ts配合db.rooms命名空间还可用usePresence、useSyncPresence、useTopicEffect、usePublishTopic、useTypingIndicator等 hooks 实现更丰富的实时协作能力。presence 与 topics 的完整使用指南见 Presence, Cursors, and Activity 文档。七、总结接入流程回顾用 create-instant-app 一键脚手架或手动执行sv create→npm i instantdb/svelte→instant-cli init→ 编写db.ts→ 定义 schema 并instant-cli push→ 关闭使用 Instant 路由的 SSR → 在 Svelte 5 组件中通过db.useQuery/db.transact读写数据。理解 Svelte 5 的$state响应式语义对象属性直接追踪、原始值走{ current }包装、函数式查询自动响应依赖变化是熟练使用该 SDK 的关键而认证useAuth 守卫组件与实时协作Cursors及 rooms hooks则可以直接复用到绝大多数应用场景中。赞分享后端数据库【免费下载链接】instantInstant is the best backend for AI-coded apps. You get auth, permissions, storage, presence, and streams — everything you need to ship apps your users will love.项目地址https://gitcode.com/gh_mirrors/inst/instant点击查看免费下载相关推荐CO-DETR-MMCV-FULL项目交付物全解析训练脚本、模型文件与文档清单CO DETR MMCV FULL项目交付物全解析训练脚本、模型文件与文档清单 想要快速上手昇腾NPU上的目标检测模型训练CO DETR MMCV FULL2025 Svelte 5 极简教程从零基础到构建响应式应用2025 Svelte 5 极简教程从零基础到构建响应式应用 你还在为复杂的前端框架配置和冗余的虚拟DOM而头疼吗想快速开发高性能的Web应用却被冗长的学习前端Web框架编译器一文读懂Llama-3.2-1B许可证商用条款、7亿月活限制与合规指南一文读懂Llama 3.2 1B许可证商用条款、7亿月活限制与合规指南 想要在商业项目中使用Llama 3.2 1B这个强大的多语言大语言模型吗 了解其上一篇Umi-OCR 批量 OCR 实操从图片导入到文本导出下一篇Screenbox媒体播放器的沉浸式体验之旅创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Phoenix 前端 Relay 数据获取实践:Store 缓存保留、查询引用所有权与 node 单实体查询

Phoenix 前端 Relay 数据获取实践:Store 缓存保留、查询引用所有权与 node 单实体查询

可观测性AI 评测LLMOpsAI 应用人工智能 【免费下载链接】phoenix AI Observability & Evaluation 项目地址: https://gitcode.com/gh_mirrors/phoenix13/phoenix 点击查看 免费下载 导读 Phoenix(AI Observability & Evaluation 平台&#xff…

2026/9/24 15:11:27 阅读更多 →
Yii 2 框架设计决策指南:路径别名、消息翻译、异常处理等 8 项核心约定及其源码依据

Yii 2 框架设计决策指南:路径别名、消息翻译、异常处理等 8 项核心约定及其源码依据

后端Web框架 【免费下载链接】yii2 Yii 2: The Fast, Secure and Professional PHP Framework 项目地址: https://gitcode.com/gh_mirrors/yi/yii2 点击查看 免费下载 导读:本文基于 Yii 2 框架内部文档 design-decisions.md(波兰语版&#…

2026/9/24 15:10:26 阅读更多 →
KuGouMusicApi源码解析(一):文件名即路由,160个接口如何自动注册到Express

KuGouMusicApi源码解析(一):文件名即路由,160个接口如何自动注册到Express

KuGouMusicApi源码解析(一):文件名即路由,160个接口如何自动注册到Express 【免费下载链接】KuGouMusicApi 酷狗音乐 Node.js API service 项目地址: https://gitcode.com/gh_mirrors/ku/KuGouMusicApi 本文带你深入解析 K…

2026/9/24 15:10:26 阅读更多 →

最新新闻

基于图像识别的跨平台 UI 自动化框架:Airtest 安装、Python API 与 CLI 实战指南

基于图像识别的跨平台 UI 自动化框架:Airtest 安装、Python API 与 CLI 实战指南

测试质量保障计算机视觉 【免费下载链接】Airtest UI Automation Framework for Games and Apps 项目地址: https://gitcode.com/gh_mirrors/ai/Airtest 点击查看 免费下载 Airtest 是网易开源的跨平台 UI 自动化框架,专为游戏和 App 设计,核…

2026/9/24 15:57:07 阅读更多 →
Docker之镜像、容器、数据卷关系

Docker之镜像、容器、数据卷关系

Docker之镜像、容器、数据卷关系一个 Image(镜像)可以创建多个 Container(容器);Container 挂载 Volume(数据卷)后,数据可以在删除旧容器、创建新容器后继续使用。概念含义类比Image…

2026/9/24 15:57:07 阅读更多 →
LX Music 桌面版免费多源音乐搜索下载完整指南

LX Music 桌面版免费多源音乐搜索下载完整指南

LX Music 桌面版免费多源音乐搜索下载完整指南 【免费下载链接】lx-music-desktop 一个基于 Electron 的音乐软件 项目地址: https://gitcode.com/GitHub_Trending/lx/lx-music-desktop 凌晨一点,想找一首歌的完整版,你翻遍了三个音乐 App&#x…

2026/9/24 15:57:07 阅读更多 →
Humanizer ByteSizeExtensions 完全指南:.NET 字节与位单位转换、人性化格式化与速率计算的扩展方法全景

Humanizer ByteSizeExtensions 完全指南:.NET 字节与位单位转换、人性化格式化与速率计算的扩展方法全景

开发工具 【免费下载链接】Humanizer Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities 项目地址: https://gitcode.com/gh_mirrors/hu/Humanizer 点击查看 免费下载 本篇技…

2026/9/24 15:57:07 阅读更多 →
ParlAI 对话安全实战:基于 Build it Break it Fix it 对抗式训练的冒犯语言检测

ParlAI 对话安全实战:基于 Build it Break it Fix it 对抗式训练的冒犯语言检测

ParlAI 对话安全实战:基于 Build it Break it Fix it 对抗式训练的冒犯语言检测 【免费下载链接】ParlAI A framework for training and evaluating AI models on a variety of openly available dialogue datasets. 项目地址: https://gitcode.com/gh_mirrors/pa…

2026/9/24 15:57:07 阅读更多 →
Python新闻网站项目-4.数据处理和算法应用

Python新闻网站项目-4.数据处理和算法应用

基于Python、Scrapy、Gerapy、NLP以及Django框架构建的新闻采集与展示系统,旨在实现自动化新闻抓取、处理、展示和管理的一体化解决方案。本项目结合了爬虫技术、分布式部署、数据处理、前后端展示以及内容管理系统的构建,最终形成一个功能全面、用户友好的新闻网站。该系统不…

2026/9/24 15:56:06 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →