Skip to content

Commit 82dcdbc

Browse files
committed
refactor(http): 优化HttpClient扩展方法并添加元数据支持
重构HttpClientExtensions类,简化JSON序列化逻辑并优化性能 在AnthropicInput类中添加metadata字段支持 统一使用ThorJsonSerializer进行JSON序列化 为前端配额查询页面添加缓存和自动刷新功能
1 parent 5c1251b commit 82dcdbc

11 files changed

Lines changed: 200 additions & 215 deletions

File tree

ClaudeCodeProxy.sln.DotSettings.user

Lines changed: 18 additions & 1 deletion
Large diffs are not rendered by default.

src/ClaudeCodeProxy.Abstraction/Anthropic/AnthropicInput.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,10 @@ public object SystemCalculated
8282
}
8383

8484
[JsonPropertyName("thinking")] public AnthropicThinkingInput? Thinking { get; set; }
85-
85+
8686
[JsonPropertyName("temperature")] public double? Temperature { get; set; }
87+
88+
[JsonPropertyName("metadata")] public Dictionary<string, object>? Metadata { get; set; }
8789
}
8890

8991
public class AnthropicThinkingInput
Lines changed: 35 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -1,231 +1,72 @@
11
using System.Net.Http.Headers;
2-
using System.Net.Http.Json;
3-
using System.Text;
42
using System.Text.Json;
5-
using System.Text.Json.Serialization;
63
using ClaudeCodeProxy.Abstraction;
74

85
namespace ClaudeCodeProxy.Core.Extensions;
96

107
public static class HttpClientExtensions
118
{
12-
public static async Task<HttpResponseMessage> HttpRequestRaw(this HttpClient httpClient, string url,
13-
object? postData,
14-
string token)
15-
{
16-
HttpRequestMessage req = new(HttpMethod.Post, url);
17-
18-
if (postData != null)
19-
{
20-
if (postData is HttpContent data)
21-
{
22-
req.Content = data;
23-
}
24-
else
25-
{
26-
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
27-
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
28-
req.Content = stringContent;
29-
}
30-
}
31-
32-
if (!string.IsNullOrEmpty(token))
33-
{
34-
req.Headers.Add("Authorization", $"Bearer {token}");
35-
}
9+
private static readonly MediaTypeHeaderValue JsonMediaType =
10+
new("application/json") { CharSet = "utf-8" };
3611

37-
var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
38-
39-
return response;
40-
}
41-
42-
public static async Task<HttpResponseMessage> HttpRequestRaw(this HttpClient httpClient, string url,
43-
object? postData,
44-
string token, string tokenKey)
12+
private static async ValueTask<HttpContent> CreateJsonContentAsync(object value)
4513
{
46-
HttpRequestMessage req = new(HttpMethod.Post, url);
47-
48-
if (postData != null)
49-
{
50-
if (postData is HttpContent data)
51-
{
52-
req.Content = data;
53-
}
54-
else
55-
{
56-
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
57-
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
58-
req.Content = stringContent;
59-
}
60-
}
61-
62-
if (!string.IsNullOrEmpty(token))
63-
{
64-
req.Headers.Add(tokenKey, token);
65-
}
66-
67-
68-
var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
69-
70-
return response;
14+
var ms = new MemoryStream(16 * 1024); // 预分配减少扩容
15+
await JsonSerializer.SerializeAsync(ms, value, value.GetType(), ThorJsonSerializer.DefaultOptions)
16+
.ConfigureAwait(false);
17+
ms.Position = 0;
18+
19+
var content = new StreamContent(ms);
20+
content.Headers.ContentType = JsonMediaType;
21+
return content;
7122
}
7223

73-
public static async Task<HttpResponseMessage> HttpRequestRaw(this HttpClient httpClient, string url,
74-
object? postData,
75-
string token, Dictionary<string, string> headers)
24+
public static async Task<HttpResponseMessage> HttpRequestRaw<T>(this HttpClient httpClient, string url,
25+
T postData, string token, Dictionary<string, string> headers) where T : class
7626
{
77-
HttpRequestMessage req = new(HttpMethod.Post, url);
78-
79-
if (postData != null)
27+
var req = new HttpRequestMessage(HttpMethod.Post, url)
8028
{
81-
if (postData is HttpContent data)
82-
{
83-
req.Content = data;
84-
}
85-
else
86-
{
87-
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
88-
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
89-
req.Content = stringContent;
90-
}
91-
}
29+
Content = await CreateJsonContentAsync(postData).ConfigureAwait(false)
30+
};
9231

93-
if (!string.IsNullOrEmpty(token))
32+
if (!string.IsNullOrWhiteSpace(token))
9433
{
95-
req.Headers.Add("Authorization", $"Bearer {token}");
34+
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
9635
}
9736

98-
foreach (var header in headers.Where(header => !req.Headers.Contains(header.Key)))
37+
foreach (var kv in headers)
9938
{
100-
req.Headers.Add(header.Key, header.Value);
39+
if (!req.Headers.Contains(kv.Key))
40+
req.Headers.TryAddWithoutValidation(kv.Key, kv.Value);
10141
}
10242

103-
104-
var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
105-
106-
return response;
43+
return await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
10744
}
10845

109-
public static async Task<HttpResponseMessage> HttpRequestRaw(this HttpClient httpClient, HttpRequestMessage req,
110-
object? postData)
46+
public static async Task<HttpResponseMessage> PostJsonAsync<T>(this HttpClient httpClient, string url,
47+
T? postData, string token, Dictionary<string, string> headers) where T : class
11148
{
112-
if (postData != null)
49+
var req = new HttpRequestMessage(HttpMethod.Post, url)
11350
{
114-
if (postData is HttpContent data)
115-
{
116-
req.Content = data;
117-
}
118-
else
119-
{
120-
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
121-
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
122-
req.Content = stringContent;
123-
}
124-
}
125-
126-
var response = await httpClient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
51+
Content = await CreateJsonContentAsync(postData).ConfigureAwait(false)
52+
};
12753

128-
return response;
129-
}
130-
131-
public static async Task<HttpResponseMessage> PostJsonAsync(this HttpClient httpClient, string url,
132-
object? postData,
133-
string token)
134-
{
135-
HttpRequestMessage req = new(HttpMethod.Post, url);
136-
137-
if (postData != null)
138-
{
139-
if (postData is HttpContent data)
140-
{
141-
req.Content = data;
142-
}
143-
else
144-
{
145-
var stringContent =
146-
new StringContent(JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions),
147-
Encoding.UTF8, "application/json");
148-
req.Content = stringContent;
149-
}
150-
}
151-
152-
if (!string.IsNullOrEmpty(token))
153-
{
154-
req.Headers.Add("Authorization", $"Bearer {token}");
155-
}
156-
157-
return await httpClient.SendAsync(req);
158-
}
159-
160-
public static async Task<HttpResponseMessage> PostJsonAsync(this HttpClient httpClient, string url,
161-
object? postData,
162-
string token, Dictionary<string, string> headers)
163-
{
164-
HttpRequestMessage req = new(HttpMethod.Post, url);
165-
166-
if (postData != null)
54+
if (!string.IsNullOrWhiteSpace(token))
16755
{
168-
if (postData is HttpContent data)
169-
{
170-
req.Content = data;
171-
}
172-
else
173-
{
174-
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
175-
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
176-
177-
if (url.StartsWith("https://chatgpt.com/backend-api/codex"))
178-
{
179-
stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
180-
}
181-
182-
req.Content = stringContent;
183-
}
56+
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
18457
}
18558

186-
if (!string.IsNullOrEmpty(token))
187-
{
188-
req.Headers.Add("Authorization", $"Bearer {token}");
189-
}
190-
191-
foreach (var header in headers.Where(header => !req.Headers.Contains(header.Key)))
192-
{
193-
req.Headers.Add(header.Key, header.Value);
194-
}
195-
196-
if (url.StartsWith("https://chatgpt.com/backend-api/codex"))
197-
{
198-
req.Headers.Add("Host", "chatgpt.com");
199-
}
200-
201-
202-
return await httpClient.SendAsync(req);
203-
}
204-
205-
public static Task<HttpResponseMessage> PostJsonAsync(this HttpClient httpClient, string url, object? postData,
206-
string token, string tokenKey)
207-
{
208-
HttpRequestMessage req = new(HttpMethod.Post, url);
209-
210-
if (postData != null)
59+
foreach (var kv in headers)
21160
{
212-
if (postData is HttpContent data)
213-
{
214-
req.Content = data;
215-
}
216-
else
217-
{
218-
string jsonContent = JsonSerializer.Serialize(postData, ThorJsonSerializer.DefaultOptions);
219-
var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
220-
req.Content = stringContent;
221-
}
61+
if (!req.Headers.Contains(kv.Key))
62+
req.Headers.TryAddWithoutValidation(kv.Key, kv.Value);
22263
}
22364

224-
if (!string.IsNullOrEmpty(token))
65+
if (url.StartsWith("https://chatgpt.com/backend-api/codex", StringComparison.OrdinalIgnoreCase))
22566
{
226-
req.Headers.Add(tokenKey, token);
67+
req.Headers.Host = "chatgpt.com";
22768
}
22869

229-
return httpClient.SendAsync(req);
70+
return await httpClient.SendAsync(req).ConfigureAwait(false);
23071
}
23172
}

src/ClaudeCodeProxy.Host/Endpoints/MessageEndpoints.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using ClaudeCodeProxy.Abstraction.Chats;
22
using ClaudeCodeProxy.Core;
33
using ClaudeCodeProxy.Host.Services;
4+
using ClaudeCodeProxy.Host.Services.AI;
45
using Microsoft.AspNetCore.Http.HttpResults;
56
using Microsoft.AspNetCore.Mvc;
67
using Thor.Abstractions;

src/ClaudeCodeProxy.Host/Endpoints/RedeemCodeEndpoints.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.AspNetCore.Authorization;
44
using Microsoft.AspNetCore.Mvc;
55
using System.Security.Claims;
6+
using ClaudeCodeProxy.Abstraction;
67
using ClaudeCodeProxy.Core;
78

89
namespace ClaudeCodeProxy.Host.Endpoints;
@@ -195,7 +196,7 @@ private static async Task<IResult> UpdateRedeemCodeStatus(
195196
{
196197
// 从请求体中提取isEnabled
197198
var requestDict = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(
198-
System.Text.Json.JsonSerializer.Serialize(requestBody));
199+
System.Text.Json.JsonSerializer.Serialize(requestBody, ThorJsonSerializer.DefaultOptions));
199200

200201
if (!requestDict.TryGetValue("isEnabled", out var isEnabledObj) ||
201202
!bool.TryParse(isEnabledObj.ToString(), out var isEnabled))

src/ClaudeCodeProxy.Host/Helper/SessionHelper.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Security.Cryptography;
22
using System.Text;
33
using System.Text.Json;
4+
using ClaudeCodeProxy.Abstraction;
45
using Microsoft.Extensions.Logging;
56
using Thor.Abstractions.Anthropic;
67

@@ -28,7 +29,7 @@ public SessionHelper(ILogger<SessionHelper> logger)
2829
try
2930
{
3031
// 将对象序列化为JSON以便处理
31-
var json = JsonSerializer.Serialize(requestBody);
32+
var json = JsonSerializer.Serialize(requestBody,ThorJsonSerializer.DefaultOptions);
3233
using var document = JsonDocument.Parse(json);
3334
var root = document.RootElement;
3435

src/ClaudeCodeProxy.Host/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using ClaudeCodeProxy.Domain;
2020
using ClaudeCodeProxy.EntityFrameworkCore.PostgreSQL;
2121
using ClaudeCodeProxy.Host.Middlewares;
22+
using ClaudeCodeProxy.Host.Services.AI;
2223
using Mapster;
2324

2425
namespace ClaudeCodeProxy.Host;

src/ClaudeCodeProxy.Host/Services/MessageService.cs renamed to src/ClaudeCodeProxy.Host/Services/AI/MessageService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
using Thor.Abstractions;
1212
using Thor.Abstractions.Anthropic;
1313

14-
namespace ClaudeCodeProxy.Host.Services;
14+
namespace ClaudeCodeProxy.Host.Services.AI;
1515

1616
[MiniApi(Route = "/v1/messages", Tags = "Messages")]
1717
public partial class MessageService(

src/ClaudeCodeProxy.Host/Services/AccountsService.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Text;
22
using System.Text.Json;
3+
using ClaudeCodeProxy.Abstraction;
34
using ClaudeCodeProxy.Core;
45
using ClaudeCodeProxy.Domain;
56
using ClaudeCodeProxy.Host.Models;
@@ -145,7 +146,8 @@ public async Task<Accounts> CreateAccountAsync(
145146
}
146147
else
147148
{
148-
account.GeminiOauth = System.Text.Json.JsonSerializer.Serialize(request.GeminiOauth);
149+
account.GeminiOauth =
150+
System.Text.Json.JsonSerializer.Serialize(request.GeminiOauth, ThorJsonSerializer.DefaultOptions);
149151
}
150152
}
151153

@@ -292,8 +294,8 @@ public async Task<int> RecoverExpiredRateLimitedAccountsAsync(CancellationToken
292294
{
293295
var now = DateTime.UtcNow;
294296
var rowsAffected = await context.Accounts
295-
.Where(x => x.Status == "rate_limited" &&
296-
(x.RateLimitedUntil == null || x.RateLimitedUntil < now))
297+
.Where(x => x.Status == "rate_limited" &&
298+
(x.RateLimitedUntil == null || x.RateLimitedUntil < now))
297299
.ExecuteUpdateAsync(x => x
298300
.SetProperty(a => a.Status, "active")
299301
.SetProperty(a => a.RateLimitedUntil, (DateTime?)null)

src/ClaudeCodeProxy.Host/Services/RequestLogService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using ClaudeCodeProxy.Host.Models;
44
using Microsoft.EntityFrameworkCore;
55
using System.Text.Json;
6+
using ClaudeCodeProxy.Abstraction;
67

78
namespace ClaudeCodeProxy.Host.Services;
89

@@ -46,7 +47,7 @@ public async Task<RequestLog> CreateRequestLogAsync(
4647
UserAgent = userAgent,
4748
RequestId = requestId,
4849
IsStreaming = isStreaming,
49-
Metadata = metadata != null ? JsonSerializer.Serialize(metadata) : null,
50+
Metadata = metadata != null ? JsonSerializer.Serialize(metadata, ThorJsonSerializer.DefaultOptions) : null,
5051
CreatedAt = DateTime.Now
5152
};
5253

0 commit comments

Comments
 (0)