mh_goadmin_server/app/admin/models/message.go
chenlin fe46d911a1 1、新增用户消息删除接口;
2、保证金审核列表增加"审核时间"筛选;
3、消息相关接口统一页码和每页数量的字段;
4、用户消息列表接口增加"接收时间"字段;
2025-07-10 14:53:24 +08:00

570 lines
23 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(&registry).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)
}
}
}