本文档基于自写的 WebApi 调用DeepSeek API项目编写,讲解 WebApi 通讯的原理、及每个方法的实现细节。
本文只用于对该项目的技术分析。项目为自用项目,源码不对外开源。
目录
1. 项目概述
1.1 项目简介
本项目是一个基于 WinForms 的上位机应用程序,演示如何通过 WebApi 调用 DeepSeek V3 大语言模型。
1.2 技术栈
| 技术 | 版本 | 说明 |
|---|---|---|
| .NET Framework | 4.8 | 运行时环境 |
| C# | - | 开发语言 |
| WinForms | - | UI框架 |
| HttpClient | - | .NET内置HTTP客户端 |
| Newtonsoft.Json | 13.0.4 | JSON序列化库 |
1.3 项目特点
- 分层架构:HTTP层、业务层、UI层分离
- 异步支持:提供同步和异步两种调用方式
- 多轮对话:支持上下文记忆的对话功能
- Token统计:实时显示输入/输出/总计Token数
2. 项目结构
2.1 文件结构
WebApi_DeepSeek/
├── WebApiHelper.cs # HTTP通信工具类(参考速控云)
├── DeepSeekApi.cs # DeepSeek业务API类(参考DeviceApi)
├── Form1.cs # 主窗体逻辑代码(参考FrmMain)
├── Form1.Designer.cs # 主窗体设计器代码
├── Program.cs # 程序入口
├── App.config # 应用配置文件
└── packages.config # NuGet包依赖
2.2 架构对比
┌─────────────────────────────────────────────────────────────────┐
│ 速控云项目结构 │
├─────────────────────────────────────────────────────────────────┤
│ WebApiHelper.cs → HTTP通信封装 │
│ DeviceApi.cs → 业务API封装(登录、项目、变量读写) │
│ FrmMain.cs → 主窗体逻辑 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ DeepSeek项目结构 │
├─────────────────────────────────────────────────────────────────┤
│ WebApiHelper.cs → HTTP通信封装(相同模式) │
│ DeepSeekApi.cs → 业务API封装(聊天、模型列表) │
│ Form1.cs → 主窗体逻辑(相同模式) │
└─────────────────────────────────────────────────────────────────┘
2.3 调用流程图
┌─────────────┐
│ 用户界面 │ Form1.cs
│ Form1 │
└──────┬──────┘
│
▼
┌─────────────┐
│ DeepSeekApi │ 业务层
│ 封装业务 │
└──────┬──────┘
│
▼
┌─────────────┐
│WebApiHelper │ HTTP层
│ 封装请求 │
└──────┬──────┘
│
▼
┌─────────────┐
│ DeepSeek │ 服务端
│ API │
└─────────────┘
3. 核心类详解
3.1 WebApiHelper - HTTP通信工具类
文件位置 : WebApiHelper.cs
作用: 封装所有HTTP请求操作,提供统一的HTTP通信接口。
类结构
csharp
public class WebApiHelper
{
// 静态HttpClient实例
private static HttpClient _httpClient = new HttpClient();
// 自定义响应类
public class RestResponse { ... }
// 方法列表
public static RestResponse HttpPost<T>(string url, T body)
public static Task<RestResponse> HttpPostAsync<T>(string url, T body)
public static RestResponse HttpPostBearerToken<T>(string url, string token, T body)
public static Task<RestResponse> HttpPostBearerTokenAsync<T>(string url, string token, T body)
public static RestResponse HttpPost(string url, Dictionary<string, string> para)
public static RestResponse HttpGet(string url)
public static Task<RestResponse> HttpGetAsync(string url)
public static RestResponse HttpGetBearerToken(string url, string token)
}
方法详解
HttpPostBearerToken - 带Token的POST请求
csharp
/// <summary>
/// POST请求 - 携带Bearer Token认证
/// </summary>
/// <typeparam name="T">请求体类型</typeparam>
/// <param name="url">API地址</param>
/// <param name="token">认证令牌</param>
/// <param name="body">请求体对象</param>
/// <returns>响应对象</returns>
public static RestResponse HttpPostBearerToken<T>(string url, string token, T body)
{
try
{
// 1. 将对象序列化为JSON
string json = JsonConvert.SerializeObject(body);
// 2. 创建StringContent,设置UTF-8编码和JSON格式
var content = new StringContent(json, Encoding.UTF8, "application/json");
// 3. 创建HTTP请求
using (var request = new HttpRequestMessage(HttpMethod.Post, url))
{
request.Content = content;
// 4. 添加Bearer Token认证头
request.Headers.Add("Authorization", $"Bearer {token}");
// 5. 发送请求
var response = _httpClient.SendAsync(request).Result;
var responseBody = response.Content.ReadAsStringAsync().Result;
// 6. 返回自定义响应对象
return new RestResponse
{
Content = responseBody,
StatusCode = (int)response.StatusCode,
ErrorMessage = !response.IsSuccessStatusCode ? response.ReasonPhrase : null
};
}
}
catch (Exception ex)
{
return new RestResponse
{
Content = null,
StatusCode = 500,
ErrorMessage = ex.Message
};
}
}
RestResponse 响应类
csharp
/// <summary>
/// 自定义HTTP响应类(兼容RestSharp的IRestResponse)
/// </summary>
public class RestResponse
{
/// <summary>响应内容(JSON字符串)</summary>
public string Content { get; set; }
/// <summary>HTTP状态码(200/401/500等)</summary>
public int StatusCode { get; set; }
/// <summary>是否成功(2xx状态码)</summary>
public bool IsSuccessful => StatusCode >= 200 && StatusCode < 300;
/// <summary>错误消息</summary>
public string ErrorMessage { get; set; }
}
3.2 OperateResult - 操作结果封装类
作用: 统一封装API调用的结果,提供成功/失败状态和消息。
csharp
/// <summary>
/// 操作结果封装类
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
public class OperateResult<T>
{
/// <summary>是否成功</summary>
public bool IsSuccess { get; set; }
/// <summary>消息</summary>
public string Message { get; set; }
/// <summary>数据内容</summary>
public T Content { get; set; }
/// <summary>HTTP状态码</summary>
public int StatusCode { get; set; }
/// <summary>
/// 创建成功结果
/// </summary>
public static OperateResult<T> CreateSuccessResult(T content)
{
return new OperateResult<T>
{
IsSuccess = true,
Message = "操作成功",
Content = content,
StatusCode = 200
};
}
/// <summary>
/// 创建失败结果
/// </summary>
public static OperateResult<T> CreateFailResult(string message, int statusCode = 400)
{
return new OperateResult<T>
{
IsSuccess = false,
Message = message,
Content = default(T),
StatusCode = statusCode
};
}
/// <summary>
/// 从HTTP响应创建结果
/// </summary>
public static OperateResult<T> FromResponse(WebApiHelper.RestResponse response)
{
if (response.IsSuccessful)
{
try
{
T content = JsonConvert.DeserializeObject<T>(response.Content);
return CreateSuccessResult(content);
}
catch (Exception ex)
{
return CreateFailResult($"JSON解析错误: {ex.Message}");
}
}
else
{
return CreateFailResult($"HTTP错误: {response.StatusCode} - {response.ErrorMessage}", response.StatusCode);
}
}
}
3.3 DeepSeekApi - 业务API类
文件位置 : DeepSeekApi.cs
作用: 封装DeepSeek API的所有业务操作。
类结构
csharp
public class DeepSeekApi
{
// API端点
private string ChatUrl = string.Empty; // 聊天API
private string ModelsUrl = string.Empty; // 模型列表API
private string token = string.Empty; // 认证令牌
// 构造函数
public DeepSeekApi(string baseUrl)
// 设置Token
public void SetToken(string token)
// 同步方法
public OperateResult<ChatResponse> SendChat(...)
public OperateResult<ChatResponse> SendChat(List<Message> messages, ...)
public OperateResult<ModelsResponse> GetModels()
// 异步方法
public Task<OperateResult<ChatResponse>> SendChatAsync(...)
public Task<OperateResult<ChatResponse>> SendChatAsync(List<Message> messages, ...)
}
初始化示例
csharp
// 创建API客户端
DeepSeekApi api = new DeepSeekApi("https://xxx.cn-xxx.xxx.com");
// 设置认证Token
api.SetToken("your-api-token-here");
// 调用API
var result = api.SendChat("你好,请介绍一下自己");
if (result.IsSuccess)
{
string reply = result.Content.choices[0].message.content;
Console.WriteLine(reply);
}
3.4 Form1 - 主窗体
文件位置 : Form1.cs
作用: 用户界面,处理用户交互和显示结果。
关键方法
csharp
/// <summary>
/// 发送按钮点击事件
/// </summary>
private async void btn_Send_Click(object sender, EventArgs e)
{
string question = this.txt_Question.Text.Trim();
// 验证输入
if (string.IsNullOrEmpty(question))
{
MessageBox.Show("请输入问题", "提示");
return;
}
// 禁用发送按钮
this.btn_Send.Enabled = false;
this.txt_Question.Enabled = false;
try
{
// 显示用户问题
AppendMessage("用户", question, Color.Blue);
// 添加用户消息到历史
conversationHistory.Add(new DeepSeekApi.Message
{
role = "user",
content = question
});
// 第一次对话添加系统消息
if (conversationHistory.Count == 1)
{
conversationHistory.Insert(0, new DeepSeekApi.Message
{
role = "system",
content = "你是一个人工智能助手,请用简洁明了的语言回答问题。"
});
}
// 调用API - 异步方法
var result = await deepSeekApi.SendChatAsync(conversationHistory);
// 处理响应
if (result.IsSuccess)
{
var aiMessage = result.Content.choices[0].message;
conversationHistory.Add(aiMessage);
AppendMessage("DeepSeek", aiMessage.content, Color.Green);
// 更新Token统计
this.lbl_token.Text = $"{result.Content.usage.total_tokens} tokens";
this.lbl_prompt.Text = $"输入: {result.Content.usage.prompt_tokens}";
this.lbl_completion.Text = $"输出: {result.Content.usage.completion_tokens}";
}
else
{
AppendMessage("错误", result.Message, Color.Red);
}
}
catch (Exception ex)
{
AppendMessage("异常", $"发生错误: {ex.Message}", Color.Red);
}
finally
{
// 恢复控件状态
this.btn_Send.Enabled = true;
this.txt_Question.Enabled = true;
this.txt_Question.Clear();
this.txt_Question.Focus();
}
}
4. HTTP通信原理
4.1 完整通信流程
┌─────────────┐ ┌─────────────┐
│ 客户端 │ │ 服务器 │
│ Client │ │ Server │
└──────┬──────┘ └──────┬──────┘
│ │
│ ① 构建HTTP请求 │
│ - 请求行: POST /api/v3/chat/completions │
│ - 请求头: Authorization: Bearer xxx │
│ - 请求体: {"model":"...","messages":[...]} │
│ │
├────────────────────── ② 发送请求 ──────────────────▶│
│ │
│ ③ 处理请求 │
│ - 验证Token │
│ - 调用AI模型 │
│ - 生成回复 │
│ │
│◀──────────────────── ④ 返回响应 ───────────────────┤
│ HTTP/1.1 200 OK │
│ {"choices":[{"message":{...}}],"usage":{...}} │
│ │
│ ⑤ 解析响应 │
│ - 检查状态码 │
│ - JSON反序列化 │
│ - 提取回复内容 │
│ │
▼ ▼
5. 代码使用示例
5.1 简单对话示例
csharp
using WebApi_DeepSeek;
// 1. 创建API客户端
var api = new DeepSeekApi("https://xxx.cn-xxx.xxx.com");
api.SetToken("your-api-token");
// 2. 发送问题(同步方式)
var result = api.SendChat("什么是xxxxx?");
// 3. 检查结果
if (result.IsSuccess)
{
string reply = result.Content.choices[0].message.content;
int tokens = result.Content.usage.total_tokens;
Console.WriteLine($"AI回复: {reply}");
Console.WriteLine($"消耗Token: {tokens}");
}
else
{
Console.WriteLine($"请求失败: {result.Message}");
}
5.2 多轮对话示例
csharp
// 1. 创建对话历史
var messages = new List<DeepSeekApi.Message>
{
new DeepSeekApi.Message { role = "system", content = "你是一个专业的编程助手" }
};
// 2. 第一轮对话
messages.Add(new DeepSeekApi.Message { role = "user", content = "什么是xxx?" });
var result1 = await api.SendChatAsync(messages);
if (result1.IsSuccess)
{
var reply = result1.Content.choices[0].message;
messages.Add(reply); // 添加AI回复到历史
Console.WriteLine($"AI: {reply.content}");
}
// 3. 第二轮对话(带上下文)
messages.Add(new DeepSeekApi.Message { role = "user", content = "它和xxx有什么区别?" });
var result2 = await api.SendChatAsync(messages);
if (result2.IsSuccess)
{
var reply = result2.Content.choices[0].message;
Console.WriteLine($"AI: {reply.content}");
}
5.3 异步调用示例(WinForms)
csharp
private async void btnSend_Click(object sender, EventArgs e)
{
btnSend.Enabled = false;
try
{
// 异步调用,不阻塞UI
var result = await deepSeekApi.SendChatAsync(txtQuestion.Text);
if (result.IsSuccess)
{
txtResult.Text = result.Content.choices[0].message.content;
}
else
{
MessageBox.Show($"错误: {result.Message}");
}
}
finally
{
btnSend.Enabled = true;
}
}
5.4 获取模型列表
csharp
// 获取可用模型
var result = api.GetModels();
if (result.IsSuccess)
{
foreach (var model in result.Content.data)
{
Console.WriteLine($"模型ID: {model.id}");
Console.WriteLine($"所有者: {model.owned_by}");
}
}
6. 数据模型
6.1 请求数据模型
ChatRequest - 聊天请求
csharp
public class ChatRequest
{
[JsonProperty("model")]
public string model { get; set; } // 模型名称
[JsonProperty("messages")]
public List<Message> messages { get; set; } // 消息列表
[JsonProperty("temperature", NullValueHandling = NullValueHandling.Ignore)]
public double? temperature { get; set; } // 温度参数(0-2)
[JsonProperty("max_tokens", NullValueHandling = NullValueHandling.Ignore)]
public int? max_tokens { get; set; } // 最大token数
[JsonProperty("top_p", NullValueHandling = NullValueHandling.Ignore)]
public double? top_p { get; set; } // 核采样参数
[JsonProperty("stream", NullValueHandling = NullValueHandling.Ignore)]
public bool? stream { get; set; } // 是否流式输出
}
Message - 消息
csharp
public class Message
{
[JsonProperty("role")]
public string role { get; set; } // 角色: system/user/assistant
[JsonProperty("content")]
public string content { get; set; } // 消息内容
}
6.2 响应数据模型
ChatResponse - 聊天响应
csharp
public class ChatResponse
{
[JsonProperty("id")]
public string id { get; set; } // 响应ID
[JsonProperty("object")]
public string @object { get; set; } // 对象类型
[JsonProperty("created")]
public long created { get; set; } // 创建时间戳
[JsonProperty("model")]
public string model { get; set; } // 使用的模型
[JsonProperty("choices")]
public List<Choice> choices { get; set; } // 选择列表
[JsonProperty("usage")]
public Usage usage { get; set; } // Token使用情况
[JsonProperty("error")]
public ErrorInfo error { get; set; } // 错误信息
}
Choice - 选择项
csharp
public class Choice
{
[JsonProperty("index")]
public int index { get; set; } // 索引
[JsonProperty("message")]
public Message message { get; set; } // AI回复消息
[JsonProperty("finish_reason")]
public string finish_reason { get; set; } // 结束原因
}
Usage - Token使用情况
csharp
public class Usage
{
[JsonProperty("prompt_tokens")]
public int prompt_tokens { get; set; } // 输入token数
[JsonProperty("completion_tokens")]
public int completion_tokens { get; set; } // 输出token数
[JsonProperty("total_tokens")]
public int total_tokens { get; set; } // 总token数
}
7. 常见问题
7.1 认证失败 (401)
错误现象: HTTP错误: 401 - Unauthorized
原因:
- Token无效或过期
- Token格式不正确
解决方案:
csharp
// 确保Token格式正确
api.SetToken("your-actual-token-here");
// 检查Token是否有效
7.2 请求超时
错误现象: 请求超时异常
解决方案:
csharp
// 在WebApiHelper中已设置60秒超时
private static HttpClient _httpClient = new HttpClient()
{
Timeout = TimeSpan.FromSeconds(60)
};
7.3 JSON解析错误
错误现象: JSON解析错误: ...
原因:
- 返回的JSON格式不正确
- 模型定义与响应不匹配
解决方案:
csharp
// 先查看原始响应
Console.WriteLine(response.Content);
// 检查模型属性名是否匹配(区分大小写)
[JsonProperty("model")] // 必须与API返回的字段名一致
public string model { get; set; }
7.4 异步调用死锁
错误现象: UI界面卡死
原因 : 在WinForms中使用 .Result 或 .Wait() 可能导致死锁
解决方案:
csharp
// ✗ 错误写法(可能导致死锁)
var result = api.SendChatAsync(question).Result;
// ✓ 正确写法
private async void btnSend_Click(object sender, EventArgs e)
{
var result = await api.SendChatAsync(question);
}
附录 A: 配置文件
App.config
xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<appSettings>
<!-- DeepSeek API 配置 -->
<add key="DeepSeekBaseUrl" value="https://xxx.cn-xxx.xxx.com"/>
<add key="DeepSeekToken" value="your-token-here"/>
<add key="DeepSeekModel" value="deepseek-v3-250324"/>
<!-- 请求超时时间(毫秒) -->
<add key="RequestTimeout" value="60000"/>
</appSettings>
</configuration>
附录 B: 快速参考
HTTP状态码
| 状态码 | 含义 | 说明 |
|---|---|---|
| 200 | OK | 请求成功 |
| 400 | Bad Request | 请求参数错误 |
| 401 | Unauthorized | 认证失败 |
| 429 | Too Many Requests | 请求过于频繁 |
| 500 | Internal Server Error | 服务器错误 |
WebApiHelper 方法速查
| 方法 | 说明 | 是否需要Token |
|---|---|---|
HttpPost<T>() |
发送JSON | 否 |
HttpPostAsync<T>() |
异步发送JSON | 否 |
HttpPostBearerToken<T>() |
带Token发送JSON | 是 |
HttpPostBearerTokenAsync<T>() |
异步带Token发送JSON | 是 |
HttpPost() |
发送表单 | 否 |
HttpGet() |
GET请求 | 否 |
HttpGetAsync() |
异步GET请求 | 否 |
HttpGetBearerToken() |
带Token的GET请求 | 是 |