forked from gongxuegit/tiku-backend.net
feat: add referral qrcode generation provider
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user