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 } if req.Status != 0 && req.Status != 1 { app.Error(c, http.StatusBadRequest, nil, "不支持的目标状态") return } // 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 for _, id := range req.TargetIDs { target := models.SystemMessageTarget{ MessageConfigID: uint(message.ID), TargetType: req.TargetType, TargetID: id, } 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 in ? AND deleted_at IS NULL", req.TargetIDs). Pluck("user_id", &userIDs).Error if err != nil { app.Error(c, http.StatusInternalServerError, nil, "获取角色用户失败: "+err.Error()) return } case models.TargetTypeByUser: // 单个用户 if len(req.TargetIDs) != 0 { userIDs = req.TargetIDs } 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 { item := 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, } // 查询该消息的所有接收对象记录 var targets []models.SystemMessageTarget if err := orm.Eloquent. Where("message_config_id = ?", cfg.ID). Find(&targets).Error; err == nil && len(targets) > 0 { // 假设所有 target_type 都一致(如不是,可按需修改) item.TargetType = targets[0].TargetType for _, t := range targets { item.TargetID = append(item.TargetID, t.TargetID) } } items = append(items, item) } resp := models.SysMessageListResp{ List: items, Total: total, Page: req.Page, PageSize: req.PageSize, } app.OK(c, resp, "查询成功") } // SysMessageDetail 公告消息详情 // @Summary 公告消息详情 // @Tags 消息中心,V1.5.0 // @Accept json // @Produce json // @Param request body models.SysMessageDetailReq true "公告消息ID" // @Success 200 {object} models.SysMessageDetailResp // @Router /api/v1/sys_message/detail [post] func SysMessageDetail(c *gin.Context) { var req models.SysMessageDetailReq 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 = 1", req.ID).First(&config).Error; err != nil { app.Error(c, http.StatusNotFound, nil, "公告不存在") return } var target models.SystemMessageTarget _ = orm.Eloquent.Where("message_config_id = ?", config.ID).First(&target) resp := models.SysMessageDetailResp{ ID: uint(config.ID), Title: config.Title, Content: config.ContentTemplate, Level: config.Level, DisplayMode: config.DisplayMode, Status: config.Status, CreatedAt: config.CreatedAt, UpdatedAt: config.UpdatedAt, StartTime: config.StartTime, EndTime: config.EndTime, } if target.ID != 0 { resp.Target = &models.SystemMessageTarget{ MessageConfigID: target.MessageConfigID, TargetType: target.TargetType, TargetID: target.TargetID, } } app.OK(c, resp, "查询成功") }