2026-05-28 16:39:51 +08:00
|
|
|
|
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,
|
|
|
|
|
|
|
2026-05-28 17:03:08 +08:00
|
|
|
|
SourceNote: "uploadToken:Runner 按 FileAuth.java 本地生成;appKey/secret/bucket 云端非空优先",
|
2026-05-28 16:39:51 +08:00
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
|
|
2026-05-28 17:03:08 +08:00
|
|
|
|
view.Diagnostics = append(view.Diagnostics, "云端预生成 uploadToken 与 Runner 本地 token 不一致(已使用本地 FileAuth.java,以 Runner 为准)")
|
2026-05-28 16:39:51 +08:00
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 "(未配置)"
|
|
|
|
|
|
|
|
|
|
|
|
}
|