作者: Zeeko

  • 前端处理响应式图片

    MDN: Responsive images 介绍了在前端实现响应式图片的几种方法:

    • 为不同尺寸的屏幕加载不同大小的图片(srcset 配合 sizes
    • 固定图片大小,为不同 DPI 的屏幕加载不同 DPI 的图片(srcset
    • 为不同尺寸的屏幕加载不同的图片(picture 配合多个 source

    WordPress 采用 srcset/sizes 方案实现响应式图片,默认的设置为 sizes="(max-width: {{image-width}}px) 100vw, {{image-width}}px"。当页面宽度小于图片原始宽度时,以页面宽度(100vw)作为图片的显示宽度,当页面宽度大于图片宽度时,总会以图片原始宽度作为显示宽度。但实际上,不是所有的图片都会以原始宽度在页面上展示,由于排版样式的影响,图片的展示宽度往往跟原始宽度不一致。

    为了解决这个问题,需要使用 Enhanced Responsive Images 插件,这个插件会基于当前主题的 Content Width, Wide Width 为图片元素设置 srcset/sizes,相较于默认的实现,可以为大屏设备在图片宽度小于窗口宽度时提供更加合适的缩略图尺寸。

    Performance Lab 提供的 Enhanced Responsive Images 插件

    启用后的效果如下图:

    可以看到,虽然 img 元素的大小只有 800×312 px,但是浏览器选择了展示原图(1341 x 523 px),这是因为我的屏幕启用了 HiDPI,1 css px 约等于 2 显示器 px,这种情况下,浏览器使用更大尺寸的图片是合理的。

  • 使用 WebP/AVIF 压缩博文图片

    使用 WebP/AVIF 压缩博文图片

    往文章中贴截图的时候,如果使用压缩率较低的图片格式(jpeg/png),长时间的图片下载耗时会降低用户的阅读体验。

    根据 CanIUse 的统计,目前绝大多数的网上冲浪用户使用的浏览器已经支持了 WebP/AVIF 这类高效的图片格式。

    https://caniuse.com/avif Can I use AVIF?
    https://caniuse.com/webp Can I use WebP?

    以文章开头的那张图片为例,原图大概 108KB,转换成 AVIF 格式并压缩后,体积缩小了接近 90% 。

    如果想要给 WordPress 启用自动转换的功能,我推荐使用下面的免费插件,只要你的 PHP 支持 imagick/GD 插件,就可以使用。

    安装完成后需要在管理面板中启用 Mordern ImageFormats 功能。

    如果需要把已有的图片都转换成 WebP/AVIF,可以使用下面的插件批量转换。

    安装完成后就可以在后台管理页面批量转换格式或者单独地转换格式。

    一些疑问

    如果用户的浏览器不支持 WebP/AVIF 该怎么办?

    AVIF 在比较新的浏览器中普及率很高 只有现代 IE —— Safari 不支持,WebP 的兼容性也非常好,不过万一有人用史前时代的浏览器上网呢?

    其实这个问题完全不用担心,上面介绍的插件在生成 WebP/AVIF 格式后并不会删除原图,浏览器会跟 WordPress 协商应该使用何种格式的图片向用户展示。

    怎么确认我正在使用 WebP/AVIF 版本的图片?

    仅仅依靠文件名无法区分图片格式,应该检查浏览器请求的响应部分,如果 Content-Type 的值为 image/avif 说明这张图片正在以 AVIF 格式展示,WebP 也是同理。

    为什么图片看起来有些糊?

    CompressX 插件默认使用有损压缩,建议手动调整为无损压缩选项。

    为什么转换格式这么慢?

    转换速度完全取决于你的服务器配置,简而言之,加钱世界可得~

    为什么 PNG 图片没有被自动转换成 WebP/AVIF ?

    因为 Modern Image Format 暂时还没加上这个功能,具体可以看这里的讨论:https://github.com/WordPress/performance/pull/1421。就目前而言,可以使用 CompressX 插件手动转换 PNG 图片。

  • 不要钱的博客网站性能优化

    赛博菩萨 Cloudflare 给免费帐户提供了 Cache Rules 功能,除了可以点亮域名后的云朵图标之外,还可以利用 Cache Rules 缓存博客网站的页面,提高访问者的使用体验。

    要给 WordPress 网站启用 Cache Rules 推荐使用下面这个插件:

    Super Page Cache for Cloudflare

    不过在使用这个插件的时候需要注意,这个插件自带的 Cloudflare API Token 权限说明有误,请参考下面的权限列表。

    如果你想我一样还使用了 LiteSpeed Cache 等缓存插件,建议参考 Super Page Cache 的 FAQ 来调整其他关联插件的配置。

    完成插件的安装跟配置后,可以在 Cloudflare 控制台看到由插件创建的 Cache Rule:

    至此,你的网站就可以享受到免费的全球 CDN 加成了。

  • 等一个后续

    像个鸵鸟一样,把头埋进沙子里。

    2024-07-09 五问煤制油罐车混装食用油:流向哪里?有何危害?谁该负责?

    https://web.archive.org/web/20240809024444/https://www.thepaper.cn/newsDetail_forward_28010047

    2024-08-27 国务院食安办通报“罐车运输食用植物油乱象问题”调查处置情况(简讯)

    https://web.archive.org/web/20240829090033/http://paper.people.com.cn/jksb/html/2024-08/27/content_26077451.htm

    2024-08-27 关于征求《食用植物油散装运输卫生要求》拟立项强制性国家标准项目意见的通知

    https://web.archive.org/web/20240829090629/https://www.samr.gov.cn/bzjss/zqyj/art/2024/art_94a628a1632644a2862d4f2ba8f6fc43.html

    2024-08-08 披露“75人涉嫌盗尸4000具”案律师:我甘愿受处罚,希望推进法律完善

    https://web.archive.org/web/20240809024851/https://news.sina.com.cn/s/2024-08-08/doc-inchxvay1591238.shtml

  • 为什么中国人均汽车保有量这么低?

    中国汽车保有量有多低?

    各国人均汽车拥有量列表

    2023 年统计数据:

    总人口 14.1亿 https://www.stats.gov.cn/xxgk/jd/sjjd2020/202401/t20240118_1946711.html

    总汽车 3.36 亿 https://www.gov.cn/lianbo/bumen/202401/content_6925362.htm

    每千人汽车数 238

    感觉上中国的人均汽车保有量不应该这么低

    收入太低导致汽车保有量低?

    对比分析几个国家的用车成本与人均可支配收入的关系

    • 用车成本:一辆车在燃料、税费、保养、维护上一年的支出,排除车辆折旧跟车贷
      • 为了方便跟美国的统计结果比较,年行驶里程假定为 24,000 公里
    • 人均可支配收入:一个人一年的税前总收入 – 税费
      • 美国是这么计算的,不太好确认中国跟南非是否也是如此计算
    美国中国南非
    千人汽车保有量868238232
    用车成本占人均可支配收入比例43.78%48.07% (燃油)
    13.95% (新能源)
    83.28%
    中国、美国、南非用车成本与人均可支配收入对比

    实际上,美国人每年平均驾驶里程数大概在1.4万英里(约2.25万公里);中国人每年平均驾驶里程大概在 1.5 万公里;南非人每年平均驾驶里程在 1.2 万公里左右 。考虑到这点对燃料的影响后,实际上中国的用车成本只会比美国更低。

    从这方面来看,用车成本似乎并不是导致中国人均汽车保有量较低的原因。

    中国用车成本与人均可支配收入

    数据来源:懂车帝排行榜最近一年(2024年8月)销量最高的10万元以下轿车:

    人均可支配收入数据:

    2023年1月17日,国家统计局发布数据显示,2022年全国居民人均可支配收入36,883元,比上年增长5.0%,扣除价格因素,实际增长2.9%。分城乡看,城镇居民人均可支配收入49,283元,增长3.9%,扣除价格因素,实际增长1.9%;农村居民人均可支配收入20,133元,增长6.3%,扣除价格因素,实际增长4.2%。

    中国居民收入的五档划分,从数据看差距_澎湃号·政务_澎湃新闻-The Paper

    中国用车成本占可支配收入比例:

    • 燃油车:17728/36883=48.07%
    • 新能源车:5116/36883=13.95%

    美国用车成本与人均可支配收入

    用车成本的数据来源(2023年)

    Based on AAA’s data, the average monthly cost of owning and operating a car is $1,015. The organization used six cost categories to determine their average: depreciation, finance costs, fuel, insurance, government taxes and fees, and maintenance, repair and tires. They based their numbers on vehicles that were driven for approximately 15,000 miles a year and assumed a five-year ownership period. Your own rate will likely differ from the average since it is based on factors unique to you, your car and your situation.

    The average cost of owning a car

    折旧费数据来源

    According to AAA, depreciation costs car owners an average of $3,334 annually.

    美国人均可支配收入(2023年):20,204.9 美元

    美国用车成本占可支配收入比例:

    1015*12-3334 / 20204.9 = 43.78%

    南非用车成本与人均可支配收入

    用车成本数据来源 The real cost of owning a standard R350,000 car in South Africa – BusinessTech

    每年用车成本(按24,000公里计算燃料消耗,排除车贷):

    (11167-7222+1150) * 12 = 61140 ZAR

    南非总可支配收入:4,449,450 百万 ZAR(2023)

    南非人口60,604,992(2022)

    南非人均可支配收入:4,449,450,000,000 / 60,604,992 = 73,417.76 ZAR

    用车成本占可支配收入比例:61140 / 73,417.76 = 83.28%

    中国人用车需求不足?

    美国人每年平均驾驶里程数大概在1.4万英里(约2.25万公里);中国人每年平均驾驶里程大概在 1.5 万公里;南非人每年平均驾驶里程在 1.2 万公里左右

    根据易车网的数据,大部分国人用车的高频场景是通勤,所以中国人对车辆的需求应该还算比较刚需的。

    中国人倾向使用共享出行?

    中国跟南非千人汽车保有量跟人均驾驶里程相差不太 ,南非在人均 GDP、人均可支配收入比中国低的情况下,而汽车保有量大,可能有共享出行性价比低的原因。

    南非公共交通并不发达,且网约车价格昂贵:

    由于南非基本没有像国内那样的公交系统,都是私人运营线路,当地人用小巴来跑这些线路,在南非这个词就叫 Taxi Bus

    可靠数据是南非 Uber 有 13000 个兼职或者专职司机(20 年数据),而中国滴滴是有 1166 万个兼职或专职司机(19 年数据)

    记得我 2015 年刚去南非没买车的时候,我为了去二手车店看车,打了个 Uber,区区 10 公里的路,打了 150 兰特,约 80 人民币,当时国内打车也就 20,30 的样子,当时真的让我觉得肉疼。后来赶紧自己买车买个几十公里也就是个油钱,也就很少打车了。

    杂谈南非打车

    中国则全然不同,公交覆盖广,而且网约车的普及率也高:

    城市交通出行大调查-36氪

    我认为大家倾向使用共享出行的方式的主要原因还是价格因素:

    • 城市公交都有补贴
    • 网约车定价超低:
      • 身边统计学,成都的网约车单价大概在2元/公里,按每年 1.2 万公里的出行里程来算,大概需要 2.4 万元;虽然比养车成本高,但如果算上自有车辆的折旧费、车贷,网约车可能跟自有车费用差不多甚至更便宜。

    结论

    中国共享出行比自有车辆成本更低,且覆盖了主要出行的上下班通勤场景,满足了当前阶段的出行需求,所以汽车保有量会保持在一个较低的位置。

    如果要让中国千人汽车保有量提升到 400?

    • 减少公交补贴
    • 网约车涨价
  • Migrate blog site to Hawk Host

    I migrated this blog site to Hawk Host last weekend, it doesn’t take much time, about 2~3 hours.

    • Create a new database and a database user for WordPress
    • Upload public_html with rsync FTP is too slow
      • rsync -avhP -e ssh ./public_html host-server:~/path/to/website/public_html
    • Update DNS records on CloudFlare

    My previous hosting service provider set up a mailer for me.

    After migration, the mail notification didn’t work, so I installed an SMTP plugin for WordPress.

    WP Mail SMTP by WPForms – The Most Popular SMTP and Email Log Plugin – WordPress plugin | WordPress.org

    Reference

  • DataStory SOW Model

    The problem

    In a workflow-style data processing system, if all the data sources are granted to complete, how do we ensure that the execution of a processing task can reach its complete state?

    Terms

    NameDescription
    Data StreamAn observable stream of async data emitted by something.
    SourceNode (Abbr. S)A node that creates a granted-to-complete Data Stream for the next node in the Flow, which is usually the start point of a data processing task.
    OperatorNode (Abbr. O)A node that observes the Data Stream from the previous node in the Flow, and creates a granted-to-complete Data Stream for the next node; which is usually used to transform data, fetch data from API using the incoming data, etc.
    WatcherNode (Abbr. W)A node that observes Data Stream from the previous node; which is usually used to inspect the data processing state, e.g. logging, and reporting progress.
    FlowA list consists of SourceNode, OperatorNode, and WatcherNode, it must start with SourceNode and end with WatcherNode (S, O….O, W) or completely consist of OperatorNode(O, O, …. O).
    DiagramA set of Flows

    Define the complement

    S

    The S creates a granted-to-complete data stream, when the data stream completes, S itself also reaches its complete state.

    The data stream S created is consumed by the next node (e.g., O, W) in the Flow list.

    type S<T> = DataStream<T>
    

    O

    In the Flow list, every incoming data from the previous node, makes O create a new granted-to-complete data stream for the next node. Therefore, O is state less, it doesn’t have a complete state, it propagates the complete state from the previous node to the next node.

    type O<A, B> = DataStream<A> => DataStream<B>
    

    As you can see, the O is a function that takes data stream in and out.

    W

    In the Flow list, W consumes data from the previous node, when the previous node comes to the complete state, the W reaches to its complete state.

    type W<T> = DataStream<T> => void
    

    Flow

    Stateful flow (S, O, …. O, W)

    If flow starts with a S and ends with a W, it is a stateful flow.

    When the last W, comes to a complete state, the Flow reaches to complete state.

    The complete state is propagated from the beginning S to the last W via many Os.

    type StatefullFlow<A, B> = {
      source: S<A>;
      operators: O[];
      watcher: W<B>;
    }
    

    Stateless flow (O, O, …. O, O)

    The stateless flow behaves like an O, it creates a granted-to-complete data stream based on the incoming data.

    type StatelessFlow = O[];
    

    Diagram

    When every flow in the Diagram completes, the diagram reaches a complete state, which means the current running data process task is completed.

    To have a granted-to-complete Diagram, we need to make sure:

    1. All S, O in the diagram are correctly implemented to create a granted-to-complete data stream, which can be verified by doing code reviews and writing unit tests for these nodes’ implementation
    2. A method to prevent an Infinite Flow

    The Infinite Flow

    For the OperatorNodes, we can have a compose function, it connects the 2 OperatorNode and returns a newly composed OperatorNode.

    function compose(op1: O<A, B>, op2: O<B,C>): O<A, C>;
    

    This enables us to convert any Stateless Flow into an OperatorNode.

    We can also have a delegate function for OperatorNode:

    function delegate(operatorRef: ()=> O<A, B>): O<A, B>;
    

    It creates a new O, which creates the data stream using the returned O of operatorRef when it receives incoming data from the previous node.

    With the help of compose and delegate, it is possible to reuse stateless flow in the diagram:

    Since the reuse is based on reference, it is possible to make reference circles in the diagram, which may lead to infinite processing.

    Branch

    To avoid this problem, we need to add the branching ability to OperatorNode.

    function branch(selector: (data: T) => O<T,T>): O<T, T>
    

    branch creates a new O, when data arrives at O, it sends the data to the O’ returned by selector(data), then pipe the output of O’ to the next node.

    If there is no need to do branching for some data, the selector function can return a identity O:

    function identity: O<T, T>
    

    It just outputs what it received.

    With the help of branch and identity, we can now add an exit branch for a circular flow, but the exit branch can not be verified until running the data processing task.

    To avoid infinite processing at runtime, we need to implement a delegation depth detection at runtime.

    Delegation Depth Detection

    The delegation depth actually means “How many times does the data pass through a DeledateNode“, it simply comes to an idea that attaching a delegateCount property to the data inside the data stream. When data passed through a DelegateNode created by delegate, the delegateCount increases.

    When the delegateCount reaches a limit, let’s say, 200k, the DelegateNode should drop the data and write a warning log.

    Answer to the beginning question

    1. Make sure all S, and O are correctly implemented to create a granted-to-complete data stream
    2. Use compose, delegate to allow circular reference in the Diagram
    3. Use branch to add an exit branch to the circular flow
    4. Use delegate to avoid infinite-traveling data in the flow at runtime
  • 2024 新年,我的 EDC 立大功

    春节假期,我的 EDC (Every Day Carry,每日携带物品)立了大功,不管是修理门锁还是处理小伤口,都派上了用场。接下来,我要和你们聊聊这些随身小物怎样解我燃眉之急。

    假期之前,带我用的

    春节假期前我按照“带你用的,用你带的”思路,给自己配置了一个可以塞进衣服口袋的随身 EDC 小包,里面放有我精心挑选的工具小物,旨在帮助我应对日常所需和一些较为紧急的情况。我的物品配置理念可以总结为三个原则:带能用的、带常用的、以及带“我”要用的。

    三个原则

    首先,选择装备应基于个人的知识储备,确保每一件装备都是你能够熟练使用的。这意味着,即使是最高端的工具,如果你不能熟练使用,它也不应该出现在你的 EDC 中。其次,针对高频场景选择用得上的装备,这要求我们对日常生活有深刻的观察和了解,确保 EDC 中的每一样物品都能在常见情况下派上用场。最后,每个人的生活方式和需求都有所不同,因此,配置 EDC 时还应考虑个人的独特需求,确保它真正适合自己。

    多功能工具

    我的 EDC 中携带的多功能工具很少,只有一把“钥匙”( https://geekey.com ),这个小工具在国内可以买到款式类似但价格更加亲民的盗版。我选择它主要是为了这些功能:

    • 开瓶器
    • 割绳锯齿
    • 扁头撬棒(一字螺丝刀)

    像瑞士军刀这样的多功能工具也有这些功能,但是国内公共交通中的安检标准过于灵活,我不想在外出的时候因为安检惹上任何麻烦,就只好选择了这个小钥匙。

    个人卫生

    受限于我贫瘠的医疗知识储备,我的随身医疗物品只能处理小创伤或者让人很“急”的症状:

    • 碘伏棉片:清理伤口,物品消毒
    • 丁腈手套:消毒后接触伤口
    • 氯雷他定片:应对突发的过敏症状
    • 蒙脱石散:应对急性腹泻
    • 创口贴、医用透气胶布:应对简单的小伤口,比如鞋子磨脚
    • 一张便签:记录药物的用法用量、保质期
    • 棉棒:无聊的时候可以掏掏耳朵
    • 牙线:由于牙缝太宽每次吃肉过后必须得清理牙缝
    • 唇膏:

    物品收纳

    除了上面介绍的多功能钥匙之外,其他所有的装备都会装进一个小包。为了方便塞进衣服口袋,我选择了一款钱包大小可以平铺展开的拉链小包。它比我的手机略宽一些,但是要矮很多,能放下手机的口袋一般也能装下它。

    小包尺寸
    尺寸示意
    内部收纳空间
    内部收纳布局

    扩展收纳

    除了小包本身之外,为了应对临时的大体积收纳场景,我在小包中放了一个小号的食品保鲜袋跟一个常规尺寸的垃圾袋。外出溜街的时候,食品保鲜袋可以装洗净的水果,垃圾袋除了用来装垃圾之外,还可以当作临时的防潮坐垫。

    春节假期,用我带的

    正如我所展示的,我的 EDC 配置基本涵盖了从基础工具到个人卫生等多方面的需求,它们在我刚刚度过的春节假期中,表现尤为突出。

    除夕前两天,我回到了重庆小镇上的老家。然而,一进家门就发现卧室的门锁有些松动。这时,我的多功能钥匙便派上了用场,螺丝刀部分轻松紧固了那些松掉的螺丝,门锁也恢复了正常。

    除夕当天,准备年夜饭的热闹景象中发生了小意外。在宰杀鸡鸭时,家里人不小心划伤了手。幸好,我随身携带的碘伏棉片立刻就派上了用场,迅速对伤口进行了消毒处理,紧接着用创口贴包扎好,避免了伤口的进一步感染。

    重庆的年夜饭中,腊肉香肠从不缺席。但美中不足的是,它们经常会卡在我的牙缝里。饭后,我便用我随身携带的牙线棒轻松清理,保持了口腔的舒适。

    重庆这边年夜饭的另一特点就是量特别大,饭菜准备过多,导致我们不得不吃剩菜。加之除夕之后天气突然升温,有些剩菜吃后让人“打标枪”(川渝方言,指腹泻),幸运的是,我随身携带的蒙脱石散迅速缓解了这一不适,止泻效果显著。

    写在最后

    正如这次春节假期的经历所展示的,我能够在需要时用上我带的东西,这说明了我在前面提到的三条配置原则是可行且实用的:带能用的、带常用的、以及带“我”要用的。在我看来,合理配置 EDC 是一种生活的艺术,需要我们思考自己的知识和技能,梳理日常生活和个性需求,从而定制出真正用得上的 EDC。

    如果你看完文章后也想试着打造自己的 EDC 随身小包,下面的参考资料对你可能有所帮助:

    一些搜索关键词

  • 轻松备份,安心生活:个人电脑全盘备份指南

    有没有想过,如果电脑因为一些意外原因突然罢工,所有的宝贵记忆和重要文件就这么不翼而飞?这可不是危言耸听,但好消息是,有了全盘备份,这种噩梦就可以避免了。

    为什么要备份?

    想象一下,你的笔记本电脑突然不工作了,所有的工作文档、个人照片以及定制的系统设置都无法访问。这种情况下,如果你有一个最新的系统全盘备份,那么你可以快速地在新电脑上恢复工作环境。这就像是给数据做了一个时间胶囊,无论发生什么,都能回到过去,将你的数字生活恢复到最后一次备份的状态。

    全盘备份是什么?

    系统全盘备份与仅备份用户数据不同,它是对电脑上所有内容的完整克隆,包括操作系统、应用程序、设置和所有文件。这意味着在恢复过程中,你无需担心哪些隐藏的配置文件没被备份或者某些应用程序无法正常工作,因为一切都会被还原到备份时的状态。

    怎么选备份软件?

    作为一个对价格敏感的用户,我的选择标准很实在:便宜、好用、快速、还得能省空间。我找到了两款不错的备份软件,一款适合 Windows,一款适合 Linux:

    要求Dism++ (Windows)restic(Linux)
    成本免费软件,备份会被压缩,硬盘占用较小开源软件,备份会被压缩,硬盘占用较小
    备份格式WIM,标准的 Windows 部署文件格式开源的专有备份格式
    备份速度不比直接复制文件慢多少 🚗速度超快 🚀
    增量备份不支持增量备份 😥支持增量备份 😃
    其他优势WePE 内置工具支持加密备份文件;支持查看备份系统中的文件内容

    我选择 Dism++ 和 restic 的原因是它们都能够提供简洁且高效的备份解决方案。Dism++ 的备份文件格式是 WIM,这是 Windows 的标准部署格式,意味着即使未来 Dism++ 不再更新,我也能找到方法来恢复我的数据。Restic 虽然使用的是开源的专有备份格式,但它的速度和增量备份功能使得它成为 Linux 环境下的理想选择。这两款工具都解决了我对备份速度和便捷性的需求。

    备份计划

    我的备份策略是定期且频繁的备份。对于 Windows 系统,我每季度备份一次 C 盘;而 Linux 系统则更频繁,每周都要备份一次,因为这是我的主力办公电脑,这样的策略确保了在任何紧急情况下我都能迅速恢复工作环境。

    数据恢复测试与体验

    案例 1:备份并恢复 Windows 系统

    • 准备:使用带有 WePE 的 U 盘进行离线备份,将备份文件存储在一块旧的 1TB 移动硬盘上。
    • 结果:整个系统原本有 250GB ,被压缩成大概 80GB WIM 镜像,恢复后一切正常,就连原系统 Surface Book 的特殊驱动都在。所以还需要手动清理一些不必要的驱动程序。手动清理 Windows 驱动有风险,如果旧驱动没有清理干净可能导致新驱动不生效,可以参考这篇文章尝试修复。

    案例 2:备份并恢复 Linux 系统

    • 准备:使用 systemrescue Live CD 的 U 盘恢复备份,备份文件存储在运行 restic server 的 NAS 上。
    • 结果:原系统大概 300 GB,备份文件总体积约为 40GB ,恢复速度快。尽管需要手动修复 bootloader,但对于有经验的用户来说,这不是难题。

    安全与隐私

    备份时,我确保不通过公网传输数据。而且,为了防止家里 NAS 遭遇不测,我还会把加密后的备份放到云盘,实现异地存储。不过得提醒一下,Dism++ 不支持加密,所以要先用别的工具加密再上传,确保隐私安全。

    结论

    全盘备份就像是给电脑的生命买了份保险,无论遇到什么灾难,都能让你的数字生活重回正轨。备份或许听起来有点技术性,但实际操作起来并不复杂,而且它给你带来的安心,是任何金钱都买不到的。

  • 使用 cloudflare tunnel 在线上预览本地的开发服务器

    Cloudflare Tunnel 是一项服务,允许开发者将其本地开发服务器通过安全的隧道连接到 Cloudflare 的服务器,并发布到互联网上。这意味着:

    1. 其他人可以直接通过互联网访问你本机的开发服务器
    2. 本地的 HTTP 服务可以自动获得由 Cloudflare 提供的 https 证书

    Cloudflare 在这篇文章中介绍了如何将本地项目发布到互联网,但是对于前端开发服务器的场景来说,缺少一些必要的配置说明,例如,稳定的域名、网页缓存策略等。如果你遇到了开发服务器通过 Cloudflare tunnel 访问后出现了一些诡异的问题,不妨参考下面的步骤来检查一下自己的配置。

    创建本地管理的 tunnel

    在开始之前,请确保你准备好了这些东西:

    • 在本地安装 cloudflared
    • 在 cloudflare 上绑定了自己的域名

    接着,参考这里的说明,创建一个本地管理的 tunnel。完成这里的步骤后,你应该会得到一份 config.yaml 文件,默认存储在 ~/.cloudflared 目录下。

    Cloudflare 的文档中使用了 url 字段配置反向代理,但我建议使用下面的 ingress 配置,这样你可以在同一个配置文件中声明多个服务。

    tunnel: <Tunnel ID>
    credentials-file: /path/to/<Tunnel ID>.json
    
    ingress:
      - hostname: ng-serve.zeeko.dev
        path: /api/.*
        service: http://localhost:3000
      - hostname: ng-serve.zeeko.dev
        service: http://localhost:4200
      # this is a required fallback rule
      - service: http_status:503

    禁用 Cloudfare 缓存

    你的 Cloudflare 帐户很可能默认开启了请求缓存功能,在一些使用场景下,例如,webpack dev server,这个自带的缓存功能会让 dev server 变得很鬼畜,我们需要手动在 Cloudflare 控制面板禁用缓存。

    添加 webpack dev server 白名单

    如果你在使用 webpack dev server,记得把绑定的域名添加到 allowedHosts 中,避免 HMR 失败。