You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
openim-sdk-cpp/go/chao-sdk-core/internal/incrversion/option.go

265 lines
7.2 KiB

package incrversion
import (
"context"
"reflect"
"sort"
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface"
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil"
"gorm.io/gorm"
)
type VersionSynchronizer[V, R any] struct {
Ctx context.Context
DB db_interface.VersionSyncModel
TableName string
EntityID string
Key func(V) string
Local func() ([]V, error)
ServerVersion func() R
Server func(version *model_struct.LocalVersionSync) (R, error)
Full func(resp R) bool
Version func(resp R) (string, uint64)
Delete func(resp R) []string
Update func(resp R) []V
Insert func(resp R) []V
ExtraData func(resp R) any
ExtraDataProcessor func(ctx context.Context, data any) error
Syncer func(server, local []V) error
FullSyncer func(ctx context.Context) error
FullID func(ctx context.Context) ([]string, error)
}
func (o *VersionSynchronizer[V, R]) getVersionInfo() (*model_struct.LocalVersionSync, error) {
versionInfo, err := o.DB.GetVersionSync(o.Ctx, o.TableName, o.EntityID)
if err != nil && errs.Unwrap(err) != gorm.ErrRecordNotFound {
log.ZWarn(o.Ctx, "get version info", err)
return nil, err
}
return versionInfo, nil
}
func (o *VersionSynchronizer[V, R]) updateVersionInfo(lvs *model_struct.LocalVersionSync, resp R) error {
lvs.Table = o.TableName
lvs.EntityID = o.EntityID
lvs.VersionID, lvs.Version = o.Version(resp)
return o.DB.SetVersionSync(o.Ctx, lvs)
}
func judgeInterfaceIsNil(data any) bool {
return reflect.ValueOf(data).Kind() == reflect.Ptr && reflect.ValueOf(data).IsNil()
}
func (o *VersionSynchronizer[V, R]) Sync() error {
var lvs *model_struct.LocalVersionSync
var resp R
var extraData any
if o.ServerVersion == nil {
var err error
lvs, err = o.getVersionInfo()
if err != nil {
return err
}
resp, err = o.Server(lvs)
if err != nil {
return err
}
} else {
var err error
lvs, err = o.getVersionInfo()
if err != nil {
return err
}
resp = o.ServerVersion()
}
delIDs := o.Delete(resp)
changes := o.Update(resp)
insert := o.Insert(resp)
if o.ExtraData != nil {
temp := o.ExtraData(resp)
if !judgeInterfaceIsNil(temp) {
extraData = temp
}
}
if len(delIDs) == 0 && len(changes) == 0 && len(insert) == 0 && !o.Full(resp) && extraData == nil {
log.ZDebug(o.Ctx, "no data to sync", "table", o.TableName, "entityID", o.EntityID)
return nil
}
if o.Full(resp) {
err := o.FullSyncer(o.Ctx)
if err != nil {
return err
}
lvs.UIDList, err = o.FullID(o.Ctx)
if err != nil {
return err
}
} else {
if len(delIDs) > 0 {
lvs.UIDList = DeleteElements(lvs.UIDList, delIDs)
}
if len(insert) > 0 {
lvs.UIDList = append(lvs.UIDList, datautil.Slice(insert, o.Key)...)
}
local, err := o.Local()
if err != nil {
return err
}
kv := datautil.SliceToMapAny(local, func(v V) (string, V) {
return o.Key(v), v
})
changes = append(changes, insert...)
for i, change := range changes {
key := o.Key(change)
kv[key] = changes[i]
}
for _, id := range delIDs {
delete(kv, id)
}
server := datautil.Values(kv)
if err := o.Syncer(server, local); err != nil {
return err
}
if extraData != nil && o.ExtraDataProcessor != nil {
if err := o.ExtraDataProcessor(o.Ctx, extraData); err != nil {
return err
}
}
}
return o.updateVersionInfo(lvs, resp)
}
func (o *VersionSynchronizer[V, R]) CheckVersionSync() error {
lvs, err := o.getVersionInfo()
if err != nil {
return err
}
var extraData any
resp := o.ServerVersion()
delIDs := o.Delete(resp)
changes := o.Update(resp)
insert := o.Insert(resp)
versionID, version := o.Version(resp)
if o.ExtraData != nil {
temp := o.ExtraData(resp)
if !judgeInterfaceIsNil(temp) {
extraData = temp
}
}
if len(delIDs) == 0 && len(changes) == 0 && len(insert) == 0 && !o.Full(resp) && extraData == nil {
log.ZWarn(o.Ctx, "exception no data to sync", errs.New("notification no data"), "table", o.TableName, "entityID", o.EntityID)
return nil
}
log.ZDebug(o.Ctx, "check version sync", "table", o.TableName, "entityID", o.EntityID, "versionID", versionID, "localVersionID", lvs.VersionID, "version", version, "localVersion", lvs.Version)
/// If the version unique ID cannot correspond with the local version,
// it indicates that the data might have been tampered with or an exception has occurred.
//Trigger the complete client-server incremental synchronization.
if versionID != lvs.VersionID {
log.ZDebug(o.Ctx, "version id not match", errs.New("version id not match"), "versionID", versionID, "localVersionID", lvs.VersionID)
o.ServerVersion = nil
return o.Sync()
}
if lvs.Version+1 == version {
if len(delIDs) > 0 {
lvs.UIDList = DeleteElements(lvs.UIDList, delIDs)
}
if len(insert) > 0 {
lvs.UIDList = append(lvs.UIDList, datautil.Slice(insert, o.Key)...)
}
local, err := o.Local()
if err != nil {
return err
}
kv := datautil.SliceToMapAny(local, func(v V) (string, V) {
return o.Key(v), v
})
changes = append(changes, insert...)
for i, change := range changes {
key := o.Key(change)
kv[key] = changes[i]
}
for _, id := range delIDs {
delete(kv, id)
}
server := datautil.Values(kv)
if err := o.Syncer(server, local); err != nil {
return err
}
if extraData != nil && o.ExtraDataProcessor != nil {
if err := o.ExtraDataProcessor(o.Ctx, extraData); err != nil {
return err
}
}
return o.updateVersionInfo(lvs, resp)
} else if version <= lvs.Version {
log.ZWarn(o.Ctx, "version less than local version", errs.New("version less than local version"),
"table", o.TableName, "entityID", o.EntityID, "version", version, "localVersion", lvs.Version)
return nil
} else {
// If the version number has a gap with the local version number,
//it indicates that some pushed data might be missing.
//Trigger the complete client-server incremental synchronization.
o.ServerVersion = nil
return o.Sync()
}
}
// DeleteElements 删除切片中包含在另一个切片中的元素,并保持切片顺序
func DeleteElements[E comparable](es []E, toDelete []E) []E {
// 将要删除的元素存储在哈希集合中
deleteSet := make(map[E]struct{}, len(toDelete))
for _, e := range toDelete {
deleteSet[e] = struct{}{}
}
// 通过一个索引 j 来跟踪新的切片位置
j := 0
for _, e := range es {
if _, found := deleteSet[e]; !found {
es[j] = e
j++
}
}
return es[:j]
}
// DeleteElement 删除切片中的指定元素,并保持切片顺序
func DeleteElement[E comparable](es []E, element E) []E {
j := 0
for _, e := range es {
if e != element {
es[j] = e
j++
}
}
return es[:j]
}
// Slice Converts slice types in batches and sorts the resulting slice using a custom comparator
func Slice[E any, T any](es []E, fn func(e E) T, less func(a, b T) bool) []T {
// 转换切片
v := make([]T, len(es))
for i := 0; i < len(es); i++ {
v[i] = fn(es[i])
}
// 排序切片
sort.Slice(v, func(i, j int) bool {
return less(v[i], v[j])
})
return v
}