Skip to content

Commit

Permalink
生成临时头像ID链接
Browse files Browse the repository at this point in the history
解决的问题:之前是根据评论ID生成对应的链接,问题是,不同的评论可能是
同一个人,所以导致同一个人也有不同的头像链接。会导致网络请求与流量的增加。

方案:后台根据相同的邮箱生成相同的ID。前端通过此ID来获取头像。

实现:能简单的哈希生成算法,对相同的邮箱在服务重启后生成相同的ID。
  • Loading branch information
movsb committed Apr 13, 2024
1 parent f9cf19a commit cb6987f
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 79 deletions.
7 changes: 3 additions & 4 deletions gateway/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (g *Gateway) runHTTPService(ctx context.Context, mux *http.ServeMux, mux2 *
handle("GET", `/v3/api`, serveProtoDocsFile(`index.html`))
handle("GET", `/v3/api/swagger`, serveProtoDocsFile(`taoblog.swagger.json`))

handle(`GET`, `/v3/comments/{id}/avatar`, g.GetAvatar)
handle(`GET`, `/v3/avatar/{id}`, g.GetAvatar)

handle(`GET`, `/v3/posts/{post_id}/files`, g.ListFiles)
handle(`GET`, `/v3/posts/{post_id}/files/{file=**}`, g.GetFile)
Expand All @@ -103,13 +103,12 @@ func redirectToGrafana(w http.ResponseWriter, req *http.Request, params map[stri
}

func (g *Gateway) GetAvatar(w http.ResponseWriter, req *http.Request, params map[string]string) {
commentID, err := strconv.Atoi(params[`id`])
ephemeral, err := strconv.Atoi(params[`id`])
if err != nil {
panic(err)
}

in := &protocols.GetAvatarRequest{
CommentID: int64(commentID),
Ephemeral: ephemeral,
IfModifiedSince: req.Header.Get("If-Modified-Since"),
IfNoneMatch: req.Header.Get("If-None-Match"),
SetStatus: func(statusCode int) {
Expand Down
127 changes: 69 additions & 58 deletions protocols/comment.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions protocols/comment.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ message Comment {
// 前端用户是否可以编辑此评论?
// 仅在 list/create 接口中返回。
bool can_edit = 17;
// 头像内部临时ID
int32 avatar = 18;
}

message GetCommentRequest {
Expand Down
2 changes: 1 addition & 1 deletion protocols/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package protocols
import "io"

type GetAvatarRequest struct {
CommentID int64
Ephemeral int
IfModifiedSince string
IfNoneMatch string
SetStatus func(statusCode int)
Expand Down
70 changes: 65 additions & 5 deletions service/avatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,78 @@ package service

import (
"fmt"
"hash/fnv"
"io"
"net/http"
"strings"
"sync"

"github.com/movsb/taoblog/protocols"
"github.com/movsb/taoblog/service/models"
"github.com/movsb/taoblog/service/modules/avatar"
)

type AvatarCache struct {
id2email map[int]string
email2id map[string]int
lock sync.RWMutex
}

func NewAvatarCache() *AvatarCache {
return &AvatarCache{
id2email: make(map[int]string),
email2id: make(map[string]int),
}
}

// 简单的“一致性”哈希生成算法。
func (c *AvatarCache) id(email string) int {
hash := fnv.New32()
hash.Write([]byte(email))
sum := hash.Sum32() & 0x7fffffff
for {
e, ok := c.id2email[int(sum)]
if ok && e != email {
sum++
continue
}
break
}
return int(sum)
}

func (c *AvatarCache) ID(email string) int {
c.lock.Lock()
defer c.lock.Unlock()

email = strings.ToLower(email)

if id, ok := c.email2id[email]; ok {
return id
}

next := c.id(email)

c.email2id[email] = int(next)
c.id2email[next] = email

return next
}

func (c *AvatarCache) Email(id int) string {
c.lock.RLock()
defer c.lock.RUnlock()

return c.id2email[id]
}

// GetAvatar ...
func (s *Service) GetAvatar(in *protocols.GetAvatarRequest) {
email := s.avatarCache.Email(in.Ephemeral)
if email == "" {
in.SetStatus(http.StatusNotFound)
return
}

p := avatar.Params{
Headers: make(http.Header),
}
Expand All @@ -23,10 +85,8 @@ func (s *Service) GetAvatar(in *protocols.GetAvatarRequest) {
p.Headers.Add("If-None-Match", in.IfNoneMatch)
}

var comment models.Comment
s.tdb.Select(`email`).Where(`id = ?`, in.CommentID).MustFind(&comment)

resp, err := avatar.Get(comment.Email, &p)
// TODO 并没有限制获取未公开发表文章的评论。
resp, err := avatar.Get(email, &p)
if err != nil {
in.SetStatus(500)
fmt.Fprint(in.W, err)
Expand Down
14 changes: 9 additions & 5 deletions service/comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@ func (s *Service) GetComment2(name int64) *models.Comment {
return &comment
}

func (s *Service) avatar(email string) int {
return s.avatarCache.ID(email)
}

// GetComment ...
// TODO perm check
// TODO remove email & user
func (s *Service) GetComment(ctx context.Context, req *protocols.GetCommentRequest) (*protocols.Comment, error) {
user := s.auth.AuthGRPC(ctx)
return s.GetComment2(req.Id).ToProtocols(s.isAdminEmail, user, s.geoLocation, ""), nil
return s.GetComment2(req.Id).ToProtocols(s.isAdminEmail, user, s.geoLocation, "", s.avatar), nil
}

// UpdateComment ...
Expand Down Expand Up @@ -91,7 +95,7 @@ func (s *Service) UpdateComment(ctx context.Context, req *protocols.UpdateCommen
s.tdb.Where(`id=?`, req.Comment.Id).MustFind(&comment)
}

return comment.ToProtocols(s.isAdminEmail, user, s.geoLocation, ""), nil
return comment.ToProtocols(s.isAdminEmail, user, s.geoLocation, "", s.avatar), nil
}

// DeleteComment ...
Expand Down Expand Up @@ -131,7 +135,7 @@ func (s *Service) ListComments(ctx context.Context, in *protocols.ListCommentsRe
stmt.InnerJoin("posts", "comments.post_id = posts.id AND posts.status = 'public'")
}
stmt.MustFind(&parents)
parentProtocolComments = parents.ToProtocols(s.isAdminEmail, user, s.geoLocation, userIP)
parentProtocolComments = parents.ToProtocols(s.isAdminEmail, user, s.geoLocation, userIP, s.avatar)
}

needChildren := in.Mode == protocols.ListCommentsMode_ListCommentsModeTree && len(parentProtocolComments) > 0
Expand All @@ -150,7 +154,7 @@ func (s *Service) ListComments(ctx context.Context, in *protocols.ListCommentsRe
stmt.InnerJoin("posts", "comments.post_id = posts.id AND posts.status = 'public'")
}
stmt.MustFind(&children)
childrenProtocolComments = children.ToProtocols(s.isAdminEmail, user, s.geoLocation, userIP)
childrenProtocolComments = children.ToProtocols(s.isAdminEmail, user, s.geoLocation, userIP, s.avatar)
}
{
childrenMap := make(map[int64][]*protocols.Comment, len(parentProtocolComments))
Expand Down Expand Up @@ -327,7 +331,7 @@ func (s *Service) CreateComment(ctx context.Context, in *protocols.Comment) (*pr

s.doCommentNotification(&comment)

return comment.ToProtocols(s.isAdminEmail, user, s.geoLocation, ip), nil
return comment.ToProtocols(s.isAdminEmail, user, s.geoLocation, ip, s.avatar), nil
}

func (s *Service) updateCommentsCount() {
Expand Down
4 changes: 4 additions & 0 deletions service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ type Service struct {
store storage.Store
cache *memory_cache.MemoryCache

avatarCache *AvatarCache

// 搜索引擎启动需要时间,所以如果网站一运行即搜索,则可能出现引擎不可用
// 的情况,此时此值为空。
searcher atomic.Pointer[search.Engine]
Expand All @@ -60,6 +62,8 @@ func NewService(cfg *config.Config, db *sql.DB, auther *auth.Auth) *Service {
store: localStorage,
cache: memory_cache.NewMemoryCache(time.Minute * 10),
cmtgeo: commentgeo.NewCommentGeo(context.TODO()),

avatarCache: NewAvatarCache(),
}

s.cmtntf = &comment_notify.CommentNotifier{
Expand Down
Loading

0 comments on commit cb6987f

Please sign in to comment.