From c3b700b1bedd04bff7d5394279cfd1c598a6a1c0 Mon Sep 17 00:00:00 2001 From: xiwangly2 Date: Fri, 2 Oct 2026 18:30:54 +0800 Subject: [PATCH] fix: allow sponsor source without local key --- client/src/pages/EnvironmentManagerPage.tsx | 4 +- ...76\346\216\245\350\257\264\346\230\216.md" | 57 +++++++++++++++---- .../__tests__/java-download-artifacts.test.ts | 38 +++++++++++-- server/src/utils/javaDownloadArtifacts.ts | 56 ++++++++++-------- 4 files changed, 113 insertions(+), 42 deletions(-) diff --git a/client/src/pages/EnvironmentManagerPage.tsx b/client/src/pages/EnvironmentManagerPage.tsx index e92f981..c2320dd 100644 --- a/client/src/pages/EnvironmentManagerPage.tsx +++ b/client/src/pages/EnvironmentManagerPage.tsx @@ -1473,7 +1473,7 @@ const EnvironmentManagerPage: React.FC = () => { '已记录本地赞助者密钥,赞助高速源会在下载时尝试启用专用通道' ) : ( <> - Java环境安装现已支持赞助者专享国内高速服务器下载,您当前还不是赞助者,欢迎前往 + 赞助高速源可直接使用;配置本地赞助者密钥后会在下载时尝试启用专用通道,也可前往 { > 爱发电 - 赞助 + 支持项目 )}

