package controller import ( "errors" "html" "strconv" "strings" "time" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) const ( announcementStatusDraft = "draft" announcementStatusActive = "active" announcementStatusArchived = "archived" announcementNotifyModeSilent = "silent" announcementNotifyModePopup = "popup" ) type createAnnouncementRequest struct { Title string `json:"title"` Content string `json:"content"` NotifyMode string `json:"notify_mode"` Status string `json:"status"` JsonRules string `json:"json_rules"` StartsAt string `json:"starts_at"` EndsAt string `json:"ends_at"` } type updateAnnouncementRequest struct { Title *string `json:"title"` Content *string `json:"content"` Status *string `json:"status"` NotifyMode *string `json:"notify_mode"` JsonRules *string `json:"json_rules"` StartsAt *string `json:"starts_at"` EndsAt *string `json:"ends_at"` } func GetAnnouncements(c *gin.Context) { pageInfo := common.GetPageQuery(c) status := strings.TrimSpace(c.Query("status")) if status != "" && !isValidAnnouncementStatus(status) { common.ApiErrorMsg(c, "status 必须是 draft、active 或 archived") return } announcements, total, err := model.GetAnnouncements(pageInfo.GetPage(), pageInfo.GetPageSize(), status) if err != nil { common.ApiError(c, err) return } pageInfo.SetTotal(int(total)) pageInfo.SetItems(announcements) common.ApiSuccess(c, pageInfo) } func CreateAnnouncement(c *gin.Context) { var req createAnnouncementRequest if err := c.ShouldBindJSON(&req); err != nil { common.ApiError(c, err) return } title := strings.TrimSpace(req.Title) if title == "" { common.ApiErrorMsg(c, "title 不能为空") return } if strings.TrimSpace(req.Content) == "" { common.ApiErrorMsg(c, "content 不能为空") return } status := strings.TrimSpace(req.Status) if status == "" { status = announcementStatusDraft } if !isValidAnnouncementStatus(status) { common.ApiErrorMsg(c, "status 必须是 draft、active 或 archived") return } notifyMode := strings.TrimSpace(req.NotifyMode) if notifyMode == "" { notifyMode = announcementNotifyModeSilent } if !isValidAnnouncementNotifyMode(notifyMode) { common.ApiErrorMsg(c, "notify_mode 必须是 silent 或 popup") return } startsAt, err := parseAnnouncementTime(req.StartsAt) if err != nil { common.ApiErrorMsg(c, "starts_at 必须是 RFC3339 格式") return } endsAt, err := parseAnnouncementTime(req.EndsAt) if err != nil { common.ApiErrorMsg(c, "ends_at 必须是 RFC3339 格式") return } announcement := &model.Announcement{ Title: title, Content: req.Content, ContentHtml: renderAnnouncementContentHTML(req.Content), Status: status, NotifyMode: notifyMode, JsonRules: req.JsonRules, StartsAt: startsAt, EndsAt: endsAt, CreatedBy: c.GetInt("id"), } if err := announcement.Insert(); err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, announcement) } func GetAnnouncement(c *gin.Context) { announcement, ok := getAnnouncementFromParam(c) if !ok { return } common.ApiSuccess(c, announcement) } func UpdateAnnouncement(c *gin.Context) { announcement, ok := getAnnouncementFromParam(c) if !ok { return } var req updateAnnouncementRequest if err := c.ShouldBindJSON(&req); err != nil { common.ApiError(c, err) return } if req.Title != nil { title := strings.TrimSpace(*req.Title) if title == "" { common.ApiErrorMsg(c, "title 不能为空") return } announcement.Title = title } if req.Content != nil { if strings.TrimSpace(*req.Content) == "" { common.ApiErrorMsg(c, "content 不能为空") return } announcement.Content = *req.Content announcement.ContentHtml = renderAnnouncementContentHTML(*req.Content) } if req.Status != nil { status := strings.TrimSpace(*req.Status) if !isValidAnnouncementStatus(status) { common.ApiErrorMsg(c, "status 必须是 draft、active 或 archived") return } announcement.Status = status } if req.NotifyMode != nil { notifyMode := strings.TrimSpace(*req.NotifyMode) if !isValidAnnouncementNotifyMode(notifyMode) { common.ApiErrorMsg(c, "notify_mode 必须是 silent 或 popup") return } announcement.NotifyMode = notifyMode } if req.JsonRules != nil { announcement.JsonRules = *req.JsonRules } if req.StartsAt != nil { startsAt, err := parseAnnouncementTime(*req.StartsAt) if err != nil { common.ApiErrorMsg(c, "starts_at 必须是 RFC3339 格式") return } announcement.StartsAt = startsAt } if req.EndsAt != nil { endsAt, err := parseAnnouncementTime(*req.EndsAt) if err != nil { common.ApiErrorMsg(c, "ends_at 必须是 RFC3339 格式") return } announcement.EndsAt = endsAt } if err := announcement.Update(); err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, announcement) } func DeleteAnnouncement(c *gin.Context) { announcement, ok := getAnnouncementFromParam(c) if !ok { return } announcement.Status = announcementStatusArchived if err := announcement.Update(); err != nil { common.ApiError(c, err) return } common.ApiSuccess(c, nil) } func getAnnouncementFromParam(c *gin.Context) (*model.Announcement, bool) { id, err := strconv.Atoi(c.Param("id")) if err != nil { common.ApiError(c, err) return nil, false } announcement, err := model.GetAnnouncementByID(id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { common.ApiErrorMsg(c, "公告不存在") return nil, false } common.ApiError(c, err) return nil, false } return announcement, true } func parseAnnouncementTime(value string) (*time.Time, error) { value = strings.TrimSpace(value) if value == "" { return nil, nil } parsed, err := time.Parse(time.RFC3339, value) if err != nil { return nil, err } return &parsed, nil } func renderAnnouncementContentHTML(content string) string { escaped := html.EscapeString(content) return strings.ReplaceAll(escaped, "\n", "
\n") } func isValidAnnouncementStatus(status string) bool { switch status { case announcementStatusDraft, announcementStatusActive, announcementStatusArchived: return true default: return false } } func isValidAnnouncementNotifyMode(notifyMode string) bool { switch notifyMode { case announcementNotifyModeSilent, announcementNotifyModePopup: return true default: return false } }