From 06539dc086393db57a4b182bb1902ea11d9daa5f Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Sat, 10 Jan 2026 03:52:35 +0800 Subject: [PATCH] feat: Add public user information retrieval endpoint - Introduced a new endpoint to fetch public user information without authentication. - Implemented UserToPublicUserInfo function to format user data for the response. - Updated UserService interface and user service implementation to support fetching users by username. - Enhanced user handler to validate input parameters and check user status before responding. --- cmd/server/main.go | 2 ++ internal/handler/helpers.go | 13 ++++++++ internal/handler/routes.go | 4 +++ internal/handler/user_handler.go | 53 ++++++++++++++++++++++++++++++++ internal/service/interfaces.go | 1 + internal/service/user_service.go | 8 +++++ internal/types/common.go | 12 ++++++++ 7 files changed, 93 insertions(+) diff --git a/cmd/server/main.go b/cmd/server/main.go index 2f98dd5..856b29d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -32,6 +32,8 @@ import ( "github.com/gin-gonic/gin" "go.uber.org/zap" + + _ "carrotskin/docs" ) func main() { diff --git a/internal/handler/helpers.go b/internal/handler/helpers.go index ae835d4..8d32149 100644 --- a/internal/handler/helpers.go +++ b/internal/handler/helpers.go @@ -62,6 +62,19 @@ func UserToUserInfo(user *model.User) *types.UserInfo { } } +// UserToPublicUserInfo 将 User 模型转换为 PublicUserInfo 响应 +func UserToPublicUserInfo(user *model.User) *types.PublicUserInfo { + return &types.PublicUserInfo{ + ID: user.ID, + Username: user.Username, + Avatar: user.Avatar, + Points: user.Points, + Role: user.Role, + Status: user.Status, + CreatedAt: user.CreatedAt, + } +} + // ProfileToProfileInfo 将 Profile 模型转换为 ProfileInfo 响应 func ProfileToProfileInfo(profile *model.Profile) *types.ProfileInfo { return &types.ProfileInfo{ diff --git a/internal/handler/routes.go b/internal/handler/routes.go index bd84550..6448362 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -93,6 +93,10 @@ func registerAuthRoutes(v1 *gin.RouterGroup, h *AuthHandler) { // registerUserRoutes 注册用户路由 func registerUserRoutes(v1 *gin.RouterGroup, h *UserHandler, jwtService *auth.JWTService) { + // 公开用户信息路由(无需认证) + v1.GET("/users/public", h.GetPublicInfo) + + // 需要认证的用户路由 userGroup := v1.Group("/user") userGroup.Use(middleware.AuthMiddleware(jwtService)) { diff --git a/internal/handler/user_handler.go b/internal/handler/user_handler.go index a7c6ffb..14bd9bd 100644 --- a/internal/handler/user_handler.go +++ b/internal/handler/user_handler.go @@ -2,6 +2,7 @@ package handler import ( "carrotskin/internal/container" + "carrotskin/internal/model" "carrotskin/internal/service" "carrotskin/internal/types" @@ -315,3 +316,55 @@ func (h *UserHandler) ResetYggdrasilPassword(c *gin.Context) { h.logger.Info("Yggdrasil密码重置成功", zap.Int64("userId", userID)) RespondSuccess(c, gin.H{"password": newPassword}) } + +// GetPublicInfo 获取用户公开信息 +// @Summary 获取用户公开信息 +// @Description 根据用户名或用户ID获取用户的公开信息(不包含敏感信息如邮箱) +// @Tags user +// @Accept json +// @Produce json +// @Param username query string false "用户名" +// @Param id query int false "用户ID" +// @Success 200 {object} model.Response{data=types.PublicUserInfo} "获取成功" +// @Failure 400 {object} model.ErrorResponse "参数错误" +// @Failure 404 {object} model.ErrorResponse "用户不存在" +// @Router /api/v1/users/public [get] +func (h *UserHandler) GetPublicInfo(c *gin.Context) { + username := c.Query("username") + idStr := c.Query("id") + + // 至少需要提供一个参数 + if username == "" && idStr == "" { + RespondBadRequest(c, "必须提供用户名或用户ID", nil) + return + } + + var user *model.User + var err error + + // 优先使用用户名查询 + if username != "" { + user, err = h.container.UserService.GetByUsername(c.Request.Context(), username) + } else { + // 使用用户ID查询 + id := parseIntWithDefault(idStr, 0) + if id == 0 { + RespondBadRequest(c, "无效的用户ID", nil) + return + } + user, err = h.container.UserService.GetByID(c.Request.Context(), int64(id)) + } + + if err != nil || user == nil { + RespondNotFound(c, "用户不存在") + return + } + + // 检查用户状态 + if user.Status != 1 { + RespondNotFound(c, "用户不可用") + return + } + + RespondSuccess(c, UserToPublicUserInfo(user)) +} diff --git a/internal/service/interfaces.go b/internal/service/interfaces.go index 0f24e08..429d573 100644 --- a/internal/service/interfaces.go +++ b/internal/service/interfaces.go @@ -19,6 +19,7 @@ type UserService interface { // 用户查询 GetByID(ctx context.Context, id int64) (*model.User, error) GetByEmail(ctx context.Context, email string) (*model.User, error) + GetByUsername(ctx context.Context, username string) (*model.User, error) // 用户更新 UpdateInfo(ctx context.Context, user *model.User) error diff --git a/internal/service/user_service.go b/internal/service/user_service.go index 476bdbd..d73954f 100644 --- a/internal/service/user_service.go +++ b/internal/service/user_service.go @@ -199,6 +199,14 @@ func (s *userService) GetByEmail(ctx context.Context, email string) (*model.User }, s.cache.Policy.UserEmailTTL) } +func (s *userService) GetByUsername(ctx context.Context, username string) (*model.User, error) { + // 使用 Cached 装饰器自动处理缓存 + cacheKey := s.cacheKeys.UserByUsername(username) + return database.Cached(ctx, s.cache, cacheKey, func() (*model.User, error) { + return s.userRepo.FindByUsername(ctx, username) + }, s.cache.Policy.UserTTL) +} + func (s *userService) UpdateInfo(ctx context.Context, user *model.User) error { err := s.userRepo.Update(ctx, user) if err != nil { diff --git a/internal/types/common.go b/internal/types/common.go index dc0fc50..dd8de31 100644 --- a/internal/types/common.go +++ b/internal/types/common.go @@ -110,6 +110,18 @@ type UserInfo struct { UpdatedAt time.Time `json:"updated_at" example:"2025-10-01T10:00:00Z"` } +// PublicUserInfo 用户公开信息 +// @Description 用户公开信息(不包含敏感信息如邮箱) +type PublicUserInfo struct { + ID int64 `json:"id" example:"1"` + Username string `json:"username" example:"testuser"` + Avatar string `json:"avatar" example:"https://example.com/avatar.png"` + Points int `json:"points" example:"100"` + Role string `json:"role" example:"user"` + Status int16 `json:"status" example:"1"` + CreatedAt time.Time `json:"created_at" example:"2025-10-01T10:00:00Z"` +} + // TextureType 材质类型 type TextureType string