@@ -0,0 +1,108 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/bsoncodec"
|
||||
"go.mongodb.org/mongo-driver/bson/bsonrw"
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
var registry = func() *bsoncodec.Registry {
|
||||
builder := bson.NewRegistryBuilder()
|
||||
builder.RegisterTypeDecoder(reflect.TypeOf(time.Time{}), &localTimeDecoder{})
|
||||
builder.RegisterTypeDecoder(reflect.TypeOf(decimal.Decimal{}), &decimalDecoder{})
|
||||
builder.RegisterTypeEncoder(reflect.TypeOf(decimal.Decimal{}), &decimalEncoder{})
|
||||
builder.RegisterDefaultDecoder(reflect.Float32, &floatDecoder{})
|
||||
builder.RegisterDefaultDecoder(reflect.Float64, &floatDecoder{})
|
||||
return builder.Build()
|
||||
}()
|
||||
|
||||
type floatDecoder struct {
|
||||
}
|
||||
|
||||
func (dvd *floatDecoder) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
|
||||
var f float64
|
||||
var err error
|
||||
switch vr.Type() {
|
||||
case bsontype.Int32:
|
||||
i32, err := vr.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f = float64(i32)
|
||||
case bsontype.Int64:
|
||||
i64, err := vr.ReadInt64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f = float64(i64)
|
||||
case bsontype.Double:
|
||||
f, err = vr.ReadDouble()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cannot decode %v into a float32 or float64 type", vr.Type())
|
||||
}
|
||||
val.SetFloat(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
type localTimeDecoder struct{}
|
||||
|
||||
func (*localTimeDecoder) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
|
||||
if err := (&bsoncodec.TimeCodec{}).DecodeValue(dc, vr, val); err != nil {
|
||||
return err
|
||||
}
|
||||
t := val.Interface().(time.Time)
|
||||
val.Set(reflect.ValueOf(t.Local()))
|
||||
return nil
|
||||
}
|
||||
|
||||
type decimalDecoder struct{}
|
||||
|
||||
func (*decimalDecoder) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
|
||||
if !val.IsValid() || val.Type() != reflect.TypeOf(decimal.Decimal{}) {
|
||||
return bsoncodec.ValueDecoderError{Name: "DecimalDecodeValue", Types: []reflect.Type{reflect.TypeOf(decimal.Decimal{})}, Received: val}
|
||||
}
|
||||
if vr.Type() == bson.TypeInt32 || vr.Type() == bson.TypeInt64 {
|
||||
_, _ = vr.ReadInt32()
|
||||
_, _ = vr.ReadInt64()
|
||||
d := decimal.NewFromFloat(0.0)
|
||||
val.Set(reflect.ValueOf(d))
|
||||
return nil
|
||||
}
|
||||
mongodecimal, err := vr.ReadDecimal128()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d, err := decimal.NewFromString(mongodecimal.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val.Set(reflect.ValueOf(d))
|
||||
return nil
|
||||
}
|
||||
|
||||
type decimalEncoder struct{}
|
||||
|
||||
func (*decimalEncoder) EncodeValue(ctx bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
|
||||
if !val.IsValid() || val.Type() != reflect.TypeOf(decimal.Decimal{}) {
|
||||
return bsoncodec.ValueDecoderError{Name: "DecimalEncodeValue", Types: []reflect.Type{reflect.TypeOf(decimal.Decimal{})}, Received: val}
|
||||
}
|
||||
if d, ok := val.Interface().(decimal.Decimal); ok {
|
||||
mongodecimal, err := primitive.ParseDecimal128(d.StringFixed(2))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val = reflect.ValueOf(mongodecimal)
|
||||
}
|
||||
dve := bsoncodec.DefaultValueEncoders{}
|
||||
return dve.Decimal128EncodeValue(ctx, vw, val)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package db
|
||||
|
||||
import "go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
func IsMongoDupKey(err error) bool {
|
||||
return mongo.IsDuplicateKeyError(err)
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/common/stderr"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
var (
|
||||
enableSortMCheck = true
|
||||
bsonDType = reflect.TypeOf(bson.D{})
|
||||
bsonDPtrType = reflect.TypeOf(&bson.D{})
|
||||
bsonEType = reflect.TypeOf(bson.E{})
|
||||
bsonEPtrType = reflect.TypeOf(&bson.E{})
|
||||
bsonMType = reflect.TypeOf(bson.M{})
|
||||
bsonMPtrType = reflect.TypeOf(&bson.M{})
|
||||
)
|
||||
|
||||
var skipErrors = []error{mongo.ErrNoDocuments}
|
||||
|
||||
func handleDbError(err error) error {
|
||||
for _, e := range skipErrors {
|
||||
if err == e {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if mongo.IsDuplicateKeyError(err) {
|
||||
return stderr.InsertExistError
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func sortCheck(sort interface{}) error {
|
||||
if !enableSortMCheck {
|
||||
return nil
|
||||
}
|
||||
if sort == nil {
|
||||
return nil
|
||||
}
|
||||
typ := reflect.TypeOf(sort)
|
||||
var m bson.M
|
||||
switch typ {
|
||||
case bsonMType:
|
||||
//log.Warn("mongo sort use bson.M use bson.D instead", log.Any("sort", sort))
|
||||
m, _ = sort.(bson.M)
|
||||
case bsonMPtrType:
|
||||
//log.Warn("mongo sort use *bson.M use bson.D instead", log.Any("sort", sort))
|
||||
pm, _ := sort.(*bson.M)
|
||||
m = *pm
|
||||
case bsonDType, bsonDPtrType, bsonEType, bsonEPtrType:
|
||||
return nil
|
||||
default:
|
||||
log.Warn("sort use unknown sort type please check", log.Any("sort", sort), log.Any("typ", typ))
|
||||
return errors.New("mongo error sort type")
|
||||
}
|
||||
if len(m) > 1 {
|
||||
return errors.New("mongo error sort, use bson.M and len(sort) > 1")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateIndex 创建数据索引.options 在index里面创建
|
||||
func (u *MongoTool) CreateIndex(models []mongo.IndexModel) ([]string, error) {
|
||||
//return nil, nil
|
||||
return u.coll.Indexes().CreateMany(u.ctx, models)
|
||||
}
|
||||
|
||||
// DropIndex 删除数据索引.options 在index里面创建
|
||||
func (u *MongoTool) DropIndex(indexname string) error {
|
||||
if _, err := u.coll.Indexes().DropOne(u.ctx, indexname); err != nil {
|
||||
log.Error(fmt.Sprintf("drop indexes error %+v", err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DropIndexIfExists 删除指定索引;索引或所在集合尚未创建时均视为成功。
|
||||
func (u *MongoTool) DropIndexIfExists(indexname string) error {
|
||||
if _, err := u.coll.Indexes().DropOne(u.ctx, indexname); err != nil {
|
||||
var commandErr mongo.CommandError
|
||||
// 27=IndexNotFound(索引不存在)、26=NamespaceNotFound(集合/库尚未创建):目标索引本就不存在,视为成功。
|
||||
if errors.As(err, &commandErr) && (commandErr.Code == 27 || commandErr.Code == 26) {
|
||||
return nil
|
||||
}
|
||||
log.Error(fmt.Sprintf("drop indexes error %+v", err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertOne 插入单条信息
|
||||
func (u *MongoTool) InsertOne(document interface{}, opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) {
|
||||
insertResult, err := u.coll.InsertOne(u.ctx, document, opts...)
|
||||
return insertResult, handleDbError(err)
|
||||
}
|
||||
|
||||
// InsertMany 批量插入信息
|
||||
func (u *MongoTool) InsertMany(documents interface{}, opts ...*options.InsertManyOptions) (*mongo.InsertManyResult, error) {
|
||||
if err := validInterfaceSlice(documents); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := setTimeForSlice(documents)
|
||||
return u.coll.InsertMany(u.ctx, res, opts...)
|
||||
}
|
||||
|
||||
// Find 查询多条数据
|
||||
func (u *MongoTool) Find(model interface{}, filter bson.M, opts ...*options.FindOptions) error {
|
||||
if err := validInterfaceSlice(model); err != nil {
|
||||
return err
|
||||
}
|
||||
cur, err := u.FindCursor(filter, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return handleDbError(cur.All(u.ctx, model))
|
||||
}
|
||||
|
||||
// FindCursor 查询多条数据并返回游标。
|
||||
// Cursor 不是并发安全的,调用方必须在完成或失败后关闭它。
|
||||
func (u *MongoTool) FindCursor(filter bson.M, opts ...*options.FindOptions) (*mongo.Cursor, error) {
|
||||
for _, opt := range opts {
|
||||
if opt == nil {
|
||||
continue
|
||||
}
|
||||
if opt.Limit != nil && *opt.Limit > 1000 {
|
||||
fmt.Println("limit beyond 1000 ==================>", *opt.Limit)
|
||||
}
|
||||
if opt.Sort != nil {
|
||||
if err := sortCheck(opt.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return u.coll.Find(u.ctx, filter, opts...)
|
||||
}
|
||||
|
||||
// FindOne 单条查询
|
||||
func (u *MongoTool) FindOne(model interface{}, filter bson.M, opts ...*options.FindOneOptions) error {
|
||||
for _, opt := range opts {
|
||||
if opt.Sort != nil {
|
||||
if err := sortCheck(opt.Sort); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return handleDbError(u.coll.FindOne(u.ctx, filter, opts...).Decode(model))
|
||||
}
|
||||
|
||||
// FindOneAndDelete 单条查询并删除
|
||||
func (u *MongoTool) FindOneAndDelete(model interface{}, filter bson.M, opts ...*options.FindOneAndDeleteOptions) error {
|
||||
for _, opt := range opts {
|
||||
if opt.Sort != nil {
|
||||
if err := sortCheck(opt.Sort); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return handleDbError(u.coll.FindOneAndDelete(u.ctx, filter, opts...).Decode(model))
|
||||
}
|
||||
|
||||
// FindOneAndReplace 单条查询 rd set to Before 表示返回原始数据, set to After 表示返回替换后的数据
|
||||
func (u *MongoTool) FindOneAndReplace(model interface{}, filter bson.M, replacement bson.M, opts ...*options.FindOneAndReplaceOptions) error {
|
||||
for _, opt := range opts {
|
||||
if opt.Sort != nil {
|
||||
if err := sortCheck(opt.Sort); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return handleDbError(u.coll.FindOneAndReplace(u.ctx, filter, replacement, opts...).Decode(model))
|
||||
}
|
||||
|
||||
// FindOneAndUpdate 单条查询 rd set to Before 表示返回原始数据, set to After 表示返回更新后的数据 默认为返回更新后的数据
|
||||
func (u *MongoTool) FindOneAndUpdate(model interface{}, filter bson.M, update bson.M, opts ...*options.FindOneAndUpdateOptions) error {
|
||||
for _, opt := range opts {
|
||||
if opt.Sort != nil {
|
||||
if err := sortCheck(opt.Sort); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
setReturn := false
|
||||
for _, opt := range opts {
|
||||
if opt != nil && opt.ReturnDocument != nil {
|
||||
setReturn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !setReturn {
|
||||
after := options.After
|
||||
opts = append(opts, &options.FindOneAndUpdateOptions{ReturnDocument: &after})
|
||||
}
|
||||
return handleDbError(u.coll.FindOneAndUpdate(u.ctx, filter, update, opts...).Decode(model))
|
||||
}
|
||||
|
||||
// FindOneAndUpsert 单条查询 匹配到数据更新,未匹配到数据则upsert
|
||||
func (u *MongoTool) FindOneAndUpsert(model interface{}, filter bson.M, update bson.M, opts ...*options.FindOneAndUpdateOptions) error {
|
||||
upsert := true
|
||||
var beforeOrAfter options.ReturnDocument
|
||||
for _, opt := range opts {
|
||||
if opt.Sort != nil {
|
||||
if err := sortCheck(opt.Sort); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
setReturn := false
|
||||
for _, opt := range opts {
|
||||
if opt != nil && opt.ReturnDocument != nil {
|
||||
beforeOrAfter = *opt.ReturnDocument
|
||||
setReturn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !setReturn {
|
||||
beforeOrAfter = options.After
|
||||
}
|
||||
opts = append(opts, &options.FindOneAndUpdateOptions{ReturnDocument: &beforeOrAfter, Upsert: &upsert})
|
||||
return handleDbError(u.coll.FindOneAndUpdate(u.ctx, filter, update, opts...).Decode(model))
|
||||
}
|
||||
|
||||
func (u *MongoTool) FindOneAndUpdateReturnTiny(bind interface{}, query bson.M, update bson.M, afterDoc bool, opts ...*options.FindOneAndUpdateOptions) error {
|
||||
if afterDoc {
|
||||
opts = append(opts, options.FindOneAndUpdate().SetReturnDocument(options.After))
|
||||
} else {
|
||||
opts = append(opts, options.FindOneAndUpdate().SetReturnDocument(options.Before))
|
||||
}
|
||||
result := handleDbError(u.coll.FindOneAndUpdate(u.ctx, query, update, opts...).Decode(bind))
|
||||
return result
|
||||
}
|
||||
|
||||
// FindOneByID 通过id查找一条数据
|
||||
func (u *MongoTool) FindOneByID(model interface{}, id primitive.ObjectID, opts ...*options.FindOneOptions) error {
|
||||
for _, opt := range opts {
|
||||
if opt.Sort != nil {
|
||||
if err := sortCheck(opt.Sort); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return handleDbError(u.coll.FindOne(u.ctx, bson.M{"_id": id}, opts...).Decode(model))
|
||||
}
|
||||
|
||||
// Aggregate 聚合查找数据
|
||||
func (u *MongoTool) Aggregate(model interface{}, pipeline []bson.M, opts ...*options.AggregateOptions) error {
|
||||
if err := validInterfaceSlice(model); err != nil {
|
||||
return err
|
||||
}
|
||||
cur, err := u.coll.Aggregate(u.ctx, pipeline, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return handleDbError(cur.All(u.ctx, model))
|
||||
}
|
||||
|
||||
// AggregateDecode 聚合.Decode
|
||||
func (u *MongoTool) AggregateDecode(model interface{}, pipeline []bson.M, opts ...*options.AggregateOptions) error {
|
||||
cur, err := u.coll.Aggregate(u.ctx, pipeline, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cur.Next(u.ctx) {
|
||||
return handleDbError(cur.Decode(model))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Distinct 去重查询
|
||||
func (u *MongoTool) Distinct(fieldName string, filter bson.M, opts ...*options.DistinctOptions) ([]interface{}, error) {
|
||||
return u.coll.Distinct(u.ctx, fieldName, filter, opts...)
|
||||
}
|
||||
|
||||
// DeleteOne 删除一条数据
|
||||
func (u *MongoTool) DeleteOne(filter bson.M, opt ...*options.DeleteOptions) (*mongo.DeleteResult, error) {
|
||||
return u.coll.DeleteOne(u.ctx, filter, opt...)
|
||||
}
|
||||
|
||||
// DeleteMany 删除多条数据
|
||||
func (u *MongoTool) DeleteMany(filter bson.M, opt ...*options.DeleteOptions) (*mongo.DeleteResult, error) {
|
||||
return u.coll.DeleteMany(u.ctx, filter, opt...)
|
||||
}
|
||||
|
||||
// DeleteById 根据ID删除数据单条数据
|
||||
func (u *MongoTool) DeleteById(id primitive.ObjectID) (*mongo.DeleteResult, error) {
|
||||
return u.coll.DeleteOne(u.ctx, bson.M{"_id": id})
|
||||
}
|
||||
|
||||
// UpdateOne 更新单条数据
|
||||
func (u *MongoTool) UpdateOne(filter bson.M, update interface{}) (*mongo.UpdateResult, error) {
|
||||
return u.coll.UpdateOne(u.ctx, filter, update)
|
||||
}
|
||||
|
||||
// UpdateMany 修改多条数据
|
||||
func (u *MongoTool) UpdateMany(filter bson.M, update bson.M) (*mongo.UpdateResult, error) {
|
||||
return u.coll.UpdateMany(u.ctx, filter, update)
|
||||
}
|
||||
|
||||
// UpsertMany 或者修改或者插入多条数据
|
||||
func (u *MongoTool) UpsertMany(filter bson.M, update bson.M) (*mongo.UpdateResult, error) {
|
||||
return u.coll.UpdateMany(u.ctx, filter, update, options.Update().SetUpsert(true))
|
||||
}
|
||||
|
||||
// UpsertOne 或者修改或者插入一条数据
|
||||
func (u *MongoTool) UpsertOne(filter bson.M, update bson.M) (*mongo.UpdateResult, error) {
|
||||
return u.coll.UpdateOne(u.ctx, filter, update, options.Update().SetUpsert(true))
|
||||
}
|
||||
|
||||
// UpdateOneForSet 修改一条数据 【根据修改数据中集合类型字段】
|
||||
func (u *MongoTool) UpdateOneForSet(filter bson.M, update bson.D) (*mongo.UpdateResult, error) {
|
||||
return u.coll.UpdateOne(u.ctx, filter, update)
|
||||
}
|
||||
|
||||
// Count 获取数量
|
||||
func (u *MongoTool) Count(filter interface{}, opts ...*options.CountOptions) (int64, error) {
|
||||
if reflect.TypeOf(filter).Kind() == reflect.Slice {
|
||||
cur, err := u.coll.Aggregate(u.ctx, filter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var count int64 = 0
|
||||
for cur.Next(context.TODO()) {
|
||||
count++
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
return u.coll.CountDocuments(u.ctx, filter, opts...)
|
||||
}
|
||||
|
||||
// EstimateCount 获取数量
|
||||
func (u *MongoTool) EstimateCount(opts ...*options.EstimatedDocumentCountOptions) (int64, error) {
|
||||
return u.coll.EstimatedDocumentCount(u.ctx, opts...)
|
||||
}
|
||||
|
||||
// Bulk Bulk
|
||||
func (u *MongoTool) Bulk(models []mongo.WriteModel, opts ...*options.BulkWriteOptions) (*mongo.BulkWriteResult, error) {
|
||||
return u.coll.BulkWrite(u.ctx, models, opts...)
|
||||
}
|
||||
|
||||
// Exists 是否存在数据
|
||||
func (u *MongoTool) Exists(filter interface{}, opts ...*options.FindOneOptions) (bool, error) {
|
||||
var limit int64 = 1
|
||||
lo := &options.CountOptions{Limit: &limit}
|
||||
n, err := u.Count(filter, lo)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
func setTimeForSlice(docs interface{}) []interface{} {
|
||||
arr := reflect.ValueOf(docs)
|
||||
if arr.Kind() == reflect.Ptr {
|
||||
arr = reflect.ValueOf(docs).Elem()
|
||||
}
|
||||
result := make([]interface{}, arr.Len())
|
||||
for i := 0; i < arr.Len(); i++ {
|
||||
ele := arr.Index(i)
|
||||
now := time.Now()
|
||||
if ma := ele.FieldByName("UpdatedAt"); ma.IsValid() {
|
||||
ma.Set(reflect.ValueOf(now))
|
||||
}
|
||||
if ca := ele.FieldByName("CreatedAt"); ca.IsValid() {
|
||||
ca.Set(reflect.ValueOf(now))
|
||||
}
|
||||
result[i] = ele.Interface()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func validInterfaceSlice(bind interface{}) error {
|
||||
t := reflect.TypeOf(bind)
|
||||
k := t.Kind()
|
||||
if t.Kind() == reflect.Ptr {
|
||||
k = t.Elem().Kind()
|
||||
}
|
||||
if k != reflect.Slice {
|
||||
return stderr.MustSliceOrSlicePtr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"91porn-server/common/log"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
|
||||
)
|
||||
|
||||
var dataSource = make(map[string]*MongoDB)
|
||||
var registerPool []Register
|
||||
|
||||
// DBmap
|
||||
type DBmap struct {
|
||||
Key string
|
||||
URL string
|
||||
}
|
||||
|
||||
type Register struct {
|
||||
Key string
|
||||
Table []string
|
||||
}
|
||||
|
||||
// MongoOptions 数据库配置
|
||||
type MongoOptions struct {
|
||||
URL string `json:"url"` // 服务器连接地址
|
||||
}
|
||||
|
||||
// MongoDB MongoDB
|
||||
type MongoDB struct {
|
||||
db *mongo.Database
|
||||
}
|
||||
|
||||
func (m *MongoDB) Tool() *MongoTool {
|
||||
return m.ToolCtx(context.Background())
|
||||
}
|
||||
|
||||
func (m *MongoDB) ToolCtx(ctx context.Context) *MongoTool {
|
||||
return &MongoTool{db: m.db, ctx: ctx}
|
||||
}
|
||||
|
||||
// Coll 获取表名
|
||||
func (m *MongoDB) Coll(name string) *MongoTool {
|
||||
t := m.Tool()
|
||||
return t.Coll(name)
|
||||
}
|
||||
|
||||
// CollCtx 获取表名 从外部传入ctx
|
||||
func (m *MongoDB) CollCtx(ctx context.Context, name string) *MongoTool {
|
||||
t := m.ToolCtx(ctx)
|
||||
return t.Coll(name)
|
||||
}
|
||||
|
||||
// MongoTool mongo官方库事务封装
|
||||
type MongoTool struct {
|
||||
db *mongo.Database
|
||||
ctx context.Context // 当前使用的ctx
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (m *MongoTool) Coll(name string) *MongoTool {
|
||||
opt := options.CollectionOptions{Registry: registry}
|
||||
m.coll = m.db.Collection(name, &opt)
|
||||
return m
|
||||
}
|
||||
|
||||
// Trans 开启事务处理包裹处理,里面处理的全是利用的事务的ctx
|
||||
func (m *MongoDB) Trans(fn func(*MongoTool) error, opts ...*TransOpts) error {
|
||||
return m.TransCtx(context.Background(), fn, opts...)
|
||||
}
|
||||
|
||||
// TransCtx 外部传入ctx
|
||||
func (m *MongoDB) TransCtx(ctx context.Context, fn func(*MongoTool) error, opts ...*TransOpts) error {
|
||||
t := m.ToolCtx(ctx)
|
||||
return m.db.Client().UseSession(ctx, func(sessionContext mongo.SessionContext) error {
|
||||
if err := sessionContext.StartTransaction(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Warn(fmt.Sprintf("caught panic during transaction, aborting. err: %+v, stack: %s", err, debug.Stack()))
|
||||
if err := sessionContext.AbortTransaction(sessionContext); err != nil {
|
||||
log.Warn("mongo AbortTransaction panic err", log.E(err))
|
||||
}
|
||||
}
|
||||
sessionContext.EndSession(sessionContext)
|
||||
}()
|
||||
t.ctx = sessionContext
|
||||
if err := runTransactionWithRetry(t, fn, MergeTransOpts(opts)); err != nil {
|
||||
if strings.Contains(err.Error(), "NoSuchTransaction") {
|
||||
log.Warn("NoSuchTransaction error, return")
|
||||
return err
|
||||
}
|
||||
if err := sessionContext.AbortTransaction(sessionContext); err != nil {
|
||||
log.Warn("mongo AbortTransaction err", log.E(err))
|
||||
}
|
||||
log.Warn("caught exception during transaction, aborting.", log.E(err))
|
||||
sessionContext.EndSession(sessionContext)
|
||||
return err
|
||||
}
|
||||
return commitWithRetry(sessionContext)
|
||||
})
|
||||
}
|
||||
|
||||
// runTransactionWithRetry is an example function demonstrating transaction retry logic.
|
||||
func runTransactionWithRetry(t *MongoTool, txnFn func(t *MongoTool) error, opts *TransOpts) error {
|
||||
//no set ReEntryCount is loop retry
|
||||
for {
|
||||
err := txnFn(t) // Performs transaction.
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
log.Warn("Transaction aborted. Caught exception during transaction.", log.E(err))
|
||||
// If transient error, retry the whole transaction
|
||||
if strings.Contains(err.Error(), "NoSuchTransaction") {
|
||||
log.Info("NoSuchTransaction error,return and break retry loop")
|
||||
return err
|
||||
}
|
||||
cmdErr, ok := err.(mongo.CommandError)
|
||||
if ok && cmdErr.HasErrorLabel("TransientTransactionError") {
|
||||
if opts != nil {
|
||||
if opts.ReEntryCount != nil {
|
||||
if *opts.ReEntryCount <= 0 {
|
||||
return cmdErr
|
||||
}
|
||||
*opts.ReEntryCount--
|
||||
}
|
||||
}
|
||||
log.Info("TransientTransactionError, retrying transaction...")
|
||||
continue
|
||||
}
|
||||
// else return err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// commitWithRetry is an example function demonstrating transaction retry logic.
|
||||
func commitWithRetry(sess mongo.SessionContext) error {
|
||||
for {
|
||||
err := sess.CommitTransaction(sess)
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
log.Info("Transaction committed.")
|
||||
return nil
|
||||
case mongo.CommandError:
|
||||
// Can retry commit
|
||||
if e.HasErrorLabel("UnknownTransactionCommitResult") {
|
||||
log.Info("UnknownTransactionCommitResult, retrying commit operation...")
|
||||
continue
|
||||
}
|
||||
log.Info("Error during commit...")
|
||||
return e
|
||||
default:
|
||||
log.Info("Error during commit...")
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect 关闭数据库连接
|
||||
func (m *MongoDB) Disconnect() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
log.Info("closing mongodb connections")
|
||||
defer cancel()
|
||||
if err := m.db.Client().Disconnect(ctx); err != nil {
|
||||
log.Warn(fmt.Sprintf("close mongo connections err: %+v", err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitMongo 初始化Mongo
|
||||
func InitMongo(murl string) (*MongoDB, error) {
|
||||
cs, err := connstring.Parse(murl)
|
||||
if err != nil {
|
||||
log.Error("mongo URL parse fail", log.Any("url", murl), log.E(err))
|
||||
return nil, err
|
||||
}
|
||||
db := cs.Database
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
// Connect to MongoDB
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(murl))
|
||||
if err != nil {
|
||||
log.Error("mongodb connect fail", log.E(err))
|
||||
return nil, err
|
||||
}
|
||||
// Check the connection
|
||||
if err = mongoClient.Ping(context.Background(), nil); err != nil {
|
||||
log.Error("mongodb connect ping is fail")
|
||||
return nil, err
|
||||
}
|
||||
mongoDataBase := mongoClient.Database(db)
|
||||
return &MongoDB{db: mongoDataBase}, nil
|
||||
}
|
||||
|
||||
func InitDS(dbmap []DBmap, register []Register) map[string]*MongoDB {
|
||||
if len(dbmap) == 0 {
|
||||
log.Error("dbmap must be not empty ")
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, v := range dbmap {
|
||||
db, err := InitMongo(v.URL)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[DB-%s] start up error", v.Key), log.E(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info(fmt.Sprintf("[DB-%s] connect is successfully", v.Key))
|
||||
dataSource[v.Key] = db
|
||||
}
|
||||
registerPool = register
|
||||
return dataSource
|
||||
}
|
||||
|
||||
func Init(tableName string) *MongoDB {
|
||||
if tableName == "" {
|
||||
panic(errors.New("table name must not be empty"))
|
||||
}
|
||||
return Selector(tableName)
|
||||
}
|
||||
|
||||
// 初始化只读数据
|
||||
func InitRead(tableName string) *MongoDB {
|
||||
if tableName == "" {
|
||||
panic(errors.New("table name must not be empty"))
|
||||
}
|
||||
return SelectorRead(tableName)
|
||||
}
|
||||
|
||||
func CloseDS() {
|
||||
if len(dataSource) == 0 {
|
||||
log.Warn("dataSource empty ")
|
||||
return
|
||||
}
|
||||
for k, v := range dataSource {
|
||||
if err := v.Disconnect(); err != nil {
|
||||
log.Error(fmt.Sprintf("[DB-%s]Mongo Disconnect error", k), log.E(err))
|
||||
continue
|
||||
}
|
||||
log.Info(fmt.Sprintf("[DB-%s]Mongo Disconnect OK", k))
|
||||
}
|
||||
}
|
||||
|
||||
// BaseDAO 如果ctx为nil 则表示不使用事务,如果ctx不为空则表示使用事务
|
||||
func BaseDAO(tableName string, ctx context.Context) *MongoTool {
|
||||
if tableName == "" {
|
||||
panic(errors.New("table name must not be empty"))
|
||||
}
|
||||
mongdb := Selector(tableName)
|
||||
if ctx == nil {
|
||||
return &MongoTool{db: mongdb.db, ctx: context.Background(), coll: mongdb.db.Collection(tableName)}
|
||||
}
|
||||
return &MongoTool{db: mongdb.db, ctx: ctx, coll: mongdb.db.Collection(tableName)}
|
||||
}
|
||||
|
||||
func Selector(tableName string) *MongoDB {
|
||||
var isExist = false
|
||||
var db *MongoDB
|
||||
for _, r := range registerPool {
|
||||
if strings.HasPrefix(r.Key, "Read") {
|
||||
continue
|
||||
}
|
||||
for _, v := range r.Table {
|
||||
if v == tableName {
|
||||
isExist = true
|
||||
db = dataSource[r.Key]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !isExist {
|
||||
log.Warn("current table not register,please register it first", log.Any("table", tableName))
|
||||
panic(errors.New("current table not register"))
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func SelectorRead(tableName string) *MongoDB {
|
||||
var isExist = false
|
||||
var db *MongoDB
|
||||
for _, r := range registerPool {
|
||||
if !strings.HasPrefix(r.Key, "Read") {
|
||||
continue
|
||||
}
|
||||
for _, v := range r.Table {
|
||||
if v == tableName {
|
||||
isExist = true
|
||||
db = dataSource[r.Key]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !isExist {
|
||||
log.Warn("current table not register,please register it first", log.Any("table", tableName))
|
||||
panic(errors.New("current table not register"))
|
||||
}
|
||||
return db
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package db
|
||||
|
||||
// 事务设置
|
||||
type TransOpts struct {
|
||||
ReEntryCount *int //重入次数
|
||||
//...
|
||||
}
|
||||
|
||||
func (t *TransOpts) SetReEntry(count int) *TransOpts {
|
||||
t.ReEntryCount = &count
|
||||
return t
|
||||
}
|
||||
|
||||
// MergeTransOpts 合并事务设置
|
||||
func MergeTransOpts(opts []*TransOpts) *TransOpts {
|
||||
transOpts := &TransOpts{}
|
||||
for _, opt := range opts {
|
||||
if opt == nil {
|
||||
continue
|
||||
}
|
||||
if opt.ReEntryCount != nil {
|
||||
transOpts.ReEntryCount = opt.ReEntryCount
|
||||
//...
|
||||
}
|
||||
}
|
||||
return transOpts
|
||||
}
|
||||
Reference in New Issue
Block a user