using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using AlibabaCloud.OpenApiClient.Models;
using AlibabaCloud.SDK.Green20220302;
using AlibabaCloud.SDK.Green20220302.Models;
using AlibabaCloud.TeaUtil.Models;
using Tea;
using NCC.Common.Configuration;
using NCC.Dependency;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OSS = AlibabaCloud.OSS.V2;
namespace NCC.Extend
{
///
/// 图片审核服务(阿里云内容安全),仅内部调用,不暴露 API
///
public class ImageModerationService : ITransient
{
private readonly HttpClient _httpClient;
private readonly string _endpoint;
private readonly string _accessKeyId;
private readonly string _accessKeySecret;
private readonly string _region;
private readonly bool _enabled;
private readonly bool _isEnhancedVersion; // 是否为增强版(green-cip)
///
/// 初始化图片审核服务
///
public ImageModerationService()
{
_httpClient = new HttpClient();
_enabled = App.Configuration["NCC_App:ImageModeration:Enabled"] == "true";
_endpoint = App.Configuration["NCC_App:ImageModeration:Endpoint"]
?? "https://green.cn-shanghai.aliyuncs.com";
_region = App.Configuration["NCC_App:ImageModeration:Region"]
?? "cn-shanghai";
// 优先使用ImageModeration配置,如果没有则使用AliyunOSS的配置
_accessKeyId = App.Configuration["NCC_App:ImageModeration:AccessKeyId"];
if (string.IsNullOrEmpty(_accessKeyId))
{
_accessKeyId = App.Configuration["NCC_App:AliyunOSS:AccessKeyId"];
}
_accessKeySecret = App.Configuration["NCC_App:ImageModeration:AccessKeySecret"];
if (string.IsNullOrEmpty(_accessKeySecret))
{
_accessKeySecret = App.Configuration["NCC_App:AliyunOSS:AccessKeySecret"];
}
// 判断是否为增强版(endpoint 包含 green-cip)
_isEnhancedVersion = _endpoint.Contains("green-cip");
}
///
/// 图片审核结果
///
public class ModerationResult
{
public bool Passed { get; set; }
public string Message { get; set; }
public string RawResponse { get; set; }
public object Details { get; set; }
}
///
/// 图片审核(从本地文件路径)
///
/// 本地文件路径
/// 审核结果详情
public async Task ScanImageFromFileAsync(string filePath)
{
if (!_enabled)
{
return new ModerationResult
{
Passed = true,
Message = "审核未启用,直接通过"
};
}
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
return new ModerationResult
{
Passed = false,
Message = "文件不存在或路径为空"
};
}
try
{
var imageBytes = await File.ReadAllBytesAsync(filePath);
return await ScanImageAsync(imageBytes);
}
catch (Exception ex)
{
// 审核异常时,根据配置决定是否通过
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = $"审核异常:{ex.Message}",
RawResponse = ex.ToString()
};
}
}
///
/// 图片审核(从字节数组)
///
/// 图片字节数组
/// 审核结果详情
public async Task ScanImageAsync(byte[] imageBytes)
{
if (!_enabled)
{
return new ModerationResult
{
Passed = true,
Message = "审核未启用,直接通过"
};
}
if (imageBytes == null || imageBytes.Length == 0)
{
return new ModerationResult
{
Passed = false,
Message = "图片数据为空"
};
}
// 检查 AccessKey 是否配置
if (string.IsNullOrEmpty(_accessKeyId) || string.IsNullOrEmpty(_accessKeySecret))
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = "图片审核服务未配置 AccessKey,无法调用审核接口",
RawResponse = "AccessKeyId 或 AccessKeySecret 未配置"
};
}
try
{
// 判断是否为增强版,使用不同的调用方式
if (_isEnhancedVersion)
{
return await ScanImageEnhancedAsync(imageBytes);
}
else
{
return await ScanImageLegacyAsync(imageBytes);
}
}
catch (Exception ex)
{
// 审核异常时,根据配置决定是否通过
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = $"审核异常:{ex.Message}",
RawResponse = ex.ToString()
};
}
}
///
/// 创建增强版内容安全 Client(Green 20220302)
///
/// Green Client 实例
private static Client CreateGreenClient(string accessKeyId, string accessKeySecret, string endpoint)
{
var endpointHost = endpoint?.Replace("https://", "").Replace("http://", "") ?? "green-cip.cn-shanghai.aliyuncs.com";
var config = new Config
{
AccessKeyId = accessKeyId,
AccessKeySecret = accessKeySecret,
Endpoint = endpointHost
};
return new Client(config);
}
///
/// 图片审核(增强版 - green-cip)
/// 使用官方 SDK:DescribeUploadToken → 上传临时 OSS → ImageModeration
///
/// 图片字节数组
/// 审核结果详情
private async Task ScanImageEnhancedAsync(byte[] imageBytes)
{
try
{
var client = CreateGreenClient(_accessKeyId, _accessKeySecret, _endpoint);
var runtime = new RuntimeOptions();
// 1. 获取临时 OSS 上传凭证
DescribeUploadTokenResponse tokenResponse = null;
await Task.Run(() =>
{
tokenResponse = client.DescribeUploadToken();
}).ConfigureAwait(false);
if (tokenResponse?.Body?.Code != 200)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = $"获取临时 OSS 凭证失败:{tokenResponse?.Body?.Msg ?? "未知错误"}",
RawResponse = tokenResponse != null ? JsonConvert.SerializeObject(tokenResponse.Body) : "null"
};
}
var tokenData = tokenResponse.Body.Data;
if (tokenData == null)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = "临时 OSS 凭证数据为空",
RawResponse = JsonConvert.SerializeObject(tokenResponse.Body)
};
}
var bucketName = tokenData.BucketName;
var fileNamePrefix = tokenData.FileNamePrefix ?? "";
var ossEndpoint = tokenData.OssInternetEndPoint ?? "";
if (string.IsNullOrEmpty(ossEndpoint))
ossEndpoint = tokenData.OssInternalEndPoint ?? "";
if (!ossEndpoint.StartsWith("http", StringComparison.OrdinalIgnoreCase))
ossEndpoint = "https://" + ossEndpoint;
// 从 token 返回的 OSS endpoint 推导 region,与签名一致(避免 Invalid signing region)
var ossRegion = _region;
const string ossCnPrefix = "oss-cn-";
var ossCnIdx = ossEndpoint.IndexOf(ossCnPrefix, StringComparison.OrdinalIgnoreCase);
if (ossCnIdx >= 0)
{
var start = ossCnIdx + ossCnPrefix.Length; // "oss-cn-" 后为 "shanghai" 等,region 为 "cn-xxx"
var end = ossEndpoint.IndexOf(".", start, StringComparison.Ordinal);
var regionSuffix = end > start ? ossEndpoint.Substring(start, end - start) : ossEndpoint.Substring(start);
ossRegion = "cn-" + regionSuffix;
}
// 2. 使用 OSS.V2 + STS 凭证上传图片到临时 OSS
var objectName = fileNamePrefix + Guid.NewGuid().ToString("N") + ".jpg";
var ossCfg = OSS.Configuration.LoadDefault();
ossCfg.CredentialsProvider = new OSS.Credentials.StaticCredentialsProvider(
tokenData.AccessKeyId,
tokenData.AccessKeySecret,
tokenData.SecurityToken);
ossCfg.Region = ossRegion;
ossCfg.Endpoint = ossEndpoint;
using (var ossClient = new OSS.Client(ossCfg))
using (var bodyStream = new MemoryStream(imageBytes))
{
await ossClient.PutObjectAsync(new OSS.Models.PutObjectRequest
{
Bucket = bucketName,
Key = objectName,
Body = bodyStream
}).ConfigureAwait(false);
}
// 3. 调用增强版审核接口 ImageModeration
var serviceParameters = new Dictionary
{
{ "ossBucketName", bucketName },
{ "ossObjectName", objectName },
{ "dataId", Guid.NewGuid().ToString() }
};
// 中国区(cn-shanghai 等)使用 baselineCheck,国际区使用 baselineCheck_global
var serviceName = _region != null && _region.StartsWith("cn-", StringComparison.OrdinalIgnoreCase)
? "baselineCheck"
: "baselineCheck_global";
var request = new ImageModerationRequest
{
Service = serviceName,
ServiceParameters = JsonConvert.SerializeObject(serviceParameters)
};
ImageModerationResponse moderationResponse = null;
await Task.Run(() =>
{
moderationResponse = client.ImageModerationWithOptions(request, runtime);
}).ConfigureAwait(false);
var rawResponse = moderationResponse != null ? JsonConvert.SerializeObject(moderationResponse.Body) : "null";
if (moderationResponse?.Body == null)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = "审核接口无响应",
RawResponse = rawResponse
};
}
var code = moderationResponse.Body.Code;
if (code != 200)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = moderationResponse.Body.Msg ?? $"审核接口返回错误:{code}",
RawResponse = rawResponse,
Details = moderationResponse.Body
};
}
var data = moderationResponse.Body.Data;
if (data == null)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = "审核结果数据为空",
RawResponse = rawResponse,
Details = moderationResponse.Body
};
}
var results = data.Result;
var riskLevel = data.RiskLevel ?? "";
if (results == null || results.Count == 0)
{
return new ModerationResult
{
Passed = true,
Message = "没有审核结果,默认通过",
RawResponse = rawResponse,
Details = data
};
}
var highRiskLabels = new List();
var allResults = new List