mh_goadmin_server/app/admin/apis/message/sys_message.go

561 lines
17 KiB
Go
Raw Normal View History

package message
import (
"github.com/gin-gonic/gin"
"go-admin/app/admin/models"
orm "go-admin/common/global"
"go-admin/logger"
"go-admin/tools/app"
"net/http"
"time"
)
// SysMessageCreate 新增公告消息
// @Summary 新增公告消息
// @Tags 消息中心V1.5.0
// @Produce json
// @Accept json
// @Param request body models.SysMessageCreateReq true "新增公告消息模型"
// @Success 200 {object} models.SysMessageCreateResp
// @Router /api/v1/sys_message/create [post]
func SysMessageCreate(c *gin.Context) {
var req models.SysMessageCreateReq
// 1. 参数绑定
if err := c.ShouldBindJSON(&req); err != nil {
app.Error(c, http.StatusBadRequest, nil, "参数错误: "+err.Error())
return
}
// 2. 解析时间
var startTimePtr, endTimePtr *time.Time
var parsedStartTime, parsedEndTime time.Time
var err error
if req.StartTime != "" {
parsedStartTime, err = time.Parse("2006-01-02 15:04:05", req.StartTime)
if err != nil {
app.Error(c, http.StatusBadRequest, nil, "StartTime 格式错误,应为 YYYY-MM-DD HH:MM:SS:111")
return
}
startTimePtr = &parsedStartTime
}
if req.EndTime != "" {
parsedEndTime, err = time.Parse("2006-01-02 15:04:05", req.EndTime)
if err != nil {
app.Error(c, http.StatusBadRequest, nil, "EndTime 格式错误,应为 YYYY-MM-DD HH:MM:SS")
return
}
endTimePtr = &parsedEndTime
}
// 3. 插入 system_message_config公告
message := models.SystemMessageConfig{
MessageType: models.AnnouncementMessageType,
Title: req.Title,
ContentTemplate: req.ContentTemplate,
Level: req.Level,
DisplayMode: req.DisplayMode,
StartTime: startTimePtr,
EndTime: endTimePtr,
Status: req.Status,
}
if err = orm.Eloquent.Create(&message).Error; err != nil {
app.Error(c, http.StatusInternalServerError, nil, "创建公告失败: "+err.Error())
return
}
// 4. 插入 system_message_target
target := models.SystemMessageTarget{
MessageConfigID: uint(message.ID),
TargetType: req.TargetType,
TargetID: req.TargetID,
}
if err = orm.Eloquent.Create(&target).Error; err != nil {
app.Error(c, http.StatusInternalServerError, nil, "接收对象配置失败: "+err.Error())
return
}
// 5. 判断是否需要立即下发用户消息
needPushNow := (startTimePtr == nil || !parsedStartTime.After(time.Now())) && req.Status == 1
if needPushNow {
var userIDs []int64
switch req.TargetType {
case models.TargetTypeByAll: // 全员
err = orm.Eloquent.Table("sys_user").Where("deleted_at IS NULL").Pluck("user_id", &userIDs).Error
if err != nil {
app.Error(c, http.StatusInternalServerError, nil, "获取全员用户失败: "+err.Error())
return
}
case models.TargetTypeByRole: // 角色
err = orm.Eloquent.Table("sys_user").
Where("role_id = ? AND deleted_at IS NULL", req.TargetID).
Pluck("user_id", &userIDs).Error
if err != nil {
app.Error(c, http.StatusInternalServerError, nil, "获取角色用户失败: "+err.Error())
return
}
case models.TargetTypeByUser: // 单个用户
if req.TargetID != 0 {
userIDs = []int64{req.TargetID}
}
default:
app.Error(c, http.StatusInternalServerError, nil, "接收对象类型有误,不在取值范围")
return
}
// 构造 system_user_message 批量插入
var userMessages []models.SystemUserMessage
for _, uid := range userIDs {
userMessages = append(userMessages, models.SystemUserMessage{
UserID: uid,
MessageConfigID: uint(message.ID),
MessageType: models.AnnouncementMessageType,
Title: message.Title,
Content: message.ContentTemplate,
IsRead: false,
IsVisible: true,
})
}
if len(userMessages) > 0 {
if err = orm.Eloquent.Create(&userMessages).Error; err != nil {
app.Error(c, http.StatusInternalServerError, nil, "写入用户消息失败: "+err.Error())
return
}
}
}
// 6. 成功响应
app.OK(c, models.SysMessageCreateResp{ID: message.ID}, "新增成功")
}
// SysMessageEdit 编辑公告消息
// @Summary 编辑公告消息(仅草稿可编辑)
// @Tags 消息中心V1.5.0
// @Accept json
// @Produce json
// @Param request body models.SysMessageEditReq true "编辑公告消息模型"
// @Success 200 {object} models.SysMessageCreateResp
// @Router /api/v1/sys_message/edit [post]
func SysMessageEdit(c *gin.Context) {
var req models.SysMessageEditReq
// 1. 参数绑定
if err := c.ShouldBindJSON(&req); err != nil {
app.Error(c, http.StatusBadRequest, nil, "参数错误: "+err.Error())
return
}
if req.ID == 0 {
app.Error(c, http.StatusBadRequest, nil, "参数错误: 缺少ID")
return
}
// 2. 查询原公告(草稿)
var message models.SystemMessageConfig
if err := orm.Eloquent.Where("id = ? AND message_type = ?", req.ID, models.AnnouncementMessageType).First(&message).Error; err != nil {
app.Error(c, http.StatusBadRequest, nil, "公告不存在")
return
}
if message.Status != models.MessageStatusOnDraft {
app.Error(c, http.StatusBadRequest, nil, "仅草稿状态的公告可以编辑")
return
}
// 3. 解析时间
var startTimePtr, endTimePtr *time.Time
var parsedStartTime, parsedEndTime time.Time
var err error
if req.StartTime != "" {
parsedStartTime, err = time.Parse("2006-01-02 15:04:05", req.StartTime)
if err != nil {
app.Error(c, http.StatusBadRequest, nil, "StartTime 格式错误,应为 YYYY-MM-DD HH:MM:SS")
return
}
startTimePtr = &parsedStartTime
}
if req.EndTime != "" {
parsedEndTime, err = time.Parse("2006-01-02 15:04:05", req.EndTime)
if err != nil {
app.Error(c, http.StatusBadRequest, nil, "EndTime 格式错误,应为 YYYY-MM-DD HH:MM:SS")
return
}
endTimePtr = &parsedEndTime
}
begin := orm.Eloquent.Begin()
// 4. 更新公告主表
update := map[string]interface{}{
"title": req.Title,
"content_template": req.ContentTemplate,
"level": req.Level,
"display_mode": req.DisplayMode,
"start_time": startTimePtr,
"end_time": endTimePtr,
"status": req.Status,
}
if err := begin.Model(&models.SystemMessageConfig{}).Where("id = ?", req.ID).Updates(update).Error; err != nil {
begin.Rollback()
app.Error(c, http.StatusInternalServerError, nil, "更新公告失败: "+err.Error())
return
}
// 5. 更新接收对象(删除再插入)
if err := begin.Where("message_config_id = ?", req.ID).Delete(&models.SystemMessageTarget{}).Error; err != nil {
begin.Rollback()
app.Error(c, http.StatusInternalServerError, nil, "更新接收对象失败: "+err.Error())
return
}
newTarget := models.SystemMessageTarget{
MessageConfigID: req.ID,
TargetType: req.TargetType,
TargetID: req.TargetID,
}
if err := begin.Create(&newTarget).Error; err != nil {
begin.Rollback()
app.Error(c, http.StatusInternalServerError, nil, "写入接收对象失败: "+err.Error())
return
}
err = begin.Commit().Error
if err != nil {
begin.Rollback()
logger.Error("SysMessageDelete commit err:", logger.Field("err", err))
app.Error(c, http.StatusInternalServerError, nil, "编辑公告失败")
return
}
// 6. 判断是否需要立即下发消息
needPushNow := (startTimePtr == nil || !parsedStartTime.After(time.Now())) && req.Status == models.MessageStart
if needPushNow {
var userIDs []int64
switch req.TargetType {
case models.TargetTypeByAll:
err = orm.Eloquent.Table("sys_user").
Where("deleted_at IS NULL").
Pluck("user_id", &userIDs).Error
case models.TargetTypeByRole:
err = orm.Eloquent.Table("sys_user").
Where("role_id = ? AND deleted_at IS NULL", req.TargetID).
Pluck("user_id", &userIDs).Error
case models.TargetTypeByUser:
if req.TargetID != 0 {
userIDs = []int64{req.TargetID}
}
default:
app.Error(c, http.StatusInternalServerError, nil, "接收对象类型无效")
return
}
if err != nil {
app.Error(c, http.StatusInternalServerError, nil, "获取用户失败: "+err.Error())
return
}
// 构建用户消息记录
var userMessages []models.SystemUserMessage
for _, uid := range userIDs {
userMessages = append(userMessages, models.SystemUserMessage{
UserID: uid,
MessageConfigID: req.ID,
MessageType: models.AnnouncementMessageType,
Title: req.Title,
Content: req.ContentTemplate,
IsRead: false,
IsVisible: true,
})
}
if len(userMessages) > 0 {
if err := orm.Eloquent.Create(&userMessages).Error; err != nil {
app.Error(c, http.StatusInternalServerError, nil, "写入用户消息失败: "+err.Error())
return
}
}
}
app.OK(c, models.SysMessageCreateResp{ID: uint32(req.ID)}, "编辑成功")
}
// SysMessageSetStatus 设置公告状态(启用/禁用)
// @Summary 设置公告状态
// @Tags 消息中心V1.5.0
// @Accept json
// @Produce json
// @Param request body models.SysMessageSetStatusReq true "状态更新请求"
// @Success 200 {object} app.Response
// @Router /api/v1/sys_message/set_status [post]
func SysMessageSetStatus(c *gin.Context) {
var req models.SysMessageSetStatusReq
if err := c.ShouldBindJSON(&req); err != nil {
app.Error(c, http.StatusBadRequest, nil, "参数错误: "+err.Error())
return
}
var config models.SystemMessageConfig
if err := orm.Eloquent.Where("id = ? AND message_type = ?", req.ID, 1).First(&config).Error; err != nil {
app.Error(c, http.StatusNotFound, nil, "公告不存在")
return
}
// 校验状态切换合法性
switch req.Status {
case 1: // 启用
if config.Status != models.MessageStatusOnDraft && config.Status != models.MessageStatusOnDisable {
app.Error(c, http.StatusBadRequest, nil, "只有草稿或禁用状态才能启用")
return
}
case 2: // 禁用
if config.Status != models.MessageStatusOnStart {
app.Error(c, http.StatusBadRequest, nil, "只有启用状态才能禁用")
return
}
default:
app.Error(c, http.StatusBadRequest, nil, "不支持的目标状态")
return
}
// 状态更新
if err := orm.Eloquent.Model(&config).Update("status", req.Status).Error; err != nil {
app.Error(c, http.StatusInternalServerError, nil, "状态更新失败")
return
}
// 状态逻辑处理
if req.Status == models.MessageStart {
// ✅ 启用逻辑:若 start_time 已开始 → 插入或恢复用户消息
if config.StartTime == nil || config.StartTime.Before(time.Now()) {
var target models.SystemMessageTarget
if err := orm.Eloquent.Where("message_config_id = ?", config.ID).First(&target).Error; err != nil {
app.Error(c, http.StatusInternalServerError, nil, "接收对象未配置")
return
}
var userIDs []int64
switch target.TargetType {
case models.TargetTypeByAll: // 全员
err := orm.Eloquent.Table("sys_user").Where("deleted_at IS NULL").Pluck("user_id", &userIDs).Error
if err != nil {
app.Error(c, http.StatusInternalServerError, nil, "获取全员用户失败: "+err.Error())
return
}
case models.TargetTypeByRole: // 角色
err := orm.Eloquent.Table("sys_user").
Where("role_id = ? AND deleted_at IS NULL", target.TargetID).
Pluck("user_id", &userIDs).Error
if err != nil {
app.Error(c, http.StatusInternalServerError, nil, "获取角色用户失败: "+err.Error())
return
}
case models.TargetTypeByUser: // 单个用户
if target.TargetType != 0 {
userIDs = []int64{target.TargetID}
}
default:
app.Error(c, http.StatusInternalServerError, nil, "接收对象类型有误,不在取值范围")
return
}
// 已存在的 user_id
var existedUserIDs []int64
orm.Eloquent.Table("system_user_message").
Where("message_config_id = ? AND user_id IN (?)", config.ID, userIDs).
Pluck("user_id", &existedUserIDs)
existedSet := make(map[int64]struct{})
for _, uid := range existedUserIDs {
existedSet[uid] = struct{}{}
}
var toInsert []models.SystemUserMessage
var toUpdateIDs []int64
for _, uid := range userIDs {
if _, ok := existedSet[uid]; ok {
toUpdateIDs = append(toUpdateIDs, uid)
} else {
toInsert = append(toInsert, models.SystemUserMessage{
UserID: uid,
MessageConfigID: uint(config.ID),
MessageType: 1,
Title: config.Title,
Content: config.ContentTemplate,
IsRead: false,
IsVisible: true,
})
}
}
if len(toInsert) > 0 {
_ = orm.Eloquent.Create(&toInsert).Error
}
if len(toUpdateIDs) > 0 {
_ = orm.Eloquent.Model(&models.SystemUserMessage{}).
Where("message_config_id = ? AND user_id IN (?)", config.ID, toUpdateIDs).
Update("is_visible", true).Error
}
}
} else if req.Status == models.MessageStop {
// ✅ 禁用逻辑:隐藏用户已发送的公告消息
_ = orm.Eloquent.Model(&models.SystemUserMessage{}).
Where("message_config_id = ?", config.ID).
Update("is_visible", false).Error
}
app.OK(c, nil, "状态更新成功")
}
// SysMessageDelete 删除公告消息(草稿/禁用/过期状态可删)
// @Summary 删除公告消息
// @Tags 消息中心V1.5.0
// @Accept json
// @Produce json
// @Param request body models.SysMessageDeleteReq true "删除公告请求"
// @Success 200 {object} app.Response
// @Router /api/v1/sys_message/delete [post]
func SysMessageDelete(c *gin.Context) {
var req models.SysMessageDeleteReq
if err := c.ShouldBindJSON(&req); err != nil {
app.Error(c, http.StatusBadRequest, nil, "参数错误: "+err.Error())
return
}
var config models.SystemMessageConfig
if err := orm.Eloquent.Where("id = ? AND message_type = ?", req.ID, models.AnnouncementMessageType).
First(&config).Error; err != nil {
app.Error(c, http.StatusNotFound, nil, "公告不存在")
return
}
if config.Status != models.MessageStatusOnDraft && config.Status != models.MessageStatusOnDisable &&
config.Status != models.MessageStatusOnExpire {
app.Error(c, http.StatusBadRequest, nil, "仅草稿、禁用、已过期状态可删除")
return
}
begin := orm.Eloquent.Begin()
// 删除主配置(软删除)
if err := begin.Delete(&config).Error; err != nil {
begin.Rollback()
app.Error(c, http.StatusInternalServerError, nil, "删除公告失败")
return
}
// 删除接收对象配置
if err := begin.Where("message_config_id = ?", config.ID).
Delete(&models.SystemMessageTarget{}).Error; err != nil {
begin.Rollback()
app.Error(c, http.StatusInternalServerError, nil, "删除公告失败")
return
}
// ✅ 更新用户消息为不可见(但保留)
if err := begin.Model(&models.SystemUserMessage{}).
Where("message_config_id = ?", config.ID).
Update("is_visible", false).Error; err != nil {
begin.Rollback()
app.Error(c, http.StatusInternalServerError, nil, "删除公告失败")
return
}
err := begin.Commit().Error
if err != nil {
begin.Rollback()
logger.Error("SysMessageDelete commit err:", logger.Field("err", err))
app.Error(c, http.StatusInternalServerError, nil, "删除公告失败")
return
}
app.OK(c, nil, "删除成功")
}
// SysMessageList 公告消息列表
// @Summary 公告消息列表
// @Tags 消息中心V1.5.0
// @Accept json
// @Produce json
// @Param request body models.SysMessageListReq true "查询条件"
// @Success 200 {object} models.SysMessageListResp
// @Router /api/v1/sys_message/list [post]
func SysMessageList(c *gin.Context) {
var req models.SysMessageListReq
if err := c.ShouldBindJSON(&req); err != nil {
app.Error(c, http.StatusBadRequest, nil, "参数错误: "+err.Error())
return
}
// 分页默认处理
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = 10
}
db := orm.Eloquent.Table("system_message_config").
Where("message_type = ?", 1)
if req.Title != "" {
db = db.Where("title LIKE ?", "%"+req.Title+"%")
}
if req.Level != 0 {
db = db.Where("level = ?", req.Level)
}
if req.DisplayMode != 0 {
db = db.Where("display_mode = ?", req.DisplayMode)
}
if req.Status != 0 {
db = db.Where("status = ?", req.Status)
}
if req.TargetType != 0 {
subQuery := orm.Eloquent.Table("system_message_target").
Select("message_config_id").
Where("target_type = ?", req.TargetType)
db = db.Where("id IN (?)", subQuery)
}
var total int64
if err := db.Count(&total).Error; err != nil {
app.Error(c, http.StatusInternalServerError, nil, "统计失败: "+err.Error())
return
}
var list []models.SystemMessageConfig
if err := db.
Order("created_at DESC").
Offset((req.Page - 1) * req.PageSize).
Limit(req.PageSize).
Find(&list).Error; err != nil {
app.Error(c, http.StatusInternalServerError, nil, "查询失败: "+err.Error())
return
}
// 转换为展示数据
var items []models.SysAnnouncementItem
for _, cfg := range list {
items = append(items, models.SysAnnouncementItem{
ID: cfg.ID,
Title: cfg.Title,
ContentTemplate: cfg.ContentTemplate,
Level: cfg.Level,
DisplayMode: cfg.DisplayMode,
StartTime: cfg.StartTime,
EndTime: cfg.EndTime,
Status: cfg.Status,
CreatedAt: cfg.CreatedAt,
UpdatedAt: cfg.UpdatedAt,
})
}
resp := models.SysMessageListResp{
List: items,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
}
app.OK(c, resp, "查询成功")
}