资讯动态

用PowerShell和WinForms打造DeepSeek Token用量监控桌面面板

发布时间:2026/9/19 8:40:29 来源:尧图企业网站定制
前阵子一直在调 DeepSeek 的 API模型能力没得说但有个问题特别烦人每跑一轮脚本都要打开浏览器、登录开放平台、点进控制台才能看到 token 消耗了多少、余额还剩多少。次数一多我就受不了了。与其反复手动查不如直接做个桌面小面板一键刷新看用量。正好我手上一直是 Windows 环境PowerShell 和 WinForms 又是系统自带的东西不需要额外装 Python、Node 那一套所以我用 PowerShell WinForms 搓了一个 DeepSeek Token 用量桌面面板。做完之后每天打开电脑顺手点一下就能看到账户余额、今天花了多少 token、哪个模型烧钱最多爽很多。这篇文章不吹不黑就把整个实现过程拆开讲清楚从 API 数据从哪来、WinForms 界面怎么搭、PowerShell 请求接口怎么解析到开机自启、日志统计、乱码和鉴权踩坑全部按我实际操作的顺序记录下来。适合两类人参考一是想用 PowerShell 快速写一个正经 GUI 工具的朋友二是想高强度用 DeepSeek API 并做好预算管理的开发者。1. 为什么选 PowerShell WinForms 做桌面面板1.1 需求很具体我想一眼看到 Token 消耗先说清楚这个面板要解决什么问题。DeepSeek API 的计费核心是 token每次调用模型都会在响应的 usage 字段里返回 prompt_tokens、completion_tokens、total_tokens。但这些消耗是分散在一次次请求里的如果只是偶尔调几次手动看后台也没啥可一旦开始批量测试 prompt、跑数据处理任务或者把 API 接进自动化流程token 消耗就成了需要持续盯住的指标。我最初的诉求其实很简单打开一个窗口就能看到当前账户还有多少余额。能看到累计消耗的 token 总数最好能按天分组。能自动刷新不用每次手动点刷新按钮。要做到这三点最安分的方式是写代码调 DeepSeek 官方 API。但我不想为了一个十几行的统计工具去装 Python 环境也不想把 Electron 那套动辄几百 MB 的依赖搬过来。于是就把目光放到了 Windows 自带能力上。1.2 为什么没选 Python、Electron 而是 PowerShell坦率地说我以前也纠结过这类工具该用什么技术栈。Python Tkinter 写起来很简单可前提是目标机器上有 Python 3.10还要处理 pip 依赖Electron 界面漂亮但为了显示几个数字就拉起 Chromium实在是杀鸡用牛刀。PowerShell WinForms 的好处非常实际系统自带只要是 Windows 10/11 都有 PowerShell 5.1WinForms 程序集也随 .NET Framework 预装。脚本即源码复制一个 .ps1 文件就能运行改一行配置立刻生效。对 REST API 的支持很完善Invoke-RestMethod直接读 JSON解析成本低。后续接计划任务、开机自启、邮件通知都很顺。当然缺点也明显WinForms 的界面风格比较旧功能复杂时不好维护跨平台更是无从谈起。但做内部工具、个人小面板这些缺点完全能接受。我的判断标准很简单能在 10 分钟内跑通、能稳定运行、后续好改就足够了。1.3 面板功能规划动工前我列了一个最小功能清单尽量克制不做多余的东西区域功能说明顶部余额显示显示 API Key 对应的总余额、赠送余额、充值余额中部Token 用量汇总显示累计 prompt tokens、completion tokens、总数中部表格模型维度消耗按模型分组统计最近一段时间的消耗底部状态栏最近刷新时间、请求状态、错误信息全局刷新按钮点击立即重新请求接口并刷新界面全局自动刷新用 Timer 每 60 秒自动查询一次这个清单看起来简单但实际落地时有不少细节比如数据从哪来、日志怎么记录、界面怎么布局下面一章开始逐步拆解。2. 准备工作API Key、接口和数据结构2.1 先拿到 DeepSeek 的 API Key在动手写代码之前必须先把 API Key 准备好。去 DeepSeek 开放平台注册账号、完成实名认证然后在 API Keys 页面创建密钥。密钥通常以sk-开头创建时机要留意很多平台只在创建时完整显示一次之后就没法再看到明文了。拿到 Key 之后务必养成一个习惯不要直接写死在脚本里更不要截图发到群里。我会在后面的“配置抽离”小节讲具体做法这一阶段先记住一个原则——API Key 就是钱泄露了等于别人能用你的配额花钱你买单。顺带一提平台通常会送一点体验额度或者有免费 token 活动新账号可以先拿这点免费额度做测试确认 API 调用链路没问题再考虑充值。我就是先用赠送额度把接口调通了避免一开始就产生费用。2.2 用量数据不是“一个接口全给”得自己攒很多朋友刚上手时容易有一个误区以为 DeepSeek 会提供一个“历史 token 用量明细”接口直接调用就能把所有消耗拉出来。根据我的实测官方目前主要提供的是余额查询接口以及每次模型响应中的 usage 字段。也就是说我能直接拿到两类数据第一类是账户余额。调用鉴权后的余额接口返回的信息包含total_balance、granted_balance、topped_up_balance等字段。简单说就是总余额、赠送余额、充值余额。第二类是单次请求的 token 消耗。每次调用对话补全接口返回的 JSON 里都会带一段usage结构大致如下{ prompt_tokens: 25, completion_tokens: 118, total_tokens: 143, prompt_tokens_details: { cached_tokens: 0 } }如果做的是流式请求usage 通常会出现在最后一个 chunk 里。如果中间做了重试重试的每一次都可能产生 token。这些细节决定了统计是否准确。因为没有官方历史用量接口我采取了最直接的方案自己写日志。我在每次成功调用模型后把响应里的 usage 追加到一个本地 JSON 文件里。桌面面板启动时读取这个文件做汇总和展示。这样既不需要后台数据库也能保证数据统计的连续性。2.3 我设计的日志结构我把日志文件命名为token_usage_log.json放在脚本同目录下结构设计成{ records: [ { timestamp: 2025-04-07T10:23:45, model: deepseek-chat, prompt_tokens: 320, completion_tokens: 480, total_tokens: 800 } ] }每个字段的用途timestamp记录请求发生的时间用于按天、按小时做维度统计。model记录模型名用于分析不同模型的消耗量。prompt_tokens输入 token 数对应计费中的输入部分。completion_tokens输出 token 数对应计费中的输出部分。total_tokens两者之和方便一眼看出单次消耗量。我特意没有在日志里记录请求体和完整响应只记录 usage 相关字段一是控制日志文件体积二是降低敏感信息泄露风险。如果你用了更长上下文的模型单次请求可能上万 token日志快速增长这时可以考虑按天滚动归档后面会讲到。3. 界面先行WinForms 控件的选择与布局3.1 加载 WinForms 的前提PowerShell 里用 WinForms第一步是加载程序集。在 Windows PowerShell 5.1 和 PowerShell 7 中都可以这样写Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing其中System.Windows.Forms是窗体控件相关System.Drawing负责颜色、字体、位置等图形相关操作。然后创建一个主窗体对象设置标题、尺寸、位置$form New-Object System.Windows.Forms.Form $form.Text DeepSeek Token 用量面板 $form.Size New-Object System.Drawing.Size(680, 520) $form.StartPosition CenterScreen $form.MaximizeBox $false这些属性和 C# WinForms 项目几乎一一对应只要稍微懂一点 WinForms 的基础概念就能在 PowerShell 里照葫芦画瓢。3.2 画窗体哪些控件够用面板主体我用了这几类控件System.Windows.Forms.Label显示余额文字、token 汇总数据。System.Windows.Forms.ListView按模型维度显示消耗明细性能比 DataGridView 在少量数据时更轻。System.Windows.Forms.Button手动刷新按钮。System.Windows.Forms.Timer定时自动刷新。System.Windows.Forms.StatusStrip或一个额外的 Label显示状态信息。创建 Label 的基本套路如下$lblBalance New-Object System.Windows.Forms.Label $lblBalance.Location New-Object System.Drawing.Point(20, 20) $lblBalance.Size New-Object System.Drawing.Size(200, 30) $lblBalance.Text 余额-- $form.Controls.Add($lblBalance)布局时我会算好坐标顶部一排放余额标签下方放大字号的总消耗标签再往下放 ListView底部放状态栏和按钮。坐标制在代码里看起来有点原始但胜在直观、可控。3.3 事件绑定与 UI 防卡死WinForms 是事件驱动的按钮点击、窗体加载都要绑定事件。在 PowerShell 里绑定事件比 C# 稍微绕一点核心是用Add_Click这类事件方法$btnRefresh.Add_Click({ Write-Host clicked })踩坑提醒如果事件处理代码里包含耗时操作比如网络请求WinForms 界面会“卡死”窗口拖不动、按钮点不了。这是因为操作都在 UI 线程上执行。解决办法有两个方向第一简单场景用System.Windows.Forms.Timer定时触发并在触发时先设置状态栏文字“刷新中…”请求结束后恢复。第二如果必须是用户点击后立刻执行的耗时请求至少要把网络请求放在后台线程或者Runspace中运行完成后把数据传回主线程更新控件。面板项目规模小我用了 Timer 方式同时把请求逻辑封装成一个函数避免在事件代码里写太多业务逻辑。4. 用 PowerShell 调 DeepSeek API核心代码拆解4.1 发请求前的 TLS 与请求头Windows PowerShell 5.1 默认使用的 TLS 版本可能比较低而很多云服务商要求至少 TLS 1.2否则连接会被拒绝。遇到“请求被服务器关闭”这类问题时多数情况是因为 PowerShell 还在用老旧的 TLS 协议。保险起见我在脚本开头统一设置[Net.ServicePointManager]::SecurityProtocol [Net.SecurityProtocolType]::Tls12然后是请求头。调用 DeepSeek API 需要在 HTTP 头里带上鉴权信息$headers { Authorization Bearer $apiKey Content-Type application/json }这里$apiKey是从配置文件中读出来的字符串格式是sk-...。保持Bearer前缀和 key 之间的空格拼错是最常见的 401 原因。4.2 余额查询接口的调用与解析余额查询用 GET 请求即可我先封装一个函数function Get-DeepSeekBalance { param($ApiKey) $headers { Authorization Bearer $ApiKey } $response Invoke-RestMethod -Uri https://api.deepseek.com/user/balance -Method Get -Headers $headers return $response }实际返回的 JSON 结构类似{ is_available: true, balance_infos: [ { currency: CNY, total_balance: 110.00, granted_balance: 10.00, topped_up_balance: 100.00 } ] }is_available标识账户当前是否可用balance_infos是一个数组里面可能有多条币种记录。我取balance_infos[0]展示即可$balanceInfo $response.balance_infos[0] $total $balanceInfo.total_balance $granted $balanceInfo.granted_balance $toppedUp $balanceInfo.topped_up_balance注意接口返回的余额字段是字符串不是数字如果你要拿来做阈值判断或告警要用[double]::Parse()做类型转换否则字符串比较会出现“100 20”这种错误。4.3 把 JSON 变成界面上的数据从日志文件读 token 用量数据后我要做两件事汇总总数、按模型分组。汇总总数的方法很简单$totalPrompt ($log.records | Measure-Object -Property prompt_tokens -Sum).Sum $totalCompletion ($log.records | Measure-Object -Property completion_tokens -Sum).Sum $totalTokens ($log.records | Measure-Object -Property total_tokens -Sum).Sum按模型分组则需要用管道配合 Group-Object$grouped $log.records | Group-Object model | ForEach-Object { [PSCustomObject]{ Model $_.Name RequestCount $_.Count PromptTokens ($_.Group | Measure-Object -Property prompt_tokens -Sum).Sum CompletionTokens ($_.Group | Measure-Object -Property completion_tokens -Sum).Sum TotalTokens ($_.Group | Measure-Object -Property total_tokens -Sum).Sum } }拿到这些数据后填充 UI 控件就非常直接了。Label 的 Text 属性一改ListView 用 Items.AddRange 逐行添加就好。4.4 串起整个刷新流程一个完整的刷新流程大概是这样的function Update-Panel { $statusLabel.Text 正在刷新... $form.Refresh() try { $balance Get-DeepSeekBalance -ApiKey $config.apiKey $lblBalance.Text 总余额$($balance.balance_infos[0].total_balance) 元 Update-TokenSummary $statusLabel.Text 刷新成功 (Get-Date -Format HH:mm:ss) } catch { $statusLabel.Text 刷新失败 $_.Exception.Message } }这里我特意在刷新前后调用$form.Refresh()否则界面上的文字不会及时变化看起来像没点中按钮。这个小细节第一次写很容易忽略。5. 让面板真正“可用”统计、自启、配置化5.1 日志汇总Token 用量的日维度统计面板只有“总消耗”还不够我希望它能告诉我今天消耗了多少 token。拆开来看就是按照timestamp的日期部分进行过滤。如果日志结构是数组records可以用Where-Object过滤当天记录$today (Get-Date).ToString(yyyy-MM-dd) $todayRecords $log.records | Where-Object { $_.timestamp.StartsWith($today) }然后对$todayRecords做求和逻辑和前面类似。因为我是把日志文件放在本地数据量不大这种遍历在启动时完全无压力。建议给记录按天归档。你可以写一个简单逻辑读取日志时如果发现记录数超过 5000 条就按月份拆分到history/目录当前目录只保留最近一个月的数据。避免日志文件无限增大导致后续读取变慢。5.2 自动刷新与定时任务我用的是 WinForms 自带的System.Windows.Forms.Timer注意它和System.Timers.Timer不一样前者需要消息循环才能触发放在 WinForms 里正合适。创建定时器$timer New-Object System.Windows.Forms.Timer $timer.Interval 60000 $timer.Add_Tick({ Update-Panel }) $timer.Start()这样每隔 60 秒自动调用一次刷新流程。但要注意如果每次刷新都请求余额接口频率太高可能触发平台限流策略因此一般建议把 Interval 设置成 300000 毫秒5 分钟或者刷新后记录时间距离上次不足 30 秒就跳过。5.3 开机自启的两种稳妥姿势做完面板后我希望它开机自动运行遂把脚本做成了带窗体的自启小工具。Windows 下的常见方案有两种第一种是启动文件夹方式。把脚本的快捷方式放到shell:startup文件夹里即C:\Users\你的用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup。优点是简单直观缺点是如果脚本路径带中文和空格快捷方式目标必须用引号包好否则会找不到文件。第二种是注册表 Run 键。在HKCU:\Software\Microsoft\Windows\CurrentVersion\Run下创建一个字符串值数据填powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File D:\script\panel.ps1。注意两点一是-WindowStyle Hidden可以避免启动时弹出黑框二是-ExecutionPolicy Bypass或者先通过Set-ExecutionPolicy -Scope CurrentUser RemoteSigned调整策略否则可能被默认执行策略拦下。注册表方式我实际用了很久稳定可靠。但如果你用的是 PowerShell 7注意把启动路径从powershell.exe改成pwsh.exe否则会用 Windows PowerShell 5.1 去跑可能缺模块。5.4 配置抽离API Key 不写死在代码里写脚本最忌讳的就是把密钥硬编码。我单独建了一个config.json{ apiKey: sk-你申请到的Key, logFile: token_usage_log.json, refreshIntervalSec: 60 }脚本读取配置用Get-Content -Raw | ConvertFrom-Json即可$config Get-Content -Raw -Path config.json | ConvertFrom-Json $apiKey $config.apiKey把 API Key 放到独立配置文件里好处很多。备份脚本时不用注意脱敏也能更方便地在多台机器之间复制配置。但请格外注意config.json不能提交到公开 Git 仓库否则等于公开自己的密钥。我一般会加一条.gitignore规则或者干脆不把 config.json 纳入版本管理。6. 我踩过的坑和排查思路6.1 PowerShell 5.1 的编码与命令兼容问题前面提到热搜里有“deepseek配置windows powershell乱码”这确实是很常见的问题。Windows PowerShell 5.1 的默认编码不是 UTF-8而是中文系统下的 GBK。当接口返回 UTF-8 编码的中文字符时控制台直接显示乱码。真正的撞墙经历是这样的调用余额接口后把返回的 JSON 直接打印到控制台发现中文全是“锟斤拷”第一反应还以为是 API 数据坏了后来确认是编码问题。解决方法是在脚本开头设置控制台编码[Console]::OutputEncoding [System.Text.Encoding]::UTF8这样控制台输出就能正确显示 UTF-8 内容。在 WinForms 界面里Label 的文本一般不会受控制台编码影响但如果日志文件写入编码不一致界面读取时也可能出现乱码所以读写日志文件时统一使用 UTF-8 编码。另外再说一下命令链运算符。经常有人喜欢在 PowerShell 里用cmd1 cmd2但 PowerShell 5.1 并不支持运算符这是 PowerShell 7 才引入的特性。如果网上教程写的代码用了而你的环境是 5.1执行时会直接报语法错误。替代写法是用分号;Update-Panel; Write-Host done或者在需要判断前一个命令是否成功时用if ($LASTEXITCODE -eq 0)。6.2 请求失败、Token 失效到底要看哪些字段接口调不通是最让人头疼的。我整理了几个常见错误及排查思路做成一个速查表现象常见原因处理方式HTTP 401Invalid API KeyAPI Key 错误、过期、前缀缺失检查 config.json 中的 key 是否完整确认以 sk- 开头HTTP 429Rate Limit请求频率过高或余额不足降低刷新频率检查账户余额HTTP 403Forbidden账户被限制或地区问题登录开放平台查看账户状态连接被关闭TLS 版本过低设置[Net.ServicePointManager]::SecurityProtocol Tls12响应中文乱码控制台编码问题设置[Console]::OutputEncoding UTF8关于 token 失效如果你的业务系统里有 JWT 登录那需要注意 token 类型和续签机制。但对于面板本身DeepSeek 的 API Key 属于长期密钥一般不会自动过期遇到 401 首先检查是否复制错了字符、是否多了空格。真正的重点是不要把 Key 拼到 URL 里这样既容易泄露也容易被系统日志记录。6.3 WinForms 界面卡死与进度提示我在“事件绑定与 UI 防卡死”里简单提过这个问题但这里必须再放大一次。事件回调里如果执行Invoke-RestMethod整个窗口会变成“未响应”状态直到请求返回。这个在网络缓慢或接口超时时特别明显用户疯狂点按钮体验极差。我的解决办法是点击刷新时先把按钮禁用$btnRefresh.Enabled $false。界面上状态栏改成“正在刷新...”。刷新结束后在finally中恢复按钮可用。连接超时设置不能省Invoke-RestMethod的-TimeoutSec参数设为 30避免无限等待。$response Invoke-RestMethod -Uri $url -Headers $headers -Method Get -TimeoutSec 30如果你希望请求完全不阻塞界面更正式的做法是用 PowerShell Runspace 或线程但考虑到面板的代码量我觉得采用“禁用按钮 超时 状态提示”这套组合拳已经足够优雅。6.4 安全习惯别把 API Key 泄露到日志最后一条经验也是我特别想强调的。PowerShell 的-Verbose、-Debug参数或者脚本中未加处理的$Error有时会把完整的 URL 或请求头打印出来。如果请求头里带着Authorization: Bearer sk-xxx而你又把输出重定向到日志文件那么密钥就等于裸奔了。我处理安全的几个习惯日志里绝不记录请求头。错误信息用$_.Exception.Message而不是把整个$_对象序列化。config.json 不参与 git 版本管理。面板界面上对 API Key 只显示后四位例如sk-****1234方便确认用的是哪把钥匙又不泄露完整内容。另外如果以后要对接其他服务比如把 token 用量超阈值提醒发送到企业微信或钉钉机器人也要注意机器人的 Webhook 地址本身相当于一个密钥别随手贴到博文或公开仓库里。最后分享一点我自己的使用体会这个小工具从动手写到稳定用其实只花了一个下午。但给我带来的改变是实打实的现在跑批量任务时我不用再去猜“这次实验花了多少钱”面板上数字一目了然续费决策也果断多了。我用得最顺手的是把日志按模型分组这个功能可以清楚看到不同任务的成本占比之后调整 prompt 策略就很有针对性。如果你也想做一个类似的面板不用照搬我的代码。把 API 请求、日志统计、WinForms 展示这三块拆开很容易根据自己的需求扩展。比如加上按时间范围筛选或者把某一天的消费折线图用原生控件画出来甚至做一个阈值告警——这些都是在现有框架上添砖加瓦的事。如果你已经在自己项目里实现了更好的 token 统计方式欢迎告诉我。工具虽小但这种“今天不把重复劳动干掉明天就会继续重复”的想法我觉得是做开发最值得保持的一点劲头。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价