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 NCC.Common.Configuration;
using NCC.Dependency;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
// 增强版 SDK 暂时注释,改用 HTTP 原生调用
// using AlibabaCloud.SDK.Green20220302;
// using AlibabaCloud.SDK.Green20220302.Models;
// using AlibabaCloud.OSS.V2;
// using AlibabaCloud.OSS.V2.Models;
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()
};
}
}
///
/// 图片审核(增强版 - green-cip)
/// 使用 HTTP 原生调用,实现本地图片审核流程:
/// 1. DescribeUploadToken - 获取临时 OSS 凭证
/// 2. 上传图片到临时 OSS
/// 3. ImageModeration - 调用审核接口
///
/// 图片字节数组
/// 审核结果详情
private async Task ScanImageEnhancedAsync(byte[] imageBytes)
{
try
{
var endpointHost = _endpoint.Replace("https://", "").Replace("http://", "");
// 1. 调用 DescribeUploadToken 获取临时 OSS 凭证
var tokenUrl = $"{_endpoint}/DescribeUploadToken";
var tokenResponse = await CallOpenApiAsync(tokenUrl, new Dictionary());
var tokenResult = JsonConvert.DeserializeObject(tokenResponse);
if (tokenResult == null || tokenResult["Code"]?.Value() != 200)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = $"获取临时 OSS 凭证失败:{tokenResult?["Msg"]?.ToString() ?? "未知错误"}",
RawResponse = tokenResponse
};
}
var tokenData = tokenResult["Data"];
if (tokenData == null)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = "临时 OSS 凭证数据为空",
RawResponse = tokenResponse
};
}
var bucketName = tokenData["BucketName"]?.ToString();
var fileNamePrefix = tokenData["FileNamePrefix"]?.ToString() ?? "";
var ossAccessKeyId = tokenData["AccessKeyId"]?.ToString();
var ossAccessKeySecret = tokenData["AccessKeySecret"]?.ToString();
var ossSecurityToken = tokenData["SecurityToken"]?.ToString();
var ossInternetEndPoint = tokenData["OssInternetEndPoint"]?.ToString();
if (string.IsNullOrEmpty(bucketName) || string.IsNullOrEmpty(ossInternetEndPoint))
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = "临时 OSS 凭证信息不完整",
RawResponse = tokenResponse
};
}
// 2. 上传图片到临时 OSS(使用 OSS PutObject API)
var objectName = $"{fileNamePrefix}{Guid.NewGuid()}.jpg";
var ossPutUrl = $"https://{bucketName}.{ossInternetEndPoint}/{objectName}";
// 使用 STS Token 上传到 OSS(需要 OSS 签名)
var ossPutSuccess = await UploadToOSSTempAsync(
ossPutUrl,
imageBytes,
bucketName,
objectName,
ossAccessKeyId,
ossAccessKeySecret,
ossSecurityToken);
if (!ossPutSuccess)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = "上传图片到临时 OSS 失败",
RawResponse = "OSS 上传失败"
};
}
// 3. 调用增强版审核接口 ImageModeration
var moderationUrl = $"{_endpoint}/ImageModeration";
var serviceParameters = new Dictionary
{
{ "ossBucketName", bucketName },
{ "ossObjectName", objectName },
{ "dataId", Guid.NewGuid().ToString() }
};
var moderationBody = new Dictionary
{
{ "Service", "baselineCheck" },
{ "ServiceParameters", JsonConvert.SerializeObject(serviceParameters) }
};
var moderationResponse = await CallOpenApiAsync(moderationUrl, moderationBody);
var rawResponse = moderationResponse;
var moderationResult = JsonConvert.DeserializeObject(moderationResponse);
if (moderationResult == null)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = "响应解析失败",
RawResponse = rawResponse
};
}
var code = moderationResult["Code"]?.Value();
if (code != 200)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = moderationResult["Msg"]?.ToString() ?? $"审核接口返回错误:{code}",
RawResponse = rawResponse,
Details = moderationResult
};
}
var data = moderationResult["Data"];
if (data == null)
{
var failOnError = App.Configuration["NCC_App:ImageModeration:FailOnError"] == "true";
return new ModerationResult
{
Passed = !failOnError,
Message = "审核结果数据为空",
RawResponse = rawResponse,
Details = moderationResult
};
}
// 增强版返回格式:Data.Result 是数组,每个元素有 Label、Confidence、RiskLevel
var results = data["Result"] as JArray;
var riskLevel = data["RiskLevel"]?.ToString();
if (results == null || results.Count == 0)
{
return new ModerationResult
{
Passed = true,
Message = "没有审核结果,默认通过",
RawResponse = rawResponse,
Details = data
};
}
// 检查风险等级和标签
var highRiskLabels = new List();
var allResults = new List