feat: add referral qrcode generation provider

This commit is contained in:
xiong
2026-07-26 20:45:14 +08:00
parent 4279795d46
commit 7c1fe7688b
11 changed files with 490 additions and 8 deletions

View File

@@ -1,4 +1,5 @@
using System.Reflection;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using AlibabaCloud.OSS.V2.Credentials;
using AlibabaCloud.OSS.V2.Models;
@@ -302,6 +303,90 @@ public sealed partial class AliyunOssObjectStorageService(
}
}
public async Task<ObjectStorageWriteResult> WriteObjectAsync(
ObjectStorageWriteRequest request,
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var provider = NormalizeProvider(request.Provider);
AssertUploadProvider(provider);
var objectKey = ValidateObjectKey(request.TenantId, request.ObjectKey);
var mimeType = ValidateMimeType(request.MimeType);
var fileSizeBytes = ValidateFileSize(request.FileSizeBytes);
var checksumSha256 = NormalizeChecksum(request.ChecksumSha256);
if (request.Content.CanSeek)
{
request.Content.Position = 0;
}
if (string.IsNullOrWhiteSpace(request.Bucket))
{
throw new ObjectStorageException("Writable asset requires bucket.", "ASSET_OBJECT_LOCATION_REQUIRED");
}
if (provider == ObjectStorageProviders.LocalDev)
{
return new ObjectStorageWriteResult(
provider,
request.Bucket,
objectKey,
BuildPublicUrl(request.Bucket, objectKey),
fileSizeBytes,
mimeType,
checksumSha256 ?? await ComputeSha256Async(request.Content, cancellationToken),
null,
new Dictionary<string, string>(),
"local-dev-write-placeholder");
}
if (provider != ObjectStorageProviders.AliyunOss)
{
throw new ObjectStorageException(
$"{provider} does not support managed server-side writes.",
"WRITE_PROVIDER_NOT_SUPPORTED");
}
var metadata = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (request.Metadata is not null)
{
foreach (var (key, value) in request.Metadata)
{
if (!string.IsNullOrWhiteSpace(key) && value is not null)
{
metadata[key.Trim()] = value;
}
}
}
if (!string.IsNullOrWhiteSpace(checksumSha256))
{
metadata["sha256"] = checksumSha256;
}
var result = await GetClient().PutObjectAsync(new PutObjectRequest
{
Bucket = request.Bucket,
Key = objectKey,
Body = request.Content,
ContentType = mimeType,
ContentLength = fileSizeBytes,
Metadata = metadata.Count == 0 ? null : metadata,
ForbidOverwrite = !request.Upsert
}, cancellationToken: cancellationToken);
return new ObjectStorageWriteResult(
provider,
request.Bucket,
objectKey,
BuildPublicUrl(request.Bucket, objectKey),
fileSizeBytes,
mimeType,
checksumSha256,
CleanEtag(result.ETag),
NormalizeHeaders(result.Headers),
"aliyun-oss-put-object");
}
public void Dispose()
{
if (disposed)
@@ -380,6 +465,23 @@ public sealed partial class AliyunOssObjectStorageService(
"local-placeholder");
}
private Uri? BuildPublicUrl(string bucket, string objectKey)
{
var baseUrl = storageOptions.Value.PublicBaseUrl?.Trim().TrimEnd('/');
if (string.IsNullOrWhiteSpace(baseUrl))
{
return null;
}
var path = string.Join(
'/',
bucket.Trim('/'),
objectKey
.Split('/', StringSplitOptions.RemoveEmptyEntries)
.Select(Uri.EscapeDataString));
return new Uri($"{baseUrl}/{path}", UriKind.Absolute);
}
private Oss.Client GetClient()
{
ObjectDisposedException.ThrowIf(disposed, this);
@@ -556,6 +658,33 @@ public sealed partial class AliyunOssObjectStorageService(
private static string? NullIfWhiteSpace(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private static string? NormalizeChecksum(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
var normalized = value.Trim().ToLowerInvariant();
return normalized.Length == 64 ? normalized : null;
}
private static async Task<string> ComputeSha256Async(Stream stream, CancellationToken cancellationToken)
{
if (stream.CanSeek)
{
stream.Position = 0;
}
var hash = await SHA256.HashDataAsync(stream, cancellationToken);
if (stream.CanSeek)
{
stream.Position = 0;
}
return Convert.ToHexString(hash).ToLowerInvariant();
}
[GeneratedRegex("""^[A-Za-z0-9][A-Za-z0-9._~!$&'()+,;=@/-]{0,1023}$""")]
private static partial Regex SafeObjectKeyRegex();