202 lines
6.7 KiB
Go
202 lines
6.7 KiB
Go
package sync
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
consts "dataengine/consts/public"
|
|
dao "dataengine/dao/dict"
|
|
dto "dataengine/model/dto/dict"
|
|
|
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
|
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// SyncRunItemResult 单次同步执行结果
|
|
type SyncRunItemResult struct {
|
|
PlatformCode string `json:"platformCode"`
|
|
InterfaceCode string `json:"interfaceCode"`
|
|
Success bool `json:"success"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// StartAutoSync 启动自动同步(独立 goroutine,每次完成后等待 interval 再执行下一次)
|
|
func StartAutoSync(ctx context.Context) {
|
|
interval := GetSyncInterval(ctx)
|
|
logrus.Infof("自动同步调度器启动,间隔: %d 分钟(完成一次后开始计时)", interval)
|
|
|
|
for {
|
|
runAutoSync(ctx, false) // 后台调度不关心返回结果,错误已内部打日志
|
|
logrus.Infof("自动同步完成,等待 %d 分钟后执行下一次", interval)
|
|
time.Sleep(time.Duration(interval) * time.Minute)
|
|
}
|
|
}
|
|
|
|
// determineSyncMode 判断本次同步应该增量还是全量
|
|
// 返回 true=全量, false=增量
|
|
func determineSyncMode(ctx context.Context) bool {
|
|
interval := GetFullSyncIntervalHours(ctx)
|
|
if interval <= 0 {
|
|
return false // 自动全量关闭,永远增量
|
|
}
|
|
|
|
// 查询 sync_tracker 中最早的全量同步时间
|
|
v, err := gfdb.DB(ctx).Model(ctx, consts.SyncTrackerTable).
|
|
Value("COALESCE(MIN(last_full_sync_time), 0)")
|
|
if err != nil {
|
|
logrus.Warnf("查询全量同步时间失败,回退增量模式: %v", err)
|
|
return false
|
|
}
|
|
minFullSync := int64(0)
|
|
if v != nil {
|
|
minFullSync = v.Int64()
|
|
}
|
|
|
|
if minFullSync == 0 {
|
|
logrus.Info("检测到从未全量同步的接口,本次执行全量同步")
|
|
return true
|
|
}
|
|
|
|
elapsed := time.Now().Unix() - minFullSync
|
|
if elapsed > int64(interval)*3600 {
|
|
logrus.Infof("距上次全量已过 %d 小时(阈值 %d 小时),本次执行全量同步", elapsed/3600, interval)
|
|
return true
|
|
}
|
|
|
|
logrus.Debugf("距上次全量 %d 小时,未到阈值 %d 小时,执行增量同步", elapsed/3600, interval)
|
|
return false
|
|
}
|
|
|
|
func runAutoSync(ctx context.Context, forceFull bool) []SyncRunItemResult {
|
|
logrus.Info("=== 开始自动同步 ===")
|
|
|
|
// 判断本次同步模式(增量 / 全量)
|
|
isFullSync := forceFull || determineSyncMode(ctx)
|
|
logrus.Infof("本次同步模式: %s", map[bool]string{true: "全量", false: "增量"}[isFullSync])
|
|
|
|
// 从配置读取同步租户 ID(运维部署时配置)
|
|
tenantId := g.Cfg().MustGet(ctx, "sync.default_tenant_id", 1).Uint64()
|
|
|
|
// 注入用户上下文(ORM 框架需要用于租户隔离)
|
|
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: tenantId})
|
|
|
|
// 查询所有 ACTIVE 平台
|
|
platforms, _, err := dao.DatasourcePlatform.List(ctx, &dto.ListDatasourcePlatformReq{
|
|
Status: "ACTIVE",
|
|
}, tenantId)
|
|
if err != nil {
|
|
logrus.Errorf("查询平台列表失败: %v", err)
|
|
return nil
|
|
}
|
|
|
|
var results []SyncRunItemResult
|
|
|
|
for _, p := range platforms {
|
|
// 查询该平台下有 table_definition 的接口
|
|
interfaces, _, err := dao.ApiInterface.List(ctx, &dto.ListApiInterfaceReq{
|
|
PlatformId: p.Id,
|
|
Status: "active",
|
|
}, tenantId)
|
|
if err != nil {
|
|
logrus.Errorf("查询接口列表失败 [platform=%s]: %v", p.PlatformCode, err)
|
|
continue
|
|
}
|
|
|
|
for _, iface := range interfaces {
|
|
if iface.TableDefinition == nil || len(iface.TableDefinition) == 0 {
|
|
continue
|
|
}
|
|
|
|
logrus.Infof("自动同步: %s / %s", p.PlatformCode, iface.Code)
|
|
|
|
_, err := SyncByConfig(ctx, p.PlatformCode, iface.Code, isFullSync)
|
|
|
|
item := SyncRunItemResult{
|
|
PlatformCode: p.PlatformCode,
|
|
InterfaceCode: iface.Code,
|
|
}
|
|
if err != nil {
|
|
logrus.Errorf("自动同步失败 [%s/%s]: %v", p.PlatformCode, iface.Code, err)
|
|
item.Success = false
|
|
item.Error = err.Error()
|
|
results = append(results, item)
|
|
// token 过期是平台级别问题,该平台剩余接口继续请求只会重复失败,直接跳过
|
|
if isTokenExpiredError(err) {
|
|
logrus.Warnf("平台 [%s] token 已过期,跳过该平台剩余接口", p.PlatformCode)
|
|
break
|
|
}
|
|
} else {
|
|
item.Success = true
|
|
results = append(results, item)
|
|
}
|
|
}
|
|
}
|
|
|
|
logrus.Info("=== 自动同步完成 ===")
|
|
return results
|
|
}
|
|
|
|
// TriggerAllSync 手动触发全量同步(等价于 runAutoSync 的一次执行)
|
|
// 由 HTTP 端点调用,用于 PPGo_Job 调度。
|
|
// forceFull=true 强制全量,false 则由自动策略判断。
|
|
// 返回每个平台接口的执行结果列表。
|
|
func TriggerAllSync(ctx context.Context, forceFull bool) []SyncRunItemResult {
|
|
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: g.Cfg().MustGet(ctx, "sync.default_tenant_id", 1).Uint64()})
|
|
return runAutoSync(ctx, forceFull)
|
|
}
|
|
|
|
// RecoverInterruptedSyncs 服务启动时恢复异常中断的同步
|
|
// 扫描 sync_tracker 中 sync_status="running" 的接口,自动重新全量同步
|
|
// 适用于服务进程崩溃后重启的场景,不等 PPGo_Job 下轮调度
|
|
func RecoverInterruptedSyncs(ctx context.Context) {
|
|
type trackerItem struct {
|
|
PlatformCode string
|
|
InterfaceCode string
|
|
}
|
|
var items []trackerItem
|
|
err := gfdb.DB(ctx).Model(ctx, consts.SyncTrackerTable).
|
|
Fields("platform_code", "interface_code").
|
|
Where("sync_status", "running").
|
|
Scan(&items)
|
|
if err != nil {
|
|
logrus.Warnf("查询中断同步任务失败: %v", err)
|
|
return
|
|
}
|
|
if len(items) == 0 {
|
|
logrus.Info("启动恢复扫描:没有发现异常中断的同步任务")
|
|
return
|
|
}
|
|
|
|
tenantId := g.Cfg().MustGet(ctx, "sync.default_tenant_id", 1).Uint64()
|
|
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: tenantId})
|
|
|
|
logrus.Infof("启动恢复扫描:发现 %d 个异常中断的同步任务,开始恢复...", len(items))
|
|
for _, item := range items {
|
|
logrus.Infof("正在恢复中断的同步 [%s/%s]...", item.PlatformCode, item.InterfaceCode)
|
|
_, err := SyncByConfig(ctx, item.PlatformCode, item.InterfaceCode, true)
|
|
if err != nil {
|
|
logrus.Errorf("恢复同步失败 [%s/%s]: %v", item.PlatformCode, item.InterfaceCode, err)
|
|
} else {
|
|
logrus.Infof("恢复同步成功 [%s/%s]", item.PlatformCode, item.InterfaceCode)
|
|
}
|
|
}
|
|
}
|
|
|
|
// InitAndStartAutoSync 在 main 中调用:初始化配置后启动自动同步和补偿
|
|
func InitAndStartAutoSync(ctx context.Context) {
|
|
// 服务启动时恢复异常中断的同步(不等 PPGo_Job 调度)
|
|
RecoverInterruptedSyncs(ctx)
|
|
|
|
// 读取配置中的同步开关
|
|
enabled := g.Cfg().MustGet(ctx, "sync.auto_sync_enabled", false).Bool()
|
|
if enabled {
|
|
go StartAutoSync(ctx)
|
|
} else {
|
|
logrus.Info("自动同步已关闭")
|
|
}
|
|
// 补偿调度器独立启动,不受 auto_sync_enabled 控制
|
|
go StartCompensation(ctx)
|
|
}
|