mh_goadmin_server/app/admin/apis/message/sys_message.go
chenlin 70b823b413 1、优化零售明细相关支付金额不能为负值的缺陷,根据实际情况展示;
2、优化门店销售对比,时间改成审核时间;避免跨月订单统计不准确;
3、新增业务消息相关接口;
2025-07-01 11:54:23 +08:00

630 lines
19 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 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, "查询成功")
}