package controllers import ( "encoding/json" "io" "strconv" "strings" "time" "unicode/utf8" "server/models" "server/pkg/jwtutil" "github.com/beego/beego/v2/client/orm" beego "github.com/beego/beego/v2/server/web" ) // BackendOaNoticeController OA通知公告管理。 // 数据按租户 tid 隔离;发布后对租户内所有后台用户可见。 type BackendOaNoticeController struct { beego.Controller } func (c *BackendOaNoticeController) oaNoticeClaims() (*jwtutil.Claims, error) { auth := c.Ctx.Request.Header.Get("Authorization") if auth == "" { return nil, orm.ErrNoRows } parts := strings.SplitN(auth, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { return nil, orm.ErrNoRows } claims, err := jwtutil.ParseToken(parts[1]) if err != nil { return nil, err } if claims.UserType != "backend" { return nil, orm.ErrNoRows } return claims, nil } func (c *BackendOaNoticeController) ontJsonErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *BackendOaNoticeController) ontOk(data interface{}) { c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} _ = c.ServeJSON() } type oaNoticePayload struct { Title string `json:"title"` Content string `json:"content"` NoticeType int8 `json:"notice_type"` Level int8 `json:"level"` IsTop int8 `json:"is_top"` // Publish 为 true 时保存即发布 Publish bool `json:"publish"` } // parseOaNoticePayload 读取并校验公告请求体;失败时直接输出错误响应。 func (c *BackendOaNoticeController) parseOaNoticePayload() (oaNoticePayload, bool) { var payload oaNoticePayload raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil || json.Unmarshal(raw, &payload) != nil { c.ontJsonErr(400, 400, "参数错误") return payload, false } payload.Title = strings.TrimSpace(payload.Title) if payload.Title == "" { c.ontJsonErr(400, 400, "请输入公告标题") return payload, false } if utf8.RuneCountInString(payload.Title) > 100 { c.ontJsonErr(400, 400, "公告标题不能超过100字") return payload, false } if payload.NoticeType < 0 || payload.NoticeType > 2 { payload.NoticeType = 0 } if payload.Level < 0 || payload.Level > 2 { payload.Level = 0 } if payload.IsTop != 1 { payload.IsTop = 0 } payload.Content = strings.TrimSpace(payload.Content) return payload, true } // noticeBase 租户内未删除公告的基础查询集 func noticeBase(tid int) orm.QuerySeter { return models.Orm.QueryTable(new(models.OaNotice)). Filter("is_deleted", 0). Filter("tid", tid) } // List GET /backend/oa/notice/list // 管理端分页列表,支持 keyword/status/notice_type 筛选;置顶优先,再按发布时间倒序。 func (c *BackendOaNoticeController) List() { claims, err := c.oaNoticeClaims() if err != nil { c.ontJsonErr(401, 401, "未登录或无权限") return } page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 20) keyword := strings.TrimSpace(c.GetString("keyword")) status := strings.TrimSpace(c.GetString("status")) noticeType := strings.TrimSpace(c.GetString("notice_type")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 500 { pageSize = 20 } qs := noticeBase(claims.TenantId) if keyword != "" { qs = qs.Filter("title__icontains", keyword) } if status == "0" || status == "1" || status == "2" { v, _ := strconv.Atoi(status) qs = qs.Filter("status", int8(v)) } if noticeType == "0" || noticeType == "1" || noticeType == "2" { v, _ := strconv.Atoi(noticeType) qs = qs.Filter("notice_type", int8(v)) } total, _ := qs.Count() var list []models.OaNotice _, err = qs.OrderBy("-is_top", "-publish_time", "-id"). Limit(pageSize).Offset((page - 1) * pageSize). All(&list) if err != nil && err != orm.ErrNoRows { c.ontJsonErr(500, 500, "查询失败") return } if list == nil { list = []models.OaNotice{} } c.ontOk(map[string]interface{}{"list": list, "total": total}) } // Portal GET /backend/oa/notice/portal // 工作台/仪表盘用:只返回已发布的公告,置顶优先,取最近 limit 条(默认 5,最多 20)。 func (c *BackendOaNoticeController) Portal() { claims, err := c.oaNoticeClaims() if err != nil { c.ontJsonErr(401, 401, "未登录或无权限") return } limit, _ := c.GetInt("limit", 5) if limit < 1 || limit > 20 { limit = 5 } var list []models.OaNotice _, err = noticeBase(claims.TenantId). Filter("status", 1). OrderBy("-is_top", "-publish_time", "-id"). Limit(limit). All(&list) if err != nil && err != orm.ErrNoRows { c.ontJsonErr(500, 500, "查询失败") return } if list == nil { list = []models.OaNotice{} } c.ontOk(map[string]interface{}{"list": list}) } // Detail GET /backend/oa/notice/detail/:id // 公告详情,同时累加阅读次数。 func (c *BackendOaNoticeController) Detail() { claims, err := c.oaNoticeClaims() if err != nil { c.ontJsonErr(401, 401, "未登录或无权限") return } id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.ontJsonErr(400, 400, "无效ID") return } var item models.OaNotice err = noticeBase(claims.TenantId).Filter("id", id).One(&item) if err != nil { c.ontJsonErr(404, 404, "公告不存在") return } // 阅读数自增(并发下允许略有偏差,仅为展示用) _, _ = models.Orm.Raw( "UPDATE yz_backend_oa_notice SET read_count = read_count + 1 WHERE id = ?", id).Exec() item.ReadCount++ c.ontOk(item) } // Create POST /backend/oa/notice/create func (c *BackendOaNoticeController) Create() { claims, err := c.oaNoticeClaims() if err != nil { c.ontJsonErr(401, 401, "未登录或无权限") return } payload, ok := c.parseOaNoticePayload() if !ok { return } now := time.Now() item := &models.OaNotice{ Tid: claims.TenantId, Title: payload.Title, Content: payload.Content, NoticeType: payload.NoticeType, Level: payload.Level, IsTop: payload.IsTop, Status: 0, PublisherID: uint64(claims.UserID), PublisherName: claims.Username, IsDeleted: 0, CreateTime: now, UpdateTime: &now, } if payload.Publish { item.Status = 1 item.PublishTime = &now } if _, err := models.Orm.Insert(item); err != nil { c.ontJsonErr(500, 500, "保存失败") return } c.ontOk(item) } // Update POST /backend/oa/notice/update/:id // 编辑公告内容;已发布的公告编辑后保持发布状态(不改变发布时间)。 func (c *BackendOaNoticeController) Update() { claims, err := c.oaNoticeClaims() if err != nil { c.ontJsonErr(401, 401, "未登录或无权限") return } id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.ontJsonErr(400, 400, "无效ID") return } payload, ok := c.parseOaNoticePayload() if !ok { return } var item models.OaNotice err = noticeBase(claims.TenantId).Filter("id", id).One(&item) if err != nil { c.ontJsonErr(404, 404, "公告不存在") return } now := time.Now() item.Title = payload.Title item.Content = payload.Content item.NoticeType = payload.NoticeType item.Level = payload.Level item.IsTop = payload.IsTop item.UpdateTime = &now fields := []string{"Title", "Content", "NoticeType", "Level", "IsTop", "UpdateTime"} // 草稿保存时选择"发布"则直接发布 if payload.Publish && item.Status != 1 { item.Status = 1 item.PublishTime = &now fields = append(fields, "Status", "PublishTime") } if _, err := models.Orm.Update(&item, fields...); err != nil { c.ontJsonErr(500, 500, "保存失败") return } c.ontOk(item) } // Delete DELETE /backend/oa/notice/delete/:id 软删除。 func (c *BackendOaNoticeController) Delete() { claims, err := c.oaNoticeClaims() if err != nil { c.ontJsonErr(401, 401, "未登录或无权限") return } id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.ontJsonErr(400, 400, "无效ID") return } now := time.Now() n, err := noticeBase(claims.TenantId).Filter("id", id). Update(map[string]interface{}{ "IsDeleted": int8(1), "DeleteTime": now, "UpdateTime": now, }) if err != nil || n == 0 { c.ontJsonErr(404, 404, "公告不存在或已删除") return } c.ontOk(nil) } // Publish POST /backend/oa/notice/publish/:id // 发布 / 下架切换:草稿、已下架 -> 已发布(写入发布时间);已发布 -> 已下架。 func (c *BackendOaNoticeController) Publish() { claims, err := c.oaNoticeClaims() if err != nil { c.ontJsonErr(401, 401, "未登录或无权限") return } id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.ontJsonErr(400, 400, "无效ID") return } var item models.OaNotice err = noticeBase(claims.TenantId).Filter("id", id).One(&item) if err != nil { c.ontJsonErr(404, 404, "公告不存在") return } now := time.Now() if item.Status == 1 { item.Status = 2 item.PublishTime = nil } else { item.Status = 1 item.PublishTime = &now } item.UpdateTime = &now if _, err := models.Orm.Update(&item, "Status", "PublishTime", "UpdateTime"); err != nil { c.ontJsonErr(500, 500, "操作失败") return } c.ontOk(map[string]interface{}{"status": item.Status}) } // Top POST /backend/oa/notice/top/:id 置顶 / 取消置顶切换。 func (c *BackendOaNoticeController) Top() { claims, err := c.oaNoticeClaims() if err != nil { c.ontJsonErr(401, 401, "未登录或无权限") return } id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.ontJsonErr(400, 400, "无效ID") return } var item models.OaNotice err = noticeBase(claims.TenantId).Filter("id", id).One(&item) if err != nil { c.ontJsonErr(404, 404, "公告不存在") return } now := time.Now() if item.IsTop == 1 { item.IsTop = 0 } else { item.IsTop = 1 } item.UpdateTime = &now if _, err := models.Orm.Update(&item, "IsTop", "UpdateTime"); err != nil { c.ontJsonErr(500, 500, "操作失败") return } c.ontOk(map[string]interface{}{"is_top": item.IsTop}) }