🐛 fix(driver): 修复达梦驱动安装误走无效直链

Fixes #320
This commit is contained in:
Syngnat
2026-04-11 21:53:52 +08:00
parent 426ef3bcf6
commit 1c2377bc62
3 changed files with 142 additions and 42 deletions

View File

@@ -22,9 +22,9 @@
| #317 | 驱动管理增加导入 jar 功能 | Blocked | - |
| #318 | mysql,bit 列,修改成 1 失败 | Fixed | `bee78be` |
| #319 | 关于运行外部 sql 文件的一些建议 | Deferred | - |
| #320 | 无法连接达梦数据库 | Investigating | - |
| #320 | 无法连接达梦数据库 | Fixed | Pending |
| #327 | SHOW DATABASES 报错 | Fixed | `5ac0221` |
| #328 | [Bug] 安装更新失败 | Fixed | Pending |
| #328 | [Bug] 安装更新失败 | Fixed | `436f130` |
## Notes
@@ -48,7 +48,9 @@
### #320
- 达梦当前走可选 Go 驱动代理安装链路,不支持 JAR 导入属于既有架构边界。
- “卡在 20%” 初步定位在驱动下载阶段前的占位进度,后续需要补充下载链路诊断与失败可观测性后再落地修复
- 根因:驱动 release 资产缓存把 `GoNavi-DriverAgents.zip` 里的 bundle 条目也混进了“顶层已发布 asset”集合导致安装链路误以为存在单独的 `dameng-driver-agent-*.exe` 下载地址
- 处理:缓存层区分真实 release 顶层 asset 与 bundle index 条目,安装 URL 解析仅在真实顶层 asset 存在时才走直链bundle-only 驱动改为直接进入总包提取回退,不再先卡在 20% 试无效 URL。
- 验证:补充 `internal/app/methods_driver_version_test.go` 回归测试,覆盖 bundle-only 达梦驱动跳过伪直链,并回归 Mongo 历史版本与本地导入链路。
### #327

View File

