diff --git a/antis-ncc-admin/.env.development b/antis-ncc-admin/.env.development
index 198955a..6462393 100644
--- a/antis-ncc-admin/.env.development
+++ b/antis-ncc-admin/.env.development
@@ -2,8 +2,8 @@
VUE_CLI_BABEL_TRANSPILE_MODULES = true
# VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com'
-VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'
-# VUE_APP_BASE_API = 'http://localhost:2011'
+# VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'
+VUE_APP_BASE_API = 'http://localhost:2011'
# VUE_APP_BASE_API = 'http://localhost:2011'
VUE_APP_IMG_API = ''
VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket'
diff --git a/docs/阿里云图片审核集成方案.md b/docs/阿里云图片审核集成方案.md
new file mode 100644
index 0000000..1d8d97e
--- /dev/null
+++ b/docs/阿里云图片审核集成方案.md
@@ -0,0 +1,765 @@
+# 阿里云图片审核集成方案
+
+**文档日期**:2026年1月
+**目标**:在图片上传流程中集成阿里云内容安全(图片审核)服务,自动识别涉黄、涉暴等违规内容
+
+---
+
+## 一、当前上传流程梳理
+
+### 1.1 标准文件上传流程(Uploader)
+
+```
+1. 接收文件(IFormFile)
+2. 验证文件类型(AllowFileType)
+3. 生成文件路径和文件名(yyyyMMdd_xxx.ext)
+4. 【当前】先上传到服务器本地
+5. 【当前】从服务器本地上传到OSS
+6. 【当前】OSS上传成功 → 删除本地文件
+7. 【当前】OSS上传失败 → 保留本地文件
+8. 返回URL(OSS成功用OSS URL,失败用本地URL)
+```
+
+**接口位置**:`FileService.cs` → `Uploader(string type, IFormFile file)`
+**接口路径**:`POST /api/File/Uploader/{type}`
+
+---
+
+### 1.2 Base64图片上传流程(UploadBase64Image)
+
+```
+1. 接收Base64数据
+2. 解析Base64数据(ParseBase64Data)
+3. 验证图片格式(IsValidImageFormat)
+4. 生成文件路径和文件名
+5. 【当前】直接上传到OSS(不经过本地)
+6. 返回OSS访问URL
+```
+
+**接口位置**:`FileService.cs` → `UploadBase64Image([FromBody] Base64ImageUploadInput input)`
+**接口路径**:`POST /api/File/UploadBase64Image`
+
+**注意**:Base64上传当前是直接上传到OSS,没有本地备份流程。
+
+---
+
+### 1.3 关键代码位置
+
+| 方法 | 位置 | 行数范围 | 说明 |
+|------|------|----------|------|
+| `Uploader` | `FileService.cs` | 101-159 | 标准文件上传主方法 |
+| `UploadBase64Image` | `FileService.cs` | 1114-1209 | Base64图片上传主方法 |
+| `UploadFileToLocalThenOSS` | `FileService.cs` | 540-630 | 先本地后OSS上传逻辑 |
+| `GetOSSAccessUrl` | `FileService.cs` | 716-780 | 获取OSS访问URL |
+
+---
+
+## 二、阿里云图片审核服务说明
+
+### 2.1 服务名称
+
+- **服务名称**:内容安全(Content Moderation)
+- **产品名称**:阿里云内容安全
+- **API接口**:图片同步检测 `/green/image/scan`
+
+### 2.2 检测能力
+
+| 检测类型 | 说明 | 风险等级 |
+|---------|------|----------|
+| **涉黄** | 色情、低俗、性感等 | 高 |
+| **涉暴** | 暴力、血腥、恐怖等 | 高 |
+| **广告** | 二维码、广告文字等 | 中 |
+| **违规文字** | OCR识别图片中的文字并审核 | 中 |
+| **其他** | 政治敏感、违禁品等 | 高 |
+
+### 2.3 接口信息
+
+**接口地址**:`https://green.cn-shanghai.aliyuncs.com/green/image/scan`
+
+**请求方式**:POST
+
+**Content-Type**:`application/json`
+
+**认证方式**:AccessKey签名认证(与OSS使用相同的AccessKey)
+
+**响应格式**:JSON
+
+---
+
+## 三、集成方案设计
+
+### 3.1 集成位置
+
+**最佳集成点**:在**保存到本地之后、上传到OSS之前**进行审核
+
+**原因**:
+1. ✅ 审核需要图片数据,本地已有文件,可直接读取
+2. ✅ 审核通过后再上传OSS,避免违规内容上传到OSS
+3. ✅ 审核不通过时,保留本地文件,不上传OSS
+4. ✅ 不影响现有流程,只是增加审核步骤
+
+### 3.2 改造后的流程
+
+#### 3.2.1 标准文件上传流程(改造后)
+
+```
+1. 接收文件(IFormFile)
+2. 验证文件类型(AllowFileType)
+3. 生成文件路径和文件名(yyyyMMdd_xxx.ext)
+4. 先上传到服务器本地
+5. 【新增】调用阿里云图片审核接口
+6. 【新增】审核不通过 → 保留本地文件,返回错误提示(不上传OSS)
+7. 【新增】审核通过 → 继续流程
+8. 从服务器本地上传到OSS
+9. OSS上传成功 → 删除本地文件
+10. OSS上传失败 → 保留本地文件
+11. 返回URL(OSS成功用OSS URL,失败用本地URL)
+```
+
+#### 3.2.2 Base64图片上传流程(改造后)
+
+```
+1. 接收Base64数据
+2. 解析Base64数据(ParseBase64Data)
+3. 验证图片格式(IsValidImageFormat)
+4. 生成文件路径和文件名
+5. 【新增】先保存Base64数据到服务器本地(临时文件)
+6. 【新增】调用阿里云图片审核接口
+7. 【新增】审核不通过 → 保留本地临时文件,返回错误提示(不上传OSS)
+8. 【新增】审核通过 → 继续流程
+9. 从服务器本地上传到OSS
+10. OSS上传成功 → 删除本地临时文件
+11. OSS上传失败 → 保留本地临时文件
+12. 返回OSS访问URL
+```
+
+---
+
+## 四、技术实现方案
+
+### 4.1 创建图片审核服务类
+
+**文件位置**:`netcore/src/Modularity/System/NCC.System/Service/Common/ImageModerationService.cs`
+
+**功能**:
+- 封装阿里云图片审核API调用
+- 处理审核结果解析
+- 统一异常处理
+
+**接口定义**:
+```csharp
+public interface IImageModerationService
+{
+ ///
+ /// 图片审核(同步检测)
+ ///
+ /// 图片字节数组
+ /// 图片URL(可选,如果提供URL则优先使用URL审核)
+ /// 审核结果
+ Task ScanImageAsync(byte[] imageBytes, string imageUrl = null);
+
+ ///
+ /// 图片审核(从本地文件路径)
+ ///
+ /// 本地文件路径
+ /// 审核结果
+ Task ScanImageFromFileAsync(string filePath);
+}
+```
+
+**审核结果模型**:
+```csharp
+public class ImageModerationResult
+{
+ ///
+ /// 是否通过审核
+ ///
+ public bool IsPass { get; set; }
+
+ ///
+ /// 审核建议(pass:通过,review:需要人工审核,block:拒绝)
+ ///
+ public string Suggestion { get; set; }
+
+ ///
+ /// 风险等级(normal:正常,low:低风险,medium:中风险,high:高风险)
+ ///
+ public string RiskLevel { get; set; }
+
+ ///
+ /// 违规类型列表
+ ///
+ public List Labels { get; set; }
+
+ ///
+ /// 错误信息(审核失败时的错误描述)
+ ///
+ public string ErrorMessage { get; set; }
+
+ ///
+ /// 审核详情(JSON格式的原始响应)
+ ///
+ public string Details { get; set; }
+}
+```
+
+---
+
+### 4.2 配置项添加
+
+**配置文件**:`appsettings.json`
+
+**新增配置项**:
+```json
+{
+ "NCC_App": {
+ "AliyunOSS": {
+ "AccessKeyId": "...",
+ "AccessKeySecret": "...",
+ "Endpoint": "...",
+ "Region": "..."
+ },
+ "ImageModeration": {
+ "Enabled": true,
+ "Endpoint": "https://green.cn-shanghai.aliyuncs.com",
+ "Region": "cn-shanghai",
+ "AccessKeyId": "", // 如果为空,使用AliyunOSS的AccessKeyId
+ "AccessKeySecret": "", // 如果为空,使用AliyunOSS的AccessKeySecret
+ "Scenes": ["porn", "terrorism", "ad", "qrcode", "live", "logo"], // 审核场景
+ "SuggestionLevel": "block", // 审核建议级别:pass/review/block
+ "RiskLevel": "high" // 风险等级阈值:normal/low/medium/high
+ }
+ }
+}
+```
+
+**配置说明**:
+- `Enabled`:是否启用图片审核(可配置开关)
+- `Endpoint`:内容安全服务端点(默认:`https://green.cn-shanghai.aliyuncs.com`)
+- `Region`:服务区域(默认:`cn-shanghai`)
+- `AccessKeyId/AccessKeySecret`:如果为空,复用OSS的AccessKey
+- `Scenes`:审核场景列表
+- `SuggestionLevel`:审核建议级别,`block`表示拒绝,`review`表示需要人工审核,`pass`表示通过
+- `RiskLevel`:风险等级阈值,超过此等级视为违规
+
+---
+
+### 4.3 服务注册
+
+**文件位置**:`Startup.cs`
+
+**注册代码**:
+```csharp
+#region 阿里云图片审核
+
+var imageModerationEnabled = App.Configuration["NCC_App:ImageModeration:Enabled"] == "true";
+if (imageModerationEnabled)
+{
+ services.AddScoped();
+}
+
+#endregion
+```
+
+---
+
+### 4.4 FileService 改造
+
+#### 4.4.1 Uploader 方法改造
+
+**改造位置**:`FileService.cs` → `Uploader` 方法
+
+**改造逻辑**:
+```csharp
+[HttpPost("Uploader/{type}")]
+[AllowAnonymous]
+public async Task Uploader(string type, IFormFile file)
+{
+ // ... 现有代码:验证文件类型、生成路径和文件名 ...
+
+ // 先上传到本地
+ var (ossSuccess, localPath, ossPath) = await UploadFileToLocalThenOSS(
+ file,
+ _filePath,
+ ossFilePath,
+ _fileName,
+ forceStoreType);
+
+ // 【新增】图片审核逻辑
+ var imageModerationEnabled = _configuration["NCC_App:ImageModeration:Enabled"] == "true";
+ if (imageModerationEnabled && IsImageFile(fileType))
+ {
+ try
+ {
+ var moderationService = _serviceProvider.GetService();
+ if (moderationService != null && !string.IsNullOrEmpty(localPath) && File.Exists(localPath))
+ {
+ var moderationResult = await moderationService.ScanImageFromFileAsync(localPath);
+
+ if (!moderationResult.IsPass)
+ {
+ // 审核不通过,保留本地文件,返回错误提示(不上传OSS)
+ throw NCCException.Oh($"图片审核未通过:{moderationResult.ErrorMessage ?? "图片包含违规内容"}");
+ }
+ }
+ }
+ catch (NCCException)
+ {
+ // 审核失败异常,直接抛出
+ throw;
+ }
+ catch (Exception ex)
+ {
+ // 审核服务异常,根据配置决定是否继续上传(降级策略)
+ var failOnError = _configuration["NCC_App:ImageModeration:FailOnError"] == "true";
+ if (failOnError)
+ {
+ throw NCCException.Oh("图片审核服务暂时不可用,请稍后重试");
+ }
+ // 否则继续上传(降级策略)
+ }
+ }
+
+ // ... 现有代码:返回URL ...
+}
+```
+
+**关键点**:
+- ✅ 审核在本地文件保存之后、OSS上传之前
+- ✅ 审核不通过时保留本地文件,返回错误(不上传OSS)
+- ✅ 审核通过后上传OSS,OSS上传成功则删除本地文件
+- ✅ 审核服务异常时,可选择降级策略(继续上传或拒绝上传)
+
+---
+
+#### 4.4.2 UploadBase64Image 方法改造
+
+**改造位置**:`FileService.cs` → `UploadBase64Image` 方法
+
+**改造逻辑**:
+```csharp
+[HttpPost("UploadBase64Image")]
+[AllowAnonymous]
+public async Task UploadBase64Image([FromBody] Base64ImageUploadInput input)
+{
+ // ... 现有代码:解析Base64、验证格式、生成路径 ...
+
+ // 【新增】先保存Base64数据到本地临时文件
+ string tempLocalPath = null;
+ try
+ {
+ var tempLocalDir = Path.Combine(FileVariable.TempFilePath, "moderation");
+ if (!Directory.Exists(tempLocalDir))
+ {
+ Directory.CreateDirectory(tempLocalDir);
+ }
+
+ tempLocalPath = Path.Combine(tempLocalDir, fileName);
+ await File.WriteAllBytesAsync(tempLocalPath, imageData);
+
+ // 【新增】图片审核逻辑
+ var imageModerationEnabled = _configuration["NCC_App:ImageModeration:Enabled"] == "true";
+ if (imageModerationEnabled)
+ {
+ try
+ {
+ var moderationService = _serviceProvider.GetService();
+ if (moderationService != null)
+ {
+ var moderationResult = await moderationService.ScanImageFromFileAsync(tempLocalPath);
+
+ if (!moderationResult.IsPass)
+ {
+ // 审核不通过,保留临时文件,返回错误提示(不上传OSS)
+ throw NCCException.Oh($"图片审核未通过:{moderationResult.ErrorMessage ?? "图片包含违规内容"}");
+ }
+ }
+ }
+ catch (NCCException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ // 审核服务异常,根据配置决定是否继续上传(降级策略)
+ var failOnError = _configuration["NCC_App:ImageModeration:FailOnError"] == "true";
+ if (failOnError)
+ {
+ throw NCCException.Oh("图片审核服务暂时不可用,请稍后重试");
+ }
+ // 否则继续上传(降级策略)
+ }
+ }
+
+ // 从临时文件上传到OSS
+ using (var stream = new FileStream(tempLocalPath, FileMode.Open))
+ {
+ await _oSSServiceFactory.Create("aliyun").PutObjectAsync(bucketName, ossPath, stream);
+ }
+
+ // OSS上传成功,删除临时文件
+ if (File.Exists(tempLocalPath))
+ {
+ File.Delete(tempLocalPath);
+ }
+
+ // ... 返回URL ...
+ }
+ catch
+ {
+ // 异常时清理临时文件
+ if (!string.IsNullOrEmpty(tempLocalPath) && File.Exists(tempLocalPath))
+ {
+ try { File.Delete(tempLocalPath); } catch { }
+ }
+ throw;
+ }
+}
+```
+
+**关键点**:
+- ✅ Base64上传改为先保存到本地临时文件
+- ✅ 审核通过后再上传到OSS,OSS上传成功则删除临时文件
+- ✅ 审核不通过时保留临时文件,返回错误(不上传OSS)
+
+---
+
+### 4.5 辅助方法
+
+#### 4.5.1 IsImageFile 方法
+
+**功能**:判断文件是否为图片类型
+
+**代码**:
+```csharp
+[NonAction]
+private bool IsImageFile(string fileType)
+{
+ var imageTypes = new[] { "jpg", "jpeg", "png", "gif", "bmp", "webp" };
+ return imageTypes.Contains(fileType.ToLower());
+}
+```
+
+---
+
+## 五、审核策略配置
+
+### 5.1 审核建议级别(SuggestionLevel)
+
+| 级别 | 说明 | 处理方式 |
+|------|------|----------|
+| `pass` | 通过 | 允许上传 |
+| `review` | 需要人工审核 | 可配置:允许上传或拒绝上传 |
+| `block` | 拒绝 | 拒绝上传,返回错误 |
+
+**配置建议**:
+- **严格模式**:`SuggestionLevel: "block"`,`block`和`review`都拒绝
+- **宽松模式**:`SuggestionLevel: "review"`,仅`block`拒绝,`review`允许上传
+
+---
+
+### 5.2 风险等级阈值(RiskLevel)
+
+| 等级 | 说明 | 处理方式 |
+|------|------|----------|
+| `normal` | 正常 | 允许上传 |
+| `low` | 低风险 | 可配置:允许上传或拒绝上传 |
+| `medium` | 中风险 | 可配置:允许上传或拒绝上传 |
+| `high` | 高风险 | 拒绝上传 |
+
+**配置建议**:
+- **严格模式**:`RiskLevel: "high"`,仅`high`拒绝
+- **中等模式**:`RiskLevel: "medium"`,`medium`和`high`都拒绝
+- **宽松模式**:`RiskLevel: "low"`,`low`、`medium`、`high`都拒绝
+
+---
+
+### 5.3 审核场景(Scenes)
+
+**可选场景**:
+- `porn`:涉黄检测
+- `terrorism`:涉暴涉恐检测
+- `ad`:广告检测
+- `qrcode`:二维码检测
+- `live`:不良场景检测
+- `logo`:Logo检测
+
+**配置建议**:
+```json
+"Scenes": ["porn", "terrorism", "ad", "qrcode"]
+```
+
+---
+
+## 六、异常处理策略
+
+### 6.1 审核服务异常
+
+**场景**:审核服务不可用、网络异常、API调用失败等
+
+**处理策略**(可配置):
+1. **严格模式**:审核服务异常时拒绝上传
+2. **宽松模式**:审核服务异常时允许上传(推荐)
+
+**实现**:
+```csharp
+catch (Exception ex)
+{
+ // 根据配置决定是否继续上传
+ var failOnError = _configuration["NCC_App:ImageModeration:FailOnError"] == "true";
+ if (failOnError)
+ {
+ throw NCCException.Oh("图片审核服务暂时不可用,请稍后重试");
+ }
+ // 否则继续上传(降级策略)
+}
+```
+
+---
+
+### 6.2 审核超时
+
+**场景**:审核接口响应时间过长
+
+**处理策略**:
+- 设置超时时间(如:5秒)
+- 超时后根据配置决定:继续上传或拒绝上传
+
+**实现**:
+```csharp
+using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
+{
+ try
+ {
+ var moderationResult = await moderationService.ScanImageFromFileAsync(localPath)
+ .WithCancellation(cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ // 超时处理,根据配置决定是否继续上传
+ var failOnError = _configuration["NCC_App:ImageModeration:FailOnError"] == "true";
+ if (failOnError)
+ {
+ throw NCCException.Oh("图片审核超时,请稍后重试");
+ }
+ }
+}
+```
+
+---
+
+## 七、性能优化
+
+### 7.1 异步审核
+
+✅ 已使用 `async/await`,不会阻塞主线程
+
+### 7.2 审核缓存(可选)
+
+**场景**:相同图片重复上传
+
+**实现**:
+- 计算图片MD5值
+- 缓存审核结果(Redis或内存缓存)
+- 相同图片直接使用缓存结果
+
+**注意**:需要评估缓存成本和收益
+
+---
+
+### 7.3 批量审核(可选)
+
+**场景**:一次上传多张图片
+
+**实现**:
+- 使用阿里云批量审核接口 `/green/image/batchscan`
+- 减少API调用次数
+
+**注意**:当前流程是单张上传,暂不需要
+
+---
+
+## 八、日志记录
+
+**说明**:企业内部使用,暂不需要日志记录功能。
+
+---
+
+## 九、测试方案
+
+### 9.1 功能测试
+
+1. **正常图片上传**:
+ - ✅ 审核通过,正常上传到OSS
+ - ✅ 返回OSS URL
+
+2. **违规图片上传**:
+ - ✅ 审核不通过,拒绝上传
+ - ✅ 返回错误提示
+ - ✅ 本地文件已删除
+
+3. **审核服务异常**:
+ - ✅ 根据配置决定是否继续上传
+
+---
+
+### 9.2 性能测试
+
+1. **审核耗时**:
+ - 目标:单张图片审核耗时 < 2秒
+ - 测试:上传100张图片,统计平均耗时
+
+2. **并发测试**:
+ - 目标:支持10个并发上传
+ - 测试:同时上传10张图片
+
+---
+
+### 9.3 边界测试
+
+1. **大图片**:
+ - 测试:上传10MB图片
+ - 验证:审核是否正常
+
+2. **小图片**:
+ - 测试:上传1KB图片
+ - 验证:审核是否正常
+
+3. **特殊格式**:
+ - 测试:上传WebP、GIF动图
+ - 验证:审核是否支持
+
+---
+
+## 十、配置清单
+
+### 10.1 必需配置
+
+| 配置项 | 位置 | 说明 |
+|--------|------|------|
+| `ImageModeration:Enabled` | `appsettings.json` | 是否启用图片审核 |
+| `ImageModeration:Endpoint` | `appsettings.json` | 内容安全服务端点 |
+| `ImageModeration:Region` | `appsettings.json` | 服务区域 |
+| `ImageModeration:AccessKeyId` | `appsettings.json` | AccessKey ID(可选,可复用OSS) |
+| `ImageModeration:AccessKeySecret` | `appsettings.json` | AccessKey Secret(可选,可复用OSS) |
+
+---
+
+### 10.2 可选配置
+
+| 配置项 | 默认值 | 说明 |
+|--------|--------|------|
+| `ImageModeration:Scenes` | `["porn", "terrorism", "ad", "qrcode"]` | 审核场景 |
+| `ImageModeration:SuggestionLevel` | `"block"` | 审核建议级别 |
+| `ImageModeration:RiskLevel` | `"high"` | 风险等级阈值 |
+| `ImageModeration:FailOnError` | `"false"` | 审核服务异常时是否拒绝上传 |
+| `ImageModeration:Timeout` | `5` | 审核超时时间(秒) |
+
+---
+
+## 十一、实施步骤
+
+### 11.1 第一阶段:基础集成
+
+1. ✅ 创建 `ImageModerationService` 服务类
+2. ✅ 添加配置项到 `appsettings.json`
+3. ✅ 在 `Startup.cs` 注册服务
+4. ✅ 改造 `Uploader` 方法,集成审核逻辑
+5. ✅ 测试标准文件上传流程
+
+**预计工作量**:2-3人天
+
+---
+
+### 11.2 第二阶段:Base64上传改造
+
+1. ✅ 改造 `UploadBase64Image` 方法
+2. ✅ 添加临时文件保存逻辑
+3. ✅ 集成审核逻辑
+4. ✅ 测试Base64上传流程
+
+**预计工作量**:1-2人天
+
+---
+
+### 11.3 第三阶段:优化与完善
+
+1. ✅ 优化异常处理
+2. ✅ 性能测试与优化
+3. ✅ 文档完善
+
+**预计工作量**:0.5人天
+
+---
+
+## 十二、风险评估
+
+### 12.1 技术风险
+
+| 风险 | 影响 | 应对措施 |
+|------|------|----------|
+| 审核服务不可用 | 高 | 实现降级策略,允许配置是否继续上传 |
+| 审核超时 | 中 | 设置超时时间,超时后根据配置决定 |
+| 审核误判 | 中 | 提供人工审核机制,支持人工复查 |
+| 性能影响 | 低 | 异步审核,不阻塞主流程 |
+
+---
+
+### 12.2 业务风险
+
+| 风险 | 影响 | 应对措施 |
+|------|------|----------|
+| 正常图片被误判 | 中 | 提供申诉机制,人工审核 |
+| 违规图片漏检 | 高 | 定期优化审核策略,人工抽检 |
+| 审核成本 | 低 | 按量计费,成本可控 |
+
+---
+
+## 十三、后续优化建议
+
+### 13.1 人工审核机制(可选)
+
+- 审核结果为 `review` 时,进入人工审核队列
+- 提供管理后台,支持人工审核
+- 审核通过后允许上传
+
+---
+
+### 13.2 审核结果统计
+
+- 统计审核通过率
+- 统计违规类型分布
+- 生成审核报告
+
+---
+
+### 13.3 白名单机制
+
+- 支持配置白名单(特定用户或IP)
+- 白名单用户跳过审核
+
+---
+
+## 十四、总结
+
+### 14.1 集成方案要点
+
+1. ✅ **集成位置**:本地保存之后、OSS上传之前
+2. ✅ **审核服务**:创建独立的 `ImageModerationService` 服务类
+3. ✅ **配置灵活**:支持开关、审核级别、异常处理策略等配置
+4. ✅ **降级策略**:审核服务异常时可选择继续上传或拒绝上传
+5. ✅ **文件处理**:审核不通过保留本地文件,审核通过后上传OSS并删除本地文件
+
+---
+
+### 14.2 改造影响
+
+- ✅ **最小化影响**:仅在上传流程中增加审核步骤
+- ✅ **向后兼容**:可通过配置开关控制是否启用审核
+- ✅ **性能可控**:异步审核,不阻塞主流程
+
+---
+
+**文档版本**:v1.0
+**最后更新**:2026年1月
+**状态**:待实施
diff --git a/netcore/src/Application/NCC.API.Core/Startup.cs b/netcore/src/Application/NCC.API.Core/Startup.cs
index d497022..5c65de3 100644
--- a/netcore/src/Application/NCC.API.Core/Startup.cs
+++ b/netcore/src/Application/NCC.API.Core/Startup.cs
@@ -1,4 +1,4 @@
-using NCC.Common.Cache;
+using NCC.Common.Cache;
using NCC.Common.Core.Filter;
using NCC.Data.SqlSugar.Extensions;
using NCC.JsonSerialization;
@@ -138,6 +138,13 @@ namespace NCC.API.Core
#endregion
+ #region 阿里云图片审核
+
+ // 始终注册服务,服务内部会根据配置判断是否启用
+ services.AddScoped();
+
+ #endregion
+
#region 微信
services.AddSenparcGlobalServices(App.Configuration)//Senparc.CO2NET 全局注册
.AddSenparcWeixinServices(App.Configuration);//Senparc.Weixin 注册(如果使用Senparc.Weixin SDK则添加)
diff --git a/netcore/src/Application/NCC.API/appsettings.json b/netcore/src/Application/NCC.API/appsettings.json
index 6f55748..5838145 100644
--- a/netcore/src/Application/NCC.API/appsettings.json
+++ b/netcore/src/Application/NCC.API/appsettings.json
@@ -218,6 +218,14 @@
"Region": "cn-chengdu",
"CustomDomain": "https://lvqian-erip.oss-cn-chengdu.aliyuncs.com"
},
+ "ImageModeration": {
+ "Enabled": "true",
+ "Endpoint": "https://green.cn-shanghai.aliyuncs.com",
+ "Region": "cn-chengdu",
+ "AccessKeyId": "",
+ "AccessKeySecret": "",
+ "FailOnError": "false"
+ },
//================== 系统错误邮件报告反馈相关 ============================== -->
//软件的错误报告
"ErrorReport": "false",
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/ImageModerationService.cs b/netcore/src/Modularity/Extend/NCC.Extend/ImageModerationService.cs
new file mode 100644
index 0000000..c3d3ef8
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend/ImageModerationService.cs
@@ -0,0 +1,659 @@
+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