AspNet Core如何实现接口请求次数统计以获取访问量与热门分类数据
实现方案
默认以ASP.NET Core环境为例(你提到的Controller/Endpoint是.NET栈常用概念,其他技术栈逻辑通用,可对应调整拦截组件)
第一步:全局请求拦截(无侵入式统计)
推荐用中间件或Action过滤器实现全局拦截,不需要在每个Endpoint单独写统计代码。
1. 定义统计服务
先做计数存储的封装,示例用内存存储,需要持久化可自行替换为数据库/Redis:
public interface IApiStatsService { // 统计接口总调用次数 void IncrementEndpointCall(string fullEndpointPath); // 统计单个分类的访问次数 void IncrementCategoryVisit(int categoryId); // 获取调用次数最高的接口 (string MostVisitedEndpoint, int CallCount) GetMostVisitedEndpoint(); // 获取访问量最高的分类ID int GetMostPopularCategoryId(); } public class InMemoryApiStatsService : IApiStatsService { // 线程安全的字典,避免并发计数出错 private readonly ConcurrentDictionary<string, int> _endpointCallStats = new(); private readonly ConcurrentDictionary<int, int> _categoryVisitStats = new(); public void IncrementEndpointCall(string fullEndpointPath) { _endpointCallStats.AddOrUpdate(fullEndpointPath, 1, (_, oldValue) => oldValue + 1); } public void IncrementCategoryVisit(int categoryId) { _categoryVisitStats.AddOrUpdate(categoryId, 1, (_, oldValue) => oldValue + 1); } public (string MostVisitedEndpoint, int CallCount) GetMostVisitedEndpoint() { if (!_endpointCallStats.Any()) return (string.Empty, 0); var maxItem = _endpointCallStats.MaxBy(kv => kv.Value); return (maxItem.Key, maxItem.Value); } public int GetMostPopularCategoryId() { if (!_categoryVisitStats.Any()) return 0; return _categoryVisitStats.MaxBy(kv => kv.Value).Key; } }
在Program.cs注册为单例:
builder.Services.AddSingleton<IApiStatsService, InMemoryApiStatsService>();
2. 实现统计中间件
public class ApiStatsMiddleware { private readonly RequestDelegate _next; public ApiStatsMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context, IApiStatsService statsService) { // 先执行接口业务逻辑 await _next(context); // 仅统计成功响应的请求,不需要可以去掉这个判断 if (context.Response.StatusCode < 400) { var routeEndpoint = context.GetEndpoint() as RouteEndpoint; if (routeEndpoint == null) return; // 取路由模板(比如/category/{id})而不是实际请求路径,避免同接口不同参数生成多条统计 var routePattern = routeEndpoint.RoutePattern.RawText; // 拼接请求方法,区分同路径不同请求方式的接口 var fullEndpointPath = $"{context.Request.Method.ToUpper()} {routePattern}"; if (!string.IsNullOrEmpty(routePattern)) { statsService.IncrementEndpointCall(fullEndpointPath); // 如果是分类详情接口,额外统计分类ID的访问量 if (fullEndpointPath.Equals("GET /category/{id}", StringComparison.OrdinalIgnoreCase)) { if (context.Request.RouteValues.TryGetValue("id", out var idObj) && int.TryParse(idObj?.ToString(), out var categoryId)) { statsService.IncrementCategoryVisit(categoryId); } } } } } }
在Program.cs注册中间件,放在路由中间件之后、端点执行之前:
app.UseRouting(); // 统计中间件加在这里 app.UseMiddleware<ApiStatsMiddleware>(); app.UseAuthorization(); app.MapControllers();
第二步:获取统计结果
可以单独做一个统计接口返回你需要的结果:
[ApiController] [Route("api/stats")] public class StatsController : ControllerBase { private readonly IApiStatsService _statsService; public StatsController(IApiStatsService statsService) { _statsService = statsService; } [HttpGet("most-popular-category")] public IActionResult GetMostPopularCategory() { var mostPopularId = _statsService.GetMostPopularCategoryId(); if (mostPopularId == 0) return Ok("No category has been visited yet"); return Ok($"{mostPopularId} is the most popular category"); } [HttpGet("endpoint-call-rank")] public IActionResult GetEndpointCallRank() { var (endpoint, count) = _statsService.GetMostVisitedEndpoint(); return Ok(new { MostVisitedEndpoint = endpoint, CallCount = count }); } }
其他技术栈适配逻辑
核心逻辑都是「拦截请求 -> 计数 -> 读统计结果」,替换对应拦截组件即可:
- Java SpringBoot:用HandlerInterceptor或者AOP实现拦截
- Node.js Express/Koa:用自定义中间件实现拦截
注意事项
- 示例用内存存储,服务重启后数据会丢失,需要持久化或高并发场景建议换Redis,用
INCR指令做原子计数,性能更好 - 如果不需要统计所有接口,可以给要统计的接口加自定义特性,拦截器只统计带特性标记的接口即可
内容的提问来源于stack exchange,提问作者user14688948
相关产品推荐
相关产品推荐

