2025-05-30 13:56:29
昨晚接到紧急需求,需要增加一个搜索功能。当时在家里,感觉很简单的需求, 随便改改,直接发布到线上服务器就行,毕竟我大 golang 只要能编译通过,基本不会有大问题 😏。
但是,不出意外,出了意外。自动发布脚本,在重启 systemd 服务后, 使用 systemctl status 查看状态,发现 golang 服务没有启动成功。
通过命令行单独启动 go 服务,发现报错:
/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found
还好,我发布前,将可执行程序备份了一份。。。挂了一分钟,我就悄悄恢复了,险些提桶跑路。 还是明早回公司再发布吧,太吓人了。
之前遇到过两次这个问题,都是因为编译的环境和运行的环境不一致导致的。
第一次是测试 DotNet 8 AOT 编译时, 在 Ubuntu 22.04 上编译,然后 scp 到 Ubuntu 20.04 上执行。报错!
./TestAOT ./TestAOT: /lib/x86_64-linux-gnu/libc.so.6: version
GLIBC_2.32' not found (required by ./TestAOT) ./TestAOT: /lib/x86_64-linux-gnu/libc.so.6: version
GLIBC_2.34' not found (required by ./TestAOT)
而在相同版本上的 Ubuntu Server 上,就能正常执行。。。详情参考: https://www.sunzhongwei.com/dotnet-8-aot
第二次是,在 docker alpine 容器内运行编译的 golang 程序,也是报这个错误。 如果使用 alpine 容器,就需要在编译 golang 代码时,增加编译配置。
CGO_ENABLED=0 go build
详情参考这个 https://www.sunzhongwei.com/docker-deploy-golang-in-production
这次问题的解决方法也很简单,禁用 cgo 编译即可。
在编译 golang 代码时,设置环境变量 CGO_ENABLED=0
,然后重新编译即可。
CGO_ENABLED=0 go build
因为我在 WSL Ubuntu 24.04 上编译的 golang 代码,而线上服务器是 Ubuntu 18.04。
家里的电脑环境:
> cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.1 LTS (Noble Numbat)"
> getconf GNU_LIBC_VERSION
glibc 2.39
服务器的环境:
> cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.5 LTS (Bionic Beaver)"
> getconf GNU_LIBC_VERSION
glibc 2.27
而公司开发机是
很奇怪,我在公司开发机上编译的 golang 代码,在服务器上运行是正常的。 难道是因为 ubuntu 20.04 的 glibc 版本和 ubuntu 18.04 的 glibc 版本是兼容的? 但是从版本号上看,不应该是兼容的啊。。。摸不着头脑。
参考
https://github.com/golang/go/issues/58550
AFAIU, for CGO_ENABLED=1, you'll need to use a glibc version during compile that matches (or is lower) than the one on your target runtime system.
也就是说,编译时,本机的 glibc 版本需要和运行时的 glibc 版本一致或更低,但是不能更高。而我在 WSL Ubuntu 24.04 上编译的代码,使用了 glibc 2.39,而服务器上是 glibc 2.27,所以会报错。
默认情况下,CGO_ENABLED 是开启的。只有交叉编译时,CGO_ENABLED 才会被禁用。
所以,还是手动设置 CGO_ENABLED=0
来禁用 cgo 编译,比较安全。
CGO is a feature of the Go programming language that allows Go programs to call C functions and access C data structures. This can be useful for interfacing with existing C libraries. However, using CGO can also introduce glibc dependencies into your Go program.
CGO 是 Go 编程语言的一个特性,它允许 Go 程序调用 C 函数和访问 C 数据结构。这对于与现有的 C 库进行交互非常有用。然而,使用 CGO 也可能会将 glibc 依赖引入到你的 Go 程序中。
glibc is the GNU C library, and it is the default C library on most Linux distributions. However, glibc versions can vary from one distribution to another. This can cause problems if you try to run a Go program that uses CGO on a different distribution than the one it was built on. For example, a Go program built on Ubuntu 22.04 will not run on Ubuntu 20.04.
glibc 是 GNU C 库,它是大多数 Linux 发行版上的默认 C 库。然而,glibc 的版本在不同的发行版之间可能会有所不同。如果你尝试在与构建时不同的发行版上运行使用 CGO 的 Go 程序,这可能会导致问题。例如,在 Ubuntu 22.04 上构建的 Go 程序将无法在 Ubuntu 20.04 上运行。
参考,最棒的一个解释文章:
https://www.elastiflow.com/blog/posts/disabling-cgo-to-remove-glibc-dependency
Portability: Disabling CGO makes Go programs more portable because they no longer depend on C libraries. This means that Go programs can be run on systems that do not have glibc installed, such as Alpine Linux and FreeBSD, or on different versions of the same operating system, like Ubuntu 20.04 and 22.04.
可移植性:禁用 CGO 可以使 Go 程序更具可移植性,因为它们不再依赖 C 库。这意味着 Go 程序可以在没有安装 glibc 的系统上运行,例如 Alpine Linux 和 FreeBSD,或者在同一操作系统的不同版本上运行,如 Ubuntu 20.04 和 22.04。
Security: Disabling CGO can make Go programs more secure because it reduces the program's attack surface. C libraries can contain security vulnerabilities, and disabling CGO prevents these vulnerabilities from being exploited in Go programs. See this vulnerability report of glibc in Debian 11 (Ubuntu 20.04).
安全性:禁用 CGO 可以使 Go 程序更安全,因为它减少了程序的攻击面。C 库可能包含安全漏洞,禁用 CGO 可以防止这些漏洞在 Go 程序中被利用。请参阅 Debian 11(Ubuntu 20.04)中 glibc 的此漏洞报告。
Performance: In most cases, disabling CGO can improve the performance of Go programs. For an example, see CockroachDB's article on The cost and complexity of Cgo.
性能:在大多数情况下,禁用 CGO 可以提高 Go 程序的性能。有关示例,请参阅 CockroachDB 关于 Cgo 的成本和复杂性的文章。
Functionality: Disabling CGO may limit the functionality of Go programs. There are C libraries that do not have an equivalent Go module, therefore making it more challenging to integrate with such systems.
功能性:禁用 CGO 可能会限制 Go 程序的功能。有些 C 库没有等效的 Go 模块。
Performance: Theoretically, disabling CGO can degrade the performance of Go programs. This is because Go will have to implement some functionality in pure Go, which may be less efficient than using a C library. However, this is theoretical because the overhead of invoking C libraries likely outweighs the implementation performance difference.
性能:理论上,禁用 CGO 可能会降低 Go 程序的性能。这是因为 Go 将不得不在纯 Go 中实现某些功能,这可能不如使用 C 库高效。然而,这只是理论上的,因为调用 C 库的开销可能超过实现性能差异。
2025-05-28 17:21:10
在开发的大赛报名系统,截至尾声,组织方希望群发一条短信通知所有参赛队的队长, 赶紧提交作品,不要挤在最后一天,防止人多导致系统卡顿。毕竟大量的文件及视频上传。
但是,众所周知的原因,现在发送验证码短信的成功率都达不到 98%,特别是贵州移动的手机号。 怎么都收不到短信验证码。
我担心群发短信,会不会也有类似的问题?
更难的地方是,组织方给出的短信模板是 180 多个字符,相当于要发送 3 条短信才能发送完整。 我担心的是:
于是,用我仅有的两个报备成功的签名进行群发测试,结果如下:
跟组织方讨论了一下解决方案,目前的想法是:
我用 golang 写了个接口,将没有提交作品的参赛队队长的手机号导出成 Excel 文件, 并且取出手机号的前三位来判断运营商。一并导出到 Excel 文件中。
然后,按照运营商列排序即可,批量选择指定运营商的手机号,在阿里云短信服务后台,使用群发助手手动发送即可。
手动群发的策略是:
现在的短信管控越来越严格,严格到了脑残的地步。
通知短信就算了,验证码短信都能被拦截,导致用户体验极差。
2025-05-27 22:04:17
不知道为啥,使用 Magento 的 Mage::app() 获取 store 信息会导致网站崩溃。。。 所以,直接使用纯 PHP 的方式获取当前域名。
<?php
//$storeName = Mage::app()->getStore()->getName(); // 这行会导致网站崩溃,所以换个 PHP 的方式获取域名
$domain = $_SERVER['HTTP_HOST'];
?>
<span><?php echo $domain; ?></span>
<?php if ($domain === 'www.sunzhongwei.com') : ?>
<a href="/about-us">About Zhongwei</a>
<?php endif; ?>
<?php if ($domain === 'sunzhongwei.com') : ?>
<a href="/contact-us">Contact Us</a>
<?php endif; ?>
PHP 的写法确实简单粗暴,要是没有那么多漏洞,性能再强悍一点,真是写 WEB 的首选语言。
可惜,PHP 的漏洞太多了 (看了几个 magento 的插件代码,代码质量惨不忍睹),性能也不够强悍。被 Golang 淘汰是必然。
2025-05-26 16:41:05
最近在使用 golang gin 开发一套公司内部的人事管理系统,但是发现权限管理比之前搞的系统要复杂不少, 所以我想借机了解一下传说中的 Casbin 权限控制库,看看是否可以借鉴一下。
总感觉,我目前实用的 gin route group + middleware 的方式,无法满足复杂的权限控制需求。 而且对于粒度更细的权限控制,无法做到灵活的配置。
我目前实现的系统,都是基于角色的权限控制。即每个角色对应一组权限。 还是不太灵活。
当然根源是,gin 没有内置权限控制的功能,我自己实现的这套感觉很乱。我不知道规范的做法是怎样的。 所以,需要找个开源的权限控制库,学习参考一下。
policy.csv 文件内容
p, alice, /api/*, read
p, bob, /version, write
预定义一些策略,也可以存储到数据库, alice 可以访问所有 /api 开头的路径,bob 只能访问 /version 路径。
go 代码
// 加载模型与策略,也可以存储到数据库
e, err := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
sub := "alice" // the user that wants to access a resource.
obj := "data1" // the resource that is going to be accessed.
act := "read" // the operation that the user performs on the resource.
ok, err := e.Enforce(sub, obj, act) //判断用户是否有权限
实际使用中,e.Enforce() 方法通常会在中间件 (middleware) 中调用,以便在处理请求之前进行权限检查。
参考:
https://juejin.cn/post/7108533587292454949
一种基于元模型的访问控制策略描述语言 PML (PERM modeling language) 及其实施机制 PML-EM (PML enforcement mechanism)
参考论文:
https://www.jos.org.cn/jos/article/pdf/5624
https://casbin.org/docs/tutorials/
里面有一堆高质量的介绍文档,找几篇读读,就能有个大概的了解。剩下的就是动手测试一下了。
一个访问请求通常由经典的三元组构成:
Policy 原语 (策略规则) 类似,也是这三个部分。
参考:
https://github.com/xizhibei/blog/issues/101
参考:
https://www.cnblogs.com/wang_yb/p/9987397.html
保存到 CSV 不是一个好的选择,应该存储到数据库中。这样可以更灵活地管理和查询策略规则。
否则,每次都需要登录服务器,修改 CSV 文件,然后重启服务,才能生效。这样不够灵活。
参考:
https://casbin.org/zh/docs/policy-storage/
这里支持 Gorm MySQL 的适配器:
https://casbin.org/zh/docs/adapters
https://www.cnblogs.com/studyzy/p/11380736.html
https://casbin.org/zh/docs/middlewares
这里推荐了两个插件 authz (这个居然是 gin 官方的,不可思议) 和 go-casbin, 不过这么简单的 middleware,我感觉并不需要三方的实现。 不过可以参考一下代码。
今天就到这里吧,实在看不下去了,太枯燥了。
2025-05-18 19:26:46
突然习惯了在 rasa custom action 中直接赋值 slot, 而不在 yml 流程中注明。
我发现在 rasa 中,如果不在 stories.yml 中写明 slot_was_set,则对应的 slot 就不会在 custom action 中赋值成功,这是为什么? 而有的流程,不在 stories.yml 中标注 slot_was_set,slot 也能赋值成功。
实际上,这是一个错觉。
因为,实际测试发现。只有特地类型的 slot 需要在 stories 中写明 slot_was_set,其他类型的 slot 都可以在 custom action 中直接赋值。
如果定义 slot 时,使用的是影响流程的类型。例如:
some_slot:
type: text
influence_conversation: true
mappings:
- type: custom
some_slot 是影响流程的 slot,其赋值必须写到 stories.yml 或者 rules.yml 中, 否则在 custom action 中赋值不会生效。
some_slot:
type: text
influence_conversation: false
mappings:
- type: from_entity
entity: some_entity
from entity,这种要么改成 from custom,要么 stories 中明确写明赋值。否则也是不会生效的。
AI 给出的建议:
2025-05-18 15:20:30
在 Rasa 某个 action 中,使用了 FollowUpAction 的方式, 但是在执行过程中,出现了多余返回的问题。
我起初以为是类似之前遇到的 对话机器人 Rasa(二十四):两例多返回额外消息的 bug 定位 ,是因为 slot set 导致的,或者 form loop 未标记结束导致的。
但是,经过一段一段代码逐一注释,并插入调试返回之后。发现问题的根源是 FollowUpAction 的使用。
return [
FollowUpAction("action_next_step"),
SetSlot("slot_name", "value"),
]
https://github.com/RasaHQ/rasa-sdk/issues/151
不推荐使用 followup action, 推荐使用 story。
将 FollowUpAction 的逻辑,抽象成一个函数,其他地方直接调用这个函数,不使用 followup action 的方式。
对应的原有的 next action, 直接调用这个封装好的函数即可。
在遇到 rasa 中诡异的返回时,可以简化 action 的逻辑,直接返回调试信息。 通过排除法,逐步缩小问题范围。
例如,一个 action 中有多个逻辑分支,可以将每个分支的逻辑逐一注释掉,或者替换为简单的返回语句。 或者,直接在 action 开始就返回调试信息,并 return。
我没有找到对应的文档,于是问了 AI,AI 说并不需要,因为这是 action 强制返回的内容。
我觉得也有道理,毕竟,如果 action 都强制返回了 FollowUpAction 了,在 Story 中再注明,是否会引起执行两次都未可知。
vscode 中搜索文件内容时,如何使用正则包含 FollowupAction 但是不包含 action_deactivate_loop
^(?=.*FollowupAction)(?!.*action_deactivate_loop).+$