V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
karott7
V2EX  ›  程序员

请问线上 web 项目如何自动更新?

  •  
  •   karott7 · 2022-11-27 21:12:24 +08:00 · 5191 次点击
    这是一个创建于 725 天前的主题,其中的信息可能已经有所发展或是发生改变。
    比如用户正在访问我的项目,然后我发布代码更新版本,想知道如何在用户不点击刷新或者不重开网页的情况下让用户获取最新版本代码?
    54 条回复    2022-11-30 02:11:04 +08:00
    neoblackcap
        1
    neoblackcap  
       2022-11-27 21:17:03 +08:00   ❤️ 1
    使用 websocket 与服务器保持连接,然后监听版本变更。如果版本变动符合更新条件,那么就触发更新动作( js 主动刷新页面,或者 popup 告知用户,甚至你自己在代码做好热更新)
    karott7
        2
    karott7  
    OP
       2022-11-27 21:21:04 +08:00
    @neoblackcap 不至于用上 websocket 吧,这成本也太高了。
    karott7
        3
    karott7  
    OP
       2022-11-27 21:23:13 +08:00
    @neoblackcap 请问线上代码怎么做热更新?
    Puteulanus
        4
    Puteulanus  
       2022-11-27 21:31:22 +08:00
    热更新也不是说客户端连重载都不用吧
    karott7
        5
    karott7  
    OP
       2022-11-27 21:41:21 +08:00
    @Puteulanus 应该要重载,我想不出怎么在不重载的情况下更新某个 js 或者 css 文件
    kaneg
        6
    kaneg  
       2022-11-27 21:55:15 +08:00
    这毕竟是个 WEB 项目, 你们真的需要一发布所有用户立即就更新到最新版吗?
    一般来说用户在各个链接跳转过程中就不知不觉到新的版本了,最差第二天重新登录。
    karott7
        7
    karott7  
    OP
       2022-11-27 22:08:14 +08:00
    @kaneg 这个方案的场景当然不是针对 toC 的,是针对后台管理以及自动售卖机等项目的,毕竟每次发布都通知别人刷新浏览器不太方便。
    比如盒马的自动售卖机,一个城市有十几二十台机器,如果能做到自动更新,有两个好处:1. 不用担心白天用户使用的时候发布代码后用户还在使用老版本 2. 不用通知工作人员重启应用
    blankmiss
        8
    blankmiss  
       2022-11-27 22:12:46 +08:00
    用灰度发布去平滑更新?
    dcsuibian
        9
    dcsuibian  
       2022-11-27 22:14:40 +08:00
    轮询,发现有新版本了就刷新一下呗。
    如果是自动售货机的话,就等一段时间没有用户操作了再更新。
    karott7
        10
    karott7  
    OP
       2022-11-27 22:15:36 +08:00
    @dcsuibian 请求怎么做版本判断?
    dcsuibian
        11
    dcsuibian  
       2022-11-27 22:15:56 +08:00
    还有你这更新过程会不会把系统自己搞崩掉啊
    比如你直接用 js 做刷新,结果刷新那一会儿网不好,直接页面都没了
    karott7
        12
    karott7  
    OP
       2022-11-27 22:19:32 +08:00
    @blankmiss 我想过,其实不会,因为有 http 缓存配合,更新版本一般只更新几个文件,没更新的文件会走缓存。我不会也不想存任何文件在 storage 里;再说网络不好,那请求也走不通,那得不好页面也没事。
    你的方案和我想的一样,轮训,然后等用户没操作了就主动刷新浏览器。但我想知道你是如何去知道有版本更新的?
    dcsuibian
        13
    dcsuibian  
       2022-11-27 22:20:00 +08:00
    @karott7 留个链接,每次返回最新的版本号?我感觉跟现在的不一样就可以刷新了吧
    或者跟 npm 那种语义化版本号,大版本更新?
    qq976739120
        14
    qq976739120  
       2022-11-27 22:37:03 +08:00
    git pull
    kill
    build
    nohup xxxxx
    啊哈哈哈哈哈哈哈
    ryougifujino
        15
    ryougifujino  
       2022-11-27 23:18:15 +08:00
    如果是纯 SPA 的话,肯定就得刷新才能更新,因为得更新 index.html 上的引入 js 的那部分代码。要么客户自己刷新,要么就推送或轮询后检测到新版本自动刷新。至于检测方式,你可以在服务器上写一个函数,功能就是对比新旧 index 的内容 hash ,如果不一致的话就代表需要更新,都不需要版本号。
    ixixi
        16
    ixixi  
       2022-11-28 00:02:15 +08:00 via Android
    不重启的,每天晚上强制刷新一次。
    kingjpa
        17
    kingjpa  
       2022-11-28 00:22:47 +08:00
    做过售货机项目。
    用的是 mqtt ,设备开机后会订阅很多事件,发生了执行重启 刷新 出货 拉取广告等。

    轮询的话 流量消耗成本太高,售货机都是物联网卡
    Features
        18
    Features  
       2022-11-28 00:39:04 +08:00
    不明白,websocket 成本怎么会高呢?
    websocket 应该是浏览器开销最低的连接了吧?
    okakuyang
        19
    okakuyang  
       2022-11-28 00:53:23 +08:00
    @Features websocket 是即时性非常高的,所以会一直发送心跳,服务端也要有一个线程来维持这个连接。
    neoblackcap
        20
    neoblackcap  
       2022-11-28 01:03:30 +08:00   ❤️ 1
    @karott7 你只要确定模块并没有在使用的情况下,把所有的依赖全部更新,并在新的交互或者请求发生时,启用新的模块就可以了。
    opengps
        21
    opengps  
       2022-11-28 01:05:16 +08:00   ❤️ 1
    你这个更新达到了无感体验实时级别,要求过于高了,现在的更新都是下次访问刷新
    opengps
        22
    opengps  
       2022-11-28 01:06:41 +08:00
    浏览器端通信,或者使用 websocket 做实时,或者使用高频轮训方式来更接近实时
    neoblackcap
        23
    neoblackcap  
       2022-11-28 01:09:13 +08:00
    @karott7 如果要达到你的需求,仅仅是不需要人工介入,定时轮询加重载页面可能是最简单的。
    能做到热更新当然好,但是没有一点思路,硬上热更新,很可能把握不住,做出来 bug 满天飞。
    neoblackcap
        24
    neoblackcap  
       2022-11-28 01:12:48 +08:00
    @opengps 本质上就是网络监听,远程注入运行,JIT 热更新。说实在的,我看了一下帖子,没看出项目有这些必要。耗费人力成本太高,效果也不见得比轮询刷新强多少
    lower
        25
    lower  
       2022-11-28 08:19:39 +08:00
    浏览器上实在不好处理的话,就用 electron 之类的搞个客户端壳子,在壳子里实现一些特定的外围功能……
    296727
        26
    296727  
       2022-11-28 09:06:41 +08:00
    每个 http 请求的返回都加一个版本号,和当前的版本号不一样了,就强制刷新一下不就 OK 了?
    0xcaffebabe
        27
    0xcaffebabe  
       2022-11-28 09:29:10 +08:00
    做过一个类似的需求,不过是用户切换窗口的时候检查一下有没有新版本,主要是比对打包 js 文件的哈希来实现
    karott7
        28
    karott7  
    OP
       2022-11-28 10:06:39 +08:00
    @neoblackcap @opengps @dcsuibian @ryougifujino

    我觉得大家都把这个功能实现想复杂了,不必用 websocket ,也不搞热更新,也不用其他同事配合,只要前端一个人能就能完成。帖子开头和 #7 楼也把前提说清楚了,这个方案是针对 to b 的,比如自动售货机,肯定有一段空闲时用户不在使用的。
    其实有些人已经说到点子上了,自动更新版本肯定得用 js 执行 location.reload(),肯定得增加版本号(但这个版本号不用是 x.y.z 的形式,应为要额外为版本判断写代码);

    我昨晚也实验了一下,因为我目前就在做自动收银机的项目,大家看看这个方案咋样:
    -- 给 index.html 文件设置 http 缓存响应头 no-store 或者 no-cache ,保证每次拿到的 index.html 文件都是最新的
    -- 给 js/css/img 等其他资源设置一个比较长时间的缓存响应头,比如一年。
    -- 每次打包都给 index.html 文件中的 html 元素增加当前打包的时间戳,打包后的 <html /> 元素就变成了 <html data-timestamp="..." />, 这个时间戳其实就是版本号,因为我们只需要探测最新版本
    -- 我在全局增加一个每隔几分钟获取 index.html 的请求,fetch('/').then(response => const bodyString = response.text()), 拿到 document 字符串,再用正则解析出 data-timestamp 去和 document.documentElement.dataset.timestamp 对比,如果比这个值大,就是最新版代码,然后再检测用户没操作多少秒执行 location.reload(), 这样就更新代码了

    再说下 location.reload() 的执行前怎么才能不破坏用户体验
    - 如果是自动售卖机,我会给个 3 秒倒计时的弹窗,有文案提示系统检测到版本更新,即将更新,倒计时结束后就 reload
    - 如果是后台管理,我会像 vscode 每次更新一样,右下角给个提示,让用户自己觉得是否更新

    这个方案我昨天以为能最小代价更新代码,比如我只更新了一个 js 文件,我就希望刷新后除了这个 js 文件其他文件都能走缓存;其实不是,因为打包工具(比如 rollup )不止会给该 js 更新 hash ,所有引入该 js 文件的文件名中的 hash 都会改变,这就导致该 js 的祖先文件都不能走缓存了,现在服务器都是按请求收费的,这样肯定不划算,但目前没办法。

    不过如果你把外部库( node_modules )都单独分割出来,这个 js 文件(一般称为 vendor )基本是不变的,所以我觉得即使是在弱网环境下,reload 也不会太慢。

    不知道大家咋看?
    karott7
        29
    karott7  
    OP
       2022-11-28 10:07:44 +08:00
    @opengps 我没有要求无感体验哦
    opengps
        30
    opengps  
       2022-11-28 10:34:28 +08:00 via Android
    @karott7 不点击刷新或者不重开网页的情况下,这句话的要求,已经高于热更新重新刷新生效了
    karott7
        31
    karott7  
    OP
       2022-11-28 10:44:05 +08:00
    @opengps '想知道如何在用户不点击刷新或者不重开网页的情况下让用户获取最新版本代码?'
    有前提,在用户不点击刷新,‘用户’
    heishu
        32
    heishu  
       2022-11-28 10:58:55 +08:00
    @karott7 #10 可不可以,你在 token 里面读取 package 的 version 加上,后端每次 token 验证 version 是不是最新的。这种的话前端要更新版本,后端存的 version 也要手动改新的版本号
    karott7
        33
    karott7  
    OP
       2022-11-28 11:17:08 +08:00
    @heishu 抱歉,#10 的话我说错了,不是‘请求’,是请问;
    我觉得不用后端参与,越少人参与越好
    menglizhi2333
        34
    menglizhi2333  
       2022-11-28 12:05:56 +08:00
    PWA 可以做到无后端参与,进行 web 更新,你需要的文件缓存规则可以在 Service Work 中进行细致化定义

    https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Service_Workers#%E6%9B%B4%E6%96%B0%E4%BD%A0%E7%9A%84_service_worker
    blankmiss
        35
    blankmiss  
       2022-11-28 12:46:28 +08:00
    我说的灰度部署 是以我后端的角度去看这个问题的 前端只更新几个文件的 我想应该能够以#15 的方式进行
    karott7
        36
    karott7  
    OP
       2022-11-28 13:09:24 +08:00
    @blankmiss 我不懂灰度部署,不过#15 的方案中对比以文件内容生成 hash 值其实更好一点,线上项目也可能遇到回滚。
    还有一种方式,fetch('/') 拿到 http etag 响应头,这也是个 hash 值,服务器自动生成,直接对比这个就好了
    karott7
        37
    karott7  
    OP
       2022-11-28 13:10:37 +08:00
    @menglizhi2333 我觉得没必要做成 PWA ,按照 #28 的方案,我觉得已经可以完成这个功能了,虽然有点瑕疵,但是对于 js 体积不大的项目来说已经完全够用了
    coderyyj
        38
    coderyyj  
       2022-11-28 13:31:50 +08:00
    我之前也有过这个需求,被我砍掉了
    GreatAuk
        39
    GreatAuk  
       2022-11-28 13:44:57 +08:00
    R1hu6Hs2sSN8pkVX
        40
    R1hu6Hs2sSN8pkVX  
       2022-11-28 13:52:20 +08:00
    gitaction
    poembre
        41
    poembre  
       2022-11-28 17:30:46 +08:00
    客户端 websocket 做个 断线重连, 服务端直接重启进程
    Finnn
        42
    Finnn  
       2022-11-28 17:39:45 +08:00
    完成这个需求其实用 websocket 并不常见, 多的是 service worker
    Finnn
        43
    Finnn  
       2022-11-28 17:41:09 +08:00
    @Finnn 像 Vue 文档官网这些之前有更新提示, 最新版也砍掉了
    star7th
        44
    star7th  
       2022-11-28 20:37:49 +08:00
    本质是个假需求。web 项目不需要那么实时的自动更新。跑一个五分钟轮询一遍的任务即可。也不用上 websocket ,免得带来太大的并发成本
    weixiangzhe
        45
    weixiangzhe  
       2022-11-29 09:15:15 +08:00
    我们只处理了 import('./page/a.js') 失败的情况,加载失败进入错误页,错误页上有个刷新按钮,点击 location.reload()
    shengchao
        46
    shengchao  
       2022-11-29 10:05:16 +08:00
    问题是你这个售货机的浏览器带 service worker 吗?
    karott7
        47
    karott7  
    OP
       2022-11-29 10:59:31 +08:00
    @shengchao 不需要用 service worker ,我就用 fetch('/') 方法每五分钟请求一次,拿到响应头 etag 对比一下,不一样的话就表示版本更新,然后我在适当的时机 location.reload() 就更新版本了
    karott7
        48
    karott7  
    OP
       2022-11-29 11:00:17 +08:00
    @star7th 也不能说是假需求,只能说场景有限,自动售货机是一个场景
    janus77
        49
    janus77  
       2022-11-29 14:43:28 +08:00
    既然你的场景是自动售货机,那么售货机在无人使用的时候都是有个类似屏保的页面吧,有人使用的时候点一下才会进入正常的业务页面,你在点一下进入的时候每次都 load 最新版的就行了
    karott7
        50
    karott7  
    OP
       2022-11-29 15:24:05 +08:00
    @janus77 你这么做完全没考虑资源加载速度的问题,万一刚好网络波动呢?万一更新的 js 资源过大呢?还有 load 的时候屏幕会闪烁吧,这体验也不好。
    再说我从头到尾都没说一定要实时更新。
    janus77
        51
    janus77  
       2022-11-29 15:53:47 +08:00
    @karott7 #50 第一如果网络波动那就进入没更新的老版页面,下一次在进入的时候再尝试更新。第二资源过大那是你们项目架构设计的问题,我这个方案并不设计 js 怎么分割这种细节。
    janus77
        52
    janus77  
       2022-11-29 15:55:25 +08:00
    @karott7 #50 还有,既然你不要求实时更新,那么就更简单了,每次进入的时候尝试请求是否有新版本,但是并不下载新资源。等结果返回有新版本时,后台自己在空闲的时候下载新资源,那么就能保证无缝体验了
    xxxbin
        53
    xxxbin  
       2022-11-30 01:12:59 +08:00
    听起来好像可以用 h2 的服务去推送
    Finnn
        54
    Finnn  
       2022-11-30 02:11:04 +08:00
    虽然略奇葩的需求,
    但是我觉得从在线文档可以找到灵感,多人协作文档更新的时候是自动更新的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1933 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 16:17 · PVG 00:17 · LAX 08:17 · JFK 11:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.