当前位置: 首页 > news >正文

为SenseVoice-Small模型开发Web管理界面:Flask快速入门

为SenseVoice-Small模型开发Web管理界面:Flask快速入门

你是不是已经部署好了SenseVoice-Small语音识别模型,但每次测试都要在命令行里敲代码,查看结果也不方便?或者想给团队里不懂技术的同事提供一个简单的测试入口?

今天,我们就来解决这个问题。我将手把手带你,用Python里最轻量级的Web框架——Flask,快速搭建一个专属的Web管理界面。这个界面不仅能让你通过网页上传音频文件、一键调用模型识别,还能查看历史记录、监控服务状态,整个过程就像点外卖一样简单。

即使你之前没怎么接触过Web开发,跟着这篇教程走,也能在半小时内拥有一个功能实用、界面清爽的管理后台。我们用的是最基础的HTML和一点点Bootstrap来美化界面,代码简单明了,保证你能看懂每一步在做什么。

1. 项目目标与环境准备

在开始敲代码之前,我们先明确一下这个Web界面要做什么,以及需要准备哪些“食材”。

1.1 我们要实现什么功能?

想象一下,你有一个已经部署好的SenseVoice-Small服务(假设它运行在本地http://localhost:8000,并提供了识别接口)。我们的Web管理界面需要围绕它提供三个核心功能:

  1. 音频识别测试:在网页上选择一个音频文件(比如.wav,.mp3),点击上传,后台自动调用SenseVoice-Small服务进行识别,并把返回的文字结果显示在页面上。
  2. 识别历史管理:每次识别的记录(包括文件名、识别文本、时间)都能被保存下来,并且可以在一个单独的页面里查看。当然,也可以删除某条或全部历史记录。
  3. 服务状态监控:在一个仪表盘页面,能直观地看到运行SenseVoice-Small服务的服务器的基本状态,比如CPU和内存的使用情况。

1.2 搭建你的开发环境

你需要准备两样东西:Python环境和几个必要的Python库。如果你已经熟悉Python,可以快速跳过这部分。

首先,确保你的电脑上安装了Python(建议3.7或以上版本)。打开终端或命令行,输入python --version检查一下。

接下来,安装我们需要的库。我们将使用pip这个Python包管理工具。在命令行中依次执行以下命令:

pip install flask

Flask是我们的核心Web框架,非常轻巧。

pip install requests

我们将用requests库来向后端的SenseVoice-Small服务发送HTTP请求。

pip install psutil

psutil是一个跨平台的库,用来获取系统信息,比如CPU、内存占用,这是我们做状态监控所需要的。

安装过程通常很快。安装完成后,你可以创建一个新的文件夹,比如叫做sensevoice_web_ui,我们所有的代码文件都将放在这里。

2. 从零开始:构建Flask应用骨架

Flask应用的结构非常清晰。我们从一个最简单的“Hello World”开始,然后逐步添加功能。

2.1 创建主程序文件

在你的项目文件夹里,新建一个名为app.py的文件。用任何文本编辑器(如VS Code, Sublime Text, 甚至记事本)打开它,输入以下代码:

from flask import Flask, render_template, request, jsonify, redirect, url_for import os import json from datetime import datetime app = Flask(__name__) # 一个简单的路由:访问网站根目录时显示“Hello World” @app.route('/') def index(): return 'Hello, SenseVoice Web UI!' if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000)

代码解释:

  • from flask import ...:导入Flask框架中我们即将用到的各种组件。
  • app = Flask(__name__):创建一个Flask应用实例,这是所有操作的起点。
  • @app.route('/'):这是一个“装饰器”,它告诉Flask,当用户访问网站的根路径(比如http://localhost:5000/)时,就执行下面定义的index()函数。
  • app.run(...):启动Flask自带的开发服务器。debug=True意味着开启调试模式,代码修改后会自动重启服务器,非常方便开发。host='0.0.0.0'表示允许同一网络下的其他设备访问,port=5000指定运行在5000端口。

保存文件,然后在命令行中,进入到你的项目文件夹,运行:

python app.py

你会看到类似这样的输出:

* Serving Flask app 'app' * Debug mode: on * Running on http://0.0.0.0:5000

现在,打开你的浏览器,访问http://localhost:5000,你应该能看到页面上显示着 “Hello, SenseVoice Web UI!”。

恭喜你,你的第一个Flask应用跑起来了!按Ctrl+C可以停止服务器。

2.2 设计我们的页面结构

一个Web应用通常有多个页面。我们来规划一下:

  • 首页 (/):音频文件上传和识别的主界面。
  • 历史记录页 (/history):展示所有过往的识别记录。
  • 状态监控页 (/status):显示服务器CPU和内存使用情况。

我们还需要一个地方来存放前端页面文件。在项目根目录下,创建一个名为templates的文件夹。Flask默认会在这个文件夹里寻找HTML模板文件。

templates文件夹里,我们先创建三个最基础的HTML文件:

  1. index.html(首页)
  2. history.html(历史记录页)
  3. status.html(状态监控页)

为了让它们有个统一的样式,我们引入Bootstrap。Bootstrap是一套现成的CSS样式和组件库,能让我们用很少的代码做出好看的界面。我们直接使用Bootstrap官方提供的CDN链接,这样连下载都不需要。

index.html为例,它的基础结构如下:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SenseVoice-Small 管理界面</title> <!-- 引入 Bootstrap 5 CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <div class="container-fluid"> <a class="navbar-brand" href="/">SenseVoice-Small Web UI</a> <div class="navbar-nav"> <a class="nav-link" href="/">识别测试</a> <a class="nav-link" href="/history">识别历史</a> <a class="nav-link" href="/status">服务状态</a> </div> </div> </nav> <div class="container mt-4"> <h1>欢迎使用语音识别测试界面</h1> <p>在这里上传音频文件,测试SenseVoice-Small模型的识别效果。</p> <!-- 具体的内容表单等会在这里添加 --> </div> <!-- 引入 Bootstrap JS (可选,用于一些交互组件) --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> </body> </html>

history.htmlstatus.html<head>和导航栏部分可以完全一样,只需要修改<div class="container">里面的内容即可。这样,我们三个页面的基础框架和导航就搭好了。

3. 核心功能实现:连接前端与后端

现在,我们来让页面“活”起来,实现具体的功能。这需要后端(app.py)和前端(HTML模板)配合。

3.1 实现音频上传与识别

这是最核心的功能。我们需要在index.html的表单里上传文件,然后在app.py里接收文件,调用SenseVoice-Small的API,最后把结果返回给页面显示。

第一步:完善index.html的表单

templates/index.html的容器 (<div class="container">) 内,添加一个文件上传表单和一个用来显示结果的区域。

<div class="container mt-4"> <h2>音频文件识别测试</h2> <form id="uploadForm" enctype="multipart/form-data"> <div class="mb-3"> <label for="audioFile" class="form-label">选择音频文件</label> <input class="form-control" type="file" id="audioFile" name="file" accept=".wav,.mp3,.m4a" required> <div class="form-text">支持 .wav, .mp3, .m4a 格式,文件大小建议小于10MB。</div> </div> <button type="submit" class="btn btn-primary">上传并识别</button> </form> <div id="resultArea" class="mt-4" style="display:none;"> <h4>识别结果:</h4> <div class="card"> <div class="card-body"> <p id="resultText" class="card-text"></p> <small class="text-muted" id="resultTime"></small> </div> </div> </div> <div id="loadingSpinner" class="mt-4 text-center" style="display:none;"> <div class="spinner-border text-primary" role="status"> <span class="visually-hidden">识别中...</span> </div> <p class="mt-2">正在识别音频,请稍候...</p> </div> </div> <!-- 添加一点JavaScript来处理表单提交和显示结果 --> <script> document.getElementById('uploadForm').addEventListener('submit', async function(event) { event.preventDefault(); // 阻止表单默认提交行为 const formData = new FormData(); const fileInput = document.getElementById('audioFile'); formData.append('file', fileInput.files[0]); // 显示加载动画,隐藏结果区域 document.getElementById('loadingSpinner').style.display = 'block'; document.getElementById('resultArea').style.display = 'none'; try { const response = await fetch('/recognize', { method: 'POST', body: formData }); const data = await response.json(); // 隐藏加载动画 document.getElementById('loadingSpinner').style.display = 'none'; if (data.success) { // 显示识别结果 document.getElementById('resultText').textContent = data.text; document.getElementById('resultTime').textContent = '识别时间: ' + data.timestamp; document.getElementById('resultArea').style.display = 'block'; } else { alert('识别失败: ' + data.error); } } catch (error) { document.getElementById('loadingSpinner').style.display = 'none'; alert('请求出错: ' + error); } }); </script>

第二步:在app.py中处理上传和识别

现在,我们需要在app.py中创建/recognize这个路由,来处理前端发送过来的请求。

首先,在文件顶部添加必要的导入和配置:

import os import requests from werkzeug.utils import secure_filename # 假设你的SenseVoice-Small服务地址 SENSEVOICE_API_URL = "http://localhost:8000/v1/audio/transcriptions" # 请根据实际API地址修改 # 允许上传的文件类型 ALLOWED_EXTENSIONS = {'wav', 'mp3', 'm4a'} # 设置一个临时上传文件夹 UPLOAD_FOLDER = 'uploads' os.makedirs(UPLOAD_FOLDER, exist_ok=True) app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

然后,在app.py中定义首页路由,让它渲染我们刚写好的index.html模板,并添加处理识别的路由:

@app.route('/') def index(): # 渲染 templates 文件夹下的 index.html return render_template('index.html') @app.route('/recognize', methods=['POST']) def recognize_audio(): if 'file' not in request.files: return jsonify({'success': False, 'error': '没有选择文件'}) file = request.files['file'] if file.filename == '': return jsonify({'success': False, 'error': '没有选择文件'}) if file and allowed_file(file.filename): # 安全地保存文件名 filename = secure_filename(file.filename) filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) file.save(filepath) try: # 调用 SenseVoice-Small API # 注意:这里需要根据你实际部署的API格式调整 with open(filepath, 'rb') as audio_file: files = {'file': audio_file} # 假设API需要一些参数,例如模型名称 data = {'model': 'sensevoice-small'} response = requests.post(SENSEVOICE_API_URL, files=files, data=data) if response.status_code == 200: result = response.json() # 假设API返回的识别文本在 `text` 字段中 recognized_text = result.get('text', '') timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # TODO: 将记录保存到历史记录(下一节实现) # save_to_history(filename, recognized_text, timestamp) # 返回成功结果给前端 return jsonify({ 'success': True, 'text': recognized_text, 'timestamp': timestamp, 'filename': filename }) else: return jsonify({'success': False, 'error': f'识别服务错误: {response.status_code}'}) except Exception as e: return jsonify({'success': False, 'error': str(e)}) finally: # 可选:识别完成后删除临时文件 if os.path.exists(filepath): os.remove(filepath) else: return jsonify({'success': False, 'error': '不支持的文件类型'})

代码解释:

  1. recognize_audio函数处理/recognize的POST请求。
  2. 检查前端是否上传了文件。
  3. 检查文件类型是否允许。
  4. 使用secure_filename处理文件名,防止安全问题,然后将文件保存到本地uploads文件夹。
  5. 使用requests库,以multipart/form-data格式将音频文件发送到我们假设的SenseVoice-Small API地址。
  6. 解析API返回的JSON,提取识别文本。
  7. 将识别结果、时间戳等信息打包成JSON返回给前端。
  8. (可选)删除临时保存的音频文件。

重要提示:你需要将SENSEVOICE_API_URL替换成你实际部署的SenseVoice-Small服务的API地址和端点。同时,API请求的格式(filesdata参数)也需要根据该服务的具体文档进行调整。

现在,重启你的Flask应用 (python app.py),刷新浏览器。你应该能看到一个带文件选择框和按钮的页面。选择一个音频文件点击上传,如果后端服务配置正确,就能看到识别结果了!

3.2 实现历史记录功能

历史记录功能需要持久化存储数据。为了简单起见,我们用一个JSON文件来模拟数据库。

第一步:在app.py中添加历史记录操作函数

app.py的开头附近,定义历史记录文件的路径和几个辅助函数:

HISTORY_FILE = 'history.json' def load_history(): """从JSON文件加载历史记录""" if os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, 'r', encoding='utf-8') as f: try: return json.load(f) except json.JSONDecodeError: return [] return [] def save_history(history_list): """保存历史记录到JSON文件""" with open(HISTORY_FILE, 'w', encoding='utf-8') as f: json.dump(history_list, f, ensure_ascii=False, indent=4) def save_to_history(filename, text, timestamp): """将单条记录添加到历史并保存""" history = load_history() # 在列表开头插入新记录,使得最新的记录显示在最前面 history.insert(0, { 'id': len(history) + 1, 'filename': filename, 'text': text, 'timestamp': timestamp }) save_history(history)

然后,修改刚才的/recognize路由,在识别成功后调用save_to_history函数(取消注释TODO那行):

# 在识别成功的代码块内 recognized_text = result.get('text', '') timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 保存到历史记录 save_to_history(filename, recognized_text, timestamp)

第二步:创建历史记录页面路由和模板

app.py中添加/history路由:

@app.route('/history') def show_history(): history_list = load_history() # 将历史记录列表传递给模板 return render_template('history.html', history=history_list)

现在,创建templates/history.html文件,用来展示历史记录:

<!-- 头部和导航栏与 index.html 相同,此处省略 --> <div class="container mt-4"> <h2>识别历史记录</h2> {% if history %} <p>共 {{ history|length }} 条记录。</p> <div class="list-group"> {% for record in history %} <div class="list-group-item list-group-item-action"> <div class="d-flex w-100 justify-content-between"> <h6 class="mb-1">{{ record.filename }}</h6> <small>{{ record.timestamp }}</small> </div> <p class="mb-1">{{ record.text }}</p> <small> <a href="#" class="text-danger" onclick="deleteRecord({{ record.id }})">删除</a> </small> </div> {% endfor %} </div> <div class="mt-3"> <button class="btn btn-outline-danger btn-sm" onclick="clearAllHistory()">清空所有历史</button> </div> {% else %} <div class="alert alert-info" role="alert"> 暂无识别历史。 </div> {% endif %} </div> <script> function deleteRecord(recordId) { if (confirm('确定要删除这条记录吗?')) { fetch(`/history/delete/${recordId}`, { method: 'DELETE' }) .then(response => response.json()) .then(data => { if (data.success) { location.reload(); // 删除成功后刷新页面 } else { alert('删除失败: ' + data.error); } }); } } function clearAllHistory() { if (confirm('确定要清空所有历史记录吗?此操作不可撤销。')) { fetch('/history/clear', { method: 'POST' }) .then(response => response.json()) .then(data => { if (data.success) { location.reload(); } else { alert('清空失败: ' + data.error); } }); } } </script>

这个模板使用了Jinja2模板语法(Flask默认的模板引擎)。{% if history %}{% for record in history %}用来循环渲染后端传递过来的history列表。{{ record.filename }}等用来插入变量值。

第三步:在app.py中实现删除和清空接口

最后,我们需要为前端的删除和清空操作提供后端接口。

@app.route('/history/delete/<int:record_id>', methods=['DELETE']) def delete_history_record(record_id): history = load_history() # 过滤掉要删除的记录 new_history = [record for record in history if record['id'] != record_id] if len(new_history) != len(history): save_history(new_history) return jsonify({'success': True}) else: return jsonify({'success': False, 'error': '记录未找到'}) @app.route('/history/clear', methods=['POST']) def clear_all_history(): # 清空历史记录文件 save_history([]) return jsonify({'success': True})

现在,重启应用,点击导航栏的“识别历史”,你就能看到所有上传识别的记录了,并且可以进行删除操作。

3.3 实现服务状态监控

状态监控页面相对简单,我们使用psutil库获取系统信息。

app.py中添加/status路由:

import psutil @app.route('/status') def show_status(): # 获取CPU使用率(间隔1秒) cpu_percent = psutil.cpu_percent(interval=1) # 获取内存信息 memory_info = psutil.virtual_memory() # 获取磁盘信息(根目录) disk_info = psutil.disk_usage('/') status_data = { 'cpu_percent': cpu_percent, 'memory_total': round(memory_info.total / (1024**3), 2), # 转换为GB 'memory_used': round(memory_info.used / (1024**3), 2), 'memory_percent': memory_info.percent, 'disk_total': round(disk_info.total / (1024**3), 2), 'disk_used': round(disk_info.used / (1024**3), 2), 'disk_percent': disk_info.percent, } return render_template('status.html', status=status_data)

然后,创建templates/status.html

<!-- 头部和导航栏与 index.html 相同,此处省略 --> <div class="container mt-4"> <h2>服务状态监控</h2> <p>当前服务器资源使用情况。</p> <div class="row"> <div class="col-md-6"> <div class="card mb-3"> <div class="card-header"> <h5 class="card-title mb-0">CPU 使用率</h5> </div> <div class="card-body"> <h2 class="display-6">{{ status.cpu_percent }}%</h2> <div class="progress"> <div class="progress-bar" role="progressbar" style="width: {{ status.cpu_percent }}%;" aria-valuenow="{{ status.cpu_percent }}" aria-valuemin="0" aria-valuemax="100"> </div> </div> </div> </div> </div> <div class="col-md-6"> <div class="card mb-3"> <div class="card-header"> <h5 class="card-title mb-0">内存使用</h5> </div> <div class="card-body"> <h2 class="display-6">{{ status.memory_percent }}%</h2> <p class="card-text"> 已用 {{ status.memory_used }} GB / 总共 {{ status.memory_total }} GB </p> <div class="progress"> <div class="progress-bar bg-success" role="progressbar" style="width: {{ status.memory_percent }}%;" aria-valuenow="{{ status.memory_percent }}" aria-valuemin="0" aria-valuemax="100"> </div> </div> </div> </div> </div> </div> <div class="card"> <div class="card-header"> <h5 class="card-title mb-0">磁盘使用</h5> </div> <div class="card-body"> <h2 class="display-6">{{ status.disk_percent }}%</h2> <p class="card-text"> 已用 {{ status.disk_used }} GB / 总共 {{ status.disk_total }} GB </p> <div class="progress"> <div class="progress-bar bg-info" role="progressbar" style="width: {{ status.disk_percent }}%;" aria-valuenow="{{ status.disk_percent }}" aria-valuemin="0" aria-valuemax="100"> </div> </div> </div> </div> </div>

这个页面使用了Bootstrap的卡片和进度条组件,直观地展示了CPU、内存和磁盘的使用情况。数据通过{{ status.xxx }}从后端传递过来。

4. 总结与后续优化建议

跟着上面的步骤走下来,一个具备基本功能的SenseVoice-Small Web管理界面就搭建完成了。整个过程其实并不复杂,核心就是理解Flask如何处理请求(路由)、如何与前端交互(接收表单、返回JSON)、以及如何渲染动态页面(模板)。

现在,你的应用应该具备了一个简洁的导航栏,可以在三个核心页面间切换:上传识别、查看历史、监控状态。对于个人使用或小团队内部测试来说,这个功能已经足够实用了。

当然,这只是一个起点。如果你想让这个工具更强大、更健壮,可以考虑从以下几个方向继续完善:

  • 美化界面与交互:现在的界面比较基础。你可以深入学习Bootstrap,使用更多的组件(如模态框、Toast提示、更漂亮的表格)来提升用户体验。也可以考虑使用一些前端图表库(如Chart.js)来让状态监控页面更直观。
  • 增强文件管理:目前我们识别完就删除了临时文件。你可以增加一个文件管理功能,让用户可以选择保留某些音频文件,或者提供批量上传和识别的能力。
  • 用户与权限:如果需要给多人使用,可以增加简单的用户登录和权限管理,不同用户只能看到自己的历史记录。
  • 使用真正的数据库:当历史记录变多时,JSON文件会变得难以管理。可以考虑换成SQLite(轻量级)或MySQL/PostgreSQL,使用Flask-SQLAlchemy这样的ORM库来操作,会方便和安全得多。
  • 错误处理与日志:增加更完善的错误处理机制,比如网络超时、API返回异常格式等。同时记录操作日志,方便排查问题。
  • 部署上线:现在用的是Flask自带的开发服务器,不适合生产环境。可以考虑使用Gunicorn + Nginx 或 Docker 等方式进行部署,让应用更稳定、安全。

最重要的是,这个项目为你提供了一个清晰的模式:用Flask快速搭建一个连接AI模型与用户的桥梁。你可以把这里的SenseVoice-Small换成任何其他提供HTTP API的AI模型(如图像识别、文本生成等),快速构建出对应的测试或管理界面。希望这个小小的项目能成为你探索更多AI应用可能性的起点。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

http://www.jsqmd.com/news/504559/

相关文章:

  • 从理论到实践:SPSS中卡方检验与Fisher精确检验的对比与选择指南
  • Android App内嵌H5页面优化实战:我是如何用腾讯TBS将加载速度提升30%的
  • 全文降AI率vs局部降AI率:从检测算法角度分析哪种策略效果更好
  • Spring Boot 循环依赖解决方案完全指南
  • 2026家电亚克力面板定制服务深度评测 - 优质品牌商家
  • 2026年推荐水泥固化地坪工厂推荐:水泥固化地坪精选公司 - 品牌宣传支持者
  • 保姆级教程:手把手教你为Linux内核和模块配置签名校验(附常见错误排查)
  • Nanbeige 4.1-3B多场景落地:教育问答、创意写作、RPG叙事助手实战解析
  • 2026年石油石化电力电缆生产厂家推荐:涵盖各品类电缆生产厂家介绍 - 品牌2026
  • 2026武汉搬家服务优质机构推荐榜:武汉附近搬家公司/湖北个人学生搬家公司/湖北仓库搬家公司/湖北价格便宜搬家公司/选择指南 - 优质品牌商家
  • Hotkey Detective:Windows热键冲突智能诊断与系统优化工具
  • 2026年3月中国电缆一线品牌、标杆品牌推荐及相关品牌解析 - 品牌2026
  • 梯形图逻辑→C语言结构体映射失败的3大根源,89%工程师至今仍在手动修补
  • Xycom XVME-560模拟输入模块
  • Nacos 1.4和Apollo 2.0配置解析对比:为什么YAML支持不是决定性因素?
  • 三菱FX系列PLC脉冲输出全攻略:从PLSY指令到多轴扩展配置
  • Spring_couplet_generation 开发环境搭建:IDE(IntelliJ IDEA)与Git版本控制
  • Qwen-Image镜像环境配置:替代传统Dockerfile的标准化、可复现推理环境
  • AIGlasses_for_navigation模型轻量化效果:体积与精度权衡分析
  • 培养非理性决策:让机器永远无法预测你的行为
  • 【Dify生产环境Token成本监控黄金架构】:20年SRE亲授3层监控体系与实时熔断设计
  • 2026年知名的龙门架杆件公司推荐:龙门架杆件推荐公司 - 品牌宣传支持者
  • CTF实战:绕过Ping命令注入中的空格过滤(GXYCTF2019题解)
  • OpenClaw本地部署VS云端部署:为什么你的AI助手不能“动手”?
  • 去AI味提示词怎么写?Kimi豆包元宝通用的10个Prompt模板
  • Ubuntu24.04下QEMU模拟ARM开发环境:从零搭建到NFS根文件系统实战
  • 数据库课程设计新思路:集成黑丝空姐-造相Z-Turbo的智能图库系统
  • 企业级Dify评估系统安全加固指南(含SOC2 Type II验证模板):从Judge微调数据溯源到评估结果不可抵赖签名
  • Windows Cleaner终极指南:三步告别C盘爆红,让电脑重获新生
  • Llama-3+Dify混合部署下的Token泄漏追踪,从Prometheus到Granfana的全链路监控闭环