跳转至

统计接口性能优化指南

问题背景

用户反馈登录成功后强制刷新页面时会出现3-5秒的卡顿现象。经过分析发现,问题出现在 /api/admin/statistics 接口上。

原始问题分析

1. 多个串行数据库查询

原始实现中,统计接口需要执行6个独立的数据库查询: - getTotalLicenses() - 查询总密钥数 - getActiveLicenses() - 查询活跃密钥数
- getExpiredLicenses() - 查询过期密钥数 - getTodayUsage() - 查询今日使用量 - getApplicationStats() - 查询应用统计 - getLicenseLimitInfo() - 查询限制信息

2. 重复的COUNT查询

  • getTotalLicenses()getLicenseLimitInfo() 都执行相同的 SELECT COUNT(*) FROM licenses

3. 复杂的JOIN查询

SELECT a.name, COUNT(l.id) as license_count
FROM applications a
LEFT JOIN licenses l ON a.id = l.application_id
GROUP BY a.id, a.name
ORDER BY license_count DESC

优化解决方案

1. 数据库查询优化

合并重复查询

将多个独立的COUNT查询合并为一个查询:

SELECT 
  (SELECT COUNT(*) FROM licenses) as totalLicenses,
  (SELECT COUNT(*) FROM licenses WHERE status = 'active') as activeLicenses,
  (SELECT COUNT(*) FROM licenses WHERE expires_at < datetime('now') AND expires_at IS NOT NULL) as expiredLicenses,
  (SELECT COUNT(*) FROM license_usage WHERE date(used_at) = date('now')) as todayUsage

并行执行查询

将统计查询分为两个并行执行的查询: 1. getAllStatistics() - 获取基础统计信息 2. getApplicationStats() - 获取应用统计信息

2. 前端加载优化

骨架屏加载

实现骨架屏机制,先显示加载状态,再显示实际数据:

async loadDashboard() {
    // 先显示骨架屏
    this.showDashboardSkeleton();

    try {
        const response = await api.get('/admin/statistics');

        if (response.success) {
            // 隐藏骨架屏,显示实际数据
            this.hideDashboardSkeleton();
            // 渲染数据...
        }
    } catch (error) {
        this.hideDashboardSkeleton();
        utils.handleApiError(error, '加载仪表板失败');
    }
}

用户体验改进

  • 立即显示页面框架,避免白屏
  • 显示加载动画,提供视觉反馈
  • 异步加载数据,不阻塞页面渲染

3. 缓存机制

服务器端缓存

实现30秒的统计结果缓存:

// 统计信息缓存
let statisticsCache = {
  data: null,
  timestamp: null,
  ttl: 30000 // 30秒缓存
};

// 检查缓存是否有效
const now = Date.now();
if (statisticsCache.data && statisticsCache.timestamp && 
    (now - statisticsCache.timestamp) < statisticsCache.ttl) {
  return res.json({
    success: true,
    statistics: statisticsCache.data,
    cached: true
  });
}

缓存策略

  • 缓存时间: 30秒
  • 缓存内容: 完整的统计结果
  • 缓存失效: 时间过期或手动清除
  • 缓存标识: 响应中包含 cached 字段

优化效果对比

性能提升

数据库查询优化

  • 原始: 6个串行查询
  • 优化后: 2个并行查询
  • 提升: 减少67%的查询次数

响应时间对比

  • 原始: 3-5秒
  • 优化后: 100-500ms
  • 提升: 80-90%的性能提升

缓存效果

  • 首次访问: 100-500ms(数据库查询)
  • 缓存访问: 10-50ms(内存读取)
  • 提升: 90%的性能提升

用户体验改进

加载体验

  • 原始: 白屏等待3-5秒
  • 优化后: 立即显示骨架屏,平滑过渡到实际数据

视觉反馈

  • 原始: 无加载指示
  • 优化后: 骨架屏动画,清晰的加载状态

技术实现细节

1. 后端优化

查询合并

const getAllStatistics = () => {
  return new Promise((resolve, reject) => {
    const maxLicenses = parseInt(process.env.MAX_LICENSES) || 200;

    getDatabase().all(`
      SELECT 
        (SELECT COUNT(*) FROM licenses) as totalLicenses,
        (SELECT COUNT(*) FROM licenses WHERE status = 'active') as activeLicenses,
        (SELECT COUNT(*) FROM licenses WHERE expires_at < datetime('now') AND expires_at IS NOT NULL) as expiredLicenses,
        (SELECT COUNT(*) FROM license_usage WHERE date(used_at) = date('now')) as todayUsage
    `, (err, results) => {
      // 处理结果...
    });
  });
};

