过去一个月(2024.12.19~2025.01.18)主要做了三件事:处理烂尾项目、开新项目、重温 PHP

处理烂尾项目

在 2020.12 的时候开了个单点登录/用户中心的项目 passport,当时是为了解决以前每次 Hackathon 都要重新做一次注册登录的繁琐事开的坑,后来第一个版本写好了,但是也没有再参加过 Hackathon,并且觉得那个版本不够完美——因为不支持 OAuth、不支持微信登录等等,于是迟迟没有正式部署。

于是这个月着手把这个项目捡起来了,完全重构了下,目前支持注册登录,近期会正式部署(等域名备案下来之后),然后就会正式启用了。

重构这个项目的时候水了一篇文章: 《Go 函数只返回结构体中的成员,GC 会怎么处理?》

开新项目

开了三个新项目(按照完成度排序):一个是 OpenCL 相关的,一个是小游戏,还有一个是 gin 的周边工具。

第一个项目的第一版基本完成了,收获了一些 CGO 相关的经验,水了一篇文章:《Go 用 CGO 调用 C 函数的两种姿势:静态编译和动态链接》

第二个项目算是 WebSocket 的练手吧,目前完成度仅 20% 的样子,争取春节前能上线

第三个项目是因为最近 gin 项目做得多,每开个新项目都要手动建立几乎一样的目录结构,略繁琐,所以想做一个类似 Laravel artisan 的工具

重温 PHP

因为博客迁移之后后台有报错以及帮同学研究网站的搭建,找了些开源项目,于是又折腾了下 PHP 的东西:编译 PHP、折腾 Primary script unknown 的报错。

感慨两点:

  • PHP 项目的兼容性比想象中的差,或者是 PHP 最近的大版本改动比较大?Typecho 1.3.0 在 PHP 8.3 是正常的,但是在 8.4 会有几个语法弃用导致的报错;帮同学看的开源项目还一直在维护的,但是直接在 composer.json 限制死只能 8.2/8.3,最新的 8.4 反而不行😂
  • 有时候环境问题会很莫名其妙,要懂得适时放弃折腾直接重装:博客迁移之后后台莫名其妙的白屏,然后同样的环境用 Typecho 源码全新安装之后是正常的,diff 了两份 Typecho 源码发现完全没有区别,最后无奈把 /usr 目录替换到全新安装的那边之后解决白屏问题;以前搭的虚拟机里的 php-fpm,只有一个目录是能正常用的,其他目录一定会报 Primary script unknown,研究过路径、文件权限,都没法解决,最后放弃这台虚拟机了😂

在 Go 里可以通过 CGO 调用 C 函数,最简单的写法就是直接在 import "C" 前一行写 C 函数:

package main

/*
int sum(int a, int b) { return a + b; }
*/
import "C"
import "fmt"

func main() {
    fmt.Println(C.sum(C.int(1), C.int(2)))
}

但,如果是那么简单的函数,也不需要用到 CGO,直接 Go 实现就行了,真要用到 CGO 了,大概率就是你需要调用一些比较复杂的 C 代码,可以用下面两种方式实现:

阅读剩余部分

最近在给一个 HTTP 服务写 SDK,这个 HTTP 服务的响应是下面这种经典格式:

{
    "cdoe": 0,
    "msg": "ok",
    "data": {}
}

那么在 Go 里定义结构体的时候就会长这样:

type Custom struct {
    // ...
}

type SomeResponse struct {
    Code int    `json:"code"`
    Msg  string `json:"msg"`
    Data Custom `json:"data"`
}

相应的方法会长这样:

SomeService(ctx context.Context, param any) (*Custom, error)

因为 Custom 的内部可能会有很多数据,所以是以指针作为返回值的,尽量避免数据拷贝;而 Code != 0 这种情况就通过 error 返回。

内部实现大概会是这样:

func (s SDK) SomeService(ctx context.Context, param any) (*Custom, error) {
    var resp SomeResponse
    err := s.request(ctx, "/someservice", param, &resp)
    if err != nil {
        return nil, err
    }

    if resp.Code != 0 {
        return nil, fmt.Errorf("code: %d, err: %s", resp.Code, resp.Msg)
    }

    return &resp.Data, nil
}

通常我们到这一步就算是完成任务了,但是这时候我想了下:return 之后,GC 是怎么回收 resp 的呢?是会把 resp.Coderesp.Msg 的内存回收了,只让 resp.Data 逃逸吗?

这个问题不算复杂,动手做个实验就知道了。先把上面的代码简化一下:

阅读剩余部分

这个月写的代码比较多了,主要在做重构

重构

这个月将以前一个烂尾的项目拿出来彻底重构了,后端由 PHP 换成了 Go,前端(之前没有,纯 iOS 捷径交互)用了 React.js。

抽空看了下开源项目 Answer 的编码风格,Answer 用的 wire 做依赖注入,整体风格还是挺清晰易懂的(我们之前做的项目也用了 wire,但还是没用到精髓,更像是为了用而用)。

这次重构进行得比较慢,因为在学习 Answer 的编码风格,同时也在整理一套我自己的 Go HTTP API 项目的 skeleton

Cloudflare Tunnel

尝试了一下 Cloudflare Tunnel,第一件事就是把博客从腾讯云香港机房迁移回到家里的服务器(没错,你看到的这篇文章就是放在广州的一台旧笔记本上的),然后用 Cloudflare Tunnel 暴露到公网,还自带 CDN

新文章

关于时区在开发中的一些思考

过去三年我在做一个面向港澳台和日本的未上线项目,关于「时区」这个概念,在做开发的过程中会有些抽象,一直觉得对于时区的处理很混乱(不管是代码、服务器设置,还是运营需求)。

今年休假去日本玩了一趟,这是我人生中第一次离开东八区,因为只是短暂的旅途,跟国内的联系还是非常紧密的,所以切实体会到了跨时区带来的一些不同体验。

《小谢尔顿》- 时区

于是决定写下这篇关于「时区」的思考的文章,可能会对各位做开发的朋友有一点帮助 :-P

阅读剩余部分

Juniper Photon 的启发,决定从这个月开始,每月写一篇总结,主要记录分享下过去一个月(2024.10.19~11.18)的产出和思考


过去一个月算是一个比较松弛的状态,全国各地跑了好几个城市,跟几个朋友聊了下近况和准备的事情,也趁着闲暇时间梳理了下之前的一些烂尾项目,把该还的技术债还一还。

阅读剩余部分