diff --git "a/docs/Java\350\265\236\345\212\251\347\211\210\344\270\213\350\275\275\351\223\276\346\216\245\350\257\264\346\230\216.md" "b/docs/Java\350\265\236\345\212\251\347\211\210\344\270\213\350\275\275\351\223\276\346\216\245\350\257\264\346\230\216.md" index d912be1..92dc0cc 100644 --- "a/docs/Java\350\265\236\345\212\251\347\211\210\344\270\213\350\275\275\351\223\276\346\216\245\350\257\264\346\230\216.md" +++ "b/docs/Java\350\265\236\345\212\251\347\211\210\344\270\213\350\275\275\351\223\276\346\216\245\350\257\264\346\230\216.md" @@ -1,12 +1,25 @@ -# Java赞助版下载链接说明 +# Java 环境下载链接与赞助者下载通道说明 -环境管理页面的 Java 8、11、17、21、25 下载地址已切换为国内高速短链,Windows、Linux x64 和 Linux ARM64 会根据服务端系统信息自动选择对应链接。 +环境管理页面的 Java 下载入口由服务端目录统一驱动,配置位于 `server/src/utils/javaDownloadArtifacts.ts`。前端只展示服务端返回的版本、提供商、通道和平台可用性,不再维护独立下载链接表。 -服务端会按照 Java 版本、操作系统和架构保留压缩包文件名。短链末尾不是压缩包扩展名,安装流程会使用对应的 `.zip` 或 `.tar.gz` 文件名进行解压。 +## 下载源 -下载式 Java 安装只适用于已有明确压缩包产物的架构。RISC-V64、macOS 和未列出的 Linux 架构不会回退到 Linux x64 包,前端会禁用安装按钮,服务端也会拒绝安装请求并提示改用系统 OpenJDK。 +当前目录包含以下提供商: -RISC-V64 等特殊架构推荐使用发行版包管理器安装 Java,例如: +| 提供商 | 说明 | +| --- | --- | +| 赞助高速源 | 项目维护的 `download.xiaozhuhouses.asia` 直链。未配置密钥时也可走普通通道下载;本地记录赞助者密钥后,会在安装时尝试建立专用下载会话。 | +| Eclipse Temurin | 通过 Adoptium latest JDK API 解析 GA / EA 包。 | +| Azul Zulu | 通过 Azul Metadata API 在安装时解析最新匹配的 Zulu JDK 包。 | +| 系统包管理器 | Linux 上的 OpenJDK headless 包预设,适合 riscv64 等特殊架构。 | + +赞助高速源、Temurin 和 Azul 都由服务端按当前系统平台与架构判断是否可用。没有明确产物的组合不会回退到其它架构的包。 + +## 版本与通道 + +内置稳定版本包括 Java 8、11、17、21、25、27。自定义 Java 版本支持手动选择主版本、GA / EA 通道和下载提供商;默认使用 Java 25 GA,EA 预览版只在高级自定义入口中主动选择。 + +RISC-V64 等特殊架构推荐优先使用发行版包管理器安装 Java,例如: ```bash sudo apt-get update @@ -16,11 +29,33 @@ sudo apt-get install -y default-jre-headless 实例未指定面板内 Java 版本时,会使用系统 `PATH` 中的 `java`。因此系统 OpenJDK 安装完成后,可以在实例中保持 Java 版本为空,或让启动命令直接使用 `java -jar ...`。 -本次更新的链接配置位于: +## 赞助者下载通道 + +赞助者通道与普通通道使用同一个文件直链,区别只在于是否携带由本地赞助者密钥换取的会话 Cookie。 + +安装流程如下: + +1. 服务端解析 Java 版本、提供商、通道、操作系统和架构,得到最终下载地址与压缩包文件名。 +2. 如果下载地址属于 `download.xiaozhuhouses.asia`,且本地记录了赞助者密钥,则调用 `createSponsorDownloadSession()` 建立会话。 +3. 会话建立成功时,下载请求携带 Cookie,下载服务会调度到赞助者专用节点。 +4. 未记录密钥、密钥无效、会话服务不可用或会话建立失败时,安装会自动回退到同一直链的普通下载通道,不会中断安装。 + +会话 Cookie 只会在 `isSponsorDownloadUrl()` 确认目标域名为 `download.xiaozhuhouses.asia` 时携带,避免泄露到第三方下载源。 + +## 代码入口 + +| 位置 | 说明 | +| --- | --- | +| `server/src/utils/javaDownloadArtifacts.ts` | Java 版本目录、提供商、平台架构可用性、赞助源链接和文件名映射。 | +| `server/src/utils/sponsorDownload.ts` | 赞助者会话建立、下载域名校验。 | +| `server/src/utils/sponsorStatus.ts` | 读取本地记录的赞助者密钥。 | +| `server/src/routes/environment.ts` | `/java/catalog` 返回下载目录;`/java/install` 解析 provider/version/channel 并组装下载选项。 | +| `server/src/modules/environment/javaManager.ts` | 下载、保存、解压 Java 安装包,并支持透传会话 Cookie。 | +| `client/src/pages/EnvironmentManagerPage.tsx` | 展示服务端目录、预设卡片、自定义版本入口和安装进度。 | -- `client/src/pages/EnvironmentManagerPage.tsx`:页面 Java 环境下载配置。 -- `server/src/routes/environment.ts`:赞助者专用下载地址和压缩包文件名映射。 -- `server/src/utils/javaDownloadArtifacts.ts`:下载式 Java 支持架构、赞助链接和压缩包文件名映射。 -- `server/src/modules/environment/javaManager.ts`:使用映射后的文件名保存并解压下载文件。 +## 验证重点 -Node.js ARM64 下载链接不属于 Java 环境管理页面,本次未纳入 Java 下载配置。 +- 无赞助者密钥时,赞助高速源在支持的平台上仍应可安装。 +- 有赞助者密钥时,同一直链会尝试携带会话 Cookie;失败时应自动回退普通通道。 +- riscv64 不应误用 Linux x64 包;没有赞助源产物时,应落到 Temurin 或系统包管理器等可用选项。 +- EA 预览版不应作为新手默认路径,只能通过高级自定义入口主动选择。 diff --git a/server/src/__tests__/java-download-artifacts.test.ts b/server/src/__tests__/java-download-artifacts.test.ts index 293663b..70cb4fd 100644 --- a/server/src/__tests__/java-download-artifacts.test.ts +++ b/server/src/__tests__/java-download-artifacts.test.ts @@ -64,15 +64,39 @@ describe('java download artifacts', () => { ) }) - it('keeps sponsor downloads behind sponsor availability', async () => { - await expect(resolveJavaDownloadOption('java17', 'sponsor', 'linux', 'x64')).rejects.toThrow(/赞助高速源/) + it('resolves sponsor downloads without requiring sponsor credentials', async () => { + const resolution = await resolveJavaDownloadOption('java17', 'sponsor', 'linux', 'x64') - const resolution = await resolveJavaDownloadOption('java17', 'sponsor', 'linux', 'x64', { + expect(resolution.downloadUrl).toBe(getSponsorDownloadUrl('java17', 'linux', 'x64')) + expect(resolution.archiveFileName).toBe(getJavaArchiveFileName('java17', 'linux', 'x64')) + }) + + it('keeps the first catalog option installable on supported platforms without sponsor credentials', () => { + const catalog = getJavaDownloadCatalog('linux', 'x64') + const java17Options = catalog.options.filter(option => option.version === 'java17') + const sponsorOption = java17Options.find(option => option.provider === 'sponsor') + + expect(java17Options[0]).toEqual(expect.objectContaining({ + provider: 'sponsor', + available: true, + recommended: true + })) + expect(sponsorOption).toEqual(expect.objectContaining({ + available: true + })) + }) + + it('keeps sponsor first when sponsor credentials are available', () => { + const catalog = getJavaDownloadCatalog('linux', 'x64', { sponsorAvailable: true }) + const java17Options = catalog.options.filter(option => option.version === 'java17') - expect(resolution.downloadUrl).toBe(getSponsorDownloadUrl('java17', 'linux', 'x64')) - expect(resolution.archiveFileName).toBe(getJavaArchiveFileName('java17', 'linux', 'x64')) + expect(java17Options[0]).toEqual(expect.objectContaining({ + provider: 'sponsor', + available: true, + recommended: true + })) }) it('keeps sponsor unavailable on riscv64 while offering Temurin and system packages where valid', () => { @@ -97,6 +121,10 @@ describe('java download artifacts', () => { expect(java17Options.find(option => option.provider === 'sponsor')).toEqual(expect.objectContaining({ available: false })) + expect(java17Options[0]).toEqual(expect.objectContaining({ + provider: 'adoptium', + available: true + })) expect(java17Options.find(option => option.provider === 'adoptium')).toEqual(expect.objectContaining({ available: true, downloadUrl: 'https://api.adoptium.net/v3/binary/latest/17/ga/linux/riscv64/jdk/hotspot/normal/eclipse' diff --git a/server/src/utils/javaDownloadArtifacts.ts b/server/src/utils/javaDownloadArtifacts.ts index e299724..08e0f70 100644 --- a/server/src/utils/javaDownloadArtifacts.ts +++ b/server/src/utils/javaDownloadArtifacts.ts @@ -234,9 +234,8 @@ const JAVA_PROVIDERS: JavaCatalogProvider[] = [ { id: 'sponsor', label: '赞助高速源', - description: '项目提供的国内高速下载源,需要本地记录赞助者密钥', + description: '项目提供的国内高速下载源;本地赞助者密钥可启用专用下载会话', source: 'download', - sponsorOnly: true, supportedChannels: ['ga'] }, { @@ -605,7 +604,6 @@ function buildDownloadOption( provider: JavaCatalogProvider, platform: string, arch: string | undefined, - sponsorAvailable: boolean, channel: JavaReleaseChannel, recommended: boolean ): JavaCatalogOption { @@ -613,18 +611,6 @@ function buildDownloadOption( return buildUnavailableDownloadOption(version, provider, platform, arch, channel, recommended, `${provider.label} 不支持 ${getReleaseChannelLabel(channel)} 通道`) } - if (provider.id === 'sponsor' && !sponsorAvailable) { - return buildUnavailableDownloadOption( - version, - provider, - platform, - arch, - channel, - recommended, - '赞助高速源需要先配置本地赞助者密钥' - ) - } - if (provider.id === 'azul') { try { getAzulPlatform(platform, arch) @@ -719,6 +705,30 @@ function buildSystemPackageOption( } } +function getOptionPriority(option: JavaCatalogOption): number { + if (option.available && option.source === 'download' && option.recommended) return 0 + if (option.available && option.source === 'download') return 1 + if (option.available && option.source === 'package-manager' && option.recommended) return 2 + if (option.available) return 3 + return 4 +} + +const PROVIDER_SORT_ORDER: Record = { + sponsor: 0, + adoptium: 1, + azul: 2, + system: 3 +} + +function sortJavaOptions(options: JavaCatalogOption[]): JavaCatalogOption[] { + return options.sort((left, right) => { + const priorityDiff = getOptionPriority(left) - getOptionPriority(right) + if (priorityDiff !== 0) return priorityDiff + + return PROVIDER_SORT_ORDER[left.provider] - PROVIDER_SORT_ORDER[right.provider] + }) +} + export function getJavaDownloadCatalog( platform: string, arch?: string, @@ -740,16 +750,19 @@ export function getJavaDownloadCatalog( for (const provider of JAVA_PROVIDERS.filter(item => item.source === 'download')) { for (const channel of version.channels) { const recommended = ( - (provider.id === 'sponsor' && sponsorAvailable && channel === version.defaultChannel) || - (provider.id === 'adoptium' && !sponsorAvailable && channel === version.defaultChannel) + provider.id === 'sponsor' && channel === version.defaultChannel ) - catalogOptions.push(buildDownloadOption(version, provider, platform, arch, sponsorAvailable, channel, recommended)) + catalogOptions.push(buildDownloadOption(version, provider, platform, arch, channel, recommended)) } } catalogOptions.push(buildSystemPackageOption(version, platform, arch, isRiscv64)) } + const sortedOptions = JAVA_VERSIONS.flatMap(version => ( + sortJavaOptions(catalogOptions.filter(option => option.version === version.id)) + )) + return { platform, arch: arch || 'unknown', @@ -758,7 +771,7 @@ export function getJavaDownloadCatalog( providers: JAVA_PROVIDERS, presets: JAVA_PRESETS, versions: JAVA_VERSIONS, - options: catalogOptions, + options: sortedOptions, custom: { defaultMajor: 25, defaultChannel: 'ga', @@ -789,17 +802,12 @@ export async function resolveJavaDownloadOption( throw new UnsupportedJavaDownloadError('赞助高速源仅支持项目维护的预设版本') } - if (!options.sponsorAvailable) { - throw new UnsupportedJavaDownloadError('赞助高速源需要先配置本地赞助者密钥') - } - return { version: versionRequest.id, major: versionRequest.major, releaseChannel: versionRequest.defaultChannel, provider, providerLabel: providerDefinition.label, - sponsorOnly: true, downloadUrl: getSponsorDownloadUrl(versionRequest.id, platform, arch), archiveFileName: getJavaArchiveFileName(versionRequest.id, platform, arch) }