github.com/godaddy-x/freego@v1.0.156/ormx/sqld/mongo_manager.go (about)

     1  package sqld
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"github.com/godaddy-x/freego/cache"
     7  	DIC "github.com/godaddy-x/freego/common"
     8  	"github.com/godaddy-x/freego/ormx/sqlc"
     9  	"github.com/godaddy-x/freego/utils"
    10  	"github.com/godaddy-x/freego/zlog"
    11  	"go.mongodb.org/mongo-driver/bson"
    12  	"go.mongodb.org/mongo-driver/bson/primitive"
    13  	"go.mongodb.org/mongo-driver/mongo"
    14  	"go.mongodb.org/mongo-driver/mongo/options"
    15  	"go.mongodb.org/mongo-driver/mongo/readpref"
    16  	"go.uber.org/zap"
    17  	"reflect"
    18  	"time"
    19  )
    20  
    21  var (
    22  	mgoSessions = make(map[string]*MGOManager)
    23  	mgoSlowlog  *zap.Logger
    24  )
    25  
    26  type SortBy struct {
    27  	Key  string
    28  	Sort int
    29  }
    30  
    31  const (
    32  	JID      = "id"
    33  	BID      = "_id"
    34  	COUNT_BY = "COUNT_BY"
    35  )
    36  
    37  /********************************** 数据库配置参数 **********************************/
    38  
    39  // 数据库配置
    40  type MGOConfig struct {
    41  	DBConfig
    42  	AuthMechanism  string
    43  	Addrs          []string
    44  	Direct         bool
    45  	ConnectTimeout int64
    46  	SocketTimeout  int64
    47  	Database       string
    48  	Username       string
    49  	Password       string
    50  	PoolLimit      int
    51  	ConnectionURI  string
    52  }
    53  
    54  type PackContext struct {
    55  	SessionContext mongo.SessionContext
    56  	Context        context.Context
    57  	CancelFunc     context.CancelFunc
    58  }
    59  
    60  func IsNullObjectID(target primitive.ObjectID) bool {
    61  	// return target.Hex() == "000000000000000000000000"
    62  	return target.IsZero()
    63  }
    64  
    65  // 数据库管理器
    66  type MGOManager struct {
    67  	DBManager
    68  	Session     *mongo.Client
    69  	PackContext *PackContext
    70  }
    71  
    72  func (self *MGOManager) Get(option ...Option) (*MGOManager, error) {
    73  	if err := self.GetDB(option...); err != nil {
    74  		return nil, err
    75  	}
    76  	return self, nil
    77  }
    78  
    79  func NewMongo(option ...Option) (*MGOManager, error) {
    80  	manager := &MGOManager{}
    81  	return manager.Get(option...)
    82  }
    83  
    84  func UseTransaction(fn func(mgo *MGOManager) error, option ...Option) error {
    85  	self, err := NewMongo(option...)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	defer self.Close()
    90  	return self.Session.UseSession(self.PackContext.Context, func(sessionContext mongo.SessionContext) error {
    91  		self.PackContext.SessionContext = sessionContext
    92  		if err := self.PackContext.SessionContext.StartTransaction(); err != nil {
    93  			return err
    94  		}
    95  		if err := fn(self); err != nil {
    96  			self.PackContext.SessionContext.AbortTransaction(self.PackContext.SessionContext)
    97  			return err
    98  		}
    99  		return self.PackContext.SessionContext.CommitTransaction(self.PackContext.SessionContext)
   100  	})
   101  }
   102  
   103  // 获取mongo的数据库连接
   104  func (self *MGOManager) GetDatabase(tb string) (*mongo.Collection, error) {
   105  	collection := self.Session.Database(self.Database).Collection(tb)
   106  	if collection == nil {
   107  		return nil, self.Error("failed to get Mongo collection")
   108  	}
   109  	return collection, nil
   110  }
   111  
   112  func (self *MGOManager) GetDB(options ...Option) error {
   113  	dsName := DIC.MASTER
   114  	var option Option
   115  	if len(options) > 0 {
   116  		option = options[0]
   117  		if len(option.DsName) > 0 {
   118  			dsName = option.DsName
   119  		} else {
   120  			option.DsName = dsName
   121  		}
   122  	}
   123  	mgo := mgoSessions[dsName]
   124  	if mgo == nil {
   125  		return self.Error("mongo session [", dsName, "] not found...")
   126  	}
   127  	self.Session = mgo.Session
   128  	self.DsName = mgo.DsName
   129  	self.Database = mgo.Database
   130  	self.Timeout = 60000
   131  	self.SlowQuery = mgo.SlowQuery
   132  	self.SlowLogPath = mgo.SlowLogPath
   133  	self.CacheManager = mgo.CacheManager
   134  	if len(option.DsName) > 0 {
   135  		if len(option.DsName) > 0 {
   136  			self.DsName = option.DsName
   137  		}
   138  		if option.OpenTx {
   139  			self.OpenTx = option.OpenTx
   140  		}
   141  		if option.Timeout > 0 {
   142  			self.Timeout = option.Timeout
   143  		}
   144  		if len(option.Database) > 0 {
   145  			self.Database = option.Database
   146  		}
   147  	}
   148  	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(self.Timeout)*time.Millisecond)
   149  	self.PackContext = &PackContext{Context: ctx, CancelFunc: cancel}
   150  	return nil
   151  }
   152  
   153  func (self *MGOManager) GetSessionContext() context.Context {
   154  	if self.PackContext.SessionContext == nil {
   155  		return self.PackContext.Context
   156  	}
   157  	return self.PackContext.SessionContext
   158  }
   159  
   160  func (self *MGOManager) InitConfig(input ...MGOConfig) error {
   161  	return self.buildByConfig(nil, input...)
   162  }
   163  
   164  func (self *MGOManager) InitConfigAndCache(manager cache.Cache, input ...MGOConfig) error {
   165  	return self.buildByConfig(manager, input...)
   166  }
   167  
   168  func (self *MGOManager) buildByConfig(manager cache.Cache, input ...MGOConfig) error {
   169  	for _, v := range input {
   170  		if len(v.Database) == 0 {
   171  			panic("mongo database is nil")
   172  		}
   173  		dsName := DIC.MASTER
   174  		if len(v.DsName) > 0 {
   175  			dsName = v.DsName
   176  		}
   177  		if _, b := mgoSessions[dsName]; b {
   178  			return utils.Error("mongo init failed: [", v.DsName, "] exist")
   179  		}
   180  		opts := options.Client()
   181  		if len(v.ConnectionURI) == 0 {
   182  			if len(v.AuthMechanism) == 0 {
   183  				v.AuthMechanism = "SCRAM-SHA-1"
   184  			}
   185  			credential := options.Credential{
   186  				AuthMechanism: v.AuthMechanism,
   187  				Username:      v.Username,
   188  				Password:      v.Password,
   189  				AuthSource:    v.Database,
   190  			}
   191  			opts = options.Client().ApplyURI(fmt.Sprintf("mongodb://%s", v.Addrs[0]))
   192  			if len(v.Username) > 0 && len(v.Password) > 0 {
   193  				opts.SetAuth(credential)
   194  			}
   195  		} else {
   196  			opts = options.Client().ApplyURI(v.ConnectionURI)
   197  		}
   198  		opts.SetDirect(v.Direct)
   199  		opts.SetTimeout(time.Second * time.Duration(v.ConnectTimeout))
   200  		opts.SetMinPoolSize(100)
   201  		opts.SetMaxPoolSize(uint64(v.PoolLimit))
   202  		opts.SetSocketTimeout(time.Second * time.Duration(v.SocketTimeout))
   203  		// 连接数据库
   204  		session, err := mongo.Connect(context.Background(), opts)
   205  		if err != nil {
   206  			panic(err)
   207  		}
   208  		// 判断服务是不是可用
   209  		if err := session.Ping(context.Background(), readpref.Primary()); err != nil {
   210  			panic(err)
   211  		}
   212  		mgo := &MGOManager{}
   213  		mgo.Session = session
   214  		mgo.CacheManager = manager
   215  		mgo.DsName = dsName
   216  		mgo.Database = v.Database
   217  		mgo.SlowQuery = v.SlowQuery
   218  		mgo.SlowLogPath = v.SlowLogPath
   219  		if v.OpenTx {
   220  			mgo.OpenTx = v.OpenTx
   221  		}
   222  		mgoSessions[mgo.DsName] = mgo
   223  		// init zlog
   224  		mgo.initSlowLog()
   225  		zlog.Printf("mongodb service【%s】has been started successful", mgo.DsName)
   226  	}
   227  	if len(mgoSessions) == 0 {
   228  		return utils.Error("mongo init failed: sessions is nil")
   229  	}
   230  	return nil
   231  }
   232  
   233  func (self *MGOManager) initSlowLog() {
   234  	if self.SlowQuery == 0 || len(self.SlowLogPath) == 0 {
   235  		return
   236  	}
   237  	if mgoSlowlog == nil {
   238  		mgoSlowlog = zlog.InitNewLog(&zlog.ZapConfig{
   239  			Level:   "warn",
   240  			Console: false,
   241  			FileConfig: &zlog.FileConfig{
   242  				Compress:   true,
   243  				Filename:   self.SlowLogPath,
   244  				MaxAge:     7,
   245  				MaxBackups: 7,
   246  				MaxSize:    512,
   247  			}})
   248  		mgoSlowlog.Info("MGO query monitoring service started successful...")
   249  	}
   250  }
   251  
   252  func (self *MGOManager) getSlowLog() *zap.Logger {
   253  	return mgoSlowlog
   254  }
   255  
   256  func (self *MGOManager) Save(data ...sqlc.Object) error {
   257  	if data == nil || len(data) == 0 {
   258  		return self.Error("[Mongo.Save] data is nil")
   259  	}
   260  	if len(data) > 2000 {
   261  		return self.Error("[Mongo.Save] data length > 2000")
   262  	}
   263  	d := data[0]
   264  	if len(self.MGOSyncData) > 0 {
   265  		d = self.MGOSyncData[0].CacheModel
   266  	}
   267  	obv, ok := modelDrivers[d.GetTable()]
   268  	if !ok {
   269  		return self.Error("[Mongo.Save] registration object type not found [", d.GetTable(), "]")
   270  	}
   271  	db, err := self.GetDatabase(d.GetTable())
   272  	if err != nil {
   273  		return self.Error(err)
   274  	}
   275  	if zlog.IsDebug() {
   276  		defer zlog.Debug("[Mongo.Save]", utils.UnixMilli(), zlog.Any("data", data))
   277  	}
   278  	adds := make([]interface{}, 0, len(data))
   279  	for _, v := range data {
   280  		if obv.PkKind == reflect.Int64 {
   281  			lastInsertId := utils.GetInt64(utils.GetPtr(v, obv.PkOffset))
   282  			if lastInsertId == 0 {
   283  				lastInsertId = utils.NextIID()
   284  				utils.SetInt64(utils.GetPtr(v, obv.PkOffset), lastInsertId)
   285  			}
   286  		} else if obv.PkKind == reflect.String {
   287  			lastInsertId := utils.GetString(utils.GetPtr(v, obv.PkOffset))
   288  			if len(lastInsertId) == 0 {
   289  				lastInsertId = utils.NextSID()
   290  				utils.SetString(utils.GetPtr(v, obv.PkOffset), lastInsertId)
   291  			}
   292  		} else if obv.PkType == "primitive.ObjectID" {
   293  			lastInsertId := utils.GetObjectID(utils.GetPtr(v, obv.PkOffset))
   294  			if IsNullObjectID(lastInsertId) {
   295  				lastInsertId = primitive.NewObjectID()
   296  				utils.SetObjectID(utils.GetPtr(v, obv.PkOffset), lastInsertId)
   297  			}
   298  		} else {
   299  			return self.Error("only Int64 and string and ObjectID type IDs are supported")
   300  		}
   301  		adds = append(adds, v)
   302  	}
   303  	res, err := db.InsertMany(self.GetSessionContext(), adds)
   304  	if err != nil {
   305  		return self.Error("[Mongo.Save] save failed: ", err)
   306  	}
   307  	if len(res.InsertedIDs) != len(adds) {
   308  		return self.Error("[Mongo.Save] save failed: InsertedIDs length invalid")
   309  	}
   310  	return nil
   311  }
   312  
   313  func (self *MGOManager) Update(data ...sqlc.Object) error {
   314  	if data == nil || len(data) == 0 {
   315  		return self.Error("[Mongo.Update] data is nil")
   316  	}
   317  	if len(data) > 2000 {
   318  		return self.Error("[Mongo.Update] data length > 2000")
   319  	}
   320  	d := data[0]
   321  	if len(self.MGOSyncData) > 0 {
   322  		d = self.MGOSyncData[0].CacheModel
   323  	}
   324  	obv, ok := modelDrivers[d.GetTable()]
   325  	if !ok {
   326  		return self.Error("[Mongo.Update] registration object type not found [", d.GetTable(), "]")
   327  	}
   328  	db, err := self.GetDatabase(d.GetTable())
   329  	if err != nil {
   330  		return self.Error(err)
   331  	}
   332  	if zlog.IsDebug() {
   333  		defer zlog.Debug("[Mongo.Update]", utils.UnixMilli(), zlog.Any("data", data))
   334  	}
   335  	var lastInsertId interface{}
   336  	for _, v := range data {
   337  		if obv.PkKind == reflect.Int64 {
   338  			pk := utils.GetInt64(utils.GetPtr(v, obv.PkOffset))
   339  			if pk == 0 {
   340  				return self.Error("[Mongo.Update] data object id is nil")
   341  			}
   342  			lastInsertId = pk
   343  		} else if obv.PkKind == reflect.String {
   344  			pk := utils.GetString(utils.GetPtr(v, obv.PkOffset))
   345  			if len(pk) == 0 {
   346  				return self.Error("[Mongo.Update] data object id is nil")
   347  			}
   348  			lastInsertId = pk
   349  		} else if obv.PkType == "primitive.ObjectID" {
   350  			pk := utils.GetObjectID(utils.GetPtr(v, obv.PkOffset))
   351  			if IsNullObjectID(pk) {
   352  				return self.Error("[Mongo.Update] data object id is nil")
   353  			}
   354  			lastInsertId = pk
   355  		} else {
   356  			return self.Error("only Int64 and string and ObjectID type IDs are supported")
   357  		}
   358  		res, err := db.ReplaceOne(self.GetSessionContext(), bson.M{"_id": lastInsertId}, v)
   359  		if err != nil {
   360  			return self.Error("[Mongo.Update] update failed: ", err)
   361  		}
   362  		if res.ModifiedCount == 0 {
   363  			return self.Error("[Mongo.Update] update failed: ModifiedCount = 0")
   364  		}
   365  	}
   366  	return nil
   367  }
   368  
   369  func (self *MGOManager) UpdateByCnd(cnd *sqlc.Cnd) (int64, error) {
   370  	if cnd.Model == nil {
   371  		return 0, self.Error("[Mongo.UpdateByCnd] data model is nil")
   372  	}
   373  	db, err := self.GetDatabase(cnd.Model.GetTable())
   374  	if err != nil {
   375  		return 0, err
   376  	}
   377  	match := buildMongoMatch(cnd)
   378  	upset := buildMongoUpset(cnd)
   379  	if match == nil || len(match) == 0 {
   380  		return 0, self.Error("pipe match is nil")
   381  	}
   382  	if upset == nil || len(upset) == 0 {
   383  		return 0, self.Error("pipe upset is nil")
   384  	}
   385  	defer self.writeLog("[Mongo.UpdateByCnd]", utils.UnixMilli(), map[string]interface{}{"match": match, "upset": upset}, nil)
   386  	res, err := db.UpdateMany(self.GetSessionContext(), match, upset)
   387  	if err != nil {
   388  		return 0, self.Error("[Mongo.UpdateByCnd] update failed: ", err)
   389  	}
   390  	if res.ModifiedCount == 0 {
   391  		return 0, self.Error("[Mongo.Update] update failed: ModifiedCount = 0")
   392  	}
   393  	return res.ModifiedCount, nil
   394  }
   395  
   396  func (self *MGOManager) Delete(data ...sqlc.Object) error {
   397  	if data == nil || len(data) == 0 {
   398  		return self.Error("[Mongo.Delete] data is nil")
   399  	}
   400  	if len(data) > 2000 {
   401  		return self.Error("[Mongo.Delete] data length > 2000")
   402  	}
   403  	d := data[0]
   404  	if len(self.MGOSyncData) > 0 {
   405  		d = self.MGOSyncData[0].CacheModel
   406  	}
   407  	obv, ok := modelDrivers[d.GetTable()]
   408  	if !ok {
   409  		return self.Error("[Mongo.Delete] registration object type not found [", d.GetTable(), "]")
   410  	}
   411  	db, err := self.GetDatabase(d.GetTable())
   412  	if err != nil {
   413  		return self.Error(err)
   414  	}
   415  	if zlog.IsDebug() {
   416  		defer zlog.Debug("[Mongo.Delete]", utils.UnixMilli(), zlog.Any("data", data))
   417  	}
   418  	delIds := make([]interface{}, 0, len(data))
   419  	for _, v := range data {
   420  		if obv.PkKind == reflect.Int64 {
   421  			lastInsertId := utils.GetInt64(utils.GetPtr(v, obv.PkOffset))
   422  			if lastInsertId == 0 {
   423  				return self.Error("[Mongo.Delete] data object id is nil")
   424  			}
   425  			delIds = append(delIds, lastInsertId)
   426  		} else if obv.PkKind == reflect.String {
   427  			lastInsertId := utils.GetString(utils.GetPtr(v, obv.PkOffset))
   428  			if len(lastInsertId) == 0 {
   429  				return self.Error("[Mongo.Delete] data object id is nil")
   430  			}
   431  			delIds = append(delIds, lastInsertId)
   432  		} else if obv.PkType == "primitive.ObjectID" {
   433  			lastInsertId := utils.GetObjectID(utils.GetPtr(v, obv.PkOffset))
   434  			if IsNullObjectID(lastInsertId) {
   435  				return self.Error("[Mongo.Update] data object id is nil")
   436  			}
   437  			delIds = append(delIds, lastInsertId)
   438  		} else {
   439  			return self.Error("only Int64 and string and ObjectID type IDs are supported")
   440  		}
   441  	}
   442  	if len(delIds) > 0 {
   443  		if _, err := db.DeleteMany(self.GetSessionContext(), bson.M{"_id": bson.M{"$in": delIds}}); err != nil {
   444  			return self.Error("[Mongo.Delete] delete failed: ", err)
   445  		}
   446  	}
   447  	return nil
   448  }
   449  
   450  func (self *MGOManager) DeleteById(object sqlc.Object, data ...interface{}) (int64, error) {
   451  	if data == nil || len(data) == 0 {
   452  		return 0, self.Error("[Mongo.DeleteById] data is nil")
   453  	}
   454  	if len(data) > 2000 {
   455  		return 0, self.Error("[Mongo.DeleteById] data length > 2000")
   456  	}
   457  	d := object
   458  	if len(self.MGOSyncData) > 0 {
   459  		d = self.MGOSyncData[0].CacheModel
   460  	}
   461  	_, ok := modelDrivers[d.GetTable()]
   462  	if !ok {
   463  		return 0, self.Error("[Mongo.DeleteById] registration object type not found [", d.GetTable(), "]")
   464  	}
   465  	db, err := self.GetDatabase(d.GetTable())
   466  	if err != nil {
   467  		return 0, self.Error(err)
   468  	}
   469  	if zlog.IsDebug() {
   470  		defer zlog.Debug("[Mongo.DeleteById]", utils.UnixMilli(), zlog.Any("data", data))
   471  	}
   472  	if len(data) > 0 {
   473  		res, err := db.DeleteMany(self.GetSessionContext(), bson.M{"_id": bson.M{"$in": data}})
   474  		if err != nil {
   475  			return 0, self.Error("[Mongo.DeleteById] delete failed: ", err)
   476  		}
   477  		return res.DeletedCount, nil
   478  	}
   479  	return 0, nil
   480  }
   481  
   482  func (self *MGOManager) DeleteByCnd(cnd *sqlc.Cnd) (int64, error) {
   483  	if cnd.Model == nil {
   484  		return 0, self.Error("[Mongo.DeleteByCnd] data model is nil")
   485  	}
   486  	db, err := self.GetDatabase(cnd.Model.GetTable())
   487  	if err != nil {
   488  		return 0, err
   489  	}
   490  	match := buildMongoMatch(cnd)
   491  	if match == nil || len(match) == 0 {
   492  		return 0, self.Error("pipe match is nil")
   493  	}
   494  	defer self.writeLog("[Mongo.DeleteByCnd]", utils.UnixMilli(), map[string]interface{}{"match": match}, nil)
   495  	res, err := db.DeleteMany(self.GetSessionContext(), match)
   496  	if err != nil {
   497  		return 0, self.Error("[Mongo.DeleteByCnd] delete failed: ", err)
   498  	}
   499  	if res.DeletedCount == 0 {
   500  		return 0, self.Error("[Mongo.DeleteByCnd] delete failed: ModifiedCount = 0")
   501  	}
   502  	return res.DeletedCount, nil
   503  }
   504  
   505  func (self *MGOManager) Count(cnd *sqlc.Cnd) (int64, error) {
   506  	if cnd.Model == nil {
   507  		return 0, self.Error("[Mongo.Count] data model is nil")
   508  	}
   509  	db, err := self.GetDatabase(cnd.Model.GetTable())
   510  	if err != nil {
   511  		return 0, self.Error(err)
   512  	}
   513  	pipe := buildMongoMatch(cnd)
   514  	defer self.writeLog("[Mongo.Count]", utils.UnixMilli(), pipe, nil)
   515  	var pageTotal int64
   516  	if pipe == nil || len(pipe) == 0 {
   517  		pageTotal, err = db.EstimatedDocumentCount(self.GetSessionContext())
   518  	} else {
   519  		pageTotal, err = db.CountDocuments(self.GetSessionContext(), pipe)
   520  	}
   521  	if err != nil {
   522  		return 0, self.Error("[Mongo.Count] count failed: ", err)
   523  	}
   524  	//pageTotal, err = db.EstimatedDocumentCount(self.GetSessionContext(), pipe)
   525  	if pageTotal > 0 && cnd.Pagination.PageSize > 0 {
   526  		var pageCount int64
   527  		if pageTotal%cnd.Pagination.PageSize == 0 {
   528  			pageCount = pageTotal / cnd.Pagination.PageSize
   529  		} else {
   530  			pageCount = pageTotal/cnd.Pagination.PageSize + 1
   531  		}
   532  		cnd.Pagination.PageCount = pageCount
   533  	} else {
   534  		cnd.Pagination.PageCount = 0
   535  	}
   536  	cnd.Pagination.PageTotal = pageTotal
   537  	return pageTotal, nil
   538  }
   539  
   540  func (self *MGOManager) Exists(cnd *sqlc.Cnd) (bool, error) {
   541  	check, err := self.Count(cnd)
   542  	if err != nil {
   543  		return false, err
   544  	}
   545  	return check > 0, nil
   546  }
   547  
   548  func (self *MGOManager) FindOne(cnd *sqlc.Cnd, data sqlc.Object) error {
   549  	if data == nil {
   550  		return self.Error("[Mongo.FindOne] data is nil")
   551  	}
   552  	db, err := self.GetDatabase(data.GetTable())
   553  	if err != nil {
   554  		return self.Error(err)
   555  	}
   556  	pipe := buildMongoMatch(cnd)
   557  	opts := buildQueryOneOptions(cnd)
   558  	defer self.writeLog("[Mongo.FindOne]", utils.UnixMilli(), pipe, opts)
   559  	cur := db.FindOne(self.GetSessionContext(), pipe, opts...)
   560  	if err := cur.Decode(data); err != nil {
   561  		if err == mongo.ErrNoDocuments {
   562  			return nil
   563  		}
   564  		return self.Error(err)
   565  	}
   566  	return nil
   567  }
   568  
   569  func (self *MGOManager) FindOneComplex(cnd *sqlc.Cnd, data sqlc.Object) error {
   570  	return self.FindOne(cnd, data)
   571  }
   572  
   573  func (self *MGOManager) FindList(cnd *sqlc.Cnd, data interface{}) error {
   574  	if data == nil {
   575  		return self.Error("[Mongo.FindList] data is nil")
   576  	}
   577  	if cnd.Model == nil {
   578  		return self.Error("[Mongo.FindList] data model is nil")
   579  	}
   580  	db, err := self.GetDatabase(cnd.Model.GetTable())
   581  	if err != nil {
   582  		return self.Error(err)
   583  	}
   584  	if cnd.Pagination.IsFastPage { // 快速分页
   585  		if cnd.Pagination.FastPageSortCountQ { // 执行总条数统计
   586  			if _, err := self.Count(cnd); err != nil {
   587  				return err
   588  			}
   589  		}
   590  		key := cnd.Pagination.FastPageKey
   591  		sort := cnd.Pagination.FastPageSort
   592  		size := cnd.Pagination.PageSize
   593  		prevID := cnd.Pagination.FastPageParam[0]
   594  		lastID := cnd.Pagination.FastPageParam[1]
   595  		cnd.ResultSize(size)
   596  		if prevID == 0 && lastID == 0 {
   597  			cnd.Orderby(key, sort)
   598  			cnd.Pagination.FastPageSortParam = sort
   599  		}
   600  		if sort == sqlc.DESC_ {
   601  			if prevID > 0 {
   602  				cnd.Gt(key, prevID)
   603  				cnd.Pagination.FastPageSortParam = sqlc.ASC_
   604  			}
   605  			if lastID > 0 {
   606  				cnd.Lt(key, lastID)
   607  				cnd.Pagination.FastPageSortParam = sqlc.DESC_
   608  			}
   609  		} else if sort == sqlc.ASC_ {
   610  			if prevID > 0 {
   611  				cnd.Lt(key, prevID)
   612  				cnd.Pagination.FastPageSortParam = sqlc.DESC_
   613  			}
   614  			if lastID > 0 {
   615  				cnd.Gt(key, lastID)
   616  				cnd.Pagination.FastPageSortParam = sqlc.ASC_
   617  			}
   618  		} else {
   619  			panic("sort value invalid")
   620  		}
   621  	}
   622  	if !cnd.Pagination.IsOffset && cnd.Pagination.IsPage { // 常规分页
   623  		if _, err := self.Count(cnd); err != nil {
   624  			return err
   625  		}
   626  	}
   627  	pipe := buildMongoMatch(cnd)
   628  	opts := buildQueryOptions(cnd)
   629  	defer self.writeLog("[Mongo.FindList]", utils.UnixMilli(), pipe, opts)
   630  	cur, err := db.Find(self.GetSessionContext(), pipe, opts...)
   631  	if err != nil {
   632  		return self.Error("[Mongo.FindList] query failed: ", err)
   633  	}
   634  	if err := cur.All(self.GetSessionContext(), data); err != nil {
   635  		if err == mongo.ErrNoDocuments {
   636  			return nil
   637  		}
   638  		return self.Error(err)
   639  	}
   640  	return nil
   641  }
   642  
   643  func (self *MGOManager) FindListComplex(cnd *sqlc.Cnd, data interface{}) error {
   644  	return self.FindList(cnd, data)
   645  }
   646  
   647  func (self *MGOManager) Close() error {
   648  	if self.PackContext.Context != nil && self.PackContext.CancelFunc != nil {
   649  		self.PackContext.CancelFunc()
   650  	}
   651  	return nil
   652  }
   653  
   654  func (self *MGOManager) GetCollectionObject(o sqlc.Object) (*mongo.Collection, error) {
   655  	if o == nil {
   656  		return nil, self.Error("[Mongo.GetDBObject] model is nil")
   657  	}
   658  	db, err := self.GetDatabase(o.GetTable())
   659  	if err != nil {
   660  		return nil, self.Error(err)
   661  	}
   662  	return db, nil
   663  }
   664  
   665  func buildQueryOneOptions(cnd *sqlc.Cnd) []*options.FindOneOptions {
   666  	var optsArr []*options.FindOneOptions
   667  	project := buildMongoProject(cnd)
   668  	if project != nil && len(project) > 0 {
   669  		projectOpts := &options.FindOneOptions{}
   670  		projectOpts.SetProjection(project)
   671  		optsArr = append(optsArr, projectOpts)
   672  	}
   673  	sortBy := buildMongoSortBy(cnd)
   674  	if sortBy != nil && len(sortBy) > 0 {
   675  		d := bson.D{}
   676  		for _, v := range sortBy {
   677  			d = append(d, bson.E{Key: v.Key, Value: v.Sort})
   678  		}
   679  		sortByOpts := &options.FindOneOptions{}
   680  		if cnd.CollationConfig != nil {
   681  			sortByOpts.SetCollation(&options.Collation{
   682  				Locale:          cnd.CollationConfig.Locale,
   683  				CaseLevel:       cnd.CollationConfig.CaseLevel,
   684  				CaseFirst:       cnd.CollationConfig.CaseFirst,
   685  				Strength:        cnd.CollationConfig.Strength,
   686  				NumericOrdering: cnd.CollationConfig.NumericOrdering,
   687  				Alternate:       cnd.CollationConfig.Alternate,
   688  				MaxVariable:     cnd.CollationConfig.MaxVariable,
   689  				Normalization:   cnd.CollationConfig.Normalization,
   690  				Backwards:       cnd.CollationConfig.Backwards,
   691  			})
   692  		}
   693  		sortByOpts.SetSort(d)
   694  		optsArr = append(optsArr, sortByOpts)
   695  	}
   696  	return optsArr
   697  }
   698  
   699  func buildQueryOptions(cnd *sqlc.Cnd) []*options.FindOptions {
   700  	var optsArr []*options.FindOptions
   701  	project := buildMongoProject(cnd)
   702  	if project != nil && len(project) > 0 {
   703  		projectOpts := &options.FindOptions{}
   704  		projectOpts.SetProjection(project)
   705  		optsArr = append(optsArr, projectOpts)
   706  	}
   707  	sortBy := buildMongoSortBy(cnd)
   708  	if sortBy != nil && len(sortBy) > 0 {
   709  		d := bson.D{}
   710  		for _, v := range sortBy {
   711  			d = append(d, bson.E{Key: v.Key, Value: v.Sort})
   712  		}
   713  		sortByOpts := &options.FindOptions{}
   714  		if cnd.CollationConfig != nil {
   715  			sortByOpts.SetCollation(&options.Collation{
   716  				Locale:          cnd.CollationConfig.Locale,
   717  				CaseLevel:       cnd.CollationConfig.CaseLevel,
   718  				CaseFirst:       cnd.CollationConfig.CaseFirst,
   719  				Strength:        cnd.CollationConfig.Strength,
   720  				NumericOrdering: cnd.CollationConfig.NumericOrdering,
   721  				Alternate:       cnd.CollationConfig.Alternate,
   722  				MaxVariable:     cnd.CollationConfig.MaxVariable,
   723  				Normalization:   cnd.CollationConfig.Normalization,
   724  				Backwards:       cnd.CollationConfig.Backwards,
   725  			})
   726  		}
   727  		sortByOpts.SetSort(d)
   728  		optsArr = append(optsArr, sortByOpts)
   729  	}
   730  	offset, limit := buildMongoLimit(cnd)
   731  	if offset > 0 || limit > 0 {
   732  		pageOpts := &options.FindOptions{}
   733  		if offset > 0 {
   734  			pageOpts.SetSkip(offset)
   735  		}
   736  		if limit > 0 {
   737  			pageOpts.SetLimit(limit)
   738  		}
   739  		optsArr = append(optsArr, pageOpts)
   740  	}
   741  	return optsArr
   742  }
   743  
   744  // 获取最终pipe条件集合,包含$match $project $sort $skip $limit
   745  //func (self *MGOManager) buildPipeCondition(cnd *sqlc.Cnd, countBy bool) ([]interface{}, error) {
   746  //	match := buildMongoMatch(cnd)
   747  //	upset := buildMongoUpset(cnd)
   748  //	project := buildMongoProject(cnd)
   749  //	aggregate := buildMongoAggregate(cnd)
   750  //	sortBy := buildMongoSortBy(cnd)
   751  //	sample := buildMongoSample(cnd)
   752  //	pageInfo := buildMongoLimit(cnd)
   753  //	pipe := make([]interface{}, 0, 10)
   754  //	if match != nil && len(match) > 0 {
   755  //		pipe = append(pipe, map[string]interface{}{"$match": match})
   756  //	}
   757  //	if upset != nil && len(upset) > 0 {
   758  //		pipe = append(pipe, map[string]interface{}{"$set": upset})
   759  //	}
   760  //	if project != nil && len(project) > 0 {
   761  //		pipe = append(pipe, map[string]interface{}{"$project": project})
   762  //	}
   763  //	if aggregate != nil && len(aggregate) > 0 {
   764  //		for _, v := range aggregate {
   765  //			if len(v) == 0 {
   766  //				continue
   767  //			}
   768  //			pipe = append(pipe, v)
   769  //		}
   770  //	}
   771  //	if sample != nil {
   772  //		pipe = append(pipe, sample)
   773  //	}
   774  //	if !countBy && sortBy != nil && len(sortBy) > 0 {
   775  //		pipe = append(pipe, map[string]interface{}{"$sort": sortBy})
   776  //	}
   777  //	if !countBy && cnd.LimitSize > 0 {
   778  //		pipe = append(pipe, map[string]interface{}{"$limit": cnd.LimitSize})
   779  //	}
   780  //	if !countBy && pageInfo != nil && len(pageInfo) == 2 {
   781  //		pipe = append(pipe, map[string]interface{}{"$skip": pageInfo[0]})
   782  //		pipe = append(pipe, map[string]interface{}{"$limit": pageInfo[1]})
   783  //		if !cnd.CacheConfig.Open && !cnd.Pagination.IsOffset {
   784  //			pageTotal, err := self.Count(cnd)
   785  //			if err != nil {
   786  //				return nil, err
   787  //			}
   788  //			var pageCount int64
   789  //			if pageTotal%cnd.Pagination.PageSize == 0 {
   790  //				pageCount = pageTotal / cnd.Pagination.PageSize
   791  //			} else {
   792  //				pageCount = pageTotal/cnd.Pagination.PageSize + 1
   793  //			}
   794  //			cnd.Pagination.PageTotal = pageTotal
   795  //			cnd.Pagination.PageCount = pageCount
   796  //		}
   797  //	}
   798  //	if countBy {
   799  //		pipe = append(pipe, map[string]interface{}{"$count": COUNT_BY})
   800  //	}
   801  //	return pipe, nil
   802  //}
   803  
   804  // 构建mongo逻辑条件命令
   805  func buildMongoMatch(cnd *sqlc.Cnd) bson.M {
   806  	if len(cnd.Conditions) == 0 {
   807  		return nil
   808  	}
   809  	query := bson.M{}
   810  	for _, v := range cnd.Conditions {
   811  		key := v.Key
   812  		if key == JID {
   813  			key = BID
   814  		}
   815  		value := v.Value
   816  		values := v.Values
   817  		switch v.Logic {
   818  		// case condition
   819  		case sqlc.EQ_:
   820  			query[key] = value
   821  		case sqlc.NOT_EQ_:
   822  			query[key] = bson.M{"$ne": value}
   823  		case sqlc.LT_:
   824  			query[key] = bson.M{"$lt": value}
   825  		case sqlc.LTE_:
   826  			query[key] = bson.M{"$lte": value}
   827  		case sqlc.GT_:
   828  			query[key] = bson.M{"$gt": value}
   829  		case sqlc.GTE_:
   830  			query[key] = bson.M{"$gte": value}
   831  		case sqlc.IS_NULL_:
   832  			query[key] = nil
   833  		case sqlc.IS_NOT_NULL_:
   834  			// unsupported
   835  		case sqlc.BETWEEN_:
   836  			query[key] = bson.M{"$gte": values[0], "$lte": values[1]}
   837  		case sqlc.BETWEEN2_:
   838  			query[key] = bson.M{"$gte": values[0], "$lt": values[1]}
   839  		case sqlc.NOT_BETWEEN_:
   840  			// unsupported
   841  		case sqlc.IN_:
   842  			query[key] = bson.M{"$in": values}
   843  		case sqlc.NOT_IN_:
   844  			query[key] = bson.M{"$nin": values}
   845  		case sqlc.LIKE_:
   846  			query[key] = bson.M{"$regex": value}
   847  		case sqlc.NOT_LIKE_:
   848  			// unsupported
   849  		case sqlc.OR_:
   850  			if values == nil || len(values) == 0 {
   851  				continue
   852  			}
   853  			var array []interface{}
   854  			for _, v := range values {
   855  				cnd, ok := v.(*sqlc.Cnd)
   856  				if !ok {
   857  					continue
   858  				}
   859  				array = append(array, buildMongoMatch(cnd))
   860  			}
   861  			query["$or"] = array
   862  		}
   863  	}
   864  	return query
   865  }
   866  
   867  // 构建mongo字段筛选命令
   868  func buildMongoProject(cnd *sqlc.Cnd) bson.M {
   869  	if len(cnd.AnyFields) == 0 && len(cnd.AnyNotFields) == 0 {
   870  		return nil
   871  	}
   872  	project := bson.M{}
   873  	for _, v := range cnd.AnyFields {
   874  		if v == JID {
   875  			project[BID] = 1
   876  		} else {
   877  			project[v] = 1
   878  		}
   879  	}
   880  	for _, v := range cnd.AnyNotFields {
   881  		if v == JID {
   882  			project[BID] = 0
   883  		} else {
   884  			project[v] = 0
   885  		}
   886  	}
   887  	return project
   888  }
   889  
   890  func buildMongoAggregate(cnd *sqlc.Cnd) []map[string]interface{} {
   891  	if len(cnd.Groupbys) == 0 && len(cnd.Aggregates) == 0 {
   892  		return nil
   893  	}
   894  	group := make(map[string]interface{}, 5)
   895  	group2 := make(map[string]interface{}, 5)
   896  	project := make(map[string]interface{}, 10)
   897  	_idMap := make(map[string]interface{}, 5)
   898  	_idMap2 := make(map[string]interface{}, 5)
   899  	project[BID] = 0
   900  	if len(cnd.Groupbys) > 0 {
   901  		for _, v := range cnd.Groupbys {
   902  			if v == JID {
   903  				_idMap[JID] = utils.AddStr("$_id")
   904  				_idMap2[JID] = utils.AddStr("$_id.id")
   905  				project[BID] = utils.AddStr("$_id.id")
   906  				project[BID] = utils.AddStr("$_id.id")
   907  			} else {
   908  				_idMap[v] = utils.AddStr("$", v)
   909  				_idMap2[v] = utils.AddStr("$_id.", v)
   910  				project[v] = utils.AddStr("$_id.", v)
   911  			}
   912  		}
   913  		group[BID] = _idMap
   914  	}
   915  	if len(cnd.Aggregates) > 0 {
   916  		if _, b := group[BID]; !b {
   917  			group[BID] = 0
   918  		}
   919  		for _, v := range cnd.Aggregates {
   920  			k := v.Key
   921  			if k == JID {
   922  				if v.Logic == sqlc.SUM_ {
   923  					group[k] = map[string]string{"$sum": "$_id"}
   924  				} else if v.Logic == sqlc.MAX_ {
   925  					group[k] = map[string]string{"$max": "$_id"}
   926  				} else if v.Logic == sqlc.MIN_ {
   927  					group[k] = map[string]string{"$min": "$_id"}
   928  				} else if v.Logic == sqlc.AVG_ {
   929  					group[k] = map[string]string{"$avg": "$_id"}
   930  				}
   931  				project[BID] = utils.AddStr("$", k)
   932  			} else {
   933  				if v.Logic == sqlc.SUM_ {
   934  					group[k] = map[string]string{"$sum": utils.AddStr("$", k)}
   935  				} else if v.Logic == sqlc.MAX_ {
   936  					group[k] = map[string]string{"$max": utils.AddStr("$", k)}
   937  				} else if v.Logic == sqlc.MIN_ {
   938  					group[k] = map[string]string{"$min": utils.AddStr("$", k)}
   939  				} else if v.Logic == sqlc.AVG_ {
   940  					group[k] = map[string]string{"$avg": utils.AddStr("$", k)}
   941  				} else if v.Logic == sqlc.CNT_ {
   942  					if _, b := _idMap2[k]; b {
   943  						delete(_idMap2, k)
   944  					}
   945  					group2[v.Alias] = map[string]interface{}{"$sum": 1}
   946  					project[v.Alias] = 1
   947  					continue
   948  				}
   949  				project[v.Alias] = utils.AddStr("$", k)
   950  			}
   951  		}
   952  	}
   953  	var result []map[string]interface{}
   954  	if len(group) > 0 {
   955  		result = append(result, map[string]interface{}{"$group": group})
   956  	}
   957  	if len(group2) > 0 {
   958  		group2[BID] = _idMap2
   959  		result = append(result, map[string]interface{}{"$group": group2})
   960  	}
   961  	if len(project) > 0 {
   962  		result = append(result, map[string]interface{}{"$project": project})
   963  	}
   964  	return result
   965  }
   966  
   967  // 构建mongo字段更新命令
   968  func buildMongoUpset(cnd *sqlc.Cnd) bson.M {
   969  	if len(cnd.Upsets) == 0 {
   970  		return nil
   971  	}
   972  	upset := bson.M{}
   973  	for k, v := range cnd.Upsets {
   974  		if k == JID || k == BID {
   975  			continue
   976  		}
   977  		upset[k] = v
   978  	}
   979  	if len(upset) == 0 {
   980  		return nil
   981  	}
   982  	return bson.M{"$set": upset}
   983  }
   984  
   985  // 构建mongo排序命令
   986  func buildMongoSortBy(cnd *sqlc.Cnd) []SortBy {
   987  	var sortBys []SortBy
   988  	if cnd.Pagination.IsFastPage {
   989  		if cnd.Pagination.FastPageSortParam == sqlc.DESC_ {
   990  			sortBys = append(sortBys, SortBy{Key: getKey(cnd.Pagination.FastPageKey), Sort: -1})
   991  		} else {
   992  			sortBys = append(sortBys, SortBy{Key: getKey(cnd.Pagination.FastPageKey), Sort: 1})
   993  		}
   994  	}
   995  	for _, v := range cnd.Orderbys {
   996  		key := getKey(v.Key)
   997  		if key == getKey(cnd.Pagination.FastPageKey) {
   998  			continue
   999  		}
  1000  		if v.Value == sqlc.DESC_ {
  1001  			sortBys = append(sortBys, SortBy{Key: key, Sort: -1})
  1002  		} else {
  1003  			sortBys = append(sortBys, SortBy{Key: key, Sort: 1})
  1004  		}
  1005  	}
  1006  	return sortBys
  1007  }
  1008  
  1009  func getKey(key string) string {
  1010  	if key == JID {
  1011  		return BID
  1012  	}
  1013  	return key
  1014  }
  1015  
  1016  // 构建mongo随机选取命令
  1017  func buildMongoSample(cnd *sqlc.Cnd) bson.M {
  1018  	if cnd.SampleSize == 0 {
  1019  		return nil
  1020  	}
  1021  	return bson.M{"$sample": bson.M{"size": cnd.SampleSize}}
  1022  }
  1023  
  1024  // 构建mongo分页命令
  1025  func buildMongoLimit(cnd *sqlc.Cnd) (int64, int64) {
  1026  	if cnd.LimitSize > 0 { // 优先resultSize截取
  1027  		return 0, cnd.LimitSize
  1028  	}
  1029  	pg := cnd.Pagination
  1030  	if pg.PageNo == 0 && pg.PageSize == 0 {
  1031  		return 0, 0
  1032  	}
  1033  	if pg.PageSize <= 0 {
  1034  		pg.PageSize = 10
  1035  	}
  1036  	if pg.IsOffset {
  1037  		return pg.PageNo, pg.PageSize
  1038  	}
  1039  	pageNo := pg.PageNo
  1040  	pageSize := pg.PageSize
  1041  	return (pageNo - 1) * pageSize, pageSize
  1042  }
  1043  
  1044  func (self *MGOManager) writeLog(title string, start int64, pipe, opts interface{}) {
  1045  	cost := utils.UnixMilli() - start
  1046  	if self.SlowQuery > 0 && cost > self.SlowQuery {
  1047  		l := self.getSlowLog()
  1048  		if l != nil {
  1049  			if opts == nil {
  1050  				opts = &options.FindOptions{}
  1051  			}
  1052  			l.Warn(title, zlog.Int64("cost", cost), zlog.Any("pipe", pipe), zlog.Any("opts", opts))
  1053  		}
  1054  	}
  1055  	if zlog.IsDebug() {
  1056  		pipeStr, _ := utils.JsonMarshal(pipe)
  1057  		defer zlog.Debug(title, start, zlog.String("pipe", utils.Bytes2Str(pipeStr)), zlog.Any("opts", opts))
  1058  	}
  1059  }