我需要从BlackDuck这款扫描工具下载扫描报告,请用C#写一份示例代码,要求是: 1. 使用BlackDuck Web UI生成的API token换取Bear token登录进BlackDuck; 2. 我需要分析项目里使用的所有第三方商业组件是否有License风险,所以需要下载License分析报告;
时间: 2025-07-20 08:24:13 浏览: 11
<think>我们需要从BlackDuck下载License分析报告。根据要求,我们使用API token换取Bearer token,然后创建并下载License报告。
步骤:
1. 使用API Token进行认证,获取Bearer Token。
2. 根据项目名称和版本名称定位到具体的项目版本。
3. 创建License报告(报告类型为"LICENSE")。
4. 轮询报告状态直到完成。
5. 下载报告。
注意:BlackDuck API中,创建报告需要指定报告格式(如CSV、PDF等)和报告类型(这里为LICENSE)。
我们将编写一个C#控制台程序,包含以下方法:
- GetBearerTokenAsync: 使用API Token获取Bearer Token
- FindProjectVersionIdAsync: 根据项目名和版本名查找项目版本的HREF(ID)
- CreateLicenseReportAsync: 创建License报告并返回报告ID
- DownloadReportAsync: 下载报告到本地文件
注意:报告生成可能需要一些时间,因此我们需要轮询报告状态直到完成。
代码结构如下:
1. 定义BlackDuckReportDownloader类,包含HttpClient和基础URL。
2. 实现认证方法。
3. 实现查找项目版本ID的方法。
4. 创建报告并轮询状态,然后下载。
我们将使用System.Text.Json进行JSON处理。
注意:BlackDuck API的响应是分页的,但我们在查找项目时假设项目名和版本名是唯一的,所以取第一个匹配项。
以下是具体代码:</think>以下是使用 C# 从 BlackDuck 下载 License 分析报告的完整解决方案,包含 API 认证和商业组件 License 风险报告生成:
```csharp
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class BlackDuckLicenseReportDownloader
{
private readonly string _baseUrl;
private readonly string _apiToken;
private readonly HttpClient _httpClient;
public BlackDuckLicenseReportDownloader(string baseUrl, string apiToken)
{
_baseUrl = baseUrl.TrimEnd('/');
_apiToken = apiToken;
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromMinutes(15); // 延长超时时间
}
// 1. 使用API Token换取Bearer Token
public async Task<string> AuthenticateAsync()
{
var request = new HttpRequestMessage(HttpMethod.Post, $"{_baseUrl}/api/tokens/authenticate");
request.Headers.Add("Authorization", $"token {_apiToken}");
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
using var jsonDoc = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
return jsonDoc.RootElement.GetProperty("bearerToken").GetString();
}
// 2. 获取项目版本ID
public async Task<string> GetProjectVersionIdAsync(string bearerToken, string projectName, string versionName)
{
// URL编码查询参数
var encodedProjectName = Uri.EscapeDataString(projectName);
var encodedVersionName = Uri.EscapeDataString(versionName);
// 查找项目
var projectRequest = new HttpRequestMessage(
HttpMethod.Get,
$"{_baseUrl}/api/projects?q=name:{encodedProjectName}"
);
projectRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
var projectResponse = await _httpClient.SendAsync(projectRequest);
projectResponse.EnsureSuccessStatusCode();
using var projectDoc = await JsonDocument.ParseAsync(await projectResponse.Content.ReadAsStreamAsync());
var items = projectDoc.RootElement.GetProperty("items");
if (items.GetArrayLength() == 0)
throw new Exception($"Project '{projectName}' not found");
var projectHref = items[0].GetProperty("_meta").GetProperty("href").GetString();
// 查找版本
var versionRequest = new HttpRequestMessage(
HttpMethod.Get,
$"{_baseUrl}{projectHref}/versions?q=versionName:{encodedVersionName}"
);
versionRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
var versionResponse = await _httpClient.SendAsync(versionRequest);
versionResponse.EnsureSuccessStatusCode();
using var versionDoc = await JsonDocument.ParseAsync(await versionResponse.Content.ReadAsStreamAsync());
var versionItems = versionDoc.RootElement.GetProperty("items");
if (versionItems.GetArrayLength() == 0)
throw new Exception($"Version '{versionName}' not found in project '{projectName}'");
return versionItems[0].GetProperty("_meta").GetProperty("href").GetString();
}
// 3. 创建License风险报告
public async Task<string> CreateLicenseRiskReportAsync(string bearerToken, string versionId)
{
var request = new HttpRequestMessage(
HttpMethod.Post,
$"{_baseUrl}{versionId}/reports"
);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
// 构建License风险报告请求体
var reportRequest = new
{
reportFormat = "CSV", // 支持CSV/PDF/JSON
reportType = "LICENSE",
reportContent = new
{
// 商业组件License风险分析所需字段
includeLicenseTexts = true,
includeSubcomponents = true,
licenseRiskProfile = true,
licenseTextAnalysis = true,
licenseConflicts = true,
licenseObligations = true
}
};
request.Content = new StringContent(
JsonSerializer.Serialize(reportRequest),
Encoding.UTF8,
"application/json"
);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
// 从Location头获取报告ID
var location = response.Headers.Location?.ToString();
if (string.IsNullOrEmpty(location))
throw new Exception("Failed to get report location");
return location.Substring(location.LastIndexOf('/') + 1);
}
// 4. 下载报告
public async Task DownloadLicenseReportAsync(string bearerToken, string reportId, string outputPath)
{
var statusUrl = $"{_baseUrl}/api/reports/{reportId}";
var downloadUrl = $"{_baseUrl}/api/reports/{reportId}/download";
// 轮询报告状态
bool isReady = false;
int attempts = 0;
const int maxAttempts = 30; // 30次尝试 (约5分钟)
while (!isReady && attempts < maxAttempts)
{
attempts++;
var statusRequest = new HttpRequestMessage(HttpMethod.Get, statusUrl);
statusRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
var statusResponse = await _httpClient.SendAsync(statusRequest);
statusResponse.EnsureSuccessStatusCode();
using var statusDoc = await JsonDocument.ParseAsync(await statusResponse.Content.ReadAsStreamAsync());
var status = statusDoc.RootElement.GetProperty("status").GetString();
switch (status)
{
case "COMPLETED":
isReady = true;
break;
case "ERROR":
var error = statusDoc.RootElement.GetProperty("error").GetString();
throw new Exception($"Report generation failed: {error}");
default:
// 等待10秒后重试
await Task.Delay(10000);
break;
}
}
if (!isReady)
throw new Exception("Report generation timed out");
// 下载报告
var downloadRequest = new HttpRequestMessage(HttpMethod.Get, downloadUrl);
downloadRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
var downloadResponse = await _httpClient.SendAsync(downloadRequest);
downloadResponse.EnsureSuccessStatusCode();
// 确保输出目录存在
var directory = Path.GetDirectoryName(outputPath);
if (!Directory.Exists(directory) && !string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
await using var fileStream = File.Create(outputPath);
await downloadResponse.Content.CopyToAsync(fileStream);
}
public static async Task Main()
{
const string baseUrl = "https://your-blackduck-instance.com";
const string apiToken = "your-api-token-from-ui";
const string projectName = "CommercialProject";
const string versionName = "Release-2.3";
const string outputPath = "C:/LicenseReports/commercial_components_license_risk.csv";
var downloader = new BlackDuckLicenseReportDownloader(baseUrl, apiToken);
try
{
Console.WriteLine("Authenticating with BlackDuck...");
string bearerToken = await downloader.AuthenticateAsync();
Console.WriteLine($"Locating project: {projectName} version: {versionName}");
string versionId = await downloader.GetProjectVersionIdAsync(bearerToken, projectName, versionName);
Console.WriteLine("Creating license risk report for commercial components...");
string reportId = await downloader.CreateLicenseRiskReportAsync(bearerToken, versionId);
Console.WriteLine($"Report ID: {reportId}");
Console.WriteLine("Generating report (this may take several minutes)...");
await downloader.DownloadLicenseReportAsync(bearerToken, reportId, outputPath);
Console.WriteLine($"License risk report saved to: {outputPath}");
Console.WriteLine("Analysis of commercial component licenses completed.");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
if (ex.InnerException != null)
{
Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
}
}
}
}
```
### 代码关键点解释:
1. **License风险报告配置**:
```csharp
var reportRequest = new
{
reportFormat = "CSV",
reportType = "LICENSE",
reportContent = new
{
includeLicenseTexts = true, // 包含许可证全文
includeSubcomponents = true, // 包含子组件
licenseRiskProfile = true, // 许可证风险分析
licenseTextAnalysis = true, // 许可证文本分析
licenseConflicts = true, // 许可证冲突检测
licenseObligations = true // 许可证义务分析
}
};
```
这些选项专门针对商业组件的License合规性分析需求
2. **商业组件风险分析重点**:
- 识别具有严格许可证要求的组件(如GPL、AGPL)
- 检测许可证冲突(如GPL与商业许可证冲突)
- 分析许可证义务(如源代码公开要求)
- 评估子组件的许可证合规性
3. **报告内容解析**:
CSV报告将包含以下关键列(示例):
```
Component Name,Version,License,License Risk,Conflicting Licenses,Obligations
openssl,1.1.1k,OpenSSL License,Medium,None,Documentation Requirement
log4j,2.14.1,Apache-2.0,Low,None,Notice Requirement
GPL-lib,3.0,GPL-3.0,High,Commercial License,Source Code Disclosure
```
4. **增强的错误处理**:
- 项目/版本不存在时的明确错误提示
- 报告生成失败时的详细错误信息
- 30次轮询尝试(约5分钟超时)
- 网络超时延长至15分钟
### 使用说明:
1. **准备工作**:
```bash
# 安装必要NuGet包
dotnet add package System.Text.Json
```
2. **配置参数**:
```csharp
const string baseUrl = "https://your-blackduck-instance.com";
const string apiToken = "your-api-token-from-ui"; // 从Web UI获取
const string projectName = "CommercialProject"; // 目标商业项目
const string versionName = "Release-2.3"; // 项目版本
const string outputPath = "C:/LicenseReports/commercial_components_license_risk.csv";
```
3. **报告内容示例**:
生成的CSV报告将包含以下关键信息:
- 组件名称和版本
- 检测到的许可证类型
- 风险等级(高/中/低)
- 与其他许可证的冲突
- 合规义务要求
- 法律风险评估
### 典型输出:
```
Authenticating with BlackDuck...
Locating project: CommercialProject version: Release-2.3
Creating license risk report for commercial components...
Report ID: 8a7f6d4c-3b2a-4e5c-9d8e-f1a2b3c4d5e6
Generating report (this may take several minutes)...
License risk report saved to: C:/LicenseReports/commercial_components_license_risk.csv
Analysis of commercial component licenses completed.
```
阅读全文
相关推荐



















