592 lines
13 KiB
Go
592 lines
13 KiB
Go
|
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
|
||
|
}
|