From 153910d9a2c00d1abc492f330964329ef5ec6d75 Mon Sep 17 00:00:00 2001 From: somhairle-marisol Date: Sat, 21 Mar 2026 06:12:05 +0000 Subject: deploy: 94813271db231824040a4fd49167a5351987077d --- search.xml | 789 ++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 420 insertions(+), 369 deletions(-) (limited to 'search.xml') diff --git a/search.xml b/search.xml index 9dd24390..fe5473fd 100644 --- a/search.xml +++ b/search.xml @@ -1096,40 +1096,6 @@ await prisma.$transaction(async tx => {

正确顺序:

location = /api/auth/me {
# 精确匹配,业务身份接口
rewrite ^/api(.*)$ $1 break;
proxy_pass http://host.docker.internal:<backend>;
}

location ^~ /api/auth/ {
# 认证框架路由,保留前缀
proxy_pass http://host.docker.internal:<auth-port>;
}

location ^~ /api/ {
# 其余 API,裁掉 /api
rewrite ^/api(.*)$ $1 break;
proxy_pass http://host.docker.internal:<backend>;
}

location / {
# 前端
proxy_pass http://host.docker.internal:<frontend>;
}

这里的顺序很重要。Nginx 匹配规则是:精确匹配 = 优先,然后是前缀匹配 ^~,然后是正则匹配,最后是普通前缀匹配。^~ 表示一旦匹配成功就不再检查正则。

-]]> - - Technique - - - - OCaml Core.Int.pow 的实现 - /2023/10/21/OCaml-Core-Int-pow-%E7%9A%84%E5%AE%9E%E7%8E%B0/ - Core内部直接引用的Base的实现, Base的实现在 src/int_math.ml 中:

-
let int_pow base exponent =
if exponent < 0 then negative_exponent ();
if abs base > 1
&& (exponent > 63
|| abs base > Pow_overflow_bounds.int_positive_overflow_bounds.(exponent))
then overflow ();
int_math_int_pow base exponent
;;
-

其中 int_math_int_pow() 由 C 实现:

-
external int_math_int_pow : int -> int -> int = "Base_int_math_int_pow_stub" [@@noalloc]
-

其实现在 src/int_math_stubs.c 中:

-
static int64_t int_pow(int64_t base, int64_t exponent) {
int64_t ret = 1;
int64_t mul[4];
mul[0] = 1;
mul[1] = base;
mul[3] = 1;

while (exponent != 0) {
mul[1] *= mul[3];
mul[2] = mul[1] * mul[1];
mul[3] = mul[2] * mul[1];
ret *= mul[exponent & 3];
exponent >>= 2;
}

return ret;
}
-

这是一个四分快速幂的实现,它是二分快速幂的一种变种。二分快速幂将指数分为两部分,然后递归地计算每一部分的结果。
-而四分快速幂将指数分为四部分,然后递归地计算每一部分的结果。
-这里通过将指数右移2位(相当于除以4)和使用位与操作来实现,进一步减少了乘法次数。

-

主要步骤:

-
    -
  • 初始化返回值ret为1,和一个包含4个元素的数组mul, mul[0]和mul[3]被初始化为1, mul[1]被初始化为基数
  • -
  • 当指数不为0时,执行循环, 在每次循环中,首先更新mul数组的值 -
    -

    mul[1]是基数和mul[3]的乘积,mul[2]是mul[1]的平方,mul[3]是mul[2]和基数的乘积

    -
    -
  • -
  • 然后,将ret乘以mul数组中的一个元素, 这个元素的索引是指数和3的位与运算的结果 -
    -

    这样做的目的是为了选择正确的乘数,因为指数被分解为4的倍数

    -
    -
  • -
  • 最后,将指数右移2位,相当于将指数除以4
  • -
  • 当指数变为0时,循环结束,返回ret
  • -
-

