Skip to content
This repository has been archived by the owner on Jan 23, 2024. It is now read-only.

Commit

Permalink
fix: error when param d is "404"
Browse files Browse the repository at this point in the history
  • Loading branch information
AH-dark committed Jan 7, 2024
1 parent 5967bed commit 83d40cd
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 64 deletions.
4 changes: 2 additions & 2 deletions server/controllers/avatar/get_avatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type GetAvatarRequest struct {
Size int64 `query:"s"`
Default string `query:"d"`
Rating string `query:"r"`
Force bool `query:"f"`
Force string `query:"f"`
}

func (h *handlers) GetAvatar(ctx context.Context, c *app.RequestContext) {
Expand All @@ -38,7 +38,7 @@ func (h *handlers) GetAvatar(ctx context.Context, c *app.RequestContext) {
args := avatar.GetAvatarArgs{
Size: req.Size,
Default: req.Default,
ForceDefault: req.Force,
ForceDefault: req.Force == "y",
Rating: req.Rating,
EnableWebp: lo.SomeBy(strings.Split(bytestring.BytesToString(c.GetHeader("Accept")), ","), func(item string) bool {
return strings.HasPrefix(item, "image/webp")
Expand Down
73 changes: 47 additions & 26 deletions services/avatar/get_gravatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package avatar
import (
"context"
"fmt"
"github.com/imroc/req/v3"
"github.com/samber/lo"
"image"
"image/jpeg"
"net/url"
"strconv"
"time"

Expand All @@ -18,44 +19,64 @@ type GetGravatarResult struct {
LastModified time.Time
}

func (s *service) getGravatar(ctx context.Context, hash string, args GetAvatarArgs) (GetGravatarResult, error) {
ctx, span := tracer.Start(ctx, "service.AvatarService.getGravatar")
defer span.End()
func initGravatarClient() *req.Client {
c := req.C().
EnableDumpEachRequest().
SetBaseURL("https://gravatar.com/avatar/").
SetCommonHeader("Accept", "image/jpeg").
SetCommonQueryParams(map[string]string{
"s": "80",
}).
OnAfterResponse(func(client *req.Client, resp *req.Response) error {
if resp.Err != nil { // There is an underlying error, e.g. network error or unmarshal error (SetSuccessResult or SetErrorResult was invoked before).
if dump := resp.Dump(); dump != "" { // Append dump content to original underlying error to help troubleshoot.
resp.Err = fmt.Errorf("error: %s\nraw content:\n%s", resp.Err.Error(), resp.Dump())
}
return nil // Skip the following logic if there is an underlying error.
}

values := url.Values{}
values.Set("d", args.Default)
values.Set("s", strconv.FormatInt(args.Size, 10))
values.Set("r", args.Rating)
if !resp.IsSuccessState() {
resp.Err = fmt.Errorf("bad response, raw content:\n%s", resp.Dump())
}

// force default
if args.ForceDefault {
hash = ""
}
return nil
}).
WrapRoundTripFunc(WithTracer)

// get gravatar
u, err := url.Parse(fmt.Sprintf("https://www.gravatar.com/avatar/%s", hash))
if err != nil {
otelzap.L().Ctx(ctx).Error("parse gravatar url failed", zap.Error(err))
return GetGravatarResult{}, err
}
u.RawQuery = values.Encode()
return c
}

stream, resp, err := s.downloadAvatar(ctx, u.String())
if err != nil {
func (s *service) getGravatar(ctx context.Context, hash string, args GetAvatarArgs) (GetGravatarResult, error) {
ctx, span := tracer.Start(ctx, "service.AvatarService.getGravatar")
defer span.End()

resp, err := s.gravatarClient.R().
SetContext(ctx).
SetPathParam("hash", lo.If(args.ForceDefault, "").Else(hash)).
SetQueryParams(map[string]string{
"d": args.Default,
"s": strconv.FormatInt(args.Size, 10),
"r": args.Rating,
}).
Get("{hash}")
if resp.GetStatusCode() == 404 {
return GetGravatarResult{}, nil
} else if err != nil {
otelzap.L().Ctx(ctx).Error("download gravatar failed", zap.Error(err))
return GetGravatarResult{}, err
} else if resp.IsErrorState() {
otelzap.L().Ctx(ctx).Error("download gravatar failed", zap.Error(resp.Err))
return GetGravatarResult{}, fmt.Errorf("download gravatar failed: %v", resp.ErrorResult())
}

img, err := jpeg.Decode(stream)
defer resp.Body.Close()

img, err := jpeg.Decode(resp.Body)
if err != nil {
otelzap.L().Ctx(ctx).Error("failed to decode gravatar", zap.Error(err))
return GetGravatarResult{}, err
}

if err := stream.Close(); err != nil {
otelzap.L().Ctx(ctx).Warn("close stream failed", zap.Error(err))
}

lastModified, err := time.Parse(time.RFC1123, resp.Header.Get("Last-Modified"))
if err != nil {
otelzap.L().Ctx(ctx).Warn("parse last modified failed", zap.Error(err))
Expand Down
47 changes: 41 additions & 6 deletions services/avatar/get_qq_avatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,45 @@ package avatar
import (
"context"
"fmt"
"github.com/imroc/req/v3"
"go.uber.org/zap"
"image"
"image/jpeg"
"strconv"

"github.com/nfnt/resize"
"github.com/uptrace/opentelemetry-go-extra/otelzap"
)

func initQQClient() *req.Client {
c := req.C().
EnableDumpEachRequest().
SetBaseURL("https://q.qlogo.cn/").
SetCommonHeader("Accept", "image/jpeg").
SetCommonQueryParams(map[string]string{
"dst_uin": "0",
"spec": "640",
"img_type": "jpg",
}).
OnAfterResponse(func(client *req.Client, resp *req.Response) error {
if resp.Err != nil { // There is an underlying error, e.g. network error or unmarshal error (SetSuccessResult or SetErrorResult was invoked before).
if dump := resp.Dump(); dump != "" { // Append dump content to original underlying error to help troubleshoot.
resp.Err = fmt.Errorf("error: %s\nraw content:\n%s", resp.Err.Error(), resp.Dump())
}
return nil // Skip the following logic if there is an underlying error.
}

if !resp.IsSuccessState() {
resp.Err = fmt.Errorf("bad response, raw content:\n%s", resp.Dump())
}

return nil
}).
WrapRoundTripFunc(WithTracer)

return c
}

func (s *service) getQQAvatar(ctx context.Context, hash string, args GetAvatarArgs) (image.Image, error) {
ctx, span := tracer.Start(ctx, "service.AvatarService.getQQAvatar")
defer span.End()
Expand All @@ -22,22 +53,26 @@ func (s *service) getQQAvatar(ctx context.Context, hash string, args GetAvatarAr
return nil, err
}

stream, _, err := s.downloadAvatar(ctx, fmt.Sprintf("https://q.qlogo.cn/headimg_dl?dst_uin=%d&spec=640&img_type=jpg", qqid))
resp, err := s.qqAvatarClient.R().
SetContext(ctx).
SetQueryParam("dst_uin", strconv.FormatInt(qqid, 10)).
Get("headimg_dl")
if err != nil {
otelzap.L().Ctx(ctx).Error("download qq avatar failed", zap.Error(err))
return nil, err
} else if resp.IsErrorState() {
otelzap.L().Ctx(ctx).Error("download qq avatar failed", zap.Error(err))
return nil, fmt.Errorf("download qq avatar failed: %v", resp.ErrorResult())
}

img, err := jpeg.Decode(stream)
defer resp.Body.Close()

img, err := jpeg.Decode(resp.Body)
if err != nil {
otelzap.L().Ctx(ctx).Error("decode qq avatar failed", zap.Error(err))
return nil, err
}

if err := stream.Close(); err != nil {
otelzap.L().Ctx(ctx).Warn("close stream failed", zap.Error(err))
}

img = resize.Resize(uint(args.Size), uint(args.Size), img, resize.Lanczos3)

return img, nil
Expand Down
42 changes: 42 additions & 0 deletions services/avatar/req_tracing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package avatar

import (
"github.com/imroc/req/v3"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)

func WithTracer(rt req.RoundTripper) req.RoundTripFunc {
return func(req *req.Request) (resp *req.Response, err error) {
ctx := req.Context()

ctx, span := tracer.Start(ctx, "req.httpRequest", trace.WithAttributes(
attribute.String("http.url", req.URL.String()),
attribute.String("http.method", req.Method),
attribute.String("http.req.header", req.HeaderToString()),
))
defer span.End()

if len(req.Body) > 0 {
span.SetAttributes(
attribute.String("http.req.body", string(req.Body)),
)
}

resp, err = rt.RoundTrip(req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
}

if resp.Response != nil {
span.SetAttributes(
attribute.Int("http.status_code", resp.StatusCode),
attribute.String("http.resp.header", resp.HeaderToString()),
)
}

return
}
}
43 changes: 13 additions & 30 deletions services/avatar/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ package avatar

import (
"context"
"errors"
"fmt"
"github.com/samber/lo"
"image/png"
"io"
"net/http"
"time"

"github.com/cloudwego/hertz/pkg/common/bytebufferpool"
Expand Down Expand Up @@ -36,34 +33,18 @@ type Service interface {
}

type service struct {
fx.In
fx.In `ignore-unexported:"true"`
MD5QQMappingRepo dal.MD5QQMappingRepo
}

func NewService(s service) Service {
return &s
qqAvatarClient *req.Client
gravatarClient *req.Client
}

func (s *service) downloadAvatar(ctx context.Context, url string) (io.ReadCloser, *req.Response, error) {
ctx, span := tracer.Start(ctx, "service.AvatarService.downloadAvatar")
defer span.End()

resp, err := req.NewRequest().SetContext(ctx).Get(url)
if err != nil {
otelzap.L().Ctx(ctx).Error("download avatar failed", zap.Error(err))
return nil, resp, err
}

if resp.StatusCode != 200 || resp.IsErrorState() {
otelzap.L().Ctx(ctx).Error("download avatar failed")
return nil, resp, errors.New(http.StatusText(resp.StatusCode))
}

if resp.IsSuccessState() {
return resp.Body, resp, nil
}
func NewService(s service) Service {
s.qqAvatarClient = initQQClient()
s.gravatarClient = initGravatarClient()

return nil, resp, fmt.Errorf("download avatar failed, status code: %d", resp.StatusCode)
return &s
}

func (s *service) GetAvatar(ctx context.Context, hash string, args GetAvatarArgs) ([]byte, time.Time, error) {
Expand All @@ -76,13 +57,15 @@ func (s *service) GetAvatar(ctx context.Context, hash string, args GetAvatarArgs
img, err := s.getQQAvatar(ctx, hash, args)
if err != nil {
res, err := s.getGravatar(ctx, hash, args)
if errors.Is(err, errors.New("404 Not Found")) {
return nil, time.Time{}, nil
} else if err != nil {
if err != nil {
otelzap.L().Ctx(ctx).Warn("get gravatar failed", zap.Error(err))
return nil, time.Time{}, err
}

if lo.IsEmpty(res) {
return nil, time.Time{}, nil
}

img = res.Avatar
lastModified = res.LastModified
}
Expand Down

0 comments on commit 83d40cd

Please sign in to comment.