From 32b899744110f8dc8524baf87a4544dafbd4d5fe Mon Sep 17 00:00:00 2001 From: xuthus5 Date: Wed, 23 Feb 2022 16:59:45 +0800 Subject: [PATCH] first commit --- .gitignore | 2 + README.md | 62 + aggregate_scope.go | 412 +++++ aggregate_scope_test.go | 80 + autogen_model_field_mdbc.go | 2411 +++++++++++++++++++++++++++ autogen_model_mdbc.go | 2835 ++++++++++++++++++++++++++++++++ breaker.go | 21 + builder/index.go | 12 + builder/pipeline.go | 63 + builder/query.go | 19 + builder/type.go | 40 + bulk_write_scope.go | 218 +++ bulk_write_scope_test.go | 114 ++ codec.go | 67 + config.go | 43 + convert.go | 241 +++ count_scope.go | 217 +++ count_scope_test.go | 41 + debugger.go | 62 + define.go | 107 ++ delete_scope.go | 339 ++++ delete_scope_test.go | 142 ++ distinct_scope.go | 305 ++++ distinct_scope_test.go | 41 + drop_scope.go | 110 ++ find_one_scope.go | 591 +++++++ find_one_scope_test.go | 62 + find_scope.go | 445 +++++ find_scope_test.go | 98 ++ gen.sh | 3 + go.mod | 15 + hook.go | 12 + icon.png | Bin 0 -> 69324 bytes index_scope.go | 7 + index_scope_test.go | 97 ++ index_scope_v2.go | 273 ++++ insert_scope.go | 281 ++++ insert_scope_test.go | 67 + mdbc.pb.go | 3086 +++++++++++++++++++++++++++++++++++ mdbc.proto | 278 ++++ mongo.go | 200 +++ new.go | 118 ++ scope.go | 221 +++ scope_test.go | 32 + transaction_scope.go | 144 ++ transaction_scope_hook.go | 61 + transaction_scope_test.go | 150 ++ transaction_scope_v2.go | 295 ++++ update_module.sh | 9 + update_scope.go | 404 +++++ update_scope_test.go | 82 + utils.go | 43 + 52 files changed, 15078 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 aggregate_scope.go create mode 100644 aggregate_scope_test.go create mode 100644 autogen_model_field_mdbc.go create mode 100644 autogen_model_mdbc.go create mode 100644 breaker.go create mode 100644 builder/index.go create mode 100644 builder/pipeline.go create mode 100644 builder/query.go create mode 100644 builder/type.go create mode 100644 bulk_write_scope.go create mode 100644 bulk_write_scope_test.go create mode 100644 codec.go create mode 100644 config.go create mode 100644 convert.go create mode 100644 count_scope.go create mode 100644 count_scope_test.go create mode 100644 debugger.go create mode 100644 define.go create mode 100644 delete_scope.go create mode 100644 delete_scope_test.go create mode 100644 distinct_scope.go create mode 100644 distinct_scope_test.go create mode 100644 drop_scope.go create mode 100644 find_one_scope.go create mode 100644 find_one_scope_test.go create mode 100644 find_scope.go create mode 100644 find_scope_test.go create mode 100755 gen.sh create mode 100644 go.mod create mode 100644 hook.go create mode 100644 icon.png create mode 100644 index_scope.go create mode 100644 index_scope_test.go create mode 100644 index_scope_v2.go create mode 100644 insert_scope.go create mode 100644 insert_scope_test.go create mode 100644 mdbc.pb.go create mode 100644 mdbc.proto create mode 100644 mongo.go create mode 100644 new.go create mode 100644 scope.go create mode 100644 scope_test.go create mode 100644 transaction_scope.go create mode 100644 transaction_scope_hook.go create mode 100644 transaction_scope_test.go create mode 100644 transaction_scope_v2.go create mode 100755 update_module.sh create mode 100644 update_scope.go create mode 100644 update_scope_test.go create mode 100644 utils.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e6b069 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +go.sum \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c923dab --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +MDBC + +### 快速开始 + +初始化mongo数据库连接 + +```go +client, err := mongodb.ConnInit("mongodb://admin:admin@10.0.0.135:27017/admin") +if err != nil { + logrus.Fatalf("get err: %+v", err) +} +mdbc.InitDB(client.Database("mdbc")) +``` + +声明 model + +```go +var m = mdbc.NewModel(&ModelSchedTask{}) +``` + +然后就可以使用 m 进行链式操作 + +### 注册全局对象 + +可以将model注册成一个全局变量 +```go +type WsConnectRecordScope struct { + *mdbc.Scope +} + +var WsConnectRecord *WsConnectRecordScope + +func NewWsConnectRecord() { + WsConnectRecord = new(WsConnectRecordScope) + WsConnectRecord.Scope = mdbc.NewModel(&model.ModelWsConnectRecord{}) +} +``` + +使用: + +```go +func beforeRemoveWs(ctx context.Context, recordID, key string) { + if WsConnectRecord == nil { + NewWsConnectRecord() + } + tm := time.Now().UnixNano() / 1e6 + if message_common.GetEtcdWatcher().RemoveWatch(key) { + // 已经移除 变更最近的一条消息 + err := WsConnectRecord.SetContext(ctx).FindOne().SetFilter(bson.M{ + model.ModelWsConnectRecordField_Id.DbFieldName: recordID, + }).Update(bson.M{ + "$set": bson.M{ + model.ModelWsConnectRecordField_LogoutAt.DbFieldName: tm, + }, + }) + if err != nil { + log.Errorf("update ws conn record err: %+v", err) + common.Logger.Error(ctx, "WsConn", log2.String("error", err.Error())) + } + } +} +``` diff --git a/aggregate_scope.go b/aggregate_scope.go new file mode 100644 index 0000000..dcd471f --- /dev/null +++ b/aggregate_scope.go @@ -0,0 +1,412 @@ +package mdbc + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "reflect" + "time" + + jsoniter "github.com/json-iterator/go" + + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type AggregateScope struct { + scope *Scope + cw *ctxWrap + err error + pipeline interface{} + opts *options.AggregateOptions + cursor *mongo.Cursor + enableCache bool + cacheKey string + cacheFunc AggregateCacheFunc +} + +// AggregateFunc 自定义获取结果集的注入函数 +type AggregateFunc func(cursor *mongo.Cursor) (interface{}, error) + +// AggregateCacheFunc 缓存Find结果集 +// field: 若无作用 可置空 DefaultAggregateCacheFunc 将使用其反射出field字段对应的value并将其设为key +// obj: 这是一个slice 只能在GetList是使用,GetMap使用将无效果 +type AggregateCacheFunc func(field string, obj interface{}) (*CacheObject, error) + +// DefaultAggregateCacheFunc 按照第一个结果的field字段作为key缓存list数据 +// field字段需要满足所有数据均有该属性 否则有概率导致缓存失败 +// 和 DefaultFindCacheFunc 有重复嫌疑 需要抽象 +var DefaultAggregateCacheFunc = func() AggregateCacheFunc { + return func(field string, obj interface{}) (*CacheObject, error) { + // 建议先断言obj 再操作 若obj断言失败 请返回nil 这样缓存将不会执行 + // 示例: res,ok := obj.(*[]*model.ModelUser) 然后操作res 将 key,value 写入 CacheObject 既可 + // 下面使用反射进行操作 保证兼容 + v := reflect.ValueOf(obj) + if v.Type().Kind() != reflect.Ptr { + return nil, fmt.Errorf("invalid list type, not ptr") + } + + v = v.Elem() + if v.Type().Kind() != reflect.Slice { + return nil, fmt.Errorf("invalid list type, not ptr to slice") + } + + // 空slice 无需缓存 + if v.Len() == 0 { + return nil, nil + } + + firstNode := v.Index(0) + // 判断过长度 无需判断nil + if firstNode.Kind() == reflect.Ptr { + firstNode = firstNode.Elem() + } + idVal := firstNode.FieldByName(field) + if idVal.Kind() == reflect.Invalid { + return nil, fmt.Errorf("first node's %s field not found", field) + } + + b, _ := json.Marshal(v.Interface()) + co := &CacheObject{ + Key: idVal.String(), + Value: string(b), + } + + return co, nil + } +} + +// SetContext 设置上下文 +func (as *AggregateScope) SetContext(ctx context.Context) *AggregateScope { + if as.cw == nil { + as.cw = &ctxWrap{} + } + as.cw.ctx = ctx + return as +} + +// getContext 获取ctx +func (as *AggregateScope) getContext() context.Context { + return as.cw.ctx +} + +// preCheck 预检查 +func (as *AggregateScope) preCheck() { + var breakerTTL time.Duration + if as.scope.breaker == nil { + breakerTTL = defaultBreakerTime + } else if as.scope.breaker.ttl == 0 { + breakerTTL = defaultBreakerTime + } else { + breakerTTL = as.scope.breaker.ttl + } + if as.cw == nil { + as.cw = &ctxWrap{} + } + if as.cw.ctx == nil { + as.cw.ctx, as.cw.cancel = context.WithTimeout(context.Background(), breakerTTL) + } +} + +// SetAggregateOption 重写配置项 +func (as *AggregateScope) SetAggregateOption(opts options.AggregateOptions) *AggregateScope { + as.opts = &opts + return as +} + +// SetPipeline 设置管道 +// 传递 []bson.D 或者mongo.Pipeline +func (as *AggregateScope) SetPipeline(pipeline interface{}) *AggregateScope { + as.pipeline = pipeline + return as +} + +// assertErr 断言错误 +func (as *AggregateScope) assertErr() { + if as.err == nil { + return + } + if errors.Is(as.err, context.DeadlineExceeded) { + as.err = &ErrRequestBroken + return + } + err, ok := as.err.(mongo.CommandError) + if !ok { + return + } + if err.HasErrorMessage(context.DeadlineExceeded.Error()) { + as.err = &ErrRequestBroken + } +} + +// doString 实现debugger的 actionDo 接口 +func (as *AggregateScope) doString() string { + var data []interface{} + builder := RegisterTimestampCodec(nil).Build() + vo := reflect.ValueOf(as.pipeline) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() != reflect.Slice { + panic("pipeline type not slice") + } + for i := 0; i < vo.Len(); i++ { + var body interface{} + b, _ := bson.MarshalExtJSONWithRegistry(builder, vo.Index(i).Interface(), true, true) + _ = jsoniter.Unmarshal(b, &body) + data = append(data, body) + } + b, _ := jsoniter.Marshal(data) + return fmt.Sprintf("aggregate(%s)", string(b)) +} + +// debug 统一debug入口 +func (as *AggregateScope) debug() { + if !as.scope.debug { + return + } + + debugger := &Debugger{ + collection: as.scope.tableName, + execT: as.scope.execT, + action: as, + } + debugger.String() +} + +// doSearch 查询的真正语句 +func (as *AggregateScope) doSearch() { + var starTime time.Time + if as.scope.debug { + starTime = time.Now() + } + as.cursor, as.err = db.Collection(as.scope.tableName).Aggregate(as.getContext(), as.pipeline, as.opts) + if as.scope.debug { + as.scope.execT = time.Since(starTime) + } + as.debug() + as.assertErr() +} + +func (as *AggregateScope) doClear() { + if as.cw != nil && as.cw.cancel != nil { + as.cw.cancel() + } + as.scope.debug = false + as.scope.execT = 0 +} + +// GetList 获取管道数据列表 传递 *[]*TypeValue +func (as *AggregateScope) GetList(list interface{}) error { + defer as.doClear() + as.preCheck() + as.doSearch() + + if as.err != nil { + return as.err + } + v := reflect.ValueOf(list) + if v.Type().Kind() != reflect.Ptr { + as.err = fmt.Errorf("invalid list type, not ptr") + return as.err + } + + v = v.Elem() + if v.Type().Kind() != reflect.Slice { + as.err = fmt.Errorf("invalid list type, not ptr to slice") + return as.err + } + + as.err = as.cursor.All(as.getContext(), list) + + if as.enableCache { + as.doCache(list) + } + + return as.err +} + +// GetOne 获取管道数据的第一个元素 传递 *struct 不建议使用而建议直接用 limit:1 +// 然后数组取第一个数据的方式获取结果 为了确保效率 当明确只获取一条时 请用 limit 限制一下 +// 当获取不到数据的时候 会报错 ErrRecordNotFound 目前不支持缓存 +func (as *AggregateScope) GetOne(obj interface{}) error { + v := reflect.ValueOf(obj) + vt := v.Type() + if vt.Kind() != reflect.Ptr { + as.err = fmt.Errorf("invalid type, not ptr") + return as.err + } + vt = vt.Elem() + if vt.Kind() != reflect.Struct { + as.err = fmt.Errorf("invalid type, not ptr to struct") + return as.err + } + + defer as.doClear() + as.preCheck() + as.doSearch() + + if as.err != nil { + return as.err + } + + objType := reflect.TypeOf(obj) + objSliceType := reflect.SliceOf(objType) + objSlice := reflect.New(objSliceType) + objSliceInterface := objSlice.Interface() + + as.err = as.cursor.All(as.getContext(), objSliceInterface) + if as.err != nil { + return as.err + } + + realV := reflect.ValueOf(objSliceInterface) + if realV.Kind() == reflect.Ptr { + realV = realV.Elem() + } + + if realV.Len() == 0 { + as.err = &ErrRecordNotFound + return as.err + } + + firstNode := realV.Index(0) + if firstNode.Kind() != reflect.Ptr { + as.err = &ErrFindObjectTypeNotSupport + return as.err + } + + v.Elem().Set(firstNode.Elem()) + + if as.enableCache { + as.doCache(obj) + } + + return as.err +} + +// GetMap 获取管道数据map 传递 *map[string]*TypeValue, field 基于哪个字段进行map构建 +func (as *AggregateScope) GetMap(m interface{}, field string) error { + defer as.doClear() + as.preCheck() + as.doSearch() + + if as.err != nil { + return as.err + } + + v := reflect.ValueOf(m) + if v.Type().Kind() != reflect.Ptr { + as.err = fmt.Errorf("invalid map type, not ptr") + return as.err + } + v = v.Elem() + if v.Type().Kind() != reflect.Map { + as.err = fmt.Errorf("invalid map type, not map") + return as.err + } + + mapType := v.Type() + valueType := mapType.Elem() + keyType := mapType.Key() + + if valueType.Kind() != reflect.Ptr { + as.err = fmt.Errorf("invalid map value type, not prt") + return as.err + } + + if !v.CanSet() { + as.err = fmt.Errorf("invalid map value type, not addressable or obtained by the use of unexported struct fields") + return as.err + } + v.Set(reflect.MakeMap(reflect.MapOf(keyType, valueType))) + + for as.cursor.Next(as.getContext()) { + t := reflect.New(valueType.Elem()) + if as.err = as.cursor.Decode(t.Interface()); as.err != nil { + logrus.Errorf("err: %+v", as.err) + return as.err + } + keyv := t.Elem().FieldByName(field) + if keyv.Kind() == reflect.Invalid { + as.err = fmt.Errorf("invalid model key: %s", field) + return as.err + } + v.SetMapIndex(keyv, t) + } + + return as.err +} + +// Error 获取错误 +func (as *AggregateScope) Error() error { + return as.err +} + +// GetByFunc 自定义函数 获取数据集 +func (as *AggregateScope) GetByFunc(cb AggregateFunc) (val interface{}, err error) { + defer as.doClear() + as.preCheck() + as.doSearch() + + if as.err != nil { + return nil, as.err + } + + val, as.err = cb(as.cursor) + if as.err != nil { + return nil, as.err + } + + // 自行断言val + return val, nil +} + +// SetCacheFunc 传递一个函数 处理查询操作的结果进行缓存 还没有实现 +func (as *AggregateScope) SetCacheFunc(key string, cb AggregateCacheFunc) *AggregateScope { + as.enableCache = true + as.cacheFunc = cb + as.cacheKey = key + return as +} + +// doCache 执行缓存 +func (as *AggregateScope) doCache(obj interface{}) *AggregateScope { + // redis句柄不存在 + if as.scope.cache == nil { + return nil + } + cacheObj, err := as.cacheFunc(as.cacheKey, obj) + if err != nil { + as.err = err + return as + } + + if cacheObj == nil { + as.err = fmt.Errorf("cache object nil") + return as + } + + ttl := as.scope.cache.ttl + if ttl == 0 { + ttl = time.Hour + } else if ttl == -1 { + ttl = 0 + } + + if as.getContext().Err() != nil { + as.err = as.getContext().Err() + as.assertErr() + return as + } + + as.err = as.scope.cache.client.Set(as.getContext(), cacheObj.Key, cacheObj.Value, ttl).Err() + if as.err != nil { + as.assertErr() + } + + return as +} diff --git a/aggregate_scope_test.go b/aggregate_scope_test.go new file mode 100644 index 0000000..f09dae8 --- /dev/null +++ b/aggregate_scope_test.go @@ -0,0 +1,80 @@ +package mdbc + +import ( + "fmt" + "testing" + + "github.com/sirupsen/logrus" + "gitlab.com/gotk/mdbc/builder" + "go.mongodb.org/mongo-driver/bson" +) + +func TestAggregateScope(t *testing.T) { + cfg := &Config{ + URI: "mongodb://mdbc:mdbc@10.0.0.135:27117/admin", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + Init(client).InitDB(client.Database("mdbc")) + + var m = NewModel(&ModelSchedTask{}) + + bu := builder.NewBuilder().Pipeline() + bu.Match(bson.M{"created_at": 0}). + Group(bson.M{"_id": "$task_type", "count": bson.M{"$sum": 1}}) + + type Res struct { + Count int `bson:"count"` + Type string `bson:"_id"` + } + + //ds := []bson.D{{{"$match", bson.M{"created_at": bson.M{"$gt": 0}}}}} + + var records Res + err = m.SetDebug(true). + Aggregate().SetPipeline(bu.Build()).GetOne(&records) + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", records) +} + +func TestAggregateScope_GetMap(t *testing.T) { + cfg := &Config{ + URI: "mongodb://admin:admin@10.0.0.135:27017/admin", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + Init(client).InitDB(client.Database("mdbc")) + + var m = NewModel(&ModelSchedTask{}) + bu := builder.NewBuilder() + //bu.Match(bson.M{"created_at": bson.M{"$gt": 0}}). + // Group(bson.M{"_id": "$task_type", "count": bson.M{"$sum": 1}}). + // Project(bson.M{"type": "$_id", "_id": 0, "count": 1}). + // Sort(bson.M{"count": 1}) + type Res struct { + Count int `bson:"count"` + Type string `bson:"type"` + } + record := make(map[string]*Res) + err = m. + SetDebug(true). + Aggregate().SetPipeline(bu.Pipeline()).GetMap(&record, "Type") + if err != nil { + panic(err) + } + fmt.Println(record) +} diff --git a/autogen_model_field_mdbc.go b/autogen_model_field_mdbc.go new file mode 100644 index 0000000..c787ee9 --- /dev/null +++ b/autogen_model_field_mdbc.go @@ -0,0 +1,2411 @@ +// Code generated by proto_parser. DO NOT EDIT. +// source: mdbc.proto + +package mdbc + +func (m *AtMsgItem) GetContentField() string { + return "content" +} + +func (m *AtMsgItem) GetNickNameField() string { + return "nick_name" +} + +func (m *AtMsgItem) GetSubTypeField() string { + return "sub_type" +} + +func (m *AtMsgItem) GetUserNameField() string { + return "user_name" +} + +func (m *ContentData) GetAtMsgItemField() string { + return "at_msg_item" +} + +func (m *ContentData) GetAtUserNameField() string { + return "at_user_name" +} + +func (m *ContentData) GetContentField() string { + return "content" +} + +func (m *ContentData) GetFileSizeField() string { + return "file_size" +} + +func (m *ContentData) GetFileUrlField() string { + return "file_url" +} + +func (m *ContentData) GetIsAtMyselfField() string { + return "is_at_myself" +} + +func (m *ContentData) GetRawContentField() string { + return "raw_content" +} + +func (m *ContentData) GetResourceDurationField() string { + return "resource_duration" +} + +func (m *ContentData) GetShareDescField() string { + return "share_desc" +} + +func (m *ContentData) GetShareNickNameField() string { + return "share_nick_name" +} + +func (m *ContentData) GetShareTitleField() string { + return "share_title" +} + +func (m *ContentData) GetShareUrlField() string { + return "share_url" +} + +func (m *ContentData) GetShareUserNameField() string { + return "share_user_name" +} + +func (m *ContentData) GetWxMsgTypeField() string { + return "wx_msg_type" +} + +func (m *ModelFriendInfo) GetAvatarUrlField() string { + return "avatar_url" +} + +func (m *ModelFriendInfo) GetCityField() string { + return "city" +} + +func (m *ModelFriendInfo) GetCountryField() string { + return "country" +} + +func (m *ModelFriendInfo) GetCreateTimeField() string { + return "create_time" +} + +func (m *ModelFriendInfo) GetIdField() string { + return "_id" +} + +func (m *ModelFriendInfo) GetNicknameField() string { + return "nick_name" +} + +func (m *ModelFriendInfo) GetPhoneField() string { + return "phone" +} + +func (m *ModelFriendInfo) GetProvinceField() string { + return "province" +} + +func (m *ModelFriendInfo) GetSexField() string { + return "sex" +} + +func (m *ModelFriendInfo) GetUpdateTimeField() string { + return "update_time" +} + +func (m *ModelFriendInfo) GetWechatAliasField() string { + return "wechat_alias" +} + +func (m *ModelFriendInfo) GetWechatIdField() string { + return "wechat_id" +} + +func (m *ModelGroupChat) GetAdminTypeField() string { + return "admin_type" +} + +func (m *ModelGroupChat) GetCreatedAtField() string { + return "created_at" +} + +func (m *ModelGroupChat) GetDeletedAtField() string { + return "deleted_at" +} + +func (m *ModelGroupChat) GetDisableInviteField() string { + return "disable_invite" +} + +func (m *ModelGroupChat) GetGroupAvatarUrlField() string { + return "group_avatar_url" +} + +func (m *ModelGroupChat) GetGroupNameField() string { + return "group_name" +} + +func (m *ModelGroupChat) GetGroupWxIdField() string { + return "group_wx_id" +} + +func (m *ModelGroupChat) GetHasBeenWatchField() string { + return "has_been_watch" +} + +func (m *ModelGroupChat) GetIdField() string { + return "_id" +} + +func (m *ModelGroupChat) GetInContactField() string { + return "in_contact" +} + +func (m *ModelGroupChat) GetIsDefaultGroupNameField() string { + return "is_default_group_name" +} + +func (m *ModelGroupChat) GetIsWatchField() string { + return "is_watch" +} + +func (m *ModelGroupChat) GetLastSyncAtField() string { + return "last_sync_at" +} + +func (m *ModelGroupChat) GetLastSyncMemberAtField() string { + return "last_sync_member_at" +} + +func (m *ModelGroupChat) GetMemberCountField() string { + return "member_count" +} + +func (m *ModelGroupChat) GetNoticeField() string { + return "notice" +} + +func (m *ModelGroupChat) GetOwnerNameField() string { + return "owner_name" +} + +func (m *ModelGroupChat) GetOwnerWxIdField() string { + return "owner_wx_id" +} + +func (m *ModelGroupChat) GetQrcodeUpdatedAtField() string { + return "qrcode_updated_at" +} + +func (m *ModelGroupChat) GetQrcodeUrlField() string { + return "qrcode_url" +} + +func (m *ModelGroupChat) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *ModelGroupChat) GetUpdatedAtField() string { + return "updated_at" +} + +func (m *ModelGroupChatMember) GetAdminTypeField() string { + return "admin_type" +} + +func (m *ModelGroupChatMember) GetCreatedAtField() string { + return "created_at" +} + +func (m *ModelGroupChatMember) GetDeletedAtField() string { + return "deleted_at" +} + +func (m *ModelGroupChatMember) GetGroupChatIdField() string { + return "group_chat_id" +} + +func (m *ModelGroupChatMember) GetIdField() string { + return "_id" +} + +func (m *ModelGroupChatMember) GetIsRobotField() string { + return "is_robot" +} + +func (m *ModelGroupChatMember) GetLastSyncAtField() string { + return "last_sync_at" +} + +func (m *ModelGroupChatMember) GetMemberAliasField() string { + return "member_alias" +} + +func (m *ModelGroupChatMember) GetMemberAvatarField() string { + return "member_avatar" +} + +func (m *ModelGroupChatMember) GetMemberNameField() string { + return "member_name" +} + +func (m *ModelGroupChatMember) GetMemberSexField() string { + return "member_sex" +} + +func (m *ModelGroupChatMember) GetMemberWxIdField() string { + return "member_wx_id" +} + +func (m *ModelGroupChatMember) GetUpdatedAtField() string { + return "updated_at" +} + +func (m *ModelRobot) GetAbilityLimitField() string { + return "ability_limit" +} + +func (m *ModelRobot) GetAliasNameField() string { + return "alias_name" +} + +func (m *ModelRobot) GetAndroidStatusField() string { + return "android_status" +} + +func (m *ModelRobot) GetAndroidWechatVersionField() string { + return "android_wechat_version" +} + +func (m *ModelRobot) GetAutoAddFriendField() string { + return "auto_add_friend" +} + +func (m *ModelRobot) GetAvatarUrlField() string { + return "avatar_url" +} + +func (m *ModelRobot) GetCityField() string { + return "city" +} + +func (m *ModelRobot) GetCountryField() string { + return "country" +} + +func (m *ModelRobot) GetCoverUrlField() string { + return "cover_url" +} + +func (m *ModelRobot) GetCreateTimeField() string { + return "create_time" +} + +func (m *ModelRobot) GetCrmAutoAddFriendField() string { + return "crm_auto_add_friend" +} + +func (m *ModelRobot) GetCrmShopIdField() string { + return "crm_shop_id" +} + +func (m *ModelRobot) GetDeleteTimeField() string { + return "delete_time" +} + +func (m *ModelRobot) GetGreetIdField() string { + return "greet_id" +} + +func (m *ModelRobot) GetIdField() string { + return "_id" +} + +func (m *ModelRobot) GetInitFriendField() string { + return "init_friend" +} + +func (m *ModelRobot) GetLastAndroidLoginAtField() string { + return "last_android_login_at" +} + +func (m *ModelRobot) GetLastAndroidLogoutAtField() string { + return "last_android_logout_at" +} + +func (m *ModelRobot) GetLastCityField() string { + return "last_city" +} + +func (m *ModelRobot) GetLastLogOutTimeField() string { + return "last_log_out_time" +} + +func (m *ModelRobot) GetLastLoginTimeField() string { + return "last_login_time" +} + +func (m *ModelRobot) GetLastPcLoginAtField() string { + return "last_pc_login_at" +} + +func (m *ModelRobot) GetLastPcLogoutAtField() string { + return "last_pc_logout_at" +} + +func (m *ModelRobot) GetLastRegionCodeField() string { + return "last_region_code" +} + +func (m *ModelRobot) GetLastRequireAddFriendTimeField() string { + return "last_require_add_friend_time" +} + +func (m *ModelRobot) GetLimitedField() string { + return "limited" +} + +func (m *ModelRobot) GetLogAndOutTimeField() string { + return "log_and_out_time" +} + +func (m *ModelRobot) GetMobileField() string { + return "mobile" +} + +func (m *ModelRobot) GetMomentPrivacyTypeField() string { + return "moment_privacy_type" +} + +func (m *ModelRobot) GetNickNameField() string { + return "nick_name" +} + +func (m *ModelRobot) GetNowFriendField() string { + return "now_friend" +} + +func (m *ModelRobot) GetOpenForStrangerField() string { + return "open_for_stranger" +} + +func (m *ModelRobot) GetProvinceField() string { + return "province" +} + +func (m *ModelRobot) GetQrcodeField() string { + return "qrcode" +} + +func (m *ModelRobot) GetRiskControlGroupField() string { + return "risk_control_group" +} + +func (m *ModelRobot) GetRiskControlTaskField() string { + return "risk_control_task" +} + +func (m *ModelRobot) GetSexField() string { + return "sex" +} + +func (m *ModelRobot) GetSignatureField() string { + return "signature" +} + +func (m *ModelRobot) GetStatusField() string { + return "status" +} + +func (m *ModelRobot) GetTodayRequireTimeField() string { + return "today_require_time" +} + +func (m *ModelRobot) GetUpdateTimeField() string { + return "update_time" +} + +func (m *ModelRobot) GetUserIdField() string { + return "user_id" +} + +func (m *ModelRobot) GetWechatAliasField() string { + return "wechat_alias" +} + +func (m *ModelRobot) GetWechatIdField() string { + return "wechat_id" +} + +func (m *ModelRobotFriend) GetAddAtField() string { + return "add_at" +} + +func (m *ModelRobotFriend) GetCreateTimeField() string { + return "create_time" +} + +func (m *ModelRobotFriend) GetCrmPhoneField() string { + return "crm_phone" +} + +func (m *ModelRobotFriend) GetDeleteTimeField() string { + return "delete_time" +} + +func (m *ModelRobotFriend) GetDeletedField() string { + return "deleted" +} + +func (m *ModelRobotFriend) GetIdField() string { + return "_id" +} + +func (m *ModelRobotFriend) GetOfflineAddField() string { + return "offline_add" +} + +func (m *ModelRobotFriend) GetPinyinField() string { + return "pinyin" +} + +func (m *ModelRobotFriend) GetPinyinHeadField() string { + return "pinyin_head" +} + +func (m *ModelRobotFriend) GetRemarkNameField() string { + return "remark_name" +} + +func (m *ModelRobotFriend) GetRobotWechatIdField() string { + return "robot_wechat_id" +} + +func (m *ModelRobotFriend) GetUpdateTimeField() string { + return "update_time" +} + +func (m *ModelRobotFriend) GetUserWechatIdField() string { + return "user_wechat_id" +} + +func (m *ModelSchedTask) GetCreatedAtField() string { + return "created_at" +} + +func (m *ModelSchedTask) GetExpiredAtField() string { + return "expired_at" +} + +func (m *ModelSchedTask) GetIdField() string { + return "_id" +} + +func (m *ModelSchedTask) GetReqIdField() string { + return "req_id" +} + +func (m *ModelSchedTask) GetReqJsonField() string { + return "req_json" +} + +func (m *ModelSchedTask) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *ModelSchedTask) GetRspJsonField() string { + return "rsp_json" +} + +func (m *ModelSchedTask) GetTaskStateField() string { + return "task_state" +} + +func (m *ModelSchedTask) GetTaskTypeField() string { + return "task_type" +} + +func (m *ModelSchedTask) GetUpdatedAtField() string { + return "updated_at" +} + +func (m *ModelTbGroupMsgSession) GetAllField() string { + return "all" +} + +func (m *ModelTbGroupMsgSession) GetIdField() string { + return "_id" +} + +func (m *ModelTbGroupMsgSession) GetLastFriendMsgAtField() string { + return "last_friend_msg_at" +} + +func (m *ModelTbGroupMsgSession) GetLastFriendMsgIdField() string { + return "last_friend_msg_id" +} + +func (m *ModelTbGroupMsgSession) GetLastMemberWxIdField() string { + return "last_member_wx_id" +} + +func (m *ModelTbGroupMsgSession) GetLastMsgAtField() string { + return "last_msg_at" +} + +func (m *ModelTbGroupMsgSession) GetLastMsgIdField() string { + return "last_msg_id" +} + +func (m *ModelTbGroupMsgSession) GetReadField() string { + return "read" +} + +func (m *ModelTbGroupMsgSession) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *ModelTbGroupMsgSession) GetUnreadField() string { + return "unread" +} + +func (m *ModelTbGroupMsgSession) GetUserWxIdField() string { + return "user_wx_id" +} + +func (m *ModelTbPrivateMsgSession) GetAllField() string { + return "all" +} + +func (m *ModelTbPrivateMsgSession) GetIdField() string { + return "_id" +} + +func (m *ModelTbPrivateMsgSession) GetLastFriendMsgAtField() string { + return "last_friend_msg_at" +} + +func (m *ModelTbPrivateMsgSession) GetLastFriendMsgIdField() string { + return "last_friend_msg_id" +} + +func (m *ModelTbPrivateMsgSession) GetLastMsgAtField() string { + return "last_msg_at" +} + +func (m *ModelTbPrivateMsgSession) GetLastMsgIdField() string { + return "last_msg_id" +} + +func (m *ModelTbPrivateMsgSession) GetReadField() string { + return "read" +} + +func (m *ModelTbPrivateMsgSession) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *ModelTbPrivateMsgSession) GetUnreadField() string { + return "unread" +} + +func (m *ModelTbPrivateMsgSession) GetUserWxIdField() string { + return "user_wx_id" +} + +func (m *ModelTbRobotGroupMsg) GetBindIdField() string { + return "bind_id" +} + +func (m *ModelTbRobotGroupMsg) GetCallBackAtField() string { + return "call_back_at" +} + +func (m *ModelTbRobotGroupMsg) GetContentDataField() string { + return "content_data" +} + +func (m *ModelTbRobotGroupMsg) GetContentReadField() string { + return "content_read" +} + +func (m *ModelTbRobotGroupMsg) GetCreatedAtField() string { + return "created_at" +} + +func (m *ModelTbRobotGroupMsg) GetCursorField() string { + return "cursor" +} + +func (m *ModelTbRobotGroupMsg) GetDirectField() string { + return "direct" +} + +func (m *ModelTbRobotGroupMsg) GetExpireAtField() string { + return "expire_at" +} + +func (m *ModelTbRobotGroupMsg) GetFailReasonField() string { + return "fail_reason" +} + +func (m *ModelTbRobotGroupMsg) GetIdField() string { + return "_id" +} + +func (m *ModelTbRobotGroupMsg) GetMsgIdField() string { + return "msg_id" +} + +func (m *ModelTbRobotGroupMsg) GetMsgTypeField() string { + return "msg_type" +} + +func (m *ModelTbRobotGroupMsg) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *ModelTbRobotGroupMsg) GetSendAtField() string { + return "send_at" +} + +func (m *ModelTbRobotGroupMsg) GetSendErrorCodeField() string { + return "send_error_code" +} + +func (m *ModelTbRobotGroupMsg) GetSendStatusField() string { + return "send_status" +} + +func (m *ModelTbRobotGroupMsg) GetSenderWxIdField() string { + return "sender_wx_id" +} + +func (m *ModelTbRobotGroupMsg) GetUpdatedAtField() string { + return "updated_at" +} + +func (m *ModelTbRobotGroupMsg) GetUserWxIdField() string { + return "user_wx_id" +} + +func (m *ModelTbRobotPrivateMsg) GetBindIdField() string { + return "bind_id" +} + +func (m *ModelTbRobotPrivateMsg) GetCallBackAtField() string { + return "call_back_at" +} + +func (m *ModelTbRobotPrivateMsg) GetContentDataField() string { + return "content_data" +} + +func (m *ModelTbRobotPrivateMsg) GetContentReadField() string { + return "content_read" +} + +func (m *ModelTbRobotPrivateMsg) GetCreatedAtField() string { + return "created_at" +} + +func (m *ModelTbRobotPrivateMsg) GetCursorField() string { + return "cursor" +} + +func (m *ModelTbRobotPrivateMsg) GetDirectField() string { + return "direct" +} + +func (m *ModelTbRobotPrivateMsg) GetExpireAtField() string { + return "expire_at" +} + +func (m *ModelTbRobotPrivateMsg) GetFailReasonField() string { + return "fail_reason" +} + +func (m *ModelTbRobotPrivateMsg) GetIdField() string { + return "_id" +} + +func (m *ModelTbRobotPrivateMsg) GetMsgIdField() string { + return "msg_id" +} + +func (m *ModelTbRobotPrivateMsg) GetMsgTypeField() string { + return "msg_type" +} + +func (m *ModelTbRobotPrivateMsg) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *ModelTbRobotPrivateMsg) GetSendAtField() string { + return "send_at" +} + +func (m *ModelTbRobotPrivateMsg) GetSendErrorCodeField() string { + return "send_error_code" +} + +func (m *ModelTbRobotPrivateMsg) GetSendStatusField() string { + return "send_status" +} + +func (m *ModelTbRobotPrivateMsg) GetUpdatedAtField() string { + return "updated_at" +} + +func (m *ModelTbRobotPrivateMsg) GetUserWxIdField() string { + return "user_wx_id" +} + +func (m *ModelWsConnectRecord) GetBindIdField() string { + return "bind_id" +} + +func (m *ModelWsConnectRecord) GetCreatedAtField() string { + return "created_at" +} + +func (m *ModelWsConnectRecord) GetExpiredAtField() string { + return "expired_at" +} + +func (m *ModelWsConnectRecord) GetIdField() string { + return "_id" +} + +func (m *ModelWsConnectRecord) GetLoginAtField() string { + return "login_at" +} + +func (m *ModelWsConnectRecord) GetLogoutAtField() string { + return "logout_at" +} + +func (m *ModelWsConnectRecord) GetUserIdField() string { + return "user_id" +} + + +func (m *AtMsgItem) GetContentFieldComment() string { + return "文本内容" +} + +func (m *AtMsgItem) GetNickNameFieldComment() string { + return "@的昵称" +} + +func (m *AtMsgItem) GetSubTypeFieldComment() string { + return "0:文本内容,1:@某人" +} + +func (m *AtMsgItem) GetUserNameFieldComment() string { + return "@的用户(wx_id)" +} + +func (m *ContentData) GetAtMsgItemFieldComment() string { + return "发送群@部分人消息的数据" +} + +func (m *ContentData) GetAtUserNameFieldComment() string { + return "群聊at消息" +} + +func (m *ContentData) GetContentFieldComment() string { + return "1文本的内容;2 语音的url(amr格式);6小程序的xml;" +} + +func (m *ContentData) GetFileSizeFieldComment() string { + return "文件大小KB单位" +} + +func (m *ContentData) GetFileUrlFieldComment() string { + return "3图片的url;4视频的Url;5链接的分享图;8表情的url(gif);9文件的url;" +} + +func (m *ContentData) GetIsAtMyselfFieldComment() string { + return "是否有at我自己 单独一个字段 方便维护和查询" +} + +func (m *ContentData) GetRawContentFieldComment() string { + return "元始的xml数据 做数据转发时用;" +} + +func (m *ContentData) GetResourceDurationFieldComment() string { + return "媒体时长 统一单位s" +} + +func (m *ContentData) GetShareDescFieldComment() string { + return "5链接的描述;" +} + +func (m *ContentData) GetShareNickNameFieldComment() string { + return "7名片的被分享(名片)的昵称;" +} + +func (m *ContentData) GetShareTitleFieldComment() string { + return "5链接的标题;" +} + +func (m *ContentData) GetShareUrlFieldComment() string { + return "5链接的URL;" +} + +func (m *ContentData) GetShareUserNameFieldComment() string { + return "7名片的被分享(名片)好友id;" +} + +func (m *ContentData) GetWxMsgTypeFieldComment() string { + return "消息类型: 1 文本;2 语音;3 图片;4 视频;5 链接;6 小程序;7" +} + +func (m *ModelFriendInfo) GetAvatarUrlFieldComment() string { + return "用户头像" +} + +func (m *ModelFriendInfo) GetCityFieldComment() string { + return "城市" +} + +func (m *ModelFriendInfo) GetCountryFieldComment() string { + return "国家" +} + +func (m *ModelFriendInfo) GetCreateTimeFieldComment() string { + return "创建时间" +} + +func (m *ModelFriendInfo) GetIdFieldComment() string { + return "主键ID wxid md5" +} + +func (m *ModelFriendInfo) GetNicknameFieldComment() string { + return "用户暱称" +} + +func (m *ModelFriendInfo) GetPhoneFieldComment() string { + return "手机号码" +} + +func (m *ModelFriendInfo) GetProvinceFieldComment() string { + return "省份" +} + +func (m *ModelFriendInfo) GetSexFieldComment() string { + return "0未知 1男 2女" +} + +func (m *ModelFriendInfo) GetUpdateTimeFieldComment() string { + return "更新时间" +} + +func (m *ModelFriendInfo) GetWechatAliasFieldComment() string { + return "用户微信号" +} + +func (m *ModelFriendInfo) GetWechatIdFieldComment() string { + return "用户微信ID" +} + +func (m *ModelGroupChat) GetAdminTypeFieldComment() string { + return "机器人权限类型" +} + +func (m *ModelGroupChat) GetCreatedAtFieldComment() string { + return "创建时间" +} + +func (m *ModelGroupChat) GetDeletedAtFieldComment() string { + return "删除时间【记: 此表正常情况下 只进行软删除】非零 历史群 0正常群" +} + +func (m *ModelGroupChat) GetDisableInviteFieldComment() string { + return "是否开启了群聊邀请确认 true 开启了 false 关闭了" +} + +func (m *ModelGroupChat) GetGroupAvatarUrlFieldComment() string { + return "群头像" +} + +func (m *ModelGroupChat) GetGroupNameFieldComment() string { + return "群名称" +} + +func (m *ModelGroupChat) GetGroupWxIdFieldComment() string { + return "群id" +} + +func (m *ModelGroupChat) GetHasBeenWatchFieldComment() string { + return "以前有关注过" +} + +func (m *ModelGroupChat) GetIdFieldComment() string { + return "主键ID" +} + +func (m *ModelGroupChat) GetInContactFieldComment() string { + return "是否在通讯录中" +} + +func (m *ModelGroupChat) GetIsDefaultGroupNameFieldComment() string { + return "是否是默认的群名称" +} + +func (m *ModelGroupChat) GetIsWatchFieldComment() string { + return "是否关注群" +} + +func (m *ModelGroupChat) GetLastSyncAtFieldComment() string { + return "最后更新群信息时间 【通过这里 指定规则 去拉群基本信息】" +} + +func (m *ModelGroupChat) GetLastSyncMemberAtFieldComment() string { + return "最后更新群成员时间 【通过这里 指定规则 去拉群成员信息】" +} + +func (m *ModelGroupChat) GetMemberCountFieldComment() string { + return "群成员数量" +} + +func (m *ModelGroupChat) GetNoticeFieldComment() string { + return "群公告" +} + +func (m *ModelGroupChat) GetOwnerNameFieldComment() string { + return "群主名称" +} + +func (m *ModelGroupChat) GetOwnerWxIdFieldComment() string { + return "群主id" +} + +func (m *ModelGroupChat) GetQrcodeUpdatedAtFieldComment() string { + return "群聊二维码更新时间" +} + +func (m *ModelGroupChat) GetQrcodeUrlFieldComment() string { + return "群聊二维码" +} + +func (m *ModelGroupChat) GetRobotWxIdFieldComment() string { + return "机器人id" +} + +func (m *ModelGroupChat) GetUpdatedAtFieldComment() string { + return "更新时间" +} + +func (m *ModelGroupChatMember) GetAdminTypeFieldComment() string { + return "权限类型 群主 管理员 普通成员" +} + +func (m *ModelGroupChatMember) GetCreatedAtFieldComment() string { + return "创建时间" +} + +func (m *ModelGroupChatMember) GetDeletedAtFieldComment() string { + return "删除时间 这个表一般直接硬删除" +} + +func (m *ModelGroupChatMember) GetGroupChatIdFieldComment() string { + return "群 ModelGroupChat 的ID" +} + +func (m *ModelGroupChatMember) GetIdFieldComment() string { + return "id" +} + +func (m *ModelGroupChatMember) GetIsRobotFieldComment() string { + return "是否是机器人" +} + +func (m *ModelGroupChatMember) GetLastSyncAtFieldComment() string { + return "该群该成员 最后更新时间" +} + +func (m *ModelGroupChatMember) GetMemberAliasFieldComment() string { + return "群昵称" +} + +func (m *ModelGroupChatMember) GetMemberAvatarFieldComment() string { + return "群成员头像" +} + +func (m *ModelGroupChatMember) GetMemberNameFieldComment() string { + return "群成员名称" +} + +func (m *ModelGroupChatMember) GetMemberSexFieldComment() string { + return "性别" +} + +func (m *ModelGroupChatMember) GetMemberWxIdFieldComment() string { + return "群成员微信id" +} + +func (m *ModelGroupChatMember) GetUpdatedAtFieldComment() string { + return "更新时间" +} + +func (m *ModelRobot) GetAbilityLimitFieldComment() string { + return "机器人是否功能受限" +} + +func (m *ModelRobot) GetAliasNameFieldComment() string { + return "微信号" +} + +func (m *ModelRobot) GetAndroidStatusFieldComment() string { + return "机器人Android是否在线 10在线 11离线" +} + +func (m *ModelRobot) GetAndroidWechatVersionFieldComment() string { + return "微信版本" +} + +func (m *ModelRobot) GetAutoAddFriendFieldComment() string { + return "机器人是否自动通过好友请求 0否 1是" +} + +func (m *ModelRobot) GetAvatarUrlFieldComment() string { + return "机器人头像" +} + +func (m *ModelRobot) GetCityFieldComment() string { + return "城市" +} + +func (m *ModelRobot) GetCountryFieldComment() string { + return "国家" +} + +func (m *ModelRobot) GetCoverUrlFieldComment() string { + return "朋友圈封面url" +} + +func (m *ModelRobot) GetCreateTimeFieldComment() string { + return "创建时间" +} + +func (m *ModelRobot) GetCrmAutoAddFriendFieldComment() string { + return "crm系统自动通过好友 1自动通过 0不自动通过" +} + +func (m *ModelRobot) GetCrmShopIdFieldComment() string { + return "机器人所属商户id" +} + +func (m *ModelRobot) GetDeleteTimeFieldComment() string { + return "删除时间" +} + +func (m *ModelRobot) GetGreetIdFieldComment() string { + return "打招呼模板id" +} + +func (m *ModelRobot) GetIdFieldComment() string { + return "主键ID wxid md5" +} + +func (m *ModelRobot) GetInitFriendFieldComment() string { + return "机器人初始好友人数" +} + +func (m *ModelRobot) GetLastAndroidLoginAtFieldComment() string { + return "最近安卓登录时间" +} + +func (m *ModelRobot) GetLastAndroidLogoutAtFieldComment() string { + return "最近安卓登出时间" +} + +func (m *ModelRobot) GetLastCityFieldComment() string { + return "最后登录的城市名称" +} + +func (m *ModelRobot) GetLastLogOutTimeFieldComment() string { + return "最后登出时间" +} + +func (m *ModelRobot) GetLastLoginTimeFieldComment() string { + return "最后登录时间" +} + +func (m *ModelRobot) GetLastPcLoginAtFieldComment() string { + return "最近PC登录时间" +} + +func (m *ModelRobot) GetLastPcLogoutAtFieldComment() string { + return "最近PC登出时间" +} + +func (m *ModelRobot) GetLastRegionCodeFieldComment() string { + return "最后登录的扫码设备的地区编码" +} + +func (m *ModelRobot) GetLastRequireAddFriendTimeFieldComment() string { + return "上一次请求添加好友的时间" +} + +func (m *ModelRobot) GetLimitedFieldComment() string { + return "机器人是否被封号 0未封号 1已封号" +} + +func (m *ModelRobot) GetLogAndOutTimeFieldComment() string { + return "登入或者登出都要记录一下" +} + +func (m *ModelRobot) GetMobileFieldComment() string { + return "手机号码" +} + +func (m *ModelRobot) GetMomentPrivacyTypeFieldComment() string { + return "朋友圈隐私选项类型" +} + +func (m *ModelRobot) GetNickNameFieldComment() string { + return "机器人暱称" +} + +func (m *ModelRobot) GetNowFriendFieldComment() string { + return "机器人当前好友数量" +} + +func (m *ModelRobot) GetOpenForStrangerFieldComment() string { + return "是否允许陌生人查看十条朋友圈" +} + +func (m *ModelRobot) GetProvinceFieldComment() string { + return "省份" +} + +func (m *ModelRobot) GetQrcodeFieldComment() string { + return "机器人二维码" +} + +func (m *ModelRobot) GetRiskControlGroupFieldComment() string { + return "风控分组" +} + +func (m *ModelRobot) GetRiskControlTaskFieldComment() string { + return "风控任务 0是全部,1是回复,2是发消息,3是看朋友圈,4是发朋友圈,5是点赞,6是评论 7是群聊 可组合,如:1,2,3" +} + +func (m *ModelRobot) GetSexFieldComment() string { + return "性别 0 未知 1 男生 2 女生" +} + +func (m *ModelRobot) GetSignatureFieldComment() string { + return "个性签名" +} + +func (m *ModelRobot) GetStatusFieldComment() string { + return "机器人PC是否在线 10在线 11离线 (兼容之前的pc登录流程和其他接口,这个登录状态不变,补多一个字段代表安卓登录状态)" +} + +func (m *ModelRobot) GetTodayRequireTimeFieldComment() string { + return "当天请求次数" +} + +func (m *ModelRobot) GetUpdateTimeFieldComment() string { + return "更新时间" +} + +func (m *ModelRobot) GetUserIdFieldComment() string { + return "机器人所属用户id" +} + +func (m *ModelRobot) GetWechatAliasFieldComment() string { + return "微信ID (用户自己定义的微信号)" +} + +func (m *ModelRobot) GetWechatIdFieldComment() string { + return "微信唯一ID (wxidxxxxxx)" +} + +func (m *ModelRobotFriend) GetAddAtFieldComment() string { + return "添加好友时间只有主动添加好友才有" +} + +func (m *ModelRobotFriend) GetCreateTimeFieldComment() string { + return "创建时间:入库时间" +} + +func (m *ModelRobotFriend) GetCrmPhoneFieldComment() string { + return "CRM自己设置的好友手机号,不同于微信手机号" +} + +func (m *ModelRobotFriend) GetDeleteTimeFieldComment() string { + return "删除好友的时间" +} + +func (m *ModelRobotFriend) GetDeletedFieldComment() string { + return "是否被删除 0双方未删除 1被好友删除 2删除了好友 3互相删除" +} + +func (m *ModelRobotFriend) GetIdFieldComment() string { + return "主键ID 机器人id+朋友id md5" +} + +func (m *ModelRobotFriend) GetOfflineAddFieldComment() string { + return "是否为离线添加" +} + +func (m *ModelRobotFriend) GetPinyinFieldComment() string { + return "用户备注或者暱称的拼音" +} + +func (m *ModelRobotFriend) GetPinyinHeadFieldComment() string { + return "拼音首字母" +} + +func (m *ModelRobotFriend) GetRemarkNameFieldComment() string { + return "微信好友备注名称" +} + +func (m *ModelRobotFriend) GetRobotWechatIdFieldComment() string { + return "机器人编号:微信ID" +} + +func (m *ModelRobotFriend) GetUpdateTimeFieldComment() string { + return "更新时间" +} + +func (m *ModelRobotFriend) GetUserWechatIdFieldComment() string { + return "用户微信ID," +} + +func (m *ModelSchedTask) GetCreatedAtFieldComment() string { + return "创建时间" +} + +func (m *ModelSchedTask) GetExpiredAtFieldComment() string { + return "过期时间" +} + +func (m *ModelSchedTask) GetIdFieldComment() string { + return "任务id" +} + +func (m *ModelSchedTask) GetReqIdFieldComment() string { + return "便于查询该任务 指定的id[作用:有些情况 无法直接通过id来查询该记录]" +} + +func (m *ModelSchedTask) GetReqJsonFieldComment() string { + return "请求内容" +} + +func (m *ModelSchedTask) GetRobotWxIdFieldComment() string { + return "机器人id" +} + +func (m *ModelSchedTask) GetRspJsonFieldComment() string { + return "完成后的内容 [成功或者失败的返回]" +} + +func (m *ModelSchedTask) GetTaskStateFieldComment() string { + return "执行状态 TaskState" +} + +func (m *ModelSchedTask) GetTaskTypeFieldComment() string { + return "任务类型 自定义的名称 用来区别是哪个模块发起的任务" +} + +func (m *ModelSchedTask) GetUpdatedAtFieldComment() string { + return "更新时间" +} + +func (m *ModelTbGroupMsgSession) GetAllFieldComment() string { + return "消息最大游标(消息总数:只算有效的消息)" +} + +func (m *ModelTbGroupMsgSession) GetIdFieldComment() string { + return "会话ID (md5(机器人id+好友id))" +} + +func (m *ModelTbGroupMsgSession) GetLastFriendMsgAtFieldComment() string { + return "接受到最后一条好友消息时间" +} + +func (m *ModelTbGroupMsgSession) GetLastFriendMsgIdFieldComment() string { + return "接收的最后一条好友消息id" +} + +func (m *ModelTbGroupMsgSession) GetLastMemberWxIdFieldComment() string { + return "最后发送消息的群成员id" +} + +func (m *ModelTbGroupMsgSession) GetLastMsgAtFieldComment() string { + return "最后一条消息时间" +} + +func (m *ModelTbGroupMsgSession) GetLastMsgIdFieldComment() string { + return "最后一条消息id" +} + +func (m *ModelTbGroupMsgSession) GetReadFieldComment() string { + return "已读游标" +} + +func (m *ModelTbGroupMsgSession) GetRobotWxIdFieldComment() string { + return "机器人id" +} + +func (m *ModelTbGroupMsgSession) GetUnreadFieldComment() string { + return "未读消息游标" +} + +func (m *ModelTbGroupMsgSession) GetUserWxIdFieldComment() string { + return "群微信id" +} + +func (m *ModelTbPrivateMsgSession) GetAllFieldComment() string { + return "消息最大游标(消息总数:只算有效的消息)" +} + +func (m *ModelTbPrivateMsgSession) GetIdFieldComment() string { + return "会话ID (md5(机器人id+好友id))" +} + +func (m *ModelTbPrivateMsgSession) GetLastFriendMsgAtFieldComment() string { + return "接受到最后一条好友消息时间" +} + +func (m *ModelTbPrivateMsgSession) GetLastFriendMsgIdFieldComment() string { + return "接收的最后一条好友消息id" +} + +func (m *ModelTbPrivateMsgSession) GetLastMsgAtFieldComment() string { + return "最后一条消息时间" +} + +func (m *ModelTbPrivateMsgSession) GetLastMsgIdFieldComment() string { + return "最后一条消息id" +} + +func (m *ModelTbPrivateMsgSession) GetReadFieldComment() string { + return "已读游标" +} + +func (m *ModelTbPrivateMsgSession) GetRobotWxIdFieldComment() string { + return "机器人id" +} + +func (m *ModelTbPrivateMsgSession) GetUnreadFieldComment() string { + return "未读消息游标" +} + +func (m *ModelTbPrivateMsgSession) GetUserWxIdFieldComment() string { + return "好友微信id" +} + +func (m *ModelTbRobotGroupMsg) GetBindIdFieldComment() string { + return "前端消息id" +} + +func (m *ModelTbRobotGroupMsg) GetCallBackAtFieldComment() string { + return "消息返回时间" +} + +func (m *ModelTbRobotGroupMsg) GetContentDataFieldComment() string { + return "消息内容" +} + +func (m *ModelTbRobotGroupMsg) GetContentReadFieldComment() string { + return "是否内容被浏览(像语音之类的,需要浏览)" +} + +func (m *ModelTbRobotGroupMsg) GetCreatedAtFieldComment() string { + return "创建时间" +} + +func (m *ModelTbRobotGroupMsg) GetCursorFieldComment() string { + return "消息游标(对应session的all)" +} + +func (m *ModelTbRobotGroupMsg) GetDirectFieldComment() string { + return "用于区分机器人是接收方还是发送方。1:机器人接收;2:机器人发送" +} + +func (m *ModelTbRobotGroupMsg) GetExpireAtFieldComment() string { + return "失效时间(用于消息的失效)" +} + +func (m *ModelTbRobotGroupMsg) GetFailReasonFieldComment() string { + return "失败原因" +} + +func (m *ModelTbRobotGroupMsg) GetIdFieldComment() string { + return "主键ID" +} + +func (m *ModelTbRobotGroupMsg) GetMsgIdFieldComment() string { + return "服务端自己生成一个消息id,来对应客户端的发送结果id" +} + +func (m *ModelTbRobotGroupMsg) GetMsgTypeFieldComment() string { + return "消息类型" +} + +func (m *ModelTbRobotGroupMsg) GetRobotWxIdFieldComment() string { + return "机器人id" +} + +func (m *ModelTbRobotGroupMsg) GetSendAtFieldComment() string { + return "发送时间(消息实际生效时间)" +} + +func (m *ModelTbRobotGroupMsg) GetSendErrorCodeFieldComment() string { + return "发送错误码:用户告诉对应的是什么错误:-1 通用错误码; -2 被拉黑; -3" +} + +func (m *ModelTbRobotGroupMsg) GetSendStatusFieldComment() string { + return "发送状态:0:发送中;1:发送请求成功;2:发送请求失败;3:发送成功;4:发送失败;仅机器人发送。接收到用户消息的默认3" +} + +func (m *ModelTbRobotGroupMsg) GetSenderWxIdFieldComment() string { + return "发送者id" +} + +func (m *ModelTbRobotGroupMsg) GetUpdatedAtFieldComment() string { + return "更新时间" +} + +func (m *ModelTbRobotGroupMsg) GetUserWxIdFieldComment() string { + return "群聊id" +} + +func (m *ModelTbRobotPrivateMsg) GetBindIdFieldComment() string { + return "前端消息id" +} + +func (m *ModelTbRobotPrivateMsg) GetCallBackAtFieldComment() string { + return "消息返回时间" +} + +func (m *ModelTbRobotPrivateMsg) GetContentDataFieldComment() string { + return "消息内容" +} + +func (m *ModelTbRobotPrivateMsg) GetContentReadFieldComment() string { + return "是否内容被浏览(像语音之类的,需要浏览)" +} + +func (m *ModelTbRobotPrivateMsg) GetCreatedAtFieldComment() string { + return "创建时间" +} + +func (m *ModelTbRobotPrivateMsg) GetCursorFieldComment() string { + return "消息游标(对应session的all)" +} + +func (m *ModelTbRobotPrivateMsg) GetDirectFieldComment() string { + return "用于区分机器人是接收方还是发送方。1:机器人接收;2:机器人发送" +} + +func (m *ModelTbRobotPrivateMsg) GetExpireAtFieldComment() string { + return "失效时间(用于消息的失效)" +} + +func (m *ModelTbRobotPrivateMsg) GetFailReasonFieldComment() string { + return "失败原因" +} + +func (m *ModelTbRobotPrivateMsg) GetIdFieldComment() string { + return "主键ID" +} + +func (m *ModelTbRobotPrivateMsg) GetMsgIdFieldComment() string { + return "服务端自己生成一个消息id,来对应客户端的发送结果id" +} + +func (m *ModelTbRobotPrivateMsg) GetMsgTypeFieldComment() string { + return "消息类型" +} + +func (m *ModelTbRobotPrivateMsg) GetRobotWxIdFieldComment() string { + return "机器人id" +} + +func (m *ModelTbRobotPrivateMsg) GetSendAtFieldComment() string { + return "发送时间(消息实际生效时间)" +} + +func (m *ModelTbRobotPrivateMsg) GetSendErrorCodeFieldComment() string { + return "发送错误码:用户告诉对应的是什么错误:-1 通用错误码; -2 被拉黑; -3" +} + +func (m *ModelTbRobotPrivateMsg) GetSendStatusFieldComment() string { + return "发送状态:0:发送中;1:发送请求成功;2:发送请求失败;3:发送成功;4:发送失败;仅机器人发送。接收到用户消息的默认3" +} + +func (m *ModelTbRobotPrivateMsg) GetUpdatedAtFieldComment() string { + return "更新时间" +} + +func (m *ModelTbRobotPrivateMsg) GetUserWxIdFieldComment() string { + return "好友id" +} + +func (m *ModelWsConnectRecord) GetBindIdFieldComment() string { + return "该ws绑定的id" +} + +func (m *ModelWsConnectRecord) GetCreatedAtFieldComment() string { + return "记录创建时间" +} + +func (m *ModelWsConnectRecord) GetExpiredAtFieldComment() string { + return "过期时间" +} + +func (m *ModelWsConnectRecord) GetIdFieldComment() string { + return "主键ID wxid md5" +} + +func (m *ModelWsConnectRecord) GetLoginAtFieldComment() string { + return "登录时间" +} + +func (m *ModelWsConnectRecord) GetLogoutAtFieldComment() string { + return "登出时间" +} + +func (m *ModelWsConnectRecord) GetUserIdFieldComment() string { + return "机器人所属用户id" +} + + + +type getAtMsgItemField struct {} +var AtMsgItemField getAtMsgItemField + +func (m *getAtMsgItemField) GetContentField() string { + return "content" +} + +func (m *getAtMsgItemField) GetNickNameField() string { + return "nick_name" +} + +func (m *getAtMsgItemField) GetSubTypeField() string { + return "sub_type" +} + +func (m *getAtMsgItemField) GetUserNameField() string { + return "user_name" +} + +type getContentDataField struct {} +var ContentDataField getContentDataField + +func (m *getContentDataField) GetAtMsgItemField() string { + return "at_msg_item" +} + +func (m *getContentDataField) GetAtUserNameField() string { + return "at_user_name" +} + +func (m *getContentDataField) GetContentField() string { + return "content" +} + +func (m *getContentDataField) GetFileSizeField() string { + return "file_size" +} + +func (m *getContentDataField) GetFileUrlField() string { + return "file_url" +} + +func (m *getContentDataField) GetIsAtMyselfField() string { + return "is_at_myself" +} + +func (m *getContentDataField) GetRawContentField() string { + return "raw_content" +} + +func (m *getContentDataField) GetResourceDurationField() string { + return "resource_duration" +} + +func (m *getContentDataField) GetShareDescField() string { + return "share_desc" +} + +func (m *getContentDataField) GetShareNickNameField() string { + return "share_nick_name" +} + +func (m *getContentDataField) GetShareTitleField() string { + return "share_title" +} + +func (m *getContentDataField) GetShareUrlField() string { + return "share_url" +} + +func (m *getContentDataField) GetShareUserNameField() string { + return "share_user_name" +} + +func (m *getContentDataField) GetWxMsgTypeField() string { + return "wx_msg_type" +} + +type getModelFriendInfoField struct {} +var ModelFriendInfoField getModelFriendInfoField + +func (m *getModelFriendInfoField) GetAvatarUrlField() string { + return "avatar_url" +} + +func (m *getModelFriendInfoField) GetCityField() string { + return "city" +} + +func (m *getModelFriendInfoField) GetCountryField() string { + return "country" +} + +func (m *getModelFriendInfoField) GetCreateTimeField() string { + return "create_time" +} + +func (m *getModelFriendInfoField) GetIdField() string { + return "_id" +} + +func (m *getModelFriendInfoField) GetNicknameField() string { + return "nick_name" +} + +func (m *getModelFriendInfoField) GetPhoneField() string { + return "phone" +} + +func (m *getModelFriendInfoField) GetProvinceField() string { + return "province" +} + +func (m *getModelFriendInfoField) GetSexField() string { + return "sex" +} + +func (m *getModelFriendInfoField) GetUpdateTimeField() string { + return "update_time" +} + +func (m *getModelFriendInfoField) GetWechatAliasField() string { + return "wechat_alias" +} + +func (m *getModelFriendInfoField) GetWechatIdField() string { + return "wechat_id" +} + +type getModelGroupChatField struct {} +var ModelGroupChatField getModelGroupChatField + +func (m *getModelGroupChatField) GetAdminTypeField() string { + return "admin_type" +} + +func (m *getModelGroupChatField) GetCreatedAtField() string { + return "created_at" +} + +func (m *getModelGroupChatField) GetDeletedAtField() string { + return "deleted_at" +} + +func (m *getModelGroupChatField) GetDisableInviteField() string { + return "disable_invite" +} + +func (m *getModelGroupChatField) GetGroupAvatarUrlField() string { + return "group_avatar_url" +} + +func (m *getModelGroupChatField) GetGroupNameField() string { + return "group_name" +} + +func (m *getModelGroupChatField) GetGroupWxIdField() string { + return "group_wx_id" +} + +func (m *getModelGroupChatField) GetHasBeenWatchField() string { + return "has_been_watch" +} + +func (m *getModelGroupChatField) GetIdField() string { + return "_id" +} + +func (m *getModelGroupChatField) GetInContactField() string { + return "in_contact" +} + +func (m *getModelGroupChatField) GetIsDefaultGroupNameField() string { + return "is_default_group_name" +} + +func (m *getModelGroupChatField) GetIsWatchField() string { + return "is_watch" +} + +func (m *getModelGroupChatField) GetLastSyncAtField() string { + return "last_sync_at" +} + +func (m *getModelGroupChatField) GetLastSyncMemberAtField() string { + return "last_sync_member_at" +} + +func (m *getModelGroupChatField) GetMemberCountField() string { + return "member_count" +} + +func (m *getModelGroupChatField) GetNoticeField() string { + return "notice" +} + +func (m *getModelGroupChatField) GetOwnerNameField() string { + return "owner_name" +} + +func (m *getModelGroupChatField) GetOwnerWxIdField() string { + return "owner_wx_id" +} + +func (m *getModelGroupChatField) GetQrcodeUpdatedAtField() string { + return "qrcode_updated_at" +} + +func (m *getModelGroupChatField) GetQrcodeUrlField() string { + return "qrcode_url" +} + +func (m *getModelGroupChatField) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *getModelGroupChatField) GetUpdatedAtField() string { + return "updated_at" +} + +type getModelGroupChatMemberField struct {} +var ModelGroupChatMemberField getModelGroupChatMemberField + +func (m *getModelGroupChatMemberField) GetAdminTypeField() string { + return "admin_type" +} + +func (m *getModelGroupChatMemberField) GetCreatedAtField() string { + return "created_at" +} + +func (m *getModelGroupChatMemberField) GetDeletedAtField() string { + return "deleted_at" +} + +func (m *getModelGroupChatMemberField) GetGroupChatIdField() string { + return "group_chat_id" +} + +func (m *getModelGroupChatMemberField) GetIdField() string { + return "_id" +} + +func (m *getModelGroupChatMemberField) GetIsRobotField() string { + return "is_robot" +} + +func (m *getModelGroupChatMemberField) GetLastSyncAtField() string { + return "last_sync_at" +} + +func (m *getModelGroupChatMemberField) GetMemberAliasField() string { + return "member_alias" +} + +func (m *getModelGroupChatMemberField) GetMemberAvatarField() string { + return "member_avatar" +} + +func (m *getModelGroupChatMemberField) GetMemberNameField() string { + return "member_name" +} + +func (m *getModelGroupChatMemberField) GetMemberSexField() string { + return "member_sex" +} + +func (m *getModelGroupChatMemberField) GetMemberWxIdField() string { + return "member_wx_id" +} + +func (m *getModelGroupChatMemberField) GetUpdatedAtField() string { + return "updated_at" +} + +type getModelRobotField struct {} +var ModelRobotField getModelRobotField + +func (m *getModelRobotField) GetAbilityLimitField() string { + return "ability_limit" +} + +func (m *getModelRobotField) GetAliasNameField() string { + return "alias_name" +} + +func (m *getModelRobotField) GetAndroidStatusField() string { + return "android_status" +} + +func (m *getModelRobotField) GetAndroidWechatVersionField() string { + return "android_wechat_version" +} + +func (m *getModelRobotField) GetAutoAddFriendField() string { + return "auto_add_friend" +} + +func (m *getModelRobotField) GetAvatarUrlField() string { + return "avatar_url" +} + +func (m *getModelRobotField) GetCityField() string { + return "city" +} + +func (m *getModelRobotField) GetCountryField() string { + return "country" +} + +func (m *getModelRobotField) GetCoverUrlField() string { + return "cover_url" +} + +func (m *getModelRobotField) GetCreateTimeField() string { + return "create_time" +} + +func (m *getModelRobotField) GetCrmAutoAddFriendField() string { + return "crm_auto_add_friend" +} + +func (m *getModelRobotField) GetCrmShopIdField() string { + return "crm_shop_id" +} + +func (m *getModelRobotField) GetDeleteTimeField() string { + return "delete_time" +} + +func (m *getModelRobotField) GetGreetIdField() string { + return "greet_id" +} + +func (m *getModelRobotField) GetIdField() string { + return "_id" +} + +func (m *getModelRobotField) GetInitFriendField() string { + return "init_friend" +} + +func (m *getModelRobotField) GetLastAndroidLoginAtField() string { + return "last_android_login_at" +} + +func (m *getModelRobotField) GetLastAndroidLogoutAtField() string { + return "last_android_logout_at" +} + +func (m *getModelRobotField) GetLastCityField() string { + return "last_city" +} + +func (m *getModelRobotField) GetLastLogOutTimeField() string { + return "last_log_out_time" +} + +func (m *getModelRobotField) GetLastLoginTimeField() string { + return "last_login_time" +} + +func (m *getModelRobotField) GetLastPcLoginAtField() string { + return "last_pc_login_at" +} + +func (m *getModelRobotField) GetLastPcLogoutAtField() string { + return "last_pc_logout_at" +} + +func (m *getModelRobotField) GetLastRegionCodeField() string { + return "last_region_code" +} + +func (m *getModelRobotField) GetLastRequireAddFriendTimeField() string { + return "last_require_add_friend_time" +} + +func (m *getModelRobotField) GetLimitedField() string { + return "limited" +} + +func (m *getModelRobotField) GetLogAndOutTimeField() string { + return "log_and_out_time" +} + +func (m *getModelRobotField) GetMobileField() string { + return "mobile" +} + +func (m *getModelRobotField) GetMomentPrivacyTypeField() string { + return "moment_privacy_type" +} + +func (m *getModelRobotField) GetNickNameField() string { + return "nick_name" +} + +func (m *getModelRobotField) GetNowFriendField() string { + return "now_friend" +} + +func (m *getModelRobotField) GetOpenForStrangerField() string { + return "open_for_stranger" +} + +func (m *getModelRobotField) GetProvinceField() string { + return "province" +} + +func (m *getModelRobotField) GetQrcodeField() string { + return "qrcode" +} + +func (m *getModelRobotField) GetRiskControlGroupField() string { + return "risk_control_group" +} + +func (m *getModelRobotField) GetRiskControlTaskField() string { + return "risk_control_task" +} + +func (m *getModelRobotField) GetSexField() string { + return "sex" +} + +func (m *getModelRobotField) GetSignatureField() string { + return "signature" +} + +func (m *getModelRobotField) GetStatusField() string { + return "status" +} + +func (m *getModelRobotField) GetTodayRequireTimeField() string { + return "today_require_time" +} + +func (m *getModelRobotField) GetUpdateTimeField() string { + return "update_time" +} + +func (m *getModelRobotField) GetUserIdField() string { + return "user_id" +} + +func (m *getModelRobotField) GetWechatAliasField() string { + return "wechat_alias" +} + +func (m *getModelRobotField) GetWechatIdField() string { + return "wechat_id" +} + +type getModelRobotFriendField struct {} +var ModelRobotFriendField getModelRobotFriendField + +func (m *getModelRobotFriendField) GetAddAtField() string { + return "add_at" +} + +func (m *getModelRobotFriendField) GetCreateTimeField() string { + return "create_time" +} + +func (m *getModelRobotFriendField) GetCrmPhoneField() string { + return "crm_phone" +} + +func (m *getModelRobotFriendField) GetDeleteTimeField() string { + return "delete_time" +} + +func (m *getModelRobotFriendField) GetDeletedField() string { + return "deleted" +} + +func (m *getModelRobotFriendField) GetIdField() string { + return "_id" +} + +func (m *getModelRobotFriendField) GetOfflineAddField() string { + return "offline_add" +} + +func (m *getModelRobotFriendField) GetPinyinField() string { + return "pinyin" +} + +func (m *getModelRobotFriendField) GetPinyinHeadField() string { + return "pinyin_head" +} + +func (m *getModelRobotFriendField) GetRemarkNameField() string { + return "remark_name" +} + +func (m *getModelRobotFriendField) GetRobotWechatIdField() string { + return "robot_wechat_id" +} + +func (m *getModelRobotFriendField) GetUpdateTimeField() string { + return "update_time" +} + +func (m *getModelRobotFriendField) GetUserWechatIdField() string { + return "user_wechat_id" +} + +type getModelSchedTaskField struct {} +var ModelSchedTaskField getModelSchedTaskField + +func (m *getModelSchedTaskField) GetCreatedAtField() string { + return "created_at" +} + +func (m *getModelSchedTaskField) GetExpiredAtField() string { + return "expired_at" +} + +func (m *getModelSchedTaskField) GetIdField() string { + return "_id" +} + +func (m *getModelSchedTaskField) GetReqIdField() string { + return "req_id" +} + +func (m *getModelSchedTaskField) GetReqJsonField() string { + return "req_json" +} + +func (m *getModelSchedTaskField) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *getModelSchedTaskField) GetRspJsonField() string { + return "rsp_json" +} + +func (m *getModelSchedTaskField) GetTaskStateField() string { + return "task_state" +} + +func (m *getModelSchedTaskField) GetTaskTypeField() string { + return "task_type" +} + +func (m *getModelSchedTaskField) GetUpdatedAtField() string { + return "updated_at" +} + +type getModelTbGroupMsgSessionField struct {} +var ModelTbGroupMsgSessionField getModelTbGroupMsgSessionField + +func (m *getModelTbGroupMsgSessionField) GetAllField() string { + return "all" +} + +func (m *getModelTbGroupMsgSessionField) GetIdField() string { + return "_id" +} + +func (m *getModelTbGroupMsgSessionField) GetLastFriendMsgAtField() string { + return "last_friend_msg_at" +} + +func (m *getModelTbGroupMsgSessionField) GetLastFriendMsgIdField() string { + return "last_friend_msg_id" +} + +func (m *getModelTbGroupMsgSessionField) GetLastMemberWxIdField() string { + return "last_member_wx_id" +} + +func (m *getModelTbGroupMsgSessionField) GetLastMsgAtField() string { + return "last_msg_at" +} + +func (m *getModelTbGroupMsgSessionField) GetLastMsgIdField() string { + return "last_msg_id" +} + +func (m *getModelTbGroupMsgSessionField) GetReadField() string { + return "read" +} + +func (m *getModelTbGroupMsgSessionField) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *getModelTbGroupMsgSessionField) GetUnreadField() string { + return "unread" +} + +func (m *getModelTbGroupMsgSessionField) GetUserWxIdField() string { + return "user_wx_id" +} + +type getModelTbPrivateMsgSessionField struct {} +var ModelTbPrivateMsgSessionField getModelTbPrivateMsgSessionField + +func (m *getModelTbPrivateMsgSessionField) GetAllField() string { + return "all" +} + +func (m *getModelTbPrivateMsgSessionField) GetIdField() string { + return "_id" +} + +func (m *getModelTbPrivateMsgSessionField) GetLastFriendMsgAtField() string { + return "last_friend_msg_at" +} + +func (m *getModelTbPrivateMsgSessionField) GetLastFriendMsgIdField() string { + return "last_friend_msg_id" +} + +func (m *getModelTbPrivateMsgSessionField) GetLastMsgAtField() string { + return "last_msg_at" +} + +func (m *getModelTbPrivateMsgSessionField) GetLastMsgIdField() string { + return "last_msg_id" +} + +func (m *getModelTbPrivateMsgSessionField) GetReadField() string { + return "read" +} + +func (m *getModelTbPrivateMsgSessionField) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *getModelTbPrivateMsgSessionField) GetUnreadField() string { + return "unread" +} + +func (m *getModelTbPrivateMsgSessionField) GetUserWxIdField() string { + return "user_wx_id" +} + +type getModelTbRobotGroupMsgField struct {} +var ModelTbRobotGroupMsgField getModelTbRobotGroupMsgField + +func (m *getModelTbRobotGroupMsgField) GetBindIdField() string { + return "bind_id" +} + +func (m *getModelTbRobotGroupMsgField) GetCallBackAtField() string { + return "call_back_at" +} + +func (m *getModelTbRobotGroupMsgField) GetContentDataField() string { + return "content_data" +} + +func (m *getModelTbRobotGroupMsgField) GetContentReadField() string { + return "content_read" +} + +func (m *getModelTbRobotGroupMsgField) GetCreatedAtField() string { + return "created_at" +} + +func (m *getModelTbRobotGroupMsgField) GetCursorField() string { + return "cursor" +} + +func (m *getModelTbRobotGroupMsgField) GetDirectField() string { + return "direct" +} + +func (m *getModelTbRobotGroupMsgField) GetExpireAtField() string { + return "expire_at" +} + +func (m *getModelTbRobotGroupMsgField) GetFailReasonField() string { + return "fail_reason" +} + +func (m *getModelTbRobotGroupMsgField) GetIdField() string { + return "_id" +} + +func (m *getModelTbRobotGroupMsgField) GetMsgIdField() string { + return "msg_id" +} + +func (m *getModelTbRobotGroupMsgField) GetMsgTypeField() string { + return "msg_type" +} + +func (m *getModelTbRobotGroupMsgField) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *getModelTbRobotGroupMsgField) GetSendAtField() string { + return "send_at" +} + +func (m *getModelTbRobotGroupMsgField) GetSendErrorCodeField() string { + return "send_error_code" +} + +func (m *getModelTbRobotGroupMsgField) GetSendStatusField() string { + return "send_status" +} + +func (m *getModelTbRobotGroupMsgField) GetSenderWxIdField() string { + return "sender_wx_id" +} + +func (m *getModelTbRobotGroupMsgField) GetUpdatedAtField() string { + return "updated_at" +} + +func (m *getModelTbRobotGroupMsgField) GetUserWxIdField() string { + return "user_wx_id" +} + +type getModelTbRobotPrivateMsgField struct {} +var ModelTbRobotPrivateMsgField getModelTbRobotPrivateMsgField + +func (m *getModelTbRobotPrivateMsgField) GetBindIdField() string { + return "bind_id" +} + +func (m *getModelTbRobotPrivateMsgField) GetCallBackAtField() string { + return "call_back_at" +} + +func (m *getModelTbRobotPrivateMsgField) GetContentDataField() string { + return "content_data" +} + +func (m *getModelTbRobotPrivateMsgField) GetContentReadField() string { + return "content_read" +} + +func (m *getModelTbRobotPrivateMsgField) GetCreatedAtField() string { + return "created_at" +} + +func (m *getModelTbRobotPrivateMsgField) GetCursorField() string { + return "cursor" +} + +func (m *getModelTbRobotPrivateMsgField) GetDirectField() string { + return "direct" +} + +func (m *getModelTbRobotPrivateMsgField) GetExpireAtField() string { + return "expire_at" +} + +func (m *getModelTbRobotPrivateMsgField) GetFailReasonField() string { + return "fail_reason" +} + +func (m *getModelTbRobotPrivateMsgField) GetIdField() string { + return "_id" +} + +func (m *getModelTbRobotPrivateMsgField) GetMsgIdField() string { + return "msg_id" +} + +func (m *getModelTbRobotPrivateMsgField) GetMsgTypeField() string { + return "msg_type" +} + +func (m *getModelTbRobotPrivateMsgField) GetRobotWxIdField() string { + return "robot_wx_id" +} + +func (m *getModelTbRobotPrivateMsgField) GetSendAtField() string { + return "send_at" +} + +func (m *getModelTbRobotPrivateMsgField) GetSendErrorCodeField() string { + return "send_error_code" +} + +func (m *getModelTbRobotPrivateMsgField) GetSendStatusField() string { + return "send_status" +} + +func (m *getModelTbRobotPrivateMsgField) GetUpdatedAtField() string { + return "updated_at" +} + +func (m *getModelTbRobotPrivateMsgField) GetUserWxIdField() string { + return "user_wx_id" +} + +type getModelWsConnectRecordField struct {} +var ModelWsConnectRecordField getModelWsConnectRecordField + +func (m *getModelWsConnectRecordField) GetBindIdField() string { + return "bind_id" +} + +func (m *getModelWsConnectRecordField) GetCreatedAtField() string { + return "created_at" +} + +func (m *getModelWsConnectRecordField) GetExpiredAtField() string { + return "expired_at" +} + +func (m *getModelWsConnectRecordField) GetIdField() string { + return "_id" +} + +func (m *getModelWsConnectRecordField) GetLoginAtField() string { + return "login_at" +} + +func (m *getModelWsConnectRecordField) GetLogoutAtField() string { + return "logout_at" +} + +func (m *getModelWsConnectRecordField) GetUserIdField() string { + return "user_id" +} + diff --git a/autogen_model_mdbc.go b/autogen_model_mdbc.go new file mode 100644 index 0000000..5e85b3f --- /dev/null +++ b/autogen_model_mdbc.go @@ -0,0 +1,2835 @@ +// Code generated by proto_parser. DO NOT EDIT. +// source: mdbc.proto + +package mdbc + +import ( + "gitlab.com/gotk/gotk/core" +) + + +// Auto Generated ModelFriendInfo Table Name. DO NOT EDIT. +const TableNameModelFriendInfo = "tb_friends_info" +func (t *ModelFriendInfo) TableName() string { + return "tb_friends_info" +} + +// Auto Generated ModelGroupChat Table Name. DO NOT EDIT. +const TableNameModelGroupChat = "tb_crm_group_chat" +func (t *ModelGroupChat) TableName() string { + return "tb_crm_group_chat" +} + +// Auto Generated ModelGroupChatMember Table Name. DO NOT EDIT. +const TableNameModelGroupChatMember = "tb_crm_group_chat_member" +func (t *ModelGroupChatMember) TableName() string { + return "tb_crm_group_chat_member" +} + +// Auto Generated ModelRobot Table Name. DO NOT EDIT. +const TableNameModelRobot = "tb_robot" +func (t *ModelRobot) TableName() string { + return "tb_robot" +} + +// Auto Generated ModelRobotFriend Table Name. DO NOT EDIT. +const TableNameModelRobotFriend = "tb_robot_friend" +func (t *ModelRobotFriend) TableName() string { + return "tb_robot_friend" +} + +// Auto Generated ModelSchedTask Table Name. DO NOT EDIT. +const TableNameModelSchedTask = "tb_crm_sched_task" +func (t *ModelSchedTask) TableName() string { + return "tb_crm_sched_task" +} + +// Auto Generated ModelTbGroupMsgSession Table Name. DO NOT EDIT. +const TableNameModelTbGroupMsgSession = "tb_crm_group_msg_session" +func (t *ModelTbGroupMsgSession) TableName() string { + return "tb_crm_group_msg_session" +} + +// Auto Generated ModelTbPrivateMsgSession Table Name. DO NOT EDIT. +const TableNameModelTbPrivateMsgSession = "tb_crm_private_msg_session" +func (t *ModelTbPrivateMsgSession) TableName() string { + return "tb_crm_private_msg_session" +} + +// Auto Generated ModelTbRobotGroupMsg Table Name. DO NOT EDIT. +const TableNameModelTbRobotGroupMsg = "tb_crm_robot_group_msg" +func (t *ModelTbRobotGroupMsg) TableName() string { + return "tb_crm_robot_group_msg" +} + +// Auto Generated ModelTbRobotPrivateMsg Table Name. DO NOT EDIT. +const TableNameModelTbRobotPrivateMsg = "tb_crm_robot_private_msg" +func (t *ModelTbRobotPrivateMsg) TableName() string { + return "tb_crm_robot_private_msg" +} + +// Auto Generated ModelWsConnectRecord Table Name. DO NOT EDIT. +const TableNameModelWsConnectRecord = "tb_ws_connect_record" +func (t *ModelWsConnectRecord) TableName() string { + return "tb_ws_connect_record" +} + + +func (m *AtMsgItem) GetContentCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Content", + DbFieldName: "content", + Comment: "文本内容", + } +} + +func (m *AtMsgItem) GetNickNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "NickName", + DbFieldName: "nick_name", + Comment: "@的昵称", + } +} + +func (m *AtMsgItem) GetSubTypeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "SubType", + DbFieldName: "sub_type", + Comment: "0:文本内容,1:@某人", + } +} + +func (m *AtMsgItem) GetUserNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UserName", + DbFieldName: "user_name", + Comment: "@的用户(wx_id)", + } +} + +func (m *ContentData) GetAtMsgItemCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AtMsgItem", + DbFieldName: "at_msg_item", + Comment: "发送群@部分人消息的数据", + } +} + +func (m *ContentData) GetAtUserNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AtUserName", + DbFieldName: "at_user_name", + Comment: "群聊at消息", + } +} + +func (m *ContentData) GetContentCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Content", + DbFieldName: "content", + Comment: "1文本的内容;2 语音的url(amr格式);6小程序的xml;", + } +} + +func (m *ContentData) GetFileSizeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "FileSize", + DbFieldName: "file_size", + Comment: "文件大小KB单位", + } +} + +func (m *ContentData) GetFileUrlCore() *core.StructField { + return &core.StructField{ + StructFieldName: "FileUrl", + DbFieldName: "file_url", + Comment: "3图片的url;4视频的Url;5链接的分享图;8表情的url(gif);9文件的url;", + } +} + +func (m *ContentData) GetIsAtMyselfCore() *core.StructField { + return &core.StructField{ + StructFieldName: "IsAtMyself", + DbFieldName: "is_at_myself", + Comment: "是否有at我自己 单独一个字段 方便维护和查询", + } +} + +func (m *ContentData) GetRawContentCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RawContent", + DbFieldName: "raw_content", + Comment: "元始的xml数据 做数据转发时用;", + } +} + +func (m *ContentData) GetResourceDurationCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ResourceDuration", + DbFieldName: "resource_duration", + Comment: "媒体时长 统一单位s", + } +} + +func (m *ContentData) GetShareDescCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ShareDesc", + DbFieldName: "share_desc", + Comment: "5链接的描述;", + } +} + +func (m *ContentData) GetShareNickNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ShareNickName", + DbFieldName: "share_nick_name", + Comment: "7名片的被分享(名片)的昵称;", + } +} + +func (m *ContentData) GetShareTitleCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ShareTitle", + DbFieldName: "share_title", + Comment: "5链接的标题;", + } +} + +func (m *ContentData) GetShareUrlCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ShareUrl", + DbFieldName: "share_url", + Comment: "5链接的URL;", + } +} + +func (m *ContentData) GetShareUserNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ShareUserName", + DbFieldName: "share_user_name", + Comment: "7名片的被分享(名片)好友id;", + } +} + +func (m *ContentData) GetWxMsgTypeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "WxMsgType", + DbFieldName: "wx_msg_type", + Comment: "消息类型: 1 文本;2 语音;3 图片;4 视频;5 链接;6 小程序;7", + } +} + +func (m *ModelFriendInfo) GetAvatarUrlCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AvatarUrl", + DbFieldName: "avatar_url", + Comment: "用户头像", + } +} + +func (m *ModelFriendInfo) GetCityCore() *core.StructField { + return &core.StructField{ + StructFieldName: "City", + DbFieldName: "city", + Comment: "城市", + } +} + +func (m *ModelFriendInfo) GetCountryCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Country", + DbFieldName: "country", + Comment: "国家", + } +} + +func (m *ModelFriendInfo) GetCreateTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CreateTime", + DbFieldName: "create_time", + Comment: "创建时间", + } +} + +func (m *ModelFriendInfo) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID wxid md5", + } +} + +func (m *ModelFriendInfo) GetNicknameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Nickname", + DbFieldName: "nick_name", + Comment: "用户暱称", + } +} + +func (m *ModelFriendInfo) GetPhoneCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Phone", + DbFieldName: "phone", + Comment: "手机号码", + } +} + +func (m *ModelFriendInfo) GetProvinceCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Province", + DbFieldName: "province", + Comment: "省份", + } +} + +func (m *ModelFriendInfo) GetSexCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Sex", + DbFieldName: "sex", + Comment: "0未知 1男 2女", + } +} + +func (m *ModelFriendInfo) GetUpdateTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UpdateTime", + DbFieldName: "update_time", + Comment: "更新时间", + } +} + +func (m *ModelFriendInfo) GetWechatAliasCore() *core.StructField { + return &core.StructField{ + StructFieldName: "WechatAlias", + DbFieldName: "wechat_alias", + Comment: "用户微信号", + } +} + +func (m *ModelFriendInfo) GetWechatIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "WechatId", + DbFieldName: "wechat_id", + Comment: "用户微信ID", + } +} + +func (m *ModelGroupChat) GetAdminTypeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AdminType", + DbFieldName: "admin_type", + Comment: "机器人权限类型", + } +} + +func (m *ModelGroupChat) GetCreatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "创建时间", + } +} + +func (m *ModelGroupChat) GetDeletedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "DeletedAt", + DbFieldName: "deleted_at", + Comment: "删除时间【记: 此表正常情况下 只进行软删除】非零 历史群 0正常群", + } +} + +func (m *ModelGroupChat) GetDisableInviteCore() *core.StructField { + return &core.StructField{ + StructFieldName: "DisableInvite", + DbFieldName: "disable_invite", + Comment: "是否开启了群聊邀请确认 true 开启了 false 关闭了", + } +} + +func (m *ModelGroupChat) GetGroupAvatarUrlCore() *core.StructField { + return &core.StructField{ + StructFieldName: "GroupAvatarUrl", + DbFieldName: "group_avatar_url", + Comment: "群头像", + } +} + +func (m *ModelGroupChat) GetGroupNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "GroupName", + DbFieldName: "group_name", + Comment: "群名称", + } +} + +func (m *ModelGroupChat) GetGroupWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "GroupWxId", + DbFieldName: "group_wx_id", + Comment: "群id", + } +} + +func (m *ModelGroupChat) GetHasBeenWatchCore() *core.StructField { + return &core.StructField{ + StructFieldName: "HasBeenWatch", + DbFieldName: "has_been_watch", + Comment: "以前有关注过", + } +} + +func (m *ModelGroupChat) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID", + } +} + +func (m *ModelGroupChat) GetInContactCore() *core.StructField { + return &core.StructField{ + StructFieldName: "InContact", + DbFieldName: "in_contact", + Comment: "是否在通讯录中", + } +} + +func (m *ModelGroupChat) GetIsDefaultGroupNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "IsDefaultGroupName", + DbFieldName: "is_default_group_name", + Comment: "是否是默认的群名称", + } +} + +func (m *ModelGroupChat) GetIsWatchCore() *core.StructField { + return &core.StructField{ + StructFieldName: "IsWatch", + DbFieldName: "is_watch", + Comment: "是否关注群", + } +} + +func (m *ModelGroupChat) GetLastSyncAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastSyncAt", + DbFieldName: "last_sync_at", + Comment: "最后更新群信息时间 【通过这里 指定规则 去拉群基本信息】", + } +} + +func (m *ModelGroupChat) GetLastSyncMemberAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastSyncMemberAt", + DbFieldName: "last_sync_member_at", + Comment: "最后更新群成员时间 【通过这里 指定规则 去拉群成员信息】", + } +} + +func (m *ModelGroupChat) GetMemberCountCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MemberCount", + DbFieldName: "member_count", + Comment: "群成员数量", + } +} + +func (m *ModelGroupChat) GetNoticeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Notice", + DbFieldName: "notice", + Comment: "群公告", + } +} + +func (m *ModelGroupChat) GetOwnerNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "OwnerName", + DbFieldName: "owner_name", + Comment: "群主名称", + } +} + +func (m *ModelGroupChat) GetOwnerWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "OwnerWxId", + DbFieldName: "owner_wx_id", + Comment: "群主id", + } +} + +func (m *ModelGroupChat) GetQrcodeUpdatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "QrcodeUpdatedAt", + DbFieldName: "qrcode_updated_at", + Comment: "群聊二维码更新时间", + } +} + +func (m *ModelGroupChat) GetQrcodeUrlCore() *core.StructField { + return &core.StructField{ + StructFieldName: "QrcodeUrl", + DbFieldName: "qrcode_url", + Comment: "群聊二维码", + } +} + +func (m *ModelGroupChat) GetRobotWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", + } +} + +func (m *ModelGroupChat) GetUpdatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UpdatedAt", + DbFieldName: "updated_at", + Comment: "更新时间", + } +} + +func (m *ModelGroupChatMember) GetAdminTypeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AdminType", + DbFieldName: "admin_type", + Comment: "权限类型 群主 管理员 普通成员", + } +} + +func (m *ModelGroupChatMember) GetCreatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "创建时间", + } +} + +func (m *ModelGroupChatMember) GetDeletedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "DeletedAt", + DbFieldName: "deleted_at", + Comment: "删除时间 这个表一般直接硬删除", + } +} + +func (m *ModelGroupChatMember) GetGroupChatIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "GroupChatId", + DbFieldName: "group_chat_id", + Comment: "群 ModelGroupChat 的ID", + } +} + +func (m *ModelGroupChatMember) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "id", + } +} + +func (m *ModelGroupChatMember) GetIsRobotCore() *core.StructField { + return &core.StructField{ + StructFieldName: "IsRobot", + DbFieldName: "is_robot", + Comment: "是否是机器人", + } +} + +func (m *ModelGroupChatMember) GetLastSyncAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastSyncAt", + DbFieldName: "last_sync_at", + Comment: "该群该成员 最后更新时间", + } +} + +func (m *ModelGroupChatMember) GetMemberAliasCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MemberAlias", + DbFieldName: "member_alias", + Comment: "群昵称", + } +} + +func (m *ModelGroupChatMember) GetMemberAvatarCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MemberAvatar", + DbFieldName: "member_avatar", + Comment: "群成员头像", + } +} + +func (m *ModelGroupChatMember) GetMemberNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MemberName", + DbFieldName: "member_name", + Comment: "群成员名称", + } +} + +func (m *ModelGroupChatMember) GetMemberSexCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MemberSex", + DbFieldName: "member_sex", + Comment: "性别", + } +} + +func (m *ModelGroupChatMember) GetMemberWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MemberWxId", + DbFieldName: "member_wx_id", + Comment: "群成员微信id", + } +} + +func (m *ModelGroupChatMember) GetUpdatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UpdatedAt", + DbFieldName: "updated_at", + Comment: "更新时间", + } +} + +func (m *ModelRobot) GetAbilityLimitCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AbilityLimit", + DbFieldName: "ability_limit", + Comment: "机器人是否功能受限", + } +} + +func (m *ModelRobot) GetAliasNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AliasName", + DbFieldName: "alias_name", + Comment: "微信号", + } +} + +func (m *ModelRobot) GetAndroidStatusCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AndroidStatus", + DbFieldName: "android_status", + Comment: "机器人Android是否在线 10在线 11离线", + } +} + +func (m *ModelRobot) GetAndroidWechatVersionCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AndroidWechatVersion", + DbFieldName: "android_wechat_version", + Comment: "微信版本", + } +} + +func (m *ModelRobot) GetAutoAddFriendCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AutoAddFriend", + DbFieldName: "auto_add_friend", + Comment: "机器人是否自动通过好友请求 0否 1是", + } +} + +func (m *ModelRobot) GetAvatarUrlCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AvatarUrl", + DbFieldName: "avatar_url", + Comment: "机器人头像", + } +} + +func (m *ModelRobot) GetCityCore() *core.StructField { + return &core.StructField{ + StructFieldName: "City", + DbFieldName: "city", + Comment: "城市", + } +} + +func (m *ModelRobot) GetCountryCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Country", + DbFieldName: "country", + Comment: "国家", + } +} + +func (m *ModelRobot) GetCoverUrlCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CoverUrl", + DbFieldName: "cover_url", + Comment: "朋友圈封面url", + } +} + +func (m *ModelRobot) GetCreateTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CreateTime", + DbFieldName: "create_time", + Comment: "创建时间", + } +} + +func (m *ModelRobot) GetCrmAutoAddFriendCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CrmAutoAddFriend", + DbFieldName: "crm_auto_add_friend", + Comment: "crm系统自动通过好友 1自动通过 0不自动通过", + } +} + +func (m *ModelRobot) GetCrmShopIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CrmShopId", + DbFieldName: "crm_shop_id", + Comment: "机器人所属商户id", + } +} + +func (m *ModelRobot) GetDeleteTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "DeleteTime", + DbFieldName: "delete_time", + Comment: "删除时间", + } +} + +func (m *ModelRobot) GetGreetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "GreetId", + DbFieldName: "greet_id", + Comment: "打招呼模板id", + } +} + +func (m *ModelRobot) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID wxid md5", + } +} + +func (m *ModelRobot) GetInitFriendCore() *core.StructField { + return &core.StructField{ + StructFieldName: "InitFriend", + DbFieldName: "init_friend", + Comment: "机器人初始好友人数", + } +} + +func (m *ModelRobot) GetLastAndroidLoginAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastAndroidLoginAt", + DbFieldName: "last_android_login_at", + Comment: "最近安卓登录时间", + } +} + +func (m *ModelRobot) GetLastAndroidLogoutAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastAndroidLogoutAt", + DbFieldName: "last_android_logout_at", + Comment: "最近安卓登出时间", + } +} + +func (m *ModelRobot) GetLastCityCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastCity", + DbFieldName: "last_city", + Comment: "最后登录的城市名称", + } +} + +func (m *ModelRobot) GetLastLogOutTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastLogOutTime", + DbFieldName: "last_log_out_time", + Comment: "最后登出时间", + } +} + +func (m *ModelRobot) GetLastLoginTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastLoginTime", + DbFieldName: "last_login_time", + Comment: "最后登录时间", + } +} + +func (m *ModelRobot) GetLastPcLoginAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastPcLoginAt", + DbFieldName: "last_pc_login_at", + Comment: "最近PC登录时间", + } +} + +func (m *ModelRobot) GetLastPcLogoutAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastPcLogoutAt", + DbFieldName: "last_pc_logout_at", + Comment: "最近PC登出时间", + } +} + +func (m *ModelRobot) GetLastRegionCodeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastRegionCode", + DbFieldName: "last_region_code", + Comment: "最后登录的扫码设备的地区编码", + } +} + +func (m *ModelRobot) GetLastRequireAddFriendTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastRequireAddFriendTime", + DbFieldName: "last_require_add_friend_time", + Comment: "上一次请求添加好友的时间", + } +} + +func (m *ModelRobot) GetLimitedCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Limited", + DbFieldName: "limited", + Comment: "机器人是否被封号 0未封号 1已封号", + } +} + +func (m *ModelRobot) GetLogAndOutTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LogAndOutTime", + DbFieldName: "log_and_out_time", + Comment: "登入或者登出都要记录一下", + } +} + +func (m *ModelRobot) GetMobileCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Mobile", + DbFieldName: "mobile", + Comment: "手机号码", + } +} + +func (m *ModelRobot) GetMomentPrivacyTypeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MomentPrivacyType", + DbFieldName: "moment_privacy_type", + Comment: "朋友圈隐私选项类型", + } +} + +func (m *ModelRobot) GetNickNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "NickName", + DbFieldName: "nick_name", + Comment: "机器人暱称", + } +} + +func (m *ModelRobot) GetNowFriendCore() *core.StructField { + return &core.StructField{ + StructFieldName: "NowFriend", + DbFieldName: "now_friend", + Comment: "机器人当前好友数量", + } +} + +func (m *ModelRobot) GetOpenForStrangerCore() *core.StructField { + return &core.StructField{ + StructFieldName: "OpenForStranger", + DbFieldName: "open_for_stranger", + Comment: "是否允许陌生人查看十条朋友圈", + } +} + +func (m *ModelRobot) GetProvinceCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Province", + DbFieldName: "province", + Comment: "省份", + } +} + +func (m *ModelRobot) GetQrcodeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Qrcode", + DbFieldName: "qrcode", + Comment: "机器人二维码", + } +} + +func (m *ModelRobot) GetRiskControlGroupCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RiskControlGroup", + DbFieldName: "risk_control_group", + Comment: "风控分组", + } +} + +func (m *ModelRobot) GetRiskControlTaskCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RiskControlTask", + DbFieldName: "risk_control_task", + Comment: "风控任务 0是全部,1是回复,2是发消息,3是看朋友圈,4是发朋友圈,5是点赞,6是评论 7是群聊 可组合,如:1,2,3", + } +} + +func (m *ModelRobot) GetSexCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Sex", + DbFieldName: "sex", + Comment: "性别 0 未知 1 男生 2 女生", + } +} + +func (m *ModelRobot) GetSignatureCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Signature", + DbFieldName: "signature", + Comment: "个性签名", + } +} + +func (m *ModelRobot) GetStatusCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Status", + DbFieldName: "status", + Comment: "机器人PC是否在线 10在线 11离线 (兼容之前的pc登录流程和其他接口,这个登录状态不变,补多一个字段代表安卓登录状态)", + } +} + +func (m *ModelRobot) GetTodayRequireTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "TodayRequireTime", + DbFieldName: "today_require_time", + Comment: "当天请求次数", + } +} + +func (m *ModelRobot) GetUpdateTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UpdateTime", + DbFieldName: "update_time", + Comment: "更新时间", + } +} + +func (m *ModelRobot) GetUserIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UserId", + DbFieldName: "user_id", + Comment: "机器人所属用户id", + } +} + +func (m *ModelRobot) GetWechatAliasCore() *core.StructField { + return &core.StructField{ + StructFieldName: "WechatAlias", + DbFieldName: "wechat_alias", + Comment: "微信ID (用户自己定义的微信号)", + } +} + +func (m *ModelRobot) GetWechatIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "WechatId", + DbFieldName: "wechat_id", + Comment: "微信唯一ID (wxidxxxxxx)", + } +} + +func (m *ModelRobotFriend) GetAddAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "AddAt", + DbFieldName: "add_at", + Comment: "添加好友时间只有主动添加好友才有", + } +} + +func (m *ModelRobotFriend) GetCreateTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CreateTime", + DbFieldName: "create_time", + Comment: "创建时间:入库时间", + } +} + +func (m *ModelRobotFriend) GetCrmPhoneCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CrmPhone", + DbFieldName: "crm_phone", + Comment: "CRM自己设置的好友手机号,不同于微信手机号", + } +} + +func (m *ModelRobotFriend) GetDeleteTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "DeleteTime", + DbFieldName: "delete_time", + Comment: "删除好友的时间", + } +} + +func (m *ModelRobotFriend) GetDeletedCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Deleted", + DbFieldName: "deleted", + Comment: "是否被删除 0双方未删除 1被好友删除 2删除了好友 3互相删除", + } +} + +func (m *ModelRobotFriend) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID 机器人id+朋友id md5", + } +} + +func (m *ModelRobotFriend) GetOfflineAddCore() *core.StructField { + return &core.StructField{ + StructFieldName: "OfflineAdd", + DbFieldName: "offline_add", + Comment: "是否为离线添加", + } +} + +func (m *ModelRobotFriend) GetPinyinCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Pinyin", + DbFieldName: "pinyin", + Comment: "用户备注或者暱称的拼音", + } +} + +func (m *ModelRobotFriend) GetPinyinHeadCore() *core.StructField { + return &core.StructField{ + StructFieldName: "PinyinHead", + DbFieldName: "pinyin_head", + Comment: "拼音首字母", + } +} + +func (m *ModelRobotFriend) GetRemarkNameCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RemarkName", + DbFieldName: "remark_name", + Comment: "微信好友备注名称", + } +} + +func (m *ModelRobotFriend) GetRobotWechatIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RobotWechatId", + DbFieldName: "robot_wechat_id", + Comment: "机器人编号:微信ID", + } +} + +func (m *ModelRobotFriend) GetUpdateTimeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UpdateTime", + DbFieldName: "update_time", + Comment: "更新时间", + } +} + +func (m *ModelRobotFriend) GetUserWechatIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UserWechatId", + DbFieldName: "user_wechat_id", + Comment: "用户微信ID,", + } +} + +func (m *ModelSchedTask) GetCreatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "创建时间", + } +} + +func (m *ModelSchedTask) GetExpiredAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ExpiredAt", + DbFieldName: "expired_at", + Comment: "过期时间", + } +} + +func (m *ModelSchedTask) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "任务id", + } +} + +func (m *ModelSchedTask) GetReqIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ReqId", + DbFieldName: "req_id", + Comment: "便于查询该任务 指定的id[作用:有些情况 无法直接通过id来查询该记录]", + } +} + +func (m *ModelSchedTask) GetReqJsonCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ReqJson", + DbFieldName: "req_json", + Comment: "请求内容", + } +} + +func (m *ModelSchedTask) GetRobotWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", + } +} + +func (m *ModelSchedTask) GetRspJsonCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RspJson", + DbFieldName: "rsp_json", + Comment: "完成后的内容 [成功或者失败的返回]", + } +} + +func (m *ModelSchedTask) GetTaskStateCore() *core.StructField { + return &core.StructField{ + StructFieldName: "TaskState", + DbFieldName: "task_state", + Comment: "执行状态 TaskState", + } +} + +func (m *ModelSchedTask) GetTaskTypeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "TaskType", + DbFieldName: "task_type", + Comment: "任务类型 自定义的名称 用来区别是哪个模块发起的任务", + } +} + +func (m *ModelSchedTask) GetUpdatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UpdatedAt", + DbFieldName: "updated_at", + Comment: "更新时间", + } +} + +func (m *ModelTbGroupMsgSession) GetAllCore() *core.StructField { + return &core.StructField{ + StructFieldName: "All", + DbFieldName: "all", + Comment: "消息最大游标(消息总数:只算有效的消息)", + } +} + +func (m *ModelTbGroupMsgSession) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "会话ID (md5(机器人id+好友id))", + } +} + +func (m *ModelTbGroupMsgSession) GetLastFriendMsgAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastFriendMsgAt", + DbFieldName: "last_friend_msg_at", + Comment: "接受到最后一条好友消息时间", + } +} + +func (m *ModelTbGroupMsgSession) GetLastFriendMsgIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastFriendMsgId", + DbFieldName: "last_friend_msg_id", + Comment: "接收的最后一条好友消息id", + } +} + +func (m *ModelTbGroupMsgSession) GetLastMemberWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastMemberWxId", + DbFieldName: "last_member_wx_id", + Comment: "最后发送消息的群成员id", + } +} + +func (m *ModelTbGroupMsgSession) GetLastMsgAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastMsgAt", + DbFieldName: "last_msg_at", + Comment: "最后一条消息时间", + } +} + +func (m *ModelTbGroupMsgSession) GetLastMsgIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastMsgId", + DbFieldName: "last_msg_id", + Comment: "最后一条消息id", + } +} + +func (m *ModelTbGroupMsgSession) GetReadCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Read", + DbFieldName: "read", + Comment: "已读游标", + } +} + +func (m *ModelTbGroupMsgSession) GetRobotWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", + } +} + +func (m *ModelTbGroupMsgSession) GetUnreadCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Unread", + DbFieldName: "unread", + Comment: "未读消息游标", + } +} + +func (m *ModelTbGroupMsgSession) GetUserWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UserWxId", + DbFieldName: "user_wx_id", + Comment: "群微信id", + } +} + +func (m *ModelTbPrivateMsgSession) GetAllCore() *core.StructField { + return &core.StructField{ + StructFieldName: "All", + DbFieldName: "all", + Comment: "消息最大游标(消息总数:只算有效的消息)", + } +} + +func (m *ModelTbPrivateMsgSession) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "会话ID (md5(机器人id+好友id))", + } +} + +func (m *ModelTbPrivateMsgSession) GetLastFriendMsgAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastFriendMsgAt", + DbFieldName: "last_friend_msg_at", + Comment: "接受到最后一条好友消息时间", + } +} + +func (m *ModelTbPrivateMsgSession) GetLastFriendMsgIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastFriendMsgId", + DbFieldName: "last_friend_msg_id", + Comment: "接收的最后一条好友消息id", + } +} + +func (m *ModelTbPrivateMsgSession) GetLastMsgAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastMsgAt", + DbFieldName: "last_msg_at", + Comment: "最后一条消息时间", + } +} + +func (m *ModelTbPrivateMsgSession) GetLastMsgIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LastMsgId", + DbFieldName: "last_msg_id", + Comment: "最后一条消息id", + } +} + +func (m *ModelTbPrivateMsgSession) GetReadCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Read", + DbFieldName: "read", + Comment: "已读游标", + } +} + +func (m *ModelTbPrivateMsgSession) GetRobotWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", + } +} + +func (m *ModelTbPrivateMsgSession) GetUnreadCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Unread", + DbFieldName: "unread", + Comment: "未读消息游标", + } +} + +func (m *ModelTbPrivateMsgSession) GetUserWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UserWxId", + DbFieldName: "user_wx_id", + Comment: "好友微信id", + } +} + +func (m *ModelTbRobotGroupMsg) GetBindIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "BindId", + DbFieldName: "bind_id", + Comment: "前端消息id", + } +} + +func (m *ModelTbRobotGroupMsg) GetCallBackAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CallBackAt", + DbFieldName: "call_back_at", + Comment: "消息返回时间", + } +} + +func (m *ModelTbRobotGroupMsg) GetContentDataCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ContentData", + DbFieldName: "content_data", + Comment: "消息内容", + } +} + +func (m *ModelTbRobotGroupMsg) GetContentReadCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ContentRead", + DbFieldName: "content_read", + Comment: "是否内容被浏览(像语音之类的,需要浏览)", + } +} + +func (m *ModelTbRobotGroupMsg) GetCreatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "创建时间", + } +} + +func (m *ModelTbRobotGroupMsg) GetCursorCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Cursor", + DbFieldName: "cursor", + Comment: "消息游标(对应session的all)", + } +} + +func (m *ModelTbRobotGroupMsg) GetDirectCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Direct", + DbFieldName: "direct", + Comment: "用于区分机器人是接收方还是发送方。1:机器人接收;2:机器人发送", + } +} + +func (m *ModelTbRobotGroupMsg) GetExpireAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ExpireAt", + DbFieldName: "expire_at", + Comment: "失效时间(用于消息的失效)", + } +} + +func (m *ModelTbRobotGroupMsg) GetFailReasonCore() *core.StructField { + return &core.StructField{ + StructFieldName: "FailReason", + DbFieldName: "fail_reason", + Comment: "失败原因", + } +} + +func (m *ModelTbRobotGroupMsg) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID", + } +} + +func (m *ModelTbRobotGroupMsg) GetMsgIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MsgId", + DbFieldName: "msg_id", + Comment: "服务端自己生成一个消息id,来对应客户端的发送结果id", + } +} + +func (m *ModelTbRobotGroupMsg) GetMsgTypeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MsgType", + DbFieldName: "msg_type", + Comment: "消息类型", + } +} + +func (m *ModelTbRobotGroupMsg) GetRobotWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", + } +} + +func (m *ModelTbRobotGroupMsg) GetSendAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "SendAt", + DbFieldName: "send_at", + Comment: "发送时间(消息实际生效时间)", + } +} + +func (m *ModelTbRobotGroupMsg) GetSendErrorCodeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "SendErrorCode", + DbFieldName: "send_error_code", + Comment: "发送错误码:用户告诉对应的是什么错误:-1 通用错误码; -2 被拉黑; -3", + } +} + +func (m *ModelTbRobotGroupMsg) GetSendStatusCore() *core.StructField { + return &core.StructField{ + StructFieldName: "SendStatus", + DbFieldName: "send_status", + Comment: "发送状态:0:发送中;1:发送请求成功;2:发送请求失败;3:发送成功;4:发送失败;仅机器人发送。接收到用户消息的默认3", + } +} + +func (m *ModelTbRobotGroupMsg) GetSenderWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "SenderWxId", + DbFieldName: "sender_wx_id", + Comment: "发送者id", + } +} + +func (m *ModelTbRobotGroupMsg) GetUpdatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UpdatedAt", + DbFieldName: "updated_at", + Comment: "更新时间", + } +} + +func (m *ModelTbRobotGroupMsg) GetUserWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UserWxId", + DbFieldName: "user_wx_id", + Comment: "群聊id", + } +} + +func (m *ModelTbRobotPrivateMsg) GetBindIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "BindId", + DbFieldName: "bind_id", + Comment: "前端消息id", + } +} + +func (m *ModelTbRobotPrivateMsg) GetCallBackAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CallBackAt", + DbFieldName: "call_back_at", + Comment: "消息返回时间", + } +} + +func (m *ModelTbRobotPrivateMsg) GetContentDataCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ContentData", + DbFieldName: "content_data", + Comment: "消息内容", + } +} + +func (m *ModelTbRobotPrivateMsg) GetContentReadCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ContentRead", + DbFieldName: "content_read", + Comment: "是否内容被浏览(像语音之类的,需要浏览)", + } +} + +func (m *ModelTbRobotPrivateMsg) GetCreatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "创建时间", + } +} + +func (m *ModelTbRobotPrivateMsg) GetCursorCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Cursor", + DbFieldName: "cursor", + Comment: "消息游标(对应session的all)", + } +} + +func (m *ModelTbRobotPrivateMsg) GetDirectCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Direct", + DbFieldName: "direct", + Comment: "用于区分机器人是接收方还是发送方。1:机器人接收;2:机器人发送", + } +} + +func (m *ModelTbRobotPrivateMsg) GetExpireAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ExpireAt", + DbFieldName: "expire_at", + Comment: "失效时间(用于消息的失效)", + } +} + +func (m *ModelTbRobotPrivateMsg) GetFailReasonCore() *core.StructField { + return &core.StructField{ + StructFieldName: "FailReason", + DbFieldName: "fail_reason", + Comment: "失败原因", + } +} + +func (m *ModelTbRobotPrivateMsg) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID", + } +} + +func (m *ModelTbRobotPrivateMsg) GetMsgIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MsgId", + DbFieldName: "msg_id", + Comment: "服务端自己生成一个消息id,来对应客户端的发送结果id", + } +} + +func (m *ModelTbRobotPrivateMsg) GetMsgTypeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "MsgType", + DbFieldName: "msg_type", + Comment: "消息类型", + } +} + +func (m *ModelTbRobotPrivateMsg) GetRobotWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", + } +} + +func (m *ModelTbRobotPrivateMsg) GetSendAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "SendAt", + DbFieldName: "send_at", + Comment: "发送时间(消息实际生效时间)", + } +} + +func (m *ModelTbRobotPrivateMsg) GetSendErrorCodeCore() *core.StructField { + return &core.StructField{ + StructFieldName: "SendErrorCode", + DbFieldName: "send_error_code", + Comment: "发送错误码:用户告诉对应的是什么错误:-1 通用错误码; -2 被拉黑; -3", + } +} + +func (m *ModelTbRobotPrivateMsg) GetSendStatusCore() *core.StructField { + return &core.StructField{ + StructFieldName: "SendStatus", + DbFieldName: "send_status", + Comment: "发送状态:0:发送中;1:发送请求成功;2:发送请求失败;3:发送成功;4:发送失败;仅机器人发送。接收到用户消息的默认3", + } +} + +func (m *ModelTbRobotPrivateMsg) GetUpdatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UpdatedAt", + DbFieldName: "updated_at", + Comment: "更新时间", + } +} + +func (m *ModelTbRobotPrivateMsg) GetUserWxIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UserWxId", + DbFieldName: "user_wx_id", + Comment: "好友id", + } +} + +func (m *ModelWsConnectRecord) GetBindIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "BindId", + DbFieldName: "bind_id", + Comment: "该ws绑定的id", + } +} + +func (m *ModelWsConnectRecord) GetCreatedAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "记录创建时间", + } +} + +func (m *ModelWsConnectRecord) GetExpiredAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "ExpiredAt", + DbFieldName: "expired_at", + Comment: "过期时间", + } +} + +func (m *ModelWsConnectRecord) GetIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID wxid md5", + } +} + +func (m *ModelWsConnectRecord) GetLoginAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LoginAt", + DbFieldName: "login_at", + Comment: "登录时间", + } +} + +func (m *ModelWsConnectRecord) GetLogoutAtCore() *core.StructField { + return &core.StructField{ + StructFieldName: "LogoutAt", + DbFieldName: "logout_at", + Comment: "登出时间", + } +} + +func (m *ModelWsConnectRecord) GetUserIdCore() *core.StructField { + return &core.StructField{ + StructFieldName: "UserId", + DbFieldName: "user_id", + Comment: "机器人所属用户id", + } +} + + +var AtMsgItemField_Content = core.StructField{ + StructFieldName: "Content", + DbFieldName: "content", + Comment: "文本内容", +} + +var AtMsgItemField_NickName = core.StructField{ + StructFieldName: "NickName", + DbFieldName: "nick_name", + Comment: "@的昵称", +} + +var AtMsgItemField_SubType = core.StructField{ + StructFieldName: "SubType", + DbFieldName: "sub_type", + Comment: "0:文本内容,1:@某人", +} + +var AtMsgItemField_UserName = core.StructField{ + StructFieldName: "UserName", + DbFieldName: "user_name", + Comment: "@的用户(wx_id)", +} + +var ContentDataField_AtMsgItem = core.StructField{ + StructFieldName: "AtMsgItem", + DbFieldName: "at_msg_item", + Comment: "发送群@部分人消息的数据", +} + +var ContentDataField_AtUserName = core.StructField{ + StructFieldName: "AtUserName", + DbFieldName: "at_user_name", + Comment: "群聊at消息", +} + +var ContentDataField_Content = core.StructField{ + StructFieldName: "Content", + DbFieldName: "content", + Comment: "1文本的内容;2 语音的url(amr格式);6小程序的xml;", +} + +var ContentDataField_FileSize = core.StructField{ + StructFieldName: "FileSize", + DbFieldName: "file_size", + Comment: "文件大小KB单位", +} + +var ContentDataField_FileUrl = core.StructField{ + StructFieldName: "FileUrl", + DbFieldName: "file_url", + Comment: "3图片的url;4视频的Url;5链接的分享图;8表情的url(gif);9文件的url;", +} + +var ContentDataField_IsAtMyself = core.StructField{ + StructFieldName: "IsAtMyself", + DbFieldName: "is_at_myself", + Comment: "是否有at我自己 单独一个字段 方便维护和查询", +} + +var ContentDataField_RawContent = core.StructField{ + StructFieldName: "RawContent", + DbFieldName: "raw_content", + Comment: "元始的xml数据 做数据转发时用;", +} + +var ContentDataField_ResourceDuration = core.StructField{ + StructFieldName: "ResourceDuration", + DbFieldName: "resource_duration", + Comment: "媒体时长 统一单位s", +} + +var ContentDataField_ShareDesc = core.StructField{ + StructFieldName: "ShareDesc", + DbFieldName: "share_desc", + Comment: "5链接的描述;", +} + +var ContentDataField_ShareNickName = core.StructField{ + StructFieldName: "ShareNickName", + DbFieldName: "share_nick_name", + Comment: "7名片的被分享(名片)的昵称;", +} + +var ContentDataField_ShareTitle = core.StructField{ + StructFieldName: "ShareTitle", + DbFieldName: "share_title", + Comment: "5链接的标题;", +} + +var ContentDataField_ShareUrl = core.StructField{ + StructFieldName: "ShareUrl", + DbFieldName: "share_url", + Comment: "5链接的URL;", +} + +var ContentDataField_ShareUserName = core.StructField{ + StructFieldName: "ShareUserName", + DbFieldName: "share_user_name", + Comment: "7名片的被分享(名片)好友id;", +} + +var ContentDataField_WxMsgType = core.StructField{ + StructFieldName: "WxMsgType", + DbFieldName: "wx_msg_type", + Comment: "消息类型: 1 文本;2 语音;3 图片;4 视频;5 链接;6 小程序;7", +} + +var ModelFriendInfoField_AvatarUrl = core.StructField{ + StructFieldName: "AvatarUrl", + DbFieldName: "avatar_url", + Comment: "用户头像", +} + +var ModelFriendInfoField_City = core.StructField{ + StructFieldName: "City", + DbFieldName: "city", + Comment: "城市", +} + +var ModelFriendInfoField_Country = core.StructField{ + StructFieldName: "Country", + DbFieldName: "country", + Comment: "国家", +} + +var ModelFriendInfoField_CreateTime = core.StructField{ + StructFieldName: "CreateTime", + DbFieldName: "create_time", + Comment: "创建时间", +} + +var ModelFriendInfoField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID wxid md5", +} + +var ModelFriendInfoField_Nickname = core.StructField{ + StructFieldName: "Nickname", + DbFieldName: "nick_name", + Comment: "用户暱称", +} + +var ModelFriendInfoField_Phone = core.StructField{ + StructFieldName: "Phone", + DbFieldName: "phone", + Comment: "手机号码", +} + +var ModelFriendInfoField_Province = core.StructField{ + StructFieldName: "Province", + DbFieldName: "province", + Comment: "省份", +} + +var ModelFriendInfoField_Sex = core.StructField{ + StructFieldName: "Sex", + DbFieldName: "sex", + Comment: "0未知 1男 2女", +} + +var ModelFriendInfoField_UpdateTime = core.StructField{ + StructFieldName: "UpdateTime", + DbFieldName: "update_time", + Comment: "更新时间", +} + +var ModelFriendInfoField_WechatAlias = core.StructField{ + StructFieldName: "WechatAlias", + DbFieldName: "wechat_alias", + Comment: "用户微信号", +} + +var ModelFriendInfoField_WechatId = core.StructField{ + StructFieldName: "WechatId", + DbFieldName: "wechat_id", + Comment: "用户微信ID", +} + +var ModelGroupChatField_AdminType = core.StructField{ + StructFieldName: "AdminType", + DbFieldName: "admin_type", + Comment: "机器人权限类型", +} + +var ModelGroupChatField_CreatedAt = core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "创建时间", +} + +var ModelGroupChatField_DeletedAt = core.StructField{ + StructFieldName: "DeletedAt", + DbFieldName: "deleted_at", + Comment: "删除时间【记: 此表正常情况下 只进行软删除】非零 历史群 0正常群", +} + +var ModelGroupChatField_DisableInvite = core.StructField{ + StructFieldName: "DisableInvite", + DbFieldName: "disable_invite", + Comment: "是否开启了群聊邀请确认 true 开启了 false 关闭了", +} + +var ModelGroupChatField_GroupAvatarUrl = core.StructField{ + StructFieldName: "GroupAvatarUrl", + DbFieldName: "group_avatar_url", + Comment: "群头像", +} + +var ModelGroupChatField_GroupName = core.StructField{ + StructFieldName: "GroupName", + DbFieldName: "group_name", + Comment: "群名称", +} + +var ModelGroupChatField_GroupWxId = core.StructField{ + StructFieldName: "GroupWxId", + DbFieldName: "group_wx_id", + Comment: "群id", +} + +var ModelGroupChatField_HasBeenWatch = core.StructField{ + StructFieldName: "HasBeenWatch", + DbFieldName: "has_been_watch", + Comment: "以前有关注过", +} + +var ModelGroupChatField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID", +} + +var ModelGroupChatField_InContact = core.StructField{ + StructFieldName: "InContact", + DbFieldName: "in_contact", + Comment: "是否在通讯录中", +} + +var ModelGroupChatField_IsDefaultGroupName = core.StructField{ + StructFieldName: "IsDefaultGroupName", + DbFieldName: "is_default_group_name", + Comment: "是否是默认的群名称", +} + +var ModelGroupChatField_IsWatch = core.StructField{ + StructFieldName: "IsWatch", + DbFieldName: "is_watch", + Comment: "是否关注群", +} + +var ModelGroupChatField_LastSyncAt = core.StructField{ + StructFieldName: "LastSyncAt", + DbFieldName: "last_sync_at", + Comment: "最后更新群信息时间 【通过这里 指定规则 去拉群基本信息】", +} + +var ModelGroupChatField_LastSyncMemberAt = core.StructField{ + StructFieldName: "LastSyncMemberAt", + DbFieldName: "last_sync_member_at", + Comment: "最后更新群成员时间 【通过这里 指定规则 去拉群成员信息】", +} + +var ModelGroupChatField_MemberCount = core.StructField{ + StructFieldName: "MemberCount", + DbFieldName: "member_count", + Comment: "群成员数量", +} + +var ModelGroupChatField_Notice = core.StructField{ + StructFieldName: "Notice", + DbFieldName: "notice", + Comment: "群公告", +} + +var ModelGroupChatField_OwnerName = core.StructField{ + StructFieldName: "OwnerName", + DbFieldName: "owner_name", + Comment: "群主名称", +} + +var ModelGroupChatField_OwnerWxId = core.StructField{ + StructFieldName: "OwnerWxId", + DbFieldName: "owner_wx_id", + Comment: "群主id", +} + +var ModelGroupChatField_QrcodeUpdatedAt = core.StructField{ + StructFieldName: "QrcodeUpdatedAt", + DbFieldName: "qrcode_updated_at", + Comment: "群聊二维码更新时间", +} + +var ModelGroupChatField_QrcodeUrl = core.StructField{ + StructFieldName: "QrcodeUrl", + DbFieldName: "qrcode_url", + Comment: "群聊二维码", +} + +var ModelGroupChatField_RobotWxId = core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", +} + +var ModelGroupChatField_UpdatedAt = core.StructField{ + StructFieldName: "UpdatedAt", + DbFieldName: "updated_at", + Comment: "更新时间", +} + +var ModelGroupChatMemberField_AdminType = core.StructField{ + StructFieldName: "AdminType", + DbFieldName: "admin_type", + Comment: "权限类型 群主 管理员 普通成员", +} + +var ModelGroupChatMemberField_CreatedAt = core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "创建时间", +} + +var ModelGroupChatMemberField_DeletedAt = core.StructField{ + StructFieldName: "DeletedAt", + DbFieldName: "deleted_at", + Comment: "删除时间 这个表一般直接硬删除", +} + +var ModelGroupChatMemberField_GroupChatId = core.StructField{ + StructFieldName: "GroupChatId", + DbFieldName: "group_chat_id", + Comment: "群 ModelGroupChat 的ID", +} + +var ModelGroupChatMemberField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "id", +} + +var ModelGroupChatMemberField_IsRobot = core.StructField{ + StructFieldName: "IsRobot", + DbFieldName: "is_robot", + Comment: "是否是机器人", +} + +var ModelGroupChatMemberField_LastSyncAt = core.StructField{ + StructFieldName: "LastSyncAt", + DbFieldName: "last_sync_at", + Comment: "该群该成员 最后更新时间", +} + +var ModelGroupChatMemberField_MemberAlias = core.StructField{ + StructFieldName: "MemberAlias", + DbFieldName: "member_alias", + Comment: "群昵称", +} + +var ModelGroupChatMemberField_MemberAvatar = core.StructField{ + StructFieldName: "MemberAvatar", + DbFieldName: "member_avatar", + Comment: "群成员头像", +} + +var ModelGroupChatMemberField_MemberName = core.StructField{ + StructFieldName: "MemberName", + DbFieldName: "member_name", + Comment: "群成员名称", +} + +var ModelGroupChatMemberField_MemberSex = core.StructField{ + StructFieldName: "MemberSex", + DbFieldName: "member_sex", + Comment: "性别", +} + +var ModelGroupChatMemberField_MemberWxId = core.StructField{ + StructFieldName: "MemberWxId", + DbFieldName: "member_wx_id", + Comment: "群成员微信id", +} + +var ModelGroupChatMemberField_UpdatedAt = core.StructField{ + StructFieldName: "UpdatedAt", + DbFieldName: "updated_at", + Comment: "更新时间", +} + +var ModelRobotField_AbilityLimit = core.StructField{ + StructFieldName: "AbilityLimit", + DbFieldName: "ability_limit", + Comment: "机器人是否功能受限", +} + +var ModelRobotField_AliasName = core.StructField{ + StructFieldName: "AliasName", + DbFieldName: "alias_name", + Comment: "微信号", +} + +var ModelRobotField_AndroidStatus = core.StructField{ + StructFieldName: "AndroidStatus", + DbFieldName: "android_status", + Comment: "机器人Android是否在线 10在线 11离线", +} + +var ModelRobotField_AndroidWechatVersion = core.StructField{ + StructFieldName: "AndroidWechatVersion", + DbFieldName: "android_wechat_version", + Comment: "微信版本", +} + +var ModelRobotField_AutoAddFriend = core.StructField{ + StructFieldName: "AutoAddFriend", + DbFieldName: "auto_add_friend", + Comment: "机器人是否自动通过好友请求 0否 1是", +} + +var ModelRobotField_AvatarUrl = core.StructField{ + StructFieldName: "AvatarUrl", + DbFieldName: "avatar_url", + Comment: "机器人头像", +} + +var ModelRobotField_City = core.StructField{ + StructFieldName: "City", + DbFieldName: "city", + Comment: "城市", +} + +var ModelRobotField_Country = core.StructField{ + StructFieldName: "Country", + DbFieldName: "country", + Comment: "国家", +} + +var ModelRobotField_CoverUrl = core.StructField{ + StructFieldName: "CoverUrl", + DbFieldName: "cover_url", + Comment: "朋友圈封面url", +} + +var ModelRobotField_CreateTime = core.StructField{ + StructFieldName: "CreateTime", + DbFieldName: "create_time", + Comment: "创建时间", +} + +var ModelRobotField_CrmAutoAddFriend = core.StructField{ + StructFieldName: "CrmAutoAddFriend", + DbFieldName: "crm_auto_add_friend", + Comment: "crm系统自动通过好友 1自动通过 0不自动通过", +} + +var ModelRobotField_CrmShopId = core.StructField{ + StructFieldName: "CrmShopId", + DbFieldName: "crm_shop_id", + Comment: "机器人所属商户id", +} + +var ModelRobotField_DeleteTime = core.StructField{ + StructFieldName: "DeleteTime", + DbFieldName: "delete_time", + Comment: "删除时间", +} + +var ModelRobotField_GreetId = core.StructField{ + StructFieldName: "GreetId", + DbFieldName: "greet_id", + Comment: "打招呼模板id", +} + +var ModelRobotField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID wxid md5", +} + +var ModelRobotField_InitFriend = core.StructField{ + StructFieldName: "InitFriend", + DbFieldName: "init_friend", + Comment: "机器人初始好友人数", +} + +var ModelRobotField_LastAndroidLoginAt = core.StructField{ + StructFieldName: "LastAndroidLoginAt", + DbFieldName: "last_android_login_at", + Comment: "最近安卓登录时间", +} + +var ModelRobotField_LastAndroidLogoutAt = core.StructField{ + StructFieldName: "LastAndroidLogoutAt", + DbFieldName: "last_android_logout_at", + Comment: "最近安卓登出时间", +} + +var ModelRobotField_LastCity = core.StructField{ + StructFieldName: "LastCity", + DbFieldName: "last_city", + Comment: "最后登录的城市名称", +} + +var ModelRobotField_LastLogOutTime = core.StructField{ + StructFieldName: "LastLogOutTime", + DbFieldName: "last_log_out_time", + Comment: "最后登出时间", +} + +var ModelRobotField_LastLoginTime = core.StructField{ + StructFieldName: "LastLoginTime", + DbFieldName: "last_login_time", + Comment: "最后登录时间", +} + +var ModelRobotField_LastPcLoginAt = core.StructField{ + StructFieldName: "LastPcLoginAt", + DbFieldName: "last_pc_login_at", + Comment: "最近PC登录时间", +} + +var ModelRobotField_LastPcLogoutAt = core.StructField{ + StructFieldName: "LastPcLogoutAt", + DbFieldName: "last_pc_logout_at", + Comment: "最近PC登出时间", +} + +var ModelRobotField_LastRegionCode = core.StructField{ + StructFieldName: "LastRegionCode", + DbFieldName: "last_region_code", + Comment: "最后登录的扫码设备的地区编码", +} + +var ModelRobotField_LastRequireAddFriendTime = core.StructField{ + StructFieldName: "LastRequireAddFriendTime", + DbFieldName: "last_require_add_friend_time", + Comment: "上一次请求添加好友的时间", +} + +var ModelRobotField_Limited = core.StructField{ + StructFieldName: "Limited", + DbFieldName: "limited", + Comment: "机器人是否被封号 0未封号 1已封号", +} + +var ModelRobotField_LogAndOutTime = core.StructField{ + StructFieldName: "LogAndOutTime", + DbFieldName: "log_and_out_time", + Comment: "登入或者登出都要记录一下", +} + +var ModelRobotField_Mobile = core.StructField{ + StructFieldName: "Mobile", + DbFieldName: "mobile", + Comment: "手机号码", +} + +var ModelRobotField_MomentPrivacyType = core.StructField{ + StructFieldName: "MomentPrivacyType", + DbFieldName: "moment_privacy_type", + Comment: "朋友圈隐私选项类型", +} + +var ModelRobotField_NickName = core.StructField{ + StructFieldName: "NickName", + DbFieldName: "nick_name", + Comment: "机器人暱称", +} + +var ModelRobotField_NowFriend = core.StructField{ + StructFieldName: "NowFriend", + DbFieldName: "now_friend", + Comment: "机器人当前好友数量", +} + +var ModelRobotField_OpenForStranger = core.StructField{ + StructFieldName: "OpenForStranger", + DbFieldName: "open_for_stranger", + Comment: "是否允许陌生人查看十条朋友圈", +} + +var ModelRobotField_Province = core.StructField{ + StructFieldName: "Province", + DbFieldName: "province", + Comment: "省份", +} + +var ModelRobotField_Qrcode = core.StructField{ + StructFieldName: "Qrcode", + DbFieldName: "qrcode", + Comment: "机器人二维码", +} + +var ModelRobotField_RiskControlGroup = core.StructField{ + StructFieldName: "RiskControlGroup", + DbFieldName: "risk_control_group", + Comment: "风控分组", +} + +var ModelRobotField_RiskControlTask = core.StructField{ + StructFieldName: "RiskControlTask", + DbFieldName: "risk_control_task", + Comment: "风控任务 0是全部,1是回复,2是发消息,3是看朋友圈,4是发朋友圈,5是点赞,6是评论 7是群聊 可组合,如:1,2,3", +} + +var ModelRobotField_Sex = core.StructField{ + StructFieldName: "Sex", + DbFieldName: "sex", + Comment: "性别 0 未知 1 男生 2 女生", +} + +var ModelRobotField_Signature = core.StructField{ + StructFieldName: "Signature", + DbFieldName: "signature", + Comment: "个性签名", +} + +var ModelRobotField_Status = core.StructField{ + StructFieldName: "Status", + DbFieldName: "status", + Comment: "机器人PC是否在线 10在线 11离线 (兼容之前的pc登录流程和其他接口,这个登录状态不变,补多一个字段代表安卓登录状态)", +} + +var ModelRobotField_TodayRequireTime = core.StructField{ + StructFieldName: "TodayRequireTime", + DbFieldName: "today_require_time", + Comment: "当天请求次数", +} + +var ModelRobotField_UpdateTime = core.StructField{ + StructFieldName: "UpdateTime", + DbFieldName: "update_time", + Comment: "更新时间", +} + +var ModelRobotField_UserId = core.StructField{ + StructFieldName: "UserId", + DbFieldName: "user_id", + Comment: "机器人所属用户id", +} + +var ModelRobotField_WechatAlias = core.StructField{ + StructFieldName: "WechatAlias", + DbFieldName: "wechat_alias", + Comment: "微信ID (用户自己定义的微信号)", +} + +var ModelRobotField_WechatId = core.StructField{ + StructFieldName: "WechatId", + DbFieldName: "wechat_id", + Comment: "微信唯一ID (wxidxxxxxx)", +} + +var ModelRobotFriendField_AddAt = core.StructField{ + StructFieldName: "AddAt", + DbFieldName: "add_at", + Comment: "添加好友时间只有主动添加好友才有", +} + +var ModelRobotFriendField_CreateTime = core.StructField{ + StructFieldName: "CreateTime", + DbFieldName: "create_time", + Comment: "创建时间:入库时间", +} + +var ModelRobotFriendField_CrmPhone = core.StructField{ + StructFieldName: "CrmPhone", + DbFieldName: "crm_phone", + Comment: "CRM自己设置的好友手机号,不同于微信手机号", +} + +var ModelRobotFriendField_DeleteTime = core.StructField{ + StructFieldName: "DeleteTime", + DbFieldName: "delete_time", + Comment: "删除好友的时间", +} + +var ModelRobotFriendField_Deleted = core.StructField{ + StructFieldName: "Deleted", + DbFieldName: "deleted", + Comment: "是否被删除 0双方未删除 1被好友删除 2删除了好友 3互相删除", +} + +var ModelRobotFriendField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID 机器人id+朋友id md5", +} + +var ModelRobotFriendField_OfflineAdd = core.StructField{ + StructFieldName: "OfflineAdd", + DbFieldName: "offline_add", + Comment: "是否为离线添加", +} + +var ModelRobotFriendField_Pinyin = core.StructField{ + StructFieldName: "Pinyin", + DbFieldName: "pinyin", + Comment: "用户备注或者暱称的拼音", +} + +var ModelRobotFriendField_PinyinHead = core.StructField{ + StructFieldName: "PinyinHead", + DbFieldName: "pinyin_head", + Comment: "拼音首字母", +} + +var ModelRobotFriendField_RemarkName = core.StructField{ + StructFieldName: "RemarkName", + DbFieldName: "remark_name", + Comment: "微信好友备注名称", +} + +var ModelRobotFriendField_RobotWechatId = core.StructField{ + StructFieldName: "RobotWechatId", + DbFieldName: "robot_wechat_id", + Comment: "机器人编号:微信ID", +} + +var ModelRobotFriendField_UpdateTime = core.StructField{ + StructFieldName: "UpdateTime", + DbFieldName: "update_time", + Comment: "更新时间", +} + +var ModelRobotFriendField_UserWechatId = core.StructField{ + StructFieldName: "UserWechatId", + DbFieldName: "user_wechat_id", + Comment: "用户微信ID,", +} + +var ModelSchedTaskField_CreatedAt = core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "创建时间", +} + +var ModelSchedTaskField_ExpiredAt = core.StructField{ + StructFieldName: "ExpiredAt", + DbFieldName: "expired_at", + Comment: "过期时间", +} + +var ModelSchedTaskField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "任务id", +} + +var ModelSchedTaskField_ReqId = core.StructField{ + StructFieldName: "ReqId", + DbFieldName: "req_id", + Comment: "便于查询该任务 指定的id[作用:有些情况 无法直接通过id来查询该记录]", +} + +var ModelSchedTaskField_ReqJson = core.StructField{ + StructFieldName: "ReqJson", + DbFieldName: "req_json", + Comment: "请求内容", +} + +var ModelSchedTaskField_RobotWxId = core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", +} + +var ModelSchedTaskField_RspJson = core.StructField{ + StructFieldName: "RspJson", + DbFieldName: "rsp_json", + Comment: "完成后的内容 [成功或者失败的返回]", +} + +var ModelSchedTaskField_TaskState = core.StructField{ + StructFieldName: "TaskState", + DbFieldName: "task_state", + Comment: "执行状态 TaskState", +} + +var ModelSchedTaskField_TaskType = core.StructField{ + StructFieldName: "TaskType", + DbFieldName: "task_type", + Comment: "任务类型 自定义的名称 用来区别是哪个模块发起的任务", +} + +var ModelSchedTaskField_UpdatedAt = core.StructField{ + StructFieldName: "UpdatedAt", + DbFieldName: "updated_at", + Comment: "更新时间", +} + +var ModelTbGroupMsgSessionField_All = core.StructField{ + StructFieldName: "All", + DbFieldName: "all", + Comment: "消息最大游标(消息总数:只算有效的消息)", +} + +var ModelTbGroupMsgSessionField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "会话ID (md5(机器人id+好友id))", +} + +var ModelTbGroupMsgSessionField_LastFriendMsgAt = core.StructField{ + StructFieldName: "LastFriendMsgAt", + DbFieldName: "last_friend_msg_at", + Comment: "接受到最后一条好友消息时间", +} + +var ModelTbGroupMsgSessionField_LastFriendMsgId = core.StructField{ + StructFieldName: "LastFriendMsgId", + DbFieldName: "last_friend_msg_id", + Comment: "接收的最后一条好友消息id", +} + +var ModelTbGroupMsgSessionField_LastMemberWxId = core.StructField{ + StructFieldName: "LastMemberWxId", + DbFieldName: "last_member_wx_id", + Comment: "最后发送消息的群成员id", +} + +var ModelTbGroupMsgSessionField_LastMsgAt = core.StructField{ + StructFieldName: "LastMsgAt", + DbFieldName: "last_msg_at", + Comment: "最后一条消息时间", +} + +var ModelTbGroupMsgSessionField_LastMsgId = core.StructField{ + StructFieldName: "LastMsgId", + DbFieldName: "last_msg_id", + Comment: "最后一条消息id", +} + +var ModelTbGroupMsgSessionField_Read = core.StructField{ + StructFieldName: "Read", + DbFieldName: "read", + Comment: "已读游标", +} + +var ModelTbGroupMsgSessionField_RobotWxId = core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", +} + +var ModelTbGroupMsgSessionField_Unread = core.StructField{ + StructFieldName: "Unread", + DbFieldName: "unread", + Comment: "未读消息游标", +} + +var ModelTbGroupMsgSessionField_UserWxId = core.StructField{ + StructFieldName: "UserWxId", + DbFieldName: "user_wx_id", + Comment: "群微信id", +} + +var ModelTbPrivateMsgSessionField_All = core.StructField{ + StructFieldName: "All", + DbFieldName: "all", + Comment: "消息最大游标(消息总数:只算有效的消息)", +} + +var ModelTbPrivateMsgSessionField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "会话ID (md5(机器人id+好友id))", +} + +var ModelTbPrivateMsgSessionField_LastFriendMsgAt = core.StructField{ + StructFieldName: "LastFriendMsgAt", + DbFieldName: "last_friend_msg_at", + Comment: "接受到最后一条好友消息时间", +} + +var ModelTbPrivateMsgSessionField_LastFriendMsgId = core.StructField{ + StructFieldName: "LastFriendMsgId", + DbFieldName: "last_friend_msg_id", + Comment: "接收的最后一条好友消息id", +} + +var ModelTbPrivateMsgSessionField_LastMsgAt = core.StructField{ + StructFieldName: "LastMsgAt", + DbFieldName: "last_msg_at", + Comment: "最后一条消息时间", +} + +var ModelTbPrivateMsgSessionField_LastMsgId = core.StructField{ + StructFieldName: "LastMsgId", + DbFieldName: "last_msg_id", + Comment: "最后一条消息id", +} + +var ModelTbPrivateMsgSessionField_Read = core.StructField{ + StructFieldName: "Read", + DbFieldName: "read", + Comment: "已读游标", +} + +var ModelTbPrivateMsgSessionField_RobotWxId = core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", +} + +var ModelTbPrivateMsgSessionField_Unread = core.StructField{ + StructFieldName: "Unread", + DbFieldName: "unread", + Comment: "未读消息游标", +} + +var ModelTbPrivateMsgSessionField_UserWxId = core.StructField{ + StructFieldName: "UserWxId", + DbFieldName: "user_wx_id", + Comment: "好友微信id", +} + +var ModelTbRobotGroupMsgField_BindId = core.StructField{ + StructFieldName: "BindId", + DbFieldName: "bind_id", + Comment: "前端消息id", +} + +var ModelTbRobotGroupMsgField_CallBackAt = core.StructField{ + StructFieldName: "CallBackAt", + DbFieldName: "call_back_at", + Comment: "消息返回时间", +} + +var ModelTbRobotGroupMsgField_ContentData = core.StructField{ + StructFieldName: "ContentData", + DbFieldName: "content_data", + Comment: "消息内容", +} + +var ModelTbRobotGroupMsgField_ContentRead = core.StructField{ + StructFieldName: "ContentRead", + DbFieldName: "content_read", + Comment: "是否内容被浏览(像语音之类的,需要浏览)", +} + +var ModelTbRobotGroupMsgField_CreatedAt = core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "创建时间", +} + +var ModelTbRobotGroupMsgField_Cursor = core.StructField{ + StructFieldName: "Cursor", + DbFieldName: "cursor", + Comment: "消息游标(对应session的all)", +} + +var ModelTbRobotGroupMsgField_Direct = core.StructField{ + StructFieldName: "Direct", + DbFieldName: "direct", + Comment: "用于区分机器人是接收方还是发送方。1:机器人接收;2:机器人发送", +} + +var ModelTbRobotGroupMsgField_ExpireAt = core.StructField{ + StructFieldName: "ExpireAt", + DbFieldName: "expire_at", + Comment: "失效时间(用于消息的失效)", +} + +var ModelTbRobotGroupMsgField_FailReason = core.StructField{ + StructFieldName: "FailReason", + DbFieldName: "fail_reason", + Comment: "失败原因", +} + +var ModelTbRobotGroupMsgField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID", +} + +var ModelTbRobotGroupMsgField_MsgId = core.StructField{ + StructFieldName: "MsgId", + DbFieldName: "msg_id", + Comment: "服务端自己生成一个消息id,来对应客户端的发送结果id", +} + +var ModelTbRobotGroupMsgField_MsgType = core.StructField{ + StructFieldName: "MsgType", + DbFieldName: "msg_type", + Comment: "消息类型", +} + +var ModelTbRobotGroupMsgField_RobotWxId = core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", +} + +var ModelTbRobotGroupMsgField_SendAt = core.StructField{ + StructFieldName: "SendAt", + DbFieldName: "send_at", + Comment: "发送时间(消息实际生效时间)", +} + +var ModelTbRobotGroupMsgField_SendErrorCode = core.StructField{ + StructFieldName: "SendErrorCode", + DbFieldName: "send_error_code", + Comment: "发送错误码:用户告诉对应的是什么错误:-1 通用错误码; -2 被拉黑; -3", +} + +var ModelTbRobotGroupMsgField_SendStatus = core.StructField{ + StructFieldName: "SendStatus", + DbFieldName: "send_status", + Comment: "发送状态:0:发送中;1:发送请求成功;2:发送请求失败;3:发送成功;4:发送失败;仅机器人发送。接收到用户消息的默认3", +} + +var ModelTbRobotGroupMsgField_SenderWxId = core.StructField{ + StructFieldName: "SenderWxId", + DbFieldName: "sender_wx_id", + Comment: "发送者id", +} + +var ModelTbRobotGroupMsgField_UpdatedAt = core.StructField{ + StructFieldName: "UpdatedAt", + DbFieldName: "updated_at", + Comment: "更新时间", +} + +var ModelTbRobotGroupMsgField_UserWxId = core.StructField{ + StructFieldName: "UserWxId", + DbFieldName: "user_wx_id", + Comment: "群聊id", +} + +var ModelTbRobotPrivateMsgField_BindId = core.StructField{ + StructFieldName: "BindId", + DbFieldName: "bind_id", + Comment: "前端消息id", +} + +var ModelTbRobotPrivateMsgField_CallBackAt = core.StructField{ + StructFieldName: "CallBackAt", + DbFieldName: "call_back_at", + Comment: "消息返回时间", +} + +var ModelTbRobotPrivateMsgField_ContentData = core.StructField{ + StructFieldName: "ContentData", + DbFieldName: "content_data", + Comment: "消息内容", +} + +var ModelTbRobotPrivateMsgField_ContentRead = core.StructField{ + StructFieldName: "ContentRead", + DbFieldName: "content_read", + Comment: "是否内容被浏览(像语音之类的,需要浏览)", +} + +var ModelTbRobotPrivateMsgField_CreatedAt = core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "创建时间", +} + +var ModelTbRobotPrivateMsgField_Cursor = core.StructField{ + StructFieldName: "Cursor", + DbFieldName: "cursor", + Comment: "消息游标(对应session的all)", +} + +var ModelTbRobotPrivateMsgField_Direct = core.StructField{ + StructFieldName: "Direct", + DbFieldName: "direct", + Comment: "用于区分机器人是接收方还是发送方。1:机器人接收;2:机器人发送", +} + +var ModelTbRobotPrivateMsgField_ExpireAt = core.StructField{ + StructFieldName: "ExpireAt", + DbFieldName: "expire_at", + Comment: "失效时间(用于消息的失效)", +} + +var ModelTbRobotPrivateMsgField_FailReason = core.StructField{ + StructFieldName: "FailReason", + DbFieldName: "fail_reason", + Comment: "失败原因", +} + +var ModelTbRobotPrivateMsgField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID", +} + +var ModelTbRobotPrivateMsgField_MsgId = core.StructField{ + StructFieldName: "MsgId", + DbFieldName: "msg_id", + Comment: "服务端自己生成一个消息id,来对应客户端的发送结果id", +} + +var ModelTbRobotPrivateMsgField_MsgType = core.StructField{ + StructFieldName: "MsgType", + DbFieldName: "msg_type", + Comment: "消息类型", +} + +var ModelTbRobotPrivateMsgField_RobotWxId = core.StructField{ + StructFieldName: "RobotWxId", + DbFieldName: "robot_wx_id", + Comment: "机器人id", +} + +var ModelTbRobotPrivateMsgField_SendAt = core.StructField{ + StructFieldName: "SendAt", + DbFieldName: "send_at", + Comment: "发送时间(消息实际生效时间)", +} + +var ModelTbRobotPrivateMsgField_SendErrorCode = core.StructField{ + StructFieldName: "SendErrorCode", + DbFieldName: "send_error_code", + Comment: "发送错误码:用户告诉对应的是什么错误:-1 通用错误码; -2 被拉黑; -3", +} + +var ModelTbRobotPrivateMsgField_SendStatus = core.StructField{ + StructFieldName: "SendStatus", + DbFieldName: "send_status", + Comment: "发送状态:0:发送中;1:发送请求成功;2:发送请求失败;3:发送成功;4:发送失败;仅机器人发送。接收到用户消息的默认3", +} + +var ModelTbRobotPrivateMsgField_UpdatedAt = core.StructField{ + StructFieldName: "UpdatedAt", + DbFieldName: "updated_at", + Comment: "更新时间", +} + +var ModelTbRobotPrivateMsgField_UserWxId = core.StructField{ + StructFieldName: "UserWxId", + DbFieldName: "user_wx_id", + Comment: "好友id", +} + +var ModelWsConnectRecordField_BindId = core.StructField{ + StructFieldName: "BindId", + DbFieldName: "bind_id", + Comment: "该ws绑定的id", +} + +var ModelWsConnectRecordField_CreatedAt = core.StructField{ + StructFieldName: "CreatedAt", + DbFieldName: "created_at", + Comment: "记录创建时间", +} + +var ModelWsConnectRecordField_ExpiredAt = core.StructField{ + StructFieldName: "ExpiredAt", + DbFieldName: "expired_at", + Comment: "过期时间", +} + +var ModelWsConnectRecordField_Id = core.StructField{ + StructFieldName: "Id", + DbFieldName: "_id", + Comment: "主键ID wxid md5", +} + +var ModelWsConnectRecordField_LoginAt = core.StructField{ + StructFieldName: "LoginAt", + DbFieldName: "login_at", + Comment: "登录时间", +} + +var ModelWsConnectRecordField_LogoutAt = core.StructField{ + StructFieldName: "LogoutAt", + DbFieldName: "logout_at", + Comment: "登出时间", +} + +var ModelWsConnectRecordField_UserId = core.StructField{ + StructFieldName: "UserId", + DbFieldName: "user_id", + Comment: "机器人所属用户id", +} diff --git a/breaker.go b/breaker.go new file mode 100644 index 0000000..b0d633f --- /dev/null +++ b/breaker.go @@ -0,0 +1,21 @@ +package mdbc + +// breakerDo db熔断相关的告警处理 +// 熔断应该记录执行时间 和单位时间间隔内执行次数 +// 这里执行时间已经记录 单位时间间隔内执行次数暂未统计 +type breakerDo interface { + doReporter() string // 告警 返回告警内容 +} + +// BreakerReporter 告警器 +type BreakerReporter struct { + reportTitle string + reportMsg string + reportErrorWrap error + breakerDo +} + +// Report 告警,向Slack发出告警信息 +func (b *BreakerReporter) Report() { + +} diff --git a/builder/index.go b/builder/index.go new file mode 100644 index 0000000..2c423e1 --- /dev/null +++ b/builder/index.go @@ -0,0 +1,12 @@ +package builder + +// Add 添加一个索引项 +//func (ib *IndexBuilder) Add(key string, sort mdbc.KeySort) *IndexBuilder { +// ib.d = append(ib.d, bson.E{Key: key, Value: sort}) +// return ib +//} + +// Build 构建完整索引 +//func (ib *IndexBuilder) Build() bson.D { +// return ib.d +//} diff --git a/builder/pipeline.go b/builder/pipeline.go new file mode 100644 index 0000000..583a498 --- /dev/null +++ b/builder/pipeline.go @@ -0,0 +1,63 @@ +package builder + +import ( + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +// AddFields do something +func (b *PipelineBuilder) AddFields(filter bson.M) *PipelineBuilder { + b.pipeline = append(b.pipeline, bson.D{{"$addFields", filter}}) + return b +} + +// Match 过滤匹配 +func (b *PipelineBuilder) Match(filter bson.M) *PipelineBuilder { + b.pipeline = append(b.pipeline, bson.D{{"$match", filter}}) + return b +} + +// Sort 排序 +func (b *PipelineBuilder) Sort(filter bson.M) *PipelineBuilder { + b.pipeline = append(b.pipeline, bson.D{{"$sort", filter}}) + return b +} + +// Group 分组 +func (b *PipelineBuilder) Group(filter bson.M) *PipelineBuilder { + b.pipeline = append(b.pipeline, bson.D{{"$group", filter}}) + return b +} + +// Project do something +func (b *PipelineBuilder) Project(filter bson.M) *PipelineBuilder { + b.pipeline = append(b.pipeline, bson.D{{"$project", filter}}) + return b +} + +// Limit do something +func (b *PipelineBuilder) Limit(limit int) *PipelineBuilder { + b.pipeline = append(b.pipeline, bson.D{{"$limit", limit}}) + return b +} + +// Skip do something +func (b *PipelineBuilder) Skip(limit int) *PipelineBuilder { + b.pipeline = append(b.pipeline, bson.D{{"$skip", limit}}) + return b +} + +// Count 统计 +func (b *PipelineBuilder) Count(fieldName string) *PipelineBuilder { + b.pipeline = append(b.pipeline, bson.D{{"$count", fieldName}}) + return b +} + +func (b *PipelineBuilder) Other(d ...bson.D) *PipelineBuilder { + b.pipeline = append(b.pipeline, d...) + return b +} + +func (b *PipelineBuilder) Build() mongo.Pipeline { + return b.pipeline +} diff --git a/builder/query.go b/builder/query.go new file mode 100644 index 0000000..4813a9d --- /dev/null +++ b/builder/query.go @@ -0,0 +1,19 @@ +package builder + +import "go.mongodb.org/mongo-driver/bson" + +// Add 添加一个默认的隐式and操作 +func (qb *QueryBuilder) Add(opName string, value interface{}) *QueryBuilder { + qb.q[opName] = value + return qb +} + +// Or 添加一个 or 操作 +func (qb *QueryBuilder) Or(filters ...bson.M) *QueryBuilder { + if qb.q["$or"] == nil { + + return qb + } + + return qb +} diff --git a/builder/type.go b/builder/type.go new file mode 100644 index 0000000..6729b3d --- /dev/null +++ b/builder/type.go @@ -0,0 +1,40 @@ +// Package builder 用来快速构建 aggregate 查询 +package builder + +import ( + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +type Builder struct{} + +type PipelineBuilder struct { + pipeline mongo.Pipeline +} + +type IndexBuilder struct { + d bson.D +} + +type QueryBuilder struct { + q bson.M +} + +func NewBuilder() *Builder { + return &Builder{} +} + +// Pipeline 构建器 +func (b *Builder) Pipeline() *PipelineBuilder { + return &PipelineBuilder{pipeline: mongo.Pipeline{}} +} + +// Index key构建器 +func (b *Builder) Index() *IndexBuilder { + return &IndexBuilder{} +} + +// Query 查询构建器 +func (b *Builder) Query() *QueryBuilder { + return &QueryBuilder{} +} diff --git a/bulk_write_scope.go b/bulk_write_scope.go new file mode 100644 index 0000000..1bbcd4c --- /dev/null +++ b/bulk_write_scope.go @@ -0,0 +1,218 @@ +package mdbc + +import ( + "context" + "errors" + "fmt" + "time" + + jsoniter "github.com/json-iterator/go" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type BulkWriteScope struct { + scope *Scope + cw *ctxWrap + err error + chunkSize uint32 + opts *options.BulkWriteOptions + models []mongo.WriteModel + result *mongo.BulkWriteResult + chunkFunc func([]mongo.WriteModel) error +} + +// doString debug string +func (bws *BulkWriteScope) doString() string { + var data []interface{} + builder := RegisterTimestampCodec(nil).Build() + for _, v := range bws.models { + var body interface{} + rawv := Struct2MapOmitEmpty(v) + b, _ := bson.MarshalExtJSONWithRegistry(builder, rawv, true, true) + _ = jsoniter.Unmarshal(b, &body) + data = append(data, body) + } + b, _ := jsoniter.Marshal(data) + return fmt.Sprintf("bulkWrite(%s)", string(b)) +} + +// debug debug +func (bws *BulkWriteScope) debug() { + if !bws.scope.debug { + return + } + + debugger := &Debugger{ + collection: bws.scope.tableName, + execT: bws.scope.execT, + action: bws, + } + debugger.String() +} + +// SetContext 设置上下文 +func (bws *BulkWriteScope) SetContext(ctx context.Context) *BulkWriteScope { + if bws.cw == nil { + bws.cw = &ctxWrap{} + } + bws.cw.ctx = ctx + return bws +} + +func (bws BulkWriteScope) getContext() context.Context { + return bws.cw.ctx +} + +// SetBulkWriteOption 设置BulkWriteOption +func (bws *BulkWriteScope) SetBulkWriteOption(opts options.BulkWriteOptions) *BulkWriteScope { + bws.opts = &opts + return bws +} + +// SetOrdered 设置BulkWriteOptions中的Ordered +func (bws *BulkWriteScope) SetOrdered(ordered bool) *BulkWriteScope { + if bws.opts == nil { + bws.opts = new(options.BulkWriteOptions) + } + bws.opts.Ordered = &ordered + return bws +} + +// SetChunkSize 指定分块操作大小 默认不分块 当数据足够大时 可能导致deadlock问题?不确定这个问题 +func (bws *BulkWriteScope) SetChunkSize(size uint32) *BulkWriteScope { + bws.chunkSize = size + return bws +} + +// SetChunkFunc 可以在进行批量插入之前做一些事情 若错误将终止这一批数据的写入而执行下一批 +func (bws *BulkWriteScope) SetChunkFunc(f func(models []mongo.WriteModel) error) *BulkWriteScope { + bws.chunkFunc = f + return bws +} + +// SetWriteModel 设置需要操作的数据 +func (bws *BulkWriteScope) SetWriteModel(models []mongo.WriteModel) *BulkWriteScope { + bws.models = models + return bws +} + +// SetWriteModelFunc 可以定义函数来返回需要操作的数据 +func (bws *BulkWriteScope) SetWriteModelFunc(f func() []mongo.WriteModel) *BulkWriteScope { + bws.models = f() + return bws +} + +// preCheck 预检查 +func (bws *BulkWriteScope) preCheck() { + var breakerTTL time.Duration + if bws.scope.breaker == nil { + breakerTTL = defaultBreakerTime + } else if bws.scope.breaker.ttl == 0 { + breakerTTL = defaultBreakerTime + } else { + breakerTTL = bws.scope.breaker.ttl + } + if bws.cw == nil { + bws.cw = &ctxWrap{} + } + if bws.cw.ctx == nil { + bws.cw.ctx, bws.cw.cancel = context.WithTimeout(context.Background(), breakerTTL) + } +} + +func (bws *BulkWriteScope) doClear() { + if bws.cw != nil && bws.cw.cancel != nil { + bws.cw.cancel() + } + bws.scope.debug = false + bws.scope.execT = 0 +} + +func (bws *BulkWriteScope) assertErr() { + if bws.err == nil { + return + } + if errors.Is(bws.err, context.DeadlineExceeded) { + bws.err = &ErrRequestBroken + return + } + err, ok := bws.err.(mongo.CommandError) + if !ok { + return + } + if err.HasErrorMessage(context.DeadlineExceeded.Error()) { + bws.err = &ErrRequestBroken + } +} + +func (bws *BulkWriteScope) doBulkWrite() { + defer bws.assertErr() + var starTime time.Time + if bws.scope.debug { + starTime = time.Now() + } + bws.result, bws.err = db.Collection(bws.scope.tableName).BulkWrite(bws.getContext(), bws.models, bws.opts) + if bws.scope.debug { + bws.scope.execT = time.Since(starTime) + bws.debug() + } +} + +func (bws *BulkWriteScope) splitBulkWrite(arr []mongo.WriteModel, chunkSize int) [][]mongo.WriteModel { + var newArr [][]mongo.WriteModel + for i := 0; i < len(arr); i += chunkSize { + end := i + chunkSize + + if end > len(arr) { + end = len(arr) + } + + newArr = append(newArr, arr[i:end]) + } + + return newArr +} + +func (bws *BulkWriteScope) checkModel() { + if len(bws.models) == 0 { + bws.err = fmt.Errorf("models empty") + return + } + // 命令检测 +} + +// Do 执行批量操作 请确保 SetWriteModel 已被设置 否则报错 +func (bws *BulkWriteScope) Do() (*mongo.BulkWriteResult, error) { + defer bws.doClear() + bws.checkModel() + bws.preCheck() + + if bws.err != nil { + return nil, bws.err + } + + // 如果设置了chunkSize就分片插入 + if bws.chunkSize > 0 { + models := bws.splitBulkWrite(bws.models, int(bws.chunkSize)) + for _, model := range models { + bws.models = model + if bws.chunkFunc != nil { + if bws.err = bws.chunkFunc(bws.models); bws.err != nil { + continue + } + } + bws.doBulkWrite() + } + } else { + bws.doBulkWrite() + } + + if bws.err != nil { + return nil, bws.err + } + + return bws.result, nil +} diff --git a/bulk_write_scope_test.go b/bulk_write_scope_test.go new file mode 100644 index 0000000..318dab2 --- /dev/null +++ b/bulk_write_scope_test.go @@ -0,0 +1,114 @@ +package mdbc + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/google/uuid" + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +func TestBulkWriteScope(t *testing.T) { + cfg := &Config{ + URI: "mongodb://admin:admin@10.0.0.135:27017/admin", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + InitDB(client.Database("heywoods_golang_jingliao_crm_dev")) + + var m = NewModel(&ModelRobotFriend{}) + + var record ModelRobotFriend + if err := m.FindOne().SetFilter(bson.M{ + ModelRobotFriendField_Id.DbFieldName: "498b1c85be41266efb29b6a79560ec7f", + }).Get(&record); err != nil { + panic(err) + } + + record.AddAt = 12345 + record.UpdateTime = time.Now().Unix() + + var updateData = bson.M{ + "$set": &record, + "$setOnInsert": bson.M{ + "create_time": 1000000, + }, + } + b, _ := bson.MarshalExtJSON(updateData, true, true) + logrus.Infof("updateData: %+v", string(b)) + SetMapOmitInsertField(&updateData) + b, _ = json.Marshal(updateData) + logrus.Infof("updateData: %+v", string(b)) + + var opData []mongo.WriteModel + opData = append(opData, mongo.NewUpdateOneModel().SetFilter(bson.M{ + ModelRobotFriendField_Id.DbFieldName: "498b1c85be41266efb29b6a79560ec7f", + }).SetUpsert(true).SetUpdate(updateData)) + + res, err := m.SetDebug(true).BulkWrite(). + SetWriteModel(opData).Do() + + if err != nil { + panic(err) + } + fmt.Printf("res %+v\n", res) + logrus.Infof("res %+v\n", res) +} + +func TestSliceChunk(t *testing.T) { + a := []int{1, 2, 3} + chunkSize := 2 + + var b [][]int + for i := 0; i < len(a); i += chunkSize { + end := i + chunkSize + + if end > len(a) { + end = len(a) + } + + b = append(b, a[i:end]) + } + + fmt.Println(b) +} + +func TestBulkWriteScope_SetChunkSize(t *testing.T) { + cfg := &Config{ + URI: "mongodb://admin:admin@10.0.0.135:27017/admin", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + InitDB(client.Database("heywoods_golang_jingliao_crm_dev")) + + var m = NewModel(&ModelRobotFriend{}) + var opData []mongo.WriteModel + + for i := 0; i < 10; i++ { + opData = append(opData, mongo.NewInsertOneModel().SetDocument(&ModelRobotFriend{Id: uuid.NewString()})) + } + + res, err := m.SetDebug(true).BulkWrite().SetChunkSize(3).SetWriteModel(opData).Do() + + if err != nil { + panic(err) + } + + fmt.Println("res", res) +} diff --git a/codec.go b/codec.go new file mode 100644 index 0000000..96d6374 --- /dev/null +++ b/codec.go @@ -0,0 +1,67 @@ +package mdbc + +import ( + "reflect" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/bsoncodec" + "go.mongodb.org/mongo-driver/bson/bsonrw" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// 该文件帮助自定义序列化bson文档 +// RegisterTimestampCodec 注册一个针对 timestamppb.Timestamp 结构的 bson文档解析器 + +var ( + timeTimeType = reflect.TypeOf(time.Time{}) + timestampType = reflect.TypeOf(×tamppb.Timestamp{}) +) + +// TimestampCodec 对 timestamppb.Timestamp <-> time.Time 进行互向转换 +// time.Time 在bson中被转换为 Date 对象 +type TimestampCodec struct{} + +func (t *TimestampCodec) EncodeValue(encodeContext bsoncodec.EncodeContext, writer bsonrw.ValueWriter, value reflect.Value) error { + var rawv time.Time + + switch t := value.Interface().(type) { + case *timestamppb.Timestamp: + rawv = t.AsTime() + case time.Time: + rawv = t + default: + panic("TimestampCodec get type: " + reflect.TypeOf(value.Interface()).String() + ", not support") + } + + enc, err := encodeContext.LookupEncoder(timeTimeType) + if err != nil { + return err + } + return enc.EncodeValue(encodeContext, writer, reflect.ValueOf(rawv.In(time.UTC))) +} + +func (t *TimestampCodec) DecodeValue(decodeContext bsoncodec.DecodeContext, reader bsonrw.ValueReader, value reflect.Value) error { + enc, err := decodeContext.LookupDecoder(timeTimeType) + if err != nil { + return err + } + var tt time.Time + if err := enc.DecodeValue(decodeContext, reader, reflect.ValueOf(&tt).Elem()); err != nil { + return err + } + + ts := timestamppb.New(tt.In(time.UTC)) + value.Set(reflect.ValueOf(ts)) + return nil +} + +// RegisterTimestampCodec 注册一个针对 timestamppb.Timestamp 结构的 bson文档解析器 +// 将 mongodb 中 bson 字段的 Date(Go中的 time.Time ) 对象解析成 timestamppb.Timestamp +func RegisterTimestampCodec(rb *bsoncodec.RegistryBuilder) *bsoncodec.RegistryBuilder { + if rb == nil { + rb = bson.NewRegistryBuilder() + } + + return rb.RegisterCodec(timestampType, &TimestampCodec{}) +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..77b82d2 --- /dev/null +++ b/config.go @@ -0,0 +1,43 @@ +package mdbc + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson/bsoncodec" + "go.mongodb.org/mongo-driver/mongo/readpref" +) + +var std *ClientInit + +// Config MongoDB连接配置 +type Config struct { + // URI 连接DSN 格式: protocol://username:password@host:port/auth_db + URI string `yaml:"uri"` + // DBName + DBName string `yaml:"db-name"` + // MinPoolSize 连接池最小 默认1个 + MinPoolSize uint64 `yaml:"min-pool-size"` + // MaxPoolSize 连接池最大 默认32 + MaxPoolSize uint64 `yaml:"max-pool-size"` + // ConnTimeout 连接超时时间 单位秒 默认10秒 + ConnTimeout uint64 `yaml:"conn-timeout"` + // RegistryBuilder 注册bson文档的自定义解析器 详见当前目录 codec.go 其中定义了一系列的bson文档解析器 + RegistryBuilder *bsoncodec.RegistryBuilder + // ReadPreference 读配置 + ReadPreference *readpref.ReadPref +} + +func (c *Config) Init(ctx context.Context) error { + var err error + c.RegistryBuilder = RegisterTimestampCodec(nil) + std, err = ConnInit(c) + if err != nil { + return err + } + + return nil +} + +func GetClient() *ClientInit { + return std +} diff --git a/convert.go b/convert.go new file mode 100644 index 0000000..316384a --- /dev/null +++ b/convert.go @@ -0,0 +1,241 @@ +package mdbc + +import ( + "fmt" + "reflect" + "strings" +) + +// 这里定义了一个特殊功能 格式转换 + +// Struct2MapWithBsonTag 结构体转map 用 bson字段名 做Key +// 注意 obj非struct结构将PANIC +func Struct2MapWithBsonTag(obj interface{}) map[string]interface{} { + vo := reflect.ValueOf(obj) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() != reflect.Struct { + panic("object type not struct") + } + var data = make(map[string]interface{}) + for i := 0; i < vo.NumField(); i++ { + vf := vo.Field(i) + key := vo.Type().Field(i).Tag.Get("bson") + if key == "" { + key = vo.Type().Field(i).Name + } + if vf.CanSet() { + data[key] = vf.Interface() + } + } + return data +} + +// Struct2MapWithJsonTag 结构体转map 用 json字段名 做Key +// 注意 obj非struct结构将PANIC +func Struct2MapWithJsonTag(obj interface{}) map[string]interface{} { + vo := reflect.ValueOf(obj) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() != reflect.Struct { + panic("object type not struct") + } + var data = make(map[string]interface{}) + for i := 0; i < vo.NumField(); i++ { + vf := vo.Field(i) + key := vo.Type().Field(i).Tag.Get("json") + if key == "" { + key = vo.Type().Field(i).Name + } + // 过滤掉 omitempty 选项 + if strings.Contains(key, ",omitempty") { + key = strings.Replace(key, ",omitempty", "", 1) + } + if vf.CanSet() { + data[key] = vf.Interface() + } + } + return data +} + +// Struct2MapOmitEmpty 结构体转map并忽略空字段 用 字段名 做Key +// 注意 只忽略顶层字段 obj非struct结构将PANIC +func Struct2MapOmitEmpty(obj interface{}) map[string]interface{} { + vo := reflect.ValueOf(obj) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() != reflect.Struct { + panic("object type not struct") + } + var data = make(map[string]interface{}) + for i := 0; i < vo.NumField(); i++ { + vf := vo.Field(i) + if !vf.IsZero() && vf.CanSet() { + data[vo.Type().Field(i).Name] = vf.Interface() + } + } + return data +} + +// Struct2MapOmitEmptyWithBsonTag 结构体转map并忽略空字段 按照 bson 标签做key +// obj 需要是一个指针 +// 注意 只忽略顶层字段 obj非struct结构将PANIC +func Struct2MapOmitEmptyWithBsonTag(obj interface{}) map[string]interface{} { + vo := reflect.ValueOf(obj) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() != reflect.Struct { + panic("object type not struct") + } + var data = make(map[string]interface{}) + for i := 0; i < vo.NumField(); i++ { + vf := vo.Field(i) + key := vo.Type().Field(i).Tag.Get("bson") + if key == "" { + key = vo.Type().Field(i).Name + } + if !vf.IsZero() && vf.CanSet() { + data[key] = vf.Interface() + } + } + return data +} + +// Struct2MapOmitEmptyWithJsonTag 结构体转map并忽略空字段 按照 json 标签做key +// 注意 只忽略顶层字段 obj非struct结构将PANIC +func Struct2MapOmitEmptyWithJsonTag(obj interface{}) map[string]interface{} { + vo := reflect.ValueOf(obj) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() != reflect.Struct { + panic("object type not struct") + } + var data = make(map[string]interface{}) + for i := 0; i < vo.NumField(); i++ { + vf := vo.Field(i) + key := vo.Type().Field(i).Tag.Get("json") + if key == "" { + key = vo.Type().Field(i).Name + } + // 过滤掉 omitempty 选项 + if strings.Contains(key, ",omitempty") { + key = strings.Replace(key, ",omitempty", "", 1) + } + if !vf.IsZero() && vf.CanSet() { + data[key] = vf.Interface() + } + } + return data +} + +// SliceStruct2MapOmitEmpty 结构体数组转map数组并忽略空字段 +// 注意 子元素非struct将被忽略 +func SliceStruct2MapOmitEmpty(obj interface{}) interface{} { + vo := reflect.ValueOf(obj) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() != reflect.Slice && vo.Kind() != reflect.Array { + panic("object type not slice") + } + var data []map[string]interface{} + + for i := 0; i < vo.Len(); i++ { + node := vo.Index(i) + if node.Kind() == reflect.Ptr { + node = node.Elem() + } + if node.Kind() != reflect.Struct && node.Kind() != reflect.Interface { + continue + } + fn := Struct2MapOmitEmpty(node.Interface()) + data = append(data, fn) + } + return data +} + +// SetMapOmitInsertField 对于一个 update-bson-map 忽略$set中的$setOnInsert字段 +// 需要传递一个map的指针 确保数据可写 否则PANIC +// $set支持map和struct 其他结构体将PANIC +// $setOnInsert只支持map 其他结构体将PANIC +// 只对包含$set和$setOnInsert的map生效 若$set或者$setOnInsert缺失 将PANIC +func SetMapOmitInsertField(m interface{}) { + vo := reflect.ValueOf(m) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() != reflect.Map { + panic("object type not map") + } + setVal := vo.MapIndex(reflect.ValueOf("$set")) + if !setVal.IsValid() { + panic("$set not found") + } + soiVal := vo.MapIndex(reflect.ValueOf("$setOnInsert")) + if !soiVal.IsValid() { + panic("$setOnInsert not found") + } + if !vo.CanSet() { + panic("map can't set") + } + soiRealVal := reflect.ValueOf(soiVal.Interface()) + if soiRealVal.Kind() == reflect.Ptr { + soiRealVal = soiRealVal.Elem() + } + if soiRealVal.Kind() != reflect.Map { + err := fmt.Errorf("$setOnInsert type not map: type(%v)", soiRealVal.Kind()) + panic(err) + } + setRealTyp := reflect.TypeOf(setVal.Interface()) + if setRealTyp.Kind() == reflect.Ptr { + setRealTyp = setRealTyp.Elem() + } + setRealVal := reflect.ValueOf(setVal.Interface()) + if setRealVal.Kind() == reflect.Ptr { + setRealVal = setRealVal.Elem() + } + if setRealVal.Kind() != reflect.Struct && setRealVal.Kind() != reflect.Map { + err := fmt.Errorf("$set type not map or struct: type(%v)", setRealVal.Kind()) + panic(err) + } + var setMap = make(map[string]interface{}) + + //builder := RegisterTimestampCodec(nil).Build() + if setRealVal.Kind() == reflect.Struct { + var data = make(map[string]interface{}) + for i := 0; i < setRealVal.NumField(); i++ { + field := setRealTyp.Field(i) + bsonTag := field.Tag.Get("bson") + if bsonTag == "" { + continue + } + fieldVal := setRealVal.Field(i) + data[bsonTag] = fieldVal.Interface() + } + vo.SetMapIndex(reflect.ValueOf("$set"), reflect.ValueOf(data)) + } + + setRealVal = reflect.ValueOf(vo.MapIndex(reflect.ValueOf("$set")).Interface()) + + if setRealVal.Kind() == reflect.Ptr { + setRealVal = setRealVal.Elem() + } + + if setRealVal.Kind() == reflect.Map { + iter := setRealVal.MapRange() + for iter.Next() { + key := iter.Key() + val := iter.Value() + soiFType := soiRealVal.MapIndex(reflect.ValueOf(key.String())) + if !soiFType.IsValid() { + setMap[key.String()] = val.Interface() + } + } + } + vo.SetMapIndex(reflect.ValueOf("$set"), reflect.ValueOf(setMap)) +} diff --git a/count_scope.go b/count_scope.go new file mode 100644 index 0000000..8628cbb --- /dev/null +++ b/count_scope.go @@ -0,0 +1,217 @@ +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 CountScope struct { + scope *Scope + cw *ctxWrap + err error + limit *int64 + skip *int64 + hint interface{} + filter interface{} + maxTimeMS *time.Duration + collation *options.Collation + opts *options.CountOptions + result int64 +} + +// SetLimit 设置获取数量 +func (cs *CountScope) SetLimit(limit int64) *CountScope { + cs.limit = &limit + return cs +} + +// SetSkip 设置跳过的数量 +func (cs *CountScope) SetSkip(skip int64) *CountScope { + cs.skip = &skip + return cs +} + +// SetHint 设置hit +func (cs *CountScope) SetHint(hint interface{}) *CountScope { + if hint == nil { + cs.hint = bson.M{} + return cs + } + + v := reflect.ValueOf(hint) + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Map || v.Kind() == reflect.Slice { + if v.IsNil() { + cs.hint = bson.M{} + } + } + cs.hint = hint + return cs +} + +// SetFilter 设置过滤条件 +func (cs *CountScope) SetFilter(filter interface{}) *CountScope { + if filter == nil { + cs.filter = bson.M{} + return cs + } + + v := reflect.ValueOf(filter) + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Map || v.Kind() == reflect.Slice { + if v.IsNil() { + cs.filter = bson.M{} + } + } + cs.filter = filter + return cs +} + +// SetMaxTime 设置MaxTime +func (cs *CountScope) SetMaxTime(maxTime time.Duration) *CountScope { + cs.maxTimeMS = &maxTime + return cs +} + +// SetCollation 设置文档 +func (cs *CountScope) SetCollation(collation options.Collation) *CountScope { + cs.collation = &collation + return cs +} + +// SetContext 设置上下文 +func (cs *CountScope) SetContext(ctx context.Context) *CountScope { + if cs.cw == nil { + cs.cw = &ctxWrap{} + } + cs.cw.ctx = ctx + return cs +} + +func (cs *CountScope) doClear() { + if cs.cw != nil && cs.cw.cancel != nil { + cs.cw.cancel() + } + cs.scope.execT = 0 + cs.scope.debug = false +} + +func (cs *CountScope) getContext() context.Context { + return cs.cw.ctx +} + +// Count 执行计数 +func (cs *CountScope) Count() (int64, error) { + defer cs.doClear() + cs.doCount() + + if cs.err != nil { + return 0, cs.err + } + return cs.result, nil +} + +func (cs *CountScope) optionAssembled() { + // 配置项被直接调用重写过 + if cs.opts != nil { + return + } + + cs.opts = new(options.CountOptions) + if cs.skip != nil { + cs.opts.Skip = cs.skip + } + + if cs.limit != nil { + cs.opts.Limit = cs.limit + } + + if cs.collation != nil { + cs.opts.Collation = cs.collation + } + + if cs.hint != nil { + cs.opts.Hint = cs.hint + } + + if cs.maxTimeMS != nil { + cs.opts.MaxTime = cs.maxTimeMS + } +} + +func (cs *CountScope) assertErr() { + if cs.err == nil { + return + } + if errors.Is(cs.err, context.DeadlineExceeded) { + cs.err = &ErrRequestBroken + return + } + err, ok := cs.err.(mongo.CommandError) + if !ok { + return + } + if err.HasErrorMessage(context.DeadlineExceeded.Error()) { + cs.err = &ErrRequestBroken + return + } +} + +func (cs *CountScope) debug() { + if !cs.scope.debug { + return + } + + debugger := &Debugger{ + collection: cs.scope.tableName, + execT: cs.scope.execT, + action: cs, + } + debugger.String() +} + +func (cs *CountScope) doString() string { + builder := RegisterTimestampCodec(nil).Build() + filter, _ := bson.MarshalExtJSONWithRegistry(builder, cs.filter, true, true) + return fmt.Sprintf("count(%s)", string(filter)) +} + +func (cs *CountScope) doCount() { + defer cs.assertErr() + cs.optionAssembled() + cs.preCheck() + var starTime time.Time + if cs.scope.debug { + starTime = time.Now() + } + cs.result, cs.err = db.Collection(cs.scope.tableName).CountDocuments(cs.getContext(), cs.filter, cs.opts) + if cs.scope.debug { + cs.scope.execT = time.Since(starTime) + cs.debug() + } +} + +func (cs *CountScope) preCheck() { + if cs.filter == nil { + cs.filter = bson.M{} + } + var breakerTTL time.Duration + if cs.scope.breaker == nil { + breakerTTL = defaultBreakerTime + } else if cs.scope.breaker.ttl == 0 { + breakerTTL = defaultBreakerTime + } else { + breakerTTL = cs.scope.breaker.ttl + } + if cs.cw == nil { + cs.cw = &ctxWrap{} + } + if cs.cw.ctx == nil { + cs.cw.ctx, cs.cw.cancel = context.WithTimeout(context.Background(), breakerTTL) + } +} diff --git a/count_scope_test.go b/count_scope_test.go new file mode 100644 index 0000000..aee955e --- /dev/null +++ b/count_scope_test.go @@ -0,0 +1,41 @@ +package mdbc + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/sirupsen/logrus" + + "go.mongodb.org/mongo-driver/bson" +) + +func TestCountScope_Count(t *testing.T) { + cfg := &Config{ + URI: "mongodb://admin:admin@10.0.0.135:27017/admin", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + Init(client).InitDB(client.Database("mdbc")) + + var m = NewModel(&ModelSchedTask{}) + count, err := m.Count().SetContext(context.Background()). + SetFilter(bson.M{"created_at": bson.M{"$gt": 0}}). + SetSkip(1). + SetLimit(10). + SetMaxTime(1 * time.Second). + Count() + if err != nil { + panic(err) + return + } + + fmt.Println(count) +} diff --git a/debugger.go b/debugger.go new file mode 100644 index 0000000..1279632 --- /dev/null +++ b/debugger.go @@ -0,0 +1,62 @@ +package mdbc + +import ( + "fmt" + "os" + "time" +) + +// 语句执行解析器 + +// actionDo 实现对 操作部份 的字符化 方便debug输出 +type actionDo interface { + doString() string +} + +type Debugger struct { + collection string // 操作集合 + errMsg string // 错误信息 + execT time.Duration // 执行时间 + action actionDo // 执行语句 +} + +// String 输出执行的基于query的语句 +// 思路:语句分三部分 集合部份;操作部份;附加条件部份 +// 集合操作部份 这个可以直接从collection中获取集合名称 +// 拼接操作部份 这是大头部份 需要每个操作实现 actionDo 接口 拼接出入参 +// 附加条件部份 对于一些sort skip limit等参数进行拼接 +func (d *Debugger) String() { + _, _ = fmt.Fprintf(os.Stdout, "db.getCollection(\"%s\").%s; execTime: %s\n", d.collection, d.action.doString(), d.execT.String()) +} + +// GetString 获取执行的SQL信息 +func (d *Debugger) GetString() string { + return fmt.Sprintf("db.getCollection(\"%s\").%s;", d.collection, d.action.doString()) +} + +// ErrorString 输出执行的基于query的语句 +// 暂时不考虑执行时长 +func (d *Debugger) ErrorString() { + queryAction := "╔\x1b[32mquery:\x1b[0m" + queryMsg := fmt.Sprintf("\x1b[36m%s\x1b[0m", fmt.Sprintf("db.getCollection(\"%s\").%s;", d.collection, d.action.doString())) + errorAction := "╠\x1b[33merror:\x1b[0m" + errorMsg := fmt.Sprintf("\x1b[31m%s\x1b[0m", d.errMsg) + execAction := "╚\x1b[34mexect:\x1b[0m" + execMsg := fmt.Sprintf("\x1b[31m%s\x1b[0m", d.execT.String()) + _, _ = fmt.Fprintf(os.Stdout, "%s %s\n%s %s\n%s %s\n", queryAction, queryMsg, errorAction, errorMsg, execAction, execMsg) +} + +// Echo 返回执行记录的string +func (d *Debugger) Echo() string { + return fmt.Sprintf("db.getCollection(\"%s\").%s; execTime: %s\n", d.collection, d.action.doString(), d.execT.String()) +} + +//前景 背景 颜色 +//30 40 黑色 +//31 41 红色 +//32 42 绿色 +//33 43 黄色 +//34 44 蓝色 +//35 45 紫色 +//36 46 深绿 +//37 47 白色 diff --git a/define.go b/define.go new file mode 100644 index 0000000..cf189bf --- /dev/null +++ b/define.go @@ -0,0 +1,107 @@ +package mdbc + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson" + + "gitlab.com/gotk/gotk/core" +) + +const defaultBreakerTime = time.Second * 5 + +const ( + // ErrFilterParamEmpty Query参数为空 + ErrFilterParamEmpty = 20001 + // ErrUpdateObjectEmpty 更新参数为空 + ErrUpdateObjectEmpty = 20002 + // ErrUpdateObjectNotSupport 更新参数类型不支持 + ErrUpdateObjectNotSupport = 20003 + // ErrOpTransaction 事务执行错误 + ErrOpTransaction = 20004 + // ErrObjectTypeNoMatch 获取对象类型不正确 + ErrObjectTypeNoMatch = 20005 +) + +var ( + coreCodeMap = map[int32]string{ + ErrFilterParamEmpty: "query param is empty", + ErrUpdateObjectEmpty: "update object is empty", + ErrUpdateObjectNotSupport: "update object type not support", + ErrOpTransaction: "abort transaction failed", + ErrObjectTypeNoMatch: "find object type not support", + } +) + +var ( + ErrRecordNotFound = core.ErrMsg{ + ErrCode: core.ErrRecordNotFound, + ErrMsg: core.GetErrMsg(core.ErrRecordNotFound), + } + + ErrRequestBroken = core.ErrMsg{ + ErrCode: core.ErrRequestBroken, + ErrMsg: core.GetErrMsg(core.ErrRequestBroken), + } + + ErrFilterEmpty = core.ErrMsg{ + ErrCode: ErrFilterParamEmpty, + ErrMsg: core.GetErrMsg(ErrFilterParamEmpty), + } + + ErrObjectEmpty = core.ErrMsg{ + ErrCode: ErrUpdateObjectEmpty, + ErrMsg: core.GetErrMsg(ErrUpdateObjectEmpty), + } + + ErrUpdateObjectTypeNotSupport = core.ErrMsg{ + ErrCode: ErrUpdateObjectNotSupport, + ErrMsg: core.GetErrMsg(ErrUpdateObjectNotSupport), + } + + ErrRollbackTransaction = core.ErrMsg{ + ErrCode: ErrOpTransaction, + ErrMsg: core.GetErrMsg(ErrOpTransaction), + } + + ErrFindObjectTypeNotSupport = core.ErrMsg{ + ErrCode: ErrObjectTypeNoMatch, + ErrMsg: core.GetErrMsg(ErrObjectTypeNoMatch), + } +) + +func register() { + core.RegisterError(coreCodeMap) +} + +type M bson.M + +type A bson.A + +type D bson.D + +type E bson.E + +type action string + +const ( + Aggregate action = "Aggregate" + BulkWrite action = "BulkWrite" + CountDocuments action = "Count" + DeleteOne action = "DeleteOne" + DeleteMany action = "DeleteMany" + Distinct action = "Distinct" + Drop action = "Drop" + Find action = "Find" + FindOne action = "FindOne" + FindOneAndDelete action = "FindOneAndDelete" + FindOneAndReplace action = "FindOneAndReplace" + FindOneAndUpdate action = "FindOneAndUpdate" + InsertOne action = "InsertOne" + InsertMany action = "InsertMany" + Indexes action = "Indexes" + ReplaceOne action = "ReplaceOne" + UpdateOne action = "UpdateOne" + UpdateMany action = "UpdateMany" + Watch action = "Watch" +) diff --git a/delete_scope.go b/delete_scope.go new file mode 100644 index 0000000..0cb41bc --- /dev/null +++ b/delete_scope.go @@ -0,0 +1,339 @@ +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 +} diff --git a/delete_scope_test.go b/delete_scope_test.go new file mode 100644 index 0000000..fe67e31 --- /dev/null +++ b/delete_scope_test.go @@ -0,0 +1,142 @@ +package mdbc + +import ( + "context" + "fmt" + "testing" + + "github.com/sirupsen/logrus" + + "go.mongodb.org/mongo-driver/bson" +) + +func TestDeleteScope_OneID(t *testing.T) { + cfg := &Config{ + URI: "mongodb://10.0.0.135:27117/mdbc", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + InitDB(client.Database("heywoods_golang_jingliao_crm_dev")) + var m = NewModel(&ModelWsConnectRecord{}) + + one, err := m.Delete().SetContext(context.Background()).SetID("697022b263e2c1528f32a26e704e63d6").One() + if err != nil { + logrus.Errorf("get err: %+v", err) + return + } + + fmt.Println(one) +} + +func TestDeleteScope_OneFilter(t *testing.T) { + cfg := &Config{ + URI: "mongodb://10.0.0.135:27117/mdbc", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + InitDB(client.Database("heywoods_golang_jingliao_crm_dev")) + var m = NewModel(&ModelWsConnectRecord{}) + + one, err := m.Delete().SetContext(context.Background()).SetFilter(bson.M{}).One() + if err != nil { + logrus.Errorf("get err: %+v", err) + return + } + + fmt.Println(one) +} + +func TestDeleteScope_Many(t *testing.T) { + cfg := &Config{ + URI: "mongodb://10.0.0.135:27117/mdbc", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + InitDB(client.Database("heywoods_golang_jingliao_crm_dev")) + var m = NewModel(&ModelSchedTask{}) + + //objs := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID()} + ms := []*ModelSchedTask{ + { + Id: "f36e55a5e4e64cc2947ae8c8a6333f6e", + TaskState: 3, + }, + { + Id: "bc227d25df1552ab4eca2610295398e4", + TaskState: 2, + }, + { + Id: "2ad97d5d330b20af0ad2ab7fd01cf32a", + TaskState: 0, + }, + } + one, err := m.SetDebug(true).Delete().SetContext(context.Background()). + SetFilter(ms, "Id", "TaskState").Many() + //one, err := m.SetDebug(true).Delete().SetContext(context.Background()).Many() + if err != nil { + panic(err) + } + + fmt.Println(one) +} + +func TestDeleteScope_Other(t *testing.T) { + cfg := &Config{ + URI: "mongodb://10.0.0.135:27117/mdbc", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + InitDB(client.Database("heywoods_golang_jingliao_crm_dev")) + var m = NewModel(&ModelSchedTask{}) + //通过id删除 + one, err := m.Delete().SetContext(context.Background()).SetIDs([]string{ + "2cab037c1ea1a96e4010b397afe703b9", + "bd13a92ff734b2912920c5baa677432b", + "435f56b94ba4f6387dca9b081c58f93b", + }).Many() + if err != nil { + panic(err) + } + fmt.Println(one) + + //通过条件删除 + //one, err = m.Delete().SetContext(context.Background()).SetFilter(bson.M{"_id": "13123"}).One() + //if err != nil { + // panic(err) + //} + //fmt.Println(one) + // + ////设置DeleteOption + //one, err = m.Delete().SetContext(context.Background()).SetDeleteOption(options.DeleteOptions{ + // Collation: nil, + // Hint: nil, + //}).SetID("aac0a95ddfc5c3344777bef62bb8baae").One() + //if err != nil { + // panic(err) + //} + //fmt.Println(one) +} diff --git a/distinct_scope.go b/distinct_scope.go new file mode 100644 index 0000000..07c9525 --- /dev/null +++ b/distinct_scope.go @@ -0,0 +1,305 @@ +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 DistinctScope struct { + scope *Scope + cw *ctxWrap + err error + fieldName string + filter interface{} + opts *options.DistinctOptions + result []interface{} + enableCache bool + cacheFunc DistinctCacheFunc + cacheKey string +} + +type DistinctCacheFunc func(field string, obj interface{}) (*CacheObject, error) + +// DefaultDistinctCacheFunc 默认的缓存方法 +// key: 缓存对象的key +// obj: 缓存对象 +var DefaultDistinctCacheFunc = func() DistinctCacheFunc { + 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("invalid list type, not ptr") + } + + v = v.Elem() + if v.Type().Kind() != reflect.Slice { + return nil, fmt.Errorf("invalid list type, not ptr to slice") + } + + // 空slice 无需缓存 + if v.Len() == 0 { + return nil, nil + } + + b, _ := json.Marshal(obj) + co = &CacheObject{ + Key: key, + Value: string(b), + } + + return co, nil + } +} + +// SetContext 设置上下文 +func (ds *DistinctScope) SetContext(ctx context.Context) *DistinctScope { + if ds.cw == nil { + ds.cw = &ctxWrap{} + } + ds.cw.ctx = ctx + return ds +} + +func (ds *DistinctScope) getContext() context.Context { + return ds.cw.ctx +} + +func (ds *DistinctScope) doClear() { + if ds.cw != nil && ds.cw.cancel != nil { + ds.cw.cancel() + } + ds.scope.execT = 0 + ds.scope.debug = false +} + +// SetUpdateOption 设置更新选项 +func (ds *DistinctScope) SetUpdateOption(opts options.DistinctOptions) *DistinctScope { + ds.opts = &opts + return ds +} + +// SetFilter 设置过滤条件 +func (ds *DistinctScope) SetFilter(filter interface{}) *DistinctScope { + if filter == nil { + ds.filter = bson.M{} + return ds + } + + v := reflect.ValueOf(filter) + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Map || v.Kind() == reflect.Slice { + if v.IsNil() { + ds.filter = bson.M{} + } + } + ds.filter = filter + return ds +} + +// SetFieldName 设置字段名 +func (ds *DistinctScope) SetFieldName(name string) *DistinctScope { + ds.fieldName = name + return ds +} + +func (ds *DistinctScope) optionAssembled() { + // 配置项被直接调用重写过 + if ds.opts != nil { + return + } + + ds.opts = new(options.DistinctOptions) +} + +// SetCacheFunc 传递一个函数 处理查询操作的结果进行缓存 +func (ds *DistinctScope) SetCacheFunc(key string, cb DistinctCacheFunc) *DistinctScope { + ds.enableCache = true + ds.cacheFunc = cb + ds.cacheKey = key + return ds +} + +func (ds *DistinctScope) preCheck() { + if ds.filter == nil { + ds.filter = bson.M{} + } + 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) + } +} + +func (ds *DistinctScope) 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 + return + } +} + +// debug 判断是否开启debug,开启的话就打印 +func (ds *DistinctScope) debug() { + if !ds.scope.debug { + 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 *DistinctScope) doString() string { + builder := RegisterTimestampCodec(nil).Build() + filter, _ := bson.MarshalExtJSONWithRegistry(builder, ds.filter, true, true) + return fmt.Sprintf(`distinct("%s",%s)`, ds.fieldName, string(filter)) +} + +func (ds *DistinctScope) doGet() { + defer ds.assertErr() + var starTime time.Time + if ds.scope.debug { + starTime = time.Now() + } + ds.result, ds.err = db.Collection(ds.scope.tableName).Distinct(ds.getContext(), ds.fieldName, ds.filter, ds.opts) + if ds.scope.debug { + ds.scope.execT = time.Since(starTime) + ds.debug() + } +} + +// doCache 执行缓存 +// 检测句柄存不存在 +// 从cacheFunc中获取cacheObj +// 判断下数据没问题以及没有错误就进行缓存 +func (ds *DistinctScope) doCache(obj interface{}) *DistinctScope { + // redis句柄不存在 + if ds.scope.cache == nil { + return nil + } + + cacheObj, err := ds.cacheFunc(ds.cacheKey, obj) + if err != nil { + ds.err = err + return ds + } + + if cacheObj == nil { + ds.err = fmt.Errorf("cache object nil") + return ds + } + + ttl := ds.scope.cache.ttl + if ttl == 0 { + ttl = time.Hour + } else if ttl == -1 { + ttl = 0 + } + + if ds.getContext().Err() != nil { + ds.err = ds.getContext().Err() + ds.assertErr() + return ds + } + + ds.err = ds.scope.cache.client.Set(ds.getContext(), cacheObj.Key, cacheObj.Value, ttl).Err() + return ds +} + +// Get 获取结果 +// list: 必须 *[]string 或者 *[]struct +func (ds *DistinctScope) Get(list interface{}) error { + defer ds.doClear() + ds.optionAssembled() + ds.preCheck() + + if ds.fieldName == "" { + return fmt.Errorf("field name empty") + } + + ds.doGet() + + if ds.err != nil { + return ds.err + } + + vo := reflect.ValueOf(list) + + if vo.Kind() != reflect.Ptr { + return fmt.Errorf("arg not ptr") + } + + vo = vo.Elem() + + if vo.Kind() != reflect.Slice { + return fmt.Errorf("arg not ptr to slice") + } + + vot := vo.Type() + + if vot.Kind() != reflect.Slice { + return fmt.Errorf("arg not ptr to slice") + } + + vot = vot.Elem() + + if vot.Kind() != reflect.String && vot.Kind() != reflect.Struct { + return fmt.Errorf("slice subtype must string or struct but get %+v", vot.Kind()) + } + + for _, obj := range ds.result { + vo = reflect.Append(vo, reflect.ValueOf(obj)) + } + + rtv := reflect.ValueOf(list) + rtv.Elem().Set(vo) + + if ds.enableCache { + ds.doCache(list) + } + + return nil +} diff --git a/distinct_scope_test.go b/distinct_scope_test.go new file mode 100644 index 0000000..e5e561a --- /dev/null +++ b/distinct_scope_test.go @@ -0,0 +1,41 @@ +package mdbc + +import ( + "testing" + + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" +) + +func TestDistinctScope(*testing.T) { + cfg := &Config{ + URI: "mongodb://admin:admin@10.0.0.135:27017/admin", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + InitDB(client.Database("mdbc")) + + var m = NewModel(&ModelSchedTask{}) + + var data []string + + err = m. + SetDebug(true). + Distinct(). + SetCacheFunc("task_type", DefaultDistinctCacheFunc()). + SetFieldName("task_type"). + SetFilter(bson.M{"task_type": bson.M{"$ne": "test"}}). + Get(&data) + + if err != nil { + panic(err) + } + logrus.Infof("get ttl: %+v\n", m.cache.ttl) + logrus.Infof("res %+v\n", data) +} diff --git a/drop_scope.go b/drop_scope.go new file mode 100644 index 0000000..37b379c --- /dev/null +++ b/drop_scope.go @@ -0,0 +1,110 @@ +package mdbc + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/mongo" +) + +type DropScope struct { + scope *Scope + cw *ctxWrap + err error +} + +// SetContext 设置上下文 +func (ds *DropScope) SetContext(ctx context.Context) *DropScope { + if ds.cw == nil { + ds.cw = &ctxWrap{} + } + ds.cw.ctx = ctx + return ds +} + +func (ds *DropScope) 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) + } +} + +func (ds *DropScope) 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 + return + } +} + +func (ds *DropScope) doString() string { + return "drop()" +} + +func (ds *DropScope) debug() { + if !ds.scope.debug { + return + } + + debugger := &Debugger{ + collection: ds.scope.tableName, + execT: ds.scope.execT, + action: ds, + } + debugger.String() +} + +func (ds *DropScope) getContext() context.Context { + return ds.cw.ctx +} + +func (ds *DropScope) doClear() { + if ds.cw != nil && ds.cw.cancel != nil { + ds.cw.cancel() + } + ds.scope.execT = 0 + ds.scope.debug = false +} + +// Do 删除Model绑定的集合 +func (ds *DropScope) Do() error { + defer ds.doClear() + ds.preCheck() + + defer ds.assertErr() + var starTime time.Time + if ds.scope.debug { + starTime = time.Now() + } + ds.err = db.Collection(ds.scope.tableName).Drop(ds.getContext()) + if ds.scope.debug { + ds.scope.execT = time.Since(starTime) + ds.debug() + } + if ds.err != nil { + return ds.err + } + + return nil +} diff --git a/find_one_scope.go b/find_one_scope.go new file mode 100644 index 0000000..8239fd2 --- /dev/null +++ b/find_one_scope.go @@ -0,0 +1,591 @@ +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 +} diff --git a/find_one_scope_test.go b/find_one_scope_test.go new file mode 100644 index 0000000..bbea1a0 --- /dev/null +++ b/find_one_scope_test.go @@ -0,0 +1,62 @@ +package mdbc + +import ( + "testing" + "time" + + "go.mongodb.org/mongo-driver/mongo/readpref" + + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" +) + +func TestFindOneScope(t *testing.T) { + + client, err := ConnInit(&Config{ + URI: "mongodb://mdbc:mdbc@10.0.0.135:27117/admin", + MinPoolSize: 32, + ConnTimeout: 10, + ReadPreference: readpref.Nearest(), + RegistryBuilder: RegisterTimestampCodec(nil), + }) + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + InitDB(client.Database("mdbc")) + + time.Sleep(time.Second * 5) + + var m = NewModel(&ModelSchedTask{}) + + var record ModelSchedTask + err = m.SetDebugError(true).FindOne().SetFilter(bson.M{"_id": "insertffdddfknkodsanfkasdf"}).Get(&record) + if err != nil { + return + } + logrus.Infof("get: %+v", &record) + + time.Sleep(time.Second * 5) +} + +func TestFindOneScope_Delete(t *testing.T) { + var m = NewModel(&ModelSchedTask{}) + err := m.SetCacheExpiredAt(time.Second*300).FindOne(). + SetFilter(bson.M{"_id": "13123"}).SetCacheFunc("Id", DefaultFindOneCacheFunc()).Delete() + if err != nil { + panic(err) + } + logrus.Infof("get ttl: %+v", m.cache.ttl) +} + +func TestFindOneScope_Replace(t *testing.T) { + var m = NewModel(&ModelSchedTask{}) + var record ModelSchedTask + err := m.FindOne(). + SetFilter(bson.M{"_id": "0e63f5962e18a8da331289caaa3fa224"}).Get(&record) + record.RspJson = "hahahahah" + err = m.FindOne().SetFilter(bson.M{"_id": record.Id}).Replace(&record) + if err != nil { + panic(err) + } + logrus.Infof("get ttl: %+v", m.cache.ttl) +} diff --git a/find_scope.go b/find_scope.go new file mode 100644 index 0000000..6076d24 --- /dev/null +++ b/find_scope.go @@ -0,0 +1,445 @@ +package mdbc + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "reflect" + "time" + + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type FindScope struct { + scope *Scope // 向上提权 + cw *ctxWrap // 查询上下文环境 + cursor *mongo.Cursor // 查询指针 + err error // 操作是否有错误 + limit *int64 // 限制查询数量 + skip *int64 // 偏移量 + count *int64 // 计数统计结果 + withCount bool // 是否开启计数统计 这将只基于filter进行计数而忽略 limit skip + selects interface{} // + sort bson.D // 排序 + filter interface{} // 过滤条件 + opts *options.FindOptions // 查询条件 + enableCache bool // 是否开启缓存 + cacheKey string // 缓存key + cacheFunc FindCacheFunc // 基于什么缓存函数进行缓存 +} + +// FindCacheFunc 缓存Find结果集 +// field: 若无作用 可置空 DefaultFindCacheFunc将使用其反射出field字段对应的value并将其设为key +// obj: 这是一个slice 只能在GetList是使用,GetMap使用将无效果 +type FindCacheFunc func(field string, obj interface{}) (*CacheObject, error) + +// DefaultFindCacheFunc 按照第一个结果的field字段作为key缓存list数据 +// field字段需要满足所有数据均有该属性 否则有概率导致缓存失败 +var DefaultFindCacheFunc = func() FindCacheFunc { + return func(field string, obj interface{}) (*CacheObject, error) { + // 建议先断言obj 再操作 若obj断言失败 请返回nil 这样缓存将不会执行 + // 示例: res,ok := obj.(*[]*model.ModelUser) 然后操作res 将 key,value 写入 CacheObject 既可 + // 下面使用反射进行操作 保证兼容 + v := reflect.ValueOf(obj) + if v.Type().Kind() != reflect.Ptr { + return nil, fmt.Errorf("invalid list type, not ptr") + } + + v = v.Elem() + if v.Type().Kind() != reflect.Slice { + return nil, fmt.Errorf("invalid list type, not ptr to slice") + } + + // 空slice 无需缓存 + if v.Len() == 0 { + return nil, nil + } + + firstNode := v.Index(0) + // 判断过长度 无需判断nil + if firstNode.Kind() == reflect.Ptr { + firstNode = firstNode.Elem() + } + idVal := firstNode.FieldByName(field) + if idVal.Kind() == reflect.Invalid { + return nil, fmt.Errorf("first node %s field not found", idVal) + } + + b, _ := json.Marshal(v.Interface()) + co := &CacheObject{ + Key: idVal.String(), + Value: string(b), + } + + return co, nil + } +} + +// SetLimit 设置获取的条数 +func (fs *FindScope) SetLimit(limit int64) *FindScope { + fs.limit = &limit + return fs +} + +// SetSkip 设置跳过的条数 +func (fs *FindScope) SetSkip(skip int64) *FindScope { + fs.skip = &skip + return fs +} + +// SetSort 设置排序 +func (fs *FindScope) SetSort(sort bson.D) *FindScope { + fs.sort = sort + return fs +} + +// SetContext 设置上下文 +func (fs *FindScope) SetContext(ctx context.Context) *FindScope { + if fs.cw == nil { + fs.cw = &ctxWrap{} + } + fs.cw.ctx = ctx + return fs +} + +// SetSelect 选择查询字段 格式 {key: 1, key: 0} +// 你可以传入 bson.M, map[string]integer_interface 其他类型将导致该功能失效 +// 1:显示 0:不显示 +func (fs *FindScope) SetSelect(selects interface{}) *FindScope { + vo := reflect.ValueOf(selects) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + // 不是map 提前返回 + if vo.Kind() != reflect.Map { + return fs + } + + fs.selects = selects + return fs +} + +func (fs *FindScope) getContext() context.Context { + if fs.cw == nil { + fs.cw = &ctxWrap{ + ctx: context.Background(), + } + } + return fs.cw.ctx +} + +// SetFilter 设置过滤条件 +func (fs *FindScope) SetFilter(filter interface{}) *FindScope { + if filter == nil { + fs.filter = bson.M{} + return fs + } + + v := reflect.ValueOf(filter) + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Map || v.Kind() == reflect.Slice { + if v.IsNil() { + fs.filter = bson.M{} + } + } + fs.filter = filter + return fs +} + +// SetFindOption 设置FindOption 优先级最低 sort/skip/limit 会被 set 函数重写 +func (fs *FindScope) SetFindOption(opts options.FindOptions) *FindScope { + fs.opts = &opts + return fs +} + +// WithCount 查询的同时获取总数量 +// 将按照 SetFilter 的条件进行查询 未设置是获取所有文档数量 +// 如果在该步骤出错 将不会进行余下操作 +func (fs *FindScope) WithCount(count *int64) *FindScope { + fs.withCount = true + fs.count = count + return fs +} + +func (fs *FindScope) optionAssembled() { + // 配置项被直接调用重写过 + if fs.opts == nil { + fs.opts = new(options.FindOptions) + } + + if fs.sort != nil { + fs.opts.Sort = fs.sort + } + + if fs.skip != nil { + fs.opts.Skip = fs.skip + } + + if fs.limit != nil { + fs.opts.Limit = fs.limit + } + + if fs.selects != nil { + fs.opts.Projection = fs.selects + } +} + +func (fs *FindScope) preCheck() { + if fs.filter == nil { + fs.filter = bson.M{} + } + var breakerTTL time.Duration + if fs.scope.breaker == nil { + breakerTTL = defaultBreakerTime + } else if fs.scope.breaker.ttl == 0 { + breakerTTL = defaultBreakerTime + } else { + breakerTTL = fs.scope.breaker.ttl + } + if fs.cw == nil { + fs.cw = &ctxWrap{} + } + if fs.cw.ctx == nil { + fs.cw.ctx, fs.cw.cancel = context.WithTimeout(context.Background(), breakerTTL) + } else { + fs.cw.ctx, fs.cw.cancel = context.WithTimeout(fs.cw.ctx, breakerTTL) + } +} + +func (fs *FindScope) assertErr() { + if fs.err == nil { + return + } + if errors.Is(fs.err, mongo.ErrNoDocuments) || errors.Is(fs.err, mongo.ErrNilDocument) { + fs.err = &ErrRecordNotFound + return + } + if errors.Is(fs.err, context.DeadlineExceeded) { + fs.err = &ErrRequestBroken + return + } + err, ok := fs.err.(mongo.CommandError) + if ok && err.HasErrorMessage(context.DeadlineExceeded.Error()) { + fs.err = &ErrRequestBroken + return + } + fs.err = err +} + +func (fs *FindScope) doString() string { + builder := RegisterTimestampCodec(nil).Build() + filter, _ := bson.MarshalExtJSONWithRegistry(builder, fs.filter, true, true) + query := fmt.Sprintf("find(%s)", string(filter)) + if fs.skip != nil { + query = fmt.Sprintf("%s.skip(%d)", query, *fs.skip) + } + if fs.limit != nil { + query = fmt.Sprintf("%s.limit(%d)", query, *fs.limit) + } + if fs.sort != nil { + sort, _ := bson.MarshalExtJSON(fs.sort, true, true) + query = fmt.Sprintf("%s.sort(%s)", query, string(sort)) + } + return query +} + +func (fs *FindScope) debug() { + if !fs.scope.debug && !fs.scope.debugWhenError { + return + } + + debugger := &Debugger{ + collection: fs.scope.tableName, + execT: fs.scope.execT, + action: fs, + } + + // 当错误时优先输出 + if fs.scope.debugWhenError { + if fs.err != nil { + debugger.errMsg = fs.err.Error() + debugger.ErrorString() + } + return + } + + // 所有bug输出 + if fs.scope.debug { + debugger.String() + } +} + +func (fs *FindScope) doClear() { + if fs.cw != nil && fs.cw.cancel != nil { + fs.cw.cancel() + } + fs.scope.debug = false + fs.scope.execT = 0 +} + +func (fs *FindScope) doSearch() { + var starTime time.Time + if fs.scope.debug { + starTime = time.Now() + } + fs.cursor, fs.err = db.Collection(fs.scope.tableName).Find(fs.getContext(), fs.filter, fs.opts) + fs.assertErr() // 断言错误 + if fs.scope.debug { + fs.scope.execT = time.Since(starTime) + fs.debug() + } + + // 有检测数量的 + if fs.withCount { + var res int64 + res, fs.err = fs.scope.Count().SetContext(fs.getContext()).SetFilter(fs.filter).Count() + *fs.count = res + } + fs.assertErr() +} + +// GetList 获取列表 +// list: 需要一个 *[]*struct +func (fs *FindScope) GetList(list interface{}) error { + defer fs.doClear() + fs.optionAssembled() + fs.preCheck() + if fs.err != nil { + return fs.err + } + fs.doSearch() + + if fs.err != nil { + return fs.err + } + v := reflect.ValueOf(list) + if v.Type().Kind() != reflect.Ptr { + fs.err = fmt.Errorf("invalid list type, not ptr") + return fs.err + } + + v = v.Elem() + if v.Type().Kind() != reflect.Slice { + fs.err = fmt.Errorf("invalid list type, not ptr to slice") + return fs.err + } + + fs.err = fs.cursor.All(fs.getContext(), list) + + if fs.enableCache { + fs.doCache(list) + } + + return fs.err +} + +// GetMap 基于结果的某个字段为Key 获取Map +// m: 传递一个 *map[string]*Struct +// field: struct的一个字段名称 需要是公开可访问的大写 +func (fs *FindScope) GetMap(m interface{}, field string) error { + defer fs.doClear() + fs.optionAssembled() + fs.preCheck() + if fs.err != nil { + return fs.err + } + fs.doSearch() + + if fs.err != nil { + return fs.err + } + + v := reflect.ValueOf(m) + if v.Type().Kind() != reflect.Ptr { + fs.err = fmt.Errorf("invalid map type, not ptr") + return fs.err + } + v = v.Elem() + if v.Type().Kind() != reflect.Map { + fs.err = fmt.Errorf("invalid map type, not map") + return fs.err + } + + mapType := v.Type() + valueType := mapType.Elem() + keyType := mapType.Key() + + if valueType.Kind() != reflect.Ptr { + fs.err = fmt.Errorf("invalid map value type, not prt") + return fs.err + } + + if !v.CanSet() { + fs.err = fmt.Errorf("invalid map value type, not addressable or obtained by the use of unexported struct fields") + return fs.err + } + v.Set(reflect.MakeMap(reflect.MapOf(keyType, valueType))) + + for fs.cursor.Next(fs.getContext()) { + t := reflect.New(valueType.Elem()) + if fs.err = fs.cursor.Decode(t.Interface()); fs.err != nil { + logrus.Errorf("err: %+v", fs.err) + return fs.err + } + fieldNode := t.Elem().FieldByName(field) + if fieldNode.Kind() == reflect.Invalid { + fs.err = fmt.Errorf("invalid model key: %s", field) + return fs.err + } + v.SetMapIndex(fieldNode, t) + } + + return fs.err +} + +func (fs *FindScope) Error() error { + return fs.err +} + +// SetCacheFunc 传递一个函数 处理查询操作的结果进行缓存 还没有实现 +func (fs *FindScope) SetCacheFunc(key string, cb FindCacheFunc) *FindScope { + fs.enableCache = true + fs.cacheFunc = cb + fs.cacheKey = key + return fs +} + +// doCache 执行缓存 +func (fs *FindScope) doCache(obj interface{}) *FindScope { + // redis句柄不存在 + if fs.scope.cache == nil { + return nil + } + cacheObj, err := fs.cacheFunc(fs.cacheKey, obj) + if err != nil { + fs.err = err + return fs + } + + if cacheObj == nil { + fs.err = fmt.Errorf("cache object nil") + return fs + } + + ttl := fs.scope.cache.ttl + if ttl == 0 { + ttl = time.Hour + } else if ttl == -1 { + ttl = 0 + } + + if fs.getContext().Err() != nil { + fs.err = fs.getContext().Err() + fs.assertErr() + return fs + } + + fs.err = fs.scope.cache.client.Set(fs.getContext(), cacheObj.Key, cacheObj.Value, ttl).Err() + + // 这里也要断言错误 看是否是熔断错误 + + return fs +} diff --git a/find_scope_test.go b/find_scope_test.go new file mode 100644 index 0000000..8b9897f --- /dev/null +++ b/find_scope_test.go @@ -0,0 +1,98 @@ +package mdbc + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + + "github.com/sirupsen/logrus" + "gitlab.com/gotk/gotk/utils" + "go.mongodb.org/mongo-driver/bson" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestFindScope(t *testing.T) { + codec := bson.NewRegistryBuilder() + + codec.RegisterCodec(reflect.TypeOf(×tamppb.Timestamp{}), &TimestampCodec{}) + + client, err := ConnInit(&Config{ + URI: "mongodb://mdbc:mdbc@10.0.0.135:27117/admin", + MinPoolSize: 32, + ConnTimeout: 10, + RegistryBuilder: codec, + }) + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + InitDB(client.Database("heywoods_golang_jingliao_crm_dev")) + + var m = NewModel(&ModelRobot{}) + + var record []*ModelRobot + var count int64 + err = m.Find().SetFilter(bson.M{ + ModelRobotField.GetStatusField(): 11, + }).SetSort(bson.D{ + { + Key: ModelRobotField.GetWechatIdField(), + Value: -1, + }, + }).WithCount(&count).SetLimit(10).GetList(&record) + if err != nil { + panic(err) + } + logrus.Infof("count: %+v", count) + logrus.Infof("list: %+v", utils.PluckString(record, "NickName")) + //p1 := utils.PluckString(record, "NickName") + //logrus.Infof("all: %+v", p1) + // + //err = m.Find().SetFilter(nil).SetSort(bson.D{ + // { + // Key: ModelRobotField.GetWechatIdField(), + // Value: -1, + // }, + //}).SetSkip(0).SetLimit(10).GetList(&record) + //if err != nil { + // panic(err) + //} + //p1 = utils.PluckString(record, "NickName") + //logrus.Infof("p1: %+v", p1) + // + //err = m.Find().SetFilter(nil).SetSort(bson.D{ + // { + // Key: ModelRobotField.GetWechatIdField(), + // Value: -1, + // }, + //}).SetSkip(10).SetLimit(10).GetList(&record) + //if err != nil { + // panic(err) + //} + //p2 := utils.PluckString(record, "NickName") + //logrus.Infof("p2: %+v", p2) +} + +func TestFindScope_GetMap(t *testing.T) { + var m = NewModel(&ModelSchedTask{}) + var record = make(map[string]*ModelSchedTask) + err := m.Find(). + SetFilter(nil).SetLimit(2).SetCacheFunc("Id", DefaultFindCacheFunc()).GetMap(&record, "Id") + if err != nil { + panic(err) + } + marshal, err := json.Marshal(record) + if err != nil { + return + } + fmt.Printf("%+v\n", string(marshal)) +} + +func TestFindScope_SetCacheFunc(t *testing.T) { + var record []*ModelSchedTask + var m = NewModel(&ModelSchedTask{}) + err := m.Find().SetCacheFunc("Id", DefaultFindCacheFunc()).GetList(&record) + if err != nil { + return + } +} diff --git a/gen.sh b/gen.sh new file mode 100755 index 0000000..80659ea --- /dev/null +++ b/gen.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +gotker gen --path . --out . --no-scope \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fcd042d --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module gitlab.com/gotk/mdbc + +go 1.16 + +require ( + github.com/antonfisher/nested-logrus-formatter v1.3.1 + github.com/go-redis/redis/v8 v8.11.4 + github.com/google/uuid v1.3.0 + github.com/json-iterator/go v1.1.12 + github.com/sirupsen/logrus v1.8.1 + gitlab.com/gotk/gotk v0.0.0-20220223083201-5d05a06943c3 + go.mongodb.org/mongo-driver v1.8.3 + golang.org/x/sys v0.0.0-20220222200937-f2425489ef4c // indirect + google.golang.org/protobuf v1.27.1 +) diff --git a/hook.go b/hook.go new file mode 100644 index 0000000..4b6ff83 --- /dev/null +++ b/hook.go @@ -0,0 +1,12 @@ +package mdbc + +// Hook mongodb的指令hook +type Hook interface { + Before() error + After() error +} + +// UpdateHook 更新指令的hook +type UpdateHook interface { + Hook +} diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..218f3407dc8e470d2875f9db83dc732b85921c70 GIT binary patch literal 69324 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 A$N&HU literal 0 HcmV?d00001 diff --git a/index_scope.go b/index_scope.go new file mode 100644 index 0000000..7e69bbd --- /dev/null +++ b/index_scope.go @@ -0,0 +1,7 @@ +package mdbc + +import ( + "errors" +) + +var ErrorCursorIsNil = errors.New("cursor is nil") diff --git a/index_scope_test.go b/index_scope_test.go new file mode 100644 index 0000000..007e3a4 --- /dev/null +++ b/index_scope_test.go @@ -0,0 +1,97 @@ +package mdbc + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/mongo/readpref" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func TestIndexScope_GetList(t *testing.T) { + client, err := ConnInit(&Config{ + URI: "mongodb://mdbc:mdbc@10.0.0.135:27117/admin", + MinPoolSize: 32, + ConnTimeout: 10, + ReadPreference: readpref.Nearest(), + RegistryBuilder: RegisterTimestampCodec(nil), + }) + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + InitDB(client.Database("mdbc")) + + var list IndexList + var m = NewModel(&ModelSchedTask{}) + err = m.Index().SetContext(context.Background()).GetIndexList(&list) + + if err != nil { + panic(err) + } + + for _, card := range list { + logrus.Infof("%+v\n", card) + } +} + +func TestIndexScope_DropAll(t *testing.T) { + var m = NewModel(&ModelSchedTask{}) + err := m.Index().SetContext(context.Background()).DropAll() + if err != nil { + panic(err) + } +} + +func TestIndexScope_DropOne(t *testing.T) { + var m = NewModel(&ModelSchedTask{}) + err := m.Index().SetContext(context.Background()).DropOne("expire_time_2") + if err != nil { + panic(err) + } +} + +func TestIndexScope_AddIndexModel(t *testing.T) { + var err error + + var m = NewModel(&ModelSchedTask{}) + // 创建多个索引 + _, err = m.Index().SetContext(context.Background()).AddIndexModels(mongo.IndexModel{ + Keys: bson.D{{Key: "expire_time", Value: 2}}, // 设置索引列 + Options: options.Index().SetExpireAfterSeconds(0), // 设置过期时间 + }).AddIndexModels(mongo.IndexModel{ + Keys: bson.D{{Key: "created_at", Value: 1}}, // 设置索引列 + }).CreateMany() + + // 创建单个索引 + _, err = m.Index().SetContext(context.Background()).AddIndexModels(mongo.IndexModel{ + Keys: bson.D{{Key: "expire_time", Value: 2}}, // 设置索引列 + Options: options.Index().SetExpireAfterSeconds(0), // 设置过期时间 + }).CreateOne() + + if err != nil { + panic(err) + } +} + +func TestIndexScope_SetListIndexesOption(t *testing.T) { + var list IndexList + s := int32(2) + mt := 10 * time.Second + var m = NewModel(&ModelSchedTask{}) + //设定ListIndexesOptions + err := m.Index().SetListIndexesOption(options.ListIndexesOptions{ + BatchSize: &s, + MaxTime: &mt, + }).GetIndexList(&list) + if err != nil { + panic(err) + } + + fmt.Printf("%+v\n", list) +} diff --git a/index_scope_v2.go b/index_scope_v2.go new file mode 100644 index 0000000..9873692 --- /dev/null +++ b/index_scope_v2.go @@ -0,0 +1,273 @@ +package mdbc + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// KeySort 排序方式 类型别名 +type KeySort = int + +const ( + ASC KeySort = 1 // 正序排列 + DESC KeySort = -1 // 倒序排列 +) + +// IndexCard 索引信息 +type IndexCard struct { + Version int32 `bson:"v"` // 版本信息 + Name string `bson:"name"` // 索引名 + Key map[string]KeySort `bson:"key"` // 索引方式 +} + +// IndexList 索引列表 +type IndexList []*IndexCard + +type IndexScope struct { + scope *Scope + cw *ctxWrap + iv mongo.IndexView + cursor *mongo.Cursor + ims []mongo.IndexModel + err error + lOpts *options.ListIndexesOptions + dOpts *options.DropIndexesOptions + cOpts *options.CreateIndexesOptions + execResult interface{} +} + +func (is *IndexScope) assertErr() { + if is.err == nil { + return + } + + err, ok := is.err.(mongo.CommandError) + if !ok { + return + } + if err.HasErrorMessage(context.DeadlineExceeded.Error()) { + is.err = &ErrRequestBroken + } +} + +func (is *IndexScope) doClear() { + if is.cw != nil && is.cw.cancel != nil { + is.cw.cancel() + } + is.scope.debug = false + is.scope.execT = 0 +} + +// doGet 拿到 iv 后续的 增删改查都基于 iv 对象 +func (is *IndexScope) doGet() { + is.iv = db.Collection(is.scope.tableName).Indexes(is.getContext()) +} + +// doCursor 拿到 cursor +func (is *IndexScope) doCursor() { + if is.lOpts == nil { + is.lOpts = &options.ListIndexesOptions{} + } + is.cursor, is.err = is.iv.List(is.getContext(), is.lOpts) + is.assertErr() +} + +// SetContext 设定 Context +func (is *IndexScope) SetContext(ctx context.Context) *IndexScope { + if is.cw == nil { + is.cw = &ctxWrap{} + } + is.cw.ctx = ctx + return is +} + +// SetListIndexesOption 设定 ListIndexesOptions +func (is *IndexScope) SetListIndexesOption(opts options.ListIndexesOptions) *IndexScope { + is.lOpts = &opts + return is +} + +// SetDropIndexesOption 设定 DropIndexesOptions +func (is *IndexScope) SetDropIndexesOption(opts options.DropIndexesOptions) *IndexScope { + is.dOpts = &opts + return is +} + +// SetCreateIndexesOption 设定 CreateIndexesOptions +func (is *IndexScope) SetCreateIndexesOption(opts options.CreateIndexesOptions) *IndexScope { + is.cOpts = &opts + return is +} + +// getContext 获取ctx +func (is *IndexScope) getContext() context.Context { + return is.cw.ctx +} + +// GetIndexList 获取索引列表 +func (is *IndexScope) GetIndexList(data *IndexList) error { + defer is.doClear() + is.doGet() + + is.doCursor() + + if is.err != nil { + return is.err + } + + is.err = is.cursor.All(is.getContext(), data) + + is.assertErr() + + if is.err != nil { + return is.err + } + + return nil +} + +// GetListCursor 获取结果集的指针 +func (is *IndexScope) GetListCursor() (*mongo.Cursor, error) { + defer is.doClear() + + is.doGet() + + is.doCursor() + + if is.err != nil { + return nil, is.err + } + + return is.cursor, nil +} + +// DropOne 删除一个索引 name 为索引名称 +func (is *IndexScope) DropOne(name string) error { + defer is.doClear() + + is.doGet() + + is.doDropOne(name) + + if is.err != nil { + return is.err + } + + return nil +} + +// DropAll 删除所有的索引 +func (is *IndexScope) DropAll() error { + defer is.doClear() + + is.doGet() + + is.doDropAll() + if is.err != nil { + return is.err + } + + return nil +} + +func (is *IndexScope) doDropAll() { + if is.dOpts == nil { + is.dOpts = &options.DropIndexesOptions{} + } + + is.execResult, is.err = is.iv.DropAll(is.getContext(), is.dOpts) + + is.assertErr() +} + +// doDropOne 删除一个索引 +func (is *IndexScope) doDropOne(name string) { + if is.dOpts == nil { + is.dOpts = &options.DropIndexesOptions{} + } + + is.execResult, is.err = is.iv.DropOne(is.getContext(), name, is.dOpts) + + is.assertErr() +} + +// CreateOne 创建单个索引 +// 在调用本方法前,需要调用 AddIndexModel() 添加 +// 当添加了多个 indexModel 后仅会创建第一个索引 +// 返回 res 索引名称 err 创建时错误信息 +func (is *IndexScope) CreateOne() (res string, err error) { + defer is.doClear() + + is.doGet() + + is.doCreateOne() + + if is.err != nil { + return "", is.err + } + + res, ok := is.execResult.(string) + if !ok { + return "", fmt.Errorf("create success but get index name empty") + } + + return res, nil +} + +// CreateMany 创建多个索引,在调用本方法前,需要调用 AddIndexModel() 添加 +func (is *IndexScope) CreateMany() (res []string, err error) { + defer is.doClear() + + is.doGet() + + is.doCreateMany() + + if is.err != nil { + return nil, is.err + } + + res, ok := is.execResult.([]string) + if !ok { + return nil, fmt.Errorf("create success but get index names empty") + } + + return res, nil +} + +func (is *IndexScope) doCreateOne() { + if len(is.ims) == 0 { + is.err = fmt.Errorf("no such index model, you need to use AddIndexModel() to add one") + return + } + if is.cOpts == nil { + is.cOpts = &options.CreateIndexesOptions{} + } + + is.execResult, is.err = is.iv.CreateOne(is.getContext(), is.ims[0], is.cOpts) + is.assertErr() +} + +func (is *IndexScope) doCreateMany() { + if len(is.ims) == 0 { + is.err = fmt.Errorf("no such index model, you need to use AddIndexModel() to add some") + return + } + if is.cOpts == nil { + is.cOpts = &options.CreateIndexesOptions{} + } + is.execResult, is.err = is.iv.CreateMany(is.getContext(), is.ims, is.cOpts) + is.assertErr() +} + +// AddIndexModels 添加 indexModel +func (is *IndexScope) AddIndexModels(obj ...mongo.IndexModel) *IndexScope { + if len(obj) == 0 { + is.err = fmt.Errorf("add index model empty") + return is + } + is.ims = append(is.ims, obj...) + return is +} diff --git a/insert_scope.go b/insert_scope.go new file mode 100644 index 0000000..1d36c15 --- /dev/null +++ b/insert_scope.go @@ -0,0 +1,281 @@ +package mdbc + +import ( + "context" + "errors" + "fmt" + "reflect" + "time" + + jsoniter "github.com/json-iterator/go" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type insertAction string + +const ( + insertOne insertAction = "insertOne" + insertMany insertAction = "insertMany" +) + +type InsertResult struct { + id interface{} +} + +// GetString 获取单条插入的字符串ID +func (i *InsertResult) GetString() string { + vo := reflect.ValueOf(i.id) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() == reflect.String { + return vo.String() + } + return "" +} + +// GetListString 获取多条插入的字符串ID +func (i *InsertResult) GetListString() []string { + var list []string + vo := reflect.ValueOf(i.id) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() == reflect.Slice || vo.Kind() == reflect.Array { + for i := 0; i < vo.Len(); i++ { + val := vo.Index(i).Interface() + valVo := reflect.ValueOf(val) + if valVo.Kind() == reflect.Ptr { + valVo = valVo.Elem() + } + if valVo.Kind() == reflect.String { + list = append(list, valVo.String()) + } + } + return list + } + return nil +} + +type InsertScope struct { + scope *Scope + cw *ctxWrap + err error + filter interface{} + action insertAction + insertObject interface{} + oopts *options.InsertOneOptions + mopts *options.InsertManyOptions + oResult *mongo.InsertOneResult + mResult *mongo.InsertManyResult +} + +// SetContext 设置上下文 +func (is *InsertScope) SetContext(ctx context.Context) *InsertScope { + if is.cw == nil { + is.cw = &ctxWrap{} + } + return is +} + +func (is *InsertScope) getContext() context.Context { + return is.cw.ctx +} + +func (is *InsertScope) doClear() { + if is.cw != nil && is.cw.cancel != nil { + is.cw.cancel() + } + is.scope.debug = false + is.scope.execT = 0 +} + +// SetInsertOneOption 设置插InsertOneOption +func (is *InsertScope) SetInsertOneOption(opts options.InsertOneOptions) *InsertScope { + is.oopts = &opts + return is +} + +// SetInsertManyOption 设置InsertManyOption +func (is *InsertScope) SetInsertManyOption(opts options.InsertManyOptions) *InsertScope { + is.mopts = &opts + return is +} + +// SetFilter 设置过滤条件 +func (is *InsertScope) SetFilter(filter interface{}) *InsertScope { + if filter == nil { + is.filter = bson.M{} + return is + } + + v := reflect.ValueOf(filter) + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Map || v.Kind() == reflect.Slice { + if v.IsNil() { + is.filter = bson.M{} + } + } + is.filter = filter + return is +} + +// preCheck 预检查 +func (is *InsertScope) preCheck() { + var breakerTTL time.Duration + if is.scope.breaker == nil { + breakerTTL = defaultBreakerTime + } else if is.scope.breaker.ttl == 0 { + breakerTTL = defaultBreakerTime + } else { + breakerTTL = is.scope.breaker.ttl + } + if is.cw == nil { + is.cw = &ctxWrap{} + } + if is.cw.ctx == nil { + is.cw.ctx, is.cw.cancel = context.WithTimeout(context.Background(), breakerTTL) + } +} + +func (is *InsertScope) assertErr() { + if is.err == nil { + return + } + if errors.Is(is.err, context.DeadlineExceeded) { + is.err = &ErrRequestBroken + return + } + err, ok := is.err.(mongo.CommandError) + if !ok { + return + } + if err.HasErrorMessage(context.DeadlineExceeded.Error()) { + is.err = &ErrRequestBroken + } +} + +func (is *InsertScope) debug() { + if !is.scope.debug && !is.scope.debugWhenError { + return + } + + debugger := &Debugger{ + collection: is.scope.tableName, + execT: is.scope.execT, + action: is, + } + + // 当错误时优先输出 + if is.scope.debugWhenError { + if is.err != nil { + debugger.errMsg = is.err.Error() + debugger.ErrorString() + } + return + } + + // 所有bug输出 + if is.scope.debug { + debugger.String() + } +} + +func (is *InsertScope) doString() string { + builder := RegisterTimestampCodec(nil).Build() + switch is.action { + case insertOne: + b, _ := bson.MarshalExtJSONWithRegistry(builder, is.insertObject, true, true) + return fmt.Sprintf("insertOne(%s)", string(b)) + case insertMany: + vo := reflect.ValueOf(is.insertObject) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() != reflect.Slice { + panic("insertMany object is not slice") + } + + var data []interface{} + for i := 0; i < vo.Len(); i++ { + var body interface{} + b, _ := bson.MarshalExtJSONWithRegistry(builder, vo.Index(i).Interface(), true, true) + _ = jsoniter.Unmarshal(b, &body) + data = append(data, body) + } + b, _ := jsoniter.Marshal(data) + return fmt.Sprintf("insertMany(%s)", string(b)) + default: + panic("not support insert type") + } +} + +func (is *InsertScope) doInsert(obj interface{}) { + var starTime time.Time + if is.scope.debug { + starTime = time.Now() + } + is.oResult, is.err = db.Collection(is.scope.tableName).InsertOne(is.getContext(), obj, is.oopts) + is.assertErr() + + if is.scope.debug { + is.action = insertOne + is.insertObject = obj + is.scope.execT = time.Since(starTime) + is.debug() + } +} + +func (is *InsertScope) doManyInsert(obj []interface{}) { + defer is.assertErr() + var starTime time.Time + if is.scope.debug { + starTime = time.Now() + } + is.mResult, is.err = db.Collection(is.scope.tableName).InsertMany(is.getContext(), obj, is.mopts) + is.assertErr() + + if is.scope.debug { + is.action = insertMany + is.insertObject = obj + is.scope.execT = time.Since(starTime) + is.debug() + } +} + +// One 插入一个对象 返回的id通过InsertResult中的方法进行获取 +func (is *InsertScope) One(obj interface{}) (id *InsertResult, err error) { + defer is.doClear() + is.preCheck() + + is.doInsert(obj) + + if is.err != nil { + return nil, is.err + } + + if is.oResult == nil { + return nil, fmt.Errorf("insert success but result empty") + } + + return &InsertResult{id: is.oResult.InsertedID}, nil +} + +// Many 插入多个对象 返回的id通过InsertResult中的方法进行获取 +func (is *InsertScope) Many(obj []interface{}) (ids *InsertResult, err error) { + defer is.doClear() + is.preCheck() + + is.doManyInsert(obj) + + if is.err != nil { + return nil, is.err + } + + if is.mResult == nil { + return nil, fmt.Errorf("insert success but result empty") + } + + return &InsertResult{id: is.mResult.InsertedIDs}, nil +} diff --git a/insert_scope_test.go b/insert_scope_test.go new file mode 100644 index 0000000..0572f6a --- /dev/null +++ b/insert_scope_test.go @@ -0,0 +1,67 @@ +package mdbc + +import ( + "testing" + + "github.com/sirupsen/logrus" +) + +func TestInsertScope_One(t *testing.T) { + cfg := &Config{ + URI: "mongodb://mdbc:mdbc@10.0.0.135:27117/admin", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + Init(client).InitDB(client.Database("mdbc")) + + var m = NewModel(&ModelSchedTask{}) + + var record = ModelSchedTask{} + + record.TaskState = uint32(TaskState_TaskStateCompleted) + record.Id = "insertffddddfdfdsdnkodsanfkasdf" + + res, err := m.SetDebug(true).Insert().One(&record) + if err != nil { + panic(err) + } + logrus.Infof("res: %+v", res.GetString()) +} + +func TestInsertScope_Many(t *testing.T) { + cfg := &Config{ + URI: "mongodb://admin:admin@10.0.0.135:27017/admin", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + Init(client).InitDB(client.Database("mdbc")) + + var m = NewModel(&ModelSchedTask{}) + + var list = []interface{}{ + &ModelSchedTask{ + Id: "insertManyaaaajkfnjaksdf", + }, + &ModelSchedTask{ + Id: "insertManybbbwdsfsadnasjkf", + }, + } + + res, err := m.SetDebug(true).Insert().Many(list) + if err != nil { + panic(err) + } + logrus.Infof("res: %+v", res.GetListString()) +} diff --git a/mdbc.pb.go b/mdbc.pb.go new file mode 100644 index 0000000..ed2327b --- /dev/null +++ b/mdbc.pb.go @@ -0,0 +1,3086 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.18.1 +// source: mdbc.proto + +package mdbc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type AdminType int32 + +const ( + // @desc: 普通用户 + AdminType_AdminTypeNil AdminType = 0 + // @desc: 管理员 + AdminType_AdminTypeAdmin AdminType = 1 + // @desc: 群主 + AdminType_AdminTypeOwner AdminType = 2 +) + +// Enum value maps for AdminType. +var ( + AdminType_name = map[int32]string{ + 0: "AdminTypeNil", + 1: "AdminTypeAdmin", + 2: "AdminTypeOwner", + } + AdminType_value = map[string]int32{ + "AdminTypeNil": 0, + "AdminTypeAdmin": 1, + "AdminTypeOwner": 2, + } +) + +func (x AdminType) Enum() *AdminType { + p := new(AdminType) + *p = x + return p +} + +func (x AdminType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AdminType) Descriptor() protoreflect.EnumDescriptor { + return file_mdbc_proto_enumTypes[0].Descriptor() +} + +func (AdminType) Type() protoreflect.EnumType { + return &file_mdbc_proto_enumTypes[0] +} + +func (x AdminType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AdminType.Descriptor instead. +func (AdminType) EnumDescriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{0} +} + +type TaskState int32 + +const ( + TaskState_TaskStateNil TaskState = 0 //已初始化 + TaskState_TaskStateRunning TaskState = 1 //运行中 + TaskState_TaskStateFailed TaskState = 2 //失败退出 + TaskState_TaskStateCompleted TaskState = 3 //完成 +) + +// Enum value maps for TaskState. +var ( + TaskState_name = map[int32]string{ + 0: "TaskStateNil", + 1: "TaskStateRunning", + 2: "TaskStateFailed", + 3: "TaskStateCompleted", + } + TaskState_value = map[string]int32{ + "TaskStateNil": 0, + "TaskStateRunning": 1, + "TaskStateFailed": 2, + "TaskStateCompleted": 3, + } +) + +func (x TaskState) Enum() *TaskState { + p := new(TaskState) + *p = x + return p +} + +func (x TaskState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TaskState) Descriptor() protoreflect.EnumDescriptor { + return file_mdbc_proto_enumTypes[1].Descriptor() +} + +func (TaskState) Type() protoreflect.EnumType { + return &file_mdbc_proto_enumTypes[1] +} + +func (x TaskState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TaskState.Descriptor instead. +func (TaskState) EnumDescriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{1} +} + +// @table_name: tb_friends_info +type ModelFriendInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` // 主键ID wxid md5 + //@gotags: bson:"wechat_id" + WechatId string `protobuf:"bytes,2,opt,name=wechat_id,json=wechatId,proto3" json:"wechat_id,omitempty" bson:"wechat_id"` // 用户微信ID + //@gotags: bson:"nick_name" + Nickname string `protobuf:"bytes,3,opt,name=nickname,proto3" json:"nickname,omitempty" bson:"nick_name"` // 用户暱称 + //@gotags: bson:"wechat_alias" + WechatAlias string `protobuf:"bytes,4,opt,name=wechat_alias,json=wechatAlias,proto3" json:"wechat_alias,omitempty" bson:"wechat_alias"` // 用户微信号 + //@gotags: bson:"avatar_url" + AvatarUrl string `protobuf:"bytes,5,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url,omitempty" bson:"avatar_url"` // 用户头像 + //@gotags: bson:"phone" + Phone string `protobuf:"bytes,6,opt,name=phone,proto3" json:"phone,omitempty" bson:"phone"` // 手机号码 + //@gotags: bson:"country" + Country string `protobuf:"bytes,7,opt,name=country,proto3" json:"country,omitempty" bson:"country"` // 国家 + //@gotags: bson:"province" + Province string `protobuf:"bytes,8,opt,name=province,proto3" json:"province,omitempty" bson:"province"` // 省份 + //@gotags: bson:"city" + City string `protobuf:"bytes,9,opt,name=city,proto3" json:"city,omitempty" bson:"city"` // 城市 + //@gotags: bson:"sex" + Sex int32 `protobuf:"varint,10,opt,name=sex,proto3" json:"sex,omitempty" bson:"sex"` // 0未知 1男 2女 + //@gotags: bson:"create_time" + CreateTime int64 `protobuf:"varint,12,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" bson:"create_time"` // 创建时间 + //@gotags: bson:"update_time" + UpdateTime int64 `protobuf:"varint,13,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" bson:"update_time"` // 更新时间 +} + +func (x *ModelFriendInfo) Reset() { + *x = ModelFriendInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelFriendInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelFriendInfo) ProtoMessage() {} + +func (x *ModelFriendInfo) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelFriendInfo.ProtoReflect.Descriptor instead. +func (*ModelFriendInfo) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{0} +} + +func (x *ModelFriendInfo) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelFriendInfo) GetWechatId() string { + if x != nil { + return x.WechatId + } + return "" +} + +func (x *ModelFriendInfo) GetNickname() string { + if x != nil { + return x.Nickname + } + return "" +} + +func (x *ModelFriendInfo) GetWechatAlias() string { + if x != nil { + return x.WechatAlias + } + return "" +} + +func (x *ModelFriendInfo) GetAvatarUrl() string { + if x != nil { + return x.AvatarUrl + } + return "" +} + +func (x *ModelFriendInfo) GetPhone() string { + if x != nil { + return x.Phone + } + return "" +} + +func (x *ModelFriendInfo) GetCountry() string { + if x != nil { + return x.Country + } + return "" +} + +func (x *ModelFriendInfo) GetProvince() string { + if x != nil { + return x.Province + } + return "" +} + +func (x *ModelFriendInfo) GetCity() string { + if x != nil { + return x.City + } + return "" +} + +func (x *ModelFriendInfo) GetSex() int32 { + if x != nil { + return x.Sex + } + return 0 +} + +func (x *ModelFriendInfo) GetCreateTime() int64 { + if x != nil { + return x.CreateTime + } + return 0 +} + +func (x *ModelFriendInfo) GetUpdateTime() int64 { + if x != nil { + return x.UpdateTime + } + return 0 +} + +// @table_name: tb_crm_group_chat +type ModelGroupChat struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` // 主键ID + //@gotags: bson:"created_at" + CreatedAt int64 `protobuf:"varint,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty" bson:"created_at"` // 创建时间 + //@gotags: bson:"updated_at" + UpdatedAt int64 `protobuf:"varint,3,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty" bson:"updated_at"` // 更新时间 + //@gotags: bson:"deleted_at" + DeletedAt int64 `protobuf:"varint,4,opt,name=deleted_at,json=deletedAt,proto3" json:"deleted_at,omitempty" bson:"deleted_at"` // 删除时间【记: 此表正常情况下 只进行软删除】非零 历史群 0正常群 + //@gotags: bson:"robot_wx_id" + RobotWxId string `protobuf:"bytes,6,opt,name=robot_wx_id,json=robotWxId,proto3" json:"robot_wx_id,omitempty" bson:"robot_wx_id"` // 机器人id + //@gotags: bson:"group_wx_id" + GroupWxId string `protobuf:"bytes,7,opt,name=group_wx_id,json=groupWxId,proto3" json:"group_wx_id,omitempty" bson:"group_wx_id"` // 群id + //@gotags: bson:"owner_wx_id" + OwnerWxId string `protobuf:"bytes,8,opt,name=owner_wx_id,json=ownerWxId,proto3" json:"owner_wx_id,omitempty" bson:"owner_wx_id"` // 群主id + //@gotags: bson:"group_name" + GroupName string `protobuf:"bytes,9,opt,name=group_name,json=groupName,proto3" json:"group_name,omitempty" bson:"group_name"` // 群名称 + //@gotags: bson:"member_count" + MemberCount uint32 `protobuf:"varint,10,opt,name=member_count,json=memberCount,proto3" json:"member_count,omitempty" bson:"member_count"` // 群成员数量 + //@gotags: bson:"owner_name" + OwnerName string `protobuf:"bytes,11,opt,name=owner_name,json=ownerName,proto3" json:"owner_name,omitempty" bson:"owner_name"` // 群主名称 + //@gotags: bson:"group_avatar_url" + GroupAvatarUrl string `protobuf:"bytes,12,opt,name=group_avatar_url,json=groupAvatarUrl,proto3" json:"group_avatar_url,omitempty" bson:"group_avatar_url"` // 群头像 + //@gotags: bson:"is_watch" + IsWatch bool `protobuf:"varint,13,opt,name=is_watch,json=isWatch,proto3" json:"is_watch,omitempty" bson:"is_watch"` // 是否关注群 + //@gotags: bson:"has_been_watch" + HasBeenWatch bool `protobuf:"varint,14,opt,name=has_been_watch,json=hasBeenWatch,proto3" json:"has_been_watch,omitempty" bson:"has_been_watch"` // 以前有关注过 + //@gotags: bson:"is_default_group_name" + IsDefaultGroupName bool `protobuf:"varint,15,opt,name=is_default_group_name,json=isDefaultGroupName,proto3" json:"is_default_group_name,omitempty" bson:"is_default_group_name"` // 是否是默认的群名称 + //@gotags: bson:"in_contact" + InContact bool `protobuf:"varint,16,opt,name=in_contact,json=inContact,proto3" json:"in_contact,omitempty" bson:"in_contact"` // 是否在通讯录中 + //@gotags: bson:"disable_invite" + DisableInvite bool `protobuf:"varint,17,opt,name=disable_invite,json=disableInvite,proto3" json:"disable_invite,omitempty" bson:"disable_invite"` // 是否开启了群聊邀请确认 true 开启了 false 关闭了 + //@gotags: bson:"last_sync_at" + LastSyncAt int64 `protobuf:"varint,20,opt,name=last_sync_at,json=lastSyncAt,proto3" json:"last_sync_at,omitempty" bson:"last_sync_at"` // 最后更新群信息时间 【通过这里 指定规则 去拉群基本信息】 + //@gotags: bson:"last_sync_member_at" + LastSyncMemberAt int64 `protobuf:"varint,21,opt,name=last_sync_member_at,json=lastSyncMemberAt,proto3" json:"last_sync_member_at,omitempty" bson:"last_sync_member_at"` // 最后更新群成员时间 【通过这里 指定规则 去拉群成员信息】 + //@gotags: bson:"notice" + Notice string `protobuf:"bytes,22,opt,name=notice,proto3" json:"notice,omitempty" bson:"notice"` // 群公告 + //@gotags: bson:"qrcode_updated_at" + QrcodeUpdatedAt int64 `protobuf:"varint,23,opt,name=qrcode_updated_at,json=qrcodeUpdatedAt,proto3" json:"qrcode_updated_at,omitempty" bson:"qrcode_updated_at"` // 群聊二维码更新时间 + //@gotags: bson:"qrcode_url" + QrcodeUrl string `protobuf:"bytes,24,opt,name=qrcode_url,json=qrcodeUrl,proto3" json:"qrcode_url,omitempty" bson:"qrcode_url"` // 群聊二维码 + //@gotags: bson:"admin_type" + AdminType AdminType `protobuf:"varint,25,opt,name=admin_type,json=adminType,proto3,enum=mdbc.AdminType" json:"admin_type,omitempty" bson:"admin_type"` // 机器人权限类型 +} + +func (x *ModelGroupChat) Reset() { + *x = ModelGroupChat{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelGroupChat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelGroupChat) ProtoMessage() {} + +func (x *ModelGroupChat) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelGroupChat.ProtoReflect.Descriptor instead. +func (*ModelGroupChat) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{1} +} + +func (x *ModelGroupChat) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelGroupChat) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *ModelGroupChat) GetUpdatedAt() int64 { + if x != nil { + return x.UpdatedAt + } + return 0 +} + +func (x *ModelGroupChat) GetDeletedAt() int64 { + if x != nil { + return x.DeletedAt + } + return 0 +} + +func (x *ModelGroupChat) GetRobotWxId() string { + if x != nil { + return x.RobotWxId + } + return "" +} + +func (x *ModelGroupChat) GetGroupWxId() string { + if x != nil { + return x.GroupWxId + } + return "" +} + +func (x *ModelGroupChat) GetOwnerWxId() string { + if x != nil { + return x.OwnerWxId + } + return "" +} + +func (x *ModelGroupChat) GetGroupName() string { + if x != nil { + return x.GroupName + } + return "" +} + +func (x *ModelGroupChat) GetMemberCount() uint32 { + if x != nil { + return x.MemberCount + } + return 0 +} + +func (x *ModelGroupChat) GetOwnerName() string { + if x != nil { + return x.OwnerName + } + return "" +} + +func (x *ModelGroupChat) GetGroupAvatarUrl() string { + if x != nil { + return x.GroupAvatarUrl + } + return "" +} + +func (x *ModelGroupChat) GetIsWatch() bool { + if x != nil { + return x.IsWatch + } + return false +} + +func (x *ModelGroupChat) GetHasBeenWatch() bool { + if x != nil { + return x.HasBeenWatch + } + return false +} + +func (x *ModelGroupChat) GetIsDefaultGroupName() bool { + if x != nil { + return x.IsDefaultGroupName + } + return false +} + +func (x *ModelGroupChat) GetInContact() bool { + if x != nil { + return x.InContact + } + return false +} + +func (x *ModelGroupChat) GetDisableInvite() bool { + if x != nil { + return x.DisableInvite + } + return false +} + +func (x *ModelGroupChat) GetLastSyncAt() int64 { + if x != nil { + return x.LastSyncAt + } + return 0 +} + +func (x *ModelGroupChat) GetLastSyncMemberAt() int64 { + if x != nil { + return x.LastSyncMemberAt + } + return 0 +} + +func (x *ModelGroupChat) GetNotice() string { + if x != nil { + return x.Notice + } + return "" +} + +func (x *ModelGroupChat) GetQrcodeUpdatedAt() int64 { + if x != nil { + return x.QrcodeUpdatedAt + } + return 0 +} + +func (x *ModelGroupChat) GetQrcodeUrl() string { + if x != nil { + return x.QrcodeUrl + } + return "" +} + +func (x *ModelGroupChat) GetAdminType() AdminType { + if x != nil { + return x.AdminType + } + return AdminType_AdminTypeNil +} + +// @table_name: tb_crm_group_chat_member +type ModelGroupChatMember struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` // id + //@gotags: bson:"created_at" + CreatedAt int64 `protobuf:"varint,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty" bson:"created_at"` // 创建时间 + //@gotags: bson:"updated_at" + UpdatedAt int64 `protobuf:"varint,3,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty" bson:"updated_at"` // 更新时间 + //@gotags: bson:"deleted_at" + DeletedAt int64 `protobuf:"varint,4,opt,name=deleted_at,json=deletedAt,proto3" json:"deleted_at,omitempty" bson:"deleted_at"` // 删除时间 这个表一般直接硬删除 + //@gotags: bson:"group_chat_id" + GroupChatId string `protobuf:"bytes,5,opt,name=group_chat_id,json=groupChatId,proto3" json:"group_chat_id,omitempty" bson:"group_chat_id"` // 群 ModelGroupChat 的ID + //@gotags: bson:"member_wx_id" + MemberWxId string `protobuf:"bytes,6,opt,name=member_wx_id,json=memberWxId,proto3" json:"member_wx_id,omitempty" bson:"member_wx_id"` // 群成员微信id + //@gotags: bson:"member_name" + MemberName string `protobuf:"bytes,7,opt,name=member_name,json=memberName,proto3" json:"member_name,omitempty" bson:"member_name"` // 群成员名称 + //@gotags: bson:"member_avatar" + MemberAvatar string `protobuf:"bytes,8,opt,name=member_avatar,json=memberAvatar,proto3" json:"member_avatar,omitempty" bson:"member_avatar"` // 群成员头像 + //@gotags: bson:"member_alias" + MemberAlias string `protobuf:"bytes,9,opt,name=member_alias,json=memberAlias,proto3" json:"member_alias,omitempty" bson:"member_alias"` // 群昵称 + //@gotags: bson:"member_sex" + MemberSex uint32 `protobuf:"varint,10,opt,name=member_sex,json=memberSex,proto3" json:"member_sex,omitempty" bson:"member_sex"` // 性别 + //@gotags: bson:"is_robot" + IsRobot bool `protobuf:"varint,11,opt,name=is_robot,json=isRobot,proto3" json:"is_robot,omitempty" bson:"is_robot"` // 是否是机器人 + //@gotags: bson:"admin_type" + AdminType AdminType `protobuf:"varint,12,opt,name=admin_type,json=adminType,proto3,enum=mdbc.AdminType" json:"admin_type,omitempty" bson:"admin_type"` // 权限类型 群主 管理员 普通成员 + //@gotags: bson:"last_sync_at" + LastSyncAt int64 `protobuf:"varint,13,opt,name=last_sync_at,json=lastSyncAt,proto3" json:"last_sync_at,omitempty" bson:"last_sync_at"` // 该群该成员 最后更新时间 +} + +func (x *ModelGroupChatMember) Reset() { + *x = ModelGroupChatMember{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelGroupChatMember) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelGroupChatMember) ProtoMessage() {} + +func (x *ModelGroupChatMember) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelGroupChatMember.ProtoReflect.Descriptor instead. +func (*ModelGroupChatMember) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{2} +} + +func (x *ModelGroupChatMember) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelGroupChatMember) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *ModelGroupChatMember) GetUpdatedAt() int64 { + if x != nil { + return x.UpdatedAt + } + return 0 +} + +func (x *ModelGroupChatMember) GetDeletedAt() int64 { + if x != nil { + return x.DeletedAt + } + return 0 +} + +func (x *ModelGroupChatMember) GetGroupChatId() string { + if x != nil { + return x.GroupChatId + } + return "" +} + +func (x *ModelGroupChatMember) GetMemberWxId() string { + if x != nil { + return x.MemberWxId + } + return "" +} + +func (x *ModelGroupChatMember) GetMemberName() string { + if x != nil { + return x.MemberName + } + return "" +} + +func (x *ModelGroupChatMember) GetMemberAvatar() string { + if x != nil { + return x.MemberAvatar + } + return "" +} + +func (x *ModelGroupChatMember) GetMemberAlias() string { + if x != nil { + return x.MemberAlias + } + return "" +} + +func (x *ModelGroupChatMember) GetMemberSex() uint32 { + if x != nil { + return x.MemberSex + } + return 0 +} + +func (x *ModelGroupChatMember) GetIsRobot() bool { + if x != nil { + return x.IsRobot + } + return false +} + +func (x *ModelGroupChatMember) GetAdminType() AdminType { + if x != nil { + return x.AdminType + } + return AdminType_AdminTypeNil +} + +func (x *ModelGroupChatMember) GetLastSyncAt() int64 { + if x != nil { + return x.LastSyncAt + } + return 0 +} + +// @table_name: tb_crm_private_msg_session +type ModelTbPrivateMsgSession struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` //会话ID (md5(机器人id+好友id)) + //@gotags: bson:"all" + All int32 `protobuf:"varint,2,opt,name=all,proto3" json:"all,omitempty" bson:"all"` //消息最大游标(消息总数:只算有效的消息) + //@gotags: bson:"read" + Read int32 `protobuf:"varint,3,opt,name=read,proto3" json:"read,omitempty" bson:"read"` //已读游标 + //@gotags: bson:"unread" + Unread int32 `protobuf:"varint,4,opt,name=unread,proto3" json:"unread,omitempty" bson:"unread"` //未读消息游标 + //@gotags: bson:"last_msg_at" + LastMsgAt int64 `protobuf:"varint,5,opt,name=last_msg_at,json=lastMsgAt,proto3" json:"last_msg_at,omitempty" bson:"last_msg_at"` //最后一条消息时间 + //@gotags: bson:"last_friend_msg_at" + LastFriendMsgAt int64 `protobuf:"varint,6,opt,name=last_friend_msg_at,json=lastFriendMsgAt,proto3" json:"last_friend_msg_at,omitempty" bson:"last_friend_msg_at"` //接受到最后一条好友消息时间 + //@gotags: bson:"robot_wx_id" + RobotWxId string `protobuf:"bytes,7,opt,name=robot_wx_id,json=robotWxId,proto3" json:"robot_wx_id,omitempty" bson:"robot_wx_id"` //机器人id + //@gotags: bson:"user_wx_id" + UserWxId string `protobuf:"bytes,8,opt,name=user_wx_id,json=userWxId,proto3" json:"user_wx_id,omitempty" bson:"user_wx_id"` //好友微信id + //@gotags: bson:"last_msg_id" + LastMsgId string `protobuf:"bytes,9,opt,name=last_msg_id,json=lastMsgId,proto3" json:"last_msg_id,omitempty" bson:"last_msg_id"` //最后一条消息id + //@gotags: bson:"last_friend_msg_id" + LastFriendMsgId string `protobuf:"bytes,10,opt,name=last_friend_msg_id,json=lastFriendMsgId,proto3" json:"last_friend_msg_id,omitempty" bson:"last_friend_msg_id"` //接收的最后一条好友消息id +} + +func (x *ModelTbPrivateMsgSession) Reset() { + *x = ModelTbPrivateMsgSession{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelTbPrivateMsgSession) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelTbPrivateMsgSession) ProtoMessage() {} + +func (x *ModelTbPrivateMsgSession) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelTbPrivateMsgSession.ProtoReflect.Descriptor instead. +func (*ModelTbPrivateMsgSession) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{3} +} + +func (x *ModelTbPrivateMsgSession) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelTbPrivateMsgSession) GetAll() int32 { + if x != nil { + return x.All + } + return 0 +} + +func (x *ModelTbPrivateMsgSession) GetRead() int32 { + if x != nil { + return x.Read + } + return 0 +} + +func (x *ModelTbPrivateMsgSession) GetUnread() int32 { + if x != nil { + return x.Unread + } + return 0 +} + +func (x *ModelTbPrivateMsgSession) GetLastMsgAt() int64 { + if x != nil { + return x.LastMsgAt + } + return 0 +} + +func (x *ModelTbPrivateMsgSession) GetLastFriendMsgAt() int64 { + if x != nil { + return x.LastFriendMsgAt + } + return 0 +} + +func (x *ModelTbPrivateMsgSession) GetRobotWxId() string { + if x != nil { + return x.RobotWxId + } + return "" +} + +func (x *ModelTbPrivateMsgSession) GetUserWxId() string { + if x != nil { + return x.UserWxId + } + return "" +} + +func (x *ModelTbPrivateMsgSession) GetLastMsgId() string { + if x != nil { + return x.LastMsgId + } + return "" +} + +func (x *ModelTbPrivateMsgSession) GetLastFriendMsgId() string { + if x != nil { + return x.LastFriendMsgId + } + return "" +} + +// @table_name: tb_crm_group_msg_session +type ModelTbGroupMsgSession struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` //会话ID (md5(机器人id+好友id)) + //@gotags: bson:"all" + All int32 `protobuf:"varint,2,opt,name=all,proto3" json:"all,omitempty" bson:"all"` //消息最大游标(消息总数:只算有效的消息) + //@gotags: bson:"read" + Read int32 `protobuf:"varint,3,opt,name=read,proto3" json:"read,omitempty" bson:"read"` //已读游标 + //@gotags: bson:"unread" + Unread int32 `protobuf:"varint,4,opt,name=unread,proto3" json:"unread,omitempty" bson:"unread"` //未读消息游标 + //@gotags: bson:"last_msg_at" + LastMsgAt int64 `protobuf:"varint,5,opt,name=last_msg_at,json=lastMsgAt,proto3" json:"last_msg_at,omitempty" bson:"last_msg_at"` //最后一条消息时间 + //@gotags: bson:"last_friend_msg_at" + LastFriendMsgAt int64 `protobuf:"varint,6,opt,name=last_friend_msg_at,json=lastFriendMsgAt,proto3" json:"last_friend_msg_at,omitempty" bson:"last_friend_msg_at"` //接受到最后一条好友消息时间 + //@gotags: bson:"robot_wx_id" + RobotWxId string `protobuf:"bytes,7,opt,name=robot_wx_id,json=robotWxId,proto3" json:"robot_wx_id,omitempty" bson:"robot_wx_id"` //机器人id + //@gotags: bson:"user_wx_id" + UserWxId string `protobuf:"bytes,8,opt,name=user_wx_id,json=userWxId,proto3" json:"user_wx_id,omitempty" bson:"user_wx_id"` //群微信id + //@gotags: bson:"last_msg_id" + LastMsgId string `protobuf:"bytes,9,opt,name=last_msg_id,json=lastMsgId,proto3" json:"last_msg_id,omitempty" bson:"last_msg_id"` //最后一条消息id + //@gotags: bson:"last_friend_msg_id" + LastFriendMsgId string `protobuf:"bytes,10,opt,name=last_friend_msg_id,json=lastFriendMsgId,proto3" json:"last_friend_msg_id,omitempty" bson:"last_friend_msg_id"` //接收的最后一条好友消息id + //@gotags: bson:"last_member_wx_id" + LastMemberWxId string `protobuf:"bytes,11,opt,name=last_member_wx_id,json=lastMemberWxId,proto3" json:"last_member_wx_id,omitempty" bson:"last_member_wx_id"` //最后发送消息的群成员id +} + +func (x *ModelTbGroupMsgSession) Reset() { + *x = ModelTbGroupMsgSession{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelTbGroupMsgSession) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelTbGroupMsgSession) ProtoMessage() {} + +func (x *ModelTbGroupMsgSession) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelTbGroupMsgSession.ProtoReflect.Descriptor instead. +func (*ModelTbGroupMsgSession) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{4} +} + +func (x *ModelTbGroupMsgSession) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelTbGroupMsgSession) GetAll() int32 { + if x != nil { + return x.All + } + return 0 +} + +func (x *ModelTbGroupMsgSession) GetRead() int32 { + if x != nil { + return x.Read + } + return 0 +} + +func (x *ModelTbGroupMsgSession) GetUnread() int32 { + if x != nil { + return x.Unread + } + return 0 +} + +func (x *ModelTbGroupMsgSession) GetLastMsgAt() int64 { + if x != nil { + return x.LastMsgAt + } + return 0 +} + +func (x *ModelTbGroupMsgSession) GetLastFriendMsgAt() int64 { + if x != nil { + return x.LastFriendMsgAt + } + return 0 +} + +func (x *ModelTbGroupMsgSession) GetRobotWxId() string { + if x != nil { + return x.RobotWxId + } + return "" +} + +func (x *ModelTbGroupMsgSession) GetUserWxId() string { + if x != nil { + return x.UserWxId + } + return "" +} + +func (x *ModelTbGroupMsgSession) GetLastMsgId() string { + if x != nil { + return x.LastMsgId + } + return "" +} + +func (x *ModelTbGroupMsgSession) GetLastFriendMsgId() string { + if x != nil { + return x.LastFriendMsgId + } + return "" +} + +func (x *ModelTbGroupMsgSession) GetLastMemberWxId() string { + if x != nil { + return x.LastMemberWxId + } + return "" +} + +// @table_name: tb_crm_robot_private_msg +type ModelTbRobotPrivateMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` // 主键ID + //@gotags: bson:"bind_id" + BindId string `protobuf:"bytes,3,opt,name=bind_id,json=bindId,proto3" json:"bind_id,omitempty" bson:"bind_id"` // 前端消息id + //@gotags: bson:"robot_wx_id" + RobotWxId string `protobuf:"bytes,4,opt,name=robot_wx_id,json=robotWxId,proto3" json:"robot_wx_id,omitempty" bson:"robot_wx_id"` // 机器人id + //@gotags: bson:"user_wx_id" + UserWxId string `protobuf:"bytes,5,opt,name=user_wx_id,json=userWxId,proto3" json:"user_wx_id,omitempty" bson:"user_wx_id"` // 好友id + //@gotags: bson:"msg_id" + MsgId string `protobuf:"bytes,6,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty" bson:"msg_id"` // 服务端自己生成一个消息id,来对应客户端的发送结果id + //@gotags: bson:"msg_type" + MsgType int32 `protobuf:"varint,7,opt,name=msg_type,json=msgType,proto3" json:"msg_type,omitempty" bson:"msg_type"` // 消息类型 + //@gotags: bson:"send_status" + SendStatus int32 `protobuf:"varint,8,opt,name=send_status,json=sendStatus,proto3" json:"send_status,omitempty" bson:"send_status"` // 发送状态:0:发送中;1:发送请求成功;2:发送请求失败;3:发送成功;4:发送失败;仅机器人发送。接收到用户消息的默认3 + //@gotags: bson:"direct" + Direct int32 `protobuf:"varint,9,opt,name=direct,proto3" json:"direct,omitempty" bson:"direct"` // 用于区分机器人是接收方还是发送方。1:机器人接收;2:机器人发送 + //@gotags: bson:"send_error_code" + SendErrorCode int32 `protobuf:"varint,10,opt,name=send_error_code,json=sendErrorCode,proto3" json:"send_error_code,omitempty" bson:"send_error_code"` // 发送错误码:用户告诉对应的是什么错误:-1 通用错误码; -2 被拉黑; -3 + // 被删除; -4 好友找不到; + //@gotags: bson:"content_read" + ContentRead bool `protobuf:"varint,12,opt,name=content_read,json=contentRead,proto3" json:"content_read,omitempty" bson:"content_read"` // 是否内容被浏览(像语音之类的,需要浏览) + //@gotags: bson:"created_at" + CreatedAt int64 `protobuf:"varint,13,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty" bson:"created_at"` // 创建时间 + //@gotags: bson:"updated_at" + UpdatedAt int64 `protobuf:"varint,14,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty" bson:"updated_at"` // 更新时间 + //@gotags: bson:"fail_reason" + FailReason string `protobuf:"bytes,15,opt,name=fail_reason,json=failReason,proto3" json:"fail_reason,omitempty" bson:"fail_reason"` // 失败原因 + //@gotags: bson:"call_back_at" + CallBackAt int64 `protobuf:"varint,16,opt,name=call_back_at,json=callBackAt,proto3" json:"call_back_at,omitempty" bson:"call_back_at"` // 消息返回时间 + //@gotags: bson:"cursor" + Cursor int64 `protobuf:"varint,17,opt,name=cursor,proto3" json:"cursor,omitempty" bson:"cursor"` // 消息游标(对应session的all) + //@gotags: bson:"send_at" + SendAt int64 `protobuf:"varint,18,opt,name=send_at,json=sendAt,proto3" json:"send_at,omitempty" bson:"send_at"` // 发送时间(消息实际生效时间) + //@gotags: bson:"expire_at" + ExpireAt int64 `protobuf:"varint,19,opt,name=expire_at,json=expireAt,proto3" json:"expire_at,omitempty" bson:"expire_at"` // 失效时间(用于消息的失效) + //@gotags: bson:"content_data" + ContentData *ContentData `protobuf:"bytes,20,opt,name=content_data,json=contentData,proto3" json:"content_data,omitempty" bson:"content_data"` // 消息内容 +} + +func (x *ModelTbRobotPrivateMsg) Reset() { + *x = ModelTbRobotPrivateMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelTbRobotPrivateMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelTbRobotPrivateMsg) ProtoMessage() {} + +func (x *ModelTbRobotPrivateMsg) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelTbRobotPrivateMsg.ProtoReflect.Descriptor instead. +func (*ModelTbRobotPrivateMsg) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{5} +} + +func (x *ModelTbRobotPrivateMsg) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelTbRobotPrivateMsg) GetBindId() string { + if x != nil { + return x.BindId + } + return "" +} + +func (x *ModelTbRobotPrivateMsg) GetRobotWxId() string { + if x != nil { + return x.RobotWxId + } + return "" +} + +func (x *ModelTbRobotPrivateMsg) GetUserWxId() string { + if x != nil { + return x.UserWxId + } + return "" +} + +func (x *ModelTbRobotPrivateMsg) GetMsgId() string { + if x != nil { + return x.MsgId + } + return "" +} + +func (x *ModelTbRobotPrivateMsg) GetMsgType() int32 { + if x != nil { + return x.MsgType + } + return 0 +} + +func (x *ModelTbRobotPrivateMsg) GetSendStatus() int32 { + if x != nil { + return x.SendStatus + } + return 0 +} + +func (x *ModelTbRobotPrivateMsg) GetDirect() int32 { + if x != nil { + return x.Direct + } + return 0 +} + +func (x *ModelTbRobotPrivateMsg) GetSendErrorCode() int32 { + if x != nil { + return x.SendErrorCode + } + return 0 +} + +func (x *ModelTbRobotPrivateMsg) GetContentRead() bool { + if x != nil { + return x.ContentRead + } + return false +} + +func (x *ModelTbRobotPrivateMsg) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *ModelTbRobotPrivateMsg) GetUpdatedAt() int64 { + if x != nil { + return x.UpdatedAt + } + return 0 +} + +func (x *ModelTbRobotPrivateMsg) GetFailReason() string { + if x != nil { + return x.FailReason + } + return "" +} + +func (x *ModelTbRobotPrivateMsg) GetCallBackAt() int64 { + if x != nil { + return x.CallBackAt + } + return 0 +} + +func (x *ModelTbRobotPrivateMsg) GetCursor() int64 { + if x != nil { + return x.Cursor + } + return 0 +} + +func (x *ModelTbRobotPrivateMsg) GetSendAt() int64 { + if x != nil { + return x.SendAt + } + return 0 +} + +func (x *ModelTbRobotPrivateMsg) GetExpireAt() int64 { + if x != nil { + return x.ExpireAt + } + return 0 +} + +func (x *ModelTbRobotPrivateMsg) GetContentData() *ContentData { + if x != nil { + return x.ContentData + } + return nil +} + +// @table_name: tb_crm_robot_group_msg +type ModelTbRobotGroupMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` // 主键ID + //@gotags: bson:"bind_id" + BindId string `protobuf:"bytes,3,opt,name=bind_id,json=bindId,proto3" json:"bind_id,omitempty" bson:"bind_id"` // 前端消息id + //@gotags: bson:"robot_wx_id" + RobotWxId string `protobuf:"bytes,4,opt,name=robot_wx_id,json=robotWxId,proto3" json:"robot_wx_id,omitempty" bson:"robot_wx_id"` // 机器人id + //@gotags: bson:"user_wx_id" + UserWxId string `protobuf:"bytes,5,opt,name=user_wx_id,json=userWxId,proto3" json:"user_wx_id,omitempty" bson:"user_wx_id"` // 群聊id + //@gotags: bson:"msg_id" + MsgId string `protobuf:"bytes,6,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty" bson:"msg_id"` // 服务端自己生成一个消息id,来对应客户端的发送结果id + //@gotags: bson:"msg_type" + MsgType int32 `protobuf:"varint,7,opt,name=msg_type,json=msgType,proto3" json:"msg_type,omitempty" bson:"msg_type"` // 消息类型 + //@gotags: bson:"send_status" + SendStatus int32 `protobuf:"varint,8,opt,name=send_status,json=sendStatus,proto3" json:"send_status,omitempty" bson:"send_status"` // 发送状态:0:发送中;1:发送请求成功;2:发送请求失败;3:发送成功;4:发送失败;仅机器人发送。接收到用户消息的默认3 + //@gotags: bson:"direct" + Direct int32 `protobuf:"varint,9,opt,name=direct,proto3" json:"direct,omitempty" bson:"direct"` // 用于区分机器人是接收方还是发送方。1:机器人接收;2:机器人发送 + //@gotags: bson:"send_error_code" + SendErrorCode int32 `protobuf:"varint,10,opt,name=send_error_code,json=sendErrorCode,proto3" json:"send_error_code,omitempty" bson:"send_error_code"` // 发送错误码:用户告诉对应的是什么错误:-1 通用错误码; -2 被拉黑; -3 + // 被删除; -4 好友找不到; + //@gotags: bson:"content_read" + ContentRead bool `protobuf:"varint,12,opt,name=content_read,json=contentRead,proto3" json:"content_read,omitempty" bson:"content_read"` // 是否内容被浏览(像语音之类的,需要浏览) + //@gotags: bson:"created_at" + CreatedAt int64 `protobuf:"varint,13,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty" bson:"created_at"` // 创建时间 + //@gotags: bson:"updated_at" + UpdatedAt int64 `protobuf:"varint,14,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty" bson:"updated_at"` // 更新时间 + //@gotags: bson:"fail_reason" + FailReason string `protobuf:"bytes,15,opt,name=fail_reason,json=failReason,proto3" json:"fail_reason,omitempty" bson:"fail_reason"` // 失败原因 + //@gotags: bson:"call_back_at" + CallBackAt int64 `protobuf:"varint,16,opt,name=call_back_at,json=callBackAt,proto3" json:"call_back_at,omitempty" bson:"call_back_at"` // 消息返回时间 + //@gotags: bson:"cursor" + Cursor int64 `protobuf:"varint,17,opt,name=cursor,proto3" json:"cursor,omitempty" bson:"cursor"` // 消息游标(对应session的all) + //@gotags: bson:"send_at" + SendAt int64 `protobuf:"varint,18,opt,name=send_at,json=sendAt,proto3" json:"send_at,omitempty" bson:"send_at"` // 发送时间(消息实际生效时间) + //@gotags: bson:"expire_at" + ExpireAt int64 `protobuf:"varint,19,opt,name=expire_at,json=expireAt,proto3" json:"expire_at,omitempty" bson:"expire_at"` // 失效时间(用于消息的失效) + //@gotags: bson:"content_data" + ContentData *ContentData `protobuf:"bytes,20,opt,name=content_data,json=contentData,proto3" json:"content_data,omitempty" bson:"content_data"` // 消息内容 + //@gotags: bson:"sender_wx_id" + SenderWxId string `protobuf:"bytes,21,opt,name=sender_wx_id,json=senderWxId,proto3" json:"sender_wx_id,omitempty" bson:"sender_wx_id"` // 发送者id +} + +func (x *ModelTbRobotGroupMsg) Reset() { + *x = ModelTbRobotGroupMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelTbRobotGroupMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelTbRobotGroupMsg) ProtoMessage() {} + +func (x *ModelTbRobotGroupMsg) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelTbRobotGroupMsg.ProtoReflect.Descriptor instead. +func (*ModelTbRobotGroupMsg) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{6} +} + +func (x *ModelTbRobotGroupMsg) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelTbRobotGroupMsg) GetBindId() string { + if x != nil { + return x.BindId + } + return "" +} + +func (x *ModelTbRobotGroupMsg) GetRobotWxId() string { + if x != nil { + return x.RobotWxId + } + return "" +} + +func (x *ModelTbRobotGroupMsg) GetUserWxId() string { + if x != nil { + return x.UserWxId + } + return "" +} + +func (x *ModelTbRobotGroupMsg) GetMsgId() string { + if x != nil { + return x.MsgId + } + return "" +} + +func (x *ModelTbRobotGroupMsg) GetMsgType() int32 { + if x != nil { + return x.MsgType + } + return 0 +} + +func (x *ModelTbRobotGroupMsg) GetSendStatus() int32 { + if x != nil { + return x.SendStatus + } + return 0 +} + +func (x *ModelTbRobotGroupMsg) GetDirect() int32 { + if x != nil { + return x.Direct + } + return 0 +} + +func (x *ModelTbRobotGroupMsg) GetSendErrorCode() int32 { + if x != nil { + return x.SendErrorCode + } + return 0 +} + +func (x *ModelTbRobotGroupMsg) GetContentRead() bool { + if x != nil { + return x.ContentRead + } + return false +} + +func (x *ModelTbRobotGroupMsg) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *ModelTbRobotGroupMsg) GetUpdatedAt() int64 { + if x != nil { + return x.UpdatedAt + } + return 0 +} + +func (x *ModelTbRobotGroupMsg) GetFailReason() string { + if x != nil { + return x.FailReason + } + return "" +} + +func (x *ModelTbRobotGroupMsg) GetCallBackAt() int64 { + if x != nil { + return x.CallBackAt + } + return 0 +} + +func (x *ModelTbRobotGroupMsg) GetCursor() int64 { + if x != nil { + return x.Cursor + } + return 0 +} + +func (x *ModelTbRobotGroupMsg) GetSendAt() int64 { + if x != nil { + return x.SendAt + } + return 0 +} + +func (x *ModelTbRobotGroupMsg) GetExpireAt() int64 { + if x != nil { + return x.ExpireAt + } + return 0 +} + +func (x *ModelTbRobotGroupMsg) GetContentData() *ContentData { + if x != nil { + return x.ContentData + } + return nil +} + +func (x *ModelTbRobotGroupMsg) GetSenderWxId() string { + if x != nil { + return x.SenderWxId + } + return "" +} + +type ContentData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"raw_content" + RawContent string `protobuf:"bytes,1,opt,name=raw_content,json=rawContent,proto3" json:"raw_content,omitempty" bson:"raw_content"` // 元始的xml数据 做数据转发时用; + //@gotags: bson:"content" + Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty" bson:"content"` // 1文本的内容;2 语音的url(amr格式);6小程序的xml; + //@gotags: bson:"share_title" + ShareTitle string `protobuf:"bytes,3,opt,name=share_title,json=shareTitle,proto3" json:"share_title,omitempty" bson:"share_title"` // 5链接的标题; + //@gotags: bson:"share_desc" + ShareDesc string `protobuf:"bytes,4,opt,name=share_desc,json=shareDesc,proto3" json:"share_desc,omitempty" bson:"share_desc"` // 5链接的描述; + //@gotags: bson:"share_url" + ShareUrl string `protobuf:"bytes,5,opt,name=share_url,json=shareUrl,proto3" json:"share_url,omitempty" bson:"share_url"` // 5链接的URL; + //@gotags: bson:"file_url" + FileUrl string `protobuf:"bytes,6,opt,name=file_url,json=fileUrl,proto3" json:"file_url,omitempty" bson:"file_url"` // 3图片的url;4视频的Url;5链接的分享图;8表情的url(gif);9文件的url; + //@gotags: bson:"share_user_name" + ShareUserName string `protobuf:"bytes,7,opt,name=share_user_name,json=shareUserName,proto3" json:"share_user_name,omitempty" bson:"share_user_name"` // 7名片的被分享(名片)好友id; + //@gotags: bson:"share_nick_name" + ShareNickName string `protobuf:"bytes,8,opt,name=share_nick_name,json=shareNickName,proto3" json:"share_nick_name,omitempty" bson:"share_nick_name"` // 7名片的被分享(名片)的昵称; + //@gotags: bson:"at_msg_item" + AtMsgItem []*AtMsgItem `protobuf:"bytes,9,rep,name=at_msg_item,json=atMsgItem,proto3" json:"at_msg_item,omitempty" bson:"at_msg_item"` // 发送群@部分人消息的数据 + //@gotags: bson:"wx_msg_type" + WxMsgType int32 `protobuf:"varint,10,opt,name=wx_msg_type,json=wxMsgType,proto3" json:"wx_msg_type,omitempty" bson:"wx_msg_type"` // 消息类型: 1 文本;2 语音;3 图片;4 视频;5 链接;6 小程序;7 + // 名片;8 表情;9 文件;10 验证消息(如好友申请);11 视频号消息;12 + // 视频号直播间;13 视频号名片; + //@gotags: bson:"file_size" + FileSize float64 `protobuf:"fixed64,11,opt,name=file_size,json=fileSize,proto3" json:"file_size,omitempty" bson:"file_size"` // 文件大小KB单位 + //@gotags: bson:"resource_duration" + ResourceDuration int32 `protobuf:"varint,12,opt,name=resource_duration,json=resourceDuration,proto3" json:"resource_duration,omitempty" bson:"resource_duration"` // 媒体时长 统一单位s + //@gotags: bson:"at_user_name" + AtUserName []string `protobuf:"bytes,13,rep,name=at_user_name,json=atUserName,proto3" json:"at_user_name,omitempty" bson:"at_user_name"` // 群聊at消息 + //@gotags: bson:"is_at_myself" + IsAtMyself bool `protobuf:"varint,14,opt,name=is_at_myself,json=isAtMyself,proto3" json:"is_at_myself,omitempty" bson:"is_at_myself"` // 是否有at我自己 单独一个字段 方便维护和查询 +} + +func (x *ContentData) Reset() { + *x = ContentData{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ContentData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContentData) ProtoMessage() {} + +func (x *ContentData) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContentData.ProtoReflect.Descriptor instead. +func (*ContentData) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{7} +} + +func (x *ContentData) GetRawContent() string { + if x != nil { + return x.RawContent + } + return "" +} + +func (x *ContentData) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *ContentData) GetShareTitle() string { + if x != nil { + return x.ShareTitle + } + return "" +} + +func (x *ContentData) GetShareDesc() string { + if x != nil { + return x.ShareDesc + } + return "" +} + +func (x *ContentData) GetShareUrl() string { + if x != nil { + return x.ShareUrl + } + return "" +} + +func (x *ContentData) GetFileUrl() string { + if x != nil { + return x.FileUrl + } + return "" +} + +func (x *ContentData) GetShareUserName() string { + if x != nil { + return x.ShareUserName + } + return "" +} + +func (x *ContentData) GetShareNickName() string { + if x != nil { + return x.ShareNickName + } + return "" +} + +func (x *ContentData) GetAtMsgItem() []*AtMsgItem { + if x != nil { + return x.AtMsgItem + } + return nil +} + +func (x *ContentData) GetWxMsgType() int32 { + if x != nil { + return x.WxMsgType + } + return 0 +} + +func (x *ContentData) GetFileSize() float64 { + if x != nil { + return x.FileSize + } + return 0 +} + +func (x *ContentData) GetResourceDuration() int32 { + if x != nil { + return x.ResourceDuration + } + return 0 +} + +func (x *ContentData) GetAtUserName() []string { + if x != nil { + return x.AtUserName + } + return nil +} + +func (x *ContentData) GetIsAtMyself() bool { + if x != nil { + return x.IsAtMyself + } + return false +} + +type AtMsgItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"sub_type" + SubType int32 `protobuf:"varint,1,opt,name=SubType,proto3" json:"SubType,omitempty" bson:"sub_type"` // 0:文本内容,1:@某人 + //@gotags: bson:"content" + Content string `protobuf:"bytes,2,opt,name=Content,proto3" json:"Content,omitempty" bson:"content"` // 文本内容 + //@gotags: bson:"user_name" + UserName string `protobuf:"bytes,3,opt,name=UserName,proto3" json:"UserName,omitempty" bson:"user_name"` // @的用户(wx_id) + //@gotags: bson:"nick_name" + NickName string `protobuf:"bytes,4,opt,name=NickName,proto3" json:"NickName,omitempty" bson:"nick_name"` // @的昵称 +} + +func (x *AtMsgItem) Reset() { + *x = AtMsgItem{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AtMsgItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AtMsgItem) ProtoMessage() {} + +func (x *AtMsgItem) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AtMsgItem.ProtoReflect.Descriptor instead. +func (*AtMsgItem) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{8} +} + +func (x *AtMsgItem) GetSubType() int32 { + if x != nil { + return x.SubType + } + return 0 +} + +func (x *AtMsgItem) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *AtMsgItem) GetUserName() string { + if x != nil { + return x.UserName + } + return "" +} + +func (x *AtMsgItem) GetNickName() string { + if x != nil { + return x.NickName + } + return "" +} + +// 异步任务 +// @table_name: tb_crm_sched_task +type ModelSchedTask struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` //任务id + //@gotags: bson:"created_at" + CreatedAt int64 `protobuf:"varint,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty" bson:"created_at"` //创建时间 + //@gotags: bson:"updated_at" + UpdatedAt int64 `protobuf:"varint,3,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty" bson:"updated_at"` //更新时间 + //@gotags: bson:"task_state" + TaskState uint32 `protobuf:"varint,4,opt,name=task_state,json=taskState,proto3" json:"task_state,omitempty" bson:"task_state"` //执行状态 TaskState + //@gotags: bson:"task_type" + TaskType string `protobuf:"bytes,5,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty" bson:"task_type"` //任务类型 自定义的名称 用来区别是哪个模块发起的任务 + //@gotags: bson:"req_id" + ReqId string `protobuf:"bytes,6,opt,name=req_id,json=reqId,proto3" json:"req_id,omitempty" bson:"req_id"` //便于查询该任务 指定的id[作用:有些情况 无法直接通过id来查询该记录] + //@gotags: bson:"req_json" + ReqJson string `protobuf:"bytes,7,opt,name=req_json,json=reqJson,proto3" json:"req_json,omitempty" bson:"req_json"` //请求内容 + //@gotags: bson:"rsp_json" + RspJson string `protobuf:"bytes,8,opt,name=rsp_json,json=rspJson,proto3" json:"rsp_json,omitempty" bson:"rsp_json"` //完成后的内容 [成功或者失败的返回] + //@gotags: bson:"robot_wx_id" + RobotWxId string `protobuf:"bytes,9,opt,name=robot_wx_id,json=robotWxId,proto3" json:"robot_wx_id,omitempty" bson:"robot_wx_id"` //机器人id + //@gotags: bson:"expired_at" + ExpiredAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=expired_at,json=expiredAt,proto3" json:"expired_at,omitempty" bson:"expired_at"` //过期时间 +} + +func (x *ModelSchedTask) Reset() { + *x = ModelSchedTask{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelSchedTask) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelSchedTask) ProtoMessage() {} + +func (x *ModelSchedTask) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelSchedTask.ProtoReflect.Descriptor instead. +func (*ModelSchedTask) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{9} +} + +func (x *ModelSchedTask) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelSchedTask) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *ModelSchedTask) GetUpdatedAt() int64 { + if x != nil { + return x.UpdatedAt + } + return 0 +} + +func (x *ModelSchedTask) GetTaskState() uint32 { + if x != nil { + return x.TaskState + } + return 0 +} + +func (x *ModelSchedTask) GetTaskType() string { + if x != nil { + return x.TaskType + } + return "" +} + +func (x *ModelSchedTask) GetReqId() string { + if x != nil { + return x.ReqId + } + return "" +} + +func (x *ModelSchedTask) GetReqJson() string { + if x != nil { + return x.ReqJson + } + return "" +} + +func (x *ModelSchedTask) GetRspJson() string { + if x != nil { + return x.RspJson + } + return "" +} + +func (x *ModelSchedTask) GetRobotWxId() string { + if x != nil { + return x.RobotWxId + } + return "" +} + +func (x *ModelSchedTask) GetExpiredAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiredAt + } + return nil +} + +// @table_name: tb_robot_friend +type ModelRobotFriend struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` // 主键ID 机器人id+朋友id md5 + //@gotags: bson:"robot_wechat_id" + RobotWechatId string `protobuf:"bytes,2,opt,name=robot_wechat_id,json=robotWechatId,proto3" json:"robot_wechat_id,omitempty" bson:"robot_wechat_id"` // 机器人编号:微信ID + //@gotags: bson:"user_wechat_id" + UserWechatId string `protobuf:"bytes,3,opt,name=user_wechat_id,json=userWechatId,proto3" json:"user_wechat_id,omitempty" bson:"user_wechat_id"` // 用户微信ID, + //@gotags: bson:"deleted" + Deleted int64 `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty" bson:"deleted"` // 是否被删除 0双方未删除 1被好友删除 2删除了好友 3互相删除 + //@gotags: bson:"offline_add" + OfflineAdd int64 `protobuf:"varint,5,opt,name=offline_add,json=offlineAdd,proto3" json:"offline_add,omitempty" bson:"offline_add"` // 是否为离线添加 + //@gotags: bson:"remark_name" + RemarkName string `protobuf:"bytes,6,opt,name=remark_name,json=remarkName,proto3" json:"remark_name,omitempty" bson:"remark_name"` // 微信好友备注名称 + //@gotags: bson:"pinyin" + Pinyin string `protobuf:"bytes,7,opt,name=pinyin,proto3" json:"pinyin,omitempty" bson:"pinyin"` // 用户备注或者暱称的拼音 + //@gotags: bson:"pinyin_head" + PinyinHead string `protobuf:"bytes,8,opt,name=pinyin_head,json=pinyinHead,proto3" json:"pinyin_head,omitempty" bson:"pinyin_head"` // 拼音首字母 + //@gotags: bson:"delete_time" + DeleteTime int64 `protobuf:"varint,9,opt,name=delete_time,json=deleteTime,proto3" json:"delete_time,omitempty" bson:"delete_time"` // 删除好友的时间 + //@gotags: bson:"create_time" + CreateTime int64 `protobuf:"varint,10,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" bson:"create_time"` // 创建时间:入库时间 + //@gotags: bson:"update_time" + UpdateTime int64 `protobuf:"varint,11,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" bson:"update_time"` // 更新时间 + //@gotags: bson:"add_at" + AddAt int64 `protobuf:"varint,12,opt,name=add_at,json=addAt,proto3" json:"add_at,omitempty" bson:"add_at"` // 添加好友时间只有主动添加好友才有 + //@gotags: bson:"crm_phone" + CrmPhone string `protobuf:"bytes,13,opt,name=crm_phone,json=crmPhone,proto3" json:"crm_phone,omitempty" bson:"crm_phone"` // CRM自己设置的好友手机号,不同于微信手机号 +} + +func (x *ModelRobotFriend) Reset() { + *x = ModelRobotFriend{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelRobotFriend) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelRobotFriend) ProtoMessage() {} + +func (x *ModelRobotFriend) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelRobotFriend.ProtoReflect.Descriptor instead. +func (*ModelRobotFriend) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{10} +} + +func (x *ModelRobotFriend) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelRobotFriend) GetRobotWechatId() string { + if x != nil { + return x.RobotWechatId + } + return "" +} + +func (x *ModelRobotFriend) GetUserWechatId() string { + if x != nil { + return x.UserWechatId + } + return "" +} + +func (x *ModelRobotFriend) GetDeleted() int64 { + if x != nil { + return x.Deleted + } + return 0 +} + +func (x *ModelRobotFriend) GetOfflineAdd() int64 { + if x != nil { + return x.OfflineAdd + } + return 0 +} + +func (x *ModelRobotFriend) GetRemarkName() string { + if x != nil { + return x.RemarkName + } + return "" +} + +func (x *ModelRobotFriend) GetPinyin() string { + if x != nil { + return x.Pinyin + } + return "" +} + +func (x *ModelRobotFriend) GetPinyinHead() string { + if x != nil { + return x.PinyinHead + } + return "" +} + +func (x *ModelRobotFriend) GetDeleteTime() int64 { + if x != nil { + return x.DeleteTime + } + return 0 +} + +func (x *ModelRobotFriend) GetCreateTime() int64 { + if x != nil { + return x.CreateTime + } + return 0 +} + +func (x *ModelRobotFriend) GetUpdateTime() int64 { + if x != nil { + return x.UpdateTime + } + return 0 +} + +func (x *ModelRobotFriend) GetAddAt() int64 { + if x != nil { + return x.AddAt + } + return 0 +} + +func (x *ModelRobotFriend) GetCrmPhone() string { + if x != nil { + return x.CrmPhone + } + return "" +} + +// @table_name: tb_ws_connect_record +type ModelWsConnectRecord struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"_id"` // 主键ID wxid md5 + //@gotags: bson:"user_id" + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty" bson:"user_id"` // 机器人所属用户id + //@gotags: bson:"created_at" + CreatedAt int64 `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty" bson:"created_at"` // 记录创建时间 + //@gotags: bson:"login_at" + LoginAt int64 `protobuf:"varint,4,opt,name=login_at,json=loginAt,proto3" json:"login_at,omitempty" bson:"login_at"` // 登录时间 + //@gotags: bson:"logout_at" + LogoutAt int64 `protobuf:"varint,5,opt,name=logout_at,json=logoutAt,proto3" json:"logout_at,omitempty" bson:"logout_at"` // 登出时间 + //@gotags: bson:"bind_id" + BindId string `protobuf:"bytes,6,opt,name=bind_id,json=bindId,proto3" json:"bind_id,omitempty" bson:"bind_id"` // 该ws绑定的id + //@gotags: bson:"expired_at" + ExpiredAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=expired_at,json=expiredAt,proto3" json:"expired_at,omitempty" bson:"expired_at"` // 过期时间 +} + +func (x *ModelWsConnectRecord) Reset() { + *x = ModelWsConnectRecord{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelWsConnectRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelWsConnectRecord) ProtoMessage() {} + +func (x *ModelWsConnectRecord) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelWsConnectRecord.ProtoReflect.Descriptor instead. +func (*ModelWsConnectRecord) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{11} +} + +func (x *ModelWsConnectRecord) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelWsConnectRecord) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *ModelWsConnectRecord) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *ModelWsConnectRecord) GetLoginAt() int64 { + if x != nil { + return x.LoginAt + } + return 0 +} + +func (x *ModelWsConnectRecord) GetLogoutAt() int64 { + if x != nil { + return x.LogoutAt + } + return 0 +} + +func (x *ModelWsConnectRecord) GetBindId() string { + if x != nil { + return x.BindId + } + return "" +} + +func (x *ModelWsConnectRecord) GetExpiredAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiredAt + } + return nil +} + +// @table_name: tb_robot +type ModelRobot struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + //@gotags: json:"_id" bson:"_id" + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"_id" bson:"_id"` // 主键ID wxid md5 + //@gotags: bson:"user_id" + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty" bson:"user_id"` // 机器人所属用户id + //@gotags: bson:"crm_shop_id" + CrmShopId string `protobuf:"bytes,3,opt,name=crm_shop_id,json=crmShopId,proto3" json:"crm_shop_id,omitempty" bson:"crm_shop_id"` // 机器人所属商户id + //@gotags: bson:"alias_name" + AliasName string `protobuf:"bytes,4,opt,name=alias_name,json=aliasName,proto3" json:"alias_name,omitempty" bson:"alias_name"` // 微信号 + //@gotags: bson:"nick_name" + NickName string `protobuf:"bytes,5,opt,name=nick_name,json=nickName,proto3" json:"nick_name,omitempty" bson:"nick_name"` // 机器人暱称 + //@gotags: bson:"wechat_id" + WechatId string `protobuf:"bytes,6,opt,name=wechat_id,json=wechatId,proto3" json:"wechat_id,omitempty" bson:"wechat_id"` // 微信唯一ID (wxidxxxxxx) + //@gotags: bson:"wechat_alias" + WechatAlias string `protobuf:"bytes,7,opt,name=wechat_alias,json=wechatAlias,proto3" json:"wechat_alias,omitempty" bson:"wechat_alias"` // 微信ID (用户自己定义的微信号) + //@gotags: bson:"avatar_url" + AvatarUrl string `protobuf:"bytes,8,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url,omitempty" bson:"avatar_url"` // 机器人头像 + //@gotags: bson:"sex" + Sex int32 `protobuf:"varint,9,opt,name=sex,proto3" json:"sex,omitempty" bson:"sex"` // 性别 0 未知 1 男生 2 女生 + //@gotags: bson:"mobile" + Mobile string `protobuf:"bytes,10,opt,name=mobile,proto3" json:"mobile,omitempty" bson:"mobile"` // 手机号码 + //@gotags: bson:"qrcode" + Qrcode string `protobuf:"bytes,11,opt,name=qrcode,proto3" json:"qrcode,omitempty" bson:"qrcode"` // 机器人二维码 + //@gotags: bson:"status" + Status int64 `protobuf:"varint,12,opt,name=status,proto3" json:"status,omitempty" bson:"status"` // 机器人PC是否在线 10在线 11离线 (兼容之前的pc登录流程和其他接口,这个登录状态不变,补多一个字段代表安卓登录状态) + //@gotags: bson:"limited" + Limited int64 `protobuf:"varint,13,opt,name=limited,proto3" json:"limited,omitempty" bson:"limited"` // 机器人是否被封号 0未封号 1已封号 + //@gotags: bson:"ability_limit" + AbilityLimit int64 `protobuf:"varint,14,opt,name=ability_limit,json=abilityLimit,proto3" json:"ability_limit,omitempty" bson:"ability_limit"` // 机器人是否功能受限 + //@gotags: bson:"init_friend" + InitFriend int64 `protobuf:"varint,15,opt,name=init_friend,json=initFriend,proto3" json:"init_friend,omitempty" bson:"init_friend"` // 机器人初始好友人数 + //@gotags: bson:"now_friend" + NowFriend int64 `protobuf:"varint,16,opt,name=now_friend,json=nowFriend,proto3" json:"now_friend,omitempty" bson:"now_friend"` // 机器人当前好友数量 + //@gotags: bson:"auto_add_friend" + AutoAddFriend int64 `protobuf:"varint,17,opt,name=auto_add_friend,json=autoAddFriend,proto3" json:"auto_add_friend,omitempty" bson:"auto_add_friend"` // 机器人是否自动通过好友请求 0否 1是 + //@gotags: bson:"last_login_time" + LastLoginTime int64 `protobuf:"varint,18,opt,name=last_login_time,json=lastLoginTime,proto3" json:"last_login_time,omitempty" bson:"last_login_time"` // 最后登录时间 + //@gotags: bson:"last_log_out_time" + LastLogOutTime int64 `protobuf:"varint,19,opt,name=last_log_out_time,json=lastLogOutTime,proto3" json:"last_log_out_time,omitempty" bson:"last_log_out_time"` // 最后登出时间 + //@gotags: bson:"last_region_code" + LastRegionCode string `protobuf:"bytes,20,opt,name=last_region_code,json=lastRegionCode,proto3" json:"last_region_code,omitempty" bson:"last_region_code"` // 最后登录的扫码设备的地区编码 + //@gotags: bson:"last_city" + LastCity string `protobuf:"bytes,21,opt,name=last_city,json=lastCity,proto3" json:"last_city,omitempty" bson:"last_city"` // 最后登录的城市名称 + //@gotags: bson:"today_require_time" + TodayRequireTime int64 `protobuf:"varint,22,opt,name=today_require_time,json=todayRequireTime,proto3" json:"today_require_time,omitempty" bson:"today_require_time"` // 当天请求次数 + //@gotags: bson:"last_require_add_friend_time" + LastRequireAddFriendTime int64 `protobuf:"varint,23,opt,name=last_require_add_friend_time,json=lastRequireAddFriendTime,proto3" json:"last_require_add_friend_time,omitempty" bson:"last_require_add_friend_time"` // 上一次请求添加好友的时间 + //@gotags: bson:"crm_auto_add_friend" + CrmAutoAddFriend int64 `protobuf:"varint,24,opt,name=crm_auto_add_friend,json=crmAutoAddFriend,proto3" json:"crm_auto_add_friend,omitempty" bson:"crm_auto_add_friend"` // crm系统自动通过好友 1自动通过 0不自动通过 + //@gotags: bson:"delete_time" + DeleteTime int64 `protobuf:"varint,25,opt,name=delete_time,json=deleteTime,proto3" json:"delete_time,omitempty" bson:"delete_time"` // 删除时间 + //@gotags: bson:"create_time" + CreateTime int64 `protobuf:"varint,26,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" bson:"create_time"` // 创建时间 + //@gotags: bson:"update_time" + UpdateTime int64 `protobuf:"varint,27,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" bson:"update_time"` // 更新时间 + //@gotags: bson:"log_and_out_time" + LogAndOutTime int64 `protobuf:"varint,28,opt,name=log_and_out_time,json=logAndOutTime,proto3" json:"log_and_out_time,omitempty" bson:"log_and_out_time"` // 登入或者登出都要记录一下 + //@gotags: bson:"android_status" + AndroidStatus int64 `protobuf:"varint,29,opt,name=android_status,json=androidStatus,proto3" json:"android_status,omitempty" bson:"android_status"` // 机器人Android是否在线 10在线 11离线 + //@gotags: bson:"greet_id" + GreetId string `protobuf:"bytes,30,opt,name=greet_id,json=greetId,proto3" json:"greet_id,omitempty" bson:"greet_id"` // 打招呼模板id + //@gotags: bson:"android_wechat_version" + AndroidWechatVersion string `protobuf:"bytes,31,opt,name=android_wechat_version,json=androidWechatVersion,proto3" json:"android_wechat_version,omitempty" bson:"android_wechat_version"` // 微信版本 + //@gotags: bson:"risk_control_group" + RiskControlGroup uint32 `protobuf:"varint,33,opt,name=risk_control_group,json=riskControlGroup,proto3" json:"risk_control_group,omitempty" bson:"risk_control_group"` // 风控分组 + //@gotags: bson:"last_pc_login_at" + LastPcLoginAt int64 `protobuf:"varint,34,opt,name=last_pc_login_at,json=lastPcLoginAt,proto3" json:"last_pc_login_at,omitempty" bson:"last_pc_login_at"` // 最近PC登录时间 + //@gotags: bson:"last_pc_logout_at" + LastPcLogoutAt int64 `protobuf:"varint,35,opt,name=last_pc_logout_at,json=lastPcLogoutAt,proto3" json:"last_pc_logout_at,omitempty" bson:"last_pc_logout_at"` // 最近PC登出时间 + //@gotags: bson:"last_android_login_at" + LastAndroidLoginAt int64 `protobuf:"varint,36,opt,name=last_android_login_at,json=lastAndroidLoginAt,proto3" json:"last_android_login_at,omitempty" bson:"last_android_login_at"` // 最近安卓登录时间 + //@gotags: bson:"last_android_logout_at" + LastAndroidLogoutAt int64 `protobuf:"varint,37,opt,name=last_android_logout_at,json=lastAndroidLogoutAt,proto3" json:"last_android_logout_at,omitempty" bson:"last_android_logout_at"` // 最近安卓登出时间 + //@gotags: bson:"risk_control_task" + RiskControlTask string `protobuf:"bytes,38,opt,name=risk_control_task,json=riskControlTask,proto3" json:"risk_control_task,omitempty" bson:"risk_control_task"` // 风控任务 0是全部,1是回复,2是发消息,3是看朋友圈,4是发朋友圈,5是点赞,6是评论 7是群聊 可组合,如:1,2,3 + //@gotags: bson:"open_for_stranger" + OpenForStranger bool `protobuf:"varint,39,opt,name=open_for_stranger,json=openForStranger,proto3" json:"open_for_stranger,omitempty" bson:"open_for_stranger"` // 是否允许陌生人查看十条朋友圈 + //@gotags: bson:"moment_privacy_type" + MomentPrivacyType int32 `protobuf:"varint,40,opt,name=moment_privacy_type,json=momentPrivacyType,proto3" json:"moment_privacy_type,omitempty" bson:"moment_privacy_type"` // 朋友圈隐私选项类型 + //@gotags: bson:"cover_url" + CoverUrl string `protobuf:"bytes,41,opt,name=cover_url,json=coverUrl,proto3" json:"cover_url,omitempty" bson:"cover_url"` // 朋友圈封面url + //@gotags: bson:"country" + Country string `protobuf:"bytes,42,opt,name=country,proto3" json:"country,omitempty" bson:"country"` // 国家 + //@gotags: bson:"province" + Province string `protobuf:"bytes,43,opt,name=province,proto3" json:"province,omitempty" bson:"province"` // 省份 + //@gotags: bson:"city" + City string `protobuf:"bytes,44,opt,name=city,proto3" json:"city,omitempty" bson:"city"` // 城市 + //@gotags: bson:"signature" + Signature string `protobuf:"bytes,45,opt,name=signature,proto3" json:"signature,omitempty" bson:"signature"` // 个性签名 +} + +func (x *ModelRobot) Reset() { + *x = ModelRobot{} + if protoimpl.UnsafeEnabled { + mi := &file_mdbc_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelRobot) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelRobot) ProtoMessage() {} + +func (x *ModelRobot) ProtoReflect() protoreflect.Message { + mi := &file_mdbc_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelRobot.ProtoReflect.Descriptor instead. +func (*ModelRobot) Descriptor() ([]byte, []int) { + return file_mdbc_proto_rawDescGZIP(), []int{12} +} + +func (x *ModelRobot) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelRobot) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *ModelRobot) GetCrmShopId() string { + if x != nil { + return x.CrmShopId + } + return "" +} + +func (x *ModelRobot) GetAliasName() string { + if x != nil { + return x.AliasName + } + return "" +} + +func (x *ModelRobot) GetNickName() string { + if x != nil { + return x.NickName + } + return "" +} + +func (x *ModelRobot) GetWechatId() string { + if x != nil { + return x.WechatId + } + return "" +} + +func (x *ModelRobot) GetWechatAlias() string { + if x != nil { + return x.WechatAlias + } + return "" +} + +func (x *ModelRobot) GetAvatarUrl() string { + if x != nil { + return x.AvatarUrl + } + return "" +} + +func (x *ModelRobot) GetSex() int32 { + if x != nil { + return x.Sex + } + return 0 +} + +func (x *ModelRobot) GetMobile() string { + if x != nil { + return x.Mobile + } + return "" +} + +func (x *ModelRobot) GetQrcode() string { + if x != nil { + return x.Qrcode + } + return "" +} + +func (x *ModelRobot) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *ModelRobot) GetLimited() int64 { + if x != nil { + return x.Limited + } + return 0 +} + +func (x *ModelRobot) GetAbilityLimit() int64 { + if x != nil { + return x.AbilityLimit + } + return 0 +} + +func (x *ModelRobot) GetInitFriend() int64 { + if x != nil { + return x.InitFriend + } + return 0 +} + +func (x *ModelRobot) GetNowFriend() int64 { + if x != nil { + return x.NowFriend + } + return 0 +} + +func (x *ModelRobot) GetAutoAddFriend() int64 { + if x != nil { + return x.AutoAddFriend + } + return 0 +} + +func (x *ModelRobot) GetLastLoginTime() int64 { + if x != nil { + return x.LastLoginTime + } + return 0 +} + +func (x *ModelRobot) GetLastLogOutTime() int64 { + if x != nil { + return x.LastLogOutTime + } + return 0 +} + +func (x *ModelRobot) GetLastRegionCode() string { + if x != nil { + return x.LastRegionCode + } + return "" +} + +func (x *ModelRobot) GetLastCity() string { + if x != nil { + return x.LastCity + } + return "" +} + +func (x *ModelRobot) GetTodayRequireTime() int64 { + if x != nil { + return x.TodayRequireTime + } + return 0 +} + +func (x *ModelRobot) GetLastRequireAddFriendTime() int64 { + if x != nil { + return x.LastRequireAddFriendTime + } + return 0 +} + +func (x *ModelRobot) GetCrmAutoAddFriend() int64 { + if x != nil { + return x.CrmAutoAddFriend + } + return 0 +} + +func (x *ModelRobot) GetDeleteTime() int64 { + if x != nil { + return x.DeleteTime + } + return 0 +} + +func (x *ModelRobot) GetCreateTime() int64 { + if x != nil { + return x.CreateTime + } + return 0 +} + +func (x *ModelRobot) GetUpdateTime() int64 { + if x != nil { + return x.UpdateTime + } + return 0 +} + +func (x *ModelRobot) GetLogAndOutTime() int64 { + if x != nil { + return x.LogAndOutTime + } + return 0 +} + +func (x *ModelRobot) GetAndroidStatus() int64 { + if x != nil { + return x.AndroidStatus + } + return 0 +} + +func (x *ModelRobot) GetGreetId() string { + if x != nil { + return x.GreetId + } + return "" +} + +func (x *ModelRobot) GetAndroidWechatVersion() string { + if x != nil { + return x.AndroidWechatVersion + } + return "" +} + +func (x *ModelRobot) GetRiskControlGroup() uint32 { + if x != nil { + return x.RiskControlGroup + } + return 0 +} + +func (x *ModelRobot) GetLastPcLoginAt() int64 { + if x != nil { + return x.LastPcLoginAt + } + return 0 +} + +func (x *ModelRobot) GetLastPcLogoutAt() int64 { + if x != nil { + return x.LastPcLogoutAt + } + return 0 +} + +func (x *ModelRobot) GetLastAndroidLoginAt() int64 { + if x != nil { + return x.LastAndroidLoginAt + } + return 0 +} + +func (x *ModelRobot) GetLastAndroidLogoutAt() int64 { + if x != nil { + return x.LastAndroidLogoutAt + } + return 0 +} + +func (x *ModelRobot) GetRiskControlTask() string { + if x != nil { + return x.RiskControlTask + } + return "" +} + +func (x *ModelRobot) GetOpenForStranger() bool { + if x != nil { + return x.OpenForStranger + } + return false +} + +func (x *ModelRobot) GetMomentPrivacyType() int32 { + if x != nil { + return x.MomentPrivacyType + } + return 0 +} + +func (x *ModelRobot) GetCoverUrl() string { + if x != nil { + return x.CoverUrl + } + return "" +} + +func (x *ModelRobot) GetCountry() string { + if x != nil { + return x.Country + } + return "" +} + +func (x *ModelRobot) GetProvince() string { + if x != nil { + return x.Province + } + return "" +} + +func (x *ModelRobot) GetCity() string { + if x != nil { + return x.City + } + return "" +} + +func (x *ModelRobot) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + +var File_mdbc_proto protoreflect.FileDescriptor + +var file_mdbc_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x6d, 0x64, 0x62, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6d, 0x64, + 0x62, 0x63, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0xd0, 0x02, 0x0a, 0x0f, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x46, 0x72, 0x69, + 0x65, 0x6e, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x65, 0x63, 0x68, 0x61, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x65, 0x63, 0x68, + 0x61, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x5f, 0x75, 0x72, + 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x55, + 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x69, + 0x74, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x03, 0x73, 0x65, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x86, 0x06, 0x0a, 0x0e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x68, 0x61, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, + 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x62, + 0x6f, 0x74, 0x57, 0x78, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, + 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x57, 0x78, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, + 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x77, 0x6e, + 0x65, 0x72, 0x57, 0x78, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77, 0x6e, 0x65, + 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x55, 0x72, + 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x24, 0x0a, 0x0e, + 0x68, 0x61, 0x73, 0x5f, 0x62, 0x65, 0x65, 0x6e, 0x5f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x42, 0x65, 0x65, 0x6e, 0x57, 0x61, 0x74, + 0x63, 0x68, 0x12, 0x31, 0x0a, 0x15, 0x69, 0x73, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x12, 0x69, 0x73, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x63, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6e, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x63, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x61, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x74, 0x12, 0x2d, 0x0a, + 0x13, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x5f, 0x61, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, + 0x53, 0x79, 0x6e, 0x63, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, + 0x74, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x71, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x71, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x71, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x18, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x71, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x55, 0x72, 0x6c, 0x12, + 0x2e, 0x0a, 0x0a, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x19, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x6d, 0x64, 0x62, 0x63, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, + 0xbe, 0x03, 0x0a, 0x14, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x43, 0x68, + 0x61, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x63, + 0x68, 0x61, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x43, 0x68, 0x61, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x5f, 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x57, 0x78, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, + 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x41, 0x76, 0x61, 0x74, 0x61, + 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x69, 0x61, + 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x41, + 0x6c, 0x69, 0x61, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x73, + 0x65, 0x78, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x53, 0x65, 0x78, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x2e, + 0x0a, 0x0a, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x6d, 0x64, 0x62, 0x63, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, + 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x61, 0x74, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x74, + 0x22, 0xc0, 0x02, 0x0a, 0x18, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x62, 0x50, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x4d, 0x73, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, + 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x6c, 0x6c, 0x12, + 0x12, 0x0a, 0x04, 0x72, 0x65, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x72, + 0x65, 0x61, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x73, 0x67, 0x41, 0x74, 0x12, 0x2b, 0x0a, 0x12, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x61, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x69, + 0x65, 0x6e, 0x64, 0x4d, 0x73, 0x67, 0x41, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x62, 0x6f, + 0x74, 0x5f, 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, + 0x6f, 0x62, 0x6f, 0x74, 0x57, 0x78, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x57, 0x78, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, + 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x61, 0x73, + 0x74, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66, + 0x72, 0x69, 0x65, 0x6e, 0x64, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x4d, 0x73, + 0x67, 0x49, 0x64, 0x22, 0xe9, 0x02, 0x0a, 0x16, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x62, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x73, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, + 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x6c, 0x6c, + 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, + 0x72, 0x65, 0x61, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x12, 0x1e, 0x0a, 0x0b, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x73, 0x67, 0x41, 0x74, 0x12, 0x2b, 0x0a, 0x12, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x5f, 0x6d, 0x73, 0x67, 0x5f, + 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x46, 0x72, + 0x69, 0x65, 0x6e, 0x64, 0x4d, 0x73, 0x67, 0x41, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x62, + 0x6f, 0x74, 0x5f, 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x57, 0x78, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x0a, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, + 0x73, 0x65, 0x72, 0x57, 0x78, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x61, + 0x73, 0x74, 0x4d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x4d, + 0x73, 0x67, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x5f, 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x57, 0x78, 0x49, 0x64, 0x22, + 0xba, 0x04, 0x0a, 0x16, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x62, 0x52, 0x6f, 0x62, 0x6f, 0x74, + 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x69, + 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x69, 0x6e, + 0x64, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x77, 0x78, 0x5f, + 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x57, + 0x78, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x77, 0x78, 0x5f, 0x69, + 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x57, 0x78, 0x49, + 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x6d, 0x73, 0x67, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x73, 0x67, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6d, 0x73, 0x67, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x26, 0x0a, 0x0f, + 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x43, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, + 0x72, 0x65, 0x61, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x61, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x61, 0x69, 0x6c, + 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x62, + 0x61, 0x63, 0x6b, 0x5f, 0x61, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x61, + 0x6c, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75, 0x72, 0x73, + 0x6f, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, + 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, 0x12, 0x34, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, + 0x64, 0x62, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, + 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0xda, 0x04, 0x0a, + 0x14, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x62, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x4d, 0x73, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x69, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1e, + 0x0a, 0x0b, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x57, 0x78, 0x49, 0x64, 0x12, 0x1c, + 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x57, 0x78, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, + 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x73, + 0x67, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x73, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6d, 0x73, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x65, 0x6e, 0x64, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0d, 0x73, 0x65, 0x6e, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x61, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, + 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x61, + 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x61, 0x6c, 0x6c, 0x42, 0x61, 0x63, + 0x6b, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x11, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x73, + 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x65, + 0x6e, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x61, + 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, + 0x74, 0x12, 0x34, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x64, 0x62, 0x63, 0x2e, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x20, 0x0a, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x5f, 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x57, 0x78, 0x49, 0x64, 0x22, 0xef, 0x03, 0x0a, 0x0b, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x61, 0x77, + 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x72, 0x61, 0x77, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x68, 0x61, 0x72, 0x65, 0x5f, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x68, 0x61, 0x72, 0x65, + 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x68, 0x61, 0x72, 0x65, 0x5f, 0x64, + 0x65, 0x73, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x68, 0x61, 0x72, 0x65, + 0x44, 0x65, 0x73, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x68, 0x61, 0x72, 0x65, 0x5f, 0x75, 0x72, + 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x68, 0x61, 0x72, 0x65, 0x55, 0x72, + 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x26, 0x0a, 0x0f, + 0x73, 0x68, 0x61, 0x72, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x65, 0x55, 0x73, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x5f, 0x6e, 0x69, + 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, + 0x68, 0x61, 0x72, 0x65, 0x4e, 0x69, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x0b, + 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x09, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0f, 0x2e, 0x6d, 0x64, 0x62, 0x63, 0x2e, 0x41, 0x74, 0x4d, 0x73, 0x67, 0x49, 0x74, + 0x65, 0x6d, 0x52, 0x09, 0x61, 0x74, 0x4d, 0x73, 0x67, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1e, 0x0a, + 0x0b, 0x77, 0x78, 0x5f, 0x6d, 0x73, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x09, 0x77, 0x78, 0x4d, 0x73, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x44, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x61, 0x74, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x73, 0x5f, + 0x61, 0x74, 0x5f, 0x6d, 0x79, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0a, 0x69, 0x73, 0x41, 0x74, 0x4d, 0x79, 0x73, 0x65, 0x6c, 0x66, 0x22, 0x77, 0x0a, 0x09, 0x41, + 0x74, 0x4d, 0x73, 0x67, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x53, 0x75, 0x62, 0x54, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x53, 0x75, 0x62, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x4e, 0x69, 0x63, 0x6b, + 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x4e, 0x69, 0x63, 0x6b, + 0x4e, 0x61, 0x6d, 0x65, 0x22, 0xc2, 0x02, 0x0a, 0x0e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x63, + 0x68, 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x74, 0x61, 0x73, 0x6b, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x73, 0x6b, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x65, 0x71, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x72, 0x65, 0x71, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x5f, + 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x71, 0x4a, + 0x73, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x73, 0x70, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x73, 0x70, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x1e, + 0x0a, 0x0b, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x77, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x57, 0x78, 0x49, 0x64, 0x12, 0x39, + 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x41, 0x74, 0x22, 0x9c, 0x03, 0x0a, 0x10, 0x4d, 0x6f, + 0x64, 0x65, 0x6c, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, + 0x0a, 0x0f, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x5f, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x6f, 0x62, 0x6f, 0x74, 0x57, 0x65, + 0x63, 0x68, 0x61, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x77, + 0x65, 0x63, 0x68, 0x61, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x75, 0x73, 0x65, 0x72, 0x57, 0x65, 0x63, 0x68, 0x61, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x64, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x66, 0x66, 0x6c, 0x69, 0x6e, + 0x65, 0x5f, 0x61, 0x64, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x6f, 0x66, 0x66, + 0x6c, 0x69, 0x6e, 0x65, 0x41, 0x64, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x61, 0x72, + 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, + 0x6d, 0x61, 0x72, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x69, 0x6e, 0x79, + 0x69, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x69, 0x6e, 0x79, 0x69, 0x6e, + 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x69, 0x6e, 0x79, 0x69, 0x6e, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x69, 0x6e, 0x79, 0x69, 0x6e, 0x48, 0x65, 0x61, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x61, 0x64, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x61, 0x64, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, + 0x72, 0x6d, 0x5f, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x63, 0x72, 0x6d, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x14, 0x4d, 0x6f, 0x64, + 0x65, 0x6c, 0x57, 0x73, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, + 0x69, 0x6e, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6c, 0x6f, 0x67, + 0x69, 0x6e, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x5f, 0x61, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x41, + 0x74, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x62, 0x69, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x65, 0x64, 0x41, 0x74, 0x22, 0xa3, 0x0c, 0x0a, 0x0a, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, + 0x6f, 0x62, 0x6f, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, + 0x0b, 0x63, 0x72, 0x6d, 0x5f, 0x73, 0x68, 0x6f, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x6d, 0x53, 0x68, 0x6f, 0x70, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x6e, 0x69, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x65, 0x63, + 0x68, 0x61, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x65, + 0x63, 0x68, 0x61, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, + 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x65, + 0x63, 0x68, 0x61, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x76, 0x61, + 0x74, 0x61, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, + 0x76, 0x61, 0x74, 0x61, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x78, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x73, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, + 0x62, 0x69, 0x6c, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x62, 0x69, + 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x71, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x71, 0x72, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x07, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, + 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x4c, 0x69, 0x6d, 0x69, + 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x69, 0x74, 0x5f, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x69, 0x6e, 0x69, 0x74, 0x46, 0x72, 0x69, 0x65, + 0x6e, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x77, 0x5f, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6e, 0x6f, 0x77, 0x46, 0x72, 0x69, 0x65, 0x6e, + 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x61, 0x64, 0x64, 0x5f, 0x66, 0x72, + 0x69, 0x65, 0x6e, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x75, 0x74, 0x6f, + 0x41, 0x64, 0x64, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x12, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x29, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6f, 0x75, + 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6c, 0x61, + 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x4f, 0x75, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x63, + 0x69, 0x74, 0x79, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x43, + 0x69, 0x74, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x10, 0x74, 0x6f, 0x64, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x3e, 0x0a, 0x1c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x5f, 0x61, 0x64, 0x64, 0x5f, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x41, 0x64, 0x64, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x2d, 0x0a, 0x13, 0x63, 0x72, 0x6d, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x61, 0x64, + 0x64, 0x5f, 0x66, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, + 0x63, 0x72, 0x6d, 0x41, 0x75, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, + 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x19, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, 0x6d, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x1a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x10, 0x6c, 0x6f, 0x67, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x6f, + 0x75, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, + 0x6f, 0x67, 0x41, 0x6e, 0x64, 0x4f, 0x75, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, + 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x1d, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x65, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x65, 0x65, 0x74, 0x49, 0x64, 0x12, 0x34, + 0x0a, 0x16, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x77, 0x65, 0x63, 0x68, 0x61, 0x74, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, + 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x57, 0x65, 0x63, 0x68, 0x61, 0x74, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x69, 0x73, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x10, 0x72, 0x69, 0x73, 0x6b, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x12, 0x27, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x70, 0x63, 0x5f, 0x6c, 0x6f, + 0x67, 0x69, 0x6e, 0x5f, 0x61, 0x74, 0x18, 0x22, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6c, 0x61, + 0x73, 0x74, 0x50, 0x63, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x74, 0x12, 0x29, 0x0a, 0x11, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x70, 0x63, 0x5f, 0x6c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x5f, 0x61, 0x74, + 0x18, 0x23, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x50, 0x63, 0x4c, 0x6f, + 0x67, 0x6f, 0x75, 0x74, 0x41, 0x74, 0x12, 0x31, 0x0a, 0x15, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x61, + 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x61, 0x74, 0x18, + 0x24, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6c, 0x61, 0x73, 0x74, 0x41, 0x6e, 0x64, 0x72, 0x6f, + 0x69, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x74, 0x12, 0x33, 0x0a, 0x16, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x6f, 0x75, 0x74, + 0x5f, 0x61, 0x74, 0x18, 0x25, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6c, 0x61, 0x73, 0x74, 0x41, + 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x41, 0x74, 0x12, 0x2a, + 0x0a, 0x11, 0x72, 0x69, 0x73, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x74, + 0x61, 0x73, 0x6b, 0x18, 0x26, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x69, 0x73, 0x6b, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, + 0x65, 0x6e, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x18, + 0x27, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x6e, 0x46, 0x6f, 0x72, 0x53, 0x74, + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x13, 0x6d, 0x6f, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x28, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x11, 0x6d, 0x6f, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x69, 0x76, 0x61, + 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, + 0x75, 0x72, 0x6c, 0x18, 0x29, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x55, 0x72, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x2a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x2b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, + 0x79, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2a, 0x45, 0x0a, 0x09, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x69, 0x6c, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x12, + 0x0a, 0x0e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, + 0x10, 0x02, 0x2a, 0x60, 0x0a, 0x09, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x10, 0x0a, 0x0c, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4e, 0x69, 0x6c, 0x10, + 0x00, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x75, + 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x61, 0x73, 0x6b, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, + 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x10, 0x03, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x3b, 0x6d, 0x64, 0x62, 0x63, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_mdbc_proto_rawDescOnce sync.Once + file_mdbc_proto_rawDescData = file_mdbc_proto_rawDesc +) + +func file_mdbc_proto_rawDescGZIP() []byte { + file_mdbc_proto_rawDescOnce.Do(func() { + file_mdbc_proto_rawDescData = protoimpl.X.CompressGZIP(file_mdbc_proto_rawDescData) + }) + return file_mdbc_proto_rawDescData +} + +var file_mdbc_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_mdbc_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_mdbc_proto_goTypes = []interface{}{ + (AdminType)(0), // 0: mdbc.AdminType + (TaskState)(0), // 1: mdbc.TaskState + (*ModelFriendInfo)(nil), // 2: mdbc.ModelFriendInfo + (*ModelGroupChat)(nil), // 3: mdbc.ModelGroupChat + (*ModelGroupChatMember)(nil), // 4: mdbc.ModelGroupChatMember + (*ModelTbPrivateMsgSession)(nil), // 5: mdbc.ModelTbPrivateMsgSession + (*ModelTbGroupMsgSession)(nil), // 6: mdbc.ModelTbGroupMsgSession + (*ModelTbRobotPrivateMsg)(nil), // 7: mdbc.ModelTbRobotPrivateMsg + (*ModelTbRobotGroupMsg)(nil), // 8: mdbc.ModelTbRobotGroupMsg + (*ContentData)(nil), // 9: mdbc.ContentData + (*AtMsgItem)(nil), // 10: mdbc.AtMsgItem + (*ModelSchedTask)(nil), // 11: mdbc.ModelSchedTask + (*ModelRobotFriend)(nil), // 12: mdbc.ModelRobotFriend + (*ModelWsConnectRecord)(nil), // 13: mdbc.ModelWsConnectRecord + (*ModelRobot)(nil), // 14: mdbc.ModelRobot + (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp +} +var file_mdbc_proto_depIdxs = []int32{ + 0, // 0: mdbc.ModelGroupChat.admin_type:type_name -> mdbc.AdminType + 0, // 1: mdbc.ModelGroupChatMember.admin_type:type_name -> mdbc.AdminType + 9, // 2: mdbc.ModelTbRobotPrivateMsg.content_data:type_name -> mdbc.ContentData + 9, // 3: mdbc.ModelTbRobotGroupMsg.content_data:type_name -> mdbc.ContentData + 10, // 4: mdbc.ContentData.at_msg_item:type_name -> mdbc.AtMsgItem + 15, // 5: mdbc.ModelSchedTask.expired_at:type_name -> google.protobuf.Timestamp + 15, // 6: mdbc.ModelWsConnectRecord.expired_at:type_name -> google.protobuf.Timestamp + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name +} + +func init() { file_mdbc_proto_init() } +func file_mdbc_proto_init() { + if File_mdbc_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_mdbc_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelFriendInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelGroupChat); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelGroupChatMember); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelTbPrivateMsgSession); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelTbGroupMsgSession); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelTbRobotPrivateMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelTbRobotGroupMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ContentData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AtMsgItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelSchedTask); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelRobotFriend); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelWsConnectRecord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mdbc_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelRobot); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_mdbc_proto_rawDesc, + NumEnums: 2, + NumMessages: 13, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_mdbc_proto_goTypes, + DependencyIndexes: file_mdbc_proto_depIdxs, + EnumInfos: file_mdbc_proto_enumTypes, + MessageInfos: file_mdbc_proto_msgTypes, + }.Build() + File_mdbc_proto = out.File + file_mdbc_proto_rawDesc = nil + file_mdbc_proto_goTypes = nil + file_mdbc_proto_depIdxs = nil +} diff --git a/mdbc.proto b/mdbc.proto new file mode 100644 index 0000000..6cd9972 --- /dev/null +++ b/mdbc.proto @@ -0,0 +1,278 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +package mdbc; + +option go_package = "./;mdbc"; + + +// @table_name: tb_friends_info +message ModelFriendInfo { + string id = 1; // 主键ID wxid md5 + string wechat_id = 2; // 用户微信ID + // @bson: nick_name + string nickname = 3; // 用户暱称 + string wechat_alias = 4; // 用户微信号 + string avatar_url = 5; // 用户头像 + string phone = 6; // 手机号码 + string country = 7; // 国家 + string province = 8; // 省份 + string city = 9; // 城市 + int32 sex = 10; // 0未知 1男 2女 + int64 create_time = 12; // 创建时间 + int64 update_time = 13; // 更新时间 +} + +enum AdminType { + // @desc: 普通用户 + AdminTypeNil = 0; + // @desc: 管理员 + AdminTypeAdmin = 1; + // @desc: 群主 + AdminTypeOwner = 2; +} + +// @table_name: tb_crm_group_chat +message ModelGroupChat { + string id = 1; // 主键ID + int64 created_at = 2; // 创建时间 + int64 updated_at = 3; // 更新时间 + int64 deleted_at = 4; // 删除时间【记: 此表正常情况下 只进行软删除】非零 历史群 0正常群 + string robot_wx_id = 6; // 机器人id + string group_wx_id = 7; // 群id + string owner_wx_id = 8; // 群主id + string group_name = 9; // 群名称 + uint32 member_count = 10; // 群成员数量 + string owner_name = 11; // 群主名称 + string group_avatar_url = 12; // 群头像 + bool is_watch = 13; // 是否关注群 + bool has_been_watch = 14; // 以前有关注过 + bool is_default_group_name = 15; // 是否是默认的群名称 + bool in_contact = 16; // 是否在通讯录中 + bool disable_invite = 17; // 是否开启了群聊邀请确认 true 开启了 false 关闭了 + int64 last_sync_at = 20; // 最后更新群信息时间 【通过这里 指定规则 去拉群基本信息】 + int64 last_sync_member_at = 21; // 最后更新群成员时间 【通过这里 指定规则 去拉群成员信息】 + string notice = 22; // 群公告 + int64 qrcode_updated_at = 23; // 群聊二维码更新时间 + string qrcode_url = 24; // 群聊二维码 + AdminType admin_type = 25; // 机器人权限类型 +} + +// @table_name: tb_crm_group_chat_member +message ModelGroupChatMember { + string id = 1; // id + int64 created_at = 2; // 创建时间 + int64 updated_at = 3; // 更新时间 + int64 deleted_at = 4; // 删除时间 这个表一般直接硬删除 + string group_chat_id = 5; // 群 ModelGroupChat 的ID + string member_wx_id = 6; // 群成员微信id + string member_name = 7; // 群成员名称 + string member_avatar = 8; // 群成员头像 + string member_alias = 9; // 群昵称 + uint32 member_sex = 10; // 性别 + bool is_robot = 11; // 是否是机器人 + AdminType admin_type = 12; // 权限类型 群主 管理员 普通成员 + int64 last_sync_at = 13; // 该群该成员 最后更新时间 +} + +// @table_name: tb_crm_private_msg_session +message ModelTbPrivateMsgSession { + string id = 1; //会话ID (md5(机器人id+好友id)) + int32 all = 2; //消息最大游标(消息总数:只算有效的消息) + int32 read = 3; //已读游标 + int32 unread = 4; //未读消息游标 + int64 last_msg_at = 5; //最后一条消息时间 + int64 last_friend_msg_at = 6; //接受到最后一条好友消息时间 + string robot_wx_id = 7; //机器人id + string user_wx_id = 8; //好友微信id + string last_msg_id = 9; //最后一条消息id + string last_friend_msg_id = 10; //接收的最后一条好友消息id +} + +// @table_name: tb_crm_group_msg_session +message ModelTbGroupMsgSession { + string id = 1; //会话ID (md5(机器人id+好友id)) + int32 all = 2; //消息最大游标(消息总数:只算有效的消息) + int32 read = 3; //已读游标 + int32 unread = 4; //未读消息游标 + int64 last_msg_at = 5; //最后一条消息时间 + int64 last_friend_msg_at = 6; //接受到最后一条好友消息时间 + string robot_wx_id = 7; //机器人id + string user_wx_id = 8; //群微信id + string last_msg_id = 9; //最后一条消息id + string last_friend_msg_id = 10; //接收的最后一条好友消息id + string last_member_wx_id = 11; //最后发送消息的群成员id +} + +// @table_name: tb_crm_robot_private_msg +message ModelTbRobotPrivateMsg { + string id = 1; // 主键ID + string bind_id = 3; // 前端消息id + string robot_wx_id = 4; // 机器人id + string user_wx_id = 5; // 好友id + string msg_id = 6; // 服务端自己生成一个消息id,来对应客户端的发送结果id + int32 msg_type = 7; // 消息类型 + int32 send_status = 8; // 发送状态:0:发送中;1:发送请求成功;2:发送请求失败;3:发送成功;4:发送失败;仅机器人发送。接收到用户消息的默认3 + int32 direct = 9; // 用于区分机器人是接收方还是发送方。1:机器人接收;2:机器人发送 + int32 send_error_code = 10; // 发送错误码:用户告诉对应的是什么错误:-1 通用错误码; -2 被拉黑; -3 + // 被删除; -4 好友找不到; + bool content_read = 12; // 是否内容被浏览(像语音之类的,需要浏览) + int64 created_at = 13; // 创建时间 + int64 updated_at = 14; // 更新时间 + string fail_reason = 15; // 失败原因 + int64 call_back_at = 16; // 消息返回时间 + int64 cursor = 17; // 消息游标(对应session的all) + int64 send_at = 18; // 发送时间(消息实际生效时间) + int64 expire_at = 19; // 失效时间(用于消息的失效) + ContentData content_data = 20; // 消息内容 +} + +// @table_name: tb_crm_robot_group_msg +message ModelTbRobotGroupMsg { + string id = 1; // 主键ID + string bind_id = 3; // 前端消息id + string robot_wx_id = 4; // 机器人id + string user_wx_id = 5; // 群聊id + string msg_id = 6; // 服务端自己生成一个消息id,来对应客户端的发送结果id + int32 msg_type = 7; // 消息类型 + int32 send_status = 8; // 发送状态:0:发送中;1:发送请求成功;2:发送请求失败;3:发送成功;4:发送失败;仅机器人发送。接收到用户消息的默认3 + int32 direct = 9; // 用于区分机器人是接收方还是发送方。1:机器人接收;2:机器人发送 + int32 send_error_code = 10; // 发送错误码:用户告诉对应的是什么错误:-1 通用错误码; -2 被拉黑; -3 + // 被删除; -4 好友找不到; + bool content_read = 12; // 是否内容被浏览(像语音之类的,需要浏览) + int64 created_at = 13; // 创建时间 + int64 updated_at = 14; // 更新时间 + string fail_reason = 15; // 失败原因 + int64 call_back_at = 16; // 消息返回时间 + int64 cursor = 17; // 消息游标(对应session的all) + int64 send_at = 18; // 发送时间(消息实际生效时间) + int64 expire_at = 19; // 失效时间(用于消息的失效) + ContentData content_data = 20; // 消息内容 + string sender_wx_id = 21; // 发送者id +} + +message ContentData { + string raw_content = 1; // 元始的xml数据 做数据转发时用; + string content = 2; // 1文本的内容;2 语音的url(amr格式);6小程序的xml; + string share_title = 3; // 5链接的标题; + string share_desc = 4; // 5链接的描述; + string share_url = 5; // 5链接的URL; + string file_url = 6; // 3图片的url;4视频的Url;5链接的分享图;8表情的url(gif);9文件的url; + string share_user_name = 7; // 7名片的被分享(名片)好友id; + string share_nick_name = 8; // 7名片的被分享(名片)的昵称; + repeated AtMsgItem at_msg_item = 9; // 发送群@部分人消息的数据 + int32 wx_msg_type = 10; // 消息类型: 1 文本;2 语音;3 图片;4 视频;5 链接;6 小程序;7 + // 名片;8 表情;9 文件;10 验证消息(如好友申请);11 视频号消息;12 + // 视频号直播间;13 视频号名片; + double file_size = 11; // 文件大小KB单位 + int32 resource_duration = 12; // 媒体时长 统一单位s + repeated string at_user_name = 13; // 群聊at消息 + bool is_at_myself = 14; // 是否有at我自己 单独一个字段 方便维护和查询 +} + +message AtMsgItem { + int32 SubType = 1; // 0:文本内容,1:@某人 + string Content = 2; // 文本内容 + string UserName = 3; // @的用户(wx_id) + string NickName = 4; // @的昵称 +} + +enum TaskState { + TaskStateNil = 0; //已初始化 + TaskStateRunning = 1; //运行中 + TaskStateFailed = 2; //失败退出 + TaskStateCompleted = 3; //完成 +} + +// 异步任务 +// @table_name: tb_crm_sched_task +message ModelSchedTask { + string id = 1; //任务id + int64 created_at = 2; //创建时间 + int64 updated_at = 3; //更新时间 + uint32 task_state = 4; //执行状态 TaskState + string task_type = 5; //任务类型 自定义的名称 用来区别是哪个模块发起的任务 + string req_id = 6; //便于查询该任务 指定的id[作用:有些情况 无法直接通过id来查询该记录] + string req_json = 7; //请求内容 + string rsp_json = 8; //完成后的内容 [成功或者失败的返回] + string robot_wx_id = 9; //机器人id + google.protobuf.Timestamp expired_at = 10; //过期时间 +} + +// @table_name: tb_robot_friend +message ModelRobotFriend { + string id = 1; // 主键ID 机器人id+朋友id md5 + string robot_wechat_id = 2; // 机器人编号:微信ID + string user_wechat_id = 3; // 用户微信ID, + int64 deleted = 4; // 是否被删除 0双方未删除 1被好友删除 2删除了好友 3互相删除 + int64 offline_add = 5; // 是否为离线添加 + string remark_name = 6; // 微信好友备注名称 + string pinyin = 7; // 用户备注或者暱称的拼音 + string pinyin_head = 8; // 拼音首字母 + int64 delete_time = 9; // 删除好友的时间 + int64 create_time = 10; // 创建时间:入库时间 + int64 update_time = 11; // 更新时间 + int64 add_at = 12; // 添加好友时间只有主动添加好友才有 + string crm_phone = 13; // CRM自己设置的好友手机号,不同于微信手机号 +} + +// @table_name: tb_ws_connect_record +message ModelWsConnectRecord { + string id = 1; // 主键ID wxid md5 + string user_id = 2; // 机器人所属用户id + int64 created_at = 3; // 记录创建时间 + int64 login_at = 4; // 登录时间 + int64 logout_at = 5; // 登出时间 + string bind_id = 6; // 该ws绑定的id + google.protobuf.Timestamp expired_at = 10; // 过期时间 +} + +// @table_name: tb_robot +message ModelRobot { + // @json: _id + string id = 1; // 主键ID wxid md5 + string user_id = 2; // 机器人所属用户id + string crm_shop_id = 3; // 机器人所属商户id + string alias_name = 4; // 微信号 + string nick_name = 5; // 机器人暱称 + string wechat_id = 6; // 微信唯一ID (wxidxxxxxx) + string wechat_alias = 7; // 微信ID (用户自己定义的微信号) + string avatar_url = 8; // 机器人头像 + int32 sex = 9; // 性别 0 未知 1 男生 2 女生 + string mobile = 10; // 手机号码 + string qrcode = 11; // 机器人二维码 + int64 status = 12; // 机器人PC是否在线 10在线 11离线 (兼容之前的pc登录流程和其他接口,这个登录状态不变,补多一个字段代表安卓登录状态) + int64 limited = 13; // 机器人是否被封号 0未封号 1已封号 + int64 ability_limit = 14; // 机器人是否功能受限 + int64 init_friend = 15; // 机器人初始好友人数 + int64 now_friend = 16; // 机器人当前好友数量 + int64 auto_add_friend = 17; // 机器人是否自动通过好友请求 0否 1是 + int64 last_login_time = 18; // 最后登录时间 + int64 last_log_out_time = 19; // 最后登出时间 + string last_region_code = 20; // 最后登录的扫码设备的地区编码 + string last_city = 21; // 最后登录的城市名称 + int64 today_require_time = 22; // 当天请求次数 + int64 last_require_add_friend_time = 23; // 上一次请求添加好友的时间 + int64 crm_auto_add_friend = 24; // crm系统自动通过好友 1自动通过 0不自动通过 + int64 delete_time = 25; // 删除时间 + int64 create_time = 26; // 创建时间 + int64 update_time = 27; // 更新时间 + int64 log_and_out_time = 28; // 登入或者登出都要记录一下 + int64 android_status = 29; // 机器人Android是否在线 10在线 11离线 + string greet_id = 30; // 打招呼模板id + string android_wechat_version = 31; // 微信版本 + uint32 risk_control_group = 33; // 风控分组 + int64 last_pc_login_at = 34; // 最近PC登录时间 + int64 last_pc_logout_at = 35; // 最近PC登出时间 + int64 last_android_login_at = 36; // 最近安卓登录时间 + int64 last_android_logout_at = 37; // 最近安卓登出时间 + string risk_control_task = 38; // 风控任务 0是全部,1是回复,2是发消息,3是看朋友圈,4是发朋友圈,5是点赞,6是评论 7是群聊 可组合,如:1,2,3 + bool open_for_stranger = 39; // 是否允许陌生人查看十条朋友圈 + int32 moment_privacy_type = 40; // 朋友圈隐私选项类型 + string cover_url = 41; // 朋友圈封面url + string country = 42; // 国家 + string province = 43; // 省份 + string city = 44; // 城市 + string signature = 45; // 个性签名 +} diff --git a/mongo.go b/mongo.go new file mode 100644 index 0000000..2a8d496 --- /dev/null +++ b/mongo.go @@ -0,0 +1,200 @@ +package mdbc + +import ( + "context" + "fmt" + "time" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/readpref" +) + +type ClientInit struct { + *mongo.Client +} + +var ( + ci *ClientInit +) + +type Database struct { + *mongo.Database + dbname string +} + +type Collection struct { + *mongo.Collection + dbname string + colname string +} + +//ConnInit 初始化mongo +func ConnInit(config *Config) (*ClientInit, error) { + if config == nil { + return nil, fmt.Errorf("config nil") + } + if config.URI == "" { + return nil, fmt.Errorf("empty uri") + } + if config.MinPoolSize == 0 { + config.MinPoolSize = 1 + } + if config.MaxPoolSize == 0 { + config.MaxPoolSize = 32 + } + var timeout time.Duration + if config.ConnTimeout == 0 { + config.ConnTimeout = 10 + } + timeout = time.Duration(config.ConnTimeout) * time.Second + if config.ReadPreference == nil { + config.ReadPreference = readpref.PrimaryPreferred() + } + + op := options.Client().ApplyURI(config.URI).SetMinPoolSize(config.MinPoolSize). + SetMaxPoolSize(config.MaxPoolSize).SetConnectTimeout(timeout). + SetReadPreference(config.ReadPreference) + + if config.RegistryBuilder != nil { + op.SetRegistry(config.RegistryBuilder.Build()) + } + + c, err := mongo.NewClient(op) + if err != nil { + return nil, err + } + var ctx = context.Background() + err = c.Connect(ctx) + if err != nil { + return nil, err + } + err = c.Ping(ctx, readpref.Primary()) + if err != nil { + return nil, err + } + ci = &ClientInit{c} + return ci, nil +} + +func (c *ClientInit) Database(dbname string, opts ...*options.DatabaseOptions) *Database { + db := c.Client.Database(dbname, opts...) + return &Database{db, dbname} +} + +func (db *Database) Collection(collection string, opts ...*options.CollectionOptions) *Collection { + col := db.Database.Collection(collection, opts...) + return &Collection{col, db.dbname, collection} +} + +func (col *Collection) InsertOne(ctx context.Context, document interface{}, + opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) { + res, err := col.Collection.InsertOne(ctx, document, opts...) + return res, err +} + +func (col *Collection) InsertMany(ctx context.Context, documents []interface{}, + opts ...*options.InsertManyOptions) (*mongo.InsertManyResult, error) { + res, err := col.Collection.InsertMany(ctx, documents, opts...) + return res, err +} + +func (col *Collection) DeleteOne(ctx context.Context, filter interface{}, + opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) { + res, err := col.Collection.DeleteOne(ctx, filter, opts...) + return res, err +} + +func (col *Collection) DeleteMany(ctx context.Context, filter interface{}, + opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) { + res, err := col.Collection.DeleteMany(ctx, filter, opts...) + return res, err +} + +func (col *Collection) UpdateOne(ctx context.Context, filter interface{}, update interface{}, + opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) { + res, err := col.Collection.UpdateOne(ctx, filter, update, opts...) + return res, err +} + +func (col *Collection) UpdateMany(ctx context.Context, filter interface{}, update interface{}, + opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) { + res, err := col.Collection.UpdateMany(ctx, filter, update, opts...) + return res, err +} + +func (col *Collection) ReplaceOne(ctx context.Context, filter interface{}, + replacement interface{}, opts ...*options.ReplaceOptions) (*mongo.UpdateResult, error) { + res, err := col.Collection.ReplaceOne(ctx, filter, replacement, opts...) + return res, err +} + +func (col *Collection) Aggregate(ctx context.Context, pipeline interface{}, + opts ...*options.AggregateOptions) (*mongo.Cursor, error) { + res, err := col.Collection.Aggregate(ctx, pipeline, opts...) + return res, err +} + +func (col *Collection) CountDocuments(ctx context.Context, filter interface{}, + opts ...*options.CountOptions) (int64, error) { + res, err := col.Collection.CountDocuments(ctx, filter, opts...) + return res, err +} + +func (col *Collection) Distinct(ctx context.Context, fieldName string, filter interface{}, + opts ...*options.DistinctOptions) ([]interface{}, error) { + res, err := col.Collection.Distinct(ctx, fieldName, filter, opts...) + return res, err +} + +func (col *Collection) Find(ctx context.Context, filter interface{}, + opts ...*options.FindOptions) (*mongo.Cursor, error) { + res, err := col.Collection.Find(ctx, filter, opts...) + return res, err +} + +func (col *Collection) FindOne(ctx context.Context, filter interface{}, + opts ...*options.FindOneOptions) *mongo.SingleResult { + res := col.Collection.FindOne(ctx, filter, opts...) + return res +} + +func (col *Collection) FindOneAndDelete(ctx context.Context, filter interface{}, + opts ...*options.FindOneAndDeleteOptions) *mongo.SingleResult { + res := col.Collection.FindOneAndDelete(ctx, filter, opts...) + return res +} + +func (col *Collection) FindOneAndReplace(ctx context.Context, filter interface{}, + replacement interface{}, opts ...*options.FindOneAndReplaceOptions) *mongo.SingleResult { + res := col.Collection.FindOneAndReplace(ctx, filter, replacement, opts...) + return res +} + +func (col *Collection) FindOneAndUpdate(ctx context.Context, filter interface{}, + update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult { + res := col.Collection.FindOneAndUpdate(ctx, filter, update, opts...) + return res +} + +func (col *Collection) Watch(ctx context.Context, pipeline interface{}, + opts ...*options.ChangeStreamOptions) (*mongo.ChangeStream, error) { + res, err := col.Collection.Watch(ctx, pipeline, opts...) + return res, err +} + +func (col *Collection) Indexes(ctx context.Context) mongo.IndexView { + res := col.Collection.Indexes() + return res +} + +func (col *Collection) Drop(ctx context.Context) error { + err := col.Collection.Drop(ctx) + return err +} + +func (col *Collection) BulkWrite(ctx context.Context, models []mongo.WriteModel, + opts ...*options.BulkWriteOptions) (*mongo.BulkWriteResult, error) { + res, err := col.Collection.BulkWrite(ctx, models, opts...) + return res, err +} diff --git a/new.go b/new.go new file mode 100644 index 0000000..797d17e --- /dev/null +++ b/new.go @@ -0,0 +1,118 @@ +package mdbc + +import ( + "errors" + "fmt" + "os" + "reflect" + "runtime" + "strings" + + "google.golang.org/protobuf/proto" + + nested "github.com/antonfisher/nested-logrus-formatter" + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/mongo" +) + +func init() { + var getServeDir = func(path string) string { + var run, _ = os.Getwd() + return strings.Replace(path, run, ".", -1) + } + var formatter = &nested.Formatter{ + NoColors: false, + HideKeys: true, + TimestampFormat: "2006-01-02 15:04:05", + CallerFirst: true, + CustomCallerFormatter: func(f *runtime.Frame) string { + s := strings.Split(f.Function, ".") + funcName := s[len(s)-1] + return fmt.Sprintf(" [%s:%d][%s()]", getServeDir(f.File), f.Line, funcName) + }, + } + logrus.SetFormatter(formatter) + logrus.SetReportCaller(true) + + // 注册错误码 + register() +} + +var ( + // db 直连数据库连接句柄 + db *Database + // rawClient mongo的client客户端 是db的上级 + rawClient *mongo.Client +) + +func GetDatabase() *Database { + return db +} + +func GetMongodbClient() *mongo.Client { + return rawClient +} + +type Model struct { + Type proto.Message // 模型原形 + modelKind reflect.Type // 模型类型 + modelName string // 模型名称 + tableName string // 绑定表名 +} + +// NewModel 实例化model +func NewModel(msg interface{}) *Scope { + if db == nil { + panic("database nil") + } + + if msg == nil { + panic("proto message nil") + } + + remsg, ok := msg.(proto.Message) + if !ok { + panic("msg no match proto message") + } + m := &Model{Type: remsg} + + typ := reflect.TypeOf(m.Type) + for typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + m.modelKind = typ + + m.modelName = string(m.Type.ProtoReflect().Descriptor().FullName()) + m.tableName = GetTableName(m.Type) + + s := &Scope{ + Model: m, + } + return s +} + +// InitC 初始化client +type InitC struct{} + +// Init 初始化client +func Init(c *ClientInit) *InitC { + rawClient = c.Client + return &InitC{} +} + +// InitDB 初始化db +func (i *InitC) InitDB(c *Database) *InitC { + db = c + return &InitC{} +} + +// InitDB 兼容以前的初始化方式 +func InitDB(c *Database) { + db = c + rawClient = c.Client() +} + +// IsRecordNotFound 检测记录是否存在 +func IsRecordNotFound(err error) bool { + return errors.Is(err, &ErrRecordNotFound) +} diff --git a/scope.go b/scope.go new file mode 100644 index 0000000..5d326aa --- /dev/null +++ b/scope.go @@ -0,0 +1,221 @@ +package mdbc + +import ( + "context" + "time" + + "github.com/go-redis/redis/v8" +) + +type cacheConfig struct { + enable bool // 是否缓存 + ttl time.Duration // 缓存时间 + client redis.UniversalClient // 全局缓存redis句柄 [缓存、熔断器] +} + +type breakerConfig struct { + ttl time.Duration // 熔断时间 + reporter bool // 是否开启熔断告警 开启熔断告警 需要配置全局的redis 否则开启无效 +} + +// CacheObject 生成的缓存对象 +type CacheObject struct { + // Key redis的key + Key string + // Value redis的value 请自行marshal对象value + // 如果value是一个对象 请将该对象实现 encoding.BinaryMarshaler + // 你可以参考 DefaultFindOneCacheFunc 其利用 json.Marshal 实现了对 value 的序列化 + Value interface{} +} + +// Scope 所有操作基于此结构 自定义结构体 将其匿名以继承其子scope +type Scope struct { + *Model // 模型 + cache *cacheConfig // 缓存配置项 + breaker *breakerConfig // 熔断配置项 + execT time.Duration // 执行时间 后续debug和熔断告警用到 + debug bool // 是否开启debug + debugWhenError bool // 当错误时才输出debug信息 +} + +type ctxWrap struct { + ctx context.Context // 上下文 + cancel context.CancelFunc // 上下文取消 +} + +func (s *Scope) newDefaultCtxWrap() *ctxWrap { + return &ctxWrap{} +} + +// SetDebug 是否开启执行语句的日志输出 目前仅支持终端输出 +func (s *Scope) SetDebug(debug bool) *Scope { + s.debug = debug + return s +} + +// SetDebugError 是否开启仅当错误时打印执行语句的日志 目前仅支持终端输出 +func (s *Scope) SetDebugError(debug bool) *Scope { + s.debugWhenError = debug + return s +} + +// SetBreaker 设置熔断时间 不配置默认5s熔断 +// 此处建议在全局进行配置一次 否则会有覆盖后失效风险 +func (s *Scope) SetBreaker(duration time.Duration) *Scope { + if s.breaker == nil { + s.breaker = &breakerConfig{} + } + s.breaker.ttl = duration + return s +} + +// SetBreakerReporter 设置开启熔断告警 +func (s *Scope) SetBreakerReporter(br bool) *Scope { + if s.breaker == nil { + s.breaker = &breakerConfig{} + } + s.breaker.reporter = br + return s +} + +// SetCacheIdle 设置redis句柄 方便查询操作进行缓存 +// 此处建议在全局进行配置一次 否则会有覆盖后失效风险 +func (s *Scope) SetCacheIdle(cli redis.UniversalClient) *Scope { + if s.cache == nil { + s.cache = &cacheConfig{} + } + s.cache.client = cli + return s +} + +// SetCacheExpiredAt 设置redis缓存过期时间 不设置 则不缓存 单位time.Duration +// 此处建议在全局进行配置一次 否则会有覆盖后失效风险 +func (s *Scope) SetCacheExpiredAt(t time.Duration) *Scope { + if s.cache == nil { + s.cache = &cacheConfig{} + } + s.cache.ttl = t + s.cache.enable = true + return s +} + +// SetCacheForever 设置key永不过期 true永不过期 +// 此处建议在全局进行配置一次 否则会有覆盖后失效风险 +func (s *Scope) SetCacheForever(b bool) *Scope { + if s.cache == nil { + s.cache = &cacheConfig{} + } + if b { + s.cache.ttl = -1 + s.cache.enable = true + } + return s +} + +// GetTableName 获取当前连接的表名 +func (s *Scope) GetTableName() string { + return s.tableName +} + +// check ctx检测和初始化 +func (s *Scope) check() { + if s.breaker == nil { + s.breaker = &breakerConfig{} + } + // 设置默认熔断时间 5秒 + if s.breaker.ttl == 0 { + s.breaker.ttl = 5 * time.Second + } +} + +// RawConn 返回原始的mongoDB 操作资源句柄 +func (s *Scope) RawConn() *Collection { + return db.Collection(s.tableName) +} + +// Aggregate 聚合操作 +func (s *Scope) Aggregate() *AggregateScope { + s.check() + as := &AggregateScope{cw: s.newDefaultCtxWrap(), scope: s} + return as +} + +// Find 查询列表操作 +func (s *Scope) Find() *FindScope { + s.check() + fs := &FindScope{cw: s.newDefaultCtxWrap(), scope: s} + return fs +} + +// FindOne 查询一条操作 +func (s *Scope) FindOne() *FindOneScope { + s.check() + fs := &FindOneScope{cw: s.newDefaultCtxWrap(), scope: s} + return fs +} + +// Update 更新文档操作 +func (s *Scope) Update() *UpdateScope { + s.check() + us := &UpdateScope{cw: s.newDefaultCtxWrap(), scope: s} + return us +} + +// Delete 删除文档操作 +func (s *Scope) Delete() *DeleteScope { + s.check() + ds := &DeleteScope{cw: s.newDefaultCtxWrap(), scope: s} + return ds +} + +// Insert 插入文档操作 +func (s *Scope) Insert() *InsertScope { + s.check() + is := &InsertScope{cw: s.newDefaultCtxWrap(), scope: s} + return is +} + +// Distinct Distinct操作 +func (s *Scope) Distinct() *DistinctScope { + s.check() + ds := &DistinctScope{cw: s.newDefaultCtxWrap(), scope: s} + return ds +} + +// Index 索引操作 +func (s *Scope) Index() *IndexScope { + s.check() + is := &IndexScope{cw: s.newDefaultCtxWrap(), scope: s} + return is +} + +// BulkWrite 批量写操作 +func (s *Scope) BulkWrite() *BulkWriteScope { + s.check() + bws := &BulkWriteScope{cw: s.newDefaultCtxWrap(), scope: s} + return bws +} + +// Count 计数操作 +func (s *Scope) Count() *CountScope { + s.check() + cs := &CountScope{cw: s.newDefaultCtxWrap(), scope: s} + return cs +} + +// Drop 集合删除操作 +func (s *Scope) Drop() *DropScope { + s.check() + ds := &DropScope{cw: s.newDefaultCtxWrap(), scope: s} + return ds +} + +// Transaction 事务操作 +func (s *Scope) Transaction() *TransactionScope { + s.check() + tx := &TransactionScope{cw: s.newDefaultCtxWrap(), scope: s} + return tx +} + +// Retry mongo失败后的重试机制 +func (s *Scope) Retry(since int64) {} diff --git a/scope_test.go b/scope_test.go new file mode 100644 index 0000000..dd19b09 --- /dev/null +++ b/scope_test.go @@ -0,0 +1,32 @@ +package mdbc + +import ( + "context" + "fmt" + "testing" + "time" +) + +func TestScope_check(t *testing.T) { + var a Scope + + fmt.Println(a.execT) +} + +func TestScopeCtx(t *testing.T) { + //var ctx, cancel = context.WithTimeout(context.Background(), time.Second) + //defer cancel() + //time.Sleep(time.Second * 2) + //fmt.Println(ctx.Err()) + + var c1 = context.Background() + var c2, cancel = context.WithTimeout(c1, time.Second) + fmt.Println(c1, c2) + cancel() + cancel() + + fmt.Println("------") + + //fmt.Printf("c1: %p, c2: %p", &c1, &c2) + fmt.Println(c2.Err()) +} diff --git a/transaction_scope.go b/transaction_scope.go new file mode 100644 index 0000000..1417168 --- /dev/null +++ b/transaction_scope.go @@ -0,0 +1,144 @@ +package mdbc + +// +//// trMap 事务集合 key:string(trid) value: mongo.Session +//var trMap = sync.Map{} +// +//// ctxTrIDKey 后续用于检测session与ctx是否一致 +//const ctxTrIDKey = "mdbc-tx-id" +// +//// opentracingKey 用于链路追踪时span的存储 +//const opentracingKey = "mdbc-tx-span" +// +//// trid 事务ID +//type trid = bson.Raw +// +//func getTrID(id trid) (string, error) { +// raw, err := id.LookupErr("id") +// if err != nil { +// return "", err +// } +// _, uuid := raw.Binary() +// return hex.EncodeToString(uuid), nil +//} +// +//// equalTrID 事务ID校验 检测两个id是否相同 +//func equalTrID(a, b trid) bool { +// as, aerr := a.LookupErr("id") +// if aerr != nil { +// return false +// } +// bs, berr := b.LookupErr("id") +// if berr != nil { +// return false +// } +// +// _, auuid := as.Binary() +// _, buuid := bs.Binary() +// return bytes.Equal(auuid, buuid) +//} +// +//// checkSession 检测session是否配置 未配置 将分配一次 +//func (tr *TransactionScope) checkSession() { +// +//} +// +//// getSession 设置session +//func (tr *TransactionScope) setSession(s mongo.Session) { +// tr.session.s = s +//} +// +//// checkTransaction 检测当前事务 未开启事务而操作事务是否将触发panic +//func (tr *TransactionScope) checkTransaction() { +// +//} +// +//// setSessionID 设置session id +//func (tr *TransactionScope) setSessionID() { +// if tr.err != nil { +// return +// } +// tr.trID = tr.getSession().ID() +// trMap.Store(tr.GetTxID(), tr.session) +//} +// +//// deleteSessionID 删除session id +//func (tr *TransactionScope) deleteSessionID() { +// if tr.err != nil { +// return +// } +// +// trMap.Delete(tr.GetTxID()) +//} +// +//// getSessionID 获取session id +//func (tr *TransactionScope) getSessionID() (mongo.Session, bool) { +// if tr.err != nil { +// return nil, false +// } +// +// res, exist := trMap.Load(tr.GetTxID()) +// if !exist { +// return nil, false +// } +// +// return res.(mongo.Session), true +//} +// +//// EndSession 关闭会话 +//func (tr *TransactionScope) EndSession() *TransactionScope { +// tr.getSession().EndSession(tr.cw.ctx) +// tr.deleteSessionID() +// return tr +//} +// +//// StartTransaction 开启事务 +//// 如果session未设置或未开启 将panic +//func (tr *TransactionScope) StartTransaction() *TransactionScope { +// tr.checkSession() +// tr.err = tr.getSession().StartTransaction(tr.trOpts...) +// tr.hasStartTx = true +// return tr +//} +// +//// GetSession 获取会话 如果未设置 session 将返回 nil +//func (tr *TransactionScope) GetSession() mongo.Session { +// tr.checkSession() +// return tr.getSession() +//} +// +//// GetSessionContext 获取当前会话的上下文 +//func (tr *TransactionScope) GetSessionContext() mongo.SessionContext { +// tr.checkSession() +// return tr.session.ctx +//} +// +//// GetCollection 获取当前会话的collection +//func (tr *TransactionScope) GetCollection() *Scope { +// return tr.scope +//} +// +//// GetTxID 获取事务ID的字符串 +//func (tr *TransactionScope) GetTxID() string { +// tr.checkSession() +// if tr.trID == nil { +// tr.err = fmt.Errorf("session id empty") +// return "" +// } +// +// id, err := getTrID(tr.trID) +// if err != nil { +// tr.err = err +// return "" +// } +// +// return id +//} +// +//// Error 获取执行的error +//func (tr *TransactionScope) Error() error { +// if tr.err != nil { +// return tr.err +// } +// return nil +//} diff --git a/transaction_scope_hook.go b/transaction_scope_hook.go new file mode 100644 index 0000000..32a7851 --- /dev/null +++ b/transaction_scope_hook.go @@ -0,0 +1,61 @@ +package mdbc + +//import ( +// "context" +// "github.com/opentracing/opentracing-go" +// "github.com/opentracing/opentracing-go/ext" +//) + +//// TrFunc 用于执行一次事务用到的执行函数 +//// 在该回调函数内不要进行 提交/回滚 +//type TrFunc func(tr *TransactionScope) error +// +//// TrHook 事务钩子 对每一个事务都执行该方法而不是对每一个会话 +//type TrHook interface { +// // BeforeTransaction 执行事务之前 需要做的操作 当error时不执行事务 +// // 其执行时机是在会话创建之后 事务开启之后尚为执行之前 +// BeforeTransaction(ctx context.Context, ts *TransactionScope) error +// // AfterTransaction 执行事务之后 需要做的操作 error需要自行处理 不会对提交的事务构成变更 +// AfterTransaction(ctx context.Context, ts *TransactionScope) error +//} +// +//// AddHook 添加钩子 +//func (tr *TransactionScope) AddHook(th TrHook) *TransactionScope { +// tr.hooks = append(tr.hooks, th) +// return tr +//} +// +//// OpentracingHook 链路追踪钩子 将该事务的id和操作记录起来 +//// 当然你可以自己实现 但这里提供一个常规的同用的案例 +//type OpentracingHook struct{} +// +//// BeforeTransaction 执行事务之前 需要做的操作 当error时不执行事务 +//// 其执行时机是在会话创建之后 事务开启之后尚为执行之前 +//func (oh *OpentracingHook) BeforeTransaction(ctx context.Context, ts *TransactionScope) error { +// txID := ts.GetTrID() +// span := trace.ObtainChildSpan(ctx, "mdbc::transaction") +// span.SetTag("txid", txID) +// ctx = context.WithValue(ctx, opentracingKey, span) +// return nil +//} +// +//// AfterTransaction 执行事务之后 需要做的操作 error需要自行处理 不会对提交的事务构成变更 +//func (oh *OpentracingHook) AfterTransaction(ctx context.Context, ts *TransactionScope) error { +// spanRaw := ctx.Value(opentracingKey) +// if spanRaw == nil { +// return nil +// } +// +// span, ok := spanRaw.(opentracing.Span) +// if !ok { +// return nil +// } +// +// // 失败了 标记一下 +// if ts.Error() != nil { +// ext.Error.Set(span, true) +// } +// +// span.Finish() +// return nil +//} diff --git a/transaction_scope_test.go b/transaction_scope_test.go new file mode 100644 index 0000000..7bae224 --- /dev/null +++ b/transaction_scope_test.go @@ -0,0 +1,150 @@ +package mdbc + +//func TestTransaction(t *testing.T) { +// cfg := &Config{ +// URI: "mongodb://10.0.0.135:27117/mdbc", +// MinPoolSize: 32, +// ConnTimeout: 10, +// } +// cfg.RegistryBuilder = RegisterTimestampCodec(nil) +// client, err := ConnInit(cfg) +// +// if err != nil { +// logrus.Fatalf("get err: %+v", err) +// } +// var m = NewModel(&ModelWsConnectRecord{}) +// +// ctx := context.Background() +// +// cli := client.Client +// stx, err := cli.StartSession() +// if err != nil { +// panic(err) +// } +// +// // ef6e39c011ee4ed8bd0f82efb0fbccee +// // 80a850d0d8584668b89809e1308a9878 +// // 457188bf227341e5a4a6c3d0242d425a +// _, uuid := stx.ID().Lookup("id").Binary() +// logrus.Infof("id: %+v", hex.EncodeToString(uuid)) +// +// col := client.Database("mdbc").Collection(m.GetTableName()) +// +// if err := stx.StartTransaction(); err != nil { +// panic(err) +// } +// +// logrus.Infof("id: %+v", stx.ID().Lookup("id").String()) +// +// err = mongo.WithSession(ctx, stx, func(tx mongo.SessionContext) error { +// logrus.Infof("id: %+v", tx.ID()) +// var record ModelWsConnectRecord +// if err := col.FindOne(tx, bson.M{"_id": "87c9c841b078bd213c79b682ffbdbec7"}).Decode(&record); err != nil { +// logrus.Errorf("err: %+v", err) +// panic(err) +// } +// +// logrus.Infof("record: %+v", &record) +// +// record.BindId = "myidsssssssssss" +// if err := col.FindOneAndUpdate(tx, bson.M{"_id": "87c9c841b078bd213c79b682ffbdbec7"}, bson.M{ +// "$set": &record, +// }).Err(); err != nil { +// if err := stx.AbortTransaction(tx); err != nil { +// panic(err) +// } +// +// return err +// } +// +// if err := stx.CommitTransaction(tx); err != nil { +// panic(err) +// } +// +// logrus.Infof("id: %+v", tx.ID().Lookup("id").String()) +// +// return nil +// }) +// +// if err != nil { +// panic(err) +// } +// +// stx.EndSession(ctx) +//} +// +//func TestNewTransaction(t *testing.T) { +// cfg := &Config{ +// URI: "mongodb://mdbc:mdbc@10.0.0.135:27117/mdbc", +// MinPoolSize: 32, +// ConnTimeout: 10, +// } +// cfg.RegistryBuilder = RegisterTimestampCodec(nil) +// client, err := ConnInit(cfg) +// client.Database("") +// +// if err != nil { +// logrus.Fatalf("get err: %+v", err) +// } +// var m = NewModel(&ModelWsConnectRecord{}) +// +// err = m.Transaction().StartSession().AddHook(&OpentracingHook{}).RollbackOnError(func(tx *TransactionScope) error { +// ctx := tx.getSessionContext() +// coll := tx.scope +// var record ModelWsConnectRecord +// if err := coll.FindOne().SetContext(ctx).SetFilter(bson.M{"_id": "d1ef466f75b9a03256a8c2a3da0409a3"}).Get(&record); err != nil { +// logrus.Errorf("update err: %+v", err) +// return err +// } +// record.BindId = uuid.New().String() +// logrus.Infof("bind_id: %+v", record.BindId) +// if _, err := coll.Update().SetID("d1ef466f75b9a03256a8c2a3da0409a3").One(&record); err != nil { +// logrus.Errorf("update err: %+v", err) +// return err +// } +// return nil +// }) +// if err != nil { +// panic(err) +// } +// +// time.Sleep(3 * time.Second) +//} +// +//func TestNewWithTransaction(t *testing.T) { +// cfg := &Config{ +// URI: "mongodb://10.0.0.135:27117/mdbc", +// MinPoolSize: 32, +// ConnTimeout: 10, +// } +// cfg.RegistryBuilder = RegisterTimestampCodec(nil) +// client, err := ConnInit(cfg) +// client.Database("") +// +// if err != nil { +// logrus.Fatalf("get err: %+v", err) +// } +// var m = NewModel(&ModelWsConnectRecord{}) +// err = m.Transaction().SetContext(context.Background()).StartSession().SingleRollbackOnError(func(tx *TransactionScope) error { +// ctx := tx.getSessionContext() +// coll := tx.scope +// var record ModelWsConnectRecord +// if err := coll.FindOne().SetContext(ctx).SetFilter(bson.M{"_id": "697022b263e2c1528f32a26e704e63d6"}).Get(&record); err != nil { +// logrus.Errorf("get err: %+v", err) +// return err +// } +// record.BindId = uuid.New().String() +// logrus.Infof("bind_id: %+v", record.BindId) +// if res, err := coll.Update().SetContext(ctx).SetID("697022b263e2c1528f32a26e704e63d6").One(&record); err != nil { +// logrus.Errorf("update err: %+v", err) +// return err +// } else if res.MatchedCount != 2 { +// //return errors.New("no match") +// return nil +// } +// return nil +// }).CloseSession().Error() +// if err != nil { +// panic(err) +// } +//} diff --git a/transaction_scope_v2.go b/transaction_scope_v2.go new file mode 100644 index 0000000..02567e0 --- /dev/null +++ b/transaction_scope_v2.go @@ -0,0 +1,295 @@ +package mdbc + +import ( + "context" + "fmt" + "sync" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// trMap 事务集合 key:string(trid) value: mongo.Session +var trMap = sync.Map{} + +// trid 事务ID +type trid = bson.Raw + +// ctxTrIDKey 后续用于检测session与ctx是否一致 +const ctxTrIDKey = "mdbc-tx-id" + +// opentracingKey 用于链路追踪时span的存储 +const opentracingKey = "mdbc-tx-span" + +// TransactionScope 事务支持 +// 由于事务不太好从gotk.driver.mongodb里面实现链路追踪 所以在这里单独实现一下opentracing +type TransactionScope struct { + scope *Scope // 能力范围 + cw *ctxWrap // 操作上下文 wrap + err error // 错误 + trID trid // 事务ID + session *sessionC // 会话 + hasStartTx bool // 是否开启事务 + sOpts []*options.SessionOptions // 有关会话的隔离级别配置项 + trOpts []*options.TransactionOptions // 有关事务的隔离级别配置项 + //hooks []TrHook // 事务钩子 +} + +type sessionC struct { + s mongo.Session // 会话 + ctx mongo.SessionContext // 会话上下文 +} + +// getContext 获取操作上下文而非会话上下文 +func (tr *TransactionScope) getContext() context.Context { + return tr.cw.ctx +} + +// setContext 设置操作上下文 +func (tr *TransactionScope) setContext(ctx context.Context) { + tr.cw.ctx = ctx +} + +// checkContext 检测操作上下文是否配置 +func (tr *TransactionScope) checkContext() { + if tr.cw == nil || tr.cw.ctx == nil { + tr.err = fmt.Errorf("op context empty") + panic(tr.err) + } +} + +func (tr *TransactionScope) getSessionContext() mongo.SessionContext { + return tr.session.ctx +} + +// setSessionContext 设置会话session上下文 +func (tr *TransactionScope) setSessionContext(ctx mongo.SessionContext) { + tr.session.ctx = ctx +} + +// getSession 获取内部session +func (tr *TransactionScope) getSession() mongo.Session { + return tr.session.s +} + +func (tr *TransactionScope) getSessionC() *sessionC { + return tr.session +} + +func (tr *TransactionScope) checkSessionC() { + if tr.session == nil { + tr.session = new(sessionC) + } +} + +func (tr *TransactionScope) checkSessionPanic() { + if tr.session == nil || tr.session.ctx == nil || tr.session.s == nil { + tr.err = fmt.Errorf("session context empty") + panic(tr.err) + } +} + +// GetTrID 获取事务ID的字符串 +func (tr *TransactionScope) GetTrID() string { + tr.checkSessionPanic() + //if tr.trID == nil { + // tr.err = fmt.Errorf("session id empty") + // return "" + //} + // + //id, err := getTrID(tr.trID) + //if err != nil { + // tr.err = err + // return "" + //} + + return "id" +} + +// SetContext 设置操作的上下文 +func (tr *TransactionScope) SetContext(ctx context.Context) *TransactionScope { + tr.cw.ctx = ctx + return tr +} + +// SetSessionContext 设置会话的上下文 +func (tr *TransactionScope) SetSessionContext(ctx mongo.SessionContext) *TransactionScope { + tr.checkSessionC() + tr.setSessionContext(ctx) + return tr +} + +// NewSessionContext 生成 [会话/事务] 对应的ctx +// 如果 ctx 传递空 将panic +func (tr *TransactionScope) NewSessionContext(ctx context.Context) mongo.SessionContext { + tr.checkSessionC() + if ctx == nil { + panic("ctx nil") + } + sctx := mongo.NewSessionContext(ctx, tr.getSession()) + tr.setSessionContext(sctx) + if ctx.Value(ctxTrIDKey) == nil { + ctx = context.WithValue(ctx, ctxTrIDKey, tr.GetTrID()) + } + return sctx +} + +// SetSessionOptions 设置事务的各项机制 [读写关注 一致性] +func (tr *TransactionScope) SetSessionOptions(opts ...*options.SessionOptions) *TransactionScope { + tr.sOpts = opts + return tr +} + +// SetTransactionOptions 设置事务的各项机制 [读写关注 一致性] +func (tr *TransactionScope) SetTransactionOptions(opts ...*options.TransactionOptions) *TransactionScope { + tr.trOpts = opts + return tr +} + +// StartSession 开启一个新的session 一个session对应一个sessionID +func (tr *TransactionScope) StartSession() *TransactionScope { + // 先检测 ctx 没有设置外部的ctx 将直接PANIC + tr.checkContext() + // 检测 sessionC 不能让他为空 否则空指针异常 + tr.checkSessionC() + // 开启会话 + tr.session.s, tr.err = rawClient.StartSession(tr.sOpts...) + if tr.err != nil { + return tr + } + // 检测 session 上下文 未设置将默认指定一个 + if tr.getSessionContext() == nil { + tr.session.ctx = tr.NewSessionContext(tr.getContext()) + } + // 会话ID 先不用理会 + //tr.setSessionID() + return tr +} + +// CloseSession 关闭会话 +func (tr *TransactionScope) CloseSession() *TransactionScope { + tr.getSession().EndSession(tr.cw.ctx) + //tr.deleteSessionID() + return tr +} + +// Commit 提交事务 如果事务不存在、已提交或者已回滚 会触发error +func (tr *TransactionScope) Commit() error { + tr.checkSessionPanic() + tr.err = tr.getSession().CommitTransaction(tr.getSessionContext()) + return tr.err +} + +// Rollback 回滚事务 如果事务不存在、已提交或者已回滚 会触发error +func (tr *TransactionScope) Rollback() error { + tr.checkSessionPanic() + tr.err = tr.getSession().AbortTransaction(tr.getSessionContext()) + return tr.err +} + +// StartTransaction 开启事务 +// 如果session未设置或未开启 将panic +func (tr *TransactionScope) StartTransaction() *TransactionScope { + tr.checkSessionPanic() + tr.err = tr.getSession().StartTransaction(tr.trOpts...) + tr.hasStartTx = true + return tr +} + +// RollbackOnError 基于回调函数执行并提交事务 失败则自动回滚 +// 该方法是一个会话对应一个事务 若希望一个会话对应多个事务 请使用 SingleRollbackOnError +// 不要在 回调函数内进行 rollback 和 commit 否则该方法恒为 error +// 注:该方法会在结束后自动结束会话 +// 若希望手动结束会话 请使用 SingleRollbackOnError +//func (tr *TransactionScope) RollbackOnError(cb TrFunc) error { +// defer tr.CloseSession() +// if tr.err != nil { +// return tr.err +// } +// +// if !tr.hasStartTx { +// tr.StartTransaction() +// } +// if tr.err != nil { +// return tr.err +// } +// +// // 执行事务前钩子 +// var hookIndex int +// var retErr error +// for ; hookIndex < len(tr.hooks); hookIndex++ { +// retErr = tr.hooks[hookIndex].BeforeTransaction(tr.getContext(), tr) +// if retErr != nil { +// return retErr +// } +// } +// +// tr.err = cb(tr) +// if tr.err != nil { +// if err := tr.Rollback(); err != nil { +// return &ErrRollbackTransaction +// } +// return core.CreateErrorWithMsg(ErrOpTransaction, fmt.Sprintf("tx rollback: %+v", tr.err)) +// } +// +// tr.err = tr.Commit() +// if tr.err != nil { +// return core.CreateErrorWithMsg(ErrOpTransaction, fmt.Sprintf("commit tx error: %+v", tr.err)) +// } +// +// // 执行事务后钩子 +// var aHookIndex int +// for ; aHookIndex < len(tr.hooks); aHookIndex++ { +// tr.err = tr.hooks[aHookIndex].AfterTransaction(tr.getContext(), tr) +// if tr.err != nil { +// return tr.err +// } +// } +// +// return nil +//} + +// SingleRollbackOnError 基于已有会话 通过链式调用 可以多次单个事务执行 +// 注:请手动结束会话 EndSession +//func (tr *TransactionScope) SingleRollbackOnError(cb TrFunc) *TransactionScope { +// if tr.err != nil { +// return tr +// } +// +// tr.checkSessionPanic() +// +// // 执行事务前钩子 +// var bHookIndex int +// for ; bHookIndex < len(tr.hooks); bHookIndex++ { +// tr.err = tr.hooks[bHookIndex].BeforeTransaction(tr.getContext(), tr) +// if tr.err != nil { +// return tr +// } +// } +// +// // 该方法会自动提交 失败自动回滚 +// _, tr.err = tr.getSession().WithTransaction(tr.getSessionContext(), +// func(sessCtx mongo.SessionContext) (interface{}, error) { +// return nil, cb(tr) +// }) +// +// // 执行事务后钩子 +// var aHookIndex int +// for ; aHookIndex < len(tr.hooks); aHookIndex++ { +// tr.err = tr.hooks[aHookIndex].AfterTransaction(tr.getContext(), tr) +// if tr.err != nil { +// return tr +// } +// } +// +// return tr +//} + +// Error 获取执行的error +func (tr *TransactionScope) Error() error { + if tr.err != nil { + return tr.err + } + return nil +} diff --git a/update_module.sh b/update_module.sh new file mode 100755 index 0000000..50e7bc6 --- /dev/null +++ b/update_module.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# 该脚本用来自动更新module + +go mod tidy + +match_required=$(cat go.mod | grep -zoE "\((.*?)\)" | awk -F ' ' '{print $1}' | awk '{if($1>1){print $1}}') + +for i in $match_required;do go get -u "$i";done diff --git a/update_scope.go b/update_scope.go new file mode 100644 index 0000000..9d678ba --- /dev/null +++ b/update_scope.go @@ -0,0 +1,404 @@ +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 updateAction string + +const ( + updateOne updateAction = "updateOne" + updateMany updateAction = "updateMany" +) + +type UpdateScope struct { + scope *Scope + cw *ctxWrap + err error + upsert *bool + id interface{} + filter interface{} + action updateAction + updateData interface{} + opts *options.UpdateOptions + result *mongo.UpdateResult +} + +// SetContext 设置上下文 +func (us *UpdateScope) SetContext(ctx context.Context) *UpdateScope { + if us.cw == nil { + us.cw = &ctxWrap{} + } + us.cw.ctx = ctx + return us +} + +func (us *UpdateScope) getContext() context.Context { + return us.cw.ctx +} + +// SetUpsert 设置upsert属性 +func (us *UpdateScope) SetUpsert(upsert bool) *UpdateScope { + us.upsert = &upsert + return us +} + +// SetID 基于ID进行文档更新 这将忽略 filter 只按照ID过滤 +func (us *UpdateScope) SetID(id interface{}) *UpdateScope { + us.id = id + return us +} + +// SetUpdateOption 设置UpdateOption +func (us *UpdateScope) SetUpdateOption(opts options.UpdateOptions) *UpdateScope { + us.opts = &opts + return us +} + +// SetFilter 设置过滤条件 +func (us *UpdateScope) SetFilter(filter interface{}) *UpdateScope { + if filter == nil { + us.filter = bson.M{} + return us + } + + v := reflect.ValueOf(filter) + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Map || v.Kind() == reflect.Slice { + if v.IsNil() { + us.filter = bson.M{} + } + } + us.filter = filter + return us +} + +func (us *UpdateScope) optionAssembled() { + // 配置项被直接调用重写过 + if us.opts != nil { + return + } + + us.opts = new(options.UpdateOptions) + if us.upsert != nil { + us.opts.Upsert = us.upsert + } +} + +func (us *UpdateScope) assertErr() { + if us.err == nil { + return + } + if errors.Is(us.err, context.DeadlineExceeded) { + us.err = &ErrRequestBroken + return + } + err, ok := us.err.(mongo.CommandError) + if !ok { + return + } + if err.HasErrorMessage(context.DeadlineExceeded.Error()) { + us.err = &ErrRequestBroken + return + } +} + +func (us *UpdateScope) debug() { + if !us.scope.debug && !us.scope.debugWhenError { + return + } + + debugger := &Debugger{ + collection: us.scope.tableName, + execT: us.scope.execT, + action: us, + } + + // 当错误时优先输出 + if us.scope.debugWhenError { + if us.err != nil { + debugger.errMsg = us.err.Error() + debugger.ErrorString() + } + return + } + + // 所有bug输出 + if us.scope.debug { + debugger.String() + } +} + +func (us *UpdateScope) doReporter() string { + debugger := &Debugger{ + collection: us.scope.tableName, + execT: us.scope.execT, + action: us, + } + return debugger.Echo() +} + +func (us *UpdateScope) reporter() { + br := &BreakerReporter{ + reportTitle: "mdbc::update", + reportMsg: "熔断错误", + reportErrorWrap: us.err, + breakerDo: us, + } + if us.err == &ErrRequestBroken { + br.Report() + } +} + +func (us *UpdateScope) doString() string { + builder := RegisterTimestampCodec(nil).Build() + switch us.action { + case updateOne: + filter, _ := bson.MarshalExtJSONWithRegistry(builder, us.filter, true, true) + updateData, _ := bson.MarshalExtJSONWithRegistry(builder, us.updateData, true, true) + return fmt.Sprintf("updateOne(%s, %s)", string(filter), string(updateData)) + case updateMany: + filter, _ := bson.MarshalExtJSONWithRegistry(builder, us.filter, true, true) + updateData, _ := bson.MarshalExtJSONWithRegistry(builder, us.updateData, true, true) + return fmt.Sprintf("updateMany(%s, %s)", string(filter), string(updateData)) + default: + panic("not support update type") + } +} + +// preCheck 预检查 +func (us *UpdateScope) preCheck() { + var breakerTTL time.Duration + if us.scope.breaker == nil { + breakerTTL = defaultBreakerTime + } else if us.scope.breaker.ttl == 0 { + breakerTTL = defaultBreakerTime + } else { + breakerTTL = us.scope.breaker.ttl + } + if us.cw == nil { + us.cw = &ctxWrap{} + } + if us.cw.ctx == nil { + us.cw.ctx, us.cw.cancel = context.WithTimeout(context.Background(), breakerTTL) + } +} + +func (us *UpdateScope) doUpdate(isMany bool) { + if isMany { + var starTime time.Time + if us.scope.debug { + starTime = time.Now() + } + us.result, us.err = db.Collection(us.scope.tableName).UpdateMany(us.getContext(), us.filter, us.updateData, us.opts) + us.assertErr() + if us.scope.debug { + us.scope.execT = time.Since(starTime) + us.action = updateMany + us.debug() + } + return + } + + if us.id != nil { + var starTime time.Time + if us.scope.debug { + starTime = time.Now() + } + us.result, us.err = db.Collection(us.scope.tableName).UpdateByID(us.getContext(), us.id, us.updateData, us.opts) + us.assertErr() + if us.scope.debug { + us.scope.execT = time.Since(starTime) + us.filter = bson.M{"_id": us.id} + us.action = updateOne + us.debug() + } + return + } + + var starTime time.Time + if us.scope.debug { + starTime = time.Now() + } + us.result, us.err = db.Collection(us.scope.tableName).UpdateOne(us.getContext(), us.filter, us.updateData, us.opts) + us.assertErr() + if us.scope.debug { + us.scope.execT = time.Since(starTime) + us.action = updateOne + us.debug() + } +} + +func (us *UpdateScope) doClear() { + if us.cw != nil && us.cw.cancel != nil { + us.cw.cancel() + } + us.scope.execT = 0 + us.scope.debug = false +} + +// checkObj 检测需要更新的obj是否正常 没有$set将被注入 +func (us *UpdateScope) checkObj(obj interface{}) { + if obj == nil { + us.err = &ErrObjectEmpty + return + } + us.updateData = obj + + vo := reflect.ValueOf(obj) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + if vo.Kind() == reflect.Struct { + us.updateData = bson.M{ + "$set": obj, + } + return + } + if vo.Kind() == reflect.Map { + setKey := vo.MapIndex(reflect.ValueOf("$set")) + if !setKey.IsValid() { + us.updateData = bson.M{ + "$set": obj, + } + } + return + } + + us.err = &ErrUpdateObjectTypeNotSupport +} + +// checkMapSetObj 检测需要更新的obj是否正常 +func (us *UpdateScope) checkMustMapObj(obj interface{}) { + if obj == nil { + us.err = &ErrObjectEmpty + return + } + us.updateData = obj + + vo := reflect.ValueOf(obj) + if vo.Kind() == reflect.Ptr { + vo = vo.Elem() + } + + if vo.Kind() == reflect.Map { + return + } + + us.err = &ErrUpdateObjectTypeNotSupport +} + +// One 单个更新 +// obj: 为*struct时将被自动转换为bson.M并注入$set +// 当指定为map时 若未指定$set 会自动注入$set +// 当指定 非 *struct/ map 类型时 将报错 +func (us *UpdateScope) One(obj interface{}) (*mongo.UpdateResult, error) { + defer us.doClear() + us.optionAssembled() + us.preCheck() + + us.checkObj(obj) + if us.err != nil { + return nil, us.err + } + us.doUpdate(false) + if us.err != nil { + return nil, us.err + } + + return us.result, nil +} + +// MustMapOne 单个更新 必须传入map并强制更新这个map +func (us *UpdateScope) MustMapOne(obj interface{}) (*mongo.UpdateResult, error) { + defer us.doClear() + us.optionAssembled() + us.preCheck() + + us.checkMustMapObj(obj) + if us.err != nil { + return nil, us.err + } + us.doUpdate(false) + if us.err != nil { + return nil, us.err + } + + return us.result, nil +} + +// RawOne 单个更新 不对更新内容进行校验 +func (us *UpdateScope) RawOne(obj interface{}) (*mongo.UpdateResult, error) { + defer us.doClear() + us.optionAssembled() + us.preCheck() + + us.updateData = obj + + us.doUpdate(false) + if us.err != nil { + return nil, us.err + } + + return us.result, nil +} + +// Many 批量更新 +// obj: 为*struct时将被自动转换为bson.M并注入$set +// 当指定为map时 若未指定$set 会自动注入$set +// 当指定 非 *struct/ map 类型时 将报错 +func (us *UpdateScope) Many(obj interface{}) (*mongo.UpdateResult, error) { + defer us.doClear() + us.optionAssembled() + us.checkObj(obj) + if us.err != nil { + return nil, us.err + } + us.doUpdate(true) + + if us.err != nil { + return nil, us.err + } + + return us.result, nil +} + +// MustMapMany 批量更新 必须传入map并强制更新这个map +func (us *UpdateScope) MustMapMany(obj interface{}) (*mongo.UpdateResult, error) { + defer us.doClear() + us.optionAssembled() + us.checkMustMapObj(obj) + if us.err != nil { + return nil, us.err + } + us.doUpdate(true) + + if us.err != nil { + return nil, us.err + } + + return us.result, nil +} + +// RawMany 批量更新 不对更新内容进行校验 +func (us *UpdateScope) RawMany(obj interface{}) (*mongo.UpdateResult, error) { + defer us.doClear() + us.optionAssembled() + us.preCheck() + + us.updateData = obj + + us.doUpdate(true) + if us.err != nil { + return nil, us.err + } + + return us.result, nil +} diff --git a/update_scope_test.go b/update_scope_test.go new file mode 100644 index 0000000..a28937a --- /dev/null +++ b/update_scope_test.go @@ -0,0 +1,82 @@ +package mdbc + +import ( + "context" + "fmt" + "testing" + + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" +) + +func TestUpdateScope_One(t *testing.T) { + cfg := &Config{ + URI: "mongodb://admin:admin@10.0.0.135:27017/admin", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + Init(client).InitDB(client.Database("mdbc")) + + var m = NewModel(&ModelSchedTask{}) + + var record = ModelSchedTask{Id: "0e434c7d5c9eb8b129ce42ce07afef09"} + if err := m.SetDebug(true).FindOne().SetFilter(bson.M{"_id": record.Id}).Get(&record); err != nil { + logrus.Errorf("get err: %+v", err) + } + + fmt.Printf("record %+v\n", record) + var ctx = context.Background() + + record.TaskState = uint32(TaskState_TaskStateCompleted) + + updateData := bson.M{ + "task_state": uint32(TaskState_TaskStateFailed), + } + _, err = m.SetDebug(true).Update().SetContext(ctx).SetFilter(bson.M{"_id": record.Id}).One(updateData) + if err != nil { + logrus.Errorf("get err: %+v", err) + } + + updateData = bson.M{ + "task_state": uint32(TaskState_TaskStateFailed), + } + _, err = m.SetDebug(true).Update().SetFilter(bson.M{"_id": record.Id}).One(updateData) + if err != nil { + logrus.Errorf("get err: %+v", err) + } +} + +func TestUpdateScope_Many(t *testing.T) { + cfg := &Config{ + URI: "mongodb://mdbc:mdbc@10.0.0.135:27117/admin", + MinPoolSize: 32, + ConnTimeout: 10, + } + cfg.RegistryBuilder = RegisterTimestampCodec(nil) + client, err := ConnInit(cfg) + + if err != nil { + logrus.Fatalf("get err: %+v", err) + } + Init(client).InitDB(client.Database("mdbc")) + + var m = NewModel(&ModelSchedTask{}) + + //updateData := bson.M{ + // "$inc": bson.M{ + // "set_group_name": 1, + // }, + //} + res, err := m.SetDebug(true).Update().SetFilter(bson.M{"task_type": "set_group_name"}).MustMapMany(m) + if err != nil { + logrus.Errorf("get err: %+v", err) + panic(err) + } + logrus.Infof("%+v", res) +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..a01c263 --- /dev/null +++ b/utils.go @@ -0,0 +1,43 @@ +package mdbc + +import "reflect" + +type StructField struct { + StructFieldName string + DbFieldName string +} + +type tabler interface { + TableName() string +} + +// GetTableName 获取表名 +func GetTableName(i interface{}) string { + if p, ok := i.(tabler); ok { + return p.TableName() + } + return "" +} + +// isReflectNumber 检测是否是数值类型 +func isReflectNumber(obj reflect.Value) bool { + var k = obj.Kind() + if k == reflect.Int || k == reflect.Int8 || k == reflect.Int16 || k == reflect.Int32 || + k == reflect.Int64 || k == reflect.Uint || k == reflect.Uint8 || k == reflect.Uint16 || + k == reflect.Uint32 || k == reflect.Uint64 || k == reflect.Float32 || k == reflect.Float64 { + return true + } + return false +} + +// getReflectTypeField 获取j指定的字段的field 没有获取到则返回原字段名称 +func getBsonTagByReflectTypeField(rt reflect.Type, fieldName string) string { + for i := 0; i < rt.NumField(); i++ { + field := rt.Field(i) + if field.Name != fieldName { + continue + } + return field.Tag.Get("bson") + } + return fieldName +}