From 96fbc0ca754356607aabe6a549636d7149fc3dc2 Mon Sep 17 00:00:00 2001 From: muqiuhan Date: Sun, 22 Mar 2026 15:14:32 +0000 Subject: deploy: bf86ca589e2db49e19cd07097827fc6c0c56dd0b --- search.xml | 419 +++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 299 insertions(+), 120 deletions(-) (limited to 'search.xml') diff --git a/search.xml b/search.xml index fe5473fd..e135aa5e 100644 --- a/search.xml +++ b/search.xml @@ -1096,6 +1096,40 @@ 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 @@ -1159,40 +1193,6 @@ 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,33 +2132,6 @@ 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/ @@ -2193,6 +2166,33 @@ 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 @@ -4371,6 +4371,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 @@ -4409,24 +4427,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 @@ -4506,6 +4506,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 @@ -4531,29 +4554,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 @@ -4731,15 +4731,6 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的

    “拍了啊,倒数,三、二、一……茄子!”

    咔擦,画面定格,幸福定格,大家终于从苦难里彻底毕业了。

    萤火虫的微光与星空交相辉映,黑夜中潜滋暗长的花香沁人心脾,晚风挠过波光粼粼的海面,彼此相爱的人相拥而眠。

    -]]> - - gallery - -
    - - 二零二三年二月一日 - /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/ - 我想在六点和日出打招呼,我想去菜市场看看卖蔬菜的阿婆,我想花一上午去准备一餐中午饭,我想下午能在阳台捧着一盏热茶慢慢看书,慢点好,再慢点,慢到能透过阳台栏杆的缝隙看看马路上车水马龙映衬下的老人家提着新鲜的一荤一素慢慢蹒跚走回家,看看贼几把大的夕阳在冒着热气的水杯中慢慢融化。

    ]]>
    gallery @@ -4766,9 +4757,9 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的
    - 二零二三年二月二十六日 - /2023/02/26/%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%85%AD%E6%97%A5/ - 翻滚的热汤接过焰火的能量把周围的蒸汽撕开一道裂缝,透过缝隙我见到一对爱人的时空交织在未来,他们幸运的错开孤独,人们举起手中的酒杯,祝因果与命运缠绵之人幸福

    + 二零二三年二月一日 + /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/ + 我想在六点和日出打招呼,我想去菜市场看看卖蔬菜的阿婆,我想花一上午去准备一餐中午饭,我想下午能在阳台捧着一盏热茶慢慢看书,慢点好,再慢点,慢到能透过阳台栏杆的缝隙看看马路上车水马龙映衬下的老人家提着新鲜的一荤一素慢慢蹒跚走回家,看看贼几把大的夕阳在冒着热气的水杯中慢慢融化。

    ]]>
    gallery @@ -4790,6 +4781,15 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 我不知道这些黑乎乎的东西从哪来的,它们填满了这黄铜色的硬币每一处,像机油,像泥泞,它们随着这五角钱硬币一起从我手上换走了一袋糖果,那是一个蓬头垢面,满身泥灰的小孩,他起身看着这个给他糖果的叔叔,我蹲下看着我拥有糖果的童年。
    ​
    ​这事很恐怖,意味着我在这个地方又多了一丝牵挂,多了一个不离开的理由。

    +]]>
    + + gallery + +
    + + 二零二三年二月二十六日 + /2023/02/26/%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%85%AD%E6%97%A5/ + 翻滚的热汤接过焰火的能量把周围的蒸汽撕开一道裂缝,透过缝隙我见到一对爱人的时空交织在未来,他们幸运的错开孤独,人们举起手中的酒杯,祝因果与命运缠绵之人幸福

    ]]>
    gallery @@ -4960,18 +4960,18 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的
    - 二零二零年十二月二十六日 - /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
    - 二零二零年十二月八日 - /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 @@ -5406,6 +5406,185 @@ NestJS 依赖其底层 HTTP 适配器(默认为 Express)来解析传入的 Medicine
    + + 在 OCaml 中通过 CTypes 绑定 libseccomp + /2026/03/22/%E5%9C%A8-OCaml-%E4%B8%AD%E9%80%9A%E8%BF%87-CTypes-%E7%BB%91%E5%AE%9A-libseccomp/ + CTypes 允许我们用纯 OCaml 代码绑定 C lib 而不用编写任何 C 代码。这篇文章是以 bonding 项目中的 libseccomp 绑定为例写的。

    +

    CTypes 的核心概念是建立 OCaml 类型与 C 类型的双向映射,ctypes 中有如下基本类型:

    +
    open Ctypes

    (* C 类型与 OCaml 类型的对应关系 *)
    let c_int : int typ = int (* int *)
    let c_uint : Unsigned.UInt.t typ = uint (* unsigned int *)
    let c_long : Signed.Long.t typ = long (* long *)
    let c_int64 : Unsigned.UInt64.t typ = uint64_t (* int64_t / uint64_t *)
    let c_float : float typ = float (* float *)
    let c_double : float typ = double (* double *)
    let c_char : char typ = char (* char *)
    let c_void : unit typ = void (* void *)
    +

    指针

    +

    对于 C 的指针,CTypes 提供了多种指针表示:

    +
    (* 通用指针:void* *)
    let void_ptr : unit ptr typ = ptr void

    (* 类型化指针:int* *)
    let int_ptr : int ptr typ = ptr int

    (* 不透明指针:用于表示我们不知道内部结构的 C 结构体 *)
    let scmp_filter_ctx : unit ptr typ = ptr void
    +

    bonding 中的 seccomp_stubs.ml 中是这么写的:

    +
    type scmp_filter_ctx = unit ptr
    let scmp_filter_ctx : scmp_filter_ctx typ = ptr void
    +

    这里 scmp_filter_ctx 是 libseccomp 的核心类型,它是一个不透明指针。C 库不暴露其内部结构,我们只能通过库函数操作它。使用 ptr void 是表示不透明指针的标准做法。

    +

    结构体

    +

    对于结构体,ctypes 通过 structure 和 field 函数定义:

    +
    (* 定义 C 结构体:
    struct scmp_arg_cmp {
    unsigned int arg;
    unsigned int op;
    uint64_t datum_a;
    uint64_t datum_b;
    };
    *)

    let scmp_arg_cmp : unit structure typ =
    let s = structure "scmp_arg_cmp" in (* 1. 创建结构体模板 *)
    let _arg = field s "arg" uint in (* 2. 添加字段 *)
    let _op = field s "op" uint in
    let _datum_a = field s "datum_a" uint64_t in
    let _datum_b = field s "datum_b" uint64_t in
    seal s; (* 3. 封印结构体 *)
    s (* 4. 返回类型描述符 *)
    +

    注意:

    +
      +
    • structure "name" 创建一个未完成的类型描述符
    • +
    • field s "fieldname" typ 添加字段,返回字段访问器
    • +
    • seal s 完成结构体定义,之后不可再添加字段
    • +
    • 返回的 s 可以作为类型使用
    • +
    +

    例如:

    +
    let scmp_arg_cmp : unit structure typ =
    let s = structure "scmp_arg_cmp" in
    let _arg = field s "arg" uint in
    let _op = field s "op" uint in
    let _datum_a = field s "datum_a" uint64_t in
    let _datum_b = field s "datum_b" uint64_t in
    seal s;
    s
    +

    定义结构体后,可以通过字段访问器读写值:

    +
    (* 写入字段值 *)
    let set_arg_cmp_values struct_ptr arg_val op_val datum_a_val datum_b_val =
    let s = structure "scmp_arg_cmp" in
    let arg = field s "arg" uint in
    let op = field s "op" uint in
    let datum_a = field s "datum_a" uint64_t in
    let datum_b = field s "datum_b" uint64_t in
    seal s;

    (* 使用 setf 设置字段值 *)
    setf struct_ptr arg (Unsigned.UInt.of_int arg_val);
    setf struct_ptr op (Unsigned.UInt.of_int op_val);
    setf struct_ptr datum_a (Unsigned.UInt64.of_int64 datum_a_val);
    setf struct_ptr datum_b (Unsigned.UInt64.of_int64 datum_b_val)
    +

    这里展示了如何创建辅助函数来操作结构体。注意:

    +
      +
    • 每次都需要重新定义结构体和字段访问器
    • +
    • setf 的参数顺序:结构体指针、字段访问器、值
    • +
    • OCaml 的无符号类型需要显式转换
    • +
    +

    动态库

    +

    使用 CTypes 绑定 C 库只需要:加载库、然后绑定函数。

    +

    首先,加载共享库:

    +
    let libseccomp =
    let paths = [ "libseccomp.so.2"; "libseccomp.so.1"; "libseccomp.so" ] in
    let rec try_paths = function
    | [] ->
    Dl.dlopen ~filename:"libseccomp.so.2" ~flags:[ Dl.RTLD_NOW ]
    | path :: rest -> (
    try Dl.dlopen ~filename:path ~flags:[ Dl.RTLD_NOW ] with
    | _ ->
    try_paths rest)
    in
    try_paths paths
    +

    这段代码用了一个提升动态库加载稳定性的小技巧:

    +
      +
    • 尝试多个可能的库文件名(不同系统可能版本不同)
    • +
    • 依次尝试,直到成功
    • +
    • 如果全部失败,使用默认路径(触发异常)
    • +
    +

    注意 Dl.dlopen 的参数:

    +
      +
    • filename: 库文件名(可以是绝对路径或库文件名)
    • +
    • flags: 加载标志 +
        +
      • RTLD_NOW: 立即解析所有符号
      • +
      • RTLD_LAZY: 延迟解析符号
      • +
      +
    • +
    +

    加载完成后使用 Foreign.foreign 绑定到函数,基本语法:

    +
    let function_name = 
    Foreign.foreign
    ~from:library
    "c_function_name"
    (arg1_type @-> arg2_type @-> ... @-> returning return_type)
    +

    单参数函数:

    +
    let seccomp_init =
    Foreign.foreign
    ~from:libseccomp
    "seccomp_init"
    (uint32_t @-> returning scmp_filter_ctx)
    +

    对应的 C 函数原型:

    +
    scmp_filter_ctx seccomp_init(uint32_t def_action);
    +

    Foreign.foreign 的参数:

    +
      +
    • ~from:libseccomp: 指定函数所在的库
    • +
    • "seccomp_init": C 函数名
    • +
    • uint32_t @-> returning scmp_filter_ctx: 函数签名 +
        +
      • @-> 分隔参数
      • +
      • returning 标记返回类型
      • +
      +
    • +
    +

    对于多参数函数:

    +
    let seccomp_rule_add =
    Foreign.foreign
    ~from:libseccomp
    "seccomp_rule_add"
    (scmp_filter_ctx @-> uint32_t @-> int @-> uint @-> returning int)
    +

    C 函数原型:

    +
    int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action, 
    int syscall, unsigned int arg_cnt);
    +

    对于无参数函数:

    +
    let seccomp_arch_native =
    Foreign.foreign
    ~from:libseccomp
    "seccomp_arch_native"
    (void @-> returning uint32_t)
    +

    对于指针参数:

    +
    let seccomp_load =
    Foreign.foreign
    ~from:libseccomp
    "seccomp_load"
    (scmp_filter_ctx @-> returning int)
    +

    C 函数原型:

    +
    int seccomp_load(scmp_filter_ctx ctx);
    +

    对于字符串参数:

    +
    let seccomp_syscall_resolve_name =
    Foreign.foreign
    ~from:libseccomp
    "seccomp_syscall_resolve_name"
    (string @-> returning int)
    +

    这里 CTypes 会自动处理 OCaml 字符串与 C 字符串的转换。

    +

    对于返回 NULL 的情况:

    +
    let seccomp_syscall_resolve_num_arch =
    Foreign.foreign
    ~from:libseccomp
    "seccomp_syscall_resolve_num_arch"
    (uint32_t @-> int @-> returning string_opt)
    +

    string_opt 处理可能返回 NULL 的字符串指针。

    +

    内存管理

    +

    C 需要手动管理内存,CTypes 提供了相应的工具。

    +

    分配单个值:

    +
    (* 分配并初始化单个值 *)
    let ptr = allocate int 42

    (* 读取值 *)
    let value = !@ ptr

    (* 写入值 *)
    ptr <-@ 100
    +

    分配数组:

    +
    (* 分配数组(未初始化) *)
    let arr = allocate_n int ~count:10

    (* 分配并初始化数组 *)
    let arr = allocate_n int ~count:5 ~initial:0
    +

    例如:

    +
    let cmp_array = Ctypes.allocate_n Stubs.Types.scmp_arg_cmp ~count:n in
    +

    指针算术

    +
    let arr = allocate_n int ~count:10

    (* 访问第 i 个元素 *)
    let elem_ptr = arr +@ i

    (* 读写元素 *)
    let value = !@ elem_ptr
    elem_ptr <-@ 42
    +

    例如:

    +
    Stubs.Types.set_arg_cmp_values
    !@(cmp_array +@ i) (* 第 i 个结构体的指针 *)
    cmp.arg
    (cmp_op_to_int cmp.op)
    cmp.datum_a
    cmp.datum_b
    +

    !@ 运算符解引用指针,+@ 进行指针算术。

    +

    指针转换

    +
    (* 类型化指针转 void 指针 *)
    let void_p = to_voidp int_ptr

    (* void 指针转类型化指针 *)
    let int_p = from_voidp int void_p

    (* 获取空指针 *)
    let null_ptr : unit ptr = null

    (* 检查是否为空指针 *)
    if is_null ptr then ...
    +

    实例解析(seccomp.ml:96):

    +
    if Ctypes.ptr_compare ctx Ctypes.null = 0 then
    Error Init_failed
    +

    检查 seccomp_init 是否返回了 NULL(失败)。

    +

    一些模式

    +

    类型视图(Type Views)

    +

    当 C 类型与 OCaml 类型不能直接对应时,使用 view 创建自定义转换:

    +
    (* 将 C int 转换为 OCaml bool *)
    let c_bool = view
    ~read:(fun i -> i <> 0)
    ~write:(fun b -> if b then 1 else 0)
    int
    +

    枚举类型映射

    +

    在 seccomp 实现中,使用 OCaml 变体和转换函数映射 C 枚举:

    +
    (* OCaml 端的类型定义(seccomp.ml:18-25) *)
    type action =
    | Allow
    | Errno of int
    | KillProcess
    | KillThread
    | Log
    | Trace of int
    | Trap

    (* C 端的常量定义(seccomp_stubs.ml:58-73) *)
    module Constants = struct
    let scmp_act_allow = 0x7fff0000l
    let scmp_act_kill_thread = 0x00000000l
    let scmp_act_kill_process = 0x00000000l
    let scmp_act_trap = 0x00030000l
    let scmp_act_log = 0x7ffc0000l
    let scmp_act_errno code =
    Int32.(logor 0x00050000l (logand (of_int code) 0x0000ffffl))
    end

    (* 转换函数(seccomp.ml:60-75) *)
    let action_to_uint32 = function
    | Allow -> int32_to_uint32 Stubs.Constants.scmp_act_allow
    | KillThread -> int32_to_uint32 Stubs.Constants.scmp_act_kill_thread
    | KillProcess -> int32_to_uint32 Stubs.Constants.scmp_act_kill_process
    | Trap -> int32_to_uint32 Stubs.Constants.scmp_act_trap
    | Log -> int32_to_uint32 Stubs.Constants.scmp_act_log
    | Errno code -> int32_to_uint32 (Stubs.Constants.scmp_act_errno code)
    | Trace code -> int32_to_uint32 (Stubs.Constants.scmp_act_trace code)
    +

    这种模式的优势:

    +
      +
    • OCaml 端使用类型安全的变体
    • +
    • C 端使用整数常量
    • +
    • 转换函数处理映射
    • +
    • 编译器帮助捕获类型错误
    • +
    +

    错误处理模式

    +

    C 那边通常用返回值表示错误。OCaml 使用 result 类型,例如在 seccomp 的 binding 中我这么做:

    +
    type error =
    | Init_failed
    | Rule_add_failed of int
    | Load_failed of int
    | Unknown_syscall of string

    let init default_action =
    let ctx = Stubs.Functions.seccomp_init (action_to_uint32 default_action) in
    if Ctypes.ptr_compare ctx Ctypes.null = 0 then
    Error Init_failed
    else
    Ok ctx
    +

    完整示例

    +

    让我们来看看一个完整的 rule_add_conditional 函数的实现:

    +
    let rule_add_conditional ctx action syscall cmps =
    let n = Array.length cmps in
    if n = 0 then
    rule_add ctx action syscall
    else (
    (* 1. 分配 C 数组 *)
    let cmp_array = Ctypes.allocate_n Stubs.Types.scmp_arg_cmp ~count:n in

    (* 2. 填充每个结构体 *)
    Array.iteri
    (fun i cmp ->
    Stubs.Types.set_arg_cmp_values
    !@(cmp_array +@ i) (* 解引用第 i 个元素 *)
    cmp.arg
    (cmp_op_to_int cmp.op)
    cmp.datum_a
    cmp.datum_b)
    cmps;

    (* 3. 调用 C 函数 *)
    let result =
    Stubs.Functions.seccomp_rule_add_array
    ctx
    (action_to_uint32 action)
    syscall
    (Unsigned.UInt.of_int n)
    cmp_array
    in

    (* 4. 检查结果 *)
    if result < 0 then
    Error (Rule_add_failed result)
    else
    Ok ()
    )
    +
      +
    • 数组分配: allocate_n 分配 n 个 scmp_arg_cmp 结构体的空间
    • +
    • 结构体填充: +
        +
      • cmp_array +@ i 获取第 i 个元素的指针
      • +
      • !@ 解引用得到结构体
      • +
      • set_arg_cmp_values 填充字段
      • +
      +
    • +
    • 函数调用: 传递数组和长度给 C 函数
    • +
    • 返回值: C 函数返回负值表示错误
    • +
    +

    对应的 C API:

    +
    int seccomp_rule_add_array(scmp_filter_ctx ctx, uint32_t action,
    int syscall, unsigned int arg_cnt,
    const struct scmp_arg_cmp *arg_array);
    +

    双层架构 Binding

    +

    seccomp 绑定用了清晰的三层架构:

    +
      +
    • seccomp.ml / seccomp.mli 是顶层 API 纯粹的OCaml 风格的类型和函数
    • +
    • seccomp_stubs.ml 绑定层,放 CTypes 绑定,类型和函数定义
    • +
    +

    模块组织

    +
    (* seccomp_stubs.ml 的模块组织 *)

    module Types = struct (* C 类型定义 *)
    type scmp_filter_ctx = unit Ctypes.ptr
    val scmp_filter_ctx : ...
    val scmp_arg_cmp : ...
    end

    module Constants = struct (* C 常量 *)
    let scmp_act_allow = ...
    let scmp_cmp_ne = ...
    end

    module Functions = struct (* C 函数绑定 *)
    let seccomp_init = ...
    let seccomp_load = ...
    end
    +

    类型,常量,函数分离。

    +

    常见问题

    +

    1. 内存泄漏

    +

    C 分配的内存需要手动释放:

    +
    (* 始终提供 release 函数 *)
    let release ctx = Stubs.Functions.seccomp_release ctx

    (* 使用 Fun.protect 确保清理 *)
    let with_filter default_action f =
    match init default_action with
    | Error e -> Error e
    | Ok ctx ->
    try
    let result = f ctx in
    release ctx;
    result
    with e ->
    release ctx;
    raise e
    +

    2. 类型不匹配

    +

    OCaml 和 C 的类型表示不同:

    +
    (* 使用 Unsigned 模块的转换函数 *)
    let int32_to_uint32 n = Unsigned.UInt32.of_int32 n

    (* 显式类型转换 *)
    let c_uint = Unsigned.UInt.of_int 42
    +

    3. 空指针检查

    +

    C 函数可能返回 NULL:

    +
    (* 始终检查返回的指针 *)
    if Ctypes.ptr_compare ctx Ctypes.null = 0 then
    Error Init_failed
    else
    Ok ctx
    +

    4. 字符串所有权

    +

    C 返回的字符串生命周期不确定:

    +
    (* 如果 C 返回需要释放的字符串,使用 string 并复制 *)
    let result = Bytes.of_string c_string in
    (* 释放 C 字符串 *)
    C.free c_string_ptr;
    Bytes.to_string result
    +

    调试技巧

    +

    1. 检查库加载

    +
    let () =
    try
    let _ = Dl.dlopen ~filename:"libseccomp.so.2" ~flags:[ Dl.RTLD_NOW ] in
    print_endline "Library loaded successfully"
    with
    | Dl.DL_error msg ->
    Printf.eprintf "Failed to load library: %s\n" msg
    +

    2. 检查函数绑定

    +
    let () =
    try
    let _ = Foreign.foreign ~from:libseccomp "seccomp_init"
    (Ctypes.uint32_t @-> returning Ctypes.ptr Ctypes.void) in
    print_endline "Function bound successfully"
    with
    | _ ->
    print_endline "Function binding failed"
    +

    3. 检查结构体大小

    +
    let () =
    let s = structure "scmp_arg_cmp" in
    let _ = field s "arg" uint in
    seal s;
    Printf.printf "Struct size: %d\n" (sizeof s)
    +

    参考资源

    + +]]>
    + + Technique + +
    在F#中处理复杂依赖注入的实践指南 /2025/03/31/%E5%9C%A8F-%E4%B8%AD%E5%A4%84%E7%90%86%E5%A4%8D%E6%9D%82%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5%E7%9A%84%E5%AE%9E%E8%B7%B5%E6%8C%87%E5%8D%97/ -- cgit v1.2.3