增加通知公告功能
This commit is contained in:
@@ -0,0 +1,416 @@
|
||||
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
|
||||
}
|
||||
|
||||
// Prepare 所有接口执行前确保通知公告表存在。
|
||||
func (c *BackendOaNoticeController) Prepare() {
|
||||
_ = models.EnsureOaNoticeTable()
|
||||
}
|
||||
|
||||
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})
|
||||
}
|
||||
Reference in New Issue
Block a user