Skip to content

Commit a45cd9a

Browse files
committed
feat: 实现主流语言操作库 #16
1 parent 6353765 commit a45cd9a

43 files changed

Lines changed: 3089 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
# Cyaim.WebSocketServer.Client
2+
3+
WebSocket 客户端生成器,支持自动从服务端获取 endpoint 列表并生成接口契约式客户端。
4+
5+
## 功能特性
6+
7+
- ✅ 自动从服务端获取 WebSocket endpoint 列表
8+
- ✅ 接口契约式调用服务端 endpoint
9+
- ✅ 类型安全的请求和响应
10+
- ✅ 自动连接管理和错误处理
11+
- ✅ 支持自定义契约接口,只需定义需要的方法
12+
- ✅ 支持通过特性指定 endpoint 映射
13+
- ✅ 支持延迟加载 endpoint,按需获取
14+
- ✅ 灵活的验证选项,支持部分匹配
15+
16+
## 快速开始
17+
18+
### 1. 定义接口契约
19+
20+
根据实际需求定义接口,**只需定义需要的方法**,不需要一次性获取所有 endpoint:
21+
22+
```csharp
23+
public interface IWeatherService
24+
{
25+
Task<WeatherForecast[]> GetForecastsAsync();
26+
Task<WeatherForecast> GetForecastAsync(string city);
27+
}
28+
```
29+
30+
### 2. 创建客户端工厂
31+
32+
#### 基本用法
33+
34+
```csharp
35+
using Cyaim.WebSocketServer.Client;
36+
37+
var factory = new WebSocketClientFactory("http://localhost:5000", "/ws");
38+
var client = await factory.CreateClientAsync<IWeatherService>();
39+
```
40+
41+
#### 使用自定义选项
42+
43+
```csharp
44+
var options = new WebSocketClientOptions
45+
{
46+
ValidateAllMethods = false, // 不验证所有方法(默认)
47+
LazyLoadEndpoints = true, // 延迟加载 endpoint
48+
ThrowOnEndpointNotFound = true // 找不到 endpoint 时抛出异常
49+
};
50+
51+
var factory = new WebSocketClientFactory("http://localhost:5000", "/ws", options);
52+
var client = await factory.CreateClientAsync<IWeatherService>();
53+
```
54+
55+
### 3. 调用服务端方法
56+
57+
```csharp
58+
// 调用无参数方法
59+
var forecasts = await client.GetForecastsAsync();
60+
61+
// 调用带参数方法
62+
var forecast = await client.GetForecastAsync("Beijing");
63+
```
64+
65+
### 4. 使用特性指定 endpoint(可选)
66+
67+
如果方法名与服务端 endpoint 不匹配,可以使用 `[WebSocketEndpoint]` 特性:
68+
69+
```csharp
70+
public interface IWeatherService
71+
{
72+
// 自动匹配:方法名 "GetForecasts" 会匹配服务端的 "GetForecasts" action
73+
Task<WeatherForecast[]> GetForecastsAsync();
74+
75+
// 使用特性指定:明确指定 endpoint target
76+
[WebSocketEndpoint("weatherforecast.getbycity")]
77+
Task<WeatherForecast> GetForecastAsync(string city);
78+
}
79+
```
80+
81+
## API 说明
82+
83+
### WebSocketClientFactory
84+
85+
客户端工厂类,用于创建 WebSocket 客户端代理。
86+
87+
#### 构造函数
88+
89+
```csharp
90+
public WebSocketClientFactory(
91+
string serverBaseUrl,
92+
string channel = "/ws",
93+
WebSocketClientOptions? options = null)
94+
```
95+
96+
- `serverBaseUrl`: 服务器基础 URL(例如:"http://localhost:5000")
97+
- `channel`: WebSocket 通道(默认:"/ws")
98+
- `options`: 客户端创建选项(可选)
99+
100+
#### 方法
101+
102+
##### CreateClientAsync<T>()
103+
104+
创建指定接口的客户端代理。
105+
106+
```csharp
107+
var client = await factory.CreateClientAsync<IWeatherService>();
108+
```
109+
110+
##### GetEndpointsAsync()
111+
112+
从服务器获取所有 WebSocket endpoint 列表。
113+
114+
```csharp
115+
var endpoints = await factory.GetEndpointsAsync();
116+
```
117+
118+
### WebSocketClientOptions
119+
120+
客户端创建选项。
121+
122+
#### 属性
123+
124+
- `ValidateAllMethods` (bool, 默认: false): 是否验证所有方法都有对应的端点
125+
- `LazyLoadEndpoints` (bool, 默认: false): 是否延迟加载端点(在调用方法时才获取)
126+
- `ThrowOnEndpointNotFound` (bool, 默认: true): 如果找不到端点是否抛出异常
127+
128+
### WebSocketEndpointAttribute
129+
130+
用于指定方法的 WebSocket endpoint 目标。
131+
132+
```csharp
133+
[WebSocketEndpoint("controller.action")]
134+
Task<Result> MethodAsync();
135+
```
136+
137+
- `Target`: 目标端点路径(例如:"weatherforecast.get" 或 "controller.action")
138+
139+
### WebSocketClient
140+
141+
底层 WebSocket 客户端,用于连接到服务器并发送请求。
142+
143+
#### 构造函数
144+
145+
```csharp
146+
public WebSocketClient(string serverUri, string channel = "/ws")
147+
```
148+
149+
- `serverUri`: 服务器 URI(例如:"ws://localhost:5000/ws")
150+
- `channel`: WebSocket 通道(默认:"/ws")
151+
152+
#### 方法
153+
154+
##### ConnectAsync()
155+
156+
连接到服务器。
157+
158+
```csharp
159+
await client.ConnectAsync();
160+
```
161+
162+
##### SendRequestAsync<TRequest, TResponse>()
163+
164+
发送请求并等待响应。
165+
166+
```csharp
167+
var response = await client.SendRequestAsync<RequestType, ResponseType>(
168+
"Controller.Action",
169+
requestBody
170+
);
171+
```
172+
173+
##### DisconnectAsync()
174+
175+
断开服务器连接。
176+
177+
```csharp
178+
await client.DisconnectAsync();
179+
```
180+
181+
## 服务端要求
182+
183+
服务端需要提供以下 API 端点来支持客户端自动发现:
184+
185+
### GET /ws_server/api/endpoints
186+
187+
返回所有可用的 WebSocket endpoint 列表。
188+
189+
响应格式:
190+
191+
```json
192+
{
193+
"Success": true,
194+
"Data": [
195+
{
196+
"Controller": "WeatherForecast",
197+
"Action": "Get",
198+
"MethodPath": "weatherforecast.get",
199+
"Methods": ["GET"],
200+
"FullName": "WeatherForecast.Get",
201+
"Target": "weatherforecast.get"
202+
}
203+
],
204+
"Error": null
205+
}
206+
```
207+
208+
## 完整示例
209+
210+
```csharp
211+
using Cyaim.WebSocketServer.Client;
212+
using System;
213+
using System.Threading.Tasks;
214+
215+
class Program
216+
{
217+
static async Task Main(string[] args)
218+
{
219+
// 创建客户端工厂
220+
var factory = new WebSocketClientFactory("http://localhost:5000", "/ws");
221+
222+
try
223+
{
224+
// 创建客户端代理
225+
var weatherService = await factory.CreateClientAsync<IWeatherService>();
226+
227+
// 调用服务端方法
228+
var forecasts = await weatherService.GetForecastsAsync();
229+
230+
foreach (var forecast in forecasts)
231+
{
232+
Console.WriteLine($"{forecast.Date}: {forecast.TemperatureC}°C");
233+
}
234+
}
235+
catch (Exception ex)
236+
{
237+
Console.WriteLine($"Error: {ex.Message}");
238+
}
239+
finally
240+
{
241+
factory.Dispose();
242+
}
243+
}
244+
}
245+
246+
// 定义接口契约
247+
public interface IWeatherService
248+
{
249+
Task<WeatherForecast[]> GetForecastsAsync();
250+
}
251+
252+
public class WeatherForecast
253+
{
254+
public DateTime Date { get; set; }
255+
public int TemperatureC { get; set; }
256+
public string Summary { get; set; } = string.Empty;
257+
}
258+
```
259+
260+
## 使用场景
261+
262+
### 场景 1:只使用部分 endpoint
263+
264+
你只需要使用服务端的部分功能,可以只定义需要的接口方法:
265+
266+
```csharp
267+
// 服务端可能有 100 个 endpoint,但你只需要 2 个
268+
public interface IUserService
269+
{
270+
Task<User> GetUserAsync(int userId);
271+
Task<bool> UpdateUserAsync(User user);
272+
}
273+
274+
var factory = new WebSocketClientFactory("http://localhost:5000");
275+
var client = await factory.CreateClientAsync<IUserService>();
276+
```
277+
278+
### 场景 2:自定义契约接口
279+
280+
你可以完全自定义接口,使用 `[WebSocketEndpoint]` 特性指定映射:
281+
282+
```csharp
283+
public interface IMyCustomService
284+
{
285+
[WebSocketEndpoint("user.getbyid")]
286+
Task<User> GetUserByIdAsync(int id);
287+
288+
[WebSocketEndpoint("user.update")]
289+
Task<bool> UpdateUserAsync(User user);
290+
}
291+
```
292+
293+
### 场景 3:延迟加载 endpoint
294+
295+
如果不想在创建客户端时立即获取所有 endpoint,可以使用延迟加载:
296+
297+
```csharp
298+
var options = new WebSocketClientOptions
299+
{
300+
LazyLoadEndpoints = true // 在调用方法时才获取 endpoint
301+
};
302+
303+
var factory = new WebSocketClientFactory("http://localhost:5000", "/ws", options);
304+
var client = await factory.CreateClientAsync<IUserService>();
305+
306+
// 第一次调用时会自动获取并匹配 endpoint
307+
var user = await client.GetUserAsync(1);
308+
```
309+
310+
## 注意事项
311+
312+
1. **接口方法命名**:接口方法名会自动匹配服务端的 Action 名称(不区分大小写),如果不匹配可以使用 `[WebSocketEndpoint]` 特性
313+
2. **参数映射**:方法参数会自动映射到请求的 Body 中
314+
3. **返回类型**:方法必须返回 `Task``Task<T>`
315+
4. **连接管理**:客户端会自动管理 WebSocket 连接,无需手动连接
316+
5. **错误处理**:如果服务端返回错误(Status != 0),会抛出异常
317+
6. **部分匹配**:默认情况下,不会验证所有方法都有对应的 endpoint,只在使用时才会检查
318+
7. **自定义契约**:你可以完全自定义接口,只定义需要的方法,不需要一次性获取所有 endpoint
319+
320+
## 许可证
321+
322+
Copyright © Cyaim Studio
323+

0 commit comments

Comments
 (0)