diff --git a/app/admin/apis/message/bus_message.go b/app/admin/apis/message/bus_message.go index 7fa5da0..a1d9f5b 100644 --- a/app/admin/apis/message/bus_message.go +++ b/app/admin/apis/message/bus_message.go @@ -1,21 +1,257 @@ package message -import "github.com/gin-gonic/gin" +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" +) +// BusMessageCreate 新增业务消息 +// @Summary 新增业务消息 +// @Tags 消息中心,V1.5.0 +// @Produce json +// @Accept json +// @Param request body models.BusMessageCreateReq true "新增业务消息模型" +// @Success 200 {object} models.BusMessageCreateResp +// @Router /api/v1/bus_message/create [post] func BusMessageCreate(c *gin.Context) { + var req models.BusMessageCreateReq + if err := c.ShouldBindJSON(&req); err != nil { + app.Error(c, http.StatusBadRequest, err, "参数绑定失败") + return + } + // 校验业务类型、事件类型、模板变量 + if err := models.ValidateBizEventAndTemplate(req.BizType, req.Event, req.ContentTemplate); err != nil { + app.Error(c, http.StatusBadRequest, err, "模板校验失败") + return + } + + // 创建业务消息配置 + msg := models.SystemMessageConfig{ + MessageType: models.BusinessMessageType, // 业务消息 + Title: req.Title, + ContentTemplate: req.ContentTemplate, + BizType: req.BizType, + Event: req.Event, + Status: req.Status, + } + if err := orm.Eloquent.Create(&msg).Error; err != nil { + app.Error(c, http.StatusInternalServerError, err, "创建业务消息失败") + return + } + + // 创建接收对象配置 + target := models.SystemMessageTarget{ + MessageConfigID: uint(msg.ID), + TargetType: req.TargetType, + TargetID: req.TargetID, + } + if err := orm.Eloquent.Create(&target).Error; err != nil { + app.Error(c, http.StatusInternalServerError, err, "接收对象配置失败") + return + } + + app.OK(c, models.BusMessageCreateResp{ID: uint(msg.ID)}, "创建成功") } -func BusMessageSetStatus(c *gin.Context) { - -} - -func BusMessageDelete(c *gin.Context) { - -} - +// BusMessageList 业务消息列表 +// @Summary 业务消息列表 +// @Tags 消息中心,V1.5.0 +// @Accept json +// @Produce json +// @Param request body models.BusMessageListReq true "查询条件" +// @Success 200 {object} models.BusMessageListResp +// @Router /api/v1/bus_message/list [post] func BusMessageList(c *gin.Context) { + var req models.BusMessageListReq + 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 = ?", models.BusinessMessageType) + + if req.BizType != "" { + db = db.Where("biz_type = ?", req.BizType) + } + if req.Event != "" { + db = db.Where("event = ?", req.Event) + } + if req.Status != 0 { + db = db.Where("status = ?", req.Status) + } + + // 角色过滤(JOIN subquery) + if len(req.RoleID) > 0 { + subQuery := orm.Eloquent.Table("system_message_target"). + Select("message_config_id"). + Where("target_type = ?", models.TargetTypeByRole). + Where("target_id IN (?)", req.RoleID) + 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.BusMessageItem + for _, cfg := range list { + item := models.BusMessageItem{ + ID: cfg.ID, + CreatedAt: cfg.CreatedAt, + UpdatedAt: cfg.UpdatedAt, + ContentTemplate: cfg.ContentTemplate, + BizType: cfg.BizType, + Event: cfg.Event, + Status: cfg.Status, + } + + // 查询该消息绑定的所有角色 + var targets []models.SystemMessageTarget + if err := orm.Eloquent.Where("message_config_id = ? AND target_type = ?", cfg.ID, + models.TargetTypeByRole).Find(&targets).Error; err == nil { + for _, t := range targets { + item.RoleID = append(item.RoleID, t.TargetID) + } + } + + items = append(items, item) + } + + resp := models.BusMessageListResp{ + List: items, + Total: total, + Page: req.Page, + PageSize: req.PageSize, + } + app.OK(c, resp, "查询成功") +} + +// BusMessageSetStatus 设置业务消息状态(启用/禁用) +// @Summary 设置业务消息状态 +// @Tags 消息中心,V1.5.0 +// @Accept json +// @Produce json +// @Param request body models.SysMessageSetStatusReq true "状态更新请求" +// @Success 200 {object} app.Response +// @Router /api/v1/bus_message/set_status [post] +func BusMessageSetStatus(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, models.BusinessMessageType). + First(&config).Error; err != nil { + app.Error(c, http.StatusNotFound, nil, "业务消息不存在") + return + } + + // 校验状态切换合法性 + switch req.Status { + case 1: // 启用 + if 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 + } + + app.OK(c, nil, "状态更新成功") +} + +// BusMessageDelete 删除业务消息(禁用状态可删) +// @Summary 删除业务消息 +// @Tags 消息中心,V1.5.0 +// @Accept json +// @Produce json +// @Param request body models.SysMessageDeleteReq true "删除业务消息" +// @Success 200 {object} app.Response +// @Router /api/v1/bus_message/delete [post] +func BusMessageDelete(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.BusinessMessageType). + First(&config).Error; err != nil { + app.Error(c, http.StatusNotFound, nil, "业务消息不存在") + return + } + + if config.Status != models.MessageStatusOnDisable { + 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 + } + + err := begin.Commit().Error + if err != nil { + begin.Rollback() + logger.Error("BusMessageDelete commit err:", logger.Field("err", err)) + app.Error(c, http.StatusInternalServerError, nil, "删除公告失败") + return + } + + app.OK(c, nil, "删除成功") } func BusMessageEdit(c *gin.Context) { diff --git a/app/admin/apis/message/sys_message.go b/app/admin/apis/message/sys_message.go index 6d64970..c742023 100644 --- a/app/admin/apis/message/sys_message.go +++ b/app/admin/apis/message/sys_message.go @@ -50,6 +50,11 @@ func SysMessageCreate(c *gin.Context) { 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, @@ -68,14 +73,16 @@ func SysMessageCreate(c *gin.Context) { } // 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 + 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. 判断是否需要立即下发用户消息 @@ -92,15 +99,15 @@ func SysMessageCreate(c *gin.Context) { } case models.TargetTypeByRole: // 角色 err = orm.Eloquent.Table("sys_user"). - Where("role_id = ? AND deleted_at IS NULL", req.TargetID). + 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 req.TargetID != 0 { - userIDs = []int64{req.TargetID} + if len(req.TargetIDs) != 0 { + userIDs = req.TargetIDs } default: app.Error(c, http.StatusInternalServerError, nil, "接收对象类型有误,不在取值范围") @@ -279,8 +286,8 @@ func SysMessageEdit(c *gin.Context) { app.OK(c, models.SysMessageCreateResp{ID: uint32(req.ID)}, "编辑成功") } -// SysMessageSetStatus 设置公告状态(启用/禁用) -// @Summary 设置公告状态 +// SysMessageSetStatus 设置公告消息状态(启用/禁用) +// @Summary 设置公告消息状态 // @Tags 消息中心,V1.5.0 // @Accept json // @Produce json @@ -535,7 +542,7 @@ func SysMessageList(c *gin.Context) { // 转换为展示数据 var items []models.SysAnnouncementItem for _, cfg := range list { - items = append(items, models.SysAnnouncementItem{ + item := models.SysAnnouncementItem{ ID: cfg.ID, Title: cfg.Title, ContentTemplate: cfg.ContentTemplate, @@ -546,7 +553,21 @@ func SysMessageList(c *gin.Context) { 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{ @@ -558,3 +579,51 @@ func SysMessageList(c *gin.Context) { 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, "查询成功") +} diff --git a/app/admin/apis/message/user_message.go b/app/admin/apis/message/user_message.go index cbe070f..356330c 100644 --- a/app/admin/apis/message/user_message.go +++ b/app/admin/apis/message/user_message.go @@ -36,11 +36,13 @@ func UserMessageList(c *gin.Context) { userID := tools.GetUserId(c) query := orm.Eloquent.Where("user_id = ? AND is_visible = ?", userID, true) - if req.IsRead != 0 && req.IsRead != 1 { + if req.IsRead != 0 && req.IsRead != 1 && req.IsRead != 2 { app.Error(c, http.StatusBadRequest, nil, "不支持的目标状态") return - } else { - query = query.Where("is_read = ?", req.IsRead) + } else if req.IsRead == 1 { + query = query.Where("is_read = ?", true) + } else if req.IsRead == 2 { + query = query.Where("is_read = ?", false) } var total int64 @@ -58,8 +60,57 @@ func UserMessageList(c *gin.Context) { return } + var result []models.UserMessageDetailRespItem + var configIDs []uint + + for _, msg := range messages { + item := models.UserMessageDetailRespItem{ + ID: uint(msg.ID), + UserID: msg.UserID, + MessageConfigID: msg.MessageConfigID, + MessageType: msg.MessageType, + Title: msg.Title, + Content: msg.Content, + IsRead: msg.IsRead, + ReadTime: msg.ReadTime, + IsVisible: msg.IsVisible, + } + if msg.MessageType == models.AnnouncementMessageType && msg.MessageConfigID != 0 { + configIDs = append(configIDs, msg.MessageConfigID) + } + result = append(result, item) + } + + // 加载所有相关公告配置 + var configs []models.SystemMessageConfig + if len(configIDs) > 0 { + err := orm.Eloquent. + Where("id IN (?)", configIDs). + Find(&configs).Error + if err != nil { + app.Error(c, http.StatusInternalServerError, nil, "加载公告配置失败: "+err.Error()) + return + } + // 构建映射 + configMap := make(map[uint]models.SystemMessageConfig) + for _, cfg := range configs { + configMap[uint(cfg.ID)] = cfg + } + // 填充扩展字段 + for i, item := range result { + if item.MessageType == models.AnnouncementMessageType { + if cfg, ok := configMap[item.MessageConfigID]; ok { + result[i].Level = cfg.Level + result[i].DisplayMode = cfg.DisplayMode + result[i].StartTime = cfg.StartTime + result[i].EndTime = cfg.EndTime + } + } + } + } + app.OK(c, models.UserMessageListResp{ - List: messages, + List: result, Total: total, Page: req.Page, PageSize: req.PageSize, diff --git a/app/admin/models/decision.go b/app/admin/models/decision.go index 83587eb..3728b01 100644 --- a/app/admin/models/decision.go +++ b/app/admin/models/decision.go @@ -3008,9 +3008,9 @@ func QueryStoreSalesData(req *ErpStoreSalesDataReq, c *gin.Context) (*ErpStoreSa logger.Error("endTime parse err") } - qs = qs.Where("maker_time BETWEEN ? AND ?", startTime, endTime) + qs = qs.Where("audit_time BETWEEN ? AND ?", startTime, endTime) } else { - qs = qs.Where("maker_time IS NOT NULL") + qs = qs.Where("audit_time IS NOT NULL") } qs.Where("state = ?", ErpOrderStateAudited) diff --git a/app/admin/models/erp_order.go b/app/admin/models/erp_order.go index 2798a05..6ced8b0 100644 --- a/app/admin/models/erp_order.go +++ b/app/admin/models/erp_order.go @@ -5843,10 +5843,10 @@ func subtractCashierData(cashierData, rejectedCashierData []ErpOrderCashier) []E var result []ErpOrderCashier for _, cashier := range cashierData { adjustedAmount := cashier.Amount - rejectedMap[cashier.CashierId] - // 如果金额为负数则视为零 - if adjustedAmount < 0 { - adjustedAmount = 0 - } + //// 如果金额为负数则视为零 + //if adjustedAmount < 0 { + // adjustedAmount = 0 + //} result = append(result, ErpOrderCashier{ CashierId: cashier.CashierId, Name: cashier.Name, diff --git a/app/admin/models/message.go b/app/admin/models/message.go index cd1f03e..b7d5460 100644 --- a/app/admin/models/message.go +++ b/app/admin/models/message.go @@ -1,11 +1,17 @@ package models import ( + "encoding/json" + "errors" + "fmt" + orm "go-admin/common/global" + "regexp" "time" ) const ( AnnouncementMessageType = 1 // 公告消息 + BusinessMessageType = 2 // 业务消息 TargetTypeByAll = 1 // 接收对象类型:1=全员 TargetTypeByRole = 2 // 接收对象类型:2=角色 @@ -58,6 +64,11 @@ type SystemEventRegistry struct { IsEnabled bool `json:"is_enabled"` // 是否启用 } +type TemplateVarItem struct { + Key string `json:"key"` + Desc string `json:"desc"` +} + // SystemUserMessage 用户收到的消息记录表(实际发送记录) type SystemUserMessage struct { Model @@ -74,15 +85,15 @@ type SystemUserMessage struct { // SysMessageCreateReq 新增公告请求结构体 type SysMessageCreateReq struct { - Title string `json:"title" binding:"required"` // 消息标题 - ContentTemplate string `json:"content_template" binding:"required"` // 消息内容 - Level uint8 `json:"level" binding:"required,oneof=1 2 3"` // 消息等级:1=普通,2=重要,3=紧急 - DisplayMode uint8 `json:"display_mode" binding:"required,oneof=1 2 3"` // 展示方式:1=弹窗,2=滚动,3=弹窗+滚动 - StartTime string `json:"start_time,omitempty"` // 公告生效时间 - EndTime string `json:"end_time,omitempty"` // 公告失效时间 - Status uint8 `json:"status" binding:"required,oneof=0 1"` // 状态:0=草稿,1=启用 - TargetType uint8 `json:"target_type" binding:"required,oneof=1 2 3"` // 接收对象类型:1=全员,2=角色,3=用户 - TargetID int64 `json:"target_id,omitempty"` // 对象ID(角色ID/用户ID) + Title string `json:"title" binding:"required"` // 消息标题 + ContentTemplate string `json:"content_template" binding:"required"` // 消息内容 + Level uint8 `json:"level" binding:"required,oneof=1 2 3"` // 消息等级:1=普通,2=重要,3=紧急 + DisplayMode uint8 `json:"display_mode" binding:"required,oneof=1 2 3"` // 展示方式:1=弹窗,2=滚动,3=弹窗+滚动 + StartTime string `json:"start_time,omitempty"` // 公告生效时间 + EndTime string `json:"end_time,omitempty"` // 公告失效时间 + Status uint8 `json:"status"` // 状态:0=草稿,1=启用 + TargetType uint8 `json:"target_type" binding:"required,oneof=1 2 3"` // 接收对象类型:1=全员,2=角色,3=用户 + TargetIDs []int64 `json:"target_id,omitempty"` // 对象ID(角色ID/用户ID) } // SysMessageCreateResp 创建成功响应 @@ -99,7 +110,7 @@ type SysMessageEditReq struct { DisplayMode uint8 `json:"display_mode" binding:"required,oneof=1 2 3"` // 展示模式 StartTime string `json:"start_time"` // 生效时间(可空) EndTime string `json:"end_time"` // 失效时间(可空) - Status uint8 `json:"status" binding:"required,oneof=0 1"` // 状态:0 草稿、1 启用 + Status uint8 `json:"status"` // 状态:0 草稿、1 启用 TargetType uint8 `json:"target_type" binding:"required,oneof=1 2 3"` // 接收对象类型:1=全员,2=角色,3=用户 TargetID int64 `json:"target_id,omitempty"` // 接收对象ID } @@ -135,6 +146,8 @@ type SysAnnouncementItem struct { StartTime *time.Time `json:"start_time,omitempty"` // 公告生效时间 EndTime *time.Time `json:"end_time,omitempty"` // 公告失效时间 Status uint8 `json:"status"` // 状态:0=草稿,1=启用,2=禁用,3=已过期 + TargetType uint8 `json:"target_type"` // 接收对象类型:1=全员,2=角色,3=用户 + TargetID []int64 `json:"target_id,omitempty"` // 对象ID(角色ID/用户ID) } // SysMessageSetStatusReq 设置公告消息状态 @@ -148,19 +161,55 @@ type SysMessageDeleteReq struct { ID uint `json:"id" binding:"required"` } +// SysMessageDetailReq 公告详情请求 +type SysMessageDetailReq struct { + ID uint `json:"id" binding:"required"` +} + +// SysMessageDetailResp 公告详情响应 +type SysMessageDetailResp struct { + ID uint `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + Level uint8 `json:"level"` + DisplayMode uint8 `json:"display_mode"` + Status uint8 `json:"status"` + StartTime *time.Time `json:"start_time,omitempty"` // 公告生效时间 + EndTime *time.Time `json:"end_time,omitempty"` // 公告失效时间 + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Target *SystemMessageTarget `json:"target,omitempty"` // 接收对象信息 +} + // UserMessageListReq 用户消息列表-入参 type UserMessageListReq struct { - IsRead int `json:"is_read"` // 0-未读;1-已读 + IsRead int `json:"is_read"` // 0-全部;1-已读;2-未读 Page int `json:"page"` // 页码 PageSize int `json:"page_size"` // 每页数量 } // UserMessageListResp 用户消息列表-出参 type UserMessageListResp struct { - List []SystemUserMessage `json:"list"` - Total int64 `json:"total"` - Page int `json:"page"` - PageSize int `json:"page_size"` + List []UserMessageDetailRespItem `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"page_size"` +} + +type UserMessageDetailRespItem struct { + ID uint `json:"id"` + UserID int64 `json:"user_id"` // 接收用户ID + MessageConfigID uint `json:"message_config_id"` // 对应消息配置ID,可为空(被删时保留记录) + MessageType uint8 `json:"message_type"` // 消息类型:1=公告,2=业务 + Title string `json:"title"` // 消息标题快照 + Content string `json:"content"` // 渲染后内容 + IsRead bool `json:"is_read"` // 是否阅读 + ReadTime *time.Time `json:"read_time,omitempty"` // 阅读时间 + IsVisible bool `json:"is_visible"` // 是否展示给用户 + Level uint8 `json:"level,omitempty"` // 公告消息字段,消息等级:1=普通,2=重要,3=紧急 + DisplayMode uint8 `json:"display_mode,omitempty"` // 公告消息字段,展示方式:1=弹窗,2=滚动,3=弹窗+滚动 + StartTime *time.Time `json:"start_time,omitempty"` // 公告消息字段,公告生效时间 + EndTime *time.Time `json:"end_time,omitempty"` // 公告消息字段,公告失效时间 } // UserMessageSetStatusReq 用户阅读消息-入参 @@ -168,3 +217,82 @@ type UserMessageSetStatusReq struct { ID uint `json:"id"` // 消息ID(可选,read_all 为 false 时必传) ReadAll bool `json:"read_all"` // 是否设为全部已读 } + +// BusMessageCreateReq 新增业务消息-入参 +type BusMessageCreateReq struct { + Title string `json:"title" binding:"required"` // 消息标题 + ContentTemplate string `json:"content_template" binding:"required"` // 消息模板 + BizType string `json:"biz_type" binding:"required"` // 业务类型 + Event string `json:"event" binding:"required"` // 事件类型 + TargetType uint8 `json:"target_type" binding:"required"` // 接收对象类型:1全员,2角色,3用户 + TargetID int64 `json:"target_id,omitempty"` // 接收对象ID(角色ID或用户ID) + Status uint8 `json:"status" binding:"required"` // 消息状态:0草稿,1启用,2禁用 +} + +// BusMessageCreateResp 新增业务消息-出参 +type BusMessageCreateResp struct { + ID uint `json:"id"` +} + +// BusMessageListReq 业务消息列表 - 入参 +type BusMessageListReq struct { + BizType string `json:"biz_type"` // 业务类型 + Event string `json:"event"` // 事件类型 + Status uint8 `json:"status"` // 状态:1启用,2禁用 + RoleID []int64 `json:"role_id"` // 角色ID + Page int `json:"page"` // 页码 + PageSize int `json:"page_size"` // 每页条数 +} + +// BusMessageListResp 业务消息列表 - 出参 +type BusMessageListResp struct { + List []BusMessageItem `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"page_size"` +} + +// BusMessageItem 单条业务消息展示项 +type BusMessageItem struct { + ID uint32 `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ContentTemplate string `json:"content_template"` // 消息模版 + BizType string `json:"biz_type"` // 业务类型 + Event string `json:"event"` // 事件类型 + Status uint8 `json:"status"` // 状态:1启用,2禁用 + RoleID []int64 `json:"role_id"` // 角色ID +} + +// ValidateBizEventAndTemplate 校验业务类型、事件类型以及模板变量是否合法 +func ValidateBizEventAndTemplate(bizType, event, contentTemplate string) error { + var registry SystemEventRegistry + err := orm.Eloquent.Where("biz_type = ? AND event = ? AND is_enabled = ?", bizType, event, true).First(®istry).Error + if err != nil { + return errors.New("业务类型或事件类型无效") + } + + // 支持结构体数组格式:[{"key":"user_name","desc":"用户名"}] + var allowedItems []TemplateVarItem + if err := json.Unmarshal([]byte(registry.TemplateVariables), &allowedItems); err != nil { + return errors.New("事件注册表中变量格式非法") + } + + // 提取合法 key + allowedMap := make(map[string]bool) + for _, item := range allowedItems { + allowedMap[item.Key] = true + } + + // 提取模板中的变量名 + varRegex := regexp.MustCompile(`\{\{\s*(\w+)\s*\}\}`) + matches := varRegex.FindAllStringSubmatch(contentTemplate, -1) + for _, match := range matches { + varName := match[1] + if !allowedMap[varName] { + return fmt.Errorf("模板中使用了未注册变量:%s", varName) + } + } + + return nil +} diff --git a/app/admin/router/message.go b/app/admin/router/message.go index 35c0b71..a415625 100644 --- a/app/admin/router/message.go +++ b/app/admin/router/message.go @@ -16,6 +16,7 @@ func registerMessageCenterRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWT r1.POST("set_status", message.SysMessageSetStatus) // 启用/禁用 r1.POST("delete", message.SysMessageDelete) // 删除 r1.POST("list", message.SysMessageList) // 查询列表 + r1.POST("detail", message.SysMessageDetail) // 查询详情 } // 业务消息(列表、新增、编辑、启用/禁用、删除) diff --git a/docs/docs.go b/docs/docs.go index 40c00b1..60f738c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -15,6 +15,39 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/api/v1/bus_message/create": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "消息中心,V1.5.0" + ], + "summary": "新增业务消息", + "parameters": [ + { + "description": "新增业务消息模型", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.BusMessageCreateReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.BusMessageCreateResp" + } + } + } + } + }, "/api/v1/cashier/create": { "post": { "consumes": [ @@ -7333,6 +7366,39 @@ const docTemplate = `{ } } }, + "/api/v1/sys_message/detail": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "消息中心,V1.5.0" + ], + "summary": "公告消息详情", + "parameters": [ + { + "description": "公告消息ID", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.SysMessageDetailReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.SysMessageDetailResp" + } + } + } + } + }, "/api/v1/sys_message/edit": { "post": { "consumes": [ @@ -8504,6 +8570,55 @@ const docTemplate = `{ } } }, + "models.BusMessageCreateReq": { + "type": "object", + "required": [ + "biz_type", + "content_template", + "event", + "status", + "target_type", + "title" + ], + "properties": { + "biz_type": { + "description": "业务类型", + "type": "string" + }, + "content_template": { + "description": "消息模板", + "type": "string" + }, + "event": { + "description": "事件类型", + "type": "string" + }, + "status": { + "description": "消息状态:0草稿,1启用,2禁用", + "type": "integer" + }, + "target_id": { + "description": "接收对象ID(角色ID或用户ID)", + "type": "integer" + }, + "target_type": { + "description": "接收对象类型:1全员,2角色,3用户", + "type": "integer" + }, + "title": { + "description": "消息标题", + "type": "string" + } + } + }, + "models.BusMessageCreateResp": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + }, "models.BusinessSummaryItem": { "type": "object", "properties": { @@ -21270,6 +21385,14 @@ const docTemplate = `{ "description": "状态:0=草稿,1=启用,2=禁用,3=已过期", "type": "integer" }, + "target": { + "description": "接收对象信息(用于前端回显)", + "allOf": [ + { + "$ref": "#/definitions/models.SystemMessageTarget" + } + ] + }, "title": { "description": "消息标题", "type": "string" @@ -21581,7 +21704,6 @@ const docTemplate = `{ "content_template", "display_mode", "level", - "status", "target_type", "title" ], @@ -21618,11 +21740,7 @@ const docTemplate = `{ }, "status": { "description": "状态:0=草稿,1=启用", - "type": "integer", - "enum": [ - 0, - 1 - ] + "type": "integer" }, "target_id": { "description": "对象ID(角色ID/用户ID)", @@ -21662,6 +21780,62 @@ const docTemplate = `{ } } }, + "models.SysMessageDetailReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + } + } + }, + "models.SysMessageDetailResp": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "display_mode": { + "type": "integer" + }, + "end_time": { + "description": "公告失效时间", + "type": "string" + }, + "id": { + "type": "integer" + }, + "level": { + "type": "integer" + }, + "start_time": { + "description": "公告生效时间", + "type": "string" + }, + "status": { + "type": "integer" + }, + "target": { + "description": "接收对象信息", + "allOf": [ + { + "$ref": "#/definitions/models.SystemMessageTarget" + } + ] + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "models.SysMessageEditReq": { "type": "object", "required": [ @@ -21669,7 +21843,6 @@ const docTemplate = `{ "display_mode", "id", "level", - "status", "target_type", "title" ], @@ -21710,11 +21883,7 @@ const docTemplate = `{ }, "status": { "description": "状态:0 草稿、1 启用", - "type": "integer", - "enum": [ - 0, - 1 - ] + "type": "integer" }, "target_id": { "description": "接收对象ID", @@ -22276,13 +22445,9 @@ const docTemplate = `{ } } }, - "models.SystemUserMessage": { + "models.SystemMessageTarget": { "type": "object", "properties": { - "content": { - "description": "渲染后内容", - "type": "string" - }, "createdAt": { "description": "创建时间", "type": "string" @@ -22291,37 +22456,21 @@ const docTemplate = `{ "description": "数据库记录编号", "type": "integer" }, - "is_read": { - "description": "是否阅读", - "type": "boolean" - }, - "is_visible": { - "description": "是否展示给用户", - "type": "boolean" - }, "message_config_id": { - "description": "对应消息配置ID,可为空(被删时保留记录)", + "description": "对应消息配置 ID", "type": "integer" }, - "message_type": { - "description": "消息类型:1=公告,2=业务", + "target_id": { + "description": "对象ID(角色ID/用户ID)", "type": "integer" }, - "read_time": { - "description": "阅读时间", - "type": "string" - }, - "title": { - "description": "消息标题快照", - "type": "string" + "target_type": { + "description": "接收对象类型:1=全员,2=角色,3=用户", + "type": "integer" }, "updatedAt": { "description": "更新时间", "type": "string" - }, - "user_id": { - "description": "接收用户ID", - "type": "integer" } } }, @@ -22790,11 +22939,67 @@ const docTemplate = `{ } } }, + "models.UserMessageDetailRespItem": { + "type": "object", + "properties": { + "content": { + "description": "渲染后内容", + "type": "string" + }, + "display_mode": { + "description": "公告消息字段,展示方式:1=弹窗,2=滚动,3=弹窗+滚动", + "type": "integer" + }, + "end_time": { + "description": "公告消息字段,公告失效时间", + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_read": { + "description": "是否阅读", + "type": "boolean" + }, + "is_visible": { + "description": "是否展示给用户", + "type": "boolean" + }, + "level": { + "description": "公告消息字段,消息等级:1=普通,2=重要,3=紧急", + "type": "integer" + }, + "message_config_id": { + "description": "对应消息配置ID,可为空(被删时保留记录)", + "type": "integer" + }, + "message_type": { + "description": "消息类型:1=公告,2=业务", + "type": "integer" + }, + "read_time": { + "description": "阅读时间", + "type": "string" + }, + "start_time": { + "description": "公告消息字段,公告生效时间", + "type": "string" + }, + "title": { + "description": "消息标题快照", + "type": "string" + }, + "user_id": { + "description": "接收用户ID", + "type": "integer" + } + } + }, "models.UserMessageListReq": { "type": "object", "properties": { "is_read": { - "description": "0-未读;1-已读", + "description": "0-全部;1-已读;2-未读", "type": "integer" }, "page": { @@ -22813,7 +23018,7 @@ const docTemplate = `{ "list": { "type": "array", "items": { - "$ref": "#/definitions/models.SystemUserMessage" + "$ref": "#/definitions/models.UserMessageDetailRespItem" } }, "page": { diff --git a/docs/swagger.json b/docs/swagger.json index f85b695..b968669 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -4,6 +4,39 @@ "contact": {} }, "paths": { + "/api/v1/bus_message/create": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "消息中心,V1.5.0" + ], + "summary": "新增业务消息", + "parameters": [ + { + "description": "新增业务消息模型", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.BusMessageCreateReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.BusMessageCreateResp" + } + } + } + } + }, "/api/v1/cashier/create": { "post": { "consumes": [ @@ -7322,6 +7355,39 @@ } } }, + "/api/v1/sys_message/detail": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "消息中心,V1.5.0" + ], + "summary": "公告消息详情", + "parameters": [ + { + "description": "公告消息ID", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.SysMessageDetailReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.SysMessageDetailResp" + } + } + } + } + }, "/api/v1/sys_message/edit": { "post": { "consumes": [ @@ -8493,6 +8559,55 @@ } } }, + "models.BusMessageCreateReq": { + "type": "object", + "required": [ + "biz_type", + "content_template", + "event", + "status", + "target_type", + "title" + ], + "properties": { + "biz_type": { + "description": "业务类型", + "type": "string" + }, + "content_template": { + "description": "消息模板", + "type": "string" + }, + "event": { + "description": "事件类型", + "type": "string" + }, + "status": { + "description": "消息状态:0草稿,1启用,2禁用", + "type": "integer" + }, + "target_id": { + "description": "接收对象ID(角色ID或用户ID)", + "type": "integer" + }, + "target_type": { + "description": "接收对象类型:1全员,2角色,3用户", + "type": "integer" + }, + "title": { + "description": "消息标题", + "type": "string" + } + } + }, + "models.BusMessageCreateResp": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + }, "models.BusinessSummaryItem": { "type": "object", "properties": { @@ -21259,6 +21374,14 @@ "description": "状态:0=草稿,1=启用,2=禁用,3=已过期", "type": "integer" }, + "target": { + "description": "接收对象信息(用于前端回显)", + "allOf": [ + { + "$ref": "#/definitions/models.SystemMessageTarget" + } + ] + }, "title": { "description": "消息标题", "type": "string" @@ -21570,7 +21693,6 @@ "content_template", "display_mode", "level", - "status", "target_type", "title" ], @@ -21607,11 +21729,7 @@ }, "status": { "description": "状态:0=草稿,1=启用", - "type": "integer", - "enum": [ - 0, - 1 - ] + "type": "integer" }, "target_id": { "description": "对象ID(角色ID/用户ID)", @@ -21651,6 +21769,62 @@ } } }, + "models.SysMessageDetailReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + } + } + }, + "models.SysMessageDetailResp": { + "type": "object", + "properties": { + "content": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "display_mode": { + "type": "integer" + }, + "end_time": { + "description": "公告失效时间", + "type": "string" + }, + "id": { + "type": "integer" + }, + "level": { + "type": "integer" + }, + "start_time": { + "description": "公告生效时间", + "type": "string" + }, + "status": { + "type": "integer" + }, + "target": { + "description": "接收对象信息", + "allOf": [ + { + "$ref": "#/definitions/models.SystemMessageTarget" + } + ] + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "models.SysMessageEditReq": { "type": "object", "required": [ @@ -21658,7 +21832,6 @@ "display_mode", "id", "level", - "status", "target_type", "title" ], @@ -21699,11 +21872,7 @@ }, "status": { "description": "状态:0 草稿、1 启用", - "type": "integer", - "enum": [ - 0, - 1 - ] + "type": "integer" }, "target_id": { "description": "接收对象ID", @@ -22265,13 +22434,9 @@ } } }, - "models.SystemUserMessage": { + "models.SystemMessageTarget": { "type": "object", "properties": { - "content": { - "description": "渲染后内容", - "type": "string" - }, "createdAt": { "description": "创建时间", "type": "string" @@ -22280,37 +22445,21 @@ "description": "数据库记录编号", "type": "integer" }, - "is_read": { - "description": "是否阅读", - "type": "boolean" - }, - "is_visible": { - "description": "是否展示给用户", - "type": "boolean" - }, "message_config_id": { - "description": "对应消息配置ID,可为空(被删时保留记录)", + "description": "对应消息配置 ID", "type": "integer" }, - "message_type": { - "description": "消息类型:1=公告,2=业务", + "target_id": { + "description": "对象ID(角色ID/用户ID)", "type": "integer" }, - "read_time": { - "description": "阅读时间", - "type": "string" - }, - "title": { - "description": "消息标题快照", - "type": "string" + "target_type": { + "description": "接收对象类型:1=全员,2=角色,3=用户", + "type": "integer" }, "updatedAt": { "description": "更新时间", "type": "string" - }, - "user_id": { - "description": "接收用户ID", - "type": "integer" } } }, @@ -22779,11 +22928,67 @@ } } }, + "models.UserMessageDetailRespItem": { + "type": "object", + "properties": { + "content": { + "description": "渲染后内容", + "type": "string" + }, + "display_mode": { + "description": "公告消息字段,展示方式:1=弹窗,2=滚动,3=弹窗+滚动", + "type": "integer" + }, + "end_time": { + "description": "公告消息字段,公告失效时间", + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_read": { + "description": "是否阅读", + "type": "boolean" + }, + "is_visible": { + "description": "是否展示给用户", + "type": "boolean" + }, + "level": { + "description": "公告消息字段,消息等级:1=普通,2=重要,3=紧急", + "type": "integer" + }, + "message_config_id": { + "description": "对应消息配置ID,可为空(被删时保留记录)", + "type": "integer" + }, + "message_type": { + "description": "消息类型:1=公告,2=业务", + "type": "integer" + }, + "read_time": { + "description": "阅读时间", + "type": "string" + }, + "start_time": { + "description": "公告消息字段,公告生效时间", + "type": "string" + }, + "title": { + "description": "消息标题快照", + "type": "string" + }, + "user_id": { + "description": "接收用户ID", + "type": "integer" + } + } + }, "models.UserMessageListReq": { "type": "object", "properties": { "is_read": { - "description": "0-未读;1-已读", + "description": "0-全部;1-已读;2-未读", "type": "integer" }, "page": { @@ -22802,7 +23007,7 @@ "list": { "type": "array", "items": { - "$ref": "#/definitions/models.SystemUserMessage" + "$ref": "#/definitions/models.UserMessageDetailRespItem" } }, "page": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 772fe20..fcbe734 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -367,6 +367,42 @@ definitions: required: - print_list_info type: object + models.BusMessageCreateReq: + properties: + biz_type: + description: 业务类型 + type: string + content_template: + description: 消息模板 + type: string + event: + description: 事件类型 + type: string + status: + description: 消息状态:0草稿,1启用,2禁用 + type: integer + target_id: + description: 接收对象ID(角色ID或用户ID) + type: integer + target_type: + description: 接收对象类型:1全员,2角色,3用户 + type: integer + title: + description: 消息标题 + type: string + required: + - biz_type + - content_template + - event + - status + - target_type + - title + type: object + models.BusMessageCreateResp: + properties: + id: + type: integer + type: object models.BusinessSummaryItem: properties: count: @@ -9615,6 +9651,10 @@ definitions: status: description: 状态:0=草稿,1=启用,2=禁用,3=已过期 type: integer + target: + allOf: + - $ref: '#/definitions/models.SystemMessageTarget' + description: 接收对象信息(用于前端回显) title: description: 消息标题 type: string @@ -9861,9 +9901,6 @@ definitions: type: string status: description: 状态:0=草稿,1=启用 - enum: - - 0 - - 1 type: integer target_id: description: 对象ID(角色ID/用户ID) @@ -9882,7 +9919,6 @@ definitions: - content_template - display_mode - level - - status - target_type - title type: object @@ -9898,6 +9934,42 @@ definitions: required: - id type: object + models.SysMessageDetailReq: + properties: + id: + type: integer + required: + - id + type: object + models.SysMessageDetailResp: + properties: + content: + type: string + created_at: + type: string + display_mode: + type: integer + end_time: + description: 公告失效时间 + type: string + id: + type: integer + level: + type: integer + start_time: + description: 公告生效时间 + type: string + status: + type: integer + target: + allOf: + - $ref: '#/definitions/models.SystemMessageTarget' + description: 接收对象信息 + title: + type: string + updated_at: + type: string + type: object models.SysMessageEditReq: properties: content_template: @@ -9928,9 +10000,6 @@ definitions: type: string status: description: 状态:0 草稿、1 启用 - enum: - - 0 - - 1 type: integer target_id: description: 接收对象ID @@ -9950,7 +10019,6 @@ definitions: - display_mode - id - level - - status - target_type - title type: object @@ -10347,41 +10415,26 @@ definitions: description: 用户名 type: string type: object - models.SystemUserMessage: + models.SystemMessageTarget: properties: - content: - description: 渲染后内容 - type: string createdAt: description: 创建时间 type: string id: description: 数据库记录编号 type: integer - is_read: - description: 是否阅读 - type: boolean - is_visible: - description: 是否展示给用户 - type: boolean message_config_id: - description: 对应消息配置ID,可为空(被删时保留记录) + description: 对应消息配置 ID type: integer - message_type: - description: 消息类型:1=公告,2=业务 + target_id: + description: 对象ID(角色ID/用户ID) + type: integer + target_type: + description: 接收对象类型:1=全员,2=角色,3=用户 type: integer - read_time: - description: 阅读时间 - type: string - title: - description: 消息标题快照 - type: string updatedAt: description: 更新时间 type: string - user_id: - description: 接收用户ID - type: integer type: object models.TableData: properties: @@ -10722,10 +10775,51 @@ definitions: description: 总条数 type: integer type: object + models.UserMessageDetailRespItem: + properties: + content: + description: 渲染后内容 + type: string + display_mode: + description: 公告消息字段,展示方式:1=弹窗,2=滚动,3=弹窗+滚动 + type: integer + end_time: + description: 公告消息字段,公告失效时间 + type: string + id: + type: integer + is_read: + description: 是否阅读 + type: boolean + is_visible: + description: 是否展示给用户 + type: boolean + level: + description: 公告消息字段,消息等级:1=普通,2=重要,3=紧急 + type: integer + message_config_id: + description: 对应消息配置ID,可为空(被删时保留记录) + type: integer + message_type: + description: 消息类型:1=公告,2=业务 + type: integer + read_time: + description: 阅读时间 + type: string + start_time: + description: 公告消息字段,公告生效时间 + type: string + title: + description: 消息标题快照 + type: string + user_id: + description: 接收用户ID + type: integer + type: object models.UserMessageListReq: properties: is_read: - description: 0-未读;1-已读 + description: 0-全部;1-已读;2-未读 type: integer page: description: 页码 @@ -10738,7 +10832,7 @@ definitions: properties: list: items: - $ref: '#/definitions/models.SystemUserMessage' + $ref: '#/definitions/models.UserMessageDetailRespItem' type: array page: type: integer @@ -11048,6 +11142,27 @@ definitions: info: contact: {} paths: + /api/v1/bus_message/create: + post: + consumes: + - application/json + parameters: + - description: 新增业务消息模型 + in: body + name: request + required: true + schema: + $ref: '#/definitions/models.BusMessageCreateReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.BusMessageCreateResp' + summary: 新增业务消息 + tags: + - 消息中心,V1.5.0 /api/v1/cashier/create: post: consumes: @@ -15593,6 +15708,27 @@ paths: summary: 删除公告消息 tags: - 消息中心,V1.5.0 + /api/v1/sys_message/detail: + post: + consumes: + - application/json + parameters: + - description: 公告消息ID + in: body + name: request + required: true + schema: + $ref: '#/definitions/models.SysMessageDetailReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.SysMessageDetailResp' + summary: 公告消息详情 + tags: + - 消息中心,V1.5.0 /api/v1/sys_message/edit: post: consumes: