<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>go on Two Tigers Engineering</title>
    <link>https://blog.twotigers.xyz/series/go/</link>
    <description>Recent content in go on Two Tigers Engineering</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 26 May 2025 14:00:00 +0800</lastBuildDate><atom:link href="https://blog.twotigers.xyz/series/go/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>如何优雅地重复读取HTTP响应体</title>
      <link>https://blog.twotigers.xyz/posts/go/http_body/</link>
      <pubDate>Mon, 26 May 2025 14:00:00 +0800</pubDate>
      
      <guid>https://blog.twotigers.xyz/posts/go/http_body/</guid>
      <description>背景介绍 在Go语言中处理HTTP请求时，我们经常需要多次读取响应体(Response Body)的内容。然而，默认情况下Response Body只能被读取一次，这是因为它实现了io.ReadCloser接口，读取完毕后数据就会被消费掉。本文将介绍一个优雅的解决方案。
问题描述 假设我们有以下场景：
需要在日志中记录API的响应内容 需要对响应内容进行多次处理 需要在中间件中查看响应内容，同时不影响后续处理 解决方案 我们可以使用io.TeeReader来实现响应体的复制，这样就能多次读取相同的内容。以下是具体实现：
package main import ( &amp;#34;bytes&amp;#34; &amp;#34;fmt&amp;#34; &amp;#34;io&amp;#34; &amp;#34;net/http&amp;#34; ) func DupReadCloser(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser) { var buf bytes.Buffer tee := io.TeeReader(reader, &amp;amp;buf) return io.NopCloser(tee), io.NopCloser(&amp;amp;buf) } func DupResponseBody(resp *http.Response) ([]byte, error) { var buf io.ReadCloser resp.Body, buf = DupReadCloser(resp.Body) data, err := io.ReadAll(resp.Body) if err != nil { return nil, err } resp.Body = buf return data, nil } 代码解析 DupReadCloser函数：接收一个io.</description>
    </item>
    
    <item>
      <title>golang 使用反射处理 string, 用于 request body 中的 string 字段进行 trim</title>
      <link>https://blog.twotigers.xyz/posts/go/parser_string/</link>
      <pubDate>Thu, 01 Dec 2022 18:21:08 +0800</pubDate>
      
      <guid>https://blog.twotigers.xyz/posts/go/parser_string/</guid>
      <description>将 struct 中的string 字段进行 trim
func trimSpace(any interface{}) { ov := reflect.ValueOf(any) if ov.Kind() == reflect.Ptr &amp;amp;&amp;amp; !ov.IsNil() { ov = ov.Elem() } ot := reflect.TypeOf(any) if ot.Kind() == reflect.Ptr { ot = ot.Elem() } for i := 0; i &amp;lt; ot.NumField(); i++ { field := ov.Field(i) if field.Kind() == reflect.Ptr { if field.Elem().Kind() == reflect.Struct { trimSpace(field.Interface()) continue } field = field.Elem() } if field.CanInterface() &amp;amp;&amp;amp; field.IsValid() { if field.</description>
    </item>
    
    <item>
      <title>golang 处理图片</title>
      <link>https://blog.twotigers.xyz/posts/go/image/</link>
      <pubDate>Mon, 28 Feb 2022 11:23:08 +0800</pubDate>
      
      <guid>https://blog.twotigers.xyz/posts/go/image/</guid>
      <description>支持不同格式之间的转换 支持图片像素的压缩以及拉长 package main import ( &amp;#34;bytes&amp;#34; &amp;#34;errors&amp;#34; &amp;#34;fmt&amp;#34; &amp;#34;image&amp;#34; &amp;#34;image/jpeg&amp;#34; &amp;#34;image/png&amp;#34; &amp;#34;io/ioutil&amp;#34; &amp;#34;os&amp;#34; &amp;#34;golang.org/x/image/draw&amp;#34; _ &amp;#34;golang.org/x/image/webp&amp;#34; // for RegisterFormat ) type imageHelper struct { img image.Image fileName string format string scale draw.Scaler } func NewImageHelper(fileName string, scale ...draw.Scaler) *imageHelper { var s draw.Scaler if scale == nil { s = draw.ApproxBiLinear } else { s = scale[0] } return &amp;amp;imageHelper{ fileName: fileName, scale: s, } } func (r *imageHelper) GuessFileFormat() (string, error) { if r.</description>
    </item>
    
    <item>
      <title>golang 合并多个文件</title>
      <link>https://blog.twotigers.xyz/posts/go/combine/</link>
      <pubDate>Wed, 15 Dec 2021 23:40:25 +0800</pubDate>
      
      <guid>https://blog.twotigers.xyz/posts/go/combine/</guid>
      <description>package main import ( &amp;#34;fmt&amp;#34; &amp;#34;github.com/coreos/pkg/progressutil&amp;#34; &amp;#34;io&amp;#34; &amp;#34;os&amp;#34; &amp;#34;path/filepath&amp;#34; &amp;#34;time&amp;#34; ) func combineMultipleFiles(outPutFile string, files ...string) { abs, err := filepath.Abs(outPutFile) if err != nil { panic(&amp;#34;can not get outPutFile absolute path&amp;#34;) } dir := filepath.Dir(abs) if _, err := os.Stat(dir); err != nil { if os.IsNotExist(err) { err := os.MkdirAll(dir, os.ModePerm) if err != nil { panic(fmt.Sprintf(&amp;#34;can not mkdirs, path: %s, err: %s&amp;#34;, dir, err)) } } } file, err := os.</description>
    </item>
    
    <item>
      <title>golang validator 支持翻译以及使用 json tag</title>
      <link>https://blog.twotigers.xyz/posts/go/validator/</link>
      <pubDate>Fri, 19 Nov 2021 23:40:25 +0800</pubDate>
      
      <guid>https://blog.twotigers.xyz/posts/go/validator/</guid>
      <description>package main import ( &amp;#34;encoding/json&amp;#34; &amp;#34;fmt&amp;#34; &amp;#34;github.com/go-playground/locales/en&amp;#34; &amp;#34;github.com/go-playground/locales/zh&amp;#34; &amp;#34;github.com/go-playground/locales/zh_Hant_TW&amp;#34; ut &amp;#34;github.com/go-playground/universal-translator&amp;#34; &amp;#34;github.com/go-playground/validator/v10&amp;#34; entranslations &amp;#34;github.com/go-playground/validator/v10/translations/en&amp;#34; zhtranslations &amp;#34;github.com/go-playground/validator/v10/translations/zh&amp;#34; zhtwtranslations &amp;#34;github.com/go-playground/validator/v10/translations/zh_tw&amp;#34; &amp;#34;reflect&amp;#34; &amp;#34;strings&amp;#34; ) type InternalAccountTransferRequest struct { Name string `json:&amp;#34;name&amp;#34; binding:&amp;#34;required&amp;#34;` Age string `json:&amp;#34;age_test&amp;#34; binding:&amp;#34;required&amp;#34;` Class string `binding:&amp;#34;required&amp;#34;` } func main() { s := `{&amp;#34;name&amp;#34;: &amp;#34;123&amp;#34;}` var requset InternalAccountTransferRequest json.Unmarshal([]byte(s), &amp;amp;requset) //requset.Class = &amp;#34;123&amp;#34; v := validator.New() var err error uni := ut.New(en.New(), en.New(), zh_Hant_TW.New(), zh.New()) zh_, _ := uni.GetTranslator(&amp;#34;zh&amp;#34;) zhtranslations.RegisterDefaultTranslations(v, zh_) tw_, _ := uni.</description>
    </item>
    
  </channel>
</rss>
