
1. 从一次看板联调说起数据链路为什么总在 Key 上卡住做 Flask 测试平台到第二十一篇产品、应用、需求提测这些模块都跑通了但每周提测了多少需求、哪些产品在动光看列表不直观于是要加数据看板。看板本身不复杂后端聚合出「周 × 应用」的提测数量前端用 Echarts 画堆叠面积图、用 G2Plot 画分组柱状图。真正让人反复返工的不是图表配置而是数据链路里的模型调用凭证管理。我试过的场景是这样的看板里想加一个「智能摘要」入口把本周提测趋势丢给模型生成一句话结论同时本地还用着 Claude Code 做代码补全、用另一个脚本做数据清洗。结果每个工具一套 Key、一套 base_url散落在.env、settings.json、config.toml里改一次要翻五个文件联调时经常出现「图表接口通了、模型调用 401」的割裂状态。这篇就把这条链路收拢Flask 后端聚合接口 Echarts/G2Plot 渲染 TaoToken 统一 Key 接入目标是一次配置跑通后面加图表、加模型能力都不用再动凭证。适合谁看正在用 Flask 写测试平台、前端用 Vue Element 做看板、并且希望把多个 AI 工具的调用凭证统一管理的同学。你需要有基本的 Flask 蓝图概念和 Vue 组件使用经验图表部分我会给完整可复制的代码。2. TaoToken 前置把分散的 Key 收成一个入口TaoToken 在这里扮演的角色是「统一模型调用入口」。它提供 OpenAI 兼容的接口形态也就是说你原来用openaiSDK 或requests直接发 chat completions 请求的代码只需要改base_url和api_key两个值其余请求体结构不变。对看板场景来说这意味着后端聚合接口里想加模型能力时不用再引入新的 SDK 或新的鉴权逻辑。先明确几个地址后面配置里会反复用到官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基址https://taotoken.net/api 注意这个不带查询参数代码里填这个模型对话页https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewriteCoding Plan 页https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteClaudeCodeAnthropic 接入https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite操作顺序建议这样先进控制台创建 Key然后在 API Keys 页面复制出来如果你同时用 Claude Code 这类编码工具可以在 ClaudeCodeAnthropic 页面看对应的接入方式把同一套 Key 复用过去。看板后端只需要一个 Key前端不直接持有 Key所有模型调用都走 Flask 后端转发这样前端打包产物里不会泄露凭证。注意Key 只放在后端环境变量或配置文件里不要写进 Vue 的.env或前端源码。看板前端只调用你自己的 Flask 接口。3. 可复制配置config.toml 与 settings.json 骨架统一配置的核心思路是「一份凭证、多处引用」。后端用config.toml管数据库和模型入口本地工具用settings.json管编码助手两者都指向同一个 Key 来源。下面是可以直接抄的骨架。3.1 后端 config.toml# configs/config.toml [mysql] host 127.0.0.1 port 3306 user root password your_db_password database test_platform [taotoken] # 统一模型调用入口所有 AI 能力都走这里 base_url https://taotoken.net/api api_key sk-你的TaoTokenKey default_model gpt-4o-mini timeout 30 [dashboard] # 看板聚合接口的默认时间跨度天 default_range_days 30对应的 Python 读取逻辑放在configs/__init__.py里用tomllibPython 3.11或tomli# configs/__init__.py import tomllib from pathlib import Path _config_path Path(__file__).parent / config.toml with open(_config_path, rb) as f: _cfg tomllib.load(f) class Config: MYSQL_HOST _cfg[mysql][host] MYSQL_PORT _cfg[mysql][port] MYSQL_USER _cfg[mysql][user] MYSQL_PASSWORD _cfg[mysql][password] MYSQL_DATABASE _cfg[mysql][database] TAOTOKEN_BASE_URL _cfg[taotoken][base_url] TAOTOKEN_API_KEY _cfg[taotoken][api_key] TAOTOKEN_DEFAULT_MODEL _cfg[taotoken][default_model] TAOTOKEN_TIMEOUT _cfg[taotoken][timeout] DASHBOARD_DEFAULT_RANGE_DAYS _cfg[dashboard][default_range_days] config Config()3.2 本地工具 settings.json如果你同时用编码助手类工具把同一套 Key 写进它的settings.json避免两处维护{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的TaoTokenKey }, model: claude-sonnet-4-20250514, permissions: { allow: [Read, Write, Bash] } }这样后端config.toml和本地settings.json指向同一个 Key 来源改 Key 时两处同步即可不会出现「看板接口能调、编码工具报 401」的错位。3.3 后端聚合接口复用现有蓝图看板的堆叠数据接口沿用你已有的test_dashboard蓝图这里补一个带模型摘要的版本展示统一 Key 怎么接进来# apis/dashboard.py import requests from flask import Blueprint, request, current_app from configs import config, format from apis.product import pool test_dashboard Blueprint(test_dashboard, __name__) test_dashboard.route(/api/dashboard/stacked, methods[POST]) def get_request_stacked(): connection None try: connection pool.connection() with connection.cursor() as cursor: sql SELECT DATE_FORMAT(r.createDate, %%Y%%u) AS weeks, COALESCE(a.note, No_App) AS note, COUNT(a.id) AS counts FROM request r LEFT JOIN apps a ON r.appId a.id GROUP BY weeks, note cursor.execute(sql) rows cursor.fetchall() weeks, notes, key_value [], [], {} for row in rows: week row[weeks] or No_Week note row[note] or No_App counts row[counts] or 0 if week not in weeks: weeks.append(week) if note not in notes: notes.append(note) key_value[f{week}_{note}] counts weeks.sort() series {note: [key_value.get(f{week}_{note}, 0) for week in weeks] for note in notes} resp format.resp_format_success resp[data] {weeks: weeks, notes: notes, series: series} return resp except Exception as e: current_app.logger.error(fstacked error: {e}) resp format.resp_format_error resp[message] Failed to process stacked data return resp, 500 finally: if connection: connection.close()模型摘要接口单独一个路由调用 TaoTokentest_dashboard.route(/api/dashboard/summary, methods[POST]) def get_dashboard_summary(): body request.get_json() or {} weeks body.get(weeks, []) totals body.get(totals, []) prompt f以下是最近几周的提测数量周 {weeks}数量 {totals}。用一句话总结趋势不超过40字。 try: resp requests.post( f{config.TAOTOKEN_BASE_URL}/v1/chat/completions, headers{ Authorization: fBearer {config.TAOTOKEN_API_KEY}, Content-Type: application/json, }, json{ model: config.TAOTOKEN_DEFAULT_MODEL, messages: [{role: user, content: prompt}], temperature: 0.3, }, timeoutconfig.TAOTOKEN_TIMEOUT, ) resp.raise_for_status() summary resp.json()[choices][0][message][content] return {code: 20000, message: success, data: {summary: summary}} except requests.RequestException as e: current_app.logger.error(fsummary error: {e}) return {code: 50000, message: summary failed, data: {}}, 500注意base_url后面拼的是/v1/chat/completions这是 OpenAI 兼容路径。如果你的接入文档里写的是别的路径以文档为准。4. 验证请求从 curl 到 Echarts/G2Plot 渲染配置写完先别急着开前端用 curl 把后端接口和模型入口分别打一遍确认链路通。4.1 验证模型入口curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的TaoTokenKey \ -H Content-Type: application/json \ -d { model: gpt-4o-mini, messages: [{role: user, content: 回复 ok}] }返回里能看到choices[0].message.content就说明 Key 和 base_url 都对。如果返回 401先检查 Key 有没有多余空格返回 404 就检查路径是不是/v1/chat/completions。4.2 验证看板聚合接口curl -X POST http://127.0.0.1:5000/api/dashboard/stacked \ -H Content-Type: application/json \ -d {}期望返回结构{ code: 20000, message: success, data: { weeks: [202601, 202602], notes: [提测平台, 性能测试], series: { 提测平台: [12, 18], 性能测试: [3, 5] } } }4.3 前端 Echarts 堆叠面积图在src/views/dashboard/index.vue里Echarts 部分沿用你原来的写法关键是series动态生成import * as echarts from echarts import { requestStacked } from /api/board export default { name: Dashboard, mounted() { this.getStackedData() }, methods: { getStackedData() { requestStacked().then(resp { this.initStackedChart(resp.data) }) }, initStackedChart(data) { const chartDom this.$refs[LineChartBoard] const myChart echarts.init(chartDom) const series [] for (const key in data.series) { series.push({ name: key, type: line, stack: Total, areaStyle: {}, emphasis: { focus: series }, data: data.series[key] }) } const option { title: { text: 周需求提测趋势 }, tooltip: { trigger: axis }, legend: { data: data.notes }, xAxis: [{ type: category, boundaryGap: false, data: data.weeks }], yAxis: [{ type: value }], series } myChart.setOption(option) } } }4.4 G2Plot 分组柱状图G2Plot 的数据绑定更直接接口返回的[{weeks, note, counts}]数组可以直接喂给Columnimport { Column } from antv/g2plot import { requestMetaData } from /api/board export default { data() { return { stackedColumnPlot: undefined, stackedColumnData: [], stackedColumnMode: true } }, mounted() { this.stackedColumnPlot new Column(ColumnBoard, { data: this.stackedColumnData, xField: weeks, yField: counts, seriesField: note, isGroup: true, columnStyle: { radius: [20, 20, 0, 0] } }) this.stackedColumnPlot.render() this.getMetaData() }, methods: { getMetaData() { requestMetaData({ date: [] }).then(resp { this.stackedColumnData resp.data this.stackedColumnPlot.changeData(this.stackedColumnData) }) } } }两个图表都渲染出来后再调一次/api/dashboard/summary把模型返回的一句话摘要显示在卡片头部整条链路就算跑通了。5. 本篇常见错排查图表空白但接口有数据Echarts 初始化必须在 DOM 挂载后用mounted而不是createdG2Plot 的容器id要确保在new Column之前已经存在否则render()找不到节点。series 里出现 undefined后端拼key_value时如果 week 或 note 为 NULL键名会变成None_None前端遍历时对不上。用or No_Week/or No_App兜底和上面代码一致。模型接口 401先确认Authorization头是Bearer sk-xxx格式再确认base_url没有多写/v1配置里填https://taotoken.net/api请求时再拼/v1/chat/completions。如果本地settings.json和后端config.toml用了不同 Key也会出现一边通一边不通。G2Plot 切换分组/累积模式不生效update方法传的是增量配置isGroup要传字符串true/false传布尔值在部分版本里不触发重绘。跨域报错前端开发服务器调 Flask 接口时在 Flask 侧加flask-cors或者用 Vue 的devServer.proxy转发不要在前端直接请求 TaoToken 的地址。6. 后续怎么接按场景分流看板数据链路跑通后如果你要继续加模型能力按用途选入口会更省事。只是想在页面上试模型效果、对比不同模型的摘要质量去模型对话页直接试不用改代码https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite如果是长期在编码工具里用、或者要把 Agent 接到看板的自动化流程里看 Coding Plan 的额度方式https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite接入过程中遇到 401、路径不对、Key 管理混乱这类问题直接翻接入文档和 API Keys 页面最快https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 和 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite我自己的习惯是后端config.toml只留一个[taotoken]段所有模型调用都从这里读base_url和api_key前端永远不碰 Key。这样看板加多少个图表、加多少个模型入口凭证都只有一处要改。