完善角色与消息权限:收紧板管内容处置,彻底删除仅站长,并统一管理删帖/评弹窗。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 14:30:08 +08:00
parent 1f2e4b2a33
commit c0462a3500
37 changed files with 1340 additions and 472 deletions

View File

@@ -21,9 +21,12 @@ func chatErrToStatus(err error) int {
errors.Is(err, service.ErrChatOwnerOnly), errors.Is(err, service.ErrChatPrivateInvite),
errors.Is(err, service.ErrChatRecallDenied), errors.Is(err, service.ErrChatDefaultLeave),
errors.Is(err, service.ErrChatDefaultDissolve), errors.Is(err, service.ErrChatDMDissolve),
errors.Is(err, service.ErrChatHallPinFixed):
errors.Is(err, service.ErrChatHallPinFixed), errors.Is(err, service.ErrChatSiteOwnerOnly),
errors.Is(err, service.ErrChatMuteDenied), errors.Is(err, service.ErrChatCannotMuteOwner),
errors.Is(err, service.ErrChatAdminAccess):
return http.StatusForbidden
case errors.Is(err, service.ErrChatDMSelf), errors.Is(err, service.ErrChatUserGone):
case errors.Is(err, service.ErrChatDMSelf), errors.Is(err, service.ErrChatUserGone),
errors.Is(err, service.ErrChatInvalidRole):
return http.StatusBadRequest
default:
return http.StatusBadRequest
@@ -307,7 +310,7 @@ func (h *Handlers) KickChatMember(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "用户 ID 无效"})
return
}
if err := h.Chat.Kick(claims.ID, roomID, uint(targetID)); err != nil {
if err := h.Chat.Kick(claims.ID, roomID, uint(targetID), h.chatOversee(claims.ID)); err != nil {
c.JSON(chatErrToStatus(err), gin.H{"error": err.Error()})
return
}
@@ -319,6 +322,63 @@ func (h *Handlers) KickChatMember(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// SetChatMemberMute 禁言/解禁成员
func (h *Handlers) SetChatMemberMute(c *gin.Context) {
claims := middleware.CurrentUser(c)
roomID, ok := chatRoomID(c)
if !ok {
return
}
targetID, err := strconv.ParseUint(c.Param("uid"), 10, 64)
if err != nil || targetID == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "用户 ID 无效"})
return
}
var body struct {
Muted bool `json:"muted"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
return
}
if err := h.Chat.SetMemberMute(claims.ID, roomID, uint(targetID), body.Muted, h.chatOversee(claims.ID)); err != nil {
c.JSON(chatErrToStatus(err), gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true, "muted": body.Muted})
}
// SetChatMemberRole 站长任命/撤销群管理员
func (h *Handlers) SetChatMemberRole(c *gin.Context) {
claims := middleware.CurrentUser(c)
roomID, ok := chatRoomID(c)
if !ok {
return
}
targetID, err := strconv.ParseUint(c.Param("uid"), 10, 64)
if err != nil || targetID == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "用户 ID 无效"})
return
}
var body struct {
Role string `json:"role"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
return
}
actor := h.loadActor(claims.ID)
role := model.RoleUser
if actor != nil {
role = actor.Role
}
if err := h.Chat.SetMemberRole(role, roomID, uint(targetID), body.Role); err != nil {
c.JSON(chatErrToStatus(err), gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true, "role": body.Role})
}
// ListChatMessages 历史消息
func (h *Handlers) ListChatMessages(c *gin.Context) {
claims := middleware.CurrentUser(c)
@@ -474,11 +534,21 @@ func (h *Handlers) ChatUnreadSummary(c *gin.Context) {
// AdminChatMessages 管理端消息监管队列;room_type=group|direct
func (h *Handlers) AdminChatMessages(c *gin.Context) {
claims := middleware.CurrentUser(c)
actor := h.loadActor(claims.ID)
if actor == nil || !h.Chat.CanAccessAdminMessages(actor) {
c.JSON(http.StatusForbidden, gin.H{"error": "无权管理消息"})
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
q := c.Query("q")
roomType := c.Query("room_type")
msgs, total, err := h.Chat.AdminListMessages(page, q, roomType)
msgs, total, err := h.Chat.AdminListMessages(actor, page, q, roomType)
if err != nil {
if errors.Is(err, service.ErrChatAdminAccess) {
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
return
}
if err.Error() == "房间类型无效" {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
@@ -486,12 +556,24 @@ func (h *Handlers) AdminChatMessages(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "加载聊天消息失败"})
return
}
c.JSON(http.StatusOK, gin.H{"messages": msgs, "total": total, "page": page})
c.JSON(http.StatusOK, gin.H{
"messages": msgs,
"total": total,
"page": page,
"can_oversee": service.CanOverseeChat(actor.Role),
"can_recall": true,
"site_messages": actor.HasSiteMessagePerm(),
})
}
// AdminRecallChatMessage 管理端撤回群聊消息(staff;监管者可撤任意)
// AdminRecallChatMessage 管理端撤回群聊消息
func (h *Handlers) AdminRecallChatMessage(c *gin.Context) {
claims := middleware.CurrentUser(c)
actor := h.loadActor(claims.ID)
if actor == nil || !h.Chat.CanAccessAdminMessages(actor) {
c.JSON(http.StatusForbidden, gin.H{"error": "无权管理消息"})
return
}
roomID, err := strconv.ParseUint(c.Param("roomId"), 10, 64)
if err != nil || roomID == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "房间 ID 无效"})
@@ -502,8 +584,7 @@ func (h *Handlers) AdminRecallChatMessage(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "消息 ID 无效"})
return
}
oversee := h.chatOversee(claims.ID)
// 非监管 staff:仅允许撤回自己发送的(与前台一致)
oversee := service.CanOverseeChat(actor.Role)
msg, newly, err := h.Chat.RecallMessage(claims.ID, uint(roomID), uint(mid), oversee)
if err != nil {
c.JSON(chatErrToStatus(err), gin.H{"error": err.Error()})