package controllers import ( "strings" "time" "server/models" "server/pkg/jwtutil" beego "github.com/beego/beego/v2/server/web" "github.com/beego/beego/v2/client/orm" ) // ============================================================= // 平台官网 - 站点公告 /platform/website/notice/* // 表:yz_platform_website_notice // 建表 SQL:docs/sql/create_platform_website_notice.sql // // 公告状态:0 草稿 / 1 已发布 / 2 已下线。 // 前台 GET /site/notices 只返回「已发布」且当前时间处于有效期内的公告, // 见 index_website.go 的 noticeVisibleCond。 // ============================================================= const ( noticeStatusDraft int8 = 0 noticeStatusPublished int8 = 1 noticeStatusOffline int8 = 2 ) type PlatformWebsiteNoticeController struct { beego.Controller } type platformWebsiteNoticePayload struct { Title *string `json:"title"` Summary *string `json:"summary"` Content *string `json:"content"` Type *int8 `json:"type"` LinkURL *string `json:"link_url"` IsPopup *int8 `json:"is_popup"` Top *int8 `json:"top"` Sort *int `json:"sort"` Status *int8 `json:"status"` StartTime *string `json:"start_time"` EndTime *string `json:"end_time"` } // parseNoticeTime 解析前端时间字符串,支持 "2006-01-02 15:04:05"、RFC3339、"2006-01-02"; // 空串或无法识别时返回 nil(对应数据库 NULL,表示不限制时间)。 func parseNoticeTime(raw *string) *time.Time { if raw == nil { return nil } v := strings.TrimSpace(*raw) if v == "" { return nil } for _, layout := range []string{"2006-01-02 15:04:05", time.RFC3339, "2006-01-02"} { if t, err := time.ParseInLocation(layout, v, time.Local); err == nil { return &t } } return nil } // noticeVisibleCond 前台可见条件:未删除 + 已发布 + 当前时间落在 [start_time, end_time] 内 // start_time / end_time 为 NULL 表示该侧不限制。 func noticeVisibleCond() *orm.Condition { now := time.Now() return orm.NewCondition(). And("delete_time__isnull", true). And("status", noticeStatusPublished). AndCond(orm.NewCondition().Or("start_time__isnull", true).Or("start_time__lte", now)). AndCond(orm.NewCondition().Or("end_time__isnull", true).Or("end_time__gte", now)) } // noticeFromPayload 依据请求体构造新公告,缺省:类型=通知公告、状态=草稿 func noticeFromPayload(p platformWebsiteNoticePayload) models.PlatformWebsiteNotice { row := models.PlatformWebsiteNotice{ Type: 1, Status: noticeStatusDraft, } if p.Title != nil { row.Title = strings.TrimSpace(*p.Title) } if p.Summary != nil { row.Summary = strings.TrimSpace(*p.Summary) } if p.Content != nil { row.Content = *p.Content } if p.Type != nil { row.Type = *p.Type } if p.LinkURL != nil { row.LinkURL = strings.TrimSpace(*p.LinkURL) } if p.IsPopup != nil { row.IsPopup = *p.IsPopup } if p.Top != nil { row.Top = *p.Top } if p.Sort != nil { row.Sort = *p.Sort } if p.Status != nil { row.Status = *p.Status } row.StartTime = parseNoticeTime(p.StartTime) row.EndTime = parseNoticeTime(p.EndTime) return row } // noticeUpdateMap 把请求体转成待更新字段,只包含显式传入的键(未传的字段保持原值) func noticeUpdateMap(p platformWebsiteNoticePayload) map[string]interface{} { up := map[string]interface{}{} if p.Title != nil { up["title"] = strings.TrimSpace(*p.Title) } if p.Summary != nil { up["summary"] = strings.TrimSpace(*p.Summary) } if p.Content != nil { up["content"] = *p.Content } if p.Type != nil { up["type"] = *p.Type } if p.LinkURL != nil { up["link_url"] = strings.TrimSpace(*p.LinkURL) } if p.IsPopup != nil { up["is_popup"] = *p.IsPopup } if p.Top != nil { up["top"] = *p.Top } if p.Sort != nil { up["sort"] = *p.Sort } if p.Status != nil { up["status"] = *p.Status } if p.StartTime != nil { up["start_time"] = parseNoticeTime(p.StartTime) } if p.EndTime != nil { up["end_time"] = parseNoticeTime(p.EndTime) } return up } // List GET /platform/website/notice/list // query: keyword(标题/摘要)、type、status、is_popup、page、pageSize func (c *PlatformWebsiteNoticeController) List() { if _, ok := websiteAuth(&c.Controller); !ok { return } page, pageSize := websitePaging(&c.Controller) cond := orm.NewCondition().And("delete_time__isnull", true) if kw := strings.TrimSpace(c.GetString("keyword")); kw != "" { cond = cond.AndCond(orm.NewCondition(). Or("title__icontains", kw). Or("summary__icontains", kw)) } if tp := websiteIntParam(&c.Controller, "type"); tp >= 0 { cond = cond.And("type", tp) } if st := websiteStatusParam(&c.Controller); st >= 0 { cond = cond.And("status", st) } if pop := websiteIntParam(&c.Controller, "is_popup"); pop >= 0 { cond = cond.And("is_popup", pop) } qs := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)).SetCond(cond) total, err := qs.Count() if err != nil { jsonErr(&c.Controller, 500, 500, "获取失败: "+err.Error()) return } // 列表不返回富文本正文,减小响应体 var rows []models.PlatformWebsiteNotice if _, err = qs.OrderBy("-top", "sort", "-id"). Limit(pageSize, (page-1)*pageSize). All(&rows, "ID", "Title", "Summary", "Type", "LinkURL", "IsPopup", "Top", "Sort", "Status", "StartTime", "EndTime", "Views", "PublishTime", "CreateTime", "UpdateTime"); err != nil { jsonErr(&c.Controller, 500, 500, "获取失败: "+err.Error()) return } websiteOK(&c.Controller, map[string]interface{}{"list": rows, "total": total}) } // Get GET /platform/website/notice/:id — 含正文,供编辑回显 func (c *PlatformWebsiteNoticeController) Get() { if _, ok := websiteAuth(&c.Controller); !ok { return } id, ok := websiteID(&c.Controller) if !ok { return } var row models.PlatformWebsiteNotice if err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)). Filter("id", id). Filter("delete_time__isnull", true). One(&row); err != nil { jsonErr(&c.Controller, 404, 404, "公告不存在") return } websiteOK(&c.Controller, row) } // Create POST /platform/website/notice func (c *PlatformWebsiteNoticeController) Create() { claims, ok := websiteAuth(&c.Controller) if !ok { return } var p platformWebsiteNoticePayload if !websiteBind(&c.Controller, &p) { return } if p.Title == nil || strings.TrimSpace(*p.Title) == "" { jsonErr(&c.Controller, 400, 400, "公告标题不能为空") return } row := noticeFromPayload(p) // 直接提交为已发布时同步补发布时间与发布人 if row.Status == noticeStatusPublished { now := time.Now() uid := uint64(claims.UserID) row.PublishTime = &now row.PublisherID = &uid } id, err := models.Orm.Insert(&row) if err != nil { jsonErr(&c.Controller, 500, 500, "创建失败: "+err.Error()) return } websiteOK(&c.Controller, map[string]interface{}{"id": id}) } // Update POST /platform/website/notice/:id func (c *PlatformWebsiteNoticeController) Update() { claims, ok := websiteAuth(&c.Controller) if !ok { return } id, ok := websiteID(&c.Controller) if !ok { return } var p platformWebsiteNoticePayload if !websiteBind(&c.Controller, &p) { return } if p.Title != nil && strings.TrimSpace(*p.Title) == "" { jsonErr(&c.Controller, 400, 400, "公告标题不能为空") return } // 先取原记录:既用于 404 判断,也用于判断是否「首次转为已发布」 var row models.PlatformWebsiteNotice if err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)). Filter("id", id). Filter("delete_time__isnull", true). One(&row, "ID", "Status"); err != nil { jsonErr(&c.Controller, 404, 404, "公告不存在") return } up := noticeUpdateMap(p) if len(up) == 0 { jsonErr(&c.Controller, 400, 400, "无更新字段") return } if p.Status != nil && *p.Status == noticeStatusPublished && row.Status != noticeStatusPublished { // 首次发布才写发布时间,避免每次保存覆盖发布时间 now := time.Now() uid := uint64(claims.UserID) up["publish_time"] = now up["publisher_id"] = uid } up["update_time"] = time.Now() if _, err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)). Filter("id", id). Filter("delete_time__isnull", true). Update(up); err != nil { jsonErr(&c.Controller, 500, 500, "更新失败: "+err.Error()) return } websiteOK(&c.Controller, nil) } // noticeToggle 更新单个开关字段(status / top / is_popup) func (c *PlatformWebsiteNoticeController) noticeToggle(claims *jwtutil.Claims, field string, value int8) { id, ok := websiteID(&c.Controller) if !ok { return } up := map[string]interface{}{field: value, "update_time": time.Now()} // 发布时补发布时间与发布人(其余开关无需登录身份,claims 可能为 nil) if field == "status" && value == noticeStatusPublished { up["publish_time"] = time.Now() if claims != nil { up["publisher_id"] = uint64(claims.UserID) } } n, err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)). Filter("id", id). Filter("delete_time__isnull", true). Update(up) if err != nil { jsonErr(&c.Controller, 500, 500, "操作失败: "+err.Error()) return } if n == 0 { jsonErr(&c.Controller, 404, 404, "公告不存在") return } websiteOK(&c.Controller, nil) } // Publish POST /platform/website/notice/:id/publish — 状态置为已发布 func (c *PlatformWebsiteNoticeController) Publish() { claims, ok := websiteAuth(&c.Controller) if !ok { return } c.noticeToggle(claims, "status", noticeStatusPublished) } // UnPublish POST /platform/website/notice/:id/unpublish — 状态置为已下线 func (c *PlatformWebsiteNoticeController) UnPublish() { if _, ok := websiteAuth(&c.Controller); !ok { return } c.noticeToggle(nil, "status", noticeStatusOffline) } // Top POST /platform/website/notice/:id/top func (c *PlatformWebsiteNoticeController) Top() { if _, ok := websiteAuth(&c.Controller); !ok { return } c.noticeToggle(nil, "top", 1) } // UnTop POST /platform/website/notice/:id/untop func (c *PlatformWebsiteNoticeController) UnTop() { if _, ok := websiteAuth(&c.Controller); !ok { return } c.noticeToggle(nil, "top", 0) } // Popup POST /platform/website/notice/:id/popup — 标记为弹窗公告 func (c *PlatformWebsiteNoticeController) Popup() { if _, ok := websiteAuth(&c.Controller); !ok { return } c.noticeToggle(nil, "is_popup", 1) } // UnPopup POST /platform/website/notice/:id/unpopup func (c *PlatformWebsiteNoticeController) UnPopup() { if _, ok := websiteAuth(&c.Controller); !ok { return } c.noticeToggle(nil, "is_popup", 0) } // Delete DELETE /platform/website/notice/:id — 软删除 func (c *PlatformWebsiteNoticeController) Delete() { if _, ok := websiteAuth(&c.Controller); !ok { return } id, ok := websiteID(&c.Controller) if !ok { return } now := time.Now() n, err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)). Filter("id", id). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now, "update_time": now}) if err != nil { jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error()) return } if n == 0 { jsonErr(&c.Controller, 404, 404, "公告不存在") return } websiteOK(&c.Controller, nil) } // BatchDelete POST /platform/website/notice/batchdelete — body { ids: [] } func (c *PlatformWebsiteNoticeController) BatchDelete() { if _, ok := websiteAuth(&c.Controller); !ok { return } var p struct { IDs []uint64 `json:"ids"` } if !websiteBind(&c.Controller, &p) || len(p.IDs) == 0 { jsonErr(&c.Controller, 400, 400, "请选择要删除的公告") return } now := time.Now() if _, err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)). Filter("id__in", p.IDs). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now, "update_time": now}); err != nil { jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error()) return } websiteOK(&c.Controller, nil) }