Refactor backend APIs to RESTful route patterns.

Align group and conversation handlers/services with path-based endpoints, and unify response/service error handling for related modules.

Made-with: Cursor
This commit is contained in:
2026-03-10 20:52:50 +08:00
parent 86ef150fec
commit 21293644b8
10 changed files with 217 additions and 189 deletions

View File

@@ -357,6 +357,18 @@ func ConvertPostsToResponse(posts []*model.Post, isLikedMap, isFavoritedMap map[
return result
}
// BuildPostResponse 构建单个帖子响应(包含交互状态)
// 这是一个语义化的辅助函数,便于 Handler 层调用
func BuildPostResponse(post *model.Post, isLiked, isFavorited bool) *PostResponse {
return ConvertPostToResponse(post, isLiked, isFavorited)
}
// BuildPostsWithInteractionResponse 批量构建帖子响应(包含交互状态)
// 这是一个语义化的辅助函数,便于 Handler 层调用
func BuildPostsWithInteractionResponse(posts []*model.Post, isLikedMap, isFavoritedMap map[string]bool) []*PostResponse {
return ConvertPostsToResponse(posts, isLikedMap, isFavoritedMap)
}
// ==================== Comment 转换 ====================
// ConvertCommentToResponse 将Comment转换为CommentResponse

View File

@@ -500,6 +500,7 @@ func (h *GroupHandler) HandleGetUserGroups(c *gin.Context) {
// HandleGetMyMemberInfo 获取我在群组中的成员信息
// GET /api/v1/groups/get_my_info?group_id=xxx
// GET /api/v1/groups/:id/me
func (h *GroupHandler) HandleGetMyMemberInfo(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -507,7 +508,7 @@ func (h *GroupHandler) HandleGetMyMemberInfo(c *gin.Context) {
return
}
groupID := c.Query("group_id")
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
@@ -840,6 +841,7 @@ func (h *GroupHandler) HandleCreateAnnouncement(c *gin.Context) {
// HandleGetAnnouncements 获取群公告列表
// GET /api/v1/groups/get_announcements?group_id=xxx
// GET /api/v1/groups/:id/announcements
func (h *GroupHandler) HandleGetAnnouncements(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -847,7 +849,7 @@ func (h *GroupHandler) HandleGetAnnouncements(c *gin.Context) {
return
}
groupID := c.Query("group_id")
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
@@ -1724,6 +1726,7 @@ func (h *GroupHandler) HandleRespondInvite(c *gin.Context) {
// HandleGetGroupInfo 获取群信息
// GET /api/v1/groups/get?group_id=xxx
// GET /api/v1/groups/:id
func (h *GroupHandler) HandleGetGroupInfo(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1731,7 +1734,7 @@ func (h *GroupHandler) HandleGetGroupInfo(c *gin.Context) {
return
}
groupID := c.Query("group_id")
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return
@@ -1759,6 +1762,7 @@ func (h *GroupHandler) HandleGetGroupInfo(c *gin.Context) {
// HandleGetGroupMemberList 获取群成员列表
// GET /api/v1/groups/get_members?group_id=xxx
// GET /api/v1/groups/:id/members
func (h *GroupHandler) HandleGetGroupMemberList(c *gin.Context) {
userID := parseUserID(c)
if userID == "" {
@@ -1766,7 +1770,7 @@ func (h *GroupHandler) HandleGetGroupMemberList(c *gin.Context) {
return
}
groupID := c.Query("group_id")
groupID := parseGroupID(c)
if groupID == "" {
response.BadRequest(c, "group_id is required")
return

View File

@@ -11,8 +11,8 @@ import (
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model"
"carrot_bbs/internal/pkg/sse"
"carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/pkg/sse"
"carrot_bbs/internal/service"
)
@@ -548,7 +548,7 @@ func (h *MessageHandler) HandleDeleteConversationForSelf(c *gin.Context) {
return
}
conversationID := c.Param("id")
conversationID := getIDParam(c, "id")
if conversationID == "" {
response.BadRequest(c, "conversation id is required")
return
@@ -730,6 +730,11 @@ func (h *MessageHandler) getMyConversationParticipant(conversationID string, use
return nil, nil
}
// getIDParam 从路径参数获取 ID
func getIDParam(c *gin.Context, paramName string) string {
return c.Param(paramName)
}
// ==================== RESTful Action 端点 ====================
// HandleCreateConversation 创建会话
@@ -776,6 +781,7 @@ func (h *MessageHandler) HandleCreateConversation(c *gin.Context) {
// HandleGetConversation 获取会话详情
// GET /api/v1/conversations/get?conversation_id=xxx
// GET /api/v1/conversations/:id
func (h *MessageHandler) HandleGetConversation(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
@@ -783,7 +789,7 @@ func (h *MessageHandler) HandleGetConversation(c *gin.Context) {
return
}
conversationID := c.Query("conversation_id")
conversationID := getIDParam(c, "id")
if conversationID == "" {
response.BadRequest(c, "conversation_id is required")
return
@@ -820,6 +826,7 @@ func (h *MessageHandler) HandleGetConversation(c *gin.Context) {
// HandleGetMessages 获取会话消息
// GET /api/v1/conversations/get_messages?conversation_id=xxx
// GET /api/v1/conversations/:id/messages
func (h *MessageHandler) HandleGetMessages(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
@@ -827,7 +834,7 @@ func (h *MessageHandler) HandleGetMessages(c *gin.Context) {
return
}
conversationID := c.Query("conversation_id")
conversationID := getIDParam(c, "id")
if conversationID == "" {
response.BadRequest(c, "conversation_id is required")
return

View File

@@ -80,11 +80,8 @@ func (h *PostHandler) GetByID(c *gin.Context) {
// 注意:不再自动增加浏览量,浏览量通过 RecordView 端点单独记录
var isLiked, isFavorited bool
if currentUserID != "" {
isLiked = h.postService.IsLiked(c.Request.Context(), id, currentUserID)
isFavorited = h.postService.IsFavorited(c.Request.Context(), id, currentUserID)
}
// 获取交互状态
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, currentUserID)
// 如果有当前用户,检查与帖子作者的相互关注状态
var authorWithFollowStatus *dto.UserResponse
@@ -196,19 +193,15 @@ func (h *PostHandler) List(c *gin.Context) {
return
}
isLikedMap := make(map[string]bool)
isFavoritedMap := make(map[string]bool)
if currentUserID != "" {
for _, post := range posts {
isLiked := h.postService.IsLiked(c.Request.Context(), post.ID, currentUserID)
isFavorited := h.postService.IsFavorited(c.Request.Context(), post.ID, currentUserID)
isLikedMap[post.ID] = isLiked
isFavoritedMap[post.ID] = isFavorited
}
// 批量获取交互状态
postIDs := make([]string, len(posts))
for i, post := range posts {
postIDs[i] = post.ID
}
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构
postResponses := dto.ConvertPostsToResponse(posts, isLikedMap, isFavoritedMap)
postResponses := dto.BuildPostsWithInteractionResponse(posts, isLikedMap, isFavoritedMap)
response.Paginated(c, postResponses, total, page, pageSize)
}
@@ -265,14 +258,11 @@ func (h *PostHandler) Update(c *gin.Context) {
return
}
// 获取交互状态
currentUserID := c.GetString("user_id")
var isLiked, isFavorited bool
if currentUserID != "" {
isLiked = h.postService.IsLiked(c.Request.Context(), post.ID, currentUserID)
isFavorited = h.postService.IsFavorited(c.Request.Context(), post.ID, currentUserID)
}
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), post.ID, currentUserID)
response.Success(c, dto.ConvertPostToResponse(post, isLiked, isFavorited))
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited))
}
// Delete 删除帖子
@@ -328,10 +318,10 @@ func (h *PostHandler) Like(c *gin.Context) {
return
}
isLiked := h.postService.IsLiked(c.Request.Context(), id, userID)
isFavorited := h.postService.IsFavorited(c.Request.Context(), id, userID)
// 获取交互状态
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
response.Success(c, dto.ConvertPostToResponse(post, isLiked, isFavorited))
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited))
}
// Unlike 取消点赞
@@ -357,10 +347,10 @@ func (h *PostHandler) Unlike(c *gin.Context) {
return
}
isLiked := h.postService.IsLiked(c.Request.Context(), id, userID)
isFavorited := h.postService.IsFavorited(c.Request.Context(), id, userID)
// 获取交互状态
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
response.Success(c, dto.ConvertPostToResponse(post, isLiked, isFavorited))
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited))
}
// Favorite 收藏帖子
@@ -386,10 +376,10 @@ func (h *PostHandler) Favorite(c *gin.Context) {
return
}
isLiked := h.postService.IsLiked(c.Request.Context(), id, userID)
isFavorited := h.postService.IsFavorited(c.Request.Context(), id, userID)
// 获取交互状态
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
response.Success(c, dto.ConvertPostToResponse(post, isLiked, isFavorited))
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited))
}
// Unfavorite 取消收藏
@@ -415,10 +405,10 @@ func (h *PostHandler) Unfavorite(c *gin.Context) {
return
}
isLiked := h.postService.IsLiked(c.Request.Context(), id, userID)
isFavorited := h.postService.IsFavorited(c.Request.Context(), id, userID)
// 获取交互状态
isLiked, isFavorited := h.postService.GetPostInteractionStatusSingle(c.Request.Context(), id, userID)
response.Success(c, dto.ConvertPostToResponse(post, isLiked, isFavorited))
response.Success(c, dto.BuildPostResponse(post, isLiked, isFavorited))
}
// GetUserPosts 获取用户帖子列表
@@ -435,18 +425,15 @@ func (h *PostHandler) GetUserPosts(c *gin.Context) {
return
}
// 获取当前用户ID用于判断点赞和收藏状态
isLikedMap := make(map[string]bool)
isFavoritedMap := make(map[string]bool)
if currentUserID != "" {
for _, post := range posts {
isLikedMap[post.ID] = h.postService.IsLiked(c.Request.Context(), post.ID, currentUserID)
isFavoritedMap[post.ID] = h.postService.IsFavorited(c.Request.Context(), post.ID, currentUserID)
}
// 批量获取交互状态
postIDs := make([]string, len(posts))
for i, post := range posts {
postIDs[i] = post.ID
}
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构
postResponses := dto.ConvertPostsToResponse(posts, isLikedMap, isFavoritedMap)
postResponses := dto.BuildPostsWithInteractionResponse(posts, isLikedMap, isFavoritedMap)
response.Paginated(c, postResponses, total, page, pageSize)
}
@@ -463,19 +450,16 @@ func (h *PostHandler) GetFavorites(c *gin.Context) {
return
}
// 获取当前用户ID用于判断点赞和收藏状态
// 批量获取交互状态
currentUserID := c.GetString("user_id")
isLikedMap := make(map[string]bool)
isFavoritedMap := make(map[string]bool)
if currentUserID != "" {
for _, post := range posts {
isLikedMap[post.ID] = h.postService.IsLiked(c.Request.Context(), post.ID, currentUserID)
isFavoritedMap[post.ID] = h.postService.IsFavorited(c.Request.Context(), post.ID, currentUserID)
}
postIDs := make([]string, len(posts))
for i, post := range posts {
postIDs[i] = post.ID
}
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构
postResponses := dto.ConvertPostsToResponse(posts, isLikedMap, isFavoritedMap)
postResponses := dto.BuildPostsWithInteractionResponse(posts, isLikedMap, isFavoritedMap)
response.Paginated(c, postResponses, total, page, pageSize)
}
@@ -492,19 +476,16 @@ func (h *PostHandler) Search(c *gin.Context) {
return
}
// 获取当前用户ID用于判断点赞和收藏状态
// 批量获取交互状态
currentUserID := c.GetString("user_id")
isLikedMap := make(map[string]bool)
isFavoritedMap := make(map[string]bool)
if currentUserID != "" {
for _, post := range posts {
isLikedMap[post.ID] = h.postService.IsLiked(c.Request.Context(), post.ID, currentUserID)
isFavoritedMap[post.ID] = h.postService.IsFavorited(c.Request.Context(), post.ID, currentUserID)
}
postIDs := make([]string, len(posts))
for i, post := range posts {
postIDs[i] = post.ID
}
isLikedMap, isFavoritedMap, _ := h.postService.GetPostInteractionStatus(c.Request.Context(), postIDs, currentUserID)
// 转换为响应结构
postResponses := dto.ConvertPostsToResponse(posts, isLikedMap, isFavoritedMap)
postResponses := dto.BuildPostsWithInteractionResponse(posts, isLikedMap, isFavoritedMap)
response.Paginated(c, postResponses, total, page, pageSize)
}

View File

@@ -42,11 +42,7 @@ func (h *UserHandler) Register(c *gin.Context) {
user, err := h.userService.Register(c.Request.Context(), req.Username, req.Email, req.Password, req.Nickname, req.Phone, req.VerificationCode)
if err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to register")
response.HandleError(c, err, "failed to register")
return
}
@@ -86,11 +82,7 @@ func (h *UserHandler) Login(c *gin.Context) {
user, err := h.userService.Login(c.Request.Context(), account, req.Password)
if err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to login")
response.HandleError(c, err, "failed to login")
return
}
@@ -118,11 +110,7 @@ func (h *UserHandler) SendRegisterCode(c *gin.Context) {
}
if err := h.userService.SendRegisterCode(c.Request.Context(), req.Email); err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to send register verification code")
response.HandleError(c, err, "failed to send register verification code")
return
}
@@ -142,11 +130,7 @@ func (h *UserHandler) SendPasswordResetCode(c *gin.Context) {
}
if err := h.userService.SendPasswordResetCode(c.Request.Context(), req.Email); err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to send reset verification code")
response.HandleError(c, err, "failed to send reset verification code")
return
}
@@ -168,11 +152,7 @@ func (h *UserHandler) ResetPassword(c *gin.Context) {
}
if err := h.userService.ResetPasswordByEmail(c.Request.Context(), req.Email, req.VerificationCode, req.NewPassword); err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to reset password")
response.HandleError(c, err, "failed to reset password")
return
}
@@ -317,11 +297,7 @@ func (h *UserHandler) SendEmailVerifyCode(c *gin.Context) {
}
if err := h.userService.SendCurrentUserEmailVerifyCode(c.Request.Context(), userID, req.Email); err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to send email verify code")
response.HandleError(c, err, "failed to send email verify code")
return
}
@@ -347,11 +323,7 @@ func (h *UserHandler) VerifyEmail(c *gin.Context) {
}
if err := h.userService.VerifyCurrentUserEmail(c.Request.Context(), userID, req.Email, req.VerificationCode); err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to verify email")
response.HandleError(c, err, "failed to verify email")
return
}
@@ -437,11 +409,7 @@ func (h *UserHandler) BlockUser(c *gin.Context) {
err := h.userService.BlockUser(c.Request.Context(), currentUserID, targetUserID)
if err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to block user")
response.HandleError(c, err, "failed to block user")
return
}
@@ -460,11 +428,7 @@ func (h *UserHandler) UnblockUser(c *gin.Context) {
err := h.userService.UnblockUser(c.Request.Context(), currentUserID, targetUserID)
if err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to unblock user")
response.HandleError(c, err, "failed to unblock user")
return
}
@@ -638,11 +602,7 @@ func (h *UserHandler) ChangePassword(c *gin.Context) {
err := h.userService.ChangePassword(c.Request.Context(), currentUserID, req.OldPassword, req.NewPassword, req.VerificationCode)
if err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to change password")
response.HandleError(c, err, "failed to change password")
return
}
@@ -659,11 +619,7 @@ func (h *UserHandler) SendChangePasswordCode(c *gin.Context) {
err := h.userService.SendChangePasswordCode(c.Request.Context(), currentUserID)
if err != nil {
if se, ok := err.(*service.ServiceError); ok {
response.Error(c, se.Code, se.Message)
return
}
response.InternalServerError(c, "failed to send change password code")
response.HandleError(c, err, "failed to send change password code")
return
}

View File

@@ -4,6 +4,8 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"carrot_bbs/internal/service"
)
// Response 统一响应结构
@@ -115,3 +117,46 @@ func Paginated(c *gin.Context, list interface{}, total int64, page, pageSize int
TotalPages: totalPages,
})
}
// HandleServiceError 统一处理 Service 错误
// 如果 err 是 *service.ServiceError返回对应的业务错误码和消息
// 如果 err 是其他错误,返回 false调用方应处理通用错误
// 返回 true 表示错误已处理false 表示需要调用方继续处理
func HandleServiceError(c *gin.Context, err error) bool {
if err == nil {
return false
}
if se, ok := err.(*service.ServiceError); ok {
ErrorWithStatusCode(c, statusCodeFromCode(se.Code), se.Code, se.Message)
return true
}
return false
}
// HandleError 统一处理错误(带默认消息)
// 如果 err 是 *service.ServiceError返回对应的业务错误码和消息
// 如果 err 是其他错误,返回 InternalServerError 并使用 defaultMessage
func HandleError(c *gin.Context, err error, defaultMessage string) {
if err == nil {
return
}
if se, ok := err.(*service.ServiceError); ok {
ErrorWithStatusCode(c, statusCodeFromCode(se.Code), se.Code, se.Message)
return
}
InternalServerError(c, defaultMessage)
}
// statusCodeFromCode 根据业务错误码获取 HTTP 状态码
func statusCodeFromCode(code int) int {
switch {
case code >= 200 && code < 300:
return http.StatusOK
case code >= 400 && code < 500:
return code
case code >= 500:
return code
default:
return http.StatusBadRequest
}
}

