From 8be05cd9c2d7728eadc38e22b1fc569188e33e48 Mon Sep 17 00:00:00 2001 From: Snowykami Date: Sat, 13 Sep 2025 16:55:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E8=AF=84=E8=AE=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=EF=BC=8C=E6=B7=BB=E5=8A=A0=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/dto/comment.go | 74 +++++----- internal/service/comment.go | 286 ++++++++++++++++++------------------ pkg/constant/constant.go | 93 ++++++------ pkg/utils/ip_info.go | 18 ++- 4 files changed, 244 insertions(+), 227 deletions(-) diff --git a/internal/dto/comment.go b/internal/dto/comment.go index 3c9ea6f..99be6ce 100644 --- a/internal/dto/comment.go +++ b/internal/dto/comment.go @@ -1,50 +1,50 @@ package dto type CommentDto struct { - ID uint `json:"id"` - TargetID uint `json:"target_id"` - TargetType string `json:"target_type"` // 目标类型,如 "post", "page" - Content string `json:"content"` - ReplyID uint `json:"reply_id"` // 回复的评论ID - Depth int `json:"depth"` // 评论的层级深度 - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - User UserDto `json:"user"` // 评论的 - ReplyCount uint64 `json:"reply_count"` // 回复数量 - LikeCount uint64 `json:"like_count"` // 点赞数量 - IsLiked bool `json:"is_liked"` // 当前用户是否点赞 - IsPrivate bool `json:"is_private"` - Location string `json:"location"` // 用户位置,基于IP - OS string `json:"os"` // 用户操作系统,基于User-Agent - Browser string `json:"browser"` // 用户浏览器,基于User-Agent - ShowClientInfo bool `json:"show_client_info"` + ID uint `json:"id"` + TargetID uint `json:"target_id"` + TargetType string `json:"target_type"` // 目标类型,如 "post", "page" + Content string `json:"content"` + ReplyID uint `json:"reply_id"` // 回复的评论ID + Depth int `json:"depth"` // 评论的层级深度 + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + User UserDto `json:"user"` // 评论的 + ReplyCount uint64 `json:"reply_count"` // 回复数量 + LikeCount uint64 `json:"like_count"` // 点赞数量 + IsLiked bool `json:"is_liked"` // 当前用户是否点赞 + IsPrivate bool `json:"is_private"` + Location string `json:"location"` // 用户位置,基于IP + OS string `json:"os"` // 用户操作系统,基于User-Agent + Browser string `json:"browser"` // 用户浏览器,基于User-Agent + ShowClientInfo bool `json:"show_client_info"` } type CreateCommentReq struct { - TargetID uint `json:"target_id" binding:"required"` // 目标ID - TargetType string `json:"target_type" binding:"required"` // 目标类型,如 "post", "page" - Content string `json:"content" binding:"required"` // 评论内容 - ReplyID uint `json:"reply_id"` // 回复的评论ID - IsPrivate bool `json:"is_private"` // 是否私密评论,默认false - RemoteAddr string `json:"remote_addr"` // 远程地址 - UserAgent string `json:"user_agent"` // 用户代理 - ShowClientInfo bool `json:"show_client_info"` // 是否显示客户端信息 + TargetID uint `json:"target_id" binding:"required"` // 目标ID + TargetType string `json:"target_type" binding:"required"` // 目标类型,如 "post", "page" + Content string `json:"content" binding:"required"` // 评论内容 + ReplyID uint `json:"reply_id"` // 回复的评论ID + IsPrivate bool `json:"is_private"` // 是否私密评论,默认false + RemoteAddr string `json:"remote_addr"` // 远程地址 + UserAgent string `json:"user_agent"` // 用户代理 + ShowClientInfo bool `json:"show_client_info"` // 是否显示客户端信息 } type UpdateCommentReq struct { - CommentID uint `json:"comment_id" binding:"required"` // 评论ID - Content string `json:"content" binding:"required"` // 评论内容 - IsPrivate bool `json:"is_private"` // 是否私密 - ShowClientInfo bool `json:"show_client_info"` // 是否显示客户端信息 + CommentID uint `json:"comment_id" binding:"required"` // 评论ID + Content string `json:"content" binding:"required"` // 评论内容 + IsPrivate bool `json:"is_private"` // 是否私密 + ShowClientInfo bool `json:"show_client_info"` // 是否显示客户端信息 } type GetCommentListReq struct { - TargetID uint `json:"target_id" binding:"required"` - TargetType string `json:"target_type" binding:"required"` - CommentID uint `json:"comment_id"` // 获取某条评论的所有子评论 - OrderBy string `json:"order_by"` // 排序方式 - Page uint64 `json:"page"` // 页码 - Size uint64 `json:"size"` - Desc bool `json:"desc"` - Depth int `json:"depth"` // 评论的层级深度 + TargetID uint `json:"target_id" binding:"required"` + TargetType string `json:"target_type" binding:"required"` + CommentID uint `json:"comment_id"` // 获取某条评论的所有子评论 + OrderBy string `json:"order_by"` // 排序方式 + Page uint64 `json:"page"` // 页码 + Size uint64 `json:"size"` + Desc bool `json:"desc"` + Depth int `json:"depth"` // 评论的层级深度 } diff --git a/internal/service/comment.go b/internal/service/comment.go index 77d69d8..7688184 100644 --- a/internal/service/comment.go +++ b/internal/service/comment.go @@ -1,188 +1,188 @@ package service import ( - "context" - "strconv" + "context" + "strconv" - "github.com/sirupsen/logrus" - "github.com/snowykami/neo-blog/pkg/constant" - "github.com/snowykami/neo-blog/pkg/utils" + "github.com/sirupsen/logrus" + "github.com/snowykami/neo-blog/pkg/constant" + "github.com/snowykami/neo-blog/pkg/utils" - "github.com/snowykami/neo-blog/internal/ctxutils" - "github.com/snowykami/neo-blog/internal/dto" - "github.com/snowykami/neo-blog/internal/model" - "github.com/snowykami/neo-blog/internal/repo" - "github.com/snowykami/neo-blog/pkg/errs" + "github.com/snowykami/neo-blog/internal/ctxutils" + "github.com/snowykami/neo-blog/internal/dto" + "github.com/snowykami/neo-blog/internal/model" + "github.com/snowykami/neo-blog/internal/repo" + "github.com/snowykami/neo-blog/pkg/errs" ) type CommentService struct{} func NewCommentService() *CommentService { - return &CommentService{} + return &CommentService{} } func (cs *CommentService) CreateComment(ctx context.Context, req *dto.CreateCommentReq) (uint, error) { - currentUser, ok := ctxutils.GetCurrentUser(ctx) - if !ok { - return 0, errs.ErrUnauthorized - } + currentUser, ok := ctxutils.GetCurrentUser(ctx) + if !ok { + return 0, errs.ErrUnauthorized + } - if ok, err := cs.checkTargetExists(req.TargetID, req.TargetType); !ok { - if err != nil { - return 0, errs.New(errs.ErrBadRequest.Code, "target not found", err) - } - return 0, errs.ErrBadRequest - } + if ok, err := cs.checkTargetExists(req.TargetID, req.TargetType); !ok { + if err != nil { + return 0, errs.New(errs.ErrBadRequest.Code, "target not found", err) + } + return 0, errs.ErrBadRequest + } - comment := &model.Comment{ - Content: req.Content, - ReplyID: req.ReplyID, - TargetID: req.TargetID, - TargetType: req.TargetType, - UserID: currentUser.ID, - IsPrivate: req.IsPrivate, - RemoteAddr: req.RemoteAddr, - UserAgent: req.UserAgent, - ShowClientInfo: req.ShowClientInfo, - } + comment := &model.Comment{ + Content: req.Content, + ReplyID: req.ReplyID, + TargetID: req.TargetID, + TargetType: req.TargetType, + UserID: currentUser.ID, + IsPrivate: req.IsPrivate, + RemoteAddr: req.RemoteAddr, + UserAgent: req.UserAgent, + ShowClientInfo: req.ShowClientInfo, + } - commentID, err := repo.Comment.CreateComment(comment) + commentID, err := repo.Comment.CreateComment(comment) - if err != nil { - return 0, err - } + if err != nil { + return 0, err + } - return commentID, nil + return commentID, nil } func (cs *CommentService) UpdateComment(ctx context.Context, req *dto.UpdateCommentReq) error { - currentUser, ok := ctxutils.GetCurrentUser(ctx) - if !ok { - return errs.ErrUnauthorized - } - logrus.Infof("UpdateComment: currentUser ID %d, req.CommentID %d", currentUser.ID, req.CommentID) + currentUser, ok := ctxutils.GetCurrentUser(ctx) + if !ok { + return errs.ErrUnauthorized + } + logrus.Infof("UpdateComment: currentUser ID %d, req.CommentID %d", currentUser.ID, req.CommentID) - comment, err := repo.Comment.GetComment(strconv.Itoa(int(req.CommentID))) - if err != nil { - return err - } + comment, err := repo.Comment.GetComment(strconv.Itoa(int(req.CommentID))) + if err != nil { + return err + } - if currentUser.ID != comment.UserID { - return errs.ErrForbidden - } + if currentUser.ID != comment.UserID { + return errs.ErrForbidden + } - comment.Content = req.Content - comment.IsPrivate = req.IsPrivate - comment.ShowClientInfo = req.ShowClientInfo - err = repo.Comment.UpdateComment(comment) - if err != nil { - return err - } - return nil + comment.Content = req.Content + comment.IsPrivate = req.IsPrivate + comment.ShowClientInfo = req.ShowClientInfo + err = repo.Comment.UpdateComment(comment) + if err != nil { + return err + } + return nil } func (cs *CommentService) DeleteComment(ctx context.Context, commentID string) error { - currentUser, ok := ctxutils.GetCurrentUser(ctx) - if !ok { - return errs.ErrUnauthorized - } - if commentID == "" { - return errs.ErrBadRequest - } + currentUser, ok := ctxutils.GetCurrentUser(ctx) + if !ok { + return errs.ErrUnauthorized + } + if commentID == "" { + return errs.ErrBadRequest + } - comment, err := repo.Comment.GetComment(commentID) - if err != nil { - return errs.New(errs.ErrNotFound.Code, "comment not found", err) - } + comment, err := repo.Comment.GetComment(commentID) + if err != nil { + return errs.New(errs.ErrNotFound.Code, "comment not found", err) + } - if comment.UserID != currentUser.ID { - return errs.ErrForbidden - } + if comment.UserID != currentUser.ID { + return errs.ErrForbidden + } - if err := repo.Comment.DeleteComment(commentID); err != nil { - return err - } - return nil + if err := repo.Comment.DeleteComment(commentID); err != nil { + return err + } + return nil } func (cs *CommentService) GetComment(ctx context.Context, commentID string) (*dto.CommentDto, error) { - comment, err := repo.Comment.GetComment(commentID) + comment, err := repo.Comment.GetComment(commentID) - if err != nil { - return nil, errs.New(errs.ErrNotFound.Code, "comment not found", err) - } + if err != nil { + return nil, errs.New(errs.ErrNotFound.Code, "comment not found", err) + } - currentUserID := uint(0) - if currentUser, ok := ctxutils.GetCurrentUser(ctx); ok { - currentUserID = currentUser.ID - } - if comment.IsPrivate && currentUserID != comment.UserID { - return nil, errs.ErrForbidden - } - commentDto := cs.toGetCommentDto(comment, currentUserID) - return &commentDto, err + currentUserID := uint(0) + if currentUser, ok := ctxutils.GetCurrentUser(ctx); ok { + currentUserID = currentUser.ID + } + if comment.IsPrivate && currentUserID != comment.UserID { + return nil, errs.ErrForbidden + } + commentDto := cs.toGetCommentDto(comment, currentUserID) + return &commentDto, err } func (cs *CommentService) GetCommentList(ctx context.Context, req *dto.GetCommentListReq) ([]dto.CommentDto, error) { - currentUserID := uint(0) - if currentUser, ok := ctxutils.GetCurrentUser(ctx); ok { - currentUserID = currentUser.ID - } - comments, err := repo.Comment.ListComments(currentUserID, req.TargetID, req.CommentID, req.TargetType, req.Page, req.Size, req.OrderBy, req.Desc, req.Depth) - if err != nil { - return nil, errs.New(errs.ErrInternalServer.Code, "failed to list comments", err) - } - commentDtos := make([]dto.CommentDto, 0) - for _, comment := range comments { - //replyCount, _ := repo.Comment.CountReplyComments(currentUserID, comment.ID) - commentDto := cs.toGetCommentDto(&comment, currentUserID) - commentDtos = append(commentDtos, commentDto) - } - return commentDtos, nil + currentUserID := uint(0) + if currentUser, ok := ctxutils.GetCurrentUser(ctx); ok { + currentUserID = currentUser.ID + } + comments, err := repo.Comment.ListComments(currentUserID, req.TargetID, req.CommentID, req.TargetType, req.Page, req.Size, req.OrderBy, req.Desc, req.Depth) + if err != nil { + return nil, errs.New(errs.ErrInternalServer.Code, "failed to list comments", err) + } + commentDtos := make([]dto.CommentDto, 0) + for _, comment := range comments { + //replyCount, _ := repo.Comment.CountReplyComments(currentUserID, comment.ID) + commentDto := cs.toGetCommentDto(&comment, currentUserID) + commentDtos = append(commentDtos, commentDto) + } + return commentDtos, nil } func (cs *CommentService) toGetCommentDto(comment *model.Comment, currentUserID uint) dto.CommentDto { - isLiked := false - if currentUserID != 0 { - isLiked, _ = repo.Like.IsLiked(currentUserID, comment.ID, constant.TargetTypeComment) - } - ua := utils.ParseUA(comment.UserAgent) - if !comment.ShowClientInfo { - comment.Location = "" - ua.OS = "" - ua.OSVersion = "" - ua.Browser = "" - ua.BrowserVer = "" - } + isLiked := false + if currentUserID != 0 { + isLiked, _ = repo.Like.IsLiked(currentUserID, comment.ID, constant.TargetTypeComment) + } + ua := utils.ParseUA(comment.UserAgent) + if !comment.ShowClientInfo { + comment.Location = "" + ua.OS = "" + ua.OSVersion = "" + ua.Browser = "" + ua.BrowserVer = "" + } - return dto.CommentDto{ - ID: comment.ID, - Content: comment.Content, - TargetID: comment.TargetID, - TargetType: comment.TargetType, - ReplyID: comment.ReplyID, - CreatedAt: comment.CreatedAt.String(), - UpdatedAt: comment.UpdatedAt.String(), - Depth: comment.Depth, - User: comment.User.ToDto(), - ReplyCount: comment.CommentCount, - LikeCount: comment.LikeCount, - IsLiked: isLiked, - IsPrivate: comment.IsPrivate, - OS: ua.OS + " " + ua.OSVersion, - Browser: ua.Browser + " " + ua.BrowserVer, - Location: comment.Location, - ShowClientInfo: comment.ShowClientInfo, - } + return dto.CommentDto{ + ID: comment.ID, + Content: comment.Content, + TargetID: comment.TargetID, + TargetType: comment.TargetType, + ReplyID: comment.ReplyID, + CreatedAt: comment.CreatedAt.String(), + UpdatedAt: comment.UpdatedAt.String(), + Depth: comment.Depth, + User: comment.User.ToDto(), + ReplyCount: comment.CommentCount, + LikeCount: comment.LikeCount, + IsLiked: isLiked, + IsPrivate: comment.IsPrivate, + OS: ua.OS + " " + ua.OSVersion, + Browser: ua.Browser + " " + ua.BrowserVer, + Location: comment.Location, + ShowClientInfo: comment.ShowClientInfo, + } } func (cs *CommentService) checkTargetExists(targetID uint, targetType string) (bool, error) { - switch targetType { - case constant.TargetTypePost: - if _, err := repo.Post.GetPostByID(strconv.Itoa(int(targetID))); err != nil { - return false, errs.New(errs.ErrNotFound.Code, "post not found", err) - } - default: - return false, errs.New(errs.ErrBadRequest.Code, "invalid target type", nil) - } - return true, nil + switch targetType { + case constant.TargetTypePost: + if _, err := repo.Post.GetPostByID(strconv.Itoa(int(targetID))); err != nil { + return false, errs.New(errs.ErrNotFound.Code, "post not found", err) + } + default: + return false, errs.New(errs.ErrBadRequest.Code, "invalid target type", nil) + } + return true, nil } diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index 31c3563..da77616 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -1,53 +1,54 @@ package constant const ( - CaptchaTypeDisable = "disable" // 禁用验证码 - CaptchaTypeHCaptcha = "hcaptcha" // HCaptcha验证码 - CaptchaTypeTurnstile = "turnstile" // Turnstile验证码 - CaptchaTypeReCaptcha = "recaptcha" // ReCaptcha验证码 - ContextKeyUserID = "user_id" // 上下文键:用户ID - ModeDev = "dev" - ModeProd = "prod" - RoleUser = "user" - RoleAdmin = "admin" - EnvKeyBaseUrl = "BASE_URL" // 环境变量:基础URL - EnvKeyCaptchaProvider = "CAPTCHA_PROVIDER" // captcha提供者 - EnvKeyCaptchaSecreteKey = "CAPTCHA_SECRET_KEY" // captcha站点密钥 - EnvKeyCaptchaUrl = "CAPTCHA_URL" // 某些自托管的captcha的url - EnvKeyCaptchaSiteKey = "CAPTCHA_SITE_KEY" // captcha密钥key - EnvKeyLogLevel = "LOG_LEVEL" // 环境变量:日志级别 - EnvKeyMode = "MODE" // 环境变量:运行模式 - EnvKeyJwtSecrete = "JWT_SECRET" // 环境变量:JWT密钥 - EnvKeyPasswordSalt = "PASSWORD_SALT" // 环境变量:密码盐 - EnvKeyTokenDuration = "TOKEN_DURATION" // 环境变量:令牌有效期 - EnvKeyMaxReplyDepth = "MAX_REPLY_DEPTH" // 环境变量:最大回复深度 - EnvKeyTokenDurationDefault = 300 // Token有效时长 - EnvKeyRefreshTokenDurationDefault = 604800 // refresh token有效时长 - EnvKeyRefreshTokenDuration = "REFRESH_TOKEN_DURATION" // 环境变量:刷新令牌有效期 - EnvKeyRefreshTokenDurationWithRemember = "REFRESH_TOKEN_DURATION_WITH_REMEMBER" // 环境变量:记住我刷新令牌有效期 - KVKeyEmailVerificationCode = "email_verification_code:" // KV存储:邮箱验证码 - KVKeyOidcState = "oidc_state:" // KV存储:OIDC状态 - ApiSuffix = "/api/v1" // API版本前缀 - OidcUri = "/user/oidc/login" // OIDC登录URI - OidcProviderTypeMisskey = "misskey" // OIDC提供者类型:Misskey - OidcProviderTypeOauth2 = "oauth2" // OIDC提供者类型:GitHub - DefaultBaseUrl = "http://localhost:3000" // 默认BaseUrl - TargetTypePost = "post" - TargetTypeComment = "comment" - OrderByCreatedAt = "created_at" // 按创建时间排序 - OrderByUpdatedAt = "updated_at" // 按更新时间排序 - OrderByLikeCount = "like_count" // 按点赞数排序 - OrderByCommentCount = "comment_count" // 按评论数排序 - OrderByViewCount = "view_count" // 按浏览量排序 - OrderByHeat = "heat" - MaxReplyDepthDefault = 3 // 默认最大回复深度 - HeatFactorViewWeight = 1 // 热度因子:浏览量权重 - HeatFactorLikeWeight = 5 // 热度因子:点赞权重 - HeatFactorCommentWeight = 10 // 热度因子:评论权重 - PageLimitDefault = 20 // 默认分页大小 + CaptchaTypeDisable = "disable" // 禁用验证码 + CaptchaTypeHCaptcha = "hcaptcha" // HCaptcha验证码 + CaptchaTypeTurnstile = "turnstile" // Turnstile验证码 + CaptchaTypeReCaptcha = "recaptcha" // ReCaptcha验证码 + ContextKeyUserID = "user_id" // 上下文键:用户ID + ModeDev = "dev" + ModeProd = "prod" + RoleUser = "user" + RoleAdmin = "admin" + EnvKeyBaseUrl = "BASE_URL" // 环境变量:基础URL + EnvKeyCaptchaProvider = "CAPTCHA_PROVIDER" // captcha提供者 + EnvKeyCaptchaSecreteKey = "CAPTCHA_SECRET_KEY" // captcha站点密钥 + EnvKeyCaptchaUrl = "CAPTCHA_URL" // 某些自托管的captcha的url + EnvKeyCaptchaSiteKey = "CAPTCHA_SITE_KEY" // captcha密钥key + EnvKeyLocationFormat = "LOCATION_FORMAT" // 环境变量:时区格式 + EnvKeyLogLevel = "LOG_LEVEL" // 环境变量:日志级别 + EnvKeyMode = "MODE" // 环境变量:运行模式 + EnvKeyJwtSecrete = "JWT_SECRET" // 环境变量:JWT密钥 + EnvKeyPasswordSalt = "PASSWORD_SALT" // 环境变量:密码盐 + EnvKeyTokenDuration = "TOKEN_DURATION" // 环境变量:令牌有效期 + EnvKeyMaxReplyDepth = "MAX_REPLY_DEPTH" // 环境变量:最大回复深度 + EnvKeyTokenDurationDefault = 300 // Token有效时长 + EnvKeyRefreshTokenDurationDefault = 604800 // refresh token有效时长 + EnvKeyRefreshTokenDuration = "REFRESH_TOKEN_DURATION" // 环境变量:刷新令牌有效期 + EnvKeyRefreshTokenDurationWithRemember = "REFRESH_TOKEN_DURATION_WITH_REMEMBER" // 环境变量:记住我刷新令牌有效期 + KVKeyEmailVerificationCode = "email_verification_code:" // KV存储:邮箱验证码 + KVKeyOidcState = "oidc_state:" // KV存储:OIDC状态 + ApiSuffix = "/api/v1" // API版本前缀 + OidcUri = "/user/oidc/login" // OIDC登录URI + OidcProviderTypeMisskey = "misskey" // OIDC提供者类型:Misskey + OidcProviderTypeOauth2 = "oauth2" // OIDC提供者类型:GitHub + DefaultBaseUrl = "http://localhost:3000" // 默认BaseUrl + TargetTypePost = "post" + TargetTypeComment = "comment" + OrderByCreatedAt = "created_at" // 按创建时间排序 + OrderByUpdatedAt = "updated_at" // 按更新时间排序 + OrderByLikeCount = "like_count" // 按点赞数排序 + OrderByCommentCount = "comment_count" // 按评论数排序 + OrderByViewCount = "view_count" // 按浏览量排序 + OrderByHeat = "heat" + MaxReplyDepthDefault = 3 // 默认最大回复深度 + HeatFactorViewWeight = 1 // 热度因子:浏览量权重 + HeatFactorLikeWeight = 5 // 热度因子:点赞权重 + HeatFactorCommentWeight = 10 // 热度因子:评论权重 + PageLimitDefault = 20 // 默认分页大小 ) var ( - OrderByEnumPost = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByLikeCount, OrderByCommentCount, OrderByViewCount, OrderByHeat} // 帖子可用的排序方式 - OrderByEnumComment = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByCommentCount} // 评论可用的排序方式 + OrderByEnumPost = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByLikeCount, OrderByCommentCount, OrderByViewCount, OrderByHeat} // 帖子可用的排序方式 + OrderByEnumComment = []string{OrderByCreatedAt, OrderByUpdatedAt, OrderByCommentCount} // 评论可用的排序方式 ) diff --git a/pkg/utils/ip_info.go b/pkg/utils/ip_info.go index 6e3a506..f2fd49b 100644 --- a/pkg/utils/ip_info.go +++ b/pkg/utils/ip_info.go @@ -1,9 +1,12 @@ package utils import ( + "bytes" "fmt" + "text/template" "github.com/sirupsen/logrus" + "github.com/snowykami/neo-blog/pkg/constant" ) type IPData struct { @@ -56,5 +59,18 @@ func GetLocationString(ip string) string { if ipInfo == nil { return "" } - return fmt.Sprintf("%s %s %s %s", ipInfo.Country, ipInfo.Province, ipInfo.City, ipInfo.ISP) + + tpl := Env.Get(constant.EnvKeyLocationFormat, "{{.Country}} {{.Province}} {{.City}} {{.ISP}}") + t, err := template.New("location").Parse(tpl) + if err != nil { + logrus.Error(err) + return "" + } + + var buf bytes.Buffer + if err := t.Execute(&buf, ipInfo); err != nil { + logrus.Error(err) + return "" + } + return buf.String() }