这个实现的优点是它可以在对数时间内计算出幂运算,而且每次循环只需要4次乘法。这比标准的二分快速幂算法需要的乘法次数更少。

]]>
Technique @@ -1193,6 +1159,40 @@ await prisma.$transaction(async tx => {
  • RFSM 2.0: A toolset for describing and simulating StateChart-like state diagrams.
  • Zbg 2.0: zbg (short for Zero Bullshit Git) is a CLI tool for using git efficiently.
  • +]]> + + Technique + +
    + + OCaml Core.Int.pow 的实现 + /2023/10/21/OCaml-Core-Int-pow-%E7%9A%84%E5%AE%9E%E7%8E%B0/ + Core内部直接引用的Base的实现, Base的实现在 src/int_math.ml 中:

    +
    let int_pow base exponent =
    if exponent < 0 then negative_exponent ();
    if abs base > 1
    && (exponent > 63
    || abs base > Pow_overflow_bounds.int_positive_overflow_bounds.(exponent))
    then overflow ();
    int_math_int_pow base exponent
    ;;
    +

    其中 int_math_int_pow() 由 C 实现:

    +
    external int_math_int_pow : int -> int -> int = "Base_int_math_int_pow_stub" [@@noalloc]
    +

    其实现在 src/int_math_stubs.c 中:

    +
    static int64_t int_pow(int64_t base, int64_t exponent) {
    int64_t ret = 1;
    int64_t mul[4];
    mul[0] = 1;
    mul[1] = base;
    mul[3] = 1;

    while (exponent != 0) {
    mul[1] *= mul[3];
    mul[2] = mul[1] * mul[1];
    mul[3] = mul[2] * mul[1];
    ret *= mul[exponent & 3];
    exponent >>= 2;
    }

    return ret;
    }
    +

    这是一个四分快速幂的实现,它是二分快速幂的一种变种。二分快速幂将指数分为两部分,然后递归地计算每一部分的结果。
    +而四分快速幂将指数分为四部分,然后递归地计算每一部分的结果。
    +这里通过将指数右移2位(相当于除以4)和使用位与操作来实现,进一步减少了乘法次数。

    +

    主要步骤:

    +
      +
    • 初始化返回值ret为1,和一个包含4个元素的数组mul, mul[0]和mul[3]被初始化为1, mul[1]被初始化为基数
    • +
    • 当指数不为0时,执行循环, 在每次循环中,首先更新mul数组的值 +
      +

      mul[1]是基数和mul[3]的乘积,mul[2]是mul[1]的平方,mul[3]是mul[2]和基数的乘积

      +
      +
    • +
    • 然后,将ret乘以mul数组中的一个元素, 这个元素的索引是指数和3的位与运算的结果 +
      +

      这样做的目的是为了选择正确的乘数,因为指数被分解为4的倍数

      +
      +
    • +
    • 最后,将指数右移2位,相当于将指数除以4
    • +
    • 当指数变为0时,循环结束,返回ret
    • +
    +

    这个实现的优点是它可以在对数时间内计算出幂运算,而且每次循环只需要4次乘法。这比标准的二分快速幂算法需要的乘法次数更少。

    ]]>
    Technique @@ -2132,6 +2132,33 @@ A relation table (also sometimes called a JOIN, link or pi Medicine
    + + Rescript and React Native + /2024/01/22/Rescript-React-Native/ + Rescript 对 React Native 的支持还算可以,但如今的 RN 已经是 Expo 的 RN 了,所以仍然有些落后,在这个 template 中,我测试了 rescript + rescript-react-native + expo + nativewind 的组合,可以跑起来:

    + +

    其中有两个问题,一是 rescript-expo 对 rescript v11 的兼容性,我通过简单的注释让其通过编译:

    + +

    二是对于 nativewind 的支持,编写的 binding 非常丑陋:

    +
    type default_style = {
    className?: string,
    children?: React.element,
    }

    @module("nativewind")
    external styled: React.component<'a> => React.component<default_style> = "styled"

    module StyledText = {
    let make = styled(ReactNative.Text.make)
    }

    module StyledView = {
    let make = styled(ReactNative.View.make)
    }

    module StyledImage = {
    type props = {
    source?: string,
    ...default_style,
    }

    @module("nativewind")
    external styled: React.component<'a> => React.component<props> = "styled"

    let make: React.component<props> = styled(ReactNative.Image.make)
    }
    +

    而这似乎并没有好的解决方案,理想中的实现应该是:

    +
    module Styled = (Component: {
    type params // or type params = 'a
    let make: React.component<props>
    }) => {
    type props = {
    ...Component.props, // This can't work as it is not defined as record
    className?: string
    }
    @module("nativewind")
    external styled: React.component<Component.props> => React.component<props> = "styled"

    let make: React.component<props> = styled(Component.make)
    }
    +

    或者退一步:

    +
    type styledProps = {
    className?: string,
    // ...
    }

    @module("nativewind")
    external styled: React.component<'a> => React.component<'b> = "styled"

    module StyledText = {
    type props = {
    ...ReactNative.Text.props,
    ...styledProps
    }

    let make: React.component<props> = styled(ReactNative.Text.make)
    }
    +

    但这在当前的 rescript 中,根本无法实现。

    +

    详细的讨论看这个帖子:

    + +]]>
    + + Technique + +
    Repository pattern in Typescript /2025/02/19/Repository-pattern-in-Typescript/ @@ -2166,33 +2193,6 @@ A relation table (also sometimes called a JOIN, link or pi

    Sunday, March 30, 2025 7:58 PM:

    感觉以上全错,不应该过度抽象 Repository 的,就让它分散在各个模块中,还可以参考 https://github.com/Papooch/nestjs-cls 实现不会抽象泄漏的事务性 Repository 方法。

    -]]> - - Technique - -
    - - Rescript and React Native - /2024/01/22/Rescript-React-Native/ - Rescript 对 React Native 的支持还算可以,但如今的 RN 已经是 Expo 的 RN 了,所以仍然有些落后,在这个 template 中,我测试了 rescript + rescript-react-native + expo + nativewind 的组合,可以跑起来:

    - -

    其中有两个问题,一是 rescript-expo 对 rescript v11 的兼容性,我通过简单的注释让其通过编译:

    - -

    二是对于 nativewind 的支持,编写的 binding 非常丑陋:

    -
    type default_style = {
    className?: string,
    children?: React.element,
    }

    @module("nativewind")
    external styled: React.component<'a> => React.component<default_style> = "styled"

    module StyledText = {
    let make = styled(ReactNative.Text.make)
    }

    module StyledView = {
    let make = styled(ReactNative.View.make)
    }

    module StyledImage = {
    type props = {
    source?: string,
    ...default_style,
    }

    @module("nativewind")
    external styled: React.component<'a> => React.component<props> = "styled"

    let make: React.component<props> = styled(ReactNative.Image.make)
    }
    -

    而这似乎并没有好的解决方案,理想中的实现应该是:

    -
    module Styled = (Component: {
    type params // or type params = 'a
    let make: React.component<props>
    }) => {
    type props = {
    ...Component.props, // This can't work as it is not defined as record
    className?: string
    }
    @module("nativewind")
    external styled: React.component<Component.props> => React.component<props> = "styled"

    let make: React.component<props> = styled(Component.make)
    }
    -

    或者退一步:

    -
    type styledProps = {
    className?: string,
    // ...
    }

    @module("nativewind")
    external styled: React.component<'a> => React.component<'b> = "styled"

    module StyledText = {
    type props = {
    ...ReactNative.Text.props,
    ...styledProps
    }

    let make: React.component<props> = styled(ReactNative.Text.make)
    }
    -

    但这在当前的 rescript 中,根本无法实现。

    -

    详细的讨论看这个帖子:

    - ]]>
    Technique @@ -2219,6 +2219,28 @@ A relation table (also sometimes called a JOIN, link or pi

    现在生成的 TypeScript 文件将会从 Js.shim.ts import 类型:

    import type {Json_t as Js_Json_t} from '../../src/model/Js.shim.ts';
    +]]> + + Technique + +
    + + Rust NewType 模式 + /2023/05/03/Rust-NewType-%E6%A8%A1%E5%BC%8F/ + New Type模式是一种软件设计模式,用于在已有类型的基础上创建一个新的类型。在Rust中,这通常是通过定义一个结构体,其中只包含一个单一成员。这个结构体(New Type)对外提供了一个新的、独立的类型,用于对原始类型增加额外的语义或限制。

    +

    加强类型安全

    +
    struct Meters(f64);
    struct Feet(f64);

    let length_in_meters = Meters(100.0);
    let length_in_feet = Feet(328.084);

    // 编译器会防止以下代码执行,因为类型不匹配
    // let wrong_length = Meters(length_in_feet); // 编译错误

    // 正确的构造
    fn add_lengths(length1: Meters, length2: Meters) -> Meters {
    Meters(length1.0 + length2.0)
    }
    +

    这个例子使用 newtype 模式避免将原始类型f64用于不同的量度,从而增强了类型的安全性。

    +

    实现特定 trait

    +
    struct Kilometers(f64);

    impl Kilometers {
    fn to_miles(&self) -> f64 {
    self.0 * 0.621371
    }
    }

    let distance = Kilometers(10.0);
    println!("The distance in miles is {}", distance.to_miles());
    +

    这里,Kilometers有一个方法to_miles,该方法是不会影响其他f64数据的。如果我们有另一个表示温度的f64类型,就不会意外调用到与距离相关的方法。

    +

    New Type模式同样适用于对Box<dyn SomeTrait>类型的包装,这可以在需要动态分派(动态调用实现了某个接口的不同类型的对象的方法)的时候提供便利。通过创建一个New Type来包装这样的Box<dyn SomeTrait>类型,可以提供自定义的方法或实现更多的trait,同时也可以让API更加清晰和易于使用。

    +

    零成本抽象

    +

    在Rust中,New Type模式不仅是类型安全的,还是一种零成本抽象。这是因为Rust编译器在编译时期会进行足够的优化,以确保New Type的使用没有运行时开销。 Rust的零成本抽象原则确保了抽象不会引入额外的运行时成本。例如,当你使用Meters这样的New Type时,Rust确保:

    +
      +
    1. 无额外内存开销:Meters只包含一个f64,在内存中的表现和单独的f64是一样的。
    2. +
    3. 无额外运行时开销:使用Meters时,性能和直接使用f64完全相同。编译器会移除任何关于New Type的包装和解包的代码。
    4. +
    ]]>
    Technique @@ -2293,28 +2315,6 @@ A relation table (also sometimes called a JOIN, link or pi
    1. Vtable format to support dyn upcasting coercion https://rust-lang.github.io/dyn-upcasting-coercion-initiative/design-discussions/vtable-layout.*html*
    -]]> - - Technique - -
    - - Rust NewType 模式 - /2023/05/03/Rust-NewType-%E6%A8%A1%E5%BC%8F/ - New Type模式是一种软件设计模式,用于在已有类型的基础上创建一个新的类型。在Rust中,这通常是通过定义一个结构体,其中只包含一个单一成员。这个结构体(New Type)对外提供了一个新的、独立的类型,用于对原始类型增加额外的语义或限制。

    -

    加强类型安全

    -
    struct Meters(f64);
    struct Feet(f64);

    let length_in_meters = Meters(100.0);
    let length_in_feet = Feet(328.084);

    // 编译器会防止以下代码执行,因为类型不匹配
    // let wrong_length = Meters(length_in_feet); // 编译错误

    // 正确的构造
    fn add_lengths(length1: Meters, length2: Meters) -> Meters {
    Meters(length1.0 + length2.0)
    }
    -

    这个例子使用 newtype 模式避免将原始类型f64用于不同的量度,从而增强了类型的安全性。

    -

    实现特定 trait

    -
    struct Kilometers(f64);

    impl Kilometers {
    fn to_miles(&self) -> f64 {
    self.0 * 0.621371
    }
    }

    let distance = Kilometers(10.0);
    println!("The distance in miles is {}", distance.to_miles());
    -

    这里,Kilometers有一个方法to_miles,该方法是不会影响其他f64数据的。如果我们有另一个表示温度的f64类型,就不会意外调用到与距离相关的方法。

    -

    New Type模式同样适用于对Box<dyn SomeTrait>类型的包装,这可以在需要动态分派(动态调用实现了某个接口的不同类型的对象的方法)的时候提供便利。通过创建一个New Type来包装这样的Box<dyn SomeTrait>类型,可以提供自定义的方法或实现更多的trait,同时也可以让API更加清晰和易于使用。

    -

    零成本抽象

    -

    在Rust中,New Type模式不仅是类型安全的,还是一种零成本抽象。这是因为Rust编译器在编译时期会进行足够的优化,以确保New Type的使用没有运行时开销。 Rust的零成本抽象原则确保了抽象不会引入额外的运行时成本。例如,当你使用Meters这样的New Type时,Rust确保:

    -
      -
    1. 无额外内存开销:Meters只包含一个f64,在内存中的表现和单独的f64是一样的。
    2. -
    3. 无额外运行时开销:使用Meters时,性能和直接使用f64完全相同。编译器会移除任何关于New Type的包装和解包的代码。
    4. -
    ]]>
    Technique @@ -2336,46 +2336,6 @@ A relation table (also sometimes called a JOIN, link or pi Technique
    - - TDD 和 DDD 的一些小想法 - /2025/03/20/TDD-%E5%92%8C-DDD-%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B0%8F%E6%83%B3%E6%B3%95/ - Test-Driven Development (TDD) 和 Domain-Driven Design (DDD) 是两种不同的软件开发方法论,各自有其独特的优缺点和应用场景。

    -

    Test-Driven Development (TDD)

    -

    优点:

    -
      -
    1. 提高代码质量:通过编写测试来驱动开发,可以确保代码的正确性和可靠性。
    2. -
    3. 减少缺陷:早期发现和修复缺陷,减少后期的调试和维护成本。
    4. -
    5. 促进可维护性:代码更加模块化和可测试,便于后续的维护和扩展。
    6. -
    7. 文档化:测试代码本身就是一种文档,描述了系统的行为和期望。
    8. -
    9. 提高开发速度:虽然初期可能会慢一些,但长期来看,由于减少了重构和调试的时间,开发速度会提高。
    10. -
    -

    缺点:

    -
      -
    1. 初期投入大:需要在开发前编写测试,初期投入的时间和精力较大。
    2. -
    3. 学习曲线陡峭:对新手开发者来说,学习和掌握TDD需要一定的时间。
    4. -
    5. 过度依赖测试:可能导致过度依赖单元测试,忽略了系统的整体性能和用户体验。
    6. -
    -

    Domain-Driven Design (DDD)

    -

    优点:

    -
      -
    1. 聚焦领域:通过深入理解业务领域,确保软件系统与业务需求紧密结合。
    2. -
    3. 模型驱动:建立一个清晰的领域模型,帮助开发者和业务专家共同理解系统。
    4. -
    5. 可维护性:通过明确的领域模型和边界,系统结构更加清晰,便于维护和扩展。
    6. -
    7. 沟通桥梁:提供了一种通用的语言(Ubiquitous Language),促进开发团队和业务团队之间的沟通。
    8. -
    -

    缺点:

    -
      -
    1. 复杂性高:DDD方法论较为复杂,需要深入理解领域模型和设计模式。
    2. -
    3. 初期投入大:需要大量的时间和精力进行领域分析和建模。
    4. -
    5. 适用范围有限:对于简单的系统或小型项目,DDD可能显得过于复杂和冗余。
    6. -
    -
    -

    TDD 和 DDD 并不冲突,实际上它们可以互补。TDD 可以帮助确保代码的正确性和可靠性,而 DDD 可以确保系统与业务需求紧密结合。在进行 TDD 时,可以使用 DDD 的领域模型和 Ubiquitous Language 来编写测试用例,确保测试覆盖了业务需求。在进行 DDD 时,可以使用 TDD 来驱动实现,确保每个领域模型的实现。

    -]]>
    - - Technique - -
    Scala 3 (Capture Checking) /2025/03/25/Scala-3-Capture-Checking/ @@ -2526,6 +2486,46 @@ A relation table (also sometimes called a JOIN, link or pi

    Scala 3 提供了几个与 Capture Checking 相关的编译选项,。-Xprint:cc 选项会打印出带有推断捕获类型的程序代码 1。这对于理解编译器是如何推断捕获集的以及调试与类型相关的错误非常有用。另一个选项是 -Ycc-debug,它提供了关于 Capture Checking 过程的详细的、面向实现的的信息。这个选项对于需要深入了解 Capture Checking 器工作原理或者遇到复杂问题的开发者来说很有帮助。这些编译选项是使用 Capture Checking 的开发者的重要工具,它们允许开发者检查编译器的推理并诊断问题。

    Capture Checking被实现为一个传播约束求解器,它在标准的类型检查阶段之后运行,未知的捕获集由约束变量表示。在类型中显式编写的捕获集被求解器视为常量。类型之间的子类型要求会转化为它们各自捕获集上的子捕获测试。求解器根据程序的结构和类型约束,将 Capability 传播给约束变量及其超集。捕获集的映射受到类型参数的变性(协变、逆变、不变)的影响。装箱(boxing)和拆箱(unboxing)是用于隐藏和恢复捕获集的虚拟操作,特别是在类型参数的上下文中用于管理捕获隧道。-Ycc-debug 的输出提供了关于变量依赖关系和Capture Checking器在编译过程中的状态的深入信息。

    +]]> + + Technique + +
    + + TDD 和 DDD 的一些小想法 + /2025/03/20/TDD-%E5%92%8C-DDD-%E7%9A%84%E4%B8%80%E4%BA%9B%E5%B0%8F%E6%83%B3%E6%B3%95/ + Test-Driven Development (TDD) 和 Domain-Driven Design (DDD) 是两种不同的软件开发方法论,各自有其独特的优缺点和应用场景。

    +

    Test-Driven Development (TDD)

    +

    优点:

    +
      +
    1. 提高代码质量:通过编写测试来驱动开发,可以确保代码的正确性和可靠性。
    2. +
    3. 减少缺陷:早期发现和修复缺陷,减少后期的调试和维护成本。
    4. +
    5. 促进可维护性:代码更加模块化和可测试,便于后续的维护和扩展。
    6. +
    7. 文档化:测试代码本身就是一种文档,描述了系统的行为和期望。
    8. +
    9. 提高开发速度:虽然初期可能会慢一些,但长期来看,由于减少了重构和调试的时间,开发速度会提高。
    10. +
    +

    缺点:

    +
      +
    1. 初期投入大:需要在开发前编写测试,初期投入的时间和精力较大。
    2. +
    3. 学习曲线陡峭:对新手开发者来说,学习和掌握TDD需要一定的时间。
    4. +
    5. 过度依赖测试:可能导致过度依赖单元测试,忽略了系统的整体性能和用户体验。
    6. +
    +

    Domain-Driven Design (DDD)

    +

    优点:

    +
      +
    1. 聚焦领域:通过深入理解业务领域,确保软件系统与业务需求紧密结合。
    2. +
    3. 模型驱动:建立一个清晰的领域模型,帮助开发者和业务专家共同理解系统。
    4. +
    5. 可维护性:通过明确的领域模型和边界,系统结构更加清晰,便于维护和扩展。
    6. +
    7. 沟通桥梁:提供了一种通用的语言(Ubiquitous Language),促进开发团队和业务团队之间的沟通。
    8. +
    +

    缺点:

    +
      +
    1. 复杂性高:DDD方法论较为复杂,需要深入理解领域模型和设计模式。
    2. +
    3. 初期投入大:需要大量的时间和精力进行领域分析和建模。
    4. +
    5. 适用范围有限:对于简单的系统或小型项目,DDD可能显得过于复杂和冗余。
    6. +
    +
    +

    TDD 和 DDD 并不冲突,实际上它们可以互补。TDD 可以帮助确保代码的正确性和可靠性,而 DDD 可以确保系统与业务需求紧密结合。在进行 TDD 时,可以使用 DDD 的领域模型和 Ubiquitous Language 来编写测试用例,确保测试覆盖了业务需求。在进行 DDD 时,可以使用 TDD 来驱动实现,确保每个领域模型的实现。

    ]]>
    Technique @@ -3517,17 +3517,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 Life
    - - tick thread在Multicore OCaml中的作用 - /2023/08/15/tick-thread%E5%9C%A8Multicore-OCaml%E4%B8%AD%E7%9A%84%E4%BD%9C%E7%94%A8/ - Multicore OCaml的程序在启动时会运行一个 tick thread,其实现如下:

    -
    /* The tick thread: posts a SIGPREEMPTION signal periodically */

    static void * caml_thread_tick(void * arg)
    {
    struct timeval timeout;
    sigset_t mask;

    /* Block all signals so that we don't try to execute an OCaml signal handler*/
    sigfillset(&mask);
    pthread_sigmask(SIG_BLOCK, &mask, NULL);
    while(! caml_tick_thread_stop) {
    /* select() seems to be the most efficient way to suspend the
    thread for sub-second intervals */
    timeout.tv_sec = 0;
    timeout.tv_usec = Thread_timeout * 1000;
    select(0, NULL, NULL, NULL, &timeout);
    /* The preemption signal should never cause a callback, so don't
    go through caml_handle_signal(), just record signal delivery via
    caml_record_signal(). */
    caml_record_signal(SIGPREEMPTION);
    }
    return NULL;
    }
    -

    这是因为Multicore OCaml的GC目前需要一个进程(或一个Domain)中的所有线程一起参与以避免并发访问。如果一个线程在system call上被阻塞,那么整个Domain就会被卡住,直到该线程可以参与当前的垃圾收集。为了避免这个问题,tick 线程可以代替被阻塞的线程执行垃圾收集操作。

    -]]>
    - - Technique - -
    使用 Runed intersection observer 与 tanstack query 实现通用的惰性加载组件 /2025/07/31/svelte-lazyquery/ @@ -3589,6 +3578,17 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的
  • https://runed.dev/docs/utilities/use-intersection-observer
  • https://tanstack.com/table/latest/docs/introduction
  • +]]> + + Technique + +
    + + tick thread在Multicore OCaml中的作用 + /2023/08/15/tick-thread%E5%9C%A8Multicore-OCaml%E4%B8%AD%E7%9A%84%E4%BD%9C%E7%94%A8/ + Multicore OCaml的程序在启动时会运行一个 tick thread,其实现如下:

    +
    /* The tick thread: posts a SIGPREEMPTION signal periodically */

    static void * caml_thread_tick(void * arg)
    {
    struct timeval timeout;
    sigset_t mask;

    /* Block all signals so that we don't try to execute an OCaml signal handler*/
    sigfillset(&mask);
    pthread_sigmask(SIG_BLOCK, &mask, NULL);
    while(! caml_tick_thread_stop) {
    /* select() seems to be the most efficient way to suspend the
    thread for sub-second intervals */
    timeout.tv_sec = 0;
    timeout.tv_usec = Thread_timeout * 1000;
    select(0, NULL, NULL, NULL, &timeout);
    /* The preemption signal should never cause a callback, so don't
    go through caml_handle_signal(), just record signal delivery via
    caml_record_signal(). */
    caml_record_signal(SIGPREEMPTION);
    }
    return NULL;
    }
    +

    这是因为Multicore OCaml的GC目前需要一个进程(或一个Domain)中的所有线程一起参与以避免并发访问。如果一个线程在system call上被阻塞,那么整个Domain就会被卡住,直到该线程可以参与当前的垃圾收集。为了避免这个问题,tick 线程可以代替被阻塞的线程执行垃圾收集操作。

    ]]>
    Technique @@ -3888,6 +3888,34 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    然后我就在山峰浮现出第一缕阳光时,笑着对这个世界说:“我爱你,会永远爱你,永远最爱你。”

    我爱这个因为有我而变得温柔的世界。

    这将是我与周遭的一切,热爱一生,共度一生的地方。

    +]]> + + gallery + +
    + + 二〇二五年三月十七日 + /2025/03/17/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E4%B8%89%E6%9C%88%E5%8D%81%E4%B8%83%E6%97%A5/ + 人类其实根本无法同时拥有对死亡的坦然和对生活的眷恋。

    +

    看见死亡,看见新生,看见朝露凝于叶尖,看见惊雷震碎山月,

    +

    它们是因缘聚散吗?

    +

    那露珠消散后去了哪里?

    +

    此有故彼有,此生故彼灭。

    +

    当晨光加热露珠表面至 深度时,表层水分子动能突破氢键束缚(键能约 )。
    +这种相变并非整齐划一的队列解散,而是呈现量子隧穿效应——单个水分子以 量级的涨落,在液态与气态间振荡,直到完全脱离范德华力作用半径(约 )。

    +

    逃逸的 H2O 分子并非直线升空,而是在空气分子碰撞下进行三维随机游走。
    +根据爱因斯坦-斯托克斯方程,其扩散系数 (25℃标准大气压)。这意味着单个水分子在1秒内将形成半径约 的概率云,与数十亿同伴共同形成水汽。

    +

    水中捞月,伸手时,涟漪碎了三千世界。

    +

    人类的眉睫处有十方虚空,三藏经书不过指月之指,

    +

    看见儿时门前溪水倒流,看见婴孩啼哭时眼底星河闪烁。

    +

    看见生死之幕薄如蝉翼,和众生颠倒梦想处。

    +

    某些水分子可能抵达对流层顶(约 ),在那里被极地涡旋捕获。

    +

    经过 天的环球旅行后,其中 的分子将逃逸到太空,携带着地球的熵增印记。

    +

    它们的氧原子核内,八个质子正以 年半衰期等待质子衰变。

    +

    诸法从本来,常自寂灭相。

    +

    那在这之前呢?

    +

    在这之前我刚吃完一碗辣椒炒肉,

    +

    香

    ]]>
    gallery @@ -3936,34 +3964,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    虽然我早已不强求世界上有这么一个人,

    但我会一直等待,甚至期待你的到来。

    我叫韩暮秋,幸会。

    -]]> - - gallery - -
    - - 二〇二五年三月十七日 - /2025/03/17/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E4%B8%89%E6%9C%88%E5%8D%81%E4%B8%83%E6%97%A5/ - 人类其实根本无法同时拥有对死亡的坦然和对生活的眷恋。

    -

    看见死亡,看见新生,看见朝露凝于叶尖,看见惊雷震碎山月,

    -

    它们是因缘聚散吗?

    -

    那露珠消散后去了哪里?

    -

    此有故彼有,此生故彼灭。

    -

    当晨光加热露珠表面至 深度时,表层水分子动能突破氢键束缚(键能约 )。
    -这种相变并非整齐划一的队列解散,而是呈现量子隧穿效应——单个水分子以 量级的涨落,在液态与气态间振荡,直到完全脱离范德华力作用半径(约 )。

    -

    逃逸的 H2O 分子并非直线升空,而是在空气分子碰撞下进行三维随机游走。
    -根据爱因斯坦-斯托克斯方程,其扩散系数 (25℃标准大气压)。这意味着单个水分子在1秒内将形成半径约 的概率云,与数十亿同伴共同形成水汽。

    -

    水中捞月,伸手时,涟漪碎了三千世界。

    -

    人类的眉睫处有十方虚空,三藏经书不过指月之指,

    -

    看见儿时门前溪水倒流,看见婴孩啼哭时眼底星河闪烁。

    -

    看见生死之幕薄如蝉翼,和众生颠倒梦想处。

    -

    某些水分子可能抵达对流层顶(约 ),在那里被极地涡旋捕获。

    -

    经过 天的环球旅行后,其中 的分子将逃逸到太空,携带着地球的熵增印记。

    -

    它们的氧原子核内,八个质子正以 年半衰期等待质子衰变。

    -

    诸法从本来,常自寂灭相。

    -

    那在这之前呢?

    -

    在这之前我刚吃完一碗辣椒炒肉,

    -

    香

    ]]>
    gallery @@ -4164,6 +4164,37 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    睡前我把被子铺在床上。藏青布贴着皮肤,暖得像奶奶的怀抱。我抱着被子滚了滚,闻到橘子皮的香,闻到太阳的香,闻到奶奶身旁桂花树的香味。

    桂花香,看不见摸不着,却一直绕着我。

    今年风里的桂花香比去年更浓,深夜的月光洒在被子上,我摸着被子上的补丁睡着了,梦里看见奶奶坐在阳台晒被子,阳光照在她的白发上,她回头对我笑:“乐乐,过来帮奶奶翻下被子。” 我跑过去,接过她手里的被子,指尖碰到她的手掌,暖得像晒过的太阳。

    +]]> + + gallery + +
    + + 二〇二五年十一月二十六日 + /2025/11/26/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E5%8D%81%E4%B8%80%E6%9C%88%E4%BA%8C%E5%8D%81%E5%85%AD%E6%97%A5/ + 我蹲在地铁口的台阶上,指尖戳着手机屏保里的枇杷树。屏幕亮度调得很低,像浸在晚春的雾里,那是去年清明回家拍的,老家后山上的枇杷树正开着花,淡白色的瓣子落得他肩头都是,陈桉站在树底下笑,说我像个移动的花筐。

    +

    吸了吸鼻子,风裹着槐花香吹过来,混着煎饼果子的油味,我把手机塞进包里。包里还装着早上买的包子,凉得发硬。

    +

    地铁呼啸着进站,我跟着人群挤上去,肩膀被撞了一下,电脑包的背带勒得锁骨发疼。旁边的阿姨抱着个小孩,小孩手里举着根糖葫芦,糖稀滴在我的裤子上,留下个黏糊糊的印子。我盯着那个印子,忽然想起小时候陈桉带我去镇上买糖葫芦,我咬得太急,糖稀粘在下巴上,陈桉用袖子给我擦,结果把我的脸擦得更花,两个人笑得上气不接下气,连卖糖葫芦的老爷爷都跟着笑。

    +

    "下一站,国贸。"地铁广播里的女声冷冰冰的。我拽了拽背包带,往车门方向挪了挪。国贸站的人永远那么多,像一群被挤散的蚂蚁,我跟着人流往出口走,鞋子踩在瓷砖上,发出沉闷的响声。

    +

    出了地铁口,天空已经暗下来,写字楼的灯光像撒了一地碎钻。我抬头看了眼自己公司的楼层,窗户里还亮着灯,叹了口气,摸出手机给同事发消息:“我到楼下了,需求改好了吗?”

    +

    同时回复得很快:"改好了,就等你过来审。"后面跟着个哭脸表情。

    +

    我把手机放进兜里,往写字楼走。路过便利店的时候停下来,买了瓶冰可乐。可乐罐贴在脸上,凉意透过皮肤渗进骨头里,想起去年晚春,陈桉带我去山上玩,我们坐在山顶的石头上,陈桉举着瓶橘子汽水,说:“暮秋,你看,山下的房子像不像积木?”,顺着她的手指看过去,真的,那些低矮的房子挤在一起,像我们小时候玩的积木。陈桉又说:“等我长大了,要站在更高的地方,看更远的风景。”,我问:"那我呢?"陈桉笑:“你当然要跟着我啊,不然谁给我买橘子汽水?”

    +

    回过神,接过可乐,付了钱。走出便利店,风更大了,吹得我的头发乱飘,用手理了理,继续往写字楼走。

    +

    加班到十点,出来的时候街上的人已经很少了,路灯把影子拉得很长。

    +

    我拦了辆出租车,报了地址。司机师傅开着收音机,里面在放老歌:“春风它吹醒了大地,吹绿了杨柳,吹开了桃花…”

    +

    我靠在椅背上,看着窗外的夜景,忽然想起老家的晚春。老家的晚春没有这么多灯光,只有星星,像撒在天上的碎银子。陈桉说,那些星星是祖先的眼睛,在看着我们。

    +

    老城区的巷子里,楼道里的灯坏了,我摸着墙往上走。走到三楼,掏出钥匙开门,门轴发出刺耳的响声。房间里很冷,把电脑放在桌子上,打开空调,然后去卫生间洗脸。镜子里的自己脸色苍白,眼睛下面有淡淡的黑眼圈,嘴唇干得裂开了。我挤了点洗面奶,揉出泡沫,往脸上抹。

    +

    洗完脸,坐在沙发上,打开电脑,翻出自己写的日记。日记里有很多关于陈桉的内容:“今天陈桉带我去山上摘枇杷,她爬树的时候摔了一跤,胳膊擦破了皮,却笑着给我递枇杷,说’这个最甜’”,“陈桉要去外地读书了,说等她毕业,要回来当老师,教孩子们认识星星”,“昨天梦见陈桉,她站在山巅,风把她的衬衫吹起来,像小时候那样喊我’暮秋,快上来’,但我怎么跑都靠近不了”…

    +

    手机在沙发上震动,我拿起来看,是陈桉的消息:"暮秋,今天山上的枇杷开始结果了,比去年的大。"后面跟着张照片,照片里的枇杷挂在枝头,青绿色的,像小拳头。

    +

    我盯着照片,手指在屏幕上划了划,回复:“我昨天梦见你了,你站在山巅喊我。”

    +

    陈桉回复得很快:"那是因为我想你了啊。"后面跟着个笑脸表情。

    +

    我笑了笑,把手机放下。走到窗户边,推开窗户,风卷着晚春的味道进来——有槐花香,有青草味,还有一点点枇杷花的味道。抬头看星星,天上的星星很少,但很亮,像家乡的星星。想起陈桉说的,那些星星是祖先的眼睛,在看着我们。

    +

    忽然听见楼下有猫叫,探出头去,看见一只黑白相间的猫,正蹲在楼下的台阶上,盯着我看,我对着猫招了招手,猫叫了一声,转身跑了。

    +

    再缩回身子,关上窗户,回到沙发上。拿起手机,又看了看陈桉的消息。然后,我打开日记,写下:“今天的晚春,风里有枇杷花的味道。我梦见陈桉,她站在山巅,喊我’暮秋,快上来’。我想,也许我该回去看看了。”

    +

    放下笔,静静地靠在沙发上,闭上眼睛。窗外的风还在吹,吹得窗帘沙沙作响。又想起小时候,陈桉带我去山上玩,我们躺在草地上,看星星。陈桉说:“暮秋,等我们老了,还要一起去山上看星星。”,我问:"那如果我走了呢?"陈桉说:“那我就站在山巅,喊你的名字,直到你听见。”

    +

    我笑了,眼泪却顺着眼角流下来,摸出手机,给陈桉发了条消息:“明年晚春,我回去陪你摘枇杷。”

    +

    消息送达那一刻,突然听见窗外的风里,有个熟悉的声音在喊:“暮秋,快上来。”

    +

    我睁开眼睛,窗外的星星很亮,像陈桉的眼睛。

    ]]>
    gallery @@ -4207,37 +4238,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    我眯着眼看远处的灯火,

    想那里面会不会有一盏,

    是你为我留的。

    -]]> - - gallery - -
    - - 二〇二五年十一月二十六日 - /2025/11/26/%E4%BA%8C%E3%80%87%E4%BA%8C%E4%BA%94%E5%B9%B4%E5%8D%81%E4%B8%80%E6%9C%88%E4%BA%8C%E5%8D%81%E5%85%AD%E6%97%A5/ - 我蹲在地铁口的台阶上,指尖戳着手机屏保里的枇杷树。屏幕亮度调得很低,像浸在晚春的雾里,那是去年清明回家拍的,老家后山上的枇杷树正开着花,淡白色的瓣子落得他肩头都是,陈桉站在树底下笑,说我像个移动的花筐。

    -

    吸了吸鼻子,风裹着槐花香吹过来,混着煎饼果子的油味,我把手机塞进包里。包里还装着早上买的包子,凉得发硬。

    -

    地铁呼啸着进站,我跟着人群挤上去,肩膀被撞了一下,电脑包的背带勒得锁骨发疼。旁边的阿姨抱着个小孩,小孩手里举着根糖葫芦,糖稀滴在我的裤子上,留下个黏糊糊的印子。我盯着那个印子,忽然想起小时候陈桉带我去镇上买糖葫芦,我咬得太急,糖稀粘在下巴上,陈桉用袖子给我擦,结果把我的脸擦得更花,两个人笑得上气不接下气,连卖糖葫芦的老爷爷都跟着笑。

    -

    "下一站,国贸。"地铁广播里的女声冷冰冰的。我拽了拽背包带,往车门方向挪了挪。国贸站的人永远那么多,像一群被挤散的蚂蚁,我跟着人流往出口走,鞋子踩在瓷砖上,发出沉闷的响声。

    -

    出了地铁口,天空已经暗下来,写字楼的灯光像撒了一地碎钻。我抬头看了眼自己公司的楼层,窗户里还亮着灯,叹了口气,摸出手机给同事发消息:“我到楼下了,需求改好了吗?”

    -

    同时回复得很快:"改好了,就等你过来审。"后面跟着个哭脸表情。

    -

    我把手机放进兜里,往写字楼走。路过便利店的时候停下来,买了瓶冰可乐。可乐罐贴在脸上,凉意透过皮肤渗进骨头里,想起去年晚春,陈桉带我去山上玩,我们坐在山顶的石头上,陈桉举着瓶橘子汽水,说:“暮秋,你看,山下的房子像不像积木?”,顺着她的手指看过去,真的,那些低矮的房子挤在一起,像我们小时候玩的积木。陈桉又说:“等我长大了,要站在更高的地方,看更远的风景。”,我问:"那我呢?"陈桉笑:“你当然要跟着我啊,不然谁给我买橘子汽水?”

    -

    回过神,接过可乐,付了钱。走出便利店,风更大了,吹得我的头发乱飘,用手理了理,继续往写字楼走。

    -

    加班到十点,出来的时候街上的人已经很少了,路灯把影子拉得很长。

    -

    我拦了辆出租车,报了地址。司机师傅开着收音机,里面在放老歌:“春风它吹醒了大地,吹绿了杨柳,吹开了桃花…”

    -

    我靠在椅背上,看着窗外的夜景,忽然想起老家的晚春。老家的晚春没有这么多灯光,只有星星,像撒在天上的碎银子。陈桉说,那些星星是祖先的眼睛,在看着我们。

    -

    老城区的巷子里,楼道里的灯坏了,我摸着墙往上走。走到三楼,掏出钥匙开门,门轴发出刺耳的响声。房间里很冷,把电脑放在桌子上,打开空调,然后去卫生间洗脸。镜子里的自己脸色苍白,眼睛下面有淡淡的黑眼圈,嘴唇干得裂开了。我挤了点洗面奶,揉出泡沫,往脸上抹。

    -

    洗完脸,坐在沙发上,打开电脑,翻出自己写的日记。日记里有很多关于陈桉的内容:“今天陈桉带我去山上摘枇杷,她爬树的时候摔了一跤,胳膊擦破了皮,却笑着给我递枇杷,说’这个最甜’”,“陈桉要去外地读书了,说等她毕业,要回来当老师,教孩子们认识星星”,“昨天梦见陈桉,她站在山巅,风把她的衬衫吹起来,像小时候那样喊我’暮秋,快上来’,但我怎么跑都靠近不了”…

    -

    手机在沙发上震动,我拿起来看,是陈桉的消息:"暮秋,今天山上的枇杷开始结果了,比去年的大。"后面跟着张照片,照片里的枇杷挂在枝头,青绿色的,像小拳头。

    -

    我盯着照片,手指在屏幕上划了划,回复:“我昨天梦见你了,你站在山巅喊我。”

    -

    陈桉回复得很快:"那是因为我想你了啊。"后面跟着个笑脸表情。

    -

    我笑了笑,把手机放下。走到窗户边,推开窗户,风卷着晚春的味道进来——有槐花香,有青草味,还有一点点枇杷花的味道。抬头看星星,天上的星星很少,但很亮,像家乡的星星。想起陈桉说的,那些星星是祖先的眼睛,在看着我们。

    -

    忽然听见楼下有猫叫,探出头去,看见一只黑白相间的猫,正蹲在楼下的台阶上,盯着我看,我对着猫招了招手,猫叫了一声,转身跑了。

    -

    再缩回身子,关上窗户,回到沙发上。拿起手机,又看了看陈桉的消息。然后,我打开日记,写下:“今天的晚春,风里有枇杷花的味道。我梦见陈桉,她站在山巅,喊我’暮秋,快上来’。我想,也许我该回去看看了。”

    -

    放下笔,静静地靠在沙发上,闭上眼睛。窗外的风还在吹,吹得窗帘沙沙作响。又想起小时候,陈桉带我去山上玩,我们躺在草地上,看星星。陈桉说:“暮秋,等我们老了,还要一起去山上看星星。”,我问:"那如果我走了呢?"陈桉说:“那我就站在山巅,喊你的名字,直到你听见。”

    -

    我笑了,眼泪却顺着眼角流下来,摸出手机,给陈桉发了条消息:“明年晚春,我回去陪你摘枇杷。”

    -

    消息送达那一刻,突然听见窗外的风里,有个熟悉的声音在喊:“暮秋,快上来。”

    -

    我睁开眼睛,窗外的星星很亮,像陈桉的眼睛。

    ]]>
    gallery @@ -4371,24 +4371,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    她忽然笑了一下,像把窗纱上的一个小结解开。她说:“我替你把玻璃擦亮了。你也在替我把风擦亮。”屋里很安静,灯温柔地照着。她把手伸到光里,让手指被照成透明。透明的东西容易破碎,却也比别的更亮。

    时间会带走了一个人,也带走了他的呼吸,他的声音,他掌心的温度。但似乎又留下了些什么

    光的颗粒在空气里浮动,在记忆里游走,她闭上眼睛,光从睫毛上滑过,像许多个早晨,像许多个傍晚,像她们再次相遇的无数个灿然的瞬间。她在其中走走停停,像一粒微尘,又像一颗星。

    -]]> - - gallery - -
    - - 二〇二五年十月三十日 - /2025/10/30/%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%E6%97%A5/ - 那是一个无云、风也很小的傍晚。天光从浅到深慢慢往你这边退,像有人慢慢把你从白昼里抱出来。街角的花店门口站着一只猫,它看着路过的车,耳朵偶尔动一下。屋檐下有风铃,轻轻的,叮的一声,时间像也跟着响了一下。

    -

    花店里里有一支黄玫瑰,花瓣像被烫过,从橙黄到金黄,层层叠叠,在傍晚的空气里慢慢散开一层暖色。它有甜味,甜里还带着一点苦,味道朦胧的像你在窗下读书,书页翻过时有阳光落下来,像你走过老城的石板路,脚下的石头被晒得很暖。

    -

    我记得那张粗糙的柜台,记得老板的手掌上有花叶的汁液。记得那种 “被允许站在这里” 的轻松。它没有要你改变什么,也没有给你安排一个更好的未来。它只是让你看见,一个傍晚可以用来想事,一条路可以一直往前延伸而不追问终点是什么。

    -

    从那以后,我偶尔还会闻到那种味道。公交车拐弯的时候,风从窗缝里钻进来,地铁门打开的时候,站台上一阵匆忙的脚步后,忽然静下来的那一刻。我不知道它为什么会出现,也不刻意寻找。它像朋友一样,来了就坐一会儿,走了也不会留下地址。

    -

    然后我遇见了你。

    -

    起初我们并肩走在一条街上,街灯刚亮,影子从脚边拖到墙根。你说话的时候,声音不急也不慢。我听见了你的过去,但不是像在听故事那样去比较谁更动人,我只是听着你。你给我看了你的手心,那上面有被太阳晒过后的浅色纹理,也有你习惯把指甲剪得很短的认真。我心里有一种安静,它不是从谁那里借来的,而是像从很远的地方自己走来的。

    -

    我知道你不是她,她是一种很亮的影子,只能在黑暗里存在。而你会在这里,在光亮里也在风里,在你的沉默里也在你的笑声里。时间不会让你跟她合在一起,时间只会让你在我旁边,成为一个可以站着的人,一个愿意陪我走过同一条街、同一种气味、同一种阳光的人。

    -

    于是我想给你那朵黄玫瑰的记忆。不是因为我想要你变成她的样子,而是因为我在你身上看见了自己喜欢的那种安静和从容。我想让你在今后的某一天,当公交门打开,站台忽然安静下来,当你走在一条熟悉的路上,风从树梢里穿过,当你坐在窗边,光从书页上滑过去的时候,能闻到那种味道,能想起那一刻的我们,我们在傍晚的风里站着,既不躲藏也不假装,我们把时间握在手里,像握着一朵温柔得让人不需要理由的花。

    -

    如果你愿意,我可以每天傍晚经过花店门口,我们可以把那种味道带回家,用一只透明的瓶子装起来,不需要解释它来自哪里,也不需要把它和任何过去比较。它只会在你需要的时候出现,像一个老朋友,提醒你,不是每一次都要变成别人,不是每一次都需要证明你已经忘记。你可以是现在的你,我可以是现在的我。我们可以在同一个黄昏里站一会儿,看看天空从浅到深,看看风从我们旁边走过,看看影子在我们的脚边拉长。

    -

    黄玫瑰的味道不会要求我们变成谁,它只会给我们一点轻的力气,让我们在时间里往前走走。我把这份力气给你,也给我自己,然后我们就接着往前走,带着这种不喧哗的光。

    ]]>
    gallery @@ -4427,6 +4409,24 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    他蜷缩在冰冷的水泥台上,看着自己的影子被月光钉死在斑驳的广告牌上。

    "除夕快乐。"他对广告牌上褪色的月饼海报说。海报里笑得喜庆的孩子嘴角开裂,被寒风吹起一角。

    雪落满了他的睫毛。

    +]]> + + gallery + +
    + + 二〇二五年十月三十日 + /2025/10/30/%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%E6%97%A5/ + 那是一个无云、风也很小的傍晚。天光从浅到深慢慢往你这边退,像有人慢慢把你从白昼里抱出来。街角的花店门口站着一只猫,它看着路过的车,耳朵偶尔动一下。屋檐下有风铃,轻轻的,叮的一声,时间像也跟着响了一下。

    +

    花店里里有一支黄玫瑰,花瓣像被烫过,从橙黄到金黄,层层叠叠,在傍晚的空气里慢慢散开一层暖色。它有甜味,甜里还带着一点苦,味道朦胧的像你在窗下读书,书页翻过时有阳光落下来,像你走过老城的石板路,脚下的石头被晒得很暖。

    +

    我记得那张粗糙的柜台,记得老板的手掌上有花叶的汁液。记得那种 “被允许站在这里” 的轻松。它没有要你改变什么,也没有给你安排一个更好的未来。它只是让你看见,一个傍晚可以用来想事,一条路可以一直往前延伸而不追问终点是什么。

    +

    从那以后,我偶尔还会闻到那种味道。公交车拐弯的时候,风从窗缝里钻进来,地铁门打开的时候,站台上一阵匆忙的脚步后,忽然静下来的那一刻。我不知道它为什么会出现,也不刻意寻找。它像朋友一样,来了就坐一会儿,走了也不会留下地址。

    +

    然后我遇见了你。

    +

    起初我们并肩走在一条街上,街灯刚亮,影子从脚边拖到墙根。你说话的时候,声音不急也不慢。我听见了你的过去,但不是像在听故事那样去比较谁更动人,我只是听着你。你给我看了你的手心,那上面有被太阳晒过后的浅色纹理,也有你习惯把指甲剪得很短的认真。我心里有一种安静,它不是从谁那里借来的,而是像从很远的地方自己走来的。

    +

    我知道你不是她,她是一种很亮的影子,只能在黑暗里存在。而你会在这里,在光亮里也在风里,在你的沉默里也在你的笑声里。时间不会让你跟她合在一起,时间只会让你在我旁边,成为一个可以站着的人,一个愿意陪我走过同一条街、同一种气味、同一种阳光的人。

    +

    于是我想给你那朵黄玫瑰的记忆。不是因为我想要你变成她的样子,而是因为我在你身上看见了自己喜欢的那种安静和从容。我想让你在今后的某一天,当公交门打开,站台忽然安静下来,当你走在一条熟悉的路上,风从树梢里穿过,当你坐在窗边,光从书页上滑过去的时候,能闻到那种味道,能想起那一刻的我们,我们在傍晚的风里站着,既不躲藏也不假装,我们把时间握在手里,像握着一朵温柔得让人不需要理由的花。

    +

    如果你愿意,我可以每天傍晚经过花店门口,我们可以把那种味道带回家,用一只透明的瓶子装起来,不需要解释它来自哪里,也不需要把它和任何过去比较。它只会在你需要的时候出现,像一个老朋友,提醒你,不是每一次都要变成别人,不是每一次都需要证明你已经忘记。你可以是现在的你,我可以是现在的我。我们可以在同一个黄昏里站一会儿,看看天空从浅到深,看看风从我们旁边走过,看看影子在我们的脚边拉长。

    +

    黄玫瑰的味道不会要求我们变成谁,它只会给我们一点轻的力气,让我们在时间里往前走走。我把这份力气给你,也给我自己,然后我们就接着往前走,带着这种不喧哗的光。

    ]]>
    gallery @@ -4506,29 +4506,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 人只不过是一根苇草,是自然界最脆弱的东西,但却是一根能思想的苇草。用不着整个宇宙都拿起武器来才能毁灭人类,一口气、一滴水就足以致人类于死命了。然而纵使宇宙毁灭了人类,在人类的视角下自己却仍然要比致其于死命的东西高贵得多,因为人类知道自己要死亡,以及宇宙对其所具有的优势,还可以主动地认为宇宙对此一无所知。

    寄蜉蝣于天地,渺沧海之一粟。人类在宇宙面前的大小不比一只金鱼大多少,金鱼不会知道在它生活的空间只是一名人类的玻璃缸,人类也无法得知在其看来无边无际的宇宙是不是只是高等文明收藏的一颗琥珀。

    思想——人的全部尊严就在于思想。

    -]]>
    - - gallery - -
    - - 二〇二四年二月七日 - /2024/02/07/%E4%BA%8C%E3%80%87%E4%BA%8C%E5%9B%9B%E5%B9%B4%E4%BA%8C%E6%9C%88%E4%B8%83%E6%97%A5/ - 瀑布的水逆流而上,
    -蒲公英种子从远处飘回,聚成伞的模样,
    -太阳从西边升起,落向东方。

    -

    子弹退回枪膛,
    -运动员回到起跑线上,
    -我交回录取通知书,忘了十年寒窗。

    -

    厨房里飘来饭菜的香,
    -你把我的卷子签好名字,
    -关掉电视,帮我把书包背上。

    -

    你还在我身旁。

    -
    -

    功利性主义总会引导我们思考结局,可生活本质上就是一场旅途。

    -

    别再去胡思乱想了,好好欣赏生活中的一切吧,去感受一朵鲜花的盛开,一束阳光的倾泻,一湖清水的静谧。

    -

    奶奶说,炉膛的柴火若是爆裂,那是有贵客要登门了。我总是不信。直到有一次,舅公伴着柴火的爆裂声,出现在家门口,我才将信将疑。

    -

    所以,让我们在下雨天烧柴火灶,在跳跃的火苗里,等待一种不确定的相逢,然后煮茶饮酒,自由一生 。

    ]]>
    gallery @@ -4554,6 +4531,29 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 然后我说人类好无聊啊
    这个地球完蛋了
    你点点头

    +]]> + + gallery + +
    + + 二〇二四年二月七日 + /2024/02/07/%E4%BA%8C%E3%80%87%E4%BA%8C%E5%9B%9B%E5%B9%B4%E4%BA%8C%E6%9C%88%E4%B8%83%E6%97%A5/ + 瀑布的水逆流而上,
    +蒲公英种子从远处飘回,聚成伞的模样,
    +太阳从西边升起,落向东方。

    +

    子弹退回枪膛,
    +运动员回到起跑线上,
    +我交回录取通知书,忘了十年寒窗。

    +

    厨房里飘来饭菜的香,
    +你把我的卷子签好名字,
    +关掉电视,帮我把书包背上。

    +

    你还在我身旁。

    +
    +

    功利性主义总会引导我们思考结局,可生活本质上就是一场旅途。

    +

    别再去胡思乱想了,好好欣赏生活中的一切吧,去感受一朵鲜花的盛开,一束阳光的倾泻,一湖清水的静谧。

    +

    奶奶说,炉膛的柴火若是爆裂,那是有贵客要登门了。我总是不信。直到有一次,舅公伴着柴火的爆裂声,出现在家门口,我才将信将疑。

    +

    所以,让我们在下雨天烧柴火灶,在跳跃的火苗里,等待一种不确定的相逢,然后煮茶饮酒,自由一生 。

    ]]>
    gallery @@ -4575,6 +4575,18 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 做一件事,无关心态,只要去做就是了。发挥最大的主观能动性,遇到问题就解决,碰到障碍就破开,需要帮助就求助。别因为所谓的自信就盲目冒进,也别因为所谓的自卑就胆小退缩,那俩玩意都不存在。

    尊重事情本身的客观发展规律,什么样的人匹配什么样的事,什么样的条件匹配什么样的理想。别把心态想得太重要,更别花太多时间精力去建设心态,客观规律不会因为心态就发生奇迹般地转变。

    你最好趁早学会尊重客观规律。

    +]]> + + gallery + +
    + + 二〇二四年八月一日 + /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/ + 阿公烧烟成老瘾,
    +阿公饮酒会面红,
    +他说日子要开心,
    +他说人生要尽兴。

    ]]>
    gallery @@ -4598,18 +4610,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    早上七点。

    太阳完全绽放的光芒照亮了这个欣欣向荣的世界,有山、有海,有自由。

    -]]> - - gallery - -
    - - 二〇二四年八月一日 - /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/ - 阿公烧烟成老瘾,
    -阿公饮酒会面红,
    -他说日子要开心,
    -他说人生要尽兴。

    ]]>
    gallery @@ -4690,6 +4690,15 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 二零二一年二月十四日 /2021/02/14/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%B8%80%E5%B9%B4%E4%BA%8C%E6%9C%88%E5%8D%81%E5%9B%9B%E6%97%A5/ 把酒从铁罐子里面赶出来,赶到脸上,被眼泪驱逐,混着干涸的血,揉着尘埃,尝尽酸甜苦辣

    +]]>
    + + gallery + +
    + + 二零二三年一月二十一日 + /2023/01/21/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%B8%89%E5%B9%B4%E4%B8%80%E6%9C%88%E4%BA%8C%E5%8D%81%E4%B8%80%E6%97%A5/ + 我踏着烟火若隐若现, 我生来就是为了把这个世界的消息带到下一个世界去。

    ]]>
    gallery @@ -4708,15 +4717,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    韩暮秋,息止安所。

    -]]> - - gallery - -
    - - 二零二三年一月二十一日 - /2023/01/21/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%B8%89%E5%B9%B4%E4%B8%80%E6%9C%88%E4%BA%8C%E5%8D%81%E4%B8%80%E6%97%A5/ - 我踏着烟火若隐若现, 我生来就是为了把这个世界的消息带到下一个世界去。

    ]]>
    gallery @@ -4775,21 +4775,21 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的
    - 二零二三年二月二日 - /2023/02/02/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%B8%89%E5%B9%B4%E4%BA%8C%E6%9C%88%E4%BA%8C%E6%97%A5/ - 我不知道这些黑乎乎的东西从哪来的,它们填满了这黄铜色的硬币每一处,像机油,像泥泞,它们随着这五角钱硬币一起从我手上换走了一袋糖果,那是一个蓬头垢面,满身泥灰的小孩,他起身看着这个给他糖果的叔叔,我蹲下看着我拥有糖果的童年。
    -​
    -​这事很恐怖,意味着我在这个地方又多了一丝牵挂,多了一个不离开的理由。

    + 二零二三年二月二十四日 + /2023/02/24/%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%E5%9B%9B%E6%97%A5/ + 黑色的袋子里装满了药,这种袋子我都是用来装垃圾的,它被昏昏欲睡的老人家抱在怀里,里面的药丸随着颠簸的车厢敲打罐子,老人家醒了,她的眼睛比袋子还浑浊。

    +

    她看着我说到站了,我拉着她的手下车,该回家了,等下还要去买支圆珠笔,中性笔没法在药盒子上写用法用量。

    ]]>
    gallery
    - 二零二三年二月二十四日 - /2023/02/24/%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%E5%9B%9B%E6%97%A5/ - 黑色的袋子里装满了药,这种袋子我都是用来装垃圾的,它被昏昏欲睡的老人家抱在怀里,里面的药丸随着颠簸的车厢敲打罐子,老人家醒了,她的眼睛比袋子还浑浊。

    -

    她看着我说到站了,我拉着她的手下车,该回家了,等下还要去买支圆珠笔,中性笔没法在药盒子上写用法用量。

    + 二零二三年二月二日 + /2023/02/02/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%B8%89%E5%B9%B4%E4%BA%8C%E6%9C%88%E4%BA%8C%E6%97%A5/ + 我不知道这些黑乎乎的东西从哪来的,它们填满了这黄铜色的硬币每一处,像机油,像泥泞,它们随着这五角钱硬币一起从我手上换走了一袋糖果,那是一个蓬头垢面,满身泥灰的小孩,他起身看着这个给他糖果的叔叔,我蹲下看着我拥有糖果的童年。
    +​
    +​这事很恐怖,意味着我在这个地方又多了一丝牵挂,多了一个不离开的理由。

    ]]>
    gallery @@ -4865,15 +4865,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    岁月中的某个文明中的某个人,曾经也骑着骆驼向远方,又是哪个人,吟着佛门心语站在我身旁,我听见大漠的念经声。

    我不是个有耐心的人, 抄经书对我来说是种折磨, 我只能选择把经文抄在大地上, 黄沙上, 用自己的双腿一点点写, 用耳去倾听狂风卷积的声音。

    我生来就是为了把这个世界的消息和秘密带到下一个世界去。

    -]]>
    - - gallery - -
    - - 二零二二年一月一日 - /2022/01/01/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%BA%8C%E5%B9%B4%E4%B8%80%E6%9C%88%E4%B8%80%E6%97%A5/ - 我在一声叹息中恍过了宁静和繁华,目的明确的化成一捧黄土,被风带到这树荫底下,看草萌木长,看人来人往。

    ]]>
    gallery @@ -4891,6 +4882,15 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    生活是属于每个人自己的感受,不属于任何别人的看法。

    人们时常流出浑浊的眼泪,并不是因为人们时常悲伤,人们在高兴时甚至是在什么事都没有的平静时刻,也会有泪流出,然后举起被社会,岁月刮伤的手指,擦去眼泪,如同掸去身上的稻草。

    也许是困苦的生活损坏了人们的记忆,面对往事人们时常显得木讷,常常以不知所措的微笑搪塞过去,对自己的经历缺乏热情,仿佛是道听途说般只记得零星几点,即便是这零星几点也都是自身之外的记忆,用一两句话表达了一切。

    +]]> + + gallery + +
    + + 二零二二年一月一日 + /2022/01/01/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E4%BA%8C%E5%B9%B4%E4%B8%80%E6%9C%88%E4%B8%80%E6%97%A5/ + 我在一声叹息中恍过了宁静和繁华,目的明确的化成一捧黄土,被风带到这树荫底下,看草萌木长,看人来人往。

    ]]>
    gallery @@ -4960,18 +4960,18 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的
    - 二零二零年十二月八日 - /2020/12/08/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E9%9B%B6%E5%B9%B4%E5%8D%81%E4%BA%8C%E6%9C%88%E5%85%AB%E6%97%A5/ - 它们勇敢的站在烈日下, 像人类一样无所畏惧, 却又恐惧的躲在黑暗中, 像当初的它们一样无处可逃。

    + 二零二零年十二月二十六日 + /2020/12/26/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E9%9B%B6%E5%B9%B4%E5%8D%81%E4%BA%8C%E6%9C%88%E4%BA%8C%E5%8D%81%E5%85%AD%E6%97%A5/ + 我被包裹在吊床中,两颗香樟树支撑着它,仲夏的热浪砸在粗壮的香樟树上,叶子一屈,抱住了热浪,可随即而来的是不知从哪浇来的阳光,叶子承受不住这样的力量,被砸的孔洞百出,砸的香气落满树下的人间。

    ]]>
    gallery
    - 二零二零年十二月二十六日 - /2020/12/26/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E9%9B%B6%E5%B9%B4%E5%8D%81%E4%BA%8C%E6%9C%88%E4%BA%8C%E5%8D%81%E5%85%AD%E6%97%A5/ - 我被包裹在吊床中,两颗香樟树支撑着它,仲夏的热浪砸在粗壮的香樟树上,叶子一屈,抱住了热浪,可随即而来的是不知从哪浇来的阳光,叶子承受不住这样的力量,被砸的孔洞百出,砸的香气落满树下的人间。

    + 二零二零年十二月八日 + /2020/12/08/%E4%BA%8C%E9%9B%B6%E4%BA%8C%E9%9B%B6%E5%B9%B4%E5%8D%81%E4%BA%8C%E6%9C%88%E5%85%AB%E6%97%A5/ + 它们勇敢的站在烈日下, 像人类一样无所畏惧, 却又恐惧的躲在黑暗中, 像当初的它们一样无处可逃。

    ]]>
    gallery @@ -5579,6 +5579,84 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 Design
    + + 实时搜索中的防抖函数 + /2025/02/10/%E5%AE%9E%E6%97%B6%E6%90%9C%E7%B4%A2%E4%B8%AD%E7%9A%84%E9%98%B2%E6%8A%96%E5%87%BD%E6%95%B0/ + 在实现实时搜索功能时,通常会使用输入框的事件监听器来捕获用户的输入变化,并在输入变化时发送搜索请求。为了避免过多的请求导致服务器负担过重,通常会使用“防抖”(debounce)技术来控制请求的频率。

    +

    实现步骤

    +
      +
    1. 监听输入框的变化:使用input事件监听器来捕获用户的输入变化。
    2. +
    3. 防抖处理:使用防抖函数来限制请求的频率。防抖函数会在用户停止输入一段时间后才发送请求。
    4. +
    5. 发送请求:在防抖函数中调用搜索请求。
    6. +
    +

    防抖函数示例

    +

    以下是一个简单的防抖函数示例:

    +
    function debounce(func, wait) {
    let timeout;
    return function(...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
    };
    }
    +

    实现实时搜索

    +

    假设你有一个输入框用于搜索患者:

    +
    <script>
    import { onMount } from 'svelte';
    import { patientsStore } from '$lib/stores/patients.svelte';

    let searchTerm = '';

    // 防抖函数
    function debounce(func, wait) {
    let timeout;
    return function(...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
    };
    }

    // 搜索函数
    const searchPatients = debounce(async (term) => {
    if (term) {
    // 发送搜索请求
    const response = await fetch(`/api/search-patients?query=${term}`);
    const data = await response.json();
    patientsStore.mbglPatients = data;
    } else {
    // 清空搜索结果或恢复默认数据
    patientsStore.mbglPatients = [];
    }
    }, 300); // 300ms 的防抖时间

    // 监听输入框变化
    function handleInput(event) {
    searchTerm = event.target.value;
    searchPatients(searchTerm);
    }
    </script>

    <input type="text" placeholder="搜索患者..." on:input={handleInput} bind:value={searchTerm} />
    +

    请求发送间隔

    +
      +
    • 防抖时间:通常设置为 300ms 到 500ms 之间。这个时间足够让用户完成输入并减少不必要的请求。
    • +
    • 考虑用户体验:防抖时间过短可能导致过多请求,过长则可能让用户感到延迟。300ms 是一个常用的折中值。
    • +
    +

    通过这种方式,你可以实现一个高效的实时搜索功能,既能保证用户体验,又能减少服务器的负担。

    +]]>
    + + Technique + +
    + + 小谈 NestJS Zod + /2026/03/21/%E5%B0%8F%E8%B0%88-NestJS-Zod/ + NestJS Zod 理论上是可以成为一层真正的契约系统的:请求校验、类型推导、OpenAPI 生成、领域约束复用,前后端一致的输入语义,都在同一份定义里。

    +
    +

    领域约束是否要复用需要讨论,但只说技术上可行。

    +
    +

    最简单的用法是用 z.object(...) 定义 schema,用 createZodDto(schema) 生成 DTO,然后引入一个全局的 ZodValidationPipe。大概是这样:

    +
    import { Module } from '@nestjs/common';
    import { APP_PIPE } from '@nestjs/core';
    import { ZodValidationPipe } from 'nestjs-zod';

    @Module({
    providers: [
    {
    provide: APP_PIPE,
    useClass: ZodValidationPipe
    }
    ]
    })
    export class AppModule {}
    +

    DTO 只是 schema 的包装:

    +
    import { createZodDto } from 'nestjs-zod';
    import { z } from 'zod';

    const createItemSchema = z.object({
    name: z.string().trim().min(1),
    price: z.number().nonnegative()
    });

    export class CreateItemDto extends createZodDto(createItemSchema) {}
    +

    这样运行时校验和 TypeScript 类型就共享同一份定义了。

    +
    +

    Zod 可以很简单的实现字符串非空、数字范围、枚举取值、对象结构、Discriminated Union。DU 比传统 DTO 写法强,因为不同 type 对应的字段集合可以天然收敛:

    +
    const requestSchema = z.discriminatedUnion('type', [
    z.object({
    type: z.literal('A'),
    payload: z.object({
    foo: z.string()
    })
    }),
    z.object({
    type: z.literal('B'),
    payload: z.object({
    bar: z.number()
    })
    })
    ]);
    +

    但真实业务的问题通常不止于单字段合法。我遇到的一些情况有:

    +
      +
    • 某个数组元素本身没问题,但数组整体不能重复
    • +
    • 某个字段语法上合法,但必须属于当前 recordType 允许的字段集合
    • +
    • 两个字段分别没问题,但组合起来语义冲突
    • +
    +

    这类规则会比较严重的影响接口可维护性,它们的特点是 "结构正确,语义错误”,假设请求结构是这样的:

    +
    {
    recordType: 'A',
    structuredData: { ... },
    fieldExceptions: [
    { fieldKey: 'x' },
    { fieldKey: 'y' }
    ]
    }
    +

    此处 fieldExceptions 的意思是 “标记 structuredData 中的某个字段在业务上的特殊情况”,例如在数据标注系统中,数据标注人员无法结构化录入某个字段。

    +

    单看每一项 { fieldKey: z.string(), reason?: z.string() } 完全合法。但可能出现这种情况:

    +
    fieldExceptions: [
    { fieldKey: 'x' },
    { fieldKey: 'x' }
    ]
    +

    JSON 结构没问题,类型也没问题,但业务语义上明显重复了。NestJS Zod 处理这类问题的方法是直接在 schema 层面操作:

    +
    function withUniqueFieldExceptions<T extends z.ZodTypeAny>(schema: T) {
    return schema.superRefine((value, ctx) => {
    const fieldExceptions = (value as {
    fieldExceptions?: Array<{ fieldKey: string }>;
    }).fieldExceptions;

    if (!fieldExceptions) return;

    const seen = new Set<string>();

    for (const [index, item] of fieldExceptions.entries()) {
    if (seen.has(item.fieldKey)) {
    ctx.addIssue({
    code: z.ZodIssueCode.custom,
    message: `Duplicate field exception for ${item.fieldKey}.`,
    path: ['fieldExceptions', index, 'fieldKey']
    });
    }

    seen.add(item.fieldKey);
    }
    });
    }
    +

    如果把去重校验写在 service/command/query 中,系统中的其他任何组件(例如测试)都可能绕过去。但如果写在 schema 中,所有入口只要用的是这份 schema,就天然继承这个约束。

    +

    注意在这个 helper 中有一个细节:错误路径要详细到具体数组项。path: ['fieldExceptions', index, 'fieldKey'] 这个写法可以让前端拿到错误后不只是知道请求失败,而是能知道哪一项重复/错误。对于复杂表单,这种可定位性比一条顶层报错有用。

    +

    另外我这里给出的 helper 主要向展示的是 schema 层面的约束能力,不是业务对象, withUniqueFieldExceptions 不是某个接口私有逻辑,而是一种可复用的 schema 装饰器模式。同理可以有 withUniqueAttachmentChanges、withNonOverlappingRanges、withConsistentDateOrder。这类 helper 一旦抽出来,schema 层就具备组合能力,业务约束可以像搭积木一样拼装。

    +

    沿着这个思路,我们可以进一步的表示 “fieldExceptions[].fieldKey 不应该是任意字符串,它必须属于当前 recordType 允许的字段集合”:

    +
    function createFieldExceptionSchema(fieldKeys: [string, ...string[]]) {
    return z.object({
    fieldKey: z.enum(fieldKeys),
    reason: z.string().trim().max(500).optional()
    });
    }
    +

    然后在 discriminatedUnion 的每个分支里绑定自己的字段白名单:

    +
    const requestSchema = z.discriminatedUnion('recordType', [
    withUniqueFieldExceptions(
    z.object({
    recordType: z.literal('A'),
    structuredData: z.object({
    foo: z.union([z.number(), z.string(), z.null()])
    }).strict(),
    fieldExceptions: z.array(createFieldExceptionSchema(['foo'])).default([])
    })
    ),
    withUniqueFieldExceptions(
    z.object({
    recordType: z.literal('B'),
    structuredData: z.object({
    bar: z.union([z.number(), z.string(), z.null()])
    }).strict(),
    fieldExceptions: z.array(createFieldExceptionSchema(['bar'])).default([])
    })
    )
    ]);
    +

    这样 recordType 和合法字段天然绑定,不需要额外写一堆 if/else,错误会在 parse 阶段就暴露而不是进入业务流程后才抛异常。schema 在这里承担了类型分支的职责。

    +
    +

    但对于复杂输入的场景,需要注意分层,validator 和 normalizer 应该分开。schema 不一定负责把所有值变干净,但它应该负责把输入约束在一个可以被 normalizer 处理的范围内。比如数值输入,前端可能传 12.3、"12.3"、null,schema 可以先允许 z.union([z.number(), z.string(), z.null()]),parse 成功之后再进入统一的 normalizer。这种分层比在 schema 里直接做满所有 coercion 会更加可维护,因为它把两个问题拆开了:schema 负责数据能不能被系统处理,normalizer 负责进来的数据如何变成领域标准格式。这能避免 schema 逐渐膨胀成一个难以维护的黑盒。

    +

    运行时 schema 和文档 schema 可以适度分离。有些真实运行时 schema 很复杂,比如 DU、superRefine、动态字段白名单等,有些约束还依赖运行时上下文,这些对 OpenAPI JSON Spec 的生成不一定友好。可以考虑保留两套:requestSchema 真实运行时校验用,requestDtoSchema 专门服务 OpenAPI 的描述性 schema。controller 的 DTO 从 requestDtoSchema 生成,但真正执行业务前,再对原始 payload 走一次 requestSchema.safeParse(...)。

    +
    +

    文档可读性和运行时严谨性不必强行绑定在同一份 schema 表达能力上。如果一份 schema 同时满足两者当然最好,如果不能,优先保证运行时正确性。

    +
    +

    注意,虽然 Controller 已经有 ZodValidationPipe,但如果用 CQRS,那么在 command/query handler 最好还要再次 safeParse 一下,因为进入 handler 的 payload 未必只来自 HTTP,还可能来自 cron、queue consumer、internal dispatch、test fixture、script。如果 handler 是真正的业务入口,那它就应该自己守住边界。这不是重复,而是分层后更加清晰的职责,因为 Pipe 只保护 HTTP 入口,handler 内 safeParse 主要保护业务入口。

    +

    最后,直接对 schema 写测试:

    +
    it('rejects duplicated field exceptions', () => {
    const result = requestSchema.safeParse({
    recordType: 'A',
    structuredData: { foo: '1' },
    fieldExceptions: [
    { fieldKey: 'foo' },
    { fieldKey: 'foo' }
    ]
    });

    expect(result.success).toBe(false);
    });
    +

    这种测试验证的是契约本身,不是某个 service 逻辑的分支,这种测试看起来更有价值也更清晰。

    +]]>
    + + Technique + +
    库欣综合征 /2025/10/29/%E5%BA%93%E6%AC%A3%E7%BB%BC%E5%90%88%E5%BE%81/ @@ -5687,33 +5765,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 Medicine - - 实时搜索中的防抖函数 - /2025/02/10/%E5%AE%9E%E6%97%B6%E6%90%9C%E7%B4%A2%E4%B8%AD%E7%9A%84%E9%98%B2%E6%8A%96%E5%87%BD%E6%95%B0/ - 在实现实时搜索功能时,通常会使用输入框的事件监听器来捕获用户的输入变化,并在输入变化时发送搜索请求。为了避免过多的请求导致服务器负担过重,通常会使用“防抖”(debounce)技术来控制请求的频率。

    -

    实现步骤

    -
      -
    1. 监听输入框的变化:使用input事件监听器来捕获用户的输入变化。
    2. -
    3. 防抖处理:使用防抖函数来限制请求的频率。防抖函数会在用户停止输入一段时间后才发送请求。
    4. -
    5. 发送请求:在防抖函数中调用搜索请求。
    6. -
    -

    防抖函数示例

    -

    以下是一个简单的防抖函数示例:

    -
    function debounce(func, wait) {
    let timeout;
    return function(...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
    };
    }
    -

    实现实时搜索

    -

    假设你有一个输入框用于搜索患者:

    -
    <script>
    import { onMount } from 'svelte';
    import { patientsStore } from '$lib/stores/patients.svelte';

    let searchTerm = '';

    // 防抖函数
    function debounce(func, wait) {
    let timeout;
    return function(...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
    };
    }

    // 搜索函数
    const searchPatients = debounce(async (term) => {
    if (term) {
    // 发送搜索请求
    const response = await fetch(`/api/search-patients?query=${term}`);
    const data = await response.json();
    patientsStore.mbglPatients = data;
    } else {
    // 清空搜索结果或恢复默认数据
    patientsStore.mbglPatients = [];
    }
    }, 300); // 300ms 的防抖时间

    // 监听输入框变化
    function handleInput(event) {
    searchTerm = event.target.value;
    searchPatients(searchTerm);
    }
    </script>

    <input type="text" placeholder="搜索患者..." on:input={handleInput} bind:value={searchTerm} />
    -

    请求发送间隔

    -
      -
    • 防抖时间:通常设置为 300ms 到 500ms 之间。这个时间足够让用户完成输入并减少不必要的请求。
    • -
    • 考虑用户体验:防抖时间过短可能导致过多请求,过长则可能让用户感到延迟。300ms 是一个常用的折中值。
    • -
    -

    通过这种方式,你可以实现一个高效的实时搜索功能,既能保证用户体验,又能减少服务器的负担。

    -]]>
    - - Technique - -
    急诊与灾难医学--烧伤 /2024/01/25/%E6%80%A5%E8%AF%8A%E4%B8%8E%E7%81%BE%E9%9A%BE%E5%8C%BB%E5%AD%A6-%E7%83%A7%E4%BC%A4/ @@ -6080,6 +6131,69 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 Life + + 气胸 + /2023/03/01/%E6%B0%94%E8%83%B8/ + 气胸.jpg

    +

    这个病多见于瘦高的年轻男性,一般感觉起来就是:

    +
      +
    1. 胸腔有刺痛感,针刺的感觉,想想那种拿小针针撮你小胸胸的感觉。
    2. +
    3. 胸闷,就像有东西压在胸口。
    4. +
    5. 呼吸困难,就像有人捂住你的口鼻,有点窒息的感觉。
    6. +
    +

    通俗来讲造成这种感觉的原因是肺泡破裂,肺漏气了。然后气体在胸腔里聚集就压缩了肺。

    +

    气胸2.jpeg
    +图中是一位右侧气胸患者(图中右面)的电脑断层扫描影像。在胸腔的边缘有着引流管,而图中黑色一片就是邻近于肺膜间(黑)和肋骨(白)的内腔。心脏则在图中央。医学专科胸腔医学、胸腔外科学症状胸痛、呼吸困难、疲劳常见始发于突发性肇因未知、创伤风险因子慢性阻塞性肺病(COPD)、结核病、抽烟诊断方法胸部X光、超声波、电脑断层扫描相似疾病或共病肺部大疱、血胸预防禁烟或戒烟治疗保守治疗、空针穿刺、胸管置放、肋膜黏连术盛行率约每10万人中20例外伤性气胸。

    +

    临床表现

    +

    在医学上的临床表现中,气胸具有如下症状:
    +患者常有持重物、屏气、剧烈运动等诱发因素,但也有在睡眠中发生气胸者,病人突感一侧胸痛、气急、憋气,可有咳嗽、但痰少,小量闭合性气胸先有气急,但数小时后逐渐平稳,X线也不一定能显示肺压缩。若积气量较大者或者原来已有广泛肺部疾患,病人常不能平卧。如果侧卧,则被迫使气胸患侧在上,以减轻气急。病人呼吸困难程度与积气量的多寡以及原来肺内病变范围有关。当有胸膜粘连和肺功能减损时,即使小量局限性气胸也可能明显胸痛和气急。

    +

    初步诊断方式

    +

    突发一侧胸痛,伴有呼吸困难并有气胸体征,即可作出初步诊断。X线显示气胸征是确诊依据。在无条件或病情危重不允许作X线检查时,可在患侧胸腔积气体征最明确处试穿,抽气测压,若为正压且抽出气体,说明有气胸存在,即应抽出气体以缓解症状,并观察抽气后胸腔内压力的变化以判断气胸类型。在原有严重哮喘或肺气肿基础上并发气胸时,气急、胸闷等症状有时不易觉察,要与原先症状仔细比较。

    +

    类似病症需要区别开来

    +
      +
    1. 支气管哮喘和阻塞性肺气肿: 有气急和呼吸困难,体征亦与自发性气胸相似,但肺气肿呼吸困难是长期缓慢加重的,支气管哮喘病人有多年哮喘反复发作史。当哮喘和肺气肿病人呼吸困难突然加重且有胸痛,应考虑并发气胸的可能,X线检查可以作出鉴别。
    2. +
    3. 急性心肌梗塞: 病人亦有急起胸痛、胸闷、甚至呼吸困难、休克等临床表现,但常有高血压、动脉粥样硬化、冠心病史。体征、心电图和X线胸透有助于诊断。
    4. +
    5. 肺栓塞: 有胸痛、呼吸困难和紫绀等酷似自发性气胸的临床表现,但病人往往有咯血和低热,并常有下肢或盆腔栓塞性静脉炎、骨折、严重心脏病、心房纤颤等病史,或发生在长期卧床的老年患者。体检和X线检查有助于鉴别。
    6. +
    7. 肺大疱: 位于肺周边部位的肺大疱有时在X线下被误为气胸。肺大疱可因先天发育形成,也可因支气管内活瓣阻塞而形成张力性囊腔或巨型空腔,起病缓慢,气急不剧烈,从不同角度作胸部透视,可见肺大疱或支气管源囊肿为圆形或卵圆形透光区,在大疱的边缘看不到发线状气胸线,疱内有细小的条纹理,为肺小叶或血管的残遗物。肺大疱向周围膨胀,将肺压向肺尖区、肋膈角和心膈角,而气胸则呈胸外侧的透光带,其中无肺纹可见。肺大疱内压力与大气压相仿,抽气后,大疱容积无显著改变。
    8. +
    9. 主动脉夹层: 夹层分离突然发生时多数患者突感胸部疼痛,向胸前及背部放射,随夹层涉及范围而可以延至腹部、下肢、臂及颈部。疼痛剧烈难以忍受,起病后即达高峰,呈刀割或撕裂样。少数起病缓慢者疼痛可以不著。必须与气胸相鉴别。
    10. +
    11. 其他如消化性溃疡穿孔,膈疝、胸膜炎和肺癌等,有时因急起的胸痛,上腹痛和气急等,亦应注意与自发性气胸鉴别。
    12. +
    +

    建议至医院就诊明确病因,例如是不是最常见的肺大疱引起,还是肺部其他疾病引起,病因治疗,以免延误病情。

    +
    +

    如果是肺大疱引起: 肺大疱先天性支气管发育异常,粘膜皱襞呈瓣膜状,软骨发育不良,引起活瓣作用所致。如果有胸闷、气短的症状,而且反复发作,建议手术治疗。如果没有任何症状可以观察,内科治疗。 病人的症状主要与大疱的数目、大小以及是否伴有炎症,肺大疱是否破裂密切相关。首先、小范围的先天性肺大疱一般不会直接导致死亡。 但是、大范围的先天性肺大疱或者出现严重并发症时,有可能引起死亡:

    +
      +
    1. 直接原因。巨大的肺大疱,因为气体交换困难,多有不同程度的呼吸困难,有的病人因而失去劳动力,甚至行动亦受到限制或者窒息可能。
    2. +
    3. 间接原因。主要是出现并发症时,先天性肺大疱绝大多数是不感染的,但如果感冒等原因引起肺部分泌物增多,引流肺大疱的支气管堵塞,肺大疱支气管内充满炎性分泌物,患者可出现发热、咳嗽、咳痰等感染症状,严重时可以导致菌血症、败血症、脓毒血症导致生命危险。而且肺大疱引起的自发性血胸,多数由肺尖部的大疱或大疱周围的肺组织与胸顶粘连及粘连撕裂活动出血。由于肺、心脏、膈肌运动的去纤维化作用,胸腔内的血液不凝固,因此出血很难自动停止。临床症状可因出血的快慢而不同,出血缓慢时,患者可表现为逐渐加重的胸闷,呼吸困难,X线可见膈角变钝,或胸腔积液的抛物线影像。出血迅速时,短期内可以有休克表现。其次,大范围的先天性肺大疱导致机体长期处于气体交换困难,缺氧时,能导致能促使肺原性心脏病的发生,甚至多器官功能衰竭。
    4. +
    +
    +
    +

    先天性肺大疱破裂也可以危及生命,破裂时会并发自发性气胸,有突然胸痛,呼吸困难。若肺大疱破裂后形成活瓣,吸气时胸腔负压增高,气体进入胸腔,呼气时活瓣关闭,气体不能排出,尤其是咳嗽时,声门关闭气道压力增高,气体进入胸腔,声门开放后,气道压力减低,裂口又闭合,每一次呼吸和咳嗽都使胸腔内气体量增加,就形成张力性气胸。 张力性气胸时患侧肺组织完全萎缩,纵隔被推向健侧,在健侧肺组织亦被压缩的同时心脏大血管移位,大静脉扭曲变形,影响血液回流,造成呼吸循环严重障碍。有可能出现很快的心肺功能衰竭,导致死亡。

    +
    +

    急救

    +
      +
    1. +

      胸部损伤: 胸部如果被刺穿,需要立即覆盖伤处,并以凡士林或胶布密封,以免空气继续经伤处流入。无菌的胶布是较理想的选择, 但是所有气密的物质,例如玻璃纸和香烟盒也可以用。密封后,需要开一个小孔(振动筏)来使吸气时排出空气。胸部刺穿的患者需要密切监察,防止引发对生命有危险的张力性气胸。

      +
    2. +
    3. +

      入院前护理: 多数救护员可以进行针刺抽气, 以减低胸部的压力。 如果情况恶化,导管抽气也可能需要,包括有知觉的病人。可能的话,进行额外的治疗和即时送病人到医院治疗。没有经过适当的治疗的话,气胸患者是不能用飞机运送的。

      +
    4. +
    +

    流行病学

    +

    经年龄调整后的年均发病率﹝AAIR﹞显示,男性患上气胸的机会较女性高出三至六倍。菲什曼在研究中提出以每十万人年计算,男女患上原发性气胸的AAIR分别为7.4和1.2。身高比平均值较高的人,他们患上气胸的AAIR也相对增加 - 至少为76英寸(1.93米)高的人,每十万人年约有200个案例。瘦削的身材也似乎增加了患上原发性气胸的风险。

    +

    此外,男性和女性烟民患上原发性气胸,相对于同性别的非吸烟者患上的机会高出约22倍和9倍。而个人吸烟的程度越凶,风险会有“大于线性”的效果:比方说每天吸食10支香烟的人,会比非吸烟者高出20倍患上气胸的机会;每天消耗20支香烟的,则会高出约100倍。

    +

    在继发自发性气胸的病例当中,男性和女性的AAIR大概为6.3和2.0,而复发的风险则取决于患者本身有否任何潜在的肺病及其严重性。一旦发生了第二次气胸,病人之后再复发的机会极高。目前来说没有周详的研究调查儿童的发病率,但估计是每年约十万份之5至10。

    +

    除外张力性气胸以外,因为气胸而死亡的案例是非常罕见的。据英国统计数字显示,每年每一百万人当中会有1.26位男性和0.62位女性会因为气胸死亡,其中中老年和继发性气胸的患者会有较高的死亡风险。

    +

    引用

    + +]]>
    + + Medicine + +
    灯笼草 /2023/06/22/%E7%81%AF%E7%AC%BC%E8%8D%89/ @@ -6144,69 +6258,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 Life - - 气胸 - /2023/03/01/%E6%B0%94%E8%83%B8/ - 气胸.jpg

    -

    这个病多见于瘦高的年轻男性,一般感觉起来就是:

    -
      -
    1. 胸腔有刺痛感,针刺的感觉,想想那种拿小针针撮你小胸胸的感觉。
    2. -
    3. 胸闷,就像有东西压在胸口。
    4. -
    5. 呼吸困难,就像有人捂住你的口鼻,有点窒息的感觉。
    6. -
    -

    通俗来讲造成这种感觉的原因是肺泡破裂,肺漏气了。然后气体在胸腔里聚集就压缩了肺。

    -

    气胸2.jpeg
    -图中是一位右侧气胸患者(图中右面)的电脑断层扫描影像。在胸腔的边缘有着引流管,而图中黑色一片就是邻近于肺膜间(黑)和肋骨(白)的内腔。心脏则在图中央。医学专科胸腔医学、胸腔外科学症状胸痛、呼吸困难、疲劳常见始发于突发性肇因未知、创伤风险因子慢性阻塞性肺病(COPD)、结核病、抽烟诊断方法胸部X光、超声波、电脑断层扫描相似疾病或共病肺部大疱、血胸预防禁烟或戒烟治疗保守治疗、空针穿刺、胸管置放、肋膜黏连术盛行率约每10万人中20例外伤性气胸。

    -

    临床表现

    -

    在医学上的临床表现中,气胸具有如下症状:
    -患者常有持重物、屏气、剧烈运动等诱发因素,但也有在睡眠中发生气胸者,病人突感一侧胸痛、气急、憋气,可有咳嗽、但痰少,小量闭合性气胸先有气急,但数小时后逐渐平稳,X线也不一定能显示肺压缩。若积气量较大者或者原来已有广泛肺部疾患,病人常不能平卧。如果侧卧,则被迫使气胸患侧在上,以减轻气急。病人呼吸困难程度与积气量的多寡以及原来肺内病变范围有关。当有胸膜粘连和肺功能减损时,即使小量局限性气胸也可能明显胸痛和气急。

    -

    初步诊断方式

    -

    突发一侧胸痛,伴有呼吸困难并有气胸体征,即可作出初步诊断。X线显示气胸征是确诊依据。在无条件或病情危重不允许作X线检查时,可在患侧胸腔积气体征最明确处试穿,抽气测压,若为正压且抽出气体,说明有气胸存在,即应抽出气体以缓解症状,并观察抽气后胸腔内压力的变化以判断气胸类型。在原有严重哮喘或肺气肿基础上并发气胸时,气急、胸闷等症状有时不易觉察,要与原先症状仔细比较。

    -

    类似病症需要区别开来

    -
      -
    1. 支气管哮喘和阻塞性肺气肿: 有气急和呼吸困难,体征亦与自发性气胸相似,但肺气肿呼吸困难是长期缓慢加重的,支气管哮喘病人有多年哮喘反复发作史。当哮喘和肺气肿病人呼吸困难突然加重且有胸痛,应考虑并发气胸的可能,X线检查可以作出鉴别。
    2. -
    3. 急性心肌梗塞: 病人亦有急起胸痛、胸闷、甚至呼吸困难、休克等临床表现,但常有高血压、动脉粥样硬化、冠心病史。体征、心电图和X线胸透有助于诊断。
    4. -
    5. 肺栓塞: 有胸痛、呼吸困难和紫绀等酷似自发性气胸的临床表现,但病人往往有咯血和低热,并常有下肢或盆腔栓塞性静脉炎、骨折、严重心脏病、心房纤颤等病史,或发生在长期卧床的老年患者。体检和X线检查有助于鉴别。
    6. -
    7. 肺大疱: 位于肺周边部位的肺大疱有时在X线下被误为气胸。肺大疱可因先天发育形成,也可因支气管内活瓣阻塞而形成张力性囊腔或巨型空腔,起病缓慢,气急不剧烈,从不同角度作胸部透视,可见肺大疱或支气管源囊肿为圆形或卵圆形透光区,在大疱的边缘看不到发线状气胸线,疱内有细小的条纹理,为肺小叶或血管的残遗物。肺大疱向周围膨胀,将肺压向肺尖区、肋膈角和心膈角,而气胸则呈胸外侧的透光带,其中无肺纹可见。肺大疱内压力与大气压相仿,抽气后,大疱容积无显著改变。
    8. -
    9. 主动脉夹层: 夹层分离突然发生时多数患者突感胸部疼痛,向胸前及背部放射,随夹层涉及范围而可以延至腹部、下肢、臂及颈部。疼痛剧烈难以忍受,起病后即达高峰,呈刀割或撕裂样。少数起病缓慢者疼痛可以不著。必须与气胸相鉴别。
    10. -
    11. 其他如消化性溃疡穿孔,膈疝、胸膜炎和肺癌等,有时因急起的胸痛,上腹痛和气急等,亦应注意与自发性气胸鉴别。
    12. -
    -

    建议至医院就诊明确病因,例如是不是最常见的肺大疱引起,还是肺部其他疾病引起,病因治疗,以免延误病情。

    -
    -

    如果是肺大疱引起: 肺大疱先天性支气管发育异常,粘膜皱襞呈瓣膜状,软骨发育不良,引起活瓣作用所致。如果有胸闷、气短的症状,而且反复发作,建议手术治疗。如果没有任何症状可以观察,内科治疗。 病人的症状主要与大疱的数目、大小以及是否伴有炎症,肺大疱是否破裂密切相关。首先、小范围的先天性肺大疱一般不会直接导致死亡。 但是、大范围的先天性肺大疱或者出现严重并发症时,有可能引起死亡:

    -
      -
    1. 直接原因。巨大的肺大疱,因为气体交换困难,多有不同程度的呼吸困难,有的病人因而失去劳动力,甚至行动亦受到限制或者窒息可能。
    2. -
    3. 间接原因。主要是出现并发症时,先天性肺大疱绝大多数是不感染的,但如果感冒等原因引起肺部分泌物增多,引流肺大疱的支气管堵塞,肺大疱支气管内充满炎性分泌物,患者可出现发热、咳嗽、咳痰等感染症状,严重时可以导致菌血症、败血症、脓毒血症导致生命危险。而且肺大疱引起的自发性血胸,多数由肺尖部的大疱或大疱周围的肺组织与胸顶粘连及粘连撕裂活动出血。由于肺、心脏、膈肌运动的去纤维化作用,胸腔内的血液不凝固,因此出血很难自动停止。临床症状可因出血的快慢而不同,出血缓慢时,患者可表现为逐渐加重的胸闷,呼吸困难,X线可见膈角变钝,或胸腔积液的抛物线影像。出血迅速时,短期内可以有休克表现。其次,大范围的先天性肺大疱导致机体长期处于气体交换困难,缺氧时,能导致能促使肺原性心脏病的发生,甚至多器官功能衰竭。
    4. -
    -
    -
    -

    先天性肺大疱破裂也可以危及生命,破裂时会并发自发性气胸,有突然胸痛,呼吸困难。若肺大疱破裂后形成活瓣,吸气时胸腔负压增高,气体进入胸腔,呼气时活瓣关闭,气体不能排出,尤其是咳嗽时,声门关闭气道压力增高,气体进入胸腔,声门开放后,气道压力减低,裂口又闭合,每一次呼吸和咳嗽都使胸腔内气体量增加,就形成张力性气胸。 张力性气胸时患侧肺组织完全萎缩,纵隔被推向健侧,在健侧肺组织亦被压缩的同时心脏大血管移位,大静脉扭曲变形,影响血液回流,造成呼吸循环严重障碍。有可能出现很快的心肺功能衰竭,导致死亡。

    -
    -

    急救

    -
      -
    1. -

      胸部损伤: 胸部如果被刺穿,需要立即覆盖伤处,并以凡士林或胶布密封,以免空气继续经伤处流入。无菌的胶布是较理想的选择, 但是所有气密的物质,例如玻璃纸和香烟盒也可以用。密封后,需要开一个小孔(振动筏)来使吸气时排出空气。胸部刺穿的患者需要密切监察,防止引发对生命有危险的张力性气胸。

      -
    2. -
    3. -

      入院前护理: 多数救护员可以进行针刺抽气, 以减低胸部的压力。 如果情况恶化,导管抽气也可能需要,包括有知觉的病人。可能的话,进行额外的治疗和即时送病人到医院治疗。没有经过适当的治疗的话,气胸患者是不能用飞机运送的。

      -
    4. -
    -

    流行病学

    -

    经年龄调整后的年均发病率﹝AAIR﹞显示,男性患上气胸的机会较女性高出三至六倍。菲什曼在研究中提出以每十万人年计算,男女患上原发性气胸的AAIR分别为7.4和1.2。身高比平均值较高的人,他们患上气胸的AAIR也相对增加 - 至少为76英寸(1.93米)高的人,每十万人年约有200个案例。瘦削的身材也似乎增加了患上原发性气胸的风险。

    -

    此外,男性和女性烟民患上原发性气胸,相对于同性别的非吸烟者患上的机会高出约22倍和9倍。而个人吸烟的程度越凶,风险会有“大于线性”的效果:比方说每天吸食10支香烟的人,会比非吸烟者高出20倍患上气胸的机会;每天消耗20支香烟的,则会高出约100倍。

    -

    在继发自发性气胸的病例当中,男性和女性的AAIR大概为6.3和2.0,而复发的风险则取决于患者本身有否任何潜在的肺病及其严重性。一旦发生了第二次气胸,病人之后再复发的机会极高。目前来说没有周详的研究调查儿童的发病率,但估计是每年约十万份之5至10。

    -

    除外张力性气胸以外,因为气胸而死亡的案例是非常罕见的。据英国统计数字显示,每年每一百万人当中会有1.26位男性和0.62位女性会因为气胸死亡,其中中老年和继发性气胸的患者会有较高的死亡风险。

    -

    引用

    - -]]>
    - - Medicine - -
    直系血亲之间不能直接输血 /2024/08/12/%E7%9B%B4%E7%B3%BB%E8%A1%80%E4%BA%B2%E4%B9%8B%E9%97%B4%E4%B8%8D%E8%83%BD%E7%9B%B4%E6%8E%A5%E8%BE%93%E8%A1%80/ -- cgit v1.2.3