缓存实现

// 检查缓存
if (statisticsCache.data && statisticsCache.timestamp && 
    (now - statisticsCache.timestamp) < statisticsCache.ttl) {
  return res.json({
    success: true,
    statistics: statisticsCache.data,
    cached: true
  });
}

// 更新缓存
statisticsCache.data = result;
statisticsCache.timestamp = now;

2. 前端优化

骨架屏实现

showDashboardSkeleton() {
  const skeletonHtml = `
    <div class="row">
      <div class="col-md-3 mb-4">
        <div class="card">
          <div class="card-body">
            <div class="placeholder-glow">
              <span class="placeholder col-6"></span>
            </div>
            <h5 class="card-title placeholder-glow">
              <span class="placeholder col-8"></span>
            </h5>
          </div>
        </div>
      </div>
      <!-- 更多骨架屏元素... -->
    </div>
  `;

  const dashboardContent = document.getElementById('dashboardPage');
  if (dashboardContent) {
    dashboardContent.innerHTML = skeletonHtml;
  }
}

异步加载

async loadDashboard() {
  // 先显示骨架屏
  this.showDashboardSkeleton();

  try {
    const response = await api.get('/admin/statistics');

    if (response.success) {
      // 隐藏骨架屏,显示实际数据
      this.hideDashboardSkeleton();
      // 渲染数据...
    }
  } catch (error) {
    this.hideDashboardSkeleton();
    utils.handleApiError(error, '加载仪表板失败');
  }
}

测试验证

性能测试脚本

创建了专门的性能测试脚本 test/test-statistics-performance.js

async function testStatisticsAPI(token) {
  const startTime = Date.now();

  try {
    const response = await axios.get('/api/admin/statistics', {
      headers: { 'Authorization': `Bearer ${token}` }
    });

    const duration = Date.now() - startTime;

    return {
      success: response.data.success,
      duration,
      cached: response.data.cached || false
    };
  } catch (error) {
    return { success: false, duration: Date.now() - startTime };
  }
}

测试结果示例

🚀 开始统计接口性能测试...

1. 获取访问令牌...
✅ Token获取成功

2. 执行性能测试...
   第 1 次测试...
   ✅ 成功 (245ms) [数据库]
   第 2 次测试...
   ✅ 成功 (15ms) [缓存]
   第 3 次测试...
   ✅ 成功 (12ms) [缓存]
   第 4 次测试...
   ✅ 成功 (18ms) [缓存]
   第 5 次测试...
   ✅ 成功 (14ms) [缓存]

3. 性能分析结果:
   总测试次数: 5
   成功次数: 5
   平均响应时间: 60.80ms
   最快响应时间: 12ms
   最慢响应时间: 245ms

   缓存效果分析:
   数据库查询平均时间: 245.00ms
   缓存响应平均时间: 14.75ms
   性能提升: 93.98%

   性能评估:
   🟢 优秀 (平均 < 100ms)

🎉 性能测试完成!

最佳实践

1. 数据库优化

  • 合并重复查询
  • 使用并行查询
  • 添加适当的索引
  • 避免N+1查询问题

2. 缓存策略

  • 合理设置缓存时间
  • 实现缓存失效机制
  • 监控缓存命中率
  • 考虑缓存预热

3. 前端优化

  • 实现骨架屏加载
  • 使用异步加载
  • 提供加载反馈
  • 优化用户体验

4. 监控和维护

  • 定期性能测试
  • 监控响应时间
  • 分析用户反馈
  • 持续优化改进

更新日志

v1.1.67 (2025-08-10)

性能优化

  • ✅ 合并统计接口的多个数据库查询
  • ✅ 实现30秒统计结果缓存
  • ✅ 添加骨架屏加载机制
  • ✅ 优化前端异步加载

技术改进

  • ✅ 减少67%的数据库查询次数
  • ✅ 实现80-90%的性能提升
  • ✅ 添加缓存标识和监控
  • ✅ 改进用户体验

用户体验提升

  • ✅ 消除3-5秒卡顿问题
  • ✅ 实现平滑的加载体验
  • ✅ 提供清晰的加载反馈
  • ✅ 优化页面响应速度

未来计划

  • 🔄 实现更智能的缓存策略
  • 🔄 添加数据库查询性能监控
  • 🔄 支持实时数据更新
  • 🔄 优化大数据量场景