Files
2026-03-03 16:04:20 +08:00

485 lines
17 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Ink_Canvas.Helpers
{
/// <summary>
/// Dlass API 客户端,用于与服务端通信
/// </summary>
public class DlassApiClient : IDisposable
{
private const string DEFAULT_BASE_URL = "https://dlass.tech";
private readonly string _appId;
private readonly string _appSecret;
private readonly string _baseUrl;
private HttpClient _httpClient;
private string _accessToken;
private DateTime _tokenExpiresAt;
private string _userToken;
/// <summary>
/// 初始化 Dlass API 客户端
/// </summary>
/// <param name="appId">应用ID</param>
/// <param name="appSecret">应用密钥</param>
/// <param name="baseUrl">API基础URL,如果为空则使用默认URL</param>
/// <param name="userToken">用户Token,如果提供则优先使用用户token而不是App Secret</param>
public DlassApiClient(string appId, string appSecret, string baseUrl = null, string userToken = null)
{
_appId = appId ?? throw new ArgumentNullException(nameof(appId));
_appSecret = appSecret ?? throw new ArgumentNullException(nameof(appSecret));
_userToken = userToken;
_baseUrl = baseUrl ?? DEFAULT_BASE_URL;
_baseUrl = _baseUrl.TrimEnd('/');
if (!_baseUrl.StartsWith("http://") && !_baseUrl.StartsWith("https://"))
{
_baseUrl = "https://" + _baseUrl;
}
_httpClient = new HttpClient
{
BaseAddress = new Uri(_baseUrl),
Timeout = TimeSpan.FromSeconds(30)
};
_httpClient.DefaultRequestHeaders.Add("User-Agent", "InkCanvas/1.0");
}
/// <summary>
/// 获取访问令牌(Access Token
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
public async Task<string> GetAccessTokenAsync(CancellationToken cancellationToken = default)
{
if (!string.IsNullOrEmpty(_userToken))
{
return _userToken;
}
if (!string.IsNullOrEmpty(_accessToken) && DateTime.Now < _tokenExpiresAt.AddMinutes(-5))
{
return _accessToken;
}
try
{
cancellationToken.ThrowIfCancellationRequested();
var requestData = new
{
app_id = _appId,
app_secret = _appSecret,
grant_type = "client_credentials"
};
var json = JsonConvert.SerializeObject(requestData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/oauth/token", content, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
_accessToken = tokenResponse.AccessToken;
_tokenExpiresAt = DateTime.Now.AddSeconds(tokenResponse.ExpiresIn ?? 3600);
return _accessToken;
}
else
{
throw new Exception($"获取Access Token失败: {response.StatusCode}");
}
}
catch (OperationCanceledException)
{
throw;
}
catch (HttpRequestException httpEx)
{
throw new Exception($"获取Access Token时网络错误: {httpEx.Message}", httpEx);
}
catch (Exception ex)
{
throw new Exception($"获取Access Token时出错: {ex.Message}", ex);
}
}
/// <summary>
/// 发送GET请求
/// </summary>
/// <param name="endpoint">API端点</param>
/// <param name="requireAuth">是否需要认证</param>
/// <param name="cancellationToken">取消令牌</param>
public async Task<T> GetAsync<T>(string endpoint, bool requireAuth = true, CancellationToken cancellationToken = default)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
string token = null;
if (requireAuth)
{
token = await GetAccessTokenAsync(cancellationToken);
}
var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
if (requireAuth && !string.IsNullOrEmpty(token))
{
if (!string.IsNullOrEmpty(_userToken))
{
request.Headers.Add("X-User-Token", token);
}
else
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
}
var response = await _httpClient.SendAsync(request, cancellationToken);
var content = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
if (string.IsNullOrEmpty(content))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(content);
}
else
{
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
}
}
catch (OperationCanceledException)
{
throw;
}
catch (HttpRequestException httpEx)
{
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
}
catch (Exception ex)
{
throw new Exception($"发送请求时出错: {ex.Message}", ex);
}
}
/// <summary>
/// 发送POST请求
/// </summary>
/// <param name="endpoint">API端点</param>
/// <param name="data">请求数据</param>
/// <param name="requireAuth">是否需要认证</param>
/// <param name="cancellationToken">取消令牌</param>
public async Task<T> PostAsync<T>(string endpoint, object data = null, bool requireAuth = true, CancellationToken cancellationToken = default)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
string token = null;
if (requireAuth)
{
token = await GetAccessTokenAsync(cancellationToken);
}
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
if (requireAuth && !string.IsNullOrEmpty(token))
{
if (!string.IsNullOrEmpty(_userToken))
{
request.Headers.Add("X-User-Token", token);
}
else
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
}
if (data != null)
{
var json = JsonConvert.SerializeObject(data);
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
}
var response = await _httpClient.SendAsync(request, cancellationToken);
var content = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
if (string.IsNullOrEmpty(content))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(content);
}
else
{
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
}
}
catch (OperationCanceledException)
{
throw;
}
catch (HttpRequestException httpEx)
{
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
}
catch (Exception ex)
{
throw new Exception($"发送请求时出错: {ex.Message}", ex);
}
}
/// <summary>
/// 发送PUT请求
/// </summary>
/// <param name="endpoint">API端点</param>
/// <param name="data">请求数据</param>
/// <param name="requireAuth">是否需要认证</param>
/// <param name="cancellationToken">取消令牌</param>
public async Task<T> PutAsync<T>(string endpoint, object data = null, bool requireAuth = true, CancellationToken cancellationToken = default)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
string token = null;
if (requireAuth)
{
token = await GetAccessTokenAsync(cancellationToken);
}
var request = new HttpRequestMessage(HttpMethod.Put, endpoint);
if (requireAuth && !string.IsNullOrEmpty(token))
{
// 如果是用户token,使用X-User-Token header
if (!string.IsNullOrEmpty(_userToken))
{
request.Headers.Add("X-User-Token", token);
}
else
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
}
if (data != null)
{
var json = JsonConvert.SerializeObject(data);
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
}
var response = await _httpClient.SendAsync(request, cancellationToken);
var content = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
if (string.IsNullOrEmpty(content))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(content);
}
else
{
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
}
}
catch (OperationCanceledException)
{
throw;
}
catch (HttpRequestException httpEx)
{
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
}
catch (Exception ex)
{
throw new Exception($"发送请求时出错: {ex.Message}", ex);
}
}
/// <summary>
/// 发送DELETE请求
/// </summary>
/// <param name="endpoint">API端点</param>
/// <param name="requireAuth">是否需要认证</param>
/// <param name="cancellationToken">取消令牌</param>
public async Task<bool> DeleteAsync(string endpoint, bool requireAuth = true, CancellationToken cancellationToken = default)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
string token = null;
if (requireAuth)
{
token = await GetAccessTokenAsync(cancellationToken);
}
var request = new HttpRequestMessage(HttpMethod.Delete, endpoint);
if (requireAuth && !string.IsNullOrEmpty(token))
{
// 如果是用户token,使用X-User-Token header
if (!string.IsNullOrEmpty(_userToken))
{
request.Headers.Add("X-User-Token", token);
}
else
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
}
var response = await _httpClient.SendAsync(request, cancellationToken);
if (response.IsSuccessStatusCode)
{
return true;
}
else
{
return false;
}
}
catch (OperationCanceledException)
{
throw;
}
catch (HttpRequestException)
{
return false;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 上传笔记文件
/// </summary>
/// <param name="endpoint">上传端点</param>
/// <param name="filePath">文件路径</param>
/// <param name="boardId">白板ID</param>
/// <param name="secretKey">白板密钥</param>
/// <param name="title">笔记标题(可选)</param>
/// <param name="description">笔记描述(可选)</param>
/// <param name="tags">笔记标签(可选)</param>
/// <param name="cancellationToken">取消令牌</param>
public async Task<T> UploadNoteAsync<T>(string endpoint, string filePath, string boardId, string secretKey, string title = null, string description = null, string tags = null, CancellationToken cancellationToken = default)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"文件不存在: {filePath}");
}
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
// 设置白板认证头
request.Headers.Add("X-Board-ID", boardId);
request.Headers.Add("X-Secret-Key", secretKey);
// 创建multipart/form-data内容
var content = new MultipartFormDataContent();
// 添加文件
var fileContent = new ByteArrayContent(File.ReadAllBytes(filePath));
var fileName = Path.GetFileName(filePath);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Add(fileContent, "file", fileName);
// 添加可选参数
if (!string.IsNullOrEmpty(title))
{
content.Add(new StringContent(title), "title");
}
if (!string.IsNullOrEmpty(description))
{
content.Add(new StringContent(description), "description");
}
if (!string.IsNullOrEmpty(tags))
{
content.Add(new StringContent(tags), "tags");
}
request.Content = content;
var response = await _httpClient.SendAsync(request, cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
if (string.IsNullOrEmpty(responseContent))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(responseContent);
}
else
{
throw new Exception($"上传文件失败: {response.StatusCode} - {responseContent}");
}
}
catch (OperationCanceledException)
{
throw;
}
catch (HttpRequestException httpEx)
{
throw new Exception($"上传文件时网络错误: {httpEx.Message}", httpEx);
}
catch (Exception ex)
{
throw new Exception($"上传文件时出错: {ex.Message}", ex);
}
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
_httpClient?.Dispose();
}
#region
/// <summary>
/// Token响应模型
/// </summary>
private class TokenResponse
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int? ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
#endregion
}
}