Files
cpdaily_xiaozhushou/backed/internal/handler/user_announcement.go
T
becomer 2e2c161770 feat: init cpdaily-xiaozhushou with blue-green deploy
- backed/: Go Fiber backend with cpdaily integration
- frondend/: Vue 3 + Vite frontend
- Dockerfile: multi-stage build (Node → Go → Alpine)
- deploy/docker-compose.yml: production compose file
- .woodpecker/build.yml: CI/CD with blue-green zero-downtime deploy
- /health endpoint for deployment health checks
- Port 8884/8885 blue-green swap via 1Panel OpenResty
2026-07-03 15:37:55 +08:00

212 lines
5.6 KiB
Go

package handler
import (
"cpdaily-new/internal/database"
"cpdaily-new/internal/model"
"cpdaily-new/internal/utils"
"fmt"
"time"
"github.com/gofiber/fiber/v2"
)
// getCurrentUser 从上下文中获取当前用户信息(支持普通用户和代理/管理员)
func getCurrentUser(c *fiber.Ctx) (userID uint, userType string, err error) {
if adminID, ok := c.Locals("adminId").(uint); ok {
return adminID, "agent", nil
}
if userID, ok := c.Locals("userId").(uint); ok {
return userID, "user", nil
}
return 0, "", fmt.Errorf("未认证")
}
// UserAnnouncementItem 用户视角的公告项
type UserAnnouncementItem struct {
model.Announcement
IsRead bool `json:"isRead"`
ReadAt *time.Time `json:"readAt,omitempty"`
}
// ListUserAnnouncements 获取当前用户的公告列表(含已读状态)
func ListUserAnnouncements(c *fiber.Ctx) error {
userID, userType, err := getCurrentUser(c)
if err != nil {
return utils.FailAuth(c, "未认证")
}
var announcements []model.Announcement
database.DB.Where("target_type = ?", userType).
Order("created_at DESC").
Find(&announcements)
// 查询该用户的已读记录
readIDs := make(map[uint]time.Time)
var reads []model.AnnouncementRead
database.DB.Where("user_id = ? AND user_type = ?", userID, userType).Find(&reads)
for _, r := range reads {
readIDs[r.AnnouncementID] = r.ReadAt
}
items := make([]UserAnnouncementItem, 0, len(announcements))
for _, a := range announcements {
readAt, isRead := readIDs[a.ID]
item := UserAnnouncementItem{
Announcement: a,
IsRead: isRead,
}
if isRead {
item.ReadAt = &readAt
}
items = append(items, item)
}
return utils.Success(c, items)
}
// GetUnreadCount 获取未读公告数
func GetUnreadCount(c *fiber.Ctx) error {
userID, userType, err := getCurrentUser(c)
if err != nil {
return utils.FailAuth(c, "未认证")
}
var totalCount int64
database.DB.Model(&model.Announcement{}).
Where("target_type = ?", userType).
Count(&totalCount)
var readCount int64
database.DB.Model(&model.AnnouncementRead{}).
Where("user_id = ? AND user_type = ?", userID, userType).
Count(&readCount)
unread := int(totalCount) - int(readCount)
if unread < 0 {
unread = 0
}
return utils.Success(c, fiber.Map{
"total": totalCount,
"read": readCount,
"unread": unread,
})
}
// PendingPopupItem 待弹窗公告项
type PendingPopupItem struct {
ID uint `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
ForcePopup bool `json:"forcePopup"`
LoopPopup bool `json:"loopPopup"`
CreatedAt string `json:"createdAt"`
}
// MyAnnouncementInfo 我的公告信息聚合
type MyAnnouncementInfo struct {
UnreadCount int `json:"unreadCount"`
PopupQueue []PendingPopupItem `json:"popupQueue"` // 需要弹窗的公告(依次弹出)
}
// GetMyAnnouncementInfo 获取当前用户的公告聚合信息(未读数 + 待弹窗队列)
func GetMyAnnouncementInfo(c *fiber.Ctx) error {
userID, userType, err := getCurrentUser(c)
if err != nil {
return utils.FailAuth(c, "未认证")
}
// 1. 总公告数
var totalCount int64
database.DB.Model(&model.Announcement{}).
Where("target_type = ?", userType).
Count(&totalCount)
// 2. 已读数
var readCount int64
database.DB.Model(&model.AnnouncementRead{}).
Where("user_id = ? AND user_type = ?", userID, userType).
Count(&readCount)
unread := int(totalCount) - int(readCount)
if unread < 0 {
unread = 0
}
// 3. 待弹窗队列:forcePopup=true 且 (loopPopup=true 或 未读)
var forceAnnouncements []model.Announcement
database.DB.Where("target_type = ? AND force_popup = 1", userType).
Order("created_at ASC").
Find(&forceAnnouncements)
// 查已读记录
readIDs := make(map[uint]bool)
var reads []model.AnnouncementRead
database.DB.Where("user_id = ? AND user_type = ?", userID, userType).Find(&reads)
for _, r := range reads {
readIDs[r.AnnouncementID] = true
}
popupQueue := make([]PendingPopupItem, 0, len(forceAnnouncements))
for _, a := range forceAnnouncements {
if a.LoopPopup || !readIDs[a.ID] {
popupQueue = append(popupQueue, PendingPopupItem{
ID: a.ID,
Title: a.Title,
Content: a.Content,
ForcePopup: a.ForcePopup,
LoopPopup: a.LoopPopup,
CreatedAt: a.CreatedAt.Format("2006-01-02 15:04:05"),
})
}
}
return utils.Success(c, MyAnnouncementInfo{
UnreadCount: unread,
PopupQueue: popupQueue,
})
}
// MarkAnnouncementRead 标记公告为已读
func MarkAnnouncementRead(c *fiber.Ctx) error {
userID, userType, err := getCurrentUser(c)
if err != nil {
return utils.FailAuth(c, "未认证")
}
announcementID := c.Params("id")
// 验证公告存在
var announcement model.Announcement
if err := database.DB.First(&announcement, announcementID).Error; err != nil {
return utils.Fail(c, 404, "公告不存在")
}
// 验证公告目标类型匹配
if announcement.TargetType != userType {
return utils.Fail(c, 403, "无权操作此公告")
}
// 检查是否已读
var existing model.AnnouncementRead
result := database.DB.Where("announcement_id = ? AND user_id = ? AND user_type = ?",
announcementID, userID, userType).First(&existing)
if result.Error == nil {
// 已读过了
return utils.Success(c, fiber.Map{"alreadyRead": true})
}
// 创建已读记录
read := model.AnnouncementRead{
AnnouncementID: announcement.ID,
UserID: userID,
UserType: userType,
ReadAt: time.Now(),
}
if err := database.DB.Create(&read).Error; err != nil {
return utils.FailServer(c, "标记失败")
}
return utils.Success(c, fiber.Map{"alreadyRead": false})
}