View File

@@ -182,30 +182,24 @@ func (r *Router) setupRoutes() {
comments.DELETE("/:id/like", authMiddleware, r.commentHandler.Unlike)
}
// 会话路由(新版 RESTful action 风格)
// 会话路由
conversations := v1.Group("/conversations")
conversations.Use(authMiddleware)
{
// 获取会话列表
conversations.GET("/list", r.messageHandler.HandleGetConversationList)
// 创建会话
conversations.POST("/create", r.messageHandler.HandleCreateConversation)
// 获取会话详情
conversations.GET("/get", r.messageHandler.HandleGetConversation)
// 获取会话消息
conversations.GET("/get_messages", r.messageHandler.HandleGetMessages)
// 发送消息
conversations.POST("/send_message", r.messageHandler.HandleSendMessage)
// 标记已读
conversations.POST("/mark_read", r.messageHandler.HandleMarkRead)
// 会话置顶
conversations.POST("/set_pinned", r.messageHandler.HandleSetConversationPinned)
// 获取未读消息总数
conversations.GET("/unread/count", r.messageHandler.GetUnreadCount)
// 上报输入状态
conversations.POST("/typing", r.messageHandler.HandleTyping)
// 仅自己删除会话
conversations.DELETE("/:id/self", r.messageHandler.HandleDeleteConversationForSelf)
// ================================================================
// 新的 RESTful 风格路由(推荐使用)
// ================================================================
conversations.GET("", r.messageHandler.HandleGetConversationList) // 列表
conversations.POST("", r.messageHandler.HandleCreateConversation) // 创建
conversations.GET("/:id", r.messageHandler.HandleGetConversation) // 详情
conversations.GET("/:id/messages", r.messageHandler.HandleGetMessages) // 消息列表
conversations.POST("/:id/messages", r.messageHandler.HandleSendMessage) // 发送消息
conversations.POST("/:id/read", r.messageHandler.HandleMarkRead) // 标记已读
conversations.PUT("/:id/pinned", r.messageHandler.HandleSetConversationPinned) // 置顶设置
conversations.GET("/unread/count", r.messageHandler.GetUnreadCount) // 未读数
conversations.POST("/:id/typing", r.messageHandler.HandleTyping) // 输入状态
conversations.DELETE("/:id/self", r.messageHandler.HandleDeleteConversationForSelf) // 删除会话(仅自己)
}
realtime := v1.Group("/realtime")
@@ -264,43 +258,47 @@ func (r *Router) setupRoutes() {
}
}
// 群组路由(新版 RESTful action 风格)
// 群组路由
if r.groupHandler != nil {
groups := v1.Group("/groups")
groups.Use(authMiddleware)
{
// 群组管理
groups.POST("/create", r.groupHandler.HandleCreateGroup)
groups.GET("/list", r.groupHandler.HandleGetUserGroups)
groups.GET("/get", r.groupHandler.HandleGetGroupInfo)
groups.GET("/get_my_info", r.groupHandler.HandleGetMyMemberInfo)
groups.POST("/dissolve", r.groupHandler.HandleDissolveGroup)
groups.POST("/transfer", r.groupHandler.HandleTransferOwner)
// ================================================================
// 新的 RESTful 风格路由(推荐使用)
// ================================================================
// 群组基本操作
groups.GET("", r.groupHandler.HandleGetUserGroups) // 列表
groups.POST("", r.groupHandler.HandleCreateGroup) // 创建
groups.GET("/:id", r.groupHandler.HandleGetGroupInfo) // 详情
groups.GET("/:id/me", r.groupHandler.HandleGetMyMemberInfo) // 当前用户成员信息
groups.DELETE("/:id", r.groupHandler.HandleDissolveGroup) // 解散群组
groups.POST("/:id/transfer", r.groupHandler.HandleTransferOwner) // 转让群主
// 成员管理
groups.POST("/invite_members", r.groupHandler.HandleInviteMembers)
groups.POST("/join", r.groupHandler.HandleJoinGroup)
groups.POST("/respond_invite", r.groupHandler.HandleRespondInvite)
groups.POST("/set_group_leave", r.groupHandler.HandleSetGroupLeave)
groups.GET("/get_members", r.groupHandler.HandleGetGroupMemberList)
groups.POST("/set_group_kick", r.groupHandler.HandleSetGroupKick)
groups.POST("/set_group_admin", r.groupHandler.HandleSetGroupAdmin)
groups.POST("/set_nickname", r.groupHandler.HandleSetNickname)
groups.POST("/set_group_ban", r.groupHandler.HandleSetGroupBan)
groups.POST("/:id/invitations", r.groupHandler.HandleInviteMembers) // 邀请成员
groups.POST("/:id/join-requests", r.groupHandler.HandleJoinGroup) // 申请加群
groups.POST("/:id/join-requests/respond", r.groupHandler.HandleRespondInvite) // 响应加群邀请/申请
groups.POST("/:id/leave", r.groupHandler.HandleSetGroupLeave) // 退群
groups.GET("/:id/members", r.groupHandler.HandleGetGroupMemberList) // 成员列表
groups.POST("/:id/members/kick", r.groupHandler.HandleSetGroupKick) // 踢出成员
groups.PUT("/:id/members/:user_id/admin", r.groupHandler.HandleSetGroupAdmin) // 设置管理员
groups.PUT("/:id/members/me/nickname", r.groupHandler.HandleSetNickname) // 设置群昵称
groups.POST("/:id/members/ban", r.groupHandler.HandleSetGroupBan) // 禁言成员
// 群设置
groups.POST("/set_group_whole_ban", r.groupHandler.HandleSetGroupWholeBan)
groups.POST("/set_join_type", r.groupHandler.HandleSetJoinType)
groups.POST("/set_group_name", r.groupHandler.HandleSetGroupName)
groups.POST("/set_group_avatar", r.groupHandler.HandleSetGroupAvatar)
groups.PUT("/:id/ban", r.groupHandler.HandleSetGroupWholeBan) // 全员禁言
groups.PUT("/:id/join-type", r.groupHandler.HandleSetJoinType) // 加群方式
groups.PUT("/:id/name", r.groupHandler.HandleSetGroupName) // 群名称
groups.PUT("/:id/avatar", r.groupHandler.HandleSetGroupAvatar) // 群头像
// 群公告
groups.POST("/create_announcement", r.groupHandler.HandleCreateAnnouncement)
groups.GET("/get_announcements", r.groupHandler.HandleGetAnnouncements)
groups.POST("/delete_announcement", r.groupHandler.HandleDeleteAnnouncement)
groups.POST("/:id/announcements", r.groupHandler.HandleCreateAnnouncement) // 创建公告
groups.GET("/:id/announcements", r.groupHandler.HandleGetAnnouncements) // 公告列表
groups.DELETE("/:id/announcements/:announcement_id", r.groupHandler.HandleDeleteAnnouncement) // 删除公告
// 加群请求处理
groups.POST("/:id/join-requests/handle", r.groupHandler.HandleSetGroupAddRequest) // 处理加群请求
// 加群请求处理(预留)
groups.POST("/set_group_add_request", r.groupHandler.HandleSetGroupAddRequest)
}
}

