Files
yunzerwebsiteallinone/go/controllers/platform_website_notice.go
T
2026-09-17 23:42:41 +08:00

427 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}