package models import ( "bytes" "encoding/json" "errors" "fmt" "github.com/codinl/go-logger" orm "go-admin/common/global" "regexp" "text/template" "time" ) const ( AnnouncementMessageType = 1 // 公告消息 BusinessMessageType = 2 // 业务消息 TargetTypeByAll = 1 // 接收对象类型:1=全员 TargetTypeByRole = 2 // 接收对象类型:2=角色 TargetTypeByUser = 3 // 接收对象类型:3=用户 MessageStatusOnDraft = 0 // 草稿 MessageStatusOnStart = 1 // 启用 MessageStatusOnDisable = 2 // 禁用 MessageStatusOnExpire = 3 // 已过期 MessageStart = 1 // 启用 MessageStop = 2 // 禁用 BizTypeOnAllot = "stock_transfer" // 库存调拨 EventOnAllot = "wait_receive" //待收货 ) // SystemMessageConfig 消息配置主表(公告 + 业务统一) type SystemMessageConfig struct { Model MessageType uint8 `json:"message_type"` // 消息类型:1=公告,2=业务 Title string `json:"title"` // 消息标题 ContentTemplate string `json:"content_template"` // 消息模板内容,支持{{变量}} Level uint8 `json:"level"` // 消息等级:1=普通,2=重要,3=紧急 DisplayMode uint8 `json:"display_mode"` // 展示方式:1=弹窗,2=滚动,3=弹窗+滚动 StartTime *time.Time `json:"start_time,omitempty"` // 公告生效时间 EndTime *time.Time `json:"end_time,omitempty"` // 公告失效时间 BizType string `json:"biz_type,omitempty"` // 业务类型 Event string `json:"event,omitempty"` // 事件类型 Status uint8 `json:"status"` // 状态:0=草稿,1=启用,2=禁用,3=已过期 } // SystemMessageTarget 接收对象配置表(支持角色/部门/用户/全员) type SystemMessageTarget struct { Model MessageConfigID uint `json:"message_config_id"` // 对应消息配置 ID TargetType uint8 `json:"target_type"` // 接收对象类型:1=全员,2=角色,3=用户 TargetID int64 `json:"target_id,omitempty"` // 对象ID(角色ID/用户ID) } // SystemEventRegistry 业务事件注册表(用于事件枚举管理) type SystemEventRegistry struct { Model BizType string `json:"biz_type"` // 业务类型 Event string `json:"event"` // 事件名 BizName string `json:"biz_name"` // 业务名称(展示用) EventName string `json:"event_name"` // 事件名称(展示用) Description string `json:"description"` // 说明 TemplateVariables string `json:"template_variables"` // 可用变量 JSON 字符串 IsEnabled bool `json:"is_enabled"` // 是否启用 } type TemplateVarItem struct { Key string `json:"key"` Desc string `json:"desc"` } // SystemUserMessage 用户收到的消息记录表(实际发送记录) type SystemUserMessage struct { Model 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"` // 是否展示给用户 } // 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"` // 状态: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) } // SysMessageCreateResp 创建成功响应 type SysMessageCreateResp struct { ID uint32 `json:"id"` } // SysMessageEditReq 编辑公告消息-入参 type SysMessageEditReq struct { ID uint `json:"id" binding:"required"` // 公告ID(必须) Title string `json:"title" binding:"required"` // 标题 ContentTemplate string `json:"content_template" binding:"required"` // 内容模板 Level uint8 `json:"level" binding:"required,oneof=1 2 3"` // 重要等级 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"` // 状态:0 草稿、1 启用 TargetType uint8 `json:"target_type" binding:"required,oneof=1 2 3"` // 接收对象类型:1=全员,2=角色,3=用户 TargetID []int64 `json:"target_id,omitempty"` // 接收对象ID } // SysMessageListReq 公告消息列表-入参 type SysMessageListReq struct { Title string `form:"title"` // 消息标题 Level uint8 `form:"level"` // 消息等级:1=普通,2=重要,3=紧急 DisplayMode uint8 `form:"display_mode"` // 展示方式:1=弹窗,2=滚动,3=弹窗+滚动 Status uint8 `form:"status"` // 状态:0=草稿,1=启用,2=禁用,3=已过期 TargetType uint8 `form:"target_type"` // 从接收对象配置表中筛选 Page int `form:"page_index,default=1"` // 页码 PageSize int `form:"page_size,default=10"` // 每页条数 } // SysMessageListResp 公告消息列表-出参 type SysMessageListResp struct { List []SysAnnouncementItem `json:"list"` Total int64 `json:"total"` // 总条数 Page int `json:"page_index"` // 页码 PageSize int `json:"page_size"` // 每页条数 } // SysAnnouncementItem 公告消息展示结构体 type SysAnnouncementItem struct { ID uint32 `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Title string `json:"title"` // 消息标题 ContentTemplate string `json:"content_template"` // 消息内容 Level uint8 `json:"level"` // 消息等级:1=普通,2=重要,3=紧急 DisplayMode uint8 `json:"display_mode"` // 展示方式:1=弹窗,2=滚动,3=弹窗+滚动 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 设置公告消息状态 type SysMessageSetStatusReq struct { ID uint `json:"id" binding:"required"` Status uint8 `json:"status" binding:"required"` // 目标状态:1=启用,2=禁用 } // SysMessageDeleteReq 删除公告消息 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"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Title string `json:"title"` // 消息标题 Content string `json:"content"` // 消息内容 Level uint8 `json:"level"` // 消息等级:1=普通,2=重要,3=紧急 DisplayMode uint8 `json:"display_mode"` // 展示方式:1=弹窗,2=滚动,3=弹窗+滚动 Status uint8 `json:"status"` // 状态:0=草稿,1=启用,2=禁用,3=已过期 StartTime *time.Time `json:"start_time,omitempty"` // 公告生效时间 EndTime *time.Time `json:"end_time,omitempty"` // 公告失效时间 TargetType uint8 `json:"target_type"` // 接收对象类型:1=全员,2=角色,3=用户 TargetID []int64 `json:"target_id,omitempty"` // 对象ID(角色ID/用户ID) } // UserMessageListReq 用户消息列表-入参 type UserMessageListReq struct { IsRead int `json:"is_read"` // 0-全部;1-已读;2-未读 Page int `json:"page_index"` // 页码 PageSize int `json:"page_size"` // 每页数量 } // UserMessageListResp 用户消息列表-出参 type UserMessageListResp struct { List []UserMessageDetailRespItem `json:"list"` Total int64 `json:"total"` Page int `json:"page_index"` PageSize int `json:"page_size"` } type UserMessageDetailRespItem struct { ID uint `json:"id"` CreatedAt time.Time `json:"created_at"` // 接收时间 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 用户阅读消息-入参 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"` // 事件类型 RoleID []int64 `json:"role_id" binding:"required"` // 接收对象:角色ID列表 Status uint8 `json:"status" binding:"required"` // 消息状态: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_index"` // 页码 PageSize int `json:"page_size"` // 每页条数 } // BusMessageListResp 业务消息列表 - 出参 type BusMessageListResp struct { List []BusMessageItem `json:"list"` Total int64 `json:"total"` Page int `json:"page_index"` 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"` Title string `json:"title"` // 消息标题 ContentTemplate string `json:"content_template"` // 消息模版 BizType string `json:"biz_type"` // 业务类型 BizName string `json:"biz_name"` // 业务名称 Event string `json:"event"` // 事件类型 EventName string `json:"event_name"` // 事件名称 Status uint8 `json:"status"` // 状态:1启用,2禁用 RoleID []int64 `json:"role_id"` // 角色ID } // BusMessageEditReq 编辑业务消息 - 入参 type BusMessageEditReq struct { ID uint32 `json:"id" binding:"required"` // 消息ID Title string `json:"title" binding:"required"` // 消息标题 BizType string `json:"biz_type" binding:"required"` // 业务类型 Event string `json:"event" binding:"required"` // 事件类型 ContentTemplate string `json:"content_template" binding:"required"` // 消息模版 Status uint8 `json:"status" binding:"required"` // 状态:1启用,2禁用 RoleID []int64 `json:"role_id" binding:"required"` // 接收对象:角色ID列表 } // BusMessageSetStatusReq 设置业务消息状态 type BusMessageSetStatusReq struct { ID uint `json:"id" binding:"required"` Status uint8 `json:"status" binding:"required"` // 目标状态:1=启用,2=禁用 } // BusMessageDeleteReq 删除业务消息 type BusMessageDeleteReq struct { ID uint `json:"id" binding:"required"` } // BusMessageDetailReq 业务消息详情 - 入参 type BusMessageDetailReq struct { ID uint64 `json:"id" binding:"required"` // 业务消息ID } // BizTypeListReq 查询业务类型列表 - 入参 type BizTypeListReq struct { Page int `json:"page_index"` PageSize int `json:"page_size"` } // BizTypeListResp 查询业务类型列表 - 出参 type BizTypeListResp struct { List []BizTypeItem `json:"list"` // 业务类型列表 Total int64 `json:"total"` // 总数 Page int `json:"page_index"` // 当前页 PageSize int `json:"page_size"` // 页大小 } // BizTypeItem 单条业务类型展示项 type BizTypeItem struct { BizType string `json:"biz_type"` // 业务类型编码 BizName string `json:"biz_name"` // 业务名称 } // EventListReq 查询事件类型列表 - 入参 type EventListReq struct { BizType string `json:"biz_type" binding:"required"` Page int `json:"page_index"` PageSize int `json:"page_size"` } // EventListResp 查询事件类型列表 - 出参 type EventListResp struct { List []EventItem `json:"list"` Total int64 `json:"total"` Page int `json:"page_index"` PageSize int `json:"page_size"` } // EventItem 单条事件类型展示项 type EventItem struct { Event string `json:"event"` // 事件类型编码 EventName string `json:"event_name"` // 业务类型编码 } // TemplateVarReq 查询模板变量字段列表 - 入参 type TemplateVarReq struct { BizType string `json:"biz_type" binding:"required"` // 业务类型 Event string `json:"event" binding:"required"` // 事件类型 Page int `json:"page_index"` // 当前页 PageSize int `json:"page_size"` // 每页大小 } // TemplateVarListResp 查询模板变量字段列表 - 出参 type TemplateVarListResp struct { List []TemplateVariable `json:"list"` // 模板变量列表 Total int64 `json:"total"` // 总数 Page int `json:"page_index"` // 当前页 PageSize int `json:"page_size"` // 每页条数 } // TemplateVariable 模板变量项 type TemplateVariable struct { Key string `json:"key"` // 模板变量键 Desc string `json:"desc"` // 模板变量描述 } // UserMessageDeleteReq 用户消息-删除 请求参数 type UserMessageDeleteReq struct { IDs []uint `json:"ids"` // 消息ID列表(DeleteAll=false时必填) DeleteAll bool `json:"delete_all"` // 是否删除全部 } // 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 } // TriggerBizEvent 业务消息-触发消息模版 func TriggerBizEvent(bizType, event string, inputVars map[string]interface{}, optionalReceivers ...uint32) error { // 1. 查找启用的消息配置 var cfg SystemMessageConfig err := orm.Eloquent.Where("message_type = ? AND biz_type = ? AND event = ? AND status = 1", BusinessMessageType, bizType, event).Find(&cfg).Error if err != nil { return nil // 无需发送 } // 2. 渲染模板内容 content, err := RenderTemplate(cfg.ContentTemplate, inputVars) if err != nil { return fmt.Errorf("模板渲染失败: %w", err) } // 查找门店ID var storeId uint32 if len(optionalReceivers) > 0 { if event == EventOnAllot { // 库存调拨-待收货消息 storeId = optionalReceivers[0] } } else { fmt.Println("没有传入 optionalReceivers,使用默认配置") } // 3. 查找接收用户 receiverIDs, err := GetTargetUserIds(cfg.ID, storeId) if err != nil { return fmt.Errorf("获取接收人失败: %w", err) } // 4. 写入 user_message 表 var msgs []SystemUserMessage for _, uid := range receiverIDs { msgs = append(msgs, SystemUserMessage{ UserID: uid, MessageConfigID: uint(cfg.ID), MessageType: BusinessMessageType, Title: cfg.Title, Content: content, IsRead: false, IsVisible: true, }) } return orm.Eloquent.Create(&msgs).Error } // GetTargetUserIds 获取接收用户函数模板(支持角色) func GetTargetUserIds(configID uint32, storeId uint32) ([]int64, error) { // 1.查询角色绑定 var targets []SystemMessageTarget err := orm.Eloquent.Where("message_config_id = ? AND target_type = ?", configID, TargetTypeByRole). Find(&targets).Error if err != nil { return nil, err } // 2.查询该模版配置的所有角色 var roleIDs []int64 for _, t := range targets { roleIDs = append(roleIDs, t.TargetID) } // 3.查询该角色下的用户(包含字段 uid, store_data) var users []SysUser err = orm.Eloquent.Table("sys_user"). Where("role_id IN ? AND deleted_at IS NULL", roleIDs). Find(&users).Error if err != nil { return nil, err } // 如果 storeId == 0,直接返回所有 uid if storeId == 0 { var allUserIDs []int64 for _, user := range users { allUserIDs = append(allUserIDs, int64(user.Uid)) } return allUserIDs, nil } // 4.筛选出门店有效用户 now := time.Now() var validUserIDs []int64 for _, user := range users { var storeList []StoreInfo if err := json.Unmarshal([]byte(user.StoreData), &storeList); err != nil { continue } for _, store := range storeList { // 解析时间 expireTime, err := time.Parse(StoreDateTimeFormat, store.ExpireTime) if err != nil { continue } // 包含当天有效时间 expireTime = expireTime.Add(24*time.Hour - time.Second) if store.StoreID == int(storeId) && expireTime.After(now) { validUserIDs = append(validUserIDs, int64(user.UserId)) break } } } return validUserIDs, nil } // RenderTemplate 渲染模版内容 func RenderTemplate(templateStr string, data map[string]interface{}) (string, error) { // 创建模板实例 tmpl, err := template.New("msg").Option("missingkey=zero").Parse(templateStr) if err != nil { return "", err } // 渲染 var buf bytes.Buffer err = tmpl.Execute(&buf, data) if err != nil { return "", err } return buf.String(), nil } // CheckExpiredSystemMessages 检查公告消息是否过期,如果过期则更新状态为3 func CheckExpiredSystemMessages() { now := time.Now() // 查询所有状态为启用(1)、类型为公告(1)、且 EndTime 不为空、已过期的消息 var expiredMessages []SystemMessageConfig err := orm.Eloquent. Table("system_message_config"). Where("message_type = ? AND status = ? AND end_time IS NOT NULL AND end_time < ?", AnnouncementMessageType, MessageStatusOnStart, now). Find(&expiredMessages).Error if err != nil { logger.Errorf("Failed to query expired messages: %v", err) return } if len(expiredMessages) == 0 { logger.Info("No expired announcements found.") return } // 遍历并更新状态 for _, msg := range expiredMessages { err := orm.Eloquent. Table("system_message_config"). Where("id = ?", msg.ID). Update("status", MessageStatusOnExpire).Error if err != nil { logger.Errorf("Failed to update status for message ID %d: %v", msg.ID, err) } else { logger.Infof("Message ID %d marked as expired.", msg.ID) } } }