@@ -235,9 +235,10 @@ type driverVersionOptionItem struct {
}
type driverReleaseAssetSizeCacheEntry struct {
LoadedAt time.Time
SizeByKey map[string]int64
Err string
LoadedAt time.Time
SizeByKey map[string]int64
PublishedAssets map[string]bool
Err string
}
type goModuleLatestVersionCacheEntry struct {
@@ -666,7 +667,7 @@ func (a *App) GetDriverVersionPackageSize(driverType string, version string) con
tag := "v" + normalizedVersion
sizeBytes := int64(0)
sizeSource := ""
if sizeByAsset, err := loadReleaseAssetSizesCached("tag:"+tag, func() (*githubRelease, error) {
if sizeByAsset, _, err := loadReleaseAssetSizesCached("tag:"+tag, func() (*githubRelease, error) {
return fetchReleaseByTag(tag)
}); err == nil {
sizeBytes = resolveOptionalDriverAssetSizeForVersion(sizeByAsset, normalizedType, normalizedVersion)
@@ -676,7 +677,7 @@ func (a *App) GetDriverVersionPackageSize(driverType string, version string) con
}
allowLatestFallback := sameDriverVersion(normalizedVersion, definition.PinnedVersion) || sameDriverVersion(normalizedVersion, latestDriverVersionMap[normalizedType])
if sizeBytes <= 0 && allowLatestFallback {
if sizeByAsset, err := loadReleaseAssetSizesCached("latest", fetchLatestReleaseForDriverAssets); err == nil {
if sizeByAsset, _, err := loadReleaseAssetSizesCached("latest", fetchLatestReleaseForDriverAssets); err == nil {
sizeBytes = resolveOptionalDriverAssetSizeForVersion(sizeByAsset, normalizedType, normalizedVersion)
if sizeBytes > 0 {
sizeSource = "latest"
@@ -1785,23 +1786,23 @@ func resolvePublishedDriverReleaseAssetName(driverType string, version string, t
}
cacheKey := "tag:" + strings.TrimSpace(tag)
if sizeByAsset, ok := readReleaseAssetSizesFromCache(cacheKey); ok {
if sizeByAsset, publishedAssets, ok := readReleaseAssetSizesFromCache(cacheKey); ok {
for _, assetName := range assetNames {
if sizeByAsset[assetName] > 0 {
if publishedAssets[assetName] && sizeByAsset[assetName] > 0 {
return assetName, true
}
}
return "", false
}
sizeByAsset, err := loadReleaseAssetSizesCached(cacheKey, func() (*githubRelease, error) {
sizeByAsset, publishedAssets, err := loadReleaseAssetSizesCached(cacheKey, func() (*githubRelease, error) {
return fetchReleaseByTag(tag)
})
if err != nil {
return "", false
}
for _, assetName := range assetNames {
if sizeByAsset[assetName] > 0 {
if publishedAssets[assetName] && sizeByAsset[assetName] > 0 {
return assetName, true
}
}
@@ -1827,13 +1828,13 @@ func resolveDriverVersionPackageSizeBytes(definition driverDefinition, option dr
}
tag := "v" + version
if sizeByAsset, ok := readReleaseAssetSizesFromCache("tag:" + tag); ok {
if sizeByAsset, _, ok := readReleaseAssetSizesFromCache("tag:" + tag); ok {
return resolveOptionalDriverAssetSizeForVersion(sizeByAsset, driverType, version)
}
// 下拉版本列表要求快速返回:仅复用已有缓存,不在这里触发网络请求。
if strings.EqualFold(strings.TrimSpace(option.Source), "latest") {
if sizeByAsset, ok := readReleaseAssetSizesFromCache("latest"); ok {
if sizeByAsset, _, ok := readReleaseAssetSizesFromCache("latest"); ok {
return resolveOptionalDriverAssetSizeForVersion(sizeByAsset, driverType, version)
}
}
@@ -1942,7 +1943,7 @@ func triggerDriverVersionMetadataWarmup(definitions []driverDefinition) {
go func(paths []string) {
defer finishDriverVersionMetadataWarmup()
// 预热 latest 资产索引,便于版本列表命中大小缓存。
_, _ = loadReleaseAssetSizesCached("latest", fetchLatestReleaseForDriverAssets)
_, _, _ = loadReleaseAssetSizesCached("latest", fetchLatestReleaseForDriverAssets)
for _, modulePath := range paths {
_ = fetchGoModuleVersionMetasCached(modulePath)
}
@@ -3687,7 +3688,6 @@ func resolveOptionalDriverBundleDownloadURLs() []string {
}
func resolveOptionalDriverAgentDownloadURLs(definition driverDefinition, rawURL string, selectedVersion string) []string {
driverType := normalizeDriverType(definition.Type)
candidates := make([]string, 0, 3)
seen := make(map[string]struct{}, 3)
appendURL := func(value string) {
@@ -3712,15 +3712,14 @@ func resolveOptionalDriverAgentDownloadURLs(definition driverDefinition, rawURL
return candidates
}
assetNames := optionalDriverReleaseAssetNames(driverType)
currentVersion := normalizeVersion(getCurrentVersion())
if currentVersion != "" && currentVersion != "0.0.0" {
for _, assetName := range assetNames {
appendURL(fmt.Sprintf("https://github.com/Syngnat/GoNavi/releases/download/v%s/%s", currentVersion, assetName))
if publishedURL, ok := resolvePublishedDriverDownloadURL(definition, currentVersion); ok {
appendURL(publishedURL)
}
}
for _, assetName := range assetNames {
appendURL(fmt.Sprintf("https://github.com/Syngnat/GoNavi/releases/latest/download/%s", assetName))
if publishedURL, ok := resolveLatestPublishedDriverDownloadURL(definition); ok {
appendURL(publishedURL)
}
return candidates
}
@@ -3967,7 +3966,7 @@ func preloadOptionalDriverPackageSizes(definitions []driverDefinition) map[strin
pending := needed
if tag != "" {
if sizeByAsset, err := loadReleaseAssetSizesCached("tag:"+tag, func() (*githubRelease, error) {
if sizeByAsset, _, err := loadReleaseAssetSizesCached("tag:"+tag, func() (*githubRelease, error) {
return fetchReleaseByTag(tag)
}); err == nil {
pending = fillFromSizes(sizeByAsset, pending)
@@ -3976,16 +3975,16 @@ func preloadOptionalDriverPackageSizes(definitions []driverDefinition) map[strin
if len(pending) == 0 {
return result
}
if sizeByAsset, err := loadReleaseAssetSizesCached("latest", fetchLatestReleaseForDriverAssets); err == nil {
if sizeByAsset, _, err := loadReleaseAssetSizesCached("latest", fetchLatestReleaseForDriverAssets); err == nil {
_ = fillFromSizes(sizeByAsset, pending)
}
return result
}
func loadReleaseAssetSizesCached(cacheKey string, fetch func() (*githubRelease, error)) (map[string]int64, error) {
func loadReleaseAssetSizesCached(cacheKey string, fetch func() (*githubRelease, error)) (map[string]int64, map[string]bool, error) {
key := strings.TrimSpace(cacheKey)
if key == "" {
return nil, fmt.Errorf("缓存 key 为空")
return nil, nil, fmt.Errorf("缓存 key 为空")
}
driverReleaseSizeMu.RLock()
@@ -3998,21 +3997,23 @@ func loadReleaseAssetSizesCached(cacheKey string, fetch func() (*githubRelease,
}
if time.Since(cached.LoadedAt) < ttl {
if strings.TrimSpace(cached.Err) != "" {
return nil, errors.New(strings.TrimSpace(cached.Err))
return nil, nil, errors.New(strings.TrimSpace(cached.Err))
}
return cached.SizeByKey, nil
return cached.SizeByKey, cached.PublishedAssets, nil
}
}
release, err := fetch()
entry := driverReleaseAssetSizeCacheEntry{
LoadedAt: time.Now(),
SizeByKey: map[string]int64{},
LoadedAt: time.Now(),
SizeByKey: map[string]int64{},
PublishedAssets: map[string]bool{},
}
if err != nil {
entry.Err = err.Error()
} else {
entry.SizeByKey = buildReleaseAssetSizeMap(release)
entry.PublishedAssets = buildReleaseAssetNameMap(release)
if indexSizes, indexErr := fetchDriverBundleAssetSizeIndex(release); indexErr == nil {
for name, size := range indexSizes {
trimmedName := strings.TrimSpace(name)
@@ -4029,22 +4030,22 @@ func loadReleaseAssetSizesCached(cacheKey string, fetch func() (*githubRelease,
driverReleaseSizeMu.Unlock()
if err != nil {
return nil, err
return nil, nil, err
}
return entry.SizeByKey, nil
return entry.SizeByKey, entry.PublishedAssets, nil
}
func readReleaseAssetSizesFromCache(cacheKey string) (map[string]int64, bool) {
func readReleaseAssetSizesFromCache(cacheKey string) (map[string]int64, map[string]bool, bool) {
key := strings.TrimSpace(cacheKey)
if key == "" {
return nil, false
return nil, nil, false
}
driverReleaseSizeMu.RLock()
cached, ok := driverReleaseSizeMap[key]
driverReleaseSizeMu.RUnlock()
if !ok {
return nil, false
return nil, nil, false
}
ttl := driverReleaseAssetSizeCacheTTL
@@ -4052,12 +4053,12 @@ func readReleaseAssetSizesFromCache(cacheKey string) (map[string]int64, bool) {
ttl = driverReleaseAssetSizeErrorCacheTTL
}
if time.Since(cached.LoadedAt) >= ttl {
return nil, false
return nil, nil, false
}
if strings.TrimSpace(cached.Err) != "" {
return nil, false
return nil, nil, false
}
return cached.SizeByKey, true
return cached.SizeByKey, cached.PublishedAssets, true
}
func buildReleaseAssetSizeMap(release *githubRelease) map[string]int64 {
@@ -4075,6 +4076,21 @@ func buildReleaseAssetSizeMap(release *githubRelease) map[string]int64 {
return sizes
}
func buildReleaseAssetNameMap(release *githubRelease) map[string]bool {
names := make(map[string]bool)
if release == nil {
return names
}
for _, asset := range release.Assets {
name := strings.TrimSpace(asset.Name)
if name == "" {
continue
}
names[name] = true
}
return names
}
func fetchDriverBundleAssetSizeIndex(release *githubRelease) (map[string]int64, error) {
if release == nil {
return nil, fmt.Errorf("release 为空")
@@ -4123,6 +4139,37 @@ func fetchLatestReleaseForDriverAssets() (*githubRelease, error) {
return fetchDriverReleaseByURL(updateAPIURL)
}
func resolveLatestPublishedDriverDownloadURL(definition driverDefinition) (string, bool) {
driverType := normalizeDriverType(definition.Type)
if driverType == "" {
return "", false
}
assetNames := optionalDriverReleaseAssetNames(driverType)
if len(assetNames) == 0 {
return "", false
}
if sizeByAsset, publishedAssets, ok := readReleaseAssetSizesFromCache("latest"); ok {
for _, assetName := range assetNames {
if publishedAssets[assetName] && sizeByAsset[assetName] > 0 {
return fmt.Sprintf("https://github.com/%s/releases/latest/download/%s", updateRepo, assetName), true
}
}
return "", false
}
sizeByAsset, publishedAssets, err := loadReleaseAssetSizesCached("latest", fetchLatestReleaseForDriverAssets)
if err != nil {
return "", false
}
for _, assetName := range assetNames {
if publishedAssets[assetName] && sizeByAsset[assetName] > 0 {
return fmt.Sprintf("https://github.com/%s/releases/latest/download/%s", updateRepo, assetName), true
}
}
return "", false
}
func fetchReleaseByTag(tag string) (*githubRelease, error) {
tagName := strings.TrimSpace(tag)
if tagName == "" {

View File

@@ -129,6 +129,27 @@ func TestResolveOptionalDriverAgentDownloadURLsDoesNotFallbackForHistoricalVersi
}
}
func TestResolveOptionalDriverAgentDownloadURLsSkipsBundleOnlyDamengAsset(t *testing.T) {
definition, ok := resolveDriverDefinition("dameng")
if !ok {
t.Fatal("expected dameng driver definition")
}
version := normalizeVersion(definition.PinnedVersion)
assetName := optionalDriverReleaseAssetNameForVersion("dameng", version)
seedReleaseAssetCacheEntry(t, "tag:v"+version, map[string]int64{
assetName: 23 << 20,
}, nil)
seedReleaseAssetCacheEntry(t, "latest", map[string]int64{
assetName: 23 << 20,
}, nil)
urls := resolveOptionalDriverAgentDownloadURLs(definition, "builtin://activate/dameng", version)
if len(urls) != 0 {
t.Fatalf("expected bundle-only dameng install to skip direct asset URLs, got %v", urls)
}
}
func TestDownloadDriverPackageRejectsUnsupportedMongoVersion(t *testing.T) {
app := &App{}
@@ -219,11 +240,18 @@ func TestInstallOptionalDriverAgentFromLocalPathSupportsMongoV1ZipImport(t *test
func seedReleaseAssetSizeCache(t *testing.T, cacheKey string, sizeByKey map[string]int64) {
t.Helper()
seedReleaseAssetCacheEntry(t, cacheKey, sizeByKey, sizeByKey)
}
func seedReleaseAssetCacheEntry(t *testing.T, cacheKey string, sizeByKey map[string]int64, publishedAssets map[string]int64) {
t.Helper()
driverReleaseSizeMu.Lock()
original := cloneReleaseAssetSizeCache(driverReleaseSizeMap)
driverReleaseSizeMap[cacheKey] = driverReleaseAssetSizeCacheEntry{
LoadedAt: time.Now(),
SizeByKey: cloneInt64Map(sizeByKey),
LoadedAt: time.Now(),
SizeByKey: cloneInt64Map(sizeByKey),
PublishedAssets: cloneBoolMapFromSizes(publishedAssets),
}
driverReleaseSizeMu.Unlock()
@@ -238,14 +266,37 @@ func cloneReleaseAssetSizeCache(src map[string]driverReleaseAssetSizeCacheEntry)
cloned := make(map[string]driverReleaseAssetSizeCacheEntry, len(src))
for key, value := range src {
cloned[key] = driverReleaseAssetSizeCacheEntry{
LoadedAt: value.LoadedAt,
SizeByKey: cloneInt64Map(value.SizeByKey),
Err: value.Err,
LoadedAt: value.LoadedAt,
SizeByKey: cloneInt64Map(value.SizeByKey),
PublishedAssets: cloneBoolMap(value.PublishedAssets),
Err: value.Err,
}
}
return cloned
}
func cloneBoolMap(src map[string]bool) map[string]bool {
if len(src) == 0 {
return map[string]bool{}
}
cloned := make(map[string]bool, len(src))
for key, value := range src {
cloned[key] = value
}
return cloned
}
func cloneBoolMapFromSizes(src map[string]int64) map[string]bool {
if len(src) == 0 {
return map[string]bool{}
}
cloned := make(map[string]bool, len(src))
for key := range src {
cloned[key] = true
}
return cloned
}
func cloneInt64Map(src map[string]int64) map[string]int64 {
if len(src) == 0 {
return map[string]int64{}