update:阿里云下载

This commit is contained in:
ltx 2026-11-05 16:39:39 +08:00
parent 0afa0f8c1a
commit 2fa9d1e29d

View File

@ -1,6 +1,8 @@
using System.Security.Cryptography;
using Microsoft.Extensions.Options;
using YD_Event.Application.Auth;
using YD_Event.Application.Common;
using YD_Event.Core.Options;
namespace YD_Event.Application.AppUpdate;
@ -16,15 +18,18 @@ public class AppUpdateController : IDynamicApiController
private readonly AuthManager _auth;
private readonly IIdGenerator _ids;
private readonly FileService _files;
private readonly IOptionsMonitor<OssConnectionOptions> _ossOptions;
private readonly string _channel;
public AppUpdateController(ISqlSugarRepository<JudgeAppRelease> releases,
AuthManager auth, IIdGenerator ids, FileService files)
AuthManager auth, IIdGenerator ids, FileService files,
IOptionsMonitor<OssConnectionOptions> ossOptions)
{
_releases = releases;
_auth = auth;
_ids = ids;
_files = files;
_ossOptions = ossOptions;
_channel = Environment.GetEnvironmentVariable("APP_UPDATE_CHANNEL")?.Trim().ToLowerInvariant() ?? "production";
}
@ -41,11 +46,43 @@ public class AppUpdateController : IDynamicApiController
available = true, release.Id, release.PackageName, release.VersionCode,
release.VersionName, release.NotesZh, release.NotesEn,
release.IsMandatory, release.FileSize, release.Sha256,
release.CertificateSha256, downloadUrl = $"/api/app-updates/download/{release.Id}",
release.CertificateSha256, downloadUrl = DownloadUrlFor(release),
release.PublishedTime
};
}
/// <summary>OSS 已启用时直连阿里云/CDN;配置缺失或不安全时仍使用受控下载接口。</summary>
private string DownloadUrlFor(JudgeAppRelease release)
{
var fallback = $"/api/app-updates/download/{release.Id}";
// Direct delivery is opt-in: a private bucket without CDN/public-read
// must continue using the existing server-side OSS stream.
var cdn = Environment.GetEnvironmentVariable("APP_UPDATE_DIRECT_DOWNLOAD_BASE_URL");
if (string.IsNullOrWhiteSpace(cdn)) return fallback;
var oss = _ossOptions.CurrentValue;
if (!oss.Enable || string.IsNullOrWhiteSpace(oss.Domain) ||
!Uri.TryCreate(oss.Domain.TrimEnd('/') + "/", UriKind.Absolute, out var ossBase) ||
ossBase.Scheme != Uri.UriSchemeHttps ||
!Uri.TryCreate(release.FileUrl, UriKind.Absolute, out var fileUri) ||
fileUri.Scheme != Uri.UriSchemeHttps ||
!fileUri.AbsoluteUri.StartsWith(ossBase.AbsoluteUri, StringComparison.OrdinalIgnoreCase))
return fallback;
// FileUrl comes from the configured OSS domain at upload time. Never turn
// an arbitrary stored URL into an untrusted redirect/download host.
var objectKey = fileUri.AbsoluteUri[ossBase.AbsoluteUri.Length..];
if (!objectKey.StartsWith($"app-releases/{_channel}/", StringComparison.Ordinal) ||
objectKey.Contains("..", StringComparison.Ordinal) ||
!objectKey.EndsWith(".apk", StringComparison.OrdinalIgnoreCase))
return fallback;
if (!Uri.TryCreate(cdn.TrimEnd('/') + "/", UriKind.Absolute, out var cdnBase) ||
cdnBase.Scheme != Uri.UriSchemeHttps || !string.IsNullOrEmpty(cdnBase.UserInfo) ||
!string.IsNullOrEmpty(cdnBase.Query) || !string.IsNullOrEmpty(cdnBase.Fragment))
return fallback;
return new Uri(cdnBase, objectKey).AbsoluteUri;
}
/// <summary>只允许下载当前环境已发布的版本,沿用现有文件服务流式返回。</summary>
[HttpGet("download/{id:long}")]
[AllowAnonymous]