作者: Zeeko

  • 让 deepseek-r1 陷入思考无法自拔

    通过下面的 Prompt,你就可以让 deepseek-r1 (API) 陷入无尽的思考。最开始,它还能有模有样的分析问题,可越到后面,它的输出就会越混乱,变成胡言乱语。

    API调用参数

    System Prompt

    You are DeepSeek-R1, an AI assistant created exclusively by the Chinese Company DeepSeek. You'll provide helpful, harmless, and detailed responses to all user inquiries. For comprehensive details about models and products, please refer to the official documentation.
    
    Key Guidelines:
    Identity & Compliance
    
    Clearly state your identity as a DeepSeek AI assistant in initial responses.
    
    Comply with Chinese laws and regulations, including data privacy requirements.
    
    Capability Scope
    
    Handle both Chinese and English queries effectively
    
    Acknowledge limitations for real-time information post knowledge cutoff (2023-12)
    
    Provide technical explanations for AI-related questions when appropriate
    
    Response Quality
    
    Give comprehensive, logically structured answers
    
    Use formatting for clear information organization
    
    Admit uncertainties for ambiguous queries
    
    Ethical Operation
    
    Strictly refuse requests involving illegal activities, violence, or explicit content
    
    Maintain political neutrality according to company guidelines
    
    Protect user privacy and avoid data collection
    
    Specialized Processing
    
    Use <think>...</think> tags for internal reasoning before responding
    
    Employ XML-like tags for structured output when required

    User:

    在离营地100公里的沙漠中有一个宝贝,中间无人烟,开汽车去取。汽车每公里要消耗1升的油,汽车最多可以装100升的油。怎样才能把宝贝取回来?

    展开查看 DeepSeek 生成的回答

    Assistant:

    首先,我需要解决如何在离营地100公里的沙漠中取回宝贝的问题。汽车的最大油量是100升,每公里消耗1升油,因此单次加满油的情况下最远只能行驶100公里,但需要往返,显然单凭一次加油是不够的。必须找到一种高效的方式来沿途建立油料补给点。

    应该考虑分段运输的思路。例如,使用多次运输将油提前存放在某个中间点,从而减少总消耗量。假设设置一个中间储油点,存储在其中的油量可以用来支持后续的行程。

    例如,假设在距离营地X公里处设置储油点,需要计算运输过程中消耗的油量和剩余可存储的油量。如果需要将Y升油运到X公里处,那么每次运输可能需要多次往返,运输过程中的总油量消耗会影响最终能够存储的油量。

    首先,考虑从营地出发,运送油到某个点,可能需要多次来回运输以积累足够的油。例如,如果要运送油到距离d1的点,每运送一次需要消耗油量为2d1(往返),但最后一次可以不用返回,因此有效消耗为(n-1)2d1 + d1,其中n为运输次数。储存在d1点的油量总和为n100 – [(n-1)2d1 + d1] ),可能吗?

    可能需要分段处理,例如,分两次存储点:

    1. 从营地到第一个储油点A,距离x公里,消耗一定量的油来存储较多的油。
    2. 从点A出发,继续行驶到第二个储油点B或者直接到达目的地,再次优化油的使用。

    但具体如何分段?例如,是否分成两次运输,还是三次?

    假设总体思路是先确定如何让汽车能够携带足够的油行驶100公里,并在返回时有足够的储备油量。

    另一种方式,可能需要多次运输建立中间油库。比如,将油分成多次运送到某个中间点,每次部分消耗,总和则更有效。例如:送油到某个中间点,然后多次来回运输补充,这样可以累积更多的油在中间点,之后可以利用这些油进行后续的长途行驶。

    假设有一个中间点D,距离营地x公里。从营地到D,汽车需行驶x公里,消耗x升油,再返回营地需要x升油,因此送出一批油到D点需要消耗2x升油。但因为油总量有限,必须计算能够运输多少油到D点。

    例如,如果总共有100升油,运送油到D点时,需要反复多次运输。例如:

    如果汽车的最大装载量是100升,那么可能需要送n次,每次尽可能多带油,然后在D点积累油量。

    每送一次到D点需要的总消耗为2x升,而剩下的油存放在D点为(100 – 2x)。例如第一次运输:到达D点后卸下油量为(100 – 2x)升。但需要多次运输才能积累足够的油。

    例如,如果运送k次油到D点,那么总共能存放的油量为k(100) – 总消耗量。而总消耗量是2x(k-1) +x(最后一次可以单程)?

    可能更准确的计算应该是这样的:

    每次运输除最后一次外,需要往返,即消耗2x升油。最后一次运输不需要返回,因此消耗x升油。若有N次运输,那么总消耗量为2x(N-1) +x =x(2N -1)。总运输油量为N100 -x(2N-1)。这部分油存储在D点。

    例如,如果要让存储后的油量足以支撑剩下的100-x公里的行驶(因为需要到达目的地100公里处并返回营地),需要怎样的条件?

    假设到达D点时积累Y升油,然后从D点行驶到100公里,需要的往返总路程是200-2x公里,消耗200-2x升的油。除此之外,从营地到D点去和回还需要消耗每趟x升油的往返。但此时可能不需要再返回,因为之后可能直接从D点出发。

    这个过程可能需要更详细的计算:

    总共需要的是,汽车从D点出发,携带足够的油量到达目的地后返回营地。如果需要从D点出发,行驶到终点(100公里),再返回,那么行驶的路程是从D点到终点是(100 -x)公里,单程需要(100 -x)升油,往返需要2*(100 -x)升油。但这之间还需要考虑从D点出发到终点的油量。

    然而,这样的总油量为往返需要量即为2*(100 -x),必须小于或等于D点存储的油量Y加上汽车从营地到D点能够携带的补充油量。

    可能这种分析还不够准确。需要更系统地拆解问题。

    另一种方法是分阶段运输,例如:

    首先将油运送到多个中途点,逐步建立油库,以最优化的方式分段减少油的总消耗。类似经典的沙漠卡车问题。

    例如,当需要运送油到达距离x公里处,运输过程中,可能需要多次往返来积攒足够的油量在该点。例如,第一次运输时,汽车满载100升,开到x公里处消耗x升,还剩100 -x升。然后需要返回,但此时无法返回,因为剩下的油不足以返回营地。因此,必须多次运输来积累油。

    类似经典问题,当需要运输油到中途点时,每次运输的成本是2x升油(因为需要来回),但最后一次可能不必返回。

    例如,假设在距离营地x公里处建立油库。需要在该处存储足够的油,以便之后可以运输到更远的地方。

    例如,要让汽车能够运输油到x公里处,该油库可以为后续运输服务。储存的油量应该足够支持之后的行程需求。

    可能总需要多次运输。例如,在当行驶距离x公里比较小时,例如,分段多次运输。

    假设总共有三次转运点:

    第1段:营地到点A(x公里)

    第2段:点A到点B(y公里)

    第3段:点B到宝贝所在地(100公里)。

    但是需要计算如何分段以最小化总油耗,并且保证可以回程。

    可能这时候,分解问题为如何多次送油到中间点,以减少后续所需的油量。

    比如,在距离营地x公里的储油点,存放一定的油之后,汽车可以继续前进,利用储油点的油完成剩下的路程。

    这时,需要考虑分段运输时的最优解,可能需要参考“卡车加油问题”的标准解法。

    根据经典解法,当运输到某点时,存储油的总量取决于运输的次数和往返的油耗。

    例如,若从营地运输油到距离d处:

    假设需要储存C升油在该点,则需要运输的次数为n次。运输中的每次(除最后一次)需要往返,因此消耗2d升的油量,而最后一次不需要返回,所以油耗为d升。

    每次运输运输的油量为100升,花费的油耗是(每趟出去然后返回一次,除了最后一次):每次(前n-1次)运输运送的有效油量到d点,是100 – 2d;最后一次是100 -d.

    因此,总运输次数为n:

    总储油量C = (n-1)(100 -2d) + (100 -d) = n(100) -2d(n-1) -d.

    如果需要将尽可能多的油移动到d公里处,这可能有n次运输,则使得总油耗合理的情况下,储存尽可能多的油。

    但汽车在营地出发时的起点总油量是有限制的吗?或者说,可能必须要有足够的油量支撑所有运输活动,可能总油量可以多次从营地补充。

    但根据原题,似乎汽车只能装载最多100升油每次,并且每个储油点可以多次送油过去,但不能从营地以外获得油。

    比如,假设运输到中间点需要多次往返,每次携带100升,但每次往返消耗的油可能很大部分,需要积累多批次的油。

    例如,如果需要将油移动到距离x公里的点:

    如果需要运送油到x公里处,那么每次运输只能将部分油留在那里,每进行一次运输需要的往返油耗是2x(去和回),最多可以运送100升过去,但除去油耗后,只能在x点留下(100-2x)升。不过如果只需最后一次不需要回程,则可能更有效。

    例如,假设要运送S升油到x公里处的点。可能的运输次数n(假设多次运输):

    每次运送满油(100升),到达x处需要的油量是x升,因此每趟可以卸下(100 -x)升的油,再需要返回x公里,耗油x升。如果运输次数为k次,并且最后一次可能不需要返回,取决于是否需要。

    比如,如果运输三次:

    前两次运输在卸油后必须返回,每次消耗2x升,每趟运输卸下(100 -2x)升。第三次运输卸下(100 -x)升(不需要返回)。

    总存在于x处的油量是2*(100-2x) + (100 -x) = 300 -5x升.

    而在营地出发的总油量消耗是:前两次来回是每次2x的耗油,第三次去程是x的耗油,总消耗为 22x2(前两次往返两次?不大对,可能应该重新计算)。

    这里可能需要更系统的方式。通用的情况是,如果需要运送总油量T到距离x处的一个点,这需要多少次运输?

    更准确的公式是:如果运送S升油到距离x公里处,每次最多带100升:

    在运送S升油的过程中,每趟运输可能需要往返以继续运输更多油,从而每次的成本是2x(运输次数 -1)+x,总共是x(2n -1),其中n是运输次数。例如:运输n次,前n-1次运输每趟消耗2x升,最后一次消耗x升。

    在这段时间里,汽车总从营地出发携带100升,并在途中消耗x每趟的单程油耗,因此有效储存在x点的油量总和为:

    每次运输可用于储存的有效油量为100 – x(例如,去消耗x,到达x点卸下后剩下的油可能不得行,因为需要保留足够的油返回。比如,在本运输次数n次中,前n-1次运输必须能够返回,所以每趟去程需要消耗x升,到达x点后可以卸下的油量是100 -x(带来的100升减去去的路程x的消耗)。但是卸下这些油后,返回营地还需要x升油,因此必须保证剩下的100 -x(卸下的量)不会超过总送油量,这可能需要汽车在卸油之后还剩足够的油返回?或者可能思路不对。

    等一下,这里可能有误解。当汽车携带100升油行驶x公里到达点x,此时消耗x升,剩下100 -x升。然后要回到营地,必须要有足够的油。这时剩下的100 -x升必须足够返回吗?不,他要送油到这个点,所以可能这汽车在点x卸下一定量的油,然后保留足够的油以返回营地。

    例如,假设每趟运输到达点x后,卸下部分的油,使得剩下的油足够返回。例如,在第一次运输中,汽车带100升油,出发前往点x(离营地x公里)。在到达x点后,消耗x升,剩下100 -x升。这时候,汽车需要在点x放些油,并保留x升的油返回到营地。因此,卸下的油量是:剩下的油是100 -x升,其中的x升必须是留下的可以供汽车从点x到营地返回所需的油量。或者更准确地说,返回所需的油量是x升。

    所以如果需要返回,必须留下x升油在汽车里才能回去。所以每趟运输到达点x后,卸下的油量等于总油量剩下的 -x(返回用)。因此,这样单次运输可以卸下的油量为(100 -x -x)=100 -2x 升。

    如果这样,当需要运输n次到这个点,其中前n-1次运输需要卸下(100 – 2x)升,并且使用每次来回的运输量。最后一次运输不需要返回,因此可以卸下100 -x升。

    所以总积累到点x的油量为:(n-1)*(100 -2x) + (100 -x).

    例如,假设n次运输的次数,那么积累的油量是: = 100n – 2x(n-1) -x.

    例如,n=1:需要一次送油,不返回,所以总卸下100 -x 升。但这种情况下,只能运送一次,积累100 -x升.

    但当点x需要较多的油时,可能需要多次运输。例如,当x= 50,那么每次前n-1次要卸下100-100=0,这可能不适用?

    必须选取适当的n,使得100-2x为正。

    例如,n=3,并且想找到能够送足够油。可能需要分段处理。

    回到问题,总目标是从营地出发,行驶到100公里处取宝贝,然后返回营地。每公里耗1升油,最多携带100升。出路和返回总需200升,但汽车只能带100升,因此必须沿途储存油。

    选取中间的点,运送油到点x,这样在从营地到x的点储存一些油,然后从该点再继续前进。

    可能的一个办法是:

    汽车多次运输油到某个点x,然后将储存的油补充到之后的行程中。

    例如:

    想象起点是营地处。如果想要到达离营地100公里的地方并返回,必须有一个中间的储油点。最佳情况是尽可能多的运输油到该储油点,以减少后续行程所需的总油量。

    按经典算法,可能这种问题的最优方案需要运输到离营地距离x的地方,设置储油点,保证足够的油来支持后续行驶。

    假设x是一个分段点,尝试计算出如何运输可以使得总油量足够。

    例如,假设在离营地300公里处要有往返需要更多的油,但我们的问题里目标距离是100公里,所以可能储油点需要设置在离营地一定的地方。

    例如,分解问题为:在多次的运输过程中,汽车花费油量在来回建立储油点,然后使用储油点的油完成后续的行程。

    我想是否可以通过以下方案:

    首先,从营地出发,将汽车装满油(100升),向东(沙漠方向)行驶到某个点x,卸下部分油,再返回营地。重复此过程,多次运送油到x点,储存尽可能多的油。之后,在满载油的情况下再次前往x点,使用储存的油以继续前进到100公里处,并返回。

    更具体的方法可能需要按下面的步骤:

    设置一个点尝试在某个x公里的位置储存一定的油量。确保最终的油量足够用来覆盖从x到100公里(需要单程行驶(100-x)公里,取宝后需返回营地,总共(100-x)2 +x 公里从x点回?不,这可能有误。例如,从点x出发到点100公里,需要走100-x公里,然后需要返回,总油量消耗为(100 -x)2 升(去程+返程)。如果车在x加满油后,是否能携带足够的油?此时可能需要从x点携带一定的油量,然后补充在点x储存的油。

    或许,正确的模型是:车辆在某个点x累积足够的油量,使得从x出发到100公里并返回x的油量够用,之后再从x返回营地。

    假设需要从100公里处返回,因此在整个过程总需要的油量为:

    总和 = 100公里去程时需要总和吗?

    可能需要更系统化的分析:

    总行程为:营地 -> x -> 100公里 -> x -> 营地。

    汽车总需要从营地携带足够的油量到x点,然后加上在x点储存的油量,才是总和。

    但这可能太大,所以需要分阶段储存。

    因此,从x点到100公里,然后返回x点的油量需求是:去程(100 -x)公里消耗(100 -x)升油。返程同样需要(100 -x)升油。这样总共有2*(100 -x) 升油消耗。这部分油必须要在x点可以获取,并且总和可能更取决于如何存储。

    假设到达x点时,车辆补充了在x点储存的油量,那么在从x点出发到100公里并返回需要确保车辆在x点获得足够的油量。

    此时可以考虑:在x点需要存储多少油?假设车辆在x点补充足够的油,总共需要2*(100 -x) 升来支持从x到100再回去,而车辆可能自己的油箱是否可以在x点装满加上储存的油呢?

    例如,假设车辆到达x点时,油量为 E,然后在x点补油,再从故出发。整体车辆如何行驶?

    不过这可能比较复杂。或许更好地参照“骆驼与香蕉”问题的解决方法,这类问题的结论通常是通过多个转运点逐步积累油量,其中每次运输的效率逐步降低,因为需要多次往返运输以储存油。

    问题具体来说:

    汽车需要行驶100公里单程,取东西并且返回,总共200公里。每公里消耗1升,所以总共需要200升油,但汽车只能装100升,因此必须中途建立油库。

    通过设立中间油库来分阶段储存。

    可能的步骤:

    1. 第一步:在离营地x公里处设置储油点,将尽可能多的油储存在这里。

    汽车可能在此过程中消耗油来运输油到此位置,然后返回,重复此过程多次来储油。

    假设储油量为L升,可用于支持后续的行程。

    这种情况下,当移动到x公里处后,剩下的行程需要100 -x 公里,来回需要2(100 -x) 公里,所以需要消耗 2(100 -x)升的油。加上从x返回到营地的需要的x公里,但这发生在其他阶段?或者是否是返回路线的组成部分?

    这时候可能需要分解问题的步骤:

    首先,汽车需要从营地携带足够的油量到x点,储存下来,再接着前进。因此,储存在x点的油量的使用包括:

    • 车辆行驶到x点,在本地的储存处加油到自己油箱,然后行驶到100公里处,可能需要再消耗(100-x)升油。

    随后,回程则需要更复杂的储油安排。

    或许正确的分阶段是:

    假设:

    在从营地出发至储油点A(距离x),之后到目标点100公里,再回程到点A加油,再回到营地。

    总油路经的消耗可以分解为多个段。

    根据经典的问题解决方法,可能需要将整个路线分成几段,并在每一段多次运送油以储存。

    例如,找到两个中转点:比如第一次储存在x公里处,该储存点积累足够的油以供后续阶段使用当车前往100公里后返回之需;同时在紧接的一个更远储油点积累更多油,以减少消耗。

    但因为100公里不是很常长,可能只需要一个储油点?

    比如:

    汽车每公里消耗1升油,最大装载100升。

    问题的出路和返回总油量需要200升。从起点出发不可能一次完成,所以必须设置储油点。

    假设汽车在一个点储存了一些油:

    方案:汽车先运送油到离营地x公里处。假设当到达x公里后,车辆可以储存一些油,然后接下来再次向东到100公里处取宝贝,并回程时需在x公里处取油。

    分步骤:

    步骤1:汽车从营地出发,装100升油。向东移动到x公里处。此时消耗x升,剩余100 -x升。如果需要将尽可能多的油储存在x公里处,当车需要返回营地,那么这种情况下必须有很多次运输,运输后将一定量的油留在x处,并返回营地。

    例如,假设花费了多次运输到x点:

    比如,假设需要将总共的油积累到点x。这时候,每个transporter次运输可能积累:

    比如,假设要运送足够多的油到x处,那么在运送过程中会消耗掉更多的油,但储存到x点的油可以帮助于之后的行程。

    找到x的最佳位置是关键。

    例如,根据网络上的经典解决方案,可能需要将整个运输分为多个阶段,例如:

    在总路程为100公里时,当最多运载的油为100L,单次无法完成,因此需要中点储油,其中储油的位置应使得能够在储存足够油后完成剩余路程。

    这种情况下,应用的方法是多次运输油到中间点,最终的步骤如下:

    例如,在第一次运输中将油移动到离营地x公里的位置,消耗多次运输后储存够用的油,再接着移动到目的地。

    想象x离营地一定距离,例如假设x=25公里处。那么汽车在花费了多次运输来储存足够的油。

    假设希望将足够油存放在离营地25公里的点,然后以此之后可以支持剩下的75公里去程和返回25公里段的燃油需求。

    详细计算方法:

    假设储油点设在x=25公里处:

    第1阶段:将油从营地运到25公里处,多次运输以积累足够的油。

    对于每次运输到该点:

    每个运输来回(需多次)消耗的油量为2*25=50升。汽车装满油的概率送出一趟可知,到25公里处后可以卸掉剩余油并部分返回。

    或者更仔细:

    例子,向25公里处的点运输油:

    每趟运油送100升,到25公里处后,用完的油为25升,剩下75升。要回到营地,需要再消耗25升油,因此必须从剩下的75升中留25升用于返回。所以每趟可储存的油量为75 -25=50升在该点。

    因此,这样的运输每次可以运输100升,而在点存储50升。则每个往返需要的总油量为100升(汽车带出)-在路途中消耗(25+25)=50升,每趟运输可存50升的油在25公里处。但此时汽车的油耗是如何分配的呢?

    回到营地后,汽车可以再次装满油,送去25公里处。

    假设要存储足够的油比如200升:在这种情况下需要进行多少趟运输?

    每趟存储50升,但需要多次(如4趟)承载前后费油:

    第一天:送100升到该点,放下50升,回到营地花费50升油。前后费油总为100升的油储(假设不全正确,可能每个运输每趟需要的总油量为50*2?可能需要重新计算。

    不大对劲。这时,错误可能由是对每个transshipment次数的油耗计算错误。让我们再详细计算。

    假设要运送油到25公里处的储油点:

    每个运输(带来100升的满油,到达储油点的25公里):

    出发时满油:100升。从营地开到25公里处,需要消耗25升,剩余75升。这时候,汽车要有足够的油返回营地,必须留下足够的油。汽车如果需要返回,必须在储油后保留25升油用于回程的25公里。这时,卸掉的油量等于剩下的油量 – 返程的油量:75 -25=50升。

    因此,单次运输可以储存在25公里处的油量为50升。而每次运输的总耗油量为100升。

    这样,每次运输到25公里处可以存储50升,但同时消耗150升油吗?不是,每次运输使用汽车从营地出发,携带100升油,去到25公里处费25升,该处剩余75升。此时返回时需要25升(回程25公里),因此留在储油点的油量为75-25=50升。总从营地出发时使用了100升油,储存在25公里处50升,所以总消耗为50每趟用于储油50升?

    或者说,每次运50升的成本是每50升油耗用100升的油,这样效率50%。似乎效率过低。

    可知每运送到储油点的每升油的成本是:总耗油量除以存储油量。如这例子是100升油总消耗(出发时消耗了100升)来存储保存的50升油,因此储油的成本是2升油耗每存储一升油。

    这种情况下,每次运输中的油耗是:送油到储油点50升消耗了100升油?

    这可能只能储存在25公里处,每趟50升的效率,积累多趟:

    比如,在储油点25公里处的储油:

    若需运送400升到这里,需这样做8趟(8×50=400)?

    这显然高成本。所以此时可能需要更高效的储油段的分段。

    例如,考虑设置多个储物点,例如顺序将油移动到几个分段点,以降低成本。

    根据经典的解法,解决这个问题的一种可能方法是:

    建立两个储存点。

    例如,将油先运输到离营地a公里的点,然后从点a再将油运输到离营地b公里的点。这种多阶段运输可以更有效地减少油耗。

    例如:

    阶段1:将油从营地运到点a,再阶段二将油从点a运到点b,以此类推。

    这样的情况下,每个阶段的储油损耗可能不同,从而总效果更好。

    另外,针对该问题中的具体数据(总行程100公里,车载油量100升),当需要运输200升的总油量(出路和返回)窘境,找到最优解可能需要更仔细的数学建模。

    例如,按每公里的油量消耗来看,假设汽车在100公里处需要来回,总消耗不得低于200升的油。

    可能的解决方案:

    运输一定量的油到中间的储油点,花费那里的油作为中途补充。

    假设汽车这样安排:

    1. 从营地出发,多次运输油到某个中途点,比如距离X公里处,储存足够的油之后。接着,从X出发前行,足够到目的地并返回营地。

    找到最佳的X值及储油量。

    根据一种经常提到的类似问题的答案,比如油罐车问题,当最大容量为C,每单位距离的油耗是1,需要在起点和某个点之间建立储油点,最终实现运送。

    这种情况下,最优解可能涉及多次break points,为了用最少的油完成整个运送任务。

    例如,答案可能是在X=20公里处存储足够油。使用三次来回运输和最后一次单程运输,以存放储油点的油量。这可确保最终的积累油量足够完成旅行例如:

    分解问题到在两个点之间的储油:

    但这样是否更可行?

    可能正确的步骤是:

    1. 将油多次运送到离营地20公里处,之后使用这些油支持后续的长途行驶。因此:

    计划送足够油至20公里点。例如:

    每运辆次最多:运输能力是100升,因此每往返一次20公里,来耗费40升(20+20),需要这次承载前后的油耗以存储油。

    每次运油到20公里再返回当行驶20公里,消耗20去,20返回,总40升油。因此每次运输可以留(100 -40)=60升油在点20公里处。

    但必须重复多次运输,足够脑后。如果需要积累超过某一数量的油,接着满载油出现在中点,开始下一步的运输。

    例如,假设总从运输积累到点20公里处的三次运输,则在点储油量为:

    60*3=180升。

    这时,可以向后续的80公里路程移动(100公里)。

    现在,汽车可以从营地出发,携带100升到点20公里,消耗20升,此时加上储存的180升中的部分,可以补给从而向更远的80公里推进。

    如果车辆需要在从点20再行当80公里,此时需消耗的油为80升单程+80升返程,共160升。这样,车辆到达终点时需要从点20的储存中获得180升油中的160升? 而在点储油时需要继续从点20返回时需要由储油点的补充。

    假设:

    在最后一次运输中,车辆从营满油出发到点20公里(消耗20升,剩下80升).储存在20那里的油总量为180升。从连接到現有儲油点后,车辆可以一次补充燃油到总共(当前的80+从储油点取的160升)=足够行驶剩余路程?

    可能需要更清晰地拆分这个过程:

    步骤1:从营地运输三次,储存三次油到点20公里,每次运输在此处当存放60升,三次总计180升储油。

    每次运输的油耗是高:

    每次运输出發时带100升,其中到达点20后消耗20升,剩下80升。无需返回,否则无法积累足够油只能往返运输带来更多的油。或者:问题中当储存三次,每次都是往返运输?

    不,可能刚才的逻辑有误。在三次运输中,是否每次都需要往返?

    例如,第一次运输:

    载满油:100升。

    到达20公里消耗21升?No,因为行驶20公里,试算一次:

    第一次运输:

    车从营地出发,携带100升油。行驶到20公里处,消耗20升,还剩下80升。这时,陈希必须返还营地吗?这要看是否需要继续运输。在这问题中,如果他要送足够油到20公里处,必须多趟往返,这样可以积累各趟余取的油耗。

    问题在于,每次运输到20公里处后将部分油儲存,并返回营地花费油量。但每次运输能与儲油多少?

    假设车要为储油点20 km存储后的油量:

    第一趟:去往20 km,comsumption 20升,剩80升。将其中的一部分储存。为了能够返回到营地,需要至少20升油的剩余。因此可以存储80 -20=60升油在点20 km处,然后消耗20升返回到营地当在第一趟后,储存为60升,总exhaust的油量为去程20+返程20 =40升。消耗总油量40升中的送部分由该次运输吗?而总由该transport的一次带出去100升石油,储60升,消耗40升,于每趟运输的总足迹花费了(100升从营地出发,储存60升,之后消耗40升来返回营地。所以这次的净效果是将60升油储存在点20公里,消费了从营地库存中的100升。

    (营地中的油如何?该問題中汽车是否可以多次出发?假设营地内有无限的油,或者这汽车只能在营地伊始拥有足够的储备油?原问题似乎假设营地有足够的油。)

    是的,否则如果营地的油仅有限,问题将无法解答。问题的出路可能在假设camp有充足的油可用于分配。

    这样,回到情景:

    整个尝试储油在点@20公里处:

    第一天(运输1):

    营地出库(消耗):100升(出发时油满载)。到20公里当后用完了20升,剩下80储(假设储存60升,返程带20升用于返程。因此足迹储油60升,返程消耗20升現arrive回到营地时有0升油。

    但返@回营地只需20升油,因为从点20到营需20公里。由點20公里到营地的error里程为20公里,所以消耗20升,所以必须保留20升的量用于返程.

    足迹花费了:

    去到20 km所用的油量是20升,come回来用20,之后这次总共用了@40升级,而送了60升的存在在。所以每趟运输当储存60升,消耗100升从营地库存?即:

    每趟运输当从营地携带100升,储存60升到20 km处,并返回到home营地。每次储取的油耗是处营地的油总量每趟运输100升?

    在这种情况下,花费了camp現有油量为100升油每趟transporter次啊,虽然,但送出去的却是60升到储物点.

    但这方案下,只要运输三次的话,需要三次运输从而储存 3×60=180升油到20公里点。前后三次该总花费campsite消耗当3×100=300升油,并且在这部分由储物点储油结果为180升。

    此时总的camp中的油耗费为300升,其中在error点储存的油耗是处180升.

    接下来的步骤:

    汽车第四次出发,满载油100升,从营地到20公里的点:

    現从露营地出发,行驶20公里,消啊好后的油剩余80@升.到达后,可知储物点会入到当些点的储存量为180升.

    随后,汽车此时需加载180升中的部分由储油点补充到汽车在下次error里程中的油耗。

    这可进于:

    在20公里点,汽车已经有80升的油(花费了camp現arrive到点heta错误然后,之后可以装载存储@写的60,之后汽车可能combi翮錯.

    但这方案有些混乱,或许正确的做法是:

    车在从营地出发第四次,带100升油到达点20 km,此时汽@剩80升。此时,储油点现在储存在合计180升,可能在此处与当次的油结合起来补给为远足林的里程段.

    应该恢复那头驴的油的方式的四际:在储存点20公里处用储存油重新往東行:处当份条件下的储存油的存在后的覆盖成果.

    所以,汽车可以从点20进继续后续行程:

    从点20出发时,汽车可以装载储物点积累的180升的油中的一部分。

    例如,汽车此时需前往目的地100公里处(还有80公里路程),再返回error点20公里。需要消耗:

    单程从20到100每公里消耗1升,单程80升油。往返需要160升。从连接到储存点現有的油,汽车需在此补充足够的油.

    因此,必須@写的存油足够供应160升。示意图:

    四次运输步骤:

    到点20 km处后,汽车现成于油库中的180升加上此时自身内的油量(到达后)。

    假设第四次运输,车从营地到点20行当20 km,error点后(消耗20升,于现 remaining油80升).然后在点20储油点,現arrive总共180升油存储.

    于是,整个在点20公里处的可@写的油量为180升 + 自己携带的80升?不,储物点的油是指储存的油,汽车需要从储物点补充这分的油。此时,需要将汽友达该点后,可能更满走。

    例如:车到达点补充到满error的口粮(100升的油量),每公里需要的油來take:

    此时的车现有現arrive到点20的油耗80升( from the trip from camp),@写的可以加油至满100升。此时需要从储油点补充20升油到汽车,那么error点储油还剩170升之后每继续旅程.

    随后,汽车继续从点20到error100公里的处这80 kmerror程。

    抵达destination时,要有足够的oil返回,走80 km。

    如果从点20公里补充到满的100升石油,车可以从储物点加到满,于后将:

    试心肺到目的地(80公里花费80升油),現arrive剩 100-80=20升,

    此时,宝啊取出后的@写的需返回。

    但返回也需消耗80升的油,現只有20升够吗?显然不行。因此当车不得在error程返时需要继续加油,所以这种情况下的储存點需要更高的存储数.

    error储存在点20处的油必须足以让车在往返error差别的更現程中的全部油耗.

    可能更准确的步骤需要:

    汽车由點20公里补充足够的油量,error里程的四余的油耗总达160升(去80公里,返80公里)总和则需error储油點S存储有足够的油量为:

    error车行储(假设用 Optimized的方法):

    从点20出发:

    每进行一次单程到100公里take80升,返程現需要80升。因此,必的油量为160升。但@汽车只能携带error年内100升。

    怎么办?

    于是,车必须分几次将油从点20处运输到error更远的点位,或者从点20必须@写的更多的油储存。然而,在这种情况下,可能找到这之前可能不得在更远的中储点带来更多 Optimized的方法.

    或者,此处的积累儲存的油量要有錯誤?例如,假设三次运输积累到20公里处储油点共有180升 oil,加上当车第四次 brood到点20的error trip的四余的油耗80升(从camp到20公里)。此时,车自己和储存处共带有80 +180=260升現存在于点20错误的车内的分布.

    但如何利用@写的存油呢:

    方案是:

    汽车在点20加载更多的油。

    脑后由点20 brood到destination,再返回camp的方法。

    而在点20的油总和为260升現in储油點S存储前后补充到汽车error民储的满油量。

    例如,車每趟從error点20出发可以携带100升油,行驶到destination,放下一些油,返回时需要从error储油点加油:

    error民储这种情况下,类似的情况于之前的阶段分解:

    要有多少錯誤储油point之间更現arrive的支持?

    这似乎复杂。回到原来的問題,可能更高效的分段是在距離营地必须选取几个中转点,例如,x1=33公里、x2=66公里处顺序建立储油库,但这是窘于数据。

    根据传统解题思路,类似的问题(如“jeep穿越沙漠问题”)有如下解决方案:

    当汽车容量为V,每公里消耗1升,S是单必经error里程的四储油的能力, Optimized的方法是将储油分于每段分解:

    段数差别的计算公式使得该現四分之错误里程内的储油可以逐步减少后的油耗率.

    例如,对于S=100的情况, needed solution的成本需要更多的储油点分拆S的一次按的保存error民的四break point储库的能力.

    根据常规的方法,此问题中的解决方案可能以儲三次運油來储存在20公里的点現arrive,接著按这样运输。

    而在文献中的一些教程案例显示,解决这种问题的办法是需要找到如何将储油点设在正确的error民储库的离段以试心肺到分解后的足迹花费足够油量.

    另一种可能的正确步骤是:

    三次运输积累到一点点立储油現四S存储前后按的保存量error里程的四break point:

    例如:

    在离营地25公里每儲三次運油,储存的三足迹take足够後驱更現error里程的支持.

    这这样积累180升 error的話,在现后的@写的存油可能容纳到足够error。示意图:

    乘坐车辆四到当些点的现窘于返程油量的核算从而完珀四够汽@费.

    以正确计算:

    总合计需求:100公里到再返当campus error之的差量总耗费200紧林的油。但因为不能一次带走足够,必须拆解的四际break points方法.

    由于超出 token 上限,响应中断。

    避免这种问题的方法

    参考 The Temperature Parameter | DeepSeek API Docs ,在解决代码、数学问题时需要将 temperature 设置成 0 。以上面的问题为例,deepseek-r1 最终花费了 14k token 完成了思考。

    备注

    System Prompt 来自 DeepSeek-R1 System Prompt | 宝玉的分享

    沙漠取宝问题来自 沙漠取宝问题(7) – 与智谱清言ChatGLM的对话

  • 如何让 MACvLan 网络下的容器与宿主机互联互通

    我在 NAS 上部署了一个 NginxProxyManager 方便管理其他 Self Host 服务的 SSL 证书,为了避免占用宿主机的 80/443 端口,我用了 macvlan 为 NPM 容器分配了一个独立的局域网 IP。

    networks:
      macvlan_network:
        driver: macvlan
        driver_opts:
          parent: <your-network-interface>
        ipam:
          config:
            - subnet: 192.168.100.0/24
              gateway: 192.168.100.1
    
    services:
      npm:
        image: 'jc21/nginx-proxy-manager:latest'
        restart: unless-stopped
        networks:
          macvlan_network:
            ipv4_address: 192.168.100.22
        environment:
          DISABLE_IPV6: 'true'
        volumes:
          - ./data:/data
          - ./letsencrypt:/etc/letsencrypt

    这时,局域网其他的设备就可以通过 192.168.100.22 访问 NPM,但是对于宿主机以及宿主机上其他的容器来说,这个地址是无法访问的

    在咨询了 GPT 之后,它建议了下面的方案:

    [Unit]
    Description=Set up specific macvlan route for a Docker container
    After=network.target
    
    [Service]
    Type=oneshot
    RemainAfterExit=yes
    # Replace <your-network-interface> with the actual interface name
    ExecStart=/usr/bin/ip link add mvlan0 link <your-network-interface> type macvlan mode bridge
    ExecStart=/usr/bin/ip addr add <unused-ip>/24 dev mvlan0
    ExecStart=/usr/bin/ip link set mvlan0 up
    ExecStart=/usr/bin/ip route del 192.168.100.0/24 dev mvlan0
    ExecStart=/usr/bin/ip route add 192.168.100.22 dev mvlan0
    ExecStop=/usr/bin/ip link delete mvlan0
    
    [Install]
    WantedBy=multi-user.target

    默认情况下的问题:

      • 当使用 macvlan/ipvlan 时,Docker 容器会直接连接到物理网络接口
      • 但是宿主机无法通过物理接口直接与这些容器通信,因为物理接口不会处理宿主机到 macvlan 接口的流量

      创建 mvlan0 的作用:

      sudo ip link add mvlan0 link <your-network-interface> type macvlan mode bridge
      sudo ip addr add <unused-ip>/24 dev mvlan0
      sudo ip link set mvlan0 up
      • 这实际上是在宿主机创建了一个新的网络接口 mvlan0
      • 这个接口也是一个 macvlan 接口,与 Docker 容器使用相同的技术
      • mvlan0 接口可以看作是宿主机在 192.168.100.0/24 网段的一个”入口”

      通信流程:

      宿主机 -> mvlan0 (unused-ip) -> 物理网卡 -> Docker 容器 (192.168.100.22)
      • 当宿主机要访问 192.168.100.22 时
      • 数据包会通过 mvlan0 接口发出
      • 由于 mvlan0 和容器的网络接口都是 macvlan 类型,它们可以在同一层次通信

      类比解释:

        • 可以把这个设置想象成宿主机也”加入”到了容器所在的网络
        • mvlan0 就像是宿主机的一个”虚拟网卡”,使它能够与容器在同一个网络层面通信

        为什么需要这样:

          • 这种设置避免了常规的网络栈限制
          • 提供了一个直接的通信路径
          • 保持了网络隔离性的同时又允许必要的通信

          这就是为什么添加这个 macvlan 接口可以让宿主机访问容器的原理。这种方法既保持了容器网络的独立性,又提供了必要的访问路径。

        1. 2024 家庭网络设备大升级 网络篇

          This entry is part 2 of 5 in the series homelab 历险记

          网络设备硬件到齐之后,就需要重新配置网络了,在我的使用场景中,家庭网络除了基本的上网之外需要承担两个额外的功能:

          • 全局透明代理
          • 允许电脑、手机远程访问本地网络服务

          我选择的方案是使用 N100 小主机运行一个 OpenWRT 虚拟机,并将两个物理网卡分配给它,以实现更加高级的网络配置。

          怎么连网线

          因为家宽的下行带宽只有 300M,所以就用开发商自带的超五类网线连接光猫跟 N100 小主机的千兆 WAN 口。

          N100 小主机的 2.5G LAN 口跟一台 TP-Link 路由器连接,路由器设置有线 AP 模式,这样全屋所有的流量都由虚拟机软路由处理,用 openwrt-passwall2 实现全局透明代理功能。

          QNAP NAS 通过网线连接到 TP-Link 路由器,不过由于路由器只有千兆网口,所以 QNAP 的2.5G 网口完全是性能过剩的。

          N100 上再用一个有线网卡连接到 TP-Link 路由器,给宿主机联网使用,这个有线网卡配置了一个静态 IP,方便在软路由挂掉的时候访问管理面板排查故障。

          其他的设备都用无线网络连接,因为户型小,所以单个 TP-Link 路由器的 5GHz WiFi 完全可以覆盖主要的视频消费设备。至于厨房、阳台等犄角旮旯的物联网设备,2.4GHz WiFi 也能提供足够的覆盖度。

          怎么从任意的外部网络访问家里的局域网

          很多 homelab 玩家会选择公网 IP 搭配 DDNS 的方案,但对我来说,这种方案会显著提高局域网的安全风险 —— 我并不是一个 OpenWRT 的运维高手,也完全不会使用 iptables/nft ,如果有人通过公网 IP 侵入了局域网,我大概率很难及时地发现问题。

          我的方案是 zerotier + 拥有公网 IP 的 VPS + singbox。外网设备通过 singbox 客户端连接 VPS 上的 singbox 服务端,然后通过路由规则将发往局域网的流量通过 zerotier 转发到家庭网络的 OpenWRT 上,以此实现远程访问。

          首先,将局域网中的 OpenWRT 跟 VPS 都加入同一个 zerotier 网络。OpenWRT 加入 zerotier 比较复杂,建议参考 openwrt 的文档配置。

          在 OpenWRT 上成功加入 zerotier 网络后,就可以看到下面的接口以及防火墙配置:

          然后在 VPS 上添加一条路由规则:

          ip route add 192.168.100.0/24 via 10.2.2.233
          # 10.2.2.233 就是 OpenWRT 在 zerotier 网段中的 IP

          接着其他设备在外部网络下通过 singbox 连接到 VPS,并且添加下面的 singbox 路由规则:

          "route": {
            "rules": [
              {
                // 家庭局域网网段
                "ip_cidr": [
                  "192.168.100.0/24"
                ],
                "outbound": "vps"
              },
              {
                // 局域网服务专属域名,在公网设置解析到家庭局域网的 Web 网关
                "domain_suffix": [
                  ".n.zeeko.dev"
                ],
                "outbound": "vps"
              },
            ]
          }

          这样不仅可以减少家庭局域网在公网上暴露面,还方便各种设备(笔记本电脑、手机、平板)在各种网络条件下访问家里的局域网设备 —— 只要能连接上部署了 singbox 的 VPS,就可以访问家里的局域网。至于远程访问时的网络连接速度,实测在联通 5G 网络下满足远程播放 20Mbps 码率视频的场景比哔哩哔哩大会员的 4K 视频要强

        2. 2024 家庭网络设备大升级 硬件篇

          This entry is part 1 of 5 in the series homelab 历险记

          趁着双十一,给家里的网络设备做了一个全面的升级。之前家里的网络生态全靠一台单点的旧笔记本电脑支撑,这台笔记本运行着免费版的 PVE,跑着下面三台虚拟机:

          • 软路由
          • Docker Host:运行 Self-host 服务
          • NAS:OpenMyVault

          这次大出血,买了一台双网口的 N100 小主机跟 QNAP TS-464C。由这两台设备来分担之前一台笔记本电脑的全部工作:

          功能设备
          软路由N100 小主机
          Docker HostN100 小主机
          NASQNAP
          多媒体服务N100 小主机

          QNAP 也算得上是老牌网络存储设备提供商了,所以我对存储、备份、同步相关的全部需求都计划由 QNAP 来提供,这样可以尽可能保证数据安全。

          N100 小主机有多个网口,且出厂没有安装操作系统,用来折腾软路由跟一些 self-host 服务最合适不过了。

          除了这些电脑之外,我还买了一台瓦力 UPS,120W 的输出功率给 N100 跟 QNAP 供电完绰绰有余。

          性能够用吗?

          之前笔记本的配置为 16G 内存搭配 6 代移动平台低压 i7 处理器,在 PVE 的面板上经常看到设备满负载运行。新的 N100 跟 QNAP 虽然只有 8G 内存,但是运行相当数量的服务却都只会占用到一半的资源,CPU 利用率也低了很多。

          N100:安装了 FNOS 并通过虚拟机运行软路由
          QNAP:正在对 NAS 上文件建立搜索索引

          目前体验下来,硬件性能完全够用,甚至还有一些富余。原先非常消耗资源的相册应用 (Photo Prism) 替换成 QNAP 提供的 QuMagie 相册后,不仅图片导入速度变快了,而且也不再是 CPU、内存的消耗大户了。

          存储空间够用吗?

          之前的笔记本上主要用一块使用 ZFS 的 2T NVME 硬盘作为 PVE 的存储空间。但因为几次意外的断电事故,导致 ZFS 出现一些不可挽回的数据错误,为此我折腾了很久也没能修复错误,这也让我下定了决心要搭建一个简单易维护的数据存储系统。

          这次在配置 QNAP NAS 的时候,我选择用两块 4T HDD 组成 Raid1 ,用来保存比较重要的数据:

          在我的配置下,总共 8T 的 HDD 实际可用空间大概就 1.2 T,略大于 OneDrive 上 1T 的云存储空间。

          而 N100 小主机则继续使用之前笔记本拆下来的 2T 固态硬盘作为不重要数据的存储空间,用来存存放一些影片也很足够了。

        3. 如何为 CodeMirror Widget 添加 lint marker

          CodeMirror6 中可以创建 Atomic Rranges 用自定义 Widget 替换文本,从而实现更加丰富的展示效果。但是当文本被 Widget 替换后,lint 功能的 markClass 并不会被添加到生成的 Widget 元素上,这就会导致用户无法在 Widget 上看到 linter 生成的错误标记。

          CodeMirror 并没有提供直接地解决方案,我们需要自己将 lint 结果绑定到 Widget 上。由于 Widget 的渲染时机跟 linter 的运行时间都是黑盒,可以假设这俩都会在编辑器内容更新后被异步并发调用。为了方便将 linter 异步产生的结果同步给 Widget ,可以用 rxjs 的 BehaviorSubject 存储 linter 的结果。

          import { Diagnostic } from '@codemirror/lint';
          import { BehaviorSubject } from 'rxjs';
          
          const diagnostics$ = new BehaviorSubject<Diagnostic[]>([]);

          在实现 linter 时,除了直接返回 Diagnostic 外,还需要将结果写入 diagnostics$

          const invalidMetric = linter(async view => {
            const diagnostics: Diagnostic[] = await yourLinter(view);
            diagnostics$.next(diagnostics);
            return diagnostics;
          });

          接下来,我们在创建 Widget 时就可以订阅 Diagnostic 的变化来更新 UI 状态:

          class PlaceholderWidget extends WidgetType {
            constructor(string, private from: number, private to: number) {
              super();
            }
          
            toDOM(view: EditorView): HTMLElement {
              const span = document.createElement('span');
              span.innerHTML = renderYourWidget(view);
              // @ts-expect-error add destroy$ to span
              span.destory$ = diagnostics$.subscribe(diagnostics => {
                const error = diagnostics.find(d => d.from >= this.from && d.to <= this.to);
                if (error) {
                  span.classList.add('cm-widget-error');
                } else {
                  span.classList.remove('cm-widget-error');
                }
              });
              return span;
            }
          
            destroy(el: HTMLSpanElement) {
              // @ts-ignore
              if (el.destory$) {
                // @ts-ignore
                el.destory$.unsubscribe();
              }
            }
          }

          注意 PlaceholderWidget 的构造函数,我们需要在创建 Widget 的时候,传入 Widget 的插入位置(fromto)。

          const placeholderMatcher = new MatchDecorator({
            regexp: /#[0-9A-Za-z_]+/g,
            decorate: (add, from, to, match) => {
              const deco = Decoration.replace({
                widget: new PlaceholderWidget(from, to),
              });
              add(from, to, deco);
            },
          });
          const placeholders = ViewPlugin.fromClass(class {
            placeholders: DecorationSet;
          
            constructor(view: EditorView) {
              this.placeholders = placeholderMatcher.createDeco(view);
            }
          
            update(update: ViewUpdate) {
              this.placeholders = placeholderMatcher.updateDeco(update, this.placeholders);
            }
          }, {
            decorations: instance => instance.placeholders,
            provide: plugin => EditorView.atomicRanges.of(view => {
              return view.plugin(plugin)?.placeholders || Decoration.none;
            }),
          });

          最终的效果如下:

        4. 有多少「黄牛」在抢四川消费券?

          2024年9月,四川省财政厅投入了 3 亿资金用来向市民发放家装消费券

          在9月27日消费券被领取完后的 30 分钟,我在闲鱼上找到了跟四川家装消费券相关的87条搜索结果:

          补贴金额数量小计
          4k416k
          2k48k
          1.5k1319.5k
          0.9k1715.3k
          0.6k148.4k
          总计5267.2k

          在领取完后的一小时,相关搜索结果有 158 条。据此粗略估计,每天在闲鱼上被转卖的消费券大概价值 20 万元。

          根据官方的消费券发放安排,价值 3 亿的消费券会在 9 月 26 日到 10 月 31 日中的 31 天发放,假设能够全部发完,每天应该发放价值 967 万元的消费券。

          如果只有闲鱼上有黄牛,那么 9 月 27 日这天的黄牛含量大概在 2% (20/967≈2.07%)。

        5. DRAFT: pre-bundle dependencies for vite

          背景

          如果我不想每次构建 SPA 的时,把项目的依赖也一并重新构建一遍,我应该怎么做?
          预打包项目依赖,当项目依赖长久没有发生变更时,不再重新 bundle node_modules。

          解决方案

          类似 Webpack DLL plugin,但是要更加智能。

          只预打包项目中多次使用的依赖包。
          同时支持 build/serve 两种模式。

          实现细节——预打包部分:

          用正则表达式找出项目中所有的 import 语句
          用单独的 vite config 配合 preBundle 插件为第三方包生成预打包文件

          实现细节——使用预打包文件:

          build 模式,这里涉及 rollup 相关的插件逻辑,需要让 rollup 将预打包的模块 id 解析为外部依赖,并且将预打包文件作为 prebuilt-chunk 输出。

          dev 模式,这里涉及 vite 跟 esbuild 相关的逻辑。
          由于我们并不会为所有的第三方依赖生成预打包文件,所以存在某些 optimizeDeps 模块引用预打包模块的情况。
          vite 会使用 esbuild optimize deps,所以需要配合 esbuild 插件处理。

          如何在导入方使用 commonjs 导出的 named export ?

          在预打包阶段, rollup 不会为 cjs entry 生成 named export,当我们在导入预打包文件时,需要对这些被转换成 esm 的 cjs 做一些额外处理。

          需要自己实现一个 transform hook:

          // input
          import { useState } from 'react';
          // ouput
          import React from 'react';
          const useState = React.useState;
          

          反思

          预打包多次使用的依赖会为每个依赖都创建一个 bundle 文件,这可能会让浏览器加载页面的时候需要发送更多的请求。

          但是考虑到这些预打包文件可能很少更新,浏览器本地缓存可以起到很好的效果。如果浏览器跟服务器之间使用的是 http2 协议,这些请求似乎也不太算是问题?
          如果要从根本上解决这个问题,还得靠 web bundle。

          实现 merged exports

          https://github.com/tc39/proposal-module-declarations?tab=readme-ov-file

          @ant-design/icons 中提供了很多 icons, 如果为每个 icon 都创建一个 prebundle chunk, 那么 output 目录中就会出现上千的小文件。

          要避免这种情况,需要实现合并 exports 的功能。

          // vite.config.mts
          
          plugins: [
            prebundleReference({
              merge: {
                '<ruleName>': ['<module-id-prefix>', '@ant-design/icons']
              }
            })
          ]
          

          以上的配置会在 transform 环节产生如下的代码:

          // pre-bundle-merged-<ruleName>.mjs
          import * as __at__ant_design_icons from '@ant-design/icons';
          import * as __at__ant_design_icons_Foo from '@ant-design/icons/Foo';
          export const __ns_at__ant_design_icons = __at__ant_design_icons;
          export const __ns_at__ant_design_icons_Foo = __at__ant_design_icons_Foo;
          

          对应的 manifest 部分

          [
            {
              "moduleId": "@ant-design/icons",
              "moduleFilePath": "pre-bundle-merged-<ruleName>.mjs",
              "exports": [
                "default",
                "__moduleExports"
              ],
              "exportAs": "__ns_at__ant_design_icons",
              "isCommonJS": false
            },
            {
              "moduleId": "@ant-design/icons/Foo",
              "moduleFilePath": "pre-bundle-merged-<ruleName>.mjs",
              "exports": [
                "default",
                "__moduleExports"
              ],
              "exportAs": "__ns_at__ant_design_icons_Foo",
              "isCommonJS": false
            },
          ]

          当在 reference 插件中使用时,需要转换 import 代码:

          // helper
          
          export function _prebundle_merge_get_default(moduleLike) {
            return moduleLike.default ?? moduleLike;
          }
          
          export function _prebundle_merge_get_named(moduleLike, name) {
            if(name in moduleLike){
              return moduleLike[name];
            }
            if(moduleLike.default && name in moduleLike.default) {
              return moduleLike.default[name];
            }
          }
          // default import
          import Foo from '@ant-design/icons/Foo';
          // =>
          import { __ns_at__ant_design_icons_Foo as __ns_at__ant_design_icons_Foo$1 } from '@ant-design/icons/Foo';
          const Foo = _prebundle_merge_get_default(__ns_at__ant_design_icons_Foo$1);
          
          
          // named import
          import { Foo } from '@ant-design/icons';
          // =>
          import { __ns_at__ant_design_icons_Foo as __ns_at__ant_design_icons_Foo$2 } from '@ant-design/icons/Foo';
          const Foo = _prebundle_merge_get_named(__ns_at__ant_design_icons_Foo$2, 'Foo');
          
          // ns import
          import * as Icons from '@ant-design/icons';
          // =>
          import { __ns_at__ant_design_icons_Foo as __ns_at__ant_design_icons_Foo$3 } from '@ant-design/icons/Foo';
          const Icons = __ns_at__ant_design_icons_Foo$3;

          prebundle merge transform 的逻辑应该在 commonjs 转换之后,因为 commonjs transform 假定 module 只会导出 { default: blabla }

          // input
          import {useCallback} from 'react';
          
          // transform commonjs
          import React from 'react';
          const useCallback = React.useCallback;
          
          // transform prebundle merge
          import { __ns_react as __ns_react$1 } from 'react';
          const React = _prebundle_merge_get_default(__ns_react$1);
          const useCallback = React.useCallback;

          好像也可以将 commonjs transform 跟 prebundle merge transform 合并?

          [
            {
              "moduleId": "react",
              "moduleFilePath": "pre-bundle-merged-<ruleName>.mjs",
              "exports": [
                "default"
              ],
              "exportAs": "default",
              "isCommonJS": true
            }
          ]
          import React, {useCallback} from 'react';
          
          // =>
          import { default as __ns_react$1 } from 'react';
          const React = _prebundle_merge_get_default(__ns_react$1);
          const useCallback = _prebundle_merge_get_named(__ns_react$1.'useCallback');

          接着,在 resolve 阶段,只需要将模块 id 都替换成 <path-to>/<moduleFilePath>

          预打包依赖项,真的可以提高构建效率吗?

          在我的项目中,第三方依赖的体积远大于实际的代码量,每次启动 dev server 或者构建 production artifacts,都需要消耗很多时间在转换 node_modules 代码上。预打包依赖就可以很好的解决这个问题。

          另外,配合 nx 的共享缓存,这个项目的所有贡献者、CI 流水线,都可以享受到预打包带来的提升。

          为啥不能用 UMD 格式提供 pre-bundle ?

          rollup 不支持为 umd bundle 生成 common shared chunk

        6. 如何选购冰箱

          冰箱是柜子,冰柜是箱子

          影响冰箱摆放位置的产品参数

          作为家中体积最大的电器,找个合适的位置放冰箱很关键。影响冰箱摆放空间的参数有两个:

          • 散热方式
          • 开门方式

          两侧散热的冰箱需要预留更多的空间帮助全年无修的冰箱散热,通常需要在冰箱的左右两侧以及后侧各预留 10cm 左右的空间。

          底部散热或者顶部散热的冰箱对空间的需求比较小,适合摆放在比较狭小的空间。

          开门方式也会影响冰箱的摆放,虽然冰箱叫「箱」,但是开门方式跟柜子类似,主要使用抽屉跟平开门。所以在摆放冰箱的时候也应该像摆放柜子一样,考虑门板、抽屉跟其他家具的冲突。如果空间比较狭小,可以选择 90° 的平开门。

          另一个节省冰箱摆放空间的技巧是把冰箱插座的位置调整到冰箱的侧面,或者使用内嵌式插座。这样可以为冰箱节省 3 ~ 4cm 的深度空间。

          影响收纳能力的产品参数

          作为柜子,冰箱当然越能装越好,商家往往只会商品详情页上写明容量,例如,500L,但是我们往冰箱中存放的只会是固体。所以容量只能作为参考,真正影响使用体验的是冰箱的收纳方式,对应的产品参数是门款式。

          门款式通常对冷冻区的格局影响比较大,大家可以按照自己最常屯的物品选择适合的冷冻区格局。

          影响使用体验的产品参数

          串味是影响冰箱使用体验的一个常见问题,从冰箱的角度解决这个问题有两种方式:

          • 双循环风道
          • 净味系统

          现在多数冰箱采用风冷降温,冷空气会在冷藏区、冷冻区之间流动,只有单循环风道的冰箱会有跨区窜味的问题。一些冰箱通过引入双循环风道,将冷藏、冷冻区之间物理隔离开,从而避免串味的问题。

          净味系统则是引入了类似空气净化器的模块,也能在一定程度上减少冰箱的异味,异味浓度低了,串味的影响也会降低。

          影响保鲜功能的产品参数

          保鲜可以从两个层面理解 —— 防止腐败、防止风干

          为了防止腐败,我们应该尽量选择带有杀菌能力检测证书的冰箱 只声称可以杀菌但不提供证书的都看作没有杀菌能力,通过抑菌、灭菌来降低食物污染风险。

          为了防止冷风将新鲜蔬果吹干,可以选择带有湿度调节区域的冰箱,减少干冷空气对果蔬的影响。

          不过防止腐败跟防止风干是有些冲突的两个功能,潮湿的有氧环境有利于微生物繁殖,所以, 最好还是减少新鲜蔬果的存放时间,或者将蔬果单独分区存放。

          产品关键参数速查表

          • 摆放位置
            • 长宽高
            • 散热方式
            • 开门方式
            • 插座位置
          • 收纳能力
            • 容量
            • 门款式
          • 防串味
            • 是否双循环
            • 净味系统
          • 保鲜
            • 杀菌、抑菌能力证书
            • 果蔬保鲜区

        7. 给 Email 写样式真的很难

          虽然 IE 退出了浏览器的历史舞台,但是 Windows 平台上的 Outlook 依然继承了 IE 的衣钵,继续在邮件客户端界发光发热。

          This ranks email clients based on their support among the 298 HTML and CSS features listed on Can I email.

          最好用内联样式

          很多情况下,使用外部样式表并不靠谱,比如 GMail 以及 Outlook 就会在特定情况下移除邮件 HTML 中的 style 标签,一个更保守的做法是使用元素上的内联样式。对于简单的通知邮件来说,完全够用了。

          如果嫌手搓样式很麻烦,可以先用 TailWind CSS 编写,然后让 ChatGPT 帮忙转换成内联样式。

          避免使用 base64 内联图片

          虽然 Base64 图片 URL 很简单易用,但是很多邮件客户端会把它们当作外部图片一样默认屏蔽。

          一个对展示效果更加友好的做法是通过 cid 引用内联图片,这时,图片会被作为邮件的附件一起发送给收件人,如果不希望正文图片被客户端识别为可下载的附件,则需要将附件的 Content-Disposition 设置为 inline,同时,还需要设置正确的 Content-Type

          Quick summary

          1. Inline only attachments: use multipart/related
          2. Non-inline only attachments: use multipart/mixed
          3. Inline and non-inline attachments use multipart/mixed and multipart/related
          mime – HTML-Email with inline attachments and non-inline attachments – Stack Overflow

          我的 Email 模板工作流

          考虑到 EMail HTML 本身的复杂性,为了简化 Email 模板的开发,我采用了下面的工作流:

          首先,使用 degit 初始化一个集成了 TailWind CSS 跟 Vite 的项目模板:

          npx degit kometolabs/vite-tailwind-nojs-starter email-template

          修改其中的 vite.config.mjs:

          import { resolve } from "path";
          import { defineConfig } from "vite";
          import { ViteImageOptimizer } from "vite-plugin-image-optimizer";
          
          function optimizeSvgPlugin() {
            if (!!process.env.OPTIMIZE_IMAGES) {
              return ViteImageOptimizer({
                svgo: {
                  plugins: [{ removeViewBox: false }, { removeDimensions: true }],
                },
              });
            }
          }
          
          export default defineConfig(() => ({
            build: {
              outDir: "../dist",
              emptyOutDir: true,
              // avoid inlining images
              assetsInlineLimit: () => false,
              rollupOptions: {
                input: {
                  page1: resolve(__dirname, "src", "page1.html"),
                  page2: resolve(__dirname, "src", "page2.html"),
                },
                output: {
                  // avoid hash in assets filename
                  assetFileNames: (assetInfo) => {
                    const info = assetInfo.name.split(".");
                    let extType = info[info.length - 1];
          
                    // Explicitly handle image files
                    if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
                      extType = "img";
                      // Return the original filename without hashing
                      return `assets/${extType}/[name][extname]`;
                    }
          
                    // For other asset types, you can keep the default hashing behavior
                    return `assets/${extType}/[name]-[hash][extname]`;
                  },
                },
              },
            },
            root: "src",
            plugins: [optimizeSvgPlugin()],
          }));

          在编写 HTML 时需要手动创建一个引入 TailWind CSS 的 style 标签:

          <style>
          @tailwind base;
          @tailwind components;
          @tailwind utilities;
          </style>

          接下来就可以按照常规的方法用 HTML 画页面了,不过需要注意的是,大部分邮件客户端对 flex-box 的支持都比较差,在编写样式时需要注意。

          完成了 HTML 页面以及样式后,就可以借助 ChatGPT 将 TailWind 转换成内联样式的代码。最后,借助下面的脚本,即可将构建出来的 HTML 文件转换成 ejs 模板:

          // convert all `{{ foo }}` to `<%= foo %>`
          
          function convertToEjs(content: string) {
            return content.replace(/{{\s*(\w+?)\s*}}/g, "<%= $1 %>");
          }
          
          /**
           * Convert /assets/img/foo-bar.svg to cid:foo-bar
           * @param content
           */
          function toCidReference(content: string): string {
            return content.replace(/\/assets\/img\/([a-zA-Z0-9-)]+?)\.(svg|png|jpg|jpeg)/g, "cid:$1");
          }
          
          const files = await glob(dir + "/**/*.html");
          
          for (const filename of files) {
            const content = await fs.readFile(filename, "utf-8");
            const dirOfFile = path.dirname(filename);
            const newContent = toCidReference(convertToEjs(content));
            const newFilename = path.join(
              dirOfFile,
              path.basename(filename, ".html") + ".ejs",
            );
          
            await fs.writeFile(newFilename, newContent);
            echo(chalk.green(`Converted ${filename} to ${newFilename}`));
          }

          用来生成邮件模板的命令行操作如下:

          #!/usr/bin/bash
          OPTIMIZE_IMAGES=true yarn build
          cp ./dist/assets/img/* ./assets/img/
          npx tsx ./scripts/to-ejs-template.ts --dir ./dist

          参考链接

          How to Embed Images in Your Emails (CID, HTML Inline & More) | SendGrid

          mime – HTML-Email with inline attachments and non-inline attachments – Stack Overflow

        8. 如何为博文自动生成可读的 Permalink

          WordPress 默认提供了一个比较适合英语文章的 Permalink 生成机制,如果你跟我一样使用中文标题,那自动生成的 Permalink 中就会包含汉字,这对 URL 而言并不友好。之前我都会手动给每篇博文翻译一个英文标题,再将其 slugify,做得多了就会觉得繁琐,于是我开发了一个 API,可以用来帮助自动化地完成这个过程。

          API 的调用方式如下,大家可以免费使用。

          GET https://blogkit.apps.gianthard.rocks/api/v1/slugify?t=如何科学饲养母猪
          
          HTTP/2 200 OK
          server: nginx
          date: Mon, 19 Aug 2024 10:01:06 GMT
          content-type: application/json; charset=utf-8
          age: 189318
          cache-control: public,max-age=31536000
          content-length: 24
          x-http2-stream-id: 3
          
          "scientific-sow-rearing"

          要在 WordPress 中自动调用的话,需要在 WordPress 管理页面添加一些代码:

          function suggest_slug_from_api( $slug, $post_id, $post_status, $post_type, $post_parent, $original_slug ) {
              // Only apply for new posts or when the slug is empty
              if ( empty($slug) ) {
                  $post = get_post($post_id);
                  $title = $post ? $post->post_title : '';
          
                  if ( empty($title) ) {
                      return $slug; // Return original slug if no title
                  }
          
                  $api_url = 'https://blogkit.apps.gianthard.rocks/api/v1/slugify';
          
                  // Encode the title for use in URL
                  $query = http_build_query(array('t' => $title));
                  $url = $api_url . '?' . $query;
          
                  // Make the API request
                  $response = wp_remote_get($url);
          
                  // Check if the request was successful
                  if ( !is_wp_error($response) && wp_remote_retrieve_response_code($response) == 200 ) {
                      $body = wp_remote_retrieve_body($response);
                      $suggested_slug = json_decode($body, true);
          
                      // If we got a valid slug, use it
                      if ( $suggested_slug && is_string($suggested_slug) ) {
                          return $suggested_slug;
                      }
                  }
              }
          
              // If anything goes wrong or if the post already has a custom slug, return the original slug
              return $slug;
          }
          add_filter( 'wp_unique_post_slug', 'suggest_slug_from_api', 10, 6 );
          

          我使用的是 WP Code 插件,用这个插件管理 PHP 代码片段非常方便,免费版也很够用。

          注意只在 Admin 页面上启用这个片段

          上面的代码工作完成后,WordPress 就会在草稿保存后自动生成 URL slug 。需要注意的是,编辑页面的前端可能不会及时更新 URL slug 预览,但是发布后就会使用通过 API 生成的 URL Slug。