View File

@@ -25,23 +25,23 @@ const (
// 群组服务错误定义
var (
ErrGroupNotFound = errors.New("群组不存在")
ErrNotGroupMember = errors.New("不是群成员")
ErrNotGroupAdmin = errors.New("不是群管理员")
ErrNotGroupOwner = errors.New("不是群主")
ErrGroupFull = errors.New("群已满")
ErrAlreadyMember = errors.New("已经是群成员")
ErrCannotRemoveOwner = errors.New("不能移除群主")
ErrCannotMuteOwner = errors.New("不能禁言群主")
ErrMuted = errors.New("你已被禁言")
ErrMuteAllEnabled = errors.New("全员禁言中")
ErrCannotJoin = errors.New("该群不允许加入")
ErrJoinRequestPending = errors.New("加群申请已提交")
ErrGroupRequestNotFound = errors.New("加群请求不存在")
ErrGroupRequestHandled = errors.New("该加群请求已处理")
ErrNotRequestTarget = errors.New("不是邀请目标用户")
ErrNoEligibleInvitee = errors.New("没有可邀请的用户")
ErrNotMutualFollow = errors.New("仅支持邀请互相关注用户")
ErrGroupNotFound = &ServiceError{Code: 404, Message: "群组不存在"}
ErrNotGroupMember = &ServiceError{Code: 403, Message: "不是群成员"}
ErrNotGroupAdmin = &ServiceError{Code: 403, Message: "不是群管理员"}
ErrNotGroupOwner = &ServiceError{Code: 403, Message: "不是群主"}
ErrGroupFull = &ServiceError{Code: 400, Message: "群已满"}
ErrAlreadyMember = &ServiceError{Code: 400, Message: "已经是群成员"}
ErrCannotRemoveOwner = &ServiceError{Code: 400, Message: "不能移除群主"}
ErrCannotMuteOwner = &ServiceError{Code: 400, Message: "不能禁言群主"}
ErrMuted = &ServiceError{Code: 403, Message: "你已被禁言"}
ErrMuteAllEnabled = &ServiceError{Code: 403, Message: "全员禁言中"}
ErrCannotJoin = &ServiceError{Code: 400, Message: "该群不允许加入"}
ErrJoinRequestPending = &ServiceError{Code: 400, Message: "加群申请已提交"}
ErrGroupRequestNotFound = &ServiceError{Code: 404, Message: "加群请求不存在"}
ErrGroupRequestHandled = &ServiceError{Code: 400, Message: "该加群请求已处理"}
ErrNotRequestTarget = &ServiceError{Code: 400, Message: "不是邀请目标用户"}
ErrNoEligibleInvitee = &ServiceError{Code: 400, Message: "没有可邀请的用户"}
ErrNotMutualFollow = &ServiceError{Code: 400, Message: "仅支持邀请互相关注用户"}
)
// GroupService 群组服务接口

View File

@@ -411,6 +411,31 @@ func (s *PostService) IsFavorited(ctx context.Context, postID, userID string) bo
return s.postRepo.IsFavorited(postID, userID)
}
// GetPostInteractionStatus 批量获取帖子的交互状态(点赞、收藏)
func (s *PostService) GetPostInteractionStatus(ctx context.Context, postIDs []string, userID string) (map[string]bool, map[string]bool, error) {
isLikedMap := make(map[string]bool)
isFavoritedMap := make(map[string]bool)
if userID == "" || len(postIDs) == 0 {
return isLikedMap, isFavoritedMap, nil
}
for _, postID := range postIDs {
isLikedMap[postID] = s.postRepo.IsLiked(postID, userID)
isFavoritedMap[postID] = s.postRepo.IsFavorited(postID, userID)
}
return isLikedMap, isFavoritedMap, nil
}
// GetPostInteractionStatusSingle 获取单个帖子的交互状态
func (s *PostService) GetPostInteractionStatusSingle(ctx context.Context, postID, userID string) (isLiked, isFavorited bool) {
if userID == "" {
return false, false
}
return s.postRepo.IsLiked(postID, userID), s.postRepo.IsFavorited(postID, userID)
}
// IncrementViews 增加帖子观看量并同步到Gorse
func (s *PostService) IncrementViews(ctx context.Context, postID, userID string) error {
if err := s.postRepo.IncrementViews(postID); err != nil {

View File

@@ -9,8 +9,8 @@ import (
)
var (
ErrStickerAlreadyExists = errors.New("sticker already exists")
ErrInvalidStickerURL = errors.New("invalid sticker url")
ErrStickerAlreadyExists = &ServiceError{Code: 400, Message: "sticker already exists"}
ErrInvalidStickerURL = &ServiceError{Code: 400, Message: "invalid sticker url"}
)
// StickerService 自定义表情服务接口