统计接口性能优化指南¶
问题背景¶
用户反馈登录成功后强制刷新页面时会出现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秒卡顿问题
- ✅ 实现平滑的加载体验
- ✅ 提供清晰的加载反馈
- ✅ 优化页面响应速度
未来计划¶
- 🔄 实现更智能的缓存策略
- 🔄 添加数据库查询性能监控
- 🔄 支持实时数据更新
- 🔄 优化大数据量场景