骐骥一跃，不能十步；驽马十驾，功在不舍。
我日常的开发环境是 Manjaro Linux，PHP 是自己编译的 7.2。当我升级系统后（起初不知道升级了，电脑开机很久也没关机），发现我的 PHP 炸了！！！php: error while loading shared libraries: libicui18n.so.73: cannot open shared object file: No such file or directory报了这个错误～libicui18n 是国际化组件 Unicode（ICU） 库的一部分，且 PHP 正是依赖它来处理国际化功能。libicui18n.so.73 找不到了？估计是升级后变成 74 或者 75 了？还是被删了……搜了资料发现可以自己安装：yay -S icu对了，如果你是其他发行版，可以参考以下命令：# Ubuntu/Debian 系统 sudo apt-get update sudo apt-get install libicu-dev # CentOS/RHEL 系统 sudo yum update sudo yum install libicu # Fedora 系统 sudo dnf update sudo dnf install libicu然而，我发现我的系统中已经安装了 icu 库，确实是 74 版本：ls /usr/lib/libicui18n.so.74OK，那我创建一个符号链接，将已安装版本的库链接到 PHP 尝试加载的版本，这下总可以了吧！sudo ln -s /usr/lib/libicui18n.so.74 /usr/lib/libicui18n.so.73很意外，报了另一个错误？php: error while loading shared libraries: libicuuc.so.73: cannot open shared object file: No such file or directory继续：sudo ln -s /usr/lib/libicuuc.so.74 /usr/lib/libicuuc.so.73好家伙，又报另一个错：php: error while loading shared libraries: libicudata.so.73: cannot open shared object file: No such file or directorysudo ln -s /usr/lib/libicudata.so.74 /usr/lib/libicudata.so.73补齐了以上 3 个符号链接后，我再次尝试运行 PHP，终于能用了！！！如果你也遇到类似的报错，可以参考。不过，对于大版本的更新，有可能链接后是没办法工作的，这个时候还是重新编译 PHP 吧！以后可能还会遇到这个问题，索性就记录下来，明天除夕，提前祝大家新你快乐～
最近文章快凉凉了，大家记得给文章点赞支持～谢谢大家的喜欢！关注我，分享有趣的知识共同学习！AI 大模型太火爆了！工作中经常使用，感觉已经离不开了大模型了！最近测试了智谱AI大模型，实测下来真的很好用！今天的股市太绿了，我让它给我写个文案，没想到还给股民按摩了哈哈！你是一名文学家，请你为我创作文案，主题是 2024 年，A 股走势绿得可怕。要求：语言流畅通顺，观点独特，行文风骚。 2024年，A股走势绿意盎然，恍若春日里的翠竹，节节败退。那抹绿，如同一幅泼墨山水，挥毫之间，惊心动魄。 这一年，股市的绿意犹如江南的梅雨，绵延不绝，投资者们的心情也随着这绿意，起伏不定。有人调侃：“绿”成了心头大患，也有人戏言：“要想生活过得去，头上总得带点绿。” 然而，在这片绿海之中，智者看到了机遇，勇者捕捉到了逆袭的曙光。绿得可怕，恰似风雨前的乌云，预示着一场翻天覆地的变革即将来临。 风骚独步的投资者，把这片绿色视为一种挑战，一种磨砺。他们深知，在这片绿意盎然的A股市场，唯有独具慧眼，方能洞察先机，把握那稍纵即逝的盈利机会。 绿得可怕，是因为市场在洗牌，在重塑。那些站在风口浪尖的企业，或将涅槃重生，或将黯然离场。而作为投资者的我们，唯有紧跟时代步伐，拥抱变革，方能在这片绿色的股市中，破茧成蝶，翩翩起舞。 2024年，A股走势绿得可怕，但这绿色背后，却蕴藏着无尽的机遇与挑战。让我们以独特的视角，审视这片绿色的股市，携手共进，书写属于我们的辉煌篇章。我平时使用 Go 语言，但我发现官方只给了 Python 和 Java 的接口！Python： https://github.com/zhipuai/zhipuai-sdk-python-v4Java： https://github.com/zhipuai/zhipuai-sdk-java-v4这对于我们 Go 语言开发者来说，确实有点不方便。翻遍了全球最大的男性交友平台——GitHub！Go 语言一个 SDK 都没有！难道要自己手写？对我这种喜欢偷懒的人来说，是不可能自己写的。我坏了十个键盘，每一个都是坏了 Ctrl C V 这三个键。研究了下接口文档（智谱AI开放平台），发现智谱 AI 的 v4 版本接口和 OpenAI 的接口长得挺像的！那就用 go-openai 这个包试试水！GitHub - sashabaranov/go-openai: OpenAI ChatGPT, GPT-3, GPT-4, DALL·E, Whisper API wrapper for Go折腾下来，发现两个问题：第一个问题是，智谱 AI 的接口认证要用 jwt 生成 token，这个在网上一搜，没人做过。根据文档，我自己写了个函数，得到 Authorization 头部后，传给 go-openai 作为请求的 Header 头发出去！import "github.com/golang-jwt/jwt/v5" // apikey 为智谱提供的 API Key func splitAPIKey(apikey string) (string, string) { parts := strings.Split(apikey, ".") if len(parts) != 2 { return "", "" } return parts[0], parts[1] } func getAuthorization(authToken string) string { id, secret := splitAPIKey(authToken) payload := jwt.MapClaims{ "api_key": id, "exp": time.Now().Add(3 * time.Minute).UnixMilli(), "timestamp": time.Now().UnixMilli(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload) token.Header["sign_type"] = "SIGN" res, _ := token.SignedString([]byte(secret)) return res }第二个问题是，你能通过认证，但调用失败！！！这个我折腾了一下午，总算找到了罪魁祸首！排查的方式是，将我的请求复制成 cURL 到终端，发现是能正常响应的！！！而 go-openai 包在底层发送请求的时候，会自己加上以下头部： req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "text/event-stream") req.Header.Set("Cache-Control", "no-cache") req.Header.Set("Connection", "keep-alive") resp, err := client.config.HTTPClient.Do(req) //nolint:bodyclose // body is closed in stream.Close() if err != nil { return new(streamReader[T]), err }智谱 AI 服务器端，是不支持 Accept: text/event-stream 的！！！感兴趣的同学可以自己测试下，只要带上 Accept 头就会报错，即便返回接口就是 text/event-stream，原因不明。那该如何解决这个问题呢？我发现 go-openai 包里的 requestBuilder 是个 interface，但可惜没有暴露出来！如果它暴露出来了，我可以套娃后替换它！不过请求头是在 requestBuilder 之后，所以这个方案只能放弃。但它提供了自定义的 HTTPClient！而 HTTPClient 最终会请求到 client.config.HTTPClient.Do(req) 方法！所以用 RoundTrip(*Request) (*Response, error) 接口截获请求就完事了！怎么搞呢？灰常简单：type removeAcceptHeaderRoundTrip struct{} func (s *removeAcceptHeaderRoundTrip) RoundTrip(req *http.Request) (resp *http.Response, err error) { req.Header.Del("Accept") resp, err = http.DefaultTransport.RoundTrip(req) return }然后初始化的时候，换成我们的 RoundTrip 就好了！ cfg := openai.DefaultConfig(authorization) cfg.BaseURL = "https://open.bigmodel.cn/api/paas/v4" cfg.HTTPClient.Transport = &removeAcceptHeaderRoundTrip{} // 换成我们的 RoundTrip cli := openai.NewClientWithConfig(cfg) stream, err := cli.CreateChatCompletionStream(ctx, openai.ChatCompletionRequest{ // Model: "glm-3-turbo", Model: "glm-4", Messages: []openai.ChatCompletionMessage{ {Role: openai.ChatMessageRoleUser, Content: input}, }, })是不是很惊艳！！！interface 大法好！抽象得当，有时候写代码真的就是几分钟的事情！！！不得不说 Go 的标准库是真的舒服，简单翻一下代码，就知道怎么写了，1 分钟的事情干完下班！！！（如果觉得文章有帮助，建议收藏起来，方便下次阅读）之前写文章的时候，有人喷我用 interface 用 wire 把代码搞得太复杂了……仁扬：我是如何组织 Go 代码的（目录结构 依赖注入 wire）现在这个就是活生生的例子……假如没有暴露 RoundTrip 接口，那你什么都做不了，你只能硬质头皮自己写 SDK 写反射写各种恶心的黑科技……总之，写代码抽象思维很重要，需要时刻注意代码的领域和边界！！！夜已深，我把完整的代码贴上来吧，大家自行食用：package main import ( "context" "errors" "fmt" "io" "net/http" "strings" "time" "github.com/golang-jwt/jwt/v5" "github.com/sashabaranov/go-openai" ) func main() { input := `你是一名文学家，请你为我创作文案，主题是2024年，A股走势绿得可怕。要求：语言流畅通顺，观点独特，行文风骚。 ` ctx := context.Background() authToken := "......" authorization := getAuthorization(authToken) cfg := openai.DefaultConfig(authorization) cfg.BaseURL = "https://open.bigmodel.cn/api/paas/v4" cfg.HTTPClient.Transport = &removeAcceptHeaderRoundTrip{} // 替换成我们自己的 cli := openai.NewClientWithConfig(cfg) stream, err := cli.CreateChatCompletionStream(ctx, openai.ChatCompletionRequest{ // Model: "glm-3-turbo", Model: "glm-4", Messages: []openai.ChatCompletionMessage{ {Role: openai.ChatMessageRoleUser, Content: input}, }, }) if err != nil { fmt.Println(err.Error()) return } defer stream.Close() fmt.Print(input) fmt.Println("") for { response, e := stream.Recv() if errors.Is(e, io.EOF) { break } if e != nil { break } content := response.Choices[0].Delta.Content fmt.Print(content) } fmt.Println("") return } func getAuthorization(authToken string) string { id, secret := splitAPIKey(authToken) payload := jwt.MapClaims{ "api_key": id, "exp": time.Now().Add(3 * time.Minute).UnixMilli(), "timestamp": time.Now().UnixMilli(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload) token.Header["sign_type"] = "SIGN" res, _ := token.SignedString([]byte(secret)) return res } func splitAPIKey(apikey string) (string, string) { parts := strings.Split(apikey, ".") if len(parts) != 2 { return "", "" } return parts[0], parts[1] } type removeAcceptHeaderRoundTrip struct{} func (s *removeAcceptHeaderRoundTrip) RoundTrip(req *http.Request) (resp *http.Response, err error) { req.Header.Del("Accept") resp, err = http.DefaultTransport.RoundTrip(req) return }如果觉得文章有帮助，建议收藏起来，方便下次阅读。喜欢我的文章可以关注我，分享有趣的知识共同学习！