package logweb import ( "strings" "time" "xk-hy-transit-go/internal/config" syncer "xk-hy-transit-go/internal/sync" "xk-hy-transit-go/internal/xkapi" ) // ConfigViewResponse Web 配置页 API 响应(敏感字段已脱敏)。 type ConfigViewResponse struct { FetchedAt string `json:"fetchedAt"` XkAPIBaseURL string `json:"xkApiBaseUrl"` Cloud cloudConfigView `json:"cloud"` LocalEnv localEnvView `json:"localEnv"` Effective effectiveConfigView `json:"effective"` Diagnostics []string `json:"diagnostics"` CloudRaw map[string]any `json:"cloudRaw,omitempty"` } type cloudConfigView struct { OrganID string `json:"organID"` UnitID string `json:"unitID"` OrganName string `json:"organName"` HosCode string `json:"hosCode"` HosName string `json:"hosName"` FileBucket string `json:"fileBucket"` FileUploadURL string `json:"fileUploadUrl"` AppKeyPreview string `json:"appKeyPreview"` AppSecretSet bool `json:"appSecretSet"` AesKeySet bool `json:"aesKeySet"` UploadTokenSet bool `json:"uploadTokenSet"` UploadTokenPreview string `json:"uploadTokenPreview"` RawError string `json:"rawError"` } type localEnvView struct { HyAppKeyPreview string `json:"hyAppKeyPreview"` HyAppSecretSet bool `json:"hyAppSecretSet"` HyAesKeySet bool `json:"hyAesKeySet"` HyFileBucket string `json:"hyFileBucket"` FileUploadViaForward bool `json:"fileUploadViaForward"` ForwardBaseURL string `json:"forwardBaseUrl"` ForwardSharedSecretSet bool `json:"forwardSharedSecretSet"` XkAPITokenSet bool `json:"xkApiTokenSet"` } type effectiveConfigView struct { OrganID string `json:"organID"` UnitID string `json:"unitID"` OrganName string `json:"organName"` AppKeyPreview string `json:"appKeyPreview"` AppSecretSet bool `json:"appSecretSet"` AesKeySet bool `json:"aesKeySet"` FileBucket string `json:"fileBucket"` UploadTokenSet bool `json:"uploadTokenSet"` UploadTokenPreview string `json:"uploadTokenPreview"` UploadTokenSource string `json:"uploadTokenSource"` FileUploadURL string `json:"fileUploadURL"` SourceNote string `json:"sourceNote"` ConfigRefreshed string `json:"configRefreshedAt"` } func maskPreview(s string) string { s = strings.TrimSpace(s) if s == "" { return "" } if len(s) <= 12 { return s } return s[:8] + "****" + s[len(s)-4:] } func BuildConfigView(snap syncer.ConfigSnapshot, cfg config.Config) ConfigViewResponse { cloud := snap.Cloud localToken := strings.TrimSpace(snap.UploadToken) cloudToken := strings.TrimSpace(snap.CloudUploadToken) if cloudToken == "" { cloudToken = strings.TrimSpace(cloud.UploadToken) } effectiveBucket := strings.TrimSpace(snap.EffectiveFileBucket) view := ConfigViewResponse{ FetchedAt: time.Now().Format(time.RFC3339), XkAPIBaseURL: cfg.XkAPIBaseURL, Cloud: cloudConfigView{ OrganID: cloud.OrganID, UnitID: cloud.UnitID, OrganName: cloud.OrganName, HosCode: cloud.HosCode, HosName: cloud.HosName, FileBucket: cloud.FileBucket, FileUploadURL: cloud.FileUploadURL, AppKeyPreview: maskPreview(cloud.AppKey), AppSecretSet: strings.TrimSpace(cloud.AppSecret) != "", AesKeySet: strings.TrimSpace(cloud.AesKey) != "", UploadTokenSet: cloudToken != "", UploadTokenPreview: maskPreview(cloudToken), RawError: snap.CloudFetchError, }, LocalEnv: localEnvView{ HyAppKeyPreview: maskPreview(cfg.HyAppKey), HyAppSecretSet: strings.TrimSpace(cfg.HyAppSecret) != "", HyAesKeySet: strings.TrimSpace(cfg.HyAesKey) != "", HyFileBucket: cfg.HyFileBucket, FileUploadViaForward: cfg.FileUploadViaForward, ForwardBaseURL: cfg.ForwardBaseURL, ForwardSharedSecretSet: strings.TrimSpace(cfg.ForwardSharedSecret) != "", XkAPITokenSet: strings.TrimSpace(cfg.XkAPIToken) != "", }, Effective: effectiveConfigView{ OrganID: snap.EffectiveOrgan.OrganID, UnitID: snap.EffectiveOrgan.UnitID, OrganName: snap.EffectiveOrgan.OrganName, AppKeyPreview: maskPreview(snap.EffectiveAppKey), AppSecretSet: strings.TrimSpace(snap.EffectiveSecret) != "", AesKeySet: strings.TrimSpace(snap.EffectiveAesKey) != "", FileBucket: effectiveBucket, UploadTokenSet: localToken != "", UploadTokenPreview: maskPreview(localToken), UploadTokenSource: snap.UploadTokenSource, FileUploadURL: snap.FileUploadURL, SourceNote: "uploadToken:Runner 按 FileAuth.java 本地生成;appKey/secret/bucket 云端非空优先", ConfigRefreshed: formatTime(snap.RefreshedAt), }, } if !snap.CloudOK { view.Diagnostics = append(view.Diagnostics, "无法连接 xk-api /supervise/config:"+snap.CloudFetchError) } if effectiveBucket == "" { view.Diagnostics = append(view.Diagnostics, "fileBucket 为空:请在 xk-api 配置 HY_FILE_BUCKET(或 transit .env HY_FILE_BUCKET)") } if !view.Effective.UploadTokenSet { view.Diagnostics = append(view.Diagnostics, "本地 uploadToken 未生成:检查 HY_APP_KEY、HY_APP_SECRET、HY_FILE_BUCKET 后点「重新拉取」") } if cloudToken != "" && localToken != "" && cloudToken != localToken { view.Diagnostics = append(view.Diagnostics, "云端预生成 uploadToken 与 Runner 本地 token 不一致(已使用本地 FileAuth.java,以 Runner 为准)") } if cloud.AppKey != "" && cfg.HyAppKey != "" && cloud.AppKey != cfg.HyAppKey { view.Diagnostics = append(view.Diagnostics, "云端 appKey 与 transit 本地 HY_APP_KEY 不一致,生效值以云端为准,请点「重新拉取」") } if view.Effective.AppKeyPreview == "" { view.Diagnostics = append(view.Diagnostics, "生效 appKey 为空:请在 xk-api 或 transit .env 配置 HY_APP_KEY") } if cfg.FileUploadViaForward { if view.LocalEnv.ForwardSharedSecretSet { view.Diagnostics = append(view.Diagnostics, "PDF 经 forward 上传:请确认 forward .env 的 FORWARD_SHARED_SECRET 与 transit 一致") } view.Diagnostics = append(view.Diagnostics, "PDF 经 forward 上传:请确认 forward ALLOW_IPS 包含中转机 IP(否则 HTTP 403 body=forbidden)") } view.CloudRaw = redactCloudRaw(cloud) return view } func formatTime(t time.Time) string { if t.IsZero() { return "-" } return t.Format(time.RFC3339) } func redactCloudRaw(c xkapi.ConfigResponse) map[string]any { return map[string]any{ "organID": c.OrganID, "unitID": c.UnitID, "organName": c.OrganName, "hosCode": c.HosCode, "hosName": c.HosName, "fileUploadUrl": c.FileUploadURL, "fileBucket": c.FileBucket, "appKey": maskPreview(c.AppKey), "appSecret": boolLabel(c.AppSecret != ""), "aesKey": boolLabel(c.AesKey != ""), "uploadToken": maskPreview(c.UploadToken), } } func boolLabel(set bool) string { if set { return "(已配置)" } return "(未配置)" }