Go语言中如何让第三方timestamppb.Timestamp实现sql.Scanner接口?
解决PostgreSQL扫描到第三方timestamppb.Timestamp的问题
问题本质
Go不允许给非本地包的类型(比如timestamppb.Timestamp)新增方法,所以没法直接给它实现sql.Scanner接口。之前用类型别名的方式,因为底层带sync.Mutex,值传递会触发锁拷贝警告;就算改成指针接收者,也没法适配第三方结构体里的*timestamppb.Timestamp字段——毕竟类型别名和原类型是两个完全独立的类型,Go不会自动关联。
直接用第三方结构体的可行方案
用包装器类型做扫描中间层,先把数据库返回值扫进包装器,再转成timestamppb.Timestamp赋值给结构体字段,完美适配第三方结构体的场景。
具体实现
- 定义包装器类型,持有
*timestamppb.Timestamp并实现sql.Scanner接口:
import ( "fmt" "time" "google.golang.org/protobuf/types/known/timestamppb" ) type TimestampWrapper struct { Ts *timestamppb.Timestamp } func (w *TimestampWrapper) Scan(src any) error { // 先将数据库返回的类型转换为time.Time var t time.Time switch v := src.(type) { case time.Time: t = v case []byte: var err error t, err = time.Parse(time.RFC3339, string(v)) if err != nil { return fmt.Errorf("解析时间戳字节失败: %w", err) } case string: var err error t, err = time.Parse(time.RFC3339, v) if err != nil { return fmt.Errorf("解析时间戳字符串失败: %w", err) } default: return fmt.Errorf("不支持的时间戳类型: %T", src) } // 将time.Time转换为timestamppb.Timestamp if w.Ts == nil { w.Ts = timestamppb.New(t) } else { w.Ts.Seconds = t.Unix() w.Ts.Nanos = int32(t.Nanosecond()) } return nil }
- 在扫描逻辑中用包装器对接第三方结构体:
func (s *Storage) test() { rows, err := s.db.Query("SELECT \"test_date\" FROM \"test_table\" LIMIT 1") if err != nil { log.Fatalln(err) } defer rows.Close() for rows.Next() { testStruct := test{} // 初始化包装器,关联结构体的date字段 wrapper := &TimestampWrapper{Ts: testStruct.date} // 执行扫描操作 if err := rows.Scan(wrapper); err != nil { log.Fatalln(err) } // 将转换后的值赋值给结构体字段 testStruct.date = wrapper.Ts // 此时testStruct已可以正常使用 } }
为什么之前的类型别名方案无效
你定义的TimestampPb是timestamppb.Timestamp的类型别名,但Go中类型别名和原类型是完全独立的——你给*TimestampPb实现的Scan接口,和*timestamppb.Timestamp没有关联关系,所以扫描&testStruct.date(类型为**timestamppb.Timestamp)时,Go找不到对应的Scan方法,自然无法生效。
内容的提问来源于stack exchange,提问作者abr_stackoverflow
相关产品推荐
相关产品推荐

