diff options
| -rw-r--r-- | 2026/08/18/房颤与血栓栓塞:从左心耳血流淤滞到缺血性卒中/index.html | 28 | ||||
| -rw-r--r-- | search.xml | 568 |
2 files changed, 298 insertions, 298 deletions
diff --git a/2026/08/18/房颤与血栓栓塞:从左心耳血流淤滞到缺血性卒中/index.html b/2026/08/18/房颤与血栓栓塞:从左心耳血流淤滞到缺血性卒中/index.html index ade5648f..556dc147 100644 --- a/2026/08/18/房颤与血栓栓塞:从左心耳血流淤滞到缺血性卒中/index.html +++ b/2026/08/18/房颤与血栓栓塞:从左心耳血流淤滞到缺血性卒中/index.html @@ -298,10 +298,10 @@ mjx-container[display="true"] + br { </div> </div> <div class="post-content"> - <p>房颤并不只是“心跳不规律”。它会破坏心房的有效收缩,使血液在左心耳等部位淤滞;同时,心房结构、内皮功能和凝血状态也会改变。血栓一旦脱落,随血流进入脑动脉,就可能引发缺血性卒中。房颤管理的核心,不是单纯把心率降下来,而是同时评估血栓风险、出血风险、症状和心律状态。</p> + <p>房颤会削弱心房的有效收缩,使血液在左心耳等部位淤滞,并伴随心房结构、内皮功能和凝血状态改变。血栓一旦脱落,随血流进入脑动脉,就可能引发缺血性卒中。房颤管理需要同时评估血栓风险、出血风险、症状和心律状态,心率控制只是其中一项。</p> <h2 id="房颤是什么"><a class="header-anchor" href="#房颤是什么">¶</a>房颤是什么</h2> <p>房颤,即心房颤动,是一种常见的持续性心律失常。正常情况下,心房先有规律地收缩,把血液推入心室;房颤时,心房电活动变得快速、紊乱,心房不再进行有效的整体收缩,心室则以不规则节律搏动。</p> -<p>患者可能感到心悸、胸闷、乏力、活动耐量下降,也可能完全没有症状。无症状并不等于没有风险。房颤对脑卒中的影响,主要来自血流淤滞和血栓形成,而不是来自心跳“乱”本身。</p> +<p>患者可能感到心悸、胸闷、乏力和活动耐量下降,也可能完全没有症状。无症状不能排除血栓风险。房颤导致卒中的主要环节是血流淤滞和血栓形成,心跳不规则本身并不是栓塞的直接原因。</p> <p>房颤与年龄增长、高血压、心力衰竭、冠心病、瓣膜疾病、糖尿病、肥胖、睡眠呼吸暂停和甲状腺功能异常等因素有关。它还可能形成一个持续恶化的循环:房颤引起心房电和结构重塑,重塑又使房颤更容易持续。<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup><sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p> <h2 id="血栓为什么容易在房颤中形成"><a class="header-anchor" href="#血栓为什么容易在房颤中形成">¶</a>血栓为什么容易在房颤中形成</h2> <p>房颤相关血栓形成,通常可以从三个方面理解:血流淤滞、心房组织改变和凝血倾向增强。这三个方面对应经典的血栓形成机制,即 Virchow 三要素。</p> @@ -310,41 +310,41 @@ mjx-container[display="true"] + br { <p>房颤患者的血栓并不一定形成在心室,也不一定发生在全身静脉。典型的房颤相关血栓来自左心房,尤其是左心耳。血栓形成后,如果受到心房压力变化、心律恢复或血流剪切力影响,就可能脱落。</p> <h3 id="心房壁会发生结构和功能改变"><a class="header-anchor" href="#心房壁会发生结构和功能改变">¶</a>心房壁会发生结构和功能改变</h3> <p>长期房颤可能导致心房扩大、纤维化和局部内皮功能异常。心房内膜受到机械牵张和炎症信号影响后,抗凝和抗血小板的保护性状态可能减弱,促凝状态增强。</p> -<p>房颤与炎症、氧化应激、纤维化和内皮功能障碍之间存在相互作用。现有研究支持“房颤不是单纯电活动异常”的观点:它同时涉及心房结构、代谢、炎症和凝血网络。<sup class="footnote-ref"><a href="#fn1" id="fnref1:1">[1:1]</a></sup><sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup></p> +<p>房颤与炎症、氧化应激、纤维化和内皮功能障碍之间存在相互作用。现有研究表明,房颤同时涉及心房结构、代谢、炎症和凝血网络。<sup class="footnote-ref"><a href="#fn1" id="fnref1:1">[1:1]</a></sup><sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup></p> <h3 id="凝血系统可能被进一步激活"><a class="header-anchor" href="#凝血系统可能被进一步激活">¶</a>凝血系统可能被进一步激活</h3> <p>房颤患者可能出现血小板活化、凝血酶生成增加和纤维蛋白形成增强。高血压、心力衰竭、糖尿病、慢性肾病和高龄等因素,又会进一步提高凝血和血管损伤风险。</p> <p>因此,房颤相关血栓不是由单一因素造成的。即使患者某一次心电图没有记录到房颤,也不能据此认为血栓风险已经消失。房颤可能具有阵发性,血栓风险还受到基础疾病、既往卒中史和心房结构等因素影响。</p> <h2 id="血栓如何变成脑卒中"><a class="header-anchor" href="#血栓如何变成脑卒中">¶</a>血栓如何变成脑卒中</h2> <p>左心房内形成的血栓如果脱落,会进入左心室,再被泵入主动脉。血栓沿动脉进入脑循环后,可能堵塞颈内动脉、大脑中动脉或其他脑动脉分支,造成局部脑组织缺血和坏死。</p> -<p>这类卒中属于心源性栓塞。与某些小动脉粥样硬化性卒中相比,房颤相关卒中往往具有栓子较大、血管阻塞位置较近端、神经功能损害较重等特点,也更容易出现较大的梗死范围。</p> +<p>这类卒中属于心源性栓塞。与某些小动脉粥样硬化性卒中相比,房颤相关卒中的栓子往往较大,阻塞部位可能更近端,神经功能损害也可能更重,因此梗死范围可能更大。</p> <p>经典的 Framingham 研究把房颤确定为卒中的独立危险因素。后续指南和综述也持续把卒中预防放在房颤管理的核心位置。<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup><sup class="footnote-ref"><a href="#fn5" id="fnref5">[5]</a></sup></p> <p>血栓也可能进入其他器官,引起外周动脉栓塞、肠系膜缺血、肾梗死或脾梗死。不过,临床上最受关注的后果仍然是缺血性卒中和短暂性脑缺血发作。</p> <h2 id="房颤患者的风险并不完全相同"><a class="header-anchor" href="#房颤患者的风险并不完全相同">¶</a>房颤患者的风险并不完全相同</h2> -<p>不能只根据“有没有房颤”判断是否需要抗凝。房颤患者的血栓栓塞风险存在很大差异。</p> +<p>是否需要抗凝,取决于患者的整体血栓栓塞风险。不同房颤患者的风险并不相同。</p> <p>临床通常会使用风险评分工具估计卒中风险。常见因素包括心力衰竭、高血压、年龄、糖尿病、既往卒中或短暂性脑缺血发作、血管疾病以及女性性别等。2023 年美国指南和 2024 年欧洲指南都强调,抗凝决策应以经过验证的卒中风险评估为基础,同时结合患者偏好、出血风险和临床情境。<sup class="footnote-ref"><a href="#fn6" id="fnref6">[6]</a></sup><sup class="footnote-ref"><a href="#fn7" id="fnref7">[7]</a></sup></p> <p>风险评分的作用是帮助估计总体风险,不是替代临床判断。它也不能回答所有问题。例如,肾功能变化、近期出血、肿瘤、贫血、合并用药和高龄,都可能改变抗凝治疗的实际获益与风险。</p> -<p>出血风险评估同样重要,但“出血风险高”并不自动等于“不能抗凝”。更合理的做法是寻找可纠正因素,例如控制血压、减少不必要的抗血小板药物、避免非甾体抗炎药、处理贫血和改善肾功能监测。</p> +<p>出血风险升高时,应先寻找可纠正因素,例如控制血压、减少不必要的抗血小板药物、避免非甾体抗炎药、处理贫血和加强肾功能监测,再综合判断抗凝获益与出血风险。</p> <h2 id="抗凝药为什么能够预防卒中"><a class="header-anchor" href="#抗凝药为什么能够预防卒中">¶</a>抗凝药为什么能够预防卒中</h2> -<p>抗凝药的作用是抑制凝血级联,减少纤维蛋白血栓形成。它并不是把已经形成的血栓“瞬间溶解”,而是降低新血栓形成和原有血栓扩大的概率。</p> +<p>抗凝药通过抑制凝血级联,减少纤维蛋白血栓形成。它主要降低新血栓形成和原有血栓扩大的概率,不能替代针对急性血栓的溶栓或取栓治疗。</p> <p>目前,房颤卒中预防常用口服抗凝药包括直接口服抗凝药和华法林。直接口服抗凝药包括直接凝血酶抑制剂以及直接凝血因子 Xa 抑制剂。它们起效较快,通常不需要像华法林那样频繁调整国际标准化比值,但仍需要根据肾功能、年龄、体重、合并用药和适应证选择剂量。</p> <p>多项随机试验的个体患者数据分析显示,直接口服抗凝药总体上适合用于非瓣膜性房颤的卒中预防,且不同年龄和性别亚组的主要疗效方向相对一致。系统综述和网络荟萃分析也支持口服抗凝药预防房颤相关卒中,但不同药物在胃肠道出血、颅内出血和其他出血结局上并不完全相同。<sup class="footnote-ref"><a href="#fn8" id="fnref8">[8]</a></sup><sup class="footnote-ref"><a href="#fn9" id="fnref9">[9]</a></sup></p> -<p>这并不意味着直接口服抗凝药对所有房颤患者都优于华法林。机械心脏瓣膜患者、部分中重度二尖瓣狭窄患者,以及某些严重肾功能异常患者,需要按照具体指南和专科医生意见选择方案。药物种类不能只凭“新药”或“方便”来决定。</p> +<p>直接口服抗凝药并不适用于所有房颤患者。机械心脏瓣膜患者、部分中重度二尖瓣狭窄患者,以及某些严重肾功能异常患者,需要按照具体指南和专科医生意见选择方案。药物种类应依据适应证、肾功能、合并用药和出血风险确定。</p> <h2 id="抗血小板药不能简单替代抗凝药"><a class="header-anchor" href="#抗血小板药不能简单替代抗凝药">¶</a>抗血小板药不能简单替代抗凝药</h2> <p>阿司匹林等抗血小板药主要影响血小板功能,而房颤相关血栓形成往往以凝血和纤维蛋白网络为重要特征。对于需要抗凝的房颤患者,单用抗血小板药通常不能等效替代口服抗凝药。</p> <p>如果患者同时存在冠状动脉疾病、冠脉支架或急性冠脉综合征,医生可能在特定阶段联合使用抗凝药和抗血小板药。但联合治疗会增加出血风险,疗程和药物组合需要严格限定,不能自行长期叠加。</p> <h2 id="恢复窦律后,是否可以马上停抗凝"><a class="header-anchor" href="#恢复窦律后,是否可以马上停抗凝">¶</a>恢复窦律后,是否可以马上停抗凝</h2> <p>通常不能仅因为心律恢复正常,就立即停用抗凝药。</p> <p>房颤患者的卒中风险不仅由当天是否出现房颤决定,还与既往房颤负荷、心房结构、年龄和伴随疾病有关。部分患者会出现无症状复发,普通门诊心电图也可能无法捕捉阵发性房颤。</p> -<p>因此,抗凝治疗一般根据长期血栓栓塞风险决定,而不是只根据是否成功复律、消融或暂时没有症状决定。2024 年欧洲指南将房颤管理放在 AF-CARE 框架中,强调合并危险因素管理、避免卒中和血栓栓塞、减少症状以及动态评估治疗效果。<sup class="footnote-ref"><a href="#fn7" id="fnref7:1">[7:1]</a></sup></p> -<h2 id="房颤管理不只是“吃抗凝药”"><a class="header-anchor" href="#房颤管理不只是“吃抗凝药”">¶</a>房颤管理不只是“吃抗凝药”</h2> -<p>房颤的完整管理需要同时处理四个问题:评估并降低卒中和血栓栓塞风险;控制心率或恢复并维持窦律,以改善症状和心脏功能;管理高血压、肥胖、睡眠呼吸暂停、糖尿病、心力衰竭和饮酒等危险因素;持续监测药物安全性、肾功能、出血表现和房颤复发。</p> -<p>节律控制可能改善症状,也可能在部分患者中改善长期结局,但它不能自动替代卒中风险评估。消融治疗也不等于血栓风险永久消失。治疗目标应从“消灭一次房颤”转向长期降低房颤负荷、卒中风险和心血管并发症。</p> +<p>抗凝治疗通常依据长期血栓栓塞风险决定。复律、消融或症状暂时消失,并不能单独决定停药。2024 年欧洲指南将房颤管理放在 AF-CARE 框架中,强调危险因素管理、卒中和血栓栓塞预防、症状控制以及治疗效果的动态评估。<sup class="footnote-ref"><a href="#fn7" id="fnref7:1">[7:1]</a></sup></p> +<h2 id="房颤管理的组成"><a class="header-anchor" href="#房颤管理的组成">¶</a>房颤管理的组成</h2> +<p>房颤管理包括四项工作。医生需要评估卒中和血栓栓塞风险,控制心率或恢复并维持窦律,处理高血压、肥胖、睡眠呼吸暂停、糖尿病、心力衰竭和饮酒等危险因素,并监测药物安全性、肾功能、出血表现和房颤复发。</p> +<p>节律控制可以改善症状,并可能改善部分患者的长期结局,但仍需持续评估卒中风险。消融治疗后也要根据长期风险决定抗凝方案。治疗目标是降低房颤负荷、卒中风险和心血管并发症。</p> <h2 id="需要立刻就医的表现"><a class="header-anchor" href="#需要立刻就医的表现">¶</a>需要立刻就医的表现</h2> <p>如果房颤患者突然出现单侧肢体无力或麻木、口角歪斜、说话含糊、理解困难、视物异常、行走不稳或突发剧烈头痛,应立即按照卒中急救流程就医。症状即使自行缓解,也可能是短暂性脑缺血发作,不能等待观察。</p> <p>正在服用抗凝药的患者,如果出现持续性呕血、黑便、血尿、无法止住的鼻出血、严重头痛、意识变化或跌倒撞击头部,也需要尽快就医。不要因为担心出血而自行停药,也不要因为漏服一次而擅自加倍补服。</p> <h2 id="结语"><a class="header-anchor" href="#结语">¶</a>结语</h2> -<p>房颤与血栓栓塞的关系,可以归纳为一条连续的病理链:<strong>心房失去有效收缩,血流在左心耳等部位淤滞;心房结构、内皮和凝血状态发生改变;血栓形成并可能脱落;栓子进入脑循环后造成缺血性卒中。</strong></p> -<p>真正有价值的房颤管理,不是只关注心电图上的节律,也不是只关注心率数字,而是同时回答三个问题:患者的血栓风险有多高,抗凝的获益是否超过出血风险,以及如何降低房颤本身和相关基础疾病的负荷。对个体患者而言,具体药物、剂量和停药时机必须由医生结合病史、肾功能、年龄、合并用药和出血情况决定。</p> +<p>房颤增加血栓栓塞风险的过程可以概括为:<strong>心房失去有效收缩,血流在左心耳等部位淤滞;心房结构、内皮和凝血状态发生改变;血栓形成并可能脱落;栓子进入脑循环后造成缺血性卒中。</strong></p> +<p>房颤管理需要回答三个问题:患者的血栓风险有多高,抗凝获益是否超过出血风险,以及怎样降低房颤和相关基础疾病的负荷。具体药物、剂量和停药时机,应由医生结合病史、肾功能、年龄、合并用药和出血情况决定。</p> <h2 id="References"><a class="header-anchor" href="#References">¶</a>References</h2> <hr class="footnotes-sep"> <section class="footnotes"> @@ -507,6 +507,58 @@ char sa_data[] socket address (variable-length data)</p> </tags> </entry> <entry> + <title>F# 重载解析问题</title> + <url>/2025/03/30/F-%E9%87%8D%E8%BD%BD%E8%A7%A3%E6%9E%90%E9%97%AE%E9%A2%98/</url> + <content><![CDATA[<p>当在 F# 中使用一些 C# 类库的时候,可能会遇到类似如下的问题:</p> +<figure class="highlight fsharp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">open</span> System</span><br><span class="line"><span class="keyword">open</span> System.Numerics</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> someFunction () <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> v<span class="operator">:</span> Vector<span class="operator"><</span><span class="type">byte</span><span class="operator">></span> <span class="operator">=</span> Vector(<span class="number">0</span>uy)</span><br><span class="line"> <span class="keyword">let</span> array<span class="operator">:</span> <span class="type">byte</span>[] <span class="operator">=</span> [<span class="operator">||</span>]</span><br><span class="line"> <span class="keyword">let</span> span<span class="operator">:</span> Span<span class="operator"><</span><span class="type">byte</span><span class="operator">></span> <span class="operator">=</span> Span(array)</span><br><span class="line"> v.CopyTo(span)</span><br><span class="line">Compiler error</span><br><span class="line"> v.CopyTo(span)</span><br><span class="line"> <span class="operator">----^^^^^^^^^^^^^^</span></span><br><span class="line"></span><br><span class="line"><span class="operator">/</span>home<span class="operator">/</span>muqiu<span class="operator">/</span>stdin(<span class="number">99</span>,<span class="number">5</span>)<span class="operator">:</span> error FS0041<span class="operator">:</span> A unique overload <span class="keyword">for</span> method <span class="symbol">'CopyTo</span>' could <span class="built_in">not</span> be determined based on <span class="keyword">type</span> information prior <span class="keyword">to</span> this program point. A <span class="keyword">type</span> annotation may be needed.</span><br><span class="line"></span><br><span class="line">Known <span class="keyword">type</span> <span class="title class_">of</span> argument<span class="operator">:</span> Span<span class="operator"><</span><span class="type">byte</span><span class="operator">></span></span><br><span class="line"></span><br><span class="line">Candidates<span class="operator">:</span></span><br><span class="line"> <span class="operator">-</span> Vector.CopyTo(destination<span class="operator">:</span> Span<span class="operator"><</span><span class="type">byte</span><span class="operator">></span>) <span class="operator">:</span> <span class="type">unit</span></span><br><span class="line"> <span class="operator">-</span> Vector.CopyTo(destination<span class="operator">:</span> Span<span class="operator"><</span><span class="type">byte</span><span class="operator">></span>) <span class="operator">:</span> <span class="type">unit</span></span><br></pre></td></tr></table></figure> +<p>编译器认为存在两个相同的 <code>CopyTo</code> 重载无法区分。但查阅文档发现,<code>Vector<T></code> 的 <code>CopyTo</code> 方法实际上只有一个匹配的重载(接受 <code>Span<T></code>),这似乎矛盾。</p> +<p>这是因为 F# 编译器处理泛型方法重载的方式:</p> +<ul> +<li>泛型接口继承:<code>Vector<T></code> 可能实现了多个接口,导致编译器看到两个签名相同的 <code>CopyTo</code> 方法(例如通过不同接口继承)。</li> +<li>语言设计差异:F# 缺乏 C# 的 “most concrete” 重载选择机制。当多个重载来自不同继承路径时,F# 不会自动选择最具体的实现,需要显式指引。</li> +</ul> +<p>仅我所知的一种解决方案是通过添加扩展方法显式指引编译器:</p> +<figure class="highlight fsharp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">Vector</span><span class="operator"><</span><span class="symbol">'T</span><span class="operator">></span> <span class="keyword">with</span></span><br><span class="line"> <span class="keyword">member</span> <span class="keyword">inline</span> this.CopyToByteSpan (span <span class="operator">:</span> Span<span class="operator"><</span><span class="type">byte</span><span class="operator">></span>) <span class="operator">=</span> </span><br><span class="line"> this.CopyTo(span) <span class="comment">// 显式绑定具体重载</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 调用时使用新方法</span></span><br><span class="line">v.CopyToByteSpan(span)</span><br></pre></td></tr></table></figure> +<p>这种方法通过创建具体的类型路径,帮助编译器绕过复杂的重载解析逻辑。</p> +<p>学艺不精,不知道这是不是语言设计的差异,可能 F# 倾向于要求更明确的类型信息以避免意外行为?或许当泛型类型继承多个接口时,有没有可能出现在具体类型中不易察觉的隐式重载冲突?</p> +<h2 id="Refs"><a class="header-anchor" href="#Refs">¶</a>Refs.</h2> +<ul> +<li><a href="https://github.com/fsharp/fslang-suggestions/issues/905">“Most concrete” tiebreaker for generic overloads #905</a></li> +<li><a href="https://github.com/dotnet/fsharp/issues/1647">Fail to resolve to non generic overload. #1647</a></li> +</ul> +]]></content> + <tags> + <tag>Technique</tag> + </tags> + </entry> + <entry> + <title>FDA 仿制药流程</title> + <url>/2025/11/29/FDA-%E4%BB%BF%E5%88%B6%E8%8D%AF%E6%B5%81%E7%A8%8B/</url> + <content><![CDATA[<p>#药物研发</p> +<p>在FDA监管的药物研发、临床试验和上市流程中,当一个已上市药物(通常指创新药,通过新药申请NDA获得批准)出现一种更低廉的制药方式,但其活性成分的分子式保持完全相同时,这种情况本质上涉及仿制药(generic drug)的开发或工艺优化申请,而非从零开始的全新药物研发。简单来说,不需要重新进行完整的临床试验(即I-III期的大规模人体试验),但必须通过简化的监管路径证明新制药方式生产的药物与原药在质量、安全性和疗效上等效。</p> +<p>首先,理解核心前提:分子式完全相同意味着活性成分(active pharmaceutical ingredient, API)是相同的化学实体,例如从专利保护的原研药转向无专利保护的仿制药生产。这不同于生物制品(如单克隆抗体)的生物类似药,后者可能需要更多比较性临床试验。FDA将此类药物归类为“仿制药”,其审批路径是缩减新药申请(Abbreviated New Drug Application, ANDA),而非完整的NDA。ANDA的目的是避免重复证明已知的安全性和有效性数据,这些数据已在原药的NDA中确立<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>。</p> +<p>FDA的《仿制药用户费用法案》(GDUFA)和相关指南明确规定,对于相同分子式的药物,新制药方式(如改进的合成路线、晶型优化或更高效的提取工艺)只需证明“生物等效性”(bioequivalence),而非全面临床试验。生物等效性研究通常包括:1)体外溶出测试(in vitro dissolution),评估药物在模拟生理条件下的释放速率;2)人体药代动力学研究(pharmacokinetic studies),在健康志愿者中比较新药与原药的吸收、分布、代谢和排泄曲线(如AUC和Cmax参数的90%置信区间在80%-125%内)。这些研究规模小,通常只需24-36名受试者,持续数周,而非数年<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>。如果新工艺导致杂质谱或稳定性变化,FDA可能要求额外的数据,如加速稳定性测试或毒性评估,但仍无需重复疗效试验,除非有证据显示潜在差异(如新杂质超过许可阈值)<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>。</p> +<p>在实际操作中,这一路径大大降低了成本和时间:ANDA审批平均需10-15个月,费用远低于NDA的数亿美元和数年周期。新制药方式的低廉性往往源于规模化生产或工艺创新,但FDA要求申请者提交化学、制造和控制(CMC)信息,证明新工艺符合良好生产规范(cGMP)。例如,阿司匹林或扑热息痛等经典药物,其多种仿制药版本均通过此路径上市,而无需重新验证其止痛或抗炎疗效<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup>。然而,如果新方式涉及重大变更(如从化学合成转为生物合成,尽管分子式相同),FDA可能视其为“混合型”申请,需要桥接研究来确认等效性。</p> +<p>如果生物等效性未能证明(例如,新工艺导致生物利用度差异>20%),申请将被拒,或需额外桥接试验,这可能增加延误和成本。不过上市后监测(post-marketing surveillance)仍适用,包括不良事件报告(FAERS系统),以捕捉任何罕见差异。</p> +<hr class="footnotes-sep"> +<section class="footnotes"> +<ol class="footnotes-list"> +<li id="fn1" class="footnote-item"><p>U.S. Food and Drug Administration. (2023). <em>Frequently Asked Questions about Generic Drugs</em>. Retrieved from <a href="https://www.fda.gov/drugs/generic-drugs/frequently-asked-questions-about-generic-drugs">https://www.fda.gov/drugs/generic-drugs/frequently-asked-questions-about-generic-drugs</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p> +</li> +<li id="fn2" class="footnote-item"><p>U.S. Food and Drug Administration. (2019). <em>Bioequivalence Studies with Pharmacokinetic Endpoints for Drugs Submitted Under an ANDA</em>. Guidance for Industry. Retrieved from <a href="https://www.fda.gov/regulatory-information/search-fda-guidance-documents/bioequivalence-studies-pharmacokinetic-endpoints-drugs-submitted-under-abbreviated-new-drug">https://www.fda.gov/regulatory-information/search-fda-guidance-documents/bioequivalence-studies-pharmacokinetic-endpoints-drugs-submitted-under-abbreviated-new-drug</a> <a href="#fnref2" class="footnote-backref">↩︎</a></p> +</li> +<li id="fn3" class="footnote-item"><p>U.S. Food and Drug Administration. (2020). <em>Changes to an Approved NDA or ANDA</em>. Guidance for Industry. Retrieved from <a href="https://www.fda.gov/regulatory-information/search-fda-guidance-documents/changes-approved-nda-or-anda">https://www.fda.gov/regulatory-information/search-fda-guidance-documents/changes-approved-nda-or-anda</a> <a href="#fnref3" class="footnote-backref">↩︎</a></p> +</li> +<li id="fn4" class="footnote-item"><p>U.S. Food and Drug Administration. (2023). <em>Approved Drug Products with Therapeutic Equivalence Evaluations (Orange Book)</em>. Retrieved from <a href="https://www.fda.gov/drugs/drug-approvals-and-databases/approved-drug-products-therapeutic-equivalence-evaluations-orange-book">https://www.fda.gov/drugs/drug-approvals-and-databases/approved-drug-products-therapeutic-equivalence-evaluations-orange-book</a> <a href="#fnref4" class="footnote-backref">↩︎</a></p> +</li> +</ol> +</section> +]]></content> + <tags> + <tag>Medicine</tag> + </tags> + </entry> + <entry> <title>Dealing with complex dependency injection in FSharp</title> <url>/2024/10/18/Dealing-with-complex-dependency-injection-in-FSharp/</url> <content><![CDATA[<p>Today, we’re going to cover different ways of encapsulating capabilities and supplying them between functions using functional programming techniques which can be realized in F#.</p> @@ -601,58 +653,6 @@ char sa_data[] socket address (variable-length data)</p> </tags> </entry> <entry> - <title>F# 重载解析问题</title> - <url>/2025/03/30/F-%E9%87%8D%E8%BD%BD%E8%A7%A3%E6%9E%90%E9%97%AE%E9%A2%98/</url> - <content><![CDATA[<p>当在 F# 中使用一些 C# 类库的时候,可能会遇到类似如下的问题:</p> -<figure class="highlight fsharp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">open</span> System</span><br><span class="line"><span class="keyword">open</span> System.Numerics</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> someFunction () <span class="operator">=</span></span><br><span class="line"> <span class="keyword">let</span> v<span class="operator">:</span> Vector<span class="operator"><</span><span class="type">byte</span><span class="operator">></span> <span class="operator">=</span> Vector(<span class="number">0</span>uy)</span><br><span class="line"> <span class="keyword">let</span> array<span class="operator">:</span> <span class="type">byte</span>[] <span class="operator">=</span> [<span class="operator">||</span>]</span><br><span class="line"> <span class="keyword">let</span> span<span class="operator">:</span> Span<span class="operator"><</span><span class="type">byte</span><span class="operator">></span> <span class="operator">=</span> Span(array)</span><br><span class="line"> v.CopyTo(span)</span><br><span class="line">Compiler error</span><br><span class="line"> v.CopyTo(span)</span><br><span class="line"> <span class="operator">----^^^^^^^^^^^^^^</span></span><br><span class="line"></span><br><span class="line"><span class="operator">/</span>home<span class="operator">/</span>muqiu<span class="operator">/</span>stdin(<span class="number">99</span>,<span class="number">5</span>)<span class="operator">:</span> error FS0041<span class="operator">:</span> A unique overload <span class="keyword">for</span> method <span class="symbol">'CopyTo</span>' could <span class="built_in">not</span> be determined based on <span class="keyword">type</span> information prior <span class="keyword">to</span> this program point. A <span class="keyword">type</span> annotation may be needed.</span><br><span class="line"></span><br><span class="line">Known <span class="keyword">type</span> <span class="title class_">of</span> argument<span class="operator">:</span> Span<span class="operator"><</span><span class="type">byte</span><span class="operator">></span></span><br><span class="line"></span><br><span class="line">Candidates<span class="operator">:</span></span><br><span class="line"> <span class="operator">-</span> Vector.CopyTo(destination<span class="operator">:</span> Span<span class="operator"><</span><span class="type">byte</span><span class="operator">></span>) <span class="operator">:</span> <span class="type">unit</span></span><br><span class="line"> <span class="operator">-</span> Vector.CopyTo(destination<span class="operator">:</span> Span<span class="operator"><</span><span class="type">byte</span><span class="operator">></span>) <span class="operator">:</span> <span class="type">unit</span></span><br></pre></td></tr></table></figure> -<p>编译器认为存在两个相同的 <code>CopyTo</code> 重载无法区分。但查阅文档发现,<code>Vector<T></code> 的 <code>CopyTo</code> 方法实际上只有一个匹配的重载(接受 <code>Span<T></code>),这似乎矛盾。</p> -<p>这是因为 F# 编译器处理泛型方法重载的方式:</p> -<ul> -<li>泛型接口继承:<code>Vector<T></code> 可能实现了多个接口,导致编译器看到两个签名相同的 <code>CopyTo</code> 方法(例如通过不同接口继承)。</li> -<li>语言设计差异:F# 缺乏 C# 的 “most concrete” 重载选择机制。当多个重载来自不同继承路径时,F# 不会自动选择最具体的实现,需要显式指引。</li> -</ul> -<p>仅我所知的一种解决方案是通过添加扩展方法显式指引编译器:</p> -<figure class="highlight fsharp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="title class_">Vector</span><span class="operator"><</span><span class="symbol">'T</span><span class="operator">></span> <span class="keyword">with</span></span><br><span class="line"> <span class="keyword">member</span> <span class="keyword">inline</span> this.CopyToByteSpan (span <span class="operator">:</span> Span<span class="operator"><</span><span class="type">byte</span><span class="operator">></span>) <span class="operator">=</span> </span><br><span class="line"> this.CopyTo(span) <span class="comment">// 显式绑定具体重载</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 调用时使用新方法</span></span><br><span class="line">v.CopyToByteSpan(span)</span><br></pre></td></tr></table></figure> -<p>这种方法通过创建具体的类型路径,帮助编译器绕过复杂的重载解析逻辑。</p> -<p>学艺不精,不知道这是不是语言设计的差异,可能 F# 倾向于要求更明确的类型信息以避免意外行为?或许当泛型类型继承多个接口时,有没有可能出现在具体类型中不易察觉的隐式重载冲突?</p> -<h2 id="Refs"><a class="header-anchor" href="#Refs">¶</a>Refs.</h2> -<ul> -<li><a href="https://github.com/fsharp/fslang-suggestions/issues/905">“Most concrete” tiebreaker for generic overloads #905</a></li> -<li><a href="https://github.com/dotnet/fsharp/issues/1647">Fail to resolve to non generic overload. #1647</a></li> -</ul> -]]></content> - <tags> - <tag>Technique</tag> - </tags> - </entry> - <entry> - <title>FDA 仿制药流程</title> - <url>/2025/11/29/FDA-%E4%BB%BF%E5%88%B6%E8%8D%AF%E6%B5%81%E7%A8%8B/</url> - <content><![CDATA[<p>#药物研发</p> -<p>在FDA监管的药物研发、临床试验和上市流程中,当一个已上市药物(通常指创新药,通过新药申请NDA获得批准)出现一种更低廉的制药方式,但其活性成分的分子式保持完全相同时,这种情况本质上涉及仿制药(generic drug)的开发或工艺优化申请,而非从零开始的全新药物研发。简单来说,不需要重新进行完整的临床试验(即I-III期的大规模人体试验),但必须通过简化的监管路径证明新制药方式生产的药物与原药在质量、安全性和疗效上等效。</p> -<p>首先,理解核心前提:分子式完全相同意味着活性成分(active pharmaceutical ingredient, API)是相同的化学实体,例如从专利保护的原研药转向无专利保护的仿制药生产。这不同于生物制品(如单克隆抗体)的生物类似药,后者可能需要更多比较性临床试验。FDA将此类药物归类为“仿制药”,其审批路径是缩减新药申请(Abbreviated New Drug Application, ANDA),而非完整的NDA。ANDA的目的是避免重复证明已知的安全性和有效性数据,这些数据已在原药的NDA中确立<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>。</p> -<p>FDA的《仿制药用户费用法案》(GDUFA)和相关指南明确规定,对于相同分子式的药物,新制药方式(如改进的合成路线、晶型优化或更高效的提取工艺)只需证明“生物等效性”(bioequivalence),而非全面临床试验。生物等效性研究通常包括:1)体外溶出测试(in vitro dissolution),评估药物在模拟生理条件下的释放速率;2)人体药代动力学研究(pharmacokinetic studies),在健康志愿者中比较新药与原药的吸收、分布、代谢和排泄曲线(如AUC和Cmax参数的90%置信区间在80%-125%内)。这些研究规模小,通常只需24-36名受试者,持续数周,而非数年<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>。如果新工艺导致杂质谱或稳定性变化,FDA可能要求额外的数据,如加速稳定性测试或毒性评估,但仍无需重复疗效试验,除非有证据显示潜在差异(如新杂质超过许可阈值)<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>。</p> -<p>在实际操作中,这一路径大大降低了成本和时间:ANDA审批平均需10-15个月,费用远低于NDA的数亿美元和数年周期。新制药方式的低廉性往往源于规模化生产或工艺创新,但FDA要求申请者提交化学、制造和控制(CMC)信息,证明新工艺符合良好生产规范(cGMP)。例如,阿司匹林或扑热息痛等经典药物,其多种仿制药版本均通过此路径上市,而无需重新验证其止痛或抗炎疗效<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup>。然而,如果新方式涉及重大变更(如从化学合成转为生物合成,尽管分子式相同),FDA可能视其为“混合型”申请,需要桥接研究来确认等效性。</p> -<p>如果生物等效性未能证明(例如,新工艺导致生物利用度差异>20%),申请将被拒,或需额外桥接试验,这可能增加延误和成本。不过上市后监测(post-marketing surveillance)仍适用,包括不良事件报告(FAERS系统),以捕捉任何罕见差异。</p> -<hr class="footnotes-sep"> -<section class="footnotes"> -<ol class="footnotes-list"> -<li id="fn1" class="footnote-item"><p>U.S. Food and Drug Administration. (2023). <em>Frequently Asked Questions about Generic Drugs</em>. Retrieved from <a href="https://www.fda.gov/drugs/generic-drugs/frequently-asked-questions-about-generic-drugs">https://www.fda.gov/drugs/generic-drugs/frequently-asked-questions-about-generic-drugs</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p> -</li> -<li id="fn2" class="footnote-item"><p>U.S. Food and Drug Administration. (2019). <em>Bioequivalence Studies with Pharmacokinetic Endpoints for Drugs Submitted Under an ANDA</em>. Guidance for Industry. Retrieved from <a href="https://www.fda.gov/regulatory-information/search-fda-guidance-documents/bioequivalence-studies-pharmacokinetic-endpoints-drugs-submitted-under-abbreviated-new-drug">https://www.fda.gov/regulatory-information/search-fda-guidance-documents/bioequivalence-studies-pharmacokinetic-endpoints-drugs-submitted-under-abbreviated-new-drug</a> <a href="#fnref2" class="footnote-backref">↩︎</a></p> -</li> -<li id="fn3" class="footnote-item"><p>U.S. Food and Drug Administration. (2020). <em>Changes to an Approved NDA or ANDA</em>. Guidance for Industry. Retrieved from <a href="https://www.fda.gov/regulatory-information/search-fda-guidance-documents/changes-approved-nda-or-anda">https://www.fda.gov/regulatory-information/search-fda-guidance-documents/changes-approved-nda-or-anda</a> <a href="#fnref3" class="footnote-backref">↩︎</a></p> -</li> -<li id="fn4" class="footnote-item"><p>U.S. Food and Drug Administration. (2023). <em>Approved Drug Products with Therapeutic Equivalence Evaluations (Orange Book)</em>. Retrieved from <a href="https://www.fda.gov/drugs/drug-approvals-and-databases/approved-drug-products-therapeutic-equivalence-evaluations-orange-book">https://www.fda.gov/drugs/drug-approvals-and-databases/approved-drug-products-therapeutic-equivalence-evaluations-orange-book</a> <a href="#fnref4" class="footnote-backref">↩︎</a></p> -</li> -</ol> -</section> -]]></content> - <tags> - <tag>Medicine</tag> - </tags> - </entry> - <entry> <title>FHIR 记录患者的用药记录</title> <url>/2025/12/29/FHIR-%E8%AE%B0%E5%BD%95%E6%82%A3%E8%80%85%E7%9A%84%E7%94%A8%E8%8D%AF%E8%AE%B0%E5%BD%95/</url> <content><![CDATA[<p>FHIR 中的“用药记录”并非一个单一概念,而是一个由不同角色、不同时间点和不同意图构成的连续工作流。</p> @@ -838,6 +838,21 @@ Given the impracticality of prolonged hospitalizations and advancing technology </tags> </entry> <entry> + <title>什么是 G⁺</title> + <url>/2025/10/19/Gram-stain/</url> + <content><![CDATA[<p><strong>G⁺(Gram‑positive)</strong> 是细菌在 <strong>Gram 染色</strong>(Gram stain)技术下呈现的染色特征。Gram 染色是一种经典的显微镜染色方法,依据细菌细胞壁结构的差异,将细菌分为两大类:<strong>Gram‑positive(G⁺)</strong> 和 <strong>Gram‑negative(G⁻)</strong>。在染色程序中,细菌首先被结晶紫染料染色,随后加入碘液形成复合物,接着用酒精或丙酮脱色,最后用沙黄或复红复染。若细菌的细胞壁能够 <strong>保留结晶紫‑碘复合物</strong>,则在显微镜下呈现紫蓝色,这类细菌即被归类为 <strong>Gram‑positive(G⁺)</strong>;若细胞壁结构不保留复合物而被脱色,随后被沙黄染成红色,则为 <strong>Gram‑negative(G⁻)</strong>。</p> +<p>G⁺ 细菌的细胞壁外层主要由 <strong>厚度约 20–80 nm 的肽聚糖(peptidoglycan)</strong> 组成,这层结构密集且富含交联的糖肽链,能够在脱色步骤中形成物理屏障,阻止酒精/丙酮的渗透,从而保留结晶紫‑碘复合物。</p> +<p>与 G⁻ 细菌不同,G⁺ 细菌没有外膜(outer membrane)和脂多糖(LPS)层。外膜的缺失使得脱色剂难以进入细胞内部,进一步加强了紫色的保留。</p> +<p>某些 G⁺ 细菌在细胞壁上表达的酶(如酸性磷酸酶)可与染料形成更为稳定的复合物,进一步提升染色的持久性。</p> +<p>在呼吸道痰涂片中出现 <strong>“有荚膜的双球菌(capsulated diplococci)”</strong>,最典型的病原体是 <strong>肺炎链球菌(Streptococcus pneumoniae)</strong>。它是一种 <strong>革兰氏阳性(G⁺)</strong>、<strong>α‑hemolytic</strong>、<strong>嗜血清素</strong> 的双球菌,表面包裹有厚厚的多糖荚膜,正是该荚膜赋予它在显微镜下呈现明显的荧光或“胶状”外观。</p> +<p>在痰涂片或血培养的显微镜检查中,观察到 G⁺ 双球菌提示肺炎链球菌感染的可能性较大,帮助临床医生在培养结果出来前就能启动经验性抗生素治疗(如 β‑lactam 类药物)。</p> +<p>肺炎链球菌的荚膜多型性(> 90 种血清型)是疫苗设计的核心依据,同时也影响抗体介导的吞噬作用。了解其 G⁺ 特性有助于解释为何某些血清型(如 3 型)在免疫逃逸方面更具优势。</p> +]]></content> + <tags> + <tag>Medicine</tag> + </tags> + </entry> + <entry> <title>G-Machine</title> <url>/2024/01/23/G-Machine/</url> <content><![CDATA[<ul> @@ -864,21 +879,6 @@ Given the impracticality of prolonged hospitalizations and advancing technology </tags> </entry> <entry> - <title>什么是 G⁺</title> - <url>/2025/10/19/Gram-stain/</url> - <content><![CDATA[<p><strong>G⁺(Gram‑positive)</strong> 是细菌在 <strong>Gram 染色</strong>(Gram stain)技术下呈现的染色特征。Gram 染色是一种经典的显微镜染色方法,依据细菌细胞壁结构的差异,将细菌分为两大类:<strong>Gram‑positive(G⁺)</strong> 和 <strong>Gram‑negative(G⁻)</strong>。在染色程序中,细菌首先被结晶紫染料染色,随后加入碘液形成复合物,接着用酒精或丙酮脱色,最后用沙黄或复红复染。若细菌的细胞壁能够 <strong>保留结晶紫‑碘复合物</strong>,则在显微镜下呈现紫蓝色,这类细菌即被归类为 <strong>Gram‑positive(G⁺)</strong>;若细胞壁结构不保留复合物而被脱色,随后被沙黄染成红色,则为 <strong>Gram‑negative(G⁻)</strong>。</p> -<p>G⁺ 细菌的细胞壁外层主要由 <strong>厚度约 20–80 nm 的肽聚糖(peptidoglycan)</strong> 组成,这层结构密集且富含交联的糖肽链,能够在脱色步骤中形成物理屏障,阻止酒精/丙酮的渗透,从而保留结晶紫‑碘复合物。</p> -<p>与 G⁻ 细菌不同,G⁺ 细菌没有外膜(outer membrane)和脂多糖(LPS)层。外膜的缺失使得脱色剂难以进入细胞内部,进一步加强了紫色的保留。</p> -<p>某些 G⁺ 细菌在细胞壁上表达的酶(如酸性磷酸酶)可与染料形成更为稳定的复合物,进一步提升染色的持久性。</p> -<p>在呼吸道痰涂片中出现 <strong>“有荚膜的双球菌(capsulated diplococci)”</strong>,最典型的病原体是 <strong>肺炎链球菌(Streptococcus pneumoniae)</strong>。它是一种 <strong>革兰氏阳性(G⁺)</strong>、<strong>α‑hemolytic</strong>、<strong>嗜血清素</strong> 的双球菌,表面包裹有厚厚的多糖荚膜,正是该荚膜赋予它在显微镜下呈现明显的荧光或“胶状”外观。</p> -<p>在痰涂片或血培养的显微镜检查中,观察到 G⁺ 双球菌提示肺炎链球菌感染的可能性较大,帮助临床医生在培养结果出来前就能启动经验性抗生素治疗(如 β‑lactam 类药物)。</p> -<p>肺炎链球菌的荚膜多型性(> 90 种血清型)是疫苗设计的核心依据,同时也影响抗体介导的吞噬作用。了解其 G⁺ 特性有助于解释为何某些血清型(如 3 型)在免疫逃逸方面更具优势。</p> -]]></content> - <tags> - <tag>Medicine</tag> - </tags> - </entry> - <entry> <title>多人协作系统中的实现策略 (CRDT,锁等实现方案)</title> <url>/2025/05/08/Multiplayer-Collaborative-Systems-tips/</url> <content><![CDATA[<p>最近碰到一块业务:在系统中可以存在多个用户同时对某个项目信息进行编辑,这种多人协作的场景挺有意思的,不过在我们的业务中,并不需要实时协作,只需要保证不会出错就行,话虽如此,但也可以探索一下实时协作的实现方案,防止老年痴呆。</p> @@ -1102,40 +1102,6 @@ await prisma.$transaction(async tx => { </tags> </entry> <entry> - <title>OCaml Core.Int.pow 的实现</title> - <url>/2023/10/21/OCaml-Core-Int-pow-%E7%9A%84%E5%AE%9E%E7%8E%B0/</url> - <content><![CDATA[<p>Core内部直接引用的Base的实现, Base的实现在 <a href="https://github.com/janestreet/base/blob/494a0876168d24cda695cbb9a3d86ad8d1eb97d8/src/int_math.ml#L11-L18">src/int_math.ml</a> 中:</p> -<figure class="highlight ocaml"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> int_pow base exponent =</span><br><span class="line"> <span class="keyword">if</span> exponent < <span class="number">0</span> <span class="keyword">then</span> negative_exponent <span class="literal">()</span>;</span><br><span class="line"> <span class="keyword">if</span> abs base > <span class="number">1</span></span><br><span class="line"> && (exponent > <span class="number">63</span></span><br><span class="line"> || abs base > <span class="type">Pow_overflow_bounds</span>.int_positive_overflow_bounds.(exponent))</span><br><span class="line"> <span class="keyword">then</span> overflow <span class="literal">()</span>;</span><br><span class="line"> int_math_int_pow base exponent</span><br><span class="line">;;</span><br></pre></td></tr></table></figure> -<p>其中 <code>int_math_int_pow()</code> 由 C 实现:</p> -<figure class="highlight ocaml"><table><tr><td class="code"><pre><span class="line"><span class="keyword">external</span> int_math_int_pow : <span class="built_in">int</span> -> <span class="built_in">int</span> -> <span class="built_in">int</span> = <span class="string">"Base_int_math_int_pow_stub"</span> [@@noalloc]</span><br></pre></td></tr></table></figure> -<p>其实现在 <a href="https://github.com/janestreet/base/blob/494a0876168d24cda695cbb9a3d86ad8d1eb97d8/src/int_math_stubs.c#L56-L92">src/int_math_stubs.c</a> 中:</p> -<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">int64_t</span> <span class="title function_">int_pow</span><span class="params">(<span class="type">int64_t</span> base, <span class="type">int64_t</span> exponent)</span> {</span><br><span class="line"> <span class="type">int64_t</span> ret = <span class="number">1</span>;</span><br><span class="line"> <span class="type">int64_t</span> mul[<span class="number">4</span>];</span><br><span class="line"> mul[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line"> mul[<span class="number">1</span>] = base;</span><br><span class="line"> mul[<span class="number">3</span>] = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (exponent != <span class="number">0</span>) {</span><br><span class="line"> mul[<span class="number">1</span>] *= mul[<span class="number">3</span>];</span><br><span class="line"> mul[<span class="number">2</span>] = mul[<span class="number">1</span>] * mul[<span class="number">1</span>];</span><br><span class="line"> mul[<span class="number">3</span>] = mul[<span class="number">2</span>] * mul[<span class="number">1</span>];</span><br><span class="line"> ret *= mul[exponent & <span class="number">3</span>];</span><br><span class="line"> exponent >>= <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line">}</span><br></pre></td></tr></table></figure> -<p>这是一个四分快速幂的实现,它是二分快速幂的一种变种。二分快速幂将指数分为两部分,然后递归地计算每一部分的结果。<br> -而四分快速幂将指数分为四部分,然后递归地计算每一部分的结果。<br> -这里通过将指数右移2位(相当于除以4)和使用位与操作来实现,进一步减少了乘法次数。</p> -<p>主要步骤:</p> -<ul> -<li>初始化返回值ret为1,和一个包含4个元素的数组mul, mul[0]和mul[3]被初始化为1, mul[1]被初始化为基数</li> -<li>当指数不为0时,执行循环, 在每次循环中,首先更新mul数组的值 -<blockquote> -<p>mul[1]是基数和mul[3]的乘积,mul[2]是mul[1]的平方,mul[3]是mul[2]和基数的乘积</p> -</blockquote> -</li> -<li>然后,将ret乘以mul数组中的一个元素, 这个元素的索引是指数和3的位与运算的结果 -<blockquote> -<p>这样做的目的是为了选择正确的乘数,因为指数被分解为4的倍数</p> -</blockquote> -</li> -<li>最后,将指数右移2位,相当于将指数除以4</li> -<li>当指数变为0时,循环结束,返回ret</li> -</ul> -<p>这个实现的优点是它可以在对数时间内计算出幂运算,而且每次循环只需要4次乘法。这比标准的二分快速幂算法需要的乘法次数更少。</p> -]]></content> - <tags> - <tag>Technique</tag> - </tags> - </entry> - <entry> <title>OCaml News 2024-1</title> <url>/2024/01/08/OCaml-News-2024-1/</url> <content><![CDATA[<h2 id="News"><a class="header-anchor" href="#News">¶</a>News</h2> @@ -1199,6 +1165,40 @@ await prisma.$transaction(async tx => { </tags> </entry> <entry> + <title>OCaml Core.Int.pow 的实现</title> + <url>/2023/10/21/OCaml-Core-Int-pow-%E7%9A%84%E5%AE%9E%E7%8E%B0/</url> + <content><![CDATA[<p>Core内部直接引用的Base的实现, Base的实现在 <a href="https://github.com/janestreet/base/blob/494a0876168d24cda695cbb9a3d86ad8d1eb97d8/src/int_math.ml#L11-L18">src/int_math.ml</a> 中:</p> +<figure class="highlight ocaml"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> int_pow base exponent =</span><br><span class="line"> <span class="keyword">if</span> exponent < <span class="number">0</span> <span class="keyword">then</span> negative_exponent <span class="literal">()</span>;</span><br><span class="line"> <span class="keyword">if</span> abs base > <span class="number">1</span></span><br><span class="line"> && (exponent > <span class="number">63</span></span><br><span class="line"> || abs base > <span class="type">Pow_overflow_bounds</span>.int_positive_overflow_bounds.(exponent))</span><br><span class="line"> <span class="keyword">then</span> overflow <span class="literal">()</span>;</span><br><span class="line"> int_math_int_pow base exponent</span><br><span class="line">;;</span><br></pre></td></tr></table></figure> +<p>其中 <code>int_math_int_pow()</code> 由 C 实现:</p> +<figure class="highlight ocaml"><table><tr><td class="code"><pre><span class="line"><span class="keyword">external</span> int_math_int_pow : <span class="built_in">int</span> -> <span class="built_in">int</span> -> <span class="built_in">int</span> = <span class="string">"Base_int_math_int_pow_stub"</span> [@@noalloc]</span><br></pre></td></tr></table></figure> +<p>其实现在 <a href="https://github.com/janestreet/base/blob/494a0876168d24cda695cbb9a3d86ad8d1eb97d8/src/int_math_stubs.c#L56-L92">src/int_math_stubs.c</a> 中:</p> +<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">int64_t</span> <span class="title function_">int_pow</span><span class="params">(<span class="type">int64_t</span> base, <span class="type">int64_t</span> exponent)</span> {</span><br><span class="line"> <span class="type">int64_t</span> ret = <span class="number">1</span>;</span><br><span class="line"> <span class="type">int64_t</span> mul[<span class="number">4</span>];</span><br><span class="line"> mul[<span class="number">0</span>] = <span class="number">1</span>;</span><br><span class="line"> mul[<span class="number">1</span>] = base;</span><br><span class="line"> mul[<span class="number">3</span>] = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (exponent != <span class="number">0</span>) {</span><br><span class="line"> mul[<span class="number">1</span>] *= mul[<span class="number">3</span>];</span><br><span class="line"> mul[<span class="number">2</span>] = mul[<span class="number">1</span>] * mul[<span class="number">1</span>];</span><br><span class="line"> mul[<span class="number">3</span>] = mul[<span class="number">2</span>] * mul[<span class="number">1</span>];</span><br><span class="line"> ret *= mul[exponent & <span class="number">3</span>];</span><br><span class="line"> exponent >>= <span class="number">2</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> ret;</span><br><span class="line">}</span><br></pre></td></tr></table></figure> +<p>这是一个四分快速幂的实现,它是二分快速幂的一种变种。二分快速幂将指数分为两部分,然后递归地计算每一部分的结果。<br> +而四分快速幂将指数分为四部分,然后递归地计算每一部分的结果。<br> +这里通过将指数右移2位(相当于除以4)和使用位与操作来实现,进一步减少了乘法次数。</p> +<p>主要步骤:</p> +<ul> +<li>初始化返回值ret为1,和一个包含4个元素的数组mul, mul[0]和mul[3]被初始化为1, mul[1]被初始化为基数</li> +<li>当指数不为0时,执行循环, 在每次循环中,首先更新mul数组的值 +<blockquote> +<p>mul[1]是基数和mul[3]的乘积,mul[2]是mul[1]的平方,mul[3]是mul[2]和基数的乘积</p> +</blockquote> +</li> +<li>然后,将ret乘以mul数组中的一个元素, 这个元素的索引是指数和3的位与运算的结果 +<blockquote> +<p>这样做的目的是为了选择正确的乘数,因为指数被分解为4的倍数</p> +</blockquote> +</li> +<li>最后,将指数右移2位,相当于将指数除以4</li> +<li>当指数变为0时,循环结束,返回ret</li> +</ul> +<p>这个实现的优点是它可以在对数时间内计算出幂运算,而且每次循环只需要4次乘法。这比标准的二分快速幂算法需要的乘法次数更少。</p> +]]></content> + <tags> + <tag>Technique</tag> + </tags> + </entry> + <entry> <title>OCaml News 2024-2</title> <url>/2024/01/18/OCaml-News-2024-2/</url> <content><![CDATA[<h2 id="News"><a class="header-anchor" href="#News">¶</a>News</h2> @@ -2043,6 +2043,28 @@ await prisma.$transaction(async tx => { </tags> </entry> <entry> + <title>Radiographic Absence of the Left Humeral Head</title> + <url>/2023/10/25/Radiographic-Absence-of-the-Left-Humeral-Head/</url> + <content><![CDATA[<p>A 67-year-old man with thoracolumbar scoliosis, poor mobility, and history of frequent falls presented to the emergency department with 2 months of left shoulder pain, stiffness and reduced range of motion, and numbness and paresthesias in his left upper extremity.</p> +<p>Ten years prior to presentation, he underwent surgical decompression for syringomyelia.</p> +<p>A magnetic resonance imaging (MRI) scan of his cervical and thoracic spine performed 2 years prior to presentation revealed a recurrent syrinx extending from Cl C1 to T11, which was not resected because it did not cause symptoms at that time.</p> +<p>On physical examination, he had mild tenderness to palpation and reduced range of motion of the left shoulder with abduction and flexion limited to 120° (normal range of motion, 180°).</p> +<p>The left scapular muscles were atrophic, and pain and temperature sensation were reduced in his proximal left arm, and dorsal aspect of his left shoulder.</p> +<p>His complete blood cell count, serum glucose levels, C-reactive protein levels, and erythrocyte sedimentation rate were normal.</p> +<p>Results of tests for rheumatoid factor and antinuclear antibody were negative.</p> +<p>Left shoulder radiograph showed complete absence of the left humeral head and a well-demarcated smooth osseous margin of the proximal humerus with associated soft tissue swelling and periarticular calcification.</p> +<p>A chest radiograph taken 2 years prior revealed a normal left shoulder joint. The patient was hospitalized for further evaluation and treatment.</p> +<ul> +<li><a href="/papers/Radiographic%20Absence%20of%20the%20Left%20Humeral%20Head.pdf">报告原档</a></li> +</ul> +<p>这里提到了一种罕见的进展性疾病, 叫Charcot肩, 是一种神经性关节病变,特点是关节迅速被破坏和相关软组织肿胀。患者通常表现为肩部逐渐增大的肿胀,可能伴有疼痛或无痛,肩部无力和活动范围减少。<br> +大约80%的Charcot肩患者有脊髓空洞症, 表现为脊髓中的液体充满腔体</p> +]]></content> + <tags> + <tag>Medicine</tag> + </tags> + </entry> + <entry> <title>Prisma 关系型数据库的 Self-relations</title> <url>/2025/02/18/Prisma-%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84-Self-relations/</url> <content><![CDATA[<blockquote> @@ -2111,28 +2133,6 @@ A relation table (also sometimes called a <em>JOIN</em>, <em>link</em> or <em>pi </tags> </entry> <entry> - <title>Radiographic Absence of the Left Humeral Head</title> - <url>/2023/10/25/Radiographic-Absence-of-the-Left-Humeral-Head/</url> - <content><![CDATA[<p>A 67-year-old man with thoracolumbar scoliosis, poor mobility, and history of frequent falls presented to the emergency department with 2 months of left shoulder pain, stiffness and reduced range of motion, and numbness and paresthesias in his left upper extremity.</p> -<p>Ten years prior to presentation, he underwent surgical decompression for syringomyelia.</p> -<p>A magnetic resonance imaging (MRI) scan of his cervical and thoracic spine performed 2 years prior to presentation revealed a recurrent syrinx extending from Cl C1 to T11, which was not resected because it did not cause symptoms at that time.</p> -<p>On physical examination, he had mild tenderness to palpation and reduced range of motion of the left shoulder with abduction and flexion limited to 120° (normal range of motion, 180°).</p> -<p>The left scapular muscles were atrophic, and pain and temperature sensation were reduced in his proximal left arm, and dorsal aspect of his left shoulder.</p> -<p>His complete blood cell count, serum glucose levels, C-reactive protein levels, and erythrocyte sedimentation rate were normal.</p> -<p>Results of tests for rheumatoid factor and antinuclear antibody were negative.</p> -<p>Left shoulder radiograph showed complete absence of the left humeral head and a well-demarcated smooth osseous margin of the proximal humerus with associated soft tissue swelling and periarticular calcification.</p> -<p>A chest radiograph taken 2 years prior revealed a normal left shoulder joint. The patient was hospitalized for further evaluation and treatment.</p> -<ul> -<li><a href="/papers/Radiographic%20Absence%20of%20the%20Left%20Humeral%20Head.pdf">报告原档</a></li> -</ul> -<p>这里提到了一种罕见的进展性疾病, 叫Charcot肩, 是一种神经性关节病变,特点是关节迅速被破坏和相关软组织肿胀。患者通常表现为肩部逐渐增大的肿胀,可能伴有疼痛或无痛,肩部无力和活动范围减少。<br> -大约80%的Charcot肩患者有脊髓空洞症, 表现为脊髓中的液体充满腔体</p> -]]></content> - <tags> - <tag>Medicine</tag> - </tags> - </entry> - <entry> <title>Repository pattern in Typescript</title> <url>/2025/02/19/Repository-pattern-in-Typescript/</url> <content><![CDATA[<blockquote> @@ -2275,6 +2275,22 @@ A relation table (also sometimes called a <em>JOIN</em>, <em>link</em> or <em>pi </tags> </entry> <entry> + <title>Rust 闭包 lifetime may not live long enough 问题</title> + <url>/2023/05/24/Rust-%E9%97%AD%E5%8C%85-lifetime-may-not-live-long-enough-%E9%97%AE%E9%A2%98/</url> + <content><![CDATA[<p>代码:</p> +<figure class="highlight rust"><table><tr><td class="code"><pre><span class="line">...</span><br><span class="line"> <span class="keyword">fn</span> <span class="title function_">handlers</span>(<span class="keyword">self</span>) <span class="punctuation">-></span> crate::server::request::Handlers {</span><br><span class="line"> <span class="built_in">vec!</span>[(</span><br><span class="line"> <span class="string">"/tree"</span>,</span><br><span class="line"> routing::<span class="title function_ invoke__">get</span>(<span class="keyword">move</span> || <span class="keyword">async</span> {</span><br><span class="line"> (</span><br><span class="line"> StatusCode::OK,</span><br><span class="line"> <span class="title function_ invoke__">Json</span>(json!(<span class="keyword">self</span>.<span class="title function_ invoke__">clone</span>().<span class="title function_ invoke__">tree</span>(<span class="keyword">self</span>.<span class="title function_ invoke__">clone</span>().root))),</span><br><span class="line"> )</span><br><span class="line"> }),</span><br><span class="line"> )]</span><br><span class="line"> }</span><br><span class="line">...</span><br></pre></td></tr></table></figure> +<p>编译错误:</p> +<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">error: lifetime may not live long enough</span><br><span class="line"> --> src/storage/filesystem/mod.rs:45:34</span><br><span class="line"> |</span><br><span class="line">45 | routing::get(move || async {</span><br><span class="line"> | __________________________-------_^</span><br><span class="line"> | | | |</span><br><span class="line"> | | | return type of closure `{async block@src/storage/filesystem/mod.rs:45:34: 50:14}` contains a lifetime `'2`</span><br><span class="line"> | | lifetime `'1` represents this closure's body</span><br><span class="line">46 | | (</span><br><span class="line">47 | | StatusCode::OK,</span><br><span class="line">48 | | Json(json!(self.clone().tree(self.clone().root))),</span><br><span class="line">49 | | )</span><br><span class="line">50 | | }),</span><br><span class="line"> | |_____________^ returning this value requires that `'1` must outlive `'2`</span><br><span class="line"> |</span><br><span class="line"> = note: closure implements `Fn`, so references to captured variables can't escape the closure</span><br></pre></td></tr></table></figure> +<p>这是因为 <code>handlers</code> 里面的闭包捕获了一个引用,并且尝试返回一个包含该引用的值导致的。</p> +<p>细说就是:闭包内部使用了 <code>self.clone()</code> 来获取一个新的实例,然后在异步块中返回一个 JSON 对象,这个 JSON 对象依赖于 <code>self.tree()</code> 的结果。因为闭包捕获了 <code>self</code> 的引用,所以它必须保证 <code>self</code> 在闭包执行完毕后仍然有效。</p> +<p>解决这个问题的思路是:确保闭包中的所有引用都在闭包执行完毕之前就不再被使用。<br> +就是说,要将闭包的作用域限制在一个更短的生命周期内,或者使用其他方式来避免闭包捕获长期存在的引用:</p> +<figure class="highlight rust"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">...</span><br><span class="line"> <span class="keyword">fn</span> <span class="title function_">handlers</span>(<span class="keyword">self</span>) <span class="punctuation">-></span> crate::server::request::Handlers {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">tree</span> = json!(<span class="keyword">self</span>.<span class="title function_ invoke__">clone</span>().<span class="title function_ invoke__">tree</span>(<span class="keyword">self</span>.<span class="title function_ invoke__">clone</span>().root));</span><br><span class="line"> <span class="built_in">vec!</span>[(</span><br><span class="line"> <span class="string">"/tree"</span>,</span><br><span class="line"> routing::<span class="title function_ invoke__">get</span>(<span class="keyword">move</span> || <span class="keyword">async</span> { (StatusCode::OK, <span class="title function_ invoke__">Json</span>(tree)) }),</span><br><span class="line"> )]</span><br><span class="line"> }</span><br><span class="line">...</span><br></pre></td></tr></table></figure>]]></content> + <tags> + <tag>Technique</tag> + </tags> + </entry> + <entry> <title>Rust 虚表布局规则介绍</title> <url>/2023/05/01/Rust-%E8%99%9A%E8%A1%A8%E5%B8%83%E5%B1%80%E8%A7%84%E5%88%99%E4%BB%8B%E7%BB%8D/</url> <content><![CDATA[<p>在 Rust 中,一个指向未知大小对象(!Sized)的引用或指针被实现为一个由两个 usize 大小的域构成的胖指针。这两个域中,其中一个域保存了被引用或被指向的对象的地址,另一个域保存了一个名为 metadata 的数据。对于 slice 的引用或指针来说,其 metadata 为 slice 的长度。对于 trait object 的引用或指针来说,其 metadata 为虚表(vtable)地址。与 C++ 虚表类似,Rust 虚表的存在使得诸多动态语言特性得以实现,例如动态派发(dynamic dispatch)、向上转换(upcasting)、向下转换(downcast)等。本文将对 Rust 中虚表的布局规则进行简要介绍,并在此过程中对 Rust 中若干动态特性的实现方法进行简要介绍。</p> @@ -2321,22 +2337,6 @@ A relation table (also sometimes called a <em>JOIN</em>, <em>link</em> or <em>pi </tags> </entry> <entry> - <title>Rust 闭包 lifetime may not live long enough 问题</title> - <url>/2023/05/24/Rust-%E9%97%AD%E5%8C%85-lifetime-may-not-live-long-enough-%E9%97%AE%E9%A2%98/</url> - <content><![CDATA[<p>代码:</p> -<figure class="highlight rust"><table><tr><td class="code"><pre><span class="line">...</span><br><span class="line"> <span class="keyword">fn</span> <span class="title function_">handlers</span>(<span class="keyword">self</span>) <span class="punctuation">-></span> crate::server::request::Handlers {</span><br><span class="line"> <span class="built_in">vec!</span>[(</span><br><span class="line"> <span class="string">"/tree"</span>,</span><br><span class="line"> routing::<span class="title function_ invoke__">get</span>(<span class="keyword">move</span> || <span class="keyword">async</span> {</span><br><span class="line"> (</span><br><span class="line"> StatusCode::OK,</span><br><span class="line"> <span class="title function_ invoke__">Json</span>(json!(<span class="keyword">self</span>.<span class="title function_ invoke__">clone</span>().<span class="title function_ invoke__">tree</span>(<span class="keyword">self</span>.<span class="title function_ invoke__">clone</span>().root))),</span><br><span class="line"> )</span><br><span class="line"> }),</span><br><span class="line"> )]</span><br><span class="line"> }</span><br><span class="line">...</span><br></pre></td></tr></table></figure> -<p>编译错误:</p> -<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">error: lifetime may not live long enough</span><br><span class="line"> --> src/storage/filesystem/mod.rs:45:34</span><br><span class="line"> |</span><br><span class="line">45 | routing::get(move || async {</span><br><span class="line"> | __________________________-------_^</span><br><span class="line"> | | | |</span><br><span class="line"> | | | return type of closure `{async block@src/storage/filesystem/mod.rs:45:34: 50:14}` contains a lifetime `'2`</span><br><span class="line"> | | lifetime `'1` represents this closure's body</span><br><span class="line">46 | | (</span><br><span class="line">47 | | StatusCode::OK,</span><br><span class="line">48 | | Json(json!(self.clone().tree(self.clone().root))),</span><br><span class="line">49 | | )</span><br><span class="line">50 | | }),</span><br><span class="line"> | |_____________^ returning this value requires that `'1` must outlive `'2`</span><br><span class="line"> |</span><br><span class="line"> = note: closure implements `Fn`, so references to captured variables can't escape the closure</span><br></pre></td></tr></table></figure> -<p>这是因为 <code>handlers</code> 里面的闭包捕获了一个引用,并且尝试返回一个包含该引用的值导致的。</p> -<p>细说就是:闭包内部使用了 <code>self.clone()</code> 来获取一个新的实例,然后在异步块中返回一个 JSON 对象,这个 JSON 对象依赖于 <code>self.tree()</code> 的结果。因为闭包捕获了 <code>self</code> 的引用,所以它必须保证 <code>self</code> 在闭包执行完毕后仍然有效。</p> -<p>解决这个问题的思路是:确保闭包中的所有引用都在闭包执行完毕之前就不再被使用。<br> -就是说,要将闭包的作用域限制在一个更短的生命周期内,或者使用其他方式来避免闭包捕获长期存在的引用:</p> -<figure class="highlight rust"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">...</span><br><span class="line"> <span class="keyword">fn</span> <span class="title function_">handlers</span>(<span class="keyword">self</span>) <span class="punctuation">-></span> crate::server::request::Handlers {</span><br><span class="line"> <span class="keyword">let</span> <span class="variable">tree</span> = json!(<span class="keyword">self</span>.<span class="title function_ invoke__">clone</span>().<span class="title function_ invoke__">tree</span>(<span class="keyword">self</span>.<span class="title function_ invoke__">clone</span>().root));</span><br><span class="line"> <span class="built_in">vec!</span>[(</span><br><span class="line"> <span class="string">"/tree"</span>,</span><br><span class="line"> routing::<span class="title function_ invoke__">get</span>(<span class="keyword">move</span> || <span class="keyword">async</span> { (StatusCode::OK, <span class="title function_ invoke__">Json</span>(tree)) }),</span><br><span class="line"> )]</span><br><span class="line"> }</span><br><span class="line">...</span><br></pre></td></tr></table></figure>]]></content> - <tags> - <tag>Technique</tag> - </tags> - </entry> - <entry> <title>Scala 3 (Capture Checking)</title> <url>/2025/03/25/Scala-3-Capture-Checking/</url> <content><![CDATA[<blockquote> @@ -3267,6 +3267,58 @@ A relation table (also sometimes called a <em>JOIN</em>, <em>link</em> or <em>pi </tags> </entry> <entry> + <title>NestJS 中 enableImplicitConversion 与 @Transform 的冲突</title> + <url>/2025/08/13/nestjs-enableImplicitConversion-and-transform/</url> + <content><![CDATA[<p>在 NestJS 生态中,<code>class-validator</code> 和 <code>class-transformer</code> 这两个库提供了以声明式的方式对 DTO 进行验证和转换。然而在处理布尔值时,如果在全局验证管道或仅仅是在局部同时开启了 <code>enableImplicitConversion</code>,可能会引入一个极其隐蔽且违反直觉的 Bug:前端传过来的布尔值恒为 true。</p> +<h2 id="一个简单的筛选功能"><a class="header-anchor" href="#一个简单的筛选功能">¶</a>一个简单的筛选功能</h2> +<p>假设正在开发一个电子商务平台的 API,需要实现一个产品列表的筛选功能。希望能够根据产品是否有库存 (<code>hasStock</code>)、是否为特色产品 (<code>isFeatured</code>) 等布尔条件进行筛选。</p> +<p>前端发出的请求 URL 可能如下所示: <code>/products?filter[hasStock]=true&filter[isFeatured]=false</code></p> +<p>在 NestJS 后端,首先会在 <code>main.ts</code> 中配置一个全局的 <code>ValidationPipe</code>,以自动处理 DTO 的验证和转换。为了方便,通常会启用 <code>enableImplicitConversion</code>,期望它能自动将 URL 查询参数中的字符串(如 <code>"123"</code>, <code>"true"</code>)转换为 DTO 中定义的类型(<code>number</code>, <code>boolean</code>):</p> +<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// main.ts</span></span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">ValidationPipe</span> } <span class="keyword">from</span> <span class="string">'@nestjs/common'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">NestFactory</span> } <span class="keyword">from</span> <span class="string">'@nestjs/core'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">AppModule</span> } <span class="keyword">from</span> <span class="string">'./app.module'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">bootstrap</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">const</span> app = <span class="keyword">await</span> <span class="title class_">NestFactory</span>.<span class="title function_">create</span>(<span class="title class_">AppModule</span>);</span><br><span class="line"></span><br><span class="line"> app.<span class="title function_">useGlobalPipes</span>(</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">ValidationPipe</span>({</span><br><span class="line"> <span class="attr">transform</span>: <span class="literal">true</span>, <span class="comment">// 启用转换</span></span><br><span class="line"> <span class="attr">whitelist</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">forbidNonWhitelisted</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">transformOptions</span>: {</span><br><span class="line"> <span class="comment">// 启用基于 TypeScript 类型的隐式转换</span></span><br><span class="line"> <span class="attr">enableImplicitConversion</span>: <span class="literal">true</span>, </span><br><span class="line"> },</span><br><span class="line"> }),</span><br><span class="line"> );</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ... 其他配置</span></span><br><span class="line"> <span class="keyword">await</span> app.<span class="title function_">listen</span>(<span class="number">3000</span>);</span><br><span class="line">}</span><br><span class="line"><span class="title function_">bootstrap</span>();</span><br></pre></td></tr></table></figure> +<p>接着,定义一个 <code>ProductFilterDto</code> 来接收这些筛选条件。</p> +<p><strong>一个看似正确的 DTO 定义:</strong></p> +<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// product-filter.dto.ts</span></span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">IsBoolean</span>, <span class="title class_">IsOptional</span> } <span class="keyword">from</span> <span class="string">'class-validator'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">ProductFilterDto</span> {</span><br><span class="line"> <span class="meta">@IsOptional</span>()</span><br><span class="line"> <span class="meta">@IsBoolean</span>()</span><br><span class="line"> <span class="attr">hasStock</span>?: <span class="built_in">boolean</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@IsOptional</span>()</span><br><span class="line"> <span class="meta">@IsBoolean</span>()</span><br><span class="line"> <span class="attr">isFeatured</span>?: <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure> +<p>在控制器中使用这个 DTO:</p> +<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// products.controller.ts</span></span><br><span class="line"><span class="meta">@Controller</span>(<span class="string">'products'</span>)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">ProductsController</span> {</span><br><span class="line"> <span class="meta">@Get</span>()</span><br><span class="line"> <span class="title function_">find</span>(<span class="params"><span class="meta">@Query</span>(<span class="string">'filter'</span>) <span class="attr">filter</span>: <span class="title class_">ProductFilterDto</span></span>) {</span><br><span class="line"> <span class="comment">// 期望 filter.isFeatured 的值为 boolean false</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(filter); </span><br><span class="line"> <span class="comment">// ... 业务逻辑</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> +<p>当请求 <code>.../products?filter[isFeatured]=false</code> 到达时,本来期望在 <code>find</code> 方法中得到的 <code>filter.isFeatured</code> 的值是布尔类型的 <code>false</code>。然而,控制台输出的结果却令人意外:<code>{ isFeatured: true }</code></p> +<h2 id="问题剖析"><a class="header-anchor" href="#问题剖析">¶</a>问题剖析</h2> +<p>这个问题的根源在于 <code>class-transformer</code> 内部的转换执行顺序,以及 JavaScript 中 <code>Boolean</code> 函数的类型转换行为。</p> +<p>所有通过 URL 查询参数传递的值,其本质都是字符串。当 NestJS 接收到请求时,<code>filter.isFeatured</code> 的原始值是字符串 <code>"false"</code>。</p> +<p><code>ValidationPipe</code> 启动 <code>class-transformer</code> 的转换流程。由于在全局管道中设置了 <code>enableImplicitConversion: true</code>,转换器会首先检查 DTO 属性的 TypeScript 类型。</p> +<ol> +<li><strong>隐式转换优先执行</strong>:<code>class-transformer</code> 看到 <code>ProductFilterDto</code> 中的 <code>isFeatured</code> 属性被声明为 <code>boolean</code> 类型。</li> +<li><strong>错误的类型转换</strong>:它立即尝试将字符串 <code>"false"</code> 转换为布尔值。这个转换等同于执行 <code>Boolean("false")</code>。在 JavaScript 中,任何非空字符串(包括 <code>"false"</code>)通过 <code>Boolean()</code> 构造函数转换后都会得到 <code>true</code>。</li> +<li><strong>结果覆盖</strong>:这个错误的 <code>true</code> 值被作为该属性的转换结果。</li> +</ol> +<p>此时,即使尝试添加一个自定义的 <code>@Transform</code> 装饰器来手动处理这个问题,也为时已晚。</p> +<p>例如,定义一个 <code>booleanTransformer</code>:</p> +<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// boolean-transformer.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">booleanTransformer</span> = (<span class="params">{ value }: { value: <span class="built_in">any</span> }</span>) => {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> value === <span class="string">'string'</span>) {</span><br><span class="line"> <span class="keyword">return</span> value === <span class="string">'true'</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line">};</span><br></pre></td></tr></table></figure> +<p>然后更新 dto:</p> +<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// product-filter.dto.ts (错误的尝试)</span></span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">Transform</span> } <span class="keyword">from</span> <span class="string">'class-transformer'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">IsBoolean</span>, <span class="title class_">IsOptional</span> } <span class="keyword">from</span> <span class="string">'class-validator'</span>;</span><br><span class="line"><span class="keyword">import</span> { booleanTransformer } <span class="keyword">from</span> <span class="string">'./boolean-transformer'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">ProductFilterDto</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="meta">@IsOptional</span>()</span><br><span class="line"> <span class="meta">@Transform</span>(booleanTransformer) <span class="comment">// 添加自定义转换</span></span><br><span class="line"> <span class="meta">@IsBoolean</span>()</span><br><span class="line"> <span class="attr">isFeatured</span>?: <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure> +<p>流程会变成这样:</p> +<ol> +<li>隐式转换首先执行:<code>Boolean("false")</code> -> <code>true</code>。</li> +<li><code>@Transform</code> 装饰器执行:此时传递给 <code>booleanTransformer</code> 的 <code>value</code> 已经是上一步错误转换后的布尔值 <code>true</code>,而不是原始的字符串 <code>"false"</code>。转换函数无从下手。</li> +</ol> +<p>最终结果依然是 <code>true</code>。</p> +<h2 id="解决方案:用-any-绕过隐式转换"><a class="header-anchor" href="#解决方案:用-any-绕过隐式转换">¶</a>## 解决方案:用 <code>any</code> 绕过隐式转换</h2> +<p>要解决这个问题,核心在于阻止 <code>class-transformer</code> 进行那次错误的、优先的隐式转换,从而确保自定义 <code>@Transform</code> 函数能接收到最原始的字符串值。</p> +<p>最直接且侵入性最小的方法,是将 DTO 中相关属性的 TypeScript 类型从 <code>boolean</code> 改为 <code>any</code>。</p> +<p><strong>修正后的 DTO 定义:</strong></p> +<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// product-filter.dto.ts (正确的实现)</span></span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">Transform</span> } <span class="keyword">from</span> <span class="string">'class-transformer'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">IsBoolean</span>, <span class="title class_">IsOptional</span> } <span class="keyword">from</span> <span class="string">'class-validator'</span>;</span><br><span class="line"><span class="keyword">import</span> { booleanTransformer } <span class="keyword">from</span> <span class="string">'./boolean-transformer'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 一个更健壮的 booleanTransformer</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">robustBooleanTransformer</span> = (<span class="params">{ value }: { value: <span class="built_in">string</span> }</span>) =></span><br><span class="line"> value === <span class="string">'true'</span> ? <span class="literal">true</span> : value === <span class="string">'false'</span> ? <span class="literal">false</span> : value;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">ProductFilterDto</span> {</span><br><span class="line"> <span class="meta">@IsOptional</span>()</span><br><span class="line"> <span class="meta">@Transform</span>(robustBooleanTransformer)</span><br><span class="line"> <span class="meta">@IsBoolean</span>()</span><br><span class="line"> <span class="attr">hasStock</span>?: <span class="built_in">any</span>; <span class="comment">// <-- 类型从 boolean 改为 any</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@IsOptional</span>()</span><br><span class="line"> <span class="meta">@Transform</span>(robustBooleanTransformer)</span><br><span class="line"> <span class="meta">@IsBoolean</span>()</span><br><span class="line"> <span class="attr">isFeatured</span>?: <span class="built_in">any</span>; <span class="comment">// <-- 类型从 boolean 改为 any</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure> +<p>这个改动虽然看起来放弃了 TypeScript 的类型检查,但在这个特定的场景下,它非常安全且有效。原因如下:</p> +<ol> +<li><strong>阻止隐式转换</strong>:当 <code>class-transformer</code> 看到属性类型是 <code>any</code> 时,它不知道该隐式转换成什么目标类型,因此会“跳过”这个属性的隐式转换步骤。</li> +<li><strong><code>@Transform</code> 接管</strong>:如此一来,原始的字符串值(<code>"true"</code> 或 <code>"false"</code>)就能原封不动地传递给 <code>robustBooleanTransformer</code> 函数。该函数现在可以正确地将字符串转换为期望的布尔值。</li> +<li><strong><code>@IsBoolean</code> 守门</strong>:在自定义转换完成后,<code>@IsBoolean()</code> 装饰器会进行最后的验证,确保存入 DTO 的最终值必须是 <code>true</code> 或 <code>false</code>。这保证了在业务逻辑中,该属性的类型是绝对安全的。</li> +</ol> +<p>好用,爱用。</p> +]]></content> + <tags> + <tag>Technique</tag> + </tags> + </entry> + <entry> <title>NestJS bullmq 邮件发送业务中的小 tips</title> <url>/2025/05/07/nestjs-bullmq-mail-business/</url> <content><![CDATA[<p>在 BullMQ(以及它在 NestJS 里包装的 <code>@Processor</code>/<code>WorkerHost</code>)里,整个生命周期大致是这样的:</p> @@ -3323,58 +3375,6 @@ A relation table (also sometimes called a <em>JOIN</em>, <em>link</em> or <em>pi </tags> </entry> <entry> - <title>NestJS 中 enableImplicitConversion 与 @Transform 的冲突</title> - <url>/2025/08/13/nestjs-enableImplicitConversion-and-transform/</url> - <content><![CDATA[<p>在 NestJS 生态中,<code>class-validator</code> 和 <code>class-transformer</code> 这两个库提供了以声明式的方式对 DTO 进行验证和转换。然而在处理布尔值时,如果在全局验证管道或仅仅是在局部同时开启了 <code>enableImplicitConversion</code>,可能会引入一个极其隐蔽且违反直觉的 Bug:前端传过来的布尔值恒为 true。</p> -<h2 id="一个简单的筛选功能"><a class="header-anchor" href="#一个简单的筛选功能">¶</a>一个简单的筛选功能</h2> -<p>假设正在开发一个电子商务平台的 API,需要实现一个产品列表的筛选功能。希望能够根据产品是否有库存 (<code>hasStock</code>)、是否为特色产品 (<code>isFeatured</code>) 等布尔条件进行筛选。</p> -<p>前端发出的请求 URL 可能如下所示: <code>/products?filter[hasStock]=true&filter[isFeatured]=false</code></p> -<p>在 NestJS 后端,首先会在 <code>main.ts</code> 中配置一个全局的 <code>ValidationPipe</code>,以自动处理 DTO 的验证和转换。为了方便,通常会启用 <code>enableImplicitConversion</code>,期望它能自动将 URL 查询参数中的字符串(如 <code>"123"</code>, <code>"true"</code>)转换为 DTO 中定义的类型(<code>number</code>, <code>boolean</code>):</p> -<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// main.ts</span></span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">ValidationPipe</span> } <span class="keyword">from</span> <span class="string">'@nestjs/common'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">NestFactory</span> } <span class="keyword">from</span> <span class="string">'@nestjs/core'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">AppModule</span> } <span class="keyword">from</span> <span class="string">'./app.module'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">bootstrap</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">const</span> app = <span class="keyword">await</span> <span class="title class_">NestFactory</span>.<span class="title function_">create</span>(<span class="title class_">AppModule</span>);</span><br><span class="line"></span><br><span class="line"> app.<span class="title function_">useGlobalPipes</span>(</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">ValidationPipe</span>({</span><br><span class="line"> <span class="attr">transform</span>: <span class="literal">true</span>, <span class="comment">// 启用转换</span></span><br><span class="line"> <span class="attr">whitelist</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">forbidNonWhitelisted</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">transformOptions</span>: {</span><br><span class="line"> <span class="comment">// 启用基于 TypeScript 类型的隐式转换</span></span><br><span class="line"> <span class="attr">enableImplicitConversion</span>: <span class="literal">true</span>, </span><br><span class="line"> },</span><br><span class="line"> }),</span><br><span class="line"> );</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// ... 其他配置</span></span><br><span class="line"> <span class="keyword">await</span> app.<span class="title function_">listen</span>(<span class="number">3000</span>);</span><br><span class="line">}</span><br><span class="line"><span class="title function_">bootstrap</span>();</span><br></pre></td></tr></table></figure> -<p>接着,定义一个 <code>ProductFilterDto</code> 来接收这些筛选条件。</p> -<p><strong>一个看似正确的 DTO 定义:</strong></p> -<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// product-filter.dto.ts</span></span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">IsBoolean</span>, <span class="title class_">IsOptional</span> } <span class="keyword">from</span> <span class="string">'class-validator'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">ProductFilterDto</span> {</span><br><span class="line"> <span class="meta">@IsOptional</span>()</span><br><span class="line"> <span class="meta">@IsBoolean</span>()</span><br><span class="line"> <span class="attr">hasStock</span>?: <span class="built_in">boolean</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@IsOptional</span>()</span><br><span class="line"> <span class="meta">@IsBoolean</span>()</span><br><span class="line"> <span class="attr">isFeatured</span>?: <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure> -<p>在控制器中使用这个 DTO:</p> -<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// products.controller.ts</span></span><br><span class="line"><span class="meta">@Controller</span>(<span class="string">'products'</span>)</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">ProductsController</span> {</span><br><span class="line"> <span class="meta">@Get</span>()</span><br><span class="line"> <span class="title function_">find</span>(<span class="params"><span class="meta">@Query</span>(<span class="string">'filter'</span>) <span class="attr">filter</span>: <span class="title class_">ProductFilterDto</span></span>) {</span><br><span class="line"> <span class="comment">// 期望 filter.isFeatured 的值为 boolean false</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(filter); </span><br><span class="line"> <span class="comment">// ... 业务逻辑</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure> -<p>当请求 <code>.../products?filter[isFeatured]=false</code> 到达时,本来期望在 <code>find</code> 方法中得到的 <code>filter.isFeatured</code> 的值是布尔类型的 <code>false</code>。然而,控制台输出的结果却令人意外:<code>{ isFeatured: true }</code></p> -<h2 id="问题剖析"><a class="header-anchor" href="#问题剖析">¶</a>问题剖析</h2> -<p>这个问题的根源在于 <code>class-transformer</code> 内部的转换执行顺序,以及 JavaScript 中 <code>Boolean</code> 函数的类型转换行为。</p> -<p>所有通过 URL 查询参数传递的值,其本质都是字符串。当 NestJS 接收到请求时,<code>filter.isFeatured</code> 的原始值是字符串 <code>"false"</code>。</p> -<p><code>ValidationPipe</code> 启动 <code>class-transformer</code> 的转换流程。由于在全局管道中设置了 <code>enableImplicitConversion: true</code>,转换器会首先检查 DTO 属性的 TypeScript 类型。</p> -<ol> -<li><strong>隐式转换优先执行</strong>:<code>class-transformer</code> 看到 <code>ProductFilterDto</code> 中的 <code>isFeatured</code> 属性被声明为 <code>boolean</code> 类型。</li> -<li><strong>错误的类型转换</strong>:它立即尝试将字符串 <code>"false"</code> 转换为布尔值。这个转换等同于执行 <code>Boolean("false")</code>。在 JavaScript 中,任何非空字符串(包括 <code>"false"</code>)通过 <code>Boolean()</code> 构造函数转换后都会得到 <code>true</code>。</li> -<li><strong>结果覆盖</strong>:这个错误的 <code>true</code> 值被作为该属性的转换结果。</li> -</ol> -<p>此时,即使尝试添加一个自定义的 <code>@Transform</code> 装饰器来手动处理这个问题,也为时已晚。</p> -<p>例如,定义一个 <code>booleanTransformer</code>:</p> -<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// boolean-transformer.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">booleanTransformer</span> = (<span class="params">{ value }: { value: <span class="built_in">any</span> }</span>) => {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> value === <span class="string">'string'</span>) {</span><br><span class="line"> <span class="keyword">return</span> value === <span class="string">'true'</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line">};</span><br></pre></td></tr></table></figure> -<p>然后更新 dto:</p> -<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// product-filter.dto.ts (错误的尝试)</span></span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">Transform</span> } <span class="keyword">from</span> <span class="string">'class-transformer'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">IsBoolean</span>, <span class="title class_">IsOptional</span> } <span class="keyword">from</span> <span class="string">'class-validator'</span>;</span><br><span class="line"><span class="keyword">import</span> { booleanTransformer } <span class="keyword">from</span> <span class="string">'./boolean-transformer'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">ProductFilterDto</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="meta">@IsOptional</span>()</span><br><span class="line"> <span class="meta">@Transform</span>(booleanTransformer) <span class="comment">// 添加自定义转换</span></span><br><span class="line"> <span class="meta">@IsBoolean</span>()</span><br><span class="line"> <span class="attr">isFeatured</span>?: <span class="built_in">boolean</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure> -<p>流程会变成这样:</p> -<ol> -<li>隐式转换首先执行:<code>Boolean("false")</code> -> <code>true</code>。</li> -<li><code>@Transform</code> 装饰器执行:此时传递给 <code>booleanTransformer</code> 的 <code>value</code> 已经是上一步错误转换后的布尔值 <code>true</code>,而不是原始的字符串 <code>"false"</code>。转换函数无从下手。</li> -</ol> -<p>最终结果依然是 <code>true</code>。</p> -<h2 id="解决方案:用-any-绕过隐式转换"><a class="header-anchor" href="#解决方案:用-any-绕过隐式转换">¶</a>## 解决方案:用 <code>any</code> 绕过隐式转换</h2> -<p>要解决这个问题,核心在于阻止 <code>class-transformer</code> 进行那次错误的、优先的隐式转换,从而确保自定义 <code>@Transform</code> 函数能接收到最原始的字符串值。</p> -<p>最直接且侵入性最小的方法,是将 DTO 中相关属性的 TypeScript 类型从 <code>boolean</code> 改为 <code>any</code>。</p> -<p><strong>修正后的 DTO 定义:</strong></p> -<figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// product-filter.dto.ts (正确的实现)</span></span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">Transform</span> } <span class="keyword">from</span> <span class="string">'class-transformer'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">IsBoolean</span>, <span class="title class_">IsOptional</span> } <span class="keyword">from</span> <span class="string">'class-validator'</span>;</span><br><span class="line"><span class="keyword">import</span> { booleanTransformer } <span class="keyword">from</span> <span class="string">'./boolean-transformer'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 一个更健壮的 booleanTransformer</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">robustBooleanTransformer</span> = (<span class="params">{ value }: { value: <span class="built_in">string</span> }</span>) =></span><br><span class="line"> value === <span class="string">'true'</span> ? <span class="literal">true</span> : value === <span class="string">'false'</span> ? <span class="literal">false</span> : value;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">ProductFilterDto</span> {</span><br><span class="line"> <span class="meta">@IsOptional</span>()</span><br><span class="line"> <span class="meta">@Transform</span>(robustBooleanTransformer)</span><br><span class="line"> <span class="meta">@IsBoolean</span>()</span><br><span class="line"> <span class="attr">hasStock</span>?: <span class="built_in">any</span>; <span class="comment">// <-- 类型从 boolean 改为 any</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@IsOptional</span>()</span><br><span class="line"> <span class="meta">@Transform</span>(robustBooleanTransformer)</span><br><span class="line"> <span class="meta">@IsBoolean</span>()</span><br><span class="line"> <span class="attr">isFeatured</span>?: <span class="built_in">any</span>; <span class="comment">// <-- 类型从 boolean 改为 any</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure> -<p>这个改动虽然看起来放弃了 TypeScript 的类型检查,但在这个特定的场景下,它非常安全且有效。原因如下:</p> -<ol> -<li><strong>阻止隐式转换</strong>:当 <code>class-transformer</code> 看到属性类型是 <code>any</code> 时,它不知道该隐式转换成什么目标类型,因此会“跳过”这个属性的隐式转换步骤。</li> -<li><strong><code>@Transform</code> 接管</strong>:如此一来,原始的字符串值(<code>"true"</code> 或 <code>"false"</code>)就能原封不动地传递给 <code>robustBooleanTransformer</code> 函数。该函数现在可以正确地将字符串转换为期望的布尔值。</li> -<li><strong><code>@IsBoolean</code> 守门</strong>:在自定义转换完成后,<code>@IsBoolean()</code> 装饰器会进行最后的验证,确保存入 DTO 的最终值必须是 <code>true</code> 或 <code>false</code>。这保证了在业务逻辑中,该属性的类型是绝对安全的。</li> -</ol> -<p>好用,爱用。</p> -]]></content> - <tags> - <tag>Technique</tag> - </tags> - </entry> - <entry> <title>NestJS 嵌套查询参数解析错误分析与解决方案</title> <url>/2025/07/29/nestjs-nested-object-dto/</url> <content><![CDATA[<p>在开发 NestJS 应用程序时,一个常见的场景是根据复杂的条件筛选资源。当使用嵌套查询参数来表达这些条件时,例如通过 URL <code>http://localhost:3000/products/filter?filters[price][min]=100</code>,可能会意外地遇到 <code>property filters[price][min] should not exist</code> 错误。本文将深入剖析此问题背后的原因,并提供针对 Express 和 Fastify 两种底层框架的解决方案。</p> @@ -3492,17 +3492,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </tags> </entry> <entry> - <title>shadow-cljs 怎么设置代理</title> - <url>/2024/04/03/shadow-cljs-proxy/</url> - <content><![CDATA[<p>The library pomegranate used for retrieving maven dependencies does not read <code>.m2/settings.xml</code>. You can however set the :proxy config either in <code>shadow-cljs.edn</code> directly or <code>~/.shadow-cljs/config.edn</code>.</p> -<p>See <a href="https://github.com/cemerick/pomegranate/blob/master/src/main/clojure/cemerick/pomegranate/aether.clj#L757-L765">https://github.com/cemerick/pomegranate/blob/master/src/main/clojure/cemerick/pomegranate/aether.clj#L757-L765</a>.</p> -<p><em>shadow-cljs.edn:</em></p> -<figure class="highlight clojure"><table><tr><td class="code"><pre><span class="line">{<span class="symbol">:source-paths</span> [<span class="string">"src/main"</span></span><br><span class="line"> <span class="string">"src/test"</span>]</span><br><span class="line"></span><br><span class="line"> <span class="symbol">:dependencies</span> [[reagent <span class="string">"1.2.0"</span>]</span><br><span class="line"> [re-frame <span class="string">"1.3.0"</span>]]</span><br><span class="line"></span><br><span class="line"> <span class="comment">;; Here</span></span><br><span class="line"> <span class="symbol">:proxy</span> {<span class="symbol">:host</span> <span class="string">"localhost"</span></span><br><span class="line"> <span class="symbol">:port</span> <span class="number">20171</span>}</span><br><span class="line"></span><br><span class="line"> <span class="symbol">:builds</span> {<span class="symbol">:app</span> {<span class="symbol">:target</span> <span class="symbol">:react-native</span></span><br><span class="line"> <span class="symbol">:init-fn</span> example.app/init</span><br><span class="line"> <span class="symbol">:output-dir</span> <span class="string">"app"</span></span><br><span class="line"> <span class="symbol">:compiler-options</span> {<span class="symbol">:infer-externs</span> <span class="symbol">:auto</span>}</span><br><span class="line"> <span class="symbol">:devtools</span> {<span class="symbol">:autoload</span> <span class="literal">true</span>}}}}</span><br></pre></td></tr></table></figure>]]></content> - <tags> - <tag>Technique</tag> - </tags> - </entry> - <entry> <title>表面活性剂</title> <url>/2025/10/28/sufactants/</url> <content><![CDATA[<p>表面活性剂(surfactants)是一种两亲性分子,具有亲水基团和疏水基团,能够在水和油脂界面上降低表面张力,从而广泛应用于消毒剂、洗涤剂和药物制剂中。它们对细菌和真菌的杀灭作用主要源于其对微生物细胞膜的破坏,这是一种非特异性物理化学机制,而非针对特定生化途径的靶向攻击。这种作用在临床消毒和感染控制中被广泛利用,尤其在处理皮肤或表面污染时。</p> @@ -3518,6 +3507,17 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </tags> </entry> <entry> + <title>shadow-cljs 怎么设置代理</title> + <url>/2024/04/03/shadow-cljs-proxy/</url> + <content><![CDATA[<p>The library pomegranate used for retrieving maven dependencies does not read <code>.m2/settings.xml</code>. You can however set the :proxy config either in <code>shadow-cljs.edn</code> directly or <code>~/.shadow-cljs/config.edn</code>.</p> +<p>See <a href="https://github.com/cemerick/pomegranate/blob/master/src/main/clojure/cemerick/pomegranate/aether.clj#L757-L765">https://github.com/cemerick/pomegranate/blob/master/src/main/clojure/cemerick/pomegranate/aether.clj#L757-L765</a>.</p> +<p><em>shadow-cljs.edn:</em></p> +<figure class="highlight clojure"><table><tr><td class="code"><pre><span class="line">{<span class="symbol">:source-paths</span> [<span class="string">"src/main"</span></span><br><span class="line"> <span class="string">"src/test"</span>]</span><br><span class="line"></span><br><span class="line"> <span class="symbol">:dependencies</span> [[reagent <span class="string">"1.2.0"</span>]</span><br><span class="line"> [re-frame <span class="string">"1.3.0"</span>]]</span><br><span class="line"></span><br><span class="line"> <span class="comment">;; Here</span></span><br><span class="line"> <span class="symbol">:proxy</span> {<span class="symbol">:host</span> <span class="string">"localhost"</span></span><br><span class="line"> <span class="symbol">:port</span> <span class="number">20171</span>}</span><br><span class="line"></span><br><span class="line"> <span class="symbol">:builds</span> {<span class="symbol">:app</span> {<span class="symbol">:target</span> <span class="symbol">:react-native</span></span><br><span class="line"> <span class="symbol">:init-fn</span> example.app/init</span><br><span class="line"> <span class="symbol">:output-dir</span> <span class="string">"app"</span></span><br><span class="line"> <span class="symbol">:compiler-options</span> {<span class="symbol">:infer-externs</span> <span class="symbol">:auto</span>}</span><br><span class="line"> <span class="symbol">:devtools</span> {<span class="symbol">:autoload</span> <span class="literal">true</span>}}}}</span><br></pre></td></tr></table></figure>]]></content> + <tags> + <tag>Technique</tag> + </tags> + </entry> + <entry> <title>使用 Runed intersection observer 与 tanstack query 实现通用的惰性加载组件</title> <url>/2025/07/31/svelte-lazyquery/</url> <content><![CDATA[<p>在前端,一个常见的性能瓶颈是初始加载时请求了用户当前 viewpoint 之外的非必要数据。惰性加载(Lazy Loading)是一种关键策略,它将数据获取推迟到组件进入 viewpoint 时才执行。本文将阐述如何结合使用 Svelte 5、<code>@tanstack/svelte-query</code> 和 <code>runed.dev</code> 的 <code>useIntersectionObserver</code>,构建一个通用的、可复用的惰性加载组件。</p> @@ -3740,6 +3740,18 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </tags> </entry> <entry> + <title>二〇二三年八月一日</title> + <url>/2023/08/01/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%B8%89%E5%B9%B4%E5%85%AB%E6%9C%88%E4%B8%80%E6%97%A5/</url> + <content><![CDATA[<p>阳光给他病态一般白的皮肤注入了一些生机,左手手臂上斑驳的伤痕,每一道都诉诸着他对家人浓重的思念。那是在暗无天日的岁月里,他通过痛楚得到的慰藉。</p> +<p>时光对谁都不温柔,愿意对我温柔的,只有身边陪我一起迎接时光洗礼的人。</p> +<p>宇宙中也不会有什么声音, 视觉上再震撼的毁灭也只是发生在沉默之中。</p> +<p>一切重新沉寂下来。</p> +]]></content> + <categories> + <category>gallery</category> + </categories> + </entry> + <entry> <title>乙酰半脱氨酸的药理学与药代动力学特性</title> <url>/2025/12/30/%E4%B9%99%E9%85%B0%E5%8D%8A%E8%84%B1%E6%B0%A8%E9%85%B8%E7%9A%84%E8%8D%AF%E7%90%86%E5%AD%A6%E4%B8%8E%E8%8D%AF%E4%BB%A3%E5%8A%A8%E5%8A%9B%E5%AD%A6%E7%89%B9%E6%80%A7/</url> <content><![CDATA[<p>乙酰半胱氨酸的药理活性核心在于其分子中的游离巯基(-SH)。这一化学基团赋予其两项主要且机制迥异的临床应用:作为粘痰溶解剂和作为对乙酰氨基酚(扑热息痛)过量的特效解毒剂。</p> @@ -3772,18 +3784,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </tags> </entry> <entry> - <title>二〇二三年八月一日</title> - <url>/2023/08/01/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%B8%89%E5%B9%B4%E5%85%AB%E6%9C%88%E4%B8%80%E6%97%A5/</url> - <content><![CDATA[<p>阳光给他病态一般白的皮肤注入了一些生机,左手手臂上斑驳的伤痕,每一道都诉诸着他对家人浓重的思念。那是在暗无天日的岁月里,他通过痛楚得到的慰藉。</p> -<p>时光对谁都不温柔,愿意对我温柔的,只有身边陪我一起迎接时光洗礼的人。</p> -<p>宇宙中也不会有什么声音, 视觉上再震撼的毁灭也只是发生在沉默之中。</p> -<p>一切重新沉寂下来。</p> -]]></content> - <categories> - <category>gallery</category> - </categories> - </entry> - <entry> <title>二〇二三年六月二十九日</title> <url>/2023/06/29/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%B8%89%E5%B9%B4%E5%85%AD%E6%9C%88%E4%BA%8C%E5%8D%81%E4%B9%9D%E6%97%A5/</url> <content><![CDATA[<p>我能走遍大街小巷,但我走不出时间,这里有太多东西网住我了,每一个不知所措的老人家,每一个满身泥土的农民,每一只嬉戏打闹的猫狗和每一寸土地下的家人。</p> @@ -3876,6 +3876,18 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </categories> </entry> <entry> + <title>二〇二五年二月二十四日</title> + <url>/2025/02/24/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E4%BA%8C%E6%9C%88%E4%BA%8C%E5%8D%81%E5%9B%9B%E6%97%A5/</url> + <content><![CDATA[<p>街上的人躲进建筑,道路上的车变得缓慢。路边的黑猫坐在屋檐下张望,在雨天,它也有了思考的地方与时间。除了淅淅沥沥的雨声再无其他。</p> +<p>衣服买大了,他们走路好急,地铁上只有老人没看手机,天怎么黑那么快是我起晚了吗,那个蹦蹦跳跳的女孩耳机里在听什么歌,我还在原地吗,我要再等等吗。</p> +<p>家就像厚酒之后突然明晰的想象,时间不语,只是沉默,我沉默地走进时间,看着我和你的年龄相差越来越小。</p> +<p>这雨天,世界都温柔的可以掐出水来。</p> +]]></content> + <categories> + <category>gallery</category> + </categories> + </entry> + <entry> <title>二〇二五年九月二十四日</title> <url>/2025/09/24/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E4%B9%9D%E6%9C%88%E4%BA%8C%E5%8D%81%E5%9B%9B%E6%97%A5/</url> <content><![CDATA[<p>从小到大,我好像一直是个独立的人。</p> @@ -3924,18 +3936,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </categories> </entry> <entry> - <title>二〇二五年二月二十四日</title> - <url>/2025/02/24/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E4%BA%8C%E6%9C%88%E4%BA%8C%E5%8D%81%E5%9B%9B%E6%97%A5/</url> - <content><![CDATA[<p>街上的人躲进建筑,道路上的车变得缓慢。路边的黑猫坐在屋檐下张望,在雨天,它也有了思考的地方与时间。除了淅淅沥沥的雨声再无其他。</p> -<p>衣服买大了,他们走路好急,地铁上只有老人没看手机,天怎么黑那么快是我起晚了吗,那个蹦蹦跳跳的女孩耳机里在听什么歌,我还在原地吗,我要再等等吗。</p> -<p>家就像厚酒之后突然明晰的想象,时间不语,只是沉默,我沉默地走进时间,看着我和你的年龄相差越来越小。</p> -<p>这雨天,世界都温柔的可以掐出水来。</p> -]]></content> - <categories> - <category>gallery</category> - </categories> - </entry> - <entry> <title>二〇二五年四月三日</title> <url>/2025/04/03/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E4%BA%94%E6%9C%88%E4%B8%89%E6%97%A5/</url> <content><![CDATA[<p>日历被一页页撕去,出钞口的暖风比任何毛毯更忠实,数字键盘的荧光就像圣徒光环般笼罩着我。</p> @@ -4091,20 +4091,20 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </categories> </entry> <entry> - <title>二〇二五年十二月十九日</title> - <url>/2025/12/19/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E5%8D%81%E4%BA%8C%E6%9C%88%E5%8D%81%E4%B9%9D%E6%97%A5/</url> - <content><![CDATA[<p>一个尚未开始就已葬于山海的情欲,在无人知晓的深夜,完成了它短暂一生中最私密的绽放。</p> + <title>二〇二五年十月三十一日</title> + <url>/2025/10/31/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E5%8D%81%E6%9C%88%E4%B8%89%E5%8D%81%E4%B8%80%E6%97%A5/</url> + <content><![CDATA[<p>傍晚的海风带着盐的咸味和鱼网的腥气吹上岸。她独自站在礁石上眺望。巨大的海面起伏不定,深蓝的尽头与灰暗的天空交融成一个模糊的线。巨大的落日悬浮其上,像一颗即将坠入海面的橙红心脏。霞光将她的脸庞染成暖色。海风掀起她披在肩上的丝巾,丝巾像一面旗帜在身后猎猎作响。她深深吸了一口气,咸冷的空气涌入肺腑,带着一种辽阔的清醒。</p> +<p>时间会带走了一个人,也带走了他的呼吸,他的声音,他掌心的温度。但似乎又留下了些什么</p> +<p>光的颗粒在空气里浮动,在记忆里游走,她闭上眼睛,光从睫毛上滑过,像许多个早晨,像许多个傍晚,像她们再次相遇的无数个灿然的瞬间。她在其中走走停停,像一粒微尘,又像一颗星。</p> ]]></content> <categories> <category>gallery</category> </categories> </entry> <entry> - <title>二〇二五年十月三十一日</title> - <url>/2025/10/31/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E5%8D%81%E6%9C%88%E4%B8%89%E5%8D%81%E4%B8%80%E6%97%A5/</url> - <content><![CDATA[<p>傍晚的海风带着盐的咸味和鱼网的腥气吹上岸。她独自站在礁石上眺望。巨大的海面起伏不定,深蓝的尽头与灰暗的天空交融成一个模糊的线。巨大的落日悬浮其上,像一颗即将坠入海面的橙红心脏。霞光将她的脸庞染成暖色。海风掀起她披在肩上的丝巾,丝巾像一面旗帜在身后猎猎作响。她深深吸了一口气,咸冷的空气涌入肺腑,带着一种辽阔的清醒。</p> -<p>时间会带走了一个人,也带走了他的呼吸,他的声音,他掌心的温度。但似乎又留下了些什么</p> -<p>光的颗粒在空气里浮动,在记忆里游走,她闭上眼睛,光从睫毛上滑过,像许多个早晨,像许多个傍晚,像她们再次相遇的无数个灿然的瞬间。她在其中走走停停,像一粒微尘,又像一颗星。</p> + <title>二〇二五年十二月十九日</title> + <url>/2025/12/19/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E5%8D%81%E4%BA%8C%E6%9C%88%E5%8D%81%E4%B9%9D%E6%97%A5/</url> + <content><![CDATA[<p>一个尚未开始就已葬于山海的情欲,在无人知晓的深夜,完成了它短暂一生中最私密的绽放。</p> ]]></content> <categories> <category>gallery</category> @@ -4271,18 +4271,18 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </categories> </entry> <entry> - <title>二〇二四年九月一日</title> - <url>/2024/09/01/%E4%BA%8C%E3%80%87%E4%BA%8C%E5%9B%9B%E5%B9%B4%E4%B9%9D%E6%9C%88%E4%B8%80%E6%97%A5/</url> - <content><![CDATA[<p>通过忠实地映射宇宙来隐藏自我,是融入永恒的唯一途径。</p> + <title>二〇二四年九月十二日</title> + <url>/2024/09/12/%E4%BA%8C%E3%80%87%E4%BA%8C%E5%9B%9B%E5%B9%B4%E4%B9%9D%E6%9C%88%E5%8D%81%E4%BA%8C%E6%97%A5/</url> + <content><![CDATA[<p>人只不过是一根苇草,是自然界最脆弱的东西,但却是一根能思想的苇草。用不着整个宇宙都拿起武器来才能毁灭人类,一口气、一滴水就足以致人类于死命了。然而纵使宇宙毁灭了人类,在人类的视角下自己却仍然要比致其于死命的东西高贵得多,因为人类知道自己要死亡,以及宇宙对其所具有的优势,还可以主动地认为宇宙对此一无所知。</p> ]]></content> <categories> <category>gallery</category> </categories> </entry> <entry> - <title>二〇二四年九月十二日</title> - <url>/2024/09/12/%E4%BA%8C%E3%80%87%E4%BA%8C%E5%9B%9B%E5%B9%B4%E4%B9%9D%E6%9C%88%E5%8D%81%E4%BA%8C%E6%97%A5/</url> - <content><![CDATA[<p>人只不过是一根苇草,是自然界最脆弱的东西,但却是一根能思想的苇草。用不着整个宇宙都拿起武器来才能毁灭人类,一口气、一滴水就足以致人类于死命了。然而纵使宇宙毁灭了人类,在人类的视角下自己却仍然要比致其于死命的东西高贵得多,因为人类知道自己要死亡,以及宇宙对其所具有的优势,还可以主动地认为宇宙对此一无所知。</p> + <title>二〇二四年九月一日</title> + <url>/2024/09/01/%E4%BA%8C%E3%80%87%E4%BA%8C%E5%9B%9B%E5%B9%B4%E4%B9%9D%E6%9C%88%E4%B8%80%E6%97%A5/</url> + <content><![CDATA[<p>通过忠实地映射宇宙来隐藏自我,是融入永恒的唯一途径。</p> ]]></content> <categories> <category>gallery</category> @@ -4346,18 +4346,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </categories> </entry> <entry> - <title>二〇二四年八月一日</title> - <url>/2024/08/02/%E4%BA%8C%E3%80%87%E4%BA%8C%E5%9B%9B%E5%B9%B4%E5%85%AB%E6%9C%88%E4%B8%80%E6%97%A5/</url> - <content><![CDATA[<p>阿公烧烟成老瘾,<br> -阿公饮酒会面红,<br> -他说日子要开心,<br> -他说人生要尽兴。</p> -]]></content> - <categories> - <category>gallery</category> - </categories> - </entry> - <entry> <title>二〇二四年六月二十七日</title> <url>/2024/06/27/%E4%BA%8C%E3%80%87%E4%BA%8C%E5%9B%9B%E5%B9%B4%E5%85%AD%E6%9C%88%E4%BA%8C%E5%8D%81%E4%B8%83%E6%97%A5/</url> <content><![CDATA[<p>钟摆晃荡 走出时间,<br> @@ -4381,6 +4369,18 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </categories> </entry> <entry> + <title>二〇二四年八月一日</title> + <url>/2024/08/02/%E4%BA%8C%E3%80%87%E4%BA%8C%E5%9B%9B%E5%B9%B4%E5%85%AB%E6%9C%88%E4%B8%80%E6%97%A5/</url> + <content><![CDATA[<p>阿公烧烟成老瘾,<br> +阿公饮酒会面红,<br> +他说日子要开心,<br> +他说人生要尽兴。</p> +]]></content> + <categories> + <category>gallery</category> + </categories> + </entry> + <entry> <title>二〇二四年十一月十七日</title> <url>/2024/11/17/%E4%BA%8C%E3%80%87%E4%BA%8C%E5%9B%9B%E5%B9%B4%E5%8D%81%E4%B8%80%E6%9C%88%E5%8D%81%E4%B8%83%E6%97%A5/</url> <content><![CDATA[<p>伤春悲秋、离愁别绪太容易了,读几首诗词即可,但获取欢乐太难。</p> @@ -4481,6 +4481,15 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </categories> </entry> <entry> + <title>二零二三年二月一日</title> + <url>/2023/02/01/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%B8%89%E5%B9%B4%E4%BA%8C%E6%9C%88%E4%B8%80%E6%97%A5/</url> + <content><![CDATA[<p>我想在六点和日出打招呼,我想去菜市场看看卖蔬菜的阿婆,我想花一上午去准备一餐中午饭,我想下午能在阳台捧着一盏热茶慢慢看书,再慢点,慢到能透过阳台栏杆的缝隙看看马路上车水马龙映衬下的老人家提着新鲜的一荤一素蹒跚走回家,看看贼几把大的夕阳在冒着热气的水杯中慢慢融化。</p> +]]></content> + <categories> + <category>gallery</category> + </categories> + </entry> + <entry> <title>二零二三年九月十三日</title> <url>/2023/09/13/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%B8%89%E5%B9%B4%E4%B9%9D%E6%9C%88%E5%8D%81%E4%B8%89%E6%97%A5/</url> <content><![CDATA[<p>“站好! 要拍了啊,倒数三秒,都不要眨眼。” 门口路过的陌生男人拿着相机,充当起了临时的摄影师。</p> @@ -4494,15 +4503,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </categories> </entry> <entry> - <title>二零二三年二月一日</title> - <url>/2023/02/01/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%B8%89%E5%B9%B4%E4%BA%8C%E6%9C%88%E4%B8%80%E6%97%A5/</url> - <content><![CDATA[<p>我想在六点和日出打招呼,我想去菜市场看看卖蔬菜的阿婆,我想花一上午去准备一餐中午饭,我想下午能在阳台捧着一盏热茶慢慢看书,再慢点,慢到能透过阳台栏杆的缝隙看看马路上车水马龙映衬下的老人家提着新鲜的一荤一素蹒跚走回家,看看贼几把大的夕阳在冒着热气的水杯中慢慢融化。</p> -]]></content> - <categories> - <category>gallery</category> - </categories> - </entry> - <entry> <title>二零二三年二月二十二日</title> <url>/2023/02/22/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%B8%89%E5%B9%B4%E4%BA%8C%E6%9C%88%E4%BA%8C%E5%8D%81%E4%BA%8C%E6%97%A5/</url> <content><![CDATA[<p>这个小城市还有很多老人家,她们跟不上这个时代的脚步,要有人带她们跟上这个时代,再目送她们去下一个时代。</p> @@ -4626,15 +4626,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </categories> </entry> <entry> - <title>二零二二年八月二十一日</title> - <url>/2022/08/21/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%BA%8C%E5%B9%B4%E5%85%AB%E6%9C%88%E4%BA%8C%E5%8D%81%E4%B8%80%E6%97%A5/</url> - <content><![CDATA[<p>不管是什么样的生活,都只是为了让自己满意,我这样活着,没什么不满的,我很痛快。这世上所有东西,我留意它时,它就存在,不留意时,它就是虚无。幸运的是我留意的事物很少,不致于身心劳碌一生奔忙。岁月会锤炼我一副丰满的灵魂和清瘦的欲望。</p> -]]></content> - <categories> - <category>gallery</category> - </categories> - </entry> - <entry> <title>二零二二年九月二十二日</title> <url>/2022/09/22/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%BA%8C%E5%B9%B4%E4%B9%9D%E6%9C%88%E4%BA%8C%E5%8D%81%E4%BA%8C%E6%97%A5/</url> <content><![CDATA[<p>似是而非的脸,和我的吉他,低头呢喃细语,抬头语焉不详,麦浪,烈日,溪水,咳嗽,敲打,药丸碰撞罐壁。</p> @@ -4653,6 +4644,15 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� </categories> </entry> <entry> + <title>二零二二年八月二十一日</title> + <url>/2022/08/21/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%BA%8C%E5%B9%B4%E5%85%AB%E6%9C%88%E4%BA%8C%E5%8D%81%E4%B8%80%E6%97%A5/</url> + <content><![CDATA[<p>不管是什么样的生活,都只是为了让自己满意,我这样活着,没什么不满的,我很痛快。这世上所有东西,我留意它时,它就存在,不留意时,它就是虚无。幸运的是我留意的事物很少,不致于身心劳碌一生奔忙。岁月会锤炼我一副丰满的灵魂和清瘦的欲望。</p> +]]></content> + <categories> + <category>gallery</category> + </categories> + </entry> + <entry> <title>二零二二年十二月三十一日</title> <url>/2022/12/31/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%BA%8C%E5%B9%B4%E5%8D%81%E4%BA%8C%E6%9C%88%E4%B8%89%E5%8D%81%E4%B8%80%E6%97%A5/</url> <content><![CDATA[<p>到模糊不见,到夜色如烟。</p> @@ -6136,10 +6136,10 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� <entry> <title>房颤与血栓栓塞:从左心耳血流淤滞到缺血性卒中</title> <url>/2026/08/18/%E6%88%BF%E9%A2%A4%E4%B8%8E%E8%A1%80%E6%A0%93%E6%A0%93%E5%A1%9E%EF%BC%9A%E4%BB%8E%E5%B7%A6%E5%BF%83%E8%80%B3%E8%A1%80%E6%B5%81%E6%B7%A4%E6%BB%9E%E5%88%B0%E7%BC%BA%E8%A1%80%E6%80%A7%E5%8D%92%E4%B8%AD/</url> - <content><![CDATA[<p>房颤并不只是“心跳不规律”。它会破坏心房的有效收缩,使血液在左心耳等部位淤滞;同时,心房结构、内皮功能和凝血状态也会改变。血栓一旦脱落,随血流进入脑动脉,就可能引发缺血性卒中。房颤管理的核心,不是单纯把心率降下来,而是同时评估血栓风险、出血风险、症状和心律状态。</p> + <content><![CDATA[<p>房颤会削弱心房的有效收缩,使血液在左心耳等部位淤滞,并伴随心房结构、内皮功能和凝血状态改变。血栓一旦脱落,随血流进入脑动脉,就可能引发缺血性卒中。房颤管理需要同时评估血栓风险、出血风险、症状和心律状态,心率控制只是其中一项。</p> <h2 id="房颤是什么"><a class="header-anchor" href="#房颤是什么">¶</a>房颤是什么</h2> <p>房颤,即心房颤动,是一种常见的持续性心律失常。正常情况下,心房先有规律地收缩,把血液推入心室;房颤时,心房电活动变得快速、紊乱,心房不再进行有效的整体收缩,心室则以不规则节律搏动。</p> -<p>患者可能感到心悸、胸闷、乏力、活动耐量下降,也可能完全没有症状。无症状并不等于没有风险。房颤对脑卒中的影响,主要来自血流淤滞和血栓形成,而不是来自心跳“乱”本身。</p> +<p>患者可能感到心悸、胸闷、乏力和活动耐量下降,也可能完全没有症状。无症状不能排除血栓风险。房颤导致卒中的主要环节是血流淤滞和血栓形成,心跳不规则本身并不是栓塞的直接原因。</p> <p>房颤与年龄增长、高血压、心力衰竭、冠心病、瓣膜疾病、糖尿病、肥胖、睡眠呼吸暂停和甲状腺功能异常等因素有关。它还可能形成一个持续恶化的循环:房颤引起心房电和结构重塑,重塑又使房颤更容易持续。<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup><sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p> <h2 id="血栓为什么容易在房颤中形成"><a class="header-anchor" href="#血栓为什么容易在房颤中形成">¶</a>血栓为什么容易在房颤中形成</h2> <p>房颤相关血栓形成,通常可以从三个方面理解:血流淤滞、心房组织改变和凝血倾向增强。这三个方面对应经典的血栓形成机制,即 Virchow 三要素。</p> @@ -6148,41 +6148,41 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的� <p>房颤患者的血栓并不一定形成在心室,也不一定发生在全身静脉。典型的房颤相关血栓来自左心房,尤其是左心耳。血栓形成后,如果受到心房压力变化、心律恢复或血流剪切力影响,就可能脱落。</p> <h3 id="心房壁会发生结构和功能改变"><a class="header-anchor" href="#心房壁会发生结构和功能改变">¶</a>心房壁会发生结构和功能改变</h3> <p>长期房颤可能导致心房扩大、纤维化和局部内皮功能异常。心房内膜受到机械牵张和炎症信号影响后,抗凝和抗血小板的保护性状态可能减弱,促凝状态增强。</p> -<p>房颤与炎症、氧化应激、纤维化和内皮功能障碍之间存在相互作用。现有研究支持“房颤不是单纯电活动异常”的观点:它同时涉及心房结构、代谢、炎症和凝血网络。<sup class="footnote-ref"><a href="#fn1" id="fnref1:1">[1:1]</a></sup><sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup></p> +<p>房颤与炎症、氧化应激、纤维化和内皮功能障碍之间存在相互作用。现有研究表明,房颤同时涉及心房结构、代谢、炎症和凝血网络。<sup class="footnote-ref"><a href="#fn1" id="fnref1:1">[1:1]</a></sup><sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup></p> <h3 id="凝血系统可能被进一步激活"><a class="header-anchor" href="#凝血系统可能被进一步激活">¶</a>凝血系统可能被进一步激活</h3> <p>房颤患者可能出现血小板活化、凝血酶生成增加和纤维蛋白形成增强。高血压、心力衰竭、糖尿病、慢性肾病和高龄等因素,又会进一步提高凝血和血管损伤风险。</p> <p>因此,房颤相关血栓不是由单一因素造成的。即使患者某一次心电图没有记录到房颤,也不能据此认为血栓风险已经消失。房颤可能具有阵发性,血栓风险还受到基础疾病、既往卒中史和心房结构等因素影响。</p> <h2 id="血栓如何变成脑卒中"><a class="header-anchor" href="#血栓如何变成脑卒中">¶</a>血栓如何变成脑卒中</h2> <p>左心房内形成的血栓如果脱落,会进入左心室,再被泵入主动脉。血栓沿动脉进入脑循环后,可能堵塞颈内动脉、大脑中动脉或其他脑动脉分支,造成局部脑组织缺血和坏死。</p> -<p>这类卒中属于心源性栓塞。与某些小动脉粥样硬化性卒中相比,房颤相关卒中往往具有栓子较大、血管阻塞位置较近端、神经功能损害较重等特点,也更容易出现较大的梗死范围。</p> +<p>这类卒中属于心源性栓塞。与某些小动脉粥样硬化性卒中相比,房颤相关卒中的栓子往往较大,阻塞部位可能更近端,神经功能损害也可能更重,因此梗死范围可能更大。</p> <p>经典的 Framingham 研究把房颤确定为卒中的独立危险因素。后续指南和综述也持续把卒中预防放在房颤管理的核心位置。<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup><sup class="footnote-ref"><a href="#fn5" id="fnref5">[5]</a></sup></p> <p>血栓也可能进入其他器官,引起外周动脉栓塞、肠系膜缺血、肾梗死或脾梗死。不过,临床上最受关注的后果仍然是缺血性卒中和短暂性脑缺血发作。</p> <h2 id="房颤患者的风险并不完全相同"><a class="header-anchor" href="#房颤患者的风险并不完全相同">¶</a>房颤患者的风险并不完全相同</h2> -<p>不能只根据“有没有房颤”判断是否需要抗凝。房颤患者的血栓栓塞风险存在很大差异。</p> +<p>是否需要抗凝,取决于患者的整体血栓栓塞风险。不同房颤患者的风险并不相同。</p> <p>临床通常会使用风险评分工具估计卒中风险。常见因素包括心力衰竭、高血压、年龄、糖尿病、既往卒中或短暂性脑缺血发作、血管疾病以及女性性别等。2023 年美国指南和 2024 年欧洲指南都强调,抗凝决策应以经过验证的卒中风险评估为基础,同时结合患者偏好、出血风险和临床情境。<sup class="footnote-ref"><a href="#fn6" id="fnref6">[6]</a></sup><sup class="footnote-ref"><a href="#fn7" id="fnref7">[7]</a></sup></p> <p>风险评分的作用是帮助估计总体风险,不是替代临床判断。它也不能回答所有问题。例如,肾功能变化、近期出血、肿瘤、贫血、合并用药和高龄,都可能改变抗凝治疗的实际获益与风险。</p> -<p>出血风险评估同样重要,但“出血风险高”并不自动等于“不能抗凝”。更合理的做法是寻找可纠正因素,例如控制血压、减少不必要的抗血小板药物、避免非甾体抗炎药、处理贫血和改善肾功能监测。</p> +<p>出血风险升高时,应先寻找可纠正因素,例如控制血压、减少不必要的抗血小板药物、避免非甾体抗炎药、处理贫血和加强肾功能监测,再综合判断抗凝获益与出血风险。</p> <h2 id="抗凝药为什么能够预防卒中"><a class="header-anchor" href="#抗凝药为什么能够预防卒中">¶</a>抗凝药为什么能够预防卒中</h2> -<p>抗凝药的作用是抑制凝血级联,减少纤维蛋白血栓形成。它并不是把已经形成的血栓“瞬间溶解”,而是降低新血栓形成和原有血栓扩大的概率。</p> +<p>抗凝药通过抑制凝血级联,减少纤维蛋白血栓形成。它主要降低新血栓形成和原有血栓扩大的概率,不能替代针对急性血栓的溶栓或取栓治疗。</p> <p>目前,房颤卒中预防常用口服抗凝药包括直接口服抗凝药和华法林。直接口服抗凝药包括直接凝血酶抑制剂以及直接凝血因子 Xa 抑制剂。它们起效较快,通常不需要像华法林那样频繁调整国际标准化比值,但仍需要根据肾功能、年龄、体重、合并用药和适应证选择剂量。</p> <p>多项随机试验的个体患者数据分析显示,直接口服抗凝药总体上适合用于非瓣膜性房颤的卒中预防,且不同年龄和性别亚组的主要疗效方向相对一致。系统综述和网络荟萃分析也支持口服抗凝药预防房颤相关卒中,但不同药物在胃肠道出血、颅内出血和其他出血结局上并不完全相同。<sup class="footnote-ref"><a href="#fn8" id="fnref8">[8]</a></sup><sup class="footnote-ref"><a href="#fn9" id="fnref9">[9]</a></sup></p> -<p>这并不意味着直接口服抗凝药对所有房颤患者都优于华法林。机械心脏瓣膜患者、部分中重度二尖瓣狭窄患者,以及某些严重肾功能异常患者,需要按照具体指南和专科医生意见选择方案。药物种类不能只凭“新药”或“方便”来决定。</p> +<p>直接口服抗凝药并不适用于所有房颤患者。机械心脏瓣膜患者、部分中重度二尖瓣狭窄患者,以及某些严重肾功能异常患者,需要按照具体指南和专科医生意见选择方案。药物种类应依据适应证、肾功能、合并用药和出血风险确定。</p> <h2 id="抗血小板药不能简单替代抗凝药"><a class="header-anchor" href="#抗血小板药不能简单替代抗凝药">¶</a>抗血小板药不能简单替代抗凝药</h2> <p>阿司匹林等抗血小板药主要影响血小板功能,而房颤相关血栓形成往往以凝血和纤维蛋白网络为重要特征。对于需要抗凝的房颤患者,单用抗血小板药通常不能等效替代口服抗凝药。</p> <p>如果患者同时存在冠状动脉疾病、冠脉支架或急性冠脉综合征,医生可能在特定阶段联合使用抗凝药和抗血小板药。但联合治疗会增加出血风险,疗程和药物组合需要严格限定,不能自行长期叠加。</p> <h2 id="恢复窦律后,是否可以马上停抗凝"><a class="header-anchor" href="#恢复窦律后,是否可以马上停抗凝">¶</a>恢复窦律后,是否可以马上停抗凝</h2> <p>通常不能仅因为心律恢复正常,就立即停用抗凝药。</p> <p>房颤患者的卒中风险不仅由当天是否出现房颤决定,还与既往房颤负荷、心房结构、年龄和伴随疾病有关。部分患者会出现无症状复发,普通门诊心电图也可能无法捕捉阵发性房颤。</p> -<p>因此,抗凝治疗一般根据长期血栓栓塞风险决定,而不是只根据是否成功复律、消融或暂时没有症状决定。2024 年欧洲指南将房颤管理放在 AF-CARE 框架中,强调合并危险因素管理、避免卒中和血栓栓塞、减少症状以及动态评估治疗效果。<sup class="footnote-ref"><a href="#fn7" id="fnref7:1">[7:1]</a></sup></p> -<h2 id="房颤管理不只是“吃抗凝药”"><a class="header-anchor" href="#房颤管理不只是“吃抗凝药”">¶</a>房颤管理不只是“吃抗凝药”</h2> -<p>房颤的完整管理需要同时处理四个问题:评估并降低卒中和血栓栓塞风险;控制心率或恢复并维持窦律,以改善症状和心脏功能;管理高血压、肥胖、睡眠呼吸暂停、糖尿病、心力衰竭和饮酒等危险因素;持续监测药物安全性、肾功能、出血表现和房颤复发。</p> -<p>节律控制可能改善症状,也可能在部分患者中改善长期结局,但它不能自动替代卒中风险评估。消融治疗也不等于血栓风险永久消失。治疗目标应从“消灭一次房颤”转向长期降低房颤负荷、卒中风险和心血管并发症。</p> +<p>抗凝治疗通常依据长期血栓栓塞风险决定。复律、消融或症状暂时消失,并不能单独决定停药。2024 年欧洲指南将房颤管理放在 AF-CARE 框架中,强调危险因素管理、卒中和血栓栓塞预防、症状控制以及治疗效果的动态评估。<sup class="footnote-ref"><a href="#fn7" id="fnref7:1">[7:1]</a></sup></p> +<h2 id="房颤管理的组成"><a class="header-anchor" href="#房颤管理的组成">¶</a>房颤管理的组成</h2> +<p>房颤管理包括四项工作。医生需要评估卒中和血栓栓塞风险,控制心率或恢复并维持窦律,处理高血压、肥胖、睡眠呼吸暂停、糖尿病、心力衰竭和饮酒等危险因素,并监测药物安全性、肾功能、出血表现和房颤复发。</p> +<p>节律控制可以改善症状,并可能改善部分患者的长期结局,但仍需持续评估卒中风险。消融治疗后也要根据长期风险决定抗凝方案。治疗目标是降低房颤负荷、卒中风险和心血管并发症。</p> <h2 id="需要立刻就医的表现"><a class="header-anchor" href="#需要立刻就医的表现">¶</a>需要立刻就医的表现</h2> <p>如果房颤患者突然出现单侧肢体无力或麻木、口角歪斜、说话含糊、理解困难、视物异常、行走不稳或突发剧烈头痛,应立即按照卒中急救流程就医。症状即使自行缓解,也可能是短暂性脑缺血发作,不能等待观察。</p> <p>正在服用抗凝药的患者,如果出现持续性呕血、黑便、血尿、无法止住的鼻出血、严重头痛、意识变化或跌倒撞击头部,也需要尽快就医。不要因为担心出血而自行停药,也不要因为漏服一次而擅自加倍补服。</p> <h2 id="结语"><a class="header-anchor" href="#结语">¶</a>结语</h2> -<p>房颤与血栓栓塞的关系,可以归纳为一条连续的病理链:<strong>心房失去有效收缩,血流在左心耳等部位淤滞;心房结构、内皮和凝血状态发生改变;血栓形成并可能脱落;栓子进入脑循环后造成缺血性卒中。</strong></p> -<p>真正有价值的房颤管理,不是只关注心电图上的节律,也不是只关注心率数字,而是同时回答三个问题:患者的血栓风险有多高,抗凝的获益是否超过出血风险,以及如何降低房颤本身和相关基础疾病的负荷。对个体患者而言,具体药物、剂量和停药时机必须由医生结合病史、肾功能、年龄、合并用药和出血情况决定。</p> +<p>房颤增加血栓栓塞风险的过程可以概括为:<strong>心房失去有效收缩,血流在左心耳等部位淤滞;心房结构、内皮和凝血状态发生改变;血栓形成并可能脱落;栓子进入脑循环后造成缺血性卒中。</strong></p> +<p>房颤管理需要回答三个问题:患者的血栓风险有多高,抗凝获益是否超过出血风险,以及怎样降低房颤和相关基础疾病的负荷。具体药物、剂量和停药时机,应由医生结合病史、肾功能、年龄、合并用药和出血情况决定。</p> <h2 id="References"><a class="header-anchor" href="#References">¶</a>References</h2> <hr class="footnotes-sep"> <section class="footnotes"> |
