package mdbc import ( "context" "errors" "fmt" "reflect" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) type deleteAction string const ( deleteOne deleteAction = "deleteOne" deleteMany deleteAction = "deleteMany" ) type DeleteScope struct { scope *Scope cw *ctxWrap err error upsert *bool id interface{} filter interface{} action deleteAction opts *options.DeleteOptions result *mongo.DeleteResult } // SetContext 设置上下文 func (ds *DeleteScope) SetContext(ctx context.Context) *DeleteScope { if ds.cw == nil { ds.cw = &ctxWrap{} } ds.cw.ctx = ctx return ds } // SetUpsert 设置upsert属性 func (ds *DeleteScope) SetUpsert(upsert bool) *DeleteScope { ds.upsert = &upsert return ds } // SetID 有设置ID优先基于ID删除 其次基于filter删除 func (ds *DeleteScope) SetID(id interface{}) *DeleteScope { ds.id = id ds.filter = bson.M{"_id": ds.id} return ds } // SetIDs 指定_id列表的方式进行删除 是 bson.M{"_id": {$in: []array}} 的快捷键 // 如果参数 ids 为空 将报错 func (ds *DeleteScope) SetIDs(ids []string) *DeleteScope { if len(ids) == 0 { ds.err = &ErrFilterEmpty return ds } ds.filter = bson.M{"_id": bson.M{"$in": ids}} return ds } // SetDeleteOption 设置删除选项 func (ds *DeleteScope) SetDeleteOption(opts options.DeleteOptions) *DeleteScope { ds.opts = &opts return ds } // SetFilter 设置过滤条件 // filter建议为map,因为它不需要进行额外的类型转换 // 若设置 filter 为struct 则会将第一级不为零值的属性平铺转换为map // 若设置 filter 为一个切片 但基于以下规则 获取不到任何值时 将触发PANIC // 若 filter 传递一个数组切片 如果结构为 []string/[]number/[]ObjectID 将被解析成 "_id": {"$in": []interface{}} // 若 filter 只传递一个参数(即filterFields 为空) []struct/[]*struct 将从struct元素的tag中抽取字段_id的bson标签组成id的数组切片 // 并组装成 "$in": []interface 然后将其解析为 "_id": {"$in": []interface{}} // 当然 在传递 []struct/[]*struct 后 可以指定第二参数 filterFields 用于获取 struct的指定字段(确保可访问该字段)的集合 作为查询条件 // 生成规则是: 基于可访问到的字段名 获取字段值 并按照其 bson_tag 将其组合成 $in 操作[无bson_tag则按传入的字段] // 示例结构: User struct: {Id int bson:_id} {Name string bson:name} {Age int bson: age} // 示例: ds.Many([]A{{Id: 1, Name: "张三", Age: 20},{Id: 2, Name: "李四", Age: 22}}, "Id", "Age") // 生成: filter := bson.M{"_id": {"$in": [1,2]}, "age": {"$in": [20, 22]}} func (ds *DeleteScope) SetFilter(filter interface{}, filterFields ...string) *DeleteScope { if filter == nil { ds.filter = bson.M{} return ds } // 空指针 vo := reflect.ValueOf(filter) if vo.Kind() == reflect.Ptr || vo.Kind() == reflect.Map || vo.Kind() == reflect.Slice || vo.Kind() == reflect.Interface { if vo.IsNil() { ds.filter = bson.M{} return ds } } if vo.Kind() == reflect.Ptr { vo = vo.Elem() } // 是一个结构体 剥离零值字段 if vo.Kind() == reflect.Struct { ds.filter = Struct2MapOmitEmptyWithBsonTag(filter) return ds } // 是一个map if vo.Kind() == reflect.Map { ds.filter = filter return ds } // 对于数组切片的struct将获取字段的和ID相关的数组转$in操作 if vo.Kind() == reflect.Array || vo.Kind() == reflect.Slice { var filterMap = make(map[string][]interface{}) // 对于数组切片的number,string,objectID for i := 0; i < vo.Len(); i++ { cvo := vo.Index(i) if cvo.Kind() == reflect.Ptr { cvo = cvo.Elem() } // 是数值类型 if isReflectNumber(cvo) { filterMap["_id"] = append(filterMap["_id"], cvo.Interface()) continue } // 是字符串 if cvo.Kind() == reflect.String { filterMap["_id"] = append(filterMap["_id"], cvo.Interface()) continue } // 来自mongo的ObjectID if cvo.Type().String() == "primitive.ObjectID" { filterMap["_id"] = append(filterMap["_id"], cvo.Interface()) continue } // 是struct 剥离字段 if cvo.Kind() == reflect.Struct { if len(filterFields) == 0 { for i := 0; i < cvo.Type().NumField(); i++ { field := cvo.Type().Field(i) if field.Tag.Get("bson") != "_id" { continue } kk := cvo.FieldByName(field.Name) if kk.IsValid() && !kk.IsZero() { filterMap["_id"] = append(filterMap["_id"], kk.Interface()) } } } else { for _, field := range filterFields { bsonName := getBsonTagByReflectTypeField(cvo.Type(), field) kk := cvo.FieldByName(field) if kk.IsValid() && !kk.IsZero() { filterMap[bsonName] = append(filterMap[bsonName], kk.Interface()) } } } } } if len(filterMap) != 0 { var f = bson.M{} for key, val := range filterMap { f[key] = bson.M{ "$in": val, } } ds.filter = f return ds } } // 不符合参数接受条件 直接PANIC panic("args invalid(required: map,struct,number slice,string slice,struct slice)") } func (ds *DeleteScope) optionAssembled() { // 配置项被直接调用重写过 if ds.opts != nil { return } ds.opts = new(options.DeleteOptions) } func (ds *DeleteScope) preCheck() { var breakerTTL time.Duration if ds.scope.breaker == nil { breakerTTL = defaultBreakerTime } else if ds.scope.breaker.ttl == 0 { breakerTTL = defaultBreakerTime } else { breakerTTL = ds.scope.breaker.ttl } if ds.cw == nil { ds.cw = &ctxWrap{} } if ds.cw.ctx == nil { ds.cw.ctx, ds.cw.cancel = context.WithTimeout(context.Background(), breakerTTL) } if ds.filter == nil { ds.err = &ErrFilterEmpty return } } func (ds *DeleteScope) assertErr() { if ds.err == nil { return } if errors.Is(ds.err, context.DeadlineExceeded) { ds.err = &ErrRequestBroken return } err, ok := ds.err.(mongo.CommandError) if !ok { return } if err.HasErrorMessage(context.DeadlineExceeded.Error()) { ds.err = &ErrRequestBroken } } func (ds *DeleteScope) getContext() context.Context { return ds.cw.ctx } func (ds *DeleteScope) doClear() { if ds.cw != nil && ds.cw.cancel != nil { ds.cw.cancel() } ds.scope.execT = 0 ds.scope.debug = false } func (ds *DeleteScope) debug() { if !ds.scope.debug && !ds.scope.debugWhenError { return } debugger := &Debugger{ collection: ds.scope.tableName, execT: ds.scope.execT, action: ds, } // 当错误时输出 if ds.scope.debugWhenError { if ds.err != nil { debugger.errMsg = ds.err.Error() debugger.ErrorString() } return } // 所有bug输出 if ds.scope.debug { debugger.String() } } func (ds *DeleteScope) doString() string { builder := RegisterTimestampCodec(nil).Build() filter, _ := bson.MarshalExtJSONWithRegistry(builder, ds.filter, true, true) switch ds.action { case deleteOne: return fmt.Sprintf("deleteOne(%s)", string(filter)) case deleteMany: return fmt.Sprintf("deleteMany(%s)", string(filter)) default: panic("not support delete type") } } func (ds *DeleteScope) doDelete(isMany bool) { if isMany { var starTime time.Time if ds.scope.debug { starTime = time.Now() } ds.result, ds.err = db.Collection(ds.scope.tableName).DeleteMany(ds.getContext(), ds.filter, ds.opts) ds.assertErr() if ds.scope.debug { ds.action = deleteMany ds.scope.execT = time.Since(starTime) ds.debug() } return } var starTime time.Time if ds.scope.debug { starTime = time.Now() } ds.result, ds.err = db.Collection(ds.scope.tableName).DeleteOne(ds.getContext(), ds.filter, ds.opts) ds.assertErr() if ds.scope.debug { ds.action = deleteOne ds.scope.execT = time.Since(starTime) ds.debug() } } // One 删除单个文档 返回影响行数 func (ds *DeleteScope) One() (int64, error) { defer ds.doClear() ds.optionAssembled() ds.preCheck() if ds.err != nil { return 0, ds.err } ds.doDelete(false) if ds.err != nil { return 0, ds.err } return ds.result.DeletedCount, nil } // Many 删除多个文档 返回影响行数 func (ds *DeleteScope) Many() (int64, error) { defer ds.doClear() ds.optionAssembled() ds.preCheck() if ds.err != nil { return 0, ds.err } ds.doDelete(true) if ds.err != nil { return 0, ds.err } return ds.result.DeletedCount, nil }