github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/cmd/internal/svc/codegen/httpclientproxy.go (about) 1 package codegen 2 3 import ( 4 "bufio" 5 "bytes" 6 "github.com/unionj-cloud/go-doudou/cmd/internal/astutils" 7 v3helper "github.com/unionj-cloud/go-doudou/cmd/internal/openapi/v3" 8 "github.com/unionj-cloud/go-doudou/toolkit/copier" 9 "github.com/unionj-cloud/go-doudou/toolkit/zlogger" 10 "github.com/unionj-cloud/go-doudou/version" 11 "go/ast" 12 "go/parser" 13 "go/token" 14 "io/ioutil" 15 "os" 16 "path/filepath" 17 "strings" 18 "text/template" 19 ) 20 21 var cpimportTmpl = ` 22 "context" 23 "github.com/pkg/errors" 24 "github.com/prometheus/client_golang/prometheus" 25 "github.com/rs/zerolog" 26 "github.com/unionj-cloud/go-doudou/toolkit/zlogger" 27 "github.com/slok/goresilience" 28 "github.com/go-resty/resty/v2" 29 "github.com/slok/goresilience/circuitbreaker" 30 rerrors "github.com/slok/goresilience/errors" 31 "github.com/slok/goresilience/metrics" 32 "github.com/slok/goresilience/retry" 33 "github.com/slok/goresilience/timeout" 34 v3 "github.com/unionj-cloud/go-doudou/toolkit/openapi/v3" 35 "os" 36 "time" 37 "{{.VoPackage}}" 38 ` 39 40 var appendTmpl = ` 41 {{- range $m := .Meta.Methods }} 42 func (receiver *{{$.SvcName}}ClientProxy) {{$m.Name}}(ctx context.Context, _headers map[string]string, {{- range $i, $p := $m.Params}} 43 {{- if ne $p.Type "context.Context" }} 44 {{- $p.Name}} {{$p.Type}}, 45 {{- end }} 46 {{- end }}) (_resp *resty.Response, {{- range $i, $r := $m.Results}} 47 {{- if $i}},{{end}} 48 {{- $r.Name}} {{$r.Type}} 49 {{- end }}) { 50 if _err := receiver.runner.Run(ctx, func(ctx context.Context) error { 51 _resp, {{ range $i, $r := $m.Results }}{{- if $i}},{{- end}}{{- $r.Name }}{{- end }} = receiver.client.{{$m.Name}}( 52 ctx, 53 _headers, 54 {{- range $p := $m.Params }} 55 {{- if ne $p.Type "context.Context" }} 56 {{- if isVarargs $p.Type }} 57 {{ $p.Name }}..., 58 {{- else }} 59 {{ $p.Name }}, 60 {{- end }} 61 {{- end }} 62 {{- end }} 63 ) 64 {{- range $r := $m.Results }} 65 {{- if eq $r.Type "error" }} 66 if {{ $r.Name }} != nil { 67 return errors.Wrap({{ $r.Name }}, "call {{$m.Name}} fail") 68 } 69 {{- end }} 70 {{- end }} 71 return nil 72 }); _err != nil { 73 // you can implement your fallback logic here 74 if errors.Is(_err, rerrors.ErrCircuitOpen) { 75 receiver.logger.Error().Err(_err).Msg("") 76 } 77 {{- range $r := $m.Results }} 78 {{- if eq $r.Type "error" }} 79 {{ $r.Name }} = errors.Wrap(_err, "call {{$m.Name}} fail") 80 {{- end }} 81 {{- end }} 82 } 83 return 84 } 85 {{- end }} 86 ` 87 88 var baseTmpl = `/** 89 * Generated by go-doudou {{.Version}}. 90 * You can edit it as your need. 91 */ 92 package client 93 94 import () 95 96 type {{.SvcName}}ClientProxy struct { 97 client *{{.SvcName}}Client 98 logger zerolog.Logger 99 runner goresilience.Runner 100 } 101 102 ` + appendTmpl + ` 103 104 type ProxyOption func(*{{.SvcName}}ClientProxy) 105 106 func WithRunner(runner goresilience.Runner) ProxyOption { 107 return func(proxy *{{.SvcName}}ClientProxy) { 108 proxy.runner = runner 109 } 110 } 111 112 func WithLogger(logger zerolog.Logger) ProxyOption { 113 return func(proxy *{{.SvcName}}ClientProxy) { 114 proxy.logger = logger 115 } 116 } 117 118 func New{{.SvcName}}ClientProxy(client *{{.SvcName}}Client, opts ...ProxyOption) *{{.SvcName}}ClientProxy { 119 cp := &{{.SvcName}}ClientProxy{ 120 client: client, 121 logger: zlogger.Logger, 122 } 123 124 for _, opt := range opts { 125 opt(cp) 126 } 127 128 if cp.runner == nil { 129 var mid []goresilience.Middleware 130 mid = append(mid, metrics.NewMiddleware("{{.ServicePackage}}_client", metrics.NewPrometheusRecorder(prometheus.DefaultRegisterer))) 131 mid = append(mid, circuitbreaker.NewMiddleware(circuitbreaker.Config{ 132 ErrorPercentThresholdToOpen: 50, 133 MinimumRequestToOpen: 6, 134 SuccessfulRequiredOnHalfOpen: 1, 135 WaitDurationInOpenState: 5 * time.Second, 136 MetricsSlidingWindowBucketQuantity: 10, 137 MetricsBucketDuration: 1 * time.Second, 138 }), 139 timeout.NewMiddleware(timeout.Config{ 140 Timeout: 3 * time.Minute, 141 }), 142 retry.NewMiddleware(retry.Config{ 143 Times: 3, 144 })) 145 146 cp.runner = goresilience.RunnerChain(mid...) 147 } 148 149 return cp 150 } 151 ` 152 153 func unimplementedSvcMethods(meta *astutils.InterfaceMeta, clientfile string) { 154 fset := token.NewFileSet() 155 root, err := parser.ParseFile(fset, clientfile, nil, parser.ParseComments) 156 if err != nil { 157 panic(err) 158 } 159 sc := astutils.NewStructCollector(astutils.ExprString) 160 ast.Walk(sc, root) 161 if handlers, exists := sc.Methods[meta.Name+"ClientProxy"]; exists { 162 var notimplemented []astutils.MethodMeta 163 for _, item := range meta.Methods { 164 for _, handler := range handlers { 165 if item.Name == handler.Name { 166 goto L 167 } 168 } 169 notimplemented = append(notimplemented, item) 170 171 L: 172 } 173 174 meta.Methods = notimplemented 175 } 176 } 177 178 // GenGoClientProxy wraps client with resiliency features 179 func GenGoClientProxy(dir string, ic astutils.InterfaceCollector) { 180 var ( 181 err error 182 clientfile string 183 f *os.File 184 tpl *template.Template 185 buf bytes.Buffer 186 clientDir string 187 fi os.FileInfo 188 modfile string 189 modName string 190 firstLine string 191 modf *os.File 192 meta astutils.InterfaceMeta 193 clientProxyTmpl string 194 importBuf bytes.Buffer 195 ) 196 clientDir = filepath.Join(dir, "client") 197 if err = os.MkdirAll(clientDir, os.ModePerm); err != nil { 198 panic(err) 199 } 200 201 clientfile = filepath.Join(clientDir, "clientproxy.go") 202 fi, err = os.Stat(clientfile) 203 if err != nil && !os.IsNotExist(err) { 204 panic(err) 205 } 206 err = copier.DeepCopy(ic.Interfaces[0], &meta) 207 if err != nil { 208 panic(err) 209 } 210 if fi != nil { 211 zlogger.Warn().Msg("New content will be append to clientproxy.go file") 212 if f, err = os.OpenFile(clientfile, os.O_APPEND, os.ModePerm); err != nil { 213 panic(err) 214 } 215 defer f.Close() 216 clientProxyTmpl = appendTmpl 217 218 unimplementedSvcMethods(&meta, clientfile) 219 } else { 220 if f, err = os.Create(clientfile); err != nil { 221 panic(err) 222 } 223 defer f.Close() 224 clientProxyTmpl = baseTmpl 225 } 226 227 modfile = filepath.Join(dir, "go.mod") 228 if modf, err = os.Open(modfile); err != nil { 229 panic(err) 230 } 231 reader := bufio.NewReader(modf) 232 firstLine, _ = reader.ReadString('\n') 233 modName = strings.TrimSpace(strings.TrimPrefix(firstLine, "module")) 234 235 funcMap := make(map[string]interface{}) 236 funcMap["isVarargs"] = v3helper.IsVarargs 237 if tpl, err = template.New("clientproxy.go.tmpl").Funcs(funcMap).Parse(clientProxyTmpl); err != nil { 238 panic(err) 239 } 240 if err = tpl.Execute(&buf, struct { 241 VoPackage string 242 Meta astutils.InterfaceMeta 243 ServicePackage string 244 ServiceAlias string 245 SvcName string 246 Version string 247 }{ 248 VoPackage: modName + "/vo", 249 Meta: meta, 250 ServicePackage: modName, 251 ServiceAlias: ic.Package.Name, 252 SvcName: ic.Interfaces[0].Name, 253 Version: version.Release, 254 }); err != nil { 255 panic(err) 256 } 257 258 original, err := ioutil.ReadAll(f) 259 if err != nil { 260 panic(err) 261 } 262 263 original = append(original, buf.Bytes()...) 264 if tpl, err = template.New("cpimportimpl.go.tmpl").Parse(cpimportTmpl); err != nil { 265 panic(err) 266 } 267 if err = tpl.Execute(&importBuf, struct { 268 ConfigPackage string 269 VoPackage string 270 }{ 271 VoPackage: modName + "/vo", 272 ConfigPackage: modName + "/config", 273 }); err != nil { 274 panic(err) 275 } 276 original = astutils.AppendImportStatements(original, importBuf.Bytes()) 277 astutils.FixImport(original, clientfile) 278 }