From f0e6bdd37cccdb27655d00a4857a5e287bedac64 Mon Sep 17 00:00:00 2001 From: qhd <1766646056@qq.com> Date: Fri, 16 Jan 2026 16:55:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=EF=BC=9A=E9=87=8D=E6=9E=84=E6=93=8D=E4=BD=9C=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=AE=B0=E5=BD=95=E4=B8=8E=E6=9F=A5=E8=AF=A2=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81=E6=89=B9=E9=87=8F=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=EF=BC=8C=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E6=97=A5=E5=BF=97=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E8=BD=AF=E5=88=A0=E9=99=A4=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- log/consts/log_const.go | 14 +- log/controller/log_controller.go | 32 +--- log/dao/log_dao.go | 79 +-------- log/model/dto/log_dto.go | 41 ++--- log/model/entity/log.go | 2 +- log/service/log_service.go | 109 +++++------- mongo/connection.go | 3 +- mongo/mongo.go | 280 ++++++++++++++++++++----------- 8 files changed, 260 insertions(+), 300 deletions(-) diff --git a/log/consts/log_const.go b/log/consts/log_const.go index ca33ca3..7f4e5fb 100644 --- a/log/consts/log_const.go +++ b/log/consts/log_const.go @@ -4,12 +4,20 @@ package consts type OperationType string const ( - OperationCreate OperationType = "create" // 创建 - OperationUpdate OperationType = "update" // 更新 - OperationDelete OperationType = "delete" // 删除 + OperationInsert OperationType = "insert" // 创建 + OperationUpdate OperationType = "update" // 更新 + OperationDelete OperationType = "delete" // 删除 + OperationDeleteSoft OperationType = "delete_soft" // 软删除 ) // OperationLogCollection 操作日志集合名称常量 const ( OperationLogCollection = "operation_logs" // 操作日志集合名称 ) + +// 消费者配置(从 Redis Stream 消费请求) +const StreamKey = "log:%s" // 请求 Stream 键名(与发消息的key一致) +const GroupName = "log:consumer:group" // 消费者组名 +const ConsumerName = "message-consumer-1" // 消费者名称(唯一标识) +const BatchSize = 1 // 批处理大小(每次读取1条) +const AutoAck = true // ACK是否自动确认(true自动确认,false不确认) diff --git a/log/controller/log_controller.go b/log/controller/log_controller.go index 9b0ec56..3485423 100644 --- a/log/controller/log_controller.go +++ b/log/controller/log_controller.go @@ -12,33 +12,7 @@ type operationLog struct{} // OperationLog 操作日志控制器 var OperationLog = new(operationLog) -// GetByID 根据ID获取操作日志 -// @Summary 获取操作日志详情 -// @Description 根据日志ID获取操作日志的详细信息 -func (c *operationLog) GetByID(ctx context.Context, req *dto.GetLogReq) (res *dto.GetLogResp, err error) { - logInfo, err := service.OperationLog.GetByID(ctx, req.ID) - if err != nil { - return - } - - res = &dto.GetLogResp{ - OperationLogInfo: *logInfo, - } - return -} - -// List 查询操作日志列表(通用方法,支持根据不同条件动态查询) -// @Summary 查询操作日志列表 -// @Description 根据多个条件查询操作日志列表 -func (c *operationLog) List(ctx context.Context, req *dto.ListLogsReq) (res *dto.ListLogsResp, err error) { - logs, total, err := service.OperationLog.List(ctx, req) - if err != nil { - return - } - - res = &dto.ListLogsResp{ - Logs: logs, - Total: total, - } - return +// GetByCollectionId 根据collectionId获取操作日志列表 +func (c *operationLog) GetByCollectionId(ctx context.Context, req *dto.ListLogsReq) (res *dto.ListLogsResp, err error) { + return service.OperationLog.GetByCollectionId(ctx, req) } diff --git a/log/dao/log_dao.go b/log/dao/log_dao.go index c209a45..7f435de 100644 --- a/log/dao/log_dao.go +++ b/log/dao/log_dao.go @@ -2,6 +2,8 @@ package dao import ( "context" + "gitee.com/red-future---jilin-g/common/beans" + "strings" "time" "gitee.com/red-future---jilin-g/common/log/consts" @@ -9,7 +11,6 @@ import ( "gitee.com/red-future---jilin-g/common/log/model/entity" "gitee.com/red-future---jilin-g/common/mongo" "go.mongodb.org/mongo-driver/v2/bson" - "go.mongodb.org/mongo-driver/v2/mongo/options" ) type log struct{} @@ -23,75 +24,14 @@ func (d *log) Create(ctx context.Context, log *entity.OperationLog) error { return err } -// CreateBatch 批量创建日志记录 -func (d *log) CreateBatch(ctx context.Context, logs []*entity.OperationLog) error { - if len(logs) == 0 { - return nil - } - - documents := make([]interface{}, len(logs)) - for i, log := range logs { - documents[i] = log - } - - _, err := mongo.DB().Insert(ctx, documents, consts.OperationLogCollection) - return err -} - -// GetByID 根据ID获取日志 -func (d *log) GetByID(ctx context.Context, id string) (*entity.OperationLog, error) { - objectID, err := bson.ObjectIDFromHex(id) - if err != nil { - return nil, err - } - - filter := bson.M{"_id": objectID} - var log entity.OperationLog - err = mongo.DB().FindOne(ctx, filter, &log, consts.OperationLogCollection) - if err != nil { - return nil, err - } - return &log, nil -} - // List 查询日志列表(通用方法,通过filter动态拼接查询条件) -func (d *log) List(ctx context.Context, filter *dto.ListLogsReq, sortFields ...string) ([]*entity.OperationLog, int64, error) { - bsonFilter := buildFilter(filter) - - total, err := mongo.DB().Count(ctx, bsonFilter, consts.OperationLogCollection) - if err != nil { - return nil, 0, err +func (d *log) List(ctx context.Context, req *dto.ListLogsReq) (res []*entity.OperationLog, total int64, err error) { + filter := buildFilter(req) + req.OrderBy = []beans.OrderBy{ + {Field: "createdAt", Order: beans.Desc}, } - - var findOptions []options.Lister[options.FindOptions] - if filter.PageNum > 0 && filter.PageSize > 0 { - findOptions = append(findOptions, options.Find().SetSkip(int64((filter.PageNum-1)*filter.PageSize)).SetLimit(int64(filter.PageSize))) - } - - if len(sortFields) > 0 { - sort := bson.D{} - for _, field := range sortFields { - var order int - if len(field) > 0 && field[0] == '-' { - order = -1 - field = field[1:] - } else { - order = 1 - } - sort = append(sort, bson.E{Key: field, Value: order}) - } - findOptions = append(findOptions, options.Find().SetSort(sort)) - } else { - findOptions = append(findOptions, options.Find().SetSort(bson.D{{Key: "createdAt", Value: -1}})) - } - - var logs []*entity.OperationLog - _, err = mongo.DB().Find(ctx, bsonFilter, &logs, consts.OperationLogCollection, nil, nil) - if err != nil { - return nil, 0, err - } - - return logs, total, nil + total, err = mongo.DB().Find(ctx, filter, &res, consts.OperationLogCollection, req.Page, req.OrderBy) + return } // buildFilter 构建MongoDB查询过滤器 @@ -107,12 +47,11 @@ func buildFilter(filter interface{}) bson.M { bsonFilter["collection"] = req.Collection } if req.CollectionID != "" { - bsonFilter["collection_id"] = req.CollectionID + bsonFilter["collection_id"] = bson.M{"$in": strings.Split(req.CollectionID, ",")} } if req.Operation != "" { bsonFilter["operation"] = req.Operation } - // 处理时间范围字段 if req.StartTime != "" || req.EndTime != "" { timeFilter := bson.M{} diff --git a/log/model/dto/log_dto.go b/log/model/dto/log_dto.go index fab47af..ba12fda 100644 --- a/log/model/dto/log_dto.go +++ b/log/model/dto/log_dto.go @@ -3,37 +3,13 @@ package dto import ( "gitee.com/red-future---jilin-g/common/beans" "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" ) -// ========== 操作日志查询相关DTO ========== - -// GetLogReq 获取操作日志请求 -type GetLogReq struct { - g.Meta `path:"/getLog" method:"get" tags:"操作日志" summary:"获取操作日志详情" dc:"根据日志ID获取操作日志的详细信息"` - ID string `json:"id" v:"required" dc:"日志ID"` -} - -// GetLogResp 获取操作日志响应 -type GetLogResp struct { - OperationLogInfo -} - -// OperationLogInfo 操作日志信息 -type OperationLogInfo struct { - ID string `json:"id" dc:"日志ID"` - ServiceName string `json:"service_name" dc:"服务名"` - Collection string `json:"collection" dc:"数据所在集合名称"` - CollectionID string `json:"collection_id" dc:"数据ID"` - Operation string `json:"operation" dc:"操作类型"` - UserName string `json:"user_name" dc:"操作人名称"` - IPAddress string `json:"ip_address" dc:"操作IP地址"` - Data map[string]interface{} `json:"data" dc:"当前数据"` -} - // ListLogsReq 查询操作日志列表请求(通用方法,支持根据不同条件动态查询) type ListLogsReq struct { g.Meta `path:"/listLogs" method:"get" tags:"操作日志" summary:"查询操作日志列表" dc:"根据多个条件查询操作日志列表"` - beans.Page + *beans.Page ServiceName string `json:"service_name" dc:"服务名(可选)"` Collection string `json:"collection" dc:"数据所在集合名称(可选)"` CollectionID string `json:"collection_id" dc:"数据ID(可选)"` @@ -48,3 +24,16 @@ type ListLogsResp struct { Logs []OperationLogInfo `json:"logs" dc:"日志列表"` Total int64 `json:"total" dc:"总数"` } + +// OperationLogInfo 操作日志信息 +type OperationLogInfo struct { + ID string `json:"id" dc:"日志ID"` + ServiceName string `json:"service_name" dc:"服务名"` + Collection string `json:"collection" dc:"数据所在集合名称"` + CollectionID interface{} `json:"collection_id" dc:"数据ID"` + Operation string `json:"operation" dc:"操作类型"` + Creator string `json:"creator" dc:"操作人名称"` + CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"` + Data interface{} `json:"data" dc:"当前数据"` + IPAddress string `json:"ip_address" dc:"操作IP地址"` +} diff --git a/log/model/entity/log.go b/log/model/entity/log.go index ac566a4..463926f 100644 --- a/log/model/entity/log.go +++ b/log/model/entity/log.go @@ -10,7 +10,7 @@ type OperationLog struct { ServiceName string `bson:"service_name" json:"service_name"` // 服务名:具体的微服务名称 Collection string `bson:"collection" json:"collection"` // 集合名:数据所在的集合名称 - CollectionID string `bson:"collection_id" json:"collection_id"` // 数据ID:具体操作的数据ID,如订单号、钱包ID等 + CollectionID interface{} `bson:"collection_id" json:"collection_id"` // 数据ID:具体操作的数据ID,如订单号、钱包ID等 Operation string `bson:"operation" json:"operation"` // 操作类型:create, update, delete IPAddress string `bson:"ip_address" json:"ip_address"` // 操作IP地址 Data interface{} `bson:"data,omitempty" json:"data"` // 当前数据:操作时的数据状态 diff --git a/log/service/log_service.go b/log/service/log_service.go index 73a92d6..c16e8cc 100644 --- a/log/service/log_service.go +++ b/log/service/log_service.go @@ -2,12 +2,11 @@ package service import ( "context" - - "gitee.com/red-future---jilin-g/common/log/consts" + "gitee.com/red-future---jilin-g/common/beans" "gitee.com/red-future---jilin-g/common/log/dao" "gitee.com/red-future---jilin-g/common/log/model/dto" logEntity "gitee.com/red-future---jilin-g/common/log/model/entity" - "github.com/gogf/gf/v2/frame/g" + "gitee.com/red-future---jilin-g/common/utils" "github.com/gogf/gf/v2/util/gconv" ) @@ -16,84 +15,52 @@ type operationLog struct{} // OperationLog 操作日志服务 var OperationLog = &operationLog{} -// RecordCreate 记录创建操作 -func (s *operationLog) RecordCreate(ctx context.Context, serviceName, collection, collectionID string, data map[string]interface{}) error { - return s.record(ctx, serviceName, collection, collectionID, string(consts.OperationCreate), data) -} - -// RecordUpdate 记录更新操作 -func (s *operationLog) RecordUpdate(ctx context.Context, serviceName, collection, collectionID string, data map[string]interface{}) error { - return s.record(ctx, serviceName, collection, collectionID, string(consts.OperationUpdate), data) -} - -// RecordDelete 记录删除操作 -func (s *operationLog) RecordDelete(ctx context.Context, serviceName, collection, collectionID string, data map[string]interface{}) error { - return s.record(ctx, serviceName, collection, collectionID, string(consts.OperationDelete), data) -} - -// BatchRecordCreate 批量记录创建操作 -func (s *operationLog) BatchRecordCreate(ctx context.Context, logs []*logEntity.OperationLog) error { - return dao.Log.CreateBatch(ctx, logs) -} - -// GetByID 根据ID获取操作日志 -func (s *operationLog) GetByID(ctx context.Context, id string) (*dto.OperationLogInfo, error) { - log, err := dao.Log.GetByID(ctx, id) - if err != nil { - return nil, err - } - - var logInfo dto.OperationLogInfo - if err := gconv.Struct(log, &logInfo); err != nil { - return nil, err - } - logInfo.ID = log.Id.Hex() - return &logInfo, nil -} - -// List 查询操作日志列表 -func (s *operationLog) List(ctx context.Context, req *dto.ListLogsReq) ([]dto.OperationLogInfo, int64, error) { - logs, total, err := dao.Log.List(ctx, req) - if err != nil { - return nil, 0, err - } - - var logInfos []dto.OperationLogInfo - err = gconv.Structs(logs, &logInfos) - if err != nil { - return nil, 0, err - } - - // 处理特殊字段 - for i, log := range logs { - logInfos[i].ID = log.Id.Hex() - } - - return logInfos, total, nil -} - -// record 记录操作日志的通用方法 -func (s *operationLog) record(ctx context.Context, serviceName, collection, collectionID, operation string, data map[string]interface{}) error { - // 获取请求信息 - ipAddress := getHTTPRequestInfo(ctx) +func (s *operationLog) AddOperationLog(ctx context.Context, msg map[string]interface{}) error { + serviceName := gconv.String(msg["service_name"]) + collection := gconv.String(msg["collection"]) + collectionId := gconv.Strings(msg["collection_id"]) + operation := gconv.String(msg["operation"]) + ipAddress := gconv.String(msg["ip_address"]) + data := gconv.Maps(msg["data"]) + creator := gconv.String(msg["creator"]) + createdAt := gconv.Time(msg["createdAt"]) + updater := gconv.String(msg["updater"]) + updatedAt := gconv.Time(msg["updatedAt"]) + tenantId := gconv.Float64(msg["tenantId"]) + // 设置 userId 和 tenantId 到 ctx + ctx = context.WithValue(ctx, "userName", creator) + ctx = context.WithValue(ctx, "tenantId", tenantId) log := &logEntity.OperationLog{ + MongoBaseDO: beans.MongoBaseDO{ + Creator: creator, + CreatedAt: &createdAt, + Updater: updater, + UpdatedAt: &updatedAt, + TenantId: tenantId, + }, ServiceName: serviceName, Collection: collection, - CollectionID: collectionID, + CollectionID: collectionId, Operation: operation, IPAddress: ipAddress, Data: data, } - return dao.Log.Create(ctx, log) } -// getHTTPRequestInfo 从上下文中获取HTTP请求信息 -func getHTTPRequestInfo(ctx context.Context) string { - request := g.RequestFromCtx(ctx) - if request != nil { - return request.GetClientIp() +// GetByCollectionId 根据集合ID获取操作日志 +func (s *operationLog) GetByCollectionId(ctx context.Context, req *dto.ListLogsReq) (res *dto.ListLogsResp, err error) { + logs, total, err := dao.Log.List(ctx, req) + if err != nil { + return } - return "" + res = &dto.ListLogsResp{ + Total: total, + } + err = utils.Struct(logs, &res.Logs) + if err != nil { + return + } + return } diff --git a/mongo/connection.go b/mongo/connection.go index 77f1cec..a46a559 100644 --- a/mongo/connection.go +++ b/mongo/connection.go @@ -8,6 +8,7 @@ package mongo import ( "context" "fmt" + "gitee.com/red-future---jilin-g/common/log/consts" "os" "os/signal" "strings" @@ -244,7 +245,7 @@ func (m *DataSourceManager) GetAllDataSourceNames() []string { func init() { logPool = grpool.New(1) serverName = g.Cfg().MustGet(context.TODO(), "server.name").String() - logRedisKey = fmt.Sprintf("log:%s", serverName) + LogRedisKey = fmt.Sprintf(consts.StreamKey, serverName) ctx := context.Background() diff --git a/mongo/mongo.go b/mongo/mongo.go index 4cc2291..2164546 100644 --- a/mongo/mongo.go +++ b/mongo/mongo.go @@ -9,6 +9,7 @@ import ( "context" "errors" "fmt" + "gitee.com/red-future---jilin-g/common/log/consts" "reflect" "time" @@ -62,9 +63,15 @@ var ( manager = GetManager() logPool *grpool.Pool serverName string - logRedisKey string + LogRedisKey string ) +// FieldInfo 定义字段信息结构体 +type FieldInfo struct { + FieldName string + FieldValue interface{} +} + const PageSize = 20 // GetDB 获取默认数据源的数据库实例(向后兼容) @@ -88,6 +95,43 @@ func (m *MongoDB) getDataSource() (DataSource, error) { return manager.GetDataSource(m.dataSource) } +// Count 查询总数 +func (m *MongoDB) Count(ctx context.Context, filter bson.M, collection string) (count int64, err error) { + source, err := m.getDataSource() + if err != nil { + return 0, err + } + db := source.Database() + + user, err := utils.GetUserInfo(ctx) + if err != nil { + return + } + filter["isDeleted"] = false + delete(filter, "tenantId") + filterKey := fmt.Sprintf("%+v", filter) + redisKey := fmt.Sprintf(redis.Count, user.TenantId, collection, filterKey) + if m.Cache { + var resultStr *gvar.Var + resultStr, err = redis.RedisClient.Get(ctx, redisKey) + if err != nil { + return + } + if !g.IsEmpty(resultStr) { + count = gconv.Int64(resultStr) + return + } + } + count, err = db.Collection(collection).CountDocuments(ctx, filter) + if m.Cache { + err = redis.RedisClient.SetEX(ctx, redisKey, count, int64(time.Hour)) + if err != nil { + return + } + } + return +} + // Find 查询多条记录 func (m *MongoDB) Find(ctx context.Context, filter bson.M, result interface{}, collection string, page *beans.Page, orderBy []beans.OrderBy) (total int64, err error) { source, err := m.getDataSource() @@ -103,7 +147,9 @@ func (m *MongoDB) Find(ctx context.Context, filter bson.M, result interface{}, c if err != nil { return } - filter["isDeleted"] = false + if g.IsEmpty(filter["isDeleted"]) { + filter["isDeleted"] = false + } filterKey := fmt.Sprintf("%+v", filter) optionsKey := fmt.Sprintf("%+v%+v", page, orderBy) redisKey := fmt.Sprintf(redis.List, user.TenantId, collection, filterKey, optionsKey) @@ -125,7 +171,7 @@ func (m *MongoDB) Find(ctx context.Context, filter bson.M, result interface{}, c limit := int64(PageSize) skip := int64(0) - if page != nil { + if page != nil && !g.IsEmpty(page.PageNum) && !g.IsEmpty(page.PageSize) { limit = page.PageSize if limit == -1 { skip = 0 @@ -229,6 +275,17 @@ func (m *MongoDB) FindOne(ctx context.Context, filter bson.M, result interface{} return } +// getDeletedData 获取要删除的数据 +func (m *MongoDB) getDeletedData(ctx context.Context, filter bson.M, collection string) (deletedIDs []bson.ObjectID, deletedData []bson.M, err error) { + // 查询要删除的数据 + _, err = m.Find(ctx, filter, &deletedData, collection, nil, nil) + // 从查询结果中获取 _id + for _, doc := range deletedData { + deletedIDs = append(deletedIDs, doc["_id"].(bson.ObjectID)) + } + return +} + func (m *MongoDB) CleanRedis(ctx context.Context, filter bson.M, tenantId interface{}, collection string) (err error) { listKeys := fmt.Sprintf(redis.CleanList, tenantId, collection) keys, err := redis.RedisClient.Keys(ctx, listKeys) @@ -263,27 +320,92 @@ func (m *MongoDB) CleanRedis(ctx context.Context, filter bson.M, tenantId interf return } -func (m *MongoDB) log(ctx context.Context, filter bson.M, collection string, data interface{}, userName, tenantId interface{}, operationType string) { - _ = logPool.AddWithRecover(ctx, func(ctx context.Context) { - log := &entity.OperationLog{ - ServiceName: serverName, - Collection: collection, - CollectionID: filter["_id"].(string), - Operation: operationType, - IPAddress: g.RequestFromCtx(ctx).GetClientIp(), - Data: data, +func (m *MongoDB) log(ctx context.Context, ids []bson.ObjectID, filter bson.M, collection string, data interface{}, userName, tenantId interface{}, operationType consts.OperationType) { + // 提前获取 IP 地址,避免异步任务执行时请求已结束 + var ipAddress string + if request := g.RequestFromCtx(ctx); request != nil { + ipAddress = request.GetClientIp() + } + if operationType != consts.OperationInsert && operationType != consts.OperationDelete { + if !g.IsEmpty(filter["_id"]) { + objectID := filter["_id"].(*bson.ObjectID) + ids = append(ids, *objectID) + } else { + var err error + if ids, _, err = m.getDeletedData(ctx, filter, collection); err != nil { + return + } } - log.Creator = userName - now := >ime.Now().Time - log.CreatedAt = now - log.UpdatedAt = now - log.TenantId = tenantId - if _, err := redis.AddToStream(ctx, logRedisKey, log); err != nil { - glog.Error(ctx, "mongoLog-AddToStream err: %v", err) + } + log := &entity.OperationLog{ + ServiceName: serverName, + Collection: collection, + CollectionID: ids, + Operation: string(operationType), + IPAddress: ipAddress, + Data: data, + } + log.Creator = userName + log.Updater = userName + now := >ime.Now().Time + log.CreatedAt = now + log.UpdatedAt = now + log.TenantId = tenantId + // 使用新的 context 进行 Redis 操作 + if _, err := redis.AddToStream(ctx, LogRedisKey, log); err != nil { + glog.Error(ctx, "mongoLog-AddToStream err: %v", err) + } + return +} + +// Insert 插入多条记录 +func (m *MongoDB) Insert(ctx context.Context, documents []interface{}, collection string, opts ...options.Lister[options.InsertManyOptions]) (ids []interface{}, err error) { + source, err := m.getDataSource() + if err != nil { + return nil, err + } + db := source.Database() + + user, err := utils.GetUserInfo(ctx) + if err != nil { + return + } + docs := make([]interface{}, 0, len(documents)) + for _, document := range documents { + doc := gconv.Map(document) + delete(doc, "id") + if !g.IsEmpty(user.UserName) && g.IsEmpty(doc["creator"]) { + doc["creator"] = user.UserName } - }, func(ctx context.Context, exception error) { - glog.Error(ctx, "mongoLog-AddWithRecover err: %v", exception) - }) + if !g.IsEmpty(user.UserName) && g.IsEmpty(doc["updater"]) { + doc["updater"] = user.UserName + } + if !g.IsEmpty(user.TenantId) && g.IsEmpty(doc["tenantId"]) { + doc["tenantId"] = user.TenantId + } + if g.IsEmpty(doc["createdAt"]) { + doc["createdAt"] = gtime.Now().Time + } + if g.IsEmpty(doc["updatedAt"]) { + doc["updatedAt"] = gtime.Now().Time + } + doc["isDeleted"] = false + docs = append(docs, doc) + } + r, err := db.Collection(collection).InsertMany(ctx, docs, opts...) + if err != nil { + return + } + ids = r.InsertedIDs + err = m.CleanRedis(ctx, bson.M{}, user.TenantId, collection) + //写日志 + if collection != consts.OperationLogCollection { + objectIds := make([]bson.ObjectID, 0) + for _, id := range ids { + objectIds = append(objectIds, id.(bson.ObjectID)) + } + m.log(ctx, objectIds, nil, collection, nil, user.UserName, user.TenantId, consts.OperationInsert) + } return } @@ -304,15 +426,30 @@ func (m *MongoDB) Delete(ctx context.Context, filter bson.M, collection string, return } filter["tenantId"] = user.TenantId + // 获取要删除的数据 + ds, ms, err := m.getDeletedData(ctx, filter, collection) + if err != nil { + return + } + // 执行删除操作 r, err := db.Collection(collection).DeleteMany(ctx, filter, opts...) if err != nil { return } count = r.DeletedCount + // 清理redis err = m.CleanRedis(ctx, filter, user.TenantId, collection) + // 写日志 + m.log(ctx, ds, nil, collection, ms, user.UserName, user.TenantId, consts.OperationDelete) return } +// DeleteSoft 假删除记录 +func (m *MongoDB) DeleteSoft(ctx context.Context, filter bson.M, collection string, opts ...options.Lister[options.UpdateManyOptions]) (result *mongo.UpdateResult, err error) { + update := bson.M{"$set": bson.M{"isDeleted": true}} + return m.Update(ctx, filter, update, collection, opts...) +} + // Update 修改记录 func (m *MongoDB) Update(ctx context.Context, filter bson.M, update bson.M, collection string, opts ...options.Lister[options.UpdateManyOptions]) (result *mongo.UpdateResult, err error) { source, err := m.getDataSource() @@ -333,17 +470,38 @@ func (m *MongoDB) Update(ctx context.Context, filter bson.M, update bson.M, coll if !g.IsEmpty(user.TenantId) { filter["tenantId"] = user.TenantId } + // 遍历 update 中的所有操作符和字段,存放到 list 中 + fieldList := make([]FieldInfo, 0) + for _, doc := range update { + if m, ok := doc.(bson.M); ok { + for fieldName, fieldValue := range m { + // 获取到字段名和字段值 + fieldList = append(fieldList, FieldInfo{ + FieldName: fieldName, + FieldValue: fieldValue, + }) + } + } + } setDoc := update["$set"].(bson.M) if !g.IsEmpty(user.UserName) { setDoc["updater"] = user.UserName } setDoc["updatedAt"] = gtime.Now().Time - update = bson.M{"$set": setDoc} + update["$set"] = setDoc result, err = db.Collection(collection).UpdateMany(ctx, filter, update, opts...) if err != nil { return } + // 清理redis err = m.CleanRedis(ctx, filter, user.TenantId, collection) + // 写日志 + if !g.IsEmpty(setDoc["isDeleted"]) && gconv.Bool(setDoc["isDeleted"]) { + filter["isDeleted"] = true + m.log(ctx, nil, filter, collection, nil, user.UserName, user.TenantId, consts.OperationDeleteSoft) + } else { + m.log(ctx, nil, filter, collection, fieldList, user.UserName, user.TenantId, consts.OperationUpdate) + } return } @@ -468,82 +626,6 @@ func (m *MongoDB) SaveOrUpdate(ctx context.Context, filter []bson.M, update []bs return bulkResult, nil } -// Insert 插入多条记录 -func (m *MongoDB) Insert(ctx context.Context, documents []interface{}, collection string, opts ...options.Lister[options.InsertManyOptions]) (ids []interface{}, err error) { - source, err := m.getDataSource() - if err != nil { - return nil, err - } - db := source.Database() - - user, err := utils.GetUserInfo(ctx) - if err != nil { - return - } - docs := make([]interface{}, 0, len(documents)) - for _, document := range documents { - doc := gconv.Map(document) - delete(doc, "id") - if !g.IsEmpty(user.UserName) { - doc["creator"] = user.UserName - } - if !g.IsEmpty(user.UserName) { - doc["updater"] = user.UserName - } - if !g.IsEmpty(user.TenantId) { - doc["tenantId"] = user.TenantId - } - doc["createdAt"] = gtime.Now().Time - doc["updatedAt"] = gtime.Now().Time - doc["isDeleted"] = false - docs = append(docs, doc) - } - r, err := db.Collection(collection).InsertMany(ctx, docs, opts...) - if err != nil { - return - } - ids = r.InsertedIDs - err = m.CleanRedis(ctx, bson.M{}, user.TenantId, collection) - return -} - -// Count 查询总数 -func (m *MongoDB) Count(ctx context.Context, filter bson.M, collection string) (count int64, err error) { - source, err := m.getDataSource() - if err != nil { - return 0, err - } - db := source.Database() - - user, err := utils.GetUserInfo(ctx) - if err != nil { - return - } - filter["isDeleted"] = false - delete(filter, "tenantId") - filterKey := fmt.Sprintf("%+v", filter) - redisKey := fmt.Sprintf(redis.Count, user.TenantId, collection, filterKey) - if m.Cache { - var resultStr *gvar.Var - resultStr, err = redis.RedisClient.Get(ctx, redisKey) - if err != nil { - return - } - if !g.IsEmpty(resultStr) { - count = gconv.Int64(resultStr) - return - } - } - count, err = db.Collection(collection).CountDocuments(ctx, filter) - if m.Cache { - err = redis.RedisClient.SetEX(ctx, redisKey, count, int64(time.Hour)) - if err != nil { - return - } - } - return -} - func BuildUpdateFilter(ctx context.Context, req interface{}) (filter bson.M, err error) { _ = ctx filter = bson.M{}