Files

440 lines
14 KiB
Go
Raw Permalink 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 gen
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2026 LiXiujie
// @Author Lxj <xxx@example.com>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
// @AutoGenerate Version
package common
import (
"context"
"fmt"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/exportcenter"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/websocket"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
"strings"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type sCommonTMessageLog struct{}
func NewCommonTMessageLog() *sCommonTMessageLog {
return &sCommonTMessageLog{}
}
func init() {
service.RegisterCommonTMessageLog(NewCommonTMessageLog())
}
// Model 消息日志ORM模型
func (s *sCommonTMessageLog) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
if len(option) == 0 {
option = append(option, &handler.Option{
FilterTenant: true,
FilterAuth: true,
})
}
return handler.Model(dao.TMessageLog.Ctx(ctx), option...)
}
// List 获取消息日志列表
func (s *sCommonTMessageLog) List(ctx context.Context, in *model.TMessageLogListParam) (list []*model.TMessageLogListModel, totalCount int, err error) {
mod := dao.TMessageLog.Ctx(ctx)
deptId := contexts.GetDeptId(ctx)
// 字段过滤
mod = mod.Fields(model.TMessageLogListModel{})
g.Log().Info(ctx, "查询用户ID:", contexts.GetUserId(ctx))
if in.MessageType != nil {
mod = mod.Where(dao.TMessageLog.Columns().MessageType, in.MessageType)
}
if title := strings.TrimSpace(in.MessageTitle); title != "" {
mod = mod.WhereLike(dao.TMessageLog.Columns().MessageTitle, "%"+title+"%")
}
// 查询发送时间范围
if in.SendTimeStart != nil {
mod = mod.WhereGTE(dao.TMessageLog.Columns().SendTime, in.SendTimeStart)
}
if in.SendTimeEnd != nil {
mod = mod.WhereLTE(dao.TMessageLog.Columns().SendTime, in.SendTimeEnd.EndOfDay())
}
// 兼容旧的单日发送时间查询
if in.SendTime != nil {
mod = mod.Where(fmt.Sprintf("DATE(%s) = ?", dao.TMessageLog.Columns().SendTime), in.SendTime.Format("Y-m-d"))
}
// 查询用户ID(JSON字段,使用JSON_CONTAINS查询)
userId := contexts.GetUserId(ctx)
var sysUser *entity.SysUser
dao.SysUser.Ctx(ctx).WherePri(userId).Scan(&sysUser)
userType := sysUser.UserType
// 1. 查找顶级部门(ParentId = 0)
var topDept entity.SysDept
err = dao.SysDept.Ctx(ctx).
Where(dao.SysDept.Columns().ParentId, 0).
Scan(&topDept)
if err != nil {
return
}
// 检查顶级部门是否存在
if topDept.DeptId > 0 && deptId != topDept.DeptId {
mod = mod.Where(dao.TMessageLog.Columns().IsRecall, 0)
mod = mod.Where(fmt.Sprintf("JSON_CONTAINS(%s, CAST(? AS JSON))", dao.TMessageLog.Columns().UserIds), userId)
if userType == "sys_user" {
mod = mod.Where(dao.TMessageLog.Columns().SendUserType, 1)
} else if userType == "app_user" {
mod = mod.Where(dao.TMessageLog.Columns().SendUserType, 2)
}
}
// 分页
mod = mod.Page(in.Page, in.PageSize)
// 排序
mod = mod.OrderDesc(dao.TMessageLog.Columns().MessageLogId)
// 操作人摘要信息
mod = mod.Hook(hook.MemberSummary)
// 查询数据
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
err = gerror.Wrap(err, "获取消息日志列表失败,请稍后重试!")
return
}
return
}
// Export 导出消息日志(异步:提交任务后到下载中心取文件)
func (s *sCommonTMessageLog) Export(ctx context.Context, in *model.TMessageLogListParam) (err error) {
_, err = exportcenter.Submit(ctx, &exportcenter.Request{
BizKey: "tMessageLog",
BizName: "消息日志",
PermCode: "cpm:common:tMessageLog:export",
QueryParam: in,
Tags: func() ([]string, error) {
return convert.GetEntityDescTags(model.TMessageLogListModel{})
},
Fetch: func(ctx context.Context, page, pageSize int) (rows []interface{}, total int, err error) {
query := *in
query.Page = page
query.PageSize = pageSize
list, count, err := s.List(ctx, &query)
if err != nil {
return nil, 0, err
}
rows = make([]interface{}, 0, len(list))
for _, item := range list {
rows = append(rows, item)
}
return rows, count, nil
},
})
return err
}
// Edit 修改/新增消息日志
func (s *sCommonTMessageLog) Edit(ctx context.Context, in *model.TMessageLogEditParam) (err error) {
var (
pushUserIds []uint64
pushPayload g.Map
)
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
// 修改
if in.MessageLogId > 0 {
tenantId := contexts.GetTenantId(ctx)
in.TenantId = &tenantId
updatedBy := contexts.GetUserId(ctx)
in.UpdatedBy = &updatedBy
in.UpdatedAt = gtime.Now()
if _, err = s.Model(ctx).
Fields(model.TMessageLogUpdateFields{}).
WherePri(in.MessageLogId).Data(in).Update(); err != nil {
err = gerror.Wrap(err, "修改消息日志失败,请稍后重试!")
}
return
}
// 新增
tenantId := contexts.GetTenantId(ctx)
in.TenantId = &tenantId
createdDept := contexts.GetDeptId(ctx)
in.CreatedDept = &createdDept
createdBy := contexts.GetUserId(ctx)
in.CreatedBy = &createdBy
in.CreatedAt = gtime.Now()
in.SendTime = gtime.Now()
result, err := tx.Ctx(ctx).Model(dao.TMessageLog.Table()).
Fields(model.TMessageLogInsertFields{}).
Data(in).Insert()
if err != nil {
err = gerror.Wrap(err, "新增消息日志失败,请稍后重试!")
return
}
// 获取插入的消息日志ID
messageLogId, err := result.LastInsertId()
if err != nil {
err = gerror.Wrap(err, "获取消息日志ID失败")
return
}
pushPayload = g.Map{
"messageLogId": messageLogId,
"messageType": gconv.Uint(in.MessageType),
"sendUserType": gconv.Uint(in.SendUserType),
"messageTitle": gconv.String(in.MessageTitle),
"messageContent": gconv.String(in.MessageContent),
"messageLink": gconv.String(in.MessageLink),
"sendTime": in.SendTime,
"isRecall": gconv.Uint(in.IsRecall),
"isRead": 0,
}
optionalUserMessageColumns := s.userMessageOptionalColumns(ctx)
// 根据 user_ids 往用户消息表插入记录
if in.UserIds != nil && len(in.UserIds.Interfaces()) > 0 {
userIds := in.UserIds.Interfaces()
for _, userId := range userIds {
userIdInt := gconv.Int64(userId)
if userIdInt <= 0 {
continue
}
userMessageData := g.Map{
dao.TUserMessage.Columns().MessageLogId: messageLogId,
dao.TUserMessage.Columns().UserId: userIdInt,
dao.TUserMessage.Columns().TenantId: tenantId,
dao.TUserMessage.Columns().CreatedDept: createdDept,
dao.TUserMessage.Columns().CreatedBy: createdBy,
dao.TUserMessage.Columns().CreatedAt: in.CreatedAt,
}
if _, ok := optionalUserMessageColumns["user_rank"]; ok {
userMessageData["user_rank"] = 0
}
if _, ok := optionalUserMessageColumns["title"]; ok {
userMessageData["title"] = messageLogStringValue(in.MessageTitle)
}
if _, ok := optionalUserMessageColumns["content"]; ok {
userMessageData["content"] = messageLogStringValue(in.MessageContent)
}
if _, ok := optionalUserMessageColumns["link"]; ok {
userMessageData["link"] = messageLogStringValue(in.MessageLink)
}
_, err = tx.Ctx(ctx).Model(dao.TUserMessage.Table()).Data(userMessageData).Insert()
if err != nil {
err = gerror.Wrap(err, "新增用户消息失败,请稍后重试!")
return
}
pushUserIds = append(pushUserIds, uint64(userIdInt))
}
}
return
})
if err != nil {
return
}
for _, userId := range pushUserIds {
g.Log().Info(ctx, "推送消息到用户 %v", userId)
websocket.SendToUser(userId, &websocket.WResponse{
Event: "message:new",
Data: pushPayload,
})
}
return
}
func (s *sCommonTMessageLog) userMessageOptionalColumns(ctx context.Context) map[string]struct{} {
result, err := g.DB().GetAll(ctx, "SHOW COLUMNS FROM "+dao.TUserMessage.Table())
if err != nil {
return map[string]struct{}{}
}
columns := make(map[string]struct{}, len(result))
for _, row := range result {
field := row["Field"].String()
if field != "" {
columns[field] = struct{}{}
}
}
return columns
}
func messageLogStringValue(value *string) string {
if value == nil {
return ""
}
return *value
}
// Delete 删除消息日志
func (s *sCommonTMessageLog) Delete(ctx context.Context, in *model.TMessageLogDeleteParam) (err error) {
if _, err = s.Model(ctx).WherePri(in.MessageLogId).Unscoped().Delete(); err != nil {
err = gerror.Wrap(err, "删除消息日志失败,请稍后重试!")
return
}
return
}
// View 获取消息日志指定信息,并标记已读
func (s *sCommonTMessageLog) View(ctx context.Context, in *model.TMessageLogViewParam) (res *model.TMessageLogViewModel, err error) {
if err = dao.TMessageLog.Ctx(ctx).WherePri(in.MessageLogId).Hook(hook.MemberSummary).Scan(&res); err != nil {
err = gerror.Wrap(err, "获取消息日志信息,请稍后重试!")
return
}
// 标记用户消息为已读
userId := contexts.GetUserId(ctx)
now := gtime.Now()
_, err = dao.TUserMessage.Ctx(ctx).Where(g.Map{
dao.TUserMessage.Columns().MessageLogId: in.MessageLogId,
dao.TUserMessage.Columns().UserId: userId,
dao.TUserMessage.Columns().IsRead: 0,
}).Data(g.Map{
dao.TUserMessage.Columns().IsRead: 1,
dao.TUserMessage.Columns().UpdatedBy: userId,
dao.TUserMessage.Columns().UpdatedAt: now,
}).Update()
if err != nil {
// 已读标记失败不影响主业务,仅记录日志
g.Log().Warning(ctx, "标记消息已读失败: %v", err)
err = nil
}
return
}
// 未读通知的默认与最大返回条数,避免一次拉取过多消息。
const (
messageUnreadDefaultLimit = 50
messageUnreadMaxLimit = 200
)
// unreadQuery 构造当前用户未读消息查询。
// 以用户消息表为准:只查本人的未读记录,关联消息日志排除已撤回消息,
// 消息日志已删除(关联为空)时也不提醒。
// 这里不按 send_user_type 过滤:收件人 user_id 全部来自 sys_user 单表,
// 该字段只表示消息面向哪个控制台,按其过滤会漏掉真正发给本人的消息。
func (s *sCommonTMessageLog) unreadQuery(ctx context.Context, userId int64) *gdb.Model {
var (
userTable = dao.TUserMessage.Table()
logTable = dao.TMessageLog.Table()
userColumns = dao.TUserMessage.Columns()
logColumns = dao.TMessageLog.Columns()
)
return dao.TUserMessage.Ctx(ctx).
LeftJoin(logTable+" ON "+logTable+"."+logColumns.MessageLogId+" = "+userTable+"."+userColumns.MessageLogId).
Where(userTable+"."+userColumns.UserId, userId).
Where(userTable+"."+userColumns.IsRead, 0).
Where(logTable+"."+logColumns.IsRecall, 0)
}
// UnreadList 获取当前登录用户的未读消息通知。
// 这是右上角消息提醒的数据来源:登录、刷新、断线重连后都必须重新拉取,
// 否则离线期间的消息会因错过 websocket 推送而不再出现提醒。
func (s *sCommonTMessageLog) UnreadList(ctx context.Context, in *model.TMessageLogUnreadListParam) (list []*model.TMessageLogUnreadListModel, totalCount int, err error) {
userId := contexts.GetUserId(ctx)
if userId <= 0 {
err = gerror.New("未获取到当前登录用户信息")
return
}
limit := messageUnreadDefaultLimit
if in != nil && in.Limit > 0 {
limit = in.Limit
}
if limit > messageUnreadMaxLimit {
limit = messageUnreadMaxLimit
}
var (
userTable = dao.TUserMessage.Table()
logTable = dao.TMessageLog.Table()
userColumn = dao.TUserMessage.Columns()
logColumn = dao.TMessageLog.Columns()
)
if err = s.unreadQuery(ctx, userId).
Fields(userTable + "." + userColumn.MessageLogId + "," +
logTable + "." + logColumn.MessageType + "," +
logTable + "." + logColumn.SendUserType + "," +
logTable + "." + logColumn.MessageTitle + "," +
logTable + "." + logColumn.MessageContent + "," +
logTable + "." + logColumn.MessageLink + "," +
logTable + "." + logColumn.SendTime + "," +
userTable + "." + userColumn.IsRead).
OrderDesc(userTable + "." + userColumn.MessageId).
Limit(limit).
Scan(&list); err != nil {
err = gerror.Wrap(err, "获取未读消息失败,请稍后重试!")
return
}
if list == nil {
list = []*model.TMessageLogUnreadListModel{}
}
// 总数不受列表条数上限影响,用于判断是否还有更多未读。
if totalCount, err = s.unreadQuery(ctx, userId).Count(); err != nil {
err = gerror.Wrap(err, "获取未读消息数量失败,请稍后重试!")
}
return
}
// Read 标记当前登录用户的指定消息为已读。
// 只允许操作本人的用户消息记录,避免越权修改他人已读状态。
func (s *sCommonTMessageLog) Read(ctx context.Context, in *model.TMessageLogReadParam) (err error) {
userId := contexts.GetUserId(ctx)
if userId <= 0 {
return gerror.New("未获取到当前登录用户信息")
}
if in == nil || in.MessageLogId <= 0 {
return gerror.New("消息ID不能为空")
}
if _, err = dao.TUserMessage.Ctx(ctx).Where(g.Map{
dao.TUserMessage.Columns().MessageLogId: in.MessageLogId,
dao.TUserMessage.Columns().UserId: userId,
}).Data(g.Map{
dao.TUserMessage.Columns().IsRead: 1,
dao.TUserMessage.Columns().UpdatedBy: userId,
dao.TUserMessage.Columns().UpdatedAt: gtime.Now(),
}).Update(); err != nil {
err = gerror.Wrap(err, "标记消息已读失败,请稍后重试!")
}
return
}
// ReadAll 标记当前登录用户的全部未读消息为已读。
func (s *sCommonTMessageLog) ReadAll(ctx context.Context, in *model.TMessageLogReadAllParam) (err error) {
userId := contexts.GetUserId(ctx)
if userId <= 0 {
return gerror.New("未获取到当前登录用户信息")
}
if _, err = dao.TUserMessage.Ctx(ctx).
Where(dao.TUserMessage.Columns().UserId, userId).
Where(dao.TUserMessage.Columns().IsRead, 0).
Data(g.Map{
dao.TUserMessage.Columns().IsRead: 1,
dao.TUserMessage.Columns().UpdatedBy: userId,
dao.TUserMessage.Columns().UpdatedAt: gtime.Now(),
}).Update(); err != nil {
err = gerror.Wrap(err, "标记全部消息已读失败,请稍后重试!")
}
return
}