package mdbc import ( "context" "encoding/json" "errors" "fmt" "reflect" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) type findOneAction string const ( findOne findOneAction = "findOne" findOneAndDelete findOneAction = "findOneAndDelete" findOneAndUpdate findOneAction = "findOneAndUpdate" findOneAndReplace findOneAction = "findOneAndReplace" ) type FindOneScope struct { scope *Scope cw *ctxWrap err error limit *int64 skip *int64 sort bson.D upsert *bool // 是否允许 upsert 操作 filter interface{} // 过滤条件 bindObject interface{} // 绑定结果 bindSQL *string // 绑定执行SQL enableCache bool // 允许缓存 cacheKey string // 缓存KEY cacheFunc FindOneCacheFunc // 缓存调用函数 action findOneAction // 标注什么动作 findOne findOneAndUpdate... fOpt *options.FindOneOptions dOpt *options.FindOneAndDeleteOptions uOpt *options.FindOneAndUpdateOptions rOpt *options.FindOneAndReplaceOptions result *mongo.SingleResult } // FindOneCacheFunc 创建缓存的key // field 是需要缓存的字段 当你需要基于结果obj进行操作 // obj 是结果 你可以对他进行操作 // defaultFindOneCacheFunc 是一个样例 基于obj某一个字段 取出其值 将其当作key进行结果缓存 // 返回 key, value error // key: 需要存储的key // value: 需要存储的value 请自行将其实例化 // error: callback错误信息 type FindOneCacheFunc func(field string, obj interface{}) (*CacheObject, error) // DefaultFindOneCacheFunc 基于obj的字段key 返回该key对应value 以及该obj的marshal string var DefaultFindOneCacheFunc = func() FindOneCacheFunc { return func(key string, obj interface{}) (co *CacheObject, err error) { // 建议先断言obj 再操作 若obj断言失败 请返回nil 这样缓存将不会执行 // 下面使用反射进行操作 保证兼容 v := reflect.ValueOf(obj) if v.Type().Kind() != reflect.Ptr { return nil, fmt.Errorf("obj not ptr") } v = v.Elem() idVal := v.FieldByName(key) if idVal.Kind() == reflect.Invalid { return nil, fmt.Errorf("%s field not found", key) } b, _ := json.Marshal(obj) co = &CacheObject{ Key: idVal.String(), Value: string(b), } return co, nil } } // SetLimit 设置获取的条数 func (fos *FindOneScope) SetLimit(limit int64) *FindOneScope { fos.limit = &limit return fos } // SetSkip 设置跳过的条数 func (fos *FindOneScope) SetSkip(skip int64) *FindOneScope { fos.skip = &skip return fos } // SetSort 设置排序 func (fos *FindOneScope) SetSort(sort bson.D) *FindOneScope { fos.sort = sort return fos } // SetContext 设置上下文 func (fos *FindOneScope) SetContext(ctx context.Context) *FindOneScope { if fos.cw == nil { fos.cw = &ctxWrap{} } fos.cw.ctx = ctx return fos } func (fos *FindOneScope) getContext() context.Context { return fos.cw.ctx } // SetUpsert 设置upsert属性 func (fos *FindOneScope) SetUpsert(upsert bool) *FindOneScope { fos.upsert = &upsert return fos } // SetFilter 设置过滤条件 func (fos *FindOneScope) SetFilter(filter interface{}) *FindOneScope { if filter == nil { fos.filter = bson.M{} return fos } v := reflect.ValueOf(filter) if v.Kind() == reflect.Ptr || v.Kind() == reflect.Map || v.Kind() == reflect.Slice { if v.IsNil() { fos.filter = bson.M{} } } fos.filter = filter return fos } // SetFindOneOption 设置FindOneOption 优先级较低 有 set 时参数将被重写 func (fos *FindOneScope) SetFindOneOption(opts options.FindOneOptions) *FindOneScope { fos.fOpt = &opts return fos } // SetFindOneAndDeleteOption 设置FindOneAndDeleteOption 优先级较低 有 set 时参数将被重写 func (fos *FindOneScope) SetFindOneAndDeleteOption(opts options.FindOneAndDeleteOptions) *FindOneScope { fos.dOpt = &opts return fos } // SetFindOneAndUpdateOption 设置FindOneAndUpdateOption 优先级较低 有 set 时参数将被重写 func (fos *FindOneScope) SetFindOneAndUpdateOption(opts options.FindOneAndUpdateOptions) *FindOneScope { fos.uOpt = &opts return fos } // SetFindOneAndReplaceOption 设置FindOneAndReplaceOption 优先级较低 有 set 时参数将被重写 func (fos *FindOneScope) SetFindOneAndReplaceOption(opts options.FindOneAndReplaceOptions) *FindOneScope { fos.rOpt = &opts return fos } // BindResult 绑定结果到一个变量中 // 一般在链式末尾没调用 Get(obj) 方法但想获取结果时使用 // 入参 r 必须是一个 *struct func (fos *FindOneScope) BindResult(r interface{}) *FindOneScope { vo := reflect.ValueOf(r) if vo.Kind() != reflect.Ptr { panic("BindResult arg r must be pointer") } vo = vo.Elem() if vo.Kind() != reflect.Struct { panic("BindResult arg r must be struct pointer") } fos.bindObject = r return fos } // BindSQL 获取本次执行的SQL信息 func (fos *FindOneScope) BindSQL(sql *string) *FindOneScope { fos.bindSQL = sql return fos } func (fos *FindOneScope) preCheck() { if fos.filter == nil { fos.filter = bson.M{} } var breakerTTL time.Duration if fos.scope.breaker == nil { breakerTTL = defaultBreakerTime } else if fos.scope.breaker.ttl == 0 { breakerTTL = defaultBreakerTime } else { breakerTTL = fos.scope.breaker.ttl } if fos.cw == nil { fos.cw = &ctxWrap{} } if fos.cw.ctx == nil { fos.cw.ctx, fos.cw.cancel = context.WithTimeout(context.Background(), breakerTTL) } } func (fos *FindOneScope) findOneOptionAssembled() { // 配置项被直接调用重写过 if fos.fOpt == nil { fos.fOpt = new(options.FindOneOptions) } if fos.skip != nil { fos.fOpt.Skip = fos.skip } if len(fos.sort) != 0 { fos.fOpt.Sort = fos.sort } } func (fos *FindOneScope) findOneDeleteOptionAssembled() { // 配置项被直接调用重写过 if fos.dOpt == nil { fos.dOpt = new(options.FindOneAndDeleteOptions) } if fos.sort != nil { fos.dOpt.Sort = fos.sort } } func (fos *FindOneScope) findOneUpdateOptionAssembled() { // 配置项被直接调用重写过 if fos.uOpt == nil { fos.uOpt = new(options.FindOneAndUpdateOptions) } if fos.sort != nil { fos.uOpt.Sort = fos.sort } if fos.upsert != nil { fos.uOpt.Upsert = fos.upsert } } func (fos *FindOneScope) findOneReplaceOptionAssembled() { // 配置项被直接调用重写过 if fos.rOpt == nil { fos.rOpt = new(options.FindOneAndReplaceOptions) } if fos.sort != nil { fos.rOpt.Sort = fos.sort } if fos.upsert != nil { fos.rOpt.Upsert = fos.upsert } } func (fos *FindOneScope) doString() string { filter, _ := bson.MarshalExtJSON(fos.filter, true, true) var query string switch fos.action { case findOne: query = fmt.Sprintf("findOne(%s)", string(filter)) case findOneAndDelete: query = fmt.Sprintf("findOneAndDelete(%s)", string(filter)) case findOneAndUpdate: query = fmt.Sprintf("findOneAndUpdate(%s)", string(filter)) case findOneAndReplace: query = fmt.Sprintf("findOneAndReplace(%s)", string(filter)) } if fos.skip != nil { query = fmt.Sprintf("%s.skip(%d)", query, *fos.skip) } if fos.limit != nil { query = fmt.Sprintf("%s.limit(%d)", query, *fos.limit) } if fos.sort != nil { sort, _ := bson.MarshalExtJSON(fos.sort, true, true) query = fmt.Sprintf("%s.sort(%s)", query, string(sort)) } return query } func (fos *FindOneScope) assertErr() { if fos.err == nil { return } if errors.Is(fos.err, mongo.ErrNoDocuments) || errors.Is(fos.err, mongo.ErrNilDocument) { fos.err = &ErrRecordNotFound return } if errors.Is(fos.err, context.DeadlineExceeded) { fos.err = &ErrRequestBroken return } err, ok := fos.err.(mongo.CommandError) if ok && err.HasErrorMessage(context.DeadlineExceeded.Error()) { fos.err = &ErrRequestBroken return } fos.err = err } func (fos *FindOneScope) debug() { if !fos.scope.debug && !fos.scope.debugWhenError && fos.bindSQL == nil { return } debugger := &Debugger{ collection: fos.scope.tableName, execT: fos.scope.execT, action: fos, } // 当错误时优先输出 if fos.scope.debugWhenError { if fos.err != nil { debugger.errMsg = fos.err.Error() debugger.ErrorString() } return } // 所有bug输出 if fos.scope.debug { debugger.String() } // 绑定 SQL if fos.bindSQL != nil { *fos.bindSQL = debugger.GetString() } } func (fos *FindOneScope) doSearch() { var starTime time.Time if fos.scope.debug { starTime = time.Now() } fos.result = db.Collection(fos.scope.tableName).FindOne(fos.getContext(), fos.filter, fos.fOpt) if fos.scope.debug { fos.scope.execT = time.Since(starTime) } fos.action = findOne fos.err = fos.result.Err() fos.assertErr() // debug fos.debug() } func (fos *FindOneScope) doUpdate(obj interface{}, isReplace bool) { if isReplace { var starTime time.Time if fos.scope.debug { starTime = time.Now() } fos.result = db.Collection(fos.scope.tableName).FindOneAndReplace(fos.getContext(), fos.filter, obj, fos.rOpt) if fos.scope.debug { fos.scope.execT = time.Since(starTime) } fos.action = findOneAndReplace fos.debug() fos.err = fos.result.Err() fos.assertErr() return } vo := reflect.ValueOf(obj) if vo.Kind() == reflect.Ptr { vo = vo.Elem() } if vo.Kind() == reflect.Struct { obj = bson.M{ "$set": obj, } } var starTime time.Time if fos.scope.debug { starTime = time.Now() } fos.result = db.Collection(fos.scope.tableName).FindOneAndUpdate(fos.getContext(), fos.filter, obj, fos.uOpt) if fos.scope.debug { fos.scope.execT = time.Since(starTime) } fos.action = findOneAndUpdate fos.err = fos.result.Err() fos.assertErr() fos.debug() } func (fos *FindOneScope) doDelete() { var starTime time.Time if fos.scope.debug { starTime = time.Now() } fos.result = db.Collection(fos.scope.tableName).FindOneAndDelete(fos.getContext(), fos.filter, fos.dOpt) if fos.scope.debug { fos.scope.execT = time.Since(starTime) } fos.action = findOneAndDelete fos.err = fos.result.Err() fos.assertErr() fos.debug() } func (fos *FindOneScope) doClear() { if fos.cw != nil && fos.cw.cancel != nil { fos.cw.cancel() } fos.scope.debug = false fos.scope.execT = 0 } // Get 传递一个指针类型来接收结果 // m: 必须是 *struct func (fos *FindOneScope) Get(m interface{}) error { defer fos.doClear() if fos.err != nil { return fos.err } fos.preCheck() fos.findOneOptionAssembled() fos.doSearch() if fos.err != nil { return fos.err } fos.err = fos.result.Decode(m) if fos.err != nil { return fos.err } // 进行缓存 if fos.enableCache { fos.doCache(m) return fos.err } return nil } // Delete 匹配一个并删除 func (fos *FindOneScope) Delete() error { defer fos.doClear() if fos.err != nil { return fos.err } fos.preCheck() fos.findOneDeleteOptionAssembled() fos.doDelete() if fos.err != nil { return fos.err } return nil } // Update 进行数据字段懒更新 // obj 传入 *struct 会 $set 自动包裹 // obj 传入 bson.M 不会 $set 自动包裹 func (fos *FindOneScope) Update(obj interface{}) error { defer fos.doClear() if fos.err != nil { return fos.err } fos.preCheck() fos.findOneUpdateOptionAssembled() fos.doUpdate(obj, false) if fos.err != nil { return fos.err } return nil } // Replace 进行数据字段全强制更新 func (fos *FindOneScope) Replace(obj interface{}) error { defer fos.doClear() if fos.err != nil { return fos.err } fos.preCheck() fos.findOneReplaceOptionAssembled() fos.doUpdate(obj, true) if fos.err != nil { return fos.err } return nil } // IsExist 是否存在 不存在返回 false, nil // 非 NotFound 错误将返回 false, error // 若找到了并想绑定结果 请在该方法前调用 BindResult func (fos *FindOneScope) IsExist() (bool, error) { defer fos.doClear() if fos.err != nil { return false, fos.err } fos.preCheck() fos.findOneOptionAssembled() fos.doSearch() if fos.err != nil { if IsRecordNotFound(fos.err) { return false, nil } return false, fos.err } if fos.bindObject != nil { fos.err = fos.result.Decode(fos.bindObject) if fos.err != nil { return false, fos.err } } return true, nil } func (fos *FindOneScope) Error() error { return fos.err } // SetCacheFunc 传递一个函数 处理查询操作的结果进行缓存 func (fos *FindOneScope) SetCacheFunc(key string, cb FindOneCacheFunc) *FindOneScope { fos.enableCache = true fos.cacheFunc = cb fos.cacheKey = key return fos } // doCache 执行缓存 func (fos *FindOneScope) doCache(obj interface{}) *FindOneScope { // redis句柄不存在 if fos.scope.cache == nil { return nil } cacheObj, err := fos.cacheFunc(fos.cacheKey, obj) if err != nil { fos.err = err return fos } if cacheObj == nil { fos.err = fmt.Errorf("cache object nil") return fos } ttl := fos.scope.cache.ttl if ttl == 0 { ttl = time.Hour } else if ttl == -1 { ttl = 0 } if fos.getContext().Err() != nil { fos.err = fos.getContext().Err() fos.assertErr() return fos } fos.err = fos.scope.cache.client.Set(fos.getContext(), cacheObj.Key, cacheObj.Value, ttl).Err() if fos.err != nil { fos.assertErr() } return fos }