1. 从一个很朴素的需求说起为什么要把 AI 管家塞进手机主屏事情的起点特别简单。我在电脑上搭了一套 AI 管家平时用浏览器打开一个本地页面输入问题、触发任务、看流式回复用着挺顺手。但问题来了——我不可能一直坐在电脑前。做饭的时候想让它帮我查个东西躺床上想让它记一笔待办出门在外想让它跑个定时任务每次都要开电脑、开浏览器、输地址这个链路太长了。我想要的其实就一件事像打开微信一样点一下主屏上的图标AI 管家就开了。不需要应用商店不需要开发者账号不需要上架审核就是一个能全屏运行、有独立图标、能接收流式输出的准原生应用。这个需求听起来像是要写一个 iOS App但实际上完全不用。iOS 的 Safari 有一个被很多人忽略的能力添加到主屏幕。把一个网页加到主屏之后它会以独立窗口的形式启动没有地址栏、没有底部工具栏看起来和原生应用几乎没区别。再配合 PWA 的 manifest 配置还能自定义图标、启动画面、主题色。对于个人自用的 AI 管家来说这就是性价比最高的方案。但加到主屏只是最后一步。真正让我折腾了好几个晚上的是中间那一堆问题AI 管家的后端跑在电脑上手机怎么访问流式输出SSE在移动网络下老是断怎么办本地开发用 HTTPiOS 加到主屏后有些能力受限怎么办多个服务端口怎么用一个域名统一入口这些问题的答案最后都指向了同一个东西——nginx 反向代理。所以这篇东西不是一篇PWA 入门教程而是我把一个跑在本地电脑上的 AI 管家一步步变成 iPhone 主屏上一个独立应用的完整过程。中间踩的坑、试错的方案、最后跑通的配置我都会写清楚。如果你也有类似的需求——不管是 AI 管家、自建笔记、家庭监控面板还是别的什么自托管服务——这套思路可以直接抄。关键词里出现的 iPhone、nginx、SSE、REST、iOS基本覆盖了这条链路上所有的技术点。下面我按实际操作的顺序来拆。2. 先想清楚架构手机、电脑、nginx 三者到底怎么连2.1 为什么不能手机直连电脑上的服务最直觉的做法是AI 管家跑在电脑的 8000 端口手机浏览器直接输http://192.168.x.x:8000不就行了局域网内确实能通我一开始也是这么干的。但很快就遇到几个问题。第一端口太多。我的 AI 管家不是一个单体服务它至少包含一个前端页面比如 3000 端口、一个后端 API比如 8000 端口、可能还有一个向量库或者任务队列的管理界面比如 6333 端口。手机上加三个主屏图标太丑了而且每个都要记 IP 和端口。第二HTTP 的坑。iOS 对非 HTTPS 的页面有一些限制尤其是涉及 Service Worker、剪贴板、部分传感器权限的时候。虽然添加到主屏本身不强制 HTTPS但一旦你想用离线缓存、想让它更像原生应用HTTP 就会成为绊脚石。第三流式输出不稳定。AI 管家的核心体验是 SSEServer-Sent Events流式返回一个字一个字往外蹦。手机在 WiFi 和蜂窝之间切换、锁屏、切后台的时候连接很容易断。如果前端直连后端断线重连的逻辑要自己写而且跨域、超时这些问题都会冒出来。所以结论很明确需要一个统一入口把多个服务聚合到一个地址下同时处理 HTTPS、超时、重连这些脏活。这个角色nginx 来当最合适。2.2 nginx 在这套架构里到底干了什么很多人对 nginx 的理解停留在装个服务器放静态文件其实它在自托管场景里是个多面手。在我这套方案里它承担了四件事反向代理手机只访问https://ai.home这一个地址nginx 根据路径把请求转发到不同的后端服务。/api/转给 8000/转给 3000/vector/转给 6333。TLS 终止nginx 持有证书对外提供 HTTPS对内还是 HTTP。后端服务不用改任何代码。SSE 长连接保持这是最关键的一点。nginx 默认会对上游连接做缓冲和超时控制SSE 流式输出会被它攒着一起发或者干脆超时断开。必须专门配置proxy_buffering off和proxy_read_timeout。统一域名配合本地 DNS 或者 hosts让ai.home这个域名指向 nginx手机和电脑都能用同一个地址访问。用一张表把职责理清楚组件职责对外暴露AI 管家前端页面渲染、SSE 接收否仅内网端口AI 管家后端REST API、任务执行否仅内网端口nginx反向代理、TLS、SSE 调优是443 端口iPhone Safari访问入口、添加到主屏客户端2.3 域名怎么解决本地 DNS 还是直接 IP这里有个现实问题手机要访问https://ai.home得先能解析这个域名。有两个思路。思路一改路由器 DNS。在路由器上把ai.home解析到电脑的内网 IP。优点是全家设备都能用缺点是很多家用路由器不支持自定义 DNS 记录或者藏得很深。思路二手机装一个能改 hosts 的工具。iOS 上比较麻烦需要描述文件或者特定 App。不推荐太重了。思路三直接用 IP 自签证书。手机访问https://192.168.x.xnginx 配一个自签证书。缺点是每次都要点继续访问而且自签证书在添加到主屏后体验不好。我最后选的是思路一的变体在电脑上跑一个轻量 DNS 服务比如 dnsmasq把ai.home指向本机然后路由器 DHCP 把这个 DNS 下发给手机。这样手机不用做任何配置输ai.home就能通。如果你嫌麻烦退而求其次用 IP 也行只是证书那块要接受自签的警告。提示如果你只是自己用不想折腾 DNS完全可以用http://电脑IP:端口先跑通等功能都验证完了再上 HTTPS 和域名。不要一上来就追求完美架构容易卡在证书上出不来。3. nginx 配置实战让 SSE 流式输出在手机上不断线3.1 一个能跑的最小配置长什么样先给一个我实际在用的 nginx 配置骨架后面再逐条解释为什么这么写。server { listen 443 ssl; server_name ai.home; ssl_certificate /etc/nginx/certs/ai.home.crt; ssl_certificate_key /etc/nginx/certs/ai.home.key; # 前端页面 location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 后端 API SSE location /api/ { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # SSE 关键配置 proxy_buffering off; proxy_cache off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; chunked_transfer_encoding on; } }这个配置看起来平平无奇但每一行都有讲究。下面拆开说。3.2 proxy_buffering offSSE 断流的头号元凶我一开始遇到的现象是AI 管家在电脑浏览器上流式输出正常一个字一个字蹦但手机通过 nginx 访问要么半天不出字要么一次性全出来要么直接报stream disconnected before completion: idle timeout waiting for sse。排查了半天问题就出在 nginx 的proxy_buffering。nginx 默认会把上游后端返回的数据先缓冲起来攒到一定大小或者上游关闭连接了才发给客户端。对于普通网页这是优化对于 SSE 这是灾难——因为 SSE 的本质就是后端持续不断地推小片段被 nginx 一缓冲流式就变成了批处理。proxy_buffering off;就是告诉 nginx别攒了上游来多少你就转发多少。这一行加上之后手机上的流式输出立刻就正常了。但光关缓冲还不够。还有一个隐藏的坑nginx 的 chunked 编码。SSE 依赖 HTTP 的 chunked transfer encoding如果 nginx 把响应重新编码了前端可能收不到正确的分块。所以配合chunked_transfer_encoding on;更稳妥。3.3 proxy_read_timeout为什么默认 60 秒会要命nginx 的proxy_read_timeout默认是 60 秒。意思是如果 60 秒内上游没有发任何数据过来nginx 就认为连接超时主动断开。问题在于AI 管家在思考的时候可能几十秒都不吐一个字。比如你问一个复杂问题后端在调用模型、检索向量库、组装上下文这期间 SSE 连接是静默的。60 秒一到nginx 咔嚓一刀前端就收到那个经典的idle timeout waiting for sse。解决办法很简单把超时时间拉长。我设的是 3600 秒1 小时足够覆盖绝大多数场景。如果你担心资源占用设个 600 秒也够用。关键是要大于你后端最长的静默时间。proxy_read_timeout 3600s; proxy_send_timeout 3600s;proxy_send_timeout是发送给上游的超时一般和 read 保持一致就行。3.4 Connection 头与 HTTP/1.1SSE 的协议前提SSE 是基于 HTTP/1.1 的长连接。nginx 默认对上游用 HTTP/1.0而且会带上Connection: close这会导致每个请求都新建连接SSE 根本没法保持。所以必须显式声明proxy_http_version 1.1; proxy_set_header Connection ;Connection 这个写法看起来有点怪它的作用是清空Connection 头让 nginx 使用 keep-alive 长连接。这是 nginx 官方推荐的做法比写Connection keep-alive更可靠因为后者在某些版本里会和上游的 keep-alive 机制冲突。3.5 实测对比改配置前后的差异我把改配置前后的表现整理成表你可以对照自己的现象现象改之前改之后流式输出攒一批一起出逐字实时输出长思考后60 秒断开持续保持锁屏再回来连接已死自动重连成功切 WiFi/蜂窝报错基本无感控制台报错idle timeout无这里要特别说一句SSE 的断线重连前端也要配合。浏览器原生的 EventSource 自带重连机制但重连间隔是固定的 3 秒而且不会带上次的上下文。如果你的 AI 管家需要断点续传得在后端记录会话状态前端重连时带上 session id。这部分不是 nginx 能解决的但 nginx 把连接稳定性提上去之后重连的频率会大幅降低。4. 把网页变成 iPhone 主屏应用manifest 与图标那些事4.1 添加到主屏幕到底做了什么在 Safari 里打开https://ai.home点分享按钮选添加到主屏幕你会得到一个图标。点这个图标启动页面会以独立窗口打开——没有地址栏没有底部导航状态栏颜色可以自定义。这就是所谓的Web App模式。它和真正的原生 App 的区别在于没有 App Store 分发、不能调用全部系统 API、后台能力有限。但对于一个自用的 AI 管家来说这些限制都不影响核心体验。你需要的打开就能用、全屏、有图标它全给了。关键是要配一个manifest.json告诉 iOS 这个网页应该被当成应用来对待。4.2 manifest.json 里哪些字段 iOS 真的认网上很多 PWA 教程列了一大堆字段但 iOS 的支持是出了名的选择性。我把实际生效的字段列出来{ name: AI 管家, short_name: 管家, start_url: /, display: standalone, background_color: #0f0f0f, theme_color: #0f0f0f, icons: [ { src: /icons/icon-192.png, sizes: 192x192, type: image/png }, { src: /icons/icon-512.png, sizes: 512x512, type: image/png } ] }几个要点display: standalone是核心。它决定了启动时是否隐藏 Safari 的界面。设成fullscreen在 iOS 上支持不好standalone最稳。icons至少给 192 和 512 两个尺寸。iOS 会自己缩放但给足尺寸能避免模糊。theme_color影响状态栏颜色配合apple-mobile-web-app-status-bar-style效果更好。start_url建议设成/如果你有登录态可以带上参数。除了 manifest还要在 HTML 的head里加几个 iOS 专属的 meta 标签meta nameapple-mobile-web-app-capable contentyes meta nameapple-mobile-web-app-status-bar-style contentblack-translucent meta nameapple-mobile-web-app-title contentAI 管家 link relapple-touch-icon href/icons/icon-192.pngapple-mobile-web-app-capable是老版本的写法新版本靠 manifest 的 display 字段但为了兼容还是加上。apple-touch-icon是 iOS 加主屏时用的图标优先级比 manifest 里的 icons 还高一定要配。4.3 图标设计的几个实操细节图标这块我踩过坑。iOS 加主屏时如果图标有透明背景会被自动填充成黑色或者白色看起来很奇怪。所以图标最好是满幅的、不透明的方形图iOS 会自己加圆角。尺寸方面我建议直接做一张 1024x1024 的源图然后用工具生成 192、512 两个版本。别用 SVGiOS 对 SVG 图标的支持不稳定。还有一个细节图标缓存。你换了图标之后删掉主屏上的旧图标重新添加有时候还是显示旧的。这是因为 Safari 缓存了。解决办法是改一下图标文件名或者在 URL 后面加个版本号比如icon-192-v2.png。4.4 启动画面让打开体验更像原生iOS 在 Web App 启动时会显示一个白屏直到页面加载完。这个白屏可以通过apple-touch-startup-image替换成自定义的启动图。link relapple-touch-startup-image href/splash/splash-1170x2532.png麻烦的地方在于iPhone 的屏幕尺寸太多了要为每个尺寸准备一张图。如果你不想折腾可以跳过这一步白屏也就一两秒的事。但如果你的 AI 管家加载比较慢比如要初始化模型启动图能显著提升体验。我自己的做法是只给主力机型准备一张其他机型接受白屏。毕竟这是自用工具不是要上架的产品。5. 本地开发环境的那些坑多端口、多站点、HTTPS5.1 为什么本地开发也要上 nginx有人会问我本地开发的时候直接localhost:3000不就行了为什么要套一层 nginx原因是开发环境和手机访问环境要一致。如果你本地用localhost:3000开发手机用https://ai.home访问两边的行为可能不一样——跨域策略、Cookie 作用域、SSE 超时、HTTPS 限制这些差异会让你在手机上遇到一堆本地复现不了的问题。所以我的做法是本地开发也走 nginx。电脑上访问https://ai.home手机上也访问https://ai.home两边环境完全一致。这样任何问题都能在电脑上先复现、先调试。5.2 多站点自定义域名怎么配如果你不止一个自托管服务比如还有笔记、监控面板、下载器可以用 nginx 的 server 块给每个服务配一个域名server { listen 443 ssl; server_name ai.home; # ... AI 管家配置 } server { listen 443 ssl; server_name note.home; # ... 笔记服务配置 } server { listen 443 ssl; server_name monitor.home; # ... 监控面板配置 }然后在本地 DNS 里把这些域名都指向电脑 IP。这样手机主屏上可以放好几个图标每个对应一个服务互不干扰。如果你不想搞这么多域名也可以用路径区分location /ai/ { proxy_pass http://127.0.0.1:8000/; } location /note/ { proxy_pass http://127.0.0.1:9000/; }但路径区分有个坑前端页面里的静态资源路径、API 请求路径都要相应调整否则会 404。域名区分就没这个问题每个服务都以为自己在根路径下。5.3 自签证书怎么让 iOS 信任HTTPS 是绕不开的。用 Lets Encrypt 需要公网域名内网用不了。所以只能自签。自签证书的流程用 openssl 生成 CA 根证书和服务器证书。把服务器证书配到 nginx。把 CA 根证书装到 iPhone 上并在设置 - 通用 - 关于本机 - 证书信任设置里手动信任。第三步是关键。很多人装了描述文件但忘了去证书信任设置里打开开关结果还是报证书错误。# 生成 CA 私钥和证书 openssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj /CNHome CA # 生成服务器私钥和 CSR openssl genrsa -out ai.home.key 2048 openssl req -new -key ai.home.key -out ai.home.csr -subj /CNai.home # 用 CA 签发服务器证书 openssl x509 -req -in ai.home.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out ai.home.crt -days 3650 -sha256生成之后把ca.crt通过 AirDrop 或者邮件发到 iPhone安装描述文件然后去信任设置里打开。注意自签证书的 CN 必须和你访问的域名一致。如果你用ai.home访问证书 CN 就得是ai.home否则会报net::ERR_CERT_COMMON_NAME_INVALID。如果要用 IP 访问CN 得是 IP而且现代浏览器对 IP 证书更严格建议还是用域名。5.4 开发时的热重载怎么和 nginx 共存前端开发一般都有热重载HMR它依赖 WebSocket。nginx 代理 WebSocket 需要额外配置location /ws { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; }Upgrade和Connection这两个头是 WebSocket 握手的关键。配好之后你在电脑上改代码手机上的页面也能自动刷新开发效率高很多。6. 从 REST 到 SSEAI 管家的通信层设计6.1 为什么 AI 管家要用 SSE 而不是普通 REST普通的 REST 接口是请求-响应模式你发一个请求服务器处理完一次性返回结果。对于 AI 管家来说这个模式有个致命问题——等待时间太长。你问一个问题后端要调用模型、生成回答可能需要 10 秒、30 秒甚至更久。如果用 REST前端发完请求就只能干等用户看到的是一个转圈的加载图标体验很差。SSE 解决的就是这个问题。它让后端可以边生成边推送前端收到一个字就显示一个字。用户看到的是文字在打字机式地出现感知上的等待时间大幅缩短。SSE 和 WebSocket 的区别也值得说一下。WebSocket 是双向的SSE 是单向的服务器到客户端。AI 管家的场景里客户端只需要发一次请求然后接收流式回复不需要双向通信。所以 SSE 更轻量、更简单而且基于 HTTP天然兼容 nginx 和各种代理。6.2 SSE 在移动网络下的特殊挑战手机上的 SSE 比电脑上脆弱得多原因有几个网络切换WiFi 和蜂窝之间切换时TCP 连接会断。锁屏休眠iOS 锁屏后Safari 可能会挂起页面SSE 连接被冻结。后台限制切到其他 App 后Safari 的后台执行时间有限。运营商中间设备某些网络设备会对长连接做超时清理。这些挑战里nginx 能解决一部分超时、缓冲前端能解决一部分重连、状态恢复后端能解决一部分会话保持、断点续传。三者配合才能做到基本无感。6.3 前端 EventSource 的重连策略浏览器原生的EventSource自带重连但它的行为是连接断开后等 3 秒然后重连不带任何上下文。对于 AI 管家来说这意味着重连后你收不到之前没传完的内容。我的做法是后端为每个会话维护一个消息队列。前端重连时带上session_id后端从队列里把没发完的部分继续推。这样即使断了用户也不会丢内容。const es new EventSource(/api/chat/stream?session_id${sessionId}); es.onmessage (event) { const data JSON.parse(event.data); appendToChat(data.content); }; es.onerror () { // EventSource 会自动重连这里只做日志 console.warn(SSE 连接中断等待自动重连); };如果你需要更精细的控制可以不用 EventSource改用fetchReadableStream手动解析 SSE。这样能自定义重连逻辑、超时时间、错误处理。但代码复杂度会高不少看你的需求。6.4 后端如何优雅地处理客户端断开后端在推 SSE 的时候如果客户端断开了继续推是浪费资源。Node.js 里可以监听req.on(close)Python 里可以捕获ConnectionResetError及时停止生成。// Node.js 示例 app.get(/api/chat/stream, (req, res) { res.setHeader(Content-Type, text/event-stream); res.setHeader(Cache-Control, no-cache); res.setHeader(Connection, keep-alive); let aborted false; req.on(close, () { aborted true; // 停止模型生成释放资源 }); const stream generateResponse(); stream.on(data, (chunk) { if (aborted) return; res.write(data: ${JSON.stringify({ content: chunk })}\n\n); }); stream.on(end, () { res.write(data: [DONE]\n\n); res.end(); }); });这里有个细节SSE 的消息格式必须是data: xxx\n\n两个换行表示消息结束。少一个换行前端就解析不出来。这个坑我踩过排查了半天才发现是格式问题。7. 装进主屏之后那些只有真机才暴露的问题7.1 状态栏颜色和刘海遮挡加到主屏之后页面是全屏的但 iPhone 顶部有刘海、底部有 Home Indicator。如果你的页面内容顶到最上面会被刘海挡住。解决办法是用env(safe-area-inset-*)body { padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); }配合viewport-fitcovermeta nameviewport contentwidthdevice-width, initial-scale1, viewport-fitcover这样内容会自动避开刘海和底部横条。状态栏颜色则通过apple-mobile-web-app-status-bar-style控制black-translucent会让状态栏透明页面内容延伸到状态栏下面视觉效果最好。7.2 输入框被键盘顶飞手机上输入文字时键盘弹出来会占据半屏。如果你的输入框在页面底部键盘一弹输入框就被顶到看不见的地方了。这个问题在 Web App 模式下尤其明显因为 Safari 的地址栏没了视口高度计算和普通浏览器不一样。我的处理方式是输入框固定在底部键盘弹出时用 JS 监听视口变化动态调整布局。window.visualViewport.addEventListener(resize, () { const offset window.innerHeight - window.visualViewport.height; inputBar.style.transform translateY(-${offset}px); });visualViewport是专门为这种场景设计的 API比监听resize事件准确得多。7.3 添加到主屏后 Cookie 和登录态丢失这是个很隐蔽的坑。Safari 里的登录态加到主屏后打开可能就没了。原因是 Web App 模式下的 Cookie 作用域和 Safari 不完全一样。我的解决办法是用 localStorage 存 token而不是依赖 Cookie。前端每次请求带上Authorization头后端从 header 里读 token。这样不管在 Safari 还是 Web App 模式下登录态都能保持。如果你非要用 Cookie记得设置SameSiteNone; Secure并且确保域名一致。7.4 后台切换后的状态恢复iOS 对后台页面的限制很严。你切到微信回个消息再切回 AI 管家页面可能已经被冻结了SSE 连接也断了。我的处理是监听visibilitychange事件页面重新可见时检查 SSE 连接状态如果断了就重连并拉取最新的会话状态。document.addEventListener(visibilitychange, () { if (document.visibilityState visible) { checkAndReconnectSSE(); syncLatestMessages(); } });这样用户切回来的时候看到的是最新的内容而不是一个卡住的旧页面。8. 一些让体验更顺滑的细节优化8.1 离线兜底没网的时候显示什么Web App 模式下如果没网Safari 会显示一个默认的错误页很难看。可以用 Service Worker 缓存一个离线页面没网的时候显示当前无网络请检查连接。// service-worker.js self.addEventListener(fetch, (event) { event.respondWith( fetch(event.request).catch(() { return caches.match(/offline.html); }) ); });Service Worker 在 HTTPS 下才能用这也是前面强调要上 HTTPS 的原因之一。8.2 触觉反馈让点击有实体感iOS 的 Safari 支持navigator.vibrate但支持度一般。更可靠的方式是用 CSS 的:active状态做视觉反馈配合-webkit-tap-highlight-color去掉默认的灰色高亮。button { -webkit-tap-highlight-color: transparent; transition: transform 0.1s; } button:active { transform: scale(0.97); }这个小小的缩放效果能让用户感觉按钮被按下去了体验上更接近原生。8.3 防止误触缩放和滚动穿透Web App 里双击缩放和滚动穿透是两个常见的烦人问题。双击缩放可以用touch-action: manipulation禁用滚动穿透可以在弹窗打开时给 body 加overflow: hidden。body { touch-action: manipulation; overscroll-behavior: none; }overscroll-behavior: none能防止页面在顶部或底部继续滚动时带动整个页面这在聊天界面里特别有用。8.4 性能首屏加载和内存占用AI 管家的前端如果是个 SPA首屏加载可能比较慢。我的优化思路是代码分割把聊天界面、设置界面拆成独立的 chunk按需加载。预加载关键资源manifest、图标、字体这些用link relpreload提前加载。避免内存泄漏SSE 连接、定时器、事件监听在页面卸载时清理干净。手机的内存比电脑紧张长时间运行后如果内存泄漏页面会变卡甚至崩溃。我一般会在开发阶段用 Safari 的 Web Inspector 连真机调试看内存曲线。9. 我踩过的几个印象深刻的坑9.1 nginx 的 location 匹配顺序把我坑了nginx 的location匹配有优先级规则精确匹配最高其次是前缀匹配^~然后是正则匹配~最后是普通前缀匹配。我一开始写了个location /api/和一个正则location ~ \.php$结果 API 请求被正则截胡了。排查这个问题的方法是在 nginx 配置里加add_header X-Debug-Location api;看响应头里到底是哪个 location 处理的。这个技巧很实用推荐记住。9.2 SSE 的data:后面少了个空格SSE 的格式规范是data: 内容\n\n冒号后面必须有一个空格。我一开始写成data:内容\n\n前端死活解析不出来。这个坑很隐蔽因为有些解析器容错有些不容错。规范写法是带空格的别省。9.3 自签证书的 SAN 字段现代浏览器包括 iOS Safari要求证书必须有SANSubject Alternative Name字段光有 CN 不够。生成证书的时候要加上openssl req -new -key ai.home.key -out ai.home.csr -subj /CNai.home \ -addext subjectAltNameDNS:ai.home少了这个iOS 会报证书无效。这个坑我卡了整整一个晚上。9.4 手机和电脑不在同一个网段有次我在外面用手机连家里的服务怎么都连不上。后来发现手机连的是访客 WiFi和电脑不在同一个网段自然访问不了。如果你需要外网访问那是另一个话题了涉及端口转发或者内网穿透但要注意安全别把服务直接暴露在公网上。10. 写在最后这套方案还能怎么扩展这套nginx PWA SSE的组合本质上是一个自托管服务的移动化方案。AI 管家只是其中一个应用场景同样的思路可以套到很多地方自建笔记加到主屏随时记录SSE 同步。家庭监控面板实时推送传感器数据SSE 天然适合。下载器管理手机上看进度、加任务。自动化脚本面板一键触发常用任务。扩展的方向也有几个多设备同步用同一个 nginx 入口电脑、手机、平板都能访问会话状态通过后端同步。推送通知iOS 的 Web Push 支持在逐步完善未来可以让 AI 管家主动推送提醒。语音输入Web Speech API 在 Safari 上可用配合 AI 管家能实现语音对话。我个人在实际操作中的体会是不要追求一步到位。先把最基本的手机能访问跑通再加 nginx再上 HTTPS再优化 SSE最后加到主屏。每一步都验证通过再往下走比一次性搭一个复杂架构然后到处 debug 要高效得多。最后分享一个小技巧如果你在调试 SSE 的时候不确定是 nginx 的问题还是后端的问题可以先用curl -N https://ai.home/api/chat/stream直接看原始输出。-N参数会禁用 curl 的缓冲让你看到实时的流。如果 curl 能看到流式输出但浏览器看不到那问题就在前端如果 curl 也看不到那问题就在 nginx 或后端。这个排查方法帮我省了很多时间。
阅读完成 · 觉得有帮助?