mdbc/find_one_scope.go

592 lines
13 KiB
Go
Raw Normal View History

2022-02-23 08:59:45 +00:00
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
}