【实战手记】如何高效构建AI使用记录统计接口:从设计到落地的全流程拆解
2025-04-08
今天,我们用不到半天的时间,完成了一个企业级AI使用监控系统中最关键的数据接口之一:使用记录概览(Usage Summary)API。
这个接口的目标是简单明了的:提供分页、筛选、关联信息丰富的用户调用统计数据,支持灵活的多维度分析,并保证前后端协同顺畅、数据结构直观、性能可控。
但要做对、做顺,不只是写几个SQL那么简单。这背后有一整套清晰的“接口即产品”的设计思维——而这正是我这次主导的核心部分。
以下是完整复盘,供你参考、复用、迭代。
一、项目背景与需求拆解
你在做一个AI平台,已经具备用户、组织、模型等基本数据结构,现在你需要有一个“总览接口”,让运营、财务或研发人员能快速看到:
某个用户/组织/模型使用了多少 token?
花了多少钱?
距离各自的配额还有多少余量?
这些数据,能否支持分页浏览、灵活筛选?
表面看是个“分页接口”,实则要解决数据聚合 + 多表关联 + 统计指标计算 + 实用性数据格式化四重难点。
二、接口设计思路:从“使用场景”倒推字段结构
我们没有从字段出发思考,而是反着来——从使用者视角出发,问自己五个问题:
谁最关心这些数据?→ 用户管理者、财务人员、模型分发方
他们想筛什么?→ 按用户、组织、模型、时间段等过滤
他们最想看什么?→ 当前用量、总花费、剩余额度(是否超限)
前端渲染需要哪些字段?→ 显示用户名、组织名、模型名等
最终导出或聚合会怎么用?→ 分页浏览,必要时导出明细表格
因此,字段不是“数据库里有什么”,而是“前端/使用者希望看到什么”。
我们提炼出了以下核心展示字段:
user_id
,username
org_id
,org_name
model_id
,model_name
total_tokens_used
,total_cost_usd
token_limit
,cost_limit_usd
tokens_remaining
,cost_remaining
updated_at
✍️ 这一步,是我主导的价值关键点。不是“字段搬运工”,而是“体验设计师”。
三、核心代码实现:不止是能跑,更要优雅
你原本写了一版能用的代码,我们在此基础上进行了一次系统性的重构,目标是:
结构清晰:筛选、分页、数据组装三层职责分明
命名统一:避免冗余缩写,提高可读性
预加载替代连接:用
with()
代替不必要的joinWith()
提升性能数据格式友好:把“剩余额度”直接计算好返回给前端,而不是让前端自己算
这是重构后的核心代码逻辑:
$query = AiUserTokenUsage::find() ->alias('u') ->with(['user', 'org', 'model']) ->orderBy(['u.updated_at' => SORT_DESC]); if ($userId = $request->get('user_id')) { $query->andWhere(['u.user_id' => $userId]); } if ($orgId = $request->get('org_id')) { $query->andWhere(['u.org_id' => $orgId]); } if ($modelId = $request->get('ai_model_id')) { $query->andWhere(['u.ai_model_id' => $modelId]); }
分页:
$pagination = new \yii\data\Pagination([ 'totalCount' => $query->count(), 'pageSize' => $pageSize, 'page' => max(0, $page - 1), ]);
数据组装:
$items = array_map(function (AiUserTokenUsage $usage) { return [ 'user_id' => $usage->user_id, 'username' => $usage->user->username ?? '未知用户', 'org_id' => $usage->org_id, 'org_name' => $usage->org->name ?? '未知组织', 'model_id' => $usage->ai_model_id, 'model_name' => $usage->model->model_name ?? '(默认)', 'total_tokens_used' => $usage->total_tokens_used, 'total_cost_usd' => $usage->total_cost_usd, 'token_limit' => $usage->token_limit, 'cost_limit_usd' => $usage->cost_limit_usd, 'tokens_remaining' => $usage->token_limit !== null ? max(0, $usage->token_limit - $usage->total_tokens_used) : null, 'cost_remaining' => $usage->cost_limit_usd !== null ? max(0, round($usage->cost_limit_usd - $usage->total_cost_usd, 5)) : null, 'updated_at' => $usage->updated_at, ]; }, $usages);
返回结构统一规范:
return [ 'items' => $items, '_meta' => [ 'totalCount' => $pagination->totalCount, 'pageCount' => $pagination->getPageCount(), 'currentPage' => $pagination->getPage() + 1, 'perPage' => $pagination->getPageSize(), ], ];
四、关键设计原则回顾
字段由使用场景推导,而非表结构主导
→ 用“前端要什么”反推“后端提供什么”,是接口设计的本质。分页逻辑内聚,避免表层冗余参数污染核心代码
→ 所有请求处理、分页、组装逻辑清晰分层,便于后期复用。字段的语义要帮前端“减负”
→ 比如剩余配额、格式化时间、组织/模型名称等都直接给到,而不是让前端自己拼。预加载而非 join 查询优先
→ 在无复杂多表过滤条件时,使用with()
是 Yii2 的性能优选。
五、可拓展的改造方向
这个接口是“核心监控模块”的支点,未来可以从以下方向继续进化:
✅ 支持时间范围筛选(
updated_at
between)✅ 增加按周/月/模型维度聚合统计接口
✅ 支持 Excel 导出
✅ 加入缓存或异步批量统计,解决高并发场景
✅ 接入权限系统,限制非管理员访问敏感数据
六、结语:技术不只是“写出来”,更是“设计出来”
一个优秀的接口,往往不是一次性写完的,而是需要从场景出发、到数据结构设计、再到语义细节打磨,每一步都要围绕“谁在用、怎么用、怎样更轻”。
这次的协作开发,我更像是一个“接口产品经理”,而你是架构师和执行者,我们一起让技术不仅可用,而且好用、易用、复用。
这才是工程师真正的生产力。
发表评论: