2026-05-28 16:39:51 +08:00
|
|
|
|
package syncer
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"xk-hy-transit-go/internal/applog"
|
|
|
|
|
|
|
|
|
|
|
|
"xk-hy-transit-go/internal/config"
|
|
|
|
|
|
|
|
|
|
|
|
"xk-hy-transit-go/internal/fileauth"
|
|
|
|
|
|
|
|
|
|
|
|
"xk-hy-transit-go/internal/hy"
|
|
|
|
|
|
|
|
|
|
|
|
"xk-hy-transit-go/internal/xkapi"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ConfigSnapshot 当前 Runner 配置快照(明文仅进程内使用,Web 层需脱敏)。
|
|
|
|
|
|
|
|
|
|
|
|
type ConfigSnapshot struct {
|
|
|
|
|
|
Cloud xkapi.ConfigResponse
|
|
|
|
|
|
|
|
|
|
|
|
CloudOK bool
|
|
|
|
|
|
|
|
|
|
|
|
CloudFetchError string
|
|
|
|
|
|
|
|
|
|
|
|
RefreshedAt time.Time
|
|
|
|
|
|
|
|
|
|
|
|
Local config.Config
|
|
|
|
|
|
|
|
|
|
|
|
EffectiveOrgan hy.OrganConfig
|
|
|
|
|
|
|
|
|
|
|
|
EffectiveAppKey string
|
|
|
|
|
|
|
|
|
|
|
|
EffectiveSecret string
|
|
|
|
|
|
|
|
|
|
|
|
EffectiveAesKey string
|
|
|
|
|
|
|
|
|
|
|
|
EffectiveFileBucket string
|
|
|
|
|
|
|
|
|
|
|
|
UploadToken string
|
|
|
|
|
|
|
|
|
|
|
|
UploadTokenSource string
|
|
|
|
|
|
|
|
|
|
|
|
CloudUploadToken string
|
|
|
|
|
|
|
|
|
|
|
|
FileUploadURL string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func firstNonEmpty(values ...string) string {
|
|
|
|
|
|
|
|
|
|
|
|
for _, v := range values {
|
|
|
|
|
|
|
|
|
|
|
|
if strings.TrimSpace(v) != "" {
|
|
|
|
|
|
|
|
|
|
|
|
return strings.TrimSpace(v)
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *Runner) applyCloudConfig(conf *xkapi.ConfigResponse) {
|
|
|
|
|
|
|
|
|
|
|
|
if conf == nil {
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
r.organ = hy.OrganConfig{
|
|
|
|
|
|
|
|
|
|
|
|
UnitID: conf.UnitID,
|
|
|
|
|
|
|
|
|
|
|
|
OrganID: conf.OrganID,
|
|
|
|
|
|
|
|
|
|
|
|
OrganName: conf.OrganName,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
r.cloudUploadToken = conf.UploadToken
|
|
|
|
|
|
|
|
|
|
|
|
r.fileUploadURL = resolveFileUploadURL(r.cfg, conf.FileUploadURL)
|
|
|
|
|
|
|
|
|
|
|
|
r.hyAppKey = firstNonEmpty(conf.AppKey, r.cfg.HyAppKey)
|
|
|
|
|
|
|
|
|
|
|
|
r.hyAppSecret = firstNonEmpty(conf.AppSecret, r.cfg.HyAppSecret)
|
|
|
|
|
|
|
|
|
|
|
|
r.hyAesKey = firstNonEmpty(conf.AesKey, r.cfg.HyAesKey)
|
|
|
|
|
|
|
|
|
|
|
|
r.fileBucket = firstNonEmpty(conf.FileBucket, r.cfg.HyFileBucket)
|
|
|
|
|
|
|
|
|
|
|
|
r.lastCloud = *conf
|
|
|
|
|
|
|
|
|
|
|
|
r.lastCloudOK = true
|
|
|
|
|
|
|
|
|
|
|
|
r.lastConfigErr = nil
|
|
|
|
|
|
|
|
|
|
|
|
r.configRefreshedAt = time.Now()
|
|
|
|
|
|
|
|
|
|
|
|
if err := r.refreshUploadTokenLocked(); err != nil {
|
|
|
|
|
|
|
|
|
|
|
|
r.uploadToken = ""
|
|
|
|
|
|
|
|
|
|
|
|
applog.Appf("warning: generate uploadToken failed: %v", err)
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-28 17:03:08 +08:00
|
|
|
|
// refreshUploadToken 按 FileAuth.java upload profile 重新生成 uploadToken(scope=仅 bucket)。
|
2026-05-28 16:39:51 +08:00
|
|
|
|
|
|
|
|
|
|
func (r *Runner) refreshUploadToken() error {
|
|
|
|
|
|
|
|
|
|
|
|
r.configMu.Lock()
|
|
|
|
|
|
|
|
|
|
|
|
defer r.configMu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
return r.refreshUploadTokenLocked()
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (r *Runner) refreshUploadTokenLocked() error {
|
|
|
|
|
|
|
|
|
|
|
|
key := strings.TrimSpace(r.hyAppKey)
|
|
|
|
|
|
|
|
|
|
|
|
secret := strings.TrimSpace(r.hyAppSecret)
|
|
|
|
|
|
|
|
|
|
|
|
bucket := strings.TrimSpace(r.fileBucket)
|
|
|
|
|
|
|
|
|
|
|
|
if key == "" || secret == "" || bucket == "" {
|
|
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("appKey/appSecret/fileBucket 未配置(检查 xk-api HY_APP_KEY、HY_APP_SECRET、HY_FILE_BUCKET)")
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tok, err := fileauth.CreateUploadToken(key, secret, bucket, 0)
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
r.uploadToken = tok
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ReloadCloudConfig 从 xk-api GET /supervise/config 刷新机构、凭证与 uploadToken。
|
|
|
|
|
|
|
|
|
|
|
|
func (r *Runner) ReloadCloudConfig() error {
|
|
|
|
|
|
|
|
|
|
|
|
r.configMu.Lock()
|
|
|
|
|
|
|
|
|
|
|
|
defer r.configMu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
conf, err := r.xk.Config()
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
|
|
|
|
r.lastCloudOK = false
|
|
|
|
|
|
|
|
|
|
|
|
r.lastConfigErr = err
|
|
|
|
|
|
|
|
|
|
|
|
applog.Appf("reload cloud config failed: %v", err)
|
|
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
r.applyCloudConfig(conf)
|
|
|
|
|
|
|
|
|
|
|
|
if strings.TrimSpace(r.uploadToken) == "" {
|
|
|
|
|
|
|
|
|
|
|
|
applog.Appf("warning: uploadToken empty after config reload")
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
applog.Appf("cloud config reloaded organID=%s appKey=%s…", conf.OrganID, previewKey(r.hyAppKey))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ConfigSnapshot 返回当前配置快照(读锁)。
|
|
|
|
|
|
|
|
|
|
|
|
func (r *Runner) ConfigSnapshot() ConfigSnapshot {
|
|
|
|
|
|
|
|
|
|
|
|
r.configMu.RLock()
|
|
|
|
|
|
|
|
|
|
|
|
defer r.configMu.RUnlock()
|
|
|
|
|
|
|
|
|
|
|
|
snap := ConfigSnapshot{
|
|
|
|
|
|
|
|
|
|
|
|
Cloud: r.lastCloud,
|
|
|
|
|
|
|
|
|
|
|
|
CloudOK: r.lastCloudOK,
|
|
|
|
|
|
|
|
|
|
|
|
RefreshedAt: r.configRefreshedAt,
|
|
|
|
|
|
|
|
|
|
|
|
Local: r.cfg,
|
|
|
|
|
|
|
|
|
|
|
|
EffectiveOrgan: r.organ,
|
|
|
|
|
|
|
|
|
|
|
|
EffectiveAppKey: r.hyAppKey,
|
|
|
|
|
|
|
|
|
|
|
|
EffectiveSecret: r.hyAppSecret,
|
|
|
|
|
|
|
|
|
|
|
|
EffectiveAesKey: r.hyAesKey,
|
|
|
|
|
|
|
|
|
|
|
|
EffectiveFileBucket: r.fileBucket,
|
|
|
|
|
|
|
|
|
|
|
|
UploadToken: r.uploadToken,
|
|
|
|
|
|
|
2026-05-28 17:03:08 +08:00
|
|
|
|
UploadTokenSource: "local-fileauth-java",
|
2026-05-28 16:39:51 +08:00
|
|
|
|
|
|
|
|
|
|
CloudUploadToken: r.cloudUploadToken,
|
|
|
|
|
|
|
|
|
|
|
|
FileUploadURL: r.fileUploadURL,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if r.lastConfigErr != nil {
|
|
|
|
|
|
|
|
|
|
|
|
snap.CloudFetchError = r.lastConfigErr.Error()
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return snap
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func previewKey(s string) string {
|
|
|
|
|
|
|
|
|
|
|
|
s = strings.TrimSpace(s)
|
|
|
|
|
|
|
|
|
|
|
|
if len(s) <= 12 {
|
|
|
|
|
|
|
|
|
|
|
|
if s == "" {
|
|
|
|
|
|
|
|
|
|
|
|
return "(empty)"
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return s[:8] + "…"
|
|
|
|
|
|
|
|
|
|
|
|
}
|